| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127 |
- """Actual r6 documentation addition, private-file preservation and rollback in an isolated tree."""
- import importlib.util
- import json
- from pathlib import Path
- import shutil
- import tempfile
- import unittest
- from unittest.mock import patch
- ROOT = Path(__file__).resolve().parents[1]
- PACKAGE = ROOT / 'docs/ksf-workshop-r10-baseline-upgrade-20261110-r1/package'
- BASELINE = ROOT / 'docs/workshop-skill-release-20261109-r7/rehearsal-baseline-r6'
- spec = importlib.util.spec_from_file_location('platform_upgrade', PACKAGE / 'upgrade.py')
- up = importlib.util.module_from_spec(spec)
- spec.loader.exec_module(up)
- def tree(directory):
- return {p.relative_to(directory).as_posix(): (p.read_bytes(), p.stat().st_mode & 0o777)
- for p in directory.rglob('*') if p.is_file()}
- class PlatformBaselineTests(unittest.TestCase):
- def setUp(self):
- temporary = tempfile.TemporaryDirectory(prefix='ksf-r6-r10-test-')
- self.addCleanup(temporary.cleanup)
- self.root = Path(temporary.name)
- self.target = self.root / 'skills/ksf-workshop-runtime'
- shutil.copytree(BASELINE, self.target)
- self.output = self.root / 'results'
- self.binding = self.target / 'credential-binding.json'
- self.binding.write_text('fixture-only-private-binding')
- self.binding.chmod(0o600)
- (self.target / 'local-settings.json').write_text('fixture-local-settings')
- self.before = tree(self.target)
- def run_upgrade(self, command):
- return up.run(command, str(self.target), str(self.output), PACKAGE)
- def test_plan_recognizes_exact_r6_without_skill_writes(self):
- result = self.run_upgrade('plan')
- self.assertEqual(result['baselineId'], 'r6')
- self.assertEqual((result['changes'], result['creates']), (24, 1))
- self.assertEqual(tree(self.target), self.before)
- def test_apply_adds_missing_document_and_preserves_full_backup_and_private_files(self):
- self.run_upgrade('plan')
- self.run_upgrade('apply')
- result = self.run_upgrade('verify')
- self.assertTrue(result['ok'])
- self.assertEqual(result['publicFilesVerified'], 43)
- self.assertEqual(tree(self.output / 'before-full'), self.before)
- self.assertTrue((self.target / 'references/workshop-report-design.md').is_file())
- for name, value in self.before.items():
- if name.startswith('scripts/') or name in ['runtime-lock.json', 'credential-binding.json', 'local-settings.json']:
- self.assertEqual(tree(self.target)[name], value)
- after = tree(self.target)
- self.assertTrue(self.run_upgrade('apply')['alreadyApplied'])
- self.assertEqual(tree(self.target), after)
- def test_modified_document_rejected_without_skill_or_result_writes(self):
- p = self.target / 'references/workshop-tasks.md'
- p.write_bytes(p.read_bytes() + b'\nlocal modification\n')
- before = tree(self.target)
- with self.assertRaisesRegex(Exception, 'UPGRADE_BASELINE_CHANGED'):
- self.run_upgrade('plan')
- self.assertEqual(tree(self.target), before)
- self.assertFalse(self.output.exists())
- def test_modified_protected_code_rejected(self):
- p = self.target / 'scripts/ksf-secure.mjs'
- p.write_bytes(p.read_bytes() + b'\nchanged code\n')
- before = tree(self.target)
- with self.assertRaisesRegex(Exception, 'UPGRADE_BASELINE_CHANGED'):
- self.run_upgrade('plan')
- self.assertEqual(tree(self.target), before)
- self.assertFalse(self.output.exists())
- def test_new_document_path_collision_is_not_overwritten(self):
- p = self.target / 'references/workshop-report-design.md'
- p.write_text('independent local work')
- before = tree(self.target)
- with self.assertRaisesRegex(Exception, 'UPGRADE_NEW_FILE_ALREADY_EXISTS'):
- self.run_upgrade('plan')
- self.assertEqual(tree(self.target), before)
- self.assertFalse(self.output.exists())
- def test_change_after_plan_stops_before_backup_or_replacement(self):
- self.run_upgrade('plan')
- self.binding.write_text('fixture-binding-edited-after-plan')
- before = tree(self.target)
- with self.assertRaisesRegex(Exception, 'TARGET_CHANGED_SINCE_PLAN'):
- self.run_upgrade('apply')
- self.assertEqual(tree(self.target), before)
- self.assertFalse((self.output / 'before-full').exists())
- def test_failure_after_new_document_restores_old_files_and_removes_addition(self):
- self.run_upgrade('plan')
- core = up.core_at(PACKAGE)
- original_replace = core.os.replace
- def fail_entry(source, destination):
- if Path(destination) == self.target / 'SKILL.md':
- self.assertTrue((self.target / 'references/workshop-report-design.md').exists())
- raise OSError('fixture replacement failure')
- return original_replace(source, destination)
- with patch.object(up, 'core_at', return_value=core), patch.object(core.os, 'replace', fail_entry):
- with self.assertRaises(OSError):
- self.run_upgrade('apply')
- self.assertEqual(tree(self.target), self.before)
- self.assertEqual(tree(self.output / 'before-full'), self.before)
- self.assertTrue(json.loads((self.output / 'apply.json').read_bytes())['originalRestored'])
- self.assertFalse(list(self.target.rglob('*.ksf-upgrade-new')))
- def test_inventory_contains_only_declared_public_hashes(self):
- result = self.run_upgrade('inventory')
- self.assertEqual(len(result['files']), 43)
- self.assertIsNone(result['files']['references/workshop-report-design.md'])
- self.assertNotIn('credential-binding.json', result['files'])
- self.assertNotIn('fixture-only-private-binding', json.dumps(result))
- self.assertEqual(tree(self.target), self.before)
- self.assertFalse(self.output.exists())
- if __name__ == '__main__':
- unittest.main()
|