The template's files at a77fd30, without its history or LICENSE, after script/rename simplexcalc. Model: opus-5-5
153 lines
3.7 KiB
Go
153 lines
3.7 KiB
Go
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"))
|
|
}
|