package api import ( "net/http" "net/http/httptest" "strings" "testing" "time" ) func TestRateLimiterEnforcesWindowAndBoundsKeyMemory(t *testing.T) { limiter, err := NewRateLimiter(2, time.Second, 1) if err != nil { t.Fatal(err) } start := time.Unix(1000, 0) if !limiter.Allow("player-1", start) || !limiter.Allow("player-1", start.Add(100*time.Millisecond)) { t.Fatal("allowed requests were rejected") } if limiter.Allow("player-1", start.Add(200*time.Millisecond)) { t.Fatal("request over the window limit was accepted") } if limiter.Allow("player-2", start.Add(300*time.Millisecond)) { t.Fatal("unbounded new key bypassed the memory bound") } if !limiter.Allow("player-1", start.Add(time.Second)) { t.Fatal("window did not reset at the boundary") } } func TestRateLimitedHTTPBoundaryReturnsGeneric429(t *testing.T) { limiter, err := NewRateLimiter(1, time.Minute, 8) if err != nil { t.Fatal(err) } service := &Service{RateLimiter: limiter, Now: func() time.Time { return time.Unix(1000, 0) }} server := httptest.NewServer(service.Handler()) defer server.Close() request, err := http.NewRequest(http.MethodGet, server.URL+"/healthz", nil) if err != nil { t.Fatal(err) } request.Header.Set("Authorization", "Bearer secret-session:secret-token") response, err := server.Client().Do(request) if err != nil { t.Fatal(err) } _ = response.Body.Close() if response.StatusCode != http.StatusOK { t.Fatalf("first request status = %d", response.StatusCode) } request, _ = http.NewRequest(http.MethodGet, server.URL+"/healthz", strings.NewReader("")) request.Header.Set("Authorization", "Bearer secret-session:secret-token") response, err = server.Client().Do(request) if err != nil { t.Fatal(err) } _ = response.Body.Close() if response.StatusCode != http.StatusTooManyRequests { t.Fatalf("limited request status = %d", response.StatusCode) } }