45edcf5e5f
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.
427 lines
15 KiB
C#
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"));
|
|
}
|
|
}
|