import { assert, describe, it } from "@effect/vitest"; import type { DesktopUpdateState } from "@t3tools/contracts"; import { MAX_REMOTE_UPDATE_CHECKS, MAX_REMOTE_UPDATE_DOWNLOADS, nextRemoteDesktopUpdateStep, normalizeRemoteUpdateReason, type RemoteDesktopUpdateAttempts, } from "./remoteUpdateFlow.ts"; const NO_ATTEMPTS: RemoteDesktopUpdateAttempts = { checks: 0, downloads: 0 }; function makeState(overrides: Partial = {}): DesktopUpdateState { return { enabled: true, status: "idle", channel: "latest", currentVersion: "1.2.3", hostArch: "arm64", appArch: "arm64", runningUnderArm64Translation: false, availableVersion: null, downloadedVersion: null, releaseNotes: [], downloadPercent: null, checkedAt: null, message: null, errorContext: null, canRetry: false, omittedReleaseCount: 0, ...overrides, }; } describe("nextRemoteDesktopUpdateStep", () => { it("fails immediately when updates are disabled, preferring the known reason", () => { const state = makeState({ enabled: false, status: "disabled" }); assert.deepEqual(nextRemoteDesktopUpdateStep(state, NO_ATTEMPTS, "dev build"), { action: "done", outcome: "failed", reason: "dev build", }); assert.deepEqual(nextRemoteDesktopUpdateStep(state, NO_ATTEMPTS, null), { action: "done", outcome: "failed", reason: "Automatic updates are disabled on this machine.", }); }); it("installs only from the downloaded status the updater will accept", () => { assert.deepEqual( nextRemoteDesktopUpdateStep( makeState({ status: "downloaded", downloadedVersion: "1.2.4" }), NO_ATTEMPTS, null, ), { action: "install" }, ); // A previous install failure keeps status "downloaded", so a remote run // retries the install. assert.deepEqual( nextRemoteDesktopUpdateStep( makeState({ status: "downloaded", downloadedVersion: "1.2.4", errorContext: "install", message: "quitAndInstall failed", }), NO_ATTEMPTS, null, ), { action: "install" }, ); // A download survives an unrelated background updater error, so a run // installs it instead of replaying that error. assert.deepEqual( nextRemoteDesktopUpdateStep( makeState({ status: "error", downloadedVersion: "1.2.4", errorContext: null, message: "background updater error", }), NO_ATTEMPTS, null, ), { action: "install" }, ); // A leftover download behind a check error is not installable: fresh // runs re-check, and post-check the error is terminal. const staleError = makeState({ status: "error", downloadedVersion: "1.2.4", errorContext: "check", message: "feed unreachable", }); assert.deepEqual(nextRemoteDesktopUpdateStep(staleError, NO_ATTEMPTS, null), { action: "check", }); assert.deepEqual(nextRemoteDesktopUpdateStep(staleError, { checks: 1, downloads: 0 }, null), { action: "done", outcome: "failed", reason: "feed unreachable", }); }); it("rides along while a check or download is already in flight", () => { assert.deepEqual( nextRemoteDesktopUpdateStep(makeState({ status: "checking" }), NO_ATTEMPTS, null), { action: "wait", }, ); assert.deepEqual( nextRemoteDesktopUpdateStep( makeState({ status: "downloading", availableVersion: "1.2.4", downloadPercent: 40 }), NO_ATTEMPTS, null, ), { action: "wait" }, ); }); it("downloads an available update until the attempt cap, then fails", () => { const available = makeState({ status: "available", availableVersion: "1.2.4" }); assert.deepEqual(nextRemoteDesktopUpdateStep(available, NO_ATTEMPTS, null), { action: "download", }); assert.deepEqual( nextRemoteDesktopUpdateStep( available, { checks: 1, downloads: MAX_REMOTE_UPDATE_DOWNLOADS }, null, ), { action: "done", outcome: "failed", reason: "The desktop app failed to download the update.", }, ); assert.deepEqual( nextRemoteDesktopUpdateStep( makeState({ status: "available", availableVersion: "1.2.4", message: "network blipped", }), { checks: 1, downloads: MAX_REMOTE_UPDATE_DOWNLOADS }, null, ), { action: "done", outcome: "failed", reason: "network blipped" }, ); }); it("re-checks stale up-to-date and error states before trusting them", () => { // These states are retained from earlier/background checks; a remote // request must look again instead of replaying them. assert.deepEqual( nextRemoteDesktopUpdateStep(makeState({ status: "up-to-date" }), NO_ATTEMPTS, null), { action: "check" }, ); assert.deepEqual( nextRemoteDesktopUpdateStep( makeState({ status: "error", message: "feed unreachable" }), NO_ATTEMPTS, null, ), { action: "check" }, ); }); it("reports up-to-date and error states as terminal after this run's check", () => { const checked = { checks: 1, downloads: 0 }; assert.deepEqual( nextRemoteDesktopUpdateStep(makeState({ status: "up-to-date" }), checked, null), { action: "done", outcome: "up-to-date" }, ); assert.deepEqual( nextRemoteDesktopUpdateStep( makeState({ status: "error", message: "feed unreachable" }), checked, null, ), { action: "done", outcome: "failed", reason: "feed unreachable" }, ); assert.deepEqual(nextRemoteDesktopUpdateStep(makeState({ status: "error" }), checked, null), { action: "done", outcome: "failed", reason: "The desktop app update failed.", }); }); it("checks from idle until the attempt cap, then fails", () => { assert.deepEqual(nextRemoteDesktopUpdateStep(makeState(), NO_ATTEMPTS, null), { action: "check", }); assert.deepEqual( nextRemoteDesktopUpdateStep( makeState(), { checks: MAX_REMOTE_UPDATE_CHECKS, downloads: 0 }, null, ), { action: "done", outcome: "failed", reason: "The desktop app did not report an update result.", }, ); }); it("drops blank reasons so the wire report still encodes", () => { assert.equal(normalizeRemoteUpdateReason(undefined), undefined); assert.equal(normalizeRemoteUpdateReason(""), undefined); assert.equal(normalizeRemoteUpdateReason(" "), undefined); assert.equal(normalizeRemoteUpdateReason(" feed unreachable "), "feed unreachable"); }); });