using System.Net; using System.Net.Http.Json; using System.Text.Json; using FluentAssertions; using Microsoft.Extensions.DependencyInjection; using StackExchange.Redis; [Collection("Integration")] public class QsofaCurrentTests : IAsyncLifetime { private readonly ApiFixture _fixture; private readonly HttpClient _client; private Guid _encounterId; private Guid _patientId; public QsofaCurrentTests(ApiFixture fixture) { _fixture = fixture; _client = fixture.CreateClient(); } public async Task InitializeAsync() { using var scope = _fixture.Services.CreateScope(); var db = scope.ServiceProvider.GetRequiredService(); await DbResetHelper.ResetAsync(db); var patient = new Patient { Id = Guid.NewGuid(), Mrn = "MRN-QSOFA-API", FirstName = "qSOFA", LastName = "Api", DateOfBirth = new DateOnly(1960, 1, 1), Gender = "M", CreatedAt = DateTimeOffset.UtcNow }; var encounter = new Encounter { Id = Guid.NewGuid(), PatientId = patient.Id, EncounterType = EncounterType.Inpatient, Status = EncounterStatus.Active, Department = Department.Icu, AttendingPhysician = "Dr. qSOFA", AdmittedAt = DateTimeOffset.UtcNow, CreatedAt = DateTimeOffset.UtcNow }; db.Patients.Add(patient); db.Encounters.Add(encounter); await db.SaveChangesAsync(); _patientId = patient.Id; _encounterId = encounter.Id; var redis = scope.ServiceProvider.GetRequiredService(); var cache = redis.GetDatabase(); foreach (var key in QsofaCalculator.AllCriterionKeys(_encounterId)) await cache.KeyDeleteAsync(key); } public Task DisposeAsync() => Task.CompletedTask; [Fact] public async Task Current_AfterBaselineVitals_ReturnsZeroCriteria() { using var scope = _fixture.Services.CreateScope(); var detector = scope.ServiceProvider.GetRequiredService(); await detector.ProcessObservationAsync(_encounterId, _patientId, "RESP_RATE", 16m); var resp = await _client.GetAsync($"/api/v1/encounters/{_encounterId}/qsofa/current"); resp.StatusCode.Should().Be(HttpStatusCode.OK); var body = await resp.Content.ReadFromJsonAsync(); body!.RootElement.GetProperty("data").GetProperty("activeCriteria").GetInt32() .Should().Be(0); } [Fact] public async Task Current_AfterTwoCriteriaMet_ReturnsTwo() { using var scope = _fixture.Services.CreateScope(); var detector = scope.ServiceProvider.GetRequiredService(); await detector.ProcessObservationAsync(_encounterId, _patientId, "RESP_RATE", 24m); await detector.ProcessObservationAsync(_encounterId, _patientId, "SYSTOLIC_BP", 95m); var resp = await _client.GetAsync($"/api/v1/encounters/{_encounterId}/qsofa/current"); resp.StatusCode.Should().Be(HttpStatusCode.OK); var data = (await resp.Content.ReadFromJsonAsync())! .RootElement.GetProperty("data"); data.GetProperty("activeCriteria").GetInt32().Should().Be(2); data.GetProperty("criteria").GetProperty("respRate").GetDecimal().Should().Be(24m); data.GetProperty("criteria").GetProperty("systolicBp").GetDecimal().Should().Be(95m); } [Fact] public async Task Current_UnknownEncounter_Returns404() { var resp = await _client.GetAsync($"/api/v1/encounters/{Guid.NewGuid()}/qsofa/current"); resp.StatusCode.Should().Be(HttpStatusCode.NotFound); } }