"""Reject broken preview contracts before publishing generated references.""" import hashlib import importlib.util import json from pathlib import Path import shutil import sys import pytest ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT / 'scripts')) spec = importlib.util.spec_from_file_location('reference_preview', ROOT / 'scripts/reference_preview.py') preview = importlib.util.module_from_spec(spec) spec.loader.exec_module(preview) @pytest.fixture def copy(tmp_path): for name in ('research', 'canon', 'workplans', 'sequences', 'technologies', 'reference'): shutil.copytree(ROOT / name, tmp_path / name) shutil.copy(ROOT / 'START.md', tmp_path / 'START.md') (tmp_path / '.git').symlink_to(ROOT / '.git', target_is_directory=True) return tmp_path def mutate(root, edit): path = root / 'reference/0.1/manifest.json' data = json.loads(path.read_text()) edit(data) path.write_text(json.dumps(data)) def test_current_export(): for name, content in preview.build().items(): assert (ROOT / 'reference/0.1' / name).read_text() == content @pytest.mark.parametrize('field,value', [('target', 'CAND-9999'), ('type', 'magically-guarantees'), ('condition', ''), ('evidence_refs', ['missing.md'])]) def test_bad_relation(copy, field, value): mutate(copy, lambda m: m['relations'][0].update({field: value})) with pytest.raises(ValueError): preview.build(copy) def test_duplicate_identity(copy): mutate(copy, lambda m: m['core'].append(m['core'][0])) with pytest.raises(ValueError): preview.build(copy) def test_live_candidate_change_does_not_break_frozen_preview(copy): with (copy / 'research/candidates/ConflictCopy.md').open('a') as f: f.write('\nChanged contract.\n') assert preview.build(copy) == preview.build(ROOT) import research_evolution as evolution with pytest.raises(ValueError, match='Stale profile'): evolution.validate(evolution.load(copy), copy) def test_escaping_reference(copy): mutate(copy, lambda m: m['entrypoints'].update(start='/etc/passwd')) with pytest.raises(ValueError, match='Published manifest changed'): preview.build(copy) def test_missing_metadata(copy): path = copy / 'reference/0.1/manifest.json' data = json.loads(path.read_text()) del data['relations'][0]['rationale'] path.write_text(json.dumps(data)) with pytest.raises((ValueError, KeyError)): preview.build(copy) def test_legitimate_new_research_remains_separate_from_old_release(copy): import research_evolution as evolution candidate = copy / 'research/candidates/ConflictCopy.md' candidate.write_text(candidate.read_text() + '\nNew research clarification.\n') digest = hashlib.sha256(candidate.read_bytes()).hexdigest() profiles = copy / 'research/evolution/profiles.json' data = json.loads(profiles.read_text()) data['profiles'][0]['sha256'] = digest profiles.write_text(json.dumps(data)) classes = copy / 'research/catalog/ClassificationCoverage.json' data = json.loads(classes.read_text()) next(c for c in data['candidates'] if c['path'] == 'research/candidates/ConflictCopy.md')['sha256'] = digest classes.write_text(json.dumps(data)) evolution.validate(evolution.load(copy), copy) assert preview.build(copy, '0.1') == preview.build(ROOT, '0.1') def test_unknown_version_and_forged_release_pin(copy): with pytest.raises(ValueError, match='Unknown'): preview.build(copy, '9.9') path = copy / 'reference/releases.json' data = json.loads(path.read_text()) data['releases']['0.1']['manifest_sha256'] = '0' * 64 path.write_text(json.dumps(data)) with pytest.raises(ValueError, match='digest mismatch'): preview.build(copy) def test_published_cases_cannot_be_rewritten(copy): path = copy / 'reference/0.1/development-cases.json' path.write_text('{}') with pytest.raises(ValueError, match='Published artifact changed'): preview.build(copy) @pytest.mark.parametrize('path', ['/etc/passwd', '../README.md', 'research/../README.md', 'research//README.md']) def test_git_blob_rejects_escaping_paths(path): with pytest.raises(ValueError, match='Invalid reference'): preview.git_blob(ROOT, '0' * 40, path)