mirror of
https://github.com/lukaszraczylo/graphql-monitoring-proxy.git
synced 2026-10-12 05:32:36 +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,312 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
libpack_logger "github.com/lukaszraczylo/graphql-monitoring-proxy/logging"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNewRetryBudget(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
config RetryBudgetConfig
|
||||
}{
|
||||
{
|
||||
name: "default config",
|
||||
config: RetryBudgetConfig{
|
||||
TokensPerSecond: 10.0,
|
||||
MaxTokens: 100,
|
||||
Enabled: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "custom config",
|
||||
config: RetryBudgetConfig{
|
||||
TokensPerSecond: 50.0,
|
||||
MaxTokens: 500,
|
||||
Enabled: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "disabled config",
|
||||
config: RetryBudgetConfig{
|
||||
TokensPerSecond: 10.0,
|
||||
MaxTokens: 100,
|
||||
Enabled: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
logger := libpack_logger.New()
|
||||
|
||||
rb := NewRetryBudget(tt.config, logger)
|
||||
|
||||
assert.NotNil(t, rb)
|
||||
assert.Equal(t, tt.config.Enabled, rb.enabled)
|
||||
assert.Equal(t, tt.config.TokensPerSecond, rb.tokensPerSecond)
|
||||
assert.Equal(t, int64(tt.config.MaxTokens), rb.maxTokens)
|
||||
|
||||
if tt.config.Enabled {
|
||||
// Should start with max tokens
|
||||
assert.Equal(t, int64(tt.config.MaxTokens), rb.currentTokens.Load())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRetryBudget_Allow(t *testing.T) {
|
||||
t.Run("allow when enabled and tokens available", func(t *testing.T) {
|
||||
config := RetryBudgetConfig{
|
||||
TokensPerSecond: 10.0,
|
||||
MaxTokens: 100,
|
||||
Enabled: true,
|
||||
}
|
||||
|
||||
rb := NewRetryBudget(config, libpack_logger.New())
|
||||
|
||||
// Should allow first request
|
||||
allowed := rb.AllowRetry()
|
||||
assert.True(t, allowed)
|
||||
|
||||
// Tokens should be decremented
|
||||
assert.Less(t, rb.currentTokens.Load(), int64(100))
|
||||
})
|
||||
|
||||
t.Run("deny when tokens exhausted", func(t *testing.T) {
|
||||
config := RetryBudgetConfig{
|
||||
TokensPerSecond: 10.0,
|
||||
MaxTokens: 2,
|
||||
Enabled: true,
|
||||
}
|
||||
|
||||
rb := NewRetryBudget(config, libpack_logger.New())
|
||||
|
||||
// Consume all tokens
|
||||
assert.True(t, rb.AllowRetry())
|
||||
assert.True(t, rb.AllowRetry())
|
||||
|
||||
// Should deny when exhausted
|
||||
assert.False(t, rb.AllowRetry())
|
||||
|
||||
stats := rb.GetStats()
|
||||
assert.Greater(t, stats["denied_retries"].(int64), int64(0))
|
||||
})
|
||||
|
||||
t.Run("always allow when disabled", func(t *testing.T) {
|
||||
config := RetryBudgetConfig{
|
||||
TokensPerSecond: 10.0,
|
||||
MaxTokens: 0,
|
||||
Enabled: false,
|
||||
}
|
||||
|
||||
rb := NewRetryBudget(config, libpack_logger.New())
|
||||
|
||||
// Should always allow when disabled
|
||||
for i := 0; i < 100; i++ {
|
||||
assert.True(t, rb.AllowRetry())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestRetryBudget_Refill(t *testing.T) {
|
||||
t.Run("tokens refill over time", func(t *testing.T) {
|
||||
config := RetryBudgetConfig{
|
||||
TokensPerSecond: 100.0, // Fast refill for testing
|
||||
MaxTokens: 100,
|
||||
Enabled: true,
|
||||
}
|
||||
|
||||
rb := NewRetryBudget(config, libpack_logger.New())
|
||||
|
||||
// Consume some tokens
|
||||
for i := 0; i < 50; i++ {
|
||||
rb.AllowRetry()
|
||||
}
|
||||
|
||||
tokensBefore := rb.currentTokens.Load()
|
||||
|
||||
// Wait for refill (multiple refill cycles at 100ms each)
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
|
||||
tokensAfter := rb.currentTokens.Load()
|
||||
|
||||
// Tokens should have increased
|
||||
assert.Greater(t, tokensAfter, tokensBefore)
|
||||
})
|
||||
|
||||
t.Run("tokens don't exceed max", func(t *testing.T) {
|
||||
config := RetryBudgetConfig{
|
||||
TokensPerSecond: 100.0,
|
||||
MaxTokens: 50,
|
||||
Enabled: true,
|
||||
}
|
||||
|
||||
rb := NewRetryBudget(config, libpack_logger.New())
|
||||
|
||||
// Wait for potential overflow
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
|
||||
tokens := rb.currentTokens.Load()
|
||||
assert.LessOrEqual(t, tokens, int64(50))
|
||||
})
|
||||
}
|
||||
|
||||
func TestRetryBudget_GetStats(t *testing.T) {
|
||||
t.Run("tracks statistics correctly", func(t *testing.T) {
|
||||
config := RetryBudgetConfig{
|
||||
TokensPerSecond: 10.0,
|
||||
MaxTokens: 5,
|
||||
Enabled: true,
|
||||
}
|
||||
|
||||
rb := NewRetryBudget(config, libpack_logger.New())
|
||||
|
||||
// Allow some requests
|
||||
rb.AllowRetry()
|
||||
rb.AllowRetry()
|
||||
rb.AllowRetry()
|
||||
|
||||
// Consume all tokens to trigger denials
|
||||
rb.AllowRetry()
|
||||
rb.AllowRetry()
|
||||
rb.AllowRetry() // Should be denied
|
||||
rb.AllowRetry() // Should be denied
|
||||
|
||||
stats := rb.GetStats()
|
||||
|
||||
assert.Equal(t, true, stats["enabled"])
|
||||
assert.Equal(t, 10.0, stats["tokens_per_sec"])
|
||||
assert.Equal(t, int64(5), stats["max_tokens"])
|
||||
assert.GreaterOrEqual(t, stats["current_tokens"].(int64), int64(0))
|
||||
assert.Equal(t, int64(7), stats["total_attempts"])
|
||||
assert.GreaterOrEqual(t, stats["denied_retries"].(int64), int64(2))
|
||||
assert.Greater(t, stats["denial_rate_pct"].(float64), 0.0)
|
||||
})
|
||||
|
||||
t.Run("stats when disabled", func(t *testing.T) {
|
||||
config := RetryBudgetConfig{
|
||||
TokensPerSecond: 10.0,
|
||||
MaxTokens: 100,
|
||||
Enabled: false,
|
||||
}
|
||||
|
||||
rb := NewRetryBudget(config, libpack_logger.New())
|
||||
|
||||
stats := rb.GetStats()
|
||||
|
||||
assert.Equal(t, false, stats["enabled"])
|
||||
assert.Equal(t, int64(0), stats["total_attempts"])
|
||||
assert.Equal(t, int64(0), stats["denied_retries"])
|
||||
})
|
||||
}
|
||||
|
||||
func TestRetryBudget_Reset(t *testing.T) {
|
||||
config := RetryBudgetConfig{
|
||||
TokensPerSecond: 10.0,
|
||||
MaxTokens: 10,
|
||||
Enabled: true,
|
||||
}
|
||||
|
||||
rb := NewRetryBudget(config, libpack_logger.New())
|
||||
|
||||
// Generate some activity
|
||||
for i := 0; i < 15; i++ {
|
||||
rb.AllowRetry()
|
||||
}
|
||||
|
||||
statsBefore := rb.GetStats()
|
||||
assert.Greater(t, statsBefore["total_attempts"].(int64), int64(0))
|
||||
|
||||
// Reset
|
||||
rb.Reset()
|
||||
|
||||
statsAfter := rb.GetStats()
|
||||
assert.Equal(t, int64(0), statsAfter["total_attempts"])
|
||||
assert.Equal(t, int64(0), statsAfter["denied_retries"])
|
||||
assert.Equal(t, int64(10), statsAfter["current_tokens"]) // Should reset to max
|
||||
}
|
||||
|
||||
func TestRetryBudget_ConcurrentAccess(t *testing.T) {
|
||||
config := RetryBudgetConfig{
|
||||
TokensPerSecond: 100.0,
|
||||
MaxTokens: 1000,
|
||||
Enabled: true,
|
||||
}
|
||||
|
||||
rb := NewRetryBudget(config, libpack_logger.New())
|
||||
|
||||
// Concurrent access test
|
||||
done := make(chan bool)
|
||||
goroutines := 100
|
||||
requestsPerGoroutine := 10
|
||||
|
||||
for i := 0; i < goroutines; i++ {
|
||||
go func() {
|
||||
for j := 0; j < requestsPerGoroutine; j++ {
|
||||
rb.AllowRetry()
|
||||
}
|
||||
done <- true
|
||||
}()
|
||||
}
|
||||
|
||||
// Wait for all goroutines
|
||||
for i := 0; i < goroutines; i++ {
|
||||
<-done
|
||||
}
|
||||
|
||||
stats := rb.GetStats()
|
||||
totalAttempts := stats["total_attempts"].(int64)
|
||||
|
||||
// Should have processed all requests
|
||||
assert.Equal(t, int64(goroutines*requestsPerGoroutine), totalAttempts)
|
||||
}
|
||||
|
||||
func TestRetryBudget_DenialRate(t *testing.T) {
|
||||
config := RetryBudgetConfig{
|
||||
TokensPerSecond: 1.0,
|
||||
MaxTokens: 10,
|
||||
Enabled: true,
|
||||
}
|
||||
|
||||
rb := NewRetryBudget(config, libpack_logger.New())
|
||||
|
||||
// Consume all tokens
|
||||
for i := 0; i < 10; i++ {
|
||||
rb.AllowRetry()
|
||||
}
|
||||
|
||||
// These should be denied
|
||||
deniedCount := 0
|
||||
for i := 0; i < 10; i++ {
|
||||
if !rb.AllowRetry() {
|
||||
deniedCount++
|
||||
}
|
||||
}
|
||||
|
||||
assert.Greater(t, deniedCount, 0)
|
||||
|
||||
stats := rb.GetStats()
|
||||
denialRate := stats["denial_rate_pct"].(float64)
|
||||
|
||||
assert.Greater(t, denialRate, 0.0)
|
||||
assert.LessOrEqual(t, denialRate, 100.0)
|
||||
}
|
||||
|
||||
func TestRetryBudget_GlobalInstance(t *testing.T) {
|
||||
config := RetryBudgetConfig{
|
||||
TokensPerSecond: 10.0,
|
||||
MaxTokens: 100,
|
||||
Enabled: true,
|
||||
}
|
||||
|
||||
rb := InitializeRetryBudget(config, libpack_logger.New())
|
||||
assert.NotNil(t, rb)
|
||||
|
||||
// Should return the same instance
|
||||
rb2 := GetRetryBudget()
|
||||
assert.Equal(t, rb, rb2)
|
||||
}
|
||||
Reference in New Issue
Block a user