Files
kms/embed_test.go
T
zeekayandHanzo Dev cd15134667 test: put back the process-global state Embed changes, and measure only our goroutines
Four failures, both causes the same shape: state that belongs to the whole
process being read or written as if it belonged to one test.

TestEnvRequired_* (x3) failed with 401 on requests whose tokens were fine.
Embed applies ExpectedIssuer/ExpectedAudience/JWKSURL to the process-global
authConfig — that is what Embed is FOR — and stopping the instance does not put
it back. So after TestEmbed the process no longer accepted sharedIssuer, and
verification reached for a JWKS server TestEmbed had already closed. Every later
test minting a shared-issuer token got 401, and the only reason it was those
three is that they sort after TestEmbed. They passed in isolation, which is
what made it look like their problem.

jwtTestEnv.cleanup already restores exactly this; the embedded path had been
missed. Registered before jwks.Close so it runs last (t.Cleanup is LIFO).

TestConnMemory measured runtime.NumGoroutine() — process-wide — and divided it
by the connections under test. Run alone that ratio is 1.00; run inside the
package it also counts badger compactors, httptest servers and whatever else is
still up, and it read 1.15 against a [0.95, 1.05] band. The test was failing for
goroutines it does not own. It now takes a goroutine baseline and reports the
delta, which is what the heap half of the same function has always done two
lines above.

Package green, twice.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-27 08:53:08 -07:00

132 lines
4.2 KiB
Go

package kms
import (
"context"
"net/http"
"net/http/httptest"
"path/filepath"
"testing"
"time"
)
// TestEmbed asserts the public Embed() entry point boots a working
// KMS server (in-memory ZapDB, dev-mode auth, no listener) and the
// returned HTTPHandler answers /healthz with 200.
//
// Runs in <2s with no external services. Mirrors the iam.Embed()
// shape so a future fused hanzo binary can call Embed() the same way
// for both services.
func TestEmbed(t *testing.T) {
if testing.Short() {
t.Skip("skipping kms.Embed live test in -short mode")
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
// Stand up a fake JWKS so Embed has somewhere to point ExpectedIssuer
// and JWKSURL. There is no permissive env any more — every Hanzo
// user supplies these four fields or Embed refuses to boot.
jwks := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"keys":[]}`))
}))
// Registered BEFORE jwks.Close so it runs LAST (t.Cleanup is LIFO).
//
// Embed applies its ExpectedIssuer/ExpectedAudience/JWKSURL to the
// PROCESS-GLOBAL authConfig — that is the whole point of Embed — and
// stopping the instance does not put it back. Without this, every test
// after TestEmbed mints tokens for sharedIssuer/sharedAudience, which the
// process no longer accepts, and verification reaches for a JWKS server
// this cleanup has already closed. They all 401, and none of them are
// broken: TestEnvRequired_* were failing exactly this way, and only because
// they happen to sort after TestEmbed.
//
// jwtTestEnv.cleanup already does this; the embedded path had been missed.
t.Cleanup(func() {
applyAuthConfig(authCfgValues{
issuer: sharedIssuer,
audience: sharedAudience,
jwksURL: sharedJWKS.URL,
})
resetJWKSCacheForTest()
})
t.Cleanup(jwks.Close)
em, err := Embed(ctx, EmbedConfig{
DataDir: filepath.Join(t.TempDir(), "kms"),
AuditDB: filepath.Join(t.TempDir(), "audit.db"),
Env: "dev",
IAMEndpoint: jwks.URL,
ExpectedIssuer: jwks.URL,
ExpectedAudience: "kms-test",
JWKSURL: jwks.URL + "/.well-known/jwks",
SkipListen: true, // mount via httptest.Server
ZAPPort: -1, // disable ZAP (no master key in env)
})
if err != nil {
t.Fatalf("Embed: %v", err)
}
t.Cleanup(func() {
stopCtx, stopCancel := context.WithTimeout(context.Background(), 2*time.Second)
defer stopCancel()
if err := em.Stop(stopCtx); err != nil {
t.Errorf("Stop: %v", err)
}
})
srv := httptest.NewServer(em.HTTPHandler())
t.Cleanup(srv.Close)
t.Run("healthz", func(t *testing.T) {
resp, err := http.Get(srv.URL + "/healthz")
if err != nil {
t.Fatalf("GET /healthz: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Fatalf("GET /healthz: status %d, want 200", resp.StatusCode)
}
})
t.Run("v1_kms_health", func(t *testing.T) {
resp, err := http.Get(srv.URL + "/v1/kms/health")
if err != nil {
t.Fatalf("GET /v1/kms/health: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Fatalf("GET /v1/kms/health: status %d, want 200", resp.StatusCode)
}
})
t.Run("secret_route_requires_auth", func(t *testing.T) {
// Sanity check: even though Env=dev tolerates missing JWT config
// at boot, every secret route still demands a verified bearer
// token at request time. Unauthenticated → 401.
resp, err := http.Get(srv.URL + "/v1/kms/orgs/hanzo/secrets/foo/bar")
if err != nil {
t.Fatalf("GET secrets: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusUnauthorized {
t.Fatalf("unauthenticated GET: status %d, want 401", resp.StatusCode)
}
})
t.Run("stop_is_idempotent", func(t *testing.T) {
// Stop twice in a row must not panic or leak goroutines.
ctx1, c1 := context.WithTimeout(context.Background(), time.Second)
defer c1()
if err := em.Stop(ctx1); err != nil {
t.Errorf("first Stop: %v", err)
}
ctx2, c2 := context.WithTimeout(context.Background(), time.Second)
defer c2()
if err := em.Stop(ctx2); err != nil {
t.Errorf("second Stop: %v", err)
}
})
}