diff --git a/src/Foundatio.TestHarness/Caching/CacheClientTestsBase.cs b/src/Foundatio.TestHarness/Caching/CacheClientTestsBase.cs index e95d892d..71d7cc81 100644 --- a/src/Foundatio.TestHarness/Caching/CacheClientTestsBase.cs +++ b/src/Foundatio.TestHarness/Caching/CacheClientTestsBase.cs @@ -384,6 +384,78 @@ public virtual async Task CanRemoveByPrefixAsync() } } + public virtual async Task CanRemoveByPrefixWithScopedCachesAsync() + { + var cache = GetCacheClient(); + if (cache == null) + return; + + using (cache) + { + await cache.RemoveAllAsync(); + var scopedCache1 = new ScopedCacheClient(cache, "scoped1"); + + const string cacheKey = "key"; + await cache.SetAsync(cacheKey, 1); + await scopedCache1.SetAsync(cacheKey, 1); + Assert.Equal(1, (await cache.GetAsync(cacheKey)).Value); + Assert.Equal(1, (await scopedCache1.GetAsync(cacheKey)).Value); + + // Remove by prefix should only remove the unscoped cache. + Assert.Equal(1, await cache.RemoveByPrefixAsync(cacheKey)); + Assert.False(await cache.ExistsAsync(cacheKey)); + Assert.True(await scopedCache1.ExistsAsync(cacheKey)); + Assert.Equal(1, (await scopedCache1.GetAsync(cacheKey)).Value); + + // Add the unscoped cache value back. + await cache.SetAsync(cacheKey, 1); + + // Remove by null key. + Assert.Equal(1, await scopedCache1.RemoveByPrefixAsync(null)); + Assert.True(await cache.ExistsAsync(cacheKey)); + Assert.False(await scopedCache1.ExistsAsync(cacheKey)); + + // Add the scoped cache value back. + await scopedCache1.SetAsync(cacheKey, 1); + + Assert.Equal(2, await cache.RemoveByPrefixAsync(null)); + Assert.False(await cache.ExistsAsync(cacheKey)); + Assert.False(await scopedCache1.ExistsAsync(cacheKey)); + + // Reset client values + await cache.SetAsync(cacheKey, 1); + await scopedCache1.SetAsync(cacheKey, 1); + + // Remove by empty key. + Assert.Equal(1, await scopedCache1.RemoveByPrefixAsync(String.Empty)); + Assert.True(await cache.ExistsAsync(cacheKey)); + Assert.False(await scopedCache1.ExistsAsync(cacheKey)); + + // Add the scoped cache value back. + await scopedCache1.SetAsync(cacheKey, 1); + + Assert.Equal(2, await cache.RemoveByPrefixAsync(String.Empty)); + Assert.False(await cache.ExistsAsync(cacheKey)); + Assert.False(await scopedCache1.ExistsAsync(cacheKey)); + + // Reset client values + await cache.SetAsync(cacheKey, 1); + await scopedCache1.SetAsync(cacheKey, 1); + + // Remove by *. + Assert.Equal(1, await scopedCache1.RemoveByPrefixAsync("*")); + Assert.True(await cache.ExistsAsync(cacheKey)); + Assert.False(await scopedCache1.ExistsAsync(cacheKey)); + + // Reset client values + await scopedCache1.SetAsync(cacheKey, 1); + + Assert.Equal(2, await cache.RemoveByPrefixAsync("*")); + Assert.False(await cache.ExistsAsync(cacheKey)); + Assert.False(await scopedCache1.ExistsAsync(cacheKey)); + } + } + public virtual async Task CanRemoveByPrefixMultipleEntriesAsync(int count) { var cache = GetCacheClient(); diff --git a/src/Foundatio.TestHarness/Caching/HybridCacheClientTests.cs b/src/Foundatio.TestHarness/Caching/HybridCacheClientTests.cs index 9e85bf70..34871750 100644 --- a/src/Foundatio.TestHarness/Caching/HybridCacheClientTests.cs +++ b/src/Foundatio.TestHarness/Caching/HybridCacheClientTests.cs @@ -87,6 +87,12 @@ public override Task CanRemoveByPrefixAsync() return base.CanRemoveByPrefixAsync(); } + [Fact] + public override Task CanRemoveByPrefixWithScopedCachesAsync() + { + return base.CanRemoveByPrefixWithScopedCachesAsync(); + } + [Theory] [InlineData(50)] [InlineData(500)] diff --git a/src/Foundatio/Caching/InMemoryCacheClient.cs b/src/Foundatio/Caching/InMemoryCacheClient.cs index 1d746877..20ac10ba 100644 --- a/src/Foundatio/Caching/InMemoryCacheClient.cs +++ b/src/Foundatio/Caching/InMemoryCacheClient.cs @@ -182,7 +182,8 @@ public Task RemoveAllAsync(IEnumerable keys = null) public Task RemoveByPrefixAsync(string prefix) { var keysToRemove = new List(); - var regex = new Regex(String.Concat(prefix, "*").Replace("*", ".*").Replace("?", ".+")); + string normalizedPrefix = String.IsNullOrWhiteSpace(prefix) ? "*" : prefix.Trim(); + var regex = new Regex(String.Concat("^", Regex.Escape(normalizedPrefix.Contains("*") ? normalizedPrefix : $"{normalizedPrefix}*"), "$").Replace("\\*", ".*?")); try { foreach (string key in _memory.Keys.ToList()) diff --git a/tests/Foundatio.Tests/Caching/InMemoryCacheClientTests.cs b/tests/Foundatio.Tests/Caching/InMemoryCacheClientTests.cs index 48f36669..b98bca34 100644 --- a/tests/Foundatio.Tests/Caching/InMemoryCacheClientTests.cs +++ b/tests/Foundatio.Tests/Caching/InMemoryCacheClientTests.cs @@ -83,6 +83,12 @@ public override Task CanRemoveByPrefixAsync() return base.CanRemoveByPrefixAsync(); } + [Fact] + public override Task CanRemoveByPrefixWithScopedCachesAsync() + { + return base.CanRemoveByPrefixWithScopedCachesAsync(); + } + [Theory] [InlineData(50)] [InlineData(500)] diff --git a/tests/Foundatio.Tests/Caching/InMemoryHybridCacheClientTests.cs b/tests/Foundatio.Tests/Caching/InMemoryHybridCacheClientTests.cs index 6e00af01..27c9091a 100644 --- a/tests/Foundatio.Tests/Caching/InMemoryHybridCacheClientTests.cs +++ b/tests/Foundatio.Tests/Caching/InMemoryHybridCacheClientTests.cs @@ -40,6 +40,12 @@ public override Task CanRemoveByPrefixAsync() return base.CanRemoveByPrefixAsync(); } + [Fact] + public override Task CanRemoveByPrefixWithScopedCachesAsync() + { + return base.CanRemoveByPrefixWithScopedCachesAsync(); + } + [Theory] [InlineData(50)] [InlineData(500)]