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!; /// /// Initializes the test environment by creating a with a mock logger /// and an in-memory lock provider, and instantiating the under test with that lock manager. /// [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 /// /// Verifies that GetOrSetObjectAsync invokes the supplied factory on the first call, persists the produced value, and on subsequent calls returns the cached value without invoking the factory again. /// [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 /// /// Verifies that GetOrSetObjectAsync does not cache results, ensuring the factory delegate is re-invoked on subsequent calls for the same key when the previously produced value was . /// [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 /// /// Verifies that when two threads call GetOrSetObjectAsync concurrently for the same key, /// the second thread does not invoke its factory if the first thread has already inserted the value, /// and both threads receive the value produced by the first thread's factory. /// [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 /// /// Verifies that calling DeleteObjectAsync removes the stored value for the given key, and that the next call to GetOrSetObjectAsync invokes the factory delegate to produce a new value instead of returning a previously cached one. /// [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 /// /// Verifies that DeleteByPatternAsync removes only the cache entries whose keys match the supplied pattern while preserving unrelated keys that do not match. /// [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 /// /// Verifies that CleanCache removes all entries from the cache, causing subsequent /// GetOrSetObjectAsync calls to invoke the provided factory delegate to repopulate the value. /// [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 }