Skip to content

Commit

Permalink
Task completed
Browse files Browse the repository at this point in the history
  • Loading branch information
grigorchibukhchyan committed Jun 5, 2024
1 parent 73f3a7c commit 6d3240f
Show file tree
Hide file tree
Showing 14 changed files with 572 additions and 5 deletions.
163 changes: 163 additions & 0 deletions RateLimiter.Tests/RateLimitRuleTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace RateLimiter.Tests
{
[TestFixture]
public class RateLimitRuleTests
{
[Test]
public void RequestCountPerTimespanRule_AllowsRequestsWithinLimit()
{
var rule = new RequestCountPerTimespanRule(5, TimeSpan.FromMinutes(1));

for (int i = 0; i < 5; i++)
{
Assert.IsTrue(rule.AllowRequest("client1"));
}

Assert.IsFalse(rule.AllowRequest("client1"));
}

[Test]
public void CooldownBetweenRequestsRule_AllowsRequestsAfterCooldown()
{
var rule = new CooldownBetweenRequestsRule(TimeSpan.FromSeconds(1));

Assert.IsTrue(rule.AllowRequest("client1"));
Assert.IsFalse(rule.AllowRequest("client1"));

Thread.Sleep(1000);

Assert.IsTrue(rule.AllowRequest("client1"));
}

[Test]
public void GeoBasedRateLimitRule_UsesCorrectRuleBasedOnRegion()
{
var usRule = new RequestCountPerTimespanRule(5, TimeSpan.FromMinutes(1));
var euRule = new CooldownBetweenRequestsRule(TimeSpan.FromSeconds(1));
var geoRule = new GeoBasedRateLimitRule(usRule, euRule, GetClientRegion);

for (int i = 0; i < 5; i++)
{
Assert.IsTrue(geoRule.AllowRequest("us_client"));
}

Assert.IsFalse(geoRule.AllowRequest("us_client"));

Assert.IsTrue(geoRule.AllowRequest("eu_client"));
Assert.IsFalse(geoRule.AllowRequest("eu_client"));
}

[Test]
public void CombinedRateLimitRule_CombinesMultipleRules()
{
var rule1 = new RequestCountPerTimespanRule(5, TimeSpan.FromMinutes(1));
var rule2 = new CooldownBetweenRequestsRule(TimeSpan.FromSeconds(1));
var combinedRule = new CombinedRateLimitRule(new List<RateLimitRule> { rule1, rule2 });

for (int i = 0; i < 5; i++)
{
bool result = combinedRule.AllowRequest("client1");
Assert.IsTrue(result);
Thread.Sleep(1000);
}

Assert.IsFalse(combinedRule.AllowRequest("client1"));
}

[Test]
public void DailyLimitRule_AllowsRequestsWithinDailyLimit()
{
var rule = new DailyLimitRule(10);

for (int i = 0; i < 10; i++)
{
Assert.IsTrue(rule.AllowRequest("client1"));
}

Assert.IsFalse(rule.AllowRequest("client1"));
}

[Test]
public void BurstLimitRule_AllowsRequestsWithinBurstLimit()
{
var rule = new BurstLimitRule(5, TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(10));

for (int i = 0; i < 5; i++)
{
Assert.IsTrue(rule.AllowRequest("client1"));
}

Assert.IsFalse(rule.AllowRequest("client1"));
}

[Test]
public void TimeOfDayLimitRule_AllowsRequestsWithinTimeOfDay()
{
var now = DateTime.UtcNow;
var startTime = now.TimeOfDay - TimeSpan.FromHours(1);
var endTime = now.TimeOfDay + TimeSpan.FromHours(1);

var rule = new TimeOfDayLimitRule(startTime, endTime);

Assert.IsTrue(rule.AllowRequest("client1"));
}

[Test]
public void UserLevelRateLimitRule_AllowsRequestsBasedOnUserLevel()
{
var rule = new UserLevelRateLimitRule(GetUserLevel);
rule.ConfigureUserLevel("free", new RequestCountPerTimespanRule(5, TimeSpan.FromMinutes(1)));
rule.ConfigureUserLevel("premium", new RequestCountPerTimespanRule(50, TimeSpan.FromMinutes(1)));

for (int i = 0; i < 5; i++)
{
Assert.IsTrue(rule.AllowRequest("free_user"));
}

Assert.IsFalse(rule.AllowRequest("free_user"));

for (int i = 0; i < 50; i++)
{
Assert.IsTrue(rule.AllowRequest("premium_user"));
}

Assert.IsFalse(rule.AllowRequest("premium_user"));
}

[Test]
public void IPAddressRateLimitRule_AllowsRequestsBasedOnIPAddress()
{
var rule = new IPAddressRateLimitRule(5, TimeSpan.FromMinutes(1), GetClientIPAddress);

for (int i = 0; i < 5; i++)
{
Assert.IsTrue(rule.AllowRequest("client1"));
}

Assert.IsFalse(rule.AllowRequest("client1"));
}

private string GetClientRegion(string clientId)
{
return clientId.StartsWith("us") ? "US" : "EU";
}

private string GetUserLevel(string clientId)
{
return clientId.StartsWith("free") ? "free" : "premium";
}

private string GetClientIPAddress(string clientId)
{
return clientId.StartsWith("client") ? "192.168.0.1" : "10.0.0.1";
}
}
}
55 changes: 50 additions & 5 deletions RateLimiter.Tests/RateLimiterTest.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,58 @@
using NUnit.Framework;
using System;
using System.Threading;

namespace RateLimiter.Tests;

