Files
poprhythm 45edcf5e5f Refactor event assignment structure and introduce new models for assignment parameters and requirements
This commit removes the obsolete EventAssignment class from Core.Entities and introduces new models in Core.Models, including AssignmentParameters, AssignmentRequirement, PartialTeam, and StudentEventStatistics. The changes enhance the organization of assignment-related data and improve the overall structure of the codebase. Additionally, several files have been updated to include references to the new Core.Models namespace, ensuring consistency across the application.
2026-01-10 18:36:52 -05:00

427 lines
15 KiB
C#

using Core.Entities;
using Core.Models;
using Core.Validation;
using Tests.Builders;
namespace Tests.Validation;
[TestFixture]
public class ValidationServiceTests
{
private ValidationConfiguration _defaultConfig;
[SetUp]
public void SetUp()
{
BuilderExtensions.ResetAllBuilders();
_defaultConfig = ValidationConfiguration.Default;
}
[Test]
public void ValidationService_DiscoverRules_FindsAllRules()
{
// Arrange
var service = new ValidationService(_defaultConfig);
// Act - rules are discovered in constructor via reflection
// Assert - verify rules were discovered by validating entities
var student = StudentBuilder.Default()
.WithRanking(EventDefinitionBuilder.Individual("Flight").Build(), 1)
.Build();
var warnings = service.ValidateStudentRankings(student, ValidationContext.StudentRanking);
// Should find warnings since student has no regional event (if required by config)
Assert.That(warnings, Is.Not.Null);
}
[Test]
public void ValidateStudent_WithValidRankings_ReturnsNoWarnings()
{
// Arrange
var config = new ValidationConfiguration
{
RequireRegionalEvent = true,
RequireOnSiteActivity = true,
RequireIndividualEvent = false,
MinRecommendedEvents = 2,
MaxRecommendedEvents = 4
};
var service = new ValidationService(config);
var flight = EventDefinitionBuilder.Individual("Flight").AsRegionalEvent().Build();
var speech = EventDefinitionBuilder.Individual("Speech").AsRegionalEvent().AsOnSite().Build();
var coding = EventDefinitionBuilder.Individual("Coding").Build();
var student = StudentBuilder.Default()
.WithRanking(flight, 1)
.WithRanking(speech, 2)
.WithRanking(coding, 3)
.Build();
// Act
var warnings = service.ValidateStudentRankings(student, ValidationContext.StudentRanking);
// Assert
Assert.That(warnings, Is.Empty, "Student with valid rankings should have no warnings");
}
[Test]
public void ValidateStudent_MissingRegionalEvent_ReturnsWarning()
{
// Arrange
var config = new ValidationConfiguration
{
RequireRegionalEvent = true,
RequireOnSiteActivity = false,
RequireIndividualEvent = false,
NoRegionalEventSeverity = ValidationSeverity.Warning
};
var service = new ValidationService(config);
var coding = EventDefinitionBuilder.Individual("Coding").Build(); // Not regional
var student = StudentBuilder.Default()
.WithRanking(coding, 1)
.Build();
// Act
var warnings = service.ValidateStudentRankings(student, ValidationContext.StudentRanking);
// Assert
Assert.That(warnings, Has.Count.EqualTo(1));
Assert.That(warnings[0].Code, Is.EqualTo("NO_REGIONAL_EVENT"));
Assert.That(warnings[0].Severity, Is.EqualTo(ValidationSeverity.Warning));
Assert.That(warnings[0].Context, Is.EqualTo(ValidationContext.StudentRanking));
}
[Test]
public void ValidateStudent_MissingOnSiteActivity_ReturnsWarning()
{
// Arrange
var config = new ValidationConfiguration
{
RequireOnSiteActivity = true,
RequireRegionalEvent = false,
NoOnSiteActivitySeverity = ValidationSeverity.Warning
};
var service = new ValidationService(config);
var flight = EventDefinitionBuilder.Individual("Flight").Build(); // Not on-site
var student = StudentBuilder.Default()
.WithRanking(flight, 1)
.Build();
// Act
var warnings = service.ValidateStudentRankings(student, ValidationContext.StudentRanking);
// Assert
Assert.That(warnings, Has.Count.EqualTo(1));
Assert.That(warnings[0].Code, Is.EqualTo("NO_ONSITE_ACTIVITY"));
Assert.That(warnings[0].Severity, Is.EqualTo(ValidationSeverity.Warning));
}
[Test]
public void ValidateStudent_TooManyRegionalEvents_ReturnsWarning()
{
// Arrange
var config = new ValidationConfiguration
{
MaxRegionalEvents = 2,
TooManyRegionalEventsSeverity = ValidationSeverity.Warning,
RequireRegionalEvent = false,
RequireOnSiteActivity = false
};
var service = new ValidationService(config);
var flight = EventDefinitionBuilder.Individual("Flight").AsRegionalEvent().Build();
var coding = EventDefinitionBuilder.Individual("Coding").AsRegionalEvent().Build();
var essays = EventDefinitionBuilder.Individual("Essays").AsRegionalEvent().Build();
var student = StudentBuilder.Default()
.WithRanking(flight, 1)
.WithRanking(coding, 2)
.WithRanking(essays, 3)
.Build();
// Act
var warnings = service.ValidateStudentRankings(student, ValidationContext.StudentRanking);
// Assert
Assert.That(warnings, Has.Count.EqualTo(1));
Assert.That(warnings[0].Code, Is.EqualTo("TOO_MANY_REGIONAL_EVENTS"));
Assert.That(warnings[0].Message, Does.Contain("3 regional events"));
Assert.That(warnings[0].Message, Does.Contain("max recommended: 2"));
}
[Test]
public void ValidateStudentAssignment_ValidAssignment_ReturnsNoWarnings()
{
// Arrange
var config = new ValidationConfiguration
{
RequireRegionalEvent = true,
RequireOnSiteActivity = true,
MinRecommendedEvents = 2,
MaxRecommendedEvents = 4
};
var service = new ValidationService(config);
var flight = EventDefinitionBuilder.Individual("Flight").AsRegionalEvent().Build();
var speech = EventDefinitionBuilder.Individual("Speech").AsOnSite().Build();
var coding = EventDefinitionBuilder.Individual("Coding").Build();
var student = StudentBuilder.Default().Build();
var stats = new StudentEventStatistics
{
Student = student,
Events = new List<EventDefinition> { flight, speech, coding }
};
// Act
var warnings = service.ValidateStudentStatistics(stats, ValidationContext.StudentAssignment);
// Assert
Assert.That(warnings, Is.Empty);
}
[Test]
public void ValidateStudentAssignment_TooFewEvents_ReturnsWarning()
{
// Arrange
var config = new ValidationConfiguration
{
MinRecommendedEvents = 3,
EventCountSeverity = ValidationSeverity.Warning,
RequireRegionalEvent = false,
RequireOnSiteActivity = false
};
var service = new ValidationService(config);
var flight = EventDefinitionBuilder.Individual("Flight").Build();
var student = StudentBuilder.Default().Build();
var stats = new StudentEventStatistics
{
Student = student,
Events = new List<EventDefinition> { flight }
};
// Act
var warnings = service.ValidateStudentStatistics(stats, ValidationContext.StudentAssignment);
// Assert
Assert.That(warnings, Has.Count.GreaterThan(0));
var warning = warnings.FirstOrDefault(w => w.Code == "TOO_FEW_EVENTS");
Assert.That(warning, Is.Not.Null);
Assert.That(warning!.Severity, Is.EqualTo(ValidationSeverity.Warning));
}
[Test]
public void ValidateStudentAssignment_TooManyEvents_ReturnsWarning()
{
// Arrange
var config = new ValidationConfiguration
{
MaxRecommendedEvents = 3,
EventCountSeverity = ValidationSeverity.Warning,
RequireRegionalEvent = false,
RequireOnSiteActivity = false
};
var service = new ValidationService(config);
var student = StudentBuilder.Default().Build();
var events = new List<EventDefinition>();
for (int i = 0; i < 5; i++)
{
events.Add(EventDefinitionBuilder.Individual($"Event{i}").Build());
}
var stats = new StudentEventStatistics
{
Student = student,
Events = events
};
// Act
var warnings = service.ValidateStudentStatistics(stats, ValidationContext.StudentAssignment);
// Assert
var warning = warnings.FirstOrDefault(w => w.Code == "TOO_MANY_EVENTS");
Assert.That(warning, Is.Not.Null);
Assert.That(warning!.Message, Does.Contain("5 events"));
}
[Test]
public void ValidateTeam_ValidTeam_ReturnsNoWarnings()
{
// Arrange
var config = new ValidationConfiguration
{
RequireTeamCaptain = true
};
var service = new ValidationService(config);
var robotics = EventDefinitionBuilder.Team("Robotics", 2, 4).Build();
var team = TeamBuilder.Default()
.ForEvent(robotics)
.WithStudent(StudentBuilder.Default().WithName("Alice", "A").Build(), isCaptain: true)
.WithStudent(StudentBuilder.Default().WithName("Bob", "B").Build())
.Build();
// Act
var warnings = service.ValidateTeam(team);
// Assert
Assert.That(warnings, Is.Empty);
}
[Test]
public void ValidateTeam_MissingCaptain_ReturnsWarning()
{
// Arrange
var config = new ValidationConfiguration
{
RequireTeamCaptain = true,
MissingCaptainSeverity = ValidationSeverity.Warning
};
var service = new ValidationService(config);
var robotics = EventDefinitionBuilder.Team("Robotics", 2, 4).Build();
var team = TeamBuilder.Default()
.ForEvent(robotics)
.WithStudent(StudentBuilder.Default().WithName("Alice", "A").Build())
.WithStudent(StudentBuilder.Default().WithName("Bob", "B").Build())
.Build();
// Act
var warnings = service.ValidateTeam(team);
// Assert
Assert.That(warnings, Has.Count.EqualTo(1));
Assert.That(warnings[0].Code, Is.EqualTo("MISSING_CAPTAIN"));
Assert.That(warnings[0].Severity, Is.EqualTo(ValidationSeverity.Warning));
}
[Test]
public void ValidateTeam_TeamTooSmall_ReturnsWarning()
{
// Arrange
var config = new ValidationConfiguration
{
TeamSizeSeverity = ValidationSeverity.Warning,
RequireTeamCaptain = false
};
var service = new ValidationService(config);
var robotics = EventDefinitionBuilder.Team("Robotics", 3, 5).Build();
var team = TeamBuilder.Default()
.ForEvent(robotics)
.WithStudent(StudentBuilder.Default().WithName("Alice", "A").Build())
.Build();
// Act
var warnings = service.ValidateTeam(team);
// Assert
var warning = warnings.FirstOrDefault(w => w.Code == "TEAM_SIZE_TOO_SMALL");
Assert.That(warning, Is.Not.Null);
Assert.That(warning!.Message, Does.Contain("1 members"));
Assert.That(warning.Message, Does.Contain("min: 3"));
}
[Test]
public void ValidateTeam_TeamTooLarge_ReturnsWarning()
{
// Arrange
var config = new ValidationConfiguration
{
TeamSizeSeverity = ValidationSeverity.Warning,
RequireTeamCaptain = false
};
var service = new ValidationService(config);
var robotics = EventDefinitionBuilder.Team("Robotics", 2, 3).Build();
var team = TeamBuilder.Default()
.ForEvent(robotics)
.WithStudent(StudentBuilder.Default().WithName("Alice", "A").Build())
.WithStudent(StudentBuilder.Default().WithName("Bob", "B").Build())
.WithStudent(StudentBuilder.Default().WithName("Carol", "C").Build())
.WithStudent(StudentBuilder.Default().WithName("David", "D").Build())
.Build();
// Act
var warnings = service.ValidateTeam(team);
// Assert
var warning = warnings.FirstOrDefault(w => w.Code == "TEAM_SIZE_TOO_LARGE");
Assert.That(warning, Is.Not.Null);
Assert.That(warning!.Message, Does.Contain("4 members"));
Assert.That(warning.Message, Does.Contain("max: 3"));
}
[Test]
public void ValidationService_ConfigurationChanges_AffectResults()
{
// Arrange
var student = StudentBuilder.Default()
.WithRanking(EventDefinitionBuilder.Individual("Flight").Build(), 1)
.Build();
// Strict config
var strictConfig = new ValidationConfiguration
{
RequireRegionalEvent = true,
RequireOnSiteActivity = false,
RequireIndividualEvent = false,
NoRegionalEventSeverity = ValidationSeverity.Error
};
var strictService = new ValidationService(strictConfig);
// Lenient config
var lenientConfig = new ValidationConfiguration
{
RequireRegionalEvent = false,
RequireOnSiteActivity = false,
RequireIndividualEvent = false
};
var lenientService = new ValidationService(lenientConfig);
// Act
var strictWarnings = strictService.ValidateStudentRankings(student, ValidationContext.StudentRanking);
var lenientWarnings = lenientService.ValidateStudentRankings(student, ValidationContext.StudentRanking);
// Assert
Assert.That(strictWarnings, Has.Count.EqualTo(1));
Assert.That(strictWarnings[0].Severity, Is.EqualTo(ValidationSeverity.Error));
Assert.That(lenientWarnings, Is.Empty, "Lenient config should not require regional event");
}
[Test]
public void ValidationWarning_ContainsMetadata()
{
// Arrange
var config = new ValidationConfiguration
{
RequireRegionalEvent = true,
RequireOnSiteActivity = false
};
var service = new ValidationService(config);
var student = StudentBuilder.Default()
.WithName("Alice", "Anderson")
.WithRanking(EventDefinitionBuilder.Individual("Coding").Build(), 1)
.Build();
// Act
var warnings = service.ValidateStudentRankings(student, ValidationContext.StudentRanking);
// Assert
Assert.That(warnings, Has.Count.EqualTo(1));
var warning = warnings[0];
Assert.That(warning.Metadata, Contains.Key("StudentId"));
Assert.That(warning.Metadata, Contains.Key("StudentName"));
Assert.That(warning.Metadata["StudentName"], Is.EqualTo("Alice Anderson"));
}
}