Repository navigation
Add a bats (or mocked-curl) test covering the offline-reclaim and online-refuse branches #159
Description
What
Add an automated test that exercises the two branches of reclaim_orphaned_name() in entrypoint.sh:
- Offline-reclaim branch — a same-named runner is listed with
status == "offline", so the function issues aDELETEand returns success. - Online-refuse branch — a same-named runner is listed with a non-
offlinestatus, so the function does not delete and the entrypoint refuses to start (preserving the deliberate no---replacestance).
The test must not require a live GitHub repo or network access.
Why
The reclaim logic is the core of PR #154 and its two decision branches are exactly the kind of behaviour that silently regresses: a future refactor of the jq extraction, the [[ -n "${id}" ]] guard, or the status comparison could flip the online-refuse branch into a delete and nobody would notice until a live runner was clobbered. The manual reproduce steps in the original issue (hard-kill, restart, observe crashloop) require a live repo and a rate-limited host, so they are not a regression guard. A bats test (or a shell test with a mocked curl) pins both branches down cheaply.
How
- Add a
batstest file (e.g.test/entrypoint.bats) or, ifbatsis not acceptable as a dev dependency, a plain shell test that sourcesentrypoint.shwith a mockedcurlonPATH. - Mock
curlso that:- The listing call (
GET /actions/runners) returns a JSON body containing a single runner with the sameRUNNER_NAMEand a controllablestatus. - The
DELETEcall records that it was invoked (e.g. writes to a temp file) and returns a success status.
- The listing call (
- Case 1 (offline): assert the mock
DELETEwas invoked exactly once and the function returned success. - Case 2 (online): assert the mock
DELETEwas not invoked and the entrypoint exits non-zero / refuses to start. - Keep the mock minimal — only the endpoints
reclaim_orphaned_name()actually calls need to be stubbed. Do not attempt to mock the full registration flow. - If
batsis used, add a short note to the repo's test docs (or the PR description) on how to run it locally.
Files Affected
entrypoint.sh(source under test; no changes required unless a seam is needed to make it sourceable)test/entrypoint.bats(new — or an equivalent shell test path ifbatsis not adopted)- Unknown (test runner config / CI workflow, if one exists and needs to invoke the new test)
Constraints
- Must not require network access, a live GitHub repo, or a real PAT.
- Must not change the runtime behaviour of
entrypoint.sh; this is test-only. If a small refactor is needed to make the function sourceable/testable, it must be behaviour-preserving. - Out of scope: the other follow-ups from the same review (the unconditional
rm -f .runner, thecommand -v jqguard, the single-passjqextraction,Retry-Afterhandling). Those are separate issues. - Out of scope: testing the
jq-missing silent no-op path — that is covered by the separatejqguard follow-up.
LLM tier
Sonnet — new test coverage requiring design decisions (how to mock curl, how to make the entrypoint sourceable, what to assert), but no architectural ambiguity.
Success looks like
- A test exists that fails if the offline branch stops issuing the
DELETE. - A test exists that fails if the online branch starts issuing a
DELETE(or stops refusing to start). - Both tests pass on the current
entrypoint.shfrom PR Reclaim our own offline registration instead of crashlooping on it #154. - The tests run without network access and without a GitHub token.
- Running the test suite is documented (command + any prerequisites).
Failure looks like
- Tests that only assert the function "ran" without checking whether
DELETEwas called — they would pass on both branches and guard nothing. - Tests that require a live repo, a real token, or network access.
- A refactor of
entrypoint.shthat changes runtime behaviour in order to make it testable. - The online-refuse case being tested only by asserting a non-zero exit, without asserting that no
DELETEwas issued.
Follow-up from AI review of PR #154.
Activity