Pre-publication review draft
日常訳NICHIJOYAKU日本語 ↗

OPEN SOURCE

software/tests/test_proto_converters.py

Read on this site and download the files you need.

Nichijoyaku was created using AI. A complete review by human biblical-language and biblical-studies specialists has not been established. Read alongside established translations and original-language resources.

← Back to the open-source guide

software/tests/test_proto_converters.py1063 linesDownload this file ↓

This is the same source included in the download. Each line number is a link you can save or share.

File verification detailsSHA-256 63250e63b82fe3d970edad8bc0aedc442ef08d31d3f6aaf1c76b963078eaac90
1"""Tests for Protocol Buffer converter functions."""23from __future__ import annotations45from datetime import datetime, timezone67import pytest89from src.models.glossary import EnhancedGlossaryTerm, TheologicalDomain10from src.models.style import (11    EquivalenceApproach,12    FormalityLevel,13    GenderLanguage,14    ReadabilityLevel,15    TermConsistency,16    TheologicalTradition,17    TranslationStyle,18)19from src.models.verse import (20    BookMetadata,21    ChapterSummary,22    TranslationContext,23    TranslationOutput,24    Verse,25    VerseReference,26)27from src.protos.converters import (28    AnnotatedWordProto,29    BookMetadataProto,30    ChapterSummaryProto,31    ConfidenceLevel,32    FuriganaAnnotationProto,33    GlossaryTermProto,34    TranslationPromptInputProto,35    TranslationResultProto,36    TranslationStyleProto,37    VerseProto,38    VerseReferenceProto,39    annotated_word_to_proto,40    book_metadata_to_proto,41    chapter_summary_to_proto,42    furigana_annotation_to_proto,43    glossary_term_to_proto,44    proto_to_translation_result,45    proto_to_verse,46    proto_to_verse_reference,47    translation_context_to_prompt_input,48    translation_result_to_proto,49    translation_style_to_proto,50    verse_reference_to_proto,51    verse_to_proto,52)53from src.protos.converters import EquivalenceApproach as EquivalenceApproachProto54from src.protos.converters import FormalityLevel as FormalityLevelProto55from src.protos.converters import GenderLanguage as GenderLanguageProto56from src.protos.converters import MatchType as MatchTypeProto57from src.protos.converters import ReadabilityLevel as ReadabilityLevelProto58from src.protos.converters import TermConsistency as TermConsistencyProto59from src.protos.converters import BiblicalTestament60from src.protos.converters import TheologicalDomain as TheologicalDomainProto61from src.protos.converters import TheologicalTradition as TheologicalTraditionProto62from src.utils.furigana import FuriganaAnnotation636465class TestVerseReferenceConversion:66    """Tests for VerseReference conversions."""6768    def test_verse_reference_to_proto(self) -> None:69        """Test converting VerseReference to proto."""70        ref = VerseReference(book="john", chapter=1, verse=1)71        proto = verse_reference_to_proto(ref)7273        assert isinstance(proto, VerseReferenceProto)74        assert proto.book == "john"75        assert proto.chapter == 176        assert proto.verse == 17778    def test_proto_to_verse_reference(self) -> None:79        """Test converting proto to VerseReference."""80        proto = VerseReferenceProto(book="genesis", chapter=1, verse=1)81        ref = proto_to_verse_reference(proto)8283        assert isinstance(ref, VerseReference)84        assert ref.book == "genesis"85        assert ref.chapter == 186        assert ref.verse == 18788    def test_verse_reference_roundtrip(self) -> None:89        """Test roundtrip conversion preserves data."""90        original = VerseReference(book="romans", chapter=8, verse=28)91        proto = verse_reference_to_proto(original)92        result = proto_to_verse_reference(proto)9394        assert result.book == original.book95        assert result.chapter == original.chapter96        assert result.verse == original.verse979899class TestVerseConversion:100    """Tests for Verse conversions."""101102    def test_verse_to_proto_full(self) -> None:103        """Test converting Verse with all fields to proto."""104        verse = Verse(105            reference=VerseReference(book="john", chapter=1, verse=1),106            greek_text="Ἐν ἀρχῇ ἦν ὁ λόγος",107            greek_translit="En arche en ho logos",108            hebrew_text=None,109            hebrew_translit=None,110            japanese_meiji="太初に言あり",111            japanese_taisho="太初に言あり、言は神と共にあり",112            english_kjv="In the beginning was the Word",113            english_asv="In the beginning was the Word",114        )115        proto = verse_to_proto(verse)116117        assert isinstance(proto, VerseProto)118        assert proto.reference.book == "john"119        assert proto.greek_text == "Ἐν ἀρχῇ ἦν ὁ λόγος"120        assert proto.greek_translit == "En arche en ho logos"121        assert proto.hebrew_text is None122        assert proto.japanese_meiji == "太初に言あり"123124    def test_verse_to_proto_minimal(self) -> None:125        """Test converting Verse with minimal fields to proto."""126        verse = Verse(127            reference=VerseReference(book="john", chapter=1, verse=1),128        )129        proto = verse_to_proto(verse)130131        assert proto.greek_text is None132        assert proto.hebrew_text is None133        assert proto.english_kjv is None134135    def test_proto_to_verse(self) -> None:136        """Test converting proto to Verse."""137        proto = VerseProto(138            reference=VerseReferenceProto(book="john", chapter=1, verse=1),139            greek_text="Ἐν ἀρχῇ",140            english_kjv="In the beginning",141        )142        verse = proto_to_verse(proto)143144        assert isinstance(verse, Verse)145        assert verse.reference.book == "john"146        assert verse.greek_text == "Ἐν ἀρχῇ"147        assert verse.english_kjv == "In the beginning"148149    def test_verse_roundtrip(self) -> None:150        """Test roundtrip conversion preserves data."""151        original = Verse(152            reference=VerseReference(book="matthew", chapter=5, verse=3),153            greek_text="Μακάριοι οἱ πτωχοὶ τῷ πνεύματι",154            english_kjv="Blessed are the poor in spirit",155        )156        proto = verse_to_proto(original)157        result = proto_to_verse(proto)158159        assert result.reference.book == original.reference.book160        assert result.greek_text == original.greek_text161        assert result.english_kjv == original.english_kjv162163164class TestBookMetadataConversion:165    """Tests for BookMetadata conversions."""166167    def test_book_metadata_to_proto_nt(self) -> None:168        """Test converting NT BookMetadata to proto."""169        metadata = BookMetadata(170            book="john",171            name_japanese="ヨハネによる福音書",172            name_english="Gospel of John",173            testament="NT",174            author="John the Apostle",175            themes=["Word", "Light", "Life", "Love"],176            description="The fourth Gospel",177            chapter_count=21,178            verse_count=879,179        )180        proto = book_metadata_to_proto(metadata)181182        assert isinstance(proto, BookMetadataProto)183        assert proto.book == "john"184        assert proto.name_japanese == "ヨハネによる福音書"185        assert proto.testament == BiblicalTestament.NEW_TESTAMENT186        assert proto.themes == ["Word", "Light", "Life", "Love"]187        assert proto.chapter_count == 21188189    def test_book_metadata_to_proto_ot(self) -> None:190        """Test converting OT BookMetadata to proto."""191        metadata = BookMetadata(192            book="genesis",193            name_japanese="創世記",194            name_english="Genesis",195            testament="OT",196            author="Moses",197            themes=["Creation", "Covenant"],198            description="The first book",199            chapter_count=50,200            verse_count=1533,201        )202        proto = book_metadata_to_proto(metadata)203204        assert proto.testament == BiblicalTestament.OLD_TESTAMENT205206207class TestChapterSummaryConversion:208    """Tests for ChapterSummary conversions."""209210    def test_chapter_summary_to_proto(self) -> None:211        """Test converting ChapterSummary to proto."""212        summary = ChapterSummary(213            book="john",214            chapter=1,215            summary="The prologue introduces the Word",216            key_events=["Word became flesh", "John testifies"],217            key_characters=["Jesus", "John the Baptist"],218            key_themes=["Word", "Light", "Witness"],219        )220        proto = chapter_summary_to_proto(summary)221222        assert isinstance(proto, ChapterSummaryProto)223        assert proto.book == "john"224        assert proto.chapter == 1225        assert proto.summary == "The prologue introduces the Word"226        assert "Word became flesh" in proto.key_events227        assert "Jesus" in proto.key_characters228        assert "Light" in proto.key_themes229230231class TestGlossaryTermConversion:232    """Tests for GlossaryTerm conversions."""233234    def test_glossary_term_to_proto_full(self) -> None:235        """Test converting full GlossaryTerm to proto."""236        term = EnhancedGlossaryTerm(237            original="λόγος",238            transliteration="logos",239            japanese="言",240            english="Word",241            notes="Theological term for divine Word",242            lemma="λόγος",243            inflected_forms=["λόγον", "λόγου"],244            strongs_number="G3056",245            definition="Word, reason, speech",246            semantic_range=["word", "reason", "account"],247            domain=TheologicalDomain.CHRISTOLOGY,248            usage_examples=["John 1:1"],249            frequency=330,250            related_terms=["ῥῆμα"],251            avoid_translations=["言葉だけ"],252        )253        proto = glossary_term_to_proto(term)254255        assert isinstance(proto, GlossaryTermProto)256        assert proto.original == "λόγος"257        assert proto.transliteration == "logos"258        assert proto.japanese == "言"259        assert proto.strongs_number == "G3056"260        assert proto.domain == TheologicalDomainProto.CHRISTOLOGY261        assert "λόγον" in proto.inflected_forms262        assert proto.frequency == 330263264    def test_glossary_term_to_proto_minimal(self) -> None:265        """Test converting minimal GlossaryTerm to proto."""266        term = EnhancedGlossaryTerm(267            original="ἀγάπη",268            transliteration="agape",269            japanese="愛",270            english="love",271        )272        proto = glossary_term_to_proto(term)273274        assert proto.original == "ἀγάπη"275        assert proto.domain == TheologicalDomainProto.GENERAL276        assert proto.notes is None277        assert proto.inflected_forms == []278279    def test_all_theological_domains(self) -> None:280        """Test all theological domain conversions."""281        domains = [282            (TheologicalDomain.CHRISTOLOGY, TheologicalDomainProto.CHRISTOLOGY),283            (TheologicalDomain.SOTERIOLOGY, TheologicalDomainProto.SOTERIOLOGY),284            (TheologicalDomain.ESCHATOLOGY, TheologicalDomainProto.ESCHATOLOGY),285            (TheologicalDomain.PNEUMATOLOGY, TheologicalDomainProto.PNEUMATOLOGY),286            (TheologicalDomain.ECCLESIOLOGY, TheologicalDomainProto.ECCLESIOLOGY),287            (TheologicalDomain.HAMARTIOLOGY, TheologicalDomainProto.HAMARTIOLOGY),288            (TheologicalDomain.THEOLOGY_PROPER, TheologicalDomainProto.THEOLOGY_PROPER),289            (TheologicalDomain.ANTHROPOLOGY, TheologicalDomainProto.ANTHROPOLOGY),290            (TheologicalDomain.GENERAL, TheologicalDomainProto.GENERAL),291        ]292293        for model_domain, expected_proto in domains:294            term = EnhancedGlossaryTerm(295                original="test",296                transliteration="test",297                japanese="テスト",298                english="test",299                domain=model_domain,300            )301            proto = glossary_term_to_proto(term)302            assert proto.domain == expected_proto303304305class TestTranslationStyleConversion:306    """Tests for TranslationStyle conversions."""307308    def test_translation_style_to_proto_default(self) -> None:309        """Test converting default TranslationStyle to proto."""310        style = TranslationStyle()311        proto = translation_style_to_proto(style)312313        assert isinstance(proto, TranslationStyleProto)314        assert proto.equivalence_approach == EquivalenceApproachProto.DYNAMIC315        assert proto.gender_language == GenderLanguageProto.MODERATE_INCLUSIVE316        assert proto.readability_level == ReadabilityLevelProto.WEB_NATIVE317        assert proto.theological_tradition == TheologicalTraditionProto.EVANGELICAL318        assert proto.term_consistency == TermConsistencyProto.BALANCED319        assert proto.formality_level == FormalityLevelProto.STANDARD320        assert "なり" in proto.avoid_archaic321322    def test_translation_style_to_proto_custom(self) -> None:323        """Test converting custom TranslationStyle to proto."""324        style = TranslationStyle(325            equivalence_approach=EquivalenceApproach.FORMAL,326            gender_language=GenderLanguage.TRADITIONAL,327            readability_level=ReadabilityLevel.SCHOLARLY,328            theological_tradition=TheologicalTradition.CATHOLIC,329            textual_basis="Majority Text",330            term_consistency=TermConsistency.STRICT,331            formality_level=FormalityLevel.FORMAL,332            avoid_archaic=[],333            target_style_reference="Literary",334            additional_instructions="Use formal language",335        )336        proto = translation_style_to_proto(style)337338        assert proto.equivalence_approach == EquivalenceApproachProto.FORMAL339        assert proto.gender_language == GenderLanguageProto.TRADITIONAL340        assert proto.readability_level == ReadabilityLevelProto.SCHOLARLY341        assert proto.theological_tradition == TheologicalTraditionProto.CATHOLIC342        assert proto.textual_basis == "Majority Text"343        assert proto.term_consistency == TermConsistencyProto.STRICT344        assert proto.formality_level == FormalityLevelProto.FORMAL_LEVEL345        assert proto.additional_instructions == "Use formal language"346347    def test_all_equivalence_approaches(self) -> None:348        """Test all equivalence approach conversions."""349        approaches = [350            (EquivalenceApproach.FORMAL, EquivalenceApproachProto.FORMAL),351            (EquivalenceApproach.DYNAMIC, EquivalenceApproachProto.DYNAMIC),352            (EquivalenceApproach.OPTIMAL, EquivalenceApproachProto.OPTIMAL),353        ]354        for model_approach, expected_proto in approaches:355            style = TranslationStyle(equivalence_approach=model_approach)356            proto = translation_style_to_proto(style)357            assert proto.equivalence_approach == expected_proto358359    def test_all_gender_languages(self) -> None:360        """Test all gender language conversions."""361        genders = [362            (GenderLanguage.TRADITIONAL, GenderLanguageProto.TRADITIONAL),363            (GenderLanguage.MODERATE_INCLUSIVE, GenderLanguageProto.MODERATE_INCLUSIVE),364            (GenderLanguage.FULLY_INCLUSIVE, GenderLanguageProto.FULLY_INCLUSIVE),365        ]366        for model_gender, expected_proto in genders:367            style = TranslationStyle(gender_language=model_gender)368            proto = translation_style_to_proto(style)369            assert proto.gender_language == expected_proto370371    def test_all_readability_levels(self) -> None:372        """Test all readability level conversions."""373        levels = [374            (ReadabilityLevel.SCHOLARLY, ReadabilityLevelProto.SCHOLARLY),375            (ReadabilityLevel.GENERAL_PUBLIC, ReadabilityLevelProto.GENERAL_PUBLIC),376            (ReadabilityLevel.CHILDREN, ReadabilityLevelProto.CHILDREN),377        ]378        for model_level, expected_proto in levels:379            style = TranslationStyle(readability_level=model_level)380            proto = translation_style_to_proto(style)381            assert proto.readability_level == expected_proto382383    def test_all_theological_traditions(self) -> None:384        """Test all theological tradition conversions."""385        traditions = [386            (TheologicalTradition.EVANGELICAL, TheologicalTraditionProto.EVANGELICAL),387            (TheologicalTradition.CATHOLIC, TheologicalTraditionProto.CATHOLIC),388            (TheologicalTradition.ORTHODOX, TheologicalTraditionProto.ORTHODOX),389            (TheologicalTradition.ECUMENICAL, TheologicalTraditionProto.ECUMENICAL),390        ]391        for model_tradition, expected_proto in traditions:392            style = TranslationStyle(theological_tradition=model_tradition)393            proto = translation_style_to_proto(style)394            assert proto.theological_tradition == expected_proto395396    def test_all_term_consistencies(self) -> None:397        """Test all term consistency conversions."""398        consistencies = [399            (TermConsistency.STRICT, TermConsistencyProto.STRICT),400            (TermConsistency.BALANCED, TermConsistencyProto.BALANCED),401            (TermConsistency.CONTEXTUAL, TermConsistencyProto.CONTEXTUAL),402        ]403        for model_consistency, expected_proto in consistencies:404            style = TranslationStyle(term_consistency=model_consistency)405            proto = translation_style_to_proto(style)406            assert proto.term_consistency == expected_proto407408    def test_all_formality_levels(self) -> None:409        """Test all formality level conversions."""410        levels = [411            (FormalityLevel.CASUAL, FormalityLevelProto.CASUAL),412            (FormalityLevel.STANDARD, FormalityLevelProto.STANDARD),413            (FormalityLevel.FORMAL, FormalityLevelProto.FORMAL_LEVEL),414        ]415        for model_level, expected_proto in levels:416            style = TranslationStyle(formality_level=model_level)417            proto = translation_style_to_proto(style)418            assert proto.formality_level == expected_proto419420421class TestFuriganaAnnotationConversion:422    """Tests for FuriganaAnnotation conversions."""423424    def test_furigana_annotation_to_proto(self) -> None:425        """Test converting FuriganaAnnotation to proto."""426        annotation = FuriganaAnnotation(427            start_offset=0,428            end_offset=1,429            base_text="神",430            reading="かみ",431            is_standard_reading=True,432            reading_source="dictionary",433        )434        proto = furigana_annotation_to_proto(annotation)435436        assert isinstance(proto, FuriganaAnnotationProto)437        assert proto.start_offset == 0438        assert proto.end_offset == 1439        assert proto.base_text == "神"440        assert proto.reading == "かみ"441        assert proto.is_standard_reading is True442        assert proto.reading_source == "dictionary"443444    def test_furigana_annotation_to_proto_non_standard(self) -> None:445        """Test converting non-standard FuriganaAnnotation to proto."""446        annotation = FuriganaAnnotation(447            start_offset=5,448            end_offset=7,449            base_text="聖書",450            reading="みことば",451            is_standard_reading=False,452            reading_source="theological",453        )454        proto = furigana_annotation_to_proto(annotation)455456        assert proto.is_standard_reading is False457        assert proto.reading_source == "theological"458459460class TestAnnotatedWordConversion:461    """Tests for AnnotatedWord conversions."""462463    def test_annotated_word_to_proto(self) -> None:464        """Test creating AnnotatedWordProto."""465        proto = annotated_word_to_proto(466            surface_form="神",467            lemma="神",468            part_of_speech="名詞",469            reading="かみ",470            start_offset=0,471            end_offset=1,472            alternative_readings=["しん", "じん"],473        )474475        assert isinstance(proto, AnnotatedWordProto)476        assert proto.surface_form == "神"477        assert proto.lemma == "神"478        assert proto.part_of_speech == "名詞"479        assert proto.reading == "かみ"480        assert proto.start_offset == 0481        assert proto.end_offset == 1482        assert proto.alternative_readings == ["しん", "じん"]483484    def test_annotated_word_to_proto_no_alternatives(self) -> None:485        """Test creating AnnotatedWordProto without alternatives."""486        proto = annotated_word_to_proto(487            surface_form="は",488            lemma="は",489            part_of_speech="助詞",490            reading="は",491            start_offset=1,492            end_offset=2,493        )494495        assert proto.alternative_readings == []496497498class TestTranslationContextConversion:499    """Tests for TranslationContext conversions."""500501    def test_translation_context_to_prompt_input_minimal(self) -> None:502        """Test converting minimal TranslationContext to proto."""503        context = TranslationContext(504            target_verse=Verse(505                reference=VerseReference(book="john", chapter=1, verse=1),506                greek_text="Ἐν ἀρχῇ ἦν ὁ λόγος",507            ),508        )509        proto = translation_context_to_prompt_input(510            context,511            system_prompt="You are a translator",512            user_prompt="Translate this verse",513            model="claude-3-opus",514            prompt_version="v1.0",515        )516517        assert isinstance(proto, TranslationPromptInputProto)518        assert proto.target_verse.reference.book == "john"519        assert proto.system_prompt == "You are a translator"520        assert proto.user_prompt == "Translate this verse"521        assert proto.model == "claude-3-opus"522        assert proto.prompt_version == "v1.0"523        assert proto.preceding_verses == []524        assert proto.following_verses == []525        assert proto.chapter_summary is None526        assert proto.book_metadata is None527528    def test_translation_context_to_prompt_input_full(self) -> None:529        """Test converting full TranslationContext to proto."""530        context = TranslationContext(531            target_verse=Verse(532                reference=VerseReference(book="john", chapter=1, verse=2),533                greek_text="οὗτος ἦν ἐν ἀρχῇ πρὸς τὸν θεόν",534            ),535            preceding_verses=[536                Verse(537                    reference=VerseReference(book="john", chapter=1, verse=1),538                    greek_text="Ἐν ἀρχῇ ἦν ὁ λόγος",539                ),540            ],541            following_verses=[542                Verse(543                    reference=VerseReference(book="john", chapter=1, verse=3),544                    greek_text="πάντα δι' αὐτοῦ ἐγένετο",545                ),546            ],547            chapter_summary=ChapterSummary(548                book="john",549                chapter=1,550                summary="The prologue",551                key_events=["Word became flesh"],552                key_characters=["Jesus"],553                key_themes=["Word"],554            ),555            book_metadata=BookMetadata(556                book="john",557                name_japanese="ヨハネ",558                name_english="John",559                testament="NT",560                author="John",561                themes=["Word"],562                description="Fourth Gospel",563                chapter_count=21,564                verse_count=879,565            ),566            glossary_terms=[567                EnhancedGlossaryTerm(568                    original="λόγος",569                    transliteration="logos",570                    japanese="言",571                    english="Word",572                ),573            ],574        )575        proto = translation_context_to_prompt_input(context)576577        assert len(proto.preceding_verses) == 1578        assert len(proto.following_verses) == 1579        assert proto.chapter_summary is not None580        assert proto.chapter_summary.book == "john"581        assert proto.book_metadata is not None582        assert proto.book_metadata.testament == BiblicalTestament.NEW_TESTAMENT583        assert len(proto.glossary_terms) == 1584        assert proto.glossary_terms[0].original == "λόγος"585586587class TestTranslationResultConversion:588    """Tests for TranslationResult conversions."""589590    def test_translation_result_to_proto(self) -> None:591        """Test converting TranslationOutput to proto."""592        output = TranslationOutput(593            reference=VerseReference(book="john", chapter=1, verse=1),594            text="初めに言葉があった",595            model="claude-3-opus",596            timestamp=datetime(2024, 1, 1, 12, 0, 0, tzinfo=timezone.utc),597            prompt_version="v1.0",598            sources_used=["greek", "kjv"],599            glossary_terms_applied=["λόγος"],600        )601602        furigana_annotations = [603            FuriganaAnnotation(0, 2, "初め", "はじめ"),604            FuriganaAnnotation(3, 5, "言葉", "ことば"),605        ]606607        proto = translation_result_to_proto(608            output,609            text_with_furigana="初め[はじめ]に言葉[ことば]があった",610            furigana_annotations=furigana_annotations,611            confidence=ConfidenceLevel.HIGH,612            confidence_score=0.95,613            input_hash="abc123",614        )615616        assert isinstance(proto, TranslationResultProto)617        assert proto.reference.book == "john"618        assert proto.text == "初めに言葉があった"619        assert proto.text_with_inline_furigana == "初め[はじめ]に言葉[ことば]があった"620        assert len(proto.furigana_annotations) == 2621        assert proto.model == "claude-3-opus"622        assert proto.confidence == ConfidenceLevel.HIGH623        assert proto.confidence_score == 0.95624        assert proto.input_hash == "abc123"625        assert "greek" in proto.sources_used626        assert "λόγος" in proto.glossary_terms_applied627628    def test_translation_result_to_proto_minimal(self) -> None:629        """Test converting TranslationOutput with minimal data."""630        output = TranslationOutput(631            reference=VerseReference(book="john", chapter=1, verse=1),632            text="初めに言葉があった",633            model="claude-3-opus",634        )635        proto = translation_result_to_proto(output)636637        assert proto.text_with_inline_furigana == "初めに言葉があった"  # Falls back to text638        assert proto.furigana_annotations == []639        assert proto.confidence == ConfidenceLevel.CONFIDENCE_LEVEL_UNSPECIFIED640        assert proto.confidence_score == 0.0641642    def test_proto_to_translation_result(self) -> None:643        """Test converting proto to TranslationOutput."""644        proto = TranslationResultProto(645            reference=VerseReferenceProto(book="john", chapter=1, verse=1),646            text="初めに言葉があった",647            model="claude-3-opus",648            timestamp=datetime(2024, 1, 1, 12, 0, 0, tzinfo=timezone.utc),649            prompt_version="v1.0",650            sources_used=["greek"],651            glossary_terms_applied=["λόγος"],652        )653        output = proto_to_translation_result(proto)654655        assert isinstance(output, TranslationOutput)656        assert output.reference.book == "john"657        assert output.text == "初めに言葉があった"658        assert output.model == "claude-3-opus"659        assert "greek" in output.sources_used660        assert "λόγος" in output.glossary_terms_applied661662    def test_translation_result_roundtrip(self) -> None:663        """Test roundtrip conversion preserves basic data."""664        original = TranslationOutput(665            reference=VerseReference(book="matthew", chapter=5, verse=3),666            text="心の貧しい人々は幸いです",667            model="claude-3-opus",668            prompt_version="v2.0",669            sources_used=["greek", "meiji"],670            glossary_terms_applied=["μακάριοι"],671        )672        proto = translation_result_to_proto(original)673        result = proto_to_translation_result(proto)674675        assert result.reference.book == original.reference.book676        assert result.text == original.text677        assert result.model == original.model678        assert result.prompt_version == original.prompt_version679        assert list(result.sources_used) == list(original.sources_used)680681682class TestProtoEnums:683    """Tests for proto enum values."""684685    def test_testament_enum_values(self) -> None:686        """Test BiblicalTestament enum has correct values."""687        assert BiblicalTestament.TESTAMENT_UNSPECIFIED == 0688        assert BiblicalTestament.OLD_TESTAMENT == 1689        assert BiblicalTestament.NEW_TESTAMENT == 2690691    def test_confidence_level_enum_values(self) -> None:692        """Test ConfidenceLevel enum has correct values."""693        assert ConfidenceLevel.CONFIDENCE_LEVEL_UNSPECIFIED == 0694        assert ConfidenceLevel.LOW == 1695        assert ConfidenceLevel.MEDIUM == 2696        assert ConfidenceLevel.HIGH == 3697        assert ConfidenceLevel.VERY_HIGH == 4698699    def test_match_type_enum_values(self) -> None:700        """Test MatchType enum has correct values."""701        assert MatchTypeProto.MATCH_TYPE_UNSPECIFIED == 0702        assert MatchTypeProto.EXACT == 1703        assert MatchTypeProto.FUZZY == 2704        assert MatchTypeProto.PHRASE == 3705706    def test_segment_level_enum_values(self) -> None:707        """Test SegmentLevel enum has correct values."""708        from src.protos.converters import SegmentLevel as SegmentLevelProto709710        assert SegmentLevelProto.SEGMENT_LEVEL_UNSPECIFIED == 0711        assert SegmentLevelProto.VERSE == 1712        assert SegmentLevelProto.PHRASE == 2713714715class TestTMSegmentConversion:716    """Tests for TMSegment conversions."""717718    def test_tm_segment_to_proto(self) -> None:719        """Test converting TMSegment to proto."""720        from src.models.translation_memory import SegmentLevel, TMSegment721        from src.protos.converters import TMSegmentProto, tm_segment_to_proto722723        segment = TMSegment(724            id="test-id-123",725            source_text="Ἐν ἀρχῇ ἦν ὁ λόγος",726            source_normalized="εν αρχη ην ο λογος",727            target_text="初めに言葉があった",728            reference=VerseReference(book="john", chapter=1, verse=1),729            level=SegmentLevel.VERSE,730            reuse_count=5,731            metadata={"model": "claude-3-opus"},732        )733        proto = tm_segment_to_proto(segment)734735        assert isinstance(proto, TMSegmentProto)736        assert proto.id == "test-id-123"737        assert proto.source_text == "Ἐν ἀρχῇ ἦν ὁ λόγος"738        assert proto.source_normalized == "εν αρχη ην ο λογος"739        assert proto.target_text == "初めに言葉があった"740        assert proto.reference.book == "john"741        assert proto.reference.chapter == 1742        assert proto.reference.verse == 1743        assert proto.reuse_count == 5744        assert proto.metadata == {"model": "claude-3-opus"}745746    def test_proto_to_tm_segment(self) -> None:747        """Test converting proto to TMSegment."""748        from src.models.translation_memory import TMSegment749        from src.protos.converters import (750            SegmentLevel,751            TMSegmentProto,752            VerseReferenceProto,753            proto_to_tm_segment,754        )755756        proto = TMSegmentProto(757            id="proto-id-456",758            source_text="Source text",759            source_normalized="source text",760            target_text="Target text",761            reference=VerseReferenceProto(book="genesis", chapter=1, verse=1),762            level=SegmentLevel.PHRASE,763            reuse_count=3,764        )765        segment = proto_to_tm_segment(proto)766767        assert isinstance(segment, TMSegment)768        assert segment.id == "proto-id-456"769        assert segment.source_text == "Source text"770        assert segment.target_text == "Target text"771        assert segment.reference.book == "genesis"772773    def test_tm_segment_roundtrip(self) -> None:774        """Test roundtrip conversion preserves data."""775        from src.models.translation_memory import SegmentLevel, TMSegment776        from src.protos.converters import proto_to_tm_segment, tm_segment_to_proto777778        original = TMSegment(779            id="roundtrip-id",780            source_text="Original source",781            source_normalized="original source",782            target_text="Original target",783            reference=VerseReference(book="matthew", chapter=5, verse=3),784            level=SegmentLevel.VERSE,785            reuse_count=10,786            metadata={"key": "value"},787        )788        proto = tm_segment_to_proto(original)789        result = proto_to_tm_segment(proto)790791        assert result.id == original.id792        assert result.source_text == original.source_text793        assert result.target_text == original.target_text794        assert result.reference.book == original.reference.book795        assert result.reuse_count == original.reuse_count796        assert result.metadata == original.metadata797798799class TestTMMatchConversion:800    """Tests for TMMatch conversions."""801802    def test_tm_match_to_proto(self) -> None:803        """Test converting TMMatch to proto."""804        from src.models.translation_memory import MatchType, SegmentLevel, TMMatch, TMSegment805        from src.protos.converters import TranslationMemoryMatchProto, tm_match_to_proto806807        segment = TMSegment(808            id="match-segment",809            source_text="Source",810            source_normalized="source",811            target_text="Target",812            reference=VerseReference(book="john", chapter=1, verse=1),813            level=SegmentLevel.VERSE,814        )815        match = TMMatch(816            segment=segment,817            similarity=0.95,818            match_type=MatchType.EXACT,819        )820        proto = tm_match_to_proto(match)821822        assert isinstance(proto, TranslationMemoryMatchProto)823        assert proto.segment.id == "match-segment"824        assert proto.similarity == 0.95825        assert proto.match_type == MatchTypeProto.EXACT826827    def test_proto_to_tm_match(self) -> None:828        """Test converting proto to TMMatch."""829        from src.models.translation_memory import TMMatch830        from src.protos.converters import (831            MatchType,832            SegmentLevel,833            TMSegmentProto,834            TranslationMemoryMatchProto,835            VerseReferenceProto,836            proto_to_tm_match,837        )838839        segment_proto = TMSegmentProto(840            id="proto-match-segment",841            source_text="Proto source",842            source_normalized="proto source",843            target_text="Proto target",844            reference=VerseReferenceProto(book="genesis", chapter=2, verse=1),845            level=SegmentLevel.VERSE,846        )847        proto = TranslationMemoryMatchProto(848            segment=segment_proto,849            similarity=0.85,850            match_type=MatchType.FUZZY,851        )852        match = proto_to_tm_match(proto)853854        assert isinstance(match, TMMatch)855        assert match.segment.id == "proto-match-segment"856        assert match.similarity == 0.85857858    def test_all_match_types(self) -> None:859        """Test all match type conversions."""860        from src.models.translation_memory import (861            MatchType as MatchTypeModel,862            SegmentLevel,863            TMMatch,864            TMSegment,865        )866        from src.protos.converters import MatchType as MatchTypeProtoEnum867        from src.protos.converters import tm_match_to_proto868869        match_types = [870            (MatchTypeModel.EXACT, MatchTypeProtoEnum.EXACT),871            (MatchTypeModel.FUZZY, MatchTypeProtoEnum.FUZZY),872            (MatchTypeModel.PHRASE, MatchTypeProtoEnum.PHRASE),873        ]874875        for model_type, expected_proto in match_types:876            segment = TMSegment(877                id="test",878                source_text="S",879                source_normalized="s",880                target_text="T",881                reference=VerseReference(book="john", chapter=1, verse=1),882                level=SegmentLevel.VERSE,883            )884            match = TMMatch(885                segment=segment,886                similarity=0.9,887                match_type=model_type,888            )889            proto = tm_match_to_proto(match)890            assert proto.match_type == expected_proto891892893class TestProvenanceConversion:894    """Tests for TranslationProvenance conversions."""895896    def test_provenance_to_proto(self) -> None:897        """Test converting TranslationProvenance to proto."""898        from src.orchestration.provenance import TranslationProvenance899        from src.protos.converters import TranslationProvenanceProto, provenance_to_proto900901        provenance = TranslationProvenance(902            source_texts={"greek": "Greek text", "kjv": "KJV text"},903            source_hashes={"greek": "hash1", "kjv": "hash2"},904            preceding_verses=["john 1:1", "john 1:2"],905            following_verses=["john 1:4"],906            pericope_id="john-1-prologue",907            style_config_hash="style-hash",908            glossary_hash="glossary-hash",909            prompt_version="v2.0",910            model="claude-3-opus",911            system_prompt="You are a translator",912            user_prompt="Translate this",913            tm_matches_used=[{"source": "s", "target": "t", "similarity": 0.9}],914            attempt_number=2,915            evaluation_score=85.5,916            evaluation_flags=["needs_review"],917            chapter_summary="Chapter about love",918            book_metadata={"name": "John"},919        )920        proto = provenance_to_proto(provenance)921922        assert isinstance(proto, TranslationProvenanceProto)923        assert proto.source_texts == {"greek": "Greek text", "kjv": "KJV text"}924        assert proto.source_hashes == {"greek": "hash1", "kjv": "hash2"}925        assert proto.preceding_verses == ["john 1:1", "john 1:2"]926        assert proto.following_verses == ["john 1:4"]927        assert proto.pericope_id == "john-1-prologue"928        assert proto.model == "claude-3-opus"929        assert proto.prompt_version == "v2.0"930        assert proto.attempt_number == 2931        assert proto.evaluation_score == 85.5932        assert proto.evaluation_flags == ["needs_review"]933934    def test_proto_to_provenance(self) -> None:935        """Test converting proto to TranslationProvenance."""936        from datetime import datetime, timezone937938        from src.orchestration.provenance import TranslationProvenance939        from src.protos.converters import TranslationProvenanceProto, proto_to_provenance940941        started = datetime(2024, 1, 1, 12, 0, 0, tzinfo=timezone.utc)942        completed = datetime(2024, 1, 1, 12, 0, 30, tzinfo=timezone.utc)943944        proto = TranslationProvenanceProto(945            source_texts={"greek": "Text"},946            source_hashes={"greek": "hash"},947            model="claude-3-haiku",948            prompt_version="v1.0",949            started_at=started,950            completed_at=completed,951        )952        provenance = proto_to_provenance(proto)953954        assert isinstance(provenance, TranslationProvenance)955        assert provenance.source_texts == {"greek": "Text"}956        assert provenance.model == "claude-3-haiku"957        assert provenance.started_at == started958        assert provenance.completed_at == completed959960    def test_provenance_roundtrip(self) -> None:961        """Test roundtrip conversion preserves data."""962        from datetime import datetime, timezone963964        from src.orchestration.provenance import TranslationProvenance965        from src.protos.converters import proto_to_provenance, provenance_to_proto966967        original = TranslationProvenance(968            source_texts={"greek": "Greek", "hebrew": "Hebrew"},969            source_hashes={"greek": "h1", "hebrew": "h2"},970            preceding_verses=["gen 1:1"],971            following_verses=["gen 1:3"],972            model="claude-opus",973            prompt_version="v3.0",974            system_prompt="System",975            user_prompt="User",976            attempt_number=1,977            started_at=datetime(2024, 6, 15, 10, 0, 0, tzinfo=timezone.utc),978            completed_at=datetime(2024, 6, 15, 10, 0, 45, tzinfo=timezone.utc),979            evaluation_score=92.0,980            evaluation_flags=["high_quality"],981        )982        proto = provenance_to_proto(original)983        result = proto_to_provenance(proto)984985        assert result.source_texts == original.source_texts986        assert result.source_hashes == original.source_hashes987        assert result.model == original.model988        assert result.prompt_version == original.prompt_version989        assert result.attempt_number == original.attempt_number990        assert result.started_at == original.started_at991        assert result.completed_at == original.completed_at992        assert result.evaluation_score == original.evaluation_score993        assert result.evaluation_flags == original.evaluation_flags994995996class TestTranslationPromptInputWithTMMatches:997    """Tests for TranslationPromptInputProto with TM matches."""998999    def test_prompt_input_with_tm_matches(self) -> None:1000        """Test TranslationPromptInputProto includes tm_matches field."""1001        from src.protos.converters import (1002            MatchType,1003            SegmentLevel,1004            TMSegmentProto,1005            TranslationMemoryMatchProto,1006            TranslationPromptInputProto,1007            VerseReferenceProto,1008        )10091010        segment = TMSegmentProto(1011            id="tm-1",1012            source_text="Source",1013            source_normalized="source",1014            target_text="Target",1015            reference=VerseReferenceProto(book="john", chapter=1, verse=1),1016            level=SegmentLevel.VERSE,1017        )1018        match = TranslationMemoryMatchProto(1019            segment=segment,1020            similarity=0.95,1021            match_type=MatchType.EXACT,1022        )10231024        proto = TranslationPromptInputProto(1025            system_prompt="System",1026            user_prompt="User",1027            model="claude-3-opus",1028            tm_matches=[match],1029        )10301031        assert len(proto.tm_matches) == 11032        assert proto.tm_matches[0].segment.id == "tm-1"1033        assert proto.tm_matches[0].similarity == 0.95103410351036class TestTranslationResultWithProvenance:1037    """Tests for TranslationResultProto with provenance."""10381039    def test_result_with_provenance(self) -> None:1040        """Test TranslationResultProto includes provenance field."""1041        from src.protos.converters import (1042            TranslationProvenanceProto,1043            TranslationResultProto,1044            VerseReferenceProto,1045        )10461047        provenance = TranslationProvenanceProto(1048            model="claude-3-opus",1049            prompt_version="v1.0",1050            source_texts={"greek": "Greek text"},1051        )10521053        result = TranslationResultProto(1054            reference=VerseReferenceProto(book="john", chapter=1, verse=1),1055            text="Translation text",1056            model="claude-3-opus",1057            provenance=provenance,1058        )10591060        assert result.provenance is not None1061        assert result.provenance.model == "claude-3-opus"1062        assert result.provenance.source_texts == {"greek": "Greek text"}1063