Public source validation / validate (push) Failing after 3m8s
120 lines
5.3 KiB
Go
120 lines
5.3 KiB
Go
package metricquery
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"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"
|
|
)
|
|
|
|
func handlerService(t *testing.T) *Service {
|
|
return testService(t, &fakeSource{result: prometheus.QueryResult{Status: "success", Data: []byte(`{"resultType":"matrix","result":[]}`)}}, NewCache(8, 1<<20))
|
|
}
|
|
func authenticatedRequest(method, path, body string) *http.Request {
|
|
request := httptest.NewRequest(method, path, strings.NewReader(body))
|
|
return request.WithContext(auth.WithPrincipal(context.Background(), auth.Principal{Subject: "viewer", Role: auth.RoleViewer}))
|
|
}
|
|
func operatorRequest(method, path, body string) *http.Request {
|
|
request := httptest.NewRequest(method, path, strings.NewReader(body))
|
|
return request.WithContext(auth.WithPrincipal(context.Background(), auth.Principal{Subject: "operator", Role: auth.RoleOperator}))
|
|
}
|
|
|
|
func TestHandlerRequiresAuthAndMapsUnavailableService(t *testing.T) {
|
|
response := httptest.NewRecorder()
|
|
Handler{Service: handlerService(t)}.ServeHTTP(response, httptest.NewRequest(http.MethodPost, "/api/v1/metrics/query-range", strings.NewReader(`{}`)))
|
|
if response.Code != http.StatusUnauthorized {
|
|
t.Fatalf("status=%d", response.Code)
|
|
}
|
|
response = httptest.NewRecorder()
|
|
Handler{}.ServeHTTP(response, authenticatedRequest(http.MethodPost, "/api/v1/metrics/query-range", `{}`))
|
|
if response.Code != http.StatusServiceUnavailable {
|
|
t.Fatalf("status=%d", response.Code)
|
|
}
|
|
}
|
|
func TestHandlerRejectsMalformedAndOversizedBodies(t *testing.T) {
|
|
service := handlerService(t)
|
|
response := httptest.NewRecorder()
|
|
Handler{Service: service}.ServeHTTP(response, authenticatedRequest(http.MethodPost, "/api/v1/metrics/query-range", `{"metric":`))
|
|
if response.Code != http.StatusBadRequest {
|
|
t.Fatalf("malformed=%d", response.Code)
|
|
}
|
|
response = httptest.NewRecorder()
|
|
oversized := `{"padding":"` + strings.Repeat("x", maxRequestBody) + `"}`
|
|
Handler{Service: service}.ServeHTTP(response, authenticatedRequest(http.MethodPost, "/api/v1/metrics/query-range", oversized))
|
|
if response.Code != http.StatusRequestEntityTooLarge {
|
|
t.Fatalf("oversized=%d", response.Code)
|
|
}
|
|
}
|
|
func TestHandlerPermissionedInspector(t *testing.T) {
|
|
service := handlerService(t)
|
|
payload, _ := json.Marshal(testRequest())
|
|
response := httptest.NewRecorder()
|
|
Handler{Service: service}.ServeHTTP(response, authenticatedRequest(http.MethodPost, "/api/v1/metrics/inspect", string(payload)))
|
|
if response.Code != http.StatusForbidden {
|
|
t.Fatalf("viewer status=%d body=%s", response.Code, response.Body.String())
|
|
}
|
|
response = httptest.NewRecorder()
|
|
Handler{Service: service}.ServeHTTP(response, operatorRequest(http.MethodPost, "/api/v1/metrics/inspect", string(payload)))
|
|
if response.Code != http.StatusOK {
|
|
t.Fatalf("operator status=%d body=%s", response.Code, response.Body.String())
|
|
}
|
|
var decoded struct {
|
|
Inspector Inspector `json:"inspector"`
|
|
}
|
|
if err := json.Unmarshal(response.Body.Bytes(), &decoded); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if decoded.Inspector.GeneratedQuery == "" || decoded.Inspector.Cost.Series < 1 || decoded.Inspector.Limits.MaxPoints < 1 {
|
|
t.Fatalf("inspector=%+v", decoded.Inspector)
|
|
}
|
|
}
|
|
|
|
func TestHandlerReturnsBoundedQueryResponse(t *testing.T) {
|
|
service := handlerService(t)
|
|
request := testRequest()
|
|
payload, _ := json.Marshal(request)
|
|
response := httptest.NewRecorder()
|
|
Handler{Service: service}.ServeHTTP(response, authenticatedRequest(http.MethodPost, "/api/v1/metrics/query-range", string(payload)))
|
|
if response.Code != http.StatusOK {
|
|
t.Fatalf("status=%d body=%s", response.Code, response.Body.String())
|
|
}
|
|
var decoded Response
|
|
if err := json.Unmarshal(response.Body.Bytes(), &decoded); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if decoded.Provenance.Source != "prometheus" || decoded.Freshness != "fresh" {
|
|
t.Fatalf("response=%+v", decoded)
|
|
}
|
|
}
|
|
func TestHandlerMapsSourceErrorsWithoutLeakingDetails(t *testing.T) {
|
|
service := testService(t, &fakeSource{err: context.DeadlineExceeded}, nil)
|
|
payload, _ := json.Marshal(testRequest())
|
|
response := httptest.NewRecorder()
|
|
Handler{Service: service}.ServeHTTP(response, authenticatedRequest(http.MethodPost, "/api/v1/metrics/query-range", string(payload)))
|
|
if response.Code != http.StatusServiceUnavailable || strings.Contains(response.Body.String(), "DeadlineExceeded") {
|
|
t.Fatalf("status=%d body=%s", response.Code, response.Body.String())
|
|
}
|
|
}
|
|
func TestHandlerInstantRoute(t *testing.T) {
|
|
registry, err := metriccatalog.DefaultRegistry()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
source := &fakeSource{result: prometheus.QueryResult{Status: "success", Data: []byte(`{}`)}}
|
|
service := NewService(queryplan.NewPlanner(registry, queryplan.Limits{}), source, nil)
|
|
payload := `{"metric":"host.cpu.utilization","scope":{"serverId":"server-1"},"at":"` + time.Date(2026, 8, 1, 12, 0, 0, 0, time.UTC).Format(time.RFC3339) + `"}`
|
|
response := httptest.NewRecorder()
|
|
Handler{Service: service}.ServeHTTP(response, authenticatedRequest(http.MethodPost, "/api/v1/metrics/query", payload))
|
|
if response.Code != http.StatusOK || source.instantCalls != 1 {
|
|
t.Fatalf("status=%d calls=%d body=%s", response.Code, source.instantCalls, response.Body.String())
|
|
}
|
|
}
|