Skip to content

Tests: fail informatively instead of panicking the whole package - #27

Open
distronode-com wants to merge 1 commit into
Calnode:mainfrom
distronode-com:test/fail-instead-of-panic
Open

Tests: fail informatively instead of panicking the whole package#27
distronode-com wants to merge 1 commit into
Calnode:mainfrom
distronode-com:test/fail-instead-of-panic

Conversation

@distronode-com

Copy link
Copy Markdown

One failed request takes the whole handler package down

id := created["id"].(string) on a body that has no id panics, and a panic aborts the
entire test binary. So a single bad request reports a nil-interface stack trace and no
other handler test result at all
, with the status and error message nowhere on screen.

Measured, not inferred. With a create that really fails (a loopback webhook URL, rejected
by the SSRF guard) plus one unrelated passing test in the same package:

Before

--- FAIL: TestZZOldPattern
panic: interface conversion: interface {} is nil, not string [recovered, repanicked]
FAIL	github.com/calnode/calnode/internal/handler

The unrelated test never reports.

After

zz_panic_demo_test.go:32: create webhook: status = 400; want 201 —
  {"error":"webhook URL must not resolve to a private or loopback address:
   \"127.0.0.1\" resolved to a private or loopback address"}
--- FAIL: TestZZNewPattern
--- PASS: TestZZSomeOtherUnrelatedTest

The diagnosis is on the first line and every other test still reports. (Those two demo
tests were temporary and are not in the diff. That 400 is a real one — it cost real
diagnosis time hidden behind the panic.)

The change

Four helpers beside the existing ones in api_test.go, each taking a what that names the
call so a test making several requests still says which one failed:

mustStatus(t, rec, want, what)                  // status + body on failure
mustJSON(t, rec, want, what)   map[string]any   // status first, then decode
mustCreated(t, rec, what)      map[string]any   // mustJSON for 201
mustString(t, body, key, what) string           // two-value form, or fail with the body

Of the 32 ].(string) sites in internal/handler/*_test.go:

  • 10 used the single-value form and could panic. 6 of those (invites) decoded the body
    with no status check at all; 2 checked the status without printing the body. All 10 now
    assert the status first and read fields through mustString.
  • 18 already used the two-value form but silently yielded "" for a missing field,
    deferring the failure to something downstream that could not explain it — a 404 on an
    empty id. Those use mustString too.
  • 4 are deliberately left alone, because there the two-value form is the assertion:
    test_email_test.go:49, public_eventtype_test.go:64, claim_test.go:81, and the
    secret-length check in webhook_test.go.

No test's assertions changed. Where a status check was missing it was added, which only
makes an already-implicit dependency explicit. No non-test file is touched.

Testing

go test ./... green (26 packages); gofmt -l . empty; go vet ./... clean. Every test
that passed before passes now.

…ackage

`id := created["id"].(string)` on a response body that has no id panics, and a
panic aborts the WHOLE test binary: one bad request reported a nil-interface
stack trace and no other handler test result at all, with the status and error
message nowhere on screen.

Measured, not inferred. With a create that really fails (a loopback webhook URL,
rejected by the SSRF guard), the old pattern gives:

  --- FAIL: TestZZOldPattern
  panic: interface conversion: interface {} is nil, not string [recovered, repanicked]
  FAIL	github.com/calnode/calnode/internal/handler

and an unrelated passing test in the same package never reports at all. The same
failure through the new helpers gives:

  zz_panic_demo_test.go:32: create webhook: status = 400; want 201 —
    {"error":"webhook URL must not resolve to a private or loopback address:
     \"127.0.0.1\" resolved to a private or loopback address"}
  --- FAIL: TestZZNewPattern
  --- PASS: TestZZSomeOtherUnrelatedTest

which is the actual diagnosis, on the first line, with every other test still
reporting. (That 400 is a real one: it cost real time to find behind the panic.)

Four helpers next to the existing ones in api_test.go, each taking a `what` so a
test making several requests still says which call failed: mustStatus, mustJSON,
mustCreated, mustString.

Of the 32 `].(string)` sites in internal/handler/*_test.go, 10 used the
single-value form and could panic — 6 of those decoded the body with no status
check at all (invites) and 2 checked the status without printing the body. All 10
now assert the status first, quoting the body, and read fields through mustString.

The remaining 22 already used the two-value form, so they could not panic, but 18
of them silently yielded "" for a missing field and deferred the failure to
something downstream that could not explain it (a 404 on an empty id). Those now
use mustString too. The last 4 are left alone deliberately, because there the
two-value form IS the assertion: test_email_test.go:49, public_eventtype_test.go:64,
claim_test.go:81, and the secret-length check in webhook_test.go.

No test's assertions changed. Where a status check was missing it was added, which
only makes an already-implicit dependency explicit; every test that passed before
passes now.

Verified: gofmt -l . empty, go vet ./... clean, go test ./... exit 0 (26 packages,
no failures). No non-test file is touched.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes in this run: full PR diff (test-only helpers + call-site migrations; 1 commit on test/fail-instead-of-panic).

  • Informative response helpersmustStatus / mustJSON / mustCreated / mustString in api_test.go fail the single test with status + body instead of panicking the package or yielding empty ids that 404 later.
  • Call-site migration — panic-prone and silent two-value .(string) extracts in availability, invites, MCP OAuth, overrides, webhooks, and a few booking/rule paths now go through those helpers; status is checked before field reads where it was missing.
  • Intentionally untouched sites — remaining two-value asserts that are the assertion (and out-of-scope leftovers like calendar_test.go) match the PR description; no production code changed.

go test ./internal/handler/ passed on this head.

Pullfrog  | View workflow run | Using Grok𝕏

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant