diff --git a/apps/api-server/internal/middleware/redis_ratelimit_test.go b/apps/api-server/internal/middleware/redis_ratelimit_test.go index 5c9744e..a4f39fb 100644 --- a/apps/api-server/internal/middleware/redis_ratelimit_test.go +++ b/apps/api-server/internal/middleware/redis_ratelimit_test.go @@ -64,8 +64,8 @@ func TestRedisRateLimiter_Allow(t *testing.T) { cmd := redis.NewCmd(context.Background()) cmd.SetVal(mockResult) - // Match the actual call signature: script, key, now, window, limit - mockClient.On("Eval", mock.AnythingOfType("string"), mock.AnythingOfType("string"), mock.AnythingOfType("int64"), mock.AnythingOfType("int64"), mock.AnythingOfType("int64")).Return(cmd) + // Match the actual call signature: script, key, now (int64), window (float64), limit (int64) + mockClient.On("Eval", mock.AnythingOfType("string"), mock.AnythingOfType("string"), mock.AnythingOfType("int64"), mock.AnythingOfType("float64"), mock.AnythingOfType("int64")).Return(cmd) // Create rate limiter with mock client limiter := NewRedisRateLimiter(mockClient, "test", tt.limit, tt.window) @@ -116,7 +116,7 @@ func TestRedisRateLimitMiddleware(t *testing.T) { // Mock successful rate limit check cmd := redis.NewCmd(context.Background()) cmd.SetVal([]any{int64(1), int64(4), int64(time.Now().Unix() + 60)}) - mockClient.On("Eval", mock.AnythingOfType("string"), mock.AnythingOfType("string"), mock.AnythingOfType("int64"), mock.AnythingOfType("int64"), mock.AnythingOfType("int64")).Return(cmd) + mockClient.On("Eval", mock.AnythingOfType("string"), mock.AnythingOfType("string"), mock.AnythingOfType("int64"), mock.AnythingOfType("float64"), mock.AnythingOfType("int64")).Return(cmd) // Create rate limiter with mock client limiter := NewRedisRateLimiter(mockClient, "test", 5, time.Minute) @@ -272,8 +272,8 @@ func TestMultiTierRateLimiter(t *testing.T) { premiumCmd := redis.NewCmd(context.Background()) premiumCmd.SetVal([]any{int64(1), int64(99), int64(time.Now().Unix() + 60)}) - mockClient.On("Eval", mock.AnythingOfType("string"), mock.AnythingOfType("string"), mock.AnythingOfType("int64"), mock.AnythingOfType("int64"), mock.AnythingOfType("int64")).Return(freeCmd).Once() - mockClient.On("Eval", mock.AnythingOfType("string"), mock.AnythingOfType("string"), mock.AnythingOfType("int64"), mock.AnythingOfType("int64"), mock.AnythingOfType("int64")).Return(premiumCmd).Once() + mockClient.On("Eval", mock.AnythingOfType("string"), mock.AnythingOfType("string"), mock.AnythingOfType("int64"), mock.AnythingOfType("float64"), mock.AnythingOfType("int64")).Return(freeCmd).Once() + mockClient.On("Eval", mock.AnythingOfType("string"), mock.AnythingOfType("string"), mock.AnythingOfType("int64"), mock.AnythingOfType("float64"), mock.AnythingOfType("int64")).Return(premiumCmd).Once() // Create multi-tier rate limiter with mock client tiers := map[string]struct { diff --git a/apps/api-server/internal/services/chaos.go b/apps/api-server/internal/services/chaos.go index 977248b..71cb431 100644 --- a/apps/api-server/internal/services/chaos.go +++ b/apps/api-server/internal/services/chaos.go @@ -30,6 +30,26 @@ type Experiment struct { Error string } +// copy creates a deep copy of the experiment to avoid race conditions +func (e *Experiment) copy() *Experiment { + var finished *time.Time + if e.Finished != nil { + fin := *e.Finished + finished = &fin + } + return &Experiment{ + ID: e.ID, + Name: e.Name, + Target: e.Target, + Type: e.Type, + Config: e.Config, + Status: e.Status, + Started: e.Started, + Finished: finished, + Error: e.Error, + } +} + type ExperimentType string const ( @@ -90,7 +110,8 @@ func (cs *ChaosService) RunExperiment(ctx context.Context, expType ExperimentTyp // Run experiment in background go cs.executeExperiment(ctx, exp) - return exp, nil + // Return a copy to avoid race condition with JSON marshaling + return exp.copy(), nil } func (cs *ChaosService) executeExperiment(ctx context.Context, exp *Experiment) {