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) } }