Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions RateLimiter.Tests/Factories/RateLimitDataStoreFactoryTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using NUnit.Framework;
using RateLimiter.Constants;
using RateLimiter.Exceptions;
using RateLimiter.Factories;

namespace RateLimiter.Tests.Factories
{
public class RateLimitDataStoreFactoryTest
{
[Test]
public void CreateDataStore_Returns_DataStore_When_DataStoreTypeIsKnown()
{
// Arrange
RateLimitDataStoreTypes dataStoreType = RateLimitDataStoreTypes.ConcurrentInMemory;
var dataStoreFactory = new RateLimitDataStoreFactory();

// Act
var dataStore = dataStoreFactory.CreateDataStore(dataStoreType);

// Assert
Assert.NotNull(dataStore);
}

[Test]
public void CreateDataStore_Throws_NotImplementedException_ForUnknownDataStoreType()
{
// Arrange
RateLimitDataStoreTypes unimplementedDataStoreType = (RateLimitDataStoreTypes)10000;
var dataStoreFactory = new RateLimitDataStoreFactory();

// Act
var datastoreDelegate = () => dataStoreFactory.CreateDataStore(unimplementedDataStoreType);

// Assert
Assert.Throws<DataStoreTypeNotImplementedException>(() => datastoreDelegate());
}
}
}
54 changes: 54 additions & 0 deletions RateLimiter.Tests/Factories/RateLimitRuleFactoryTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using System;
using NUnit.Framework;
using RateLimiter.Constants;
using RateLimiter.Exceptions;
using RateLimiter.Factories;

namespace RateLimiter.Tests.Factories
{
public class RateLimitRuleFactoryTest
{
[Test]
public void CreateRule_Returns_ExpectedRule()
{
// Arrange
int numRequestsAllowed = 1;
var interval = new TimeSpan(days: 0, hours: 0, minutes: 0, seconds: 0, milliseconds: 10);
var dataStoreFactory = new RateLimitDataStoreFactory();
var ruleFactory = new RateLimitRuleFactory(dataStoreFactory);

// Act
var rateLimitRule = ruleFactory.CreateRule(
RateLimitRuleTypes.RequestsPerTimeSpan,
RateLimitDataStoreTypes.ConcurrentInMemory,
DataStoreKeyTypes.RequestsPerResource,
numRequestsAllowed,
interval);

// Assert
Assert.NotNull(rateLimitRule);
}

[Test]
public void CreateRule_Throws_NotImplementedException()
{
// Arrange
RateLimitRuleTypes unimplementedRuleType = (RateLimitRuleTypes)10000;
int numRequestsAllowed = 1;
var interval = new TimeSpan(days: 0, hours: 0, minutes: 0, seconds: 0, milliseconds: 10);
var dataStoreFactory = new RateLimitDataStoreFactory();
var ruleFactory = new RateLimitRuleFactory(dataStoreFactory);

// Act
var rateLimitRule = () => ruleFactory.CreateRule(
unimplementedRuleType,
RateLimitDataStoreTypes.ConcurrentInMemory,
DataStoreKeyTypes.RequestsPerResource,
numRequestsAllowed,
interval);

// Assert
Assert.Throws<RuleTypeNotImplementedException>(() => rateLimitRule());
}
}
}
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.72" />
<PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.4.2" />
</ItemGroup>
Expand Down
83 changes: 80 additions & 3 deletions RateLimiter.Tests/RateLimiterTest.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,90 @@
using NUnit.Framework;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Moq;
using NUnit.Framework;
using RateLimiter.Models;
using RateLimiter.Rules;
using RateLimiter.Stores;

namespace RateLimiter.Tests;

