using System.Security.Claims;
using adas_core.Application.Repositories.Interfaces;
using adas_core.Application.Services;
using adas_core.Application.Services.Interfaces;
using adas_core.Application.Subscriptions;
using adas_core.Domain.Enums;
using adas_core.Domain.Models;
using adas_core.Domain.Models.AppSettings;
using adas_core.Domain.Models.Filter;
using adas_core.Domain.Models.MongoModels;
using adas_core.Domain.Models.Pumps;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using MongoDB.Bson;
using Moq;
using Options = Microsoft.Extensions.Options.Options;
namespace adas_core.Test.Services;
///
/// Provides a test fixture for verifying the behavior of the PumpService.
///
///
/// This class is decorated with the TestFixture attribute and hosts the unit tests for the PumpService.
///
///
[TestFixture]
public class PumpServiceTest
{
private PumpService _service = null!;
private Mock _obsRepo = null!;
private Mock _stateRepo = null!;
private Mock _alarmEventRepo = null!;
private Mock _alarmStateRepo = null!;
private Mock _archiveRepo = null!;
private Mock _patientSvc = null!;
private Mock _configPumps = null!;
private Mock _subs = null!;
private Mock _clientMsg = null!;
private Mock _configUnits = null!;
private Mock _calcObs = null!;
private Lazy _lazyCalc = null!;
private Mock _http = null!;
private Mock _audit = null!;
private Mock> _logger = null!;
private static readonly ObjectId PatientId = ObjectId.GenerateNewId();
private static readonly DateTime Now = DateTime.UtcNow;
///
/// Initializes the unit test fixture by creating mock repositories, services, and a mocked with a test claims principal, then instantiates the under test using the configured API settings (5-second pump expiration, zero pump messages disabled). Pass-through mappings are configured for , , and so that supplied pump observations are returned unchanged.
///
///
[SetUp]
public void Setup()
{
_obsRepo = new Mock();
_stateRepo = new Mock();
_alarmEventRepo = new Mock();
_alarmStateRepo = new Mock();
_archiveRepo = new Mock();
_patientSvc = new Mock();
_configPumps = new Mock();
_subs = new Mock();
_clientMsg = new Mock();
_calcObs = new Mock();
_lazyCalc = new Lazy(() => _calcObs.Object);
_http = new Mock();
_audit = new Mock();
_logger = new Mock>();
_configUnits = new Mock();
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()))
.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()))
.ReturnsAsync((PumpObservation o) => o);
_configUnits.Setup(x => x.Map(It.IsAny()))
.ReturnsAsync((PumpObservation o) => o);
}
// --------------------------------------------------------------
// SAVE REQUEST — casos básicos
// --------------------------------------------------------------
///
/// Verifies that processing does not insert any records
/// when the request is saved without associated observations.
///
///
[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()), Times.Never);
}
///
/// Verifies that SaveRequest does not insert a when the is an unrecognized value.
///
///
[Test]
public async Task SaveRequest_UnknownType_DoesNotInsert()
{
var req = new ApiRequest
{
Type = "UNKNOWN",
PumpObservation = new PumpObservation { Time = Now }
};
await _service.SaveRequest(req);
_obsRepo.Verify(r => r.InsertAsync(It.IsAny()), Times.Never);
}
///
/// Verifies that SaveRequest converts a single PumpObservation on an incoming
/// ApiRequest into a list with one entry on the request after processing.
///
///
[Test]
public async Task SaveRequest_Converts_SingleObservation_ToList()
{
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));
}
///
/// Verifies that Service.SaveRequest sets the Expires property of the before persisting it via the repository.
///
///
[Test]
public async Task SaveRequest_SetsExpires_BeforeInsert()
{
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(o => o.Expires == 5)), Times.Once);
}
// --------------------------------------------------------------
// PROCESS ALARM
// --------------------------------------------------------------
///
/// Verifies that saving a request of type "ORU_R40" creates a
/// and does not create a , ensuring alarm-phase events are
/// routed to the alarm event store rather than the observation store.
///
///
[Test]
public async Task SaveRequest_ORU_R40_CreatesAlarmEvent()
{
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()), Times.Once);
_obsRepo.Verify(r => r.InsertAsync(It.IsAny()), Times.Never);
}
///
/// Verifies that processing a pump observation with EventPhase.End removes the corresponding alarm state
/// by calling RemoveAsync on the alarm state repository with the matching device, alarm type, and MDC code.
///
///
[Test]
public async Task ProcessAlarm_End_RemovesAlarmState()
{
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
// --------------------------------------------------------------
///
/// Verifies that SaveRequest creates and upserts a new
/// for the device when no existing pump state is found in the repository.
///
///
[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(s => s.DeviceId == "P1")), Times.Once);
}
// --------------------------------------------------------------
// BROADCAST
// --------------------------------------------------------------
///
/// Verifies that SaveRequest does not broadcast a message via the client when there are no active subscribers.
///
/// A task that completes when the assertion has been executed.
///
[Test]
public async Task SaveRequest_NoSubscribers_NoBroadcast()
{
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(), It.IsAny(), It.IsAny