1
- using NUnit . Framework ;
1
+ using Microsoft . AspNetCore . Http ;
2
+ using Moq ;
3
+ using RateLimiter . Rules ;
4
+ using System . Text ;
5
+ using System . Threading . Tasks ;
6
+ using Xunit ;
7
+ using Microsoft . Extensions . Caching . Distributed ;
8
+ using System . Net ;
9
+ using RateLimiter . Configuration ;
10
+ using System . Collections . Generic ;
11
+ using RateLimiter . Policy ;
12
+ using System . Linq ;
2
13
3
- namespace RateLimiter . Tests ;
4
-
5
- [ TestFixture ]
6
- public class RateLimiterTest
14
+ namespace RateLimiter . Tests
7
15
{
8
- [ Test ]
9
- public void Example ( )
10
- {
11
- Assert . That ( true , Is . True ) ;
12
- }
13
- }
16
+ public class RateLimiterTest
17
+ {
18
+ private readonly Mock < IDistributedCache > _cacheMock ;
19
+
20
+ public RateLimiterTest ( )
21
+ {
22
+ _cacheMock = new Mock < IDistributedCache > ( ) ;
23
+ }
24
+
25
+ #region Combined
26
+
27
+ [ Fact ]
28
+ public async Task EvaluateAll_ShouldAllow_WhenAllUnderLimit ( )
29
+ {
30
+ // Arrange
31
+ List < string > configIpBlocked = [ "192.168.1.1" , "192.168.1.2" ] ;
32
+ FixedWindowConfig configFixed = new ( 5 , 10 ) ;
33
+ string current = "2" ;
34
+ _cacheMock . Setup ( c => c . GetAsync ( It . IsAny < string > ( ) , default ) )
35
+ . ReturnsAsync ( Encoding . UTF8 . GetBytes ( current ) ) ;
36
+
37
+ var ruleFixed = new FixedWindowRule ( _cacheMock . Object , configFixed . Limit , configFixed . Seconds ) ;
38
+ var ruleIp = new IpBlacklistRule ( configIpBlocked ) ;
39
+ var policy = new RateLimitPolicy ( ) ;
40
+ policy . AddRule ( ruleFixed ) ;
41
+ policy . AddRule ( ruleIp ) ;
42
+
43
+ var httpContext = GetHttpContext ( ) ;
44
+
45
+ // Act
46
+ bool result = await policy . EvaluateAllAsync ( httpContext ) ;
47
+
48
+ // Assert
49
+ Assert . True ( result ) ;
50
+ }
51
+
52
+ [ Fact ]
53
+ public async Task EvaluateAll_ShouldNotAllow_BlockedIp ( )
54
+ {
55
+ // Arrange
56
+ List < string > configIpBlocked = [ "192.168.1.101" ] ;
57
+ FixedWindowConfig configFixed = new ( 5 , 10 ) ;
58
+ string current = "2" ;
59
+ _cacheMock . Setup ( c => c . GetAsync ( It . IsAny < string > ( ) , default ) )
60
+ . ReturnsAsync ( Encoding . UTF8 . GetBytes ( current ) ) ;
61
+
62
+ var ruleFixed = new FixedWindowRule ( _cacheMock . Object , configFixed . Limit , configFixed . Seconds ) ;
63
+ var ruleIp = new IpBlacklistRule ( configIpBlocked ) ;
64
+ var policy = new RateLimitPolicy ( ) ;
65
+ policy . AddRule ( ruleFixed ) ;
66
+ policy . AddRule ( ruleIp ) ;
67
+
68
+ var httpContext = GetHttpContext ( configIpBlocked . First ( ) ) ;
69
+
70
+ // Act
71
+ bool result = await policy . EvaluateAllAsync ( httpContext ) ;
72
+
73
+ // Assert
74
+ Assert . False ( result ) ;
75
+ }
76
+
77
+ #endregion
78
+
79
+ #region Fixed
80
+
81
+ [ Fact ]
82
+ public async Task EvaluateFixed_ShouldAllow_WhenUnderLimit ( )
83
+ {
84
+ // Arrange
85
+ FixedWindowConfig config = new ( 5 , 10 ) ;
86
+ string current = "2" ;
87
+ _cacheMock . Setup ( c => c . GetAsync ( It . IsAny < string > ( ) , default ) )
88
+ . ReturnsAsync ( Encoding . UTF8 . GetBytes ( current ) ) ;
89
+
90
+ var rule = new FixedWindowRule ( _cacheMock . Object , config . Limit , config . Seconds ) ;
91
+ var httpContext = GetHttpContext ( ) ;
92
+
93
+ // Act
94
+ bool result = await rule . EvaluateAsync ( httpContext ) ;
95
+
96
+ // Assert
97
+ Assert . True ( result ) ;
98
+ }
99
+
100
+ [ Fact ]
101
+ public async Task EvaluateFixed_ShouldNotAllow_WhenOverLimit ( )
102
+ {
103
+ // Arrange
104
+ FixedWindowConfig config = new ( 2 , 10 ) ;
105
+ string current = "2" ;
106
+ _cacheMock . Setup ( c => c . GetAsync ( It . IsAny < string > ( ) , default ) )
107
+ . ReturnsAsync ( Encoding . UTF8 . GetBytes ( current ) ) ;
108
+
109
+ var rule = new FixedWindowRule ( _cacheMock . Object , config . Limit , config . Seconds ) ;
110
+ var httpContext = GetHttpContext ( ) ;
111
+
112
+ // Act
113
+ bool result = await rule . EvaluateAsync ( httpContext ) ;
114
+
115
+ // Assert
116
+ Assert . False ( result ) ;
117
+ }
118
+
119
+ [ Fact ]
120
+ public async Task EvaluateFixed_ShouldAllow_WhenCacheEmpty ( )
121
+ {
122
+ // Arrange
123
+ FixedWindowConfig config = new ( 5 , 10 ) ;
124
+ _cacheMock . Setup ( c => c . GetAsync ( It . IsAny < string > ( ) , default ) )
125
+ . ReturnsAsync ( null as byte [ ] ) ;
126
+
127
+ var rule = new FixedWindowRule ( _cacheMock . Object , config . Limit , config . Seconds ) ;
128
+ var httpContext = GetHttpContext ( ) ;
129
+
130
+ // Act
131
+ bool result = await rule . EvaluateAsync ( httpContext ) ;
132
+
133
+ // Assert
134
+ Assert . True ( result ) ; // First request should pass
135
+ }
136
+
137
+ [ Fact ]
138
+ public async Task EvaluateFixed_ShouldUpdateCache_WhenRequestMade ( )
139
+ {
140
+ // Arrange
141
+ FixedWindowConfig config = new ( 5 , 10 ) ;
142
+ _cacheMock . Setup ( c => c . GetAsync ( It . IsAny < string > ( ) , default ) )
143
+ . ReturnsAsync ( Encoding . UTF8 . GetBytes ( "2" ) ) ; // Simulate 2 requests so far
144
+
145
+ var rule = new FixedWindowRule ( _cacheMock . Object , config . Limit , config . Seconds ) ;
146
+ var httpContext = GetHttpContext ( ) ;
147
+
148
+ // Act
149
+ bool result = await rule . EvaluateAsync ( httpContext ) ;
150
+
151
+ // Assert
152
+ Assert . True ( result ) ; // Request should pass
153
+ _cacheMock . Verify ( c => c . SetAsync ( It . IsAny < string > ( ) , It . IsAny < byte [ ] > ( ) , It . IsAny < DistributedCacheEntryOptions > ( ) , default ) , Times . Once ) ;
154
+ }
155
+
156
+ #endregion
157
+
158
+ #region Geo
159
+
160
+ [ Fact ]
161
+ public async Task EvaluateGeo_ShouldAllow_WhenUnderLimit ( )
162
+ {
163
+ // Arrange
164
+ GeoBasedConfig config = new ( Country . EU , 10 ) ;
165
+ _cacheMock . Setup ( c => c . GetAsync ( It . IsAny < string > ( ) , default ) )
166
+ . ReturnsAsync ( null as byte [ ] ) ;
167
+
168
+ var rule = new GeoBasedRule ( _cacheMock . Object , [ config ] ) ;
169
+ var httpContext = GetHttpContext ( ) ;
170
+
171
+ // Act
172
+ bool result = await rule . EvaluateAsync ( httpContext ) ;
173
+
174
+ // Assert
175
+ Assert . True ( result ) ;
176
+ }
177
+
178
+ [ Fact ]
179
+ public async Task EvaluateGeo_ShouldNotAllow_WhenOverLimit ( )
180
+ {
181
+ // Arrange
182
+ GeoBasedConfig config = new ( Country . EU , 10 ) ;
183
+ string current = "0" ;
184
+ _cacheMock . Setup ( c => c . GetAsync ( It . IsAny < string > ( ) , default ) )
185
+ . ReturnsAsync ( Encoding . UTF8 . GetBytes ( current ) ) ;
186
+
187
+ var rule = new GeoBasedRule ( _cacheMock . Object , [ config ] ) ;
188
+ var httpContext = GetHttpContext ( ) ;
189
+
190
+ // Act
191
+ bool result = await rule . EvaluateAsync ( httpContext ) ;
192
+
193
+ // Assert
194
+ Assert . False ( result ) ;
195
+ }
196
+
197
+ #endregion
198
+
199
+ private static DefaultHttpContext GetHttpContext ( string ip = "192.168.1.100" )
200
+ {
201
+ var httpContext = new DefaultHttpContext ( ) ;
202
+ httpContext . Connection . RemoteIpAddress = IPAddress . Parse ( ip ) ;
203
+ return httpContext ;
204
+ }
205
+ }
206
+ }
0 commit comments