426 lines
14 KiB
C#
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"));
|
|
}
|
|
}
|