import json from pathlib import Path import shutil import subprocess import sys import tempfile import unittest ROOT = Path(__file__).resolve().parents[1] SCRIPT = ROOT / 'scripts/install-unified-skills.py' class UnifiedSkillInstallerTests(unittest.TestCase): def setUp(self): self.tmp = tempfile.TemporaryDirectory();self.addCleanup(self.tmp.cleanup) self.root = Path(self.tmp.name) def run_cmd(self, command, skill, target, out, plan=None): selection = ['--component', 'research-runtime'] if skill == 'ksf-voc-library' else ['--skill', skill] argv = [sys.executable, '-B', '-X', 'utf8', str(SCRIPT), command, *selection, '--target', str(target), '--out', str(out)] if plan: argv += ['--plan', str(plan)] result = subprocess.run(argv, capture_output=True, encoding='utf-8') return result.returncode, json.loads(result.stdout) def target(self, skill): dest = self.root / skill shutil.copytree(ROOT / 'skills' / skill, dest) (dest / 'SKILL.md').write_text('previous-entry', encoding='utf-8') (dest / 'extra-local-file').write_text('preserve', encoding='utf-8') return dest def test_plan_apply_verify_preserve_other_files_and_business_code(self): for skill in ('ksf-voc-library', 'ksf-workshop-runtime'): target = self.target(skill) plans, applied = self.root / (skill + '-plan'), self.root / (skill + '-apply') self.assertEqual(self.run_cmd('plan', skill, target, plans)[0], 0) self.assertEqual((target / 'SKILL.md').read_text(), 'previous-entry') self.assertEqual(self.run_cmd('apply', skill, target, applied, plans / 'plan.json')[0], 0) self.assertEqual((applied / 'before/SKILL.md').read_text(), 'previous-entry') self.assertEqual((target / 'extra-local-file').read_text(), 'preserve') self.assertEqual(self.run_cmd('verify', skill, target, self.root / (skill + '-verify'))[0], 0) if skill == 'ksf-voc-library': self.assertFalse((target / 'SKILL.md').exists()) self.assertNotIn('SKILL.md', (target / 'FILES.sha256').read_text()) else: self.assertTrue((target / 'SKILL.md').exists()) def test_already_merged_research_runtime_needs_no_skill_entry(self): skill = 'ksf-voc-library';target = self.target(skill) (target / 'SKILL.md').unlink() plans, applied = self.root / 'runtime-plan', self.root / 'runtime-apply' self.assertEqual(self.run_cmd('plan', skill, target, plans)[0], 0) self.assertEqual(self.run_cmd('apply', skill, target, applied, plans / 'plan.json')[0], 0) self.assertFalse((target / 'SKILL.md').exists()) self.assertEqual(self.run_cmd('verify', skill, target, self.root / 'runtime-verify')[0], 0) def test_concurrent_change_and_protected_runtime_mismatch_stop_before_mutation(self): skill = 'ksf-workshop-runtime';target = self.target(skill) plans = self.root / 'plan' self.assertEqual(self.run_cmd('plan', skill, target, plans)[0], 0) (target / 'SKILL.md').write_text('concurrent-change', encoding='utf-8') self.assertNotEqual(self.run_cmd('apply', skill, target, self.root / 'apply', plans / 'plan.json')[0], 0) self.assertEqual((target / 'SKILL.md').read_text(), 'concurrent-change') protected = target / 'scripts/ksf-secure.mjs' protected.write_text('changed-runtime', encoding='utf-8') code, result = self.run_cmd('plan', skill, target, self.root / 'plan-bad-runtime') self.assertNotEqual(code, 0) self.assertEqual(result['error']['code'], 'PROTECTED_BUSINESS_RUNTIME_MISMATCH') if __name__ == '__main__': unittest.main()