- 新增 providers/models 数据库表、迁移和数据访问层 - 新增 15 个后端 API 路由(供应商/模型 CRUD + 连通性测试) - 新增 AI 服务层(registry.ts: buildProviderRegistry + testProviderConnection) - 新增前端模型管理页面(Tabs: 供应商/模型,含表格、表单、工具栏) - 新增前端 hooks(use-providers, use-models) - 新增共享类型和 MODEL_CAPABILITIES 常量 - 新增 10 个测试文件(66 个测试用例,4 个因 bun test ESM 兼容问题待修复) - 更新开发文档(architecture, backend, frontend) - 附带 apply-review 修复:统一错误响应、提取共享常量、清理重复测试 注意:registry.test.ts 中 4 个测试因 bun test 无法解析 createProviderRegistry ESM 导出而失败,详情见 context.md
90 lines
3.0 KiB
TypeScript
90 lines
3.0 KiB
TypeScript
import { fireEvent, screen, waitFor } from "@testing-library/react";
|
|
import { describe, expect, mock, test } from "bun:test";
|
|
import { createElement } from "react";
|
|
|
|
import type { Provider } from "../../../src/shared/api";
|
|
|
|
import { ProviderTable } from "../../../src/web/pages/models/components/ProviderTable";
|
|
import { renderWithProviders } from "../test-utils";
|
|
|
|
const ENABLED_PROVIDER: Provider = {
|
|
apiKey: "sk-test",
|
|
baseUrl: "https://api.openai.com/v1",
|
|
createdAt: "2024-01-01T00:00:00.000Z",
|
|
enabled: true,
|
|
id: "pv1",
|
|
name: "OpenAI",
|
|
type: "openai",
|
|
updatedAt: "2024-01-01T00:00:00.000Z",
|
|
};
|
|
|
|
const DISABLED_PROVIDER: Provider = {
|
|
apiKey: "sk-off",
|
|
baseUrl: "https://api.deepseek.com/v1",
|
|
createdAt: "2024-01-01T00:00:00.000Z",
|
|
enabled: false,
|
|
id: "pv2",
|
|
name: "DeepSeek",
|
|
type: "openai-compatible",
|
|
updatedAt: "2024-01-01T00:00:00.000Z",
|
|
};
|
|
|
|
function clickLatestConfirmButton() {
|
|
const buttons = screen.getAllByRole("button", { name: /OK|确定/ });
|
|
fireEvent.click(buttons[buttons.length - 1]!);
|
|
}
|
|
|
|
describe("ProviderTable", () => {
|
|
test("渲染供应商表格数据", () => {
|
|
renderWithProviders(
|
|
createElement(ProviderTable, {
|
|
data: { items: [ENABLED_PROVIDER, DISABLED_PROVIDER], page: 1, pageSize: 20, total: 2 },
|
|
loading: false,
|
|
onDelete: () => Promise.resolve(),
|
|
onDisable: () => Promise.resolve(),
|
|
onEdit: () => undefined,
|
|
onEnable: () => Promise.resolve(),
|
|
onPageChange: () => undefined,
|
|
onTest: () => Promise.resolve({ message: "ok", ok: true }),
|
|
page: 1,
|
|
pageSize: 20,
|
|
}),
|
|
);
|
|
|
|
expect(screen.getAllByText("OpenAI").length).toBeGreaterThan(0);
|
|
expect(screen.getByText("DeepSeek")).not.toBeNull();
|
|
expect(screen.getByText("https://api.openai.com/v1")).not.toBeNull();
|
|
});
|
|
|
|
test("供应商表格操作触发 enable/disable/delete", async () => {
|
|
const onDisable = mock(() => Promise.resolve());
|
|
const onEnable = mock(() => Promise.resolve());
|
|
const onDelete = mock(() => Promise.resolve());
|
|
|
|
renderWithProviders(
|
|
createElement(ProviderTable, {
|
|
data: { items: [ENABLED_PROVIDER, DISABLED_PROVIDER], page: 1, pageSize: 20, total: 2 },
|
|
loading: false,
|
|
onDelete,
|
|
onDisable,
|
|
onEdit: () => undefined,
|
|
onEnable,
|
|
onPageChange: () => undefined,
|
|
onTest: () => Promise.resolve({ message: "ok", ok: true }),
|
|
page: 1,
|
|
pageSize: 20,
|
|
}),
|
|
);
|
|
|
|
const disableButtons = screen.getAllByRole("button", { name: /禁用/ });
|
|
fireEvent.click(disableButtons[0]!);
|
|
await waitFor(() => expect(screen.getByText("确认禁用此供应商?")).not.toBeNull());
|
|
clickLatestConfirmButton();
|
|
await waitFor(() => expect(onDisable).toHaveBeenCalledWith("pv1"));
|
|
|
|
const enableButtons = screen.getAllByRole("button", { name: /启用/ });
|
|
fireEvent.click(enableButtons[0]!);
|
|
await waitFor(() => expect(onEnable).toHaveBeenCalledWith("pv2"));
|
|
});
|
|
});
|