Files
yovision/Sense/internal/metrics/metrics_test.go
T
QiuSW 12857fdf32
Harness governance / validate (push) Has been cancelled
Harness governance / validate (pull_request) Has been cancelled
feat(sense): add reconciliation safety controls [T-012]
2026-08-07 23:00:03 +08:00

85 lines
2.5 KiB
Go

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)
}
}