using Moq; using SportsDivision.Application.Services; using SportsDivision.Domain.Entities; using SportsDivision.Domain.Enums; using SportsDivision.Domain.Interfaces; namespace SportsDivision.Application.Tests; public class ScoringPlacementTests { private readonly Mock _mockUow; private readonly ScoringService _service; public ScoringPlacementTests() { _mockUow = new Mock(); _service = new ScoringService(_mockUow.Object, null!); } private void Setup(EventCategory category, List scores) { _mockUow.Setup(u => u.TournamentEventLevels.GetWithRegistrationsAsync(It.IsAny())) .ReturnsAsync(new TournamentEventLevel { TournamentEventLevelId = 1, Event = new Event { Category = category } }); _mockUow.Setup(u => u.Scores.GetByTournamentEventLevelAsync(It.IsAny())) .ReturnsAsync(scores); _mockUow.Setup(u => u.PlacementPointConfigs.GetAllAsync()) .ReturnsAsync(new List { new() { Placement = 1, Points = 10 }, new() { Placement = 2, Points = 8 }, new() { Placement = 3, Points = 6 }, new() { Placement = 4, Points = 5 }, }); _mockUow.Setup(u => u.SaveChangesAsync()).ReturnsAsync(1); } private static Score MakeScore(int regId, decimal performance) => new() { EventRegistrationId = regId, RawPerformance = performance }; [Fact] public async Task CalculatePlacements_FieldEvent_RanksByPerformanceDescending() { // Points-based ranking used to assign arbitrary places when no scoring // constant existed (all CalculatedPoints = 0); ranking is now on the mark. var s1 = MakeScore(1, 10.00m); var s2 = MakeScore(2, 12.50m); var s3 = MakeScore(3, 11.00m); Setup(EventCategory.Field, new List { s1, s2, s3 }); await _service.CalculatePlacementsAsync(1); Assert.Equal(1, s2.Placement); Assert.Equal(2, s3.Placement); Assert.Equal(3, s1.Placement); Assert.Equal(10, s2.PlacementPoints); } [Fact] public async Task CalculatePlacements_TrackEvent_RanksAscending() { var s1 = MakeScore(1, 11.20m); var s2 = MakeScore(2, 10.90m); Setup(EventCategory.Track, new List { s1, s2 }); await _service.CalculatePlacementsAsync(1); Assert.Equal(1, s2.Placement); Assert.Equal(2, s1.Placement); } [Fact] public async Task CalculatePlacements_Ties_SharePlaceAndSplitPooledPoints() { // Two identical throws share 1st (competition ranking 1, 1, 3) and split // the pooled points for places 1 and 2: (10 + 8) / 2 = 9. var s1 = MakeScore(1, 12.50m); var s2 = MakeScore(2, 12.50m); var s3 = MakeScore(3, 11.00m); Setup(EventCategory.Field, new List { s1, s2, s3 }); await _service.CalculatePlacementsAsync(1); Assert.Equal(1, s1.Placement); Assert.Equal(1, s2.Placement); Assert.Equal(9, s1.PlacementPoints); Assert.Equal(9, s2.PlacementPoints); Assert.Equal(3, s3.Placement); Assert.Equal(6, s3.PlacementPoints); } [Fact] public async Task CalculatePlacements_ZeroMark_GetsNoPlacementAndOldPlacementCleared() { var foul = MakeScore(1, 0m); foul.Placement = 1; // stale from an earlier calculation foul.PlacementPoints = 10; var valid = MakeScore(2, 9.50m); Setup(EventCategory.Field, new List { foul, valid }); await _service.CalculatePlacementsAsync(1); Assert.Null(foul.Placement); Assert.Equal(0, foul.PlacementPoints); Assert.Equal(1, valid.Placement); } } public class HeatAdvancementResetTests { [Fact] public async Task CalculateAdvancement_ClearsStaleFlagsBeforeRecalculating() { // An athlete who advanced in a previous calculation but no longer qualifies // after a time correction must lose the flag on recalculation. var stale = new HeatLane { HeatLaneId = 1, Time = 12.5m, IsAdvanced = true, AdvanceReason = AdvanceReason.TopN }; var faster = new HeatLane { HeatLaneId = 2, Time = 11.0m }; var round = new Round { RoundId = 1, AdvanceTopN = 1, AdvanceFastestLosers = 0, Heats = new List { new() { HeatId = 1, HeatLanes = new List { stale, faster } } } }; var mockUow = new Mock(); mockUow.Setup(u => u.Rounds.GetWithHeatsAsync(1)).ReturnsAsync(round); mockUow.Setup(u => u.HeatLanes.Update(It.IsAny())); mockUow.Setup(u => u.SaveChangesAsync()).ReturnsAsync(1); var service = new HeatManagementService(mockUow.Object, null!); await service.CalculateAdvancementAsync(1); Assert.False(stale.IsAdvanced); Assert.Null(stale.AdvanceReason); Assert.True(faster.IsAdvanced); Assert.Equal(AdvanceReason.TopN, faster.AdvanceReason); } }