using System.Collections.Specialized; using adas_core.Application.Services; using adas_core.Application.Services.Interfaces; using adas_core.Domain.Enums; using adas_core.Domain.Models; using adas_core.Domain.Models.AppSettings; using adas_core.Domain.Models.MongoModels; using adas_core.Domain.Models.Providers; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using MongoDB.Bson; using Moq; using Quartz; using Quartz.Impl; using Options = Microsoft.Extensions.Options.Options; namespace adas_core.Test.Services; [TestFixture] public class SchedulerServiceTest { [OneTimeSetUp] public async Task SetUp() { #region [Services Mock seUp] _treatmentServiceMock = new Mock(); _treatmentServiceLazy = new Lazy(() => _treatmentServiceMock.Object); _patientServiceMock = new Mock(); _patientServiceLazy = new Lazy(() => _patientServiceMock.Object); _medicineServiceMock = new Mock(); _medicineServiceLazy = new Lazy(() => _medicineServiceMock.Object); _observationServiceMock = new Mock(); _observationServiceLazy = new Lazy(() => _observationServiceMock.Object); _configObservationServiceMock = new Mock(); var configObservationServiceLazy = new Lazy(() => _configObservationServiceMock.Object); _logger = new Mock>(); _httpClientFactoryMock = new Mock(); _httpMessageHandlerMock = new Mock(); _calculatedObservationsServiceMock = new Mock(); _calculatedObservationsServiceLazy = new Lazy(() => _calculatedObservationsServiceMock.Object); _appointmentServiceServiceMock = new Mock(); _appointmentServiceServiceLazy = new Lazy(() => _appointmentServiceServiceMock.Object); _diagnosisServiceMock = new Mock(); _diagnosisServiceLazy = new Lazy(() => _diagnosisServiceMock.Object); var client = new HttpClient(_httpMessageHandlerMock.Object); _httpClientFactoryMock.Setup(c => c.CreateClient(It.IsAny())).Returns(client); _optionsApiSettings = Options.Create(_apiSettings); _optionsProvidersSettings = Options.Create(_providersSettings); #endregion var unused = new SchedulerService( _optionsApiSettings, _optionsProvidersSettings, _patientServiceLazy, _treatmentServiceLazy, _appointmentServiceServiceLazy, _diagnosisServiceLazy, _medicineServiceLazy, _observationServiceLazy, configObservationServiceLazy, _logger.Object, _httpClientFactoryMock.Object, _calculatedObservationsServiceLazy, _patientCarePlanLazy ); #region [Service item fill] _sinceDate = Now.AddHours(-24); _sinceDischargeTimeToArchive = 48; _archivePatientsWithoutObservationsSinceHours = 28; _jobDataMap = new JobDataMap { { "sinceDate", _sinceDate }, { "sinceDischargeTime", _sinceDischargeTimeToArchive }, { "archivePatientsWithoutObservationsSinceHours", _archivePatientsWithoutObservationsSinceHours }, { "checkNewsJobIntervalMinutes", 15 } }; // Grab the Scheduler instance from the Factory var properties = new NameValueCollection { { "quartz.scheduler.instanceName", "MyUniqueSchedulerName" } }; _factory = new StdSchedulerFactory(properties); if (_scheduler == null) { var getFactory = await _factory.GetScheduler(); _scheduler = getFactory; } // Set up observation list var itemList = new List { new() { Name = "Resp_Rate_Calculated", CodingSystem = "ADAS", MinAlert = 14, MaxAlert = 25, Expires = 10, ShowOnExpired = false }, new() { Name = "SpO2", Code = "150456", OriginalName = "MDC_PULS_OXIM_SAT_O2", CodingSystem = "MDC", ParentCode = "69965", ParentName = "MDC_DEV_MON_PHYSIO_MULTI_PARAM_MDS", MinAlert = 94, MaxAlert = 100, ParentCodingSystem = "MDC", Expires = 10, ShowOnExpired = false }, new() { Name = "Temperature", Code = "386053000", OriginalName = "Temperatura", CodingSystem = "SNM", ParentCode = "386053000", ParentName = "Temperatura(ºC)", MinAlert = 34.5, MinWarn = 35.5, MaxWarn = 37.4, MaxAlert = 38, ParentCodingSystem = "SNM", Expires = 60 }, new() { CodingSystem = "MDC", Code = "150033", OriginalName = "MDC_PRESS_BLD_ART_SYS", Name = "TAs", Expires = 10 }, new() { Name = "FC", Code = "149514", OriginalName = "MDC_PULS_RATE", CodingSystem = "MDC", MinAlert = 60, MaxAlert = 100, Expires = 10, ShowOnExpired = false } }; var observations = new List { new() { Name = "Resp_Rate_Calculated", Value = 18, Time = DateTime.UtcNow.AddMinutes(-60), PatientId = PatientId }, new() { Name = "SpO2", Value = 93, Time = DateTime.UtcNow.AddMinutes(-60), PatientId = PatientId }, new() { Name = "Temperature", Value = 36.5, Time = DateTime.UtcNow, PatientId = PatientId }, new() { Name = "TAs", Value = 103, Time = DateTime.UtcNow, PatientId = PatientId }, new() { Name = "FC", Value = 80, Time = DateTime.UtcNow.AddMinutes(-60), PatientId = PatientId }, new() { Name = "Resp_Rate_Calculated", Value = 18, Time = DateTime.UtcNow.AddMinutes(-60), PatientId = PatientId2 }, new() { Name = "SpO2", Value = 93, Time = DateTime.UtcNow.AddMinutes(-60), PatientId = PatientId2 }, new() { Name = "Temperature", Value = 36.5, Time = DateTime.UtcNow.AddMinutes(-60), PatientId = PatientId2 }, new() { Name = "TAs", Value = 103, Time = DateTime.UtcNow, PatientId = PatientId2 }, new() { Name = "FC", Value = 40, Time = DateTime.UtcNow, PatientId = PatientId2 } }; // Set Up patientList var patientList = new List { new() { Id = PatientId, UnitId = ObjectId.GenerateNewId(), PointOfCareId = ObjectId.GenerateNewId(), PatientNumber = "patientNumber1", Person = new Person { FirstName = "firstName1", LastName = "lastName1", BirthDate = new DateTime(), Gender = PatientEnum.Gender.Male } }, new() { Id = PatientId2, UnitId = ObjectId.GenerateNewId(), PointOfCareId = ObjectId.GenerateNewId(), PatientNumber = "patientNumber2", Person = new Person { FirstName = "firstName2", LastName = "lastName2", BirthDate = new DateTime(), Gender = PatientEnum.Gender.Male } } }; #endregion #region [Service behavior] // Set up configObservationServiceMock to return desire item _configObservationServiceMock .Setup(service => service.Get(It.IsAny(), It.IsAny())) .ReturnsAsync((BasePatientObservation obs, bool _) => { return itemList.FirstOrDefault(obsConfig => obsConfig.Name == obs.Name); }); // Set up observationService behavior _observationServiceMock .Setup(service => service.FindLastObservations(It.IsAny(), 1, It.IsAny>())) .ReturnsAsync((ObjectId patientId, int _, List? filterObservations) => { var filteredObservations = observations .Where(obs => filterObservations != null && obs.Name != null && filterObservations.Contains(obs.Name) && obs.PatientId == patientId) .ToList(); return filteredObservations; }); // Set up patientServiceMock to return patientList for FindInActivePoC _patientServiceMock.Setup(service => service.FindInActivePoC()).ReturnsAsync(patientList); #endregion // Asignación de dependencias estáticas para el job CalculateNewsJob.PatientService = _patientServiceMock.Object; CalculateNewsJob.ObservationService = _observationServiceMock.Object; CheckExpiredAlertsJob.ObservationService = _observationServiceMock.Object; CheckExpiredObservationsJob.ObservationService = _observationServiceMock.Object; CalculateNewsJob.ConfigObservationService = _configObservationServiceMock.Object; // and start it off _scheduler?.Start(); } //[TearDown] [OneTimeTearDown] public void TearDown() { // Detener el motor de Quartz.NET después de las pruebas //scheduler.Shutdown().Wait(); _scheduler?.Shutdown(); } private Mock? _treatmentServiceMock; private Mock? _patientServiceMock; private Mock? _medicineServiceMock; private Mock? _observationServiceMock; private Mock? _configObservationServiceMock; private Mock? _httpClientFactoryMock; private Mock? _httpMessageHandlerMock; private Mock? _calculatedObservationsServiceMock; private Mock? _appointmentServiceServiceMock; private Mock? _diagnosisServiceMock; private Lazy? _treatmentServiceLazy; private Lazy? _patientServiceLazy; private Lazy? _medicineServiceLazy; private Lazy? _observationServiceLazy; private Lazy _calculatedObservationsServiceLazy; private Lazy _appointmentServiceServiceLazy; private Lazy _diagnosisServiceLazy; private readonly Lazy _patientCarePlanLazy = new(); private readonly ApiSettings _apiSettings = new(); private IOptions? _optionsApiSettings; private readonly List _providersSettings = [ new() { Name = "Adas", Url = "http://localhost/info.html" } ]; private IOptions>? _optionsProvidersSettings; private Mock>? _logger; private JobDataMap _jobDataMap = new(); private DateTime _sinceDate; private int _sinceDischargeTimeToArchive; private int _archivePatientsWithoutObservationsSinceHours; private StdSchedulerFactory? _factory; private IScheduler? _scheduler; private static readonly DateTime Now = DateTime.Now; private static readonly ObjectId PatientId = ObjectId.GenerateNewId(); private static readonly ObjectId PatientId2 = ObjectId.GenerateNewId(); [Test] public void CheckInactivePatientsJob_ShouldExecuteOnTrigger() { var checkInactivePatientSchedulerIntervalHours = 12; // Arrange var checkInactivePatientsJob = JobBuilder.Create() .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(), It.IsAny()), Times.AtLeastOnce()); } [Test] public void CheckActiveTreatmentsJob_ShouldExecuteOnTrigger() { var patient = new Patient { 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() .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())).ReturnsAsync([patient]); // Create a mock of the singleton class subscribers var mockSingleton = new Mock(); // Set up the mock object to return a specific value when a method is called var patientTreatmentList = new List { 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>())) .ReturnsAsync(new List()); // 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>()), Times.Once()); } [Test] public void CheckExpiredObservationsJob_ShouldExecuteOnTrigger() { var checkExpiredObservationsIntervalMinutes = 3; // Arrange var checkExpiredObservationsJob = JobBuilder.Create() .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() .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 { 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() .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())).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(),true,true), Times.AtLeastOnce()); _observationServiceMock?.Verify(p => p.InsertObservation( It.Is(obs => obs.Name != "NEWS"), true, true), Times.Never()); } [Test] public void CheckCalculateNewsJob_ShouldExecuteOnTrigger() { var checkExpiredAlertsIntervalMinutes = 3; // Arrange var calculateNewsJob = JobBuilder.Create() .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(), true, true), Times.AtLeastOnce()); } [Test] public async Task CalculateNewsJob_Insert_ObsFull_MultiplePatient_With_Correct_Value() { var job = new CalculateNewsJob(); // Act await job.Execute(Mock.Of()); // Assert _observationServiceMock?.Verify( service => service.InsertObservation( It.Is(obs => obs.Name == "NEWS" && (int)obs.Value == 1 && obs.PatientId == PatientId), true, true), Times.Once); _observationServiceMock?.Verify( service => service.InsertObservation( It.Is(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 }