updater.test.ts 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
  1. import { lstatSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
  2. import { tmpdir } from 'node:os'
  3. import { join } from 'node:path'
  4. import { afterEach, describe, expect, it } from 'vitest'
  5. import { backupPlugin, currentTarget, enabledPlugins, formatCommandFailure, restorePlugin, switchCurrent, validateManifest } from '../bin/hermes-yaoyao-updater.mjs'
  6. const roots: string[] = []
  7. afterEach(() => {
  8. for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true })
  9. })
  10. describe('standalone updater primitives', () => {
  11. it('atomically switches the stable current symlink', () => {
  12. const root = mkdtempSync(join(tmpdir(), 'hermes-updater-'))
  13. roots.push(root)
  14. const first = join(root, 'releases', '0.2.0-a')
  15. const second = join(root, 'releases', '0.3.0-b')
  16. switchCurrent(root, first, 'first')
  17. expect(currentTarget(root)).toBe(first)
  18. switchCurrent(root, second, 'second')
  19. expect(currentTarget(root)).toBe(second)
  20. expect(lstatSync(join(root, 'current')).isSymbolicLink()).toBe(true)
  21. })
  22. it('rejects a mismatched release manifest before any service transition', () => {
  23. expect(() => validateManifest({
  24. schemaVersion: 1,
  25. releaseVersion: '0.3.0',
  26. webVersion: '0.3.1',
  27. pluginVersion: '1.8.0',
  28. gitTag: 'v0.3.0',
  29. })).toThrow('版本组合不一致')
  30. })
  31. it('keeps useful command output while redacting credentials', () => {
  32. const error = Object.assign(new Error('Command failed'), {
  33. status: 127,
  34. stdout: '\u001B[31m发布版本校验通过\u001B[0m',
  35. stderr: 'https://secret@example.test/release.git\nsh: vue-tsc: command not found',
  36. })
  37. const message = formatCommandFailure('npm', ['run', 'build'], error)
  38. expect(message).toContain('npm run build 失败,退出码 127')
  39. expect(message).toContain('发布版本校验通过')
  40. expect(message).toContain('https://***@example.test/release.git')
  41. expect(message).toContain('vue-tsc: command not found')
  42. expect(message).not.toContain('secret')
  43. expect(message).not.toContain('\u001B')
  44. })
  45. it('requests Hermes plugin configuration as JSON', () => {
  46. const calls: Array<{ command: string, args: string[] }> = []
  47. const plugins = enabledPlugins((command: string, args: string[]) => {
  48. calls.push({ command, args })
  49. return '["yaoyao", "other"]\n'
  50. })
  51. expect(plugins).toEqual(['yaoyao', 'other'])
  52. expect(calls).toEqual([{
  53. command: 'hermes',
  54. args: ['config', 'get', '--json', 'plugins.enabled'],
  55. }])
  56. expect(() => enabledPlugins(() => '- yaoyao\n')).toThrow('未返回有效 JSON')
  57. })
  58. it('backs up and restores plugin code without touching durable plugin data', () => {
  59. const root = mkdtempSync(join(tmpdir(), 'hermes-updater-plugin-'))
  60. roots.push(root)
  61. const hermesHome = join(root, 'hermes')
  62. const dataHome = join(root, 'web-data')
  63. const dashboard = join(hermesHome, 'plugins', 'yaoyao', 'dashboard')
  64. const durable = join(hermesHome, 'plugin-data', 'yaoyao')
  65. mkdirSync(dashboard, { recursive: true })
  66. mkdirSync(durable, { recursive: true })
  67. writeFileSync(join(dashboard, 'manifest.json'), JSON.stringify({ version: '1.7.1' }))
  68. writeFileSync(join(dashboard, 'plugin.py'), 'old plugin')
  69. writeFileSync(join(hermesHome, 'plugins', 'yaoyao', 'plugin.yaml'), 'version: "1.7.1"\n')
  70. writeFileSync(join(durable, 'archive.db'), 'durable data')
  71. const backup = backupPlugin({ hermesHome, dataHome }, '11111111-1111-4111-8111-111111111111')
  72. writeFileSync(join(dashboard, 'plugin.py'), 'new plugin')
  73. restorePlugin(backup.pluginRoot, backup.backupRoot, 'restore', true)
  74. expect(readFileSync(join(dashboard, 'plugin.py'), 'utf8')).toBe('old plugin')
  75. expect(readFileSync(join(durable, 'archive.db'), 'utf8')).toBe('durable data')
  76. })
  77. })