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

OPEN SOURCE

software/tests/test_provenance.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_provenance.py265 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 609d83df7c11b49bfaf633a7c7b019127f737ddcca2e85bce6b01f6041602400
1"""Tests for provenance tracking."""23from __future__ import annotations45from datetime import datetime67import pytest89from src.orchestration.provenance import (10    TranslationProvenance,11    compute_glossary_hash,12    compute_hash,13    compute_style_hash,14)151617class TestComputeHash:18    """Tests for compute_hash function."""1920    def test_hash_text(self) -> None:21        """Test hashing a text string."""22        result = compute_hash("hello world")23        assert len(result) == 64  # SHA256 hex length24        assert result.isalnum()2526    def test_hash_none(self) -> None:27        """Test hashing None returns empty string."""28        result = compute_hash(None)29        assert result == ""3031    def test_hash_empty_string(self) -> None:32        """Test hashing empty string returns empty string."""33        result = compute_hash("")34        assert result == ""3536    def test_hash_deterministic(self) -> None:37        """Test that hashing is deterministic."""38        text = "test content"39        hash1 = compute_hash(text)40        hash2 = compute_hash(text)41        assert hash1 == hash24243    def test_different_text_different_hash(self) -> None:44        """Test that different text produces different hash."""45        hash1 = compute_hash("text1")46        hash2 = compute_hash("text2")47        assert hash1 != hash2484950class TestComputeGlossaryHash:51    """Tests for compute_glossary_hash function."""5253    def test_hash_empty_list(self) -> None:54        """Test hashing empty list returns empty string."""55        result = compute_glossary_hash([])56        assert result == ""5758    def test_hash_terms(self) -> None:59        """Test hashing glossary terms."""60        terms = ["logos", "agape", "pneuma"]61        result = compute_glossary_hash(terms)62        assert len(result) == 646364    def test_hash_order_independent(self) -> None:65        """Test that hash is order-independent (sorted internally)."""66        terms1 = ["beta", "alpha", "gamma"]67        terms2 = ["alpha", "beta", "gamma"]68        hash1 = compute_glossary_hash(terms1)69        hash2 = compute_glossary_hash(terms2)70        assert hash1 == hash2717273class TestComputeStyleHash:74    """Tests for compute_style_hash function."""7576    def test_hash_empty_config(self) -> None:77        """Test hashing empty config returns empty string."""78        result = compute_style_hash({})79        assert result == ""8081    def test_hash_config(self) -> None:82        """Test hashing style config."""83        config = {"equivalence": "formal", "readability": "scholarly"}84        result = compute_style_hash(config)85        assert len(result) == 648687    def test_hash_key_order_independent(self) -> None:88        """Test that hash is key-order independent."""89        config1 = {"a": 1, "b": 2}90        config2 = {"b": 2, "a": 1}91        hash1 = compute_style_hash(config1)92        hash2 = compute_style_hash(config2)93        assert hash1 == hash2949596class TestTranslationProvenance:97    """Tests for TranslationProvenance dataclass."""9899    def test_default_values(self) -> None:100        """Test default values for TranslationProvenance."""101        prov = TranslationProvenance()102        assert prov.source_texts == {}103        assert prov.source_hashes == {}104        assert prov.preceding_verses == []105        assert prov.following_verses == []106        assert prov.pericope_id is None107        assert prov.style_config_hash == ""108        assert prov.glossary_hash == ""109        assert prov.prompt_version == "v1.0"110        assert prov.model == ""111        assert prov.system_prompt == ""112        assert prov.user_prompt == ""113        assert prov.tm_matches_used == []114        assert prov.attempt_number == 1115        assert prov.started_at is None116        assert prov.completed_at is None117        assert prov.evaluation_score is None118        assert prov.evaluation_flags == []119120    def test_compute_source_hashes(self) -> None:121        """Test computing source hashes."""122        prov = TranslationProvenance(123            source_texts={124                "greek": "Ἐν ἀρχῇ ἦν ὁ λόγος",125                "kjv": "In the beginning was the Word",126            }127        )128        prov.compute_source_hashes()129130        assert len(prov.source_hashes) == 2131        assert "greek" in prov.source_hashes132        assert "kjv" in prov.source_hashes133        assert len(prov.source_hashes["greek"]) == 64134135    def test_to_dict(self) -> None:136        """Test converting provenance to dictionary."""137        now = datetime.utcnow()138        prov = TranslationProvenance(139            model="test-model",140            started_at=now,141            completed_at=now,142            evaluation_score=85.5,143            evaluation_flags=["flag1"],144        )145146        result = prov.to_dict()147148        assert result["model"] == "test-model"149        assert result["started_at"] == now.isoformat()150        assert result["completed_at"] == now.isoformat()151        assert result["evaluation_score"] == 85.5152        assert result["evaluation_flags"] == ["flag1"]153154    def test_from_dict(self) -> None:155        """Test creating provenance from dictionary."""156        now = datetime.utcnow()157        data = {158            "model": "test-model",159            "prompt_version": "v2.0",160            "source_texts": {"greek": "text"},161            "source_hashes": {"greek": "abc123"},162            "started_at": now.isoformat(),163            "completed_at": now.isoformat(),164            "evaluation_score": 90.0,165        }166167        prov = TranslationProvenance.from_dict(data)168169        assert prov.model == "test-model"170        assert prov.prompt_version == "v2.0"171        assert prov.source_texts == {"greek": "text"}172        assert prov.source_hashes == {"greek": "abc123"}173        assert prov.evaluation_score == 90.0174175    def test_from_dict_missing_fields(self) -> None:176        """Test from_dict handles missing fields."""177        prov = TranslationProvenance.from_dict({})178        assert prov.model == ""179        assert prov.source_texts == {}180181    def test_roundtrip(self) -> None:182        """Test that to_dict/from_dict roundtrip preserves data."""183        now = datetime.utcnow()184        original = TranslationProvenance(185            source_texts={"greek": "text"},186            source_hashes={"greek": "hash"},187            preceding_verses=["john:1:1"],188            following_verses=["john:1:3"],189            pericope_id="john-1-prologue",190            model="test-model",191            system_prompt="system",192            user_prompt="user",193            tm_matches_used=[{"source": "a", "target": "b"}],194            attempt_number=2,195            started_at=now,196            completed_at=now,197            evaluation_score=80.0,198            evaluation_flags=["flag"],199        )200201        restored = TranslationProvenance.from_dict(original.to_dict())202203        assert restored.source_texts == original.source_texts204        assert restored.source_hashes == original.source_hashes205        assert restored.preceding_verses == original.preceding_verses206        assert restored.following_verses == original.following_verses207        assert restored.pericope_id == original.pericope_id208        assert restored.model == original.model209        assert restored.tm_matches_used == original.tm_matches_used210        assert restored.attempt_number == original.attempt_number211        assert restored.evaluation_score == original.evaluation_score212213    def test_has_source_change_no_change(self) -> None:214        """Test has_source_change with identical hashes."""215        prov1 = TranslationProvenance(216            source_hashes={"greek": "abc123", "kjv": "def456"}217        )218        prov2 = TranslationProvenance(219            source_hashes={"greek": "abc123", "kjv": "def456"}220        )221222        assert prov1.has_source_change(prov2) is False223224    def test_has_source_change_with_change(self) -> None:225        """Test has_source_change with different hashes."""226        prov1 = TranslationProvenance(227            source_hashes={"greek": "abc123", "kjv": "def456"}228        )229        prov2 = TranslationProvenance(230            source_hashes={"greek": "abc123", "kjv": "changed"}231        )232233        assert prov1.has_source_change(prov2) is True234235    def test_has_source_change_missing_key(self) -> None:236        """Test has_source_change when other is missing a key."""237        prov1 = TranslationProvenance(238            source_hashes={"greek": "abc123", "kjv": "def456"}239        )240        prov2 = TranslationProvenance(source_hashes={"greek": "abc123"})241242        assert prov1.has_source_change(prov2) is True243244    def test_get_inputs_summary(self) -> None:245        """Test get_inputs_summary."""246        prov = TranslationProvenance(247            source_texts={"greek": "text", "kjv": "text"},248            preceding_verses=["john:1:1", "john:1:2"],249            following_verses=["john:1:4"],250            pericope_id="john-1",251            tm_matches_used=[{"source": "a"}],252            model="test-model",253            attempt_number=2,254        )255256        summary = prov.get_inputs_summary()257258        assert set(summary["sources"]) == {"greek", "kjv"}259        assert summary["preceding_context"] == 2260        assert summary["following_context"] == 1261        assert summary["pericope"] is True262        assert summary["tm_matches"] == 1263        assert summary["model"] == "test-model"264        assert summary["attempt"] == 2265