all repos

x @ main

go extra()

x/ratelimit/ratelimit_test.go (view raw)

Oleksandr Smirnov Oleksandr Smirnov
olexsmir@gmail.com
add ratelimit modules, 1 month ago
1
package ratelimit
2
3
import (
4
	"net/http"
5
	"net/http/httptest"
6
	"testing"
7
	"time"
8
)
9
10
func TestMiddleware(t *testing.T) {
11
	tests := map[string]struct {
12
		config       Config
13
		requests     int
14
		expectedCode int
15
	}{
16
		"allows requests within limit": {
17
			config: Config{
18
				RPS:   2,
19
				Burst: 2,
20
				TTL:   time.Minute,
21
			},
22
			requests:     1,
23
			expectedCode: http.StatusOK,
24
		},
25
		"blocks requests over limit": {
26
			config: Config{
27
				RPS:   1,
28
				Burst: 1,
29
				TTL:   time.Minute,
30
			},
31
			requests:     2,
32
			expectedCode: http.StatusTooManyRequests,
33
		},
34
		"allows burst requests": {
35
			config: Config{
36
				RPS:   1,
37
				Burst: 3,
38
				TTL:   time.Minute,
39
			},
40
			requests:     3,
41
			expectedCode: http.StatusOK,
42
		},
43
	}
44
	for name, tt := range tests {
45
		t.Run(name, func(t *testing.T) {
46
			handler := Middleware(tt.config)
47
			nextHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
48
				w.WriteHeader(http.StatusOK)
49
			})
50
			var lastCode int
51
52
			for range tt.requests {
53
				w := httptest.NewRecorder()
54
				r := httptest.NewRequest(http.MethodGet, "/", nil)
55
56
				handler(nextHandler).ServeHTTP(w, r)
57
				lastCode = w.Code
58
			}
59
60
			if lastCode != tt.expectedCode {
61
				t.Errorf("expected status %d, got %d", tt.expectedCode, lastCode)
62
			}
63
		})
64
	}
65
}
66
67
func TestRateLimiter_getVisitor(t *testing.T) {
68
	limiter := newLimiter(10, 20, time.Second)
69
	ip := visitorIP("127.0.0.1")
70
71
	v := limiter.getVisitor(ip)
72
	if v == nil {
73
		t.Fatal("expected non-nil limiter")
74
	}
75
76
	vAgain := limiter.getVisitor(ip)
77
	if v != vAgain {
78
		t.Fatal("expected the same limiter for the same IP")
79
	}
80
81
	if want := 1; len(limiter.visitors) != want {
82
		t.Fatalf("expected %d visitor, got %d", want, len(limiter.visitors))
83
	}
84
}
85
86
func TestRateLimiter_cleanupVisitors(t *testing.T) {
87
	limiter := newLimiter(10, 20, time.Millisecond)
88
	limiter.getVisitor("192.168.9.1")
89
	if want := 1; len(limiter.visitors) != want {
90
		t.Fatalf("expected %d visitor, got %d", want, len(limiter.visitors))
91
	}
92
93
	time.Sleep(5 * time.Millisecond)
94
	limiter.cleanupVisitors()
95
	if want := 0; len(limiter.visitors) != want {
96
		t.Fatalf("expected %d visitors after cleanup, got %d", want, len(limiter.visitors))
97
	}
98
}
99
100
func TestRateLimiter_differentIPs(t *testing.T) {
101
	limiter := newLimiter(10, 20, time.Second)
102
103
	ip1 := limiter.getVisitor("1.1.1.1")
104
	ip2 := limiter.getVisitor("2.2.2.2")
105
	if ip1 == ip2 {
106
		t.Fatal("expected different limiters for different IPs")
107
	}
108
109
	if want := 2; len(limiter.visitors) != want {
110
		t.Fatalf("expected %d visitors, got %d", want, len(limiter.visitors))
111
	}
112
}
113
114
func TestGetIP(t *testing.T) {
115
	t.Run("uses X-Forwarded-For", func(t *testing.T) {
116
		r := httptest.NewRequest(http.MethodGet, "/", nil)
117
		r.Header.Set("X-Forwarded-For", "203.0.113.1")
118
		if got := getIP(r); got != "203.0.113.1" {
119
			t.Errorf("expected %q, got %q", "203.0.113.1", got)
120
		}
121
	})
122
123
	t.Run("uses first IP from X-Forwarded-For list", func(t *testing.T) {
124
		r := httptest.NewRequest(http.MethodGet, "/", nil)
125
		r.Header.Set("X-Forwarded-For", "203.0.113.1, 198.51.100.2, 192.0.2.3")
126
		if got := getIP(r); got != "203.0.113.1" {
127
			t.Errorf("expected %q, got %q", "203.0.113.1", got)
128
		}
129
	})
130
131
	t.Run("uses X-Real-IP when no X-Forwarded-For", func(t *testing.T) {
132
		r := httptest.NewRequest(http.MethodGet, "/", nil)
133
		r.Header.Set("X-Real-IP", "10.0.0.1")
134
		if got := getIP(r); got != "10.0.0.1" {
135
			t.Errorf("expected %q, got %q", "10.0.0.1", got)
136
		}
137
	})
138
139
	t.Run("falls back to RemoteAddr", func(t *testing.T) {
140
		r := httptest.NewRequest(http.MethodGet, "/", nil)
141
		r.RemoteAddr = "192.168.1.1:12345"
142
		if got := getIP(r); got != "192.168.1.1" {
143
			t.Errorf("expected %q, got %q", "192.168.1.1", got)
144
		}
145
	})
146
}