using Core.Entities; using Core.Validation; using Core.Validation.Rules.StudentAssignmentRules; using Tests.Builders; namespace Tests.Validation.Rules; [TestFixture] public class StudentAssignmentRulesTests { private Student _testStudent; [SetUp] public void SetUp() { BuilderExtensions.ResetAllBuilders(); _testStudent = StudentBuilder.Default().WithName("Test", "Student").Build(); } #region TooFewEventsRule Tests [Test] public void TooFewEventsRule_AboveMinimum_ReturnsNull() { // Arrange var config = new ValidationConfiguration { MinRecommendedEvents = 2 }; var rule = new TooFewEventsRule(); var stats = new StudentEventStatistics { Student = _testStudent, Events = new List { EventDefinitionBuilder.Individual("Flight").Build(), EventDefinitionBuilder.Individual("Coding").Build(), EventDefinitionBuilder.Individual("Speech").Build() } }; // Act var warning = rule.Validate(stats, config); // Assert Assert.That(warning, Is.Null); } [Test] public void TooFewEventsRule_BelowMinimum_ReturnsWarning() { // Arrange var config = new ValidationConfiguration { MinRecommendedEvents = 3, MinCriticalEvents = 1, EventCountSeverity = ValidationSeverity.Warning }; var rule = new TooFewEventsRule(); var stats = new StudentEventStatistics { Student = _testStudent, Events = new List { EventDefinitionBuilder.Individual("Flight").Build() } }; // Act var warning = rule.Validate(stats, config); // Assert Assert.That(warning, Is.Not.Null); Assert.That(warning!.Code, Is.EqualTo("TOO_FEW_EVENTS")); Assert.That(warning.Message, Does.Contain("1 events")); Assert.That(warning.Message, Does.Contain("min recommended: 3")); Assert.That(warning.Severity, Is.EqualTo(ValidationSeverity.Warning)); } [Test] public void TooFewEventsRule_BelowCritical_EscalatesToError() { // Arrange var config = new ValidationConfiguration { MinRecommendedEvents = 3, MinCriticalEvents = 2, EventCountSeverity = ValidationSeverity.Warning }; var rule = new TooFewEventsRule(); var stats = new StudentEventStatistics { Student = _testStudent, Events = new List { EventDefinitionBuilder.Individual("Flight").Build() } }; // Act var warning = rule.Validate(stats, config); // Assert Assert.That(warning, Is.Not.Null); Assert.That(warning!.Severity, Is.EqualTo(ValidationSeverity.Error), "Should escalate to Error when below critical threshold"); } #endregion #region TooManyEventsRule Tests [Test] public void TooManyEventsRule_BelowMaximum_ReturnsNull() { // Arrange var config = new ValidationConfiguration { MaxRecommendedEvents = 4 }; var rule = new TooManyEventsRule(); var stats = new StudentEventStatistics { Student = _testStudent, Events = new List { EventDefinitionBuilder.Individual("Event1").Build(), EventDefinitionBuilder.Individual("Event2").Build(), EventDefinitionBuilder.Individual("Event3").Build() } }; // Act var warning = rule.Validate(stats, config); // Assert Assert.That(warning, Is.Null); } [Test] public void TooManyEventsRule_AboveMaximum_ReturnsWarning() { // Arrange var config = new ValidationConfiguration { MaxRecommendedEvents = 3, MaxCriticalEvents = 6, EventCountSeverity = ValidationSeverity.Warning }; var rule = new TooManyEventsRule(); var events = new List(); for (int i = 0; i < 5; i++) { events.Add(EventDefinitionBuilder.Individual($"Event{i}").Build()); } var stats = new StudentEventStatistics { Student = _testStudent, Events = events }; // Act var warning = rule.Validate(stats, config); // Assert Assert.That(warning, Is.Not.Null); Assert.That(warning!.Code, Is.EqualTo("TOO_MANY_EVENTS")); Assert.That(warning.Message, Does.Contain("5 events")); Assert.That(warning.Message, Does.Contain("max recommended: 3")); Assert.That(warning.Severity, Is.EqualTo(ValidationSeverity.Warning)); } [Test] public void TooManyEventsRule_AboveCritical_EscalatesToError() { // Arrange var config = new ValidationConfiguration { MaxRecommendedEvents = 4, MaxCriticalEvents = 6, EventCountSeverity = ValidationSeverity.Warning }; var rule = new TooManyEventsRule(); var events = new List(); for (int i = 0; i < 7; i++) { events.Add(EventDefinitionBuilder.Individual($"Event{i}").Build()); } var stats = new StudentEventStatistics { Student = _testStudent, Events = events }; // Act var warning = rule.Validate(stats, config); // Assert Assert.That(warning, Is.Not.Null); Assert.That(warning!.Severity, Is.EqualTo(ValidationSeverity.Error), "Should escalate to Error when above critical threshold"); } [Test] public void TooManyEventsRule_ContainsMetadata() { // Arrange var config = new ValidationConfiguration { MaxRecommendedEvents = 2, MaxCriticalEvents = 5 }; var rule = new TooManyEventsRule(); var events = new List(); for (int i = 0; i < 4; i++) { events.Add(EventDefinitionBuilder.Individual($"Event{i}").Build()); } var stats = new StudentEventStatistics { Student = _testStudent, Events = events }; // Act var warning = rule.Validate(stats, config); // Assert Assert.That(warning, Is.Not.Null); Assert.That(warning!.Metadata, Contains.Key("MaxRecommended")); Assert.That(warning.Metadata, Contains.Key("MaxCritical")); Assert.That(warning.Metadata["MaxRecommended"], Is.EqualTo(2)); Assert.That(warning.Metadata["MaxCritical"], Is.EqualTo(5)); } #endregion #region NoRegionalEventAssignmentRule Tests [Test] public void NoRegionalEventAssignmentRule_HasRegionalEvent_ReturnsNull() { // Arrange var config = new ValidationConfiguration { RequireRegionalEvent = true }; var rule = new NoRegionalEventAssignmentRule(); var stats = new StudentEventStatistics { Student = _testStudent, Events = new List { EventDefinitionBuilder.Individual("Flight").AsRegionalEvent().Build() } }; // Act var warning = rule.Validate(stats, config); // Assert Assert.That(warning, Is.Null); } [Test] public void NoRegionalEventAssignmentRule_LacksRegionalEvent_ReturnsWarning() { // Arrange var config = new ValidationConfiguration { RequireRegionalEvent = true, NoRegionalEventSeverity = ValidationSeverity.Warning }; var rule = new NoRegionalEventAssignmentRule(); var stats = new StudentEventStatistics { Student = _testStudent, Events = new List { EventDefinitionBuilder.Individual("Coding").Build() } }; // Act var warning = rule.Validate(stats, config); // Assert Assert.That(warning, Is.Not.Null); Assert.That(warning!.Code, Is.EqualTo("NO_REGIONAL_EVENT_ASSIGNED")); Assert.That(warning.Context, Is.EqualTo(ValidationContext.StudentAssignment)); } #endregion #region NoOnSiteActivityAssignmentRule Tests [Test] public void NoOnSiteActivityAssignmentRule_HasOnSiteActivity_ReturnsNull() { // Arrange var config = new ValidationConfiguration { RequireOnSiteActivity = true }; var rule = new NoOnSiteActivityAssignmentRule(); var stats = new StudentEventStatistics { Student = _testStudent, Events = new List { EventDefinitionBuilder.Individual("Speech").AsOnSite().Build() } }; // Act var warning = rule.Validate(stats, config); // Assert Assert.That(warning, Is.Null); } [Test] public void NoOnSiteActivityAssignmentRule_LacksOnSiteActivity_ReturnsWarning() { // Arrange var config = new ValidationConfiguration { RequireOnSiteActivity = true, NoOnSiteActivitySeverity = ValidationSeverity.Error }; var rule = new NoOnSiteActivityAssignmentRule(); var stats = new StudentEventStatistics { Student = _testStudent, Events = new List { EventDefinitionBuilder.Individual("Flight").Build() } }; // Act var warning = rule.Validate(stats, config); // Assert Assert.That(warning, Is.Not.Null); Assert.That(warning!.Code, Is.EqualTo("NO_ONSITE_ACTIVITY_ASSIGNED")); Assert.That(warning.Severity, Is.EqualTo(ValidationSeverity.Error)); } #endregion #region TooManyRegionalEventsAssignmentRule Tests [Test] public void TooManyRegionalEventsAssignmentRule_WithinLimit_ReturnsNull() { // Arrange var config = new ValidationConfiguration { MaxRegionalEvents = 3 }; var rule = new TooManyRegionalEventsAssignmentRule(); var stats = new StudentEventStatistics { Student = _testStudent, Events = new List { EventDefinitionBuilder.Individual("Flight").AsRegionalEvent().Build(), EventDefinitionBuilder.Individual("Coding").AsRegionalEvent().Build() } }; // Act var warning = rule.Validate(stats, config); // Assert Assert.That(warning, Is.Null); } [Test] public void TooManyRegionalEventsAssignmentRule_ExceedsLimit_ReturnsWarning() { // Arrange var config = new ValidationConfiguration { MaxRegionalEvents = 2, TooManyRegionalEventsSeverity = ValidationSeverity.Warning }; var rule = new TooManyRegionalEventsAssignmentRule(); var stats = new StudentEventStatistics { Student = _testStudent, Events = new List { EventDefinitionBuilder.Individual("Flight").AsRegionalEvent().Build(), EventDefinitionBuilder.Individual("Coding").AsRegionalEvent().Build(), EventDefinitionBuilder.Individual("Essays").AsRegionalEvent().Build() } }; // Act var warning = rule.Validate(stats, config); // Assert Assert.That(warning, Is.Not.Null); Assert.That(warning!.Code, Is.EqualTo("TOO_MANY_REGIONAL_EVENTS")); Assert.That(warning.Message, Does.Contain("3 regional events")); } #endregion #region AppliesTo Tests [Test] public void AssignmentRules_ApplyToCorrectContexts() { // Arrange var rules = new IValidationRule[] { new TooFewEventsRule(), new TooManyEventsRule(), new NoRegionalEventAssignmentRule(), new NoOnSiteActivityAssignmentRule(), new TooManyRegionalEventsAssignmentRule() }; // Act & Assert - all assignment rules should apply to these contexts foreach (var rule in rules) { Assert.That(rule.AppliesTo(ValidationContext.StudentAssignment), Is.True, $"{rule.GetType().Name} should apply to StudentAssignment"); Assert.That(rule.AppliesTo(ValidationContext.StudentRegistration), Is.True, $"{rule.GetType().Name} should apply to StudentRegistration"); Assert.That(rule.AppliesTo(ValidationContext.StudentRanking), Is.False, $"{rule.GetType().Name} should not apply to StudentRanking"); Assert.That(rule.AppliesTo(ValidationContext.Team), Is.False, $"{rule.GetType().Name} should not apply to Team"); } } #endregion }