mirror of
https://github.com/esiur/esiur-dotnet.git
synced 2026-07-30 17:30:40 +00:00
142 lines
5.5 KiB
C#
142 lines
5.5 KiB
C#
using Esiur.Core;
|
|
using Esiur.Data;
|
|
using Esiur.Data.Types;
|
|
using Esiur.Protocol;
|
|
using Esiur.Resource;
|
|
using Esiur.Security.Authority;
|
|
using Esiur.Security.Permissions;
|
|
using Esiur.Security.RateLimiting;
|
|
using System.Diagnostics;
|
|
|
|
namespace Esiur.Tests.Unit.Integration;
|
|
|
|
[Collection("Integration")]
|
|
public class RateControlIntegrationTests
|
|
{
|
|
[Fact]
|
|
public async Task FunctionCalls_AreDeniedWhenPolicyIsExhausted()
|
|
{
|
|
await using var cluster = await StartCluster().WaitAsync(TimeSpan.FromSeconds(10));
|
|
var remote = await GetRemote(cluster);
|
|
var function = remote.Instance.Definition.GetFunctionDefByName(nameof(RateLimitedResource.Call));
|
|
|
|
var first = await remote._Invoke(function.Index, Array.Empty<object>());
|
|
var exception = await Assert.ThrowsAsync<AsyncException>(async () =>
|
|
await remote._Invoke(function.Index, Array.Empty<object>()));
|
|
|
|
Assert.Equal(1, Convert.ToInt32(first));
|
|
Assert.Equal(ExceptionCode.RateLimitExceeded, exception.Code);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task PropertySets_AreDeniedWhenPolicyIsExhausted()
|
|
{
|
|
await using var cluster = await StartCluster().WaitAsync(TimeSpan.FromSeconds(10));
|
|
var remote = await GetRemote(cluster);
|
|
var property = remote.Instance.Definition.GetPropertyDefByName(nameof(RateLimitedResource.Value));
|
|
|
|
await remote.SetResourcePropertyAsync(property.Index, 10);
|
|
var exception = await Assert.ThrowsAsync<AsyncException>(async () =>
|
|
await remote.SetResourcePropertyAsync(property.Index, 20));
|
|
|
|
Assert.Equal(ExceptionCode.RateLimitExceeded, exception.Code);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task RepeatedDenials_BlockTheConnection()
|
|
{
|
|
await using var cluster = await StartCluster(denialsBeforeBlock: 1)
|
|
.WaitAsync(TimeSpan.FromSeconds(10));
|
|
cluster.Connection.AutoReconnect = false;
|
|
var remote = await GetRemote(cluster);
|
|
var function = remote.Instance.Definition.GetFunctionDefByName(nameof(RateLimitedResource.Call));
|
|
|
|
await remote._Invoke(function.Index, Array.Empty<object>());
|
|
var exception = await Assert.ThrowsAsync<AsyncException>(async () =>
|
|
await remote._Invoke(function.Index, Array.Empty<object>()));
|
|
|
|
Assert.Equal(ExceptionCode.RateLimitExceeded, exception.Code);
|
|
await WaitUntilAsync(() => !cluster.Connection.IsConnected, TimeSpan.FromSeconds(3));
|
|
Assert.False(cluster.Connection.IsConnected);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task QueuedCalls_AreDelayedAndQueueOverflowIsDenied()
|
|
{
|
|
await using var cluster = await StartCluster(
|
|
queueLimit: 1,
|
|
period: TimeSpan.FromMilliseconds(250)).WaitAsync(TimeSpan.FromSeconds(10));
|
|
var remote = await GetRemote(cluster);
|
|
var function = remote.Instance.Definition.GetFunctionDefByName(nameof(RateLimitedResource.Call));
|
|
|
|
await remote._Invoke(function.Index, Array.Empty<object>());
|
|
|
|
var stopwatch = Stopwatch.StartNew();
|
|
var queued = remote._Invoke(function.Index, Array.Empty<object>());
|
|
var exception = await Assert.ThrowsAsync<AsyncException>(async () =>
|
|
await remote._Invoke(function.Index, Array.Empty<object>()));
|
|
var result = await queued;
|
|
|
|
Assert.Equal(ExceptionCode.RateLimitExceeded, exception.Code);
|
|
Assert.Equal(2, Convert.ToInt32(result));
|
|
Assert.True(stopwatch.Elapsed >= TimeSpan.FromMilliseconds(150));
|
|
}
|
|
|
|
static Task<IntegrationCluster> StartCluster(
|
|
int denialsBeforeBlock = 10,
|
|
int queueLimit = 0,
|
|
TimeSpan? period = null)
|
|
=> IntegrationCluster.StartAsync(async warehouse =>
|
|
{
|
|
warehouse.Configuration.RateControl.DenialsBeforeConnectionBlock = denialsBeforeBlock;
|
|
warehouse.Configuration.RateControl.ConnectionBlockDelay = TimeSpan.FromMilliseconds(100);
|
|
|
|
warehouse.AddRatePolicy(new BurstRatePolicy("standard-call")
|
|
{
|
|
PermitLimit = 1,
|
|
Period = period ?? TimeSpan.FromMinutes(1),
|
|
QueueLimit = queueLimit,
|
|
});
|
|
warehouse.AddRatePolicy(new BurstRatePolicy("standard-set")
|
|
{
|
|
PermitLimit = 1,
|
|
Period = period ?? TimeSpan.FromMinutes(1),
|
|
QueueLimit = queueLimit,
|
|
});
|
|
|
|
var resource = await warehouse.Put("sys/rate", new RateLimitedResource());
|
|
resource.Instance!.Managers.Add(new AllowPropertySetPermissions());
|
|
});
|
|
|
|
static async Task<EpResource> GetRemote(IntegrationCluster cluster)
|
|
=> await Task.Run(async () =>
|
|
(EpResource)await cluster.Connection.Get("sys/rate"))
|
|
.WaitAsync(TimeSpan.FromSeconds(10));
|
|
|
|
static async Task WaitUntilAsync(Func<bool> condition, TimeSpan timeout)
|
|
{
|
|
var deadline = DateTime.UtcNow + timeout;
|
|
while (!condition())
|
|
{
|
|
if (DateTime.UtcNow >= deadline)
|
|
throw new TimeoutException("The expected condition was not reached.");
|
|
await Task.Delay(20);
|
|
}
|
|
}
|
|
|
|
sealed class AllowPropertySetPermissions : IPermissionsManager
|
|
{
|
|
public Map<string, object> Settings { get; } = new();
|
|
|
|
public Ruling Applicable(
|
|
IResource resource,
|
|
Session session,
|
|
ActionType action,
|
|
MemberDef member,
|
|
object inquirer = null!)
|
|
=> action == ActionType.SetProperty ? Ruling.Allowed : Ruling.DontCare;
|
|
|
|
public bool Initialize(Map<string, object> settings, IResource resource) => true;
|
|
}
|
|
}
|