* 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>
93 lines
No EOL
3.9 KiB
TypeScript
93 lines
No EOL
3.9 KiB
TypeScript
/**
|
|
* Tests for .opencode/tools/tunnel.ts — the tunnel custom tool.
|
|
*
|
|
* The tool is a thin spawnSync wrapper around `bash .opencode/scripts/tunnel.sh`
|
|
* (toggle: 1st call starts, 2nd call stops). The toggle/PID-file logic lives
|
|
* in the bash script and is covered by tests/test_tunnel_tool.py (which runs
|
|
* the script directly with a fake cloudflared). These TS tests stub spawnSync
|
|
* to verify the tool wiring: it invokes `bash <script>` with cwd from context,
|
|
* returns stdout on success, and formats the canonical `⚠️ tunnel failed` error
|
|
* on non-zero exit.
|
|
*
|
|
* 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_tunnel_tool.py.
|
|
*
|
|
* Test cases (mirror tests/test_tunnel_tool.py success/error wiring):
|
|
* - test_starts_with_bash_script — spawnSync called with bash + script path
|
|
* - test_success_returns_stdout — exit 0 + stdout → returns trimmed stdout
|
|
* - test_failure_returns_error — exit non-zero → "⚠️ tunnel failed (exit K): ..."
|
|
* - test_uses_cwd_from_context — spawnSync opts.cwd == context.worktree
|
|
*/
|
|
|
|
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", "tunnel.ts")
|
|
|
|
function ctx() {
|
|
return {
|
|
sessionID: "t", messageID: "t", agent: "t",
|
|
directory: ".", worktree: ".",
|
|
abort: new AbortController().signal,
|
|
metadata() {}, async ask() {},
|
|
}
|
|
}
|
|
|
|
describe("tunnel tool", () => {
|
|
test("test_starts_with_bash_script — spawnSync called with bash + script path", async () => {
|
|
let capturedCmd
|
|
let capturedArgs
|
|
mock.module("child_process", () => ({
|
|
spawnSync: (cmd, args) => {
|
|
capturedCmd = cmd
|
|
capturedArgs = args
|
|
return { status: 0, stdout: "started (PID: 12345)\n", stderr: "" }
|
|
},
|
|
}))
|
|
const mod = await import(TOOL_SRC + "?t=" + Date.now())
|
|
const result = await mod.default.execute({}, ctx())
|
|
expect(capturedCmd).toBe("bash")
|
|
// The single arg is the path to .opencode/scripts/tunnel.sh (resolved
|
|
// relative to the tool's own directory via import.meta.dir/../scripts).
|
|
expect(capturedArgs).toHaveLength(1)
|
|
expect(capturedArgs[0]).toContain("tunnel.sh")
|
|
expect(result).toBe("started (PID: 12345)")
|
|
})
|
|
|
|
test("test_success_returns_stdout — exit 0 + stdout → returns trimmed stdout", async () => {
|
|
mock.module("child_process", () => ({
|
|
spawnSync: () => ({ status: 0, stdout: "stopped\n", stderr: "" }),
|
|
}))
|
|
const mod = await import(TOOL_SRC + "?t=" + Date.now())
|
|
const result = await mod.default.execute({}, ctx())
|
|
expect(result).toBe("stopped")
|
|
})
|
|
|
|
test("test_failure_returns_error — exit non-zero → '⚠️ tunnel failed (exit K): ...'", async () => {
|
|
mock.module("child_process", () => ({
|
|
spawnSync: () => ({ status: 1, stdout: "", stderr: "❌ CLOUDFLARE_TUNNEL_TOKEN is not set" }),
|
|
}))
|
|
const mod = await import(TOOL_SRC + "?t=" + Date.now())
|
|
const result = await mod.default.execute({}, ctx())
|
|
expect(result).toContain("⚠️ tunnel failed")
|
|
expect(result).toContain("exit 1")
|
|
expect(result).toContain("CLOUDFLARE_TUNNEL_TOKEN is not set")
|
|
})
|
|
|
|
test("test_uses_cwd_from_context — spawnSync opts.cwd == context.worktree", async () => {
|
|
let capturedOpts
|
|
mock.module("child_process", () => ({
|
|
spawnSync: (_cmd, _args, opts) => {
|
|
capturedOpts = opts
|
|
return { status: 0, stdout: "started (PID: 1)\n", stderr: "" }
|
|
},
|
|
}))
|
|
const mod = await import(TOOL_SRC + "?t=" + Date.now())
|
|
await mod.default.execute({}, ctx())
|
|
expect(capturedOpts).not.toBeNull()
|
|
expect(capturedOpts).toHaveProperty("cwd")
|
|
expect(capturedOpts.cwd).toBe(".")
|
|
})
|
|
}) |