Files
sports-division/tests/SportsDivision.Application.Tests/RegistrationEligibilityTests.cs
warringtond 810f721e48 Fix all 45 audited bugs from bug-fixes.md
Verified every finding against the code, then fixed correctness (redirects,
advancement reset, duplicate heats, high-jump attempt entry, placement
ranking with tie handling, delete-dependency 500s), security (secrets out of
config, config-driven admin seed, login lockout, role authorization with
school scoping, heat-time IDOR, forwarded headers, last-admin guards),
schema integrity (nullable+filtered ExistingStudentId, unique indexes for
rounds/heats/bar heights via SchemaIntegrityFixes migration), performance
(N+1 removal in high jump/reports/standings/dashboard, SQL-side student
paging), and hygiene (duplicate notifications, auto-dismiss scope, local
bootstrap-icons, orphaned files, test-data.sql tournament creation).

FluentValidation is now registered; AutoMapper bumped to 14.0.0 (advisory
fully patched only in licence-changed 15.1.1 — documented). 11 new tests;
63/63 passing. Credential rotation and deploy-time DB_CONNECTION_STRING are
required manual follow-ups, documented in bug-fixes.md.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 08:30:53 -04:00

358 lines
11 KiB
C#

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_InactiveStudent_ReturnsFalse()
{
var tel = CreateTel(Sex.Male, SchoolLevel.Secondary);
var student = CreateStudent(Sex.Male);
student.IsActive = false;
SetupTournamentEventLevel(tel);
SetupStudent(student);
var (isEligible, reason) = await _service.CheckEligibilityAsync(1, 1);
Assert.False(isEligible);
Assert.Contains("deactivated", reason!, StringComparison.OrdinalIgnoreCase);
}
[Fact]
public async Task CheckEligibility_InactiveSchool_ReturnsFalse()
{
var tel = CreateTel(Sex.Male, SchoolLevel.Secondary);
var student = CreateStudent(Sex.Male);
var school = CreateSchool(SchoolLevel.Secondary);
school.IsActive = false;
SetupTournamentEventLevel(tel);
SetupStudent(student);
SetupSchool(school);
var (isEligible, reason) = await _service.CheckEligibilityAsync(1, 1);
Assert.False(isEligible);
Assert.Contains("school is deactivated", reason!, StringComparison.OrdinalIgnoreCase);
}
[Fact]
public async Task CheckEligibility_CompletedTournament_ReturnsFalse()
{
var tel = CreateTel(Sex.Male, SchoolLevel.Secondary);
tel.Tournament.Status = TournamentStatus.Completed;
var student = CreateStudent(Sex.Male);
SetupTournamentEventLevel(tel);
SetupStudent(student);
var (isEligible, reason) = await _service.CheckEligibilityAsync(1, 1);
Assert.False(isEligible);
Assert.Contains("completed", reason!, StringComparison.OrdinalIgnoreCase);
}
[Fact]
public async Task CheckEligibility_ArchivedTournament_ReturnsFalse()
{
var tel = CreateTel(Sex.Male, SchoolLevel.Secondary);
tel.Tournament.IsArchived = true;
var student = CreateStudent(Sex.Male);
SetupTournamentEventLevel(tel);
SetupStudent(student);
var (isEligible, reason) = await _service.CheckEligibilityAsync(1, 1);
Assert.False(isEligible);
Assert.Contains("archived", reason!, StringComparison.OrdinalIgnoreCase);
}
[Fact]
public async Task RegisterStudent_RelayOnlyRegistration_ThrowsInsteadOfCrashing()
{
var dto = new Application.DTOs.EventRegistrationCreateDto
{
TournamentEventLevelId = 1,
StudentId = null,
RelayTeamId = 5
};
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
() => _service.RegisterStudentAsync(dto, "tester"));
Assert.Contains("relay", ex.Message, StringComparison.OrdinalIgnoreCase);
}
[Fact]
public async Task RegisterStudent_NeitherStudentNorRelay_Throws()
{
var dto = new Application.DTOs.EventRegistrationCreateDto
{
TournamentEventLevelId = 1,
StudentId = null,
RelayTeamId = null
};
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
() => _service.RegisterStudentAsync(dto, "tester"));
Assert.Contains("student", ex.Message, 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);
}
}