import importlib.util import json import os from pathlib import Path import subprocess import tempfile import unittest SCRIPTS = Path(__file__).resolve().parents[1] / 'scripts' class ComponentGovernanceTests(unittest.TestCase): def setUp(self): self.temp = tempfile.TemporaryDirectory() self.addCleanup(self.temp.cleanup) self.root = Path(self.temp.name) self.run_git(self.root, 'init', '-q') self.run_git(self.root, 'config', 'user.name', 'Fixture') self.run_git(self.root, 'config', 'user.email', 'fixture@example.test') self.child = self.root / '.workspace/Provider' self.child.mkdir(parents=True) self.run_git(self.child, 'init', '-q') self.run_git(self.child, 'config', 'user.name', 'Fixture') self.run_git(self.child, 'config', 'user.email', 'fixture@example.test') (self.child/'value').write_text('one') self.run_git(self.child, 'add', 'value') self.run_git(self.child, 'commit', '-qm', 'Add value') (self.root/'.gitignore').write_text('.workspace/\n') (self.root/'components.json').write_text(json.dumps({'schema':1,'components':[{ 'id':'Provider','repo':self.child.as_uri(),'path':'.workspace/Provider', 'role':'documentation','modules':[],'target':'main'}]})) self.run_git(self.root, 'add', '.gitignore', 'components.json') self.run_git(self.root, 'commit', '-qm', 'Add component mapping') self.old = Path.cwd() os.chdir(self.root) self.addCleanup(os.chdir,self.old) def run_git(self,root,*args): return subprocess.check_output(['git','-C',str(root),*args],stderr=subprocess.STDOUT,text=True) def snapshot(self): spec=importlib.util.spec_from_file_location('component_evidence',SCRIPTS/'evidence.py') module=importlib.util.module_from_spec(spec); spec.loader.exec_module(module) return module.snapshot() def test_evidence_captures_ignored_component_revision_and_dirty_bytes(self): first=self.snapshot() (self.child/'value').write_text('two') second=self.snapshot() self.assertIn('components',first,'receipts must identify ordinary ignored component checkouts') self.assertNotEqual(first['components'],second['components']) self.run_git(self.child,'commit','-qam','Change value') third=self.snapshot() self.assertNotEqual(second['components'],third['components']) self.assertEqual(first['head'],third['head']) def test_before_sync_rejects_component_git_lock(self): (self.child/'.git/index.lock').write_text('active owner') result=subprocess.run(['python3',str(SCRIPTS/'check-worktree.py'),'before-sync'],cwd=self.root,capture_output=True,text=True) self.assertNotEqual(result.returncode,0,'a hidden component writer must block synchronization') self.assertTrue((self.child/'.git/index.lock').exists()) def test_startup_receipt_rejects_changed_ignored_component_objects(self): spec=importlib.util.spec_from_file_location('component_evidence_validation',SCRIPTS/'evidence.py') module=importlib.util.module_from_spec(spec); spec.loader.exec_module(module) self.run_git(self.root,'update-ref','refs/remotes/origin/main','HEAD') before=module.snapshot() receipt=module.create_receipt('make precheck','[verdict] READY\n',0,before,before, timestamp='2026-10-13T00:00:00+00:00',cwd=str(self.root), git_common_dir=module.common_dir()) module.validate_precheck(receipt) (self.child/'value').write_text('changed after startup') with self.assertRaises(module.EvidenceError): module.validate_precheck(receipt) def test_new_layout_cannot_use_receipt_without_component_identity(self): spec=importlib.util.spec_from_file_location('component_evidence_legacy',SCRIPTS/'evidence.py') module=importlib.util.module_from_spec(spec); spec.loader.exec_module(module) self.run_git(self.root,'update-ref','refs/remotes/origin/main','HEAD') before=module.snapshot();before.pop('components') receipt=module.create_receipt('make precheck','[verdict] READY\n',0,before,before, timestamp='2026-10-13T00:00:00+00:00',cwd=str(self.root), git_common_dir=module.common_dir()) with self.assertRaises(module.EvidenceError): module.validate_precheck(receipt) def test_receipts_reject_component_path_escape(self): data=json.loads((self.root/'components.json').read_text()) data['components'][0]['path']='../outside' (self.root/'components.json').write_text(json.dumps(data)) with self.assertRaises(Exception): self.snapshot() if __name__=='__main__': unittest.main()