import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; import type { Dirent } from "node:fs"; // Mock node:fs — all fs operations are controlled by tests vi.mock("node:fs", () => ({ existsSync: vi.fn(() => false), readFileSync: vi.fn(() => ""), readdirSync: vi.fn(() => []), writeFileSync: vi.fn(), mkdirSync: vi.fn(), })); // Mock node:child_process vi.mock("node:child_process", () => ({ execSync: vi.fn(() => ""), exec: vi.fn((_cmd: string, _opts: unknown, cb: (err: Error | null, result: { stdout: string }) => void) => { cb(null, { stdout: "" }); }), })); // Mock node:os vi.mock("node:os", () => ({ homedir: vi.fn(() => "/home/testuser"), })); import { existsSync, readFileSync, readdirSync } from "node:fs"; import { execSync } from "node:child_process"; import { join } from "node:path"; const _mockExistsSync = vi.mocked(existsSync); const _mockReadFileSync = vi.mocked(readFileSync); const _mockReaddirSync = vi.mocked(readdirSync); const _mockExecSync = vi.mocked(execSync); /** Helper to create mock Dirent entries. */ function makeDirent(name: string, isDir: boolean): Dirent { return { name, isDirectory: () => isDir, isFile: () => !isDir, isBlockDevice: () => false, isCharacterDevice: () => false, isFIFO: () => false, isSocket: () => false, isSymbolicLink: () => false, path: "", parentPath: "", } as Dirent; } describe("workspace utilities", () => { const originalEnv = { ...process.env }; const STATE_DIR = join("/home/testuser", ".openclaw-ironclaw"); const WS_DIR = join(STATE_DIR, "workspace-test"); beforeEach(() => { vi.resetModules(); vi.restoreAllMocks(); process.env = { ...originalEnv }; // Re-wire mocks after resetModules vi.mock("node:fs", () => ({ existsSync: vi.fn(() => false), readFileSync: vi.fn(() => ""), readdirSync: vi.fn(() => []), writeFileSync: vi.fn(), mkdirSync: vi.fn(), })); vi.mock("node:child_process", () => ({ execSync: vi.fn(() => ""), exec: vi.fn((_cmd: string, _opts: unknown, cb: (err: Error | null, result: { stdout: string }) => void) => { cb(null, { stdout: "" }); }), })); vi.mock("node:os", () => ({ homedir: vi.fn(() => "/home/testuser"), })); }); afterEach(() => { process.env = originalEnv; }); /** Fresh import after mocks are wired. */ async function importWorkspace() { const { existsSync: es, readFileSync: rfs, readdirSync: rds } = await import("node:fs"); const { execSync: exs } = await import("node:child_process"); const mod = await import("./workspace.js"); return { ...mod, mockExists: vi.mocked(es), mockReadFile: vi.mocked(rfs), mockReaddir: vi.mocked(rds), mockExec: vi.mocked(exs), }; } // ─── resolveWorkspaceRoot ──────────────────────────────────────── describe("resolveWorkspaceRoot", () => { it("returns OPENCLAW_WORKSPACE env var when set and exists", async () => { process.env.OPENCLAW_WORKSPACE = WS_DIR; const { resolveWorkspaceRoot, mockExists } = await importWorkspace(); mockExists.mockImplementation((p) => String(p) === WS_DIR); expect(resolveWorkspaceRoot()).toBe(WS_DIR); }); it("returns discovered workspace when env not set", async () => { delete process.env.OPENCLAW_WORKSPACE; const { resolveWorkspaceRoot, mockExists, mockReaddir } = await importWorkspace(); mockReaddir.mockImplementation((dir, _opts) => { if (String(dir) === STATE_DIR) { return [makeDirent("workspace-test", true)] as unknown as Dirent[]; } return [] as unknown as Dirent[]; }); mockExists.mockImplementation((p) => String(p) === WS_DIR); expect(resolveWorkspaceRoot()).toBe(WS_DIR); }); it("returns null when no candidate directory exists", async () => { delete process.env.OPENCLAW_WORKSPACE; const { resolveWorkspaceRoot, mockExists } = await importWorkspace(); mockExists.mockReturnValue(false); expect(resolveWorkspaceRoot()).toBeNull(); }); it("prefers OPENCLAW_WORKSPACE over discovered workspace", async () => { const envWs = join(STATE_DIR, "workspace-fromenv"); process.env.OPENCLAW_WORKSPACE = envWs; const { resolveWorkspaceRoot, mockExists, mockReaddir } = await importWorkspace(); mockReaddir.mockImplementation((dir, _opts) => { if (String(dir) === STATE_DIR) { return [ makeDirent("workspace-fromenv", true), makeDirent("workspace-other", true), ] as unknown as Dirent[]; } return [] as unknown as Dirent[]; }); mockExists.mockReturnValue(true); expect(resolveWorkspaceRoot()).toBe(envWs); }); it("falls back to discovered workspace when env var path does not exist", async () => { process.env.OPENCLAW_WORKSPACE = join(STATE_DIR, "workspace-nonexistent"); const { resolveWorkspaceRoot, mockExists, mockReaddir } = await importWorkspace(); const fallbackWs = join(STATE_DIR, "workspace-fallback"); mockReaddir.mockImplementation((dir, _opts) => { if (String(dir) === STATE_DIR) { return [makeDirent("workspace-fallback", true)] as unknown as Dirent[]; } return [] as unknown as Dirent[]; }); mockExists.mockImplementation((p) => String(p) === fallbackWs); expect(resolveWorkspaceRoot()).toBe(fallbackWs); }); it("resolves bootstrap root workspace as ironclaw default", async () => { delete process.env.OPENCLAW_WORKSPACE; const { resolveWorkspaceRoot, mockExists, mockReaddir } = await importWorkspace(); const rootWorkspace = join(STATE_DIR, "workspace"); mockReaddir.mockImplementation((dir, _opts) => { if (String(dir) === STATE_DIR) { return [makeDirent("workspace", true)] as unknown as Dirent[]; } return [] as unknown as Dirent[]; }); mockExists.mockImplementation((p) => String(p) === rootWorkspace); expect(resolveWorkspaceRoot()).toBe(rootWorkspace); }); }); // ─── resolveWebChatDir ──────────────────────────────────────────── describe("resolveWebChatDir", () => { it("falls back to root workspace chat dir for ironclaw default", async () => { delete process.env.OPENCLAW_WORKSPACE; const { resolveWebChatDir, mockReadFile, mockReaddir } = await importWorkspace(); mockReadFile.mockImplementation(() => { throw new Error("ENOENT"); }); mockReaddir.mockReturnValue([] as unknown as Dirent[]); expect(resolveWebChatDir()).toBe( join(STATE_DIR, "workspace", ".openclaw", "web-chat"), ); }); }); // ─── resolveAgentWorkspacePrefix ───────────────────────────────── describe("resolveAgentWorkspacePrefix", () => { it("returns null when no workspace root", async () => { delete process.env.OPENCLAW_WORKSPACE; const { resolveAgentWorkspacePrefix, mockExists } = await importWorkspace(); mockExists.mockReturnValue(false); expect(resolveAgentWorkspacePrefix()).toBeNull(); }); it("returns absolute path when workspace is outside repo", async () => { process.env.OPENCLAW_WORKSPACE = WS_DIR; const { resolveAgentWorkspacePrefix, mockExists } = await importWorkspace(); mockExists.mockImplementation((p) => String(p) === WS_DIR); vi.spyOn(process, "cwd").mockReturnValue("/repo/apps/web"); expect(resolveAgentWorkspacePrefix()).toBe(WS_DIR); }); it("returns relative path when workspace is inside repo", async () => { const repoWs = join(STATE_DIR, "workspace-test"); process.env.OPENCLAW_WORKSPACE = repoWs; const { resolveAgentWorkspacePrefix, mockExists } = await importWorkspace(); mockExists.mockImplementation((p) => String(p) === repoWs); vi.spyOn(process, "cwd").mockReturnValue(STATE_DIR); expect(resolveAgentWorkspacePrefix()).toBe("workspace-test"); }); it("handles non apps/web cwd", async () => { const repoWs = join(STATE_DIR, "workspace-test"); process.env.OPENCLAW_WORKSPACE = repoWs; const { resolveAgentWorkspacePrefix, mockExists } = await importWorkspace(); mockExists.mockImplementation((p) => String(p) === repoWs); vi.spyOn(process, "cwd").mockReturnValue(STATE_DIR); expect(resolveAgentWorkspacePrefix()).toBe("workspace-test"); }); }); // ─── discoverDuckDBPaths ────────────────────────────────────────── describe("discoverDuckDBPaths", () => { it("returns empty array when root is null", async () => { delete process.env.OPENCLAW_WORKSPACE; const { discoverDuckDBPaths, mockExists } = await importWorkspace(); mockExists.mockReturnValue(false); expect(discoverDuckDBPaths()).toEqual([]); }); it("returns empty when root has no duckdb files", async () => { const { discoverDuckDBPaths, mockExists, mockReaddir } = await importWorkspace(); mockExists.mockReturnValue(false); mockReaddir.mockReturnValue([]); expect(discoverDuckDBPaths("/ws")).toEqual([]); }); it("discovers root-level workspace.duckdb", async () => { const { discoverDuckDBPaths, mockExists, mockReaddir } = await importWorkspace(); mockExists.mockImplementation((p) => String(p) === join("/ws", "workspace.duckdb")); mockReaddir.mockReturnValue([]); expect(discoverDuckDBPaths("/ws")).toEqual([join("/ws", "workspace.duckdb")]); }); it("discovers nested workspace.duckdb files sorted by depth", async () => { const { discoverDuckDBPaths, mockExists, mockReaddir } = await importWorkspace(); mockExists.mockImplementation((p) => { const s = String(p); return s === join("/ws", "workspace.duckdb") || s === join("/ws", "sub", "workspace.duckdb"); }); mockReaddir.mockImplementation((dir) => { if (String(dir) === "/ws") { return [makeDirent("sub", true)] as unknown as Dirent[]; } return [] as unknown as Dirent[]; }); const result = discoverDuckDBPaths("/ws"); expect(result).toEqual([ join("/ws", "workspace.duckdb"), join("/ws", "sub", "workspace.duckdb"), ]); }); it("skips hidden directories", async () => { const { discoverDuckDBPaths, mockExists, mockReaddir } = await importWorkspace(); mockExists.mockImplementation((p) => String(p) === join("/ws", ".hidden", "workspace.duckdb"), ); mockReaddir.mockImplementation((dir) => { if (String(dir) === "/ws") { return [makeDirent(".hidden", true)] as unknown as Dirent[]; } return [] as unknown as Dirent[]; }); expect(discoverDuckDBPaths("/ws")).toEqual([]); }); it("skips tmp, exports, and node_modules directories", async () => { const { discoverDuckDBPaths, mockExists, mockReaddir } = await importWorkspace(); mockExists.mockReturnValue(false); mockReaddir.mockImplementation((dir) => { if (String(dir) === "/ws") { return [ makeDirent("tmp", true), makeDirent("exports", true), makeDirent("node_modules", true), ] as unknown as Dirent[]; } return [] as unknown as Dirent[]; }); expect(discoverDuckDBPaths("/ws")).toEqual([]); }); it("handles unreadable directories gracefully", async () => { const { discoverDuckDBPaths, mockExists, mockReaddir } = await importWorkspace(); mockExists.mockReturnValue(false); mockReaddir.mockImplementation(() => { throw new Error("EACCES"); }); expect(discoverDuckDBPaths("/ws")).toEqual([]); }); it("skips non-directory entries", async () => { const { discoverDuckDBPaths, mockExists, mockReaddir } = await importWorkspace(); mockExists.mockReturnValue(false); mockReaddir.mockImplementation((dir) => { if (String(dir) === "/ws") { return [makeDirent("somefile.txt", false)] as unknown as Dirent[]; } return [] as unknown as Dirent[]; }); expect(discoverDuckDBPaths("/ws")).toEqual([]); }); }); // ─── duckdbPath ────────────────────────────────────────────────── describe("duckdbPath", () => { it("returns root-level workspace.duckdb when it exists", async () => { process.env.OPENCLAW_WORKSPACE = WS_DIR; const { duckdbPath, mockExists, mockReaddir } = await importWorkspace(); const rootDb = join(WS_DIR, "workspace.duckdb"); mockExists.mockImplementation((p) => { const s = String(p); return s === WS_DIR || s === rootDb; }); mockReaddir.mockReturnValue([]); expect(duckdbPath()).toBe(rootDb); }); it("falls back to discovered nested db when root has none", async () => { process.env.OPENCLAW_WORKSPACE = WS_DIR; const { duckdbPath, mockExists, mockReaddir } = await importWorkspace(); const nestedDb = join(WS_DIR, "sub", "workspace.duckdb"); mockExists.mockImplementation((p) => { const s = String(p); return s === WS_DIR || s === nestedDb; }); mockReaddir.mockImplementation((dir) => { if (String(dir) === WS_DIR) { return [makeDirent("sub", true)] as unknown as Dirent[]; } return [] as unknown as Dirent[]; }); expect(duckdbPath()).toBe(nestedDb); }); it("returns null when no workspace root", async () => { delete process.env.OPENCLAW_WORKSPACE; const { duckdbPath, mockExists } = await importWorkspace(); mockExists.mockReturnValue(false); expect(duckdbPath()).toBeNull(); }); it("returns null when workspace exists but no duckdb files", async () => { process.env.OPENCLAW_WORKSPACE = WS_DIR; const { duckdbPath, mockExists, mockReaddir } = await importWorkspace(); mockExists.mockImplementation((p) => String(p) === WS_DIR); mockReaddir.mockReturnValue([]); expect(duckdbPath()).toBeNull(); }); }); // ─── duckdbRelativeScope ───────────────────────────────────────── describe("duckdbRelativeScope", () => { it("returns empty string for root-level db", async () => { process.env.OPENCLAW_WORKSPACE = WS_DIR; const { duckdbRelativeScope, mockExists } = await importWorkspace(); mockExists.mockImplementation((p) => String(p) === WS_DIR); expect(duckdbRelativeScope(join(WS_DIR, "workspace.duckdb"))).toBe(""); }); it("returns relative path for nested db", async () => { process.env.OPENCLAW_WORKSPACE = WS_DIR; const { duckdbRelativeScope, mockExists } = await importWorkspace(); mockExists.mockImplementation((p) => String(p) === WS_DIR); expect(duckdbRelativeScope(join(WS_DIR, "sub", "deep", "workspace.duckdb"))).toBe(join("sub", "deep")); }); it("returns empty string when no workspace root", async () => { delete process.env.OPENCLAW_WORKSPACE; const { duckdbRelativeScope, mockExists } = await importWorkspace(); mockExists.mockReturnValue(false); expect(duckdbRelativeScope("/any/workspace.duckdb")).toBe(""); }); }); // ─── resolveDuckdbBin ──────────────────────────────────────────── describe("resolveDuckdbBin", () => { it("finds user-local duckdb install", async () => { const { resolveDuckdbBin, mockExists } = await importWorkspace(); const expected = join("/home/testuser", ".duckdb", "cli", "latest", "duckdb"); mockExists.mockImplementation((p) => String(p) === expected); expect(resolveDuckdbBin()).toBe(expected); }); it("finds homebrew install", async () => { const { resolveDuckdbBin, mockExists } = await importWorkspace(); mockExists.mockImplementation((p) => String(p) === "/opt/homebrew/bin/duckdb"); expect(resolveDuckdbBin()).toBe("/opt/homebrew/bin/duckdb"); }); it("falls back to which duckdb", async () => { const { resolveDuckdbBin, mockExists, mockExec } = await importWorkspace(); mockExists.mockReturnValue(false); mockExec.mockReturnValue("/usr/local/bin/duckdb\n" as never); expect(resolveDuckdbBin()).toBe("duckdb"); }); it("returns null when nothing found", async () => { const { resolveDuckdbBin, mockExists, mockExec } = await importWorkspace(); mockExists.mockReturnValue(false); mockExec.mockImplementation(() => { throw new Error("not found"); }); expect(resolveDuckdbBin()).toBeNull(); }); it("checks user-local before homebrew", async () => { const { resolveDuckdbBin, mockExists } = await importWorkspace(); const userLocal = join("/home/testuser", ".duckdb", "cli", "latest", "duckdb"); mockExists.mockImplementation((p) => { const s = String(p); return s === userLocal || s === "/opt/homebrew/bin/duckdb"; }); expect(resolveDuckdbBin()).toBe(userLocal); }); }); // ─── duckdbQuery ───────────────────────────────────────────────── describe("duckdbQuery", () => { it("returns parsed JSON rows on success", async () => { process.env.OPENCLAW_WORKSPACE = WS_DIR; const { duckdbQuery, mockExists, mockExec } = await importWorkspace(); const rootDb = join(WS_DIR, "workspace.duckdb"); const bin = "/opt/homebrew/bin/duckdb"; mockExists.mockImplementation((p) => { const s = String(p); return s === WS_DIR || s === rootDb || s === bin; }); mockExec.mockReturnValue('[{"id":"1","name":"test"}]' as never); const result = duckdbQuery("SELECT * FROM objects"); expect(result).toEqual([{ id: "1", name: "test" }]); }); it("returns empty array for empty result", async () => { process.env.OPENCLAW_WORKSPACE = WS_DIR; const { duckdbQuery, mockExists, mockExec } = await importWorkspace(); mockExists.mockReturnValue(true); mockExec.mockReturnValue("[]" as never); expect(duckdbQuery("SELECT * FROM empty")).toEqual([]); }); it("returns empty array when no db", async () => { delete process.env.OPENCLAW_WORKSPACE; const { duckdbQuery, mockExists } = await importWorkspace(); mockExists.mockReturnValue(false); expect(duckdbQuery("SELECT 1")).toEqual([]); }); it("returns empty array on execSync error", async () => { process.env.OPENCLAW_WORKSPACE = WS_DIR; const { duckdbQuery, mockExists, mockExec } = await importWorkspace(); mockExists.mockReturnValue(true); mockExec.mockImplementation(() => { throw new Error("query failed"); }); expect(duckdbQuery("BAD SQL")).toEqual([]); }); }); // ─── duckdbQueryAsync ──────────────────────────────────────────── describe("duckdbQueryAsync", () => { it("returns parsed JSON rows on success", async () => { process.env.OPENCLAW_WORKSPACE = WS_DIR; const { duckdbQueryAsync, mockExists } = await importWorkspace(); const { exec: mockExecFn } = await import("node:child_process"); const rootDb = join(WS_DIR, "workspace.duckdb"); const bin = "/opt/homebrew/bin/duckdb"; mockExists.mockImplementation((p) => { const s = String(p); return s === WS_DIR || s === rootDb || s === bin; }); vi.mocked(mockExecFn).mockImplementation((_cmd: unknown, _opts: unknown, cb: unknown) => { (cb as (err: null, r: { stdout: string }) => void)(null, { stdout: '[{"id":"1"}]' }); return {} as never; }); const result = await duckdbQueryAsync("SELECT * FROM t"); expect(result).toEqual([{ id: "1" }]); }); it("returns empty array when no db path", async () => { delete process.env.OPENCLAW_WORKSPACE; const { duckdbQueryAsync, mockExists } = await importWorkspace(); mockExists.mockReturnValue(false); const result = await duckdbQueryAsync("SELECT 1"); expect(result).toEqual([]); }); it("returns empty array for empty stdout", async () => { process.env.OPENCLAW_WORKSPACE = WS_DIR; const { duckdbQueryAsync, mockExists } = await importWorkspace(); const { exec: mockExecFn } = await import("node:child_process"); mockExists.mockReturnValue(true); vi.mocked(mockExecFn).mockImplementation((_cmd: unknown, _opts: unknown, cb: unknown) => { (cb as (err: null, r: { stdout: string }) => void)(null, { stdout: "" }); return {} as never; }); const result = await duckdbQueryAsync("SELECT 1"); expect(result).toEqual([]); }); it("returns empty array on exec error", async () => { process.env.OPENCLAW_WORKSPACE = WS_DIR; const { duckdbQueryAsync, mockExists } = await importWorkspace(); const { exec: mockExecFn } = await import("node:child_process"); mockExists.mockReturnValue(true); vi.mocked(mockExecFn).mockImplementation((_cmd: unknown, _opts: unknown, cb: unknown) => { (cb as (err: Error) => void)(new Error("fail")); return {} as never; }); const result = await duckdbQueryAsync("BAD SQL"); expect(result).toEqual([]); }); }); // ─── duckdbQueryAll ────────────────────────────────────────────── describe("duckdbQueryAll", () => { it("merges results from multiple databases", async () => { process.env.OPENCLAW_WORKSPACE = WS_DIR; const { duckdbQueryAll, mockExists, mockExec, mockReaddir } = await importWorkspace(); const rootDb = join(WS_DIR, "workspace.duckdb"); const subDb = join(WS_DIR, "sub", "workspace.duckdb"); const bin = "/opt/homebrew/bin/duckdb"; mockExists.mockImplementation((p) => { const s = String(p); return s === WS_DIR || s === rootDb || s === subDb || s === bin; }); mockReaddir.mockImplementation((dir) => { if (String(dir) === WS_DIR) { return [makeDirent("sub", true)] as unknown as Dirent[]; } return [] as unknown as Dirent[]; }); let callCount = 0; mockExec.mockImplementation(() => { callCount++; if (callCount <= 1) {return '[{"name":"rootObj"}]' as never;} return '[{"name":"subObj"}]' as never; }); const result = duckdbQueryAll<{ name: string }>("SELECT * FROM objects"); expect(result).toEqual([{ name: "rootObj" }, { name: "subObj" }]); }); it("deduplicates by key (shallower wins)", async () => { process.env.OPENCLAW_WORKSPACE = WS_DIR; const { duckdbQueryAll, mockExists, mockExec, mockReaddir } = await importWorkspace(); const rootDb = join(WS_DIR, "workspace.duckdb"); const subDb = join(WS_DIR, "sub", "workspace.duckdb"); const bin = "/opt/homebrew/bin/duckdb"; mockExists.mockImplementation((p) => { const s = String(p); return s === WS_DIR || s === rootDb || s === subDb || s === bin; }); mockReaddir.mockImplementation((dir) => { if (String(dir) === WS_DIR) {return [makeDirent("sub", true)] as unknown as Dirent[];} return [] as unknown as Dirent[]; }); let callCount = 0; mockExec.mockImplementation(() => { callCount++; if (callCount <= 1) {return '[{"name":"obj","val":"root"}]' as never;} return '[{"name":"obj","val":"sub"}]' as never; }); const result = duckdbQueryAll<{ name: string; val: string }>("SQL", "name"); expect(result).toEqual([{ name: "obj", val: "root" }]); }); it("returns empty when no dbs discovered", async () => { delete process.env.OPENCLAW_WORKSPACE; const { duckdbQueryAll, mockExists } = await importWorkspace(); mockExists.mockReturnValue(false); expect(duckdbQueryAll("SELECT 1")).toEqual([]); }); it("skips failing databases", async () => { process.env.OPENCLAW_WORKSPACE = WS_DIR; const { duckdbQueryAll, mockExists, mockExec, mockReaddir } = await importWorkspace(); const rootDb = join(WS_DIR, "workspace.duckdb"); const subDb = join(WS_DIR, "sub", "workspace.duckdb"); const bin = "/opt/homebrew/bin/duckdb"; mockExists.mockImplementation((p) => { const s = String(p); return s === WS_DIR || s === rootDb || s === subDb || s === bin; }); mockReaddir.mockImplementation((dir) => { if (String(dir) === WS_DIR) {return [makeDirent("sub", true)] as unknown as Dirent[];} return [] as unknown as Dirent[]; }); let callCount = 0; mockExec.mockImplementation(() => { callCount++; if (callCount <= 1) {throw new Error("corrupt db");} return '[{"name":"subObj"}]' as never; }); const result = duckdbQueryAll<{ name: string }>("SELECT *"); expect(result).toEqual([{ name: "subObj" }]); }); }); // ─── findDuckDBForObject ───────────────────────────────────────── describe("findDuckDBForObject", () => { it("finds object in first database", async () => { process.env.OPENCLAW_WORKSPACE = WS_DIR; const { findDuckDBForObject, mockExists, mockExec, mockReaddir } = await importWorkspace(); const rootDb = join(WS_DIR, "workspace.duckdb"); const bin = "/opt/homebrew/bin/duckdb"; mockExists.mockImplementation((p) => { const s = String(p); return s === WS_DIR || s === rootDb || s === bin; }); mockReaddir.mockReturnValue([]); mockExec.mockReturnValue('[{"id":"123"}]' as never); expect(findDuckDBForObject("leads")).toBe(rootDb); }); it("returns null when object not found in any db", async () => { process.env.OPENCLAW_WORKSPACE = WS_DIR; const { findDuckDBForObject, mockExists, mockExec, mockReaddir } = await importWorkspace(); const rootDb = join(WS_DIR, "workspace.duckdb"); const bin = "/opt/homebrew/bin/duckdb"; mockExists.mockImplementation((p) => { const s = String(p); return s === WS_DIR || s === rootDb || s === bin; }); mockReaddir.mockReturnValue([]); mockExec.mockReturnValue("[]" as never); expect(findDuckDBForObject("nonexistent")).toBeNull(); }); it("returns null when no dbs exist", async () => { delete process.env.OPENCLAW_WORKSPACE; const { findDuckDBForObject, mockExists } = await importWorkspace(); mockExists.mockReturnValue(false); expect(findDuckDBForObject("any")).toBeNull(); }); it("handles object names with single quotes", async () => { process.env.OPENCLAW_WORKSPACE = WS_DIR; const { findDuckDBForObject, mockExists, mockExec, mockReaddir } = await importWorkspace(); const rootDb = join(WS_DIR, "workspace.duckdb"); const bin = "/opt/homebrew/bin/duckdb"; mockExists.mockImplementation((p) => { const s = String(p); return s === WS_DIR || s === rootDb || s === bin; }); mockReaddir.mockReturnValue([]); mockExec.mockReturnValue('[{"id":"1"}]' as never); expect(findDuckDBForObject("O'Brien's")).toBe(rootDb); }); }); // ─── duckdbExec / duckdbExecOnFile ─────────────────────────────── describe("duckdbExec", () => { it("returns true on successful exec", async () => { process.env.OPENCLAW_WORKSPACE = WS_DIR; const { duckdbExec, mockExists, mockExec } = await importWorkspace(); const rootDb = join(WS_DIR, "workspace.duckdb"); const bin = "/opt/homebrew/bin/duckdb"; mockExists.mockImplementation((p) => { const s = String(p); return s === WS_DIR || s === rootDb || s === bin; }); mockExec.mockReturnValue("" as never); expect(duckdbExec("INSERT INTO t VALUES (1)")).toBe(true); }); it("returns false when no database path", async () => { delete process.env.OPENCLAW_WORKSPACE; const { duckdbExec, mockExists } = await importWorkspace(); mockExists.mockReturnValue(false); expect(duckdbExec("INSERT INTO t VALUES (1)")).toBe(false); }); }); describe("duckdbExecOnFile", () => { it("returns true on success", async () => { const { duckdbExecOnFile, mockExists, mockExec } = await importWorkspace(); mockExists.mockImplementation((p) => String(p) === "/opt/homebrew/bin/duckdb"); mockExec.mockReturnValue("" as never); expect(duckdbExecOnFile("/db/file.duckdb", "CREATE TABLE t(id INT)")).toBe(true); }); it("returns false when no bin", async () => { const { duckdbExecOnFile, mockExists, mockExec } = await importWorkspace(); mockExists.mockReturnValue(false); mockExec.mockImplementation(() => { throw new Error("not found"); }); expect(duckdbExecOnFile("/db/file.duckdb", "SQL")).toBe(false); }); it("returns false on exec error", async () => { const { duckdbExecOnFile, mockExists, mockExec } = await importWorkspace(); mockExists.mockImplementation((p) => String(p) === "/opt/homebrew/bin/duckdb"); mockExec.mockImplementation(() => { throw new Error("exec failed"); }); expect(duckdbExecOnFile("/db/file.duckdb", "BAD SQL")).toBe(false); }); }); // ─── parseRelationValue ────────────────────────────────────────── describe("parseRelationValue", () => { it("returns empty array for null", async () => { const { parseRelationValue } = await importWorkspace(); expect(parseRelationValue(null)).toEqual([]); }); it("returns empty array for undefined", async () => { const { parseRelationValue } = await importWorkspace(); expect(parseRelationValue(undefined)).toEqual([]); }); it("returns empty array for empty string", async () => { const { parseRelationValue } = await importWorkspace(); expect(parseRelationValue("")).toEqual([]); }); it("returns empty array for whitespace-only string", async () => { const { parseRelationValue } = await importWorkspace(); expect(parseRelationValue(" ")).toEqual([]); }); it("returns single ID for simple string", async () => { const { parseRelationValue } = await importWorkspace(); expect(parseRelationValue("abc-123")).toEqual(["abc-123"]); }); it("parses JSON array of IDs", async () => { const { parseRelationValue } = await importWorkspace(); expect(parseRelationValue('["id1","id2","id3"]')).toEqual(["id1", "id2", "id3"]); }); it("converts numeric array elements to strings", async () => { const { parseRelationValue } = await importWorkspace(); expect(parseRelationValue("[1,2,3]")).toEqual(["1", "2", "3"]); }); it("filters empty values from array", async () => { const { parseRelationValue } = await importWorkspace(); expect(parseRelationValue('["a","","b"]')).toEqual(["a", "b"]); }); it("treats invalid JSON starting with [ as single value", async () => { const { parseRelationValue } = await importWorkspace(); expect(parseRelationValue("[not-json")).toEqual(["[not-json"]); }); it("handles empty JSON array", async () => { const { parseRelationValue } = await importWorkspace(); expect(parseRelationValue("[]")).toEqual([]); }); }); // ─── isDatabaseFile ────────────────────────────────────────────── describe("isDatabaseFile", () => { it("returns true for .duckdb", async () => { const { isDatabaseFile } = await importWorkspace(); expect(isDatabaseFile("workspace.duckdb")).toBe(true); }); it("returns true for .sqlite", async () => { const { isDatabaseFile } = await importWorkspace(); expect(isDatabaseFile("data.sqlite")).toBe(true); }); it("returns true for .sqlite3", async () => { const { isDatabaseFile } = await importWorkspace(); expect(isDatabaseFile("main.sqlite3")).toBe(true); }); it("returns true for .db", async () => { const { isDatabaseFile } = await importWorkspace(); expect(isDatabaseFile("app.db")).toBe(true); }); it("returns true for .postgres", async () => { const { isDatabaseFile } = await importWorkspace(); expect(isDatabaseFile("conn.postgres")).toBe(true); }); it("returns false for .txt", async () => { const { isDatabaseFile } = await importWorkspace(); expect(isDatabaseFile("notes.txt")).toBe(false); }); it("returns false for .json", async () => { const { isDatabaseFile } = await importWorkspace(); expect(isDatabaseFile("data.json")).toBe(false); }); it("returns false for no extension", async () => { const { isDatabaseFile } = await importWorkspace(); expect(isDatabaseFile("Makefile")).toBe(false); }); }); // ─── DB_EXTENSIONS ─────────────────────────────────────────────── describe("DB_EXTENSIONS", () => { it("contains all expected extensions", async () => { const { DB_EXTENSIONS } = await importWorkspace(); expect(DB_EXTENSIONS.has("duckdb")).toBe(true); expect(DB_EXTENSIONS.has("sqlite")).toBe(true); expect(DB_EXTENSIONS.has("sqlite3")).toBe(true); expect(DB_EXTENSIONS.has("db")).toBe(true); expect(DB_EXTENSIONS.has("postgres")).toBe(true); }); it("does not contain non-database extensions", async () => { const { DB_EXTENSIONS } = await importWorkspace(); expect(DB_EXTENSIONS.has("json")).toBe(false); expect(DB_EXTENSIONS.has("txt")).toBe(false); expect(DB_EXTENSIONS.has("csv")).toBe(false); }); }); // ─── duckdbQueryOnFile ─────────────────────────────────────────── describe("duckdbQueryOnFile", () => { it("executes query against specific db file", async () => { const { duckdbQueryOnFile, mockExists, mockExec } = await importWorkspace(); mockExists.mockImplementation((p) => String(p) === "/opt/homebrew/bin/duckdb"); mockExec.mockReturnValue('[{"col":"val"}]' as never); expect(duckdbQueryOnFile("/any/db.duckdb", "SELECT *")).toEqual([{ col: "val" }]); }); it("returns empty array when no bin found", async () => { const { duckdbQueryOnFile, mockExists, mockExec } = await importWorkspace(); mockExists.mockReturnValue(false); mockExec.mockImplementation(() => { throw new Error("not found"); }); expect(duckdbQueryOnFile("/any/db.duckdb", "SELECT *")).toEqual([]); }); it("returns empty for empty result", async () => { const { duckdbQueryOnFile, mockExists, mockExec } = await importWorkspace(); mockExists.mockImplementation((p) => String(p) === "/opt/homebrew/bin/duckdb"); mockExec.mockReturnValue("" as never); expect(duckdbQueryOnFile("/any/db.duckdb", "SELECT *")).toEqual([]); }); }); // ─── safeResolvePath ───────────────────────────────────────────── describe("safeResolvePath", () => { it("resolves valid path within workspace", async () => { process.env.OPENCLAW_WORKSPACE = WS_DIR; const { safeResolvePath, mockExists } = await importWorkspace(); mockExists.mockImplementation((p) => { const s = String(p); return s === WS_DIR || s === join(WS_DIR, "knowledge", "doc.md"); }); expect(safeResolvePath("knowledge/doc.md")).toBe(join(WS_DIR, "knowledge", "doc.md")); }); it("returns null for traversal with ..", async () => { process.env.OPENCLAW_WORKSPACE = WS_DIR; const { safeResolvePath, mockExists } = await importWorkspace(); mockExists.mockReturnValue(true); expect(safeResolvePath("../etc/passwd")).toBeNull(); }); it("returns null for traversal with /../", async () => { process.env.OPENCLAW_WORKSPACE = WS_DIR; const { safeResolvePath, mockExists } = await importWorkspace(); mockExists.mockReturnValue(true); expect(safeResolvePath("foo/../../../etc/passwd")).toBeNull(); }); it("returns null when file does not exist", async () => { process.env.OPENCLAW_WORKSPACE = WS_DIR; const { safeResolvePath, mockExists } = await importWorkspace(); mockExists.mockImplementation((p) => String(p) === WS_DIR); expect(safeResolvePath("nonexistent.txt")).toBeNull(); }); it("returns null when no workspace root", async () => { delete process.env.OPENCLAW_WORKSPACE; const { safeResolvePath, mockExists } = await importWorkspace(); mockExists.mockReturnValue(false); expect(safeResolvePath("any/file.txt")).toBeNull(); }); }); // ─── safeResolveNewPath ────────────────────────────────────────── describe("safeResolveNewPath", () => { it("resolves valid new path (does not require existence)", async () => { process.env.OPENCLAW_WORKSPACE = WS_DIR; const { safeResolveNewPath, mockExists } = await importWorkspace(); mockExists.mockImplementation((p) => String(p) === WS_DIR); expect(safeResolveNewPath("new-folder/file.txt")).toBe(join(WS_DIR, "new-folder", "file.txt")); }); it("returns null for traversal attempts", async () => { process.env.OPENCLAW_WORKSPACE = WS_DIR; const { safeResolveNewPath, mockExists } = await importWorkspace(); mockExists.mockReturnValue(true); expect(safeResolveNewPath("../../outside")).toBeNull(); }); it("returns null when no workspace root", async () => { delete process.env.OPENCLAW_WORKSPACE; const { safeResolveNewPath, mockExists } = await importWorkspace(); mockExists.mockReturnValue(false); expect(safeResolveNewPath("any")).toBeNull(); }); it("handles deeply nested new paths", async () => { process.env.OPENCLAW_WORKSPACE = WS_DIR; const { safeResolveNewPath, mockExists } = await importWorkspace(); mockExists.mockImplementation((p) => String(p) === WS_DIR); expect(safeResolveNewPath("a/b/c/d/e.txt")).toBe(join(WS_DIR, "a", "b", "c", "d", "e.txt")); }); }); // ─── isSystemFile ──────────────────────────────────────────────── describe("isSystemFile", () => { it("returns true for .object.yaml at any depth", async () => { const { isSystemFile } = await importWorkspace(); expect(isSystemFile(".object.yaml")).toBe(true); expect(isSystemFile("sub/.object.yaml")).toBe(true); expect(isSystemFile("a/b/c/.object.yaml")).toBe(true); }); it("returns true for .wal files at any depth", async () => { const { isSystemFile } = await importWorkspace(); expect(isSystemFile("workspace.duckdb.wal")).toBe(true); expect(isSystemFile("sub/data.wal")).toBe(true); }); it("returns true for .tmp files at any depth", async () => { const { isSystemFile } = await importWorkspace(); expect(isSystemFile("upload.tmp")).toBe(true); expect(isSystemFile("sub/temp.tmp")).toBe(true); }); it("returns true for workspace.duckdb at root only", async () => { const { isSystemFile } = await importWorkspace(); expect(isSystemFile("workspace.duckdb")).toBe(true); }); it("returns false for workspace.duckdb in subdirectory", async () => { const { isSystemFile } = await importWorkspace(); expect(isSystemFile("sub/workspace.duckdb")).toBe(false); }); it("returns true for workspace_context.yaml at root", async () => { const { isSystemFile } = await importWorkspace(); expect(isSystemFile("workspace_context.yaml")).toBe(true); }); it("returns false for workspace_context.yaml in subdirectory", async () => { const { isSystemFile } = await importWorkspace(); expect(isSystemFile("sub/workspace_context.yaml")).toBe(false); }); it("returns false for IDENTITY.md (not a system file)", async () => { const { isSystemFile } = await importWorkspace(); expect(isSystemFile("IDENTITY.md")).toBe(false); }); it("returns false for IDENTITY.md in subdirectory", async () => { const { isSystemFile } = await importWorkspace(); expect(isSystemFile("sub/IDENTITY.md")).toBe(false); }); it("returns false for regular files", async () => { const { isSystemFile } = await importWorkspace(); expect(isSystemFile("readme.md")).toBe(false); expect(isSystemFile("knowledge/notes.md")).toBe(false); expect(isSystemFile("data.json")).toBe(false); }); }); // ─── parseSimpleYaml ───────────────────────────────────────────── describe("parseSimpleYaml", () => { it("parses basic key-value pairs", async () => { const { parseSimpleYaml } = await importWorkspace(); const result = parseSimpleYaml("name: My Workspace\nversion: 1"); expect(result).toEqual({ name: "My Workspace", version: 1 }); }); it("parses boolean values", async () => { const { parseSimpleYaml } = await importWorkspace(); const result = parseSimpleYaml("enabled: true\ndisabled: false"); expect(result).toEqual({ enabled: true, disabled: false }); }); it("parses null value", async () => { const { parseSimpleYaml } = await importWorkspace(); const result = parseSimpleYaml("empty: null"); expect(result).toEqual({ empty: null }); }); it("parses numeric values", async () => { const { parseSimpleYaml } = await importWorkspace(); const result = parseSimpleYaml("count: 42\nratio: 3.14\nneg: -5"); expect(result).toEqual({ count: 42, ratio: 3.14, neg: -5 }); }); it("strips double quotes from values", async () => { const { parseSimpleYaml } = await importWorkspace(); const result = parseSimpleYaml('title: "My Title"'); expect(result).toEqual({ title: "My Title" }); }); it("strips single quotes from values", async () => { const { parseSimpleYaml } = await importWorkspace(); const result = parseSimpleYaml("title: 'My Title'"); expect(result).toEqual({ title: "My Title" }); }); it("skips comment lines", async () => { const { parseSimpleYaml } = await importWorkspace(); const result = parseSimpleYaml("# This is a comment\nname: test"); expect(result).toEqual({ name: "test" }); }); it("skips empty lines", async () => { const { parseSimpleYaml } = await importWorkspace(); const result = parseSimpleYaml("a: 1\n\n\nb: 2"); expect(result).toEqual({ a: 1, b: 2 }); }); it("handles keys with hyphens and underscores", async () => { const { parseSimpleYaml } = await importWorkspace(); const result = parseSimpleYaml("my-key: val\nmy_key2: val2"); expect(result).toEqual({ "my-key": "val", "my_key2": "val2" }); }); it("returns empty object for empty input", async () => { const { parseSimpleYaml } = await importWorkspace(); expect(parseSimpleYaml("")).toEqual({}); }); }); // ─── readWorkspaceFile ─────────────────────────────────────────── describe("readWorkspaceFile", () => { it("reads markdown file and detects type", async () => { process.env.OPENCLAW_WORKSPACE = WS_DIR; const { readWorkspaceFile, mockExists, mockReadFile } = await importWorkspace(); mockExists.mockImplementation((p) => { const s = String(p); return s === WS_DIR || s === join(WS_DIR, "doc.md"); }); mockReadFile.mockReturnValue("# Hello" as never); const result = readWorkspaceFile("doc.md"); expect(result).toEqual({ content: "# Hello", type: "markdown" }); }); it("reads yaml file and detects type", async () => { process.env.OPENCLAW_WORKSPACE = WS_DIR; const { readWorkspaceFile, mockExists, mockReadFile } = await importWorkspace(); mockExists.mockImplementation((p) => { const s = String(p); return s === WS_DIR || s === join(WS_DIR, "config.yaml"); }); mockReadFile.mockReturnValue("key: value" as never); const result = readWorkspaceFile("config.yaml"); expect(result).toEqual({ content: "key: value", type: "yaml" }); }); it("reads yml file as yaml type", async () => { process.env.OPENCLAW_WORKSPACE = WS_DIR; const { readWorkspaceFile, mockExists, mockReadFile } = await importWorkspace(); mockExists.mockImplementation((p) => { const s = String(p); return s === WS_DIR || s === join(WS_DIR, "config.yml"); }); mockReadFile.mockReturnValue("key: value" as never); const result = readWorkspaceFile("config.yml"); expect(result).toEqual({ content: "key: value", type: "yaml" }); }); it("reads text file with generic type", async () => { process.env.OPENCLAW_WORKSPACE = WS_DIR; const { readWorkspaceFile, mockExists, mockReadFile } = await importWorkspace(); mockExists.mockImplementation((p) => { const s = String(p); return s === WS_DIR || s === join(WS_DIR, "notes.txt"); }); mockReadFile.mockReturnValue("plain text" as never); const result = readWorkspaceFile("notes.txt"); expect(result).toEqual({ content: "plain text", type: "text" }); }); it("returns null when file not found", async () => { process.env.OPENCLAW_WORKSPACE = WS_DIR; const { readWorkspaceFile, mockExists } = await importWorkspace(); mockExists.mockImplementation((p) => String(p) === WS_DIR); expect(readWorkspaceFile("nonexistent.md")).toBeNull(); }); it("returns null when readFileSync throws", async () => { process.env.OPENCLAW_WORKSPACE = WS_DIR; const { readWorkspaceFile, mockExists, mockReadFile } = await importWorkspace(); mockExists.mockReturnValue(true); mockReadFile.mockImplementation(() => { throw new Error("EACCES"); }); expect(readWorkspaceFile("forbidden.md")).toBeNull(); }); }); });