Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 21 additions & 6 deletions 27 cli/src/__tests__/config-command.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { join } from "path"
import { tmpdir } from "os"
import { setConfigPaths, resetConfigPaths, saveConfig, configExists, getConfigPath } from "../config/persistence.js"
import { DEFAULT_CONFIG } from "../config/defaults.js"
import type { CLIConfig } from "../config/types.js"

// Mock fs/promises to handle schema.json reads
vi.mock("fs/promises", async () => {
Expand All @@ -28,13 +29,27 @@ vi.mock("fs/promises", async () => {
describe("Config Command", () => {
let testDir: string
let testConfigFile: string
let validConfig: CLIConfig

beforeEach(() => {
// Create a temporary directory for testing
testDir = join(tmpdir(), `kilocode-test-${Date.now()}`)
testConfigFile = join(testDir, "config.json")
mkdirSync(testDir, { recursive: true })
setConfigPaths(testDir, testConfigFile)

// Create a valid config with non-empty credentials
validConfig = {
...DEFAULT_CONFIG,
providers: [
{
id: "default",
provider: "kilocode",
kilocodeToken: "valid-token-1234567890",
kilocodeModel: "anthropic/claude-sonnet-4.5",
},
],
}
})

afterEach(() => {
Expand All @@ -49,17 +64,17 @@ describe("Config Command", () => {
// Verify config doesn't exist
expect(await configExists()).toBe(false)

// Save default config (simulating what the command does)
await saveConfig(DEFAULT_CONFIG)
// Save valid config (simulating what the command does)
await saveConfig(validConfig)

// Verify config was created
expect(await configExists()).toBe(true)
expect(existsSync(testConfigFile)).toBe(true)
})

it("should create config with correct default values", async () => {
// Save default config
await saveConfig(DEFAULT_CONFIG)
// Save valid config
await saveConfig(validConfig)

// Read and verify the config
const configContent = readFileSync(testConfigFile, "utf-8")
Expand All @@ -81,14 +96,14 @@ describe("Config Command", () => {

it("should detect existing config file", async () => {
// Create config
await saveConfig(DEFAULT_CONFIG)
await saveConfig(validConfig)

// Verify it's detected
expect(await configExists()).toBe(true)
})

it("should format config file with proper indentation", async () => {
await saveConfig(DEFAULT_CONFIG)
await saveConfig(validConfig)

const configContent = readFileSync(testConfigFile, "utf-8")

Expand Down
70 changes: 37 additions & 33 deletions 70 cli/src/config/__tests__/persistence-merge.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,18 +56,19 @@ describe("Config Persistence - Merge with Defaults", () => {
{
id: "default",
provider: "kilocode",
kilocodeToken: "",
kilocodeToken: "valid-token-1234567890",
kilocodeModel: "anthropic/claude-sonnet-4",
},
],
}

await fs.writeFile(testFile, JSON.stringify(partialConfig, null, 2))

const loaded = await loadConfig()
const result = await loadConfig()

expect(loaded.autoApproval).toBeDefined()
expect(loaded.autoApproval).toEqual(DEFAULT_AUTO_APPROVAL)
expect(result.config.autoApproval).toBeDefined()
expect(result.config.autoApproval).toEqual(DEFAULT_AUTO_APPROVAL)
expect(result.validation.valid).toBe(true)
})

it("should fill missing autoApproval nested keys with defaults", async () => {
Expand All @@ -81,7 +82,7 @@ describe("Config Persistence - Merge with Defaults", () => {
{
id: "default",
provider: "kilocode",
kilocodeToken: "",
kilocodeToken: "valid-token-1234567890",
kilocodeModel: "anthropic/claude-sonnet-4",
},
],
Expand All @@ -96,16 +97,17 @@ describe("Config Persistence - Merge with Defaults", () => {

await fs.writeFile(testFile, JSON.stringify(partialConfig, null, 2))

const loaded = await loadConfig()

expect(loaded.autoApproval?.enabled).toBe(true)
expect(loaded.autoApproval?.read?.enabled).toBe(false)
expect(loaded.autoApproval?.read?.outside).toBe(DEFAULT_AUTO_APPROVAL.read?.outside)
expect(loaded.autoApproval?.write).toEqual(DEFAULT_AUTO_APPROVAL.write)
expect(loaded.autoApproval?.browser).toEqual(DEFAULT_AUTO_APPROVAL.browser)
expect(loaded.autoApproval?.retry).toEqual(DEFAULT_AUTO_APPROVAL.retry)
expect(loaded.autoApproval?.execute).toEqual(DEFAULT_AUTO_APPROVAL.execute)
expect(loaded.autoApproval?.question).toEqual(DEFAULT_AUTO_APPROVAL.question)
const result = await loadConfig()

expect(result.config.autoApproval?.enabled).toBe(true)
expect(result.config.autoApproval?.read?.enabled).toBe(false)
expect(result.config.autoApproval?.read?.outside).toBe(DEFAULT_AUTO_APPROVAL.read?.outside)
expect(result.config.autoApproval?.write).toEqual(DEFAULT_AUTO_APPROVAL.write)
expect(result.config.autoApproval?.browser).toEqual(DEFAULT_AUTO_APPROVAL.browser)
expect(result.config.autoApproval?.retry).toEqual(DEFAULT_AUTO_APPROVAL.retry)
expect(result.config.autoApproval?.execute).toEqual(DEFAULT_AUTO_APPROVAL.execute)
expect(result.config.autoApproval?.question).toEqual(DEFAULT_AUTO_APPROVAL.question)
expect(result.validation.valid).toBe(true)
})

it("should preserve existing values while filling missing ones", async () => {
Expand All @@ -119,7 +121,7 @@ describe("Config Persistence - Merge with Defaults", () => {
{
id: "default",
provider: "kilocode",
kilocodeToken: "test-token",
kilocodeToken: "test-token-1234567890",
kilocodeModel: "anthropic/claude-sonnet-4",
},
],
Expand All @@ -135,20 +137,21 @@ describe("Config Persistence - Merge with Defaults", () => {

await fs.writeFile(testFile, JSON.stringify(partialConfig, null, 2))

const loaded = await loadConfig()
const result = await loadConfig()

// Custom values should be preserved
expect(loaded.mode).toBe("architect")
expect(loaded.telemetry).toBe(false)
expect(loaded.autoApproval?.enabled).toBe(true)
expect(loaded.autoApproval?.execute?.enabled).toBe(true)
expect(loaded.autoApproval?.execute?.allowed).toEqual(["npm", "git"])
expect(loaded.autoApproval?.execute?.denied).toEqual(["rm -rf"])
expect(result.config.mode).toBe("architect")
expect(result.config.telemetry).toBe(false)
expect(result.config.autoApproval?.enabled).toBe(true)
expect(result.config.autoApproval?.execute?.enabled).toBe(true)
expect(result.config.autoApproval?.execute?.allowed).toEqual(["npm", "git"])
expect(result.config.autoApproval?.execute?.denied).toEqual(["rm -rf"])

// Missing values should be filled with defaults
expect(loaded.autoApproval?.read).toEqual(DEFAULT_AUTO_APPROVAL.read)
expect(loaded.autoApproval?.write).toEqual(DEFAULT_AUTO_APPROVAL.write)
expect(loaded.autoApproval?.retry).toEqual(DEFAULT_AUTO_APPROVAL.retry)
expect(result.config.autoApproval?.read).toEqual(DEFAULT_AUTO_APPROVAL.read)
expect(result.config.autoApproval?.write).toEqual(DEFAULT_AUTO_APPROVAL.write)
expect(result.config.autoApproval?.retry).toEqual(DEFAULT_AUTO_APPROVAL.retry)
expect(result.validation.valid).toBe(true)
})

it("should save merged config back to file", async () => {
Expand All @@ -162,7 +165,7 @@ describe("Config Persistence - Merge with Defaults", () => {
{
id: "default",
provider: "kilocode",
kilocodeToken: "",
kilocodeToken: "valid-token-1234567890",
kilocodeModel: "anthropic/claude-sonnet-4",
},
],
Expand Down Expand Up @@ -193,7 +196,7 @@ describe("Config Persistence - Merge with Defaults", () => {
{
id: "default",
provider: "kilocode",
kilocodeToken: "",
kilocodeToken: "valid-token-1234567890",
kilocodeModel: "anthropic/claude-sonnet-4",
},
],
Expand All @@ -211,11 +214,12 @@ describe("Config Persistence - Merge with Defaults", () => {

await fs.writeFile(testFile, JSON.stringify(partialConfig, null, 2))

const loaded = await loadConfig()
const result = await loadConfig()

expect(loaded.autoApproval?.retry?.enabled).toBe(true)
expect(loaded.autoApproval?.retry?.delay).toBe(DEFAULT_AUTO_APPROVAL.retry?.delay)
expect(loaded.autoApproval?.question?.enabled).toBe(DEFAULT_AUTO_APPROVAL.question?.enabled)
expect(loaded.autoApproval?.question?.timeout).toBe(DEFAULT_AUTO_APPROVAL.question?.timeout)
expect(result.config.autoApproval?.retry?.enabled).toBe(true)
expect(result.config.autoApproval?.retry?.delay).toBe(DEFAULT_AUTO_APPROVAL.retry?.delay)
expect(result.config.autoApproval?.question?.enabled).toBe(DEFAULT_AUTO_APPROVAL.question?.enabled)
expect(result.config.autoApproval?.question?.timeout).toBe(DEFAULT_AUTO_APPROVAL.question?.timeout)
expect(result.validation.valid).toBe(true)
})
})
116 changes: 116 additions & 0 deletions 116 cli/src/config/__tests__/persistence-provider-merge.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"
import * as fs from "fs/promises"
import * as path from "path"
import { loadConfig, setConfigPaths, resetConfigPaths } from "../persistence.js"
import type { CLIConfig } from "../types.js"

// Mock the validation module
vi.mock("../validation.js", () => ({
validateConfig: vi.fn().mockResolvedValue({ valid: true }),
}))

describe("Provider Merging", () => {
const testDir = path.join(process.cwd(), "test-config-provider-merge")
const testFile = path.join(testDir, "config.json")

beforeEach(async () => {
await fs.mkdir(testDir, { recursive: true })
setConfigPaths(testDir, testFile)
})

afterEach(async () => {
resetConfigPaths()
await fs.rm(testDir, { recursive: true, force: true })
})

it("should merge provider field from defaults when missing in loaded config", async () => {
// Create a config file without the provider field in the provider object
const configWithoutProviderField = {
version: "1.0.0",
mode: "code",
telemetry: true,
provider: "default",
providers: [
{
id: "default",
// Missing 'provider' field
kilocodeToken: "test-token-1234567890",
kilocodeModel: "anthropic/claude-sonnet-4.5",
},
],
theme: "dark",
}

await fs.writeFile(testFile, JSON.stringify(configWithoutProviderField, null, 2))

// Load the config - it should merge with defaults
const result = await loadConfig()

// Check that the provider field was added from defaults
expect(result.config.providers[0]).toHaveProperty("provider")
expect(result.config.providers[0].provider).toBe("kilocode")
expect(result.config.providers[0].id).toBe("default")
expect(result.config.providers[0].kilocodeToken).toBe("test-token-1234567890")
expect(result.config.providers[0].kilocodeModel).toBe("anthropic/claude-sonnet-4.5")
expect(result.validation.valid).toBe(true)
})

it("should preserve provider field when present in loaded config", async () => {
// Create a config file with the provider field
const configWithProviderField: CLIConfig = {
version: "1.0.0",
mode: "code",
telemetry: true,
provider: "default",
providers: [
{
id: "default",
provider: "kilocode",
kilocodeToken: "test-token-1234567890",
kilocodeModel: "anthropic/claude-sonnet-4.5",
},
],
theme: "dark",
}

await fs.writeFile(testFile, JSON.stringify(configWithProviderField, null, 2))

// Load the config
const result = await loadConfig()

// Check that the provider field is preserved
expect(result.config.providers[0].provider).toBe("kilocode")
expect(result.config.providers[0].id).toBe("default")
expect(result.config.providers[0].kilocodeToken).toBe("test-token-1234567890")
expect(result.validation.valid).toBe(true)
})

it("should handle providers with different ids", async () => {
// Create a config with a provider that doesn't match default id
const configWithDifferentId = {
version: "1.0.0",
mode: "code",
telemetry: true,
provider: "custom",
providers: [
{
id: "custom",
// Missing 'provider' field
kilocodeToken: "test-token-1234567890",
kilocodeModel: "anthropic/claude-sonnet-4.5",
},
],
theme: "dark",
}

await fs.writeFile(testFile, JSON.stringify(configWithDifferentId, null, 2))

// Load the config
const result = await loadConfig()

// Since there's no matching default provider with id "custom",
// the provider field won't be added automatically
// This will be caught by validation
expect(result.config.providers[0].id).toBe("custom")
})
})
Loading
Loading
Morty Proxy This is a proxified and sanitized view of the page, visit original site.