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
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using RateLimiter.Rules.Interfaces;
using System.Collections.Generic;
using System.Threading.Tasks;

public class InMemoryConfigurationStorage : IConfigurationStorageProvider<IRateLimiterRule>
{
private readonly Dictionary<string, List<IRateLimiterRule>> _rulesConfig;

public InMemoryConfigurationStorage()
{
_rulesConfig = new Dictionary<string, List<IRateLimiterRule>>();
}

public async Task<List<IRateLimiterRule>?> LoadAsync(string endpoint)
{
_rulesConfig.TryGetValue(endpoint, out var rules);

return await Task.FromResult(rules ?? new List<IRateLimiterRule>());
}

public async Task<bool> SaveAsync(string key, List<IRateLimiterRule> rules)
{
_rulesConfig[key] = rules;

return await Task.FromResult(true);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Moq;
using RateLimiter.ConfigurationStorageProvider;
using RateLimiter.Core;
using RateLimiter.Rules.Interfaces;
using RateLimiter.Rules.Implementations.RequestPerPeriodRule;
using System.Collections.Generic;
using System;
using System.Threading.Tasks;
using Xunit;


namespace RateLimiter.IntegrationTests.Rules.RequestPerPeriodRule;

public class RequestPerPeriodRuleTests
{

private readonly int NUMBER_OF_REQUESTS_ENDPOINT_A = 2;
private readonly int PERIOD_ENDPOINT_A = 10; // seconds
private readonly int NUMBER_OF_REQUESTS_ENDPOINT_B = 2;
private readonly int PERIOD_ENDPOINT_B = 60; // seconds
private readonly Mock<ILogger<RateLimiterService>> _logger;
private readonly InMemoryConfigurationStorage _memoryStorage;
private readonly ConfigurationProvider _configurationProvider;

public RequestPerPeriodRuleTests()
{

_logger = new Mock<ILogger<RateLimiterService>>();

_memoryStorage = new InMemoryConfigurationStorage();
_configurationProvider = new ConfigurationProvider(_memoryStorage);

SetupConfig();

}

private async void SetupConfig()
{
var ruleRepository = new InMemoryRequestPerPeriodRuleRepository();

// Save configuration
await _configurationProvider.SaveConfigAsync("/api/resourceA", new List<IRateLimiterRule> {
new RequestsPerPeriodRule(NUMBER_OF_REQUESTS_ENDPOINT_A, TimeSpan.FromSeconds(PERIOD_ENDPOINT_A), ruleRepository)
});

await _configurationProvider.SaveConfigAsync("/api/resourceB", new List<IRateLimiterRule> {
new RequestsPerPeriodRule(NUMBER_OF_REQUESTS_ENDPOINT_B, TimeSpan.FromSeconds(PERIOD_ENDPOINT_B), ruleRepository)
});
}

private DefaultHttpContext CreateHttpContext(string token, string path)
{
var context = new DefaultHttpContext();
context.Request.Headers["Authorization"] = $"Bearer {token}";
context.Request.Path = path;
return context;
}

private RateLimiterService CreateRateLimiterService()
{
return new RateLimiterService(_memoryStorage, _logger.Object);
}

[Fact]
public async Task InvokeAsync_EndPointA_RequestAllowed_ReturnsTrue()
{
// Arrange
var context = CreateHttpContext("test-token", "/api/resourceA");

await Task.Delay((PERIOD_ENDPOINT_A * 1000) + 1); // Simulate async operation

var RateLimiterService = CreateRateLimiterService();

// Act
var result = await RateLimiterService.InvokeAsync(context);

// Assert
Assert.True(result);
}

[Fact]
public async Task InvokeAsync_EndPointA_RequestNotAllowed_ReturnsFalse()
{
// Arrange
var context = CreateHttpContext("test-token", "/api/resourceA");

var RateLimiterService = CreateRateLimiterService();

// Simulate a request to set the last call time
await RateLimiterService.InvokeAsync(context);

await Task.Delay(1); // Simulate async operation

// Act
var result = await RateLimiterService.InvokeAsync(context);

// Assert
Assert.False(result);
}

[Fact]
public async Task InvokeAsync_EndPointB_RequestAllowed_ReturnsTrue()
{
// Arrange
var context = CreateHttpContext("test-token", "/api/resourceB");
var RateLimiterService = CreateRateLimiterService();

// Simulate a request to set the last call time
for (int i = 1; i < NUMBER_OF_REQUESTS_ENDPOINT_B -1; i++)
{
await RateLimiterService.InvokeAsync(context);
}
// Act
var result = await RateLimiterService.InvokeAsync(context);

// Assert
Assert.True(result);
}

[Fact]
public async Task InvokeAsync_EndPointB_RequestNotAllowed_ReturnsFalse()
{
// Arrange
var context = CreateHttpContext("test-token", "/api/resourceB");
var RateLimiterService = CreateRateLimiterService();

// Simulate a request to set the last call time
for (int i = 1; i < NUMBER_OF_REQUESTS_ENDPOINT_B + 10; i++)
{
await RateLimiterService.InvokeAsync(context);
}

// Act
var result = await RateLimiterService.InvokeAsync(context);

// Assert
Assert.False(result);
}

[Fact]
public async Task InvokeAsync_NoRulesDefined_ReturnsTrue()
{
// Arrange
var context = CreateHttpContext("test-token", "/api/unknown");
var RateLimiterService = CreateRateLimiterService();

// Act
var result = await RateLimiterService.InvokeAsync(context);

// Assert
Assert.True(result);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using RateLimiter.Rules.Implementations.RequestPerPeriodRule;
using System;
using System.Collections.Concurrent;

namespace RateLimiter.IntegrationTests.Rules.RequestPerPeriodRule
{
public class InMemoryRequestPerPeriodRuleRepository : IRequestPerPeriodRuleRepository
{
private readonly ConcurrentDictionary<string, (DateTime start, int count)> _requests = new();

public (DateTime start, int count) GetOrAdd(string key, DateTime start, int counter)
{
return _requests.GetOrAdd(key, (start, counter));
}

public void Update(string key, DateTime start, int counter)
{
_requests[key] = (start, counter);
}
}

}
10 changes: 8 additions & 2 deletions RateLimiter.Tests/RateLimiter.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,14 @@
<ProjectReference Include="..\RateLimiter\RateLimiter.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.3.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.3" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
<PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.4.2" />
<PackageReference Include="Moq" Version="4.20.72" />
<PackageReference Include="xunit" Version="2.5.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>
13 changes: 0 additions & 13 deletions RateLimiter.Tests/RateLimiterTest.cs

This file was deleted.

Loading