diff --git a/src/dotnet/QuantEngine.Application/Services/FactorComputationService.cs b/src/dotnet/QuantEngine.Application/Services/FactorComputationService.cs index b93fac1d..9e783669 100644 --- a/src/dotnet/QuantEngine.Application/Services/FactorComputationService.cs +++ b/src/dotnet/QuantEngine.Application/Services/FactorComputationService.cs @@ -42,6 +42,10 @@ public sealed class FactorComputationService FactorOutputs outputs, DateTimeOffset? observedAt = null) { + ticker = RequireValue(ticker, nameof(ticker)); + sourceVersion = RequireValue(sourceVersion, nameof(sourceVersion)); + ArgumentNullException.ThrowIfNull(outputs); + var when = observedAt ?? DateTimeOffset.UtcNow; await _history.AppendFactorOutputAsync("momentum_20d", sourceVersion, outputs.Momentum20D, "PASS", sourceVersion, when); await _history.AppendFactorOutputAsync("momentum_60d", sourceVersion, outputs.Momentum60D, "PASS", sourceVersion, when); @@ -52,4 +56,14 @@ public sealed class FactorComputationService await _history.AppendFactorOutputAsync("rs_20d", sourceVersion, outputs.Rs20D, "PASS", sourceVersion, when); _auditTrail.Append("factor_audit", ticker, new FactorComputationAudit(ticker, 0, 0, "PERSISTED", when, sourceVersion)); } + + private static string RequireValue(string value, string parameterName) + { + if (string.IsNullOrWhiteSpace(value)) + { + throw new ArgumentException("Value is required.", parameterName); + } + + return value.Trim(); + } } diff --git a/src/dotnet/QuantEngine.Core.Tests/FactorComputationServiceTests.cs b/src/dotnet/QuantEngine.Core.Tests/FactorComputationServiceTests.cs index d536a086..42311b66 100644 --- a/src/dotnet/QuantEngine.Core.Tests/FactorComputationServiceTests.cs +++ b/src/dotnet/QuantEngine.Core.Tests/FactorComputationServiceTests.cs @@ -29,4 +29,16 @@ public class FactorComputationServiceTests storeMock.Verify(s => s.AppendAsync("factor_output_history", It.IsAny>()), Times.Exactly(7)); auditTrailMock.Verify(a => a.Append("factor_audit", "005930", It.IsAny()), Times.Once); } + + [Fact] + public async Task AppendFactorOutputsAsync_RejectsBlankTicker() + { + var storeMock = new Mock(); + var auditTrailMock = new Mock(); + var history = new HistoryIngestionService(storeMock.Object); + var service = new FactorComputationService(history, auditTrailMock.Object); + + await Assert.ThrowsAsync(() => + service.AppendFactorOutputsAsync(" ", "v1", new FactorOutputs(1, 2, 3, 4, 5, 6, 7))); + } }