perf(S01/T01): Add token-bucket rate limiter (1 req/sec), SQLite respon…
- backend/explore/ratelimiter.go - backend/explore/cache.go - backend/database/sql/schemas/explore_cache.sql - backend/database/database.go
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
package explore
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestRateLimiterBurst(t *testing.T) {
|
||||
rl := NewRateLimiter()
|
||||
ctx := context.Background()
|
||||
|
||||
const n = 5
|
||||
|
||||
start := time.Now()
|
||||
|
||||
for i := range n {
|
||||
if err := rl.Wait(ctx); err != nil {
|
||||
t.Fatalf("Wait %d: %v", i, err)
|
||||
}
|
||||
}
|
||||
|
||||
elapsed := time.Since(start)
|
||||
|
||||
// First request is immediate; 4 more at 1/sec = ≥4s total.
|
||||
if elapsed < 4*time.Second {
|
||||
t.Errorf(
|
||||
"elapsed %v, want ≥ 4s (rate limiter too fast)", elapsed,
|
||||
)
|
||||
}
|
||||
|
||||
// Generous upper bound to avoid CI flakes.
|
||||
if elapsed > 7*time.Second {
|
||||
t.Errorf(
|
||||
"elapsed %v, want ≤ 7s (rate limiter too slow)", elapsed,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRateLimiterContextCancel(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
rl := NewRateLimiter()
|
||||
|
||||
// Drain the initial token so the next Wait must block.
|
||||
if err := rl.Wait(context.Background()); err != nil {
|
||||
t.Fatalf("drain token: %v", err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
cancel() // cancel immediately
|
||||
|
||||
err := rl.Wait(ctx)
|
||||
if err == nil {
|
||||
t.Fatal("expected error from cancelled context, got nil")
|
||||
}
|
||||
|
||||
if !errors.Is(err, context.Canceled) {
|
||||
t.Errorf("error = %v, want context.Canceled", err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user