mirror of
https://github.com/hanzoai/kms.git
synced 2026-10-17 11:18:16 +00:00
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]>
132 lines
4.2 KiB
Go
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)
|
|
}
|
|
})
|
|
}
|