diff --git a/kjvstudy_org/templates/about.html b/kjvstudy_org/templates/about.html
index df8f29e..9fc39dc 100644
--- a/kjvstudy_org/templates/about.html
+++ b/kjvstudy_org/templates/about.html
@@ -130,9 +130,6 @@
Site Statistics — Comprehensive metrics on coverage and content
Commentary Index — Browse all verses with in-depth analysis
Cross-References Index — Explore Scripture's interconnections
- Strong's Concordance — Hebrew and Greek word studies
- Study Guides — Thematic studies on biblical topics
- Reading Plans — Structured approaches to Scripture reading
API Documentation — For developers building on our data
diff --git a/pyproject.toml b/pyproject.toml
index ebc20d7..f13d17c 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -41,6 +41,7 @@ package = true
[dependency-groups]
dev = [
"pytest>=8.3.5",
+ "pytest-cov>=7.0.0",
]
[tool.pytest.ini_options]
diff --git a/tests/test_utils.py b/tests/test_utils.py
new file mode 100644
index 0000000..d7122d8
--- /dev/null
+++ b/tests/test_utils.py
@@ -0,0 +1,828 @@
+"""
+Tests for utility modules - search, books, stories, biographies, etc.
+Targeting modules with lower test coverage.
+"""
+import pytest
+from fastapi.testclient import TestClient
+
+
+class TestSearchModule:
+ """Tests for kjvstudy_org.utils.search module"""
+
+ def test_calculate_relevance_score_single_term(self):
+ """Test relevance score calculation with single term"""
+ from kjvstudy_org.utils.search import calculate_relevance_score
+
+ text = "In the beginning God created the heaven and the earth."
+ score = calculate_relevance_score(text, ["beginning"])
+ assert score > 0
+
+ def test_calculate_relevance_score_multiple_terms(self):
+ """Test relevance score with multiple search terms"""
+ from kjvstudy_org.utils.search import calculate_relevance_score
+
+ text = "For God so loved the world"
+ score = calculate_relevance_score(text, ["god", "loved"])
+ # Should get points for both terms
+ assert score >= 2.0
+
+ def test_calculate_relevance_score_word_boundaries(self):
+ """Test that exact word matches get bonus"""
+ from kjvstudy_org.utils.search import calculate_relevance_score
+
+ text = "God is love"
+ score_exact = calculate_relevance_score(text, ["love"])
+ # Exact word match should have bonus
+ assert score_exact > 1.0
+
+ def test_calculate_relevance_score_multiple_occurrences(self):
+ """Test that multiple occurrences increase score"""
+ from kjvstudy_org.utils.search import calculate_relevance_score
+
+ text = "The LORD is my shepherd. The LORD is good."
+ score = calculate_relevance_score(text, ["lord"])
+ # Should count both occurrences
+ assert score >= 2.0
+
+ def test_highlight_search_terms_basic(self):
+ """Test basic search term highlighting"""
+ from kjvstudy_org.utils.search import highlight_search_terms
+
+ text = "For God so loved the world"
+ highlighted = highlight_search_terms(text, ["god"])
+ assert "" in highlighted
+ assert "" in highlighted
+
+ def test_highlight_search_terms_case_insensitive(self):
+ """Test case insensitive highlighting"""
+ from kjvstudy_org.utils.search import highlight_search_terms
+
+ text = "The LORD is my shepherd"
+ highlighted = highlight_search_terms(text, ["lord"])
+ assert "" in highlighted
+
+ def test_highlight_search_terms_multiple(self):
+ """Test highlighting multiple terms"""
+ from kjvstudy_org.utils.search import highlight_search_terms
+
+ text = "God is love"
+ highlighted = highlight_search_terms(text, ["god", "love"])
+ # Should have two mark tags
+ assert highlighted.count("") == 2
+
+ def test_ensure_search_index(self):
+ """Test search index initialization"""
+ from kjvstudy_org.utils.search import ensure_search_index
+
+ # Should return True if FTS available, False otherwise
+ result = ensure_search_index()
+ assert isinstance(result, bool)
+
+ def test_perform_full_text_search_verse_reference(self):
+ """Test search with verse reference"""
+ from kjvstudy_org.utils.search import perform_full_text_search
+
+ # Test with verse reference format
+ results = perform_full_text_search("John 3:16")
+ assert len(results) >= 1
+ assert results[0]["book"] == "John"
+ assert results[0]["chapter"] == 3
+ assert results[0]["verse"] == 16
+
+ def test_perform_full_text_search_regular_query(self):
+ """Test regular keyword search"""
+ from kjvstudy_org.utils.search import perform_full_text_search
+
+ results = perform_full_text_search("love", limit=10)
+ assert len(results) > 0
+ # All results should have the search term (or stem)
+ for result in results:
+ text_lower = result["text"].lower()
+ # FTS may use stemming, so check for love/loved/loves
+ assert "love" in text_lower or "loved" in text_lower or "loves" in text_lower
+
+
+class TestBooksModule:
+ """Tests for kjvstudy_org.books module"""
+
+ def test_get_book_data_genesis(self):
+ """Test getting data for Genesis"""
+ from kjvstudy_org.books import get_book_data
+
+ data = get_book_data("Genesis")
+ assert data is not None
+ assert "introduction" in data or "name" in data
+
+ def test_get_book_data_psalms(self):
+ """Test getting data for Psalms"""
+ from kjvstudy_org.books import get_book_data
+
+ data = get_book_data("Psalms")
+ assert data is not None
+
+ def test_get_book_data_invalid(self):
+ """Test getting data for non-existent book"""
+ from kjvstudy_org.books import get_book_data
+
+ data = get_book_data("NotABook")
+ assert data is None
+
+ def test_get_book_introduction(self):
+ """Test getting book introduction"""
+ from kjvstudy_org.books import get_book_introduction
+
+ intro = get_book_introduction("Genesis")
+ # Should return string or None
+ assert intro is None or isinstance(intro, str)
+
+ def test_get_book_themes(self):
+ """Test getting book themes"""
+ from kjvstudy_org.books import get_book_themes
+
+ themes = get_book_themes("Genesis")
+ # Should return list or None
+ assert themes is None or isinstance(themes, list)
+
+ def test_get_book_key_verses(self):
+ """Test getting key verses"""
+ from kjvstudy_org.books import get_book_key_verses
+
+ verses = get_book_key_verses("John")
+ assert verses is None or isinstance(verses, list)
+
+ def test_get_book_outline(self):
+ """Test getting book outline"""
+ from kjvstudy_org.books import get_book_outline
+
+ outline = get_book_outline("Romans")
+ assert outline is None or isinstance(outline, list)
+
+ def test_get_book_christ_in_book(self):
+ """Test getting Christ in book section"""
+ from kjvstudy_org.books import get_book_christ_in_book
+
+ result = get_book_christ_in_book("Isaiah")
+ assert result is None or isinstance(result, str)
+
+ def test_get_book_metadata(self):
+ """Test getting book metadata"""
+ from kjvstudy_org.books import get_book_metadata
+
+ metadata = get_book_metadata("Matthew")
+ if metadata:
+ assert "testament" in metadata or "name" in metadata
+
+ def test_get_book_metadata_invalid(self):
+ """Test getting metadata for invalid book"""
+ from kjvstudy_org.books import get_book_metadata
+
+ metadata = get_book_metadata("FakeBook")
+ assert metadata is None
+
+ def test_get_all_books_metadata(self):
+ """Test getting all books metadata"""
+ from kjvstudy_org.books import get_all_books_metadata
+
+ all_books = get_all_books_metadata()
+ assert isinstance(all_books, list)
+ # Should have 66 books
+ assert len(all_books) == 66
+
+ def test_has_book_data(self):
+ """Test checking if book has data"""
+ from kjvstudy_org.books import has_book_data
+
+ assert has_book_data("Genesis") is True
+ assert has_book_data("NotABook") is False
+
+ def test_get_books_by_category(self):
+ """Test getting books by category"""
+ from kjvstudy_org.books import get_books_by_category
+
+ categories = get_books_by_category()
+ assert isinstance(categories, dict)
+ # Should have multiple categories
+ assert len(categories) > 0
+
+
+class TestStoriesModule:
+ """Tests for kjvstudy_org.stories module"""
+
+ def test_load_all_stories(self):
+ """Test loading all stories"""
+ from kjvstudy_org.stories import load_all_stories
+
+ stories = load_all_stories()
+ assert isinstance(stories, list)
+
+ def test_get_all_stories_flat(self):
+ """Test getting flat list of stories"""
+ from kjvstudy_org.stories import get_all_stories_flat
+
+ stories = get_all_stories_flat()
+ assert isinstance(stories, list)
+ if len(stories) > 0:
+ # Each story should have category info attached
+ story = stories[0]
+ assert "category_name" in story or "slug" in story
+
+ def test_get_story_by_slug_exists(self):
+ """Test getting existing story by slug"""
+ from kjvstudy_org.stories import get_all_stories_flat, get_story_by_slug
+
+ all_stories = get_all_stories_flat()
+ if len(all_stories) > 0:
+ first_slug = all_stories[0].get("slug")
+ if first_slug:
+ story = get_story_by_slug(first_slug)
+ assert story is not None
+
+ def test_get_story_by_slug_not_exists(self):
+ """Test getting non-existent story"""
+ from kjvstudy_org.stories import get_story_by_slug
+
+ story = get_story_by_slug("not-a-real-story-slug-12345")
+ assert story is None
+
+ def test_get_stories_by_category(self):
+ """Test getting stories by category"""
+ from kjvstudy_org.stories import load_all_stories, get_stories_by_category
+
+ categories = load_all_stories()
+ if len(categories) > 0:
+ cat_slug = categories[0].get("slug")
+ if cat_slug:
+ stories = get_stories_by_category(cat_slug)
+ assert isinstance(stories, list)
+
+ def test_get_stories_by_invalid_category(self):
+ """Test getting stories from non-existent category"""
+ from kjvstudy_org.stories import get_stories_by_category
+
+ stories = get_stories_by_category("not-a-real-category")
+ assert stories == []
+
+ def test_get_category_by_slug_exists(self):
+ """Test getting existing category"""
+ from kjvstudy_org.stories import load_all_stories, get_category_by_slug
+
+ categories = load_all_stories()
+ if len(categories) > 0:
+ slug = categories[0].get("slug")
+ if slug:
+ category = get_category_by_slug(slug)
+ assert category is not None
+
+ def test_get_category_by_slug_not_exists(self):
+ """Test getting non-existent category"""
+ from kjvstudy_org.stories import get_category_by_slug
+
+ category = get_category_by_slug("fake-category-slug-xyz")
+ assert category is None
+
+ def test_get_story_count(self):
+ """Test getting story count"""
+ from kjvstudy_org.stories import get_story_count
+
+ count = get_story_count()
+ assert isinstance(count, int)
+ assert count >= 0
+
+ def test_get_category_count(self):
+ """Test getting category count"""
+ from kjvstudy_org.stories import get_category_count
+
+ count = get_category_count()
+ assert isinstance(count, int)
+ assert count >= 0
+
+ def test_get_categories(self):
+ """Test getting categories"""
+ from kjvstudy_org.stories import get_categories
+
+ categories = get_categories()
+ assert isinstance(categories, list)
+
+ def test_refresh_stories(self):
+ """Test refreshing story cache"""
+ from kjvstudy_org.stories import refresh_stories, get_categories
+
+ # Should not raise
+ refresh_stories()
+ categories = get_categories()
+ assert isinstance(categories, list)
+
+
+class TestBiblicalBiographies:
+ """Tests for kjvstudy_org.biblical_biographies module"""
+
+ def test_get_biography_abraham(self):
+ """Test getting Abraham's biography"""
+ from kjvstudy_org.biblical_biographies import get_biography
+
+ bio = get_biography("Abraham")
+ assert bio is not None
+ assert isinstance(bio, dict)
+
+ def test_get_biography_david(self):
+ """Test getting David's biography"""
+ from kjvstudy_org.biblical_biographies import get_biography
+
+ bio = get_biography("David")
+ assert bio is not None
+
+ def test_get_biography_not_exists(self):
+ """Test getting non-existent biography"""
+ from kjvstudy_org.biblical_biographies import get_biography
+
+ bio = get_biography("Not A Real Person At All")
+ assert bio is None
+
+ def test_get_biography_via_alias(self):
+ """Test getting biography via alias name"""
+ from kjvstudy_org.biblical_biographies import get_biography, BIOGRAPHY_ALIASES
+
+ # If there are aliases, test one
+ if BIOGRAPHY_ALIASES:
+ alias = next(iter(BIOGRAPHY_ALIASES.keys()))
+ bio = get_biography(alias)
+ # Should either find via alias or return None if canonical not found
+ # Just verify no crash
+ assert bio is None or isinstance(bio, dict)
+
+ def test_has_biography_exists(self):
+ """Test checking existing biography"""
+ from kjvstudy_org.biblical_biographies import has_biography
+
+ assert has_biography("Abraham") is True
+
+ def test_has_biography_not_exists(self):
+ """Test checking non-existent biography"""
+ from kjvstudy_org.biblical_biographies import has_biography
+
+ assert has_biography("Totally Made Up Person") is False
+
+ def test_has_biography_via_alias(self):
+ """Test checking biography via alias"""
+ from kjvstudy_org.biblical_biographies import has_biography, BIOGRAPHY_ALIASES
+
+ if BIOGRAPHY_ALIASES:
+ alias = next(iter(BIOGRAPHY_ALIASES.keys()))
+ # Should return boolean
+ result = has_biography(alias)
+ assert isinstance(result, bool)
+
+
+class TestRedLetterModule:
+ """Tests for kjvstudy_org.red_letter module"""
+
+ def test_load_red_letter_verses(self):
+ """Test loading red letter verses"""
+ from kjvstudy_org.red_letter import load_red_letter_verses
+
+ verses = load_red_letter_verses()
+ assert isinstance(verses, dict)
+
+ def test_get_christ_words_known_verse(self):
+ """Test getting Christ's words from known red letter verse"""
+ from kjvstudy_org.red_letter import get_christ_words
+
+ # John 3:16 - Jesus speaking
+ words = get_christ_words("John", 3, 16)
+ # May or may not be marked as red letter depending on data
+ # Just verify no crash
+ assert words is None or isinstance(words, str)
+
+ def test_get_christ_words_not_red_letter(self):
+ """Test verse without Christ's words"""
+ from kjvstudy_org.red_letter import get_christ_words
+
+ # Genesis 1:1 - Not Jesus speaking
+ words = get_christ_words("Genesis", 1, 1)
+ assert words is None
+
+ def test_wrap_red_letter_text_no_words(self):
+ """Test wrapping text when no Christ words"""
+ from kjvstudy_org.red_letter import wrap_red_letter_text
+
+ text = "In the beginning God created the heaven and the earth."
+ result = wrap_red_letter_text(text, "Genesis", 1, 1)
+ # Should return unchanged if no red letter
+ assert result == text
+
+ def test_wrap_red_letter_text_full_verse(self):
+ """Test wrapping full verse red letter"""
+ from kjvstudy_org.red_letter import wrap_red_letter_text, load_red_letter_verses
+
+ verses = load_red_letter_verses()
+ # Find a verse marked as 'full'
+ full_verse = None
+ for ref, words in verses.items():
+ if words == 'full':
+ full_verse = ref
+ break
+
+ if full_verse:
+ # Parse reference
+ parts = full_verse.rsplit(' ', 1)
+ if len(parts) == 2:
+ book = parts[0]
+ chap_verse = parts[1].split(':')
+ if len(chap_verse) == 2:
+ chapter, verse = int(chap_verse[0]), int(chap_verse[1])
+ text = "Some verse text"
+ result = wrap_red_letter_text(text, book, chapter, verse)
+ assert 'class="words-of-christ"' in result
+
+
+class TestHelpersModule:
+ """Tests for kjvstudy_org.utils.helpers module"""
+
+ def test_create_slug_basic(self):
+ """Test basic slug creation"""
+ from kjvstudy_org.utils.helpers import create_slug
+
+ slug = create_slug("Hello World")
+ assert slug == "hello-world"
+
+ def test_create_slug_special_chars(self):
+ """Test slug with special characters"""
+ from kjvstudy_org.utils.helpers import create_slug
+
+ slug = create_slug("God's Love!")
+ assert "'" not in slug
+ assert "!" not in slug
+
+ def test_create_slug_numbers(self):
+ """Test slug with numbers"""
+ from kjvstudy_org.utils.helpers import create_slug
+
+ slug = create_slug("John 3:16")
+ assert isinstance(slug, str)
+ assert len(slug) > 0
+
+ def test_is_verse_reference_valid(self):
+ """Test valid verse reference detection"""
+ from kjvstudy_org.utils.helpers import is_verse_reference
+
+ assert is_verse_reference("John 3:16") is True
+ assert is_verse_reference("Genesis 1:1") is True
+ assert is_verse_reference("1 John 4:8") is True
+
+ def test_is_verse_reference_invalid(self):
+ """Test invalid verse reference detection"""
+ from kjvstudy_org.utils.helpers import is_verse_reference
+
+ assert is_verse_reference("hello world") is False
+ assert is_verse_reference("God is love") is False
+
+ def test_parse_verse_reference_valid(self):
+ """Test parsing valid verse reference"""
+ from kjvstudy_org.utils.helpers import parse_verse_reference
+
+ result = parse_verse_reference("John 3:16")
+ assert result is not None
+ assert result["book"] == "John"
+ assert result["chapter"] == 3
+ assert result["verse"] == 16
+
+ def test_parse_verse_reference_invalid(self):
+ """Test parsing invalid verse reference"""
+ from kjvstudy_org.utils.helpers import parse_verse_reference
+
+ result = parse_verse_reference("not a verse")
+ assert result is None
+
+ def test_get_verse_text(self):
+ """Test getting verse text"""
+ from kjvstudy_org.utils.helpers import get_verse_text
+
+ text = get_verse_text("John", 3, 16)
+ assert isinstance(text, str)
+ assert len(text) > 0
+
+ def test_get_verse_text_invalid(self):
+ """Test getting text for invalid verse"""
+ from kjvstudy_org.utils.helpers import get_verse_text
+
+ text = get_verse_text("FakeBook", 999, 999)
+ # Should return some fallback string
+ assert isinstance(text, str)
+
+ def test_get_daily_verse(self):
+ """Test getting daily verse"""
+ from kjvstudy_org.utils.helpers import get_daily_verse
+
+ verse = get_daily_verse()
+ assert isinstance(verse, dict)
+ assert "book" in verse
+ assert "chapter" in verse
+ assert "verse" in verse
+ assert "text" in verse
+
+ def test_get_chapter_popularity_score(self):
+ """Test getting chapter popularity score"""
+ from kjvstudy_org.utils.helpers import get_chapter_popularity_score
+
+ # Psalms 23 should be popular
+ score = get_chapter_popularity_score("Psalms", 23)
+ assert isinstance(score, int)
+ assert 1 <= score <= 10
+
+ def test_get_chapter_popularity_explanation(self):
+ """Test getting chapter popularity explanation"""
+ from kjvstudy_org.utils.helpers import get_chapter_popularity_explanation
+
+ explanation = get_chapter_popularity_explanation("Psalms", 23)
+ assert isinstance(explanation, str)
+ assert len(explanation) > 0
+
+ def test_get_related_content(self):
+ """Test getting related content"""
+ from kjvstudy_org.utils.helpers import get_related_content
+
+ related = get_related_content("John", 3, 16)
+ assert isinstance(related, dict)
+ assert "study_guides" in related
+ assert "topics" in related
+
+
+class TestCommentaryLoaderModule:
+ """Tests for kjvstudy_org.utils.commentary_loader module"""
+
+ def test_load_commentary(self):
+ """Test loading commentary data"""
+ from kjvstudy_org.utils.commentary_loader import load_commentary
+
+ commentary = load_commentary()
+ assert isinstance(commentary, dict)
+
+ def test_load_commentary_flat(self):
+ """Test loading flat commentary"""
+ from kjvstudy_org.utils.commentary_loader import load_commentary_flat
+
+ flat = load_commentary_flat()
+ assert isinstance(flat, dict)
+ # Keys should be like "Book Chapter:Verse"
+ if flat:
+ key = next(iter(flat.keys()))
+ assert ":" in key
+
+ def test_normalize_entry_valid(self):
+ """Test normalizing valid entry"""
+ from kjvstudy_org.utils.commentary_loader import _normalize_entry
+
+ entry = {
+ "analysis": "Some analysis",
+ "historical": "Historical context",
+ "questions": ["Q1", "Q2"]
+ }
+ normalized = _normalize_entry(entry)
+ assert normalized["analysis"] == "Some analysis"
+ assert normalized["historical"] == "Historical context"
+ assert len(normalized["questions"]) == 2
+
+ def test_normalize_entry_with_historical_context(self):
+ """Test normalizing entry with historical_context key"""
+ from kjvstudy_org.utils.commentary_loader import _normalize_entry
+
+ entry = {
+ "analysis": "Analysis",
+ "historical_context": "Context via alternative key"
+ }
+ normalized = _normalize_entry(entry)
+ assert normalized["historical"] == "Context via alternative key"
+
+ def test_normalize_entry_invalid(self):
+ """Test normalizing non-dict entry"""
+ from kjvstudy_org.utils.commentary_loader import _normalize_entry
+
+ result = _normalize_entry("not a dict")
+ 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("Genesis") == "genesis"
+ assert _slugify("1 John") == "1_john"
+ assert _slugify("Song of Solomon") == "song_of_solomon"
+
+
+class TestInterlinearLoader:
+ """Tests for kjvstudy_org.interlinear_loader module"""
+
+ def test_get_interlinear_data_genesis(self):
+ """Test getting interlinear data for Genesis 1:1"""
+ from kjvstudy_org.interlinear_loader import get_interlinear_data
+
+ data = get_interlinear_data("Genesis", 1, 1)
+ # Returns list of word data or None
+ assert data is None or isinstance(data, (dict, list))
+ if isinstance(data, list) and len(data) > 0:
+ # Each item should have word info
+ assert "original" in data[0] or "english" in data[0]
+
+ def test_get_interlinear_data_john(self):
+ """Test getting interlinear data for John 1:1"""
+ from kjvstudy_org.interlinear_loader import get_interlinear_data
+
+ data = get_interlinear_data("John", 1, 1)
+ assert data is None or isinstance(data, (dict, list))
+
+ def test_get_interlinear_data_invalid(self):
+ """Test getting interlinear data for invalid verse"""
+ from kjvstudy_org.interlinear_loader import get_interlinear_data
+
+ data = get_interlinear_data("FakeBook", 999, 999)
+ assert data is None
+
+
+class TestCrossReferencesModule:
+ """Tests for kjvstudy_org.cross_references module"""
+
+ def test_get_cross_references_john_3_16(self):
+ """Test getting cross references for John 3:16"""
+ from kjvstudy_org.cross_references import get_cross_references
+
+ refs = get_cross_references("John", 3, 16)
+ assert isinstance(refs, list)
+
+ def test_get_cross_references_genesis(self):
+ """Test getting cross references for Genesis 1:1"""
+ from kjvstudy_org.cross_references import get_cross_references
+
+ refs = get_cross_references("Genesis", 1, 1)
+ assert isinstance(refs, list)
+
+ def test_get_cross_references_invalid(self):
+ """Test getting cross references for invalid verse"""
+ from kjvstudy_org.cross_references import get_cross_references
+
+ refs = get_cross_references("NotABook", 999, 999)
+ assert isinstance(refs, list)
+ assert len(refs) == 0
+
+
+class TestPdfModule:
+ """Tests for kjvstudy_org.utils.pdf module"""
+
+ def test_weasyprint_available_flag(self):
+ """Test that WEASYPRINT_AVAILABLE flag is set"""
+ from kjvstudy_org.utils.pdf import WEASYPRINT_AVAILABLE
+
+ assert isinstance(WEASYPRINT_AVAILABLE, bool)
+
+ def test_render_html_to_pdf_when_available(self):
+ """Test PDF rendering when weasyprint available"""
+ from kjvstudy_org.utils.pdf import WEASYPRINT_AVAILABLE, render_html_to_pdf
+
+ if WEASYPRINT_AVAILABLE:
+ html = "Test
"
+ pdf_buffer = render_html_to_pdf(html)
+ assert pdf_buffer is not None
+ # Should be at position 0
+ assert pdf_buffer.tell() == 0
+ # Should have PDF content
+ content = pdf_buffer.read()
+ assert content.startswith(b'%PDF')
+ else:
+ # Should raise RuntimeError when not available
+ with pytest.raises(RuntimeError):
+ render_html_to_pdf("")
+
+ def test_render_html_to_pdf_async(self):
+ """Test async PDF rendering function exists"""
+ from kjvstudy_org.utils.pdf import WEASYPRINT_AVAILABLE, render_html_to_pdf_async
+ import asyncio
+
+ # Just verify the async function exists and is callable
+ assert callable(render_html_to_pdf_async)
+
+ # If weasyprint available, test via sync wrapper
+ if WEASYPRINT_AVAILABLE:
+ html = "Async Test
"
+ # Run async function synchronously for testing
+ loop = asyncio.new_event_loop()
+ try:
+ pdf_buffer = loop.run_until_complete(render_html_to_pdf_async(html))
+ assert pdf_buffer is not None
+ content = pdf_buffer.read()
+ assert content.startswith(b'%PDF')
+ finally:
+ loop.close()
+
+
+class TestTopicsModule:
+ """Tests for kjvstudy_org.topics module"""
+
+ def test_get_all_topics(self):
+ """Test getting all topics"""
+ from kjvstudy_org.topics import get_all_topics
+
+ topics = get_all_topics()
+ assert isinstance(topics, dict)
+ assert len(topics) > 0
+
+ def test_get_topic_valid(self):
+ """Test getting valid topic"""
+ from kjvstudy_org.topics import get_all_topics, get_topic
+
+ all_topics = get_all_topics()
+ if all_topics:
+ topic_name = next(iter(all_topics.keys()))
+ topic = get_topic(topic_name)
+ assert topic is not None
+
+ def test_get_topic_invalid(self):
+ """Test getting invalid topic"""
+ from kjvstudy_org.topics import get_topic
+
+ topic = get_topic("not_a_real_topic_xyz123")
+ assert topic is None
+
+
+class TestStoriesRoutes:
+ """Tests for kjvstudy_org.routes.stories module"""
+
+ 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.lower()
+
+ def test_stories_category_page(self, client):
+ """Test stories category page"""
+ from kjvstudy_org.stories import load_all_stories
+
+ categories = load_all_stories()
+ if categories:
+ slug = categories[0].get("slug")
+ if slug:
+ response = client.get(f"/stories/category/{slug}")
+ # Should return 200 or redirect
+ assert response.status_code in [200, 301, 302, 307, 404]
+
+ def test_stories_invalid_category(self, client):
+ """Test invalid category returns 404"""
+ response = client.get("/stories/category/not-a-real-category-xyz")
+ assert response.status_code == 404
+
+ def test_story_detail_page(self, client):
+ """Test individual story page"""
+ from kjvstudy_org.stories import get_all_stories_flat
+
+ stories = get_all_stories_flat()
+ if stories:
+ slug = stories[0].get("slug")
+ if slug:
+ response = client.get(f"/story/{slug}")
+ # Should return 200 or 404
+ assert response.status_code in [200, 404]
+
+ def test_story_invalid_slug(self, client):
+ """Test invalid story slug returns 404"""
+ response = client.get("/story/not-a-real-story-slug-xyz")
+ assert response.status_code == 404
+
+
+class TestReadingPlansModule:
+ """Tests for kjvstudy_org.reading_plans module"""
+
+ def test_get_all_plans(self):
+ """Test getting all reading plans"""
+ from kjvstudy_org.reading_plans import get_all_plans
+
+ plans = get_all_plans()
+ assert isinstance(plans, dict)
+ assert len(plans) > 0
+
+ def test_get_plan_by_id(self):
+ """Test getting reading plan by ID"""
+ from kjvstudy_org.reading_plans import get_all_plans, get_plan
+
+ plans = get_all_plans()
+ if plans:
+ plan_id = next(iter(plans.keys()))
+ plan = get_plan(plan_id)
+ assert plan is not None
+
+ def test_get_plan_invalid_id(self):
+ """Test getting reading plan with invalid ID"""
+ from kjvstudy_org.reading_plans import get_plan
+
+ plan = get_plan("not-a-real-plan-id-xyz")
+ assert plan is None
+
+ def test_get_plan_summary(self):
+ """Test getting plan summary"""
+ from kjvstudy_org.reading_plans import get_plan_summary
+
+ summary = get_plan_summary()
+ assert isinstance(summary, list)
+ assert len(summary) > 0
+ # Each item should have name and id
+ if summary:
+ assert "name" in summary[0] or "id" in summary[0]
diff --git a/uv.lock b/uv.lock
index fca4109..309782a 100644
--- a/uv.lock
+++ b/uv.lock
@@ -493,6 +493,7 @@ dev = [
[package.dev-dependencies]
dev = [
{ name = "pytest" },
+ { name = "pytest-cov" },
]
[package.metadata]
@@ -511,7 +512,10 @@ requires-dist = [
provides-extras = ["dev"]
[package.metadata.requires-dev]
-dev = [{ name = "pytest", specifier = ">=8.3.5" }]
+dev = [
+ { name = "pytest", specifier = ">=8.3.5" },
+ { name = "pytest-cov", specifier = ">=7.0.0" },
+]
[[package]]
name = "markdown-it-py"