update-coordinator.spec.ts 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. import { describe, expect, it, vi } from 'vitest'
  2. import type { AppUpdater } from 'electron-updater'
  3. import { DESKTOP_HOST_PROTOCOL_VERSION } from '../src/host-protocol.ts'
  4. import { parseDesktopRelease } from '../src/release.ts'
  5. import type { DesktopUpdateState } from '../src/ipc.ts'
  6. vi.mock('electron', () => ({ app: { isPackaged: false } }))
  7. vi.mock('electron-updater', () => ({
  8. default: { autoUpdater: { autoDownload: true, autoInstallOnAppQuit: true } },
  9. }))
  10. const { DesktopUpdateCoordinator } = await import('../src/update-coordinator.ts')
  11. describe('desktop release metadata', () => {
  12. it('accepts one exact release identity for Electron and dsh', () => {
  13. expect(parseDesktopRelease({
  14. schemaVersion: 1,
  15. version: '1.2.3',
  16. hostProtocolVersion: DESKTOP_HOST_PROTOCOL_VERSION,
  17. nodeVersion: '24.17.0',
  18. pnpmVersion: '11.7.0',
  19. })).toEqual({
  20. schemaVersion: 1,
  21. version: '1.2.3',
  22. hostProtocolVersion: DESKTOP_HOST_PROTOCOL_VERSION,
  23. nodeVersion: '24.17.0',
  24. pnpmVersion: '11.7.0',
  25. })
  26. })
  27. it('rejects invalid versions and unsupported host protocols', () => {
  28. const base = {
  29. schemaVersion: 1,
  30. version: '1.2.3',
  31. hostProtocolVersion: DESKTOP_HOST_PROTOCOL_VERSION,
  32. nodeVersion: '24.17.0',
  33. pnpmVersion: '11.7.0',
  34. }
  35. expect(() => parseDesktopRelease({ ...base, version: 'latest' })).toThrow(/invalid desktop release metadata/u)
  36. expect(() => parseDesktopRelease({ ...base, hostProtocolVersion: 999 })).toThrow(/invalid desktop release metadata/u)
  37. })
  38. })
  39. describe('desktop update coordinator', () => {
  40. it('installs one Electron release and restarts after download', async () => {
  41. const states: DesktopUpdateState[] = []
  42. const downloadUpdate = vi.fn(async () => [])
  43. const quitAndInstall = vi.fn()
  44. const beforeRestart = vi.fn(async () => {})
  45. const updater = {
  46. autoDownload: true,
  47. autoInstallOnAppQuit: true,
  48. checkForUpdates: vi.fn(async () => ({
  49. isUpdateAvailable: true,
  50. updateInfo: { version: '1.1.0' },
  51. })),
  52. downloadUpdate,
  53. quitAndInstall,
  54. } as unknown as AppUpdater
  55. const coordinator = new DesktopUpdateCoordinator(
  56. (state) => {
  57. states.push(state)
  58. return state
  59. },
  60. beforeRestart,
  61. updater,
  62. () => true,
  63. )
  64. await expect(coordinator.check()).resolves.toEqual({ phase: 'available', version: '1.1.0' })
  65. await expect(coordinator.install()).resolves.toEqual({ phase: 'ready', version: '1.1.0' })
  66. expect(downloadUpdate).toHaveBeenCalledOnce()
  67. expect(beforeRestart).toHaveBeenCalledOnce()
  68. expect(quitAndInstall).toHaveBeenCalledWith(false, true)
  69. expect(states.map(state => state.phase)).toEqual(['checking', 'available', 'installing', 'ready'])
  70. })
  71. it('queues install behind an in-flight check instead of returning the check result', async () => {
  72. const checked = Promise.withResolvers<{
  73. isUpdateAvailable: true
  74. updateInfo: { version: string }
  75. }>()
  76. const downloadUpdate = vi.fn(async () => [])
  77. const updater = {
  78. autoDownload: true,
  79. autoInstallOnAppQuit: true,
  80. checkForUpdates: vi.fn(() => checked.promise),
  81. downloadUpdate,
  82. quitAndInstall: vi.fn(),
  83. } as unknown as AppUpdater
  84. const coordinator = new DesktopUpdateCoordinator(state => state, async () => {}, updater, () => true)
  85. const checking = coordinator.check()
  86. const installing = coordinator.install()
  87. expect(downloadUpdate).not.toHaveBeenCalled()
  88. checked.resolve({ isUpdateAvailable: true, updateInfo: { version: '1.2.0' } })
  89. await expect(checking).resolves.toEqual({ phase: 'available', version: '1.2.0' })
  90. await expect(installing).resolves.toEqual({ phase: 'ready', version: '1.2.0' })
  91. expect(downloadUpdate).toHaveBeenCalledOnce()
  92. })
  93. })