import { mkdirSync, realpathSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
import {
type DirectiveRequest,
parseDirectives,
resolveHostDirective,
resolveSandboxDirective,
} from "../daemon/assistant-attachments.js";
// Use realpath to avoid macOS /tmp → /private/tmp symlink mismatches
const RAW_TEST_DIR = join(tmpdir(), `vellum-sandbox-test-${Date.now()}`);
mkdirSync(RAW_TEST_DIR, { recursive: true });
const TEST_DIR = realpathSync(RAW_TEST_DIR);
// ---------------------------------------------------------------------------
// parseDirectives
// ---------------------------------------------------------------------------
describe("parseDirectives", () => {
test("parses a single sandbox directive with all attributes", () => {
const text =
'Here is the report:\n';
const result = parseDirectives(text);
expect(result.directiveRequests).toHaveLength(1);
expect(result.directiveRequests[0]).toEqual({
source: "sandbox",
path: "output/report.pdf",
filename: "report.pdf",
mimeType: "application/pdf",
filenameSource: "explicit",
});
expect(result.cleanText).toBe("Here is the report:");
expect(result.parseWarnings).toHaveLength(0);
});
test("defaults source to sandbox when omitted", () => {
const text = '';
const result = parseDirectives(text);
expect(result.directiveRequests).toHaveLength(1);
expect(result.directiveRequests[0].source).toBe("sandbox");
});
test("parses host source", () => {
const text = '';
const result = parseDirectives(text);
expect(result.directiveRequests).toHaveLength(1);
expect(result.directiveRequests[0].source).toBe("host");
expect(result.directiveRequests[0].path).toBe("/Users/me/doc.pdf");
});
test("leaves optional filename and mime_type undefined when absent", () => {
const text = '';
const result = parseDirectives(text);
expect(result.directiveRequests[0].filename).toBeUndefined();
expect(result.directiveRequests[0].mimeType).toBeUndefined();
});
test("parses multiple directives preserving order", () => {
const text = [
"Results:",
'',
"And also:",
'',
].join("\n");
const result = parseDirectives(text);
expect(result.directiveRequests).toHaveLength(2);
expect(result.directiveRequests[0].path).toBe("a.png");
expect(result.directiveRequests[1].path).toBe("b.pdf");
expect(result.cleanText).toBe("Results:\n\nAnd also:");
});
test("rejects directive without path attribute", () => {
const text = '';
const result = parseDirectives(text);
expect(result.directiveRequests).toHaveLength(0);
expect(result.parseWarnings).toHaveLength(1);
expect(result.parseWarnings[0]).toContain('missing required "path"');
// Malformed tag preserved in text
expect(result.cleanText).toContain(" {
const text = '';
const result = parseDirectives(text);
expect(result.directiveRequests).toHaveLength(0);
expect(result.parseWarnings).toHaveLength(1);
expect(result.parseWarnings[0]).toContain('invalid source="cloud"');
expect(result.cleanText).toContain(" {
const text = [
'',
'',
'',
].join("\n");
const result = parseDirectives(text);
expect(result.directiveRequests).toHaveLength(2);
expect(result.directiveRequests[0].path).toBe("good.png");
expect(result.directiveRequests[1].path).toBe("also-good.pdf");
expect(result.parseWarnings).toHaveLength(1);
});
test("returns original text when no directives present", () => {
const text = "Hello world, no attachments here.";
const result = parseDirectives(text);
expect(result.cleanText).toBe(text);
expect(result.directiveRequests).toHaveLength(0);
expect(result.parseWarnings).toHaveLength(0);
});
test("preserves whitespace when no directives are present", () => {
const text = "\n Leading space\n\n\n\nTrailing space \n";
const result = parseDirectives(text);
expect(result.cleanText).toBe(text);
expect(result.directiveRequests).toHaveLength(0);
});
test("preserves non-self-closing tags as plain text", () => {
const text =
'content';
const result = parseDirectives(text);
// The regex only matches self-closing tags, so non-self-closing is not matched
expect(result.directiveRequests).toHaveLength(0);
expect(result.cleanText).toContain("content");
});
test("handles single-quoted attributes", () => {
const text =
"";
const result = parseDirectives(text);
expect(result.directiveRequests).toHaveLength(1);
expect(result.directiveRequests[0].path).toBe("report.pdf");
expect(result.directiveRequests[0].filename).toBe("my report.pdf");
});
test("collapses excess blank lines after tag removal", () => {
const text = 'Before\n\n\n\n\nAfter';
const result = parseDirectives(text);
// Should not have triple+ newlines
expect(result.cleanText).not.toMatch(/\n{3,}/);
expect(result.cleanText).toBe("Before\n\nAfter");
});
test("handles directive with multiline attributes", () => {
const text = [
"",
].join("\n");
const result = parseDirectives(text);
expect(result.directiveRequests).toHaveLength(1);
expect(result.directiveRequests[0].source).toBe("host");
expect(result.directiveRequests[0].path).toBe("/tmp/data.csv");
expect(result.directiveRequests[0].mimeType).toBe("text/csv");
});
});
// ---------------------------------------------------------------------------
// resolveSandboxDirective
// ---------------------------------------------------------------------------
describe("resolveSandboxDirective", () => {
beforeEach(() => {
mkdirSync(join(TEST_DIR, "sub"), { recursive: true });
});
afterEach(() => {
rmSync(TEST_DIR, { recursive: true, force: true });
});
function makeDirective(
overrides: Partial = {},
): DirectiveRequest {
return {
source: "sandbox",
path: "hello.txt",
filename: undefined,
mimeType: undefined,
...overrides,
};
}
test("resolves a valid sandbox file to a draft", () => {
const filePath = join(TEST_DIR, "hello.txt");
writeFileSync(filePath, "hello world");
const result = resolveSandboxDirective(
makeDirective({ path: "hello.txt" }),
TEST_DIR,
);
expect(result.draft).not.toBeNull();
expect(result.warning).toBeNull();
expect(result.draft!.sourceType).toBe("sandbox_file");
expect(result.draft!.filename).toBe("hello.txt");
expect(result.draft!.mimeType).toBe("text/plain");
expect(result.draft!.sizeBytes).toBe(11);
expect(result.draft!.kind).toBe("document");
});
test("uses directive filename override when provided", () => {
writeFileSync(join(TEST_DIR, "data.txt"), "content");
const result = resolveSandboxDirective(
makeDirective({ path: "data.txt", filename: "custom-name.txt" }),
TEST_DIR,
);
expect(result.draft!.filename).toBe("custom-name.txt");
});
test("uses directive mimeType override when provided", () => {
writeFileSync(join(TEST_DIR, "data.bin"), Buffer.from([1, 2, 3]));
const result = resolveSandboxDirective(
makeDirective({ path: "data.bin", mimeType: "application/x-custom" }),
TEST_DIR,
);
expect(result.draft!.mimeType).toBe("application/x-custom");
});
test("infers MIME type from extension", () => {
writeFileSync(
join(TEST_DIR, "image.png"),
Buffer.from([0x89, 0x50, 0x4e, 0x47]),
);
const result = resolveSandboxDirective(
makeDirective({ path: "image.png" }),
TEST_DIR,
);
expect(result.draft!.mimeType).toBe("image/png");
expect(result.draft!.kind).toBe("image");
});
test("rejects paths that escape sandbox boundary", () => {
const result = resolveSandboxDirective(
makeDirective({ path: "../../etc/passwd" }),
TEST_DIR,
);
expect(result.draft).toBeNull();
expect(result.warning).toContain("outside the working directory");
});
test("warns when file does not exist", () => {
const result = resolveSandboxDirective(
makeDirective({ path: "nonexistent.txt" }),
TEST_DIR,
);
expect(result.draft).toBeNull();
expect(result.warning).toContain("file not found");
});
test("warns when path is a directory", () => {
const result = resolveSandboxDirective(
makeDirective({ path: "sub" }),
TEST_DIR,
);
expect(result.draft).toBeNull();
expect(result.warning).toContain("not a regular file");
});
test("resolves relative subdirectory paths", () => {
writeFileSync(join(TEST_DIR, "sub", "nested.json"), '{"key":"value"}');
const result = resolveSandboxDirective(
makeDirective({ path: "sub/nested.json" }),
TEST_DIR,
);
expect(result.draft).not.toBeNull();
expect(result.draft!.filename).toBe("nested.json");
expect(result.draft!.mimeType).toBe("application/json");
});
test("base64 encodes file content correctly", () => {
const content = "test content";
writeFileSync(join(TEST_DIR, "test.txt"), content);
const result = resolveSandboxDirective(
makeDirective({ path: "test.txt" }),
TEST_DIR,
);
expect(result.draft).not.toBeNull();
const decoded = Buffer.from(result.draft!.dataBase64, "base64").toString(
"utf-8",
);
expect(decoded).toBe(content);
});
});
// ---------------------------------------------------------------------------
// resolveHostDirective
// ---------------------------------------------------------------------------
describe("resolveHostDirective", () => {
beforeEach(() => {
mkdirSync(join(TEST_DIR, "host-sub"), { recursive: true });
});
afterEach(() => {
rmSync(TEST_DIR, { recursive: true, force: true });
});
function makeHostDirective(
overrides: Partial = {},
): DirectiveRequest {
return {
source: "host",
path: join(TEST_DIR, "doc.txt"),
filename: undefined,
mimeType: undefined,
...overrides,
};
}
const alwaysApprove = async () => true;
const alwaysDeny = async () => false;
test("resolves an approved host file to a draft", async () => {
const filePath = join(TEST_DIR, "doc.txt");
writeFileSync(filePath, "host content");
const result = await resolveHostDirective(
makeHostDirective(),
alwaysApprove,
);
expect(result.draft).not.toBeNull();
expect(result.warning).toBeNull();
expect(result.draft!.sourceType).toBe("host_file");
expect(result.draft!.filename).toBe("doc.txt");
expect(result.draft!.mimeType).toBe("text/plain");
expect(result.draft!.sizeBytes).toBe(12);
});
test("skips when user denies", async () => {
writeFileSync(join(TEST_DIR, "secret.txt"), "private");
const result = await resolveHostDirective(
makeHostDirective({ path: join(TEST_DIR, "secret.txt") }),
alwaysDeny,
);
expect(result.draft).toBeNull();
expect(result.warning).toContain("access denied by user");
});
test("rejects relative paths", async () => {
const result = await resolveHostDirective(
makeHostDirective({ path: "relative/path.txt" }),
alwaysApprove,
);
expect(result.draft).toBeNull();
expect(result.warning).toContain("must be absolute");
});
test("warns when file does not exist", async () => {
const result = await resolveHostDirective(
makeHostDirective({ path: join(TEST_DIR, "nonexistent.txt") }),
alwaysApprove,
);
expect(result.draft).toBeNull();
expect(result.warning).toContain("file not found");
});
test("warns when path is a directory", async () => {
const result = await resolveHostDirective(
makeHostDirective({ path: join(TEST_DIR, "host-sub") }),
alwaysApprove,
);
expect(result.draft).toBeNull();
expect(result.warning).toContain("not a regular file");
});
test("uses directive filename and mimeType overrides", async () => {
writeFileSync(join(TEST_DIR, "data.bin"), Buffer.from([1, 2, 3]));
const result = await resolveHostDirective(
makeHostDirective({
path: join(TEST_DIR, "data.bin"),
filename: "custom.dat",
mimeType: "application/x-custom",
}),
alwaysApprove,
);
expect(result.draft!.filename).toBe("custom.dat");
expect(result.draft!.mimeType).toBe("application/x-custom");
});
test("handles approval callback error gracefully", async () => {
writeFileSync(join(TEST_DIR, "doc.txt"), "content");
const failApprove = async () => {
throw new Error("connection lost");
};
const result = await resolveHostDirective(makeHostDirective(), failApprove);
expect(result.draft).toBeNull();
expect(result.warning).toContain("approval request failed");
});
test("calls approve with the resolved absolute path", async () => {
writeFileSync(join(TEST_DIR, "doc.txt"), "content");
let approvedPath = "";
const captureApprove = async (p: string) => {
approvedPath = p;
return true;
};
await resolveHostDirective(makeHostDirective(), captureApprove);
expect(approvedPath).toBe(join(TEST_DIR, "doc.txt"));
});
});