Introduce unit testing on calculation objects

This commit is contained in:
2025-12-02 20:06:05 -05:00
parent 8039e751d8
commit 1c38003027
10 changed files with 1203 additions and 51 deletions
@@ -0,0 +1,69 @@
using Core.Entities;
namespace Tests.Builders;
/// <summary>
/// Fluent builder for creating AssignmentRequirement test entities.
/// </summary>
public class AssignmentRequirementBuilder
{
private EventDefinition? _eventDefinition = null;
private Student? _student = null;
private Requirement _requirement = Requirement.Include;
public AssignmentRequirementBuilder ForEvent(EventDefinition eventDef)
{
_eventDefinition = eventDef;
return this;
}
public AssignmentRequirementBuilder ForStudent(Student student)
{
_student = student;
return this;
}
public AssignmentRequirementBuilder AsInclude()
{
_requirement = Requirement.Include;
return this;
}
public AssignmentRequirementBuilder AsExclude()
{
_requirement = Requirement.Exclude;
return this;
}
public AssignmentRequirementBuilder WithRequirement(Requirement requirement)
{
_requirement = requirement;
return this;
}
public AssignmentRequirement Build()
{
if (_eventDefinition == null)
throw new InvalidOperationException("AssignmentRequirement must have an event. Call ForEvent() before Build().");
if (_student == null)
throw new InvalidOperationException("AssignmentRequirement must have a student. Call ForStudent() before Build().");
return new AssignmentRequirement(_eventDefinition, _student, _requirement);
}
// Static factory methods
public static AssignmentRequirementBuilder Default() => new AssignmentRequirementBuilder();
public static AssignmentRequirementBuilder Include(EventDefinition eventDef, Student student) =>
new AssignmentRequirementBuilder()
.ForEvent(eventDef)
.ForStudent(student)
.AsInclude();
public static AssignmentRequirementBuilder Exclude(EventDefinition eventDef, Student student) =>
new AssignmentRequirementBuilder()
.ForEvent(eventDef)
.ForStudent(student)
.AsExclude();
}
+80
View File
@@ -0,0 +1,80 @@
using Core.Entities;
namespace Tests.Builders;
/// <summary>
/// Helper utilities for working with test data builders.
/// </summary>
public static class BuilderExtensions
{
/// <summary>
/// Resets all builder ID counters to 1.
/// Call this in test SetUp to ensure test isolation.
/// </summary>
public static void ResetAllBuilders()
{
EventDefinitionBuilder.ResetIdCounter();
StudentBuilder.ResetIdCounter();
TeamBuilder.ResetIdCounter();
}
/// <summary>
/// Creates a student with event rankings for multiple events.
/// </summary>
/// <param name="builder">The student builder</param>
/// <param name="rankedEvents">Dictionary of events to their ranks (1-10)</param>
/// <returns>The builder for method chaining</returns>
public static StudentBuilder WithRankings(this StudentBuilder builder, Dictionary<EventDefinition, int> rankedEvents)
{
foreach (var (eventDef, rank) in rankedEvents)
{
builder.WithRanking(eventDef, rank);
}
return builder;
}
/// <summary>
/// Creates a team with a captain as the first student.
/// </summary>
/// <param name="builder">The team builder</param>
/// <param name="students">Students to add (first one becomes captain)</param>
/// <returns>The builder for method chaining</returns>
public static TeamBuilder WithStudentsAndCaptain(this TeamBuilder builder, params Student[] students)
{
if (students.Length == 0)
return builder;
builder.WithStudents(students);
builder.WithCaptain(students[0]);
return builder;
}
/// <summary>
/// Creates multiple event definitions at once.
/// </summary>
/// <param name="names">Event names</param>
/// <returns>Array of individual event definitions</returns>
public static EventDefinition[] CreateIndividualEvents(params string[] names)
{
return names.Select(name => EventDefinitionBuilder.Individual(name).Build()).ToArray();
}
/// <summary>
/// Creates multiple students at once with sequential names.
/// </summary>
/// <param name="count">Number of students to create</param>
/// <param name="baseFirstName">Base first name (will be suffixed with numbers)</param>
/// <param name="baseLastName">Base last name</param>
/// <returns>Array of students</returns>
public static Student[] CreateStudents(int count, string baseFirstName = "Student", string baseLastName = "Test")
{
var students = new Student[count];
for (int i = 0; i < count; i++)
{
students[i] = StudentBuilder.Default()
.WithName($"{baseFirstName}{i + 1}", baseLastName)
.Build();
}
return students;
}
}
+178
View File
@@ -0,0 +1,178 @@
using Core.Entities;
namespace Tests.Builders;
/// <summary>
/// Fluent builder for creating EventDefinition test entities.
/// </summary>
public class EventDefinitionBuilder
{
private static int _idCounter = 1;
private int _id = _idCounter++;
private string _name = "Test Event";
private string _shortName = "Test";
private EventFormat _format = EventFormat.Individual;
private int _minTeamSize = 1;
private int _maxTeamSize = 1;
private string? _semifinalistActivity = null;
private bool _onSiteActivity = false;
private int _regionalCount = 0;
private int _stateCount = 2;
private bool _presubmission = false;
private string _eligibility = "All students";
private string? _theme = null;
private string? _description = null;
private int? _levelOfEffort = null;
private string? _documentation = null;
private string? _notes = null;
public EventDefinitionBuilder WithName(string name)
{
_name = name;
if (_shortName == "Test") _shortName = name; // Auto-sync short name
return this;
}
public EventDefinitionBuilder WithShortName(string shortName)
{
_shortName = shortName;
return this;
}
public EventDefinitionBuilder AsTeamEvent(int minSize, int maxSize)
{
_format = EventFormat.Team;
_minTeamSize = minSize;
_maxTeamSize = maxSize;
return this;
}
public EventDefinitionBuilder AsIndividualEvent()
{
_format = EventFormat.Individual;
_minTeamSize = 1;
_maxTeamSize = 1;
return this;
}
public EventDefinitionBuilder WithInterview()
{
_semifinalistActivity = "Interview";
return this;
}
public EventDefinitionBuilder WithPresentation()
{
_semifinalistActivity = "Presentation";
return this;
}
public EventDefinitionBuilder WithSemifinalistActivity(string activity)
{
_semifinalistActivity = activity;
return this;
}
public EventDefinitionBuilder AsOnSite()
{
_onSiteActivity = true;
return this;
}
public EventDefinitionBuilder AsRegionalEvent(int count = 3)
{
_regionalCount = count;
return this;
}
public EventDefinitionBuilder WithStateCount(int count)
{
_stateCount = count;
return this;
}
public EventDefinitionBuilder WithPresubmission()
{
_presubmission = true;
return this;
}
public EventDefinitionBuilder WithEligibility(string eligibility)
{
_eligibility = eligibility;
return this;
}
public EventDefinitionBuilder WithTheme(string theme)
{
_theme = theme;
return this;
}
public EventDefinitionBuilder WithDescription(string description)
{
_description = description;
return this;
}
public EventDefinitionBuilder WithLevelOfEffort(int level)
{
_levelOfEffort = level;
return this;
}
public EventDefinitionBuilder WithDocumentation(string documentation)
{
_documentation = documentation;
return this;
}
public EventDefinitionBuilder WithNotes(string notes)
{
_notes = notes;
return this;
}
public EventDefinition Build()
{
return new EventDefinition
{
Id = _id,
Name = _name,
ShortName = _shortName,
EventFormat = _format,
MinTeamSize = _minTeamSize,
MaxTeamSize = _maxTeamSize,
SemifinalistActivity = _semifinalistActivity,
OnSiteActivity = _onSiteActivity,
ChapterEligibilityCountRegionals = _regionalCount,
ChapterEligibilityCountState = _stateCount,
Presubmission = _presubmission,
Eligibility = _eligibility,
Theme = _theme,
Description = _description,
LevelOfEffort = _levelOfEffort,
Documentation = _documentation,
Notes = _notes
};
}
// Static factory methods
public static EventDefinitionBuilder Default() => new EventDefinitionBuilder();
public static EventDefinitionBuilder Individual(string name) =>
new EventDefinitionBuilder()
.WithName(name)
.AsIndividualEvent();
public static EventDefinitionBuilder Team(string name, int minSize, int maxSize) =>
new EventDefinitionBuilder()
.WithName(name)
.AsTeamEvent(minSize, maxSize);
/// <summary>
/// Reset the ID counter for test isolation. Call this in test SetUp.
/// </summary>
public static void ResetIdCounter() => _idCounter = 1;
}
+166
View File
@@ -0,0 +1,166 @@
using Core.Entities;
namespace Tests.Builders;
/// <summary>
/// Fluent builder for creating Student test entities.
/// </summary>
public class StudentBuilder
{
private static int _idCounter = 1;
private int _id = _idCounter++;
private string _firstName = "Test";
private string _lastName = "Student";
private int _grade = 9;
private string? _email = null;
private string? _phoneNumber = null;
private int _tsaYear = 1;
private string? _stateId = null;
private string? _regionalId = null;
private string? _nationalId = null;
private OfficerRole? _officerRole = null;
private List<(EventDefinition eventDef, int rank)> _rankings = new();
public StudentBuilder WithName(string firstName, string lastName)
{
_firstName = firstName;
_lastName = lastName;
return this;
}
public StudentBuilder WithFirstName(string firstName)
{
_firstName = firstName;
return this;
}
public StudentBuilder WithLastName(string lastName)
{
_lastName = lastName;
return this;
}
public StudentBuilder WithGrade(int grade)
{
_grade = grade;
return this;
}
public StudentBuilder WithEmail(string email)
{
_email = email;
return this;
}
public StudentBuilder WithPhone(string phoneNumber)
{
_phoneNumber = phoneNumber;
return this;
}
public StudentBuilder WithTsaYear(int year)
{
_tsaYear = year;
return this;
}
public StudentBuilder WithStateId(string stateId)
{
_stateId = stateId;
return this;
}
public StudentBuilder WithRegionalId(string regionalId)
{
_regionalId = regionalId;
return this;
}
public StudentBuilder WithNationalId(string nationalId)
{
_nationalId = nationalId;
return this;
}
public StudentBuilder AsOfficer(OfficerRole role)
{
_officerRole = role;
return this;
}
public StudentBuilder AsPresident()
{
_officerRole = OfficerRole.President;
return this;
}
public StudentBuilder AsVicePresident()
{
_officerRole = OfficerRole.VicePresident;
return this;
}
public StudentBuilder AsSecretary()
{
_officerRole = OfficerRole.Secretary;
return this;
}
public StudentBuilder AsTreasurer()
{
_officerRole = OfficerRole.Treasurer;
return this;
}
public StudentBuilder WithRanking(EventDefinition eventDef, int rank)
{
_rankings.Add((eventDef, rank));
return this;
}
public Student Build()
{
var student = new Student
{
Id = _id,
FirstName = _firstName,
LastName = _lastName,
Grade = _grade,
Email = _email,
PhoneNumber = _phoneNumber,
TsaYear = _tsaYear,
StateId = _stateId,
RegionalId = _regionalId,
NationalId = _nationalId,
OfficerRole = _officerRole,
Teams = new List<Team>() // Initialize Teams collection
};
// Set up event rankings if any were specified
foreach (var (eventDef, rank) in _rankings)
{
var ranking = new StudentEventRanking
{
Student = student,
EventDefinition = eventDef,
Rank = rank
};
student.EventRankings.Add(ranking);
}
return student;
}
// Static factory methods
public static StudentBuilder Default() => new StudentBuilder();
public static StudentBuilder Create(string firstName, string lastName) =>
new StudentBuilder()
.WithName(firstName, lastName);
/// <summary>
/// Reset the ID counter for test isolation. Call this in test SetUp.
/// </summary>
public static void ResetIdCounter() => _idCounter = 1;
}
@@ -0,0 +1,65 @@
using Core.Entities;
namespace Tests.Builders;
/// <summary>
/// Fluent builder for creating StudentEventRanking test entities.
/// </summary>
public class StudentEventRankingBuilder
{
private Student? _student = null;
private EventDefinition? _eventDefinition = null;
private int _rank = 1;
public StudentEventRankingBuilder ForStudent(Student student)
{
_student = student;
return this;
}
public StudentEventRankingBuilder ForEvent(EventDefinition eventDef)
{
_eventDefinition = eventDef;
return this;
}
public StudentEventRankingBuilder WithRank(int rank)
{
if (rank < 1 || rank > StudentEventRanking.MaxRank)
throw new ArgumentOutOfRangeException(nameof(rank),
$"Rank must be between 1 and {StudentEventRanking.MaxRank}");
_rank = rank;
return this;
}
public StudentEventRanking Build()
{
if (_student == null)
throw new InvalidOperationException("StudentEventRanking must have a student. Call ForStudent() before Build().");
if (_eventDefinition == null)
throw new InvalidOperationException("StudentEventRanking must have an event. Call ForEvent() before Build().");
var ranking = new StudentEventRanking
{
Student = _student,
EventDefinition = _eventDefinition,
Rank = _rank
};
// Set up bidirectional relationship: add to student's EventRankings if not already there
if (!_student.EventRankings.Any(er => er.EventDefinition == _eventDefinition))
{
_student.EventRankings.Add(ranking);
}
return ranking;
}
// Static factory methods
public static StudentEventRankingBuilder Default() => new StudentEventRankingBuilder();
public static StudentEventRankingBuilder Create(Student student, EventDefinition eventDef) =>
new StudentEventRankingBuilder().ForStudent(student).ForEvent(eventDef);
}
+100
View File
@@ -0,0 +1,100 @@
using Core.Entities;
namespace Tests.Builders;
/// <summary>
/// Fluent builder for creating Team test entities.
/// </summary>
public class TeamBuilder
{
private static int _idCounter = 1;
private int _id = _idCounter++;
private EventDefinition? _event = null;
private List<Student> _students = new();
private Student? _captain = null;
private string? _identifier = null;
public TeamBuilder ForEvent(EventDefinition eventDef)
{
_event = eventDef;
return this;
}
public TeamBuilder WithStudent(Student student)
{
if (!_students.Contains(student))
_students.Add(student);
return this;
}
public TeamBuilder WithStudents(params Student[] students)
{
foreach (var student in students)
{
if (!_students.Contains(student))
_students.Add(student);
}
return this;
}
public TeamBuilder WithStudents(IEnumerable<Student> students)
{
foreach (var student in students)
{
if (!_students.Contains(student))
_students.Add(student);
}
return this;
}
public TeamBuilder WithCaptain(Student captain)
{
_captain = captain;
// Ensure captain is in the students list
if (!_students.Contains(captain))
_students.Add(captain);
return this;
}
public TeamBuilder WithIdentifier(string identifier)
{
_identifier = identifier;
return this;
}
public Team Build()
{
if (_event == null)
throw new InvalidOperationException("Team must have an event. Call ForEvent() before Build().");
var team = new Team
{
Id = _id,
Event = _event,
Students = _students.ToList(),
Captain = _captain,
Identifier = _identifier
};
// Set up bidirectional relationship: add team to each student's Teams collection
foreach (var student in _students)
{
if (!student.Teams.Contains(team))
student.Teams.Add(team);
}
return team;
}
// Static factory methods
public static TeamBuilder Default() => new TeamBuilder();
public static TeamBuilder Create(EventDefinition eventDef) =>
new TeamBuilder().ForEvent(eventDef);
/// <summary>
/// Reset the ID counter for test isolation. Call this in test SetUp.
/// </summary>
public static void ResetIdCounter() => _idCounter = 1;
}