| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104 |
- """Observable r7-to-r8 upgrade, preservation and rollback using the official artifact."""
- import argparse
- import importlib.util
- import json
- from pathlib import Path
- import tempfile
- import unittest
- from unittest.mock import patch
- ROOT = Path(__file__).resolve().parents[1]
- OLD = ROOT / 'docs/workshop-onboarding-20261109-r2/package'
- NEW = ROOT / 'docs/workshop-onboarding-20261109-r3/package'
- ARCHIVE = ROOT / 'docs/workshop-onboarding-20261109/source-audit/voc-skill-0.3.42.tgz'
- spec = importlib.util.spec_from_file_location('upgrade_installer', ROOT / 'scripts/workshop-onboarding/install.py')
- installer = importlib.util.module_from_spec(spec)
- spec.loader.exec_module(installer)
- class UpgradeTests(unittest.TestCase):
- def setUp(self):
- self.temp = tempfile.TemporaryDirectory(prefix='ksf-upgrade-test-')
- self.addCleanup(self.temp.cleanup)
- self.root = Path(self.temp.name)
- self.skills = self.root / 'skills'
- self.args = argparse.Namespace(skills_dir=str(self.skills), out=str(self.root / 'old-result'),
- voc_archive=str(ARCHIVE), apply=True, upgrade=False, prepare_runtime=False)
- def old_install(self):
- installer.run(self.args, OLD)
- self.args.out = str(self.root / 'new-result')
- self.binding = self.skills / 'ksf-workshop-runtime/credential-binding.json'
- self.binding.write_text('local-fixture-binding')
- def test_fresh_r8_and_repeat(self):
- result = installer.run(self.args, NEW)
- self.assertTrue(result['filesInstalled'])
- self.args.apply = False
- repeat = installer.run(self.args, NEW)
- self.assertEqual([r['action'] for r in repeat['actions']], ['verify-only', 'verify-only'])
- self.assertEqual(len(list(self.skills.glob('*/SKILL.md'))), 2) # workshop plus official external VOC
- self.assertFalse((self.skills / 'ksf-voc-library').exists())
- def test_r7_plan_no_write_upgrade_backup_and_binding_preserved(self):
- self.old_install()
- before = (self.skills / 'ksf-workshop-runtime/SKILL.md').read_bytes()
- self.args.apply = False
- plan = installer.run(self.args, NEW)
- self.assertEqual(plan['actions'][0]['action'], 'upgrade')
- self.assertEqual((self.skills / 'ksf-workshop-runtime/SKILL.md').read_bytes(), before)
- self.assertFalse(Path(self.args.out).exists())
- self.args.apply = self.args.upgrade = True
- applied = installer.run(self.args, NEW)
- self.assertTrue(applied['filesInstalled'])
- self.assertEqual(self.binding.read_text(), 'local-fixture-binding')
- self.assertEqual((Path(self.args.out) / 'before/ksf-workshop-runtime/SKILL.md').read_bytes(), before)
- installer.verify_component(self.skills / 'ksf-workshop-runtime',
- installer.source_files(NEW, installer.verify_package(NEW), ARCHIVE)['ksf-workshop-runtime'])
- self.args.apply = False
- self.assertEqual(installer.run(self.args, NEW)['actions'][0]['action'], 'verify-only')
- def test_apply_upgrade_requires_flag_before_writes(self):
- self.old_install()
- before = (self.skills / 'ksf-workshop-runtime/SKILL.md').read_bytes()
- with self.assertRaisesRegex(installer.InstallError, 'UPGRADE_FLAG_REQUIRED'):
- installer.run(self.args, NEW)
- self.assertEqual((self.skills / 'ksf-workshop-runtime/SKILL.md').read_bytes(), before)
- self.assertFalse(Path(self.args.out).exists())
- def test_local_edit_or_code_edit_stops_without_overwrite(self):
- self.old_install()
- path = self.skills / 'ksf-workshop-runtime/references/workshop-tasks.md'
- original = path.read_bytes()
- for target in [path, self.skills / 'ksf-workshop-runtime/scripts/ksf-secure.mjs']:
- old = target.read_bytes()
- target.write_bytes(old + b'\nlocal edit\n')
- self.args.upgrade = True
- with self.assertRaisesRegex(installer.InstallError, 'UPGRADE_BASELINE_CHANGED'):
- installer.run(self.args, NEW)
- self.assertEqual(target.read_bytes(), old + b'\nlocal edit\n')
- self.assertFalse(Path(self.args.out).exists())
- target.write_bytes(old)
- self.assertEqual(path.read_bytes(), original)
- def test_replacement_failure_restores_original_and_keeps_binding(self):
- self.old_install()
- skill = self.skills / 'ksf-workshop-runtime'
- originals = {str(p.relative_to(skill)): p.read_bytes() for p in skill.rglob('*') if p.is_file()}
- real_replace = installer.os.replace
- def fail_entry(source, dest):
- if Path(dest).name == 'SKILL.md':
- raise OSError('synthetic replacement failure')
- return real_replace(source, dest)
- self.args.upgrade = True
- with patch.object(installer.os, 'replace', fail_entry), self.assertRaises(OSError):
- installer.run(self.args, NEW)
- self.assertEqual({str(p.relative_to(skill)): p.read_bytes() for p in skill.rglob('*') if p.is_file()}, originals)
- self.assertFalse(json.loads((Path(self.args.out) / 'install-receipt.json').read_bytes())['ok'])
- self.assertEqual(len(list(self.skills.glob('.ksf-init-*'))), 0)
- if __name__ == '__main__':
- unittest.main()
|