diff --git a/src/WHMapper.Tests/Services/Cache/CacheTest.cs b/src/WHMapper.Tests/Services/Cache/CacheIntegrationTests.cs similarity index 75% rename from src/WHMapper.Tests/Services/Cache/CacheTest.cs rename to src/WHMapper.Tests/Services/Cache/CacheIntegrationTests.cs index b76506a3..ea8f1f68 100644 --- a/src/WHMapper.Tests/Services/Cache/CacheTest.cs +++ b/src/WHMapper.Tests/Services/Cache/CacheIntegrationTests.cs @@ -1,107 +1,106 @@ -using Microsoft.Extensions.Caching.Distributed; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; -using WHMapper.Services.Cache; - -namespace WHMapper.Tests.Services; - -[Collection("C1-Services")] -public class CacheTest -{ - private readonly ICacheService? _services; - private readonly ICacheService? _badServices; - public CacheTest() - { - var services = new ServiceCollection(); - - - var configuration = new ConfigurationBuilder() - .AddJsonFile("appsettings.json") - .AddEnvironmentVariables() - .Build(); - - services.AddStackExchangeRedisCache(option => - { - option.Configuration = configuration.GetConnectionString("RedisConnection"); - option.InstanceName = "WHMapper"; - }); - - - var provider = services.BuildServiceProvider(); - if(provider != null) - { - IDistributedCache? _distriCache = provider.GetService(); - ILogger loggerCache = new NullLogger(); - - if(_distriCache != null && loggerCache != null) - { - _services = new CacheService(loggerCache,_distriCache); - Assert.NotNull(_services); - - _badServices = new CacheService(loggerCache,null!); - Assert.NotNull(_badServices); - } - } - } - - [Fact] - public async Task Set_Get_Remove() - { - var key = "test"; - var value = "test"; - - var key2 = "test2"; - var value2 = "test2"; - - Assert.NotNull(_services); - - bool successSet = await _services.Set(key,value); - Assert.True(successSet); - - var result = await _services.Get(key); - Assert.NotNull(result); - Assert.Equal(value,result); - - bool successSetTimed = await _services.Set(key2,value2,TimeSpan.FromSeconds(5)); - Assert.True(successSetTimed); - - await Task.Delay(TimeSpan.FromSeconds(7)); - - var result2 = await _services.Get(key2); - Assert.Null(result2); - - bool successDel = await _services.Remove(key); - Assert.True(successDel); - } - - [Fact] - public async Task Set_Get_Remove_With_Bad_Config() - { - - - var key = "test"; - var value = "test"; - - var key2 = "test2"; - var value2 = "test2"; - - Assert.NotNull(_badServices); - bool badSet = await _badServices.Set(key,value); - Assert.False(badSet); - - var result = await _badServices.Get(key); - Assert.Null(result); - - bool badSetTimed = await _badServices.Set(key2,value2,TimeSpan.FromSeconds(5)); - Assert.False(badSetTimed); - - var result2 = await _badServices.Get(key2); - Assert.Null(result2); - - bool badDel = await _badServices.Remove(key); - Assert.False(badDel); - } - +using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using WHMapper.Services.Cache; + +namespace WHMapper.Tests.Services.Cache; + +[Collection("C1-Services")] +public class CacheIntegrationTests +{ + private readonly ICacheService? _services; + private readonly ICacheService? _badServices; + + public CacheIntegrationTests() + { + var services = new ServiceCollection(); + + var configuration = new ConfigurationBuilder() + .AddJsonFile("appsettings.json") + .AddEnvironmentVariables() + .Build(); + + services.AddStackExchangeRedisCache(option => + { + option.Configuration = configuration.GetConnectionString("RedisConnection"); + option.InstanceName = "WHMapper"; + }); + + + var provider = services.BuildServiceProvider(); + if (provider != null) + { + IDistributedCache? _distriCache = provider.GetService(); + ILogger loggerCache = new NullLogger(); + + if (_distriCache != null && loggerCache != null) + { + _services = new CacheService(loggerCache, _distriCache); + Assert.NotNull(_services); + + _badServices = new CacheService(loggerCache, null!); + Assert.NotNull(_badServices); + } + } + } + + [Fact] + public async Task Set_Get_Remove() + { + var key = "test"; + var value = "test"; + + var key2 = "test2"; + var value2 = "test2"; + + Assert.NotNull(_services); + + bool successSet = await _services.Set(key, value); + Assert.True(successSet); + + var result = await _services.Get(key); + Assert.NotNull(result); + Assert.Equal(value, result); + + bool successSetTimed = await _services.Set(key2, value2, TimeSpan.FromSeconds(5)); + Assert.True(successSetTimed); + + await Task.Delay(TimeSpan.FromSeconds(7)); + + var result2 = await _services.Get(key2); + Assert.Null(result2); + + bool successDel = await _services.Remove(key); + Assert.True(successDel); + } + + [Fact] + public async Task Set_Get_Remove_With_Bad_Config() + { + + + var key = "test"; + var value = "test"; + + var key2 = "test2"; + var value2 = "test2"; + + Assert.NotNull(_badServices); + bool badSet = await _badServices.Set(key, value); + Assert.False(badSet); + + var result = await _badServices.Get(key); + Assert.Null(result); + + bool badSetTimed = await _badServices.Set(key2, value2, TimeSpan.FromSeconds(5)); + Assert.False(badSetTimed); + + var result2 = await _badServices.Get(key2); + Assert.Null(result2); + + bool badDel = await _badServices.Remove(key); + Assert.False(badDel); + } } \ No newline at end of file diff --git a/src/WHMapper.Tests/Services/Cache/CacheServiceObjectTests.cs b/src/WHMapper.Tests/Services/Cache/CacheServiceObjectTests.cs new file mode 100644 index 00000000..ca512f88 --- /dev/null +++ b/src/WHMapper.Tests/Services/Cache/CacheServiceObjectTests.cs @@ -0,0 +1,143 @@ +using AutoFixture.Xunit2; +using Microsoft.Extensions.Caching.Distributed; +using Moq; +using System.Text; +using System.Text.Json; +using WHMapper.Services.Cache; + +namespace WHMapper.Tests.Services.Cache +{ + public class CacheServiceObjectTests + { + [Theory, AutoDomainData] + public async Task WhenCacheContainsSerializedByteArray_GettingKey_ReturnsValue( + string key, + FakeCacheObject value, + [Frozen] Mock cacheMock, + CacheService sut) + { + var serializedCachedValue = JsonSerializer.Serialize(value); + byte[] cachedValueBytes = Encoding.ASCII.GetBytes(serializedCachedValue); + cacheMock.Setup(cache => cache.GetAsync(key, It.IsAny())) + .ReturnsAsync(cachedValueBytes); + + var result = await sut.Get(key); + + Assert.Equal(value, result); + } + + [Theory, AutoDomainData] + public async Task WhenCacheContainsDeformedString_GettingKey_ReturnsNull( + string key, + string value, + [Frozen] Mock cacheMock, + CacheService sut) + { + byte[] valueBytes = Encoding.ASCII.GetBytes(value); + cacheMock.Setup(cache => cache.GetAsync(key, It.IsAny())) + .ReturnsAsync(valueBytes); + + var result = await sut.Get(key); + + Assert.Null(result); + } + + [Theory, AutoDomainData] + public async Task WhenCacheDoesntContainKey_GettingKey_ReturnsNull( + string key, + [Frozen] Mock cacheMock, + CacheService sut) + { + cacheMock.Setup(cache => cache.GetAsync(key, It.IsAny())) + .ReturnsAsync((byte[]?)null); + + var result = await sut.Get(key); + + Assert.Null(result); + } + + [Theory, AutoMoqData] + public async Task WhenSettingValue_WithExpiry_SetsValue( + string key, + FakeCacheObject value, + [Frozen] Mock cacheMock, + CacheService sut) + { + var serializedValue = JsonSerializer.Serialize(value); + byte[] serializedValueBytes = Encoding.ASCII.GetBytes(serializedValue); + + var result = await sut.Set(key, value, new TimeSpan(0,5,0)); + + Assert.True(result); + cacheMock.Verify(cache => cache.SetAsync(key, serializedValueBytes, It.Is(x => x.AbsoluteExpirationRelativeToNow != null), It.IsAny()), Times.Once); + } + + [Theory, AutoMoqData] + public async Task WhenSettingValue_WithoutExpiry_SetsValue( + string key, + FakeCacheObject value, + [Frozen] Mock cacheMock, + CacheService sut) + { + var serializedValue = JsonSerializer.Serialize(value); + byte[] serializedValueBytes = Encoding.ASCII.GetBytes(serializedValue); + + var result = await sut.Set(key, value); + + Assert.True(result); + cacheMock.Verify(cache => cache.SetAsync(key, serializedValueBytes, It.Is(x => x.AbsoluteExpirationRelativeToNow == null), It.IsAny()), Times.Once); + } + + [Theory, AutoMoqData] + public async Task WhenSettingValue_WhenKeyIsNull_ReturnsFalse( + FakeCacheObject value, + CacheService sut) + { + var result = await sut.Set(null!, value); + Assert.False(result); + } + + [Theory, AutoMoqData] + public async Task WhenSettingValue_ThatExists_OverWritesValue( + string key, + FakeCacheObject value, + [Frozen] Mock cacheMock, + CacheService sut) + { + var serializedValue = JsonSerializer.Serialize(value); + byte[] serializedValueBytes = Encoding.ASCII.GetBytes(serializedValue); + + //Ensuring that the IDistributedCache always returns something, this is to + //ensure that this behaviour doesn't change in the future. + cacheMock.Setup(cache => cache.GetAsync(key, It.IsAny())) + .ReturnsAsync(serializedValueBytes); + cacheMock.Setup(cache => cache.Get(key)) + .Returns(serializedValueBytes); + + var result = await sut.Set(key, value); + + Assert.True(result); + cacheMock.Verify(cache => cache.SetAsync(key, serializedValueBytes, It.Is(x => x.AbsoluteExpirationRelativeToNow == null), It.IsAny()), Times.Once); + } + + [Theory, AutoMoqData] + public async Task WhenRemovingValue_RemovesValue( + string key, + [Frozen] Mock cacheMock, + CacheService sut) + { + var result = await sut.Remove(key); + + Assert.True(result); + cacheMock.Verify(cache => cache.RemoveAsync(key, default), Times.Once); + } + + [Theory, AutoMoqData] + public async Task WhenRemovingValue_KeyIsNull_ReturnsFalse( + CacheService sut) + { + var result = await sut.Remove(null!); + Assert.False(result); + } + } +} \ No newline at end of file diff --git a/src/WHMapper.Tests/Services/Cache/CacheServiceStringTests.cs b/src/WHMapper.Tests/Services/Cache/CacheServiceStringTests.cs new file mode 100644 index 00000000..fd5d94bb --- /dev/null +++ b/src/WHMapper.Tests/Services/Cache/CacheServiceStringTests.cs @@ -0,0 +1,143 @@ +using AutoFixture.Xunit2; +using Microsoft.Extensions.Caching.Distributed; +using Moq; +using System.Text; +using System.Text.Json; +using WHMapper.Services.Cache; + +namespace WHMapper.Tests.Services.Cache +{ + public class CacheServiceStringTests + { + [Theory, AutoDomainData] + public async Task WhenCacheContainsSerializedByteArray_GettingKey_ReturnsValue( + string key, + string cachedValue, + [Frozen] Mock cacheMock, + CacheService sut) + { + var serializedCachedValue = JsonSerializer.Serialize(cachedValue); + byte[] cachedValueBytes = Encoding.ASCII.GetBytes(serializedCachedValue); + cacheMock.Setup(cache => cache.GetAsync(key, It.IsAny())) + .ReturnsAsync(cachedValueBytes); + + var result = await sut.Get(key); + + Assert.Equal(cachedValue, result); + } + + [Theory, AutoDomainData] + public async Task WhenCacheContainsDeformedString_GettingKey_ReturnsNull( + string key, + string value, + [Frozen] Mock cacheMock, + CacheService sut) + { + byte[] valueBytes = Encoding.ASCII.GetBytes(value); + cacheMock.Setup(cache => cache.GetAsync(key, It.IsAny())) + .ReturnsAsync(valueBytes); + + var result = await sut.Get(key); + + Assert.Null(result); + } + + [Theory, AutoDomainData] + public async Task WhenCacheDoesntContainKey_ServiceReturnsNull( + string key, + [Frozen] Mock cacheMock, + CacheService sut) + { + cacheMock.Setup(cache => cache.GetAsync(key, It.IsAny())) + .ReturnsAsync((byte[]?)null); + + var result = await sut.Get(key); + + Assert.Null(result); + } + + [Theory, AutoMoqData] + public async Task WhenSettingValue_WithExpiry_SetsValue( + string key, + string value, + [Frozen] Mock cacheMock, + CacheService sut) + { + var serializedValue = JsonSerializer.Serialize(value); + byte[] serializedValueBytes = Encoding.ASCII.GetBytes(serializedValue); + + var result = await sut.Set(key, value, new TimeSpan(0,5,0)); + + Assert.True(result); + cacheMock.Verify(cache => cache.SetAsync(key, serializedValueBytes, It.Is(x => x.AbsoluteExpirationRelativeToNow != null), It.IsAny()), Times.Once); + } + + [Theory, AutoMoqData] + public async Task WhenSettingValue_WithoutExpiry_SetsValue( + string key, + string value, + [Frozen] Mock cacheMock, + CacheService sut) + { + var serializedValue = JsonSerializer.Serialize(value); + byte[] serializedValueBytes = Encoding.ASCII.GetBytes(serializedValue); + + var result = await sut.Set(key, value); + + Assert.True(result); + cacheMock.Verify(cache => cache.SetAsync(key, serializedValueBytes, It.Is(x => x.AbsoluteExpirationRelativeToNow == null), It.IsAny()), Times.Once); + } + + [Theory, AutoMoqData] + public async Task WhenSettingValue_WhenKeyIsNull_ReturnsFalse( + string value, + CacheService sut) + { + var result = await sut.Set(null!, value); + Assert.False(result); + } + + [Theory, AutoMoqData] + public async Task WhenSettingValue_ThatExists_OverWritesValue( + string key, + string value, + [Frozen] Mock cacheMock, + CacheService sut) + { + var serializedValue = JsonSerializer.Serialize(value); + byte[] serializedValueBytes = Encoding.ASCII.GetBytes(serializedValue); + + //Ensuring that the IDistributedCache always returns something, this is to + //ensure that this behaviour doesn't change in the future. + cacheMock.Setup(cache => cache.GetAsync(key, It.IsAny())) + .ReturnsAsync(serializedValueBytes); + cacheMock.Setup(cache => cache.Get(key)) + .Returns(serializedValueBytes); + + var result = await sut.Set(key, value); + + Assert.True(result); + cacheMock.Verify(cache => cache.SetAsync(key, serializedValueBytes, It.Is(x => x.AbsoluteExpirationRelativeToNow == null), It.IsAny()), Times.Once); + } + + [Theory, AutoMoqData] + public async Task WhenRemovingValue_RemovesValue( + string key, + [Frozen] Mock cacheMock, + CacheService sut) + { + var result = await sut.Remove(key); + + Assert.True(result); + cacheMock.Verify(cache => cache.RemoveAsync(key, default), Times.Once); + } + + [Theory, AutoMoqData] + public async Task WhenRemovingValue_KeyIsNull_ReturnsFalse( + CacheService sut) + { + var result = await sut.Remove(null!); + Assert.False(result); + } + } +} \ No newline at end of file diff --git a/src/WHMapper.Tests/Services/Cache/FakeCacheObject.cs b/src/WHMapper.Tests/Services/Cache/FakeCacheObject.cs new file mode 100644 index 00000000..85ef5aee --- /dev/null +++ b/src/WHMapper.Tests/Services/Cache/FakeCacheObject.cs @@ -0,0 +1,22 @@ +namespace WHMapper.Tests.Services.Cache +{ + public class FakeCacheObject + { + public int Id { get; set; } + public string? Name { get; set; } + + public override bool Equals(object? obj) + { + if (obj is FakeCacheObject other) + { + return Id == other.Id && Name == other.Name; + } + return false; + } + + public override int GetHashCode() + { + throw new NotImplementedException(); + } + } +} diff --git a/src/WHMapper/Services/Cache/CacheService.cs b/src/WHMapper/Services/Cache/CacheService.cs index a2b3c049..541e2c03 100644 --- a/src/WHMapper/Services/Cache/CacheService.cs +++ b/src/WHMapper/Services/Cache/CacheService.cs @@ -57,6 +57,11 @@ public async Task Set(string key, T value, TimeSpan? absoluteExpiration public async Task Remove(string key) { + if (string.IsNullOrEmpty(key)) + { + _logger.LogError("Error removing cache key: key is null or empty"); + return false; + } try { _logger.LogInformation($"Removing cache key {key}"); @@ -69,6 +74,4 @@ public async Task Remove(string key) return false; } } - - }