import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { boxUploadsDir, planBox } from "./bot-box/plan.ts";
import { attachmentsForBox } from "./box-attachments.ts";
describe("attachmentsForBox", () => {
let root: string, dataDir: string, uploads: string, cwd: string, shown: string;
beforeEach(() => {
root = mkdtempSync(join(tmpdir(), "box-attachments-"));
dataDir = join(root, "data");
uploads = join(dataDir, "attachments");
cwd = join(dataDir, "task-workspaces", "bot", "thread");
shown = boxUploadsDir(dataDir, "bot", "thread");
mkdirSync(uploads, { recursive: true });
mkdirSync(cwd, { recursive: true });
});
afterEach(() => rmSync(root, { recursive: true, force: true }));
const run = (text: string, at: string | undefined = cwd, boxed = true) => attachmentsForBox(text, at, boxed, dataDir, uploads);
it("puts each upload in the conversation's read-only uploads folder and points the engine there", () => {
const upload = join(uploads, "1f0c2a.md");
writeFileSync(upload, "# notes\n");
writeFileSync(join(uploads, "9b7e.png"), "png");
const text = `Read this\n\n`;
const out = run(text);
expect(out).toBe(`Read this\n\n`);
expect(readFileSync(join(shown, "1f0c2a.md"), "utf8")).toBe("# notes\n");
// Nothing is written into the working folder, which the bot controls.
expect(existsSync(join(cwd, ".attachments"))).toBe(false);
expect(run(text)).toBe(out);
});
it("finds an upload whose path the tag escaped", () => {
const dir = join(root, "a&b", "attachments");
mkdirSync(dir, { recursive: true });
writeFileSync(join(dir, "x.md"), "x");
const out = attachmentsForBox(``, cwd, true, dataDir, dir);
expect(out).toBe(``);
});
it("does not point at an upload that was deleted, even if an earlier turn linked it", () => {
const upload = join(uploads, "gone.md");
writeFileSync(upload, "x");
const text = ``;
expect(run(text)).not.toBe(text);
rmSync(upload);
expect(run(text)).toBe(text);
});
it("changes nothing with the box off, without a working folder, or outside a conversation's own folder", () => {
const upload = join(uploads, "a.md");
writeFileSync(upload, "x");
const text = ``;
expect(run(text, cwd, false)).toBe(text);
expect(attachmentsForBox(text, undefined, true, dataDir, uploads)).toBe(text);
expect(run(text, join(dataDir, "workspaces", "bot"))).toBe(text);
expect(existsSync(shown)).toBe(false);
});
it("leaves paths outside the uploads folder, odd names and missing files as they are", () => {
writeFileSync(join(root, "elsewhere.md"), "x");
writeFileSync(join(dataDir, "secret.md"), "x");
const text = [
``,
``,
``,
``,
].join("\n");
expect(run(text)).toBe(text);
});
it("is bound read-only into that conversation's boxes only, after its working folder", () => {
const setup = { dataDir, home: root, codeRoots: [], engineRoots: [], bridges: [] };
const targets = (at: string) => planBox(setup, { command: "/usr/bin/true", args: [], cwd: at, env: {} }).mounts.map((mount) => ("target" in mount ? mount.target : null));
const plan = planBox(setup, { command: "/usr/bin/true", args: [], cwd, env: {} });
const at = targets(cwd).indexOf(shown);
expect(plan.mounts[at]).toEqual({ kind: "ro", source: shown, target: shown, optional: true });
expect(at).toBeGreaterThan(targets(cwd).indexOf(cwd));
expect(targets(join(dataDir, "task-workspaces", "bot", "other"))).not.toContain(shown);
});
});