diff --git a/src/KArtSell.Host/KArtSell.Host.csproj b/src/KArtSell.Host/KArtSell.Host.csproj index 2e8d1492..e5b986a8 100644 --- a/src/KArtSell.Host/KArtSell.Host.csproj +++ b/src/KArtSell.Host/KArtSell.Host.csproj @@ -4,16 +4,18 @@ + + + + - - - - diff --git a/src/KArtSell.Modules.ModelOperations/ApprovalWorkflow/ApprovalSql.cs b/src/KArtSell.Modules.ModelOperations/ApprovalWorkflow/ApprovalSql.cs index 897d2008..62ed678c 100644 --- a/src/KArtSell.Modules.ModelOperations/ApprovalWorkflow/ApprovalSql.cs +++ b/src/KArtSell.Modules.ModelOperations/ApprovalWorkflow/ApprovalSql.cs @@ -69,7 +69,7 @@ public class ApprovalSql const string sql = """ INSERT INTO model_operations.approval_proposals (id, model_id, status, created_by, created_at, justification, effective_at, published_at, revision, correlation_id) - VALUES (@id, @modelId, @status, @createdBy, @createdAt, @justification, @effectiveAt, @publishedAt, 1, @correlationId) + VALUES (@id, @modelId, @status, @createdBy, @createdAt, @justification, @effectiveAt::date, @publishedAt, 1, @correlationId) """; await conn.ExecuteAsync(sql, new @@ -80,7 +80,7 @@ public class ApprovalSql createdBy, createdAt = _clock.UtcNow, justification, - effectiveAt, + effectiveAt = effectiveAt.ToString("yyyy-MM-dd"), // Dapper: DateOnly cannot be used as a parameter value directly publishedAt, correlationId }); diff --git a/src/KArtSell.Modules.ModelOperations/Compliance/AuditSql.cs b/src/KArtSell.Modules.ModelOperations/Compliance/AuditSql.cs index bebdad94..f7d69e1c 100644 --- a/src/KArtSell.Modules.ModelOperations/Compliance/AuditSql.cs +++ b/src/KArtSell.Modules.ModelOperations/Compliance/AuditSql.cs @@ -135,7 +135,7 @@ public class AuditSql // Get paginated results var sql = $""" SELECT id, event_type, entity_type, entity_id, actor_email, actor_role, event_at, - result, error_message, details, evidence_links, ip_address, user_agent, + result, error_message, details, evidence_links, ip_address::text as ip_address, user_agent, published_at, correlation_id, revision FROM compliance.audit_events WHERE {whereClause} @@ -162,7 +162,7 @@ public class AuditSql { const string sql = """ SELECT id, event_type, entity_type, entity_id, actor_email, actor_role, event_at, - result, error_message, details, evidence_links, ip_address, user_agent, + result, error_message, details, evidence_links, ip_address::text as ip_address, user_agent, published_at, correlation_id, revision FROM compliance.audit_events WHERE id = @EventId diff --git a/src/KArtSell.Modules.ModelOperations/Compliance/GdprRetention.cs b/src/KArtSell.Modules.ModelOperations/Compliance/GdprRetention.cs index e2d7b884..0adccb22 100644 --- a/src/KArtSell.Modules.ModelOperations/Compliance/GdprRetention.cs +++ b/src/KArtSell.Modules.ModelOperations/Compliance/GdprRetention.cs @@ -10,7 +10,7 @@ public class GdprRetention public Guid EventId { get; set; } public Guid? CustomerId { get; set; } public string[]? DataCategories { get; set; } // PII, EMAIL, TRADING_HISTORY, PORTFOLIO_DATA, etc. - public DateTime RetentionEndsAt { get; set; } + public DateOnly RetentionEndsAt { get; set; } public required string PurgeStatus { get; set; } // PENDING, PURGED, EXCEPTION public DateTime? PurgedAt { get; set; } public string? ExceptionReason { get; set; } diff --git a/src/KArtSell.Modules.ModelOperations/TradeExecution/Trade.cs b/src/KArtSell.Modules.ModelOperations/TradeExecution/Trade.cs index d7ce3783..49a73361 100644 --- a/src/KArtSell.Modules.ModelOperations/TradeExecution/Trade.cs +++ b/src/KArtSell.Modules.ModelOperations/TradeExecution/Trade.cs @@ -26,7 +26,7 @@ public class Trade public decimal? Commission { get; set; } public decimal? NetProceeds { get; set; } public string? ErrorMessage { get; set; } - public JsonElement? KisResponse { get; set; } + public string? KisResponse { get; set; } public DateTime? ExecutionTimestamp { get; set; } public DateTime? SettlementTimestamp { get; set; } public DateTime PublishedAt { get; set; } @@ -55,14 +55,14 @@ public class Trade { Status = TradeStatus.Submitted; KisOrderId = kisOrderId; - KisResponse = response; + KisResponse = response.ToString(); Revision++; } public void MarkAccepted(JsonElement response) { Status = TradeStatus.Accepted; - KisResponse = response; + KisResponse = response.ToString(); Revision++; } @@ -73,7 +73,7 @@ public class Trade TotalAmount = executedQty * unitPrice; Status = executedQty >= Quantity ? TradeStatus.FullyFilled : TradeStatus.PartiallyFilled; ExecutionTimestamp = now; - KisResponse = response; + KisResponse = response.ToString(); Revision++; } @@ -98,7 +98,7 @@ public class Trade public void MarkErrored(KisTradeExecutionException exception) { ErrorMessage = exception.Message; - KisResponse = exception.KisResponse; + KisResponse = exception.KisResponse?.ToString(); Revision++; } } diff --git a/src/KArtSell.Modules.ModelOperations/TradeExecution/TradeHandlers.cs b/src/KArtSell.Modules.ModelOperations/TradeExecution/TradeHandlers.cs index 1179e413..21aa73b3 100644 --- a/src/KArtSell.Modules.ModelOperations/TradeExecution/TradeHandlers.cs +++ b/src/KArtSell.Modules.ModelOperations/TradeExecution/TradeHandlers.cs @@ -57,14 +57,7 @@ public class SubmitTradeHandler ); trade.MarkSubmitted(orderId, response); - await _sql.UpdateTradeStatusAsync( - trade.Id, - TradeStatus.Submitted, - response, - null, - command.CorrelationId, - ct - ); + await _sql.UpdateTradeStatusAsync(trade, response, null, ct); await PublishEventAsync( "TradeSubmitted", @@ -84,14 +77,7 @@ public class SubmitTradeHandler catch (KisTradeExecutionException ex) { trade.MarkErrored(ex); - await _sql.UpdateTradeStatusAsync( - trade.Id, - trade.Status, - ex.KisResponse, - ex.Message, - command.CorrelationId, - ct - ); + await _sql.UpdateTradeStatusAsync(trade, ex.KisResponse, ex.Message, ct); _logger.LogError( "Trade submission failed: {TradeId} {Classification}", @@ -163,14 +149,7 @@ public class PollTradeStatusHandler trade.MarkFilled(executedQty, unitPrice, response, _clock.UtcNow.UtcDateTime); } - await _sql.UpdateTradeStatusAsync( - trade.Id, - trade.Status, - response, - null, - command.CorrelationId, - ct - ); + await _sql.UpdateTradeStatusAsync(trade, response, null, ct); if (trade.Status is TradeStatus.FullyFilled) { @@ -195,14 +174,7 @@ public class PollTradeStatusHandler } catch (KisTradeExecutionException ex) { - await _sql.UpdateTradeStatusAsync( - trade.Id, - trade.Status, - ex.KisResponse, - ex.Message, - command.CorrelationId, - ct - ); + await _sql.UpdateTradeStatusAsync(trade, ex.KisResponse, ex.Message, ct); _logger.LogError("Failed to poll trade status: {TradeId}", trade.Id); } @@ -262,14 +234,7 @@ public class ConfirmSettlementHandler if (success) { trade.MarkConfirmed(_clock.UtcNow.UtcDateTime, command.Commission); - await _sql.UpdateTradeStatusAsync( - trade.Id, - TradeStatus.Confirmed, - response, - null, - command.CorrelationId, - ct - ); + await _sql.UpdateTradeStatusAsync(trade, response, null, ct); await TradeOutboxPublisher.PublishAsync( _connectionFactory, @@ -290,14 +255,7 @@ public class ConfirmSettlementHandler } catch (KisTradeExecutionException ex) { - await _sql.UpdateTradeStatusAsync( - trade.Id, - trade.Status, - ex.KisResponse, - ex.Message, - command.CorrelationId, - ct - ); + await _sql.UpdateTradeStatusAsync(trade, ex.KisResponse, ex.Message, ct); _logger.LogError("Failed to confirm settlement: {TradeId}", trade.Id); } diff --git a/src/KArtSell.Modules.ModelOperations/TradeExecution/TradeSql.cs b/src/KArtSell.Modules.ModelOperations/TradeExecution/TradeSql.cs index 542ec168..b1df1a00 100644 --- a/src/KArtSell.Modules.ModelOperations/TradeExecution/TradeSql.cs +++ b/src/KArtSell.Modules.ModelOperations/TradeExecution/TradeSql.cs @@ -12,7 +12,7 @@ public interface ITradeSql Task> GetTradesByStatusAsync(TradeStatus status, Guid correlationId, CancellationToken ct = default); Task> GetTradesByDecisionIdAsync(Guid sellDecisionId, Guid correlationId, CancellationToken ct = default); Task InsertTradeAsync(Trade trade, CancellationToken ct = default); - Task UpdateTradeStatusAsync(Guid tradeId, TradeStatus newStatus, JsonElement? kisResponse, string? errorMessage, Guid correlationId, CancellationToken ct = default); + Task UpdateTradeStatusAsync(Trade trade, JsonElement? kisResponse, string? errorMessage, CancellationToken ct = default); Task CountTradesByStatusAsync(TradeStatus status, CancellationToken ct = default); } @@ -21,6 +21,16 @@ public class TradeSql : ITradeSql private readonly NpgsqlDataSource _dataSource; private readonly ILogger _logger; + static TradeSql() + { + // KArtSell.BuildingBlocks.Data.DapperBootstrap sets this via [ModuleInitializer], but that + // only fires once its assembly is actually loaded into the process. Nothing in this class + // references a BuildingBlocks type, so under test isolation (or any host that queries Trade + // before touching BuildingBlocks) that assembly load - and the mapping - can be skipped, + // silently nulling out every snake_case column (kis_order_id, sell_decision_id, ...). + Dapper.DefaultTypeMap.MatchNamesWithUnderscores = true; + } + public TradeSql(NpgsqlDataSource dataSource, ILogger logger) { _dataSource = dataSource; @@ -33,7 +43,7 @@ public class TradeSql : ITradeSql const string sql = """ SELECT id, sell_decision_id, kis_order_id, status, quantity, executed_quantity, - unit_price, total_amount, commission, net_proceeds, error_message, kis_response, + unit_price, total_amount, commission, net_proceeds, error_message, kis_response::text as kis_response, execution_timestamp, settlement_timestamp, published_at, correlation_id, revision FROM model_operations.trades WHERE id = @tradeId @@ -61,7 +71,7 @@ public class TradeSql : ITradeSql const string sql = """ SELECT id, sell_decision_id, kis_order_id, status, quantity, executed_quantity, - unit_price, total_amount, commission, net_proceeds, error_message, kis_response, + unit_price, total_amount, commission, net_proceeds, error_message, kis_response::text as kis_response, execution_timestamp, settlement_timestamp, published_at, correlation_id, revision FROM model_operations.trades WHERE kis_order_id = @kisOrderId @@ -82,7 +92,7 @@ public class TradeSql : ITradeSql const string sql = """ SELECT id, sell_decision_id, kis_order_id, status, quantity, executed_quantity, - unit_price, total_amount, commission, net_proceeds, error_message, kis_response, + unit_price, total_amount, commission, net_proceeds, error_message, kis_response::text as kis_response, execution_timestamp, settlement_timestamp, published_at, correlation_id, revision FROM model_operations.trades WHERE status = @status @@ -102,7 +112,7 @@ public class TradeSql : ITradeSql const string sql = """ SELECT id, sell_decision_id, kis_order_id, status, quantity, executed_quantity, - unit_price, total_amount, commission, net_proceeds, error_message, kis_response, + unit_price, total_amount, commission, net_proceeds, error_message, kis_response::text as kis_response, execution_timestamp, settlement_timestamp, published_at, correlation_id, revision FROM model_operations.trades WHERE sell_decision_id = @sellDecisionId @@ -143,7 +153,7 @@ public class TradeSql : ITradeSql trade.Commission, trade.NetProceeds, trade.ErrorMessage, - kisResponse = trade.KisResponse?.ToString(), + trade.KisResponse, trade.ExecutionTimestamp, trade.SettlementTimestamp, trade.PublishedAt, @@ -155,11 +165,9 @@ public class TradeSql : ITradeSql } public async Task UpdateTradeStatusAsync( - Guid tradeId, - TradeStatus newStatus, + Trade trade, JsonElement? kisResponse, string? errorMessage, - Guid correlationId, CancellationToken ct = default) { using var connection = await _dataSource.OpenConnectionAsync(ct); @@ -173,6 +181,14 @@ public class TradeSql : ITradeSql UPDATE model_operations.trades SET status = @newStatus, + kis_order_id = COALESCE(@kisOrderId, kis_order_id), + executed_quantity = COALESCE(@executedQuantity, executed_quantity), + unit_price = COALESCE(@unitPrice, unit_price), + total_amount = COALESCE(@totalAmount, total_amount), + commission = COALESCE(@commission, commission), + net_proceeds = COALESCE(@netProceeds, net_proceeds), + execution_timestamp = COALESCE(@executionTimestamp, execution_timestamp), + settlement_timestamp = COALESCE(@settlementTimestamp, settlement_timestamp), kis_response = COALESCE(@kisResponse::jsonb, kis_response), error_message = COALESCE(@errorMessage, error_message), revision = revision + 1 @@ -182,14 +198,22 @@ public class TradeSql : ITradeSql await connection.ExecuteAsync(sql, new { id = Guid.NewGuid(), - tradeId, - newStatus = newStatus.ToString(), + tradeId = trade.Id, + newStatus = trade.Status.ToString(), + kisOrderId = trade.KisOrderId, + executedQuantity = trade.ExecutedQuantity, + unitPrice = trade.UnitPrice, + totalAmount = trade.TotalAmount, + commission = trade.Commission, + netProceeds = trade.NetProceeds, + executionTimestamp = trade.ExecutionTimestamp, + settlementTimestamp = trade.SettlementTimestamp, kisResponse = kisResponse?.ToString(), errorMessage, - correlationId + correlationId = trade.CorrelationId }); - _logger.LogInformation("Updated trade {TradeId} status to {Status}", tradeId, newStatus); + _logger.LogInformation("Updated trade {TradeId} status to {Status}", trade.Id, trade.Status); } public async Task CountTradesByStatusAsync(TradeStatus status, CancellationToken ct = default) diff --git a/tests/KArtSell.Integration.Tests/ApprovalWorkflow/ApprovalWorkflowTests.cs b/tests/KArtSell.Integration.Tests/ApprovalWorkflow/ApprovalWorkflowTests.cs index 791498f2..ee9440fb 100644 --- a/tests/KArtSell.Integration.Tests/ApprovalWorkflow/ApprovalWorkflowTests.cs +++ b/tests/KArtSell.Integration.Tests/ApprovalWorkflow/ApprovalWorkflowTests.cs @@ -3,6 +3,7 @@ namespace KArtSell.Integration.Tests.ApprovalWorkflow; using System; using System.Collections.Generic; using System.Threading.Tasks; +using Dapper; using Xunit; using KArtSell.BuildingBlocks.Time; using KArtSell.Modules.ModelOperations.ApprovalWorkflow; @@ -165,6 +166,7 @@ public class ApprovalWorkflowTests : IAsyncLifetime var id = Guid.NewGuid(); var modelId = Guid.NewGuid(); var correlationId = Guid.NewGuid(); + await SeedModelAsync(modelId); // Act await _sql.InsertProposalAsync( @@ -185,6 +187,15 @@ public class ApprovalWorkflowTests : IAsyncLifetime Assert.Equal(modelId, retrieved.ModelId); Assert.Equal(correlationId, retrieved.CorrelationId); } + + private async Task SeedModelAsync(Guid modelId) + { + await using var conn = new Npgsql.NpgsqlConnection(_connectionString); + await conn.OpenAsync(); + await conn.ExecuteAsync( + "INSERT INTO model_operations.models (id, ticker, correlation_id) VALUES (@Id, @Ticker, @CorrelationId)", + new { Id = modelId, Ticker = "TEST", CorrelationId = Guid.NewGuid() }); + } } public class InMemoryOutbox : IOutbox diff --git a/tests/KArtSell.Integration.Tests/Compliance/AuditTrailTests.cs b/tests/KArtSell.Integration.Tests/Compliance/AuditTrailTests.cs index ea22d52e..bf1fdc2a 100644 --- a/tests/KArtSell.Integration.Tests/Compliance/AuditTrailTests.cs +++ b/tests/KArtSell.Integration.Tests/Compliance/AuditTrailTests.cs @@ -106,6 +106,11 @@ public class AuditTrailTests : IAsyncLifetime var customerId = Guid.NewGuid(); var retentionId = Guid.NewGuid(); + await _sql.InsertAuditEventAsync( + _db, eventId, AuditEventTypes.ModelActivated, AuditEntityTypes.Model, + Guid.NewGuid(), "customer@company.com", null, DateTime.UtcNow, "SUCCESS", null, + null, null, null, null, Guid.NewGuid(), CancellationToken.None); + // Act await _sql.InsertGdprRetentionAsync( _db, retentionId, eventId, customerId, @@ -175,7 +180,8 @@ public class AuditTrailTests : IAsyncLifetime // Assert var @event = await _sql.GetAuditEventByIdAsync(_db, eventId, CancellationToken.None); Assert.NotNull(@event); - Assert.Contains("", @event.Details?.ToString() ?? ""); + Assert.Equal("", @event.Details?["actor_email"].ToString()); + Assert.Equal("", @event.Details?["customer_id"].ToString()); } private const string TestConnectionString = diff --git a/tests/KArtSell.Integration.Tests/SellDecision/SellDecisionTests.cs b/tests/KArtSell.Integration.Tests/SellDecision/SellDecisionTests.cs index 67451a26..a9bb2fdb 100644 --- a/tests/KArtSell.Integration.Tests/SellDecision/SellDecisionTests.cs +++ b/tests/KArtSell.Integration.Tests/SellDecision/SellDecisionTests.cs @@ -118,8 +118,8 @@ public class SellPriorityRankerTests [Fact] public void CalculateScore_HardImpairment_ReturnsLowestScore() { - var score = _ranker.CalculateScore(SellPriority.HardImpairment, fundAgeDays: 200, liquidityPercent: 0.5m); - Assert.Equal(950m, score); // 1000 - 50 (age boost) + var score = _ranker.CalculateScore(SellPriority.HardImpairment, fundAgeDays: 400, liquidityPercent: 0.5m); + Assert.Equal(950m, score); // 1000 - 50 (age boost, fundAgeDays > 365 per VS-10-SLICE_SPEC.md) } [Fact] diff --git a/tests/KArtSell.Integration.Tests/TradeExecution/TradeExecutionTests.cs b/tests/KArtSell.Integration.Tests/TradeExecution/TradeExecutionTests.cs index b8d4e011..20e44a38 100644 --- a/tests/KArtSell.Integration.Tests/TradeExecution/TradeExecutionTests.cs +++ b/tests/KArtSell.Integration.Tests/TradeExecution/TradeExecutionTests.cs @@ -1,4 +1,5 @@ using System.Text.Json; +using Dapper; using KArtSell.Modules.ModelOperations.TradeExecution; using Microsoft.Extensions.Logging; using Npgsql; @@ -31,11 +32,31 @@ public class TradeExecutionTests : IAsyncLifetime await _dataSource.DisposeAsync(); } + private async Task SeedSellDecisionAsync() + { + await using var connection = await _dataSource.OpenConnectionAsync(); + var modelId = Guid.NewGuid(); + await connection.ExecuteAsync( + "INSERT INTO model_operations.models (id, ticker, correlation_id) VALUES (@Id, @Ticker, @CorrelationId)", + new { Id = modelId, Ticker = "TEST", CorrelationId = Guid.NewGuid() }); + + var sellDecisionId = Guid.NewGuid(); + await connection.ExecuteAsync( + """ + INSERT INTO model_operations.sell_decisions + (id, model_id, status, created_by, published_at, correlation_id) + VALUES (@Id, @ModelId, 'PENDING', 'test@company.com', NOW(), @CorrelationId) + """, + new { Id = sellDecisionId, ModelId = modelId, CorrelationId = Guid.NewGuid() }); + + return sellDecisionId; + } + [Fact] public async Task CreateTrade_WithValidData_ShouldInsertSuccessfully() { var sql = new TradeSql(_dataSource, _logger); - var sellDecisionId = Guid.NewGuid(); + var sellDecisionId = await SeedSellDecisionAsync(); var correlationId = Guid.NewGuid(); var trade = Trade.Create(sellDecisionId, 1000, correlationId, DateTime.UtcNow); @@ -55,14 +76,14 @@ public class TradeExecutionTests : IAsyncLifetime { var sql = new TradeSql(_dataSource, _logger); var correlationId = Guid.NewGuid(); - var trade = Trade.Create(Guid.NewGuid(), 1000, correlationId, DateTime.UtcNow); + var trade = Trade.Create(await SeedSellDecisionAsync(), 1000, correlationId, DateTime.UtcNow); await sql.InsertTradeAsync(trade); var response = JsonDocument.Parse("{}").RootElement; trade.MarkSubmitted("KIS-ORDER-123", response); - await sql.UpdateTradeStatusAsync(trade.Id, TradeStatus.Submitted, response, null, correlationId); + await sql.UpdateTradeStatusAsync(trade, response, null); var retrieved = await sql.GetTradeByIdAsync(trade.Id, correlationId); @@ -76,14 +97,14 @@ public class TradeExecutionTests : IAsyncLifetime { var sql = new TradeSql(_dataSource, _logger); var correlationId = Guid.NewGuid(); - var trade = Trade.Create(Guid.NewGuid(), 1000, correlationId, DateTime.UtcNow); + var trade = Trade.Create(await SeedSellDecisionAsync(), 1000, correlationId, DateTime.UtcNow); await sql.InsertTradeAsync(trade); var response = JsonDocument.Parse("{}").RootElement; trade.MarkFilled(1000, 49.95m, response, DateTime.UtcNow); - await sql.UpdateTradeStatusAsync(trade.Id, TradeStatus.FullyFilled, response, null, correlationId); + await sql.UpdateTradeStatusAsync(trade, response, null); var retrieved = await sql.GetTradeByIdAsync(trade.Id, correlationId); @@ -100,8 +121,8 @@ public class TradeExecutionTests : IAsyncLifetime var sql = new TradeSql(_dataSource, _logger); var correlationId = Guid.NewGuid(); - var trade1 = Trade.Create(Guid.NewGuid(), 1000, correlationId, DateTime.UtcNow); - var trade2 = Trade.Create(Guid.NewGuid(), 2000, correlationId, DateTime.UtcNow); + var trade1 = Trade.Create(await SeedSellDecisionAsync(), 1000, correlationId, DateTime.UtcNow); + var trade2 = Trade.Create(await SeedSellDecisionAsync(), 2000, correlationId, DateTime.UtcNow); await sql.InsertTradeAsync(trade1); await sql.InsertTradeAsync(trade2); @@ -118,14 +139,14 @@ public class TradeExecutionTests : IAsyncLifetime { var sql = new TradeSql(_dataSource, _logger); var correlationId = Guid.NewGuid(); - var trade = Trade.Create(Guid.NewGuid(), 1000, correlationId, DateTime.UtcNow); + var trade = Trade.Create(await SeedSellDecisionAsync(), 1000, correlationId, DateTime.UtcNow); await sql.InsertTradeAsync(trade); trade.TotalAmount = 49950m; trade.MarkConfirmed(DateTime.UtcNow, 50m); - await sql.UpdateTradeStatusAsync(trade.Id, TradeStatus.Confirmed, null, null, correlationId); + await sql.UpdateTradeStatusAsync(trade, null, null); var retrieved = await sql.GetTradeByIdAsync(trade.Id, correlationId); @@ -140,11 +161,16 @@ public class TradeExecutionTests : IAsyncLifetime { var sql = new TradeSql(_dataSource, _logger); var correlationId = Guid.NewGuid(); - var trade = Trade.Create(Guid.NewGuid(), 1000, correlationId, DateTime.UtcNow); + var trade = Trade.Create(await SeedSellDecisionAsync(), 1000, correlationId, DateTime.UtcNow); await sql.InsertTradeAsync(trade); - await sql.UpdateTradeStatusAsync(trade.Id, TradeStatus.Submitted, null, null, correlationId); - await sql.UpdateTradeStatusAsync(trade.Id, TradeStatus.Accepted, null, null, correlationId); + + var response = JsonDocument.Parse("{}").RootElement; + trade.MarkSubmitted("KIS-1", response); + await sql.UpdateTradeStatusAsync(trade, response, null); + + trade.MarkAccepted(response); + await sql.UpdateTradeStatusAsync(trade, response, null); var retrieved = await sql.GetTradeByIdAsync(trade.Id, correlationId); @@ -158,8 +184,8 @@ public class TradeExecutionTests : IAsyncLifetime var sql = new TradeSql(_dataSource, _logger); var correlationId = Guid.NewGuid(); - var trade1 = Trade.Create(Guid.NewGuid(), 1000, correlationId, DateTime.UtcNow); - var trade2 = Trade.Create(Guid.NewGuid(), 2000, correlationId, DateTime.UtcNow); + var trade1 = Trade.Create(await SeedSellDecisionAsync(), 1000, correlationId, DateTime.UtcNow); + var trade2 = Trade.Create(await SeedSellDecisionAsync(), 2000, correlationId, DateTime.UtcNow); await sql.InsertTradeAsync(trade1); await sql.InsertTradeAsync(trade2);