feature: In-App Simulation Runner (Backend)
CI / frontend (push) Canceled after 0s
CI / backend (push) Canceled after 8m32s

This commit is contained in:
voltsrage
2026-08-06 01:52:53 +08:00
parent 943d41339c
commit 24f45851e9
83 changed files with 3974 additions and 120 deletions
@@ -1,9 +1,11 @@
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.AspNetCore.TestHost;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
using StackExchange.Redis;
@@ -24,6 +26,12 @@ public class ApiFixture : WebApplicationFactory<Program>, IAsyncLifetime
public static int RabbitPort { get; } =
int.TryParse(Environment.GetEnvironmentVariable("RabbitMq__Port"), out var p) ? p : 5674;
public static string SimulationScenarioDirectory { get; } = Path.GetFullPath(Path.Combine(
AppContext.BaseDirectory, "Fixtures", "Scenarios"));
public TestSimulationClientFactory SimulationClientFactory =>
Services.GetRequiredService<TestSimulationClientFactory>();
// Override configuration to point at a test database — never run tests against
// the development database; a botched rollback could corrupt seed data.
protected override void ConfigureWebHost(IWebHostBuilder builder)
@@ -62,6 +70,16 @@ public class ApiFixture : WebApplicationFactory<Program>, IAsyncLifetime
});
config.AddJsonFile("appsettings.Testing.json", optional: true, reloadOnChange: false);
// Win over appsettings.Testing.json catalogue path / concurrency defaults.
config.AddInMemoryCollection(new Dictionary<string, string?>
{
["Simulation:Enabled"] = "true",
["Simulation:ScenarioDirectory"] = SimulationScenarioDirectory,
["Simulation:MaxConcurrentRuns"] = "2",
["Simulation:MaxSpeed"] = "600",
["Simulation:RunHistoryLimit"] = "50",
});
});
builder.ConfigureServices(services =>
@@ -77,6 +95,15 @@ public class ApiFixture : WebApplicationFactory<Program>, IAsyncLifetime
.AddScheme<AuthenticationSchemeOptions, TestingAuthHandler>(
TestingAuthHandler.SchemeName, _ => { });
});
builder.ConfigureTestServices(services =>
{
services.RemoveAll<ISimulationClientFactory>();
services.AddSingleton<TestSimulationClientFactory>(_ =>
new TestSimulationClientFactory(this));
services.AddSingleton<ISimulationClientFactory>(sp =>
sp.GetRequiredService<TestSimulationClientFactory>());
});
}
public async Task InitializeAsync()
@@ -0,0 +1,44 @@
{
"scenario": {
"id": "minimal-sim-01",
"name": "Minimal Simulation Fixture",
"description": "Three observation clusters for Phase 36 CI tests.",
"durationMinutes": 2,
"tags": ["test", "minimal"]
},
"patient": {
"firstName": "Sim",
"lastName": "Fixture",
"dateOfBirth": "1980-01-15",
"gender": "Female"
},
"encounter": {
"department": "GeneralMedicine",
"encounterType": "Inpatient",
"attendingPhysician": "Dr. Test",
"roomBed": "T-1",
"admissionReason": "Simulation fixture"
},
"events": [
{
"offsetMinutes": 0,
"type": "observation",
"data": { "code": "HEART_RATE", "value": 72, "unit": "bpm", "source": "Manual" }
},
{
"offsetMinutes": 0,
"type": "observation",
"data": { "code": "RESP_RATE", "value": 14, "unit": "/min", "source": "Manual" }
},
{
"offsetMinutes": 1,
"type": "observation",
"data": { "code": "HEART_RATE", "value": 74, "unit": "bpm", "source": "Manual" }
},
{
"offsetMinutes": 2,
"type": "observation",
"data": { "code": "HEART_RATE", "value": 70, "unit": "bpm", "source": "Manual" }
}
]
}