using System.Net; using System.Net.Http.Json; using System.Text.Json; using FluentAssertions; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; /// /// Degraded-mode tests — central is unreachable; gateway clinical path remains authoritative locally. /// [Collection("GatewayIntegration")] public class WardGatewayPartitionTests : IAsyncLifetime { private readonly GatewayApiFixture _fixture; private readonly HttpClient _client; private Guid _encounterId; public WardGatewayPartitionTests(GatewayApiFixture fixture) { _fixture = fixture; _client = fixture.CreateClient(); } public async Task InitializeAsync() { (_, _encounterId) = await GatewayTestSeeder.SeedActiveEncounterAsync(_fixture.Services); } public Task DisposeAsync() => Task.CompletedTask; [Fact] public async Task Partition_CriticalIngest_CreatesLocalAlertAndBuffer() { var resp = await _client.PostAsJsonAsync( $"/api/v1/encounters/{_encounterId}/observations", new { observationCode = "HEART_RATE", value = 160, unit = "bpm", source = "DEVICE", recordedAt = DateTimeOffset.UtcNow, idempotencyKey = $"partition-{Guid.NewGuid()}" }); resp.StatusCode.Should().Be(HttpStatusCode.Created); using var scope = _fixture.Services.CreateScope(); var db = scope.ServiceProvider.GetRequiredService(); var alerts = await db.ClinicalAlerts.Where(a => a.EncounterId == _encounterId).ToListAsync(); alerts.Should().ContainSingle(); alerts[0].Severity.Should().Be(AlertSeverity.Critical); (await db.BufferedSyncItems.CountAsync(b => !b.Synced)).Should().BeGreaterThanOrEqualTo(2); } [Fact] public async Task Partition_ListAlertsViaGatewayApi() { await _client.PostAsJsonAsync( $"/api/v1/encounters/{_encounterId}/observations", new { observationCode = "HEART_RATE", value = 160, unit = "bpm", source = "DEVICE", recordedAt = DateTimeOffset.UtcNow, idempotencyKey = $"partition-list-{Guid.NewGuid()}" }); var resp = await _client.GetAsync($"/api/v1/encounters/{_encounterId}/alerts"); resp.StatusCode.Should().Be(HttpStatusCode.OK); var doc = await resp.Content.ReadFromJsonAsync(); doc!.RootElement.GetProperty("data").GetProperty("items").GetArrayLength() .Should().BeGreaterThan(0); } [Fact] public async Task Partition_ListActiveEncounters_FromLocalReplica() { var resp = await _client.GetAsync("/api/v1/encounters?status=ACTIVE&department=ICU"); resp.StatusCode.Should().Be(HttpStatusCode.OK); var doc = await resp.Content.ReadFromJsonAsync(); var items = doc!.RootElement.GetProperty("data").GetProperty("items"); items.GetArrayLength().Should().BeGreaterThan(0); items[0].GetProperty("encounterId").GetGuid().Should().Be(_encounterId); } [Fact] public async Task Partition_AcknowledgeAndResolve_BuffersLocally() { await _client.PostAsJsonAsync( $"/api/v1/encounters/{_encounterId}/observations", new { observationCode = "HEART_RATE", value = 160, unit = "bpm", source = "DEVICE", recordedAt = DateTimeOffset.UtcNow, idempotencyKey = $"partition-lifecycle-{Guid.NewGuid()}" }); var alertsResp = await _client.GetAsync($"/api/v1/encounters/{_encounterId}/alerts"); var alertId = (await alertsResp.Content.ReadFromJsonAsync())! .RootElement.GetProperty("data").GetProperty("items")[0].GetProperty("id").GetGuid(); (await _client.PostAsJsonAsync( $"/api/v1/alerts/{alertId}/acknowledge", new AcknowledgeAlertRequest("Partition ack"))).StatusCode.Should().Be(HttpStatusCode.OK); (await _client.PostAsync($"/api/v1/alerts/{alertId}/resolve", null)) .StatusCode.Should().Be(HttpStatusCode.OK); using var scope = _fixture.Services.CreateScope(); var db = scope.ServiceProvider.GetRequiredService(); var alert = await db.ClinicalAlerts.FindAsync(alertId); alert!.Status.Should().Be(AlertStatus.Resolved); (await db.BufferedSyncItems.CountAsync(b => !b.Synced)).Should().BeGreaterThanOrEqualTo(4); } }