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

Giorgi Merebashvili #268

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
275 changes: 269 additions & 6 deletions RateLimiter.Tests/RateLimiterTest.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,276 @@
using NUnit.Framework;
using System;
using System.Threading;
using RateLimiter.DataStore;
using System.Collections.Generic;
using RateLimiter.Ruls.Abstract;
using RateLimiter.Ruls;
using RateLimiter.User;

namespace RateLimiter.Tests;

[TestFixture]
public class RateLimiterTest
{
[Test]
public void Example()
{
Assert.That(true, Is.True);
}
{
[Test]
public void IsAllowedWithNoRules()
{
Cashing.Clear();
// Arrange
var rules = new RateLimiterRuleDecorator[] { };

var userData = new UserData() { CountryCode = "US", Token = "tempToken", IpAddress = "192.168.18.22" };

// Act
var result = new ConcreteRateLimiter(rules).IsAllowed(userData);

// Assert
Assert.IsTrue(result);
}
[Test]
public void IsAllowedWithAllRulesPositive()
{
Cashing.Clear();
Dictionary<string, int> restrictionsByCountry = new()
{
{ "US", 2 },
{ "GE", 1 }
};

// Arrange
var rules = new RateLimiterRuleDecorator[] {
new IpWhiteListRule(new string[] { "192.168.18.22", "192.168.18.23" }),
new RequestMinAllowedTimeRule(TimeSpan.FromSeconds(1)),
new IpBlackListRule(new string[] { "192.168.18.48", "192.168.18.49" }),
new MaxRequestAmountInTimeSpanRule(TimeSpan.FromSeconds(1),5),
new MaxRequestAmountInTimeSpanByCountryRule(TimeSpan.FromSeconds(1), restrictionsByCountry, 2),
};

var userData = new UserData() { CountryCode = "US",Token="tempToken",IpAddress = "192.168.18.22" };

// Act
var result = new ConcreteRateLimiter(rules).IsAllowed(userData);

// Assert
Assert.IsTrue(result);
}
[Test]
public void IpWhtieListRuleNegative()
{
Cashing.Clear();
// Arrange
var rules = new RateLimiterRuleDecorator[] { new IpWhiteListRule(new string[] { "192.168.18.22", "192.168.18.23" }) };

var userData = new UserData() { CountryCode = "US", Token = "tempToken", IpAddress = "192.168.18.30" };

// Act
var result = new ConcreteRateLimiter(rules).IsAllowed(userData);


// Assert
Assert.IsFalse(result);
}
[Test]
public void IpBlackListRulePositive()
{
Cashing.Clear();
// Arrange
var rules = new RateLimiterRuleDecorator[] { new IpBlackListRule(new string[] { "192.168.18.22", "192.168.18.23" }) };

var userData = new UserData() { CountryCode = "US", Token = "tempToken", IpAddress = "192.168.18.30" };

// Act
var result = new ConcreteRateLimiter(rules).IsAllowed(userData);

// Assert
Assert.IsTrue(result);
}
[Test]
public void IpBlackListRuleNegative()
{
Cashing.Clear();
// Arrange
var rules = new RateLimiterRuleDecorator[] { new IpBlackListRule(new string[] { "192.168.18.22", "192.168.18.23" }) };

var userData = new UserData() { CountryCode = "US", Token = "tempToken", IpAddress = "192.168.18.22" };

// Act
var result = new ConcreteRateLimiter(rules).IsAllowed(userData);

// Assert
Assert.IsFalse(result);
}
[Test]
public void IsAllowedWithRequestMinAllowedTimePositive()
{
Cashing.Clear();
// Arrange
var rules = new RateLimiterRuleDecorator[] { new RequestMinAllowedTimeRule(TimeSpan.FromSeconds(1)) };

var userData = new UserData() { CountryCode = "US", Token = "tempToken", IpAddress = "192.168.18.22" };

// Act
new ConcreteRateLimiter(rules).IsAllowed(userData);
Thread.Sleep(2000);
var result = new ConcreteRateLimiter(rules).IsAllowed(userData);

// Assert
Assert.IsTrue(result);
}
[Test]
public void IsAllowedRequestMinAllowedTimeNegative()
{
Cashing.Clear();
// Arrange
var rules = new RateLimiterRuleDecorator[] { new RequestMinAllowedTimeRule(TimeSpan.FromSeconds(1)) };

var userData = new UserData() { CountryCode = "US", Token = "tempToken", IpAddress = "192.168.18.22" };

// Act
new ConcreteRateLimiter(rules).IsAllowed(userData);
var result = new ConcreteRateLimiter(rules).IsAllowed(userData);

// Assert
Assert.IsFalse(result);
}
[Test]
public void IsAllowedMaxRequestAmountInTimeSpanRulePositive()
{
Cashing.Clear();
// Arrange
var rules = new RateLimiterRuleDecorator[] { new MaxRequestAmountInTimeSpanRule(TimeSpan.FromSeconds(1),5) };

var userData = new UserData() { CountryCode = "US", Token = "tempToken", IpAddress = "192.168.18.22" };

// Act
new ConcreteRateLimiter(rules).IsAllowed(userData);
new ConcreteRateLimiter(rules).IsAllowed(userData);
var result = new ConcreteRateLimiter(rules).IsAllowed(userData);

// Assert
Assert.IsTrue(result);
}
[Test]
public void IsAllowedMaxRequestAmountInTimeSpanRuleWithDeleyPositive()
{
Cashing.Clear();
// Arrange
var rules = new RateLimiterRuleDecorator[] { new MaxRequestAmountInTimeSpanRule(TimeSpan.FromSeconds(1), 2) };

var userData = new UserData() { CountryCode = "US", Token = "tempToken", IpAddress = "192.168.18.22" };

// Act
new ConcreteRateLimiter(rules).IsAllowed(userData);
new ConcreteRateLimiter(rules).IsAllowed(userData);
Thread.Sleep(2000);
var result = new ConcreteRateLimiter(rules).IsAllowed(userData);

// Assert
Assert.IsTrue(result);
}
[Test]
public void IsAllowedMaxRequestAmountInTimeSpanRuleNegative()
{
Cashing.Clear();
// Arrange
var rules = new RateLimiterRuleDecorator[] { new MaxRequestAmountInTimeSpanRule(TimeSpan.FromSeconds(1), 2) };

var userData = new UserData() { CountryCode = "US", Token = "tempToken", IpAddress = "192.168.18.22" };

// Act
new ConcreteRateLimiter(rules).IsAllowed(userData);
new ConcreteRateLimiter(rules).IsAllowed(userData);
var result = new ConcreteRateLimiter(rules).IsAllowed(userData);

// Assert
Assert.IsFalse(result);
}
[Test]
public void IsAllowedMaxRequestAmountInTimeSpanByCountryPositive()
{
Cashing.Clear();
// Arrange
Dictionary<string, int> restrictionsByCountry = new()
{
{ "US", 2 },
{ "GE", 1 }
};
var rules = new RateLimiterRuleDecorator[] { new MaxRequestAmountInTimeSpanByCountryRule( TimeSpan.FromSeconds(1), restrictionsByCountry, 2) };

var userData = new UserData() { CountryCode = "US", Token = "tempToken", IpAddress = "192.168.18.22" };

// Act
new ConcreteRateLimiter(rules).IsAllowed(userData);
var result = new ConcreteRateLimiter(rules).IsAllowed(userData);

// Assert
Assert.IsTrue(result);
}
[Test]
public void IsAllowedMaxRequestAmountInTimeSpanByCountryNegative()
{
Cashing.Clear();
// Arrange
Dictionary<string, int> restrictionsByCountry = new()
{
{ "US", 2 },
{ "GE", 1 }
};
var rules = new RateLimiterRuleDecorator[] { new MaxRequestAmountInTimeSpanByCountryRule(TimeSpan.FromSeconds(1), restrictionsByCountry, 1) };

var userData = new UserData() { CountryCode = "US", Token = "tempToken", IpAddress = "192.168.18.22" };

// Act
new ConcreteRateLimiter(rules).IsAllowed(userData);
new ConcreteRateLimiter(rules).IsAllowed(userData);
var result = new ConcreteRateLimiter(rules).IsAllowed(userData);

// Assert
Assert.IsFalse(result);
}
[Test]
public void IsAllowedMaxRequestAmountInTimeSpanByCountryDefoultPositive()
{
Cashing.Clear();
// Arrange
Dictionary<string, int> restrictionsByCountry = new()
{
{ "US", 2 },
{ "GE", 1 }
};
var rules = new RateLimiterRuleDecorator[] { new MaxRequestAmountInTimeSpanByCountryRule(TimeSpan.FromSeconds(1), restrictionsByCountry, 2) };

var userData = new UserData() { CountryCode = "TR", Token = "tempToken", IpAddress = "192.168.18.22" };

// Act
new ConcreteRateLimiter(rules).IsAllowed(userData);
var result = new ConcreteRateLimiter(rules).IsAllowed(userData);

// Assert
Assert.IsTrue(result);
}
[Test]
public void IsAllowedMaxRequestAmountInTimeSpanByCountryDefoultNegative()
{
Cashing.Clear();
// Arrange
Dictionary<string, int> restrictionsByCountry = new()
{
{ "US", 2 },
{ "GE", 1 }
};
var rules = new RateLimiterRuleDecorator[] { new MaxRequestAmountInTimeSpanByCountryRule(TimeSpan.FromSeconds(1), restrictionsByCountry, 2) };

var userData = new UserData() { CountryCode = "TR", Token = "tempToken", IpAddress = "192.168.18.22" };

// Act
new ConcreteRateLimiter(rules).IsAllowed(userData);
new ConcreteRateLimiter(rules).IsAllowed(userData);
var result = new ConcreteRateLimiter(rules).IsAllowed(userData);

// Assert
Assert.IsFalse(result);
}


}
8 changes: 4 additions & 4 deletions RateLimiter.sln
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26730.15
# Visual Studio Version 17
VisualStudioVersion = 17.5.33502.453
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
Expand Down
30 changes: 30 additions & 0 deletions RateLimiter/ConcreteRateLimiter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using RateLimiter.Ruls.Abstract;
using RateLimiter.User;

namespace RateLimiter
{
public class ConcreteRateLimiter
{
private readonly RateLimiterRuleDecorator? _rateLimiter;
public ConcreteRateLimiter(RateLimiterRuleDecorator[] rules)
{
foreach (var rule in rules)
{
if (_rateLimiter == null)
{
_rateLimiter = rule;
}
else
{
rule.RateLimiterRule = _rateLimiter;
_rateLimiter = rule;
}
}
}
public bool IsAllowed(IUserData userData)
{
return _rateLimiter == null || _rateLimiter.IsAllowed(userData);
}

}
}
20 changes: 20 additions & 0 deletions RateLimiter/DataStore/Cashing.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System.Collections.Concurrent;

namespace RateLimiter.DataStore
{
//this is only for demonstration in real world scenario will be used redis or other data store
public static class Cashing
{
private static ConcurrentDictionary<string, string> Store = new();

public static string? Get(string key) {
Store.TryGetValue(key, out string? temp);
return temp;
}

public static void Set(string key, string value) {
Store.AddOrUpdate(key, value, (existingKey, existingValue) => value);
}
public static void Clear() { Store.Clear(); }
}
}
10 changes: 10 additions & 0 deletions RateLimiter/Ruls/Abstract/IRateLimiterRuleDecorator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using RateLimiter.User;

namespace RateLimiter.Ruls.Abstract
{
public abstract class RateLimiterRuleDecorator
{
public RateLimiterRuleDecorator? RateLimiterRule { get; set; }
public abstract bool IsAllowed(IUserData userData);
}
}
22 changes: 22 additions & 0 deletions RateLimiter/Ruls/IpBlackListRule.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using RateLimiter.Ruls.Abstract;
using RateLimiter.User;
using System;
using System.Linq;

namespace RateLimiter.Ruls
{
public class IpBlackListRule : RateLimiterRuleDecorator
{
private readonly string[] _restrictedIpAddresses;

public IpBlackListRule(string[] allowedIpAddresses)
{
_restrictedIpAddresses = allowedIpAddresses;
}

public override bool IsAllowed(IUserData userData)
{
return !_restrictedIpAddresses.Contains(userData.IpAddress) && (RateLimiterRule == null || RateLimiterRule.IsAllowed(userData));
}
}
}
Loading
Loading