1
0
Files
nex/frontend/src/__tests__/api/providers.test.ts
lanyuanxiaoyao feff97acbd feat: 前端适配后端新接口
适配后端统一模型 ID、协议字段、UUID 自动生成和结构化错误响应:

- 类型定义:Provider 新增 protocol 字段,Model 新增 unifiedId,CreateModelInput 移除 id
- API 客户端:提取结构化错误响应中的错误码
- 供应商管理:添加协议选择下拉框和表格列
- 模型管理:移除 ID 输入,显示统一模型 ID(只读)
- Hooks:错误码映射为友好中文消息
- 测试:所有组件测试通过,mock 数据适配新字段
- 文档:更新 README 说明协议字段和统一模型 ID
2026-04-21 20:49:37 +08:00

171 lines
4.9 KiB
TypeScript

import { describe, it, expect, beforeAll, afterEach, afterAll } from 'vitest';
import { setupServer } from 'msw/node';
import { http, HttpResponse } from 'msw';
import { listProviders, createProvider, updateProvider, deleteProvider } from '@/api/providers';
const mockProviders = [
{
id: 'prov-1',
name: 'OpenAI',
api_key: 'sk-xxx',
base_url: 'https://api.openai.com',
protocol: 'openai',
enabled: true,
created_at: '2025-01-01T00:00:00Z',
updated_at: '2025-01-01T00:00:00Z',
},
{
id: 'prov-2',
name: 'Anthropic',
api_key: 'sk-yyy',
base_url: 'https://api.anthropic.com',
protocol: 'anthropic',
enabled: false,
created_at: '2025-01-02T00:00:00Z',
updated_at: '2025-01-02T00:00:00Z',
},
];
describe('providers API', () => {
const server = setupServer();
beforeAll(() => server.listen({ onUnhandledRequest: 'bypass' }));
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
describe('listProviders', () => {
it('returns array of Provider objects with camelCase keys', async () => {
server.use(
http.get('http://localhost:3000/api/providers', () => {
return HttpResponse.json(mockProviders);
}),
);
const result = await listProviders();
expect(result).toEqual([
{
id: 'prov-1',
name: 'OpenAI',
apiKey: 'sk-xxx',
baseUrl: 'https://api.openai.com',
protocol: 'openai',
enabled: true,
createdAt: '2025-01-01T00:00:00Z',
updatedAt: '2025-01-01T00:00:00Z',
},
{
id: 'prov-2',
name: 'Anthropic',
apiKey: 'sk-yyy',
baseUrl: 'https://api.anthropic.com',
protocol: 'anthropic',
enabled: false,
createdAt: '2025-01-02T00:00:00Z',
updatedAt: '2025-01-02T00:00:00Z',
},
]);
});
});
describe('createProvider', () => {
it('sends POST with correct body and returns provider', async () => {
let receivedMethod: string | null = null;
let receivedBody: Record<string, unknown> | null = null;
server.use(
http.post('http://localhost:3000/api/providers', async ({ request }) => {
receivedMethod = request.method;
receivedBody = (await request.json()) as Record<string, unknown>;
return HttpResponse.json(mockProviders[0]);
}),
);
const input = {
id: 'prov-1',
name: 'OpenAI',
apiKey: 'sk-xxx',
baseUrl: 'https://api.openai.com',
enabled: true,
};
const result = await createProvider(input);
expect(receivedMethod).toBe('POST');
expect(receivedBody).toEqual({
id: 'prov-1',
name: 'OpenAI',
api_key: 'sk-xxx',
base_url: 'https://api.openai.com',
enabled: true,
});
expect(result).toEqual({
id: 'prov-1',
name: 'OpenAI',
apiKey: 'sk-xxx',
baseUrl: 'https://api.openai.com',
protocol: 'openai',
enabled: true,
createdAt: '2025-01-01T00:00:00Z',
updatedAt: '2025-01-01T00:00:00Z',
});
});
});
describe('updateProvider', () => {
it('sends PUT with correct body and returns provider', async () => {
let receivedMethod: string | null = null;
let receivedUrl: string | null = null;
let receivedBody: Record<string, unknown> | null = null;
server.use(
http.put('http://localhost:3000/api/providers/:id', async ({ request, params }) => {
receivedMethod = request.method;
receivedUrl = new URL(request.url).pathname;
receivedBody = (await request.json()) as Record<string, unknown>;
return HttpResponse.json({
...mockProviders[0],
name: 'Updated',
api_key: 'sk-updated',
});
}),
);
const result = await updateProvider('prov-1', {
name: 'Updated',
apiKey: 'sk-updated',
});
expect(receivedMethod).toBe('PUT');
expect(receivedUrl).toBe('/api/providers/prov-1');
expect(receivedBody).toEqual({
name: 'Updated',
api_key: 'sk-updated',
});
expect(result.name).toBe('Updated');
expect(result.apiKey).toBe('sk-updated');
});
});
describe('deleteProvider', () => {
it('sends DELETE and returns void', async () => {
let receivedMethod: string | null = null;
let receivedUrl: string | null = null;
server.use(
http.delete('http://localhost:3000/api/providers/:id', ({ request, params }) => {
receivedMethod = request.method;
receivedUrl = new URL(request.url).pathname;
return new HttpResponse(null, { status: 204 });
}),
);
const result = await deleteProvider('prov-1');
expect(receivedMethod).toBe('DELETE');
expect(receivedUrl).toBe('/api/providers/prov-1');
expect(result).toBeUndefined();
});
});
});