opencode-config/tests/test_tunnel_tool.ts
Sergey 15fc7d014d
refactor(tools): add repo parameter + shared module + tunnel tests (#65)
* 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>
2026-07-25 19:19:19 +03:00

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(".")
})
})