From a2c7bb2a70d41c91b3a1c8efa65c4ae7f4c4c1cc Mon Sep 17 00:00:00 2001 From: Anthony Tsavdaridis Date: Thu, 27 Aug 2020 19:24:12 +0300 Subject: [PATCH] Add async methods + tests --- global.json | 5 - .../RedisThrottleRepository.cs | 52 +++++- .../MemoryThrottleRepositoryTests.cs | 101 ++++++++++++ .../RedisThrottleRepositoryTests.cs | 150 +++++++++++++++++- src/BrakePedal.NETStandard/IThrottlePolicy.cs | 7 + .../IThrottleRepository.cs | 14 +- .../MemoryThrottleRepository.cs | 32 ++++ src/BrakePedal.NETStandard/ThrottlePolicy.cs | 16 ++ 8 files changed, 368 insertions(+), 9 deletions(-) delete mode 100644 global.json diff --git a/global.json b/global.json deleted file mode 100644 index d151b80..0000000 --- a/global.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "sdk": { - "version": "3.1.301" - } -} diff --git a/src/BrakePedal.NETStandard.Redis/RedisThrottleRepository.cs b/src/BrakePedal.NETStandard.Redis/RedisThrottleRepository.cs index 45e5808..d8be9c1 100644 --- a/src/BrakePedal.NETStandard.Redis/RedisThrottleRepository.cs +++ b/src/BrakePedal.NETStandard.Redis/RedisThrottleRepository.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; + using StackExchange.Redis; namespace BrakePedal.NETStandard.Redis @@ -27,6 +29,17 @@ public RedisThrottleRepository(IDatabase database) return null; } + public async Task GetThrottleCountAsync(IThrottleKey key, Limiter limiter) + { + string id = CreateThrottleKey(key, limiter); + RedisValue value = await _db.StringGetAsync(id); + long convert; + if (long.TryParse(value, out convert)) + return convert; + + return null; + } + public void AddOrIncrementWithExpiration(IThrottleKey key, Limiter limiter) { string id = CreateThrottleKey(key, limiter); @@ -39,12 +52,32 @@ public void AddOrIncrementWithExpiration(IThrottleKey key, Limiter limiter) _db.KeyExpire(id, limiter.Period); } + public async Task AddOrIncrementWithExpirationAsync(IThrottleKey key, Limiter limiter) + { + string id = CreateThrottleKey(key, limiter); + + long result = await _db.StringIncrementAsync(id); + + // If we get back 1, that means the key was incremented as it + // was expiring or it's a new key. Ensure we set the expiration. + if (result == 1) + { + await _db.KeyExpireAsync(id, limiter.Period); + } + } + public bool LockExists(IThrottleKey key, Limiter limiter) { string id = CreateLockKey(key, limiter); return _db.KeyExists(id); } + public Task LockExistsAsync(IThrottleKey key, Limiter limiter) + { + string id = CreateLockKey(key, limiter); + return _db.KeyExistsAsync(id); + } + public void SetLock(IThrottleKey key, Limiter limiter) { string id = CreateLockKey(key, limiter); @@ -54,12 +87,27 @@ public void SetLock(IThrottleKey key, Limiter limiter) trans.Execute(); } + public async Task SetLockAsync(IThrottleKey key, Limiter limiter) + { + string id = CreateLockKey(key, limiter); + ITransaction trans = _db.CreateTransaction(); + await trans.StringIncrementAsync(id); + await trans.KeyExpireAsync(id, limiter.LockDuration); + await trans.ExecuteAsync(); + } + public void RemoveThrottle(IThrottleKey key, Limiter limiter) { string id = CreateThrottleKey(key, limiter); _db.KeyDelete(id); } + public Task RemoveThrottleAsync(IThrottleKey key, Limiter limiter) + { + string id = CreateThrottleKey(key, limiter); + return _db.KeyDeleteAsync(id); + } + public string CreateThrottleKey(IThrottleKey key, Limiter limiter) { List values = CreateBaseKeyValues(key, limiter); @@ -75,7 +123,7 @@ public string CreateThrottleKey(IThrottleKey key, Limiter limiter) string id = string.Join(":", values); return id; } - + public string CreateLockKey(IThrottleKey key, Limiter limiter) { List values = CreateBaseKeyValues(key, limiter); @@ -86,7 +134,7 @@ public string CreateLockKey(IThrottleKey key, Limiter limiter) string id = string.Join(":", values); return id; - } + } private List CreateBaseKeyValues(IThrottleKey key, Limiter limiter) { diff --git a/src/BrakePedal.NETStandard.Tests/MemoryThrottleRepositoryTests.cs b/src/BrakePedal.NETStandard.Tests/MemoryThrottleRepositoryTests.cs index e602ca5..0aa2d86 100644 --- a/src/BrakePedal.NETStandard.Tests/MemoryThrottleRepositoryTests.cs +++ b/src/BrakePedal.NETStandard.Tests/MemoryThrottleRepositoryTests.cs @@ -1,5 +1,8 @@ using System; +using System.Threading.Tasks; + using Microsoft.Extensions.Caching.Memory; + using Xunit; namespace BrakePedal.NETStandard.Tests @@ -32,6 +35,30 @@ public void NewObject_SetsCountToOneWithExpiration() Assert.Equal(new DateTime(2030, 1, 1, 0, 1, 40), item.Expiration); } + [Fact] + public async Task NewObject_SetsCountToOneWithExpirationAsync() + { + // Arrange + var key = new SimpleThrottleKey("test", "key"); + var limiter = new Limiter() + .Limit(1) + .Over(100); + var cache = new MemoryCache(new MemoryCacheOptions()); + var repository = new MemoryThrottleRepository(cache); + repository.CurrentDate = () => new DateTime(2030, 1, 1); + + string id = await repository.CreateThrottleKeyAsync(key, limiter); + + // Act + await repository.AddOrIncrementWithExpirationAsync(key, limiter); + + // Assert + var item = (MemoryThrottleRepository.ThrottleCacheItem)cache.Get(id); + Assert.Equal(1L, item.Count); + // We're testing a future date by 100 seconds which is 40 seconds + 1 minute + Assert.Equal(new DateTime(2030, 1, 1, 0, 1, 40), item.Expiration); + } + [Fact] public void ExistingObject_IncrementByOneAndSetExpirationDate() { @@ -62,6 +89,35 @@ public void ExistingObject_IncrementByOneAndSetExpirationDate() Assert.Equal(new DateTime(2030, 1, 1), item.Expiration); } + [Fact] + public async Task ExistingObject_IncrementByOneAndSetExpirationDateAsync() + { + // Arrange + var key = new SimpleThrottleKey("test", "key"); + var limiter = new Limiter() + .Limit(1) + .Over(100); + var cache = new MemoryCache(new MemoryCacheOptions()); + var repository = new MemoryThrottleRepository(cache); + string id = repository.CreateThrottleKey(key, limiter); + + var cacheItem = new MemoryThrottleRepository.ThrottleCacheItem() + { + Count = 1, + Expiration = new DateTime(2030, 1, 1) + }; + + cache + .Set(id, cacheItem, cacheItem.Expiration); + + // Act + await repository.AddOrIncrementWithExpirationAsync(key, limiter); + + // Assert + var item = (MemoryThrottleRepository.ThrottleCacheItem)cache.Get(id); + Assert.Equal(2L, item.Count); + Assert.Equal(new DateTime(2030, 1, 1), item.Expiration); + } [Fact] public void RetrieveValidThrottleCountFromRepostitory() @@ -90,6 +146,33 @@ public void RetrieveValidThrottleCountFromRepostitory() Assert.Equal(1, count); } + [Fact] + public async Task RetrieveValidThrottleCountFromRepostitoryAsync() + { + // Arrange + var key = new SimpleThrottleKey("test", "key"); + var limiter = new Limiter() + .Limit(1) + .Over(100); + var cache = new MemoryCache(new MemoryCacheOptions()); + var repository = new MemoryThrottleRepository(cache); + string id = repository.CreateThrottleKey(key, limiter); + + var cacheItem = new MemoryThrottleRepository.ThrottleCacheItem() + { + Count = 1, + Expiration = new DateTime(2030, 1, 1) + }; + + await repository.AddOrIncrementWithExpirationAsync(key, limiter); + + // Act + var count = await repository.GetThrottleCountAsync(key, limiter); + + // Assert + Assert.Equal(1, count); + } + [Fact] public void ThrottleCountReturnsNullWhenUsingInvalidKey() { @@ -107,6 +190,24 @@ public void ThrottleCountReturnsNullWhenUsingInvalidKey() // Assert Assert.Null(count); } + + [Fact] + public async Task ThrottleCountReturnsNullWhenUsingInvalidKeyAsync() + { + // Arrange + var key = new SimpleThrottleKey("test", "key"); + var limiter = new Limiter() + .Limit(1) + .Over(100); + var cache = new MemoryCache(new MemoryCacheOptions()); + var repository = new MemoryThrottleRepository(cache); + + // Act + var count = await repository.GetThrottleCountAsync(key, limiter); + + // Assert + Assert.Null(count); + } } public class ThrottleCacheItemTests diff --git a/src/BrakePedal.NETStandard.Tests/RedisThrottleRepositoryTests.cs b/src/BrakePedal.NETStandard.Tests/RedisThrottleRepositoryTests.cs index 90eee9b..6c254ac 100644 --- a/src/BrakePedal.NETStandard.Tests/RedisThrottleRepositoryTests.cs +++ b/src/BrakePedal.NETStandard.Tests/RedisThrottleRepositoryTests.cs @@ -1,6 +1,11 @@ -using BrakePedal.NETStandard.Redis; +using System.Threading.Tasks; + +using BrakePedal.NETStandard.Redis; + using NSubstitute; + using StackExchange.Redis; + using Xunit; namespace BrakePedal.NETStandard.Tests @@ -35,6 +40,33 @@ public void IncrementReturnsOne_ExpireKey() .Received(1) .KeyExpire(id, limiter.Period); } + + [Fact] + public async Task IncrementReturnsOne_ExpireKeyAsync() + { + // Arrange + var key = new SimpleThrottleKey("testAsync", "keyAsync"); + Limiter limiter = new Limiter().Limit(1).Over(10); + var db = Substitute.For(); + var repository = new RedisThrottleRepository(db); + string id = repository.CreateThrottleKey(key, limiter); + + db + .StringIncrementAsync(id) + .Returns(1); + + // Act + await repository.AddOrIncrementWithExpirationAsync(key, limiter); + + // Assert + await db + .Received(1) + .StringIncrementAsync(id); + + await db + .Received(1) + .KeyExpireAsync(id, limiter.Period); + } } public class GetThrottleCountMethod @@ -60,6 +92,27 @@ public void KeyDoesNotExist_ReturnsNull() Assert.Null(result); } + [Fact] + public async Task KeyDoesNotExist_ReturnsNullAsync() + { + // Arrange + var key = new SimpleThrottleKey("test", "key"); + Limiter limiter = new Limiter().Limit(1).Over(1); + var db = Substitute.For(); + var repository = new RedisThrottleRepository(db); + string id = repository.CreateThrottleKey(key, limiter); + + db + .StringGet(id) + .Returns((long?)null); + + // Act + long? result = await repository.GetThrottleCountAsync(key, limiter); + + // Assert + Assert.Null(result); + } + [Fact] public void KeyExists_ReturnsParsedValue() { @@ -80,6 +133,27 @@ public void KeyExists_ReturnsParsedValue() // Assert Assert.Equal(10, result); } + + [Fact] + public async Task KeyExists_ReturnsParsedValueAsync() + { + // Arrange + var key = new SimpleThrottleKey("test", "key"); + Limiter limiter = new Limiter().Limit(1).Over(1); + var db = Substitute.For(); + var repository = new RedisThrottleRepository(db); + string id = repository.CreateThrottleKey(key, limiter); + + db + .StringGetAsync(id) + .Returns((RedisValue)"10"); + + // Act + long? result = await repository.GetThrottleCountAsync(key, limiter); + + // Assert + Assert.Equal(10, result); + } } public class LockExistsMethod @@ -106,6 +180,29 @@ public void KeyExists_ReturnsTrue(bool keyExists, bool expected) // Assert Assert.Equal(expected, result); } + + [Theory] + [InlineData(true, true)] + [InlineData(false, false)] + public async Task KeyExists_ReturnsTrueAsync(bool keyExists, bool expected) + { + // Arrange + var key = new SimpleThrottleKey("test", "key"); + Limiter limiter = new Limiter().Limit(1).Over(1).LockFor(1); + var db = Substitute.For(); + var repository = new RedisThrottleRepository(db); + string id = repository.CreateLockKey(key, limiter); + + db + .KeyExistsAsync(id) + .Returns(keyExists); + + // Act + bool result = await repository.LockExistsAsync(key, limiter); + + // Assert + Assert.Equal(expected, result); + } } public class RemoveThrottleMethod @@ -128,6 +225,25 @@ public void RemoveThrottle() .Received(1) .KeyDelete(id); } + + [Fact] + public async Task RemoveThrottleAsync() + { + // Arrange + var key = new SimpleThrottleKey("test", "key"); + Limiter limiter = new Limiter().Limit(1).Over(1); + var db = Substitute.For(); + var repository = new RedisThrottleRepository(db); + string id = repository.CreateThrottleKey(key, limiter); + + // Act + await repository.RemoveThrottleAsync(key, limiter); + + // Assert + await db + .Received(1) + .KeyDeleteAsync(id); + } } public class SetLockMethod @@ -163,6 +279,38 @@ public void SetLock() .Received(1) .Execute(); } + + [Fact] + public async Task SetLockAsync() + { + // Arrange + var key = new SimpleThrottleKey("test", "key"); + Limiter limiter = new Limiter().Limit(1).Over(1).LockFor(1); + var db = Substitute.For(); + var repository = new RedisThrottleRepository(db); + string id = repository.CreateLockKey(key, limiter); + var transaction = Substitute.For(); + + db + .CreateTransaction() + .Returns(transaction); + + // Act + await repository.SetLockAsync(key, limiter); + + // Assert + await transaction + .Received(1) + .StringIncrementAsync(id); + + await transaction + .Received(1) + .KeyExpireAsync(id, limiter.LockDuration); + + await transaction + .Received(1) + .ExecuteAsync(); + } } } } \ No newline at end of file diff --git a/src/BrakePedal.NETStandard/IThrottlePolicy.cs b/src/BrakePedal.NETStandard/IThrottlePolicy.cs index 801f10a..bbb1b43 100644 --- a/src/BrakePedal.NETStandard/IThrottlePolicy.cs +++ b/src/BrakePedal.NETStandard/IThrottlePolicy.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Threading.Tasks; namespace BrakePedal.NETStandard { @@ -10,8 +11,14 @@ public interface IThrottlePolicy CheckResult Check(IThrottleKey key, bool increment = true); + Task CheckAsync(IThrottleKey key, bool increment = true); + bool IsThrottled(IThrottleKey key, out CheckResult result, bool increment = true); + Task IsThrottledAsync(IThrottleKey key, bool increment = true); + bool IsLocked(IThrottleKey key, out CheckResult result, bool increment = true); + + Task IsLockedAsync(IThrottleKey key, bool increment = true); } } \ No newline at end of file diff --git a/src/BrakePedal.NETStandard/IThrottleRepository.cs b/src/BrakePedal.NETStandard/IThrottleRepository.cs index 57cf0da..c90b8d8 100644 --- a/src/BrakePedal.NETStandard/IThrottleRepository.cs +++ b/src/BrakePedal.NETStandard/IThrottleRepository.cs @@ -1,4 +1,6 @@ -namespace BrakePedal.NETStandard +using System.Threading.Tasks; + +namespace BrakePedal.NETStandard { public interface IThrottleRepository { @@ -6,14 +8,24 @@ public interface IThrottleRepository long? GetThrottleCount(IThrottleKey key, Limiter limiter); + Task GetThrottleCountAsync(IThrottleKey key, Limiter limiter); + void AddOrIncrementWithExpiration(IThrottleKey key, Limiter limiter); + Task AddOrIncrementWithExpirationAsync(IThrottleKey key, Limiter limiter); + void SetLock(IThrottleKey key, Limiter limiter); + Task SetLockAsync(IThrottleKey key, Limiter limiter); + bool LockExists(IThrottleKey key, Limiter limiter); + Task LockExistsAsync(IThrottleKey key, Limiter limiter); + void RemoveThrottle(IThrottleKey key, Limiter limiter); + Task RemoveThrottleAsync(IThrottleKey key, Limiter limiter); + string CreateThrottleKey(IThrottleKey key, Limiter limiter); string CreateLockKey(IThrottleKey key, Limiter limiter); diff --git a/src/BrakePedal.NETStandard/MemoryThrottleRepository.cs b/src/BrakePedal.NETStandard/MemoryThrottleRepository.cs index 884dc44..dc1c569 100644 --- a/src/BrakePedal.NETStandard/MemoryThrottleRepository.cs +++ b/src/BrakePedal.NETStandard/MemoryThrottleRepository.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; + using Microsoft.Extensions.Caching.Memory; namespace BrakePedal.NETStandard @@ -37,6 +39,9 @@ public MemoryThrottleRepository() return null; } + public Task GetThrottleCountAsync(IThrottleKey key, Limiter limiter) + => Task.FromResult(GetThrottleCount(key, limiter)); + public void AddOrIncrementWithExpiration(IThrottleKey key, Limiter limiter) { string id = CreateThrottleKey(key, limiter); @@ -58,6 +63,12 @@ public void AddOrIncrementWithExpiration(IThrottleKey key, Limiter limiter) _store.Set(id, cacheItem, cacheItem.Expiration); } + public Task AddOrIncrementWithExpirationAsync(IThrottleKey key, Limiter limiter) + { + AddOrIncrementWithExpiration(key, limiter); + return Task.CompletedTask; + } + public void SetLock(IThrottleKey key, Limiter limiter) { string throttleId = CreateThrottleKey(key, limiter); @@ -68,18 +79,33 @@ public void SetLock(IThrottleKey key, Limiter limiter) _store.Set(lockId, true, expiration); } + public Task SetLockAsync(IThrottleKey key, Limiter limiter) + { + SetLock(key, limiter); + return Task.CompletedTask; + } + public bool LockExists(IThrottleKey key, Limiter limiter) { string lockId = CreateLockKey(key, limiter); return _store.TryGetValue(lockId, out _); } + public Task LockExistsAsync(IThrottleKey key, Limiter limiter) + => Task.FromResult(LockExists(key, limiter)); + public void RemoveThrottle(IThrottleKey key, Limiter limiter) { string lockId = CreateThrottleKey(key, limiter); _store.Remove(lockId); } + public Task RemoveThrottleAsync(IThrottleKey key, Limiter limiter) + { + RemoveThrottle(key, limiter); + return Task.CompletedTask; + } + public string CreateLockKey(IThrottleKey key, Limiter limiter) { List values = CreateBaseKeyValues(key, limiter); @@ -92,6 +118,9 @@ public string CreateLockKey(IThrottleKey key, Limiter limiter) return id; } + public Task CreateLockKeyAsync(IThrottleKey key, Limiter limiter) + => Task.FromResult(CreateLockKey(key, limiter)); + public string CreateThrottleKey(IThrottleKey key, Limiter limiter) { List values = CreateBaseKeyValues(key, limiter); @@ -108,6 +137,9 @@ public string CreateThrottleKey(IThrottleKey key, Limiter limiter) return id; } + public Task CreateThrottleKeyAsync(IThrottleKey key, Limiter limiter) + => Task.FromResult(CreateThrottleKey(key, limiter)); + private List CreateBaseKeyValues(IThrottleKey key, Limiter limiter) { List values = key.Values.ToList(); diff --git a/src/BrakePedal.NETStandard/ThrottlePolicy.cs b/src/BrakePedal.NETStandard/ThrottlePolicy.cs index be3c42f..28f7793 100644 --- a/src/BrakePedal.NETStandard/ThrottlePolicy.cs +++ b/src/BrakePedal.NETStandard/ThrottlePolicy.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; namespace BrakePedal.NETStandard { @@ -71,12 +72,24 @@ public bool IsThrottled(IThrottleKey key, out CheckResult result, bool increment return result.IsThrottled; } + public async Task IsThrottledAsync(IThrottleKey key, bool increment = true) + { + var result = await CheckAsync(key, increment); + return result.IsThrottled; + } + public bool IsLocked(IThrottleKey key, out CheckResult result, bool increment = true) { result = Check(key, increment); return result.IsLocked; } + public async Task IsLockedAsync(IThrottleKey key, bool increment = true) + { + var result = await CheckAsync(key, increment); + return result.IsLocked; + } + public CheckResult Check(IThrottleKey key, bool increment = true) { foreach (Limiter limiter in Limiters) @@ -126,6 +139,9 @@ public CheckResult Check(IThrottleKey key, bool increment = true) return CheckResult.NotThrottled; } + public Task CheckAsync(IThrottleKey key, bool increment = true) + => Task.FromResult(Check(key, increment)); + private void SetLimiter(TimeSpan span, long? count) { Limiter item = Limiters.FirstOrDefault(l => l.Period == span);