公開前の確認用サイト
日常訳NICHIJOYAKUEnglish ↗

OPEN SOURCE

software/tests/test_coverage_gaps.py

このサイトで読み、必要なファイルを保存できます。

日常訳はAIを用いて作成された翻訳です。人間の聖書学・原語の専門家による全訳の検証は確認されていません。既存の翻訳や原語資料と照らし合わせてお読みください。

← オープンソースの案内へ

software/tests/test_coverage_gaps.py457 行このファイルを保存 ↓

ZIPに含まれるものと同じ公開ソースです。行番号から、その行へのリンクを保存できます。

ファイルの確認情報SHA-256 39654c86a646ec3facfcd3835063e50261c96496ae6f4a1c2d5ef4210460e6d9
1"""Tests to fill coverage gaps in various modules."""23import json4import pytest5from pathlib import Path6from tempfile import TemporaryDirectory78from src.context.cross_references import (9    CrossReference,10    CrossReferenceManager,11    ReferenceType,12)13from src.context.glossary import GlossaryManager14from src.context.pericope_generator import PericopeGenerator15from src.models.glossary import EnhancedGlossaryTerm, TheologicalDomain16from src.utils.retry import RetryConfig, RetryState17from src.utils.validation import (18    normalize_book_name,19    parse_reference_string,20    validate_chapter,21)22from src.exceptions import InvalidReferenceError, RateLimitError232425class TestGlossaryMorphologicalMatching:26    """Tests for morphological glossary matching."""2728    def test_get_by_lemma(self) -> None:29        """Test getting terms by lemma."""30        terms = [31            EnhancedGlossaryTerm(32                original="λόγος",33                transliteration="logos",34                japanese="言葉",35                english="word",36                lemma="λογος",37            )38        ]39        manager = GlossaryManager(terms)4041        # Get by lemma42        found = manager.get_by_lemma("λογος")43        assert found is not None44        assert found.original == "λόγος"4546    def test_get_by_lemma_not_found(self) -> None:47        """Test getting by lemma returns None for unknown lemma."""48        terms = [49            EnhancedGlossaryTerm(50                original="λόγος",51                transliteration="logos",52                japanese="言葉",53                english="word",54            )55        ]56        manager = GlossaryManager(terms)5758        found = manager.get_by_lemma("unknown")59        assert found is None6061    def test_get_by_strongs(self) -> None:62        """Test getting terms by Strong's number."""63        terms = [64            EnhancedGlossaryTerm(65                original="λόγος",66                transliteration="logos",67                japanese="言葉",68                english="word",69                strongs_number="G3056",70            )71        ]72        manager = GlossaryManager(terms)7374        found = manager.get_by_strongs("G3056")75        assert found is not None76        assert found.original == "λόγος"7778    def test_get_by_strongs_case_insensitive(self) -> None:79        """Test Strong's lookup is case insensitive."""80        terms = [81            EnhancedGlossaryTerm(82                original="λόγος",83                transliteration="logos",84                japanese="言葉",85                english="word",86                strongs_number="G3056",87            )88        ]89        manager = GlossaryManager(terms)9091        # Lowercase should work too92        found = manager.get_by_strongs("g3056")93        assert found is not None94        assert found.original == "λόγος"9596    def test_get_by_strongs_not_found(self) -> None:97        """Test Strong's lookup returns None for unknown number."""98        terms = [99            EnhancedGlossaryTerm(100                original="λόγος",101                transliteration="logos",102                japanese="言葉",103                english="word",104            )105        ]106        manager = GlossaryManager(terms)107108        found = manager.get_by_strongs("G9999")109        assert found is None110111    def test_find_terms_with_inflected_forms(self) -> None:112        """Test finding terms via inflected forms."""113        terms = [114            EnhancedGlossaryTerm(115                original="λόγος",116                transliteration="logos",117                japanese="言葉",118                english="word",119                inflected_forms=["λόγον", "λόγου", "λόγῳ"],120            )121        ]122        manager = GlossaryManager(terms)123124        # Text containing inflected form125        text = "καὶ ὁ λόγον ἦν"126        found = manager.find_terms_in_text(text)127        assert len(found) == 1128        assert found[0].original == "λόγος"129130    def test_get_by_domain(self) -> None:131        """Test getting terms by theological domain."""132        terms = [133            EnhancedGlossaryTerm(134                original="λόγος",135                transliteration="logos",136                japanese="言葉",137                english="word",138                domain=TheologicalDomain.CHRISTOLOGY,139            ),140            EnhancedGlossaryTerm(141                original="σωτηρία",142                transliteration="soteria",143                japanese="救い",144                english="salvation",145                domain=TheologicalDomain.SOTERIOLOGY,146            ),147        ]148        manager = GlossaryManager(terms)149150        christology = manager.get_by_domain(TheologicalDomain.CHRISTOLOGY)151        assert len(christology) == 1152        assert christology[0].original == "λόγος"153154        soteriology = manager.get_by_domain(TheologicalDomain.SOTERIOLOGY)155        assert len(soteriology) == 1156        assert soteriology[0].original == "σωτηρία"157158    def test_get_by_any_form(self) -> None:159        """Test getting terms by any matchable form."""160        terms = [161            EnhancedGlossaryTerm(162                original="λόγος",163                transliteration="logos",164                japanese="言葉",165                english="word",166                lemma="λογος",167                inflected_forms=["λόγον"],168            )169        ]170        manager = GlossaryManager(terms)171172        # Should find by original173        found = manager.get_by_any_form("λόγος")174        assert found is not None175176        # Should find by lemma177        found = manager.get_by_any_form("λογος")178        assert found is not None179180        # Should find by inflected form181        found = manager.get_by_any_form("λόγον")182        assert found is not None183184185class TestCrossReferenceEdgeCases:186    """Tests for cross-reference edge cases."""187188    def test_find_references_to_verse_exact_match(self) -> None:189        """Test finding references to exact verse (not range)."""190        manager = CrossReferenceManager()191        ref = CrossReference(192            source_book="john",193            source_chapter=1,194            source_verse=1,195            target_book="genesis",196            target_chapter=1,197            target_verse_start=1,198        )199        manager.add_reference(ref)200201        # Should find reference TO verse 1202        found = manager.find_references_to_verse("genesis", 1, 1)203        assert len(found) == 1204205    def test_get_consistency_notes_no_hint(self) -> None:206        """Test consistency notes when no translation hint."""207        manager = CrossReferenceManager()208        ref = CrossReference(209            source_book="john",210            source_chapter=1,211            source_verse=23,212            target_book="isaiah",213            target_chapter=40,214            target_verse_start=3,215            reference_type=ReferenceType.QUOTATION,216            # No translation_hint217        )218        manager.add_reference(ref)219220        notes = manager.get_translation_consistency_notes("john", 1, 23)221        # Should still have note about quotation222        assert any("quotes" in n.lower() for n in notes)223224    def test_format_for_prompt_with_max(self) -> None:225        """Test prompt formatting respects max_references."""226        manager = CrossReferenceManager()227        for i in range(10):228            manager.add_reference(229                CrossReference(230                    source_book="john",231                    source_chapter=1,232                    source_verse=1,233                    target_book="genesis",234                    target_chapter=1,235                    target_verse_start=i + 1,236                )237            )238239        refs = manager.find_references_for_verse("john", 1, 1)240        formatted = manager.format_for_prompt(refs, max_references=3)241242        # Should only include 3243        assert formatted.count("genesis") == 3244245246class TestPericopeGeneratorEdgeCases:247    """Tests for pericope generator edge cases."""248249    def test_generate_pericopes_no_markers(self) -> None:250        """Test generating pericopes when no paragraph markers exist."""251        with TemporaryDirectory() as tmpdir:252            data_dir = Path(tmpdir)253            greek_dir = data_dir / "sources" / "greek"254            greek_dir.mkdir(parents=True)255256            # Greek data without paragraph markers257            greek_data = {258                "1:1": {"text": "Ἐν ἀρχῇ ἦν ὁ λόγος"},259                "1:2": {"text": "οὗτος ἦν ἐν ἀρχῇ"},260                "1:3": {"text": "πάντα δι' αὐτοῦ ἐγένετο"},261            }262            with (greek_dir / "test.json").open("w") as f:263                json.dump(greek_data, f)264265            generator = PericopeGenerator(data_dir)266            index = generator.generate_pericopes("test", min_size=1)267268            # Should create one pericope covering all verses269            assert len(index.pericopes) >= 1270271    def test_ref_sort_key_complex(self) -> None:272        """Test reference sort key with various formats."""273        with TemporaryDirectory() as tmpdir:274            data_dir = Path(tmpdir)275            (data_dir / "sources" / "greek").mkdir(parents=True)276277            generator = PericopeGenerator(data_dir)278279            # Standard refs280            assert generator._ref_sort_key("10:25") == (10, 25)281            assert generator._ref_sort_key("1:1") < generator._ref_sort_key("1:2")282            assert generator._ref_sort_key("1:10") < generator._ref_sort_key("2:1")283284    def test_split_large_pericope_exact_fit(self) -> None:285        """Test splitting when verses exactly fit max_size."""286        with TemporaryDirectory() as tmpdir:287            data_dir = Path(tmpdir)288            (data_dir / "sources" / "greek").mkdir(parents=True)289290            generator = PericopeGenerator(data_dir)291292            # 10 verses with max_size=10 should not split293            verses = [f"1:{i}" for i in range(1, 11)]294            result = generator._split_large_pericope(verses, max_size=10)295            assert len(result) == 1296            assert result[0] == verses297298299class TestRetryEdgeCases:300    """Tests for retry utility edge cases."""301302    def test_retry_state_with_rate_limit_retry_after(self) -> None:303        """Test retry state uses retry_after from rate limit error."""304        config = RetryConfig(305            base_delay=10.0,306            jitter=False,307            retryable_exceptions=(RateLimitError,),308        )309        state = RetryState(config)310311        error = RateLimitError("Rate limit", retry_after=0.001)312        state.should_retry(error)313314        # Wait should use retry_after, not base_delay315        import time316317        start = time.time()318        state.wait()319        elapsed = time.time() - start320321        # Should be very fast (using retry_after=0.001)322        assert elapsed < 1.0323324    def test_retry_config_jitter_bounds(self) -> None:325        """Test jitter stays within bounds."""326        config = RetryConfig(base_delay=1.0, jitter=True)327328        for _ in range(50):329            delay = config.get_delay(0)330            # Jitter should be +/- 25%331            assert 0.75 <= delay <= 1.25332333334class TestValidationEdgeCases:335    """Tests for validation edge cases."""336337    def test_parse_reference_numbered_book_variants(self) -> None:338        """Test parsing various numbered book formats."""339        # Space between number and name340        book, chapter, verse = parse_reference_string("1 John 1:1")341        assert book == "1john"342        assert chapter == 1343        assert verse == 1344345        # No space346        book, chapter, verse = parse_reference_string("1John 1:1")347        assert book == "1john"348349    def test_normalize_book_with_spaces_and_hyphens(self) -> None:350        """Test normalizing book names with various separators."""351        assert normalize_book_name("song of solomon") == "songofsolomon"352        assert normalize_book_name("song-of-solomon") == "songofsolomon"353354    def test_validate_chapter_known_books(self) -> None:355        """Test chapter validation for known books."""356        # John has 21 chapters357        validate_chapter("john", 21)  # Should pass358359        with pytest.raises(InvalidReferenceError):360            validate_chapter("john", 22)  # Should fail361362        # Psalms has 150 chapters363        validate_chapter("psalms", 150)  # Should pass364365366class TestEnhancedGlossaryTerm:367    """Tests for enhanced glossary term model."""368369    def test_get_all_matchable_forms_with_lemma(self) -> None:370        """Test getting all matchable forms."""371        term = EnhancedGlossaryTerm(372            original="λόγος",373            transliteration="logos",374            japanese="言葉",375            english="word",376            lemma="λογος",377            inflected_forms=["λόγον", "λόγου"],378        )379380        forms = term.get_all_matchable_forms()381        assert "λόγος" in forms382        # Lemma should be included if different from original383        assert "λογος" in forms384        assert "λόγον" in forms385        assert "λόγου" in forms386387    def test_get_all_matchable_forms_minimal(self) -> None:388        """Test matchable forms with minimal data."""389        term = EnhancedGlossaryTerm(390            original="λόγος",391            transliteration="logos",392            japanese="言葉",393            english="word",394        )395396        forms = term.get_all_matchable_forms()397        assert "λόγος" in forms398        # Transliteration is NOT included in get_all_matchable_forms399        # Only original, lemma, and inflected forms400        assert len(forms) == 1401402    def test_get_all_matchable_forms_same_lemma(self) -> None:403        """Test that lemma is not duplicated if same as original."""404        term = EnhancedGlossaryTerm(405            original="λόγος",406            transliteration="logos",407            japanese="言葉",408            english="word",409            lemma="λόγος",  # Same as original410        )411412        forms = term.get_all_matchable_forms()413        # Should not duplicate the original414        assert len(forms) == 1415        assert forms[0] == "λόγος"416417418class TestCrossReferenceManagerSaveLoad:419    """Tests for cross-reference save/load functionality."""420421    def test_save_and_load_with_all_fields(self) -> None:422        """Test saving and loading cross-references with all fields."""423        with TemporaryDirectory() as tmpdir:424            data_dir = Path(tmpdir)425            metadata_dir = data_dir / "metadata"426            metadata_dir.mkdir(parents=True)427428            manager = CrossReferenceManager()429            manager._data_dir = data_dir430431            # Add reference with all optional fields432            manager.add_reference(433                CrossReference(434                    source_book="john",435                    source_chapter=1,436                    source_verse=1,437                    target_book="genesis",438                    target_chapter=1,439                    target_verse_start=1,440                    target_verse_end=3,441                    reference_type=ReferenceType.ALLUSION,442                    note="Echoes Genesis",443                    translation_hint="Use similar phrasing",444                )445            )446447            manager.save()448449            # Load into new manager450            manager2 = CrossReferenceManager(data_dir)451            assert len(manager2) == 1452453            found = manager2.find_references_for_verse("john", 1, 1)454            assert len(found) == 1455            assert found[0].note == "Echoes Genesis"456            assert found[0].translation_hint == "Use similar phrasing"457