using Xunit; using KArtSell.Modules.ModelOperations.ShadowRun.Events; namespace KArtSell.Integration.Tests; /// /// E2E async pipeline: Shadow Run → Outbox → Inbox → Consumer /// Tests event structure, routing, and idempotency contracts. /// Note: Database integration tests deferred (requires PostgreSQL fixture). /// public sealed class ShadowRunAsyncPipelineTests { [Fact] public void Event_CreatedWithAllGatesPassed_IsRouteableToConsumers() { // Arrange: Create event matching async pipeline contract var runId = Guid.NewGuid(); var modelId = Guid.NewGuid(); var correlationId = Guid.NewGuid(); var @event = new ShadowRunCompletedEvent( RunId: runId, ModelId: modelId, CorrelationId: correlationId, WindowStartDate: new DateOnly(2024, 1, 1), WindowEndDate: new DateOnly(2024, 8, 31), AllGatesPassed: true, TotalReturn: 0.15m, SharpeRatio: 1.8m, ProbOfBacktestOverfit: 0.12m, DailySharePercentile: 0.96m, ErrorMessage: null, CompletedAt: DateTime.UtcNow); // Assert: Event has all required fields for downstream routing Assert.Equal(runId, @event.RunId); Assert.Equal(modelId, @event.ModelId); Assert.Equal(correlationId, @event.CorrelationId); Assert.True(@event.AllGatesPassed); Assert.NotEqual(default(DateTime), @event.CompletedAt); } [Fact] public void Event_IdempotencyKey_EnsuresDuplicateDetection() { // Arrange: Same event, two instances var runId = Guid.NewGuid(); var event1 = new ShadowRunCompletedEvent( RunId: runId, ModelId: Guid.NewGuid(), CorrelationId: Guid.NewGuid(), WindowStartDate: new DateOnly(2024, 1, 1), WindowEndDate: new DateOnly(2024, 8, 31), AllGatesPassed: true, TotalReturn: 0.15m, SharpeRatio: 1.8m, ProbOfBacktestOverfit: 0.12m, DailySharePercentile: 0.96m, ErrorMessage: null, CompletedAt: DateTime.UtcNow); var event2 = new ShadowRunCompletedEvent( RunId: runId, ModelId: event1.ModelId, CorrelationId: event1.CorrelationId, WindowStartDate: event1.WindowStartDate, WindowEndDate: event1.WindowEndDate, AllGatesPassed: event1.AllGatesPassed, TotalReturn: event1.TotalReturn, SharpeRatio: event1.SharpeRatio, ProbOfBacktestOverfit: event1.ProbOfBacktestOverfit, DailySharePercentile: event1.DailySharePercentile, ErrorMessage: event1.ErrorMessage, CompletedAt: event1.CompletedAt); // Assert: Idempotency keys match (same event = duplicate detected) Assert.Equal(event1.IdempotencyKey, event2.IdempotencyKey); Assert.Equal($"{runId}#1", event1.IdempotencyKey); } [Fact] public void Pipeline_ApprovalQueueRoute_OnlyProcessesPassedGates() { // Arrange: Event with AllGatesPassed=false var @event = new ShadowRunCompletedEvent( RunId: Guid.NewGuid(), ModelId: Guid.NewGuid(), CorrelationId: Guid.NewGuid(), WindowStartDate: new DateOnly(2024, 1, 1), WindowEndDate: new DateOnly(2024, 8, 31), AllGatesPassed: false, // ← Failed validation TotalReturn: 0.05m, SharpeRatio: 0.8m, // < 1.0 target ProbOfBacktestOverfit: 0.35m, // > 20% limit DailySharePercentile: 0.90m, // < 95% target ErrorMessage: "PBO exceeded", CompletedAt: DateTime.UtcNow); // Assert: ApprovalQueueConsumer logic (should skip) // "Only create approval queue if all gates passed" Assert.False(@event.AllGatesPassed); // In production: ApprovalQueueConsumer.HandleAsync returns early } }