mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-06-16 13:34:04 +08:00
a04e72a267
The reported "OAuth access token disappears when enabling Codex proxy takeover" was a display artifact, not data loss: auth.json on disk kept the OAuth login the whole time. During takeover the edit dialog falls back to the stored provider config (so it does not surface the proxy placeholder), which for a third-party provider shows that provider's own key instead of the live auth.json, making the OAuth token look gone. Thread an isProxyTakeover flag from App through ProviderForm into the Codex editor and show an explicit notice plus storage-aware auth/config hints clarifying that the form displays the stored provider config while the live config is temporarily managed by the proxy. Drop the proxy-running condition so the notice shows whenever takeover is active, even with the proxy stopped. Add a regression test asserting the dialog does not read live settings during takeover and renders the database config. i18n synced across en/zh/ja/zh-TW.
206 lines
5.4 KiB
TypeScript
206 lines
5.4 KiB
TypeScript
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 ? (
|
|
<div>
|
|
<div>{children}</div>
|
|
<div>{footer}</div>
|
|
</div>
|
|
) : null,
|
|
}));
|
|
|
|
vi.mock("@/components/providers/forms/ProviderForm", () => ({
|
|
ProviderForm: ({
|
|
initialData,
|
|
onSubmit,
|
|
isProxyTakeover,
|
|
}: {
|
|
initialData: {
|
|
name?: string;
|
|
websiteUrl?: string;
|
|
notes?: string;
|
|
settingsConfig?: Record<string, unknown>;
|
|
meta?: Record<string, unknown>;
|
|
icon?: string;
|
|
iconColor?: string;
|
|
};
|
|
onSubmit: (values: {
|
|
name: string;
|
|
websiteUrl: string;
|
|
notes?: string;
|
|
settingsConfig: string;
|
|
meta?: Record<string, unknown>;
|
|
icon?: string;
|
|
iconColor?: string;
|
|
}) => void;
|
|
isProxyTakeover?: boolean;
|
|
}) => (
|
|
<form
|
|
id="provider-form"
|
|
onSubmit={(event) => {
|
|
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,
|
|
});
|
|
}}
|
|
>
|
|
<output data-testid="settings-config">
|
|
{JSON.stringify(initialData.settingsConfig ?? {})}
|
|
</output>
|
|
<output data-testid="is-proxy-takeover">
|
|
{isProxyTakeover ? "true" : "false"}
|
|
</output>
|
|
</form>
|
|
),
|
|
}));
|
|
|
|
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(
|
|
<EditProviderDialog
|
|
open
|
|
provider={provider}
|
|
onOpenChange={vi.fn()}
|
|
onSubmit={handleSubmit}
|
|
appId="codex"
|
|
/>,
|
|
);
|
|
|
|
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(
|
|
<EditProviderDialog
|
|
open
|
|
provider={provider}
|
|
onOpenChange={vi.fn()}
|
|
onSubmit={vi.fn()}
|
|
appId="codex"
|
|
isProxyTakeover
|
|
/>,
|
|
);
|
|
|
|
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);
|
|
});
|
|
});
|