Files
vigilcare-clinical/VigilCareClinicalAPI.Tests/ClinicalDemographicsAndObservationTests.cs
T

222 lines
9.4 KiB
C#

using System.Net;
using System.Net.Http.Json;
using System.Text.Json;
using FluentAssertions;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using StackExchange.Redis;
[Collection("Integration")]
public class ClinicalDemographicsAndObservationTests : IAsyncLifetime
{
private readonly ApiFixture _fixture;
private readonly HttpClient _client;
private Guid _patientId;
private Guid _encounterId;
public ClinicalDemographicsAndObservationTests(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);
var patient = new Patient
{
Id = Guid.NewGuid(), Mrn = "MRN-P10-001", FirstName = "Phase10", LastName = "Test",
DateOfBirth = new DateOnly(1965, 3, 15), Gender = "F",
BloodType = BloodType.BPositive, Allergies = "Sulfa",
EmergencyContactName = "Test Contact", EmergencyContactPhone = "555-9999",
CreatedAt = DateTimeOffset.UtcNow
};
var encounter = new Encounter
{
Id = Guid.NewGuid(), PatientId = patient.Id, EncounterType = EncounterType.Inpatient,
Status = EncounterStatus.Active, Department = Department.Icu,
AttendingPhysician = "Dr. Phase10",
RoomBed = "ICU-7A", AdmissionReason = "Sepsis workup",
AdmittedAt = DateTimeOffset.UtcNow, CreatedAt = DateTimeOffset.UtcNow
};
db.Patients.Add(patient);
db.Encounters.Add(encounter);
db.AlertThresholds.AddRange(
new AlertThreshold { Id = Guid.NewGuid(), ObservationCode = "HEART_RATE",
DisplayName = "Heart Rate", Unit = "bpm",
CriticalLow = 30, WarningLow = 50, WarningHigh = 100, CriticalHigh = 150,
CreatedAt = DateTimeOffset.UtcNow },
new AlertThreshold { Id = Guid.NewGuid(), ObservationCode = "SYSTOLIC_BP",
DisplayName = "Systolic BP", Unit = "mmHg",
CriticalLow = 70, WarningLow = 90, WarningHigh = 160, CriticalHigh = 180,
CreatedAt = DateTimeOffset.UtcNow },
new AlertThreshold { Id = Guid.NewGuid(), ObservationCode = "DIASTOLIC_BP",
DisplayName = "Diastolic BP", Unit = "mmHg",
CriticalLow = 40, WarningLow = 60, WarningHigh = 90, CriticalHigh = 110,
CreatedAt = DateTimeOffset.UtcNow },
new AlertThreshold { Id = Guid.NewGuid(), ObservationCode = "LACTATE_MMOL_L",
DisplayName = "Serum Lactate", Unit = "mmol/L",
CriticalLow = null, WarningLow = null, WarningHigh = 2.0m, CriticalHigh = 4.0m,
CreatedAt = DateTimeOffset.UtcNow },
new AlertThreshold { Id = Guid.NewGuid(), ObservationCode = "AVPU",
DisplayName = "AVPU Consciousness", Unit = "score",
CriticalLow = null, WarningLow = null, WarningHigh = null, CriticalHigh = 2m,
CreatedAt = DateTimeOffset.UtcNow },
new AlertThreshold { Id = Guid.NewGuid(), ObservationCode = "SUPPLEMENTAL_O2",
DisplayName = "Supplemental O2", Unit = "flag",
CriticalLow = null, WarningLow = null, WarningHigh = null, CriticalHigh = null,
CreatedAt = DateTimeOffset.UtcNow },
new AlertThreshold { Id = Guid.NewGuid(), ObservationCode = "GLUCOSE_MG_DL",
DisplayName = "Blood Glucose", Unit = "mg/dL",
CriticalLow = 40, WarningLow = 70, WarningHigh = 180, CriticalHigh = 400,
CreatedAt = DateTimeOffset.UtcNow }
);
await db.SaveChangesAsync();
var redis = scope.ServiceProvider.GetRequiredService<IConnectionMultiplexer>();
var cache = redis.GetDatabase(1);
foreach (var t in await db.AlertThresholds.ToListAsync())
{
var json = JsonSerializer.Serialize(new
{
t.ObservationCode, t.CriticalLow, t.WarningLow, t.WarningHigh, t.CriticalHigh
});
await cache.StringSetAsync($"threshold:{t.ObservationCode}", json);
}
_patientId = patient.Id;
_encounterId = encounter.Id;
}
public Task DisposeAsync() => Task.CompletedTask;
[Fact]
public async Task CriticalSystolicBp_AlertCreated()
{
var resp = await _client.PostAsJsonAsync(
$"/api/v1/encounters/{_encounterId}/observations",
new BatchIngestRequest(new List<IngestObservationRequest>
{
new("SYSTOLIC_BP", 65, "mmHg", ObservationSource.Device, DateTimeOffset.UtcNow, null)
}));
resp.StatusCode.Should().Be(HttpStatusCode.Created);
var body = await resp.Content.ReadFromJsonAsync<JsonDocument>();
body!.RootElement.GetProperty("data").GetProperty("alertGenerated").GetBoolean()
.Should().BeTrue();
using var scope = _fixture.Services.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
var alert = await db.ClinicalAlerts.SingleAsync();
alert.AlertType.Should().Be(AlertType.CriticalSystolicBp);
alert.Severity.Should().Be(AlertSeverity.Critical);
}
[Fact]
public async Task AvpuUnresponsive_CriticalAlert()
{
var resp = await _client.PostAsJsonAsync(
$"/api/v1/encounters/{_encounterId}/observations",
new BatchIngestRequest(new List<IngestObservationRequest>
{
new("AVPU", 3, "score", ObservationSource.Manual, DateTimeOffset.UtcNow, null)
}));
resp.StatusCode.Should().Be(HttpStatusCode.Created);
using var scope = _fixture.Services.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
var alert = await db.ClinicalAlerts.SingleAsync();
alert.AlertType.Should().Be(AlertType.CriticalAvpu);
}
[Fact]
public async Task SupplementalO2_NoAlert()
{
var resp = await _client.PostAsJsonAsync(
$"/api/v1/encounters/{_encounterId}/observations",
new BatchIngestRequest(new List<IngestObservationRequest>
{
new("SUPPLEMENTAL_O2", 1, "flag", ObservationSource.Manual, DateTimeOffset.UtcNow, null)
}));
resp.StatusCode.Should().Be(HttpStatusCode.Created);
using var scope = _fixture.Services.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
(await db.ClinicalAlerts.CountAsync()).Should().Be(0);
}
[Fact]
public async Task CriticalGlucose_AlertCreated()
{
var resp = await _client.PostAsJsonAsync(
$"/api/v1/encounters/{_encounterId}/observations",
new BatchIngestRequest(new List<IngestObservationRequest>
{
new("GLUCOSE_MG_DL", 30, "mg/dL", ObservationSource.Lab, DateTimeOffset.UtcNow, null)
}));
resp.StatusCode.Should().Be(HttpStatusCode.Created);
using var scope = _fixture.Services.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
var alert = await db.ClinicalAlerts.SingleAsync();
alert.AlertType.Should().Be(AlertType.CriticalGlucoseMgDl);
}
[Fact]
public async Task PatientRegistration_ClinicalFields_RoundTrip()
{
var resp = await _client.PostAsJsonAsync("/api/v1/patients", new
{
firstName = "Demo",
lastName = "Patient",
dateOfBirth = "1990-06-15",
gender = "M",
bloodType = "AB-",
allergies = "Latex, Iodine",
emergencyContactName = "Demo Contact",
emergencyContactPhone = "555-1234"
});
resp.StatusCode.Should().Be(HttpStatusCode.Created);
var body = await resp.Content.ReadFromJsonAsync<JsonDocument>();
var data = body!.RootElement.GetProperty("data");
data.GetProperty("bloodType").GetString().Should().Be("AB-");
data.GetProperty("allergies").GetString().Should().Be("Latex, Iodine");
data.GetProperty("emergencyContactName").GetString().Should().Be("Demo Contact");
}
[Fact]
public async Task OpenEncounter_WithRoomBed_Success()
{
var patientResp = await _client.PostAsJsonAsync("/api/v1/patients", new
{
firstName = "Room", lastName = "Test",
dateOfBirth = "1985-01-01", gender = "F"
});
var patientBody = await patientResp.Content.ReadFromJsonAsync<JsonDocument>();
var newPatientId = patientBody!.RootElement.GetProperty("data").GetProperty("id").GetGuid();
var resp = await _client.PostAsJsonAsync($"/api/v1/patients/{newPatientId}/encounters", new
{
encounterType = "INPATIENT",
department = "ICU",
attendingPhysician = "Dr. Room",
roomBed = "ICU-3C",
admissionReason = "Acute respiratory distress"
});
resp.StatusCode.Should().Be(HttpStatusCode.Created);
var body = await resp.Content.ReadFromJsonAsync<JsonDocument>();
var data = body!.RootElement.GetProperty("data");
data.GetProperty("roomBed").GetString().Should().Be("ICU-3C");
data.GetProperty("admissionReason").GetString().Should().Be("Acute respiratory distress");
}
}