openclaw/apps/web/app/api/workspace/tree-browse.test.ts
kumarabhirup 72d5204e52
test: update tests for dench-to-crm rename and IDENTITY.md visibility
All test assertions updated to reference crm instead of dench, and IDENTITY.md is now expected to be visible in the workspace tree.
2026-03-03 15:38:46 -08:00

331 lines
13 KiB
TypeScript

import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import type { Dirent } from "node:fs";
// Mock node:fs
vi.mock("node:fs", () => ({
readdirSync: vi.fn(() => []),
readFileSync: vi.fn(() => ""),
existsSync: vi.fn(() => false),
statSync: vi.fn(() => ({ isDirectory: () => false, size: 100 })),
}));
// Mock node:os
vi.mock("node:os", () => ({
homedir: vi.fn(() => "/home/testuser"),
}));
// Mock workspace
vi.mock("@/lib/workspace", () => ({
resolveWorkspaceRoot: vi.fn(() => null),
resolveOpenClawStateDir: vi.fn(() => "/home/testuser/.openclaw-ironclaw"),
getActiveWorkspaceName: vi.fn(() => null),
parseSimpleYaml: vi.fn(() => ({})),
duckdbQueryAll: vi.fn(() => []),
duckdbQueryAllAsync: vi.fn(async () => []),
isDatabaseFile: vi.fn(() => false),
discoverDuckDBPaths: vi.fn(() => []),
resolveDuckdbBin: vi.fn(() => null),
safeResolvePath: vi.fn(() => null),
}));
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 Tree & Browse API", () => {
beforeEach(() => {
vi.resetModules();
vi.mock("node:fs", () => ({
readdirSync: vi.fn(() => []),
readFileSync: vi.fn(() => ""),
existsSync: vi.fn(() => false),
statSync: vi.fn(() => ({ isDirectory: () => false, size: 100 })),
}));
vi.mock("node:os", () => ({
homedir: vi.fn(() => "/home/testuser"),
}));
vi.mock("@/lib/workspace", () => ({
resolveWorkspaceRoot: vi.fn(() => null),
resolveOpenClawStateDir: vi.fn(() => "/home/testuser/.openclaw-ironclaw"),
getActiveWorkspaceName: vi.fn(() => null),
parseSimpleYaml: vi.fn(() => ({})),
duckdbQueryAll: vi.fn(() => []),
duckdbQueryAllAsync: vi.fn(async () => []),
isDatabaseFile: vi.fn(() => false),
discoverDuckDBPaths: vi.fn(() => []),
resolveDuckdbBin: vi.fn(() => null),
safeResolvePath: vi.fn(() => null),
}));
});
afterEach(() => {
vi.restoreAllMocks();
});
// ─── GET /api/workspace/tree ────────────────────────────────────
describe("GET /api/workspace/tree", () => {
it("returns tree with exists=false when no workspace root", async () => {
const { GET } = await import("./tree/route.js");
const req = new Request("http://localhost/api/workspace/tree");
const res = await GET(req);
const json = await res.json();
expect(json.exists).toBe(false);
expect(json.tree).toEqual([]);
expect(json.workspace).toBeNull();
});
it("returns tree with workspace files", async () => {
const { resolveWorkspaceRoot, getActiveWorkspaceName } = await import("@/lib/workspace");
vi.mocked(resolveWorkspaceRoot).mockReturnValue("/ws");
vi.mocked(getActiveWorkspaceName).mockReturnValue("default");
const { readdirSync: mockReaddir, existsSync: mockExists } = await import("node:fs");
vi.mocked(mockExists).mockReturnValue(true);
vi.mocked(mockReaddir).mockImplementation((dir) => {
if (String(dir) === "/ws") {
return [
makeDirent("knowledge", true),
makeDirent("readme.md", false),
] as unknown as Dirent[];
}
return [] as unknown as Dirent[];
});
const { GET } = await import("./tree/route.js");
const req = new Request("http://localhost/api/workspace/tree");
const res = await GET(req);
const json = await res.json();
expect(json.exists).toBe(true);
expect(json.tree.length).toBeGreaterThan(0);
expect(json.workspace).toBe("default");
});
it("includes workspaceRoot in response", async () => {
const { resolveWorkspaceRoot } = await import("@/lib/workspace");
vi.mocked(resolveWorkspaceRoot).mockReturnValue("/ws");
const { existsSync: mockExists } = await import("node:fs");
vi.mocked(mockExists).mockReturnValue(true);
const { GET } = await import("./tree/route.js");
const req = new Request("http://localhost/api/workspace/tree");
const res = await GET(req);
const json = await res.json();
expect(json.workspaceRoot).toBe("/ws");
});
it("includes root IDENTITY.md in the workspace tree", async () => {
const { resolveWorkspaceRoot } = await import("@/lib/workspace");
vi.mocked(resolveWorkspaceRoot).mockReturnValue("/ws");
const { readdirSync: mockReaddir, existsSync: mockExists } = await import("node:fs");
vi.mocked(mockExists).mockImplementation((p) => String(p) === "/ws");
vi.mocked(mockReaddir).mockImplementation((dir) => {
if (String(dir) === "/ws") {
return [
makeDirent("IDENTITY.md", false),
makeDirent("notes.md", false),
] as unknown as Dirent[];
}
return [] as unknown as Dirent[];
});
const { GET } = await import("./tree/route.js");
const req = new Request("http://localhost/api/workspace/tree");
const res = await GET(req);
const json = await res.json();
const paths = (json.tree as Array<{ path: string }>).map((n) => n.path);
expect(paths).toContain("IDENTITY.md");
expect(paths).toContain("notes.md");
});
it("omits managed crm skill from the virtual skills folder", async () => {
const { resolveWorkspaceRoot } = await import("@/lib/workspace");
vi.mocked(resolveWorkspaceRoot).mockReturnValue("/ws");
const { readdirSync: mockReaddir, existsSync: mockExists } = await import("node:fs");
vi.mocked(mockExists).mockImplementation((p) => {
const value = String(p);
return (
value === "/ws" ||
value === "/ws/skills" ||
value === "/ws/skills/alpha/SKILL.md" ||
value === "/ws/skills/crm/SKILL.md"
);
});
vi.mocked(mockReaddir).mockImplementation((dir) => {
if (String(dir) === "/ws") {
return [] as unknown as Dirent[];
}
if (String(dir) === "/ws/skills") {
return [
makeDirent("alpha", true),
makeDirent("crm", true),
] as unknown as Dirent[];
}
return [] as unknown as Dirent[];
});
const { GET } = await import("./tree/route.js");
const req = new Request("http://localhost/api/workspace/tree");
const res = await GET(req);
const json = await res.json();
const skillsFolder = (json.tree as Array<{ path: string; children?: Array<{ path: string }> }>).find(
(node) => node.path === "~skills",
);
const skillPaths = (skillsFolder?.children ?? []).map((child) => child.path);
expect(skillPaths).toContain("~skills/alpha/SKILL.md");
expect(skillPaths).not.toContain("~skills/crm/SKILL.md");
});
});
// ─── GET /api/workspace/browse ──────────────────────────────────
describe("GET /api/workspace/browse", () => {
it("returns directory listing", async () => {
const { existsSync: mockExists, readdirSync: mockReaddir, statSync: mockStat } = await import("node:fs");
vi.mocked(mockExists).mockReturnValue(true);
vi.mocked(mockReaddir).mockReturnValue([
makeDirent("file.txt", false),
makeDirent("subfolder", true),
] as unknown as Dirent[]);
vi.mocked(mockStat).mockReturnValue({ isDirectory: () => false, size: 100 } as never);
const { GET } = await import("./browse/route.js");
const req = new Request("http://localhost/api/workspace/browse?dir=/tmp/test");
const res = await GET(req);
const json = await res.json();
expect(json.entries).toBeDefined();
expect(json.currentDir).toBeDefined();
});
it("returns parentDir for nested directories", async () => {
const { existsSync: mockExists, readdirSync: mockReaddir, statSync: mockStat } = await import("node:fs");
vi.mocked(mockExists).mockReturnValue(true);
vi.mocked(mockReaddir).mockReturnValue([]);
vi.mocked(mockStat).mockReturnValue({ isDirectory: () => true, size: 0 } as never);
const { GET } = await import("./browse/route.js");
const req = new Request("http://localhost/api/workspace/browse?dir=/tmp/test/sub");
const res = await GET(req);
const json = await res.json();
expect(json.parentDir).toBeDefined();
});
});
// ─── GET /api/workspace/suggest-files ────────────────────────────
describe("GET /api/workspace/suggest-files", () => {
it("returns suggestions when workspace exists", async () => {
const { resolveWorkspaceRoot } = await import("@/lib/workspace");
vi.mocked(resolveWorkspaceRoot).mockReturnValue("/ws");
const { existsSync: mockExists, readdirSync: mockReaddir } = await import("node:fs");
vi.mocked(mockExists).mockReturnValue(true);
vi.mocked(mockReaddir).mockReturnValue([
makeDirent("doc.md", false),
] as unknown as Dirent[]);
const { GET } = await import("./suggest-files/route.js");
const req = new Request("http://localhost/api/workspace/suggest-files?q=doc");
const res = await GET(req);
expect(res.status).toBe(200);
const json = await res.json();
expect(json.items).toBeDefined();
});
it("includes root IDENTITY.md in sidebar file suggestions", async () => {
const { resolveWorkspaceRoot } = await import("@/lib/workspace");
vi.mocked(resolveWorkspaceRoot).mockReturnValue("/ws");
const { existsSync: mockExists, readdirSync: mockReaddir } = await import("node:fs");
vi.mocked(mockExists).mockReturnValue(true);
vi.mocked(mockReaddir).mockImplementation((dir) => {
if (String(dir) === "/ws") {
return [
makeDirent("IDENTITY.md", false),
makeDirent("doc.md", false),
] as unknown as Dirent[];
}
return [] as unknown as Dirent[];
});
const { GET } = await import("./suggest-files/route.js");
const req = new Request("http://localhost/api/workspace/suggest-files");
const res = await GET(req);
expect(res.status).toBe(200);
const json = await res.json();
const names = (json.items as Array<{ name: string }>).map((item) => item.name);
expect(names).toContain("doc.md");
expect(names).toContain("IDENTITY.md");
});
});
// ─── GET /api/workspace/context ──────────────────────────────────
describe("GET /api/workspace/context", () => {
it("returns exists=false when no workspace root", async () => {
const { resolveWorkspaceRoot } = await import("@/lib/workspace");
vi.mocked(resolveWorkspaceRoot).mockReturnValue(null);
const { GET } = await import("./context/route.js");
const res = await GET();
const json = await res.json();
expect(json.exists).toBe(false);
});
it("returns context when workspace_context.yaml exists", async () => {
const { resolveWorkspaceRoot, parseSimpleYaml } = await import("@/lib/workspace");
vi.mocked(resolveWorkspaceRoot).mockReturnValue("/ws");
vi.mocked(parseSimpleYaml).mockReturnValue({ org_name: "Acme", org_slug: "acme" });
const { existsSync: mockExists, readFileSync: mockReadFile } = await import("node:fs");
vi.mocked(mockExists).mockReturnValue(true);
vi.mocked(mockReadFile).mockReturnValue("org_name: Acme" as never);
const { GET } = await import("./context/route.js");
const res = await GET();
const json = await res.json();
expect(json.exists).toBe(true);
});
});
// ─── GET /api/workspace/search-index ─────────────────────────────
describe("GET /api/workspace/search-index", () => {
it("returns empty items when no workspace", async () => {
const { resolveWorkspaceRoot } = await import("@/lib/workspace");
vi.mocked(resolveWorkspaceRoot).mockReturnValue(null);
const { GET } = await import("./search-index/route.js");
const res = await GET();
const json = await res.json();
expect(json.items).toEqual([]);
});
it("returns file items from workspace tree", async () => {
const { resolveWorkspaceRoot, duckdbQueryAll } = await import("@/lib/workspace");
vi.mocked(resolveWorkspaceRoot).mockReturnValue("/ws");
vi.mocked(duckdbQueryAll).mockReturnValue([]);
const { existsSync: mockExists, readdirSync: mockReaddir } = await import("node:fs");
vi.mocked(mockExists).mockReturnValue(true);
vi.mocked(mockReaddir).mockImplementation((dir) => {
if (String(dir) === "/ws") {
return [makeDirent("readme.md", false)] as unknown as Dirent[];
}
return [] as unknown as Dirent[];
});
const { GET } = await import("./search-index/route.js");
const res = await GET();
const json = await res.json();
expect(json.items.length).toBeGreaterThanOrEqual(0);
});
});
});