-
Notifications
You must be signed in to change notification settings - Fork 232
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Giorgi
committed
Jan 17, 2025
1 parent
d0a5741
commit 482dc13
Showing
14 changed files
with
568 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,276 @@ | ||
using NUnit.Framework; | ||
using System; | ||
using System.Threading; | ||
using RateLimiter.DataStore; | ||
using System.Collections.Generic; | ||
using RateLimiter.Ruls.Abstract; | ||
using RateLimiter.Ruls; | ||
using RateLimiter.User; | ||
|
||
namespace RateLimiter.Tests; | ||
|
||
[TestFixture] | ||
public class RateLimiterTest | ||
{ | ||
[Test] | ||
public void Example() | ||
{ | ||
Assert.That(true, Is.True); | ||
} | ||
{ | ||
[Test] | ||
public void IsAllowedWithNoRules() | ||
{ | ||
Cashing.Clear(); | ||
// Arrange | ||
var rules = new RateLimiterRuleDecorator[] { }; | ||
|
||
var userData = new UserData() { CountryCode = "US", Token = "tempToken", IpAddress = "192.168.18.22" }; | ||
|
||
// Act | ||
var result = new ConcreteRateLimiter(rules).IsAllowed(userData); | ||
|
||
// Assert | ||
Assert.IsTrue(result); | ||
} | ||
[Test] | ||
public void IsAllowedWithAllRulesPositive() | ||
{ | ||
Cashing.Clear(); | ||
Dictionary<string, int> restrictionsByCountry = new() | ||
{ | ||
{ "US", 2 }, | ||
{ "GE", 1 } | ||
}; | ||
|
||
// Arrange | ||
var rules = new RateLimiterRuleDecorator[] { | ||
new IpWhiteListRule(new string[] { "192.168.18.22", "192.168.18.23" }), | ||
new RequestMinAllowedTimeRule(TimeSpan.FromSeconds(1)), | ||
new IpBlackListRule(new string[] { "192.168.18.48", "192.168.18.49" }), | ||
new MaxRequestAmountInTimeSpanRule(TimeSpan.FromSeconds(1),5), | ||
new MaxRequestAmountInTimeSpanByCountryRule(TimeSpan.FromSeconds(1), restrictionsByCountry, 2), | ||
}; | ||
|
||
var userData = new UserData() { CountryCode = "US",Token="tempToken",IpAddress = "192.168.18.22" }; | ||
|
||
// Act | ||
var result = new ConcreteRateLimiter(rules).IsAllowed(userData); | ||
|
||
// Assert | ||
Assert.IsTrue(result); | ||
} | ||
[Test] | ||
public void IpWhtieListRuleNegative() | ||
{ | ||
Cashing.Clear(); | ||
// Arrange | ||
var rules = new RateLimiterRuleDecorator[] { new IpWhiteListRule(new string[] { "192.168.18.22", "192.168.18.23" }) }; | ||
|
||
var userData = new UserData() { CountryCode = "US", Token = "tempToken", IpAddress = "192.168.18.30" }; | ||
|
||
// Act | ||
var result = new ConcreteRateLimiter(rules).IsAllowed(userData); | ||
|
||
|
||
// Assert | ||
Assert.IsFalse(result); | ||
} | ||
[Test] | ||
public void IpBlackListRulePositive() | ||
{ | ||
Cashing.Clear(); | ||
// Arrange | ||
var rules = new RateLimiterRuleDecorator[] { new IpBlackListRule(new string[] { "192.168.18.22", "192.168.18.23" }) }; | ||
|
||
var userData = new UserData() { CountryCode = "US", Token = "tempToken", IpAddress = "192.168.18.30" }; | ||
|
||
// Act | ||
var result = new ConcreteRateLimiter(rules).IsAllowed(userData); | ||
|
||
// Assert | ||
Assert.IsTrue(result); | ||
} | ||
[Test] | ||
public void IpBlackListRuleNegative() | ||
{ | ||
Cashing.Clear(); | ||
// Arrange | ||
var rules = new RateLimiterRuleDecorator[] { new IpBlackListRule(new string[] { "192.168.18.22", "192.168.18.23" }) }; | ||
|
||
var userData = new UserData() { CountryCode = "US", Token = "tempToken", IpAddress = "192.168.18.22" }; | ||
|
||
// Act | ||
var result = new ConcreteRateLimiter(rules).IsAllowed(userData); | ||
|
||
// Assert | ||
Assert.IsFalse(result); | ||
} | ||
[Test] | ||
public void IsAllowedWithRequestMinAllowedTimePositive() | ||
{ | ||
Cashing.Clear(); | ||
// Arrange | ||
var rules = new RateLimiterRuleDecorator[] { new RequestMinAllowedTimeRule(TimeSpan.FromSeconds(1)) }; | ||
|
||
var userData = new UserData() { CountryCode = "US", Token = "tempToken", IpAddress = "192.168.18.22" }; | ||
|
||
// Act | ||
new ConcreteRateLimiter(rules).IsAllowed(userData); | ||
Thread.Sleep(2000); | ||
var result = new ConcreteRateLimiter(rules).IsAllowed(userData); | ||
|
||
// Assert | ||
Assert.IsTrue(result); | ||
} | ||
[Test] | ||
public void IsAllowedRequestMinAllowedTimeNegative() | ||
{ | ||
Cashing.Clear(); | ||
// Arrange | ||
var rules = new RateLimiterRuleDecorator[] { new RequestMinAllowedTimeRule(TimeSpan.FromSeconds(1)) }; | ||
|
||
var userData = new UserData() { CountryCode = "US", Token = "tempToken", IpAddress = "192.168.18.22" }; | ||
|
||
// Act | ||
new ConcreteRateLimiter(rules).IsAllowed(userData); | ||
var result = new ConcreteRateLimiter(rules).IsAllowed(userData); | ||
|
||
// Assert | ||
Assert.IsFalse(result); | ||
} | ||
[Test] | ||
public void IsAllowedMaxRequestAmountInTimeSpanRulePositive() | ||
{ | ||
Cashing.Clear(); | ||
// Arrange | ||
var rules = new RateLimiterRuleDecorator[] { new MaxRequestAmountInTimeSpanRule(TimeSpan.FromSeconds(1),5) }; | ||
|
||
var userData = new UserData() { CountryCode = "US", Token = "tempToken", IpAddress = "192.168.18.22" }; | ||
|
||
// Act | ||
new ConcreteRateLimiter(rules).IsAllowed(userData); | ||
new ConcreteRateLimiter(rules).IsAllowed(userData); | ||
var result = new ConcreteRateLimiter(rules).IsAllowed(userData); | ||
|
||
// Assert | ||
Assert.IsTrue(result); | ||
} | ||
[Test] | ||
public void IsAllowedMaxRequestAmountInTimeSpanRuleWithDeleyPositive() | ||
{ | ||
Cashing.Clear(); | ||
// Arrange | ||
var rules = new RateLimiterRuleDecorator[] { new MaxRequestAmountInTimeSpanRule(TimeSpan.FromSeconds(1), 2) }; | ||
|
||
var userData = new UserData() { CountryCode = "US", Token = "tempToken", IpAddress = "192.168.18.22" }; | ||
|
||
// Act | ||
new ConcreteRateLimiter(rules).IsAllowed(userData); | ||
new ConcreteRateLimiter(rules).IsAllowed(userData); | ||
Thread.Sleep(2000); | ||
var result = new ConcreteRateLimiter(rules).IsAllowed(userData); | ||
|
||
// Assert | ||
Assert.IsTrue(result); | ||
} | ||
[Test] | ||
public void IsAllowedMaxRequestAmountInTimeSpanRuleNegative() | ||
{ | ||
Cashing.Clear(); | ||
// Arrange | ||
var rules = new RateLimiterRuleDecorator[] { new MaxRequestAmountInTimeSpanRule(TimeSpan.FromSeconds(1), 2) }; | ||
|
||
var userData = new UserData() { CountryCode = "US", Token = "tempToken", IpAddress = "192.168.18.22" }; | ||
|
||
// Act | ||
new ConcreteRateLimiter(rules).IsAllowed(userData); | ||
new ConcreteRateLimiter(rules).IsAllowed(userData); | ||
var result = new ConcreteRateLimiter(rules).IsAllowed(userData); | ||
|
||
// Assert | ||
Assert.IsFalse(result); | ||
} | ||
[Test] | ||
public void IsAllowedMaxRequestAmountInTimeSpanByCountryPositive() | ||
{ | ||
Cashing.Clear(); | ||
// Arrange | ||
Dictionary<string, int> restrictionsByCountry = new() | ||
{ | ||
{ "US", 2 }, | ||
{ "GE", 1 } | ||
}; | ||
var rules = new RateLimiterRuleDecorator[] { new MaxRequestAmountInTimeSpanByCountryRule( TimeSpan.FromSeconds(1), restrictionsByCountry, 2) }; | ||
|
||
var userData = new UserData() { CountryCode = "US", Token = "tempToken", IpAddress = "192.168.18.22" }; | ||
|
||
// Act | ||
new ConcreteRateLimiter(rules).IsAllowed(userData); | ||
var result = new ConcreteRateLimiter(rules).IsAllowed(userData); | ||
|
||
// Assert | ||
Assert.IsTrue(result); | ||
} | ||
[Test] | ||
public void IsAllowedMaxRequestAmountInTimeSpanByCountryNegative() | ||
{ | ||
Cashing.Clear(); | ||
// Arrange | ||
Dictionary<string, int> restrictionsByCountry = new() | ||
{ | ||
{ "US", 2 }, | ||
{ "GE", 1 } | ||
}; | ||
var rules = new RateLimiterRuleDecorator[] { new MaxRequestAmountInTimeSpanByCountryRule(TimeSpan.FromSeconds(1), restrictionsByCountry, 1) }; | ||
|
||
var userData = new UserData() { CountryCode = "US", Token = "tempToken", IpAddress = "192.168.18.22" }; | ||
|
||
// Act | ||
new ConcreteRateLimiter(rules).IsAllowed(userData); | ||
new ConcreteRateLimiter(rules).IsAllowed(userData); | ||
var result = new ConcreteRateLimiter(rules).IsAllowed(userData); | ||
|
||
// Assert | ||
Assert.IsFalse(result); | ||
} | ||
[Test] | ||
public void IsAllowedMaxRequestAmountInTimeSpanByCountryDefoultPositive() | ||
{ | ||
Cashing.Clear(); | ||
// Arrange | ||
Dictionary<string, int> restrictionsByCountry = new() | ||
{ | ||
{ "US", 2 }, | ||
{ "GE", 1 } | ||
}; | ||
var rules = new RateLimiterRuleDecorator[] { new MaxRequestAmountInTimeSpanByCountryRule(TimeSpan.FromSeconds(1), restrictionsByCountry, 2) }; | ||
|
||
var userData = new UserData() { CountryCode = "TR", Token = "tempToken", IpAddress = "192.168.18.22" }; | ||
|
||
// Act | ||
new ConcreteRateLimiter(rules).IsAllowed(userData); | ||
var result = new ConcreteRateLimiter(rules).IsAllowed(userData); | ||
|
||
// Assert | ||
Assert.IsTrue(result); | ||
} | ||
[Test] | ||
public void IsAllowedMaxRequestAmountInTimeSpanByCountryDefoultNegative() | ||
{ | ||
Cashing.Clear(); | ||
// Arrange | ||
Dictionary<string, int> restrictionsByCountry = new() | ||
{ | ||
{ "US", 2 }, | ||
{ "GE", 1 } | ||
}; | ||
var rules = new RateLimiterRuleDecorator[] { new MaxRequestAmountInTimeSpanByCountryRule(TimeSpan.FromSeconds(1), restrictionsByCountry, 2) }; | ||
|
||
var userData = new UserData() { CountryCode = "TR", Token = "tempToken", IpAddress = "192.168.18.22" }; | ||
|
||
// Act | ||
new ConcreteRateLimiter(rules).IsAllowed(userData); | ||
new ConcreteRateLimiter(rules).IsAllowed(userData); | ||
var result = new ConcreteRateLimiter(rules).IsAllowed(userData); | ||
|
||
// Assert | ||
Assert.IsFalse(result); | ||
} | ||
|
||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
using RateLimiter.Ruls.Abstract; | ||
using RateLimiter.User; | ||
|
||
namespace RateLimiter | ||
{ | ||
public class ConcreteRateLimiter | ||
{ | ||
private readonly RateLimiterRuleDecorator? _rateLimiter; | ||
public ConcreteRateLimiter(RateLimiterRuleDecorator[] rules) | ||
{ | ||
foreach (var rule in rules) | ||
{ | ||
if (_rateLimiter == null) | ||
{ | ||
_rateLimiter = rule; | ||
} | ||
else | ||
{ | ||
rule.RateLimiterRule = _rateLimiter; | ||
_rateLimiter = rule; | ||
} | ||
} | ||
} | ||
public bool IsAllowed(IUserData userData) | ||
{ | ||
return _rateLimiter == null || _rateLimiter.IsAllowed(userData); | ||
} | ||
|
||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
using System.Collections.Concurrent; | ||
|
||
namespace RateLimiter.DataStore | ||
{ | ||
//this is only for demonstration in real world scenario will be used redis or other data store | ||
public static class Cashing | ||
{ | ||
private static ConcurrentDictionary<string, string> Store = new(); | ||
|
||
public static string? Get(string key) { | ||
Store.TryGetValue(key, out string? temp); | ||
return temp; | ||
} | ||
|
||
public static void Set(string key, string value) { | ||
Store.AddOrUpdate(key, value, (existingKey, existingValue) => value); | ||
} | ||
public static void Clear() { Store.Clear(); } | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
using RateLimiter.User; | ||
|
||
namespace RateLimiter.Ruls.Abstract | ||
{ | ||
public abstract class RateLimiterRuleDecorator | ||
{ | ||
public RateLimiterRuleDecorator? RateLimiterRule { get; set; } | ||
public abstract bool IsAllowed(IUserData userData); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
using RateLimiter.Ruls.Abstract; | ||
using RateLimiter.User; | ||
using System; | ||
using System.Linq; | ||
|
||
namespace RateLimiter.Ruls | ||
{ | ||
public class IpBlackListRule : RateLimiterRuleDecorator | ||
{ | ||
private readonly string[] _restrictedIpAddresses; | ||
|
||
public IpBlackListRule(string[] allowedIpAddresses) | ||
{ | ||
_restrictedIpAddresses = allowedIpAddresses; | ||
} | ||
|
||
public override bool IsAllowed(IUserData userData) | ||
{ | ||
return !_restrictedIpAddresses.Contains(userData.IpAddress) && (RateLimiterRule == null || RateLimiterRule.IsAllowed(userData)); | ||
} | ||
} | ||
} |
Oops, something went wrong.