from __future__ import annotations import copy import pytest from tools.graphrag.evaluation.freeze_graphrag import make_freeze from tools.graphrag.evaluation.reproducibility import sha256_file, verify_freeze @pytest.fixture def freeze_case(): questions = [{'id': 'development-a', 'split': 'dev'}, {'id': 'holdout-a', 'split': 'test'}] fingerprints = {'implementation': {'search.py': 'version-one'}, 'dataset_sha256': 'dataset', 'inputs': {}} graph = {'sha256': 'graph', 'nodes': 1, 'relationships': 0} audit = {'questions': [{'id': q['id']} for q in questions], 'by_split': {}, 'dataset_sha256': 'dataset', 'inputs': {}} dev = {'configuration': {'split': 'dev', 'limit': 40, 'max_entities': 8}, 'summary': {'error_count': 0}, 'questions': [{'id': 'development-a', 'error': None}], 'run_metadata': {'fingerprints': fingerprints, 'graph': graph}} return questions, fingerprints, graph, audit, dev def test_freeze_rejects_partial_dev(freeze_case): questions, fingerprints, _, audit, dev = freeze_case dev['questions'] = [] with pytest.raises(RuntimeError, match='complete DEV'): make_freeze(dev, questions, fingerprints, audit) def test_freeze_rejects_runtime_errors(freeze_case): questions, fingerprints, _, audit, dev = freeze_case dev['questions'][0]['error'] = 'database unavailable' with pytest.raises(RuntimeError, match='runtime errors'): make_freeze(dev, questions, fingerprints, audit) def test_freeze_rejects_code_changed_after_dev(freeze_case): questions, fingerprints, _, audit, dev = freeze_case changed = copy.deepcopy(fingerprints) changed['implementation']['search.py'] = 'version-two' with pytest.raises(RuntimeError, match='changed after DEV'): make_freeze(dev, questions, changed, audit) def test_freeze_rejects_stale_source_audit(freeze_case): questions, fingerprints, _, audit, dev = freeze_case audit['inputs'] = {'documents': 'old-snapshot'} with pytest.raises(RuntimeError, match='audit does not match'): make_freeze(dev, questions, fingerprints, audit) @pytest.mark.parametrize('mutation', ['code', 'graph', 'parameters', 'audit']) def test_holdout_rejects_changes_after_freeze(freeze_case, tmp_path, mutation): questions, fingerprints, graph, audit, dev = freeze_case manifest = make_freeze(dev, questions, fingerprints, audit) audit_path = tmp_path / 'audit.json' audit_path.write_text('original-audit') manifest['audit_sha256'] = sha256_file(audit_path) metadata = {'fingerprints': copy.deepcopy(fingerprints)} actual_graph = dict(graph) limit = 40 if mutation == 'code': metadata['fingerprints']['implementation']['search.py'] = 'changed' elif mutation == 'graph': actual_graph['sha256'] = 'changed' elif mutation == 'parameters': limit = 41 else: audit_path.write_text('changed-audit') with pytest.raises(RuntimeError, match='TEST refused'): verify_freeze(manifest, metadata, actual_graph, limit=limit, max_entities=8, audit=audit_path)