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;
|
||||||
|
}
|
||||||
@@ -1,19 +1,58 @@
|
|||||||
using Core.Calculation;
|
using Core.Calculation;
|
||||||
using Tests.Parsers;
|
using Core.Entities;
|
||||||
|
using Tests.Builders;
|
||||||
|
using Tests.Fixtures;
|
||||||
|
|
||||||
namespace Tests.Calculation;
|
namespace Tests.Calculation;
|
||||||
|
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class DataProcessingTests
|
public class DataProcessingTests
|
||||||
{
|
{
|
||||||
|
[SetUp]
|
||||||
|
public void SetUp()
|
||||||
|
{
|
||||||
|
BuilderExtensions.ResetAllBuilders();
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void GetEventStudentRankingsTest()
|
public void GetEventStudentRankingsTest()
|
||||||
{
|
{
|
||||||
var events = TestEntityHandler.GetEvents();
|
// Create test data with explicit rankings
|
||||||
var students = TestEntityHandler.GetStudents(events);
|
var flight = EventDefinitionBuilder.Individual("Flight").Build();
|
||||||
var rankings = TestEntityHandler.GetStudentEventRankings(students, events);
|
var coding = EventDefinitionBuilder.Individual("Coding").Build();
|
||||||
|
var robotics = EventDefinitionBuilder.Team("Robotics", 2, 5).Build();
|
||||||
|
|
||||||
foreach (var ranking in rankings)
|
var alice = StudentBuilder.Default()
|
||||||
|
.WithName("Alice", "Anderson")
|
||||||
|
.WithGrade(9)
|
||||||
|
.WithRanking(flight, rank: 1)
|
||||||
|
.WithRanking(coding, rank: 2)
|
||||||
|
.WithRanking(robotics, rank: 3)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var bob = StudentBuilder.Default()
|
||||||
|
.WithName("Bob", "Brown")
|
||||||
|
.WithGrade(10)
|
||||||
|
.WithRanking(coding, rank: 1)
|
||||||
|
.WithRanking(flight, rank: 3)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
// Verify rankings are accessible
|
||||||
|
Assert.That(alice.EventRankings, Has.Count.EqualTo(3));
|
||||||
|
Assert.That(bob.EventRankings, Has.Count.EqualTo(2));
|
||||||
|
|
||||||
|
// Verify rankings are correctly associated
|
||||||
|
var aliceFlightRanking = alice.EventRankings.First(r => r.EventDefinition == flight);
|
||||||
|
Assert.That(aliceFlightRanking.Rank, Is.EqualTo(1));
|
||||||
|
Assert.That(aliceFlightRanking.Student, Is.EqualTo(alice));
|
||||||
|
|
||||||
|
// Print rankings for verification (matching original test output)
|
||||||
|
var allRankings = new[] { alice, bob }
|
||||||
|
.SelectMany(s => s.EventRankings)
|
||||||
|
.OrderBy(r => r.EventDefinition.Name)
|
||||||
|
.ThenBy(r => r.Student.FirstName);
|
||||||
|
|
||||||
|
foreach (var ranking in allRankings)
|
||||||
{
|
{
|
||||||
Console.WriteLine(ranking.EventDefinition.Name);
|
Console.WriteLine(ranking.EventDefinition.Name);
|
||||||
Console.WriteLine($"{ranking.Student.FirstName}: {ranking.Rank}");
|
Console.WriteLine($"{ranking.Student.FirstName}: {ranking.Rank}");
|
||||||
|
|||||||
@@ -1,24 +1,175 @@
|
|||||||
using Core.Calculation;
|
using Core.Calculation;
|
||||||
using Core.Entities;
|
using Core.Entities;
|
||||||
using Core.Parsers;
|
using Tests.Builders;
|
||||||
using Tests.Parsers;
|
using Tests.Fixtures;
|
||||||
using EventAssignment = Core.Calculation.EventAssignment;
|
using EventAssignment = Core.Calculation.EventAssignment;
|
||||||
|
|
||||||
namespace Tests.Calculation;
|
namespace Tests.Calculation;
|
||||||
|
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class EventAssignmentTests
|
public class EventAssignmentTests
|
||||||
{
|
{
|
||||||
|
[SetUp]
|
||||||
|
public void SetUp()
|
||||||
|
{
|
||||||
|
BuilderExtensions.ResetAllBuilders();
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void SolutionTest()
|
public async Task SolutionTest()
|
||||||
{
|
{
|
||||||
var events = TestEntityHandler.GetEvents();
|
// Create custom test data with sufficient rankings for EventAssignment
|
||||||
var students = TestEntityHandler.GetStudents(events);
|
// Need enough events and rankings to satisfy the default parameters:
|
||||||
|
// - 2-4 events per student
|
||||||
|
// - 6-8 effort points per student
|
||||||
|
// - At least 1 regional event
|
||||||
|
// - At least 1 on-site activity
|
||||||
|
|
||||||
var eventAssignment = new EventAssignment(events, students, new AssignmentParameters());
|
var flight = EventDefinitionBuilder.Individual("Flight").AsRegionalEvent().Build();
|
||||||
var solution = eventAssignment.Solve().Result;
|
var coding = EventDefinitionBuilder.Individual("Coding").AsRegionalEvent().Build();
|
||||||
|
var speech = EventDefinitionBuilder.Individual("Prepared Speech").AsOnSite().Build();
|
||||||
|
var photo = EventDefinitionBuilder.Individual("Digital Photography").Build();
|
||||||
|
var essays = EventDefinitionBuilder.Individual("Essays on Technology").Build();
|
||||||
|
|
||||||
//var teamWriter = new TeamWriter(solution.Teams, @"c:\temp\teams.csv");
|
var robotics = EventDefinitionBuilder.Team("Robotics", 2, 5).AsRegionalEvent().AsOnSite().Build();
|
||||||
//teamWriter.Write();
|
var biotech = EventDefinitionBuilder.Team("Biotechnology", 2, 6).AsRegionalEvent().Build();
|
||||||
|
var webDesign = EventDefinitionBuilder.Team("Website Design", 3, 6).Build();
|
||||||
|
var videoGame = EventDefinitionBuilder.Team("Video Game Design", 2, 6).Build();
|
||||||
|
var engineering = EventDefinitionBuilder.Team("Engineering Design", 3, 6).AsOnSite().Build();
|
||||||
|
|
||||||
|
var events = new[] { flight, coding, speech, photo, essays, robotics, biotech, webDesign, videoGame, engineering };
|
||||||
|
|
||||||
|
// Create students with diverse rankings (each ranks 7-8 events)
|
||||||
|
var alice = StudentBuilder.Default()
|
||||||
|
.WithName("Alice", "A").WithGrade(9)
|
||||||
|
.WithRanking(flight, 1).WithRanking(robotics, 2).WithRanking(biotech, 3)
|
||||||
|
.WithRanking(speech, 4).WithRanking(webDesign, 5).WithRanking(coding, 6)
|
||||||
|
.WithRanking(videoGame, 7)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var bob = StudentBuilder.Default()
|
||||||
|
.WithName("Bob", "B").WithGrade(10)
|
||||||
|
.WithRanking(coding, 1).WithRanking(engineering, 2).WithRanking(robotics, 3)
|
||||||
|
.WithRanking(speech, 4).WithRanking(biotech, 5).WithRanking(photo, 6)
|
||||||
|
.WithRanking(videoGame, 7)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var carol = StudentBuilder.Default()
|
||||||
|
.WithName("Carol", "C").WithGrade(11)
|
||||||
|
.WithRanking(photo, 1).WithRanking(webDesign, 2).WithRanking(engineering, 3)
|
||||||
|
.WithRanking(speech, 4).WithRanking(biotech, 5).WithRanking(flight, 6)
|
||||||
|
.WithRanking(coding, 7)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var david = StudentBuilder.Default()
|
||||||
|
.WithName("David", "D").WithGrade(9)
|
||||||
|
.WithRanking(essays, 1).WithRanking(robotics, 2).WithRanking(videoGame, 3)
|
||||||
|
.WithRanking(engineering, 4).WithRanking(speech, 5).WithRanking(coding, 6)
|
||||||
|
.WithRanking(biotech, 7)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var eve = StudentBuilder.Default()
|
||||||
|
.WithName("Eve", "E").WithGrade(10)
|
||||||
|
.WithRanking(flight, 1).WithRanking(webDesign, 2).WithRanking(engineering, 3)
|
||||||
|
.WithRanking(robotics, 4).WithRanking(photo, 5).WithRanking(coding, 6)
|
||||||
|
.WithRanking(speech, 7)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var frank = StudentBuilder.Default()
|
||||||
|
.WithName("Frank", "F").WithGrade(11)
|
||||||
|
.WithRanking(coding, 1).WithRanking(biotech, 2).WithRanking(videoGame, 3)
|
||||||
|
.WithRanking(speech, 4).WithRanking(webDesign, 5).WithRanking(flight, 6)
|
||||||
|
.WithRanking(robotics, 7)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var students = new[] { alice, bob, carol, david, eve, frank };
|
||||||
|
|
||||||
|
// Use relaxed parameters for test
|
||||||
|
var parameters = new AssignmentParameters(
|
||||||
|
effortLowerBound: 4,
|
||||||
|
effortUpperBound: 7,
|
||||||
|
eventsLowerBound: 2,
|
||||||
|
eventsUpperBound: 4,
|
||||||
|
requireRegional: false, // Relax for test simplicity
|
||||||
|
requireOnSite: false // Relax for test simplicity
|
||||||
|
);
|
||||||
|
|
||||||
|
var eventAssignment = new EventAssignment(events, students, parameters);
|
||||||
|
var solution = await eventAssignment.Solve();
|
||||||
|
|
||||||
|
// Verify solution
|
||||||
|
Assert.That(solution.Teams, Is.Not.Null);
|
||||||
|
Assert.That(solution.Status, Is.EqualTo("Optimal").Or.EqualTo("Feasible"),
|
||||||
|
$"Solution should be Optimal or Feasible, but was {solution.Status}");
|
||||||
|
|
||||||
|
// Print solution summary for verification
|
||||||
|
Console.WriteLine($"Solution Status: {solution.Status}");
|
||||||
|
Console.WriteLine($"Teams Created: {solution.Teams.Length}");
|
||||||
|
Console.WriteLine($"Students Assigned: {solution.Teams.SelectMany(t => t.Students).Distinct().Count()}");
|
||||||
|
|
||||||
|
// Verify each team has valid structure
|
||||||
|
foreach (var team in solution.Teams)
|
||||||
|
{
|
||||||
|
Assert.That(team.Event, Is.Not.Null, $"Team should have an event");
|
||||||
|
Assert.That(team.Students, Is.Not.Empty, $"Team for {team.Event.Name} should have students");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task SolutionTest_WithMinimalData()
|
||||||
|
{
|
||||||
|
// Test with minimal custom data to verify basic functionality
|
||||||
|
var flight = EventDefinitionBuilder.Individual("Flight").Build();
|
||||||
|
var coding = EventDefinitionBuilder.Individual("Coding").Build();
|
||||||
|
var robotics = EventDefinitionBuilder.Team("Robotics", 2, 5).AsRegionalEvent().Build();
|
||||||
|
var biotech = EventDefinitionBuilder.Team("Biotechnology", 2, 6).AsRegionalEvent().Build();
|
||||||
|
|
||||||
|
var events = new[] { flight, coding, robotics, biotech };
|
||||||
|
|
||||||
|
var alice = StudentBuilder.Default()
|
||||||
|
.WithName("Alice", "A")
|
||||||
|
.WithGrade(9)
|
||||||
|
.WithRanking(flight, 1)
|
||||||
|
.WithRanking(robotics, 2)
|
||||||
|
.WithRanking(biotech, 3)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var bob = StudentBuilder.Default()
|
||||||
|
.WithName("Bob", "B")
|
||||||
|
.WithGrade(10)
|
||||||
|
.WithRanking(coding, 1)
|
||||||
|
.WithRanking(robotics, 2)
|
||||||
|
.WithRanking(biotech, 3)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var carol = StudentBuilder.Default()
|
||||||
|
.WithName("Carol", "C")
|
||||||
|
.WithGrade(11)
|
||||||
|
.WithRanking(flight, 2)
|
||||||
|
.WithRanking(biotech, 1)
|
||||||
|
.WithRanking(robotics, 3)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var students = new[] { alice, bob, carol };
|
||||||
|
|
||||||
|
// Run the event assignment algorithm with minimal data
|
||||||
|
var parameters = new AssignmentParameters(
|
||||||
|
effortLowerBound: 2,
|
||||||
|
effortUpperBound: 4,
|
||||||
|
eventsLowerBound: 2,
|
||||||
|
eventsUpperBound: 3,
|
||||||
|
requireRegional: false,
|
||||||
|
requireOnSite: false
|
||||||
|
);
|
||||||
|
|
||||||
|
var eventAssignment = new EventAssignment(events, students, parameters);
|
||||||
|
var solution = await eventAssignment.Solve();
|
||||||
|
|
||||||
|
// Verify solution
|
||||||
|
Assert.That(solution.Status, Is.EqualTo("Optimal").Or.EqualTo("Feasible"));
|
||||||
|
Assert.That(solution.Teams, Is.Not.Empty, "Should create some teams");
|
||||||
|
|
||||||
|
Console.WriteLine($"Minimal Test - Status: {solution.Status}");
|
||||||
|
Console.WriteLine($"Teams: {solution.Teams.Length}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,12 +1,19 @@
|
|||||||
using Core.Calculation;
|
using Core.Calculation;
|
||||||
using Core.Entities;
|
using Core.Entities;
|
||||||
using Tests.Parsers;
|
using Tests.Builders;
|
||||||
|
using Tests.Fixtures;
|
||||||
|
|
||||||
namespace Tests.Calculation;
|
namespace Tests.Calculation;
|
||||||
|
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class TeamSchedulerTest
|
public class TeamSchedulerTest
|
||||||
{
|
{
|
||||||
|
[SetUp]
|
||||||
|
public void SetUp()
|
||||||
|
{
|
||||||
|
BuilderExtensions.ResetAllBuilders();
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void Prototype_Test()
|
public void Prototype_Test()
|
||||||
{
|
{
|
||||||
@@ -17,40 +24,57 @@ public class TeamSchedulerTest
|
|||||||
[Test]
|
[Test]
|
||||||
public void SolutionTest()
|
public void SolutionTest()
|
||||||
{
|
{
|
||||||
var events = TestEntityHandler.GetEvents();
|
// Create test data with regional team events for TeamScheduler
|
||||||
var students = TestEntityHandler.GetStudents(events);
|
var robotics = EventDefinitionBuilder.Team("Robotics", 2, 5).AsRegionalEvent().Build();
|
||||||
|
var biotech = EventDefinitionBuilder.Team("Biotechnology", 2, 6).AsRegionalEvent().Build();
|
||||||
|
var webDesign = EventDefinitionBuilder.Team("Website Design", 3, 6).AsRegionalEvent().Build();
|
||||||
|
var videoGame = EventDefinitionBuilder.Team("Video Game Design", 2, 6).AsRegionalEvent().Build();
|
||||||
|
var engineering = EventDefinitionBuilder.Team("Engineering Design", 3, 6).AsRegionalEvent().Build();
|
||||||
|
var cybersecurity = EventDefinitionBuilder.Team("Cybersecurity", 2, 6).AsRegionalEvent().Build();
|
||||||
|
|
||||||
var allTeams = TestEntityHandler.GetTeams(events, students);
|
var events = new[] { robotics, biotech, webDesign, videoGame, engineering, cybersecurity };
|
||||||
var teams = allTeams;
|
|
||||||
|
|
||||||
teams =
|
// Create students
|
||||||
(from e in events
|
var alice = StudentBuilder.Default().WithName("Alice", "A").WithGrade(9).WithTsaYear(1).Build();
|
||||||
from t in teams
|
var bob = StudentBuilder.Default().WithName("Bob", "B").WithGrade(10).WithTsaYear(2).Build();
|
||||||
where t.Event == e
|
var carol = StudentBuilder.Default().WithName("Carol", "C").WithGrade(11).WithTsaYear(3).Build();
|
||||||
//&& t.Students.Count > 1
|
var david = StudentBuilder.Default().WithName("David", "D").WithGrade(9).WithTsaYear(1).Build();
|
||||||
&& (e.EventFormat == EventFormat.Team && e.RegionalEvent)
|
var eve = StudentBuilder.Default().WithName("Eve", "E").WithGrade(10).WithTsaYear(2).Build();
|
||||||
select t).ToArray();
|
var frank = StudentBuilder.Default().WithName("Frank", "F").WithGrade(11).WithTsaYear(3).Build();
|
||||||
teams =
|
var grace = StudentBuilder.Default().WithName("Grace", "G").WithGrade(9).WithTsaYear(1).Build();
|
||||||
teams.Where(t => !t.Event.Name.Contains("Tech Bowl")).ToArray();
|
var henry = StudentBuilder.Default().WithName("Henry", "H").WithGrade(10).WithTsaYear(2).Build();
|
||||||
|
|
||||||
//var eventAssignment = new EventAssignment(events, students);
|
var students = new[] { alice, bob, carol, david, eve, frank, grace, henry };
|
||||||
//var teams = eventAssignment.Solve();
|
|
||||||
|
|
||||||
TeamSchedulerSolution solution;
|
// Create teams with student assignments
|
||||||
if (true)
|
var allTeams = new[]
|
||||||
{
|
{
|
||||||
var teamScheduler = new TeamScheduler(teams, 3, students);
|
TeamBuilder.Default().ForEvent(robotics).WithStudentsAndCaptain(alice, bob, carol).WithIdentifier("Robotics A").Build(),
|
||||||
solution = teamScheduler.Solve();
|
TeamBuilder.Default().ForEvent(biotech).WithStudentsAndCaptain(david, eve, frank).WithIdentifier("Biotech A").Build(),
|
||||||
}
|
TeamBuilder.Default().ForEvent(webDesign).WithStudentsAndCaptain(grace, henry, alice).WithIdentifier("WebDesign A").Build(),
|
||||||
else
|
TeamBuilder.Default().ForEvent(videoGame).WithStudentsAndCaptain(bob, david).WithIdentifier("VideoGame A").Build(),
|
||||||
{
|
TeamBuilder.Default().ForEvent(engineering).WithStudentsAndCaptain(carol, eve, frank).WithIdentifier("Engineering A").Build(),
|
||||||
var teamScheduler = new TeamScheduler_DecisionTree(teams, 3);
|
TeamBuilder.Default().ForEvent(cybersecurity).WithStudentsAndCaptain(grace, henry).WithIdentifier("Cybersecurity A").Build()
|
||||||
solution = teamScheduler.Solve();
|
};
|
||||||
}
|
|
||||||
|
|
||||||
//solution = new UnassignedStudentScheduler(allTeams, solution.TimeSlots).ScheduleStrategy(UnassignedScheduleStrategy.BiggestGroup);
|
// Filter to only regional team events (matching original test logic)
|
||||||
//solution = new UnassignedStudentScheduler(allTeams, solution.TimeSlots).ScheduleStrategy(UnassignedScheduleStrategy.IndividualEvents);
|
var teams = allTeams
|
||||||
|
.Where(t => t.Event.EventFormat == EventFormat.Team && t.Event.RegionalEvent)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
// Verify we have teams to schedule
|
||||||
|
Assert.That(teams, Is.Not.Empty, "Should have teams to schedule");
|
||||||
|
|
||||||
|
// Run TeamScheduler with 3 time slots
|
||||||
|
var teamScheduler = new TeamScheduler(teams, 3, students);
|
||||||
|
var solution = teamScheduler.Solve();
|
||||||
|
|
||||||
|
// Verify solution
|
||||||
|
Assert.That(solution, Is.Not.Null);
|
||||||
|
Assert.That(solution.TimeSlots, Is.Not.Null);
|
||||||
|
Assert.That(solution.TimeSlots.Length, Is.EqualTo(3), "Should have 3 time slots");
|
||||||
|
|
||||||
|
// Print solution (matching original test output format)
|
||||||
var i = 1;
|
var i = 1;
|
||||||
foreach (var slot in solution.TimeSlots)
|
foreach (var slot in solution.TimeSlots)
|
||||||
{
|
{
|
||||||
@@ -78,12 +102,5 @@ public class TeamSchedulerTest
|
|||||||
Console.WriteLine("\tunassigned");
|
Console.WriteLine("\tunassigned");
|
||||||
Console.WriteLine($"\t\t{string.Join(", ", unassigned.Select(s => s.FirstName))}");
|
Console.WriteLine($"\t\t{string.Join(", ", unassigned.Select(s => s.FirstName))}");
|
||||||
}
|
}
|
||||||
|
|
||||||
//var allScheduledTeams = timeSlots.SelectMany(list => list).GroupBy(t => t.Name).SelectMany(g => g).Distinct();
|
|
||||||
//foreach (var allScheduledTeam in allScheduledTeams.OrderBy(a => a.Name))
|
|
||||||
//{
|
|
||||||
// Console.WriteLine($"{allScheduledTeam.Name}");
|
|
||||||
// Console.WriteLine($"\t{string.Join(", ", allScheduledTeam.Students.Select(s => s.FirstName))}");
|
|
||||||
//}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,287 @@
|
|||||||
|
using Core.Entities;
|
||||||
|
using Tests.Builders;
|
||||||
|
|
||||||
|
namespace Tests.Fixtures;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Pre-configured test data fixtures for different testing scenarios.
|
||||||
|
/// </summary>
|
||||||
|
public static class TestDataFixtures
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Small dataset for simple unit tests (5 events, 6 students, minimal teams).
|
||||||
|
/// </summary>
|
||||||
|
public static class Small
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a small set of test data: 5 events, 6 students, and 3 teams.
|
||||||
|
/// Suitable for simple unit tests that don't require realistic volume.
|
||||||
|
/// </summary>
|
||||||
|
public static (EventDefinition[] Events, Student[] Students, Team[] Teams) Full()
|
||||||
|
{
|
||||||
|
BuilderExtensions.ResetAllBuilders();
|
||||||
|
|
||||||
|
var events = new[]
|
||||||
|
{
|
||||||
|
EventDefinitionBuilder.Individual("Flight").Build(),
|
||||||
|
EventDefinitionBuilder.Individual("Coding").Build(),
|
||||||
|
EventDefinitionBuilder.Team("Biotechnology", 2, 6).WithInterview().Build(),
|
||||||
|
EventDefinitionBuilder.Team("Robotics", 2, 5).AsOnSite().Build(),
|
||||||
|
EventDefinitionBuilder.Individual("Essays on Technology").Build()
|
||||||
|
};
|
||||||
|
|
||||||
|
var students = new[]
|
||||||
|
{
|
||||||
|
StudentBuilder.Default().WithName("Alice", "Anderson").WithGrade(9).Build(),
|
||||||
|
StudentBuilder.Default().WithName("Bob", "Brown").WithGrade(10).Build(),
|
||||||
|
StudentBuilder.Default().WithName("Carol", "Clark").WithGrade(11).Build(),
|
||||||
|
StudentBuilder.Default().WithName("David", "Davis").WithGrade(9).Build(),
|
||||||
|
StudentBuilder.Default().WithName("Eve", "Evans").WithGrade(10).Build(),
|
||||||
|
StudentBuilder.Default().WithName("Frank", "Foster").WithGrade(11).Build()
|
||||||
|
};
|
||||||
|
|
||||||
|
var teams = new[]
|
||||||
|
{
|
||||||
|
TeamBuilder.Default().ForEvent(events[2]) // Biotechnology
|
||||||
|
.WithStudentsAndCaptain(students[0], students[1])
|
||||||
|
.Build(),
|
||||||
|
TeamBuilder.Default().ForEvent(events[3]) // Robotics
|
||||||
|
.WithStudentsAndCaptain(students[2], students[3], students[4])
|
||||||
|
.Build(),
|
||||||
|
TeamBuilder.Default().ForEvent(events[0]) // Flight (individual)
|
||||||
|
.WithStudent(students[5])
|
||||||
|
.Build()
|
||||||
|
};
|
||||||
|
|
||||||
|
return (events, students, teams);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns just events from the small fixture.
|
||||||
|
/// </summary>
|
||||||
|
public static EventDefinition[] Events()
|
||||||
|
{
|
||||||
|
BuilderExtensions.ResetAllBuilders();
|
||||||
|
return Full().Events;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns just students from the small fixture.
|
||||||
|
/// </summary>
|
||||||
|
public static Student[] Students()
|
||||||
|
{
|
||||||
|
BuilderExtensions.ResetAllBuilders();
|
||||||
|
return Full().Students;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Large dataset for algorithm tests requiring realistic volume (25 events, 18 students, 15+ teams).
|
||||||
|
/// </summary>
|
||||||
|
public static class Large
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a realistic large set of test data: 25 events, 18 students, and 15+ teams.
|
||||||
|
/// Includes real TSA event names and realistic team configurations.
|
||||||
|
/// Suitable for testing algorithms like EventAssignment and TeamScheduler.
|
||||||
|
/// </summary>
|
||||||
|
public static (EventDefinition[] Events, Student[] Students, Team[] Teams) Full()
|
||||||
|
{
|
||||||
|
BuilderExtensions.ResetAllBuilders();
|
||||||
|
|
||||||
|
// Real TSA events with realistic configurations
|
||||||
|
var events = new[]
|
||||||
|
{
|
||||||
|
// Individual events (some are regional)
|
||||||
|
EventDefinitionBuilder.Individual("Architectural Design").WithStateCount(3).AsRegionalEvent().Build(),
|
||||||
|
EventDefinitionBuilder.Individual("Biotechnology Design").WithInterview().AsRegionalEvent().Build(),
|
||||||
|
EventDefinitionBuilder.Individual("Coding").WithStateCount(3).Build(),
|
||||||
|
EventDefinitionBuilder.Individual("Digital Photography").AsRegionalEvent().Build(),
|
||||||
|
EventDefinitionBuilder.Individual("Essays on Technology").Build(),
|
||||||
|
EventDefinitionBuilder.Individual("Extemporaneous Speech").AsOnSite().Build(),
|
||||||
|
EventDefinitionBuilder.Individual("Flight").WithStateCount(3).AsRegionalEvent().Build(),
|
||||||
|
EventDefinitionBuilder.Individual("Prepared Speech").AsOnSite().Build(),
|
||||||
|
EventDefinitionBuilder.Individual("Scientific Visualization").Build(),
|
||||||
|
EventDefinitionBuilder.Individual("System Control Technology").Build(),
|
||||||
|
|
||||||
|
// Team events (2-6 members, many are regional)
|
||||||
|
EventDefinitionBuilder.Team("Animatronics", 2, 6).WithInterview().AsRegionalEvent().Build(),
|
||||||
|
EventDefinitionBuilder.Team("Chapter Team", 6, 6).WithPresentation().Build(),
|
||||||
|
EventDefinitionBuilder.Team("Cybersecurity", 2, 6).AsRegionalEvent().Build(),
|
||||||
|
EventDefinitionBuilder.Team("Debate", 2, 2).AsOnSite().Build(),
|
||||||
|
EventDefinitionBuilder.Team("Dragster", 1, 3).AsRegionalEvent().Build(),
|
||||||
|
EventDefinitionBuilder.Team("Engineering Design", 3, 6).WithInterview().AsRegionalEvent().Build(),
|
||||||
|
EventDefinitionBuilder.Team("Fashion Design", 2, 6).WithInterview().AsRegionalEvent().Build(),
|
||||||
|
EventDefinitionBuilder.Team("Forensic Technology", 2, 6).Build(),
|
||||||
|
EventDefinitionBuilder.Team("Future Technology Teacher", 2, 6).WithPresentation().AsRegionalEvent().Build(),
|
||||||
|
EventDefinitionBuilder.Team("Junior Solar Sprint", 2, 3).AsRegionalEvent().Build(),
|
||||||
|
EventDefinitionBuilder.Team("Music Production", 2, 6).WithInterview().AsRegionalEvent().Build(),
|
||||||
|
EventDefinitionBuilder.Team("On Demand Video", 2, 6).Build(),
|
||||||
|
EventDefinitionBuilder.Team("Promotional Marketing", 1, 6).WithPresentation().Build(),
|
||||||
|
EventDefinitionBuilder.Team("Software Development", 2, 6).WithInterview().AsRegionalEvent().Build(),
|
||||||
|
EventDefinitionBuilder.Team("Video Game Design", 2, 6).WithInterview().AsRegionalEvent().Build()
|
||||||
|
};
|
||||||
|
|
||||||
|
// 18 students with varied grades and rankings
|
||||||
|
var students = new[]
|
||||||
|
{
|
||||||
|
StudentBuilder.Default().WithName("Alice", "Anderson").WithGrade(9).WithTsaYear(1).Build(),
|
||||||
|
StudentBuilder.Default().WithName("Bob", "Brown").WithGrade(10).WithTsaYear(2).Build(),
|
||||||
|
StudentBuilder.Default().WithName("Carol", "Clark").WithGrade(11).WithTsaYear(3).Build(),
|
||||||
|
StudentBuilder.Default().WithName("David", "Davis").WithGrade(9).WithTsaYear(1).Build(),
|
||||||
|
StudentBuilder.Default().WithName("Eve", "Evans").WithGrade(10).WithTsaYear(2).Build(),
|
||||||
|
StudentBuilder.Default().WithName("Frank", "Foster").WithGrade(11).WithTsaYear(3).Build(),
|
||||||
|
StudentBuilder.Default().WithName("Grace", "Garcia").WithGrade(9).WithTsaYear(1).Build(),
|
||||||
|
StudentBuilder.Default().WithName("Henry", "Harris").WithGrade(10).WithTsaYear(2).Build(),
|
||||||
|
StudentBuilder.Default().WithName("Ivy", "Irwin").WithGrade(11).WithTsaYear(3).Build(),
|
||||||
|
StudentBuilder.Default().WithName("Jack", "Johnson").WithGrade(9).WithTsaYear(1).Build(),
|
||||||
|
StudentBuilder.Default().WithName("Kelly", "King").WithGrade(10).WithTsaYear(2).Build(),
|
||||||
|
StudentBuilder.Default().WithName("Leo", "Lopez").WithGrade(11).WithTsaYear(3).Build(),
|
||||||
|
StudentBuilder.Default().WithName("Maria", "Martinez").WithGrade(9).WithTsaYear(1).Build(),
|
||||||
|
StudentBuilder.Default().WithName("Noah", "Nelson").WithGrade(10).WithTsaYear(2).Build(),
|
||||||
|
StudentBuilder.Default().WithName("Olivia", "O'Brien").WithGrade(11).WithTsaYear(3).Build(),
|
||||||
|
StudentBuilder.Default().WithName("Paul", "Patel").WithGrade(9).WithTsaYear(1).Build(),
|
||||||
|
StudentBuilder.Default().WithName("Quinn", "Quinn").WithGrade(10).WithTsaYear(2).Build(),
|
||||||
|
StudentBuilder.Default().WithName("Rachel", "Robinson").WithGrade(11).WithTsaYear(3).Build()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add event rankings for students (realistic preferences)
|
||||||
|
AddRankingsToStudent(students[0], events, new[] { 0, 2, 6, 10, 12 }); // Individual + teams
|
||||||
|
AddRankingsToStudent(students[1], events, new[] { 1, 3, 11, 13, 16 });
|
||||||
|
AddRankingsToStudent(students[2], events, new[] { 4, 5, 14, 17, 20 });
|
||||||
|
AddRankingsToStudent(students[3], events, new[] { 6, 7, 15, 18, 21 });
|
||||||
|
AddRankingsToStudent(students[4], events, new[] { 8, 9, 16, 19, 22 });
|
||||||
|
AddRankingsToStudent(students[5], events, new[] { 0, 2, 12, 20, 23 });
|
||||||
|
AddRankingsToStudent(students[6], events, new[] { 1, 3, 10, 13, 24 });
|
||||||
|
AddRankingsToStudent(students[7], events, new[] { 4, 5, 11, 14, 15 });
|
||||||
|
AddRankingsToStudent(students[8], events, new[] { 6, 7, 16, 17, 18 });
|
||||||
|
AddRankingsToStudent(students[9], events, new[] { 8, 9, 19, 21, 22 });
|
||||||
|
AddRankingsToStudent(students[10], events, new[] { 0, 10, 12, 14, 23 });
|
||||||
|
AddRankingsToStudent(students[11], events, new[] { 1, 11, 13, 15, 24 });
|
||||||
|
AddRankingsToStudent(students[12], events, new[] { 2, 12, 16, 18, 20 });
|
||||||
|
AddRankingsToStudent(students[13], events, new[] { 3, 13, 17, 19, 21 });
|
||||||
|
AddRankingsToStudent(students[14], events, new[] { 4, 14, 18, 20, 22 });
|
||||||
|
AddRankingsToStudent(students[15], events, new[] { 5, 15, 19, 21, 23 });
|
||||||
|
AddRankingsToStudent(students[16], events, new[] { 6, 16, 20, 22, 24 });
|
||||||
|
AddRankingsToStudent(students[17], events, new[] { 7, 10, 11, 12, 13 });
|
||||||
|
|
||||||
|
// Create representative teams (some will be created by algorithm tests)
|
||||||
|
var teams = new[]
|
||||||
|
{
|
||||||
|
TeamBuilder.Default().ForEvent(events[10]) // Animatronics
|
||||||
|
.WithStudentsAndCaptain(students[0], students[1], students[2])
|
||||||
|
.WithIdentifier("Team A")
|
||||||
|
.Build(),
|
||||||
|
TeamBuilder.Default().ForEvent(events[11]) // Chapter Team
|
||||||
|
.WithStudentsAndCaptain(students[3], students[4], students[5], students[6], students[7], students[8])
|
||||||
|
.WithIdentifier("Chapter")
|
||||||
|
.Build(),
|
||||||
|
TeamBuilder.Default().ForEvent(events[12]) // Cybersecurity
|
||||||
|
.WithStudentsAndCaptain(students[9], students[10])
|
||||||
|
.WithIdentifier("Team C")
|
||||||
|
.Build(),
|
||||||
|
TeamBuilder.Default().ForEvent(events[13]) // Debate
|
||||||
|
.WithStudentsAndCaptain(students[11], students[12])
|
||||||
|
.WithIdentifier("Team D")
|
||||||
|
.Build(),
|
||||||
|
TeamBuilder.Default().ForEvent(events[16]) // Fashion Design
|
||||||
|
.WithStudentsAndCaptain(students[13], students[14], students[15])
|
||||||
|
.WithIdentifier("Team F")
|
||||||
|
.Build(),
|
||||||
|
TeamBuilder.Default().ForEvent(events[24]) // Video Game Design
|
||||||
|
.WithStudentsAndCaptain(students[16], students[17])
|
||||||
|
.WithIdentifier("Team V")
|
||||||
|
.Build()
|
||||||
|
};
|
||||||
|
|
||||||
|
return (events, students, teams);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Helper to add event rankings to a student.
|
||||||
|
/// </summary>
|
||||||
|
private static void AddRankingsToStudent(Student student, EventDefinition[] events, int[] eventIndices)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < eventIndices.Length; i++)
|
||||||
|
{
|
||||||
|
var eventIndex = eventIndices[i];
|
||||||
|
if (eventIndex >= 0 && eventIndex < events.Length)
|
||||||
|
{
|
||||||
|
StudentEventRankingBuilder.Default()
|
||||||
|
.ForStudent(student)
|
||||||
|
.ForEvent(events[eventIndex])
|
||||||
|
.WithRank(i + 1)
|
||||||
|
.Build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns just events from the large fixture.
|
||||||
|
/// </summary>
|
||||||
|
public static EventDefinition[] Events()
|
||||||
|
{
|
||||||
|
BuilderExtensions.ResetAllBuilders();
|
||||||
|
return Full().Events;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns just students from the large fixture.
|
||||||
|
/// </summary>
|
||||||
|
public static Student[] Students()
|
||||||
|
{
|
||||||
|
BuilderExtensions.ResetAllBuilders();
|
||||||
|
return Full().Students;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Custom helpers for creating specific numbers of minimal test entities.
|
||||||
|
/// </summary>
|
||||||
|
public static class Custom
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates N simple individual events with generic names.
|
||||||
|
/// </summary>
|
||||||
|
public static EventDefinition[] MinimalEvents(int count)
|
||||||
|
{
|
||||||
|
BuilderExtensions.ResetAllBuilders();
|
||||||
|
var events = new EventDefinition[count];
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
events[i] = EventDefinitionBuilder.Individual($"Event{i + 1}").Build();
|
||||||
|
}
|
||||||
|
return events;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates N simple students with sequential names.
|
||||||
|
/// </summary>
|
||||||
|
public static Student[] MinimalStudents(int count)
|
||||||
|
{
|
||||||
|
BuilderExtensions.ResetAllBuilders();
|
||||||
|
return BuilderExtensions.CreateStudents(count);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates N simple teams for a given event.
|
||||||
|
/// </summary>
|
||||||
|
public static Team[] MinimalTeams(EventDefinition eventDef, int teamCount, int studentsPerTeam)
|
||||||
|
{
|
||||||
|
var allStudents = MinimalStudents(teamCount * studentsPerTeam);
|
||||||
|
var teams = new Team[teamCount];
|
||||||
|
|
||||||
|
for (int i = 0; i < teamCount; i++)
|
||||||
|
{
|
||||||
|
var teamStudents = allStudents.Skip(i * studentsPerTeam).Take(studentsPerTeam).ToArray();
|
||||||
|
teams[i] = TeamBuilder.Default().ForEvent(eventDef)
|
||||||
|
.WithStudents(teamStudents)
|
||||||
|
.WithCaptain(teamStudents[0])
|
||||||
|
.WithIdentifier($"Team{i + 1}")
|
||||||
|
.Build();
|
||||||
|
}
|
||||||
|
|
||||||
|
return teams;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user