mirror of
https://github.com/lukaszraczylo/graphql-monitoring-proxy.git
synced 2026-10-22 06:38:13 +00:00
perf+coverage: optimisation pass + coverage push to ≥70%
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.
This commit is contained in:
@@ -0,0 +1,601 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user