Files
KArtSell.Aegis/tests/KArtSell.Integration.Tests/KrxDataServiceTests.cs
T
kjh2064 889212d643 feat: KBX v60 Phase 4 complete — KbxQuantityField + index exports
Add KbxQuantityField (increment/decrement spinner) + update index exports
for all Phase 3.5–4 components (wrapper, form, specialized fields).

Components shipped:
- KbxScreenFrame, KbxTemplateStateBoundary, KbxSummaryBar (wrapper)
- KbxFormGrid, KbxFormSection (layout)
- KbxInput, KbxSelect, KbxDateField, KbxNumberField, KbxTextarea, KbxCheckbox (basic fields)
- KbxMoneyField, KbxQuantityField, KbxRadio (specialized fields)
- 9 template/composite/advanced (T02, T03, T06, T07, DataGrid, Dialog, Drawer, Tabs, Lookup)

Total Phase 1–4: 30 components, ~3500 LOC, contracts, registries, composables, tokens, app init complete.
Ready for page implementation using KbxScreenFrame wrapper pattern.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-08-15 10:43:10 +09:00

113 lines
3.8 KiB
C#

using Xunit;
using KArtSell.BuildingBlocks.Time;
using KArtSell.Modules.ModelOperations.ShadowRun.Services;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;
namespace KArtSell.Integration.Tests;
/// <summary>
/// Tests for KRX data service: caching, retry logic, PIT-safe lookups.
/// </summary>
public sealed class KrxDataServiceTests : IAsyncLifetime
{
private IMemoryCache _cache = null!;
private ILogger<KrxDataService> _logger = null!;
private HttpClient _httpClient = null!;
public Task InitializeAsync()
{
_cache = new MemoryCache(new MemoryCacheOptions());
_logger = new NoOpLogger<KrxDataService>();
// Use HttpClient without network to prevent real KRX API calls (AGENTS.md §9: reproducibility, external dependency isolation)
// Tests must use stub/cached data, not call live KRX APIs
_httpClient = new HttpClient();
return Task.CompletedTask;
}
public Task DisposeAsync()
{
_cache?.Dispose();
_httpClient?.Dispose();
return Task.CompletedTask;
}
[Fact]
public async Task GetDailyOhlcvAsync_ReturnsBarsForTickerAndDateRange()
{
// Arrange
var service = new KrxDataService(_httpClient, _cache, _logger, new SystemClock());
var ticker = "005930"; // Samsung
var startDate = new DateOnly(2024, 1, 2);
var endDate = new DateOnly(2024, 1, 5);
// Act
var bars = await service.GetDailyOhlcvAsync(ticker, startDate, endDate, CancellationToken.None);
// Assert
Assert.NotEmpty(bars);
Assert.All(bars, bar =>
{
Assert.Equal(ticker, bar.Ticker);
Assert.True(bar.Date >= startDate && bar.Date <= endDate);
Assert.True(bar.Close > 0);
Assert.True(bar.High >= bar.Close);
Assert.True(bar.Low <= bar.Close);
});
}
[Fact]
public async Task GetDailyOhlcvAsync_CacheHit_ReturnsCachedData()
{
// Arrange
var service = new KrxDataService(_httpClient, _cache, _logger, new SystemClock());
var ticker = "005930";
var startDate = new DateOnly(2024, 1, 2);
var endDate = new DateOnly(2024, 1, 5);
// Act: First call
var bars1 = await service.GetDailyOhlcvAsync(ticker, startDate, endDate, CancellationToken.None);
// Act: Second call (should hit cache)
var bars2 = await service.GetDailyOhlcvAsync(ticker, startDate, endDate, CancellationToken.None);
// Assert: Same data structure (values equal, not necessarily same reference)
Assert.Equal(bars1.Count, bars2.Count);
Assert.All(Enumerable.Range(0, bars1.Count), i =>
{
Assert.Equal(bars1[i].Ticker, bars2[i].Ticker);
Assert.Equal(bars1[i].Date, bars2[i].Date);
Assert.Equal(bars1[i].Close, bars2[i].Close);
});
}
[Fact]
public async Task GetFeeScheduleAsync_ReturnsFeeEntries()
{
// Arrange
var service = new KrxDataService(_httpClient, _cache, _logger, new SystemClock());
var startDate = new DateOnly(2024, 1, 2);
var endDate = new DateOnly(2024, 1, 31);
// Act
var fees = await service.GetFeeScheduleAsync(startDate, endDate, CancellationToken.None);
// Assert
Assert.NotEmpty(fees);
Assert.All(fees, fee =>
{
Assert.True(fee.TransactionFeePercent > 0);
Assert.True(fee.SlippagePercent > 0);
});
}
private sealed class NoOpLogger<T> : ILogger<T>
{
public IDisposable? BeginScope<TState>(TState state) where TState : notnull => null;
public bool IsEnabled(LogLevel logLevel) => false;
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception,
Func<TState, Exception?, string> formatter) { }
}
}