"""Tests for context building and glossary management.""" import json import pytest from pathlib import Path from src.context.builder import ContextBuilder from src.context.glossary import GlossaryManager from src.data.loader import DataLoader from src.models.verse import GlossaryTerm, VerseReference @pytest.fixture def glossary_terms() -> list[GlossaryTerm]: """Create test glossary terms.""" return [ GlossaryTerm( original="λόγος", transliteration="logos", japanese="言葉", english="word", notes="The Word", ), GlossaryTerm( original="θεός", transliteration="theos", japanese="神", english="God", ), GlossaryTerm( original="ἀγάπη", transliteration="agape", japanese="愛", english="love", ), ] @pytest.fixture def temp_data_dir(tmp_path: Path) -> Path: """Create temporary data directory with test data.""" sources_dir = tmp_path / "sources" (sources_dir / "greek").mkdir(parents=True) (sources_dir / "japanese" / "meiji").mkdir(parents=True) (sources_dir / "japanese" / "taisho").mkdir(parents=True) (sources_dir / "english" / "kjv").mkdir(parents=True) (sources_dir / "english" / "asv").mkdir(parents=True) (tmp_path / "metadata" / "john").mkdir(parents=True) # Greek data with 5 verses greek_data = { "1:1": {"text": "Ἐν ἀρχῇ ἦν ὁ λόγος", "translit": "En archē ēn ho logos"}, "1:2": {"text": "οὗτος ἦν ἐν ἀρχῇ", "translit": "houtos ēn en archē"}, "1:3": {"text": "πάντα δι' αὐτοῦ ἐγένετο", "translit": "panta di autou egeneto"}, "1:4": {"text": "ἐν αὐτῷ ζωὴ ἦν", "translit": "en autō zōē ēn"}, "1:5": {"text": "τὸ φῶς ἐν τῇ σκοτίᾳ", "translit": "to phōs en tē skotia"}, } with (sources_dir / "greek" / "john.json").open("w") as f: json.dump(greek_data, f) # KJV data kjv_data = { "1:1": "In the beginning was the Word", "1:2": "The same was in the beginning with God", "1:3": "All things were made by him", "1:4": "In him was life", "1:5": "And the light shineth in darkness", } with (sources_dir / "english" / "kjv" / "john.json").open("w") as f: json.dump(kjv_data, f) # Book metadata book_metadata = { "book": "john", "name_japanese": "ヨハネによる福音書", "name_english": "Gospel of John", "testament": "NT", "author": "John the Apostle", "themes": ["love", "light"], "description": "The fourth Gospel", "chapter_count": 21, "verse_count": 879, } with (tmp_path / "metadata" / "john" / "book_context.json").open("w") as f: json.dump(book_metadata, f) # Chapter summaries chapter_summaries = { "1": { "summary": "The prologue introduces Jesus as the Word", "key_events": ["Word becomes flesh"], "key_characters": ["Jesus", "John"], "key_themes": ["Light", "Word"], } } with (tmp_path / "metadata" / "john" / "chapter_summaries.json").open("w") as f: json.dump(chapter_summaries, f) # Glossary glossary = { "terms": [ { "original": "λόγος", "transliteration": "logos", "japanese": "言葉", "english": "word", "notes": "The Word", } ] } with (tmp_path / "metadata" / "glossary.json").open("w") as f: json.dump(glossary, f) return tmp_path class TestGlossaryManager: """Tests for GlossaryManager.""" def test_init_empty(self) -> None: """Test initializing empty glossary.""" glossary = GlossaryManager() assert len(glossary) == 0 def test_init_with_terms(self, glossary_terms: list[GlossaryTerm]) -> None: """Test initializing with terms.""" glossary = GlossaryManager(glossary_terms) assert len(glossary) == 3 def test_add_term(self) -> None: """Test adding a term.""" glossary = GlossaryManager() term = GlossaryTerm( original="λόγος", transliteration="logos", japanese="言葉", english="word", ) glossary.add_term(term) assert len(glossary) == 1 def test_get_by_original(self, glossary_terms: list[GlossaryTerm]) -> None: """Test getting term by original.""" glossary = GlossaryManager(glossary_terms) term = glossary.get_by_original("λόγος") assert term is not None assert term.japanese == "言葉" def test_get_by_original_not_found( self, glossary_terms: list[GlossaryTerm] ) -> None: """Test getting non-existent term.""" glossary = GlossaryManager(glossary_terms) term = glossary.get_by_original("nonexistent") assert term is None def test_get_by_transliteration( self, glossary_terms: list[GlossaryTerm] ) -> None: """Test getting term by transliteration.""" glossary = GlossaryManager(glossary_terms) term = glossary.get_by_transliteration("logos") assert term is not None assert term.original == "λόγος" def test_get_by_transliteration_case_insensitive( self, glossary_terms: list[GlossaryTerm] ) -> None: """Test transliteration lookup is case insensitive.""" glossary = GlossaryManager(glossary_terms) term = glossary.get_by_transliteration("LOGOS") assert term is not None def test_find_terms_in_text_by_original( self, glossary_terms: list[GlossaryTerm] ) -> None: """Test finding terms by original in text.""" glossary = GlossaryManager(glossary_terms) found = glossary.find_terms_in_text("Ἐν ἀρχῇ ἦν ὁ λόγος") assert len(found) == 1 assert found[0].original == "λόγος" def test_find_terms_in_text_by_transliteration( self, glossary_terms: list[GlossaryTerm] ) -> None: """Test finding terms by transliteration in text.""" glossary = GlossaryManager(glossary_terms) found = glossary.find_terms_in_text("en archē ēn ho logos") assert len(found) == 1 assert found[0].original == "λόγος" def test_find_terms_in_text_multiple( self, glossary_terms: list[GlossaryTerm] ) -> None: """Test finding multiple terms.""" glossary = GlossaryManager(glossary_terms) found = glossary.find_terms_in_text("λόγος and θεός") assert len(found) == 2 def test_find_terms_no_duplicates( self, glossary_terms: list[GlossaryTerm] ) -> None: """Test no duplicate terms returned.""" glossary = GlossaryManager(glossary_terms) # Text with both original and transliteration found = glossary.find_terms_in_text("λόγος logos") assert len(found) == 1 def test_get_all_terms(self, glossary_terms: list[GlossaryTerm]) -> None: """Test getting all terms.""" glossary = GlossaryManager(glossary_terms) all_terms = glossary.get_all_terms() assert len(all_terms) == 3 def test_format_for_prompt_with_terms( self, glossary_terms: list[GlossaryTerm] ) -> None: """Test formatting terms for prompt.""" glossary = GlossaryManager(glossary_terms) formatted = glossary.format_for_prompt(glossary_terms[:2]) assert "λόγος" in formatted assert "言葉" in formatted assert "[The Word]" in formatted # Notes should be included def test_format_for_prompt_empty(self) -> None: """Test formatting empty list.""" glossary = GlossaryManager() formatted = glossary.format_for_prompt([]) assert "No specific glossary" in formatted class TestContextBuilder: """Tests for ContextBuilder.""" def test_build_context(self, temp_data_dir: Path) -> None: """Test building translation context.""" loader = DataLoader(temp_data_dir) glossary = GlossaryManager(loader.load_glossary()) builder = ContextBuilder(loader, glossary) ref = VerseReference(book="john", chapter=1, verse=3) context = builder.build_context(ref) assert context.target_verse.reference == ref assert len(context.preceding_verses) > 0 assert len(context.following_verses) > 0 def test_build_context_first_verse(self, temp_data_dir: Path) -> None: """Test context for first verse has no preceding.""" loader = DataLoader(temp_data_dir) glossary = GlossaryManager(loader.load_glossary()) builder = ContextBuilder(loader, glossary) ref = VerseReference(book="john", chapter=1, verse=1) context = builder.build_context(ref) assert len(context.preceding_verses) == 0 def test_build_context_with_metadata(self, temp_data_dir: Path) -> None: """Test context includes metadata.""" loader = DataLoader(temp_data_dir) glossary = GlossaryManager(loader.load_glossary()) builder = ContextBuilder(loader, glossary) ref = VerseReference(book="john", chapter=1, verse=1) context = builder.build_context(ref) assert context.book_metadata is not None assert context.chapter_summary is not None def test_build_context_finds_glossary_terms(self, temp_data_dir: Path) -> None: """Test context finds glossary terms in verse.""" loader = DataLoader(temp_data_dir) glossary = GlossaryManager(loader.load_glossary()) builder = ContextBuilder(loader, glossary) ref = VerseReference(book="john", chapter=1, verse=1) context = builder.build_context(ref) # John 1:1 has λόγος which is in our glossary assert len(context.glossary_terms) > 0 def test_context_window_size(self, temp_data_dir: Path) -> None: """Test context window size parameter.""" loader = DataLoader(temp_data_dir) glossary = GlossaryManager() builder = ContextBuilder(loader, glossary, context_window=2) ref = VerseReference(book="john", chapter=1, verse=3) context = builder.build_context(ref) # Should have at most 2 preceding and 2 following assert len(context.preceding_verses) <= 2 assert len(context.following_verses) <= 2