""" Tests to improve coverage for low-coverage modules. Targets: - routes/reading_plans.py (32% -> higher) - routes/stories.py (40% -> higher) - routes/bible.py (45% -> higher) - utils/search_index.py (48% -> higher) - interlinear_loader.py (58% -> higher) """ import pytest from fastapi.testclient import TestClient class TestReadingPlansRoutes: """Tests for reading plans web routes.""" def test_reading_plans_index(self, client): """Test reading plans index page.""" response = client.get("/reading-plans") assert response.status_code == 200 assert "Reading Plans" in response.text def test_reading_plan_detail_valid(self, client): """Test valid reading plan detail page.""" # First get the list to find valid plan IDs response = client.get("/api/reading-plans") assert response.status_code == 200 data = response.json() plans = data.get("plans", []) if plans: # Test with first available plan plan_id = plans[0]["id"] response = client.get(f"/reading-plans/{plan_id}") assert response.status_code == 200 assert plans[0]["name"] in response.text def test_reading_plan_detail_invalid(self, client): """Test invalid reading plan returns 404.""" response = client.get("/reading-plans/nonexistent-plan-xyz") assert response.status_code == 404 def test_reading_plan_pdf_unavailable(self, client): """Test PDF endpoint when WeasyPrint may not be available.""" # Get valid plan first response = client.get("/api/reading-plans") data = response.json() plans = data.get("plans", []) if plans: plan_id = plans[0]["id"] response = client.get(f"/reading-plans/{plan_id}/pdf") # Either 503 (WeasyPrint unavailable) or 200 (PDF generated) assert response.status_code in [200, 503] def test_reading_plan_pdf_invalid_plan(self, client): """Test PDF for invalid plan.""" response = client.get("/reading-plans/nonexistent-plan/pdf") # Either 404 (plan not found) or 503 (WeasyPrint unavailable) assert response.status_code in [404, 503] class TestStoriesRoutes: """Tests for Bible stories web routes.""" def test_stories_index(self, client): """Test stories index page.""" response = client.get("/stories") assert response.status_code == 200 assert "Stories" in response.text or "Bible Stories" in response.text def test_stories_kids_index(self, client): """Test kids stories index page.""" response = client.get("/stories/kids") assert response.status_code == 200 assert "Kids" in response.text or "Stories" in response.text def test_story_detail_creation(self, client): """Test story detail page for a known story.""" # Try common story slugs story_slugs = ["creation", "noah-ark", "david-goliath", "exodus"] for slug in story_slugs: response = client.get(f"/stories/{slug}") if response.status_code == 200: # Found a valid story assert "story" in response.text.lower() or response.status_code == 200 break def test_story_detail_invalid(self, client): """Test invalid story returns 404.""" response = client.get("/stories/nonexistent-story-xyz123") assert response.status_code == 404 def test_story_kids_version(self, client): """Test kids version of a story.""" story_slugs = ["creation", "noah-ark", "david-goliath"] for slug in story_slugs: response = client.get(f"/stories/{slug}/kids") # Either 200 (kids version exists) or 404 (no kids version) if response.status_code == 200: break def test_story_kids_invalid(self, client): """Test kids version of invalid story.""" response = client.get("/stories/nonexistent-story/kids") assert response.status_code == 404 def test_story_pdf(self, client): """Test story PDF generation.""" story_slugs = ["creation", "noah-ark"] for slug in story_slugs: response = client.get(f"/stories/{slug}/pdf") # Either 503 (WeasyPrint unavailable), 200 (PDF), or 404 (story not found) if response.status_code in [200, 503]: break def test_story_kids_pdf(self, client): """Test kids story PDF generation.""" response = client.get("/stories/creation/kids/pdf") # Either 503 (WeasyPrint unavailable), 200 (PDF), or 404 (not found) assert response.status_code in [200, 404, 503] class TestBibleRoutes: """Tests for Bible book/chapter/verse web routes.""" def test_book_page_valid(self, client): """Test valid book page.""" response = client.get("/book/Genesis") assert response.status_code == 200 assert "Genesis" in response.text def test_book_page_psalms(self, client): """Test Psalms book page (longest book).""" response = client.get("/book/Psalms") assert response.status_code == 200 assert "Psalms" in response.text def test_book_page_invalid(self, client): """Test invalid book returns 404.""" response = client.get("/book/NotABook") assert response.status_code == 404 def test_book_page_abbreviation_redirect(self, client): """Test book abbreviation redirects to canonical name.""" response = client.get("/book/Gen", follow_redirects=False) assert response.status_code == 301 assert "/book/Genesis" in response.headers.get("location", "") def test_chapter_page_valid(self, client): """Test valid chapter page.""" response = client.get("/book/Genesis/chapter/1") assert response.status_code == 200 assert "Genesis" in response.text assert "Chapter" in response.text or "1" in response.text def test_chapter_page_invalid_chapter(self, client): """Test invalid chapter number.""" response = client.get("/book/Genesis/chapter/999") assert response.status_code == 404 def test_chapter_page_invalid_book(self, client): """Test chapter with invalid book.""" response = client.get("/book/NotABook/chapter/1") assert response.status_code == 404 def test_chapter_abbreviation_redirect(self, client): """Test chapter with book abbreviation redirects.""" response = client.get("/book/Gen/chapter/1", follow_redirects=False) assert response.status_code == 301 def test_chapter_legacy_url_redirect(self, client): """Test legacy chapter URL format redirects.""" response = client.get("/book/Genesis/1", follow_redirects=False) assert response.status_code == 301 assert "chapter" in response.headers.get("location", "") def test_verse_page_valid(self, client): """Test valid verse page.""" response = client.get("/book/John/chapter/3/verse/16") assert response.status_code == 200 assert "John" in response.text def test_verse_page_genesis_1_1(self, client): """Test Genesis 1:1 verse page.""" response = client.get("/book/Genesis/chapter/1/verse/1") assert response.status_code == 200 assert "beginning" in response.text.lower() def test_verse_page_invalid_verse(self, client): """Test invalid verse number.""" response = client.get("/book/Genesis/chapter/1/verse/999") assert response.status_code == 404 def test_verse_page_invalid_chapter(self, client): """Test verse with invalid chapter.""" response = client.get("/book/Genesis/chapter/999/verse/1") assert response.status_code == 404 def test_verse_abbreviation_redirect(self, client): """Test verse with book abbreviation redirects.""" response = client.get("/book/Gen/chapter/1/verse/1", follow_redirects=False) assert response.status_code == 301 def test_interlinear_chapter_valid(self, client): """Test interlinear chapter view.""" response = client.get("/book/Genesis/chapter/1/interlinear") assert response.status_code == 200 assert "Interlinear" in response.text or "Hebrew" in response.text def test_interlinear_chapter_nt(self, client): """Test interlinear for New Testament (Greek).""" response = client.get("/book/John/chapter/1/interlinear") assert response.status_code == 200 def test_interlinear_invalid_book(self, client): """Test interlinear with invalid book.""" response = client.get("/book/NotABook/chapter/1/interlinear") assert response.status_code == 404 def test_interlinear_invalid_chapter(self, client): """Test interlinear with invalid chapter.""" response = client.get("/book/Genesis/chapter/999/interlinear") assert response.status_code == 404 def test_interlinear_abbreviation_redirect(self, client): """Test interlinear with abbreviation redirects.""" response = client.get("/book/Gen/chapter/1/interlinear", follow_redirects=False) assert response.status_code == 301 def test_book_commentary_redirect(self, client): """Test old book commentary URL redirects.""" response = client.get("/book/Genesis/commentary", follow_redirects=False) assert response.status_code == 301 assert "/book/Genesis" in response.headers.get("location", "") def test_book_pdf(self, client): """Test book PDF generation.""" response = client.get("/book/Philemon/pdf") # Either 503 (WeasyPrint unavailable) or 200 (PDF generated) assert response.status_code in [200, 503] def test_book_pdf_invalid(self, client): """Test PDF for invalid book.""" response = client.get("/book/NotABook/pdf") assert response.status_code in [404, 503] def test_book_pdf_abbreviation_redirect(self, client): """Test book PDF with abbreviation redirects.""" response = client.get("/book/Gen/pdf", follow_redirects=False) # Either redirect (301) or WeasyPrint unavailable (503) assert response.status_code in [301, 503] def test_chapter_pdf(self, client): """Test chapter PDF generation.""" response = client.get("/book/Philemon/chapter/1/pdf") assert response.status_code in [200, 503] def test_chapter_pdf_invalid(self, client): """Test chapter PDF for invalid book/chapter.""" response = client.get("/book/NotABook/chapter/1/pdf") assert response.status_code in [404, 503] def test_verse_pdf(self, client): """Test verse PDF generation.""" response = client.get("/book/John/chapter/3/verse/16/pdf") assert response.status_code in [200, 503] def test_verse_pdf_invalid(self, client): """Test verse PDF for invalid verse.""" response = client.get("/book/John/chapter/3/verse/999/pdf") assert response.status_code in [404, 503] def test_interlinear_pdf(self, client): """Test interlinear chapter PDF.""" response = client.get("/book/Philemon/chapter/1/interlinear/pdf") assert response.status_code in [200, 503] class TestSearchIndex: """Tests for search index functionality.""" def test_search_api_basic(self, client): """Test basic search API.""" response = client.get("/api/search?q=love") assert response.status_code == 200 data = response.json() assert isinstance(data, dict) results = data.get("results", []) if results: assert "text" in results[0] assert "reference" in results[0] def test_search_api_with_limit(self, client): """Test search API with limit.""" response = client.get("/api/search?q=God&limit=5") assert response.status_code == 200 data = response.json() results = data.get("results", []) assert len(results) <= 5 def test_search_api_with_book_filter(self, client): """Test search API with book filter.""" response = client.get("/api/search?q=Jesus&book=John") assert response.status_code == 200 data = response.json() # Book filter may or may not be implemented - just check API works assert "results" in data or isinstance(data, dict) def test_search_api_old_testament(self, client): """Test search API with Old Testament filter.""" response = client.get("/api/search?q=LORD&testament=old") assert response.status_code == 200 data = response.json() # Testament filter may or may not be implemented - just check API works assert "results" in data or isinstance(data, dict) def test_search_api_new_testament(self, client): """Test search API with New Testament filter.""" response = client.get("/api/search?q=Jesus&testament=new") assert response.status_code == 200 data = response.json() # Testament filter may or may not be implemented - just check API works assert "results" in data or isinstance(data, dict) def test_search_api_empty_query(self, client): """Test search API with empty query.""" response = client.get("/api/search?q=") assert response.status_code == 200 # Should return empty or handle gracefully def test_search_api_special_characters(self, client): """Test search API with special characters.""" response = client.get("/api/search?q=God's") assert response.status_code == 200 def test_search_api_multiple_words(self, client): """Test search API with multiple words.""" response = client.get("/api/search?q=love one another") assert response.status_code == 200 data = response.json() assert isinstance(data, dict) def test_search_web_page(self, client): """Test search web page.""" response = client.get("/search?q=faith") assert response.status_code == 200 assert "Search" in response.text or "Results" in response.text class TestInterlinearLoader: """Tests for interlinear data loading.""" def test_interlinear_api_genesis(self, client): """Test interlinear API for Genesis (Hebrew).""" response = client.get("/api/interlinear/Genesis/1/1") assert response.status_code == 200 data = response.json() # Should have words with Hebrew data if data.get("words"): word = data["words"][0] # Check for expected fields assert "english" in word or "transliteration" in word or "original" in word def test_interlinear_api_john(self, client): """Test interlinear API for John (Greek).""" response = client.get("/api/interlinear/John/1/1") assert response.status_code == 200 data = response.json() if data.get("words"): assert len(data["words"]) > 0 def test_interlinear_api_psalms(self, client): """Test interlinear API for Psalms.""" response = client.get("/api/interlinear/Psalms/23/1") assert response.status_code == 200 def test_interlinear_api_invalid_book(self, client): """Test interlinear API with invalid book.""" response = client.get("/api/interlinear/NotABook/1/1") assert response.status_code == 404 def test_interlinear_api_invalid_chapter(self, client): """Test interlinear API with invalid chapter.""" response = client.get("/api/interlinear/Genesis/999/1") assert response.status_code == 404 def test_interlinear_api_invalid_verse(self, client): """Test interlinear API with invalid verse.""" response = client.get("/api/interlinear/Genesis/1/999") assert response.status_code == 404 def test_interlinear_api_abbreviation(self, client): """Test interlinear API with book abbreviation.""" response = client.get("/api/interlinear/Gen/1/1") assert response.status_code == 200 class TestReadingPlanHelpers: """Tests for reading plan helper functions.""" def test_reading_plan_api_list(self, client): """Test reading plans API list.""" response = client.get("/api/reading-plans") assert response.status_code == 200 data = response.json() assert isinstance(data, dict) plans = data.get("plans", []) if plans: assert "id" in plans[0] assert "name" in plans[0] def test_reading_plan_api_detail(self, client): """Test reading plan API detail.""" response = client.get("/api/reading-plans") data = response.json() plans = data.get("plans", []) if plans: plan_id = plans[0]["id"] response = client.get(f"/api/reading-plans/{plan_id}") assert response.status_code == 200 plan = response.json() assert "name" in plan assert "days" in plan or "sample_days" in plan def test_reading_plan_api_invalid(self, client): """Test reading plan API with invalid ID.""" response = client.get("/api/reading-plans/nonexistent-plan") assert response.status_code == 404 def test_reading_plan_day_text_api(self, client): """Test reading plan day text API.""" # Get a valid plan first response = client.get("/api/reading-plans") data = response.json() plans = data.get("plans", []) if plans: plan_id = plans[0]["id"] # Try to get day 1 text response = client.get(f"/api/reading-plans/{plan_id}/days/1/text") # Should either succeed or return appropriate error assert response.status_code in [200, 404] class TestStoriesModule: """Tests for stories data module.""" def test_stories_api_list(self, client): """Test stories API returns categories.""" response = client.get("/api/stories") # If endpoint exists if response.status_code == 200: data = response.json() assert isinstance(data, (list, dict)) def test_stories_api_story_detail(self, client): """Test story detail API.""" story_slugs = ["creation", "noah-ark", "david-goliath"] for slug in story_slugs: response = client.get(f"/api/stories/{slug}") if response.status_code == 200: story = response.json() assert "title" in story or "slug" in story break class TestCrossReferences: """Tests for cross-reference functionality.""" def test_cross_references_api(self, client): """Test cross-references API.""" response = client.get("/api/cross-references/John/3/16") assert response.status_code == 200 data = response.json() # Could be a list or dict with references assert isinstance(data, (list, dict)) def test_cross_references_genesis(self, client): """Test cross-references for Genesis 1:1.""" response = client.get("/api/cross-references/Genesis/1/1") assert response.status_code == 200 def test_cross_references_invalid(self, client): """Test cross-references for invalid verse.""" response = client.get("/api/cross-references/NotABook/1/1") assert response.status_code == 404 class TestTopicsRoutes: """Tests for topics routes.""" def test_topics_index(self, client): """Test topics index page.""" response = client.get("/topics") assert response.status_code == 200 assert "Topics" in response.text or "topic" in response.text.lower() def test_topics_api(self, client): """Test topics API.""" response = client.get("/api/topics") assert response.status_code == 200 data = response.json() # API returns dict with total_topics and topics keys assert isinstance(data, dict) topics = data.get("topics", []) assert isinstance(topics, list) def test_topic_detail(self, client): """Test topic detail page.""" # Get topics list first response = client.get("/api/topics") data = response.json() topics = data.get("topics", []) if topics: topic_slug = topics[0].get("slug") or topics[0].get("name", "").lower().replace(" ", "-") response = client.get(f"/topics/{topic_slug}") # Should either work or return 404 for invalid slug format assert response.status_code in [200, 404] class TestAdditionalEdgeCases: """Additional edge case tests for improved coverage.""" def test_book_with_number_prefix(self, client): """Test books with number prefixes.""" numbered_books = ["1 Samuel", "2 Samuel", "1 Kings", "2 Kings", "1 Chronicles", "2 Chronicles", "1 John", "2 John", "3 John"] for book in numbered_books: response = client.get(f"/book/{book}") assert response.status_code == 200, f"Failed for {book}" def test_song_of_solomon(self, client): """Test Song of Solomon (multi-word book name).""" response = client.get("/book/Song of Solomon") assert response.status_code == 200 def test_philemon_full_book(self, client): """Test single-chapter book (Philemon).""" response = client.get("/book/Philemon") assert response.status_code == 200 # Should show chapter 1 link response = client.get("/book/Philemon/chapter/1") assert response.status_code == 200 def test_obadiah_single_chapter(self, client): """Test Obadiah (single chapter book).""" response = client.get("/book/Obadiah/chapter/1") assert response.status_code == 200 def test_jude_single_chapter(self, client): """Test Jude (single chapter book).""" response = client.get("/book/Jude/chapter/1") assert response.status_code == 200 def test_psalms_119_longest_chapter(self, client): """Test Psalm 119 (longest chapter).""" response = client.get("/book/Psalms/chapter/119") assert response.status_code == 200 # Should have 176 verses assert "176" in response.text or "verse" in response.text.lower() def test_genesis_chapter_count(self, client): """Test Genesis has correct chapter count.""" response = client.get("/book/Genesis") assert response.status_code == 200 # Genesis has 50 chapters assert "50" in response.text or "Chapter" in response.text def test_revelation_last_book(self, client): """Test Revelation (last book).""" response = client.get("/book/Revelation") assert response.status_code == 200 # Last chapter is 22 response = client.get("/book/Revelation/chapter/22") assert response.status_code == 200