using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Xunit; using Moq; using Microsoft.Extensions.Caching.Memory; using Haoliang.Core.Services; using Haoliang.Models.Models.Device; using Haoliang.Models.Models.Production; namespace Haoliang.Tests.Services { public class CacheServiceTests { private readonly Mock _mockMemoryCache; private readonly Mock _mockProductionRepository; private readonly Mock _mockDeviceRepository; private readonly Mock _mockCollectionRepository; private readonly CacheService _cacheService; public CacheServiceTests() { _mockMemoryCache = new Mock(); _mockProductionRepository = new Mock(); _mockDeviceRepository = new Mock(); _mockCollectionRepository = new Mock(); _cacheService = new CacheService(_mockMemoryCache.Object); } [Fact] public async Task GetOrSetAsync_ValueNotInCache_ExecutesFactoryAndCachesResult() { // Arrange var key = "test-key"; var factoryResult = "test-value"; var factoryMock = new Mock>>(); factoryMock.Setup(f => f()).ReturnsAsync(factoryResult); // Setup cache miss _mockMemoryCache.Setup(cache => cache.TryGetValue(key, out It.Ref.IsAny)) .Returns(false); // Setup cache set _mockMemoryCache.Setup(cache => cache.Set( key, factoryResult, It.IsAny())) .Callback((k, v, o) => { }); // Act var result = await _cacheService.GetOrSetAsync(key, factoryMock.Object); // Assert Assert.Equal(factoryResult, result); factoryMock.Verify(f => f(), Times.Once); _mockMemoryCache.Verify(cache => cache.TryGetValue(key, out It.Ref.IsAny), Times.Once); _mockMemoryCache.Verify(cache => cache.Set( key, factoryResult, It.IsAny()), Times.Once); } [Fact] public async Task GetOrSetAsync_ValueInCache_ReturnsCachedValue() { // Arrange var key = "test-key"; var cachedValue = "cached-value"; var factoryMock = new Mock>>(); factoryMock.Setup(f => f()).ReturnsAsync("new-value"); // Setup cache hit _mockMemoryCache.Setup(cache => cache.TryGetValue(key, out It.Ref.IsAny)) .Callback((k, out string v) => v = cachedValue) .Returns(true); // Act var result = await _cacheService.GetOrSetAsync(key, factoryMock.Object); // Assert Assert.Equal(cachedValue, result); factoryMock.Verify(f => f(), Times.Never); _mockMemoryCache.Verify(cache => cache.TryGetValue(key, out It.Ref.IsAny), Times.Once); _mockMemoryCache.Verify(cache => cache.Set( key, It.IsAny(), It.IsAny()), Times.Never); } [Fact] public void Get_ValueExists_ReturnsCachedValue() { // Arrange var key = "test-key"; var cachedValue = "cached-value"; // Setup cache hit _mockMemoryCache.Setup(cache => cache.TryGetValue(key, out It.Ref.IsAny)) .Callback((k, out string v) => v = cachedValue) .Returns(true); // Act var result = _cacheService.Get(key); // Assert Assert.Equal(cachedValue, result); _mockMemoryCache.Verify(cache => cache.TryGetValue(key, out It.Ref.IsAny), Times.Once); } [Fact] public void Get_ValueNotExists_ReturnsDefault() { // Arrange var key = "test-key"; // Setup cache miss _mockMemoryCache.Setup(cache => cache.TryGetValue(key, out It.Ref.IsAny)) .Returns(false); // Act var result = _cacheService.Get(key); // Assert Assert.Null(result); _mockMemoryCache.Verify(cache => cache.TryGetValue(key, out It.Ref.IsAny), Times.Once); } [Fact] public void Set_ValidValue_CachesValue() { // Arrange var key = "test-key"; var value = "test-value"; var mockEntryOptions = new Mock(); _mockMemoryCache.Setup(cache => cache.Set(key, value, mockEntryOptions.Object)) .Verifiable(); // Act _cacheService.Set(key, value, mockEntryOptions.Object); // Assert _mockMemoryCache.Verify(cache => cache.Set(key, value, mockEntryOptions.Object), Times.Once); } [Fact] public void Set_NullValue_DoesNotCache() { // Arrange var key = "test-key"; // Act _cacheService.Set(key, null); // Assert _mockMemoryCache.Verify(cache => cache.Set( It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); } [Fact] public void Remove_KeyExists_RemovesFromCache() { // Arrange var key = "test-key"; // Setup cache hit for removal check _mockMemoryCache.Setup(cache => cache.TryGetValue(key, out It.Ref.IsAny)) .Callback((k, out object v) => v = "some-value") .Returns(true); _mockMemoryCache.Setup(cache => cache.Remove(key)) .Verifiable(); // Act var result = _cacheService.Remove(key); // Assert Assert.True(result); _mockMemoryCache.Verify(cache => cache.Remove(key), Times.Once); } [Fact] public void Remove_KeyNotExists_ReturnsFalse() { // Arrange var key = "test-key"; // Setup cache miss _mockMemoryCache.Setup(cache => cache.TryGetValue(key, out It.Ref.IsAny)) .Returns(false); // Act var result = _cacheService.Remove(key); // Assert Assert.False(result); _mockMemoryCache.Verify(cache => cache.Remove(key), Times.Never); } [Fact] public void Exists_KeyExists_ReturnsTrue() { // Arrange var key = "test-key"; // Setup cache hit _mockMemoryCache.Setup(cache => cache.TryGetValue(key, out It.Ref.IsAny)) .Returns(true); // Act var result = _cacheService.Exists(key); // Assert Assert.True(result); _mockMemoryCache.Verify(cache => cache.TryGetValue(key, out It.Ref.IsAny), Times.Once); } [Fact] public void Exists_KeyNotExists_ReturnsFalse() { // Arrange var key = "test-key"; // Setup cache miss _mockMemoryCache.Setup(cache => cache.TryGetValue(key, out It.Ref.IsAny)) .Returns(false); // Act var result = _cacheService.Exists(key); // Assert Assert.False(result); _mockMemoryCache.Verify(cache => cache.TryGetValue(key, out It.Ref.IsAny), Times.Once); } [Fact] public void Clear_ClearsAllCache() { // Act _cacheService.Clear(); // Assert _mockMemoryCache.Verify(cache => cache.Compact(1.0), Times.Once); } [Fact] public void GetStatistics_ReturnsCacheStatistics() { // Arrange _mockMemoryCache.Setup(cache => cache.Count) .Returns(5); // Act var result = _cacheService.GetStatistics(); // Assert Assert.NotNull(result); Assert.True(result.TotalItems >= 0); Assert.True(result.HitRate >= 0 && result.HitRate <= 1); Assert.True(result.MemoryUsageBytes >= 0); Assert.NotNull(result.ItemsByType); } [Fact] public void GetKeys_MatchingPattern_ReturnsKeys() { // Arrange var pattern = "device:*"; var expectedKeys = new List { "device:1", "device:2", "device:3" }; // Mock cache keys var mockKeys = new List { "device:1", "template:1", "device:2", "config:1", "device:3" }; _mockMemoryCache.Setup(cache => cache.Keys) .Returns(mockKeys.Cast().ToList()); // Act var result = _cacheService.GetKeys(pattern).ToList(); // Assert Assert.Equal(3, result.Count); Assert.Contains("device:1", result); Assert.Contains("device:2", result); Assert.Contains("device:3", result); } [Fact] public void GetKeys_NoMatches_ReturnsEmptyList() { // Arrange var pattern = "nonexistent:*"; // Mock cache keys var mockKeys = new List { "device:1", "template:1", "config:1" }; _mockMemoryCache.Setup(cache => cache.Keys) .Returns(mockKeys.Cast().ToList()); // Act var result = _cacheService.GetKeys(pattern).ToList(); // Assert Assert.Empty(result); } [Fact] public void Refresh_ExistingKey_RefreshesCache() { // Arrange var key = "test-key"; var value = "test-value"; var mockOptions = new MemoryCacheEntryOptions(); // Setup cache hit _mockMemoryCache.Setup(cache => cache.TryGetValue(key, out It.Ref.IsAny)) .Callback((k, out object v) => v = value) .Returns(true); _mockMemoryCache.Setup(cache => cache.Remove(key)) .Verifiable(); _mockMemoryCache.Setup(cache => cache.Set(key, value, mockOptions)) .Verifiable(); // Act var result = _cacheService.Refresh(key); // Assert Assert.True(result); _mockMemoryCache.Verify(cache => cache.Remove(key), Times.Once); _mockMemoryCache.Verify(cache => cache.Set(key, value, mockOptions), Times.Once); } [Fact] public void Refresh_NonExistingKey_ReturnsFalse() { // Arrange var key = "nonexistent-key"; // Setup cache miss _mockMemoryCache.Setup(cache => cache.TryGetValue(key, out It.Ref.IsAny)) .Returns(false); // Act var result = _cacheService.Refresh(key); // Assert Assert.False(result); _mockMemoryCache.Verify(cache => cache.Remove(key), Times.Never); _mockMemoryCache.Verify(cache => cache.Set( It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); } [Fact] public async Task GetOrSetDeviceAsync_ValidDevice_ReturnsDevice() { // Arrange var deviceId = 1; var device = new CNCDevice { Id = deviceId, Name = "Test Device" }; var factoryMock = new Mock>>(); factoryMock.Setup(f => f()).ReturnsAsync(device); // Setup cache miss _mockMemoryCache.Setup(cache => cache.TryGetValue(It.IsAny(), out It.Ref.IsAny)) .Returns(false); // Setup cache set _mockMemoryCache.Setup(cache => cache.Set( It.IsAny(), device, It.IsAny())) .Callback((k, v, o) => { }); // Act var result = await _cacheService.GetOrSetDeviceAsync(deviceId, factoryMock.Object); // Assert Assert.Equal(device, result); factoryMock.Verify(f => f(), Times.Once); } [Fact] public void InvalidateDeviceCache_ValidDevice_RemovesRelatedKeys() { // Arrange var deviceId = 1; // Setup cache hits for removal _mockMemoryCache.Setup(cache => cache.TryGetValue(It.IsAny(), out It.Ref.IsAny)) .Callback((k, out object v) => v = "some-value") .Returns(true); _mockMemoryCache.Setup(cache => cache.Remove(It.IsAny())) .Verifiable(); // Act _cacheService.InvalidateDeviceCache(deviceId, "additional:key"); // Assert _mockMemoryCache.Verify(cache => cache.Remove("device:1"), Times.Once); _mockMemoryCache.Verify(cache => cache.Remove("device:status:1"), Times.Once); _mockMemoryCache.Verify(cache => cache.Remove(It.IsAny()), Times.AtLeast(3)); } [Fact] public async Task GetOrSetSystemConfigurationAsync_ValidFactory_ReturnsConfiguration() { // Arrange var config = new SystemConfiguration { DailyProductionTarget = 100 }; var factoryMock = new Mock>>(); factoryMock.Setup(f => f()).ReturnsAsync(config); // Setup cache miss _mockMemoryCache.Setup(cache => cache.TryGetValue(It.IsAny(), out It.Ref.IsAny)) .Returns(false); // Setup cache set _mockMemoryCache.Setup(cache => cache.Set( It.IsAny(), config, It.IsAny())) .Callback((k, v, o) => { }); // Act var result = await _cacheService.GetOrSetSystemConfigurationAsync(factoryMock.Object); // Assert Assert.Equal(config, result); factoryMock.Verify(f => f(), Times.Once); } [Fact] public void InvalidateSystemConfigCache_RemovesSystemConfigKeys() { // Arrange // Setup cache hits for removal _mockMemoryCache.Setup(cache => cache.TryGetValue(It.IsAny(), out It.Ref.IsAny)) .Callback((k, out object v) => v = "some-value") .Returns(true); _mockMemoryCache.Setup(cache => cache.Remove(It.IsAny())) .Verifiable(); // Act _cacheService.InvalidateSystemConfigCache(); // Assert _mockMemoryCache.Verify(cache => cache.Remove("config:system"), Times.Once); _mockMemoryCache.Verify(cache => cache.Remove("config:alerts"), Times.Once); } } }