You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

460 lines
16 KiB
C#

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<IMemoryCache> _mockMemoryCache;
private readonly Mock<IProductionRepository> _mockProductionRepository;
private readonly Mock<IDeviceRepository> _mockDeviceRepository;
private readonly Mock<ICollectionRepository> _mockCollectionRepository;
private readonly CacheService _cacheService;
public CacheServiceTests()
{
_mockMemoryCache = new Mock<IMemoryCache>();
_mockProductionRepository = new Mock<IProductionRepository>();
_mockDeviceRepository = new Mock<IDeviceRepository>();
_mockCollectionRepository = new Mock<ICollectionRepository>();
_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<Func<Task<string>>>();
factoryMock.Setup(f => f()).ReturnsAsync(factoryResult);
// Setup cache miss
_mockMemoryCache.Setup(cache => cache.TryGetValue(key, out It.Ref<string>.IsAny))
.Returns(false);
// Setup cache set
_mockMemoryCache.Setup(cache => cache.Set(
key,
factoryResult,
It.IsAny<MemoryCacheEntryOptions>()))
.Callback<string, object, MemoryCacheEntryOptions>((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<string>.IsAny), Times.Once);
_mockMemoryCache.Verify(cache => cache.Set(
key,
factoryResult,
It.IsAny<MemoryCacheEntryOptions>()), Times.Once);
}
[Fact]
public async Task GetOrSetAsync_ValueInCache_ReturnsCachedValue()
{
// Arrange
var key = "test-key";
var cachedValue = "cached-value";
var factoryMock = new Mock<Func<Task<string>>>();
factoryMock.Setup(f => f()).ReturnsAsync("new-value");
// Setup cache hit
_mockMemoryCache.Setup(cache => cache.TryGetValue(key, out It.Ref<string>.IsAny))
.Callback<string, out string>((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<string>.IsAny), Times.Once);
_mockMemoryCache.Verify(cache => cache.Set(
key,
It.IsAny<string>(),
It.IsAny<MemoryCacheEntryOptions>()), 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<string>.IsAny))
.Callback<string, out string>((k, out string v) => v = cachedValue)
.Returns(true);
// Act
var result = _cacheService.Get<string>(key);
// Assert
Assert.Equal(cachedValue, result);
_mockMemoryCache.Verify(cache => cache.TryGetValue(key, out It.Ref<string>.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<string>.IsAny))
.Returns(false);
// Act
var result = _cacheService.Get<string>(key);
// Assert
Assert.Null(result);
_mockMemoryCache.Verify(cache => cache.TryGetValue(key, out It.Ref<string>.IsAny), Times.Once);
}
[Fact]
public void Set_ValidValue_CachesValue()
{
// Arrange
var key = "test-key";
var value = "test-value";
var mockEntryOptions = new Mock<MemoryCacheEntryOptions>();
_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<string>(key, null);
// Assert
_mockMemoryCache.Verify(cache => cache.Set(
It.IsAny<string>(),
It.IsAny<string>(),
It.IsAny<MemoryCacheEntryOptions>()), 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<object>.IsAny))
.Callback<string, out object>((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<object>.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<object>.IsAny))
.Returns(true);
// Act
var result = _cacheService.Exists(key);
// Assert
Assert.True(result);
_mockMemoryCache.Verify(cache => cache.TryGetValue(key, out It.Ref<object>.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<object>.IsAny))
.Returns(false);
// Act
var result = _cacheService.Exists(key);
// Assert
Assert.False(result);
_mockMemoryCache.Verify(cache => cache.TryGetValue(key, out It.Ref<object>.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<string> { "device:1", "device:2", "device:3" };
// Mock cache keys
var mockKeys = new List<string> { "device:1", "template:1", "device:2", "config:1", "device:3" };
_mockMemoryCache.Setup(cache => cache.Keys)
.Returns(mockKeys.Cast<object>().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<string> { "device:1", "template:1", "config:1" };
_mockMemoryCache.Setup(cache => cache.Keys)
.Returns(mockKeys.Cast<object>().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<object>.IsAny))
.Callback<string, out object>((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<object>(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<object>.IsAny))
.Returns(false);
// Act
var result = _cacheService.Refresh<object>(key);
// Assert
Assert.False(result);
_mockMemoryCache.Verify(cache => cache.Remove(key), Times.Never);
_mockMemoryCache.Verify(cache => cache.Set(
It.IsAny<string>(),
It.IsAny<object>(),
It.IsAny<MemoryCacheEntryOptions>()), 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<Func<Task<CNCDevice>>>();
factoryMock.Setup(f => f()).ReturnsAsync(device);
// Setup cache miss
_mockMemoryCache.Setup(cache => cache.TryGetValue(It.IsAny<string>(), out It.Ref<CNCDevice>.IsAny))
.Returns(false);
// Setup cache set
_mockMemoryCache.Setup(cache => cache.Set(
It.IsAny<string>(),
device,
It.IsAny<MemoryCacheEntryOptions>()))
.Callback<string, object, MemoryCacheEntryOptions>((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<string>(), out It.Ref<object>.IsAny))
.Callback<string, out object>((k, out object v) => v = "some-value")
.Returns(true);
_mockMemoryCache.Setup(cache => cache.Remove(It.IsAny<string>()))
.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<string>()), Times.AtLeast(3));
}
[Fact]
public async Task GetOrSetSystemConfigurationAsync_ValidFactory_ReturnsConfiguration()
{
// Arrange
var config = new SystemConfiguration { DailyProductionTarget = 100 };
var factoryMock = new Mock<Func<Task<SystemConfiguration>>>();
factoryMock.Setup(f => f()).ReturnsAsync(config);
// Setup cache miss
_mockMemoryCache.Setup(cache => cache.TryGetValue(It.IsAny<string>(), out It.Ref<SystemConfiguration>.IsAny))
.Returns(false);
// Setup cache set
_mockMemoryCache.Setup(cache => cache.Set(
It.IsAny<string>(),
config,
It.IsAny<MemoryCacheEntryOptions>()))
.Callback<string, object, MemoryCacheEntryOptions>((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<string>(), out It.Ref<object>.IsAny))
.Callback<string, out object>((k, out object v) => v = "some-value")
.Returns(true);
_mockMemoryCache.Setup(cache => cache.Remove(It.IsAny<string>()))
.Verifiable();
// Act
_cacheService.InvalidateSystemConfigCache();
// Assert
_mockMemoryCache.Verify(cache => cache.Remove("config:system"), Times.Once);
_mockMemoryCache.Verify(cache => cache.Remove("config:alerts"), Times.Once);
}
}
}