internal/server/health/health_test.go
Ref: Size: 3.2 KiB History
package health
import (
"context"
"encoding/json"
"errors"
"net/http"
"net/http/httptest"
"testing"
"time"
)
func TestLive(t *testing.T) {
rec := httptest.NewRecorder()
Live(rec, httptest.NewRequest(http.MethodGet, "/livez", nil))
if rec.Code != http.StatusOK {
t.Fatalf("status = %d, want 200", rec.Code)
}
if body := rec.Body.String(); body != "ok\n" {
t.Fatalf("body = %q, want %q", body, "ok\n")
}
}
func TestReadyAllPass(t *testing.T) {
h := Ready(time.Second,
Check{Name: "db", Probe: func(context.Context) error { return nil }},
Check{Name: "cache", Probe: func(context.Context) error { return nil }},
)
rec := httptest.NewRecorder()
h(rec, httptest.NewRequest(http.MethodGet, "/readyz", nil))
if rec.Code != http.StatusOK {
t.Fatalf("status = %d, want 200", rec.Code)
}
var got struct {
Status string `json:"status"`
Checks map[string]string `json:"checks"`
}
if err := json.Unmarshal(rec.Body.Bytes(), &got); err != nil {
t.Fatalf("decode: %v", err)
}
if got.Status != "ready" {
t.Fatalf("status = %q, want ready", got.Status)
}
if got.Checks["db"] != "ok" || got.Checks["cache"] != "ok" {
t.Fatalf("checks = %v, want all ok", got.Checks)
}
}
func TestReadyOneFails(t *testing.T) {
h := Ready(time.Second,
Check{Name: "db", Probe: func(context.Context) error { return nil }},
Check{Name: "cache", Probe: func(context.Context) error { return errors.New("dial /run/cache.sock: connection refused") }},
)
rec := httptest.NewRecorder()
h(rec, httptest.NewRequest(http.MethodGet, "/readyz", nil))
if rec.Code != http.StatusServiceUnavailable {
t.Fatalf("status = %d, want 503", rec.Code)
}
var got struct {
Status string `json:"status"`
Checks map[string]string `json:"checks"`
}
if err := json.Unmarshal(rec.Body.Bytes(), &got); err != nil {
t.Fatalf("decode: %v", err)
}
if got.Status != "unready" {
t.Fatalf("status = %q, want unready", got.Status)
}
if got.Checks["db"] != "ok" {
t.Fatalf("db check = %q, want ok", got.Checks["db"])
}
// The body reports the failing check as unavailable WITHOUT leaking the raw
// error (which may carry socket paths / internal detail) to an
// unauthenticated endpoint.
if got.Checks["cache"] != "unavailable" {
t.Fatalf("cache check = %q, want unavailable", got.Checks["cache"])
}
if raw := rec.Body.String(); containsAny(raw, "connection refused", "/run/cache.sock") {
t.Fatalf("body leaked internal error detail: %q", raw)
}
}
func TestReadyProbeSeesTimeout(t *testing.T) {
// A probe that outlives the readiness budget must see a cancelled context —
// Ready bounds each probe so a wedged dependency cannot hang the endpoint.
var deadlineOK bool
h := Ready(10*time.Millisecond, Check{Name: "slow", Probe: func(ctx context.Context) error {
_, ok := ctx.Deadline()
deadlineOK = ok
return ctx.Err()
}})
rec := httptest.NewRecorder()
h(rec, httptest.NewRequest(http.MethodGet, "/readyz", nil))
if !deadlineOK {
t.Fatal("probe did not receive a deadline-bounded context")
}
}
func containsAny(s string, subs ...string) bool {
for _, sub := range subs {
for i := 0; i+len(sub) <= len(s); i++ {
if s[i:i+len(sub)] == sub {
return true
}
}
}
return false
}