Files
9router/tests/unit/cli-tool-config-contract.test.js
T

99 lines
3.8 KiB
JavaScript

import { describe, expect, it } from "vitest";
import {
CliToolConfigValidationError,
isPersistableCliTool,
normalizeCliToolConfig,
} from "@/shared/constants/cliToolConfig.js";
describe("CLI tool configuration contract", () => {
it("allows every non-MITM CLI tool and rejects MITM tool IDs", () => {
for (const toolId of ["claude", "codex", "opencode", "cowork", "cursor", "copilot"]) {
expect(isPersistableCliTool(toolId)).toBe(true);
}
for (const toolId of ["antigravity", "kiro", "unknown", ""]) {
expect(isPersistableCliTool(toolId)).toBe(false);
}
});
it("normalizes Claude config and strips unknown fields", () => {
expect(normalizeCliToolConfig("claude", {
baseUrl: "https://router.example/v1/",
apiKeyMode: "managed",
apiKeyId: "key-1",
claudeModels: { sonnet: " cc/sonnet ", opus: "cc/opus", haiku: "" },
claudeThinking: { sonnet: "high", opus: "", ignored: "max" },
transientModalOpen: true,
})).toEqual({
baseUrl: "https://router.example/v1",
apiKeyMode: "managed",
apiKeyId: "key-1",
claudeModels: { sonnet: "cc/sonnet", opus: "cc/opus", haiku: "" },
claudeThinking: { sonnet: "high" },
});
});
it("normalizes tool-specific model selections", () => {
expect(normalizeCliToolConfig("codex", {
baseUrl: "http://localhost:20128/v1",
apiKeyMode: "custom",
apiKeyId: "must-be-removed",
codexModel: "cx/gpt",
codexThinking: "xhigh",
})).toMatchObject({ apiKeyMode: "custom", apiKeyId: null, codexModel: "cx/gpt", codexThinking: "xhigh" });
expect(normalizeCliToolConfig("opencode", {
baseUrl: "https://router.example",
apiKeyMode: "managed",
apiKeyId: null,
opencodeModels: ["cc/a", "cc/a", "cx/b"],
opencodeDefaultModel: "missing/model",
})).toMatchObject({ opencodeModels: ["cc/a", "cc/a", "cx/b"], opencodeDefaultModel: "cc/a" });
expect(normalizeCliToolConfig("cowork", {
baseUrl: "https://router.example",
apiKeyMode: "managed",
selectedModels: ["cc/a", "cc/a"],
coworkThinking: { "cc/a": "high", "stale/model": "low" },
})).toMatchObject({ selectedModels: ["cc/a", "cc/a"], coworkThinking: { "cc/a": "high" } });
expect(normalizeCliToolConfig("cursor", {
apiKeyMode: "managed",
apiKeyId: "key-1",
selectedModels: ["cc/a", "cc/a", "cx/b"],
})).toEqual({ apiKeyMode: "managed", apiKeyId: "key-1", selectedModels: ["cc/a", "cc/a", "cx/b"] });
expect(normalizeCliToolConfig("copilot", {
baseUrl: "https://router.example/v1",
selectedModels: ["cc/a", "cc/a"],
copilotThinking: { "cc/a": "high", "stale/model": "low" },
copilotTokens: {
"cc/a": { maxInputTokens: 100000, maxOutputTokens: 32000 },
"stale/model": { maxInputTokens: 1 },
},
})).toEqual({
baseUrl: "https://router.example/v1",
selectedModels: ["cc/a", "cc/a"],
copilotThinking: { "cc/a": "high" },
copilotTokens: { "cc/a": { maxInputTokens: 100000, maxOutputTokens: 32000 } },
});
});
it("rejects plaintext secrets, invalid URLs, and invalid token limits", () => {
expect(() => normalizeCliToolConfig("claude", {
baseUrl: "https://router.example",
apiKey: "secret",
})).toThrowError(CliToolConfigValidationError);
expect(() => normalizeCliToolConfig("codex", {
baseUrl: "file:///tmp/socket",
apiKeyMode: "managed",
})).toThrow("Endpoint must use HTTP or HTTPS");
expect(() => normalizeCliToolConfig("copilot", {
baseUrl: "https://router.example",
selectedModels: ["cc/a"],
copilotTokens: { "cc/a": { maxInputTokens: -1 } },
})).toThrow("maxInputTokens must be a positive integer");
});
});