[TestFixture]
public class RateLimiterTest
{
[Test]
public void Example()
public async Task RateLimiter_AllowsRequest_When_NoRulesAreSet()
{
Assert.That(true, Is.True);
// Arrange
var allowRequestsOnFailure = true;
var resourceId = "/api/resource";
var userId = "user1";
var request = new RequestModel(resourceId, userId, string.Empty, string.Empty, string.Empty);
var rulesetStoreMock = new Mock<IRulesetStore>();
var loggerMock = new Mock<ILogger<RateLimiter>>();
var rateLimiter = new RateLimiter(rulesetStoreMock.Object, loggerMock.Object, allowRequestsOnFailure);

// Act
var allowed = await rateLimiter.IsRequestAllowedAsync(request);

// Assert
Assert.That(allowed, Is.True);
}

[Test]
public async Task RateLimiter_AllowsRequest_When_WithinConfiguredRuleLimits()
{
// Arrange
var allowRequestsOnFailure = true;
var resourceId = "/api/resource";
var userId = "user1";
var request = new RequestModel(resourceId, userId, string.Empty, string.Empty, string.Empty);
var testRule = new Mock<IRateLimitRule>();
var ruleList = new List<IRateLimitRule>()
{
testRule.Object
};
var rulesetStoreMock = new Mock<IRulesetStore>();
var loggerMock = new Mock<ILogger<RateLimiter>>();
var rateLimiter = new RateLimiter(rulesetStoreMock.Object, loggerMock.Object, allowRequestsOnFailure);

testRule.Setup(testRule => testRule.IsRequestAllowedAsync(request)).ReturnsAsync(true);
rulesetStoreMock.Setup(store => store.GetRules(resourceId)).Returns(ruleList);

// Act
var allowed = await rateLimiter.IsRequestAllowedAsync(request);

// Assert
Assert.That(allowed, Is.True);
}

[Test]
public async Task RateLimiter_DeniesRequest_When_ConfiguredRulesFail()
{
// Arrange
var allowRequestsOnFailure = true;
var requestPath = "/api/resource";
var userId = "user1";
var request = new RequestModel(requestPath, userId, string.Empty, string.Empty, string.Empty);
var testRule1 = new Mock<IRateLimitRule>();
var testRule2 = new Mock<IRateLimitRule>();
var ruleList = new List<IRateLimitRule>()
{
testRule1.Object,
testRule2.Object
};
var rulesetStoreMock = new Mock<IRulesetStore>();
var loggerMock = new Mock<ILogger<RateLimiter>>();
var rateLimiter = new RateLimiter(rulesetStoreMock.Object, loggerMock.Object, allowRequestsOnFailure);

testRule1.Setup(testRule => testRule.IsRequestAllowedAsync(request)).ReturnsAsync(true);
testRule2.Setup(testRule => testRule.IsRequestAllowedAsync(request)).ReturnsAsync(false);
rulesetStoreMock.Setup(store => store.GetRules(requestPath)).Returns(ruleList);

// Act
var allowed = await rateLimiter.IsRequestAllowedAsync(request);

// Assert
Assert.That(allowed, Is.False);
}
}
95 changes: 95 additions & 0 deletions RateLimiter.Tests/Rules/RequestPerTimeSpanRuleTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
using System;
using System.Threading.Tasks;
using NUnit.Framework;
using RateLimiter.Constants;
using RateLimiter.Factories;
using RateLimiter.Models;

namespace RateLimiter.Tests.Rules
{
internal class RequestPerTimeSpanRuleTest
{
[Test]
public async Task RequestPerTimeSpanRule_IsWithinLimit_ReturnsTrue_When_LimitIsNotExceeded()
{
// Arrange
var resourceId = "/api/path";
var userId = "user1";
var request = new RequestModel(resourceId, userId, string.Empty, string.Empty, string.Empty);
var numRequestsAllowed = 1;
var interval = new TimeSpan(hours: 0, minutes: 0, seconds: 5);
var dataStoreFactory = new RateLimitDataStoreFactory();
var ruleFactory = new RateLimitRuleFactory(dataStoreFactory);
var rateLimitRule = ruleFactory.CreateRule(
RateLimitRuleTypes.RequestsPerTimeSpan,
RateLimitDataStoreTypes.ConcurrentInMemory,
DataStoreKeyTypes.RequestsPerResource,
numRequestsAllowed,
interval);

// Act
var firstRequestAllowed = await rateLimitRule.IsRequestAllowedAsync(request);

// Assert
Assert.IsTrue(firstRequestAllowed);
}

[Test]
public async Task RequestPerTimeSpanRule_IsWithinLimit_ReturnsFalse_When_LimitIsExceeded()
{
// Arrange
var resourceId = "/api/path";
int numRequestsAllowed = 1;
var request = new RequestModel(resourceId, string.Empty, string.Empty, string.Empty, string.Empty);
var interval = new TimeSpan(days: 0, hours: 0, minutes: 0, seconds: 0, milliseconds: 1);
var dataStoreFactory = new RateLimitDataStoreFactory();
var ruleFactory = new RateLimitRuleFactory(dataStoreFactory);
var rateLimitRule = ruleFactory.CreateRule(
RateLimitRuleTypes.RequestsPerTimeSpan,
RateLimitDataStoreTypes.ConcurrentInMemory,
DataStoreKeyTypes.RequestsPerResource,
numRequestsAllowed,
interval);

// Act
var firstRequestAllowed = await rateLimitRule.IsRequestAllowedAsync(request);
var secondRequestAllowed = await rateLimitRule.IsRequestAllowedAsync(request);

// Assert
Assert.IsTrue(firstRequestAllowed, "Expected first request to be allowed");
Assert.IsFalse(secondRequestAllowed, "Expected second request to be denied");
}


[Test]
public async Task RequestPerTimeSpanRule_IsWithinLimit_MultipleRequests()
{
// Arrange
var delayInMs = 11;
var resourceId = "/api/path";
var request = new RequestModel(resourceId, string.Empty, string.Empty, string.Empty, string.Empty);
int numRequestsAllowed = 1;
var interval = new TimeSpan(days: 0, hours: 0, minutes: 0, seconds: 0, milliseconds: 10);
var dataStoreFactory = new RateLimitDataStoreFactory();
var ruleFactory = new RateLimitRuleFactory(dataStoreFactory);
var rateLimitRule = ruleFactory.CreateRule(
RateLimitRuleTypes.RequestsPerTimeSpan,
RateLimitDataStoreTypes.ConcurrentInMemory,
DataStoreKeyTypes.RequestsPerResource,
numRequestsAllowed,
interval);

// Act
var firstRequestAllowed = await rateLimitRule.IsRequestAllowedAsync(request);
var secondRequestAllowed = await rateLimitRule.IsRequestAllowedAsync(request);
var thirdRequestAllowed = async () => await rateLimitRule.IsRequestAllowedAsync(request);
var fourthRequestAllowed = async () => await rateLimitRule.IsRequestAllowedAsync(request);

// Assert
Assert.IsTrue(firstRequestAllowed, "Expected first request to be allowed");
Assert.IsFalse(secondRequestAllowed, "Expected second request to be denied");
Assert.That(() => thirdRequestAllowed(), Is.True.After(delayInMs), "Expected third request to be allowed");
Assert.That(() => fourthRequestAllowed(), Is.False, "Expected fourth request to be denied");
}
}
}
95 changes: 95 additions & 0 deletions RateLimiter.Tests/Rules/TimeSpanSinceLastRequestRuleTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
using System;
using System.Threading.Tasks;
using NUnit.Framework;
using RateLimiter.Constants;
using RateLimiter.Factories;
using RateLimiter.Models;

