Files

461 lines
16 KiB
C#

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;
[TestFixture]
public class PumpServiceTest
{
private PumpService _service = null!;
private Mock<IPumpObservationRepository> _obsRepo = null!;
private Mock<IPumpStateRepository> _stateRepo = null!;
private Mock<IPumpAlarmEventRepository> _alarmEventRepo = null!;
private Mock<IPumpAlarmStateRepository> _alarmStateRepo = null!;
private Mock<IPumpArchiveRepository> _archiveRepo = null!;
private Mock<IPatientService> _patientSvc = null!;
private Mock<IConfigPumpsService> _configPumps = null!;
private Mock<ISubscribersService> _subs = null!;
private Mock<IClientMessageService> _clientMsg = null!;
private Mock<IConfigUnitsService> _configUnits = null!;
private Mock<ICalculatedObservationsService> _calcObs = null!;
private Lazy<ICalculatedObservationsService> _lazyCalc = null!;
private Mock<IHttpContextAccessor> _http = null!;
private Mock<ILocalAuditService> _audit = null!;
private Mock<ILogger<PumpService>> _logger = null!;
private static readonly ObjectId PatientId = ObjectId.GenerateNewId();
private static readonly DateTime Now = DateTime.UtcNow;
[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
{
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
// --------------------------------------------------------------
[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);
}
[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<PumpObservation>()), Times.Never);
}
[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));
}
[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<PumpObservation>(o => o.Expires == 5)), Times.Once);
}
// --------------------------------------------------------------
// PROCESS ALARM
// --------------------------------------------------------------
[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<PumpAlarmEvent>()), Times.Once);
_obsRepo.Verify(r => r.InsertAsync(It.IsAny<PumpObservation>()), Times.Never);
}
[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
// --------------------------------------------------------------
[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);
}
// --------------------------------------------------------------
// BROADCAST
// --------------------------------------------------------------
[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<string>(), It.IsAny<OperationType>(), It.IsAny<object>()),
Times.Never);
}
[Test]
public async Task SaveRequest_WithSubscribers_UsesReqLocation_AndSendsBroadcast()
{
// Arrange
var obs = new PumpObservation
{
Time = Now,
DeviceId = "D22",
PatientId = null // <- clave para que se use req.Location
};
var req = new ApiRequest
{
Type = "ORU_R01",
PumpObservation = obs,
Location = new PatientLocation("UCI5C", "Box4", "Room1"),
PatientNumber = "437537"
};
var subscriber = new WsSubscriber("sub1")
{
Locations = [ new PatientLocation("UCI5C", "Box4", "Room1") ]
};
// 1) NO devolver paciente en FindPatientByApiRequest -> fuerza uso de req. Location
_patientSvc.Setup(p => p.FindPatientByApiRequest(req))
.ReturnsAsync((Patient?)null);
// 2) Suscriptores con la misma ubicación que req.Location
_subs.Setup(x => x.GetSubscribers())
.Returns([subscriber]);
// 3) Evitar null en alarmas activas al enumerar (foreach)
_alarmStateRepo.Setup(r => r.FindAllActiveByDeviceAsync("D22"))
.ReturnsAsync(Enumerable.Empty<PumpAlarmState>());
// 4) Snapshot: por claridad, fuerza que se cree uno nuevo
_stateRepo.Setup(r => r.FindByDeviceIdAsync("D22"))
.ReturnsAsync((PumpState?)null);
// Map mínimo para que avance el flujo
_configPumps.Setup(m => m.Map(It.IsAny<PumpObservation>()))
.ReturnsAsync((PumpObservation o) => o);
_configUnits.Setup(m => m.Map(It.IsAny<PumpObservation>()))
.ReturnsAsync((PumpObservation o) => o);
// Act
await _service.SaveRequest(req);
// Assert
_clientMsg.Verify(x =>
x.SendAsync("sub1", OperationType.Pump, It.IsAny<PumpState>()),
Times.Once);
// No se envían PumpAlarm si no hay activas
_clientMsg.Verify(x =>
x.SendAsync("sub1", OperationType.PumpAlarm, It.IsAny<object>()),
Times.Never);
}
// --------------------------------------------------------------
// RETENCIÓN — DeleteOlderDays
// --------------------------------------------------------------
[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);
}
// --------------------------------------------------------------
// PAGINACIÓN
// --------------------------------------------------------------
[Test]
public async Task GetPaginatedPump_ByPatient_Works()
{
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
// --------------------------------------------------------------
[Test]
public async Task ArchiveByPatientId_InsertsArchive_ThenDeletes()
{
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
// --------------------------------------------------------------
[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);
}
// --------------------------------------------------------------
// FIND LAST OBSERVATIONS
// --------------------------------------------------------------
[Test]
public async Task FindLastPumpObservations_ReturnsOrdered()
{
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
// --------------------------------------------------------------
[Test]
public async Task InsertPumpObservation_Inserts_AndBroadcasts()
{
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);
}
}