enterprise-voc.test.py 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. """VOC bootstrap behavior: verified reuse, missing install, failure isolation and r1 upgrade."""
  2. import importlib.util
  3. import json
  4. from pathlib import Path
  5. import subprocess
  6. import sys
  7. import unittest
  8. sys.dont_write_bytecode = True
  9. ROOT = Path(__file__).resolve().parents[1]
  10. spec = importlib.util.spec_from_file_location('enterprise_fixtures', ROOT / 'test/enterprise-workshop.test.py')
  11. base = importlib.util.module_from_spec(spec)
  12. spec.loader.exec_module(base)
  13. import bootstrap as core
  14. import gogs_setup
  15. import voc
  16. class VocTests(unittest.TestCase):
  17. @classmethod
  18. def setUpClass(cls):
  19. cls.payload = base.packager.content()
  20. cls.archive = ROOT / 'work/enterprise-workshop-20261110/voc-0344/voc-skill-0.3.44.tgz'
  21. setUp = base.EnterpriseTests.setUp
  22. save_current = base.EnterpriseTests.save_current
  23. def official_fixture(self, name='voc-intelligence'):
  24. _, meta = voc.release(self.package)
  25. target = self.data / 'skills' / name
  26. for relative, raw in voc.unpack(self.archive.read_bytes(), meta).items():
  27. core.atomic_create(target / relative, raw, 0o644)
  28. (target / '.mcp.json').write_bytes(core.encode(voc.fragment(target)))
  29. return target
  30. def runner(self, npm_ok=True):
  31. calls = []
  32. def run(args, **kwargs):
  33. calls.append(args)
  34. if 'ci' in args:
  35. return subprocess.CompletedProcess(args, 0 if npm_ok else 1, b'', b'')
  36. self.assertTrue(any(str(x).endswith('check-voc.cjs') for x in args))
  37. value = {'ok': True, 'version': '0.3.44', 'credentialAvailable': False,
  38. 'collectionRequests': 0, 'tools': ['voc_api_search','voc_api_doc','voc_api_call']}
  39. return subprocess.CompletedProcess(args, 0, core.encode(value), b'')
  40. return calls, run
  41. def test_missing_voc_is_required_even_with_generic_social_skill(self):
  42. core.atomic_create(self.data / 'skills/voc-social-suite/SKILL.md', b'GENERIC VOC GUIDE')
  43. result = voc.plan(self.package, self.data)
  44. self.assertEqual(result['action'], 'install-missing')
  45. self.assertTrue(result['target'].endswith('voc-intelligence'))
  46. def test_existing_official_package_is_reused_without_npm_or_config_changes(self):
  47. target = self.official_fixture('voc-social-suite')
  48. config = voc.fragment(target)
  49. config['mcpServers']['voc']['env']['PRIVATE_EXISTING_VALUE'] = 'local-only'
  50. (target / '.mcp.json').write_bytes(core.encode(config))
  51. before = (target / '.mcp.json').read_bytes(), (target / '.mcp.json').stat().st_mtime_ns
  52. output = self.data / 'results'
  53. output.mkdir()
  54. calls, runner = self.runner()
  55. result = voc.apply(self.package, self.data, voc.plan(self.package, self.data), output, runner=runner)
  56. self.assertFalse(result['vocModified'])
  57. self.assertEqual(result['action'], 'verified-existing')
  58. self.assertEqual(len(calls), 1)
  59. self.assertEqual(before, ((target / '.mcp.json').read_bytes(), (target / '.mcp.json').stat().st_mtime_ns))
  60. self.assertFalse(result['runtime']['credentialAvailable'])
  61. def test_other_version_is_not_silently_downgraded_or_upgraded(self):
  62. for version in ('0.3.42', '0.3.45'):
  63. path = self.data / 'skills/voc-intelligence/package.json'
  64. path.parent.mkdir(parents=True, exist_ok=True)
  65. path.write_bytes(core.encode({'name': '@vocmarket/voc-skill', 'version': version}))
  66. with self.assertRaisesRegex(core.Stopped, 'EXISTING_VOC_VERSION_DIFFERS'):
  67. voc.plan(self.package, self.data)
  68. self.assertEqual(json.loads(path.read_bytes())['version'], version)
  69. def test_modified_public_script_stops_before_install(self):
  70. target = self.official_fixture()
  71. script = target / 'mcp/src/server.js'
  72. script.write_bytes(b'UNKNOWN LOCAL CODE')
  73. with self.assertRaisesRegex(core.Stopped, 'EXISTING_VOC_FILES_CHANGED'):
  74. voc.plan(self.package, self.data)
  75. self.assertEqual(script.read_bytes(), b'UNKNOWN LOCAL CODE')
  76. def test_tampered_tarball_is_rejected(self):
  77. _, meta = voc.release(self.package)
  78. with self.assertRaisesRegex(core.Stopped, 'VOC_ARCHIVE_HASH_MISMATCH'):
  79. voc.unpack(self.archive.read_bytes() + b'TAMPER', meta)
  80. def test_missing_install_stages_then_commits_and_replay_does_not_reinstall(self):
  81. output = self.data / 'results'
  82. output.mkdir()
  83. original_plan = voc.plan(self.package, self.data)
  84. calls, runner = self.runner()
  85. result = voc.apply(self.package, self.data, original_plan, output, self.archive, runner)
  86. self.assertTrue(result['vocModified'])
  87. self.assertEqual(result['publicFilesVerified'], 169)
  88. again = voc.apply(self.package, self.data, original_plan, output, self.archive, runner)
  89. self.assertFalse(again['vocModified'])
  90. self.assertEqual(sum('ci' in call for call in calls), 1)
  91. self.assertFalse(result['liveAccessVerified'])
  92. self.assertFalse(result['hostRegistrationVerified'])
  93. def test_dependency_failure_does_not_leave_an_installed_skill(self):
  94. output = self.data / 'results'
  95. output.mkdir()
  96. calls, runner = self.runner(False)
  97. with self.assertRaisesRegex(core.Stopped, 'VOC_DEPENDENCY_INSTALL_FAILED'):
  98. voc.apply(self.package, self.data, voc.plan(self.package, self.data), output, self.archive, runner)
  99. self.assertFalse((self.data / 'skills/voc-intelligence').exists())
  100. def test_r1_binding_upgrade_preserves_skill_and_private_identity(self):
  101. old = ROOT / 'docs/ksf-enterprise-workshop-20261110-r1/package'
  102. for source in (old / 'payload').rglob('*'):
  103. if source.is_file():
  104. core.atomic_create(self.data / source.relative_to(old / 'payload'), source.read_bytes(), 0o644)
  105. before = core.encode({'schema': 'ksf.enterprise-workshop-binding.v1', 'companyObjectId': 'FKieRbIoGt',
  106. 'appId': 'FKieRbIoGt_main', 'configurationSha256': core.sha((old / 'enterprise.json').read_bytes()),
  107. 'packageManifestSha256': core.sha((old / 'FILES.sha256').read_bytes()),
  108. 'skill': 'ksf-workshop-runtime', 'version': 'r13'})
  109. binding = self.data / 'ksf-enterprise-workshop/binding.json'
  110. core.atomic_create(binding, before)
  111. identity = self.data / 'skills/ksf-workshop-runtime/credential-binding.json'
  112. core.atomic_create(identity, b'ORIGINAL PRIVATE IDENTITY')
  113. p = core.plan(self.package, self.data)
  114. self.assertTrue(p['bindingUpdateRequired'])
  115. self.assertEqual(p['changes'], [])
  116. result = core.apply(self.package, self.data, p)
  117. self.assertEqual(result['installedFiles'], 0)
  118. self.assertEqual(identity.read_bytes(), b'ORIGINAL PRIVATE IDENTITY')
  119. backups = list(binding.parent.glob('binding-before-*'))
  120. self.assertEqual(len(backups), 1)
  121. self.assertEqual(backups[0].read_bytes(), before)
  122. def test_only_exact_r1_configuration_repository_can_upgrade(self):
  123. old = ROOT / 'docs/ksf-enterprise-workshop-20261110-r1/package'
  124. files = {p.relative_to(old).as_posix(): p.read_bytes() for p in old.rglob('*') if p.is_file()}
  125. _, candidate = core.sealed(self.package)
  126. self.assertEqual(gogs_setup.repository_state(files, self.package, candidate), 'known-r1')
  127. files['README.md'] += b'UNKNOWN LOCAL CHANGE'
  128. with self.assertRaisesRegex(core.Stopped, 'EXISTING_REPOSITORY_CONTENT_CHANGED'):
  129. gogs_setup.repository_state(files, self.package, candidate)
  130. if __name__ == '__main__':
  131. unittest.main(verbosity=2)