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; } }