Files
aryx/tests/main/autoUpdater.test.ts
T
David KayaandCopilot 535adc64be fix: enable background update checks in dev builds
Remove the isPackaged guard from AutoUpdateService.start() so that
background update checks run in both packaged and dev builds.
forceDevUpdateConfig already handles dev-mode config correctly via
dev-app-update.yml — the start() gate was preventing the 10s initial
check and 4h periodic checks from ever being scheduled in dev.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-30 17:18:02 +02:00

196 lines
5.7 KiB
TypeScript

import { EventEmitter } from 'node:events';
import { describe, expect, test } from 'bun:test';
import { AutoUpdateService, type AutoUpdateScheduler } from '@main/services/autoUpdater';
import type { UpdateStatus } from '@shared/contracts/ipc';
class FakeUpdater extends EventEmitter {
autoDownload = false;
autoInstallOnAppQuit = true;
forceDevUpdateConfig = false;
checkForUpdatesCalls = 0;
quitAndInstallCalls = 0;
async checkForUpdates(): Promise<void> {
this.checkForUpdatesCalls += 1;
}
quitAndInstall(): void {
this.quitAndInstallCalls += 1;
}
}
class FakeScheduler implements AutoUpdateScheduler {
readonly timeouts: Array<{ callback: () => void; delayMs: number }> = [];
readonly intervals: Array<{ callback: () => void; delayMs: number }> = [];
setTimeout(callback: () => void, delayMs: number): number {
this.timeouts.push({ callback, delayMs });
return this.timeouts.length - 1;
}
clearTimeout(handle: unknown): void {
const index = Number(handle);
if (Number.isInteger(index) && index >= 0) {
this.timeouts.splice(index, 1);
}
}
setInterval(callback: () => void, delayMs: number): number {
this.intervals.push({ callback, delayMs });
return this.intervals.length - 1;
}
clearInterval(handle: unknown): void {
const index = Number(handle);
if (Number.isInteger(index) && index >= 0) {
this.intervals.splice(index, 1);
}
}
async runTimeout(index = 0): Promise<void> {
await Promise.resolve(this.timeouts[index]?.callback());
}
async runInterval(index = 0): Promise<void> {
await Promise.resolve(this.intervals[index]?.callback());
}
}
describe('AutoUpdateService', () => {
test('schedules checks for unpackaged apps using dev update config', async () => {
const updater = new FakeUpdater();
const scheduler = new FakeScheduler();
const service = new AutoUpdateService({ isPackaged: false, scheduler, updater });
service.start();
expect(updater.forceDevUpdateConfig).toBe(true);
expect(scheduler.timeouts).toEqual([{ callback: expect.any(Function), delayMs: 10_000 }]);
expect(scheduler.intervals).toEqual([{ callback: expect.any(Function), delayMs: 4 * 60 * 60 * 1000 }]);
await scheduler.runTimeout();
await Promise.resolve();
expect(updater.checkForUpdatesCalls).toBe(1);
});
test('configures auto download and schedules startup and periodic checks', async () => {
const updater = new FakeUpdater();
const scheduler = new FakeScheduler();
const service = new AutoUpdateService({ isPackaged: true, scheduler, updater });
service.start();
expect(updater.autoDownload).toBe(true);
expect(updater.autoInstallOnAppQuit).toBe(false);
expect(scheduler.timeouts).toEqual([{ callback: expect.any(Function), delayMs: 10_000 }]);
expect(scheduler.intervals).toEqual([{ callback: expect.any(Function), delayMs: 4 * 60 * 60 * 1000 }]);
await scheduler.runTimeout();
await Promise.resolve();
await scheduler.runInterval();
await Promise.resolve();
expect(updater.checkForUpdatesCalls).toBe(2);
});
test('maps updater events into renderer-facing status snapshots', () => {
const updater = new FakeUpdater();
const service = new AutoUpdateService({ isPackaged: true, updater });
const statuses: UpdateStatus[] = [];
service.onStatus((status) => statuses.push(status));
updater.emit('checking-for-update');
updater.emit('update-available', {
version: '1.2.0',
releaseDate: '2026-03-29T00:00:00.000Z',
releaseNotes: 'Important fixes.',
});
updater.emit('download-progress', {
bytesPerSecond: 512,
percent: 25,
total: 400,
transferred: 100,
});
updater.emit('update-downloaded', {
version: '1.2.0',
releaseDate: '2026-03-29T00:00:00.000Z',
releaseNotes: 'Important fixes.',
});
expect(statuses).toEqual([
{ state: 'checking' },
{
state: 'available',
version: '1.2.0',
releaseDate: '2026-03-29T00:00:00.000Z',
releaseNotes: 'Important fixes.',
},
{
state: 'downloading',
version: '1.2.0',
releaseDate: '2026-03-29T00:00:00.000Z',
releaseNotes: 'Important fixes.',
downloadProgress: {
bytesPerSecond: 512,
percent: 25,
total: 400,
transferred: 100,
},
},
{
state: 'downloaded',
version: '1.2.0',
releaseDate: '2026-03-29T00:00:00.000Z',
releaseNotes: 'Important fixes.',
},
]);
});
test('transitions to up-to-date when no update is available', () => {
const updater = new FakeUpdater();
const service = new AutoUpdateService({ isPackaged: true, updater });
const statuses: UpdateStatus[] = [];
service.onStatus((status) => statuses.push(status));
updater.emit('checking-for-update');
updater.emit('update-not-available', {});
expect(statuses).toEqual([
{ state: 'checking' },
{ state: 'up-to-date' },
]);
expect(service.getStatus()).toEqual({ state: 'up-to-date' });
});
test('reports updater errors and only installs once an update is downloaded', () => {
const updater = new FakeUpdater();
const service = new AutoUpdateService({ isPackaged: true, updater });
service.installUpdate();
expect(updater.quitAndInstallCalls).toBe(0);
updater.emit('error', new Error('network down'));
expect(service.getStatus()).toEqual({
state: 'error',
error: 'network down',
});
updater.emit('update-downloaded', {
version: '1.2.1',
releaseDate: '2026-03-29T00:00:00.000Z',
releaseNotes: 'Patch release.',
});
service.installUpdate();
expect(updater.quitAndInstallCalls).toBe(1);
});
});