openclaw/apps/web/app/api/workspace/tree-browse.test.ts
2026-02-16 01:01:12 -08:00

234 lines
8.9 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),
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),
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 res = await GET();
const json = await res.json();
expect(json.exists).toBe(false);
expect(json.tree).toEqual([]);
});
it("returns tree with workspace files", async () => {
const { resolveWorkspaceRoot } = await import("@/lib/workspace");
vi.mocked(resolveWorkspaceRoot).mockReturnValue("/ws");
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 res = await GET();
const json = await res.json();
expect(json.exists).toBe(true);
expect(json.tree.length).toBeGreaterThan(0);
});
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 res = await GET();
const json = await res.json();
expect(json.workspaceRoot).toBe("/ws");
});
});
// ─── 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();
});
});
// ─── 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);
});
});
});