Let manual update checks bypass rollout
Automatic desktop update checks still respect rollout admission. Manual checks carry an explicit intent through the app and desktop updater service, and the up-to-date state now shows when the last check completed.
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import {
|
||||
createAppUpdateService,
|
||||
type AppUpdateRuntime,
|
||||
type AppUpdateRuntimeConfiguration,
|
||||
type RuntimeUpdateInfo,
|
||||
} from "./app-update-service";
|
||||
|
||||
class FakeAppUpdateRuntime implements AppUpdateRuntime {
|
||||
private checks: Array<{ isUpdateAvailable: boolean; updateInfo: RuntimeUpdateInfo } | null> = [];
|
||||
private gate: ((info: RuntimeUpdateInfo) => boolean | Promise<boolean>) | null = null;
|
||||
|
||||
configure(input: AppUpdateRuntimeConfiguration): void {
|
||||
this.gate = input.shouldAdmitUpdate;
|
||||
}
|
||||
|
||||
nextCheck(result: { isUpdateAvailable: boolean; updateInfo: RuntimeUpdateInfo } | null): void {
|
||||
this.checks.push(result);
|
||||
}
|
||||
|
||||
async checkForUpdates(): Promise<{
|
||||
isUpdateAvailable: boolean;
|
||||
updateInfo: RuntimeUpdateInfo;
|
||||
} | null> {
|
||||
const result = this.checks.shift() ?? null;
|
||||
if (!result || !this.gate) return result;
|
||||
const admitted = await this.gate(result.updateInfo);
|
||||
return { ...result, isUpdateAvailable: result.isUpdateAvailable && admitted };
|
||||
}
|
||||
|
||||
async downloadUpdate(): Promise<void> {}
|
||||
|
||||
quitAndInstall(): void {}
|
||||
}
|
||||
|
||||
function createService(input?: { now?: () => number; bucket?: () => Promise<number> }) {
|
||||
const runtime = new FakeAppUpdateRuntime();
|
||||
const service = createAppUpdateService({
|
||||
runtime,
|
||||
isPackaged: () => true,
|
||||
now: input?.now ?? (() => Date.parse("2026-04-28T12:00:00.000Z")),
|
||||
bucket: input?.bucket ?? (async () => 0.99),
|
||||
});
|
||||
return { runtime, service };
|
||||
}
|
||||
|
||||
const rolledOutUpdate = {
|
||||
version: "1.2.4",
|
||||
releaseDate: "2026-04-28T00:00:00.000Z",
|
||||
rolloutHours: 24,
|
||||
};
|
||||
|
||||
describe("app update service", () => {
|
||||
it("does not expose automatic stable updates before the user is admitted to rollout", async () => {
|
||||
const { runtime, service } = createService();
|
||||
runtime.nextCheck({ isUpdateAvailable: true, updateInfo: rolledOutUpdate });
|
||||
|
||||
const result = await service.checkForAppUpdate({
|
||||
currentVersion: "1.2.3",
|
||||
releaseChannel: "stable",
|
||||
intent: "automatic",
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
hasUpdate: false,
|
||||
readyToInstall: false,
|
||||
currentVersion: "1.2.3",
|
||||
latestVersion: "1.2.3",
|
||||
body: null,
|
||||
date: null,
|
||||
});
|
||||
});
|
||||
|
||||
it("exposes manual stable updates even before the user is admitted to rollout", async () => {
|
||||
const { runtime, service } = createService();
|
||||
runtime.nextCheck({ isUpdateAvailable: true, updateInfo: rolledOutUpdate });
|
||||
|
||||
const result = await service.checkForAppUpdate({
|
||||
currentVersion: "1.2.3",
|
||||
releaseChannel: "stable",
|
||||
intent: "manual",
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
hasUpdate: true,
|
||||
readyToInstall: false,
|
||||
currentVersion: "1.2.3",
|
||||
latestVersion: "1.2.4",
|
||||
body: null,
|
||||
date: "2026-04-28T00:00:00.000Z",
|
||||
});
|
||||
});
|
||||
|
||||
it("trusts the runtime availability decision before comparing versions", async () => {
|
||||
const { runtime, service } = createService({ bucket: async () => 0 });
|
||||
runtime.nextCheck({ isUpdateAvailable: false, updateInfo: rolledOutUpdate });
|
||||
|
||||
const result = await service.checkForAppUpdate({
|
||||
currentVersion: "1.2.3",
|
||||
releaseChannel: "stable",
|
||||
intent: "manual",
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
hasUpdate: false,
|
||||
readyToInstall: false,
|
||||
currentVersion: "1.2.3",
|
||||
latestVersion: "1.2.3",
|
||||
body: null,
|
||||
date: null,
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user