package telemetry_test import ( "net/http" "net/http/httptest" "strings" "testing" "time" "sneak.berlin/go/simplexcalc/internal/config" "sneak.berlin/go/simplexcalc/internal/telemetry" ) func newMetrics(t *testing.T, user, password string) *telemetry.Metrics { t.Helper() m, err := telemetry.NewMetrics(telemetry.MetricsParams{ Config: &config.Config{MetricsUser: user, MetricsPassword: password}, }) if err != nil { t.Fatalf("building metrics: %v", err) } return m } // TestMetricsRequireCredentialsWhenConfigured: an exposition endpoint // leaks route names, traffic volume and process layout, so credentials // have to actually be enforced. func TestMetricsRequireCredentialsWhenConfigured(t *testing.T) { t.Parallel() m := newMetrics(t, user, pass) if !m.AuthRequired() { t.Fatal("credentials are configured but AuthRequired is false") } const ( unauthorized = http.StatusUnauthorized ok = http.StatusOK ) cases := []struct { name string user, pass string useAuth bool want int }{ {name: "no credentials", want: unauthorized}, {name: "wrong password", user: user, pass: "no", useAuth: true, want: unauthorized}, {name: "wrong user", user: "nobody", pass: pass, useAuth: true, want: unauthorized}, {name: "correct", user: user, pass: pass, useAuth: true, want: ok}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { t.Parallel() req := scrapeReq(t) if tc.useAuth { req.SetBasicAuth(tc.user, tc.pass) } w := httptest.NewRecorder() m.Handler().ServeHTTP(w, req) if w.Code != tc.want { t.Errorf("status = %d, want %d", w.Code, tc.want) } if tc.want == http.StatusUnauthorized { if w.Header().Get("WWW-Authenticate") == "" { t.Error("a 401 with no WWW-Authenticate gives the client nothing to do") } if strings.Contains(w.Body.String(), "http_requests_total") { t.Error("metrics were served in the body of a 401") } } }) } } // TestMetricsOpenWhenNoCredentials documents the other half: with // nothing configured the endpoint is open, which config permits only // when BOTH values are absent. func TestMetricsOpenWhenNoCredentials(t *testing.T) { t.Parallel() m := newMetrics(t, "", "") if m.AuthRequired() { t.Fatal("no credentials configured but AuthRequired is true") } w := httptest.NewRecorder() m.Handler().ServeHTTP(w, scrapeReq(t)) if w.Code != http.StatusOK { t.Errorf("status = %d, want 200", w.Code) } } // TestObservedRequestsAreExported: the middleware records through // Observe, and what it records has to come back out of the endpoint. func TestObservedRequestsAreExported(t *testing.T) { t.Parallel() m := newMetrics(t, "", "") m.Observe(http.MethodGet, "/widgets/{id}", "200", 25*time.Millisecond) w := httptest.NewRecorder() m.Handler().ServeHTTP(w, scrapeReq(t)) body := w.Body.String() for _, want := range []string{ `http_requests_total{code="200",method="GET",route="/widgets/{id}"} 1`, "http_request_duration_seconds_bucket", "go_goroutines", // the Go collector is registered } { if !strings.Contains(body, want) { t.Errorf("exposition output is missing %q", want) } } } // TestSentryDisabledWithoutDSN: every method must be safe with no DSN, // because that is how the service runs in development and in tests. func TestSentryDisabledWithoutDSN(t *testing.T) { t.Parallel() s, err := telemetry.NewSentry(nil, telemetry.SentryParams{ Config: &config.Config{}, Globals: testGlobals(), Logger: testLogger(t), }) if err != nil { t.Fatalf("building sentry: %v", err) } if s.Enabled() { t.Error("sentry reports enabled with no DSN") } // Must not panic. s.CaptureError(nil) s.CaptureError(errTest) s.CapturePanic("boom", []byte("stack")) }