rama creada apartir de master en j

This commit is contained in:
jrojas
2026-06-26 10:29:23 +02:00
parent 319fd3dfb0
commit c1517fda87
2810 changed files with 1927392 additions and 25392 deletions
+389 -336
View File
@@ -15,6 +15,9 @@ using Moq;
namespace adas_core.Test.Services;
/// <summary>
/// Contains unit tests for verifying the behavior of the <see cref="AdmissionService"/> class.
/// </summary>
public class AdmissionServiceTest
{
private Mock<IAdmissionRepository> _admissionRepositoryMock;
@@ -78,97 +81,113 @@ public class AdmissionServiceTest
_masterListServiceFactoryMock.Object);
}
/// <summary>
/// Verifies that the admission service successfully deletes a valid admission by ensuring the repository's
/// Delete operation is invoked exactly once for the corresponding admission identifier.
/// </summary>
[Test]
public async Task DeleteAdmissionAsync_ValidAdmission_DeletesSuccessfully()
{
// Arrange
var admissionId = ObjectId.GenerateNewId();
var admission = new Admission { Id = admissionId };
_admissionRepositoryMock.Setup(repo => repo.FindById(admissionId))
.ReturnsAsync(admission);
// Act
await _admissionService.DeleteAdmissionAsync(admission);
// Assert
_admissionRepositoryMock.Verify(repo => repo.Delete(admissionId), Times.Once);
}
public async Task DeleteAdmissionAsync_ValidAdmission_DeletesSuccessfully()
{
// Arrange
var admissionId = ObjectId.GenerateNewId();
var admission = new Admission { Id = admissionId };
_admissionRepositoryMock.Setup(repo => repo.FindById(admissionId))
.ReturnsAsync(admission);
// Act
await _admissionService.DeleteAdmissionAsync(admission);
// Assert
_admissionRepositoryMock.Verify(repo => repo.Delete(admissionId), Times.Once);
}
/// <summary>
/// Verifies that <c>DeleteAdmissionByIdAsync</c> successfully deletes an admission when it exists in the repository, by ensuring the repository's <c>Delete</c> method is invoked exactly once for the given admission identifier.
/// </summary>
[Test]
public async Task DeleteAdmissionByIdAsync_AdmissionExists_DeletesSuccessfully()
{
// Arrange
var admissionId = ObjectId.GenerateNewId();
var admission = new Admission { Id = admissionId, PointOfCareId = ObjectId.GenerateNewId() };
_admissionRepositoryMock.Setup(repo => repo.FindById(admissionId))
.ReturnsAsync(admission);
// Act
await _admissionService.DeleteAdmissionByIdAsync(admissionId);
// Assert
_admissionRepositoryMock.Verify(repo => repo.Delete(admissionId), Times.Once);
}
public async Task DeleteAdmissionByIdAsync_AdmissionExists_DeletesSuccessfully()
{
// Arrange
var admissionId = ObjectId.GenerateNewId();
var admission = new Admission { Id = admissionId, PointOfCareId = ObjectId.GenerateNewId() };
_admissionRepositoryMock.Setup(repo => repo.FindById(admissionId))
.ReturnsAsync(admission);
// Act
await _admissionService.DeleteAdmissionByIdAsync(admissionId);
// Assert
_admissionRepositoryMock.Verify(repo => repo.Delete(admissionId), Times.Once);
}
/// <summary>
/// Verifies that <see cref="AdmissionService.GetAdmissionByIdAsync"/> returns the admission together with its
/// associated patient location (unit, bed, and room) when the admission exists in the repository and the
/// corresponding point of care is successfully resolved.
/// </summary>
[Test]
public async Task GetAdmissionByIdAsync_AdmissionExists_ReturnsAdmissionWithPatientLocation()
{
// Arrange
var admissionId = ObjectId.GenerateNewId();
var pointOfCareId = ObjectId.GenerateNewId();
var admission = new Admission { Id = admissionId, PointOfCareId = pointOfCareId };
var poc = new PointOfCare { Id = pointOfCareId, UnitName = "TestUnit", Bed = "TestBed", Room = "TestRoom" };
_admissionRepositoryMock.Setup(repo => repo.FindById(admissionId))
.ReturnsAsync(admission);
_pointOfCareServiceMock.Setup(repo => repo.GetInfo(pointOfCareId, null, false, It.IsAny<CancellationToken>()))
.ReturnsAsync(poc);
// Act
var result = await _admissionService.GetAdmissionByIdAsync(admissionId);
// Assert
Assert.That(result, Is.Not.Null);
Assert.That(result?.PatientLocation, Is.Not.Null);
Assert.That(result?.PatientLocation?.UnitName, Is.EqualTo("TestUnit"));
Assert.That(result?.PatientLocation?.Bed, Is.EqualTo("TestBed"));
Assert.That(result?.PatientLocation?.Room, Is.EqualTo("TestRoom"));
}
public async Task GetAdmissionByIdAsync_AdmissionExists_ReturnsAdmissionWithPatientLocation()
{
// Arrange
var admissionId = ObjectId.GenerateNewId();
var pointOfCareId = ObjectId.GenerateNewId();
var admission = new Admission { Id = admissionId, PointOfCareId = pointOfCareId };
var poc = new PointOfCare { Id = pointOfCareId, UnitName = "TestUnit", Bed = "TestBed", Room = "TestRoom" };
_admissionRepositoryMock.Setup(repo => repo.FindById(admissionId))
.ReturnsAsync(admission);
_pointOfCareServiceMock.Setup(repo => repo.GetInfo(pointOfCareId, null, false, It.IsAny<CancellationToken>()))
.ReturnsAsync(poc);
// Act
var result = await _admissionService.GetAdmissionByIdAsync(admissionId);
// Assert
Assert.That(result, Is.Not.Null);
Assert.That(result?.PatientLocation, Is.Not.Null);
Assert.That(result?.PatientLocation?.UnitName, Is.EqualTo("TestUnit"));
Assert.That(result?.PatientLocation?.Bed, Is.EqualTo("TestBed"));
Assert.That(result?.PatientLocation?.Room, Is.EqualTo("TestRoom"));
}
/// <summary>
/// Verifies that GetAdmissionsAsync returns admissions enriched with their associated patient location details
/// retrieved from the point of care service.
/// </summary>
[Test]
public async Task GetAdmissionsAsync_ReturnsAdmissionsWithPatientLocations()
{
// Arrange
var admission1Id = ObjectId.GenerateNewId();
var admission2Id = ObjectId.GenerateNewId();
var poc1Id = ObjectId.GenerateNewId();
var poc2Id = ObjectId.GenerateNewId();
var admission1 = new Admission { Id = admission1Id, PointOfCareId = poc1Id };
var admission2 = new Admission { Id = admission2Id, PointOfCareId = poc2Id };
var admissionsList = new List<Admission> { admission1, admission2 };
_admissionRepositoryMock.Setup(repo => repo.FindAll())
.ReturnsAsync(admissionsList);
_pointOfCareServiceMock.Setup(repo => repo.GetInfo(poc1Id, null, false, It.IsAny<CancellationToken>()))
.ReturnsAsync(TestUtilities.CreateValidPointOfCare());
_pointOfCareServiceMock.Setup(repo => repo.GetInfo(poc2Id, null, false, It.IsAny<CancellationToken>()))
.ReturnsAsync(TestUtilities.CreateValidPointOfCare());
// Act
var result = await _admissionService.GetAdmissionsAsync();
// Assert
var admissions = result.ToList();
Assert.That(admissions, Is.Not.Null);
Assert.That(admissions.Count(), Is.EqualTo(2));
var admissionArray = admissions.ToArray();
Assert.That(admissionArray.First().PatientLocation, Is.Not.Null);
Assert.That(admissionArray.First().PatientLocation?.UnitName, Is.EqualTo("Test Unit"));
Assert.That(admissionArray.First().PatientLocation?.Bed, Is.EqualTo("Test Bed"));
Assert.That(admissionArray.First().PatientLocation?.Room, Is.EqualTo("Test Room"));
}
public async Task GetAdmissionsAsync_ReturnsAdmissionsWithPatientLocations()
{
// Arrange
var admission1Id = ObjectId.GenerateNewId();
var admission2Id = ObjectId.GenerateNewId();
var poc1Id = ObjectId.GenerateNewId();
var poc2Id = ObjectId.GenerateNewId();
var admission1 = new Admission { Id = admission1Id, PointOfCareId = poc1Id };
var admission2 = new Admission { Id = admission2Id, PointOfCareId = poc2Id };
var admissionsList = new List<Admission> { admission1, admission2 };
_admissionRepositoryMock.Setup(repo => repo.FindAll())
.ReturnsAsync(admissionsList);
_pointOfCareServiceMock.Setup(repo => repo.GetInfo(poc1Id, null, false, It.IsAny<CancellationToken>()))
.ReturnsAsync(TestUtilities.CreateValidPointOfCare());
_pointOfCareServiceMock.Setup(repo => repo.GetInfo(poc2Id, null, false, It.IsAny<CancellationToken>()))
.ReturnsAsync(TestUtilities.CreateValidPointOfCare());
// Act
var result = await _admissionService.GetAdmissionsAsync();
// Assert
var admissions = result.ToList();
Assert.That(admissions, Is.Not.Null);
Assert.That(admissions.Count(), Is.EqualTo(2));
var admissionArray = admissions.ToArray();
Assert.That(admissionArray.First().PatientLocation, Is.Not.Null);
Assert.That(admissionArray.First().PatientLocation?.UnitName, Is.EqualTo("Test Unit"));
Assert.That(admissionArray.First().PatientLocation?.Bed, Is.EqualTo("Test Bed"));
Assert.That(admissionArray.First().PatientLocation?.Room, Is.EqualTo("Test Room"));
}
// [Test]
// public async Task InsertAdmission_WhenPointOfCareExists_InsertsAdmission()
@@ -205,224 +224,255 @@ public class AdmissionServiceTest
// pointOfCareServiceMock.Verify(repo => repo.Update(It.IsAny<PointOfCare>()), Times.Once);
// }
/// <summary>
/// Verifies that <see cref="_admissionService"/>.UpdateAdmissionAsync correctly updates an existing admission by
/// invoking the repository's Update method and retrieving point-of-care information for both the new and the
/// previous point of care associated with the admission.
/// </summary>
[Test]
public async Task UpdateAdmissionAsync_WhenAdmissionExists_UpdatesAdmission()
{
// Arrange
var admissionId = ObjectId.GenerateNewId();
var admission = new Admission
public async Task UpdateAdmissionAsync_WhenAdmissionExists_UpdatesAdmission()
{
Id = admissionId,
AdmissionDate = DateTime.UtcNow,
Nhc = "TestNhc",
PointOfCareId = ObjectId.GenerateNewId(),
Person = new Person(),
Origin = TestUtilities.CreateValidOptionList(),
Diagnosis = TestUtilities.CreateValidOptionList(),
Allergies = [TestUtilities.CreateValidOptionList()],
Insulation = TestUtilities.CreateValidOptionList(),
LanguageBarrier = [TestUtilities.CreateValidOptionList()]
};
var oldAdmission = new Admission
{
Id = admissionId,
AdmissionDate = DateTime.UtcNow.AddDays(-1), // Update admission date
Nhc = "OldNhc",
PointOfCareId = ObjectId.GenerateNewId(), // Change PointOfCareId
Person = new Person(),
Origin = TestUtilities.CreateValidOptionList(),
Diagnosis = TestUtilities.CreateValidOptionList(),
Allergies = [TestUtilities.CreateValidOptionList()],
Insulation = TestUtilities.CreateValidOptionList(),
LanguageBarrier = [TestUtilities.CreateValidOptionList()]
};
_admissionRepositoryMock
.Setup(repo => repo.FindById(admissionId))
.ReturnsAsync(oldAdmission);
var updatedPointOfCare = TestUtilities.CreateValidPointOfCare();
_pointOfCareServiceMock
.Setup(repo => repo.GetInfo(admission.PointOfCareId.Value, null, false, It.IsAny<CancellationToken>()))
.ReturnsAsync(updatedPointOfCare);
// Act
await _admissionService.UpdateAdmissionAsync(admission);
// Assert
_admissionRepositoryMock.Verify(repo => repo.Update(admission), Times.Once);
_pointOfCareServiceMock.Verify(repo => repo.GetInfo(admission.PointOfCareId.Value, null, false, It.IsAny<CancellationToken>()), Times.Once);
_pointOfCareServiceMock.Verify(repo => repo.GetInfo(oldAdmission.PointOfCareId.Value, null, false, It.IsAny<CancellationToken>()), Times.Once);
}
[Test]
public async Task AdmitPatient_WithValidAdmission_CreatesPatientAndDeletesAdmission()
{
// Arrange
var admission = TestUtilities.CreateValidAdmission();
var unit = TestUtilities.CreateValidUnit();
var pointOfCare = TestUtilities.CreateValidPointOfCare();
_unitServiceMock
.Setup(repo => repo.FindById(admission.UnitId))
.ReturnsAsync(unit);
_pointOfCareServiceMock
.Setup(repo => repo.FindById(It.IsAny<ObjectId>()))
.ReturnsAsync((ObjectId id) => id == admission.PointOfCareId ? pointOfCare : null);
// Act
await _admissionService.AdmitPatient(admission);
// Assert
_patientServiceMock.Verify(repo => repo.Insert(It.IsAny<Patient>()), Times.Once);
}
[Test]
public async Task GetAdmissionByLocation_LocationExists_ReturnsAdmissions()
{
// Arrange
var location = new PatientLocation
{
UnitName = "Test Unit",
Bed = "Test Bed",
Room = "Test Room"
};
var expectedAdmissions = new List<Admission>
{
new() { Id = ObjectId.GenerateNewId() },
new() { Id = ObjectId.GenerateNewId() }
};
_admissionRepositoryMock.Setup(repo => repo.FindByLocation(location))
.ReturnsAsync(expectedAdmissions);
// Act
var result = await _admissionService.GetAdmissionByLocation(location);
// Assert
Assert.That(result, Is.Not.Null);
Assert.That(result, Is.EqualTo(expectedAdmissions));
}
[Test]
public async Task GetAdmissionByLocation_LocationNotFound_ReturnsNull()
{
// Arrange
var location = new PatientLocation
{
UnitName = "Nonexistent Unit",
Bed = "Nonexistent Bed",
Room = "Nonexistent Room"
};
_admissionRepositoryMock.Setup(repo => repo.FindByLocation(location))
.ThrowsAsync(new Exception());
// Act
var result = await _admissionService.GetAdmissionByLocation(location);
// Assert
Assert.That(result, Is.Empty);
}
[Test]
public async Task GetAdmissionByPointOfCareId_POCExists_ReturnsAdmissionsWithLocation()
{
// Arrange
var pocId = ObjectId.GenerateNewId();
var expectedAdmissions = new List<Admission>
{
new() { Id = ObjectId.GenerateNewId() },
new() { Id = ObjectId.GenerateNewId() }
};
var poc = new PointOfCare
{
Id = pocId,
UnitName = "Test Unit",
Bed = "Test Bed",
Room = "Test Room"
};
_admissionRepositoryMock.Setup(repo => repo.FindByPointOfCareId(pocId))
.ReturnsAsync(expectedAdmissions);
_pointOfCareServiceMock.Setup(repo => repo.GetInfo(pocId, null, false, It.IsAny<CancellationToken>()))
.ReturnsAsync(poc);
// Act
var result = await _admissionService.GetAdmissionByPointOfCareId(pocId);
// Assert
Assert.That(result, Is.Not.Null);
Assert.That(result.Count, Is.EqualTo(expectedAdmissions.Count));
foreach (var admission in result)
{
Assert.That(admission.PatientLocation, Is.Not.Null);
Assert.That(admission.PatientLocation?.UnitName, Is.EqualTo(poc.UnitName));
Assert.That(admission.PatientLocation?.Bed, Is.EqualTo(poc.Bed));
Assert.That(admission.PatientLocation?.Room, Is.EqualTo(poc.Room));
// Arrange
var admissionId = ObjectId.GenerateNewId();
var admission = new Admission
{
Id = admissionId,
AdmissionDate = DateTime.UtcNow,
Nhc = "TestNhc",
PointOfCareId = ObjectId.GenerateNewId(),
Person = new Person(),
Origin = TestUtilities.CreateValidOptionList(),
Diagnosis = TestUtilities.CreateValidOptionList(),
Allergies = [TestUtilities.CreateValidOptionList()],
Insulation = TestUtilities.CreateValidOptionList(),
LanguageBarrier = [TestUtilities.CreateValidOptionList()]
};
var oldAdmission = new Admission
{
Id = admissionId,
AdmissionDate = DateTime.UtcNow.AddDays(-1), // Update admission date
Nhc = "OldNhc",
PointOfCareId = ObjectId.GenerateNewId(), // Change PointOfCareId
Person = new Person(),
Origin = TestUtilities.CreateValidOptionList(),
Diagnosis = TestUtilities.CreateValidOptionList(),
Allergies = [TestUtilities.CreateValidOptionList()],
Insulation = TestUtilities.CreateValidOptionList(),
LanguageBarrier = [TestUtilities.CreateValidOptionList()]
};
_admissionRepositoryMock
.Setup(repo => repo.FindById(admissionId))
.ReturnsAsync(oldAdmission);
var updatedPointOfCare = TestUtilities.CreateValidPointOfCare();
_pointOfCareServiceMock
.Setup(repo => repo.GetInfo(admission.PointOfCareId.Value, null, false, It.IsAny<CancellationToken>()))
.ReturnsAsync(updatedPointOfCare);
// Act
await _admissionService.UpdateAdmissionAsync(admission);
// Assert
_admissionRepositoryMock.Verify(repo => repo.Update(admission), Times.Once);
_pointOfCareServiceMock.Verify(repo => repo.GetInfo(admission.PointOfCareId.Value, null, false, It.IsAny<CancellationToken>()), Times.Once);
_pointOfCareServiceMock.Verify(repo => repo.GetInfo(oldAdmission.PointOfCareId.Value, null, false, It.IsAny<CancellationToken>()), Times.Once);
}
}
/// <summary>
/// Verifies that admitting a patient with a valid admission—where the associated unit is found and the point of care matches—results in a new patient being inserted.
/// </summary>
[Test]
public async Task GetAdmissionByPointOfCareId_POCNotFound_ReturnsNull()
{
// Arrange
var pocId = ObjectId.GenerateNewId();
_admissionRepositoryMock.Setup(repo => repo.FindByPointOfCareId(pocId))
.ThrowsAsync(new Exception("Repository error"));
// Act
var result = await _admissionService.GetAdmissionByPointOfCareId(pocId);
// Assert
Assert.That(result, Is.Empty);
}
[Test]
public async Task GetAdmissionByUnitIdWithOutPoC_UnitIdExists_ReturnsAdmissions()
{
// Arrange
var unitId = ObjectId.GenerateNewId();
var expectedAdmissions = new List<Admission>
public async Task AdmitPatient_WithValidAdmission_CreatesPatientAndDeletesAdmission()
{
new() { Id = ObjectId.GenerateNewId() },
new() { Id = ObjectId.GenerateNewId() }
};
_admissionRepositoryMock.Setup(repo => repo.GetAdmissionByUnitIdWithOutPoC(unitId))
.ReturnsAsync(expectedAdmissions);
// Act
var result = await _admissionService.GetAdmissionByUnitIdWithOutPoC(unitId);
// Assert
Assert.That(result, Is.Not.Null);
Assert.That(result.Count, Is.EqualTo(expectedAdmissions.Count));
Assert.That(result, Is.EqualTo(expectedAdmissions));
}
// Arrange
var admission = TestUtilities.CreateValidAdmission();
var unit = TestUtilities.CreateValidUnit();
var pointOfCare = TestUtilities.CreateValidPointOfCare();
_unitServiceMock
.Setup(repo => repo.FindById(admission.UnitId))
.ReturnsAsync(unit);
_pointOfCareServiceMock
.Setup(repo => repo.FindById(It.IsAny<ObjectId>()))
.ReturnsAsync((ObjectId id) => id == admission.PointOfCareId ? pointOfCare : null);
// Act
await _admissionService.AdmitPatient(admission);
// Assert
_patientServiceMock.Verify(repo => repo.Insert(It.IsAny<Patient>()), Times.Once);
}
/// <summary>
/// Verifies that GetAdmissionByLocation returns the expected list of admissions when a matching patient location is found, confirming the service correctly retrieves results from the repository.
/// </summary>
[Test]
public async Task GetAdmissionByUnitIdWithOutPoC_UnitIdNotFound_ReturnsNull()
{
// Arrange
var unitId = ObjectId.GenerateNewId();
public async Task GetAdmissionByLocation_LocationExists_ReturnsAdmissions()
{
// Arrange
var location = new PatientLocation
{
UnitName = "Test Unit",
Bed = "Test Bed",
Room = "Test Room"
};
var expectedAdmissions = new List<Admission>
{
new() { Id = ObjectId.GenerateNewId() },
new() { Id = ObjectId.GenerateNewId() }
};
_admissionRepositoryMock.Setup(repo => repo.FindByLocation(location))
.ReturnsAsync(expectedAdmissions);
// Act
var result = await _admissionService.GetAdmissionByLocation(location);
// Assert
Assert.That(result, Is.Not.Null);
Assert.That(result, Is.EqualTo(expectedAdmissions));
}
_admissionRepositoryMock.Setup(repo => repo.GetAdmissionByUnitIdWithOutPoC(unitId))
.ThrowsAsync(new Exception("Repository error"));
/// <summary>
/// Verifies that <see cref="AdmissionService.GetAdmissionByLocation"/> returns an empty result when the requested location does not exist, simulating the not-found scenario by having the repository throw an exception.
/// </summary>
[Test]
public async Task GetAdmissionByLocation_LocationNotFound_ReturnsNull()
{
// Arrange
var location = new PatientLocation
{
UnitName = "Nonexistent Unit",
Bed = "Nonexistent Bed",
Room = "Nonexistent Room"
};
_admissionRepositoryMock.Setup(repo => repo.FindByLocation(location))
.ThrowsAsync(new Exception());
// Act
var result = await _admissionService.GetAdmissionByLocation(location);
// Assert
Assert.That(result, Is.Empty);
}
// Act
var result = await _admissionService.GetAdmissionByUnitIdWithOutPoC(unitId);
/// <summary>
/// Verifies that when a Point of Care exists for the given identifier, the admissions returned by
/// <c>GetAdmissionByPointOfCareId</c> are enriched with patient location details (unit name, bed, and room)
/// retrieved from the corresponding Point of Care entity.
/// </summary>
[Test]
public async Task GetAdmissionByPointOfCareId_POCExists_ReturnsAdmissionsWithLocation()
{
// Arrange
var pocId = ObjectId.GenerateNewId();
var expectedAdmissions = new List<Admission>
{
new() { Id = ObjectId.GenerateNewId() },
new() { Id = ObjectId.GenerateNewId() }
};
var poc = new PointOfCare
{
Id = pocId,
UnitName = "Test Unit",
Bed = "Test Bed",
Room = "Test Room"
};
_admissionRepositoryMock.Setup(repo => repo.FindByPointOfCareId(pocId))
.ReturnsAsync(expectedAdmissions);
_pointOfCareServiceMock.Setup(repo => repo.GetInfo(pocId, null, false, It.IsAny<CancellationToken>()))
.ReturnsAsync(poc);
// Act
var result = await _admissionService.GetAdmissionByPointOfCareId(pocId);
// Assert
Assert.That(result, Is.Not.Null);
Assert.That(result.Count, Is.EqualTo(expectedAdmissions.Count));
foreach (var admission in result)
{
Assert.That(admission.PatientLocation, Is.Not.Null);
Assert.That(admission.PatientLocation?.UnitName, Is.EqualTo(poc.UnitName));
Assert.That(admission.PatientLocation?.Bed, Is.EqualTo(poc.Bed));
Assert.That(admission.PatientLocation?.Room, Is.EqualTo(poc.Room));
}
}
// Assert
Assert.That(result, Is.Empty);
}
/// <summary>
/// Verifies that <see cref="AdmissionService.GetAdmissionByPointOfCareId"/> returns an empty result when the underlying repository throws an exception while attempting to find an admission by point of care id.
/// </summary>
[Test]
public async Task GetAdmissionByPointOfCareId_POCNotFound_ReturnsNull()
{
// Arrange
var pocId = ObjectId.GenerateNewId();
_admissionRepositoryMock.Setup(repo => repo.FindByPointOfCareId(pocId))
.ThrowsAsync(new Exception("Repository error"));
// Act
var result = await _admissionService.GetAdmissionByPointOfCareId(pocId);
// Assert
Assert.That(result, Is.Empty);
}
/// <summary>
/// Verifies that <see cref="AdmissionService.GetAdmissionByUnitIdWithOutPoC"/> returns the expected list of admissions
/// when a valid unit identifier is provided.
/// </summary>
[Test]
public async Task GetAdmissionByUnitIdWithOutPoC_UnitIdExists_ReturnsAdmissions()
{
// Arrange
var unitId = ObjectId.GenerateNewId();
var expectedAdmissions = new List<Admission>
{
new() { Id = ObjectId.GenerateNewId() },
new() { Id = ObjectId.GenerateNewId() }
};
_admissionRepositoryMock.Setup(repo => repo.GetAdmissionByUnitIdWithOutPoC(unitId))
.ReturnsAsync(expectedAdmissions);
// Act
var result = await _admissionService.GetAdmissionByUnitIdWithOutPoC(unitId);
// Assert
Assert.That(result, Is.Not.Null);
Assert.That(result.Count, Is.EqualTo(expectedAdmissions.Count));
Assert.That(result, Is.EqualTo(expectedAdmissions));
}
/// <summary>
/// Verifies that <c>GetAdmissionByUnitIdWithOutPoC</c> returns an empty result when the underlying repository
/// throws an exception while looking up the given unit identifier.
/// </summary>
/// <param name="unitId">The identifier of the unit whose admission record is being requested.</param>
[Test]
public async Task GetAdmissionByUnitIdWithOutPoC_UnitIdNotFound_ReturnsNull()
{
// Arrange
var unitId = ObjectId.GenerateNewId();
_admissionRepositoryMock.Setup(repo => repo.GetAdmissionByUnitIdWithOutPoC(unitId))
.ThrowsAsync(new Exception("Repository error"));
// Act
var result = await _admissionService.GetAdmissionByUnitIdWithOutPoC(unitId);
// Assert
Assert.That(result, Is.Empty);
}
// [Test]
// public async Task SaveRequest_NewAdmission_ValidRequest_InsertsAdmission()
@@ -523,52 +573,55 @@ public class AdmissionServiceTest
// admissionServiceMock.Verify(service => service.SaveRequest(apiRequest), Times.Once);
// }
/// <summary>
/// Verifies that InsertAdmission throws a NotFoundException and performs no insert or update operations when a required associated resource is not found while processing a valid admission.
/// </summary>
[Test]
public async Task InsertAdmission_ValidAdmission_InsertsSuccessfullyWithUser()
{
// Arrange
var admission = new Admission
public async Task InsertAdmission_ValidAdmission_InsertsSuccessfullyWithUser()
{
Id = ObjectId.GenerateNewId(),
AdmissionDate = DateTime.UtcNow,
Nhc = "TestNhc",
PointOfCareId = ObjectId.GenerateNewId(),
Person = new Person(),
Origin = new OptionList(),
Diagnosis = new OptionList(),
Allergies = [new OptionList()],
Insulation = new OptionList(),
LanguageBarrier = [new OptionList()]
};
var pointOfCare = TestUtilities.CreateValidPointOfCare();
_admissionRepositoryMock
.Setup(repo => repo.FindByNhc(admission.Nhc))
.ReturnsAsync((Admission?)null);
_pointOfCareServiceMock
.Setup(repo => repo.GetInfo(admission.PointOfCareId.Value, null, false, It.IsAny<CancellationToken>()))
.ReturnsAsync(pointOfCare);
_admissionRepositoryMock
.Setup(repo => repo.InsertOneAsyncAndReturn(admission))
.ReturnsAsync(admission);
Func<Task> act = () => _admissionService.InsertAdmission(admission);
var ex = Assert.ThrowsAsync<NotFoundException>(act);
Assert.That(ex!.Message,
Is.EqualTo(((int)HttpEnum.ErrorMessage.NotFoundResourceMissing).ToString()));
// Verify no insert
_admissionRepositoryMock
.Verify(repo => repo.InsertOneAsyncAndReturn(It.IsAny<Admission>()), Times.Never);
// Verify no update
_pointOfCareServiceMock
.Verify(service => service.Update(It.IsAny<PointOfCare>()), Times.Never);
}
// Arrange
var admission = new Admission
{
Id = ObjectId.GenerateNewId(),
AdmissionDate = DateTime.UtcNow,
Nhc = "TestNhc",
PointOfCareId = ObjectId.GenerateNewId(),
Person = new Person(),
Origin = new OptionList(),
Diagnosis = new OptionList(),
Allergies = [new OptionList()],
Insulation = new OptionList(),
LanguageBarrier = [new OptionList()]
};
var pointOfCare = TestUtilities.CreateValidPointOfCare();
_admissionRepositoryMock
.Setup(repo => repo.FindByNhc(admission.Nhc))
.ReturnsAsync((Admission?)null);
_pointOfCareServiceMock
.Setup(repo => repo.GetInfo(admission.PointOfCareId.Value, null, false, It.IsAny<CancellationToken>()))
.ReturnsAsync(pointOfCare);
_admissionRepositoryMock
.Setup(repo => repo.InsertOneAsyncAndReturn(admission))
.ReturnsAsync(admission);
Func<Task> act = () => _admissionService.InsertAdmission(admission);
var ex = Assert.ThrowsAsync<NotFoundException>(act);
Assert.That(ex!.Message,
Is.EqualTo(((int)HttpEnum.ErrorMessage.NotFoundResourceMissing).ToString()));
// Verify no insert
_admissionRepositoryMock
.Verify(repo => repo.InsertOneAsyncAndReturn(It.IsAny<Admission>()), Times.Never);
// Verify no update
_pointOfCareServiceMock
.Verify(service => service.Update(It.IsAny<PointOfCare>()), Times.Never);
}
}
+140 -120
View File
@@ -14,6 +14,9 @@ using Options = Microsoft.Extensions.Options.Options;
namespace adas_core.Test.Services;
/// <summary>
/// Contains unit tests for verifying the behavior and functionality of the <see cref="AlarmService"/> class.
/// </summary>
public class AlarmServiceTest
{
private readonly Mock<IAlarmRepository> _alarmRepositoryMock;
@@ -111,137 +114,154 @@ public class AlarmServiceTest
}
/// <summary>
/// Verifies that when an ORU_R40 type request is processed without any alarms, the patient is located but the observation is not persisted.
/// </summary>
[Test]
public async Task SaveRequest_Oru_R40_Type_Without_Alarms_Dont_Insert()
{
// Arrange
var apiRequest = new ApiRequest
public async Task SaveRequest_Oru_R40_Type_Without_Alarms_Dont_Insert()
{
Type = "ORU_R40",
PatientNumber = "12345",
Observation = new PatientObservation { Value = "Test" },
Alarms = [],
Location = new PatientLocation { Bed = "Bed1", Room = "Bed1", UnitName = "Unit1" }
};
var unit = new Unit { Configuration = new UnitConfiguration { AutoAdt = true } };
var patient = new Patient { Id = ObjectId.GenerateNewId(), Bed = "Bed1" };
_unitServiceMock.Setup(u => u.FindByUnitNameOrPocName(It.IsAny<string>(), It.IsAny<string>()))
.ReturnsAsync(unit);
_patientServiceMock.Setup(p => p.FindPatientByApiRequest(apiRequest)).ReturnsAsync(patient);
_observationServiceMock.Setup(o => o.SaveRequestAsync(It.IsAny<ApiRequest>())).Returns(Task.CompletedTask);
// Act
await _alarmService.SaveRequest(apiRequest);
// Assert
_patientServiceMock.Verify(p => p.FindPatientByApiRequest(apiRequest), Times.Once);
_observationServiceMock.Verify(o => o.SaveRequestAsync(It.IsAny<ApiRequest>()), Times.Never);
}
// Arrange
var apiRequest = new ApiRequest
{
Type = "ORU_R40",
PatientNumber = "12345",
Observation = new PatientObservation { Value = "Test" },
Alarms = [],
Location = new PatientLocation { Bed = "Bed1", Room = "Bed1", UnitName = "Unit1" }
};
var unit = new Unit { Configuration = new UnitConfiguration { AutoAdt = true } };
var patient = new Patient { Id = ObjectId.GenerateNewId(), Bed = "Bed1" };
_unitServiceMock.Setup(u => u.FindByUnitNameOrPocName(It.IsAny<string>(), It.IsAny<string>()))
.ReturnsAsync(unit);
_patientServiceMock.Setup(p => p.FindPatientByApiRequest(apiRequest)).ReturnsAsync(patient);
_observationServiceMock.Setup(o => o.SaveRequestAsync(It.IsAny<ApiRequest>())).Returns(Task.CompletedTask);
// Act
await _alarmService.SaveRequest(apiRequest);
// Assert
_patientServiceMock.Verify(p => p.FindPatientByApiRequest(apiRequest), Times.Once);
_observationServiceMock.Verify(o => o.SaveRequestAsync(It.IsAny<ApiRequest>()), Times.Never);
}
/// <summary>
/// Verifies that <see cref="AlarmService.ProcessAlarmObservations"/> inserts the provided alarm observations into the alarm repository.
/// </summary>
[Test]
public async Task ProcessAlarmObservations_Should_Insert_Alarm_Observations()
{
// Arrange
var patient = new Patient { Id = ObjectId.GenerateNewId(), Bed = "Bed1" };
var alarmObservations = new List<PatientObservationAlarm>
public async Task ProcessAlarmObservations_Should_Insert_Alarm_Observations()
{
new() { Value = "Alarm1", Time = DateTime.UtcNow}
};
var observations = new List<PatientObservation>
{
new() { Value = "Alarm1", Time = DateTime.MinValue}
};
_configObservationServiceMock.Setup(o => o.Map(It.IsAny<PatientObservationAlarm>(), It.IsAny<bool>()))
.ReturnsAsync((PatientObservationAlarm obs, bool _) => obs); // Correct setup
_calculatedObservationsServiceMock.Setup(o => o.Map(It.IsAny<PatientObservationAlarm>(), It.IsAny<bool>()))
.ReturnsAsync((PatientObservationAlarm obs, bool _) => obs); // Correct setup
// Act
await _alarmService.ProcessAlarmObservations(alarmObservations, observations, patient, DateTime.UtcNow);
// Assert
_alarmRepositoryMock.Verify(a => a.InsertOneAsync(It.IsAny<PatientObservationAlarm>()), Times.Once);
}
// Arrange
var patient = new Patient { Id = ObjectId.GenerateNewId(), Bed = "Bed1" };
var alarmObservations = new List<PatientObservationAlarm>
{
new() { Value = "Alarm1", Time = DateTime.UtcNow}
};
var observations = new List<PatientObservation>
{
new() { Value = "Alarm1", Time = DateTime.MinValue}
};
_configObservationServiceMock.Setup(o => o.Map(It.IsAny<PatientObservationAlarm>(), It.IsAny<bool>()))
.ReturnsAsync((PatientObservationAlarm obs, bool _) => obs); // Correct setup
_calculatedObservationsServiceMock.Setup(o => o.Map(It.IsAny<PatientObservationAlarm>(), It.IsAny<bool>()))
.ReturnsAsync((PatientObservationAlarm obs, bool _) => obs); // Correct setup
// Act
await _alarmService.ProcessAlarmObservations(alarmObservations, observations, patient, DateTime.UtcNow);
// Assert
_alarmRepositoryMock.Verify(a => a.InsertOneAsync(It.IsAny<PatientObservationAlarm>()), Times.Once);
}
/// <summary>
/// Verifies that when checking observation alarms for a patient observation with an "Event_PEEP_Low" configuration
/// (coding system "ADAS_EVENT", description "EVT_LO and EVT_EXTR_LO"), the alarm service does not insert a new observation,
/// as indicated by the verification that <c>InsertObservation</c> is never invoked.
/// </summary>
[Test]
public async Task CheckObservationAlarm_Alarm_EventPEEP_PEEP_bajo_create_Event()
{
var obs = new PatientObservation();
var configs = new ConfigObservation
public async Task CheckObservationAlarm_Alarm_EventPEEP_PEEP_bajo_create_Event()
{
Id = ObjectId.GenerateNewId(),
Name = "Event_PEEP_Low",
CodingSystem = "ADAS_EVENT",
Description = "EVT_LO and EVT_EXTR_LO"
};
_configObservationServiceMock.Setup(c => c.Get(It.IsAny<PatientObservation>(), false))
.ReturnsAsync(configs);
await _alarmService.CheckObservationAlarm(obs);
_observationServiceMock.Verify(o => o.InsertObservation(obs, true, true), Times.Never);
}
var obs = new PatientObservation();
var configs = new ConfigObservation
{
Id = ObjectId.GenerateNewId(),
Name = "Event_PEEP_Low",
CodingSystem = "ADAS_EVENT",
Description = "EVT_LO and EVT_EXTR_LO"
};
_configObservationServiceMock.Setup(c => c.Get(It.IsAny<PatientObservation>(), false))
.ReturnsAsync(configs);
await _alarmService.CheckObservationAlarm(obs);
_observationServiceMock.Verify(o => o.InsertObservation(obs, true, true), Times.Never);
}
/// <summary>
/// Verifies that <see cref="AlarmService.CheckObservationAlarm"/> processes a patient observation
/// matching a configured rule (name "EVT_LO" with required value "PEEP") by inserting a new
/// observation with the original value preserved (e.g., "PEEP High") while the alarm
/// configuration handles the red beacon signaling.
/// </summary>
[Test]
public async Task CheckObservationAlarm_Should_Launch_Red_Alarm_And_Set_BeaconColor_Red()
{
var alarmConfig = new AlarmConfig
public async Task CheckObservationAlarm_Should_Launch_Red_Alarm_And_Set_BeaconColor_Red()
{
Enabled = true,
Priority = 10,
Beacon = new AlarmItem
var alarmConfig = new AlarmConfig
{
Enabled = true,
BeaconColor = AlarmEnum.BeaconColor.Red, // Using the enum value
EndAfter = 60 // Assuming the alarm should end after 60 seconds
}
};
var configs = new ConfigObservation
{
Id = ObjectId.GenerateNewId(),
Name = "Event_PEEP_Low",
CodingSystem = "ADAS_EVENT",
Description = "logs an event. No generate an alarm.",
Alarm = alarmConfig,
RequiredValue = "PEEP"
};
var obsConfig = new ConfigObservation
{
Id = ObjectId.GenerateNewId(),
Name = "EVT_LO",
CheckObservations = true,
CreateObservation = [configs]
};
var patId = ObjectId.GenerateNewId();
// Arrange
var obs = new PatientObservation
{
Id = ObjectId.GenerateNewId(),
Name = "EVT_LO",
PatientId = patId,
Value = "PEEP High",
Time = DateTime.UtcNow,
CheckObservations = true,
CreateObservation = [configs]
};
_configObservationServiceMock.Setup(c => c.Get(It.IsAny<PatientObservation>(), false))
.ReturnsAsync(obsConfig);
// Act
await _alarmService.CheckObservationAlarm(obs);
_observationServiceMock.Verify(
o => o.InsertObservation(
It.Is<PatientObservation>(ob => ob.Value.ToString() == "PEEP High"), true, true),
Times.Once);
}
Priority = 10,
Beacon = new AlarmItem
{
Enabled = true,
BeaconColor = AlarmEnum.BeaconColor.Red, // Using the enum value
EndAfter = 60 // Assuming the alarm should end after 60 seconds
}
};
var configs = new ConfigObservation
{
Id = ObjectId.GenerateNewId(),
Name = "Event_PEEP_Low",
CodingSystem = "ADAS_EVENT",
Description = "logs an event. No generate an alarm.",
Alarm = alarmConfig,
RequiredValue = "PEEP"
};
var obsConfig = new ConfigObservation
{
Id = ObjectId.GenerateNewId(),
Name = "EVT_LO",
CheckObservations = true,
CreateObservation = [configs]
};
var patId = ObjectId.GenerateNewId();
// Arrange
var obs = new PatientObservation
{
Id = ObjectId.GenerateNewId(),
Name = "EVT_LO",
PatientId = patId,
Value = "PEEP High",
Time = DateTime.UtcNow,
CheckObservations = true,
CreateObservation = [configs]
};
_configObservationServiceMock.Setup(c => c.Get(It.IsAny<PatientObservation>(), false))
.ReturnsAsync(obsConfig);
// Act
await _alarmService.CheckObservationAlarm(obs);
_observationServiceMock.Verify(
o => o.InsertObservation(
It.Is<PatientObservation>(ob => ob.Value.ToString() == "PEEP High"), true, true),
Times.Once);
}
}
+106 -43
View File
@@ -14,12 +14,32 @@ namespace adas_core.Test.Services;
/// <summary>
/// Provides a fake implementation of the <see cref="ILockProvider"/> interface, typically used as a test double or non-functional placeholder.
/// </summary>
public class FakeLockProvider : ILockProvider
{
/// <summary>
/// Asynchronously attempts to acquire a resource identified by the specified key within the given timeout.
/// This implementation always reports a successful acquisition by returning <c>true</c>.
/// </summary>
/// <param name="key">The identifier of the resource to acquire.</param>
/// <param name="timeout">The maximum time to wait for the resource to become available.</param>
/// <returns>A <see cref="Task{TResult}"/> that always completes with <c>true</c>, indicating the resource was acquired.</returns>
public Task<bool> AcquireAsync(string key, TimeSpan timeout) => Task.FromResult(true);
/// <summary>
/// Releases the resource associated with the specified key. This implementation completes immediately without performing any additional operation.
/// </summary>
/// <param name="key">The identifier of the resource to release.</param>
public Task ReleaseAsync(string key) => Task.CompletedTask;
}
/// <summary>
/// Represents a fake implementation of <see cref="LockManagerService"/>, typically used as a test double to provide controlled or simplified behavior in unit tests.
/// </summary>
/// <remarks>
/// Inherits all members from <see cref="LockManagerService"/> and is intended to be used in place of the real service when actual locking functionality is not required.
/// </remarks>
public class FakeLockManagerService : LockManagerService
{
public FakeLockManagerService() : base(
@@ -30,6 +50,12 @@ public class FakeLockManagerService : LockManagerService
}
/// <summary>
/// Represents a fake or test double implementation of <see cref="RedisService"/> that also implements <see cref="ICacheService"/>, typically used to simulate Redis caching behavior in testing scenarios.
/// </summary>
/// <remarks>
/// This class combines the inheritance of <see cref="RedisService"/> with the contract of <see cref="ICacheService"/>, allowing it to stand in for a real Redis-backed cache during unit tests or development.
/// </remarks>
public class FakeRedisService : RedisService, ICacheService
{
public bool WasCalled { get; private set; }
@@ -43,30 +69,55 @@ public class FakeRedisService : RedisService, ICacheService
}
/// <summary>
/// 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.
/// </summary>
/// <param name="key">The cache key used to identify the stored object.</param>
/// <param name="factory">A delegate that asynchronously produces the object to cache when no entry exists for the specified key.</param>
/// <param name="ttl">An optional time-to-live duration for the cached entry. Not used in this implementation.</param>
/// <param name="cancellationToken">A token to observe for cancellation requests. Not used in this implementation.</param>
/// <returns>A task that represents the asynchronous operation, containing the object produced by the factory.</returns>
Task<T> ICacheService.GetOrSetObjectAsync<T>(
string key,
Func<Task<T>> factory,
TimeSpan? ttl,
CancellationToken cancellationToken)
{
WasCalled = true;
LastKey = key;
return factory();
}
string key,
Func<Task<T>> factory,
TimeSpan? ttl,
CancellationToken cancellationToken)
{
WasCalled = true;
LastKey = key;
return factory();
}
/// <summary>
/// Retrieves or sets a cached object associated with the specified grouped field and patient identifier.
/// Marks the call as executed via the <c>WasCalled</c> flag and returns the value produced by the supplied factory delegate.
/// </summary>
/// <param name="groupedField">The grouped field used to identify the cached object.</param>
/// <param name="patientId">The identifier of the patient associated with the cached object.</param>
/// <param name="factory">The asynchronous factory delegate invoked to produce the value when no cached entry exists.</param>
/// <param name="ttl">An optional time-to-live duration for the cached entry.</param>
/// <param name="cancellationToken">The token used to cancel the asynchronous operation.</param>
/// <returns>The value of type <typeparamref name="T"/> produced by the <paramref name="factory"/> delegate.</returns>
Task<T> ICacheService.GetOrSetObjectAsync<T>(
GroupedField groupedField,
ObjectId patientId,
Func<Task<T>> factory,
TimeSpan? ttl,
CancellationToken cancellationToken)
{
WasCalled = true;
return factory();
}
GroupedField groupedField,
ObjectId patientId,
Func<Task<T>> factory,
TimeSpan? ttl,
CancellationToken cancellationToken)
{
WasCalled = true;
return factory();
}
}
/// <summary>
/// Represents a fake implementation of <see cref="CacheService"/> that also implements the <see cref="ICacheService"/> interface, typically used for testing or stubbing scenarios.
/// </summary>
/// <remarks>
/// This class combines inheritance from the concrete <see cref="CacheService"/> base class with the <see cref="ICacheService"/> contract, allowing it to be used wherever an <see cref="ICacheService"/> is required.
/// </remarks>
public class FakeCacheService : CacheService, ICacheService
{
public bool WasCalled { get; private set; }
@@ -100,6 +151,12 @@ public class FakeCacheService : CacheService, ICacheService
}
/// <summary>
/// Represents a fake implementation of the cache service, used for testing or scenarios where a no-op cache behavior is required.
/// </summary>
/// <remarks>
/// This class inherits from <see cref="NoCacheService"/> and implements the <see cref="ICacheService"/> interface, providing a non-functional cache suitable for unit tests or environments where caching should be bypassed.
/// </remarks>
public class FakeNoCacheService : NoCacheService, ICacheService
{
public bool WasCalled { get; private set; }
@@ -138,21 +195,24 @@ public class CacheDispatcherTest
private CacheSettings _cacheSettings = null!;
private CacheDispatcher _cacheDispatcher = null!;
/// <summary>
/// Initializes the test environment by instantiating fake implementations of the Redis, in-memory, and no-op cache services, along with default <see cref="CacheSettings"/>, and constructs a <see cref="CacheDispatcher"/> under test using these dependencies.
/// </summary>
[SetUp]
public void SetUp()
{
_redisServiceFake = new FakeRedisService();
_memoryServiceFake = new FakeCacheService();
_noopServiceFake = new FakeNoCacheService();
_cacheSettings = new CacheSettings();
_cacheDispatcher = new CacheDispatcher(
_redisServiceFake,
_memoryServiceFake,
_noopServiceFake,
_cacheSettings);
}
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
@@ -240,17 +300,20 @@ public class CacheDispatcherTest
Assert.That(_noopServiceFake.WasCalled, Is.False, "NoCacheService NO debería haber sido invocado");
}
/// <summary>
/// Verifies that <see cref="CacheKeyClassifier.Classify"/> returns <see cref="CacheEnum.EntityType.Unknown"/> when the provided cache key does not start with any recognized prefix.
/// </summary>
[Test]
public void Classify_ReturnsUnknown_ForUnrecognizedPrefix()
{
// Arrange
var key = "unknown:prefix:key";
// Act
var result = CacheKeyClassifier.Classify(key);
// Assert
Assert.That(result, Is.EqualTo(CacheEnum.EntityType.Unknown));
}
public void Classify_ReturnsUnknown_ForUnrecognizedPrefix()
{
// Arrange
var key = "unknown:prefix:key";
// Act
var result = CacheKeyClassifier.Classify(key);
// Assert
Assert.That(result, Is.EqualTo(CacheEnum.EntityType.Unknown));
}
#endregion
}
+174 -149
View File
@@ -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
}
+46 -40
View File
@@ -11,49 +11,55 @@ internal class CameraServiceTest
//Dictionary<string, object> cameraSettings;
/// <summary>
/// NUnit <see cref="SetUpAttribute"/> method executed before each test to initialize shared test state, such as camera configuration dictionaries, mocked camera and logger services, and a mocked subscribers service. Currently all initialization logic is commented out, so the method performs no setup actions.
/// </summary>
[SetUp]
public void Setup()
{
//cameraSettings = new Dictionary<string, object>()
//{
// { "camHost", "http://localhost:8080" },
// { "camUser", "username" },
// { "camPassword", "password" }
//};
//cameraServiceMock = new Mock<CameraService>();
//logger = new Mock<ILogger<CameraService>>();
//cameraService = new CameraService(
// logger.Object
// );
// Create a mock of the singleton class subscribers
//var mockSingleton = new Mock<ISubscribersService>();
//var subscribers = new List<WsSubscriber>();
// Set up the mock object to return a specific value when a method is called
//mockSingleton.Setup(x => x.GetSubscribers()).Returns(subscribers);
}
public void Setup()
{
//cameraSettings = new Dictionary<string, object>()
//{
// { "camHost", "http://localhost:8080" },
// { "camUser", "username" },
// { "camPassword", "password" }
//};
//cameraServiceMock = new Mock<CameraService>();
//logger = new Mock<ILogger<CameraService>>();
//cameraService = new CameraService(
// logger.Object
// );
// Create a mock of the singleton class subscribers
//var mockSingleton = new Mock<ISubscribersService>();
//var subscribers = new List<WsSubscriber>();
// Set up the mock object to return a specific value when a method is called
//mockSingleton.Setup(x => x.GetSubscribers()).Returns(subscribers);
}
//TODO HttpWebResponse test
//[Test]
/// <summary>
/// Verifies that the <c>MaskStream</c> operation on the camera service returns a valid response when invoked with activation and coordinate parameters against the configured camera settings. Covers the scenario where the underlying grab response is null, ensuring the service still produces a non-null response with the expected status code and content.
/// </summary>
public void MaskStream_Return_Ok()
{
//bool activate = true;
//string coordinates = "100,100";
//var expectedResponse = new HttpResponseMessage();
////cameraServiceMock.Setup(c => c.GrabResponse(It.IsAny<string>(), It.IsAny<string>())).Returns(((string)null));
//// Act
//var response = cameraService.MaskStream(activate, coordinates, cameraSettings);
//// Assert
//Assert.IsNotNull(response);
//Assert.AreEqual(expectedResponse.StatusCode, response.StatusCode);
//Assert.AreEqual(expectedResponse.Content, response.Content);
}
{
//bool activate = true;
//string coordinates = "100,100";
//var expectedResponse = new HttpResponseMessage();
////cameraServiceMock.Setup(c => c.GrabResponse(It.IsAny<string>(), It.IsAny<string>())).Returns(((string)null));
//// Act
//var response = cameraService.MaskStream(activate, coordinates, cameraSettings);
//// Assert
//Assert.IsNotNull(response);
//Assert.AreEqual(expectedResponse.StatusCode, response.StatusCode);
//Assert.AreEqual(expectedResponse.Content, response.Content);
}
}
@@ -73,309 +73,373 @@ public class ConfigObservationServiceTest
new() { Id = ObjectId.GenerateNewId(), Name = "Alarm_BlueCode", CodingSystem = "ADAS_EVENT" }
];
/// <summary>
/// Initializes the test fixture by creating mock instances of all dependencies required by <see cref="ConfigObservationService"/>,
/// including the repository, unit service, audit service, HTTP context accessor, cache service, and logger.
/// Configures a default authenticated <see cref="ClaimsPrincipal"/> in the HTTP context and sets the cache mocks to bypass caching by executing the factory function directly,
/// ensuring repository calls are invoked during tests.
/// </summary>
[SetUp]
public void Setup()
{
_repo = new Mock<IConfigObservationRepository>();
_unitSvc = new Mock<IUnitService>();
_auditSvc = new Mock<ILocalAuditService>();
_http = new Mock<IHttpContextAccessor>();
_cache = new Mock<ICacheService>();
_logger = new Mock<ILogger<ConfigObservationService>>();
var user = new ClaimsPrincipal(
new ClaimsIdentity([new Claim(ClaimTypes.Name, "TestUser")], "mock"));
_http.Setup(x => x.HttpContext).Returns(new DefaultHttpContext { User = user });
_settings = Options.Create(new ApiSettings
public void Setup()
{
ConfigObservation = new ConfigObservationSettings
_repo = new Mock<IConfigObservationRepository>();
_unitSvc = new Mock<IUnitService>();
_auditSvc = new Mock<ILocalAuditService>();
_http = new Mock<IHttpContextAccessor>();
_cache = new Mock<ICacheService>();
_logger = new Mock<ILogger<ConfigObservationService>>();
var user = new ClaimsPrincipal(
new ClaimsIdentity([new Claim(ClaimTypes.Name, "TestUser")], "mock"));
_http.Setup(x => x.HttpContext).Returns(new DefaultHttpContext { User = user });
_settings = Options.Create(new ApiSettings
{
IgnoreUnknownObservation = false,
Refresh = null
},
});
_cacheSettings = Options.Create(new CacheSettings());
// KEY: mock cache to execute repository calls
_cache.Setup(c => c.GetOrSetObjectAsync(
It.IsAny<string>(),
It.IsAny<Func<Task<ICollection<ConfigObservation>>>>(),
It.IsAny<TimeSpan?>(),
It.IsAny<CancellationToken>()))
.Returns((string _, Func<Task<ICollection<ConfigObservation>>> f, TimeSpan? __, CancellationToken ___) => f());
_cache.Setup(c => c.GetOrSetObjectAsync(
It.IsAny<string>(),
It.IsAny<Func<Task<ConfigObservation>>>(),
It.IsAny<TimeSpan?>(),
It.IsAny<CancellationToken>()))
.Returns((string _, Func<Task<ConfigObservation>> f, TimeSpan? __, CancellationToken ___) => f());
_service = new ConfigObservationService(
_repo.Object,
_settings,
_cacheSettings,
_logger.Object,
_unitSvc.Object,
_http.Object,
_auditSvc.Object,
_cache.Object);
_repo.Setup(x => x.FindAll()).ReturnsAsync([ConfigList]);
}
ConfigObservation = new ConfigObservationSettings
{
IgnoreUnknownObservation = false,
Refresh = null
},
});
_cacheSettings = Options.Create(new CacheSettings());
// KEY: mock cache to execute repository calls
_cache.Setup(c => c.GetOrSetObjectAsync(
It.IsAny<string>(),
It.IsAny<Func<Task<ICollection<ConfigObservation>>>>(),
It.IsAny<TimeSpan?>(),
It.IsAny<CancellationToken>()))
.Returns((string _, Func<Task<ICollection<ConfigObservation>>> f, TimeSpan? __, CancellationToken ___) => f());
_cache.Setup(c => c.GetOrSetObjectAsync(
It.IsAny<string>(),
It.IsAny<Func<Task<ConfigObservation>>>(),
It.IsAny<TimeSpan?>(),
It.IsAny<CancellationToken>()))
.Returns((string _, Func<Task<ConfigObservation>> f, TimeSpan? __, CancellationToken ___) => f());
_service = new ConfigObservationService(
_repo.Object,
_settings,
_cacheSettings,
_logger.Object,
_unitSvc.Object,
_http.Object,
_auditSvc.Object,
_cache.Object);
_repo.Setup(x => x.FindAll()).ReturnsAsync([ConfigList]);
}
// ---------------------------------------------------------
// TESTS
// ---------------------------------------------------------
/// <summary>
/// Verifies that querying a patient observation by <c>CodingSystem</c> only, without specifying a code or name, returns a null result.
/// </summary>
[Test]
public async Task Get_config_observation_item_by_codingSystem_only_return_null()
{
var obs = new PatientObservation
public async Task Get_config_observation_item_by_codingSystem_only_return_null()
{
CodingSystem = "ADAS_EVENT",
Code = "Pump_X",
Name = "Alarm_Pump_X"
};
var result = await _service.Get(obs);
Assert.That(result, Is.Null);
}
[Test]
public async Task Get_config_observation_item_by_code_and_codingSystem()
{
_repo.Setup(x => x.FindAll()).ReturnsAsync(_allConfigList);
var obs = new BasePatientObservation
{
Name = "Hemo^GBr",
Code = "12345",
CodingSystem = "SNM"
};
var result = await _service.Get(obs);
Assert.That(result, Is.Not.Null);
Assert.That(result!.Name, Is.EqualTo("Hemoglobina"));
}
[Test]
public async Task Get_config_observation_item_by_name()
{
_repo.Setup(x => x.FindAll()).ReturnsAsync(_allConfigList);
var obs = new BasePatientObservation { Name = "Hemoglobina" };
var result = await _service.Get(obs);
Assert.That(result!.Name, Is.EqualTo("Hemoglobina"));
}
[Test]
public async Task Get_config_observation_item_by_name_not_exist_returns_null()
{
_repo.Setup(x => x.FindAll()).ReturnsAsync(_allConfigList);
var obs = new BasePatientObservation { Name = "XXX" };
var result = await _service.Get(obs);
Assert.That(result, Is.Null);
}
[Test]
public async Task Get_config_by_code_and_parent()
{
_repo.Setup(x => x.FindAll()).ReturnsAsync(_allConfigList);
var obs = new BasePatientObservation
{
Code = "555",
CodingSystem = "MG4",
ParentData = new ParentDataClass
var obs = new PatientObservation
{
Code = "3333",
CodingSystem = "ADAS_EVENT",
Code = "Pump_X",
Name = "Alarm_Pump_X"
};
var result = await _service.Get(obs);
Assert.That(result, Is.Null);
}
/// <summary>
/// Verifies that <c>Get</c> returns the matching patient observation configuration for a given observation identified by its code and coding system.
/// Asserts that the resolved item is not null and that the displayed name is mapped from the configured lookup to the expected localized value.
/// </summary>
[Test]
public async Task Get_config_observation_item_by_code_and_codingSystem()
{
_repo.Setup(x => x.FindAll()).ReturnsAsync(_allConfigList);
var obs = new BasePatientObservation
{
Name = "Hemo^GBr",
Code = "12345",
CodingSystem = "SNM"
}
};
var result = await _service.Get(obs);
Assert.That(result!.Name, Is.EqualTo("ph"));
}
};
var result = await _service.Get(obs);
Assert.That(result, Is.Not.Null);
Assert.That(result!.Name, Is.EqualTo("Hemoglobina"));
}
/// <summary>
/// Verifies that the service retrieves the correct configuration observation item by its name,
/// matching the requested observation against the list returned by the repository and returning
/// the item with the expected name.
/// </summary>
[Test]
public async Task Map_unknown_ignore_true_returns_null()
{
_settings.Value.ConfigObservation!.IgnoreUnknownObservation = true;
_repo.Setup(x => x.FindAll()).ReturnsAsync([]);
var result = await _service.Map(new BasePatientObservation { Name = "XX" });
Assert.That(result, Is.Null);
}
[Test]
public async Task Map_unknown_ignore_false_returns_obs()
{
_settings.Value.ConfigObservation!.IgnoreUnknownObservation = false;
_repo.Setup(x => x.FindAll()).ReturnsAsync([]);
var obs = new BasePatientObservation { Name = "XX" };
var result = await _service.Map(obs);
Assert.That(result, Is.EqualTo(obs));
}
[Test]
public async Task Map_threshold_Ok()
{
_repo.Setup(x => x.FindAll()).ReturnsAsync(_allConfigList);
var obs = new PatientObservation
public async Task Get_config_observation_item_by_name()
{
Name = "SOFA",
CodingSystem = "SNM",
Value = 14
};
var result = await _service.Map(obs);
Assert.That(result!.Status, Is.EqualTo(StatusEnum.Type.Ok));
}
_repo.Setup(x => x.FindAll()).ReturnsAsync(_allConfigList);
var obs = new BasePatientObservation { Name = "Hemoglobina" };
var result = await _service.Get(obs);
Assert.That(result!.Name, Is.EqualTo("Hemoglobina"));
}
/// <summary>
/// Verifies that the service returns null when a patient observation item is requested by a name that does not exist in the configuration list.
/// </summary>
[Test]
public async Task Map_threshold_Warning()
{
_repo.Setup(x => x.FindAll()).ReturnsAsync(_allConfigList);
var obs = new PatientObservation
public async Task Get_config_observation_item_by_name_not_exist_returns_null()
{
Name = "SOFA",
CodingSystem = "SNM",
Value = 13
};
var result = await _service.Map(obs);
Assert.That(result!.Status, Is.EqualTo(StatusEnum.Type.Warning));
}
_repo.Setup(x => x.FindAll()).ReturnsAsync(_allConfigList);
var obs = new BasePatientObservation { Name = "XXX" };
var result = await _service.Get(obs);
Assert.That(result, Is.Null);
}
/// <summary>
/// Verifies that the service retrieves the correct configuration by matching both the observation code with its coding system and the parent data code with its coding system, returning the configuration named "ph".
/// </summary>
[Test]
public async Task Map_threshold_Alert()
{
_repo.Setup(x => x.FindAll()).ReturnsAsync(_allConfigList);
var obs = new PatientObservation
public async Task Get_config_by_code_and_parent()
{
Name = "SOFA",
CodingSystem = "SNM",
Value = 9
};
var result = await _service.Map(obs);
Assert.That(result!.Status, Is.EqualTo(StatusEnum.Type.Alert));
}
_repo.Setup(x => x.FindAll()).ReturnsAsync(_allConfigList);
var obs = new BasePatientObservation
{
Code = "555",
CodingSystem = "MG4",
ParentData = new ParentDataClass
{
Code = "3333",
CodingSystem = "SNM"
}
};
var result = await _service.Get(obs);
Assert.That(result!.Name, Is.EqualTo("ph"));
}
/// <summary>
/// Verifies that the mapping service returns <c>null</c> when the observation name is unknown
/// and the configuration is set to ignore unknown observations.
/// </summary>
[Test]
public async Task Map_parent_ok()
{
_repo.Setup(x => x.FindById(Id)).ReturnsAsync(ConfigList);
var fc = new PatientObservation
public async Task Map_unknown_ignore_true_returns_null()
{
Id = Id,
Name = "FC",
Value = 70
};
var result = await _service.Map(fc);
Assert.That(result!.Status, Is.EqualTo(StatusEnum.Type.Ok));
}
_settings.Value.ConfigObservation!.IgnoreUnknownObservation = true;
_repo.Setup(x => x.FindAll()).ReturnsAsync([]);
var result = await _service.Map(new BasePatientObservation { Name = "XX" });
Assert.That(result, Is.Null);
}
/// <summary>
/// Verifies that when ConfigObservation.IgnoreUnknownObservation is set to <c>false</c>,
/// the <c>Map</c> method returns the original observation as-is when no matching record is found
/// in the repository, rather than discarding it as an unknown observation.
/// </summary>
[Test]
public async Task UpdateConfig_ok()
{
var newCfg = new ConfigObservation
public async Task Map_unknown_ignore_false_returns_obs()
{
Id = Id,
Name = "New",
Code = "X",
CodingSystem = "S"
};
_repo.Setup(r => r.FindById(Id)).ReturnsAsync(ConfigList);
_repo.Setup(r => r.Update(It.IsAny<ConfigObservation>())).ReturnsAsync(newCfg);
var result = await _service.UpdateConfig(newCfg);
Assert.That(result!.Name, Is.EqualTo("New"));
}
_settings.Value.ConfigObservation!.IgnoreUnknownObservation = false;
_repo.Setup(x => x.FindAll()).ReturnsAsync([]);
var obs = new BasePatientObservation { Name = "XX" };
var result = await _service.Map(obs);
Assert.That(result, Is.EqualTo(obs));
}
/// <summary>
/// Verifies that mapping a patient observation with a value of 14 results in an "Ok" status, confirming the threshold mapping logic returns the expected outcome.
/// </summary>
[Test]
public async Task UpdateConfig_notfound()
{
_repo
.Setup(r => r.FindById(Id))
.ReturnsAsync((ConfigObservation?)null);
Func<Task> act = () => _service.UpdateConfig(new ConfigObservation { Id = Id });
Assert.ThrowsAsync<NotFoundException>(act);
}
public async Task Map_threshold_Ok()
{
_repo.Setup(x => x.FindAll()).ReturnsAsync(_allConfigList);
var obs = new PatientObservation
{
Name = "SOFA",
CodingSystem = "SNM",
Value = 14
};
var result = await _service.Map(obs);
Assert.That(result!.Status, Is.EqualTo(StatusEnum.Type.Ok));
}
/// <summary>
/// Tests that the Map method returns a Warning status when a patient observation value exceeds the configured threshold.
/// </summary>
[Test]
public async Task CreateConfig_ok()
{
_repo.Setup(r => r.InsertOneAsyncAndReturn(ConfigList)).Returns(Task.FromResult(ConfigList));
var result = await _service.CreateConfig(ConfigList);
Assert.That(result, Is.EqualTo(ConfigList));
}
public async Task Map_threshold_Warning()
{
_repo.Setup(x => x.FindAll()).ReturnsAsync(_allConfigList);
var obs = new PatientObservation
{
Name = "SOFA",
CodingSystem = "SNM",
Value = 13
};
var result = await _service.Map(obs);
Assert.That(result!.Status, Is.EqualTo(StatusEnum.Type.Warning));
}
/// <summary>
/// Verifies that mapping a <see cref="PatientObservation"/> with a SOFA score of 9 through the configured service results in a result with an <see cref="StatusEnum.Type.Alert"/> status.
/// </summary>
[Test]
public void CreateConfig_duplicate_throws()
{
_repo.Setup(r => r.FindById(It.IsAny<ObjectId>())).ReturnsAsync(ConfigList);
Func<Task> act = () => _service.CreateConfig(ConfigList);
Assert.ThrowsAsync<BadRequestException>(act);
}
public async Task Map_threshold_Alert()
{
_repo.Setup(x => x.FindAll()).ReturnsAsync(_allConfigList);
var obs = new PatientObservation
{
Name = "SOFA",
CodingSystem = "SNM",
Value = 9
};
var result = await _service.Map(obs);
Assert.That(result!.Status, Is.EqualTo(StatusEnum.Type.Alert));
}
/// <summary>
/// Verifies that mapping a parent <see cref="PatientObservation"/> returns an <see cref="StatusEnum.Type.Ok"/> status
/// when the repository successfully locates the configuration by identifier.
/// </summary>
[Test]
public async Task RemoveConfigItem_ok()
{
var deleted = new ConfigObservation { Id = ObjectId.GenerateNewId(), Name = "X" };
_repo.Setup(r => r.FindById(It.IsAny<ObjectId>())).ReturnsAsync(ConfigList);
_repo.Setup(r => r.Delete(It.IsAny<ObjectId>())).ReturnsAsync(deleted);
var result = await _service.RemoveConfigItem(ObjectId.GenerateNewId());
Assert.That(result, Is.EqualTo(deleted));
}
public async Task Map_parent_ok()
{
_repo.Setup(x => x.FindById(Id)).ReturnsAsync(ConfigList);
var fc = new PatientObservation
{
Id = Id,
Name = "FC",
Value = 70
};
var result = await _service.Map(fc);
Assert.That(result!.Status, Is.EqualTo(StatusEnum.Type.Ok));
}
/// <summary>
/// Verifies that <c>UpdateConfig</c> successfully updates an existing configuration and returns the updated <see cref="ConfigObservation"/> with the new name.
/// </summary>
[Test]
public async Task RemoveConfigItem_null()
{
_repo.Setup(r => r.FindById(Id)).ReturnsAsync(ConfigList);
_repo.Setup(r => r.Delete(Id)).ReturnsAsync((ConfigObservation?)null);
public async Task UpdateConfig_ok()
{
var newCfg = new ConfigObservation
{
Id = Id,
Name = "New",
Code = "X",
CodingSystem = "S"
};
_repo.Setup(r => r.FindById(Id)).ReturnsAsync(ConfigList);
_repo.Setup(r => r.Update(It.IsAny<ConfigObservation>())).ReturnsAsync(newCfg);
var result = await _service.UpdateConfig(newCfg);
Assert.That(result!.Name, Is.EqualTo("New"));
}
var result = await _service.RemoveConfigItem(Id);
/// <summary>
/// Verifies that <c>UpdateConfig</c> throws a <see cref="NotFoundException"/> when the target <c>ConfigObservation</c> cannot be found by its identifier.
/// </summary>
[Test]
public async Task UpdateConfig_notfound()
{
_repo
.Setup(r => r.FindById(Id))
.ReturnsAsync((ConfigObservation?)null);
Func<Task> act = () => _service.UpdateConfig(new ConfigObservation { Id = Id });
Assert.ThrowsAsync<NotFoundException>(act);
}
Assert.That(result, Is.Null);
}
/// <summary>
/// Verifies that the <c>CreateConfig</c> service method successfully inserts the provided configuration and returns the expected result.
/// </summary>
[Test]
public async Task CreateConfig_ok()
{
_repo.Setup(r => r.InsertOneAsyncAndReturn(ConfigList)).Returns(Task.FromResult(ConfigList));
var result = await _service.CreateConfig(ConfigList);
Assert.That(result, Is.EqualTo(ConfigList));
}
/// <summary>
/// Verifies that <see cref="BadRequestException"/> is thrown when attempting to create a configuration that already exists.
/// </summary>
[Test]
public void CreateConfig_duplicate_throws()
{
_repo.Setup(r => r.FindById(It.IsAny<ObjectId>())).ReturnsAsync(ConfigList);
Func<Task> act = () => _service.CreateConfig(ConfigList);
Assert.ThrowsAsync<BadRequestException>(act);
}
/// <summary>
/// Verifies that RemoveConfigItem successfully removes a configuration item and returns the deleted entity when the repository finds and deletes it.
/// </summary>
[Test]
public async Task RemoveConfigItem_ok()
{
var deleted = new ConfigObservation { Id = ObjectId.GenerateNewId(), Name = "X" };
_repo.Setup(r => r.FindById(It.IsAny<ObjectId>())).ReturnsAsync(ConfigList);
_repo.Setup(r => r.Delete(It.IsAny<ObjectId>())).ReturnsAsync(deleted);
var result = await _service.RemoveConfigItem(ObjectId.GenerateNewId());
Assert.That(result, Is.EqualTo(deleted));
}
/// <summary>
/// Verifies that <c>RemoveConfigItem</c> returns <c>null</c> when the underlying delete operation on the repository yields no result, simulating the case where the configuration item does not exist.
/// </summary>
[Test]
public async Task RemoveConfigItem_null()
{
_repo.Setup(r => r.FindById(Id)).ReturnsAsync(ConfigList);
_repo.Setup(r => r.Delete(Id)).ReturnsAsync((ConfigObservation?)null);
var result = await _service.RemoveConfigItem(Id);
Assert.That(result, Is.Null);
}
}
+132 -113
View File
@@ -17,23 +17,26 @@ namespace adas_core.Test.Services;
[TestFixture]
public class ConfigPumpsServiceTest
{
/// <summary>
/// Initializes the test environment by creating mock dependencies and instantiating the <see cref="ConfigPumpsService"/> under test.
/// </summary>
[SetUp]
public void Setup()
{
//configPumpsServiceMock = new Mock<IConfigPumpsService>();
_configPumpsRepositoryMock = new Mock<IConfigPumpsRepository>();
_optionsApiSettings = Options.Create(_apiSettings);
_logger = new Mock<ILogger<ConfigPumpsService>>();
_configPumpsService = new ConfigPumpsService(
_configPumpsRepositoryMock.Object,
_optionsApiSettings,
_logger.Object,
_httpContextAccessor.Object,
_auditService.Object);
}
public void Setup()
{
//configPumpsServiceMock = new Mock<IConfigPumpsService>();
_configPumpsRepositoryMock = new Mock<IConfigPumpsRepository>();
_optionsApiSettings = Options.Create(_apiSettings);
_logger = new Mock<ILogger<ConfigPumpsService>>();
_configPumpsService = new ConfigPumpsService(
_configPumpsRepositoryMock.Object,
_optionsApiSettings,
_logger.Object,
_httpContextAccessor.Object,
_auditService.Object);
}
private ConfigPumpsService _configPumpsService;
@@ -59,112 +62,128 @@ public class ConfigPumpsServiceTest
private static readonly DateTime Now = DateTime.Now;
/// <summary>
/// Verifies that the <see cref="ConfigPumpsService.Map"/> method returns the same <see cref="PumpObservation"/> instance unchanged when the <c>ConfigPumpsRequired</c> option is set to <c>false</c>, bypassing any pump configuration lookup or transformation.
/// </summary>
[Test]
public async Task Map_configPumpsRequired_false_Return_same_pump()
{
_optionsApiSettings.Value.ConfigPumpsRequired = false;
var pump = new PumpObservation
{
Code = "code",
Name = "name",
Time = Now
};
var configPumpsService = new ConfigPumpsService(
_configPumpsRepositoryMock.Object,
_optionsApiSettings,
_logger.Object,
_httpContextAccessor.Object,
_auditService.Object);
var result = await configPumpsService.Map(pump);
Assert.That(pump, Is.EqualTo(result));
}
[Test]
public async Task Map_not_alarmType_Return_same_pump()
{
_optionsApiSettings.Value.ConfigPumpsRequired = true;
var pump = new PumpObservation
{
Code = "code",
Name = "name",
Time = Now
};
var result = await _configPumpsService.Map(pump);
Assert.That(pump, Is.EqualTo(result));
}
[Test]
public async Task Map_Not_uiConfiguration_Return_same_pump()
{
_optionsApiSettings.Value.ConfigPumpsRequired = true;
var pump = new PumpObservation
{
Code = "code",
Name = "name",
Time = Now,
AlarmType = PumpEnum.AlarmType.Attention
};
var configPumpItem = new ConfigPumpItem();
var configPumps = new ConfigPumps
{
Id = "PV1",
Items = [configPumpItem]
};
_configPumpsRepositoryMock.Setup(c => c.FindById(It.IsAny<string>())).ReturnsAsync(configPumps);
var result = await _configPumpsService.Map(pump);
using (Assert.EnterMultipleScope())
public async Task Map_configPumpsRequired_false_Return_same_pump()
{
_optionsApiSettings.Value.ConfigPumpsRequired = false;
var pump = new PumpObservation
{
Code = "code",
Name = "name",
Time = Now
};
var configPumpsService = new ConfigPumpsService(
_configPumpsRepositoryMock.Object,
_optionsApiSettings,
_logger.Object,
_httpContextAccessor.Object,
_auditService.Object);
var result = await configPumpsService.Map(pump);
Assert.That(pump, Is.EqualTo(result));
Assert.That(result.UiConfiguration, Is.Null);
}
}
/// <summary>
/// Verifies that when pump configuration is required and the pump has no alarm type,
/// the <c>Map</c> method returns the same pump instance unchanged.
/// </summary>
[Test]
public async Task Map_uiConfiguration_Return_pump_uiConfiguration()
{
_optionsApiSettings.Value.ConfigPumpsRequired = true;
var pump = new PumpObservation
public async Task Map_not_alarmType_Return_same_pump()
{
Code = "code",
Name = "name",
Time = Now,
AlarmType = PumpEnum.AlarmType.AirInLine
};
_optionsApiSettings.Value.ConfigPumpsRequired = true;
var pump = new PumpObservation
{
Code = "code",
Name = "name",
Time = Now
};
var result = await _configPumpsService.Map(pump);
Assert.That(pump, Is.EqualTo(result));
}
var configPumpItem = new ConfigPumpItem
/// <summary>
/// Verifies that the <see cref="ConfigPumpsService.Map"/> method returns the original <see cref="PumpObservation"/> unchanged when the configuration pump item is empty, ensuring that no <c>UiConfiguration</c> is assigned in that case.
/// </summary>
[Test]
public async Task Map_Not_uiConfiguration_Return_same_pump()
{
AlarmType = PumpEnum.AlarmType.AirInLine,
UiConfiguration = new Dictionary<string, object> { { "screenAlarmLabel", PumpEnum.AlarmType.AirInLine } }
};
_optionsApiSettings.Value.ConfigPumpsRequired = true;
var pump = new PumpObservation
{
Code = "code",
Name = "name",
Time = Now,
AlarmType = PumpEnum.AlarmType.Attention
};
var configPumpItem = new ConfigPumpItem();
var configPumps = new ConfigPumps
{
Id = "PV1",
Items = [configPumpItem]
};
_configPumpsRepositoryMock.Setup(c => c.FindById(It.IsAny<string>())).ReturnsAsync(configPumps);
var result = await _configPumpsService.Map(pump);
using (Assert.EnterMultipleScope())
{
Assert.That(pump, Is.EqualTo(result));
Assert.That(result.UiConfiguration, Is.Null);
}
}
var configPumps = new ConfigPumps
/// <summary>
/// Verifies that mapping a <see cref="PumpObservation"/> returns the expected pump UI
/// configuration when a matching <see cref="ConfigPumpItem"/> is found for the pump's
/// alarm type, ensuring the resulting UI configuration contains the expected label
/// entries such as the "screenAlarmLabel" mapped to the alarm type.
/// </summary>
[Test]
public async Task Map_uiConfiguration_Return_pump_uiConfiguration()
{
Id = "PV1",
Items = [configPumpItem]
};
_configPumpsRepositoryMock.Setup(c => c.FindById(It.IsAny<string>())).ReturnsAsync(configPumps);
var result = await _configPumpsService.Map(pump);
Assert.That(result.UiConfiguration, Is.Not.Null);
using (Assert.EnterMultipleScope())
{
Assert.That(result.UiConfiguration, Has.Count.EqualTo(1));
Assert.That(result.UiConfiguration!["screenAlarmLabel"], Is.EqualTo(PumpEnum.AlarmType.AirInLine));
};
}
_optionsApiSettings.Value.ConfigPumpsRequired = true;
var pump = new PumpObservation
{
Code = "code",
Name = "name",
Time = Now,
AlarmType = PumpEnum.AlarmType.AirInLine
};
var configPumpItem = new ConfigPumpItem
{
AlarmType = PumpEnum.AlarmType.AirInLine,
UiConfiguration = new Dictionary<string, object> { { "screenAlarmLabel", PumpEnum.AlarmType.AirInLine } }
};
var configPumps = new ConfigPumps
{
Id = "PV1",
Items = [configPumpItem]
};
_configPumpsRepositoryMock.Setup(c => c.FindById(It.IsAny<string>())).ReturnsAsync(configPumps);
var result = await _configPumpsService.Map(pump);
Assert.That(result.UiConfiguration, Is.Not.Null);
using (Assert.EnterMultipleScope())
{
Assert.That(result.UiConfiguration, Has.Count.EqualTo(1));
Assert.That(result.UiConfiguration!["screenAlarmLabel"], Is.EqualTo(PumpEnum.AlarmType.AirInLine));
};
}
}
+126 -110
View File
@@ -13,21 +13,24 @@ namespace adas_core.Test.Services;
[TestFixture]
public class ConfigUnitsServiceTest
{
/// <summary>
/// Initializes the test dependencies and creates a new instance of <see cref="ConfigUnitsService"/> with mocked repository, options, and logger for use in unit tests.
/// </summary>
[SetUp]
public void Setup()
{
//configUnitsServiceMock = new Mock<IConfigUnitsService>();
_configUnitsRepositoryMock = new Mock<IConfigUnitsRepository>();
_optionsApiSettings = Options.Create(_apiSettings);
_logger = new Mock<ILogger<ConfigUnitsService>>();
_configUnitsService = new ConfigUnitsService(
_configUnitsRepositoryMock.Object,
_optionsApiSettings,
_logger.Object);
}
public void Setup()
{
//configUnitsServiceMock = new Mock<IConfigUnitsService>();
_configUnitsRepositoryMock = new Mock<IConfigUnitsRepository>();
_optionsApiSettings = Options.Create(_apiSettings);
_logger = new Mock<ILogger<ConfigUnitsService>>();
_configUnitsService = new ConfigUnitsService(
_configUnitsRepositoryMock.Object,
_optionsApiSettings,
_logger.Object);
}
private ConfigUnitsService _configUnitsService;
@@ -48,110 +51,123 @@ public class ConfigUnitsServiceTest
private static readonly DateTime Now = DateTime.Now;
/// <summary>
/// Verifies that when <c>ConfigUnitsRequired</c> is set to <c>false</c>, the <see cref="ConfigUnitsService.Map"/> method returns the same observation instance without applying any unit mapping or transformation.
/// </summary>
[Test]
public async Task Map_configUnitsRequired_false_Return_same_obs()
{
_optionsApiSettings.Value.ConfigUnitsRequired = false;
var obs = new PatientObservation
{
Code = "code",
CodingSystem = "SNM",
Name = "name",
Time = Now
};
var configUnitsService = new ConfigUnitsService(
_configUnitsRepositoryMock.Object,
_optionsApiSettings,
_logger.Object);
var result = await configUnitsService.Map(obs);
Assert.That(obs, Is.EqualTo(result));
}
[Test]
public async Task Map_not_units_Return_same_obs()
{
_optionsApiSettings.Value.ConfigUnitsRequired = true;
var obs = new PatientObservation
{
Code = "code",
CodingSystem = "SNM",
Name = "name",
Time = Now
};
var result = await _configUnitsService.Map(obs);
Assert.That(obs, Is.EqualTo(result));
}
[Test]
public async Task Map_Not_Find_Config_Return_same_obs()
{
_optionsApiSettings.Value.ConfigUnitsRequired = true;
var obs = new PatientObservation
{
Code = "code",
CodingSystem = "SNM",
Name = "name",
Time = Now
};
var configUnitItem = new ConfigUnitItem();
var configUnits = new ConfigUnits
{
Id = "PV1",
Items = [configUnitItem]
};
_configUnitsRepositoryMock.Setup(c => c.FindById(It.IsAny<string>())).ReturnsAsync(configUnits);
var result = await _configUnitsService.Map(obs);
using (Assert.EnterMultipleScope())
public async Task Map_configUnitsRequired_false_Return_same_obs()
{
_optionsApiSettings.Value.ConfigUnitsRequired = false;
var obs = new PatientObservation
{
Code = "code",
CodingSystem = "SNM",
Name = "name",
Time = Now
};
var configUnitsService = new ConfigUnitsService(
_configUnitsRepositoryMock.Object,
_optionsApiSettings,
_logger.Object);
var result = await configUnitsService.Map(obs);
Assert.That(obs, Is.EqualTo(result));
Assert.That(result.Units, Is.Null);
};
}
}
/// <summary>
/// Verifies that mapping a patient observation that does not contain units returns the same observation unchanged, confirming the mapping logic preserves the original data when no unit conversion is applicable.
/// </summary>
[Test]
public async Task Map_configUnits_Return_obs_ConfigUnit()
{
_optionsApiSettings.Value.ConfigUnitsRequired = true;
var obs = new PatientObservation
public async Task Map_not_units_Return_same_obs()
{
Code = "code",
CodingSystem = "SNM",
Name = "name",
Time = Now,
Units = "MDC_DIM_X_G_PER_KG"
};
_optionsApiSettings.Value.ConfigUnitsRequired = true;
var obs = new PatientObservation
{
Code = "code",
CodingSystem = "SNM",
Name = "name",
Time = Now
};
var result = await _configUnitsService.Map(obs);
Assert.That(obs, Is.EqualTo(result));
}
var configUnitItem = new ConfigUnitItem
/// <summary>
/// Verifies that the Map method returns the original observation unchanged with a null Units property when configuration units are required and no matching config unit is found for the observation's code.
/// </summary>
[Test]
public async Task Map_Not_Find_Config_Return_same_obs()
{
Code = "MDC_DIM_X_G_PER_KG",
Value = "g/kg"
};
_optionsApiSettings.Value.ConfigUnitsRequired = true;
var obs = new PatientObservation
{
Code = "code",
CodingSystem = "SNM",
Name = "name",
Time = Now
};
var configUnitItem = new ConfigUnitItem();
var configUnits = new ConfigUnits
{
Id = "PV1",
Items = [configUnitItem]
};
_configUnitsRepositoryMock.Setup(c => c.FindById(It.IsAny<string>())).ReturnsAsync(configUnits);
var result = await _configUnitsService.Map(obs);
using (Assert.EnterMultipleScope())
{
Assert.That(obs, Is.EqualTo(result));
Assert.That(result.Units, Is.Null);
};
}
var configUnits = new ConfigUnits
/// <summary>
/// Verifies that Map returns the configured unit value for a patient observation when
/// ConfigUnitsRequired is enabled and a matching config unit is found in the repository.
/// </summary>
[Test]
public async Task Map_configUnits_Return_obs_ConfigUnit()
{
Id = "PV1",
Items = [configUnitItem]
};
_configUnitsRepositoryMock.Setup(c => c.FindById(It.IsAny<string>())).ReturnsAsync(configUnits);
var result = await _configUnitsService.Map(obs);
Assert.That(result.Units, Is.Not.Null);
Assert.That(result.Units, Is.EqualTo("g/kg"));
}
_optionsApiSettings.Value.ConfigUnitsRequired = true;
var obs = new PatientObservation
{
Code = "code",
CodingSystem = "SNM",
Name = "name",
Time = Now,
Units = "MDC_DIM_X_G_PER_KG"
};
var configUnitItem = new ConfigUnitItem
{
Code = "MDC_DIM_X_G_PER_KG",
Value = "g/kg"
};
var configUnits = new ConfigUnits
{
Id = "PV1",
Items = [configUnitItem]
};
_configUnitsRepositoryMock.Setup(c => c.FindById(It.IsAny<string>())).ReturnsAsync(configUnits);
var result = await _configUnitsService.Map(obs);
Assert.That(result.Units, Is.Not.Null);
Assert.That(result.Units, Is.EqualTo("g/kg"));
}
}
+115 -103
View File
@@ -17,51 +17,54 @@ namespace adas_core.Test.Services;
[TestFixture]
public class DiagnosisServiceTest
{
/// <summary>
/// Initializes the mocked dependencies (patient, diagnosis, unit, audit, subscribers and calculated observations services along with their repositories) and constructs the <see cref="DiagnosisService"/> instance under test.
/// </summary>
[SetUp]
public void Setup()
{
_patientServiceMock = new Mock<IPatientService>();
_patientServiceLazy = new Lazy<IPatientService>(() => _patientServiceMock.Object);
//var sectionServiceMock = new Mock<ISectionService>();
//sectionServiceLazy = new Lazy<ISectionService>(() => sectionServiceMock.Object);
_diagnosisRepositoryMock = new Mock<IDiagnosisRepository>();
_diagnosisArchiveRepositoryMock = new Mock<IDiagnosisArchiveRepository>();
_clientMessageServiceMock = new Mock<IClientMessageService>();
_subscribersServiceMock = new Mock<ISubscribersService>();
_unitServiceMock = new Mock<IUnitService>();
_httpContextAccessor = new Mock<IHttpContextAccessor>();
_auditService = new Mock<ILocalAuditService>();
_calculatedObservationsServiceMock = new Mock<ICalculatedObservationsService>();
_calculatedObservationsServiceLazy =
new Lazy<ICalculatedObservationsService>(() => _calculatedObservationsServiceMock.Object);
_calculatedObservationsServiceMock.Setup(x => x.Map(It.IsAny<PatientDiagnosis>()))
.ReturnsAsync((PatientDiagnosis? value) => value);
_optionsApiSettings = Options.Create(_apiSettings);
_logger = new Mock<ILogger<DiagnosisService>>();
_diagnosisService = new DiagnosisService(
_patientServiceLazy,
//sectionServiceLazy,
_optionsApiSettings,
_diagnosisRepositoryMock.Object,
_diagnosisArchiveRepositoryMock.Object,
_logger.Object,
_clientMessageServiceMock.Object,
_subscribersServiceMock.Object,
_calculatedObservationsServiceLazy,
_httpContextAccessor.Object,
_auditService.Object,
_unitServiceMock.Object
);
// Create a mock of the singleton class subscribers
var mockSingleton = new Mock<ICalculatedObservationsService>();
// Set up the mock object to return a specific value when a method is called
mockSingleton.Setup(x => x.Map(It.IsAny<PatientDiagnosis>())).ReturnsAsync((PatientDiagnosis? value) => value);
}
public void Setup()
{
_patientServiceMock = new Mock<IPatientService>();
_patientServiceLazy = new Lazy<IPatientService>(() => _patientServiceMock.Object);
//var sectionServiceMock = new Mock<ISectionService>();
//sectionServiceLazy = new Lazy<ISectionService>(() => sectionServiceMock.Object);
_diagnosisRepositoryMock = new Mock<IDiagnosisRepository>();
_diagnosisArchiveRepositoryMock = new Mock<IDiagnosisArchiveRepository>();
_clientMessageServiceMock = new Mock<IClientMessageService>();
_subscribersServiceMock = new Mock<ISubscribersService>();
_unitServiceMock = new Mock<IUnitService>();
_httpContextAccessor = new Mock<IHttpContextAccessor>();
_auditService = new Mock<ILocalAuditService>();
_calculatedObservationsServiceMock = new Mock<ICalculatedObservationsService>();
_calculatedObservationsServiceLazy =
new Lazy<ICalculatedObservationsService>(() => _calculatedObservationsServiceMock.Object);
_calculatedObservationsServiceMock.Setup(x => x.Map(It.IsAny<PatientDiagnosis>()))
.ReturnsAsync((PatientDiagnosis? value) => value);
_optionsApiSettings = Options.Create(_apiSettings);
_logger = new Mock<ILogger<DiagnosisService>>();
_diagnosisService = new DiagnosisService(
_patientServiceLazy,
//sectionServiceLazy,
_optionsApiSettings,
_diagnosisRepositoryMock.Object,
_diagnosisArchiveRepositoryMock.Object,
_logger.Object,
_clientMessageServiceMock.Object,
_subscribersServiceMock.Object,
_calculatedObservationsServiceLazy,
_httpContextAccessor.Object,
_auditService.Object,
_unitServiceMock.Object
);
// Create a mock of the singleton class subscribers
var mockSingleton = new Mock<ICalculatedObservationsService>();
// Set up the mock object to return a specific value when a method is called
mockSingleton.Setup(x => x.Map(It.IsAny<PatientDiagnosis>())).ReturnsAsync((PatientDiagnosis? value) => value);
}
private DiagnosisService _diagnosisService;
@@ -137,69 +140,78 @@ public class DiagnosisServiceTest
// }
/// <summary>
/// Verifies that <c>SaveRequest</c> does not insert a diagnosis when the patient referenced by the <see cref="ApiRequest"/> cannot be found.
/// </summary>
/// <returns>A task representing the asynchronous test execution.</returns>
[Test]
public async Task SaveRequest_Not_FindPatient_Return_not_insert()
{
var apiRequest = new ApiRequest
public async Task SaveRequest_Not_FindPatient_Return_not_insert()
{
Location = new PatientLocation("UCI5C", "Box4"),
PatientNumber = "437537",
Type = "ORU_R01",
PatientId = PatientId.ToString(),
MessageTime = Now
};
_patientServiceMock.Setup(cm => cm.FindPatientByApiRequest(apiRequest)).ReturnsAsync((Patient?)null);
await _diagnosisService.SaveRequest(apiRequest, null);
_diagnosisRepositoryMock.Verify(d => d.InsertOneAsync(It.IsAny<PatientDiagnosis>()), Times.Never);
}
[Test]
public async Task SaveRequest_Not_Observations_Return_not_insert()
{
var patientObs = new Person
{
FirstName = "Miguel",
LastName = "Villanueva",
Ids = new Dictionary<string, string> { { "MR", "437537" } }
};
var patient = new Patient
{
Id = ObjectId.GenerateNewId(),
UnitString = "UCI5C",
Bed = "Box4",
PatientNumber = "437537",
Person = patientObs
};
var apiRequest = new ApiRequest
{
Location = new PatientLocation("UCI5C", "Box4"),
ObservationData = new ObservationData
var apiRequest = new ApiRequest
{
Code = "302147001",
CodingSystem = "SNM",
Value = "Aire; Contacto; Preventivo",
Text = "Aislamiento",
Time = Now
},
Patient = patientObs,
PatientNumber = "437537",
Type = "ORU_R01",
PatientId = PatientId.ToString(),
MessageTime = Now
};
Location = new PatientLocation("UCI5C", "Box4"),
PatientNumber = "437537",
Type = "ORU_R01",
PatientId = PatientId.ToString(),
MessageTime = Now
};
_patientServiceMock.Setup(cm => cm.FindPatientByApiRequest(apiRequest)).ReturnsAsync((Patient?)null);
await _diagnosisService.SaveRequest(apiRequest, null);
_diagnosisRepositoryMock.Verify(d => d.InsertOneAsync(It.IsAny<PatientDiagnosis>()), Times.Never);
}
_patientServiceMock.Setup(cm => cm.FindPatientByApiRequest(apiRequest)).ReturnsAsync(patient);
await _diagnosisService.SaveRequest(apiRequest, null);
_diagnosisRepositoryMock.Verify(d => d.InsertOneAsync(It.IsAny<PatientDiagnosis>()), Times.Never);
}
/// <summary>
/// Verifies that <c>SaveRequest</c> does not insert a <see cref="PatientDiagnosis"/> when the provided <see cref="ApiRequest"/> is not considered an observation request, ensuring observations must be present to trigger a database insertion.
/// </summary>
/// <param name="apiRequest">The API request containing the patient and observation data being evaluated.</param>
/// <param name="null">Reserved parameter passed to <c>SaveRequest</c> as a null value.</param>
[Test]
public async Task SaveRequest_Not_Observations_Return_not_insert()
{
var patientObs = new Person
{
FirstName = "Miguel",
LastName = "Villanueva",
Ids = new Dictionary<string, string> { { "MR", "437537" } }
};
var patient = new Patient
{
Id = ObjectId.GenerateNewId(),
UnitString = "UCI5C",
Bed = "Box4",
PatientNumber = "437537",
Person = patientObs
};
var apiRequest = new ApiRequest
{
Location = new PatientLocation("UCI5C", "Box4"),
ObservationData = new ObservationData
{
Code = "302147001",
CodingSystem = "SNM",
Value = "Aire; Contacto; Preventivo",
Text = "Aislamiento",
Time = Now
},
Patient = patientObs,
PatientNumber = "437537",
Type = "ORU_R01",
PatientId = PatientId.ToString(),
MessageTime = Now
};
_patientServiceMock.Setup(cm => cm.FindPatientByApiRequest(apiRequest)).ReturnsAsync(patient);
await _diagnosisService.SaveRequest(apiRequest, null);
_diagnosisRepositoryMock.Verify(d => d.InsertOneAsync(It.IsAny<PatientDiagnosis>()), Times.Never);
}
[Test]
public async Task SaveRequest_Not_DiagnosisCodeSettings_Return_not_insert()
+159 -124
View File
@@ -13,6 +13,9 @@ using Moq;
namespace adas_core.Test.Services;
/// <summary>
/// Provides unit tests for the <see cref="DischargeService"/> class, verifying the behavior and correctness of discharge-related operations.
/// </summary>
public class DischargeServiceTest
{
private readonly Mock<ILocalAuditService> _auditServiceMock = new();
@@ -28,47 +31,55 @@ public class DischargeServiceTest
private readonly Mock<IUnitService> _unitServiceMock = new();
private DischargeService _dischargeService = null!;
/// <summary>
/// Sets up the test environment by creating a mock authenticated user context and instantiating the <see cref="DischargeService"/> with its required dependencies for use in unit tests.
/// </summary>
[SetUp]
public void Setup()
{
var userClaims = new ClaimsPrincipal(new ClaimsIdentity([
new Claim(ClaimTypes.Name, "TestUser")
], "mock"));
var httpContextMock = new DefaultHttpContext
public void Setup()
{
User = userClaims
};
_httpContextAccessorMock.Setup(accessor => accessor.HttpContext)
.Returns(httpContextMock);
_dischargeService = new DischargeService(
_loggerMock.Object,
_subscribersServiceMock.Object,
_dischargeRepositoryMock.Object,
_patientServiceLazyMock.Object,
_clientMessageServiceMock.Object,
_pointOfCareServiceMock.Object,
_httpContextAccessorMock.Object,
_auditServiceMock.Object,
_unitServiceMock.Object,
_masterMock.Object);
}
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);
_dischargeService = new DischargeService(
_loggerMock.Object,
_subscribersServiceMock.Object,
_dischargeRepositoryMock.Object,
_patientServiceLazyMock.Object,
_clientMessageServiceMock.Object,
_pointOfCareServiceMock.Object,
_httpContextAccessorMock.Object,
_auditServiceMock.Object,
_unitServiceMock.Object,
_masterMock.Object);
}
/// <summary>
/// Verifies that <see cref="DischargeService.DeleteDischargeAsync(Discharge)"/> successfully deletes a discharge
/// when it exists in the repository.
/// </summary>
/// <param name="discharge">The discharge entity expected to be deleted; its identifier is used to invoke the repository delete operation.</param>
[Test]
public async Task DeleteDischargeAsync_WhenDischargeExists_DeletesDischarge()
{
// Arrange
var dischargeId = ObjectId.GenerateNewId();
var discharge = new Discharge { Id = dischargeId };
_dischargeRepositoryMock.Setup(repo => repo.FindById(dischargeId)).ReturnsAsync(discharge);
// Act
await _dischargeService.DeleteDischargeAsync(discharge);
// Assert
_dischargeRepositoryMock.Verify(repo => repo.Delete(dischargeId), Times.Once);
}
public async Task DeleteDischargeAsync_WhenDischargeExists_DeletesDischarge()
{
// Arrange
var dischargeId = ObjectId.GenerateNewId();
var discharge = new Discharge { Id = dischargeId };
_dischargeRepositoryMock.Setup(repo => repo.FindById(dischargeId)).ReturnsAsync(discharge);
// Act
await _dischargeService.DeleteDischargeAsync(discharge);
// Assert
_dischargeRepositoryMock.Verify(repo => repo.Delete(dischargeId), Times.Once);
}
//necesita el pocservice
// [Test]
@@ -86,32 +97,39 @@ public class DischargeServiceTest
// Assert.That(result, Is.EqualTo(discharge));
// }
/// <summary>
/// Verifies that <see cref="DischargeService.GetDischargeByIdAsync"/> throws a <see cref="NotFoundException"/> when the requested discharge is not found.
/// </summary>
[Test]
public void GetDischargeByIdAsync_WhenExceptionOccurs_ReturnsNullAndLogsError()
{
// Arrange
var dischargeId = ObjectId.GenerateNewId();
// Act and assert
Func<Task> act = async () => await _dischargeService.GetDischargeByIdAsync(dischargeId);
Assert.ThrowsAsync<NotFoundException>(act);
}
public void GetDischargeByIdAsync_WhenExceptionOccurs_ReturnsNullAndLogsError()
{
// Arrange
var dischargeId = ObjectId.GenerateNewId();
// Act and assert
Func<Task> act = async () => await _dischargeService.GetDischargeByIdAsync(dischargeId);
Assert.ThrowsAsync<NotFoundException>(act);
}
/// <summary>
/// Verifies that the discharge service returns the expected discharges retrieved from the repository when the underlying repository call completes successfully without exceptions.
/// </summary>
/// <returns>A <see cref="Task"/> representing the asynchronous test execution.</returns>
[Test]
public async Task GetDischargesAsync_WhenNoException_ReturnsDischarges()
{
// Arrange
var discharge = TestUtilities.CreateValidDischarge();
var discharges = new List<Discharge> { discharge };
_dischargeRepositoryMock.Setup(repo => repo.FindAll()).ReturnsAsync(discharges);
// Act
var result = await _dischargeService.GetDischargesAsync();
var resultList = result.ToList();
// Assert
Assert.That(resultList.First().Id, Is.EqualTo(discharges.First().Id));
}
public async Task GetDischargesAsync_WhenNoException_ReturnsDischarges()
{
// Arrange
var discharge = TestUtilities.CreateValidDischarge();
var discharges = new List<Discharge> { discharge };
_dischargeRepositoryMock.Setup(repo => repo.FindAll()).ReturnsAsync(discharges);
// Act
var result = await _dischargeService.GetDischargesAsync();
var resultList = result.ToList();
// Assert
Assert.That(resultList.First().Id, Is.EqualTo(discharges.First().Id));
}
//necesita el pocservice
// [Test]
@@ -128,64 +146,78 @@ public class DischargeServiceTest
// Assert.That(result?.Id, Is.EqualTo(discharge.Id));
// }
/// <summary>
/// Verifies that <see cref="DischargeService.InsertDischarge"/> returns the inserted discharge
/// when the underlying repository successfully completes the insertion and the entity can be retrieved by its identifier.
/// </summary>
[Test]
public async Task InsertDischarge_WhenInsertionSuccessful_ReturnsInsertedDischarge()
{
// Arrange
var dischargeId = ObjectId.GenerateNewId();
var discharge = new Discharge { Id = dischargeId };
_dischargeRepositoryMock.Setup(repo => repo.InsertOneAsync(discharge)).Returns(Task.CompletedTask);
_dischargeRepositoryMock.Setup(repo => repo.FindById(dischargeId)).ReturnsAsync(discharge);
// Act
var result = await _dischargeService.InsertDischarge(discharge);
// Assert
Assert.That(result, Is.EqualTo(discharge));
}
public async Task InsertDischarge_WhenInsertionSuccessful_ReturnsInsertedDischarge()
{
// Arrange
var dischargeId = ObjectId.GenerateNewId();
var discharge = new Discharge { Id = dischargeId };
_dischargeRepositoryMock.Setup(repo => repo.InsertOneAsync(discharge)).Returns(Task.CompletedTask);
_dischargeRepositoryMock.Setup(repo => repo.FindById(dischargeId)).ReturnsAsync(discharge);
// Act
var result = await _dischargeService.InsertDischarge(discharge);
// Assert
Assert.That(result, Is.EqualTo(discharge));
}
/// <summary>
/// Verifies that <see cref="DischargeService.InsertDischarge"/> handles repository insertion failures by propagating the exception thrown by the underlying data store.
/// </summary>
[Test]
public void InsertDischarge_WhenInsertionFails_ReturnsNullAndLogsError()
{
// Arrange
var discharge = new Discharge { Id = ObjectId.GenerateNewId() };
var exception = new Exception("Failed to insert discharge");
_dischargeRepositoryMock.Setup(repo => repo.InsertOneAsync(discharge)).ThrowsAsync(exception);
// Assert
Func<Task> act = async () => await _dischargeService.InsertDischarge(discharge);
Assert.ThrowsAsync<Exception>(act);
}
public void InsertDischarge_WhenInsertionFails_ReturnsNullAndLogsError()
{
// Arrange
var discharge = new Discharge { Id = ObjectId.GenerateNewId() };
var exception = new Exception("Failed to insert discharge");
_dischargeRepositoryMock.Setup(repo => repo.InsertOneAsync(discharge)).ThrowsAsync(exception);
// Assert
Func<Task> act = async () => await _dischargeService.InsertDischarge(discharge);
Assert.ThrowsAsync<Exception>(act);
}
/// <summary>
/// Verifies that <see cref="DischargeService.GetDischargeByLocation"/> returns the discharge
/// retrieved from the repository when a discharge exists for the specified patient location.
/// </summary>
[Test]
public async Task GetDischargeByLocation_WhenDischargeExists_ReturnsDischarge()
{
// Arrange
var location = new PatientLocation("UnitName", "Bed", "Room");
var discharge = new Discharge();
_dischargeRepositoryMock.Setup(repo => repo.GetDischargeByLocation(location)).ReturnsAsync(discharge);
// Act
var result = await _dischargeService.GetDischargeByLocation(location);
// Assert
Assert.That(result, Is.EqualTo(discharge));
}
public async Task GetDischargeByLocation_WhenDischargeExists_ReturnsDischarge()
{
// Arrange
var location = new PatientLocation("UnitName", "Bed", "Room");
var discharge = new Discharge();
_dischargeRepositoryMock.Setup(repo => repo.GetDischargeByLocation(location)).ReturnsAsync(discharge);
// Act
var result = await _dischargeService.GetDischargeByLocation(location);
// Assert
Assert.That(result, Is.EqualTo(discharge));
}
/// <summary>
/// Verifies that <see cref="IDischargeService.GetDischargeByLocation"/> returns <c>null</c> when the underlying discharge repository throws an exception while retrieving the discharge record for the specified patient location.
/// </summary>
[Test]
public async Task GetDischargeByLocation_WhenExceptionOccurs_ReturnsNullAndLogsError()
{
// Arrange
var location = new PatientLocation("UnitName", "Bed", "Room");
var exception = new Exception("Failed to retrieve discharge by location");
_dischargeRepositoryMock.Setup(repo => repo.GetDischargeByLocation(location)).ThrowsAsync(exception);
// Act
var result = await _dischargeService.GetDischargeByLocation(location);
// Assert
Assert.That(result, Is.Null);
}
public async Task GetDischargeByLocation_WhenExceptionOccurs_ReturnsNullAndLogsError()
{
// Arrange
var location = new PatientLocation("UnitName", "Bed", "Room");
var exception = new Exception("Failed to retrieve discharge by location");
_dischargeRepositoryMock.Setup(repo => repo.GetDischargeByLocation(location)).ThrowsAsync(exception);
// Act
var result = await _dischargeService.GetDischargeByLocation(location);
// Assert
Assert.That(result, Is.Null);
}
// [Test]
// public async Task GetDischargeByPointOfCareId_WhenDischargeExists_ReturnsDischarge()
@@ -201,18 +233,21 @@ public class DischargeServiceTest
// Assert.That(result, Is.EqualTo(discharge));
// }
/// <summary>
/// Verifies that <see cref="DischargeService.GetDischargeByPointOfCareId"/> returns <c>null</c> when the underlying repository throws an exception while attempting to retrieve a discharge by its point-of-care identifier.
/// </summary>
[Test]
public async Task GetDischargeByPointOfCareId_WhenExceptionOccurs_ReturnsNullAndLogsError()
{
// Arrange
var poc = ObjectId.GenerateNewId();
var exception = new Exception("Failed to retrieve discharge by PointOfCareId");
_dischargeRepositoryMock.Setup(repo => repo.GetDischargeByPointOfCareId(poc)).ThrowsAsync(exception);
// Act
var result = await _dischargeService.GetDischargeByPointOfCareId(poc);
// Assert
Assert.That(result, Is.Null);
}
public async Task GetDischargeByPointOfCareId_WhenExceptionOccurs_ReturnsNullAndLogsError()
{
// Arrange
var poc = ObjectId.GenerateNewId();
var exception = new Exception("Failed to retrieve discharge by PointOfCareId");
_dischargeRepositoryMock.Setup(repo => repo.GetDischargeByPointOfCareId(poc)).ThrowsAsync(exception);
// Act
var result = await _dischargeService.GetDischargeByPointOfCareId(poc);
// Assert
Assert.That(result, Is.Null);
}
}
+63 -14
View File
@@ -35,6 +35,10 @@ public class DisplayServiceTest
private DisplayService _displayService = null!;
/// <summary>
/// Initializes mocked dependencies and configuration required to construct a <see cref="DisplayService"/> instance for unit tests.
/// Sets up the HTTP context with a test user claim, creates default cache settings, and wires all collaborators (repositories, services, logger, permissions) into the service under test.
/// </summary>
[SetUp]
public void SetUp()
{
@@ -56,7 +60,7 @@ public class DisplayServiceTest
_httpContextAccessorMock.Setup(a => a.HttpContext).Returns(new DefaultHttpContext { User = claims });
var cacheSettings = Options.Create(new CacheSettings());
_displayService = new DisplayService(
_mockDisplayRepository.Object,
_mockPointOfCareService.Object,
@@ -73,12 +77,15 @@ public class DisplayServiceTest
_mockCacheService.Object,
cacheSettings
);
}
// -----------------------------------------------------------
// InsertOne
// -----------------------------------------------------------
/// <summary>
/// Verifies that <see cref="DisplayService.InsertOne"/> inserts a display by resolving the default configuration for its type and persisting it through the repository's insert method.
/// </summary>
[Test]
public async Task InsertOne_ShouldInsertDisplay()
{
@@ -104,6 +111,9 @@ public class DisplayServiceTest
// -----------------------------------------------------------
// GetById
// -----------------------------------------------------------
/// <summary>
/// Verifies that the <see cref="DisplayService"/> returns the expected <see cref="Display"/> instance when a matching id is provided through the repository.
/// </summary>
[Test]
public async Task GetById_ShouldReturnDisplay()
{
@@ -120,6 +130,9 @@ public class DisplayServiceTest
// -----------------------------------------------------------
// GetAllByUser - userName null
// -----------------------------------------------------------
/// <summary>
/// Verifies that _displayService.GetAllByUser returns an empty collection when the user name is null.
/// </summary>
[Test]
public async Task GetAllByUser_ShouldReturnEmpty_WhenUserNameNull()
{
@@ -130,7 +143,7 @@ public class DisplayServiceTest
// -----------------------------------------------------------
// GetAllByUser - Admin case
// -----------------------------------------------------------
[Test]
public async Task GetAllByUser_ShouldReturnDisplays_WhenAdmin()
{
@@ -144,7 +157,7 @@ public class DisplayServiceTest
{
Id = adminId,
UserName = userName,
Authorization =
Authorization =
[
new Authorization { UnitId = unitId.ToString(), Rol = nameof(PermissionEnum.RolesType.AuthAdmin) }
]
@@ -182,13 +195,13 @@ public class DisplayServiceTest
_mockPermissionService
.Setup(p => p.GetPermissionsForUnit(unitId.ToString(), user))
.ReturnsAsync(new DisplayPermissionTypes(
new UserActions(true,true,true,true,true),
new UserActions(true,true,true,true,true),
new UserActions(true,true,true,true,true),
new UserActions(true,true,true,true,true),
new UserActions(true,true,true,true,true),
new UserActions(true,true,true,true,true),
new UserActions(true,true,true,true,true),
new UserActions(true, true, true, true, true),
new UserActions(true, true, true, true, true),
new UserActions(true, true, true, true, true),
new UserActions(true, true, true, true, true),
new UserActions(true, true, true, true, true),
new UserActions(true, true, true, true, true),
new UserActions(true, true, true, true, true),
true
));
@@ -207,6 +220,11 @@ public class DisplayServiceTest
// -----------------------------------------------------------
// GetByType
// -----------------------------------------------------------
/// <summary>
/// Verifies that retrieving display configurations by type returns the associated displays
/// from the display repository, ensuring the service correctly resolves configurations and
/// their linked displays for the given display type.
/// </summary>
[Test]
public async Task GetByType_ShouldReturnDisplays()
{
@@ -226,6 +244,10 @@ public class DisplayServiceTest
That(result[0], Is.EqualTo(display));
}
/// <summary>
/// Verifies that <see cref="DisplayService.GetByType"/> returns an empty collection when no display configurations are found for the specified display type.
/// </summary>
/// <returns>A task that completes when the assertion confirming the empty result has been executed.</returns>
[Test]
public async Task GetByType_ShouldReturnEmpty_WhenNoConfigsFound()
{
@@ -240,6 +262,11 @@ public class DisplayServiceTest
// -----------------------------------------------------------
// GetByPointOfCare
// -----------------------------------------------------------
/// <summary>
/// Tests that GetByPointOfCare returns the displays associated with the specified point of care.
/// </summary>
/// <param name="poc">The point of care used to look up associated displays.</param>
/// <returns>A task representing the asynchronous test execution.</returns>
[Test]
public async Task GetByPointOfCare_ShouldReturnDisplays()
{
@@ -257,11 +284,15 @@ public class DisplayServiceTest
// -----------------------------------------------------------
// GetByConfigId
// -----------------------------------------------------------
/// <summary>
/// Verifies that <see cref="DisplayService.GetByConfigId"/> returns the displays associated with the specified configuration ID retrieved from the repository.
/// </summary>
/// <returns>A task that completes when the assertion confirms the returned collection contains the expected number of displays.</returns>
[Test]
public async Task GetByConfigId_ShouldReturnDisplays()
{
var cfgId = ObjectId.GenerateNewId();
var d = new Display { Id = ObjectId.GenerateNewId(), DisplayConfigId = cfgId, Name = "display1"};
var d = new Display { Id = ObjectId.GenerateNewId(), DisplayConfigId = cfgId, Name = "display1" };
_mockDisplayRepository.Setup(r => r.GetByConfigId(cfgId))
.ReturnsAsync([d]);
@@ -274,6 +305,9 @@ public class DisplayServiceTest
// -----------------------------------------------------------
// GetByName
// -----------------------------------------------------------
/// <summary>
/// Verifies that the <see cref="DisplayService.GetByName"/> method throws a <see cref="NotFoundException"/> when no display matching the specified name is found in the repository.
/// </summary>
[Test]
public void GetByName_ShouldThrow_WhenNotFound()
{
@@ -287,6 +321,9 @@ public class DisplayServiceTest
// -----------------------------------------------------------
// GetInfo
// -----------------------------------------------------------
/// <summary>
/// Verifies that <c>GetInfo</c> returns a <see cref="Display"/> with its associated <c>PointOfCare</c> entries populated when invoked with the "with POC" flag enabled and a valid display identifier.
/// </summary>
[Test]
public async Task GetInfo_ShouldReturnDisplayWithPoc()
{
@@ -309,7 +346,7 @@ public class DisplayServiceTest
It.IsAny<TimeSpan?>(),
It.IsAny<CancellationToken>()))
.Returns((string _, Func<Task<Display?>> factory, TimeSpan? _, CancellationToken _) => factory());
_mockDisplayRepository.Setup(r => r.GetById(id)).ReturnsAsync(display);
_mockDisplayConfigService.Setup(s => s.GetById(cfgId))
.ReturnsAsync(new DisplayConfig { Id = cfgId });
@@ -328,11 +365,14 @@ public class DisplayServiceTest
// -----------------------------------------------------------
// GetByUnitId
// -----------------------------------------------------------
/// <summary>
/// Verifies that <see cref="IDisplayService.GetByUnitId"/> returns the displays associated with the specified unit identifier when the repository contains matching records.
/// </summary>
[Test]
public async Task GetByUnitId_ShouldReturnDisplays()
{
var id = ObjectId.GenerateNewId();
var d = new Display { Id = ObjectId.GenerateNewId(), UnitId = id , Name = "display1"};
var d = new Display { Id = ObjectId.GenerateNewId(), UnitId = id, Name = "display1" };
_mockDisplayRepository.Setup(r => r.GetByUnitId(id))
.ReturnsAsync([d]);
@@ -345,6 +385,10 @@ public class DisplayServiceTest
// -----------------------------------------------------------
// UpdatePointOfCareList
// -----------------------------------------------------------
/// <summary>
/// Verifies that UpdatePointOfCareList throws a <see cref="NotFoundException"/> when the display with the specified identifier does not exist.
/// </summary>
/// <exception cref="NotFoundException">Thrown when no display is found for the given id.</exception>
[Test]
public void UpdatePointOfCareList_ShouldThrow_WhenDisplayNotFound()
{
@@ -360,6 +404,11 @@ public class DisplayServiceTest
// -----------------------------------------------------------
// UpdateConfigPreset
// -----------------------------------------------------------
/// <summary>
/// Verifies that <see cref="DisplayService.UpdateConfigPreset"/> throws a <see cref="NotFoundException"/> when the repository update operation returns a null result, indicating the display or configuration preset could not be found.
/// </summary>
/// <param name="id">The unique identifier of the display whose configuration preset is being updated.</param>
/// <param name="cfgId">The unique identifier of the configuration preset to associate with the display.</param>
[Test]
public void UpdateConfigPreset_ShouldThrow_WhenUpdateFails()
{
@@ -15,124 +15,140 @@ namespace adas_core.Test.Services;
[TestFixture]
public class GroupedObservationServiceTest
{
/// <summary>
/// Initializes the test fixture before each test by creating mocked dependencies
/// (configuration observation service, observation repository, logger, and cache service)
/// and instantiating the <see cref="GroupedObservationService"/> under test with default API and cache settings.
/// </summary>
[SetUp]
public void Setup()
{
var configObservationService = new Mock<IConfigObservationService>();
var observationRepository = new Mock<IObservationRepository>();
_logger = new Mock<ILogger<GroupedObservationService>>();
_groupedObservationService = new GroupedObservationService(
observationRepository.Object, configObservationService.Object,
_logger.Object, Mock.Of<ICacheService>(), Options.Create(new ApiSettings()), Options.Create(new CacheSettings()));
}
public void Setup()
{
var configObservationService = new Mock<IConfigObservationService>();
var observationRepository = new Mock<IObservationRepository>();
_logger = new Mock<ILogger<GroupedObservationService>>();
_groupedObservationService = new GroupedObservationService(
observationRepository.Object, configObservationService.Object,
_logger.Object, Mock.Of<ICacheService>(), Options.Create(new ApiSettings()), Options.Create(new CacheSettings()));
}
private GroupedObservationService _groupedObservationService;
private Mock<ILogger<GroupedObservationService>> _logger;
/// <summary>
/// Tests that shift observations are correctly generated and grouped by shift time intervals (08:00, 15:00, 22:00) across multiple days,
/// verifying the distribution of observations across shifts for the current day, the previous day, and two days prior when the
/// Result.Last aggregation is applied with a maximum count of 36.
/// </summary>
[Test]
public void Generate_Shift_Observations()
{
var dateTime = DateTime.Now;
if (dateTime.Day <= 2) dateTime = dateTime.AddDays(2);
var result = new List<BsonDocument>
public void Generate_Shift_Observations()
{
GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day, 12, 30, "FC", 100, Result.Last, null),
GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day, 10, 30, "FC", 108, Result.Last, null),
GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day, 9, 30, "FC", 106, Result.Last, null),
GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day, 5, 30, "FC", 112, Result.Last, null),
GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day, 4, 30, "FC", 105, Result.Last, null),
GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day - 1, 23, 30, "FC", 190, Result.Last, null),
GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day - 1, 19, 30, "FC", 120, Result.Last, null),
GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day - 1, 15, 30, "FC", 130, Result.Last, null),
GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day - 1, 12, 30, "FC", 140, Result.Last, null),
GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day - 1, 09, 30, "FC", 150, Result.Last, null),
GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day - 2, 15, 30, "FC", 150, Result.Last, null)
};
var groupedField = new GroupedField
{
StartTimeShift = ["08:00", "15:00", "22:00"],
Max = 36,
Result = [Result.Last]
};
var shiftObservations = _groupedObservationService.GenerateShiftObservations(result, groupedField);
var shift1ObsToday = shiftObservations.Count(s => s.Get("shift") == 0 && s.Get("day") == dateTime.Day);
var shift1ObsYesterday = shiftObservations.Count(s => s.Get("shift") == 0 && s.Get("day") == dateTime.Day - 1);
var shift2ObsYesterday = shiftObservations.Count(s => s.Get("shift") == 1 && s.Get("day") == dateTime.Day - 1);
var shif3ObsYesterday = shiftObservations.Count(s => s.Get("shift") == 2 && s.Get("day") == dateTime.Day - 1);
var shiftTwoDaysAgo = shiftObservations.Count(s => s.Get("shift") == 1 && s.Get("day") == dateTime.Day - 2);
using (Assert.EnterMultipleScope())
{
Assert.That(shift1ObsToday, Is.EqualTo(3));
Assert.That(shif3ObsYesterday, Is.EqualTo(3));
Assert.That(shift2ObsYesterday, Is.EqualTo(2));
Assert.That(shift1ObsYesterday, Is.EqualTo(2));
Assert.That(shiftTwoDaysAgo, Is.EqualTo(1));
};
}
var dateTime = DateTime.Now;
if (dateTime.Day <= 2) dateTime = dateTime.AddDays(2);
var result = new List<BsonDocument>
{
GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day, 12, 30, "FC", 100, Result.Last, null),
GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day, 10, 30, "FC", 108, Result.Last, null),
GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day, 9, 30, "FC", 106, Result.Last, null),
GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day, 5, 30, "FC", 112, Result.Last, null),
GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day, 4, 30, "FC", 105, Result.Last, null),
GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day - 1, 23, 30, "FC", 190, Result.Last, null),
GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day - 1, 19, 30, "FC", 120, Result.Last, null),
GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day - 1, 15, 30, "FC", 130, Result.Last, null),
GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day - 1, 12, 30, "FC", 140, Result.Last, null),
GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day - 1, 09, 30, "FC", 150, Result.Last, null),
GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day - 2, 15, 30, "FC", 150, Result.Last, null)
};
var groupedField = new GroupedField
{
StartTimeShift = ["08:00", "15:00", "22:00"],
Max = 36,
Result = [Result.Last]
};
var shiftObservations = _groupedObservationService.GenerateShiftObservations(result, groupedField);
var shift1ObsToday = shiftObservations.Count(s => s.Get("shift") == 0 && s.Get("day") == dateTime.Day);
var shift1ObsYesterday = shiftObservations.Count(s => s.Get("shift") == 0 && s.Get("day") == dateTime.Day - 1);
var shift2ObsYesterday = shiftObservations.Count(s => s.Get("shift") == 1 && s.Get("day") == dateTime.Day - 1);
var shif3ObsYesterday = shiftObservations.Count(s => s.Get("shift") == 2 && s.Get("day") == dateTime.Day - 1);
var shiftTwoDaysAgo = shiftObservations.Count(s => s.Get("shift") == 1 && s.Get("day") == dateTime.Day - 2);
using (Assert.EnterMultipleScope())
{
Assert.That(shift1ObsToday, Is.EqualTo(3));
Assert.That(shif3ObsYesterday, Is.EqualTo(3));
Assert.That(shift2ObsYesterday, Is.EqualTo(2));
Assert.That(shift1ObsYesterday, Is.EqualTo(2));
Assert.That(shiftTwoDaysAgo, Is.EqualTo(1));
};
}
/// <summary>
/// Verifies that the grouped observation service correctly calculates sum values for shift observations
/// across multiple days and shift periods (08:00, 15:00, 22:00), including observations that fall within
/// the current day, the previous day, and two days prior, ensuring that data is aggregated into the
/// correct shift and day buckets.
/// </summary>
[Test]
public void Calculate_Shift_Observations()
{
var dateTime = DateTime.Now;
if (dateTime.Day <= 2) dateTime = dateTime.AddDays(2);
var groupedField = new GroupedField
public void Calculate_Shift_Observations()
{
StartTimeShift = ["08:00", "15:00", "22:00"],
Max = 36,
Result = [Result.Sum]
};
var result = new List<BsonDocument>
{
GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day, 12, 30, "FC", 100, Result.Sum, null),
GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day, 10, 30, "FC", 108, Result.Sum, null),
GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day, 9, 30, "FC", 106, Result.Sum, null),
GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day, 5, 30, "FC", 112, Result.Sum, null),
GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day, 4, 30, "FC", 105, Result.Sum, null),
GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day - 1, 23, 30, "FC", 190, Result.Sum, null),
GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day - 1, 19, 30, "FC", 120, Result.Sum, null),
GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day - 1, 15, 30, "FC", 130, Result.Sum, null),
GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day - 1, 12, 30, "FC", 140, Result.Sum, null),
GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day - 1, 09, 30, "FC", 150, Result.Sum, null),
GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day - 2, 15, 30, "FC", 150, Result.Sum, null)
};
var shiftObservations = _groupedObservationService.GenerateShiftObservations(result, groupedField);
_groupedObservationService.CalculateShiftObservations(shiftObservations, groupedField);
var shift1ObsToday = shiftObservations.First(s => s.Get("shift") == 0 && s.Get("day") == dateTime.Day);
var shift1ObsYesterday = shiftObservations.First(s => s.Get("shift") == 0 && s.Get("day") == dateTime.Day - 1);
var shift2ObsYesterday = shiftObservations.First(s => s.Get("shift") == 1 && s.Get("day") == dateTime.Day - 1);
var shif3ObsYesterday = shiftObservations.First(s => s.Get("shift") == 2 && s.Get("day") == dateTime.Day - 1);
var shiftTwoDaysAgo = shiftObservations.First(s => s.Get("shift") == 1 && s.Get("day") == dateTime.Day - 2);
using (Assert.EnterMultipleScope())
{
Assert.That(shift1ObsToday.Get("sum")?.ToInt32(), Is.EqualTo(314));
Assert.That(shif3ObsYesterday.Get("sum")?.ToInt32(), Is.EqualTo(407));
Assert.That(shift2ObsYesterday.Get("sum")?.ToInt32(), Is.EqualTo(250));
Assert.That(shift1ObsYesterday.Get("sum")?.ToInt32(), Is.EqualTo(290));
Assert.That(shiftTwoDaysAgo.Get("sum")?.ToInt32(), Is.EqualTo(150));
};
}
var dateTime = DateTime.Now;
if (dateTime.Day <= 2) dateTime = dateTime.AddDays(2);
var groupedField = new GroupedField
{
StartTimeShift = ["08:00", "15:00", "22:00"],
Max = 36,
Result = [Result.Sum]
};
var result = new List<BsonDocument>
{
GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day, 12, 30, "FC", 100, Result.Sum, null),
GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day, 10, 30, "FC", 108, Result.Sum, null),
GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day, 9, 30, "FC", 106, Result.Sum, null),
GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day, 5, 30, "FC", 112, Result.Sum, null),
GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day, 4, 30, "FC", 105, Result.Sum, null),
GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day - 1, 23, 30, "FC", 190, Result.Sum, null),
GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day - 1, 19, 30, "FC", 120, Result.Sum, null),
GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day - 1, 15, 30, "FC", 130, Result.Sum, null),
GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day - 1, 12, 30, "FC", 140, Result.Sum, null),
GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day - 1, 09, 30, "FC", 150, Result.Sum, null),
GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day - 2, 15, 30, "FC", 150, Result.Sum, null)
};
var shiftObservations = _groupedObservationService.GenerateShiftObservations(result, groupedField);
_groupedObservationService.CalculateShiftObservations(shiftObservations, groupedField);
var shift1ObsToday = shiftObservations.First(s => s.Get("shift") == 0 && s.Get("day") == dateTime.Day);
var shift1ObsYesterday = shiftObservations.First(s => s.Get("shift") == 0 && s.Get("day") == dateTime.Day - 1);
var shift2ObsYesterday = shiftObservations.First(s => s.Get("shift") == 1 && s.Get("day") == dateTime.Day - 1);
var shif3ObsYesterday = shiftObservations.First(s => s.Get("shift") == 2 && s.Get("day") == dateTime.Day - 1);
var shiftTwoDaysAgo = shiftObservations.First(s => s.Get("shift") == 1 && s.Get("day") == dateTime.Day - 2);
using (Assert.EnterMultipleScope())
{
Assert.That(shift1ObsToday.Get("sum")?.ToInt32(), Is.EqualTo(314));
Assert.That(shif3ObsYesterday.Get("sum")?.ToInt32(), Is.EqualTo(407));
Assert.That(shift2ObsYesterday.Get("sum")?.ToInt32(), Is.EqualTo(250));
Assert.That(shift1ObsYesterday.Get("sum")?.ToInt32(), Is.EqualTo(290));
Assert.That(shiftTwoDaysAgo.Get("sum")?.ToInt32(), Is.EqualTo(150));
};
}
// [Test]
// public void Calculate_Half_Time_Observations()
@@ -223,265 +239,291 @@ public class GroupedObservationServiceTest
// });
// }
/// <summary>
/// Verifies that <c>CalculateLastFilledObservations</c> fills the missing hourly slot at <c>now - 4 hours</c>
/// so that the returned sequence contains a complete set of five consecutive hourly observations
/// (covering the last five hours relative to the current time) for a group with hourly regularity and a maximum of five entries.
/// </summary>
[Test]
public async Task Calculate_Last_Filled_Observations_should_add_Minus_4_hour_to_complete_last_five_hours()
{
var dateTime = DateTime.Now;
//Falta el -4 tiene que crearlo el calculate last filled observation
var result = new List<BsonDocument>
public async Task Calculate_Last_Filled_Observations_should_add_Minus_4_hour_to_complete_last_five_hours()
{
GenerateBsonDocument(dateTime.AddHours(-5).Year, dateTime.AddHours(-5).Month, dateTime.AddHours(-5).Day,
dateTime.AddHours(-5).Hour, 0, "TAM", 100, Result.LastFilled, null),
GenerateBsonDocument(dateTime.AddHours(-3).Year, dateTime.AddHours(-3).Month, dateTime.AddHours(-3).Day,
dateTime.AddHours(-3).Hour, 0, "TAM", 100, Result.LastFilled, null),
GenerateBsonDocument(dateTime.AddHours(-2).Year, dateTime.AddHours(-2).Month, dateTime.AddHours(-2).Day,
dateTime.AddHours(-2).Hour, 0, "TAM", 100, Result.LastFilled, null),
GenerateBsonDocument(dateTime.AddHours(-1).Year, dateTime.AddHours(-1).Month, dateTime.AddHours(-1).Day,
dateTime.AddHours(-1).Hour, 0, "TAM", 100, Result.LastFilled, null)
};
var groupedField = new GroupedField
{
Max = 5,
Name = "TAM",
Regularity = Regularity.Hour
};
var patientId = ObjectId.GenerateNewId();
var lastFilledObservations =
await _groupedObservationService.CalculateLastFilledObservations(result, groupedField, patientId);
using (Assert.EnterMultipleScope())
{
Assert.That(lastFilledObservations, Has.Count.EqualTo(5));
Assert.That(
lastFilledObservations.Exists(i => i.Get("time")?.ToLocalTime().Hour == dateTime.AddHours(-5).Hour),
Is.True);
Assert.That(
lastFilledObservations.Exists(i => i.Get("time")?.ToLocalTime().Hour == dateTime.AddHours(-4).Hour),
Is.True);
Assert.That(
lastFilledObservations.Exists(i => i.Get("time")?.ToLocalTime().Hour == dateTime.AddHours(-3).Hour),
Is.True);
Assert.That(
lastFilledObservations.Exists(i => i.Get("time")?.ToLocalTime().Hour == dateTime.AddHours(-2).Hour),
Is.True);
Assert.That(
lastFilledObservations.Exists(i => i.Get("time")?.ToLocalTime().Hour == dateTime.AddHours(-1).Hour),
Is.True);
};
}
var dateTime = DateTime.Now;
//Falta el -4 tiene que crearlo el calculate last filled observation
var result = new List<BsonDocument>
{
GenerateBsonDocument(dateTime.AddHours(-5).Year, dateTime.AddHours(-5).Month, dateTime.AddHours(-5).Day,
dateTime.AddHours(-5).Hour, 0, "TAM", 100, Result.LastFilled, null),
GenerateBsonDocument(dateTime.AddHours(-3).Year, dateTime.AddHours(-3).Month, dateTime.AddHours(-3).Day,
dateTime.AddHours(-3).Hour, 0, "TAM", 100, Result.LastFilled, null),
GenerateBsonDocument(dateTime.AddHours(-2).Year, dateTime.AddHours(-2).Month, dateTime.AddHours(-2).Day,
dateTime.AddHours(-2).Hour, 0, "TAM", 100, Result.LastFilled, null),
GenerateBsonDocument(dateTime.AddHours(-1).Year, dateTime.AddHours(-1).Month, dateTime.AddHours(-1).Day,
dateTime.AddHours(-1).Hour, 0, "TAM", 100, Result.LastFilled, null)
};
var groupedField = new GroupedField
{
Max = 5,
Name = "TAM",
Regularity = Regularity.Hour
};
var patientId = ObjectId.GenerateNewId();
var lastFilledObservations =
await _groupedObservationService.CalculateLastFilledObservations(result, groupedField, patientId);
using (Assert.EnterMultipleScope())
{
Assert.That(lastFilledObservations, Has.Count.EqualTo(5));
Assert.That(
lastFilledObservations.Exists(i => i.Get("time")?.ToLocalTime().Hour == dateTime.AddHours(-5).Hour),
Is.True);
Assert.That(
lastFilledObservations.Exists(i => i.Get("time")?.ToLocalTime().Hour == dateTime.AddHours(-4).Hour),
Is.True);
Assert.That(
lastFilledObservations.Exists(i => i.Get("time")?.ToLocalTime().Hour == dateTime.AddHours(-3).Hour),
Is.True);
Assert.That(
lastFilledObservations.Exists(i => i.Get("time")?.ToLocalTime().Hour == dateTime.AddHours(-2).Hour),
Is.True);
Assert.That(
lastFilledObservations.Exists(i => i.Get("time")?.ToLocalTime().Hour == dateTime.AddHours(-1).Hour),
Is.True);
};
}
/// <summary>
/// Verifies that <c>CalculateLastFilledObservations</c> fills missing hourly entries in the last five hours,
/// adding the <c>-1 hour</c> observation when it is not present in the provided list so that the returned
/// collection always contains a complete five-hour window of last filled observations.
/// </summary>
[Test]
public async Task Calculate_Last_Filled_Observations_should_add_Minus_1_hour_to_complete_last_five_hours()
{
var dateTime = DateTime.Now;
//Falta el -4 tiene que crearlo el calculate last filled observation
var result = new List<BsonDocument>
public async Task Calculate_Last_Filled_Observations_should_add_Minus_1_hour_to_complete_last_five_hours()
{
GenerateBsonDocument(dateTime.AddHours(-5).Year, dateTime.AddHours(-5).Month, dateTime.AddHours(-5).Day,
dateTime.AddHours(-5).Hour, 0, "TAM", 100, Result.LastFilled, null),
GenerateBsonDocument(dateTime.AddHours(-4).Year, dateTime.AddHours(-4).Month, dateTime.AddHours(-4).Day,
dateTime.AddHours(-4).Hour, 0, "TAM", 100, Result.LastFilled, null),
GenerateBsonDocument(dateTime.AddHours(-3).Year, dateTime.AddHours(-3).Month, dateTime.AddHours(-3).Day,
dateTime.AddHours(-3).Hour, 0, "TAM", 100, Result.LastFilled, null),
GenerateBsonDocument(dateTime.AddHours(-2).Year, dateTime.AddHours(-2).Month, dateTime.AddHours(-2).Day,
dateTime.AddHours(-2).Hour, 0, "TAM", 100, Result.LastFilled, null)
};
var groupedField = new GroupedField
{
Max = 5,
Name = "TAM",
Regularity = Regularity.Hour
};
var patientId = ObjectId.GenerateNewId();
var lastFilledObservations =
await _groupedObservationService.CalculateLastFilledObservations(result, groupedField, patientId);
using (Assert.EnterMultipleScope())
{
Assert.That(lastFilledObservations, Has.Count.EqualTo(5));
Assert.That(
lastFilledObservations.Exists(i => i.Get("time")?.ToLocalTime().Hour == dateTime.AddHours(-5).Hour),
Is.True);
Assert.That(
lastFilledObservations.Exists(i => i.Get("time")?.ToLocalTime().Hour == dateTime.AddHours(-4).Hour),
Is.True);
Assert.That(
lastFilledObservations.Exists(i => i.Get("time")?.ToLocalTime().Hour == dateTime.AddHours(-3).Hour),
Is.True);
Assert.That(
lastFilledObservations.Exists(i => i.Get("time")?.ToLocalTime().Hour == dateTime.AddHours(-2).Hour),
Is.True);
Assert.That(
lastFilledObservations.Exists(i => i.Get("time")?.ToLocalTime().Hour == dateTime.AddHours(-1).Hour),
Is.True);
};
}
var dateTime = DateTime.Now;
//Falta el -4 tiene que crearlo el calculate last filled observation
var result = new List<BsonDocument>
{
GenerateBsonDocument(dateTime.AddHours(-5).Year, dateTime.AddHours(-5).Month, dateTime.AddHours(-5).Day,
dateTime.AddHours(-5).Hour, 0, "TAM", 100, Result.LastFilled, null),
GenerateBsonDocument(dateTime.AddHours(-4).Year, dateTime.AddHours(-4).Month, dateTime.AddHours(-4).Day,
dateTime.AddHours(-4).Hour, 0, "TAM", 100, Result.LastFilled, null),
GenerateBsonDocument(dateTime.AddHours(-3).Year, dateTime.AddHours(-3).Month, dateTime.AddHours(-3).Day,
dateTime.AddHours(-3).Hour, 0, "TAM", 100, Result.LastFilled, null),
GenerateBsonDocument(dateTime.AddHours(-2).Year, dateTime.AddHours(-2).Month, dateTime.AddHours(-2).Day,
dateTime.AddHours(-2).Hour, 0, "TAM", 100, Result.LastFilled, null)
};
var groupedField = new GroupedField
{
Max = 5,
Name = "TAM",
Regularity = Regularity.Hour
};
var patientId = ObjectId.GenerateNewId();
var lastFilledObservations =
await _groupedObservationService.CalculateLastFilledObservations(result, groupedField, patientId);
using (Assert.EnterMultipleScope())
{
Assert.That(lastFilledObservations, Has.Count.EqualTo(5));
Assert.That(
lastFilledObservations.Exists(i => i.Get("time")?.ToLocalTime().Hour == dateTime.AddHours(-5).Hour),
Is.True);
Assert.That(
lastFilledObservations.Exists(i => i.Get("time")?.ToLocalTime().Hour == dateTime.AddHours(-4).Hour),
Is.True);
Assert.That(
lastFilledObservations.Exists(i => i.Get("time")?.ToLocalTime().Hour == dateTime.AddHours(-3).Hour),
Is.True);
Assert.That(
lastFilledObservations.Exists(i => i.Get("time")?.ToLocalTime().Hour == dateTime.AddHours(-2).Hour),
Is.True);
Assert.That(
lastFilledObservations.Exists(i => i.Get("time")?.ToLocalTime().Hour == dateTime.AddHours(-1).Hour),
Is.True);
};
}
/// <summary>
/// Verifies that the service correctly fills hourly observations from half-hour observation data, grouping timestamped values by hour and forwarding missing slots up to the configured maximum (5) per hour.
/// </summary>
[Test]
public void FillHoursObservations()
{
/*
09:18 - Toma 1: 50/100/75
09:38 - Toma 2: 52/102/76
10:07 - Toma 3: 54/101/72
10:18 - Toma 4: 51/102/73
10:58 - Toma 5: 52/107/79
12:04 - Toma 6: 60/120/90
El api convierte a
09h - Toma 2: 52/102/76
10h - Toma 4: 51/102/73
11h - Toma 5: 52/107/79
12h- Toma 6: 60/120/90
*/
var groupedField = new GroupedField
public void FillHoursObservations()
{
Max = 5,
Name = "TAM",
Regularity = Regularity.Hour
};
var dateTime =
new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, 12, 30, 0).ToUniversalTime();
var values9 = new BsonArray
{
new BsonDocument
/*
09:18 - Toma 1: 50/100/75
09:38 - Toma 2: 52/102/76
10:07 - Toma 3: 54/101/72
10:18 - Toma 4: 51/102/73
10:58 - Toma 5: 52/107/79
12:04 - Toma 6: 60/120/90
El api convierte a
09h - Toma 2: 52/102/76
10h - Toma 4: 51/102/73
11h - Toma 5: 52/107/79
12h- Toma 6: 60/120/90
*/
var groupedField = new GroupedField
{
{ "value", 50 },
{
"time",
new BsonDateTime(
new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, 9, 18, 0).ToUniversalTime())
}
},
new BsonDocument
Max = 5,
Name = "TAM",
Regularity = Regularity.Hour
};
var dateTime =
new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, 12, 30, 0).ToUniversalTime();
var values9 = new BsonArray
{
{ "value", 100 },
new BsonDocument
{
"time",
new BsonDateTime(
new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, 9, 38, 0).ToUniversalTime())
{ "value", 50 },
{
"time",
new BsonDateTime(
new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, 9, 18, 0).ToUniversalTime())
}
},
new BsonDocument
{
{ "value", 100 },
{
"time",
new BsonDateTime(
new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, 9, 38, 0).ToUniversalTime())
}
}
}
};
var values10 = new BsonArray
{
new BsonDocument
};
var values10 = new BsonArray
{
{ "value", 50 },
new BsonDocument
{
"time",
new BsonDateTime(
new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, 10, 7, 0).ToUniversalTime())
{ "value", 50 },
{
"time",
new BsonDateTime(
new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, 10, 7, 0).ToUniversalTime())
}
},
new BsonDocument
{
{ "value", 60 },
{
"time",
new BsonDateTime(new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, 10, 18, 0)
.ToUniversalTime())
}
},
new BsonDocument
{
{ "value", 70 },
{
"time",
new BsonDateTime(new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, 10, 58, 0)
.ToUniversalTime())
}
}
},
new BsonDocument
};
var values12 = new BsonArray
{
{ "value", 60 },
new BsonDocument
{
"time",
new BsonDateTime(new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, 10, 18, 0)
.ToUniversalTime())
{ "value", 50 },
{
"time",
new BsonDateTime(new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, 12, 04, 0)
.ToUniversalTime())
}
}
},
new BsonDocument
};
var result = new List<BsonDocument>
{
{ "value", 70 },
{
"time",
new BsonDateTime(new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, 10, 58, 0)
.ToUniversalTime())
}
}
};
var values12 = new BsonArray
{
new BsonDocument
{
{ "value", 50 },
{
"time",
new BsonDateTime(new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, 12, 04, 0)
.ToUniversalTime())
}
}
};
var result = new List<BsonDocument>
{
GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day, 9, 0, "TAM", 100, Result.HalfHour,
values9),
GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day, 10, 0, "TAM", 100, Result.HalfHour,
values10),
GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day, 12, 0, "TAM", 100, Result.HalfHour,
values12)
};
var halfObservations = _groupedObservationService.CalculateHalfHourObservations(result);
var fillHoursObservations = _groupedObservationService.FillHours(halfObservations, groupedField);
Assert.That(fillHoursObservations, Is.Not.Empty);
}
GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day, 9, 0, "TAM", 100, Result.HalfHour,
values9),
GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day, 10, 0, "TAM", 100, Result.HalfHour,
values10),
GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day, 12, 0, "TAM", 100, Result.HalfHour,
values12)
};
var halfObservations = _groupedObservationService.CalculateHalfHourObservations(result);
var fillHoursObservations = _groupedObservationService.FillHours(halfObservations, groupedField);
Assert.That(fillHoursObservations, Is.Not.Empty);
}
/// <summary>
/// Generates a BsonDocument representing a time-stamped result entry, with date component fallbacks to the current date when null and conditional payload structure based on the result type.
/// </summary>
/// <param name="year">The year component of the entry timestamp, or null to default to the current year.</param>
/// <param name="month">The month component of the entry timestamp, or null to default to the current month.</param>
/// <param name="day">The day component of the entry timestamp, or null to default to the current day.</param>
/// <param name="hour">The hour component of the entry timestamp, or null to default to the current hour.</param>
/// <param name="minute">The minute component of the entry timestamp, or null to default to the current minute.</param>
/// <param name="name">The name identifier included in the document's _id.</param>
/// <param name="value">The raw value associated with the result.</param>
/// <param name="resultType">The result type that determines how the value is stored; aggregate types (HalfHour, Sum, Count, Min, Average, Max) are stored directly, while others are wrapped in a sub-document with time and stringified value.</param>
/// <param name="all">Optional BsonArray of additional entries; when provided, it is appended to the result under the "all" key.</param>
/// <returns>A BsonDocument containing the _id composite key, a time field, the result-type-specific payload, and optionally the "all" array.</returns>
private static BsonDocument GenerateBsonDocument(int? year, int? month, int? day, int? hour, int? minute,
string name, object value, Result resultType, BsonArray? all)
{
var result = new BsonDocument
string name, object value, Result resultType, BsonArray? all)
{
var result = new BsonDocument
{
"_id", new BsonDocument
{
{ "year", year ?? new DateTime().Year },
{ "month", month ?? new DateTime().Month },
{ "day", day ?? new DateTime().Day },
{ "hour", hour ?? new DateTime().Hour },
{ "minute", minute ?? new DateTime().Minute },
{ "name", name }
}
},
{ "time", new BsonDateTime(new DateTime((int)year!, (int)month!, (int)day!, (int)hour!, (int)minute!, 0)) }
};
/*
{resultType.ToString().ToLower(), new BsonDocument{
{"time", new BsonDateTime(new System.DateTime((int)year, (int)month, (int)day, (int)hour, (int)minute,0)) },
{"value", value.ToString() }
}},
{ "time", new BsonDateTime(new System.DateTime((int)year, (int)month, (int)day, (int)hour, (int)minute,0)) },
};
*/
if (resultType != Result.HalfHour && resultType != Result.Sum && resultType != Result.Count
&& resultType != Result.Min && resultType != Result.Average && resultType != Result.Max)
result.Add(resultType.ToString().ToLower(), new BsonDocument
{
{ "time", new BsonDateTime(new DateTime((int)year, (int)month, (int)day, (int)hour, (int)minute, 0)) },
{ "value", value.ToString() }
});
else
result.AddRange(new Dictionary<string, object> { { resultType.ToString().ToLower(), value } });
if (all != null) result.Add("all", all);
return result;
}
"_id", new BsonDocument
{
{ "year", year ?? new DateTime().Year },
{ "month", month ?? new DateTime().Month },
{ "day", day ?? new DateTime().Day },
{ "hour", hour ?? new DateTime().Hour },
{ "minute", minute ?? new DateTime().Minute },
{ "name", name }
}
},
{ "time", new BsonDateTime(new DateTime((int)year!, (int)month!, (int)day!, (int)hour!, (int)minute!, 0)) }
};
/*
{resultType.ToString().ToLower(), new BsonDocument{
{"time", new BsonDateTime(new System.DateTime((int)year, (int)month, (int)day, (int)hour, (int)minute,0)) },
{"value", value.ToString() }
}},
{ "time", new BsonDateTime(new System.DateTime((int)year, (int)month, (int)day, (int)hour, (int)minute,0)) },
};
*/
if (resultType != Result.HalfHour && resultType != Result.Sum && resultType != Result.Count
&& resultType != Result.Min && resultType != Result.Average && resultType != Result.Max)
result.Add(resultType.ToString().ToLower(), new BsonDocument
{
{ "time", new BsonDateTime(new DateTime((int)year, (int)month, (int)day, (int)hour, (int)minute, 0)) },
{ "value", value.ToString() }
});
else
result.AddRange(new Dictionary<string, object> { { resultType.ToString().ToLower(), value } });
if (all != null) result.Add("all", all);
return result;
}
}
@@ -13,14 +13,17 @@ namespace adas_core.Test.Services;
[NonParallelizable]
internal class HistoricalConfigChangesServiceTest
{
/// <summary>
/// Initializes the mock repository, mock logger, and the <see cref="HistoricalConfigChangesService"/> instance under test prior to each unit test execution.
/// </summary>
[SetUp]
public void Setup()
{
_mockRepo = new Mock<IHistoricalConfigChangesRepository>();
_mockLogger = new Mock<ILogger<HistoricalConfigChangesService>>();
_service = new HistoricalConfigChangesService(_mockRepo.Object, _mockLogger.Object, _httpContextAccessor.Object,
_auditService.Object);
}
public void Setup()
{
_mockRepo = new Mock<IHistoricalConfigChangesRepository>();
_mockLogger = new Mock<ILogger<HistoricalConfigChangesService>>();
_service = new HistoricalConfigChangesService(_mockRepo.Object, _mockLogger.Object, _httpContextAccessor.Object,
_auditService.Object);
}
private HistoricalConfigChangesService _service;
private Mock<IHistoricalConfigChangesRepository> _mockRepo;
@@ -29,108 +32,129 @@ internal class HistoricalConfigChangesServiceTest
private readonly Mock<ILocalAuditService> _auditService = new();
/// <summary>
/// Verifies that <see cref="FindLastConfigChanges"/> returns the last configuration change retrieved from the repository for the specified configuration type.
/// </summary>
/// <param name="configType">The type of configuration whose last historical changes are being retrieved.</param>
/// <returns>A task that represents the asynchronous test execution.</returns>
[Test]
public async Task FindLastConfigChanges_ReturnsLastConfigChange()
{
// Arrange
var configType = DisplayConfigEnums.ConfigTypes.ObservationCfg;
var mockResult = new List<HistoricalConfigChanges>
public async Task FindLastConfigChanges_ReturnsLastConfigChange()
{
new() // Mock object with desired properties
};
_mockRepo.Setup(r => r.FindLastHistoricalConfigChangesByType(configType, 10)).ReturnsAsync(mockResult);
// Act
var result = await _service.FindLastConfigChanges(configType);
// Assert
Assert.That(result, Is.Not.Null);
// Further assertions based on the expected result
}
// Arrange
var configType = DisplayConfigEnums.ConfigTypes.ObservationCfg;
var mockResult = new List<HistoricalConfigChanges>
{
new() // Mock object with desired properties
};
_mockRepo.Setup(r => r.FindLastHistoricalConfigChangesByType(configType, 10)).ReturnsAsync(mockResult);
// Act
var result = await _service.FindLastConfigChanges(configType);
// Assert
Assert.That(result, Is.Not.Null);
// Further assertions based on the expected result
}
/// <summary>
/// Verifies that the service returns <c>null</c> when the underlying repository throws an exception while inserting a <see cref="HistoricalConfigChanges"/> entity.
/// </summary>
[Test]
public async Task InsertOne_WhenExceptionOccurs_ReturnsNull()
{
// Arrange
var mockChange = new HistoricalConfigChanges();
_mockRepo.Setup(r => r.InsertOneAsync(mockChange)).Throws(new Exception());
// Act
var result = await _service.InsertOne(mockChange);
// Assert
Assert.That(result, Is.Null);
}
[Test]
public async Task GetAll_ReturnsAllConfigChanges()
{
// Arrange
var mockResult = new List<HistoricalConfigChanges>
public async Task InsertOne_WhenExceptionOccurs_ReturnsNull()
{
new(),
new()
};
_mockRepo.Setup(r => r.FindAll()).ReturnsAsync(mockResult);
// Arrange
var mockChange = new HistoricalConfigChanges();
_mockRepo.Setup(r => r.InsertOneAsync(mockChange)).Throws(new Exception());
// Act
var result = await _service.InsertOne(mockChange);
// Assert
Assert.That(result, Is.Null);
}
// Act
var result = await _service.GetAll();
// Assert
Assert.That(result, Has.Count.EqualTo(2));
}
/// <summary>
/// Verifies that the service's <c>GetAll</c> method returns all historical configuration changes retrieved from the repository.
/// </summary>
[Test]
public async Task GetByType_ReturnsCorrectAmountOfConfigs()
{
// Arrange
var configType = DisplayConfigEnums.ConfigTypes.ObservationCfg;
var mockResult = new List<HistoricalConfigChanges>
public async Task GetAll_ReturnsAllConfigChanges()
{
new(),
new(),
new()
};
_mockRepo.Setup(r => r.FindLastHistoricalConfigChangesByType(configType, 3)).ReturnsAsync(mockResult);
// Act
var result = await _service.GetByType(configType, 3);
// Assert
Assert.That(result, Has.Count.EqualTo(3));
}
// Arrange
var mockResult = new List<HistoricalConfigChanges>
{
new(),
new()
};
_mockRepo.Setup(r => r.FindAll()).ReturnsAsync(mockResult);
// Act
var result = await _service.GetAll();
// Assert
Assert.That(result, Has.Count.EqualTo(2));
}
/// <summary>
/// Verifies that the service's <c>GetByType</c> method returns the expected number of historical configuration changes for the specified configuration type.
/// </summary>
[Test]
public async Task GetByUser_ReturnsCorrectConfigs()
{
// Arrange
var user = "TestUser";
var mockResult = new List<HistoricalConfigChanges>
public async Task GetByType_ReturnsCorrectAmountOfConfigs()
{
new(),
new()
};
_mockRepo.Setup(r => r.FindLastHistoricalConfigChangesByUser(user, null, 2)).ReturnsAsync(mockResult);
// Act
var result = await _service.GetByUser(user, null, 2);
// Assert
Assert.That(result, Has.Count.EqualTo(2));
}
// Arrange
var configType = DisplayConfigEnums.ConfigTypes.ObservationCfg;
var mockResult = new List<HistoricalConfigChanges>
{
new(),
new(),
new()
};
_mockRepo.Setup(r => r.FindLastHistoricalConfigChangesByType(configType, 3)).ReturnsAsync(mockResult);
// Act
var result = await _service.GetByType(configType, 3);
// Assert
Assert.That(result, Has.Count.EqualTo(3));
}
/// <summary>
/// Verifies that the service returns the expected number of historical configuration changes for a given user,
/// confirming the repository result is correctly forwarded to the caller.
/// </summary>
[Test]
public async Task UpdateHistoricalConfigChange_WhenExceptionOccurs_ReturnsNull()
{
// Arrange
var mockChange = new HistoricalConfigChanges();
_mockRepo.Setup(r => r.Update(mockChange)).Throws(new Exception());
public async Task GetByUser_ReturnsCorrectConfigs()
{
// Arrange
var user = "TestUser";
var mockResult = new List<HistoricalConfigChanges>
{
new(),
new()
};
_mockRepo.Setup(r => r.FindLastHistoricalConfigChangesByUser(user, null, 2)).ReturnsAsync(mockResult);
// Act
var result = await _service.GetByUser(user, null, 2);
// Assert
Assert.That(result, Has.Count.EqualTo(2));
}
// Act
var result = await _service.UpdateHistoricalConfigChange(mockChange);
// Assert
Assert.That(result, Is.Null);
}
/// <summary>
/// Verifies that the service returns null when an exception is thrown while updating a historical configuration change, ensuring graceful error handling.
/// </summary>
[Test]
public async Task UpdateHistoricalConfigChange_WhenExceptionOccurs_ReturnsNull()
{
// Arrange
var mockChange = new HistoricalConfigChanges();
_mockRepo.Setup(r => r.Update(mockChange)).Throws(new Exception());
// Act
var result = await _service.UpdateHistoricalConfigChange(mockChange);
// Assert
Assert.That(result, Is.Null);
}
}
@@ -9,113 +9,137 @@ public class InMemoryLockProviderTest
{
private InMemoryLockProvider _provider = null!;
/// <summary>
/// Initializes a fresh <see cref="InMemoryLockProvider"/> instance for the test fixture, ensuring each test starts with a clean, isolated provider state.
/// </summary>
[SetUp]
public void SetUp()
{
_provider = new InMemoryLockProvider();
}
public void SetUp()
{
_provider = new InMemoryLockProvider();
}
/// <summary>
/// Retrieves the private <c>_locks</c> dictionary from the associated <see cref="InMemoryLockProvider"/> instance using reflection, exposing the underlying locks for inspection or manipulation in tests.
/// </summary>
/// <returns>The <see cref="ConcurrentDictionary{TKey, TValue}"/> mapping lock keys to their <see cref="SemaphoreSlim"/> instances held by the provider.</returns>
private ConcurrentDictionary<string, SemaphoreSlim> GetLocks()
{
var field = typeof(InMemoryLockProvider)
.GetField("_locks", BindingFlags.NonPublic | BindingFlags.Instance);
return (ConcurrentDictionary<string, SemaphoreSlim>)field!.GetValue(_provider)!;
}
{
var field = typeof(InMemoryLockProvider)
.GetField("_locks", BindingFlags.NonPublic | BindingFlags.Instance);
return (ConcurrentDictionary<string, SemaphoreSlim>)field!.GetValue(_provider)!;
}
#region TC-50
/// <summary>
/// Verifies that AcquireAsync returns <c>true</c> and creates a corresponding entry in the locks dictionary when called with a valid key and time span.
/// </summary>
[Test]
public async Task AcquireAsync_ReturnsTrue_AndCreatesEntryInLocksDictionary()
{
var result = await _provider.AcquireAsync("key", TimeSpan.FromSeconds(1));
Assert.That(result, Is.True);
var locks = GetLocks();
Assert.That(locks.ContainsKey("key"), Is.True);
}
public async Task AcquireAsync_ReturnsTrue_AndCreatesEntryInLocksDictionary()
{
var result = await _provider.AcquireAsync("key", TimeSpan.FromSeconds(1));
Assert.That(result, Is.True);
var locks = GetLocks();
Assert.That(locks.ContainsKey("key"), Is.True);
}
#endregion
#region TC-51
/// <summary>
/// Verifies that AcquireAsync returns false when the semaphore for the specified key is already occupied and the requested timeout expires before the lock can be acquired, and that the underlying semaphore is restored to a count of 1 after release.
/// </summary>
[Test]
public async Task AcquireAsync_ReturnsFalse_WhenSemaphoreOccupiedAndTimeoutExpires()
{
await _provider.AcquireAsync("key", TimeSpan.FromSeconds(5));
var result = await _provider.AcquireAsync("key", TimeSpan.FromMilliseconds(100));
Assert.That(result, Is.False);
await _provider.ReleaseAsync("key");
var locks = GetLocks();
Assert.That(locks["key"].CurrentCount, Is.EqualTo(1));
}
public async Task AcquireAsync_ReturnsFalse_WhenSemaphoreOccupiedAndTimeoutExpires()
{
await _provider.AcquireAsync("key", TimeSpan.FromSeconds(5));
var result = await _provider.AcquireAsync("key", TimeSpan.FromMilliseconds(100));
Assert.That(result, Is.False);
await _provider.ReleaseAsync("key");
var locks = GetLocks();
Assert.That(locks["key"].CurrentCount, Is.EqualTo(1));
}
#endregion
#region TC-52
/// <summary>
/// Verifies that calling <c>ReleaseAsync</c> on a semaphore provider restores availability,
/// allowing a subsequent <c>AcquireAsync</c> for the same key to succeed and the semaphore's
/// <c>CurrentCount</c> to transition back to its released value.
/// </summary>
[Test]
public async Task ReleaseAsync_MakesSemaphoreAvailableForNextAcquire()
{
await _provider.AcquireAsync("key", TimeSpan.FromSeconds(1));
await _provider.ReleaseAsync("key");
var result = await _provider.AcquireAsync("key", TimeSpan.FromSeconds(1));
Assert.That(result, Is.True);
var locks = GetLocks();
Assert.That(locks["key"].CurrentCount, Is.EqualTo(0));
await _provider.ReleaseAsync("key");
Assert.That(locks["key"].CurrentCount, Is.EqualTo(1));
}
public async Task ReleaseAsync_MakesSemaphoreAvailableForNextAcquire()
{
await _provider.AcquireAsync("key", TimeSpan.FromSeconds(1));
await _provider.ReleaseAsync("key");
var result = await _provider.AcquireAsync("key", TimeSpan.FromSeconds(1));
Assert.That(result, Is.True);
var locks = GetLocks();
Assert.That(locks["key"].CurrentCount, Is.EqualTo(0));
await _provider.ReleaseAsync("key");
Assert.That(locks["key"].CurrentCount, Is.EqualTo(1));
}
#endregion
#region TC-53
/// <summary>
/// Verifies that <c>ReleaseAsync</c> does not throw when invoked without a prior <c>AcquireAsync</c> for the given key, and remains safe to call multiple times after a single successful acquire.
/// </summary>
[Test]
public async Task ReleaseAsync_DoesNotThrow_WhenCalledWithoutPriorAcquire()
{
await _provider.ReleaseAsync("nonexistent-key");
await _provider.AcquireAsync("key", TimeSpan.FromSeconds(1));
await _provider.ReleaseAsync("key");
await _provider.ReleaseAsync("key");
}
public async Task ReleaseAsync_DoesNotThrow_WhenCalledWithoutPriorAcquire()
{
await _provider.ReleaseAsync("nonexistent-key");
await _provider.AcquireAsync("key", TimeSpan.FromSeconds(1));
await _provider.ReleaseAsync("key");
await _provider.ReleaseAsync("key");
}
#endregion
#region TC-54
/// <summary>
/// Verifies that <c>AcquireAsync</c> is thread-safe for a given key, ensuring that only one caller can hold the semaphore at a time even when multiple tasks compete concurrently for the same key, and that a single semaphore instance is created and reused per key.
/// </summary>
[Test]
public async Task AcquireAsync_IsThreadSafe_OnlyOneHolderAtATime_SingleSemaphorePerKey()
{
const string key = "same-key";
const int threadCount = 10;
var acquiredCount = 0;
var currentHolders = 0;
var tasks = Enumerable.Range(0, threadCount).Select(_ => Task.Run(async () =>
public async Task AcquireAsync_IsThreadSafe_OnlyOneHolderAtATime_SingleSemaphorePerKey()
{
var acquired = await _provider.AcquireAsync(key, TimeSpan.FromSeconds(10));
Assert.That(acquired, Is.True);
var current = Interlocked.Increment(ref currentHolders);
Assert.That(current, Is.EqualTo(1));
Interlocked.Increment(ref acquiredCount);
await Task.Delay(5);
Interlocked.Decrement(ref currentHolders);
await _provider.ReleaseAsync(key);
})).ToArray();
await Task.WhenAll(tasks);
Assert.That(acquiredCount, Is.EqualTo(threadCount));
var locks = GetLocks();
Assert.That(locks.ContainsKey(key), Is.True);
Assert.That(locks.Keys.Count(k => k == key), Is.EqualTo(1));
}
const string key = "same-key";
const int threadCount = 10;
var acquiredCount = 0;
var currentHolders = 0;
var tasks = Enumerable.Range(0, threadCount).Select(_ => Task.Run(async () =>
{
var acquired = await _provider.AcquireAsync(key, TimeSpan.FromSeconds(10));
Assert.That(acquired, Is.True);
var current = Interlocked.Increment(ref currentHolders);
Assert.That(current, Is.EqualTo(1));
Interlocked.Increment(ref acquiredCount);
await Task.Delay(5);
Interlocked.Decrement(ref currentHolders);
await _provider.ReleaseAsync(key);
})).ToArray();
await Task.WhenAll(tasks);
Assert.That(acquiredCount, Is.EqualTo(threadCount));
var locks = GetLocks();
Assert.That(locks.ContainsKey(key), Is.True);
Assert.That(locks.Keys.Count(k => k == key), Is.EqualTo(1));
}
#endregion
}
@@ -15,25 +15,30 @@ namespace adas_core.Test.Services;
[TestFixture]
public class LightBeaconServiceTest
{
/// <summary>
/// Initializes the test fixture by creating mock instances of the service's dependencies
/// and instantiating the <see cref="LightBeaconService"/> under test with those mocks.
/// This setup runs before each test to ensure a clean, isolated test environment.
/// </summary>
[SetUp]
public void Setup()
{
_optionsApiSettings = Options.Create(_apiSettings);
_pocService = new Mock<IPointOfCareService>();
_clientMessageService = new Mock<IClientMessageService>();
_subscribersService = new Mock<ISubscribersService>();
_logger = new Mock<ILogger<LightBeaconService>>();
_lightBeaconRepository = new Mock<ILightBeaconRepository>();
_lightBeaconService = new LightBeaconService(
_optionsApiSettings,
_logger.Object,
_clientMessageService.Object,
_subscribersService.Object,
_pocService.Object,
_lightBeaconRepository.Object
);
}
public void Setup()
{
_optionsApiSettings = Options.Create(_apiSettings);
_pocService = new Mock<IPointOfCareService>();
_clientMessageService = new Mock<IClientMessageService>();
_subscribersService = new Mock<ISubscribersService>();
_logger = new Mock<ILogger<LightBeaconService>>();
_lightBeaconRepository = new Mock<ILightBeaconRepository>();
_lightBeaconService = new LightBeaconService(
_optionsApiSettings,
_logger.Object,
_clientMessageService.Object,
_subscribersService.Object,
_pocService.Object,
_lightBeaconRepository.Object
);
}
private LightBeaconService _lightBeaconService = null!;
@@ -51,20 +56,23 @@ public class LightBeaconServiceTest
private Mock<ISubscribersService> _subscribersService = null!;
private Mock<ILightBeaconRepository> _lightBeaconRepository = null!;
/// <summary>
/// Integration test that verifies the light beacon service returns <see cref="LightBeaconColor.Off"/> for a given point of care when the patient has no associated beacon color.
/// </summary>
[Ignore("Integration test")]
[Test]
public async Task GetBeaconColor()
{
var patient = new Patient
[Test]
public async Task GetBeaconColor()
{
UnitId = ObjectId.GenerateNewId(),
PointOfCareId = ObjectId.GenerateNewId()
};
var color = await _lightBeaconService.GetColor(patient.PointOfCareId.Value);
Assert.That(color, Is.EqualTo(
LightBeaconColor.Off));
}
var patient = new Patient
{
UnitId = ObjectId.GenerateNewId(),
PointOfCareId = ObjectId.GenerateNewId()
};
var color = await _lightBeaconService.GetColor(patient.PointOfCareId.Value);
Assert.That(color, Is.EqualTo(
LightBeaconColor.Off));
}
}
@@ -16,6 +16,11 @@ namespace adas_core.Test.Services;
[TestFixture]
public class MasterListServiceTest
{
/// <summary>
/// Initializes all required mock dependencies and constructs a <see cref="MasterListService{MasterList}"/> instance
/// for use in unit tests. Configures the HTTP context with a test user principal, registers the master list repository
/// in the service provider, and supplies default API settings.
/// </summary>
[SetUp]
public void Setup()
{
@@ -83,6 +88,9 @@ public class MasterListServiceTest
private MasterListService<MasterList> _service = null!;
/// <summary>
/// Verifies that <c>DeleteMasterListById</c> invokes the repository's <c>Delete</c> method once with the provided identifier when the master list is found.
/// </summary>
[Test]
public async Task DeleteMasterListById_ShouldCallRepositoryDelete_WhenMasterListFound()
{
@@ -117,6 +125,9 @@ public class MasterListServiceTest
// Assert.That(masterLists, Is.EqualTo(result));
// }
/// <summary>
/// Verifies that the service returns the master list when it is found by the specified id.
/// </summary>
[Test]
public async Task GetMasterListById_ShouldReturnMasterList_WhenFound()
{
@@ -133,6 +144,9 @@ public class MasterListServiceTest
Assert.That(masterList, Is.EqualTo(result));
}
/// <summary>
/// Verifies that <c>GetMasterListByName</c> returns the matching <see cref="MasterList"/> when a master list with the specified name is found in the repository.
/// </summary>
[Test]
public async Task GetMasterListByName_ShouldReturnMasterList_WhenFound()
{
@@ -149,6 +163,9 @@ public class MasterListServiceTest
Assert.That(masterList, Is.EqualTo(result));
}
/// <summary>
/// Verifies that the InsertMasterList service method returns the same <see cref="MasterList"/> instance that was inserted, ensuring the service correctly retrieves the inserted entity by its identifier after persistence.
/// </summary>
[Test]
public async Task InsertMasterList_ShouldReturnInsertedMasterList()
{
@@ -166,6 +183,10 @@ public class MasterListServiceTest
Assert.That(masterList, Is.EqualTo(result));
}
/// <summary>
/// Verifies that UpdateMasterList returns the updated master list when the repository successfully completes the update and finds the entity by id.
/// </summary>
/// <returns>A <see cref="Task"/> representing the asynchronous test execution.</returns>
[Test]
public async Task UpdateMasterList_ShouldReturnUpdatedMasterList()
{
+74 -53
View File
@@ -7,11 +7,15 @@ public class NoCacheServiceTest
{
private NoCacheService _noCacheService = null!;
/// <summary>
/// Initializes a fresh <see cref="NoCacheService"/> instance and assigns it to <c>_noCacheService</c>
/// to provide a clean, dependency-free test fixture prior to executing each test.
/// </summary>
[SetUp]
public void SetUp()
{
_noCacheService = new NoCacheService();
}
public void SetUp()
{
_noCacheService = new NoCacheService();
}
#region TC-27
@@ -67,75 +71,92 @@ public class NoCacheServiceTest
#region TC-28
/// <summary>
/// Verifies that the no-cache service implementation never persists data to any backend when <c>GetOrSetObjectAsync</c> is invoked, and confirms that a subsequent <c>DeleteObjectAsync</c> on the same key does not throw.
/// </summary>
[Test]
public async Task GetOrSetObjectAsync_DoesNotStoreData_InAnyBackend()
{
var key = "patients:abc123";
var expectedResult = new { Name = "TestPatient" };
Func<Task<object>> factory = () => Task.FromResult<object>(expectedResult);
await _noCacheService.GetOrSetObjectAsync(key, factory);
var retrieved = await _noCacheService.GetObjectAsync<object>(key);
Assert.That(retrieved, Is.Null);
Func<Task> act = async () => await _noCacheService.DeleteObjectAsync(key);
Assert.That(act, Throws.Nothing);
}
public async Task GetOrSetObjectAsync_DoesNotStoreData_InAnyBackend()
{
var key = "patients:abc123";
var expectedResult = new { Name = "TestPatient" };
Func<Task<object>> factory = () => Task.FromResult<object>(expectedResult);
await _noCacheService.GetOrSetObjectAsync(key, factory);
var retrieved = await _noCacheService.GetObjectAsync<object>(key);
Assert.That(retrieved, Is.Null);
Func<Task> act = async () => await _noCacheService.DeleteObjectAsync(key);
Assert.That(act, Throws.Nothing);
}
#endregion
#region TC-29
/// <summary>
/// Verifies that the no-cache service's <c>DeleteByPatternAsync</c> returns 0 and does not throw when invoked with arbitrary patterns, confirming the no-op delete behavior for any key pattern.
/// </summary>
[Test]
public async Task DeleteByPatternAsync_ReturnsZero_DoesNotThrow()
{
// Act
var result = await _noCacheService.DeleteByPatternAsync("patients:*");
var result2 = await _noCacheService.DeleteByPatternAsync("any:pattern:*");
// Assert
Assert.That(result, Is.EqualTo(0));
Assert.That(result2, Is.EqualTo(0));
}
public async Task DeleteByPatternAsync_ReturnsZero_DoesNotThrow()
{
// Act
var result = await _noCacheService.DeleteByPatternAsync("patients:*");
var result2 = await _noCacheService.DeleteByPatternAsync("any:pattern:*");
// Assert
Assert.That(result, Is.EqualTo(0));
Assert.That(result2, Is.EqualTo(0));
}
#endregion
#region TC-30
/// <summary>
/// Verifies that calling CleanCache on the no-cache service is a safe no-op that does not throw,
/// and that subsequent calls to GetValue consistently return <c>null</c> for any key.
/// </summary>
[Test]
public void CleanCache_IsNoOp_DoesNotThrow()
{
Action act = () => _noCacheService.CleanCache();
Assert.That(act, Throws.Nothing);
var result = _noCacheService.GetValue("any-key");
Assert.That(result, Is.Null);
}
public void CleanCache_IsNoOp_DoesNotThrow()
{
Action act = () => _noCacheService.CleanCache();
Assert.That(act, Throws.Nothing);
var result = _noCacheService.GetValue("any-key");
Assert.That(result, Is.Null);
}
/// <summary>
/// Verifies that calling <c>SetValue</c> on the no-cache service is a no-op and does not throw any exception.
/// </summary>
[Test]
public void SetValue_IsNoOp_DoesNotThrow()
{
Action act = () => _noCacheService.SetValue("key", "value");
Assert.That(act, Throws.Nothing);
}
public void SetValue_IsNoOp_DoesNotThrow()
{
Action act = () => _noCacheService.SetValue("key", "value");
Assert.That(act, Throws.Nothing);
}
/// <summary>
/// Verifies that the no-cache service's GetValue method returns null for any key,
/// confirming that the no-op implementation does not return or simulate cached values.
/// </summary>
[Test]
public void GetValue_IsNoOp_ReturnsNull()
{
var result = _noCacheService.GetValue("any-key");
Assert.That(result, Is.Null);
}
public void GetValue_IsNoOp_ReturnsNull()
{
var result = _noCacheService.GetValue("any-key");
Assert.That(result, Is.Null);
}
#endregion
}
+259 -247
View File
@@ -21,98 +21,104 @@ namespace adas_core.Test.Services;
[TestFixture]
public class ObservationServiceTest
{
/// <summary>
/// Initializes the test fixture for the <see cref="ObservationService"/> by creating and configuring
/// all required dependency mocks, instantiating the service under test with the mocked collaborators,
/// and pre-configuring common lookup behavior for the default "UCI5C" unit (including <c>FindByName</c>
/// and <c>FindById</c>) along with the <c>ICalculatedObservationsService.Map</c> passthrough.
/// </summary>
[SetUp]
public void Setup()
{
//_observationServiceMock = new Mock<IObservationService>();
//var medicineServiceMock = new Mock<IMedicineService>();1
var groupedObservationServiceMock = new Mock<IGroupedObservationService>();
var alarmServiceMock = new Mock<IAlarmService>();
var clientMessageServiceMock = new Mock<IClientMessageService>();
var subscribersServiceMock = new Mock<ISubscribersService>();
var subscriberGroupedServiceMock = new Mock<ISubscriberGroupedService>();
_patientServiceMock = new Mock<IPatientService>();
_configObservationService = new Mock<IConfigObservationService>();
_observationRepository = new Mock<IObservationRepository>();
var observationArchiveRepository = new Mock<IObservationArchiveRepository>();
var calculatedObservationsServiceMock = new Mock<ICalculatedObservationsService>();
var calculatedObservationsServiceLazy =
new Lazy<ICalculatedObservationsService>(() => calculatedObservationsServiceMock.Object);
calculatedObservationsServiceMock.Setup(o => o.Map(It.IsAny<PatientObservation>(), It.IsAny<bool>()))
.ReturnsAsync((PatientObservation obs, bool _) => obs);
_configUnitsService = new Mock<IConfigUnitsService>();
_unitServiceMock = new Mock<IUnitService>();
var diagnosisServiceMock = new Mock<IDiagnosisService>();
_optionsApiSettings = Options.Create(_apiSettings);
Options.Create(_recordingSettings);
_optionsCacheSettings = Options.Create(_cacheSettings);
var balizaService = new Mock<ILightBeaconService>();
var pocService = new Mock<IPointOfCareService>();
var relayService = new Mock<IRelayService>();
var recordingService = new Mock<IRecordingService>();
_logger = new Mock<ILogger<ObservationService>>();
_observationService = new ObservationService(
_patientServiceMock.Object,
//Ipoc.Object,
_configObservationService.Object,
_observationRepository.Object,
observationArchiveRepository.Object,
_configUnitsService.Object,
diagnosisServiceMock.Object,
_optionsApiSettings,
_optionsCacheSettings,
balizaService.Object,
relayService.Object,
recordingService.Object,
_logger.Object,
groupedObservationServiceMock.Object,
alarmServiceMock.Object,
clientMessageServiceMock.Object,
subscribersServiceMock.Object,
subscriberGroupedServiceMock.Object,
calculatedObservationsServiceLazy,
_httpContextAccessor.Object,
_auditService.Object,
pocService.Object,
Mock.Of<ICacheService>()
);
// Create a mock of the singleton class subscribers
var mockSingleton = new Mock<ICalculatedObservationsService>();
// Set up the mock object to return a specific value when a method is called
mockSingleton.Setup(x => x.Map(It.IsAny<PatientObservation>(), It.IsAny<bool>()))
.ReturnsAsync((PatientObservation? value, bool _) => value);
var unitId = ObjectId.GenerateNewId();
var unit = new Unit
public void Setup()
{
Id = unitId,
Name = "UCI5C",
Title = "CONTROLC",
Configuration = new UnitConfiguration
//_observationServiceMock = new Mock<IObservationService>();
//var medicineServiceMock = new Mock<IMedicineService>();1
var groupedObservationServiceMock = new Mock<IGroupedObservationService>();
var alarmServiceMock = new Mock<IAlarmService>();
var clientMessageServiceMock = new Mock<IClientMessageService>();
var subscribersServiceMock = new Mock<ISubscribersService>();
var subscriberGroupedServiceMock = new Mock<ISubscriberGroupedService>();
_patientServiceMock = new Mock<IPatientService>();
_configObservationService = new Mock<IConfigObservationService>();
_observationRepository = new Mock<IObservationRepository>();
var observationArchiveRepository = new Mock<IObservationArchiveRepository>();
var calculatedObservationsServiceMock = new Mock<ICalculatedObservationsService>();
var calculatedObservationsServiceLazy =
new Lazy<ICalculatedObservationsService>(() => calculatedObservationsServiceMock.Object);
calculatedObservationsServiceMock.Setup(o => o.Map(It.IsAny<PatientObservation>(), It.IsAny<bool>()))
.ReturnsAsync((PatientObservation obs, bool _) => obs);
_configUnitsService = new Mock<IConfigUnitsService>();
_unitServiceMock = new Mock<IUnitService>();
var diagnosisServiceMock = new Mock<IDiagnosisService>();
_optionsApiSettings = Options.Create(_apiSettings);
Options.Create(_recordingSettings);
_optionsCacheSettings = Options.Create(_cacheSettings);
var balizaService = new Mock<ILightBeaconService>();
var pocService = new Mock<IPointOfCareService>();
var relayService = new Mock<IRelayService>();
var recordingService = new Mock<IRecordingService>();
_logger = new Mock<ILogger<ObservationService>>();
_observationService = new ObservationService(
_patientServiceMock.Object,
//Ipoc.Object,
_configObservationService.Object,
_observationRepository.Object,
observationArchiveRepository.Object,
_configUnitsService.Object,
diagnosisServiceMock.Object,
_optionsApiSettings,
_optionsCacheSettings,
balizaService.Object,
relayService.Object,
recordingService.Object,
_logger.Object,
groupedObservationServiceMock.Object,
alarmServiceMock.Object,
clientMessageServiceMock.Object,
subscribersServiceMock.Object,
subscriberGroupedServiceMock.Object,
calculatedObservationsServiceLazy,
_httpContextAccessor.Object,
_auditService.Object,
pocService.Object,
Mock.Of<ICacheService>()
);
// Create a mock of the singleton class subscribers
var mockSingleton = new Mock<ICalculatedObservationsService>();
// Set up the mock object to return a specific value when a method is called
mockSingleton.Setup(x => x.Map(It.IsAny<PatientObservation>(), It.IsAny<bool>()))
.ReturnsAsync((PatientObservation? value, bool _) => value);
var unitId = ObjectId.GenerateNewId();
var unit = new Unit
{
AutoAdt = true
}
};
_unitServiceMock.Setup(u => u.FindByName(unit.Name)).ReturnsAsync(unit);
_unitServiceMock.Setup(u => u.FindById(unit.Id)).ReturnsAsync(unit);
}
Id = unitId,
Name = "UCI5C",
Title = "CONTROLC",
Configuration = new UnitConfiguration
{
AutoAdt = true
}
};
_unitServiceMock.Setup(u => u.FindByName(unit.Name)).ReturnsAsync(unit);
_unitServiceMock.Setup(u => u.FindById(unit.Id)).ReturnsAsync(unit);
}
private ObservationService _observationService;
@@ -637,172 +643,178 @@ public class ObservationServiceTest
)));
}
/// <summary>
/// Verifies that when an isolation observation is processed through SaveRequest, the configuration observation and units mapping services are invoked, and the resulting mapped observation is inserted into the observation repository with the expected value, patient identifier, name, time, message time, and coding system.
/// </summary>
[Test]
public async Task ProcessIsolationObservation_Return_IsolationObs()
{
var patientObs = new Person
public async Task ProcessIsolationObservation_Return_IsolationObs()
{
FirstName = "Miguel",
LastName = "Villanueva",
Ids = new Dictionary<string, string> { { "MR", "437537" } }
};
var patient = new Patient
{
Id = ObjectId.GenerateNewId(),
UnitId = ObjectId.GenerateNewId(),
UnitString = "UCI5C",
Bed = "Box4",
PatientNumber = "437537",
Person = patientObs
};
var apiRequest = new ApiRequest
{
Location = new PatientLocation("UCI5C", "Box4"),
ObservationData = new ObservationData
var patientObs = new Person
{
Code = "302147001",
CodingSystem = "SNM",
Value = "Aire; Contacto; Preventivo",
Text = "Aislamiento",
Time = Now
},
Observations =
[
new PatientObservation
FirstName = "Miguel",
LastName = "Villanueva",
Ids = new Dictionary<string, string> { { "MR", "437537" } }
};
var patient = new Patient
{
Id = ObjectId.GenerateNewId(),
UnitId = ObjectId.GenerateNewId(),
UnitString = "UCI5C",
Bed = "Box4",
PatientNumber = "437537",
Person = patientObs
};
var apiRequest = new ApiRequest
{
Location = new PatientLocation("UCI5C", "Box4"),
ObservationData = new ObservationData
{
Code = "code",
Code = "302147001",
CodingSystem = "SNM",
Name = "name",
Status = StatusEnum.Type.Ok,
Time = Now,
Value = "value"
}
],
Patient = patientObs,
PatientNumber = "437537",
Type = "ORU_R01",
PatientId = PatientId.ToString(),
MessageTime = Now
};
var newObs = new PatientObservation
{
Value = "Aire, Contacto, Preventivo",
Name = "Isolation",
CodingSystem = "ADAS",
PatientId = patient.Id,
MessageTime = Now,
Time = Now
};
_patientServiceMock.Setup(cm => cm.FindPatientByApiRequest(apiRequest)).ReturnsAsync(patient);
_configObservationService
.Setup(o => o.Map(It.Is<PatientObservation>(arg => arg.PatientId == patient.Id), false))
.ReturnsAsync(newObs);
_configUnitsService.Setup(o => o.Map(It.Is<PatientObservation>(arg => arg.PatientId == patient.Id)))
.ReturnsAsync(newObs);
_configObservationService.Setup(o => o.RetentionActions(It.IsAny<PatientObservation>()))
.ReturnsAsync(new ObservatitonRetentionResult());
_unitServiceMock.Setup(o => o.FindById(It.IsAny<ObjectId>())).ReturnsAsync(new Unit
{ Configuration = new UnitConfiguration { AutoAdt = true } });
await _observationService.SaveRequest(apiRequest);
_observationRepository.Verify(o => o.InsertOneAsync(It.Is<PatientObservation>(arg =>
arg.Value == newObs.Value &&
arg.PatientId == newObs.PatientId &&
arg.Name == newObs.Name &&
arg.Time == newObs.Time &&
arg.MessageTime == newObs.MessageTime &&
arg.CodingSystem == newObs.CodingSystem
)));
}
[Test]
public async Task ProcessPositionObservation_Return_PositionObs()
{
var patientObs = new Person
{
FirstName = "Miguel",
LastName = "Villanueva",
Ids = new Dictionary<string, string> { { "MR", "437537" } }
};
var patient = new Patient
{
Id = ObjectId.GenerateNewId(),
UnitId = ObjectId.GenerateNewId(),
UnitString = "UCI5C",
Bed = "Box4",
PatientNumber = "437537",
Person = patientObs
};
var apiRequest = new ApiRequest
{
Location = new PatientLocation("UCI5C", "Box4"),
ObservationData = new ObservationData
Value = "Aire; Contacto; Preventivo",
Text = "Aislamiento",
Time = Now
},
Observations =
[
new PatientObservation
{
Code = "code",
CodingSystem = "SNM",
Name = "name",
Status = StatusEnum.Type.Ok,
Time = Now,
Value = "value"
}
],
Patient = patientObs,
PatientNumber = "437537",
Type = "ORU_R01",
PatientId = PatientId.ToString(),
MessageTime = Now
};
var newObs = new PatientObservation
{
Value = "Aire, Contacto, Preventivo",
Name = "Isolation",
CodingSystem = "ADAS",
PatientId = patient.Id,
MessageTime = Now,
Time = Now
};
_patientServiceMock.Setup(cm => cm.FindPatientByApiRequest(apiRequest)).ReturnsAsync(patient);
_configObservationService
.Setup(o => o.Map(It.Is<PatientObservation>(arg => arg.PatientId == patient.Id), false))
.ReturnsAsync(newObs);
_configUnitsService.Setup(o => o.Map(It.Is<PatientObservation>(arg => arg.PatientId == patient.Id)))
.ReturnsAsync(newObs);
_configObservationService.Setup(o => o.RetentionActions(It.IsAny<PatientObservation>()))
.ReturnsAsync(new ObservatitonRetentionResult());
_unitServiceMock.Setup(o => o.FindById(It.IsAny<ObjectId>())).ReturnsAsync(new Unit
{ Configuration = new UnitConfiguration { AutoAdt = true } });
await _observationService.SaveRequest(apiRequest);
_observationRepository.Verify(o => o.InsertOneAsync(It.Is<PatientObservation>(arg =>
arg.Value == newObs.Value &&
arg.PatientId == newObs.PatientId &&
arg.Name == newObs.Name &&
arg.Time == newObs.Time &&
arg.MessageTime == newObs.MessageTime &&
arg.CodingSystem == newObs.CodingSystem
)));
}
/// <summary>
/// Verifies that processing a position observation through the observation service persists a new <see cref="PatientObservation"/> with the expected values (Value, PatientId, Name, Time, MessageTime, and CodingSystem) when a valid API request is provided.
/// </summary>
[Test]
public async Task ProcessPositionObservation_Return_PositionObs()
{
var patientObs = new Person
{
FirstName = "Miguel",
LastName = "Villanueva",
Ids = new Dictionary<string, string> { { "MR", "437537" } }
};
var patient = new Patient
{
Id = ObjectId.GenerateNewId(),
UnitId = ObjectId.GenerateNewId(),
UnitString = "UCI5C",
Bed = "Box4",
PatientNumber = "437537",
Person = patientObs
};
var apiRequest = new ApiRequest
{
Location = new PatientLocation("UCI5C", "Box4"),
ObservationData = new ObservationData
{
Code = "386053000",
CodingSystem = "SNM",
Text = "CAMBIOS POSTURALES",
Value = "Cama Hill-rom",
Time = Now
},
Observations =
[
new PatientObservation
{
Code = "code",
CodingSystem = "SNM",
Name = "name",
Status = StatusEnum.Type.Ok,
Time = Now,
Value = "Cama Hill-rom"
}
],
Patient = patientObs,
PatientNumber = "437537",
Type = "ORU_R01",
PatientId = PatientId.ToString(),
MessageTime = Now
};
var newObs = new PatientObservation
{
Code = "386053000",
CodingSystem = "SNM",
Text = "CAMBIOS POSTURALES",
Value = "Cama Hill-rom",
Name = "Patient_Position",
CodingSystem = "ADAS",
PatientId = patient.Id,
MessageTime = Now,
Time = Now
},
Observations =
[
new PatientObservation
{
Code = "code",
CodingSystem = "SNM",
Name = "name",
Status = StatusEnum.Type.Ok,
Time = Now,
Value = "Cama Hill-rom"
}
],
Patient = patientObs,
PatientNumber = "437537",
Type = "ORU_R01",
PatientId = PatientId.ToString(),
MessageTime = Now
};
var newObs = new PatientObservation
{
Value = "Cama Hill-rom",
Name = "Patient_Position",
CodingSystem = "ADAS",
PatientId = patient.Id,
MessageTime = Now,
Time = Now
};
_patientServiceMock.Setup(cm => cm.FindPatientByApiRequest(apiRequest)).ReturnsAsync(patient);
_configObservationService
.Setup(o => o.Map(It.Is<PatientObservation>(arg => arg.PatientId == patient.Id), false))
.ReturnsAsync(newObs);
_configUnitsService.Setup(o => o.Map(It.Is<PatientObservation>(arg => arg.PatientId == patient.Id)))
.ReturnsAsync(newObs);
_configObservationService.Setup(o => o.RetentionActions(It.IsAny<PatientObservation>()))
.ReturnsAsync(new ObservatitonRetentionResult());
_unitServiceMock.Setup(o => o.FindById(It.IsAny<ObjectId>())).ReturnsAsync(new Unit
{ Configuration = new UnitConfiguration { AutoAdt = true } });
await _observationService.SaveRequest(apiRequest);
_observationRepository.Verify(o => o.InsertOneAsync(It.Is<PatientObservation>(arg =>
arg.Value == newObs.Value &&
arg.PatientId == newObs.PatientId &&
arg.Name == newObs.Name &&
arg.Time == newObs.Time &&
arg.MessageTime == newObs.MessageTime &&
arg.CodingSystem == newObs.CodingSystem
)));
}
};
_patientServiceMock.Setup(cm => cm.FindPatientByApiRequest(apiRequest)).ReturnsAsync(patient);
_configObservationService
.Setup(o => o.Map(It.Is<PatientObservation>(arg => arg.PatientId == patient.Id), false))
.ReturnsAsync(newObs);
_configUnitsService.Setup(o => o.Map(It.Is<PatientObservation>(arg => arg.PatientId == patient.Id)))
.ReturnsAsync(newObs);
_configObservationService.Setup(o => o.RetentionActions(It.IsAny<PatientObservation>()))
.ReturnsAsync(new ObservatitonRetentionResult());
_unitServiceMock.Setup(o => o.FindById(It.IsAny<ObjectId>())).ReturnsAsync(new Unit
{ Configuration = new UnitConfiguration { AutoAdt = true } });
await _observationService.SaveRequest(apiRequest);
_observationRepository.Verify(o => o.InsertOneAsync(It.Is<PatientObservation>(arg =>
arg.Value == newObs.Value &&
arg.PatientId == newObs.PatientId &&
arg.Name == newObs.Name &&
arg.Time == newObs.Time &&
arg.MessageTime == newObs.MessageTime &&
arg.CodingSystem == newObs.CodingSystem
)));
}
[Test]
public async Task ProcessIntravenousLinesObservation_Return_PositionObs()
File diff suppressed because it is too large Load Diff
+182 -149
View File
@@ -17,47 +17,52 @@ 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
public void SetUp()
{
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())
);
}
_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!;
@@ -67,79 +72,98 @@ public class PointOfCareServiceTests
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();
}
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();
}
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));
}
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);
}
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));
}
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()
@@ -162,26 +186,29 @@ public class PointOfCareServiceTests
// 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>
public async Task FindByUnitAndStatus_ValidUnitIdAndStatus_ReturnsListOfPointOfCare()
{
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));
}
// 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()
@@ -256,36 +283,42 @@ public class PointOfCareServiceTests
// 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);
}
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);
}
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);
}
}
+37 -28
View File
@@ -14,19 +14,22 @@ public class PublisherServiceTest
//private static readonly DateTime now = DateTime.Now;
//private static readonly ObjectId patientId = ObjectId.GenerateNewId();
/// <summary>
/// Initializes the test environment for <see cref="PublisherService"/> by configuring RabbitMQ settings, creating a mocked logger, and instantiating the service under test.
/// </summary>
[SetUp]
public void Setup()
{
//advancedBusMock = new Mock<IAdvancedBus>();
_optionsRabbitMqSettings = Options.Create(_rabbitMqSettings);
_logger = new Mock<ILogger<PublisherService>>();
_publisherService = new PublisherService(
_optionsRabbitMqSettings,
_logger.Object);
}
public void Setup()
{
//advancedBusMock = new Mock<IAdvancedBus>();
_optionsRabbitMqSettings = Options.Create(_rabbitMqSettings);
_logger = new Mock<ILogger<PublisherService>>();
_publisherService = new PublisherService(
_optionsRabbitMqSettings,
_logger.Object);
}
private PublisherService _publisherService = null!;
//private Mock<IAdvancedBus> advancedBusMock;
@@ -40,23 +43,29 @@ public class PublisherServiceTest
private Mock<ILogger<PublisherService>> _logger = null!;
/// <summary>
/// Verifies that the publisher service's SendMessage method returns a non-null result when sending a message to the observations queue.
/// </summary>
[Test]
public void SendMessage_Return_true()
{
var newMessage = new Message<string>();
var result = _publisherService.SendMessage(newMessage, _rabbitMqSettings.ObservationsQueue);
Assert.That(result, Is.Not.Null);
}
public void SendMessage_Return_true()
{
var newMessage = new Message<string>();
var result = _publisherService.SendMessage(newMessage, _rabbitMqSettings.ObservationsQueue);
Assert.That(result, Is.Not.Null);
}
/// <summary>
/// Verifies that <c>SendMessageError</c> returns a non-null result when publishing a new <see cref="Message{Error}"/> to the observations queue.
/// </summary>
[Test]
public void SendMessage_Error_Return_true()
{
var newMessage = new Message<Error>();
var result = _publisherService.SendMessageError(newMessage, _rabbitMqSettings.ObservationsQueue);
Assert.That(result, Is.Not.Null);
}
public void SendMessage_Error_Return_true()
{
var newMessage = new Message<Error>();
var result = _publisherService.SendMessageError(newMessage, _rabbitMqSettings.ObservationsQueue);
Assert.That(result, Is.Not.Null);
}
}
+348 -289
View File
@@ -40,224 +40,258 @@ public class PumpServiceTest
private static readonly ObjectId PatientId = ObjectId.GenerateNewId();
private static readonly DateTime Now = DateTime.UtcNow;
/// <summary>
/// Initializes the unit test fixture by creating mock repositories, services, and a mocked <see cref="HttpContext"/> with a test claims principal, then instantiates the <see cref="PumpService"/> under test using the configured API settings (5-second pump expiration, zero pump messages disabled). Pass-through mappings are configured for <see cref="ICalculatedObservationsService"/>, <see cref="IConfigPumpsService"/>, and <see cref="IConfigUnitsService"/> so that supplied pump observations are returned unchanged.
/// </summary>
[SetUp]
public void Setup()
{
_obsRepo = new Mock<IPumpObservationRepository>();
_stateRepo = new Mock<IPumpStateRepository>();
_alarmEventRepo = new Mock<IPumpAlarmEventRepository>();
_alarmStateRepo = new Mock<IPumpAlarmStateRepository>();
_archiveRepo = new Mock<IPumpArchiveRepository>();
_patientSvc = new Mock<IPatientService>();
_configPumps = new Mock<IConfigPumpsService>();
_subs = new Mock<ISubscribersService>();
_clientMsg = new Mock<IClientMessageService>();
_calcObs = new Mock<ICalculatedObservationsService>();
_lazyCalc = new Lazy<ICalculatedObservationsService>(() => _calcObs.Object);
_http = new Mock<IHttpContextAccessor>();
_audit = new Mock<ILocalAuditService>();
_logger = new Mock<ILogger<PumpService>>();
_configUnits = new Mock<IConfigUnitsService>();
var principal = new ClaimsPrincipal(
new ClaimsIdentity([new Claim(ClaimTypes.Name, "test")], "mock"));
_http.Setup(x => x.HttpContext)
.Returns(new DefaultHttpContext { User = principal });
_calcObs.Setup(x => x.Map(It.IsAny<PumpObservation>()))
.ReturnsAsync((PumpObservation o) => o);
var api = Options.Create(new ApiSettings
public void Setup()
{
PumpExpiresSeconds = 5,
SendPumpsZero = false
});
_service = new PumpService(
_obsRepo.Object,
_stateRepo.Object,
_alarmEventRepo.Object,
_alarmStateRepo.Object,
_archiveRepo.Object,
_patientSvc.Object,
_configPumps.Object,
api,
_logger.Object,
_subs.Object,
_clientMsg.Object,
_lazyCalc,
_http.Object,
_audit.Object,
_configUnits.Object
);
_configPumps.Setup(x => x.Map(It.IsAny<PumpObservation>()))
.ReturnsAsync((PumpObservation o) => o);
_configUnits.Setup(x => x.Map(It.IsAny<PumpObservation>()))
.ReturnsAsync((PumpObservation o) => o);
}
_obsRepo = new Mock<IPumpObservationRepository>();
_stateRepo = new Mock<IPumpStateRepository>();
_alarmEventRepo = new Mock<IPumpAlarmEventRepository>();
_alarmStateRepo = new Mock<IPumpAlarmStateRepository>();
_archiveRepo = new Mock<IPumpArchiveRepository>();
_patientSvc = new Mock<IPatientService>();
_configPumps = new Mock<IConfigPumpsService>();
_subs = new Mock<ISubscribersService>();
_clientMsg = new Mock<IClientMessageService>();
_calcObs = new Mock<ICalculatedObservationsService>();
_lazyCalc = new Lazy<ICalculatedObservationsService>(() => _calcObs.Object);
_http = new Mock<IHttpContextAccessor>();
_audit = new Mock<ILocalAuditService>();
_logger = new Mock<ILogger<PumpService>>();
_configUnits = new Mock<IConfigUnitsService>();
var principal = new ClaimsPrincipal(
new ClaimsIdentity([new Claim(ClaimTypes.Name, "test")], "mock"));
_http.Setup(x => x.HttpContext)
.Returns(new DefaultHttpContext { User = principal });
_calcObs.Setup(x => x.Map(It.IsAny<PumpObservation>()))
.ReturnsAsync((PumpObservation o) => o);
var api = Options.Create(new ApiSettings
{
PumpExpiresSeconds = 5,
SendPumpsZero = false
});
_service = new PumpService(
_obsRepo.Object,
_stateRepo.Object,
_alarmEventRepo.Object,
_alarmStateRepo.Object,
_archiveRepo.Object,
_patientSvc.Object,
_configPumps.Object,
api,
_logger.Object,
_subs.Object,
_clientMsg.Object,
_lazyCalc,
_http.Object,
_audit.Object,
_configUnits.Object
);
_configPumps.Setup(x => x.Map(It.IsAny<PumpObservation>()))
.ReturnsAsync((PumpObservation o) => o);
_configUnits.Setup(x => x.Map(It.IsAny<PumpObservation>()))
.ReturnsAsync((PumpObservation o) => o);
}
// --------------------------------------------------------------
// SAVE REQUEST — casos básicos
// --------------------------------------------------------------
/// <summary>
/// Verifies that <see cref="ApiRequest"/> processing does not insert any <see cref="PumpObservation"/> records
/// when the request is saved without associated observations.
/// </summary>
[Test]
public async Task SaveRequest_Returns_When_No_Observations()
{
var req = new ApiRequest { Type = "ORU_R01" };
await _service.SaveRequest(req);
_obsRepo.Verify(r => r.InsertAsync(It.IsAny<PumpObservation>()), Times.Never);
}
public async Task SaveRequest_Returns_When_No_Observations()
{
var req = new ApiRequest { Type = "ORU_R01" };
await _service.SaveRequest(req);
_obsRepo.Verify(r => r.InsertAsync(It.IsAny<PumpObservation>()), Times.Never);
}
/// <summary>
/// Verifies that <c>SaveRequest</c> does not insert a <see cref="PumpObservation"/> when the <see cref="ApiRequest.Type"/> is an unrecognized value.
/// </summary>
[Test]
public async Task SaveRequest_UnknownType_DoesNotInsert()
{
var req = new ApiRequest
public async Task SaveRequest_UnknownType_DoesNotInsert()
{
Type = "UNKNOWN",
PumpObservation = new PumpObservation { Time = Now }
};
await _service.SaveRequest(req);
_obsRepo.Verify(r => r.InsertAsync(It.IsAny<PumpObservation>()), Times.Never);
}
var req = new ApiRequest
{
Type = "UNKNOWN",
PumpObservation = new PumpObservation { Time = Now }
};
await _service.SaveRequest(req);
_obsRepo.Verify(r => r.InsertAsync(It.IsAny<PumpObservation>()), Times.Never);
}
/// <summary>
/// Verifies that SaveRequest converts a single <c>PumpObservation</c> on an incoming
/// <c>ApiRequest</c> into a list with one entry on the request after processing.
/// </summary>
[Test]
public async Task SaveRequest_Converts_SingleObservation_ToList()
{
var obs = new PumpObservation { Time = Now };
var req = new ApiRequest
public async Task SaveRequest_Converts_SingleObservation_ToList()
{
Type = "ORU_R01",
PumpObservation = obs,
PatientNumber = "123"
};
_patientSvc.Setup(p => p.FindPatientByApiRequest(req))
.ReturnsAsync(new Patient { Id = PatientId });
await _service.SaveRequest(req);
Assert.That(req.PumpObservations, Has.Count.EqualTo(1));
}
var obs = new PumpObservation { Time = Now };
var req = new ApiRequest
{
Type = "ORU_R01",
PumpObservation = obs,
PatientNumber = "123"
};
_patientSvc.Setup(p => p.FindPatientByApiRequest(req))
.ReturnsAsync(new Patient { Id = PatientId });
await _service.SaveRequest(req);
Assert.That(req.PumpObservations, Has.Count.EqualTo(1));
}
/// <summary>
/// Verifies that Service.SaveRequest sets the <c>Expires</c> property of the <see cref="PumpObservation"/> before persisting it via the repository.
/// </summary>
[Test]
public async Task SaveRequest_SetsExpires_BeforeInsert()
{
var obs = new PumpObservation
public async Task SaveRequest_SetsExpires_BeforeInsert()
{
Time = Now,
DeviceId = "D1"
};
var req = new ApiRequest
{
Type = "ORU_R01",
PumpObservation = obs,
PatientNumber = "1"
};
_patientSvc.Setup(x => x.FindPatientByApiRequest(req))
.ReturnsAsync(new Patient { Id = PatientId });
await _service.SaveRequest(req);
_obsRepo.Verify(r => r.InsertAsync(
It.Is<PumpObservation>(o => o.Expires == 5)), Times.Once);
}
var obs = new PumpObservation
{
Time = Now,
DeviceId = "D1"
};
var req = new ApiRequest
{
Type = "ORU_R01",
PumpObservation = obs,
PatientNumber = "1"
};
_patientSvc.Setup(x => x.FindPatientByApiRequest(req))
.ReturnsAsync(new Patient { Id = PatientId });
await _service.SaveRequest(req);
_obsRepo.Verify(r => r.InsertAsync(
It.Is<PumpObservation>(o => o.Expires == 5)), Times.Once);
}
// --------------------------------------------------------------
// PROCESS ALARM
// --------------------------------------------------------------
/// <summary>
/// Verifies that saving a request of type "ORU_R40" creates a <see cref="PumpAlarmEvent"/>
/// and does not create a <see cref="PumpObservation"/>, ensuring alarm-phase events are
/// routed to the alarm event store rather than the observation store.
/// </summary>
[Test]
public async Task SaveRequest_ORU_R40_CreatesAlarmEvent()
{
var obs = new PumpObservation
public async Task SaveRequest_ORU_R40_CreatesAlarmEvent()
{
Time = Now,
DeviceId = "D1",
AlarmType = PumpEnum.AlarmType.Occlusion,
EventPhase = PumpEnum.EventPhase.Start
};
var req = new ApiRequest { Type = "ORU_R40", PumpObservation = obs };
_patientSvc.Setup(x => x.FindPatientByApiRequest(req))
.ReturnsAsync(new Patient { Id = PatientId });
await _service.SaveRequest(req);
_alarmEventRepo.Verify(r => r.InsertAsync(It.IsAny<PumpAlarmEvent>()), Times.Once);
_obsRepo.Verify(r => r.InsertAsync(It.IsAny<PumpObservation>()), Times.Never);
}
var obs = new PumpObservation
{
Time = Now,
DeviceId = "D1",
AlarmType = PumpEnum.AlarmType.Occlusion,
EventPhase = PumpEnum.EventPhase.Start
};
var req = new ApiRequest { Type = "ORU_R40", PumpObservation = obs };
_patientSvc.Setup(x => x.FindPatientByApiRequest(req))
.ReturnsAsync(new Patient { Id = PatientId });
await _service.SaveRequest(req);
_alarmEventRepo.Verify(r => r.InsertAsync(It.IsAny<PumpAlarmEvent>()), Times.Once);
_obsRepo.Verify(r => r.InsertAsync(It.IsAny<PumpObservation>()), Times.Never);
}
/// <summary>
/// Verifies that processing a pump observation with <c>EventPhase.End</c> removes the corresponding alarm state
/// by calling <c>RemoveAsync</c> on the alarm state repository with the matching device, alarm type, and MDC code.
/// </summary>
[Test]
public async Task ProcessAlarm_End_RemovesAlarmState()
{
var obs = new PumpObservation
public async Task ProcessAlarm_End_RemovesAlarmState()
{
Time = Now,
DeviceId = "DX",
AlarmType = PumpEnum.AlarmType.Occlusion,
AlarmTypeMdc = "H1",
EventPhase = PumpEnum.EventPhase.End
};
var req = new ApiRequest { Type = "ORU_R40", PumpObservation = obs };
_patientSvc.Setup(x => x.FindPatientByApiRequest(req))
.ReturnsAsync(new Patient { Id = PatientId });
await _service.SaveRequest(req);
_alarmStateRepo.Verify(r => r.RemoveAsync("DX", PumpEnum.AlarmType.Occlusion, "H1"), Times.Once);
}
var obs = new PumpObservation
{
Time = Now,
DeviceId = "DX",
AlarmType = PumpEnum.AlarmType.Occlusion,
AlarmTypeMdc = "H1",
EventPhase = PumpEnum.EventPhase.End
};
var req = new ApiRequest { Type = "ORU_R40", PumpObservation = obs };
_patientSvc.Setup(x => x.FindPatientByApiRequest(req))
.ReturnsAsync(new Patient { Id = PatientId });
await _service.SaveRequest(req);
_alarmStateRepo.Verify(r => r.RemoveAsync("DX", PumpEnum.AlarmType.Occlusion, "H1"), Times.Once);
}
// --------------------------------------------------------------
// SNAPSHOT DE BOMBA
// --------------------------------------------------------------
/// <summary>
/// Verifies that <c>SaveRequest</c> creates and upserts a new <see cref="PumpState"/>
/// for the device when no existing pump state is found in the repository.
/// </summary>
[Test]
public async Task SaveRequest_CreatesPumpState_IfNotExists()
{
var obs = new PumpObservation { Time = Now, DeviceId = "P1" };
var req = new ApiRequest { Type = "ORU_R01", PumpObservation = obs, PatientNumber = "1" };
_patientSvc.Setup(x => x.FindPatientByApiRequest(req))
.ReturnsAsync(new Patient { Id = PatientId });
_stateRepo.Setup(r => r.FindByDeviceIdAsync("P1"))
.ReturnsAsync((PumpState?)null);
await _service.SaveRequest(req);
_stateRepo.Verify(r => r.UpsertAsync(It.Is<PumpState>(s => s.DeviceId == "P1")), Times.Once);
}
public async Task SaveRequest_CreatesPumpState_IfNotExists()
{
var obs = new PumpObservation { Time = Now, DeviceId = "P1" };
var req = new ApiRequest { Type = "ORU_R01", PumpObservation = obs, PatientNumber = "1" };
_patientSvc.Setup(x => x.FindPatientByApiRequest(req))
.ReturnsAsync(new Patient { Id = PatientId });
_stateRepo.Setup(r => r.FindByDeviceIdAsync("P1"))
.ReturnsAsync((PumpState?)null);
await _service.SaveRequest(req);
_stateRepo.Verify(r => r.UpsertAsync(It.Is<PumpState>(s => s.DeviceId == "P1")), Times.Once);
}
// --------------------------------------------------------------
// BROADCAST
// --------------------------------------------------------------
/// <summary>
/// Verifies that SaveRequest does not broadcast a message via the client when there are no active subscribers.
/// </summary>
/// <returns>A task that completes when the assertion has been executed.</returns>
[Test]
public async Task SaveRequest_NoSubscribers_NoBroadcast()
{
var obs = new PumpObservation { Time = Now, DeviceId = "BR1", PatientId = PatientId };
var patient = new Patient
public async Task SaveRequest_NoSubscribers_NoBroadcast()
{
Id = PatientId,
Location = new PatientLocation("U1", "B1")
};
var req = new ApiRequest
{
Type = "ORU_R01",
PumpObservation = obs,
PatientNumber = "1"
};
_subs.Setup(x => x.GetSubscribers()).Returns([]);
_patientSvc.Setup(x => x.FindPatientByApiRequest(req))
.ReturnsAsync(patient);
await _service.SaveRequest(req);
_clientMsg.Verify(r => r.SendAsync(It.IsAny<string>(), It.IsAny<OperationType>(), It.IsAny<object>()),
Times.Never);
}
var obs = new PumpObservation { Time = Now, DeviceId = "BR1", PatientId = PatientId };
var patient = new Patient
{
Id = PatientId,
Location = new PatientLocation("U1", "B1")
};
var req = new ApiRequest
{
Type = "ORU_R01",
PumpObservation = obs,
PatientNumber = "1"
};
_subs.Setup(x => x.GetSubscribers()).Returns([]);
_patientSvc.Setup(x => x.FindPatientByApiRequest(req))
.ReturnsAsync(patient);
await _service.SaveRequest(req);
_clientMsg.Verify(r => r.SendAsync(It.IsAny<string>(), It.IsAny<OperationType>(), It.IsAny<object>()),
Times.Never);
}
[Test]
public async Task SaveRequest_WithSubscribers_UsesReqLocation_AndSendsBroadcast()
@@ -322,140 +356,165 @@ public class PumpServiceTest
// --------------------------------------------------------------
// RETENCIÓN — DeleteOlderDays
// --------------------------------------------------------------
/// <summary>
/// Verifies that <c>SaveRequest</c> invokes the observation repository's
/// <c>DeleteOlderThanDaysAsync</c> method with the configured retention value when the
/// retention policy returned for the pump observation is <c>DeleteOlderDays</c>.
/// </summary>
[Test]
public async Task SaveRequest_Retention_DeleteOlderDays_CallsRepo()
{
var obs = new PumpObservation { Time = Now, DeviceId = "R1", PatientId = PatientId };
var req = new ApiRequest { Type = "ORU_R01", PumpObservation = obs, PatientNumber = "1" };
_patientSvc.Setup(x => x.FindPatientByApiRequest(req))
.ReturnsAsync(new Patient { Id = PatientId });
_configPumps.Setup(x => x.RetentionActions(It.IsAny<PumpObservation>()))
.ReturnsAsync(new ObservatitonRetentionResult
{
RetentionPolicy = RetentionPolicy.DeleteOlderDays,
RetentionPolicyValue = 7
});
await _service.SaveRequest(req);
_obsRepo.Verify(r => r.DeleteOlderThanDaysAsync(7, null), Times.Once);
}
public async Task SaveRequest_Retention_DeleteOlderDays_CallsRepo()
{
var obs = new PumpObservation { Time = Now, DeviceId = "R1", PatientId = PatientId };
var req = new ApiRequest { Type = "ORU_R01", PumpObservation = obs, PatientNumber = "1" };
_patientSvc.Setup(x => x.FindPatientByApiRequest(req))
.ReturnsAsync(new Patient { Id = PatientId });
_configPumps.Setup(x => x.RetentionActions(It.IsAny<PumpObservation>()))
.ReturnsAsync(new ObservatitonRetentionResult
{
RetentionPolicy = RetentionPolicy.DeleteOlderDays,
RetentionPolicyValue = 7
});
await _service.SaveRequest(req);
_obsRepo.Verify(r => r.DeleteOlderThanDaysAsync(7, null), Times.Once);
}
// --------------------------------------------------------------
// PAGINACIÓN
// --------------------------------------------------------------
/// <summary>
/// Verifies that <c>GetPaginatedPump</c> returns the correct paginated results when filtering by patient identifier.
/// Ensures the first page contains the most recent observation within the configured time tolerance.
/// </summary>
[Test]
public async Task GetPaginatedPump_ByPatient_Works()
{
var obs = new List<PumpObservation>
public async Task GetPaginatedPump_ByPatient_Works()
{
new() { Time = Now.AddMinutes(-1), PatientId = PatientId },
new() { Time = Now.AddMinutes(-5), PatientId = PatientId }
};
_obsRepo.Setup(r => r.FindByPatientId(PatientId))
.ReturnsAsync(obs);
var fileredRequest = new FilteredRequest
{
PatientId = PatientId.ToString()
};
var filter = new PaginationFilter(1, 1, fileredRequest);
var result = await _service.GetPaginatedPump(filter);
using (Assert.EnterMultipleScope())
{
Assert.That(result!.Data, Has.Count.EqualTo(1));
Assert.That(result.Data[0].Time, Is.EqualTo(Now.AddMinutes(-1)).Within(TimeSpan.FromMilliseconds(2)));
var obs = new List<PumpObservation>
{
new() { Time = Now.AddMinutes(-1), PatientId = PatientId },
new() { Time = Now.AddMinutes(-5), PatientId = PatientId }
};
_obsRepo.Setup(r => r.FindByPatientId(PatientId))
.ReturnsAsync(obs);
var fileredRequest = new FilteredRequest
{
PatientId = PatientId.ToString()
};
var filter = new PaginationFilter(1, 1, fileredRequest);
var result = await _service.GetPaginatedPump(filter);
using (Assert.EnterMultipleScope())
{
Assert.That(result!.Data, Has.Count.EqualTo(1));
Assert.That(result.Data[0].Time, Is.EqualTo(Now.AddMinutes(-1)).Within(TimeSpan.FromMilliseconds(2)));
}
}
}
// --------------------------------------------------------------
// ARCHIVO
// --------------------------------------------------------------
/// <summary>
/// Verifies that <c>ArchiveByPatientId</c> inserts the patient's pump observations into the archive
/// repository and then deletes them, along with their related alarm events and alarm states.
/// </summary>
[Test]
public async Task ArchiveByPatientId_InsertsArchive_ThenDeletes()
{
var list = new List<PumpObservation>
public async Task ArchiveByPatientId_InsertsArchive_ThenDeletes()
{
new() { Id = ObjectId.GenerateNewId(), PatientId = PatientId }
};
_obsRepo.Setup(r => r.FindByPatientId(PatientId)).ReturnsAsync(list);
await _service.ArchiveByPatientId(PatientId);
_archiveRepo.Verify(r => r.InsertManyAsync(list), Times.Once);
_obsRepo.Verify(r => r.DeleteByPatientId(PatientId), Times.Once);
_alarmEventRepo.Verify(r => r.DeleteByPatientId(PatientId), Times.Once);
_alarmStateRepo.Verify(r => r.DeleteByPatientId(PatientId), Times.Once);
}
var list = new List<PumpObservation>
{
new() { Id = ObjectId.GenerateNewId(), PatientId = PatientId }
};
_obsRepo.Setup(r => r.FindByPatientId(PatientId)).ReturnsAsync(list);
await _service.ArchiveByPatientId(PatientId);
_archiveRepo.Verify(r => r.InsertManyAsync(list), Times.Once);
_obsRepo.Verify(r => r.DeleteByPatientId(PatientId), Times.Once);
_alarmEventRepo.Verify(r => r.DeleteByPatientId(PatientId), Times.Once);
_alarmStateRepo.Verify(r => r.DeleteByPatientId(PatientId), Times.Once);
}
// --------------------------------------------------------------
// UPDATE MANY
// --------------------------------------------------------------
/// <summary>
/// Verifies that <c>UpdateManyObjectId</c> correctly updates the patient identifier across observations, alarms, and alarm state repositories.
/// </summary>
/// <returns>A task representing the asynchronous test execution.</returns>
[Test]
public async Task UpdateManyObjectId_UpdatesObs_Alarms_States()
{
var oldId = ObjectId.GenerateNewId();
var newId = ObjectId.GenerateNewId();
_obsRepo.Setup(r => r.UpdateManyObjectIdByFieldAsync("patientId", newId, oldId))
.ReturnsAsync(3);
_alarmEventRepo.Setup(r => r.UpdateManyObjectIdByFiledNameAsync("patientId", newId, oldId))
.ReturnsAsync(1);
_alarmStateRepo.Setup(r => r.UpdateManyObjectIdByFieldNameAsync("patientId", newId, oldId))
.ReturnsAsync(2);
await _service.UpdateManyObjectId("patientId", newId, oldId);
}
public async Task UpdateManyObjectId_UpdatesObs_Alarms_States()
{
var oldId = ObjectId.GenerateNewId();
var newId = ObjectId.GenerateNewId();
_obsRepo.Setup(r => r.UpdateManyObjectIdByFieldAsync("patientId", newId, oldId))
.ReturnsAsync(3);
_alarmEventRepo.Setup(r => r.UpdateManyObjectIdByFiledNameAsync("patientId", newId, oldId))
.ReturnsAsync(1);
_alarmStateRepo.Setup(r => r.UpdateManyObjectIdByFieldNameAsync("patientId", newId, oldId))
.ReturnsAsync(2);
await _service.UpdateManyObjectId("patientId", newId, oldId);
}
// --------------------------------------------------------------
// FIND LAST OBSERVATIONS
// --------------------------------------------------------------
/// <summary>
/// Verifies that <c>FindLastPumpObservations</c> returns pump observations ordered with the most recent first,
/// returning only the specified number of latest entries when the repository provides multiple observations.
/// </summary>
[Test]
public async Task FindLastPumpObservations_ReturnsOrdered()
{
var items = new List<PumpObservation>
public async Task FindLastPumpObservations_ReturnsOrdered()
{
new() { Time = Now.AddMinutes(-10) },
new() { Time = Now.AddMinutes(-1) }
};
_obsRepo.Setup(r => r.FindByPatientId(PatientId))
.ReturnsAsync(items);
var result = await _service.FindLastPumpObservations(PatientId, 1);
Assert.That(result, Has.Count.EqualTo(1));
Assert.That(result[0].Time, Is.EqualTo(items[1].Time));
}
var items = new List<PumpObservation>
{
new() { Time = Now.AddMinutes(-10) },
new() { Time = Now.AddMinutes(-1) }
};
_obsRepo.Setup(r => r.FindByPatientId(PatientId))
.ReturnsAsync(items);
var result = await _service.FindLastPumpObservations(PatientId, 1);
Assert.That(result, Has.Count.EqualTo(1));
Assert.That(result[0].Time, Is.EqualTo(items[1].Time));
}
// --------------------------------------------------------------
// INSERT MANUAL
// --------------------------------------------------------------
/// <summary>
/// Verifies that InsertPumpObservation persists the observation and upserts the corresponding
/// pump state when no existing state is found for the device and there are no active subscribers.
/// </summary>
[Test]
public async Task InsertPumpObservation_Inserts_AndBroadcasts()
{
var obs = new PumpObservation
public async Task InsertPumpObservation_Inserts_AndBroadcasts()
{
DeviceId = "D11",
PatientId = PatientId,
Time = Now
};
_stateRepo.Setup(r => r.FindByDeviceIdAsync("D11"))
.ReturnsAsync((PumpState?)null);
_subs.Setup(x => x.GetSubscribers()).Returns([]);
await _service.InsertPumpObservation(obs);
_obsRepo.Verify(r => r.InsertAsync(It.IsAny<PumpObservation>()), Times.Once);
_stateRepo.Verify(r => r.UpsertAsync(It.IsAny<PumpState>()), Times.Once);
}
var obs = new PumpObservation
{
DeviceId = "D11",
PatientId = PatientId,
Time = Now
};
_stateRepo.Setup(r => r.FindByDeviceIdAsync("D11"))
.ReturnsAsync((PumpState?)null);
_subs.Setup(x => x.GetSubscribers()).Returns([]);
await _service.InsertPumpObservation(obs);
_obsRepo.Verify(r => r.InsertAsync(It.IsAny<PumpObservation>()), Times.Once);
_stateRepo.Verify(r => r.UpsertAsync(It.IsAny<PumpState>()), Times.Once);
}
}
@@ -14,45 +14,48 @@ namespace adas_core.Test.Services;
[TestFixture]
public class RecordingAlertServiceTest
{
/// <summary>
/// Initializes the mocked dependencies and test infrastructure required by the <see cref="RecordingAlertService"/> unit tests, including service and repository mocks, a fake HTTP context with a "TestUser" claim, and the system-under-test instance.
/// </summary>
[SetUp]
public void Setup()
{
_patientServiceMock = new Mock<IPatientService>();
_patientServiceLazy = new Lazy<IPatientService>(() => _patientServiceMock.Object);
_configObservationServiceMock = new Mock<IConfigObservationService>();
_recordingAlertRepositoryMock = new Mock<IRecordingAlertRepository>();
_recordingAlertArchiveRepositoryMock = new Mock<IRecordingAlertArchiveRepository>();
_clientMessageServiceMock = new Mock<IClientMessageService>();
_subscribersServiceMock = new Mock<ISubscribersService>();
var userClaims = new ClaimsPrincipal(new ClaimsIdentity([
new Claim(ClaimTypes.Name, "TestUser")
], "mock"));
var httpContextMock = new DefaultHttpContext
public void Setup()
{
User = userClaims
};
_httpContextAccessorMock.Setup(accessor => accessor.HttpContext)
.Returns(httpContextMock);
//optionsApiSettings = Microsoft.Extensions.Options.Options.Create<ApiSettings>(apiSettings);
_logger = new Mock<ILogger<RecordingAlertService>>();
_recordingAlertService = new RecordingAlertService(
_patientServiceLazy,
_configObservationServiceMock.Object,
_recordingAlertRepositoryMock.Object,
_recordingAlertArchiveRepositoryMock.Object,
//optionsApiSettings,
_logger.Object,
_clientMessageServiceMock.Object,
_subscribersServiceMock.Object,
_httpContextAccessorMock.Object,
_auditServiceMock.Object
);
}
_patientServiceMock = new Mock<IPatientService>();
_patientServiceLazy = new Lazy<IPatientService>(() => _patientServiceMock.Object);
_configObservationServiceMock = new Mock<IConfigObservationService>();
_recordingAlertRepositoryMock = new Mock<IRecordingAlertRepository>();
_recordingAlertArchiveRepositoryMock = new Mock<IRecordingAlertArchiveRepository>();
_clientMessageServiceMock = new Mock<IClientMessageService>();
_subscribersServiceMock = new Mock<ISubscribersService>();
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);
//optionsApiSettings = Microsoft.Extensions.Options.Options.Create<ApiSettings>(apiSettings);
_logger = new Mock<ILogger<RecordingAlertService>>();
_recordingAlertService = new RecordingAlertService(
_patientServiceLazy,
_configObservationServiceMock.Object,
_recordingAlertRepositoryMock.Object,
_recordingAlertArchiveRepositoryMock.Object,
//optionsApiSettings,
_logger.Object,
_clientMessageServiceMock.Object,
_subscribersServiceMock.Object,
_httpContextAccessorMock.Object,
_auditServiceMock.Object
);
}
private RecordingAlertService _recordingAlertService = null!;
private Mock<IPatientService> _patientServiceMock = null!;
@@ -78,96 +81,108 @@ public class RecordingAlertServiceTest
private static readonly ObjectId PatientId = ObjectId.GenerateNewId();
/// <summary>
/// Verifies that <see cref="RecordingAlertService.SaveRequest"/> does not persist a <see cref="PatientRecordingAlert"/> when the provided <see cref="ApiRequest"/> is neither a recording alert nor an ORU R01 message.
/// </summary>
[Test]
public async Task SaveRequest_Not_RecordingAlert_Not_ORU_R01_Return_not_insert()
{
var apiRequest = new ApiRequest();
await _recordingAlertService.SaveRequest(apiRequest);
_recordingAlertRepositoryMock.Verify(d => d.InsertOneAsync(It.IsAny<PatientRecordingAlert>()), Times.Never);
}
public async Task SaveRequest_Not_RecordingAlert_Not_ORU_R01_Return_not_insert()
{
var apiRequest = new ApiRequest();
await _recordingAlertService.SaveRequest(apiRequest);
_recordingAlertRepositoryMock.Verify(d => d.InsertOneAsync(It.IsAny<PatientRecordingAlert>()), Times.Never);
}
/// <summary>
/// Verifies that <see cref="RecordingAlertService.SaveRequest"/> does not insert a <see cref="PatientRecordingAlert"/> when both the patient number and point of care are null in the <see cref="ApiRequest"/>, even when the message type is "ORU_R11".
/// </summary>
[Test]
public async Task SaveRequest_patientNumber_and_pointOfCare_null_Return_not_insert()
{
var apiRequest = new ApiRequest
public async Task SaveRequest_patientNumber_and_pointOfCare_null_Return_not_insert()
{
Type = "ORU_R11",
MessageTime = Now
};
await _recordingAlertService.SaveRequest(apiRequest);
_recordingAlertRepositoryMock.Verify(d => d.InsertOneAsync(It.IsAny<PatientRecordingAlert>()), Times.Never);
}
var apiRequest = new ApiRequest
{
Type = "ORU_R11",
MessageTime = Now
};
await _recordingAlertService.SaveRequest(apiRequest);
_recordingAlertRepositoryMock.Verify(d => d.InsertOneAsync(It.IsAny<PatientRecordingAlert>()), Times.Never);
}
/// <summary>
/// Verifies that the SaveRequest method does not insert a patient recording alert when the specified patient cannot be found.
/// </summary>
[Test]
public async Task SaveRequest_Not_Find_Patient_Return_not_insert()
{
var person = new Person
public async Task SaveRequest_Not_Find_Patient_Return_not_insert()
{
FirstName = "Miguel",
LastName = "Villanueva",
Ids = new Dictionary<string, string> { { "MR", "437537" } }
};
var apiRequest = new ApiRequest
{
Location = new PatientLocation("UCI5C", "Box4"),
Patient = person,
PatientNumber = "437537",
Type = "ORU_R01",
PatientId = PatientId.ToString(),
MessageTime = Now
};
await _recordingAlertService.SaveRequest(apiRequest);
_recordingAlertRepositoryMock.Verify(d => d.InsertOneAsync(It.IsAny<PatientRecordingAlert>()), Times.Never);
}
var person = new Person
{
FirstName = "Miguel",
LastName = "Villanueva",
Ids = new Dictionary<string, string> { { "MR", "437537" } }
};
var apiRequest = new ApiRequest
{
Location = new PatientLocation("UCI5C", "Box4"),
Patient = person,
PatientNumber = "437537",
Type = "ORU_R01",
PatientId = PatientId.ToString(),
MessageTime = Now
};
await _recordingAlertService.SaveRequest(apiRequest);
_recordingAlertRepositoryMock.Verify(d => d.InsertOneAsync(It.IsAny<PatientRecordingAlert>()), Times.Never);
}
/// <summary>
/// Verifies that SaveRequest inserts a new <see cref="PatientRecordingAlert"/> into the repository when the configuration observation service returns no retention actions for the given recording alert.
/// </summary>
[Test]
public async Task SaveRequest_Alert_Return_insert()
{
var person = new Person
public async Task SaveRequest_Alert_Return_insert()
{
FirstName = "Miguel",
LastName = "Villanueva",
Ids = new Dictionary<string, string> { { "MR", "437537" } }
};
var patient = new Patient
{
Id = ObjectId.GenerateNewId(),
UnitString = "UCI5C",
Bed = "Box4",
PatientNumber = "437537",
Person = person
};
var recordingAlert = new PatientRecordingAlert
{
IsRecording = true
};
var apiRequest = new ApiRequest
{
Location = new PatientLocation("UCI5C", "Box4"),
Patient = person,
PatientNumber = "437537",
Type = "ORU_R01",
PatientId = PatientId.ToString(),
MessageTime = Now,
RecordingAlert = recordingAlert
};
_patientServiceMock.Setup(p => p.FindPatientByApiRequest(apiRequest)).ReturnsAsync(patient);
_configObservationServiceMock.Setup(c => c.RetentionActions(It.IsAny<PatientRecordingAlert>()))
.ReturnsAsync((ObservatitonRetentionResult?)null);
await _recordingAlertService.SaveRequest(apiRequest);
_recordingAlertRepositoryMock.Verify(d => d.InsertOneAsync(It.IsAny<PatientRecordingAlert>()), Times.Once);
}
var person = new Person
{
FirstName = "Miguel",
LastName = "Villanueva",
Ids = new Dictionary<string, string> { { "MR", "437537" } }
};
var patient = new Patient
{
Id = ObjectId.GenerateNewId(),
UnitString = "UCI5C",
Bed = "Box4",
PatientNumber = "437537",
Person = person
};
var recordingAlert = new PatientRecordingAlert
{
IsRecording = true
};
var apiRequest = new ApiRequest
{
Location = new PatientLocation("UCI5C", "Box4"),
Patient = person,
PatientNumber = "437537",
Type = "ORU_R01",
PatientId = PatientId.ToString(),
MessageTime = Now,
RecordingAlert = recordingAlert
};
_patientServiceMock.Setup(p => p.FindPatientByApiRequest(apiRequest)).ReturnsAsync(patient);
_configObservationServiceMock.Setup(c => c.RetentionActions(It.IsAny<PatientRecordingAlert>()))
.ReturnsAsync((ObservatitonRetentionResult?)null);
await _recordingAlertService.SaveRequest(apiRequest);
_recordingAlertRepositoryMock.Verify(d => d.InsertOneAsync(It.IsAny<PatientRecordingAlert>()), Times.Once);
}
}
+137 -121
View File
@@ -19,45 +19,50 @@ namespace adas_core.Test.Services;
[TestFixture]
public class RecordingServiceTest
{
/// <summary>
/// Sets up the test environment for <see cref="RecordingService"/> by initializing configuration options, creating mock
/// dependencies (logger, HTTP client factory, publisher, authentication, client message, subscribers, and patient services),
/// and configuring the publisher mock to successfully send both regular messages and errors.
/// </summary>
[SetUp]
public void Setup()
{
_optionsApiSettings = Options.Create(_apiSettings);
_optionsRabbitMqSettings = Options.Create(_rabbitMqSettings);
_optionsRecordingSettings = Options.Create(_recordingSettings);
_publisherServiceMock = new Mock<IPublisherService>();
_logger = new Mock<ILogger<RecordingService>>();
_authServiceMock = new Mock<IAuthService>();
_httpClientFactoryMock = new Mock<IHttpClientFactory>();
_httpMessageHandlerMock = new Mock<HttpMessageHandler>();
_clientMessageServiceMock = new Mock<IClientMessageService>();
_subscribersServiceMock = new Mock<ISubscribersService>();
_patientServiceMock = new Mock<Lazy<IPatientService>>();
var client = new HttpClient(_httpMessageHandlerMock.Object);
_httpClientFactoryMock.Setup(c => c.CreateClient(It.IsAny<string>())).Returns(client);
_recordingService = new RecordingService(
_optionsRabbitMqSettings,
_optionsRecordingSettings,
_logger.Object,
_httpClientFactoryMock.Object,
_publisherServiceMock.Object,
_authServiceMock.Object,
_optionsApiSettings,
_clientMessageServiceMock.Object,
_subscribersServiceMock.Object,
_patientServiceMock.Object
);
// Set up the mock object to return a specific value when a method is called
_publisherServiceMock.Setup(x => x.SendMessage(It.IsAny<object>(), It.IsAny<string>())).ReturnsAsync(true);
_publisherServiceMock.Setup(x => x.SendMessageError(It.IsAny<Error>(), It.IsAny<string>())).ReturnsAsync(true);
}
public void Setup()
{
_optionsApiSettings = Options.Create(_apiSettings);
_optionsRabbitMqSettings = Options.Create(_rabbitMqSettings);
_optionsRecordingSettings = Options.Create(_recordingSettings);
_publisherServiceMock = new Mock<IPublisherService>();
_logger = new Mock<ILogger<RecordingService>>();
_authServiceMock = new Mock<IAuthService>();
_httpClientFactoryMock = new Mock<IHttpClientFactory>();
_httpMessageHandlerMock = new Mock<HttpMessageHandler>();
_clientMessageServiceMock = new Mock<IClientMessageService>();
_subscribersServiceMock = new Mock<ISubscribersService>();
_patientServiceMock = new Mock<Lazy<IPatientService>>();
var client = new HttpClient(_httpMessageHandlerMock.Object);
_httpClientFactoryMock.Setup(c => c.CreateClient(It.IsAny<string>())).Returns(client);
_recordingService = new RecordingService(
_optionsRabbitMqSettings,
_optionsRecordingSettings,
_logger.Object,
_httpClientFactoryMock.Object,
_publisherServiceMock.Object,
_authServiceMock.Object,
_optionsApiSettings,
_clientMessageServiceMock.Object,
_subscribersServiceMock.Object,
_patientServiceMock.Object
);
// Set up the mock object to return a specific value when a method is called
_publisherServiceMock.Setup(x => x.SendMessage(It.IsAny<object>(), It.IsAny<string>())).ReturnsAsync(true);
_publisherServiceMock.Setup(x => x.SendMessageError(It.IsAny<Error>(), It.IsAny<string>())).ReturnsAsync(true);
}
private RecordingService _recordingService = null!;
@@ -93,95 +98,106 @@ public class RecordingServiceTest
private Mock<IClientMessageService> _clientMessageServiceMock = null!;
private Mock<ISubscribersService> _subscribersServiceMock = null!;
/// <summary>
/// Verifies that <see cref="RecordingService.GetRecordings"/> returns an empty collection of <see cref="RecordingData"/> when the HTTP response indicates an unauthorized (401) status, ensuring the service correctly handles failed authentication scenarios by yielding no recordings rather than throwing or returning null.
/// </summary>
[Test]
public async Task GetRecordings_Return_Empty()
{
var httpResponseMessage = new HttpResponseMessage
public async Task GetRecordings_Return_Empty()
{
StatusCode = HttpStatusCode.Unauthorized,
Content = new StringContent("Content text.")
};
var recordingData = new List<RecordingData>();
_authServiceMock.Setup(s => s.GetToken()).ReturnsAsync("abc");
_httpMessageHandlerMock.Protected()
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(),
ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(httpResponseMessage);
var result = await _recordingService.GetRecordings(4);
Assert.That(result, Is.Not.Null);
//Assert.AreEqual(result, recordingData);
Assert.That(result, Is.EqualTo(recordingData));
}
[Test]
public async Task GetRecordings_Return_RecordingData()
{
var patient = new Patient
{
Id = "id",
FirstName = "firstName",
LastName = "lastName"
};
var recordingData = new List<RecordingData>
{
new()
var httpResponseMessage = new HttpResponseMessage
{
Patient = patient,
RoomId = 4,
Status = "INITIALIZED"
}
};
var httpResponseMessage = new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent(JsonConvert.SerializeObject(recordingData), Encoding.UTF8, "application/json")
};
_httpMessageHandlerMock.Protected()
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(),
ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(httpResponseMessage);
_authServiceMock.Setup(s => s.GetToken()).ReturnsAsync("abc");
var result = await _recordingService.GetRecordings(4);
Assert.That(result, Is.Not.Null);
using (Assert.EnterMultipleScope())
{
Assert.That(recordingData[0].Status, Is.EqualTo(result![0].Status));
Assert.That(recordingData[0].Patient?.Id, Is.EqualTo(result[0].Patient?.Id));
Assert.That(recordingData[0].Patient?.LastName, Is.EqualTo(result[0].Patient?.LastName));
Assert.That(recordingData[0].Patient?.FirstName, Is.EqualTo(result[0].Patient?.FirstName));
Assert.That(recordingData[0].RoomId, Is.EqualTo(result[0].RoomId));
StatusCode = HttpStatusCode.Unauthorized,
Content = new StringContent("Content text.")
};
var recordingData = new List<RecordingData>();
_authServiceMock.Setup(s => s.GetToken()).ReturnsAsync("abc");
_httpMessageHandlerMock.Protected()
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(),
ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(httpResponseMessage);
var result = await _recordingService.GetRecordings(4);
Assert.That(result, Is.Not.Null);
//Assert.AreEqual(result, recordingData);
Assert.That(result, Is.EqualTo(recordingData));
}
}
/// <summary>
/// Verifies that <see cref="RecordingService.GetRecordings"/> returns the expected <see cref="RecordingData"/> list
/// when the HTTP endpoint responds with a successful payload containing a recording and its associated patient.
/// Asserts that the returned items match the source data for status, room identifier, and patient identity fields.
/// </summary>
[Test]
public Task SaveRequest()
{
ApiRequest apiRequest = new();
var httpResponseMessage = new HttpResponseMessage
public async Task GetRecordings_Return_RecordingData()
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent("Content text.")
};
var patient = new Patient
{
Id = "id",
FirstName = "firstName",
LastName = "lastName"
};
var recordingData = new List<RecordingData>
{
new()
{
Patient = patient,
RoomId = 4,
Status = "INITIALIZED"
}
};
var httpResponseMessage = new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent(JsonConvert.SerializeObject(recordingData), Encoding.UTF8, "application/json")
};
_httpMessageHandlerMock.Protected()
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(),
ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(httpResponseMessage);
_authServiceMock.Setup(s => s.GetToken()).ReturnsAsync("abc");
var result = await _recordingService.GetRecordings(4);
Assert.That(result, Is.Not.Null);
using (Assert.EnterMultipleScope())
{
Assert.That(recordingData[0].Status, Is.EqualTo(result![0].Status));
Assert.That(recordingData[0].Patient?.Id, Is.EqualTo(result[0].Patient?.Id));
Assert.That(recordingData[0].Patient?.LastName, Is.EqualTo(result[0].Patient?.LastName));
Assert.That(recordingData[0].Patient?.FirstName, Is.EqualTo(result[0].Patient?.FirstName));
Assert.That(recordingData[0].RoomId, Is.EqualTo(result[0].RoomId));
}
}
_httpMessageHandlerMock.Protected()
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(),
ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(httpResponseMessage);
Func<Task> act = async () => await _recordingService.SaveRequest(apiRequest);
Assert.ThrowsAsync<NotImplementedException>(act);
return Task.CompletedTask;
}
/// <summary>
/// Tests that calling <c>SaveRequest</c> on the recording service throws a <see cref="NotImplementedException"/> when supplied with an <see cref="ApiRequest"/>, using a mocked HTTP message handler that returns a successful response.
/// </summary>
[Test]
public Task SaveRequest()
{
ApiRequest apiRequest = new();
var httpResponseMessage = new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent("Content text.")
};
_httpMessageHandlerMock.Protected()
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(),
ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(httpResponseMessage);
Func<Task> act = async () => await _recordingService.SaveRequest(apiRequest);
Assert.ThrowsAsync<NotImplementedException>(act);
return Task.CompletedTask;
}
}
+148 -126
View File
@@ -11,165 +11,187 @@ public class RedisLockProviderTest
private Mock<IDatabase> _mockDb = null!;
private RedisLockProvider _provider = null!;
/// <summary>
/// Initializes the test environment by creating a mock <see cref="IDatabase"/> and instantiating a <see cref="RedisLockProvider"/> configured to use the mocked database.
/// </summary>
[SetUp]
public void SetUp()
{
_mockDb = new Mock<IDatabase>();
_provider = new RedisLockProvider(() => _mockDb.Object);
}
public void SetUp()
{
_mockDb = new Mock<IDatabase>();
_provider = new RedisLockProvider(() => _mockDb.Object);
}
#region TC-45
/// <summary>
/// Verifies that <c>AcquireAsync</c> returns <c>true</c>, stores the lock token under the <c>lock:</c> prefixed key, applies the configured TTL, and uses the <c>When.NotExists</c> flag when Redis accepts the NX SET operation. Also asserts that a subsequent <c>ReleaseAsync</c> invokes the Redis release script.
/// </summary>
[Test]
public async Task AcquireAsync_ReturnsTrue_AndStoresToken_WhenRedisAcceptsNxSet()
{
_mockDb
.Setup(db => db.StringSetAsync(
It.IsAny<RedisKey>(),
public async Task AcquireAsync_ReturnsTrue_AndStoresToken_WhenRedisAcceptsNxSet()
{
_mockDb
.Setup(db => db.StringSetAsync(
It.IsAny<RedisKey>(),
It.IsAny<RedisValue>(),
It.IsAny<TimeSpan?>(),
It.IsAny<When>()))
.ReturnsAsync(true);
_mockDb
.Setup(db => db.ScriptEvaluateAsync(
It.IsAny<string>(),
It.IsAny<RedisKey[]?>(),
It.IsAny<RedisValue[]?>(),
It.IsAny<CommandFlags>()))
.Returns(Task.FromResult<RedisResult>(null!));
var result = await _provider.AcquireAsync("key", TimeSpan.FromSeconds(5));
Assert.That(result, Is.True);
_mockDb.Verify(db => db.StringSetAsync(
It.Is<RedisKey>(k => k == (RedisKey)"lock:key"),
It.IsAny<RedisValue>(),
It.IsAny<TimeSpan?>(),
It.IsAny<When>()))
.ReturnsAsync(true);
_mockDb
.Setup(db => db.ScriptEvaluateAsync(
(TimeSpan?)TimeSpan.FromSeconds(5),
When.NotExists), Times.Once());
await _provider.ReleaseAsync("key");
_mockDb.Verify(db => db.ScriptEvaluateAsync(
It.IsAny<string>(),
It.IsAny<RedisKey[]?>(),
It.IsAny<RedisValue[]?>(),
It.IsAny<CommandFlags>()))
.Returns(Task.FromResult<RedisResult>(null!));
var result = await _provider.AcquireAsync("key", TimeSpan.FromSeconds(5));
Assert.That(result, Is.True);
_mockDb.Verify(db => db.StringSetAsync(
It.Is<RedisKey>(k => k == (RedisKey)"lock:key"),
It.IsAny<RedisValue>(),
(TimeSpan?)TimeSpan.FromSeconds(5),
When.NotExists), Times.Once());
await _provider.ReleaseAsync("key");
_mockDb.Verify(db => db.ScriptEvaluateAsync(
It.IsAny<string>(),
It.IsAny<RedisKey[]?>(),
It.IsAny<RedisValue[]?>(),
It.IsAny<CommandFlags>()), Times.Once());
}
It.IsAny<CommandFlags>()), Times.Once());
}
#endregion
#region TC-46
/// <summary>
/// Verifies that <c>AcquireAsync</c> retries the underlying Nx-style <c>StringSetAsync</c> call with delays
/// between attempts when the operation initially fails, continuing until it succeeds.
/// Asserts the call is retried the expected number of times and that the elapsed time confirms
/// delays were actually applied between attempts.
/// </summary>
[Test]
public async Task AcquireAsync_RetriesWithDelays_UntilNxSucceeds()
{
_mockDb
.SetupSequence(db => db.StringSetAsync(
public async Task AcquireAsync_RetriesWithDelays_UntilNxSucceeds()
{
_mockDb
.SetupSequence(db => db.StringSetAsync(
It.IsAny<RedisKey>(),
It.IsAny<RedisValue>(),
It.IsAny<TimeSpan?>(),
It.IsAny<When>()))
.ReturnsAsync(false)
.ReturnsAsync(false)
.ReturnsAsync(false)
.ReturnsAsync(true);
var sw = Stopwatch.StartNew();
var result = await _provider.AcquireAsync("key", TimeSpan.FromSeconds(1));
sw.Stop();
Assert.That(result, Is.True);
_mockDb.Verify(db => db.StringSetAsync(
It.IsAny<RedisKey>(),
It.IsAny<RedisValue>(),
It.IsAny<TimeSpan?>(),
It.IsAny<When>()))
.ReturnsAsync(false)
.ReturnsAsync(false)
.ReturnsAsync(false)
.ReturnsAsync(true);
var sw = Stopwatch.StartNew();
var result = await _provider.AcquireAsync("key", TimeSpan.FromSeconds(1));
sw.Stop();
Assert.That(result, Is.True);
_mockDb.Verify(db => db.StringSetAsync(
It.IsAny<RedisKey>(),
It.IsAny<RedisValue>(),
It.IsAny<TimeSpan?>(),
It.IsAny<When>()), Times.Exactly(4));
Assert.That(sw.ElapsedMilliseconds, Is.GreaterThan(100));
}
It.IsAny<When>()), Times.Exactly(4));
Assert.That(sw.ElapsedMilliseconds, Is.GreaterThan(100));
}
#endregion
#region TC-47
/// <summary>
/// Verifies that AcquireAsync returns <c>false</c> and performs multiple retry attempts when the underlying lock acquisition operation consistently fails within the specified timeout.
/// </summary>
[Test]
public async Task AcquireAsync_ReturnsFalse_AfterTimeoutWithMultipleRetries()
{
_mockDb
.Setup(db => db.StringSetAsync(
public async Task AcquireAsync_ReturnsFalse_AfterTimeoutWithMultipleRetries()
{
_mockDb
.Setup(db => db.StringSetAsync(
It.IsAny<RedisKey>(),
It.IsAny<RedisValue>(),
It.IsAny<TimeSpan?>(),
It.IsAny<When>()))
.ReturnsAsync(false);
var sw = Stopwatch.StartNew();
var result = await _provider.AcquireAsync("key", TimeSpan.FromMilliseconds(200));
sw.Stop();
Assert.That(result, Is.False);
Assert.That(sw.ElapsedMilliseconds, Is.GreaterThan(100));
_mockDb.Verify(db => db.StringSetAsync(
It.IsAny<RedisKey>(),
It.IsAny<RedisValue>(),
It.IsAny<TimeSpan?>(),
It.IsAny<When>()))
.ReturnsAsync(false);
var sw = Stopwatch.StartNew();
var result = await _provider.AcquireAsync("key", TimeSpan.FromMilliseconds(200));
sw.Stop();
Assert.That(result, Is.False);
Assert.That(sw.ElapsedMilliseconds, Is.GreaterThan(100));
_mockDb.Verify(db => db.StringSetAsync(
It.IsAny<RedisKey>(),
It.IsAny<RedisValue>(),
It.IsAny<TimeSpan?>(),
It.IsAny<When>()), Times.AtLeast(2));
}
It.IsAny<When>()), Times.AtLeast(2));
}
#endregion
#region TC-48
/// <summary>
/// Verifies that releasing a lock invokes the Lua script evaluation with the correct lock key (prefixed with "lock:") and the token previously captured during lock acquisition.
/// </summary>
[Test]
public async Task ReleaseAsync_CallsScriptEvaluateWithCorrectKeyAndToken()
{
RedisValue capturedToken = default;
_mockDb
.Setup(db => db.StringSetAsync(
It.IsAny<RedisKey>(),
It.IsAny<RedisValue>(),
It.IsAny<TimeSpan?>(),
It.IsAny<When>()))
.Callback<RedisKey, RedisValue, TimeSpan?, When>(
(_, v, _, _) => capturedToken = v)
.ReturnsAsync(true);
_mockDb
.Setup(db => db.ScriptEvaluateAsync(
public async Task ReleaseAsync_CallsScriptEvaluateWithCorrectKeyAndToken()
{
RedisValue capturedToken = default;
_mockDb
.Setup(db => db.StringSetAsync(
It.IsAny<RedisKey>(),
It.IsAny<RedisValue>(),
It.IsAny<TimeSpan?>(),
It.IsAny<When>()))
.Callback<RedisKey, RedisValue, TimeSpan?, When>(
(_, v, _, _) => capturedToken = v)
.ReturnsAsync(true);
_mockDb
.Setup(db => db.ScriptEvaluateAsync(
It.IsAny<string>(),
It.IsAny<RedisKey[]?>(),
It.IsAny<RedisValue[]?>(),
It.IsAny<CommandFlags>()))
.Returns(Task.FromResult<RedisResult>(null!));
await _provider.AcquireAsync("key", TimeSpan.FromSeconds(5));
await _provider.ReleaseAsync("key");
_mockDb.Verify(db => db.ScriptEvaluateAsync(
It.IsAny<string>(),
It.IsAny<RedisKey[]?>(),
It.IsAny<RedisValue[]?>(),
It.IsAny<CommandFlags>()))
.Returns(Task.FromResult<RedisResult>(null!));
await _provider.AcquireAsync("key", TimeSpan.FromSeconds(5));
await _provider.ReleaseAsync("key");
_mockDb.Verify(db => db.ScriptEvaluateAsync(
It.IsAny<string>(),
It.Is<RedisKey[]?>(keys => keys != null && keys[0] == (RedisKey)"lock:key"),
It.Is<RedisValue[]?>(vals => vals != null && vals[0] == capturedToken),
It.IsAny<CommandFlags>()), Times.Once());
}
It.Is<RedisKey[]?>(keys => keys != null && keys[0] == (RedisKey)"lock:key"),
It.Is<RedisValue[]?>(vals => vals != null && vals[0] == capturedToken),
It.IsAny<CommandFlags>()), Times.Once());
}
#endregion
#region TC-49
/// <summary>
/// Verifies that ReleaseAsync is idempotent when invoked without a prior acquire operation,
/// ensuring that no script evaluation is performed on the underlying database in this scenario.
/// </summary>
[Test]
public async Task ReleaseAsync_IsIdempotent_WhenCalledWithoutPriorAcquire()
{
_mockDb
.Setup(db => db.ScriptEvaluateAsync(
public async Task ReleaseAsync_IsIdempotent_WhenCalledWithoutPriorAcquire()
{
_mockDb
.Setup(db => db.ScriptEvaluateAsync(
It.IsAny<string>(),
It.IsAny<RedisKey[]?>(),
It.IsAny<RedisValue[]?>(),
It.IsAny<CommandFlags>()))
.Returns(Task.FromResult<RedisResult>(null!));
await _provider.ReleaseAsync("key");
_mockDb.Verify(db => db.ScriptEvaluateAsync(
It.IsAny<string>(),
It.IsAny<RedisKey[]?>(),
It.IsAny<RedisValue[]?>(),
It.IsAny<CommandFlags>()))
.Returns(Task.FromResult<RedisResult>(null!));
await _provider.ReleaseAsync("key");
_mockDb.Verify(db => db.ScriptEvaluateAsync(
It.IsAny<string>(),
It.IsAny<RedisKey[]?>(),
It.IsAny<RedisValue[]?>(),
It.IsAny<CommandFlags>()), Times.Never());
}
It.IsAny<CommandFlags>()), Times.Never());
}
#endregion
}
+245 -189
View File
@@ -17,235 +17,291 @@ public class RedisServiceTest
private Mock<IDatabase> _mockDb = null!;
private LockManagerService _lockMgr = null!;
/// <summary>
/// Initializes test dependencies before each test execution by creating a mock <see cref="IDatabase"/> instance and instantiating the <see cref="LockManagerService"/> with a mocked logger and an in-memory lock provider.
/// </summary>
[SetUp]
public void SetUp()
{
_mockDb = new Mock<IDatabase>();
_lockMgr = new LockManagerService(
new Mock<ILogger<LockManagerService>>().Object,
new InMemoryLockProvider());
}
public void SetUp()
{
_mockDb = new Mock<IDatabase>();
_lockMgr = new LockManagerService(
new Mock<ILogger<LockManagerService>>().Object,
new InMemoryLockProvider());
}
/// <summary>
/// Creates a configured instance of <see cref="RedisService"/> for testing, allowing
/// optional customization of cache settings and the simulated availability of the Redis backend.
/// </summary>
/// <param name="settings">Optional cache settings to apply; when <c>null</c>, a new default <see cref="CacheSettings"/> instance is used.</param>
/// <param name="redisAvailable">Flag indicating whether the Redis service should be marked as available; defaults to <c>true</c>.</param>
/// <returns>A <see cref="RedisService"/> instance with the database and availability state initialized for testing.</returns>
private RedisService CreateSut(CacheSettings? settings = null, bool redisAvailable = true)
{
var sut = new RedisService(
Options.Create(settings ?? new CacheSettings()),
new Mock<ILogger<RedisService>>().Object,
_lockMgr);
SetField(sut, "_database", _mockDb.Object);
SetField(sut, "_isRedisAvailable", redisAvailable);
return sut;
}
{
var sut = new RedisService(
Options.Create(settings ?? new CacheSettings()),
new Mock<ILogger<RedisService>>().Object,
_lockMgr);
SetField(sut, "_database", _mockDb.Object);
SetField(sut, "_isRedisAvailable", redisAvailable);
return sut;
}
/// <summary>
/// Sets the value of a non-public instance field on a <see cref="RedisService"/> object using reflection.
/// </summary>
/// <param name="target">The <see cref="RedisService"/> instance whose field will be set.</param>
/// <param name="name">The name of the non-public instance field to assign.</param>
/// <param name="value">The value to assign to the field. Can be null.</param>
private static void SetField(object target, string name, object? value)
=> typeof(RedisService)
.GetField(name, BindingFlags.NonPublic | BindingFlags.Instance)!
.SetValue(target, value);
=> typeof(RedisService)
.GetField(name, BindingFlags.NonPublic | BindingFlags.Instance)!
.SetValue(target, value);
/// <summary>
/// Configures the mock database to return a successful result (<c>true</c>) for any invocation of <c>StringSetAsync</c>, regardless of the supplied key, value, expiry, overwrite flag, condition, or command flags.
/// </summary>
private void SetupStringSetAsync()
=> _mockDb
.Setup(db => db.StringSetAsync(
It.IsAny<RedisKey>(), It.IsAny<RedisValue>(), It.IsAny<TimeSpan?>(),
It.IsAny<bool>(), It.IsAny<When>(), It.IsAny<CommandFlags>()))
.ReturnsAsync(true);
=> _mockDb
.Setup(db => db.StringSetAsync(
It.IsAny<RedisKey>(), It.IsAny<RedisValue>(), It.IsAny<TimeSpan?>(),
It.IsAny<bool>(), It.IsAny<When>(), It.IsAny<CommandFlags>()))
.ReturnsAsync(true);
/// <summary>
/// Configures the mock Redis database to handle <c>KeyExpire</c> calls by returning <c>true</c> for any combination of key, expiration, condition, and command flag arguments.
/// </summary>
private void SetupKeyExpire()
=> _mockDb
.Setup(db => db.KeyExpire(
It.IsAny<RedisKey>(), It.IsAny<TimeSpan?>(),
It.IsAny<ExpireWhen>(), It.IsAny<CommandFlags>()))
.Returns(true);
=> _mockDb
.Setup(db => db.KeyExpire(
It.IsAny<RedisKey>(), It.IsAny<TimeSpan?>(),
It.IsAny<ExpireWhen>(), It.IsAny<CommandFlags>()))
.Returns(true);
#region TC-38
/// <summary>
/// Verifies that <c>GetOrSetObjectAsync</c> invokes the supplied factory when Redis is unavailable, returning the factory's result without attempting any Redis read or write operations.
/// </summary>
[Test]
public async Task GetOrSetObjectAsync_ExecutesFactory_WhenRedisUnavailable()
{
var sut = CreateSut(redisAvailable: false);
var factoryInvoked = false;
var result = await sut.GetOrSetObjectAsync<string>(
"patients:latestObs:abc",
() => { factoryInvoked = true; return Task.FromResult("result"); });
Assert.That(factoryInvoked, Is.True);
Assert.That(result, Is.EqualTo("result"));
_mockDb.Verify(db => db.StringGetAsync(It.IsAny<RedisKey>(), It.IsAny<CommandFlags>()), Times.Never);
_mockDb.Verify(db => db.StringSetAsync(It.IsAny<RedisKey>(), It.IsAny<RedisValue>(), It.IsAny<TimeSpan?>(), It.IsAny<bool>(), It.IsAny<When>(), It.IsAny<CommandFlags>()), Times.Never);
}
public async Task GetOrSetObjectAsync_ExecutesFactory_WhenRedisUnavailable()
{
var sut = CreateSut(redisAvailable: false);
var factoryInvoked = false;
var result = await sut.GetOrSetObjectAsync<string>(
"patients:latestObs:abc",
() => { factoryInvoked = true; return Task.FromResult("result"); });
Assert.That(factoryInvoked, Is.True);
Assert.That(result, Is.EqualTo("result"));
_mockDb.Verify(db => db.StringGetAsync(It.IsAny<RedisKey>(), It.IsAny<CommandFlags>()), Times.Never);
_mockDb.Verify(db => db.StringSetAsync(It.IsAny<RedisKey>(), It.IsAny<RedisValue>(), It.IsAny<TimeSpan?>(), It.IsAny<bool>(), It.IsAny<When>(), It.IsAny<CommandFlags>()), Times.Never);
}
#endregion
#region TC-39
/// <summary>
/// Verifies that <c>GetOrSetObjectAsync</c> returns the cached object deserialized from Redis
/// when a value is present in the cache, without invoking the factory delegate and without
/// attempting to write back to Redis.
/// </summary>
[Test]
public async Task GetOrSetObjectAsync_ReturnsCachedObject_WhenRedisHit()
{
var cached = new TestModel("cached");
_mockDb.Setup(db => db.StringGetAsync(It.IsAny<RedisKey>(), It.IsAny<CommandFlags>()))
.ReturnsAsync((RedisValue)JsonConvert.SerializeObject(cached));
SetupKeyExpire();
var sut = CreateSut();
var factoryInvoked = false;
var result = await sut.GetOrSetObjectAsync<TestModel>(
"patients:latestObs:abc",
() => { factoryInvoked = true; return Task.FromResult(new TestModel("fresh")); });
Assert.That(factoryInvoked, Is.False);
Assert.That(result?.Name, Is.EqualTo("cached"));
_mockDb.Verify(db => db.StringSetAsync(It.IsAny<RedisKey>(), It.IsAny<RedisValue>(), It.IsAny<TimeSpan?>(), It.IsAny<bool>(), It.IsAny<When>(), It.IsAny<CommandFlags>()), Times.Never);
}
public async Task GetOrSetObjectAsync_ReturnsCachedObject_WhenRedisHit()
{
var cached = new TestModel("cached");
_mockDb.Setup(db => db.StringGetAsync(It.IsAny<RedisKey>(), It.IsAny<CommandFlags>()))
.ReturnsAsync((RedisValue)JsonConvert.SerializeObject(cached));
SetupKeyExpire();
var sut = CreateSut();
var factoryInvoked = false;
var result = await sut.GetOrSetObjectAsync<TestModel>(
"patients:latestObs:abc",
() => { factoryInvoked = true; return Task.FromResult(new TestModel("fresh")); });
Assert.That(factoryInvoked, Is.False);
Assert.That(result?.Name, Is.EqualTo("cached"));
_mockDb.Verify(db => db.StringSetAsync(It.IsAny<RedisKey>(), It.IsAny<RedisValue>(), It.IsAny<TimeSpan?>(), It.IsAny<bool>(), It.IsAny<When>(), It.IsAny<CommandFlags>()), Times.Never);
}
#endregion
#region TC-40
/// <summary>
/// Verifies that <c>GetObjectAsync</c> calls the underlying Redis <c>KeyExpire</c> command with the configured TTL
/// when the <c>updateExpiration</c> flag is set to <c>true</c>, ensuring cache entries are refreshed upon a successful hit.
/// </summary>
[Test]
public async Task GetObjectAsync_CallsKeyExpire_WhenUpdateExpirationIsTrue()
{
var settings = new CacheSettings
public async Task GetObjectAsync_CallsKeyExpire_WhenUpdateExpirationIsTrue()
{
Redis = new RedisSettings { Ttl = new TtlSettings { PatientsSeconds = 300 } }
};
_mockDb.Setup(db => db.StringGetAsync(It.IsAny<RedisKey>(), It.IsAny<CommandFlags>()))
.ReturnsAsync((RedisValue)JsonConvert.SerializeObject(new TestModel("hit")));
SetupKeyExpire();
var sut = CreateSut(settings);
await sut.GetObjectAsync<TestModel>("patients:latestObs:abc", updateExpiration: true);
_mockDb.Verify(
db => db.KeyExpire(
It.IsAny<RedisKey>(),
It.Is<TimeSpan?>(t => t == TimeSpan.FromSeconds(300)),
It.IsAny<ExpireWhen>(),
It.IsAny<CommandFlags>()),
Times.Once);
}
var settings = new CacheSettings
{
Redis = new RedisSettings { Ttl = new TtlSettings { PatientsSeconds = 300 } }
};
_mockDb.Setup(db => db.StringGetAsync(It.IsAny<RedisKey>(), It.IsAny<CommandFlags>()))
.ReturnsAsync((RedisValue)JsonConvert.SerializeObject(new TestModel("hit")));
SetupKeyExpire();
var sut = CreateSut(settings);
await sut.GetObjectAsync<TestModel>("patients:latestObs:abc", updateExpiration: true);
_mockDb.Verify(
db => db.KeyExpire(
It.IsAny<RedisKey>(),
It.Is<TimeSpan?>(t => t == TimeSpan.FromSeconds(300)),
It.IsAny<ExpireWhen>(),
It.IsAny<CommandFlags>()),
Times.Once);
}
/// <summary>
/// Verifies that <c>GetObjectAsync</c> does not invoke the Redis key expiration command
/// when the caller explicitly requests that the existing expiration be left unchanged.
/// </summary>
[Test]
public async Task GetObjectAsync_DoesNotCallKeyExpire_WhenUpdateExpirationIsFalse()
{
_mockDb.Setup(db => db.StringGetAsync(It.IsAny<RedisKey>(), It.IsAny<CommandFlags>()))
.ReturnsAsync((RedisValue)JsonConvert.SerializeObject(new TestModel("hit")));
var sut = CreateSut();
await sut.GetObjectAsync<TestModel>("patients:latestObs:abc", updateExpiration: false);
_mockDb.Verify(
db => db.KeyExpire(It.IsAny<RedisKey>(), It.IsAny<TimeSpan?>(), It.IsAny<ExpireWhen>(), It.IsAny<CommandFlags>()),
Times.Never);
}
public async Task GetObjectAsync_DoesNotCallKeyExpire_WhenUpdateExpirationIsFalse()
{
_mockDb.Setup(db => db.StringGetAsync(It.IsAny<RedisKey>(), It.IsAny<CommandFlags>()))
.ReturnsAsync((RedisValue)JsonConvert.SerializeObject(new TestModel("hit")));
var sut = CreateSut();
await sut.GetObjectAsync<TestModel>("patients:latestObs:abc", updateExpiration: false);
_mockDb.Verify(
db => db.KeyExpire(It.IsAny<RedisKey>(), It.IsAny<TimeSpan?>(), It.IsAny<ExpireWhen>(), It.IsAny<CommandFlags>()),
Times.Never);
}
#endregion
#region TC-41
/// <summary>
/// Verifies that <c>GetOrSetObjectAsync</c> invokes the supplied factory and persists the resulting object
/// to Redis with the configured TTL when the cache lookup returns a miss (i.e., the stored value is null).
/// Ensures the factory delegate is executed, the deserialized value matches the factory output, and the
/// object is written to cache with the expected expiration.
/// </summary>
[Test]
public async Task GetOrSetObjectAsync_InvokesFactoryAndPersists_WhenCacheMiss()
{
var settings = new CacheSettings
public async Task GetOrSetObjectAsync_InvokesFactoryAndPersists_WhenCacheMiss()
{
Redis = new RedisSettings { Ttl = new TtlSettings { PatientsSeconds = 300, PatientObservationsSeconds = 300} }
};
_mockDb.Setup(db => db.StringGetAsync(It.IsAny<RedisKey>(), It.IsAny<CommandFlags>()))
.ReturnsAsync(RedisValue.Null);
SetupStringSetAsync();
var sut = CreateSut(settings);
var expected = new TestModel("fresh");
var factoryInvoked = false;
var result = await sut.GetOrSetObjectAsync<TestModel>(
"patients:latestObs:abc",
() => { factoryInvoked = true; return Task.FromResult(expected); });
var expectedJson = JsonConvert.SerializeObject(expected);
Assert.That(factoryInvoked, Is.True);
Assert.That(result?.Name, Is.EqualTo("fresh"));
_mockDb.Verify(
db => db.StringSetAsync(
It.IsAny<RedisKey>(),
It.IsAny<RedisValue>(),
It.Is<TimeSpan?>(t => t == TimeSpan.FromSeconds(300)),
It.IsAny<bool>(),
It.IsAny<When>(),
It.IsAny<CommandFlags>()),
Times.Once);
It.Is<RedisValue>(v => v.Equals(expectedJson));
}
var settings = new CacheSettings
{
Redis = new RedisSettings { Ttl = new TtlSettings { PatientsSeconds = 300, PatientObservationsSeconds = 300} }
};
_mockDb.Setup(db => db.StringGetAsync(It.IsAny<RedisKey>(), It.IsAny<CommandFlags>()))
.ReturnsAsync(RedisValue.Null);
SetupStringSetAsync();
var sut = CreateSut(settings);
var expected = new TestModel("fresh");
var factoryInvoked = false;
var result = await sut.GetOrSetObjectAsync<TestModel>(
"patients:latestObs:abc",
() => { factoryInvoked = true; return Task.FromResult(expected); });
var expectedJson = JsonConvert.SerializeObject(expected);
Assert.That(factoryInvoked, Is.True);
Assert.That(result?.Name, Is.EqualTo("fresh"));
_mockDb.Verify(
db => db.StringSetAsync(
It.IsAny<RedisKey>(),
It.IsAny<RedisValue>(),
It.Is<TimeSpan?>(t => t == TimeSpan.FromSeconds(300)),
It.IsAny<bool>(),
It.IsAny<When>(),
It.IsAny<CommandFlags>()),
Times.Once);
It.Is<RedisValue>(v => v.Equals(expectedJson));
}
#endregion
#region TC-42
/// <summary>
/// Verifies that when the cache lookup returns no value and the factory delegate produces <c>null</c>,
/// the method returns <c>null</c> and does not persist any value to the underlying cache store.
/// </summary>
[Test]
public async Task GetOrSetObjectAsync_DoesNotPersist_WhenFactoryReturnsNull()
{
_mockDb.Setup(db => db.StringGetAsync(It.IsAny<RedisKey>(), It.IsAny<CommandFlags>()))
.ReturnsAsync(RedisValue.Null);
var sut = CreateSut();
var result = await sut.GetOrSetObjectAsync<TestModel?>(
"patients:latestObs:abc",
() => Task.FromResult<TestModel?>(null));
Assert.That(result, Is.Null);
_mockDb.Verify(
db => db.StringSetAsync(It.IsAny<RedisKey>(), It.IsAny<RedisValue>(), It.IsAny<TimeSpan?>(), It.IsAny<bool>(), It.IsAny<When>(), It.IsAny<CommandFlags>()),
Times.Never);
}
public async Task GetOrSetObjectAsync_DoesNotPersist_WhenFactoryReturnsNull()
{
_mockDb.Setup(db => db.StringGetAsync(It.IsAny<RedisKey>(), It.IsAny<CommandFlags>()))
.ReturnsAsync(RedisValue.Null);
var sut = CreateSut();
var result = await sut.GetOrSetObjectAsync<TestModel?>(
"patients:latestObs:abc",
() => Task.FromResult<TestModel?>(null));
Assert.That(result, Is.Null);
_mockDb.Verify(
db => db.StringSetAsync(It.IsAny<RedisKey>(), It.IsAny<RedisValue>(), It.IsAny<TimeSpan?>(), It.IsAny<bool>(), It.IsAny<When>(), It.IsAny<CommandFlags>()),
Times.Never);
}
#endregion
#region TC-43
/// <summary>
/// Verifies that <c>GetObjectAsync</c> successfully retrieves and deserializes the stored object while skipping the
/// key-expiration refresh when <c>updateExpiration</c> is set to <c>false</c>, ensuring <c>KeyExpire</c> is never invoked.
/// </summary>
[Test]
public async Task GetObjectAsync_ReturnsObject_AndSkipsKeyExpire_WhenUpdateExpirationFalse()
{
var expected = new TestModel("data");
_mockDb.Setup(db => db.StringGetAsync(It.IsAny<RedisKey>(), It.IsAny<CommandFlags>()))
.ReturnsAsync((RedisValue)JsonConvert.SerializeObject(expected));
var sut = CreateSut();
var result = await sut.GetObjectAsync<TestModel>("patients:latestObs:abc", updateExpiration: false);
Assert.That(result?.Name, Is.EqualTo("data"));
_mockDb.Verify(
db => db.KeyExpire(It.IsAny<RedisKey>(), It.IsAny<TimeSpan?>(), It.IsAny<ExpireWhen>(), It.IsAny<CommandFlags>()),
Times.Never);
}
public async Task GetObjectAsync_ReturnsObject_AndSkipsKeyExpire_WhenUpdateExpirationFalse()
{
var expected = new TestModel("data");
_mockDb.Setup(db => db.StringGetAsync(It.IsAny<RedisKey>(), It.IsAny<CommandFlags>()))
.ReturnsAsync((RedisValue)JsonConvert.SerializeObject(expected));
var sut = CreateSut();
var result = await sut.GetObjectAsync<TestModel>("patients:latestObs:abc", updateExpiration: false);
Assert.That(result?.Name, Is.EqualTo("data"));
_mockDb.Verify(
db => db.KeyExpire(It.IsAny<RedisKey>(), It.IsAny<TimeSpan?>(), It.IsAny<ExpireWhen>(), It.IsAny<CommandFlags>()),
Times.Never);
}
#endregion
#region TC-44
/// <summary>
/// Verifies that <c>SetObjectAsync</c> serializes the supplied object to JSON and persists it in Redis
/// with the entity-specific TTL (600 seconds) configured for the "Patients" entity in <see cref="CacheSettings"/>.
/// </summary>
[Test]
public async Task SetObjectAsync_SerializesToJson_AndPersistsWithEntityTtl()
{
var settings = new CacheSettings
public async Task SetObjectAsync_SerializesToJson_AndPersistsWithEntityTtl()
{
Redis = new RedisSettings { Ttl = new TtlSettings { PatientsSeconds = 600 } }
};
SetupStringSetAsync();
var sut = CreateSut(settings);
var obj = new TestModel("save-me");
var expectedJson = JsonConvert.SerializeObject(obj);
await sut.SetObjectAsync("patients:latestObs:abc", obj, null, true);
_mockDb.Verify(
db => db.StringSetAsync(
It.IsAny<RedisKey>(),
It.IsAny<RedisValue>(),
It.Is<TimeSpan?>(t => t == TimeSpan.FromSeconds(600)),
It.IsAny<bool>(),
It.IsAny<When>(),
It.IsAny<CommandFlags>()),
Times.Once);
It.Is<RedisValue>(v => v.Equals(expectedJson));
}
var settings = new CacheSettings
{
Redis = new RedisSettings { Ttl = new TtlSettings { PatientsSeconds = 600 } }
};
SetupStringSetAsync();
var sut = CreateSut(settings);
var obj = new TestModel("save-me");
var expectedJson = JsonConvert.SerializeObject(obj);
await sut.SetObjectAsync("patients:latestObs:abc", obj, null, true);
_mockDb.Verify(
db => db.StringSetAsync(
It.IsAny<RedisKey>(),
It.IsAny<RedisValue>(),
It.Is<TimeSpan?>(t => t == TimeSpan.FromSeconds(600)),
It.IsAny<bool>(),
It.IsAny<When>(),
It.IsAny<CommandFlags>()),
Times.Once);
It.Is<RedisValue>(v => v.Equals(expectedJson));
}
#endregion
}
+174 -157
View File
@@ -16,26 +16,29 @@ namespace adas_core.Test.Services;
[TestFixture]
public class RelayServiceTest
{
/// <summary>
/// Sets up the test environment by initializing mocks and dependencies required for unit testing the <see cref="RelayService"/>, including logger, HTTP client factory, HTTP message handler, relay settings, point-of-care service, and relay repository.
/// </summary>
[SetUp]
public void Setup()
{
_logger = new Mock<ILogger<RelayService>>();
_httpClientFactoryMock = new Mock<IHttpClientFactory>();
_httpMessageHandlerMock = new Mock<HttpMessageHandler>();
_relaySettings.RecordingOrApiUrl = "http://localhost:8080";
_relaySettings.Cache = true;
_optionsRelaySettings = Options.Create(_relaySettings);
_pocServiceMock = new Mock<IPointOfCareService>();
_relayRepositoryMock = new Mock<IRelayRepository>();
_relayService = new RelayService(
//optionsApiSettings,
_logger.Object,
_httpClientFactoryMock.Object,
_optionsRelaySettings,
_pocServiceMock.Object,
_relayRepositoryMock.Object);
}
public void Setup()
{
_logger = new Mock<ILogger<RelayService>>();
_httpClientFactoryMock = new Mock<IHttpClientFactory>();
_httpMessageHandlerMock = new Mock<HttpMessageHandler>();
_relaySettings.RecordingOrApiUrl = "http://localhost:8080";
_relaySettings.Cache = true;
_optionsRelaySettings = Options.Create(_relaySettings);
_pocServiceMock = new Mock<IPointOfCareService>();
_relayRepositoryMock = new Mock<IRelayRepository>();
_relayService = new RelayService(
//optionsApiSettings,
_logger.Object,
_httpClientFactoryMock.Object,
_optionsRelaySettings,
_pocServiceMock.Object,
_relayRepositoryMock.Object);
}
private RelayService _relayService = null!;
private Mock<IHttpClientFactory> _httpClientFactoryMock = null!;
@@ -52,117 +55,126 @@ public class RelayServiceTest
private Mock<ILogger<RelayService>> _logger = null!;
/// <summary>
/// Verifies that the relay service successfully powers off a relay by issuing an HTTP request through the configured HTTP client factory.
/// </summary>
[Test]
public async Task PowerOffAsync()
{
var relay = new Relay
public async Task PowerOffAsync()
{
Driver = "KMTronicV2",
Ip = "10.133.10.169",
Port = 80,
RelayNumber = 1,
RelayName = "Relay1",
Total = 8,
Username = "adas",
Password = "!HULPM22"
};
var httpResponseMessage = new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent("Test content")
};
_httpMessageHandlerMock.Protected()
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(),
ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(httpResponseMessage);
var client = new HttpClient(_httpMessageHandlerMock.Object);
_httpClientFactoryMock.Setup(c => c.CreateClient(It.IsAny<string>())).Returns(client);
await _relayService.PowerOff(relay);
}
var relay = new Relay
{
Driver = "KMTronicV2",
Ip = "10.133.10.169",
Port = 80,
RelayNumber = 1,
RelayName = "Relay1",
Total = 8,
Username = "adas",
Password = "!HULPM22"
};
var httpResponseMessage = new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent("Test content")
};
_httpMessageHandlerMock.Protected()
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(),
ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(httpResponseMessage);
var client = new HttpClient(_httpMessageHandlerMock.Object);
_httpClientFactoryMock.Setup(c => c.CreateClient(It.IsAny<string>())).Returns(client);
await _relayService.PowerOff(relay);
}
/// <summary>
/// Verifies that <see cref="RelayService.PowerOn"/> correctly sends a power-on request to the configured relay device using the HTTP client.
/// </summary>
[Test]
public async Task PowerOnAsync()
{
var relay = new Relay
public async Task PowerOnAsync()
{
Driver = "KMTronicV2",
Ip = "10.133.10.169",
Port = 80,
RelayNumber = 1,
RelayName = "Relay1",
Total = 8,
Username = "adas",
Password = "!HULPM22"
};
var httpResponseMessage = new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent("Test content")
};
_httpMessageHandlerMock.Protected()
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(),
ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(httpResponseMessage);
var client = new HttpClient(_httpMessageHandlerMock.Object);
_httpClientFactoryMock.Setup(c => c.CreateClient(It.IsAny<string>())).Returns(client);
await _relayService.PowerOn(relay);
}
var relay = new Relay
{
Driver = "KMTronicV2",
Ip = "10.133.10.169",
Port = 80,
RelayNumber = 1,
RelayName = "Relay1",
Total = 8,
Username = "adas",
Password = "!HULPM22"
};
var httpResponseMessage = new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent("Test content")
};
_httpMessageHandlerMock.Protected()
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(),
ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(httpResponseMessage);
var client = new HttpClient(_httpMessageHandlerMock.Object);
_httpClientFactoryMock.Setup(c => c.CreateClient(It.IsAny<string>())).Returns(client);
await _relayService.PowerOn(relay);
}
/// <summary>
/// Verifies that <c>CheckRelayStatus</c> returns <see cref="RelayEnum.Status.On"/> when the mocked HTTP response indicates the relay is on.
/// </summary>
[Test]
public async Task CheckRelayStatus_Return_On()
{
var relay = new Relay
public async Task CheckRelayStatus_Return_On()
{
Driver = "KMTronicV2",
Ip = "10.133.10.169",
Port = 80,
RelayNumber = 1,
RelayName = "Relay1",
Total = 8,
Username = "adas",
Password = "!HULPM22"
};
var httpResponseMessage = new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent("\"On")
};
_httpMessageHandlerMock.Protected()
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(),
ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(httpResponseMessage);
//httpMessageHandlerMock.Protected()
// .Setup<Task<string>>("ReadAsStringAsync", ItExpr.IsAny<string>(), ItExpr.IsAny<CancellationToken>())
// .ReturnsAsync("\\On");
var client = new HttpClient(_httpMessageHandlerMock.Object);
_httpClientFactoryMock.Setup(c => c.CreateClient(It.IsAny<string>())).Returns(client);
await _relayService.PowerOn(relay);
var result = await _relayService.CheckRelayStatus(relay);
//Assert.That(result, Is.Not.Null);
Assert.That(result, Is.EqualTo(RelayEnum.Status.On));
}
var relay = new Relay
{
Driver = "KMTronicV2",
Ip = "10.133.10.169",
Port = 80,
RelayNumber = 1,
RelayName = "Relay1",
Total = 8,
Username = "adas",
Password = "!HULPM22"
};
var httpResponseMessage = new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent("\"On")
};
_httpMessageHandlerMock.Protected()
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(),
ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(httpResponseMessage);
//httpMessageHandlerMock.Protected()
// .Setup<Task<string>>("ReadAsStringAsync", ItExpr.IsAny<string>(), ItExpr.IsAny<CancellationToken>())
// .ReturnsAsync("\\On");
var client = new HttpClient(_httpMessageHandlerMock.Object);
_httpClientFactoryMock.Setup(c => c.CreateClient(It.IsAny<string>())).Returns(client);
await _relayService.PowerOn(relay);
var result = await _relayService.CheckRelayStatus(relay);
//Assert.That(result, Is.Not.Null);
Assert.That(result, Is.EqualTo(RelayEnum.Status.On));
}
[Test]
public async Task CheckRelayStatus_Return_Off()
@@ -201,41 +213,46 @@ public class RelayServiceTest
Assert.That(result, Is.EqualTo(RelayEnum.Status.Off));
}
/// <summary>
/// Verifies that <c>CheckRelayStatus</c> returns <see cref="RelayEnum.Status.Unknown"/> when the relay
/// driver response does not match a recognized on/off state (e.g., the raw payload "\\Off" is not
/// mapped to a known status).
/// </summary>
[Test]
public async Task CheckRelayStatus_Return_Unknown()
{
var relay = new Relay
public async Task CheckRelayStatus_Return_Unknown()
{
Driver = "KMTronicV2",
Ip = "10.133.10.169",
Port = 80,
RelayNumber = 1,
RelayName = "Relay1",
Total = 8,
Username = "adas",
Password = "!HULPM22"
};
var httpResponseMessage = new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent("\\Off")
};
_httpMessageHandlerMock.Protected()
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(),
ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(httpResponseMessage);
var client = new HttpClient(_httpMessageHandlerMock.Object);
_httpClientFactoryMock.Setup(c => c.CreateClient(It.IsAny<string>())).Returns(client);
var result = await _relayService.CheckRelayStatus(relay);
//Assert.That(result, Is.Not.Null);
Assert.That(result, Is.EqualTo(RelayEnum.Status.Unknown));
}
var relay = new Relay
{
Driver = "KMTronicV2",
Ip = "10.133.10.169",
Port = 80,
RelayNumber = 1,
RelayName = "Relay1",
Total = 8,
Username = "adas",
Password = "!HULPM22"
};
var httpResponseMessage = new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent("\\Off")
};
_httpMessageHandlerMock.Protected()
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(),
ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(httpResponseMessage);
var client = new HttpClient(_httpMessageHandlerMock.Object);
_httpClientFactoryMock.Setup(c => c.CreateClient(It.IsAny<string>())).Returns(client);
var result = await _relayService.CheckRelayStatus(relay);
//Assert.That(result, Is.Not.Null);
Assert.That(result, Is.EqualTo(RelayEnum.Status.Unknown));
}
}
+275 -249
View File
@@ -323,268 +323,294 @@ public class SchedulerServiceTest
private static readonly ObjectId PatientId = ObjectId.GenerateNewId();
private static readonly ObjectId PatientId2 = ObjectId.GenerateNewId();
/// <summary>
/// Verifies that the <see cref="CheckInactivePatientsJob"/> is executed when its Quartz.NET trigger fires,
/// ensuring the scheduled job invokes the patient discharge process for inactive patients at the configured interval.
/// </summary>
[Test]
public void CheckInactivePatientsJob_ShouldExecuteOnTrigger()
{
var checkInactivePatientSchedulerIntervalHours = 12;
// Arrange
var checkInactivePatientsJob = JobBuilder.Create<CheckInactivePatientsJob>()
.UsingJobData(_jobDataMap)
.Build();
// Crear un disparador personalizado que incremente el contador
var checkInactivePatientsTrigger = TriggerBuilder.Create()
.StartNow()
.WithSimpleSchedule(x => x
.WithIntervalInHours(checkInactivePatientSchedulerIntervalHours)
.RepeatForever())
.Build();
_patientServiceMock?.Setup(p => p.DischargeInactivePatients(_sinceDate, _sinceDischargeTimeToArchive));
// Asociar el trabajo y el desencadenador en el motor de Quartz.NET
_scheduler?.ScheduleJob(checkInactivePatientsJob, checkInactivePatientsTrigger).Wait();
// Act
// Esperar un tiempo suficiente para que el trabajo se ejecute varias veces
Thread.Sleep(TimeSpan.FromSeconds(1));
_patientServiceMock?.Verify(p => p.DischargeInactivePatients(It.IsAny<DateTime>(), It.IsAny<int>()),
Times.AtLeastOnce());
}
[Test]
public void CheckActiveTreatmentsJob_ShouldExecuteOnTrigger()
{
var patient = new Patient
public void CheckInactivePatientsJob_ShouldExecuteOnTrigger()
{
PatientId = PatientId.ToString(),
UnitString = "NEONATAL",
Bed = "CINA02",
PatientNumber = "123456",
Person = new Person
{
FirstName = "Jose",
LastName = "Luis",
BirthDate = new DateTime(),
Gender = PatientEnum.Gender.Male
}
};
var checkInactivePatientSchedulerIntervalHours = 12;
// Arrange
var checkInactivePatientsJob = JobBuilder.Create<CheckInactivePatientsJob>()
.UsingJobData(_jobDataMap)
.Build();
// Crear un disparador personalizado que incremente el contador
var checkInactivePatientsTrigger = TriggerBuilder.Create()
.StartNow()
.WithSimpleSchedule(x => x
.WithIntervalInHours(checkInactivePatientSchedulerIntervalHours)
.RepeatForever())
.Build();
_patientServiceMock?.Setup(p => p.DischargeInactivePatients(_sinceDate, _sinceDischargeTimeToArchive));
// Asociar el trabajo y el desencadenador en el motor de Quartz.NET
_scheduler?.ScheduleJob(checkInactivePatientsJob, checkInactivePatientsTrigger).Wait();
// Act
// Esperar un tiempo suficiente para que el trabajo se ejecute varias veces
Thread.Sleep(TimeSpan.FromSeconds(1));
_patientServiceMock?.Verify(p => p.DischargeInactivePatients(It.IsAny<DateTime>(), It.IsAny<int>()),
Times.AtLeastOnce());
}
const int checkActiveTreatmentsSchedulerIntervalMinutes = 10;
var treatmentsJob = JobBuilder.Create<CheckActiveTreatmentsJob>()
.Build();
// Trigger the job to run now, and then repeat every 10 seconds
var treatmentsTrigger = TriggerBuilder.Create()
.StartNow()
.WithSimpleSchedule(x => x
.WithIntervalInMinutes(checkActiveTreatmentsSchedulerIntervalMinutes)
.RepeatForever())
.Build();
_patientServiceMock?.Setup(p => p.FindAll(It.IsAny<bool>())).ReturnsAsync([patient]);
// Create a mock of the singleton class subscribers
var mockSingleton = new Mock<ICalculatedObservationsService>();
// Set up the mock object to return a specific value when a method is called
var patientTreatmentList = new List<PatientTreatment?>
/// <summary>
/// Verifies that the <see cref="CheckActiveTreatmentsJob"/> is executed by the Quartz scheduler
/// when triggered, and that the medicine service is invoked exactly once to retrieve medicines
/// for the active treatments of the mocked patients.
/// </summary>
[Test]
public void CheckActiveTreatmentsJob_ShouldExecuteOnTrigger()
{
new()
var patient = new Patient
{
Id = ObjectId.GenerateNewId(),
PatientId = PatientId,
OrderControl = OrderControlType.Nw,
PlacerOrder = new Entity { EntityIdentifier = "3690", NamespaceId = "CareVue" },
FillerOrder = new Entity { EntityIdentifier = "3690", NamespaceId = "CareVue" },
OrderStatus = "A",
OrderTime = Now,
Notes = [],
Routes = []
}
};
mockSingleton.Setup(x => x.GetActiveTreatmentsByPatient(PatientId)).ReturnsAsync(patientTreatmentList);
_medicineServiceMock?.Setup(m => m.GetMedicinesOfTreatments(It.IsAny<List<PatientTreatment>>()))
.ReturnsAsync(new List<Medicine>());
// Asociar el trabajo y el desencadenador en el motor de Quartz.NET
_scheduler?.ScheduleJob(treatmentsJob, treatmentsTrigger).Wait();
// Act
// Esperar un tiempo suficiente para que el trabajo se ejecute varias veces
Thread.Sleep(TimeSpan.FromSeconds(10));
_medicineServiceMock?.Verify(x => x.GetMedicinesOfTreatments(It.IsAny<IEnumerable<PatientTreatment>>()),
Times.Once());
}
PatientId = PatientId.ToString(),
UnitString = "NEONATAL",
Bed = "CINA02",
PatientNumber = "123456",
Person = new Person
{
FirstName = "Jose",
LastName = "Luis",
BirthDate = new DateTime(),
Gender = PatientEnum.Gender.Male
}
};
const int checkActiveTreatmentsSchedulerIntervalMinutes = 10;
var treatmentsJob = JobBuilder.Create<CheckActiveTreatmentsJob>()
.Build();
// Trigger the job to run now, and then repeat every 10 seconds
var treatmentsTrigger = TriggerBuilder.Create()
.StartNow()
.WithSimpleSchedule(x => x
.WithIntervalInMinutes(checkActiveTreatmentsSchedulerIntervalMinutes)
.RepeatForever())
.Build();
_patientServiceMock?.Setup(p => p.FindAll(It.IsAny<bool>())).ReturnsAsync([patient]);
// Create a mock of the singleton class subscribers
var mockSingleton = new Mock<ICalculatedObservationsService>();
// Set up the mock object to return a specific value when a method is called
var patientTreatmentList = new List<PatientTreatment?>
{
new()
{
Id = ObjectId.GenerateNewId(),
PatientId = PatientId,
OrderControl = OrderControlType.Nw,
PlacerOrder = new Entity { EntityIdentifier = "3690", NamespaceId = "CareVue" },
FillerOrder = new Entity { EntityIdentifier = "3690", NamespaceId = "CareVue" },
OrderStatus = "A",
OrderTime = Now,
Notes = [],
Routes = []
}
};
mockSingleton.Setup(x => x.GetActiveTreatmentsByPatient(PatientId)).ReturnsAsync(patientTreatmentList);
_medicineServiceMock?.Setup(m => m.GetMedicinesOfTreatments(It.IsAny<List<PatientTreatment>>()))
.ReturnsAsync(new List<Medicine>());
// Asociar el trabajo y el desencadenador en el motor de Quartz.NET
_scheduler?.ScheduleJob(treatmentsJob, treatmentsTrigger).Wait();
// Act
// Esperar un tiempo suficiente para que el trabajo se ejecute varias veces
Thread.Sleep(TimeSpan.FromSeconds(10));
_medicineServiceMock?.Verify(x => x.GetMedicinesOfTreatments(It.IsAny<IEnumerable<PatientTreatment>>()),
Times.Once());
}
/// <summary>
/// Verifies that the <see cref="CheckExpiredObservationsJob"/> is executed at least once by the Quartz.NET scheduler when triggered with a recurring schedule.
/// </summary>
[Test]
public void CheckExpiredObservationsJob_ShouldExecuteOnTrigger()
{
var checkExpiredObservationsIntervalMinutes = 3;
// Arrange
var checkExpiredObservationsJob = JobBuilder.Create<CheckExpiredObservationsJob>()
.UsingJobData(_jobDataMap)
.Build();
// Crear un disparador personalizado que incremente el contador
var checkExpiredObservationsTrigger = TriggerBuilder.Create()
.StartNow()
.WithSimpleSchedule(x => x
.WithIntervalInHours(checkExpiredObservationsIntervalMinutes)
.RepeatForever())
.Build();
// Asociar el trabajo y el desencadenador en el motor de Quartz.NET
_scheduler?.ScheduleJob(checkExpiredObservationsJob, checkExpiredObservationsTrigger).Wait();
// Act
// Esperar un tiempo suficiente para que el trabajo se ejecute varias veces
Thread.Sleep(TimeSpan.FromSeconds(1));
_observationServiceMock?.Verify(p => p.ExpireObservationsAndRecalculateAsync(), Times.AtLeastOnce());
}
[Test]
public void CheckExpiredAlertsJob_ShouldExecuteOnTrigger()
{
var checkExpiredAlertsIntervalMinutes = 3;
// Arrange
var checkExpiredAlertsJob = JobBuilder.Create<CheckExpiredAlertsJob>()
.UsingJobData(_jobDataMap)
.Build();
// Crear un disparador personalizado que incremente el contador
var checkExpiredAlertsTrigger = TriggerBuilder.Create()
.StartNow()
.WithSimpleSchedule(x => x
.WithIntervalInHours(checkExpiredAlertsIntervalMinutes)
.RepeatForever())
.Build();
// Asociar el trabajo y el desencadenador en el motor de Quartz.NET
_scheduler?.ScheduleJob(checkExpiredAlertsJob, checkExpiredAlertsTrigger).Wait();
// Act
// Esperar un tiempo suficiente para que el trabajo se ejecute varias veces
Thread.Sleep(TimeSpan.FromSeconds(1));
_observationServiceMock?.Verify(p => p.ExpireAlertsAndPowerOffAsync(), Times.AtLeastOnce());
}
[Test]
public void GetProvidersObservationsJob_ShouldExecuteOnTrigger_Result_GetType_null()
{
var getProviderObservationsIntervalMinutes = 5;
var patient = new Patient
public void CheckExpiredObservationsJob_ShouldExecuteOnTrigger()
{
PatientId = PatientId.ToString(),
UnitString = "NEONATAL",
Bed = "CINA02",
PatientNumber = "123456",
Person = new Person
var checkExpiredObservationsIntervalMinutes = 3;
// Arrange
var checkExpiredObservationsJob = JobBuilder.Create<CheckExpiredObservationsJob>()
.UsingJobData(_jobDataMap)
.Build();
// Crear un disparador personalizado que incremente el contador
var checkExpiredObservationsTrigger = TriggerBuilder.Create()
.StartNow()
.WithSimpleSchedule(x => x
.WithIntervalInHours(checkExpiredObservationsIntervalMinutes)
.RepeatForever())
.Build();
// Asociar el trabajo y el desencadenador en el motor de Quartz.NET
_scheduler?.ScheduleJob(checkExpiredObservationsJob, checkExpiredObservationsTrigger).Wait();
// Act
// Esperar un tiempo suficiente para que el trabajo se ejecute varias veces
Thread.Sleep(TimeSpan.FromSeconds(1));
_observationServiceMock?.Verify(p => p.ExpireObservationsAndRecalculateAsync(), Times.AtLeastOnce());
}
/// <summary>
/// Verifies that the <see cref="CheckExpiredAlertsJob"/> is executed when triggered by the Quartz.NET scheduler, ensuring that the <c>ExpireAlertsAndPowerOffAsync</c> method on the observation service is invoked at least once.
/// </summary>
[Test]
public void CheckExpiredAlertsJob_ShouldExecuteOnTrigger()
{
var checkExpiredAlertsIntervalMinutes = 3;
// Arrange
var checkExpiredAlertsJob = JobBuilder.Create<CheckExpiredAlertsJob>()
.UsingJobData(_jobDataMap)
.Build();
// Crear un disparador personalizado que incremente el contador
var checkExpiredAlertsTrigger = TriggerBuilder.Create()
.StartNow()
.WithSimpleSchedule(x => x
.WithIntervalInHours(checkExpiredAlertsIntervalMinutes)
.RepeatForever())
.Build();
// Asociar el trabajo y el desencadenador en el motor de Quartz.NET
_scheduler?.ScheduleJob(checkExpiredAlertsJob, checkExpiredAlertsTrigger).Wait();
// Act
// Esperar un tiempo suficiente para que el trabajo se ejecute varias veces
Thread.Sleep(TimeSpan.FromSeconds(1));
_observationServiceMock?.Verify(p => p.ExpireAlertsAndPowerOffAsync(), Times.AtLeastOnce());
}
/// <summary>
/// Verifies that the <see cref="GetProvidersObservationsJob"/> Quartz.NET job, when triggered, never invokes
/// <c>InsertObservation</c> with a <see cref="PatientObservation"/> whose <c>Name</c> is not "NEWS", ensuring
/// that only NEWS observations are considered for persistence during scheduled execution.
/// </summary>
[Test]
public void GetProvidersObservationsJob_ShouldExecuteOnTrigger_Result_GetType_null()
{
var getProviderObservationsIntervalMinutes = 5;
var patient = new Patient
{
FirstName = "Jose",
LastName = "Luis",
BirthDate = new DateTime(),
Gender = PatientEnum.Gender.Male
}
};
// Arrange
var getProvidersObservationsJob = JobBuilder.Create<GetProvidersObservationsJob>()
.UsingJobData(_jobDataMap)
.Build();
// Crear un disparador personalizado que incremente el contador
var getProvidersObservationsTrigger = TriggerBuilder.Create()
.StartNow()
.WithSimpleSchedule(x => x
.WithIntervalInHours(getProviderObservationsIntervalMinutes)
.RepeatForever())
.Build();
_patientServiceMock?.Setup(p => p.FindAll(It.IsAny<bool>())).ReturnsAsync([patient]);
// Asociar el trabajo y el desencadenador en el motor de Quartz.NET
_scheduler?.ScheduleJob(getProvidersObservationsJob, getProvidersObservationsTrigger).Wait();
// Act
// Esperar un tiempo suficiente para que el trabajo se ejecute varias veces
Thread.Sleep(TimeSpan.FromSeconds(1));
//observationServiceMock.Verify(p => p.InsertObservation(It.IsAny<PatientObservation>(),true,true), Times.AtLeastOnce());
_observationServiceMock?.Verify(p => p.InsertObservation(
It.Is<PatientObservation>(obs => obs.Name != "NEWS"), true, true), Times.Never());
}
PatientId = PatientId.ToString(),
UnitString = "NEONATAL",
Bed = "CINA02",
PatientNumber = "123456",
Person = new Person
{
FirstName = "Jose",
LastName = "Luis",
BirthDate = new DateTime(),
Gender = PatientEnum.Gender.Male
}
};
// Arrange
var getProvidersObservationsJob = JobBuilder.Create<GetProvidersObservationsJob>()
.UsingJobData(_jobDataMap)
.Build();
// Crear un disparador personalizado que incremente el contador
var getProvidersObservationsTrigger = TriggerBuilder.Create()
.StartNow()
.WithSimpleSchedule(x => x
.WithIntervalInHours(getProviderObservationsIntervalMinutes)
.RepeatForever())
.Build();
_patientServiceMock?.Setup(p => p.FindAll(It.IsAny<bool>())).ReturnsAsync([patient]);
// Asociar el trabajo y el desencadenador en el motor de Quartz.NET
_scheduler?.ScheduleJob(getProvidersObservationsJob, getProvidersObservationsTrigger).Wait();
// Act
// Esperar un tiempo suficiente para que el trabajo se ejecute varias veces
Thread.Sleep(TimeSpan.FromSeconds(1));
//observationServiceMock.Verify(p => p.InsertObservation(It.IsAny<PatientObservation>(),true,true), Times.AtLeastOnce());
_observationServiceMock?.Verify(p => p.InsertObservation(
It.Is<PatientObservation>(obs => obs.Name != "NEWS"), true, true), Times.Never());
}
/// <summary>
/// Verifies that the <see cref="CalculateNewsJob"/> is executed when triggered by the Quartz.NET scheduler, confirming that the associated <c>InsertObservation</c> call on the observation service is invoked at least once within the allowed execution window.
/// </summary>
[Test]
public void CheckCalculateNewsJob_ShouldExecuteOnTrigger()
{
var checkExpiredAlertsIntervalMinutes = 3;
// Arrange
var calculateNewsJob = JobBuilder.Create<CalculateNewsJob>()
.UsingJobData(_jobDataMap)
.Build();
// Crear un disparador personalizado que incremente el contador
var checkExpiredAlertsTrigger = TriggerBuilder.Create()
.StartNow()
.WithSimpleSchedule(x => x
.WithIntervalInHours(checkExpiredAlertsIntervalMinutes)
.RepeatForever())
.Build();
// Asociar el trabajo y el desencadenador en el motor de Quartz.NET
_scheduler?.ScheduleJob(calculateNewsJob, checkExpiredAlertsTrigger).Wait();
// Act
// Esperar un tiempo suficiente para que el trabajo se ejecute varias veces
Thread.Sleep(TimeSpan.FromSeconds(6));
_observationServiceMock?.Verify(p => p.InsertObservation(It.IsAny<PatientObservation>(), true, true),
Times.AtLeastOnce());
}
public void CheckCalculateNewsJob_ShouldExecuteOnTrigger()
{
var checkExpiredAlertsIntervalMinutes = 3;
// Arrange
var calculateNewsJob = JobBuilder.Create<CalculateNewsJob>()
.UsingJobData(_jobDataMap)
.Build();
// Crear un disparador personalizado que incremente el contador
var checkExpiredAlertsTrigger = TriggerBuilder.Create()
.StartNow()
.WithSimpleSchedule(x => x
.WithIntervalInHours(checkExpiredAlertsIntervalMinutes)
.RepeatForever())
.Build();
// Asociar el trabajo y el desencadenador en el motor de Quartz.NET
_scheduler?.ScheduleJob(calculateNewsJob, checkExpiredAlertsTrigger).Wait();
// Act
// Esperar un tiempo suficiente para que el trabajo se ejecute varias veces
Thread.Sleep(TimeSpan.FromSeconds(6));
_observationServiceMock?.Verify(p => p.InsertObservation(It.IsAny<PatientObservation>(), true, true),
Times.AtLeastOnce());
}
/// <summary>
/// Verifies that <see cref="CalculateNewsJob.Execute"/> correctly inserts NEWS observations with the expected calculated values (1 and 4) for multiple patients in a single execution.
/// </summary>
[Test]
public async Task CalculateNewsJob_Insert_ObsFull_MultiplePatient_With_Correct_Value()
{
var job = new CalculateNewsJob();
// Act
await job.Execute(Mock.Of<IJobExecutionContext>());
// Assert
_observationServiceMock?.Verify(
service => service.InsertObservation(
It.Is<PatientObservation>(obs =>
obs.Name == "NEWS" && (int)obs.Value == 1 && obs.PatientId == PatientId),
true, true),
Times.Once);
_observationServiceMock?.Verify(
service => service.InsertObservation(
It.Is<PatientObservation>(obs =>
obs.Name == "NEWS" && (int)obs.Value == 4 && obs.PatientId == PatientId2),
true, true),
Times.Once);
}
public async Task CalculateNewsJob_Insert_ObsFull_MultiplePatient_With_Correct_Value()
{
var job = new CalculateNewsJob();
// Act
await job.Execute(Mock.Of<IJobExecutionContext>());
// Assert
_observationServiceMock?.Verify(
service => service.InsertObservation(
It.Is<PatientObservation>(obs =>
obs.Name == "NEWS" && (int)obs.Value == 1 && obs.PatientId == PatientId),
true, true),
Times.Once);
_observationServiceMock?.Verify(
service => service.InsertObservation(
It.Is<PatientObservation>(obs =>
obs.Name == "NEWS" && (int)obs.Value == 4 && obs.PatientId == PatientId2),
true, true),
Times.Once);
}
//Se deshabilita el mensaje de warning porque lo detecta como no usado y sugiere suprimirlo siendo necesario
}
+99 -81
View File
@@ -10,26 +10,31 @@ namespace adas_core.Test.Services;
[TestFixture]
public class SendAlertServiceTest
{
/// <summary>
/// Initializes test dependencies for <see cref="SendAlertService"/> unit tests, including
/// RabbitMQ options, a mocked logger, an instance of the service under test, and the set
/// of Windows platform identifiers used to validate platform-specific behavior.
/// </summary>
[SetUp]
public void Setup()
{
_optionsRabbitMqSettings = Options.Create(_rabbitMqSettings);
_logger = new Mock<ILogger<SendAlertService>>();
_sendAlertService = new SendAlertService(
_optionsRabbitMqSettings,
_logger.Object
);
_windowsPlatforms =
[
PlatformID.Win32NT,
PlatformID.Win32S,
PlatformID.Win32Windows,
PlatformID.WinCE
];
}
public void Setup()
{
_optionsRabbitMqSettings = Options.Create(_rabbitMqSettings);
_logger = new Mock<ILogger<SendAlertService>>();
_sendAlertService = new SendAlertService(
_optionsRabbitMqSettings,
_logger.Object
);
_windowsPlatforms =
[
PlatformID.Win32NT,
PlatformID.Win32S,
PlatformID.Win32Windows,
PlatformID.WinCE
];
}
private SendAlertService _sendAlertService = null!;
@@ -40,76 +45,89 @@ public class SendAlertServiceTest
private PlatformID[] _windowsPlatforms = null!;
/// <summary>
/// Verifies that <see cref="_sendAlertService"/>.<c>GetConsumedCpu</c> returns a valid performance CPU
/// reading (non-null with a positive <c>ValueTotal</c>) when executed on a Windows platform. When the
/// host operating system is not Windows, the test is skipped with an ignore notice.
/// </summary>
[Ignore("Integration test to pull request")]
[Test]
public void GetConsumedCpu_Return_PerformanceCpu_data()
{
if (_windowsPlatforms.Contains(Environment.OSVersion.Platform))
[Test]
public void GetConsumedCpu_Return_PerformanceCpu_data()
{
var result = _sendAlertService.GetConsumedCpu();
//result = await sendAlertService.GetConsumedCpu();
Assert.That(result, Is.Not.Null);
Assert.That(result.ValueTotal, Is.GreaterThan(0));
}
else
{
Assert.Ignore("This test can only be run on Windows.");
}
}
[Ignore("Integration test to pull request")]
[Test]
public void GetConsumedRAM_Return_PerformanceRAM_data()
{
if (_windowsPlatforms.Contains(Environment.OSVersion.Platform))
{
var result = _sendAlertService.GetConsumedRam();
//result = await sendAlertService.GetConsumedRAM();
Assert.That(result, Is.Not.Null);
using (Assert.EnterMultipleScope())
if (_windowsPlatforms.Contains(Environment.OSVersion.Platform))
{
Assert.That(result.PercentageConsumed, Is.GreaterThan(0));
Assert.That(result.ValueConsumed, Is.GreaterThan(0));
var result = _sendAlertService.GetConsumedCpu();
//result = await sendAlertService.GetConsumedCpu();
Assert.That(result, Is.Not.Null);
Assert.That(result.ValueTotal, Is.GreaterThan(0));
}
}
else
{
Assert.Ignore("This test can only be run on Windows.");
}
}
[Ignore("Integration test to pull request")]
[Test]
public void GetConsumedStorage_Return_PerformanceStorage_data()
{
if (_windowsPlatforms.Contains(Environment.OSVersion.Platform))
{
List<Performance> performanceList = [];
foreach (var drive in DriveInfo.GetDrives())
if (drive.IsReady)
{
var performance = _sendAlertService.GetConsumedStorage(drive);
performanceList.Add(performance);
}
Assert.That(performanceList, Is.Not.Null);
using (Assert.EnterMultipleScope())
else
{
Assert.That(performanceList, Is.Not.Empty);
Assert.That(performanceList[0].PercentageConsumed, Is.GreaterThan(0));
Assert.That(performanceList[0].ValueConsumed, Is.GreaterThan(0));
Assert.That(performanceList[0].ValueTotal, Is.GreaterThan(0));
Assert.Ignore("This test can only be run on Windows.");
}
}
else
/// <summary>
/// Verifies that <c>GetConsumedRam</c> returns a non-null performance RAM data object with positive values
/// for <c>PercentageConsumed</c>, <c>ValueConsumed</c>, and <c>ValueTotal</c>. The test only executes on
/// Windows platforms and is ignored on other operating systems.
/// </summary>
[Ignore("Integration test to pull request")]
[Test]
public void GetConsumedRAM_Return_PerformanceRAM_data()
{
Assert.Ignore("This test can only be run on Windows.");
if (_windowsPlatforms.Contains(Environment.OSVersion.Platform))
{
var result = _sendAlertService.GetConsumedRam();
//result = await sendAlertService.GetConsumedRAM();
Assert.That(result, Is.Not.Null);
using (Assert.EnterMultipleScope())
{
Assert.That(result.PercentageConsumed, Is.GreaterThan(0));
Assert.That(result.ValueConsumed, Is.GreaterThan(0));
Assert.That(result.ValueTotal, Is.GreaterThan(0));
}
}
else
{
Assert.Ignore("This test can only be run on Windows.");
}
}
/// <summary>
/// Integration test that verifies the GetConsumedStorage method returns valid Performance data with positive percentage and value metrics for all ready drives on Windows platforms. The test is ignored when executed on a non-Windows platform.
/// </summary>
[Ignore("Integration test to pull request")]
[Test]
public void GetConsumedStorage_Return_PerformanceStorage_data()
{
if (_windowsPlatforms.Contains(Environment.OSVersion.Platform))
{
List<Performance> performanceList = [];
foreach (var drive in DriveInfo.GetDrives())
if (drive.IsReady)
{
var performance = _sendAlertService.GetConsumedStorage(drive);
performanceList.Add(performance);
}
Assert.That(performanceList, Is.Not.Null);
using (Assert.EnterMultipleScope())
{
Assert.That(performanceList, Is.Not.Empty);
Assert.That(performanceList[0].PercentageConsumed, Is.GreaterThan(0));
Assert.That(performanceList[0].ValueConsumed, Is.GreaterThan(0));
Assert.That(performanceList[0].ValueTotal, Is.GreaterThan(0));
}
}
else
{
Assert.Ignore("This test can only be run on Windows.");
}
}
}
}
@@ -10,18 +10,21 @@ namespace adas_core.Test.Services;
[TestFixture]
public class ServiceConfigServiceTest
{
/// <summary>
/// Initializes the test environment by creating mock instances of the service configuration repository and logger, and instantiating the <see cref="ServiceConfigService"/> under test.
/// </summary>
[SetUp]
public void Setup()
{
_serviceConfigRepositoryMock = new Mock<IServiceConfigRepository>();
_logger = new Mock<ILogger<ServiceConfigService>>();
_serviceConfigService = new ServiceConfigService(
_serviceConfigRepositoryMock.Object,
_logger.Object
);
}
public void Setup()
{
_serviceConfigRepositoryMock = new Mock<IServiceConfigRepository>();
_logger = new Mock<ILogger<ServiceConfigService>>();
_serviceConfigService = new ServiceConfigService(
_serviceConfigRepositoryMock.Object,
_logger.Object
);
}
private ServiceConfigService _serviceConfigService;
@@ -30,34 +33,41 @@ public class ServiceConfigServiceTest
private static ObjectId _id = ObjectId.GenerateNewId();
/// <summary>
/// Verifies that the <see cref="ServiceConfig"/> service returns a <see cref="ServiceConfig"/> instance
/// when retrieving an existing configuration by its string identifier.
/// </summary>
[Test]
public async Task Get_Find_Id_Return_ServiceConfig()
{
var serviceConfig = new ServiceConfig
public async Task Get_Find_Id_Return_ServiceConfig()
{
StrId = _id.ToString()
};
_serviceConfigRepositoryMock.Setup(s => s.FindById(_id.ToString())).ReturnsAsync(serviceConfig);
var result = await _serviceConfigService.Get(serviceConfig.StrId);
Assert.That(result, Is.Not.Null);
}
var serviceConfig = new ServiceConfig
{
StrId = _id.ToString()
};
_serviceConfigRepositoryMock.Setup(s => s.FindById(_id.ToString())).ReturnsAsync(serviceConfig);
var result = await _serviceConfigService.Get(serviceConfig.StrId);
Assert.That(result, Is.Not.Null);
}
/// <summary>
/// Verifies that the Get method returns a <see cref="ServiceConfig"/> when the specified id is not found by the repository's <c>FindById</c>, falling back to a lookup with any <see cref="ObjectId"/>.
/// </summary>
[Test]
public async Task Get_Not_Find_Id_Return_ServiceConfig()
{
var serviceConfig = new ServiceConfig
public async Task Get_Not_Find_Id_Return_ServiceConfig()
{
StrId = _id.ToString()
};
_serviceConfigRepositoryMock.Setup(s => s.FindById(_id)).ReturnsAsync((ServiceConfig?)null);
_serviceConfigRepositoryMock.Setup(s => s.FindById(It.IsAny<ObjectId>())).ReturnsAsync(serviceConfig);
var result = await _serviceConfigService.Get(_id.ToString());
Assert.That(result, Is.Not.Null);
}
var serviceConfig = new ServiceConfig
{
StrId = _id.ToString()
};
_serviceConfigRepositoryMock.Setup(s => s.FindById(_id)).ReturnsAsync((ServiceConfig?)null);
_serviceConfigRepositoryMock.Setup(s => s.FindById(It.IsAny<ObjectId>())).ReturnsAsync(serviceConfig);
var result = await _serviceConfigService.Get(_id.ToString());
Assert.That(result, Is.Not.Null);
}
}
@@ -16,6 +16,9 @@ namespace adas_core.Test.Services;
[TestFixture]
public class TreatmentServiceTest
{
/// <summary>
/// Initializes the mock dependencies and creates a <see cref="TreatmentService"/> instance for unit testing.
/// </summary>
[SetUp]
public void Setup()
{
@@ -172,6 +175,10 @@ public class TreatmentServiceTest
}
];
/// <summary>
/// Verifies that saving an <see cref="ApiRequest"/> without the Case option throws an <see cref="ApiRequestException"/>, preventing the insert operation.
/// </summary>
/// <exception cref="ApiRequestException">Thrown by the service when the Case option is not provided in the request.</exception>
[Test]
public void SaveRequest_Not_Case_option_Return_not_insert()
{
@@ -181,6 +188,9 @@ public class TreatmentServiceTest
Assert.ThrowsAsync<ApiRequestException>(act);
}
/// <summary>
/// Verifies that _treatmentService.SaveRequest throws an <see cref="ApiRequestException"/> when the <see cref="ApiRequest"/> is missing both the patient number and the point of care, preventing insertion of an incomplete request.
/// </summary>
[Test]
public void SaveRequest_patientNumber_and_pointOfCare_null_Return_not_insert()
{
@@ -194,6 +204,9 @@ public class TreatmentServiceTest
Assert.ThrowsAsync<ApiRequestException>(act);
}
/// <summary>
/// Verifies that _treatmentService.SaveRequest does not insert a patient treatment record when the patient cannot be found in the archive.
/// </summary>
[Test]
public async Task SaveRequest_Not_Find_Patient_Return_not_insert()
{
@@ -219,6 +232,9 @@ public class TreatmentServiceTest
_treatmentArchiveRepositoryMock.Verify(d => d.InsertOneAsync(It.IsAny<PatientTreatment>()), Times.Never);
}
/// <summary>
/// Verifies that SaveRequest does not insert a new patient treatment into the treatment archive when an existing patient is found by patient number.
/// </summary>
[Test]
public async Task SaveRequest_Find_Patient_Return_insert()
{
@@ -255,6 +271,9 @@ public class TreatmentServiceTest
_treatmentArchiveRepositoryMock.Verify(d => d.InsertOneAsync(It.IsAny<PatientTreatment>()), Times.Never);
}
/// <summary>
/// Verifies that <c>GetActiveTreatmentsByPatient</c> retrieves the full set of active treatments for the specified patient, returning a non-empty collection that includes all expected treatment records identified by their placer order entity identifiers.
/// </summary>
[Test]
public async Task GetActiveTreatmentsByPatient()
{
@@ -272,6 +291,7 @@ public class TreatmentServiceTest
That(result.Find(t => t?.PlacerOrder?.EntityIdentifier == "2100"), Is.Not.Null);
That(result.Find(t => t?.PlacerOrder?.EntityIdentifier == "4590"), Is.Not.Null);
That(result.Find(t => t?.PlacerOrder?.EntityIdentifier == "3690"), Is.Not.Null);
};
}
;
}
}
+70 -14
View File
@@ -12,6 +12,12 @@ using Moq;
namespace adas_core.Test.Services;
/// <summary>
/// Provides a unit test class for testing the functionality of the UnitService.
/// </summary>
/// <remarks>
/// This class is intended to contain test methods that validate the behavior and correctness of the UnitService.
/// </remarks>
public class UnitServiceTest
{
private readonly Mock<ILocalAuditService> _auditServiceMock = new();
@@ -25,6 +31,10 @@ public class UnitServiceTest
private Mock<IUnitRepository> _unitRepositoryMock = null!;
private UnitService _unitService = null!;
/// <summary>
/// Initializes mocked dependencies and a configured <see cref="UnitService"/> instance for each test,
/// including a mock <see cref="HttpContext"/> with a test user claim.
/// </summary>
[SetUp]
public void SetUp()
{
@@ -59,6 +69,9 @@ public class UnitServiceTest
);
}
/// <summary>
/// Verifies that the unit service returns all units retrieved from the repository, including the correct count and content.
/// </summary>
[Test]
public async Task GetAll_ShouldReturnAllUnits()
{
@@ -75,6 +88,9 @@ public class UnitServiceTest
Assert.That(result, Is.EqualTo(units));
}
/// <summary>
/// Verifies that the Get method on the unit service returns the expected unit when a valid identifier is provided.
/// </summary>
[Test]
public async Task Get_ShouldReturnUnitById()
{
@@ -91,16 +107,19 @@ public class UnitServiceTest
Assert.That(result, Is.EqualTo(unit));
}
/// <summary>
/// Verifies that the Get method retrieves a unit by name when the provided identifier is not a valid ObjectId, matching against either the unit's Title or Name.
/// </summary>
[Test]
public async Task Get_ShouldReturnUnitByName_WhenIdIsNotObjectId()
{
// Arrange
var unitName = "TestUnit";
var units = new List<Unit>
{
new() { Title = unitName },
new() { Name = unitName }
};
{
new() { Title = unitName },
new() { Name = unitName }
};
_unitRepositoryMock.Setup(repo => repo.GetAll()).ReturnsAsync(units);
// Act
@@ -111,16 +130,20 @@ public class UnitServiceTest
Assert.That(result?.Title ?? result?.Name, Is.EqualTo(unitName));
}
/// <summary>
/// Verifies that _unitService" returns all units retrieved from the repository,
/// ensuring the result is not null, contains the expected number of units, and matches the source data.
/// </summary>
[Test]
public async Task GetAllUnits_ShouldReturnAllUnits()
{
// Arrange
var units = new List<Unit>
{
new() { Id = ObjectId.GenerateNewId(), Name = "Unit1" },
new() { Id = ObjectId.GenerateNewId(), Name = "Unit2" }
};
{
new() { Id = ObjectId.GenerateNewId(), Name = "Unit1" },
new() { Id = ObjectId.GenerateNewId(), Name = "Unit2" }
};
_unitRepositoryMock.Setup(repo => repo.GetAll()).ReturnsAsync(units);
@@ -133,6 +156,9 @@ public class UnitServiceTest
Assert.That(result, Is.EquivalentTo(units));
}
/// <summary>
/// Verifies that _unitService.FindById" returns the matching <see cref="Unit"/> when the repository locates it by id.
/// </summary>
[Test]
public async Task GetUnitById_ShouldReturnUnit_WhenFound()
{
@@ -150,6 +176,9 @@ public class UnitServiceTest
Assert.That(result, Is.EqualTo(unit));
}
/// <summary>
/// Verifies that <c>GetByName</c> returns the matching unit when a unit with the specified name exists in the repository.
/// </summary>
[Test]
public async Task GetUnitByName_ShouldReturnUnit_WhenFound()
{
@@ -167,6 +196,9 @@ public class UnitServiceTest
Assert.That(result, Is.EqualTo(expectedUnit));
}
/// <summary>
/// Verifies that <c>FindByPatientId</c> returns the associated unit when the patient is found in an active (in-use) point of care.
/// </summary>
[Test]
public async Task FindUnitByPatientId_ShouldReturnUnit_WhenPatientFoundInActivePoC()
{
@@ -194,6 +226,9 @@ public class UnitServiceTest
}
/// <summary>
/// Verifies that the unit service returns the matching units retrieved from the repository when a call is made to find units by the specified master list identifier and type.
/// </summary>
[Test]
public async Task FindUnitsByMasterListId_ShouldReturnUnits_WhenUnitsFound()
{
@@ -216,18 +251,22 @@ public class UnitServiceTest
}
}
/// <summary>
/// Verifies that the unit service returns the matching units when the repository contains units
/// whose point of care matches the specified patient location.
/// </summary>
[Test]
public async Task FindByLocation_ShouldReturnUnits_WhenUnitsFound()
{
// Arrange
var location = new PatientLocation("TestUnit", "TestBed", "TestRoom");
var units = new List<Unit>
{
new()
{
PointOfCares = [new PointOfCare { UnitName = "TestUnit", Bed = "TestBed" }]
}
};
new()
{
PointOfCares = [new PointOfCare { UnitName = "TestUnit", Bed = "TestBed" }]
}
};
_unitRepositoryMock.Setup(repo => repo.GetAll()).ReturnsAsync(units);
// Act
@@ -240,6 +279,9 @@ public class UnitServiceTest
Assert.That(result?.First().PointOfCares?.First().Bed, Is.EqualTo("TestBed"));
}
/// <summary>
/// Verifies that FindByLocation returns an empty list when no units match the specified patient location.
/// </summary>
[Test]
public async Task FindByLocation_ShouldReturnEmptyList_WhenNoUnitsFound()
{
@@ -255,6 +297,10 @@ public class UnitServiceTest
Assert.That(result?.Count, Is.EqualTo(0));
}
/// <summary>
/// Verifies that the unit service returns the expected unit when a valid unit identifier is provided.
/// </summary>
/// <returns>A task that represents the asynchronous test execution.</returns>
[Test]
public async Task FindById_ShouldReturnUnit_WhenValidIdProvided()
{
@@ -271,6 +317,10 @@ public class UnitServiceTest
Assert.That(result, Is.EqualTo(expectedUnit));
}
/// <summary>
/// Verifies that the unit service's FindById method returns null when invoked with a null identifier,
/// ensuring graceful handling of null input without throwing or returning a default entity.
/// </summary>
[Test]
public async Task FindById_ShouldReturnNull_WhenNullIdProvided()
{
@@ -283,6 +333,9 @@ public class UnitServiceTest
Assert.That(result, Is.Null);
}
/// <summary>
/// Verifies that FindByName retrieves and returns the expected <c>Unit</c> when a valid unit name is provided.
/// </summary>
[Test]
public async Task FindByName_ShouldReturnUnit_WhenValidNameProvided()
{
@@ -300,6 +353,9 @@ public class UnitServiceTest
}
/// <summary>
/// Verifies that the unit service correctly delegates the insert operation to the repository and returns the inserted unit entity unchanged.
/// </summary>
[Test]
public async Task InsertOne_ShouldReturnInsertedUnit()
{