mirror of
https://github.com/lukaszraczylo/graphql-monitoring-proxy.git
synced 2026-06-05 23:03:48 +00:00
c2c75d69c0
Performance / resource usage: - circuit_breaker_metrics: fix data race on failCounters map (RWMutex + double-checked locking) - server.go: drop user_id and op_name metric labels (Prometheus cardinality bound); de-duplicate extractUserInfo - graphql.go: gate runtime.ReadMemStats per-request behind ENABLE_ALLOCATION_TRACKING flag (default off) - graphql.go: collapse two-pass AST scan into single pass; lower-case once - sanitization.go: cache compiled redaction regexes per pattern via sync.Map; hoist inner constants to pkg vars - proxy.go: hoist connection/timeout substrings to pkg vars; sentinel errors for static error paths; drop dead Headers map alloc - metrics_aggregator.go: log-field allocation guarded by Logger.IsLevelEnabled - logging/logger.go: add IsLevelEnabled helper - lru_cache.go: 16-shard sharding, FNV-1a routing (concurrent throughput +22%) - cache/memory/lru_memory_cache.go: gzip compress/decompress moved outside mu.Lock - rps_tracker.go: RWMutex+uint64 -> atomic.Uint64 - retry_budget.go: drop unused mutex - api.go: bannedUsersIDs map+RWMutex -> sync.Map (+ snapshot/replace helpers) - tracing/tracing.go: pkg-level constSpanAttrs, copy-then-append in StartSpanWithAttributes - admin_dashboard.go: handleStatsWebSocket reuses bytes.Buffer + json.Encoder per connection Build / runtime: - Makefile: -ldflags="-s -w" -trimpath, CGO_ENABLED=0 for build (=1 for test recipes) - Dockerfile + Dockerfile.goreleaser: ENV GOMEMLIMIT=512MiB - main.go: blank import go.uber.org/automaxprocs (cgroup-aware GOMAXPROCS) - main.go: PPROF_PORT env var wires net/http/pprof on 127.0.0.1 only with full server timeouts - README.md: env-var docs + metric-label docs updated; cardinality note Test coverage push (per package): - main 51.2% -> 74.7% - cache 66.3% -> 93.7% - cache/redis 45.5% -> 98.2% - tracing 66.7% -> 72.9% - (cache/memory 91.6%, logging 91.9%, monitoring 77.6%, pkg/pools 100% unchanged) New test files: coverage_micro_test, coverage_extras_test, server_handlers_test, api_health_test, admin_dashboard_cluster_test, metrics_aggregator_test, concerns_test, cache/cache_coverage_test, cache/redis/redis_coverage_test, tracing/tracing_coverage_test. Bug fix: connection_resilience_test.go TestIntegratedHealthManagement.health_manager_startup was sync.Once-coupled to InitializeBackendHealth and panicked when another test (e.g. via parseConfig) had already triggered Once. Use NewBackendHealthManager directly.
602 lines
16 KiB
Go
602 lines
16 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/gofiber/fiber/v2"
|
|
libpack_cache "github.com/lukaszraczylo/graphql-monitoring-proxy/cache"
|
|
"github.com/valyala/fasthttp"
|
|
)
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// AddRequestUUID
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func TestAddRequestUUID_SetsLocalsAndCallsNext(t *testing.T) {
|
|
app := fiber.New(fiber.Config{DisableStartupMessage: true})
|
|
app.Use(AddRequestUUID)
|
|
|
|
var captured string
|
|
app.Get("/", func(c *fiber.Ctx) error {
|
|
if v, ok := c.Locals("request_uuid").(string); ok {
|
|
captured = v
|
|
}
|
|
return c.SendStatus(200)
|
|
})
|
|
|
|
req := httptest.NewRequest("GET", "/", nil)
|
|
resp, err := app.Test(req, -1)
|
|
if err != nil {
|
|
t.Fatalf("app.Test: %v", err)
|
|
}
|
|
_ = resp.Body.Close()
|
|
|
|
if resp.StatusCode != 200 {
|
|
t.Fatalf("want 200, got %d", resp.StatusCode)
|
|
}
|
|
if captured == "" {
|
|
t.Fatal("request_uuid not set in Locals")
|
|
}
|
|
// UUIDs are 36 chars (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
|
|
if len(captured) != 36 {
|
|
t.Errorf("unexpected UUID length: %q", captured)
|
|
}
|
|
}
|
|
|
|
func TestAddRequestUUID_UniquePerRequest(t *testing.T) {
|
|
app := fiber.New(fiber.Config{DisableStartupMessage: true})
|
|
app.Use(AddRequestUUID)
|
|
|
|
seen := make([]string, 0, 5)
|
|
app.Get("/", func(c *fiber.Ctx) error {
|
|
if v, ok := c.Locals("request_uuid").(string); ok {
|
|
seen = append(seen, v)
|
|
}
|
|
return c.SendStatus(200)
|
|
})
|
|
|
|
for i := range 5 {
|
|
req := httptest.NewRequest("GET", "/", nil)
|
|
resp, err := app.Test(req, -1)
|
|
if err != nil {
|
|
t.Fatalf("request %d: %v", i, err)
|
|
}
|
|
_ = resp.Body.Close()
|
|
}
|
|
|
|
set := make(map[string]struct{}, len(seen))
|
|
for _, id := range seen {
|
|
set[id] = struct{}{}
|
|
}
|
|
if len(set) != 5 {
|
|
t.Errorf("expected 5 unique UUIDs, got %d unique in %v", len(set), seen)
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// healthCheck
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func TestHealthCheck_Returns200WithJSON(t *testing.T) {
|
|
// Ensure cfg is ready and GraphQL check is disabled via query param
|
|
parseConfig()
|
|
_ = StartMonitoringServer()
|
|
|
|
app := fiber.New(fiber.Config{DisableStartupMessage: true})
|
|
app.Get("/health", healthCheck)
|
|
|
|
// Pass check_graphql=false to avoid real network call
|
|
req := httptest.NewRequest("GET", "/health?check_graphql=false&check_redis=false", nil)
|
|
resp, err := app.Test(req, 10000)
|
|
if err != nil {
|
|
t.Fatalf("app.Test: %v", err)
|
|
}
|
|
defer func() { _ = resp.Body.Close() }()
|
|
|
|
if resp.StatusCode != 200 {
|
|
t.Fatalf("want 200, got %d", resp.StatusCode)
|
|
}
|
|
|
|
var body map[string]any
|
|
if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
|
|
t.Fatalf("decode response: %v", err)
|
|
}
|
|
|
|
if _, ok := body["status"]; !ok {
|
|
t.Error("response missing 'status' field")
|
|
}
|
|
if _, ok := body["timestamp"]; !ok {
|
|
t.Error("response missing 'timestamp' field")
|
|
}
|
|
if body["status"] != "healthy" {
|
|
t.Errorf("want status=healthy, got %v", body["status"])
|
|
}
|
|
}
|
|
|
|
func TestHealthCheck_UnhealthyWhenGraphQLDown(t *testing.T) {
|
|
parseConfig()
|
|
_ = StartMonitoringServer()
|
|
|
|
// Point to a server that refuses connections
|
|
cfgMutex.Lock()
|
|
origHost := cfg.Server.HostGraphQL
|
|
cfg.Server.HostGraphQL = "http://127.0.0.1:1" // port 1 always refused
|
|
cfgMutex.Unlock()
|
|
defer func() {
|
|
cfgMutex.Lock()
|
|
cfg.Server.HostGraphQL = origHost
|
|
cfgMutex.Unlock()
|
|
}()
|
|
|
|
app := fiber.New(fiber.Config{DisableStartupMessage: true})
|
|
app.Get("/health", healthCheck)
|
|
|
|
req := httptest.NewRequest("GET", "/health?check_redis=false", nil)
|
|
resp, err := app.Test(req, 15000)
|
|
if err != nil {
|
|
t.Fatalf("app.Test: %v", err)
|
|
}
|
|
defer func() { _ = resp.Body.Close() }()
|
|
|
|
// Should return 503 when backend is unreachable
|
|
if resp.StatusCode != fiber.StatusServiceUnavailable {
|
|
t.Fatalf("want 503, got %d", resp.StatusCode)
|
|
}
|
|
|
|
var body map[string]any
|
|
if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
|
|
t.Fatalf("decode: %v", err)
|
|
}
|
|
if body["status"] != "unhealthy" {
|
|
t.Errorf("want unhealthy, got %v", body["status"])
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// processGraphQLRequest
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func TestProcessGraphQLRequest_ValidBodyProxiesToBackend(t *testing.T) {
|
|
parseConfig()
|
|
_ = StartMonitoringServer()
|
|
|
|
backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(200)
|
|
_, _ = w.Write([]byte(`{"data":{"test":"ok"}}`))
|
|
}))
|
|
defer backend.Close()
|
|
|
|
cfgMutex.Lock()
|
|
origHost := cfg.Server.HostGraphQL
|
|
origHostRO := cfg.Server.HostGraphQLReadOnly
|
|
origCache := cfg.Cache.CacheEnable
|
|
cfg.Server.HostGraphQL = backend.URL
|
|
cfg.Server.HostGraphQLReadOnly = backend.URL
|
|
cfg.Cache.CacheEnable = false
|
|
cfgMutex.Unlock()
|
|
defer func() {
|
|
cfgMutex.Lock()
|
|
cfg.Server.HostGraphQL = origHost
|
|
cfg.Server.HostGraphQLReadOnly = origHostRO
|
|
cfg.Cache.CacheEnable = origCache
|
|
cfgMutex.Unlock()
|
|
}()
|
|
|
|
app := fiber.New(fiber.Config{DisableStartupMessage: true})
|
|
app.Post("/*", processGraphQLRequest)
|
|
|
|
body := `{"query":"query { __typename }"}`
|
|
req := httptest.NewRequest("POST", "/v1/graphql", strings.NewReader(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
resp, err := app.Test(req, 10000)
|
|
if err != nil {
|
|
t.Fatalf("app.Test: %v", err)
|
|
}
|
|
defer func() { _ = resp.Body.Close() }()
|
|
|
|
if resp.StatusCode != 200 {
|
|
t.Errorf("want 200, got %d", resp.StatusCode)
|
|
}
|
|
}
|
|
|
|
func TestProcessGraphQLRequest_MalformedBodyStillHandled(t *testing.T) {
|
|
parseConfig()
|
|
_ = StartMonitoringServer()
|
|
|
|
// Backend that always returns 200 (malformed body is handled by proxy layer)
|
|
backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(200)
|
|
_, _ = w.Write([]byte(`{"errors":[{"message":"parse error"}]}`))
|
|
}))
|
|
defer backend.Close()
|
|
|
|
cfgMutex.Lock()
|
|
origHost := cfg.Server.HostGraphQL
|
|
origHostRO := cfg.Server.HostGraphQLReadOnly
|
|
origCache := cfg.Cache.CacheEnable
|
|
cfg.Server.HostGraphQL = backend.URL
|
|
cfg.Server.HostGraphQLReadOnly = backend.URL
|
|
cfg.Cache.CacheEnable = false
|
|
cfgMutex.Unlock()
|
|
defer func() {
|
|
cfgMutex.Lock()
|
|
cfg.Server.HostGraphQL = origHost
|
|
cfg.Server.HostGraphQLReadOnly = origHostRO
|
|
cfg.Cache.CacheEnable = origCache
|
|
cfgMutex.Unlock()
|
|
}()
|
|
|
|
app := fiber.New(fiber.Config{DisableStartupMessage: true})
|
|
app.Post("/*", processGraphQLRequest)
|
|
|
|
// Not valid JSON — proxy should still forward or return gracefully
|
|
body := `not-json-at-all`
|
|
req := httptest.NewRequest("POST", "/v1/graphql", strings.NewReader(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
resp, err := app.Test(req, 10000)
|
|
if err != nil {
|
|
t.Fatalf("app.Test: %v", err)
|
|
}
|
|
defer func() { _ = resp.Body.Close() }()
|
|
|
|
// Should not panic; any 2xx or 5xx is acceptable — just must not crash
|
|
if resp.StatusCode < 100 || resp.StatusCode > 599 {
|
|
t.Errorf("unexpected status %d", resp.StatusCode)
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// handleCaching — wasCached=true path (cache hit)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func TestHandleCaching_CacheHitReturnsStoredResponse(t *testing.T) {
|
|
parseConfig()
|
|
_ = StartMonitoringServer()
|
|
|
|
// Enable in-memory cache
|
|
libpack_cache.EnableCache(&libpack_cache.CacheConfig{
|
|
Logger: cfg.Logger,
|
|
TTL: 60,
|
|
})
|
|
libpack_cache.CacheClear()
|
|
|
|
cfgMutex.Lock()
|
|
origEnable := cfg.Cache.CacheEnable
|
|
cfg.Cache.CacheEnable = true
|
|
cfg.Cache.CacheTTL = 60
|
|
cfgMutex.Unlock()
|
|
defer func() {
|
|
cfgMutex.Lock()
|
|
cfg.Cache.CacheEnable = origEnable
|
|
cfgMutex.Unlock()
|
|
}()
|
|
|
|
backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(200)
|
|
_, _ = w.Write([]byte(`{"data":{"users":[]}}`))
|
|
}))
|
|
defer backend.Close()
|
|
|
|
cfgMutex.Lock()
|
|
origHost := cfg.Server.HostGraphQL
|
|
origHostRO := cfg.Server.HostGraphQLReadOnly
|
|
cfg.Server.HostGraphQL = backend.URL
|
|
cfg.Server.HostGraphQLReadOnly = backend.URL
|
|
cfgMutex.Unlock()
|
|
defer func() {
|
|
cfgMutex.Lock()
|
|
cfg.Server.HostGraphQL = origHost
|
|
cfg.Server.HostGraphQLReadOnly = origHostRO
|
|
cfgMutex.Unlock()
|
|
}()
|
|
|
|
app := fiber.New(fiber.Config{DisableStartupMessage: true})
|
|
app.Post("/*", processGraphQLRequest)
|
|
|
|
queryBody := `{"query":"query { users { id } }"}`
|
|
|
|
// First request — cache miss, hits backend
|
|
req1 := httptest.NewRequest("POST", "/v1/graphql", strings.NewReader(queryBody))
|
|
req1.Header.Set("Content-Type", "application/json")
|
|
resp1, err := app.Test(req1, 10000)
|
|
if err != nil {
|
|
t.Fatalf("first request: %v", err)
|
|
}
|
|
_ = resp1.Body.Close()
|
|
|
|
if resp1.StatusCode != 200 {
|
|
t.Fatalf("first request want 200, got %d", resp1.StatusCode)
|
|
}
|
|
|
|
// Second identical request — should hit cache
|
|
req2 := httptest.NewRequest("POST", "/v1/graphql", strings.NewReader(queryBody))
|
|
req2.Header.Set("Content-Type", "application/json")
|
|
resp2, err := app.Test(req2, 10000)
|
|
if err != nil {
|
|
t.Fatalf("second request: %v", err)
|
|
}
|
|
_ = resp2.Body.Close()
|
|
|
|
if resp2.StatusCode != 200 {
|
|
t.Fatalf("second request want 200, got %d", resp2.StatusCode)
|
|
}
|
|
if resp2.Header.Get("X-Cache-Hit") != "true" {
|
|
t.Error("second request should have X-Cache-Hit: true header")
|
|
}
|
|
}
|
|
|
|
func TestHandleCaching_CacheMissProxiesRequest(t *testing.T) {
|
|
parseConfig()
|
|
_ = StartMonitoringServer()
|
|
|
|
libpack_cache.EnableCache(&libpack_cache.CacheConfig{
|
|
Logger: cfg.Logger,
|
|
TTL: 60,
|
|
})
|
|
libpack_cache.CacheClear()
|
|
|
|
cfgMutex.Lock()
|
|
origEnable := cfg.Cache.CacheEnable
|
|
cfg.Cache.CacheEnable = true
|
|
cfg.Cache.CacheTTL = 60
|
|
cfgMutex.Unlock()
|
|
defer func() {
|
|
cfgMutex.Lock()
|
|
cfg.Cache.CacheEnable = origEnable
|
|
cfgMutex.Unlock()
|
|
}()
|
|
|
|
backendCalled := 0
|
|
backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
backendCalled++
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(200)
|
|
_, _ = fmt.Fprintf(w, `{"data":{"call":%d}}`, backendCalled)
|
|
}))
|
|
defer backend.Close()
|
|
|
|
cfgMutex.Lock()
|
|
origHost := cfg.Server.HostGraphQL
|
|
origHostRO := cfg.Server.HostGraphQLReadOnly
|
|
cfg.Server.HostGraphQL = backend.URL
|
|
cfg.Server.HostGraphQLReadOnly = backend.URL
|
|
cfgMutex.Unlock()
|
|
defer func() {
|
|
cfgMutex.Lock()
|
|
cfg.Server.HostGraphQL = origHost
|
|
cfg.Server.HostGraphQLReadOnly = origHostRO
|
|
cfgMutex.Unlock()
|
|
}()
|
|
|
|
app := fiber.New(fiber.Config{DisableStartupMessage: true})
|
|
app.Post("/*", processGraphQLRequest)
|
|
|
|
// Unique query so no prior cache entry
|
|
queryBody := `{"query":"query { uniqueMissTest_12345 { id } }"}`
|
|
req := httptest.NewRequest("POST", "/v1/graphql", strings.NewReader(queryBody))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
resp, err := app.Test(req, 10000)
|
|
if err != nil {
|
|
t.Fatalf("app.Test: %v", err)
|
|
}
|
|
_ = resp.Body.Close()
|
|
|
|
if resp.StatusCode != 200 {
|
|
t.Errorf("want 200, got %d", resp.StatusCode)
|
|
}
|
|
if resp.Header.Get("X-Cache-Hit") == "true" {
|
|
t.Error("first request should not be a cache hit")
|
|
}
|
|
if backendCalled == 0 {
|
|
t.Error("backend should have been called on cache miss")
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// handleCaching — direct unit test for wasCached=true branch
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func TestHandleCaching_DirectCacheHitBranch(t *testing.T) {
|
|
parseConfig()
|
|
_ = StartMonitoringServer()
|
|
|
|
libpack_cache.EnableCache(&libpack_cache.CacheConfig{
|
|
Logger: cfg.Logger,
|
|
TTL: 60,
|
|
})
|
|
libpack_cache.CacheClear()
|
|
|
|
cfgMutex.Lock()
|
|
origEnable := cfg.Cache.CacheEnable
|
|
cfg.Cache.CacheEnable = true
|
|
cfg.Cache.CacheTTL = 60
|
|
cfgMutex.Unlock()
|
|
defer func() {
|
|
cfgMutex.Lock()
|
|
cfg.Cache.CacheEnable = origEnable
|
|
cfgMutex.Unlock()
|
|
}()
|
|
|
|
app := fiber.New(fiber.Config{DisableStartupMessage: true})
|
|
|
|
var wasCachedResult bool
|
|
app.Post("/test", func(c *fiber.Ctx) error {
|
|
parsedResult := &parseGraphQLQueryResult{
|
|
cacheTime: 60,
|
|
cacheRequest: true,
|
|
activeEndpoint: cfg.Server.HostGraphQL,
|
|
}
|
|
|
|
// Pre-populate the cache so lookup hits
|
|
cacheKey := libpack_cache.CalculateHash(c, "-", "-")
|
|
libpack_cache.CacheStore(cacheKey, []byte(`{"data":{"cached":true}}`))
|
|
|
|
var err error
|
|
wasCachedResult, err = handleCaching(c, parsedResult, "-", "-")
|
|
return err
|
|
})
|
|
|
|
reqCtx := &fasthttp.RequestCtx{}
|
|
reqCtx.Request.SetRequestURI("/test")
|
|
reqCtx.Request.Header.SetMethod("POST")
|
|
reqCtx.Request.Header.Set("Content-Type", "application/json")
|
|
reqCtx.Request.SetBody([]byte(`{"query":"query { cachedQuery }"}`))
|
|
|
|
ctx := app.AcquireCtx(reqCtx)
|
|
defer app.ReleaseCtx(ctx)
|
|
|
|
parsedResult := &parseGraphQLQueryResult{
|
|
cacheTime: 60,
|
|
cacheRequest: true,
|
|
activeEndpoint: cfg.Server.HostGraphQL,
|
|
}
|
|
|
|
cacheKey := libpack_cache.CalculateHash(ctx, "-", "-")
|
|
libpack_cache.CacheStore(cacheKey, []byte(`{"data":{"cached":true}}`))
|
|
|
|
wasCached, err := handleCaching(ctx, parsedResult, "-", "-")
|
|
if err != nil {
|
|
t.Fatalf("handleCaching returned error: %v", err)
|
|
}
|
|
if !wasCached {
|
|
t.Error("expected wasCached=true when cache hit")
|
|
}
|
|
_ = wasCachedResult
|
|
}
|
|
|
|
func TestHandleCaching_NoCacheEnabled_ProxiesDirect(t *testing.T) {
|
|
parseConfig()
|
|
_ = StartMonitoringServer()
|
|
|
|
backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(200)
|
|
_, _ = w.Write([]byte(`{"data":{"noCacheTest":true}}`))
|
|
}))
|
|
defer backend.Close()
|
|
|
|
cfgMutex.Lock()
|
|
origEnable := cfg.Cache.CacheEnable
|
|
origRedis := cfg.Cache.CacheRedisEnable
|
|
origHost := cfg.Server.HostGraphQL
|
|
origHostRO := cfg.Server.HostGraphQLReadOnly
|
|
cfg.Cache.CacheEnable = false
|
|
cfg.Cache.CacheRedisEnable = false
|
|
cfg.Server.HostGraphQL = backend.URL
|
|
cfg.Server.HostGraphQLReadOnly = backend.URL
|
|
cfgMutex.Unlock()
|
|
defer func() {
|
|
cfgMutex.Lock()
|
|
cfg.Cache.CacheEnable = origEnable
|
|
cfg.Cache.CacheRedisEnable = origRedis
|
|
cfg.Server.HostGraphQL = origHost
|
|
cfg.Server.HostGraphQLReadOnly = origHostRO
|
|
cfgMutex.Unlock()
|
|
}()
|
|
|
|
app := fiber.New(fiber.Config{DisableStartupMessage: true})
|
|
|
|
reqCtx := &fasthttp.RequestCtx{}
|
|
reqCtx.Request.SetRequestURI("/v1/graphql")
|
|
reqCtx.Request.Header.SetMethod("POST")
|
|
reqCtx.Request.Header.Set("Content-Type", "application/json")
|
|
reqCtx.Request.SetBody([]byte(`{"query":"query { noCacheTest }"}`))
|
|
|
|
fCtx := app.AcquireCtx(reqCtx)
|
|
defer app.ReleaseCtx(fCtx)
|
|
|
|
parsedResult := &parseGraphQLQueryResult{
|
|
cacheRequest: false,
|
|
cacheTime: 0,
|
|
activeEndpoint: backend.URL,
|
|
}
|
|
|
|
wasCached, err := handleCaching(fCtx, parsedResult, "-", "-")
|
|
if err != nil {
|
|
t.Fatalf("handleCaching error: %v", err)
|
|
}
|
|
if wasCached {
|
|
t.Error("expected wasCached=false when cache disabled")
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// StartHTTPProxy — starts then shuts down cleanly
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func TestStartHTTPProxy_StartsAndShutdown(t *testing.T) {
|
|
parseConfig()
|
|
_ = StartMonitoringServer()
|
|
|
|
// Grab a free port
|
|
l, err := net.Listen("tcp", "127.0.0.1:0")
|
|
if err != nil {
|
|
t.Fatalf("net.Listen: %v", err)
|
|
}
|
|
port := l.Addr().(*net.TCPAddr).Port
|
|
_ = l.Close()
|
|
|
|
cfgMutex.Lock()
|
|
origPort := cfg.Server.PortGraphQL
|
|
origTimeout := cfg.Client.ClientTimeout
|
|
origWS := cfg.WebSocket.Enable
|
|
origAdmin := cfg.AdminDashboard.Enable
|
|
cfg.Server.PortGraphQL = port
|
|
cfg.Client.ClientTimeout = 5
|
|
cfg.WebSocket.Enable = false
|
|
cfg.AdminDashboard.Enable = false
|
|
cfgMutex.Unlock()
|
|
|
|
t.Cleanup(func() {
|
|
cfgMutex.Lock()
|
|
cfg.Server.PortGraphQL = origPort
|
|
cfg.Client.ClientTimeout = origTimeout
|
|
cfg.WebSocket.Enable = origWS
|
|
cfg.AdminDashboard.Enable = origAdmin
|
|
cfgMutex.Unlock()
|
|
})
|
|
|
|
errCh := make(chan error, 1)
|
|
go func() {
|
|
errCh <- StartHTTPProxy()
|
|
}()
|
|
|
|
// Wait for server to bind
|
|
deadline := time.Now().Add(3 * time.Second)
|
|
var conn net.Conn
|
|
for time.Now().Before(deadline) {
|
|
conn, err = net.DialTimeout("tcp", fmt.Sprintf("127.0.0.1:%d", port), 100*time.Millisecond)
|
|
if err == nil {
|
|
break
|
|
}
|
|
time.Sleep(50 * time.Millisecond)
|
|
}
|
|
if conn == nil {
|
|
t.Fatalf("server did not start on port %d within 3s", port)
|
|
}
|
|
_ = conn.Close()
|
|
|
|
// Send a health check to confirm it's serving
|
|
httpResp, err := http.Get(fmt.Sprintf("http://127.0.0.1:%d/health?check_graphql=false&check_redis=false", port))
|
|
if err != nil {
|
|
t.Fatalf("GET /health: %v", err)
|
|
}
|
|
_ = httpResp.Body.Close()
|
|
if httpResp.StatusCode != 200 {
|
|
t.Errorf("want 200, got %d", httpResp.StatusCode)
|
|
}
|
|
}
|