diff --git a/tests/test_coverage_part5.py b/tests/test_coverage_part5.py new file mode 100644 index 0000000..632183c --- /dev/null +++ b/tests/test_coverage_part5.py @@ -0,0 +1,636 @@ +""" +Additional tests to improve coverage - Part 5. + +Focus on low-coverage modules: +- routes/stories.py (54%) +- routes/bible.py (60%) +- utils/commentary_loader.py (64%) +- utils/search.py (70%) +- routes/topics.py (74%) +""" +import pytest + + +class TestStoriesRoutes: + """Tests for stories routes (54% coverage).""" + + def test_stories_index_page(self, client): + """Test stories index page loads.""" + response = client.get("/stories") + assert response.status_code == 200 + assert "Stories" in response.text or "stories" in response.text + + def test_stories_kids_index_page(self, client): + """Test kids stories index page loads.""" + response = client.get("/stories/kids") + assert response.status_code == 200 + + def test_story_detail_valid(self, client): + """Test valid story detail page.""" + # First get the stories index to find a valid story + response = client.get("/stories") + assert response.status_code == 200 + # Try some common story slugs + story_slugs = ["creation", "noahs-ark", "david-and-goliath", "the-exodus"] + for slug in story_slugs: + response = client.get(f"/stories/{slug}") + if response.status_code == 200: + break + # At least the route should work (even if 404) + assert response.status_code in [200, 404] + + def test_story_detail_invalid(self, client): + """Test invalid story returns 404.""" + response = client.get("/stories/this-story-does-not-exist-12345") + assert response.status_code == 404 + + def test_story_kids_version(self, client): + """Test kids version of a story.""" + story_slugs = ["creation", "noahs-ark", "david-and-goliath"] + for slug in story_slugs: + response = client.get(f"/stories/{slug}/kids") + if response.status_code == 200: + break + assert response.status_code in [200, 404] + + def test_story_kids_invalid(self, client): + """Test invalid kids story returns 404.""" + response = client.get("/stories/nonexistent-story/kids") + assert response.status_code == 404 + + def test_story_pdf_endpoint(self, client): + """Test story PDF endpoint (may return 503 if WeasyPrint unavailable).""" + story_slugs = ["creation", "noahs-ark"] + for slug in story_slugs: + response = client.get(f"/stories/{slug}/pdf") + # 503 means WeasyPrint not installed, 404 means story not found + assert response.status_code in [200, 404, 503] + if response.status_code != 404: + break + + def test_story_kids_pdf_endpoint(self, client): + """Test kids story PDF endpoint.""" + response = client.get("/stories/creation/kids/pdf") + assert response.status_code in [200, 404, 503] + + +class TestBibleRoutes: + """Tests for bible routes (60% coverage).""" + + def test_book_page_genesis(self, client): + """Test book page for Genesis.""" + response = client.get("/book/Genesis") + assert response.status_code == 200 + assert "Genesis" in response.text + + def test_book_page_invalid(self, client): + """Test invalid book returns 404.""" + response = client.get("/book/NotARealBook") + assert response.status_code == 404 + + def test_book_abbreviation_redirect(self, client): + """Test book abbreviation redirects to full name.""" + response = client.get("/book/Gen", follow_redirects=False) + # Should redirect to /book/Genesis + assert response.status_code in [200, 301, 307] + + def test_book_pdf_endpoint(self, client): + """Test book PDF endpoint.""" + response = client.get("/book/Philemon/pdf") + # 503 if WeasyPrint not installed + assert response.status_code in [200, 503] + + def test_book_pdf_invalid(self, client): + """Test invalid book PDF returns 404.""" + response = client.get("/book/FakeBook/pdf") + assert response.status_code in [404, 503] + + def test_chapter_page(self, client): + """Test chapter page loads.""" + response = client.get("/book/Genesis/chapter/1") + assert response.status_code == 200 + assert "Genesis" in response.text + + def test_chapter_page_invalid_chapter(self, client): + """Test invalid chapter returns 404.""" + response = client.get("/book/Genesis/chapter/999") + assert response.status_code == 404 + + def test_chapter_page_invalid_book(self, client): + """Test invalid book in chapter route.""" + response = client.get("/book/FakeBook/chapter/1") + assert response.status_code == 404 + + def test_chapter_pdf_endpoint(self, client): + """Test chapter PDF endpoint.""" + response = client.get("/book/Genesis/chapter/1/pdf") + assert response.status_code in [200, 503] + + def test_chapter_pdf_invalid(self, client): + """Test invalid chapter PDF.""" + response = client.get("/book/Genesis/chapter/999/pdf") + assert response.status_code in [404, 503] + + def test_legacy_chapter_redirect(self, client): + """Test legacy chapter URL redirects.""" + response = client.get("/book/Genesis/1", follow_redirects=False) + assert response.status_code in [301, 307] + + def test_book_commentary_redirect(self, client): + """Test book commentary redirect.""" + response = client.get("/book/Genesis/commentary", follow_redirects=False) + assert response.status_code in [301, 307] + + def test_verse_page(self, client): + """Test verse page loads.""" + response = client.get("/book/Genesis/chapter/1/verse/1") + assert response.status_code == 200 + + def test_verse_page_invalid_verse(self, client): + """Test invalid verse returns 404.""" + response = client.get("/book/Genesis/chapter/1/verse/999") + assert response.status_code == 404 + + def test_verse_pdf_endpoint(self, client): + """Test verse PDF endpoint.""" + response = client.get("/book/Genesis/chapter/1/verse/1/pdf") + assert response.status_code in [200, 503] + + def test_interlinear_chapter_page(self, client): + """Test interlinear chapter page.""" + response = client.get("/book/Genesis/chapter/1/interlinear") + assert response.status_code == 200 + + def test_interlinear_chapter_invalid(self, client): + """Test invalid interlinear chapter.""" + response = client.get("/book/FakeBook/chapter/1/interlinear") + assert response.status_code == 404 + + def test_interlinear_pdf_endpoint(self, client): + """Test interlinear PDF endpoint.""" + response = client.get("/book/Genesis/chapter/1/interlinear/pdf") + assert response.status_code in [200, 503] + + def test_new_testament_book(self, client): + """Test New Testament book page.""" + response = client.get("/book/Matthew") + assert response.status_code == 200 + + def test_new_testament_interlinear(self, client): + """Test New Testament interlinear.""" + response = client.get("/book/Matthew/chapter/1/interlinear") + assert response.status_code == 200 + + +class TestCommentaryLoaderModule: + """Tests for commentary_loader module (64% coverage).""" + + def test_load_commentary(self): + """Test loading commentary data.""" + from kjvstudy_org.utils.commentary_loader import load_commentary + result = load_commentary() + assert isinstance(result, dict) + # Should have some books loaded + assert len(result) > 0 + + def test_load_commentary_flat(self): + """Test loading flat commentary.""" + from kjvstudy_org.utils.commentary_loader import load_commentary_flat + result = load_commentary_flat() + assert isinstance(result, dict) + # Keys should be "Book Chapter:Verse" format + if result: + key = list(result.keys())[0] + assert ":" in key + + def test_normalize_entry_valid(self): + """Test normalizing valid entry.""" + from kjvstudy_org.utils.commentary_loader import _normalize_entry + entry = { + "analysis": "Test analysis", + "historical": "Test context", + "questions": ["Q1", "Q2"] + } + result = _normalize_entry(entry) + assert result["analysis"] == "Test analysis" + assert result["historical"] == "Test context" + assert result["questions"] == ["Q1", "Q2"] + + def test_normalize_entry_historical_context_key(self): + """Test normalizing entry with historical_context key.""" + from kjvstudy_org.utils.commentary_loader import _normalize_entry + entry = { + "analysis": "Test", + "historical_context": "Context from alt key" + } + result = _normalize_entry(entry) + assert result["historical"] == "Context from alt key" + + def test_normalize_entry_not_dict(self): + """Test normalizing non-dict entry.""" + from kjvstudy_org.utils.commentary_loader import _normalize_entry + result = _normalize_entry("not a dict") + assert result == {"analysis": "", "historical": "", "questions": []} + + def test_normalize_entry_empty(self): + """Test normalizing empty entry.""" + from kjvstudy_org.utils.commentary_loader import _normalize_entry + result = _normalize_entry({}) + assert result["analysis"] == "" + assert result["historical"] == "" + assert result["questions"] == [] + + def test_slugify(self): + """Test slugify function.""" + from kjvstudy_org.utils.commentary_loader import _slugify + assert _slugify("1 Samuel") == "1_samuel" + assert _slugify("Song of Solomon") == "song_of_solomon" + assert _slugify("Genesis") == "genesis" + + def test_slugify_empty(self): + """Test slugify with empty string.""" + from kjvstudy_org.utils.commentary_loader import _slugify + result = _slugify("") + assert result == "book" + + def test_slugify_special_chars(self): + """Test slugify with special characters.""" + from kjvstudy_org.utils.commentary_loader import _slugify + result = _slugify("Book!!!Name???") + assert result == "book_name" + + +class TestSearchModule: + """Tests for search module (70% coverage).""" + + def test_perform_full_text_search(self): + """Test full text search.""" + from kjvstudy_org.utils.search import perform_full_text_search + results = perform_full_text_search("love", limit=10) + assert isinstance(results, list) + if results: + assert "book" in results[0] + assert "text" in results[0] + + def test_perform_search_verse_reference(self): + """Test search with verse reference.""" + from kjvstudy_org.utils.search import perform_full_text_search + results = perform_full_text_search("John 3:16") + assert isinstance(results, list) + + def test_calculate_relevance_score(self): + """Test relevance score calculation.""" + from kjvstudy_org.utils.search import calculate_relevance_score + text = "For God so loved the world that he gave" + terms = ["loved", "world"] + score = calculate_relevance_score(text, terms) + assert isinstance(score, float) + assert score > 0 + + def test_calculate_relevance_score_exact_match(self): + """Test relevance score with exact word matches.""" + from kjvstudy_org.utils.search import calculate_relevance_score + text = "God so loved the world" + terms = ["loved"] + score = calculate_relevance_score(text, terms) + # Should have bonus for exact word match + assert score > 1.0 + + def test_highlight_search_terms(self): + """Test search term highlighting.""" + from kjvstudy_org.utils.search import highlight_search_terms + text = "For God so loved the world" + terms = ["loved", "world"] + result = highlight_search_terms(text, terms) + assert "" in result + assert "loved" in result or "Loved" in result + + def test_ensure_search_index(self): + """Test ensure search index.""" + from kjvstudy_org.utils.search import ensure_search_index + result = ensure_search_index() + # Returns True if FTS available, False otherwise + assert isinstance(result, bool) + + def test_legacy_search(self): + """Test legacy search function.""" + from kjvstudy_org.utils.search import _legacy_search + results = _legacy_search("God", limit=5) + assert isinstance(results, list) + assert len(results) <= 5 + + +class TestTopicsRoutes: + """Tests for topics routes (74% coverage).""" + + def test_topics_page(self, client): + """Test topics index page.""" + response = client.get("/topics") + assert response.status_code == 200 + assert "Topics" in response.text or "topics" in response.text + + def test_topic_detail_love(self, client): + """Test topic detail for love.""" + response = client.get("/topics/Love") + # May or may not exist + assert response.status_code in [200, 404] + + def test_topic_detail_faith(self, client): + """Test topic detail for faith.""" + response = client.get("/topics/Faith") + assert response.status_code in [200, 404] + + def test_topic_detail_invalid(self, client): + """Test invalid topic returns 404.""" + response = client.get("/topics/NotARealTopic12345") + assert response.status_code == 404 + + def test_topic_pdf_endpoint(self, client): + """Test topic PDF endpoint.""" + # Try common topics + topics = ["Love", "Faith", "Salvation"] + for topic in topics: + response = client.get(f"/topics/{topic}/pdf") + if response.status_code in [200, 503]: + break + # 503 if WeasyPrint not installed, 404 if topic not found + assert response.status_code in [200, 404, 503] + + +class TestPdfModule: + """Tests for PDF module (68% coverage).""" + + def test_weasyprint_available_constant(self): + """Test WEASYPRINT_AVAILABLE constant exists.""" + from kjvstudy_org.utils.pdf import WEASYPRINT_AVAILABLE + assert isinstance(WEASYPRINT_AVAILABLE, bool) + + def test_render_html_to_pdf_sync(self): + """Test synchronous PDF rendering.""" + from kjvstudy_org.utils.pdf import render_html_to_pdf, WEASYPRINT_AVAILABLE + if not WEASYPRINT_AVAILABLE: + pytest.skip("WeasyPrint not available") + html = "

