From 61bc4255ec05f8b4fbd12ea39d3487869f84da80 Mon Sep 17 00:00:00 2001 From: Blake Niemyjski Date: Tue, 21 Oct 2025 16:06:18 -0500 Subject: [PATCH 01/26] Improves Redis cache list retrieval performance Optimizes list retrieval by using SortedSetRangeByScoreAsync instead of SortedSetRangeByRankAsync, utilizing timestamp-based scores for efficient expiration management. Handles legacy sets by migrating them to sorted sets when encountering WRONGTYPE exceptions, ensuring compatibility and preventing data loss during the transition. Logs trace information when a sorted set is empty during expiration setting. --- src/Foundatio.Redis/Cache/RedisCacheClient.cs | 78 +++++++++++-------- 1 file changed, 46 insertions(+), 32 deletions(-) diff --git a/src/Foundatio.Redis/Cache/RedisCacheClient.cs b/src/Foundatio.Redis/Cache/RedisCacheClient.cs index 8080d2f..bac34f9 100644 --- a/src/Foundatio.Redis/Cache/RedisCacheClient.cs +++ b/src/Foundatio.Redis/Cache/RedisCacheClient.cs @@ -246,6 +246,7 @@ private CacheValue RedisValueToCacheValue(RedisValue redisValue) } } + public async Task>> GetAllAsync(IEnumerable keys) { if (keys is null) @@ -256,6 +257,7 @@ public async Task>> GetAllAsync(IEnumerable if (redisKeys.Length == 0) return result; + // parallelize? if (_options.ConnectionMultiplexer.IsCluster()) { foreach (var hashSlotGroup in redisKeys.GroupBy(k => _options.ConnectionMultiplexer.HashSlot(k))) @@ -284,19 +286,25 @@ public async Task>> GetListAsync(string key, int? p if (page is < 1) throw new ArgumentOutOfRangeException(nameof(page), "Page cannot be less than 1"); - await RemoveExpiredListValuesAsync(key, typeof(T) == typeof(string)).AnyContext(); - - if (!page.HasValue) + try { - var set = await Database.SortedSetRangeByScoreAsync(key, flags: _options.ReadMode).AnyContext(); - return RedisValuesToCacheValue(set); + if (!page.HasValue) + { + var set = await Database.SortedSetRangeByScoreAsync(key, _timeProvider.GetUtcNow().ToUnixTimeMilliseconds() + 1, flags: _options.ReadMode).AnyContext(); + return RedisValuesToCacheValue(set); + } + else + { + long skip = (page.Value - 1) * pageSize; + var set = await Database.SortedSetRangeByScoreAsync(key, _timeProvider.GetUtcNow().ToUnixTimeMilliseconds() + 1, skip: skip, take: pageSize, flags: _options.ReadMode).AnyContext(); + return RedisValuesToCacheValue(set); + } } - else + catch (RedisServerException ex) when (ex.Message.StartsWith("WRONGTYPE")) { - long start = (page.Value - 1) * pageSize; - long end = start + pageSize - 1; - var set = await Database.SortedSetRangeByRankAsync(key, start, end, flags: _options.ReadMode).AnyContext(); - return RedisValuesToCacheValue(set); + _logger.LogInformation(ex, "Migrating legacy set to sorted set for key: {Key}", key); + await MigrateLegacySetToSortedSetForKeyAsync(key, typeof(T) == typeof(string)).AnyContext(); + return await GetListAsync(key, page, pageSize).AnyContext(); } } @@ -375,7 +383,10 @@ private async Task SetListExpirationAsync(string key) { var items = await Database.SortedSetRangeByRankWithScoresAsync(key, 0, 0, order: Order.Descending).AnyContext(); if (items.Length == 0) + { + _logger.LogTrace("Sorted set is empty for key: {Key}, expiration will not be set", key); return; + } long highestExpirationInMs = (long)items.Single().Score; if (highestExpirationInMs > MaxUnixEpochMilliseconds) @@ -403,32 +414,35 @@ private async Task RemoveExpiredListValuesAsync(string key, bool isStringValu catch (RedisServerException ex) when (ex.Message.StartsWith("WRONGTYPE")) { _logger.LogInformation(ex, "Migrating legacy set to sorted set for key: {Key}", key); + await MigrateLegacySetToSortedSetForKeyAsync(key, isStringValues).AnyContext(); + } + } - // convert legacy set to sorted set - var oldItems = await Database.SetMembersAsync(key).AnyContext(); - await Database.KeyDeleteAsync(key).AnyContext(); - - var currentKeyExpiresIn = await GetExpirationAsync(key).AnyContext(); - if (isStringValues) - { - var oldItemValues = new List(oldItems.Length); - foreach (string oldItem in RedisValuesToCacheValue(oldItems).Value) - oldItemValues.Add(oldItem); - - await ListAddAsync(key, oldItemValues, currentKeyExpiresIn).AnyContext(); - } - else - { - var oldItemValues = new List(oldItems.Length); - foreach (var oldItem in RedisValuesToCacheValue(oldItems).Value) - oldItemValues.Add(oldItem); + private async Task MigrateLegacySetToSortedSetForKeyAsync(string key, bool isStringValues) + { + // convert legacy set to sorted set + var oldItems = await Database.SetMembersAsync(key).AnyContext(); + var currentKeyExpiresIn = await GetExpirationAsync(key).AnyContext(); + await Database.KeyDeleteAsync(key).AnyContext(); + if (isStringValues) + { + var oldItemValues = new List(oldItems.Length); + foreach (string oldItem in RedisValuesToCacheValue(oldItems).Value) + oldItemValues.Add(oldItem); - await ListAddAsync(key, oldItemValues, currentKeyExpiresIn).AnyContext(); - } + await ListAddAsync(key, oldItemValues, currentKeyExpiresIn).AnyContext(); + } + else + { + var oldItemValues = new List(oldItems.Length); + foreach (var oldItem in RedisValuesToCacheValue(oldItems).Value) + oldItemValues.Add(oldItem); - if (currentKeyExpiresIn.HasValue) - await Database.KeyExpireAsync(key, (DateTime?)null).AnyContext(); + await ListAddAsync(key, oldItemValues, currentKeyExpiresIn).AnyContext(); } + + if (currentKeyExpiresIn.HasValue) + await Database.KeyExpireAsync(key, (DateTime?)null).AnyContext(); } public Task SetAsync(string key, T value, TimeSpan? expiresIn = null) From df6857154ecd71c089bb4ce80f6f12e52145bb5f Mon Sep 17 00:00:00 2001 From: Blake Niemyjski Date: Tue, 21 Oct 2025 16:53:41 -0500 Subject: [PATCH 02/26] Improves Redis cache list retrieval performance. Updates the sorted set range query for retrieving cached lists to use Double.PositiveInfinity for the maximum score, ensuring all valid items are returned. This change enhances performance by avoiding unnecessary additions of 1 millisecond to the timestamp, streamlining the retrieval process and improving efficiency. --- src/Foundatio.Redis/Cache/RedisCacheClient.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/Foundatio.Redis/Cache/RedisCacheClient.cs b/src/Foundatio.Redis/Cache/RedisCacheClient.cs index bac34f9..dcb64b1 100644 --- a/src/Foundatio.Redis/Cache/RedisCacheClient.cs +++ b/src/Foundatio.Redis/Cache/RedisCacheClient.cs @@ -246,7 +246,6 @@ private CacheValue RedisValueToCacheValue(RedisValue redisValue) } } - public async Task>> GetAllAsync(IEnumerable keys) { if (keys is null) @@ -290,13 +289,13 @@ public async Task>> GetListAsync(string key, int? p { if (!page.HasValue) { - var set = await Database.SortedSetRangeByScoreAsync(key, _timeProvider.GetUtcNow().ToUnixTimeMilliseconds() + 1, flags: _options.ReadMode).AnyContext(); + var set = await Database.SortedSetRangeByScoreAsync(key, _timeProvider.GetUtcNow().ToUnixTimeMilliseconds(), Double.PositiveInfinity, Exclude.Start, flags: _options.ReadMode).AnyContext(); return RedisValuesToCacheValue(set); } else { long skip = (page.Value - 1) * pageSize; - var set = await Database.SortedSetRangeByScoreAsync(key, _timeProvider.GetUtcNow().ToUnixTimeMilliseconds() + 1, skip: skip, take: pageSize, flags: _options.ReadMode).AnyContext(); + var set = await Database.SortedSetRangeByScoreAsync(key, _timeProvider.GetUtcNow().ToUnixTimeMilliseconds(), Double.PositiveInfinity, Exclude.Start, skip: skip, take: pageSize, flags: _options.ReadMode).AnyContext(); return RedisValuesToCacheValue(set); } } @@ -440,9 +439,6 @@ private async Task MigrateLegacySetToSortedSetForKeyAsync(string key, bool is await ListAddAsync(key, oldItemValues, currentKeyExpiresIn).AnyContext(); } - - if (currentKeyExpiresIn.HasValue) - await Database.KeyExpireAsync(key, (DateTime?)null).AnyContext(); } public Task SetAsync(string key, T value, TimeSpan? expiresIn = null) From cb640e034d464aa2eaaf295b1a82f8389e9a55fb Mon Sep 17 00:00:00 2001 From: Blake Niemyjski Date: Tue, 21 Oct 2025 16:55:32 -0500 Subject: [PATCH 03/26] Remove comment --- src/Foundatio.Redis/Cache/RedisCacheClient.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Foundatio.Redis/Cache/RedisCacheClient.cs b/src/Foundatio.Redis/Cache/RedisCacheClient.cs index dcb64b1..4bd075d 100644 --- a/src/Foundatio.Redis/Cache/RedisCacheClient.cs +++ b/src/Foundatio.Redis/Cache/RedisCacheClient.cs @@ -256,7 +256,6 @@ public async Task>> GetAllAsync(IEnumerable if (redisKeys.Length == 0) return result; - // parallelize? if (_options.ConnectionMultiplexer.IsCluster()) { foreach (var hashSlotGroup in redisKeys.GroupBy(k => _options.ConnectionMultiplexer.HashSlot(k))) From 9aa8e42374bf8349ab00748eb443b158e6e30cc0 Mon Sep 17 00:00:00 2001 From: Blake Niemyjski Date: Thu, 6 Nov 2025 15:26:55 -0600 Subject: [PATCH 04/26] .NET 8 improvements --- src/Foundatio.Redis/Cache/RedisCacheClient.cs | 77 ++++++++++++------- .../Extensions/EnumerableExtensions.cs | 35 --------- 2 files changed, 50 insertions(+), 62 deletions(-) diff --git a/src/Foundatio.Redis/Cache/RedisCacheClient.cs b/src/Foundatio.Redis/Cache/RedisCacheClient.cs index 4bd075d..1b50931 100644 --- a/src/Foundatio.Redis/Cache/RedisCacheClient.cs +++ b/src/Foundatio.Redis/Cache/RedisCacheClient.cs @@ -1,6 +1,9 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Foundatio.AsyncEx; using Foundatio.Extensions; @@ -104,7 +107,7 @@ public async Task RemoveAllAsync(IEnumerable keys = null) await foreach (var key in server.KeysAsync(_options.Database).ConfigureAwait(false)) seen.Add(key); - foreach (var batch in seen.Batch(batchSize)) + foreach (var batch in seen.Chunk(batchSize)) deleted += await Database.KeyDeleteAsync(batch.ToArray()).AnyContext(); } catch (Exception ex) @@ -115,25 +118,28 @@ public async Task RemoveAllAsync(IEnumerable keys = null) } else if (Database.Multiplexer.IsCluster()) { - foreach (var redisKeys in keys.Where(k => !String.IsNullOrEmpty(k)).Select(k => (RedisKey)k).Batch(batchSize)) + foreach (var redisKeys in keys.Where(k => !String.IsNullOrEmpty(k)).Select(k => (RedisKey)k).Chunk(batchSize)) { - foreach (var hashSlotGroup in redisKeys.GroupBy(k => Database.Multiplexer.HashSlot(k))) - { - var hashSlotKeys = hashSlotGroup.ToArray(); - try + await Parallel.ForEachAsync( + redisKeys.GroupBy(k => Database.Multiplexer.HashSlot(k)), + async (hashSlotGroup, ct) => { - deleted += await Database.KeyDeleteAsync(hashSlotKeys).AnyContext(); - } - catch (Exception ex) - { - _logger.LogError(ex, "Unable to delete {HashSlot} keys ({Keys}): {Message}", hashSlotGroup.Key, hashSlotKeys, ex.Message); - } - } + var hashSlotKeys = hashSlotGroup.ToArray(); + try + { + long count = await Database.KeyDeleteAsync(hashSlotKeys).AnyContext(); + Interlocked.Add(ref deleted, count); + } + catch (Exception ex) + { + _logger.LogError(ex, "Unable to delete {HashSlot} keys ({Keys}): {Message}", hashSlotGroup.Key, hashSlotKeys, ex.Message); + } + }).AnyContext(); } } else { - foreach (var redisKeys in keys.Where(k => !String.IsNullOrEmpty(k)).Select(k => (RedisKey)k).Batch(batchSize)) + foreach (var redisKeys in keys.Where(k => !String.IsNullOrEmpty(k)).Select(k => (RedisKey)k).Chunk(batchSize)) { try { @@ -176,8 +182,13 @@ public async Task RemoveByPrefixAsync(string prefix) { if (isCluster) { - foreach (var slotGroup in keys.GroupBy(k => _options.ConnectionMultiplexer.HashSlot(k))) - deleted += await Database.KeyDeleteAsync(slotGroup.ToArray()).AnyContext(); + await Parallel.ForEachAsync( + keys.GroupBy(k => _options.ConnectionMultiplexer.HashSlot(k)), + async (slotGroup, ct) => + { + long count = await Database.KeyDeleteAsync(slotGroup.ToArray()).AnyContext(); + Interlocked.Add(ref deleted, count); + }).AnyContext(); } else { @@ -252,28 +263,40 @@ public async Task>> GetAllAsync(IEnumerable throw new ArgumentNullException(nameof(keys)); var redisKeys = keys.Distinct().Select(k => (RedisKey)k).ToArray(); - var result = new Dictionary>(redisKeys.Length); if (redisKeys.Length == 0) - return result; + return ReadOnlyDictionary>.Empty; if (_options.ConnectionMultiplexer.IsCluster()) { - foreach (var hashSlotGroup in redisKeys.GroupBy(k => _options.ConnectionMultiplexer.HashSlot(k))) - { - var hashSlotKeys = hashSlotGroup.ToArray(); - var values = await Database.StringGetAsync(hashSlotKeys, _options.ReadMode).AnyContext(); - for (int i = 0; i < hashSlotKeys.Length; i++) - result[hashSlotKeys[i]] = RedisValueToCacheValue(values[i]); - } + // Use the default concurrency on .NET 8 (-1) + var result = new ConcurrentDictionary>(-1, redisKeys.Length); + await Parallel.ForEachAsync( + redisKeys.GroupBy(k => _options.ConnectionMultiplexer.HashSlot(k)), + async (hashSlotGroup, ct) => + { + var hashSlotKeys = hashSlotGroup.ToArray(); + var values = await Database.StringGetAsync(hashSlotKeys, _options.ReadMode).AnyContext(); + + // Redis MGET guarantees that values are returned in the same order as keys. + // Non-existent keys return nil/empty values in their respective positions. + // https://redis.io/commands/mget + for (int i = 0; i < hashSlotKeys.Length; i++) + result[hashSlotKeys[i]] = RedisValueToCacheValue(values[i]); + }).AnyContext(); + + return result.AsReadOnly(); } else { + var result = new Dictionary>(redisKeys.Length); var values = await Database.StringGetAsync(redisKeys, _options.ReadMode).AnyContext(); + + // Redis MGET guarantees that values are returned in the same order as keys for (int i = 0; i < redisKeys.Length; i++) result[redisKeys[i]] = RedisValueToCacheValue(values[i]); - } - return result; + return result.AsReadOnly(); + } } public async Task>> GetListAsync(string key, int? page = null, int pageSize = 100) diff --git a/src/Foundatio.Redis/Extensions/EnumerableExtensions.cs b/src/Foundatio.Redis/Extensions/EnumerableExtensions.cs index b190101..e4c11c3 100644 --- a/src/Foundatio.Redis/Extensions/EnumerableExtensions.cs +++ b/src/Foundatio.Redis/Extensions/EnumerableExtensions.cs @@ -8,41 +8,6 @@ namespace Foundatio.Redis.Extensions; internal static class EnumerableExtensions { - public static IEnumerable Batch( - this IEnumerable source, - int batchSize) - { - if (source == null) throw new ArgumentNullException(nameof(source)); - if (batchSize <= 0) throw new ArgumentOutOfRangeException(nameof(batchSize)); - - using var e = source.GetEnumerator(); - while (true) - { - // allocate one buffer for this batch - var buffer = new T[batchSize]; - int count = 0; - - while (count < batchSize && e.MoveNext()) - buffer[count++] = e.Current; - - if (count == 0) - yield break; - - if (count == batchSize) - { - yield return buffer; - } - else - { - // final partial batch: copy to a right-sized array - var tail = new T[count]; - Array.Copy(buffer, tail, count); - yield return tail; - yield break; - } - } - } - public static async IAsyncEnumerable BatchAsync( this IAsyncEnumerable source, int batchSize, From 58863a27311dbde2b243a3011b86b8360e43dbf7 Mon Sep 17 00:00:00 2001 From: Blake Niemyjski Date: Thu, 6 Nov 2025 15:29:12 -0600 Subject: [PATCH 05/26] Adds GetAllExpiration and SetAllExpiration Implements GetAllExpiration and SetAllExpiration methods for retrieving and setting expirations for multiple cache keys. This significantly improves performance when dealing with numerous keys by leveraging Lua scripts and parallel processing, minimizing round trips to the Redis server. It also optimizes the RemoveAllAsync method to use parallel batch deletions and flushdb where possible. --- src/Foundatio.Redis/Cache/RedisCacheClient.cs | 270 +++++++++++++++--- .../Scripts/GetAllExpiration.lua | 15 + .../Scripts/SetAllExpiration.lua | 17 ++ .../Caching/RedisCacheClientTests.cs | 72 ++++- .../Caching/RedisHybridCacheClientTests.cs | 72 ++++- .../Caching/ScopedRedisCacheClientTests.cs | 72 ++++- .../ScopedRedisHybridCacheClientTests.cs | 72 ++++- 7 files changed, 517 insertions(+), 73 deletions(-) create mode 100644 src/Foundatio.Redis/Scripts/GetAllExpiration.lua create mode 100644 src/Foundatio.Redis/Scripts/SetAllExpiration.lua diff --git a/src/Foundatio.Redis/Cache/RedisCacheClient.cs b/src/Foundatio.Redis/Cache/RedisCacheClient.cs index 1b50931..4e4f929 100644 --- a/src/Foundatio.Redis/Cache/RedisCacheClient.cs +++ b/src/Foundatio.Redis/Cache/RedisCacheClient.cs @@ -31,8 +31,11 @@ public sealed class RedisCacheClient : ICacheClient, IHaveSerializer private LoadedLuaScript _replaceIfEqual; private LoadedLuaScript _setIfHigher; private LoadedLuaScript _setIfLower; + private LoadedLuaScript _getAllExpiration; + private LoadedLuaScript _setAllExpiration; public RedisCacheClient(RedisCacheClientOptions options) + { _options = options; _timeProvider = options.TimeProvider ?? TimeProvider.System; @@ -80,41 +83,63 @@ public async Task RemoveAllAsync(IEnumerable keys = null) if (endpoints.Length == 0) return 0; - foreach (var endpoint in endpoints) - { - var server = _options.ConnectionMultiplexer.GetServer(endpoint); - if (server.IsReplica) - continue; + // Get non-replica endpoints for processing + var nonReplicaEndpoints = endpoints + .Select(endpoint => _options.ConnectionMultiplexer.GetServer(endpoint)) + .Where(server => !server.IsReplica) + .ToArray(); + + // Most Redis deployments have few endpoints (1-3), so parallelism here is helpful + // but not critical. Controlling it helps prevent excessive load on Redis cluster. + int maxEndpointParallelism = Math.Min(Environment.ProcessorCount, nonReplicaEndpoints.Length); + await Parallel.ForEachAsync(nonReplicaEndpoints, new ParallelOptions { MaxDegreeOfParallelism = maxEndpointParallelism }, + async (server, ct) => + { + // Try FLUSHDB first (fastest approach) + bool flushed = false; try { long dbSize = await server.DatabaseSizeAsync(_options.Database).AnyContext(); await server.FlushDatabaseAsync(_options.Database).AnyContext(); - deleted += dbSize; - continue; + Interlocked.Add(ref deleted, dbSize); + flushed = true; } catch (Exception ex) { - _logger.LogError(ex, "Unable to flush database: {Message}", ex.Message); + _logger.LogError(ex, "Unable to flush database on {Endpoint}: {Message}", server.EndPoint, ex.Message); } - try - { - // NOTE: We need to use a HashSet to avoid duplicate counts due to SCAN is non-deterministic. - // A Performance win could be had if we are sure dbSize didn't fail and we know nothing was changing - // keys while we were deleting. - var seen = new HashSet(); - await foreach (var key in server.KeysAsync(_options.Database).ConfigureAwait(false)) - seen.Add(key); - - foreach (var batch in seen.Chunk(batchSize)) - deleted += await Database.KeyDeleteAsync(batch.ToArray()).AnyContext(); - } - catch (Exception ex) + // If FLUSHDB failed, fall back to SCAN + DELETE + if (!flushed) { - _logger.LogError(ex, "Error deleting all keys: {Message}", ex.Message); + try + { + // NOTE: We need to use a HashSet to avoid duplicate counts due to SCAN is non-deterministic. + // A Performance win could be had if we are sure dbSize didn't fail and we know nothing was changing + // keys while we were deleting. + var seen = new HashSet(); + await foreach (var key in server.KeysAsync(_options.Database).ConfigureAwait(false)) + seen.Add(key); + + // Parallelize batch deletions with moderate concurrency to avoid Redis overload + // Redis can become unstable with too many concurrent connections/requests + // See: https://redis.io/docs/reference/clients/ + // Lower limit (8) since this runs after FLUSHDB failed, meaning we're deleting + // potentially thousands or millions of keys - must avoid overwhelming Redis + int maxParallelism = Math.Min(8, Environment.ProcessorCount); + await Parallel.ForEachAsync(seen.Chunk(batchSize), new ParallelOptions { MaxDegreeOfParallelism = maxParallelism }, async (batch, ct) => + { + long count = await Database.KeyDeleteAsync(batch.ToArray()).AnyContext(); + Interlocked.Add(ref deleted, count); + }).AnyContext(); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error deleting all keys on {Endpoint}: {Message}", server.EndPoint, ex.Message); + } } - } + }).AnyContext(); } else if (Database.Multiplexer.IsCluster()) { @@ -139,17 +164,26 @@ await Parallel.ForEachAsync( } else { - foreach (var redisKeys in keys.Where(k => !String.IsNullOrEmpty(k)).Select(k => (RedisKey)k).Chunk(batchSize)) + var keyBatches = keys.Where(k => !String.IsNullOrEmpty(k)) + .Select(k => (RedisKey)k) + .Chunk(batchSize) + .ToArray(); + + // Parallelize batch deletions with moderate concurrency to avoid Redis overload + // Limit parallelism to 8 or Environment.ProcessorCount, whichever is smaller + int maxParallelism = Math.Min(8, Environment.ProcessorCount); + await Parallel.ForEachAsync(keyBatches, new ParallelOptions { MaxDegreeOfParallelism = maxParallelism }, async (redisKeys, ct) => { try { - deleted += await Database.KeyDeleteAsync(redisKeys).AnyContext(); + long count = await Database.KeyDeleteAsync(redisKeys).AnyContext(); + Interlocked.Add(ref deleted, count); } catch (Exception ex) { _logger.LogError(ex, "Unable to delete keys ({Keys}): {Message}", redisKeys, ex.Message); } - } + }).AnyContext(); } return (int)deleted; @@ -671,6 +705,110 @@ public Task SetExpirationAsync(string key, TimeSpan expiresIn) return Database.KeyExpireAsync(key, expiresIn); } + public async Task> GetAllExpirationAsync(IEnumerable keys) + { + if (keys == null) + throw new ArgumentNullException(nameof(keys)); + + var keyList = keys.Where(k => !String.IsNullOrEmpty(k)).Distinct().ToList(); + if (keyList.Count == 0) + return ReadOnlyDictionary.Empty; + + await LoadScriptsAsync().AnyContext(); + + if (_options.ConnectionMultiplexer.IsCluster()) + { + // Use the default concurrency on .NET 8 (-1) + var result = new ConcurrentDictionary(-1, keyList.Count); + await Parallel.ForEachAsync( + keyList.GroupBy(k => _options.ConnectionMultiplexer.HashSlot(k)), + async (hashSlotGroup, ct) => + { + var hashSlotKeys = hashSlotGroup.Select(k => (RedisKey)k).ToArray(); + var redisResult = await Database.ScriptEvaluateAsync(_getAllExpiration.Hash, hashSlotKeys).AnyContext(); + + if (redisResult.IsNull) + return; + + // Lua script returns array of TTL values in milliseconds (in same order as keys) + // -2 = key doesn't exist, -1 = no expiration, positive = TTL in ms + var ttls = (long[])redisResult; + for (int i = 0; i < hashSlotKeys.Length; i++) + { + string key = hashSlotKeys[i]; + long ttl = ttls[i]; + + if (ttl >= 0) // Only include keys with positive TTL (exclude non-existent and persistent) + result[key] = TimeSpan.FromMilliseconds(ttl); + } + }).AnyContext(); + + return result.AsReadOnly(); + } + else + { + var redisKeys = keyList.Select(k => (RedisKey)k).ToArray(); + var redisResult = await Database.ScriptEvaluateAsync(_getAllExpiration.Hash, redisKeys).AnyContext(); + + if (redisResult.IsNull) + return ReadOnlyDictionary.Empty; + + // Lua script returns array of TTL values in milliseconds (in same order as keys) + // -2 = key doesn't exist, -1 = no expiration, positive = TTL in ms + var ttls = (long[])redisResult; + var result = new Dictionary(); + + for (int i = 0; i < redisKeys.Length; i++) + { + string key = redisKeys[i]; + long ttl = ttls[i]; + + if (ttl >= 0) // Only include keys with positive TTL (exclude non-existent and persistent) + result[key] = TimeSpan.FromMilliseconds(ttl); + } + + return result.AsReadOnly(); + } + } + + public async Task SetAllExpirationAsync(IDictionary expirations) + { + if (expirations == null) + throw new ArgumentNullException(nameof(expirations)); + + var validExpirations = expirations.Where(kvp => !String.IsNullOrEmpty(kvp.Key)).ToList(); + + if (validExpirations.Count == 0) + return; + + await LoadScriptsAsync().AnyContext(); + + if (_options.ConnectionMultiplexer.IsCluster()) + { + await Parallel.ForEachAsync( + validExpirations.GroupBy(kvp => _options.ConnectionMultiplexer.HashSlot(kvp.Key)), + async (hashSlotGroup, ct) => + { + var hashSlotExpirations = hashSlotGroup.ToList(); + var keys = hashSlotExpirations.Select(kvp => (RedisKey)kvp.Key).ToArray(); + var values = hashSlotExpirations + .Select(kvp => (RedisValue)(kvp.Value.HasValue ? (long)kvp.Value.Value.TotalMilliseconds : -1)) + .ToArray(); + + await Database.ScriptEvaluateAsync(_setAllExpiration.Hash, keys, values).AnyContext(); + }).AnyContext(); + } + else + { + var keys = validExpirations.Select(kvp => (RedisKey)kvp.Key).ToArray(); + var values = validExpirations + .Select(kvp => (RedisValue)(kvp.Value.HasValue ? (long)kvp.Value.Value.TotalMilliseconds : -1)) + .ToArray(); + + await Database.ScriptEvaluateAsync(_setAllExpiration.Hash, keys, values).AnyContext(); + } + } + private async Task LoadScriptsAsync() { if (_scriptsLoaded) @@ -681,25 +819,88 @@ private async Task LoadScriptsAsync() if (_scriptsLoaded) return; + // Prepare all the Lua scripts var incrementWithExpire = LuaScript.Prepare(IncrementWithScript); var removeIfEqual = LuaScript.Prepare(RemoveIfEqualScript); var replaceIfEqual = LuaScript.Prepare(ReplaceIfEqualScript); var setIfHigher = LuaScript.Prepare(SetIfHigherScript); var setIfLower = LuaScript.Prepare(SetIfLowerScript); + var getAllExpiration = LuaScript.Prepare(GetAllExpirationScript); + var setAllExpiration = LuaScript.Prepare(SetAllExpirationScript); + + // Get all non-replica endpoints + var endpoints = _options.ConnectionMultiplexer.GetEndPoints() + .Select(ep => _options.ConnectionMultiplexer.GetServer(ep)) + .Where(server => !server.IsReplica) + .ToArray(); + + if (endpoints.Length == 0) + return; - foreach (var endpoint in _options.ConnectionMultiplexer.GetEndPoints()) + // In Redis Cluster, each node maintains its own script cache + // Scripts must be loaded on every node where they might execute + // See: https://redis.io/docs/latest/develop/programmability/eval-intro/#evalsha-and-script-load + // See: https://redis.io/docs/management/scaling/#redis-cluster-architecture + + // Store the loaded scripts from each node separately + // We'll load scripts on all servers in parallel first, then set the class fields + // once using the results from any server (scripts have same SHA everywhere) + + // Create a task to load scripts on all servers in parallel + var loadTasks = new List>(); + + // Limit parallelism to Environment.ProcessorCount + int maxParallelism = Math.Min(Environment.ProcessorCount, endpoints.Length); + var options = new ParallelOptions { MaxDegreeOfParallelism = maxParallelism }; + + // Start script loading tasks for all endpoints + foreach (var server in endpoints) { - var server = _options.ConnectionMultiplexer.GetServer(endpoint); if (server.IsReplica) continue; - _incrementWithExpire = await incrementWithExpire.LoadAsync(server).AnyContext(); - _removeIfEqual = await removeIfEqual.LoadAsync(server).AnyContext(); - _replaceIfEqual = await replaceIfEqual.LoadAsync(server).AnyContext(); - _setIfHigher = await setIfHigher.LoadAsync(server).AnyContext(); - _setIfLower = await setIfLower.LoadAsync(server).AnyContext(); + // Create and start task for this server - completely independent + var loadTask = Task.Run(async () => { + // Load all scripts on this server + var incr = await incrementWithExpire.LoadAsync(server).AnyContext(); + var remove = await removeIfEqual.LoadAsync(server).AnyContext(); + var replace = await replaceIfEqual.LoadAsync(server).AnyContext(); + var setHigher = await setIfHigher.LoadAsync(server).AnyContext(); + var setLower = await setIfLower.LoadAsync(server).AnyContext(); + var getAllExp = await getAllExpiration.LoadAsync(server).AnyContext(); + var setAllExp = await setAllExpiration.LoadAsync(server).AnyContext(); + + return (incr, remove, replace, setHigher, setLower, getAllExp, setAllExp); + }); + + loadTasks.Add(loadTask); } + // Wait for any server to complete loading its scripts + // All should produce identical SHA hashes, so we only need one result + var completedTask = await Task.WhenAny(loadTasks).AnyContext(); + var scripts = await completedTask.AnyContext(); + + // Continue loading on other servers in the background (required for Redis Cluster) + // but don't wait for them to finish - the scripts are available immediately + // once loaded on at least one node + + // Assign the results to the instance fields + _incrementWithExpire = scripts.IncrementWithExpire; + _removeIfEqual = scripts.RemoveIfEqual; + _replaceIfEqual = scripts.ReplaceIfEqual; + _setIfHigher = scripts.SetIfHigher; + _setIfLower = scripts.SetIfLower; + _getAllExpiration = scripts.GetAllExpiration; + _setAllExpiration = scripts.SetAllExpiration; + _scriptsLoaded = true; } } @@ -722,4 +923,7 @@ public void Dispose() private static readonly string ReplaceIfEqualScript = EmbeddedResourceLoader.GetEmbeddedResource("Foundatio.Redis.Scripts.ReplaceIfEqual.lua"); private static readonly string SetIfHigherScript = EmbeddedResourceLoader.GetEmbeddedResource("Foundatio.Redis.Scripts.SetIfHigher.lua"); private static readonly string SetIfLowerScript = EmbeddedResourceLoader.GetEmbeddedResource("Foundatio.Redis.Scripts.SetIfLower.lua"); + private static readonly string GetAllExpirationScript = EmbeddedResourceLoader.GetEmbeddedResource("Foundatio.Redis.Scripts.GetAllExpiration.lua"); + private static readonly string SetAllExpirationScript = EmbeddedResourceLoader.GetEmbeddedResource("Foundatio.Redis.Scripts.SetAllExpiration.lua"); } + diff --git a/src/Foundatio.Redis/Scripts/GetAllExpiration.lua b/src/Foundatio.Redis/Scripts/GetAllExpiration.lua new file mode 100644 index 0000000..9d563c4 --- /dev/null +++ b/src/Foundatio.Redis/Scripts/GetAllExpiration.lua @@ -0,0 +1,15 @@ +-- Get expiration times for multiple keys +-- KEYS: All keys to check expiration for +-- Returns: Array of TTL values in milliseconds (in same order as KEYS) +-- Returns -2 for non-existent keys +-- Returns -1 for keys without expiration +-- Returns positive number for keys with expiration + +local result = {} + +for i = 1, #KEYS do + local ttl = redis.call('pttl', KEYS[i]) + table.insert(result, ttl) +end + +return result diff --git a/src/Foundatio.Redis/Scripts/SetAllExpiration.lua b/src/Foundatio.Redis/Scripts/SetAllExpiration.lua new file mode 100644 index 0000000..a7510c9 --- /dev/null +++ b/src/Foundatio.Redis/Scripts/SetAllExpiration.lua @@ -0,0 +1,17 @@ +-- Set expiration times for multiple keys +-- KEYS: All keys to set expiration for +-- ARGV: TTL values in milliseconds corresponding to each key in KEYS +-- Use -1 or 0 to remove expiration (persist) +-- Positive values set the expiration + +for i = 1, #KEYS do + local ttl = tonumber(ARGV[i]) + + if ttl == nil or ttl <= 0 then + redis.call('persist', KEYS[i]) + else + redis.call('pexpire', KEYS[i], math.ceil(ttl)) + end +end + +return true diff --git a/tests/Foundatio.Redis.Tests/Caching/RedisCacheClientTests.cs b/tests/Foundatio.Redis.Tests/Caching/RedisCacheClientTests.cs index eb55d72..411d4bc 100644 --- a/tests/Foundatio.Redis.Tests/Caching/RedisCacheClientTests.cs +++ b/tests/Foundatio.Redis.Tests/Caching/RedisCacheClientTests.cs @@ -76,12 +76,6 @@ public override Task CanUseScopedCachesAsync() return base.CanUseScopedCachesAsync(); } - [Fact] - public override Task CanSetAndGetObjectAsync() - { - return base.CanSetAndGetObjectAsync(); - } - [Fact] public override Task CanRemoveAllAsync() { @@ -170,15 +164,73 @@ public override Task CanRemoveByPrefixMultipleEntriesAsync(int count) } [Fact] - public override Task CanSetExpirationAsync() + public override Task CanSetAndGetObjectAsync() + { + return base.CanSetAndGetObjectAsync(); + } + + [Fact] + public override Task GetExpirationAsync_WithVariousStates_ReturnsCorrectly() + { + return base.GetExpirationAsync_WithVariousStates_ReturnsCorrectly(); + } + + [Fact] + public override Task GetAllExpiration_WithMultipleKeys_ReturnsAllExpirations() + { + return base.GetAllExpiration_WithMultipleKeys_ReturnsAllExpirations(); + } + + [Theory] + [InlineData(1000)] + [InlineData(10000)] + public override Task GetAllExpiration_WithLargeNumberOfKeys_ReturnsAllExpirations(int count) + { + return base.GetAllExpiration_WithLargeNumberOfKeys_ReturnsAllExpirations(count); + } + + [Fact] + public override Task GetAllExpiration_WithExpiredKeys_ReturnsNullForExpiredKeys() + { + return base.GetAllExpiration_WithExpiredKeys_ReturnsNullForExpiredKeys(); + } + + [Fact] + public override Task SetExpirationAsync_WithValidDateTime_SetsExpirationCorrectly() + { + return base.SetExpirationAsync_WithValidDateTime_SetsExpirationCorrectly(); + } + + [Fact] + public override Task SetExpirationAsync_WithMinMaxValues_HandlesEdgeCases() + { + return base.SetExpirationAsync_WithMinMaxValues_HandlesEdgeCases(); + } + + [Fact] + public override Task SetAllExpiration_WithMultipleKeys_SetsExpirationForAll() + { + return base.SetAllExpiration_WithMultipleKeys_SetsExpirationForAll(); + } + + [Fact] + public override Task SetAllExpiration_WithNullValues_RemovesExpiration() + { + return base.SetAllExpiration_WithNullValues_RemovesExpiration(); + } + + [Theory] + [InlineData(1000)] + [InlineData(10000)] + public override Task SetAllExpiration_WithLargeNumberOfKeys_SetsAllExpirations(int count) { - return base.CanSetExpirationAsync(); + return base.SetAllExpiration_WithLargeNumberOfKeys_SetsAllExpirations(count); } [Fact] - public override Task CanSetMinMaxExpirationAsync() + public override Task SetAllExpiration_WithNonExistentKeys_HandlesGracefully() { - return base.CanSetMinMaxExpirationAsync(); + return base.SetAllExpiration_WithNonExistentKeys_HandlesGracefully(); } [Fact] diff --git a/tests/Foundatio.Redis.Tests/Caching/RedisHybridCacheClientTests.cs b/tests/Foundatio.Redis.Tests/Caching/RedisHybridCacheClientTests.cs index c2aef22..86765c1 100644 --- a/tests/Foundatio.Redis.Tests/Caching/RedisHybridCacheClientTests.cs +++ b/tests/Foundatio.Redis.Tests/Caching/RedisHybridCacheClientTests.cs @@ -79,12 +79,6 @@ public override Task CanUseScopedCachesAsync() return base.CanUseScopedCachesAsync(); } - [Fact] - public override Task CanSetAndGetObjectAsync() - { - return base.CanSetAndGetObjectAsync(); - } - [Fact] public override Task CanRemoveAllAsync() { @@ -173,15 +167,73 @@ public override Task CanRemoveByPrefixMultipleEntriesAsync(int count) } [Fact] - public override Task CanSetExpirationAsync() + public override Task CanSetAndGetObjectAsync() + { + return base.CanSetAndGetObjectAsync(); + } + + [Fact] + public override Task GetExpirationAsync_WithVariousStates_ReturnsCorrectly() + { + return base.GetExpirationAsync_WithVariousStates_ReturnsCorrectly(); + } + + [Fact] + public override Task GetAllExpiration_WithMultipleKeys_ReturnsAllExpirations() + { + return base.GetAllExpiration_WithMultipleKeys_ReturnsAllExpirations(); + } + + [Theory] + [InlineData(1000)] + [InlineData(10000)] + public override Task GetAllExpiration_WithLargeNumberOfKeys_ReturnsAllExpirations(int count) + { + return base.GetAllExpiration_WithLargeNumberOfKeys_ReturnsAllExpirations(count); + } + + [Fact] + public override Task GetAllExpiration_WithExpiredKeys_ReturnsNullForExpiredKeys() + { + return base.GetAllExpiration_WithExpiredKeys_ReturnsNullForExpiredKeys(); + } + + [Fact] + public override Task SetExpirationAsync_WithValidDateTime_SetsExpirationCorrectly() + { + return base.SetExpirationAsync_WithValidDateTime_SetsExpirationCorrectly(); + } + + [Fact] + public override Task SetExpirationAsync_WithMinMaxValues_HandlesEdgeCases() + { + return base.SetExpirationAsync_WithMinMaxValues_HandlesEdgeCases(); + } + + [Fact] + public override Task SetAllExpiration_WithMultipleKeys_SetsExpirationForAll() + { + return base.SetAllExpiration_WithMultipleKeys_SetsExpirationForAll(); + } + + [Fact] + public override Task SetAllExpiration_WithNullValues_RemovesExpiration() + { + return base.SetAllExpiration_WithNullValues_RemovesExpiration(); + } + + [Theory] + [InlineData(1000)] + [InlineData(10000)] + public override Task SetAllExpiration_WithLargeNumberOfKeys_SetsAllExpirations(int count) { - return base.CanSetExpirationAsync(); + return base.SetAllExpiration_WithLargeNumberOfKeys_SetsAllExpirations(count); } [Fact] - public override Task CanSetMinMaxExpirationAsync() + public override Task SetAllExpiration_WithNonExistentKeys_HandlesGracefully() { - return base.CanSetMinMaxExpirationAsync(); + return base.SetAllExpiration_WithNonExistentKeys_HandlesGracefully(); } [Fact] diff --git a/tests/Foundatio.Redis.Tests/Caching/ScopedRedisCacheClientTests.cs b/tests/Foundatio.Redis.Tests/Caching/ScopedRedisCacheClientTests.cs index 9554155..4e51566 100644 --- a/tests/Foundatio.Redis.Tests/Caching/ScopedRedisCacheClientTests.cs +++ b/tests/Foundatio.Redis.Tests/Caching/ScopedRedisCacheClientTests.cs @@ -73,12 +73,6 @@ public override Task CanUseScopedCachesAsync() return base.CanUseScopedCachesAsync(); } - [Fact] - public override Task CanSetAndGetObjectAsync() - { - return base.CanSetAndGetObjectAsync(); - } - [Fact] public override Task CanRemoveAllAsync() { @@ -167,15 +161,73 @@ public override Task CanRemoveByPrefixMultipleEntriesAsync(int count) } [Fact] - public override Task CanSetExpirationAsync() + public override Task CanSetAndGetObjectAsync() + { + return base.CanSetAndGetObjectAsync(); + } + + [Fact] + public override Task GetExpirationAsync_WithVariousStates_ReturnsCorrectly() + { + return base.GetExpirationAsync_WithVariousStates_ReturnsCorrectly(); + } + + [Fact] + public override Task GetAllExpiration_WithMultipleKeys_ReturnsAllExpirations() + { + return base.GetAllExpiration_WithMultipleKeys_ReturnsAllExpirations(); + } + + [Theory] + [InlineData(1000)] + [InlineData(10000)] + public override Task GetAllExpiration_WithLargeNumberOfKeys_ReturnsAllExpirations(int count) + { + return base.GetAllExpiration_WithLargeNumberOfKeys_ReturnsAllExpirations(count); + } + + [Fact] + public override Task GetAllExpiration_WithExpiredKeys_ReturnsNullForExpiredKeys() + { + return base.GetAllExpiration_WithExpiredKeys_ReturnsNullForExpiredKeys(); + } + + [Fact] + public override Task SetExpirationAsync_WithValidDateTime_SetsExpirationCorrectly() + { + return base.SetExpirationAsync_WithValidDateTime_SetsExpirationCorrectly(); + } + + [Fact] + public override Task SetExpirationAsync_WithMinMaxValues_HandlesEdgeCases() + { + return base.SetExpirationAsync_WithMinMaxValues_HandlesEdgeCases(); + } + + [Fact] + public override Task SetAllExpiration_WithMultipleKeys_SetsExpirationForAll() + { + return base.SetAllExpiration_WithMultipleKeys_SetsExpirationForAll(); + } + + [Fact] + public override Task SetAllExpiration_WithNullValues_RemovesExpiration() + { + return base.SetAllExpiration_WithNullValues_RemovesExpiration(); + } + + [Theory] + [InlineData(1000)] + [InlineData(10000)] + public override Task SetAllExpiration_WithLargeNumberOfKeys_SetsAllExpirations(int count) { - return base.CanSetExpirationAsync(); + return base.SetAllExpiration_WithLargeNumberOfKeys_SetsAllExpirations(count); } [Fact] - public override Task CanSetMinMaxExpirationAsync() + public override Task SetAllExpiration_WithNonExistentKeys_HandlesGracefully() { - return base.CanSetMinMaxExpirationAsync(); + return base.SetAllExpiration_WithNonExistentKeys_HandlesGracefully(); } [Fact] diff --git a/tests/Foundatio.Redis.Tests/Caching/ScopedRedisHybridCacheClientTests.cs b/tests/Foundatio.Redis.Tests/Caching/ScopedRedisHybridCacheClientTests.cs index d6f909c..e2b3f7e 100644 --- a/tests/Foundatio.Redis.Tests/Caching/ScopedRedisHybridCacheClientTests.cs +++ b/tests/Foundatio.Redis.Tests/Caching/ScopedRedisHybridCacheClientTests.cs @@ -86,12 +86,6 @@ public override Task CanUseScopedCachesAsync() return base.CanUseScopedCachesAsync(); } - [Fact] - public override Task CanSetAndGetObjectAsync() - { - return base.CanSetAndGetObjectAsync(); - } - [Fact] public override Task CanRemoveAllAsync() { @@ -180,15 +174,73 @@ public override Task CanRemoveByPrefixMultipleEntriesAsync(int count) } [Fact] - public override Task CanSetExpirationAsync() + public override Task CanSetAndGetObjectAsync() + { + return base.CanSetAndGetObjectAsync(); + } + + [Fact] + public override Task GetExpirationAsync_WithVariousStates_ReturnsCorrectly() + { + return base.GetExpirationAsync_WithVariousStates_ReturnsCorrectly(); + } + + [Fact] + public override Task GetAllExpiration_WithMultipleKeys_ReturnsAllExpirations() + { + return base.GetAllExpiration_WithMultipleKeys_ReturnsAllExpirations(); + } + + [Theory] + [InlineData(1000)] + [InlineData(10000)] + public override Task GetAllExpiration_WithLargeNumberOfKeys_ReturnsAllExpirations(int count) + { + return base.GetAllExpiration_WithLargeNumberOfKeys_ReturnsAllExpirations(count); + } + + [Fact] + public override Task GetAllExpiration_WithExpiredKeys_ReturnsNullForExpiredKeys() + { + return base.GetAllExpiration_WithExpiredKeys_ReturnsNullForExpiredKeys(); + } + + [Fact] + public override Task SetExpirationAsync_WithValidDateTime_SetsExpirationCorrectly() + { + return base.SetExpirationAsync_WithValidDateTime_SetsExpirationCorrectly(); + } + + [Fact] + public override Task SetExpirationAsync_WithMinMaxValues_HandlesEdgeCases() + { + return base.SetExpirationAsync_WithMinMaxValues_HandlesEdgeCases(); + } + + [Fact] + public override Task SetAllExpiration_WithMultipleKeys_SetsExpirationForAll() + { + return base.SetAllExpiration_WithMultipleKeys_SetsExpirationForAll(); + } + + [Fact] + public override Task SetAllExpiration_WithNullValues_RemovesExpiration() + { + return base.SetAllExpiration_WithNullValues_RemovesExpiration(); + } + + [Theory] + [InlineData(1000)] + [InlineData(10000)] + public override Task SetAllExpiration_WithLargeNumberOfKeys_SetsAllExpirations(int count) { - return base.CanSetExpirationAsync(); + return base.SetAllExpiration_WithLargeNumberOfKeys_SetsAllExpirations(count); } [Fact] - public override Task CanSetMinMaxExpirationAsync() + public override Task SetAllExpiration_WithNonExistentKeys_HandlesGracefully() { - return base.CanSetMinMaxExpirationAsync(); + return base.SetAllExpiration_WithNonExistentKeys_HandlesGracefully(); } [Fact] From 97bb8c20500562ebe33b2471e43a6e9969c9ad9e Mon Sep 17 00:00:00 2001 From: Blake Niemyjski Date: Tue, 18 Nov 2025 22:06:40 +1300 Subject: [PATCH 06/26] Updated redis tests --- .../Caching/RedisCacheClientTests.cs | 1174 ++++++++++++++-- .../Caching/RedisHybridCacheClientTests.cs | 1201 ++++++++++++++-- .../Caching/ScopedRedisCacheClientTests.cs | 1172 ++++++++++++++-- .../ScopedRedisHybridCacheClientTests.cs | 1212 +++++++++++++++-- 4 files changed, 4290 insertions(+), 469 deletions(-) diff --git a/tests/Foundatio.Redis.Tests/Caching/RedisCacheClientTests.cs b/tests/Foundatio.Redis.Tests/Caching/RedisCacheClientTests.cs index 411d4bc..6ff1501 100644 --- a/tests/Foundatio.Redis.Tests/Caching/RedisCacheClientTests.cs +++ b/tests/Foundatio.Redis.Tests/Caching/RedisCacheClientTests.cs @@ -23,134 +23,810 @@ protected override ICacheClient GetCacheClient(bool shouldThrowOnSerializationEr } [Fact] - public override Task CanGetAllAsync() + public override Task AddAsync_WithConcurrentRequests_OnlyOneSucceeds() { - return base.CanGetAllAsync(); + return base.AddAsync_WithConcurrentRequests_OnlyOneSucceeds(); } [Fact] - public override Task CanGetAllWithOverlapAsync() + public override Task AddAsync_WithEmptyKey_ThrowsArgumentNullException() { - return base.CanGetAllWithOverlapAsync(); + return base.AddAsync_WithEmptyKey_ThrowsArgumentNullException(); } [Fact] - public override Task CanSetAsync() + public override Task AddAsync_WithExistingKey_ReturnsFalseAndPreservesValue() { - return base.CanSetAsync(); + return base.AddAsync_WithExistingKey_ReturnsFalseAndPreservesValue(); } [Fact] - public override Task CanSetAndGetValueAsync() + public override Task AddAsync_WithNestedKeyUsingSeparator_StoresCorrectly() { - return base.CanSetAndGetValueAsync(); + return base.AddAsync_WithNestedKeyUsingSeparator_StoresCorrectly(); } [Fact] - public override Task CanAddAsync() + public override Task AddAsync_WithNewKey_ReturnsTrue() { - return base.CanAddAsync(); + return base.AddAsync_WithNewKey_ReturnsTrue(); } [Fact] - public override Task CanAddConcurrentlyAsync() + public override Task AddAsync_WithNullKey_ThrowsArgumentNullException() { - return base.CanAddConcurrentlyAsync(); + return base.AddAsync_WithNullKey_ThrowsArgumentNullException(); } [Fact] - public override Task CanGetAsync() + public override Task AddAsync_WithWhitespaceKey_ThrowsArgumentException() { - return base.CanGetAsync(); + return base.AddAsync_WithWhitespaceKey_ThrowsArgumentException(); + } + + [Fact(Skip = "Performance Test")] + public override Task CacheOperations_WithMultipleTypes_MeasuresThroughput() + { + return base.CacheOperations_WithMultipleTypes_MeasuresThroughput(); + } + + [Fact(Skip = "Performance Test")] + public override Task CacheOperations_WithRepeatedSetAndGet_MeasuresThroughput() + { + return base.CacheOperations_WithRepeatedSetAndGet_MeasuresThroughput(); + } + + [Fact] + public override Task ExistsAsync_AfterKeyExpires_ReturnsFalse() + { + return base.ExistsAsync_AfterKeyExpires_ReturnsFalse(); + } + + [Fact] + public override Task ExistsAsync_WithDifferentCasedKeys_ChecksExactMatch() + { + return base.ExistsAsync_WithDifferentCasedKeys_ChecksExactMatch(); + } + + [Fact] + public override Task ExistsAsync_WithEmptyKey_ThrowsArgumentNullException() + { + return base.ExistsAsync_WithEmptyKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task ExistsAsync_WithExistingKey_ReturnsTrue() + { + return base.ExistsAsync_WithExistingKey_ReturnsTrue(); + } + + [Fact] + public override Task ExistsAsync_WithExpiredKey_ReturnsFalse() + { + return base.ExistsAsync_WithExpiredKey_ReturnsFalse(); + } + + [Fact] + public override Task ExistsAsync_WithNonExistentKey_ReturnsFalse() + { + return base.ExistsAsync_WithNonExistentKey_ReturnsFalse(); + } + + [Fact] + public override Task ExistsAsync_WithNullKey_ThrowsArgumentNullException() + { + return base.ExistsAsync_WithNullKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task ExistsAsync_WithNullStoredValue_ReturnsTrue() + { + return base.ExistsAsync_WithNullStoredValue_ReturnsTrue(); + } + + [Fact] + public override Task ExistsAsync_WithScopedCache_ChecksOnlyWithinScope() + { + return base.ExistsAsync_WithScopedCache_ChecksOnlyWithinScope(); + } + + [Fact] + public override Task ExistsAsync_WithWhitespaceKey_ThrowsArgumentException() + { + return base.ExistsAsync_WithWhitespaceKey_ThrowsArgumentException(); + } + + [Fact] + public override Task GetAllAsync_WithEmptyKeys_ReturnsEmpty() + { + return base.GetAllAsync_WithEmptyKeys_ReturnsEmpty(); + } + + [Fact] + public override Task GetAllAsync_WithExistingKeys_ReturnsAllValues() + { + return base.GetAllAsync_WithExistingKeys_ReturnsAllValues(); + } + + [Fact] + public override Task GetAllAsync_WithKeysContainingEmpty_ThrowsArgumentException() + { + return base.GetAllAsync_WithKeysContainingEmpty_ThrowsArgumentException(); + } + + [Fact] + public override Task GetAllAsync_WithKeysContainingNull_ThrowsArgumentException() + { + return base.GetAllAsync_WithKeysContainingNull_ThrowsArgumentException(); + } + + [Fact] + public override Task GetAllAsync_WithKeysContainingWhitespace_ThrowsArgumentException() + { + return base.GetAllAsync_WithKeysContainingWhitespace_ThrowsArgumentException(); + } + + [Fact] + public override Task GetAllAsync_WithMixedCaseKeys_RetrievesExactMatches() + { + return base.GetAllAsync_WithMixedCaseKeys_RetrievesExactMatches(); + } + + [Fact] + public override Task GetAllAsync_WithMixedObjectTypes_ReturnsCorrectValues() + { + return base.GetAllAsync_WithMixedObjectTypes_ReturnsCorrectValues(); + } + + [Fact] + public override Task GetAllAsync_WithNonExistentKeys_ReturnsEmptyResults() + { + return base.GetAllAsync_WithNonExistentKeys_ReturnsEmptyResults(); + } + + [Fact] + public override Task GetAllAsync_WithNullKeys_ThrowsArgumentNullException() + { + return base.GetAllAsync_WithNullKeys_ThrowsArgumentNullException(); + } + + [Fact] + public override Task GetAllAsync_WithNullValues_HandlesNullsCorrectly() + { + return base.GetAllAsync_WithNullValues_HandlesNullsCorrectly(); + } + + [Fact] + public override Task GetAllAsync_WithOverlappingKeys_UsesLatestValues() + { + return base.GetAllAsync_WithOverlappingKeys_UsesLatestValues(); + } + + [Fact] + public override Task GetAllAsync_WithScopedCache_ReturnsUnscopedKeys() + { + return base.GetAllAsync_WithScopedCache_ReturnsUnscopedKeys(); + } + + [Fact] + public override Task GetAllExpirationAsync_WithExpiredKeys_ExcludesExpiredKeys() + { + return base.GetAllExpirationAsync_WithExpiredKeys_ExcludesExpiredKeys(); + } + + [Theory] + [InlineData(1000)] + [InlineData(10000)] + public override Task GetAllExpirationAsync_WithLargeNumberOfKeys_ReturnsAllExpirations(int count) + { + return base.GetAllExpirationAsync_WithLargeNumberOfKeys_ReturnsAllExpirations(count); + } + + [Fact] + public override Task GetAllExpirationAsync_WithMixedKeys_ReturnsOnlyKeysWithExpiration() + { + return base.GetAllExpirationAsync_WithMixedKeys_ReturnsOnlyKeysWithExpiration(); + } + + [Fact] + public override Task GetAsync_WithComplexObject_PreservesAllProperties() + { + return base.GetAsync_WithComplexObject_PreservesAllProperties(); + } + + [Fact] + public override Task GetAsync_WithComplexObject_ReturnsNewInstance() + { + return base.GetAsync_WithComplexObject_ReturnsNewInstance(); + } + + [Fact] + public override Task GetAsync_WithDifferentCasedKeys_TreatsAsDifferentKeys() + { + return base.GetAsync_WithDifferentCasedKeys_TreatsAsDifferentKeys(); + } + + [Fact] + public override Task GetAsync_WithEmptyKey_ThrowsArgumentNullException() + { + return base.GetAsync_WithEmptyKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task GetAsync_WithLargeNumber_ReturnsCorrectValue() + { + return base.GetAsync_WithLargeNumber_ReturnsCorrectValue(); + } + + [Fact] + public override Task GetAsync_WithMaxLongAsInt_ThrowsException() + { + return base.GetAsync_WithMaxLongAsInt_ThrowsException(); + } + + [Fact] + public override Task GetAsync_WithNonExistentKey_ReturnsNoValue() + { + return base.GetAsync_WithNonExistentKey_ReturnsNoValue(); + } + + [Fact] + public override Task GetAsync_WithNullKey_ThrowsArgumentNullException() + { + return base.GetAsync_WithNullKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task GetAsync_WithNumericTypeConversion_ConvertsIntToLong() + { + return base.GetAsync_WithNumericTypeConversion_ConvertsIntToLong(); + } + + [Fact] + public override Task GetAsync_WithNumericTypeConversion_ConvertsLongToInt() + { + return base.GetAsync_WithNumericTypeConversion_ConvertsLongToInt(); + } + + [Fact] + public override Task GetAsync_WithTryGetSemanticsAndComplexTypeAsLong_ReturnsNoValue() + { + return base.GetAsync_WithTryGetSemanticsAndComplexTypeAsLong_ReturnsNoValue(); + } + + [Fact] + public override Task GetAsync_WithTryGetSemanticsAndIntAsLong_ConvertsSuccessfully() + { + return base.GetAsync_WithTryGetSemanticsAndIntAsLong_ConvertsSuccessfully(); + } + + [Fact] + public override Task GetAsync_WithTryGetSemanticsAndMaxLongAsInt_ReturnsNoValue() + { + return base.GetAsync_WithTryGetSemanticsAndMaxLongAsInt_ReturnsNoValue(); + } + + [Fact] + public override Task GetAsync_WithWhitespaceKey_ThrowsArgumentException() + { + return base.GetAsync_WithWhitespaceKey_ThrowsArgumentException(); + } + + [Fact] + public override Task GetExpirationAsync_AfterExpiry_ReturnsNull() + { + return base.GetExpirationAsync_AfterExpiry_ReturnsNull(); + } + + [Fact] + public override Task GetExpirationAsync_WithDifferentCasedKeys_GetsExactMatch() + { + return base.GetExpirationAsync_WithDifferentCasedKeys_GetsExactMatch(); + } + + [Fact] + public override Task GetExpirationAsync_WithEmptyKey_ThrowsArgumentNullException() + { + return base.GetExpirationAsync_WithEmptyKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task GetExpirationAsync_WithExpiration_ReturnsCorrectTimeSpan() + { + return base.GetExpirationAsync_WithExpiration_ReturnsCorrectTimeSpan(); + } + + [Fact] + public override Task GetExpirationAsync_WithExpiredKey_ReturnsNull() + { + return base.GetExpirationAsync_WithExpiredKey_ReturnsNull(); + } + + [Fact] + public override Task GetExpirationAsync_WithNoExpiration_ReturnsNull() + { + return base.GetExpirationAsync_WithNoExpiration_ReturnsNull(); + } + + [Fact] + public override Task GetExpirationAsync_WithNonExistentKey_ReturnsNull() + { + return base.GetExpirationAsync_WithNonExistentKey_ReturnsNull(); + } + + [Fact] + public override Task GetExpirationAsync_WithNullKey_ThrowsArgumentNullException() + { + return base.GetExpirationAsync_WithNullKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task GetExpirationAsync_WithWhitespaceKey_ThrowsArgumentException() + { + return base.GetExpirationAsync_WithWhitespaceKey_ThrowsArgumentException(); + } + + [Fact] + public override Task GetListAsync_WithEmptyKey_ThrowsArgumentNullException() + { + return base.GetListAsync_WithEmptyKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task GetListAsync_WithExpiredItems_RemovesExpiredAndReturnsActive() + { + return base.GetListAsync_WithExpiredItems_RemovesExpiredAndReturnsActive(); + } + + [Fact] + public override Task GetListAsync_WithInvalidPageNumber_ThrowsArgumentOutOfRangeException() + { + return base.GetListAsync_WithInvalidPageNumber_ThrowsArgumentOutOfRangeException(); + } + + [Fact] + public override Task GetListAsync_WithMultiplePages_ReturnsAllItems() + { + return base.GetListAsync_WithMultiplePages_ReturnsAllItems(); + } + + [Fact] + public override Task GetListAsync_WithNewItemsAdded_ReturnsNewItemsLast() + { + return base.GetListAsync_WithNewItemsAdded_ReturnsNewItemsLast(); + } + + [Fact] + public override Task GetListAsync_WithNullKey_ThrowsArgumentNullException() + { + return base.GetListAsync_WithNullKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task GetListAsync_WithPageBeyondEnd_ReturnsEmptyCollection() + { + return base.GetListAsync_WithPageBeyondEnd_ReturnsEmptyCollection(); + } + + [Fact] + public override Task GetListAsync_WithPaging_ReturnsCorrectPageSize() + { + return base.GetListAsync_WithPaging_ReturnsCorrectPageSize(); + } + + [Fact] + public override Task GetListAsync_WithWhitespaceKey_ThrowsArgumentException() + { + return base.GetListAsync_WithWhitespaceKey_ThrowsArgumentException(); + } + + [Fact] + public override Task GetUnixTimeMillisecondsAsync_WithLocalDateTime_ReturnsCorrectly() + { + return base.GetUnixTimeMillisecondsAsync_WithLocalDateTime_ReturnsCorrectly(); + } + + [Fact] + public override Task GetUnixTimeMillisecondsAsync_WithUtcDateTime_ReturnsCorrectly() + { + return base.GetUnixTimeMillisecondsAsync_WithUtcDateTime_ReturnsCorrectly(); + } + + [Fact] + public override Task GetUnixTimeSecondsAsync_WithUtcDateTime_ReturnsCorrectly() + { + return base.GetUnixTimeSecondsAsync_WithUtcDateTime_ReturnsCorrectly(); + } + + [Fact] + public override Task IncrementAsync_WithDifferentCasedKeys_IncrementsDistinctCounters() + { + return base.IncrementAsync_WithDifferentCasedKeys_IncrementsDistinctCounters(); + } + + [Fact] + public override Task IncrementAsync_WithEmptyKey_ThrowsArgumentNullException() + { + return base.IncrementAsync_WithEmptyKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task IncrementAsync_WithExistingKey_IncrementsValue() + { + return base.IncrementAsync_WithExistingKey_IncrementsValue(); + } + + [Fact] + public override Task IncrementAsync_WithExpiration_ExpiresCorrectly() + { + return base.IncrementAsync_WithExpiration_ExpiresCorrectly(); + } + + [Fact] + public override Task IncrementAsync_WithNonExistentKey_InitializesToOne() + { + return base.IncrementAsync_WithNonExistentKey_InitializesToOne(); + } + + [Fact] + public override Task IncrementAsync_WithNullKey_ThrowsArgumentNullException() + { + return base.IncrementAsync_WithNullKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task IncrementAsync_WithScopedCache_WorksWithinScope() + { + return base.IncrementAsync_WithScopedCache_WorksWithinScope(); + } + + [Fact] + public override Task IncrementAsync_WithSpecifiedAmount_IncrementsCorrectly() + { + return base.IncrementAsync_WithSpecifiedAmount_IncrementsCorrectly(); + } + + [Fact] + public override Task IncrementAsync_WithWhitespaceKey_ThrowsArgumentException() + { + return base.IncrementAsync_WithWhitespaceKey_ThrowsArgumentException(); + } + + [Fact] + public override Task ListAddAsync_WithDifferentCasedKeys_MaintainsDistinctLists() + { + return base.ListAddAsync_WithDifferentCasedKeys_MaintainsDistinctLists(); + } + + [Fact] + public override Task ListAddAsync_WithDuplicates_RemovesDuplicatesAndAddsItems() + { + return base.ListAddAsync_WithDuplicates_RemovesDuplicatesAndAddsItems(); + } + + [Fact] + public override Task ListAddAsync_WithDuplicates_StoresUniqueValuesOnly() + { + return base.ListAddAsync_WithDuplicates_StoresUniqueValuesOnly(); + } + + [Fact] + public override Task ListAddAsync_WithEmptyCollection_NoOp() + { + return base.ListAddAsync_WithEmptyCollection_NoOp(); + } + + [Fact] + public override Task ListAddAsync_WithEmptyKey_ThrowsArgumentNullException() + { + return base.ListAddAsync_WithEmptyKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task ListAddAsync_WithExistingNonListKey_ThrowsException() + { + return base.ListAddAsync_WithExistingNonListKey_ThrowsException(); + } + + [Fact] + public override Task ListAddAsync_WithFutureExpiration_AddsAndExpiresCorrectly() + { + return base.ListAddAsync_WithFutureExpiration_AddsAndExpiresCorrectly(); + } + + [Fact] + public override Task ListAddAsync_WithMultipleExpirations_ExpiresIndividualItems() + { + return base.ListAddAsync_WithMultipleExpirations_ExpiresIndividualItems(); + } + + [Fact] + public override Task ListAddAsync_WithNullCollection_ThrowsArgumentNullException() + { + return base.ListAddAsync_WithNullCollection_ThrowsArgumentNullException(); + } + + [Fact] + public override Task ListAddAsync_WithNullItem_IgnoresNull() + { + return base.ListAddAsync_WithNullItem_IgnoresNull(); + } + + [Fact] + public override Task ListAddAsync_WithNullKey_ThrowsArgumentNullException() + { + return base.ListAddAsync_WithNullKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task ListAddAsync_WithNullValues_ThrowsArgumentNullException() + { + return base.ListAddAsync_WithNullValues_ThrowsArgumentNullException(); + } + + [Fact] + public override Task ListAddAsync_WithPastExpiration_RemovesItem() + { + return base.ListAddAsync_WithPastExpiration_RemovesItem(); + } + + [Fact] + public override Task ListAddAsync_WithSingleString_StoresAsStringNotCharArray() + { + return base.ListAddAsync_WithSingleString_StoresAsStringNotCharArray(); + } + + [Fact] + public override Task ListAddAsync_WithWhitespaceKey_ThrowsArgumentException() + { + return base.ListAddAsync_WithWhitespaceKey_ThrowsArgumentException(); + } + + [Fact] + public override Task ListRemoveAsync_WithEmptyKey_ThrowsArgumentNullException() + { + return base.ListRemoveAsync_WithEmptyKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task ListRemoveAsync_WithMultipleValues_RemovesAll() + { + return base.ListRemoveAsync_WithMultipleValues_RemovesAll(); + } + + [Fact] + public override Task ListRemoveAsync_WithNullCollection_ThrowsArgumentNullException() + { + return base.ListRemoveAsync_WithNullCollection_ThrowsArgumentNullException(); + } + + [Fact] + public override Task ListRemoveAsync_WithNullItem_IgnoresNull() + { + return base.ListRemoveAsync_WithNullItem_IgnoresNull(); + } + + [Fact] + public override Task ListRemoveAsync_WithNullKey_ThrowsArgumentNullException() + { + return base.ListRemoveAsync_WithNullKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task ListRemoveAsync_WithNullValues_ThrowsArgumentNullException() + { + return base.ListRemoveAsync_WithNullValues_ThrowsArgumentNullException(); + } + + [Fact] + public override Task ListRemoveAsync_WithSingleValue_RemovesCorrectly() + { + return base.ListRemoveAsync_WithSingleValue_RemovesCorrectly(); + } + + [Fact] + public override Task ListRemoveAsync_WithValidValues_RemovesKeyWhenEmpty() + { + return base.ListRemoveAsync_WithValidValues_RemovesKeyWhenEmpty(); + } + + [Fact] + public override Task ListRemoveAsync_WithWhitespaceKey_ThrowsArgumentException() + { + return base.ListRemoveAsync_WithWhitespaceKey_ThrowsArgumentException(); + } + + [Fact] + public override Task RemoveAllAsync_WithEmptyKeys_Succeeds() + { + return base.RemoveAllAsync_WithEmptyKeys_Succeeds(); + } + + [Fact] + public override Task RemoveAllAsync_WithKeysContainingEmpty_ThrowsArgumentException() + { + return base.RemoveAllAsync_WithKeysContainingEmpty_ThrowsArgumentException(); + } + + [Fact] + public override Task RemoveAllAsync_WithKeysContainingNull_ThrowsArgumentException() + { + return base.RemoveAllAsync_WithKeysContainingNull_ThrowsArgumentException(); + } + + [Fact] + public override Task RemoveAllAsync_WithKeysContainingWhitespace_ThrowsArgumentException() + { + return base.RemoveAllAsync_WithKeysContainingWhitespace_ThrowsArgumentException(); + } + + [Fact] + public override Task RemoveAllAsync_WithLargeNumberOfKeys_RemovesAllKeysEfficiently() + { + return base.RemoveAllAsync_WithLargeNumberOfKeys_RemovesAllKeysEfficiently(); + } + + [Fact] + public override Task RemoveAllAsync_WithMixedCaseKeys_RemovesOnlyExactMatches() + { + return base.RemoveAllAsync_WithMixedCaseKeys_RemovesOnlyExactMatches(); } [Fact] - public override Task CanTryGetAsync() + public override Task RemoveAllAsync_WithNullKeys_ThrowsArgumentNullException() { - return base.CanTryGetAsync(); + return base.RemoveAllAsync_WithNullKeys_ThrowsArgumentNullException(); } [Fact] - public override Task CanUseScopedCachesAsync() + public override Task RemoveAllAsync_WithScopedCache_AffectsOnlyScopedKeys() { - return base.CanUseScopedCachesAsync(); + return base.RemoveAllAsync_WithScopedCache_AffectsOnlyScopedKeys(); } [Fact] - public override Task CanRemoveAllAsync() + public override Task RemoveAllAsync_WithSpecificKeyCollection_RemovesOnlySpecifiedKeys() { - return base.CanRemoveAllAsync(); + return base.RemoveAllAsync_WithSpecificKeyCollection_RemovesOnlySpecifiedKeys(); } [Fact] - public override Task CanRemoveAllKeysAsync() + public override Task RemoveAsync_AfterSetAndGet_RemovesCorrectly() { - return base.CanRemoveAllKeysAsync(); + return base.RemoveAsync_AfterSetAndGet_RemovesCorrectly(); } [Fact] - public override Task CanRemoveByPrefixAsync() + public override Task RemoveAsync_MultipleTimes_Succeeds() { - return base.CanRemoveByPrefixAsync(); + return base.RemoveAsync_MultipleTimes_Succeeds(); + } + + [Fact] + public override Task RemoveAsync_WithEmptyKey_ThrowsArgumentNullException() + { + return base.RemoveAsync_WithEmptyKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task RemoveAsync_WithExistingKey_RemovesSuccessfully() + { + return base.RemoveAsync_WithExistingKey_RemovesSuccessfully(); + } + + [Fact] + public override Task RemoveAsync_WithExpiredKey_Succeeds() + { + return base.RemoveAsync_WithExpiredKey_Succeeds(); + } + + [Fact] + public override Task RemoveAsync_WithNonExistentKey_Succeeds() + { + return base.RemoveAsync_WithNonExistentKey_Succeeds(); + } + + [Fact] + public override Task RemoveAsync_WithNullKey_ThrowsArgumentNullException() + { + return base.RemoveAsync_WithNullKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task RemoveAsync_WithNullValue_RemovesSuccessfully() + { + return base.RemoveAsync_WithNullValue_RemovesSuccessfully(); + } + + [Fact] + public override Task RemoveAsync_WithScopedCache_RemovesOnlyWithinScope() + { + return base.RemoveAsync_WithScopedCache_RemovesOnlyWithinScope(); + } + + [Fact] + public override Task RemoveAsync_WithSpecificCase_RemovesOnlyMatchingKey() + { + return base.RemoveAsync_WithSpecificCase_RemovesOnlyMatchingKey(); + } + + [Fact] + public override Task RemoveAsync_WithWhitespaceKey_ThrowsArgumentException() + { + return base.RemoveAsync_WithWhitespaceKey_ThrowsArgumentException(); + } + + [Fact] + public override Task RemoveByPrefixAsync_AsteriskPrefixWithScopedCache_TreatedAsLiteral() + { + return base.RemoveByPrefixAsync_AsteriskPrefixWithScopedCache_TreatedAsLiteral(); } [Theory] - [MemberData(nameof(GetRegexSpecialCharacters))] - public override Task CanRemoveByPrefixWithRegexCharactersAsync(string specialChar) + [InlineData("snowboard", 1, true)] // Exact key match + [InlineData("s", 1, true)] // Partial prefix match + [InlineData(null, 1, false)] // Null prefix (all keys in scope) + [InlineData("", 1, false)] // Empty prefix (all keys in scope) + public override Task RemoveByPrefixAsync_FromScopedCache_RemovesOnlyScopedKeys(string prefixToRemove, int expectedRemovedCount, bool shouldUnscopedRemain) { - return base.CanRemoveByPrefixWithRegexCharactersAsync(specialChar); + return base.RemoveByPrefixAsync_FromScopedCache_RemovesOnlyScopedKeys(prefixToRemove, expectedRemovedCount, shouldUnscopedRemain); } [Theory] - [MemberData(nameof(GetWildcardPatterns))] - public override Task CanRemoveByPrefixWithWildcardPatternsAsync(string pattern) + [InlineData(null)] + [InlineData("")] + public override Task RemoveByPrefixAsync_NullOrEmptyPrefixWithScopedCache_RemovesCorrectKeys(string prefix) { - return base.CanRemoveByPrefixWithWildcardPatternsAsync(pattern); + return base.RemoveByPrefixAsync_NullOrEmptyPrefixWithScopedCache_RemovesCorrectKeys(prefix); } [Fact] - public override Task CanRemoveByPrefixWithDoubleAsteriskAsync() + public override Task RemoveByPrefixAsync_PartialPrefixWithScopedCache_RemovesMatchingKeys() { - return base.CanRemoveByPrefixWithDoubleAsteriskAsync(); + return base.RemoveByPrefixAsync_PartialPrefixWithScopedCache_RemovesMatchingKeys(); } - [Theory] - [MemberData(nameof(GetSpecialPrefixes))] - public override Task CanRemoveByPrefixWithSpecialCharactersAsync(string specialPrefix) + [Fact] + public override Task RemoveByPrefixAsync_WithCaseSensitivePrefix_RemovesOnlyMatchingCase() { - return base.CanRemoveByPrefixWithSpecialCharactersAsync(specialPrefix); + return base.RemoveByPrefixAsync_WithCaseSensitivePrefix_RemovesOnlyMatchingCase(); } [Fact] - public override Task CanRemoveByPrefixWithNullAsync() + public override Task RemoveByPrefixAsync_WithDifferentCasedScopes_RemovesOnlyMatchingScope() { - return base.CanRemoveByPrefixWithNullAsync(); + return base.RemoveByPrefixAsync_WithDifferentCasedScopes_RemovesOnlyMatchingScope(); } [Fact] - public override Task CanRemoveByPrefixWithEmptyStringAsync() + public override Task RemoveByPrefixAsync_WithDoubleAsteriskPrefix_TreatsAsLiteral() { - return base.CanRemoveByPrefixWithEmptyStringAsync(); + return base.RemoveByPrefixAsync_WithDoubleAsteriskPrefix_TreatsAsLiteral(); } - [Theory] - [MemberData(nameof(GetWhitespaceOnlyPrefixes))] - public override Task CanRemoveByPrefixWithWhitespaceAsync(string whitespacePrefix) + [Fact] + public override Task RemoveByPrefixAsync_WithEmptyPrefix_RemovesAllKeys() { - return base.CanRemoveByPrefixWithWhitespaceAsync(whitespacePrefix); + return base.RemoveByPrefixAsync_WithEmptyPrefix_RemovesAllKeys(); + } + + [Fact] + public override Task RemoveByPrefixAsync_WithEmptyPrefix_ThrowsArgumentException() + { + return base.RemoveByPrefixAsync_WithEmptyPrefix_ThrowsArgumentException(); } [Theory] [MemberData(nameof(GetLineEndingPrefixes))] - public override Task CanRemoveByPrefixWithLineEndingsAsync(string lineEndingPrefix) + public override Task RemoveByPrefixAsync_WithLineEndingPrefix_TreatsAsLiteral(string lineEndingPrefix) { - return base.CanRemoveByPrefixWithLineEndingsAsync(lineEndingPrefix); + return base.RemoveByPrefixAsync_WithLineEndingPrefix_TreatsAsLiteral(lineEndingPrefix); } [Fact] - public override Task CanRemoveByPrefixWithScopedCachesAsync() + public override Task RemoveByPrefixAsync_WithMatchingPrefix_RemovesOnlyPrefixedKeys() { - return base.CanRemoveByPrefixWithScopedCachesAsync(); + return base.RemoveByPrefixAsync_WithMatchingPrefix_RemovesOnlyPrefixedKeys(); } [Theory] @@ -158,65 +834,241 @@ public override Task CanRemoveByPrefixWithScopedCachesAsync() [InlineData(500)] [InlineData(5000)] [InlineData(50000)] - public override Task CanRemoveByPrefixMultipleEntriesAsync(int count) + public override Task RemoveByPrefixAsync_WithMultipleMatchingKeys_RemovesOnlyPrefixedKeys(int count) { - return base.CanRemoveByPrefixMultipleEntriesAsync(count); + return base.RemoveByPrefixAsync_WithMultipleMatchingKeys_RemovesOnlyPrefixedKeys(count); } [Fact] - public override Task CanSetAndGetObjectAsync() + public override Task RemoveByPrefixAsync_WithNonMatchingPrefix_RemovesZeroKeys() { - return base.CanSetAndGetObjectAsync(); + return base.RemoveByPrefixAsync_WithNonMatchingPrefix_RemovesZeroKeys(); } [Fact] - public override Task GetExpirationAsync_WithVariousStates_ReturnsCorrectly() + public override Task RemoveByPrefixAsync_WithNullPrefix_RemovesAllKeys() { - return base.GetExpirationAsync_WithVariousStates_ReturnsCorrectly(); + return base.RemoveByPrefixAsync_WithNullPrefix_RemovesAllKeys(); } [Fact] - public override Task GetAllExpiration_WithMultipleKeys_ReturnsAllExpirations() + public override Task RemoveByPrefixAsync_WithNullPrefix_ThrowsArgumentNullException() { - return base.GetAllExpiration_WithMultipleKeys_ReturnsAllExpirations(); + return base.RemoveByPrefixAsync_WithNullPrefix_ThrowsArgumentNullException(); } [Theory] - [InlineData(1000)] - [InlineData(10000)] - public override Task GetAllExpiration_WithLargeNumberOfKeys_ReturnsAllExpirations(int count) + [MemberData(nameof(GetRegexSpecialCharacters))] + public override Task RemoveByPrefixAsync_WithRegexMetacharacter_TreatsAsLiteral(string specialChar) + { + return base.RemoveByPrefixAsync_WithRegexMetacharacter_TreatsAsLiteral(specialChar); + } + + [Fact] + public override Task RemoveByPrefixAsync_WithScopedCache_AffectsOnlyScopedKeys() + { + return base.RemoveByPrefixAsync_WithScopedCache_AffectsOnlyScopedKeys(); + } + + [Theory] + [MemberData(nameof(GetSpecialPrefixes))] + public override Task RemoveByPrefixAsync_WithSpecialCharacterPrefix_TreatsAsLiteral(string specialPrefix) { - return base.GetAllExpiration_WithLargeNumberOfKeys_ReturnsAllExpirations(count); + return base.RemoveByPrefixAsync_WithSpecialCharacterPrefix_TreatsAsLiteral(specialPrefix); } [Fact] - public override Task GetAllExpiration_WithExpiredKeys_ReturnsNullForExpiredKeys() + public override Task RemoveByPrefixAsync_WithWhitespacePrefix_ThrowsArgumentException() { - return base.GetAllExpiration_WithExpiredKeys_ReturnsNullForExpiredKeys(); + return base.RemoveByPrefixAsync_WithWhitespacePrefix_ThrowsArgumentException(); + } + + [Theory] + [MemberData(nameof(GetWhitespaceOnlyPrefixes))] + public override Task RemoveByPrefixAsync_WithWhitespacePrefix_TreatsAsLiteral(string whitespacePrefix) + { + return base.RemoveByPrefixAsync_WithWhitespacePrefix_TreatsAsLiteral(whitespacePrefix); + } + + [Theory] + [MemberData(nameof(GetWildcardPatterns))] + public override Task RemoveByPrefixAsync_WithWildcardPattern_TreatsAsLiteral(string pattern) + { + return base.RemoveByPrefixAsync_WithWildcardPattern_TreatsAsLiteral(pattern); } [Fact] - public override Task SetExpirationAsync_WithValidDateTime_SetsExpirationCorrectly() + public override Task RemoveIfEqualAsync_WithEmptyKey_ThrowsArgumentNullException() { - return base.SetExpirationAsync_WithValidDateTime_SetsExpirationCorrectly(); + return base.RemoveIfEqualAsync_WithEmptyKey_ThrowsArgumentNullException(); } [Fact] - public override Task SetExpirationAsync_WithMinMaxValues_HandlesEdgeCases() + public override Task RemoveIfEqualAsync_WithMatchingValue_ReturnsTrueAndRemoves() { - return base.SetExpirationAsync_WithMinMaxValues_HandlesEdgeCases(); + return base.RemoveIfEqualAsync_WithMatchingValue_ReturnsTrueAndRemoves(); } [Fact] - public override Task SetAllExpiration_WithMultipleKeys_SetsExpirationForAll() + public override Task RemoveIfEqualAsync_WithMismatchedValue_ReturnsFalseAndDoesNotRemove() { - return base.SetAllExpiration_WithMultipleKeys_SetsExpirationForAll(); + return base.RemoveIfEqualAsync_WithMismatchedValue_ReturnsFalseAndDoesNotRemove(); } [Fact] - public override Task SetAllExpiration_WithNullValues_RemovesExpiration() + public override Task RemoveIfEqualAsync_WithNullKey_ThrowsArgumentNullException() { - return base.SetAllExpiration_WithNullValues_RemovesExpiration(); + return base.RemoveIfEqualAsync_WithNullKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task RemoveIfEqualAsync_WithWhitespaceKey_ThrowsArgumentException() + { + return base.RemoveIfEqualAsync_WithWhitespaceKey_ThrowsArgumentException(); + } + + [Fact] + public override Task ReplaceAsync_WithDifferentCasedKeys_TreatsAsDifferentKeys() + { + return base.ReplaceAsync_WithDifferentCasedKeys_TreatsAsDifferentKeys(); + } + + [Fact] + public override Task ReplaceAsync_WithEmptyKey_ThrowsArgumentNullException() + { + return base.ReplaceAsync_WithEmptyKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task ReplaceAsync_WithExistingKey_ReturnsTrueAndReplacesValue() + { + return base.ReplaceAsync_WithExistingKey_ReturnsTrueAndReplacesValue(); + } + + [Fact] + public override Task ReplaceAsync_WithExpiration_SetsExpirationCorrectly() + { + return base.ReplaceAsync_WithExpiration_SetsExpirationCorrectly(); + } + + [Fact] + public override Task ReplaceAsync_WithNonExistentKey_ReturnsFalseAndDoesNotCreateKey() + { + return base.ReplaceAsync_WithNonExistentKey_ReturnsFalseAndDoesNotCreateKey(); + } + + [Fact] + public override Task ReplaceAsync_WithNullKey_ThrowsArgumentNullException() + { + return base.ReplaceAsync_WithNullKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task ReplaceAsync_WithWhitespaceKey_ThrowsArgumentException() + { + return base.ReplaceAsync_WithWhitespaceKey_ThrowsArgumentException(); + } + + [Fact] + public override Task ReplaceIfEqualAsync_WithDifferentCasedKeys_ReplacesOnlyExactMatch() + { + return base.ReplaceIfEqualAsync_WithDifferentCasedKeys_ReplacesOnlyExactMatch(); + } + + [Fact] + public override Task ReplaceIfEqualAsync_WithEmptyKey_ThrowsArgumentNullException() + { + return base.ReplaceIfEqualAsync_WithEmptyKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task ReplaceIfEqualAsync_WithExpiration_SetsExpirationCorrectly() + { + return base.ReplaceIfEqualAsync_WithExpiration_SetsExpirationCorrectly(); + } + + [Fact] + public override Task ReplaceIfEqualAsync_WithMatchingOldValue_ReturnsTrueAndReplacesValue() + { + return base.ReplaceIfEqualAsync_WithMatchingOldValue_ReturnsTrueAndReplacesValue(); + } + + [Fact] + public override Task ReplaceIfEqualAsync_WithMismatchedOldValue_ReturnsFalseAndDoesNotReplace() + { + return base.ReplaceIfEqualAsync_WithMismatchedOldValue_ReturnsFalseAndDoesNotReplace(); + } + + [Fact] + public override Task ReplaceIfEqualAsync_WithNullKey_ThrowsArgumentNullException() + { + return base.ReplaceIfEqualAsync_WithNullKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task ReplaceIfEqualAsync_WithWhitespaceKey_ThrowsArgumentException() + { + return base.ReplaceIfEqualAsync_WithWhitespaceKey_ThrowsArgumentException(); + } + + [Fact(Skip = "Performance Test")] + public override Task Serialization_WithComplexObjectsAndValidation_MeasuresThroughput() + { + return base.Serialization_WithComplexObjectsAndValidation_MeasuresThroughput(); + } + + [Fact(Skip = "Performance Test")] + public override Task Serialization_WithSimpleObjectsAndValidation_MeasuresThroughput() + { + return base.Serialization_WithSimpleObjectsAndValidation_MeasuresThroughput(); + } + + [Fact] + public override Task SetAllAsync_WithDateTimeMinValue_DoesNotAddKeys() + { + return base.SetAllAsync_WithDateTimeMinValue_DoesNotAddKeys(); + } + + [Fact] + public override Task SetAllAsync_WithDifferentCasedKeys_CreatesDistinctEntries() + { + return base.SetAllAsync_WithDifferentCasedKeys_CreatesDistinctEntries(); + } + + [Fact] + public override Task SetAllAsync_WithEmptyItems_ReturnsTrue() + { + return base.SetAllAsync_WithEmptyItems_ReturnsTrue(); + } + + [Fact] + public override Task SetAllAsync_WithExpiration_KeysExpireCorrectly() + { + return base.SetAllAsync_WithExpiration_KeysExpireCorrectly(); + } + + [Fact] + public override Task SetAllAsync_WithItemsContainingEmptyKey_ThrowsArgumentException() + { + return base.SetAllAsync_WithItemsContainingEmptyKey_ThrowsArgumentException(); + } + + [Fact] + public override Task SetAllAsync_WithItemsContainingNullKey_ThrowsArgumentException() + { + return base.SetAllAsync_WithItemsContainingNullKey_ThrowsArgumentException(); + } + + [Fact] + public override Task SetAllAsync_WithItemsContainingWhitespaceKey_ThrowsArgumentException() + { + return base.SetAllAsync_WithItemsContainingWhitespaceKey_ThrowsArgumentException(); + } + + [Fact] + public override Task SetAllAsync_WithNullItems_ThrowsArgumentNullException() + { + return base.SetAllAsync_WithNullItems_ThrowsArgumentNullException(); } [Theory] @@ -227,6 +1079,12 @@ public override Task SetAllExpiration_WithLargeNumberOfKeys_SetsAllExpirations(i return base.SetAllExpiration_WithLargeNumberOfKeys_SetsAllExpirations(count); } + [Fact] + public override Task SetAllExpiration_WithMultipleKeys_SetsExpirationForAll() + { + return base.SetAllExpiration_WithMultipleKeys_SetsExpirationForAll(); + } + [Fact] public override Task SetAllExpiration_WithNonExistentKeys_HandlesGracefully() { @@ -234,115 +1092,211 @@ public override Task SetAllExpiration_WithNonExistentKeys_HandlesGracefully() } [Fact] - public override Task CanIncrementAsync() + public override Task SetAllExpiration_WithNullValues_RemovesExpiration() { - return base.CanIncrementAsync(); + return base.SetAllExpiration_WithNullValues_RemovesExpiration(); } [Fact] - public override Task CanIncrementAndExpireAsync() + public override Task SetAsync_WithComplexObject_StoresCorrectly() { - return base.CanIncrementAndExpireAsync(); + return base.SetAsync_WithComplexObject_StoresCorrectly(); } [Fact] - public override Task SetAllShouldExpireAsync() + public override Task SetAsync_WithDifferentCasedKeys_CreatesDistinctEntries() { - return base.SetAllShouldExpireAsync(); + return base.SetAsync_WithDifferentCasedKeys_CreatesDistinctEntries(); } [Fact] - public override Task CanReplaceIfEqual() + public override Task SetAsync_WithDifferentCasedScopes_MaintainsDistinctNamespaces() { - return base.CanReplaceIfEqual(); + return base.SetAsync_WithDifferentCasedScopes_MaintainsDistinctNamespaces(); } [Fact] - public override Task CanRemoveIfEqual() + public override Task SetAsync_WithDifferentScopes_IsolatesKeys() { - return base.CanRemoveIfEqual(); + return base.SetAsync_WithDifferentScopes_IsolatesKeys(); } [Fact] - public override Task CanGetAndSetDateTimeAsync() + public override Task SetAsync_WithEmptyKey_ThrowsArgumentNullException() { - return base.CanGetAndSetDateTimeAsync(); + return base.SetAsync_WithEmptyKey_ThrowsArgumentNullException(); } [Fact] - public override Task CanRoundTripLargeNumbersAsync() + public override Task SetAsync_WithExpirationEdgeCases_HandlesCorrectly() { - return base.CanRoundTripLargeNumbersAsync(); + return base.SetAsync_WithExpirationEdgeCases_HandlesCorrectly(); } [Fact] - public override Task CanRoundTripLargeNumbersWithExpirationAsync() + public override Task SetAsync_WithLargeNumber_StoresCorrectly() { - return base.CanRoundTripLargeNumbersWithExpirationAsync(); + return base.SetAsync_WithLargeNumber_StoresCorrectly(); } [Fact] - public override Task CanManageListsAsync() + public override Task SetAsync_WithLargeNumbersAndExpiration_PreservesValues() { - return base.CanManageListsAsync(); + return base.SetAsync_WithLargeNumbersAndExpiration_PreservesValues(); } [Fact] - public override Task CanManageListsWithNullItemsAsync() + public override Task SetAsync_WithNestedScopes_PreservesHierarchy() { - return base.CanManageListsWithNullItemsAsync(); + return base.SetAsync_WithNestedScopes_PreservesHierarchy(); } [Fact] - public override Task CanManageStringListsAsync() + public override Task SetAsync_WithNullKey_ThrowsArgumentNullException() { - return base.CanManageStringListsAsync(); + return base.SetAsync_WithNullKey_ThrowsArgumentNullException(); } [Fact] - public override Task CanManageListPagingAsync() + public override Task SetAsync_WithNullReferenceType_StoresAsNullValue() { - return base.CanManageListPagingAsync(); + return base.SetAsync_WithNullReferenceType_StoresAsNullValue(); } [Fact] - public override Task CanManageGetListExpirationAsync() + public override Task SetAsync_WithNullValueType_StoresAsNullValue() { - return base.CanManageGetListExpirationAsync(); + return base.SetAsync_WithNullValueType_StoresAsNullValue(); } [Fact] - public override Task CanManageListAddExpirationAsync() + public override Task SetAsync_WithShortExpiration_ExpiresCorrectly() { - return base.CanManageListAddExpirationAsync(); + return base.SetAsync_WithShortExpiration_ExpiresCorrectly(); } [Fact] - public override Task CanManageListRemoveExpirationAsync() + public override Task SetAsync_WithWhitespaceKey_ThrowsArgumentException() { - return base.CanManageListRemoveExpirationAsync(); + return base.SetAsync_WithWhitespaceKey_ThrowsArgumentException(); } - [Fact(Skip = "Performance Test")] - public override Task MeasureThroughputAsync() + [Fact] + public override Task SetExpirationAsync_ChangingFromNoExpirationToFutureTime_UpdatesCorrectly() { - return base.MeasureThroughputAsync(); + return base.SetExpirationAsync_ChangingFromNoExpirationToFutureTime_UpdatesCorrectly(); } - [Fact(Skip = "Performance Test")] - public override Task MeasureSerializerSimpleThroughputAsync() + [Fact] + public override Task SetExpirationAsync_ChangingToDateTimeMinValue_RemovesKey() { - return base.MeasureSerializerSimpleThroughputAsync(); + return base.SetExpirationAsync_ChangingToDateTimeMinValue_RemovesKey(); } - [Fact(Skip = "Performance Test")] - public override Task MeasureSerializerComplexThroughputAsync() + [Fact] + public override Task SetExpirationAsync_WithCurrentTime_ExpiresImmediately() + { + return base.SetExpirationAsync_WithCurrentTime_ExpiresImmediately(); + } + + [Fact] + public override Task SetExpirationAsync_WithDateTimeMaxValue_NeverExpires() + { + return base.SetExpirationAsync_WithDateTimeMaxValue_NeverExpires(); + } + + [Fact] + public override Task SetExpirationAsync_WithDateTimeMinValue_ExpiresImmediately() + { + return base.SetExpirationAsync_WithDateTimeMinValue_ExpiresImmediately(); + } + + [Fact] + public override Task SetExpirationAsync_WithDifferentCasedKeys_SetsOnlyExactMatch() + { + return base.SetExpirationAsync_WithDifferentCasedKeys_SetsOnlyExactMatch(); + } + + [Fact] + public override Task SetExpirationAsync_WithEmptyKey_ThrowsArgumentNullException() + { + return base.SetExpirationAsync_WithEmptyKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task SetExpirationAsync_WithNullKey_ThrowsArgumentNullException() + { + return base.SetExpirationAsync_WithNullKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task SetExpirationAsync_WithWhitespaceKey_ThrowsArgumentException() + { + return base.SetExpirationAsync_WithWhitespaceKey_ThrowsArgumentException(); + } + + [Fact] + public override Task SetIfHigherAsync_WithDateTime_DoesNotUpdateWhenLower() + { + return base.SetIfHigherAsync_WithDateTime_DoesNotUpdateWhenLower(); + } + + [Fact] + public override Task SetIfHigherAsync_WithDateTime_InitializesWhenKeyNotExists() + { + return base.SetIfHigherAsync_WithDateTime_InitializesWhenKeyNotExists(); + } + + [Fact] + public override Task SetIfHigherAsync_WithDateTime_UpdatesWhenHigher() + { + return base.SetIfHigherAsync_WithDateTime_UpdatesWhenHigher(); + } + + [Fact] + public override Task SetIfHigherAsync_WithLargeNumbers_UpdatesWhenHigher() + { + return base.SetIfHigherAsync_WithLargeNumbers_UpdatesWhenHigher(); + } + + [Fact] + public override Task SetIfLowerAsync_WithDateTime_DoesNotUpdateWhenHigher() + { + return base.SetIfLowerAsync_WithDateTime_DoesNotUpdateWhenHigher(); + } + + [Fact] + public override Task SetIfLowerAsync_WithDateTime_InitializesWhenKeyNotExists() + { + return base.SetIfLowerAsync_WithDateTime_InitializesWhenKeyNotExists(); + } + + [Fact] + public override Task SetIfLowerAsync_WithDateTime_UpdatesWhenLower() + { + return base.SetIfLowerAsync_WithDateTime_UpdatesWhenLower(); + } + + [Fact] + public override Task SetIfLowerAsync_WithLargeNumbers_UpdatesWhenLower() + { + return base.SetIfLowerAsync_WithLargeNumbers_UpdatesWhenLower(); + } + + [Fact] + public override Task SetUnixTimeMillisecondsAsync_WithLocalDateTime_StoresCorrectly() + { + return base.SetUnixTimeMillisecondsAsync_WithLocalDateTime_StoresCorrectly(); + } + + [Fact] + public override Task SetUnixTimeSecondsAsync_WithUtcDateTime_StoresCorrectly() { - return base.MeasureSerializerComplexThroughputAsync(); + return base.SetUnixTimeSecondsAsync_WithUtcDateTime_StoresCorrectly(); } [Fact] - public async Task CanUpgradeListType() + public async Task GetListAsync_WithExistingFormat_UpgradeListType() { var db = SharedConnection.GetMuxer(Log).GetDatabase(); var cache = GetCacheClient(); diff --git a/tests/Foundatio.Redis.Tests/Caching/RedisHybridCacheClientTests.cs b/tests/Foundatio.Redis.Tests/Caching/RedisHybridCacheClientTests.cs index 86765c1..7d72988 100644 --- a/tests/Foundatio.Redis.Tests/Caching/RedisHybridCacheClientTests.cs +++ b/tests/Foundatio.Redis.Tests/Caching/RedisHybridCacheClientTests.cs @@ -26,134 +26,852 @@ protected override ICacheClient GetCacheClient(bool shouldThrowOnSerializationEr } [Fact] - public override Task CanGetAllAsync() + public override Task AddAsync_WithConcurrentRequests_OnlyOneSucceeds() { - return base.CanGetAllAsync(); + return base.AddAsync_WithConcurrentRequests_OnlyOneSucceeds(); } [Fact] - public override Task CanGetAllWithOverlapAsync() + public override Task AddAsync_WithEmptyKey_ThrowsArgumentNullException() { - return base.CanGetAllWithOverlapAsync(); + return base.AddAsync_WithEmptyKey_ThrowsArgumentNullException(); } [Fact] - public override Task CanSetAsync() + public override Task AddAsync_WithExistingKey_ReturnsFalseAndPreservesValue() { - return base.CanSetAsync(); + return base.AddAsync_WithExistingKey_ReturnsFalseAndPreservesValue(); + } + + [Fact(Skip = "TODO: Look into this as the local cache client maintenance never schedules for expiration")] + public override Task AddAsync_WithExpiration_ExpiresRemoteItems() + { + return base.AddAsync_WithExpiration_ExpiresRemoteItems(); + } + + [Fact] + public override Task AddAsync_WithNestedKeyUsingSeparator_StoresCorrectly() + { + return base.AddAsync_WithNestedKeyUsingSeparator_StoresCorrectly(); + } + + [Fact] + public override Task AddAsync_WithNewKey_ReturnsTrue() + { + return base.AddAsync_WithNewKey_ReturnsTrue(); + } + + [Fact] + public override Task AddAsync_WithNullKey_ThrowsArgumentNullException() + { + return base.AddAsync_WithNullKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task AddAsync_WithWhitespaceKey_ThrowsArgumentException() + { + return base.AddAsync_WithWhitespaceKey_ThrowsArgumentException(); + } + + [Fact] + public override Task CacheOperations_WithMultipleTypes_MeasuresThroughput() + { + return base.CacheOperations_WithMultipleTypes_MeasuresThroughput(); + } + + [Fact] + public override Task CacheOperations_WithRepeatedSetAndGet_MeasuresThroughput() + { + return base.CacheOperations_WithRepeatedSetAndGet_MeasuresThroughput(); + } + + [Fact] + public override Task ExistsAsync_AfterKeyExpires_ReturnsFalse() + { + return base.ExistsAsync_AfterKeyExpires_ReturnsFalse(); + } + + [Fact] + public override Task ExistsAsync_WithDifferentCasedKeys_ChecksExactMatch() + { + return base.ExistsAsync_WithDifferentCasedKeys_ChecksExactMatch(); + } + + [Fact] + public override Task ExistsAsync_WithEmptyKey_ThrowsArgumentNullException() + { + return base.ExistsAsync_WithEmptyKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task ExistsAsync_WithExistingKey_ReturnsTrue() + { + return base.ExistsAsync_WithExistingKey_ReturnsTrue(); + } + + [Fact] + public override Task ExistsAsync_WithExpiredKey_ReturnsFalse() + { + return base.ExistsAsync_WithExpiredKey_ReturnsFalse(); + } + + [Fact] + public override Task ExistsAsync_WithLocalCache_ChecksLocalCacheFirst() + { + return base.ExistsAsync_WithLocalCache_ChecksLocalCacheFirst(); + } + + [Fact] + public override Task ExistsAsync_WithNonExistentKey_ReturnsFalse() + { + return base.ExistsAsync_WithNonExistentKey_ReturnsFalse(); + } + + [Fact] + public override Task ExistsAsync_WithNullKey_ThrowsArgumentNullException() + { + return base.ExistsAsync_WithNullKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task ExistsAsync_WithNullStoredValue_ReturnsTrue() + { + return base.ExistsAsync_WithNullStoredValue_ReturnsTrue(); + } + + [Fact] + public override Task ExistsAsync_WithScopedCache_ChecksOnlyWithinScope() + { + return base.ExistsAsync_WithScopedCache_ChecksOnlyWithinScope(); + } + + [Fact] + public override Task ExistsAsync_WithWhitespaceKey_ThrowsArgumentException() + { + return base.ExistsAsync_WithWhitespaceKey_ThrowsArgumentException(); + } + + [Fact] + public override Task GetAllAsync_WithEmptyKeys_ReturnsEmpty() + { + return base.GetAllAsync_WithEmptyKeys_ReturnsEmpty(); + } + + [Fact] + public override Task GetAllAsync_WithExistingKeys_ReturnsAllValues() + { + return base.GetAllAsync_WithExistingKeys_ReturnsAllValues(); + } + + [Fact] + public override Task GetAllAsync_WithKeysContainingEmpty_ThrowsArgumentException() + { + return base.GetAllAsync_WithKeysContainingEmpty_ThrowsArgumentException(); + } + + [Fact] + public override Task GetAllAsync_WithKeysContainingNull_ThrowsArgumentException() + { + return base.GetAllAsync_WithKeysContainingNull_ThrowsArgumentException(); + } + + [Fact] + public override Task GetAllAsync_WithKeysContainingWhitespace_ThrowsArgumentException() + { + return base.GetAllAsync_WithKeysContainingWhitespace_ThrowsArgumentException(); + } + + [Fact] + public override Task GetAllAsync_WithMixedCaseKeys_RetrievesExactMatches() + { + return base.GetAllAsync_WithMixedCaseKeys_RetrievesExactMatches(); + } + + [Fact] + public override Task GetAllAsync_WithMixedObjectTypes_ReturnsCorrectValues() + { + return base.GetAllAsync_WithMixedObjectTypes_ReturnsCorrectValues(); + } + + [Fact] + public override Task GetAllAsync_WithMultipleKeys_UsesHybridCache() + { + return base.GetAllAsync_WithMultipleKeys_UsesHybridCache(); + } + + [Fact] + public override Task GetAllAsync_WithNonExistentKeys_ReturnsEmptyResults() + { + return base.GetAllAsync_WithNonExistentKeys_ReturnsEmptyResults(); + } + + [Fact] + public override Task GetAllAsync_WithNullKeys_ThrowsArgumentNullException() + { + return base.GetAllAsync_WithNullKeys_ThrowsArgumentNullException(); + } + + [Fact] + public override Task GetAllAsync_WithNullValues_HandlesNullsCorrectly() + { + return base.GetAllAsync_WithNullValues_HandlesNullsCorrectly(); + } + + [Fact] + public override Task GetAllAsync_WithOverlappingKeys_UsesLatestValues() + { + return base.GetAllAsync_WithOverlappingKeys_UsesLatestValues(); + } + + [Fact] + public override Task GetAllAsync_WithScopedCache_ReturnsUnscopedKeys() + { + return base.GetAllAsync_WithScopedCache_ReturnsUnscopedKeys(); + } + + [Fact] + public override Task GetAllExpirationAsync_WithExpiredKeys_ExcludesExpiredKeys() + { + return base.GetAllExpirationAsync_WithExpiredKeys_ExcludesExpiredKeys(); + } + + [Theory] + [InlineData(1000)] + [InlineData(10000)] + public override Task GetAllExpirationAsync_WithLargeNumberOfKeys_ReturnsAllExpirations(int count) + { + return base.GetAllExpirationAsync_WithLargeNumberOfKeys_ReturnsAllExpirations(count); + } + + [Fact] + public override Task GetAllExpirationAsync_WithMixedKeys_ReturnsOnlyKeysWithExpiration() + { + return base.GetAllExpirationAsync_WithMixedKeys_ReturnsOnlyKeysWithExpiration(); + } + + [Fact] + public override Task GetAsync_WithComplexObject_PreservesAllProperties() + { + return base.GetAsync_WithComplexObject_PreservesAllProperties(); + } + + [Fact] + public override Task GetAsync_WithComplexObject_ReturnsNewInstance() + { + return base.GetAsync_WithComplexObject_ReturnsNewInstance(); + } + + [Fact] + public override Task GetAsync_WithDifferentCasedKeys_TreatsAsDifferentKeys() + { + return base.GetAsync_WithDifferentCasedKeys_TreatsAsDifferentKeys(); + } + + [Fact] + public override Task GetAsync_WithEmptyKey_ThrowsArgumentNullException() + { + return base.GetAsync_WithEmptyKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task GetAsync_WithLargeNumber_ReturnsCorrectValue() + { + return base.GetAsync_WithLargeNumber_ReturnsCorrectValue(); + } + + [Fact] + public override Task GetAsync_WithMaxLongAsInt_ThrowsException() + { + return base.GetAsync_WithMaxLongAsInt_ThrowsException(); + } + + [Fact] + public override Task GetAsync_WithNonExistentKey_ReturnsNoValue() + { + return base.GetAsync_WithNonExistentKey_ReturnsNoValue(); + } + + [Fact] + public override Task GetAsync_WithNullKey_ThrowsArgumentNullException() + { + return base.GetAsync_WithNullKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task GetAsync_WithNumericTypeConversion_ConvertsIntToLong() + { + return base.GetAsync_WithNumericTypeConversion_ConvertsIntToLong(); + } + + [Fact] + public override Task GetAsync_WithNumericTypeConversion_ConvertsLongToInt() + { + return base.GetAsync_WithNumericTypeConversion_ConvertsLongToInt(); + } + + [Fact] + public override Task GetAsync_WithTryGetSemanticsAndComplexTypeAsLong_ReturnsNoValue() + { + return base.GetAsync_WithTryGetSemanticsAndComplexTypeAsLong_ReturnsNoValue(); + } + + [Fact] + public override Task GetAsync_WithTryGetSemanticsAndIntAsLong_ConvertsSuccessfully() + { + return base.GetAsync_WithTryGetSemanticsAndIntAsLong_ConvertsSuccessfully(); + } + + [Fact] + public override Task GetAsync_WithTryGetSemanticsAndMaxLongAsInt_ReturnsNoValue() + { + return base.GetAsync_WithTryGetSemanticsAndMaxLongAsInt_ReturnsNoValue(); + } + + [Fact] + public override Task GetAsync_WithWhitespaceKey_ThrowsArgumentException() + { + return base.GetAsync_WithWhitespaceKey_ThrowsArgumentException(); + } + + [Fact] + public override Task GetExpirationAsync_AfterExpiry_ReturnsNull() + { + return base.GetExpirationAsync_AfterExpiry_ReturnsNull(); + } + + [Fact] + public override Task GetExpirationAsync_WithDifferentCasedKeys_GetsExactMatch() + { + return base.GetExpirationAsync_WithDifferentCasedKeys_GetsExactMatch(); + } + + [Fact] + public override Task GetExpirationAsync_WithEmptyKey_ThrowsArgumentNullException() + { + return base.GetExpirationAsync_WithEmptyKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task GetExpirationAsync_WithExpiration_ReturnsCorrectTimeSpan() + { + return base.GetExpirationAsync_WithExpiration_ReturnsCorrectTimeSpan(); + } + + [Fact] + public override Task GetExpirationAsync_WithLocalCache_ChecksLocalCacheFirst() + { + return base.GetExpirationAsync_WithLocalCache_ChecksLocalCacheFirst(); + } + + [Fact] + public override Task GetExpirationAsync_WithExpiredKey_ReturnsNull() + { + return base.GetExpirationAsync_WithExpiredKey_ReturnsNull(); + } + + [Fact] + public override Task GetExpirationAsync_WithNoExpiration_ReturnsNull() + { + return base.GetExpirationAsync_WithNoExpiration_ReturnsNull(); + } + + [Fact] + public override Task GetExpirationAsync_WithNonExistentKey_ReturnsNull() + { + return base.GetExpirationAsync_WithNonExistentKey_ReturnsNull(); + } + + [Fact] + public override Task GetExpirationAsync_WithNullKey_ThrowsArgumentNullException() + { + return base.GetExpirationAsync_WithNullKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task GetExpirationAsync_WithWhitespaceKey_ThrowsArgumentException() + { + return base.GetExpirationAsync_WithWhitespaceKey_ThrowsArgumentException(); + } + + [Fact] + public override Task GetListAsync_WithEmptyKey_ThrowsArgumentNullException() + { + return base.GetListAsync_WithEmptyKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task GetListAsync_WithExpiredItems_RemovesExpiredAndReturnsActive() + { + return base.GetListAsync_WithExpiredItems_RemovesExpiredAndReturnsActive(); + } + + [Fact] + public override Task GetListAsync_WithInvalidPageNumber_ThrowsArgumentOutOfRangeException() + { + return base.GetListAsync_WithInvalidPageNumber_ThrowsArgumentOutOfRangeException(); + } + + [Fact] + public override Task GetListAsync_WithMultiplePages_ReturnsAllItems() + { + return base.GetListAsync_WithMultiplePages_ReturnsAllItems(); + } + + [Fact] + public override Task GetListAsync_WithNewItemsAdded_ReturnsNewItemsLast() + { + return base.GetListAsync_WithNewItemsAdded_ReturnsNewItemsLast(); + } + + [Fact] + public override Task GetListAsync_WithNullKey_ThrowsArgumentNullException() + { + return base.GetListAsync_WithNullKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task GetListAsync_WithPageBeyondEnd_ReturnsEmptyCollection() + { + return base.GetListAsync_WithPageBeyondEnd_ReturnsEmptyCollection(); + } + + [Fact] + public override Task GetListAsync_WithPaging_ReturnsCorrectPageSize() + { + return base.GetListAsync_WithPaging_ReturnsCorrectPageSize(); + } + + [Fact] + public override Task GetListAsync_WithWhitespaceKey_ThrowsArgumentException() + { + return base.GetListAsync_WithWhitespaceKey_ThrowsArgumentException(); + } + + [Fact] + public override Task GetUnixTimeMillisecondsAsync_WithLocalDateTime_ReturnsCorrectly() + { + return base.GetUnixTimeMillisecondsAsync_WithLocalDateTime_ReturnsCorrectly(); + } + + [Fact] + public override Task GetUnixTimeMillisecondsAsync_WithUtcDateTime_ReturnsCorrectly() + { + return base.GetUnixTimeMillisecondsAsync_WithUtcDateTime_ReturnsCorrectly(); + } + + [Fact] + public override Task GetUnixTimeSecondsAsync_WithUtcDateTime_ReturnsCorrectly() + { + return base.GetUnixTimeSecondsAsync_WithUtcDateTime_ReturnsCorrectly(); + } + + [Fact] + public override Task IncrementAsync_WithDifferentCasedKeys_IncrementsDistinctCounters() + { + return base.IncrementAsync_WithDifferentCasedKeys_IncrementsDistinctCounters(); + } + + [Fact] + public override Task IncrementAsync_WithEmptyKey_ThrowsArgumentNullException() + { + return base.IncrementAsync_WithEmptyKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task IncrementAsync_WithExistingKey_IncrementsValue() + { + return base.IncrementAsync_WithExistingKey_IncrementsValue(); + } + + [Fact] + public override Task IncrementAsync_WithExpiration_ExpiresCorrectly() + { + return base.IncrementAsync_WithExpiration_ExpiresCorrectly(); + } + + [Fact] + public override Task IncrementAsync_WithNonExistentKey_InitializesToOne() + { + return base.IncrementAsync_WithNonExistentKey_InitializesToOne(); + } + + [Fact] + public override Task IncrementAsync_WithNullKey_ThrowsArgumentNullException() + { + return base.IncrementAsync_WithNullKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task IncrementAsync_WithScopedCache_WorksWithinScope() + { + return base.IncrementAsync_WithScopedCache_WorksWithinScope(); + } + + [Fact] + public override Task IncrementAsync_WithSpecifiedAmount_IncrementsCorrectly() + { + return base.IncrementAsync_WithSpecifiedAmount_IncrementsCorrectly(); + } + + [Fact] + public override Task IncrementAsync_WithWhitespaceKey_ThrowsArgumentException() + { + return base.IncrementAsync_WithWhitespaceKey_ThrowsArgumentException(); + } + + [Fact] + public override Task ListAddAsync_WithDifferentCasedKeys_MaintainsDistinctLists() + { + return base.ListAddAsync_WithDifferentCasedKeys_MaintainsDistinctLists(); + } + + [Fact] + public override Task ListAddAsync_WithDuplicates_RemovesDuplicatesAndAddsItems() + { + return base.ListAddAsync_WithDuplicates_RemovesDuplicatesAndAddsItems(); + } + + [Fact] + public override Task ListAddAsync_WithDuplicates_StoresUniqueValuesOnly() + { + return base.ListAddAsync_WithDuplicates_StoresUniqueValuesOnly(); + } + + [Fact] + public override Task ListAddAsync_WithEmptyCollection_NoOp() + { + return base.ListAddAsync_WithEmptyCollection_NoOp(); + } + + [Fact] + public override Task ListAddAsync_WithEmptyKey_ThrowsArgumentNullException() + { + return base.ListAddAsync_WithEmptyKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task ListAddAsync_WithExistingNonListKey_ThrowsException() + { + return base.ListAddAsync_WithExistingNonListKey_ThrowsException(); + } + + [Fact] + public override Task ListAddAsync_WithFutureExpiration_AddsAndExpiresCorrectly() + { + return base.ListAddAsync_WithFutureExpiration_AddsAndExpiresCorrectly(); + } + + [Fact] + public override Task ListAddAsync_WithMultipleExpirations_ExpiresIndividualItems() + { + return base.ListAddAsync_WithMultipleExpirations_ExpiresIndividualItems(); + } + + [Fact] + public override Task ListAddAsync_WithMultipleInstances_WorksCorrectly() + { + return base.ListAddAsync_WithMultipleInstances_WorksCorrectly(); + } + + [Fact] + public override Task ListAddAsync_WithNullCollection_ThrowsArgumentNullException() + { + return base.ListAddAsync_WithNullCollection_ThrowsArgumentNullException(); + } + + [Fact] + public override Task ListAddAsync_WithNullItem_IgnoresNull() + { + return base.ListAddAsync_WithNullItem_IgnoresNull(); + } + + [Fact] + public override Task ListAddAsync_WithNullKey_ThrowsArgumentNullException() + { + return base.ListAddAsync_WithNullKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task ListAddAsync_WithNullValues_ThrowsArgumentNullException() + { + return base.ListAddAsync_WithNullValues_ThrowsArgumentNullException(); + } + + [Fact] + public override Task ListAddAsync_WithPastExpiration_RemovesItem() + { + return base.ListAddAsync_WithPastExpiration_RemovesItem(); + } + + [Fact] + public override Task ListAddAsync_WithSingleString_StoresAsStringNotCharArray() + { + return base.ListAddAsync_WithSingleString_StoresAsStringNotCharArray(); + } + + [Fact] + public override Task ListAddAsync_WithWhitespaceKey_ThrowsArgumentException() + { + return base.ListAddAsync_WithWhitespaceKey_ThrowsArgumentException(); + } + + [Fact] + public override Task ListRemoveAsync_WithEmptyKey_ThrowsArgumentNullException() + { + return base.ListRemoveAsync_WithEmptyKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task ListRemoveAsync_WithMultipleValues_RemovesAll() + { + return base.ListRemoveAsync_WithMultipleValues_RemovesAll(); + } + + [Fact] + public override Task ListRemoveAsync_WithNullCollection_ThrowsArgumentNullException() + { + return base.ListRemoveAsync_WithNullCollection_ThrowsArgumentNullException(); + } + + [Fact] + public override Task ListRemoveAsync_WithNullItem_IgnoresNull() + { + return base.ListRemoveAsync_WithNullItem_IgnoresNull(); + } + + [Fact] + public override Task ListRemoveAsync_WithNullKey_ThrowsArgumentNullException() + { + return base.ListRemoveAsync_WithNullKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task ListRemoveAsync_WithNullValues_ThrowsArgumentNullException() + { + return base.ListRemoveAsync_WithNullValues_ThrowsArgumentNullException(); + } + + [Fact] + public override Task ListRemoveAsync_WithSingleValue_RemovesCorrectly() + { + return base.ListRemoveAsync_WithSingleValue_RemovesCorrectly(); + } + + [Fact] + public override Task ListRemoveAsync_WithValidValues_RemovesKeyWhenEmpty() + { + return base.ListRemoveAsync_WithValidValues_RemovesKeyWhenEmpty(); + } + + [Fact] + public override Task ListRemoveAsync_WithWhitespaceKey_ThrowsArgumentException() + { + return base.ListRemoveAsync_WithWhitespaceKey_ThrowsArgumentException(); + } + + [Fact] + public override Task RemoveAllAsync_WithEmptyKeys_Succeeds() + { + return base.RemoveAllAsync_WithEmptyKeys_Succeeds(); + } + + [Fact] + public override Task RemoveAllAsync_WithKeysContainingEmpty_ThrowsArgumentException() + { + return base.RemoveAllAsync_WithKeysContainingEmpty_ThrowsArgumentException(); + } + + [Fact] + public override Task RemoveAllAsync_WithKeysContainingNull_ThrowsArgumentException() + { + return base.RemoveAllAsync_WithKeysContainingNull_ThrowsArgumentException(); + } + + [Fact] + public override Task RemoveAllAsync_WithKeysContainingWhitespace_ThrowsArgumentException() + { + return base.RemoveAllAsync_WithKeysContainingWhitespace_ThrowsArgumentException(); + } + + [Fact] + public override Task RemoveAllAsync_WithLargeNumberOfKeys_RemovesAllKeysEfficiently() + { + return base.RemoveAllAsync_WithLargeNumberOfKeys_RemovesAllKeysEfficiently(); + } + + [Fact] + public override Task RemoveAllAsync_WithLocalCache_InvalidatesLocalCache() + { + return base.RemoveAllAsync_WithLocalCache_InvalidatesLocalCache(); + } + + [Fact] + public override Task RemoveAllAsync_WithMixedCaseKeys_RemovesOnlyExactMatches() + { + return base.RemoveAllAsync_WithMixedCaseKeys_RemovesOnlyExactMatches(); + } + + [Fact] + public override Task RemoveAllAsync_WithNullKeys_ThrowsArgumentNullException() + { + return base.RemoveAllAsync_WithNullKeys_ThrowsArgumentNullException(); + } + + [Fact] + public override Task RemoveAllAsync_WithScopedCache_AffectsOnlyScopedKeys() + { + return base.RemoveAllAsync_WithScopedCache_AffectsOnlyScopedKeys(); + } + + [Fact] + public override Task RemoveAllAsync_WithSpecificKeyCollection_RemovesOnlySpecifiedKeys() + { + return base.RemoveAllAsync_WithSpecificKeyCollection_RemovesOnlySpecifiedKeys(); + } + + [Fact] + public override Task RemoveAsync_AfterSetAndGet_RemovesCorrectly() + { + return base.RemoveAsync_AfterSetAndGet_RemovesCorrectly(); + } + + [Fact] + public override Task RemoveAsync_MultipleTimes_Succeeds() + { + return base.RemoveAsync_MultipleTimes_Succeeds(); + } + + [Fact] + public override Task RemoveAsync_WithEmptyKey_ThrowsArgumentNullException() + { + return base.RemoveAsync_WithEmptyKey_ThrowsArgumentNullException(); } [Fact] - public override Task CanSetAndGetValueAsync() + public override Task RemoveAsync_WithExistingKey_RemovesSuccessfully() { - return base.CanSetAndGetValueAsync(); + return base.RemoveAsync_WithExistingKey_RemovesSuccessfully(); } [Fact] - public override Task CanAddAsync() + public override Task RemoveAsync_WithExpiredKey_Succeeds() { - return base.CanAddAsync(); + return base.RemoveAsync_WithExpiredKey_Succeeds(); } [Fact] - public override Task CanAddConcurrentlyAsync() + public override Task RemoveAsync_WithNonExistentKey_Succeeds() { - return base.CanAddConcurrentlyAsync(); + return base.RemoveAsync_WithNonExistentKey_Succeeds(); } [Fact] - public override Task CanGetAsync() + public override Task RemoveAsync_WithNullKey_ThrowsArgumentNullException() { - return base.CanGetAsync(); + return base.RemoveAsync_WithNullKey_ThrowsArgumentNullException(); } [Fact] - public override Task CanTryGetAsync() + public override Task RemoveAsync_WithNullValue_RemovesSuccessfully() { - return base.CanTryGetAsync(); + return base.RemoveAsync_WithNullValue_RemovesSuccessfully(); } [Fact] - public override Task CanUseScopedCachesAsync() + public override Task RemoveAsync_WithScopedCache_RemovesOnlyWithinScope() { - return base.CanUseScopedCachesAsync(); + return base.RemoveAsync_WithScopedCache_RemovesOnlyWithinScope(); } [Fact] - public override Task CanRemoveAllAsync() + public override Task RemoveAsync_WithSpecificCase_RemovesOnlyMatchingKey() { - return base.CanRemoveAllAsync(); + return base.RemoveAsync_WithSpecificCase_RemovesOnlyMatchingKey(); } [Fact] - public override Task CanRemoveAllKeysAsync() + public override Task RemoveAsync_WithWhitespaceKey_ThrowsArgumentException() { - return base.CanRemoveAllKeysAsync(); + return base.RemoveAsync_WithWhitespaceKey_ThrowsArgumentException(); } [Fact] - public override Task CanRemoveByPrefixAsync() + public override Task RemoveByPrefixAsync_AsteriskPrefixWithScopedCache_TreatedAsLiteral() { - return base.CanRemoveByPrefixAsync(); + return base.RemoveByPrefixAsync_AsteriskPrefixWithScopedCache_TreatedAsLiteral(); } [Theory] - [MemberData(nameof(GetRegexSpecialCharacters))] - public override Task CanRemoveByPrefixWithRegexCharactersAsync(string specialChar) + [InlineData("snowboard", 1, true)] // Exact key match + [InlineData("s", 1, true)] // Partial prefix match + [InlineData(null, 1, false)] // Null prefix (all keys in scope) + [InlineData("", 1, false)] // Empty prefix (all keys in scope) + public override Task RemoveByPrefixAsync_FromScopedCache_RemovesOnlyScopedKeys(string prefixToRemove, int expectedRemovedCount, bool shouldUnscopedRemain) { - return base.CanRemoveByPrefixWithRegexCharactersAsync(specialChar); + return base.RemoveByPrefixAsync_FromScopedCache_RemovesOnlyScopedKeys(prefixToRemove, expectedRemovedCount, shouldUnscopedRemain); } [Theory] - [MemberData(nameof(GetWildcardPatterns))] - public override Task CanRemoveByPrefixWithWildcardPatternsAsync(string pattern) + [InlineData(null)] + [InlineData("")] + public override Task RemoveByPrefixAsync_NullOrEmptyPrefixWithScopedCache_RemovesCorrectKeys(string prefix) { - return base.CanRemoveByPrefixWithWildcardPatternsAsync(pattern); + return base.RemoveByPrefixAsync_NullOrEmptyPrefixWithScopedCache_RemovesCorrectKeys(prefix); } [Fact] - public override Task CanRemoveByPrefixWithDoubleAsteriskAsync() + public override Task RemoveByPrefixAsync_PartialPrefixWithScopedCache_RemovesMatchingKeys() { - return base.CanRemoveByPrefixWithDoubleAsteriskAsync(); + return base.RemoveByPrefixAsync_PartialPrefixWithScopedCache_RemovesMatchingKeys(); } - [Theory] - [MemberData(nameof(GetSpecialPrefixes))] - public override Task CanRemoveByPrefixWithSpecialCharactersAsync(string specialPrefix) + [Fact] + public override Task RemoveByPrefixAsync_WithCaseSensitivePrefix_RemovesOnlyMatchingCase() { - return base.CanRemoveByPrefixWithSpecialCharactersAsync(specialPrefix); + return base.RemoveByPrefixAsync_WithCaseSensitivePrefix_RemovesOnlyMatchingCase(); } [Fact] - public override Task CanRemoveByPrefixWithNullAsync() + public override Task RemoveByPrefixAsync_WithDifferentCasedScopes_RemovesOnlyMatchingScope() { - return base.CanRemoveByPrefixWithNullAsync(); + return base.RemoveByPrefixAsync_WithDifferentCasedScopes_RemovesOnlyMatchingScope(); } [Fact] - public override Task CanRemoveByPrefixWithEmptyStringAsync() + public override Task RemoveByPrefixAsync_WithDoubleAsteriskPrefix_TreatsAsLiteral() { - return base.CanRemoveByPrefixWithEmptyStringAsync(); + return base.RemoveByPrefixAsync_WithDoubleAsteriskPrefix_TreatsAsLiteral(); } - [Theory] - [MemberData(nameof(GetWhitespaceOnlyPrefixes))] - public override Task CanRemoveByPrefixWithWhitespaceAsync(string whitespacePrefix) + [Fact] + public override Task RemoveByPrefixAsync_WithEmptyPrefix_RemovesAllKeys() + { + return base.RemoveByPrefixAsync_WithEmptyPrefix_RemovesAllKeys(); + } + + [Fact] + public override Task RemoveByPrefixAsync_WithEmptyPrefix_ThrowsArgumentException() { - return base.CanRemoveByPrefixWithWhitespaceAsync(whitespacePrefix); + return base.RemoveByPrefixAsync_WithEmptyPrefix_ThrowsArgumentException(); } [Theory] [MemberData(nameof(GetLineEndingPrefixes))] - public override Task CanRemoveByPrefixWithLineEndingsAsync(string lineEndingPrefix) + public override Task RemoveByPrefixAsync_WithLineEndingPrefix_TreatsAsLiteral(string lineEndingPrefix) + { + return base.RemoveByPrefixAsync_WithLineEndingPrefix_TreatsAsLiteral(lineEndingPrefix); + } + + [Fact] + public override Task RemoveByPrefixAsync_WithLocalCache_InvalidatesLocalCache() { - return base.CanRemoveByPrefixWithLineEndingsAsync(lineEndingPrefix); + return base.RemoveByPrefixAsync_WithLocalCache_InvalidatesLocalCache(); } [Fact] - public override Task CanRemoveByPrefixWithScopedCachesAsync() + public override Task RemoveByPrefixAsync_WithMatchingPrefix_RemovesOnlyPrefixedKeys() { - return base.CanRemoveByPrefixWithScopedCachesAsync(); + return base.RemoveByPrefixAsync_WithMatchingPrefix_RemovesOnlyPrefixedKeys(); } [Theory] @@ -161,65 +879,241 @@ public override Task CanRemoveByPrefixWithScopedCachesAsync() [InlineData(500)] [InlineData(5000)] [InlineData(50000)] - public override Task CanRemoveByPrefixMultipleEntriesAsync(int count) + public override Task RemoveByPrefixAsync_WithMultipleMatchingKeys_RemovesOnlyPrefixedKeys(int count) { - return base.CanRemoveByPrefixMultipleEntriesAsync(count); + return base.RemoveByPrefixAsync_WithMultipleMatchingKeys_RemovesOnlyPrefixedKeys(count); } [Fact] - public override Task CanSetAndGetObjectAsync() + public override Task RemoveByPrefixAsync_WithNonMatchingPrefix_RemovesZeroKeys() { - return base.CanSetAndGetObjectAsync(); + return base.RemoveByPrefixAsync_WithNonMatchingPrefix_RemovesZeroKeys(); } [Fact] - public override Task GetExpirationAsync_WithVariousStates_ReturnsCorrectly() + public override Task RemoveByPrefixAsync_WithNullPrefix_RemovesAllKeys() { - return base.GetExpirationAsync_WithVariousStates_ReturnsCorrectly(); + return base.RemoveByPrefixAsync_WithNullPrefix_RemovesAllKeys(); } [Fact] - public override Task GetAllExpiration_WithMultipleKeys_ReturnsAllExpirations() + public override Task RemoveByPrefixAsync_WithNullPrefix_ThrowsArgumentNullException() { - return base.GetAllExpiration_WithMultipleKeys_ReturnsAllExpirations(); + return base.RemoveByPrefixAsync_WithNullPrefix_ThrowsArgumentNullException(); } [Theory] - [InlineData(1000)] - [InlineData(10000)] - public override Task GetAllExpiration_WithLargeNumberOfKeys_ReturnsAllExpirations(int count) + [MemberData(nameof(GetRegexSpecialCharacters))] + public override Task RemoveByPrefixAsync_WithRegexMetacharacter_TreatsAsLiteral(string specialChar) + { + return base.RemoveByPrefixAsync_WithRegexMetacharacter_TreatsAsLiteral(specialChar); + } + + [Fact] + public override Task RemoveByPrefixAsync_WithScopedCache_AffectsOnlyScopedKeys() + { + return base.RemoveByPrefixAsync_WithScopedCache_AffectsOnlyScopedKeys(); + } + + [Theory] + [MemberData(nameof(GetSpecialPrefixes))] + public override Task RemoveByPrefixAsync_WithSpecialCharacterPrefix_TreatsAsLiteral(string specialPrefix) + { + return base.RemoveByPrefixAsync_WithSpecialCharacterPrefix_TreatsAsLiteral(specialPrefix); + } + + [Fact] + public override Task RemoveByPrefixAsync_WithWhitespacePrefix_ThrowsArgumentException() + { + return base.RemoveByPrefixAsync_WithWhitespacePrefix_ThrowsArgumentException(); + } + + [Theory] + [MemberData(nameof(GetWhitespaceOnlyPrefixes))] + public override Task RemoveByPrefixAsync_WithWhitespacePrefix_TreatsAsLiteral(string whitespacePrefix) + { + return base.RemoveByPrefixAsync_WithWhitespacePrefix_TreatsAsLiteral(whitespacePrefix); + } + + [Theory] + [MemberData(nameof(GetWildcardPatterns))] + public override Task RemoveByPrefixAsync_WithWildcardPattern_TreatsAsLiteral(string pattern) { - return base.GetAllExpiration_WithLargeNumberOfKeys_ReturnsAllExpirations(count); + return base.RemoveByPrefixAsync_WithWildcardPattern_TreatsAsLiteral(pattern); } [Fact] - public override Task GetAllExpiration_WithExpiredKeys_ReturnsNullForExpiredKeys() + public override Task RemoveIfEqualAsync_WithEmptyKey_ThrowsArgumentNullException() { - return base.GetAllExpiration_WithExpiredKeys_ReturnsNullForExpiredKeys(); + return base.RemoveIfEqualAsync_WithEmptyKey_ThrowsArgumentNullException(); } [Fact] - public override Task SetExpirationAsync_WithValidDateTime_SetsExpirationCorrectly() + public override Task RemoveIfEqualAsync_WithMatchingValue_ReturnsTrueAndRemoves() { - return base.SetExpirationAsync_WithValidDateTime_SetsExpirationCorrectly(); + return base.RemoveIfEqualAsync_WithMatchingValue_ReturnsTrueAndRemoves(); } [Fact] - public override Task SetExpirationAsync_WithMinMaxValues_HandlesEdgeCases() + public override Task RemoveIfEqualAsync_WithMismatchedValue_ReturnsFalseAndDoesNotRemove() { - return base.SetExpirationAsync_WithMinMaxValues_HandlesEdgeCases(); + return base.RemoveIfEqualAsync_WithMismatchedValue_ReturnsFalseAndDoesNotRemove(); } [Fact] - public override Task SetAllExpiration_WithMultipleKeys_SetsExpirationForAll() + public override Task RemoveIfEqualAsync_WithNullKey_ThrowsArgumentNullException() { - return base.SetAllExpiration_WithMultipleKeys_SetsExpirationForAll(); + return base.RemoveIfEqualAsync_WithNullKey_ThrowsArgumentNullException(); } [Fact] - public override Task SetAllExpiration_WithNullValues_RemovesExpiration() + public override Task RemoveIfEqualAsync_WithWhitespaceKey_ThrowsArgumentException() { - return base.SetAllExpiration_WithNullValues_RemovesExpiration(); + return base.RemoveIfEqualAsync_WithWhitespaceKey_ThrowsArgumentException(); + } + + [Fact] + public override Task ReplaceAsync_WithDifferentCasedKeys_TreatsAsDifferentKeys() + { + return base.ReplaceAsync_WithDifferentCasedKeys_TreatsAsDifferentKeys(); + } + + [Fact] + public override Task ReplaceAsync_WithEmptyKey_ThrowsArgumentNullException() + { + return base.ReplaceAsync_WithEmptyKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task ReplaceAsync_WithExistingKey_ReturnsTrueAndReplacesValue() + { + return base.ReplaceAsync_WithExistingKey_ReturnsTrueAndReplacesValue(); + } + + [Fact] + public override Task ReplaceAsync_WithExpiration_SetsExpirationCorrectly() + { + return base.ReplaceAsync_WithExpiration_SetsExpirationCorrectly(); + } + + [Fact] + public override Task ReplaceAsync_WithNonExistentKey_ReturnsFalseAndDoesNotCreateKey() + { + return base.ReplaceAsync_WithNonExistentKey_ReturnsFalseAndDoesNotCreateKey(); + } + + [Fact] + public override Task ReplaceAsync_WithNullKey_ThrowsArgumentNullException() + { + return base.ReplaceAsync_WithNullKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task ReplaceAsync_WithWhitespaceKey_ThrowsArgumentException() + { + return base.ReplaceAsync_WithWhitespaceKey_ThrowsArgumentException(); + } + + [Fact] + public override Task ReplaceIfEqualAsync_WithDifferentCasedKeys_ReplacesOnlyExactMatch() + { + return base.ReplaceIfEqualAsync_WithDifferentCasedKeys_ReplacesOnlyExactMatch(); + } + + [Fact] + public override Task ReplaceIfEqualAsync_WithEmptyKey_ThrowsArgumentNullException() + { + return base.ReplaceIfEqualAsync_WithEmptyKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task ReplaceIfEqualAsync_WithExpiration_SetsExpirationCorrectly() + { + return base.ReplaceIfEqualAsync_WithExpiration_SetsExpirationCorrectly(); + } + + [Fact] + public override Task ReplaceIfEqualAsync_WithMatchingOldValue_ReturnsTrueAndReplacesValue() + { + return base.ReplaceIfEqualAsync_WithMatchingOldValue_ReturnsTrueAndReplacesValue(); + } + + [Fact] + public override Task ReplaceIfEqualAsync_WithMismatchedOldValue_ReturnsFalseAndDoesNotReplace() + { + return base.ReplaceIfEqualAsync_WithMismatchedOldValue_ReturnsFalseAndDoesNotReplace(); + } + + [Fact] + public override Task ReplaceIfEqualAsync_WithNullKey_ThrowsArgumentNullException() + { + return base.ReplaceIfEqualAsync_WithNullKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task ReplaceIfEqualAsync_WithWhitespaceKey_ThrowsArgumentException() + { + return base.ReplaceIfEqualAsync_WithWhitespaceKey_ThrowsArgumentException(); + } + + [Fact] + public override Task Serialization_WithComplexObjectsAndValidation_MeasuresThroughput() + { + return base.Serialization_WithComplexObjectsAndValidation_MeasuresThroughput(); + } + + [Fact] + public override Task Serialization_WithSimpleObjectsAndValidation_MeasuresThroughput() + { + return base.Serialization_WithSimpleObjectsAndValidation_MeasuresThroughput(); + } + + [Fact] + public override Task SetAllAsync_WithDateTimeMinValue_DoesNotAddKeys() + { + return base.SetAllAsync_WithDateTimeMinValue_DoesNotAddKeys(); + } + + [Fact] + public override Task SetAllAsync_WithDifferentCasedKeys_CreatesDistinctEntries() + { + return base.SetAllAsync_WithDifferentCasedKeys_CreatesDistinctEntries(); + } + + [Fact] + public override Task SetAllAsync_WithEmptyItems_ReturnsTrue() + { + return base.SetAllAsync_WithEmptyItems_ReturnsTrue(); + } + + [Fact] + public override Task SetAllAsync_WithExpiration_KeysExpireCorrectly() + { + return base.SetAllAsync_WithExpiration_KeysExpireCorrectly(); + } + + [Fact] + public override Task SetAllAsync_WithItemsContainingEmptyKey_ThrowsArgumentException() + { + return base.SetAllAsync_WithItemsContainingEmptyKey_ThrowsArgumentException(); + } + + [Fact] + public override Task SetAllAsync_WithItemsContainingNullKey_ThrowsArgumentException() + { + return base.SetAllAsync_WithItemsContainingNullKey_ThrowsArgumentException(); + } + + [Fact] + public override Task SetAllAsync_WithItemsContainingWhitespaceKey_ThrowsArgumentException() + { + return base.SetAllAsync_WithItemsContainingWhitespaceKey_ThrowsArgumentException(); + } + + [Fact] + public override Task SetAllAsync_WithNullItems_ThrowsArgumentNullException() + { + return base.SetAllAsync_WithNullItems_ThrowsArgumentNullException(); } [Theory] @@ -230,6 +1124,12 @@ public override Task SetAllExpiration_WithLargeNumberOfKeys_SetsAllExpirations(i return base.SetAllExpiration_WithLargeNumberOfKeys_SetsAllExpirations(count); } + [Fact] + public override Task SetAllExpiration_WithMultipleKeys_SetsExpirationForAll() + { + return base.SetAllExpiration_WithMultipleKeys_SetsExpirationForAll(); + } + [Fact] public override Task SetAllExpiration_WithNonExistentKeys_HandlesGracefully() { @@ -237,172 +1137,213 @@ public override Task SetAllExpiration_WithNonExistentKeys_HandlesGracefully() } [Fact] - public override Task CanIncrementAsync() + public override Task SetAllExpiration_WithNullValues_RemovesExpiration() + { + return base.SetAllExpiration_WithNullValues_RemovesExpiration(); + } + + [Fact] + public override Task SetAsync_WithComplexObject_StoresCorrectly() { - return base.CanIncrementAsync(); + return base.SetAsync_WithComplexObject_StoresCorrectly(); } [Fact] - public override Task CanIncrementAndExpireAsync() + public override Task SetAsync_WithDifferentCasedKeys_CreatesDistinctEntries() { - return base.CanIncrementAndExpireAsync(); + return base.SetAsync_WithDifferentCasedKeys_CreatesDistinctEntries(); } [Fact] - public override Task SetAllShouldExpireAsync() + public override Task SetAsync_WithDifferentCasedScopes_MaintainsDistinctNamespaces() { - return base.SetAllShouldExpireAsync(); + return base.SetAsync_WithDifferentCasedScopes_MaintainsDistinctNamespaces(); } [Fact] - public override Task CanReplaceIfEqual() + public override Task SetAsync_WithDifferentScopes_IsolatesKeys() { - return base.CanReplaceIfEqual(); + return base.SetAsync_WithDifferentScopes_IsolatesKeys(); } [Fact] - public override Task CanRemoveIfEqual() + public override Task SetAsync_WithEmptyKey_ThrowsArgumentNullException() { - return base.CanRemoveIfEqual(); + return base.SetAsync_WithEmptyKey_ThrowsArgumentNullException(); } [Fact] - public override Task CanGetAndSetDateTimeAsync() + public override Task SetAsync_WithExpirationEdgeCases_HandlesCorrectly() { - return base.CanGetAndSetDateTimeAsync(); + return base.SetAsync_WithExpirationEdgeCases_HandlesCorrectly(); } [Fact] - public override Task CanRoundTripLargeNumbersAsync() + public override Task SetAsync_WithLargeNumber_StoresCorrectly() { - return base.CanRoundTripLargeNumbersAsync(); + return base.SetAsync_WithLargeNumber_StoresCorrectly(); } [Fact] - public override Task CanRoundTripLargeNumbersWithExpirationAsync() + public override Task SetAsync_WithLargeNumbersAndExpiration_PreservesValues() { - return base.CanRoundTripLargeNumbersWithExpirationAsync(); + return base.SetAsync_WithLargeNumbersAndExpiration_PreservesValues(); } [Fact] - public override Task CanManageListsAsync() + public override Task SetAsync_WithMultipleInstances_UsesLocalCache() { - return base.CanManageListsAsync(); + return base.SetAsync_WithMultipleInstances_UsesLocalCache(); } [Fact] - public override Task CanManageListsWithNullItemsAsync() + public override Task SetAsync_WithNestedScopes_PreservesHierarchy() { - return base.CanManageListsWithNullItemsAsync(); + return base.SetAsync_WithNestedScopes_PreservesHierarchy(); } [Fact] - public override Task CanManageStringListsAsync() + public override Task SetAsync_WithNullKey_ThrowsArgumentNullException() { - return base.CanManageStringListsAsync(); + return base.SetAsync_WithNullKey_ThrowsArgumentNullException(); } [Fact] - public override Task CanManageListPagingAsync() + public override Task SetAsync_WithNullReferenceType_StoresAsNullValue() { - return base.CanManageListPagingAsync(); + return base.SetAsync_WithNullReferenceType_StoresAsNullValue(); } [Fact] - public override Task CanManageGetListExpirationAsync() + public override Task SetAsync_WithNullValueType_StoresAsNullValue() { - return base.CanManageGetListExpirationAsync(); + return base.SetAsync_WithNullValueType_StoresAsNullValue(); } [Fact] - public override Task CanManageListAddExpirationAsync() + public override Task SetAsync_WithShortExpiration_ExpiresCorrectly() { - return base.CanManageListAddExpirationAsync(); + return base.SetAsync_WithShortExpiration_ExpiresCorrectly(); } [Fact] - public override Task CanManageListRemoveExpirationAsync() + public override Task SetAsync_WithWhitespaceKey_ThrowsArgumentException() { - return base.CanManageListRemoveExpirationAsync(); + return base.SetAsync_WithWhitespaceKey_ThrowsArgumentException(); } - [Fact(Skip = "Performance Test")] - public override Task MeasureThroughputAsync() + [Fact] + public override Task SetExpirationAsync_ChangingFromNoExpirationToFutureTime_UpdatesCorrectly() { - return base.MeasureThroughputAsync(); + return base.SetExpirationAsync_ChangingFromNoExpirationToFutureTime_UpdatesCorrectly(); } - [Fact(Skip = "Performance Test")] - public override Task MeasureSerializerSimpleThroughputAsync() + [Fact] + public override Task SetExpirationAsync_ChangingToDateTimeMinValue_RemovesKey() { - return base.MeasureSerializerSimpleThroughputAsync(); + return base.SetExpirationAsync_ChangingToDateTimeMinValue_RemovesKey(); } - [Fact(Skip = "Performance Test")] - public override Task MeasureSerializerComplexThroughputAsync() + [Fact] + public override Task SetExpirationAsync_WithCurrentTime_ExpiresImmediately() { - return base.MeasureSerializerComplexThroughputAsync(); + return base.SetExpirationAsync_WithCurrentTime_ExpiresImmediately(); } [Fact] - public override Task CanInvalidateLocalCacheViaRemoveAllAsync() + public override Task SetExpirationAsync_WithDateTimeMaxValue_NeverExpires() { - return base.CanInvalidateLocalCacheViaRemoveAllAsync(); + return base.SetExpirationAsync_WithDateTimeMaxValue_NeverExpires(); } [Fact] - protected override Task CanInvalidateLocalCacheViaRemoveByPrefixAsync() + public override Task SetExpirationAsync_WithDateTimeMinValue_ExpiresImmediately() { - return base.CanInvalidateLocalCacheViaRemoveByPrefixAsync(); + return base.SetExpirationAsync_WithDateTimeMinValue_ExpiresImmediately(); } [Fact] - protected override Task WillUseLocalCache() + public override Task SetExpirationAsync_WithDifferentCasedKeys_SetsOnlyExactMatch() { - return base.WillUseLocalCache(); + return base.SetExpirationAsync_WithDifferentCasedKeys_SetsOnlyExactMatch(); } - [Fact(Skip = "TODO: Look into this as the local cache client maintenance never schedules for expiration")] - protected override Task WillExpireRemoteItems() + [Fact] + public override Task SetExpirationAsync_WithEmptyKey_ThrowsArgumentNullException() + { + return base.SetExpirationAsync_WithEmptyKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task SetExpirationAsync_WithNullKey_ThrowsArgumentNullException() + { + return base.SetExpirationAsync_WithNullKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task SetExpirationAsync_WithWhitespaceKey_ThrowsArgumentException() + { + return base.SetExpirationAsync_WithWhitespaceKey_ThrowsArgumentException(); + } + + [Fact] + public override Task SetIfHigherAsync_WithDateTime_DoesNotUpdateWhenLower() + { + return base.SetIfHigherAsync_WithDateTime_DoesNotUpdateWhenLower(); + } + + [Fact] + public override Task SetIfHigherAsync_WithDateTime_InitializesWhenKeyNotExists() + { + return base.SetIfHigherAsync_WithDateTime_InitializesWhenKeyNotExists(); + } + + [Fact] + public override Task SetIfHigherAsync_WithDateTime_UpdatesWhenHigher() { - return base.WillExpireRemoteItems(); + return base.SetIfHigherAsync_WithDateTime_UpdatesWhenHigher(); } [Fact] - protected override Task WillWorkWithSets() + public override Task SetIfHigherAsync_WithLargeNumbers_UpdatesWhenHigher() { - return base.WillWorkWithSets(); + return base.SetIfHigherAsync_WithLargeNumbers_UpdatesWhenHigher(); } + [Fact] + public override Task SetIfLowerAsync_WithDateTime_DoesNotUpdateWhenHigher() + { + return base.SetIfLowerAsync_WithDateTime_DoesNotUpdateWhenHigher(); + } [Fact] - protected override Task ExistsAsyncShouldCheckLocalCacheFirst() + public override Task SetIfLowerAsync_WithDateTime_InitializesWhenKeyNotExists() { - return base.ExistsAsyncShouldCheckLocalCacheFirst(); + return base.SetIfLowerAsync_WithDateTime_InitializesWhenKeyNotExists(); } [Fact] - protected override Task GetExpirationAsyncShouldCheckLocalCacheFirst() + public override Task SetIfLowerAsync_WithDateTime_UpdatesWhenLower() { - return base.GetExpirationAsyncShouldCheckLocalCacheFirst(); + return base.SetIfLowerAsync_WithDateTime_UpdatesWhenLower(); } [Fact] - protected override Task GetAllAsyncShouldUseHybridCache() + public override Task SetIfLowerAsync_WithLargeNumbers_UpdatesWhenLower() { - return base.GetAllAsyncShouldUseHybridCache(); + return base.SetIfLowerAsync_WithLargeNumbers_UpdatesWhenLower(); } [Fact] - protected override Task GetAllAsyncShouldHandleEmptyKeys() + public override Task SetUnixTimeMillisecondsAsync_WithLocalDateTime_StoresCorrectly() { - return base.GetAllAsyncShouldHandleEmptyKeys(); + return base.SetUnixTimeMillisecondsAsync_WithLocalDateTime_StoresCorrectly(); } [Fact] - protected override Task GetAllAsyncShouldSkipNullKeys() + public override Task SetUnixTimeSecondsAsync_WithUtcDateTime_StoresCorrectly() { - return base.GetAllAsyncShouldSkipNullKeys(); + return base.SetUnixTimeSecondsAsync_WithUtcDateTime_StoresCorrectly(); } public Task InitializeAsync() diff --git a/tests/Foundatio.Redis.Tests/Caching/ScopedRedisCacheClientTests.cs b/tests/Foundatio.Redis.Tests/Caching/ScopedRedisCacheClientTests.cs index 4e51566..44da326 100644 --- a/tests/Foundatio.Redis.Tests/Caching/ScopedRedisCacheClientTests.cs +++ b/tests/Foundatio.Redis.Tests/Caching/ScopedRedisCacheClientTests.cs @@ -20,134 +20,810 @@ protected override ICacheClient GetCacheClient(bool shouldThrowOnSerializationEr } [Fact] - public override Task CanGetAllAsync() + public override Task AddAsync_WithConcurrentRequests_OnlyOneSucceeds() { - return base.CanGetAllAsync(); + return base.AddAsync_WithConcurrentRequests_OnlyOneSucceeds(); } [Fact] - public override Task CanGetAllWithOverlapAsync() + public override Task AddAsync_WithEmptyKey_ThrowsArgumentNullException() { - return base.CanGetAllWithOverlapAsync(); + return base.AddAsync_WithEmptyKey_ThrowsArgumentNullException(); } [Fact] - public override Task CanSetAsync() + public override Task AddAsync_WithExistingKey_ReturnsFalseAndPreservesValue() { - return base.CanSetAsync(); + return base.AddAsync_WithExistingKey_ReturnsFalseAndPreservesValue(); } [Fact] - public override Task CanSetAndGetValueAsync() + public override Task AddAsync_WithNestedKeyUsingSeparator_StoresCorrectly() { - return base.CanSetAndGetValueAsync(); + return base.AddAsync_WithNestedKeyUsingSeparator_StoresCorrectly(); } [Fact] - public override Task CanAddAsync() + public override Task AddAsync_WithNewKey_ReturnsTrue() { - return base.CanAddAsync(); + return base.AddAsync_WithNewKey_ReturnsTrue(); } [Fact] - public override Task CanAddConcurrentlyAsync() + public override Task AddAsync_WithNullKey_ThrowsArgumentNullException() { - return base.CanAddConcurrentlyAsync(); + return base.AddAsync_WithNullKey_ThrowsArgumentNullException(); } [Fact] - public override Task CanGetAsync() + public override Task AddAsync_WithWhitespaceKey_ThrowsArgumentException() { - return base.CanGetAsync(); + return base.AddAsync_WithWhitespaceKey_ThrowsArgumentException(); + } + + [Fact(Skip = "Performance Test")] + public override Task CacheOperations_WithMultipleTypes_MeasuresThroughput() + { + return base.CacheOperations_WithMultipleTypes_MeasuresThroughput(); + } + + [Fact(Skip = "Performance Test")] + public override Task CacheOperations_WithRepeatedSetAndGet_MeasuresThroughput() + { + return base.CacheOperations_WithRepeatedSetAndGet_MeasuresThroughput(); + } + + [Fact] + public override Task ExistsAsync_AfterKeyExpires_ReturnsFalse() + { + return base.ExistsAsync_AfterKeyExpires_ReturnsFalse(); + } + + [Fact] + public override Task ExistsAsync_WithDifferentCasedKeys_ChecksExactMatch() + { + return base.ExistsAsync_WithDifferentCasedKeys_ChecksExactMatch(); + } + + [Fact] + public override Task ExistsAsync_WithEmptyKey_ThrowsArgumentNullException() + { + return base.ExistsAsync_WithEmptyKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task ExistsAsync_WithExistingKey_ReturnsTrue() + { + return base.ExistsAsync_WithExistingKey_ReturnsTrue(); + } + + [Fact] + public override Task ExistsAsync_WithExpiredKey_ReturnsFalse() + { + return base.ExistsAsync_WithExpiredKey_ReturnsFalse(); + } + + [Fact] + public override Task ExistsAsync_WithNonExistentKey_ReturnsFalse() + { + return base.ExistsAsync_WithNonExistentKey_ReturnsFalse(); + } + + [Fact] + public override Task ExistsAsync_WithNullKey_ThrowsArgumentNullException() + { + return base.ExistsAsync_WithNullKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task ExistsAsync_WithNullStoredValue_ReturnsTrue() + { + return base.ExistsAsync_WithNullStoredValue_ReturnsTrue(); + } + + [Fact] + public override Task ExistsAsync_WithScopedCache_ChecksOnlyWithinScope() + { + return base.ExistsAsync_WithScopedCache_ChecksOnlyWithinScope(); + } + + [Fact] + public override Task ExistsAsync_WithWhitespaceKey_ThrowsArgumentException() + { + return base.ExistsAsync_WithWhitespaceKey_ThrowsArgumentException(); + } + + [Fact] + public override Task GetAllAsync_WithEmptyKeys_ReturnsEmpty() + { + return base.GetAllAsync_WithEmptyKeys_ReturnsEmpty(); + } + + [Fact] + public override Task GetAllAsync_WithExistingKeys_ReturnsAllValues() + { + return base.GetAllAsync_WithExistingKeys_ReturnsAllValues(); + } + + [Fact] + public override Task GetAllAsync_WithKeysContainingEmpty_ThrowsArgumentException() + { + return base.GetAllAsync_WithKeysContainingEmpty_ThrowsArgumentException(); + } + + [Fact] + public override Task GetAllAsync_WithKeysContainingNull_ThrowsArgumentException() + { + return base.GetAllAsync_WithKeysContainingNull_ThrowsArgumentException(); + } + + [Fact] + public override Task GetAllAsync_WithKeysContainingWhitespace_ThrowsArgumentException() + { + return base.GetAllAsync_WithKeysContainingWhitespace_ThrowsArgumentException(); + } + + [Fact] + public override Task GetAllAsync_WithMixedCaseKeys_RetrievesExactMatches() + { + return base.GetAllAsync_WithMixedCaseKeys_RetrievesExactMatches(); + } + + [Fact] + public override Task GetAllAsync_WithMixedObjectTypes_ReturnsCorrectValues() + { + return base.GetAllAsync_WithMixedObjectTypes_ReturnsCorrectValues(); + } + + [Fact] + public override Task GetAllAsync_WithNonExistentKeys_ReturnsEmptyResults() + { + return base.GetAllAsync_WithNonExistentKeys_ReturnsEmptyResults(); + } + + [Fact] + public override Task GetAllAsync_WithNullKeys_ThrowsArgumentNullException() + { + return base.GetAllAsync_WithNullKeys_ThrowsArgumentNullException(); + } + + [Fact] + public override Task GetAllAsync_WithNullValues_HandlesNullsCorrectly() + { + return base.GetAllAsync_WithNullValues_HandlesNullsCorrectly(); + } + + [Fact] + public override Task GetAllAsync_WithOverlappingKeys_UsesLatestValues() + { + return base.GetAllAsync_WithOverlappingKeys_UsesLatestValues(); + } + + [Fact] + public override Task GetAllAsync_WithScopedCache_ReturnsUnscopedKeys() + { + return base.GetAllAsync_WithScopedCache_ReturnsUnscopedKeys(); + } + + [Fact] + public override Task GetAllExpirationAsync_WithExpiredKeys_ExcludesExpiredKeys() + { + return base.GetAllExpirationAsync_WithExpiredKeys_ExcludesExpiredKeys(); + } + + [Theory] + [InlineData(1000)] + [InlineData(10000)] + public override Task GetAllExpirationAsync_WithLargeNumberOfKeys_ReturnsAllExpirations(int count) + { + return base.GetAllExpirationAsync_WithLargeNumberOfKeys_ReturnsAllExpirations(count); + } + + [Fact] + public override Task GetAllExpirationAsync_WithMixedKeys_ReturnsOnlyKeysWithExpiration() + { + return base.GetAllExpirationAsync_WithMixedKeys_ReturnsOnlyKeysWithExpiration(); + } + + [Fact] + public override Task GetAsync_WithComplexObject_PreservesAllProperties() + { + return base.GetAsync_WithComplexObject_PreservesAllProperties(); + } + + [Fact] + public override Task GetAsync_WithComplexObject_ReturnsNewInstance() + { + return base.GetAsync_WithComplexObject_ReturnsNewInstance(); + } + + [Fact] + public override Task GetAsync_WithDifferentCasedKeys_TreatsAsDifferentKeys() + { + return base.GetAsync_WithDifferentCasedKeys_TreatsAsDifferentKeys(); + } + + [Fact] + public override Task GetAsync_WithEmptyKey_ThrowsArgumentNullException() + { + return base.GetAsync_WithEmptyKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task GetAsync_WithLargeNumber_ReturnsCorrectValue() + { + return base.GetAsync_WithLargeNumber_ReturnsCorrectValue(); + } + + [Fact] + public override Task GetAsync_WithMaxLongAsInt_ThrowsException() + { + return base.GetAsync_WithMaxLongAsInt_ThrowsException(); + } + + [Fact] + public override Task GetAsync_WithNonExistentKey_ReturnsNoValue() + { + return base.GetAsync_WithNonExistentKey_ReturnsNoValue(); + } + + [Fact] + public override Task GetAsync_WithNullKey_ThrowsArgumentNullException() + { + return base.GetAsync_WithNullKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task GetAsync_WithNumericTypeConversion_ConvertsIntToLong() + { + return base.GetAsync_WithNumericTypeConversion_ConvertsIntToLong(); + } + + [Fact] + public override Task GetAsync_WithNumericTypeConversion_ConvertsLongToInt() + { + return base.GetAsync_WithNumericTypeConversion_ConvertsLongToInt(); + } + + [Fact] + public override Task GetAsync_WithTryGetSemanticsAndComplexTypeAsLong_ReturnsNoValue() + { + return base.GetAsync_WithTryGetSemanticsAndComplexTypeAsLong_ReturnsNoValue(); + } + + [Fact] + public override Task GetAsync_WithTryGetSemanticsAndIntAsLong_ConvertsSuccessfully() + { + return base.GetAsync_WithTryGetSemanticsAndIntAsLong_ConvertsSuccessfully(); + } + + [Fact] + public override Task GetAsync_WithTryGetSemanticsAndMaxLongAsInt_ReturnsNoValue() + { + return base.GetAsync_WithTryGetSemanticsAndMaxLongAsInt_ReturnsNoValue(); + } + + [Fact] + public override Task GetAsync_WithWhitespaceKey_ThrowsArgumentException() + { + return base.GetAsync_WithWhitespaceKey_ThrowsArgumentException(); + } + + [Fact] + public override Task GetExpirationAsync_AfterExpiry_ReturnsNull() + { + return base.GetExpirationAsync_AfterExpiry_ReturnsNull(); + } + + [Fact] + public override Task GetExpirationAsync_WithDifferentCasedKeys_GetsExactMatch() + { + return base.GetExpirationAsync_WithDifferentCasedKeys_GetsExactMatch(); + } + + [Fact] + public override Task GetExpirationAsync_WithEmptyKey_ThrowsArgumentNullException() + { + return base.GetExpirationAsync_WithEmptyKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task GetExpirationAsync_WithExpiration_ReturnsCorrectTimeSpan() + { + return base.GetExpirationAsync_WithExpiration_ReturnsCorrectTimeSpan(); + } + + [Fact] + public override Task GetExpirationAsync_WithExpiredKey_ReturnsNull() + { + return base.GetExpirationAsync_WithExpiredKey_ReturnsNull(); + } + + [Fact] + public override Task GetExpirationAsync_WithNoExpiration_ReturnsNull() + { + return base.GetExpirationAsync_WithNoExpiration_ReturnsNull(); + } + + [Fact] + public override Task GetExpirationAsync_WithNonExistentKey_ReturnsNull() + { + return base.GetExpirationAsync_WithNonExistentKey_ReturnsNull(); + } + + [Fact] + public override Task GetExpirationAsync_WithNullKey_ThrowsArgumentNullException() + { + return base.GetExpirationAsync_WithNullKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task GetExpirationAsync_WithWhitespaceKey_ThrowsArgumentException() + { + return base.GetExpirationAsync_WithWhitespaceKey_ThrowsArgumentException(); + } + + [Fact] + public override Task GetListAsync_WithEmptyKey_ThrowsArgumentNullException() + { + return base.GetListAsync_WithEmptyKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task GetListAsync_WithExpiredItems_RemovesExpiredAndReturnsActive() + { + return base.GetListAsync_WithExpiredItems_RemovesExpiredAndReturnsActive(); + } + + [Fact] + public override Task GetListAsync_WithInvalidPageNumber_ThrowsArgumentOutOfRangeException() + { + return base.GetListAsync_WithInvalidPageNumber_ThrowsArgumentOutOfRangeException(); + } + + [Fact] + public override Task GetListAsync_WithMultiplePages_ReturnsAllItems() + { + return base.GetListAsync_WithMultiplePages_ReturnsAllItems(); + } + + [Fact] + public override Task GetListAsync_WithNewItemsAdded_ReturnsNewItemsLast() + { + return base.GetListAsync_WithNewItemsAdded_ReturnsNewItemsLast(); + } + + [Fact] + public override Task GetListAsync_WithNullKey_ThrowsArgumentNullException() + { + return base.GetListAsync_WithNullKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task GetListAsync_WithPageBeyondEnd_ReturnsEmptyCollection() + { + return base.GetListAsync_WithPageBeyondEnd_ReturnsEmptyCollection(); + } + + [Fact] + public override Task GetListAsync_WithPaging_ReturnsCorrectPageSize() + { + return base.GetListAsync_WithPaging_ReturnsCorrectPageSize(); + } + + [Fact] + public override Task GetListAsync_WithWhitespaceKey_ThrowsArgumentException() + { + return base.GetListAsync_WithWhitespaceKey_ThrowsArgumentException(); + } + + [Fact] + public override Task GetUnixTimeMillisecondsAsync_WithLocalDateTime_ReturnsCorrectly() + { + return base.GetUnixTimeMillisecondsAsync_WithLocalDateTime_ReturnsCorrectly(); + } + + [Fact] + public override Task GetUnixTimeMillisecondsAsync_WithUtcDateTime_ReturnsCorrectly() + { + return base.GetUnixTimeMillisecondsAsync_WithUtcDateTime_ReturnsCorrectly(); + } + + [Fact] + public override Task GetUnixTimeSecondsAsync_WithUtcDateTime_ReturnsCorrectly() + { + return base.GetUnixTimeSecondsAsync_WithUtcDateTime_ReturnsCorrectly(); + } + + [Fact] + public override Task IncrementAsync_WithDifferentCasedKeys_IncrementsDistinctCounters() + { + return base.IncrementAsync_WithDifferentCasedKeys_IncrementsDistinctCounters(); + } + + [Fact] + public override Task IncrementAsync_WithEmptyKey_ThrowsArgumentNullException() + { + return base.IncrementAsync_WithEmptyKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task IncrementAsync_WithExistingKey_IncrementsValue() + { + return base.IncrementAsync_WithExistingKey_IncrementsValue(); + } + + [Fact] + public override Task IncrementAsync_WithExpiration_ExpiresCorrectly() + { + return base.IncrementAsync_WithExpiration_ExpiresCorrectly(); + } + + [Fact] + public override Task IncrementAsync_WithNonExistentKey_InitializesToOne() + { + return base.IncrementAsync_WithNonExistentKey_InitializesToOne(); + } + + [Fact] + public override Task IncrementAsync_WithNullKey_ThrowsArgumentNullException() + { + return base.IncrementAsync_WithNullKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task IncrementAsync_WithScopedCache_WorksWithinScope() + { + return base.IncrementAsync_WithScopedCache_WorksWithinScope(); + } + + [Fact] + public override Task IncrementAsync_WithSpecifiedAmount_IncrementsCorrectly() + { + return base.IncrementAsync_WithSpecifiedAmount_IncrementsCorrectly(); + } + + [Fact] + public override Task IncrementAsync_WithWhitespaceKey_ThrowsArgumentException() + { + return base.IncrementAsync_WithWhitespaceKey_ThrowsArgumentException(); + } + + [Fact] + public override Task ListAddAsync_WithDifferentCasedKeys_MaintainsDistinctLists() + { + return base.ListAddAsync_WithDifferentCasedKeys_MaintainsDistinctLists(); + } + + [Fact] + public override Task ListAddAsync_WithDuplicates_RemovesDuplicatesAndAddsItems() + { + return base.ListAddAsync_WithDuplicates_RemovesDuplicatesAndAddsItems(); + } + + [Fact] + public override Task ListAddAsync_WithDuplicates_StoresUniqueValuesOnly() + { + return base.ListAddAsync_WithDuplicates_StoresUniqueValuesOnly(); + } + + [Fact] + public override Task ListAddAsync_WithEmptyCollection_NoOp() + { + return base.ListAddAsync_WithEmptyCollection_NoOp(); + } + + [Fact] + public override Task ListAddAsync_WithEmptyKey_ThrowsArgumentNullException() + { + return base.ListAddAsync_WithEmptyKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task ListAddAsync_WithExistingNonListKey_ThrowsException() + { + return base.ListAddAsync_WithExistingNonListKey_ThrowsException(); + } + + [Fact] + public override Task ListAddAsync_WithFutureExpiration_AddsAndExpiresCorrectly() + { + return base.ListAddAsync_WithFutureExpiration_AddsAndExpiresCorrectly(); + } + + [Fact] + public override Task ListAddAsync_WithMultipleExpirations_ExpiresIndividualItems() + { + return base.ListAddAsync_WithMultipleExpirations_ExpiresIndividualItems(); + } + + [Fact] + public override Task ListAddAsync_WithNullCollection_ThrowsArgumentNullException() + { + return base.ListAddAsync_WithNullCollection_ThrowsArgumentNullException(); + } + + [Fact] + public override Task ListAddAsync_WithNullItem_IgnoresNull() + { + return base.ListAddAsync_WithNullItem_IgnoresNull(); + } + + [Fact] + public override Task ListAddAsync_WithNullKey_ThrowsArgumentNullException() + { + return base.ListAddAsync_WithNullKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task ListAddAsync_WithNullValues_ThrowsArgumentNullException() + { + return base.ListAddAsync_WithNullValues_ThrowsArgumentNullException(); + } + + [Fact] + public override Task ListAddAsync_WithPastExpiration_RemovesItem() + { + return base.ListAddAsync_WithPastExpiration_RemovesItem(); + } + + [Fact] + public override Task ListAddAsync_WithSingleString_StoresAsStringNotCharArray() + { + return base.ListAddAsync_WithSingleString_StoresAsStringNotCharArray(); + } + + [Fact] + public override Task ListAddAsync_WithWhitespaceKey_ThrowsArgumentException() + { + return base.ListAddAsync_WithWhitespaceKey_ThrowsArgumentException(); + } + + [Fact] + public override Task ListRemoveAsync_WithEmptyKey_ThrowsArgumentNullException() + { + return base.ListRemoveAsync_WithEmptyKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task ListRemoveAsync_WithMultipleValues_RemovesAll() + { + return base.ListRemoveAsync_WithMultipleValues_RemovesAll(); + } + + [Fact] + public override Task ListRemoveAsync_WithNullCollection_ThrowsArgumentNullException() + { + return base.ListRemoveAsync_WithNullCollection_ThrowsArgumentNullException(); + } + + [Fact] + public override Task ListRemoveAsync_WithNullItem_IgnoresNull() + { + return base.ListRemoveAsync_WithNullItem_IgnoresNull(); + } + + [Fact] + public override Task ListRemoveAsync_WithNullKey_ThrowsArgumentNullException() + { + return base.ListRemoveAsync_WithNullKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task ListRemoveAsync_WithNullValues_ThrowsArgumentNullException() + { + return base.ListRemoveAsync_WithNullValues_ThrowsArgumentNullException(); + } + + [Fact] + public override Task ListRemoveAsync_WithSingleValue_RemovesCorrectly() + { + return base.ListRemoveAsync_WithSingleValue_RemovesCorrectly(); + } + + [Fact] + public override Task ListRemoveAsync_WithValidValues_RemovesKeyWhenEmpty() + { + return base.ListRemoveAsync_WithValidValues_RemovesKeyWhenEmpty(); + } + + [Fact] + public override Task ListRemoveAsync_WithWhitespaceKey_ThrowsArgumentException() + { + return base.ListRemoveAsync_WithWhitespaceKey_ThrowsArgumentException(); + } + + [Fact] + public override Task RemoveAllAsync_WithEmptyKeys_Succeeds() + { + return base.RemoveAllAsync_WithEmptyKeys_Succeeds(); + } + + [Fact] + public override Task RemoveAllAsync_WithKeysContainingEmpty_ThrowsArgumentException() + { + return base.RemoveAllAsync_WithKeysContainingEmpty_ThrowsArgumentException(); + } + + [Fact] + public override Task RemoveAllAsync_WithKeysContainingNull_ThrowsArgumentException() + { + return base.RemoveAllAsync_WithKeysContainingNull_ThrowsArgumentException(); + } + + [Fact] + public override Task RemoveAllAsync_WithKeysContainingWhitespace_ThrowsArgumentException() + { + return base.RemoveAllAsync_WithKeysContainingWhitespace_ThrowsArgumentException(); + } + + [Fact] + public override Task RemoveAllAsync_WithLargeNumberOfKeys_RemovesAllKeysEfficiently() + { + return base.RemoveAllAsync_WithLargeNumberOfKeys_RemovesAllKeysEfficiently(); + } + + [Fact] + public override Task RemoveAllAsync_WithMixedCaseKeys_RemovesOnlyExactMatches() + { + return base.RemoveAllAsync_WithMixedCaseKeys_RemovesOnlyExactMatches(); } [Fact] - public override Task CanTryGetAsync() + public override Task RemoveAllAsync_WithNullKeys_ThrowsArgumentNullException() { - return base.CanTryGetAsync(); + return base.RemoveAllAsync_WithNullKeys_ThrowsArgumentNullException(); } [Fact] - public override Task CanUseScopedCachesAsync() + public override Task RemoveAllAsync_WithScopedCache_AffectsOnlyScopedKeys() { - return base.CanUseScopedCachesAsync(); + return base.RemoveAllAsync_WithScopedCache_AffectsOnlyScopedKeys(); } [Fact] - public override Task CanRemoveAllAsync() + public override Task RemoveAllAsync_WithSpecificKeyCollection_RemovesOnlySpecifiedKeys() { - return base.CanRemoveAllAsync(); + return base.RemoveAllAsync_WithSpecificKeyCollection_RemovesOnlySpecifiedKeys(); } [Fact] - public override Task CanRemoveAllKeysAsync() + public override Task RemoveAsync_AfterSetAndGet_RemovesCorrectly() { - return base.CanRemoveAllKeysAsync(); + return base.RemoveAsync_AfterSetAndGet_RemovesCorrectly(); } [Fact] - public override Task CanRemoveByPrefixAsync() + public override Task RemoveAsync_MultipleTimes_Succeeds() { - return base.CanRemoveByPrefixAsync(); + return base.RemoveAsync_MultipleTimes_Succeeds(); + } + + [Fact] + public override Task RemoveAsync_WithEmptyKey_ThrowsArgumentNullException() + { + return base.RemoveAsync_WithEmptyKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task RemoveAsync_WithExistingKey_RemovesSuccessfully() + { + return base.RemoveAsync_WithExistingKey_RemovesSuccessfully(); + } + + [Fact] + public override Task RemoveAsync_WithExpiredKey_Succeeds() + { + return base.RemoveAsync_WithExpiredKey_Succeeds(); + } + + [Fact] + public override Task RemoveAsync_WithNonExistentKey_Succeeds() + { + return base.RemoveAsync_WithNonExistentKey_Succeeds(); + } + + [Fact] + public override Task RemoveAsync_WithNullKey_ThrowsArgumentNullException() + { + return base.RemoveAsync_WithNullKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task RemoveAsync_WithNullValue_RemovesSuccessfully() + { + return base.RemoveAsync_WithNullValue_RemovesSuccessfully(); + } + + [Fact] + public override Task RemoveAsync_WithScopedCache_RemovesOnlyWithinScope() + { + return base.RemoveAsync_WithScopedCache_RemovesOnlyWithinScope(); + } + + [Fact] + public override Task RemoveAsync_WithSpecificCase_RemovesOnlyMatchingKey() + { + return base.RemoveAsync_WithSpecificCase_RemovesOnlyMatchingKey(); + } + + [Fact] + public override Task RemoveAsync_WithWhitespaceKey_ThrowsArgumentException() + { + return base.RemoveAsync_WithWhitespaceKey_ThrowsArgumentException(); + } + + [Fact] + public override Task RemoveByPrefixAsync_AsteriskPrefixWithScopedCache_TreatedAsLiteral() + { + return base.RemoveByPrefixAsync_AsteriskPrefixWithScopedCache_TreatedAsLiteral(); } [Theory] - [MemberData(nameof(GetRegexSpecialCharacters))] - public override Task CanRemoveByPrefixWithRegexCharactersAsync(string specialChar) + [InlineData("snowboard", 1, true)] // Exact key match + [InlineData("s", 1, true)] // Partial prefix match + [InlineData(null, 1, false)] // Null prefix (all keys in scope) + [InlineData("", 1, false)] // Empty prefix (all keys in scope) + public override Task RemoveByPrefixAsync_FromScopedCache_RemovesOnlyScopedKeys(string prefixToRemove, int expectedRemovedCount, bool shouldUnscopedRemain) { - return base.CanRemoveByPrefixWithRegexCharactersAsync(specialChar); + return base.RemoveByPrefixAsync_FromScopedCache_RemovesOnlyScopedKeys(prefixToRemove, expectedRemovedCount, shouldUnscopedRemain); } [Theory] - [MemberData(nameof(GetWildcardPatterns))] - public override Task CanRemoveByPrefixWithWildcardPatternsAsync(string pattern) + [InlineData(null)] + [InlineData("")] + public override Task RemoveByPrefixAsync_NullOrEmptyPrefixWithScopedCache_RemovesCorrectKeys(string prefix) { - return base.CanRemoveByPrefixWithWildcardPatternsAsync(pattern); + return base.RemoveByPrefixAsync_NullOrEmptyPrefixWithScopedCache_RemovesCorrectKeys(prefix); } [Fact] - public override Task CanRemoveByPrefixWithDoubleAsteriskAsync() + public override Task RemoveByPrefixAsync_PartialPrefixWithScopedCache_RemovesMatchingKeys() { - return base.CanRemoveByPrefixWithDoubleAsteriskAsync(); + return base.RemoveByPrefixAsync_PartialPrefixWithScopedCache_RemovesMatchingKeys(); } - [Theory] - [MemberData(nameof(GetSpecialPrefixes))] - public override Task CanRemoveByPrefixWithSpecialCharactersAsync(string specialPrefix) + [Fact] + public override Task RemoveByPrefixAsync_WithCaseSensitivePrefix_RemovesOnlyMatchingCase() { - return base.CanRemoveByPrefixWithSpecialCharactersAsync(specialPrefix); + return base.RemoveByPrefixAsync_WithCaseSensitivePrefix_RemovesOnlyMatchingCase(); } [Fact] - public override Task CanRemoveByPrefixWithNullAsync() + public override Task RemoveByPrefixAsync_WithDifferentCasedScopes_RemovesOnlyMatchingScope() { - return base.CanRemoveByPrefixWithNullAsync(); + return base.RemoveByPrefixAsync_WithDifferentCasedScopes_RemovesOnlyMatchingScope(); } [Fact] - public override Task CanRemoveByPrefixWithEmptyStringAsync() + public override Task RemoveByPrefixAsync_WithDoubleAsteriskPrefix_TreatsAsLiteral() { - return base.CanRemoveByPrefixWithEmptyStringAsync(); + return base.RemoveByPrefixAsync_WithDoubleAsteriskPrefix_TreatsAsLiteral(); } - [Theory] - [MemberData(nameof(GetWhitespaceOnlyPrefixes))] - public override Task CanRemoveByPrefixWithWhitespaceAsync(string whitespacePrefix) + [Fact] + public override Task RemoveByPrefixAsync_WithEmptyPrefix_RemovesAllKeys() { - return base.CanRemoveByPrefixWithWhitespaceAsync(whitespacePrefix); + return base.RemoveByPrefixAsync_WithEmptyPrefix_RemovesAllKeys(); + } + + [Fact] + public override Task RemoveByPrefixAsync_WithEmptyPrefix_ThrowsArgumentException() + { + return base.RemoveByPrefixAsync_WithEmptyPrefix_ThrowsArgumentException(); } [Theory] [MemberData(nameof(GetLineEndingPrefixes))] - public override Task CanRemoveByPrefixWithLineEndingsAsync(string lineEndingPrefix) + public override Task RemoveByPrefixAsync_WithLineEndingPrefix_TreatsAsLiteral(string lineEndingPrefix) { - return base.CanRemoveByPrefixWithLineEndingsAsync(lineEndingPrefix); + return base.RemoveByPrefixAsync_WithLineEndingPrefix_TreatsAsLiteral(lineEndingPrefix); } [Fact] - public override Task CanRemoveByPrefixWithScopedCachesAsync() + public override Task RemoveByPrefixAsync_WithMatchingPrefix_RemovesOnlyPrefixedKeys() { - return base.CanRemoveByPrefixWithScopedCachesAsync(); + return base.RemoveByPrefixAsync_WithMatchingPrefix_RemovesOnlyPrefixedKeys(); } [Theory] @@ -155,65 +831,241 @@ public override Task CanRemoveByPrefixWithScopedCachesAsync() [InlineData(500)] [InlineData(5000)] [InlineData(50000)] - public override Task CanRemoveByPrefixMultipleEntriesAsync(int count) + public override Task RemoveByPrefixAsync_WithMultipleMatchingKeys_RemovesOnlyPrefixedKeys(int count) { - return base.CanRemoveByPrefixMultipleEntriesAsync(count); + return base.RemoveByPrefixAsync_WithMultipleMatchingKeys_RemovesOnlyPrefixedKeys(count); } [Fact] - public override Task CanSetAndGetObjectAsync() + public override Task RemoveByPrefixAsync_WithNonMatchingPrefix_RemovesZeroKeys() { - return base.CanSetAndGetObjectAsync(); + return base.RemoveByPrefixAsync_WithNonMatchingPrefix_RemovesZeroKeys(); } [Fact] - public override Task GetExpirationAsync_WithVariousStates_ReturnsCorrectly() + public override Task RemoveByPrefixAsync_WithNullPrefix_RemovesAllKeys() { - return base.GetExpirationAsync_WithVariousStates_ReturnsCorrectly(); + return base.RemoveByPrefixAsync_WithNullPrefix_RemovesAllKeys(); } [Fact] - public override Task GetAllExpiration_WithMultipleKeys_ReturnsAllExpirations() + public override Task RemoveByPrefixAsync_WithNullPrefix_ThrowsArgumentNullException() { - return base.GetAllExpiration_WithMultipleKeys_ReturnsAllExpirations(); + return base.RemoveByPrefixAsync_WithNullPrefix_ThrowsArgumentNullException(); } [Theory] - [InlineData(1000)] - [InlineData(10000)] - public override Task GetAllExpiration_WithLargeNumberOfKeys_ReturnsAllExpirations(int count) + [MemberData(nameof(GetRegexSpecialCharacters))] + public override Task RemoveByPrefixAsync_WithRegexMetacharacter_TreatsAsLiteral(string specialChar) + { + return base.RemoveByPrefixAsync_WithRegexMetacharacter_TreatsAsLiteral(specialChar); + } + + [Fact] + public override Task RemoveByPrefixAsync_WithScopedCache_AffectsOnlyScopedKeys() + { + return base.RemoveByPrefixAsync_WithScopedCache_AffectsOnlyScopedKeys(); + } + + [Theory] + [MemberData(nameof(GetSpecialPrefixes))] + public override Task RemoveByPrefixAsync_WithSpecialCharacterPrefix_TreatsAsLiteral(string specialPrefix) { - return base.GetAllExpiration_WithLargeNumberOfKeys_ReturnsAllExpirations(count); + return base.RemoveByPrefixAsync_WithSpecialCharacterPrefix_TreatsAsLiteral(specialPrefix); } [Fact] - public override Task GetAllExpiration_WithExpiredKeys_ReturnsNullForExpiredKeys() + public override Task RemoveByPrefixAsync_WithWhitespacePrefix_ThrowsArgumentException() { - return base.GetAllExpiration_WithExpiredKeys_ReturnsNullForExpiredKeys(); + return base.RemoveByPrefixAsync_WithWhitespacePrefix_ThrowsArgumentException(); + } + + [Theory] + [MemberData(nameof(GetWhitespaceOnlyPrefixes))] + public override Task RemoveByPrefixAsync_WithWhitespacePrefix_TreatsAsLiteral(string whitespacePrefix) + { + return base.RemoveByPrefixAsync_WithWhitespacePrefix_TreatsAsLiteral(whitespacePrefix); + } + + [Theory] + [MemberData(nameof(GetWildcardPatterns))] + public override Task RemoveByPrefixAsync_WithWildcardPattern_TreatsAsLiteral(string pattern) + { + return base.RemoveByPrefixAsync_WithWildcardPattern_TreatsAsLiteral(pattern); } [Fact] - public override Task SetExpirationAsync_WithValidDateTime_SetsExpirationCorrectly() + public override Task RemoveIfEqualAsync_WithEmptyKey_ThrowsArgumentNullException() { - return base.SetExpirationAsync_WithValidDateTime_SetsExpirationCorrectly(); + return base.RemoveIfEqualAsync_WithEmptyKey_ThrowsArgumentNullException(); } [Fact] - public override Task SetExpirationAsync_WithMinMaxValues_HandlesEdgeCases() + public override Task RemoveIfEqualAsync_WithMatchingValue_ReturnsTrueAndRemoves() { - return base.SetExpirationAsync_WithMinMaxValues_HandlesEdgeCases(); + return base.RemoveIfEqualAsync_WithMatchingValue_ReturnsTrueAndRemoves(); } [Fact] - public override Task SetAllExpiration_WithMultipleKeys_SetsExpirationForAll() + public override Task RemoveIfEqualAsync_WithMismatchedValue_ReturnsFalseAndDoesNotRemove() { - return base.SetAllExpiration_WithMultipleKeys_SetsExpirationForAll(); + return base.RemoveIfEqualAsync_WithMismatchedValue_ReturnsFalseAndDoesNotRemove(); } [Fact] - public override Task SetAllExpiration_WithNullValues_RemovesExpiration() + public override Task RemoveIfEqualAsync_WithNullKey_ThrowsArgumentNullException() { - return base.SetAllExpiration_WithNullValues_RemovesExpiration(); + return base.RemoveIfEqualAsync_WithNullKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task RemoveIfEqualAsync_WithWhitespaceKey_ThrowsArgumentException() + { + return base.RemoveIfEqualAsync_WithWhitespaceKey_ThrowsArgumentException(); + } + + [Fact] + public override Task ReplaceAsync_WithDifferentCasedKeys_TreatsAsDifferentKeys() + { + return base.ReplaceAsync_WithDifferentCasedKeys_TreatsAsDifferentKeys(); + } + + [Fact] + public override Task ReplaceAsync_WithEmptyKey_ThrowsArgumentNullException() + { + return base.ReplaceAsync_WithEmptyKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task ReplaceAsync_WithExistingKey_ReturnsTrueAndReplacesValue() + { + return base.ReplaceAsync_WithExistingKey_ReturnsTrueAndReplacesValue(); + } + + [Fact] + public override Task ReplaceAsync_WithExpiration_SetsExpirationCorrectly() + { + return base.ReplaceAsync_WithExpiration_SetsExpirationCorrectly(); + } + + [Fact] + public override Task ReplaceAsync_WithNonExistentKey_ReturnsFalseAndDoesNotCreateKey() + { + return base.ReplaceAsync_WithNonExistentKey_ReturnsFalseAndDoesNotCreateKey(); + } + + [Fact] + public override Task ReplaceAsync_WithNullKey_ThrowsArgumentNullException() + { + return base.ReplaceAsync_WithNullKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task ReplaceAsync_WithWhitespaceKey_ThrowsArgumentException() + { + return base.ReplaceAsync_WithWhitespaceKey_ThrowsArgumentException(); + } + + [Fact] + public override Task ReplaceIfEqualAsync_WithDifferentCasedKeys_ReplacesOnlyExactMatch() + { + return base.ReplaceIfEqualAsync_WithDifferentCasedKeys_ReplacesOnlyExactMatch(); + } + + [Fact] + public override Task ReplaceIfEqualAsync_WithEmptyKey_ThrowsArgumentNullException() + { + return base.ReplaceIfEqualAsync_WithEmptyKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task ReplaceIfEqualAsync_WithExpiration_SetsExpirationCorrectly() + { + return base.ReplaceIfEqualAsync_WithExpiration_SetsExpirationCorrectly(); + } + + [Fact] + public override Task ReplaceIfEqualAsync_WithMatchingOldValue_ReturnsTrueAndReplacesValue() + { + return base.ReplaceIfEqualAsync_WithMatchingOldValue_ReturnsTrueAndReplacesValue(); + } + + [Fact] + public override Task ReplaceIfEqualAsync_WithMismatchedOldValue_ReturnsFalseAndDoesNotReplace() + { + return base.ReplaceIfEqualAsync_WithMismatchedOldValue_ReturnsFalseAndDoesNotReplace(); + } + + [Fact] + public override Task ReplaceIfEqualAsync_WithNullKey_ThrowsArgumentNullException() + { + return base.ReplaceIfEqualAsync_WithNullKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task ReplaceIfEqualAsync_WithWhitespaceKey_ThrowsArgumentException() + { + return base.ReplaceIfEqualAsync_WithWhitespaceKey_ThrowsArgumentException(); + } + + [Fact(Skip = "Performance Test")] + public override Task Serialization_WithComplexObjectsAndValidation_MeasuresThroughput() + { + return base.Serialization_WithComplexObjectsAndValidation_MeasuresThroughput(); + } + + [Fact(Skip = "Performance Test")] + public override Task Serialization_WithSimpleObjectsAndValidation_MeasuresThroughput() + { + return base.Serialization_WithSimpleObjectsAndValidation_MeasuresThroughput(); + } + + [Fact] + public override Task SetAllAsync_WithDateTimeMinValue_DoesNotAddKeys() + { + return base.SetAllAsync_WithDateTimeMinValue_DoesNotAddKeys(); + } + + [Fact] + public override Task SetAllAsync_WithDifferentCasedKeys_CreatesDistinctEntries() + { + return base.SetAllAsync_WithDifferentCasedKeys_CreatesDistinctEntries(); + } + + [Fact] + public override Task SetAllAsync_WithEmptyItems_ReturnsTrue() + { + return base.SetAllAsync_WithEmptyItems_ReturnsTrue(); + } + + [Fact] + public override Task SetAllAsync_WithExpiration_KeysExpireCorrectly() + { + return base.SetAllAsync_WithExpiration_KeysExpireCorrectly(); + } + + [Fact] + public override Task SetAllAsync_WithItemsContainingEmptyKey_ThrowsArgumentException() + { + return base.SetAllAsync_WithItemsContainingEmptyKey_ThrowsArgumentException(); + } + + [Fact] + public override Task SetAllAsync_WithItemsContainingNullKey_ThrowsArgumentException() + { + return base.SetAllAsync_WithItemsContainingNullKey_ThrowsArgumentException(); + } + + [Fact] + public override Task SetAllAsync_WithItemsContainingWhitespaceKey_ThrowsArgumentException() + { + return base.SetAllAsync_WithItemsContainingWhitespaceKey_ThrowsArgumentException(); + } + + [Fact] + public override Task SetAllAsync_WithNullItems_ThrowsArgumentNullException() + { + return base.SetAllAsync_WithNullItems_ThrowsArgumentNullException(); } [Theory] @@ -224,6 +1076,12 @@ public override Task SetAllExpiration_WithLargeNumberOfKeys_SetsAllExpirations(i return base.SetAllExpiration_WithLargeNumberOfKeys_SetsAllExpirations(count); } + [Fact] + public override Task SetAllExpiration_WithMultipleKeys_SetsExpirationForAll() + { + return base.SetAllExpiration_WithMultipleKeys_SetsExpirationForAll(); + } + [Fact] public override Task SetAllExpiration_WithNonExistentKeys_HandlesGracefully() { @@ -231,111 +1089,207 @@ public override Task SetAllExpiration_WithNonExistentKeys_HandlesGracefully() } [Fact] - public override Task CanIncrementAsync() + public override Task SetAllExpiration_WithNullValues_RemovesExpiration() { - return base.CanIncrementAsync(); + return base.SetAllExpiration_WithNullValues_RemovesExpiration(); } [Fact] - public override Task CanIncrementAndExpireAsync() + public override Task SetAsync_WithComplexObject_StoresCorrectly() { - return base.CanIncrementAndExpireAsync(); + return base.SetAsync_WithComplexObject_StoresCorrectly(); } [Fact] - public override Task SetAllShouldExpireAsync() + public override Task SetAsync_WithDifferentCasedKeys_CreatesDistinctEntries() { - return base.SetAllShouldExpireAsync(); + return base.SetAsync_WithDifferentCasedKeys_CreatesDistinctEntries(); } [Fact] - public override Task CanReplaceIfEqual() + public override Task SetAsync_WithDifferentCasedScopes_MaintainsDistinctNamespaces() { - return base.CanReplaceIfEqual(); + return base.SetAsync_WithDifferentCasedScopes_MaintainsDistinctNamespaces(); } [Fact] - public override Task CanRemoveIfEqual() + public override Task SetAsync_WithDifferentScopes_IsolatesKeys() { - return base.CanRemoveIfEqual(); + return base.SetAsync_WithDifferentScopes_IsolatesKeys(); } [Fact] - public override Task CanGetAndSetDateTimeAsync() + public override Task SetAsync_WithEmptyKey_ThrowsArgumentNullException() { - return base.CanGetAndSetDateTimeAsync(); + return base.SetAsync_WithEmptyKey_ThrowsArgumentNullException(); } [Fact] - public override Task CanRoundTripLargeNumbersAsync() + public override Task SetAsync_WithExpirationEdgeCases_HandlesCorrectly() { - return base.CanRoundTripLargeNumbersAsync(); + return base.SetAsync_WithExpirationEdgeCases_HandlesCorrectly(); } [Fact] - public override Task CanRoundTripLargeNumbersWithExpirationAsync() + public override Task SetAsync_WithLargeNumber_StoresCorrectly() { - return base.CanRoundTripLargeNumbersWithExpirationAsync(); + return base.SetAsync_WithLargeNumber_StoresCorrectly(); } [Fact] - public override Task CanManageListsAsync() + public override Task SetAsync_WithLargeNumbersAndExpiration_PreservesValues() { - return base.CanManageListsAsync(); + return base.SetAsync_WithLargeNumbersAndExpiration_PreservesValues(); } [Fact] - public override Task CanManageListsWithNullItemsAsync() + public override Task SetAsync_WithNestedScopes_PreservesHierarchy() { - return base.CanManageListsWithNullItemsAsync(); + return base.SetAsync_WithNestedScopes_PreservesHierarchy(); } [Fact] - public override Task CanManageStringListsAsync() + public override Task SetAsync_WithNullKey_ThrowsArgumentNullException() { - return base.CanManageStringListsAsync(); + return base.SetAsync_WithNullKey_ThrowsArgumentNullException(); } [Fact] - public override Task CanManageListPagingAsync() + public override Task SetAsync_WithNullReferenceType_StoresAsNullValue() { - return base.CanManageListPagingAsync(); + return base.SetAsync_WithNullReferenceType_StoresAsNullValue(); } [Fact] - public override Task CanManageGetListExpirationAsync() + public override Task SetAsync_WithNullValueType_StoresAsNullValue() { - return base.CanManageGetListExpirationAsync(); + return base.SetAsync_WithNullValueType_StoresAsNullValue(); } [Fact] - public override Task CanManageListAddExpirationAsync() + public override Task SetAsync_WithShortExpiration_ExpiresCorrectly() { - return base.CanManageListAddExpirationAsync(); + return base.SetAsync_WithShortExpiration_ExpiresCorrectly(); } [Fact] - public override Task CanManageListRemoveExpirationAsync() + public override Task SetAsync_WithWhitespaceKey_ThrowsArgumentException() { - return base.CanManageListRemoveExpirationAsync(); + return base.SetAsync_WithWhitespaceKey_ThrowsArgumentException(); } - [Fact(Skip = "Performance Test")] - public override Task MeasureThroughputAsync() + [Fact] + public override Task SetExpirationAsync_ChangingFromNoExpirationToFutureTime_UpdatesCorrectly() { - return base.MeasureThroughputAsync(); + return base.SetExpirationAsync_ChangingFromNoExpirationToFutureTime_UpdatesCorrectly(); } - [Fact(Skip = "Performance Test")] - public override Task MeasureSerializerSimpleThroughputAsync() + [Fact] + public override Task SetExpirationAsync_ChangingToDateTimeMinValue_RemovesKey() { - return base.MeasureSerializerSimpleThroughputAsync(); + return base.SetExpirationAsync_ChangingToDateTimeMinValue_RemovesKey(); } - [Fact(Skip = "Performance Test")] - public override Task MeasureSerializerComplexThroughputAsync() + [Fact] + public override Task SetExpirationAsync_WithCurrentTime_ExpiresImmediately() + { + return base.SetExpirationAsync_WithCurrentTime_ExpiresImmediately(); + } + + [Fact] + public override Task SetExpirationAsync_WithDateTimeMaxValue_NeverExpires() + { + return base.SetExpirationAsync_WithDateTimeMaxValue_NeverExpires(); + } + + [Fact] + public override Task SetExpirationAsync_WithDateTimeMinValue_ExpiresImmediately() + { + return base.SetExpirationAsync_WithDateTimeMinValue_ExpiresImmediately(); + } + + [Fact] + public override Task SetExpirationAsync_WithDifferentCasedKeys_SetsOnlyExactMatch() + { + return base.SetExpirationAsync_WithDifferentCasedKeys_SetsOnlyExactMatch(); + } + + [Fact] + public override Task SetExpirationAsync_WithEmptyKey_ThrowsArgumentNullException() + { + return base.SetExpirationAsync_WithEmptyKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task SetExpirationAsync_WithNullKey_ThrowsArgumentNullException() + { + return base.SetExpirationAsync_WithNullKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task SetExpirationAsync_WithWhitespaceKey_ThrowsArgumentException() + { + return base.SetExpirationAsync_WithWhitespaceKey_ThrowsArgumentException(); + } + + [Fact] + public override Task SetIfHigherAsync_WithDateTime_DoesNotUpdateWhenLower() + { + return base.SetIfHigherAsync_WithDateTime_DoesNotUpdateWhenLower(); + } + + [Fact] + public override Task SetIfHigherAsync_WithDateTime_InitializesWhenKeyNotExists() + { + return base.SetIfHigherAsync_WithDateTime_InitializesWhenKeyNotExists(); + } + + [Fact] + public override Task SetIfHigherAsync_WithDateTime_UpdatesWhenHigher() + { + return base.SetIfHigherAsync_WithDateTime_UpdatesWhenHigher(); + } + + [Fact] + public override Task SetIfHigherAsync_WithLargeNumbers_UpdatesWhenHigher() + { + return base.SetIfHigherAsync_WithLargeNumbers_UpdatesWhenHigher(); + } + + [Fact] + public override Task SetIfLowerAsync_WithDateTime_DoesNotUpdateWhenHigher() + { + return base.SetIfLowerAsync_WithDateTime_DoesNotUpdateWhenHigher(); + } + + [Fact] + public override Task SetIfLowerAsync_WithDateTime_InitializesWhenKeyNotExists() + { + return base.SetIfLowerAsync_WithDateTime_InitializesWhenKeyNotExists(); + } + + [Fact] + public override Task SetIfLowerAsync_WithDateTime_UpdatesWhenLower() + { + return base.SetIfLowerAsync_WithDateTime_UpdatesWhenLower(); + } + + [Fact] + public override Task SetIfLowerAsync_WithLargeNumbers_UpdatesWhenLower() + { + return base.SetIfLowerAsync_WithLargeNumbers_UpdatesWhenLower(); + } + + [Fact] + public override Task SetUnixTimeMillisecondsAsync_WithLocalDateTime_StoresCorrectly() + { + return base.SetUnixTimeMillisecondsAsync_WithLocalDateTime_StoresCorrectly(); + } + + [Fact] + public override Task SetUnixTimeSecondsAsync_WithUtcDateTime_StoresCorrectly() { - return base.MeasureSerializerComplexThroughputAsync(); + return base.SetUnixTimeSecondsAsync_WithUtcDateTime_StoresCorrectly(); } public Task InitializeAsync() diff --git a/tests/Foundatio.Redis.Tests/Caching/ScopedRedisHybridCacheClientTests.cs b/tests/Foundatio.Redis.Tests/Caching/ScopedRedisHybridCacheClientTests.cs index e2b3f7e..3448275 100644 --- a/tests/Foundatio.Redis.Tests/Caching/ScopedRedisHybridCacheClientTests.cs +++ b/tests/Foundatio.Redis.Tests/Caching/ScopedRedisHybridCacheClientTests.cs @@ -33,134 +33,852 @@ protected override HybridCacheClient GetDistributedHybridCacheClient(bool should } [Fact] - public override Task CanGetAllAsync() + public override Task AddAsync_WithConcurrentRequests_OnlyOneSucceeds() { - return base.CanGetAllAsync(); + return base.AddAsync_WithConcurrentRequests_OnlyOneSucceeds(); } [Fact] - public override Task CanGetAllWithOverlapAsync() + public override Task AddAsync_WithEmptyKey_ThrowsArgumentNullException() { - return base.CanGetAllWithOverlapAsync(); + return base.AddAsync_WithEmptyKey_ThrowsArgumentNullException(); } [Fact] - public override Task CanSetAsync() + public override Task AddAsync_WithExistingKey_ReturnsFalseAndPreservesValue() { - return base.CanSetAsync(); + return base.AddAsync_WithExistingKey_ReturnsFalseAndPreservesValue(); + } + + [Fact(Skip = "TODO: Look into this as the local cache client maintenance never schedules for expiration")] + public override Task AddAsync_WithExpiration_ExpiresRemoteItems() + { + return base.AddAsync_WithExpiration_ExpiresRemoteItems(); + } + + [Fact] + public override Task AddAsync_WithNestedKeyUsingSeparator_StoresCorrectly() + { + return base.AddAsync_WithNestedKeyUsingSeparator_StoresCorrectly(); + } + + [Fact] + public override Task AddAsync_WithNewKey_ReturnsTrue() + { + return base.AddAsync_WithNewKey_ReturnsTrue(); + } + + [Fact] + public override Task AddAsync_WithNullKey_ThrowsArgumentNullException() + { + return base.AddAsync_WithNullKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task AddAsync_WithWhitespaceKey_ThrowsArgumentException() + { + return base.AddAsync_WithWhitespaceKey_ThrowsArgumentException(); + } + + [Fact] + public override Task CacheOperations_WithMultipleTypes_MeasuresThroughput() + { + return base.CacheOperations_WithMultipleTypes_MeasuresThroughput(); + } + + [Fact] + public override Task CacheOperations_WithRepeatedSetAndGet_MeasuresThroughput() + { + return base.CacheOperations_WithRepeatedSetAndGet_MeasuresThroughput(); + } + + [Fact] + public override Task ExistsAsync_AfterKeyExpires_ReturnsFalse() + { + return base.ExistsAsync_AfterKeyExpires_ReturnsFalse(); + } + + [Fact] + public override Task ExistsAsync_WithDifferentCasedKeys_ChecksExactMatch() + { + return base.ExistsAsync_WithDifferentCasedKeys_ChecksExactMatch(); + } + + [Fact] + public override Task ExistsAsync_WithEmptyKey_ThrowsArgumentNullException() + { + return base.ExistsAsync_WithEmptyKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task ExistsAsync_WithExistingKey_ReturnsTrue() + { + return base.ExistsAsync_WithExistingKey_ReturnsTrue(); + } + + [Fact] + public override Task ExistsAsync_WithExpiredKey_ReturnsFalse() + { + return base.ExistsAsync_WithExpiredKey_ReturnsFalse(); + } + + [Fact] + public override Task ExistsAsync_WithLocalCache_ChecksLocalCacheFirst() + { + return base.ExistsAsync_WithLocalCache_ChecksLocalCacheFirst(); + } + + [Fact] + public override Task ExistsAsync_WithNonExistentKey_ReturnsFalse() + { + return base.ExistsAsync_WithNonExistentKey_ReturnsFalse(); + } + + [Fact] + public override Task ExistsAsync_WithNullKey_ThrowsArgumentNullException() + { + return base.ExistsAsync_WithNullKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task ExistsAsync_WithNullStoredValue_ReturnsTrue() + { + return base.ExistsAsync_WithNullStoredValue_ReturnsTrue(); + } + + [Fact] + public override Task ExistsAsync_WithScopedCache_ChecksOnlyWithinScope() + { + return base.ExistsAsync_WithScopedCache_ChecksOnlyWithinScope(); + } + + [Fact] + public override Task ExistsAsync_WithWhitespaceKey_ThrowsArgumentException() + { + return base.ExistsAsync_WithWhitespaceKey_ThrowsArgumentException(); + } + + [Fact] + public override Task GetAllAsync_WithEmptyKeys_ReturnsEmpty() + { + return base.GetAllAsync_WithEmptyKeys_ReturnsEmpty(); + } + + [Fact] + public override Task GetAllAsync_WithExistingKeys_ReturnsAllValues() + { + return base.GetAllAsync_WithExistingKeys_ReturnsAllValues(); + } + + [Fact] + public override Task GetAllAsync_WithKeysContainingEmpty_ThrowsArgumentException() + { + return base.GetAllAsync_WithKeysContainingEmpty_ThrowsArgumentException(); + } + + [Fact] + public override Task GetAllAsync_WithKeysContainingNull_ThrowsArgumentException() + { + return base.GetAllAsync_WithKeysContainingNull_ThrowsArgumentException(); + } + + [Fact] + public override Task GetAllAsync_WithKeysContainingWhitespace_ThrowsArgumentException() + { + return base.GetAllAsync_WithKeysContainingWhitespace_ThrowsArgumentException(); + } + + [Fact] + public override Task GetAllAsync_WithMixedCaseKeys_RetrievesExactMatches() + { + return base.GetAllAsync_WithMixedCaseKeys_RetrievesExactMatches(); + } + + [Fact] + public override Task GetAllAsync_WithMixedObjectTypes_ReturnsCorrectValues() + { + return base.GetAllAsync_WithMixedObjectTypes_ReturnsCorrectValues(); + } + + [Fact] + public override Task GetAllAsync_WithMultipleKeys_UsesHybridCache() + { + return base.GetAllAsync_WithMultipleKeys_UsesHybridCache(); + } + + [Fact] + public override Task GetAllAsync_WithNonExistentKeys_ReturnsEmptyResults() + { + return base.GetAllAsync_WithNonExistentKeys_ReturnsEmptyResults(); + } + + [Fact] + public override Task GetAllAsync_WithNullKeys_ThrowsArgumentNullException() + { + return base.GetAllAsync_WithNullKeys_ThrowsArgumentNullException(); + } + + [Fact] + public override Task GetAllAsync_WithNullValues_HandlesNullsCorrectly() + { + return base.GetAllAsync_WithNullValues_HandlesNullsCorrectly(); + } + + [Fact] + public override Task GetAllAsync_WithOverlappingKeys_UsesLatestValues() + { + return base.GetAllAsync_WithOverlappingKeys_UsesLatestValues(); + } + + [Fact] + public override Task GetAllAsync_WithScopedCache_ReturnsUnscopedKeys() + { + return base.GetAllAsync_WithScopedCache_ReturnsUnscopedKeys(); + } + + [Fact] + public override Task GetAllExpirationAsync_WithExpiredKeys_ExcludesExpiredKeys() + { + return base.GetAllExpirationAsync_WithExpiredKeys_ExcludesExpiredKeys(); + } + + [Theory] + [InlineData(1000)] + [InlineData(10000)] + public override Task GetAllExpirationAsync_WithLargeNumberOfKeys_ReturnsAllExpirations(int count) + { + return base.GetAllExpirationAsync_WithLargeNumberOfKeys_ReturnsAllExpirations(count); + } + + [Fact] + public override Task GetAllExpirationAsync_WithMixedKeys_ReturnsOnlyKeysWithExpiration() + { + return base.GetAllExpirationAsync_WithMixedKeys_ReturnsOnlyKeysWithExpiration(); + } + + [Fact] + public override Task GetAsync_WithComplexObject_PreservesAllProperties() + { + return base.GetAsync_WithComplexObject_PreservesAllProperties(); + } + + [Fact] + public override Task GetAsync_WithComplexObject_ReturnsNewInstance() + { + return base.GetAsync_WithComplexObject_ReturnsNewInstance(); + } + + [Fact] + public override Task GetAsync_WithDifferentCasedKeys_TreatsAsDifferentKeys() + { + return base.GetAsync_WithDifferentCasedKeys_TreatsAsDifferentKeys(); + } + + [Fact] + public override Task GetAsync_WithEmptyKey_ThrowsArgumentNullException() + { + return base.GetAsync_WithEmptyKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task GetAsync_WithLargeNumber_ReturnsCorrectValue() + { + return base.GetAsync_WithLargeNumber_ReturnsCorrectValue(); + } + + [Fact] + public override Task GetAsync_WithMaxLongAsInt_ThrowsException() + { + return base.GetAsync_WithMaxLongAsInt_ThrowsException(); + } + + [Fact] + public override Task GetAsync_WithNonExistentKey_ReturnsNoValue() + { + return base.GetAsync_WithNonExistentKey_ReturnsNoValue(); + } + + [Fact] + public override Task GetAsync_WithNullKey_ThrowsArgumentNullException() + { + return base.GetAsync_WithNullKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task GetAsync_WithNumericTypeConversion_ConvertsIntToLong() + { + return base.GetAsync_WithNumericTypeConversion_ConvertsIntToLong(); + } + + [Fact] + public override Task GetAsync_WithNumericTypeConversion_ConvertsLongToInt() + { + return base.GetAsync_WithNumericTypeConversion_ConvertsLongToInt(); + } + + [Fact] + public override Task GetAsync_WithTryGetSemanticsAndComplexTypeAsLong_ReturnsNoValue() + { + return base.GetAsync_WithTryGetSemanticsAndComplexTypeAsLong_ReturnsNoValue(); + } + + [Fact] + public override Task GetAsync_WithTryGetSemanticsAndIntAsLong_ConvertsSuccessfully() + { + return base.GetAsync_WithTryGetSemanticsAndIntAsLong_ConvertsSuccessfully(); + } + + [Fact] + public override Task GetAsync_WithTryGetSemanticsAndMaxLongAsInt_ReturnsNoValue() + { + return base.GetAsync_WithTryGetSemanticsAndMaxLongAsInt_ReturnsNoValue(); + } + + [Fact] + public override Task GetAsync_WithWhitespaceKey_ThrowsArgumentException() + { + return base.GetAsync_WithWhitespaceKey_ThrowsArgumentException(); + } + + [Fact] + public override Task GetExpirationAsync_AfterExpiry_ReturnsNull() + { + return base.GetExpirationAsync_AfterExpiry_ReturnsNull(); + } + + [Fact] + public override Task GetExpirationAsync_WithDifferentCasedKeys_GetsExactMatch() + { + return base.GetExpirationAsync_WithDifferentCasedKeys_GetsExactMatch(); + } + + [Fact] + public override Task GetExpirationAsync_WithEmptyKey_ThrowsArgumentNullException() + { + return base.GetExpirationAsync_WithEmptyKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task GetExpirationAsync_WithExpiration_ReturnsCorrectTimeSpan() + { + return base.GetExpirationAsync_WithExpiration_ReturnsCorrectTimeSpan(); + } + + [Fact] + public override Task GetExpirationAsync_WithLocalCache_ChecksLocalCacheFirst() + { + return base.GetExpirationAsync_WithLocalCache_ChecksLocalCacheFirst(); + } + + [Fact] + public override Task GetExpirationAsync_WithExpiredKey_ReturnsNull() + { + return base.GetExpirationAsync_WithExpiredKey_ReturnsNull(); + } + + [Fact] + public override Task GetExpirationAsync_WithNoExpiration_ReturnsNull() + { + return base.GetExpirationAsync_WithNoExpiration_ReturnsNull(); + } + + [Fact] + public override Task GetExpirationAsync_WithNonExistentKey_ReturnsNull() + { + return base.GetExpirationAsync_WithNonExistentKey_ReturnsNull(); + } + + [Fact] + public override Task GetExpirationAsync_WithNullKey_ThrowsArgumentNullException() + { + return base.GetExpirationAsync_WithNullKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task GetExpirationAsync_WithWhitespaceKey_ThrowsArgumentException() + { + return base.GetExpirationAsync_WithWhitespaceKey_ThrowsArgumentException(); + } + + [Fact] + public override Task GetListAsync_WithEmptyKey_ThrowsArgumentNullException() + { + return base.GetListAsync_WithEmptyKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task GetListAsync_WithExpiredItems_RemovesExpiredAndReturnsActive() + { + return base.GetListAsync_WithExpiredItems_RemovesExpiredAndReturnsActive(); + } + + [Fact] + public override Task GetListAsync_WithInvalidPageNumber_ThrowsArgumentOutOfRangeException() + { + return base.GetListAsync_WithInvalidPageNumber_ThrowsArgumentOutOfRangeException(); + } + + [Fact] + public override Task GetListAsync_WithMultiplePages_ReturnsAllItems() + { + return base.GetListAsync_WithMultiplePages_ReturnsAllItems(); + } + + [Fact] + public override Task GetListAsync_WithNewItemsAdded_ReturnsNewItemsLast() + { + return base.GetListAsync_WithNewItemsAdded_ReturnsNewItemsLast(); + } + + [Fact] + public override Task GetListAsync_WithNullKey_ThrowsArgumentNullException() + { + return base.GetListAsync_WithNullKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task GetListAsync_WithPageBeyondEnd_ReturnsEmptyCollection() + { + return base.GetListAsync_WithPageBeyondEnd_ReturnsEmptyCollection(); + } + + [Fact] + public override Task GetListAsync_WithPaging_ReturnsCorrectPageSize() + { + return base.GetListAsync_WithPaging_ReturnsCorrectPageSize(); + } + + [Fact] + public override Task GetListAsync_WithWhitespaceKey_ThrowsArgumentException() + { + return base.GetListAsync_WithWhitespaceKey_ThrowsArgumentException(); + } + + [Fact] + public override Task GetUnixTimeMillisecondsAsync_WithLocalDateTime_ReturnsCorrectly() + { + return base.GetUnixTimeMillisecondsAsync_WithLocalDateTime_ReturnsCorrectly(); + } + + [Fact] + public override Task GetUnixTimeMillisecondsAsync_WithUtcDateTime_ReturnsCorrectly() + { + return base.GetUnixTimeMillisecondsAsync_WithUtcDateTime_ReturnsCorrectly(); + } + + [Fact] + public override Task GetUnixTimeSecondsAsync_WithUtcDateTime_ReturnsCorrectly() + { + return base.GetUnixTimeSecondsAsync_WithUtcDateTime_ReturnsCorrectly(); + } + + [Fact] + public override Task IncrementAsync_WithDifferentCasedKeys_IncrementsDistinctCounters() + { + return base.IncrementAsync_WithDifferentCasedKeys_IncrementsDistinctCounters(); + } + + [Fact] + public override Task IncrementAsync_WithEmptyKey_ThrowsArgumentNullException() + { + return base.IncrementAsync_WithEmptyKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task IncrementAsync_WithExistingKey_IncrementsValue() + { + return base.IncrementAsync_WithExistingKey_IncrementsValue(); + } + + [Fact] + public override Task IncrementAsync_WithExpiration_ExpiresCorrectly() + { + return base.IncrementAsync_WithExpiration_ExpiresCorrectly(); + } + + [Fact] + public override Task IncrementAsync_WithNonExistentKey_InitializesToOne() + { + return base.IncrementAsync_WithNonExistentKey_InitializesToOne(); + } + + [Fact] + public override Task IncrementAsync_WithNullKey_ThrowsArgumentNullException() + { + return base.IncrementAsync_WithNullKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task IncrementAsync_WithScopedCache_WorksWithinScope() + { + return base.IncrementAsync_WithScopedCache_WorksWithinScope(); + } + + [Fact] + public override Task IncrementAsync_WithSpecifiedAmount_IncrementsCorrectly() + { + return base.IncrementAsync_WithSpecifiedAmount_IncrementsCorrectly(); + } + + [Fact] + public override Task IncrementAsync_WithWhitespaceKey_ThrowsArgumentException() + { + return base.IncrementAsync_WithWhitespaceKey_ThrowsArgumentException(); + } + + [Fact] + public override Task ListAddAsync_WithDifferentCasedKeys_MaintainsDistinctLists() + { + return base.ListAddAsync_WithDifferentCasedKeys_MaintainsDistinctLists(); + } + + [Fact] + public override Task ListAddAsync_WithDuplicates_RemovesDuplicatesAndAddsItems() + { + return base.ListAddAsync_WithDuplicates_RemovesDuplicatesAndAddsItems(); + } + + [Fact] + public override Task ListAddAsync_WithDuplicates_StoresUniqueValuesOnly() + { + return base.ListAddAsync_WithDuplicates_StoresUniqueValuesOnly(); + } + + [Fact] + public override Task ListAddAsync_WithEmptyCollection_NoOp() + { + return base.ListAddAsync_WithEmptyCollection_NoOp(); + } + + [Fact] + public override Task ListAddAsync_WithEmptyKey_ThrowsArgumentNullException() + { + return base.ListAddAsync_WithEmptyKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task ListAddAsync_WithExistingNonListKey_ThrowsException() + { + return base.ListAddAsync_WithExistingNonListKey_ThrowsException(); + } + + [Fact] + public override Task ListAddAsync_WithFutureExpiration_AddsAndExpiresCorrectly() + { + return base.ListAddAsync_WithFutureExpiration_AddsAndExpiresCorrectly(); + } + + [Fact] + public override Task ListAddAsync_WithMultipleExpirations_ExpiresIndividualItems() + { + return base.ListAddAsync_WithMultipleExpirations_ExpiresIndividualItems(); + } + + [Fact] + public override Task ListAddAsync_WithMultipleInstances_WorksCorrectly() + { + return base.ListAddAsync_WithMultipleInstances_WorksCorrectly(); + } + + [Fact] + public override Task ListAddAsync_WithNullCollection_ThrowsArgumentNullException() + { + return base.ListAddAsync_WithNullCollection_ThrowsArgumentNullException(); + } + + [Fact] + public override Task ListAddAsync_WithNullItem_IgnoresNull() + { + return base.ListAddAsync_WithNullItem_IgnoresNull(); + } + + [Fact] + public override Task ListAddAsync_WithNullKey_ThrowsArgumentNullException() + { + return base.ListAddAsync_WithNullKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task ListAddAsync_WithNullValues_ThrowsArgumentNullException() + { + return base.ListAddAsync_WithNullValues_ThrowsArgumentNullException(); + } + + [Fact] + public override Task ListAddAsync_WithPastExpiration_RemovesItem() + { + return base.ListAddAsync_WithPastExpiration_RemovesItem(); + } + + [Fact] + public override Task ListAddAsync_WithSingleString_StoresAsStringNotCharArray() + { + return base.ListAddAsync_WithSingleString_StoresAsStringNotCharArray(); + } + + [Fact] + public override Task ListAddAsync_WithWhitespaceKey_ThrowsArgumentException() + { + return base.ListAddAsync_WithWhitespaceKey_ThrowsArgumentException(); + } + + [Fact] + public override Task ListRemoveAsync_WithEmptyKey_ThrowsArgumentNullException() + { + return base.ListRemoveAsync_WithEmptyKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task ListRemoveAsync_WithMultipleValues_RemovesAll() + { + return base.ListRemoveAsync_WithMultipleValues_RemovesAll(); + } + + [Fact] + public override Task ListRemoveAsync_WithNullCollection_ThrowsArgumentNullException() + { + return base.ListRemoveAsync_WithNullCollection_ThrowsArgumentNullException(); + } + + [Fact] + public override Task ListRemoveAsync_WithNullItem_IgnoresNull() + { + return base.ListRemoveAsync_WithNullItem_IgnoresNull(); + } + + [Fact] + public override Task ListRemoveAsync_WithNullKey_ThrowsArgumentNullException() + { + return base.ListRemoveAsync_WithNullKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task ListRemoveAsync_WithNullValues_ThrowsArgumentNullException() + { + return base.ListRemoveAsync_WithNullValues_ThrowsArgumentNullException(); + } + + [Fact] + public override Task ListRemoveAsync_WithSingleValue_RemovesCorrectly() + { + return base.ListRemoveAsync_WithSingleValue_RemovesCorrectly(); + } + + [Fact] + public override Task ListRemoveAsync_WithValidValues_RemovesKeyWhenEmpty() + { + return base.ListRemoveAsync_WithValidValues_RemovesKeyWhenEmpty(); + } + + [Fact] + public override Task ListRemoveAsync_WithWhitespaceKey_ThrowsArgumentException() + { + return base.ListRemoveAsync_WithWhitespaceKey_ThrowsArgumentException(); + } + + [Fact] + public override Task RemoveAllAsync_WithEmptyKeys_Succeeds() + { + return base.RemoveAllAsync_WithEmptyKeys_Succeeds(); + } + + [Fact] + public override Task RemoveAllAsync_WithKeysContainingEmpty_ThrowsArgumentException() + { + return base.RemoveAllAsync_WithKeysContainingEmpty_ThrowsArgumentException(); + } + + [Fact] + public override Task RemoveAllAsync_WithKeysContainingNull_ThrowsArgumentException() + { + return base.RemoveAllAsync_WithKeysContainingNull_ThrowsArgumentException(); + } + + [Fact] + public override Task RemoveAllAsync_WithKeysContainingWhitespace_ThrowsArgumentException() + { + return base.RemoveAllAsync_WithKeysContainingWhitespace_ThrowsArgumentException(); + } + + [Fact] + public override Task RemoveAllAsync_WithLargeNumberOfKeys_RemovesAllKeysEfficiently() + { + return base.RemoveAllAsync_WithLargeNumberOfKeys_RemovesAllKeysEfficiently(); + } + + [Fact] + public override Task RemoveAllAsync_WithLocalCache_InvalidatesLocalCache() + { + return base.RemoveAllAsync_WithLocalCache_InvalidatesLocalCache(); + } + + [Fact] + public override Task RemoveAllAsync_WithMixedCaseKeys_RemovesOnlyExactMatches() + { + return base.RemoveAllAsync_WithMixedCaseKeys_RemovesOnlyExactMatches(); + } + + [Fact] + public override Task RemoveAllAsync_WithNullKeys_ThrowsArgumentNullException() + { + return base.RemoveAllAsync_WithNullKeys_ThrowsArgumentNullException(); + } + + [Fact] + public override Task RemoveAllAsync_WithScopedCache_AffectsOnlyScopedKeys() + { + return base.RemoveAllAsync_WithScopedCache_AffectsOnlyScopedKeys(); + } + + [Fact] + public override Task RemoveAllAsync_WithSpecificKeyCollection_RemovesOnlySpecifiedKeys() + { + return base.RemoveAllAsync_WithSpecificKeyCollection_RemovesOnlySpecifiedKeys(); + } + + [Fact] + public override Task RemoveAsync_AfterSetAndGet_RemovesCorrectly() + { + return base.RemoveAsync_AfterSetAndGet_RemovesCorrectly(); + } + + [Fact] + public override Task RemoveAsync_MultipleTimes_Succeeds() + { + return base.RemoveAsync_MultipleTimes_Succeeds(); + } + + [Fact] + public override Task RemoveAsync_WithEmptyKey_ThrowsArgumentNullException() + { + return base.RemoveAsync_WithEmptyKey_ThrowsArgumentNullException(); } [Fact] - public override Task CanSetAndGetValueAsync() + public override Task RemoveAsync_WithExistingKey_RemovesSuccessfully() { - return base.CanSetAndGetValueAsync(); + return base.RemoveAsync_WithExistingKey_RemovesSuccessfully(); } [Fact] - public override Task CanAddAsync() + public override Task RemoveAsync_WithExpiredKey_Succeeds() { - return base.CanAddAsync(); + return base.RemoveAsync_WithExpiredKey_Succeeds(); } [Fact] - public override Task CanAddConcurrentlyAsync() + public override Task RemoveAsync_WithNonExistentKey_Succeeds() { - return base.CanAddConcurrentlyAsync(); + return base.RemoveAsync_WithNonExistentKey_Succeeds(); } [Fact] - public override Task CanGetAsync() + public override Task RemoveAsync_WithNullKey_ThrowsArgumentNullException() { - return base.CanGetAsync(); + return base.RemoveAsync_WithNullKey_ThrowsArgumentNullException(); } [Fact] - public override Task CanTryGetAsync() + public override Task RemoveAsync_WithNullValue_RemovesSuccessfully() { - return base.CanTryGetAsync(); + return base.RemoveAsync_WithNullValue_RemovesSuccessfully(); } [Fact] - public override Task CanUseScopedCachesAsync() + public override Task RemoveAsync_WithScopedCache_RemovesOnlyWithinScope() { - return base.CanUseScopedCachesAsync(); + return base.RemoveAsync_WithScopedCache_RemovesOnlyWithinScope(); } [Fact] - public override Task CanRemoveAllAsync() + public override Task RemoveAsync_WithSpecificCase_RemovesOnlyMatchingKey() { - return base.CanRemoveAllAsync(); + return base.RemoveAsync_WithSpecificCase_RemovesOnlyMatchingKey(); } [Fact] - public override Task CanRemoveAllKeysAsync() + public override Task RemoveAsync_WithWhitespaceKey_ThrowsArgumentException() { - return base.CanRemoveAllKeysAsync(); + return base.RemoveAsync_WithWhitespaceKey_ThrowsArgumentException(); } [Fact] - public override Task CanRemoveByPrefixAsync() + public override Task RemoveByPrefixAsync_AsteriskPrefixWithScopedCache_TreatedAsLiteral() { - return base.CanRemoveByPrefixAsync(); + return base.RemoveByPrefixAsync_AsteriskPrefixWithScopedCache_TreatedAsLiteral(); } [Theory] - [MemberData(nameof(GetRegexSpecialCharacters))] - public override Task CanRemoveByPrefixWithRegexCharactersAsync(string specialChar) + [InlineData("snowboard", 1, true)] // Exact key match + [InlineData("s", 1, true)] // Partial prefix match + [InlineData(null, 1, false)] // Null prefix (all keys in scope) + [InlineData("", 1, false)] // Empty prefix (all keys in scope) + public override Task RemoveByPrefixAsync_FromScopedCache_RemovesOnlyScopedKeys(string prefixToRemove, int expectedRemovedCount, bool shouldUnscopedRemain) { - return base.CanRemoveByPrefixWithRegexCharactersAsync(specialChar); + return base.RemoveByPrefixAsync_FromScopedCache_RemovesOnlyScopedKeys(prefixToRemove, expectedRemovedCount, shouldUnscopedRemain); } [Theory] - [MemberData(nameof(GetWildcardPatterns))] - public override Task CanRemoveByPrefixWithWildcardPatternsAsync(string pattern) + [InlineData(null)] + [InlineData("")] + public override Task RemoveByPrefixAsync_NullOrEmptyPrefixWithScopedCache_RemovesCorrectKeys(string prefix) { - return base.CanRemoveByPrefixWithWildcardPatternsAsync(pattern); + return base.RemoveByPrefixAsync_NullOrEmptyPrefixWithScopedCache_RemovesCorrectKeys(prefix); } [Fact] - public override Task CanRemoveByPrefixWithDoubleAsteriskAsync() + public override Task RemoveByPrefixAsync_PartialPrefixWithScopedCache_RemovesMatchingKeys() { - return base.CanRemoveByPrefixWithDoubleAsteriskAsync(); + return base.RemoveByPrefixAsync_PartialPrefixWithScopedCache_RemovesMatchingKeys(); } - [Theory] - [MemberData(nameof(GetSpecialPrefixes))] - public override Task CanRemoveByPrefixWithSpecialCharactersAsync(string specialPrefix) + [Fact] + public override Task RemoveByPrefixAsync_WithCaseSensitivePrefix_RemovesOnlyMatchingCase() { - return base.CanRemoveByPrefixWithSpecialCharactersAsync(specialPrefix); + return base.RemoveByPrefixAsync_WithCaseSensitivePrefix_RemovesOnlyMatchingCase(); } [Fact] - public override Task CanRemoveByPrefixWithNullAsync() + public override Task RemoveByPrefixAsync_WithDifferentCasedScopes_RemovesOnlyMatchingScope() { - return base.CanRemoveByPrefixWithNullAsync(); + return base.RemoveByPrefixAsync_WithDifferentCasedScopes_RemovesOnlyMatchingScope(); } [Fact] - public override Task CanRemoveByPrefixWithEmptyStringAsync() + public override Task RemoveByPrefixAsync_WithDoubleAsteriskPrefix_TreatsAsLiteral() { - return base.CanRemoveByPrefixWithEmptyStringAsync(); + return base.RemoveByPrefixAsync_WithDoubleAsteriskPrefix_TreatsAsLiteral(); } - [Theory] - [MemberData(nameof(GetWhitespaceOnlyPrefixes))] - public override Task CanRemoveByPrefixWithWhitespaceAsync(string whitespacePrefix) + [Fact] + public override Task RemoveByPrefixAsync_WithEmptyPrefix_RemovesAllKeys() + { + return base.RemoveByPrefixAsync_WithEmptyPrefix_RemovesAllKeys(); + } + + [Fact] + public override Task RemoveByPrefixAsync_WithEmptyPrefix_ThrowsArgumentException() { - return base.CanRemoveByPrefixWithWhitespaceAsync(whitespacePrefix); + return base.RemoveByPrefixAsync_WithEmptyPrefix_ThrowsArgumentException(); } [Theory] [MemberData(nameof(GetLineEndingPrefixes))] - public override Task CanRemoveByPrefixWithLineEndingsAsync(string lineEndingPrefix) + public override Task RemoveByPrefixAsync_WithLineEndingPrefix_TreatsAsLiteral(string lineEndingPrefix) + { + return base.RemoveByPrefixAsync_WithLineEndingPrefix_TreatsAsLiteral(lineEndingPrefix); + } + + [Fact] + public override Task RemoveByPrefixAsync_WithLocalCache_InvalidatesLocalCache() { - return base.CanRemoveByPrefixWithLineEndingsAsync(lineEndingPrefix); + return base.RemoveByPrefixAsync_WithLocalCache_InvalidatesLocalCache(); } [Fact] - public override Task CanRemoveByPrefixWithScopedCachesAsync() + public override Task RemoveByPrefixAsync_WithMatchingPrefix_RemovesOnlyPrefixedKeys() { - return base.CanRemoveByPrefixWithScopedCachesAsync(); + return base.RemoveByPrefixAsync_WithMatchingPrefix_RemovesOnlyPrefixedKeys(); } [Theory] @@ -168,65 +886,241 @@ public override Task CanRemoveByPrefixWithScopedCachesAsync() [InlineData(500)] [InlineData(5000)] [InlineData(50000)] - public override Task CanRemoveByPrefixMultipleEntriesAsync(int count) + public override Task RemoveByPrefixAsync_WithMultipleMatchingKeys_RemovesOnlyPrefixedKeys(int count) { - return base.CanRemoveByPrefixMultipleEntriesAsync(count); + return base.RemoveByPrefixAsync_WithMultipleMatchingKeys_RemovesOnlyPrefixedKeys(count); } [Fact] - public override Task CanSetAndGetObjectAsync() + public override Task RemoveByPrefixAsync_WithNonMatchingPrefix_RemovesZeroKeys() { - return base.CanSetAndGetObjectAsync(); + return base.RemoveByPrefixAsync_WithNonMatchingPrefix_RemovesZeroKeys(); } [Fact] - public override Task GetExpirationAsync_WithVariousStates_ReturnsCorrectly() + public override Task RemoveByPrefixAsync_WithNullPrefix_RemovesAllKeys() { - return base.GetExpirationAsync_WithVariousStates_ReturnsCorrectly(); + return base.RemoveByPrefixAsync_WithNullPrefix_RemovesAllKeys(); } [Fact] - public override Task GetAllExpiration_WithMultipleKeys_ReturnsAllExpirations() + public override Task RemoveByPrefixAsync_WithNullPrefix_ThrowsArgumentNullException() { - return base.GetAllExpiration_WithMultipleKeys_ReturnsAllExpirations(); + return base.RemoveByPrefixAsync_WithNullPrefix_ThrowsArgumentNullException(); } [Theory] - [InlineData(1000)] - [InlineData(10000)] - public override Task GetAllExpiration_WithLargeNumberOfKeys_ReturnsAllExpirations(int count) + [MemberData(nameof(GetRegexSpecialCharacters))] + public override Task RemoveByPrefixAsync_WithRegexMetacharacter_TreatsAsLiteral(string specialChar) + { + return base.RemoveByPrefixAsync_WithRegexMetacharacter_TreatsAsLiteral(specialChar); + } + + [Fact] + public override Task RemoveByPrefixAsync_WithScopedCache_AffectsOnlyScopedKeys() + { + return base.RemoveByPrefixAsync_WithScopedCache_AffectsOnlyScopedKeys(); + } + + [Theory] + [MemberData(nameof(GetSpecialPrefixes))] + public override Task RemoveByPrefixAsync_WithSpecialCharacterPrefix_TreatsAsLiteral(string specialPrefix) { - return base.GetAllExpiration_WithLargeNumberOfKeys_ReturnsAllExpirations(count); + return base.RemoveByPrefixAsync_WithSpecialCharacterPrefix_TreatsAsLiteral(specialPrefix); } [Fact] - public override Task GetAllExpiration_WithExpiredKeys_ReturnsNullForExpiredKeys() + public override Task RemoveByPrefixAsync_WithWhitespacePrefix_ThrowsArgumentException() + { + return base.RemoveByPrefixAsync_WithWhitespacePrefix_ThrowsArgumentException(); + } + + [Theory] + [MemberData(nameof(GetWhitespaceOnlyPrefixes))] + public override Task RemoveByPrefixAsync_WithWhitespacePrefix_TreatsAsLiteral(string whitespacePrefix) + { + return base.RemoveByPrefixAsync_WithWhitespacePrefix_TreatsAsLiteral(whitespacePrefix); + } + + [Theory] + [MemberData(nameof(GetWildcardPatterns))] + public override Task RemoveByPrefixAsync_WithWildcardPattern_TreatsAsLiteral(string pattern) { - return base.GetAllExpiration_WithExpiredKeys_ReturnsNullForExpiredKeys(); + return base.RemoveByPrefixAsync_WithWildcardPattern_TreatsAsLiteral(pattern); } [Fact] - public override Task SetExpirationAsync_WithValidDateTime_SetsExpirationCorrectly() + public override Task RemoveIfEqualAsync_WithEmptyKey_ThrowsArgumentNullException() { - return base.SetExpirationAsync_WithValidDateTime_SetsExpirationCorrectly(); + return base.RemoveIfEqualAsync_WithEmptyKey_ThrowsArgumentNullException(); } [Fact] - public override Task SetExpirationAsync_WithMinMaxValues_HandlesEdgeCases() + public override Task RemoveIfEqualAsync_WithMatchingValue_ReturnsTrueAndRemoves() { - return base.SetExpirationAsync_WithMinMaxValues_HandlesEdgeCases(); + return base.RemoveIfEqualAsync_WithMatchingValue_ReturnsTrueAndRemoves(); } [Fact] - public override Task SetAllExpiration_WithMultipleKeys_SetsExpirationForAll() + public override Task RemoveIfEqualAsync_WithMismatchedValue_ReturnsFalseAndDoesNotRemove() { - return base.SetAllExpiration_WithMultipleKeys_SetsExpirationForAll(); + return base.RemoveIfEqualAsync_WithMismatchedValue_ReturnsFalseAndDoesNotRemove(); } [Fact] - public override Task SetAllExpiration_WithNullValues_RemovesExpiration() + public override Task RemoveIfEqualAsync_WithNullKey_ThrowsArgumentNullException() { - return base.SetAllExpiration_WithNullValues_RemovesExpiration(); + return base.RemoveIfEqualAsync_WithNullKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task RemoveIfEqualAsync_WithWhitespaceKey_ThrowsArgumentException() + { + return base.RemoveIfEqualAsync_WithWhitespaceKey_ThrowsArgumentException(); + } + + [Fact] + public override Task ReplaceAsync_WithDifferentCasedKeys_TreatsAsDifferentKeys() + { + return base.ReplaceAsync_WithDifferentCasedKeys_TreatsAsDifferentKeys(); + } + + [Fact] + public override Task ReplaceAsync_WithEmptyKey_ThrowsArgumentNullException() + { + return base.ReplaceAsync_WithEmptyKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task ReplaceAsync_WithExistingKey_ReturnsTrueAndReplacesValue() + { + return base.ReplaceAsync_WithExistingKey_ReturnsTrueAndReplacesValue(); + } + + [Fact] + public override Task ReplaceAsync_WithExpiration_SetsExpirationCorrectly() + { + return base.ReplaceAsync_WithExpiration_SetsExpirationCorrectly(); + } + + [Fact] + public override Task ReplaceAsync_WithNonExistentKey_ReturnsFalseAndDoesNotCreateKey() + { + return base.ReplaceAsync_WithNonExistentKey_ReturnsFalseAndDoesNotCreateKey(); + } + + [Fact] + public override Task ReplaceAsync_WithNullKey_ThrowsArgumentNullException() + { + return base.ReplaceAsync_WithNullKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task ReplaceAsync_WithWhitespaceKey_ThrowsArgumentException() + { + return base.ReplaceAsync_WithWhitespaceKey_ThrowsArgumentException(); + } + + [Fact] + public override Task ReplaceIfEqualAsync_WithDifferentCasedKeys_ReplacesOnlyExactMatch() + { + return base.ReplaceIfEqualAsync_WithDifferentCasedKeys_ReplacesOnlyExactMatch(); + } + + [Fact] + public override Task ReplaceIfEqualAsync_WithEmptyKey_ThrowsArgumentNullException() + { + return base.ReplaceIfEqualAsync_WithEmptyKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task ReplaceIfEqualAsync_WithExpiration_SetsExpirationCorrectly() + { + return base.ReplaceIfEqualAsync_WithExpiration_SetsExpirationCorrectly(); + } + + [Fact] + public override Task ReplaceIfEqualAsync_WithMatchingOldValue_ReturnsTrueAndReplacesValue() + { + return base.ReplaceIfEqualAsync_WithMatchingOldValue_ReturnsTrueAndReplacesValue(); + } + + [Fact] + public override Task ReplaceIfEqualAsync_WithMismatchedOldValue_ReturnsFalseAndDoesNotReplace() + { + return base.ReplaceIfEqualAsync_WithMismatchedOldValue_ReturnsFalseAndDoesNotReplace(); + } + + [Fact] + public override Task ReplaceIfEqualAsync_WithNullKey_ThrowsArgumentNullException() + { + return base.ReplaceIfEqualAsync_WithNullKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task ReplaceIfEqualAsync_WithWhitespaceKey_ThrowsArgumentException() + { + return base.ReplaceIfEqualAsync_WithWhitespaceKey_ThrowsArgumentException(); + } + + [Fact] + public override Task Serialization_WithComplexObjectsAndValidation_MeasuresThroughput() + { + return base.Serialization_WithComplexObjectsAndValidation_MeasuresThroughput(); + } + + [Fact] + public override Task Serialization_WithSimpleObjectsAndValidation_MeasuresThroughput() + { + return base.Serialization_WithSimpleObjectsAndValidation_MeasuresThroughput(); + } + + [Fact] + public override Task SetAllAsync_WithDateTimeMinValue_DoesNotAddKeys() + { + return base.SetAllAsync_WithDateTimeMinValue_DoesNotAddKeys(); + } + + [Fact] + public override Task SetAllAsync_WithDifferentCasedKeys_CreatesDistinctEntries() + { + return base.SetAllAsync_WithDifferentCasedKeys_CreatesDistinctEntries(); + } + + [Fact] + public override Task SetAllAsync_WithEmptyItems_ReturnsTrue() + { + return base.SetAllAsync_WithEmptyItems_ReturnsTrue(); + } + + [Fact] + public override Task SetAllAsync_WithExpiration_KeysExpireCorrectly() + { + return base.SetAllAsync_WithExpiration_KeysExpireCorrectly(); + } + + [Fact] + public override Task SetAllAsync_WithItemsContainingEmptyKey_ThrowsArgumentException() + { + return base.SetAllAsync_WithItemsContainingEmptyKey_ThrowsArgumentException(); + } + + [Fact] + public override Task SetAllAsync_WithItemsContainingNullKey_ThrowsArgumentException() + { + return base.SetAllAsync_WithItemsContainingNullKey_ThrowsArgumentException(); + } + + [Fact] + public override Task SetAllAsync_WithItemsContainingWhitespaceKey_ThrowsArgumentException() + { + return base.SetAllAsync_WithItemsContainingWhitespaceKey_ThrowsArgumentException(); + } + + [Fact] + public override Task SetAllAsync_WithNullItems_ThrowsArgumentNullException() + { + return base.SetAllAsync_WithNullItems_ThrowsArgumentNullException(); } [Theory] @@ -237,6 +1131,12 @@ public override Task SetAllExpiration_WithLargeNumberOfKeys_SetsAllExpirations(i return base.SetAllExpiration_WithLargeNumberOfKeys_SetsAllExpirations(count); } + [Fact] + public override Task SetAllExpiration_WithMultipleKeys_SetsExpirationForAll() + { + return base.SetAllExpiration_WithMultipleKeys_SetsExpirationForAll(); + } + [Fact] public override Task SetAllExpiration_WithNonExistentKeys_HandlesGracefully() { @@ -244,141 +1144,213 @@ public override Task SetAllExpiration_WithNonExistentKeys_HandlesGracefully() } [Fact] - public override Task CanIncrementAsync() + public override Task SetAllExpiration_WithNullValues_RemovesExpiration() { - return base.CanIncrementAsync(); + return base.SetAllExpiration_WithNullValues_RemovesExpiration(); } [Fact] - public override Task CanIncrementAndExpireAsync() + public override Task SetAsync_WithComplexObject_StoresCorrectly() { - return base.CanIncrementAndExpireAsync(); + return base.SetAsync_WithComplexObject_StoresCorrectly(); } [Fact] - public override Task SetAllShouldExpireAsync() + public override Task SetAsync_WithDifferentCasedKeys_CreatesDistinctEntries() { - return base.SetAllShouldExpireAsync(); + return base.SetAsync_WithDifferentCasedKeys_CreatesDistinctEntries(); } [Fact] - public override Task CanReplaceIfEqual() + public override Task SetAsync_WithDifferentCasedScopes_MaintainsDistinctNamespaces() { - return base.CanReplaceIfEqual(); + return base.SetAsync_WithDifferentCasedScopes_MaintainsDistinctNamespaces(); } [Fact] - public override Task CanRemoveIfEqual() + public override Task SetAsync_WithDifferentScopes_IsolatesKeys() { - return base.CanRemoveIfEqual(); + return base.SetAsync_WithDifferentScopes_IsolatesKeys(); } [Fact] - public override Task CanGetAndSetDateTimeAsync() + public override Task SetAsync_WithEmptyKey_ThrowsArgumentNullException() { - return base.CanGetAndSetDateTimeAsync(); + return base.SetAsync_WithEmptyKey_ThrowsArgumentNullException(); } [Fact] - public override Task CanRoundTripLargeNumbersAsync() + public override Task SetAsync_WithExpirationEdgeCases_HandlesCorrectly() { - return base.CanRoundTripLargeNumbersAsync(); + return base.SetAsync_WithExpirationEdgeCases_HandlesCorrectly(); } [Fact] - public override Task CanRoundTripLargeNumbersWithExpirationAsync() + public override Task SetAsync_WithLargeNumber_StoresCorrectly() { - return base.CanRoundTripLargeNumbersWithExpirationAsync(); + return base.SetAsync_WithLargeNumber_StoresCorrectly(); } [Fact] - public override Task CanManageListsAsync() + public override Task SetAsync_WithLargeNumbersAndExpiration_PreservesValues() { - return base.CanManageListsAsync(); + return base.SetAsync_WithLargeNumbersAndExpiration_PreservesValues(); } [Fact] - public override Task CanManageListsWithNullItemsAsync() + public override Task SetAsync_WithMultipleInstances_UsesLocalCache() { - return base.CanManageListsWithNullItemsAsync(); + return base.SetAsync_WithMultipleInstances_UsesLocalCache(); } [Fact] - public override Task CanManageStringListsAsync() + public override Task SetAsync_WithNestedScopes_PreservesHierarchy() { - return base.CanManageStringListsAsync(); + return base.SetAsync_WithNestedScopes_PreservesHierarchy(); } [Fact] - public override Task CanManageListPagingAsync() + public override Task SetAsync_WithNullKey_ThrowsArgumentNullException() { - return base.CanManageListPagingAsync(); + return base.SetAsync_WithNullKey_ThrowsArgumentNullException(); } [Fact] - public override Task CanManageGetListExpirationAsync() + public override Task SetAsync_WithNullReferenceType_StoresAsNullValue() { - return base.CanManageGetListExpirationAsync(); + return base.SetAsync_WithNullReferenceType_StoresAsNullValue(); } [Fact] - public override Task CanManageListAddExpirationAsync() + public override Task SetAsync_WithNullValueType_StoresAsNullValue() { - return base.CanManageListAddExpirationAsync(); + return base.SetAsync_WithNullValueType_StoresAsNullValue(); } [Fact] - public override Task CanManageListRemoveExpirationAsync() + public override Task SetAsync_WithShortExpiration_ExpiresCorrectly() { - return base.CanManageListRemoveExpirationAsync(); + return base.SetAsync_WithShortExpiration_ExpiresCorrectly(); } - [Fact(Skip = "Performance Test")] - public override Task MeasureThroughputAsync() + [Fact] + public override Task SetAsync_WithWhitespaceKey_ThrowsArgumentException() { - return base.MeasureThroughputAsync(); + return base.SetAsync_WithWhitespaceKey_ThrowsArgumentException(); } - [Fact(Skip = "Performance Test")] - public override Task MeasureSerializerSimpleThroughputAsync() + [Fact] + public override Task SetExpirationAsync_ChangingFromNoExpirationToFutureTime_UpdatesCorrectly() { - return base.MeasureSerializerSimpleThroughputAsync(); + return base.SetExpirationAsync_ChangingFromNoExpirationToFutureTime_UpdatesCorrectly(); } - [Fact(Skip = "Performance Test")] - public override Task MeasureSerializerComplexThroughputAsync() + [Fact] + public override Task SetExpirationAsync_ChangingToDateTimeMinValue_RemovesKey() { - return base.MeasureSerializerComplexThroughputAsync(); + return base.SetExpirationAsync_ChangingToDateTimeMinValue_RemovesKey(); } [Fact] - public override Task CanInvalidateLocalCacheViaRemoveAllAsync() + public override Task SetExpirationAsync_WithCurrentTime_ExpiresImmediately() { - return base.CanInvalidateLocalCacheViaRemoveAllAsync(); + return base.SetExpirationAsync_WithCurrentTime_ExpiresImmediately(); } [Fact] - protected override Task CanInvalidateLocalCacheViaRemoveByPrefixAsync() + public override Task SetExpirationAsync_WithDateTimeMaxValue_NeverExpires() { - return base.CanInvalidateLocalCacheViaRemoveByPrefixAsync(); + return base.SetExpirationAsync_WithDateTimeMaxValue_NeverExpires(); } [Fact] - protected override Task WillUseLocalCache() + public override Task SetExpirationAsync_WithDateTimeMinValue_ExpiresImmediately() { - return base.WillUseLocalCache(); + return base.SetExpirationAsync_WithDateTimeMinValue_ExpiresImmediately(); } - [Fact(Skip = "TODO: Look into this as the local cache client maintenance never schedules for expiration")] - protected override Task WillExpireRemoteItems() + [Fact] + public override Task SetExpirationAsync_WithDifferentCasedKeys_SetsOnlyExactMatch() + { + return base.SetExpirationAsync_WithDifferentCasedKeys_SetsOnlyExactMatch(); + } + + [Fact] + public override Task SetExpirationAsync_WithEmptyKey_ThrowsArgumentNullException() + { + return base.SetExpirationAsync_WithEmptyKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task SetExpirationAsync_WithNullKey_ThrowsArgumentNullException() + { + return base.SetExpirationAsync_WithNullKey_ThrowsArgumentNullException(); + } + + [Fact] + public override Task SetExpirationAsync_WithWhitespaceKey_ThrowsArgumentException() + { + return base.SetExpirationAsync_WithWhitespaceKey_ThrowsArgumentException(); + } + + [Fact] + public override Task SetIfHigherAsync_WithDateTime_DoesNotUpdateWhenLower() + { + return base.SetIfHigherAsync_WithDateTime_DoesNotUpdateWhenLower(); + } + + [Fact] + public override Task SetIfHigherAsync_WithDateTime_InitializesWhenKeyNotExists() + { + return base.SetIfHigherAsync_WithDateTime_InitializesWhenKeyNotExists(); + } + + [Fact] + public override Task SetIfHigherAsync_WithDateTime_UpdatesWhenHigher() + { + return base.SetIfHigherAsync_WithDateTime_UpdatesWhenHigher(); + } + + [Fact] + public override Task SetIfHigherAsync_WithLargeNumbers_UpdatesWhenHigher() + { + return base.SetIfHigherAsync_WithLargeNumbers_UpdatesWhenHigher(); + } + + [Fact] + public override Task SetIfLowerAsync_WithDateTime_DoesNotUpdateWhenHigher() + { + return base.SetIfLowerAsync_WithDateTime_DoesNotUpdateWhenHigher(); + } + + [Fact] + public override Task SetIfLowerAsync_WithDateTime_InitializesWhenKeyNotExists() + { + return base.SetIfLowerAsync_WithDateTime_InitializesWhenKeyNotExists(); + } + + [Fact] + public override Task SetIfLowerAsync_WithDateTime_UpdatesWhenLower() + { + return base.SetIfLowerAsync_WithDateTime_UpdatesWhenLower(); + } + + [Fact] + public override Task SetIfLowerAsync_WithLargeNumbers_UpdatesWhenLower() + { + return base.SetIfLowerAsync_WithLargeNumbers_UpdatesWhenLower(); + } + + [Fact] + public override Task SetUnixTimeMillisecondsAsync_WithLocalDateTime_StoresCorrectly() { - return base.WillExpireRemoteItems(); + return base.SetUnixTimeMillisecondsAsync_WithLocalDateTime_StoresCorrectly(); } [Fact] - protected override Task WillWorkWithSets() + public override Task SetUnixTimeSecondsAsync_WithUtcDateTime_StoresCorrectly() { - return base.WillWorkWithSets(); + return base.SetUnixTimeSecondsAsync_WithUtcDateTime_StoresCorrectly(); } public Task InitializeAsync() From dfea977af43ef873e10333e076a8b5f869fca283 Mon Sep 17 00:00:00 2001 From: Blake Niemyjski Date: Sat, 22 Nov 2025 20:18:42 +1300 Subject: [PATCH 07/26] Updated tests --- .../Caching/RedisCacheClientTests.cs | 376 +++++++---------- .../Caching/RedisHybridCacheClientTests.cs | 379 +++++++----------- .../Caching/ScopedRedisCacheClientTests.cs | 379 +++++++----------- .../ScopedRedisHybridCacheClientTests.cs | 379 +++++++----------- 4 files changed, 540 insertions(+), 973 deletions(-) diff --git a/tests/Foundatio.Redis.Tests/Caching/RedisCacheClientTests.cs b/tests/Foundatio.Redis.Tests/Caching/RedisCacheClientTests.cs index 6ff1501..46d99de 100644 --- a/tests/Foundatio.Redis.Tests/Caching/RedisCacheClientTests.cs +++ b/tests/Foundatio.Redis.Tests/Caching/RedisCacheClientTests.cs @@ -29,15 +29,17 @@ public override Task AddAsync_WithConcurrentRequests_OnlyOneSucceeds() } [Fact] - public override Task AddAsync_WithEmptyKey_ThrowsArgumentNullException() + public override Task AddAsync_WithEmptyKey_ThrowsArgumentException() { - return base.AddAsync_WithEmptyKey_ThrowsArgumentNullException(); + return base.AddAsync_WithEmptyKey_ThrowsArgumentException(); } - [Fact] - public override Task AddAsync_WithExistingKey_ReturnsFalseAndPreservesValue() + [Theory] + [InlineData("user:profile")] + [InlineData(" ")] + public override Task AddAsync_WithExistingKey_ReturnsFalseAndPreservesValue(string cacheKey) { - return base.AddAsync_WithExistingKey_ReturnsFalseAndPreservesValue(); + return base.AddAsync_WithExistingKey_ReturnsFalseAndPreservesValue(cacheKey); } [Fact] @@ -46,10 +48,12 @@ public override Task AddAsync_WithNestedKeyUsingSeparator_StoresCorrectly() return base.AddAsync_WithNestedKeyUsingSeparator_StoresCorrectly(); } - [Fact] - public override Task AddAsync_WithNewKey_ReturnsTrue() + [Theory] + [InlineData("user:profile")] + [InlineData(" ")] + public override Task AddAsync_WithValidKey_ReturnsTrue(string cacheKey) { - return base.AddAsync_WithNewKey_ReturnsTrue(); + return base.AddAsync_WithValidKey_ReturnsTrue(cacheKey); } [Fact] @@ -57,13 +61,6 @@ public override Task AddAsync_WithNullKey_ThrowsArgumentNullException() { return base.AddAsync_WithNullKey_ThrowsArgumentNullException(); } - - [Fact] - public override Task AddAsync_WithWhitespaceKey_ThrowsArgumentException() - { - return base.AddAsync_WithWhitespaceKey_ThrowsArgumentException(); - } - [Fact(Skip = "Performance Test")] public override Task CacheOperations_WithMultipleTypes_MeasuresThroughput() { @@ -89,15 +86,17 @@ public override Task ExistsAsync_WithDifferentCasedKeys_ChecksExactMatch() } [Fact] - public override Task ExistsAsync_WithEmptyKey_ThrowsArgumentNullException() + public override Task ExistsAsync_WithEmptyKey_ThrowsArgumentException() { - return base.ExistsAsync_WithEmptyKey_ThrowsArgumentNullException(); + return base.ExistsAsync_WithEmptyKey_ThrowsArgumentException(); } - [Fact] - public override Task ExistsAsync_WithExistingKey_ReturnsTrue() + [Theory] + [InlineData("user:profile")] + [InlineData(" ")] + public override Task ExistsAsync_WithExistingKey_ReturnsTrue(string cacheKey) { - return base.ExistsAsync_WithExistingKey_ReturnsTrue(); + return base.ExistsAsync_WithExistingKey_ReturnsTrue(cacheKey); } [Fact] @@ -129,23 +128,18 @@ public override Task ExistsAsync_WithScopedCache_ChecksOnlyWithinScope() { return base.ExistsAsync_WithScopedCache_ChecksOnlyWithinScope(); } - - [Fact] - public override Task ExistsAsync_WithWhitespaceKey_ThrowsArgumentException() - { - return base.ExistsAsync_WithWhitespaceKey_ThrowsArgumentException(); - } - [Fact] public override Task GetAllAsync_WithEmptyKeys_ReturnsEmpty() { return base.GetAllAsync_WithEmptyKeys_ReturnsEmpty(); } - [Fact] - public override Task GetAllAsync_WithExistingKeys_ReturnsAllValues() + [Theory] + [InlineData("test2")] + [InlineData(" ")] + public override Task GetAllAsync_WithExistingKeys_ReturnsAllValues(string cacheKey) { - return base.GetAllAsync_WithExistingKeys_ReturnsAllValues(); + return base.GetAllAsync_WithExistingKeys_ReturnsAllValues(cacheKey); } [Fact] @@ -155,15 +149,9 @@ public override Task GetAllAsync_WithKeysContainingEmpty_ThrowsArgumentException } [Fact] - public override Task GetAllAsync_WithKeysContainingNull_ThrowsArgumentException() + public override Task GetAllAsync_WithKeysContainingNull_ThrowsArgumentNullException() { - return base.GetAllAsync_WithKeysContainingNull_ThrowsArgumentException(); - } - - [Fact] - public override Task GetAllAsync_WithKeysContainingWhitespace_ThrowsArgumentException() - { - return base.GetAllAsync_WithKeysContainingWhitespace_ThrowsArgumentException(); + return base.GetAllAsync_WithKeysContainingNull_ThrowsArgumentNullException(); } [Fact] @@ -234,10 +222,12 @@ public override Task GetAsync_WithComplexObject_PreservesAllProperties() return base.GetAsync_WithComplexObject_PreservesAllProperties(); } - [Fact] - public override Task GetAsync_WithComplexObject_ReturnsNewInstance() + [Theory] + [InlineData("order:details")] + [InlineData(" ")] + public override Task GetAsync_WithComplexObject_ReturnsNewInstance(string cacheKey) { - return base.GetAsync_WithComplexObject_ReturnsNewInstance(); + return base.GetAsync_WithComplexObject_ReturnsNewInstance(cacheKey); } [Fact] @@ -247,9 +237,9 @@ public override Task GetAsync_WithDifferentCasedKeys_TreatsAsDifferentKeys() } [Fact] - public override Task GetAsync_WithEmptyKey_ThrowsArgumentNullException() + public override Task GetAsync_WithEmptyKey_ThrowsArgumentException() { - return base.GetAsync_WithEmptyKey_ThrowsArgumentNullException(); + return base.GetAsync_WithEmptyKey_ThrowsArgumentException(); } [Fact] @@ -305,13 +295,6 @@ public override Task GetAsync_WithTryGetSemanticsAndMaxLongAsInt_ReturnsNoValue( { return base.GetAsync_WithTryGetSemanticsAndMaxLongAsInt_ReturnsNoValue(); } - - [Fact] - public override Task GetAsync_WithWhitespaceKey_ThrowsArgumentException() - { - return base.GetAsync_WithWhitespaceKey_ThrowsArgumentException(); - } - [Fact] public override Task GetExpirationAsync_AfterExpiry_ReturnsNull() { @@ -325,15 +308,17 @@ public override Task GetExpirationAsync_WithDifferentCasedKeys_GetsExactMatch() } [Fact] - public override Task GetExpirationAsync_WithEmptyKey_ThrowsArgumentNullException() + public override Task GetExpirationAsync_WithEmptyKey_ThrowsArgumentException() { - return base.GetExpirationAsync_WithEmptyKey_ThrowsArgumentNullException(); + return base.GetExpirationAsync_WithEmptyKey_ThrowsArgumentException(); } - [Fact] - public override Task GetExpirationAsync_WithExpiration_ReturnsCorrectTimeSpan() + [Theory] + [InlineData("token:refresh")] + [InlineData(" ")] + public override Task GetExpirationAsync_WithExpiration_ReturnsCorrectTimeSpan(string cacheKey) { - return base.GetExpirationAsync_WithExpiration_ReturnsCorrectTimeSpan(); + return base.GetExpirationAsync_WithExpiration_ReturnsCorrectTimeSpan(cacheKey); } [Fact] @@ -359,17 +344,10 @@ public override Task GetExpirationAsync_WithNullKey_ThrowsArgumentNullException( { return base.GetExpirationAsync_WithNullKey_ThrowsArgumentNullException(); } - [Fact] - public override Task GetExpirationAsync_WithWhitespaceKey_ThrowsArgumentException() + public override Task GetListAsync_WithEmptyKey_ThrowsArgumentException() { - return base.GetExpirationAsync_WithWhitespaceKey_ThrowsArgumentException(); - } - - [Fact] - public override Task GetListAsync_WithEmptyKey_ThrowsArgumentNullException() - { - return base.GetListAsync_WithEmptyKey_ThrowsArgumentNullException(); + return base.GetListAsync_WithEmptyKey_ThrowsArgumentException(); } [Fact] @@ -408,18 +386,13 @@ public override Task GetListAsync_WithPageBeyondEnd_ReturnsEmptyCollection() return base.GetListAsync_WithPageBeyondEnd_ReturnsEmptyCollection(); } - [Fact] - public override Task GetListAsync_WithPaging_ReturnsCorrectPageSize() - { - return base.GetListAsync_WithPaging_ReturnsCorrectPageSize(); - } - - [Fact] - public override Task GetListAsync_WithWhitespaceKey_ThrowsArgumentException() + [Theory] + [InlineData("cart:items")] + [InlineData(" ")] + public override Task GetListAsync_WithPaging_ReturnsCorrectPageSize(string cacheKey) { - return base.GetListAsync_WithWhitespaceKey_ThrowsArgumentException(); + return base.GetListAsync_WithPaging_ReturnsCorrectPageSize(cacheKey); } - [Fact] public override Task GetUnixTimeMillisecondsAsync_WithLocalDateTime_ReturnsCorrectly() { @@ -445,15 +418,17 @@ public override Task IncrementAsync_WithDifferentCasedKeys_IncrementsDistinctCou } [Fact] - public override Task IncrementAsync_WithEmptyKey_ThrowsArgumentNullException() + public override Task IncrementAsync_WithEmptyKey_ThrowsArgumentException() { - return base.IncrementAsync_WithEmptyKey_ThrowsArgumentNullException(); + return base.IncrementAsync_WithEmptyKey_ThrowsArgumentException(); } - [Fact] - public override Task IncrementAsync_WithExistingKey_IncrementsValue() + [Theory] + [InlineData("metrics:page-views")] + [InlineData(" ")] + public override Task IncrementAsync_WithExistingKey_IncrementsValue(string cacheKey) { - return base.IncrementAsync_WithExistingKey_IncrementsValue(); + return base.IncrementAsync_WithExistingKey_IncrementsValue(cacheKey); } [Fact] @@ -485,23 +460,18 @@ public override Task IncrementAsync_WithSpecifiedAmount_IncrementsCorrectly() { return base.IncrementAsync_WithSpecifiedAmount_IncrementsCorrectly(); } - - [Fact] - public override Task IncrementAsync_WithWhitespaceKey_ThrowsArgumentException() - { - return base.IncrementAsync_WithWhitespaceKey_ThrowsArgumentException(); - } - [Fact] public override Task ListAddAsync_WithDifferentCasedKeys_MaintainsDistinctLists() { return base.ListAddAsync_WithDifferentCasedKeys_MaintainsDistinctLists(); } - [Fact] - public override Task ListAddAsync_WithDuplicates_RemovesDuplicatesAndAddsItems() + [Theory] + [InlineData("cart:items")] + [InlineData(" ")] + public override Task ListAddAsync_WithDuplicates_RemovesDuplicatesAndAddsItems(string cacheKey) { - return base.ListAddAsync_WithDuplicates_RemovesDuplicatesAndAddsItems(); + return base.ListAddAsync_WithDuplicates_RemovesDuplicatesAndAddsItems(cacheKey); } [Fact] @@ -517,9 +487,9 @@ public override Task ListAddAsync_WithEmptyCollection_NoOp() } [Fact] - public override Task ListAddAsync_WithEmptyKey_ThrowsArgumentNullException() + public override Task ListAddAsync_WithEmptyKey_ThrowsArgumentException() { - return base.ListAddAsync_WithEmptyKey_ThrowsArgumentNullException(); + return base.ListAddAsync_WithEmptyKey_ThrowsArgumentException(); } [Fact] @@ -575,23 +545,18 @@ public override Task ListAddAsync_WithSingleString_StoresAsStringNotCharArray() { return base.ListAddAsync_WithSingleString_StoresAsStringNotCharArray(); } - - [Fact] - public override Task ListAddAsync_WithWhitespaceKey_ThrowsArgumentException() - { - return base.ListAddAsync_WithWhitespaceKey_ThrowsArgumentException(); - } - [Fact] - public override Task ListRemoveAsync_WithEmptyKey_ThrowsArgumentNullException() + public override Task ListRemoveAsync_WithEmptyKey_ThrowsArgumentException() { - return base.ListRemoveAsync_WithEmptyKey_ThrowsArgumentNullException(); + return base.ListRemoveAsync_WithEmptyKey_ThrowsArgumentException(); } - [Fact] - public override Task ListRemoveAsync_WithMultipleValues_RemovesAll() + [Theory] + [InlineData("cart:items")] + [InlineData(" ")] + public override Task ListRemoveAsync_WithMultipleValues_RemovesAll(string cacheKey) { - return base.ListRemoveAsync_WithMultipleValues_RemovesAll(); + return base.ListRemoveAsync_WithMultipleValues_RemovesAll(cacheKey); } [Fact] @@ -629,13 +594,6 @@ public override Task ListRemoveAsync_WithValidValues_RemovesKeyWhenEmpty() { return base.ListRemoveAsync_WithValidValues_RemovesKeyWhenEmpty(); } - - [Fact] - public override Task ListRemoveAsync_WithWhitespaceKey_ThrowsArgumentException() - { - return base.ListRemoveAsync_WithWhitespaceKey_ThrowsArgumentException(); - } - [Fact] public override Task RemoveAllAsync_WithEmptyKeys_Succeeds() { @@ -649,15 +607,9 @@ public override Task RemoveAllAsync_WithKeysContainingEmpty_ThrowsArgumentExcept } [Fact] - public override Task RemoveAllAsync_WithKeysContainingNull_ThrowsArgumentException() - { - return base.RemoveAllAsync_WithKeysContainingNull_ThrowsArgumentException(); - } - - [Fact] - public override Task RemoveAllAsync_WithKeysContainingWhitespace_ThrowsArgumentException() + public override Task RemoveAllAsync_WithKeysContainingNull_ThrowsArgumentNullException() { - return base.RemoveAllAsync_WithKeysContainingWhitespace_ThrowsArgumentException(); + return base.RemoveAllAsync_WithKeysContainingNull_ThrowsArgumentNullException(); } [Fact] @@ -673,9 +625,9 @@ public override Task RemoveAllAsync_WithMixedCaseKeys_RemovesOnlyExactMatches() } [Fact] - public override Task RemoveAllAsync_WithNullKeys_ThrowsArgumentNullException() + public override Task RemoveAllAsync_WithNullKeys_RemovesAllValues() { - return base.RemoveAllAsync_WithNullKeys_ThrowsArgumentNullException(); + return base.RemoveAllAsync_WithNullKeys_RemovesAllValues(); } [Fact] @@ -684,10 +636,12 @@ public override Task RemoveAllAsync_WithScopedCache_AffectsOnlyScopedKeys() return base.RemoveAllAsync_WithScopedCache_AffectsOnlyScopedKeys(); } - [Fact] - public override Task RemoveAllAsync_WithSpecificKeyCollection_RemovesOnlySpecifiedKeys() + [Theory] + [InlineData("remove-all-keys:")] + [InlineData(" ")] + public override Task RemoveAllAsync_WithSpecificKeyCollection_RemovesOnlySpecifiedKeys(string keyPrefix) { - return base.RemoveAllAsync_WithSpecificKeyCollection_RemovesOnlySpecifiedKeys(); + return base.RemoveAllAsync_WithSpecificKeyCollection_RemovesOnlySpecifiedKeys(keyPrefix); } [Fact] @@ -703,15 +657,17 @@ public override Task RemoveAsync_MultipleTimes_Succeeds() } [Fact] - public override Task RemoveAsync_WithEmptyKey_ThrowsArgumentNullException() + public override Task RemoveAsync_WithEmptyKey_ThrowsArgumentException() { - return base.RemoveAsync_WithEmptyKey_ThrowsArgumentNullException(); + return base.RemoveAsync_WithEmptyKey_ThrowsArgumentException(); } - [Fact] - public override Task RemoveAsync_WithExistingKey_RemovesSuccessfully() + [Theory] + [InlineData("session:active")] + [InlineData(" ")] + public override Task RemoveAsync_WithExistingKey_RemovesSuccessfully(string cacheKey) { - return base.RemoveAsync_WithExistingKey_RemovesSuccessfully(); + return base.RemoveAsync_WithExistingKey_RemovesSuccessfully(cacheKey); } [Fact] @@ -749,13 +705,6 @@ public override Task RemoveAsync_WithSpecificCase_RemovesOnlyMatchingKey() { return base.RemoveAsync_WithSpecificCase_RemovesOnlyMatchingKey(); } - - [Fact] - public override Task RemoveAsync_WithWhitespaceKey_ThrowsArgumentException() - { - return base.RemoveAsync_WithWhitespaceKey_ThrowsArgumentException(); - } - [Fact] public override Task RemoveByPrefixAsync_AsteriskPrefixWithScopedCache_TreatedAsLiteral() { @@ -763,13 +712,13 @@ public override Task RemoveByPrefixAsync_AsteriskPrefixWithScopedCache_TreatedAs } [Theory] - [InlineData("snowboard", 1, true)] // Exact key match - [InlineData("s", 1, true)] // Partial prefix match - [InlineData(null, 1, false)] // Null prefix (all keys in scope) - [InlineData("", 1, false)] // Empty prefix (all keys in scope) - public override Task RemoveByPrefixAsync_FromScopedCache_RemovesOnlyScopedKeys(string prefixToRemove, int expectedRemovedCount, bool shouldUnscopedRemain) + [InlineData("snowboard", 1)] // Exact key match + [InlineData("s", 1)] // Partial prefix match + [InlineData(null, 1)] // Null prefix (all keys in scope) + [InlineData("", 1)] // Empty prefix (all keys in scope) + public override Task RemoveByPrefixAsync_FromScopedCache_RemovesOnlyScopedKeys(string prefixToRemove, int expectedRemovedCount) { - return base.RemoveByPrefixAsync_FromScopedCache_RemovesOnlyScopedKeys(prefixToRemove, expectedRemovedCount, shouldUnscopedRemain); + return base.RemoveByPrefixAsync_FromScopedCache_RemovesOnlyScopedKeys(prefixToRemove, expectedRemovedCount); } [Theory] @@ -810,12 +759,6 @@ public override Task RemoveByPrefixAsync_WithEmptyPrefix_RemovesAllKeys() return base.RemoveByPrefixAsync_WithEmptyPrefix_RemovesAllKeys(); } - [Fact] - public override Task RemoveByPrefixAsync_WithEmptyPrefix_ThrowsArgumentException() - { - return base.RemoveByPrefixAsync_WithEmptyPrefix_ThrowsArgumentException(); - } - [Theory] [MemberData(nameof(GetLineEndingPrefixes))] public override Task RemoveByPrefixAsync_WithLineEndingPrefix_TreatsAsLiteral(string lineEndingPrefix) @@ -823,10 +766,12 @@ public override Task RemoveByPrefixAsync_WithLineEndingPrefix_TreatsAsLiteral(st return base.RemoveByPrefixAsync_WithLineEndingPrefix_TreatsAsLiteral(lineEndingPrefix); } - [Fact] - public override Task RemoveByPrefixAsync_WithMatchingPrefix_RemovesOnlyPrefixedKeys() + [Theory] + [InlineData("blah:")] + [InlineData(" ")] + public override Task RemoveByPrefixAsync_WithMatchingPrefix_RemovesOnlyPrefixedKeys(string prefix) { - return base.RemoveByPrefixAsync_WithMatchingPrefix_RemovesOnlyPrefixedKeys(); + return base.RemoveByPrefixAsync_WithMatchingPrefix_RemovesOnlyPrefixedKeys(prefix); } [Theory] @@ -851,12 +796,6 @@ public override Task RemoveByPrefixAsync_WithNullPrefix_RemovesAllKeys() return base.RemoveByPrefixAsync_WithNullPrefix_RemovesAllKeys(); } - [Fact] - public override Task RemoveByPrefixAsync_WithNullPrefix_ThrowsArgumentNullException() - { - return base.RemoveByPrefixAsync_WithNullPrefix_ThrowsArgumentNullException(); - } - [Theory] [MemberData(nameof(GetRegexSpecialCharacters))] public override Task RemoveByPrefixAsync_WithRegexMetacharacter_TreatsAsLiteral(string specialChar) @@ -877,19 +816,6 @@ public override Task RemoveByPrefixAsync_WithSpecialCharacterPrefix_TreatsAsLite return base.RemoveByPrefixAsync_WithSpecialCharacterPrefix_TreatsAsLiteral(specialPrefix); } - [Fact] - public override Task RemoveByPrefixAsync_WithWhitespacePrefix_ThrowsArgumentException() - { - return base.RemoveByPrefixAsync_WithWhitespacePrefix_ThrowsArgumentException(); - } - - [Theory] - [MemberData(nameof(GetWhitespaceOnlyPrefixes))] - public override Task RemoveByPrefixAsync_WithWhitespacePrefix_TreatsAsLiteral(string whitespacePrefix) - { - return base.RemoveByPrefixAsync_WithWhitespacePrefix_TreatsAsLiteral(whitespacePrefix); - } - [Theory] [MemberData(nameof(GetWildcardPatterns))] public override Task RemoveByPrefixAsync_WithWildcardPattern_TreatsAsLiteral(string pattern) @@ -898,15 +824,17 @@ public override Task RemoveByPrefixAsync_WithWildcardPattern_TreatsAsLiteral(str } [Fact] - public override Task RemoveIfEqualAsync_WithEmptyKey_ThrowsArgumentNullException() + public override Task RemoveIfEqualAsync_WithEmptyKey_ThrowsArgumentException() { - return base.RemoveIfEqualAsync_WithEmptyKey_ThrowsArgumentNullException(); + return base.RemoveIfEqualAsync_WithEmptyKey_ThrowsArgumentException(); } - [Fact] - public override Task RemoveIfEqualAsync_WithMatchingValue_ReturnsTrueAndRemoves() + [Theory] + [InlineData("session:active")] + [InlineData(" ")] + public override Task RemoveIfEqualAsync_WithMatchingValue_ReturnsTrueAndRemoves(string cacheKey) { - return base.RemoveIfEqualAsync_WithMatchingValue_ReturnsTrueAndRemoves(); + return base.RemoveIfEqualAsync_WithMatchingValue_ReturnsTrueAndRemoves(cacheKey); } [Fact] @@ -921,12 +849,6 @@ public override Task RemoveIfEqualAsync_WithNullKey_ThrowsArgumentNullException( return base.RemoveIfEqualAsync_WithNullKey_ThrowsArgumentNullException(); } - [Fact] - public override Task RemoveIfEqualAsync_WithWhitespaceKey_ThrowsArgumentException() - { - return base.RemoveIfEqualAsync_WithWhitespaceKey_ThrowsArgumentException(); - } - [Fact] public override Task ReplaceAsync_WithDifferentCasedKeys_TreatsAsDifferentKeys() { @@ -934,15 +856,17 @@ public override Task ReplaceAsync_WithDifferentCasedKeys_TreatsAsDifferentKeys() } [Fact] - public override Task ReplaceAsync_WithEmptyKey_ThrowsArgumentNullException() + public override Task ReplaceAsync_WithEmptyKey_ThrowsArgumentException() { - return base.ReplaceAsync_WithEmptyKey_ThrowsArgumentNullException(); + return base.ReplaceAsync_WithEmptyKey_ThrowsArgumentException(); } - [Fact] - public override Task ReplaceAsync_WithExistingKey_ReturnsTrueAndReplacesValue() + [Theory] + [InlineData("settings:theme")] + [InlineData(" ")] + public override Task ReplaceAsync_WithExistingKey_ReturnsTrueAndReplacesValue(string cacheKey) { - return base.ReplaceAsync_WithExistingKey_ReturnsTrueAndReplacesValue(); + return base.ReplaceAsync_WithExistingKey_ReturnsTrueAndReplacesValue(cacheKey); } [Fact] @@ -963,12 +887,6 @@ public override Task ReplaceAsync_WithNullKey_ThrowsArgumentNullException() return base.ReplaceAsync_WithNullKey_ThrowsArgumentNullException(); } - [Fact] - public override Task ReplaceAsync_WithWhitespaceKey_ThrowsArgumentException() - { - return base.ReplaceAsync_WithWhitespaceKey_ThrowsArgumentException(); - } - [Fact] public override Task ReplaceIfEqualAsync_WithDifferentCasedKeys_ReplacesOnlyExactMatch() { @@ -976,9 +894,9 @@ public override Task ReplaceIfEqualAsync_WithDifferentCasedKeys_ReplacesOnlyExac } [Fact] - public override Task ReplaceIfEqualAsync_WithEmptyKey_ThrowsArgumentNullException() + public override Task ReplaceIfEqualAsync_WithEmptyKey_ThrowsArgumentException() { - return base.ReplaceIfEqualAsync_WithEmptyKey_ThrowsArgumentNullException(); + return base.ReplaceIfEqualAsync_WithEmptyKey_ThrowsArgumentException(); } [Fact] @@ -987,10 +905,12 @@ public override Task ReplaceIfEqualAsync_WithExpiration_SetsExpirationCorrectly( return base.ReplaceIfEqualAsync_WithExpiration_SetsExpirationCorrectly(); } - [Fact] - public override Task ReplaceIfEqualAsync_WithMatchingOldValue_ReturnsTrueAndReplacesValue() + [Theory] + [InlineData("workflow:state")] + [InlineData(" ")] + public override Task ReplaceIfEqualAsync_WithMatchingOldValue_ReturnsTrueAndReplacesValue(string cacheKey) { - return base.ReplaceIfEqualAsync_WithMatchingOldValue_ReturnsTrueAndReplacesValue(); + return base.ReplaceIfEqualAsync_WithMatchingOldValue_ReturnsTrueAndReplacesValue(cacheKey); } [Fact] @@ -1005,12 +925,6 @@ public override Task ReplaceIfEqualAsync_WithNullKey_ThrowsArgumentNullException return base.ReplaceIfEqualAsync_WithNullKey_ThrowsArgumentNullException(); } - [Fact] - public override Task ReplaceIfEqualAsync_WithWhitespaceKey_ThrowsArgumentException() - { - return base.ReplaceIfEqualAsync_WithWhitespaceKey_ThrowsArgumentException(); - } - [Fact(Skip = "Performance Test")] public override Task Serialization_WithComplexObjectsAndValidation_MeasuresThroughput() { @@ -1041,10 +955,12 @@ public override Task SetAllAsync_WithEmptyItems_ReturnsTrue() return base.SetAllAsync_WithEmptyItems_ReturnsTrue(); } - [Fact] - public override Task SetAllAsync_WithExpiration_KeysExpireCorrectly() + [Theory] + [InlineData("test")] + [InlineData(" ")] + public override Task SetAllAsync_WithExpiration_KeysExpireCorrectly(string cacheKey) { - return base.SetAllAsync_WithExpiration_KeysExpireCorrectly(); + return base.SetAllAsync_WithExpiration_KeysExpireCorrectly(cacheKey); } [Fact] @@ -1053,18 +969,6 @@ public override Task SetAllAsync_WithItemsContainingEmptyKey_ThrowsArgumentExcep return base.SetAllAsync_WithItemsContainingEmptyKey_ThrowsArgumentException(); } - [Fact] - public override Task SetAllAsync_WithItemsContainingNullKey_ThrowsArgumentException() - { - return base.SetAllAsync_WithItemsContainingNullKey_ThrowsArgumentException(); - } - - [Fact] - public override Task SetAllAsync_WithItemsContainingWhitespaceKey_ThrowsArgumentException() - { - return base.SetAllAsync_WithItemsContainingWhitespaceKey_ThrowsArgumentException(); - } - [Fact] public override Task SetAllAsync_WithNullItems_ThrowsArgumentNullException() { @@ -1097,10 +1001,12 @@ public override Task SetAllExpiration_WithNullValues_RemovesExpiration() return base.SetAllExpiration_WithNullValues_RemovesExpiration(); } - [Fact] - public override Task SetAsync_WithComplexObject_StoresCorrectly() + [Theory] + [InlineData("user:profile")] + [InlineData(" ")] + public override Task SetAsync_WithComplexObject_StoresCorrectly(string cacheKey) { - return base.SetAsync_WithComplexObject_StoresCorrectly(); + return base.SetAsync_WithComplexObject_StoresCorrectly(cacheKey); } [Fact] @@ -1122,9 +1028,9 @@ public override Task SetAsync_WithDifferentScopes_IsolatesKeys() } [Fact] - public override Task SetAsync_WithEmptyKey_ThrowsArgumentNullException() + public override Task SetAsync_WithEmptyKey_ThrowsArgumentException() { - return base.SetAsync_WithEmptyKey_ThrowsArgumentNullException(); + return base.SetAsync_WithEmptyKey_ThrowsArgumentException(); } [Fact] @@ -1174,17 +1080,12 @@ public override Task SetAsync_WithShortExpiration_ExpiresCorrectly() { return base.SetAsync_WithShortExpiration_ExpiresCorrectly(); } - - [Fact] - public override Task SetAsync_WithWhitespaceKey_ThrowsArgumentException() - { - return base.SetAsync_WithWhitespaceKey_ThrowsArgumentException(); - } - - [Fact] - public override Task SetExpirationAsync_ChangingFromNoExpirationToFutureTime_UpdatesCorrectly() + [Theory] + [InlineData("token:refresh")] + [InlineData(" ")] + public override Task SetExpirationAsync_ChangingFromNoExpirationToFutureTime_UpdatesCorrectly(string cacheKey) { - return base.SetExpirationAsync_ChangingFromNoExpirationToFutureTime_UpdatesCorrectly(); + return base.SetExpirationAsync_ChangingFromNoExpirationToFutureTime_UpdatesCorrectly(cacheKey); } [Fact] @@ -1218,9 +1119,9 @@ public override Task SetExpirationAsync_WithDifferentCasedKeys_SetsOnlyExactMatc } [Fact] - public override Task SetExpirationAsync_WithEmptyKey_ThrowsArgumentNullException() + public override Task SetExpirationAsync_WithEmptyKey_ThrowsArgumentException() { - return base.SetExpirationAsync_WithEmptyKey_ThrowsArgumentNullException(); + return base.SetExpirationAsync_WithEmptyKey_ThrowsArgumentException(); } [Fact] @@ -1228,13 +1129,6 @@ public override Task SetExpirationAsync_WithNullKey_ThrowsArgumentNullException( { return base.SetExpirationAsync_WithNullKey_ThrowsArgumentNullException(); } - - [Fact] - public override Task SetExpirationAsync_WithWhitespaceKey_ThrowsArgumentException() - { - return base.SetExpirationAsync_WithWhitespaceKey_ThrowsArgumentException(); - } - [Fact] public override Task SetIfHigherAsync_WithDateTime_DoesNotUpdateWhenLower() { diff --git a/tests/Foundatio.Redis.Tests/Caching/RedisHybridCacheClientTests.cs b/tests/Foundatio.Redis.Tests/Caching/RedisHybridCacheClientTests.cs index 7d72988..a204f1f 100644 --- a/tests/Foundatio.Redis.Tests/Caching/RedisHybridCacheClientTests.cs +++ b/tests/Foundatio.Redis.Tests/Caching/RedisHybridCacheClientTests.cs @@ -32,15 +32,17 @@ public override Task AddAsync_WithConcurrentRequests_OnlyOneSucceeds() } [Fact] - public override Task AddAsync_WithEmptyKey_ThrowsArgumentNullException() + public override Task AddAsync_WithEmptyKey_ThrowsArgumentException() { - return base.AddAsync_WithEmptyKey_ThrowsArgumentNullException(); + return base.AddAsync_WithEmptyKey_ThrowsArgumentException(); } - [Fact] - public override Task AddAsync_WithExistingKey_ReturnsFalseAndPreservesValue() + [Theory] + [InlineData("user:profile")] + [InlineData(" ")] + public override Task AddAsync_WithExistingKey_ReturnsFalseAndPreservesValue(string cacheKey) { - return base.AddAsync_WithExistingKey_ReturnsFalseAndPreservesValue(); + return base.AddAsync_WithExistingKey_ReturnsFalseAndPreservesValue(cacheKey); } [Fact(Skip = "TODO: Look into this as the local cache client maintenance never schedules for expiration")] @@ -55,10 +57,12 @@ public override Task AddAsync_WithNestedKeyUsingSeparator_StoresCorrectly() return base.AddAsync_WithNestedKeyUsingSeparator_StoresCorrectly(); } - [Fact] - public override Task AddAsync_WithNewKey_ReturnsTrue() + [Theory] + [InlineData("user:profile")] + [InlineData(" ")] + public override Task AddAsync_WithValidKey_ReturnsTrue(string cacheKey) { - return base.AddAsync_WithNewKey_ReturnsTrue(); + return base.AddAsync_WithValidKey_ReturnsTrue(cacheKey); } [Fact] @@ -66,13 +70,6 @@ public override Task AddAsync_WithNullKey_ThrowsArgumentNullException() { return base.AddAsync_WithNullKey_ThrowsArgumentNullException(); } - - [Fact] - public override Task AddAsync_WithWhitespaceKey_ThrowsArgumentException() - { - return base.AddAsync_WithWhitespaceKey_ThrowsArgumentException(); - } - [Fact] public override Task CacheOperations_WithMultipleTypes_MeasuresThroughput() { @@ -98,15 +95,17 @@ public override Task ExistsAsync_WithDifferentCasedKeys_ChecksExactMatch() } [Fact] - public override Task ExistsAsync_WithEmptyKey_ThrowsArgumentNullException() + public override Task ExistsAsync_WithEmptyKey_ThrowsArgumentException() { - return base.ExistsAsync_WithEmptyKey_ThrowsArgumentNullException(); + return base.ExistsAsync_WithEmptyKey_ThrowsArgumentException(); } - [Fact] - public override Task ExistsAsync_WithExistingKey_ReturnsTrue() + [Theory] + [InlineData("user:profile")] + [InlineData(" ")] + public override Task ExistsAsync_WithExistingKey_ReturnsTrue(string cacheKey) { - return base.ExistsAsync_WithExistingKey_ReturnsTrue(); + return base.ExistsAsync_WithExistingKey_ReturnsTrue(cacheKey); } [Fact] @@ -144,23 +143,18 @@ public override Task ExistsAsync_WithScopedCache_ChecksOnlyWithinScope() { return base.ExistsAsync_WithScopedCache_ChecksOnlyWithinScope(); } - - [Fact] - public override Task ExistsAsync_WithWhitespaceKey_ThrowsArgumentException() - { - return base.ExistsAsync_WithWhitespaceKey_ThrowsArgumentException(); - } - [Fact] public override Task GetAllAsync_WithEmptyKeys_ReturnsEmpty() { return base.GetAllAsync_WithEmptyKeys_ReturnsEmpty(); } - [Fact] - public override Task GetAllAsync_WithExistingKeys_ReturnsAllValues() + [Theory] + [InlineData("test2")] + [InlineData(" ")] + public override Task GetAllAsync_WithExistingKeys_ReturnsAllValues(string cacheKey) { - return base.GetAllAsync_WithExistingKeys_ReturnsAllValues(); + return base.GetAllAsync_WithExistingKeys_ReturnsAllValues(cacheKey); } [Fact] @@ -170,15 +164,9 @@ public override Task GetAllAsync_WithKeysContainingEmpty_ThrowsArgumentException } [Fact] - public override Task GetAllAsync_WithKeysContainingNull_ThrowsArgumentException() + public override Task GetAllAsync_WithKeysContainingNull_ThrowsArgumentNullException() { - return base.GetAllAsync_WithKeysContainingNull_ThrowsArgumentException(); - } - - [Fact] - public override Task GetAllAsync_WithKeysContainingWhitespace_ThrowsArgumentException() - { - return base.GetAllAsync_WithKeysContainingWhitespace_ThrowsArgumentException(); + return base.GetAllAsync_WithKeysContainingNull_ThrowsArgumentNullException(); } [Fact] @@ -255,10 +243,12 @@ public override Task GetAsync_WithComplexObject_PreservesAllProperties() return base.GetAsync_WithComplexObject_PreservesAllProperties(); } - [Fact] - public override Task GetAsync_WithComplexObject_ReturnsNewInstance() + [Theory] + [InlineData("order:details")] + [InlineData(" ")] + public override Task GetAsync_WithComplexObject_ReturnsNewInstance(string cacheKey) { - return base.GetAsync_WithComplexObject_ReturnsNewInstance(); + return base.GetAsync_WithComplexObject_ReturnsNewInstance(cacheKey); } [Fact] @@ -268,9 +258,9 @@ public override Task GetAsync_WithDifferentCasedKeys_TreatsAsDifferentKeys() } [Fact] - public override Task GetAsync_WithEmptyKey_ThrowsArgumentNullException() + public override Task GetAsync_WithEmptyKey_ThrowsArgumentException() { - return base.GetAsync_WithEmptyKey_ThrowsArgumentNullException(); + return base.GetAsync_WithEmptyKey_ThrowsArgumentException(); } [Fact] @@ -326,13 +316,6 @@ public override Task GetAsync_WithTryGetSemanticsAndMaxLongAsInt_ReturnsNoValue( { return base.GetAsync_WithTryGetSemanticsAndMaxLongAsInt_ReturnsNoValue(); } - - [Fact] - public override Task GetAsync_WithWhitespaceKey_ThrowsArgumentException() - { - return base.GetAsync_WithWhitespaceKey_ThrowsArgumentException(); - } - [Fact] public override Task GetExpirationAsync_AfterExpiry_ReturnsNull() { @@ -346,15 +329,17 @@ public override Task GetExpirationAsync_WithDifferentCasedKeys_GetsExactMatch() } [Fact] - public override Task GetExpirationAsync_WithEmptyKey_ThrowsArgumentNullException() + public override Task GetExpirationAsync_WithEmptyKey_ThrowsArgumentException() { - return base.GetExpirationAsync_WithEmptyKey_ThrowsArgumentNullException(); + return base.GetExpirationAsync_WithEmptyKey_ThrowsArgumentException(); } - [Fact] - public override Task GetExpirationAsync_WithExpiration_ReturnsCorrectTimeSpan() + [Theory] + [InlineData("token:refresh")] + [InlineData(" ")] + public override Task GetExpirationAsync_WithExpiration_ReturnsCorrectTimeSpan(string cacheKey) { - return base.GetExpirationAsync_WithExpiration_ReturnsCorrectTimeSpan(); + return base.GetExpirationAsync_WithExpiration_ReturnsCorrectTimeSpan(cacheKey); } [Fact] @@ -386,17 +371,10 @@ public override Task GetExpirationAsync_WithNullKey_ThrowsArgumentNullException( { return base.GetExpirationAsync_WithNullKey_ThrowsArgumentNullException(); } - - [Fact] - public override Task GetExpirationAsync_WithWhitespaceKey_ThrowsArgumentException() - { - return base.GetExpirationAsync_WithWhitespaceKey_ThrowsArgumentException(); - } - [Fact] - public override Task GetListAsync_WithEmptyKey_ThrowsArgumentNullException() + public override Task GetListAsync_WithEmptyKey_ThrowsArgumentException() { - return base.GetListAsync_WithEmptyKey_ThrowsArgumentNullException(); + return base.GetListAsync_WithEmptyKey_ThrowsArgumentException(); } [Fact] @@ -435,18 +413,13 @@ public override Task GetListAsync_WithPageBeyondEnd_ReturnsEmptyCollection() return base.GetListAsync_WithPageBeyondEnd_ReturnsEmptyCollection(); } - [Fact] - public override Task GetListAsync_WithPaging_ReturnsCorrectPageSize() - { - return base.GetListAsync_WithPaging_ReturnsCorrectPageSize(); - } - - [Fact] - public override Task GetListAsync_WithWhitespaceKey_ThrowsArgumentException() + [Theory] + [InlineData("cart:items")] + [InlineData(" ")] + public override Task GetListAsync_WithPaging_ReturnsCorrectPageSize(string cacheKey) { - return base.GetListAsync_WithWhitespaceKey_ThrowsArgumentException(); + return base.GetListAsync_WithPaging_ReturnsCorrectPageSize(cacheKey); } - [Fact] public override Task GetUnixTimeMillisecondsAsync_WithLocalDateTime_ReturnsCorrectly() { @@ -472,15 +445,17 @@ public override Task IncrementAsync_WithDifferentCasedKeys_IncrementsDistinctCou } [Fact] - public override Task IncrementAsync_WithEmptyKey_ThrowsArgumentNullException() + public override Task IncrementAsync_WithEmptyKey_ThrowsArgumentException() { - return base.IncrementAsync_WithEmptyKey_ThrowsArgumentNullException(); + return base.IncrementAsync_WithEmptyKey_ThrowsArgumentException(); } - [Fact] - public override Task IncrementAsync_WithExistingKey_IncrementsValue() + [Theory] + [InlineData("metrics:page-views")] + [InlineData(" ")] + public override Task IncrementAsync_WithExistingKey_IncrementsValue(string cacheKey) { - return base.IncrementAsync_WithExistingKey_IncrementsValue(); + return base.IncrementAsync_WithExistingKey_IncrementsValue(cacheKey); } [Fact] @@ -512,23 +487,18 @@ public override Task IncrementAsync_WithSpecifiedAmount_IncrementsCorrectly() { return base.IncrementAsync_WithSpecifiedAmount_IncrementsCorrectly(); } - - [Fact] - public override Task IncrementAsync_WithWhitespaceKey_ThrowsArgumentException() - { - return base.IncrementAsync_WithWhitespaceKey_ThrowsArgumentException(); - } - [Fact] public override Task ListAddAsync_WithDifferentCasedKeys_MaintainsDistinctLists() { return base.ListAddAsync_WithDifferentCasedKeys_MaintainsDistinctLists(); } - [Fact] - public override Task ListAddAsync_WithDuplicates_RemovesDuplicatesAndAddsItems() + [Theory] + [InlineData("cart:items")] + [InlineData(" ")] + public override Task ListAddAsync_WithDuplicates_RemovesDuplicatesAndAddsItems(string cacheKey) { - return base.ListAddAsync_WithDuplicates_RemovesDuplicatesAndAddsItems(); + return base.ListAddAsync_WithDuplicates_RemovesDuplicatesAndAddsItems(cacheKey); } [Fact] @@ -544,9 +514,9 @@ public override Task ListAddAsync_WithEmptyCollection_NoOp() } [Fact] - public override Task ListAddAsync_WithEmptyKey_ThrowsArgumentNullException() + public override Task ListAddAsync_WithEmptyKey_ThrowsArgumentException() { - return base.ListAddAsync_WithEmptyKey_ThrowsArgumentNullException(); + return base.ListAddAsync_WithEmptyKey_ThrowsArgumentException(); } [Fact] @@ -608,23 +578,18 @@ public override Task ListAddAsync_WithSingleString_StoresAsStringNotCharArray() { return base.ListAddAsync_WithSingleString_StoresAsStringNotCharArray(); } - [Fact] - public override Task ListAddAsync_WithWhitespaceKey_ThrowsArgumentException() + public override Task ListRemoveAsync_WithEmptyKey_ThrowsArgumentException() { - return base.ListAddAsync_WithWhitespaceKey_ThrowsArgumentException(); + return base.ListRemoveAsync_WithEmptyKey_ThrowsArgumentException(); } - [Fact] - public override Task ListRemoveAsync_WithEmptyKey_ThrowsArgumentNullException() - { - return base.ListRemoveAsync_WithEmptyKey_ThrowsArgumentNullException(); - } - - [Fact] - public override Task ListRemoveAsync_WithMultipleValues_RemovesAll() + [Theory] + [InlineData("cart:items")] + [InlineData(" ")] + public override Task ListRemoveAsync_WithMultipleValues_RemovesAll(string cacheKey) { - return base.ListRemoveAsync_WithMultipleValues_RemovesAll(); + return base.ListRemoveAsync_WithMultipleValues_RemovesAll(cacheKey); } [Fact] @@ -662,13 +627,6 @@ public override Task ListRemoveAsync_WithValidValues_RemovesKeyWhenEmpty() { return base.ListRemoveAsync_WithValidValues_RemovesKeyWhenEmpty(); } - - [Fact] - public override Task ListRemoveAsync_WithWhitespaceKey_ThrowsArgumentException() - { - return base.ListRemoveAsync_WithWhitespaceKey_ThrowsArgumentException(); - } - [Fact] public override Task RemoveAllAsync_WithEmptyKeys_Succeeds() { @@ -682,15 +640,9 @@ public override Task RemoveAllAsync_WithKeysContainingEmpty_ThrowsArgumentExcept } [Fact] - public override Task RemoveAllAsync_WithKeysContainingNull_ThrowsArgumentException() - { - return base.RemoveAllAsync_WithKeysContainingNull_ThrowsArgumentException(); - } - - [Fact] - public override Task RemoveAllAsync_WithKeysContainingWhitespace_ThrowsArgumentException() + public override Task RemoveAllAsync_WithKeysContainingNull_ThrowsArgumentNullException() { - return base.RemoveAllAsync_WithKeysContainingWhitespace_ThrowsArgumentException(); + return base.RemoveAllAsync_WithKeysContainingNull_ThrowsArgumentNullException(); } [Fact] @@ -712,9 +664,9 @@ public override Task RemoveAllAsync_WithMixedCaseKeys_RemovesOnlyExactMatches() } [Fact] - public override Task RemoveAllAsync_WithNullKeys_ThrowsArgumentNullException() + public override Task RemoveAllAsync_WithNullKeys_RemovesAllValues() { - return base.RemoveAllAsync_WithNullKeys_ThrowsArgumentNullException(); + return base.RemoveAllAsync_WithNullKeys_RemovesAllValues(); } [Fact] @@ -723,10 +675,12 @@ public override Task RemoveAllAsync_WithScopedCache_AffectsOnlyScopedKeys() return base.RemoveAllAsync_WithScopedCache_AffectsOnlyScopedKeys(); } - [Fact] - public override Task RemoveAllAsync_WithSpecificKeyCollection_RemovesOnlySpecifiedKeys() + [Theory] + [InlineData("remove-all-keys:")] + [InlineData(" ")] + public override Task RemoveAllAsync_WithSpecificKeyCollection_RemovesOnlySpecifiedKeys(string keyPrefix) { - return base.RemoveAllAsync_WithSpecificKeyCollection_RemovesOnlySpecifiedKeys(); + return base.RemoveAllAsync_WithSpecificKeyCollection_RemovesOnlySpecifiedKeys(keyPrefix); } [Fact] @@ -742,15 +696,17 @@ public override Task RemoveAsync_MultipleTimes_Succeeds() } [Fact] - public override Task RemoveAsync_WithEmptyKey_ThrowsArgumentNullException() + public override Task RemoveAsync_WithEmptyKey_ThrowsArgumentException() { - return base.RemoveAsync_WithEmptyKey_ThrowsArgumentNullException(); + return base.RemoveAsync_WithEmptyKey_ThrowsArgumentException(); } - [Fact] - public override Task RemoveAsync_WithExistingKey_RemovesSuccessfully() + [Theory] + [InlineData("session:active")] + [InlineData(" ")] + public override Task RemoveAsync_WithExistingKey_RemovesSuccessfully(string cacheKey) { - return base.RemoveAsync_WithExistingKey_RemovesSuccessfully(); + return base.RemoveAsync_WithExistingKey_RemovesSuccessfully(cacheKey); } [Fact] @@ -788,13 +744,6 @@ public override Task RemoveAsync_WithSpecificCase_RemovesOnlyMatchingKey() { return base.RemoveAsync_WithSpecificCase_RemovesOnlyMatchingKey(); } - - [Fact] - public override Task RemoveAsync_WithWhitespaceKey_ThrowsArgumentException() - { - return base.RemoveAsync_WithWhitespaceKey_ThrowsArgumentException(); - } - [Fact] public override Task RemoveByPrefixAsync_AsteriskPrefixWithScopedCache_TreatedAsLiteral() { @@ -802,13 +751,13 @@ public override Task RemoveByPrefixAsync_AsteriskPrefixWithScopedCache_TreatedAs } [Theory] - [InlineData("snowboard", 1, true)] // Exact key match - [InlineData("s", 1, true)] // Partial prefix match - [InlineData(null, 1, false)] // Null prefix (all keys in scope) - [InlineData("", 1, false)] // Empty prefix (all keys in scope) - public override Task RemoveByPrefixAsync_FromScopedCache_RemovesOnlyScopedKeys(string prefixToRemove, int expectedRemovedCount, bool shouldUnscopedRemain) + [InlineData("snowboard", 1)] // Exact key match + [InlineData("s", 1)] // Partial prefix match + [InlineData(null, 1)] // Null prefix (all keys in scope) + [InlineData("", 1)] // Empty prefix (all keys in scope) + public override Task RemoveByPrefixAsync_FromScopedCache_RemovesOnlyScopedKeys(string prefixToRemove, int expectedRemovedCount) { - return base.RemoveByPrefixAsync_FromScopedCache_RemovesOnlyScopedKeys(prefixToRemove, expectedRemovedCount, shouldUnscopedRemain); + return base.RemoveByPrefixAsync_FromScopedCache_RemovesOnlyScopedKeys(prefixToRemove, expectedRemovedCount); } [Theory] @@ -849,12 +798,6 @@ public override Task RemoveByPrefixAsync_WithEmptyPrefix_RemovesAllKeys() return base.RemoveByPrefixAsync_WithEmptyPrefix_RemovesAllKeys(); } - [Fact] - public override Task RemoveByPrefixAsync_WithEmptyPrefix_ThrowsArgumentException() - { - return base.RemoveByPrefixAsync_WithEmptyPrefix_ThrowsArgumentException(); - } - [Theory] [MemberData(nameof(GetLineEndingPrefixes))] public override Task RemoveByPrefixAsync_WithLineEndingPrefix_TreatsAsLiteral(string lineEndingPrefix) @@ -868,10 +811,12 @@ public override Task RemoveByPrefixAsync_WithLocalCache_InvalidatesLocalCache() return base.RemoveByPrefixAsync_WithLocalCache_InvalidatesLocalCache(); } - [Fact] - public override Task RemoveByPrefixAsync_WithMatchingPrefix_RemovesOnlyPrefixedKeys() + [Theory] + [InlineData("blah:")] + [InlineData(" ")] + public override Task RemoveByPrefixAsync_WithMatchingPrefix_RemovesOnlyPrefixedKeys(string prefix) { - return base.RemoveByPrefixAsync_WithMatchingPrefix_RemovesOnlyPrefixedKeys(); + return base.RemoveByPrefixAsync_WithMatchingPrefix_RemovesOnlyPrefixedKeys(prefix); } [Theory] @@ -896,12 +841,6 @@ public override Task RemoveByPrefixAsync_WithNullPrefix_RemovesAllKeys() return base.RemoveByPrefixAsync_WithNullPrefix_RemovesAllKeys(); } - [Fact] - public override Task RemoveByPrefixAsync_WithNullPrefix_ThrowsArgumentNullException() - { - return base.RemoveByPrefixAsync_WithNullPrefix_ThrowsArgumentNullException(); - } - [Theory] [MemberData(nameof(GetRegexSpecialCharacters))] public override Task RemoveByPrefixAsync_WithRegexMetacharacter_TreatsAsLiteral(string specialChar) @@ -922,19 +861,6 @@ public override Task RemoveByPrefixAsync_WithSpecialCharacterPrefix_TreatsAsLite return base.RemoveByPrefixAsync_WithSpecialCharacterPrefix_TreatsAsLiteral(specialPrefix); } - [Fact] - public override Task RemoveByPrefixAsync_WithWhitespacePrefix_ThrowsArgumentException() - { - return base.RemoveByPrefixAsync_WithWhitespacePrefix_ThrowsArgumentException(); - } - - [Theory] - [MemberData(nameof(GetWhitespaceOnlyPrefixes))] - public override Task RemoveByPrefixAsync_WithWhitespacePrefix_TreatsAsLiteral(string whitespacePrefix) - { - return base.RemoveByPrefixAsync_WithWhitespacePrefix_TreatsAsLiteral(whitespacePrefix); - } - [Theory] [MemberData(nameof(GetWildcardPatterns))] public override Task RemoveByPrefixAsync_WithWildcardPattern_TreatsAsLiteral(string pattern) @@ -943,15 +869,17 @@ public override Task RemoveByPrefixAsync_WithWildcardPattern_TreatsAsLiteral(str } [Fact] - public override Task RemoveIfEqualAsync_WithEmptyKey_ThrowsArgumentNullException() + public override Task RemoveIfEqualAsync_WithEmptyKey_ThrowsArgumentException() { - return base.RemoveIfEqualAsync_WithEmptyKey_ThrowsArgumentNullException(); + return base.RemoveIfEqualAsync_WithEmptyKey_ThrowsArgumentException(); } - [Fact] - public override Task RemoveIfEqualAsync_WithMatchingValue_ReturnsTrueAndRemoves() + [Theory] + [InlineData("session:active")] + [InlineData(" ")] + public override Task RemoveIfEqualAsync_WithMatchingValue_ReturnsTrueAndRemoves(string cacheKey) { - return base.RemoveIfEqualAsync_WithMatchingValue_ReturnsTrueAndRemoves(); + return base.RemoveIfEqualAsync_WithMatchingValue_ReturnsTrueAndRemoves(cacheKey); } [Fact] @@ -965,13 +893,6 @@ public override Task RemoveIfEqualAsync_WithNullKey_ThrowsArgumentNullException( { return base.RemoveIfEqualAsync_WithNullKey_ThrowsArgumentNullException(); } - - [Fact] - public override Task RemoveIfEqualAsync_WithWhitespaceKey_ThrowsArgumentException() - { - return base.RemoveIfEqualAsync_WithWhitespaceKey_ThrowsArgumentException(); - } - [Fact] public override Task ReplaceAsync_WithDifferentCasedKeys_TreatsAsDifferentKeys() { @@ -979,15 +900,17 @@ public override Task ReplaceAsync_WithDifferentCasedKeys_TreatsAsDifferentKeys() } [Fact] - public override Task ReplaceAsync_WithEmptyKey_ThrowsArgumentNullException() + public override Task ReplaceAsync_WithEmptyKey_ThrowsArgumentException() { - return base.ReplaceAsync_WithEmptyKey_ThrowsArgumentNullException(); + return base.ReplaceAsync_WithEmptyKey_ThrowsArgumentException(); } - [Fact] - public override Task ReplaceAsync_WithExistingKey_ReturnsTrueAndReplacesValue() + [Theory] + [InlineData("settings:theme")] + [InlineData(" ")] + public override Task ReplaceAsync_WithExistingKey_ReturnsTrueAndReplacesValue(string cacheKey) { - return base.ReplaceAsync_WithExistingKey_ReturnsTrueAndReplacesValue(); + return base.ReplaceAsync_WithExistingKey_ReturnsTrueAndReplacesValue(cacheKey); } [Fact] @@ -1007,13 +930,6 @@ public override Task ReplaceAsync_WithNullKey_ThrowsArgumentNullException() { return base.ReplaceAsync_WithNullKey_ThrowsArgumentNullException(); } - - [Fact] - public override Task ReplaceAsync_WithWhitespaceKey_ThrowsArgumentException() - { - return base.ReplaceAsync_WithWhitespaceKey_ThrowsArgumentException(); - } - [Fact] public override Task ReplaceIfEqualAsync_WithDifferentCasedKeys_ReplacesOnlyExactMatch() { @@ -1021,9 +937,9 @@ public override Task ReplaceIfEqualAsync_WithDifferentCasedKeys_ReplacesOnlyExac } [Fact] - public override Task ReplaceIfEqualAsync_WithEmptyKey_ThrowsArgumentNullException() + public override Task ReplaceIfEqualAsync_WithEmptyKey_ThrowsArgumentException() { - return base.ReplaceIfEqualAsync_WithEmptyKey_ThrowsArgumentNullException(); + return base.ReplaceIfEqualAsync_WithEmptyKey_ThrowsArgumentException(); } [Fact] @@ -1032,10 +948,12 @@ public override Task ReplaceIfEqualAsync_WithExpiration_SetsExpirationCorrectly( return base.ReplaceIfEqualAsync_WithExpiration_SetsExpirationCorrectly(); } - [Fact] - public override Task ReplaceIfEqualAsync_WithMatchingOldValue_ReturnsTrueAndReplacesValue() + [Theory] + [InlineData("workflow:state")] + [InlineData(" ")] + public override Task ReplaceIfEqualAsync_WithMatchingOldValue_ReturnsTrueAndReplacesValue(string cacheKey) { - return base.ReplaceIfEqualAsync_WithMatchingOldValue_ReturnsTrueAndReplacesValue(); + return base.ReplaceIfEqualAsync_WithMatchingOldValue_ReturnsTrueAndReplacesValue(cacheKey); } [Fact] @@ -1049,13 +967,6 @@ public override Task ReplaceIfEqualAsync_WithNullKey_ThrowsArgumentNullException { return base.ReplaceIfEqualAsync_WithNullKey_ThrowsArgumentNullException(); } - - [Fact] - public override Task ReplaceIfEqualAsync_WithWhitespaceKey_ThrowsArgumentException() - { - return base.ReplaceIfEqualAsync_WithWhitespaceKey_ThrowsArgumentException(); - } - [Fact] public override Task Serialization_WithComplexObjectsAndValidation_MeasuresThroughput() { @@ -1086,10 +997,12 @@ public override Task SetAllAsync_WithEmptyItems_ReturnsTrue() return base.SetAllAsync_WithEmptyItems_ReturnsTrue(); } - [Fact] - public override Task SetAllAsync_WithExpiration_KeysExpireCorrectly() + [Theory] + [InlineData("test")] + [InlineData(" ")] + public override Task SetAllAsync_WithExpiration_KeysExpireCorrectly(string cacheKey) { - return base.SetAllAsync_WithExpiration_KeysExpireCorrectly(); + return base.SetAllAsync_WithExpiration_KeysExpireCorrectly(cacheKey); } [Fact] @@ -1098,18 +1011,6 @@ public override Task SetAllAsync_WithItemsContainingEmptyKey_ThrowsArgumentExcep return base.SetAllAsync_WithItemsContainingEmptyKey_ThrowsArgumentException(); } - [Fact] - public override Task SetAllAsync_WithItemsContainingNullKey_ThrowsArgumentException() - { - return base.SetAllAsync_WithItemsContainingNullKey_ThrowsArgumentException(); - } - - [Fact] - public override Task SetAllAsync_WithItemsContainingWhitespaceKey_ThrowsArgumentException() - { - return base.SetAllAsync_WithItemsContainingWhitespaceKey_ThrowsArgumentException(); - } - [Fact] public override Task SetAllAsync_WithNullItems_ThrowsArgumentNullException() { @@ -1142,10 +1043,12 @@ public override Task SetAllExpiration_WithNullValues_RemovesExpiration() return base.SetAllExpiration_WithNullValues_RemovesExpiration(); } - [Fact] - public override Task SetAsync_WithComplexObject_StoresCorrectly() + [Theory] + [InlineData("user:profile")] + [InlineData(" ")] + public override Task SetAsync_WithComplexObject_StoresCorrectly(string cacheKey) { - return base.SetAsync_WithComplexObject_StoresCorrectly(); + return base.SetAsync_WithComplexObject_StoresCorrectly(cacheKey); } [Fact] @@ -1167,9 +1070,9 @@ public override Task SetAsync_WithDifferentScopes_IsolatesKeys() } [Fact] - public override Task SetAsync_WithEmptyKey_ThrowsArgumentNullException() + public override Task SetAsync_WithEmptyKey_ThrowsArgumentException() { - return base.SetAsync_WithEmptyKey_ThrowsArgumentNullException(); + return base.SetAsync_WithEmptyKey_ThrowsArgumentException(); } [Fact] @@ -1225,17 +1128,12 @@ public override Task SetAsync_WithShortExpiration_ExpiresCorrectly() { return base.SetAsync_WithShortExpiration_ExpiresCorrectly(); } - - [Fact] - public override Task SetAsync_WithWhitespaceKey_ThrowsArgumentException() - { - return base.SetAsync_WithWhitespaceKey_ThrowsArgumentException(); - } - - [Fact] - public override Task SetExpirationAsync_ChangingFromNoExpirationToFutureTime_UpdatesCorrectly() + [Theory] + [InlineData("token:refresh")] + [InlineData(" ")] + public override Task SetExpirationAsync_ChangingFromNoExpirationToFutureTime_UpdatesCorrectly(string cacheKey) { - return base.SetExpirationAsync_ChangingFromNoExpirationToFutureTime_UpdatesCorrectly(); + return base.SetExpirationAsync_ChangingFromNoExpirationToFutureTime_UpdatesCorrectly(cacheKey); } [Fact] @@ -1269,9 +1167,9 @@ public override Task SetExpirationAsync_WithDifferentCasedKeys_SetsOnlyExactMatc } [Fact] - public override Task SetExpirationAsync_WithEmptyKey_ThrowsArgumentNullException() + public override Task SetExpirationAsync_WithEmptyKey_ThrowsArgumentException() { - return base.SetExpirationAsync_WithEmptyKey_ThrowsArgumentNullException(); + return base.SetExpirationAsync_WithEmptyKey_ThrowsArgumentException(); } [Fact] @@ -1279,13 +1177,6 @@ public override Task SetExpirationAsync_WithNullKey_ThrowsArgumentNullException( { return base.SetExpirationAsync_WithNullKey_ThrowsArgumentNullException(); } - - [Fact] - public override Task SetExpirationAsync_WithWhitespaceKey_ThrowsArgumentException() - { - return base.SetExpirationAsync_WithWhitespaceKey_ThrowsArgumentException(); - } - [Fact] public override Task SetIfHigherAsync_WithDateTime_DoesNotUpdateWhenLower() { diff --git a/tests/Foundatio.Redis.Tests/Caching/ScopedRedisCacheClientTests.cs b/tests/Foundatio.Redis.Tests/Caching/ScopedRedisCacheClientTests.cs index 44da326..910bcbd 100644 --- a/tests/Foundatio.Redis.Tests/Caching/ScopedRedisCacheClientTests.cs +++ b/tests/Foundatio.Redis.Tests/Caching/ScopedRedisCacheClientTests.cs @@ -26,15 +26,17 @@ public override Task AddAsync_WithConcurrentRequests_OnlyOneSucceeds() } [Fact] - public override Task AddAsync_WithEmptyKey_ThrowsArgumentNullException() + public override Task AddAsync_WithEmptyKey_ThrowsArgumentException() { - return base.AddAsync_WithEmptyKey_ThrowsArgumentNullException(); + return base.AddAsync_WithEmptyKey_ThrowsArgumentException(); } - [Fact] - public override Task AddAsync_WithExistingKey_ReturnsFalseAndPreservesValue() + [Theory] + [InlineData("user:profile")] + [InlineData(" ")] + public override Task AddAsync_WithExistingKey_ReturnsFalseAndPreservesValue(string cacheKey) { - return base.AddAsync_WithExistingKey_ReturnsFalseAndPreservesValue(); + return base.AddAsync_WithExistingKey_ReturnsFalseAndPreservesValue(cacheKey); } [Fact] @@ -43,10 +45,12 @@ public override Task AddAsync_WithNestedKeyUsingSeparator_StoresCorrectly() return base.AddAsync_WithNestedKeyUsingSeparator_StoresCorrectly(); } - [Fact] - public override Task AddAsync_WithNewKey_ReturnsTrue() + [Theory] + [InlineData("user:profile")] + [InlineData(" ")] + public override Task AddAsync_WithValidKey_ReturnsTrue(string cacheKey) { - return base.AddAsync_WithNewKey_ReturnsTrue(); + return base.AddAsync_WithValidKey_ReturnsTrue(cacheKey); } [Fact] @@ -54,13 +58,6 @@ public override Task AddAsync_WithNullKey_ThrowsArgumentNullException() { return base.AddAsync_WithNullKey_ThrowsArgumentNullException(); } - - [Fact] - public override Task AddAsync_WithWhitespaceKey_ThrowsArgumentException() - { - return base.AddAsync_WithWhitespaceKey_ThrowsArgumentException(); - } - [Fact(Skip = "Performance Test")] public override Task CacheOperations_WithMultipleTypes_MeasuresThroughput() { @@ -86,15 +83,17 @@ public override Task ExistsAsync_WithDifferentCasedKeys_ChecksExactMatch() } [Fact] - public override Task ExistsAsync_WithEmptyKey_ThrowsArgumentNullException() + public override Task ExistsAsync_WithEmptyKey_ThrowsArgumentException() { - return base.ExistsAsync_WithEmptyKey_ThrowsArgumentNullException(); + return base.ExistsAsync_WithEmptyKey_ThrowsArgumentException(); } - [Fact] - public override Task ExistsAsync_WithExistingKey_ReturnsTrue() + [Theory] + [InlineData("user:profile")] + [InlineData(" ")] + public override Task ExistsAsync_WithExistingKey_ReturnsTrue(string cacheKey) { - return base.ExistsAsync_WithExistingKey_ReturnsTrue(); + return base.ExistsAsync_WithExistingKey_ReturnsTrue(cacheKey); } [Fact] @@ -126,23 +125,18 @@ public override Task ExistsAsync_WithScopedCache_ChecksOnlyWithinScope() { return base.ExistsAsync_WithScopedCache_ChecksOnlyWithinScope(); } - - [Fact] - public override Task ExistsAsync_WithWhitespaceKey_ThrowsArgumentException() - { - return base.ExistsAsync_WithWhitespaceKey_ThrowsArgumentException(); - } - [Fact] public override Task GetAllAsync_WithEmptyKeys_ReturnsEmpty() { return base.GetAllAsync_WithEmptyKeys_ReturnsEmpty(); } - [Fact] - public override Task GetAllAsync_WithExistingKeys_ReturnsAllValues() + [Theory] + [InlineData("test2")] + [InlineData(" ")] + public override Task GetAllAsync_WithExistingKeys_ReturnsAllValues(string cacheKey) { - return base.GetAllAsync_WithExistingKeys_ReturnsAllValues(); + return base.GetAllAsync_WithExistingKeys_ReturnsAllValues(cacheKey); } [Fact] @@ -152,15 +146,9 @@ public override Task GetAllAsync_WithKeysContainingEmpty_ThrowsArgumentException } [Fact] - public override Task GetAllAsync_WithKeysContainingNull_ThrowsArgumentException() + public override Task GetAllAsync_WithKeysContainingNull_ThrowsArgumentNullException() { - return base.GetAllAsync_WithKeysContainingNull_ThrowsArgumentException(); - } - - [Fact] - public override Task GetAllAsync_WithKeysContainingWhitespace_ThrowsArgumentException() - { - return base.GetAllAsync_WithKeysContainingWhitespace_ThrowsArgumentException(); + return base.GetAllAsync_WithKeysContainingNull_ThrowsArgumentNullException(); } [Fact] @@ -231,10 +219,12 @@ public override Task GetAsync_WithComplexObject_PreservesAllProperties() return base.GetAsync_WithComplexObject_PreservesAllProperties(); } - [Fact] - public override Task GetAsync_WithComplexObject_ReturnsNewInstance() + [Theory] + [InlineData("order:details")] + [InlineData(" ")] + public override Task GetAsync_WithComplexObject_ReturnsNewInstance(string cacheKey) { - return base.GetAsync_WithComplexObject_ReturnsNewInstance(); + return base.GetAsync_WithComplexObject_ReturnsNewInstance(cacheKey); } [Fact] @@ -244,9 +234,9 @@ public override Task GetAsync_WithDifferentCasedKeys_TreatsAsDifferentKeys() } [Fact] - public override Task GetAsync_WithEmptyKey_ThrowsArgumentNullException() + public override Task GetAsync_WithEmptyKey_ThrowsArgumentException() { - return base.GetAsync_WithEmptyKey_ThrowsArgumentNullException(); + return base.GetAsync_WithEmptyKey_ThrowsArgumentException(); } [Fact] @@ -302,13 +292,6 @@ public override Task GetAsync_WithTryGetSemanticsAndMaxLongAsInt_ReturnsNoValue( { return base.GetAsync_WithTryGetSemanticsAndMaxLongAsInt_ReturnsNoValue(); } - - [Fact] - public override Task GetAsync_WithWhitespaceKey_ThrowsArgumentException() - { - return base.GetAsync_WithWhitespaceKey_ThrowsArgumentException(); - } - [Fact] public override Task GetExpirationAsync_AfterExpiry_ReturnsNull() { @@ -322,15 +305,17 @@ public override Task GetExpirationAsync_WithDifferentCasedKeys_GetsExactMatch() } [Fact] - public override Task GetExpirationAsync_WithEmptyKey_ThrowsArgumentNullException() + public override Task GetExpirationAsync_WithEmptyKey_ThrowsArgumentException() { - return base.GetExpirationAsync_WithEmptyKey_ThrowsArgumentNullException(); + return base.GetExpirationAsync_WithEmptyKey_ThrowsArgumentException(); } - [Fact] - public override Task GetExpirationAsync_WithExpiration_ReturnsCorrectTimeSpan() + [Theory] + [InlineData("token:refresh")] + [InlineData(" ")] + public override Task GetExpirationAsync_WithExpiration_ReturnsCorrectTimeSpan(string cacheKey) { - return base.GetExpirationAsync_WithExpiration_ReturnsCorrectTimeSpan(); + return base.GetExpirationAsync_WithExpiration_ReturnsCorrectTimeSpan(cacheKey); } [Fact] @@ -356,17 +341,10 @@ public override Task GetExpirationAsync_WithNullKey_ThrowsArgumentNullException( { return base.GetExpirationAsync_WithNullKey_ThrowsArgumentNullException(); } - - [Fact] - public override Task GetExpirationAsync_WithWhitespaceKey_ThrowsArgumentException() - { - return base.GetExpirationAsync_WithWhitespaceKey_ThrowsArgumentException(); - } - [Fact] - public override Task GetListAsync_WithEmptyKey_ThrowsArgumentNullException() + public override Task GetListAsync_WithEmptyKey_ThrowsArgumentException() { - return base.GetListAsync_WithEmptyKey_ThrowsArgumentNullException(); + return base.GetListAsync_WithEmptyKey_ThrowsArgumentException(); } [Fact] @@ -405,18 +383,13 @@ public override Task GetListAsync_WithPageBeyondEnd_ReturnsEmptyCollection() return base.GetListAsync_WithPageBeyondEnd_ReturnsEmptyCollection(); } - [Fact] - public override Task GetListAsync_WithPaging_ReturnsCorrectPageSize() - { - return base.GetListAsync_WithPaging_ReturnsCorrectPageSize(); - } - - [Fact] - public override Task GetListAsync_WithWhitespaceKey_ThrowsArgumentException() + [Theory] + [InlineData("cart:items")] + [InlineData(" ")] + public override Task GetListAsync_WithPaging_ReturnsCorrectPageSize(string cacheKey) { - return base.GetListAsync_WithWhitespaceKey_ThrowsArgumentException(); + return base.GetListAsync_WithPaging_ReturnsCorrectPageSize(cacheKey); } - [Fact] public override Task GetUnixTimeMillisecondsAsync_WithLocalDateTime_ReturnsCorrectly() { @@ -442,15 +415,17 @@ public override Task IncrementAsync_WithDifferentCasedKeys_IncrementsDistinctCou } [Fact] - public override Task IncrementAsync_WithEmptyKey_ThrowsArgumentNullException() + public override Task IncrementAsync_WithEmptyKey_ThrowsArgumentException() { - return base.IncrementAsync_WithEmptyKey_ThrowsArgumentNullException(); + return base.IncrementAsync_WithEmptyKey_ThrowsArgumentException(); } - [Fact] - public override Task IncrementAsync_WithExistingKey_IncrementsValue() + [Theory] + [InlineData("metrics:page-views")] + [InlineData(" ")] + public override Task IncrementAsync_WithExistingKey_IncrementsValue(string cacheKey) { - return base.IncrementAsync_WithExistingKey_IncrementsValue(); + return base.IncrementAsync_WithExistingKey_IncrementsValue(cacheKey); } [Fact] @@ -482,23 +457,18 @@ public override Task IncrementAsync_WithSpecifiedAmount_IncrementsCorrectly() { return base.IncrementAsync_WithSpecifiedAmount_IncrementsCorrectly(); } - - [Fact] - public override Task IncrementAsync_WithWhitespaceKey_ThrowsArgumentException() - { - return base.IncrementAsync_WithWhitespaceKey_ThrowsArgumentException(); - } - [Fact] public override Task ListAddAsync_WithDifferentCasedKeys_MaintainsDistinctLists() { return base.ListAddAsync_WithDifferentCasedKeys_MaintainsDistinctLists(); } - [Fact] - public override Task ListAddAsync_WithDuplicates_RemovesDuplicatesAndAddsItems() + [Theory] + [InlineData("cart:items")] + [InlineData(" ")] + public override Task ListAddAsync_WithDuplicates_RemovesDuplicatesAndAddsItems(string cacheKey) { - return base.ListAddAsync_WithDuplicates_RemovesDuplicatesAndAddsItems(); + return base.ListAddAsync_WithDuplicates_RemovesDuplicatesAndAddsItems(cacheKey); } [Fact] @@ -514,9 +484,9 @@ public override Task ListAddAsync_WithEmptyCollection_NoOp() } [Fact] - public override Task ListAddAsync_WithEmptyKey_ThrowsArgumentNullException() + public override Task ListAddAsync_WithEmptyKey_ThrowsArgumentException() { - return base.ListAddAsync_WithEmptyKey_ThrowsArgumentNullException(); + return base.ListAddAsync_WithEmptyKey_ThrowsArgumentException(); } [Fact] @@ -572,23 +542,18 @@ public override Task ListAddAsync_WithSingleString_StoresAsStringNotCharArray() { return base.ListAddAsync_WithSingleString_StoresAsStringNotCharArray(); } - [Fact] - public override Task ListAddAsync_WithWhitespaceKey_ThrowsArgumentException() + public override Task ListRemoveAsync_WithEmptyKey_ThrowsArgumentException() { - return base.ListAddAsync_WithWhitespaceKey_ThrowsArgumentException(); + return base.ListRemoveAsync_WithEmptyKey_ThrowsArgumentException(); } - [Fact] - public override Task ListRemoveAsync_WithEmptyKey_ThrowsArgumentNullException() - { - return base.ListRemoveAsync_WithEmptyKey_ThrowsArgumentNullException(); - } - - [Fact] - public override Task ListRemoveAsync_WithMultipleValues_RemovesAll() + [Theory] + [InlineData("cart:items")] + [InlineData(" ")] + public override Task ListRemoveAsync_WithMultipleValues_RemovesAll(string cacheKey) { - return base.ListRemoveAsync_WithMultipleValues_RemovesAll(); + return base.ListRemoveAsync_WithMultipleValues_RemovesAll(cacheKey); } [Fact] @@ -626,13 +591,6 @@ public override Task ListRemoveAsync_WithValidValues_RemovesKeyWhenEmpty() { return base.ListRemoveAsync_WithValidValues_RemovesKeyWhenEmpty(); } - - [Fact] - public override Task ListRemoveAsync_WithWhitespaceKey_ThrowsArgumentException() - { - return base.ListRemoveAsync_WithWhitespaceKey_ThrowsArgumentException(); - } - [Fact] public override Task RemoveAllAsync_WithEmptyKeys_Succeeds() { @@ -646,15 +604,9 @@ public override Task RemoveAllAsync_WithKeysContainingEmpty_ThrowsArgumentExcept } [Fact] - public override Task RemoveAllAsync_WithKeysContainingNull_ThrowsArgumentException() - { - return base.RemoveAllAsync_WithKeysContainingNull_ThrowsArgumentException(); - } - - [Fact] - public override Task RemoveAllAsync_WithKeysContainingWhitespace_ThrowsArgumentException() + public override Task RemoveAllAsync_WithKeysContainingNull_ThrowsArgumentNullException() { - return base.RemoveAllAsync_WithKeysContainingWhitespace_ThrowsArgumentException(); + return base.RemoveAllAsync_WithKeysContainingNull_ThrowsArgumentNullException(); } [Fact] @@ -670,9 +622,9 @@ public override Task RemoveAllAsync_WithMixedCaseKeys_RemovesOnlyExactMatches() } [Fact] - public override Task RemoveAllAsync_WithNullKeys_ThrowsArgumentNullException() + public override Task RemoveAllAsync_WithNullKeys_RemovesAllValues() { - return base.RemoveAllAsync_WithNullKeys_ThrowsArgumentNullException(); + return base.RemoveAllAsync_WithNullKeys_RemovesAllValues(); } [Fact] @@ -681,10 +633,12 @@ public override Task RemoveAllAsync_WithScopedCache_AffectsOnlyScopedKeys() return base.RemoveAllAsync_WithScopedCache_AffectsOnlyScopedKeys(); } - [Fact] - public override Task RemoveAllAsync_WithSpecificKeyCollection_RemovesOnlySpecifiedKeys() + [Theory] + [InlineData("remove-all-keys:")] + [InlineData(" ")] + public override Task RemoveAllAsync_WithSpecificKeyCollection_RemovesOnlySpecifiedKeys(string keyPrefix) { - return base.RemoveAllAsync_WithSpecificKeyCollection_RemovesOnlySpecifiedKeys(); + return base.RemoveAllAsync_WithSpecificKeyCollection_RemovesOnlySpecifiedKeys(keyPrefix); } [Fact] @@ -700,15 +654,17 @@ public override Task RemoveAsync_MultipleTimes_Succeeds() } [Fact] - public override Task RemoveAsync_WithEmptyKey_ThrowsArgumentNullException() + public override Task RemoveAsync_WithEmptyKey_ThrowsArgumentException() { - return base.RemoveAsync_WithEmptyKey_ThrowsArgumentNullException(); + return base.RemoveAsync_WithEmptyKey_ThrowsArgumentException(); } - [Fact] - public override Task RemoveAsync_WithExistingKey_RemovesSuccessfully() + [Theory] + [InlineData("session:active")] + [InlineData(" ")] + public override Task RemoveAsync_WithExistingKey_RemovesSuccessfully(string cacheKey) { - return base.RemoveAsync_WithExistingKey_RemovesSuccessfully(); + return base.RemoveAsync_WithExistingKey_RemovesSuccessfully(cacheKey); } [Fact] @@ -746,13 +702,6 @@ public override Task RemoveAsync_WithSpecificCase_RemovesOnlyMatchingKey() { return base.RemoveAsync_WithSpecificCase_RemovesOnlyMatchingKey(); } - - [Fact] - public override Task RemoveAsync_WithWhitespaceKey_ThrowsArgumentException() - { - return base.RemoveAsync_WithWhitespaceKey_ThrowsArgumentException(); - } - [Fact] public override Task RemoveByPrefixAsync_AsteriskPrefixWithScopedCache_TreatedAsLiteral() { @@ -760,13 +709,13 @@ public override Task RemoveByPrefixAsync_AsteriskPrefixWithScopedCache_TreatedAs } [Theory] - [InlineData("snowboard", 1, true)] // Exact key match - [InlineData("s", 1, true)] // Partial prefix match - [InlineData(null, 1, false)] // Null prefix (all keys in scope) - [InlineData("", 1, false)] // Empty prefix (all keys in scope) - public override Task RemoveByPrefixAsync_FromScopedCache_RemovesOnlyScopedKeys(string prefixToRemove, int expectedRemovedCount, bool shouldUnscopedRemain) + [InlineData("snowboard", 1)] // Exact key match + [InlineData("s", 1)] // Partial prefix match + [InlineData(null, 1)] // Null prefix (all keys in scope) + [InlineData("", 1)] // Empty prefix (all keys in scope) + public override Task RemoveByPrefixAsync_FromScopedCache_RemovesOnlyScopedKeys(string prefixToRemove, int expectedRemovedCount) { - return base.RemoveByPrefixAsync_FromScopedCache_RemovesOnlyScopedKeys(prefixToRemove, expectedRemovedCount, shouldUnscopedRemain); + return base.RemoveByPrefixAsync_FromScopedCache_RemovesOnlyScopedKeys(prefixToRemove, expectedRemovedCount); } [Theory] @@ -807,12 +756,6 @@ public override Task RemoveByPrefixAsync_WithEmptyPrefix_RemovesAllKeys() return base.RemoveByPrefixAsync_WithEmptyPrefix_RemovesAllKeys(); } - [Fact] - public override Task RemoveByPrefixAsync_WithEmptyPrefix_ThrowsArgumentException() - { - return base.RemoveByPrefixAsync_WithEmptyPrefix_ThrowsArgumentException(); - } - [Theory] [MemberData(nameof(GetLineEndingPrefixes))] public override Task RemoveByPrefixAsync_WithLineEndingPrefix_TreatsAsLiteral(string lineEndingPrefix) @@ -820,10 +763,12 @@ public override Task RemoveByPrefixAsync_WithLineEndingPrefix_TreatsAsLiteral(st return base.RemoveByPrefixAsync_WithLineEndingPrefix_TreatsAsLiteral(lineEndingPrefix); } - [Fact] - public override Task RemoveByPrefixAsync_WithMatchingPrefix_RemovesOnlyPrefixedKeys() + [Theory] + [InlineData("blah:")] + [InlineData(" ")] + public override Task RemoveByPrefixAsync_WithMatchingPrefix_RemovesOnlyPrefixedKeys(string prefix) { - return base.RemoveByPrefixAsync_WithMatchingPrefix_RemovesOnlyPrefixedKeys(); + return base.RemoveByPrefixAsync_WithMatchingPrefix_RemovesOnlyPrefixedKeys(prefix); } [Theory] @@ -848,12 +793,6 @@ public override Task RemoveByPrefixAsync_WithNullPrefix_RemovesAllKeys() return base.RemoveByPrefixAsync_WithNullPrefix_RemovesAllKeys(); } - [Fact] - public override Task RemoveByPrefixAsync_WithNullPrefix_ThrowsArgumentNullException() - { - return base.RemoveByPrefixAsync_WithNullPrefix_ThrowsArgumentNullException(); - } - [Theory] [MemberData(nameof(GetRegexSpecialCharacters))] public override Task RemoveByPrefixAsync_WithRegexMetacharacter_TreatsAsLiteral(string specialChar) @@ -874,19 +813,6 @@ public override Task RemoveByPrefixAsync_WithSpecialCharacterPrefix_TreatsAsLite return base.RemoveByPrefixAsync_WithSpecialCharacterPrefix_TreatsAsLiteral(specialPrefix); } - [Fact] - public override Task RemoveByPrefixAsync_WithWhitespacePrefix_ThrowsArgumentException() - { - return base.RemoveByPrefixAsync_WithWhitespacePrefix_ThrowsArgumentException(); - } - - [Theory] - [MemberData(nameof(GetWhitespaceOnlyPrefixes))] - public override Task RemoveByPrefixAsync_WithWhitespacePrefix_TreatsAsLiteral(string whitespacePrefix) - { - return base.RemoveByPrefixAsync_WithWhitespacePrefix_TreatsAsLiteral(whitespacePrefix); - } - [Theory] [MemberData(nameof(GetWildcardPatterns))] public override Task RemoveByPrefixAsync_WithWildcardPattern_TreatsAsLiteral(string pattern) @@ -895,15 +821,17 @@ public override Task RemoveByPrefixAsync_WithWildcardPattern_TreatsAsLiteral(str } [Fact] - public override Task RemoveIfEqualAsync_WithEmptyKey_ThrowsArgumentNullException() + public override Task RemoveIfEqualAsync_WithEmptyKey_ThrowsArgumentException() { - return base.RemoveIfEqualAsync_WithEmptyKey_ThrowsArgumentNullException(); + return base.RemoveIfEqualAsync_WithEmptyKey_ThrowsArgumentException(); } - [Fact] - public override Task RemoveIfEqualAsync_WithMatchingValue_ReturnsTrueAndRemoves() + [Theory] + [InlineData("session:active")] + [InlineData(" ")] + public override Task RemoveIfEqualAsync_WithMatchingValue_ReturnsTrueAndRemoves(string cacheKey) { - return base.RemoveIfEqualAsync_WithMatchingValue_ReturnsTrueAndRemoves(); + return base.RemoveIfEqualAsync_WithMatchingValue_ReturnsTrueAndRemoves(cacheKey); } [Fact] @@ -917,13 +845,6 @@ public override Task RemoveIfEqualAsync_WithNullKey_ThrowsArgumentNullException( { return base.RemoveIfEqualAsync_WithNullKey_ThrowsArgumentNullException(); } - - [Fact] - public override Task RemoveIfEqualAsync_WithWhitespaceKey_ThrowsArgumentException() - { - return base.RemoveIfEqualAsync_WithWhitespaceKey_ThrowsArgumentException(); - } - [Fact] public override Task ReplaceAsync_WithDifferentCasedKeys_TreatsAsDifferentKeys() { @@ -931,15 +852,17 @@ public override Task ReplaceAsync_WithDifferentCasedKeys_TreatsAsDifferentKeys() } [Fact] - public override Task ReplaceAsync_WithEmptyKey_ThrowsArgumentNullException() + public override Task ReplaceAsync_WithEmptyKey_ThrowsArgumentException() { - return base.ReplaceAsync_WithEmptyKey_ThrowsArgumentNullException(); + return base.ReplaceAsync_WithEmptyKey_ThrowsArgumentException(); } - [Fact] - public override Task ReplaceAsync_WithExistingKey_ReturnsTrueAndReplacesValue() + [Theory] + [InlineData("settings:theme")] + [InlineData(" ")] + public override Task ReplaceAsync_WithExistingKey_ReturnsTrueAndReplacesValue(string cacheKey) { - return base.ReplaceAsync_WithExistingKey_ReturnsTrueAndReplacesValue(); + return base.ReplaceAsync_WithExistingKey_ReturnsTrueAndReplacesValue(cacheKey); } [Fact] @@ -959,13 +882,6 @@ public override Task ReplaceAsync_WithNullKey_ThrowsArgumentNullException() { return base.ReplaceAsync_WithNullKey_ThrowsArgumentNullException(); } - - [Fact] - public override Task ReplaceAsync_WithWhitespaceKey_ThrowsArgumentException() - { - return base.ReplaceAsync_WithWhitespaceKey_ThrowsArgumentException(); - } - [Fact] public override Task ReplaceIfEqualAsync_WithDifferentCasedKeys_ReplacesOnlyExactMatch() { @@ -973,9 +889,9 @@ public override Task ReplaceIfEqualAsync_WithDifferentCasedKeys_ReplacesOnlyExac } [Fact] - public override Task ReplaceIfEqualAsync_WithEmptyKey_ThrowsArgumentNullException() + public override Task ReplaceIfEqualAsync_WithEmptyKey_ThrowsArgumentException() { - return base.ReplaceIfEqualAsync_WithEmptyKey_ThrowsArgumentNullException(); + return base.ReplaceIfEqualAsync_WithEmptyKey_ThrowsArgumentException(); } [Fact] @@ -984,10 +900,12 @@ public override Task ReplaceIfEqualAsync_WithExpiration_SetsExpirationCorrectly( return base.ReplaceIfEqualAsync_WithExpiration_SetsExpirationCorrectly(); } - [Fact] - public override Task ReplaceIfEqualAsync_WithMatchingOldValue_ReturnsTrueAndReplacesValue() + [Theory] + [InlineData("workflow:state")] + [InlineData(" ")] + public override Task ReplaceIfEqualAsync_WithMatchingOldValue_ReturnsTrueAndReplacesValue(string cacheKey) { - return base.ReplaceIfEqualAsync_WithMatchingOldValue_ReturnsTrueAndReplacesValue(); + return base.ReplaceIfEqualAsync_WithMatchingOldValue_ReturnsTrueAndReplacesValue(cacheKey); } [Fact] @@ -1001,13 +919,6 @@ public override Task ReplaceIfEqualAsync_WithNullKey_ThrowsArgumentNullException { return base.ReplaceIfEqualAsync_WithNullKey_ThrowsArgumentNullException(); } - - [Fact] - public override Task ReplaceIfEqualAsync_WithWhitespaceKey_ThrowsArgumentException() - { - return base.ReplaceIfEqualAsync_WithWhitespaceKey_ThrowsArgumentException(); - } - [Fact(Skip = "Performance Test")] public override Task Serialization_WithComplexObjectsAndValidation_MeasuresThroughput() { @@ -1038,10 +949,12 @@ public override Task SetAllAsync_WithEmptyItems_ReturnsTrue() return base.SetAllAsync_WithEmptyItems_ReturnsTrue(); } - [Fact] - public override Task SetAllAsync_WithExpiration_KeysExpireCorrectly() + [Theory] + [InlineData("test")] + [InlineData(" ")] + public override Task SetAllAsync_WithExpiration_KeysExpireCorrectly(string cacheKey) { - return base.SetAllAsync_WithExpiration_KeysExpireCorrectly(); + return base.SetAllAsync_WithExpiration_KeysExpireCorrectly(cacheKey); } [Fact] @@ -1050,18 +963,6 @@ public override Task SetAllAsync_WithItemsContainingEmptyKey_ThrowsArgumentExcep return base.SetAllAsync_WithItemsContainingEmptyKey_ThrowsArgumentException(); } - [Fact] - public override Task SetAllAsync_WithItemsContainingNullKey_ThrowsArgumentException() - { - return base.SetAllAsync_WithItemsContainingNullKey_ThrowsArgumentException(); - } - - [Fact] - public override Task SetAllAsync_WithItemsContainingWhitespaceKey_ThrowsArgumentException() - { - return base.SetAllAsync_WithItemsContainingWhitespaceKey_ThrowsArgumentException(); - } - [Fact] public override Task SetAllAsync_WithNullItems_ThrowsArgumentNullException() { @@ -1094,10 +995,12 @@ public override Task SetAllExpiration_WithNullValues_RemovesExpiration() return base.SetAllExpiration_WithNullValues_RemovesExpiration(); } - [Fact] - public override Task SetAsync_WithComplexObject_StoresCorrectly() + [Theory] + [InlineData("user:profile")] + [InlineData(" ")] + public override Task SetAsync_WithComplexObject_StoresCorrectly(string cacheKey) { - return base.SetAsync_WithComplexObject_StoresCorrectly(); + return base.SetAsync_WithComplexObject_StoresCorrectly(cacheKey); } [Fact] @@ -1119,9 +1022,9 @@ public override Task SetAsync_WithDifferentScopes_IsolatesKeys() } [Fact] - public override Task SetAsync_WithEmptyKey_ThrowsArgumentNullException() + public override Task SetAsync_WithEmptyKey_ThrowsArgumentException() { - return base.SetAsync_WithEmptyKey_ThrowsArgumentNullException(); + return base.SetAsync_WithEmptyKey_ThrowsArgumentException(); } [Fact] @@ -1171,17 +1074,12 @@ public override Task SetAsync_WithShortExpiration_ExpiresCorrectly() { return base.SetAsync_WithShortExpiration_ExpiresCorrectly(); } - - [Fact] - public override Task SetAsync_WithWhitespaceKey_ThrowsArgumentException() - { - return base.SetAsync_WithWhitespaceKey_ThrowsArgumentException(); - } - - [Fact] - public override Task SetExpirationAsync_ChangingFromNoExpirationToFutureTime_UpdatesCorrectly() + [Theory] + [InlineData("token:refresh")] + [InlineData(" ")] + public override Task SetExpirationAsync_ChangingFromNoExpirationToFutureTime_UpdatesCorrectly(string cacheKey) { - return base.SetExpirationAsync_ChangingFromNoExpirationToFutureTime_UpdatesCorrectly(); + return base.SetExpirationAsync_ChangingFromNoExpirationToFutureTime_UpdatesCorrectly(cacheKey); } [Fact] @@ -1215,9 +1113,9 @@ public override Task SetExpirationAsync_WithDifferentCasedKeys_SetsOnlyExactMatc } [Fact] - public override Task SetExpirationAsync_WithEmptyKey_ThrowsArgumentNullException() + public override Task SetExpirationAsync_WithEmptyKey_ThrowsArgumentException() { - return base.SetExpirationAsync_WithEmptyKey_ThrowsArgumentNullException(); + return base.SetExpirationAsync_WithEmptyKey_ThrowsArgumentException(); } [Fact] @@ -1225,13 +1123,6 @@ public override Task SetExpirationAsync_WithNullKey_ThrowsArgumentNullException( { return base.SetExpirationAsync_WithNullKey_ThrowsArgumentNullException(); } - - [Fact] - public override Task SetExpirationAsync_WithWhitespaceKey_ThrowsArgumentException() - { - return base.SetExpirationAsync_WithWhitespaceKey_ThrowsArgumentException(); - } - [Fact] public override Task SetIfHigherAsync_WithDateTime_DoesNotUpdateWhenLower() { diff --git a/tests/Foundatio.Redis.Tests/Caching/ScopedRedisHybridCacheClientTests.cs b/tests/Foundatio.Redis.Tests/Caching/ScopedRedisHybridCacheClientTests.cs index 3448275..f652762 100644 --- a/tests/Foundatio.Redis.Tests/Caching/ScopedRedisHybridCacheClientTests.cs +++ b/tests/Foundatio.Redis.Tests/Caching/ScopedRedisHybridCacheClientTests.cs @@ -39,15 +39,17 @@ public override Task AddAsync_WithConcurrentRequests_OnlyOneSucceeds() } [Fact] - public override Task AddAsync_WithEmptyKey_ThrowsArgumentNullException() + public override Task AddAsync_WithEmptyKey_ThrowsArgumentException() { - return base.AddAsync_WithEmptyKey_ThrowsArgumentNullException(); + return base.AddAsync_WithEmptyKey_ThrowsArgumentException(); } - [Fact] - public override Task AddAsync_WithExistingKey_ReturnsFalseAndPreservesValue() + [Theory] + [InlineData("user:profile")] + [InlineData(" ")] + public override Task AddAsync_WithExistingKey_ReturnsFalseAndPreservesValue(string cacheKey) { - return base.AddAsync_WithExistingKey_ReturnsFalseAndPreservesValue(); + return base.AddAsync_WithExistingKey_ReturnsFalseAndPreservesValue(cacheKey); } [Fact(Skip = "TODO: Look into this as the local cache client maintenance never schedules for expiration")] @@ -62,10 +64,12 @@ public override Task AddAsync_WithNestedKeyUsingSeparator_StoresCorrectly() return base.AddAsync_WithNestedKeyUsingSeparator_StoresCorrectly(); } - [Fact] - public override Task AddAsync_WithNewKey_ReturnsTrue() + [Theory] + [InlineData("user:profile")] + [InlineData(" ")] + public override Task AddAsync_WithValidKey_ReturnsTrue(string cacheKey) { - return base.AddAsync_WithNewKey_ReturnsTrue(); + return base.AddAsync_WithValidKey_ReturnsTrue(cacheKey); } [Fact] @@ -73,13 +77,6 @@ public override Task AddAsync_WithNullKey_ThrowsArgumentNullException() { return base.AddAsync_WithNullKey_ThrowsArgumentNullException(); } - - [Fact] - public override Task AddAsync_WithWhitespaceKey_ThrowsArgumentException() - { - return base.AddAsync_WithWhitespaceKey_ThrowsArgumentException(); - } - [Fact] public override Task CacheOperations_WithMultipleTypes_MeasuresThroughput() { @@ -105,15 +102,17 @@ public override Task ExistsAsync_WithDifferentCasedKeys_ChecksExactMatch() } [Fact] - public override Task ExistsAsync_WithEmptyKey_ThrowsArgumentNullException() + public override Task ExistsAsync_WithEmptyKey_ThrowsArgumentException() { - return base.ExistsAsync_WithEmptyKey_ThrowsArgumentNullException(); + return base.ExistsAsync_WithEmptyKey_ThrowsArgumentException(); } - [Fact] - public override Task ExistsAsync_WithExistingKey_ReturnsTrue() + [Theory] + [InlineData("user:profile")] + [InlineData(" ")] + public override Task ExistsAsync_WithExistingKey_ReturnsTrue(string cacheKey) { - return base.ExistsAsync_WithExistingKey_ReturnsTrue(); + return base.ExistsAsync_WithExistingKey_ReturnsTrue(cacheKey); } [Fact] @@ -151,23 +150,18 @@ public override Task ExistsAsync_WithScopedCache_ChecksOnlyWithinScope() { return base.ExistsAsync_WithScopedCache_ChecksOnlyWithinScope(); } - - [Fact] - public override Task ExistsAsync_WithWhitespaceKey_ThrowsArgumentException() - { - return base.ExistsAsync_WithWhitespaceKey_ThrowsArgumentException(); - } - [Fact] public override Task GetAllAsync_WithEmptyKeys_ReturnsEmpty() { return base.GetAllAsync_WithEmptyKeys_ReturnsEmpty(); } - [Fact] - public override Task GetAllAsync_WithExistingKeys_ReturnsAllValues() + [Theory] + [InlineData("test2")] + [InlineData(" ")] + public override Task GetAllAsync_WithExistingKeys_ReturnsAllValues(string cacheKey) { - return base.GetAllAsync_WithExistingKeys_ReturnsAllValues(); + return base.GetAllAsync_WithExistingKeys_ReturnsAllValues(cacheKey); } [Fact] @@ -177,15 +171,9 @@ public override Task GetAllAsync_WithKeysContainingEmpty_ThrowsArgumentException } [Fact] - public override Task GetAllAsync_WithKeysContainingNull_ThrowsArgumentException() + public override Task GetAllAsync_WithKeysContainingNull_ThrowsArgumentNullException() { - return base.GetAllAsync_WithKeysContainingNull_ThrowsArgumentException(); - } - - [Fact] - public override Task GetAllAsync_WithKeysContainingWhitespace_ThrowsArgumentException() - { - return base.GetAllAsync_WithKeysContainingWhitespace_ThrowsArgumentException(); + return base.GetAllAsync_WithKeysContainingNull_ThrowsArgumentNullException(); } [Fact] @@ -262,10 +250,12 @@ public override Task GetAsync_WithComplexObject_PreservesAllProperties() return base.GetAsync_WithComplexObject_PreservesAllProperties(); } - [Fact] - public override Task GetAsync_WithComplexObject_ReturnsNewInstance() + [Theory] + [InlineData("order:details")] + [InlineData(" ")] + public override Task GetAsync_WithComplexObject_ReturnsNewInstance(string cacheKey) { - return base.GetAsync_WithComplexObject_ReturnsNewInstance(); + return base.GetAsync_WithComplexObject_ReturnsNewInstance(cacheKey); } [Fact] @@ -275,9 +265,9 @@ public override Task GetAsync_WithDifferentCasedKeys_TreatsAsDifferentKeys() } [Fact] - public override Task GetAsync_WithEmptyKey_ThrowsArgumentNullException() + public override Task GetAsync_WithEmptyKey_ThrowsArgumentException() { - return base.GetAsync_WithEmptyKey_ThrowsArgumentNullException(); + return base.GetAsync_WithEmptyKey_ThrowsArgumentException(); } [Fact] @@ -333,13 +323,6 @@ public override Task GetAsync_WithTryGetSemanticsAndMaxLongAsInt_ReturnsNoValue( { return base.GetAsync_WithTryGetSemanticsAndMaxLongAsInt_ReturnsNoValue(); } - - [Fact] - public override Task GetAsync_WithWhitespaceKey_ThrowsArgumentException() - { - return base.GetAsync_WithWhitespaceKey_ThrowsArgumentException(); - } - [Fact] public override Task GetExpirationAsync_AfterExpiry_ReturnsNull() { @@ -353,15 +336,17 @@ public override Task GetExpirationAsync_WithDifferentCasedKeys_GetsExactMatch() } [Fact] - public override Task GetExpirationAsync_WithEmptyKey_ThrowsArgumentNullException() + public override Task GetExpirationAsync_WithEmptyKey_ThrowsArgumentException() { - return base.GetExpirationAsync_WithEmptyKey_ThrowsArgumentNullException(); + return base.GetExpirationAsync_WithEmptyKey_ThrowsArgumentException(); } - [Fact] - public override Task GetExpirationAsync_WithExpiration_ReturnsCorrectTimeSpan() + [Theory] + [InlineData("token:refresh")] + [InlineData(" ")] + public override Task GetExpirationAsync_WithExpiration_ReturnsCorrectTimeSpan(string cacheKey) { - return base.GetExpirationAsync_WithExpiration_ReturnsCorrectTimeSpan(); + return base.GetExpirationAsync_WithExpiration_ReturnsCorrectTimeSpan(cacheKey); } [Fact] @@ -393,17 +378,10 @@ public override Task GetExpirationAsync_WithNullKey_ThrowsArgumentNullException( { return base.GetExpirationAsync_WithNullKey_ThrowsArgumentNullException(); } - - [Fact] - public override Task GetExpirationAsync_WithWhitespaceKey_ThrowsArgumentException() - { - return base.GetExpirationAsync_WithWhitespaceKey_ThrowsArgumentException(); - } - [Fact] - public override Task GetListAsync_WithEmptyKey_ThrowsArgumentNullException() + public override Task GetListAsync_WithEmptyKey_ThrowsArgumentException() { - return base.GetListAsync_WithEmptyKey_ThrowsArgumentNullException(); + return base.GetListAsync_WithEmptyKey_ThrowsArgumentException(); } [Fact] @@ -442,18 +420,13 @@ public override Task GetListAsync_WithPageBeyondEnd_ReturnsEmptyCollection() return base.GetListAsync_WithPageBeyondEnd_ReturnsEmptyCollection(); } - [Fact] - public override Task GetListAsync_WithPaging_ReturnsCorrectPageSize() - { - return base.GetListAsync_WithPaging_ReturnsCorrectPageSize(); - } - - [Fact] - public override Task GetListAsync_WithWhitespaceKey_ThrowsArgumentException() + [Theory] + [InlineData("cart:items")] + [InlineData(" ")] + public override Task GetListAsync_WithPaging_ReturnsCorrectPageSize(string cacheKey) { - return base.GetListAsync_WithWhitespaceKey_ThrowsArgumentException(); + return base.GetListAsync_WithPaging_ReturnsCorrectPageSize(cacheKey); } - [Fact] public override Task GetUnixTimeMillisecondsAsync_WithLocalDateTime_ReturnsCorrectly() { @@ -479,15 +452,17 @@ public override Task IncrementAsync_WithDifferentCasedKeys_IncrementsDistinctCou } [Fact] - public override Task IncrementAsync_WithEmptyKey_ThrowsArgumentNullException() + public override Task IncrementAsync_WithEmptyKey_ThrowsArgumentException() { - return base.IncrementAsync_WithEmptyKey_ThrowsArgumentNullException(); + return base.IncrementAsync_WithEmptyKey_ThrowsArgumentException(); } - [Fact] - public override Task IncrementAsync_WithExistingKey_IncrementsValue() + [Theory] + [InlineData("metrics:page-views")] + [InlineData(" ")] + public override Task IncrementAsync_WithExistingKey_IncrementsValue(string cacheKey) { - return base.IncrementAsync_WithExistingKey_IncrementsValue(); + return base.IncrementAsync_WithExistingKey_IncrementsValue(cacheKey); } [Fact] @@ -519,23 +494,18 @@ public override Task IncrementAsync_WithSpecifiedAmount_IncrementsCorrectly() { return base.IncrementAsync_WithSpecifiedAmount_IncrementsCorrectly(); } - - [Fact] - public override Task IncrementAsync_WithWhitespaceKey_ThrowsArgumentException() - { - return base.IncrementAsync_WithWhitespaceKey_ThrowsArgumentException(); - } - [Fact] public override Task ListAddAsync_WithDifferentCasedKeys_MaintainsDistinctLists() { return base.ListAddAsync_WithDifferentCasedKeys_MaintainsDistinctLists(); } - [Fact] - public override Task ListAddAsync_WithDuplicates_RemovesDuplicatesAndAddsItems() + [Theory] + [InlineData("cart:items")] + [InlineData(" ")] + public override Task ListAddAsync_WithDuplicates_RemovesDuplicatesAndAddsItems(string cacheKey) { - return base.ListAddAsync_WithDuplicates_RemovesDuplicatesAndAddsItems(); + return base.ListAddAsync_WithDuplicates_RemovesDuplicatesAndAddsItems(cacheKey); } [Fact] @@ -551,9 +521,9 @@ public override Task ListAddAsync_WithEmptyCollection_NoOp() } [Fact] - public override Task ListAddAsync_WithEmptyKey_ThrowsArgumentNullException() + public override Task ListAddAsync_WithEmptyKey_ThrowsArgumentException() { - return base.ListAddAsync_WithEmptyKey_ThrowsArgumentNullException(); + return base.ListAddAsync_WithEmptyKey_ThrowsArgumentException(); } [Fact] @@ -615,23 +585,18 @@ public override Task ListAddAsync_WithSingleString_StoresAsStringNotCharArray() { return base.ListAddAsync_WithSingleString_StoresAsStringNotCharArray(); } - [Fact] - public override Task ListAddAsync_WithWhitespaceKey_ThrowsArgumentException() + public override Task ListRemoveAsync_WithEmptyKey_ThrowsArgumentException() { - return base.ListAddAsync_WithWhitespaceKey_ThrowsArgumentException(); + return base.ListRemoveAsync_WithEmptyKey_ThrowsArgumentException(); } - [Fact] - public override Task ListRemoveAsync_WithEmptyKey_ThrowsArgumentNullException() - { - return base.ListRemoveAsync_WithEmptyKey_ThrowsArgumentNullException(); - } - - [Fact] - public override Task ListRemoveAsync_WithMultipleValues_RemovesAll() + [Theory] + [InlineData("cart:items")] + [InlineData(" ")] + public override Task ListRemoveAsync_WithMultipleValues_RemovesAll(string cacheKey) { - return base.ListRemoveAsync_WithMultipleValues_RemovesAll(); + return base.ListRemoveAsync_WithMultipleValues_RemovesAll(cacheKey); } [Fact] @@ -669,13 +634,6 @@ public override Task ListRemoveAsync_WithValidValues_RemovesKeyWhenEmpty() { return base.ListRemoveAsync_WithValidValues_RemovesKeyWhenEmpty(); } - - [Fact] - public override Task ListRemoveAsync_WithWhitespaceKey_ThrowsArgumentException() - { - return base.ListRemoveAsync_WithWhitespaceKey_ThrowsArgumentException(); - } - [Fact] public override Task RemoveAllAsync_WithEmptyKeys_Succeeds() { @@ -689,15 +647,9 @@ public override Task RemoveAllAsync_WithKeysContainingEmpty_ThrowsArgumentExcept } [Fact] - public override Task RemoveAllAsync_WithKeysContainingNull_ThrowsArgumentException() - { - return base.RemoveAllAsync_WithKeysContainingNull_ThrowsArgumentException(); - } - - [Fact] - public override Task RemoveAllAsync_WithKeysContainingWhitespace_ThrowsArgumentException() + public override Task RemoveAllAsync_WithKeysContainingNull_ThrowsArgumentNullException() { - return base.RemoveAllAsync_WithKeysContainingWhitespace_ThrowsArgumentException(); + return base.RemoveAllAsync_WithKeysContainingNull_ThrowsArgumentNullException(); } [Fact] @@ -719,9 +671,9 @@ public override Task RemoveAllAsync_WithMixedCaseKeys_RemovesOnlyExactMatches() } [Fact] - public override Task RemoveAllAsync_WithNullKeys_ThrowsArgumentNullException() + public override Task RemoveAllAsync_WithNullKeys_RemovesAllValues() { - return base.RemoveAllAsync_WithNullKeys_ThrowsArgumentNullException(); + return base.RemoveAllAsync_WithNullKeys_RemovesAllValues(); } [Fact] @@ -730,10 +682,12 @@ public override Task RemoveAllAsync_WithScopedCache_AffectsOnlyScopedKeys() return base.RemoveAllAsync_WithScopedCache_AffectsOnlyScopedKeys(); } - [Fact] - public override Task RemoveAllAsync_WithSpecificKeyCollection_RemovesOnlySpecifiedKeys() + [Theory] + [InlineData("remove-all-keys:")] + [InlineData(" ")] + public override Task RemoveAllAsync_WithSpecificKeyCollection_RemovesOnlySpecifiedKeys(string keyPrefix) { - return base.RemoveAllAsync_WithSpecificKeyCollection_RemovesOnlySpecifiedKeys(); + return base.RemoveAllAsync_WithSpecificKeyCollection_RemovesOnlySpecifiedKeys(keyPrefix); } [Fact] @@ -749,15 +703,17 @@ public override Task RemoveAsync_MultipleTimes_Succeeds() } [Fact] - public override Task RemoveAsync_WithEmptyKey_ThrowsArgumentNullException() + public override Task RemoveAsync_WithEmptyKey_ThrowsArgumentException() { - return base.RemoveAsync_WithEmptyKey_ThrowsArgumentNullException(); + return base.RemoveAsync_WithEmptyKey_ThrowsArgumentException(); } - [Fact] - public override Task RemoveAsync_WithExistingKey_RemovesSuccessfully() + [Theory] + [InlineData("session:active")] + [InlineData(" ")] + public override Task RemoveAsync_WithExistingKey_RemovesSuccessfully(string cacheKey) { - return base.RemoveAsync_WithExistingKey_RemovesSuccessfully(); + return base.RemoveAsync_WithExistingKey_RemovesSuccessfully(cacheKey); } [Fact] @@ -795,13 +751,6 @@ public override Task RemoveAsync_WithSpecificCase_RemovesOnlyMatchingKey() { return base.RemoveAsync_WithSpecificCase_RemovesOnlyMatchingKey(); } - - [Fact] - public override Task RemoveAsync_WithWhitespaceKey_ThrowsArgumentException() - { - return base.RemoveAsync_WithWhitespaceKey_ThrowsArgumentException(); - } - [Fact] public override Task RemoveByPrefixAsync_AsteriskPrefixWithScopedCache_TreatedAsLiteral() { @@ -809,13 +758,13 @@ public override Task RemoveByPrefixAsync_AsteriskPrefixWithScopedCache_TreatedAs } [Theory] - [InlineData("snowboard", 1, true)] // Exact key match - [InlineData("s", 1, true)] // Partial prefix match - [InlineData(null, 1, false)] // Null prefix (all keys in scope) - [InlineData("", 1, false)] // Empty prefix (all keys in scope) - public override Task RemoveByPrefixAsync_FromScopedCache_RemovesOnlyScopedKeys(string prefixToRemove, int expectedRemovedCount, bool shouldUnscopedRemain) + [InlineData("snowboard", 1)] // Exact key match + [InlineData("s", 1)] // Partial prefix match + [InlineData(null, 1)] // Null prefix (all keys in scope) + [InlineData("", 1)] // Empty prefix (all keys in scope) + public override Task RemoveByPrefixAsync_FromScopedCache_RemovesOnlyScopedKeys(string prefixToRemove, int expectedRemovedCount) { - return base.RemoveByPrefixAsync_FromScopedCache_RemovesOnlyScopedKeys(prefixToRemove, expectedRemovedCount, shouldUnscopedRemain); + return base.RemoveByPrefixAsync_FromScopedCache_RemovesOnlyScopedKeys(prefixToRemove, expectedRemovedCount); } [Theory] @@ -856,12 +805,6 @@ public override Task RemoveByPrefixAsync_WithEmptyPrefix_RemovesAllKeys() return base.RemoveByPrefixAsync_WithEmptyPrefix_RemovesAllKeys(); } - [Fact] - public override Task RemoveByPrefixAsync_WithEmptyPrefix_ThrowsArgumentException() - { - return base.RemoveByPrefixAsync_WithEmptyPrefix_ThrowsArgumentException(); - } - [Theory] [MemberData(nameof(GetLineEndingPrefixes))] public override Task RemoveByPrefixAsync_WithLineEndingPrefix_TreatsAsLiteral(string lineEndingPrefix) @@ -875,10 +818,12 @@ public override Task RemoveByPrefixAsync_WithLocalCache_InvalidatesLocalCache() return base.RemoveByPrefixAsync_WithLocalCache_InvalidatesLocalCache(); } - [Fact] - public override Task RemoveByPrefixAsync_WithMatchingPrefix_RemovesOnlyPrefixedKeys() + [Theory] + [InlineData("blah:")] + [InlineData(" ")] + public override Task RemoveByPrefixAsync_WithMatchingPrefix_RemovesOnlyPrefixedKeys(string prefix) { - return base.RemoveByPrefixAsync_WithMatchingPrefix_RemovesOnlyPrefixedKeys(); + return base.RemoveByPrefixAsync_WithMatchingPrefix_RemovesOnlyPrefixedKeys(prefix); } [Theory] @@ -903,12 +848,6 @@ public override Task RemoveByPrefixAsync_WithNullPrefix_RemovesAllKeys() return base.RemoveByPrefixAsync_WithNullPrefix_RemovesAllKeys(); } - [Fact] - public override Task RemoveByPrefixAsync_WithNullPrefix_ThrowsArgumentNullException() - { - return base.RemoveByPrefixAsync_WithNullPrefix_ThrowsArgumentNullException(); - } - [Theory] [MemberData(nameof(GetRegexSpecialCharacters))] public override Task RemoveByPrefixAsync_WithRegexMetacharacter_TreatsAsLiteral(string specialChar) @@ -929,19 +868,6 @@ public override Task RemoveByPrefixAsync_WithSpecialCharacterPrefix_TreatsAsLite return base.RemoveByPrefixAsync_WithSpecialCharacterPrefix_TreatsAsLiteral(specialPrefix); } - [Fact] - public override Task RemoveByPrefixAsync_WithWhitespacePrefix_ThrowsArgumentException() - { - return base.RemoveByPrefixAsync_WithWhitespacePrefix_ThrowsArgumentException(); - } - - [Theory] - [MemberData(nameof(GetWhitespaceOnlyPrefixes))] - public override Task RemoveByPrefixAsync_WithWhitespacePrefix_TreatsAsLiteral(string whitespacePrefix) - { - return base.RemoveByPrefixAsync_WithWhitespacePrefix_TreatsAsLiteral(whitespacePrefix); - } - [Theory] [MemberData(nameof(GetWildcardPatterns))] public override Task RemoveByPrefixAsync_WithWildcardPattern_TreatsAsLiteral(string pattern) @@ -950,15 +876,17 @@ public override Task RemoveByPrefixAsync_WithWildcardPattern_TreatsAsLiteral(str } [Fact] - public override Task RemoveIfEqualAsync_WithEmptyKey_ThrowsArgumentNullException() + public override Task RemoveIfEqualAsync_WithEmptyKey_ThrowsArgumentException() { - return base.RemoveIfEqualAsync_WithEmptyKey_ThrowsArgumentNullException(); + return base.RemoveIfEqualAsync_WithEmptyKey_ThrowsArgumentException(); } - [Fact] - public override Task RemoveIfEqualAsync_WithMatchingValue_ReturnsTrueAndRemoves() + [Theory] + [InlineData("session:active")] + [InlineData(" ")] + public override Task RemoveIfEqualAsync_WithMatchingValue_ReturnsTrueAndRemoves(string cacheKey) { - return base.RemoveIfEqualAsync_WithMatchingValue_ReturnsTrueAndRemoves(); + return base.RemoveIfEqualAsync_WithMatchingValue_ReturnsTrueAndRemoves(cacheKey); } [Fact] @@ -972,13 +900,6 @@ public override Task RemoveIfEqualAsync_WithNullKey_ThrowsArgumentNullException( { return base.RemoveIfEqualAsync_WithNullKey_ThrowsArgumentNullException(); } - - [Fact] - public override Task RemoveIfEqualAsync_WithWhitespaceKey_ThrowsArgumentException() - { - return base.RemoveIfEqualAsync_WithWhitespaceKey_ThrowsArgumentException(); - } - [Fact] public override Task ReplaceAsync_WithDifferentCasedKeys_TreatsAsDifferentKeys() { @@ -986,15 +907,17 @@ public override Task ReplaceAsync_WithDifferentCasedKeys_TreatsAsDifferentKeys() } [Fact] - public override Task ReplaceAsync_WithEmptyKey_ThrowsArgumentNullException() + public override Task ReplaceAsync_WithEmptyKey_ThrowsArgumentException() { - return base.ReplaceAsync_WithEmptyKey_ThrowsArgumentNullException(); + return base.ReplaceAsync_WithEmptyKey_ThrowsArgumentException(); } - [Fact] - public override Task ReplaceAsync_WithExistingKey_ReturnsTrueAndReplacesValue() + [Theory] + [InlineData("settings:theme")] + [InlineData(" ")] + public override Task ReplaceAsync_WithExistingKey_ReturnsTrueAndReplacesValue(string cacheKey) { - return base.ReplaceAsync_WithExistingKey_ReturnsTrueAndReplacesValue(); + return base.ReplaceAsync_WithExistingKey_ReturnsTrueAndReplacesValue(cacheKey); } [Fact] @@ -1014,13 +937,6 @@ public override Task ReplaceAsync_WithNullKey_ThrowsArgumentNullException() { return base.ReplaceAsync_WithNullKey_ThrowsArgumentNullException(); } - - [Fact] - public override Task ReplaceAsync_WithWhitespaceKey_ThrowsArgumentException() - { - return base.ReplaceAsync_WithWhitespaceKey_ThrowsArgumentException(); - } - [Fact] public override Task ReplaceIfEqualAsync_WithDifferentCasedKeys_ReplacesOnlyExactMatch() { @@ -1028,9 +944,9 @@ public override Task ReplaceIfEqualAsync_WithDifferentCasedKeys_ReplacesOnlyExac } [Fact] - public override Task ReplaceIfEqualAsync_WithEmptyKey_ThrowsArgumentNullException() + public override Task ReplaceIfEqualAsync_WithEmptyKey_ThrowsArgumentException() { - return base.ReplaceIfEqualAsync_WithEmptyKey_ThrowsArgumentNullException(); + return base.ReplaceIfEqualAsync_WithEmptyKey_ThrowsArgumentException(); } [Fact] @@ -1039,10 +955,12 @@ public override Task ReplaceIfEqualAsync_WithExpiration_SetsExpirationCorrectly( return base.ReplaceIfEqualAsync_WithExpiration_SetsExpirationCorrectly(); } - [Fact] - public override Task ReplaceIfEqualAsync_WithMatchingOldValue_ReturnsTrueAndReplacesValue() + [Theory] + [InlineData("workflow:state")] + [InlineData(" ")] + public override Task ReplaceIfEqualAsync_WithMatchingOldValue_ReturnsTrueAndReplacesValue(string cacheKey) { - return base.ReplaceIfEqualAsync_WithMatchingOldValue_ReturnsTrueAndReplacesValue(); + return base.ReplaceIfEqualAsync_WithMatchingOldValue_ReturnsTrueAndReplacesValue(cacheKey); } [Fact] @@ -1056,13 +974,6 @@ public override Task ReplaceIfEqualAsync_WithNullKey_ThrowsArgumentNullException { return base.ReplaceIfEqualAsync_WithNullKey_ThrowsArgumentNullException(); } - - [Fact] - public override Task ReplaceIfEqualAsync_WithWhitespaceKey_ThrowsArgumentException() - { - return base.ReplaceIfEqualAsync_WithWhitespaceKey_ThrowsArgumentException(); - } - [Fact] public override Task Serialization_WithComplexObjectsAndValidation_MeasuresThroughput() { @@ -1093,10 +1004,12 @@ public override Task SetAllAsync_WithEmptyItems_ReturnsTrue() return base.SetAllAsync_WithEmptyItems_ReturnsTrue(); } - [Fact] - public override Task SetAllAsync_WithExpiration_KeysExpireCorrectly() + [Theory] + [InlineData("test")] + [InlineData(" ")] + public override Task SetAllAsync_WithExpiration_KeysExpireCorrectly(string cacheKey) { - return base.SetAllAsync_WithExpiration_KeysExpireCorrectly(); + return base.SetAllAsync_WithExpiration_KeysExpireCorrectly(cacheKey); } [Fact] @@ -1105,18 +1018,6 @@ public override Task SetAllAsync_WithItemsContainingEmptyKey_ThrowsArgumentExcep return base.SetAllAsync_WithItemsContainingEmptyKey_ThrowsArgumentException(); } - [Fact] - public override Task SetAllAsync_WithItemsContainingNullKey_ThrowsArgumentException() - { - return base.SetAllAsync_WithItemsContainingNullKey_ThrowsArgumentException(); - } - - [Fact] - public override Task SetAllAsync_WithItemsContainingWhitespaceKey_ThrowsArgumentException() - { - return base.SetAllAsync_WithItemsContainingWhitespaceKey_ThrowsArgumentException(); - } - [Fact] public override Task SetAllAsync_WithNullItems_ThrowsArgumentNullException() { @@ -1149,10 +1050,12 @@ public override Task SetAllExpiration_WithNullValues_RemovesExpiration() return base.SetAllExpiration_WithNullValues_RemovesExpiration(); } - [Fact] - public override Task SetAsync_WithComplexObject_StoresCorrectly() + [Theory] + [InlineData("user:profile")] + [InlineData(" ")] + public override Task SetAsync_WithComplexObject_StoresCorrectly(string cacheKey) { - return base.SetAsync_WithComplexObject_StoresCorrectly(); + return base.SetAsync_WithComplexObject_StoresCorrectly(cacheKey); } [Fact] @@ -1174,9 +1077,9 @@ public override Task SetAsync_WithDifferentScopes_IsolatesKeys() } [Fact] - public override Task SetAsync_WithEmptyKey_ThrowsArgumentNullException() + public override Task SetAsync_WithEmptyKey_ThrowsArgumentException() { - return base.SetAsync_WithEmptyKey_ThrowsArgumentNullException(); + return base.SetAsync_WithEmptyKey_ThrowsArgumentException(); } [Fact] @@ -1232,17 +1135,12 @@ public override Task SetAsync_WithShortExpiration_ExpiresCorrectly() { return base.SetAsync_WithShortExpiration_ExpiresCorrectly(); } - - [Fact] - public override Task SetAsync_WithWhitespaceKey_ThrowsArgumentException() - { - return base.SetAsync_WithWhitespaceKey_ThrowsArgumentException(); - } - - [Fact] - public override Task SetExpirationAsync_ChangingFromNoExpirationToFutureTime_UpdatesCorrectly() + [Theory] + [InlineData("token:refresh")] + [InlineData(" ")] + public override Task SetExpirationAsync_ChangingFromNoExpirationToFutureTime_UpdatesCorrectly(string cacheKey) { - return base.SetExpirationAsync_ChangingFromNoExpirationToFutureTime_UpdatesCorrectly(); + return base.SetExpirationAsync_ChangingFromNoExpirationToFutureTime_UpdatesCorrectly(cacheKey); } [Fact] @@ -1276,9 +1174,9 @@ public override Task SetExpirationAsync_WithDifferentCasedKeys_SetsOnlyExactMatc } [Fact] - public override Task SetExpirationAsync_WithEmptyKey_ThrowsArgumentNullException() + public override Task SetExpirationAsync_WithEmptyKey_ThrowsArgumentException() { - return base.SetExpirationAsync_WithEmptyKey_ThrowsArgumentNullException(); + return base.SetExpirationAsync_WithEmptyKey_ThrowsArgumentException(); } [Fact] @@ -1286,13 +1184,6 @@ public override Task SetExpirationAsync_WithNullKey_ThrowsArgumentNullException( { return base.SetExpirationAsync_WithNullKey_ThrowsArgumentNullException(); } - - [Fact] - public override Task SetExpirationAsync_WithWhitespaceKey_ThrowsArgumentException() - { - return base.SetExpirationAsync_WithWhitespaceKey_ThrowsArgumentException(); - } - [Fact] public override Task SetIfHigherAsync_WithDateTime_DoesNotUpdateWhenLower() { From cc78a638eaa705c7cb735c3d38dd40ccf7ade48c Mon Sep 17 00:00:00 2001 From: Blake Niemyjski Date: Sat, 22 Nov 2025 20:20:59 +1300 Subject: [PATCH 08/26] Guards against null/empty cache keys Adds argument validation to prevent errors when null or empty keys are used with the Redis cache. Replaces multiple null or empty checks with ArgumentException.ThrowIfNullOrEmpty calls for consistency and conciseness. Also replaces `keys == null` with `keys is null`. --- src/Foundatio.Redis/Cache/RedisCacheClient.cs | 80 +++++++------------ 1 file changed, 31 insertions(+), 49 deletions(-) diff --git a/src/Foundatio.Redis/Cache/RedisCacheClient.cs b/src/Foundatio.Redis/Cache/RedisCacheClient.cs index 4e4f929..51615be 100644 --- a/src/Foundatio.Redis/Cache/RedisCacheClient.cs +++ b/src/Foundatio.Redis/Cache/RedisCacheClient.cs @@ -54,13 +54,15 @@ public RedisCacheClient(Builder RemoveAsync(string key) { + ArgumentException.ThrowIfNullOrEmpty(key); + + _logger.LogTrace("RemoveAsync: Removing key: {Key}", key); return Database.KeyDeleteAsync(key); } public async Task RemoveIfEqualAsync(string key, T expected) { - if (String.IsNullOrEmpty(key)) - throw new ArgumentNullException(nameof(key), "Key cannot be null or empty"); + ArgumentException.ThrowIfNullOrEmpty(key); await LoadScriptsAsync().AnyContext(); @@ -77,7 +79,7 @@ public async Task RemoveAllAsync(IEnumerable keys = null) int batchSize = 250; long deleted = 0; - if (keys == null) + if (keys is null) { var endpoints = _options.ConnectionMultiplexer.GetEndPoints(); if (endpoints.Length == 0) @@ -238,8 +240,7 @@ await Parallel.ForEachAsync( public async Task> GetAsync(string key) { - if (String.IsNullOrEmpty(key)) - throw new ArgumentNullException(nameof(key), "Key cannot be null or empty"); + ArgumentException.ThrowIfNullOrEmpty(key); var redisValue = await Database.StringGetAsync(key, _options.ReadMode).AnyContext(); return RedisValueToCacheValue(redisValue); @@ -273,8 +274,11 @@ private CacheValue> RedisValuesToCacheValue(RedisValue[] redis private CacheValue RedisValueToCacheValue(RedisValue redisValue) { - if (!redisValue.HasValue) return CacheValue.NoValue; - if (redisValue == _nullValue) return CacheValue.Null; + if (!redisValue.HasValue) + return CacheValue.NoValue; + + if (redisValue == _nullValue) + return CacheValue.Null; try { @@ -335,8 +339,7 @@ await Parallel.ForEachAsync( public async Task>> GetListAsync(string key, int? page = null, int pageSize = 100) { - if (String.IsNullOrEmpty(key)) - throw new ArgumentNullException(nameof(key), "Key cannot be null or empty"); + ArgumentException.ThrowIfNullOrEmpty(key); if (page is < 1) throw new ArgumentOutOfRangeException(nameof(page), "Page cannot be less than 1"); @@ -365,19 +368,15 @@ public async Task>> GetListAsync(string key, int? p public Task AddAsync(string key, T value, TimeSpan? expiresIn = null) { - if (String.IsNullOrEmpty(key)) - throw new ArgumentNullException(nameof(key), "Key cannot be null or empty"); + ArgumentException.ThrowIfNullOrEmpty(key); return InternalSetAsync(key, value, expiresIn, When.NotExists); } public async Task ListAddAsync(string key, IEnumerable values, TimeSpan? expiresIn = null) { - if (String.IsNullOrEmpty(key)) - throw new ArgumentNullException(nameof(key), "Key cannot be null or empty"); - - if (values == null) - throw new ArgumentNullException(nameof(values)); + ArgumentException.ThrowIfNullOrEmpty(key); + ArgumentNullException.ThrowIfNull(values); var expiresAt = expiresIn.HasValue ? _timeProvider.GetUtcNow().UtcDateTime.SafeAdd(expiresIn.Value) : DateTime.MaxValue; if (expiresAt < _timeProvider.GetUtcNow().UtcDateTime) @@ -408,11 +407,8 @@ public async Task ListAddAsync(string key, IEnumerable values, TimeS public async Task ListRemoveAsync(string key, IEnumerable values, TimeSpan? expiresIn = null) { - if (String.IsNullOrEmpty(key)) - throw new ArgumentNullException(nameof(key), "Key cannot be null or empty"); - - if (values == null) - throw new ArgumentNullException(nameof(values)); + ArgumentException.ThrowIfNullOrEmpty(key); + ArgumentNullException.ThrowIfNull(values); var redisValues = new List(); if (values is string stringValue) @@ -499,16 +495,14 @@ private async Task MigrateLegacySetToSortedSetForKeyAsync(string key, bool is public Task SetAsync(string key, T value, TimeSpan? expiresIn = null) { - if (String.IsNullOrEmpty(key)) - throw new ArgumentNullException(nameof(key), "Key cannot be null or empty"); + ArgumentException.ThrowIfNullOrEmpty(key); return InternalSetAsync(key, value, expiresIn); } public async Task SetIfHigherAsync(string key, double value, TimeSpan? expiresIn = null) { - if (String.IsNullOrEmpty(key)) - throw new ArgumentNullException(nameof(key), "Key cannot be null or empty"); + ArgumentException.ThrowIfNullOrEmpty(key); await LoadScriptsAsync().AnyContext(); @@ -526,8 +520,7 @@ public async Task SetIfHigherAsync(string key, double value, TimeSpan? e public async Task SetIfHigherAsync(string key, long value, TimeSpan? expiresIn = null) { - if (String.IsNullOrEmpty(key)) - throw new ArgumentNullException(nameof(key), "Key cannot be null or empty"); + ArgumentException.ThrowIfNullOrEmpty(key); await LoadScriptsAsync().AnyContext(); @@ -545,8 +538,7 @@ public async Task SetIfHigherAsync(string key, long value, TimeSpan? expir public async Task SetIfLowerAsync(string key, double value, TimeSpan? expiresIn = null) { - if (String.IsNullOrEmpty(key)) - throw new ArgumentNullException(nameof(key), "Key cannot be null or empty"); + ArgumentException.ThrowIfNullOrEmpty(key); await LoadScriptsAsync().AnyContext(); @@ -564,8 +556,7 @@ public async Task SetIfLowerAsync(string key, double value, TimeSpan? ex public async Task SetIfLowerAsync(string key, long value, TimeSpan? expiresIn = null) { - if (String.IsNullOrEmpty(key)) - throw new ArgumentNullException(nameof(key), "Key cannot be null or empty"); + ArgumentException.ThrowIfNullOrEmpty(key); await LoadScriptsAsync().AnyContext(); @@ -616,16 +607,14 @@ public async Task SetAllAsync(IDictionary values, TimeSpan? e public Task ReplaceAsync(string key, T value, TimeSpan? expiresIn = null) { - if (String.IsNullOrEmpty(key)) - throw new ArgumentNullException(nameof(key), "Key cannot be null or empty"); + ArgumentException.ThrowIfNullOrEmpty(key); return InternalSetAsync(key, value, expiresIn, When.Exists); } public async Task ReplaceIfEqualAsync(string key, T value, T expected, TimeSpan? expiresIn = null) { - if (String.IsNullOrEmpty(key)) - throw new ArgumentNullException(nameof(key), "Key cannot be null or empty"); + ArgumentException.ThrowIfNullOrEmpty(key); await LoadScriptsAsync().AnyContext(); @@ -646,8 +635,7 @@ public async Task ReplaceIfEqualAsync(string key, T value, T expected, public async Task IncrementAsync(string key, double amount = 1, TimeSpan? expiresIn = null) { - if (String.IsNullOrEmpty(key)) - throw new ArgumentNullException(nameof(key), "Key cannot be null or empty"); + ArgumentException.ThrowIfNullOrEmpty(key); if (expiresIn.HasValue) { @@ -661,8 +649,7 @@ public async Task IncrementAsync(string key, double amount = 1, TimeSpan public async Task IncrementAsync(string key, long amount = 1, TimeSpan? expiresIn = null) { - if (String.IsNullOrEmpty(key)) - throw new ArgumentNullException(nameof(key), "Key cannot be null or empty"); + ArgumentException.ThrowIfNullOrEmpty(key); if (expiresIn?.Ticks <= 0) { @@ -683,32 +670,28 @@ public async Task IncrementAsync(string key, long amount = 1, TimeSpan? ex public Task ExistsAsync(string key) { - if (String.IsNullOrEmpty(key)) - throw new ArgumentNullException(nameof(key), "Key cannot be null or empty"); + ArgumentException.ThrowIfNullOrEmpty(key); return Database.KeyExistsAsync(key); } public Task GetExpirationAsync(string key) { - if (String.IsNullOrEmpty(key)) - throw new ArgumentNullException(nameof(key), "Key cannot be null or empty"); + ArgumentException.ThrowIfNullOrEmpty(key); return Database.KeyTimeToLiveAsync(key); } public Task SetExpirationAsync(string key, TimeSpan expiresIn) { - if (String.IsNullOrEmpty(key)) - throw new ArgumentNullException(nameof(key), "Key cannot be null or empty"); + ArgumentException.ThrowIfNullOrEmpty(key); return Database.KeyExpireAsync(key, expiresIn); } public async Task> GetAllExpirationAsync(IEnumerable keys) { - if (keys == null) - throw new ArgumentNullException(nameof(keys)); + ArgumentNullException.ThrowIfNull(keys); var keyList = keys.Where(k => !String.IsNullOrEmpty(k)).Distinct().ToList(); if (keyList.Count == 0) @@ -773,8 +756,7 @@ await Parallel.ForEachAsync( public async Task SetAllExpirationAsync(IDictionary expirations) { - if (expirations == null) - throw new ArgumentNullException(nameof(expirations)); + ArgumentNullException.ThrowIfNull(expirations); var validExpirations = expirations.Where(kvp => !String.IsNullOrEmpty(kvp.Key)).ToList(); From bb6f286a428eb338b422909c63a95823a90384a2 Mon Sep 17 00:00:00 2001 From: Blake Niemyjski Date: Sat, 22 Nov 2025 20:21:07 +1300 Subject: [PATCH 09/26] cleanup --- src/Foundatio.Redis/Extensions/TypeExtensions.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Foundatio.Redis/Extensions/TypeExtensions.cs b/src/Foundatio.Redis/Extensions/TypeExtensions.cs index 79b1176..bd01844 100644 --- a/src/Foundatio.Redis/Extensions/TypeExtensions.cs +++ b/src/Foundatio.Redis/Extensions/TypeExtensions.cs @@ -1,4 +1,4 @@ -using System; +using System; using Foundatio.Utility; namespace Foundatio.Extensions; @@ -42,7 +42,6 @@ public static bool IsNumeric(this Type type) return false; } - public static bool IsNullableNumeric(this Type type) { if (type.IsArray) From ae446f12196eb512addaa8409b3ac8292a5f671b Mon Sep 17 00:00:00 2001 From: Blake Niemyjski Date: Sat, 22 Nov 2025 20:21:26 +1300 Subject: [PATCH 10/26] Guards against null or empty cache keys Ensures that the cache client throws an exception when attempting to set a value with a null or empty key. This prevents potential runtime errors and improves the reliability of the caching mechanism. --- src/Foundatio.Redis/Cache/RedisCacheClient.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Foundatio.Redis/Cache/RedisCacheClient.cs b/src/Foundatio.Redis/Cache/RedisCacheClient.cs index 51615be..656e886 100644 --- a/src/Foundatio.Redis/Cache/RedisCacheClient.cs +++ b/src/Foundatio.Redis/Cache/RedisCacheClient.cs @@ -599,7 +599,10 @@ public async Task SetAllAsync(IDictionary values, TimeSpan? e var tasks = new List>(); foreach (var pair in values) + { + ArgumentException.ThrowIfNullOrEmpty(pair.Key, nameof(values)); tasks.Add(Database.StringSetAsync(pair.Key, pair.Value.ToRedisValue(_options.Serializer), expiresIn)); + } bool[] results = await Task.WhenAll(tasks).AnyContext(); return results.Count(r => r); From 8567269b5af8e9535c9d34ad49de7e6436ab0381 Mon Sep 17 00:00:00 2001 From: Blake Niemyjski Date: Sat, 22 Nov 2025 20:21:48 +1300 Subject: [PATCH 11/26] Improves GetAllAsync input validation Adds validation for the input keys to GetAllAsync. Ensures that the input keys are not null or empty, and eliminates duplicate keys before proceeding with the cache retrieval operation. This enhancement improves the reliability and robustness of the method by preventing potential exceptions. --- src/Foundatio.Redis/Cache/RedisCacheClient.cs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/Foundatio.Redis/Cache/RedisCacheClient.cs b/src/Foundatio.Redis/Cache/RedisCacheClient.cs index 656e886..e82a220 100644 --- a/src/Foundatio.Redis/Cache/RedisCacheClient.cs +++ b/src/Foundatio.Redis/Cache/RedisCacheClient.cs @@ -297,17 +297,22 @@ private CacheValue RedisValueToCacheValue(RedisValue redisValue) public async Task>> GetAllAsync(IEnumerable keys) { - if (keys is null) - throw new ArgumentNullException(nameof(keys)); + ArgumentNullException.ThrowIfNull(keys); + + var redisKeys = keys is ICollection collection ? new List(collection.Count) : []; + foreach (string key in keys.Distinct()) + { + ArgumentException.ThrowIfNullOrEmpty(key, nameof(keys)); + redisKeys.Add(key); + } - var redisKeys = keys.Distinct().Select(k => (RedisKey)k).ToArray(); - if (redisKeys.Length == 0) + if (redisKeys.Count is 0) return ReadOnlyDictionary>.Empty; if (_options.ConnectionMultiplexer.IsCluster()) { // Use the default concurrency on .NET 8 (-1) - var result = new ConcurrentDictionary>(-1, redisKeys.Length); + var result = new ConcurrentDictionary>(-1, redisKeys.Count); await Parallel.ForEachAsync( redisKeys.GroupBy(k => _options.ConnectionMultiplexer.HashSlot(k)), async (hashSlotGroup, ct) => From 98bad3e0acf6573820557ba78bc0c1ff0830d85e Mon Sep 17 00:00:00 2001 From: Blake Niemyjski Date: Sat, 22 Nov 2025 20:30:38 +1300 Subject: [PATCH 12/26] Improves Redis cache key deletion efficiency. Optimizes key deletion within the Redis cache client, particularly for cluster configurations, by ensuring keys are processed in batches based on hash slots, thus minimizing Redis overload. Also, handles null or empty keys more robustly, preventing exceptions and improving overall stability. --- src/Foundatio.Redis/Cache/RedisCacheClient.cs | 52 +++++++++++++------ 1 file changed, 35 insertions(+), 17 deletions(-) diff --git a/src/Foundatio.Redis/Cache/RedisCacheClient.cs b/src/Foundatio.Redis/Cache/RedisCacheClient.cs index e82a220..15b7a30 100644 --- a/src/Foundatio.Redis/Cache/RedisCacheClient.cs +++ b/src/Foundatio.Redis/Cache/RedisCacheClient.cs @@ -124,11 +124,11 @@ public async Task RemoveAllAsync(IEnumerable keys = null) await foreach (var key in server.KeysAsync(_options.Database).ConfigureAwait(false)) seen.Add(key); - // Parallelize batch deletions with moderate concurrency to avoid Redis overload - // Redis can become unstable with too many concurrent connections/requests - // See: https://redis.io/docs/reference/clients/ - // Lower limit (8) since this runs after FLUSHDB failed, meaning we're deleting - // potentially thousands or millions of keys - must avoid overwhelming Redis + // Parallelize batch deletions with moderate concurrency to avoid Redis overload + // Redis can become unstable with too many concurrent connections/requests + // See: https://redis.io/docs/reference/clients/ + // Lower limit (8) since this runs after FLUSHDB failed, meaning we're deleting + // potentially thousands or millions of keys - must avoid overwhelming Redis int maxParallelism = Math.Min(8, Environment.ProcessorCount); await Parallel.ForEachAsync(seen.Chunk(batchSize), new ParallelOptions { MaxDegreeOfParallelism = maxParallelism }, async (batch, ct) => { @@ -145,10 +145,20 @@ public async Task RemoveAllAsync(IEnumerable keys = null) } else if (Database.Multiplexer.IsCluster()) { - foreach (var redisKeys in keys.Where(k => !String.IsNullOrEmpty(k)).Select(k => (RedisKey)k).Chunk(batchSize)) + var redisKeys = keys is ICollection collection ? new List(collection.Count) : []; + foreach (string key in keys.Distinct()) + { + ArgumentException.ThrowIfNullOrEmpty(key, nameof(keys)); + redisKeys.Add(key); + } + + if (redisKeys.Count is 0) + return 0; + + foreach (var batch in redisKeys.Chunk(batchSize)) { await Parallel.ForEachAsync( - redisKeys.GroupBy(k => Database.Multiplexer.HashSlot(k)), + batch.GroupBy(k => Database.Multiplexer.HashSlot(k)), async (hashSlotGroup, ct) => { var hashSlotKeys = hashSlotGroup.ToArray(); @@ -166,24 +176,31 @@ await Parallel.ForEachAsync( } else { - var keyBatches = keys.Where(k => !String.IsNullOrEmpty(k)) - .Select(k => (RedisKey)k) - .Chunk(batchSize) - .ToArray(); + var redisKeys = keys is ICollection collection ? new List(collection.Count) : []; + foreach (string key in keys.Distinct()) + { + ArgumentException.ThrowIfNullOrEmpty(key, nameof(keys)); + redisKeys.Add(key); + } + + if (redisKeys.Count is 0) + return 0; + + var keyBatches = redisKeys.Chunk(batchSize).ToArray(); // Parallelize batch deletions with moderate concurrency to avoid Redis overload // Limit parallelism to 8 or Environment.ProcessorCount, whichever is smaller int maxParallelism = Math.Min(8, Environment.ProcessorCount); - await Parallel.ForEachAsync(keyBatches, new ParallelOptions { MaxDegreeOfParallelism = maxParallelism }, async (redisKeys, ct) => + await Parallel.ForEachAsync(keyBatches, new ParallelOptions { MaxDegreeOfParallelism = maxParallelism }, async (batch, ct) => { try { - long count = await Database.KeyDeleteAsync(redisKeys).AnyContext(); + long count = await Database.KeyDeleteAsync(batch).AnyContext(); Interlocked.Add(ref deleted, count); } catch (Exception ex) { - _logger.LogError(ex, "Unable to delete keys ({Keys}): {Message}", redisKeys, ex.Message); + _logger.LogError(ex, "Unable to delete keys ({Keys}): {Message}", batch, ex.Message); } }).AnyContext(); } @@ -331,11 +348,11 @@ await Parallel.ForEachAsync( } else { - var result = new Dictionary>(redisKeys.Length); - var values = await Database.StringGetAsync(redisKeys, _options.ReadMode).AnyContext(); + var result = new Dictionary>(redisKeys.Count); + var values = await Database.StringGetAsync(redisKeys.ToArray(), _options.ReadMode).AnyContext(); // Redis MGET guarantees that values are returned in the same order as keys - for (int i = 0; i < redisKeys.Length; i++) + for (int i = 0; i < redisKeys.Count; i++) result[redisKeys[i]] = RedisValueToCacheValue(values[i]); return result.AsReadOnly(); @@ -602,6 +619,7 @@ public async Task SetAllAsync(IDictionary values, TimeSpan? e return 0; } + // TODO: Look into optimizing this. var tasks = new List>(); foreach (var pair in values) { From e926d4310eb6328c9fbe6b8f2d45df79045ab9ab Mon Sep 17 00:00:00 2001 From: Blake Niemyjski Date: Sat, 22 Nov 2025 20:34:10 +1300 Subject: [PATCH 13/26] Adds argument null check to SetAllAsync Adds an ArgumentNullException check for the values parameter in the SetAllAsync method to prevent unexpected behavior when a null dictionary is passed. This improves consistency with all other callers --- src/Foundatio.Redis/Cache/RedisCacheClient.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Foundatio.Redis/Cache/RedisCacheClient.cs b/src/Foundatio.Redis/Cache/RedisCacheClient.cs index 15b7a30..cb25957 100644 --- a/src/Foundatio.Redis/Cache/RedisCacheClient.cs +++ b/src/Foundatio.Redis/Cache/RedisCacheClient.cs @@ -609,7 +609,8 @@ private async Task InternalSetAsync(string key, T value, TimeSpan? expi public async Task SetAllAsync(IDictionary values, TimeSpan? expiresIn = null) { - if (values == null || values.Count == 0) + ArgumentNullException.ThrowIfNull(values); + if (values.Count is 0) return 0; if (expiresIn?.Ticks <= 0) From 88705dd455fe05cb0997693c6055eb93ddf245e1 Mon Sep 17 00:00:00 2001 From: Blake Niemyjski Date: Tue, 25 Nov 2025 13:20:06 +1300 Subject: [PATCH 14/26] Clarifies Redis cache parallelism limits Documents that StackExchange.Redis handles pipelining internally, meaning the parallelism limits in place primarily affect client-side task creation. --- src/Foundatio.Redis/Cache/RedisCacheClient.cs | 13 ++++++------- .../Cache/RedisHybridCacheClientOptions.cs | 1 - 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/Foundatio.Redis/Cache/RedisCacheClient.cs b/src/Foundatio.Redis/Cache/RedisCacheClient.cs index cb25957..08685b4 100644 --- a/src/Foundatio.Redis/Cache/RedisCacheClient.cs +++ b/src/Foundatio.Redis/Cache/RedisCacheClient.cs @@ -124,11 +124,9 @@ public async Task RemoveAllAsync(IEnumerable keys = null) await foreach (var key in server.KeysAsync(_options.Database).ConfigureAwait(false)) seen.Add(key); - // Parallelize batch deletions with moderate concurrency to avoid Redis overload - // Redis can become unstable with too many concurrent connections/requests - // See: https://redis.io/docs/reference/clients/ - // Lower limit (8) since this runs after FLUSHDB failed, meaning we're deleting - // potentially thousands or millions of keys - must avoid overwhelming Redis + // NOTE: StackExchange.Redis multiplexes all operations over a single connection and handles + // pipelining internally, so parallelism limits here only affect client-side Task creation, + // not Redis server load. Consider simplifying to Task.WhenAll in a future refactor. int maxParallelism = Math.Min(8, Environment.ProcessorCount); await Parallel.ForEachAsync(seen.Chunk(batchSize), new ParallelOptions { MaxDegreeOfParallelism = maxParallelism }, async (batch, ct) => { @@ -188,8 +186,9 @@ await Parallel.ForEachAsync( var keyBatches = redisKeys.Chunk(batchSize).ToArray(); - // Parallelize batch deletions with moderate concurrency to avoid Redis overload - // Limit parallelism to 8 or Environment.ProcessorCount, whichever is smaller + // NOTE: StackExchange.Redis multiplexes all operations over a single connection and handles + // pipelining internally, so parallelism limits here only affect client-side Task creation, + // not Redis server load. Consider simplifying to Task.WhenAll in a future refactor. int maxParallelism = Math.Min(8, Environment.ProcessorCount); await Parallel.ForEachAsync(keyBatches, new ParallelOptions { MaxDegreeOfParallelism = maxParallelism }, async (batch, ct) => { diff --git a/src/Foundatio.Redis/Cache/RedisHybridCacheClientOptions.cs b/src/Foundatio.Redis/Cache/RedisHybridCacheClientOptions.cs index a201ee3..251ac39 100644 --- a/src/Foundatio.Redis/Cache/RedisHybridCacheClientOptions.cs +++ b/src/Foundatio.Redis/Cache/RedisHybridCacheClientOptions.cs @@ -11,7 +11,6 @@ public class RedisHybridCacheClientOptions : RedisCacheClientOptions public class RedisHybridCacheClientOptionsBuilder : SharedOptionsBuilder { - public RedisHybridCacheClientOptionsBuilder ConnectionMultiplexer(IConnectionMultiplexer connectionMultiplexer) { Target.ConnectionMultiplexer = connectionMultiplexer; From 1dad2cdf99009e27001ada28c9f2c9c86b9a7561 Mon Sep 17 00:00:00 2001 From: Blake Niemyjski Date: Tue, 25 Nov 2025 14:39:41 +1300 Subject: [PATCH 15/26] feat(cache): Optimize SetAllAsync with MSET/MSETEX batch operations - Use MSET for batch sets without expiration (Redis 1.0.1+) instead of pipelined individual SET commands, reducing round trips and Task overhead - Add MSETEX support for batch sets with expiration on Redis 8.4+ - Implement version detection with SupportsMsetexCommand() that: - Checks all connected primary servers for Redis 8.4+ (version 8.3.224) - Caches the result to avoid repeated version checks - Invalidates cache on connection restored (handles failover scenarios) - Includes debug logging for troubleshooting - Maintain cluster/sentinel support by grouping keys by hash slot - Fall back to pipelined individual SETs for expiration on Redis < 8.4 Note: MSET/MSETEX are atomic operations that replace existing values and remove any existing TTL, consistent with standard SET behavior. --- src/Foundatio.Redis/Cache/RedisCacheClient.cs | 136 +++++++++++++++++- src/Foundatio.Redis/Foundatio.Redis.csproj | 2 +- .../Caching/RedisCacheClientTests.cs | 6 + .../Caching/RedisHybridCacheClientTests.cs | 6 + .../Caching/ScopedRedisCacheClientTests.cs | 6 + .../ScopedRedisHybridCacheClientTests.cs | 6 + 6 files changed, 154 insertions(+), 8 deletions(-) diff --git a/src/Foundatio.Redis/Cache/RedisCacheClient.cs b/src/Foundatio.Redis/Cache/RedisCacheClient.cs index 08685b4..3a1d6ee 100644 --- a/src/Foundatio.Redis/Cache/RedisCacheClient.cs +++ b/src/Foundatio.Redis/Cache/RedisCacheClient.cs @@ -25,6 +25,7 @@ public sealed class RedisCacheClient : ICacheClient, IHaveSerializer private readonly AsyncLock _lock = new(); private bool _scriptsLoaded; + private bool? _supportsMsetEx; private LoadedLuaScript _incrementWithExpire; private LoadedLuaScript _removeIfEqual; @@ -43,6 +44,7 @@ public RedisCacheClient(RedisCacheClientOptions options) _logger = options.LoggerFactory?.CreateLogger(typeof(RedisCacheClient)) ?? NullLogger.Instance; options.ConnectionMultiplexer.ConnectionRestored += ConnectionMultiplexerOnConnectionRestored; + options.ConnectionMultiplexer.ConnectionFailed += ConnectionMultiplexerOnConnectionFailed; } public RedisCacheClient(Builder config) @@ -619,16 +621,129 @@ public async Task SetAllAsync(IDictionary values, TimeSpan? e return 0; } - // TODO: Look into optimizing this. - var tasks = new List>(); - foreach (var pair in values) + // Validate keys and serialize values upfront + var pairs = new KeyValuePair[values.Count]; + int index = 0; + foreach (var kvp in values) { - ArgumentException.ThrowIfNullOrEmpty(pair.Key, nameof(values)); - tasks.Add(Database.StringSetAsync(pair.Key, pair.Value.ToRedisValue(_options.Serializer), expiresIn)); + ArgumentException.ThrowIfNullOrEmpty(kvp.Key, nameof(values)); + pairs[index++] = new KeyValuePair(kvp.Key, kvp.Value.ToRedisValue(_options.Serializer)); } - bool[] results = await Task.WhenAll(tasks).AnyContext(); - return results.Count(r => r); + if (_options.ConnectionMultiplexer.IsCluster()) + { + // For cluster/sentinel, group keys by hash slot since batch operations + // require all keys to be in the same slot + int successCount = 0; + await Parallel.ForEachAsync( + pairs.GroupBy(p => _options.ConnectionMultiplexer.HashSlot(p.Key)), + async (slotGroup, ct) => + { + int count = await SetAllInternalAsync(slotGroup.ToArray(), expiresIn).AnyContext(); + Interlocked.Add(ref successCount, count); + }).AnyContext(); + return successCount; + } + + return await SetAllInternalAsync(pairs, expiresIn).AnyContext(); + } + + /// + /// Internal method that performs the actual batch set operation. + /// All keys in must be in the same hash slot for cluster mode. + /// + private async Task SetAllInternalAsync(KeyValuePair[] pairs, TimeSpan? expiresIn) + { + if (expiresIn.HasValue) + { + // With expiration: use MSETEX if available, otherwise fall back to pipelined SETs + if (SupportsMsetexCommand()) + { + bool success = await Database.StringSetAsync(pairs, When.Always, new Expiration(expiresIn.Value)).AnyContext(); + return success ? pairs.Length : 0; + } + + // Fallback for Redis < 8.4: pipelined individual SET commands with expiration + var tasks = new List>(pairs.Length); + foreach (var pair in pairs) + { + tasks.Add(Database.StringSetAsync(pair.Key, pair.Value, expiresIn, When.Always)); + } + + bool[] results = await Task.WhenAll(tasks).AnyContext(); + return results.Count(r => r); + } + + // No expiration: use MSET (available since Redis 1.0.1) - single atomic batch operation + bool msetSuccess = await Database.StringSetAsync(pairs).AnyContext(); + return msetSuccess ? pairs.Length : 0; + } + + /// + /// Checks if the connected Redis server supports the MSETEX command (Redis 8.4+). + /// + /// + /// + /// MSETEX allows setting multiple keys with expiration in a single atomic operation. + /// StackExchange.Redis 2.10.1+ supports this via the StringSetAsync overload that + /// accepts an parameter. However, SE.Redis does NOT automatically + /// fall back to individual SET commands on older Redis versions - it will fail. + /// + /// + /// This method detects the Redis server version and caches the result. The cache is + /// invalidated when the connection is restored (e.g., after failover to a different server). + /// + /// + /// Note: For batch sets WITHOUT expiration, use StringSetAsync(pairs, When, CommandFlags) + /// which uses MSET - available since Redis 1.0.1 and doesn't require version detection. + /// MSET replaces existing values and removes any existing TTL, just like regular SET. + /// + /// + /// + /// true if all connected primary servers support MSETEX; + /// false if any primary is running Redis < 8.4 or no primaries are connected. + /// + private bool SupportsMsetexCommand() + { + if (_supportsMsetEx.HasValue) + return _supportsMsetEx.Value; + + // Redis 8.4 RC1 is internally versioned as 8.3.224 + var minVersion = new Version(8, 3, 224); + + var endpoints = _options.ConnectionMultiplexer.GetEndPoints(); + if (endpoints.Length == 0) + { + _logger.LogDebug("SupportsMsetexCommand: No endpoints configured, MSETEX not available"); + return false; // Don't cache - no endpoints configured + } + + bool foundConnectedPrimary = false; + foreach (var endpoint in endpoints) + { + var server = _options.ConnectionMultiplexer.GetServer(endpoint); + if (server.IsConnected && !server.IsReplica) + { + foundConnectedPrimary = true; + if (server.Version < minVersion) + { + _logger.LogDebug("SupportsMsetexCommand: Server {Endpoint} version {Version} does not support MSETEX (requires {MinVersion}+)", + endpoint, server.Version, minVersion); + _supportsMsetEx = false; + return false; + } + } + } + + if (foundConnectedPrimary) + { + _supportsMsetEx = true; + return true; + } + + _logger.LogDebug("SupportsMsetexCommand: No connected primaries found, MSETEX availability unknown"); + // No connected primaries found - don't cache, will retry on next call + return false; } public Task ReplaceAsync(string key, T value, TimeSpan? expiresIn = null) @@ -917,11 +1032,18 @@ private void ConnectionMultiplexerOnConnectionRestored(object sender, Connection { _logger.LogInformation("Redis connection restored"); _scriptsLoaded = false; + _supportsMsetEx = null; // Re-check version on next call + } + + private void ConnectionMultiplexerOnConnectionFailed(object sender, ConnectionFailedEventArgs connectionFailedEventArgs) + { + _logger.LogWarning("Redis connection failed: {FailureType}", connectionFailedEventArgs.FailureType); } public void Dispose() { _options.ConnectionMultiplexer.ConnectionRestored -= ConnectionMultiplexerOnConnectionRestored; + _options.ConnectionMultiplexer.ConnectionFailed -= ConnectionMultiplexerOnConnectionFailed; } ISerializer IHaveSerializer.Serializer => _options.Serializer; diff --git a/src/Foundatio.Redis/Foundatio.Redis.csproj b/src/Foundatio.Redis/Foundatio.Redis.csproj index c6663f4..f2caec5 100644 --- a/src/Foundatio.Redis/Foundatio.Redis.csproj +++ b/src/Foundatio.Redis/Foundatio.Redis.csproj @@ -4,7 +4,7 @@ - + diff --git a/tests/Foundatio.Redis.Tests/Caching/RedisCacheClientTests.cs b/tests/Foundatio.Redis.Tests/Caching/RedisCacheClientTests.cs index 46d99de..4bfffba 100644 --- a/tests/Foundatio.Redis.Tests/Caching/RedisCacheClientTests.cs +++ b/tests/Foundatio.Redis.Tests/Caching/RedisCacheClientTests.cs @@ -949,6 +949,12 @@ public override Task SetAllAsync_WithDifferentCasedKeys_CreatesDistinctEntries() return base.SetAllAsync_WithDifferentCasedKeys_CreatesDistinctEntries(); } + [Fact] + public override Task SetAllAsync_WithLargeNumberOfKeys_MeasuresThroughput() + { + return base.SetAllAsync_WithLargeNumberOfKeys_MeasuresThroughput(); + } + [Fact] public override Task SetAllAsync_WithEmptyItems_ReturnsTrue() { diff --git a/tests/Foundatio.Redis.Tests/Caching/RedisHybridCacheClientTests.cs b/tests/Foundatio.Redis.Tests/Caching/RedisHybridCacheClientTests.cs index a204f1f..f81d78a 100644 --- a/tests/Foundatio.Redis.Tests/Caching/RedisHybridCacheClientTests.cs +++ b/tests/Foundatio.Redis.Tests/Caching/RedisHybridCacheClientTests.cs @@ -991,6 +991,12 @@ public override Task SetAllAsync_WithDifferentCasedKeys_CreatesDistinctEntries() return base.SetAllAsync_WithDifferentCasedKeys_CreatesDistinctEntries(); } + [Fact] + public override Task SetAllAsync_WithLargeNumberOfKeys_MeasuresThroughput() + { + return base.SetAllAsync_WithLargeNumberOfKeys_MeasuresThroughput(); + } + [Fact] public override Task SetAllAsync_WithEmptyItems_ReturnsTrue() { diff --git a/tests/Foundatio.Redis.Tests/Caching/ScopedRedisCacheClientTests.cs b/tests/Foundatio.Redis.Tests/Caching/ScopedRedisCacheClientTests.cs index 910bcbd..7fb3fd3 100644 --- a/tests/Foundatio.Redis.Tests/Caching/ScopedRedisCacheClientTests.cs +++ b/tests/Foundatio.Redis.Tests/Caching/ScopedRedisCacheClientTests.cs @@ -943,6 +943,12 @@ public override Task SetAllAsync_WithDifferentCasedKeys_CreatesDistinctEntries() return base.SetAllAsync_WithDifferentCasedKeys_CreatesDistinctEntries(); } + [Fact] + public override Task SetAllAsync_WithLargeNumberOfKeys_MeasuresThroughput() + { + return base.SetAllAsync_WithLargeNumberOfKeys_MeasuresThroughput(); + } + [Fact] public override Task SetAllAsync_WithEmptyItems_ReturnsTrue() { diff --git a/tests/Foundatio.Redis.Tests/Caching/ScopedRedisHybridCacheClientTests.cs b/tests/Foundatio.Redis.Tests/Caching/ScopedRedisHybridCacheClientTests.cs index f652762..e1048cb 100644 --- a/tests/Foundatio.Redis.Tests/Caching/ScopedRedisHybridCacheClientTests.cs +++ b/tests/Foundatio.Redis.Tests/Caching/ScopedRedisHybridCacheClientTests.cs @@ -998,6 +998,12 @@ public override Task SetAllAsync_WithDifferentCasedKeys_CreatesDistinctEntries() return base.SetAllAsync_WithDifferentCasedKeys_CreatesDistinctEntries(); } + [Fact] + public override Task SetAllAsync_WithLargeNumberOfKeys_MeasuresThroughput() + { + return base.SetAllAsync_WithLargeNumberOfKeys_MeasuresThroughput(); + } + [Fact] public override Task SetAllAsync_WithEmptyItems_ReturnsTrue() { From f4764ef9f2e0e526f6a53873f3a559a82dc21b73 Mon Sep 17 00:00:00 2001 From: Blake Niemyjski Date: Tue, 25 Nov 2025 14:39:54 +1300 Subject: [PATCH 16/26] Improves GetAllExpiration logic Adds null checks to the TTL results from the Lua script evaluations. This ensures that the code gracefully handles unexpected responses from Redis, preventing potential errors when processing the TTL values. --- src/Foundatio.Redis/Cache/RedisCacheClient.cs | 40 +++++++++---------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/src/Foundatio.Redis/Cache/RedisCacheClient.cs b/src/Foundatio.Redis/Cache/RedisCacheClient.cs index 3a1d6ee..6ed833a 100644 --- a/src/Foundatio.Redis/Cache/RedisCacheClient.cs +++ b/src/Foundatio.Redis/Cache/RedisCacheClient.cs @@ -835,7 +835,7 @@ public Task SetExpirationAsync(string key, TimeSpan expiresIn) ArgumentNullException.ThrowIfNull(keys); var keyList = keys.Where(k => !String.IsNullOrEmpty(k)).Distinct().ToList(); - if (keyList.Count == 0) + if (keyList.Count is 0) return ReadOnlyDictionary.Empty; await LoadScriptsAsync().AnyContext(); @@ -850,19 +850,20 @@ await Parallel.ForEachAsync( { var hashSlotKeys = hashSlotGroup.Select(k => (RedisKey)k).ToArray(); var redisResult = await Database.ScriptEvaluateAsync(_getAllExpiration.Hash, hashSlotKeys).AnyContext(); - if (redisResult.IsNull) return; // Lua script returns array of TTL values in milliseconds (in same order as keys) // -2 = key doesn't exist, -1 = no expiration, positive = TTL in ms - var ttls = (long[])redisResult; - for (int i = 0; i < hashSlotKeys.Length; i++) - { - string key = hashSlotKeys[i]; - long ttl = ttls[i]; + long[] ttls = (long[])redisResult; + if (ttls is null || ttls.Length != hashSlotKeys.Length) + throw new ArgumentException("Hash slot count mismatch"); - if (ttl >= 0) // Only include keys with positive TTL (exclude non-existent and persistent) + for (int hashSlotIndex = 0; hashSlotIndex < hashSlotKeys.Length; hashSlotIndex++) + { + string key = hashSlotKeys[hashSlotIndex]; + long ttl = ttls[hashSlotIndex]; + if (ttl >= 0) // Only include keys with positive TTL (exclude non-existent and persistent) result[key] = TimeSpan.FromMilliseconds(ttl); } }).AnyContext(); @@ -879,15 +880,16 @@ await Parallel.ForEachAsync( // Lua script returns array of TTL values in milliseconds (in same order as keys) // -2 = key doesn't exist, -1 = no expiration, positive = TTL in ms - var ttls = (long[])redisResult; - var result = new Dictionary(); + long[] ttls = (long[])redisResult; + if (ttls is null || ttls.Length != redisKeys.Length) + throw new ArgumentException("Hash slot count mismatch"); - for (int i = 0; i < redisKeys.Length; i++) + var result = new Dictionary(); + for (int keyIndex = 0; keyIndex < redisKeys.Length; keyIndex++) { - string key = redisKeys[i]; - long ttl = ttls[i]; - - if (ttl >= 0) // Only include keys with positive TTL (exclude non-existent and persistent) + string key = redisKeys[keyIndex]; + long ttl = ttls[keyIndex]; + if (ttl >= 0) // Only include keys with positive TTL (exclude non-existent and persistent) result[key] = TimeSpan.FromMilliseconds(ttl); } @@ -900,7 +902,6 @@ public async Task SetAllExpirationAsync(IDictionary expiratio ArgumentNullException.ThrowIfNull(expirations); var validExpirations = expirations.Where(kvp => !String.IsNullOrEmpty(kvp.Key)).ToList(); - if (validExpirations.Count == 0) return; @@ -979,10 +980,6 @@ private async Task LoadScriptsAsync() LoadedLuaScript GetAllExpiration, LoadedLuaScript SetAllExpiration)>>(); - // Limit parallelism to Environment.ProcessorCount - int maxParallelism = Math.Min(Environment.ProcessorCount, endpoints.Length); - var options = new ParallelOptions { MaxDegreeOfParallelism = maxParallelism }; - // Start script loading tasks for all endpoints foreach (var server in endpoints) { @@ -990,7 +987,8 @@ private async Task LoadScriptsAsync() continue; // Create and start task for this server - completely independent - var loadTask = Task.Run(async () => { + var loadTask = Task.Run(async () => + { // Load all scripts on this server var incr = await incrementWithExpire.LoadAsync(server).AnyContext(); var remove = await removeIfEqual.LoadAsync(server).AnyContext(); From 298f04a99139773bc0b6deb5c092b79335f3f347 Mon Sep 17 00:00:00 2001 From: Blake Niemyjski Date: Tue, 25 Nov 2025 14:59:57 +1300 Subject: [PATCH 17/26] PR Feedback --- tests/Foundatio.Redis.Tests/Caching/RedisCacheClientTests.cs | 2 ++ .../Caching/RedisHybridCacheClientTests.cs | 2 ++ .../Caching/ScopedRedisCacheClientTests.cs | 2 ++ .../Caching/ScopedRedisHybridCacheClientTests.cs | 2 ++ 4 files changed, 8 insertions(+) diff --git a/tests/Foundatio.Redis.Tests/Caching/RedisCacheClientTests.cs b/tests/Foundatio.Redis.Tests/Caching/RedisCacheClientTests.cs index 4bfffba..261bd2c 100644 --- a/tests/Foundatio.Redis.Tests/Caching/RedisCacheClientTests.cs +++ b/tests/Foundatio.Redis.Tests/Caching/RedisCacheClientTests.cs @@ -61,6 +61,7 @@ public override Task AddAsync_WithNullKey_ThrowsArgumentNullException() { return base.AddAsync_WithNullKey_ThrowsArgumentNullException(); } + [Fact(Skip = "Performance Test")] public override Task CacheOperations_WithMultipleTypes_MeasuresThroughput() { @@ -128,6 +129,7 @@ public override Task ExistsAsync_WithScopedCache_ChecksOnlyWithinScope() { return base.ExistsAsync_WithScopedCache_ChecksOnlyWithinScope(); } + [Fact] public override Task GetAllAsync_WithEmptyKeys_ReturnsEmpty() { diff --git a/tests/Foundatio.Redis.Tests/Caching/RedisHybridCacheClientTests.cs b/tests/Foundatio.Redis.Tests/Caching/RedisHybridCacheClientTests.cs index f81d78a..52255c1 100644 --- a/tests/Foundatio.Redis.Tests/Caching/RedisHybridCacheClientTests.cs +++ b/tests/Foundatio.Redis.Tests/Caching/RedisHybridCacheClientTests.cs @@ -70,6 +70,7 @@ public override Task AddAsync_WithNullKey_ThrowsArgumentNullException() { return base.AddAsync_WithNullKey_ThrowsArgumentNullException(); } + [Fact] public override Task CacheOperations_WithMultipleTypes_MeasuresThroughput() { @@ -143,6 +144,7 @@ public override Task ExistsAsync_WithScopedCache_ChecksOnlyWithinScope() { return base.ExistsAsync_WithScopedCache_ChecksOnlyWithinScope(); } + [Fact] public override Task GetAllAsync_WithEmptyKeys_ReturnsEmpty() { diff --git a/tests/Foundatio.Redis.Tests/Caching/ScopedRedisCacheClientTests.cs b/tests/Foundatio.Redis.Tests/Caching/ScopedRedisCacheClientTests.cs index 7fb3fd3..1e518a5 100644 --- a/tests/Foundatio.Redis.Tests/Caching/ScopedRedisCacheClientTests.cs +++ b/tests/Foundatio.Redis.Tests/Caching/ScopedRedisCacheClientTests.cs @@ -58,6 +58,7 @@ public override Task AddAsync_WithNullKey_ThrowsArgumentNullException() { return base.AddAsync_WithNullKey_ThrowsArgumentNullException(); } + [Fact(Skip = "Performance Test")] public override Task CacheOperations_WithMultipleTypes_MeasuresThroughput() { @@ -125,6 +126,7 @@ public override Task ExistsAsync_WithScopedCache_ChecksOnlyWithinScope() { return base.ExistsAsync_WithScopedCache_ChecksOnlyWithinScope(); } + [Fact] public override Task GetAllAsync_WithEmptyKeys_ReturnsEmpty() { diff --git a/tests/Foundatio.Redis.Tests/Caching/ScopedRedisHybridCacheClientTests.cs b/tests/Foundatio.Redis.Tests/Caching/ScopedRedisHybridCacheClientTests.cs index e1048cb..d474988 100644 --- a/tests/Foundatio.Redis.Tests/Caching/ScopedRedisHybridCacheClientTests.cs +++ b/tests/Foundatio.Redis.Tests/Caching/ScopedRedisHybridCacheClientTests.cs @@ -77,6 +77,7 @@ public override Task AddAsync_WithNullKey_ThrowsArgumentNullException() { return base.AddAsync_WithNullKey_ThrowsArgumentNullException(); } + [Fact] public override Task CacheOperations_WithMultipleTypes_MeasuresThroughput() { @@ -150,6 +151,7 @@ public override Task ExistsAsync_WithScopedCache_ChecksOnlyWithinScope() { return base.ExistsAsync_WithScopedCache_ChecksOnlyWithinScope(); } + [Fact] public override Task GetAllAsync_WithEmptyKeys_ReturnsEmpty() { From 0f11e249d29aa7d739367f60e50d7af55ebeba1a Mon Sep 17 00:00:00 2001 From: Blake Niemyjski Date: Thu, 27 Nov 2025 13:18:41 +1300 Subject: [PATCH 18/26] Updated test coverage --- .../Caching/RedisCacheClientTests.cs | 824 +++-------------- .../Caching/RedisHybridCacheClientTests.cs | 834 +++-------------- .../Caching/ScopedRedisCacheClientTests.cs | 832 ++--------------- .../ScopedRedisHybridCacheClientTests.cs | 844 +++--------------- 4 files changed, 419 insertions(+), 2915 deletions(-) diff --git a/tests/Foundatio.Redis.Tests/Caching/RedisCacheClientTests.cs b/tests/Foundatio.Redis.Tests/Caching/RedisCacheClientTests.cs index 261bd2c..8a34971 100644 --- a/tests/Foundatio.Redis.Tests/Caching/RedisCacheClientTests.cs +++ b/tests/Foundatio.Redis.Tests/Caching/RedisCacheClientTests.cs @@ -29,39 +29,18 @@ public override Task AddAsync_WithConcurrentRequests_OnlyOneSucceeds() } [Fact] - public override Task AddAsync_WithEmptyKey_ThrowsArgumentException() + public override Task AddAsync_WithInvalidKey_ThrowsArgumentException() { - return base.AddAsync_WithEmptyKey_ThrowsArgumentException(); + return base.AddAsync_WithInvalidKey_ThrowsArgumentException(); } [Theory] [InlineData("user:profile")] [InlineData(" ")] - public override Task AddAsync_WithExistingKey_ReturnsFalseAndPreservesValue(string cacheKey) + public override Task AddAsync_WhenKeyDoesNotExist_AddsValueAndReturnsTrue(string cacheKey) { - return base.AddAsync_WithExistingKey_ReturnsFalseAndPreservesValue(cacheKey); + return base.AddAsync_WhenKeyDoesNotExist_AddsValueAndReturnsTrue(cacheKey); } - - [Fact] - public override Task AddAsync_WithNestedKeyUsingSeparator_StoresCorrectly() - { - return base.AddAsync_WithNestedKeyUsingSeparator_StoresCorrectly(); - } - - [Theory] - [InlineData("user:profile")] - [InlineData(" ")] - public override Task AddAsync_WithValidKey_ReturnsTrue(string cacheKey) - { - return base.AddAsync_WithValidKey_ReturnsTrue(cacheKey); - } - - [Fact] - public override Task AddAsync_WithNullKey_ThrowsArgumentNullException() - { - return base.AddAsync_WithNullKey_ThrowsArgumentNullException(); - } - [Fact(Skip = "Performance Test")] public override Task CacheOperations_WithMultipleTypes_MeasuresThroughput() { @@ -74,122 +53,31 @@ public override Task CacheOperations_WithRepeatedSetAndGet_MeasuresThroughput() return base.CacheOperations_WithRepeatedSetAndGet_MeasuresThroughput(); } - [Fact] - public override Task ExistsAsync_AfterKeyExpires_ReturnsFalse() - { - return base.ExistsAsync_AfterKeyExpires_ReturnsFalse(); - } - - [Fact] - public override Task ExistsAsync_WithDifferentCasedKeys_ChecksExactMatch() - { - return base.ExistsAsync_WithDifferentCasedKeys_ChecksExactMatch(); - } - - [Fact] - public override Task ExistsAsync_WithEmptyKey_ThrowsArgumentException() - { - return base.ExistsAsync_WithEmptyKey_ThrowsArgumentException(); - } - - [Theory] - [InlineData("user:profile")] - [InlineData(" ")] - public override Task ExistsAsync_WithExistingKey_ReturnsTrue(string cacheKey) - { - return base.ExistsAsync_WithExistingKey_ReturnsTrue(cacheKey); - } - - [Fact] - public override Task ExistsAsync_WithExpiredKey_ReturnsFalse() - { - return base.ExistsAsync_WithExpiredKey_ReturnsFalse(); - } - - [Fact] - public override Task ExistsAsync_WithNonExistentKey_ReturnsFalse() - { - return base.ExistsAsync_WithNonExistentKey_ReturnsFalse(); - } - - [Fact] - public override Task ExistsAsync_WithNullKey_ThrowsArgumentNullException() - { - return base.ExistsAsync_WithNullKey_ThrowsArgumentNullException(); - } - - [Fact] - public override Task ExistsAsync_WithNullStoredValue_ReturnsTrue() - { - return base.ExistsAsync_WithNullStoredValue_ReturnsTrue(); - } - - [Fact] - public override Task ExistsAsync_WithScopedCache_ChecksOnlyWithinScope() - { - return base.ExistsAsync_WithScopedCache_ChecksOnlyWithinScope(); - } - - [Fact] - public override Task GetAllAsync_WithEmptyKeys_ReturnsEmpty() - { - return base.GetAllAsync_WithEmptyKeys_ReturnsEmpty(); - } - - [Theory] - [InlineData("test2")] - [InlineData(" ")] - public override Task GetAllAsync_WithExistingKeys_ReturnsAllValues(string cacheKey) - { - return base.GetAllAsync_WithExistingKeys_ReturnsAllValues(cacheKey); - } - - [Fact] - public override Task GetAllAsync_WithKeysContainingEmpty_ThrowsArgumentException() - { - return base.GetAllAsync_WithKeysContainingEmpty_ThrowsArgumentException(); - } - - [Fact] - public override Task GetAllAsync_WithKeysContainingNull_ThrowsArgumentNullException() + [Fact] + public override Task ExistsAsync_WithVariousKeys_ReturnsCorrectExistenceStatus() { - return base.GetAllAsync_WithKeysContainingNull_ThrowsArgumentNullException(); + return base.ExistsAsync_WithVariousKeys_ReturnsCorrectExistenceStatus(); } [Fact] - public override Task GetAllAsync_WithMixedCaseKeys_RetrievesExactMatches() - { - return base.GetAllAsync_WithMixedCaseKeys_RetrievesExactMatches(); - } + public override Task ExistsAsync_WithExpiredKey_ReturnsFalse() => base.ExistsAsync_WithExpiredKey_ReturnsFalse(); [Fact] - public override Task GetAllAsync_WithMixedObjectTypes_ReturnsCorrectValues() - { - return base.GetAllAsync_WithMixedObjectTypes_ReturnsCorrectValues(); - } + public override Task ExistsAsync_WithScopedCache_ChecksOnlyWithinScope() => base.ExistsAsync_WithScopedCache_ChecksOnlyWithinScope(); [Fact] - public override Task GetAllAsync_WithNonExistentKeys_ReturnsEmptyResults() - { - return base.GetAllAsync_WithNonExistentKeys_ReturnsEmptyResults(); - } - - [Fact] - public override Task GetAllAsync_WithNullKeys_ThrowsArgumentNullException() - { - return base.GetAllAsync_WithNullKeys_ThrowsArgumentNullException(); - } + public override Task ExistsAsync_WithInvalidKey_ThrowsArgumentException() => base.ExistsAsync_WithInvalidKey_ThrowsArgumentException(); [Fact] - public override Task GetAllAsync_WithNullValues_HandlesNullsCorrectly() + public override Task GetAllAsync_WithInvalidKeys_ValidatesCorrectly() { - return base.GetAllAsync_WithNullValues_HandlesNullsCorrectly(); + return base.GetAllAsync_WithInvalidKeys_ValidatesCorrectly(); } [Fact] - public override Task GetAllAsync_WithOverlappingKeys_UsesLatestValues() + public override Task GetAllAsync_WithMultipleKeys_ReturnsCorrectValues() { - return base.GetAllAsync_WithOverlappingKeys_UsesLatestValues(); + return base.GetAllAsync_WithMultipleKeys_ReturnsCorrectValues(); } [Fact] @@ -198,12 +86,6 @@ public override Task GetAllAsync_WithScopedCache_ReturnsUnscopedKeys() return base.GetAllAsync_WithScopedCache_ReturnsUnscopedKeys(); } - [Fact] - public override Task GetAllExpirationAsync_WithExpiredKeys_ExcludesExpiredKeys() - { - return base.GetAllExpirationAsync_WithExpiredKeys_ExcludesExpiredKeys(); - } - [Theory] [InlineData(1000)] [InlineData(10000)] @@ -213,143 +95,45 @@ public override Task GetAllExpirationAsync_WithLargeNumberOfKeys_ReturnsAllExpir } [Fact] - public override Task GetAllExpirationAsync_WithMixedKeys_ReturnsOnlyKeysWithExpiration() - { - return base.GetAllExpirationAsync_WithMixedKeys_ReturnsOnlyKeysWithExpiration(); - } - - [Fact] - public override Task GetAsync_WithComplexObject_PreservesAllProperties() - { - return base.GetAsync_WithComplexObject_PreservesAllProperties(); - } - - [Theory] - [InlineData("order:details")] - [InlineData(" ")] - public override Task GetAsync_WithComplexObject_ReturnsNewInstance(string cacheKey) - { - return base.GetAsync_WithComplexObject_ReturnsNewInstance(cacheKey); - } - - [Fact] - public override Task GetAsync_WithDifferentCasedKeys_TreatsAsDifferentKeys() - { - return base.GetAsync_WithDifferentCasedKeys_TreatsAsDifferentKeys(); - } - - [Fact] - public override Task GetAsync_WithEmptyKey_ThrowsArgumentException() + public override Task GetAllExpirationAsync_WithMixedKeys_ReturnsExpectedResults() { - return base.GetAsync_WithEmptyKey_ThrowsArgumentException(); + return base.GetAllExpirationAsync_WithMixedKeys_ReturnsExpectedResults(); } [Fact] - public override Task GetAsync_WithLargeNumber_ReturnsCorrectValue() + public override Task GetAsync_WithComplexObject_PreservesPropertiesAndReturnsNewInstance() { - return base.GetAsync_WithLargeNumber_ReturnsCorrectValue(); + return base.GetAsync_WithComplexObject_PreservesPropertiesAndReturnsNewInstance(); } [Fact] - public override Task GetAsync_WithMaxLongAsInt_ThrowsException() + public override Task GetAsync_WithInvalidKey_ThrowsArgumentException() { - return base.GetAsync_WithMaxLongAsInt_ThrowsException(); + return base.GetAsync_WithInvalidKey_ThrowsArgumentException(); } [Fact] - public override Task GetAsync_WithNonExistentKey_ReturnsNoValue() + public override Task GetAsync_WithNumericTypeConversion_ConvertsBetweenTypes() { - return base.GetAsync_WithNonExistentKey_ReturnsNoValue(); + return base.GetAsync_WithNumericTypeConversion_ConvertsBetweenTypes(); } [Fact] - public override Task GetAsync_WithNullKey_ThrowsArgumentNullException() + public override Task GetAsync_WithTryGetSemantics_HandlesTypeConversions() { - return base.GetAsync_WithNullKey_ThrowsArgumentNullException(); + return base.GetAsync_WithTryGetSemantics_HandlesTypeConversions(); } [Fact] - public override Task GetAsync_WithNumericTypeConversion_ConvertsIntToLong() + public override Task GetExpirationAsync_WithVariousKeyStates_ReturnsExpectedExpiration() { - return base.GetAsync_WithNumericTypeConversion_ConvertsIntToLong(); + return base.GetExpirationAsync_WithVariousKeyStates_ReturnsExpectedExpiration(); } [Fact] - public override Task GetAsync_WithNumericTypeConversion_ConvertsLongToInt() + public override Task GetExpirationAsync_WithInvalidKey_ThrowsArgumentException() { - return base.GetAsync_WithNumericTypeConversion_ConvertsLongToInt(); - } - - [Fact] - public override Task GetAsync_WithTryGetSemanticsAndComplexTypeAsLong_ReturnsNoValue() - { - return base.GetAsync_WithTryGetSemanticsAndComplexTypeAsLong_ReturnsNoValue(); - } - - [Fact] - public override Task GetAsync_WithTryGetSemanticsAndIntAsLong_ConvertsSuccessfully() - { - return base.GetAsync_WithTryGetSemanticsAndIntAsLong_ConvertsSuccessfully(); - } - - [Fact] - public override Task GetAsync_WithTryGetSemanticsAndMaxLongAsInt_ReturnsNoValue() - { - return base.GetAsync_WithTryGetSemanticsAndMaxLongAsInt_ReturnsNoValue(); - } - [Fact] - public override Task GetExpirationAsync_AfterExpiry_ReturnsNull() - { - return base.GetExpirationAsync_AfterExpiry_ReturnsNull(); - } - - [Fact] - public override Task GetExpirationAsync_WithDifferentCasedKeys_GetsExactMatch() - { - return base.GetExpirationAsync_WithDifferentCasedKeys_GetsExactMatch(); - } - - [Fact] - public override Task GetExpirationAsync_WithEmptyKey_ThrowsArgumentException() - { - return base.GetExpirationAsync_WithEmptyKey_ThrowsArgumentException(); - } - - [Theory] - [InlineData("token:refresh")] - [InlineData(" ")] - public override Task GetExpirationAsync_WithExpiration_ReturnsCorrectTimeSpan(string cacheKey) - { - return base.GetExpirationAsync_WithExpiration_ReturnsCorrectTimeSpan(cacheKey); - } - - [Fact] - public override Task GetExpirationAsync_WithExpiredKey_ReturnsNull() - { - return base.GetExpirationAsync_WithExpiredKey_ReturnsNull(); - } - - [Fact] - public override Task GetExpirationAsync_WithNoExpiration_ReturnsNull() - { - return base.GetExpirationAsync_WithNoExpiration_ReturnsNull(); - } - - [Fact] - public override Task GetExpirationAsync_WithNonExistentKey_ReturnsNull() - { - return base.GetExpirationAsync_WithNonExistentKey_ReturnsNull(); - } - - [Fact] - public override Task GetExpirationAsync_WithNullKey_ThrowsArgumentNullException() - { - return base.GetExpirationAsync_WithNullKey_ThrowsArgumentNullException(); - } - [Fact] - public override Task GetListAsync_WithEmptyKey_ThrowsArgumentException() - { - return base.GetListAsync_WithEmptyKey_ThrowsArgumentException(); + return base.GetExpirationAsync_WithInvalidKey_ThrowsArgumentException(); } [Fact] @@ -359,42 +143,17 @@ public override Task GetListAsync_WithExpiredItems_RemovesExpiredAndReturnsActiv } [Fact] - public override Task GetListAsync_WithInvalidPageNumber_ThrowsArgumentOutOfRangeException() + public override Task GetListAsync_WithInvalidKey_ThrowsArgumentException() { - return base.GetListAsync_WithInvalidPageNumber_ThrowsArgumentOutOfRangeException(); + return base.GetListAsync_WithInvalidKey_ThrowsArgumentException(); } [Fact] - public override Task GetListAsync_WithMultiplePages_ReturnsAllItems() + public override Task GetListAsync_WithPaging_ReturnsCorrectResults() { - return base.GetListAsync_WithMultiplePages_ReturnsAllItems(); + return base.GetListAsync_WithPaging_ReturnsCorrectResults(); } - [Fact] - public override Task GetListAsync_WithNewItemsAdded_ReturnsNewItemsLast() - { - return base.GetListAsync_WithNewItemsAdded_ReturnsNewItemsLast(); - } - - [Fact] - public override Task GetListAsync_WithNullKey_ThrowsArgumentNullException() - { - return base.GetListAsync_WithNullKey_ThrowsArgumentNullException(); - } - - [Fact] - public override Task GetListAsync_WithPageBeyondEnd_ReturnsEmptyCollection() - { - return base.GetListAsync_WithPageBeyondEnd_ReturnsEmptyCollection(); - } - - [Theory] - [InlineData("cart:items")] - [InlineData(" ")] - public override Task GetListAsync_WithPaging_ReturnsCorrectPageSize(string cacheKey) - { - return base.GetListAsync_WithPaging_ReturnsCorrectPageSize(cacheKey); - } [Fact] public override Task GetUnixTimeMillisecondsAsync_WithLocalDateTime_ReturnsCorrectly() { @@ -413,26 +172,6 @@ public override Task GetUnixTimeSecondsAsync_WithUtcDateTime_ReturnsCorrectly() return base.GetUnixTimeSecondsAsync_WithUtcDateTime_ReturnsCorrectly(); } - [Fact] - public override Task IncrementAsync_WithDifferentCasedKeys_IncrementsDistinctCounters() - { - return base.IncrementAsync_WithDifferentCasedKeys_IncrementsDistinctCounters(); - } - - [Fact] - public override Task IncrementAsync_WithEmptyKey_ThrowsArgumentException() - { - return base.IncrementAsync_WithEmptyKey_ThrowsArgumentException(); - } - - [Theory] - [InlineData("metrics:page-views")] - [InlineData(" ")] - public override Task IncrementAsync_WithExistingKey_IncrementsValue(string cacheKey) - { - return base.IncrementAsync_WithExistingKey_IncrementsValue(cacheKey); - } - [Fact] public override Task IncrementAsync_WithExpiration_ExpiresCorrectly() { @@ -440,15 +179,15 @@ public override Task IncrementAsync_WithExpiration_ExpiresCorrectly() } [Fact] - public override Task IncrementAsync_WithNonExistentKey_InitializesToOne() + public override Task IncrementAsync_WithInvalidKey_ThrowsException() { - return base.IncrementAsync_WithNonExistentKey_InitializesToOne(); + return base.IncrementAsync_WithInvalidKey_ThrowsException(); } [Fact] - public override Task IncrementAsync_WithNullKey_ThrowsArgumentNullException() + public override Task IncrementAsync_WithKey_IncrementsCorrectly() { - return base.IncrementAsync_WithNullKey_ThrowsArgumentNullException(); + return base.IncrementAsync_WithKey_IncrementsCorrectly(); } [Fact] @@ -458,88 +197,15 @@ public override Task IncrementAsync_WithScopedCache_WorksWithinScope() } [Fact] - public override Task IncrementAsync_WithSpecifiedAmount_IncrementsCorrectly() - { - return base.IncrementAsync_WithSpecifiedAmount_IncrementsCorrectly(); - } - [Fact] - public override Task ListAddAsync_WithDifferentCasedKeys_MaintainsDistinctLists() - { - return base.ListAddAsync_WithDifferentCasedKeys_MaintainsDistinctLists(); - } - - [Theory] - [InlineData("cart:items")] - [InlineData(" ")] - public override Task ListAddAsync_WithDuplicates_RemovesDuplicatesAndAddsItems(string cacheKey) + public override Task ListAddAsync_WithExpiration_ExpiresCorrectly() { - return base.ListAddAsync_WithDuplicates_RemovesDuplicatesAndAddsItems(cacheKey); + return base.ListAddAsync_WithExpiration_ExpiresCorrectly(); } [Fact] - public override Task ListAddAsync_WithDuplicates_StoresUniqueValuesOnly() + public override Task ListAddAsync_WithInvalidArguments_ThrowsException() { - return base.ListAddAsync_WithDuplicates_StoresUniqueValuesOnly(); - } - - [Fact] - public override Task ListAddAsync_WithEmptyCollection_NoOp() - { - return base.ListAddAsync_WithEmptyCollection_NoOp(); - } - - [Fact] - public override Task ListAddAsync_WithEmptyKey_ThrowsArgumentException() - { - return base.ListAddAsync_WithEmptyKey_ThrowsArgumentException(); - } - - [Fact] - public override Task ListAddAsync_WithExistingNonListKey_ThrowsException() - { - return base.ListAddAsync_WithExistingNonListKey_ThrowsException(); - } - - [Fact] - public override Task ListAddAsync_WithFutureExpiration_AddsAndExpiresCorrectly() - { - return base.ListAddAsync_WithFutureExpiration_AddsAndExpiresCorrectly(); - } - - [Fact] - public override Task ListAddAsync_WithMultipleExpirations_ExpiresIndividualItems() - { - return base.ListAddAsync_WithMultipleExpirations_ExpiresIndividualItems(); - } - - [Fact] - public override Task ListAddAsync_WithNullCollection_ThrowsArgumentNullException() - { - return base.ListAddAsync_WithNullCollection_ThrowsArgumentNullException(); - } - - [Fact] - public override Task ListAddAsync_WithNullItem_IgnoresNull() - { - return base.ListAddAsync_WithNullItem_IgnoresNull(); - } - - [Fact] - public override Task ListAddAsync_WithNullKey_ThrowsArgumentNullException() - { - return base.ListAddAsync_WithNullKey_ThrowsArgumentNullException(); - } - - [Fact] - public override Task ListAddAsync_WithNullValues_ThrowsArgumentNullException() - { - return base.ListAddAsync_WithNullValues_ThrowsArgumentNullException(); - } - - [Fact] - public override Task ListAddAsync_WithPastExpiration_RemovesItem() - { - return base.ListAddAsync_WithPastExpiration_RemovesItem(); + return base.ListAddAsync_WithInvalidArguments_ThrowsException(); } [Fact] @@ -547,48 +213,17 @@ public override Task ListAddAsync_WithSingleString_StoresAsStringNotCharArray() { return base.ListAddAsync_WithSingleString_StoresAsStringNotCharArray(); } - [Fact] - public override Task ListRemoveAsync_WithEmptyKey_ThrowsArgumentException() - { - return base.ListRemoveAsync_WithEmptyKey_ThrowsArgumentException(); - } - - [Theory] - [InlineData("cart:items")] - [InlineData(" ")] - public override Task ListRemoveAsync_WithMultipleValues_RemovesAll(string cacheKey) - { - return base.ListRemoveAsync_WithMultipleValues_RemovesAll(cacheKey); - } - - [Fact] - public override Task ListRemoveAsync_WithNullCollection_ThrowsArgumentNullException() - { - return base.ListRemoveAsync_WithNullCollection_ThrowsArgumentNullException(); - } [Fact] - public override Task ListRemoveAsync_WithNullItem_IgnoresNull() + public override Task ListAddAsync_WithVariousInputs_HandlesCorrectly() { - return base.ListRemoveAsync_WithNullItem_IgnoresNull(); + return base.ListAddAsync_WithVariousInputs_HandlesCorrectly(); } [Fact] - public override Task ListRemoveAsync_WithNullKey_ThrowsArgumentNullException() + public override Task ListRemoveAsync_WithInvalidInputs_ThrowsAppropriateException() { - return base.ListRemoveAsync_WithNullKey_ThrowsArgumentNullException(); - } - - [Fact] - public override Task ListRemoveAsync_WithNullValues_ThrowsArgumentNullException() - { - return base.ListRemoveAsync_WithNullValues_ThrowsArgumentNullException(); - } - - [Fact] - public override Task ListRemoveAsync_WithSingleValue_RemovesCorrectly() - { - return base.ListRemoveAsync_WithSingleValue_RemovesCorrectly(); + return base.ListRemoveAsync_WithInvalidInputs_ThrowsAppropriateException(); } [Fact] @@ -596,22 +231,16 @@ public override Task ListRemoveAsync_WithValidValues_RemovesKeyWhenEmpty() { return base.ListRemoveAsync_WithValidValues_RemovesKeyWhenEmpty(); } - [Fact] - public override Task RemoveAllAsync_WithEmptyKeys_Succeeds() - { - return base.RemoveAllAsync_WithEmptyKeys_Succeeds(); - } [Fact] - public override Task RemoveAllAsync_WithKeysContainingEmpty_ThrowsArgumentException() + public override Task ListRemoveAsync_WithValues_RemovesCorrectly() { - return base.RemoveAllAsync_WithKeysContainingEmpty_ThrowsArgumentException(); + return base.ListRemoveAsync_WithValues_RemovesCorrectly(); } - [Fact] - public override Task RemoveAllAsync_WithKeysContainingNull_ThrowsArgumentNullException() + public override Task RemoveAllAsync_WithInvalidKeys_ValidatesCorrectly() { - return base.RemoveAllAsync_WithKeysContainingNull_ThrowsArgumentNullException(); + return base.RemoveAllAsync_WithInvalidKeys_ValidatesCorrectly(); } [Fact] @@ -620,80 +249,34 @@ public override Task RemoveAllAsync_WithLargeNumberOfKeys_RemovesAllKeysEfficien return base.RemoveAllAsync_WithLargeNumberOfKeys_RemovesAllKeysEfficiently(); } - [Fact] - public override Task RemoveAllAsync_WithMixedCaseKeys_RemovesOnlyExactMatches() - { - return base.RemoveAllAsync_WithMixedCaseKeys_RemovesOnlyExactMatches(); - } - - [Fact] - public override Task RemoveAllAsync_WithNullKeys_RemovesAllValues() - { - return base.RemoveAllAsync_WithNullKeys_RemovesAllValues(); - } - [Fact] public override Task RemoveAllAsync_WithScopedCache_AffectsOnlyScopedKeys() { return base.RemoveAllAsync_WithScopedCache_AffectsOnlyScopedKeys(); } - [Theory] - [InlineData("remove-all-keys:")] - [InlineData(" ")] - public override Task RemoveAllAsync_WithSpecificKeyCollection_RemovesOnlySpecifiedKeys(string keyPrefix) - { - return base.RemoveAllAsync_WithSpecificKeyCollection_RemovesOnlySpecifiedKeys(keyPrefix); - } - [Fact] - public override Task RemoveAsync_AfterSetAndGet_RemovesCorrectly() + public override Task RemoveAllAsync_WithSpecificKeyCollection_RemovesOnlySpecifiedKeys() { - return base.RemoveAsync_AfterSetAndGet_RemovesCorrectly(); + return base.RemoveAllAsync_WithSpecificKeyCollection_RemovesOnlySpecifiedKeys(); } [Fact] - public override Task RemoveAsync_MultipleTimes_Succeeds() + public override Task RemoveAsync_WithInvalidKey_ThrowsArgumentException() { - return base.RemoveAsync_MultipleTimes_Succeeds(); + return base.RemoveAsync_WithInvalidKey_ThrowsArgumentException(); } [Fact] - public override Task RemoveAsync_WithEmptyKey_ThrowsArgumentException() + public override Task RemoveAsync_WithNonExistentKey_ReturnsFalse() { - return base.RemoveAsync_WithEmptyKey_ThrowsArgumentException(); - } - - [Theory] - [InlineData("session:active")] - [InlineData(" ")] - public override Task RemoveAsync_WithExistingKey_RemovesSuccessfully(string cacheKey) - { - return base.RemoveAsync_WithExistingKey_RemovesSuccessfully(cacheKey); - } - - [Fact] - public override Task RemoveAsync_WithExpiredKey_Succeeds() - { - return base.RemoveAsync_WithExpiredKey_Succeeds(); - } - - [Fact] - public override Task RemoveAsync_WithNonExistentKey_Succeeds() - { - return base.RemoveAsync_WithNonExistentKey_Succeeds(); + return base.RemoveAsync_WithNonExistentKey_ReturnsFalse(); } [Fact] - public override Task RemoveAsync_WithNullKey_ThrowsArgumentNullException() + public override Task RemoveAsync_WithExpiredKey_KeyDoesNotExist() { - return base.RemoveAsync_WithNullKey_ThrowsArgumentNullException(); - } - - [Fact] - public override Task RemoveAsync_WithNullValue_RemovesSuccessfully() - { - return base.RemoveAsync_WithNullValue_RemovesSuccessfully(); + return base.RemoveAsync_WithExpiredKey_KeyDoesNotExist(); } [Fact] @@ -703,14 +286,9 @@ public override Task RemoveAsync_WithScopedCache_RemovesOnlyWithinScope() } [Fact] - public override Task RemoveAsync_WithSpecificCase_RemovesOnlyMatchingKey() - { - return base.RemoveAsync_WithSpecificCase_RemovesOnlyMatchingKey(); - } - [Fact] - public override Task RemoveByPrefixAsync_AsteriskPrefixWithScopedCache_TreatedAsLiteral() + public override Task RemoveAsync_WithValidKey_RemovesSuccessfully() { - return base.RemoveByPrefixAsync_AsteriskPrefixWithScopedCache_TreatedAsLiteral(); + return base.RemoveAsync_WithValidKey_RemovesSuccessfully(); } [Theory] @@ -738,27 +316,9 @@ public override Task RemoveByPrefixAsync_PartialPrefixWithScopedCache_RemovesMat } [Fact] - public override Task RemoveByPrefixAsync_WithCaseSensitivePrefix_RemovesOnlyMatchingCase() + public override Task RemoveByPrefixAsync_WithAsteriskPrefix_TreatedAsLiteral() { - return base.RemoveByPrefixAsync_WithCaseSensitivePrefix_RemovesOnlyMatchingCase(); - } - - [Fact] - public override Task RemoveByPrefixAsync_WithDifferentCasedScopes_RemovesOnlyMatchingScope() - { - return base.RemoveByPrefixAsync_WithDifferentCasedScopes_RemovesOnlyMatchingScope(); - } - - [Fact] - public override Task RemoveByPrefixAsync_WithDoubleAsteriskPrefix_TreatsAsLiteral() - { - return base.RemoveByPrefixAsync_WithDoubleAsteriskPrefix_TreatsAsLiteral(); - } - - [Fact] - public override Task RemoveByPrefixAsync_WithEmptyPrefix_RemovesAllKeys() - { - return base.RemoveByPrefixAsync_WithEmptyPrefix_RemovesAllKeys(); + return base.RemoveByPrefixAsync_WithAsteriskPrefix_TreatedAsLiteral(); } [Theory] @@ -768,12 +328,10 @@ public override Task RemoveByPrefixAsync_WithLineEndingPrefix_TreatsAsLiteral(st return base.RemoveByPrefixAsync_WithLineEndingPrefix_TreatsAsLiteral(lineEndingPrefix); } - [Theory] - [InlineData("blah:")] - [InlineData(" ")] - public override Task RemoveByPrefixAsync_WithMatchingPrefix_RemovesOnlyPrefixedKeys(string prefix) + [Fact] + public override Task RemoveByPrefixAsync_WithMatchingPrefix_RemovesOnlyMatchingKeys() { - return base.RemoveByPrefixAsync_WithMatchingPrefix_RemovesOnlyPrefixedKeys(prefix); + return base.RemoveByPrefixAsync_WithMatchingPrefix_RemovesOnlyMatchingKeys(); } [Theory] @@ -787,15 +345,9 @@ public override Task RemoveByPrefixAsync_WithMultipleMatchingKeys_RemovesOnlyPre } [Fact] - public override Task RemoveByPrefixAsync_WithNonMatchingPrefix_RemovesZeroKeys() - { - return base.RemoveByPrefixAsync_WithNonMatchingPrefix_RemovesZeroKeys(); - } - - [Fact] - public override Task RemoveByPrefixAsync_WithNullPrefix_RemovesAllKeys() + public override Task RemoveByPrefixAsync_WithNullOrEmptyPrefix_RemovesAllKeys() { - return base.RemoveByPrefixAsync_WithNullPrefix_RemovesAllKeys(); + return base.RemoveByPrefixAsync_WithNullOrEmptyPrefix_RemovesAllKeys(); } [Theory] @@ -826,17 +378,15 @@ public override Task RemoveByPrefixAsync_WithWildcardPattern_TreatsAsLiteral(str } [Fact] - public override Task RemoveIfEqualAsync_WithEmptyKey_ThrowsArgumentException() + public override Task RemoveIfEqualAsync_WithInvalidKey_ThrowsArgumentException() { - return base.RemoveIfEqualAsync_WithEmptyKey_ThrowsArgumentException(); + return base.RemoveIfEqualAsync_WithInvalidKey_ThrowsArgumentException(); } - [Theory] - [InlineData("session:active")] - [InlineData(" ")] - public override Task RemoveIfEqualAsync_WithMatchingValue_ReturnsTrueAndRemoves(string cacheKey) + [Fact] + public override Task RemoveIfEqualAsync_WithMatchingValue_ReturnsTrueAndRemoves() { - return base.RemoveIfEqualAsync_WithMatchingValue_ReturnsTrueAndRemoves(cacheKey); + return base.RemoveIfEqualAsync_WithMatchingValue_ReturnsTrueAndRemoves(); } [Fact] @@ -846,35 +396,15 @@ public override Task RemoveIfEqualAsync_WithMismatchedValue_ReturnsFalseAndDoesN } [Fact] - public override Task RemoveIfEqualAsync_WithNullKey_ThrowsArgumentNullException() - { - return base.RemoveIfEqualAsync_WithNullKey_ThrowsArgumentNullException(); - } - - [Fact] - public override Task ReplaceAsync_WithDifferentCasedKeys_TreatsAsDifferentKeys() - { - return base.ReplaceAsync_WithDifferentCasedKeys_TreatsAsDifferentKeys(); - } - - [Fact] - public override Task ReplaceAsync_WithEmptyKey_ThrowsArgumentException() - { - return base.ReplaceAsync_WithEmptyKey_ThrowsArgumentException(); - } - - [Theory] - [InlineData("settings:theme")] - [InlineData(" ")] - public override Task ReplaceAsync_WithExistingKey_ReturnsTrueAndReplacesValue(string cacheKey) + public override Task ReplaceAsync_WithExistingKey_ReturnsTrueAndReplacesValue() { - return base.ReplaceAsync_WithExistingKey_ReturnsTrueAndReplacesValue(cacheKey); + return base.ReplaceAsync_WithExistingKey_ReturnsTrueAndReplacesValue(); } [Fact] - public override Task ReplaceAsync_WithExpiration_SetsExpirationCorrectly() + public override Task ReplaceAsync_WithInvalidKey_ThrowsException() { - return base.ReplaceAsync_WithExpiration_SetsExpirationCorrectly(); + return base.ReplaceAsync_WithInvalidKey_ThrowsException(); } [Fact] @@ -884,35 +414,21 @@ public override Task ReplaceAsync_WithNonExistentKey_ReturnsFalseAndDoesNotCreat } [Fact] - public override Task ReplaceAsync_WithNullKey_ThrowsArgumentNullException() - { - return base.ReplaceAsync_WithNullKey_ThrowsArgumentNullException(); - } - - [Fact] - public override Task ReplaceIfEqualAsync_WithDifferentCasedKeys_ReplacesOnlyExactMatch() + public override Task ReplaceIfEqualAsync_WithExpiration_SetsExpirationCorrectly() { - return base.ReplaceIfEqualAsync_WithDifferentCasedKeys_ReplacesOnlyExactMatch(); + return base.ReplaceIfEqualAsync_WithExpiration_SetsExpirationCorrectly(); } [Fact] - public override Task ReplaceIfEqualAsync_WithEmptyKey_ThrowsArgumentException() + public override Task ReplaceIfEqualAsync_WithInvalidKey_ThrowsArgumentException() { - return base.ReplaceIfEqualAsync_WithEmptyKey_ThrowsArgumentException(); + return base.ReplaceIfEqualAsync_WithInvalidKey_ThrowsArgumentException(); } [Fact] - public override Task ReplaceIfEqualAsync_WithExpiration_SetsExpirationCorrectly() - { - return base.ReplaceIfEqualAsync_WithExpiration_SetsExpirationCorrectly(); - } - - [Theory] - [InlineData("workflow:state")] - [InlineData(" ")] - public override Task ReplaceIfEqualAsync_WithMatchingOldValue_ReturnsTrueAndReplacesValue(string cacheKey) + public override Task ReplaceIfEqualAsync_WithMatchingOldValue_ReturnsTrueAndReplacesValue() { - return base.ReplaceIfEqualAsync_WithMatchingOldValue_ReturnsTrueAndReplacesValue(cacheKey); + return base.ReplaceIfEqualAsync_WithMatchingOldValue_ReturnsTrueAndReplacesValue(); } [Fact] @@ -921,12 +437,6 @@ public override Task ReplaceIfEqualAsync_WithMismatchedOldValue_ReturnsFalseAndD return base.ReplaceIfEqualAsync_WithMismatchedOldValue_ReturnsFalseAndDoesNotReplace(); } - [Fact] - public override Task ReplaceIfEqualAsync_WithNullKey_ThrowsArgumentNullException() - { - return base.ReplaceIfEqualAsync_WithNullKey_ThrowsArgumentNullException(); - } - [Fact(Skip = "Performance Test")] public override Task Serialization_WithComplexObjectsAndValidation_MeasuresThroughput() { @@ -940,15 +450,15 @@ public override Task Serialization_WithSimpleObjectsAndValidation_MeasuresThroug } [Fact] - public override Task SetAllAsync_WithDateTimeMinValue_DoesNotAddKeys() + public override Task SetAllAsync_WithExpiration_KeysExpireCorrectly() { - return base.SetAllAsync_WithDateTimeMinValue_DoesNotAddKeys(); + return base.SetAllAsync_WithExpiration_KeysExpireCorrectly(); } [Fact] - public override Task SetAllAsync_WithDifferentCasedKeys_CreatesDistinctEntries() + public override Task SetAllAsync_WithInvalidItems_ValidatesCorrectly() { - return base.SetAllAsync_WithDifferentCasedKeys_CreatesDistinctEntries(); + return base.SetAllAsync_WithInvalidItems_ValidatesCorrectly(); } [Fact] @@ -957,88 +467,24 @@ public override Task SetAllAsync_WithLargeNumberOfKeys_MeasuresThroughput() return base.SetAllAsync_WithLargeNumberOfKeys_MeasuresThroughput(); } - [Fact] - public override Task SetAllAsync_WithEmptyItems_ReturnsTrue() - { - return base.SetAllAsync_WithEmptyItems_ReturnsTrue(); - } - - [Theory] - [InlineData("test")] - [InlineData(" ")] - public override Task SetAllAsync_WithExpiration_KeysExpireCorrectly(string cacheKey) - { - return base.SetAllAsync_WithExpiration_KeysExpireCorrectly(cacheKey); - } - - [Fact] - public override Task SetAllAsync_WithItemsContainingEmptyKey_ThrowsArgumentException() - { - return base.SetAllAsync_WithItemsContainingEmptyKey_ThrowsArgumentException(); - } - - [Fact] - public override Task SetAllAsync_WithNullItems_ThrowsArgumentNullException() - { - return base.SetAllAsync_WithNullItems_ThrowsArgumentNullException(); - } - [Theory] [InlineData(1000)] [InlineData(10000)] - public override Task SetAllExpiration_WithLargeNumberOfKeys_SetsAllExpirations(int count) - { - return base.SetAllExpiration_WithLargeNumberOfKeys_SetsAllExpirations(count); - } - - [Fact] - public override Task SetAllExpiration_WithMultipleKeys_SetsExpirationForAll() - { - return base.SetAllExpiration_WithMultipleKeys_SetsExpirationForAll(); - } - - [Fact] - public override Task SetAllExpiration_WithNonExistentKeys_HandlesGracefully() + public override Task SetAllExpirationAsync_WithLargeNumberOfKeys_SetsAllExpirations(int count) { - return base.SetAllExpiration_WithNonExistentKeys_HandlesGracefully(); + return base.SetAllExpirationAsync_WithLargeNumberOfKeys_SetsAllExpirations(count); } [Fact] - public override Task SetAllExpiration_WithNullValues_RemovesExpiration() - { - return base.SetAllExpiration_WithNullValues_RemovesExpiration(); - } - - [Theory] - [InlineData("user:profile")] - [InlineData(" ")] - public override Task SetAsync_WithComplexObject_StoresCorrectly(string cacheKey) + public override Task SetAllExpirationAsync_WithMixedExpirations_SetsExpirationsCorrectly() { - return base.SetAsync_WithComplexObject_StoresCorrectly(cacheKey); + return base.SetAllExpirationAsync_WithMixedExpirations_SetsExpirationsCorrectly(); } [Fact] - public override Task SetAsync_WithDifferentCasedKeys_CreatesDistinctEntries() + public override Task SetAsync_WithComplexObject_StoresCorrectly() { - return base.SetAsync_WithDifferentCasedKeys_CreatesDistinctEntries(); - } - - [Fact] - public override Task SetAsync_WithDifferentCasedScopes_MaintainsDistinctNamespaces() - { - return base.SetAsync_WithDifferentCasedScopes_MaintainsDistinctNamespaces(); - } - - [Fact] - public override Task SetAsync_WithDifferentScopes_IsolatesKeys() - { - return base.SetAsync_WithDifferentScopes_IsolatesKeys(); - } - - [Fact] - public override Task SetAsync_WithEmptyKey_ThrowsArgumentException() - { - return base.SetAsync_WithEmptyKey_ThrowsArgumentException(); + return base.SetAsync_WithComplexObject_StoresCorrectly(); } [Fact] @@ -1048,9 +494,9 @@ public override Task SetAsync_WithExpirationEdgeCases_HandlesCorrectly() } [Fact] - public override Task SetAsync_WithLargeNumber_StoresCorrectly() + public override Task SetAsync_WithInvalidKey_ThrowsArgumentException() { - return base.SetAsync_WithLargeNumber_StoresCorrectly(); + return base.SetAsync_WithInvalidKey_ThrowsArgumentException(); } [Fact] @@ -1060,27 +506,15 @@ public override Task SetAsync_WithLargeNumbersAndExpiration_PreservesValues() } [Fact] - public override Task SetAsync_WithNestedScopes_PreservesHierarchy() - { - return base.SetAsync_WithNestedScopes_PreservesHierarchy(); - } - - [Fact] - public override Task SetAsync_WithNullKey_ThrowsArgumentNullException() - { - return base.SetAsync_WithNullKey_ThrowsArgumentNullException(); - } - - [Fact] - public override Task SetAsync_WithNullReferenceType_StoresAsNullValue() + public override Task SetAsync_WithNullValue_StoresAsNullValue() { - return base.SetAsync_WithNullReferenceType_StoresAsNullValue(); + return base.SetAsync_WithNullValue_StoresAsNullValue(); } [Fact] - public override Task SetAsync_WithNullValueType_StoresAsNullValue() + public override Task SetAsync_WithScopedCaches_IsolatesKeys() { - return base.SetAsync_WithNullValueType_StoresAsNullValue(); + return base.SetAsync_WithScopedCaches_IsolatesKeys(); } [Fact] @@ -1088,24 +522,17 @@ public override Task SetAsync_WithShortExpiration_ExpiresCorrectly() { return base.SetAsync_WithShortExpiration_ExpiresCorrectly(); } - [Theory] - [InlineData("token:refresh")] - [InlineData(" ")] - public override Task SetExpirationAsync_ChangingFromNoExpirationToFutureTime_UpdatesCorrectly(string cacheKey) - { - return base.SetExpirationAsync_ChangingFromNoExpirationToFutureTime_UpdatesCorrectly(cacheKey); - } [Fact] - public override Task SetExpirationAsync_ChangingToDateTimeMinValue_RemovesKey() + public override Task SetExpirationAsync_ChangingFromNoExpirationToFutureTime_UpdatesCorrectly() { - return base.SetExpirationAsync_ChangingToDateTimeMinValue_RemovesKey(); + return base.SetExpirationAsync_ChangingFromNoExpirationToFutureTime_UpdatesCorrectly(); } [Fact] - public override Task SetExpirationAsync_WithCurrentTime_ExpiresImmediately() + public override Task SetExpirationAsync_ChangingToDateTimeMinValue_RemovesKey() { - return base.SetExpirationAsync_WithCurrentTime_ExpiresImmediately(); + return base.SetExpirationAsync_ChangingToDateTimeMinValue_RemovesKey(); } [Fact] @@ -1115,38 +542,15 @@ public override Task SetExpirationAsync_WithDateTimeMaxValue_NeverExpires() } [Fact] - public override Task SetExpirationAsync_WithDateTimeMinValue_ExpiresImmediately() + public override Task SetExpirationAsync_WithInvalidKey_ThrowsArgumentException() { - return base.SetExpirationAsync_WithDateTimeMinValue_ExpiresImmediately(); + return base.SetExpirationAsync_WithInvalidKey_ThrowsArgumentException(); } [Fact] - public override Task SetExpirationAsync_WithDifferentCasedKeys_SetsOnlyExactMatch() - { - return base.SetExpirationAsync_WithDifferentCasedKeys_SetsOnlyExactMatch(); - } - - [Fact] - public override Task SetExpirationAsync_WithEmptyKey_ThrowsArgumentException() - { - return base.SetExpirationAsync_WithEmptyKey_ThrowsArgumentException(); - } - - [Fact] - public override Task SetExpirationAsync_WithNullKey_ThrowsArgumentNullException() - { - return base.SetExpirationAsync_WithNullKey_ThrowsArgumentNullException(); - } - [Fact] - public override Task SetIfHigherAsync_WithDateTime_DoesNotUpdateWhenLower() + public override Task SetExpirationAsync_WithPastOrCurrentTime_ExpiresImmediately() { - return base.SetIfHigherAsync_WithDateTime_DoesNotUpdateWhenLower(); - } - - [Fact] - public override Task SetIfHigherAsync_WithDateTime_InitializesWhenKeyNotExists() - { - return base.SetIfHigherAsync_WithDateTime_InitializesWhenKeyNotExists(); + return base.SetExpirationAsync_WithPastOrCurrentTime_ExpiresImmediately(); } [Fact] @@ -1156,21 +560,9 @@ public override Task SetIfHigherAsync_WithDateTime_UpdatesWhenHigher() } [Fact] - public override Task SetIfHigherAsync_WithLargeNumbers_UpdatesWhenHigher() - { - return base.SetIfHigherAsync_WithLargeNumbers_UpdatesWhenHigher(); - } - - [Fact] - public override Task SetIfLowerAsync_WithDateTime_DoesNotUpdateWhenHigher() - { - return base.SetIfLowerAsync_WithDateTime_DoesNotUpdateWhenHigher(); - } - - [Fact] - public override Task SetIfLowerAsync_WithDateTime_InitializesWhenKeyNotExists() + public override Task SetIfHigherAsync_WithLargeNumbers_HandlesCorrectly() { - return base.SetIfLowerAsync_WithDateTime_InitializesWhenKeyNotExists(); + return base.SetIfHigherAsync_WithLargeNumbers_HandlesCorrectly(); } [Fact] @@ -1180,9 +572,9 @@ public override Task SetIfLowerAsync_WithDateTime_UpdatesWhenLower() } [Fact] - public override Task SetIfLowerAsync_WithLargeNumbers_UpdatesWhenLower() + public override Task SetIfLowerAsync_WithLargeNumbers_HandlesCorrectly() { - return base.SetIfLowerAsync_WithLargeNumbers_UpdatesWhenLower(); + return base.SetIfLowerAsync_WithLargeNumbers_HandlesCorrectly(); } [Fact] diff --git a/tests/Foundatio.Redis.Tests/Caching/RedisHybridCacheClientTests.cs b/tests/Foundatio.Redis.Tests/Caching/RedisHybridCacheClientTests.cs index 52255c1..9e54382 100644 --- a/tests/Foundatio.Redis.Tests/Caching/RedisHybridCacheClientTests.cs +++ b/tests/Foundatio.Redis.Tests/Caching/RedisHybridCacheClientTests.cs @@ -32,17 +32,9 @@ public override Task AddAsync_WithConcurrentRequests_OnlyOneSucceeds() } [Fact] - public override Task AddAsync_WithEmptyKey_ThrowsArgumentException() + public override Task AddAsync_WithInvalidKey_ThrowsArgumentException() { - return base.AddAsync_WithEmptyKey_ThrowsArgumentException(); - } - - [Theory] - [InlineData("user:profile")] - [InlineData(" ")] - public override Task AddAsync_WithExistingKey_ReturnsFalseAndPreservesValue(string cacheKey) - { - return base.AddAsync_WithExistingKey_ReturnsFalseAndPreservesValue(cacheKey); + return base.AddAsync_WithInvalidKey_ThrowsArgumentException(); } [Fact(Skip = "TODO: Look into this as the local cache client maintenance never schedules for expiration")] @@ -51,136 +43,51 @@ public override Task AddAsync_WithExpiration_ExpiresRemoteItems() return base.AddAsync_WithExpiration_ExpiresRemoteItems(); } - [Fact] - public override Task AddAsync_WithNestedKeyUsingSeparator_StoresCorrectly() - { - return base.AddAsync_WithNestedKeyUsingSeparator_StoresCorrectly(); - } - [Theory] [InlineData("user:profile")] [InlineData(" ")] - public override Task AddAsync_WithValidKey_ReturnsTrue(string cacheKey) - { - return base.AddAsync_WithValidKey_ReturnsTrue(cacheKey); - } - - [Fact] - public override Task AddAsync_WithNullKey_ThrowsArgumentNullException() + public override Task AddAsync_WhenKeyDoesNotExist_AddsValueAndReturnsTrue(string cacheKey) { - return base.AddAsync_WithNullKey_ThrowsArgumentNullException(); + return base.AddAsync_WhenKeyDoesNotExist_AddsValueAndReturnsTrue(cacheKey); } - [Fact] + [Fact(Skip = "Performance Test")] public override Task CacheOperations_WithMultipleTypes_MeasuresThroughput() { return base.CacheOperations_WithMultipleTypes_MeasuresThroughput(); } - [Fact] + [Fact(Skip = "Performance Test")] public override Task CacheOperations_WithRepeatedSetAndGet_MeasuresThroughput() { return base.CacheOperations_WithRepeatedSetAndGet_MeasuresThroughput(); } - [Fact] - public override Task ExistsAsync_AfterKeyExpires_ReturnsFalse() + [Fact] + public override Task ExistsAsync_WithVariousKeys_ReturnsCorrectExistenceStatus() { - return base.ExistsAsync_AfterKeyExpires_ReturnsFalse(); + return base.ExistsAsync_WithVariousKeys_ReturnsCorrectExistenceStatus(); } [Fact] - public override Task ExistsAsync_WithDifferentCasedKeys_ChecksExactMatch() - { - return base.ExistsAsync_WithDifferentCasedKeys_ChecksExactMatch(); - } + public override Task ExistsAsync_WithExpiredKey_ReturnsFalse() => base.ExistsAsync_WithExpiredKey_ReturnsFalse(); [Fact] - public override Task ExistsAsync_WithEmptyKey_ThrowsArgumentException() - { - return base.ExistsAsync_WithEmptyKey_ThrowsArgumentException(); - } - - [Theory] - [InlineData("user:profile")] - [InlineData(" ")] - public override Task ExistsAsync_WithExistingKey_ReturnsTrue(string cacheKey) - { - return base.ExistsAsync_WithExistingKey_ReturnsTrue(cacheKey); - } + public override Task ExistsAsync_WithScopedCache_ChecksOnlyWithinScope() => base.ExistsAsync_WithScopedCache_ChecksOnlyWithinScope(); [Fact] - public override Task ExistsAsync_WithExpiredKey_ReturnsFalse() - { - return base.ExistsAsync_WithExpiredKey_ReturnsFalse(); - } - - [Fact] - public override Task ExistsAsync_WithLocalCache_ChecksLocalCacheFirst() - { - return base.ExistsAsync_WithLocalCache_ChecksLocalCacheFirst(); - } - - [Fact] - public override Task ExistsAsync_WithNonExistentKey_ReturnsFalse() - { - return base.ExistsAsync_WithNonExistentKey_ReturnsFalse(); - } - - [Fact] - public override Task ExistsAsync_WithNullKey_ThrowsArgumentNullException() - { - return base.ExistsAsync_WithNullKey_ThrowsArgumentNullException(); - } + public override Task ExistsAsync_WithInvalidKey_ThrowsArgumentException() => base.ExistsAsync_WithInvalidKey_ThrowsArgumentException(); [Fact] - public override Task ExistsAsync_WithNullStoredValue_ReturnsTrue() + public override Task GetAllAsync_WithInvalidKeys_ValidatesCorrectly() { - return base.ExistsAsync_WithNullStoredValue_ReturnsTrue(); + return base.GetAllAsync_WithInvalidKeys_ValidatesCorrectly(); } [Fact] - public override Task ExistsAsync_WithScopedCache_ChecksOnlyWithinScope() + public override Task GetAllAsync_WithMultipleKeys_ReturnsCorrectValues() { - return base.ExistsAsync_WithScopedCache_ChecksOnlyWithinScope(); - } - - [Fact] - public override Task GetAllAsync_WithEmptyKeys_ReturnsEmpty() - { - return base.GetAllAsync_WithEmptyKeys_ReturnsEmpty(); - } - - [Theory] - [InlineData("test2")] - [InlineData(" ")] - public override Task GetAllAsync_WithExistingKeys_ReturnsAllValues(string cacheKey) - { - return base.GetAllAsync_WithExistingKeys_ReturnsAllValues(cacheKey); - } - - [Fact] - public override Task GetAllAsync_WithKeysContainingEmpty_ThrowsArgumentException() - { - return base.GetAllAsync_WithKeysContainingEmpty_ThrowsArgumentException(); - } - - [Fact] - public override Task GetAllAsync_WithKeysContainingNull_ThrowsArgumentNullException() - { - return base.GetAllAsync_WithKeysContainingNull_ThrowsArgumentNullException(); - } - - [Fact] - public override Task GetAllAsync_WithMixedCaseKeys_RetrievesExactMatches() - { - return base.GetAllAsync_WithMixedCaseKeys_RetrievesExactMatches(); - } - - [Fact] - public override Task GetAllAsync_WithMixedObjectTypes_ReturnsCorrectValues() - { - return base.GetAllAsync_WithMixedObjectTypes_ReturnsCorrectValues(); + return base.GetAllAsync_WithMultipleKeys_ReturnsCorrectValues(); } [Fact] @@ -189,42 +96,12 @@ public override Task GetAllAsync_WithMultipleKeys_UsesHybridCache() return base.GetAllAsync_WithMultipleKeys_UsesHybridCache(); } - [Fact] - public override Task GetAllAsync_WithNonExistentKeys_ReturnsEmptyResults() - { - return base.GetAllAsync_WithNonExistentKeys_ReturnsEmptyResults(); - } - - [Fact] - public override Task GetAllAsync_WithNullKeys_ThrowsArgumentNullException() - { - return base.GetAllAsync_WithNullKeys_ThrowsArgumentNullException(); - } - - [Fact] - public override Task GetAllAsync_WithNullValues_HandlesNullsCorrectly() - { - return base.GetAllAsync_WithNullValues_HandlesNullsCorrectly(); - } - - [Fact] - public override Task GetAllAsync_WithOverlappingKeys_UsesLatestValues() - { - return base.GetAllAsync_WithOverlappingKeys_UsesLatestValues(); - } - [Fact] public override Task GetAllAsync_WithScopedCache_ReturnsUnscopedKeys() { return base.GetAllAsync_WithScopedCache_ReturnsUnscopedKeys(); } - [Fact] - public override Task GetAllExpirationAsync_WithExpiredKeys_ExcludesExpiredKeys() - { - return base.GetAllExpirationAsync_WithExpiredKeys_ExcludesExpiredKeys(); - } - [Theory] [InlineData(1000)] [InlineData(10000)] @@ -234,114 +111,45 @@ public override Task GetAllExpirationAsync_WithLargeNumberOfKeys_ReturnsAllExpir } [Fact] - public override Task GetAllExpirationAsync_WithMixedKeys_ReturnsOnlyKeysWithExpiration() - { - return base.GetAllExpirationAsync_WithMixedKeys_ReturnsOnlyKeysWithExpiration(); - } - - [Fact] - public override Task GetAsync_WithComplexObject_PreservesAllProperties() - { - return base.GetAsync_WithComplexObject_PreservesAllProperties(); - } - - [Theory] - [InlineData("order:details")] - [InlineData(" ")] - public override Task GetAsync_WithComplexObject_ReturnsNewInstance(string cacheKey) - { - return base.GetAsync_WithComplexObject_ReturnsNewInstance(cacheKey); - } - - [Fact] - public override Task GetAsync_WithDifferentCasedKeys_TreatsAsDifferentKeys() + public override Task GetAllExpirationAsync_WithMixedKeys_ReturnsExpectedResults() { - return base.GetAsync_WithDifferentCasedKeys_TreatsAsDifferentKeys(); + return base.GetAllExpirationAsync_WithMixedKeys_ReturnsExpectedResults(); } [Fact] - public override Task GetAsync_WithEmptyKey_ThrowsArgumentException() + public override Task GetAsync_WithComplexObject_PreservesPropertiesAndReturnsNewInstance() { - return base.GetAsync_WithEmptyKey_ThrowsArgumentException(); + return base.GetAsync_WithComplexObject_PreservesPropertiesAndReturnsNewInstance(); } [Fact] - public override Task GetAsync_WithLargeNumber_ReturnsCorrectValue() + public override Task GetAsync_WithInvalidKey_ThrowsArgumentException() { - return base.GetAsync_WithLargeNumber_ReturnsCorrectValue(); + return base.GetAsync_WithInvalidKey_ThrowsArgumentException(); } [Fact] - public override Task GetAsync_WithMaxLongAsInt_ThrowsException() + public override Task GetAsync_WithNumericTypeConversion_ConvertsBetweenTypes() { - return base.GetAsync_WithMaxLongAsInt_ThrowsException(); + return base.GetAsync_WithNumericTypeConversion_ConvertsBetweenTypes(); } [Fact] - public override Task GetAsync_WithNonExistentKey_ReturnsNoValue() + public override Task GetAsync_WithTryGetSemantics_HandlesTypeConversions() { - return base.GetAsync_WithNonExistentKey_ReturnsNoValue(); + return base.GetAsync_WithTryGetSemantics_HandlesTypeConversions(); } [Fact] - public override Task GetAsync_WithNullKey_ThrowsArgumentNullException() + public override Task GetExpirationAsync_WithVariousKeyStates_ReturnsExpectedExpiration() { - return base.GetAsync_WithNullKey_ThrowsArgumentNullException(); + return base.GetExpirationAsync_WithVariousKeyStates_ReturnsExpectedExpiration(); } [Fact] - public override Task GetAsync_WithNumericTypeConversion_ConvertsIntToLong() + public override Task GetExpirationAsync_WithInvalidKey_ThrowsArgumentException() { - return base.GetAsync_WithNumericTypeConversion_ConvertsIntToLong(); - } - - [Fact] - public override Task GetAsync_WithNumericTypeConversion_ConvertsLongToInt() - { - return base.GetAsync_WithNumericTypeConversion_ConvertsLongToInt(); - } - - [Fact] - public override Task GetAsync_WithTryGetSemanticsAndComplexTypeAsLong_ReturnsNoValue() - { - return base.GetAsync_WithTryGetSemanticsAndComplexTypeAsLong_ReturnsNoValue(); - } - - [Fact] - public override Task GetAsync_WithTryGetSemanticsAndIntAsLong_ConvertsSuccessfully() - { - return base.GetAsync_WithTryGetSemanticsAndIntAsLong_ConvertsSuccessfully(); - } - - [Fact] - public override Task GetAsync_WithTryGetSemanticsAndMaxLongAsInt_ReturnsNoValue() - { - return base.GetAsync_WithTryGetSemanticsAndMaxLongAsInt_ReturnsNoValue(); - } - [Fact] - public override Task GetExpirationAsync_AfterExpiry_ReturnsNull() - { - return base.GetExpirationAsync_AfterExpiry_ReturnsNull(); - } - - [Fact] - public override Task GetExpirationAsync_WithDifferentCasedKeys_GetsExactMatch() - { - return base.GetExpirationAsync_WithDifferentCasedKeys_GetsExactMatch(); - } - - [Fact] - public override Task GetExpirationAsync_WithEmptyKey_ThrowsArgumentException() - { - return base.GetExpirationAsync_WithEmptyKey_ThrowsArgumentException(); - } - - [Theory] - [InlineData("token:refresh")] - [InlineData(" ")] - public override Task GetExpirationAsync_WithExpiration_ReturnsCorrectTimeSpan(string cacheKey) - { - return base.GetExpirationAsync_WithExpiration_ReturnsCorrectTimeSpan(cacheKey); + return base.GetExpirationAsync_WithInvalidKey_ThrowsArgumentException(); } [Fact] @@ -350,35 +158,6 @@ public override Task GetExpirationAsync_WithLocalCache_ChecksLocalCacheFirst() return base.GetExpirationAsync_WithLocalCache_ChecksLocalCacheFirst(); } - [Fact] - public override Task GetExpirationAsync_WithExpiredKey_ReturnsNull() - { - return base.GetExpirationAsync_WithExpiredKey_ReturnsNull(); - } - - [Fact] - public override Task GetExpirationAsync_WithNoExpiration_ReturnsNull() - { - return base.GetExpirationAsync_WithNoExpiration_ReturnsNull(); - } - - [Fact] - public override Task GetExpirationAsync_WithNonExistentKey_ReturnsNull() - { - return base.GetExpirationAsync_WithNonExistentKey_ReturnsNull(); - } - - [Fact] - public override Task GetExpirationAsync_WithNullKey_ThrowsArgumentNullException() - { - return base.GetExpirationAsync_WithNullKey_ThrowsArgumentNullException(); - } - [Fact] - public override Task GetListAsync_WithEmptyKey_ThrowsArgumentException() - { - return base.GetListAsync_WithEmptyKey_ThrowsArgumentException(); - } - [Fact] public override Task GetListAsync_WithExpiredItems_RemovesExpiredAndReturnsActive() { @@ -386,42 +165,17 @@ public override Task GetListAsync_WithExpiredItems_RemovesExpiredAndReturnsActiv } [Fact] - public override Task GetListAsync_WithInvalidPageNumber_ThrowsArgumentOutOfRangeException() - { - return base.GetListAsync_WithInvalidPageNumber_ThrowsArgumentOutOfRangeException(); - } - - [Fact] - public override Task GetListAsync_WithMultiplePages_ReturnsAllItems() - { - return base.GetListAsync_WithMultiplePages_ReturnsAllItems(); - } - - [Fact] - public override Task GetListAsync_WithNewItemsAdded_ReturnsNewItemsLast() + public override Task GetListAsync_WithInvalidKey_ThrowsArgumentException() { - return base.GetListAsync_WithNewItemsAdded_ReturnsNewItemsLast(); + return base.GetListAsync_WithInvalidKey_ThrowsArgumentException(); } [Fact] - public override Task GetListAsync_WithNullKey_ThrowsArgumentNullException() + public override Task GetListAsync_WithPaging_ReturnsCorrectResults() { - return base.GetListAsync_WithNullKey_ThrowsArgumentNullException(); + return base.GetListAsync_WithPaging_ReturnsCorrectResults(); } - [Fact] - public override Task GetListAsync_WithPageBeyondEnd_ReturnsEmptyCollection() - { - return base.GetListAsync_WithPageBeyondEnd_ReturnsEmptyCollection(); - } - - [Theory] - [InlineData("cart:items")] - [InlineData(" ")] - public override Task GetListAsync_WithPaging_ReturnsCorrectPageSize(string cacheKey) - { - return base.GetListAsync_WithPaging_ReturnsCorrectPageSize(cacheKey); - } [Fact] public override Task GetUnixTimeMillisecondsAsync_WithLocalDateTime_ReturnsCorrectly() { @@ -440,26 +194,6 @@ public override Task GetUnixTimeSecondsAsync_WithUtcDateTime_ReturnsCorrectly() return base.GetUnixTimeSecondsAsync_WithUtcDateTime_ReturnsCorrectly(); } - [Fact] - public override Task IncrementAsync_WithDifferentCasedKeys_IncrementsDistinctCounters() - { - return base.IncrementAsync_WithDifferentCasedKeys_IncrementsDistinctCounters(); - } - - [Fact] - public override Task IncrementAsync_WithEmptyKey_ThrowsArgumentException() - { - return base.IncrementAsync_WithEmptyKey_ThrowsArgumentException(); - } - - [Theory] - [InlineData("metrics:page-views")] - [InlineData(" ")] - public override Task IncrementAsync_WithExistingKey_IncrementsValue(string cacheKey) - { - return base.IncrementAsync_WithExistingKey_IncrementsValue(cacheKey); - } - [Fact] public override Task IncrementAsync_WithExpiration_ExpiresCorrectly() { @@ -467,15 +201,15 @@ public override Task IncrementAsync_WithExpiration_ExpiresCorrectly() } [Fact] - public override Task IncrementAsync_WithNonExistentKey_InitializesToOne() + public override Task IncrementAsync_WithInvalidKey_ThrowsException() { - return base.IncrementAsync_WithNonExistentKey_InitializesToOne(); + return base.IncrementAsync_WithInvalidKey_ThrowsException(); } [Fact] - public override Task IncrementAsync_WithNullKey_ThrowsArgumentNullException() + public override Task IncrementAsync_WithKey_IncrementsCorrectly() { - return base.IncrementAsync_WithNullKey_ThrowsArgumentNullException(); + return base.IncrementAsync_WithKey_IncrementsCorrectly(); } [Fact] @@ -485,58 +219,15 @@ public override Task IncrementAsync_WithScopedCache_WorksWithinScope() } [Fact] - public override Task IncrementAsync_WithSpecifiedAmount_IncrementsCorrectly() - { - return base.IncrementAsync_WithSpecifiedAmount_IncrementsCorrectly(); - } - [Fact] - public override Task ListAddAsync_WithDifferentCasedKeys_MaintainsDistinctLists() - { - return base.ListAddAsync_WithDifferentCasedKeys_MaintainsDistinctLists(); - } - - [Theory] - [InlineData("cart:items")] - [InlineData(" ")] - public override Task ListAddAsync_WithDuplicates_RemovesDuplicatesAndAddsItems(string cacheKey) - { - return base.ListAddAsync_WithDuplicates_RemovesDuplicatesAndAddsItems(cacheKey); - } - - [Fact] - public override Task ListAddAsync_WithDuplicates_StoresUniqueValuesOnly() - { - return base.ListAddAsync_WithDuplicates_StoresUniqueValuesOnly(); - } - - [Fact] - public override Task ListAddAsync_WithEmptyCollection_NoOp() - { - return base.ListAddAsync_WithEmptyCollection_NoOp(); - } - - [Fact] - public override Task ListAddAsync_WithEmptyKey_ThrowsArgumentException() - { - return base.ListAddAsync_WithEmptyKey_ThrowsArgumentException(); - } - - [Fact] - public override Task ListAddAsync_WithExistingNonListKey_ThrowsException() + public override Task ListAddAsync_WithExpiration_ExpiresCorrectly() { - return base.ListAddAsync_WithExistingNonListKey_ThrowsException(); + return base.ListAddAsync_WithExpiration_ExpiresCorrectly(); } [Fact] - public override Task ListAddAsync_WithFutureExpiration_AddsAndExpiresCorrectly() + public override Task ListAddAsync_WithInvalidArguments_ThrowsException() { - return base.ListAddAsync_WithFutureExpiration_AddsAndExpiresCorrectly(); - } - - [Fact] - public override Task ListAddAsync_WithMultipleExpirations_ExpiresIndividualItems() - { - return base.ListAddAsync_WithMultipleExpirations_ExpiresIndividualItems(); + return base.ListAddAsync_WithInvalidArguments_ThrowsException(); } [Fact] @@ -545,83 +236,22 @@ public override Task ListAddAsync_WithMultipleInstances_WorksCorrectly() return base.ListAddAsync_WithMultipleInstances_WorksCorrectly(); } - [Fact] - public override Task ListAddAsync_WithNullCollection_ThrowsArgumentNullException() - { - return base.ListAddAsync_WithNullCollection_ThrowsArgumentNullException(); - } - - [Fact] - public override Task ListAddAsync_WithNullItem_IgnoresNull() - { - return base.ListAddAsync_WithNullItem_IgnoresNull(); - } - - [Fact] - public override Task ListAddAsync_WithNullKey_ThrowsArgumentNullException() - { - return base.ListAddAsync_WithNullKey_ThrowsArgumentNullException(); - } - - [Fact] - public override Task ListAddAsync_WithNullValues_ThrowsArgumentNullException() - { - return base.ListAddAsync_WithNullValues_ThrowsArgumentNullException(); - } - - [Fact] - public override Task ListAddAsync_WithPastExpiration_RemovesItem() - { - return base.ListAddAsync_WithPastExpiration_RemovesItem(); - } - [Fact] public override Task ListAddAsync_WithSingleString_StoresAsStringNotCharArray() { return base.ListAddAsync_WithSingleString_StoresAsStringNotCharArray(); } - [Fact] - public override Task ListRemoveAsync_WithEmptyKey_ThrowsArgumentException() - { - return base.ListRemoveAsync_WithEmptyKey_ThrowsArgumentException(); - } - - [Theory] - [InlineData("cart:items")] - [InlineData(" ")] - public override Task ListRemoveAsync_WithMultipleValues_RemovesAll(string cacheKey) - { - return base.ListRemoveAsync_WithMultipleValues_RemovesAll(cacheKey); - } [Fact] - public override Task ListRemoveAsync_WithNullCollection_ThrowsArgumentNullException() + public override Task ListAddAsync_WithVariousInputs_HandlesCorrectly() { - return base.ListRemoveAsync_WithNullCollection_ThrowsArgumentNullException(); + return base.ListAddAsync_WithVariousInputs_HandlesCorrectly(); } [Fact] - public override Task ListRemoveAsync_WithNullItem_IgnoresNull() + public override Task ListRemoveAsync_WithInvalidInputs_ThrowsAppropriateException() { - return base.ListRemoveAsync_WithNullItem_IgnoresNull(); - } - - [Fact] - public override Task ListRemoveAsync_WithNullKey_ThrowsArgumentNullException() - { - return base.ListRemoveAsync_WithNullKey_ThrowsArgumentNullException(); - } - - [Fact] - public override Task ListRemoveAsync_WithNullValues_ThrowsArgumentNullException() - { - return base.ListRemoveAsync_WithNullValues_ThrowsArgumentNullException(); - } - - [Fact] - public override Task ListRemoveAsync_WithSingleValue_RemovesCorrectly() - { - return base.ListRemoveAsync_WithSingleValue_RemovesCorrectly(); + return base.ListRemoveAsync_WithInvalidInputs_ThrowsAppropriateException(); } [Fact] @@ -629,22 +259,16 @@ public override Task ListRemoveAsync_WithValidValues_RemovesKeyWhenEmpty() { return base.ListRemoveAsync_WithValidValues_RemovesKeyWhenEmpty(); } - [Fact] - public override Task RemoveAllAsync_WithEmptyKeys_Succeeds() - { - return base.RemoveAllAsync_WithEmptyKeys_Succeeds(); - } [Fact] - public override Task RemoveAllAsync_WithKeysContainingEmpty_ThrowsArgumentException() + public override Task ListRemoveAsync_WithValues_RemovesCorrectly() { - return base.RemoveAllAsync_WithKeysContainingEmpty_ThrowsArgumentException(); + return base.ListRemoveAsync_WithValues_RemovesCorrectly(); } - [Fact] - public override Task RemoveAllAsync_WithKeysContainingNull_ThrowsArgumentNullException() + public override Task RemoveAllAsync_WithInvalidKeys_ValidatesCorrectly() { - return base.RemoveAllAsync_WithKeysContainingNull_ThrowsArgumentNullException(); + return base.RemoveAllAsync_WithInvalidKeys_ValidatesCorrectly(); } [Fact] @@ -659,80 +283,34 @@ public override Task RemoveAllAsync_WithLocalCache_InvalidatesLocalCache() return base.RemoveAllAsync_WithLocalCache_InvalidatesLocalCache(); } - [Fact] - public override Task RemoveAllAsync_WithMixedCaseKeys_RemovesOnlyExactMatches() - { - return base.RemoveAllAsync_WithMixedCaseKeys_RemovesOnlyExactMatches(); - } - - [Fact] - public override Task RemoveAllAsync_WithNullKeys_RemovesAllValues() - { - return base.RemoveAllAsync_WithNullKeys_RemovesAllValues(); - } - [Fact] public override Task RemoveAllAsync_WithScopedCache_AffectsOnlyScopedKeys() { return base.RemoveAllAsync_WithScopedCache_AffectsOnlyScopedKeys(); } - [Theory] - [InlineData("remove-all-keys:")] - [InlineData(" ")] - public override Task RemoveAllAsync_WithSpecificKeyCollection_RemovesOnlySpecifiedKeys(string keyPrefix) - { - return base.RemoveAllAsync_WithSpecificKeyCollection_RemovesOnlySpecifiedKeys(keyPrefix); - } - - [Fact] - public override Task RemoveAsync_AfterSetAndGet_RemovesCorrectly() - { - return base.RemoveAsync_AfterSetAndGet_RemovesCorrectly(); - } - - [Fact] - public override Task RemoveAsync_MultipleTimes_Succeeds() - { - return base.RemoveAsync_MultipleTimes_Succeeds(); - } - - [Fact] - public override Task RemoveAsync_WithEmptyKey_ThrowsArgumentException() - { - return base.RemoveAsync_WithEmptyKey_ThrowsArgumentException(); - } - - [Theory] - [InlineData("session:active")] - [InlineData(" ")] - public override Task RemoveAsync_WithExistingKey_RemovesSuccessfully(string cacheKey) - { - return base.RemoveAsync_WithExistingKey_RemovesSuccessfully(cacheKey); - } - [Fact] - public override Task RemoveAsync_WithExpiredKey_Succeeds() + public override Task RemoveAllAsync_WithSpecificKeyCollection_RemovesOnlySpecifiedKeys() { - return base.RemoveAsync_WithExpiredKey_Succeeds(); + return base.RemoveAllAsync_WithSpecificKeyCollection_RemovesOnlySpecifiedKeys(); } [Fact] - public override Task RemoveAsync_WithNonExistentKey_Succeeds() + public override Task RemoveAsync_WithInvalidKey_ThrowsArgumentException() { - return base.RemoveAsync_WithNonExistentKey_Succeeds(); + return base.RemoveAsync_WithInvalidKey_ThrowsArgumentException(); } [Fact] - public override Task RemoveAsync_WithNullKey_ThrowsArgumentNullException() + public override Task RemoveAsync_WithNonExistentKey_ReturnsFalse() { - return base.RemoveAsync_WithNullKey_ThrowsArgumentNullException(); + return base.RemoveAsync_WithNonExistentKey_ReturnsFalse(); } [Fact] - public override Task RemoveAsync_WithNullValue_RemovesSuccessfully() + public override Task RemoveAsync_WithExpiredKey_KeyDoesNotExist() { - return base.RemoveAsync_WithNullValue_RemovesSuccessfully(); + return base.RemoveAsync_WithExpiredKey_KeyDoesNotExist(); } [Fact] @@ -742,14 +320,9 @@ public override Task RemoveAsync_WithScopedCache_RemovesOnlyWithinScope() } [Fact] - public override Task RemoveAsync_WithSpecificCase_RemovesOnlyMatchingKey() + public override Task RemoveAsync_WithValidKey_RemovesSuccessfully() { - return base.RemoveAsync_WithSpecificCase_RemovesOnlyMatchingKey(); - } - [Fact] - public override Task RemoveByPrefixAsync_AsteriskPrefixWithScopedCache_TreatedAsLiteral() - { - return base.RemoveByPrefixAsync_AsteriskPrefixWithScopedCache_TreatedAsLiteral(); + return base.RemoveAsync_WithValidKey_RemovesSuccessfully(); } [Theory] @@ -777,27 +350,9 @@ public override Task RemoveByPrefixAsync_PartialPrefixWithScopedCache_RemovesMat } [Fact] - public override Task RemoveByPrefixAsync_WithCaseSensitivePrefix_RemovesOnlyMatchingCase() - { - return base.RemoveByPrefixAsync_WithCaseSensitivePrefix_RemovesOnlyMatchingCase(); - } - - [Fact] - public override Task RemoveByPrefixAsync_WithDifferentCasedScopes_RemovesOnlyMatchingScope() + public override Task RemoveByPrefixAsync_WithAsteriskPrefix_TreatedAsLiteral() { - return base.RemoveByPrefixAsync_WithDifferentCasedScopes_RemovesOnlyMatchingScope(); - } - - [Fact] - public override Task RemoveByPrefixAsync_WithDoubleAsteriskPrefix_TreatsAsLiteral() - { - return base.RemoveByPrefixAsync_WithDoubleAsteriskPrefix_TreatsAsLiteral(); - } - - [Fact] - public override Task RemoveByPrefixAsync_WithEmptyPrefix_RemovesAllKeys() - { - return base.RemoveByPrefixAsync_WithEmptyPrefix_RemovesAllKeys(); + return base.RemoveByPrefixAsync_WithAsteriskPrefix_TreatedAsLiteral(); } [Theory] @@ -813,12 +368,10 @@ public override Task RemoveByPrefixAsync_WithLocalCache_InvalidatesLocalCache() return base.RemoveByPrefixAsync_WithLocalCache_InvalidatesLocalCache(); } - [Theory] - [InlineData("blah:")] - [InlineData(" ")] - public override Task RemoveByPrefixAsync_WithMatchingPrefix_RemovesOnlyPrefixedKeys(string prefix) + [Fact] + public override Task RemoveByPrefixAsync_WithMatchingPrefix_RemovesOnlyMatchingKeys() { - return base.RemoveByPrefixAsync_WithMatchingPrefix_RemovesOnlyPrefixedKeys(prefix); + return base.RemoveByPrefixAsync_WithMatchingPrefix_RemovesOnlyMatchingKeys(); } [Theory] @@ -832,15 +385,9 @@ public override Task RemoveByPrefixAsync_WithMultipleMatchingKeys_RemovesOnlyPre } [Fact] - public override Task RemoveByPrefixAsync_WithNonMatchingPrefix_RemovesZeroKeys() - { - return base.RemoveByPrefixAsync_WithNonMatchingPrefix_RemovesZeroKeys(); - } - - [Fact] - public override Task RemoveByPrefixAsync_WithNullPrefix_RemovesAllKeys() + public override Task RemoveByPrefixAsync_WithNullOrEmptyPrefix_RemovesAllKeys() { - return base.RemoveByPrefixAsync_WithNullPrefix_RemovesAllKeys(); + return base.RemoveByPrefixAsync_WithNullOrEmptyPrefix_RemovesAllKeys(); } [Theory] @@ -871,17 +418,15 @@ public override Task RemoveByPrefixAsync_WithWildcardPattern_TreatsAsLiteral(str } [Fact] - public override Task RemoveIfEqualAsync_WithEmptyKey_ThrowsArgumentException() + public override Task RemoveIfEqualAsync_WithInvalidKey_ThrowsArgumentException() { - return base.RemoveIfEqualAsync_WithEmptyKey_ThrowsArgumentException(); + return base.RemoveIfEqualAsync_WithInvalidKey_ThrowsArgumentException(); } - [Theory] - [InlineData("session:active")] - [InlineData(" ")] - public override Task RemoveIfEqualAsync_WithMatchingValue_ReturnsTrueAndRemoves(string cacheKey) + [Fact] + public override Task RemoveIfEqualAsync_WithMatchingValue_ReturnsTrueAndRemoves() { - return base.RemoveIfEqualAsync_WithMatchingValue_ReturnsTrueAndRemoves(cacheKey); + return base.RemoveIfEqualAsync_WithMatchingValue_ReturnsTrueAndRemoves(); } [Fact] @@ -891,34 +436,15 @@ public override Task RemoveIfEqualAsync_WithMismatchedValue_ReturnsFalseAndDoesN } [Fact] - public override Task RemoveIfEqualAsync_WithNullKey_ThrowsArgumentNullException() - { - return base.RemoveIfEqualAsync_WithNullKey_ThrowsArgumentNullException(); - } - [Fact] - public override Task ReplaceAsync_WithDifferentCasedKeys_TreatsAsDifferentKeys() - { - return base.ReplaceAsync_WithDifferentCasedKeys_TreatsAsDifferentKeys(); - } - - [Fact] - public override Task ReplaceAsync_WithEmptyKey_ThrowsArgumentException() + public override Task ReplaceAsync_WithExistingKey_ReturnsTrueAndReplacesValue() { - return base.ReplaceAsync_WithEmptyKey_ThrowsArgumentException(); - } - - [Theory] - [InlineData("settings:theme")] - [InlineData(" ")] - public override Task ReplaceAsync_WithExistingKey_ReturnsTrueAndReplacesValue(string cacheKey) - { - return base.ReplaceAsync_WithExistingKey_ReturnsTrueAndReplacesValue(cacheKey); + return base.ReplaceAsync_WithExistingKey_ReturnsTrueAndReplacesValue(); } [Fact] - public override Task ReplaceAsync_WithExpiration_SetsExpirationCorrectly() + public override Task ReplaceAsync_WithInvalidKey_ThrowsException() { - return base.ReplaceAsync_WithExpiration_SetsExpirationCorrectly(); + return base.ReplaceAsync_WithInvalidKey_ThrowsException(); } [Fact] @@ -928,34 +454,21 @@ public override Task ReplaceAsync_WithNonExistentKey_ReturnsFalseAndDoesNotCreat } [Fact] - public override Task ReplaceAsync_WithNullKey_ThrowsArgumentNullException() - { - return base.ReplaceAsync_WithNullKey_ThrowsArgumentNullException(); - } - [Fact] - public override Task ReplaceIfEqualAsync_WithDifferentCasedKeys_ReplacesOnlyExactMatch() + public override Task ReplaceIfEqualAsync_WithExpiration_SetsExpirationCorrectly() { - return base.ReplaceIfEqualAsync_WithDifferentCasedKeys_ReplacesOnlyExactMatch(); + return base.ReplaceIfEqualAsync_WithExpiration_SetsExpirationCorrectly(); } [Fact] - public override Task ReplaceIfEqualAsync_WithEmptyKey_ThrowsArgumentException() + public override Task ReplaceIfEqualAsync_WithInvalidKey_ThrowsArgumentException() { - return base.ReplaceIfEqualAsync_WithEmptyKey_ThrowsArgumentException(); + return base.ReplaceIfEqualAsync_WithInvalidKey_ThrowsArgumentException(); } [Fact] - public override Task ReplaceIfEqualAsync_WithExpiration_SetsExpirationCorrectly() - { - return base.ReplaceIfEqualAsync_WithExpiration_SetsExpirationCorrectly(); - } - - [Theory] - [InlineData("workflow:state")] - [InlineData(" ")] - public override Task ReplaceIfEqualAsync_WithMatchingOldValue_ReturnsTrueAndReplacesValue(string cacheKey) + public override Task ReplaceIfEqualAsync_WithMatchingOldValue_ReturnsTrueAndReplacesValue() { - return base.ReplaceIfEqualAsync_WithMatchingOldValue_ReturnsTrueAndReplacesValue(cacheKey); + return base.ReplaceIfEqualAsync_WithMatchingOldValue_ReturnsTrueAndReplacesValue(); } [Fact] @@ -964,33 +477,28 @@ public override Task ReplaceIfEqualAsync_WithMismatchedOldValue_ReturnsFalseAndD return base.ReplaceIfEqualAsync_WithMismatchedOldValue_ReturnsFalseAndDoesNotReplace(); } - [Fact] - public override Task ReplaceIfEqualAsync_WithNullKey_ThrowsArgumentNullException() - { - return base.ReplaceIfEqualAsync_WithNullKey_ThrowsArgumentNullException(); - } - [Fact] + [Fact(Skip = "Performance Test")] public override Task Serialization_WithComplexObjectsAndValidation_MeasuresThroughput() { return base.Serialization_WithComplexObjectsAndValidation_MeasuresThroughput(); } - [Fact] + [Fact(Skip = "Performance Test")] public override Task Serialization_WithSimpleObjectsAndValidation_MeasuresThroughput() { return base.Serialization_WithSimpleObjectsAndValidation_MeasuresThroughput(); } [Fact] - public override Task SetAllAsync_WithDateTimeMinValue_DoesNotAddKeys() + public override Task SetAllAsync_WithExpiration_KeysExpireCorrectly() { - return base.SetAllAsync_WithDateTimeMinValue_DoesNotAddKeys(); + return base.SetAllAsync_WithExpiration_KeysExpireCorrectly(); } [Fact] - public override Task SetAllAsync_WithDifferentCasedKeys_CreatesDistinctEntries() + public override Task SetAllAsync_WithInvalidItems_ValidatesCorrectly() { - return base.SetAllAsync_WithDifferentCasedKeys_CreatesDistinctEntries(); + return base.SetAllAsync_WithInvalidItems_ValidatesCorrectly(); } [Fact] @@ -999,88 +507,24 @@ public override Task SetAllAsync_WithLargeNumberOfKeys_MeasuresThroughput() return base.SetAllAsync_WithLargeNumberOfKeys_MeasuresThroughput(); } - [Fact] - public override Task SetAllAsync_WithEmptyItems_ReturnsTrue() - { - return base.SetAllAsync_WithEmptyItems_ReturnsTrue(); - } - - [Theory] - [InlineData("test")] - [InlineData(" ")] - public override Task SetAllAsync_WithExpiration_KeysExpireCorrectly(string cacheKey) - { - return base.SetAllAsync_WithExpiration_KeysExpireCorrectly(cacheKey); - } - - [Fact] - public override Task SetAllAsync_WithItemsContainingEmptyKey_ThrowsArgumentException() - { - return base.SetAllAsync_WithItemsContainingEmptyKey_ThrowsArgumentException(); - } - - [Fact] - public override Task SetAllAsync_WithNullItems_ThrowsArgumentNullException() - { - return base.SetAllAsync_WithNullItems_ThrowsArgumentNullException(); - } - [Theory] [InlineData(1000)] [InlineData(10000)] - public override Task SetAllExpiration_WithLargeNumberOfKeys_SetsAllExpirations(int count) - { - return base.SetAllExpiration_WithLargeNumberOfKeys_SetsAllExpirations(count); - } - - [Fact] - public override Task SetAllExpiration_WithMultipleKeys_SetsExpirationForAll() - { - return base.SetAllExpiration_WithMultipleKeys_SetsExpirationForAll(); - } - - [Fact] - public override Task SetAllExpiration_WithNonExistentKeys_HandlesGracefully() - { - return base.SetAllExpiration_WithNonExistentKeys_HandlesGracefully(); - } - - [Fact] - public override Task SetAllExpiration_WithNullValues_RemovesExpiration() - { - return base.SetAllExpiration_WithNullValues_RemovesExpiration(); - } - - [Theory] - [InlineData("user:profile")] - [InlineData(" ")] - public override Task SetAsync_WithComplexObject_StoresCorrectly(string cacheKey) - { - return base.SetAsync_WithComplexObject_StoresCorrectly(cacheKey); - } - - [Fact] - public override Task SetAsync_WithDifferentCasedKeys_CreatesDistinctEntries() - { - return base.SetAsync_WithDifferentCasedKeys_CreatesDistinctEntries(); - } - - [Fact] - public override Task SetAsync_WithDifferentCasedScopes_MaintainsDistinctNamespaces() + public override Task SetAllExpirationAsync_WithLargeNumberOfKeys_SetsAllExpirations(int count) { - return base.SetAsync_WithDifferentCasedScopes_MaintainsDistinctNamespaces(); + return base.SetAllExpirationAsync_WithLargeNumberOfKeys_SetsAllExpirations(count); } [Fact] - public override Task SetAsync_WithDifferentScopes_IsolatesKeys() + public override Task SetAllExpirationAsync_WithMixedExpirations_SetsExpirationsCorrectly() { - return base.SetAsync_WithDifferentScopes_IsolatesKeys(); + return base.SetAllExpirationAsync_WithMixedExpirations_SetsExpirationsCorrectly(); } [Fact] - public override Task SetAsync_WithEmptyKey_ThrowsArgumentException() + public override Task SetAsync_WithComplexObject_StoresCorrectly() { - return base.SetAsync_WithEmptyKey_ThrowsArgumentException(); + return base.SetAsync_WithComplexObject_StoresCorrectly(); } [Fact] @@ -1090,9 +534,9 @@ public override Task SetAsync_WithExpirationEdgeCases_HandlesCorrectly() } [Fact] - public override Task SetAsync_WithLargeNumber_StoresCorrectly() + public override Task SetAsync_WithInvalidKey_ThrowsArgumentException() { - return base.SetAsync_WithLargeNumber_StoresCorrectly(); + return base.SetAsync_WithInvalidKey_ThrowsArgumentException(); } [Fact] @@ -1108,27 +552,15 @@ public override Task SetAsync_WithMultipleInstances_UsesLocalCache() } [Fact] - public override Task SetAsync_WithNestedScopes_PreservesHierarchy() + public override Task SetAsync_WithNullValue_StoresAsNullValue() { - return base.SetAsync_WithNestedScopes_PreservesHierarchy(); + return base.SetAsync_WithNullValue_StoresAsNullValue(); } [Fact] - public override Task SetAsync_WithNullKey_ThrowsArgumentNullException() + public override Task SetAsync_WithScopedCaches_IsolatesKeys() { - return base.SetAsync_WithNullKey_ThrowsArgumentNullException(); - } - - [Fact] - public override Task SetAsync_WithNullReferenceType_StoresAsNullValue() - { - return base.SetAsync_WithNullReferenceType_StoresAsNullValue(); - } - - [Fact] - public override Task SetAsync_WithNullValueType_StoresAsNullValue() - { - return base.SetAsync_WithNullValueType_StoresAsNullValue(); + return base.SetAsync_WithScopedCaches_IsolatesKeys(); } [Fact] @@ -1136,24 +568,17 @@ public override Task SetAsync_WithShortExpiration_ExpiresCorrectly() { return base.SetAsync_WithShortExpiration_ExpiresCorrectly(); } - [Theory] - [InlineData("token:refresh")] - [InlineData(" ")] - public override Task SetExpirationAsync_ChangingFromNoExpirationToFutureTime_UpdatesCorrectly(string cacheKey) - { - return base.SetExpirationAsync_ChangingFromNoExpirationToFutureTime_UpdatesCorrectly(cacheKey); - } [Fact] - public override Task SetExpirationAsync_ChangingToDateTimeMinValue_RemovesKey() + public override Task SetExpirationAsync_ChangingFromNoExpirationToFutureTime_UpdatesCorrectly() { - return base.SetExpirationAsync_ChangingToDateTimeMinValue_RemovesKey(); + return base.SetExpirationAsync_ChangingFromNoExpirationToFutureTime_UpdatesCorrectly(); } [Fact] - public override Task SetExpirationAsync_WithCurrentTime_ExpiresImmediately() + public override Task SetExpirationAsync_ChangingToDateTimeMinValue_RemovesKey() { - return base.SetExpirationAsync_WithCurrentTime_ExpiresImmediately(); + return base.SetExpirationAsync_ChangingToDateTimeMinValue_RemovesKey(); } [Fact] @@ -1163,38 +588,15 @@ public override Task SetExpirationAsync_WithDateTimeMaxValue_NeverExpires() } [Fact] - public override Task SetExpirationAsync_WithDateTimeMinValue_ExpiresImmediately() - { - return base.SetExpirationAsync_WithDateTimeMinValue_ExpiresImmediately(); - } - - [Fact] - public override Task SetExpirationAsync_WithDifferentCasedKeys_SetsOnlyExactMatch() - { - return base.SetExpirationAsync_WithDifferentCasedKeys_SetsOnlyExactMatch(); - } - - [Fact] - public override Task SetExpirationAsync_WithEmptyKey_ThrowsArgumentException() - { - return base.SetExpirationAsync_WithEmptyKey_ThrowsArgumentException(); - } - - [Fact] - public override Task SetExpirationAsync_WithNullKey_ThrowsArgumentNullException() + public override Task SetExpirationAsync_WithInvalidKey_ThrowsArgumentException() { - return base.SetExpirationAsync_WithNullKey_ThrowsArgumentNullException(); - } - [Fact] - public override Task SetIfHigherAsync_WithDateTime_DoesNotUpdateWhenLower() - { - return base.SetIfHigherAsync_WithDateTime_DoesNotUpdateWhenLower(); + return base.SetExpirationAsync_WithInvalidKey_ThrowsArgumentException(); } [Fact] - public override Task SetIfHigherAsync_WithDateTime_InitializesWhenKeyNotExists() + public override Task SetExpirationAsync_WithPastOrCurrentTime_ExpiresImmediately() { - return base.SetIfHigherAsync_WithDateTime_InitializesWhenKeyNotExists(); + return base.SetExpirationAsync_WithPastOrCurrentTime_ExpiresImmediately(); } [Fact] @@ -1204,21 +606,9 @@ public override Task SetIfHigherAsync_WithDateTime_UpdatesWhenHigher() } [Fact] - public override Task SetIfHigherAsync_WithLargeNumbers_UpdatesWhenHigher() - { - return base.SetIfHigherAsync_WithLargeNumbers_UpdatesWhenHigher(); - } - - [Fact] - public override Task SetIfLowerAsync_WithDateTime_DoesNotUpdateWhenHigher() - { - return base.SetIfLowerAsync_WithDateTime_DoesNotUpdateWhenHigher(); - } - - [Fact] - public override Task SetIfLowerAsync_WithDateTime_InitializesWhenKeyNotExists() + public override Task SetIfHigherAsync_WithLargeNumbers_HandlesCorrectly() { - return base.SetIfLowerAsync_WithDateTime_InitializesWhenKeyNotExists(); + return base.SetIfHigherAsync_WithLargeNumbers_HandlesCorrectly(); } [Fact] @@ -1228,9 +618,9 @@ public override Task SetIfLowerAsync_WithDateTime_UpdatesWhenLower() } [Fact] - public override Task SetIfLowerAsync_WithLargeNumbers_UpdatesWhenLower() + public override Task SetIfLowerAsync_WithLargeNumbers_HandlesCorrectly() { - return base.SetIfLowerAsync_WithLargeNumbers_UpdatesWhenLower(); + return base.SetIfLowerAsync_WithLargeNumbers_HandlesCorrectly(); } [Fact] diff --git a/tests/Foundatio.Redis.Tests/Caching/ScopedRedisCacheClientTests.cs b/tests/Foundatio.Redis.Tests/Caching/ScopedRedisCacheClientTests.cs index 1e518a5..7da9bd2 100644 --- a/tests/Foundatio.Redis.Tests/Caching/ScopedRedisCacheClientTests.cs +++ b/tests/Foundatio.Redis.Tests/Caching/ScopedRedisCacheClientTests.cs @@ -25,40 +25,6 @@ public override Task AddAsync_WithConcurrentRequests_OnlyOneSucceeds() return base.AddAsync_WithConcurrentRequests_OnlyOneSucceeds(); } - [Fact] - public override Task AddAsync_WithEmptyKey_ThrowsArgumentException() - { - return base.AddAsync_WithEmptyKey_ThrowsArgumentException(); - } - - [Theory] - [InlineData("user:profile")] - [InlineData(" ")] - public override Task AddAsync_WithExistingKey_ReturnsFalseAndPreservesValue(string cacheKey) - { - return base.AddAsync_WithExistingKey_ReturnsFalseAndPreservesValue(cacheKey); - } - - [Fact] - public override Task AddAsync_WithNestedKeyUsingSeparator_StoresCorrectly() - { - return base.AddAsync_WithNestedKeyUsingSeparator_StoresCorrectly(); - } - - [Theory] - [InlineData("user:profile")] - [InlineData(" ")] - public override Task AddAsync_WithValidKey_ReturnsTrue(string cacheKey) - { - return base.AddAsync_WithValidKey_ReturnsTrue(cacheKey); - } - - [Fact] - public override Task AddAsync_WithNullKey_ThrowsArgumentNullException() - { - return base.AddAsync_WithNullKey_ThrowsArgumentNullException(); - } - [Fact(Skip = "Performance Test")] public override Task CacheOperations_WithMultipleTypes_MeasuresThroughput() { @@ -71,122 +37,31 @@ public override Task CacheOperations_WithRepeatedSetAndGet_MeasuresThroughput() return base.CacheOperations_WithRepeatedSetAndGet_MeasuresThroughput(); } - [Fact] - public override Task ExistsAsync_AfterKeyExpires_ReturnsFalse() - { - return base.ExistsAsync_AfterKeyExpires_ReturnsFalse(); - } - - [Fact] - public override Task ExistsAsync_WithDifferentCasedKeys_ChecksExactMatch() - { - return base.ExistsAsync_WithDifferentCasedKeys_ChecksExactMatch(); - } - - [Fact] - public override Task ExistsAsync_WithEmptyKey_ThrowsArgumentException() - { - return base.ExistsAsync_WithEmptyKey_ThrowsArgumentException(); - } - - [Theory] - [InlineData("user:profile")] - [InlineData(" ")] - public override Task ExistsAsync_WithExistingKey_ReturnsTrue(string cacheKey) - { - return base.ExistsAsync_WithExistingKey_ReturnsTrue(cacheKey); - } - - [Fact] - public override Task ExistsAsync_WithExpiredKey_ReturnsFalse() - { - return base.ExistsAsync_WithExpiredKey_ReturnsFalse(); - } - - [Fact] - public override Task ExistsAsync_WithNonExistentKey_ReturnsFalse() - { - return base.ExistsAsync_WithNonExistentKey_ReturnsFalse(); - } - - [Fact] - public override Task ExistsAsync_WithNullKey_ThrowsArgumentNullException() - { - return base.ExistsAsync_WithNullKey_ThrowsArgumentNullException(); - } - - [Fact] - public override Task ExistsAsync_WithNullStoredValue_ReturnsTrue() - { - return base.ExistsAsync_WithNullStoredValue_ReturnsTrue(); - } - - [Fact] - public override Task ExistsAsync_WithScopedCache_ChecksOnlyWithinScope() - { - return base.ExistsAsync_WithScopedCache_ChecksOnlyWithinScope(); - } - - [Fact] - public override Task GetAllAsync_WithEmptyKeys_ReturnsEmpty() - { - return base.GetAllAsync_WithEmptyKeys_ReturnsEmpty(); - } - - [Theory] - [InlineData("test2")] - [InlineData(" ")] - public override Task GetAllAsync_WithExistingKeys_ReturnsAllValues(string cacheKey) - { - return base.GetAllAsync_WithExistingKeys_ReturnsAllValues(cacheKey); - } - - [Fact] - public override Task GetAllAsync_WithKeysContainingEmpty_ThrowsArgumentException() + [Fact] + public override Task ExistsAsync_WithVariousKeys_ReturnsCorrectExistenceStatus() { - return base.GetAllAsync_WithKeysContainingEmpty_ThrowsArgumentException(); + return base.ExistsAsync_WithVariousKeys_ReturnsCorrectExistenceStatus(); } [Fact] - public override Task GetAllAsync_WithKeysContainingNull_ThrowsArgumentNullException() - { - return base.GetAllAsync_WithKeysContainingNull_ThrowsArgumentNullException(); - } + public override Task ExistsAsync_WithExpiredKey_ReturnsFalse() => base.ExistsAsync_WithExpiredKey_ReturnsFalse(); [Fact] - public override Task GetAllAsync_WithMixedCaseKeys_RetrievesExactMatches() - { - return base.GetAllAsync_WithMixedCaseKeys_RetrievesExactMatches(); - } + public override Task ExistsAsync_WithScopedCache_ChecksOnlyWithinScope() => base.ExistsAsync_WithScopedCache_ChecksOnlyWithinScope(); [Fact] - public override Task GetAllAsync_WithMixedObjectTypes_ReturnsCorrectValues() - { - return base.GetAllAsync_WithMixedObjectTypes_ReturnsCorrectValues(); - } + public override Task ExistsAsync_WithInvalidKey_ThrowsArgumentException() => base.ExistsAsync_WithInvalidKey_ThrowsArgumentException(); [Fact] - public override Task GetAllAsync_WithNonExistentKeys_ReturnsEmptyResults() + public override Task GetAllAsync_WithInvalidKeys_ValidatesCorrectly() { - return base.GetAllAsync_WithNonExistentKeys_ReturnsEmptyResults(); + return base.GetAllAsync_WithInvalidKeys_ValidatesCorrectly(); } [Fact] - public override Task GetAllAsync_WithNullKeys_ThrowsArgumentNullException() + public override Task GetAllAsync_WithMultipleKeys_ReturnsCorrectValues() { - return base.GetAllAsync_WithNullKeys_ThrowsArgumentNullException(); - } - - [Fact] - public override Task GetAllAsync_WithNullValues_HandlesNullsCorrectly() - { - return base.GetAllAsync_WithNullValues_HandlesNullsCorrectly(); - } - - [Fact] - public override Task GetAllAsync_WithOverlappingKeys_UsesLatestValues() - { - return base.GetAllAsync_WithOverlappingKeys_UsesLatestValues(); + return base.GetAllAsync_WithMultipleKeys_ReturnsCorrectValues(); } [Fact] @@ -195,12 +70,6 @@ public override Task GetAllAsync_WithScopedCache_ReturnsUnscopedKeys() return base.GetAllAsync_WithScopedCache_ReturnsUnscopedKeys(); } - [Fact] - public override Task GetAllExpirationAsync_WithExpiredKeys_ExcludesExpiredKeys() - { - return base.GetAllExpirationAsync_WithExpiredKeys_ExcludesExpiredKeys(); - } - [Theory] [InlineData(1000)] [InlineData(10000)] @@ -210,143 +79,27 @@ public override Task GetAllExpirationAsync_WithLargeNumberOfKeys_ReturnsAllExpir } [Fact] - public override Task GetAllExpirationAsync_WithMixedKeys_ReturnsOnlyKeysWithExpiration() - { - return base.GetAllExpirationAsync_WithMixedKeys_ReturnsOnlyKeysWithExpiration(); - } - - [Fact] - public override Task GetAsync_WithComplexObject_PreservesAllProperties() - { - return base.GetAsync_WithComplexObject_PreservesAllProperties(); - } - - [Theory] - [InlineData("order:details")] - [InlineData(" ")] - public override Task GetAsync_WithComplexObject_ReturnsNewInstance(string cacheKey) - { - return base.GetAsync_WithComplexObject_ReturnsNewInstance(cacheKey); - } - - [Fact] - public override Task GetAsync_WithDifferentCasedKeys_TreatsAsDifferentKeys() - { - return base.GetAsync_WithDifferentCasedKeys_TreatsAsDifferentKeys(); - } - - [Fact] - public override Task GetAsync_WithEmptyKey_ThrowsArgumentException() - { - return base.GetAsync_WithEmptyKey_ThrowsArgumentException(); - } - - [Fact] - public override Task GetAsync_WithLargeNumber_ReturnsCorrectValue() - { - return base.GetAsync_WithLargeNumber_ReturnsCorrectValue(); - } - - [Fact] - public override Task GetAsync_WithMaxLongAsInt_ThrowsException() - { - return base.GetAsync_WithMaxLongAsInt_ThrowsException(); - } - - [Fact] - public override Task GetAsync_WithNonExistentKey_ReturnsNoValue() - { - return base.GetAsync_WithNonExistentKey_ReturnsNoValue(); - } - - [Fact] - public override Task GetAsync_WithNullKey_ThrowsArgumentNullException() - { - return base.GetAsync_WithNullKey_ThrowsArgumentNullException(); - } - - [Fact] - public override Task GetAsync_WithNumericTypeConversion_ConvertsIntToLong() - { - return base.GetAsync_WithNumericTypeConversion_ConvertsIntToLong(); - } - - [Fact] - public override Task GetAsync_WithNumericTypeConversion_ConvertsLongToInt() - { - return base.GetAsync_WithNumericTypeConversion_ConvertsLongToInt(); - } - - [Fact] - public override Task GetAsync_WithTryGetSemanticsAndComplexTypeAsLong_ReturnsNoValue() - { - return base.GetAsync_WithTryGetSemanticsAndComplexTypeAsLong_ReturnsNoValue(); - } - - [Fact] - public override Task GetAsync_WithTryGetSemanticsAndIntAsLong_ConvertsSuccessfully() - { - return base.GetAsync_WithTryGetSemanticsAndIntAsLong_ConvertsSuccessfully(); - } - - [Fact] - public override Task GetAsync_WithTryGetSemanticsAndMaxLongAsInt_ReturnsNoValue() - { - return base.GetAsync_WithTryGetSemanticsAndMaxLongAsInt_ReturnsNoValue(); - } - [Fact] - public override Task GetExpirationAsync_AfterExpiry_ReturnsNull() - { - return base.GetExpirationAsync_AfterExpiry_ReturnsNull(); - } - - [Fact] - public override Task GetExpirationAsync_WithDifferentCasedKeys_GetsExactMatch() - { - return base.GetExpirationAsync_WithDifferentCasedKeys_GetsExactMatch(); - } - - [Fact] - public override Task GetExpirationAsync_WithEmptyKey_ThrowsArgumentException() - { - return base.GetExpirationAsync_WithEmptyKey_ThrowsArgumentException(); - } - - [Theory] - [InlineData("token:refresh")] - [InlineData(" ")] - public override Task GetExpirationAsync_WithExpiration_ReturnsCorrectTimeSpan(string cacheKey) - { - return base.GetExpirationAsync_WithExpiration_ReturnsCorrectTimeSpan(cacheKey); - } - - [Fact] - public override Task GetExpirationAsync_WithExpiredKey_ReturnsNull() + public override Task GetAllExpirationAsync_WithMixedKeys_ReturnsExpectedResults() { - return base.GetExpirationAsync_WithExpiredKey_ReturnsNull(); + return base.GetAllExpirationAsync_WithMixedKeys_ReturnsExpectedResults(); } [Fact] - public override Task GetExpirationAsync_WithNoExpiration_ReturnsNull() + public override Task GetAsync_WithNumericTypeConversion_ConvertsBetweenTypes() { - return base.GetExpirationAsync_WithNoExpiration_ReturnsNull(); + return base.GetAsync_WithNumericTypeConversion_ConvertsBetweenTypes(); } [Fact] - public override Task GetExpirationAsync_WithNonExistentKey_ReturnsNull() + public override Task GetExpirationAsync_WithVariousKeyStates_ReturnsExpectedExpiration() { - return base.GetExpirationAsync_WithNonExistentKey_ReturnsNull(); + return base.GetExpirationAsync_WithVariousKeyStates_ReturnsExpectedExpiration(); } [Fact] - public override Task GetExpirationAsync_WithNullKey_ThrowsArgumentNullException() - { - return base.GetExpirationAsync_WithNullKey_ThrowsArgumentNullException(); - } - [Fact] - public override Task GetListAsync_WithEmptyKey_ThrowsArgumentException() + public override Task GetExpirationAsync_WithInvalidKey_ThrowsArgumentException() { - return base.GetListAsync_WithEmptyKey_ThrowsArgumentException(); + return base.GetExpirationAsync_WithInvalidKey_ThrowsArgumentException(); } [Fact] @@ -356,42 +109,17 @@ public override Task GetListAsync_WithExpiredItems_RemovesExpiredAndReturnsActiv } [Fact] - public override Task GetListAsync_WithInvalidPageNumber_ThrowsArgumentOutOfRangeException() - { - return base.GetListAsync_WithInvalidPageNumber_ThrowsArgumentOutOfRangeException(); - } - - [Fact] - public override Task GetListAsync_WithMultiplePages_ReturnsAllItems() - { - return base.GetListAsync_WithMultiplePages_ReturnsAllItems(); - } - - [Fact] - public override Task GetListAsync_WithNewItemsAdded_ReturnsNewItemsLast() - { - return base.GetListAsync_WithNewItemsAdded_ReturnsNewItemsLast(); - } - - [Fact] - public override Task GetListAsync_WithNullKey_ThrowsArgumentNullException() + public override Task GetListAsync_WithInvalidKey_ThrowsArgumentException() { - return base.GetListAsync_WithNullKey_ThrowsArgumentNullException(); + return base.GetListAsync_WithInvalidKey_ThrowsArgumentException(); } [Fact] - public override Task GetListAsync_WithPageBeyondEnd_ReturnsEmptyCollection() + public override Task GetListAsync_WithPaging_ReturnsCorrectResults() { - return base.GetListAsync_WithPageBeyondEnd_ReturnsEmptyCollection(); + return base.GetListAsync_WithPaging_ReturnsCorrectResults(); } - [Theory] - [InlineData("cart:items")] - [InlineData(" ")] - public override Task GetListAsync_WithPaging_ReturnsCorrectPageSize(string cacheKey) - { - return base.GetListAsync_WithPaging_ReturnsCorrectPageSize(cacheKey); - } [Fact] public override Task GetUnixTimeMillisecondsAsync_WithLocalDateTime_ReturnsCorrectly() { @@ -410,26 +138,6 @@ public override Task GetUnixTimeSecondsAsync_WithUtcDateTime_ReturnsCorrectly() return base.GetUnixTimeSecondsAsync_WithUtcDateTime_ReturnsCorrectly(); } - [Fact] - public override Task IncrementAsync_WithDifferentCasedKeys_IncrementsDistinctCounters() - { - return base.IncrementAsync_WithDifferentCasedKeys_IncrementsDistinctCounters(); - } - - [Fact] - public override Task IncrementAsync_WithEmptyKey_ThrowsArgumentException() - { - return base.IncrementAsync_WithEmptyKey_ThrowsArgumentException(); - } - - [Theory] - [InlineData("metrics:page-views")] - [InlineData(" ")] - public override Task IncrementAsync_WithExistingKey_IncrementsValue(string cacheKey) - { - return base.IncrementAsync_WithExistingKey_IncrementsValue(cacheKey); - } - [Fact] public override Task IncrementAsync_WithExpiration_ExpiresCorrectly() { @@ -437,15 +145,15 @@ public override Task IncrementAsync_WithExpiration_ExpiresCorrectly() } [Fact] - public override Task IncrementAsync_WithNonExistentKey_InitializesToOne() + public override Task IncrementAsync_WithInvalidKey_ThrowsException() { - return base.IncrementAsync_WithNonExistentKey_InitializesToOne(); + return base.IncrementAsync_WithInvalidKey_ThrowsException(); } [Fact] - public override Task IncrementAsync_WithNullKey_ThrowsArgumentNullException() + public override Task IncrementAsync_WithKey_IncrementsCorrectly() { - return base.IncrementAsync_WithNullKey_ThrowsArgumentNullException(); + return base.IncrementAsync_WithKey_IncrementsCorrectly(); } [Fact] @@ -455,88 +163,15 @@ public override Task IncrementAsync_WithScopedCache_WorksWithinScope() } [Fact] - public override Task IncrementAsync_WithSpecifiedAmount_IncrementsCorrectly() - { - return base.IncrementAsync_WithSpecifiedAmount_IncrementsCorrectly(); - } - [Fact] - public override Task ListAddAsync_WithDifferentCasedKeys_MaintainsDistinctLists() - { - return base.ListAddAsync_WithDifferentCasedKeys_MaintainsDistinctLists(); - } - - [Theory] - [InlineData("cart:items")] - [InlineData(" ")] - public override Task ListAddAsync_WithDuplicates_RemovesDuplicatesAndAddsItems(string cacheKey) - { - return base.ListAddAsync_WithDuplicates_RemovesDuplicatesAndAddsItems(cacheKey); - } - - [Fact] - public override Task ListAddAsync_WithDuplicates_StoresUniqueValuesOnly() - { - return base.ListAddAsync_WithDuplicates_StoresUniqueValuesOnly(); - } - - [Fact] - public override Task ListAddAsync_WithEmptyCollection_NoOp() - { - return base.ListAddAsync_WithEmptyCollection_NoOp(); - } - - [Fact] - public override Task ListAddAsync_WithEmptyKey_ThrowsArgumentException() - { - return base.ListAddAsync_WithEmptyKey_ThrowsArgumentException(); - } - - [Fact] - public override Task ListAddAsync_WithExistingNonListKey_ThrowsException() - { - return base.ListAddAsync_WithExistingNonListKey_ThrowsException(); - } - - [Fact] - public override Task ListAddAsync_WithFutureExpiration_AddsAndExpiresCorrectly() - { - return base.ListAddAsync_WithFutureExpiration_AddsAndExpiresCorrectly(); - } - - [Fact] - public override Task ListAddAsync_WithMultipleExpirations_ExpiresIndividualItems() - { - return base.ListAddAsync_WithMultipleExpirations_ExpiresIndividualItems(); - } - - [Fact] - public override Task ListAddAsync_WithNullCollection_ThrowsArgumentNullException() + public override Task ListAddAsync_WithExpiration_ExpiresCorrectly() { - return base.ListAddAsync_WithNullCollection_ThrowsArgumentNullException(); + return base.ListAddAsync_WithExpiration_ExpiresCorrectly(); } [Fact] - public override Task ListAddAsync_WithNullItem_IgnoresNull() + public override Task ListAddAsync_WithInvalidArguments_ThrowsException() { - return base.ListAddAsync_WithNullItem_IgnoresNull(); - } - - [Fact] - public override Task ListAddAsync_WithNullKey_ThrowsArgumentNullException() - { - return base.ListAddAsync_WithNullKey_ThrowsArgumentNullException(); - } - - [Fact] - public override Task ListAddAsync_WithNullValues_ThrowsArgumentNullException() - { - return base.ListAddAsync_WithNullValues_ThrowsArgumentNullException(); - } - - [Fact] - public override Task ListAddAsync_WithPastExpiration_RemovesItem() - { - return base.ListAddAsync_WithPastExpiration_RemovesItem(); + return base.ListAddAsync_WithInvalidArguments_ThrowsException(); } [Fact] @@ -544,48 +179,17 @@ public override Task ListAddAsync_WithSingleString_StoresAsStringNotCharArray() { return base.ListAddAsync_WithSingleString_StoresAsStringNotCharArray(); } - [Fact] - public override Task ListRemoveAsync_WithEmptyKey_ThrowsArgumentException() - { - return base.ListRemoveAsync_WithEmptyKey_ThrowsArgumentException(); - } - - [Theory] - [InlineData("cart:items")] - [InlineData(" ")] - public override Task ListRemoveAsync_WithMultipleValues_RemovesAll(string cacheKey) - { - return base.ListRemoveAsync_WithMultipleValues_RemovesAll(cacheKey); - } - - [Fact] - public override Task ListRemoveAsync_WithNullCollection_ThrowsArgumentNullException() - { - return base.ListRemoveAsync_WithNullCollection_ThrowsArgumentNullException(); - } [Fact] - public override Task ListRemoveAsync_WithNullItem_IgnoresNull() + public override Task ListAddAsync_WithVariousInputs_HandlesCorrectly() { - return base.ListRemoveAsync_WithNullItem_IgnoresNull(); + return base.ListAddAsync_WithVariousInputs_HandlesCorrectly(); } [Fact] - public override Task ListRemoveAsync_WithNullKey_ThrowsArgumentNullException() + public override Task ListRemoveAsync_WithInvalidInputs_ThrowsAppropriateException() { - return base.ListRemoveAsync_WithNullKey_ThrowsArgumentNullException(); - } - - [Fact] - public override Task ListRemoveAsync_WithNullValues_ThrowsArgumentNullException() - { - return base.ListRemoveAsync_WithNullValues_ThrowsArgumentNullException(); - } - - [Fact] - public override Task ListRemoveAsync_WithSingleValue_RemovesCorrectly() - { - return base.ListRemoveAsync_WithSingleValue_RemovesCorrectly(); + return base.ListRemoveAsync_WithInvalidInputs_ThrowsAppropriateException(); } [Fact] @@ -593,22 +197,16 @@ public override Task ListRemoveAsync_WithValidValues_RemovesKeyWhenEmpty() { return base.ListRemoveAsync_WithValidValues_RemovesKeyWhenEmpty(); } - [Fact] - public override Task RemoveAllAsync_WithEmptyKeys_Succeeds() - { - return base.RemoveAllAsync_WithEmptyKeys_Succeeds(); - } [Fact] - public override Task RemoveAllAsync_WithKeysContainingEmpty_ThrowsArgumentException() + public override Task ListRemoveAsync_WithValues_RemovesCorrectly() { - return base.RemoveAllAsync_WithKeysContainingEmpty_ThrowsArgumentException(); + return base.ListRemoveAsync_WithValues_RemovesCorrectly(); } - [Fact] - public override Task RemoveAllAsync_WithKeysContainingNull_ThrowsArgumentNullException() + public override Task RemoveAllAsync_WithInvalidKeys_ValidatesCorrectly() { - return base.RemoveAllAsync_WithKeysContainingNull_ThrowsArgumentNullException(); + return base.RemoveAllAsync_WithInvalidKeys_ValidatesCorrectly(); } [Fact] @@ -617,80 +215,34 @@ public override Task RemoveAllAsync_WithLargeNumberOfKeys_RemovesAllKeysEfficien return base.RemoveAllAsync_WithLargeNumberOfKeys_RemovesAllKeysEfficiently(); } - [Fact] - public override Task RemoveAllAsync_WithMixedCaseKeys_RemovesOnlyExactMatches() - { - return base.RemoveAllAsync_WithMixedCaseKeys_RemovesOnlyExactMatches(); - } - - [Fact] - public override Task RemoveAllAsync_WithNullKeys_RemovesAllValues() - { - return base.RemoveAllAsync_WithNullKeys_RemovesAllValues(); - } - [Fact] public override Task RemoveAllAsync_WithScopedCache_AffectsOnlyScopedKeys() { return base.RemoveAllAsync_WithScopedCache_AffectsOnlyScopedKeys(); } - [Theory] - [InlineData("remove-all-keys:")] - [InlineData(" ")] - public override Task RemoveAllAsync_WithSpecificKeyCollection_RemovesOnlySpecifiedKeys(string keyPrefix) - { - return base.RemoveAllAsync_WithSpecificKeyCollection_RemovesOnlySpecifiedKeys(keyPrefix); - } - - [Fact] - public override Task RemoveAsync_AfterSetAndGet_RemovesCorrectly() - { - return base.RemoveAsync_AfterSetAndGet_RemovesCorrectly(); - } - [Fact] - public override Task RemoveAsync_MultipleTimes_Succeeds() + public override Task RemoveAllAsync_WithSpecificKeyCollection_RemovesOnlySpecifiedKeys() { - return base.RemoveAsync_MultipleTimes_Succeeds(); + return base.RemoveAllAsync_WithSpecificKeyCollection_RemovesOnlySpecifiedKeys(); } [Fact] - public override Task RemoveAsync_WithEmptyKey_ThrowsArgumentException() + public override Task RemoveAsync_WithInvalidKey_ThrowsArgumentException() { - return base.RemoveAsync_WithEmptyKey_ThrowsArgumentException(); - } - - [Theory] - [InlineData("session:active")] - [InlineData(" ")] - public override Task RemoveAsync_WithExistingKey_RemovesSuccessfully(string cacheKey) - { - return base.RemoveAsync_WithExistingKey_RemovesSuccessfully(cacheKey); + return base.RemoveAsync_WithInvalidKey_ThrowsArgumentException(); } [Fact] - public override Task RemoveAsync_WithExpiredKey_Succeeds() + public override Task RemoveAsync_WithNonExistentKey_ReturnsFalse() { - return base.RemoveAsync_WithExpiredKey_Succeeds(); + return base.RemoveAsync_WithNonExistentKey_ReturnsFalse(); } [Fact] - public override Task RemoveAsync_WithNonExistentKey_Succeeds() + public override Task RemoveAsync_WithExpiredKey_KeyDoesNotExist() { - return base.RemoveAsync_WithNonExistentKey_Succeeds(); - } - - [Fact] - public override Task RemoveAsync_WithNullKey_ThrowsArgumentNullException() - { - return base.RemoveAsync_WithNullKey_ThrowsArgumentNullException(); - } - - [Fact] - public override Task RemoveAsync_WithNullValue_RemovesSuccessfully() - { - return base.RemoveAsync_WithNullValue_RemovesSuccessfully(); + return base.RemoveAsync_WithExpiredKey_KeyDoesNotExist(); } [Fact] @@ -700,14 +252,9 @@ public override Task RemoveAsync_WithScopedCache_RemovesOnlyWithinScope() } [Fact] - public override Task RemoveAsync_WithSpecificCase_RemovesOnlyMatchingKey() - { - return base.RemoveAsync_WithSpecificCase_RemovesOnlyMatchingKey(); - } - [Fact] - public override Task RemoveByPrefixAsync_AsteriskPrefixWithScopedCache_TreatedAsLiteral() + public override Task RemoveAsync_WithValidKey_RemovesSuccessfully() { - return base.RemoveByPrefixAsync_AsteriskPrefixWithScopedCache_TreatedAsLiteral(); + return base.RemoveAsync_WithValidKey_RemovesSuccessfully(); } [Theory] @@ -735,27 +282,9 @@ public override Task RemoveByPrefixAsync_PartialPrefixWithScopedCache_RemovesMat } [Fact] - public override Task RemoveByPrefixAsync_WithCaseSensitivePrefix_RemovesOnlyMatchingCase() + public override Task RemoveByPrefixAsync_WithAsteriskPrefix_TreatedAsLiteral() { - return base.RemoveByPrefixAsync_WithCaseSensitivePrefix_RemovesOnlyMatchingCase(); - } - - [Fact] - public override Task RemoveByPrefixAsync_WithDifferentCasedScopes_RemovesOnlyMatchingScope() - { - return base.RemoveByPrefixAsync_WithDifferentCasedScopes_RemovesOnlyMatchingScope(); - } - - [Fact] - public override Task RemoveByPrefixAsync_WithDoubleAsteriskPrefix_TreatsAsLiteral() - { - return base.RemoveByPrefixAsync_WithDoubleAsteriskPrefix_TreatsAsLiteral(); - } - - [Fact] - public override Task RemoveByPrefixAsync_WithEmptyPrefix_RemovesAllKeys() - { - return base.RemoveByPrefixAsync_WithEmptyPrefix_RemovesAllKeys(); + return base.RemoveByPrefixAsync_WithAsteriskPrefix_TreatedAsLiteral(); } [Theory] @@ -765,12 +294,10 @@ public override Task RemoveByPrefixAsync_WithLineEndingPrefix_TreatsAsLiteral(st return base.RemoveByPrefixAsync_WithLineEndingPrefix_TreatsAsLiteral(lineEndingPrefix); } - [Theory] - [InlineData("blah:")] - [InlineData(" ")] - public override Task RemoveByPrefixAsync_WithMatchingPrefix_RemovesOnlyPrefixedKeys(string prefix) + [Fact] + public override Task RemoveByPrefixAsync_WithMatchingPrefix_RemovesOnlyMatchingKeys() { - return base.RemoveByPrefixAsync_WithMatchingPrefix_RemovesOnlyPrefixedKeys(prefix); + return base.RemoveByPrefixAsync_WithMatchingPrefix_RemovesOnlyMatchingKeys(); } [Theory] @@ -784,15 +311,9 @@ public override Task RemoveByPrefixAsync_WithMultipleMatchingKeys_RemovesOnlyPre } [Fact] - public override Task RemoveByPrefixAsync_WithNonMatchingPrefix_RemovesZeroKeys() - { - return base.RemoveByPrefixAsync_WithNonMatchingPrefix_RemovesZeroKeys(); - } - - [Fact] - public override Task RemoveByPrefixAsync_WithNullPrefix_RemovesAllKeys() + public override Task RemoveByPrefixAsync_WithNullOrEmptyPrefix_RemovesAllKeys() { - return base.RemoveByPrefixAsync_WithNullPrefix_RemovesAllKeys(); + return base.RemoveByPrefixAsync_WithNullOrEmptyPrefix_RemovesAllKeys(); } [Theory] @@ -823,17 +344,15 @@ public override Task RemoveByPrefixAsync_WithWildcardPattern_TreatsAsLiteral(str } [Fact] - public override Task RemoveIfEqualAsync_WithEmptyKey_ThrowsArgumentException() + public override Task RemoveIfEqualAsync_WithInvalidKey_ThrowsArgumentException() { - return base.RemoveIfEqualAsync_WithEmptyKey_ThrowsArgumentException(); + return base.RemoveIfEqualAsync_WithInvalidKey_ThrowsArgumentException(); } - [Theory] - [InlineData("session:active")] - [InlineData(" ")] - public override Task RemoveIfEqualAsync_WithMatchingValue_ReturnsTrueAndRemoves(string cacheKey) + [Fact] + public override Task RemoveIfEqualAsync_WithMatchingValue_ReturnsTrueAndRemoves() { - return base.RemoveIfEqualAsync_WithMatchingValue_ReturnsTrueAndRemoves(cacheKey); + return base.RemoveIfEqualAsync_WithMatchingValue_ReturnsTrueAndRemoves(); } [Fact] @@ -843,34 +362,15 @@ public override Task RemoveIfEqualAsync_WithMismatchedValue_ReturnsFalseAndDoesN } [Fact] - public override Task RemoveIfEqualAsync_WithNullKey_ThrowsArgumentNullException() - { - return base.RemoveIfEqualAsync_WithNullKey_ThrowsArgumentNullException(); - } - [Fact] - public override Task ReplaceAsync_WithDifferentCasedKeys_TreatsAsDifferentKeys() - { - return base.ReplaceAsync_WithDifferentCasedKeys_TreatsAsDifferentKeys(); - } - - [Fact] - public override Task ReplaceAsync_WithEmptyKey_ThrowsArgumentException() + public override Task ReplaceAsync_WithExistingKey_ReturnsTrueAndReplacesValue() { - return base.ReplaceAsync_WithEmptyKey_ThrowsArgumentException(); - } - - [Theory] - [InlineData("settings:theme")] - [InlineData(" ")] - public override Task ReplaceAsync_WithExistingKey_ReturnsTrueAndReplacesValue(string cacheKey) - { - return base.ReplaceAsync_WithExistingKey_ReturnsTrueAndReplacesValue(cacheKey); + return base.ReplaceAsync_WithExistingKey_ReturnsTrueAndReplacesValue(); } [Fact] - public override Task ReplaceAsync_WithExpiration_SetsExpirationCorrectly() + public override Task ReplaceAsync_WithInvalidKey_ThrowsException() { - return base.ReplaceAsync_WithExpiration_SetsExpirationCorrectly(); + return base.ReplaceAsync_WithInvalidKey_ThrowsException(); } [Fact] @@ -880,34 +380,21 @@ public override Task ReplaceAsync_WithNonExistentKey_ReturnsFalseAndDoesNotCreat } [Fact] - public override Task ReplaceAsync_WithNullKey_ThrowsArgumentNullException() - { - return base.ReplaceAsync_WithNullKey_ThrowsArgumentNullException(); - } - [Fact] - public override Task ReplaceIfEqualAsync_WithDifferentCasedKeys_ReplacesOnlyExactMatch() + public override Task ReplaceIfEqualAsync_WithExpiration_SetsExpirationCorrectly() { - return base.ReplaceIfEqualAsync_WithDifferentCasedKeys_ReplacesOnlyExactMatch(); + return base.ReplaceIfEqualAsync_WithExpiration_SetsExpirationCorrectly(); } [Fact] - public override Task ReplaceIfEqualAsync_WithEmptyKey_ThrowsArgumentException() + public override Task ReplaceIfEqualAsync_WithInvalidKey_ThrowsArgumentException() { - return base.ReplaceIfEqualAsync_WithEmptyKey_ThrowsArgumentException(); + return base.ReplaceIfEqualAsync_WithInvalidKey_ThrowsArgumentException(); } [Fact] - public override Task ReplaceIfEqualAsync_WithExpiration_SetsExpirationCorrectly() + public override Task ReplaceIfEqualAsync_WithMatchingOldValue_ReturnsTrueAndReplacesValue() { - return base.ReplaceIfEqualAsync_WithExpiration_SetsExpirationCorrectly(); - } - - [Theory] - [InlineData("workflow:state")] - [InlineData(" ")] - public override Task ReplaceIfEqualAsync_WithMatchingOldValue_ReturnsTrueAndReplacesValue(string cacheKey) - { - return base.ReplaceIfEqualAsync_WithMatchingOldValue_ReturnsTrueAndReplacesValue(cacheKey); + return base.ReplaceIfEqualAsync_WithMatchingOldValue_ReturnsTrueAndReplacesValue(); } [Fact] @@ -916,11 +403,6 @@ public override Task ReplaceIfEqualAsync_WithMismatchedOldValue_ReturnsFalseAndD return base.ReplaceIfEqualAsync_WithMismatchedOldValue_ReturnsFalseAndDoesNotReplace(); } - [Fact] - public override Task ReplaceIfEqualAsync_WithNullKey_ThrowsArgumentNullException() - { - return base.ReplaceIfEqualAsync_WithNullKey_ThrowsArgumentNullException(); - } [Fact(Skip = "Performance Test")] public override Task Serialization_WithComplexObjectsAndValidation_MeasuresThroughput() { @@ -934,15 +416,15 @@ public override Task Serialization_WithSimpleObjectsAndValidation_MeasuresThroug } [Fact] - public override Task SetAllAsync_WithDateTimeMinValue_DoesNotAddKeys() + public override Task SetAllAsync_WithExpiration_KeysExpireCorrectly() { - return base.SetAllAsync_WithDateTimeMinValue_DoesNotAddKeys(); + return base.SetAllAsync_WithExpiration_KeysExpireCorrectly(); } [Fact] - public override Task SetAllAsync_WithDifferentCasedKeys_CreatesDistinctEntries() + public override Task SetAllAsync_WithInvalidItems_ValidatesCorrectly() { - return base.SetAllAsync_WithDifferentCasedKeys_CreatesDistinctEntries(); + return base.SetAllAsync_WithInvalidItems_ValidatesCorrectly(); } [Fact] @@ -951,88 +433,24 @@ public override Task SetAllAsync_WithLargeNumberOfKeys_MeasuresThroughput() return base.SetAllAsync_WithLargeNumberOfKeys_MeasuresThroughput(); } - [Fact] - public override Task SetAllAsync_WithEmptyItems_ReturnsTrue() - { - return base.SetAllAsync_WithEmptyItems_ReturnsTrue(); - } - - [Theory] - [InlineData("test")] - [InlineData(" ")] - public override Task SetAllAsync_WithExpiration_KeysExpireCorrectly(string cacheKey) - { - return base.SetAllAsync_WithExpiration_KeysExpireCorrectly(cacheKey); - } - - [Fact] - public override Task SetAllAsync_WithItemsContainingEmptyKey_ThrowsArgumentException() - { - return base.SetAllAsync_WithItemsContainingEmptyKey_ThrowsArgumentException(); - } - - [Fact] - public override Task SetAllAsync_WithNullItems_ThrowsArgumentNullException() - { - return base.SetAllAsync_WithNullItems_ThrowsArgumentNullException(); - } - [Theory] [InlineData(1000)] [InlineData(10000)] - public override Task SetAllExpiration_WithLargeNumberOfKeys_SetsAllExpirations(int count) - { - return base.SetAllExpiration_WithLargeNumberOfKeys_SetsAllExpirations(count); - } - - [Fact] - public override Task SetAllExpiration_WithMultipleKeys_SetsExpirationForAll() - { - return base.SetAllExpiration_WithMultipleKeys_SetsExpirationForAll(); - } - - [Fact] - public override Task SetAllExpiration_WithNonExistentKeys_HandlesGracefully() - { - return base.SetAllExpiration_WithNonExistentKeys_HandlesGracefully(); - } - - [Fact] - public override Task SetAllExpiration_WithNullValues_RemovesExpiration() - { - return base.SetAllExpiration_WithNullValues_RemovesExpiration(); - } - - [Theory] - [InlineData("user:profile")] - [InlineData(" ")] - public override Task SetAsync_WithComplexObject_StoresCorrectly(string cacheKey) - { - return base.SetAsync_WithComplexObject_StoresCorrectly(cacheKey); - } - - [Fact] - public override Task SetAsync_WithDifferentCasedKeys_CreatesDistinctEntries() - { - return base.SetAsync_WithDifferentCasedKeys_CreatesDistinctEntries(); - } - - [Fact] - public override Task SetAsync_WithDifferentCasedScopes_MaintainsDistinctNamespaces() + public override Task SetAllExpirationAsync_WithLargeNumberOfKeys_SetsAllExpirations(int count) { - return base.SetAsync_WithDifferentCasedScopes_MaintainsDistinctNamespaces(); + return base.SetAllExpirationAsync_WithLargeNumberOfKeys_SetsAllExpirations(count); } [Fact] - public override Task SetAsync_WithDifferentScopes_IsolatesKeys() + public override Task SetAllExpirationAsync_WithMixedExpirations_SetsExpirationsCorrectly() { - return base.SetAsync_WithDifferentScopes_IsolatesKeys(); + return base.SetAllExpirationAsync_WithMixedExpirations_SetsExpirationsCorrectly(); } [Fact] - public override Task SetAsync_WithEmptyKey_ThrowsArgumentException() + public override Task SetAsync_WithComplexObject_StoresCorrectly() { - return base.SetAsync_WithEmptyKey_ThrowsArgumentException(); + return base.SetAsync_WithComplexObject_StoresCorrectly(); } [Fact] @@ -1042,9 +460,9 @@ public override Task SetAsync_WithExpirationEdgeCases_HandlesCorrectly() } [Fact] - public override Task SetAsync_WithLargeNumber_StoresCorrectly() + public override Task SetAsync_WithInvalidKey_ThrowsArgumentException() { - return base.SetAsync_WithLargeNumber_StoresCorrectly(); + return base.SetAsync_WithInvalidKey_ThrowsArgumentException(); } [Fact] @@ -1054,27 +472,15 @@ public override Task SetAsync_WithLargeNumbersAndExpiration_PreservesValues() } [Fact] - public override Task SetAsync_WithNestedScopes_PreservesHierarchy() + public override Task SetAsync_WithNullValue_StoresAsNullValue() { - return base.SetAsync_WithNestedScopes_PreservesHierarchy(); + return base.SetAsync_WithNullValue_StoresAsNullValue(); } [Fact] - public override Task SetAsync_WithNullKey_ThrowsArgumentNullException() + public override Task SetAsync_WithScopedCaches_IsolatesKeys() { - return base.SetAsync_WithNullKey_ThrowsArgumentNullException(); - } - - [Fact] - public override Task SetAsync_WithNullReferenceType_StoresAsNullValue() - { - return base.SetAsync_WithNullReferenceType_StoresAsNullValue(); - } - - [Fact] - public override Task SetAsync_WithNullValueType_StoresAsNullValue() - { - return base.SetAsync_WithNullValueType_StoresAsNullValue(); + return base.SetAsync_WithScopedCaches_IsolatesKeys(); } [Fact] @@ -1082,24 +488,17 @@ public override Task SetAsync_WithShortExpiration_ExpiresCorrectly() { return base.SetAsync_WithShortExpiration_ExpiresCorrectly(); } - [Theory] - [InlineData("token:refresh")] - [InlineData(" ")] - public override Task SetExpirationAsync_ChangingFromNoExpirationToFutureTime_UpdatesCorrectly(string cacheKey) - { - return base.SetExpirationAsync_ChangingFromNoExpirationToFutureTime_UpdatesCorrectly(cacheKey); - } [Fact] - public override Task SetExpirationAsync_ChangingToDateTimeMinValue_RemovesKey() + public override Task SetExpirationAsync_ChangingFromNoExpirationToFutureTime_UpdatesCorrectly() { - return base.SetExpirationAsync_ChangingToDateTimeMinValue_RemovesKey(); + return base.SetExpirationAsync_ChangingFromNoExpirationToFutureTime_UpdatesCorrectly(); } [Fact] - public override Task SetExpirationAsync_WithCurrentTime_ExpiresImmediately() + public override Task SetExpirationAsync_ChangingToDateTimeMinValue_RemovesKey() { - return base.SetExpirationAsync_WithCurrentTime_ExpiresImmediately(); + return base.SetExpirationAsync_ChangingToDateTimeMinValue_RemovesKey(); } [Fact] @@ -1109,38 +508,15 @@ public override Task SetExpirationAsync_WithDateTimeMaxValue_NeverExpires() } [Fact] - public override Task SetExpirationAsync_WithDateTimeMinValue_ExpiresImmediately() + public override Task SetExpirationAsync_WithInvalidKey_ThrowsArgumentException() { - return base.SetExpirationAsync_WithDateTimeMinValue_ExpiresImmediately(); + return base.SetExpirationAsync_WithInvalidKey_ThrowsArgumentException(); } [Fact] - public override Task SetExpirationAsync_WithDifferentCasedKeys_SetsOnlyExactMatch() - { - return base.SetExpirationAsync_WithDifferentCasedKeys_SetsOnlyExactMatch(); - } - - [Fact] - public override Task SetExpirationAsync_WithEmptyKey_ThrowsArgumentException() - { - return base.SetExpirationAsync_WithEmptyKey_ThrowsArgumentException(); - } - - [Fact] - public override Task SetExpirationAsync_WithNullKey_ThrowsArgumentNullException() - { - return base.SetExpirationAsync_WithNullKey_ThrowsArgumentNullException(); - } - [Fact] - public override Task SetIfHigherAsync_WithDateTime_DoesNotUpdateWhenLower() + public override Task SetExpirationAsync_WithPastOrCurrentTime_ExpiresImmediately() { - return base.SetIfHigherAsync_WithDateTime_DoesNotUpdateWhenLower(); - } - - [Fact] - public override Task SetIfHigherAsync_WithDateTime_InitializesWhenKeyNotExists() - { - return base.SetIfHigherAsync_WithDateTime_InitializesWhenKeyNotExists(); + return base.SetExpirationAsync_WithPastOrCurrentTime_ExpiresImmediately(); } [Fact] @@ -1150,21 +526,9 @@ public override Task SetIfHigherAsync_WithDateTime_UpdatesWhenHigher() } [Fact] - public override Task SetIfHigherAsync_WithLargeNumbers_UpdatesWhenHigher() - { - return base.SetIfHigherAsync_WithLargeNumbers_UpdatesWhenHigher(); - } - - [Fact] - public override Task SetIfLowerAsync_WithDateTime_DoesNotUpdateWhenHigher() - { - return base.SetIfLowerAsync_WithDateTime_DoesNotUpdateWhenHigher(); - } - - [Fact] - public override Task SetIfLowerAsync_WithDateTime_InitializesWhenKeyNotExists() + public override Task SetIfHigherAsync_WithLargeNumbers_HandlesCorrectly() { - return base.SetIfLowerAsync_WithDateTime_InitializesWhenKeyNotExists(); + return base.SetIfHigherAsync_WithLargeNumbers_HandlesCorrectly(); } [Fact] @@ -1174,9 +538,9 @@ public override Task SetIfLowerAsync_WithDateTime_UpdatesWhenLower() } [Fact] - public override Task SetIfLowerAsync_WithLargeNumbers_UpdatesWhenLower() + public override Task SetIfLowerAsync_WithLargeNumbers_HandlesCorrectly() { - return base.SetIfLowerAsync_WithLargeNumbers_UpdatesWhenLower(); + return base.SetIfLowerAsync_WithLargeNumbers_HandlesCorrectly(); } [Fact] diff --git a/tests/Foundatio.Redis.Tests/Caching/ScopedRedisHybridCacheClientTests.cs b/tests/Foundatio.Redis.Tests/Caching/ScopedRedisHybridCacheClientTests.cs index d474988..6d2d01d 100644 --- a/tests/Foundatio.Redis.Tests/Caching/ScopedRedisHybridCacheClientTests.cs +++ b/tests/Foundatio.Redis.Tests/Caching/ScopedRedisHybridCacheClientTests.cs @@ -38,156 +38,49 @@ public override Task AddAsync_WithConcurrentRequests_OnlyOneSucceeds() return base.AddAsync_WithConcurrentRequests_OnlyOneSucceeds(); } - [Fact] - public override Task AddAsync_WithEmptyKey_ThrowsArgumentException() - { - return base.AddAsync_WithEmptyKey_ThrowsArgumentException(); - } - - [Theory] - [InlineData("user:profile")] - [InlineData(" ")] - public override Task AddAsync_WithExistingKey_ReturnsFalseAndPreservesValue(string cacheKey) - { - return base.AddAsync_WithExistingKey_ReturnsFalseAndPreservesValue(cacheKey); - } - [Fact(Skip = "TODO: Look into this as the local cache client maintenance never schedules for expiration")] public override Task AddAsync_WithExpiration_ExpiresRemoteItems() { return base.AddAsync_WithExpiration_ExpiresRemoteItems(); } - [Fact] - public override Task AddAsync_WithNestedKeyUsingSeparator_StoresCorrectly() - { - return base.AddAsync_WithNestedKeyUsingSeparator_StoresCorrectly(); - } - - [Theory] - [InlineData("user:profile")] - [InlineData(" ")] - public override Task AddAsync_WithValidKey_ReturnsTrue(string cacheKey) - { - return base.AddAsync_WithValidKey_ReturnsTrue(cacheKey); - } - - [Fact] - public override Task AddAsync_WithNullKey_ThrowsArgumentNullException() - { - return base.AddAsync_WithNullKey_ThrowsArgumentNullException(); - } - - [Fact] + [Fact(Skip = "Performance Test")] public override Task CacheOperations_WithMultipleTypes_MeasuresThroughput() { return base.CacheOperations_WithMultipleTypes_MeasuresThroughput(); } - [Fact] + [Fact(Skip = "Performance Test")] public override Task CacheOperations_WithRepeatedSetAndGet_MeasuresThroughput() { return base.CacheOperations_WithRepeatedSetAndGet_MeasuresThroughput(); } [Fact] - public override Task ExistsAsync_AfterKeyExpires_ReturnsFalse() - { - return base.ExistsAsync_AfterKeyExpires_ReturnsFalse(); - } - - [Fact] - public override Task ExistsAsync_WithDifferentCasedKeys_ChecksExactMatch() - { - return base.ExistsAsync_WithDifferentCasedKeys_ChecksExactMatch(); - } - - [Fact] - public override Task ExistsAsync_WithEmptyKey_ThrowsArgumentException() - { - return base.ExistsAsync_WithEmptyKey_ThrowsArgumentException(); - } - - [Theory] - [InlineData("user:profile")] - [InlineData(" ")] - public override Task ExistsAsync_WithExistingKey_ReturnsTrue(string cacheKey) - { - return base.ExistsAsync_WithExistingKey_ReturnsTrue(cacheKey); - } - - [Fact] - public override Task ExistsAsync_WithExpiredKey_ReturnsFalse() - { - return base.ExistsAsync_WithExpiredKey_ReturnsFalse(); - } - - [Fact] - public override Task ExistsAsync_WithLocalCache_ChecksLocalCacheFirst() - { - return base.ExistsAsync_WithLocalCache_ChecksLocalCacheFirst(); - } - - [Fact] - public override Task ExistsAsync_WithNonExistentKey_ReturnsFalse() - { - return base.ExistsAsync_WithNonExistentKey_ReturnsFalse(); - } - - [Fact] - public override Task ExistsAsync_WithNullKey_ThrowsArgumentNullException() - { - return base.ExistsAsync_WithNullKey_ThrowsArgumentNullException(); - } - - [Fact] - public override Task ExistsAsync_WithNullStoredValue_ReturnsTrue() + public override Task ExistsAsync_WithVariousKeys_ReturnsCorrectExistenceStatus() { - return base.ExistsAsync_WithNullStoredValue_ReturnsTrue(); + return base.ExistsAsync_WithVariousKeys_ReturnsCorrectExistenceStatus(); } [Fact] - public override Task ExistsAsync_WithScopedCache_ChecksOnlyWithinScope() - { - return base.ExistsAsync_WithScopedCache_ChecksOnlyWithinScope(); - } + public override Task ExistsAsync_WithExpiredKey_ReturnsFalse() => base.ExistsAsync_WithExpiredKey_ReturnsFalse(); [Fact] - public override Task GetAllAsync_WithEmptyKeys_ReturnsEmpty() - { - return base.GetAllAsync_WithEmptyKeys_ReturnsEmpty(); - } - - [Theory] - [InlineData("test2")] - [InlineData(" ")] - public override Task GetAllAsync_WithExistingKeys_ReturnsAllValues(string cacheKey) - { - return base.GetAllAsync_WithExistingKeys_ReturnsAllValues(cacheKey); - } - - [Fact] - public override Task GetAllAsync_WithKeysContainingEmpty_ThrowsArgumentException() - { - return base.GetAllAsync_WithKeysContainingEmpty_ThrowsArgumentException(); - } + public override Task ExistsAsync_WithScopedCache_ChecksOnlyWithinScope() => base.ExistsAsync_WithScopedCache_ChecksOnlyWithinScope(); [Fact] - public override Task GetAllAsync_WithKeysContainingNull_ThrowsArgumentNullException() - { - return base.GetAllAsync_WithKeysContainingNull_ThrowsArgumentNullException(); - } + public override Task ExistsAsync_WithInvalidKey_ThrowsArgumentException() => base.ExistsAsync_WithInvalidKey_ThrowsArgumentException(); [Fact] - public override Task GetAllAsync_WithMixedCaseKeys_RetrievesExactMatches() + public override Task GetAllAsync_WithInvalidKeys_ValidatesCorrectly() { - return base.GetAllAsync_WithMixedCaseKeys_RetrievesExactMatches(); + return base.GetAllAsync_WithInvalidKeys_ValidatesCorrectly(); } [Fact] - public override Task GetAllAsync_WithMixedObjectTypes_ReturnsCorrectValues() + public override Task GetAllAsync_WithMultipleKeys_ReturnsCorrectValues() { - return base.GetAllAsync_WithMixedObjectTypes_ReturnsCorrectValues(); + return base.GetAllAsync_WithMultipleKeys_ReturnsCorrectValues(); } [Fact] @@ -196,42 +89,12 @@ public override Task GetAllAsync_WithMultipleKeys_UsesHybridCache() return base.GetAllAsync_WithMultipleKeys_UsesHybridCache(); } - [Fact] - public override Task GetAllAsync_WithNonExistentKeys_ReturnsEmptyResults() - { - return base.GetAllAsync_WithNonExistentKeys_ReturnsEmptyResults(); - } - - [Fact] - public override Task GetAllAsync_WithNullKeys_ThrowsArgumentNullException() - { - return base.GetAllAsync_WithNullKeys_ThrowsArgumentNullException(); - } - - [Fact] - public override Task GetAllAsync_WithNullValues_HandlesNullsCorrectly() - { - return base.GetAllAsync_WithNullValues_HandlesNullsCorrectly(); - } - - [Fact] - public override Task GetAllAsync_WithOverlappingKeys_UsesLatestValues() - { - return base.GetAllAsync_WithOverlappingKeys_UsesLatestValues(); - } - [Fact] public override Task GetAllAsync_WithScopedCache_ReturnsUnscopedKeys() { return base.GetAllAsync_WithScopedCache_ReturnsUnscopedKeys(); } - [Fact] - public override Task GetAllExpirationAsync_WithExpiredKeys_ExcludesExpiredKeys() - { - return base.GetAllExpirationAsync_WithExpiredKeys_ExcludesExpiredKeys(); - } - [Theory] [InlineData(1000)] [InlineData(10000)] @@ -241,114 +104,27 @@ public override Task GetAllExpirationAsync_WithLargeNumberOfKeys_ReturnsAllExpir } [Fact] - public override Task GetAllExpirationAsync_WithMixedKeys_ReturnsOnlyKeysWithExpiration() - { - return base.GetAllExpirationAsync_WithMixedKeys_ReturnsOnlyKeysWithExpiration(); - } - - [Fact] - public override Task GetAsync_WithComplexObject_PreservesAllProperties() - { - return base.GetAsync_WithComplexObject_PreservesAllProperties(); - } - - [Theory] - [InlineData("order:details")] - [InlineData(" ")] - public override Task GetAsync_WithComplexObject_ReturnsNewInstance(string cacheKey) - { - return base.GetAsync_WithComplexObject_ReturnsNewInstance(cacheKey); - } - - [Fact] - public override Task GetAsync_WithDifferentCasedKeys_TreatsAsDifferentKeys() - { - return base.GetAsync_WithDifferentCasedKeys_TreatsAsDifferentKeys(); - } - - [Fact] - public override Task GetAsync_WithEmptyKey_ThrowsArgumentException() + public override Task GetAllExpirationAsync_WithMixedKeys_ReturnsExpectedResults() { - return base.GetAsync_WithEmptyKey_ThrowsArgumentException(); + return base.GetAllExpirationAsync_WithMixedKeys_ReturnsExpectedResults(); } [Fact] - public override Task GetAsync_WithLargeNumber_ReturnsCorrectValue() + public override Task GetAsync_WithNumericTypeConversion_ConvertsBetweenTypes() { - return base.GetAsync_WithLargeNumber_ReturnsCorrectValue(); + return base.GetAsync_WithNumericTypeConversion_ConvertsBetweenTypes(); } [Fact] - public override Task GetAsync_WithMaxLongAsInt_ThrowsException() + public override Task GetExpirationAsync_WithVariousKeyStates_ReturnsExpectedExpiration() { - return base.GetAsync_WithMaxLongAsInt_ThrowsException(); + return base.GetExpirationAsync_WithVariousKeyStates_ReturnsExpectedExpiration(); } [Fact] - public override Task GetAsync_WithNonExistentKey_ReturnsNoValue() + public override Task GetExpirationAsync_WithInvalidKey_ThrowsArgumentException() { - return base.GetAsync_WithNonExistentKey_ReturnsNoValue(); - } - - [Fact] - public override Task GetAsync_WithNullKey_ThrowsArgumentNullException() - { - return base.GetAsync_WithNullKey_ThrowsArgumentNullException(); - } - - [Fact] - public override Task GetAsync_WithNumericTypeConversion_ConvertsIntToLong() - { - return base.GetAsync_WithNumericTypeConversion_ConvertsIntToLong(); - } - - [Fact] - public override Task GetAsync_WithNumericTypeConversion_ConvertsLongToInt() - { - return base.GetAsync_WithNumericTypeConversion_ConvertsLongToInt(); - } - - [Fact] - public override Task GetAsync_WithTryGetSemanticsAndComplexTypeAsLong_ReturnsNoValue() - { - return base.GetAsync_WithTryGetSemanticsAndComplexTypeAsLong_ReturnsNoValue(); - } - - [Fact] - public override Task GetAsync_WithTryGetSemanticsAndIntAsLong_ConvertsSuccessfully() - { - return base.GetAsync_WithTryGetSemanticsAndIntAsLong_ConvertsSuccessfully(); - } - - [Fact] - public override Task GetAsync_WithTryGetSemanticsAndMaxLongAsInt_ReturnsNoValue() - { - return base.GetAsync_WithTryGetSemanticsAndMaxLongAsInt_ReturnsNoValue(); - } - [Fact] - public override Task GetExpirationAsync_AfterExpiry_ReturnsNull() - { - return base.GetExpirationAsync_AfterExpiry_ReturnsNull(); - } - - [Fact] - public override Task GetExpirationAsync_WithDifferentCasedKeys_GetsExactMatch() - { - return base.GetExpirationAsync_WithDifferentCasedKeys_GetsExactMatch(); - } - - [Fact] - public override Task GetExpirationAsync_WithEmptyKey_ThrowsArgumentException() - { - return base.GetExpirationAsync_WithEmptyKey_ThrowsArgumentException(); - } - - [Theory] - [InlineData("token:refresh")] - [InlineData(" ")] - public override Task GetExpirationAsync_WithExpiration_ReturnsCorrectTimeSpan(string cacheKey) - { - return base.GetExpirationAsync_WithExpiration_ReturnsCorrectTimeSpan(cacheKey); + return base.GetExpirationAsync_WithInvalidKey_ThrowsArgumentException(); } [Fact] @@ -357,35 +133,6 @@ public override Task GetExpirationAsync_WithLocalCache_ChecksLocalCacheFirst() return base.GetExpirationAsync_WithLocalCache_ChecksLocalCacheFirst(); } - [Fact] - public override Task GetExpirationAsync_WithExpiredKey_ReturnsNull() - { - return base.GetExpirationAsync_WithExpiredKey_ReturnsNull(); - } - - [Fact] - public override Task GetExpirationAsync_WithNoExpiration_ReturnsNull() - { - return base.GetExpirationAsync_WithNoExpiration_ReturnsNull(); - } - - [Fact] - public override Task GetExpirationAsync_WithNonExistentKey_ReturnsNull() - { - return base.GetExpirationAsync_WithNonExistentKey_ReturnsNull(); - } - - [Fact] - public override Task GetExpirationAsync_WithNullKey_ThrowsArgumentNullException() - { - return base.GetExpirationAsync_WithNullKey_ThrowsArgumentNullException(); - } - [Fact] - public override Task GetListAsync_WithEmptyKey_ThrowsArgumentException() - { - return base.GetListAsync_WithEmptyKey_ThrowsArgumentException(); - } - [Fact] public override Task GetListAsync_WithExpiredItems_RemovesExpiredAndReturnsActive() { @@ -393,42 +140,17 @@ public override Task GetListAsync_WithExpiredItems_RemovesExpiredAndReturnsActiv } [Fact] - public override Task GetListAsync_WithInvalidPageNumber_ThrowsArgumentOutOfRangeException() - { - return base.GetListAsync_WithInvalidPageNumber_ThrowsArgumentOutOfRangeException(); - } - - [Fact] - public override Task GetListAsync_WithMultiplePages_ReturnsAllItems() - { - return base.GetListAsync_WithMultiplePages_ReturnsAllItems(); - } - - [Fact] - public override Task GetListAsync_WithNewItemsAdded_ReturnsNewItemsLast() + public override Task GetListAsync_WithInvalidKey_ThrowsArgumentException() { - return base.GetListAsync_WithNewItemsAdded_ReturnsNewItemsLast(); + return base.GetListAsync_WithInvalidKey_ThrowsArgumentException(); } [Fact] - public override Task GetListAsync_WithNullKey_ThrowsArgumentNullException() + public override Task GetListAsync_WithPaging_ReturnsCorrectResults() { - return base.GetListAsync_WithNullKey_ThrowsArgumentNullException(); + return base.GetListAsync_WithPaging_ReturnsCorrectResults(); } - [Fact] - public override Task GetListAsync_WithPageBeyondEnd_ReturnsEmptyCollection() - { - return base.GetListAsync_WithPageBeyondEnd_ReturnsEmptyCollection(); - } - - [Theory] - [InlineData("cart:items")] - [InlineData(" ")] - public override Task GetListAsync_WithPaging_ReturnsCorrectPageSize(string cacheKey) - { - return base.GetListAsync_WithPaging_ReturnsCorrectPageSize(cacheKey); - } [Fact] public override Task GetUnixTimeMillisecondsAsync_WithLocalDateTime_ReturnsCorrectly() { @@ -447,26 +169,6 @@ public override Task GetUnixTimeSecondsAsync_WithUtcDateTime_ReturnsCorrectly() return base.GetUnixTimeSecondsAsync_WithUtcDateTime_ReturnsCorrectly(); } - [Fact] - public override Task IncrementAsync_WithDifferentCasedKeys_IncrementsDistinctCounters() - { - return base.IncrementAsync_WithDifferentCasedKeys_IncrementsDistinctCounters(); - } - - [Fact] - public override Task IncrementAsync_WithEmptyKey_ThrowsArgumentException() - { - return base.IncrementAsync_WithEmptyKey_ThrowsArgumentException(); - } - - [Theory] - [InlineData("metrics:page-views")] - [InlineData(" ")] - public override Task IncrementAsync_WithExistingKey_IncrementsValue(string cacheKey) - { - return base.IncrementAsync_WithExistingKey_IncrementsValue(cacheKey); - } - [Fact] public override Task IncrementAsync_WithExpiration_ExpiresCorrectly() { @@ -474,15 +176,15 @@ public override Task IncrementAsync_WithExpiration_ExpiresCorrectly() } [Fact] - public override Task IncrementAsync_WithNonExistentKey_InitializesToOne() + public override Task IncrementAsync_WithInvalidKey_ThrowsException() { - return base.IncrementAsync_WithNonExistentKey_InitializesToOne(); + return base.IncrementAsync_WithInvalidKey_ThrowsException(); } [Fact] - public override Task IncrementAsync_WithNullKey_ThrowsArgumentNullException() + public override Task IncrementAsync_WithKey_IncrementsCorrectly() { - return base.IncrementAsync_WithNullKey_ThrowsArgumentNullException(); + return base.IncrementAsync_WithKey_IncrementsCorrectly(); } [Fact] @@ -492,58 +194,15 @@ public override Task IncrementAsync_WithScopedCache_WorksWithinScope() } [Fact] - public override Task IncrementAsync_WithSpecifiedAmount_IncrementsCorrectly() - { - return base.IncrementAsync_WithSpecifiedAmount_IncrementsCorrectly(); - } - [Fact] - public override Task ListAddAsync_WithDifferentCasedKeys_MaintainsDistinctLists() - { - return base.ListAddAsync_WithDifferentCasedKeys_MaintainsDistinctLists(); - } - - [Theory] - [InlineData("cart:items")] - [InlineData(" ")] - public override Task ListAddAsync_WithDuplicates_RemovesDuplicatesAndAddsItems(string cacheKey) - { - return base.ListAddAsync_WithDuplicates_RemovesDuplicatesAndAddsItems(cacheKey); - } - - [Fact] - public override Task ListAddAsync_WithDuplicates_StoresUniqueValuesOnly() - { - return base.ListAddAsync_WithDuplicates_StoresUniqueValuesOnly(); - } - - [Fact] - public override Task ListAddAsync_WithEmptyCollection_NoOp() - { - return base.ListAddAsync_WithEmptyCollection_NoOp(); - } - - [Fact] - public override Task ListAddAsync_WithEmptyKey_ThrowsArgumentException() + public override Task ListAddAsync_WithExpiration_ExpiresCorrectly() { - return base.ListAddAsync_WithEmptyKey_ThrowsArgumentException(); + return base.ListAddAsync_WithExpiration_ExpiresCorrectly(); } [Fact] - public override Task ListAddAsync_WithExistingNonListKey_ThrowsException() + public override Task ListAddAsync_WithInvalidArguments_ThrowsException() { - return base.ListAddAsync_WithExistingNonListKey_ThrowsException(); - } - - [Fact] - public override Task ListAddAsync_WithFutureExpiration_AddsAndExpiresCorrectly() - { - return base.ListAddAsync_WithFutureExpiration_AddsAndExpiresCorrectly(); - } - - [Fact] - public override Task ListAddAsync_WithMultipleExpirations_ExpiresIndividualItems() - { - return base.ListAddAsync_WithMultipleExpirations_ExpiresIndividualItems(); + return base.ListAddAsync_WithInvalidArguments_ThrowsException(); } [Fact] @@ -552,83 +211,22 @@ public override Task ListAddAsync_WithMultipleInstances_WorksCorrectly() return base.ListAddAsync_WithMultipleInstances_WorksCorrectly(); } - [Fact] - public override Task ListAddAsync_WithNullCollection_ThrowsArgumentNullException() - { - return base.ListAddAsync_WithNullCollection_ThrowsArgumentNullException(); - } - - [Fact] - public override Task ListAddAsync_WithNullItem_IgnoresNull() - { - return base.ListAddAsync_WithNullItem_IgnoresNull(); - } - - [Fact] - public override Task ListAddAsync_WithNullKey_ThrowsArgumentNullException() - { - return base.ListAddAsync_WithNullKey_ThrowsArgumentNullException(); - } - - [Fact] - public override Task ListAddAsync_WithNullValues_ThrowsArgumentNullException() - { - return base.ListAddAsync_WithNullValues_ThrowsArgumentNullException(); - } - - [Fact] - public override Task ListAddAsync_WithPastExpiration_RemovesItem() - { - return base.ListAddAsync_WithPastExpiration_RemovesItem(); - } - [Fact] public override Task ListAddAsync_WithSingleString_StoresAsStringNotCharArray() { return base.ListAddAsync_WithSingleString_StoresAsStringNotCharArray(); } - [Fact] - public override Task ListRemoveAsync_WithEmptyKey_ThrowsArgumentException() - { - return base.ListRemoveAsync_WithEmptyKey_ThrowsArgumentException(); - } - - [Theory] - [InlineData("cart:items")] - [InlineData(" ")] - public override Task ListRemoveAsync_WithMultipleValues_RemovesAll(string cacheKey) - { - return base.ListRemoveAsync_WithMultipleValues_RemovesAll(cacheKey); - } - - [Fact] - public override Task ListRemoveAsync_WithNullCollection_ThrowsArgumentNullException() - { - return base.ListRemoveAsync_WithNullCollection_ThrowsArgumentNullException(); - } [Fact] - public override Task ListRemoveAsync_WithNullItem_IgnoresNull() + public override Task ListAddAsync_WithVariousInputs_HandlesCorrectly() { - return base.ListRemoveAsync_WithNullItem_IgnoresNull(); + return base.ListAddAsync_WithVariousInputs_HandlesCorrectly(); } [Fact] - public override Task ListRemoveAsync_WithNullKey_ThrowsArgumentNullException() + public override Task ListRemoveAsync_WithInvalidInputs_ThrowsAppropriateException() { - return base.ListRemoveAsync_WithNullKey_ThrowsArgumentNullException(); - } - - [Fact] - public override Task ListRemoveAsync_WithNullValues_ThrowsArgumentNullException() - { - return base.ListRemoveAsync_WithNullValues_ThrowsArgumentNullException(); - } - - [Fact] - public override Task ListRemoveAsync_WithSingleValue_RemovesCorrectly() - { - return base.ListRemoveAsync_WithSingleValue_RemovesCorrectly(); + return base.ListRemoveAsync_WithInvalidInputs_ThrowsAppropriateException(); } [Fact] @@ -636,22 +234,16 @@ public override Task ListRemoveAsync_WithValidValues_RemovesKeyWhenEmpty() { return base.ListRemoveAsync_WithValidValues_RemovesKeyWhenEmpty(); } - [Fact] - public override Task RemoveAllAsync_WithEmptyKeys_Succeeds() - { - return base.RemoveAllAsync_WithEmptyKeys_Succeeds(); - } [Fact] - public override Task RemoveAllAsync_WithKeysContainingEmpty_ThrowsArgumentException() + public override Task ListRemoveAsync_WithValues_RemovesCorrectly() { - return base.RemoveAllAsync_WithKeysContainingEmpty_ThrowsArgumentException(); + return base.ListRemoveAsync_WithValues_RemovesCorrectly(); } - [Fact] - public override Task RemoveAllAsync_WithKeysContainingNull_ThrowsArgumentNullException() + public override Task RemoveAllAsync_WithInvalidKeys_ValidatesCorrectly() { - return base.RemoveAllAsync_WithKeysContainingNull_ThrowsArgumentNullException(); + return base.RemoveAllAsync_WithInvalidKeys_ValidatesCorrectly(); } [Fact] @@ -666,80 +258,34 @@ public override Task RemoveAllAsync_WithLocalCache_InvalidatesLocalCache() return base.RemoveAllAsync_WithLocalCache_InvalidatesLocalCache(); } - [Fact] - public override Task RemoveAllAsync_WithMixedCaseKeys_RemovesOnlyExactMatches() - { - return base.RemoveAllAsync_WithMixedCaseKeys_RemovesOnlyExactMatches(); - } - - [Fact] - public override Task RemoveAllAsync_WithNullKeys_RemovesAllValues() - { - return base.RemoveAllAsync_WithNullKeys_RemovesAllValues(); - } - [Fact] public override Task RemoveAllAsync_WithScopedCache_AffectsOnlyScopedKeys() { return base.RemoveAllAsync_WithScopedCache_AffectsOnlyScopedKeys(); } - [Theory] - [InlineData("remove-all-keys:")] - [InlineData(" ")] - public override Task RemoveAllAsync_WithSpecificKeyCollection_RemovesOnlySpecifiedKeys(string keyPrefix) - { - return base.RemoveAllAsync_WithSpecificKeyCollection_RemovesOnlySpecifiedKeys(keyPrefix); - } - - [Fact] - public override Task RemoveAsync_AfterSetAndGet_RemovesCorrectly() - { - return base.RemoveAsync_AfterSetAndGet_RemovesCorrectly(); - } - - [Fact] - public override Task RemoveAsync_MultipleTimes_Succeeds() - { - return base.RemoveAsync_MultipleTimes_Succeeds(); - } - - [Fact] - public override Task RemoveAsync_WithEmptyKey_ThrowsArgumentException() - { - return base.RemoveAsync_WithEmptyKey_ThrowsArgumentException(); - } - - [Theory] - [InlineData("session:active")] - [InlineData(" ")] - public override Task RemoveAsync_WithExistingKey_RemovesSuccessfully(string cacheKey) - { - return base.RemoveAsync_WithExistingKey_RemovesSuccessfully(cacheKey); - } - [Fact] - public override Task RemoveAsync_WithExpiredKey_Succeeds() + public override Task RemoveAllAsync_WithSpecificKeyCollection_RemovesOnlySpecifiedKeys() { - return base.RemoveAsync_WithExpiredKey_Succeeds(); + return base.RemoveAllAsync_WithSpecificKeyCollection_RemovesOnlySpecifiedKeys(); } [Fact] - public override Task RemoveAsync_WithNonExistentKey_Succeeds() + public override Task RemoveAsync_WithInvalidKey_ThrowsArgumentException() { - return base.RemoveAsync_WithNonExistentKey_Succeeds(); + return base.RemoveAsync_WithInvalidKey_ThrowsArgumentException(); } [Fact] - public override Task RemoveAsync_WithNullKey_ThrowsArgumentNullException() + public override Task RemoveAsync_WithNonExistentKey_ReturnsFalse() { - return base.RemoveAsync_WithNullKey_ThrowsArgumentNullException(); + return base.RemoveAsync_WithNonExistentKey_ReturnsFalse(); } [Fact] - public override Task RemoveAsync_WithNullValue_RemovesSuccessfully() + public override Task RemoveAsync_WithExpiredKey_KeyDoesNotExist() { - return base.RemoveAsync_WithNullValue_RemovesSuccessfully(); + return base.RemoveAsync_WithExpiredKey_KeyDoesNotExist(); } [Fact] @@ -749,14 +295,9 @@ public override Task RemoveAsync_WithScopedCache_RemovesOnlyWithinScope() } [Fact] - public override Task RemoveAsync_WithSpecificCase_RemovesOnlyMatchingKey() - { - return base.RemoveAsync_WithSpecificCase_RemovesOnlyMatchingKey(); - } - [Fact] - public override Task RemoveByPrefixAsync_AsteriskPrefixWithScopedCache_TreatedAsLiteral() + public override Task RemoveAsync_WithValidKey_RemovesSuccessfully() { - return base.RemoveByPrefixAsync_AsteriskPrefixWithScopedCache_TreatedAsLiteral(); + return base.RemoveAsync_WithValidKey_RemovesSuccessfully(); } [Theory] @@ -784,27 +325,9 @@ public override Task RemoveByPrefixAsync_PartialPrefixWithScopedCache_RemovesMat } [Fact] - public override Task RemoveByPrefixAsync_WithCaseSensitivePrefix_RemovesOnlyMatchingCase() - { - return base.RemoveByPrefixAsync_WithCaseSensitivePrefix_RemovesOnlyMatchingCase(); - } - - [Fact] - public override Task RemoveByPrefixAsync_WithDifferentCasedScopes_RemovesOnlyMatchingScope() - { - return base.RemoveByPrefixAsync_WithDifferentCasedScopes_RemovesOnlyMatchingScope(); - } - - [Fact] - public override Task RemoveByPrefixAsync_WithDoubleAsteriskPrefix_TreatsAsLiteral() - { - return base.RemoveByPrefixAsync_WithDoubleAsteriskPrefix_TreatsAsLiteral(); - } - - [Fact] - public override Task RemoveByPrefixAsync_WithEmptyPrefix_RemovesAllKeys() + public override Task RemoveByPrefixAsync_WithAsteriskPrefix_TreatedAsLiteral() { - return base.RemoveByPrefixAsync_WithEmptyPrefix_RemovesAllKeys(); + return base.RemoveByPrefixAsync_WithAsteriskPrefix_TreatedAsLiteral(); } [Theory] @@ -820,12 +343,10 @@ public override Task RemoveByPrefixAsync_WithLocalCache_InvalidatesLocalCache() return base.RemoveByPrefixAsync_WithLocalCache_InvalidatesLocalCache(); } - [Theory] - [InlineData("blah:")] - [InlineData(" ")] - public override Task RemoveByPrefixAsync_WithMatchingPrefix_RemovesOnlyPrefixedKeys(string prefix) + [Fact] + public override Task RemoveByPrefixAsync_WithMatchingPrefix_RemovesOnlyMatchingKeys() { - return base.RemoveByPrefixAsync_WithMatchingPrefix_RemovesOnlyPrefixedKeys(prefix); + return base.RemoveByPrefixAsync_WithMatchingPrefix_RemovesOnlyMatchingKeys(); } [Theory] @@ -839,15 +360,9 @@ public override Task RemoveByPrefixAsync_WithMultipleMatchingKeys_RemovesOnlyPre } [Fact] - public override Task RemoveByPrefixAsync_WithNonMatchingPrefix_RemovesZeroKeys() + public override Task RemoveByPrefixAsync_WithNullOrEmptyPrefix_RemovesAllKeys() { - return base.RemoveByPrefixAsync_WithNonMatchingPrefix_RemovesZeroKeys(); - } - - [Fact] - public override Task RemoveByPrefixAsync_WithNullPrefix_RemovesAllKeys() - { - return base.RemoveByPrefixAsync_WithNullPrefix_RemovesAllKeys(); + return base.RemoveByPrefixAsync_WithNullOrEmptyPrefix_RemovesAllKeys(); } [Theory] @@ -878,17 +393,15 @@ public override Task RemoveByPrefixAsync_WithWildcardPattern_TreatsAsLiteral(str } [Fact] - public override Task RemoveIfEqualAsync_WithEmptyKey_ThrowsArgumentException() + public override Task RemoveIfEqualAsync_WithInvalidKey_ThrowsArgumentException() { - return base.RemoveIfEqualAsync_WithEmptyKey_ThrowsArgumentException(); + return base.RemoveIfEqualAsync_WithInvalidKey_ThrowsArgumentException(); } - [Theory] - [InlineData("session:active")] - [InlineData(" ")] - public override Task RemoveIfEqualAsync_WithMatchingValue_ReturnsTrueAndRemoves(string cacheKey) + [Fact] + public override Task RemoveIfEqualAsync_WithMatchingValue_ReturnsTrueAndRemoves() { - return base.RemoveIfEqualAsync_WithMatchingValue_ReturnsTrueAndRemoves(cacheKey); + return base.RemoveIfEqualAsync_WithMatchingValue_ReturnsTrueAndRemoves(); } [Fact] @@ -898,34 +411,15 @@ public override Task RemoveIfEqualAsync_WithMismatchedValue_ReturnsFalseAndDoesN } [Fact] - public override Task RemoveIfEqualAsync_WithNullKey_ThrowsArgumentNullException() + public override Task ReplaceAsync_WithExistingKey_ReturnsTrueAndReplacesValue() { - return base.RemoveIfEqualAsync_WithNullKey_ThrowsArgumentNullException(); - } - [Fact] - public override Task ReplaceAsync_WithDifferentCasedKeys_TreatsAsDifferentKeys() - { - return base.ReplaceAsync_WithDifferentCasedKeys_TreatsAsDifferentKeys(); + return base.ReplaceAsync_WithExistingKey_ReturnsTrueAndReplacesValue(); } [Fact] - public override Task ReplaceAsync_WithEmptyKey_ThrowsArgumentException() + public override Task ReplaceAsync_WithInvalidKey_ThrowsException() { - return base.ReplaceAsync_WithEmptyKey_ThrowsArgumentException(); - } - - [Theory] - [InlineData("settings:theme")] - [InlineData(" ")] - public override Task ReplaceAsync_WithExistingKey_ReturnsTrueAndReplacesValue(string cacheKey) - { - return base.ReplaceAsync_WithExistingKey_ReturnsTrueAndReplacesValue(cacheKey); - } - - [Fact] - public override Task ReplaceAsync_WithExpiration_SetsExpirationCorrectly() - { - return base.ReplaceAsync_WithExpiration_SetsExpirationCorrectly(); + return base.ReplaceAsync_WithInvalidKey_ThrowsException(); } [Fact] @@ -935,34 +429,21 @@ public override Task ReplaceAsync_WithNonExistentKey_ReturnsFalseAndDoesNotCreat } [Fact] - public override Task ReplaceAsync_WithNullKey_ThrowsArgumentNullException() - { - return base.ReplaceAsync_WithNullKey_ThrowsArgumentNullException(); - } - [Fact] - public override Task ReplaceIfEqualAsync_WithDifferentCasedKeys_ReplacesOnlyExactMatch() + public override Task ReplaceIfEqualAsync_WithExpiration_SetsExpirationCorrectly() { - return base.ReplaceIfEqualAsync_WithDifferentCasedKeys_ReplacesOnlyExactMatch(); + return base.ReplaceIfEqualAsync_WithExpiration_SetsExpirationCorrectly(); } [Fact] - public override Task ReplaceIfEqualAsync_WithEmptyKey_ThrowsArgumentException() + public override Task ReplaceIfEqualAsync_WithInvalidKey_ThrowsArgumentException() { - return base.ReplaceIfEqualAsync_WithEmptyKey_ThrowsArgumentException(); + return base.ReplaceIfEqualAsync_WithInvalidKey_ThrowsArgumentException(); } [Fact] - public override Task ReplaceIfEqualAsync_WithExpiration_SetsExpirationCorrectly() - { - return base.ReplaceIfEqualAsync_WithExpiration_SetsExpirationCorrectly(); - } - - [Theory] - [InlineData("workflow:state")] - [InlineData(" ")] - public override Task ReplaceIfEqualAsync_WithMatchingOldValue_ReturnsTrueAndReplacesValue(string cacheKey) + public override Task ReplaceIfEqualAsync_WithMatchingOldValue_ReturnsTrueAndReplacesValue() { - return base.ReplaceIfEqualAsync_WithMatchingOldValue_ReturnsTrueAndReplacesValue(cacheKey); + return base.ReplaceIfEqualAsync_WithMatchingOldValue_ReturnsTrueAndReplacesValue(); } [Fact] @@ -971,33 +452,28 @@ public override Task ReplaceIfEqualAsync_WithMismatchedOldValue_ReturnsFalseAndD return base.ReplaceIfEqualAsync_WithMismatchedOldValue_ReturnsFalseAndDoesNotReplace(); } - [Fact] - public override Task ReplaceIfEqualAsync_WithNullKey_ThrowsArgumentNullException() - { - return base.ReplaceIfEqualAsync_WithNullKey_ThrowsArgumentNullException(); - } - [Fact] + [Fact(Skip = "Performance Test")] public override Task Serialization_WithComplexObjectsAndValidation_MeasuresThroughput() { return base.Serialization_WithComplexObjectsAndValidation_MeasuresThroughput(); } - [Fact] + [Fact(Skip = "Performance Test")] public override Task Serialization_WithSimpleObjectsAndValidation_MeasuresThroughput() { return base.Serialization_WithSimpleObjectsAndValidation_MeasuresThroughput(); } [Fact] - public override Task SetAllAsync_WithDateTimeMinValue_DoesNotAddKeys() + public override Task SetAllAsync_WithExpiration_KeysExpireCorrectly() { - return base.SetAllAsync_WithDateTimeMinValue_DoesNotAddKeys(); + return base.SetAllAsync_WithExpiration_KeysExpireCorrectly(); } [Fact] - public override Task SetAllAsync_WithDifferentCasedKeys_CreatesDistinctEntries() + public override Task SetAllAsync_WithInvalidItems_ValidatesCorrectly() { - return base.SetAllAsync_WithDifferentCasedKeys_CreatesDistinctEntries(); + return base.SetAllAsync_WithInvalidItems_ValidatesCorrectly(); } [Fact] @@ -1006,88 +482,24 @@ public override Task SetAllAsync_WithLargeNumberOfKeys_MeasuresThroughput() return base.SetAllAsync_WithLargeNumberOfKeys_MeasuresThroughput(); } - [Fact] - public override Task SetAllAsync_WithEmptyItems_ReturnsTrue() - { - return base.SetAllAsync_WithEmptyItems_ReturnsTrue(); - } - - [Theory] - [InlineData("test")] - [InlineData(" ")] - public override Task SetAllAsync_WithExpiration_KeysExpireCorrectly(string cacheKey) - { - return base.SetAllAsync_WithExpiration_KeysExpireCorrectly(cacheKey); - } - - [Fact] - public override Task SetAllAsync_WithItemsContainingEmptyKey_ThrowsArgumentException() - { - return base.SetAllAsync_WithItemsContainingEmptyKey_ThrowsArgumentException(); - } - - [Fact] - public override Task SetAllAsync_WithNullItems_ThrowsArgumentNullException() - { - return base.SetAllAsync_WithNullItems_ThrowsArgumentNullException(); - } - [Theory] [InlineData(1000)] [InlineData(10000)] - public override Task SetAllExpiration_WithLargeNumberOfKeys_SetsAllExpirations(int count) + public override Task SetAllExpirationAsync_WithLargeNumberOfKeys_SetsAllExpirations(int count) { - return base.SetAllExpiration_WithLargeNumberOfKeys_SetsAllExpirations(count); + return base.SetAllExpirationAsync_WithLargeNumberOfKeys_SetsAllExpirations(count); } [Fact] - public override Task SetAllExpiration_WithMultipleKeys_SetsExpirationForAll() + public override Task SetAllExpirationAsync_WithMixedExpirations_SetsExpirationsCorrectly() { - return base.SetAllExpiration_WithMultipleKeys_SetsExpirationForAll(); + return base.SetAllExpirationAsync_WithMixedExpirations_SetsExpirationsCorrectly(); } [Fact] - public override Task SetAllExpiration_WithNonExistentKeys_HandlesGracefully() + public override Task SetAsync_WithComplexObject_StoresCorrectly() { - return base.SetAllExpiration_WithNonExistentKeys_HandlesGracefully(); - } - - [Fact] - public override Task SetAllExpiration_WithNullValues_RemovesExpiration() - { - return base.SetAllExpiration_WithNullValues_RemovesExpiration(); - } - - [Theory] - [InlineData("user:profile")] - [InlineData(" ")] - public override Task SetAsync_WithComplexObject_StoresCorrectly(string cacheKey) - { - return base.SetAsync_WithComplexObject_StoresCorrectly(cacheKey); - } - - [Fact] - public override Task SetAsync_WithDifferentCasedKeys_CreatesDistinctEntries() - { - return base.SetAsync_WithDifferentCasedKeys_CreatesDistinctEntries(); - } - - [Fact] - public override Task SetAsync_WithDifferentCasedScopes_MaintainsDistinctNamespaces() - { - return base.SetAsync_WithDifferentCasedScopes_MaintainsDistinctNamespaces(); - } - - [Fact] - public override Task SetAsync_WithDifferentScopes_IsolatesKeys() - { - return base.SetAsync_WithDifferentScopes_IsolatesKeys(); - } - - [Fact] - public override Task SetAsync_WithEmptyKey_ThrowsArgumentException() - { - return base.SetAsync_WithEmptyKey_ThrowsArgumentException(); + return base.SetAsync_WithComplexObject_StoresCorrectly(); } [Fact] @@ -1097,9 +509,9 @@ public override Task SetAsync_WithExpirationEdgeCases_HandlesCorrectly() } [Fact] - public override Task SetAsync_WithLargeNumber_StoresCorrectly() + public override Task SetAsync_WithInvalidKey_ThrowsArgumentException() { - return base.SetAsync_WithLargeNumber_StoresCorrectly(); + return base.SetAsync_WithInvalidKey_ThrowsArgumentException(); } [Fact] @@ -1115,27 +527,15 @@ public override Task SetAsync_WithMultipleInstances_UsesLocalCache() } [Fact] - public override Task SetAsync_WithNestedScopes_PreservesHierarchy() + public override Task SetAsync_WithNullValue_StoresAsNullValue() { - return base.SetAsync_WithNestedScopes_PreservesHierarchy(); + return base.SetAsync_WithNullValue_StoresAsNullValue(); } [Fact] - public override Task SetAsync_WithNullKey_ThrowsArgumentNullException() + public override Task SetAsync_WithScopedCaches_IsolatesKeys() { - return base.SetAsync_WithNullKey_ThrowsArgumentNullException(); - } - - [Fact] - public override Task SetAsync_WithNullReferenceType_StoresAsNullValue() - { - return base.SetAsync_WithNullReferenceType_StoresAsNullValue(); - } - - [Fact] - public override Task SetAsync_WithNullValueType_StoresAsNullValue() - { - return base.SetAsync_WithNullValueType_StoresAsNullValue(); + return base.SetAsync_WithScopedCaches_IsolatesKeys(); } [Fact] @@ -1143,24 +543,17 @@ public override Task SetAsync_WithShortExpiration_ExpiresCorrectly() { return base.SetAsync_WithShortExpiration_ExpiresCorrectly(); } - [Theory] - [InlineData("token:refresh")] - [InlineData(" ")] - public override Task SetExpirationAsync_ChangingFromNoExpirationToFutureTime_UpdatesCorrectly(string cacheKey) - { - return base.SetExpirationAsync_ChangingFromNoExpirationToFutureTime_UpdatesCorrectly(cacheKey); - } [Fact] - public override Task SetExpirationAsync_ChangingToDateTimeMinValue_RemovesKey() + public override Task SetExpirationAsync_ChangingFromNoExpirationToFutureTime_UpdatesCorrectly() { - return base.SetExpirationAsync_ChangingToDateTimeMinValue_RemovesKey(); + return base.SetExpirationAsync_ChangingFromNoExpirationToFutureTime_UpdatesCorrectly(); } [Fact] - public override Task SetExpirationAsync_WithCurrentTime_ExpiresImmediately() + public override Task SetExpirationAsync_ChangingToDateTimeMinValue_RemovesKey() { - return base.SetExpirationAsync_WithCurrentTime_ExpiresImmediately(); + return base.SetExpirationAsync_ChangingToDateTimeMinValue_RemovesKey(); } [Fact] @@ -1170,38 +563,15 @@ public override Task SetExpirationAsync_WithDateTimeMaxValue_NeverExpires() } [Fact] - public override Task SetExpirationAsync_WithDateTimeMinValue_ExpiresImmediately() + public override Task SetExpirationAsync_WithInvalidKey_ThrowsArgumentException() { - return base.SetExpirationAsync_WithDateTimeMinValue_ExpiresImmediately(); + return base.SetExpirationAsync_WithInvalidKey_ThrowsArgumentException(); } [Fact] - public override Task SetExpirationAsync_WithDifferentCasedKeys_SetsOnlyExactMatch() - { - return base.SetExpirationAsync_WithDifferentCasedKeys_SetsOnlyExactMatch(); - } - - [Fact] - public override Task SetExpirationAsync_WithEmptyKey_ThrowsArgumentException() - { - return base.SetExpirationAsync_WithEmptyKey_ThrowsArgumentException(); - } - - [Fact] - public override Task SetExpirationAsync_WithNullKey_ThrowsArgumentNullException() - { - return base.SetExpirationAsync_WithNullKey_ThrowsArgumentNullException(); - } - [Fact] - public override Task SetIfHigherAsync_WithDateTime_DoesNotUpdateWhenLower() + public override Task SetExpirationAsync_WithPastOrCurrentTime_ExpiresImmediately() { - return base.SetIfHigherAsync_WithDateTime_DoesNotUpdateWhenLower(); - } - - [Fact] - public override Task SetIfHigherAsync_WithDateTime_InitializesWhenKeyNotExists() - { - return base.SetIfHigherAsync_WithDateTime_InitializesWhenKeyNotExists(); + return base.SetExpirationAsync_WithPastOrCurrentTime_ExpiresImmediately(); } [Fact] @@ -1211,21 +581,9 @@ public override Task SetIfHigherAsync_WithDateTime_UpdatesWhenHigher() } [Fact] - public override Task SetIfHigherAsync_WithLargeNumbers_UpdatesWhenHigher() - { - return base.SetIfHigherAsync_WithLargeNumbers_UpdatesWhenHigher(); - } - - [Fact] - public override Task SetIfLowerAsync_WithDateTime_DoesNotUpdateWhenHigher() - { - return base.SetIfLowerAsync_WithDateTime_DoesNotUpdateWhenHigher(); - } - - [Fact] - public override Task SetIfLowerAsync_WithDateTime_InitializesWhenKeyNotExists() + public override Task SetIfHigherAsync_WithLargeNumbers_HandlesCorrectly() { - return base.SetIfLowerAsync_WithDateTime_InitializesWhenKeyNotExists(); + return base.SetIfHigherAsync_WithLargeNumbers_HandlesCorrectly(); } [Fact] @@ -1235,9 +593,9 @@ public override Task SetIfLowerAsync_WithDateTime_UpdatesWhenLower() } [Fact] - public override Task SetIfLowerAsync_WithLargeNumbers_UpdatesWhenLower() + public override Task SetIfLowerAsync_WithLargeNumbers_HandlesCorrectly() { - return base.SetIfLowerAsync_WithLargeNumbers_UpdatesWhenLower(); + return base.SetIfLowerAsync_WithLargeNumbers_HandlesCorrectly(); } [Fact] From 43a1a874c0a796c83aeeabe9c95806fd5f89c58b Mon Sep 17 00:00:00 2001 From: Blake Niemyjski Date: Thu, 27 Nov 2025 13:37:52 +1300 Subject: [PATCH 19/26] Updated to foundatio nightly --- .../Foundatio.SampleJobClient/Foundatio.SampleJobClient.csproj | 2 +- src/Foundatio.Redis/Foundatio.Redis.csproj | 2 +- tests/Directory.Build.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/samples/Foundatio.SampleJobClient/Foundatio.SampleJobClient.csproj b/samples/Foundatio.SampleJobClient/Foundatio.SampleJobClient.csproj index 806cb6b..b6e201a 100644 --- a/samples/Foundatio.SampleJobClient/Foundatio.SampleJobClient.csproj +++ b/samples/Foundatio.SampleJobClient/Foundatio.SampleJobClient.csproj @@ -8,7 +8,7 @@ - + diff --git a/src/Foundatio.Redis/Foundatio.Redis.csproj b/src/Foundatio.Redis/Foundatio.Redis.csproj index f2caec5..67d785f 100644 --- a/src/Foundatio.Redis/Foundatio.Redis.csproj +++ b/src/Foundatio.Redis/Foundatio.Redis.csproj @@ -1,7 +1,7 @@ - + diff --git a/tests/Directory.Build.props b/tests/Directory.Build.props index 6022b45..73200dd 100644 --- a/tests/Directory.Build.props +++ b/tests/Directory.Build.props @@ -11,7 +11,7 @@ - + From ff93427a25977568e3e927fa1a677e23c0db975a Mon Sep 17 00:00:00 2001 From: Blake Niemyjski Date: Thu, 27 Nov 2025 13:40:35 +1300 Subject: [PATCH 20/26] Updates test and benchmark dependencies Updates GitHubActionsTestLogger to version 3.0.1. Updates BenchmarkDotNet to version 0.15.6. These updates likely bring bug fixes, performance improvements, and new features to the testing and benchmarking processes. --- tests/Directory.Build.props | 2 +- tests/Foundatio.Benchmarks/Foundatio.Benchmarks.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Directory.Build.props b/tests/Directory.Build.props index 73200dd..c0cae48 100644 --- a/tests/Directory.Build.props +++ b/tests/Directory.Build.props @@ -9,7 +9,7 @@ - + diff --git a/tests/Foundatio.Benchmarks/Foundatio.Benchmarks.csproj b/tests/Foundatio.Benchmarks/Foundatio.Benchmarks.csproj index b98a268..14a5b32 100644 --- a/tests/Foundatio.Benchmarks/Foundatio.Benchmarks.csproj +++ b/tests/Foundatio.Benchmarks/Foundatio.Benchmarks.csproj @@ -6,6 +6,6 @@ - + From 46fe65ec81cdce7c96c2e0023cdecffb01e89aab Mon Sep 17 00:00:00 2001 From: Blake Niemyjski Date: Thu, 27 Nov 2025 14:27:54 +1300 Subject: [PATCH 21/26] Updates exception type for script result mismatch Changes the exception type thrown when the script returns an unexpected number of results. Replaces ArgumentException with InvalidOperationException to better reflect the nature of the error. --- src/Foundatio.Redis/Cache/RedisCacheClient.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Foundatio.Redis/Cache/RedisCacheClient.cs b/src/Foundatio.Redis/Cache/RedisCacheClient.cs index 6ed833a..d0385ef 100644 --- a/src/Foundatio.Redis/Cache/RedisCacheClient.cs +++ b/src/Foundatio.Redis/Cache/RedisCacheClient.cs @@ -36,7 +36,6 @@ public sealed class RedisCacheClient : ICacheClient, IHaveSerializer private LoadedLuaScript _setAllExpiration; public RedisCacheClient(RedisCacheClientOptions options) - { _options = options; _timeProvider = options.TimeProvider ?? TimeProvider.System; @@ -857,7 +856,7 @@ await Parallel.ForEachAsync( // -2 = key doesn't exist, -1 = no expiration, positive = TTL in ms long[] ttls = (long[])redisResult; if (ttls is null || ttls.Length != hashSlotKeys.Length) - throw new ArgumentException("Hash slot count mismatch"); + throw new InvalidOperationException($"Script returned {ttls?.Length ?? 0} results for {hashSlotKeys.Length} keys"); for (int hashSlotIndex = 0; hashSlotIndex < hashSlotKeys.Length; hashSlotIndex++) { @@ -882,7 +881,7 @@ await Parallel.ForEachAsync( // -2 = key doesn't exist, -1 = no expiration, positive = TTL in ms long[] ttls = (long[])redisResult; if (ttls is null || ttls.Length != redisKeys.Length) - throw new ArgumentException("Hash slot count mismatch"); + throw new InvalidOperationException($"Script returned {ttls?.Length ?? 0} results for {redisKeys.Length} keys"); var result = new Dictionary(); for (int keyIndex = 0; keyIndex < redisKeys.Length; keyIndex++) From afb5238f50541f8d600bda6088a7f75038c64ef1 Mon Sep 17 00:00:00 2001 From: Blake Niemyjski Date: Thu, 27 Nov 2025 14:46:22 +1300 Subject: [PATCH 22/26] Adds expiration validation tests Adds validation tests for GetAllExpirationAsync and SetAllExpirationAsync methods. Updates Foundatio dependency to pick up new test fixes. --- .../Foundatio.SampleJobClient.csproj | 2 +- src/Foundatio.Redis/Foundatio.Redis.csproj | 2 +- tests/Directory.Build.props | 2 +- .../Caching/RedisCacheClientTests.cs | 12 ++++++++++++ .../Caching/RedisHybridCacheClientTests.cs | 12 ++++++++++++ .../Caching/ScopedRedisCacheClientTests.cs | 12 ++++++++++++ .../Caching/ScopedRedisHybridCacheClientTests.cs | 12 ++++++++++++ 7 files changed, 51 insertions(+), 3 deletions(-) diff --git a/samples/Foundatio.SampleJobClient/Foundatio.SampleJobClient.csproj b/samples/Foundatio.SampleJobClient/Foundatio.SampleJobClient.csproj index b6e201a..d651d94 100644 --- a/samples/Foundatio.SampleJobClient/Foundatio.SampleJobClient.csproj +++ b/samples/Foundatio.SampleJobClient/Foundatio.SampleJobClient.csproj @@ -8,7 +8,7 @@ - + diff --git a/src/Foundatio.Redis/Foundatio.Redis.csproj b/src/Foundatio.Redis/Foundatio.Redis.csproj index 67d785f..2f9b304 100644 --- a/src/Foundatio.Redis/Foundatio.Redis.csproj +++ b/src/Foundatio.Redis/Foundatio.Redis.csproj @@ -1,7 +1,7 @@ - + diff --git a/tests/Directory.Build.props b/tests/Directory.Build.props index c0cae48..8496f86 100644 --- a/tests/Directory.Build.props +++ b/tests/Directory.Build.props @@ -11,7 +11,7 @@ - + diff --git a/tests/Foundatio.Redis.Tests/Caching/RedisCacheClientTests.cs b/tests/Foundatio.Redis.Tests/Caching/RedisCacheClientTests.cs index 8a34971..aec4a0a 100644 --- a/tests/Foundatio.Redis.Tests/Caching/RedisCacheClientTests.cs +++ b/tests/Foundatio.Redis.Tests/Caching/RedisCacheClientTests.cs @@ -94,6 +94,12 @@ public override Task GetAllExpirationAsync_WithLargeNumberOfKeys_ReturnsAllExpir return base.GetAllExpirationAsync_WithLargeNumberOfKeys_ReturnsAllExpirations(count); } + [Fact] + public override Task GetAllExpirationAsync_WithInvalidKeys_ValidatesCorrectly() + { + return base.GetAllExpirationAsync_WithInvalidKeys_ValidatesCorrectly(); + } + [Fact] public override Task GetAllExpirationAsync_WithMixedKeys_ReturnsExpectedResults() { @@ -467,6 +473,12 @@ public override Task SetAllAsync_WithLargeNumberOfKeys_MeasuresThroughput() return base.SetAllAsync_WithLargeNumberOfKeys_MeasuresThroughput(); } + [Fact] + public override Task SetAllExpirationAsync_WithInvalidItems_ValidatesCorrectly() + { + return base.SetAllExpirationAsync_WithInvalidItems_ValidatesCorrectly(); + } + [Theory] [InlineData(1000)] [InlineData(10000)] diff --git a/tests/Foundatio.Redis.Tests/Caching/RedisHybridCacheClientTests.cs b/tests/Foundatio.Redis.Tests/Caching/RedisHybridCacheClientTests.cs index 9e54382..a9d0633 100644 --- a/tests/Foundatio.Redis.Tests/Caching/RedisHybridCacheClientTests.cs +++ b/tests/Foundatio.Redis.Tests/Caching/RedisHybridCacheClientTests.cs @@ -110,6 +110,12 @@ public override Task GetAllExpirationAsync_WithLargeNumberOfKeys_ReturnsAllExpir return base.GetAllExpirationAsync_WithLargeNumberOfKeys_ReturnsAllExpirations(count); } + [Fact] + public override Task GetAllExpirationAsync_WithInvalidKeys_ValidatesCorrectly() + { + return base.GetAllExpirationAsync_WithInvalidKeys_ValidatesCorrectly(); + } + [Fact] public override Task GetAllExpirationAsync_WithMixedKeys_ReturnsExpectedResults() { @@ -507,6 +513,12 @@ public override Task SetAllAsync_WithLargeNumberOfKeys_MeasuresThroughput() return base.SetAllAsync_WithLargeNumberOfKeys_MeasuresThroughput(); } + [Fact] + public override Task SetAllExpirationAsync_WithInvalidItems_ValidatesCorrectly() + { + return base.SetAllExpirationAsync_WithInvalidItems_ValidatesCorrectly(); + } + [Theory] [InlineData(1000)] [InlineData(10000)] diff --git a/tests/Foundatio.Redis.Tests/Caching/ScopedRedisCacheClientTests.cs b/tests/Foundatio.Redis.Tests/Caching/ScopedRedisCacheClientTests.cs index 7da9bd2..4cfaab4 100644 --- a/tests/Foundatio.Redis.Tests/Caching/ScopedRedisCacheClientTests.cs +++ b/tests/Foundatio.Redis.Tests/Caching/ScopedRedisCacheClientTests.cs @@ -78,6 +78,12 @@ public override Task GetAllExpirationAsync_WithLargeNumberOfKeys_ReturnsAllExpir return base.GetAllExpirationAsync_WithLargeNumberOfKeys_ReturnsAllExpirations(count); } + [Fact] + public override Task GetAllExpirationAsync_WithInvalidKeys_ValidatesCorrectly() + { + return base.GetAllExpirationAsync_WithInvalidKeys_ValidatesCorrectly(); + } + [Fact] public override Task GetAllExpirationAsync_WithMixedKeys_ReturnsExpectedResults() { @@ -433,6 +439,12 @@ public override Task SetAllAsync_WithLargeNumberOfKeys_MeasuresThroughput() return base.SetAllAsync_WithLargeNumberOfKeys_MeasuresThroughput(); } + [Fact] + public override Task SetAllExpirationAsync_WithInvalidItems_ValidatesCorrectly() + { + return base.SetAllExpirationAsync_WithInvalidItems_ValidatesCorrectly(); + } + [Theory] [InlineData(1000)] [InlineData(10000)] diff --git a/tests/Foundatio.Redis.Tests/Caching/ScopedRedisHybridCacheClientTests.cs b/tests/Foundatio.Redis.Tests/Caching/ScopedRedisHybridCacheClientTests.cs index 6d2d01d..f4c4aee 100644 --- a/tests/Foundatio.Redis.Tests/Caching/ScopedRedisHybridCacheClientTests.cs +++ b/tests/Foundatio.Redis.Tests/Caching/ScopedRedisHybridCacheClientTests.cs @@ -103,6 +103,12 @@ public override Task GetAllExpirationAsync_WithLargeNumberOfKeys_ReturnsAllExpir return base.GetAllExpirationAsync_WithLargeNumberOfKeys_ReturnsAllExpirations(count); } + [Fact] + public override Task GetAllExpirationAsync_WithInvalidKeys_ValidatesCorrectly() + { + return base.GetAllExpirationAsync_WithInvalidKeys_ValidatesCorrectly(); + } + [Fact] public override Task GetAllExpirationAsync_WithMixedKeys_ReturnsExpectedResults() { @@ -482,6 +488,12 @@ public override Task SetAllAsync_WithLargeNumberOfKeys_MeasuresThroughput() return base.SetAllAsync_WithLargeNumberOfKeys_MeasuresThroughput(); } + [Fact] + public override Task SetAllExpirationAsync_WithInvalidItems_ValidatesCorrectly() + { + return base.SetAllExpirationAsync_WithInvalidItems_ValidatesCorrectly(); + } + [Theory] [InlineData(1000)] [InlineData(10000)] From 230303f791d7e63ca01d47100ae8cee8e9015369 Mon Sep 17 00:00:00 2001 From: Blake Niemyjski Date: Thu, 27 Nov 2025 14:47:14 +1300 Subject: [PATCH 23/26] Improves Redis cache expiration handling Refactors cache key and expiration processing to prevent exceptions caused by null or empty keys. This change improves the robustness of the Redis cache client by adding explicit checks for null or empty keys, which can cause issues when setting expirations. It also optimizes key processing by pre-allocating lists when the input is a collection. This change avoids grouping by hash slot when no expirations are present to prevent unnecessary overhead. --- src/Foundatio.Redis/Cache/RedisCacheClient.cs | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/Foundatio.Redis/Cache/RedisCacheClient.cs b/src/Foundatio.Redis/Cache/RedisCacheClient.cs index d0385ef..8956f83 100644 --- a/src/Foundatio.Redis/Cache/RedisCacheClient.cs +++ b/src/Foundatio.Redis/Cache/RedisCacheClient.cs @@ -833,7 +833,13 @@ public Task SetExpirationAsync(string key, TimeSpan expiresIn) { ArgumentNullException.ThrowIfNull(keys); - var keyList = keys.Where(k => !String.IsNullOrEmpty(k)).Distinct().ToList(); + var keyList = keys is ICollection collection ? new List(collection.Count) : []; + foreach (string key in keys.Distinct()) + { + ArgumentException.ThrowIfNullOrEmpty(key, nameof(keys)); + keyList.Add(key); + } + if (keyList.Count is 0) return ReadOnlyDictionary.Empty; @@ -899,17 +905,18 @@ await Parallel.ForEachAsync( public async Task SetAllExpirationAsync(IDictionary expirations) { ArgumentNullException.ThrowIfNull(expirations); - - var validExpirations = expirations.Where(kvp => !String.IsNullOrEmpty(kvp.Key)).ToList(); - if (validExpirations.Count == 0) + if (expirations.Count == 0) return; + if (expirations.ContainsKey(String.Empty)) + throw new ArgumentException("Keys cannot be empty", nameof(expirations)); + await LoadScriptsAsync().AnyContext(); if (_options.ConnectionMultiplexer.IsCluster()) { await Parallel.ForEachAsync( - validExpirations.GroupBy(kvp => _options.ConnectionMultiplexer.HashSlot(kvp.Key)), + expirations.GroupBy(kvp => _options.ConnectionMultiplexer.HashSlot(kvp.Key)), async (hashSlotGroup, ct) => { var hashSlotExpirations = hashSlotGroup.ToList(); @@ -923,8 +930,8 @@ await Parallel.ForEachAsync( } else { - var keys = validExpirations.Select(kvp => (RedisKey)kvp.Key).ToArray(); - var values = validExpirations + var keys = expirations.Select(kvp => (RedisKey)kvp.Key).ToArray(); + var values = expirations .Select(kvp => (RedisValue)(kvp.Value.HasValue ? (long)kvp.Value.Value.TotalMilliseconds : -1)) .ToArray(); From 1cfae4ad08fcb5b1bb47ca92a6d691fb056f6945 Mon Sep 17 00:00:00 2001 From: Blake Niemyjski Date: Thu, 27 Nov 2025 21:04:40 +1300 Subject: [PATCH 24/26] Updates Foundatio package versions Updates Foundatio and related package references to the 12.0.1-preview.0.26 version. --- .../Foundatio.SampleJobClient/Foundatio.SampleJobClient.csproj | 2 +- src/Foundatio.Redis/Foundatio.Redis.csproj | 2 +- tests/Directory.Build.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/samples/Foundatio.SampleJobClient/Foundatio.SampleJobClient.csproj b/samples/Foundatio.SampleJobClient/Foundatio.SampleJobClient.csproj index d651d94..6742a92 100644 --- a/samples/Foundatio.SampleJobClient/Foundatio.SampleJobClient.csproj +++ b/samples/Foundatio.SampleJobClient/Foundatio.SampleJobClient.csproj @@ -8,7 +8,7 @@ - + diff --git a/src/Foundatio.Redis/Foundatio.Redis.csproj b/src/Foundatio.Redis/Foundatio.Redis.csproj index 2f9b304..e7c6e04 100644 --- a/src/Foundatio.Redis/Foundatio.Redis.csproj +++ b/src/Foundatio.Redis/Foundatio.Redis.csproj @@ -1,7 +1,7 @@ - + diff --git a/tests/Directory.Build.props b/tests/Directory.Build.props index 8496f86..6db073d 100644 --- a/tests/Directory.Build.props +++ b/tests/Directory.Build.props @@ -11,7 +11,7 @@ - + From f5f1afd564677e27b586687f71963395d597ead7 Mon Sep 17 00:00:00 2001 From: Blake Niemyjski Date: Fri, 28 Nov 2025 15:24:45 +1300 Subject: [PATCH 25/26] Reverted some .net 8 changes, these can be in a follow up pr. --- src/Foundatio.Redis/Cache/RedisCacheClient.cs | 136 ++++-------------- 1 file changed, 31 insertions(+), 105 deletions(-) diff --git a/src/Foundatio.Redis/Cache/RedisCacheClient.cs b/src/Foundatio.Redis/Cache/RedisCacheClient.cs index 8956f83..7a6cdde 100644 --- a/src/Foundatio.Redis/Cache/RedisCacheClient.cs +++ b/src/Foundatio.Redis/Cache/RedisCacheClient.cs @@ -83,22 +83,15 @@ public async Task RemoveAllAsync(IEnumerable keys = null) if (keys is null) { var endpoints = _options.ConnectionMultiplexer.GetEndPoints(); - if (endpoints.Length == 0) + if (endpoints.Length is 0) return 0; - // Get non-replica endpoints for processing - var nonReplicaEndpoints = endpoints - .Select(endpoint => _options.ConnectionMultiplexer.GetServer(endpoint)) - .Where(server => !server.IsReplica) - .ToArray(); - - // Most Redis deployments have few endpoints (1-3), so parallelism here is helpful - // but not critical. Controlling it helps prevent excessive load on Redis cluster. - int maxEndpointParallelism = Math.Min(Environment.ProcessorCount, nonReplicaEndpoints.Length); - - await Parallel.ForEachAsync(nonReplicaEndpoints, new ParallelOptions { MaxDegreeOfParallelism = maxEndpointParallelism }, - async (server, ct) => + foreach (var endpoint in endpoints) { + var server = _options.ConnectionMultiplexer.GetServer(endpoint); + if (server.IsReplica) + continue; + // Try FLUSHDB first (fastest approach) bool flushed = false; try @@ -106,41 +99,30 @@ public async Task RemoveAllAsync(IEnumerable keys = null) long dbSize = await server.DatabaseSizeAsync(_options.Database).AnyContext(); await server.FlushDatabaseAsync(_options.Database).AnyContext(); Interlocked.Add(ref deleted, dbSize); - flushed = true; + continue; } catch (Exception ex) { _logger.LogError(ex, "Unable to flush database on {Endpoint}: {Message}", server.EndPoint, ex.Message); } - // If FLUSHDB failed, fall back to SCAN + DELETE - if (!flushed) + try { - try - { - // NOTE: We need to use a HashSet to avoid duplicate counts due to SCAN is non-deterministic. - // A Performance win could be had if we are sure dbSize didn't fail and we know nothing was changing - // keys while we were deleting. - var seen = new HashSet(); - await foreach (var key in server.KeysAsync(_options.Database).ConfigureAwait(false)) - seen.Add(key); - - // NOTE: StackExchange.Redis multiplexes all operations over a single connection and handles - // pipelining internally, so parallelism limits here only affect client-side Task creation, - // not Redis server load. Consider simplifying to Task.WhenAll in a future refactor. - int maxParallelism = Math.Min(8, Environment.ProcessorCount); - await Parallel.ForEachAsync(seen.Chunk(batchSize), new ParallelOptions { MaxDegreeOfParallelism = maxParallelism }, async (batch, ct) => - { - long count = await Database.KeyDeleteAsync(batch.ToArray()).AnyContext(); - Interlocked.Add(ref deleted, count); - }).AnyContext(); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error deleting all keys on {Endpoint}: {Message}", server.EndPoint, ex.Message); - } + // NOTE: We need to use a HashSet to avoid duplicate counts due to SCAN is non-deterministic. + // A Performance win could be had if we are sure dbSize didn't fail and we know nothing was changing + // keys while we were deleting. + var seen = new HashSet(); + await foreach (var key in server.KeysAsync(_options.Database).ConfigureAwait(false)) + seen.Add(key); + + foreach (var batch in seen.Chunk(batchSize)) + deleted += await Database.KeyDeleteAsync(batch.ToArray()).AnyContext(); } - }).AnyContext(); + catch (Exception ex) + { + _logger.LogError(ex, "Error deleting all keys on {Endpoint}: {Message}", server.EndPoint, ex.Message); + } + } } else if (Database.Multiplexer.IsCluster()) { @@ -949,7 +931,6 @@ private async Task LoadScriptsAsync() if (_scriptsLoaded) return; - // Prepare all the Lua scripts var incrementWithExpire = LuaScript.Prepare(IncrementWithScript); var removeIfEqual = LuaScript.Prepare(RemoveIfEqualScript); var replaceIfEqual = LuaScript.Prepare(ReplaceIfEqualScript); @@ -958,76 +939,21 @@ private async Task LoadScriptsAsync() var getAllExpiration = LuaScript.Prepare(GetAllExpirationScript); var setAllExpiration = LuaScript.Prepare(SetAllExpirationScript); - // Get all non-replica endpoints - var endpoints = _options.ConnectionMultiplexer.GetEndPoints() - .Select(ep => _options.ConnectionMultiplexer.GetServer(ep)) - .Where(server => !server.IsReplica) - .ToArray(); - - if (endpoints.Length == 0) - return; - - // In Redis Cluster, each node maintains its own script cache - // Scripts must be loaded on every node where they might execute - // See: https://redis.io/docs/latest/develop/programmability/eval-intro/#evalsha-and-script-load - // See: https://redis.io/docs/management/scaling/#redis-cluster-architecture - - // Store the loaded scripts from each node separately - // We'll load scripts on all servers in parallel first, then set the class fields - // once using the results from any server (scripts have same SHA everywhere) - - // Create a task to load scripts on all servers in parallel - var loadTasks = new List>(); - - // Start script loading tasks for all endpoints - foreach (var server in endpoints) + foreach (var endpoint in _options.ConnectionMultiplexer.GetEndPoints()) { + var server = _options.ConnectionMultiplexer.GetServer(endpoint); if (server.IsReplica) continue; - // Create and start task for this server - completely independent - var loadTask = Task.Run(async () => - { - // Load all scripts on this server - var incr = await incrementWithExpire.LoadAsync(server).AnyContext(); - var remove = await removeIfEqual.LoadAsync(server).AnyContext(); - var replace = await replaceIfEqual.LoadAsync(server).AnyContext(); - var setHigher = await setIfHigher.LoadAsync(server).AnyContext(); - var setLower = await setIfLower.LoadAsync(server).AnyContext(); - var getAllExp = await getAllExpiration.LoadAsync(server).AnyContext(); - var setAllExp = await setAllExpiration.LoadAsync(server).AnyContext(); - - return (incr, remove, replace, setHigher, setLower, getAllExp, setAllExp); - }); - - loadTasks.Add(loadTask); + _incrementWithExpire = await incrementWithExpire.LoadAsync(server).AnyContext(); + _removeIfEqual = await removeIfEqual.LoadAsync(server).AnyContext(); + _replaceIfEqual = await replaceIfEqual.LoadAsync(server).AnyContext(); + _setIfHigher = await setIfHigher.LoadAsync(server).AnyContext(); + _setIfLower = await setIfLower.LoadAsync(server).AnyContext(); + _getAllExpiration = await getAllExpiration.LoadAsync(server).AnyContext(); + _setAllExpiration = await setAllExpiration.LoadAsync(server).AnyContext(); } - // Wait for any server to complete loading its scripts - // All should produce identical SHA hashes, so we only need one result - var completedTask = await Task.WhenAny(loadTasks).AnyContext(); - var scripts = await completedTask.AnyContext(); - - // Continue loading on other servers in the background (required for Redis Cluster) - // but don't wait for them to finish - the scripts are available immediately - // once loaded on at least one node - - // Assign the results to the instance fields - _incrementWithExpire = scripts.IncrementWithExpire; - _removeIfEqual = scripts.RemoveIfEqual; - _replaceIfEqual = scripts.ReplaceIfEqual; - _setIfHigher = scripts.SetIfHigher; - _setIfLower = scripts.SetIfLower; - _getAllExpiration = scripts.GetAllExpiration; - _setAllExpiration = scripts.SetAllExpiration; - _scriptsLoaded = true; } } From c29daf5bac31da126d6bb857bb4ecb30486fa75a Mon Sep 17 00:00:00 2001 From: Blake Niemyjski Date: Fri, 28 Nov 2025 16:10:02 +1300 Subject: [PATCH 26/26] Reverted some .NET 8 async changes that could change the runtime behavior. --- src/Foundatio.Redis/Cache/RedisCacheClient.cs | 159 ++++++++---------- 1 file changed, 68 insertions(+), 91 deletions(-) diff --git a/src/Foundatio.Redis/Cache/RedisCacheClient.cs b/src/Foundatio.Redis/Cache/RedisCacheClient.cs index 7a6cdde..add1f1d 100644 --- a/src/Foundatio.Redis/Cache/RedisCacheClient.cs +++ b/src/Foundatio.Redis/Cache/RedisCacheClient.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; @@ -92,8 +91,6 @@ public async Task RemoveAllAsync(IEnumerable keys = null) if (server.IsReplica) continue; - // Try FLUSHDB first (fastest approach) - bool flushed = false; try { long dbSize = await server.DatabaseSizeAsync(_options.Database).AnyContext(); @@ -138,21 +135,19 @@ public async Task RemoveAllAsync(IEnumerable keys = null) foreach (var batch in redisKeys.Chunk(batchSize)) { - await Parallel.ForEachAsync( - batch.GroupBy(k => Database.Multiplexer.HashSlot(k)), - async (hashSlotGroup, ct) => + foreach (var hashSlotGroup in batch.GroupBy(k => Database.Multiplexer.HashSlot(k))) + { + var hashSlotKeys = hashSlotGroup.ToArray(); + try + { + long count = await Database.KeyDeleteAsync(hashSlotKeys).AnyContext(); + Interlocked.Add(ref deleted, count); + } + catch (Exception ex) { - var hashSlotKeys = hashSlotGroup.ToArray(); - try - { - long count = await Database.KeyDeleteAsync(hashSlotKeys).AnyContext(); - Interlocked.Add(ref deleted, count); - } - catch (Exception ex) - { - _logger.LogError(ex, "Unable to delete {HashSlot} keys ({Keys}): {Message}", hashSlotGroup.Key, hashSlotKeys, ex.Message); - } - }).AnyContext(); + _logger.LogError(ex, "Unable to delete {HashSlot} keys ({Keys}): {Message}", hashSlotGroup.Key, hashSlotKeys, ex.Message); + } + } } } else @@ -167,13 +162,7 @@ await Parallel.ForEachAsync( if (redisKeys.Count is 0) return 0; - var keyBatches = redisKeys.Chunk(batchSize).ToArray(); - - // NOTE: StackExchange.Redis multiplexes all operations over a single connection and handles - // pipelining internally, so parallelism limits here only affect client-side Task creation, - // not Redis server load. Consider simplifying to Task.WhenAll in a future refactor. - int maxParallelism = Math.Min(8, Environment.ProcessorCount); - await Parallel.ForEachAsync(keyBatches, new ParallelOptions { MaxDegreeOfParallelism = maxParallelism }, async (batch, ct) => + foreach (var batch in redisKeys.Chunk(batchSize)) { try { @@ -184,7 +173,7 @@ await Parallel.ForEachAsync( { _logger.LogError(ex, "Unable to delete keys ({Keys}): {Message}", batch, ex.Message); } - }).AnyContext(); + } } return (int)deleted; @@ -217,13 +206,11 @@ public async Task RemoveByPrefixAsync(string prefix) { if (isCluster) { - await Parallel.ForEachAsync( - keys.GroupBy(k => _options.ConnectionMultiplexer.HashSlot(k)), - async (slotGroup, ct) => - { - long count = await Database.KeyDeleteAsync(slotGroup.ToArray()).AnyContext(); - Interlocked.Add(ref deleted, count); - }).AnyContext(); + foreach (var slotGroup in keys.GroupBy(k => _options.ConnectionMultiplexer.HashSlot(k))) + { + long count = await Database.KeyDeleteAsync(slotGroup.ToArray()).AnyContext(); + Interlocked.Add(ref deleted, count); + } } else { @@ -310,21 +297,18 @@ public async Task>> GetAllAsync(IEnumerable if (_options.ConnectionMultiplexer.IsCluster()) { - // Use the default concurrency on .NET 8 (-1) - var result = new ConcurrentDictionary>(-1, redisKeys.Count); - await Parallel.ForEachAsync( - redisKeys.GroupBy(k => _options.ConnectionMultiplexer.HashSlot(k)), - async (hashSlotGroup, ct) => - { - var hashSlotKeys = hashSlotGroup.ToArray(); - var values = await Database.StringGetAsync(hashSlotKeys, _options.ReadMode).AnyContext(); - - // Redis MGET guarantees that values are returned in the same order as keys. - // Non-existent keys return nil/empty values in their respective positions. - // https://redis.io/commands/mget - for (int i = 0; i < hashSlotKeys.Length; i++) - result[hashSlotKeys[i]] = RedisValueToCacheValue(values[i]); - }).AnyContext(); + var result = new Dictionary>(redisKeys.Count); + foreach (var hashSlotGroup in redisKeys.GroupBy(k => _options.ConnectionMultiplexer.HashSlot(k))) + { + var hashSlotKeys = hashSlotGroup.ToArray(); + var values = await Database.StringGetAsync(hashSlotKeys, _options.ReadMode).AnyContext(); + + // Redis MGET guarantees that values are returned in the same order as keys. + // Non-existent keys return nil/empty values in their respective positions. + // https://redis.io/commands/mget + for (int i = 0; i < hashSlotKeys.Length; i++) + result[hashSlotKeys[i]] = RedisValueToCacheValue(values[i]); + } return result.AsReadOnly(); } @@ -616,13 +600,11 @@ public async Task SetAllAsync(IDictionary values, TimeSpan? e // For cluster/sentinel, group keys by hash slot since batch operations // require all keys to be in the same slot int successCount = 0; - await Parallel.ForEachAsync( - pairs.GroupBy(p => _options.ConnectionMultiplexer.HashSlot(p.Key)), - async (slotGroup, ct) => - { - int count = await SetAllInternalAsync(slotGroup.ToArray(), expiresIn).AnyContext(); - Interlocked.Add(ref successCount, count); - }).AnyContext(); + foreach (var slotGroup in pairs.GroupBy(p => _options.ConnectionMultiplexer.HashSlot(p.Key))) + { + int count = await SetAllInternalAsync(slotGroup.ToArray(), expiresIn).AnyContext(); + Interlocked.Add(ref successCount, count); + } return successCount; } @@ -829,37 +811,34 @@ public Task SetExpirationAsync(string key, TimeSpan expiresIn) if (_options.ConnectionMultiplexer.IsCluster()) { - // Use the default concurrency on .NET 8 (-1) - var result = new ConcurrentDictionary(-1, keyList.Count); - await Parallel.ForEachAsync( - keyList.GroupBy(k => _options.ConnectionMultiplexer.HashSlot(k)), - async (hashSlotGroup, ct) => + var result = new Dictionary(keyList.Count); + foreach (var hashSlotGroup in keyList.GroupBy(k => _options.ConnectionMultiplexer.HashSlot(k))) + { + var hashSlotKeys = hashSlotGroup.ToArray(); + var redisResult = await Database.ScriptEvaluateAsync(_getAllExpiration.Hash, hashSlotKeys).AnyContext(); + if (redisResult.IsNull) + continue; + + // Lua script returns array of TTL values in milliseconds (in same order as keys) + // -2 = key doesn't exist, -1 = no expiration, positive = TTL in ms + long[] ttls = (long[])redisResult; + if (ttls is null || ttls.Length != hashSlotKeys.Length) + throw new InvalidOperationException($"Script returned {ttls?.Length ?? 0} results for {hashSlotKeys.Length} keys"); + + for (int hashSlotIndex = 0; hashSlotIndex < hashSlotKeys.Length; hashSlotIndex++) { - var hashSlotKeys = hashSlotGroup.Select(k => (RedisKey)k).ToArray(); - var redisResult = await Database.ScriptEvaluateAsync(_getAllExpiration.Hash, hashSlotKeys).AnyContext(); - if (redisResult.IsNull) - return; - - // Lua script returns array of TTL values in milliseconds (in same order as keys) - // -2 = key doesn't exist, -1 = no expiration, positive = TTL in ms - long[] ttls = (long[])redisResult; - if (ttls is null || ttls.Length != hashSlotKeys.Length) - throw new InvalidOperationException($"Script returned {ttls?.Length ?? 0} results for {hashSlotKeys.Length} keys"); - - for (int hashSlotIndex = 0; hashSlotIndex < hashSlotKeys.Length; hashSlotIndex++) - { - string key = hashSlotKeys[hashSlotIndex]; - long ttl = ttls[hashSlotIndex]; - if (ttl >= 0) // Only include keys with positive TTL (exclude non-existent and persistent) - result[key] = TimeSpan.FromMilliseconds(ttl); - } - }).AnyContext(); + string key = hashSlotKeys[hashSlotIndex]; + long ttl = ttls[hashSlotIndex]; + if (ttl >= 0) // Only include keys with positive TTL (exclude non-existent and persistent) + result[key] = TimeSpan.FromMilliseconds(ttl); + } + } return result.AsReadOnly(); } else { - var redisKeys = keyList.Select(k => (RedisKey)k).ToArray(); + var redisKeys = keyList.ToArray(); var redisResult = await Database.ScriptEvaluateAsync(_getAllExpiration.Hash, redisKeys).AnyContext(); if (redisResult.IsNull) @@ -897,18 +876,16 @@ public async Task SetAllExpirationAsync(IDictionary expiratio if (_options.ConnectionMultiplexer.IsCluster()) { - await Parallel.ForEachAsync( - expirations.GroupBy(kvp => _options.ConnectionMultiplexer.HashSlot(kvp.Key)), - async (hashSlotGroup, ct) => - { - var hashSlotExpirations = hashSlotGroup.ToList(); - var keys = hashSlotExpirations.Select(kvp => (RedisKey)kvp.Key).ToArray(); - var values = hashSlotExpirations - .Select(kvp => (RedisValue)(kvp.Value.HasValue ? (long)kvp.Value.Value.TotalMilliseconds : -1)) - .ToArray(); - - await Database.ScriptEvaluateAsync(_setAllExpiration.Hash, keys, values).AnyContext(); - }).AnyContext(); + foreach (var hashSlotGroup in expirations.GroupBy(kvp => _options.ConnectionMultiplexer.HashSlot(kvp.Key))) + { + var hashSlotExpirations = hashSlotGroup.ToList(); + var keys = hashSlotExpirations.Select(kvp => (RedisKey)kvp.Key).ToArray(); + var values = hashSlotExpirations + .Select(kvp => (RedisValue)(kvp.Value.HasValue ? (long)kvp.Value.Value.TotalMilliseconds : -1)) + .ToArray(); + + await Database.ScriptEvaluateAsync(_setAllExpiration.Hash, keys, values).AnyContext(); + } } else {