namespace RateLimiter.Tests.Rules
{
public class TimeSpanSinceLastRequestRuleTest
{
[Test]
public async Task IsRequestAllowedAsync_ReturnsTrue_When_LimitIsNotExceeded()
{
// Arrange
var resourceId = "/api/path";
var userId = "user1";
var request = new RequestModel(resourceId, userId, string.Empty, string.Empty, string.Empty);
var numRequestsAllowed = 1;
var interval = new TimeSpan(hours: 0, minutes: 0, seconds: 5);
var dataStoreFactory = new RateLimitDataStoreFactory();
var ruleFactory = new RateLimitRuleFactory(dataStoreFactory);
var rateLimitRule = ruleFactory.CreateRule(
RateLimitRuleTypes.TimeSpanSinceLastRequest,
RateLimitDataStoreTypes.ConcurrentInMemory,
DataStoreKeyTypes.RequestsPerResource,
numRequestsAllowed,
interval);

// Act
var firstRequestAllowed = await rateLimitRule.IsRequestAllowedAsync(request);

// Assert
Assert.IsTrue(firstRequestAllowed);
}

[Test]
public async Task IsRequestAllowedAsync_ReturnsFalse_When_LimitIsExceeded()
{
// Arrange
var resourceId = "/api/path";
int numRequestsAllowed = 1;
var request = new RequestModel(resourceId, string.Empty, string.Empty, string.Empty, string.Empty);
var interval = new TimeSpan(days: 0, hours: 0, minutes: 0, seconds: 0, milliseconds: 1);
var dataStoreFactory = new RateLimitDataStoreFactory();
var ruleFactory = new RateLimitRuleFactory(dataStoreFactory);
var rateLimitRule = ruleFactory.CreateRule(
RateLimitRuleTypes.TimeSpanSinceLastRequest,
RateLimitDataStoreTypes.ConcurrentInMemory,
DataStoreKeyTypes.RequestsPerResource,
numRequestsAllowed,
interval);

// Act
var firstRequestAllowed = await rateLimitRule.IsRequestAllowedAsync(request);
var secondRequestAllowed = await rateLimitRule.IsRequestAllowedAsync(request);

// Assert
Assert.IsTrue(firstRequestAllowed, "Expected first request to be allowed");
Assert.IsFalse(secondRequestAllowed, "Expected second request to be denied");
}


[Test]
public async Task IsRequestAllowedAsync_MultipleRequests()
{
// Arrange
var delayInMs = 11;
var resourceId = "/api/path";
var request = new RequestModel(resourceId, string.Empty, string.Empty, string.Empty, string.Empty);
int numRequestsAllowed = 1;
var interval = new TimeSpan(days: 0, hours: 0, minutes: 0, seconds: 0, milliseconds: 10);
var dataStoreFactory = new RateLimitDataStoreFactory();
var ruleFactory = new RateLimitRuleFactory(dataStoreFactory);
var rateLimitRule = ruleFactory.CreateRule(
RateLimitRuleTypes.TimeSpanSinceLastRequest,
RateLimitDataStoreTypes.ConcurrentInMemory,
DataStoreKeyTypes.RequestsPerResource,
numRequestsAllowed,
interval);

// Act
var firstRequestAllowed = await rateLimitRule.IsRequestAllowedAsync(request);
var secondRequestAllowed = await rateLimitRule.IsRequestAllowedAsync(request);
var thirdRequestAllowed = async () => await rateLimitRule.IsRequestAllowedAsync(request);
var fourthRequestAllowed = async () => await rateLimitRule.IsRequestAllowedAsync(request);

// Assert
Assert.IsTrue(firstRequestAllowed, "Expected first request to be allowed");
Assert.IsFalse(secondRequestAllowed, "Expected second request to be denied");
Assert.That(() => thirdRequestAllowed(), Is.True.After(delayInMs), "Expected third request to be allowed");
Assert.That(() => fourthRequestAllowed(), Is.False, "Expected fourth request to be denied");
}
}
}
Loading