Test

" + result = render_html_to_pdf(html) + assert result is not None + + def test_render_html_to_pdf_async_import(self): + """Test async PDF rendering function exists.""" + from kjvstudy_org.utils.pdf import render_html_to_pdf_async, WEASYPRINT_AVAILABLE + # Just verify the async function exists and is callable + assert callable(render_html_to_pdf_async) + + def test_render_pdf_sync_internal(self): + """Test internal sync render function.""" + from kjvstudy_org.utils.pdf import _render_pdf_sync, WEASYPRINT_AVAILABLE + if not WEASYPRINT_AVAILABLE: + with pytest.raises(RuntimeError): + _render_pdf_sync("") + else: + result = _render_pdf_sync("Test") + assert result is not None + + +class TestBooksUtilModule: + """Tests for utils/books.py module (79% coverage).""" + + def test_normalize_book_name_valid(self): + """Test normalizing valid book abbreviation.""" + from kjvstudy_org.utils.books import normalize_book_name + # Should return None for already canonical names + result = normalize_book_name("Genesis") + # If it's already canonical, returns None + assert result is None or result == "Genesis" + + def test_normalize_book_name_abbreviation(self): + """Test normalizing book abbreviation.""" + from kjvstudy_org.utils.books import normalize_book_name + result = normalize_book_name("Gen") + assert result == "Genesis" or result is None + + def test_ot_books_constant(self): + """Test OT_BOOKS constant exists.""" + from kjvstudy_org.utils.books import OT_BOOKS + assert isinstance(OT_BOOKS, (list, set, tuple)) + assert "Genesis" in OT_BOOKS + + def test_nt_books_constant(self): + """Test NT_BOOKS constant exists.""" + from kjvstudy_org.utils.books import NT_BOOKS + assert isinstance(NT_BOOKS, (list, set, tuple)) + assert "Matthew" in NT_BOOKS + + +class TestResourceData: + """Tests for resource_data module (50% coverage).""" + + def test_resource_data_module_import(self): + """Test resource data module imports.""" + from kjvstudy_org import resource_data + assert resource_data is not None + + +class TestMainModule: + """Tests for main module (62% coverage).""" + + def test_main_module_import(self): + """Test main module imports.""" + from kjvstudy_org import main + assert main is not None + + def test_main_app_exists(self): + """Test main has app reference.""" + from kjvstudy_org import main + # main.py typically imports app from server + assert hasattr(main, 'app') or hasattr(main, 'server') + + +class TestKjvModule: + """Tests for kjv module (83% coverage).""" + + def test_bible_get_books(self): + """Test getting all books.""" + from kjvstudy_org.kjv import bible + books = bible.get_books() + assert isinstance(books, list) + assert len(books) == 66 + + def test_bible_get_chapters(self): + """Test getting chapters for a book.""" + from kjvstudy_org.kjv import bible + chapters = bible.get_chapters_for_book("Genesis") + assert isinstance(chapters, list) + assert len(chapters) == 50 + + def test_bible_get_verses_by_book_chapter(self): + """Test getting verses by book and chapter.""" + from kjvstudy_org.kjv import bible + verses = bible.get_verses_by_book_chapter("Genesis", 1) + assert isinstance(verses, list) + assert len(verses) == 31 + + def test_bible_iter_verses(self): + """Test iterating over all verses.""" + from kjvstudy_org.kjv import bible + count = 0 + for verse in bible.iter_verses(): + count += 1 + if count >= 10: + break + assert count == 10 + + +class TestRedLetterModule: + """Tests for red_letter module (92% coverage).""" + + def test_red_letter_import(self): + """Test red letter module imports.""" + from kjvstudy_org import red_letter + assert red_letter is not None + + +class TestCrossReferencesModule: + """Tests for cross_references module (88% coverage).""" + + def test_get_cross_references(self): + """Test getting cross references.""" + from kjvstudy_org.cross_references import get_cross_references + refs = get_cross_references("John", 3, 16) + assert isinstance(refs, list) + + def test_parse_reference(self): + """Test parsing a reference.""" + from kjvstudy_org.cross_references import parse_reference + result = parse_reference("Genesis 1:1") + assert result is not None + assert result.get("book") == "Genesis" + + +class TestServerModule: + """Tests for server module (86% coverage).""" + + def test_app_exists(self, client): + """Test app exists and responds.""" + response = client.get("/") + assert response.status_code == 200 + + def test_api_docs_endpoint(self, client): + """Test API docs endpoint.""" + response = client.get("/api/docs") + assert response.status_code == 200 + + def test_openapi_endpoint(self, client): + """Test OpenAPI endpoint.""" + response = client.get("/api/openapi.json") + assert response.status_code == 200 + + +class TestJinjaFiltersModule: + """Tests for jinja_filters module (96% coverage).""" + + def test_filters_module_import(self): + """Test jinja filters module imports.""" + from kjvstudy_org import jinja_filters + assert jinja_filters is not None + + +class TestFamilyTreeRoutes: + """Tests for family_tree routes (85% coverage).""" + + def test_family_tree_index(self, client): + """Test family tree index page.""" + response = client.get("/family-tree") + assert response.status_code == 200 + + def test_family_tree_person_adam(self, client): + """Test family tree for Adam.""" + response = client.get("/family-tree/person/adam") + assert response.status_code in [200, 404] + + def test_family_tree_person_abraham(self, client): + """Test family tree for Abraham.""" + response = client.get("/family-tree/person/abraham") + assert response.status_code in [200, 404] + + def test_family_tree_generation(self, client): + """Test family tree generation view.""" + response = client.get("/family-tree/generation/1") + assert response.status_code in [200, 404] + + +class TestMiscRoutes: + """Tests for misc routes (90% coverage).""" + + def test_random_verse_redirect(self, client): + """Test random verse endpoint.""" + response = client.get("/random", follow_redirects=False) + # Should redirect to a random verse or return 404 if not implemented + assert response.status_code in [200, 302, 307, 404] + + +class TestStudyGuidesRoutes: + """Tests for study_guides routes (86% coverage).""" + + def test_study_guides_index(self, client): + """Test study guides index.""" + response = client.get("/study-guides") + assert response.status_code == 200 + + def test_study_guide_salvation(self, client): + """Test salvation study guide.""" + response = client.get("/study-guides/salvation") + assert response.status_code in [200, 404] + + +class TestTimelineRoutes: + """Tests for timeline routes (90% coverage).""" + + def test_timeline_index(self, client): + """Test timeline index.""" + response = client.get("/timeline") + assert response.status_code in [200, 302, 307, 404] + + def test_timeline_event(self, client): + """Test timeline event page.""" + response = client.get("/timeline/creation") + assert response.status_code in [200, 404] + + +class TestAboutRoutes: + """Tests for about routes (94% coverage).""" + + def test_about_page(self, client): + """Test about page.""" + response = client.get("/about") + assert response.status_code == 200 + + +class TestDataInit: + """Tests for data __init__ module (95% coverage).""" + + def test_data_module_import(self): + """Test data module imports.""" + from kjvstudy_org import data + assert data is not None + + +class TestInterlinearLoaderExtended: + """Extended tests for interlinear_loader module.""" + + def test_get_interlinear_data_genesis(self): + """Test getting interlinear data for Genesis.""" + from kjvstudy_org.interlinear_loader import get_interlinear_data + result = get_interlinear_data("Genesis", 1, 1) + assert result is None or isinstance(result, list) + + def test_has_interlinear_data(self): + """Test checking if interlinear data exists.""" + from kjvstudy_org.interlinear_loader import has_interlinear_data + result = has_interlinear_data("Genesis", 1, 1) + assert isinstance(result, bool) + + def test_find_verses_by_strongs_h430(self): + """Test finding verses by Strong's number H430.""" + from kjvstudy_org.interlinear_loader import find_verses_by_strongs + result = find_verses_by_strongs("H430", limit=5) + assert isinstance(result, list)