Files

711 lines
27 KiB
JavaScript

import { describe, it } from "node:test";
import assert from "node:assert/strict";
import { createHash } from "node:crypto";
import { mkdtempSync, readFileSync } from "node:fs";
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import {
preventPendingUpdaterInstall,
registerUpdaterIpc,
staleUpdaterStatePaths,
targetedStableUpdaterFeed,
} from "./updater.mjs";
import {
cacheVerifiedRecoveryArtifact,
compatibleRecoveryReleases,
parseRecoveryManifest,
readCachedRecoveryArtifact,
readRecoveryState,
recordHealthyVersion,
recoveryManifestName,
recoveryVersionMarkers,
selectRecoveryArtifact,
} from "./recovery.mjs";
const fakeApp = { getPath: (key) => (key === "home" ? "/Users/test" : `/Users/test/${key}`) };
// Unpackaged builds resolve their version from package.json, so release bumps
// must not require touching this test.
const desktopVersion = JSON.parse(
readFileSync(new URL("../package.json", import.meta.url), "utf8"),
).version;
let isolatedUpdaterImportId = 0;
function fakeUpdaterHarness({ version }) {
const listeners = new Map();
const calls = [];
const feeds = [];
const downloadFeeds = [];
const updater = {
autoDownload: true,
autoInstallOnAppQuit: false,
disableDifferentialDownload: false,
allowPrerelease: false,
allowDowngrade: false,
on: (name, fn) => listeners.set(name, fn),
setFeedURL: (feed) => feeds.push(feed),
checkForUpdates: async () => ({ updateInfo: { version } }),
downloadUpdate: async () => {
calls.push("download");
downloadFeeds.push(feeds.at(-1));
},
quitAndInstall: () => {
calls.push("quitAndInstall");
},
};
return { updater, listeners, calls, feeds, downloadFeeds };
}
async function registerFakeUpdaterIpc({ version }) {
const tempDir = mkdtempSync(path.join(os.tmpdir(), "openwork-updater-test-"));
const handlers = new Map();
const harness = fakeUpdaterHarness({ version });
isolatedUpdaterImportId += 1;
const updaterModuleUrl = new URL(
`./updater.mjs?updater-lifecycle=${isolatedUpdaterImportId}`,
import.meta.url,
);
const { registerUpdaterIpc: registerIsolatedUpdaterIpc } = await import(
updaterModuleUrl.href
);
registerIsolatedUpdaterIpc({
app: {
isPackaged: true,
getVersion: () => "0.17.0",
getPath: (key) => path.join(tempDir, key),
},
ipcMain: { handle: (name, handler) => handlers.set(name, handler) },
getMainWindow: () => null,
loadAutoUpdater: async () => ({ autoUpdater: harness.updater }),
});
return { tempDir, handlers, ...harness };
}
describe("staleUpdaterStatePaths", () => {
it("targets the ShipIt cache on macOS", { skip: process.platform !== "darwin" }, () => {
assert.deepEqual(staleUpdaterStatePaths(fakeApp), [
"/Users/test/Library/Caches/com.differentai.openwork.ShipIt",
]);
});
it("is a no-op off macOS", { skip: process.platform === "darwin" }, () => {
assert.deepEqual(staleUpdaterStatePaths(fakeApp), []);
});
});
describe("targetedStableUpdaterFeed", () => {
it("builds a fixed GitHub release feed from a strict stable version", () => {
assert.equal(
targetedStableUpdaterFeed("0.17.22", "0.17.23"),
"https://github.com/different-ai/openwork/releases/download/v0.17.23",
);
});
it("rejects arbitrary URLs and prerelease targets", () => {
assert.throws(
() => targetedStableUpdaterFeed("0.17.22", "https://example.test/latest.yml"),
/stable x\.y\.z format/,
);
assert.throws(
() => targetedStableUpdaterFeed("0.17.22", "0.17.23-alpha.1"),
/stable x\.y\.z format/,
);
});
it("rejects equal and older targets", () => {
assert.throws(
() => targetedStableUpdaterFeed("0.17.23", "0.17.23"),
/newer than the installed version/,
);
assert.throws(
() => targetedStableUpdaterFeed("0.17.23", "0.17.22"),
/newer than the installed version/,
);
});
it("allows only an explicit exact recovery downgrade", () => {
assert.equal(
targetedStableUpdaterFeed("0.17.23", "0.17.22", true),
"https://github.com/different-ai/openwork/releases/download/v0.17.22",
);
assert.throws(
() => targetedStableUpdaterFeed("0.17.23", "0.17.23", true),
/must differ/,
);
});
it("fails closed when the installed version cannot be compared", () => {
assert.throws(
() => targetedStableUpdaterFeed("unknown", "0.17.23"),
/could not be validated/,
);
});
});
describe("recovery metadata and candidates", () => {
it("marks the installed version current and the last healthy version previous after a failed update boot", () => {
assert.deepEqual(recoveryVersionMarkers("2.0.0", {
currentVersion: "1.9.0",
previousVersion: "1.8.0",
}), {
currentVersion: "2.0.0",
previousVersion: "1.9.0",
});
});
it("keeps the prior healthy version previous after the installed version boots successfully", () => {
assert.deepEqual(recoveryVersionMarkers("2.0.0", {
currentVersion: "2.0.0",
previousVersion: "1.9.0",
}), {
currentVersion: "2.0.0",
previousVersion: "1.9.0",
});
});
it("atomically preserves the immediately prior healthy version", async () => {
const userData = await mkdtemp(path.join(os.tmpdir(), "openwork-recovery-state-"));
const app = { getPath: () => userData };
try {
await recordHealthyVersion(app, "public", "1.2.3");
await recordHealthyVersion(app, "public", "1.2.4");
await recordHealthyVersion(app, "public", "1.2.4");
assert.deepEqual(await readRecoveryState(app, "public"), {
currentVersion: "1.2.4",
previousVersion: "1.2.3",
});
assert.match(await readFile(path.join(userData, "app-recovery.v1.json"), "utf8"), /"previousVersion": "1\.2\.3"/);
assert.deepEqual(await readRecoveryState(app, "cloud"), {
currentVersion: null,
previousVersion: null,
});
} finally {
await rm(userData, { recursive: true, force: true });
}
});
it("filters strict stable versions by minimum and fresh organization policy", async () => {
const releases = await compatibleRecoveryReleases({
versions: ["2.4.0", "2.3.1", "2.3.0-beta.1", "2.2.9", "https://invalid"],
currentVersion: "2.4.0",
previousVersion: "2.3.1",
minimumVersion: "2.3.0",
allowedVersions: ["2.4.0", "2.3.1"],
resolveArtifact: async (version) => ({ url: `verified:${version}` }),
});
assert.deepEqual(releases.map(({ version, marking }) => ({ version, marking })), [
{ version: "2.4.0", marking: "current" },
{ version: "2.3.1", marking: "previous" },
]);
});
it("accepts only the exact platform, architecture, distribution release artifact", () => {
const files = [
{ url: "openwork-mac-x64-1.2.3.dmg", sha512: "wrong-arch" },
{ url: "https://tampered.invalid/openwork-mac-arm64-1.2.3.dmg", sha512: "tampered" },
{ url: "openwork-mac-arm64-1.2.3.dmg", sha512: "verified" },
];
assert.deepEqual(selectRecoveryArtifact(files, {
version: "1.2.3",
platform: "darwin",
arch: "arm64",
distribution: "public",
}), {
version: "1.2.3",
platform: "darwin",
arch: "arm64",
distribution: "public",
url: "https://github.com/different-ai/openwork/releases/download/v1.2.3/openwork-mac-arm64-1.2.3.dmg",
sha512: "verified",
});
assert.equal(selectRecoveryArtifact(files, {
version: "1.2.3-beta.1",
platform: "darwin",
arch: "arm64",
distribution: "public",
}), null);
});
it("accepts each artifact flavor only for its matching distribution", () => {
const artifacts = {
public: "openwork-mac-arm64-1.2.3.dmg",
cloud: "openwork-cloud-mac-arm64-1.2.3.dmg",
enterprise: "openwork-enterprise-mac-arm64-1.2.3.dmg",
};
for (const [distribution, fileName] of Object.entries(artifacts)) {
const files = [{ url: fileName, sha512: `${distribution}-checksum` }];
assert.equal(selectRecoveryArtifact(files, {
version: "1.2.3", platform: "darwin", arch: "arm64", distribution,
})?.url, `https://github.com/different-ai/openwork/releases/download/v1.2.3/${fileName}`);
for (const otherDistribution of Object.keys(artifacts).filter((flavor) => flavor !== distribution)) {
assert.equal(selectRecoveryArtifact(files, {
version: "1.2.3", platform: "darwin", arch: "arm64", distribution: otherDistribution,
}), null);
}
}
});
it("parses representative builder manifests and selects published installer extensions", () => {
const files = parseRecoveryManifest(`version: 1.2.3
files:
- url: openwork-mac-arm64-1.2.3.dmg
sha512: mac-checksum
size: 100
- url: openwork-cloud-win-x64-1.2.3.exe
sha512: win-checksum
- url: openwork-enterprise-linux-x86_64-1.2.3.AppImage
sha512: linux-checksum
path: openwork-mac-arm64-1.2.3.zip
sha512: updater-zip-checksum
releaseDate: '2026-08-11T00:00:00.000Z'
`);
assert.equal(selectRecoveryArtifact(files, {
version: "1.2.3", platform: "darwin", arch: "arm64", distribution: "public",
})?.url.endsWith(".dmg"), true);
assert.equal(selectRecoveryArtifact(files, {
version: "1.2.3", platform: "win32", arch: "x64", distribution: "cloud",
})?.url.endsWith(".exe"), true);
assert.equal(selectRecoveryArtifact(files, {
version: "1.2.3", platform: "linux", arch: "x64", distribution: "enterprise",
})?.url.endsWith(".AppImage"), true);
assert.equal(recoveryManifestName("darwin", "arm64", "public"), "latest-mac.yml");
assert.equal(recoveryManifestName("win32", "x64", "cloud"), "cloud.yml");
assert.equal(recoveryManifestName("linux", "arm64", "enterprise"), "enterprise-linux-arm64.yml");
});
it("rejects a checksum mismatch without producing a cached installer", async () => {
const userData = await mkdtemp(path.join(os.tmpdir(), "openwork-recovery-checksum-"));
try {
await assert.rejects(
cacheVerifiedRecoveryArtifact({
app: { getPath: () => userData },
artifact: { url: "https://github.com/different-ai/openwork/releases/download/v1.2.3/openwork.dmg", sha512: "invalid" },
fetchArtifact: async () => new Response("tampered"),
}),
/checksum did not match/,
);
} finally {
await rm(userData, { recursive: true, force: true });
}
});
it("preserves a valid rollback cache across network and checksum replacement failures", async () => {
const userData = await mkdtemp(path.join(os.tmpdir(), "openwork-recovery-cache-preserve-"));
const app = { getPath: () => userData };
const bytes = Buffer.from("known-good");
const artifact = {
version: "1.2.3",
platform: "darwin",
arch: "arm64",
distribution: "public",
url: "https://github.com/different-ai/openwork/releases/download/v1.2.3/openwork-mac-arm64-1.2.3.dmg",
sha512: createHash("sha512").update(bytes).digest("base64"),
};
try {
await cacheVerifiedRecoveryArtifact({
app,
artifact,
fetchArtifact: async () => new Response(bytes),
});
await assert.rejects(cacheVerifiedRecoveryArtifact({
app,
artifact: { ...artifact, version: "1.2.4", url: artifact.url.replace("1.2.3", "1.2.4") },
fetchArtifact: async () => { throw new Error("offline"); },
}), /offline/);
await assert.rejects(cacheVerifiedRecoveryArtifact({
app,
artifact: { ...artifact, version: "1.2.4", url: artifact.url.replace("1.2.3", "1.2.4") },
fetchArtifact: async () => new Response("tampered"),
}), /checksum did not match/);
assert.equal((await readCachedRecoveryArtifact(app, {
platform: "darwin", arch: "arm64", distribution: "public",
}))?.artifact.version, "1.2.3");
} finally {
await rm(userData, { recursive: true, force: true });
}
});
it("rejects cached metadata with a modified URL or wrong installer filename", async () => {
const userData = await mkdtemp(path.join(os.tmpdir(), "openwork-recovery-cache-identity-"));
const app = { getPath: () => userData };
const bytes = Buffer.from("known-good-identity");
const artifact = {
version: "0.18.18",
platform: "darwin",
arch: "arm64",
distribution: "public",
url: "https://github.com/different-ai/openwork/releases/download/v0.18.18/openwork-mac-arm64-0.18.18.dmg",
sha512: createHash("sha512").update(bytes).digest("base64"),
};
const expected = { platform: "darwin", arch: "arm64", distribution: "public" };
try {
await cacheVerifiedRecoveryArtifact({ app, artifact, fetchArtifact: async () => new Response(bytes) });
const metadataPath = path.join(userData, "app-recovery-cache", "metadata.json");
const metadata = JSON.parse(await readFile(metadataPath, "utf8"));
await writeFile(metadataPath, JSON.stringify({ ...metadata, url: "https://tampered.invalid/OpenWork.dmg" }), "utf8");
assert.equal(await readCachedRecoveryArtifact(app, expected), null);
await writeFile(metadataPath, JSON.stringify({ ...metadata, fileName: "OpenWork.dmg" }), "utf8");
assert.equal(await readCachedRecoveryArtifact(app, expected), null);
} finally {
await rm(userData, { recursive: true, force: true });
}
});
it("discovers and opens a reverified cached healthy installer while offline", async () => {
const userData = await mkdtemp(path.join(os.tmpdir(), "openwork-recovery-offline-"));
const quitCalls = [];
const app = {
isPackaged: true,
getVersion: () => "2.0.0",
getPath: () => userData,
quit: () => quitCalls.push("quit"),
};
const bytes = Buffer.from("offline-known-good");
const artifact = {
version: "1.9.0",
platform: "darwin",
arch: "arm64",
distribution: "public",
url: "https://github.com/different-ai/openwork/releases/download/v1.9.0/openwork-mac-arm64-1.9.0.dmg",
sha512: createHash("sha512").update(bytes).digest("base64"),
};
const handlers = new Map();
const opened = [];
const networkCalls = [];
let openError = "installer blocked";
try {
await recordHealthyVersion(app, "public", "1.9.0");
await cacheVerifiedRecoveryArtifact({ app, artifact, fetchArtifact: async () => new Response(bytes) });
isolatedUpdaterImportId += 1;
const isolated = await import(`./updater.mjs?offline-recovery=${isolatedUpdaterImportId}`);
isolated.registerUpdaterIpc({
app,
ipcMain: { handle: (name, handler) => handlers.set(name, handler) },
getMainWindow: () => null,
electronNet: { fetch: async () => { networkCalls.push("fetch"); throw new Error("offline"); } },
shell: { openPath: async (filePath) => { opened.push(filePath); return openError; } },
platform: "darwin",
arch: "arm64",
distribution: "public",
});
const listed = await handlers.get("openwork:recovery:list")(null, {
versions: [], minimumVersion: "0.0.0",
});
assert.deepEqual(listed.releases, [{ id: "1.9.0", version: "1.9.0", marking: "previous" }]);
assert.deepEqual(networkCalls, []);
assert.deepEqual(await handlers.get("openwork:recovery:use")(null, "1.9.0"), {
ok: false,
reason: "installer blocked",
});
assert.deepEqual(quitCalls, []);
openError = "";
assert.deepEqual(await handlers.get("openwork:recovery:use")(null, "1.9.0"), {
ok: true,
action: "installer",
message: "The verified installer is open. Follow the operating system steps to finish.",
});
assert.equal(opened.length, 2);
} finally {
await rm(userData, { recursive: true, force: true });
}
});
it("rejects a candidate whose fresh manifest changes without any destructive action", async () => {
const userData = await mkdtemp(path.join(os.tmpdir(), "openwork-recovery-fresh-mismatch-"));
const handlers = new Map();
const destructiveCalls = [];
let candidateFetches = 0;
const manifest = (checksum) => `version: 1.9.0\nfiles:\n - url: openwork-mac-arm64-1.9.0.dmg\n sha512: ${checksum}\n`;
try {
isolatedUpdaterImportId += 1;
const isolated = await import(`./updater.mjs?fresh-mismatch=${isolatedUpdaterImportId}`);
isolated.registerUpdaterIpc({
app: {
isPackaged: true,
getVersion: () => "2.0.0",
getPath: () => userData,
quit: () => destructiveCalls.push("quit"),
},
ipcMain: { handle: (name, handler) => handlers.set(name, handler) },
getMainWindow: () => null,
electronNet: { fetch: async (url) => {
if (!url.includes("/v1.9.0/")) return new Response("missing", { status: 404 });
candidateFetches += 1;
return new Response(manifest(candidateFetches === 1 ? "first-checksum" : "changed-checksum"));
} },
shell: { openPath: async () => { destructiveCalls.push("open"); return ""; } },
platform: "darwin",
arch: "arm64",
distribution: "public",
});
const listed = await handlers.get("openwork:recovery:list")(null, {
versions: ["1.9.0"], minimumVersion: "0.0.0",
});
assert.deepEqual(listed.releases, [{ id: "1.9.0", version: "1.9.0", marking: null }]);
const result = await handlers.get("openwork:recovery:use")(null, "1.9.0");
assert.equal(result.ok, false);
assert.match(result.reason, /could not be verified/);
assert.deepEqual(destructiveCalls, []);
} finally {
await rm(userData, { recursive: true, force: true });
}
});
it("does not download, install, or quit for an unverified renderer selection", async () => {
const handlers = new Map();
const destructiveCalls = [];
registerUpdaterIpc({
app: {
isPackaged: true,
getVersion: () => "1.2.3",
getPath: () => os.tmpdir(),
quit: () => destructiveCalls.push("quit"),
},
ipcMain: { handle: (name, handler) => handlers.set(name, handler) },
getMainWindow: () => null,
electronNet: { fetch: async () => {
destructiveCalls.push("download");
return new Response("unexpected");
} },
shell: { openPath: async () => {
destructiveCalls.push("open");
return "";
} },
env: {
OPENWORK_EVAL_RECOVERY_CANDIDATES: JSON.stringify([
{ version: "1.2.2", verified: false, artifactUrl: "https://tampered.invalid/openwork.dmg" },
]),
},
});
await handlers.get("openwork:recovery:list")(null, {});
assert.equal((await handlers.get("openwork:recovery:use")(null, "1.2.2")).ok, false);
assert.deepEqual(destructiveCalls, []);
});
});
describe("installAndRestart", () => {
it("refuses to invoke the installer before an update is downloaded", async () => {
const handlers = new Map();
registerUpdaterIpc({
app: { isPackaged: false },
ipcMain: { handle: (name, handler) => handlers.set(name, handler) },
getMainWindow: () => null,
});
const install = handlers.get("openwork:updater:installAndRestart");
assert.equal(typeof install, "function");
assert.deepEqual(await install(), {
ok: false,
reason: "update-not-downloaded",
});
});
});
describe("downloaded update lifecycle", () => {
it("a transient failed check does not invalidate a downloaded update", async () => {
const { tempDir, handlers, updater, calls } = await registerFakeUpdaterIpc({
version: "0.17.1",
});
try {
const check = handlers.get("openwork:updater:check");
const download = handlers.get("openwork:updater:download");
const install = handlers.get("openwork:updater:installAndRestart");
assert.equal(typeof check, "function");
assert.equal(typeof download, "function");
assert.equal(typeof install, "function");
assert.equal((await check(null, "stable")).available, true);
assert.deepEqual(await download(), { ok: true });
updater.checkForUpdates = async () => {
throw new Error("network flake");
};
const failedCheck = await check(null, "stable");
assert.equal(failedCheck.available, false);
assert.match(failedCheck.reason, /network flake/);
assert.deepEqual(await install(), { ok: true });
assert.deepEqual(calls, ["download", "quitAndInstall"]);
} finally {
await rm(tempDir, { recursive: true, force: true });
}
});
it("an updater error event does not invalidate a downloaded update", async () => {
const { tempDir, handlers, listeners, calls } = await registerFakeUpdaterIpc({
version: "0.17.1",
});
try {
const check = handlers.get("openwork:updater:check");
const download = handlers.get("openwork:updater:download");
const install = handlers.get("openwork:updater:installAndRestart");
assert.equal(typeof check, "function");
assert.equal(typeof download, "function");
assert.equal(typeof install, "function");
assert.equal((await check(null, "stable")).available, true);
assert.deepEqual(await download(), { ok: true });
const onError = listeners.get("error");
assert.equal(typeof onError, "function");
onError(new Error("network flake"));
assert.deepEqual(await install(), { ok: true });
assert.deepEqual(calls, ["download", "quitAndInstall"]);
} finally {
await rm(tempDir, { recursive: true, force: true });
}
});
it("a successful check reporting no update still blocks install", async () => {
const { tempDir, handlers, updater } = await registerFakeUpdaterIpc({
version: "0.17.1",
});
try {
const check = handlers.get("openwork:updater:check");
const download = handlers.get("openwork:updater:download");
const install = handlers.get("openwork:updater:installAndRestart");
assert.equal(typeof check, "function");
assert.equal(typeof download, "function");
assert.equal(typeof install, "function");
assert.equal((await check(null, "stable")).available, true);
assert.deepEqual(await download(), { ok: true });
updater.checkForUpdates = async () => ({
updateInfo: { version: "0.17.0" },
});
const currentCheck = await check(null, "stable");
assert.equal(currentCheck.available, false);
assert.deepEqual(await install(), {
ok: false,
reason: "update-not-downloaded",
});
} finally {
await rm(tempDir, { recursive: true, force: true });
}
});
});
describe("release channel changes", () => {
it("prevents a previously downloaded update from installing on quit", () => {
const updater = { autoInstallOnAppQuit: true };
preventPendingUpdaterInstall(updater);
assert.equal(updater.autoInstallOnAppQuit, false);
});
it("pins enterprise builds to their parallel stable manifest channel", async () => {
const handlers = new Map();
const userData = await mkdtemp(path.join(os.tmpdir(), "openwork-enterprise-updater-"));
try {
registerUpdaterIpc({
app: {
isPackaged: false,
getVersion: () => desktopVersion,
getPath: () => userData,
},
ipcMain: { handle: (name, handler) => handlers.set(name, handler) },
getMainWindow: () => null,
manifestChannel: "enterprise",
});
const setChannel = handlers.get("openwork:updater:setChannel");
assert.equal(typeof setChannel, "function");
assert.deepEqual(await setChannel(null, "alpha"), {
channel: "stable",
feedUrl: "https://github.com/different-ai/openwork/releases/latest/download",
currentVersion: desktopVersion,
});
} finally {
await rm(userData, { recursive: true, force: true });
}
});
it("does not let a check overwrite the selected channel", {
skip: process.platform !== "darwin",
}, async () => {
const { tempDir, handlers } = await registerFakeUpdaterIpc({
version: "0.18.0",
});
try {
const check = handlers.get("openwork:updater:check");
const setChannel = handlers.get("openwork:updater:setChannel");
const getChannel = handlers.get("openwork:updater:getChannel");
assert.equal((await setChannel(null, "alpha")).channel, "alpha");
assert.equal((await check(null, "stable")).channel, "stable");
assert.equal((await getChannel()).channel, "alpha");
} finally {
await rm(tempDir, { recursive: true, force: true });
}
});
it("downloads from the channel used by the successful check", {
skip: process.platform !== "darwin",
}, async () => {
const { tempDir, handlers, downloadFeeds } = await registerFakeUpdaterIpc({
version: "0.18.0-alpha.1",
});
try {
const check = handlers.get("openwork:updater:check");
const download = handlers.get("openwork:updater:download");
assert.equal((await check(null, "alpha")).channel, "alpha");
assert.deepEqual(await download(), { ok: true });
assert.equal(
downloadFeeds.at(-1)?.url,
"https://github.com/different-ai/openwork/releases/download/alpha-macos-latest",
);
} finally {
await rm(tempDir, { recursive: true, force: true });
}
});
it("keeps Alpha selected when a Stable check is already in flight", {
skip: process.platform !== "darwin",
}, async () => {
const { tempDir, handlers, updater, feeds } = await registerFakeUpdaterIpc({
version: "0.18.0",
});
/** @type {{ finish: null | (() => void) }} */
const stableCheckControl = { finish: null };
const stableCheckStarted = new Promise((resolve) => {
updater.checkForUpdates = () => new Promise((finish) => {
stableCheckControl.finish = () => finish({ updateInfo: { version: "0.18.0" } });
resolve();
updater.checkForUpdates = async () => ({ updateInfo: { version: "0.18.0-alpha.1" } });
});
});
try {
const check = handlers.get("openwork:updater:check");
const setChannel = handlers.get("openwork:updater:setChannel");
const getChannel = handlers.get("openwork:updater:getChannel");
const stableCheck = check(null, "stable");
await stableCheckStarted;
const alphaSelection = setChannel(null, "alpha");
const alphaCheck = check(null, "alpha");
const finishStableCheck = stableCheckControl.finish;
if (!finishStableCheck) throw new Error("Stable update check did not start.");
finishStableCheck();
assert.equal((await stableCheck).channel, "stable");
assert.equal((await alphaSelection).channel, "alpha");
assert.equal((await alphaCheck).channel, "alpha");
assert.equal((await getChannel()).channel, "alpha");
assert.equal(
JSON.parse(await readFile(
path.join(tempDir, "userData", "electron-updater-channel.v1.json"),
"utf8",
)).channel,
"alpha",
);
assert.equal(
feeds.at(-1)?.url,
"https://github.com/different-ai/openwork/releases/download/alpha-macos-latest",
);
} finally {
await rm(tempDir, { recursive: true, force: true });
}
});
});