* refactor(tools): extract shared module for gh spawnSync logic * feat(tools): add repo parameter to 5 GitHub tools * test(tools): add tunnel tool tests * test(tools): add repo parameter test cases for 5 tools * docs(handoff): scaffold handoff and ADR for PR * docs(handoff): set PR number * docs(project-map): update after PR#65 structural changes --------- Co-authored-by: opencode-agent <agent@opencode.local>
113 lines
No EOL
4.5 KiB
TypeScript
113 lines
No EOL
4.5 KiB
TypeScript
/**
|
|
* 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")
|
|
})
|
|
}) |