co-mono/packages/coding-agent/test/settings-manager-bug.test.ts
Nico Bailon bac57f81be
fix: preserve external settings.json edits on reload (#1046)
Co-authored-by: Mario Zechner <badlogicgames@gmail.com>
2026-01-29 02:42:23 +01:00

99 lines
3.4 KiB
TypeScript

import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
import { join } from "path";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { SettingsManager } from "../src/core/settings-manager.js";
/**
* Tests for the fix to a bug where external file changes to arrays were overwritten.
*
* The bug scenario was:
* 1. Pi starts with settings.json containing packages: ["npm:some-pkg"]
* 2. User externally edits file to packages: []
* 3. User changes an unrelated setting (e.g., theme) via UI
* 4. save() would overwrite packages back to ["npm:some-pkg"] from stale in-memory state
*
* The fix tracks which fields were explicitly modified during the session, and only
* those fields override file values during save().
*/
describe("SettingsManager - External Edit Preservation", () => {
const testDir = join(process.cwd(), "test-settings-bug-tmp");
const agentDir = join(testDir, "agent");
const projectDir = join(testDir, "project");
beforeEach(() => {
if (existsSync(testDir)) {
rmSync(testDir, { recursive: true });
}
mkdirSync(agentDir, { recursive: true });
mkdirSync(join(projectDir, ".pi"), { recursive: true });
});
afterEach(() => {
if (existsSync(testDir)) {
rmSync(testDir, { recursive: true });
}
});
it("should preserve file changes to packages array when changing unrelated setting", () => {
const settingsPath = join(agentDir, "settings.json");
// Initial state: packages has one item
writeFileSync(
settingsPath,
JSON.stringify({
theme: "dark",
packages: ["npm:pi-mcp-adapter"],
}),
);
// Pi starts up, loads settings into memory
const manager = SettingsManager.create(projectDir, agentDir);
// At this point, globalSettings.packages = ["npm:pi-mcp-adapter"]
expect(manager.getPackages()).toEqual(["npm:pi-mcp-adapter"]);
// User externally edits settings.json to remove the package
const currentSettings = JSON.parse(readFileSync(settingsPath, "utf-8"));
currentSettings.packages = []; // User wants to remove this!
writeFileSync(settingsPath, JSON.stringify(currentSettings, null, 2));
// Verify file was changed
expect(JSON.parse(readFileSync(settingsPath, "utf-8")).packages).toEqual([]);
// User changes an UNRELATED setting via UI (this triggers save)
manager.setTheme("light");
// With the fix, packages should be preserved as [] (not reverted to startup value)
const savedSettings = JSON.parse(readFileSync(settingsPath, "utf-8"));
expect(savedSettings.packages).toEqual([]);
expect(savedSettings.theme).toBe("light");
});
it("should preserve file changes to extensions array when changing unrelated setting", () => {
const settingsPath = join(agentDir, "settings.json");
writeFileSync(
settingsPath,
JSON.stringify({
theme: "dark",
extensions: ["/old/extension.ts"],
}),
);
const manager = SettingsManager.create(projectDir, agentDir);
// User externally updates extensions
const currentSettings = JSON.parse(readFileSync(settingsPath, "utf-8"));
currentSettings.extensions = ["/new/extension.ts"];
writeFileSync(settingsPath, JSON.stringify(currentSettings, null, 2));
// Change unrelated setting
manager.setDefaultThinkingLevel("high");
const savedSettings = JSON.parse(readFileSync(settingsPath, "utf-8"));
// With the fix, extensions should be preserved (not reverted to startup value)
expect(savedSettings.extensions).toEqual(["/new/extension.ts"]);
});
});