public async void UnexpectedExceptionThrownByRedisDuringRemove()
        {
            // Given
            var connectionMultplexerMock = new Mock <IConnectionMultiplexer>();
            var configuration            = new ConcurrentRequestLimiterConfiguration();
            var loggerMock = new Mock <ILogger <RedisConcurrentRequestsManager> >();

            connectionMultplexerMock.Setup(mock => mock.GetDatabase(
                                               Moq.It.IsAny <int>(),
                                               Moq.It.IsAny <object>()))
            .Throws <Exception>();

            var manager = new RedisConcurrentRequestsManager(
                connectionMultplexerMock.Object,
                configuration,
                loggerMock.Object);

            // When a request is removed
            var result = await manager.RemoveAsync(
                Guid.NewGuid().ToString(),
                Guid.NewGuid().ToString());

            // Then it should log a warning
            loggerMock.Verify(mock => mock.Log(
                                  LogLevel.Warning,
                                  Moq.It.IsAny <EventId>(),
                                  Moq.It.IsAny <FormattedLogValues>(),
                                  Moq.It.IsAny <Exception>(),
                                  Moq.It.IsAny <Func <object, Exception, string> >()));

            // And it should return false
            result.Should().BeFalse();
        }
        public async void MultipleConcurrentRequestsAddedUnderCapacity()
        {
            // Given
            const string ClientId = "tester";

            var configuration = new ConcurrentRequestLimiterConfiguration();
            var loggerMock    = new Mock <ILogger <InMemoryConcurrentRequestsManager> >();
            var manager       = new InMemoryConcurrentRequestsManager(configuration, loggerMock.Object);

            // When multiple requests are added
            var tasks = new List <Task <AddConcurrentRequestResult> >();

            tasks.Add(manager.AddAsync(ClientId, Guid.NewGuid().ToString(), DateTimeOffset.UtcNow.ToUnixTimeSeconds()));
            tasks.Add(manager.AddAsync(ClientId, Guid.NewGuid().ToString(), DateTimeOffset.UtcNow.ToUnixTimeSeconds()));
            var results = await Task.WhenAll(tasks);

            // Then the manager should allow first request
            results[0].IsAllowed.Should().BeTrue();
            results[0].Limit.Should().Be(configuration.Capacity);
            results[0].Remaining.Should().Be(configuration.Capacity - 1);

            // And the manager should allow second request
            results[1].IsAllowed.Should().BeTrue();
            results[1].Limit.Should().Be(configuration.Capacity);
            results[1].Remaining.Should().Be(configuration.Capacity - 2);
        }
