Initial Commit
This commit is contained in:
1
tests/SportsDivision.Application.Tests/GlobalUsings.cs
Normal file
1
tests/SportsDivision.Application.Tests/GlobalUsings.cs
Normal file
@@ -0,0 +1 @@
|
||||
global using Xunit;
|
||||
214
tests/SportsDivision.Application.Tests/HeatAdvancementTests.cs
Normal file
214
tests/SportsDivision.Application.Tests/HeatAdvancementTests.cs
Normal file
@@ -0,0 +1,214 @@
|
||||
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<IUnitOfWork> _mockUow;
|
||||
private readonly HeatManagementService _service;
|
||||
|
||||
public HeatAdvancementTests()
|
||||
{
|
||||
_mockUow = new Mock<IUnitOfWork>();
|
||||
_service = new HeatManagementService(_mockUow.Object, null!);
|
||||
}
|
||||
|
||||
private Round CreateRoundWithHeats(int topN, int fastestLosers, List<Heat> heats)
|
||||
{
|
||||
return new Round
|
||||
{
|
||||
RoundId = 1,
|
||||
TournamentEventLevelId = 1,
|
||||
RoundOrder = 1,
|
||||
AdvanceTopN = topN,
|
||||
AdvanceFastestLosers = fastestLosers,
|
||||
Heats = heats
|
||||
};
|
||||
}
|
||||
|
||||
private Heat CreateHeat(int heatId, int heatNumber, List<HeatLane> 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<HeatLane>
|
||||
{
|
||||
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<HeatLane>
|
||||
{
|
||||
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<Heat> { heat1, heat2 });
|
||||
|
||||
_mockUow.Setup(u => u.Rounds.GetWithHeatsAsync(1)).ReturnsAsync(round);
|
||||
_mockUow.Setup(u => u.HeatLanes.Update(It.IsAny<HeatLane>()));
|
||||
_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<HeatLane>
|
||||
{
|
||||
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<HeatLane>
|
||||
{
|
||||
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<Heat> { heat1, heat2 });
|
||||
|
||||
_mockUow.Setup(u => u.Rounds.GetWithHeatsAsync(1)).ReturnsAsync(round);
|
||||
_mockUow.Setup(u => u.HeatLanes.Update(It.IsAny<HeatLane>()));
|
||||
_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<HeatLane>
|
||||
{
|
||||
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<Heat> { heat1 });
|
||||
|
||||
_mockUow.Setup(u => u.Rounds.GetWithHeatsAsync(1)).ReturnsAsync(round);
|
||||
_mockUow.Setup(u => u.HeatLanes.Update(It.IsAny<HeatLane>()));
|
||||
_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<HeatLane>
|
||||
{
|
||||
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<Heat> { heat1 });
|
||||
|
||||
_mockUow.Setup(u => u.Rounds.GetWithHeatsAsync(1)).ReturnsAsync(round);
|
||||
_mockUow.Setup(u => u.HeatLanes.Update(It.IsAny<HeatLane>()));
|
||||
_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<HeatLane>
|
||||
{
|
||||
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<Heat> { heat1 });
|
||||
|
||||
_mockUow.Setup(u => u.Rounds.GetWithHeatsAsync(1)).ReturnsAsync(round);
|
||||
_mockUow.Setup(u => u.HeatLanes.Update(It.IsAny<HeatLane>()));
|
||||
_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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
using Moq;
|
||||
using SportsDivision.Application.Services;
|
||||
using SportsDivision.Domain.Entities;
|
||||
using SportsDivision.Domain.Enums;
|
||||
using SportsDivision.Domain.Interfaces;
|
||||
|
||||
namespace SportsDivision.Application.Tests;
|
||||
|
||||
public class HighJumpEliminationTests
|
||||
{
|
||||
private readonly Mock<IUnitOfWork> _mockUow;
|
||||
private readonly HighJumpService _service;
|
||||
|
||||
public HighJumpEliminationTests()
|
||||
{
|
||||
_mockUow = new Mock<IUnitOfWork>();
|
||||
_service = new HighJumpService(_mockUow.Object, null!);
|
||||
}
|
||||
|
||||
private HighJumpHeight CreateHeight(int heightId, decimal height, int sortOrder, HighJumpAttempt? attempt = null)
|
||||
{
|
||||
var h = new HighJumpHeight
|
||||
{
|
||||
HighJumpHeightId = heightId,
|
||||
Height = height,
|
||||
SortOrder = sortOrder,
|
||||
TournamentEventLevelId = 1,
|
||||
Attempts = new List<HighJumpAttempt>()
|
||||
};
|
||||
if (attempt != null)
|
||||
{
|
||||
attempt.HighJumpHeightId = heightId;
|
||||
h.Attempts.Add(attempt);
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
private HighJumpAttempt CreateAttempt(int regId, HighJumpAttemptResult? a1, HighJumpAttemptResult? a2 = null, HighJumpAttemptResult? a3 = null)
|
||||
{
|
||||
return new HighJumpAttempt
|
||||
{
|
||||
EventRegistrationId = regId,
|
||||
Attempt1 = a1,
|
||||
Attempt2 = a2,
|
||||
Attempt3 = a3
|
||||
};
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task IsEliminated_ThreeConsecutiveFailsAtOneHeight_ReturnsTrue()
|
||||
{
|
||||
var heights = new List<HighJumpHeight>
|
||||
{
|
||||
CreateHeight(1, 1.50m, 1, CreateAttempt(1, HighJumpAttemptResult.Clear)),
|
||||
CreateHeight(2, 1.55m, 2, CreateAttempt(1, HighJumpAttemptResult.Fail, HighJumpAttemptResult.Fail, HighJumpAttemptResult.Fail))
|
||||
};
|
||||
|
||||
_mockUow.Setup(u => u.HighJumpHeights.GetByTournamentEventLevelAsync(1))
|
||||
.ReturnsAsync(heights);
|
||||
_mockUow.Setup(u => u.HighJumpHeights.GetWithAttemptsAsync(1)).ReturnsAsync(heights[0]);
|
||||
_mockUow.Setup(u => u.HighJumpHeights.GetWithAttemptsAsync(2)).ReturnsAsync(heights[1]);
|
||||
|
||||
var result = await _service.IsEliminatedAsync(1, 1);
|
||||
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task IsEliminated_ClearsAllHeights_ReturnsFalse()
|
||||
{
|
||||
var heights = new List<HighJumpHeight>
|
||||
{
|
||||
CreateHeight(1, 1.50m, 1, CreateAttempt(1, HighJumpAttemptResult.Clear)),
|
||||
CreateHeight(2, 1.55m, 2, CreateAttempt(1, HighJumpAttemptResult.Fail, HighJumpAttemptResult.Clear)),
|
||||
CreateHeight(3, 1.60m, 3, CreateAttempt(1, HighJumpAttemptResult.Clear))
|
||||
};
|
||||
|
||||
_mockUow.Setup(u => u.HighJumpHeights.GetByTournamentEventLevelAsync(1))
|
||||
.ReturnsAsync(heights);
|
||||
_mockUow.Setup(u => u.HighJumpHeights.GetWithAttemptsAsync(1)).ReturnsAsync(heights[0]);
|
||||
_mockUow.Setup(u => u.HighJumpHeights.GetWithAttemptsAsync(2)).ReturnsAsync(heights[1]);
|
||||
_mockUow.Setup(u => u.HighJumpHeights.GetWithAttemptsAsync(3)).ReturnsAsync(heights[2]);
|
||||
|
||||
var result = await _service.IsEliminatedAsync(1, 1);
|
||||
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task IsEliminated_TwoFailsThenClear_ResetsCount_ReturnsFalse()
|
||||
{
|
||||
var heights = new List<HighJumpHeight>
|
||||
{
|
||||
CreateHeight(1, 1.50m, 1, CreateAttempt(1, HighJumpAttemptResult.Fail, HighJumpAttemptResult.Fail, HighJumpAttemptResult.Clear)),
|
||||
CreateHeight(2, 1.55m, 2, CreateAttempt(1, HighJumpAttemptResult.Fail, HighJumpAttemptResult.Clear))
|
||||
};
|
||||
|
||||
_mockUow.Setup(u => u.HighJumpHeights.GetByTournamentEventLevelAsync(1))
|
||||
.ReturnsAsync(heights);
|
||||
_mockUow.Setup(u => u.HighJumpHeights.GetWithAttemptsAsync(1)).ReturnsAsync(heights[0]);
|
||||
_mockUow.Setup(u => u.HighJumpHeights.GetWithAttemptsAsync(2)).ReturnsAsync(heights[1]);
|
||||
|
||||
var result = await _service.IsEliminatedAsync(1, 1);
|
||||
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task IsEliminated_NoAttempts_ReturnsFalse()
|
||||
{
|
||||
var heights = new List<HighJumpHeight>
|
||||
{
|
||||
CreateHeight(1, 1.50m, 1)
|
||||
};
|
||||
|
||||
_mockUow.Setup(u => u.HighJumpHeights.GetByTournamentEventLevelAsync(1))
|
||||
.ReturnsAsync(heights);
|
||||
_mockUow.Setup(u => u.HighJumpHeights.GetWithAttemptsAsync(1)).ReturnsAsync(heights[0]);
|
||||
|
||||
var result = await _service.IsEliminatedAsync(1, 1);
|
||||
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task IsEliminated_PassedHeight_FailCountDoesNotReset()
|
||||
{
|
||||
// If an athlete passes a height, fails should continue accumulating
|
||||
var heights = new List<HighJumpHeight>
|
||||
{
|
||||
CreateHeight(1, 1.50m, 1, CreateAttempt(1, HighJumpAttemptResult.Fail, HighJumpAttemptResult.Fail, HighJumpAttemptResult.Pass)),
|
||||
// Height 2: passed (no attempt recorded in this test)
|
||||
CreateHeight(2, 1.55m, 2, CreateAttempt(1, HighJumpAttemptResult.Fail))
|
||||
};
|
||||
|
||||
_mockUow.Setup(u => u.HighJumpHeights.GetByTournamentEventLevelAsync(1))
|
||||
.ReturnsAsync(heights);
|
||||
_mockUow.Setup(u => u.HighJumpHeights.GetWithAttemptsAsync(1)).ReturnsAsync(heights[0]);
|
||||
_mockUow.Setup(u => u.HighJumpHeights.GetWithAttemptsAsync(2)).ReturnsAsync(heights[1]);
|
||||
|
||||
var result = await _service.IsEliminatedAsync(1, 1);
|
||||
|
||||
// 2 fails at height 1 (pass doesn't reset) + 1 fail at height 2 = 3 consecutive fails
|
||||
Assert.True(result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,261 @@
|
||||
using Moq;
|
||||
using SportsDivision.Application.Services;
|
||||
using SportsDivision.Domain.Entities;
|
||||
using SportsDivision.Domain.Enums;
|
||||
using SportsDivision.Domain.Interfaces;
|
||||
|
||||
namespace SportsDivision.Application.Tests;
|
||||
|
||||
public class RegistrationEligibilityTests
|
||||
{
|
||||
private readonly Mock<IUnitOfWork> _mockUow;
|
||||
private readonly RegistrationService _service;
|
||||
|
||||
public RegistrationEligibilityTests()
|
||||
{
|
||||
_mockUow = new Mock<IUnitOfWork>();
|
||||
_service = new RegistrationService(_mockUow.Object, null!);
|
||||
}
|
||||
|
||||
private void SetupTournamentEventLevel(TournamentEventLevel tel)
|
||||
{
|
||||
_mockUow.Setup(u => u.TournamentEventLevels.GetWithRegistrationsAsync(It.IsAny<int>()))
|
||||
.ReturnsAsync(tel);
|
||||
}
|
||||
|
||||
private void SetupStudent(Student student)
|
||||
{
|
||||
_mockUow.Setup(u => u.Students.GetByIdAsync(student.StudentId))
|
||||
.ReturnsAsync(student);
|
||||
}
|
||||
|
||||
private void SetupSchool(School school)
|
||||
{
|
||||
_mockUow.Setup(u => u.Schools.GetByIdAsync(school.SchoolId))
|
||||
.ReturnsAsync(school);
|
||||
}
|
||||
|
||||
private void SetupNotAlreadyRegistered()
|
||||
{
|
||||
_mockUow.Setup(u => u.EventRegistrations.IsStudentRegisteredAsync(It.IsAny<int>(), It.IsAny<int>()))
|
||||
.ReturnsAsync(false);
|
||||
}
|
||||
|
||||
private void SetupAlreadyRegistered()
|
||||
{
|
||||
_mockUow.Setup(u => u.EventRegistrations.IsStudentRegisteredAsync(It.IsAny<int>(), It.IsAny<int>()))
|
||||
.ReturnsAsync(true);
|
||||
}
|
||||
|
||||
private TournamentEventLevel CreateTel(Sex sex = Sex.Male, SchoolLevel schoolLevel = SchoolLevel.Secondary,
|
||||
int? maxAge = null, bool isAgeBased = false, bool ageWaived = false)
|
||||
{
|
||||
return new TournamentEventLevel
|
||||
{
|
||||
TournamentEventLevelId = 1,
|
||||
EventLevel = new EventLevel
|
||||
{
|
||||
Sex = sex,
|
||||
SchoolLevel = schoolLevel,
|
||||
MaxAge = maxAge,
|
||||
IsAgeBased = isAgeBased
|
||||
},
|
||||
AgeRestrictionWaived = ageWaived,
|
||||
Tournament = new Tournament
|
||||
{
|
||||
StartDate = new DateOnly(2024, 6, 1)
|
||||
},
|
||||
Registrations = new List<EventRegistration>()
|
||||
};
|
||||
}
|
||||
|
||||
private Student CreateStudent(Sex sex = Sex.Male, int schoolId = 1, DateOnly? dob = null)
|
||||
{
|
||||
return new Student
|
||||
{
|
||||
StudentId = 1,
|
||||
FirstName = "Test",
|
||||
LastName = "Student",
|
||||
Sex = sex,
|
||||
SchoolId = schoolId,
|
||||
DateOfBirth = dob ?? new DateOnly(2010, 1, 1)
|
||||
};
|
||||
}
|
||||
|
||||
private School CreateSchool(SchoolLevel level = SchoolLevel.Secondary)
|
||||
{
|
||||
return new School
|
||||
{
|
||||
SchoolId = 1,
|
||||
Name = "Test School",
|
||||
SchoolLevel = level,
|
||||
ZoneId = 1
|
||||
};
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CheckEligibility_EligibleStudent_ReturnsTrue()
|
||||
{
|
||||
var tel = CreateTel(Sex.Male, SchoolLevel.Secondary);
|
||||
var student = CreateStudent(Sex.Male);
|
||||
var school = CreateSchool(SchoolLevel.Secondary);
|
||||
|
||||
SetupTournamentEventLevel(tel);
|
||||
SetupStudent(student);
|
||||
SetupSchool(school);
|
||||
SetupNotAlreadyRegistered();
|
||||
|
||||
var (isEligible, reason) = await _service.CheckEligibilityAsync(1, 1);
|
||||
|
||||
Assert.True(isEligible);
|
||||
Assert.Null(reason);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CheckEligibility_SexMismatch_ReturnsFalse()
|
||||
{
|
||||
var tel = CreateTel(Sex.Male, SchoolLevel.Secondary);
|
||||
var student = CreateStudent(Sex.Female);
|
||||
|
||||
SetupTournamentEventLevel(tel);
|
||||
SetupStudent(student);
|
||||
|
||||
var (isEligible, reason) = await _service.CheckEligibilityAsync(1, 1);
|
||||
|
||||
Assert.False(isEligible);
|
||||
Assert.Contains("sex", reason!, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CheckEligibility_SecondaryStudentInPrimaryEvent_ReturnsFalse()
|
||||
{
|
||||
var tel = CreateTel(Sex.Male, SchoolLevel.Primary);
|
||||
var student = CreateStudent(Sex.Male);
|
||||
var school = CreateSchool(SchoolLevel.Secondary);
|
||||
|
||||
SetupTournamentEventLevel(tel);
|
||||
SetupStudent(student);
|
||||
SetupSchool(school);
|
||||
|
||||
var (isEligible, reason) = await _service.CheckEligibilityAsync(1, 1);
|
||||
|
||||
Assert.False(isEligible);
|
||||
Assert.Contains("level", reason!, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CheckEligibility_PrimaryStudentInSecondaryEvent_CompeteUp_ReturnsTrue()
|
||||
{
|
||||
var tel = CreateTel(Sex.Male, SchoolLevel.Secondary);
|
||||
var student = CreateStudent(Sex.Male);
|
||||
var school = CreateSchool(SchoolLevel.Primary);
|
||||
|
||||
SetupTournamentEventLevel(tel);
|
||||
SetupStudent(student);
|
||||
SetupSchool(school);
|
||||
SetupNotAlreadyRegistered();
|
||||
|
||||
var (isEligible, reason) = await _service.CheckEligibilityAsync(1, 1);
|
||||
|
||||
Assert.True(isEligible);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CheckEligibility_AgeExceedsMax_ReturnsFalse()
|
||||
{
|
||||
var tel = CreateTel(Sex.Male, SchoolLevel.Secondary, maxAge: 13, isAgeBased: true);
|
||||
// Student born 2010-01-01, tournament June 2024 → age 14
|
||||
var student = CreateStudent(Sex.Male, dob: new DateOnly(2010, 1, 1));
|
||||
var school = CreateSchool(SchoolLevel.Secondary);
|
||||
|
||||
SetupTournamentEventLevel(tel);
|
||||
SetupStudent(student);
|
||||
SetupSchool(school);
|
||||
|
||||
var (isEligible, reason) = await _service.CheckEligibilityAsync(1, 1);
|
||||
|
||||
Assert.False(isEligible);
|
||||
Assert.Contains("age", reason!, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CheckEligibility_AgeWithinLimit_ReturnsTrue()
|
||||
{
|
||||
var tel = CreateTel(Sex.Male, SchoolLevel.Secondary, maxAge: 15, isAgeBased: true);
|
||||
// Student born 2010-01-01, tournament June 2024 → age 14
|
||||
var student = CreateStudent(Sex.Male, dob: new DateOnly(2010, 1, 1));
|
||||
var school = CreateSchool(SchoolLevel.Secondary);
|
||||
|
||||
SetupTournamentEventLevel(tel);
|
||||
SetupStudent(student);
|
||||
SetupSchool(school);
|
||||
SetupNotAlreadyRegistered();
|
||||
|
||||
var (isEligible, reason) = await _service.CheckEligibilityAsync(1, 1);
|
||||
|
||||
Assert.True(isEligible);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CheckEligibility_AgeExceedsButWaived_ReturnsTrue()
|
||||
{
|
||||
var tel = CreateTel(Sex.Male, SchoolLevel.Secondary, maxAge: 13, isAgeBased: true, ageWaived: true);
|
||||
// Student age 14, max is 13, but waived
|
||||
var student = CreateStudent(Sex.Male, dob: new DateOnly(2010, 1, 1));
|
||||
var school = CreateSchool(SchoolLevel.Secondary);
|
||||
|
||||
SetupTournamentEventLevel(tel);
|
||||
SetupStudent(student);
|
||||
SetupSchool(school);
|
||||
SetupNotAlreadyRegistered();
|
||||
|
||||
var (isEligible, reason) = await _service.CheckEligibilityAsync(1, 1);
|
||||
|
||||
Assert.True(isEligible);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CheckEligibility_AlreadyRegistered_ReturnsFalse()
|
||||
{
|
||||
var tel = CreateTel(Sex.Male, SchoolLevel.Secondary);
|
||||
var student = CreateStudent(Sex.Male);
|
||||
var school = CreateSchool(SchoolLevel.Secondary);
|
||||
|
||||
SetupTournamentEventLevel(tel);
|
||||
SetupStudent(student);
|
||||
SetupSchool(school);
|
||||
SetupAlreadyRegistered();
|
||||
|
||||
var (isEligible, reason) = await _service.CheckEligibilityAsync(1, 1);
|
||||
|
||||
Assert.False(isEligible);
|
||||
Assert.Contains("already registered", reason!, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CheckEligibility_TelNotFound_ReturnsFalse()
|
||||
{
|
||||
_mockUow.Setup(u => u.TournamentEventLevels.GetWithRegistrationsAsync(It.IsAny<int>()))
|
||||
.ReturnsAsync((TournamentEventLevel?)null);
|
||||
|
||||
var (isEligible, reason) = await _service.CheckEligibilityAsync(999, 1);
|
||||
|
||||
Assert.False(isEligible);
|
||||
Assert.Contains("not found", reason!, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CheckEligibility_StudentNotFound_ReturnsFalse()
|
||||
{
|
||||
var tel = CreateTel();
|
||||
SetupTournamentEventLevel(tel);
|
||||
|
||||
_mockUow.Setup(u => u.Students.GetByIdAsync(It.IsAny<int>()))
|
||||
.ReturnsAsync((Student?)null);
|
||||
|
||||
var (isEligible, reason) = await _service.CheckEligibilityAsync(1, 999);
|
||||
|
||||
Assert.False(isEligible);
|
||||
Assert.Contains("not found", reason!, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
155
tests/SportsDivision.Application.Tests/ScoringServiceTests.cs
Normal file
155
tests/SportsDivision.Application.Tests/ScoringServiceTests.cs
Normal file
@@ -0,0 +1,155 @@
|
||||
using SportsDivision.Application.Services;
|
||||
|
||||
namespace SportsDivision.Application.Tests;
|
||||
|
||||
public class ScoringServiceTests
|
||||
{
|
||||
private readonly ScoringService _service;
|
||||
|
||||
public ScoringServiceTests()
|
||||
{
|
||||
// ScoringService is needed; CalculatePoints is a pure method so we can pass nulls for unused deps
|
||||
_service = new ScoringService(null!, null!);
|
||||
}
|
||||
|
||||
// World Athletics scoring formula tests
|
||||
// Track: Points = A * (B - T)^C where T = time in seconds
|
||||
// Field/Jump: Points = A * (P - B)^C where P = performance (distance/height)
|
||||
|
||||
[Fact]
|
||||
public void CalculatePoints_Track_BasicCalculation()
|
||||
{
|
||||
// Example: A=24.63, B=18.0, C=1.81, T=10.0 (100m time)
|
||||
// Points = 24.63 * (18.0 - 10.0)^1.81
|
||||
decimal a = 24.63m;
|
||||
decimal b = 18.0m;
|
||||
decimal c = 1.81m;
|
||||
decimal time = 10.0m;
|
||||
|
||||
var points = _service.CalculatePoints(time, a, b, c, isTrack: true);
|
||||
|
||||
// (18.0 - 10.0) = 8.0
|
||||
// 8.0^1.81 ≈ 49.33
|
||||
// 24.63 * 49.33 ≈ 1215
|
||||
Assert.True(points > 0);
|
||||
Assert.True(points > 1000, $"Expected > 1000 for elite 100m time, got {points}");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CalculatePoints_Track_SlowerTimeLowerPoints()
|
||||
{
|
||||
decimal a = 24.63m;
|
||||
decimal b = 18.0m;
|
||||
decimal c = 1.81m;
|
||||
|
||||
var fastPoints = _service.CalculatePoints(10.0m, a, b, c, isTrack: true);
|
||||
var slowPoints = _service.CalculatePoints(12.0m, a, b, c, isTrack: true);
|
||||
|
||||
Assert.True(fastPoints > slowPoints,
|
||||
$"Fast time ({fastPoints}) should score more than slow time ({slowPoints})");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CalculatePoints_Track_TimeEqualsB_ReturnsZero()
|
||||
{
|
||||
// When T = B, diff = 0, points should be 0
|
||||
decimal a = 24.63m;
|
||||
decimal b = 18.0m;
|
||||
decimal c = 1.81m;
|
||||
|
||||
var points = _service.CalculatePoints(18.0m, a, b, c, isTrack: true);
|
||||
Assert.Equal(0, points);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CalculatePoints_Track_TimeExceedsB_ReturnsZero()
|
||||
{
|
||||
// When T > B, diff is negative, should return 0
|
||||
decimal a = 24.63m;
|
||||
decimal b = 18.0m;
|
||||
decimal c = 1.81m;
|
||||
|
||||
var points = _service.CalculatePoints(20.0m, a, b, c, isTrack: true);
|
||||
Assert.Equal(0, points);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CalculatePoints_Field_BasicCalculation()
|
||||
{
|
||||
// Field: Points = A * (P - B)^C
|
||||
// Example: A=0.14354, B=220, C=1.4, P=800 (shot put in cm)
|
||||
decimal a = 0.14354m;
|
||||
decimal b = 220m;
|
||||
decimal c = 1.4m;
|
||||
decimal performance = 800m;
|
||||
|
||||
var points = _service.CalculatePoints(performance, a, b, c, isTrack: false);
|
||||
|
||||
Assert.True(points > 0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CalculatePoints_Field_HigherPerformanceMorePoints()
|
||||
{
|
||||
decimal a = 0.14354m;
|
||||
decimal b = 220m;
|
||||
decimal c = 1.4m;
|
||||
|
||||
var highPoints = _service.CalculatePoints(800m, a, b, c, isTrack: false);
|
||||
var lowPoints = _service.CalculatePoints(600m, a, b, c, isTrack: false);
|
||||
|
||||
Assert.True(highPoints > lowPoints,
|
||||
$"Higher performance ({highPoints}) should score more than lower ({lowPoints})");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CalculatePoints_Field_PerformanceEqualsB_ReturnsZero()
|
||||
{
|
||||
decimal a = 0.14354m;
|
||||
decimal b = 220m;
|
||||
decimal c = 1.4m;
|
||||
|
||||
var points = _service.CalculatePoints(220m, a, b, c, isTrack: false);
|
||||
Assert.Equal(0, points);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CalculatePoints_Field_PerformanceBelowB_ReturnsZero()
|
||||
{
|
||||
decimal a = 0.14354m;
|
||||
decimal b = 220m;
|
||||
decimal c = 1.4m;
|
||||
|
||||
var points = _service.CalculatePoints(100m, a, b, c, isTrack: false);
|
||||
Assert.Equal(0, points);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CalculatePoints_FloorsTruncatesDecimal()
|
||||
{
|
||||
// Use values that would produce a fractional result
|
||||
decimal a = 1.0m;
|
||||
decimal b = 10.0m;
|
||||
decimal c = 1.0m;
|
||||
decimal performance = 5.0m;
|
||||
|
||||
// Track: Points = 1.0 * (10.0 - 5.0)^1.0 = 5.0 → Floor = 5
|
||||
var points = _service.CalculatePoints(performance, a, b, c, isTrack: true);
|
||||
Assert.Equal(5, points);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CalculatePoints_Track_KnownWorldAthleticsValues()
|
||||
{
|
||||
// Men's 100m: A=25.4347, B=18, C=1.81
|
||||
// 10.00s should give ~1096 points
|
||||
decimal a = 25.4347m;
|
||||
decimal b = 18.0m;
|
||||
decimal c = 1.81m;
|
||||
|
||||
var points = _service.CalculatePoints(10.0m, a, b, c, isTrack: true);
|
||||
|
||||
// Expected approximately 1096 for 10.00s 100m
|
||||
Assert.InRange(points, 1050, 1150);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\SportsDivision.Application\SportsDivision.Application.csproj" />
|
||||
<ProjectReference Include="..\..\src\SportsDivision.Domain\SportsDivision.Domain.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.*" />
|
||||
<PackageReference Include="xunit" Version="2.*" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.*" />
|
||||
<PackageReference Include="Moq" Version="4.*" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
Reference in New Issue
Block a user