-
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.
Implement Rate Limiter with Unit tests
- Loading branch information
Arsen
committed
May 16, 2024
1 parent
73f3a7c
commit b18a113
Showing
11 changed files
with
584 additions
and
4 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
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,124 @@ | ||
using NUnit.Framework; | ||
using Moq; | ||
using NUnit.Framework; | ||
using RateLimiter.Rules; | ||
using System.Collections.Generic; | ||
|
||
namespace RateLimiter.Tests; | ||
|
||
[TestFixture] | ||
public class RateLimiterTest | ||
{ | ||
[Test] | ||
public void Example() | ||
private readonly List<Mock<IRateLimitRule>> _rateLimitRuleMocks = new List<Mock<IRateLimitRule>> | ||
{ | ||
Assert.That(true, Is.True); | ||
new(), | ||
new() | ||
}; | ||
|
||
[Test] | ||
public void IsRequestAllowed_NoRulesForResource_ReturnsTrue() | ||
{ | ||
// arrange | ||
string resource = "resource"; | ||
string token = "token"; | ||
|
||
var rateLimiter = new RateLimiter(new Dictionary<string, ICollection<IRateLimitRule>> | ||
{ | ||
}); | ||
|
||
// act | ||
// assert | ||
Assert.True(rateLimiter.IsRequestAllowed(token, resource)); | ||
} | ||
|
||
[Test] | ||
public void IsRequestAllowed_AllRulesPass_ReturnsTrue() | ||
{ | ||
// arrange | ||
string resource = "resource"; | ||
string token = "token"; | ||
var key = $"{resource}:{token}"; | ||
|
||
_rateLimitRuleMocks[0].Setup(x => x.IsRequestAllowed(resource, token)).Returns(true); | ||
_rateLimitRuleMocks[1].Setup(x => x.IsRequestAllowed(resource, token)).Returns(true); | ||
|
||
var rateLimiter = new RateLimiter(new Dictionary<string, ICollection<IRateLimitRule>> | ||
{ | ||
{ | ||
resource, | ||
new List<IRateLimitRule> { _rateLimitRuleMocks[0].Object, _rateLimitRuleMocks[1].Object } | ||
} | ||
}); | ||
|
||
// act | ||
// assert | ||
Assert.True(rateLimiter.IsRequestAllowed(token, resource)); | ||
} | ||
|
||
[Test] | ||
public void IsRequestAllowed_OneRuleFails_ReturnsTrue() | ||
{ | ||
// arrange | ||
string resource = "resource"; | ||
string token = "token"; | ||
var key = $"{resource}:{token}"; | ||
|
||
_rateLimitRuleMocks[0].Setup(x => x.IsRequestAllowed(resource, token)).Returns(true); | ||
_rateLimitRuleMocks[1].Setup(x => x.IsRequestAllowed(resource, token)).Returns(false); | ||
|
||
// act | ||
var rateLimiter = new RateLimiter(new Dictionary<string, ICollection<IRateLimitRule>> | ||
{ | ||
{ | ||
resource, | ||
new List<IRateLimitRule> { _rateLimitRuleMocks[0].Object, _rateLimitRuleMocks[1].Object } | ||
} | ||
}); | ||
|
||
// act | ||
// assert | ||
Assert.False(rateLimiter.IsRequestAllowed(resource, token)); | ||
} | ||
|
||
[TestCase(false)] | ||
[TestCase(true)] | ||
public void AddRule_AddRuleForResource_NewRuleExecuted(bool isRequestAllowed) | ||
{ | ||
// arrange | ||
string resource = "resource"; | ||
string token = "token"; | ||
|
||
var rateLimiter = new RateLimiter(new Dictionary<string, ICollection<IRateLimitRule>> | ||
{ | ||
}); | ||
|
||
_rateLimitRuleMocks[0].Setup(x => x.IsRequestAllowed(resource, token)).Returns(isRequestAllowed); | ||
|
||
// act | ||
rateLimiter.AddRule(resource, _rateLimitRuleMocks[0].Object); | ||
|
||
// assert | ||
Assert.AreEqual(rateLimiter.IsRequestAllowed(resource, token), isRequestAllowed); | ||
} | ||
|
||
[Test] | ||
public void AddRule_AddRuleForResourceWithExistingRules_NewRuleExecuted() | ||
{ | ||
// arrange | ||
string resource = "resource"; | ||
string token = "token"; | ||
|
||
_rateLimitRuleMocks[0].Setup(x => x.IsRequestAllowed(resource, token)).Returns(true); | ||
_rateLimitRuleMocks[1].Setup(x => x.IsRequestAllowed(resource, token)).Returns(false); | ||
|
||
var rateLimiter = new RateLimiter(new Dictionary<string, ICollection<IRateLimitRule>> | ||
{ | ||
{ resource, new List<IRateLimitRule> { _rateLimitRuleMocks[0].Object } } | ||
}); | ||
|
||
// act | ||
rateLimiter.AddRule(resource, _rateLimitRuleMocks[1].Object); | ||
|
||
// assert | ||
Assert.AreEqual(rateLimiter.IsRequestAllowed(resource, token), false); | ||
} | ||
} |
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,105 @@ | ||
using Moq; | ||
using NUnit.Framework; | ||
using RateLimiter.Rules; | ||
using RateLimiter.Storage; | ||
using System.Collections.Generic; | ||
|
||
namespace RateLimiter.Tests.Rules | ||
{ | ||
[TestFixture] | ||
public class CountryBasedRuleTests | ||
{ | ||
[Test] | ||
public void IsRequestAllowed_CountryWithoutRules_ReturnsTrue() | ||
{ | ||
// arrange | ||
string resource = "resource"; | ||
string token = "token"; | ||
string tokenCountry = "us"; | ||
|
||
DataStorage.TokenOrigins = new Dictionary<string, string> | ||
{ | ||
{ token, tokenCountry } | ||
}; | ||
|
||
var rules = new Dictionary<string, IEnumerable<IRateLimitRule>> | ||
{ | ||
{ tokenCountry, new List<IRateLimitRule> { } } | ||
}; | ||
|
||
var sut = new CountryBasedRule(rules); | ||
|
||
// act | ||
var actualResult = sut.IsRequestAllowed(resource, token); | ||
|
||
// assert | ||
Assert.True(actualResult); | ||
} | ||
|
||
[Test] | ||
public void IsRequestAllowed_CountryWithoutFailingRules_ReturnsTrue() | ||
{ | ||
// arrange | ||
string resource = "resource"; | ||
string token = "token"; | ||
string tokenCountry = "us"; | ||
|
||
DataStorage.TokenOrigins = new Dictionary<string, string> | ||
{ | ||
{ token, tokenCountry } | ||
}; | ||
|
||
Mock<IRateLimitRule> rateLimiter = new Mock<IRateLimitRule>(); | ||
rateLimiter.Setup(x => x.IsRequestAllowed(resource, token)) | ||
.Returns(true); | ||
|
||
var rules = new Dictionary<string, IEnumerable<IRateLimitRule>> | ||
{ | ||
{ tokenCountry, new List<IRateLimitRule> { rateLimiter.Object } } | ||
}; | ||
|
||
var sut = new CountryBasedRule(rules); | ||
|
||
// act | ||
var actualResult = sut.IsRequestAllowed(resource, token); | ||
|
||
// assert | ||
Assert.True(actualResult); | ||
} | ||
|
||
[Test] | ||
public void IsRequestAllowed_CountryWithOneFailingRule_ReturnsFalse() | ||
{ | ||
// arrange | ||
string resource = "resource"; | ||
string token = "token"; | ||
string tokenCountry = "us"; | ||
|
||
DataStorage.TokenOrigins = new Dictionary<string, string> | ||
{ | ||
{ token, tokenCountry } | ||
}; | ||
|
||
var rateLimiter1 = new Mock<IRateLimitRule>(); | ||
rateLimiter1.Setup(x => x.IsRequestAllowed(resource, token)) | ||
.Returns(true); | ||
|
||
var rateLimiter2 = new Mock<IRateLimitRule>(); | ||
rateLimiter2.Setup(x => x.IsRequestAllowed(resource, token)) | ||
.Returns(false); | ||
|
||
var rules = new Dictionary<string, IEnumerable<IRateLimitRule>> | ||
{ | ||
{ tokenCountry, new List<IRateLimitRule> { rateLimiter1.Object, rateLimiter2.Object } } | ||
}; | ||
|
||
var sut = new CountryBasedRule(rules); | ||
|
||
// act | ||
var actualResult = sut.IsRequestAllowed(resource, token); | ||
|
||
// assert | ||
Assert.False(actualResult); | ||
} | ||
} | ||
} |
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,89 @@ | ||
using NUnit.Framework; | ||
using RateLimiter.Rules; | ||
using RateLimiter.Storage; | ||
using System; | ||
using System.Collections.Generic; | ||
|
||
namespace RateLimiter.Tests.Rules | ||
{ | ||
[TestFixture] | ||
public class FixedRequestNumberRuleTests | ||
{ | ||
[Test] | ||
public void IsRequestAllowed_FirstRequestForResourceToken_ReturnsTrue() | ||
{ | ||
// arrange | ||
string resource = "resource"; | ||
string token = "token"; | ||
|
||
TimeSpan timeSpan = TimeSpan.FromMinutes(1); | ||
|
||
var maxRequestCount = 1; | ||
|
||
DataStorage.Requests = new Dictionary<string, List<DateTime>> | ||
{ | ||
}; | ||
|
||
var sut = new FixedRequestNumberRule(timeSpan, maxRequestCount); | ||
|
||
// act | ||
var actualResult = sut.IsRequestAllowed(resource, token); | ||
|
||
// assert | ||
Assert.True(actualResult); | ||
} | ||
|
||
[Test] | ||
public void IsRequestAllowed_MaxRequestCountNotExceeded_ReturnsTrue() | ||
{ | ||
// arrange | ||
string resource = "resource"; | ||
string token = "token"; | ||
var key = $"{resource}:{token}"; | ||
|
||
TimeSpan timeSpan = TimeSpan.FromMinutes(1); | ||
|
||
var maxRequestCount = 2; | ||
var dateInLatestTimeSpan = DateTime.UtcNow; | ||
var dateNotInLatestTimeSpan = DateTime.UtcNow.AddMinutes(-2); | ||
|
||
DataStorage.Requests = new Dictionary<string, List<DateTime>> | ||
{ | ||
{ key, new List<DateTime> { dateNotInLatestTimeSpan, dateInLatestTimeSpan } } | ||
}; | ||
|
||
var sut = new FixedRequestNumberRule(timeSpan, maxRequestCount); | ||
|
||
// act | ||
var actualResult = sut.IsRequestAllowed(resource, token); | ||
|
||
// assert | ||
Assert.True(actualResult); | ||
} | ||
|
||
[Test] | ||
public void IsRequestAllowed_MaxRequestCountExceeded_ReturnsFalse() | ||
{ | ||
// arrange | ||
string resource = "resource"; | ||
string token = "token"; | ||
var key = $"{resource}:{token}"; | ||
|
||
TimeSpan timeSpan = TimeSpan.FromMinutes(1); | ||
var maxRequestCount = 1; | ||
|
||
DataStorage.Requests = new Dictionary<string, List<DateTime>> | ||
{ | ||
{ key, new List<DateTime> { DateTime.UtcNow } } | ||
}; | ||
|
||
var sut = new FixedRequestNumberRule(timeSpan, maxRequestCount); | ||
|
||
// act | ||
var actualResult = sut.IsRequestAllowed(resource, token); | ||
|
||
// assert | ||
Assert.False(actualResult); | ||
} | ||
} | ||
} |
Oops, something went wrong.