mirror of
https://github.com/lukaszraczylo/graphql-monitoring-proxy.git
synced 2026-06-05 23:03:48 +00:00
3aa83d4480
* chore(security,refactor): extract sanitization and improve code quality
- [x] Extract sanitization functions to dedicated sanitization.go module
- [x] Add comprehensive golangci-lint v2 configuration with security rules
- [x] Replace interface{} with any type throughout codebase
- [x] Add admin API authentication security warning
- [x] Extract WebSocket and stats streaming constants
- [x] Add best-effort error handling comments for resource cleanup
- [x] Expand sensitive field patterns for improved PII redaction
- [x] Simplify safety checks and remove redundant nil validations
- [x] Improve test coverage for password field redaction patterns
* refactor: replace interface{} with any type alias
- [x] Replace all `map[string]interface{}` with `map[string]any`
- [x] Replace all `interface{}` with `any` in function signatures and type definitions
- [x] Update sync.Pool New function returns from `interface{}` to `any`
- [x] Add package documentation comments to 8 package files
- [x] Update type assertions and casts to work with `any` type
293 lines
6.6 KiB
Go
293 lines
6.6 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/goccy/go-json"
|
|
"github.com/gofiber/fiber/v2"
|
|
libpack_cache "github.com/lukaszraczylo/graphql-monitoring-proxy/cache/memory"
|
|
libpack_logging "github.com/lukaszraczylo/graphql-monitoring-proxy/logging"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/suite"
|
|
"github.com/valyala/fasthttp"
|
|
)
|
|
|
|
type Tests struct {
|
|
suite.Suite
|
|
app *fiber.App
|
|
ctx context.Context
|
|
cancel context.CancelFunc
|
|
apiDone chan struct{}
|
|
}
|
|
|
|
func (suite *Tests) BeforeTest(suiteName, testName string) {
|
|
}
|
|
|
|
func (suite *Tests) SetupTest() {
|
|
// Setup test
|
|
suite.app = fiber.New(
|
|
fiber.Config{
|
|
DisableStartupMessage: true,
|
|
JSONEncoder: json.Marshal,
|
|
JSONDecoder: json.Unmarshal,
|
|
},
|
|
)
|
|
|
|
// Initialize a simple in-memory cache client for testing purposes
|
|
libpack_cache.New(5 * time.Minute)
|
|
parseConfig()
|
|
|
|
// Create context with cancel for cleanup
|
|
suite.ctx, suite.cancel = context.WithCancel(context.Background())
|
|
suite.apiDone = make(chan struct{})
|
|
|
|
// Start API server in goroutine
|
|
// Temporarily disable API server in tests to isolate issues
|
|
// go func() {
|
|
// enableApi(suite.ctx)
|
|
// close(suite.apiDone)
|
|
// }()
|
|
close(suite.apiDone) // Close immediately since we're not starting the server
|
|
|
|
_ = StartMonitoringServer()
|
|
|
|
// Update logger with proper synchronization
|
|
logger := libpack_logging.New().SetMinLogLevel(libpack_logging.GetLogLevel(getDetailsFromEnv("LOG_LEVEL", "info")))
|
|
cfgMutex.Lock()
|
|
cfg.Logger = logger
|
|
cfgMutex.Unlock()
|
|
|
|
// Setup environment variables here if needed
|
|
_ = os.Setenv("GMP_TEST_STRING", "testValue")
|
|
_ = os.Setenv("GMP_TEST_INT", "123")
|
|
_ = os.Setenv("GMP_TEST_BOOL", "true")
|
|
_ = os.Setenv("NON_GMP_TEST_INT", "31337")
|
|
}
|
|
|
|
// TearDownTest is run after each test to clean up
|
|
func (suite *Tests) TearDownTest() {
|
|
// Cancel context to shutdown API server
|
|
if suite.cancel != nil {
|
|
suite.cancel()
|
|
// Wait for API server to shutdown
|
|
select {
|
|
case <-suite.apiDone:
|
|
case <-time.After(2 * time.Second):
|
|
// Timeout waiting for shutdown
|
|
}
|
|
}
|
|
|
|
// Shutdown connection pool
|
|
ShutdownConnectionPool()
|
|
|
|
// Clean up environment variables here if needed
|
|
_ = os.Unsetenv("GMP_TEST_STRING")
|
|
_ = os.Unsetenv("GMP_TEST_INT")
|
|
_ = os.Unsetenv("GMP_TEST_BOOL")
|
|
_ = os.Unsetenv("NON_GMP_TEST_INT")
|
|
}
|
|
|
|
// func (suite *Tests) AfterTest(suiteName, testName string) {)
|
|
|
|
func TestSuite(t *testing.T) {
|
|
suite.Run(t, new(Tests))
|
|
}
|
|
|
|
func (suite *Tests) Test_envVariableSetting() {
|
|
tests := []struct {
|
|
defaultValue any
|
|
expected any
|
|
name string
|
|
envKey string
|
|
}{
|
|
{
|
|
name: "test_string",
|
|
envKey: "TEST_STRING",
|
|
defaultValue: "default",
|
|
expected: "testValue",
|
|
},
|
|
{
|
|
name: "test_int",
|
|
envKey: "TEST_INT",
|
|
defaultValue: 0,
|
|
expected: 123,
|
|
},
|
|
{
|
|
name: "test_bool",
|
|
envKey: "TEST_BOOL",
|
|
defaultValue: false,
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "test_non_prefixed",
|
|
envKey: "NON_GMP_TEST_INT",
|
|
defaultValue: 0,
|
|
expected: 31337,
|
|
},
|
|
{
|
|
name: "test_non_existing",
|
|
envKey: "NON_EXISTING",
|
|
defaultValue: "default_val",
|
|
expected: "default_val",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
suite.Run(tt.name, func() {
|
|
result := getDetailsFromEnv(tt.envKey, tt.defaultValue)
|
|
assert.Equal(suite.T(), tt.expected, result)
|
|
})
|
|
}
|
|
}
|
|
|
|
func (suite *Tests) Test_getDetailsFromEnv() {
|
|
tests := []struct {
|
|
defaultValue any
|
|
expected any
|
|
name string
|
|
key string
|
|
envValue string
|
|
}{
|
|
{"default", "envValue", "string value", "TEST_STRING", "envValue"},
|
|
{0, 123, "int value", "TEST_INT", "123"},
|
|
{false, true, "bool value", "TEST_BOOL", "true"},
|
|
{"default", "default", "default value", "NON_EXISTENT", ""},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
suite.Run(tt.name, func() {
|
|
if tt.envValue != "" {
|
|
_ = os.Setenv("GMP_"+tt.key, tt.envValue)
|
|
defer func() { _ = os.Unsetenv("GMP_" + tt.key) }()
|
|
}
|
|
result := getDetailsFromEnv(tt.key, tt.defaultValue)
|
|
assert.Equal(suite.T(), tt.expected, result)
|
|
})
|
|
}
|
|
}
|
|
|
|
func (suite *Tests) TestIntrospectionEnvironmentConfig() {
|
|
// Save original env vars
|
|
oldEnv := make(map[string]string)
|
|
varsToSave := []string{
|
|
"BLOCK_SCHEMA_INTROSPECTION",
|
|
"ALLOWED_INTROSPECTION",
|
|
"GMP_BLOCK_SCHEMA_INTROSPECTION",
|
|
"GMP_ALLOWED_INTROSPECTION",
|
|
}
|
|
for _, env := range varsToSave {
|
|
if val, exists := os.LookupEnv(env); exists {
|
|
oldEnv[env] = val
|
|
_ = os.Unsetenv(env)
|
|
}
|
|
}
|
|
defer func() {
|
|
// Restore original env vars
|
|
for k, v := range oldEnv {
|
|
_ = os.Setenv(k, v)
|
|
}
|
|
}()
|
|
|
|
tests := []struct {
|
|
envVars map[string]string
|
|
name string
|
|
query string
|
|
wantEndpoint string
|
|
wantBlocked bool
|
|
}{
|
|
{
|
|
name: "basic typename allowed",
|
|
envVars: map[string]string{
|
|
"BLOCK_SCHEMA_INTROSPECTION": "true",
|
|
"ALLOWED_INTROSPECTION": "__typename",
|
|
},
|
|
query: `{
|
|
users {
|
|
id
|
|
__typename
|
|
}
|
|
}`,
|
|
wantBlocked: false,
|
|
},
|
|
{
|
|
name: "GMP prefix takes precedence",
|
|
envVars: map[string]string{
|
|
"BLOCK_SCHEMA_INTROSPECTION": "false",
|
|
"GMP_BLOCK_SCHEMA_INTROSPECTION": "true",
|
|
"ALLOWED_INTROSPECTION": "__type",
|
|
"GMP_ALLOWED_INTROSPECTION": "__typename",
|
|
},
|
|
query: `{
|
|
users {
|
|
__typename
|
|
}
|
|
}`,
|
|
wantBlocked: false,
|
|
},
|
|
{
|
|
name: "multiple allowed queries",
|
|
envVars: map[string]string{
|
|
"BLOCK_SCHEMA_INTROSPECTION": "true",
|
|
"ALLOWED_INTROSPECTION": "__typename,__schema",
|
|
},
|
|
query: `{
|
|
__schema {
|
|
types {
|
|
name
|
|
__typename
|
|
}
|
|
}
|
|
}`,
|
|
wantBlocked: false,
|
|
},
|
|
{
|
|
name: "multiple allowed queries with one of them blocked",
|
|
envVars: map[string]string{
|
|
"BLOCK_SCHEMA_INTROSPECTION": "true",
|
|
"ALLOWED_INTROSPECTION": "__schema",
|
|
},
|
|
query: `{
|
|
__schema {
|
|
types {
|
|
name
|
|
__typename
|
|
}
|
|
}
|
|
}`,
|
|
wantBlocked: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
suite.Run(tt.name, func() {
|
|
// Set test env vars
|
|
for k, v := range tt.envVars {
|
|
_ = os.Setenv(k, v)
|
|
}
|
|
|
|
// Reset global config with proper synchronization
|
|
cfgMutex.Lock()
|
|
cfg = nil
|
|
cfgMutex.Unlock()
|
|
parseConfig()
|
|
|
|
// Create test request
|
|
app := fiber.New()
|
|
ctx := app.AcquireCtx(&fasthttp.RequestCtx{})
|
|
defer app.ReleaseCtx(ctx)
|
|
ctx.Request().Header.SetMethod("POST")
|
|
ctx.Request().SetBody([]byte(fmt.Sprintf(`{"query": %q}`, tt.query)))
|
|
|
|
result := parseGraphQLQuery(ctx)
|
|
assert.Equal(suite.T(), tt.wantBlocked, result.shouldBlock)
|
|
for k := range tt.envVars {
|
|
_ = os.Unsetenv(k)
|
|
}
|
|
})
|
|
}
|
|
}
|