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

OPEN SOURCE

software/tests/test_proto_serialization.py

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

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

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

software/tests/test_proto_serialization.py458 行このファイルを保存 ↓

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

ファイルの確認情報SHA-256 a27737119a953e9000d0376534fd7aa7c4940dcc6f25c1200f26237e9a10db85
1"""Tests for Protocol Buffer serialization utilities."""23from __future__ import annotations45from dataclasses import dataclass, field6from datetime import datetime, timezone78import pytest910from src.protos.serialization import (11    compute_proto_hash,12    deserialize_proto,13    serialize_proto,14)151617@dataclass18class SimpleProto:19    """Simple test proto."""2021    name: str = ""22    value: int = 0232425@dataclass26class NestedProto:27    """Proto with nested dataclass."""2829    simple: SimpleProto = field(default_factory=SimpleProto)30    items: list[str] = field(default_factory=list)313233@dataclass34class DateTimeProto:35    """Proto with datetime fields."""3637    created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))38    updated_at: datetime | None = None394041@dataclass42class ComplexProto:43    """Proto with various field types."""4445    name: str = ""46    count: int = 047    score: float = 0.048    active: bool = False49    tags: list[str] = field(default_factory=list)50    metadata: dict = field(default_factory=dict)51    nested: SimpleProto = field(default_factory=SimpleProto)52    nested_list: list[SimpleProto] = field(default_factory=list)53    timestamp: datetime = field(default_factory=lambda: datetime.now(timezone.utc))54    optional_time: datetime | None = None555657class TestSerializeProto:58    """Tests for serialize_proto function."""5960    def test_serialize_simple(self) -> None:61        """Test serializing a simple proto."""62        proto = SimpleProto(name="test", value=42)63        data = serialize_proto(proto)6465        assert isinstance(data, bytes)66        assert len(data) > 06768    def test_serialize_nested(self) -> None:69        """Test serializing a proto with nested dataclass."""70        proto = NestedProto(71            simple=SimpleProto(name="nested", value=100),72            items=["a", "b", "c"],73        )74        data = serialize_proto(proto)7576        assert isinstance(data, bytes)77        assert len(data) > 07879    def test_serialize_datetime(self) -> None:80        """Test serializing a proto with datetime fields."""81        now = datetime(2024, 6, 15, 12, 0, 0, tzinfo=timezone.utc)82        proto = DateTimeProto(83            created_at=now,84            updated_at=now,85        )86        data = serialize_proto(proto)8788        assert isinstance(data, bytes)8990    def test_serialize_complex(self) -> None:91        """Test serializing a complex proto."""92        proto = ComplexProto(93            name="complex",94            count=100,95            score=0.95,96            active=True,97            tags=["tag1", "tag2"],98            metadata={"key": "value"},99            nested=SimpleProto(name="inner", value=50),100            nested_list=[101                SimpleProto(name="item1", value=1),102                SimpleProto(name="item2", value=2),103            ],104            timestamp=datetime(2024, 1, 1, 0, 0, 0, tzinfo=timezone.utc),105            optional_time=None,106        )107        data = serialize_proto(proto)108109        assert isinstance(data, bytes)110111    def test_serialize_raises_for_non_dataclass(self) -> None:112        """Test serialize_proto raises TypeError for non-dataclass."""113        with pytest.raises(TypeError, match="Expected dataclass instance"):114            serialize_proto({"not": "dataclass"})115116    def test_serialize_raises_for_class_type(self) -> None:117        """Test serialize_proto raises TypeError for class type."""118        with pytest.raises(TypeError, match="Expected dataclass instance"):119            serialize_proto(SimpleProto)120121122class TestDeserializeProto:123    """Tests for deserialize_proto function."""124125    def test_deserialize_simple(self) -> None:126        """Test deserializing a simple proto."""127        original = SimpleProto(name="test", value=42)128        data = serialize_proto(original)129        result = deserialize_proto(data, SimpleProto)130131        assert isinstance(result, SimpleProto)132        assert result.name == "test"133        assert result.value == 42134135    def test_deserialize_nested(self) -> None:136        """Test deserializing a proto with nested dataclass."""137        original = NestedProto(138            simple=SimpleProto(name="nested", value=100),139            items=["a", "b", "c"],140        )141        data = serialize_proto(original)142        result = deserialize_proto(data, NestedProto)143144        assert isinstance(result, NestedProto)145        assert isinstance(result.simple, SimpleProto)146        assert result.simple.name == "nested"147        assert result.simple.value == 100148        assert result.items == ["a", "b", "c"]149150    def test_deserialize_datetime(self) -> None:151        """Test deserializing a proto with datetime fields."""152        now = datetime(2024, 6, 15, 12, 0, 0, tzinfo=timezone.utc)153        original = DateTimeProto(154            created_at=now,155            updated_at=now,156        )157        data = serialize_proto(original)158        result = deserialize_proto(data, DateTimeProto)159160        assert isinstance(result, DateTimeProto)161        assert result.created_at == now162        assert result.updated_at == now163164    def test_deserialize_datetime_naive(self) -> None:165        """Test deserializing converts naive datetime to UTC."""166        # Create a proto with a naive datetime167        proto = DateTimeProto(168            created_at=datetime(2024, 1, 1, 12, 0, 0),  # Naive datetime169            updated_at=None,170        )171        data = serialize_proto(proto)172        result = deserialize_proto(data, DateTimeProto)173174        # Result should have timezone-aware datetime175        assert result.created_at.tzinfo is not None176177    def test_deserialize_complex(self) -> None:178        """Test deserializing a complex proto."""179        original = ComplexProto(180            name="complex",181            count=100,182            score=0.95,183            active=True,184            tags=["tag1", "tag2"],185            metadata={"key": "value"},186            nested=SimpleProto(name="inner", value=50),187            nested_list=[188                SimpleProto(name="item1", value=1),189                SimpleProto(name="item2", value=2),190            ],191            timestamp=datetime(2024, 1, 1, 0, 0, 0, tzinfo=timezone.utc),192            optional_time=None,193        )194        data = serialize_proto(original)195        result = deserialize_proto(data, ComplexProto)196197        assert result.name == "complex"198        assert result.count == 100199        assert result.score == 0.95200        assert result.active is True201        assert result.tags == ["tag1", "tag2"]202        assert result.metadata == {"key": "value"}203        assert result.nested.name == "inner"204        assert len(result.nested_list) == 2205206    def test_deserialize_raises_for_non_dataclass(self) -> None:207        """Test deserialize_proto raises TypeError for non-dataclass type."""208        data = serialize_proto(SimpleProto(name="test", value=1))209        with pytest.raises(TypeError, match="Expected dataclass type"):210            deserialize_proto(data, dict)211212    def test_deserialize_raises_for_invalid_data(self) -> None:213        """Test deserialize_proto raises ValueError for invalid data."""214        with pytest.raises(ValueError, match="Failed to decode msgpack"):215            deserialize_proto(b"invalid msgpack data", SimpleProto)216217218class TestRoundtripConversions:219    """Tests for roundtrip serialize/deserialize conversions."""220221    def test_simple_roundtrip(self) -> None:222        """Test simple proto roundtrip."""223        original = SimpleProto(name="roundtrip", value=999)224        data = serialize_proto(original)225        result = deserialize_proto(data, SimpleProto)226227        assert result.name == original.name228        assert result.value == original.value229230    def test_empty_proto_roundtrip(self) -> None:231        """Test empty proto roundtrip."""232        original = SimpleProto()233        data = serialize_proto(original)234        result = deserialize_proto(data, SimpleProto)235236        assert result.name == ""237        assert result.value == 0238239    def test_unicode_roundtrip(self) -> None:240        """Test proto with unicode content roundtrip."""241        original = SimpleProto(name="日本語テスト", value=42)242        data = serialize_proto(original)243        result = deserialize_proto(data, SimpleProto)244245        assert result.name == "日本語テスト"246247    def test_nested_list_roundtrip(self) -> None:248        """Test proto with nested list roundtrip."""249        original = ComplexProto(250            nested_list=[251                SimpleProto(name="first", value=1),252                SimpleProto(name="second", value=2),253                SimpleProto(name="third", value=3),254            ],255        )256        data = serialize_proto(original)257        result = deserialize_proto(data, ComplexProto)258259        assert len(result.nested_list) == 3260        assert result.nested_list[0].name == "first"261        assert result.nested_list[2].value == 3262263264class TestComputeProtoHash:265    """Tests for compute_proto_hash function."""266267    def test_compute_hash_returns_string(self) -> None:268        """Test compute_proto_hash returns a hex string."""269        proto = SimpleProto(name="test", value=42)270        hash_value = compute_proto_hash(proto)271272        assert isinstance(hash_value, str)273        assert len(hash_value) == 64  # SHA256 produces 64 hex chars274275    def test_same_proto_same_hash(self) -> None:276        """Test identical protos produce identical hashes."""277        proto1 = SimpleProto(name="test", value=42)278        proto2 = SimpleProto(name="test", value=42)279280        hash1 = compute_proto_hash(proto1)281        hash2 = compute_proto_hash(proto2)282283        assert hash1 == hash2284285    def test_different_proto_different_hash(self) -> None:286        """Test different protos produce different hashes."""287        proto1 = SimpleProto(name="test1", value=42)288        proto2 = SimpleProto(name="test2", value=42)289290        hash1 = compute_proto_hash(proto1)291        hash2 = compute_proto_hash(proto2)292293        assert hash1 != hash2294295    def test_hash_deterministic(self) -> None:296        """Test hash is deterministic across multiple calls."""297        proto = ComplexProto(298            name="complex",299            count=100,300            tags=["a", "b", "c"],301            nested=SimpleProto(name="inner", value=50),302        )303304        hashes = [compute_proto_hash(proto) for _ in range(5)]305        assert all(h == hashes[0] for h in hashes)306307    def test_hash_raises_for_non_dataclass(self) -> None:308        """Test compute_proto_hash raises TypeError for non-dataclass."""309        with pytest.raises(TypeError, match="Expected dataclass instance"):310            compute_proto_hash({"not": "dataclass"})311312313class TestRealProtoTypes:314    """Tests with actual proto types from the application."""315316    def test_verse_reference_proto_roundtrip(self) -> None:317        """Test VerseReferenceProto serialization roundtrip."""318        from src.protos.converters import VerseReferenceProto319320        original = VerseReferenceProto(book="john", chapter=1, verse=1)321        data = serialize_proto(original)322        result = deserialize_proto(data, VerseReferenceProto)323324        assert result.book == "john"325        assert result.chapter == 1326        assert result.verse == 1327328    def test_translation_result_proto_roundtrip(self) -> None:329        """Test TranslationResultProto serialization roundtrip."""330        from src.protos.converters import (331            ConfidenceLevel,332            TranslationResultProto,333            VerseReferenceProto,334        )335336        original = TranslationResultProto(337            reference=VerseReferenceProto(book="john", chapter=1, verse=1),338            text="初めに言葉があった",339            text_with_inline_furigana="初め[はじめ]に言葉[ことば]があった",340            model="claude-3-opus",341            confidence=ConfidenceLevel.HIGH,342            confidence_score=0.95,343            input_hash="abc123",344        )345        data = serialize_proto(original)346        result = deserialize_proto(data, TranslationResultProto)347348        assert result.text == "初めに言葉があった"349        assert result.model == "claude-3-opus"350        assert result.confidence_score == 0.95351        assert result.input_hash == "abc123"352353    def test_translation_prompt_input_proto_roundtrip(self) -> None:354        """Test TranslationPromptInputProto serialization roundtrip."""355        from src.protos.converters import (356            TranslationPromptInputProto,357            VerseProto,358            VerseReferenceProto,359        )360361        original = TranslationPromptInputProto(362            target_verse=VerseProto(363                reference=VerseReferenceProto(book="john", chapter=1, verse=1),364                greek_text="Ἐν ἀρχῇ ἦν ὁ λόγος",365            ),366            system_prompt="You are a translator",367            user_prompt="Translate this verse",368            model="claude-3-opus",369        )370        data = serialize_proto(original)371        result = deserialize_proto(data, TranslationPromptInputProto)372373        assert result.target_verse.greek_text == "Ἐν ἀρχῇ ἦν ὁ λόγος"374        assert result.system_prompt == "You are a translator"375        assert result.model == "claude-3-opus"376377    def test_provenance_proto_roundtrip(self) -> None:378        """Test TranslationProvenanceProto serialization roundtrip."""379        from datetime import datetime, timezone380381        from src.protos.converters import TranslationProvenanceProto382383        started = datetime(2024, 1, 1, 12, 0, 0, tzinfo=timezone.utc)384        completed = datetime(2024, 1, 1, 12, 0, 30, tzinfo=timezone.utc)385386        original = TranslationProvenanceProto(387            source_texts={"greek": "Greek text", "kjv": "KJV text"},388            source_hashes={"greek": "hash1", "kjv": "hash2"},389            model="claude-3-opus",390            prompt_version="v1.0",391            started_at=started,392            completed_at=completed,393            evaluation_score=85.5,394        )395        data = serialize_proto(original)396        result = deserialize_proto(data, TranslationProvenanceProto)397398        assert result.source_texts == {"greek": "Greek text", "kjv": "KJV text"}399        assert result.model == "claude-3-opus"400        assert result.started_at == started401        assert result.completed_at == completed402        assert result.evaluation_score == 85.5403404405class TestReconstructEdgeCases:406    """Tests for _reconstruct_dataclass edge branches."""407408    def test_reconstruct_rejects_non_dict(self) -> None:409        """Non-dict input raises ValueError."""410        from src.protos.serialization import _reconstruct_dataclass411412        with pytest.raises(ValueError, match="Expected dict"):413            _reconstruct_dataclass(["not", "a", "dict"], SimpleProto)414415    def test_reconstruct_skips_unknown_fields(self) -> None:416        """Keys not present on the dataclass are ignored."""417        from src.protos.serialization import _reconstruct_dataclass418419        result = _reconstruct_dataclass(420            {"name": "x", "value": 1, "unknown_field": "dropped"}, SimpleProto421        )422        assert result == SimpleProto(name="x", value=1)423424    def test_reconstruct_unresolvable_string_annotation(self) -> None:425        """String annotations that can't be resolved pass values through."""426        import typing as typing_mod427        from unittest.mock import patch428429        from src.protos.serialization import _reconstruct_dataclass430431        fake_hints = {"name": "SomeUnresolvableType", "value": int}432        with patch.object(typing_mod, "get_type_hints", return_value=fake_hints):433            result = _reconstruct_dataclass({"name": "kept", "value": 2}, SimpleProto)434        assert result.name == "kept"435        assert result.value == 2436437    def test_reconstruct_union_all_none_types(self) -> None:438        """Union whose non-None arms are exhausted leaves value as-is."""439        from src.protos.serialization import _reconstruct_dataclass440441        @dataclass442        class OnlyNoneProto:443            maybe: None = None444445        result = _reconstruct_dataclass({"maybe": None}, OnlyNoneProto)446        assert result.maybe is None447448    def test_reconstruct_bare_list_annotation(self) -> None:449        """list annotation without item args keeps the raw list."""450        from src.protos.serialization import _reconstruct_dataclass451452        @dataclass453        class BareListProto:454            items: list = field(default_factory=list)455456        result = _reconstruct_dataclass({"items": [1, "two", 3.0]}, BareListProto)457        assert result.items == [1, "two", 3.0]458