axe/internal/ratelimit/ratelimit_test.go

103 lines
2.5 KiB
Go
Raw Normal View History

2026-10-18 19:07:25 +00:00
package ratelimit
import (
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/gin-gonic/gin"
)
// performRequest — минимальный прогон запроса через gin-роутер.
func performRequest(r *gin.Engine, method, path string) *httptest.ResponseRecorder {
w := httptest.NewRecorder()
req := httptest.NewRequest(method, path, nil)
r.ServeHTTP(w, req)
return w
}
func TestLimiterAllow(t *testing.T) {
l := New(3, time.Minute)
for i := 1; i <= 3; i++ {
if !l.Allow("ip-1") {
t.Fatalf("attempt %d: want allowed", i)
}
}
if l.Allow("ip-1") {
t.Error("4th attempt: want denied")
}
// Ключи независимы: чужой бюджет не задет.
if !l.Allow("ip-2") {
t.Error("other key: want allowed")
}
}
func TestLimiterWindowReset(t *testing.T) {
l := New(1, 30*time.Millisecond)
if !l.Allow("k") {
t.Fatal("first attempt: want allowed")
}
if l.Allow("k") {
t.Error("second attempt: want denied")
}
time.Sleep(40 * time.Millisecond)
if !l.Allow("k") {
t.Error("after window: want allowed")
}
}
// TestLimiterCleanup — после чистки карта не тащит протухшие ключи,
// иначе под потоком разных адресов она росла бы без предела.
func TestLimiterCleanup(t *testing.T) {
l := New(5, 20*time.Millisecond)
l.Allow("old")
time.Sleep(30 * time.Millisecond) // ключ протух, но чистки ещё не было
l.Allow("fresh") // триггерит cleanupLocked
if _, exists := l.buckets["old"]; exists {
t.Error("stale key survived cleanup")
}
if _, exists := l.buckets["fresh"]; !exists {
t.Error("fresh key missing")
}
}
func TestMiddlewareAborts(t *testing.T) {
gin.SetMode(gin.TestMode)
l := New(1, time.Minute)
h := Middleware(l, ByIP, "too many attempts")
var passed int
r := gin.New()
r.Use(func(c *gin.Context) { // абстрактный «хендлер», который просто считает проходы
h(c)
if c.IsAborted() {
return
}
passed++
c.JSON(200, gin.H{"ok": true})
})
w := performRequest(r, "POST", "/x")
if w.Code != http.StatusOK {
t.Fatalf("first request: status = %d, want 200", w.Code)
}
w = performRequest(r, "POST", "/x")
if w.Code != http.StatusTooManyRequests {
t.Fatalf("second request: status = %d, want 429", w.Code)
}
if body := w.Body.String(); body != `{"error":"too many attempts"}` {
t.Errorf("body = %s", body)
}
if passed != 1 {
t.Errorf("handler ran %d times, want 1", passed)
}
}