openclaw/apps/web/lib/workspace.test.ts

1066 lines
44 KiB
TypeScript

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(() => []),
}));
// 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 };
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(() => []),
}));
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 = "/custom/workspace";
const { resolveWorkspaceRoot, mockExists } = await importWorkspace();
mockExists.mockImplementation((p) => String(p) === "/custom/workspace");
expect(resolveWorkspaceRoot()).toBe("/custom/workspace");
});
it("returns default ~/.openclaw/workspace when env not set", async () => {
delete process.env.OPENCLAW_WORKSPACE;
const { resolveWorkspaceRoot, mockExists } = await importWorkspace();
const defaultPath = join("/home/testuser", ".openclaw", "workspace");
mockExists.mockImplementation((p) => String(p) === defaultPath);
expect(resolveWorkspaceRoot()).toBe(defaultPath);
});
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 default when both exist", async () => {
process.env.OPENCLAW_WORKSPACE = "/custom/workspace";
const { resolveWorkspaceRoot, mockExists } = await importWorkspace();
mockExists.mockReturnValue(true);
expect(resolveWorkspaceRoot()).toBe("/custom/workspace");
});
it("falls back to default when env var path does not exist", async () => {
process.env.OPENCLAW_WORKSPACE = "/nonexistent";
const { resolveWorkspaceRoot, mockExists } = await importWorkspace();
const defaultPath = join("/home/testuser", ".openclaw", "workspace");
mockExists.mockImplementation((p) => String(p) === defaultPath);
expect(resolveWorkspaceRoot()).toBe(defaultPath);
});
});
// ─── 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 = "/external/workspace";
const { resolveAgentWorkspacePrefix, mockExists } = await importWorkspace();
mockExists.mockImplementation((p) => String(p) === "/external/workspace");
vi.spyOn(process, "cwd").mockReturnValue("/repo/apps/web");
expect(resolveAgentWorkspacePrefix()).toBe("/external/workspace");
});
it("returns relative path when workspace is inside repo", async () => {
process.env.OPENCLAW_WORKSPACE = "/repo/workspace";
const { resolveAgentWorkspacePrefix, mockExists } = await importWorkspace();
mockExists.mockImplementation((p) => String(p) === "/repo/workspace");
vi.spyOn(process, "cwd").mockReturnValue("/repo/apps/web");
expect(resolveAgentWorkspacePrefix()).toBe("workspace");
});
it("handles non apps/web cwd", async () => {
process.env.OPENCLAW_WORKSPACE = "/repo/workspace";
const { resolveAgentWorkspacePrefix, mockExists } = await importWorkspace();
mockExists.mockImplementation((p) => String(p) === "/repo/workspace");
vi.spyOn(process, "cwd").mockReturnValue("/repo");
expect(resolveAgentWorkspacePrefix()).toBe("workspace");
});
});
// ─── 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";
const { duckdbPath, mockExists, mockReaddir } = await importWorkspace();
const rootDb = join("/ws", "workspace.duckdb");
mockExists.mockImplementation((p) => {
const s = String(p);
return s === "/ws" || 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";
const { duckdbPath, mockExists, mockReaddir } = await importWorkspace();
const nestedDb = join("/ws", "sub", "workspace.duckdb");
mockExists.mockImplementation((p) => {
const s = String(p);
return s === "/ws" || s === nestedDb;
});
mockReaddir.mockImplementation((dir) => {
if (String(dir) === "/ws") {
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";
const { duckdbPath, mockExists, mockReaddir } = await importWorkspace();
mockExists.mockImplementation((p) => String(p) === "/ws");
mockReaddir.mockReturnValue([]);
expect(duckdbPath()).toBeNull();
});
});
// ─── duckdbRelativeScope ─────────────────────────────────────────
describe("duckdbRelativeScope", () => {
it("returns empty string for root-level db", async () => {
process.env.OPENCLAW_WORKSPACE = "/ws";
const { duckdbRelativeScope, mockExists } = await importWorkspace();
mockExists.mockImplementation((p) => String(p) === "/ws");
expect(duckdbRelativeScope("/ws/workspace.duckdb")).toBe("");
});
it("returns relative path for nested db", async () => {
process.env.OPENCLAW_WORKSPACE = "/ws";
const { duckdbRelativeScope, mockExists } = await importWorkspace();
mockExists.mockImplementation((p) => String(p) === "/ws");
expect(duckdbRelativeScope("/ws/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";
const { duckdbQuery, mockExists, mockExec } = await importWorkspace();
const rootDb = join("/ws", "workspace.duckdb");
const bin = "/opt/homebrew/bin/duckdb";
mockExists.mockImplementation((p) => {
const s = String(p);
return s === "/ws" || 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";
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";
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";
const { duckdbQueryAsync, mockExists } = await importWorkspace();
const { exec: mockExecFn } = await import("node:child_process");
const rootDb = join("/ws", "workspace.duckdb");
const bin = "/opt/homebrew/bin/duckdb";
mockExists.mockImplementation((p) => {
const s = String(p);
return s === "/ws" || 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";
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";
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";
const { duckdbQueryAll, mockExists, mockExec, mockReaddir } = await importWorkspace();
const rootDb = join("/ws", "workspace.duckdb");
const subDb = join("/ws", "sub", "workspace.duckdb");
const bin = "/opt/homebrew/bin/duckdb";
mockExists.mockImplementation((p) => {
const s = String(p);
return s === "/ws" || s === rootDb || s === subDb || s === bin;
});
mockReaddir.mockImplementation((dir) => {
if (String(dir) === "/ws") {
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";
const { duckdbQueryAll, mockExists, mockExec, mockReaddir } = await importWorkspace();
const rootDb = join("/ws", "workspace.duckdb");
const subDb = join("/ws", "sub", "workspace.duckdb");
const bin = "/opt/homebrew/bin/duckdb";
mockExists.mockImplementation((p) => {
const s = String(p);
return s === "/ws" || s === rootDb || s === subDb || s === bin;
});
mockReaddir.mockImplementation((dir) => {
if (String(dir) === "/ws") {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";
const { duckdbQueryAll, mockExists, mockExec, mockReaddir } = await importWorkspace();
const rootDb = join("/ws", "workspace.duckdb");
const subDb = join("/ws", "sub", "workspace.duckdb");
const bin = "/opt/homebrew/bin/duckdb";
mockExists.mockImplementation((p) => {
const s = String(p);
return s === "/ws" || s === rootDb || s === subDb || s === bin;
});
mockReaddir.mockImplementation((dir) => {
if (String(dir) === "/ws") {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";
const { findDuckDBForObject, mockExists, mockExec, mockReaddir } = await importWorkspace();
const rootDb = join("/ws", "workspace.duckdb");
const bin = "/opt/homebrew/bin/duckdb";
mockExists.mockImplementation((p) => {
const s = String(p);
return s === "/ws" || 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";
const { findDuckDBForObject, mockExists, mockExec, mockReaddir } = await importWorkspace();
const rootDb = join("/ws", "workspace.duckdb");
const bin = "/opt/homebrew/bin/duckdb";
mockExists.mockImplementation((p) => {
const s = String(p);
return s === "/ws" || 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";
const { findDuckDBForObject, mockExists, mockExec, mockReaddir } = await importWorkspace();
const rootDb = join("/ws", "workspace.duckdb");
const bin = "/opt/homebrew/bin/duckdb";
mockExists.mockImplementation((p) => {
const s = String(p);
return s === "/ws" || 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";
const { duckdbExec, mockExists, mockExec } = await importWorkspace();
const rootDb = join("/ws", "workspace.duckdb");
const bin = "/opt/homebrew/bin/duckdb";
mockExists.mockImplementation((p) => {
const s = String(p);
return s === "/ws" || 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";
const { safeResolvePath, mockExists } = await importWorkspace();
mockExists.mockImplementation((p) => {
const s = String(p);
return s === "/ws" || s === "/ws/knowledge/doc.md";
});
expect(safeResolvePath("knowledge/doc.md")).toBe("/ws/knowledge/doc.md");
});
it("returns null for traversal with ..", async () => {
process.env.OPENCLAW_WORKSPACE = "/ws";
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";
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";
const { safeResolvePath, mockExists } = await importWorkspace();
mockExists.mockImplementation((p) => String(p) === "/ws");
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";
const { safeResolveNewPath, mockExists } = await importWorkspace();
mockExists.mockImplementation((p) => String(p) === "/ws");
expect(safeResolveNewPath("new-folder/file.txt")).toBe("/ws/new-folder/file.txt");
});
it("returns null for traversal attempts", async () => {
process.env.OPENCLAW_WORKSPACE = "/ws";
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";
const { safeResolveNewPath, mockExists } = await importWorkspace();
mockExists.mockImplementation((p) => String(p) === "/ws");
expect(safeResolveNewPath("a/b/c/d/e.txt")).toBe("/ws/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 true for IDENTITY.md at root", async () => {
const { isSystemFile } = await importWorkspace();
expect(isSystemFile("IDENTITY.md")).toBe(true);
});
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";
const { readWorkspaceFile, mockExists, mockReadFile } = await importWorkspace();
mockExists.mockImplementation((p) => {
const s = String(p);
return s === "/ws" || s === "/ws/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";
const { readWorkspaceFile, mockExists, mockReadFile } = await importWorkspace();
mockExists.mockImplementation((p) => {
const s = String(p);
return s === "/ws" || s === "/ws/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";
const { readWorkspaceFile, mockExists, mockReadFile } = await importWorkspace();
mockExists.mockImplementation((p) => {
const s = String(p);
return s === "/ws" || s === "/ws/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";
const { readWorkspaceFile, mockExists, mockReadFile } = await importWorkspace();
mockExists.mockImplementation((p) => {
const s = String(p);
return s === "/ws" || s === "/ws/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";
const { readWorkspaceFile, mockExists } = await importWorkspace();
mockExists.mockImplementation((p) => String(p) === "/ws");
expect(readWorkspaceFile("nonexistent.md")).toBeNull();
});
it("returns null when readFileSync throws", async () => {
process.env.OPENCLAW_WORKSPACE = "/ws";
const { readWorkspaceFile, mockExists, mockReadFile } = await importWorkspace();
mockExists.mockReturnValue(true);
mockReadFile.mockImplementation(() => { throw new Error("EACCES"); });
expect(readWorkspaceFile("forbidden.md")).toBeNull();
});
});
});