Files
makelore/tests/unit/app-updater.test.ts
2026-07-29 17:22:35 +08:00

189 lines
6.8 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
const electronMocks = vi.hoisted(() => ({
getVersion: vi.fn(() => '0.9.1'),
ipcHandle: vi.fn(),
}));
const updaterMocks = vi.hoisted(() => {
const listeners = new Map<string, Array<(...args: unknown[]) => void>>();
const autoUpdater = {
autoDownload: false,
autoInstallOnAppQuit: true,
logger: null as unknown,
channel: 'latest',
on: vi.fn((event: string, listener: (...args: unknown[]) => void) => {
const current = listeners.get(event) ?? [];
current.push(listener);
listeners.set(event, current);
return autoUpdater;
}),
setFeedURL: vi.fn(),
checkForUpdates: vi.fn(),
downloadUpdate: vi.fn(),
quitAndInstall: vi.fn(),
};
return { autoUpdater, listeners };
});
vi.mock('electron', () => ({
app: {
getVersion: electronMocks.getVersion,
isPackaged: true,
},
BrowserWindow: vi.fn(),
ipcMain: { handle: electronMocks.ipcHandle },
}));
vi.mock('electron-updater', () => ({ autoUpdater: updaterMocks.autoUpdater }));
vi.mock('@electron/utils/logger', () => ({
logger: {
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
debug: vi.fn(),
},
}));
import { AppUpdater, resolveUpdateFeed } from '@electron/main/updater';
afterEach(() => {
vi.restoreAllMocks();
});
describe('resolveUpdateFeed', () => {
it('routes stable Windows x64 updates through Works Square', () => {
expect(resolveUpdateFeed('0.9.2', 'win32', 'x64')).toEqual({
channel: 'latest',
url: 'https://square.nianxx.cn/api/app-updates/windows/x64',
});
});
it.each(['x64', 'arm64'])('routes stable macOS %s updates through Works Square', (architecture) => {
expect(resolveUpdateFeed('0.9.2', 'darwin', architecture)).toEqual({
channel: 'latest',
url: `https://square.nianxx.cn/api/app-updates/mac/${architecture}`,
});
});
it('keeps prerelease updates on the existing OSS channel feed', () => {
expect(resolveUpdateFeed('0.9.3-beta.1', 'darwin', 'arm64')).toEqual({
channel: 'beta',
url: 'https://oss.intelli-spectrum.com/beta',
});
});
it.each([
['linux', 'x64'],
['win32', 'arm64'],
['darwin', 'ia32'],
])('reports unsupported stable target %s/%s without throwing', (platform, architecture) => {
expect(resolveUpdateFeed('0.9.2', platform, architecture)).toMatchObject({
channel: 'latest',
error: expect.stringContaining('Unsupported update platform'),
});
});
});
describe('AppUpdater feed delegation', () => {
beforeEach(() => {
vi.clearAllMocks();
electronMocks.getVersion.mockReturnValue('0.9.1');
updaterMocks.autoUpdater.channel = 'latest';
updaterMocks.autoUpdater.checkForUpdates.mockResolvedValue(null);
});
it('delegates stable checks directly to electron-updater without a manifest preflight', async () => {
const platformDescriptor = Object.getOwnPropertyDescriptor(process, 'platform');
const architectureDescriptor = Object.getOwnPropertyDescriptor(process, 'arch');
Object.defineProperty(process, 'platform', { configurable: true, value: 'win32' });
Object.defineProperty(process, 'arch', { configurable: true, value: 'x64' });
const fetchSpy = vi.spyOn(globalThis, 'fetch');
updaterMocks.autoUpdater.checkForUpdates.mockResolvedValue({
isUpdateAvailable: true,
updateInfo: { version: '0.9.2' },
});
try {
const updater = new AppUpdater();
await expect(updater.checkForUpdates()).resolves.toMatchObject({ version: '0.9.2' });
expect(updaterMocks.autoUpdater.setFeedURL).toHaveBeenCalledWith({
provider: 'generic',
url: 'https://square.nianxx.cn/api/app-updates/windows/x64',
useMultipleRangeRequest: false,
});
expect(updaterMocks.autoUpdater.setFeedURL).toHaveBeenCalledTimes(1);
expect(updaterMocks.autoUpdater.checkForUpdates).toHaveBeenCalledTimes(1);
expect(fetchSpy).not.toHaveBeenCalled();
expect(updater.getStatus()).toMatchObject({
status: 'available',
info: { version: '0.9.2' },
});
} finally {
if (platformDescriptor) Object.defineProperty(process, 'platform', platformDescriptor);
if (architectureDescriptor) Object.defineProperty(process, 'arch', architectureDescriptor);
}
});
it('keeps prerelease channels on their existing electron-updater feed', async () => {
electronMocks.getVersion.mockReturnValue('0.9.2-beta.1');
updaterMocks.autoUpdater.checkForUpdates.mockResolvedValue({
isUpdateAvailable: false,
updateInfo: { version: '0.9.2-beta.1' },
});
const updater = new AppUpdater();
await updater.checkForUpdates();
expect(updaterMocks.autoUpdater.setFeedURL).toHaveBeenCalledWith({
provider: 'generic',
url: 'https://oss.intelli-spectrum.com/beta',
useMultipleRangeRequest: false,
});
expect(updaterMocks.autoUpdater.checkForUpdates).toHaveBeenCalledTimes(1);
expect(updater.getStatus()).toMatchObject({ status: 'not-available' });
});
it('defers unsupported stable target errors until an update check', async () => {
const platformDescriptor = Object.getOwnPropertyDescriptor(process, 'platform');
const architectureDescriptor = Object.getOwnPropertyDescriptor(process, 'arch');
Object.defineProperty(process, 'platform', { configurable: true, value: 'linux' });
Object.defineProperty(process, 'arch', { configurable: true, value: 'x64' });
try {
const updater = new AppUpdater();
const errorListener = vi.fn();
const rendererSend = vi.fn();
updater.on('error', errorListener);
updater.setMainWindow({
isDestroyed: () => false,
webContents: { send: rendererSend },
} as unknown as Parameters<AppUpdater['setMainWindow']>[0]);
expect(updater.getStatus()).toEqual({ status: 'idle' });
expect(updaterMocks.autoUpdater.setFeedURL).not.toHaveBeenCalled();
await expect(updater.checkForUpdates()).rejects.toThrow('Unsupported update platform: linux/x64');
expect(updater.getStatus()).toEqual({
status: 'error',
error: 'Unsupported update platform: linux/x64',
info: undefined,
progress: undefined,
});
expect(errorListener).toHaveBeenCalledWith(expect.objectContaining({
message: 'Unsupported update platform: linux/x64',
}));
expect(rendererSend.mock.calls.filter(([channel, status]) => (
channel === 'update:status-changed' && status?.status === 'error'
))).toHaveLength(1);
expect(updaterMocks.autoUpdater.checkForUpdates).not.toHaveBeenCalled();
} finally {
if (platformDescriptor) Object.defineProperty(process, 'platform', platformDescriptor);
if (architectureDescriptor) Object.defineProperty(process, 'arch', architectureDescriptor);
}
});
});