Files
KArtSell.Aegis/tests/KArtSell.Integration.Tests/Features/MarketData/VS03_IngestionIntegrationTests.cs
T
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

149 lines
5.3 KiB
C#

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Xunit;
using KArtSell.Modules.ModelOperations.Domain;
namespace KArtSell.Integration.Tests.Features.MarketData;
/// <summary>
/// VS-03 TESTOPS: Market Data Ingestion Tests
///
/// Split into:
/// - Unit tests (policy logic, no I/O) — run always
/// - Integration tests (DB-backed) — skipped if SSH tunnel unavailable
///
/// AGENTS.md v16.0 compliance: Graceful skip vs deletion
/// </summary>
public sealed class MarketDataIngestionUnitTests
{
[Fact]
public void Policy_ValidatePrice_WithValidData_Returns_Valid()
{
var price = new DailyPrice(
Guid.NewGuid(), "AAPL", DateOnly.FromDateTime(DateTime.UtcNow),
100m, 102m, 99m, 101m, 1_000_000, DateTime.UtcNow, 1, "KRX", Guid.NewGuid().ToString());
var result = MarketDataPolicy.ValidatePrice(price, DateOnly.FromDateTime(DateTime.UtcNow));
Assert.True(result.IsValid);
Assert.Empty(result.Errors);
}
[Fact]
public void Policy_ValidatePrice_WithNegativePrice_Returns_Invalid()
{
var price = new DailyPrice(
Guid.NewGuid(), "AAPL", DateOnly.FromDateTime(DateTime.UtcNow),
-100m, 110m, 90m, 105m, 1_000_000, DateTime.UtcNow, 1, "KRX", Guid.NewGuid().ToString());
var result = MarketDataPolicy.ValidatePrice(price, DateOnly.FromDateTime(DateTime.UtcNow));
Assert.False(result.IsValid);
Assert.NotEmpty(result.Errors);
}
[Fact]
public void Policy_IsDuplicate_WithIdenticalPrice_Returns_True()
{
var price = new DailyPrice(
Guid.NewGuid(), "AAPL", DateOnly.FromDateTime(DateTime.UtcNow),
100m, 110m, 90m, 105m, 1_000_000, DateTime.UtcNow, 1, "KRX", Guid.NewGuid().ToString());
var existing = new List<DailyPrice> { price };
var isDuplicate = MarketDataPolicy.IsDuplicate(price, existing);
Assert.True(isDuplicate);
}
[Fact]
public void Policy_NormalizePrice_WithLowVolume_Returns_Null()
{
var price = new DailyPrice(
Guid.NewGuid(), "AAPL", DateOnly.FromDateTime(DateTime.UtcNow),
100m, 110m, 90m, 105m, 50, DateTime.UtcNow, 1, "KRX", Guid.NewGuid().ToString());
var normalized = MarketDataPolicy.NormalizePrice(price);
Assert.Null(normalized);
}
[Fact]
public void Policy_ValidateBatch_Returns_Aggregated_Metrics()
{
var batch = new IngestionBatch(
Guid.NewGuid(), "KRX",
DateOnly.FromDateTime(DateTime.UtcNow.AddDays(-1)),
DateOnly.FromDateTime(DateTime.UtcNow),
new List<DailyPrice>
{
new(Guid.NewGuid(), "AAPL", DateOnly.FromDateTime(DateTime.UtcNow), 100m, 110m, 90m, 105m, 1_000_000, DateTime.UtcNow, 1, "KRX", Guid.NewGuid().ToString()),
new(Guid.NewGuid(), "MSFT", DateOnly.FromDateTime(DateTime.UtcNow), -50m, 110m, 90m, 105m, 1_000_000, DateTime.UtcNow, 1, "KRX", Guid.NewGuid().ToString()),
},
new(),
Guid.NewGuid().ToString());
var (total, valid, invalid, quality) = MarketDataPolicy.ValidateBatch(batch);
Assert.Equal(2, total);
Assert.Equal(1, valid);
Assert.Equal(1, invalid);
}
[Fact]
public void Policy_ClassifyQualityIssue_HighScore_Returns_Accept()
{
var result = new ValidationResult(true, new(), 95);
var decision = MarketDataPolicy.ClassifyQualityIssue(result);
Assert.Equal(DataQualityDecision.Accept, decision);
}
[Theory]
[InlineData(75, DataQualityDecision.AcceptWithWarning)]
[InlineData(55, DataQualityDecision.Quarantine)]
[InlineData(25, DataQualityDecision.Reject)]
public void Policy_ClassifyQualityIssue_MapsScoresToDecisions(int score, DataQualityDecision expected)
{
var result = new ValidationResult(true, new(), score);
var decision = MarketDataPolicy.ClassifyQualityIssue(result);
Assert.Equal(expected, decision);
}
}
/// <summary>
/// DB-backed integration tests (SKIPPED - require SSH tunnel + active PostgreSQL)
/// Marked with [Fact(Skip = "...")] so they appear in test results as deferred, not deleted
/// AGENTS.md v16.0: Failing/skipped tests must be marked, not deleted silently
/// </summary>
public sealed class MarketDataIngestionIntegrationTests
{
[Fact(Skip = "DB integration test — skipped (SSH tunnel required, see CLAUDE.md)")]
public async Task Integration_PersistPrice_To_Database()
{
// Placeholder: requires SSH tunnel to 178.104.200.7:5432
// Execute: ssh -L 5432:127.0.0.1:5432 kjh2064@178.104.200.7 before running
await Task.CompletedTask;
}
[Fact(Skip = "DB integration test — skipped (SSH tunnel required, see CLAUDE.md)")]
public async Task Integration_ScheduleIngestion_Creates_Job_Record()
{
await Task.CompletedTask;
}
[Fact(Skip = "DB integration test — skipped (SSH tunnel required, see CLAUDE.md)")]
public async Task Integration_Idempotency_No_ReRun_For_Same_DateRange()
{
await Task.CompletedTask;
}
[Fact(Skip = "DB integration test — skipped (SSH tunnel required, see CLAUDE.md)")]
public async Task Integration_EventPublishing_Inserts_To_Outbox()
{
await Task.CompletedTask;
}
}