workshop-skill-install.test.js 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354
  1. import test from 'node:test';
  2. import assert from 'node:assert/strict';
  3. import fs from 'node:fs';
  4. import os from 'node:os';
  5. import path from 'node:path';
  6. import { fileURLToPath } from 'node:url';
  7. import { installWorkshopSkill } from '../scripts/install-workshop-skill.mjs';
  8. const source = fileURLToPath(new URL('../skills/ksf-workshop-runtime', import.meta.url));
  9. function fixture(t) {
  10. const root = fs.mkdtempSync(path.join(os.tmpdir(), 'ksf-skill-install-'));
  11. t.after(() => {
  12. assert.equal(path.dirname(root), fs.realpathSync(os.tmpdir()));
  13. assert.ok(path.basename(root).startsWith('ksf-skill-install-'));
  14. fs.rmSync(root, { recursive: true });
  15. });
  16. const destination = path.join(root, 'skills', 'ksf-workshop-runtime');
  17. fs.cpSync(source, destination, { recursive: true });
  18. fs.writeFileSync(path.join(destination, 'credential-binding.json'), 'fixture-private-binding');
  19. const state = path.join(root, 'ksf-workshop-state', 'participants');
  20. fs.mkdirSync(state, { recursive: true });
  21. fs.writeFileSync(path.join(state, 'fixture.json'), 'fixture-private-state');
  22. return { root, destination, state };
  23. }
  24. test('Skill只读预检与重复安装不改变绑定、状态和文件,也不创建备份', t => {
  25. const f = fixture(t);
  26. const check = installWorkshopSkill(source, f.destination, { apply: false });
  27. assert.equal(check.readOnly, true); assert.equal(check.filesChecked, 11); assert.deepEqual(check.changed, []);
  28. const result = installWorkshopSkill(source, f.destination);
  29. assert.deepEqual(result.changed, []); assert.equal(result.backup, null);
  30. assert.equal(fs.readFileSync(path.join(f.destination, 'credential-binding.json'), 'utf8'), 'fixture-private-binding');
  31. assert.equal(fs.readFileSync(path.join(f.state, 'fixture.json'), 'utf8'), 'fixture-private-state');
  32. assert.ok(!JSON.stringify(result).includes('fixture-private'));
  33. });
  34. test('未知目标修改在升级前停止,不覆盖维护人的Skill或清单', t => {
  35. for (const name of ['SKILL.md', 'FILES.sha256']) {
  36. const f = fixture(t), target = path.join(f.destination, name);
  37. fs.writeFileSync(target, 'maintainer-concurrent-edit');
  38. assert.throws(() => installWorkshopSkill(source, f.destination), { code: name === 'SKILL.md' ? 'TARGET_HAS_UNEXPECTED_CONTENT' : 'TARGET_MANIFEST_HAS_UNEXPECTED_CONTENT' });
  39. assert.equal(fs.readFileSync(target, 'utf8'), 'maintainer-concurrent-edit');
  40. assert.deepEqual(fs.readdirSync(f.root).sort(), ['ksf-workshop-state', 'skills']);
  41. }
  42. });
  43. test('包内脚本被篡改时拒绝升级且保留已有文件', t => {
  44. const f = fixture(t), altered = path.join(f.root, 'altered-package');
  45. fs.cpSync(source, altered, { recursive: true });
  46. fs.appendFileSync(path.join(altered, 'scripts', 'ksf-workshop.mjs'), '\n// tampered\n');
  47. const before = fs.readFileSync(path.join(f.destination, 'SKILL.md'));
  48. assert.throws(() => installWorkshopSkill(altered, f.destination), { code: 'PACKAGE_HASH_MISMATCH' });
  49. assert.deepEqual(fs.readFileSync(path.join(f.destination, 'SKILL.md')), before);
  50. });