修复客户端更新诊断与错误展示

This commit is contained in:
2026-08-13 15:28:14 +08:00
parent a03043d048
commit f05b9d4cb5
5 changed files with 376 additions and 13 deletions

View File

@@ -5,6 +5,10 @@ const electronMocks = vi.hoisted(() => ({
ipcHandle: vi.fn(),
}));
const loggerMocks = vi.hoisted(() => ({
error: vi.fn(),
}));
const updaterMocks = vi.hoisted(() => {
const listeners = new Map<string, Array<(...args: unknown[]) => void>>();
const autoUpdater = {
@@ -41,7 +45,7 @@ vi.mock('@electron/utils/logger', () => ({
logger: {
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
error: loggerMocks.error,
debug: vi.fn(),
},
}));
@@ -127,6 +131,97 @@ describe('AppUpdater feed delegation', () => {
}
});
it('reports a concise actionable error when the stable feed has no latest manifest', async () => {
const rawError = new Error(
'Cannot find channel "latest.yml" update info: HttpError: 404\n' +
'GET https://square.nianxx.cn/api/app-updates/windows/x64/latest.yml',
);
updaterMocks.autoUpdater.checkForUpdates.mockRejectedValue(rawError);
const updater = new AppUpdater();
const rendererSend = vi.fn();
updater.setMainWindow({
isDestroyed: () => false,
webContents: { send: rendererSend },
} as unknown as Parameters<AppUpdater['setMainWindow']>[0]);
await expect(updater.checkForUpdates()).rejects.toBe(rawError);
expect(updater.getStatus()).toMatchObject({
status: 'error',
error: '当前平台的正式更新包尚未发布,请稍后重试或从官网下载最新版',
});
expect(rendererSend.mock.calls.filter(([channel, status]) => (
channel === 'update:status-changed' && status?.status === 'error'
))).toHaveLength(1);
});
it('reports one error status when electron-updater emits and rejects the same check error', async () => {
const rawError = new Error(
'Cannot find channel "latest.yml" update info: HttpError: 404\n' +
'GET https://square.nianxx.cn/api/app-updates/windows/x64/latest.yml',
);
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]);
let rejectSharedCheck!: (error: Error) => void;
const sharedCheck = new Promise<never>((_resolve, reject) => {
rejectSharedCheck = reject;
});
updaterMocks.autoUpdater.checkForUpdates.mockImplementationOnce(() => sharedCheck);
updaterMocks.autoUpdater.checkForUpdates.mockImplementationOnce(() => sharedCheck);
updaterMocks.autoUpdater.checkForUpdates.mockImplementationOnce(async () => {
const updaterErrorListeners = updaterMocks.listeners.get('error') ?? [];
updaterErrorListeners.at(-1)?.(rawError);
throw rawError;
});
const firstCheck = updater.checkForUpdates();
const concurrentCheck = updater.checkForUpdates();
const updaterErrorListeners = updaterMocks.listeners.get('error') ?? [];
updaterErrorListeners.at(-1)?.(rawError);
rejectSharedCheck(rawError);
await expect(Promise.allSettled([firstCheck, concurrentCheck])).resolves.toEqual([
{ status: 'rejected', reason: rawError },
{ status: 'rejected', reason: rawError },
]);
expect(rendererSend.mock.calls.filter(([channel, status]) => (
channel === 'update:status-changed' && status?.status === 'error'
))).toHaveLength(1);
expect(errorListener).toHaveBeenCalledOnce();
expect(errorListener).toHaveBeenCalledWith(rawError);
expect(loggerMocks.error).toHaveBeenCalledWith('[Updater] Check for updates failed:', rawError);
await expect(updater.checkForUpdates()).rejects.toBe(rawError);
expect(rendererSend.mock.calls.filter(([channel, status]) => (
channel === 'update:status-changed' && status?.status === 'error'
))).toHaveLength(2);
expect(errorListener).toHaveBeenCalledTimes(2);
});
it('reports the same actionable error when the macOS stable feed has no latest manifest', async () => {
const rawError = new Error(
'Cannot find channel "latest-mac.yml" update info: HttpError: 404\n' +
'GET https://square.nianxx.cn/api/app-updates/mac/arm64/latest-mac.yml',
);
updaterMocks.autoUpdater.checkForUpdates.mockRejectedValue(rawError);
const updater = new AppUpdater();
await expect(updater.checkForUpdates()).rejects.toBe(rawError);
expect(updater.getStatus()).toMatchObject({
status: 'error',
error: '当前平台的正式更新包尚未发布,请稍后重试或从官网下载最新版',
});
});
it('keeps prerelease channels on their existing electron-updater feed', async () => {
electronMocks.getVersion.mockReturnValue('0.9.2-beta.1');
updaterMocks.autoUpdater.checkForUpdates.mockResolvedValue({