/** * Tests for .opencode/tools/merge-pr.ts — the merge-pr custom tool. * * Mirror of tests/test_post_review_tool.ts / test_commit_tool.ts: * the tool is a spawnSync wrapper around `gh pr merge N --squash --delete-branch`. * * Runtime note: opencode ships a standalone binary with Bun bundled inside; * there is no separate `bun` CLI on the host (CI runner uses node + pytest). * The CI runs the equivalent Python tests in tests/test_merge_pr_tool.py * via the JS loader tests/_ts_loader.mjs (exec_stub_json mode for multi-arg * tools). This file documents the intended TS-side test cases and is * runnable under `bun test` once a bun runtime is available on the host. * * Test cases (mirror tests/test_merge_pr_tool.py): * - test_valid_merge — valid pr_number → "PR #N merged successfully..." * - test_spawnsync_args — spawnSync called with correct gh args (no --repo) * - test_repo_explicit — repo='foo/bar' prepends --repo to gh argv * - test_repo_omitted — no --repo flag (auto-detect from cwd) * - test_repo_invalid — invalid repo + gh failure → tool error */ import { describe, test, expect, mock } from "bun:test" with { type: "'bun-test'" } import { spawnSync } from "child_process" import path from "path" const TOOL_SRC = path.resolve(import.meta.dir, "..", ".opencode", "tools", "merge-pr.ts") function ctx() { return { sessionID: "t", messageID: "t", agent: "t", directory: ".", worktree: ".", abort: new AbortController().signal, metadata() {}, async ask() {}, } } const MERGE_OK = { status: 0, stdout: "", stderr: "" } describe("merge-pr tool", () => { test("test_valid_merge — valid pr_number succeeds", async () => { mock.module("child_process", () => ({ spawnSync: () => MERGE_OK, })) const mod = await import(TOOL_SRC + "?t=" + Date.now()) const result = await mod.default.execute({ pr_number: 42 }, ctx()) expect(result).toBe("PR #42 merged successfully (squash, branch deleted).") }) test("test_spawnsync_args — spawnSync called with gh pr merge N --squash --delete-branch (no --repo)", async () => { let capturedCmd let capturedArgs mock.module("child_process", () => ({ spawnSync: (cmd, args) => { capturedCmd = cmd capturedArgs = args return MERGE_OK }, })) const mod = await import(TOOL_SRC + "?t=" + Date.now()) await mod.default.execute({ pr_number: 42 }, ctx()) expect(capturedCmd).toBe("gh") expect(capturedArgs[0]).toBe("pr") expect(capturedArgs[1]).toBe("merge") expect(capturedArgs[2]).toBe("42") expect(capturedArgs).toContain("--squash") expect(capturedArgs).toContain("--delete-branch") // --repo MUST NOT be present — gh auto-detects from cwd (PR#60). expect(capturedArgs).not.toContain("--repo") }) test("test_repo_explicit — repo='foo/bar' prepends --repo to gh argv", async () => { let capturedArgs mock.module("child_process", () => ({ spawnSync: (_cmd, args) => { capturedArgs = args return MERGE_OK }, })) const mod = await import(TOOL_SRC + "?t=" + Date.now()) const result = await mod.default.execute({ pr_number: 42, repo: "foo/bar" }, ctx()) expect(result).toBe("PR #42 merged successfully (squash, branch deleted).") expect(capturedArgs[0]).toBe("--repo") expect(capturedArgs[1]).toBe("foo/bar") expect(capturedArgs[2]).toBe("pr") expect(capturedArgs).toContain("--squash") expect(capturedArgs).toContain("--delete-branch") }) test("test_repo_omitted — no --repo flag (auto-detect from cwd)", async () => { let capturedArgs mock.module("child_process", () => ({ spawnSync: (_cmd, args) => { capturedArgs = args return MERGE_OK }, })) const mod = await import(TOOL_SRC + "?t=" + Date.now()) await mod.default.execute({ pr_number: 42 }, ctx()) // Backward-compat: --repo MUST NOT be present when repo arg omitted. expect(capturedArgs).not.toContain("--repo") expect(capturedArgs[0]).toBe("pr") }) test("test_repo_invalid — invalid repo + gh failure → tool error", async () => { mock.module("child_process", () => ({ spawnSync: () => ({ status: 1, stdout: "", stderr: 'expected the "owner/repo" format' }), })) const mod = await import(TOOL_SRC + "?t=" + Date.now()) const result = await mod.default.execute({ pr_number: 42, repo: "not-a-valid-repo" }, ctx()) expect(result).toContain("merge_pr failed") expect(result).toContain("exit 1") }) })