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;
///
/// Integration test fixture for the .
///
///
/// Marked with the Integration category, so these tests target interactions with external dependencies rather than isolated unit logic.
///
///
[TestFixture]
[Category("Integration")]
public class PumpObservationRepositoryTest
{
private PumpObservationRepository _repo;
private IOptions _apiSettings;
private static readonly DateTime Now = DateTime.UtcNow;
private static readonly ObjectId PatientId = ObjectId.GenerateNewId();
// -------------------------------------------------------------------
// INIT – SETUP DE LA COLECCIÓN CON DATOS DE PRUEBA
// -------------------------------------------------------------------
///
/// One-time setup that prepares the integration test environment for the by clearing the
/// pump_observations collection, recreating it, building the indexes, and seeding it with three sample
/// records belonging to . The setup then asserts that exactly three documents were
/// inserted into the collection.
///
///
[OneTimeSetUp]
public async Task Init()
{
_apiSettings = Options.Create(new ApiSettings
{
PumpObservations = "pump_observations"
});
await IntegrationDb.Database.DropCollectionAsync("pump_observations");
await IntegrationDb.Database.CreateCollectionAsync("pump_observations");
_repo = new PumpObservationRepository(
_apiSettings,
IntegrationDb.Database
);
await _repo.CreateIndexes();
var samples = new List
{
new()
{
Id = ObjectId.GenerateNewId(),
PatientId = PatientId,
DeviceId = "Device-A",
Name = "PumpX",
Time = Now.AddSeconds(-1),
MessageType = PumpEnum.PumpMessageType.Observation
},
new()
{
Id = ObjectId.GenerateNewId(),
PatientId = PatientId,
DeviceId = "Device-A",
Name = "PumpY",
Time = Now.AddSeconds(-2),
MessageType = PumpEnum.PumpMessageType.Observation
},
new()
{
Id = ObjectId.GenerateNewId(),
PatientId = PatientId,
DeviceId = "Device-A",
Name = "PumpX",
Time = Now.AddSeconds(-3),
MessageType = PumpEnum.PumpMessageType.Observation
}
};
await _repo.InsertManyAsync(samples);
var count = await _repo.Collection.CountDocumentsAsync(_ => true);
Assert.That(count, Is.EqualTo(3));
}
// -------------------------------------------------------------------
// FIND LAST OBSERVATIONS (LÍMITE = 2)
// -------------------------------------------------------------------
///
/// Verifies that the repository returns exactly two observations for the specified patient and "PumpX", and that the returned list is ordered with the first observation's time greater than or equal to the second observation's time.
///
///
[Test]
public async Task FindLastObservations_ReturnsTwo()
{
var list = await _repo.FindLastObservations(PatientId, "PumpX");
Assert.That(list, Is.Not.Null);
Assert.That(list, Has.Count.EqualTo(2));
Assert.That(list[0].Time, Is.GreaterThanOrEqualTo(list[1].Time));
}
// -------------------------------------------------------------------
// INSERT
// -------------------------------------------------------------------
///
/// Verifies that persists a new so that a subsequent lookup by returns the stored entity.
///
///
[Test]
public async Task InsertAsync_Works()
{
var pump = new PumpObservation
{
Id = ObjectId.GenerateNewId(),
PatientId = ObjectId.GenerateNewId(),
DeviceId = "Device-B",
Time = Now
};
await _repo.InsertAsync(pump);
var found = await _repo.Collection.FindAsync(x => x.Id == pump.Id);
Assert.That(found.FirstOrDefault(), Is.Not.Null);
}
///
/// Verifies that the repository successfully inserts a batch of documents and that all items can be retrieved by their identifiers from the underlying collection.
///
///
[Test]
public async Task InsertManyAsync_Works()
{
var list = new List
{
new() { Id = ObjectId.GenerateNewId(), DeviceId="D", Time=Now },
new() { Id = ObjectId.GenerateNewId(), DeviceId="D", Time=Now }
};
await _repo.InsertManyAsync(list);
var count = await _repo.Collection.CountDocumentsAsync(x => list.Select(y => y.Id).Contains(x.Id));
Assert.That(count, Is.EqualTo(2));
}
// -------------------------------------------------------------------
// FIND METHODS
// -------------------------------------------------------------------
///
/// Verifies that the FindByDeviceIdAsync repository method returns a non-empty collection of pump observations for the specified device, ordered by time in descending order with the most recent observation first.
///
///
[Test]
public async Task FindByDeviceIdAsync_ReturnsOrdered()
{
var result = await _repo.FindByDeviceIdAsync("Device-A");
var pumpObservations = result.ToList();
using (Assert.EnterMultipleScope())
{
Assert.That(pumpObservations, Is.Not.Empty);
Assert.That(pumpObservations.First().Time, Is.GreaterThanOrEqualTo(pumpObservations.Last().Time));
}
}
///
/// Verifies that the repository's FindByPatientAsync call returns a non-empty collection of observations for the configured PatientId, ordered by time in descending order so that the most recent observation appears first.
///
///
[Test]
public async Task FindByPatientAsync_ReturnsOrdered()
{
var result = await _repo.FindByPatientAsync(PatientId);
var pumpObservations = result.ToList();
using (Assert.EnterMultipleScope())
{
Assert.That(pumpObservations, Is.Not.Empty);
Assert.That(pumpObservations.First().Time, Is.GreaterThanOrEqualTo(pumpObservations.Last().Time));
}
}
///
/// Verifies that returns a non-null observation whose matches the device identifier passed in.
///
///
[Test]
public async Task FindLastByDeviceIdAsync_Works()
{
var obs = await _repo.FindLastByDeviceIdAsync("Device-A");
Assert.That(obs, Is.Not.Null);
Assert.That(obs.DeviceId, Is.EqualTo("Device-A"));
}
///
/// Verifies that the repository lookup by patient identifier returns a non-empty collection of results.
///
///
[Test]
public async Task FindByPatientId_Works()
{
var list = await _repo.FindByPatientId(PatientId);
Assert.That(list.Any(), Is.True);
}
// -------------------------------------------------------------------
// AGGREGATED LAST OBSERVATIONS
// -------------------------------------------------------------------
///
/// Verifies that the aggregated last observations retrieved for the test PatientId are distinct by name, ensuring the resulting collection is non-empty and contains at most two entries.
///
///
///
[Test]
public async Task AggregatedPatientLastObservations_DistinctByName()
{
var result = await _repo.AggregatedPatientLastObservations(PatientId);
using (Assert.EnterMultipleScope())
{
Assert.That(result, Is.Not.Empty);
Assert.That(result, Has.Count.LessThanOrEqualTo(2));
}
}
// -------------------------------------------------------------------
// DELETE METHODS
// -------------------------------------------------------------------
///
/// Verifies that records associated with a given patient can be removed via DeleteByPatientId, ensuring no matching observations remain in the repository after deletion.
///
///
[Test]
public async Task DeleteByPatientId_Works()
{
var pid = ObjectId.GenerateNewId();
await _repo.InsertAsync(new PumpObservation
{
Id = ObjectId.GenerateNewId(),
PatientId = pid,
DeviceId = "Z",
Time = Now
});
await _repo.DeleteByPatientId(pid);
var list = await _repo.FindByPatientId(pid);
Assert.That(list.Any(), Is.False);
}
///
/// Tests that DeleteOlderThanDaysAsync successfully deletes pump observations older than the given number of days, asserting that the returned count is at least one when matching data exists.
///
///
[Test]
public async Task DeleteOlderThanDaysAsync_Works()
{
var pid = ObjectId.GenerateNewId();
await _repo.InsertAsync(new PumpObservation
{
Id = ObjectId.GenerateNewId(),
PatientId = pid,
Name = "TestOld",
Time = Now.AddDays(-10)
});
var deleted = await _repo.DeleteOlderThanDaysAsync(5);
Assert.That(deleted, Is.GreaterThanOrEqualTo(1));
}
///
/// Verifies that the older-observation pruning operation retains only the requested number of newest entries for the given name and returns the count of deleted items.
/// Inserts five timed observations and asserts that exactly three older entries are removed when two are kept.
///
///
[Test]
public async Task DeleteOlderNumberAsync_Works()
{
const string name = "HistoryPump";
var obs = Enumerable.Range(0, 5).Select(i =>
new PumpObservation
{
Id = ObjectId.GenerateNewId(),
DeviceId = "H",
Name = name,
Time = Now.AddSeconds(-i)
});
await _repo.InsertManyAsync(obs);
var deleted = await _repo.DeleteOlderNumberAsync(name, 2);
Assert.That(deleted, Is.EqualTo(3));
}
///
/// Verifies that correctly deletes the older records while preserving the most recent entries, returning the expected count of deleted items.
///
///
///
[Test]
public async Task DeleteKeepLastNAsync_Works()
{
var items = Enumerable.Range(0, 8).Select(i =>
new PumpObservation
{
Id = ObjectId.GenerateNewId(),
DeviceId = "Device-Clean",
Time = Now.AddSeconds(-i)
});
await _repo.InsertManyAsync(items);
var deleted = await _repo.DeleteKeepLastNAsync(3);
Assert.That(deleted, Is.EqualTo(5));
}
// -------------------------------------------------------------------
// UPDATE METHOD
// -------------------------------------------------------------------
///
/// Verifies that correctly updates the field of all matching records, replacing the old value with a new one, and reports the number of documents modified.
///
///
[Test]
public async Task UpdateManyObjectIdByFieldAsync_Works()
{
var oldId = ObjectId.GenerateNewId();
var newId = ObjectId.GenerateNewId();
await _repo.InsertAsync(new PumpObservation
{
Id = ObjectId.GenerateNewId(),
PatientId = oldId,
DeviceId = "UpdateTest",
Time = Now
});
var updated = await _repo.UpdateManyObjectIdByFieldAsync("PatientId", newId, oldId);
Assert.That(updated, Is.EqualTo(1));
var found = await _repo.FindByPatientId(newId);
Assert.That(found.Any(), Is.True);
}
// -------------------------------------------------------------------
// AGGREGATION: LAST OBSERVATION TIME PER PATIENT
// -------------------------------------------------------------------
///
/// Verifies that the repository returns a non-null dictionary containing the last patient observation
/// time for the specified patient, and that the recorded timestamp is not in the future relative to
/// .
///
///
[Test]
public async Task FindAllLastPatientObservationTimeAsync_Works()
{
var dict = await _repo.FindAllLastPatientObservationTimeAsync();
Assert.That(dict, Is.Not.Null);
using (Assert.EnterMultipleScope())
{
Assert.That(dict.ContainsKey(PatientId), Is.True);
Assert.That(dict[PatientId], Is.LessThanOrEqualTo(DateTime.UtcNow));
}
}
///
/// Verifies that looking up records by device ID with a date range entirely outside the stored data window returns an empty collection.
///
///
[Test]
public async Task FindByDeviceIdAsync_EmptyWhenOutOfDateRange()
{
var from = Now.AddYears(-1);
var to = Now.AddYears(-1).AddHours(1);
var result = await _repo.FindByDeviceIdAsync("Device-A", from, to);
Assert.That(result, Is.Empty);
}
///
/// Verifies that the repository's FindByPatientAsync method respects the limit parameter when retrieving records for a patient, asserting that the returned collection contains exactly one item when the limit is set to one.
///
///
[Test]
public async Task FindByPatientAsync_RespectsLimit()
{
var result = await _repo.FindByPatientAsync(PatientId, limit: 1);
var list = result.ToList();
Assert.That(list, Has.Count.EqualTo(1));
}
///
/// Verifies that FindLastByDeviceIdAsync returns null when no observation exists for the supplied device identifier.
///
///
[Test]
public async Task FindLastByDeviceIdAsync_ReturnsNullWhenNotExists()
{
var obs = await _repo.FindLastByDeviceIdAsync("Device-DoesNotExist");
Assert.That(obs, Is.Null);
}
///
/// Verifies that InsertManyAsync completes without throwing when invoked with an empty collection of .
///
///
[Test]
public async Task InsertManyAsync_IgnoresEmptyList()
{
var empty = new List();
Func act = () => _repo.InsertManyAsync(empty);
Assert.That(act, Throws.Nothing);
}
///
/// Verifies that DeleteOlderNumberAsync leaves the stored data unchanged and returns zero when the total number of entries for the given device name is below the specified maxCount threshold.
///
///
[Test]
public async Task DeleteOlderNumberAsync_DoesNothingWhenCountBelowLimit()
{
const string name = "LimitTestPump";
var obs = new PumpObservation
{
Id = ObjectId.GenerateNewId(),
DeviceId = "Device-X",
Name = name,
Time = Now
};
await _repo.InsertAsync(obs);
var deleted = await _repo.DeleteOlderNumberAsync(name, maxCount: 5);
Assert.That(deleted, Is.Zero);
}
///
/// Verifies that the repository cleanup operation preserves recent observations by inserting a whose timestamp is one minute in the past and asserting that the deletion count returned by DeleteOlderThanDaysAsync is zero.
///
///
[Test]
public async Task DeleteOlderThanDaysAsync_DoesNotDeleteRecentObservations()
{
var obs = new PumpObservation
{
Id = ObjectId.GenerateNewId(),
PatientId = PatientId,
DeviceId = "Device-Recent",
Name = "RecentPump",
Time = Now.AddMinutes(-1) // reciente
};
await _repo.InsertAsync(obs);
var deleted = await _repo.DeleteOlderThanDaysAsync(10);
Assert.That(deleted, Is.Zero);
}
///
/// Verifies that deduplicates entries that share the same and for a given patient, returning only a single entry per code/name combination.
///
///
[Test]
public async Task AggregatedPatientLastObservations_RemovesDuplicatesByCodeAndName()
{
var patient = ObjectId.GenerateNewId();
var p1 = new PumpObservation
{
Id = ObjectId.GenerateNewId(),
PatientId = patient,
DeviceId = "DevA",
Code = "C1",
Name = "PumpA",
Time = Now
};
var p2 = new PumpObservation
{
Id = ObjectId.GenerateNewId(),
PatientId = patient,
DeviceId = "DevA",
Code = "C1",
Name = "PumpA",
Time = Now.AddSeconds(-1)
};
await _repo.InsertManyAsync([p1, p2]);
var result = await _repo.AggregatedPatientLastObservations(patient);
Assert.That(result, Has.Count.EqualTo(1));
}
}