test: 添加配置与设置系统单元测试 (测试计划 09)

为 SettingsSchema、PermissionsSchema、AllowedMcpServerEntrySchema
验证,MCP 类型守卫,设置常量函数,以及 validation 工具函数添加
62 个测试用例。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
claude-code-best 2026-04-01 22:39:21 +08:00
parent 3df4b95ff9
commit 183421361e

View File

@ -0,0 +1,476 @@
import { describe, expect, test } from "bun:test";
import {
SettingsSchema,
EnvironmentVariablesSchema,
PermissionsSchema,
AllowedMcpServerEntrySchema,
DeniedMcpServerEntrySchema,
isMcpServerNameEntry,
isMcpServerCommandEntry,
isMcpServerUrlEntry,
CUSTOMIZATION_SURFACES,
} from "../types";
import {
SETTING_SOURCES,
SOURCES,
CLAUDE_CODE_SETTINGS_SCHEMA_URL,
getSettingSourceName,
getSourceDisplayName,
getSettingSourceDisplayNameLowercase,
getSettingSourceDisplayNameCapitalized,
parseSettingSourcesFlag,
} from "../constants";
import {
formatZodError,
filterInvalidPermissionRules,
validateSettingsFileContent,
} from "../validation";
// ─── Settings Schema Validation ──────────────────────────────────────────
describe("SettingsSchema", () => {
test("accepts empty object", () => {
const result = SettingsSchema().safeParse({});
expect(result.success).toBe(true);
});
test("accepts model string", () => {
const result = SettingsSchema().safeParse({ model: "sonnet" });
expect(result.success).toBe(true);
});
test("accepts permissions block with allow rules", () => {
const result = SettingsSchema().safeParse({
permissions: { allow: ["Bash(npm install)"] },
});
expect(result.success).toBe(true);
});
test("accepts permissions block with deny rules", () => {
const result = SettingsSchema().safeParse({
permissions: { deny: ["Bash(rm -rf *)"] },
});
expect(result.success).toBe(true);
});
test("accepts env variables", () => {
const result = SettingsSchema().safeParse({
env: { FOO: "bar", DEBUG: "1" },
});
expect(result.success).toBe(true);
});
test("accepts hooks configuration", () => {
const result = SettingsSchema().safeParse({
hooks: {
PreToolUse: [
{
matcher: "Bash",
hooks: [{ type: "command", command: "echo test" }],
},
],
},
});
expect(result.success).toBe(true);
});
test("accepts attribution settings", () => {
const result = SettingsSchema().safeParse({
attribution: {
commit: "Generated by AI",
pr: "AI-generated PR",
},
});
expect(result.success).toBe(true);
});
test("accepts worktree settings", () => {
const result = SettingsSchema().safeParse({
worktree: {
symlinkDirectories: ["node_modules", ".cache"],
sparsePaths: ["src/"],
},
});
expect(result.success).toBe(true);
});
test("accepts $schema field", () => {
const result = SettingsSchema().safeParse({
$schema: CLAUDE_CODE_SETTINGS_SCHEMA_URL,
});
expect(result.success).toBe(true);
});
test("passes through unknown keys (passthrough mode)", () => {
const result = SettingsSchema().safeParse({ unknownKey: "value" });
expect(result.success).toBe(true);
if (result.success) {
expect((result.data as any).unknownKey).toBe("value");
}
});
test("coerces env var numbers to strings", () => {
const result = EnvironmentVariablesSchema().safeParse({ PORT: 3000 });
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.PORT).toBe("3000");
}
});
test("accepts boolean settings", () => {
const result = SettingsSchema().safeParse({
includeCoAuthoredBy: true,
respectGitignore: false,
disableAllHooks: true,
});
expect(result.success).toBe(true);
});
test("accepts cleanupPeriodDays", () => {
const result = SettingsSchema().safeParse({ cleanupPeriodDays: 30 });
expect(result.success).toBe(true);
});
test("rejects negative cleanupPeriodDays", () => {
const result = SettingsSchema().safeParse({ cleanupPeriodDays: -1 });
expect(result.success).toBe(false);
});
test("accepts statusLine configuration", () => {
const result = SettingsSchema().safeParse({
statusLine: { type: "command", command: "echo status" },
});
expect(result.success).toBe(true);
});
test("accepts sshConfigs", () => {
const result = SettingsSchema().safeParse({
sshConfigs: [
{
id: "dev-server",
name: "Development Server",
sshHost: "dev.example.com",
sshPort: 22,
},
],
});
expect(result.success).toBe(true);
});
});
// ─── Permissions Schema ─────────────────────────────────────────────────
describe("PermissionsSchema", () => {
test("accepts defaultMode", () => {
const result = PermissionsSchema().safeParse({
defaultMode: "acceptEdits",
});
expect(result.success).toBe(true);
});
test("accepts additionalDirectories", () => {
const result = PermissionsSchema().safeParse({
additionalDirectories: ["/tmp/extra"],
});
expect(result.success).toBe(true);
});
test("accepts disableBypassPermissionsMode", () => {
const result = PermissionsSchema().safeParse({
disableBypassPermissionsMode: "disable",
});
expect(result.success).toBe(true);
});
});
// ─── AllowedMcpServerEntrySchema ────────────────────────────────────────
describe("AllowedMcpServerEntrySchema", () => {
test("accepts serverName entry", () => {
const result = AllowedMcpServerEntrySchema().safeParse({
serverName: "my-server",
});
expect(result.success).toBe(true);
});
test("accepts serverCommand entry", () => {
const result = AllowedMcpServerEntrySchema().safeParse({
serverCommand: ["npx", "mcp-server"],
});
expect(result.success).toBe(true);
});
test("accepts serverUrl entry", () => {
const result = AllowedMcpServerEntrySchema().safeParse({
serverUrl: "https://*.example.com/*",
});
expect(result.success).toBe(true);
});
test("rejects entry with no fields", () => {
const result = AllowedMcpServerEntrySchema().safeParse({});
expect(result.success).toBe(false);
});
test("rejects entry with multiple fields", () => {
const result = AllowedMcpServerEntrySchema().safeParse({
serverName: "my-server",
serverUrl: "https://example.com",
});
expect(result.success).toBe(false);
});
test("rejects invalid serverName characters", () => {
const result = AllowedMcpServerEntrySchema().safeParse({
serverName: "my server with spaces",
});
expect(result.success).toBe(false);
});
test("rejects empty serverCommand array", () => {
const result = AllowedMcpServerEntrySchema().safeParse({
serverCommand: [],
});
expect(result.success).toBe(false);
});
});
// ─── Type guards ─────────────────────────────────────────────────────────
describe("MCP server entry type guards", () => {
test("isMcpServerNameEntry identifies name entry", () => {
expect(isMcpServerNameEntry({ serverName: "test" })).toBe(true);
});
test("isMcpServerNameEntry rejects non-name entry", () => {
expect(isMcpServerNameEntry({ serverUrl: "https://example.com" })).toBe(
false
);
});
test("isMcpServerCommandEntry identifies command entry", () => {
expect(isMcpServerCommandEntry({ serverCommand: ["npx", "srv"] })).toBe(
true
);
});
test("isMcpServerCommandEntry rejects non-command entry", () => {
expect(isMcpServerCommandEntry({ serverName: "test" })).toBe(false);
});
test("isMcpServerUrlEntry identifies url entry", () => {
expect(
isMcpServerUrlEntry({ serverUrl: "https://example.com" })
).toBe(true);
});
test("isMcpServerUrlEntry rejects non-url entry", () => {
expect(isMcpServerUrlEntry({ serverName: "test" })).toBe(false);
});
});
// ─── Constants ──────────────────────────────────────────────────────────
describe("SETTING_SOURCES", () => {
test("contains all five sources in order", () => {
expect(SETTING_SOURCES).toEqual([
"userSettings",
"projectSettings",
"localSettings",
"flagSettings",
"policySettings",
]);
});
});
describe("SOURCES (editable)", () => {
test("contains three editable sources", () => {
expect(SOURCES).toEqual([
"localSettings",
"projectSettings",
"userSettings",
]);
});
});
describe("CUSTOMIZATION_SURFACES", () => {
test("contains expected surfaces", () => {
expect(CUSTOMIZATION_SURFACES).toEqual([
"skills",
"agents",
"hooks",
"mcp",
]);
});
});
describe("getSettingSourceName", () => {
test("maps userSettings to user", () => {
expect(getSettingSourceName("userSettings")).toBe("user");
});
test("maps projectSettings to project", () => {
expect(getSettingSourceName("projectSettings")).toBe("project");
});
test("maps localSettings to project, gitignored", () => {
expect(getSettingSourceName("localSettings")).toBe("project, gitignored");
});
test("maps flagSettings to cli flag", () => {
expect(getSettingSourceName("flagSettings")).toBe("cli flag");
});
test("maps policySettings to managed", () => {
expect(getSettingSourceName("policySettings")).toBe("managed");
});
});
describe("getSourceDisplayName", () => {
test("maps userSettings to User", () => {
expect(getSourceDisplayName("userSettings")).toBe("User");
});
test("maps plugin to Plugin", () => {
expect(getSourceDisplayName("plugin")).toBe("Plugin");
});
test("maps built-in to Built-in", () => {
expect(getSourceDisplayName("built-in")).toBe("Built-in");
});
});
describe("getSettingSourceDisplayNameLowercase", () => {
test("maps policySettings correctly", () => {
expect(getSettingSourceDisplayNameLowercase("policySettings")).toBe(
"enterprise managed settings"
);
});
test("maps cliArg correctly", () => {
expect(getSettingSourceDisplayNameLowercase("cliArg")).toBe("CLI argument");
});
test("maps session correctly", () => {
expect(getSettingSourceDisplayNameLowercase("session")).toBe(
"current session"
);
});
});
describe("getSettingSourceDisplayNameCapitalized", () => {
test("maps userSettings correctly", () => {
expect(getSettingSourceDisplayNameCapitalized("userSettings")).toBe(
"User settings"
);
});
test("maps command correctly", () => {
expect(getSettingSourceDisplayNameCapitalized("command")).toBe(
"Command configuration"
);
});
});
describe("parseSettingSourcesFlag", () => {
test("parses comma-separated sources", () => {
expect(parseSettingSourcesFlag("user,project,local")).toEqual([
"userSettings",
"projectSettings",
"localSettings",
]);
});
test("parses single source", () => {
expect(parseSettingSourcesFlag("user")).toEqual(["userSettings"]);
});
test("returns empty array for empty string", () => {
expect(parseSettingSourcesFlag("")).toEqual([]);
});
test("trims whitespace", () => {
expect(parseSettingSourcesFlag("user , project")).toEqual([
"userSettings",
"projectSettings",
]);
});
test("throws for invalid source name", () => {
expect(() => parseSettingSourcesFlag("invalid")).toThrow(
"Invalid setting source"
);
});
});
// ─── Validation ─────────────────────────────────────────────────────────
describe("filterInvalidPermissionRules", () => {
test("returns empty for non-object input", () => {
expect(filterInvalidPermissionRules(null, "test.json")).toEqual([]);
expect(filterInvalidPermissionRules("string", "test.json")).toEqual([]);
});
test("returns empty when no permissions", () => {
expect(filterInvalidPermissionRules({}, "test.json")).toEqual([]);
});
test("filters non-string rules and returns warnings", () => {
const data = { permissions: { allow: ["Bash", 123, "Read"] } };
const warnings = filterInvalidPermissionRules(data, "test.json");
expect(warnings.length).toBe(1);
expect(warnings[0]!.path).toBe("permissions.allow");
expect((data.permissions as any).allow).toEqual(["Bash", "Read"]);
});
test("preserves valid rules", () => {
const data = {
permissions: { allow: ["Bash(npm install)", "Read", "Write"] },
};
const warnings = filterInvalidPermissionRules(data, "test.json");
expect(warnings).toEqual([]);
expect((data.permissions as any).allow).toEqual([
"Bash(npm install)",
"Read",
"Write",
]);
});
});
describe("validateSettingsFileContent", () => {
test("accepts valid JSON settings", () => {
const result = validateSettingsFileContent('{"model": "sonnet"}');
expect(result.isValid).toBe(true);
});
test("accepts empty object", () => {
const result = validateSettingsFileContent("{}");
expect(result.isValid).toBe(true);
});
test("rejects invalid JSON", () => {
const result = validateSettingsFileContent("not json");
expect(result.isValid).toBe(false);
if (!result.isValid) {
expect(result.error).toContain("Invalid JSON");
}
});
test("rejects unknown keys in strict mode", () => {
const result = validateSettingsFileContent('{"unknownField": true}');
expect(result.isValid).toBe(false);
});
});
describe("formatZodError", () => {
test("formats invalid type error", () => {
const result = SettingsSchema().safeParse({ model: 123 });
expect(result.success).toBe(false);
if (!result.success) {
const errors = formatZodError(result.error, "settings.json");
expect(errors.length).toBeGreaterThan(0);
expect(errors[0]!.file).toBe("settings.json");
expect(errors[0]!.path).toContain("model");
}
});
});