lolly/internal/server/healthz_test.go
xfy f605ef3b44 feat(server): add /healthz and /readyz endpoints for Kubernetes probes
Add lightweight health check endpoints for container orchestration
(Kubernetes liveness/readiness probes, load balancer health checks).

New config under monitoring:
- healthz.enabled (default true), healthz.path (default /healthz)
- readyz.enabled (default true), readyz.path (default /readyz)

/healthz (liveness):
- Always returns 200 {"status":"ok"} if process is alive
- No dependency checks, minimal overhead

/readyz (readiness):
- Returns 200 {"status":"ready"} when server is running
- Returns 503 {"status":"not ready","reasons":[...]} when not ready
- Static-only servers (no proxies) always return 200

Registration:
- Registered alongside status/pprof endpoints
- Available in single mode (LocationEngine) and multi-server mode (Router)
- No IP allowlist required (K8s probes come from localhost)
- 6 unit tests covering all response scenarios
2026-06-11 23:41:45 +08:00

77 lines
2.1 KiB
Go

package server
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/assert"
"github.com/valyala/fasthttp"
)
func TestHealthzHandler(t *testing.T) {
t.Parallel()
var ctx fasthttp.RequestCtx
HealthzHandler(&ctx)
assert.Equal(t, 200, ctx.Response.StatusCode())
assert.Equal(t, "application/json", string(ctx.Response.Header.ContentType()))
assert.Equal(t, `{"status":"ok"}`, string(ctx.Response.Body()))
}
func TestHealthzHandler_ValidJSON(t *testing.T) {
t.Parallel()
var ctx fasthttp.RequestCtx
HealthzHandler(&ctx)
var result map[string]string
err := json.Unmarshal(ctx.Response.Body(), &result)
assert.NoError(t, err)
assert.Equal(t, "ok", result["status"])
}
func TestReadyzHandler_Ready(t *testing.T) {
t.Parallel()
handler := NewReadyzHandler(func() (bool, []string) {
return true, nil
})
var ctx fasthttp.RequestCtx
handler(&ctx)
assert.Equal(t, 200, ctx.Response.StatusCode())
assert.Equal(t, `{"status":"ready"}`, string(ctx.Response.Body()))
}
func TestReadyzHandler_NotReady(t *testing.T) {
t.Parallel()
handler := NewReadyzHandler(func() (bool, []string) {
return false, []string{"test reason"}
})
var ctx fasthttp.RequestCtx
handler(&ctx)
assert.Equal(t, 503, ctx.Response.StatusCode())
assert.Equal(t, `{"status":"not ready","reasons":["test reason"]}`, string(ctx.Response.Body()))
}
func TestReadyzHandler_NotReady_NoReasons(t *testing.T) {
t.Parallel()
handler := NewReadyzHandler(func() (bool, []string) {
return false, nil
})
var ctx fasthttp.RequestCtx
handler(&ctx)
assert.Equal(t, 503, ctx.Response.StatusCode())
assert.Equal(t, `{"status":"not ready"}`, string(ctx.Response.Body()))
}
func TestBuildReasonsJSON_MultipleReasons(t *testing.T) {
t.Parallel()
result := buildReasonsJSON([]string{"reason A", "reason B", "reason C"})
var parsed map[string]any
err := json.Unmarshal([]byte(result), &parsed)
assert.NoError(t, err)
assert.Equal(t, "not ready", parsed["status"])
reasons, ok := parsed["reasons"].([]any)
assert.True(t, ok)
assert.Equal(t, 3, len(reasons))
assert.Equal(t, "reason A", reasons[0])
assert.Equal(t, "reason B", reasons[1])
assert.Equal(t, "reason C", reasons[2])
}