test(web): add workspace file-ops, objects, and tree-browse tests
This commit is contained in:
parent
f23b7133cb
commit
7309d649dd
@ -109,6 +109,20 @@ describe("Workspace File Operations API", () => {
|
||||
expect(mockWrite).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("returns 403 when attempting to modify a system file", async () => {
|
||||
const { isSystemFile } = await import("@/lib/workspace");
|
||||
vi.mocked(isSystemFile).mockReturnValueOnce(true);
|
||||
|
||||
const { POST } = await import("./file/route.js");
|
||||
const req = new Request("http://localhost/api/workspace/file", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ path: "IDENTITY.md", content: "# override" }),
|
||||
});
|
||||
const res = await POST(req);
|
||||
expect(res.status).toBe(403);
|
||||
});
|
||||
|
||||
it("returns 400 for missing path", async () => {
|
||||
const { POST } = await import("./file/route.js");
|
||||
const req = new Request("http://localhost/api/workspace/file", {
|
||||
|
||||
@ -120,6 +120,63 @@ describe("Workspace Objects API", () => {
|
||||
expect(json.fields).toBeDefined();
|
||||
});
|
||||
|
||||
it("returns saved views and active view from object yaml metadata", async () => {
|
||||
const {
|
||||
findDuckDBForObject,
|
||||
duckdbQueryOnFile,
|
||||
resolveDuckdbBin,
|
||||
discoverDuckDBPaths,
|
||||
getObjectViews,
|
||||
} = await import("@/lib/workspace");
|
||||
|
||||
vi.mocked(findDuckDBForObject).mockReturnValue("/ws/workspace.duckdb");
|
||||
vi.mocked(resolveDuckdbBin).mockReturnValue("/opt/homebrew/bin/duckdb");
|
||||
vi.mocked(discoverDuckDBPaths).mockReturnValue(["/ws/workspace.duckdb"]);
|
||||
vi.mocked(getObjectViews).mockReturnValue({
|
||||
views: [
|
||||
{
|
||||
name: "Important",
|
||||
filters: {
|
||||
id: "root",
|
||||
conjunction: "and",
|
||||
rules: [
|
||||
{ id: "rule-1", field: "Status", operator: "is", value: "Important" },
|
||||
],
|
||||
},
|
||||
columns: ["Name", "Status"],
|
||||
},
|
||||
],
|
||||
activeView: "Important",
|
||||
});
|
||||
|
||||
let queryCall = 0;
|
||||
vi.mocked(duckdbQueryOnFile).mockImplementation(() => {
|
||||
queryCall += 1;
|
||||
if (queryCall === 1) {
|
||||
return [{ id: "obj1", name: "leads", description: "Leads object", icon: "star" }];
|
||||
}
|
||||
if (queryCall === 2) {
|
||||
return [{ id: "f1", name: "name", type: "text", sort_order: 0 }];
|
||||
}
|
||||
if (queryCall === 3) {
|
||||
return [];
|
||||
}
|
||||
return [];
|
||||
});
|
||||
|
||||
const { GET } = await import("./objects/[name]/route.js");
|
||||
const res = await GET(
|
||||
new Request("http://localhost/api/workspace/objects/leads"),
|
||||
{ params: Promise.resolve({ name: "leads" }) },
|
||||
);
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
const json = await res.json();
|
||||
expect(json.savedViews).toHaveLength(1);
|
||||
expect(json.savedViews[0].name).toBe("Important");
|
||||
expect(json.activeView).toBe("Important");
|
||||
});
|
||||
|
||||
it("accepts underscored names", async () => {
|
||||
const { findDuckDBForObject } = await import("@/lib/workspace");
|
||||
vi.mocked(findDuckDBForObject).mockReturnValue(null);
|
||||
|
||||
@ -120,6 +120,68 @@ describe("Workspace Tree & Browse API", () => {
|
||||
const json = await res.json();
|
||||
expect(json.workspaceRoot).toBe("/ws");
|
||||
});
|
||||
|
||||
it("omits root IDENTITY.md from 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).not.toContain("IDENTITY.md");
|
||||
expect(paths).toContain("notes.md");
|
||||
});
|
||||
|
||||
it("omits managed dench 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 === "/home/testuser/.openclaw/skills" ||
|
||||
value === "/home/testuser/.openclaw/skills/alpha/SKILL.md" ||
|
||||
value === "/home/testuser/.openclaw/skills/dench/SKILL.md"
|
||||
);
|
||||
});
|
||||
vi.mocked(mockReaddir).mockImplementation((dir) => {
|
||||
if (String(dir) === "/ws") {
|
||||
return [] as unknown as Dirent[];
|
||||
}
|
||||
if (String(dir) === "/home/testuser/.openclaw/skills") {
|
||||
return [
|
||||
makeDirent("alpha", true),
|
||||
makeDirent("dench", 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/dench/SKILL.md");
|
||||
});
|
||||
});
|
||||
|
||||
// ─── GET /api/workspace/browse ──────────────────────────────────
|
||||
@ -175,6 +237,31 @@ describe("Workspace Tree & Browse API", () => {
|
||||
const json = await res.json();
|
||||
expect(json.items).toBeDefined();
|
||||
});
|
||||
|
||||
it("omits root IDENTITY.md from 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).not.toContain("IDENTITY.md");
|
||||
});
|
||||
});
|
||||
|
||||
// ─── GET /api/workspace/context ──────────────────────────────────
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user