mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-24 21:30:17 +08:00
325ba48486
Grok Build previously reused the whole Codex preset list, leaking cn_official providers and Codex default models into the Grok CLI form. Replace the inline filter with a standalone, independently maintained preset module (no data linkage to codexProviderPresets): - drop cn_official direct providers and open-source-only hosts (SiliconFlow/ModelScope/Novita/Nvidia/AtlasCloud/OpenCode Go) that have no Grok models upstream - keep aggregators and third-party relays with grok-4.5 as the default model (x-ai/grok-4.5 for OpenRouter-style namespaced routers) - move the Grok Official seed entry into the presets module - add standalone integrity tests; retarget the form's chat-mapping test since no openai_chat preset remains in the list
194 lines
6.2 KiB
TypeScript
194 lines
6.2 KiB
TypeScript
import { fireEvent, render, screen } from "@testing-library/react";
|
|
import userEvent from "@testing-library/user-event";
|
|
import { parse as parseToml } from "smol-toml";
|
|
import { describe, expect, it, vi } from "vitest";
|
|
import {
|
|
GrokBuildProviderForm,
|
|
grokApiBackendFromApiFormat,
|
|
} from "@/components/providers/forms/GrokBuildProviderForm";
|
|
|
|
vi.mock("@/components/JsonEditor", () => ({
|
|
default: ({
|
|
value,
|
|
onChange,
|
|
}: {
|
|
value: string;
|
|
onChange: (value: string) => void;
|
|
}) => (
|
|
<textarea
|
|
aria-label="raw-config"
|
|
value={value}
|
|
onChange={(event) => onChange(event.target.value)}
|
|
/>
|
|
),
|
|
}));
|
|
|
|
describe("GrokBuildProviderForm", () => {
|
|
it("offers curated Grok Build presets and applies one", async () => {
|
|
const user = userEvent.setup();
|
|
const { container } = render(
|
|
<GrokBuildProviderForm
|
|
submitLabel="Save"
|
|
onSubmit={() => {}}
|
|
onCancel={() => {}}
|
|
/>,
|
|
);
|
|
|
|
// 国产官方直连(cn_official)不在 Grok Build 预设列表里
|
|
expect(screen.queryByRole("button", { name: /BytePlus/ })).toBeNull();
|
|
expect(screen.queryByRole("button", { name: /Kimi/ })).toBeNull();
|
|
|
|
await user.click(screen.getByRole("button", { name: /PatewayAI/ }));
|
|
|
|
const baseUrlInput =
|
|
container.querySelector<HTMLInputElement>("#codexBaseUrl");
|
|
const nameInput =
|
|
container.querySelector<HTMLInputElement>('input[name="name"]');
|
|
expect(baseUrlInput?.value).toBe("https://api.pateway.ai/v1");
|
|
expect(nameInput?.value).toBe("PatewayAI");
|
|
});
|
|
|
|
it("submits a complete config.toml payload with Grok defaults", async () => {
|
|
const user = userEvent.setup();
|
|
const onSubmit = vi.fn();
|
|
const { container } = render(
|
|
<GrokBuildProviderForm
|
|
submitLabel="Save"
|
|
onSubmit={onSubmit}
|
|
onCancel={() => {}}
|
|
/>,
|
|
);
|
|
|
|
const nameInput =
|
|
container.querySelector<HTMLInputElement>('input[name="name"]');
|
|
const baseUrlInput =
|
|
container.querySelector<HTMLInputElement>("#codexBaseUrl");
|
|
expect(nameInput).not.toBeNull();
|
|
expect(baseUrlInput).not.toBeNull();
|
|
|
|
fireEvent.change(nameInput!, { target: { value: "Example Relay" } });
|
|
fireEvent.change(baseUrlInput!, {
|
|
target: { value: "https://relay.example.com/v1" },
|
|
});
|
|
fireEvent.change(screen.getByLabelText("API Key"), {
|
|
target: { value: "secret-key" },
|
|
});
|
|
await user.click(screen.getByRole("button", { name: "Save" }));
|
|
|
|
expect(onSubmit).toHaveBeenCalledTimes(1);
|
|
const submitted = onSubmit.mock.calls[0][0];
|
|
expect(submitted.icon).toBe("");
|
|
const settings = JSON.parse(submitted.settingsConfig);
|
|
const config = parseToml(settings.config) as any;
|
|
|
|
expect(config.models.default).toBe("grok-4.5");
|
|
expect(config.model["grok-4.5"]).toEqual({
|
|
model: "grok-4.5",
|
|
base_url: "https://relay.example.com/v1",
|
|
name: "Example Relay",
|
|
api_key: "secret-key",
|
|
api_backend: "responses",
|
|
context_window: 500000,
|
|
});
|
|
});
|
|
|
|
it("maps preset API formats into Grok api_backend", async () => {
|
|
// 预设列表已不含 Chat Completions 条目(国产官方直连被移除),
|
|
// chat/messages 映射分支由纯函数覆盖
|
|
expect(grokApiBackendFromApiFormat("openai_chat")).toBe("chat_completions");
|
|
expect(grokApiBackendFromApiFormat("anthropic")).toBe("messages");
|
|
expect(grokApiBackendFromApiFormat("openai_responses")).toBe("responses");
|
|
|
|
// 组件级接线用带显式 apiFormat 的 Responses 预设验证
|
|
const user = userEvent.setup();
|
|
const onSubmit = vi.fn();
|
|
render(
|
|
<GrokBuildProviderForm
|
|
submitLabel="Save"
|
|
onSubmit={onSubmit}
|
|
onCancel={() => {}}
|
|
/>,
|
|
);
|
|
|
|
await user.click(screen.getByRole("button", { name: /APIKEY\.FUN/ }));
|
|
await user.type(screen.getByLabelText("API Key"), "secret-key");
|
|
await user.click(screen.getByRole("button", { name: "Save" }));
|
|
|
|
expect(onSubmit).toHaveBeenCalledTimes(1);
|
|
const submitted = onSubmit.mock.calls[0][0];
|
|
const settings = JSON.parse(submitted.settingsConfig);
|
|
const config = parseToml(settings.config) as any;
|
|
expect(submitted.meta.apiFormat).toBe("openai_responses");
|
|
const selected = config.model[config.models.default];
|
|
expect(selected.api_backend).toBe("responses");
|
|
expect(selected.model).toBe("grok-4.5");
|
|
expect(selected.base_url).toBe("https://api.apikey.fun/v1");
|
|
});
|
|
|
|
it("renders localized validation feedback for malformed TOML", async () => {
|
|
const onSubmit = vi.fn();
|
|
render(
|
|
<GrokBuildProviderForm
|
|
submitLabel="Save"
|
|
onSubmit={onSubmit}
|
|
onCancel={() => {}}
|
|
/>,
|
|
);
|
|
|
|
fireEvent.change(screen.getByLabelText("raw-config"), {
|
|
target: { value: "[models" },
|
|
});
|
|
|
|
expect(screen.getByText(/Invalid config\.toml:/)).toBeInTheDocument();
|
|
expect(onSubmit).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("loads edit-mode values and does not resubmit stale custom endpoints", async () => {
|
|
const user = userEvent.setup();
|
|
const onSubmit = vi.fn();
|
|
const config = `[models]
|
|
default = "existing-profile"
|
|
|
|
[model."existing-profile"]
|
|
model = "grok-upstream"
|
|
base_url = "https://existing.example.com/v1"
|
|
name = "Existing Relay"
|
|
api_key = "existing-key"
|
|
api_backend = "responses"
|
|
context_window = 250000
|
|
`;
|
|
const { container } = render(
|
|
<GrokBuildProviderForm
|
|
providerId="existing-provider"
|
|
submitLabel="Save"
|
|
onSubmit={onSubmit}
|
|
onCancel={() => {}}
|
|
initialData={{
|
|
name: "Existing Relay",
|
|
settingsConfig: { config },
|
|
meta: {
|
|
custom_endpoints: {
|
|
"https://deleted.example.com/v1": {
|
|
url: "https://deleted.example.com/v1",
|
|
addedAt: 1,
|
|
},
|
|
},
|
|
},
|
|
}}
|
|
/>,
|
|
);
|
|
|
|
expect(
|
|
container.querySelector<HTMLInputElement>("#grokbuild-profile")?.value,
|
|
).toBe("existing-profile");
|
|
expect(
|
|
container.querySelector<HTMLInputElement>("#codexBaseUrl")?.value,
|
|
).toBe("https://existing.example.com/v1");
|
|
|
|
await user.click(screen.getByRole("button", { name: "Save" }));
|
|
|
|
expect(onSubmit).toHaveBeenCalledTimes(1);
|
|
expect(onSubmit.mock.calls[0][0].meta.custom_endpoints).toBeUndefined();
|
|
});
|
|
});
|