package metrics import ( "net/http/httptest" "strings" "sync" "testing" "time" ) func TestHandlerUsesOnlyFixedLowCardinalityLabels(t *testing.T) { t.Parallel() registry := New("ins_test-1", "v-test") registry.ObserveReconcileRun(nil, 1500*time.Millisecond) registry.ObserveReconcileItem("success") registry.ObserveReconcileItem("lease_lost") registry.SetConvergence(16, 2) registry.ObserveOrphanScan(20, 1, 3, nil) registry.ObserveOrphanCleanupBlocked("ratio_exceeded") registry.ObserveOrphanCleanup(1, 1) request := httptest.NewRequest("GET", "/metrics", nil) response := httptest.NewRecorder() registry.Handler().ServeHTTP(response, request) body := response.Body.String() for _, expected := range []string{ `sense_build_info{instance_id="ins_test-1",version="v-test"} 1`, `sense_reconcile_items_total{result="lease_lost"} 1`, `sense_reconcile_devices{state="unconverged"} 2`, `sense_orphan_paths{classification="owned_stale"} 1`, `sense_orphan_cleanup_blocked_total{reason="ratio_exceeded"} 1`, } { if !strings.Contains(body, expected) { t.Fatalf("metrics output lacks %q:\n%s", expected, body) } } for _, forbidden := range []string{ "tenant_id", "site_id", "device_id", "path_name", "rtsp://", "credential", } { if strings.Contains(body, forbidden) { t.Fatalf("high-cardinality or secret-bearing label appeared: %q", forbidden) } } if response.Header().Get("Cache-Control") != "no-store" { t.Fatal("metrics responses must not be cached") } } func TestRegistryIsSafeForConcurrentObservers(t *testing.T) { t.Parallel() registry := New("ins_concurrent", "dev") var wait sync.WaitGroup for index := 0; index < 32; index++ { wait.Add(1) go func() { defer wait.Done() for count := 0; count < 100; count++ { registry.ObserveReconcileItem("success") registry.SetConvergence(128, count%17) } }() } wait.Wait() response := httptest.NewRecorder() registry.Handler().ServeHTTP(response, httptest.NewRequest("GET", "/metrics", nil)) if !strings.Contains(response.Body.String(), `sense_reconcile_items_total{result="success"} 3200`) { t.Fatalf("concurrent counter lost updates: %s", response.Body.String()) } } func TestGenerateInstanceIDShape(t *testing.T) { t.Parallel() first, err := GenerateInstanceID() if err != nil { t.Fatal(err) } second, err := GenerateInstanceID() if err != nil { t.Fatal(err) } if len(first) != 28 || !strings.HasPrefix(first, "ins_") || first == second { t.Fatalf("unexpected instance identifiers: %q %q", first, second) } }