using Marathon.Application.Abstractions;
using Marathon.Application.Configuration;
using Marathon.Domain.Entities;
using Marathon.Domain.Enums;
using Marathon.Domain.ValueObjects;
using Microsoft.Extensions.Options;
using NSubstitute;
namespace Marathon.Application.Tests.UseCases;
///
/// Shared factory helpers for domain objects used across use-case tests.
///
internal static class TestFixtures
{
private static readonly TimeSpan MoscowOffset = TimeSpan.FromHours(3);
///
/// Bridges the legacy per-id GetAsync stubs to the batched
/// GetManyAsync the use cases now call: each requested id is resolved
/// through whatever GetAsync was configured to return for it. Lets the
/// existing per-id .Returns(...) setups keep working unchanged.
///
public static void BridgeGetMany(IEventRepository events)
{
events.GetManyAsync(Arg.Any>(), Arg.Any())
.Returns(ci =>
{
var ids = ci.Arg>();
var dict = new Dictionary();
foreach (var id in ids.Distinct())
{
var ev = events.GetAsync(id, CancellationToken.None).GetAwaiter().GetResult();
if (ev is not null) dict[id] = ev;
}
return (IReadOnlyDictionary)dict;
});
}
///
public static void BridgeGetMany(IResultRepository results)
{
results.GetManyAsync(Arg.Any>(), Arg.Any())
.Returns(ci =>
{
var ids = ci.Arg>();
var dict = new Dictionary();
foreach (var id in ids.Distinct())
{
var r = results.GetAsync(id, CancellationToken.None).GetAwaiter().GetResult();
if (r is not null) dict[id] = r;
}
return (IReadOnlyDictionary)dict;
});
}
/// Creates a minimal valid with the given event ID string.
public static Event MakeEvent(string eventIdValue = "12345678")
{
return new Event(
Id: new EventId(eventIdValue),
Sport: new SportCode(6),
CountryCode: "BY",
LeagueId: "league-1",
Category: "Group A",
ScheduledAt: new DateTimeOffset(2026, 5, 10, 18, 0, 0, MoscowOffset),
Side1Name: "Team A",
Side2Name: "Team B");
}
/// Creates a minimal valid for the given event.
public static OddsSnapshot MakeSnapshot(EventId eventId, OddsSource source = OddsSource.PreMatch)
{
var bets = new List
{
new Bet(MatchScope.Instance, BetType.Win, Side.Side1, value: null, new OddsRate(1.85m)),
new Bet(MatchScope.Instance, BetType.Win, Side.Side2, value: null, new OddsRate(2.10m)),
};
return new OddsSnapshot(eventId, DateTimeOffset.UtcNow, source, bets);
}
/// Creates a minimal valid for the given event ID.
public static EventResult MakeResult(EventId eventId)
{
return new EventResult(eventId, 2, 1, Side.Side1, DateTimeOffset.UtcNow);
}
///
/// Creates an that always returns the given
/// throttle. Use 1 for sequential test behaviour, higher values to exercise fan-out.
///
public static IOptionsMonitor Throttle(int maxConcurrentRequests = 1) =>
new StaticOptionsMonitor(new ScrapingThrottle
{
MaxConcurrentRequests = maxConcurrentRequests,
});
private sealed class StaticOptionsMonitor : IOptionsMonitor where T : class
{
private readonly T _value;
public StaticOptionsMonitor(T value) => _value = value;
public T CurrentValue => _value;
public T Get(string? name) => _value;
public IDisposable? OnChange(Action listener) => null;
}
}