/** * Tests for agent-path helpers and JSON file I/O. */ import * as fs from "node:fs"; import * as fsp from "node:fs/promises"; import * as os from "node:os"; import * as path from "node:path"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; // Mock pi-coding-agent before importing the module under test vi.mock("@mariozechner/pi-coding-agent", () => ({ getAgentDir: () => path.join(os.homedir(), ".pi", "agent"), })); import { ensureParentDir, getAgentAuthPath, getAgentPath, getAgentSettingsPath, readJsonObjectFile, readJsonObjectFileAsync, writeJsonObjectFile, writeJsonObjectFileAsync, } from "../agent-paths.js"; // Use a temp directory for file I/O tests let tmpDir: string; beforeEach(() => { tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-provider-utils-")); }); afterEach(() => { fs.rmSync(tmpDir, { recursive: true, force: true }); }); // --------------------------------------------------------------------------- // Path helpers // --------------------------------------------------------------------------- describe("getAgentPath", () => { it("returns the agent directory when called with no arguments", () => { const result = getAgentPath(); expect(result).toBe(path.join(os.homedir(), ".pi", "agent")); }); it("joins segments onto the agent directory", () => { const result = getAgentPath("settings.json"); expect(result).toBe( path.join(os.homedir(), ".pi", "agent", "settings.json"), ); }); it("joins multiple segments", () => { const result = getAgentPath("data", "vault.age.json"); expect(result).toBe( path.join(os.homedir(), ".pi", "agent", "data", "vault.age.json"), ); }); }); describe("getAgentSettingsPath", () => { it("returns the settings.json path", () => { expect(getAgentSettingsPath()).toBe( path.join(os.homedir(), ".pi", "agent", "settings.json"), ); }); }); describe("getAgentAuthPath", () => { it("returns the auth.json path", () => { expect(getAgentAuthPath()).toBe( path.join(os.homedir(), ".pi", "agent", "auth.json"), ); }); }); // --------------------------------------------------------------------------- // ensureParentDir // --------------------------------------------------------------------------- describe("ensureParentDir", () => { it("creates parent directories that do not exist", () => { const filePath = path.join(tmpDir, "a", "b", "file.json"); ensureParentDir(filePath); expect(fs.existsSync(path.join(tmpDir, "a", "b"))).toBe(true); }); it("is a no-op when the parent directory already exists", () => { const filePath = path.join(tmpDir, "file.json"); ensureParentDir(filePath); expect(fs.existsSync(tmpDir)).toBe(true); }); }); // --------------------------------------------------------------------------- // readJsonObjectFile (sync) // --------------------------------------------------------------------------- describe("readJsonObjectFile", () => { it("returns empty object for nonexistent file", () => { const result = readJsonObjectFile(path.join(tmpDir, "missing.json")); expect(result).toEqual({}); }); it("reads a valid JSON object file", () => { const filePath = path.join(tmpDir, "test.json"); fs.writeFileSync(filePath, JSON.stringify({ key: "value" })); const result = readJsonObjectFile(filePath); expect(result).toEqual({ key: "value" }); }); it("returns empty object for a JSON array file", () => { const filePath = path.join(tmpDir, "array.json"); fs.writeFileSync(filePath, JSON.stringify([1, 2, 3])); const result = readJsonObjectFile(filePath); expect(result).toEqual({}); }); it("returns empty object for invalid JSON", () => { const filePath = path.join(tmpDir, "bad.json"); fs.writeFileSync(filePath, "not json at all"); const result = readJsonObjectFile(filePath); expect(result).toEqual({}); }); it("returns empty object for a JSON null file", () => { const filePath = path.join(tmpDir, "null.json"); fs.writeFileSync(filePath, "null"); const result = readJsonObjectFile(filePath); expect(result).toEqual({}); }); it("preserves nested objects", () => { const data = { outer: { inner: 42 }, list: [1, 2] }; const filePath = path.join(tmpDir, "nested.json"); fs.writeFileSync(filePath, JSON.stringify(data)); const result = readJsonObjectFile(filePath); expect(result).toEqual(data); }); }); // --------------------------------------------------------------------------- // writeJsonObjectFile (sync) // --------------------------------------------------------------------------- describe("writeJsonObjectFile", () => { it("writes a JSON object with 2-space indentation", () => { const filePath = path.join(tmpDir, "out.json"); writeJsonObjectFile(filePath, { hello: "world" }); const raw = fs.readFileSync(filePath, "utf-8"); expect(raw).toBe(JSON.stringify({ hello: "world" }, null, 2)); }); it("creates parent directories when needed", () => { const filePath = path.join(tmpDir, "deep", "nested", "out.json"); writeJsonObjectFile(filePath, { created: true }); expect(fs.existsSync(filePath)).toBe(true); const parsed = JSON.parse(fs.readFileSync(filePath, "utf-8")) as Record< string, unknown >; expect(parsed).toEqual({ created: true }); }); it("overwrites an existing file", () => { const filePath = path.join(tmpDir, "overwrite.json"); fs.writeFileSync(filePath, JSON.stringify({ old: true })); writeJsonObjectFile(filePath, { new: true }); const parsed = JSON.parse(fs.readFileSync(filePath, "utf-8")) as Record< string, unknown >; expect(parsed).toEqual({ new: true }); }); }); // --------------------------------------------------------------------------- // readJsonObjectFileAsync // --------------------------------------------------------------------------- describe("readJsonObjectFileAsync", () => { it("returns empty object for nonexistent file", async () => { const result = await readJsonObjectFileAsync( path.join(tmpDir, "missing.json"), ); expect(result).toEqual({}); }); it("reads a valid JSON object file", async () => { const filePath = path.join(tmpDir, "test.json"); await fsp.writeFile(filePath, JSON.stringify({ async: true })); const result = await readJsonObjectFileAsync(filePath); expect(result).toEqual({ async: true }); }); it("returns empty object for a JSON array file", async () => { const filePath = path.join(tmpDir, "array.json"); await fsp.writeFile(filePath, JSON.stringify([1, 2])); const result = await readJsonObjectFileAsync(filePath); expect(result).toEqual({}); }); }); // --------------------------------------------------------------------------- // writeJsonObjectFileAsync // --------------------------------------------------------------------------- describe("writeJsonObjectFileAsync", () => { it("writes a JSON object with 2-space indentation", async () => { const filePath = path.join(tmpDir, "async-out.json"); await writeJsonObjectFileAsync(filePath, { async: "write" }); const raw = await fsp.readFile(filePath, "utf-8"); expect(raw).toBe(JSON.stringify({ async: "write" }, null, 2)); }); it("creates parent directories when needed", async () => { const filePath = path.join(tmpDir, "deep", "async", "out.json"); await writeJsonObjectFileAsync(filePath, { deep: true }); const exists = await fsp .stat(filePath) .then(() => true) .catch(() => false); expect(exists).toBe(true); }); });