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 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[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); } }); });