using Xunit;
using KArtSell.Modules.ModelOperations.ShadowRun.Services;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;
namespace KArtSell.Integration.Tests;
///
/// Tests for market calendar: trading sessions, holiday exclusion, determinism.
///
public sealed class MarketCalendarServiceTests : IAsyncLifetime
{
private IMemoryCache _cache = null!;
private ILogger _logger = null!;
public Task InitializeAsync()
{
_cache = new MemoryCache(new MemoryCacheOptions());
_logger = new NoOpLogger();
return Task.CompletedTask;
}
public Task DisposeAsync()
{
_cache?.Dispose();
return Task.CompletedTask;
}
[Fact]
public async Task GetTradingSessionsAsync_ReturnsSessionsInWindow()
{
// Arrange
var service = new MarketCalendarService(_cache, _logger);
var startDate = new DateOnly(2024, 1, 2);
var endDate = new DateOnly(2024, 1, 31);
// Act
var sessions = await service.GetTradingSessionsAsync(startDate, endDate, CancellationToken.None);
// Assert
Assert.NotEmpty(sessions);
Assert.All(sessions, session =>
{
Assert.True(session >= startDate && session <= endDate);
Assert.NotEqual(DayOfWeek.Saturday, session.DayOfWeek);
Assert.NotEqual(DayOfWeek.Sunday, session.DayOfWeek);
});
}
[Fact]
public async Task GetTradingSessionsAsync_ExcludesHolidays()
{
// Arrange
var service = new MarketCalendarService(_cache, _logger);
var startDate = new DateOnly(2024, 2, 1);
var endDate = new DateOnly(2024, 2, 15); // Includes Lunar New Year
// Act
var sessions = await service.GetTradingSessionsAsync(startDate, endDate, CancellationToken.None);
// Assert
// 2024-02-09 (Lunar New Year Eve), 2024-02-10 (Lunar New Year), 2024-02-11, 2024-02-12 should be excluded
var lunarNewYearDates = new[]
{
new DateOnly(2024, 2, 9),
new DateOnly(2024, 2, 10),
new DateOnly(2024, 2, 11),
new DateOnly(2024, 2, 12)
};
Assert.DoesNotContain(lunarNewYearDates, d => sessions.Contains(d));
}
[Fact]
public async Task GetTradingSessionsAsync_IsDeterministic()
{
// Arrange
var service = new MarketCalendarService(_cache, _logger);
var startDate = new DateOnly(2024, 1, 2);
var endDate = new DateOnly(2024, 1, 31);
// Act: Call twice
var sessions1 = await service.GetTradingSessionsAsync(startDate, endDate, CancellationToken.None);
var sessions2 = await service.GetTradingSessionsAsync(startDate, endDate, CancellationToken.None);
// Assert
Assert.Equal(sessions1.Count, sessions2.Count);
for (int i = 0; i < sessions1.Count; i++)
{
Assert.Equal(sessions1[i], sessions2[i]);
}
}
[Fact]
public async Task GetTradingSessionsAsync_Covers252DaysForAnnualWindow()
{
// Arrange
var service = new MarketCalendarService(_cache, _logger);
var startDate = new DateOnly(2024, 1, 2);
var endDate = new DateOnly(2025, 1, 1);
// Act
var sessions = await service.GetTradingSessionsAsync(startDate, endDate, CancellationToken.None);
// Assert
// Typical: 250–252 trading days per year (accounting for holidays)
Assert.InRange(sessions.Count, 245, 260);
}
private sealed class NoOpLogger : ILogger
{
public IDisposable? BeginScope(TState state) where TState : notnull => null;
public bool IsEnabled(LogLevel logLevel) => false;
public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception,
Func formatter) { }
}
}