Files
oh-my-pi/packages/coding-agent/test/config-cli.test.ts
T
oldschoola 14252e71cb fix: Windows test failures — path handling, EBUSY, SQLite handle leaks
Fix all Windows-specific test failures caused by path handling problems
and EBUSY errors from unclosed SQLite database handles.

Root causes fixed:
1. POSIX path assumptions: replaced hard-coded file:///tmp, /repo, etc.
   with pathToFileURL/path.resolve/path.join computed expectations
2. shortenPath() now normalizes backslashes to forward slashes after ~
   and respects home directory boundaries
3. HistoryStorage.resetInstance() leaked its Database — added #close()
   that finalizes all prepared statements and closes the DB
4. AgentStorage gained the same resetInstance()/#close() pattern
5. SqliteAuthCredentialStore.close() leaked one-off prepared statements
   from inline this.#db.prepare() calls — wrapped each in try/finally
6. model-cache.ts used a process-global DB even for custom dbPath —
   now opens/closes per-call via withModelCacheDb
7. createAgentSession leaked AuthStorage on construction failure —
   added ownsAuthStorage cleanup in catch block
8. MnemopiBackend.removeDbFiles() now truly best-effort (catches errors)
9. TempDir retry window expanded from 4x10ms to 40x25ms
10. TempDir prefix convention: non-@ prefixes created dirs relative to
    cwd instead of os.tmpdir() — all test temp dirs now use @ prefix
11. Shell-escaped interpolated paths in bash tool tests
12. git core.autocrlf false in autoresearch test repo init

All 522 previously-failing Windows tests now pass.
2026-06-18 21:32:38 -07:00

130 lines
5.1 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it, vi } from "bun:test";
import * as path from "node:path";
import { runConfigCommand } from "@oh-my-pi/pi-coding-agent/cli/config-cli";
import { resetSettingsForTest } from "@oh-my-pi/pi-coding-agent/config/settings";
import { AgentStorage } from "@oh-my-pi/pi-coding-agent/session/agent-storage";
import { getConfigRootDir, setAgentDir, TempDir } from "@oh-my-pi/pi-utils";
let testAgentDir: TempDir | undefined;
const originalAgentDir = process.env.PI_CODING_AGENT_DIR;
const fallbackAgentDir = path.join(getConfigRootDir(), "agent");
beforeEach(() => {
resetSettingsForTest();
testAgentDir = TempDir.createSync("omp-config-cli-");
setAgentDir(testAgentDir.path());
});
afterEach(async () => {
vi.restoreAllMocks();
AgentStorage.resetInstance();
resetSettingsForTest();
if (originalAgentDir) {
setAgentDir(originalAgentDir);
} else {
setAgentDir(fallbackAgentDir);
delete process.env.PI_CODING_AGENT_DIR;
}
if (testAgentDir) {
try {
await testAgentDir.remove();
} catch {}
testAgentDir = undefined;
}
});
describe("config CLI schema coverage", () => {
it("renders record settings as JSON and with record type in text output", async () => {
const logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
await runConfigCommand({ action: "list", flags: {} });
const lines = logSpy.mock.calls.map(call => String(call[0] ?? ""));
const plainLines = lines.map(line => Bun.stripANSI(line));
const modelRolesLine = plainLines.find(line => line.includes("modelRoles ="));
expect(modelRolesLine).toBeDefined();
const plainModelRolesLine = String(modelRolesLine);
expect(plainModelRolesLine).toContain("modelRoles =");
expect(plainModelRolesLine).toContain("(record)");
expect(plainModelRolesLine).toContain("{");
expect(plainModelRolesLine).toContain("}");
expect(plainModelRolesLine).not.toContain("[object Object]");
});
it("sets and gets record settings as JSON objects", async () => {
const logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
const recordValue = '{"default":"claude-opus-4-6"}';
await runConfigCommand({ action: "set", key: "modelRoles", value: recordValue, flags: { json: true } });
await runConfigCommand({ action: "get", key: "modelRoles", flags: { json: true } });
const payload = logSpy.mock.calls.at(-1)?.[0];
expect(typeof payload).toBe("string");
const parsed = JSON.parse(String(payload)) as { key: string; value: unknown; type: string };
expect(parsed.key).toBe("modelRoles");
expect(parsed.type).toBe("record");
expect(parsed.value).toEqual({ default: "claude-opus-4-6" });
});
it("sets and gets array settings as JSON arrays", async () => {
const logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
const arrayValue = '["claude-opus-4-6","gpt-5.3-codex"]';
await runConfigCommand({ action: "set", key: "enabledModels", value: arrayValue, flags: { json: true } });
await runConfigCommand({ action: "get", key: "enabledModels", flags: { json: true } });
const payload = logSpy.mock.calls.at(-1)?.[0];
expect(typeof payload).toBe("string");
const parsed = JSON.parse(String(payload)) as { key: string; value: unknown; type: string };
expect(parsed.key).toBe("enabledModels");
expect(parsed.type).toBe("array");
expect(parsed.value).toEqual(["claude-opus-4-6", "gpt-5.3-codex"]);
});
it("sets numeric idle compaction settings from CLI values", async () => {
const logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
await runConfigCommand({
action: "set",
key: "compaction.idleThresholdTokens",
value: "300000",
flags: { json: true },
});
await runConfigCommand({
action: "set",
key: "compaction.idleTimeoutSeconds",
value: "600",
flags: { json: true },
});
await runConfigCommand({ action: "get", key: "compaction.idleThresholdTokens", flags: { json: true } });
await runConfigCommand({ action: "get", key: "compaction.idleTimeoutSeconds", flags: { json: true } });
const thresholdPayload = logSpy.mock.calls.at(-2)?.[0];
const timeoutPayload = logSpy.mock.calls.at(-1)?.[0];
expect(typeof thresholdPayload).toBe("string");
expect(typeof timeoutPayload).toBe("string");
expect(JSON.parse(String(thresholdPayload))).toMatchObject({
key: "compaction.idleThresholdTokens",
type: "number",
value: 300000,
});
expect(JSON.parse(String(timeoutPayload))).toMatchObject({
key: "compaction.idleTimeoutSeconds",
type: "number",
value: 600,
});
});
it("accepts max as a persisted default thinking level", async () => {
const logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
await runConfigCommand({ action: "set", key: "defaultThinkingLevel", value: "max", flags: { json: true } });
await runConfigCommand({ action: "get", key: "defaultThinkingLevel", flags: { json: true } });
const payload = logSpy.mock.calls.at(-1)?.[0];
expect(typeof payload).toBe("string");
const parsed = JSON.parse(String(payload)) as { key: string; value: unknown; type: string };
expect(parsed.key).toBe("defaultThinkingLevel");
expect(parsed.type).toBe("enum");
expect(parsed.value).toBe("max");
});
});