using FluentAssertions; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using StackExchange.Redis; [Collection("Integration")] public class SimulationSessionTests : IAsyncLifetime { private readonly ApiFixture _fixture; private ISimulationRunner _runner = null!; private TestSimulationClientFactory _clientFactory = null!; public SimulationSessionTests(ApiFixture fixture) => _fixture = fixture; 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); _runner = _fixture.Services.GetRequiredService(); _clientFactory = _fixture.SimulationClientFactory; _clientFactory.HangOnCreate = false; _clientFactory.FailOnCreate = false; foreach (var run in _runner.ListRuns()) _runner.Cancel(run.RunId); await WaitForAsync(() => !_runner.HasActiveRuns()); _runner.ClearRegistry(); } public Task DisposeAsync() { _clientFactory.HangOnCreate = false; _clientFactory.FailOnCreate = false; return Task.CompletedTask; } [Fact] public async Task StartSession_StartsAllScenarios() { _clientFactory.HangOnCreate = true; var session = await _runner.StartSessionAsync( "session-test-two", 60, "tester", CancellationToken.None); session.RunIds.Should().HaveCount(2); _runner.ListRuns().Count(r => r.Status is SimulationRunStatus.Pending or SimulationRunStatus.Running) .Should().Be(2); foreach (var runId in session.RunIds) _runner.Cancel(runId); await WaitForAsync(() => !_runner.HasActiveRuns()); } [Fact] public async Task StartSession_ExceedingConcurrency_StartsNothing() { _clientFactory.HangOnCreate = true; var act = () => _runner.StartSessionAsync( "session-test-three", 60, "tester", CancellationToken.None); var ex = await act.Should().ThrowAsync(); ex.Which.ErrorCode.Should().Be("SIMULATION_CONCURRENCY_LIMIT"); _runner.ListRuns().Should().BeEmpty(); using var scope = _fixture.Services.CreateScope(); var db = scope.ServiceProvider.GetRequiredService(); (await db.SimulationRuns.CountAsync()).Should().Be(0); } [Fact] public async Task StartSession_TagsRunsWithSessionId() { _clientFactory.HangOnCreate = true; var session = await _runner.StartSessionAsync( "session-test-one", null, "tester", CancellationToken.None); session.SessionId.Should().Be("session-test-one"); var run = _runner.GetRun(session.RunIds[0])!; run.SessionId.Should().Be("session-test-one"); run.Speed.Should().Be(120); // defaultSpeed from fixture manifest using var scope = _fixture.Services.CreateScope(); var db = scope.ServiceProvider.GetRequiredService(); var row = await db.SimulationRuns.SingleAsync(r => r.Id == run.RunId); row.SessionId.Should().Be("session-test-one"); _runner.Cancel(run.RunId); await WaitForAsync(() => !_runner.HasActiveRuns()); } private static async Task WaitForAsync(Func condition, TimeSpan? timeout = null) { var deadline = DateTimeOffset.UtcNow + (timeout ?? TimeSpan.FromSeconds(15)); while (DateTimeOffset.UtcNow < deadline) { if (condition()) return; await Task.Delay(25); } throw new TimeoutException("Condition was not met within the timeout."); } }