[TestFixture]
public class RateLimiterTest
{
[Test]
public void Example()
{
Assert.That(true, Is.True);
}
[Test]
public void RateLimiter_AllowsRequestsWithinConfiguredLimits()
{
var rateLimiter = new RateLimiter();

var dailyRule = new DailyLimitRule(100);
var burstRule = new BurstLimitRule(10, TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(10));
var timeOfDayRule = new TimeOfDayLimitRule(TimeSpan.FromHours(9), TimeSpan.FromHours(20));
var userLevelRule = new UserLevelRateLimitRule(GetUserLevel);
var ipAddressRule = new IPAddressRateLimitRule(20, TimeSpan.FromMinutes(10), GetClientIPAddress);

userLevelRule.ConfigureUserLevel("free", new RequestCountPerTimespanRule(5, TimeSpan.FromMinutes(1)));
userLevelRule.ConfigureUserLevel("premium", new RequestCountPerTimespanRule(50, TimeSpan.FromMinutes(1)));

rateLimiter.ConfigureResource("api/resource1", dailyRule);
rateLimiter.ConfigureResource("api/resource2", burstRule);
rateLimiter.ConfigureResource("api/resource3", timeOfDayRule);
rateLimiter.ConfigureResource("api/resource4", userLevelRule);
rateLimiter.ConfigureResource("api/resource5", ipAddressRule);

var clientId = "premium_customer_1";

for (int i = 0; i < 10; i++)
{
Assert.IsTrue(rateLimiter.AllowRequest("api/resource1", clientId), $"Request to resource1 at iteration {i} should be allowed.");
Assert.IsTrue(rateLimiter.AllowRequest("api/resource2", clientId), $"Request to resource2 at iteration {i} should be allowed.");
Assert.IsTrue(rateLimiter.AllowRequest("api/resource3", clientId), $"Request to resource3 at iteration {i} should be allowed.");
Assert.IsTrue(rateLimiter.AllowRequest("api/resource4", clientId), $"Request to resource4 at iteration {i} should be allowed.");
Assert.IsTrue(rateLimiter.AllowRequest("api/resource5", clientId), $"Request to resource5 at iteration {i} should be allowed.");
}

for (int i = 0; i < 90; i++)
{
rateLimiter.AllowRequest("api/resource1", clientId);
}
bool finalRequestResult = rateLimiter.AllowRequest("api/resource1", clientId);
Assert.IsFalse(finalRequestResult, "Request to resource1 after 100 requests should be denied.");
}

private string GetUserLevel(string clientId)
{
return clientId.StartsWith("premium") ? "premium" : "free";
}

private string GetClientIPAddress(string clientId)
{
return clientId.StartsWith("client") ? "192.168.0.1" : "10.0.0.1";
}
}
50 changes: 50 additions & 0 deletions RateLimiter/BurstLimitRule.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace RateLimiter
{
public class BurstLimitRule : RateLimitRule
{
private readonly int _burstLimit;
private readonly TimeSpan _burstPeriod;
private readonly TimeSpan _cooldown;
private readonly ConcurrentDictionary<string, List<DateTime>> _requests = new();
private readonly ConcurrentDictionary<string, DateTime> _cooldowns = new();

public BurstLimitRule(int burstLimit, TimeSpan burstPeriod, TimeSpan cooldown)
{
_burstLimit = burstLimit;
_burstPeriod = burstPeriod;
_cooldown = cooldown;
}

public override bool AllowRequest(string clientId)
{
var now = DateTime.UtcNow;

if (_cooldowns.TryGetValue(clientId, out var cooldownEnd) && now < cooldownEnd)
{
return false;
}

_requests.AddOrUpdate(clientId, new List<DateTime> { now }, (key, list) =>
{
list.Add(now);
list.RemoveAll(t => t < now - _burstPeriod);
return list;
});

if (_requests[clientId].Count > _burstLimit)
{
_cooldowns[clientId] = now + _cooldown;
return false;
}

return true;
}
}
}
24 changes: 24 additions & 0 deletions RateLimiter/CombinedRateLimitRule.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace RateLimiter
{
public class CombinedRateLimitRule : RateLimitRule
{
private readonly List<RateLimitRule> _rules;

public CombinedRateLimitRule(List<RateLimitRule> rules)
{
_rules = rules;
}

public override bool AllowRequest(string clientId)
{
return _rules.All(rule => rule.AllowRequest(clientId));
}
}

}
33 changes: 33 additions & 0 deletions RateLimiter/CooldownBetweenRequestsRule.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace RateLimiter
{
public class CooldownBetweenRequestsRule : RateLimitRule
{
private readonly TimeSpan _cooldown;
private readonly ConcurrentDictionary<string, DateTime> _lastRequestTime = new();

public CooldownBetweenRequestsRule(TimeSpan cooldown)
{
_cooldown = cooldown;
}

public override bool AllowRequest(string clientId)
{
var now = DateTime.UtcNow;
if (_lastRequestTime.TryGetValue(clientId, out var lastRequest) && now - lastRequest < _cooldown)
{
return false;
}

_lastRequestTime[clientId] = now;
return true;
}
}

}
35 changes: 35 additions & 0 deletions RateLimiter/DailyLimitRule.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace RateLimiter
{
public class DailyLimitRule : RateLimitRule
{
private readonly int _dailyLimit;
private readonly ConcurrentDictionary<string, List<DateTime>> _requests = new();

public DailyLimitRule(int dailyLimit)
{
_dailyLimit = dailyLimit;
}

public override bool AllowRequest(string clientId)
{
var now = DateTime.UtcNow;
var today = now.Date;
_requests.AddOrUpdate(clientId, new List<DateTime> { now }, (key, list) =>
{
list.Add(now);
list.RemoveAll(t => t.Date < today);
return list;
});

return _requests[clientId].Count <= _dailyLimit;
}
}

}
Loading

0 comments on commit 6d3240f

Please sign in to comment.