using System.Net; using System.Net.Http.Json; using System.Text.Json; using FluentAssertions; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using StackExchange.Redis; [Collection("Integration")] public class SimulationEndpointTests : IAsyncLifetime { private readonly ApiFixture _fixture; private readonly HttpClient _client; public SimulationEndpointTests(ApiFixture fixture) { _fixture = fixture; _client = fixture.CreateClient(); } public async Task InitializeAsync() { using var scope = _fixture.Services.CreateScope(); var db = scope.ServiceProvider.GetRequiredService(); var redis = scope.ServiceProvider.GetRequiredService(); await DbResetHelper.ResetAsync(db); await DataSeeder.SeedThresholdsOnlyAsync(db, redis); _fixture.SimulationClientFactory.HangOnCreate = false; _fixture.SimulationClientFactory.FailOnCreate = false; _client.DefaultRequestHeaders.Remove("X-Test-Role"); _client.DefaultRequestHeaders.Remove("X-Test-User-Id"); _client.AsAdmin(); } public Task DisposeAsync() => Task.CompletedTask; [Fact] public async Task Config_ReturnsEnabledFalse_WhenDisabled() { using var factory = _fixture.WithWebHostBuilder(builder => { builder.ConfigureAppConfiguration((_, config) => { config.AddInMemoryCollection(new Dictionary { ["Simulation:Enabled"] = "false", }); }); }); var client = factory.CreateClient(); client.AsAdmin(); var resp = await client.GetAsync("/api/v1/simulation/config"); resp.StatusCode.Should().Be(HttpStatusCode.OK); var body = await resp.Content.ReadFromJsonAsync(); body.GetProperty("data").GetProperty("enabled").GetBoolean().Should().BeFalse(); } [Fact] public async Task Scenarios_WhenDisabled_Returns404() { using var factory = _fixture.WithWebHostBuilder(builder => { builder.ConfigureAppConfiguration((_, config) => { config.AddInMemoryCollection(new Dictionary { ["Simulation:Enabled"] = "false", }); }); }); var client = factory.CreateClient(); client.AsAdmin(); var resp = await client.GetAsync("/api/v1/simulation/scenarios"); resp.StatusCode.Should().Be(HttpStatusCode.NotFound); } [Fact] public async Task Scenarios_AsNurse_Returns200() { _client.AsNurse(); var resp = await _client.GetAsync("/api/v1/simulation/scenarios"); resp.StatusCode.Should().Be(HttpStatusCode.OK); var body = await resp.Content.ReadFromJsonAsync(); var items = body.GetProperty("data"); items.GetArrayLength().Should().BeGreaterThan(0); items[0].GetProperty("id").GetString().Should().Be("minimal-sim-01"); } [Fact] public async Task StartRun_AsIntegrationRole_Returns403() { _client.AsIntegration(); var resp = await _client.PostAsJsonAsync("/api/v1/simulation/runs", new { scenarioId = "minimal-sim-01", speed = 600 }); resp.StatusCode.Should().Be(HttpStatusCode.Forbidden); } [Fact] public async Task StartRun_WritesAuditLog() { _client.AsPhysician(); var resp = await _client.PostAsJsonAsync("/api/v1/simulation/runs", new { scenarioId = "minimal-sim-01", speed = 600 }); resp.StatusCode.Should().Be(HttpStatusCode.Created); var body = await resp.Content.ReadFromJsonAsync(); var runId = body.GetProperty("data").GetProperty("runId").GetGuid(); using var scope = _fixture.Services.CreateScope(); var db = scope.ServiceProvider.GetRequiredService(); var audit = await db.ClinicalAuditLogs .Where(a => a.Action == AuditAction.SimulationRunStarted && a.EntityId == runId) .SingleOrDefaultAsync(); audit.Should().NotBeNull(); audit!.EntityType.Should().Be("SimulationRun"); } [Fact] public async Task Config_WhenEnabled_ReturnsLimits() { _client.AsNurse(); var resp = await _client.GetAsync("/api/v1/simulation/config"); resp.StatusCode.Should().Be(HttpStatusCode.OK); var body = await resp.Content.ReadFromJsonAsync(); var data = body.GetProperty("data"); data.GetProperty("enabled").GetBoolean().Should().BeTrue(); data.GetProperty("maxSpeed").GetDouble().Should().Be(600); data.GetProperty("maxConcurrentRuns").GetInt32().Should().Be(2); } }