Skip to content

Commit 284a22f

Browse files
committed
Add comprehensive tests for all rate limiting rules
1 parent f9b1e85 commit 284a22f

File tree

1 file changed

+252
-6
lines changed

1 file changed

+252
-6
lines changed

RateLimiter.Tests/RateLimiterTest.cs

+252-6
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,259 @@
11
using NUnit.Framework;
2+
using System;
3+
using System.Threading;
24

35
namespace RateLimiter.Tests;
46

57
[TestFixture]
68
public class RateLimiterTest
79
{
8-
[Test]
9-
public void Example()
10-
{
11-
Assert.That(true, Is.True);
12-
}
13-
}
10+
[Test]
11+
public void Example()
12+
{
13+
Assert.That(true, Is.True);
14+
}
15+
16+
[Test]
17+
public void TimeIntervalBetweenRequestsRule_ShouldAllowFirstRequest()
18+
{
19+
// Arrange
20+
var requestTracker = new RequestTracker();
21+
var rule = new TimeIntervalBetweenRequestsRule(requestTracker, TimeSpan.FromSeconds(1));
22+
23+
// Act
24+
bool isAllowed = rule.IsRequestAllowed("token1", "resource1");
25+
26+
// Assert
27+
Assert.That(isAllowed, Is.True);
28+
}
29+
30+
[Test]
31+
public void TimeIntervalBetweenRequestsRule_ShouldBlockRequestsWithinInterval()
32+
{
33+
// Arrange
34+
var requestTracker = new RequestTracker();
35+
var rule = new TimeIntervalBetweenRequestsRule(requestTracker, TimeSpan.FromSeconds(1));
36+
37+
// Act
38+
requestTracker.RecordRequest("token1", "resource1");
39+
bool isAllowed = rule.IsRequestAllowed("token1", "resource1");
40+
41+
// Assert
42+
Assert.That(isAllowed, Is.False);
43+
}
44+
45+
[Test]
46+
public void TimeIntervalBetweenRequestsRule_ShouldAllowRequestsAfterInterval()
47+
{
48+
// Arrange
49+
var requestTracker = new RequestTracker();
50+
var rule = new TimeIntervalBetweenRequestsRule(requestTracker, TimeSpan.FromMilliseconds(50));
51+
52+
// Act
53+
requestTracker.RecordRequest("token1", "resource1");
54+
Thread.Sleep(100); // Wait longer than the interval
55+
bool isAllowed = rule.IsRequestAllowed("token1", "resource1");
56+
57+
// Assert
58+
Assert.That(isAllowed, Is.True);
59+
}
60+
61+
[Test]
62+
public void RequestsPerTimespanRule_ShouldAllowRequestsWithinLimit()
63+
{
64+
// Arrange
65+
var requestTracker = new RequestTracker();
66+
var rule = new RequestsPerTimespanRule(requestTracker, 3, TimeSpan.FromSeconds(10));
67+
68+
// Act
69+
requestTracker.RecordRequest("token1", "resource1");
70+
requestTracker.RecordRequest("token1", "resource1");
71+
bool isAllowed = rule.IsRequestAllowed("token1", "resource1");
72+
73+
// Assert
74+
Assert.That(isAllowed, Is.True);
75+
}
76+
77+
[Test]
78+
public void RequestsPerTimespanRule_ShouldBlockRequestsOverLimit()
79+
{
80+
// Arrange
81+
var requestTracker = new RequestTracker();
82+
var rule = new RequestsPerTimespanRule(requestTracker, 2, TimeSpan.FromSeconds(10));
83+
84+
// Act
85+
requestTracker.RecordRequest("token1", "resource1");
86+
requestTracker.RecordRequest("token1", "resource1");
87+
bool isAllowed = rule.IsRequestAllowed("token1", "resource1");
88+
89+
// Assert
90+
Assert.That(isAllowed, Is.False);
91+
}
92+
93+
[Test]
94+
public void CompositeRule_And_ShouldRequireAllRulesToPass()
95+
{
96+
// Arrange
97+
var requestTracker = new RequestTracker();
98+
var rule1 = new RequestsPerTimespanRule(requestTracker, 3, TimeSpan.FromSeconds(10));
99+
var rule2 = new TimeIntervalBetweenRequestsRule(requestTracker, TimeSpan.FromMilliseconds(50));
100+
101+
var compositeRule = new CompositeRule(CompositeRule.LogicalOperator.And);
102+
compositeRule.AddRule(rule1);
103+
compositeRule.AddRule(rule2);
104+
105+
// Act - First request should pass both rules
106+
bool firstRequest = compositeRule.IsRequestAllowed("token1", "resource1");
107+
108+
// Record the request
109+
requestTracker.RecordRequest("token1", "resource1");
110+
111+
// Second request should fail the interval rule but pass the count rule
112+
bool secondRequest = compositeRule.IsRequestAllowed("token1", "resource1");
113+
114+
// Wait for the interval to pass
115+
Thread.Sleep(100);
116+
117+
// Third request should now pass both rules again
118+
bool thirdRequest = compositeRule.IsRequestAllowed("token1", "resource1");
119+
120+
// Assert
121+
Assert.That(firstRequest, Is.True);
122+
Assert.That(secondRequest, Is.False);
123+
Assert.That(thirdRequest, Is.True);
124+
}
125+
126+
[Test]
127+
public void CompositeRule_Or_ShouldRequireAnyRuleToPass()
128+
{
129+
// Arrange
130+
var requestTracker = new RequestTracker();
131+
var rule1 = new RequestsPerTimespanRule(requestTracker, 1, TimeSpan.FromSeconds(10));
132+
var rule2 = new TimeIntervalBetweenRequestsRule(requestTracker, TimeSpan.FromMilliseconds(50));
133+
134+
var compositeRule = new CompositeRule(CompositeRule.LogicalOperator.Or);
135+
compositeRule.AddRule(rule1);
136+
compositeRule.AddRule(rule2);
137+
138+
// Act - First request should pass both rules
139+
bool firstRequest = compositeRule.IsRequestAllowed("token1", "resource1");
140+
141+
// Record the request
142+
requestTracker.RecordRequest("token1", "resource1");
143+
144+
// Second request should fail the count rule but pass the interval rule after waiting
145+
Thread.Sleep(100);
146+
bool secondRequest = compositeRule.IsRequestAllowed("token1", "resource1");
147+
148+
// Record the second request
149+
requestTracker.RecordRequest("token1", "resource1");
150+
151+
// Third request should fail both rules (over count limit and within interval)
152+
bool thirdRequest = compositeRule.IsRequestAllowed("token1", "resource1");
153+
154+
// Assert
155+
Assert.That(firstRequest, Is.True);
156+
Assert.That(secondRequest, Is.True);
157+
Assert.That(thirdRequest, Is.False);
158+
}
159+
160+
[Test]
161+
public void RegionalRule_ShouldApplyDifferentRulesBasedOnRegion()
162+
{
163+
// Arrange
164+
var requestTracker = new RequestTracker();
165+
166+
// Strict rule for US region - only 1 request allowed
167+
var usRule = new RequestsPerTimespanRule(requestTracker, 1, TimeSpan.FromSeconds(10));
168+
169+
// Lenient rule for EU region - 3 requests allowed
170+
var euRule = new RequestsPerTimespanRule(requestTracker, 3, TimeSpan.FromSeconds(10));
171+
172+
// Function to resolve region from token
173+
Func<string, Region> regionResolver = token =>
174+
{
175+
if (token.StartsWith("us-"))
176+
return Region.US;
177+
else if (token.StartsWith("eu-"))
178+
return Region.EU;
179+
else
180+
return Region.Other;
181+
};
182+
183+
var regionalRule = new RegionalRule(regionResolver);
184+
regionalRule.SetRuleForRegion(Region.US, usRule);
185+
regionalRule.SetRuleForRegion(Region.EU, euRule);
186+
187+
// Act & Assert
188+
189+
// US token - first request allowed
190+
Assert.That(regionalRule.IsRequestAllowed("us-token", "resource1"), Is.True);
191+
requestTracker.RecordRequest("us-token", "resource1");
192+
193+
// US token - second request blocked (over limit)
194+
Assert.That(regionalRule.IsRequestAllowed("us-token", "resource1"), Is.False);
195+
196+
// EU token - first request allowed
197+
Assert.That(regionalRule.IsRequestAllowed("eu-token", "resource1"), Is.True);
198+
requestTracker.RecordRequest("eu-token", "resource1");
199+
200+
// EU token - second request allowed
201+
Assert.That(regionalRule.IsRequestAllowed("eu-token", "resource1"), Is.True);
202+
requestTracker.RecordRequest("eu-token", "resource1");
203+
204+
// EU token - third request allowed
205+
Assert.That(regionalRule.IsRequestAllowed("eu-token", "resource1"), Is.True);
206+
requestTracker.RecordRequest("eu-token", "resource1");
207+
208+
// EU token - fourth request blocked (over limit)
209+
Assert.That(regionalRule.IsRequestAllowed("eu-token", "resource1"), Is.False);
210+
211+
// Other region - no rule set, so allowed
212+
Assert.That(regionalRule.IsRequestAllowed("other-token", "resource1"), Is.True);
213+
}
214+
215+
[Test]
216+
public void RateLimiter_ShouldApplyRulesForResources()
217+
{
218+
// Arrange
219+
var rateLimiter = new RateLimiter();
220+
var requestTracker = rateLimiter.GetRequestTracker();
221+
222+
// Create rules
223+
var rule1 = new RequestsPerTimespanRule(requestTracker, 2, TimeSpan.FromSeconds(10));
224+
var rule2 = new TimeIntervalBetweenRequestsRule(requestTracker, TimeSpan.FromMilliseconds(50));
225+
226+
// Set rules for different resources
227+
rateLimiter.SetRuleForResource("resource1", rule1);
228+
rateLimiter.SetRuleForResource("resource2", rule2);
229+
230+
// Act & Assert
231+
232+
// Resource 1 - first request allowed
233+
Assert.That(rateLimiter.IsRequestAllowed("token1", "resource1"), Is.True);
234+
requestTracker.RecordRequest("token1", "resource1");
235+
236+
// Resource 1 - second request allowed
237+
Assert.That(rateLimiter.IsRequestAllowed("token1", "resource1"), Is.True);
238+
requestTracker.RecordRequest("token1", "resource1");
239+
240+
// Resource 1 - third request blocked (over limit)
241+
Assert.That(rateLimiter.IsRequestAllowed("token1", "resource1"), Is.False);
242+
243+
// Resource 2 - first request allowed
244+
Assert.That(rateLimiter.IsRequestAllowed("token1", "resource2"), Is.True);
245+
requestTracker.RecordRequest("token1", "resource2");
246+
247+
// Resource 2 - second request blocked (within interval)
248+
Assert.That(rateLimiter.IsRequestAllowed("token1", "resource2"), Is.False);
249+
250+
// Wait for interval to pass
251+
Thread.Sleep(100);
252+
253+
// Resource 2 - third request allowed (after interval)
254+
Assert.That(rateLimiter.IsRequestAllowed("token1", "resource2"), Is.True);
255+
256+
// Resource 3 - no rule set, so allowed
257+
Assert.That(rateLimiter.IsRequestAllowed("token1", "resource3"), Is.True);
258+
}
259+
}

0 commit comments

Comments
 (0)