Skip to content

Commit efa6c64

Browse files
committed
add tests
1 parent 62413c9 commit efa6c64

File tree

2 files changed

+210
-13
lines changed

2 files changed

+210
-13
lines changed

RateLimiter.Tests/RateLimiter.Tests.csproj

+6-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@
99
</ItemGroup>
1010
<ItemGroup>
1111
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
12-
<PackageReference Include="NUnit" Version="3.13.3" />
13-
<PackageReference Include="NUnit3TestAdapter" Version="4.4.2" />
12+
<PackageReference Include="Moq" Version="4.20.72" />
13+
<PackageReference Include="xunit" Version="2.9.3" />
14+
<PackageReference Include="xunit.runner.visualstudio" Version="3.0.1">
15+
<PrivateAssets>all</PrivateAssets>
16+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
17+
</PackageReference>
1418
</ItemGroup>
1519
</Project>

RateLimiter.Tests/RateLimiterTest.cs

+204-11
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,206 @@
1-
using NUnit.Framework;
1+
using Microsoft.AspNetCore.Http;
2+
using Moq;
3+
using RateLimiter.Rules;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
using Xunit;
7+
using Microsoft.Extensions.Caching.Distributed;
8+
using System.Net;
9+
using RateLimiter.Configuration;
10+
using System.Collections.Generic;
11+
using RateLimiter.Policy;
12+
using System.Linq;
213

