CI / Docker compose check (push) Successful in 13s
CI / Env docs check (push) Successful in 14s
CI / Nginx config check (push) Successful in 15s
CI / Frontend major deps (push) Successful in 25s
CI / Frontend deps check (push) Successful in 26s
CI / Secrets scan (push) Successful in 35s
CI / Go build (push) Successful in 42s
CI / Frontend build (push) Successful in 45s
CI / Knip (push) Successful in 23s
CI / Frontend a11y check (push) Successful in 1m13s
CI / go mod tidy (push) Successful in 37s
CI / Go vet (prod) (push) Successful in 2m5s
CI / Go vet (dev) (push) Successful in 2m16s
CI / Frontend QC (audit) (push) Successful in 45s
CI / Go vulnerabilities (push) Successful in 1m29s
CI / Staticcheck (prod) (push) Successful in 3m3s
CI / Staticcheck (dev) (push) Successful in 3m46s
CI / Frontend QC (typecheck) (push) Successful in 2m1s
CI / golangci-lint (push) Successful in 4m19s
CI / Security scan (prod) (push) Successful in 4m23s
CI / Security scan (dev) (push) Successful in 4m35s
CI / Frontend QC (lint) (push) Successful in 1m51s
CI / Svelte strict check (push) Successful in 1m21s
CI / Tests (prod) (push) Successful in 3m39s
CI / Tests (dev) (push) Failing after 3m50s
CI / Race (prod) (push) Successful in 7m4s
CI / Race (dev) (push) Failing after 7m12s
111 lines
2.8 KiB
Go
111 lines
2.8 KiB
Go
package mw
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"crussell/clock"
|
|
)
|
|
|
|
// TestProgressiveRateLimiter_SingleRequest verifies no delay for first request.
|
|
func TestProgressiveRateLimiter_SingleRequest(t *testing.T) {
|
|
prl := NewProgressiveRateLimiter()
|
|
delay := prl.Check("192.168.1.1")
|
|
if delay != 0 {
|
|
t.Errorf("expected 0 delay for first request, got %d", delay)
|
|
}
|
|
}
|
|
|
|
// TestProgressiveRateLimiter_BurstAllowsPageLoad verifies 15 requests in 5s are OK.
|
|
func TestProgressiveRateLimiter_BurstAllowsPageLoad(t *testing.T) {
|
|
prl := NewProgressiveRateLimiter()
|
|
ip := "192.168.1.1"
|
|
|
|
for i := 0; i < 15; i++ {
|
|
delay := prl.Check(ip)
|
|
if delay != 0 {
|
|
t.Errorf("expected 0 delay for request %d (within burst), got %d", i+1, delay)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestProgressiveRateLimiter_ExcessBurstDelays verifies 35+ requests in 5s get delayed.
|
|
func TestProgressiveRateLimiter_ExcessBurstDelays(t *testing.T) {
|
|
prl := NewProgressiveRateLimiter()
|
|
ip := "192.168.1.1"
|
|
|
|
delayed := false
|
|
for i := 0; i < 35; i++ {
|
|
delay := prl.Check(ip)
|
|
if delay > 0 {
|
|
delayed = true
|
|
}
|
|
}
|
|
|
|
if !delayed {
|
|
t.Error("expected at least one delay after 35 burst requests")
|
|
}
|
|
}
|
|
|
|
// TestProgressiveRateLimiter_SustainedAllowsNormal verifies 30 requests
|
|
// spread over 60 seconds are not delayed.
|
|
func TestProgressiveRateLimiter_SustainedAllowsNormal(t *testing.T) {
|
|
prl := NewProgressiveRateLimiter()
|
|
ip := "192.168.1.2"
|
|
|
|
for i := 0; i < 30; i++ {
|
|
prl.mu.Lock()
|
|
state, exists := prl.requests[ip]
|
|
if !exists {
|
|
prl.requests[ip] = &ipProgressiveState{
|
|
timestamps: []time.Time{clock.Now().Add(-time.Duration(60-i*2) * time.Second)},
|
|
}
|
|
prl.mu.Unlock()
|
|
continue
|
|
}
|
|
state.timestamps = append(state.timestamps, clock.Now().Add(-time.Duration(60-i*2)*time.Second))
|
|
prl.mu.Unlock()
|
|
}
|
|
|
|
delay := prl.Check(ip)
|
|
if delay != 0 {
|
|
t.Errorf("expected 0 delay for 30 sustained requests over 60s, got %d", delay)
|
|
}
|
|
}
|
|
|
|
// TestProgressiveRateLimiter_ExcessSustainedDelays verifies 130+ requests
|
|
// in 60s triggers delay.
|
|
func TestProgressiveRateLimiter_ExcessSustainedDelays(t *testing.T) {
|
|
prl := NewProgressiveRateLimiter()
|
|
ip := "192.168.1.3"
|
|
|
|
now := clock.Now()
|
|
prl.mu.Lock()
|
|
state := &ipProgressiveState{timestamps: make([]time.Time, 130)}
|
|
for i := 0; i < 130; i++ {
|
|
state.timestamps[i] = now.Add(-time.Duration(60-i/3) * time.Second)
|
|
}
|
|
prl.requests[ip] = state
|
|
prl.mu.Unlock()
|
|
|
|
delay := prl.Check(ip)
|
|
if delay == 0 {
|
|
t.Error("expected delay > 0 for 130 sustained requests")
|
|
}
|
|
}
|
|
|
|
// TestProgressiveRateLimiter_DifferentIPs verifies rate limiter
|
|
// tracks IPs independently.
|
|
func TestProgressiveRateLimiter_DifferentIPs(t *testing.T) {
|
|
prl := NewProgressiveRateLimiter()
|
|
|
|
for i := 0; i < 40; i++ {
|
|
prl.Check("10.0.0.1")
|
|
}
|
|
|
|
delay := prl.Check("10.0.0.2")
|
|
if delay != 0 {
|
|
t.Errorf("expected 0 delay for separate IP, got %d", delay)
|
|
}
|
|
}
|