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
}