92 lines
3.1 KiB
C#
92 lines
3.1 KiB
C#
using Moq;
|
|
using QuantEngine.Application.Services;
|
|
using QuantEngine.Core.Interfaces;
|
|
|
|
namespace QuantEngine.Core.Tests;
|
|
|
|
public class DecisionLearningServiceTests
|
|
{
|
|
[Fact]
|
|
public async Task RecordDecisionAsync_TrimsAndPersistsNormalizedPayloads()
|
|
{
|
|
var store = new Mock<INormalizedLearningStore>(MockBehavior.Strict);
|
|
Guid decisionId = Guid.NewGuid();
|
|
Guid factorObservationId = Guid.NewGuid();
|
|
Guid sourceObservationId = Guid.NewGuid();
|
|
|
|
store.Setup(s => s.AppendDecisionAsync(It.Is<DecisionEventRecord>(r =>
|
|
r.DecisionKey == "decision-1" &&
|
|
r.InstrumentId == "005930" &&
|
|
r.Action == "BUY" &&
|
|
r.Gate == "PASS" &&
|
|
r.SourceVersion == "v1" &&
|
|
r.TraceJson.Contains("\"mode\":\"test\"") &&
|
|
r.ProvenanceJson.Contains("\"origin\":\"unit\"")))).ReturnsAsync(decisionId);
|
|
|
|
store.Setup(s => s.AppendSourceObservationAsync(It.Is<SourceObservationRecord>(r =>
|
|
r.InstrumentId == "005930" &&
|
|
r.SourceName == "kis" &&
|
|
r.SourceVersion == "v1" &&
|
|
r.PayloadJson == "{}" &&
|
|
r.ProvenanceJson == "{}"))).ReturnsAsync(sourceObservationId);
|
|
|
|
store.Setup(s => s.AppendFactorObservationAsync(It.Is<FactorObservationRecord>(r =>
|
|
r.ObservationId == sourceObservationId &&
|
|
r.FactorObservationId != Guid.Empty &&
|
|
r.FactorId == "factor_a" &&
|
|
r.FactorVersion == "1.0" &&
|
|
r.Gate == "PASS" &&
|
|
r.ProvenanceJson == "{}"))).ReturnsAsync(factorObservationId);
|
|
|
|
store.Setup(s => s.AppendDecisionFactorEvidenceAsync(decisionId, factorObservationId, "primary"))
|
|
.Returns(Task.CompletedTask);
|
|
|
|
var service = new DecisionLearningService(store.Object);
|
|
|
|
var result = await service.RecordDecisionAsync(
|
|
" decision-1 ",
|
|
DateTimeOffset.Parse("2026-07-13T00:00:00Z"),
|
|
" 005930 ",
|
|
" BUY ",
|
|
" PASS ",
|
|
12.5m,
|
|
" v1 ",
|
|
new[]
|
|
{
|
|
new FactorEvidenceInput(
|
|
Guid.NewGuid(),
|
|
" factor_a ",
|
|
" 1.0 ",
|
|
DateTimeOffset.Parse("2026-07-13T00:00:00Z"),
|
|
3.14m,
|
|
null,
|
|
" PASS ",
|
|
" primary ",
|
|
" kis ",
|
|
"{}",
|
|
"{}")
|
|
},
|
|
new { mode = "test" },
|
|
new { origin = "unit" });
|
|
|
|
Assert.Equal(decisionId, result);
|
|
store.VerifyAll();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task RecordDecisionAsync_RejectsMissingFactors()
|
|
{
|
|
var service = new DecisionLearningService(new Mock<INormalizedLearningStore>().Object);
|
|
|
|
await Assert.ThrowsAsync<ArgumentNullException>(() => service.RecordDecisionAsync(
|
|
"decision-1",
|
|
DateTimeOffset.UtcNow,
|
|
"005930",
|
|
"BUY",
|
|
"PASS",
|
|
null,
|
|
"v1",
|
|
null!));
|
|
}
|
|
}
|