test_query_skill_install.py 4.0 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576
  1. import hashlib
  2. import importlib.util
  3. import json
  4. from pathlib import Path
  5. import tempfile
  6. import unittest
  7. from unittest.mock import patch
  8. MODULE = Path(__file__).resolve().parents[2] / 'scripts/install-query-skills.py'
  9. spec = importlib.util.spec_from_file_location('query_install', MODULE)
  10. installer = importlib.util.module_from_spec(spec)
  11. spec.loader.exec_module(installer)
  12. class QuerySkillInstallTests(unittest.TestCase):
  13. def setUp(self):
  14. self.temp = tempfile.TemporaryDirectory()
  15. self.addCleanup(self.temp.cleanup)
  16. self.root = Path(self.temp.name) / 'data'
  17. self.package = self.root / 'delivery'
  18. self.targets = self.root / 'skills'
  19. baselines = {}
  20. for name in installer.NAMES:
  21. source = self.package / 'skills' / name
  22. source.mkdir(parents=True)
  23. (source / 'SKILL.md').write_text('new rules', encoding='utf-8')
  24. (source / 'FILES.sha256').write_text(hashlib.sha256(b'new rules').hexdigest() + ' SKILL.md\n', encoding='utf-8')
  25. target = self.targets / name
  26. target.mkdir(parents=True)
  27. (target / 'SKILL.md').write_text('old rules', encoding='utf-8')
  28. (target / 'FILES.sha256').write_text(hashlib.sha256(b'old rules').hexdigest() + ' SKILL.md\n', encoding='utf-8')
  29. baselines[name] = [installer.snapshot(target)]
  30. (self.package / 'baselines.json').write_text(json.dumps(baselines), encoding='utf-8')
  31. (self.targets / 'ksf-workshop-runtime/credential-binding.json').write_text('private sentinel', encoding='utf-8')
  32. self.state = self.root / 'ksf-workshop-state/sessions'
  33. self.state.mkdir(parents=True)
  34. (self.state / 'existing.json').write_text('existing session', encoding='utf-8')
  35. def test_read_only_plan_apply_and_noop_preserve_binding_and_state(self):
  36. before = installer.snapshot(self.root)
  37. plan = installer.install(self.package, self.root)
  38. self.assertTrue(plan['readOnly']); self.assertFalse(plan['applied'])
  39. self.assertEqual(installer.snapshot(self.root), before)
  40. binding = (self.targets / 'ksf-workshop-runtime/credential-binding.json').read_bytes()
  41. state = installer.snapshot(self.root / 'ksf-workshop-state')
  42. result = installer.install(self.package, self.root, True)
  43. self.assertTrue(result['filesystemVerified'])
  44. self.assertEqual((self.targets / 'ksf-workshop-runtime/credential-binding.json').read_bytes(), binding)
  45. self.assertEqual(installer.snapshot(self.root / 'ksf-workshop-state'), state)
  46. self.assertEqual((Path(result['backup']) / 'ksf-workshop-runtime/SKILL.md').read_text(), 'old rules')
  47. self.assertFalse(installer.install(self.package, self.root, True)['applied'])
  48. def test_unknown_baseline_and_tampered_package_fail_before_writing(self):
  49. (self.targets / 'ksf-voc-library/SKILL.md').write_text('concurrent change', encoding='utf-8')
  50. before = installer.snapshot(self.root)
  51. with self.assertRaisesRegex(ValueError, 'UNKNOWN_SKILL_BASELINE'):
  52. installer.install(self.package, self.root, True)
  53. self.assertEqual(installer.snapshot(self.root), before)
  54. (self.package / 'skills/ksf-workshop-runtime/SKILL.md').write_text('tampered', encoding='utf-8')
  55. with self.assertRaisesRegex(ValueError, 'PACKAGE_HASH_MISMATCH'):
  56. installer.install(self.package, self.root, True)
  57. def test_partial_install_failure_restores_only_our_changes(self):
  58. before = installer.snapshot(self.targets)
  59. replace = installer.os.replace
  60. count = 0
  61. def fail_second(source, target):
  62. nonlocal count
  63. count += 1
  64. if count == 2:
  65. raise OSError('simulated failure')
  66. return replace(source, target)
  67. with patch.object(installer.os, 'replace', fail_second):
  68. with self.assertRaisesRegex(OSError, 'simulated failure'):
  69. installer.install(self.package, self.root, True)
  70. self.assertEqual(installer.snapshot(self.targets), before)