3-
namespace RateLimiter.Tests;
4-
5-
[TestFixture]
6-
public class RateLimiterTest
14+
namespace RateLimiter.Tests
715
{
8-
[Test]
9-
public void Example()
10-
{
11-
Assert.That(true, Is.True);
12-
}
13-
}
16+
public class RateLimiterTest
17+
{
18+
private readonly Mock<IDistributedCache> _cacheMock;
19+
20+
public RateLimiterTest()
21+
{
22+
_cacheMock = new Mock<IDistributedCache>();
23+
}
24+
25+
#region Combined
26+
27+
[Fact]
28+
public async Task EvaluateAll_ShouldAllow_WhenAllUnderLimit()
29+
{
30+
// Arrange
31+
List<string> configIpBlocked = ["192.168.1.1", "192.168.1.2"];
32+
FixedWindowConfig configFixed = new(5, 10);
33+
string current = "2";
34+
_cacheMock.Setup(c => c.GetAsync(It.IsAny<string>(), default))
35+
.ReturnsAsync(Encoding.UTF8.GetBytes(current));
36+
37+
var ruleFixed = new FixedWindowRule(_cacheMock.Object, configFixed.Limit, configFixed.Seconds);
38+
var ruleIp = new IpBlacklistRule(configIpBlocked);
39+
var policy = new RateLimitPolicy();
40+
policy.AddRule(ruleFixed);
41+
policy.AddRule(ruleIp);
42+
43+
var httpContext = GetHttpContext();
44+
45+
// Act
46+
bool result = await policy.EvaluateAllAsync(httpContext);
47+
48+
// Assert
49+
Assert.True(result);
50+
}
51+
52+
[Fact]
53+
public async Task EvaluateAll_ShouldNotAllow_BlockedIp()
54+
{
55+
// Arrange
56+
List<string> configIpBlocked = ["192.168.1.101"];
57+
FixedWindowConfig configFixed = new(5, 10);
58+
string current = "2";
59+
_cacheMock.Setup(c => c.GetAsync(It.IsAny<string>(), default))
60+
.ReturnsAsync(Encoding.UTF8.GetBytes(current));
61+
62+
var ruleFixed = new FixedWindowRule(_cacheMock.Object, configFixed.Limit, configFixed.Seconds);
63+
var ruleIp = new IpBlacklistRule(configIpBlocked);
64+
var policy = new RateLimitPolicy();
65+
policy.AddRule(ruleFixed);
66+
policy.AddRule(ruleIp);
67+
68+
var httpContext = GetHttpContext(configIpBlocked.First());
69+
70+
// Act
71+
bool result = await policy.EvaluateAllAsync(httpContext);
72+
73+
// Assert
74+
Assert.False(result);
75+
}
76+
77+
#endregion
78+
79+
#region Fixed
80+
81+
[Fact]
82+
public async Task EvaluateFixed_ShouldAllow_WhenUnderLimit()
83+
{
84+
// Arrange
85+
FixedWindowConfig config = new(5, 10);
86+
string current = "2";
87+
_cacheMock.Setup(c => c.GetAsync(It.IsAny<string>(), default))
88+
.ReturnsAsync(Encoding.UTF8.GetBytes(current));
89+
90+
var rule = new FixedWindowRule(_cacheMock.Object, config.Limit, config.Seconds);
91+
var httpContext = GetHttpContext();
92+
93+
// Act
94+
bool result = await rule.EvaluateAsync(httpContext);
95+
96+
// Assert
97+
Assert.True(result);
98+
}
99+
100+
[Fact]
101+
public async Task EvaluateFixed_ShouldNotAllow_WhenOverLimit()
102+
{
103+
// Arrange
104+
FixedWindowConfig config = new(2, 10);
105+
string current = "2";
106+
_cacheMock.Setup(c => c.GetAsync(It.IsAny<string>(), default))
107+
.ReturnsAsync(Encoding.UTF8.GetBytes(current));
108+
109+
var rule = new FixedWindowRule(_cacheMock.Object, config.Limit, config.Seconds);
110+
var httpContext = GetHttpContext();
111+
112+
// Act
113+
bool result = await rule.EvaluateAsync(httpContext);
114+
115+
// Assert
116+
Assert.False(result);
117+
}
118+
119+
[Fact]
120+
public async Task EvaluateFixed_ShouldAllow_WhenCacheEmpty()
121+
{
122+
// Arrange
123+
FixedWindowConfig config = new(5, 10);
124+
_cacheMock.Setup(c => c.GetAsync(It.IsAny<string>(), default))
125+
.ReturnsAsync(null as byte[]);
126+
127+
var rule = new FixedWindowRule(_cacheMock.Object, config.Limit, config.Seconds);
128+
var httpContext = GetHttpContext();
129+
130+
// Act
131+
bool result = await rule.EvaluateAsync(httpContext);
132+
133+
// Assert
134+
Assert.True(result); // First request should pass
135+
}
136+
137+
[Fact]
138+
public async Task EvaluateFixed_ShouldUpdateCache_WhenRequestMade()
139+
{
140+
// Arrange
141+
FixedWindowConfig config = new(5, 10);
142+
_cacheMock.Setup(c => c.GetAsync(It.IsAny<string>(), default))
143+
.ReturnsAsync(Encoding.UTF8.GetBytes("2")); // Simulate 2 requests so far
144+
145+
var rule = new FixedWindowRule(_cacheMock.Object, config.Limit, config.Seconds);
146+
var httpContext = GetHttpContext();
147+
148+
// Act
149+
bool result = await rule.EvaluateAsync(httpContext);
150+
151+
// Assert
152+
Assert.True(result); // Request should pass
153+
_cacheMock.Verify(c => c.SetAsync(It.IsAny<string>(), It.IsAny<byte[]>(), It.IsAny<DistributedCacheEntryOptions>(), default), Times.Once);
154+
}
155+
156+
#endregion
157+
158+
#region Geo
159+
160+
[Fact]
161+
public async Task EvaluateGeo_ShouldAllow_WhenUnderLimit()
162+
{
163+
// Arrange
164+
GeoBasedConfig config = new(Country.EU, 10);
165+
_cacheMock.Setup(c => c.GetAsync(It.IsAny<string>(), default))
166+
.ReturnsAsync(null as byte[]);
167+
168+
var rule = new GeoBasedRule(_cacheMock.Object, [config]);
169+
var httpContext = GetHttpContext();
170+
171+
// Act
172+
bool result = await rule.EvaluateAsync(httpContext);
173+
174+
// Assert
175+
Assert.True(result);
176+
}
177+
178+
[Fact]
179+
public async Task EvaluateGeo_ShouldNotAllow_WhenOverLimit()
180+
{
181+
// Arrange
182+
GeoBasedConfig config = new(Country.EU, 10);
183+
string current = "0";
184+
_cacheMock.Setup(c => c.GetAsync(It.IsAny<string>(), default))
185+
.ReturnsAsync(Encoding.UTF8.GetBytes(current));
186+
187+
var rule = new GeoBasedRule(_cacheMock.Object, [config]);
188+
var httpContext = GetHttpContext();
189+
190+
// Act
191+
bool result = await rule.EvaluateAsync(httpContext);
192+
193+
// Assert
194+
Assert.False(result);
195+
}
196+
197+
#endregion
198+
199+
private static DefaultHttpContext GetHttpContext(string ip = "192.168.1.100")
200+
{
201+
var httpContext = new DefaultHttpContext();
202+
httpContext.Connection.RemoteIpAddress = IPAddress.Parse(ip);
203+
return httpContext;
204+
}
205+
}
206+
}

0 commit comments

Comments
 (0)