142 lines
6.0 KiB
C#
142 lines
6.0 KiB
C#
using System.Net;
|
|
using System.Net.Http.Json;
|
|
using FluentAssertions;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using StackExchange.Redis;
|
|
using VigilCare.ClinicalContracts.Sync;
|
|
|
|
[Collection("Integration")]
|
|
public class ClinicalSyncBatchTests : IAsyncLifetime
|
|
{
|
|
private readonly ApiFixture _fixture;
|
|
private readonly HttpClient _client;
|
|
private Guid _gatewayId;
|
|
private Guid _siteId;
|
|
private Guid _encounterId;
|
|
|
|
public ClinicalSyncBatchTests(ApiFixture fixture)
|
|
{
|
|
_fixture = fixture;
|
|
_client = fixture.CreateClient();
|
|
}
|
|
|
|
public async Task InitializeAsync()
|
|
{
|
|
using var scope = _fixture.Services.CreateScope();
|
|
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
|
await DbResetHelper.ResetAsync(db);
|
|
await GatewayRegistrySeeder.SeedAsync(db);
|
|
await DataSeeder.SeedAsync(db, scope.ServiceProvider.GetRequiredService<IConnectionMultiplexer>());
|
|
_gatewayId = GatewayRegistrySeeder.DemoGatewayId;
|
|
_siteId = GatewayRegistrySeeder.DemoSiteId;
|
|
_encounterId = await db.Encounters.Where(e => e.Status == EncounterStatus.Active)
|
|
.Select(e => e.Id).FirstAsync();
|
|
}
|
|
|
|
public Task DisposeAsync() => Task.CompletedTask;
|
|
|
|
private ClinicalSyncBatchRequest BuildBatch(int obsCount = 1)
|
|
{
|
|
var batchRef = Guid.NewGuid();
|
|
var observations = Enumerable.Range(0, obsCount).Select(i => new SyncedObservation(
|
|
Guid.NewGuid(), $"sync-key-{batchRef}-{i}", _encounterId,
|
|
"HEART_RATE", 80m + i, "bpm", "DEVICE", DateTimeOffset.UtcNow.AddMinutes(-i)
|
|
)).ToList();
|
|
|
|
return new ClinicalSyncBatchRequest(
|
|
batchRef, _gatewayId, _siteId, DateTimeOffset.UtcNow,
|
|
observations, [], [], []);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task UploadBatch_ReturnsReceived()
|
|
{
|
|
_client.WithGatewayApiKey(_gatewayId);
|
|
var resp = await _client.PostAsJsonAsync("/api/v1/sync/batches", BuildBatch());
|
|
resp.StatusCode.Should().Be(HttpStatusCode.Created);
|
|
|
|
using var scope = _fixture.Services.CreateScope();
|
|
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
|
(await db.OutboxEvents.CountAsync(e => e.Topic == "clinical.sync.batch_received"))
|
|
.Should().BeGreaterThan(0);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task DuplicateBatchReference_ReturnsExisting()
|
|
{
|
|
_client.WithGatewayApiKey(_gatewayId);
|
|
var batch = BuildBatch();
|
|
var first = await _client.PostAsJsonAsync("/api/v1/sync/batches", batch);
|
|
var firstBody = await first.Content.ReadFromJsonAsync<ApiResponse<ClinicalBatchUploadResponse>>();
|
|
|
|
var second = await _client.PostAsJsonAsync("/api/v1/sync/batches", batch);
|
|
var secondBody = await second.Content.ReadFromJsonAsync<ApiResponse<ClinicalBatchUploadResponse>>();
|
|
|
|
secondBody!.Data!.BatchId.Should().Be(firstBody!.Data!.BatchId);
|
|
using var scope = _fixture.Services.CreateScope();
|
|
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
|
(await db.ClinicalSyncBatches.CountAsync(b => b.BatchReference == batch.BatchReference))
|
|
.Should().Be(1);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Processor_AppliesObservations()
|
|
{
|
|
_client.WithGatewayApiKey(_gatewayId);
|
|
var upload = await _client.PostAsJsonAsync("/api/v1/sync/batches", BuildBatch(3));
|
|
var batchId = (await upload.Content.ReadFromJsonAsync<ApiResponse<ClinicalBatchUploadResponse>>())!.Data!.BatchId;
|
|
|
|
using var scope = _fixture.Services.CreateScope();
|
|
var processor = scope.ServiceProvider.GetRequiredService<ClinicalSyncBatchProcessor>();
|
|
await processor.ProcessBatchAsync(batchId, CancellationToken.None);
|
|
|
|
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
|
(await db.Observations.CountAsync()).Should().BeGreaterThanOrEqualTo(3);
|
|
(await db.OutboxEvents.CountAsync(e => e.Topic == "observation.recorded")).Should().BeGreaterThanOrEqualTo(3);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Processor_ConflictOnAckBeforeAlert()
|
|
{
|
|
var batchRef = Guid.NewGuid();
|
|
var clientAlertId = Guid.NewGuid();
|
|
var batch = new ClinicalSyncBatchRequest(
|
|
batchRef, _gatewayId, _siteId, DateTimeOffset.UtcNow,
|
|
[],
|
|
[],
|
|
[new SyncedAlertAcknowledgment(Guid.NewGuid(), clientAlertId, "RN-Smith",
|
|
DateTimeOffset.UtcNow, null)],
|
|
[]);
|
|
|
|
_client.WithGatewayApiKey(_gatewayId);
|
|
var upload = await _client.PostAsJsonAsync("/api/v1/sync/batches", batch);
|
|
var batchId = (await upload.Content.ReadFromJsonAsync<ApiResponse<ClinicalBatchUploadResponse>>())!.Data!.BatchId;
|
|
|
|
using var scope = _fixture.Services.CreateScope();
|
|
await scope.ServiceProvider.GetRequiredService<ClinicalSyncBatchProcessor>()
|
|
.ProcessBatchAsync(batchId, CancellationToken.None);
|
|
|
|
var status = await _client.GetAsync($"/api/v1/sync/batches/{batchId}");
|
|
var body = await status.Content.ReadFromJsonAsync<ApiResponse<ClinicalBatchStatusResponse>>();
|
|
body!.Data!.Status.Should().Be("CONFLICT");
|
|
body.Data.Conflicts.Should().ContainSingle(c => c.ConflictReason == "ALERT_NOT_YET_SYNCED");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Processor_UpdatesGatewayLastSyncAt()
|
|
{
|
|
_client.WithGatewayApiKey(_gatewayId);
|
|
var upload = await _client.PostAsJsonAsync("/api/v1/sync/batches", BuildBatch());
|
|
var batchId = (await upload.Content.ReadFromJsonAsync<ApiResponse<ClinicalBatchUploadResponse>>())!.Data!.BatchId;
|
|
|
|
using var scope = _fixture.Services.CreateScope();
|
|
await scope.ServiceProvider.GetRequiredService<ClinicalSyncBatchProcessor>()
|
|
.ProcessBatchAsync(batchId, CancellationToken.None);
|
|
|
|
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
|
var gw = await db.WardGateways.FindAsync(_gatewayId);
|
|
gw!.LastSyncAt.Should().NotBeNull();
|
|
gw.ReportedBufferDepth.Should().Be(0);
|
|
}
|
|
} |