import { describe, test, expect } from "bun:test"; import { buildSamaCliInstallScript } from "./b32_sama_cli_install.ts"; describe("buildSamaCliInstallScript", () => { test("shebang + set -eu in the header", () => { const out = buildSamaCliInstallScript([]); expect(out.startsWith("#!/usr/bin/env bash\n")).toBe(true); expect(out).toContain("set -eu"); }); test("embeds each file inside a single-quoted heredoc", () => { const out = buildSamaCliInstallScript([ { path: "sama", content: "echo hi\n", executable: true }, ]); expect(out).toContain("cat > \"$INSTALL_DIR/sama\" <<'__SAMACLI_INSTALL_EOF__'"); expect(out).toContain("echo hi"); expect(out).toContain("__SAMACLI_INSTALL_EOF__\n"); expect(out).toContain('chmod +x "$INSTALL_DIR/sama"'); }); test("non-executable file omits chmod +x", () => { const out = buildSamaCliInstallScript([ { path: "README.md", content: "# hi\n" }, ]); expect(out).toContain('cat > "$INSTALL_DIR/README.md"'); expect(out).not.toContain('chmod +x "$INSTALL_DIR/README.md"'); }); test("emits mkdir -p for nested paths", () => { const out = buildSamaCliInstallScript([ { path: "src/a31_constants.sh", content: "X=1\n" }, ]); expect(out).toContain('mkdir -p "$INSTALL_DIR/$(dirname "src/a31_constants.sh")"'); }); test("symlinks sama into $BIN_LINK", () => { const out = buildSamaCliInstallScript([]); expect(out).toContain('ln -sf "$INSTALL_DIR/sama" "$BIN_LINK"'); }); test("refuses to embed a file containing the heredoc marker", () => { expect(() => buildSamaCliInstallScript([ { path: "evil.sh", content: "innocent\n__SAMACLI_INSTALL_EOF__\noops\n" }, ]), ).toThrow(/heredoc marker/); }); test("refuses path escapes", () => { expect(() => buildSamaCliInstallScript([{ path: "../evil.sh", content: "x\n" }]), ).toThrow(/path escape/); expect(() => buildSamaCliInstallScript([{ path: "/etc/passwd", content: "x\n" }]), ).toThrow(/path escape/); }); test("missing trailing newline still terminates heredoc cleanly", () => { // Content without trailing newline → emitter must add one so // the closing marker is recognized. const out = buildSamaCliInstallScript([ { path: "no-nl", content: "line1\nline2" }, ]); // The marker must land on its own line, preceded by the content + \n. expect(out).toMatch(/line2\n__SAMACLI_INSTALL_EOF__/); }); test("repo + spec URLs default to tdd.md", () => { const out = buildSamaCliInstallScript([]); expect(out).toContain("https://tdd.md/GIT/tdd.md/tree/main/tools/sama-cli"); expect(out).toContain("https://tdd.md/sama/v2"); }); test("custom URLs override defaults", () => { const out = buildSamaCliInstallScript([], { repoUrl: "https://example.org/repo", specUrl: "https://example.org/spec", }); expect(out).toContain("https://example.org/repo"); expect(out).toContain("https://example.org/spec"); }); test("end-to-end: produces a bash script that, when executed, writes the file content verbatim", async () => { // Run the emitted script in a temp dir, then read what it wrote. const dir = await Bun.file(`/tmp/sama-cli-install-test-${Date.now()}`).name; const out = buildSamaCliInstallScript([ { path: "src/a31.sh", content: "X=1\nY=2\n" }, { path: "sama", content: "#!/bin/sh\necho hi\n", executable: true }, ]); const proc = Bun.spawnSync({ cmd: ["bash", "-c", out], env: { ...process.env, SAMA_CLI_INSTALL_DIR: dir, SAMA_CLI_BIN_LINK: `${dir}-bin/sama`, }, }); expect(proc.exitCode).toBe(0); const wrote = await Bun.file(`${dir}/src/a31.sh`).text(); expect(wrote).toBe("X=1\nY=2\n"); const samaContent = await Bun.file(`${dir}/sama`).text(); expect(samaContent).toBe("#!/bin/sh\necho hi\n"); // Cleanup Bun.spawnSync({ cmd: ["rm", "-rf", dir, `${dir}-bin`] }); }); });