import { afterEach, describe, expect, test } from "bun:test"; import { prFiles, prView } from "./gh/auth-pr-read"; import { issueBodyReadCommands, prBodyReadCommands } from "./gh/client"; import { withGhDefaultRendered } from "./gh/default-render"; import { ghScopedHelp, ghScopedHelpNotes } from "./gh/help"; import { runGhCommand } from "./gh/index"; import { renderIssueCreateTable } from "./gh/issue-write"; import { readViewSupportedCommands } from "./gh/refs"; const originalFetch = globalThis.fetch; afterEach(() => { globalThis.fetch = originalFetch; }); function renderedText(value: unknown): string { return String((value as { renderedText?: unknown }).renderedText ?? ""); } function nextSection(text: string): string { return text.split("Next:\n", 2)[1]?.split("\n\nDisclosure:", 1)[0] ?? ""; } describe("gh default body read path", () => { test("puts trans gh issue cat first and keeps comments bounded", () => { const repo = "pikasTech/unidesk"; const rendered = renderedText(withGhDefaultRendered(["issue", "view", "1890", "--repo", repo], { ok: true, command: "issue view", repo, issue: { number: 1890, title: "trans 文本短路径", state: "open", updatedAt: "2026-07-13T00:00:00Z", commentCount: 8, bodyChars: 30_000, bodySha: "abc123", bodyPreview: "preview", bodyOmitted: true, }, readCommands: issueBodyReadCommands(repo, 1890), })); const next = nextSection(rendered); expect(next.trimStart().split("\n")[0]).toBe("trans gh:/pikasTech/unidesk/issue/1890 cat"); expect(next).toContain("trans gh:/pikasTech/unidesk/issue/1890 rg "); expect(next).toContain("bun scripts/cli.ts gh issue comments 1890 --repo pikasTech/unidesk"); expect(next).not.toContain("--json body"); expect(next).not.toContain("--full"); expect(rendered).not.toContain("/tmp/unidesk-cli-output"); }); test("puts trans gh PR cat before file review commands", () => { const repo = "pikasTech/unidesk"; const rendered = renderedText(withGhDefaultRendered(["pr", "view", "1893", "--repo", repo], { ok: true, command: "pr view", repo, pullRequest: { number: 1893, title: "fix trans", state: "open", stateDetail: "open", head: { ref: "fix/trans" }, base: { ref: "master" }, bodyChars: 1_000, bodySha: "def456", bodyPreview: "preview", bodyOmitted: true, }, readCommands: prBodyReadCommands(repo, 1893), })); const next = nextSection(rendered); expect(next.trimStart().split("\n")[0]).toBe("trans gh:/pikasTech/unidesk/pr/1893 cat"); expect(next).toContain("trans gh:/pikasTech/unidesk/pr/1893 rg "); expect(next).toContain("bun scripts/cli.ts gh pr files 1893 --repo pikasTech/unidesk"); expect(next).not.toContain("--json body"); expect(next).not.toContain("--full"); expect(rendered).not.toContain("/tmp/unidesk-cli-output"); }); test("keeps machine body disclosure explicit in shared read commands", () => { expect(issueBodyReadCommands("owner/repo", 12)).toMatchObject({ body: "trans gh:/owner/repo/issue/12 cat", json: "bun scripts/cli.ts gh issue view 12 --repo owner/repo --json body", }); expect(prBodyReadCommands("owner/repo", 34)).toMatchObject({ body: "trans gh:/owner/repo/pr/34 cat", json: "bun scripts/cli.ts gh pr view 34 --repo owner/repo --json body", }); }); test("scoped help separates human body reads from machine disclosure", () => { const issueNotes = ghScopedHelpNotes(["issue", "view"]).join("\n"); const prNotes = ghScopedHelpNotes(["pr", "view"]).join("\n"); expect(issueNotes).toContain("trans gh:/owner/repo/issue/ cat"); expect(issueNotes).toContain("explicit structured machine disclosure"); expect(prNotes).toContain("trans gh:/owner/repo/pr/ cat"); expect(prNotes).toContain("explicit structured machine disclosure"); const issueHelp = JSON.stringify(ghScopedHelp(["issue", "view", "--help"])); const prHelp = JSON.stringify(ghScopedHelp(["pr", "view", "--help"])); expect(issueHelp).toContain("trans gh:/owner/repo/issue/ cat"); expect(prHelp).toContain("trans gh:/owner/repo/pr/ cat"); }); test("uses PR cat after a complete bounded file listing", async () => { globalThis.fetch = (async (input: string | URL | Request) => { const url = new URL(typeof input === "string" || input instanceof URL ? input.toString() : input.url); if (url.pathname.endsWith("/pulls/42")) { return Response.json({ id: 42, number: 42, title: "bounded PR", body: "body", state: "open", html_url: "https://github.com/owner/repo/pull/42", head: { ref: "fix/test", sha: "a".repeat(40) }, base: { ref: "master", sha: "b".repeat(40) }, changed_files: 1, additions: 2, deletions: 1, commits: 1, }); } return Response.json([{ filename: "src/test.ts", status: "modified", additions: 2, deletions: 1, changes: 3 }]); }) as typeof fetch; const result = await prFiles("owner/repo", "token", 42, 30); expect((result.next as { command?: string }).command).toBe("trans gh:/owner/repo/pr/42 cat"); expect(result.readCommands).toMatchObject({ body: "trans gh:/owner/repo/pr/42 cat", metadata: "bun scripts/cli.ts gh pr view 42 --repo owner/repo", }); const view = await prView("owner/repo", "token", 42, undefined); expect(view.readCommands).toMatchObject({ body: "trans gh:/owner/repo/pr/42 cat", search: "trans gh:/owner/repo/pr/42 rg ", }); }); test("uses cat and rg after issue create and PR writes", () => { const issueRendered = renderIssueCreateTable({ ok: true, command: "issue create", repo: "owner/repo", issue: { number: 12, title: "new issue", state: "open" }, }); const prRendered = renderedText(withGhDefaultRendered(["pr", "update", "34", "--repo", "owner/repo"], { ok: true, command: "pr update", repo: "owner/repo", number: 34, pullRequest: { number: 34, title: "updated PR", state: "open" }, })); for (const [text, route] of [ [issueRendered, "trans gh:/owner/repo/issue/12"], [prRendered, "trans gh:/owner/repo/pr/34"], ] as const) { const next = nextSection(text); expect(next).toContain(`${route} cat`); expect(next).toContain(`${route} rg `); expect(next).not.toContain("--json body"); expect(next).not.toContain("--full"); expect(text).not.toContain("/tmp/unidesk-cli-output"); } }); test("keeps invalid --number guidance on human issue and PR routes", async () => { const result = await runGhCommand(["issue", "list", "--number", "12", "--repo", "owner/repo"]) as { supportedCommands?: string[]; }; expect(result.supportedCommands).toEqual([ "trans gh:/owner/name/issue/ cat", "trans gh:/owner/name/issue/ rg ", "trans gh:/owner/name/pr/ cat", "trans gh:/owner/name/pr/ rg ", ]); expect(readViewSupportedCommands("issue", "owner/repo", 12)).toEqual([ "trans gh:/owner/repo/issue/12 cat", "trans gh:/owner/repo/issue/12 rg ", "bun scripts/cli.ts gh issue view 12 --repo owner/repo", ]); }); });