rama creada apartir de master en j
This commit is contained in:
@@ -7,15 +7,19 @@ public class CacheServiceTest
|
||||
{
|
||||
private CacheService _svc = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the test environment by creating a <see cref="LockManagerService"/> with a mock logger
|
||||
/// and an in-memory lock provider, and instantiating the <see cref="CacheService"/> under test with that lock manager.
|
||||
/// </summary>
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
var lockMgr = new LockManagerService(
|
||||
new Mock<ILogger<LockManagerService>>().Object,
|
||||
new InMemoryLockProvider());
|
||||
|
||||
_svc = new CacheService(lockMgr);
|
||||
}
|
||||
public void SetUp()
|
||||
{
|
||||
var lockMgr = new LockManagerService(
|
||||
new Mock<ILogger<LockManagerService>>().Object,
|
||||
new InMemoryLockProvider());
|
||||
|
||||
_svc = new CacheService(lockMgr);
|
||||
}
|
||||
#region TC-31
|
||||
[Test]
|
||||
public async Task GetOrSetObjectAsync_ReturnsCachedValue_AndDoesNotInvokeFactory_WhenKeyAlreadyExists()
|
||||
@@ -37,172 +41,193 @@ public class CacheServiceTest
|
||||
}
|
||||
#endregion
|
||||
#region TC-32
|
||||
/// <summary>
|
||||
/// Verifies that <c>GetOrSetObjectAsync</c> 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.
|
||||
/// </summary>
|
||||
[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, () =>
|
||||
public async Task GetOrSetObjectAsync_InvokesFactory_StoresResult_AndDoesNotInvokeAgainOnSecondCall()
|
||||
{
|
||||
factoryCallCount++;
|
||||
return Task.FromResult(factoryValue);
|
||||
});
|
||||
|
||||
Assert.That(factoryCallCount, Is.EqualTo(1));
|
||||
Assert.That(firstResult, Is.EqualTo(factoryValue));
|
||||
|
||||
var cached = await _svc.GetObjectAsync<string>(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));
|
||||
}
|
||||
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<string>(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
|
||||
/// <summary>
|
||||
/// Verifies that <c>GetOrSetObjectAsync</c> does not cache <see langword="null"/> results, ensuring the factory delegate is re-invoked on subsequent calls for the same key when the previously produced value was <see langword="null"/>.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task GetOrSetObjectAsync_DoesNotCacheNullResult_AndInvokesFactoryOnSubsequentCalls()
|
||||
{
|
||||
const string key = "patients:latestObs:null";
|
||||
var factoryCallCount = 0;
|
||||
|
||||
await _svc.GetOrSetObjectAsync(key, () =>
|
||||
public async Task GetOrSetObjectAsync_DoesNotCacheNullResult_AndInvokesFactoryOnSubsequentCalls()
|
||||
{
|
||||
factoryCallCount++;
|
||||
return Task.FromResult<string?>(null);
|
||||
});
|
||||
|
||||
Assert.That(factoryCallCount, Is.EqualTo(1));
|
||||
|
||||
await _svc.GetOrSetObjectAsync(key, () =>
|
||||
{
|
||||
factoryCallCount++;
|
||||
return Task.FromResult<string?>(null);
|
||||
});
|
||||
|
||||
Assert.That(factoryCallCount, Is.EqualTo(2));
|
||||
}
|
||||
const string key = "patients:latestObs:null";
|
||||
var factoryCallCount = 0;
|
||||
|
||||
await _svc.GetOrSetObjectAsync(key, () =>
|
||||
{
|
||||
factoryCallCount++;
|
||||
return Task.FromResult<string?>(null);
|
||||
});
|
||||
|
||||
Assert.That(factoryCallCount, Is.EqualTo(1));
|
||||
|
||||
await _svc.GetOrSetObjectAsync(key, () =>
|
||||
{
|
||||
factoryCallCount++;
|
||||
return Task.FromResult<string?>(null);
|
||||
});
|
||||
|
||||
Assert.That(factoryCallCount, Is.EqualTo(2));
|
||||
}
|
||||
#endregion
|
||||
#region TC-34
|
||||
/// <summary>
|
||||
/// Verifies that when two threads call <c>GetOrSetObjectAsync</c> 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.
|
||||
/// </summary>
|
||||
[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<string> ControlledFactory()
|
||||
public async Task GetOrSetObjectAsync_SecondCheckInLock_PreventsFactoryExecution_WhenValueAlreadyInsertedByFirstThread()
|
||||
{
|
||||
Interlocked.Increment(ref factoryCallCount);
|
||||
task1InFactory.Release();
|
||||
await task1CanFinish.WaitAsync();
|
||||
return "first-result";
|
||||
const string key = "patients:concurrent:abc";
|
||||
var factoryCallCount = 0;
|
||||
var task1InFactory = new SemaphoreSlim(0, 1);
|
||||
var task1CanFinish = new SemaphoreSlim(0, 1);
|
||||
|
||||
async Task<string> 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"));
|
||||
}
|
||||
|
||||
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
|
||||
/// <summary>
|
||||
/// Verifies that calling <c>DeleteObjectAsync</c> removes the stored value for the given key, and that the next call to <c>GetOrSetObjectAsync</c> invokes the factory delegate to produce a new value instead of returning a previously cached one.
|
||||
/// </summary>
|
||||
[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<string>(key);
|
||||
Assert.That(valueAfterDelete, Is.Null);
|
||||
|
||||
var factoryCallCount = 0;
|
||||
var result = await _svc.GetOrSetObjectAsync(key, () =>
|
||||
public async Task DeleteObjectAsync_RemovesKey_AndFactoryIsInvokedOnNextCall()
|
||||
{
|
||||
factoryCallCount++;
|
||||
return Task.FromResult("after-delete");
|
||||
});
|
||||
|
||||
Assert.That(factoryCallCount, Is.EqualTo(1));
|
||||
Assert.That(result, Is.EqualTo("after-delete"));
|
||||
}
|
||||
const string key = "patients:latestObs:delete";
|
||||
await _svc.SetObjectAsync(key, "stored-value");
|
||||
|
||||
await _svc.DeleteObjectAsync(key);
|
||||
|
||||
var valueAfterDelete = await _svc.GetObjectAsync<string>(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
|
||||
/// <summary>
|
||||
/// Verifies that <c>DeleteByPatternAsync</c> removes only the cache entries whose keys match the supplied pattern while preserving unrelated keys that do not match.
|
||||
/// </summary>
|
||||
[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<string>("patients:abc");
|
||||
var p2 = await _svc.GetObjectAsync<string>("patients:def");
|
||||
var a1 = await _svc.GetObjectAsync<string>("appointments:xyz");
|
||||
|
||||
Assert.That(p1, Is.Null);
|
||||
Assert.That(p2, Is.Null);
|
||||
Assert.That(a1, Is.EqualTo("val3"));
|
||||
}
|
||||
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<string>("patients:abc");
|
||||
var p2 = await _svc.GetObjectAsync<string>("patients:def");
|
||||
var a1 = await _svc.GetObjectAsync<string>("appointments:xyz");
|
||||
|
||||
Assert.That(p1, Is.Null);
|
||||
Assert.That(p2, Is.Null);
|
||||
Assert.That(a1, Is.EqualTo("val3"));
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region TC-37
|
||||
/// <summary>
|
||||
/// Verifies that <c>CleanCache</c> removes all entries from the cache, causing subsequent
|
||||
/// <c>GetOrSetObjectAsync</c> calls to invoke the provided factory delegate to repopulate the value.
|
||||
/// </summary>
|
||||
[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<string>("patients:1"), Is.Null);
|
||||
Assert.That(await _svc.GetObjectAsync<string>("patients:2"), Is.Null);
|
||||
Assert.That(await _svc.GetObjectAsync<string>("appointments:1"), Is.Null);
|
||||
Assert.That(await _svc.GetObjectAsync<string>("configDisplays:1"), Is.Null);
|
||||
Assert.That(await _svc.GetObjectAsync<string>("pumpObs:1"), Is.Null);
|
||||
|
||||
var factoryCallCount = 0;
|
||||
await _svc.GetOrSetObjectAsync("patients:1", () =>
|
||||
public async Task CleanCache_RemovesAllKeys_AndFactoryIsInvokedAfterClean()
|
||||
{
|
||||
factoryCallCount++;
|
||||
return Task.FromResult("after-clean");
|
||||
});
|
||||
|
||||
Assert.That(factoryCallCount, Is.EqualTo(1));
|
||||
}
|
||||
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<string>("patients:1"), Is.Null);
|
||||
Assert.That(await _svc.GetObjectAsync<string>("patients:2"), Is.Null);
|
||||
Assert.That(await _svc.GetObjectAsync<string>("appointments:1"), Is.Null);
|
||||
Assert.That(await _svc.GetObjectAsync<string>("configDisplays:1"), Is.Null);
|
||||
Assert.That(await _svc.GetObjectAsync<string>("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
|
||||
}
|
||||
Reference in New Issue
Block a user