Files
CC-Switch/tests/hooks/useCodexConfigState.catalog.test.ts
T
Jason 85b38c1bed refactor(codex): decouple model mapping from local-routing toggle
Align the Codex provider form with Claude Code. Model mapping (the
catalog) is independent from route takeover: native Responses providers
(MiMo, Doubao, MiniMax) need it for direct connect with no proxy, while
Chat providers use the proxy regardless of any per-provider flag.

- Remove the "Needs Local Routing" toggle. It had no backend field and
  only gated catalog/reasoning persistence, which is equivalent to
  "is the mapping filled".
- Always show model mapping for non-official providers; persist when the
  list is non-empty (backend already keys off modelCatalog.models).
- Gate reasoning visibility/persistence on Chat format, not the toggle.
- Mark the Chat upstream-format option "routing required" and refresh the
  advanced-section hint (zh/en/ja/zh-TW); drop dead localRouting* keys.
- Move the model-mapping block above the custom User-Agent block.

fix(codex): preserve native-profile hidden catalog fields on edit

useCodexConfigState dropped supportsParallelToolCalls / inputModalities /
baseInstructions when loading a saved provider, so editing and saving a
native Responses provider lost parallel tools, image input and the
official base instructions from the generated catalog. Preserve them on
load (camelCase + snake_case) and compare them in the row sync. Add a
regression test.
2026-06-29 23:30:58 +08:00

80 lines
2.7 KiB
TypeScript

import { renderHook } from "@testing-library/react";
import { describe, it, expect } from "vitest";
import { useCodexConfigState } from "@/components/providers/forms/hooks/useCodexConfigState";
// 回归:编辑已存在的原生 Responses 供应商时,读回 modelCatalog 必须保留隐藏字段
// (supportsParallelToolCalls / inputModalities / baseInstructions),否则保存会
// 把它们剥掉,导致生成的 Codex catalog 丢官方 base_instructions、并行工具、图像模态。
//
// 注意:initialData 必须是稳定引用(hook 的 init effect 依赖 [initialData])。
// 写成内联字面量会每次 re-render 产生新引用 → effect 反复 setState → 死循环 OOM。
describe("useCodexConfigState catalog load", () => {
it("preserves native-profile hidden fields (camelCase, DB SSOT)", () => {
const initialData = {
settingsConfig: {
auth: { OPENAI_API_KEY: "sk-x" },
config: "",
modelCatalog: {
models: [
{
model: "MiniMax-M3",
displayName: "MiniMax-M3",
contextWindow: 1000000,
supportsParallelToolCalls: true,
inputModalities: ["text", "image"],
baseInstructions: "You are Codex, based on MiniMax-M3.",
},
],
},
},
};
const { result } = renderHook(() => useCodexConfigState({ initialData }));
expect(result.current.codexCatalogModels).toEqual([
{
model: "MiniMax-M3",
displayName: "MiniMax-M3",
contextWindow: 1000000,
supportsParallelToolCalls: true,
inputModalities: ["text", "image"],
baseInstructions: "You are Codex, based on MiniMax-M3.",
},
]);
});
it("maps snake_case hidden fields (live reverse-parse fallback) to camelCase", () => {
const initialData = {
settingsConfig: {
auth: {},
config: "",
modelCatalog: {
models: [
{
model: "mimo-v2.5-pro",
display_name: "MiMo V2.5 Pro",
context_window: 262144,
supports_parallel_tool_calls: false,
input_modalities: ["text"],
base_instructions: "You are MiMo, developed by Xiaomi.",
},
],
},
},
};
const { result } = renderHook(() => useCodexConfigState({ initialData }));
expect(result.current.codexCatalogModels).toEqual([
{
model: "mimo-v2.5-pro",
displayName: "MiMo V2.5 Pro",
contextWindow: 262144,
supportsParallelToolCalls: false,
inputModalities: ["text"],
baseInstructions: "You are MiMo, developed by Xiaomi.",
},
]);
});
});