Skip to content

Commit 482dc13

Browse files
author
Giorgi
committed
Rate Limiter and Tests
1 parent d0a5741 commit 482dc13

14 files changed

+568
-10
lines changed
Lines changed: 269 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,276 @@
11
using NUnit.Framework;
2+
using System;
3+
using System.Threading;
4+
using RateLimiter.DataStore;
5+
using System.Collections.Generic;
6+
using RateLimiter.Ruls.Abstract;
7+
using RateLimiter.Ruls;
8+
using RateLimiter.User;
29

310
namespace RateLimiter.Tests;
411

512
[TestFixture]
613
public class RateLimiterTest
7-
{
8-
[Test]
9-
public void Example()
10-
{
11-
Assert.That(true, Is.True);
12-
}
14+
{
15+
[Test]
16+
public void IsAllowedWithNoRules()
17+
{
18+
Cashing.Clear();
19+
// Arrange
20+
var rules = new RateLimiterRuleDecorator[] { };
21+
22+
var userData = new UserData() { CountryCode = "US", Token = "tempToken", IpAddress = "192.168.18.22" };
23+
24+
// Act
25+
var result = new ConcreteRateLimiter(rules).IsAllowed(userData);
26+
27+
// Assert
28+
Assert.IsTrue(result);
29+
}
30+
[Test]
31+
public void IsAllowedWithAllRulesPositive()
32+
{
33+
Cashing.Clear();
34+
Dictionary<string, int> restrictionsByCountry = new()
35+
{
36+
{ "US", 2 },
37+
{ "GE", 1 }
38+
};
39+
40+
// Arrange
41+
var rules = new RateLimiterRuleDecorator[] {
42+
new IpWhiteListRule(new string[] { "192.168.18.22", "192.168.18.23" }),
43+
new RequestMinAllowedTimeRule(TimeSpan.FromSeconds(1)),
44+
new IpBlackListRule(new string[] { "192.168.18.48", "192.168.18.49" }),
45+
new MaxRequestAmountInTimeSpanRule(TimeSpan.FromSeconds(1),5),
46+
new MaxRequestAmountInTimeSpanByCountryRule(TimeSpan.FromSeconds(1), restrictionsByCountry, 2),
47+
};
48+
49+
var userData = new UserData() { CountryCode = "US",Token="tempToken",IpAddress = "192.168.18.22" };
50+
51+
// Act
52+
var result = new ConcreteRateLimiter(rules).IsAllowed(userData);
53+
54+
// Assert
55+
Assert.IsTrue(result);
56+
}
57+
[Test]
58+
public void IpWhtieListRuleNegative()
59+
{
60+
Cashing.Clear();
61+
// Arrange
62+
var rules = new RateLimiterRuleDecorator[] { new IpWhiteListRule(new string[] { "192.168.18.22", "192.168.18.23" }) };
63+
64+
var userData = new UserData() { CountryCode = "US", Token = "tempToken", IpAddress = "192.168.18.30" };
65+
66+
// Act
67+
var result = new ConcreteRateLimiter(rules).IsAllowed(userData);
68+
69+
70+
// Assert
71+
Assert.IsFalse(result);
72+
}
73+
[Test]
74+
public void IpBlackListRulePositive()
75+
{
76+
Cashing.Clear();
77+
// Arrange
78+
var rules = new RateLimiterRuleDecorator[] { new IpBlackListRule(new string[] { "192.168.18.22", "192.168.18.23" }) };
79+
80+
var userData = new UserData() { CountryCode = "US", Token = "tempToken", IpAddress = "192.168.18.30" };
81+
82+
// Act
83+
var result = new ConcreteRateLimiter(rules).IsAllowed(userData);
84+
85+
// Assert
86+
Assert.IsTrue(result);
87+
}
88+
[Test]
89+
public void IpBlackListRuleNegative()
90+
{
91+
Cashing.Clear();
92+
// Arrange
93+
var rules = new RateLimiterRuleDecorator[] { new IpBlackListRule(new string[] { "192.168.18.22", "192.168.18.23" }) };
94+
95+
var userData = new UserData() { CountryCode = "US", Token = "tempToken", IpAddress = "192.168.18.22" };
96+
97+
// Act
98+
var result = new ConcreteRateLimiter(rules).IsAllowed(userData);
99+
100+
// Assert
101+
Assert.IsFalse(result);
102+
}
103+
[Test]
104+
public void IsAllowedWithRequestMinAllowedTimePositive()
105+
{
106+
Cashing.Clear();
107+
// Arrange
108+
var rules = new RateLimiterRuleDecorator[] { new RequestMinAllowedTimeRule(TimeSpan.FromSeconds(1)) };
109+
110+
var userData = new UserData() { CountryCode = "US", Token = "tempToken", IpAddress = "192.168.18.22" };
111+
112+
// Act
113+
new ConcreteRateLimiter(rules).IsAllowed(userData);
114+
Thread.Sleep(2000);
115+
var result = new ConcreteRateLimiter(rules).IsAllowed(userData);
116+
117+
// Assert
118+
Assert.IsTrue(result);
119+
}
120+
[Test]
121+
public void IsAllowedRequestMinAllowedTimeNegative()
122+
{
123+
Cashing.Clear();
124+
// Arrange
125+
var rules = new RateLimiterRuleDecorator[] { new RequestMinAllowedTimeRule(TimeSpan.FromSeconds(1)) };
126+
127+
var userData = new UserData() { CountryCode = "US", Token = "tempToken", IpAddress = "192.168.18.22" };
128+
129+
// Act
130+
new ConcreteRateLimiter(rules).IsAllowed(userData);
131+
var result = new ConcreteRateLimiter(rules).IsAllowed(userData);
132+
133+
// Assert
134+
Assert.IsFalse(result);
135+
}
136+
[Test]
137+
public void IsAllowedMaxRequestAmountInTimeSpanRulePositive()
138+
{
139+
Cashing.Clear();
140+
// Arrange
141+
var rules = new RateLimiterRuleDecorator[] { new MaxRequestAmountInTimeSpanRule(TimeSpan.FromSeconds(1),5) };
142+
143+
var userData = new UserData() { CountryCode = "US", Token = "tempToken", IpAddress = "192.168.18.22" };
144+
145+
// Act
146+
new ConcreteRateLimiter(rules).IsAllowed(userData);
147+
new ConcreteRateLimiter(rules).IsAllowed(userData);
148+
var result = new ConcreteRateLimiter(rules).IsAllowed(userData);
149+
150+
// Assert
151+
Assert.IsTrue(result);
152+
}
153+
[Test]
154+
public void IsAllowedMaxRequestAmountInTimeSpanRuleWithDeleyPositive()
155+
{
156+
Cashing.Clear();
157+
// Arrange
158+
var rules = new RateLimiterRuleDecorator[] { new MaxRequestAmountInTimeSpanRule(TimeSpan.FromSeconds(1), 2) };
159+
160+
var userData = new UserData() { CountryCode = "US", Token = "tempToken", IpAddress = "192.168.18.22" };
161+
162+
// Act
163+
new ConcreteRateLimiter(rules).IsAllowed(userData);
164+
new ConcreteRateLimiter(rules).IsAllowed(userData);
165+
Thread.Sleep(2000);
166+
var result = new ConcreteRateLimiter(rules).IsAllowed(userData);
167+
168+
// Assert
169+
Assert.IsTrue(result);
170+
}
171+
[Test]
172+
public void IsAllowedMaxRequestAmountInTimeSpanRuleNegative()
173+
{
174+
Cashing.Clear();
175+
// Arrange
176+
var rules = new RateLimiterRuleDecorator[] { new MaxRequestAmountInTimeSpanRule(TimeSpan.FromSeconds(1), 2) };
177+
178+
var userData = new UserData() { CountryCode = "US", Token = "tempToken", IpAddress = "192.168.18.22" };
179+
180+
// Act
181+
new ConcreteRateLimiter(rules).IsAllowed(userData);
182+
new ConcreteRateLimiter(rules).IsAllowed(userData);
183+
var result = new ConcreteRateLimiter(rules).IsAllowed(userData);
184+
185+
// Assert
186+
Assert.IsFalse(result);
187+
}
188+
[Test]
189+
public void IsAllowedMaxRequestAmountInTimeSpanByCountryPositive()
190+
{
191+
Cashing.Clear();
192+
// Arrange
193+
Dictionary<string, int> restrictionsByCountry = new()
194+
{
195+
{ "US", 2 },
196+
{ "GE", 1 }
197+
};
198+
var rules = new RateLimiterRuleDecorator[] { new MaxRequestAmountInTimeSpanByCountryRule( TimeSpan.FromSeconds(1), restrictionsByCountry, 2) };
199+
200+
var userData = new UserData() { CountryCode = "US", Token = "tempToken", IpAddress = "192.168.18.22" };
201+
202+
// Act
203+
new ConcreteRateLimiter(rules).IsAllowed(userData);
204+
var result = new ConcreteRateLimiter(rules).IsAllowed(userData);
205+
206+
// Assert
207+
Assert.IsTrue(result);
208+
}
209+
[Test]
210+
public void IsAllowedMaxRequestAmountInTimeSpanByCountryNegative()
211+
{
212+
Cashing.Clear();
213+
// Arrange
214+
Dictionary<string, int> restrictionsByCountry = new()
215+
{
216+
{ "US", 2 },
217+
{ "GE", 1 }
218+
};
219+
var rules = new RateLimiterRuleDecorator[] { new MaxRequestAmountInTimeSpanByCountryRule(TimeSpan.FromSeconds(1), restrictionsByCountry, 1) };
220+
221+
var userData = new UserData() { CountryCode = "US", Token = "tempToken", IpAddress = "192.168.18.22" };
222+
223+
// Act
224+
new ConcreteRateLimiter(rules).IsAllowed(userData);
225+
new ConcreteRateLimiter(rules).IsAllowed(userData);
226+
var result = new ConcreteRateLimiter(rules).IsAllowed(userData);
227+
228+
// Assert
229+
Assert.IsFalse(result);
230+
}
231+
[Test]
232+
public void IsAllowedMaxRequestAmountInTimeSpanByCountryDefoultPositive()
233+
{
234+
Cashing.Clear();
235+
// Arrange
236+
Dictionary<string, int> restrictionsByCountry = new()
237+
{
238+
{ "US", 2 },
239+
{ "GE", 1 }
240+
};
241+
var rules = new RateLimiterRuleDecorator[] { new MaxRequestAmountInTimeSpanByCountryRule(TimeSpan.FromSeconds(1), restrictionsByCountry, 2) };
242+
243+
var userData = new UserData() { CountryCode = "TR", Token = "tempToken", IpAddress = "192.168.18.22" };
244+
245+
// Act
246+
new ConcreteRateLimiter(rules).IsAllowed(userData);
247+
var result = new ConcreteRateLimiter(rules).IsAllowed(userData);
248+
249+
// Assert
250+
Assert.IsTrue(result);
251+
}
252+
[Test]
253+
public void IsAllowedMaxRequestAmountInTimeSpanByCountryDefoultNegative()
254+
{
255+
Cashing.Clear();
256+
// Arrange
257+
Dictionary<string, int> restrictionsByCountry = new()
258+
{
259+
{ "US", 2 },
260+
{ "GE", 1 }
261+
};
262+
var rules = new RateLimiterRuleDecorator[] { new MaxRequestAmountInTimeSpanByCountryRule(TimeSpan.FromSeconds(1), restrictionsByCountry, 2) };
263+
264+
var userData = new UserData() { CountryCode = "TR", Token = "tempToken", IpAddress = "192.168.18.22" };
265+
266+
// Act
267+
new ConcreteRateLimiter(rules).IsAllowed(userData);
268+
new ConcreteRateLimiter(rules).IsAllowed(userData);
269+
var result = new ConcreteRateLimiter(rules).IsAllowed(userData);
270+
271+
// Assert
272+
Assert.IsFalse(result);
273+
}
274+
275+
13276
}

RateLimiter.sln

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11

22
Microsoft Visual Studio Solution File, Format Version 12.00
3-
# Visual Studio 15
4-
VisualStudioVersion = 15.0.26730.15
3+
# Visual Studio Version 17
4+
VisualStudioVersion = 17.5.33502.453
55
MinimumVisualStudioVersion = 10.0.40219.1
6-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RateLimiter", "RateLimiter\RateLimiter.csproj", "{36F4BDC6-D3DA-403A-8DB7-0C79F94B938F}"
6+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RateLimiter", "RateLimiter\RateLimiter.csproj", "{36F4BDC6-D3DA-403A-8DB7-0C79F94B938F}"
77
EndProject
8-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RateLimiter.Tests", "RateLimiter.Tests\RateLimiter.Tests.csproj", "{C4F9249B-010E-46BE-94B8-DD20D82F1E60}"
8+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RateLimiter.Tests", "RateLimiter.Tests\RateLimiter.Tests.csproj", "{C4F9249B-010E-46BE-94B8-DD20D82F1E60}"
99
EndProject
1010
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{9B206889-9841-4B5E-B79B-D5B2610CCCFF}"
1111
ProjectSection(SolutionItems) = preProject

RateLimiter/ConcreteRateLimiter.cs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
using RateLimiter.Ruls.Abstract;
2+
using RateLimiter.User;
3+
4+
namespace RateLimiter
5+
{
6+
public class ConcreteRateLimiter
7+
{
8+
private readonly RateLimiterRuleDecorator? _rateLimiter;
9+
public ConcreteRateLimiter(RateLimiterRuleDecorator[] rules)
10+
{
11+
foreach (var rule in rules)
12+
{
13+
if (_rateLimiter == null)
14+
{
15+
_rateLimiter = rule;
16+
}
17+
else
18+
{
19+
rule.RateLimiterRule = _rateLimiter;
20+
_rateLimiter = rule;
21+
}
22+
}
23+
}
24+
public bool IsAllowed(IUserData userData)
25+
{
26+
return _rateLimiter == null || _rateLimiter.IsAllowed(userData);
27+
}
28+
29+
}
30+
}

RateLimiter/DataStore/Cashing.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using System.Collections.Concurrent;
2+
3+
namespace RateLimiter.DataStore
4+
{
5+
//this is only for demonstration in real world scenario will be used redis or other data store
6+
public static class Cashing
7+
{
8+
private static ConcurrentDictionary<string, string> Store = new();
9+
10+
public static string? Get(string key) {
11+
Store.TryGetValue(key, out string? temp);
12+
return temp;
13+
}
14+
15+
public static void Set(string key, string value) {
16+
Store.AddOrUpdate(key, value, (existingKey, existingValue) => value);
17+
}
18+
public static void Clear() { Store.Clear(); }
19+
}
20+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using RateLimiter.User;
2+
3+
namespace RateLimiter.Ruls.Abstract
4+
{
5+
public abstract class RateLimiterRuleDecorator
6+
{
7+
public RateLimiterRuleDecorator? RateLimiterRule { get; set; }
8+
public abstract bool IsAllowed(IUserData userData);
9+
}
10+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
using RateLimiter.Ruls.Abstract;
2+
using RateLimiter.User;
3+
using System;
4+
using System.Linq;
5+
6+
namespace RateLimiter.Ruls
7+
{
8+
public class IpBlackListRule : RateLimiterRuleDecorator
9+
{
10+
private readonly string[] _restrictedIpAddresses;
11+
12+
public IpBlackListRule(string[] allowedIpAddresses)
13+
{
14+
_restrictedIpAddresses = allowedIpAddresses;
15+
}
16+
17+
public override bool IsAllowed(IUserData userData)
18+
{
19+
return !_restrictedIpAddresses.Contains(userData.IpAddress) && (RateLimiterRule == null || RateLimiterRule.IsAllowed(userData));
20+
}
21+
}
22+
}

0 commit comments

Comments
 (0)