Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add rate limiter with dependencies. Add tests #194

Closed
wants to merge 1 commit into from
Closed
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
74 changes: 74 additions & 0 deletions RateLimiter.Tests/FixedWindowLimiterTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using NUnit.Framework;
using RateLimiter.RateLimiter;
using RateLimiter.RateLimiter.Models;
using RateLimiter.RateLimiter.Options;
using System;

namespace RateLimiter.Tests;

[TestFixture]
public class FixedWindowLimiterTest
{
private FixedWindowLimiter _subject;
private FixedWindowLimiterOptions _options;

[SetUp]
public void Setup()
{
_options = new FixedWindowLimiterOptions
{
Limit = 2,
Window = TimeSpan.FromSeconds(3),
};

_subject = new FixedWindowLimiter(_options);
}

[Test]
public void CheckLimit_LimitIsNotReached_ShouldAllowRequest()
{
var clientRequest = new ClientRequest
{
LastHitAt = DateTime.MinValue.ToUniversalTime(),
AmountOfHits = 0,
};

var result = _subject.CheckLimit(clientRequest);

Assert.That(result.Limited, Is.False);
Assert.That(result.CurrentLimit, Is.EqualTo(_options.Limit));
Assert.That(result.RemainingAmountOfCalls, Is.EqualTo(1));
}

[Test]
public void CheckLimit_LimitIsReached_ShouldNotAllowRequest()
{
var clientRequest = new ClientRequest
{
LastHitAt = DateTime.UtcNow,
AmountOfHits = 2,
};

var result = _subject.CheckLimit(clientRequest);

Assert.That(result.Limited, Is.True);
Assert.That(result.CurrentLimit, Is.EqualTo(_options.Limit));
Assert.That(result.RemainingAmountOfCalls, Is.EqualTo(0));
}

[Test]
public void CheckLimit_TimeWindowIsExpired_ShouldAllowRequest()
{
var clientRequest = new ClientRequest
{
LastHitAt = DateTime.UtcNow.AddSeconds(-10),
AmountOfHits = 2,
};

var result = _subject.CheckLimit(clientRequest);

Assert.That(result.Limited, Is.False);
Assert.That(result.CurrentLimit, Is.EqualTo(_options.Limit));
Assert.That(result.RemainingAmountOfCalls, Is.EqualTo(1));
}
}
52 changes: 52 additions & 0 deletions RateLimiter.Tests/RateLimitProcessorTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
using Moq;
using NUnit.Framework;
using RateLimiter.RateLimiter;
using RateLimiter.RateLimiter.Models;
using System.Collections.Generic;

namespace RateLimiter.Tests
{
public class RateLimitProcessorTest
{
private const string DefaultPolicyName = "testPolicy";
private const Region DefaultRegion = Region.EU;

private RateLimitProcessor _subject;

[Test]
public void VerifyRequest_LimiterExistsForPolicyAndLimitNotExceeded_ShouldAllowRequest()
{
var limiter = new Mock<ILimiter>();
limiter
.Setup(x => x.CheckLimit(It.IsAny<ClientRequest>()))
.Returns(new LimitResult { Limited = false, CurrentLimit = 1, RemainingAmountOfCalls = 1 });

var data = new Dictionary<(string, Region), ILimiter>()
{
{ (DefaultPolicyName, DefaultRegion), limiter.Object }
};

_subject = new RateLimitProcessor(data);

var result = _subject.VerifyRequest("token", DefaultRegion, "resource", [DefaultPolicyName]);

Assert.That(result.Limited, Is.False);
Assert.That(result.CurrentLimit, Is.EqualTo(1));
Assert.That(result.RemainingAmountOfCalls, Is.EqualTo(1));
}

[Test]
public void VerifyRequest_LimiterNotExistForPolicy_ShouldAllowRequest()
{
var data = new Dictionary<(string, Region), ILimiter>();

_subject = new RateLimitProcessor(data);

var result = _subject.VerifyRequest("token", DefaultRegion, "resource", [DefaultPolicyName]);

Assert.That(result.Limited, Is.False);
Assert.That(result.CurrentLimit, Is.EqualTo(0));
Assert.That(result.RemainingAmountOfCalls, Is.EqualTo(0));
}
}
}
5 changes: 4 additions & 1 deletion RateLimiter.Tests/RateLimiter.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<Nullable>disable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\RateLimiter\RateLimiter.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="6.0.31" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
<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
13 changes: 0 additions & 13 deletions RateLimiter.Tests/RateLimiterTest.cs

This file was deleted.

116 changes: 116 additions & 0 deletions RateLimiter.Tests/RateLimitingMiddlewareTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using NUnit.Framework;
using RateLimiter.Middleware;
using RateLimiter.RateLimiter.Models;
using System;
using System.Threading.Tasks;

