Files
kjh2064 e0d58ac31d
ci / backend (push) Failing after 1s
ci / static (push) Failing after 7s
ci / backend (pull_request) Failing after 1s
ci / static (pull_request) Failing after 10s
Build & Test with Secrets / build (pull_request) Failing after 2s
ci / publish (pull_request) Has been cancelled
ci / frontend (pull_request) Has been cancelled
Build & Test with Secrets / security-scan (pull_request) Has been cancelled
Build & Test with Secrets / notification (pull_request) Has been cancelled
Build & Test with Secrets / frontend (pull_request) Has been cancelled
ci / publish (push) Has been cancelled
ci / frontend (push) Has been cancelled
fix: restore clock and validation contracts
2026-08-06 13:39:25 +09:00

252 lines
7.8 KiB
C#

using KArtSell.Modules.ModelOperations.Domain;
using Xunit;
namespace KArtSell.ModelOperations.UnitTests;
public class VS03_MarketDataPolicyTests
{
[Fact]
public void ValidatePrice_ValidPrice_ReturnsPass()
{
var price = new DailyPrice(
PriceId: Guid.NewGuid(),
Symbol: "005930",
TradingDate: DateOnly.FromDateTime(DateTime.UtcNow.AddDays(-1)),
OpenPrice: 70000m,
HighPrice: 71000m,
LowPrice: 69000m,
ClosePrice: 70500m,
Volume: 1000000,
PublishedAt: DateTime.UtcNow,
Revision: 1,
DataSource: "KRX",
CorrelationId: "test-123");
var result = MarketDataPolicy.ValidatePrice(price, DateOnly.FromDateTime(DateTime.UtcNow));
Assert.True(result.IsValid);
Assert.Empty(result.Errors);
Assert.True(result.QualityScore >= 85);
}
[Fact]
public void ValidatePrice_NegativePrice_ReturnsFail()
{
var price = new DailyPrice(
PriceId: Guid.NewGuid(),
Symbol: "005930",
TradingDate: DateOnly.FromDateTime(DateTime.UtcNow.AddDays(-1)),
OpenPrice: -100m,
HighPrice: 71000m,
LowPrice: 69000m,
ClosePrice: 70500m,
Volume: 1000000,
PublishedAt: DateTime.UtcNow,
Revision: 1,
DataSource: "KRX",
CorrelationId: "test-123");
var result = MarketDataPolicy.ValidatePrice(price, DateOnly.FromDateTime(DateTime.UtcNow));
Assert.False(result.IsValid);
Assert.Contains("Open price must be > 0", result.Errors);
}
[Fact]
public void ValidatePrice_HighLowerThanLow_ReturnsFail()
{
var price = new DailyPrice(
PriceId: Guid.NewGuid(),
Symbol: "005930",
TradingDate: DateOnly.FromDateTime(DateTime.UtcNow.AddDays(-1)),
OpenPrice: 70000m,
HighPrice: 68000m,
LowPrice: 69000m,
ClosePrice: 70500m,
Volume: 1000000,
PublishedAt: DateTime.UtcNow,
Revision: 1,
DataSource: "KRX",
CorrelationId: "test-123");
var result = MarketDataPolicy.ValidatePrice(price, DateOnly.FromDateTime(DateTime.UtcNow));
Assert.False(result.IsValid);
Assert.Contains("High must be >= Low", result.Errors);
}
[Fact]
public void ValidatePrice_FutureDate_ReturnsFail()
{
var price = new DailyPrice(
PriceId: Guid.NewGuid(),
Symbol: "005930",
TradingDate: DateOnly.FromDateTime(DateTime.UtcNow.AddDays(1)),
OpenPrice: 70000m,
HighPrice: 71000m,
LowPrice: 69000m,
ClosePrice: 70500m,
Volume: 1000000,
PublishedAt: DateTime.UtcNow,
Revision: 1,
DataSource: "KRX",
CorrelationId: "test-123");
var result = MarketDataPolicy.ValidatePrice(price, DateOnly.FromDateTime(DateTime.UtcNow));
Assert.False(result.IsValid);
Assert.Contains("cannot be in the future", result.Errors[0]);
}
[Fact]
public void ValidatePrice_ZeroVolume_LowersScore()
{
var price = new DailyPrice(
PriceId: Guid.NewGuid(),
Symbol: "005930",
TradingDate: DateOnly.FromDateTime(DateTime.UtcNow.AddDays(-1)),
OpenPrice: 70000m,
HighPrice: 71000m,
LowPrice: 69000m,
ClosePrice: 70500m,
Volume: 0,
PublishedAt: DateTime.UtcNow,
Revision: 1,
DataSource: "KRX",
CorrelationId: "test-123");
var result = MarketDataPolicy.ValidatePrice(price, DateOnly.FromDateTime(DateTime.UtcNow));
Assert.True(result.IsValid);
Assert.True(result.QualityScore < 80); // Quality degraded but still valid
}
[Fact]
public void IsDuplicate_IdenticalPrice_ReturnsTrue()
{
var price1 = new DailyPrice(
PriceId: Guid.NewGuid(),
Symbol: "005930",
TradingDate: DateOnly.FromDateTime(DateTime.UtcNow.AddDays(-1)),
OpenPrice: 70000m,
HighPrice: 71000m,
LowPrice: 69000m,
ClosePrice: 70500m,
Volume: 1000000,
PublishedAt: DateTime.UtcNow,
Revision: 1,
DataSource: "KRX",
CorrelationId: "test-123");
var price2 = price1 with { PriceId = Guid.NewGuid(), Revision = 2 };
var isDuplicate = MarketDataPolicy.IsDuplicate(price2, new List<DailyPrice> { price1 });
Assert.True(isDuplicate);
}
[Fact]
public void IsDuplicate_DifferentSymbol_ReturnsFalse()
{
var price1 = new DailyPrice(
PriceId: Guid.NewGuid(),
Symbol: "005930",
TradingDate: DateOnly.FromDateTime(DateTime.UtcNow.AddDays(-1)),
OpenPrice: 70000m,
HighPrice: 71000m,
LowPrice: 69000m,
ClosePrice: 70500m,
Volume: 1000000,
PublishedAt: DateTime.UtcNow,
Revision: 1,
DataSource: "KRX",
CorrelationId: "test-123");
var price2 = price1 with
{
PriceId = Guid.NewGuid(),
Symbol = "000660"
};
var isDuplicate = MarketDataPolicy.IsDuplicate(price2, new List<DailyPrice> { price1 });
Assert.False(isDuplicate);
}
[Fact]
public void NormalizePrice_ValidPrice_RoundsTo2Decimals()
{
var price = new DailyPrice(
PriceId: Guid.NewGuid(),
Symbol: "005930",
TradingDate: DateOnly.FromDateTime(DateTime.UtcNow.AddDays(-1)),
OpenPrice: 70000.123m,
HighPrice: 71000.456m,
LowPrice: 69000.789m,
ClosePrice: 70500.999m,
Volume: 1000000,
PublishedAt: DateTime.UtcNow,
Revision: 1,
DataSource: "KRX",
CorrelationId: "test-123");
var normalized = MarketDataPolicy.NormalizePrice(price);
Assert.NotNull(normalized);
Assert.Equal(70000.12m, normalized!.OpenPrice);
Assert.Equal(71000.46m, normalized.HighPrice);
}
[Fact]
public void NormalizePrice_LowVolume_ReturnsNull()
{
var price = new DailyPrice(
PriceId: Guid.NewGuid(),
Symbol: "005930",
TradingDate: DateOnly.FromDateTime(DateTime.UtcNow.AddDays(-1)),
OpenPrice: 70000m,
HighPrice: 71000m,
LowPrice: 69000m,
ClosePrice: 70500m,
Volume: 50, // Suspiciously low
PublishedAt: DateTime.UtcNow,
Revision: 1,
DataSource: "KRX",
CorrelationId: "test-123");
var normalized = MarketDataPolicy.NormalizePrice(price);
Assert.Null(normalized);
}
[Fact]
public void ClassifyQualityIssue_HighScore_ReturnsAccept()
{
var result = new ValidationResult(IsValid: true, Errors: new(), QualityScore: 95);
var decision = MarketDataPolicy.ClassifyQualityIssue(result);
Assert.Equal(DataQualityDecision.Accept, decision);
}
[Fact]
public void ClassifyQualityIssue_MediumScore_ReturnsAcceptWithWarning()
{
var result = new ValidationResult(IsValid: true, Errors: new(), QualityScore: 75);
var decision = MarketDataPolicy.ClassifyQualityIssue(result);
Assert.Equal(DataQualityDecision.AcceptWithWarning, decision);
}
[Fact]
public void ClassifyQualityIssue_LowScore_ReturnsQuarantine()
{
var result = new ValidationResult(IsValid: true, Errors: new(), QualityScore: 60);
var decision = MarketDataPolicy.ClassifyQualityIssue(result);
Assert.Equal(DataQualityDecision.Quarantine, decision);
}
}