updater-controller.test.ts 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  1. import { describe, expect, test } from "bun:test"
  2. import { createUpdaterController, type UpdaterBackend, type UpdaterReadyRecord } from "./updater-controller"
  3. function setup(input?: { currentVersion?: string; ready?: UpdaterReadyRecord }) {
  4. const calls: string[] = []
  5. const backend: UpdaterBackend = {
  6. async checkForUpdates() {
  7. calls.push("check")
  8. return { isUpdateAvailable: true, updateInfo: { version: "2.0.0" } }
  9. },
  10. async downloadUpdate() {
  11. calls.push("download")
  12. },
  13. quitAndInstall() {
  14. calls.push("install")
  15. },
  16. }
  17. let ready = input?.ready
  18. const controller = createUpdaterController({
  19. enabled: true,
  20. currentVersion: input?.currentVersion ?? "1.0.0",
  21. backend,
  22. persistence: {
  23. get: () => ready,
  24. set: (value) => {
  25. ready = value
  26. },
  27. clear: () => {
  28. ready = undefined
  29. },
  30. },
  31. stop: async () => {
  32. calls.push("stop")
  33. },
  34. })
  35. return { controller, calls, getReady: () => ready }
  36. }
  37. describe("updater controller", () => {
  38. test("checks, downloads, persists, and publishes one authoritative ready state", async () => {
  39. const app = setup()
  40. const states: ReturnType<typeof app.controller.getState>[] = []
  41. app.controller.subscribe((state) => states.push(state))
  42. await app.controller.start()
  43. expect(app.calls).toEqual(["check", "download"])
  44. expect(app.getReady()).toEqual({ version: "2.0.0" })
  45. expect(states.map((state) => state.status)).toEqual(["idle", "checking", "downloading", "ready"])
  46. expect(app.controller.getState()).toEqual({ status: "ready", version: "2.0.0" })
  47. })
  48. test("revalidates a persisted target through the updater cache on launch", async () => {
  49. const app = setup({ ready: { version: "2.0.0" } })
  50. await app.controller.start()
  51. expect(app.calls).toEqual(["check", "download"])
  52. expect(app.controller.getState()).toEqual({ status: "ready", version: "2.0.0" })
  53. })
  54. test("clears a target already installed before checking", async () => {
  55. const app = setup({ currentVersion: "2.0.0", ready: { version: "2.0.0" } })
  56. await app.controller.start()
  57. expect(app.getReady()).toBeUndefined()
  58. expect(app.calls).toEqual(["check"])
  59. })
  60. test("coalesces concurrent checks", async () => {
  61. const app = setup()
  62. await Promise.all([app.controller.check(), app.controller.check(), app.controller.check()])
  63. expect(app.calls).toEqual(["check", "download"])
  64. })
  65. test("returns to ready when quitAndInstall returns without exiting", async () => {
  66. const app = setup()
  67. await app.controller.start()
  68. await app.controller.install()
  69. expect(app.calls).toEqual(["check", "download", "stop", "install"])
  70. expect(app.controller.getState()).toEqual({ status: "ready", version: "2.0.0" })
  71. })
  72. test("returns to ready when installation cannot start", async () => {
  73. const app = setup()
  74. await app.controller.start()
  75. const failed = createUpdaterController({
  76. enabled: true,
  77. currentVersion: "1.0.0",
  78. backend: {
  79. checkForUpdates: async () => ({ isUpdateAvailable: true, updateInfo: { version: "2.0.0" } }),
  80. downloadUpdate: async () => {},
  81. quitAndInstall() {},
  82. },
  83. persistence: { get: () => undefined, set() {}, clear() {} },
  84. stop: async () => {
  85. throw new Error("stop failed")
  86. },
  87. })
  88. await failed.start()
  89. await expect(failed.install()).rejects.toThrow("stop failed")
  90. expect(failed.getState()).toEqual({ status: "ready", version: "2.0.0" })
  91. })
  92. })