package observability import ( "net/http" "net/http/httptest" "strings" "testing" "time" ) func TestRegistryUsesBoundedRouteAndStatusLabels(t *testing.T) { registry := NewRegistry(time.Unix(100, 0)) registry.ObserveRequest(http.MethodGet, "/api/v1/entities/secret-id", http.StatusNotFound, 25*time.Millisecond) registry.ObserveRequest(http.MethodGet, "/api/v1/entities/another-id", http.StatusNotFound, 10*time.Millisecond) exposition := registry.Exposition(time.Unix(101, 0)) if !strings.Contains(exposition, `route="api_other"`) { t.Fatalf("expected bounded api route labels, got %s", exposition) } if strings.Contains(exposition, "secret-id") || strings.Contains(exposition, "another-id") { t.Fatalf("request path leaked into metrics: %s", exposition) } } func TestMetricsHandlerIsBoundedAndDeterministic(t *testing.T) { registry := NewRegistry(time.Unix(100, 0)) registry.SetGauge("pulse_database_ready", 1) registry.ObserveRequest(http.MethodGet, "/healthz", http.StatusOK, time.Second) handler := registry.Handler() request := httptest.NewRequest(http.MethodGet, "/api/v1/system/metrics", nil) first := httptest.NewRecorder() handler.ServeHTTP(first, request) if first.Code != http.StatusOK || first.Header().Get("Content-Type") == "" { t.Fatalf("unexpected metrics response: %d %s", first.Code, first.Body.String()) } if !strings.Contains(first.Body.String(), "pulse_http_requests_total") || !strings.Contains(first.Body.String(), "pulse_database_ready 1") { t.Fatalf("missing expected metrics: %s", first.Body.String()) } second := httptest.NewRecorder() handler.ServeHTTP(second, httptest.NewRequest(http.MethodGet, "/api/v1/system/metrics", nil)) if first.Body.String() == second.Body.String() { t.Fatalf("scrape counter did not change between scrapes") } } func TestRegistryCapsUnexpectedSeries(t *testing.T) { registry := NewRegistry(time.Unix(100, 0)) methods := []string{http.MethodGet, http.MethodHead, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete, http.MethodOptions} paths := []string{"/healthz", "/readyz", "/auth/test-login", "/session/logout", "/api/v1/system/status", "/api/v1/system/metrics", "/api/v1/system/diagnostics", "/api/v1/metrics/query", "/api/v1/live"} for index := 0; index < 500; index++ { registry.ObserveRequest(methods[index%len(methods)], paths[index%len(paths)], 200+(index%5)*100, time.Millisecond) } if got := strings.Count(registry.Exposition(time.Unix(101, 0)), "pulse_http_requests_total{"); got > maxRequestSeries { t.Fatalf("request series = %d, want at most %d", got, maxRequestSeries) } } func TestMetricsMiddlewareRecordsImplicitStatus(t *testing.T) { registry := NewRegistry(time.Unix(100, 0)) handler := Middleware(registry, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { _, _ = w.Write([]byte("ok")) })) handler.ServeHTTP(httptest.NewRecorder(), httptest.NewRequest(http.MethodGet, "/healthz", nil)) if !strings.Contains(registry.Exposition(time.Unix(101, 0)), `status_class="2xx"`) { t.Fatal("implicit 200 response was not measured") } }