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