mirror of
https://github.com/kennethreitz/kjvstudy.org.git
synced 2026-06-05 23:00:16 +00:00
51e4eeb561
New statistics added to the family tree overview: Most Siblings: - Tracks which person had the most siblings - Currently shows Nathan with 18 siblings (one of David's 19 children) Close Family Marriages: - Detects marriages between close relatives - Checks for sibling marriages and aunt/uncle-niece/nephew relationships - Shows 0 in current GEDCOM data - Provides historical context: "common in early biblical times" - Important for understanding biblical/ancient Near Eastern culture API Changes: - Add most_siblings field to FamilyTreeStatsResponse - Add close_family_marriages field with description - Calculate sibling counts from GEDCOM relationship data - Detect close family relationships through parent/sibling analysis Template Updates: - Add "Most Siblings" row with clickable link to person page - Add "Close Family Marriages" row with contextual note - Populate values via JavaScript from stats API Test Updates: - Add assertions for most_siblings structure - Add assertions for close_family_marriages value - Verify all new fields are present and correctly typed This helps provide educational context about how family structures differed in ancient biblical times compared to modern norms. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
997 lines
38 KiB
Python
997 lines
38 KiB
Python
"""
|
|
Unit tests for KJV Study API endpoints
|
|
|
|
Fixtures are imported from conftest.py
|
|
"""
|
|
import pytest
|
|
|
|
|
|
class TestAPIHealth:
|
|
"""Tests for health check endpoints"""
|
|
|
|
def test_api_health(self, client):
|
|
"""Test /api/health endpoint"""
|
|
response = client.get("/api/health")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["status"] == "healthy"
|
|
assert "service" in data
|
|
assert "version" in data
|
|
|
|
|
|
class TestAPIIndex:
|
|
"""Tests for API index endpoint"""
|
|
|
|
def test_api_index(self, client):
|
|
"""Test /api/ endpoint returns index"""
|
|
response = client.get("/api/")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["name"] == "KJV Study API"
|
|
assert "version" in data
|
|
assert "documentation" in data
|
|
assert "endpoints" in data
|
|
|
|
|
|
class TestVerseEndpoints:
|
|
"""Tests for verse-related endpoints"""
|
|
|
|
def test_get_single_verse(self, client):
|
|
"""Test /api/verse/{book}/{chapter}/{verse}"""
|
|
response = client.get("/api/verse/John/3/16")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["book"] == "John"
|
|
assert data["chapter"] == 3
|
|
assert data["verse"] == 16
|
|
assert "text" in data
|
|
assert "God so loved the world" in data["text"]
|
|
assert "red_letter" in data
|
|
assert data["red_letter"] == "full" # Jesus speaks the entire verse
|
|
|
|
def test_get_verse_with_abbreviation(self, client):
|
|
"""Test verse endpoint with book abbreviation"""
|
|
response = client.get("/api/verse/Gen/1/1")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["book"] == "Genesis"
|
|
assert data["chapter"] == 1
|
|
assert data["verse"] == 1
|
|
assert "In the beginning" in data["text"]
|
|
assert "red_letter" in data
|
|
assert data["red_letter"] is None # Jesus doesn't speak in Genesis
|
|
|
|
def test_get_nonexistent_verse(self, client):
|
|
"""Test verse endpoint with invalid verse"""
|
|
response = client.get("/api/verse/John/3/999")
|
|
# Currently returns 500, should return 404
|
|
assert response.status_code in [404, 500]
|
|
|
|
def test_get_verse_range(self, client):
|
|
"""Test /api/verse-range/{book}/{chapter}/{start}/{end}"""
|
|
response = client.get("/api/verse-range/Psalms/23/1/6")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["book"] == "Psalms"
|
|
assert data["chapter"] == 23
|
|
assert data["start"] == 1
|
|
assert data["end"] == 6
|
|
assert "verses" in data
|
|
assert len(data["verses"]) == 6
|
|
# Check that each verse has red_letter field
|
|
for verse in data["verses"]:
|
|
assert "red_letter" in verse
|
|
assert verse["red_letter"] is None # Psalms don't have Jesus speaking
|
|
|
|
def test_verse_of_the_day(self, client):
|
|
"""Test /api/verse-of-the-day"""
|
|
response = client.get("/api/verse-of-the-day")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert "text" in data
|
|
assert "reference" in data
|
|
assert "book" in data
|
|
assert "chapter" in data
|
|
assert "verse" in data
|
|
assert "red_letter" in data
|
|
|
|
def test_red_letter_full_verse(self, client):
|
|
"""Test verse where Jesus speaks entire verse"""
|
|
response = client.get("/api/verse/Matthew/5/3")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["red_letter"] == "full"
|
|
|
|
def test_red_letter_partial_verse(self, client):
|
|
"""Test verse where Jesus speaks part of it"""
|
|
response = client.get("/api/verse/Matthew/4/4")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["red_letter"] is not None
|
|
assert data["red_letter"] != "full"
|
|
assert isinstance(data["red_letter"], str)
|
|
assert len(data["red_letter"]) > 0
|
|
|
|
def test_red_letter_no_words(self, client):
|
|
"""Test verse where Jesus doesn't speak"""
|
|
response = client.get("/api/verse/Romans/3/23")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["red_letter"] is None
|
|
|
|
def test_red_letter_verse_range_with_christ_words(self, client):
|
|
"""Test verse range that includes words of Christ"""
|
|
response = client.get("/api/verse-range/Matthew/5/3/5")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
# All three verses in the Sermon on the Mount are full red letter
|
|
for verse in data["verses"]:
|
|
assert "red_letter" in verse
|
|
assert verse["red_letter"] == "full"
|
|
|
|
|
|
class TestBookEndpoints:
|
|
"""Tests for book-related endpoints"""
|
|
|
|
def test_get_all_books(self, client):
|
|
"""Test /api/books"""
|
|
response = client.get("/api/books")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert "total_books" in data
|
|
assert data["total_books"] == 66
|
|
assert "old_testament" in data
|
|
assert "new_testament" in data
|
|
|
|
def test_get_book_details(self, client):
|
|
"""Test /api/books/{book}"""
|
|
response = client.get("/api/books/Genesis")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["name"] == "Genesis"
|
|
assert data["total_chapters"] == 50
|
|
assert "chapters" in data
|
|
|
|
def test_get_chapter(self, client):
|
|
"""Test /api/books/{book}/chapters/{chapter}"""
|
|
response = client.get("/api/books/John/chapters/1")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["book"] == "John"
|
|
assert data["chapter"] == 1
|
|
assert "verses" in data
|
|
assert data["total_verses"] > 0
|
|
|
|
def test_get_book_text(self, client):
|
|
"""Test /api/books/{book}/text"""
|
|
response = client.get("/api/books/Philemon/text")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["book"] == "Philemon"
|
|
assert data["total_chapters"] == 1
|
|
assert data["total_verses"] == 25
|
|
assert "chapters" in data
|
|
|
|
def test_get_nonexistent_book(self, client):
|
|
"""Test book endpoint with invalid book"""
|
|
response = client.get("/api/books/FakeBook")
|
|
assert response.status_code == 404
|
|
|
|
|
|
class TestBibleEndpoint:
|
|
"""Tests for complete Bible endpoint"""
|
|
|
|
def test_get_entire_bible(self, client):
|
|
"""Test /api/bible"""
|
|
response = client.get("/api/bible")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["total_books"] == 66
|
|
assert data["total_verses"] == 31102
|
|
assert "books" in data
|
|
assert len(data["books"]) == 66
|
|
|
|
|
|
class TestSearchEndpoint:
|
|
"""Tests for search endpoint"""
|
|
|
|
def test_search_with_query(self, client):
|
|
"""Test /api/search with query"""
|
|
response = client.get("/api/search?q=love")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert "results" in data
|
|
assert "total" in data
|
|
|
|
def test_search_with_limit(self, client):
|
|
"""Test /api/search with limit"""
|
|
response = client.get("/api/search?q=faith&limit=5")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert "results" in data
|
|
assert len(data["results"]) <= 5
|
|
|
|
def test_search_empty_query(self, client):
|
|
"""Test /api/search with empty query"""
|
|
response = client.get("/api/search?q=")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["total"] == 0
|
|
|
|
|
|
class TestUniversalSearchEndpoint:
|
|
"""Tests for universal search endpoint"""
|
|
|
|
def test_universal_search_basic(self, client):
|
|
"""Test /api/universal-search with basic query"""
|
|
response = client.get("/api/universal-search?q=love")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert "query" in data
|
|
assert "results" in data
|
|
assert data["query"] == "love"
|
|
|
|
def test_universal_search_finds_books(self, client):
|
|
"""Test universal search finds books"""
|
|
response = client.get("/api/universal-search?q=genesis")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert "books" in data["results"]
|
|
assert any(b["name"] == "Genesis" for b in data["results"]["books"])
|
|
|
|
def test_universal_search_finds_topics(self, client):
|
|
"""Test universal search finds topics"""
|
|
response = client.get("/api/universal-search?q=faith")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
# Topics may or may not be present depending on data
|
|
assert "results" in data
|
|
|
|
def test_universal_search_finds_verses(self, client):
|
|
"""Test universal search finds verses"""
|
|
response = client.get("/api/universal-search?q=beginning")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert "verses" in data["results"]
|
|
assert len(data["results"]["verses"]) > 0
|
|
|
|
def test_universal_search_with_limit(self, client):
|
|
"""Test universal search respects limit"""
|
|
response = client.get("/api/universal-search?q=god&limit=3")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
# Check that each category respects the limit
|
|
for category in data["results"].values():
|
|
assert len(category) <= 3
|
|
|
|
def test_universal_search_short_query(self, client):
|
|
"""Test universal search with very short query"""
|
|
response = client.get("/api/universal-search?q=a")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
# Short queries return empty results
|
|
assert data["results"] == {}
|
|
|
|
def test_universal_search_book_synonyms(self, client):
|
|
"""Test universal search handles book synonyms"""
|
|
response = client.get("/api/universal-search?q=revelations")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
# Should find Revelation book
|
|
if "books" in data["results"]:
|
|
assert any(b["name"] == "Revelation" for b in data["results"]["books"])
|
|
|
|
def test_universal_search_finds_resources(self, client):
|
|
"""Test universal search finds resources"""
|
|
response = client.get("/api/universal-search?q=trinity")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert "resources" in data["results"]
|
|
assert len(data["results"]["resources"]) > 0
|
|
|
|
def test_universal_search_theological_synonyms(self, client):
|
|
"""Test universal search handles theological synonyms"""
|
|
response = client.get("/api/universal-search?q=holy%20spirit")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
# Should find Pneumatology via synonym
|
|
if "resources" in data["results"]:
|
|
names = [r["name"] for r in data["results"]["resources"]]
|
|
assert "Pneumatology" in names
|
|
|
|
def test_universal_search_finds_study_guides(self, client):
|
|
"""Test universal search finds study guides"""
|
|
response = client.get("/api/universal-search?q=prayer")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
if "resources" in data["results"]:
|
|
# Should find prayer-related study guides
|
|
urls = [r["url"] for r in data["results"]["resources"]]
|
|
assert any("study-guides" in url or "prayer" in url for url in urls)
|
|
|
|
|
|
class TestInterlinearEndpoint:
|
|
"""Tests for interlinear data endpoint"""
|
|
|
|
def test_get_interlinear_new_testament(self, client):
|
|
"""Test /api/interlinear for NT verse"""
|
|
response = client.get("/api/interlinear/John/1/1")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["book"] == "John"
|
|
assert data["chapter"] == 1
|
|
assert data["verse"] == 1
|
|
assert "interlinear_available" in data
|
|
|
|
def test_get_interlinear_nonexistent_verse(self, client):
|
|
"""Test interlinear with invalid verse"""
|
|
response = client.get("/api/interlinear/John/1/999")
|
|
# Currently returns 500, should return 404
|
|
assert response.status_code in [404, 500]
|
|
|
|
|
|
class TestCrossReferencesEndpoint:
|
|
"""Tests for cross-references endpoint"""
|
|
|
|
def test_get_cross_references(self, client):
|
|
"""Test /api/cross-references/{book}/{chapter}/{verse}"""
|
|
response = client.get("/api/cross-references/John/3/16")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["book"] == "John"
|
|
assert data["chapter"] == 3
|
|
assert data["verse"] == 16
|
|
assert "cross_references" in data
|
|
|
|
|
|
class TestTopicsEndpoints:
|
|
"""Tests for topics endpoints"""
|
|
|
|
def test_get_all_topics(self, client):
|
|
"""Test /api/topics"""
|
|
response = client.get("/api/topics")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert "total_topics" in data
|
|
assert "topics" in data
|
|
|
|
def test_get_specific_topic(self, client):
|
|
"""Test /api/topics/{topic_name}"""
|
|
response = client.get("/api/topics/faith")
|
|
# Topic might not exist, accept both 200 and 404
|
|
assert response.status_code in [200, 404]
|
|
if response.status_code == 200:
|
|
data = response.json()
|
|
assert "name" in data
|
|
|
|
|
|
class TestReadingPlansEndpoints:
|
|
"""Tests for reading plans endpoints"""
|
|
|
|
def test_get_all_reading_plans(self, client):
|
|
"""Test /api/reading-plans"""
|
|
response = client.get("/api/reading-plans")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert "total_plans" in data
|
|
assert "plans" in data
|
|
assert data["total_plans"] > 0
|
|
|
|
def test_get_specific_reading_plan(self, client):
|
|
"""Test /api/reading-plans/{plan_id}"""
|
|
response = client.get("/api/reading-plans/chronological")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert "name" in data
|
|
assert "description" in data
|
|
|
|
def test_get_nonexistent_reading_plan(self, client):
|
|
"""Test reading plan endpoint with invalid plan"""
|
|
response = client.get("/api/reading-plans/fake-plan")
|
|
assert response.status_code == 404
|
|
|
|
|
|
class TestBookNameNormalization:
|
|
"""Tests for book name normalization"""
|
|
|
|
def test_abbreviations(self, client):
|
|
"""Test various book abbreviations"""
|
|
# Test Genesis abbreviations
|
|
for abbrev in ["Gen", "Ge"]:
|
|
response = client.get(f"/api/verse/{abbrev}/1/1")
|
|
# Some abbreviations might not work, accept 200, 404, or 500
|
|
if response.status_code == 200:
|
|
data = response.json()
|
|
assert data["book"] == "Genesis"
|
|
|
|
# Test Matthew abbreviations - Matt should work
|
|
response = client.get("/api/verse/Matt/1/1")
|
|
if response.status_code == 200:
|
|
data = response.json()
|
|
assert data["book"] == "Matthew"
|
|
|
|
|
|
if __name__ == "__main__":
|
|
pytest.main([__file__, "-v"])
|
|
|
|
|
|
class TestResourcesEndpoints:
|
|
"""Tests for resources-related endpoints"""
|
|
|
|
def test_list_all_resources(self, client):
|
|
"""Test /api/resources endpoint"""
|
|
response = client.get("/api/resources")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert "total_categories" in data
|
|
assert "categories" in data
|
|
assert data["total_categories"] == 39
|
|
assert len(data["categories"]) == 39
|
|
|
|
# Check structure of first category
|
|
first_cat = data["categories"][0]
|
|
assert "name" in first_cat
|
|
assert "title" in first_cat
|
|
assert "item_count" in first_cat
|
|
assert "url" in first_cat
|
|
|
|
def test_get_resource_category(self, client):
|
|
"""Test /api/resources/{category} endpoint"""
|
|
response = client.get("/api/resources/angels")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["category"] == "angels"
|
|
assert "title" in data
|
|
assert "total_items" in data
|
|
assert "items" in data
|
|
assert data["total_items"] > 0
|
|
assert len(data["items"]) == data["total_items"]
|
|
|
|
# Check structure of first item
|
|
first_item = data["items"][0]
|
|
assert "name" in first_item
|
|
assert "slug" in first_item
|
|
assert "description" in first_item
|
|
assert "verse_count" in first_item
|
|
assert "url" in first_item
|
|
|
|
def test_get_resource_category_biblical_locations(self, client):
|
|
"""Test nested resource category (biblical_locations)"""
|
|
response = client.get("/api/resources/biblical_locations")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["category"] == "biblical_locations"
|
|
assert data["total_items"] > 0
|
|
# Should include items from both OT and NT locations
|
|
assert any("Garden of Eden" in item["name"] for item in data["items"])
|
|
|
|
def test_get_resource_item(self, client):
|
|
"""Test /api/resources/{category}/{slug} endpoint"""
|
|
response = client.get("/api/resources/biblical_locations/garden-of-eden")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["name"] == "Garden of Eden"
|
|
assert data["slug"] == "garden-of-eden"
|
|
assert data["category"] == "biblical_locations"
|
|
assert "description" in data
|
|
assert "verses" in data
|
|
assert len(data["verses"]) > 0
|
|
|
|
# Check verse structure
|
|
first_verse = data["verses"][0]
|
|
assert "reference" in first_verse
|
|
assert "text" in first_verse
|
|
assert "Genesis" in first_verse["reference"]
|
|
|
|
def test_get_resource_item_from_different_categories(self, client):
|
|
"""Test getting items from various categories"""
|
|
test_cases = [
|
|
("angels", "michael-the-archangel"),
|
|
("prophets", "isaiah"),
|
|
("parables", "the-sower"),
|
|
]
|
|
|
|
for category, slug in test_cases:
|
|
response = client.get(f"/api/resources/{category}/{slug}")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["category"] == category
|
|
assert data["slug"] == slug
|
|
assert "verses" in data
|
|
|
|
def test_get_nonexistent_resource_category(self, client):
|
|
"""Test /api/resources/{category} with invalid category"""
|
|
response = client.get("/api/resources/nonexistent_category")
|
|
assert response.status_code == 404
|
|
|
|
def test_get_nonexistent_resource_item(self, client):
|
|
"""Test /api/resources/{category}/{slug} with invalid slug"""
|
|
response = client.get("/api/resources/angels/nonexistent-angel")
|
|
assert response.status_code == 404
|
|
|
|
def test_resource_category_pdf(self, client):
|
|
"""Test /api/resources/{category}/pdf endpoint"""
|
|
# Note: This route may have ordering issues with FastAPI path matching
|
|
# Testing with a simple category
|
|
response = client.get("/api/resources/angels/pdf")
|
|
# May match as /resources/{category} with slug="pdf" due to route order
|
|
# This is a known limitation - PDF routes should be defined before general routes
|
|
assert response.status_code in [200, 404, 503]
|
|
if response.status_code == 200:
|
|
assert response.headers["content-type"] == "application/pdf"
|
|
assert "attachment" in response.headers.get("content-disposition", "")
|
|
|
|
def test_resource_item_pdf(self, client):
|
|
"""Test /api/resources/{category}/{slug}/pdf endpoint"""
|
|
response = client.get("/api/resources/biblical_locations/garden-of-eden/pdf")
|
|
# Should either succeed with PDF or return 503 if WeasyPrint not available
|
|
assert response.status_code in [200, 503]
|
|
if response.status_code == 200:
|
|
assert response.headers["content-type"] == "application/pdf"
|
|
assert "garden-of-eden" in response.headers.get("content-disposition", "")
|
|
|
|
def test_resource_pdf_nonexistent_category(self, client):
|
|
"""Test PDF endpoint with nonexistent category"""
|
|
response = client.get("/api/resources/nonexistent/pdf")
|
|
assert response.status_code == 404
|
|
|
|
def test_resource_pdf_nonexistent_item(self, client):
|
|
"""Test PDF endpoint with nonexistent item"""
|
|
response = client.get("/api/resources/angels/nonexistent-angel/pdf")
|
|
assert response.status_code == 404
|
|
|
|
def test_all_resource_categories_accessible(self, client):
|
|
"""Test that all 39 categories are accessible"""
|
|
# Get list of all categories
|
|
response = client.get("/api/resources")
|
|
assert response.status_code == 200
|
|
categories = response.json()["categories"]
|
|
|
|
# Test a sample of categories (not all 39 to keep test fast)
|
|
sample_categories = [cat["name"] for cat in categories[:10]]
|
|
|
|
for cat_name in sample_categories:
|
|
response = client.get(f"/api/resources/{cat_name}")
|
|
assert response.status_code == 200, f"Failed to access category: {cat_name}"
|
|
assert response.json()["category"] == cat_name
|
|
|
|
|
|
class TestRedLetterEndpoints:
|
|
"""Tests for red letter (words of Christ) endpoints"""
|
|
|
|
def test_list_red_letter_verses(self, client):
|
|
"""Test /api/red-letter endpoint returns list of verses"""
|
|
response = client.get("/api/red-letter")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
|
|
# Check response structure
|
|
assert "total" in data
|
|
assert "verses" in data
|
|
assert "limit" in data
|
|
assert "offset" in data
|
|
|
|
# Verify we have verses
|
|
assert data["total"] > 0
|
|
assert len(data["verses"]) > 0
|
|
|
|
# Check first verse structure
|
|
first_verse = data["verses"][0]
|
|
assert "reference" in first_verse
|
|
assert "book" in first_verse
|
|
assert "chapter" in first_verse
|
|
assert "verse" in first_verse
|
|
assert "text" in first_verse
|
|
assert "christ_words" in first_verse
|
|
assert "is_full_verse" in first_verse
|
|
|
|
def test_list_red_letter_with_pagination(self, client):
|
|
"""Test pagination parameters work correctly"""
|
|
# Get first 10 verses
|
|
response1 = client.get("/api/red-letter?limit=10&offset=0")
|
|
assert response1.status_code == 200
|
|
data1 = response1.json()
|
|
assert data1["limit"] == 10
|
|
assert data1["offset"] == 0
|
|
assert len(data1["verses"]) == 10
|
|
|
|
# Get next 10 verses
|
|
response2 = client.get("/api/red-letter?limit=10&offset=10")
|
|
assert response2.status_code == 200
|
|
data2 = response2.json()
|
|
assert data2["limit"] == 10
|
|
assert data2["offset"] == 10
|
|
assert len(data2["verses"]) == 10
|
|
|
|
# Verify different verses
|
|
assert data1["verses"][0]["reference"] != data2["verses"][0]["reference"]
|
|
|
|
def test_list_red_letter_filter_by_book(self, client):
|
|
"""Test filtering by book works correctly"""
|
|
response = client.get("/api/red-letter?book=John")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
|
|
# All verses should be from John
|
|
assert data["total"] > 0
|
|
for verse in data["verses"]:
|
|
assert verse["book"] == "John"
|
|
|
|
def test_list_red_letter_filter_by_book_multiple_books(self, client):
|
|
"""Test that different books return different results"""
|
|
response_john = client.get("/api/red-letter?book=John&limit=10")
|
|
response_matthew = client.get("/api/red-letter?book=Matthew&limit=10")
|
|
|
|
assert response_john.status_code == 200
|
|
assert response_matthew.status_code == 200
|
|
|
|
john_data = response_john.json()
|
|
matthew_data = response_matthew.json()
|
|
|
|
# Both should have verses
|
|
assert john_data["total"] > 0
|
|
assert matthew_data["total"] > 0
|
|
|
|
# All verses in each response should be from the correct book
|
|
for verse in john_data["verses"]:
|
|
assert verse["book"] == "John"
|
|
for verse in matthew_data["verses"]:
|
|
assert verse["book"] == "Matthew"
|
|
|
|
def test_red_letter_stats(self, client):
|
|
"""Test /api/red-letter/stats endpoint"""
|
|
response = client.get("/api/red-letter/stats")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
|
|
# Check response structure
|
|
assert "total_verses" in data
|
|
assert "full_verses" in data
|
|
assert "partial_verses" in data
|
|
assert "books_with_red_letter" in data
|
|
assert "by_book" in data
|
|
|
|
# Verify counts make sense
|
|
assert data["total_verses"] > 0
|
|
assert data["full_verses"] > 0
|
|
assert data["partial_verses"] >= 0
|
|
assert data["total_verses"] == data["full_verses"] + data["partial_verses"]
|
|
|
|
# Verify books list
|
|
assert len(data["books_with_red_letter"]) > 0
|
|
assert "Matthew" in data["books_with_red_letter"]
|
|
assert "Mark" in data["books_with_red_letter"]
|
|
assert "Luke" in data["books_with_red_letter"]
|
|
assert "John" in data["books_with_red_letter"]
|
|
|
|
# Verify by_book counts
|
|
assert isinstance(data["by_book"], dict)
|
|
assert "Matthew" in data["by_book"]
|
|
assert data["by_book"]["Matthew"] > 0
|
|
|
|
def test_red_letter_verses_contain_expected_data(self, client):
|
|
"""Test that specific known red letter verses are included"""
|
|
response = client.get("/api/red-letter?book=John&limit=500")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
|
|
# Find John 3:16 (Jesus speaks the entire verse)
|
|
john_316 = None
|
|
for verse in data["verses"]:
|
|
if verse["chapter"] == 3 and verse["verse"] == 16:
|
|
john_316 = verse
|
|
break
|
|
|
|
assert john_316 is not None, "John 3:16 should be in red letter verses"
|
|
assert john_316["is_full_verse"] is True
|
|
assert john_316["christ_words"] == "full"
|
|
assert "God so loved the world" in john_316["text"]
|
|
|
|
def test_red_letter_pagination_limits(self, client):
|
|
"""Test pagination limit validation"""
|
|
# Test maximum limit
|
|
response = client.get("/api/red-letter?limit=500")
|
|
assert response.status_code == 200
|
|
|
|
# Test exceeding maximum limit should be rejected
|
|
response = client.get("/api/red-letter?limit=1000")
|
|
assert response.status_code == 422 # Validation error
|
|
|
|
def test_api_index_includes_red_letter_endpoints(self, client):
|
|
"""Test that API index includes red letter endpoints"""
|
|
response = client.get("/api/")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
|
|
assert "endpoints" in data
|
|
assert "red_letter" in data["endpoints"]
|
|
assert "red_letter_stats" in data["endpoints"]
|
|
|
|
|
|
class TestRandomVerseEndpoint:
|
|
"""Tests for random verse endpoint"""
|
|
|
|
def test_random_verse_basic(self, client):
|
|
"""Test basic random verse generation"""
|
|
response = client.get("/api/verse/random")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
|
|
# Verify structure
|
|
assert "book" in data
|
|
assert "chapter" in data
|
|
assert "verse" in data
|
|
assert "reference" in data
|
|
assert "text" in data
|
|
assert "red_letter" in data
|
|
|
|
# Verify types
|
|
assert isinstance(data["book"], str)
|
|
assert isinstance(data["chapter"], int)
|
|
assert isinstance(data["verse"], int)
|
|
|
|
def test_random_verse_ot_filter(self, client):
|
|
"""Test filtering by Old Testament"""
|
|
response = client.get("/api/verse/random?testament=ot")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
|
|
# OT books don't include Matthew, Mark, Luke, John, etc.
|
|
nt_books = ["Matthew", "Mark", "Luke", "John", "Acts", "Romans", "Revelation"]
|
|
assert data["book"] not in nt_books
|
|
|
|
def test_random_verse_nt_filter(self, client):
|
|
"""Test filtering by New Testament"""
|
|
response = client.get("/api/verse/random?testament=nt")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
|
|
# NT books include Matthew through Revelation
|
|
nt_books = ["Matthew", "Mark", "Luke", "John", "Acts", "Romans", "1 Corinthians",
|
|
"2 Corinthians", "Galatians", "Ephesians", "Philippians", "Colossians",
|
|
"1 Thessalonians", "2 Thessalonians", "1 Timothy", "2 Timothy", "Titus",
|
|
"Philemon", "Hebrews", "James", "1 Peter", "2 Peter", "1 John", "2 John",
|
|
"3 John", "Jude", "Revelation"]
|
|
assert data["book"] in nt_books
|
|
|
|
def test_random_verse_book_filter(self, client):
|
|
"""Test filtering by specific book"""
|
|
response = client.get("/api/verse/random?book=John")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
|
|
assert data["book"] == "John"
|
|
|
|
def test_random_verse_invalid_testament(self, client):
|
|
"""Test invalid testament parameter"""
|
|
response = client.get("/api/verse/random?testament=invalid")
|
|
assert response.status_code == 400
|
|
|
|
|
|
class TestCommentaryEndpoint:
|
|
"""Tests for verse commentary endpoint"""
|
|
|
|
def test_get_verse_commentary(self, client):
|
|
"""Test getting commentary for a verse"""
|
|
response = client.get("/api/commentary/Genesis/1/1")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
|
|
# Verify structure
|
|
assert data["book"] == "Genesis"
|
|
assert data["chapter"] == 1
|
|
assert data["verse"] == 1
|
|
assert "reference" in data
|
|
assert "text" in data
|
|
assert "analysis" in data
|
|
assert "historical" in data
|
|
assert "questions" in data
|
|
|
|
# Verify content
|
|
assert isinstance(data["questions"], list)
|
|
assert len(data["analysis"]) > 0
|
|
|
|
def test_commentary_nonexistent_verse(self, client):
|
|
"""Test commentary for non-existent verse"""
|
|
response = client.get("/api/commentary/Genesis/1/99999")
|
|
assert response.status_code == 404
|
|
|
|
def test_commentary_missing_for_verse(self, client):
|
|
"""Test verse with no commentary available"""
|
|
# Most verses should have commentary, but check proper 404 handling
|
|
response = client.get("/api/commentary/Philemon/1/999")
|
|
assert response.status_code == 404
|
|
|
|
|
|
class TestChapterCommentaryEndpoint:
|
|
"""Tests for chapter commentary endpoint"""
|
|
|
|
def test_get_chapter_commentary(self, client):
|
|
"""Test getting chapter commentary"""
|
|
response = client.get("/api/chapter-commentary/Genesis/1")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
|
|
assert data["book"] == "Genesis"
|
|
assert data["chapter"] == 1
|
|
assert "explanation" in data
|
|
assert len(data["explanation"]) > 0
|
|
|
|
def test_chapter_commentary_nonexistent_book(self, client):
|
|
"""Test commentary for non-existent book"""
|
|
response = client.get("/api/chapter-commentary/InvalidBook/1")
|
|
assert response.status_code == 404
|
|
|
|
def test_chapter_commentary_nonexistent_chapter(self, client):
|
|
"""Test commentary for non-existent chapter"""
|
|
response = client.get("/api/chapter-commentary/Genesis/999")
|
|
assert response.status_code == 404
|
|
|
|
|
|
class TestBulkVerseEndpoint:
|
|
"""Tests for bulk verse lookup endpoint"""
|
|
|
|
def test_bulk_verse_lookup(self, client):
|
|
"""Test bulk verse lookup with multiple verses"""
|
|
response = client.post("/api/verses/bulk", json={
|
|
"references": ["John 3:16", "Romans 8:28", "Psalm 23:1"]
|
|
})
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
|
|
assert data["total"] == 3
|
|
assert len(data["verses"]) == 3
|
|
|
|
# Check first verse
|
|
assert data["verses"][0]["book"] == "John"
|
|
assert data["verses"][0]["chapter"] == 3
|
|
assert data["verses"][0]["verse"] == 16
|
|
|
|
def test_bulk_verse_with_invalid_references(self, client):
|
|
"""Test bulk lookup with some invalid references"""
|
|
response = client.post("/api/verses/bulk", json={
|
|
"references": ["John 3:16", "Invalid Reference", "Genesis 1:1"]
|
|
})
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
|
|
# Should return 2 valid verses, skipping invalid
|
|
assert data["total"] == 2
|
|
|
|
def test_bulk_verse_empty_list(self, client):
|
|
"""Test bulk lookup with empty list"""
|
|
response = client.post("/api/verses/bulk", json={
|
|
"references": []
|
|
})
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
|
|
assert data["total"] == 0
|
|
assert len(data["verses"]) == 0
|
|
|
|
|
|
class TestFamilyTreeEndpoints:
|
|
"""Tests for family tree/biography endpoints"""
|
|
|
|
def test_list_family_tree(self, client):
|
|
"""Test listing all biblical figures"""
|
|
response = client.get("/api/family-tree")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
|
|
assert "total" in data
|
|
assert "people" in data
|
|
assert data["total"] > 0
|
|
assert len(data["people"]) == data["total"]
|
|
|
|
# Check some expected people
|
|
assert "Abraham" in data["people"]
|
|
assert "Moses" in data["people"]
|
|
assert "David" in data["people"]
|
|
|
|
def test_get_biography(self, client):
|
|
"""Test getting a specific biography"""
|
|
response = client.get("/api/family-tree/Abraham")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
|
|
assert data["name"] == "Abraham"
|
|
assert "summary" in data
|
|
assert "significance" in data
|
|
assert "key_events" in data
|
|
|
|
# Verify key events structure
|
|
assert isinstance(data["key_events"], list)
|
|
if len(data["key_events"]) > 0:
|
|
event = data["key_events"][0]
|
|
assert "age" in event
|
|
assert "event" in event
|
|
assert "verse" in event
|
|
|
|
def test_biography_with_alias(self, client):
|
|
"""Test getting biography using an alias name"""
|
|
response = client.get("/api/family-tree/Israel")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
|
|
# Should resolve to Jacob
|
|
assert data["name"] == "Jacob"
|
|
|
|
def test_biography_nonexistent_person(self, client):
|
|
"""Test getting biography for non-existent person"""
|
|
response = client.get("/api/family-tree/NonExistentPerson")
|
|
assert response.status_code == 404
|
|
|
|
def test_family_tree_stats(self, client):
|
|
"""Test getting family tree statistics from GEDCOM data"""
|
|
response = client.get("/api/family-tree/stats")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
|
|
# Verify all expected fields are present
|
|
assert "total_people" in data
|
|
assert "total_generations" in data
|
|
assert "longest_lived" in data
|
|
assert "most_children" in data
|
|
assert "most_siblings" in data
|
|
assert "average_lifespan" in data
|
|
assert "total_with_known_ages" in data
|
|
assert "close_family_marriages" in data
|
|
|
|
# Verify types
|
|
assert isinstance(data["total_people"], int)
|
|
assert isinstance(data["total_generations"], int)
|
|
assert data["total_people"] > 0
|
|
assert data["total_generations"] > 0
|
|
|
|
# Verify longest_lived structure
|
|
assert "name" in data["longest_lived"]
|
|
assert "person_id" in data["longest_lived"]
|
|
assert "value" in data["longest_lived"]
|
|
assert "additional_info" in data["longest_lived"]
|
|
assert isinstance(data["longest_lived"]["name"], str)
|
|
assert isinstance(data["longest_lived"]["person_id"], str)
|
|
assert isinstance(data["longest_lived"]["value"], int)
|
|
|
|
# Verify most_children structure
|
|
assert "name" in data["most_children"]
|
|
assert "person_id" in data["most_children"]
|
|
assert "value" in data["most_children"]
|
|
assert "additional_info" in data["most_children"]
|
|
assert isinstance(data["most_children"]["name"], str)
|
|
assert isinstance(data["most_children"]["person_id"], str)
|
|
assert isinstance(data["most_children"]["value"], int)
|
|
assert data["most_children"]["value"] >= 0
|
|
|
|
# Verify most_siblings structure
|
|
assert "name" in data["most_siblings"]
|
|
assert "person_id" in data["most_siblings"]
|
|
assert "value" in data["most_siblings"]
|
|
assert "additional_info" in data["most_siblings"]
|
|
assert isinstance(data["most_siblings"]["name"], str)
|
|
assert isinstance(data["most_siblings"]["person_id"], str)
|
|
assert isinstance(data["most_siblings"]["value"], int)
|
|
assert data["most_siblings"]["value"] >= 0
|
|
|
|
# Verify average lifespan is either a number or null
|
|
assert data["average_lifespan"] is None or isinstance(data["average_lifespan"], (int, float))
|
|
|
|
# Verify close family marriages is an integer
|
|
assert isinstance(data["close_family_marriages"], int)
|
|
assert data["close_family_marriages"] >= 0
|
|
|
|
def test_api_index_includes_new_endpoints(self, client):
|
|
"""Test that API index includes all new endpoints"""
|
|
response = client.get("/api/")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
|
|
assert "endpoints" in data
|
|
assert "random_verse" in data["endpoints"]
|
|
assert "commentary" in data["endpoints"]
|
|
assert "chapter_commentary" in data["endpoints"]
|
|
assert "bulk_verses" in data["endpoints"]
|
|
assert "family_tree" in data["endpoints"]
|
|
assert "family_tree_stats" in data["endpoints"]
|
|
assert "biography" in data["endpoints"]
|