import { fireEvent, screen, waitFor } from "@testing-library/react"; import { describe, expect, mock, test } from "bun:test"; import { createElement } from "react"; import type { Model, Provider } from "../../../src/shared/api"; import { ModelFormModal } from "../../../src/web/pages/models/components/ModelFormModal"; import { ProviderFormModal } from "../../../src/web/pages/models/components/ProviderFormModal"; 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", 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", id: "pv2", name: "DeepSeek", type: "openai-compatible", updatedAt: "2024-01-01T00:00:00.000Z", }; const ENABLED_MODEL: Model = { capabilities: ["text", "reasoning"], contextLength: 128000, createdAt: "2024-01-01T00:00:00.000Z", id: "m1", maxOutputTokens: 4096, modelId: "gpt-4o", name: "GPT-4o", providerId: "pv1", updatedAt: "2024-01-01T00:00:00.000Z", }; function clickLatestConfirmButton() { const buttons = screen.getAllByRole("button", { name: /OK|确\s*定/ }); fireEvent.click(buttons[buttons.length - 1]!); } describe("ProviderFormModal", () => { test("编辑供应商表单只提交变更字段", async () => { const updateCalls: unknown[] = []; renderWithProviders( createElement(ProviderFormModal, { editingProvider: ENABLED_PROVIDER, onCancel: () => undefined, onCreate: () => Promise.resolve(), onOpenChange: () => undefined, onTest: () => Promise.resolve({ message: "连接成功", ok: true }), onUpdate: (args: unknown) => { updateCalls.push(args); return Promise.resolve(); }, open: true, submitting: false, }), ); await waitFor(() => expect(screen.getByPlaceholderText("请输入供应商名称")).not.toBeNull()); fireEvent.change(screen.getByPlaceholderText("请输入供应商名称"), { target: { value: "New OpenAI" } }); clickLatestConfirmButton(); await waitFor(() => expect(updateCalls.length).toBe(1)); expect(updateCalls[0]).toEqual({ data: { name: "New OpenAI" }, id: "pv1" }); }); test("新建供应商默认使用 openai-compatible 类型", async () => { const createCalls: unknown[] = []; renderWithProviders( createElement(ProviderFormModal, { editingProvider: null, onCancel: () => undefined, onCreate: (data: unknown) => { createCalls.push(data); return Promise.resolve(); }, onOpenChange: () => undefined, onTest: () => Promise.resolve({ message: "连接成功", ok: true }), onUpdate: () => Promise.resolve(), open: true, submitting: false, }), ); await waitFor(() => expect(screen.getByPlaceholderText("请输入供应商名称")).not.toBeNull()); fireEvent.change(screen.getByPlaceholderText("请输入供应商名称"), { target: { value: "兼容供应商" } }); fireEvent.change(screen.getByPlaceholderText("https://api.openai.com/v1"), { target: { value: "https://api.test.com/v1" }, }); fireEvent.change(screen.getByPlaceholderText("请输入 API Key"), { target: { value: "sk-test" } }); clickLatestConfirmButton(); await waitFor(() => expect(createCalls.length).toBe(1)); expect(createCalls[0]).toEqual({ apiKey: "sk-test", baseUrl: "https://api.test.com/v1", name: "兼容供应商", type: "openai-compatible", }); }); test("供应商表单可使用当前表单配置测试连接", async () => { const testCalls: unknown[] = []; renderWithProviders( createElement(ProviderFormModal, { editingProvider: null, onCancel: () => undefined, onCreate: () => Promise.resolve(), onOpenChange: () => undefined, onTest: (data: unknown) => { testCalls.push(data); return Promise.resolve({ message: "连接成功", ok: true }); }, onUpdate: () => Promise.resolve(), open: true, submitting: false, }), ); await waitFor(() => expect(screen.getByPlaceholderText("请输入供应商名称")).not.toBeNull()); fireEvent.change(screen.getByPlaceholderText("请输入供应商名称"), { target: { value: "兼容供应商" } }); fireEvent.change(screen.getByPlaceholderText("https://api.openai.com/v1"), { target: { value: "https://api.test.com/v1" }, }); fireEvent.change(screen.getByPlaceholderText("请输入 API Key"), { target: { value: "sk-test" } }); fireEvent.click(screen.getByRole("button", { name: "测试连接" })); await waitFor(() => expect(testCalls.length).toBe(1)); expect(testCalls[0]).toEqual({ apiKey: "sk-test", baseUrl: "https://api.test.com/v1", name: "兼容供应商", type: "openai-compatible", }); }); }); describe("ModelFormModal", () => { test("编辑模型表单只提交变更字段", async () => { const updateCalls: unknown[] = []; renderWithProviders( createElement(ModelFormModal, { editingModel: ENABLED_MODEL, onCancel: () => undefined, onCreate: () => Promise.resolve(), onOpenChange: () => undefined, onUpdate: (args: unknown) => { updateCalls.push(args); return Promise.resolve(); }, open: true, providers: [ENABLED_PROVIDER, DISABLED_PROVIDER], providersError: null, providersLoading: false, submitting: false, }), ); await waitFor(() => expect(screen.getByPlaceholderText("请输入模型名称")).not.toBeNull()); fireEvent.change(screen.getByPlaceholderText("请输入模型名称"), { target: { value: "GPT-4o Mini" } }); clickLatestConfirmButton(); await waitFor(() => expect(updateCalls.length).toBe(1)); expect(updateCalls[0]).toEqual({ data: { name: "GPT-4o Mini" }, id: "m1" }); }); test("模型表单校验失败不会提交", async () => { const onCreate = mock(() => Promise.resolve()); renderWithProviders( createElement(ModelFormModal, { editingModel: null, onCancel: () => undefined, onCreate, onOpenChange: () => undefined, onUpdate: () => Promise.resolve(), open: true, providers: [ENABLED_PROVIDER], providersError: null, providersLoading: false, submitting: false, }), ); await waitFor(() => expect(screen.getByPlaceholderText("请输入模型名称")).not.toBeNull()); clickLatestConfirmButton(); expect(onCreate).not.toHaveBeenCalled(); }); test("新建模型默认选中文本和推理能力", async () => { renderWithProviders( createElement(ModelFormModal, { editingModel: null, onCancel: () => undefined, onCreate: () => Promise.resolve(), onOpenChange: () => undefined, onUpdate: () => Promise.resolve(), open: true, providers: [ENABLED_PROVIDER], providersError: null, providersLoading: false, submitting: false, }), ); await waitFor(() => expect(screen.getByLabelText("文本")).not.toBeNull()); const textCheckbox = screen.getByLabelText("文本"); const reasoningCheckbox = screen.getByLabelText("推理"); expect((textCheckbox as { checked?: boolean }).checked).toBe(true); expect((reasoningCheckbox as { checked?: boolean }).checked).toBe(true); }); test("新建模型展示供应商 options 列表", async () => { renderWithProviders( createElement(ModelFormModal, { editingModel: null, onCancel: () => undefined, onCreate: () => Promise.resolve(), onOpenChange: () => undefined, onUpdate: () => Promise.resolve(), open: true, providers: [ENABLED_PROVIDER, DISABLED_PROVIDER], providersError: null, providersLoading: false, submitting: false, }), ); await waitFor(() => expect(screen.getByPlaceholderText("请输入模型名称")).not.toBeNull()); fireEvent.mouseDown(screen.getByRole("combobox")); expect(await screen.findByText("OpenAI")).not.toBeNull(); expect(await screen.findByText("DeepSeek")).not.toBeNull(); }); test("供应商下拉展示加载错误提示", async () => { renderWithProviders( createElement(ModelFormModal, { editingModel: null, onCancel: () => undefined, onCreate: () => Promise.resolve(), onOpenChange: () => undefined, onUpdate: () => Promise.resolve(), open: true, providers: [], providersError: new Error("options failed"), providersLoading: false, submitting: false, }), ); await waitFor(() => expect(screen.getByPlaceholderText("请输入模型名称")).not.toBeNull()); fireEvent.mouseDown(screen.getByRole("combobox")); expect(await screen.findByText("供应商加载失败:options failed")).not.toBeNull(); }); test("编辑模型时可测试模型连接", async () => { const testModelConnection = mock(() => Promise.resolve({ message: "模型连接成功", ok: true })); renderWithProviders( createElement(ModelFormModal, { editingModel: ENABLED_MODEL, onCancel: () => undefined, onCreate: () => Promise.resolve(), onOpenChange: () => undefined, onUpdate: () => Promise.resolve(), open: true, providers: [ENABLED_PROVIDER], providersError: null, providersLoading: false, submitting: false, testModelConnection, }), ); await waitFor(() => expect(screen.getByRole("button", { name: "测试连接" })).not.toBeNull()); fireEvent.click(screen.getByRole("button", { name: "测试连接" })); await waitFor(() => expect(testModelConnection).toHaveBeenCalledWith({ modelId: "gpt-4o", providerId: "pv1", }), ); }); test("新建模型也显示测试连接按钮", async () => { renderWithProviders( createElement(ModelFormModal, { editingModel: null, onCancel: () => undefined, onCreate: () => Promise.resolve(), onOpenChange: () => undefined, onUpdate: () => Promise.resolve(), open: true, providers: [ENABLED_PROVIDER], providersError: null, providersLoading: false, submitting: false, testModelConnection: () => Promise.resolve({ message: "ok", ok: true }), }), ); await waitFor(() => expect(screen.getByRole("button", { name: "测试连接" })).not.toBeNull()); }); });