package observability import ( "context" "errors" "testing" "github.com/prometheus/client_golang/prometheus" "evobgp/internal/store" ) type failingMetricsBackend struct{ store.Backend } func (failingMetricsBackend) ObserveStoreMetrics(context.Context) (int, int, int, map[string]int, error) { return 0, 0, 0, nil, errors.New("database unavailable") } type storedMetricsBackend struct{ store.Backend } func (storedMetricsBackend) ObserveStoreMetrics(context.Context) (int, int, int, map[string]int, error) { return 12, 24, 1, map[string]int{"established": 1}, nil } type legacyStateBackend struct{ store.Backend } func (legacyStateBackend) ObserveStoreMetrics(context.Context) (int, int, int, map[string]int, error) { return 0, 0, 2, map[string]int{" Established ": 1, "established": 1}, nil } func TestStoreMetricsCollectionErrorOmitsAggregates(t *testing.T) { metricsStore.Store(storeBackendHolder{backend: failingMetricsBackend{}}) registry := prometheus.NewRegistry() if err := registry.Register(newMemoryStoreCollector()); err != nil { t.Fatal(err) } families, err := registry.Gather() if err != nil { t.Fatal(err) } if len(families) != 1 || families[0].GetName() != "evobgp_store_metrics_collection_success" { t.Fatalf("expected only collection status on error, got %v", families) } if got := families[0].Metric[0].Gauge.GetValue(); got != 0 { t.Fatalf("collection status = %v, want 0", got) } } func TestStoredRevisionMetricsDoNotClaimCurrentRoutes(t *testing.T) { metricsStore.Store(storeBackendHolder{backend: storedMetricsBackend{}}) registry := prometheus.NewRegistry() if err := registry.Register(newMemoryStoreCollector()); err != nil { t.Fatal(err) } families, err := registry.Gather() if err != nil { t.Fatal(err) } values := make(map[string]float64) for _, family := range families { if len(family.Metric) > 0 && family.Metric[0].Gauge != nil { values[family.GetName()] = family.Metric[0].Gauge.GetValue() } } if values["evobgp_stored_revision_materialized_prefixes_sum"] != 24 || values["evobgp_materialized_prefixes_sum"] != 24 { t.Fatalf("stored revision and legacy sums must match: %v", values) } if values["evobgp_store_metrics_collection_success"] != 1 { t.Fatalf("successful collection not reported: %v", values) } if _, ok := values["evobgp_current_routes"]; ok { t.Fatal("historical revision sum must not be published as current routes") } } func TestCanonicalPeerStateBoundsNewLabelValues(t *testing.T) { if got := canonicalPeerState("Established"); got != "established" { t.Fatalf("state = %q", got) } if got := canonicalPeerState("unexpected peer text"); got != "other" { t.Fatalf("state = %q", got) } } func TestLegacyPeerStateLabelPreservedWhileNewLabelIsCanonical(t *testing.T) { metricsStore.Store(storeBackendHolder{backend: legacyStateBackend{}}) registry := prometheus.NewRegistry() if err := registry.Register(newMemoryStoreCollector()); err != nil { t.Fatal(err) } families, err := registry.Gather() if err != nil { t.Fatal(err) } legacyRaw, newCanonical := false, false for _, family := range families { for _, metric := range family.Metric { if len(metric.Label) != 1 { continue } switch family.GetName() { case "evobgp_bgp_peer_sessions": if metric.Label[0].GetValue() == " Established " && metric.Gauge.GetValue() == 1 { legacyRaw = true } case "evobgp_stored_bgp_peer_session_state": if metric.Label[0].GetValue() == "established" && metric.Gauge.GetValue() == 2 { newCanonical = true } } } } if !legacyRaw || !newCanonical { t.Fatalf("legacy raw state preserved=%v, new canonical state aggregated=%v", legacyRaw, newCanonical) } }