namespace RateLimiter.Tests
{
public class RateLimitingMiddlewareTest
{
[Test]
public async Task FixedWindowRateLimiter_TwoRequests_ShouldLimitSecondRequest()
{
using var host = await new HostBuilder()
.ConfigureWebHost(webBuilder =>
{
webBuilder
.UseTestServer()
.ConfigureServices(services =>
{
services.AddRouting();
services.AddFixedWindowRateLimiter(
policy: "policy1",
region: Region.EU,
configureOptions: x =>
{
x.Window = TimeSpan.FromSeconds(3);
x.Limit = 1;
});

services.AddRateLimiter();
})
.Configure(app =>
{
app.UseRouting();
app.UseMiddleware<RateLimitingMiddleware>();
app.UseEndpoints(endpoints =>
{
endpoints
.MapGet("/test", () => "Test responce")
.RequireRateLimiting("policy1", Region.EU);
});

app.UseRateLimiter();
});
})
.StartAsync();

var client = host.GetTestClient();

client.DefaultRequestHeaders.Add("Authorization", "test-token");
client.DefaultRequestHeaders.Add("Region", "EU");

var response1 = await client.GetAsync("/test");
var response2 = await client.GetAsync("/test");

Assert.True(response1.IsSuccessStatusCode);
Assert.True(response2.StatusCode == System.Net.HttpStatusCode.TooManyRequests);
}

[Test]
public async Task TimeSpanRateLimiter_TwoRequests_ShouldLimitSecondRequest()
{
using var host = await new HostBuilder()
.ConfigureWebHost(webBuilder =>
{
webBuilder
.UseTestServer()
.ConfigureServices(services =>
{
services.AddRouting();

services.AddTimespanRateLimiter(
policy: "policy2",
region: Region.US,
configureOptions: x =>
{
x.Window = TimeSpan.FromSeconds(3);
});

services.AddRateLimiter();
})
.Configure(app =>
{
app.UseRouting();
app.UseMiddleware<RateLimitingMiddleware>();
app.UseEndpoints(endpoints =>
{
endpoints
.MapGet("/test", () => "Test responce")
.RequireRateLimiting("policy2", Region.US);
});

app.UseRateLimiter();
});
})
.StartAsync();

var client = host.GetTestClient();

client.DefaultRequestHeaders.Add("Authorization", "test-token");
client.DefaultRequestHeaders.Add("Region", "EU");

var response1 = await client.GetAsync("/test");
var response2 = await client.GetAsync("/test");

Assert.True(response1.IsSuccessStatusCode);
Assert.True(response2.StatusCode == System.Net.HttpStatusCode.TooManyRequests);
}
}
}
60 changes: 60 additions & 0 deletions RateLimiter.Tests/TimespanLimiterTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
using NUnit.Framework;
using RateLimiter.RateLimiter.Options;
using RateLimiter.RateLimiter;
using System;
using RateLimiter.RateLimiter.Models;

namespace RateLimiter.Tests
{
public class TimespanLimiterTest
{
private TimespanLimiter _subject;
private TimespanLimiterOptions _options;

[SetUp]
public void Setup()
{
_options = new TimespanLimiterOptions
{
Window = TimeSpan.FromSeconds(3),
};

_subject = new TimespanLimiter(_options);
}

[Test]
public void CheckLimit_FirstRequest_ShouldAllowRequest()
{
var clientRequest = new ClientRequest
{
LastHitAt = DateTime.MinValue.ToUniversalTime(),
AmountOfHits = 0,
};

var result = _subject.CheckLimit(clientRequest);

Assert.That(result.Limited, Is.False);
Assert.That(result.CurrentLimit, Is.EqualTo(_options.Limit));
Assert.That(result.RemainingAmountOfCalls, Is.EqualTo(0));
Assert.That(result.RetryAfterSeconds, Is.EqualTo(_options.Window.TotalSeconds));
}

[Test]
public void CheckLimit_SecondRequest_ShouldNotAllowRequest()
{
var lastRequestDelay = 1;
var clientRequest = new ClientRequest
{
LastHitAt = DateTime.UtcNow.AddSeconds(-lastRequestDelay),
AmountOfHits = 0,
};

var result = _subject.CheckLimit(clientRequest);

Assert.That(result.Limited, Is.True);
Assert.That(result.CurrentLimit, Is.EqualTo(_options.Limit));
Assert.That(result.RemainingAmountOfCalls, Is.EqualTo(0));
Assert.That(result.RetryAfterSeconds, Is.EqualTo(_options.Window.TotalSeconds - lastRequestDelay));
}
}
}
9 changes: 5 additions & 4 deletions RateLimiter.sln
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26730.15
# Visual Studio Version 17
VisualStudioVersion = 17.10.34928.147
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RateLimiter", "RateLimiter\RateLimiter.csproj", "{36F4BDC6-D3DA-403A-8DB7-0C79F94B938F}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RateLimiter", "RateLimiter\RateLimiter.csproj", "{36F4BDC6-D3DA-403A-8DB7-0C79F94B938F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RateLimiter.Tests", "RateLimiter.Tests\RateLimiter.Tests.csproj", "{C4F9249B-010E-46BE-94B8-DD20D82F1E60}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RateLimiter.Tests", "RateLimiter.Tests\RateLimiter.Tests.csproj", "{C4F9249B-010E-46BE-94B8-DD20D82F1E60}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{9B206889-9841-4B5E-B79B-D5B2610CCCFF}"
ProjectSection(SolutionItems) = preProject
README.md = README.md
ReadMe_MA.md = ReadMe_MA.md
EndProjectSection
EndProject
Global
Expand Down
Loading
Loading