From 18e35bcd451ebf8b941f0dcaae883691129c4d84 Mon Sep 17 00:00:00 2001 From: kami Date: Sun, 26 Jul 2026 20:49:21 +0400 Subject: [PATCH] fix readiness router health contract --- internal/admin/admin.go | 5 ++--- internal/admin/admin_test.go | 40 ++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/internal/admin/admin.go b/internal/admin/admin.go index e467945..d4e99d1 100644 --- a/internal/admin/admin.go +++ b/internal/admin/admin.go @@ -67,10 +67,9 @@ func (s *Server) authorize(r *http.Request) error { } func (s *Server) Readiness(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") checks := []Probe{{Name: "store", Ready: s.Store != nil}} - if s.RouterReady { - checks = append(checks, Probe{Name: "router", Ready: true}) - } + checks = append(checks, Probe{Name: "router", Ready: s.RouterReady}) for name, probe := range s.Probes { ok, detail := probe() checks = append(checks, Probe{Name: name, Ready: ok, Detail: detail}) diff --git a/internal/admin/admin_test.go b/internal/admin/admin_test.go index f2e9062..56939af 100644 --- a/internal/admin/admin_test.go +++ b/internal/admin/admin_test.go @@ -3,6 +3,7 @@ package admin import ( "context" "encoding/json" + "net/http" "net/http/httptest" "orchestra/internal/authz" "orchestra/internal/domain" @@ -11,6 +12,45 @@ import ( "testing" ) +func TestReadinessRequiresRouterAndReturnsJSON(t *testing.T) { + s, err := store.Open(t.TempDir()) + if err != nil { + t.Fatal(err) + } + h := (&Server{Store: s, RouterReady: false}).Readiness + r := httptest.NewRequest(http.MethodGet, "/readyz", nil) + w := httptest.NewRecorder() + h(w, r) + if w.Code != http.StatusServiceUnavailable { + t.Fatalf("status=%d body=%s", w.Code, w.Body) + } + if got := w.Header().Get("Content-Type"); got != "application/json" { + t.Fatalf("content type=%q", got) + } + var body struct { + Ready bool `json:"ready"` + Checks []Probe `json:"checks"` + } + if err := json.Unmarshal(w.Body.Bytes(), &body); err != nil { + t.Fatal(err) + } + if body.Ready || len(body.Checks) != 2 || body.Checks[1].Name != "router" || body.Checks[1].Ready { + t.Fatalf("readiness=%+v", body) + } +} + +func TestReadinessSucceedsWhenStoreAndRouterReady(t *testing.T) { + s, err := store.Open(t.TempDir()) + if err != nil { + t.Fatal(err) + } + w := httptest.NewRecorder() + (&Server{Store: s, RouterReady: true}).Readiness(w, httptest.NewRequest(http.MethodGet, "/readyz", nil)) + if w.Code != http.StatusOK || !strings.Contains(w.Body.String(), `"ready":true`) { + t.Fatalf("status=%d body=%s", w.Code, w.Body) + } +} + func TestDiagnosticsRequiresFullControl(t *testing.T) { s, err := store.Open(t.TempDir()) if err != nil {