示例#3
0
 /// <summary>
 /// Initializes a new instance of <see cref="InMemoryConcurrentRequestsManager"/> class.
 /// </summary>
 /// <param name="configuration">
 /// The concurrent request limiter configuration.
 /// </param>
 /// <param name="logger">
 /// The logger.
 /// </param>
 public InMemoryConcurrentRequestsManager(
     ConcurrentRequestLimiterConfiguration configuration,
     ILogger <InMemoryConcurrentRequestsManager> logger)
 {
     _configuration = configuration;
     _logger        = logger;
 }
 /// <summary>
 /// Initializes a new instance of <see cref="RedisConcurrentRequestsManager"/>
 /// class.
 /// </summary>
 /// <param name="redisClient">
 /// The Redis API client.
 /// </param>
 /// <param name="configuration">
 /// The concurrent request limiter configuration options.
 /// </param>
 /// <param name="logger">
 /// The logger.
 /// </param>
 public RedisConcurrentRequestsManager(
     IConnectionMultiplexer redisClient,
     ConcurrentRequestLimiterConfiguration configuration,
     ILogger <RedisConcurrentRequestsManager> logger)
 {
     _redisClient   = redisClient;
     _configuration = configuration;
     _logger        = logger;
 }
        public void ValidateWithDefaultConfiguration()
        {
            // Given
            var configuration = new ConcurrentRequestLimiterConfiguration();

            // When configuration is validated
            var exception = Record.Exception(() => configuration.Validate());

            // Then it should successfully validate
            exception.Should().BeNull();
        }
        public async void RemovingARequestWhichWasNeverAdded()
        {
            // Given
            const string ClientId = "tester";

            var configuration = new ConcurrentRequestLimiterConfiguration();
            var loggerMock    = new Mock <ILogger <InMemoryConcurrentRequestsManager> >();
            var manager       = new InMemoryConcurrentRequestsManager(configuration, loggerMock.Object);

            // When a request is removed
            var result = await manager.RemoveAsync(ClientId, Guid.NewGuid().ToString());

            // Then the manager should not remove anything
            result.Should().BeFalse();
        }
        public void ValidateWithNullKeyPrefix()
        {
            // Given
            var configuration = new ConcurrentRequestLimiterConfiguration
            {
                KeysPrefix = null
            };

            // When configuration is validated
            var exception = Record.Exception(() => configuration.Validate());

            // It should throw ArgumentNullException
            exception.Should().NotBeNull();
            exception.Should().BeOfType <ArgumentNullException>();
            exception.Message.Should().Contain("Keys prefix must be provided.");
            ((ArgumentNullException)exception).ParamName.Should().Be("KeysPrefix");
        }
        public void ValidateWithZeroTimeToLive()
        {
            // Given
            var configuration = new ConcurrentRequestLimiterConfiguration
            {
                RequestTimeToLive = 0
            };

            // When configuration is validated
            var exception = Record.Exception(() => configuration.Validate());

            // Then it should throw ArgumentOutOfRangeException
            exception.Should().NotBeNull();
            exception.Should().BeOfType <ArgumentOutOfRangeException>();
            exception.Message.Should().Contain("Request time-to-live must be greater than 0.");
            ((ArgumentOutOfRangeException)exception).ParamName.Should().Be("RequestTimeToLive");
        }
        public async void APreviouslyAddedRequestIsRemoved()
        {
            // Given
            const string ClientId  = "tester";
            const string RequestId = "123";

            var configuration = new ConcurrentRequestLimiterConfiguration();
            var loggerMock    = new Mock <ILogger <InMemoryConcurrentRequestsManager> >();
            var manager       = new InMemoryConcurrentRequestsManager(configuration, loggerMock.Object);

            await manager.AddAsync(ClientId, RequestId, DateTimeOffset.UtcNow.ToUnixTimeSeconds());

            // When a request is removed
            var result = await manager.RemoveAsync(ClientId, RequestId);

            // Then the manager should remove the request
            result.Should().BeTrue();
        }
        public async void RequestIsNotRemoved()
        {
            // Given
            var connectionMultplexerMock = new Mock <IConnectionMultiplexer>();
            var databaseMock             = new Mock <IDatabase>();
            var configuration            = new ConcurrentRequestLimiterConfiguration();
            var loggerMock = new Mock <ILogger <RedisConcurrentRequestsManager> >();

            databaseMock.Setup(mock => mock.SortedSetRemoveAsync(
                                   Moq.It.IsAny <RedisKey>(),
                                   Moq.It.IsAny <RedisValue>(),
                                   Moq.It.IsAny <CommandFlags>()))
            .ReturnsAsync(false);

            connectionMultplexerMock.Setup(mock => mock.GetDatabase(
                                               Moq.It.IsAny <int>(),
                                               Moq.It.IsAny <object>()))
            .Returns(databaseMock.Object);

            var manager = new RedisConcurrentRequestsManager(
                connectionMultplexerMock.Object,
                configuration,
                loggerMock.Object);

            // When a request is removed
            var result = await manager.RemoveAsync(
                Guid.NewGuid().ToString(),
                Guid.NewGuid().ToString());

            // Then it should log a debug message
            loggerMock.Verify(mock => mock.Log(
                                  LogLevel.Debug,
                                  Moq.It.IsAny <EventId>(),
                                  Moq.It.IsAny <FormattedLogValues>(),
                                  Moq.It.IsAny <Exception>(),
                                  Moq.It.IsAny <Func <object, Exception, string> >()));

            // And it should return false
            result.Should().BeFalse();
        }
        public async void ARequestIsAddedOverCapacity()
        {
            // Given
            const string ClientId = "tester";

            var configuration = new ConcurrentRequestLimiterConfiguration();
            var loggerMock    = new Mock <ILogger <InMemoryConcurrentRequestsManager> >();
            var manager       = new InMemoryConcurrentRequestsManager(configuration, loggerMock.Object);

            for (var i = 0; i < configuration.Capacity; i++)
            {
                await manager.AddAsync(ClientId, Guid.NewGuid().ToString(), DateTimeOffset.UtcNow.ToUnixTimeSeconds());
            }

            // When a request is added over capacity
            var result = await manager.AddAsync(ClientId, Guid.NewGuid().ToString(), DateTimeOffset.UtcNow.ToUnixTimeSeconds());

            // Then the manager should not allow the request
            result.IsAllowed.Should().BeFalse();
            result.Limit.Should().Be(configuration.Capacity);
            result.Remaining.Should().Be(0);
        }