/** * UpdateService 测试(v0.6.4 P4-2) * 锁定:语义化版本比较表、feed 拉取/解析/状态映射、禁用态。 */ import { describe, it, expect, vi, afterEach } from 'vitest'; vi.mock('electron-log', () => ({ default: { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() }, })); import { UpdateService, isNewerVersion } from '../update.service'; describe('isNewerVersion 语义化比较', () => { it.each([ ['0.7.0', '0.6.4', true], ['1.0.0', '0.9.9', true], ['0.6.10', '0.6.9', true], ['0.6.4', '0.6.4', false], ['0.6.3', '0.6.4', false], ['v0.7.0', '0.6.4', true], // 容忍 v 前缀 ['not-a-version', '0.6.4', false], // 非法输入宁可不提示 ['', '0.6.4', false], // 预发布:同号正式版 > 预发布;候选预发布不提示升级 ['0.6.5-beta', '0.6.4', true], ['0.6.4-beta', '0.6.4', false], ['0.6.4', '0.6.4-beta', true], ])('%s vs %s → %s', (candidate, current, expected) => { expect(isNewerVersion(candidate, current)).toBe(expected); }); }); describe('UpdateService.check 状态映射', () => { afterEach(() => { vi.unstubAllGlobals(); vi.restoreAllMocks(); }); it('未配置 feed → disabled(不发起任何请求)', async () => { const fetchSpy = vi.fn(); vi.stubGlobal('fetch', fetchSpy); const svc = new UpdateService(() => '0.6.4', () => null); const result = await svc.check(); expect(result.status).toBe('disabled'); expect(fetchSpy).not.toHaveBeenCalled(); }); it('feed 声明更新版本 → available 并透传下载直链与 notes', async () => { vi.stubGlobal( 'fetch', vi.fn().mockResolvedValue( new Response(JSON.stringify({ version: '0.7.0', url: 'https://dl.example/setup.exe', notes: '- fix x' }), { status: 200 }), ), ); const svc = new UpdateService(() => '0.6.4', () => 'https://update.example/feed.json'); const result = await svc.check(); expect(result.status).toBe('available'); if (result.status === 'available') { expect(result.latestVersion).toBe('0.7.0'); expect(result.downloadUrl).toBe('https://dl.example/setup.exe'); expect(result.notes).toBe('- fix x'); } }); it('同版本 / 更旧 → up-to-date', async () => { vi.stubGlobal( 'fetch', vi.fn().mockResolvedValue(new Response(JSON.stringify({ version: '0.6.3' }), { status: 200 })), ); const svc = new UpdateService(() => '0.6.4', () => 'https://u.example/f.json'); const result = await svc.check(); expect(result.status).toBe('up-to-date'); if (result.status === 'up-to-date') expect(result.latestVersion).toBe('0.6.3'); }); it('HTTP 失败与非法 JSON → error', async () => { vi.stubGlobal('fetch', vi.fn().mockResolvedValue(new Response('oops', { status: 500 }))); const svc = new UpdateService(() => '0.6.4', () => 'https://u.example/f.json'); expect((await svc.check()).status).toBe('error'); vi.stubGlobal('fetch', vi.fn().mockResolvedValue(new Response('', { status: 200 }))); expect((await svc.check()).status).toBe('error'); }); it('下载 URL 非 http(s) 时被剔除(防协议劫持)', async () => { vi.stubGlobal( 'fetch', vi.fn().mockResolvedValue( new Response(JSON.stringify({ version: '9.9.9', url: 'file:///C:/evil.exe' }), { status: 200 }), ), ); const svc = new UpdateService(() => '0.6.4', () => 'https://u.example/f.json'); const result = await svc.check(); expect(result.status).toBe('available'); if (result.status === 'available') expect(result.downloadUrl).toBeUndefined(); }); });