Files
chapter-organizer/Tests/Validation/ValidationServiceTests.cs
T
2025-12-14 11:08:04 -05:00

426 lines
14 KiB
C#

using Core.Entities;
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"));
}
}