From afb94a14f0ceca6a6ed39ba45f11d86688ec0085 Mon Sep 17 00:00:00 2001 From: sneak Date: Thu, 15 Oct 2026 18:35:54 +0000 Subject: [PATCH] Test that made-up methods share one metrics method label The test sends two requests with made-up methods through the metrics middleware and expects /metrics to count both under the method label "other", and the handler to see each method as sent. It fails until the middleware stops using every method name as its own label value. Model: opus-5-5 --- .../middleware/middleware_internal_test.go | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/internal/middleware/middleware_internal_test.go b/internal/middleware/middleware_internal_test.go index 0e17a05..21d18b4 100644 --- a/internal/middleware/middleware_internal_test.go +++ b/internal/middleware/middleware_internal_test.go @@ -7,6 +7,7 @@ import ( "net/http" "net/http/httptest" "net/url" + "slices" "strings" "testing" @@ -314,6 +315,58 @@ func requestCountsByHandler( return counts } +// TestMetricsGivesMadeUpMethodsOneLabel checks that the metrics middleware +// gives requests with methods that are not standard ones a single method +// label, "other", and still serves each request with the method it was sent +// with. With a label per method, any client could add series to the +// registry without limit by making up a new method for each request. +func TestMetricsGivesMadeUpMethodsOneLabel(t *testing.T) { + t.Parallel() + + // The line /metrics shows once both made-up methods have been served. + const want = `http_request_duration_seconds_count{` + + `code="200",handler="unmatched",method="other",service=""} 2` + + registry := prometheus.NewRegistry() + mw := &Middleware{ + log: slog.Default(), + config: &config.Config{}, + metricsRegistry: registry, + } + + var served []string + + handler := mw.Metrics()(http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + served = append(served, r.Method) + + w.WriteHeader(http.StatusOK) + })) + + methods := []string{"FROBNICATE", "WIBBLE"} + for _, method := range methods { + handler.ServeHTTP(httptest.NewRecorder(), httptest.NewRequestWithContext( + t.Context(), method, "/", nil)) + } + + t.Logf("methods the handler was served: %v", served) + + if !slices.Equal(served, methods) { + t.Errorf("methods the handler was served = %v, want %v", + served, methods) + } + + rec := httptest.NewRecorder() + promhttp.HandlerFor(registry, promhttp.HandlerOpts{}).ServeHTTP(rec, + httptest.NewRequestWithContext( + t.Context(), http.MethodGet, "/metrics", nil)) + + if !strings.Contains(rec.Body.String(), want) { + t.Errorf("/metrics does not count both made-up methods as one; "+ + "want the line %q in:\n%s", want, rec.Body.String()) + } +} + // TestLoggingLeavesOutSubmittedSigningKey checks that a login, a POST / // whose form carries the signing key, leaves no trace of the key in the // request's log line.