import { fireEvent, render, screen, waitFor } from "@testing-library/react"; import { beforeEach, describe, expect, it, vi } from "vitest"; import type { Provider } from "@/types"; const apiMocks = vi.hoisted(() => ({ getCurrent: vi.fn(), getLiveProviderSettings: vi.fn(), getOpenClawLiveProvider: vi.fn(), })); vi.mock("@/lib/api", () => ({ providersApi: { getCurrent: apiMocks.getCurrent, }, vscodeApi: { getLiveProviderSettings: apiMocks.getLiveProviderSettings, }, openclawApi: { getLiveProvider: apiMocks.getOpenClawLiveProvider, }, })); vi.mock("@/components/common/FullScreenPanel", () => ({ FullScreenPanel: ({ isOpen, children, footer, }: { isOpen: boolean; children: React.ReactNode; footer?: React.ReactNode; }) => isOpen ? (
{children}
{footer}
) : null, })); vi.mock("@/components/providers/forms/ProviderForm", () => ({ ProviderForm: ({ initialData, onSubmit, isProxyTakeover, }: { initialData: { name?: string; websiteUrl?: string; notes?: string; settingsConfig?: Record; meta?: Record; icon?: string; iconColor?: string; }; onSubmit: (values: { name: string; websiteUrl: string; notes?: string; settingsConfig: string; meta?: Record; icon?: string; iconColor?: string; }) => void; isProxyTakeover?: boolean; }) => (
{ event.preventDefault(); onSubmit({ name: initialData.name ?? "", websiteUrl: initialData.websiteUrl ?? "", notes: initialData.notes, settingsConfig: JSON.stringify(initialData.settingsConfig ?? {}), meta: initialData.meta, icon: initialData.icon, iconColor: initialData.iconColor, }); }} > {JSON.stringify(initialData.settingsConfig ?? {})} {isProxyTakeover ? "true" : "false"}
), })); import { EditProviderDialog } from "@/components/providers/EditProviderDialog"; describe("EditProviderDialog", () => { beforeEach(() => { apiMocks.getCurrent.mockReset(); apiMocks.getLiveProviderSettings.mockReset(); apiMocks.getOpenClawLiveProvider.mockReset(); }); it("保留 Codex 数据库中的 modelCatalog,避免 live 配置缺字段时清空模型映射", async () => { const dbModelCatalog = { models: [ { model: "deepseek-v4-flash", displayName: "DeepSeek V4 Flash", contextWindow: 1000000, }, ], }; const provider: Provider = { id: "deepseek", name: "DeepSeek", category: "aggregator", settingsConfig: { auth: { OPENAI_API_KEY: "db-key", }, config: 'model_provider = "custom"\nmodel = "deepseek-v4-flash"\n', modelCatalog: dbModelCatalog, }, }; const liveSettings = { auth: { OPENAI_API_KEY: "live-key", }, config: 'model_provider = "custom"\nmodel = "deepseek-v4-pro"\n', }; const handleSubmit = vi.fn().mockResolvedValue(undefined); apiMocks.getCurrent.mockResolvedValue(provider.id); apiMocks.getLiveProviderSettings.mockResolvedValue(liveSettings); render( , ); await waitFor(() => { expect( JSON.parse(screen.getByTestId("settings-config").textContent ?? "{}"), ).toEqual({ ...liveSettings, modelCatalog: dbModelCatalog, }); }); fireEvent.click(screen.getByRole("button", { name: "common.save" })); await waitFor(() => expect(handleSubmit).toHaveBeenCalledTimes(1)); expect(handleSubmit.mock.calls[0][0].provider.settingsConfig).toEqual({ ...liveSettings, modelCatalog: dbModelCatalog, }); }); it("代理接管中编辑 Codex 供应商时展示数据库配置而不是读取 live 代理配置", async () => { const provider: Provider = { id: "deepseek", name: "DeepSeek", category: "custom", settingsConfig: { auth: { OPENAI_API_KEY: "db-key", }, config: 'model_provider = "custom"\n[model_providers.custom]\nbase_url = "https://api.deepseek.com/v1"\n', }, }; apiMocks.getCurrent.mockResolvedValue(provider.id); apiMocks.getLiveProviderSettings.mockResolvedValue({ auth: { OPENAI_API_KEY: "PROXY_MANAGED", }, config: 'model_provider = "custom"\n[model_providers.custom]\nbase_url = "http://127.0.0.1:15721/v1"\nexperimental_bearer_token = "PROXY_MANAGED"\n', }); render( , ); await waitFor(() => { expect(screen.getByTestId("is-proxy-takeover").textContent).toBe("true"); }); expect(apiMocks.getLiveProviderSettings).not.toHaveBeenCalled(); expect( JSON.parse(screen.getByTestId("settings-config").textContent ?? "{}"), ).toEqual(provider.settingsConfig); }); });