""" 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") assert response.status_code == 404 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") assert response.status_code == 404 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"] class TestStatsEndpoint: """Tests for the /api/stats endpoint""" def test_stats_endpoint_returns_200(self, client): """Test /api/stats returns 200""" response = client.get("/api/stats") assert response.status_code == 200 def test_stats_has_bible_section(self, client): """Test stats includes bible statistics""" response = client.get("/api/stats") data = response.json() assert "bible" in data assert data["bible"]["total_verses"] == 31102 assert data["bible"]["total_books"] == 66 assert data["bible"]["ot_books"] == 39 assert data["bible"]["nt_books"] == 27 def test_stats_has_commentary_section(self, client): """Test stats includes commentary statistics""" response = client.get("/api/stats") data = response.json() assert "commentary" in data assert "files" in data["commentary"] assert "verses_covered" in data["commentary"] assert "coverage_percent" in data["commentary"] def test_stats_has_cross_references_section(self, client): """Test stats includes cross-references statistics""" response = client.get("/api/stats") data = response.json() assert "cross_references" in data assert "total_references" in data["cross_references"] assert "coverage_percent" in data["cross_references"] def test_stats_has_red_letter_section(self, client): """Test stats includes red letter statistics""" response = client.get("/api/stats") data = response.json() assert "red_letter" in data assert "total_verses" in data["red_letter"] assert "percent_of_bible" in data["red_letter"] def test_stats_has_study_resources_section(self, client): """Test stats includes study resources statistics""" response = client.get("/api/stats") data = response.json() assert "study_resources" in data assert "study_guides" in data["study_resources"] assert "topics" in data["study_resources"] assert "biographies" in data["study_resources"] def test_stats_has_language_tools_section(self, client): """Test stats includes language tools statistics""" response = client.get("/api/stats") data = response.json() assert "language_tools" in data assert "hebrew_entries" in data["language_tools"] assert "greek_entries" in data["language_tools"] assert "total_strongs" in data["language_tools"] def test_stats_has_data_section(self, client): """Test stats includes data statistics""" response = client.get("/api/stats") data = response.json() assert "data" in data assert "total_json_files" in data["data"] assert "total_size_mb" in data["data"]