mac-auto-update.test.mjs 4.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485
  1. import { test, before, after } from 'node:test'
  2. import assert from 'node:assert/strict'
  3. import { EventEmitter } from 'node:events'
  4. import Module, { createRequire } from 'node:module'
  5. import { mkdtempSync, writeFileSync, rmSync } from 'node:fs'
  6. import { get } from 'node:http'
  7. import { join } from 'node:path'
  8. import { tmpdir } from 'node:os'
  9. import { bundleDesktopUpdater } from '../scripts/bundle-desktop-updater.mjs'
  10. import { DesktopAutoUpdateManager } from './auto-update-manager.mjs'
  11. const directory = mkdtempSync(join(tmpdir(), 'yaoyao-native-update-'))
  12. const bundle = join(directory, 'electron-updater.cjs')
  13. before(async () => bundleDesktopUpdater(bundle))
  14. after(() => rmSync(directory, { recursive: true, force: true }))
  15. const deferred = () => { let resolve, reject; const promise = new Promise((yes, no) => { resolve = yes; reject = no }); return { promise, resolve, reject } }
  16. const httpGet = (url, headers) => new Promise((resolve, reject) => {
  17. get(url, { headers, agent: false }, response => {
  18. const chunks = []; response.on('data', chunk => chunks.push(chunk)); response.on('error', reject)
  19. response.on('end', () => resolve({ status: response.statusCode, text: Buffer.concat(chunks).toString() }))
  20. }).on('error', reject)
  21. })
  22. function fixture(t) {
  23. const native = new EventEmitter(), transferred = deferred(), calls = []
  24. let feed
  25. native.setFeedURL = value => { feed = value }
  26. native.quitAndInstall = () => calls.push('install')
  27. native.checkForUpdates = () => {
  28. void (async () => {
  29. assert.equal((await httpGet(feed.url)).status, 401)
  30. const metadata = await httpGet(feed.url, feed.headers)
  31. assert.equal(metadata.status, 200)
  32. const zip = await httpGet(JSON.parse(metadata.text).url)
  33. assert.equal(zip.text, 'fixture ZIP bytes')
  34. transferred.resolve()
  35. })().catch(transferred.reject)
  36. }
  37. const load = Module._load
  38. let driver
  39. try {
  40. Module._load = (name, ...args) => name === 'electron' ? { autoUpdater: native } : load(name, ...args)
  41. const { MacUpdater } = createRequire(import.meta.url)(bundle)
  42. driver = new MacUpdater(null, { version: '0.4.62', quit: () => assert.fail('unexpected quit') })
  43. } finally { Module._load = load }
  44. driver.logger = { info() {}, warn() {}, error() {}, debug() {} }
  45. const file = join(directory, 'update.zip'); writeFileSync(file, 'fixture ZIP bytes')
  46. driver.checkForUpdates = async () => ({ isUpdateAvailable: true, updateInfo: { version: '0.4.63' } })
  47. driver.downloadUpdate = () => driver.updateDownloaded({ info: { size: 17 }, url: new URL('https://fixture.invalid/update.zip') }, { version: '0.4.63', downloadedFile: file })
  48. const manager = new DesktopAutoUpdateManager({ driver, version: '0.4.62', prepareInstall: async () => calls.push('cleanup') })
  49. t.after(async () => {
  50. clearTimeout(manager.stagingTimer); clearTimeout(manager.restartTimer); manager.stopChecking()
  51. if (driver.server?.listening) await new Promise(done => driver.server.close(done))
  52. native.removeAllListeners()
  53. })
  54. return { native, driver, manager, transferred, calls }
  55. }
  56. test('bundled Mac updater waits for native validation after the real ZIP proxy transfer', async t => {
  57. const h = fixture(t)
  58. await h.manager.check(); const downloading = h.manager.download()
  59. await h.transferred.promise
  60. assert.equal(h.manager.snapshot().phase, 'preparing')
  61. assert.equal(h.driver.squirrelDownloadedUpdate, false)
  62. await h.manager.install(); assert.deepEqual(h.calls, [])
  63. assert.throws(() => h.driver.quitAndInstall(), /not ready/)
  64. h.native.emit('update-downloaded'); await downloading
  65. assert.equal(h.manager.snapshot().phase, 'ready')
  66. assert.equal(h.native.listenerCount('update-downloaded'), 1)
  67. await h.manager.install(); await h.manager.install()
  68. assert.deepEqual(h.calls, ['cleanup', 'install'])
  69. })
  70. test('native signature rejection leaves no restart action and cleans temporary listeners', async t => {
  71. const h = fixture(t)
  72. await h.manager.check(); const downloading = h.manager.download()
  73. await h.transferred.promise
  74. h.native.emit('error', new Error('signature validation failed'))
  75. await downloading
  76. assert.equal(h.manager.snapshot().phase, 'failed')
  77. assert.equal(h.manager.snapshot().retryable, false)
  78. assert.equal(h.native.listenerCount('update-downloaded'), 1)
  79. h.native.emit('update-downloaded'); await h.manager.install()
  80. assert.deepEqual(h.calls, [])
  81. })