Introduce unit testing on calculation objects
This commit is contained in:
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user