using Moq; using SportsDivision.Application.Services; using SportsDivision.Domain.Entities; using SportsDivision.Domain.Enums; using SportsDivision.Domain.Interfaces; namespace SportsDivision.Application.Tests; public class HeatAdvancementTests { private readonly Mock _mockUow; private readonly HeatManagementService _service; public HeatAdvancementTests() { _mockUow = new Mock(); _service = new HeatManagementService(_mockUow.Object, null!); } private Round CreateRoundWithHeats(int topN, int fastestLosers, List heats) { return new Round { RoundId = 1, TournamentEventLevelId = 1, RoundOrder = 1, AdvanceTopN = topN, AdvanceFastestLosers = fastestLosers, Heats = heats }; } private Heat CreateHeat(int heatId, int heatNumber, List lanes) { var heat = new Heat { HeatId = heatId, HeatNumber = heatNumber, Status = HeatStatus.Completed, HeatLanes = lanes }; foreach (var lane in lanes) lane.Heat = heat; return heat; } private HeatLane CreateLane(int laneId, int regId, decimal? time, bool isDNS = false, bool isDNF = false, bool isDQ = false) { return new HeatLane { HeatLaneId = laneId, EventRegistrationId = regId, LaneNumber = laneId, Time = time, IsDNS = isDNS, IsDNF = isDNF, IsDQ = isDQ, IsAdvanced = false }; } [Fact] public async Task CalculateAdvancement_TopNFromEachHeat() { var heat1 = CreateHeat(1, 1, new List { CreateLane(1, 101, 10.5m), CreateLane(2, 102, 10.8m), CreateLane(3, 103, 11.2m), CreateLane(4, 104, 11.5m) }); var heat2 = CreateHeat(2, 2, new List { CreateLane(5, 201, 10.6m), CreateLane(6, 202, 10.9m), CreateLane(7, 203, 11.3m), CreateLane(8, 204, 11.6m) }); var round = CreateRoundWithHeats(topN: 2, fastestLosers: 0, new List { heat1, heat2 }); _mockUow.Setup(u => u.Rounds.GetWithHeatsAsync(1)).ReturnsAsync(round); _mockUow.Setup(u => u.HeatLanes.Update(It.IsAny())); _mockUow.Setup(u => u.SaveChangesAsync()).ReturnsAsync(1); await _service.CalculateAdvancementAsync(1); // Top 2 from heat 1: lanes 1 (10.5) and 2 (10.8) Assert.True(heat1.HeatLanes.ElementAt(0).IsAdvanced); Assert.True(heat1.HeatLanes.ElementAt(1).IsAdvanced); Assert.False(heat1.HeatLanes.ElementAt(2).IsAdvanced); Assert.False(heat1.HeatLanes.ElementAt(3).IsAdvanced); // Top 2 from heat 2: lanes 5 (10.6) and 6 (10.9) Assert.True(heat2.HeatLanes.ElementAt(0).IsAdvanced); Assert.True(heat2.HeatLanes.ElementAt(1).IsAdvanced); Assert.False(heat2.HeatLanes.ElementAt(2).IsAdvanced); Assert.False(heat2.HeatLanes.ElementAt(3).IsAdvanced); } [Fact] public async Task CalculateAdvancement_TopNPlusFastestLosers() { var heat1 = CreateHeat(1, 1, new List { CreateLane(1, 101, 10.5m), CreateLane(2, 102, 10.8m), CreateLane(3, 103, 11.0m), // 3rd in heat 1 → fastest loser candidate CreateLane(4, 104, 11.5m) }); var heat2 = CreateHeat(2, 2, new List { CreateLane(5, 201, 10.6m), CreateLane(6, 202, 10.9m), CreateLane(7, 203, 11.1m), // 3rd in heat 2 → fastest loser candidate CreateLane(8, 204, 11.8m) }); var round = CreateRoundWithHeats(topN: 2, fastestLosers: 2, new List { heat1, heat2 }); _mockUow.Setup(u => u.Rounds.GetWithHeatsAsync(1)).ReturnsAsync(round); _mockUow.Setup(u => u.HeatLanes.Update(It.IsAny())); _mockUow.Setup(u => u.SaveChangesAsync()).ReturnsAsync(1); await _service.CalculateAdvancementAsync(1); // Top 2 from each heat advance as TopN Assert.Equal(AdvanceReason.TopN, heat1.HeatLanes.ElementAt(0).AdvanceReason); Assert.Equal(AdvanceReason.TopN, heat1.HeatLanes.ElementAt(1).AdvanceReason); Assert.Equal(AdvanceReason.TopN, heat2.HeatLanes.ElementAt(0).AdvanceReason); Assert.Equal(AdvanceReason.TopN, heat2.HeatLanes.ElementAt(1).AdvanceReason); // Fastest 2 losers: lane 3 (11.0) and lane 7 (11.1) Assert.True(heat1.HeatLanes.ElementAt(2).IsAdvanced); Assert.Equal(AdvanceReason.FastestLoser, heat1.HeatLanes.ElementAt(2).AdvanceReason); Assert.True(heat2.HeatLanes.ElementAt(2).IsAdvanced); Assert.Equal(AdvanceReason.FastestLoser, heat2.HeatLanes.ElementAt(2).AdvanceReason); // Last in each heat should NOT advance Assert.False(heat1.HeatLanes.ElementAt(3).IsAdvanced); Assert.False(heat2.HeatLanes.ElementAt(3).IsAdvanced); } [Fact] public async Task CalculateAdvancement_DNSExcluded() { var heat1 = CreateHeat(1, 1, new List { CreateLane(1, 101, 10.5m), CreateLane(2, 102, null, isDNS: true), // DNS CreateLane(3, 103, 11.0m), }); var round = CreateRoundWithHeats(topN: 2, fastestLosers: 0, new List { heat1 }); _mockUow.Setup(u => u.Rounds.GetWithHeatsAsync(1)).ReturnsAsync(round); _mockUow.Setup(u => u.HeatLanes.Update(It.IsAny())); _mockUow.Setup(u => u.SaveChangesAsync()).ReturnsAsync(1); await _service.CalculateAdvancementAsync(1); Assert.True(heat1.HeatLanes.ElementAt(0).IsAdvanced); Assert.False(heat1.HeatLanes.ElementAt(1).IsAdvanced); // DNS not advanced Assert.True(heat1.HeatLanes.ElementAt(2).IsAdvanced); } [Fact] public async Task CalculateAdvancement_DNFExcluded() { var heat1 = CreateHeat(1, 1, new List { CreateLane(1, 101, 10.5m), CreateLane(2, 102, 10.8m, isDNF: true), // DNF CreateLane(3, 103, 11.0m), }); var round = CreateRoundWithHeats(topN: 2, fastestLosers: 0, new List { heat1 }); _mockUow.Setup(u => u.Rounds.GetWithHeatsAsync(1)).ReturnsAsync(round); _mockUow.Setup(u => u.HeatLanes.Update(It.IsAny())); _mockUow.Setup(u => u.SaveChangesAsync()).ReturnsAsync(1); await _service.CalculateAdvancementAsync(1); Assert.True(heat1.HeatLanes.ElementAt(0).IsAdvanced); Assert.False(heat1.HeatLanes.ElementAt(1).IsAdvanced); // DNF not advanced Assert.True(heat1.HeatLanes.ElementAt(2).IsAdvanced); } [Fact] public async Task CalculateAdvancement_DQExcluded() { var heat1 = CreateHeat(1, 1, new List { CreateLane(1, 101, 10.5m), CreateLane(2, 102, 10.2m, isDQ: true), // DQ - fastest time but disqualified CreateLane(3, 103, 11.0m), }); var round = CreateRoundWithHeats(topN: 2, fastestLosers: 0, new List { heat1 }); _mockUow.Setup(u => u.Rounds.GetWithHeatsAsync(1)).ReturnsAsync(round); _mockUow.Setup(u => u.HeatLanes.Update(It.IsAny())); _mockUow.Setup(u => u.SaveChangesAsync()).ReturnsAsync(1); await _service.CalculateAdvancementAsync(1); Assert.True(heat1.HeatLanes.ElementAt(0).IsAdvanced); Assert.False(heat1.HeatLanes.ElementAt(1).IsAdvanced); // DQ not advanced Assert.True(heat1.HeatLanes.ElementAt(2).IsAdvanced); } }