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 as any).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"); } }); }); describe("gemini settings", () => { test("accepts gemini modelType", () => { const result = SettingsSchema().safeParse({ modelType: "gemini" }); expect(result.success).toBe(true); }); });