适配后端统一模型 ID、协议字段、UUID 自动生成和结构化错误响应: - 类型定义:Provider 新增 protocol 字段,Model 新增 unifiedId,CreateModelInput 移除 id - API 客户端:提取结构化错误响应中的错误码 - 供应商管理:添加协议选择下拉框和表格列 - 模型管理:移除 ID 输入,显示统一模型 ID(只读) - Hooks:错误码映射为友好中文消息 - 测试:所有组件测试通过,mock 数据适配新字段 - 文档:更新 README 说明协议字段和统一模型 ID
171 lines
4.9 KiB
TypeScript
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();
|
|
});
|
|
});
|
|
});
|