f756b52d8c
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Failing after 2m23s
Project CI / Backend tests (pull_request) Failing after 19s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Failing after 1m48s
Project CI / AI game creator shell Rust smoke (pull_request) Failing after 2m14s
Project CI / Repository checks (pull_request) Failing after 36s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 1m49s
Project CI / Frontend tests (pull_request) Successful in 4m7s
Project CI / AI game creator shell web tests (pull_request) Successful in 3m41s
Project CI / Native shell tests (pull_request) Failing after 5m46s
# Conflicts: # docs/project-memory/shared-memory/pitfalls.md
209 lines
7.7 KiB
TypeScript
209 lines
7.7 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,
|
|
checkForAppUpdateResult,
|
|
resetAppUpdateCheckForTests,
|
|
} = await import('../src/services/appUpdate');
|
|
|
|
// 启动期提示用旧投影:失败仍然静默,不阻塞启动、不弹更新。
|
|
await expect(checkForAppUpdate()).resolves.toBeNull();
|
|
// 手动检查必须拿到失败原因,不能显示成「当前已是最新版本」。
|
|
await expect(checkForAppUpdateResult({ force: true })).resolves.toEqual({
|
|
status: 'failed',
|
|
message: 'updater: manifest 404',
|
|
});
|
|
resetAppUpdateCheckForTests();
|
|
});
|
|
|
|
it('结果型入口区分「有更新」与「已是最新」', async () => {
|
|
vi.stubEnv('VITE_AGC_ENABLE_APP_UPDATE_CHECK', '1');
|
|
vi.resetModules();
|
|
checkMock.mockResolvedValueOnce(null as never);
|
|
const { checkForAppUpdateResult, resetAppUpdateCheckForTests } =
|
|
await import('../src/services/appUpdate');
|
|
|
|
await expect(checkForAppUpdateResult()).resolves.toEqual({
|
|
status: 'current',
|
|
});
|
|
|
|
checkMock.mockResolvedValueOnce(fakeUpdate() as never);
|
|
await expect(checkForAppUpdateResult({ force: true })).resolves.toEqual({
|
|
status: 'available',
|
|
update: {
|
|
version: '99.0.0',
|
|
currentVersion: '0.1.47',
|
|
releaseNotes: '修复与改进',
|
|
},
|
|
});
|
|
resetAppUpdateCheckForTests();
|
|
});
|
|
|
|
it('开关关闭时结果型入口报 disabled,不冒充已是最新', async () => {
|
|
vi.stubEnv('VITE_AGC_ENABLE_APP_UPDATE_CHECK', '0');
|
|
vi.resetModules();
|
|
const { checkForAppUpdateResult, resetAppUpdateCheckForTests } =
|
|
await import('../src/services/appUpdate');
|
|
|
|
await expect(checkForAppUpdateResult()).resolves.toEqual({
|
|
status: 'disabled',
|
|
});
|
|
expect(checkMock).not.toHaveBeenCalled();
|
|
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 invoke = stubTauriWindow();
|
|
const update = fakeUpdate();
|
|
// 官方插件在原生侧校验签名;校验不过时它拒绝安装并抛错。客户端不得把这次失败
|
|
// 当成安装成功(更不能重启进程把坏包"坐实"),同时要保留待装更新让重试仍走同一条链路。
|
|
update.downloadAndInstall.mockRejectedValueOnce(
|
|
new Error('signature verification failed: invalid minisign signature'),
|
|
);
|
|
checkMock.mockResolvedValue(update as never);
|
|
const { checkForAppUpdate, installAppUpdate, resetAppUpdateCheckForTests } =
|
|
await import('../src/services/appUpdate');
|
|
|
|
await checkForAppUpdate();
|
|
await expect(installAppUpdate()).rejects.toThrow(
|
|
'signature verification failed',
|
|
);
|
|
expect(invoke).not.toHaveBeenCalledWith('restart_agc_app');
|
|
|
|
// 待装更新仍在:重试会再次走安装链路,成功后才会重启。
|
|
await installAppUpdate();
|
|
expect(update.downloadAndInstall).toHaveBeenCalledTimes(2);
|
|
expect(invoke).toHaveBeenCalledWith('restart_agc_app');
|
|
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();
|
|
});
|
|
});
|