import { homedir, userInfo } from "node:os";
import { afterEach, describe, expect, test } from "bun:test";
import { renderWorkspaceTopLevelContext } from "../workspace/top-level-renderer.js";
import type { TopLevelSnapshot } from "../workspace/top-level-scanner.js";
describe("renderWorkspaceTopLevelContext", () => {
test("renders basic snapshot with directories, files, and host env", () => {
const snapshot: TopLevelSnapshot = {
rootPath: "/sandbox",
directories: ["lib", "src", "tests"],
files: ["README.md", "package.json"],
truncated: false,
};
const result = renderWorkspaceTopLevelContext(snapshot);
expect(result).toBe(
[
"",
"Root: /sandbox",
"Directories: lib, src, tests",
"Files: README.md, package.json",
`Host home directory: ${homedir()}`,
`Host username: ${userInfo().username}`,
"",
].join("\n"),
);
});
test("includes truncation note when truncated", () => {
const snapshot: TopLevelSnapshot = {
rootPath: "/sandbox",
directories: ["a", "b"],
files: ["c.txt"],
truncated: true,
};
const result = renderWorkspaceTopLevelContext(snapshot);
expect(result).toContain("(list truncated — more entries exist)");
expect(result).toContain("Directories: a, b");
expect(result).toContain("Files: c.txt");
});
test("does not include truncation note when not truncated", () => {
const snapshot: TopLevelSnapshot = {
rootPath: "/sandbox",
directories: ["src"],
files: ["index.ts"],
truncated: false,
};
const result = renderWorkspaceTopLevelContext(snapshot);
expect(result).not.toContain("truncated");
});
test("renders empty directory and file lists", () => {
const snapshot: TopLevelSnapshot = {
rootPath: "/empty",
directories: [],
files: [],
truncated: false,
};
const result = renderWorkspaceTopLevelContext(snapshot);
expect(result).toBe(
[
"",
"Root: /empty",
"Directories: ",
"Files: ",
`Host home directory: ${homedir()}`,
`Host username: ${userInfo().username}`,
"",
].join("\n"),
);
});
test("produces stable output for equal input", () => {
const snapshot: TopLevelSnapshot = {
rootPath: "/sandbox",
directories: ["alpha", "beta", "gamma"],
files: ["config.json"],
truncated: false,
};
const r1 = renderWorkspaceTopLevelContext(snapshot);
const r2 = renderWorkspaceTopLevelContext(snapshot);
expect(r1).toBe(r2);
});
test("starts with opening tag and ends with closing tag", () => {
const snapshot: TopLevelSnapshot = {
rootPath: "/test",
directories: ["src"],
files: [],
truncated: false,
};
const result = renderWorkspaceTopLevelContext(snapshot);
expect(result.startsWith("")).toBe(true);
expect(result.endsWith("")).toBe(true);
});
test("includes hidden directories", () => {
const snapshot: TopLevelSnapshot = {
rootPath: "/project",
directories: [".git", ".vscode", "src"],
files: [".gitignore"],
truncated: false,
};
const result = renderWorkspaceTopLevelContext(snapshot);
expect(result).toContain(".git");
expect(result).toContain(".vscode");
expect(result).toContain("src");
expect(result).toContain(".gitignore");
});
test("renders files-only snapshot (no directories)", () => {
const snapshot: TopLevelSnapshot = {
rootPath: "/flat",
directories: [],
files: ["a.txt", "b.txt"],
truncated: false,
};
const result = renderWorkspaceTopLevelContext(snapshot);
expect(result).toContain("Directories: ");
expect(result).toContain("Files: a.txt, b.txt");
});
test("renders attachment path when provided", () => {
const snapshot: TopLevelSnapshot = {
rootPath: "/sandbox",
directories: ["src"],
files: ["package.json"],
truncated: false,
};
const result = renderWorkspaceTopLevelContext(snapshot, {
conversationAttachmentsPath:
"conversations/2026-03-19T12-00-00.000Z_conv-1/attachments/",
});
expect(result).toContain(
"Current conversation attachments: conversations/2026-03-19T12-00-00.000Z_conv-1/attachments/",
);
expect(result).not.toContain("Current conversation folder");
});
test("prefers client-reported host env over daemon os.homedir()", () => {
const snapshot: TopLevelSnapshot = {
rootPath: "/sandbox",
directories: ["src"],
files: ["package.json"],
truncated: false,
};
const result = renderWorkspaceTopLevelContext(snapshot, {
hostHomeDir: "/Users/alice",
hostUsername: "alice",
});
expect(result).toContain("Host home directory: /Users/alice");
expect(result).toContain("Host username: alice");
// Fallback values must NOT appear when client values are provided.
expect(result).not.toContain(`Host home directory: ${homedir()}`);
expect(result).not.toContain(`Host username: ${userInfo().username}`);
});
test("falls back to daemon os info when host env options omitted", () => {
const snapshot: TopLevelSnapshot = {
rootPath: "/sandbox",
directories: ["src"],
files: ["package.json"],
truncated: false,
};
const result = renderWorkspaceTopLevelContext(snapshot, {});
expect(result).toContain(`Host home directory: ${homedir()}`);
expect(result).toContain(`Host username: ${userInfo().username}`);
});
test("falls back to daemon os info when host env options are undefined", () => {
const snapshot: TopLevelSnapshot = {
rootPath: "/sandbox",
directories: ["src"],
files: ["package.json"],
truncated: false,
};
const result = renderWorkspaceTopLevelContext(snapshot, {
hostHomeDir: undefined,
hostUsername: undefined,
});
expect(result).toContain(`Host home directory: ${homedir()}`);
expect(result).toContain(`Host username: ${userInfo().username}`);
});
test("uses client home dir but falls back to os username when only home is provided", () => {
const snapshot: TopLevelSnapshot = {
rootPath: "/sandbox",
directories: ["src"],
files: ["package.json"],
truncated: false,
};
const result = renderWorkspaceTopLevelContext(snapshot, {
hostHomeDir: "/Users/alice",
});
expect(result).toContain("Host home directory: /Users/alice");
expect(result).toContain(`Host username: ${userInfo().username}`);
});
describe("containerized daemon", () => {
// In a container the daemon's own homedir/username describe the
// container, not the user's machine. Reporting them as the HOST values
// is what leads a host_bash command to write to a path that exists
// nowhere on the user's machine.
const priorIsContainerized = process.env.IS_CONTAINERIZED;
afterEach(() => {
if (priorIsContainerized === undefined) {
delete process.env.IS_CONTAINERIZED;
} else {
process.env.IS_CONTAINERIZED = priorIsContainerized;
}
});
const snapshot: TopLevelSnapshot = {
rootPath: "/workspace",
directories: ["src"],
files: ["package.json"],
truncated: false,
};
test("does not pass off the container's own home dir as the host's", () => {
process.env.IS_CONTAINERIZED = "true";
const result = renderWorkspaceTopLevelContext(snapshot, {});
expect(result).not.toContain(`Host home directory: ${homedir()}`);
// Name the way to find the real value rather than leaving a dead end.
expect(result.split("\n")).toEqual([
"",
"Root: /workspace",
"Directories: src",
"Files: package.json",
"Host home directory: unknown (ask the host shell: `host_bash` with `echo $HOME`)",
"Host username: unknown",
"",
]);
});
test("still reports the client-supplied host env when it is known", () => {
process.env.IS_CONTAINERIZED = "true";
const result = renderWorkspaceTopLevelContext(snapshot, {
hostHomeDir: "/Users/alice",
hostUsername: "alice",
});
expect(result).toContain("Host home directory: /Users/alice");
expect(result).toContain("Host username: alice");
});
});
});