0eaaa54e25
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
62 lines
2.4 KiB
TypeScript
62 lines
2.4 KiB
TypeScript
/**
|
|
* Unit tests for Workspace validation: only requires an existing directory;
|
|
* location is not constrained to the Project directory (reachability is
|
|
* governed by file permissions).
|
|
*/
|
|
import fs from "node:fs/promises";
|
|
import path from "node:path";
|
|
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
import { HttpError } from "../src/http/errors.js";
|
|
import { assertWorkspaceAllowed } from "../src/services/workspace-guard.js";
|
|
import { makeTempRoot } from "./helpers.js";
|
|
|
|
describe("workspace-guard", () => {
|
|
let root: string;
|
|
let projectA: string;
|
|
|
|
beforeEach(async () => {
|
|
root = await makeTempRoot();
|
|
projectA = path.join(root, "project-aaaa0001");
|
|
await fs.mkdir(path.join(projectA, "workdir"), { recursive: true });
|
|
});
|
|
afterEach(async () => {
|
|
await fs.rm(root, { recursive: true, force: true });
|
|
});
|
|
|
|
const guard = (workspace: string) => assertWorkspaceAllowed({ workspace });
|
|
|
|
it("an existing directory is allowed and its realpath returned", async () => {
|
|
const ws = await guard(path.join(projectA, "workdir"));
|
|
expect(ws).toBe(await fs.realpath(path.join(projectA, "workdir")));
|
|
});
|
|
|
|
it("any directory outside the Project directory is likewise allowed", async () => {
|
|
const outside = path.join(root, "elsewhere");
|
|
await fs.mkdir(outside, { recursive: true });
|
|
await expect(guard(outside)).resolves.toBe(await fs.realpath(outside));
|
|
});
|
|
|
|
it("symlinks resolve to their realpath before returning", async () => {
|
|
const outside = path.join(root, "linked");
|
|
await fs.mkdir(outside, { recursive: true });
|
|
const link = path.join(projectA, "escape");
|
|
await fs.symlink(outside, link, "dir");
|
|
await expect(guard(link)).resolves.toBe(await fs.realpath(outside));
|
|
});
|
|
|
|
it("a nonexistent path → 400 workspace_not_found", async () => {
|
|
const err = await guard(path.join(projectA, "ghost")).catch((e: unknown) => e);
|
|
expect(err).toBeInstanceOf(HttpError);
|
|
expect((err as HttpError).status).toBe(400);
|
|
expect((err as HttpError).code).toBe("workspace_not_found");
|
|
});
|
|
|
|
it("a file (not a directory) → 400 workspace_not_found", async () => {
|
|
const file = path.join(projectA, "a-file.txt");
|
|
await fs.writeFile(file, "x", "utf8");
|
|
const err = await guard(file).catch((e: unknown) => e);
|
|
expect((err as HttpError).status).toBe(400);
|
|
expect((err as HttpError).code).toBe("workspace_not_found");
|
|
});
|
|
});
|