Files
CrossRelay/src/pages/Codex.test.tsx
T
2026-10-19 13:26:52 +08:00

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",
});
});
});