// @vitest-environment jsdom import { afterEach, expect, it, vi } from 'vitest'; import type { TauriInvoke } from '../src/app/types'; import { beginDirectRunAnalytics } from '../src/services/clientAnalytics'; afterEach(() => vi.restoreAllMocks()); it('自动重试保持业务回合,只确认最后一次尝试且确认不等待桥接', async () => { const first = '11111111-1111-4111-8111-111111111111'; const last = '22222222-2222-4222-8222-222222222222'; vi.spyOn(crypto, 'randomUUID') .mockReturnValueOnce(first) .mockReturnValueOnce(last); const invoke = vi.fn(() => new Promise(() => {})); const analytics = beginDirectRunAnalytics(invoke as TauriInvoke, () => 1); const attempts: string[] = []; const operation = async () => { attempts.push(analytics.nextAttempt()!); if (attempts.length === 1) throw new Error('authentication-required'); return '完成'; }; let result: string; try { result = await operation().catch(() => operation()); expect(invoke).not.toHaveBeenCalled(); } finally { analytics.settle(); } expect(result).toBe('完成'); expect(attempts).toEqual([first, last]); expect(invoke).toHaveBeenCalledTimes(1); expect(invoke).toHaveBeenCalledWith('settle_direct_run_analytics', { attemptId: last, discard: false, }); analytics.settle(); expect(invoke).toHaveBeenCalledTimes(1); }); it('最终 UUID 失败不能回退确认第一次失败', () => { vi.spyOn(crypto, 'randomUUID') .mockReturnValueOnce('11111111-1111-4111-8111-111111111111') .mockImplementationOnce(() => { throw new Error('UUID unavailable'); }); const invoke = vi.fn(); const analytics = beginDirectRunAnalytics(invoke as TauriInvoke, () => 1); expect(analytics.nextAttempt()).toBeDefined(); expect(analytics.nextAttempt()).toBeUndefined(); analytics.settle(); expect(invoke).not.toHaveBeenCalled(); }); it('账号代次变化只丢弃候选,不提交成功失败结论', () => { let generation = 1; const invoke = vi.fn(async () => undefined); const analytics = beginDirectRunAnalytics( invoke as TauriInvoke, () => generation, ); const attemptId = analytics.nextAttempt(); generation = 2; analytics.settle(); expect(invoke).toHaveBeenCalledWith('settle_direct_run_analytics', { attemptId, discard: true, }); }); it.each(['sync', 'async'])( '确认桥接 %s 失败不会覆盖最终业务错误', async (mode) => { const invoke = vi.fn(() => { if (mode === 'sync') throw new Error('bridge'); return Promise.reject(new Error('bridge')); }); const analytics = beginDirectRunAnalytics(invoke as TauriInvoke, () => 1); const failure = new Error('最终执行失败'); const operation = async () => { try { analytics.nextAttempt(); throw failure; } finally { analytics.settle(); } }; await expect(operation()).rejects.toBe(failure); expect(invoke).toHaveBeenCalledTimes(1); }, );