package metricquery import ( "context" "errors" "sort" "strings" "sync" "testing" "time" "github.com/itworx/pulse/internal/auth" "github.com/itworx/pulse/internal/metriccatalog" "github.com/itworx/pulse/internal/prometheus" "github.com/itworx/pulse/internal/queryplan" ) type fakeSource struct { mu sync.Mutex rangeCalls int instantCalls int started chan struct{} release chan struct{} result prometheus.QueryResult err error } func (f *fakeSource) Query(ctx context.Context, _ string, _ *time.Time) (prometheus.QueryResult, error) { f.mu.Lock() f.instantCalls++ f.mu.Unlock() return f.result, f.err } func (f *fakeSource) QueryRange(ctx context.Context, _ string, _, _ time.Time, _ time.Duration) (prometheus.QueryResult, error) { f.mu.Lock() f.rangeCalls++ started := f.started release := f.release f.mu.Unlock() if started != nil { select { case started <- struct{}{}: default: } } if release != nil { select { case <-release: case <-ctx.Done(): return prometheus.QueryResult{}, ctx.Err() } } return f.result, f.err } func (f *fakeSource) calls() int { f.mu.Lock(); defer f.mu.Unlock(); return f.rangeCalls } func testService(t *testing.T, source Source, cache *Cache) *Service { t.Helper() registry, err := metriccatalog.DefaultRegistry() if err != nil { t.Fatal(err) } return NewService(queryplan.NewPlanner(registry, queryplan.Limits{}), source, cache) } func testRequest() queryplan.Request { now := time.Date(2026, 8, 1, 12, 0, 0, 0, time.UTC) return queryplan.Request{Metric: "container.cpu.utilization", Scope: map[string]string{"containerId": "media_server"}, Range: queryplan.Range{From: now.Add(-time.Hour), To: now, StepSeconds: 60}, GroupBy: []string{"container"}} } func userContext(subject string) context.Context { return auth.WithPrincipal(context.Background(), auth.Principal{Subject: subject, Role: auth.RoleViewer}) } func TestServiceDeduplicatesConcurrentEquivalentQueriesAndCachesBySubject(t *testing.T) { source := &fakeSource{started: make(chan struct{}, 1), release: make(chan struct{}), result: prometheus.QueryResult{Status: "success", Data: []byte(`{"resultType":"matrix","result":[]}`)}} service := testService(t, source, NewCache(8, 1<<20)) firstDone := make(chan struct{}) var firstErr error go func() { _, firstErr = service.ExecuteRange(userContext("alice"), testRequest()); close(firstDone) }() <-source.started equivalent := testRequest() equivalent.GroupBy = []string{"container"} secondDone := make(chan struct{}) var second Response go func() { second, _ = service.ExecuteRange(userContext("alice"), equivalent); close(secondDone) }() close(source.release) <-firstDone <-secondDone if firstErr != nil { t.Fatal(firstErr) } if source.calls() != 1 { t.Fatalf("source calls=%d", source.calls()) } if second.CacheHit != true { t.Fatal("deduplicated waiter was not marked cache hit") } cached, err := service.ExecuteRange(userContext("alice"), testRequest()) if err != nil || !cached.CacheHit || source.calls() != 1 { t.Fatalf("cache=%+v err=%v calls=%d", cached, err, source.calls()) } _, err = service.ExecuteRange(userContext("bob"), testRequest()) if err != nil || source.calls() != 2 { t.Fatalf("subject cache isolation err=%v calls=%d", err, source.calls()) } } func TestServiceMapsSourceErrorsAndDoesNotCacheFailures(t *testing.T) { source := &fakeSource{err: errors.New("upstream private detail")} service := testService(t, source, NewCache(8, 1<<20)) _, err := service.ExecuteRange(userContext("alice"), testRequest()) if err == nil || !errors.Is(err, ErrSourceUnavailable) { t.Fatalf("err=%v", err) } if source.calls() != 1 { t.Fatal("unexpected calls") } _, err = service.ExecuteRange(userContext("alice"), testRequest()) if err == nil || source.calls() != 2 { t.Fatalf("failure was cached err=%v calls=%d", err, source.calls()) } } func TestServiceFollowerHonorsCancellation(t *testing.T) { source := &fakeSource{started: make(chan struct{}, 1), release: make(chan struct{}), result: prometheus.QueryResult{Status: "success", Data: []byte(`{}`)}} service := testService(t, source, nil) go func() { _, _ = service.ExecuteRange(userContext("alice"), testRequest()) }() <-source.started ctx, cancel := context.WithCancel(userContext("alice")) cancel() if _, err := service.ExecuteRange(ctx, testRequest()); !errors.Is(err, context.Canceled) { t.Fatalf("err=%v", err) } close(source.release) } func TestServiceInstantReturnsProvenanceAndFreshness(t *testing.T) { source := &fakeSource{result: prometheus.QueryResult{Status: "success", Data: []byte(`{"resultType":"vector","result":[]`)}} service := testService(t, source, nil) response, err := service.ExecuteInstant(userContext("alice"), InstantRequest{Metric: "host.cpu.utilization", Scope: map[string]string{"serverId": "server-1"}, At: time.Date(2026, 8, 1, 12, 0, 0, 0, time.UTC)}) if err != nil { t.Fatal(err) } if response.Provenance.Source != "prometheus" || response.Provenance.CatalogVersion == "" || response.Freshness != "fresh" || source.instantCalls != 1 { t.Fatalf("response=%+v calls=%d", response, source.instantCalls) } } func TestClassifyFreshnessNeverReportsMissingOrStaleAsFresh(t *testing.T) { registry, err := metriccatalog.DefaultRegistry() if err != nil { t.Fatal(err) } definition, ok := registry.Find("container.cpu.utilization") if !ok { t.Fatal("seed metric is missing") } window := FreshnessWindow(definition) if window != 30*time.Second { t.Fatalf("window=%s", window) } now := time.Date(2026, 8, 1, 12, 0, 0, 0, time.UTC) if state := ClassifyFreshness(now.Add(-window), now, window); state != FreshnessFresh { t.Fatalf("state=%s", state) } if state := ClassifyFreshness(now.Add(-window-time.Second), now, window); state != FreshnessStale { t.Fatalf("state=%s", state) } if state := ClassifyFreshness(time.Time{}, now, window); state != FreshnessUnknown { t.Fatalf("state=%s", state) } if state := ClassifyFreshness(now, now, 0); state != FreshnessUnknown { t.Fatalf("state=%s", state) } } func TestCacheIsBoundedByEntriesAndBytes(t *testing.T) { cache := NewCache(1, 1024) response := Response{Data: []byte(`{"large":"payload"}`)} cache.Put("a", response, time.Minute) cache.Put("b", response, time.Minute) if cache.Len() != 1 || cache.Bytes() <= 0 { t.Fatalf("len=%d bytes=%d", cache.Len(), cache.Bytes()) } } func operatorContext(subject string) context.Context { return auth.WithPrincipal(context.Background(), auth.Principal{Subject: subject, Role: auth.RoleOperator}) } func TestInspectorIsPermissionedAndWarningsAreRedacted(t *testing.T) { source := &fakeSource{result: prometheus.QueryResult{Status: "success", Data: []byte("{}"), Warnings: []string{"authorization=Bearer abc123"}}} service := testService(t, source, NewCache(8, 1<<20)) viewer, err := service.ExecuteRange(userContext("viewer"), testRequest()) if err != nil { t.Fatal(err) } if viewer.Inspector != nil { t.Fatal("viewer received query inspector") } if len(viewer.Warnings) != 1 || strings.Contains(viewer.Warnings[0], "abc123") { t.Fatalf("warnings leaked: %#v", viewer.Warnings) } operator, err := service.ExecuteRange(operatorContext("operator"), testRequest()) if err != nil { t.Fatal(err) } if operator.Inspector == nil || operator.Inspector.SemanticMetric != "container.cpu.utilization" || operator.Inspector.GeneratedQuery == "" { t.Fatalf("inspector missing: %+v", operator.Inspector) } if operator.Inspector.Cost.Series < 1 || operator.Inspector.Cost.Points < 1 { t.Fatalf("unexpected cost: %+v", operator.Inspector.Cost) } if strings.Contains(operator.Inspector.GeneratedQuery, "abc123") || strings.Contains(strings.Join(operator.Warnings, " "), "abc123") { t.Fatal("secret fixture leaked") } } func BenchmarkServiceCacheHit(b *testing.B) { source := &fakeSource{result: prometheus.QueryResult{Status: "success", Data: []byte(`{}`)}} registry, err := metriccatalog.DefaultRegistry() if err != nil { b.Fatal(err) } service := NewService(queryplan.NewPlanner(registry, queryplan.Limits{}), source, NewCache(8, 1<<20)) ctx := userContext("benchmark") request := testRequest() if _, err := service.ExecuteRange(ctx, request); err != nil { b.Fatal(err) } b.ResetTimer() for i := 0; i < b.N; i++ { if _, err := service.ExecuteRange(ctx, request); err != nil { b.Fatal(err) } } } func TestServiceCacheHitP95Budget(t *testing.T) { source := &fakeSource{result: prometheus.QueryResult{Status: "success", Data: []byte(`{}`)}} service := testService(t, source, NewCache(8, 1<<20)) ctx := userContext("p95") request := testRequest() if _, err := service.ExecuteRange(ctx, request); err != nil { t.Fatal(err) } durations := make([]time.Duration, 200) for i := range durations { started := time.Now() if _, err := service.ExecuteRange(ctx, request); err != nil { t.Fatal(err) } durations[i] = time.Since(started) } sort.Slice(durations, func(i, j int) bool { return durations[i] < durations[j] }) if p95 := durations[189]; p95 > 750*time.Millisecond { t.Fatalf("local cache p95=%s exceeds 750ms budget", p95) } }