167 lines
6.7 KiB
TypeScript
167 lines
6.7 KiB
TypeScript
import { describe, expect, it, vi, beforeEach } from "vitest";
|
|
import { screen, waitFor } from "@testing-library/react";
|
|
import { renderWithProviders } from "@/test/utils";
|
|
import userEvent from "@testing-library/user-event";
|
|
|
|
const listProviders = vi.fn();
|
|
const createProvider = vi.fn();
|
|
const applyProvider = vi.fn();
|
|
const restore = vi.fn();
|
|
const getStatus = vi.fn();
|
|
const fetchModels = vi.fn();
|
|
|
|
vi.mock("@/lib/api", () => ({
|
|
api: {
|
|
listProviders: (target: string) => listProviders(target),
|
|
createProvider: (input: unknown) => createProvider(input),
|
|
updateProvider: vi.fn(),
|
|
deleteProvider: vi.fn(),
|
|
applyProvider: (id: string, target: string) => applyProvider(id, target),
|
|
restore: (input: unknown) => restore(input),
|
|
getStatus: () => getStatus(),
|
|
getSettings: vi.fn().mockResolvedValue({ language: "zh-CN", port_mode: "auto", fixed_port: 0 }),
|
|
updateSettings: vi.fn(),
|
|
listLogs: vi.fn().mockResolvedValue({ items: [] }),
|
|
listBackups: vi.fn().mockResolvedValue({ items: [] }),
|
|
fetchModels: (input: unknown) => fetchModels(input),
|
|
},
|
|
}));
|
|
|
|
import Codex from "./Codex";
|
|
|
|
const provider = {
|
|
id: "p_1",
|
|
name: "DeepSeek",
|
|
preset: "deepseek",
|
|
base_url: "https://api.deepseek.com/v1",
|
|
api_key: "sk-x",
|
|
models: { default: "deepseek-chat", sonnet: "deepseek-chat", haiku: "deepseek-chat", opus: "deepseek-chat" },
|
|
enabled_for_claude: false,
|
|
enabled_for_codex: false,
|
|
target: "codex",
|
|
created_at: "2026-10-18T00:00:00Z",
|
|
};
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
listProviders.mockResolvedValue({ items: [provider] });
|
|
getStatus.mockResolvedValue({
|
|
running: true,
|
|
version: "0.1.0",
|
|
port: 52917,
|
|
uptime_sec: 95,
|
|
active_claude_provider: null,
|
|
active_codex_provider: null,
|
|
stats: { requests_total: 0, requests_failed: 0, input_tokens: 0, output_tokens: 0 },
|
|
language: "zh-CN",
|
|
port_mode: "auto",
|
|
fixed_port: 0,
|
|
});
|
|
createProvider.mockResolvedValue({ ...provider, id: "p_2" });
|
|
applyProvider.mockResolvedValue({
|
|
provider,
|
|
injection: {
|
|
model_provider: "crossrelay",
|
|
table: {
|
|
name: "crossrelay",
|
|
wire_api: "responses",
|
|
base_url: "http://127.0.0.1:52917/v1",
|
|
experimental_bearer_token: "crl_x",
|
|
requires_openai_auth: "false",
|
|
},
|
|
},
|
|
});
|
|
restore.mockResolvedValue({ restored_from: "" });
|
|
fetchModels.mockResolvedValue({ items: [{ id: "deepseek-chat" }, { id: "deepseek-reasoner" }] });
|
|
});
|
|
|
|
describe("Codex page", () => {
|
|
it("shows the injection status, config path and provider list", async () => {
|
|
renderWithProviders(<Codex />);
|
|
expect(await screen.findByText("未注入")).toBeInTheDocument();
|
|
expect(screen.getByText(/config\.toml/)).toBeInTheDocument();
|
|
expect(screen.getByText("DeepSeek")).toBeInTheDocument();
|
|
});
|
|
|
|
it("requests only Codex providers", async () => {
|
|
renderWithProviders(<Codex />);
|
|
await screen.findByText("DeepSeek");
|
|
expect(listProviders).toHaveBeenCalledWith("codex");
|
|
});
|
|
|
|
it("creates a provider scoped to Codex", async () => {
|
|
const user = userEvent.setup();
|
|
renderWithProviders(<Codex />);
|
|
await user.click(await screen.findByRole("button", { name: /新增供应商|Add provider/ }));
|
|
await user.type(screen.getByLabelText(/名称|Name/), "Kimi");
|
|
await user.type(screen.getByLabelText(/Base URL/), "https://api.moonshot.cn/v1");
|
|
await user.type(screen.getByLabelText(/API Key/), "sk-kimi");
|
|
await user.click(screen.getByRole("button", { name: /保存|Save/ }));
|
|
await waitFor(() => expect(createProvider).toHaveBeenCalledTimes(1));
|
|
expect(createProvider.mock.calls[0][0]).toMatchObject({
|
|
name: "Kimi",
|
|
base_url: "https://api.moonshot.cn/v1",
|
|
target: "codex",
|
|
});
|
|
});
|
|
|
|
it("offers model fetching instead of Claude role fields", async () => {
|
|
const user = userEvent.setup();
|
|
renderWithProviders(<Codex />);
|
|
await user.click(await screen.findByRole("button", { name: /新增供应商|Add provider/ }));
|
|
expect(screen.queryByLabelText(/Sonnet 模型|Sonnet model/)).not.toBeInTheDocument();
|
|
expect(screen.queryByLabelText(/Haiku 模型|Haiku model/)).not.toBeInTheDocument();
|
|
expect(screen.queryByLabelText(/Opus 模型|Opus model/)).not.toBeInTheDocument();
|
|
await user.type(screen.getByLabelText(/Base URL/), "https://api.deepseek.com/v1");
|
|
await user.type(screen.getByLabelText(/API Key/), "sk-x");
|
|
await user.click(screen.getByRole("button", { name: /获取模型列表|Fetch models/ }));
|
|
await waitFor(() =>
|
|
expect(fetchModels).toHaveBeenCalledWith({
|
|
base_url: "https://api.deepseek.com/v1",
|
|
api_key: "sk-x",
|
|
}),
|
|
);
|
|
expect(await screen.findByText(/已获取 2 个模型|Fetched 2 models/)).toBeInTheDocument();
|
|
});
|
|
|
|
it("activates a provider for Codex and previews the injected keys", async () => {
|
|
const user = userEvent.setup();
|
|
renderWithProviders(<Codex />);
|
|
await user.click(await screen.findByRole("button", { name: /激活并写入|Activate & apply/ }));
|
|
await waitFor(() => expect(applyProvider).toHaveBeenCalledWith("p_1", "codex"));
|
|
expect(await screen.findByText(/experimental_bearer_token/)).toBeInTheDocument();
|
|
expect(screen.getByText(/wire_api/)).toBeInTheDocument();
|
|
});
|
|
|
|
it("restores the official config", async () => {
|
|
const user = userEvent.setup();
|
|
renderWithProviders(<Codex />);
|
|
await user.click(await screen.findByRole("button", { name: /还原官方配置|Restore official config/ }));
|
|
await waitFor(() => expect(restore).toHaveBeenCalledWith({ target: "codex" }));
|
|
});
|
|
|
|
it("renders protocol options and submits selected protocol for Codex", async () => {
|
|
const user = userEvent.setup();
|
|
renderWithProviders(<Codex />);
|
|
await user.click(await screen.findByRole("button", { name: /新增供应商|Add provider/ }));
|
|
const select = screen.getByLabelText(/协议选择|Protocol/);
|
|
expect(select).toBeInTheDocument();
|
|
expect(screen.getByRole("option", { name: /OpenAI Responses API/ })).toBeInTheDocument();
|
|
expect(screen.getByRole("option", { name: /OpenAI Chat Completions/ })).toBeInTheDocument();
|
|
expect(screen.getByRole("option", { name: /Anthropic Messages/ })).toBeInTheDocument();
|
|
|
|
await user.selectOptions(select, "openai_responses");
|
|
await user.type(screen.getByLabelText(/名称|Name/), "OpenAI-Direct");
|
|
await user.type(screen.getByLabelText(/Base URL/), "https://api.openai.com/v1");
|
|
await user.type(screen.getByLabelText(/API Key/), "sk-oa");
|
|
await user.click(screen.getByRole("button", { name: /保存|Save/ }));
|
|
await waitFor(() => expect(createProvider).toHaveBeenCalledTimes(1));
|
|
const input = createProvider.mock.calls[0][0];
|
|
expect(input).toMatchObject({
|
|
name: "OpenAI-Direct",
|
|
protocol: "openai_responses",
|
|
target: "codex",
|
|
});
|
|
});
|
|
});
|