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.