Files
EasyCLIProxyAPI/tests/coreHealthCheck.test.ts
lzt404 d29a1af69b feat: add plugin management and redesign dashboard and usage analytics
- Add plugin store, version selection, configuration, and OAuth login
- Support Muse (Meta) OAuth device authorization
- Redesign home dashboard and add batch model health checks
- Support per-credential provider editing, removal, and reordering
- Improve usage charts and track actual response models
- Preserve installed plugins and runtime data during portable rebuilds
- Update translations, documentation, and related tests
2026-10-04 17:31:52 +08:00

87 lines
3.9 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it } from 'bun:test';
import { clearMocks, mockIPC } from '@tauri-apps/api/mocks';
import { checkCoreModelHealth, checkCoreModelsHealth } from '../src/services/coreHealthCheck';
describe('CPA 内核模型健康检测', () => {
let originalWindow: PropertyDescriptor | undefined;
beforeEach(() => {
originalWindow = Object.getOwnPropertyDescriptor(globalThis, 'window');
Object.defineProperty(globalThis, 'window', { value: {}, writable: true, configurable: true });
});
afterEach(() => {
clearMocks();
if (originalWindow) Object.defineProperty(globalThis, 'window', originalWindow);
else Reflect.deleteProperty(globalThis, 'window');
});
it('只传入暴露的模型 ID 和超时,由原生命令选择内核地址和凭据', async () => {
mockIPC((command, args) => {
expect(command).toBe('core_health_probe');
expect(args).toEqual({ model: 'team/GPT-Alias', timeoutMs: 15_000 });
return { firstTokenLatencyMs: 120.5, responseLatencyMs: 180.1 };
});
expect(await checkCoreModelHealth('team/GPT-Alias')).toEqual({
model: 'team/GPT-Alias', status: 'healthy', success: true,
firstTokenLatencyMs: 121, responseLatencyMs: 180,
});
});
it('无首 Token 的非流式结果只显示响应时间', async () => {
mockIPC(() => ({ responseLatencyMs: 0 }));
expect(await checkCoreModelHealth('chat')).toMatchObject({
success: true, firstTokenLatencyMs: undefined, responseLatencyMs: 1,
});
});
it('保留失败原因并识别超时,失败模型不打断其他模型', async () => {
mockIPC((_command, args) => {
if (args?.model === 'timeout') throw new Error('request timed out');
if (args?.model === 'auth') throw 'HTTP 401: unauthorized';
return { firstTokenLatencyMs: 20, responseLatencyMs: 30 };
});
const results = await checkCoreModelsHealth([{ name: 'timeout' }, { name: 'auth' }, { name: 'working' }]);
expect(results[0]).toMatchObject({ model: 'timeout', success: false, status: 'failed', timedOut: true, error: 'request timed out' });
expect(results[1]).toMatchObject({ model: 'auth', success: false, timedOut: false, error: 'HTTP 401: unauthorized' });
expect(results[2]).toMatchObject({ model: 'working', success: true });
});
it('去除重复 ID,保留大小写及别名,并限制同时请求数为四个', async () => {
const requested: string[] = [];
let active = 0;
let maxActive = 0;
mockIPC(async (_command, args) => {
requested.push(String(args?.model));
active += 1;
maxActive = Math.max(maxActive, active);
await new Promise((resolve) => setTimeout(resolve, 5));
active -= 1;
return { responseLatencyMs: 5 };
});
const models = ['alias', 'ALIAS', 'team/model', 'one', 'two', 'three', 'four', 'alias', ''];
const results = await checkCoreModelsHealth(models.map((name) => ({ name })));
expect(maxActive).toBe(4);
expect(requested).toEqual(['alias', 'ALIAS', 'team/model', 'one', 'two', 'three', 'four']);
expect(results.map((result) => result.model)).toEqual(requested);
});
it('停止或离开页面后不再启动排队请求,也不交付迟到的结果', async () => {
const controller = new AbortController();
const started: string[] = [];
const delivered: string[] = [];
let release!: () => void;
const gate = new Promise<void>((resolve) => { release = resolve; });
mockIPC(async () => { await gate; return { responseLatencyMs: 10 }; });
const checking = checkCoreModelsHealth(
Array.from({ length: 20 }, (_, index) => ({ name: `model-${index}` })),
(result) => delivered.push(result.model), controller.signal,
(model) => started.push(model.name),
);
expect(started).toHaveLength(4);
controller.abort();
release();
expect(await checking).toEqual([]);
expect(started).toHaveLength(4);
expect(delivered).toEqual([]);
});
});