package metrics import ( "io" "net/http/httptest" "strings" "testing" ) func scrape(t *testing.T, m *Metrics) string { t.Helper() rec := httptest.NewRecorder() req := httptest.NewRequest("GET", "/metrics", nil) m.Handler().ServeHTTP(rec, req) if rec.Code != 200 { t.Fatalf("metrics handler status = %d, want 200", rec.Code) } body, _ := io.ReadAll(rec.Result().Body) return string(body) } func TestNewRegistersAllCollectors(t *testing.T) { m := New(func() float64 { return 3 }, func() float64 { return 12.5 }) // CounterVecs emit no series until a label set is observed; touch each once // so the metric family name surfaces in the scrape. m.RecordSessionExit("exited") m.RecordPermissionDecision("auto") m.RecordDeadLetter("webhook.deliver") body := scrape(t, m) for _, name := range []string{ "acp_sessions_active", "acp_session_exit_total", "acp_permission_decisions_total", "jobs_dead_letter_total", "llm_cost_usd_total", } { if !strings.Contains(body, name) { t.Errorf("metrics output missing %q\n%s", name, body) } } // GaugeFunc values surface immediately even without any Record* call. if !strings.Contains(body, "acp_sessions_active 3") { t.Errorf("acp_sessions_active gauge not reporting func value; body:\n%s", body) } if !strings.Contains(body, "llm_cost_usd_total 12.5") { t.Errorf("llm_cost_usd_total gauge not reporting func value; body:\n%s", body) } } func TestRecordHelpersIncrementWithLabels(t *testing.T) { m := New(nil, nil) m.RecordSessionExit("crashed") m.RecordSessionExit("crashed") m.RecordSessionExit("exited") m.RecordPermissionDecision("approved") m.RecordPermissionDecision("denied") m.RecordPermissionDecision("expired") m.RecordDeadLetter("webhook.deliver") body := scrape(t, m) wants := []string{ `acp_session_exit_total{status="crashed"} 2`, `acp_session_exit_total{status="exited"} 1`, `acp_permission_decisions_total{outcome="approved"} 1`, `acp_permission_decisions_total{outcome="denied"} 1`, `acp_permission_decisions_total{outcome="expired"} 1`, `jobs_dead_letter_total{type="webhook.deliver"} 1`, } for _, w := range wants { if !strings.Contains(body, w) { t.Errorf("metrics output missing %q\n%s", w, body) } } } func TestRegistryIsolatedNotGlobalDefault(t *testing.T) { // Two independent Metrics must not collide (would panic if both used the // global default registry). _ = New(nil, nil) _ = New(nil, nil) } func TestNilReceiverRecordSafe(t *testing.T) { var m *Metrics // Must not panic — callers may hold a nil *Metrics when metrics disabled. m.RecordSessionExit("exited") m.RecordPermissionDecision("auto") m.RecordDeadLetter("x") }