using Xunit; using KArtSell.Host.Features.ShadowRun; namespace KArtSell.Integration.Tests; /// /// Tests for shadow run polling endpoint: in-progress, complete, failed scenarios. /// public sealed class GetShadowRunPollingTests { [Fact] public void Query_InProgressStatus_ReturnsWithoutMetrics() { // Arrange var response = new GetShadowRunResponse( RunId: Guid.NewGuid(), ModelId: Guid.NewGuid(), Status: "DataBackfill", CreatedAt: DateTimeOffset.UtcNow.AddHours(-1), CompletedAt: null, Metrics: null, ValidationGates: null, ErrorMessage: null); // Assert Assert.Equal("DataBackfill", response.Status); Assert.Null(response.CompletedAt); Assert.Null(response.Metrics); Assert.Null(response.ValidationGates); } [Fact] public void Query_CompleteStatus_ReturnsWithMetrics() { // Arrange var runId = Guid.NewGuid(); var modelId = Guid.NewGuid(); var metrics = new ShadowRunMetricsDto( TotalReturn: 0.15m, SharpeRatio: 1.2m, CalmurRatio: 0.5m, MaximumDrawdown: 0.08m, WinRate: 0.55m, ProbOfBacktestOverfit: 0.15m, DailySharePercentile: 0.95m, TradingDays: 252); var gates = new ValidationGatesDto( PboUnder20: true, DsrAbove95: true, CostTwoXPositive: true, AllGatesPassed: true); var response = new GetShadowRunResponse( RunId: runId, ModelId: modelId, Status: "EvaluationComplete", CreatedAt: DateTimeOffset.UtcNow.AddHours(-2), CompletedAt: DateTimeOffset.UtcNow.AddHours(-1), Metrics: metrics, ValidationGates: gates, ErrorMessage: null); // Assert Assert.Equal("EvaluationComplete", response.Status); Assert.NotNull(response.CompletedAt); Assert.NotNull(response.Metrics); Assert.Equal(0.15m, response.Metrics!.ProbOfBacktestOverfit); Assert.NotNull(response.ValidationGates); Assert.True(response.ValidationGates!.AllGatesPassed); } [Fact] public void Query_FailedStatus_ReturnsWithErrorMessage() { // Arrange var response = new GetShadowRunResponse( RunId: Guid.NewGuid(), ModelId: Guid.NewGuid(), Status: "Failed", CreatedAt: DateTimeOffset.UtcNow.AddHours(-1), CompletedAt: DateTimeOffset.UtcNow.AddMinutes(-30), Metrics: null, ValidationGates: null, ErrorMessage: "OHLCV data fetch failed: HTTP 503 Service Unavailable"); // Assert Assert.Equal("Failed", response.Status); Assert.NotNull(response.ErrorMessage); Assert.Contains("HTTP 503", response.ErrorMessage); } [Fact] public void Response_PartialGateFail_ReturnsWithGates() { // Arrange var gates = new ValidationGatesDto( PboUnder20: false, // PBO > 20% DsrAbove95: true, CostTwoXPositive: true, AllGatesPassed: false); var response = new GetShadowRunResponse( RunId: Guid.NewGuid(), ModelId: Guid.NewGuid(), Status: "EvaluationComplete", CreatedAt: DateTimeOffset.UtcNow.AddHours(-2), CompletedAt: DateTimeOffset.UtcNow, Metrics: new ShadowRunMetricsDto(0.1m, 1.0m, 0.4m, 0.1m, 0.5m, 0.25m, 0.95m, 252), ValidationGates: gates, ErrorMessage: null); // Assert Assert.False(response.ValidationGates!.PboUnder20); Assert.False(response.ValidationGates.AllGatesPassed); } [Fact] public void Request_WithValidGuid_Deserializes() { // Arrange var runId = Guid.NewGuid(); var request = new GetShadowRunPollingRequest(runId); // Assert Assert.Equal(runId, request.RunId); } [Fact] public void Metrics_AllFieldsPopulated_Deserializes() { // Arrange var metrics = new ShadowRunMetricsDto( TotalReturn: 0.25m, SharpeRatio: 1.5m, CalmurRatio: 0.75m, MaximumDrawdown: 0.12m, WinRate: 0.60m, ProbOfBacktestOverfit: 0.10m, DailySharePercentile: 0.98m, TradingDays: 252); // Assert Assert.Equal(0.25m, metrics.TotalReturn); Assert.Equal(0.10m, metrics.ProbOfBacktestOverfit); Assert.Equal(252, metrics.TradingDays); } }