Files
adas-core/adas-core.Test/Services/PointOfCareServiceTest.cs
T
2026-06-26 10:29:23 +02:00

324 lines
14 KiB
C#

using System.Security.Claims;
using adas_core.Application.Exceptions;
using adas_core.Application.Repositories.Interfaces;
using adas_core.Application.Services;
using adas_core.Application.Services.Interfaces;
using adas_core.Domain.Enums;
using adas_core.Domain.Models.AppSettings;
using adas_core.Domain.Models.MongoModels;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using MongoDB.Bson;
using Moq;
using Options = Microsoft.Extensions.Options.Options;
namespace adas_core.Test.Services;
[TestFixture]
public class PointOfCareServiceTests
{
/// <summary>
/// Initializes the mock dependencies and test environment required for unit testing the <see cref="PointOfCareService"/>.
/// Configures repository, service, cache, admission, unit, and HTTP context mocks, including a simulated authenticated user
/// and cache behavior that invokes the supplied factory directly to return the produced <see cref="PointOfCare"/> instance.
/// </summary>
[SetUp]
public void SetUp()
{
_pointOfCareRepositoryMock = new Mock<IPointOfCareRepository>();
_unitServiceMock = new Mock<IUnitService>();
_admissionServiceMock = new Mock<IAdmissionService>();
_cacheServiceMock = new Mock<ICacheService>();
_cacheServiceMock
.Setup(c => c.GetOrSetObjectAsync(
It.IsAny<string>(),
It.IsAny<Func<Task<PointOfCare?>>>(),
It.IsAny<TimeSpan?>(),
It.IsAny<CancellationToken>()))
.Returns((string _, Func<Task<PointOfCare?>> factory, TimeSpan? _, CancellationToken _) => factory());
var loggerMock = new Mock<ILogger<PointOfCareService>>();
var userClaims = new ClaimsPrincipal(new ClaimsIdentity([
new Claim(ClaimTypes.Name, "TestUser")
], "mock"));
var httpContextMock = new DefaultHttpContext
{
User = userClaims
};
_httpContextAccessorMock.Setup(accessor => accessor.HttpContext)
.Returns(httpContextMock);
_pointOfCareService = new PointOfCareService(
loggerMock.Object,
_pointOfCareRepositoryMock.Object,
new Lazy<IPatientService>(Mock.Of<IPatientService>),
new Lazy<IUnitService>(() => _unitServiceMock.Object),
Mock.Of<ISubscribersService>(),
Mock.Of<Lazy<IClientMessageService>>(),
new Lazy<IAdmissionService>(() => _admissionServiceMock.Object),
_httpContextAccessorMock.Object,
_auditServiceMock.Object,
_cacheServiceMock.Object,
Options.Create(new CacheSettings())
);
}
private PointOfCareService _pointOfCareService = null!;
private Mock<IPointOfCareRepository> _pointOfCareRepositoryMock = null!;
private Mock<IUnitService> _unitServiceMock = null!;
private Mock<ICacheService> _cacheServiceMock = null!;
private Mock<IAdmissionService> _admissionServiceMock = null!;
private readonly Mock<IHttpContextAccessor> _httpContextAccessorMock = new();
private readonly Mock<ILocalAuditService> _auditServiceMock = new();
/// <summary>
/// Verifies that the <see cref="PointOfCareService.Delete"/> method successfully deletes a <c>PointOfCare</c>
/// when invoked with a valid <see cref="ObjectId"/> whose associated admission is <c>null</c>, ensuring
/// the underlying repository's delete operation is invoked.
/// </summary>
[Test]
public async Task Delete_ValidObjectId_DeletesPointOfCare()
{
// Arrange
var id = ObjectId.GenerateNewId();
_pointOfCareRepositoryMock.Setup(m => m.FindByIdAllConfig(id))
.ReturnsAsync(new PointOfCare { Id = id, AdmissionId = null });
_pointOfCareRepositoryMock.Setup(m => m.Delete(id)).Verifiable();
// Act
await _pointOfCareService.Delete(id);
// Assert
_pointOfCareRepositoryMock.Verify();
}
/// <summary>
/// Verifies that the <see cref="PointOfCareService.Update"/> method successfully updates a valid
/// <see cref="PointOfCare"/> instance by delegating the operation to the underlying repository.
/// </summary>
/// <returns>A <see cref="Task"/> representing the asynchronous test execution.</returns>
[Test]
public async Task Update_ValidPointOfCare_UpdatesPointOfCare()
{
// Arrange
var pointOfCare = new PointOfCare { Id = ObjectId.GenerateNewId(), UnitName = "TestUnit" };
_pointOfCareRepositoryMock.Setup(m => m.Update(pointOfCare)).Verifiable();
// Act
await _pointOfCareService.Update(pointOfCare);
// Assert
_pointOfCareRepositoryMock.Verify();
}
/// <summary>
/// Verifies that <see cref="PointOfCareService.GetAll"/> returns the complete list of point of care records provided by the repository when invoked with no arguments.
/// </summary>
[Test]
public async Task GetAll_NoArguments_ReturnsListOfPointOfCare()
{
// Arrange
var pointOfCareList = new List<PointOfCare> { new(), new() };
_pointOfCareRepositoryMock.Setup(m => m.GetAll()).ReturnsAsync(pointOfCareList);
// Act
var result = await _pointOfCareService.GetAll();
// Assert
Assert.That(result, Is.EqualTo(pointOfCareList));
}
/// <summary>
/// Verifies that <see cref="PointOfCareService.UpdateConfiguration"/> throws a <see cref="ConflictException"/> when invoked with a valid id and configuration, ensuring the service surfaces conflict conditions during the update operation.
/// </summary>
[Test]
public void UpdateConfiguration_ValidIdAndConfiguration_InvokesRepositoryUpdateConfiguration()
{
// Arrange
var id = ObjectId.GenerateNewId();
var configuration = new PointOfCareConfiguration();
// Act & Assert
Assert.That(_pointOfCareService, Is.Not.Null);
Assert.That(_pointOfCareRepositoryMock, Is.Not.Null);
Func<Task> act = async () => await _pointOfCareService.UpdateConfiguration(id, configuration);
Assert.ThrowsAsync<ConflictException>(act);
}
/// <summary>
/// Verifies that <c>FindById</c> returns the expected <see cref="PointOfCare"/> when a valid identifier is provided.
/// </summary>
[Test]
public async Task FindById_ValidId_ReturnsPointOfCare()
{
// Arrange
var id = ObjectId.GenerateNewId();
var expectedPointOfCare = new PointOfCare { Id = id };
_pointOfCareRepositoryMock.Setup(m => m.FindByIdAllConfig(id)).ReturnsAsync(expectedPointOfCare);
// Act
var result = await _pointOfCareService.FindById(id);
// Assert
Assert.That(result, Is.EqualTo(expectedPointOfCare));
}
// [Test]
// public async Task FindByUnit_ValidUnit_ReturnsListOfPointOfCare()
// {
// // Arrange
// var id = ObjectId.GenerateNewId();
// var pointOfCare = new PointOfCare { Id = id, UnitId = ObjectId.GenerateNewId() };
// var unit = new Unit { Id = pointOfCare.UnitId, Name = "TestUnit" };
// var expectedPointOfCareList = new List<PointOfCare>
// {
// new PointOfCare { Unit = unit },
// new PointOfCare { Unit = unit },
// };
// _pointOfCareRepositoryMock.Setup(m => m.FindByUnit(unit)).ReturnsAsync(expectedPointOfCareList);
//
// // Act
// var result = await _pointOfCareService.FindByUnit(unit);
//
// // Assert
// Assert.That(result, Is.EqualTo(expectedPointOfCareList));
// }
/// <summary>
/// Verifies that <see cref="PointOfCareService.FindByUnitAndStatus"/> returns the expected list of point-of-care records when invoked with a valid unit identifier and status.
/// </summary>
[Test]
public async Task FindByUnitAndStatus_ValidUnitIdAndStatus_ReturnsListOfPointOfCare()
{
// Arrange
var unitId = ObjectId.GenerateNewId();
var pocStatus = StatusEnum.PointOfCare.Available;
var expectedPointOfCareList = new List<PointOfCare>
{
new(),
new()
};
_pointOfCareRepositoryMock.Setup(m => m.FindByUnitAndStatus(unitId, pocStatus, false))
.ReturnsAsync(expectedPointOfCareList);
// Act
var result = await _pointOfCareService.FindByUnitAndStatus(unitId, pocStatus);
// Assert
Assert.That(result, Is.EqualTo(expectedPointOfCareList));
}
[Test]
public void CheckNextAdmission_ValidPatientLocation_UpdatesPoCStatus_AndCallsUpdate()
{
// Arrange
var patientLocationId = ObjectId.GenerateNewId();
var pocToCheck = new PointOfCare
{
Id = patientLocationId,
AdmissionId = null, // Forzamos la rama que busca la siguiente admisión
Status = StatusEnum.PointOfCare.Locked // Locked → no debe actualizar. Cambia para probar actualización:
// Usa Available o Reserved para que se ejecute el update.
};
// Para que ejecute la lógica de actualización, cambiamos el estado inicial:
pocToCheck.Status = StatusEnum.PointOfCare.Available;
var nextAdmission = new Admission { Id = ObjectId.GenerateNewId() };
// 1) Cache: debe ejecutar el factory y devolver el PoC
_cacheServiceMock
.Setup(c => c.GetOrSetObjectAsync(
It.IsAny<string>(),
It.IsAny<Func<Task<PointOfCare?>>>(),
It.IsAny<TimeSpan?>(),
It.IsAny<CancellationToken>()))
.Returns((string _, Func<Task<PointOfCare?>> factory, TimeSpan? _, CancellationToken _) => factory());
// 2) Repo: FindById (usado por el factory del caché)
_pointOfCareRepositoryMock
.Setup(m => m.FindById(patientLocationId))
.ReturnsAsync(pocToCheck);
// 3) AdmissionService (ruta AdmissionId == null → buscar siguiente)
_admissionServiceMock
.Setup(m => m.GetAdmissionByPointOfCareId(pocToCheck.Id))
.ReturnsAsync([nextAdmission]);
// 4) Update(poc) → queremos esperar a que se invoque
var updatedSignal = new ManualResetEventSlim(false);
_pointOfCareRepositoryMock
.Setup(m => m.Update(It.IsAny<PointOfCare>()))
.Callback<PointOfCare>(_ => updatedSignal.Set())
.Returns(Task.CompletedTask);
// Update llama internamente a FindById otra vez (en Update)
_pointOfCareRepositoryMock
.Setup(m => m.FindById(pocToCheck.Id))
.ReturnsAsync(pocToCheck);
// 5) Ejecutar (async void) y esperar a que se dispare el update
_pointOfCareService.CheckNextAdmission(patientLocationId);
// Espera razonable a que termine la operación asíncrona interna
var completed = updatedSignal.Wait(TimeSpan.FromSeconds(1));
Assert.That(completed, Is.True, "CheckNextAdmission no disparó Update a tiempo.");
// Assert
// a) Se llamó a FindById (vía caché)
_pointOfCareRepositoryMock.Verify(m => m.FindById(patientLocationId), Times.AtLeastOnce);
// b) Se llamó a Update con el PoC actualizado: Status Reserved + Admission asignada
_pointOfCareRepositoryMock.Verify(m => m.Update(
It.Is<PointOfCare>(p =>
p.Id == pocToCheck.Id &&
p.Status == StatusEnum.PointOfCare.Reserved &&
p.AdmissionId == nextAdmission.Id &&
p.Admission == nextAdmission)
), Times.Once);
// c) NO verifiques FindByIdAllConfig: en CheckNextAdmission no se usa esa ruta
}
/// <summary>
/// Tests that the service's FindByRoom method, when called with a valid room, returns the collection provided by the repository and invokes the repository's FindByRoom exactly once.
/// </summary>
[Test]
public async Task FindByRoom_ValidRoom_CallsRepositoryFindByRoom()
{
// Arrange
const string room = "TestRoom";
var expectedPointOfCares = new List<PointOfCare>();
_pointOfCareRepositoryMock.Setup(m => m.FindByRoom(room)).ReturnsAsync(expectedPointOfCares);
// Act
var result = await _pointOfCareService.FindByRoom(room);
// Assert
Assert.That(result, Is.EqualTo(expectedPointOfCares));
_pointOfCareRepositoryMock.Verify(m => m.FindByRoom(room), Times.Once);
}
/// <summary>
/// Verifies that the <see cref="PointOfCareService.FindByBed"/> method correctly delegates to the repository's FindByBed method when a valid bed identifier is provided, returning the expected collection of point of care records.
/// </summary>
[Test]
public async Task FindByBed_ValidBed_CallsRepositoryFindByBed()
{
// Arrange
const string bed = "TestBed";
var expectedPointOfCares = new List<PointOfCare>();
_pointOfCareRepositoryMock.Setup(m => m.FindByBed(bed)).ReturnsAsync(expectedPointOfCares);
// Act
var result = await _pointOfCareService.FindByBed(bed);
// Assert
Assert.That(result, Is.EqualTo(expectedPointOfCares));
_pointOfCareRepositoryMock.Verify(m => m.FindByBed(bed), Times.Once);
}
}