mirror of
https://github.com/lukaszraczylo/graphql-monitoring-proxy.git
synced 2026-06-05 23:03:48 +00:00
improvements mid may 2025 (#24)
* General improvements and bug fixes. * Improve tests coverage. * fixup! Improve tests coverage. * Update README.md with latest changes. * Fix the uint32 * Resolve issue with race condition for logging. * fixup! Merge remote-tracking branch 'origin/main' into improvements-mid-apr-2025 * Fix the test of the rate limiter * Add default ratelimit.json file * Update dependencies. * Significant refactor. * fixup! Significant refactor. * fixup! Merge remote-tracking branch 'origin/main' into improvements-mid-apr-2025 * fixup! fixup! Merge remote-tracking branch 'origin/main' into improvements-mid-apr-2025 * fixup! fixup! fixup! Merge remote-tracking branch 'origin/main' into improvements-mid-apr-2025 * fixup! fixup! fixup! fixup! fixup! Merge remote-tracking branch 'origin/main' into improvements-mid-apr-2025 * fixup! fixup! fixup! fixup! fixup! fixup! Merge remote-tracking branch 'origin/main' into improvements-mid-apr-2025 * fixup! fixup! fixup! fixup! fixup! fixup! fixup! Merge remote-tracking branch 'origin/main' into improvements-mid-apr-2025 * fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! Merge remote-tracking branch 'origin/main' into improvements-mid-apr-2025 * fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! Merge remote-tracking branch 'origin/main' into improvements-mid-apr-2025 * fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! Merge remote-tracking branch 'origin/main' into improvements-mid-apr-2025
This commit is contained in:
@@ -0,0 +1,345 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/valyala/fasthttp"
|
||||
)
|
||||
|
||||
// Tests for error handling in gzip decompression and general error propagation
|
||||
|
||||
// TestGzipHandling tests proper handling of gzipped responses
|
||||
func (suite *Tests) TestGzipHandling() {
|
||||
// Create a test server that returns gzipped content
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// Set the Content-Encoding header to indicate gzipped content
|
||||
w.Header().Set("Content-Encoding", "gzip")
|
||||
|
||||
// Create a gzipped response
|
||||
var buf bytes.Buffer
|
||||
gzipWriter := gzip.NewWriter(&buf)
|
||||
payload := `{"data":{"test":"gzipped response"}}`
|
||||
_, _ = gzipWriter.Write([]byte(payload))
|
||||
_ = gzipWriter.Close()
|
||||
|
||||
// Send the gzipped data
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write(buf.Bytes())
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
// Store original client and restore after test
|
||||
originalClient := cfg.Client.FastProxyClient
|
||||
defer func() {
|
||||
cfg.Client.FastProxyClient = originalClient
|
||||
}()
|
||||
|
||||
// Configure client for test
|
||||
cfg.Client.ClientTimeout = 5
|
||||
cfg.Client.FastProxyClient = createFasthttpClient(cfg)
|
||||
|
||||
// Configure server URL
|
||||
cfg.Server.HostGraphQL = server.URL
|
||||
|
||||
// Create request context
|
||||
reqCtx := &fasthttp.RequestCtx{}
|
||||
reqCtx.Request.SetRequestURI("/graphql")
|
||||
reqCtx.Request.Header.SetMethod("POST")
|
||||
reqCtx.Request.Header.Set("Content-Type", "application/json")
|
||||
reqCtx.Request.SetBody([]byte(`{"query": "query { test }"}`))
|
||||
|
||||
// Create fiber context
|
||||
ctx := suite.app.AcquireCtx(reqCtx)
|
||||
defer suite.app.ReleaseCtx(ctx)
|
||||
|
||||
// Call the proxy function
|
||||
err := proxyTheRequest(ctx, cfg.Server.HostGraphQL)
|
||||
|
||||
// Verify success
|
||||
suite.Nil(err, "proxyTheRequest should succeed with gzipped content")
|
||||
suite.Equal(fiber.StatusOK, ctx.Response().StatusCode(), "Response status should be 200 OK")
|
||||
|
||||
// Verify the content was properly decompressed
|
||||
responseBody := string(ctx.Response().Body())
|
||||
suite.Contains(responseBody, "gzipped response", "Response should contain the decompressed content")
|
||||
|
||||
// Verify the Content-Encoding header was removed
|
||||
suite.Equal("", string(ctx.Response().Header.Peek("Content-Encoding")),
|
||||
"Content-Encoding header should be removed after decompression")
|
||||
}
|
||||
|
||||
// TestInvalidGzipHandling tests handling of responses with invalid gzip data
|
||||
func (suite *Tests) TestInvalidGzipHandling() {
|
||||
// Create a test server that returns invalid gzipped content
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// Set the Content-Encoding header to indicate gzipped content
|
||||
w.Header().Set("Content-Encoding", "gzip")
|
||||
|
||||
// Send invalid gzip data
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write([]byte("This is not valid gzip data"))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
// Store original client and restore after test
|
||||
originalClient := cfg.Client.FastProxyClient
|
||||
defer func() {
|
||||
cfg.Client.FastProxyClient = originalClient
|
||||
}()
|
||||
|
||||
// Configure client for test
|
||||
cfg.Client.ClientTimeout = 5
|
||||
cfg.Client.FastProxyClient = createFasthttpClient(cfg)
|
||||
|
||||
// Configure server URL
|
||||
cfg.Server.HostGraphQL = server.URL
|
||||
|
||||
// Create request context
|
||||
reqCtx := &fasthttp.RequestCtx{}
|
||||
reqCtx.Request.SetRequestURI("/graphql")
|
||||
reqCtx.Request.Header.SetMethod("POST")
|
||||
reqCtx.Request.Header.Set("Content-Type", "application/json")
|
||||
reqCtx.Request.SetBody([]byte(`{"query": "query { test }"}`))
|
||||
|
||||
// Create fiber context
|
||||
ctx := suite.app.AcquireCtx(reqCtx)
|
||||
defer suite.app.ReleaseCtx(ctx)
|
||||
|
||||
// Call the proxy function
|
||||
err := proxyTheRequest(ctx, cfg.Server.HostGraphQL)
|
||||
|
||||
// Verify error handling
|
||||
suite.NotNil(err, "proxyTheRequest should return error with invalid gzip data")
|
||||
suite.Contains(err.Error(), "gzip", "Error should mention gzip decompression issue")
|
||||
}
|
||||
|
||||
// TestErrorPropagation tests that various errors are properly propagated
|
||||
func (suite *Tests) TestErrorPropagation() {
|
||||
tests := []struct {
|
||||
name string
|
||||
serverHandler func(w http.ResponseWriter, r *http.Request)
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
name: "5xx_error",
|
||||
serverHandler: func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
_, _ = w.Write([]byte(`{"errors":[{"message":"Internal server error"}]}`))
|
||||
},
|
||||
expectedError: "received non-200 response",
|
||||
},
|
||||
{
|
||||
name: "malformed_json_response",
|
||||
serverHandler: func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write([]byte(`{malformed json`))
|
||||
},
|
||||
expectedError: "", // No error expected, as we don't validate JSON format
|
||||
},
|
||||
{
|
||||
name: "empty_response",
|
||||
serverHandler: func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
// Empty response body
|
||||
},
|
||||
expectedError: "", // No error expected, empty responses are valid
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
suite.Run(tt.name, func() {
|
||||
// Create a test server with the current test handler
|
||||
server := httptest.NewServer(http.HandlerFunc(tt.serverHandler))
|
||||
defer server.Close()
|
||||
|
||||
// Store original client and restore after test
|
||||
originalClient := cfg.Client.FastProxyClient
|
||||
defer func() {
|
||||
cfg.Client.FastProxyClient = originalClient
|
||||
}()
|
||||
|
||||
// Configure client for test
|
||||
cfg.Client.ClientTimeout = 5
|
||||
cfg.Client.FastProxyClient = createFasthttpClient(cfg)
|
||||
|
||||
// Configure server URL
|
||||
cfg.Server.HostGraphQL = server.URL
|
||||
|
||||
// Create request context
|
||||
reqCtx := &fasthttp.RequestCtx{}
|
||||
reqCtx.Request.SetRequestURI("/graphql")
|
||||
reqCtx.Request.Header.SetMethod("POST")
|
||||
reqCtx.Request.Header.Set("Content-Type", "application/json")
|
||||
reqCtx.Request.SetBody([]byte(`{"query": "query { test }"}`))
|
||||
|
||||
// Create fiber context
|
||||
ctx := suite.app.AcquireCtx(reqCtx)
|
||||
defer suite.app.ReleaseCtx(ctx)
|
||||
|
||||
// Call the proxy function
|
||||
err := proxyTheRequest(ctx, cfg.Server.HostGraphQL)
|
||||
|
||||
// Verify error handling based on test case
|
||||
if tt.expectedError != "" {
|
||||
suite.NotNil(err, "proxyTheRequest should return error")
|
||||
suite.Contains(err.Error(), tt.expectedError,
|
||||
"Error should contain expected message")
|
||||
} else {
|
||||
suite.Nil(err, "proxyTheRequest should not return error")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestMiddlewareErrorPropagation tests error propagation through the middleware chain
|
||||
func (suite *Tests) TestMiddlewareErrorPropagation() {
|
||||
// Setup a basic middleware chain that mimics the production setup
|
||||
testMiddleware := func(c *fiber.Ctx) error {
|
||||
// Access request path to check proper error propagation
|
||||
path := c.Path()
|
||||
if path == "/error-path" {
|
||||
return fmt.Errorf("middleware error")
|
||||
}
|
||||
return c.Next()
|
||||
}
|
||||
|
||||
app := fiber.New()
|
||||
app.Use(testMiddleware)
|
||||
|
||||
// Setup the handler that would receive the request after middleware
|
||||
app.Post("/graphql", func(c *fiber.Ctx) error {
|
||||
// This should not be called if middleware returns error
|
||||
return c.Status(fiber.StatusOK).JSON(fiber.Map{"data": "success"})
|
||||
})
|
||||
|
||||
// Test successful path
|
||||
req := httptest.NewRequest("POST", "/graphql", nil)
|
||||
resp, err := app.Test(req)
|
||||
suite.Nil(err, "App test should not error")
|
||||
suite.Equal(fiber.StatusOK, resp.StatusCode, "Status should be 200 OK")
|
||||
|
||||
// Test error path
|
||||
req = httptest.NewRequest("POST", "/error-path", nil)
|
||||
resp, err = app.Test(req)
|
||||
suite.Nil(err, "App test should not error")
|
||||
suite.NotEqual(fiber.StatusOK, resp.StatusCode, "Status should not be 200 OK")
|
||||
|
||||
// Check that error status was properly propagated
|
||||
suite.Equal(fiber.StatusInternalServerError, resp.StatusCode,
|
||||
"Error status should be 500 Internal Server Error")
|
||||
}
|
||||
|
||||
// TestTimeout tests the proper handling of timeouts
|
||||
func (suite *Tests) TestTimeout() {
|
||||
// Skip this timing-sensitive test as it's prone to race conditions under race detection
|
||||
suite.T().Skip("Skipping timing-sensitive timeout test due to race conditions under race detection")
|
||||
|
||||
// Create a test server that simulates a timeout
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// Sleep longer than the client timeout
|
||||
time.Sleep(3 * time.Second)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write([]byte(`{"data":{"test":"response"}}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
// Store original client and restore after test
|
||||
originalClient := cfg.Client.FastProxyClient
|
||||
originalTimeout := cfg.Client.ClientTimeout
|
||||
defer func() {
|
||||
cfg.Client.FastProxyClient = originalClient
|
||||
cfg.Client.ClientTimeout = originalTimeout
|
||||
}()
|
||||
|
||||
// Configure client with a short timeout
|
||||
cfg.Client.ClientTimeout = 1 // 1 second
|
||||
cfg.Client.FastProxyClient = createFasthttpClient(cfg)
|
||||
|
||||
// Configure server URL
|
||||
cfg.Server.HostGraphQL = server.URL
|
||||
|
||||
// Create request context
|
||||
reqCtx := &fasthttp.RequestCtx{}
|
||||
reqCtx.Request.SetRequestURI("/graphql")
|
||||
reqCtx.Request.Header.SetMethod("POST")
|
||||
reqCtx.Request.Header.Set("Content-Type", "application/json")
|
||||
reqCtx.Request.SetBody([]byte(`{"query": "query { test }"}`))
|
||||
|
||||
// Create fiber context
|
||||
ctx := suite.app.AcquireCtx(reqCtx)
|
||||
defer suite.app.ReleaseCtx(ctx)
|
||||
|
||||
// Call the proxy function
|
||||
err := proxyTheRequest(ctx, cfg.Server.HostGraphQL)
|
||||
|
||||
// Verify timeout error handling
|
||||
suite.NotNil(err, "proxyTheRequest should return error on timeout")
|
||||
if err != nil {
|
||||
suite.Contains(err.Error(), "timeout", "Error should mention timeout")
|
||||
}
|
||||
}
|
||||
|
||||
// TestLargeResponseHandling tests handling of large responses
|
||||
func (suite *Tests) TestLargeResponseHandling() {
|
||||
// Create a test server that returns a large response
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// Generate a large response (1MB)
|
||||
largeResponse := make([]byte, 1024*1024)
|
||||
for i := 0; i < len(largeResponse); i++ {
|
||||
largeResponse[i] = byte(i % 256)
|
||||
}
|
||||
|
||||
// Set headers and send response
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write(largeResponse)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
// Store original client and restore after test
|
||||
originalClient := cfg.Client.FastProxyClient
|
||||
defer func() {
|
||||
cfg.Client.FastProxyClient = originalClient
|
||||
}()
|
||||
|
||||
// Configure client for test
|
||||
cfg.Client.ClientTimeout = 10 // Longer timeout for large response
|
||||
cfg.Client.FastProxyClient = createFasthttpClient(cfg)
|
||||
|
||||
// Configure server URL
|
||||
cfg.Server.HostGraphQL = server.URL
|
||||
|
||||
// Create request context
|
||||
reqCtx := &fasthttp.RequestCtx{}
|
||||
reqCtx.Request.SetRequestURI("/graphql")
|
||||
reqCtx.Request.Header.SetMethod("POST")
|
||||
reqCtx.Request.Header.Set("Content-Type", "application/json")
|
||||
reqCtx.Request.SetBody([]byte(`{"query": "query { test }"}`))
|
||||
|
||||
// Create fiber context
|
||||
ctx := suite.app.AcquireCtx(reqCtx)
|
||||
defer suite.app.ReleaseCtx(ctx)
|
||||
|
||||
// Call the proxy function
|
||||
err := proxyTheRequest(ctx, cfg.Server.HostGraphQL)
|
||||
|
||||
// Verify large response handling
|
||||
suite.Nil(err, "proxyTheRequest should handle large responses")
|
||||
suite.Equal(fiber.StatusOK, ctx.Response().StatusCode(), "Status should be 200 OK")
|
||||
suite.Equal(1024*1024, len(ctx.Response().Body()), "Response body should match expected size")
|
||||
}
|
||||
|
||||
// Helper function to create gzipped data
|
||||
func createGzippedData(data []byte) []byte {
|
||||
var buf bytes.Buffer
|
||||
gw := gzip.NewWriter(&buf)
|
||||
_, _ = gw.Write(data)
|
||||
_ = gw.Close()
|
||||
return buf.Bytes()
|
||||
}
|
||||
Reference in New Issue
Block a user