f3cc66b38a
Implements FastEndpoints integration for 252+ trading-day validation trigger:
Contract-First Design (AGENTS.md v16.0):
- POST /api/shadow-runs (202 Accepted)
- Request: model_id, window_start, window_end, phase_filter
- Response: run_id, status, job_id, estimated_seconds
- Idempotency: Idempotency-Key header (deduplication)
Vertical Slice Components:
- Request.cs, Response.cs (DTOs with validation constraints)
- Validator.cs (FluentValidation): window >= 250 days, valid enum
- Handler.cs (Application): orchestrates command creation, Hangfire job enqueue
- Endpoint.cs (FastEndpoints): HTTP routing, error handling, 202 response
- Policy.cs: model existence validation (stub)
Integration:
- Hangfire background job client injection
- ShadowRunCommand creation with CorrelationId
- Queued to q-research (non-critical background queue)
Tests (9/9 passing):
- Validator: valid/invalid requests, phase filters, window constraints
- All validation scenarios: empty model, short window, invalid phase
Architecture Adherence:
- SOLID: Endpoint → Handler → Validator → Policy separation
- Complexity: Each component cyclomatic < 10
- Safety: Idempotent request (client-supplied key), async job model (202 response)
- Maturity: Contract verified, tests before implementation
Next Phase (Pending):
- Hangfire Job registration in Program.cs
- GET /api/shadow-runs/{run_id} polling endpoint
- E2E test: trigger → job execution → result persistence
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
107 lines
2.9 KiB
C#
107 lines
2.9 KiB
C#
using Xunit;
|
|
using KArtSell.Host.Features.ShadowRun;
|
|
|
|
namespace KArtSell.Integration.Tests;
|
|
|
|
/// <summary>
|
|
/// Tests for shadow run request validation.
|
|
/// </summary>
|
|
public sealed class InitiateShadowRunTests
|
|
{
|
|
[Fact]
|
|
public void Validator_ValidRequest_Passes()
|
|
{
|
|
// Arrange
|
|
var validator = new InitiateShadowRunValidator();
|
|
var request = new InitiateShadowRunRequest(
|
|
ModelId: Guid.NewGuid(),
|
|
WindowStart: new DateOnly(2024, 1, 2),
|
|
WindowEnd: new DateOnly(2026, 8, 2),
|
|
PhaseFilter: "All");
|
|
|
|
// Act
|
|
var result = validator.Validate(request);
|
|
|
|
// Assert
|
|
Assert.True(result.IsValid);
|
|
}
|
|
|
|
[Fact]
|
|
public void Validator_WindowTooShort_Rejects()
|
|
{
|
|
// Arrange
|
|
var validator = new InitiateShadowRunValidator();
|
|
var request = new InitiateShadowRunRequest(
|
|
ModelId: Guid.NewGuid(),
|
|
WindowStart: new DateOnly(2024, 1, 2),
|
|
WindowEnd: new DateOnly(2024, 1, 3), // Only 1 day
|
|
PhaseFilter: "All");
|
|
|
|
// Act
|
|
var result = validator.Validate(request);
|
|
|
|
// Assert
|
|
Assert.False(result.IsValid);
|
|
Assert.NotEmpty(result.Errors);
|
|
}
|
|
|
|
[Fact]
|
|
public void Validator_EmptyModelId_Rejects()
|
|
{
|
|
// Arrange
|
|
var validator = new InitiateShadowRunValidator();
|
|
var request = new InitiateShadowRunRequest(
|
|
ModelId: Guid.Empty,
|
|
WindowStart: new DateOnly(2024, 1, 2),
|
|
WindowEnd: new DateOnly(2026, 8, 2),
|
|
PhaseFilter: "All");
|
|
|
|
// Act
|
|
var result = validator.Validate(request);
|
|
|
|
// Assert
|
|
Assert.False(result.IsValid);
|
|
}
|
|
|
|
[Fact]
|
|
public void Validator_InvalidPhaseFilter_Rejects()
|
|
{
|
|
// Arrange
|
|
var validator = new InitiateShadowRunValidator();
|
|
var request = new InitiateShadowRunRequest(
|
|
ModelId: Guid.NewGuid(),
|
|
WindowStart: new DateOnly(2024, 1, 2),
|
|
WindowEnd: new DateOnly(2026, 8, 2),
|
|
PhaseFilter: "InvalidPhase");
|
|
|
|
// Act
|
|
var result = validator.Validate(request);
|
|
|
|
// Assert
|
|
Assert.False(result.IsValid);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("All")]
|
|
[InlineData("BullMarket")]
|
|
[InlineData("BearMarket")]
|
|
[InlineData("Sideways")]
|
|
[InlineData("HighVolatility")]
|
|
public void Validator_ValidPhaseFilters_Pass(string phase)
|
|
{
|
|
// Arrange
|
|
var validator = new InitiateShadowRunValidator();
|
|
var request = new InitiateShadowRunRequest(
|
|
ModelId: Guid.NewGuid(),
|
|
WindowStart: new DateOnly(2024, 1, 2),
|
|
WindowEnd: new DateOnly(2026, 8, 2),
|
|
PhaseFilter: phase);
|
|
|
|
// Act
|
|
var result = validator.Validate(request);
|
|
|
|
// Assert
|
|
Assert.True(result.IsValid);
|
|
}
|
|
}
|