namespace adas_core.Test.Services; using adas_core.Application.Services.Caching; using Microsoft.Extensions.Logging; using Moq; [TestFixture] public class CacheServiceTest { private CacheService _svc = null!; [SetUp] public void SetUp() { var lockMgr = new LockManagerService( new Mock>().Object, new InMemoryLockProvider()); _svc = new CacheService(lockMgr); } #region TC-31 [Test] public async Task GetOrSetObjectAsync_ReturnsCachedValue_AndDoesNotInvokeFactory_WhenKeyAlreadyExists() { const string key = "patients:latestObs:abc"; const string expected = "cached-value"; await _svc.SetObjectAsync(key, expected); var factoryCallCount = 0; var result = await _svc.GetOrSetObjectAsync(key, () => { factoryCallCount++; return Task.FromResult("factory-value"); }); Assert.That(factoryCallCount, Is.EqualTo(0));// por qué como la clave esta, no se ejecutó el factory Assert.That(result, Is.EqualTo(expected)); } #endregion #region TC-32 [Test] public async Task GetOrSetObjectAsync_InvokesFactory_StoresResult_AndDoesNotInvokeAgainOnSecondCall() { const string key = "patients:latestObs:new"; const string factoryValue = "factory-result"; var factoryCallCount = 0; var firstResult = await _svc.GetOrSetObjectAsync(key, () => { factoryCallCount++; return Task.FromResult(factoryValue); }); Assert.That(factoryCallCount, Is.EqualTo(1)); Assert.That(firstResult, Is.EqualTo(factoryValue)); var cached = await _svc.GetObjectAsync(key); Assert.That(cached, Is.EqualTo(factoryValue)); var secondResult = await _svc.GetOrSetObjectAsync(key, () => { factoryCallCount++; return Task.FromResult("second-call-value"); }); Assert.That(factoryCallCount, Is.EqualTo(1)); Assert.That(secondResult, Is.EqualTo(factoryValue)); } #endregion #region TC-33 [Test] public async Task GetOrSetObjectAsync_DoesNotCacheNullResult_AndInvokesFactoryOnSubsequentCalls() { const string key = "patients:latestObs:null"; var factoryCallCount = 0; await _svc.GetOrSetObjectAsync(key, () => { factoryCallCount++; return Task.FromResult(null); }); Assert.That(factoryCallCount, Is.EqualTo(1)); await _svc.GetOrSetObjectAsync(key, () => { factoryCallCount++; return Task.FromResult(null); }); Assert.That(factoryCallCount, Is.EqualTo(2)); } #endregion #region TC-34 [Test] public async Task GetOrSetObjectAsync_SecondCheckInLock_PreventsFactoryExecution_WhenValueAlreadyInsertedByFirstThread() { const string key = "patients:concurrent:abc"; var factoryCallCount = 0; var task1InFactory = new SemaphoreSlim(0, 1); var task1CanFinish = new SemaphoreSlim(0, 1); async Task ControlledFactory() { Interlocked.Increment(ref factoryCallCount); task1InFactory.Release(); await task1CanFinish.WaitAsync(); return "first-result"; } var t1 = _svc.GetOrSetObjectAsync(key, ControlledFactory); await task1InFactory.WaitAsync(); var t2 = _svc.GetOrSetObjectAsync(key, () => { Interlocked.Increment(ref factoryCallCount); return Task.FromResult("second-result"); }); await Task.Delay(20); task1CanFinish.Release(); var r1 = await t1; var r2 = await t2; Assert.That(factoryCallCount, Is.EqualTo(1)); Assert.That(r1, Is.EqualTo("first-result")); Assert.That(r2, Is.EqualTo("first-result")); } #endregion #region TC-35 [Test] public async Task DeleteObjectAsync_RemovesKey_AndFactoryIsInvokedOnNextCall() { const string key = "patients:latestObs:delete"; await _svc.SetObjectAsync(key, "stored-value"); await _svc.DeleteObjectAsync(key); var valueAfterDelete = await _svc.GetObjectAsync(key); Assert.That(valueAfterDelete, Is.Null); var factoryCallCount = 0; var result = await _svc.GetOrSetObjectAsync(key, () => { factoryCallCount++; return Task.FromResult("after-delete"); }); Assert.That(factoryCallCount, Is.EqualTo(1)); Assert.That(result, Is.EqualTo("after-delete")); } #endregion #region TC-36 [Test] public async Task DeleteByPatternAsync_RemovesMatchingKeys_AndKeepsNonMatchingKeys() { await _svc.SetObjectAsync("patients:abc", "val1"); await _svc.SetObjectAsync("patients:def", "val2"); await _svc.SetObjectAsync("appointments:xyz", "val3"); var deleted = await _svc.DeleteByPatternAsync("patients:"); Assert.That(deleted, Is.EqualTo(2)); var p1 = await _svc.GetObjectAsync("patients:abc"); var p2 = await _svc.GetObjectAsync("patients:def"); var a1 = await _svc.GetObjectAsync("appointments:xyz"); Assert.That(p1, Is.Null); Assert.That(p2, Is.Null); Assert.That(a1, Is.EqualTo("val3")); } #endregion #region TC-37 [Test] public async Task CleanCache_RemovesAllKeys_AndFactoryIsInvokedAfterClean() { await _svc.SetObjectAsync("patients:1", "v1"); await _svc.SetObjectAsync("patients:2", "v2"); await _svc.SetObjectAsync("appointments:1", "v3"); await _svc.SetObjectAsync("configDisplays:1", "v4"); await _svc.SetObjectAsync("pumpObs:1", "v5"); _svc.CleanCache(); Assert.That(await _svc.GetObjectAsync("patients:1"), Is.Null); Assert.That(await _svc.GetObjectAsync("patients:2"), Is.Null); Assert.That(await _svc.GetObjectAsync("appointments:1"), Is.Null); Assert.That(await _svc.GetObjectAsync("configDisplays:1"), Is.Null); Assert.That(await _svc.GetObjectAsync("pumpObs:1"), Is.Null); var factoryCallCount = 0; await _svc.GetOrSetObjectAsync("patients:1", () => { factoryCallCount++; return Task.FromResult("after-clean"); }); Assert.That(factoryCallCount, Is.EqualTo(1)); } #endregion }