450 lines
18 KiB
C#
450 lines
18 KiB
C#
using adas_core.Domain.Enums;
|
||
using adas_core.Domain.Models.AppSettings;
|
||
using adas_core.Domain.Models.Pumps;
|
||
using adas_core.Infrastructure.Repositories;
|
||
using Microsoft.Extensions.Options;
|
||
using MongoDB.Bson;
|
||
using MongoDB.Driver;
|
||
|
||
namespace adas_core.Test.Repositories;
|
||
|
||
[TestFixture]
|
||
[Category("Integration")]
|
||
public class PumpAlarmEventRepositoryTest
|
||
{
|
||
private PumpAlarmEventRepository _repo;
|
||
private IOptions<ApiSettings> _apiSettings;
|
||
|
||
private static readonly DateTime Now = DateTime.UtcNow;
|
||
private const string DeviceA = "Device-A";
|
||
private const string DeviceB = "Device-B";
|
||
private static readonly ObjectId PatientId = ObjectId.GenerateNewId();
|
||
|
||
// -------------------------------------------------------------------
|
||
// INIT – Setup inicial de colección con índices y datos de prueba
|
||
// -------------------------------------------------------------------
|
||
/// <summary>
|
||
/// Performs one-time integration test setup for the pump alarm event repository by configuring API settings,
|
||
/// resetting the dedicated MongoDB collection, creating required indexes, and seeding it with three initial
|
||
/// alarm events covering both Device and DeviceB across attention and occlusion
|
||
/// alarm types. Verifies that exactly three documents are present after seeding.
|
||
/// </summary>
|
||
[OneTimeSetUp]
|
||
public async Task Init()
|
||
{
|
||
_apiSettings = Options.Create(new ApiSettings
|
||
{
|
||
PumpAlarmEvent = "pump_alarm_event"
|
||
});
|
||
|
||
await IntegrationDb.Database.DropCollectionAsync("pump_alarm_event");
|
||
await IntegrationDb.Database.CreateCollectionAsync("pump_alarm_event");
|
||
|
||
_repo = new PumpAlarmEventRepository(
|
||
_apiSettings,
|
||
IntegrationDb.Database
|
||
);
|
||
|
||
await _repo.CreateIndexes();
|
||
|
||
var initialEvents = new List<PumpAlarmEvent>
|
||
{
|
||
new()
|
||
{
|
||
Id = ObjectId.GenerateNewId(),
|
||
DeviceId = DeviceA,
|
||
PatientId = PatientId,
|
||
AlarmType = PumpEnum.AlarmType.Attention,
|
||
Time = Now.AddSeconds(-1)
|
||
},
|
||
new()
|
||
{
|
||
Id = ObjectId.GenerateNewId(),
|
||
DeviceId = DeviceA,
|
||
PatientId = PatientId,
|
||
AlarmType = PumpEnum.AlarmType.Occlusion,
|
||
Time = Now.AddSeconds(-3)
|
||
},
|
||
new()
|
||
{
|
||
Id = ObjectId.GenerateNewId(),
|
||
DeviceId = DeviceB,
|
||
PatientId = PatientId,
|
||
AlarmType = PumpEnum.AlarmType.Occlusion,
|
||
Time = Now.AddSeconds(-2)
|
||
}
|
||
};
|
||
|
||
await IntegrationDb.Database
|
||
.GetCollection<PumpAlarmEvent>("pump_alarm_event")
|
||
.InsertManyAsync(initialEvents);
|
||
|
||
var count = await _repo.Collection.CountDocumentsAsync(_ => true);
|
||
Assert.That(count, Is.EqualTo(3));
|
||
}
|
||
|
||
// -------------------------------------------------------------------
|
||
// INSERT
|
||
// -------------------------------------------------------------------
|
||
/// <summary>
|
||
/// Verifies that InsertAsync correctly persists a <see cref="PumpAlarmEvent"/> to the repository by inserting an event and confirming it can be retrieved by its identifier.
|
||
/// </summary>
|
||
[Test]
|
||
public async Task InsertAsync_Works()
|
||
{
|
||
var evt = new PumpAlarmEvent
|
||
{
|
||
Id = ObjectId.GenerateNewId(),
|
||
DeviceId = "Device-C",
|
||
PatientId = PatientId,
|
||
AlarmType = PumpEnum.AlarmType.Occlusion,
|
||
Time = Now
|
||
};
|
||
|
||
await _repo.InsertAsync(evt);
|
||
|
||
var found = await _repo.Collection.FindAsync(x => x.Id == evt.Id);
|
||
Assert.That(found.FirstOrDefault(), Is.Not.Null);
|
||
}
|
||
|
||
// -------------------------------------------------------------------
|
||
// FIND BY DEVICE ID
|
||
// -------------------------------------------------------------------
|
||
/// <summary>
|
||
/// Verifies that <c>FindByDeviceIdAsync</c> returns a non-empty collection of records for the specified device, ordered by time so that the most recent entry appears first.
|
||
/// </summary>
|
||
[Test]
|
||
public async Task FindByDeviceIdAsync_ReturnsOrdered()
|
||
{
|
||
var result = await _repo.FindByDeviceIdAsync(DeviceA);
|
||
|
||
var list = result.ToList();
|
||
|
||
using (Assert.EnterMultipleScope())
|
||
{
|
||
Assert.That(list, Is.Not.Empty);
|
||
Assert.That(list.First().Time, Is.GreaterThanOrEqualTo(list.Last().Time));
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Verifies that <c>FindByDeviceIdAsync</c> returns only pump alarm events whose timestamps fall within the specified date range.
|
||
/// </summary>
|
||
[Test]
|
||
public async Task FindByDeviceIdAsync_WithDateRange_Works()
|
||
{
|
||
var from = Now.AddSeconds(-2);
|
||
var to = Now;
|
||
|
||
var result = await _repo.FindByDeviceIdAsync(DeviceA, from, to);
|
||
|
||
using (Assert.EnterMultipleScope())
|
||
{
|
||
var pumpAlarmEvents = result.ToList();
|
||
Assert.That(pumpAlarmEvents, Is.Not.Empty);
|
||
Assert.That(pumpAlarmEvents.All(x => x.Time >= from && x.Time <= to), Is.True);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Verifies that <c>FindByDeviceIdAsync</c> returns at most the specified number of results when a limit is provided.
|
||
/// </summary>
|
||
[Test]
|
||
public async Task FindByDeviceIdAsync_WithLimit_Works()
|
||
{
|
||
var result = await _repo.FindByDeviceIdAsync(DeviceA, limit: 1);
|
||
|
||
var list = result.ToList();
|
||
Assert.That(list, Has.Count.EqualTo(1));
|
||
}
|
||
|
||
// -------------------------------------------------------------------
|
||
// FIND LAST BY DEVICE
|
||
// -------------------------------------------------------------------
|
||
[Test]
|
||
public async Task FindLastByDeviceIdAsync_Works()
|
||
{
|
||
// Arrange: dataset ya insertado en Init()
|
||
|
||
var events = await _repo.FindByDeviceIdAsync("Device-A");
|
||
var expected = events.OrderByDescending(e => e.Time).First();
|
||
|
||
// Act
|
||
var evt = await _repo.FindLastByDeviceIdAsync("Device-A");
|
||
|
||
// Assert
|
||
Assert.That(evt, Is.Not.Null);
|
||
Assert.That(evt!.Id, Is.EqualTo(expected.Id)); // comparación robusta
|
||
}
|
||
|
||
// -------------------------------------------------------------------
|
||
// DELETE BY PATIENT ID
|
||
// -------------------------------------------------------------------
|
||
/// <summary>
|
||
/// Verifies that DeleteByPatientId correctly removes all alarm events associated with a given patient.
|
||
/// Inserts a sample <c>PumpAlarmEvent</c> for a specific patient, deletes it by patient ID, and asserts that no matching events remain in the collection.
|
||
/// </summary>
|
||
[Test]
|
||
public async Task DeleteByPatientId_Works()
|
||
{
|
||
var patient2 = ObjectId.GenerateNewId();
|
||
|
||
var evt = new PumpAlarmEvent
|
||
{
|
||
Id = ObjectId.GenerateNewId(),
|
||
DeviceId = "Device-X",
|
||
PatientId = patient2,
|
||
AlarmType = PumpEnum.AlarmType.Occlusion,
|
||
Time = Now
|
||
};
|
||
|
||
await _repo.InsertAsync(evt);
|
||
|
||
await _repo.DeleteByPatientId(patient2);
|
||
|
||
var found = await _repo.Collection.FindAsync(x => x.PatientId == patient2);
|
||
Assert.That(await found.AnyAsync(), Is.False);
|
||
}
|
||
|
||
// -------------------------------------------------------------------
|
||
// UPDATE MANY (CAMBIO DE PATIENTID O SIMILAR)
|
||
// -------------------------------------------------------------------
|
||
|
||
/// <summary>
|
||
/// Verifies that <c>UpdateManyObjectIdByFiledNameAsync</c> correctly updates documents matching the specified field name and old ObjectId value, replacing it with the new ObjectId, and that the changes are persisted and queryable.
|
||
/// </summary>
|
||
[Test]
|
||
public async Task UpdateManyObjectIdByFiledNameAsync_Works()
|
||
{
|
||
var oldId = ObjectId.GenerateNewId();
|
||
var newId = ObjectId.GenerateNewId();
|
||
|
||
var evt = new PumpAlarmEvent
|
||
{
|
||
Id = ObjectId.GenerateNewId(),
|
||
DeviceId = "Device-U",
|
||
PatientId = oldId,
|
||
AlarmType = PumpEnum.AlarmType.Attention,
|
||
Time = Now
|
||
};
|
||
|
||
await _repo.InsertAsync(evt);
|
||
|
||
var updated = await _repo.UpdateManyObjectIdByFiledNameAsync("PatientId", newId, oldId);
|
||
|
||
Assert.That(updated, Is.EqualTo(1));
|
||
|
||
var found = await _repo.Collection.FindAsync(x => x.PatientId == newId);
|
||
Assert.That(await found.AnyAsync(), Is.True);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Verifies that <c>FindByDeviceIdAsync</c> returns an empty result when the queried date range falls outside of the available data window.
|
||
/// </summary>
|
||
[Test]
|
||
public async Task FindByDeviceIdAsync_ReturnsEmpty_WhenOutOfRange()
|
||
{
|
||
var from = Now.AddYears(-2);
|
||
var to = Now.AddYears(-2).AddDays(1);
|
||
|
||
var result = await _repo.FindByDeviceIdAsync("Device-A", from, to);
|
||
|
||
Assert.That(result, Is.Empty);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Verifies that <c>FindByDeviceIdAsync</c> correctly applies the requested limit and orders results so that the most recent alarm event for the given device is returned first.
|
||
/// Seeds multiple events for the target device, requests a single record, and asserts that the returned entry matches the latest event retrieved by <c>FindLastByDeviceIdAsync</c>.
|
||
/// </summary>
|
||
[Test]
|
||
public async Task FindByDeviceIdAsync_RespectsLimitAndOrder()
|
||
{
|
||
// Semilla adicional para asegurar > 1
|
||
var events = new[]
|
||
{
|
||
new PumpAlarmEvent { Id = ObjectId.GenerateNewId(), DeviceId = DeviceA, PatientId = PatientId, AlarmType = PumpEnum.AlarmType.AirInLine, Time = Now.AddMilliseconds(-10) },
|
||
new PumpAlarmEvent { Id = ObjectId.GenerateNewId(), DeviceId = DeviceA, PatientId = PatientId, AlarmType = PumpEnum.AlarmType.Occlusion, Time = Now.AddMilliseconds(-5) }
|
||
};
|
||
await _repo.Collection.InsertManyAsync(events);
|
||
|
||
var result = await _repo.FindByDeviceIdAsync(DeviceA, limit: 1);
|
||
var list = result.ToList();
|
||
|
||
using (Assert.EnterMultipleScope())
|
||
{
|
||
Assert.That(list, Has.Count.EqualTo(1));
|
||
var last = await _repo.FindLastByDeviceIdAsync(DeviceA);
|
||
Assert.That(list[0].Id, Is.EqualTo(last!.Id));
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Verifies that InsertAsync successfully persists a <see cref="PumpAlarmEvent"/> when its optional fields (<c>PatientId</c> and <c>AlarmType</c>) are <c>null</c>, and that the record can be retrieved afterwards.
|
||
/// </summary>
|
||
[Test]
|
||
public async Task InsertAsync_AllowsNullOptionalFields()
|
||
{
|
||
var evt = new PumpAlarmEvent
|
||
{
|
||
Id = ObjectId.GenerateNewId(),
|
||
DeviceId = "Device-Nulls",
|
||
PatientId = null, // opcional
|
||
AlarmType = null, // opcional
|
||
Time = Now
|
||
};
|
||
|
||
await _repo.InsertAsync(evt);
|
||
|
||
var found = await _repo.Collection.FindAsync(x => x.Id == evt.Id);
|
||
Assert.That(found.FirstOrDefault(), Is.Not.Null);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Verifies that <c>DeleteByPatientId</c> removes all alarm events associated with the given patient and can be invoked repeatedly without throwing once the records no longer exist.
|
||
/// </summary>
|
||
[Test]
|
||
public async Task DeleteByPatientId_DeletesMany_AndIsIdempotent()
|
||
{
|
||
var pid = ObjectId.GenerateNewId();
|
||
|
||
var docs = Enumerable.Range(0, 3).Select(i => new PumpAlarmEvent
|
||
{
|
||
Id = ObjectId.GenerateNewId(),
|
||
DeviceId = "Device-Del",
|
||
PatientId = pid,
|
||
AlarmType = PumpEnum.AlarmType.Attention,
|
||
Time = Now.AddSeconds(-i)
|
||
});
|
||
|
||
await _repo.Collection.InsertManyAsync(docs);
|
||
|
||
// Primera vez: borra 3
|
||
await _repo.DeleteByPatientId(pid);
|
||
var left = await _repo.Collection.FindAsync(x => x.PatientId == pid);
|
||
Assert.That(left.ToList(), Is.Empty);
|
||
|
||
Func<Task> act = async () => await _repo.DeleteByPatientId(pid);
|
||
Assert.That(act, Throws.Nothing);
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// Verifies that <c>UpdateManyObjectIdByFiledNameAsync</c> updates the <c>PatientId</c> field from an old <see cref="ObjectId"/> to a new one across all matching documents, returning the modified count and persisting the new identifier in the collection.
|
||
/// </summary>
|
||
[Test]
|
||
public async Task UpdateManyObjectIdByFiledNameAsync_UpdatesMultiple()
|
||
{
|
||
var oldId = ObjectId.GenerateNewId();
|
||
var newId = ObjectId.GenerateNewId();
|
||
|
||
var events = Enumerable.Range(0, 4).Select(i => new PumpAlarmEvent
|
||
{
|
||
Id = ObjectId.GenerateNewId(),
|
||
DeviceId = "Device-U",
|
||
PatientId = oldId,
|
||
AlarmType = PumpEnum.AlarmType.AirInLine,
|
||
Time = Now.AddMilliseconds(-i)
|
||
});
|
||
|
||
await _repo.Collection.InsertManyAsync(events);
|
||
|
||
var modified = await _repo.UpdateManyObjectIdByFiledNameAsync("PatientId", newId, oldId);
|
||
|
||
using (Assert.EnterMultipleScope())
|
||
{
|
||
Assert.That(modified, Is.EqualTo(4));
|
||
var foundNew = await _repo.Collection.FindAsync(x => x.PatientId == newId);
|
||
Assert.That(foundNew.ToList(), Has.Count.EqualTo(4));
|
||
}
|
||
}
|
||
/// <summary>
|
||
/// Verifies that <c>FindByDeviceIdAsync</c> treats the <c>from</c> and <c>to</c> time bounds as inclusive,
|
||
/// ensuring events whose timestamp matches the upper bound exactly are returned and that all returned
|
||
/// events fall within the supplied range.
|
||
/// </summary>
|
||
[Test]
|
||
public async Task FindByDeviceIdAsync_InclusiveRange_BoundsRespected()
|
||
{
|
||
var start = Now.AddMinutes(-30);
|
||
var end = Now.AddMinutes(-29);
|
||
|
||
var exactEvt = new PumpAlarmEvent
|
||
{
|
||
Id = ObjectId.GenerateNewId(),
|
||
DeviceId = "Device-Range",
|
||
PatientId = PatientId,
|
||
AlarmType = PumpEnum.AlarmType.Attention,
|
||
Time = end // exactamente igual al to
|
||
};
|
||
|
||
await _repo.InsertAsync(exactEvt);
|
||
|
||
var result = await _repo.FindByDeviceIdAsync("Device-Range", from: start, to: end);
|
||
|
||
using (Assert.EnterMultipleScope())
|
||
{
|
||
var pumpAlarmEvents = result as PumpAlarmEvent[] ?? result.ToArray();
|
||
Assert.That(pumpAlarmEvents, Is.Not.Empty);
|
||
Assert.That(pumpAlarmEvents.All(r => r.Time >= start && r.Time <= end), Is.True);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Verifies that FindLastByDeviceIdAsync returns the most recent alarm event for a given device and tracks newly inserted events as the latest entry.
|
||
/// </summary>
|
||
[Test]
|
||
public async Task FindLastByDeviceIdAsync_TracksNewest()
|
||
{
|
||
const string dev = "Device-LastCheck";
|
||
|
||
var e1 = new PumpAlarmEvent { Id = ObjectId.GenerateNewId(), DeviceId = dev, PatientId = PatientId, AlarmType = PumpEnum.AlarmType.AirInLine, Time = Now.AddSeconds(-10) };
|
||
var e2 = new PumpAlarmEvent { Id = ObjectId.GenerateNewId(), DeviceId = dev, PatientId = PatientId, AlarmType = PumpEnum.AlarmType.Occlusion, Time = Now.AddSeconds(-5) };
|
||
|
||
await _repo.Collection.InsertManyAsync([e1, e2]);
|
||
|
||
var last1 = await _repo.FindLastByDeviceIdAsync(dev);
|
||
Assert.That(last1!.Id, Is.EqualTo(e2.Id));
|
||
|
||
var e3 = new PumpAlarmEvent { Id = ObjectId.GenerateNewId(), DeviceId = dev, PatientId = PatientId, AlarmType = PumpEnum.AlarmType.Attention, Time = Now };
|
||
await _repo.InsertAsync(e3);
|
||
|
||
var last2 = await _repo.FindLastByDeviceIdAsync(dev);
|
||
Assert.That(last2!.Id, Is.EqualTo(e3.Id));
|
||
}
|
||
|
||
/// <summary>
|
||
/// Verifies that <c>FindByDeviceIdAsync</c> correctly filters pump alarm events by device identifier
|
||
/// and time range when multiple queries are issued against overlapping windows, ensuring each
|
||
/// range returns only the event that falls within its boundaries.
|
||
/// </summary>
|
||
[Test]
|
||
public async Task FindByDeviceIdAsync_MultipleOverlappingRanges()
|
||
{
|
||
const string dev = "Device-Timeline";
|
||
|
||
var batch = new[]
|
||
{
|
||
new PumpAlarmEvent { Id = ObjectId.GenerateNewId(), DeviceId = dev, PatientId = PatientId, AlarmType = PumpEnum.AlarmType.AirInLine, Time = Now.AddMinutes(-20) },
|
||
new PumpAlarmEvent { Id = ObjectId.GenerateNewId(), DeviceId = dev, PatientId = PatientId, AlarmType = PumpEnum.AlarmType.Occlusion, Time = Now.AddMinutes(-10) },
|
||
new PumpAlarmEvent { Id = ObjectId.GenerateNewId(), DeviceId = dev, PatientId = PatientId, AlarmType = PumpEnum.AlarmType.Attention, Time = Now.AddMinutes(-5) }
|
||
};
|
||
|
||
await _repo.Collection.InsertManyAsync(batch);
|
||
|
||
var r1 = await _repo.FindByDeviceIdAsync(dev, from: Now.AddMinutes(-30), to: Now.AddMinutes(-15));
|
||
var r2 = await _repo.FindByDeviceIdAsync(dev, from: Now.AddMinutes(-12), to: Now.AddMinutes(-8));
|
||
var r3 = await _repo.FindByDeviceIdAsync(dev, from: Now.AddMinutes(-6), to: Now);
|
||
|
||
using (Assert.EnterMultipleScope())
|
||
{
|
||
var r1List = r1.ToList();
|
||
Assert.That(r1List, Has.Count.EqualTo(1)); // -20
|
||
var r2List = r2.ToList();
|
||
Assert.That(r2List, Has.Count.EqualTo(1)); // -10
|
||
var r3List = r3.ToList();
|
||
Assert.That(r3List, Has.Count.EqualTo(1)); // -5
|
||
}
|
||
}
|
||
|
||
}
|