Files
kdletters a5fd25f10a 客户端更新切换到官方更新插件并按渠道分发
- 客户端接入 tauri-plugin-updater:原生侧注册插件,删除自研更新下载命令、下载进度事件与安装器启动逻辑
- 客户端更新服务与更新提示改走官方插件接口,删除自研清单解析、版本比较与下载实现
- 更新能力只授予客户端主窗口,移除只为自研清单放行的 OSS 白名单与 CSP 连接项
- 新增构建期更新检查开关:开发态默认关闭,agc 启动不请求更新清单、不显示更新入口
- 发布脚本按渠道生成官方更新插件清单与签名,universal macOS 产物同时挂两个平台键,缺签名失败关闭
- 发布脚本按渠道上传安装包、签名与渠道清单,并为 dev-win 生成旧协议 sha256 迁移清单
- 构建期按渠道注入更新端点配置,渠道与目标平台不匹配时发布失败关闭
- Jenkins 流水线新增渠道参数与签名凭据注入,归档补充签名与迁移清单
- 新增发布上传 dry-run 开关,只打印 ossutil 命令且不回显凭据
- 更新技术方案与开发运维文档,登记 macOS 渠道落地待办
2026-09-17 17:56:53 +08:00

138 lines
4.8 KiB
TypeScript

import { check } from '@tauri-apps/plugin-updater';
import { afterEach, describe, expect, it, vi } from 'vitest';
vi.mock('@tauri-apps/plugin-updater', () => ({ check: vi.fn() }));
const checkMock = vi.mocked(check);
type FakeDownloadEvent =
| { event: 'Started'; data: { contentLength?: number } }
| { event: 'Progress'; data: { chunkLength: number } }
| { event: 'Finished' };
function fakeUpdate() {
return {
version: '99.0.0',
currentVersion: '0.1.47',
body: '修复与改进',
downloadAndInstall: vi.fn(
async (onEvent: (event: FakeDownloadEvent) => void) => {
onEvent({ event: 'Started', data: { contentLength: 100 } });
onEvent({ event: 'Progress', data: { chunkLength: 40 } });
onEvent({ event: 'Progress', data: { chunkLength: 60 } });
onEvent({ event: 'Finished' });
},
),
};
}
function stubTauriWindow() {
const invoke = vi.fn(async () => undefined);
vi.stubGlobal('window', { __TAURI__: { core: { invoke } } });
return invoke;
}
afterEach(() => {
vi.unstubAllEnvs();
vi.unstubAllGlobals();
vi.resetModules();
checkMock.mockReset();
});
describe('AGC 客户端更新', () => {
it('开发态开关关闭时不请求清单', async () => {
vi.stubEnv('VITE_AGC_ENABLE_APP_UPDATE_CHECK', '0');
vi.resetModules();
const { checkForAppUpdate, resetAppUpdateCheckForTests } = await import(
'../src/services/appUpdate'
);
await expect(checkForAppUpdate()).resolves.toBeNull();
expect(checkMock).not.toHaveBeenCalled();
resetAppUpdateCheckForTests();
});
it('开关打开时把插件返回的更新映射给界面,且同一生命周期只查一次', async () => {
vi.stubEnv('VITE_AGC_ENABLE_APP_UPDATE_CHECK', '1');
vi.resetModules();
checkMock.mockResolvedValue(fakeUpdate() as never);
const { checkForAppUpdate, resetAppUpdateCheckForTests } = await import(
'../src/services/appUpdate'
);
await expect(checkForAppUpdate()).resolves.toEqual({
version: '99.0.0',
currentVersion: '0.1.47',
releaseNotes: '修复与改进',
});
await checkForAppUpdate();
expect(checkMock).toHaveBeenCalledTimes(1);
resetAppUpdateCheckForTests();
});
it('清单缺失或网络失败时静默按无更新收口', async () => {
vi.stubEnv('VITE_AGC_ENABLE_APP_UPDATE_CHECK', '1');
vi.resetModules();
checkMock.mockRejectedValue(new Error('updater: manifest 404'));
const { checkForAppUpdate, resetAppUpdateCheckForTests } = await import(
'../src/services/appUpdate'
);
await expect(checkForAppUpdate()).resolves.toBeNull();
resetAppUpdateCheckForTests();
});
it('安装时按下载事件上报进度并在完成后重启进程', async () => {
vi.stubEnv('VITE_AGC_ENABLE_APP_UPDATE_CHECK', '1');
vi.resetModules();
const invoke = stubTauriWindow();
const update = fakeUpdate();
checkMock.mockResolvedValue(update as never);
const { checkForAppUpdate, installAppUpdate, resetAppUpdateCheckForTests } =
await import('../src/services/appUpdate');
await checkForAppUpdate();
const progress: Array<{ downloadedBytes: number; totalBytes?: number }> =
[];
await installAppUpdate((value) => progress.push(value));
expect(update.downloadAndInstall).toHaveBeenCalledTimes(1);
expect(progress).toEqual([
{ downloadedBytes: 0, totalBytes: 100 },
{ downloadedBytes: 40, totalBytes: 100 },
{ downloadedBytes: 100, totalBytes: 100 },
{ downloadedBytes: 100, totalBytes: 100 },
]);
expect(invoke).toHaveBeenCalledWith('restart_agc_app');
resetAppUpdateCheckForTests();
});
it('没有待安装更新时安装请求失败关闭', async () => {
vi.stubEnv('VITE_AGC_ENABLE_APP_UPDATE_CHECK', '1');
vi.resetModules();
const { installAppUpdate, resetAppUpdateCheckForTests } = await import(
'../src/services/appUpdate'
);
await expect(installAppUpdate()).rejects.toThrow('没有可安装的更新');
resetAppUpdateCheckForTests();
});
it('下载失败后仍可重试安装', async () => {
vi.stubEnv('VITE_AGC_ENABLE_APP_UPDATE_CHECK', '1');
vi.resetModules();
const update = fakeUpdate();
update.downloadAndInstall.mockRejectedValue(new Error('下载更新失败'));
checkMock.mockResolvedValue(update as never);
const { checkForAppUpdate, installAppUpdate, resetAppUpdateCheckForTests } =
await import('../src/services/appUpdate');
await checkForAppUpdate();
await expect(installAppUpdate()).rejects.toThrow('下载更新失败');
// 重试仍能拿到待装更新,而不是报“没有可安装的更新”。
await expect(installAppUpdate()).rejects.toThrow('下载更新失败');
expect(update.downloadAndInstall).toHaveBeenCalledTimes(2);
resetAppUpdateCheckForTests();
});
});