using adas_core.Application.Services.Caching;
using adas_core.Application.Services.Interfaces;
using adas_core.Domain.Enums;
using adas_core.Domain.Models.AppSettings;
using adas_core.Domain.Models.GroupedObservations;
using adas_core.Domain.Utils;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using MongoDB.Bson;
using Moq;
namespace adas_core.Test.Services;
///
/// Provides a fake implementation of the interface, typically used as a test double or non-functional placeholder.
///
///
public class FakeLockProvider : ILockProvider
{
///
/// Asynchronously attempts to acquire a resource identified by the specified key within the given timeout.
/// This implementation always reports a successful acquisition by returning true.
///
/// The identifier of the resource to acquire.
/// The maximum time to wait for the resource to become available.
/// A that always completes with true, indicating the resource was acquired.
///
public Task AcquireAsync(string key, TimeSpan timeout) => Task.FromResult(true);
///
/// Releases the resource associated with the specified key. This implementation completes immediately without performing any additional operation.
///
/// The identifier of the resource to release.
///
public Task ReleaseAsync(string key) => Task.CompletedTask;
}
///
/// Represents a fake implementation of , typically used as a test double to provide controlled or simplified behavior in unit tests.
///
///
/// Inherits all members from and is intended to be used in place of the real service when actual locking functionality is not required.
///
///
public class FakeLockManagerService : LockManagerService
{
///
/// Initializes a new instance of , a test fake of , by forwarding a mocked and a to the base constructor.
///
///
public FakeLockManagerService() : base(
Mock.Of>(),
new FakeLockProvider())
{
}
}
///
/// Represents a fake or test double implementation of that also implements , typically used to simulate Redis caching behavior in testing scenarios.
///
///
/// This class combines the inheritance of with the contract of , allowing it to stand in for a real Redis-backed cache during unit tests or development.
///
///
public class FakeRedisService : RedisService, ICacheService
{
public bool WasCalled { get; private set; }
public string? LastKey { get; private set; }
///
/// Initializes a new instance of , a test double for , supplying default , a mocked , and a to the base constructor.
///
///
public FakeRedisService() : base(
Options.Create(new CacheSettings()),
Mock.Of>(),
new FakeLockManagerService())
{
}
///
/// Retrieves a cached object associated with the specified key, or creates and stores one using the provided factory if no cached entry exists.
/// This implementation bypasses caching and directly invokes the factory, recording the invocation for verification purposes while ignoring the TTL and cancellation token.
///
/// The cache key used to identify the stored object.
/// A delegate that asynchronously produces the object to cache when no entry exists for the specified key.
/// An optional time-to-live duration for the cached entry. Not used in this implementation.
/// A token to observe for cancellation requests. Not used in this implementation.
/// A task that represents the asynchronous operation, containing the object produced by the factory.
///
Task ICacheService.GetOrSetObjectAsync(
string key,
Func> factory,
TimeSpan? ttl,
CancellationToken cancellationToken)
{
WasCalled = true;
LastKey = key;
return factory();
}
///
/// Retrieves or sets a cached object associated with the specified grouped field and patient identifier.
/// Marks the call as executed via the WasCalled flag and returns the value produced by the supplied factory delegate.
///
/// The grouped field used to identify the cached object.
/// The identifier of the patient associated with the cached object.
/// The asynchronous factory delegate invoked to produce the value when no cached entry exists.
/// An optional time-to-live duration for the cached entry.
/// The token used to cancel the asynchronous operation.
/// The value of type produced by the delegate.
///
Task ICacheService.GetOrSetObjectAsync(
GroupedField groupedField,
ObjectId patientId,
Func> factory,
TimeSpan? ttl,
CancellationToken cancellationToken)
{
WasCalled = true;
return factory();
}
}
///
/// Represents a fake implementation of that also implements the interface, typically used for testing or stubbing scenarios.
///
///
/// This class combines inheritance from the concrete base class with the contract, allowing it to be used wherever an is required.
///
///
public class FakeCacheService : CacheService, ICacheService
{
public bool WasCalled { get; private set; }
public string? LastKey { get; private set; }
///
/// Initializes a new instance of the test double, supplying a newly created to the base cache service as its lock-management dependency.
///
///
public FakeCacheService() : base(new FakeLockManagerService())
{
}
///
/// Stub implementation of that records the invocation and the supplied key without performing any caching, and returns the value produced by .
///
/// The cache key under which the value would be stored; recorded for later verification.
/// The invoked to produce the value returned by this method.
/// Optional time-to-live for the cache entry; ignored by this implementation.
/// Token to cancel the operation; ignored by this implementation.
/// The produced by invoking .
///
///
/// Explicit implementation that always invokes and ignores and , effectively bypassing the cache and returning the value freshly produced for the supplied .
///
/// The cache key identifying the requested entry.
/// The asynchronous delegate invoked to produce the value.
/// The optional cache lifetime. Not applied by this implementation.
/// The token used to cancel the operation. Not observed by this implementation.
/// A that completes with the value returned by .
///
Task ICacheService.GetOrSetObjectAsync(
string key,
Func> factory,
TimeSpan? ttl,
CancellationToken cancellationToken)
{
WasCalled = true;
LastKey = key;
return factory();
}
///
/// Retrieves an object from the cache for the given and , or produces it via when no cached entry exists. This implementation unconditionally invokes and records the call by setting to true, ignoring any cached value and the .
///
/// The type of the object being retrieved or created.
/// The grouped field used as part of the cache lookup key.
/// The patient identifier used as part of the cache lookup key.
/// The asynchronous factory invoked to produce the value when no cached entry is available.
/// An optional time-to-live for the cached entry. Not used by this implementation.
/// A to cancel the operation.
/// A that resolves to the value returned by .
///
///
/// Test implementation of that bypasses caching
/// and directly invokes the supplied delegate, recording the call via the
/// WasCalled flag for verification purposes.
///
/// The identifying the group context for the cached or computed object.
/// The of the patient whose data is being retrieved.
/// The delegate invoked to produce the result.
/// Optional time-to-live duration; ignored by this implementation.
/// Token to observe for cancellation; ignored by this implementation.
/// The produced by invoking .
///
Task ICacheService.GetOrSetObjectAsync(
GroupedField groupedField,
ObjectId patientId,
Func> factory,
TimeSpan? ttl,
CancellationToken cancellationToken)
{
WasCalled = true;
return factory();
}
}
///
/// Represents a fake implementation of the cache service, used for testing or scenarios where a no-op cache behavior is required.
///
///
/// This class inherits from and implements the interface, providing a non-functional cache suitable for unit tests or environments where caching should be bypassed.
///
///
public class FakeNoCacheService : NoCacheService, ICacheService
{
public bool WasCalled { get; private set; }
public string? LastKey { get; private set; }
Task ICacheService.GetOrSetObjectAsync(
string key,
Func> factory,
TimeSpan? ttl,
CancellationToken cancellationToken)
{
WasCalled = true;
LastKey = key;
return factory();
}
Task ICacheService.GetOrSetObjectAsync(
GroupedField groupedField,
ObjectId patientId,
Func> factory,
TimeSpan? ttl,
CancellationToken cancellationToken)
{
WasCalled = true;
return factory();
}
}
///
/// Represents a test fixture that exercises the to validate its expected runtime behavior.
///
///
[TestFixture]
public class CacheDispatcherTest
{
private FakeRedisService _redisServiceFake = null!;
private FakeCacheService _memoryServiceFake = null!;
private FakeNoCacheService _noopServiceFake = null!;
private CacheSettings _cacheSettings = null!;
private CacheDispatcher _cacheDispatcher = null!;
///
/// Initializes the test environment by instantiating fake implementations of the Redis, in-memory, and no-op cache services, along with default , and constructs a under test using these dependencies.
///
///
[SetUp]
public void SetUp()
{
_redisServiceFake = new FakeRedisService();
_memoryServiceFake = new FakeCacheService();
_noopServiceFake = new FakeNoCacheService();
_cacheSettings = new CacheSettings();
_cacheDispatcher = new CacheDispatcher(
_redisServiceFake,
_memoryServiceFake,
_noopServiceFake,
_cacheSettings);
}
#region TC-23
///
/// Verifies that GetOrSetObjectAsync uses the Redis service when the patients cache mode is configured as , and does not fall back to the memory or no-op cache services.
///
///
[Test]
public async Task GetOrSetObjectAsync_UsesRedisService_WhenPatientsModeIsRedis()
{
// Arrange
var key = "patients:latestObs:abc";
var expectedResult = new { Name = "TestPatient" };
Func> factory = () => Task.FromResult