Files
ITWorx-Pulse-Public/internal/metricquery/service_test.go
T
ITWorx Pulse release export bd774932d5
Public source validation / validate (push) Failing after 3m8s
Publish ITWorx Pulse source
2026-09-03 02:09:19 +02:00

258 lines
9.0 KiB
Go

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