This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
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())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user