"""Tests for Protocol Buffer converter functions.""" from __future__ import annotations from datetime import datetime, timezone import pytest from src.models.glossary import EnhancedGlossaryTerm, TheologicalDomain from src.models.style import ( EquivalenceApproach, FormalityLevel, GenderLanguage, ReadabilityLevel, TermConsistency, TheologicalTradition, TranslationStyle, ) from src.models.verse import ( BookMetadata, ChapterSummary, TranslationContext, TranslationOutput, Verse, VerseReference, ) from src.protos.converters import ( AnnotatedWordProto, BookMetadataProto, ChapterSummaryProto, ConfidenceLevel, FuriganaAnnotationProto, GlossaryTermProto, TranslationPromptInputProto, TranslationResultProto, TranslationStyleProto, VerseProto, VerseReferenceProto, annotated_word_to_proto, book_metadata_to_proto, chapter_summary_to_proto, furigana_annotation_to_proto, glossary_term_to_proto, proto_to_translation_result, proto_to_verse, proto_to_verse_reference, translation_context_to_prompt_input, translation_result_to_proto, translation_style_to_proto, verse_reference_to_proto, verse_to_proto, ) from src.protos.converters import EquivalenceApproach as EquivalenceApproachProto from src.protos.converters import FormalityLevel as FormalityLevelProto from src.protos.converters import GenderLanguage as GenderLanguageProto from src.protos.converters import MatchType as MatchTypeProto from src.protos.converters import ReadabilityLevel as ReadabilityLevelProto from src.protos.converters import TermConsistency as TermConsistencyProto from src.protos.converters import BiblicalTestament from src.protos.converters import TheologicalDomain as TheologicalDomainProto from src.protos.converters import TheologicalTradition as TheologicalTraditionProto from src.utils.furigana import FuriganaAnnotation class TestVerseReferenceConversion: """Tests for VerseReference conversions.""" def test_verse_reference_to_proto(self) -> None: """Test converting VerseReference to proto.""" ref = VerseReference(book="john", chapter=1, verse=1) proto = verse_reference_to_proto(ref) assert isinstance(proto, VerseReferenceProto) assert proto.book == "john" assert proto.chapter == 1 assert proto.verse == 1 def test_proto_to_verse_reference(self) -> None: """Test converting proto to VerseReference.""" proto = VerseReferenceProto(book="genesis", chapter=1, verse=1) ref = proto_to_verse_reference(proto) assert isinstance(ref, VerseReference) assert ref.book == "genesis" assert ref.chapter == 1 assert ref.verse == 1 def test_verse_reference_roundtrip(self) -> None: """Test roundtrip conversion preserves data.""" original = VerseReference(book="romans", chapter=8, verse=28) proto = verse_reference_to_proto(original) result = proto_to_verse_reference(proto) assert result.book == original.book assert result.chapter == original.chapter assert result.verse == original.verse class TestVerseConversion: """Tests for Verse conversions.""" def test_verse_to_proto_full(self) -> None: """Test converting Verse with all fields to proto.""" verse = Verse( reference=VerseReference(book="john", chapter=1, verse=1), greek_text="Ἐν ἀρχῇ ἦν ὁ λόγος", greek_translit="En arche en ho logos", hebrew_text=None, hebrew_translit=None, japanese_meiji="太初に言あり", japanese_taisho="太初に言あり、言は神と共にあり", english_kjv="In the beginning was the Word", english_asv="In the beginning was the Word", ) proto = verse_to_proto(verse) assert isinstance(proto, VerseProto) assert proto.reference.book == "john" assert proto.greek_text == "Ἐν ἀρχῇ ἦν ὁ λόγος" assert proto.greek_translit == "En arche en ho logos" assert proto.hebrew_text is None assert proto.japanese_meiji == "太初に言あり" def test_verse_to_proto_minimal(self) -> None: """Test converting Verse with minimal fields to proto.""" verse = Verse( reference=VerseReference(book="john", chapter=1, verse=1), ) proto = verse_to_proto(verse) assert proto.greek_text is None assert proto.hebrew_text is None assert proto.english_kjv is None def test_proto_to_verse(self) -> None: """Test converting proto to Verse.""" proto = VerseProto( reference=VerseReferenceProto(book="john", chapter=1, verse=1), greek_text="Ἐν ἀρχῇ", english_kjv="In the beginning", ) verse = proto_to_verse(proto) assert isinstance(verse, Verse) assert verse.reference.book == "john" assert verse.greek_text == "Ἐν ἀρχῇ" assert verse.english_kjv == "In the beginning" def test_verse_roundtrip(self) -> None: """Test roundtrip conversion preserves data.""" original = Verse( reference=VerseReference(book="matthew", chapter=5, verse=3), greek_text="Μακάριοι οἱ πτωχοὶ τῷ πνεύματι", english_kjv="Blessed are the poor in spirit", ) proto = verse_to_proto(original) result = proto_to_verse(proto) assert result.reference.book == original.reference.book assert result.greek_text == original.greek_text assert result.english_kjv == original.english_kjv class TestBookMetadataConversion: """Tests for BookMetadata conversions.""" def test_book_metadata_to_proto_nt(self) -> None: """Test converting NT BookMetadata to proto.""" metadata = BookMetadata( book="john", name_japanese="ヨハネによる福音書", name_english="Gospel of John", testament="NT", author="John the Apostle", themes=["Word", "Light", "Life", "Love"], description="The fourth Gospel", chapter_count=21, verse_count=879, ) proto = book_metadata_to_proto(metadata) assert isinstance(proto, BookMetadataProto) assert proto.book == "john" assert proto.name_japanese == "ヨハネによる福音書" assert proto.testament == BiblicalTestament.NEW_TESTAMENT assert proto.themes == ["Word", "Light", "Life", "Love"] assert proto.chapter_count == 21 def test_book_metadata_to_proto_ot(self) -> None: """Test converting OT BookMetadata to proto.""" metadata = BookMetadata( book="genesis", name_japanese="創世記", name_english="Genesis", testament="OT", author="Moses", themes=["Creation", "Covenant"], description="The first book", chapter_count=50, verse_count=1533, ) proto = book_metadata_to_proto(metadata) assert proto.testament == BiblicalTestament.OLD_TESTAMENT class TestChapterSummaryConversion: """Tests for ChapterSummary conversions.""" def test_chapter_summary_to_proto(self) -> None: """Test converting ChapterSummary to proto.""" summary = ChapterSummary( book="john", chapter=1, summary="The prologue introduces the Word", key_events=["Word became flesh", "John testifies"], key_characters=["Jesus", "John the Baptist"], key_themes=["Word", "Light", "Witness"], ) proto = chapter_summary_to_proto(summary) assert isinstance(proto, ChapterSummaryProto) assert proto.book == "john" assert proto.chapter == 1 assert proto.summary == "The prologue introduces the Word" assert "Word became flesh" in proto.key_events assert "Jesus" in proto.key_characters assert "Light" in proto.key_themes class TestGlossaryTermConversion: """Tests for GlossaryTerm conversions.""" def test_glossary_term_to_proto_full(self) -> None: """Test converting full GlossaryTerm to proto.""" term = EnhancedGlossaryTerm( original="λόγος", transliteration="logos", japanese="言", english="Word", notes="Theological term for divine Word", lemma="λόγος", inflected_forms=["λόγον", "λόγου"], strongs_number="G3056", definition="Word, reason, speech", semantic_range=["word", "reason", "account"], domain=TheologicalDomain.CHRISTOLOGY, usage_examples=["John 1:1"], frequency=330, related_terms=["ῥῆμα"], avoid_translations=["言葉だけ"], ) proto = glossary_term_to_proto(term) assert isinstance(proto, GlossaryTermProto) assert proto.original == "λόγος" assert proto.transliteration == "logos" assert proto.japanese == "言" assert proto.strongs_number == "G3056" assert proto.domain == TheologicalDomainProto.CHRISTOLOGY assert "λόγον" in proto.inflected_forms assert proto.frequency == 330 def test_glossary_term_to_proto_minimal(self) -> None: """Test converting minimal GlossaryTerm to proto.""" term = EnhancedGlossaryTerm( original="ἀγάπη", transliteration="agape", japanese="愛", english="love", ) proto = glossary_term_to_proto(term) assert proto.original == "ἀγάπη" assert proto.domain == TheologicalDomainProto.GENERAL assert proto.notes is None assert proto.inflected_forms == [] def test_all_theological_domains(self) -> None: """Test all theological domain conversions.""" domains = [ (TheologicalDomain.CHRISTOLOGY, TheologicalDomainProto.CHRISTOLOGY), (TheologicalDomain.SOTERIOLOGY, TheologicalDomainProto.SOTERIOLOGY), (TheologicalDomain.ESCHATOLOGY, TheologicalDomainProto.ESCHATOLOGY), (TheologicalDomain.PNEUMATOLOGY, TheologicalDomainProto.PNEUMATOLOGY), (TheologicalDomain.ECCLESIOLOGY, TheologicalDomainProto.ECCLESIOLOGY), (TheologicalDomain.HAMARTIOLOGY, TheologicalDomainProto.HAMARTIOLOGY), (TheologicalDomain.THEOLOGY_PROPER, TheologicalDomainProto.THEOLOGY_PROPER), (TheologicalDomain.ANTHROPOLOGY, TheologicalDomainProto.ANTHROPOLOGY), (TheologicalDomain.GENERAL, TheologicalDomainProto.GENERAL), ] for model_domain, expected_proto in domains: term = EnhancedGlossaryTerm( original="test", transliteration="test", japanese="テスト", english="test", domain=model_domain, ) proto = glossary_term_to_proto(term) assert proto.domain == expected_proto class TestTranslationStyleConversion: """Tests for TranslationStyle conversions.""" def test_translation_style_to_proto_default(self) -> None: """Test converting default TranslationStyle to proto.""" style = TranslationStyle() proto = translation_style_to_proto(style) assert isinstance(proto, TranslationStyleProto) assert proto.equivalence_approach == EquivalenceApproachProto.DYNAMIC assert proto.gender_language == GenderLanguageProto.MODERATE_INCLUSIVE assert proto.readability_level == ReadabilityLevelProto.WEB_NATIVE assert proto.theological_tradition == TheologicalTraditionProto.EVANGELICAL assert proto.term_consistency == TermConsistencyProto.BALANCED assert proto.formality_level == FormalityLevelProto.STANDARD assert "なり" in proto.avoid_archaic def test_translation_style_to_proto_custom(self) -> None: """Test converting custom TranslationStyle to proto.""" style = TranslationStyle( equivalence_approach=EquivalenceApproach.FORMAL, gender_language=GenderLanguage.TRADITIONAL, readability_level=ReadabilityLevel.SCHOLARLY, theological_tradition=TheologicalTradition.CATHOLIC, textual_basis="Majority Text", term_consistency=TermConsistency.STRICT, formality_level=FormalityLevel.FORMAL, avoid_archaic=[], target_style_reference="Literary", additional_instructions="Use formal language", ) proto = translation_style_to_proto(style) assert proto.equivalence_approach == EquivalenceApproachProto.FORMAL assert proto.gender_language == GenderLanguageProto.TRADITIONAL assert proto.readability_level == ReadabilityLevelProto.SCHOLARLY assert proto.theological_tradition == TheologicalTraditionProto.CATHOLIC assert proto.textual_basis == "Majority Text" assert proto.term_consistency == TermConsistencyProto.STRICT assert proto.formality_level == FormalityLevelProto.FORMAL_LEVEL assert proto.additional_instructions == "Use formal language" def test_all_equivalence_approaches(self) -> None: """Test all equivalence approach conversions.""" approaches = [ (EquivalenceApproach.FORMAL, EquivalenceApproachProto.FORMAL), (EquivalenceApproach.DYNAMIC, EquivalenceApproachProto.DYNAMIC), (EquivalenceApproach.OPTIMAL, EquivalenceApproachProto.OPTIMAL), ] for model_approach, expected_proto in approaches: style = TranslationStyle(equivalence_approach=model_approach) proto = translation_style_to_proto(style) assert proto.equivalence_approach == expected_proto def test_all_gender_languages(self) -> None: """Test all gender language conversions.""" genders = [ (GenderLanguage.TRADITIONAL, GenderLanguageProto.TRADITIONAL), (GenderLanguage.MODERATE_INCLUSIVE, GenderLanguageProto.MODERATE_INCLUSIVE), (GenderLanguage.FULLY_INCLUSIVE, GenderLanguageProto.FULLY_INCLUSIVE), ] for model_gender, expected_proto in genders: style = TranslationStyle(gender_language=model_gender) proto = translation_style_to_proto(style) assert proto.gender_language == expected_proto def test_all_readability_levels(self) -> None: """Test all readability level conversions.""" levels = [ (ReadabilityLevel.SCHOLARLY, ReadabilityLevelProto.SCHOLARLY), (ReadabilityLevel.GENERAL_PUBLIC, ReadabilityLevelProto.GENERAL_PUBLIC), (ReadabilityLevel.CHILDREN, ReadabilityLevelProto.CHILDREN), ] for model_level, expected_proto in levels: style = TranslationStyle(readability_level=model_level) proto = translation_style_to_proto(style) assert proto.readability_level == expected_proto def test_all_theological_traditions(self) -> None: """Test all theological tradition conversions.""" traditions = [ (TheologicalTradition.EVANGELICAL, TheologicalTraditionProto.EVANGELICAL), (TheologicalTradition.CATHOLIC, TheologicalTraditionProto.CATHOLIC), (TheologicalTradition.ORTHODOX, TheologicalTraditionProto.ORTHODOX), (TheologicalTradition.ECUMENICAL, TheologicalTraditionProto.ECUMENICAL), ] for model_tradition, expected_proto in traditions: style = TranslationStyle(theological_tradition=model_tradition) proto = translation_style_to_proto(style) assert proto.theological_tradition == expected_proto def test_all_term_consistencies(self) -> None: """Test all term consistency conversions.""" consistencies = [ (TermConsistency.STRICT, TermConsistencyProto.STRICT), (TermConsistency.BALANCED, TermConsistencyProto.BALANCED), (TermConsistency.CONTEXTUAL, TermConsistencyProto.CONTEXTUAL), ] for model_consistency, expected_proto in consistencies: style = TranslationStyle(term_consistency=model_consistency) proto = translation_style_to_proto(style) assert proto.term_consistency == expected_proto def test_all_formality_levels(self) -> None: """Test all formality level conversions.""" levels = [ (FormalityLevel.CASUAL, FormalityLevelProto.CASUAL), (FormalityLevel.STANDARD, FormalityLevelProto.STANDARD), (FormalityLevel.FORMAL, FormalityLevelProto.FORMAL_LEVEL), ] for model_level, expected_proto in levels: style = TranslationStyle(formality_level=model_level) proto = translation_style_to_proto(style) assert proto.formality_level == expected_proto class TestFuriganaAnnotationConversion: """Tests for FuriganaAnnotation conversions.""" def test_furigana_annotation_to_proto(self) -> None: """Test converting FuriganaAnnotation to proto.""" annotation = FuriganaAnnotation( start_offset=0, end_offset=1, base_text="神", reading="かみ", is_standard_reading=True, reading_source="dictionary", ) proto = furigana_annotation_to_proto(annotation) assert isinstance(proto, FuriganaAnnotationProto) assert proto.start_offset == 0 assert proto.end_offset == 1 assert proto.base_text == "神" assert proto.reading == "かみ" assert proto.is_standard_reading is True assert proto.reading_source == "dictionary" def test_furigana_annotation_to_proto_non_standard(self) -> None: """Test converting non-standard FuriganaAnnotation to proto.""" annotation = FuriganaAnnotation( start_offset=5, end_offset=7, base_text="聖書", reading="みことば", is_standard_reading=False, reading_source="theological", ) proto = furigana_annotation_to_proto(annotation) assert proto.is_standard_reading is False assert proto.reading_source == "theological" class TestAnnotatedWordConversion: """Tests for AnnotatedWord conversions.""" def test_annotated_word_to_proto(self) -> None: """Test creating AnnotatedWordProto.""" proto = annotated_word_to_proto( surface_form="神", lemma="神", part_of_speech="名詞", reading="かみ", start_offset=0, end_offset=1, alternative_readings=["しん", "じん"], ) assert isinstance(proto, AnnotatedWordProto) assert proto.surface_form == "神" assert proto.lemma == "神" assert proto.part_of_speech == "名詞" assert proto.reading == "かみ" assert proto.start_offset == 0 assert proto.end_offset == 1 assert proto.alternative_readings == ["しん", "じん"] def test_annotated_word_to_proto_no_alternatives(self) -> None: """Test creating AnnotatedWordProto without alternatives.""" proto = annotated_word_to_proto( surface_form="は", lemma="は", part_of_speech="助詞", reading="は", start_offset=1, end_offset=2, ) assert proto.alternative_readings == [] class TestTranslationContextConversion: """Tests for TranslationContext conversions.""" def test_translation_context_to_prompt_input_minimal(self) -> None: """Test converting minimal TranslationContext to proto.""" context = TranslationContext( target_verse=Verse( reference=VerseReference(book="john", chapter=1, verse=1), greek_text="Ἐν ἀρχῇ ἦν ὁ λόγος", ), ) proto = translation_context_to_prompt_input( context, system_prompt="You are a translator", user_prompt="Translate this verse", model="claude-3-opus", prompt_version="v1.0", ) assert isinstance(proto, TranslationPromptInputProto) assert proto.target_verse.reference.book == "john" assert proto.system_prompt == "You are a translator" assert proto.user_prompt == "Translate this verse" assert proto.model == "claude-3-opus" assert proto.prompt_version == "v1.0" assert proto.preceding_verses == [] assert proto.following_verses == [] assert proto.chapter_summary is None assert proto.book_metadata is None def test_translation_context_to_prompt_input_full(self) -> None: """Test converting full TranslationContext to proto.""" context = TranslationContext( target_verse=Verse( reference=VerseReference(book="john", chapter=1, verse=2), greek_text="οὗτος ἦν ἐν ἀρχῇ πρὸς τὸν θεόν", ), preceding_verses=[ Verse( reference=VerseReference(book="john", chapter=1, verse=1), greek_text="Ἐν ἀρχῇ ἦν ὁ λόγος", ), ], following_verses=[ Verse( reference=VerseReference(book="john", chapter=1, verse=3), greek_text="πάντα δι' αὐτοῦ ἐγένετο", ), ], chapter_summary=ChapterSummary( book="john", chapter=1, summary="The prologue", key_events=["Word became flesh"], key_characters=["Jesus"], key_themes=["Word"], ), book_metadata=BookMetadata( book="john", name_japanese="ヨハネ", name_english="John", testament="NT", author="John", themes=["Word"], description="Fourth Gospel", chapter_count=21, verse_count=879, ), glossary_terms=[ EnhancedGlossaryTerm( original="λόγος", transliteration="logos", japanese="言", english="Word", ), ], ) proto = translation_context_to_prompt_input(context) assert len(proto.preceding_verses) == 1 assert len(proto.following_verses) == 1 assert proto.chapter_summary is not None assert proto.chapter_summary.book == "john" assert proto.book_metadata is not None assert proto.book_metadata.testament == BiblicalTestament.NEW_TESTAMENT assert len(proto.glossary_terms) == 1 assert proto.glossary_terms[0].original == "λόγος" class TestTranslationResultConversion: """Tests for TranslationResult conversions.""" def test_translation_result_to_proto(self) -> None: """Test converting TranslationOutput to proto.""" output = TranslationOutput( reference=VerseReference(book="john", chapter=1, verse=1), text="初めに言葉があった", model="claude-3-opus", timestamp=datetime(2024, 1, 1, 12, 0, 0, tzinfo=timezone.utc), prompt_version="v1.0", sources_used=["greek", "kjv"], glossary_terms_applied=["λόγος"], ) furigana_annotations = [ FuriganaAnnotation(0, 2, "初め", "はじめ"), FuriganaAnnotation(3, 5, "言葉", "ことば"), ] proto = translation_result_to_proto( output, text_with_furigana="初め[はじめ]に言葉[ことば]があった", furigana_annotations=furigana_annotations, confidence=ConfidenceLevel.HIGH, confidence_score=0.95, input_hash="abc123", ) assert isinstance(proto, TranslationResultProto) assert proto.reference.book == "john" assert proto.text == "初めに言葉があった" assert proto.text_with_inline_furigana == "初め[はじめ]に言葉[ことば]があった" assert len(proto.furigana_annotations) == 2 assert proto.model == "claude-3-opus" assert proto.confidence == ConfidenceLevel.HIGH assert proto.confidence_score == 0.95 assert proto.input_hash == "abc123" assert "greek" in proto.sources_used assert "λόγος" in proto.glossary_terms_applied def test_translation_result_to_proto_minimal(self) -> None: """Test converting TranslationOutput with minimal data.""" output = TranslationOutput( reference=VerseReference(book="john", chapter=1, verse=1), text="初めに言葉があった", model="claude-3-opus", ) proto = translation_result_to_proto(output) assert proto.text_with_inline_furigana == "初めに言葉があった" # Falls back to text assert proto.furigana_annotations == [] assert proto.confidence == ConfidenceLevel.CONFIDENCE_LEVEL_UNSPECIFIED assert proto.confidence_score == 0.0 def test_proto_to_translation_result(self) -> None: """Test converting proto to TranslationOutput.""" proto = TranslationResultProto( reference=VerseReferenceProto(book="john", chapter=1, verse=1), text="初めに言葉があった", model="claude-3-opus", timestamp=datetime(2024, 1, 1, 12, 0, 0, tzinfo=timezone.utc), prompt_version="v1.0", sources_used=["greek"], glossary_terms_applied=["λόγος"], ) output = proto_to_translation_result(proto) assert isinstance(output, TranslationOutput) assert output.reference.book == "john" assert output.text == "初めに言葉があった" assert output.model == "claude-3-opus" assert "greek" in output.sources_used assert "λόγος" in output.glossary_terms_applied def test_translation_result_roundtrip(self) -> None: """Test roundtrip conversion preserves basic data.""" original = TranslationOutput( reference=VerseReference(book="matthew", chapter=5, verse=3), text="心の貧しい人々は幸いです", model="claude-3-opus", prompt_version="v2.0", sources_used=["greek", "meiji"], glossary_terms_applied=["μακάριοι"], ) proto = translation_result_to_proto(original) result = proto_to_translation_result(proto) assert result.reference.book == original.reference.book assert result.text == original.text assert result.model == original.model assert result.prompt_version == original.prompt_version assert list(result.sources_used) == list(original.sources_used) class TestProtoEnums: """Tests for proto enum values.""" def test_testament_enum_values(self) -> None: """Test BiblicalTestament enum has correct values.""" assert BiblicalTestament.TESTAMENT_UNSPECIFIED == 0 assert BiblicalTestament.OLD_TESTAMENT == 1 assert BiblicalTestament.NEW_TESTAMENT == 2 def test_confidence_level_enum_values(self) -> None: """Test ConfidenceLevel enum has correct values.""" assert ConfidenceLevel.CONFIDENCE_LEVEL_UNSPECIFIED == 0 assert ConfidenceLevel.LOW == 1 assert ConfidenceLevel.MEDIUM == 2 assert ConfidenceLevel.HIGH == 3 assert ConfidenceLevel.VERY_HIGH == 4 def test_match_type_enum_values(self) -> None: """Test MatchType enum has correct values.""" assert MatchTypeProto.MATCH_TYPE_UNSPECIFIED == 0 assert MatchTypeProto.EXACT == 1 assert MatchTypeProto.FUZZY == 2 assert MatchTypeProto.PHRASE == 3 def test_segment_level_enum_values(self) -> None: """Test SegmentLevel enum has correct values.""" from src.protos.converters import SegmentLevel as SegmentLevelProto assert SegmentLevelProto.SEGMENT_LEVEL_UNSPECIFIED == 0 assert SegmentLevelProto.VERSE == 1 assert SegmentLevelProto.PHRASE == 2 class TestTMSegmentConversion: """Tests for TMSegment conversions.""" def test_tm_segment_to_proto(self) -> None: """Test converting TMSegment to proto.""" from src.models.translation_memory import SegmentLevel, TMSegment from src.protos.converters import TMSegmentProto, tm_segment_to_proto segment = TMSegment( id="test-id-123", source_text="Ἐν ἀρχῇ ἦν ὁ λόγος", source_normalized="εν αρχη ην ο λογος", target_text="初めに言葉があった", reference=VerseReference(book="john", chapter=1, verse=1), level=SegmentLevel.VERSE, reuse_count=5, metadata={"model": "claude-3-opus"}, ) proto = tm_segment_to_proto(segment) assert isinstance(proto, TMSegmentProto) assert proto.id == "test-id-123" assert proto.source_text == "Ἐν ἀρχῇ ἦν ὁ λόγος" assert proto.source_normalized == "εν αρχη ην ο λογος" assert proto.target_text == "初めに言葉があった" assert proto.reference.book == "john" assert proto.reference.chapter == 1 assert proto.reference.verse == 1 assert proto.reuse_count == 5 assert proto.metadata == {"model": "claude-3-opus"} def test_proto_to_tm_segment(self) -> None: """Test converting proto to TMSegment.""" from src.models.translation_memory import TMSegment from src.protos.converters import ( SegmentLevel, TMSegmentProto, VerseReferenceProto, proto_to_tm_segment, ) proto = TMSegmentProto( id="proto-id-456", source_text="Source text", source_normalized="source text", target_text="Target text", reference=VerseReferenceProto(book="genesis", chapter=1, verse=1), level=SegmentLevel.PHRASE, reuse_count=3, ) segment = proto_to_tm_segment(proto) assert isinstance(segment, TMSegment) assert segment.id == "proto-id-456" assert segment.source_text == "Source text" assert segment.target_text == "Target text" assert segment.reference.book == "genesis" def test_tm_segment_roundtrip(self) -> None: """Test roundtrip conversion preserves data.""" from src.models.translation_memory import SegmentLevel, TMSegment from src.protos.converters import proto_to_tm_segment, tm_segment_to_proto original = TMSegment( id="roundtrip-id", source_text="Original source", source_normalized="original source", target_text="Original target", reference=VerseReference(book="matthew", chapter=5, verse=3), level=SegmentLevel.VERSE, reuse_count=10, metadata={"key": "value"}, ) proto = tm_segment_to_proto(original) result = proto_to_tm_segment(proto) assert result.id == original.id assert result.source_text == original.source_text assert result.target_text == original.target_text assert result.reference.book == original.reference.book assert result.reuse_count == original.reuse_count assert result.metadata == original.metadata class TestTMMatchConversion: """Tests for TMMatch conversions.""" def test_tm_match_to_proto(self) -> None: """Test converting TMMatch to proto.""" from src.models.translation_memory import MatchType, SegmentLevel, TMMatch, TMSegment from src.protos.converters import TranslationMemoryMatchProto, tm_match_to_proto segment = TMSegment( id="match-segment", source_text="Source", source_normalized="source", target_text="Target", reference=VerseReference(book="john", chapter=1, verse=1), level=SegmentLevel.VERSE, ) match = TMMatch( segment=segment, similarity=0.95, match_type=MatchType.EXACT, ) proto = tm_match_to_proto(match) assert isinstance(proto, TranslationMemoryMatchProto) assert proto.segment.id == "match-segment" assert proto.similarity == 0.95 assert proto.match_type == MatchTypeProto.EXACT def test_proto_to_tm_match(self) -> None: """Test converting proto to TMMatch.""" from src.models.translation_memory import TMMatch from src.protos.converters import ( MatchType, SegmentLevel, TMSegmentProto, TranslationMemoryMatchProto, VerseReferenceProto, proto_to_tm_match, ) segment_proto = TMSegmentProto( id="proto-match-segment", source_text="Proto source", source_normalized="proto source", target_text="Proto target", reference=VerseReferenceProto(book="genesis", chapter=2, verse=1), level=SegmentLevel.VERSE, ) proto = TranslationMemoryMatchProto( segment=segment_proto, similarity=0.85, match_type=MatchType.FUZZY, ) match = proto_to_tm_match(proto) assert isinstance(match, TMMatch) assert match.segment.id == "proto-match-segment" assert match.similarity == 0.85 def test_all_match_types(self) -> None: """Test all match type conversions.""" from src.models.translation_memory import ( MatchType as MatchTypeModel, SegmentLevel, TMMatch, TMSegment, ) from src.protos.converters import MatchType as MatchTypeProtoEnum from src.protos.converters import tm_match_to_proto match_types = [ (MatchTypeModel.EXACT, MatchTypeProtoEnum.EXACT), (MatchTypeModel.FUZZY, MatchTypeProtoEnum.FUZZY), (MatchTypeModel.PHRASE, MatchTypeProtoEnum.PHRASE), ] for model_type, expected_proto in match_types: segment = TMSegment( id="test", source_text="S", source_normalized="s", target_text="T", reference=VerseReference(book="john", chapter=1, verse=1), level=SegmentLevel.VERSE, ) match = TMMatch( segment=segment, similarity=0.9, match_type=model_type, ) proto = tm_match_to_proto(match) assert proto.match_type == expected_proto class TestProvenanceConversion: """Tests for TranslationProvenance conversions.""" def test_provenance_to_proto(self) -> None: """Test converting TranslationProvenance to proto.""" from src.orchestration.provenance import TranslationProvenance from src.protos.converters import TranslationProvenanceProto, provenance_to_proto provenance = TranslationProvenance( source_texts={"greek": "Greek text", "kjv": "KJV text"}, source_hashes={"greek": "hash1", "kjv": "hash2"}, preceding_verses=["john 1:1", "john 1:2"], following_verses=["john 1:4"], pericope_id="john-1-prologue", style_config_hash="style-hash", glossary_hash="glossary-hash", prompt_version="v2.0", model="claude-3-opus", system_prompt="You are a translator", user_prompt="Translate this", tm_matches_used=[{"source": "s", "target": "t", "similarity": 0.9}], attempt_number=2, evaluation_score=85.5, evaluation_flags=["needs_review"], chapter_summary="Chapter about love", book_metadata={"name": "John"}, ) proto = provenance_to_proto(provenance) assert isinstance(proto, TranslationProvenanceProto) assert proto.source_texts == {"greek": "Greek text", "kjv": "KJV text"} assert proto.source_hashes == {"greek": "hash1", "kjv": "hash2"} assert proto.preceding_verses == ["john 1:1", "john 1:2"] assert proto.following_verses == ["john 1:4"] assert proto.pericope_id == "john-1-prologue" assert proto.model == "claude-3-opus" assert proto.prompt_version == "v2.0" assert proto.attempt_number == 2 assert proto.evaluation_score == 85.5 assert proto.evaluation_flags == ["needs_review"] def test_proto_to_provenance(self) -> None: """Test converting proto to TranslationProvenance.""" from datetime import datetime, timezone from src.orchestration.provenance import TranslationProvenance from src.protos.converters import TranslationProvenanceProto, proto_to_provenance started = datetime(2024, 1, 1, 12, 0, 0, tzinfo=timezone.utc) completed = datetime(2024, 1, 1, 12, 0, 30, tzinfo=timezone.utc) proto = TranslationProvenanceProto( source_texts={"greek": "Text"}, source_hashes={"greek": "hash"}, model="claude-3-haiku", prompt_version="v1.0", started_at=started, completed_at=completed, ) provenance = proto_to_provenance(proto) assert isinstance(provenance, TranslationProvenance) assert provenance.source_texts == {"greek": "Text"} assert provenance.model == "claude-3-haiku" assert provenance.started_at == started assert provenance.completed_at == completed def test_provenance_roundtrip(self) -> None: """Test roundtrip conversion preserves data.""" from datetime import datetime, timezone from src.orchestration.provenance import TranslationProvenance from src.protos.converters import proto_to_provenance, provenance_to_proto original = TranslationProvenance( source_texts={"greek": "Greek", "hebrew": "Hebrew"}, source_hashes={"greek": "h1", "hebrew": "h2"}, preceding_verses=["gen 1:1"], following_verses=["gen 1:3"], model="claude-opus", prompt_version="v3.0", system_prompt="System", user_prompt="User", attempt_number=1, started_at=datetime(2024, 6, 15, 10, 0, 0, tzinfo=timezone.utc), completed_at=datetime(2024, 6, 15, 10, 0, 45, tzinfo=timezone.utc), evaluation_score=92.0, evaluation_flags=["high_quality"], ) proto = provenance_to_proto(original) result = proto_to_provenance(proto) assert result.source_texts == original.source_texts assert result.source_hashes == original.source_hashes assert result.model == original.model assert result.prompt_version == original.prompt_version assert result.attempt_number == original.attempt_number assert result.started_at == original.started_at assert result.completed_at == original.completed_at assert result.evaluation_score == original.evaluation_score assert result.evaluation_flags == original.evaluation_flags class TestTranslationPromptInputWithTMMatches: """Tests for TranslationPromptInputProto with TM matches.""" def test_prompt_input_with_tm_matches(self) -> None: """Test TranslationPromptInputProto includes tm_matches field.""" from src.protos.converters import ( MatchType, SegmentLevel, TMSegmentProto, TranslationMemoryMatchProto, TranslationPromptInputProto, VerseReferenceProto, ) segment = TMSegmentProto( id="tm-1", source_text="Source", source_normalized="source", target_text="Target", reference=VerseReferenceProto(book="john", chapter=1, verse=1), level=SegmentLevel.VERSE, ) match = TranslationMemoryMatchProto( segment=segment, similarity=0.95, match_type=MatchType.EXACT, ) proto = TranslationPromptInputProto( system_prompt="System", user_prompt="User", model="claude-3-opus", tm_matches=[match], ) assert len(proto.tm_matches) == 1 assert proto.tm_matches[0].segment.id == "tm-1" assert proto.tm_matches[0].similarity == 0.95 class TestTranslationResultWithProvenance: """Tests for TranslationResultProto with provenance.""" def test_result_with_provenance(self) -> None: """Test TranslationResultProto includes provenance field.""" from src.protos.converters import ( TranslationProvenanceProto, TranslationResultProto, VerseReferenceProto, ) provenance = TranslationProvenanceProto( model="claude-3-opus", prompt_version="v1.0", source_texts={"greek": "Greek text"}, ) result = TranslationResultProto( reference=VerseReferenceProto(book="john", chapter=1, verse=1), text="Translation text", model="claude-3-opus", provenance=provenance, ) assert result.provenance is not None assert result.provenance.model == "claude-3-opus" assert result.provenance.source_texts == {"greek": "Greek text"}