import { describe, expect, test } from "bun:test"; import { MAX_TALE_BYTES, safeParseTale, taleByteSize } from "@taleseal/core"; import { buildRedactionReport } from "./gate"; import { INIT_TOOLS, type InitTool, type Runner, runToolSetup, welcomeTale } from "./init"; import { redactDeep } from "./redact"; /** Every tool value the command accepts, plus the bare-CLI case. */ const TOOL_CASES: (InitTool | undefined)[] = [...INIT_TOOLS, undefined]; const MODES = ["anonymous", "account"] as const; describe("welcomeTale", () => { test("is schema-valid for every tool × mode, opens with a lead, carries no image", () => { for (const tool of TOOL_CASES) { for (const mode of MODES) { const tale = welcomeTale(tool, { mode, now: new Date("2026-07-21T09:14:03Z") }); const parsed = safeParseTale(tale); expect(parsed.success).toBe(true); expect(tale.blocks[0]?.kind).toBe("lead"); expect(tale.blocks.some((b) => b.kind === "image")).toBe(false); expect(taleByteSize(tale)).toBeLessThan(MAX_TALE_BYTES); } } }); test("the template never trips the scrubber — redaction is a no-op with zero markers", () => { const tale = welcomeTale("claude", { mode: "anonymous" }); const scrubbed = redactDeep(tale); expect(scrubbed).toEqual(tale); expect(buildRedactionReport(scrubbed).total).toBe(0); }); test("names the tool; bare init stays generic", () => { const claude = JSON.stringify(welcomeTale("claude", { mode: "anonymous" })); expect(claude).toContain("Claude Code"); const bare = JSON.stringify(welcomeTale(undefined, { mode: "anonymous" })); expect(bare).not.toContain("Claude Code"); expect(bare).toContain("npx taleseal draft tale.json"); }); test("anonymous mode explains the 24-hour claim; account mode says the page is owned", () => { const anon = JSON.stringify(welcomeTale("codex", { mode: "anonymous" })); expect(anon).toContain("claim"); expect(anon).toContain("24 hours"); const owned = JSON.stringify(welcomeTale("codex", { mode: "account" })); expect(owned).toContain("already yours"); }); test("the timestamp is manual UTC, immune to locale", () => { const tale = welcomeTale(undefined, { mode: "anonymous", now: new Date("2026-07-21T09:14:03Z") }); expect(JSON.stringify(tale)).toContain("09:14 UTC on 21 July 2026"); }); }); describe("runToolSetup", () => { const enoent = (): NodeJS.ErrnoException => Object.assign(new Error("spawn ENOENT"), { code: "ENOENT" }); function recordingRunner(behaviour: (cmd: string, args: string[]) => ReturnType): { run: Runner; calls: string[]; } { const calls: string[] = []; const run: Runner = (cmd, args) => { calls.push(`${cmd} ${args.join(" ")}`); return behaviour(cmd, args); }; return { run, calls }; } test("claude: probes, adds the marketplace, installs — in that order", () => { const { run, calls } = recordingRunner(() => ({ status: 0 })); const outcome = runToolSetup("claude", run); expect(outcome.status).toBe("installed"); expect(calls).toEqual([ "claude --version", "claude plugin marketplace add Taleseal/taleseal", "claude plugin install taleseal@taleseal", ]); }); test("codex uses `plugin add`, not `plugin install`", () => { const { run, calls } = recordingRunner(() => ({ status: 0 })); expect(runToolSetup("codex", run).status).toBe("installed"); expect(calls[2]).toBe("codex plugin add taleseal@taleseal"); }); test("an absent tool CLI reports manual steps and never runs the plugin commands", () => { const { run, calls } = recordingRunner(() => ({ status: null, error: enoent() })); const outcome = runToolSetup("claude", run); expect(outcome.status).toBe("absent"); expect(outcome.lines.join("\n")).toContain("claude plugin marketplace add Taleseal/taleseal"); expect(calls).toEqual(["claude --version"]); }); test("a failing marketplace add is non-fatal — the install step still runs", () => { const { run, calls } = recordingRunner((_cmd, args) => args.includes("marketplace") ? { status: 1, stderr: "already added" } : { status: 0 }, ); expect(runToolSetup("claude", run).status).toBe("installed"); expect(calls).toHaveLength(3); }); test("a failing install reports failed, with the last stderr line as detail", () => { const { run } = recordingRunner((_cmd, args) => args.includes("install") ? { status: 1, stderr: "boom\nplugin not found" } : { status: 0 }, ); const outcome = runToolSetup("claude", run); expect(outcome.status).toBe("failed"); if (outcome.status === "failed") expect(outcome.detail).toBe("plugin not found"); }); test("cursor is manual-only and spawns nothing; bare init is skipped", () => { const { run, calls } = recordingRunner(() => ({ status: 0 })); expect(runToolSetup("cursor", run).status).toBe("manual"); expect(runToolSetup(undefined, run).status).toBe("skipped"); expect(calls).toEqual([]); }); });