Skip to content

Commit

Permalink
Implement Rate Limiter with Unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Arsen committed May 16, 2024
1 parent 73f3a7c commit b18a113
Show file tree
Hide file tree
Showing 11 changed files with 584 additions and 4 deletions.
1 change: 1 addition & 0 deletions RateLimiter.Tests/RateLimiter.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
<PackageReference Include="Moq" Version="4.20.70" />
<PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.4.2" />
</ItemGroup>
Expand Down
119 changes: 115 additions & 4 deletions RateLimiter.Tests/RateLimiterTest.cs
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);
}
}
105 changes: 105 additions & 0 deletions RateLimiter.Tests/Rules/CountryBasedRuleTests.cs
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);
}
}
}
89 changes: 89 additions & 0 deletions RateLimiter.Tests/Rules/FixedRequestNumberRuleTests.cs
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);
}
}
}
Loading

0 comments on commit b18a113

Please sign in to comment.