package server_test import ( "encoding/json" "errors" "net/http" "net/http/httptest" "testing" "git.codelab.vc/pkg/httpx/server" ) func TestHealthHandler(t *testing.T) { t.Run("liveness always returns 200", func(t *testing.T) { h := server.HealthHandler() w := httptest.NewRecorder() req := httptest.NewRequest(http.MethodGet, "/healthz", nil) h.ServeHTTP(w, req) if w.Code != http.StatusOK { t.Fatalf("got status %d, want %d", w.Code, http.StatusOK) } var resp map[string]any if err := json.NewDecoder(w.Body).Decode(&resp); err != nil { t.Fatalf("decode failed: %v", err) } if resp["status"] != "ok" { t.Fatalf("got status %q, want %q", resp["status"], "ok") } }) t.Run("readiness returns 200 when all checks pass", func(t *testing.T) { h := server.HealthHandler( func() error { return nil }, func() error { return nil }, ) w := httptest.NewRecorder() req := httptest.NewRequest(http.MethodGet, "/readyz", nil) h.ServeHTTP(w, req) if w.Code != http.StatusOK { t.Fatalf("got status %d, want %d", w.Code, http.StatusOK) } }) t.Run("readiness returns 503 when a check fails", func(t *testing.T) { h := server.HealthHandler( func() error { return nil }, func() error { return errors.New("db down") }, ) w := httptest.NewRecorder() req := httptest.NewRequest(http.MethodGet, "/readyz", nil) h.ServeHTTP(w, req) if w.Code != http.StatusServiceUnavailable { t.Fatalf("got status %d, want %d", w.Code, http.StatusServiceUnavailable) } var resp map[string]any if err := json.NewDecoder(w.Body).Decode(&resp); err != nil { t.Fatalf("decode failed: %v", err) } if resp["status"] != "unavailable" { t.Fatalf("got status %q, want %q", resp["status"], "unavailable") } errs, ok := resp["errors"].([]any) if !ok || len(errs) != 1 { t.Fatalf("expected 1 error, got %v", resp["errors"]) } if errs[0] != "db down" { t.Fatalf("got error %q, want %q", errs[0], "db down") } }) t.Run("readiness returns 200 with no checkers", func(t *testing.T) { h := server.HealthHandler() w := httptest.NewRecorder() req := httptest.NewRequest(http.MethodGet, "/readyz", nil) h.ServeHTTP(w, req) if w.Code != http.StatusOK { t.Fatalf("got status %d, want %d", w.Code, http.StatusOK) } }) } func TestHealth_MultipleFailingCheckers(t *testing.T) { h := server.HealthHandler( func() error { return errors.New("db down") }, func() error { return errors.New("cache down") }, ) w := httptest.NewRecorder() req := httptest.NewRequest(http.MethodGet, "/readyz", nil) h.ServeHTTP(w, req) if w.Code != http.StatusServiceUnavailable { t.Fatalf("got status %d, want %d", w.Code, http.StatusServiceUnavailable) } var resp map[string]any if err := json.NewDecoder(w.Body).Decode(&resp); err != nil { t.Fatalf("decode failed: %v", err) } errs, ok := resp["errors"].([]any) if !ok || len(errs) != 2 { t.Fatalf("expected 2 errors, got %v", resp["errors"]) } errStrs := make(map[string]bool) for _, e := range errs { errStrs[e.(string)] = true } if !errStrs["db down"] || !errStrs["cache down"] { t.Fatalf("expected 'db down' and 'cache down', got %v", errs) } } func TestHealth_LivenessContentType(t *testing.T) { h := server.HealthHandler() w := httptest.NewRecorder() req := httptest.NewRequest(http.MethodGet, "/healthz", nil) h.ServeHTTP(w, req) ct := w.Header().Get("Content-Type") if ct != "application/json" { t.Fatalf("got Content-Type %q, want %q", ct, "application/json") } } func TestHealth_ReadinessContentType(t *testing.T) { h := server.HealthHandler() w := httptest.NewRecorder() req := httptest.NewRequest(http.MethodGet, "/readyz", nil) h.ServeHTTP(w, req) ct := w.Header().Get("Content-Type") if ct != "application/json" { t.Fatalf("got Content-Type %q, want %q", ct, "application/json") } } func TestHealth_PostMethodNotAllowed(t *testing.T) { h := server.HealthHandler() for _, path := range []string{"/healthz", "/readyz"} { t.Run("POST "+path, func(t *testing.T) { w := httptest.NewRecorder() req := httptest.NewRequest(http.MethodPost, path, nil) h.ServeHTTP(w, req) // ServeMux with "GET /healthz" pattern should reject POST. if w.Code == http.StatusOK { t.Fatalf("POST %s should not return 200, got %d", path, w.Code) } }) } }