Public source validation / validate (push) Failing after 3m8s
144 lines
5.5 KiB
Go
144 lines
5.5 KiB
Go
package promqlbinding
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/itworx/pulse/internal/auth"
|
|
"github.com/itworx/pulse/internal/datasource"
|
|
"github.com/itworx/pulse/internal/metriccatalog"
|
|
"github.com/itworx/pulse/internal/queryplan"
|
|
)
|
|
|
|
func TestCompileQuotesLabelValuesAndUsesBoundedWindow(t *testing.T) {
|
|
registry, err := metriccatalog.DefaultRegistry()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
definition, ok := registry.Find("host.cpu.utilization")
|
|
if !ok {
|
|
t.Fatal("seed metric missing")
|
|
}
|
|
compiled, err := Compile(definition, map[string]string{"instance": "server-1", "window": "5m"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !strings.Contains(compiled, `instance="server-1"`) || !strings.Contains(compiled, "[5m]") || strings.Contains(compiled, "{{") {
|
|
t.Fatalf("compiled=%s", compiled)
|
|
}
|
|
}
|
|
|
|
func TestCompileRejectsInjectionMissingAndUnknownValues(t *testing.T) {
|
|
registry, err := metriccatalog.DefaultRegistry()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
definition, _ := registry.Find("host.cpu.utilization")
|
|
cases := []map[string]string{
|
|
{"instance": `server-1" or up`, "window": "5m"},
|
|
{"instance": "server-1"},
|
|
{"instance": "server-1", "window": "5m", "groupBy": "container"},
|
|
{"instance": "server-1", "window": "5m;drop"},
|
|
}
|
|
for index, values := range cases {
|
|
if _, err := Compile(definition, values); err == nil {
|
|
t.Fatalf("case %d accepted", index)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCompilePlanDoesNotAppendArbitraryGrouping(t *testing.T) {
|
|
registry, err := metriccatalog.DefaultRegistry()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
planner := queryplan.NewPlanner(registry, queryplan.Limits{})
|
|
request := queryplan.Request{Metric: "container.cpu.utilization", Scope: map[string]string{"containerId": "media_server"}, Range: queryplan.Range{From: now().Add(-3600), To: now(), StepSeconds: 60}, GroupBy: []string{"container"}}
|
|
plan, err := planner.Plan(auth.WithPrincipal(context.Background(), auth.Principal{Subject: "viewer", Role: auth.RoleViewer}), request)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
compiled, err := CompilePlan(plan)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !strings.Contains(compiled, `name="media_server"`) || strings.Contains(compiled, `container="media_server"`) || strings.Contains(compiled, "groupBy") {
|
|
t.Fatalf("compiled=%s", compiled)
|
|
}
|
|
if WindowForStep(15) != "1m" || WindowForStep(60) != "1m" || WindowForStep(90) != "90s" || WindowForStep(3600) != "1h" {
|
|
t.Fatal("unexpected window formatting")
|
|
}
|
|
}
|
|
|
|
func TestCompilePlanOmitsWindowForInstantGaugeTemplate(t *testing.T) {
|
|
registry, err := metriccatalog.DefaultRegistry()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
planner := queryplan.NewPlanner(registry, queryplan.Limits{})
|
|
request := queryplan.Request{Metric: "host.memory.utilization", Scope: map[string]string{"serverId": "smoke-host"}, Range: queryplan.Range{From: now().Add(-5 * time.Minute), To: now(), StepSeconds: 15}}
|
|
plan, err := planner.Plan(auth.WithPrincipal(context.Background(), auth.Principal{Subject: "viewer", Role: auth.RoleViewer}), request)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
compiled, err := CompilePlan(plan)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !strings.Contains(compiled, `instance="smoke-host"`) || strings.Contains(compiled, "{{") || strings.Contains(compiled, "[15s]") {
|
|
t.Fatalf("compiled=%s", compiled)
|
|
}
|
|
}
|
|
|
|
func TestBindingFallbackIsDeterministic(t *testing.T) {
|
|
registry, err := metriccatalog.DefaultRegistry()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
definition, _ := registry.Find("host.cpu.utilization")
|
|
first := Binding{ID: "z", Definition: definition, RequiredCapabilities: []string{"cap-z"}, Priority: 1}
|
|
second := Binding{ID: "a", Definition: definition, RequiredCapabilities: []string{"cap-a"}, Priority: 1}
|
|
second.Definition.Version = 2
|
|
capabilities := datasource.CapabilitySet{{ID: "cap-a", Version: "v1", State: datasource.CapabilityEnabled}}
|
|
selected, err := SelectBinding([]Binding{first, second}, capabilities)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if selected.ID != "a" {
|
|
t.Fatalf("selected=%s", selected.ID)
|
|
}
|
|
fallback, err := SelectBinding([]Binding{second, first}, nil)
|
|
if err == nil || fallback.ID != "a" {
|
|
t.Fatalf("fallback=%+v err=%v", fallback, err)
|
|
}
|
|
}
|
|
|
|
func TestRawQueryRequiresAdminAndHonorsCancellation(t *testing.T) {
|
|
viewer := auth.WithPrincipal(context.Background(), auth.Principal{Subject: "viewer", Role: auth.RoleViewer})
|
|
if _, err := ValidateRawQuery(viewer, "up"); err == nil {
|
|
t.Fatal("viewer raw query accepted")
|
|
}
|
|
admin := auth.WithPrincipal(context.Background(), auth.Principal{Subject: "admin", Role: auth.RoleAdministrator})
|
|
query, err := ValidateRawQuery(admin, " up ")
|
|
if err != nil || query != "up" {
|
|
t.Fatalf("query=%q err=%v", query, err)
|
|
}
|
|
canceled, cancel := context.WithCancel(admin)
|
|
cancel()
|
|
if _, err := ValidateRawQuery(canceled, "up"); !errors.Is(err, context.Canceled) {
|
|
t.Fatalf("cancel=%v", err)
|
|
}
|
|
}
|
|
|
|
func TestCompileRejectsOversizedCompiledQuery(t *testing.T) {
|
|
definition := metriccatalog.Definition{SemanticName: "host.cpu.utilization", Version: 1, Description: "cpu", Unit: "percent", ValueKind: "gauge", SourceKind: "prometheus", QueryTemplate: strings.Repeat("up ", 2100) + "{{instance}}", AllowedLabels: []string{"instance"}, CardinalityBudget: 1, Limits: metriccatalog.Limits{MaxRangeSeconds: 60, MaxSeries: 1, MaxPoints: 10, TimeoutSeconds: 1}, AllowedVisualizations: []string{"stat"}, FreshnessSeconds: 1}
|
|
if _, err := Compile(definition, map[string]string{"instance": "server-1"}); err == nil {
|
|
t.Fatal("oversized query accepted")
|
|
}
|
|
}
|
|
|
|
func now() time.Time { return time.Date(2026, 8, 1, 12, 0, 0, 0, time.UTC) }
|