test: one gate, and a ceiling that was never chosen
This repo had no test target at all, so "run the tests" meant whatever each
person typed. `make test` is now the one command, and it is not a bare
`go test ./...`: that reuses cached PASS results, so a stale build reports green
for code you just changed, and it runs without the race detector, which is where
this repo's store and session defects surface. -count=1 defeats the cache.
Adding -race exposed the reason nobody had: 8 failures, all `i/o timeout`, all on
argon2id paths (signup, onboard, registry token, SCIM create), in a different
package each run. fiber's App.Test defaults to TestConfig{Timeout: time.Second}.
The detector costs roughly an order of magnitude, `go test ./...` runs packages
in parallel, and one second is simply not a valid budget for a deliberately
expensive KDF under that load. Each suite had inherited the default separately —
23 call sites, 19 files, none of them having chosen it.
That is the worst failure mode a gate can have. It is red without a defect, so it
teaches everyone to re-run until green, and a real regression hiding in the noise
gets re-run away with it.
internal/testhttp states the ceiling once — generous enough that only a genuine
hang trips it, bounded so a deadlock still fails rather than blocking forever —
and every suite drives the router through it. The KDF cost is untouched: it is
the security property, and lowering it for tests would be testing something else.
Verified: `make test` green end to end, and 0 remaining direct Fiber().Test calls
so no suite can drift back to its own timeout.
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
# The test gate. One command, run identically by a human and by CI.
|
||||
#
|
||||
# Not a bare `go test ./...`: that reuses cached PASS results, so a stale build
|
||||
# can report green for code you just changed, and it runs without the race
|
||||
# detector, which is where this repo's store and session defects actually show
|
||||
# up. -count=1 defeats the cache; -race is the point.
|
||||
|
||||
.PHONY: test build fmt vet
|
||||
|
||||
test: ## Run the full suite — the gate. Everything must be green to ship.
|
||||
go test ./... -race -count=1
|
||||
|
||||
build: ## Build every package.
|
||||
go build ./...
|
||||
|
||||
fmt: ## Format.
|
||||
go fmt ./...
|
||||
|
||||
vet: ## Vet.
|
||||
go vet ./...
|
||||
@@ -35,6 +35,8 @@ import (
|
||||
"github.com/hanzoai/iam/internal/routes"
|
||||
"github.com/hanzoai/iam/internal/schema"
|
||||
"github.com/hanzoai/iam/internal/store"
|
||||
|
||||
"github.com/hanzoai/iam/internal/testhttp"
|
||||
)
|
||||
|
||||
const signingKid = "cert-hanzo" // the seeded admin signing cert's name = JWKS kid
|
||||
@@ -187,7 +189,7 @@ func (h *harness) do(t *testing.T, method, path, bearer string, body any) int {
|
||||
if bearer != "" {
|
||||
req.Header.Set("Authorization", "Bearer "+bearer)
|
||||
}
|
||||
resp, err := h.app.Fiber().Test(req)
|
||||
resp, err := testhttp.Do(h.app, req)
|
||||
if err != nil {
|
||||
t.Fatalf("%s %s: %v", method, path, err)
|
||||
}
|
||||
@@ -220,7 +222,7 @@ func (h *harness) mcpToolCall(t *testing.T, bearer, tool string, args any) (stat
|
||||
if bearer != "" {
|
||||
req.Header.Set("Authorization", "Bearer "+bearer)
|
||||
}
|
||||
resp, err := h.app.Fiber().Test(req)
|
||||
resp, err := testhttp.Do(h.app, req)
|
||||
if err != nil {
|
||||
t.Fatalf("mcp tools/call %s: %v", tool, err)
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@ import (
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hanzoai/iam/internal/testhttp"
|
||||
)
|
||||
|
||||
// doBody is do() plus the response body — the read surface's real contract.
|
||||
@@ -32,7 +34,7 @@ func (h *harness) doBody(t *testing.T, method, path, bearer string, body any) (i
|
||||
if bearer != "" {
|
||||
req.Header.Set("Authorization", "Bearer "+bearer)
|
||||
}
|
||||
resp, err := h.app.Fiber().Test(req)
|
||||
resp, err := testhttp.Do(h.app, req)
|
||||
if err != nil {
|
||||
t.Fatalf("%s %s: %v", method, path, err)
|
||||
}
|
||||
|
||||
@@ -36,6 +36,8 @@ import (
|
||||
"github.com/hanzoai/orm"
|
||||
|
||||
"github.com/hanzoai/iam/internal/schema"
|
||||
|
||||
"github.com/hanzoai/iam/internal/testhttp"
|
||||
)
|
||||
|
||||
// The two spellings an unauthorized caller must not be able to tell apart: a
|
||||
@@ -95,7 +97,7 @@ func (h *harness) send(t *testing.T, method, path, auth string, body any) reply
|
||||
if auth != "" {
|
||||
req.Header.Set("Authorization", auth)
|
||||
}
|
||||
resp, err := h.app.Fiber().Test(req)
|
||||
resp, err := testhttp.Do(h.app, req)
|
||||
if err != nil {
|
||||
t.Fatalf("%s %s: %v", method, path, err)
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ import (
|
||||
"github.com/hanzoai/orm"
|
||||
|
||||
"github.com/hanzoai/iam/internal/schema"
|
||||
|
||||
"github.com/hanzoai/iam/internal/testhttp"
|
||||
)
|
||||
|
||||
// THE REQUEST CLOUD ACTUALLY MAKES.
|
||||
@@ -59,7 +61,7 @@ func (h *harness) basicGet(t *testing.T, path, clientID, secret string) int {
|
||||
req.Host = "hanzo.id"
|
||||
req.Header.Set("Authorization", "Basic "+
|
||||
base64.StdEncoding.EncodeToString([]byte(clientID+":"+secret)))
|
||||
resp, err := h.app.Fiber().Test(req)
|
||||
resp, err := testhttp.Do(h.app, req)
|
||||
if err != nil {
|
||||
t.Fatalf("GET %s: %v", path, err)
|
||||
}
|
||||
@@ -170,7 +172,7 @@ func TestSelfRead_ReturnsTheRowNotAnEmptyOk(t *testing.T) {
|
||||
req.Host = "hanzo.id"
|
||||
req.Header.Set("Authorization", "Basic "+
|
||||
base64.StdEncoding.EncodeToString([]byte("hanzo-cloud:s3cret")))
|
||||
resp, err := h.app.Fiber().Test(req)
|
||||
resp, err := testhttp.Do(h.app, req)
|
||||
if err != nil {
|
||||
t.Fatalf("request: %v", err)
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@ import (
|
||||
"github.com/hanzoai/iam/internal/routes"
|
||||
"github.com/hanzoai/iam/internal/schema"
|
||||
"github.com/hanzoai/iam/internal/store"
|
||||
|
||||
"github.com/hanzoai/iam/internal/testhttp"
|
||||
)
|
||||
|
||||
const svcToken = "svc-token-secret-value"
|
||||
@@ -49,7 +51,7 @@ func post(t *testing.T, app *zip.App, path, token, body string) (int, map[string
|
||||
if token != "" {
|
||||
req.Header.Set("Authorization", "Bearer "+token)
|
||||
}
|
||||
resp, err := app.Fiber().Test(req)
|
||||
resp, err := testhttp.Do(app, req)
|
||||
if err != nil {
|
||||
t.Fatalf("POST %s: %v", path, err)
|
||||
}
|
||||
|
||||
@@ -32,6 +32,8 @@ import (
|
||||
|
||||
"github.com/hanzoai/iam/internal/routes"
|
||||
"github.com/hanzoai/iam/internal/schema"
|
||||
|
||||
"github.com/hanzoai/iam/internal/testhttp"
|
||||
)
|
||||
|
||||
const signingKid = "cert-hanzo"
|
||||
@@ -112,7 +114,7 @@ func (h *harness) get(t *testing.T, path, bearer string) (int, string) {
|
||||
if bearer != "" {
|
||||
req.Header.Set("Authorization", "Bearer "+bearer)
|
||||
}
|
||||
resp, err := h.app.Fiber().Test(req)
|
||||
resp, err := testhttp.Do(h.app, req)
|
||||
if err != nil {
|
||||
t.Fatalf("GET %s: %v", path, err)
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ import (
|
||||
"github.com/hanzoai/orm"
|
||||
|
||||
"github.com/hanzoai/iam/internal/schema"
|
||||
|
||||
"github.com/hanzoai/iam/internal/testhttp"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -53,7 +55,7 @@ func (h *harness) getBasic(t *testing.T, path, clientID, secret string) (int, st
|
||||
req := httptest.NewRequest("GET", path, nil)
|
||||
req.Host = "hanzo.id"
|
||||
req.SetBasicAuth(clientID, secret)
|
||||
resp, err := h.app.Fiber().Test(req)
|
||||
resp, err := testhttp.Do(h.app, req)
|
||||
if err != nil {
|
||||
t.Fatalf("GET %s: %v", path, err)
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@ import (
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hanzoai/iam/internal/testhttp"
|
||||
)
|
||||
|
||||
// post issues a JSON POST through the real router and returns (status, rawBody).
|
||||
@@ -30,7 +32,7 @@ func (h *harness) post(t *testing.T, path, bearer string, body any) (int, string
|
||||
if bearer != "" {
|
||||
req.Header.Set("Authorization", "Bearer "+bearer)
|
||||
}
|
||||
resp, err := h.app.Fiber().Test(req)
|
||||
resp, err := testhttp.Do(h.app, req)
|
||||
if err != nil {
|
||||
t.Fatalf("POST %s: %v", path, err)
|
||||
}
|
||||
|
||||
@@ -36,6 +36,8 @@ import (
|
||||
"github.com/hanzoai/iam/internal/oidc"
|
||||
"github.com/hanzoai/iam/internal/routes"
|
||||
"github.com/hanzoai/iam/internal/schema"
|
||||
|
||||
"github.com/hanzoai/iam/internal/testhttp"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -251,7 +253,7 @@ func (e *env) form(t *testing.T, path, clientID, secret string, form url.Values)
|
||||
req.Host = "hanzo.id"
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
req.Header.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(clientID+":"+secret)))
|
||||
resp, err := e.app.Fiber().Test(req)
|
||||
resp, err := testhttp.Do(e.app, req)
|
||||
if err != nil {
|
||||
t.Fatalf("form %s: %v", path, err)
|
||||
}
|
||||
@@ -275,7 +277,7 @@ func (e *env) req(t *testing.T, method, path, bearer, body, contentType string)
|
||||
if bearer != "" {
|
||||
req.Header.Set("Authorization", "Bearer "+bearer)
|
||||
}
|
||||
resp, err := e.app.Fiber().Test(req)
|
||||
resp, err := testhttp.Do(e.app, req)
|
||||
if err != nil {
|
||||
t.Fatalf("%s %s: %v", method, path, err)
|
||||
}
|
||||
|
||||
@@ -34,6 +34,8 @@ import (
|
||||
"github.com/hanzoai/iam/internal/routes"
|
||||
"github.com/hanzoai/iam/internal/schema"
|
||||
"github.com/hanzoai/iam/internal/store"
|
||||
|
||||
"github.com/hanzoai/iam/internal/testhttp"
|
||||
)
|
||||
|
||||
const signingKid = "cert-hanzo"
|
||||
@@ -126,7 +128,7 @@ func (h *harness) postBasic(t *testing.T, path string, body any, clientID, secre
|
||||
// caller asserts on the status alone.
|
||||
func (h *harness) do(t *testing.T, req *http.Request) (int, env) {
|
||||
t.Helper()
|
||||
resp, err := h.app.Fiber().Test(req)
|
||||
resp, err := testhttp.Do(h.app, req)
|
||||
if err != nil {
|
||||
t.Fatalf("%s %s: %v", req.Method, req.URL.Path, err)
|
||||
}
|
||||
|
||||
@@ -33,6 +33,8 @@ import (
|
||||
"github.com/hanzoai/iam/internal/routes"
|
||||
"github.com/hanzoai/iam/internal/schema"
|
||||
"github.com/hanzoai/iam/internal/store"
|
||||
|
||||
"github.com/hanzoai/iam/internal/testhttp"
|
||||
)
|
||||
|
||||
const signingKid = "cert-hanzo"
|
||||
@@ -98,7 +100,7 @@ func (h *harness) do(t *testing.T, path, bearer, body string) (int, map[string]a
|
||||
if bearer != "" {
|
||||
req.Header.Set("Authorization", "Bearer "+bearer)
|
||||
}
|
||||
resp, err := h.app.Fiber().Test(req)
|
||||
resp, err := testhttp.Do(h.app, req)
|
||||
if err != nil {
|
||||
t.Fatalf("POST %s: %v", path, err)
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@ import (
|
||||
"github.com/zap-proto/zip"
|
||||
|
||||
"github.com/hanzoai/iam/internal/schema"
|
||||
|
||||
"github.com/hanzoai/iam/internal/testhttp"
|
||||
)
|
||||
|
||||
// HTTP-level test harness: register the whole OIDC surface on a fresh store and
|
||||
@@ -137,7 +139,7 @@ func jsonReq(method, path string, body any) *http.Request {
|
||||
|
||||
func do(t *testing.T, app *zip.App, req *http.Request) (*http.Response, []byte) {
|
||||
t.Helper()
|
||||
resp, err := app.Fiber().Test(req)
|
||||
resp, err := testhttp.Do(app, req)
|
||||
if err != nil {
|
||||
t.Fatalf("test request %s %s: %v", req.Method, req.URL.Path, err)
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@ import (
|
||||
|
||||
"github.com/hanzoai/iam/internal/routes"
|
||||
"github.com/hanzoai/iam/internal/schema"
|
||||
|
||||
"github.com/hanzoai/iam/internal/testhttp"
|
||||
)
|
||||
|
||||
// bootApp brings up the full IAM app over an embedded SQLite store, with the unified
|
||||
@@ -48,7 +50,7 @@ func postProvision(t *testing.T, app *zip.App, token, body string) (int, map[str
|
||||
if token != "" {
|
||||
req.Header.Set("Authorization", "Bearer "+token)
|
||||
}
|
||||
resp, err := app.Fiber().Test(req)
|
||||
resp, err := testhttp.Do(app, req)
|
||||
if err != nil {
|
||||
t.Fatalf("POST /v1/iam/admin/provision: %v", err)
|
||||
}
|
||||
|
||||
@@ -32,6 +32,8 @@ import (
|
||||
|
||||
"github.com/hanzoai/iam/internal/routes"
|
||||
"github.com/hanzoai/iam/internal/schema"
|
||||
|
||||
"github.com/hanzoai/iam/internal/testhttp"
|
||||
)
|
||||
|
||||
const signingKid = "cert-hanzo"
|
||||
@@ -96,7 +98,7 @@ func (h *harness) do(t *testing.T, method, path, bearer, body string) (int, map[
|
||||
if bearer != "" {
|
||||
req.Header.Set("Authorization", "Bearer "+bearer)
|
||||
}
|
||||
resp, err := h.app.Fiber().Test(req)
|
||||
resp, err := testhttp.Do(h.app, req)
|
||||
if err != nil {
|
||||
t.Fatalf("%s %s: %v", method, path, err)
|
||||
}
|
||||
|
||||
@@ -28,6 +28,8 @@ import (
|
||||
"github.com/hanzoai/iam/internal/schema"
|
||||
"github.com/hanzoai/iam/internal/store"
|
||||
"github.com/hanzoai/iam/internal/users"
|
||||
|
||||
"github.com/hanzoai/iam/internal/testhttp"
|
||||
)
|
||||
|
||||
// HTTP-level harness: every test drives the REAL registered token/jwks routes through
|
||||
@@ -240,7 +242,7 @@ func tokenPOST(t *testing.T, app *zip.App, id, secret, service string, scopes ..
|
||||
|
||||
func do(t *testing.T, app *zip.App, req *http.Request) (int, map[string]any, http.Header) {
|
||||
t.Helper()
|
||||
resp, err := app.Fiber().Test(req)
|
||||
resp, err := testhttp.Do(app, req)
|
||||
if err != nil {
|
||||
t.Fatalf("%s %s: %v", req.Method, req.URL.Path, err)
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@ import (
|
||||
|
||||
"github.com/hanzoai/iam/internal/routes"
|
||||
"github.com/hanzoai/iam/internal/schema"
|
||||
|
||||
"github.com/hanzoai/iam/internal/testhttp"
|
||||
)
|
||||
|
||||
// IAM EMBEDDED ALONGSIDE A SIBLING SUBSYSTEM.
|
||||
@@ -50,7 +52,7 @@ func TestGuard_DoesNotGateASiblingSubsystemsRoutes(t *testing.T) {
|
||||
|
||||
req := httptest.NewRequest("GET", "/v1/models", nil)
|
||||
req.Host = "api.hanzo.ai"
|
||||
resp, err := app.Fiber().Test(req)
|
||||
resp, err := testhttp.Do(app, req)
|
||||
if err != nil {
|
||||
t.Fatalf("GET /v1/models: %v", err)
|
||||
}
|
||||
@@ -91,7 +93,7 @@ func TestGuard_StillGatesIamsOwnPaths(t *testing.T) {
|
||||
} {
|
||||
req := httptest.NewRequest("GET", path, nil)
|
||||
req.Host = "hanzo.id"
|
||||
resp, err := app.Fiber().Test(req)
|
||||
resp, err := testhttp.Do(app, req)
|
||||
if err != nil {
|
||||
t.Fatalf("GET %s: %v", path, err)
|
||||
}
|
||||
|
||||
@@ -32,6 +32,8 @@ import (
|
||||
"github.com/hanzoai/iam/internal/routes"
|
||||
"github.com/hanzoai/iam/internal/schema"
|
||||
"github.com/hanzoai/iam/internal/store"
|
||||
|
||||
"github.com/hanzoai/iam/internal/testhttp"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -106,7 +108,7 @@ func (h *harness) do(t *testing.T, method, path, bearer, body string) (int, stri
|
||||
if bearer != "" {
|
||||
req.Header.Set("Authorization", "Bearer "+bearer)
|
||||
}
|
||||
resp, err := h.app.Fiber().Test(req)
|
||||
resp, err := testhttp.Do(h.app, req)
|
||||
if err != nil {
|
||||
t.Fatalf("%s %s: %v", method, path, err)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
// Copyright 2026 Hanzo AI, Inc. All rights reserved.
|
||||
|
||||
// Package testhttp is the ONE way a test drives the registered router.
|
||||
//
|
||||
// It exists for a single value: the timeout. fiber's App.Test defaults to
|
||||
// TestConfig{Timeout: time.Second}, and this repo's hot paths are argon2id —
|
||||
// deliberately expensive, and under -race (the detector costs roughly an order of
|
||||
// magnitude) on a box running `go test ./...` with packages in parallel, a signup,
|
||||
// an onboard, a registry token or a SCIM create routinely exceeds one second. The
|
||||
// suite then fails with `i/o timeout` on whichever package lost the CPU race, in a
|
||||
// different place each run.
|
||||
//
|
||||
// That is the worst failure a gate can have: it is red without a defect, so it
|
||||
// teaches everyone to re-run until green, and a real regression hiding among the
|
||||
// noise gets re-run away with it. The KDF cost is deliberate and must not be
|
||||
// lowered for tests; the one-second ceiling was simply never chosen.
|
||||
//
|
||||
// So the ceiling is stated once, here, generously — long enough that only a
|
||||
// genuine hang trips it, short enough that a deadlock still fails rather than
|
||||
// blocking the run forever. Every suite calls Do; none carries its own timeout,
|
||||
// so none can drift.
|
||||
package testhttp
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/zap-proto/fiber/v3"
|
||||
"github.com/zap-proto/zip"
|
||||
)
|
||||
|
||||
// timeout bounds one in-process test request. It is not a latency budget — no
|
||||
// assertion depends on it — only a deadlock guard.
|
||||
const timeout = 2 * time.Minute
|
||||
|
||||
// Do issues req against app's registered router in process and returns the
|
||||
// response. The caller closes the body.
|
||||
func Do(app *zip.App, req *http.Request) (*http.Response, error) {
|
||||
return app.Fiber().Test(req, fiber.TestConfig{Timeout: timeout, FailOnTimeout: true})
|
||||
}
|
||||
@@ -27,6 +27,8 @@ import (
|
||||
"github.com/hanzoai/iam/internal/authz"
|
||||
"github.com/hanzoai/iam/internal/oidc"
|
||||
"github.com/hanzoai/iam/internal/schema"
|
||||
|
||||
"github.com/hanzoai/iam/internal/testhttp"
|
||||
)
|
||||
|
||||
// HTTP-level harness: every test drives the REAL registered router behind the REAL
|
||||
@@ -121,7 +123,7 @@ func seed(t *testing.T, db orm.DB, o opts) *schema.Application {
|
||||
// a 200, the envelope proves what the client reads.
|
||||
func do(t *testing.T, app *zip.App, req *http.Request) (int, map[string]any) {
|
||||
t.Helper()
|
||||
resp, err := app.Fiber().Test(req)
|
||||
resp, err := testhttp.Do(app, req)
|
||||
if err != nil {
|
||||
t.Fatalf("%s %s: %v", req.Method, req.URL.Path, err)
|
||||
}
|
||||
|
||||
@@ -35,6 +35,8 @@ import (
|
||||
|
||||
"github.com/hanzoai/iam/internal/routes"
|
||||
"github.com/hanzoai/iam/internal/schema"
|
||||
|
||||
"github.com/hanzoai/iam/internal/testhttp"
|
||||
)
|
||||
|
||||
const signingKid = "cert-hanzo"
|
||||
@@ -99,7 +101,7 @@ func (h *harness) do(t *testing.T, method, path, bearer, body string) (int, map[
|
||||
if bearer != "" {
|
||||
req.Header.Set("Authorization", "Bearer "+bearer)
|
||||
}
|
||||
resp, err := h.app.Fiber().Test(req)
|
||||
resp, err := testhttp.Do(h.app, req)
|
||||
if err != nil {
|
||||
t.Fatalf("%s %s: %v", method, path, err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user