package httpx import ( "context" "encoding/base64" "encoding/json" "errors" "io" "log/slog" "net/http" "net/http/httptest" "testing" "bountyboard/internal/config" "bountyboard/internal/metrics" "bountyboard/internal/ulid" ) func newTestServer(t *testing.T) *Server { t.Helper() t.Setenv("SESSION_SECRET", "0123456789abcdef0123456789abcdef") t.Setenv("CREDENTIALS_ENC_KEY", base64.StdEncoding.EncodeToString(make([]byte, 32))) cfg, err := config.Load() if err != nil { t.Fatalf("config: %v", err) } log := slog.New(slog.NewTextHandler(io.Discard, nil)) return New(cfg, log, metrics.NewRegistry(), nil, nil) } func get(t *testing.T, h http.Handler, path string) *httptest.ResponseRecorder { t.Helper() req := httptest.NewRequest(http.MethodGet, path, nil) rec := httptest.NewRecorder() h.ServeHTTP(rec, req) return rec } func TestHealthz(t *testing.T) { s := newTestServer(t) rec := get(t, s.Handler(), "/healthz") if rec.Code != http.StatusOK { t.Fatalf("status = %d", rec.Code) } var body map[string]string if err := json.Unmarshal(rec.Body.Bytes(), &body); err != nil { t.Fatal(err) } if body["status"] != "ok" { t.Errorf("status field = %q", body["status"]) } if id := rec.Header().Get("X-Request-Id"); !ulid.IsValid(id) { t.Errorf("X-Request-Id %q is not a ULID", id) } } func TestReadyzAggregation(t *testing.T) { tests := []struct { name string checks []ReadinessCheck wantStatus string wantCode int }{ { name: "no checks", wantStatus: "ok", wantCode: http.StatusOK, }, { name: "all passing", checks: []ReadinessCheck{ {Name: "mongo", Required: true, Probe: func(context.Context) error { return nil }}, {Name: "atomizer", Probe: func(context.Context) error { return nil }}, }, wantStatus: "ok", wantCode: http.StatusOK, }, { name: "optional failing is non-fatal", checks: []ReadinessCheck{ {Name: "mongo", Required: true, Probe: func(context.Context) error { return nil }}, {Name: "atomizer", Probe: func(context.Context) error { return errors.New("down") }}, }, wantStatus: "degraded", wantCode: http.StatusOK, }, { name: "required failing gates readiness", checks: []ReadinessCheck{ {Name: "mongo", Required: true, Probe: func(context.Context) error { return errors.New("no reachable servers") }}, }, wantStatus: "unavailable", wantCode: http.StatusServiceUnavailable, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { s := newTestServer(t) for _, c := range tt.checks { s.AddReadinessCheck(c) } rec := get(t, s.Handler(), "/readyz") if rec.Code != tt.wantCode { t.Fatalf("code = %d, want %d", rec.Code, tt.wantCode) } var body struct { Status string `json:"status"` Checks map[string]checkResult `json:"checks"` } if err := json.Unmarshal(rec.Body.Bytes(), &body); err != nil { t.Fatal(err) } if body.Status != tt.wantStatus { t.Errorf("status = %q, want %q", body.Status, tt.wantStatus) } if len(body.Checks) != len(tt.checks) { t.Errorf("checks = %d, want %d", len(body.Checks), len(tt.checks)) } }) } } func TestMetricszCountsRequests(t *testing.T) { s := newTestServer(t) get(t, s.Handler(), "/healthz") get(t, s.Handler(), "/healthz") rec := get(t, s.Handler(), "/metricsz") if rec.Code != http.StatusOK { t.Fatalf("status = %d", rec.Code) } var body struct { Counters map[string]int64 `json:"counters"` } if err := json.Unmarshal(rec.Body.Bytes(), &body); err != nil { t.Fatal(err) } if body.Counters["http_requests_total"] != 2 { t.Errorf("http_requests_total = %d, want 2", body.Counters["http_requests_total"]) } } func TestPanicRecoveryEnvelope(t *testing.T) { s := newTestServer(t) mux := http.NewServeMux() mux.HandleFunc("GET /boom", func(http.ResponseWriter, *http.Request) { panic("kaboom") }) h := s.withMiddleware(mux) rec := get(t, h, "/boom") if rec.Code != http.StatusInternalServerError { t.Fatalf("status = %d", rec.Code) } var env struct { Error struct { Code string `json:"code"` Message string `json:"message"` } `json:"error"` } if err := json.Unmarshal(rec.Body.Bytes(), &env); err != nil { t.Fatal(err) } if env.Error.Code != "internal" { t.Errorf("error code = %q", env.Error.Code) } } func TestUnknownRouteNotFound(t *testing.T) { s := newTestServer(t) rec := get(t, s.Handler(), "/nope") if rec.Code != http.StatusNotFound { t.Fatalf("status = %d, want 404", rec.Code) } }