feat(health): enhance server with probe-specific handlers
- Add separate handlers for liveness (/healthz), readiness (/readyz), and startup (/startupz) probes - Implement WithLivenessHandler, WithReadinessHandler, WithStartupHandler, and WithServiceName options - Add probe-specific JSON response formats - Add comprehensive package documentation with usage examples - Maintain backward compatibility for /__health and / endpoints - Add tests for all probe types and fallback scenarios Enables proper Kubernetes health monitoring with different probe types.
This commit is contained in:
@@ -1,13 +1,14 @@
|
||||
package health
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHealthHandler(t *testing.T) {
|
||||
func TestBasicHealthHandler(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodGet, "/__health", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
@@ -24,3 +25,129 @@ func TestHealthHandler(t *testing.T) {
|
||||
t.Errorf("expected ok got %q", string(data))
|
||||
}
|
||||
}
|
||||
|
||||
func TestProbeHandlers(t *testing.T) {
|
||||
// Test with separate handlers for each probe type
|
||||
srv := NewServer(nil,
|
||||
WithLivenessHandler(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}),
|
||||
WithReadinessHandler(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}),
|
||||
WithStartupHandler(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}),
|
||||
WithServiceName("test-service"),
|
||||
)
|
||||
|
||||
tests := []struct {
|
||||
handler http.HandlerFunc
|
||||
expectedStatus int
|
||||
expectedBody string
|
||||
}{
|
||||
{srv.createProbeHandler("liveness"), 200, `{"status":"alive"}`},
|
||||
{srv.createProbeHandler("readiness"), 200, `{"ready":true}`},
|
||||
{srv.createProbeHandler("startup"), 200, `{"started":true}`},
|
||||
{srv.createGeneralHandler(), 200, `{"service":"test-service","status":"healthy"}`},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
t.Run(fmt.Sprintf("test_%d", i), func(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
tt.handler(w, req)
|
||||
|
||||
if w.Code != tt.expectedStatus {
|
||||
t.Errorf("expected status %d, got %d", tt.expectedStatus, w.Code)
|
||||
}
|
||||
|
||||
body := w.Body.String()
|
||||
if body != tt.expectedBody+"\n" { // json.Encoder adds newline
|
||||
t.Errorf("expected body %q, got %q", tt.expectedBody, body)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestProbeHandlerFallback(t *testing.T) {
|
||||
// Test fallback to general handler when no specific handler is configured
|
||||
generalHandler := func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
srv := NewServer(generalHandler, WithServiceName("test-service"))
|
||||
|
||||
tests := []struct {
|
||||
handler http.HandlerFunc
|
||||
expectedStatus int
|
||||
expectedBody string
|
||||
}{
|
||||
{srv.createProbeHandler("liveness"), 200, `{"status":"alive"}`},
|
||||
{srv.createProbeHandler("readiness"), 200, `{"ready":true}`},
|
||||
{srv.createProbeHandler("startup"), 200, `{"started":true}`},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
t.Run(fmt.Sprintf("fallback_%d", i), func(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
tt.handler(w, req)
|
||||
|
||||
if w.Code != tt.expectedStatus {
|
||||
t.Errorf("expected status %d, got %d", tt.expectedStatus, w.Code)
|
||||
}
|
||||
|
||||
body := w.Body.String()
|
||||
if body != tt.expectedBody+"\n" { // json.Encoder adds newline
|
||||
t.Errorf("expected body %q, got %q", tt.expectedBody, body)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnhealthyProbeHandlers(t *testing.T) {
|
||||
// Test with handlers that return unhealthy status
|
||||
srv := NewServer(nil,
|
||||
WithLivenessHandler(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusServiceUnavailable)
|
||||
}),
|
||||
WithReadinessHandler(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusServiceUnavailable)
|
||||
}),
|
||||
WithStartupHandler(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusServiceUnavailable)
|
||||
}),
|
||||
WithServiceName("test-service"),
|
||||
)
|
||||
|
||||
tests := []struct {
|
||||
handler http.HandlerFunc
|
||||
expectedStatus int
|
||||
expectedBody string
|
||||
}{
|
||||
{srv.createProbeHandler("liveness"), 503, `{"status":"unhealthy"}`},
|
||||
{srv.createProbeHandler("readiness"), 503, `{"ready":false}`},
|
||||
{srv.createProbeHandler("startup"), 503, `{"started":false}`},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
t.Run(fmt.Sprintf("unhealthy_%d", i), func(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
tt.handler(w, req)
|
||||
|
||||
if w.Code != tt.expectedStatus {
|
||||
t.Errorf("expected status %d, got %d", tt.expectedStatus, w.Code)
|
||||
}
|
||||
|
||||
body := w.Body.String()
|
||||
if body != tt.expectedBody+"\n" { // json.Encoder adds newline
|
||||
t.Errorf("expected body %q, got %q", tt.expectedBody, body)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user