Files
graphql-monitoring-proxy/errors_test.go
T
lukaszraczylo cedee416a8 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
2025-09-30 18:27:33 +01:00

244 lines
6.5 KiB
Go

package main
import (
"errors"
"net/http"
"testing"
"github.com/stretchr/testify/assert"
)
func TestNewProxyError(t *testing.T) {
tests := []struct {
name string
code string
message string
statusCode int
retryable bool
expectStatus int
}{
{
name: "connection refused error",
code: ErrCodeConnectionRefused,
message: "backend unavailable",
statusCode: http.StatusServiceUnavailable,
retryable: true,
expectStatus: http.StatusServiceUnavailable,
},
{
name: "timeout error",
code: ErrCodeTimeout,
message: "request timeout",
statusCode: http.StatusGatewayTimeout,
retryable: true,
expectStatus: http.StatusGatewayTimeout,
},
{
name: "circuit breaker open",
code: ErrCodeCircuitOpen,
message: "circuit breaker open",
statusCode: http.StatusServiceUnavailable,
retryable: false,
expectStatus: http.StatusServiceUnavailable,
},
{
name: "rate limit exceeded",
code: ErrCodeRateLimited,
message: "too many requests",
statusCode: http.StatusTooManyRequests,
retryable: false,
expectStatus: http.StatusTooManyRequests,
},
{
name: "service unavailable",
code: ErrCodeServiceUnavailable,
message: "no retry tokens available",
statusCode: http.StatusServiceUnavailable,
retryable: false,
expectStatus: http.StatusServiceUnavailable,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := NewProxyError(tt.code, tt.message, tt.statusCode, tt.retryable)
assert.NotNil(t, err)
assert.Equal(t, tt.code, err.Code)
assert.Equal(t, tt.message, err.Message)
assert.Equal(t, tt.retryable, err.Retryable)
assert.Equal(t, tt.expectStatus, err.StatusCode)
assert.NotEmpty(t, err.Timestamp)
assert.NotNil(t, err.Metadata)
})
}
}
func TestProxyError_Error(t *testing.T) {
tests := []struct {
name string
err *ProxyError
expected string
}{
{
name: "error with details",
err: NewProxyError(ErrCodeConnectionRefused, "backend unavailable", http.StatusServiceUnavailable, true).
WithDetails("connection refused"),
expected: "CONNECTION_REFUSED: backend unavailable (connection refused)",
},
{
name: "error without details",
err: NewProxyError(ErrCodeCircuitOpen, "circuit breaker open", http.StatusServiceUnavailable, false),
expected: "CIRCUIT_OPEN: circuit breaker open",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.expected, tt.err.Error())
})
}
}
func TestProxyError_Unwrap(t *testing.T) {
cause := errors.New("original error")
err := NewProxyError(ErrCodeTimeout, "timeout occurred", http.StatusGatewayTimeout, true).WithCause(cause)
unwrapped := errors.Unwrap(err)
assert.Equal(t, cause, unwrapped)
}
func TestProxyError_WithMethods(t *testing.T) {
t.Run("with details", func(t *testing.T) {
err := NewProxyError(ErrCodeTimeout, "timeout", http.StatusGatewayTimeout, true).
WithDetails("operation timed out")
assert.Equal(t, "operation timed out", err.Details)
})
t.Run("with cause", func(t *testing.T) {
cause := errors.New("original error")
err := NewProxyError(ErrCodeTimeout, "timeout", http.StatusGatewayTimeout, true).
WithCause(cause)
assert.Equal(t, cause, err.Cause)
})
t.Run("with trace ID", func(t *testing.T) {
err := NewProxyError(ErrCodeTimeout, "timeout", http.StatusGatewayTimeout, true).
WithTraceID("trace-123")
assert.Equal(t, "trace-123", err.TraceID)
})
t.Run("with metadata", func(t *testing.T) {
err := NewProxyError(ErrCodeTimeout, "timeout", http.StatusGatewayTimeout, true).
WithMetadata("attempt", 3).
WithMetadata("endpoint", "/graphql")
assert.Equal(t, 3, err.Metadata["attempt"])
assert.Equal(t, "/graphql", err.Metadata["endpoint"])
})
}
func TestProxyError_MarshalJSON(t *testing.T) {
cause := errors.New("connection refused")
err := NewProxyError(ErrCodeConnectionRefused, "backend unavailable", http.StatusServiceUnavailable, true).
WithDetails("network error").
WithCause(cause).
WithTraceID("trace-456")
data, jsonErr := err.MarshalJSON()
assert.NoError(t, jsonErr)
assert.NotEmpty(t, data)
assert.Contains(t, string(data), "CONNECTION_REFUSED")
assert.Contains(t, string(data), "backend unavailable")
assert.Contains(t, string(data), "connection refused")
}
func TestErrorCodes(t *testing.T) {
// Verify all error codes are defined
codes := []string{
ErrCodeConnectionRefused,
ErrCodeConnectionReset,
ErrCodeTimeout,
ErrCodeCircuitOpen,
ErrCodeRateLimited,
ErrCodeInvalidRequest,
ErrCodeBackendError,
ErrCodeInternalError,
ErrCodeUnauthorized,
ErrCodeForbidden,
ErrCodeNotFound,
ErrCodeServiceUnavailable,
ErrCodeBadGateway,
ErrCodeInvalidResponse,
ErrCodeQueryTooComplex,
ErrCodeCacheFailed,
ErrCodeContextCanceled,
}
for _, code := range codes {
assert.NotEmpty(t, code, "Error code should not be empty")
}
// Verify codes are unique
codeMap := make(map[string]bool)
for _, code := range codes {
assert.False(t, codeMap[code], "Error code %s should be unique", code)
codeMap[code] = true
}
}
func TestProxyError_ChainableMethods(t *testing.T) {
// Test that methods can be chained
err := NewProxyError(ErrCodeTimeout, "timeout", http.StatusGatewayTimeout, true).
WithDetails("operation timeout").
WithCause(errors.New("deadline exceeded")).
WithTraceID("trace-789").
WithMetadata("attempt", 1).
WithMetadata("duration_ms", 5000)
assert.Equal(t, "operation timeout", err.Details)
assert.NotNil(t, err.Cause)
assert.Equal(t, "trace-789", err.TraceID)
assert.Equal(t, 1, err.Metadata["attempt"])
assert.Equal(t, 5000, err.Metadata["duration_ms"])
}
func TestProxyError_Retryable(t *testing.T) {
tests := []struct {
name string
code string
retryable bool
}{
{
name: "timeout is retryable",
code: ErrCodeTimeout,
retryable: true,
},
{
name: "connection refused is retryable",
code: ErrCodeConnectionRefused,
retryable: true,
},
{
name: "rate limited is not retryable",
code: ErrCodeRateLimited,
retryable: false,
},
{
name: "circuit open is not retryable",
code: ErrCodeCircuitOpen,
retryable: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := NewProxyError(tt.code, "test error", http.StatusInternalServerError, tt.retryable)
assert.Equal(t, tt.retryable, err.Retryable)
})
}
}