Add testing for validation
This commit is contained in:
@@ -148,7 +148,12 @@ public class ValidationConfiguration
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return JsonSerializer.Deserialize<ValidationConfiguration>(json) ?? Default;
|
var options = new JsonSerializerOptions
|
||||||
|
{
|
||||||
|
PropertyNameCaseInsensitive = true,
|
||||||
|
Converters = { new System.Text.Json.Serialization.JsonStringEnumConverter() }
|
||||||
|
};
|
||||||
|
return JsonSerializer.Deserialize<ValidationConfiguration>(json, options) ?? Default;
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
@@ -183,10 +188,12 @@ public class ValidationConfiguration
|
|||||||
/// <param name="path">Path where the JSON file should be saved</param>
|
/// <param name="path">Path where the JSON file should be saved</param>
|
||||||
public async Task SaveToFileAsync(string path)
|
public async Task SaveToFileAsync(string path)
|
||||||
{
|
{
|
||||||
var json = JsonSerializer.Serialize(this, new JsonSerializerOptions
|
var options = new JsonSerializerOptions
|
||||||
{
|
{
|
||||||
WriteIndented = true
|
WriteIndented = true,
|
||||||
});
|
Converters = { new System.Text.Json.Serialization.JsonStringEnumConverter() }
|
||||||
|
};
|
||||||
|
var json = JsonSerializer.Serialize(this, options);
|
||||||
await File.WriteAllTextAsync(path, json);
|
await File.WriteAllTextAsync(path, json);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -147,6 +147,7 @@ public class StudentBuilder
|
|||||||
Rank = rank
|
Rank = rank
|
||||||
};
|
};
|
||||||
student.EventRankings.Add(ranking);
|
student.EventRankings.Add(ranking);
|
||||||
|
student.RankedEvents.Add(eventDef); // Also add to RankedEvents for validation rules
|
||||||
}
|
}
|
||||||
|
|
||||||
return student;
|
return student;
|
||||||
|
|||||||
@@ -21,10 +21,12 @@ public class TeamBuilder
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TeamBuilder WithStudent(Student student)
|
public TeamBuilder WithStudent(Student student, bool isCaptain = false)
|
||||||
{
|
{
|
||||||
if (!_students.Contains(student))
|
if (!_students.Contains(student))
|
||||||
_students.Add(student);
|
_students.Add(student);
|
||||||
|
if (isCaptain)
|
||||||
|
_captain = student;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,440 @@
|
|||||||
|
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<EventDefinition>
|
||||||
|
{
|
||||||
|
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<EventDefinition>
|
||||||
|
{
|
||||||
|
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<EventDefinition>
|
||||||
|
{
|
||||||
|
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<EventDefinition>
|
||||||
|
{
|
||||||
|
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<EventDefinition>();
|
||||||
|
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<EventDefinition>();
|
||||||
|
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<EventDefinition>();
|
||||||
|
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<EventDefinition>
|
||||||
|
{
|
||||||
|
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<EventDefinition>
|
||||||
|
{
|
||||||
|
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<EventDefinition>
|
||||||
|
{
|
||||||
|
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<EventDefinition>
|
||||||
|
{
|
||||||
|
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<EventDefinition>
|
||||||
|
{
|
||||||
|
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<EventDefinition>
|
||||||
|
{
|
||||||
|
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<StudentEventStatistics>[]
|
||||||
|
{
|
||||||
|
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
|
||||||
|
}
|
||||||
@@ -0,0 +1,294 @@
|
|||||||
|
using Core.Entities;
|
||||||
|
using Core.Validation;
|
||||||
|
using Core.Validation.Rules.StudentRankingRules;
|
||||||
|
using Tests.Builders;
|
||||||
|
|
||||||
|
namespace Tests.Validation.Rules;
|
||||||
|
|
||||||
|
[TestFixture]
|
||||||
|
public class StudentRankingRulesTests
|
||||||
|
{
|
||||||
|
[SetUp]
|
||||||
|
public void SetUp()
|
||||||
|
{
|
||||||
|
BuilderExtensions.ResetAllBuilders();
|
||||||
|
}
|
||||||
|
|
||||||
|
#region NoRegionalEventRule Tests
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void NoRegionalEventRule_StudentHasRegionalEvent_ReturnsNull()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var config = new ValidationConfiguration
|
||||||
|
{
|
||||||
|
RequireRegionalEvent = true,
|
||||||
|
RequireOnSiteActivity = false,
|
||||||
|
RequireIndividualEvent = false
|
||||||
|
};
|
||||||
|
var rule = new NoRegionalEventRule();
|
||||||
|
|
||||||
|
var flight = EventDefinitionBuilder.Individual("Flight").AsRegionalEvent().Build();
|
||||||
|
var student = StudentBuilder.Default()
|
||||||
|
.WithRanking(flight, 1)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var warning = rule.Validate(student, config);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.That(warning, Is.Null, "Student with regional event should not trigger warning");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void NoRegionalEventRule_StudentLacksRegionalEvent_ReturnsWarning()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var config = new ValidationConfiguration
|
||||||
|
{
|
||||||
|
RequireRegionalEvent = true,
|
||||||
|
NoRegionalEventSeverity = ValidationSeverity.Warning
|
||||||
|
};
|
||||||
|
var rule = new NoRegionalEventRule();
|
||||||
|
|
||||||
|
var coding = EventDefinitionBuilder.Individual("Coding").Build(); // Not regional
|
||||||
|
var student = StudentBuilder.Default()
|
||||||
|
.WithRanking(coding, 1)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var warning = rule.Validate(student, config);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.That(warning, Is.Not.Null);
|
||||||
|
Assert.That(warning!.Code, Is.EqualTo("NO_REGIONAL_EVENT"));
|
||||||
|
Assert.That(warning.Message, Is.EqualTo("No Regional Event"));
|
||||||
|
Assert.That(warning.Severity, Is.EqualTo(ValidationSeverity.Warning));
|
||||||
|
Assert.That(warning.IconIdentifier, Is.EqualTo("RegionalEvent"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void NoRegionalEventRule_RequirementDisabled_ReturnsNull()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var config = new ValidationConfiguration { RequireRegionalEvent = false };
|
||||||
|
var rule = new NoRegionalEventRule();
|
||||||
|
|
||||||
|
var coding = EventDefinitionBuilder.Individual("Coding").Build();
|
||||||
|
var student = StudentBuilder.Default()
|
||||||
|
.WithRanking(coding, 1)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var warning = rule.Validate(student, config);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.That(warning, Is.Null, "Warning should not be returned when requirement is disabled");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void NoRegionalEventRule_AppliesTo_CorrectContexts()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var rule = new NoRegionalEventRule();
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
Assert.That(rule.AppliesTo(ValidationContext.StudentRanking), Is.True);
|
||||||
|
Assert.That(rule.AppliesTo(ValidationContext.StudentRegistration), Is.True);
|
||||||
|
Assert.That(rule.AppliesTo(ValidationContext.StudentAssignment), Is.False);
|
||||||
|
Assert.That(rule.AppliesTo(ValidationContext.Team), Is.False);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region NoOnSiteActivityRule Tests
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void NoOnSiteActivityRule_StudentHasOnSiteActivity_ReturnsNull()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var config = new ValidationConfiguration
|
||||||
|
{
|
||||||
|
RequireOnSiteActivity = true,
|
||||||
|
RequireRegionalEvent = false,
|
||||||
|
RequireIndividualEvent = false
|
||||||
|
};
|
||||||
|
var rule = new NoOnSiteActivityRule();
|
||||||
|
|
||||||
|
var speech = EventDefinitionBuilder.Individual("Speech").AsOnSite().Build();
|
||||||
|
var student = StudentBuilder.Default()
|
||||||
|
.WithRanking(speech, 1)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var warning = rule.Validate(student, config);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.That(warning, Is.Null);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void NoOnSiteActivityRule_StudentLacksOnSiteActivity_ReturnsWarning()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var config = new ValidationConfiguration
|
||||||
|
{
|
||||||
|
RequireOnSiteActivity = true,
|
||||||
|
NoOnSiteActivitySeverity = ValidationSeverity.Error
|
||||||
|
};
|
||||||
|
var rule = new NoOnSiteActivityRule();
|
||||||
|
|
||||||
|
var flight = EventDefinitionBuilder.Individual("Flight").Build(); // Not on-site
|
||||||
|
var student = StudentBuilder.Default()
|
||||||
|
.WithRanking(flight, 1)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var warning = rule.Validate(student, config);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.That(warning, Is.Not.Null);
|
||||||
|
Assert.That(warning!.Code, Is.EqualTo("NO_ONSITE_ACTIVITY"));
|
||||||
|
Assert.That(warning.Severity, Is.EqualTo(ValidationSeverity.Error));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region NoIndividualEventRule Tests
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void NoIndividualEventRule_StudentHasIndividualEvent_ReturnsNull()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var config = new ValidationConfiguration
|
||||||
|
{
|
||||||
|
RequireIndividualEvent = true,
|
||||||
|
RequireRegionalEvent = false,
|
||||||
|
RequireOnSiteActivity = false
|
||||||
|
};
|
||||||
|
var rule = new NoIndividualEventRule();
|
||||||
|
|
||||||
|
var flight = EventDefinitionBuilder.Individual("Flight").Build();
|
||||||
|
var student = StudentBuilder.Default()
|
||||||
|
.WithRanking(flight, 1)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var warning = rule.Validate(student, config);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.That(warning, Is.Null);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void NoIndividualEventRule_StudentOnlyHasTeamEvents_ReturnsWarning()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var config = new ValidationConfiguration
|
||||||
|
{
|
||||||
|
RequireIndividualEvent = true,
|
||||||
|
NoIndividualEventSeverity = ValidationSeverity.Warning
|
||||||
|
};
|
||||||
|
var rule = new NoIndividualEventRule();
|
||||||
|
|
||||||
|
var robotics = EventDefinitionBuilder.Team("Robotics", 2, 5).Build();
|
||||||
|
var student = StudentBuilder.Default()
|
||||||
|
.WithRanking(robotics, 1)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var warning = rule.Validate(student, config);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.That(warning, Is.Not.Null);
|
||||||
|
Assert.That(warning!.Code, Is.EqualTo("NO_INDIVIDUAL_EVENT"));
|
||||||
|
Assert.That(warning.Message, Is.EqualTo("No Individual Event"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region TooManyRegionalEventsRule Tests
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TooManyRegionalEventsRule_WithinLimit_ReturnsNull()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var config = new ValidationConfiguration { MaxRegionalEvents = 3 };
|
||||||
|
var rule = new TooManyRegionalEventsRule();
|
||||||
|
|
||||||
|
var flight = EventDefinitionBuilder.Individual("Flight").AsRegionalEvent().Build();
|
||||||
|
var coding = EventDefinitionBuilder.Individual("Coding").AsRegionalEvent().Build();
|
||||||
|
|
||||||
|
var student = StudentBuilder.Default()
|
||||||
|
.WithRanking(flight, 1)
|
||||||
|
.WithRanking(coding, 2)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var warning = rule.Validate(student, config);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.That(warning, Is.Null);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TooManyRegionalEventsRule_ExceedsLimit_ReturnsWarning()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var config = new ValidationConfiguration
|
||||||
|
{
|
||||||
|
MaxRegionalEvents = 2,
|
||||||
|
TooManyRegionalEventsSeverity = ValidationSeverity.Warning
|
||||||
|
};
|
||||||
|
var rule = new TooManyRegionalEventsRule();
|
||||||
|
|
||||||
|
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 warning = rule.Validate(student, 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"));
|
||||||
|
Assert.That(warning.Message, Does.Contain("max recommended: 2"));
|
||||||
|
Assert.That(warning.IconIdentifier, Is.EqualTo("RegionalEvent"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TooManyRegionalEventsRule_ContainsMetadata()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var config = new ValidationConfiguration { MaxRegionalEvents = 1 };
|
||||||
|
var rule = new TooManyRegionalEventsRule();
|
||||||
|
|
||||||
|
var flight = EventDefinitionBuilder.Individual("Flight").AsRegionalEvent().Build();
|
||||||
|
var coding = EventDefinitionBuilder.Individual("Coding").AsRegionalEvent().Build();
|
||||||
|
|
||||||
|
var student = StudentBuilder.Default()
|
||||||
|
.WithName("Alice", "Anderson")
|
||||||
|
.WithRanking(flight, 1)
|
||||||
|
.WithRanking(coding, 2)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var warning = rule.Validate(student, config);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.That(warning, Is.Not.Null);
|
||||||
|
Assert.That(warning!.Metadata, Contains.Key("RegionalEventCount"));
|
||||||
|
Assert.That(warning.Metadata, Contains.Key("MaxRegionalEvents"));
|
||||||
|
Assert.That(warning.Metadata["RegionalEventCount"], Is.EqualTo(2));
|
||||||
|
Assert.That(warning.Metadata["MaxRegionalEvents"], Is.EqualTo(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@@ -0,0 +1,310 @@
|
|||||||
|
using Core.Entities;
|
||||||
|
using Core.Validation;
|
||||||
|
using Core.Validation.Rules.TeamRules;
|
||||||
|
using Tests.Builders;
|
||||||
|
|
||||||
|
namespace Tests.Validation.Rules;
|
||||||
|
|
||||||
|
[TestFixture]
|
||||||
|
public class TeamRulesTests
|
||||||
|
{
|
||||||
|
[SetUp]
|
||||||
|
public void SetUp()
|
||||||
|
{
|
||||||
|
BuilderExtensions.ResetAllBuilders();
|
||||||
|
}
|
||||||
|
|
||||||
|
#region MissingCaptainRule Tests
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void MissingCaptainRule_TeamHasCaptain_ReturnsNull()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var config = new ValidationConfiguration { RequireTeamCaptain = true };
|
||||||
|
var rule = new MissingCaptainRule();
|
||||||
|
|
||||||
|
var robotics = EventDefinitionBuilder.Team("Robotics", 2, 4).Build();
|
||||||
|
var captain = StudentBuilder.Default().WithName("Alice", "A").Build();
|
||||||
|
var member = StudentBuilder.Default().WithName("Bob", "B").Build();
|
||||||
|
|
||||||
|
var team = TeamBuilder.Default()
|
||||||
|
.ForEvent(robotics)
|
||||||
|
.WithStudent(captain, isCaptain: true)
|
||||||
|
.WithStudent(member)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var warning = rule.Validate(team, config);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.That(warning, Is.Null);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void MissingCaptainRule_TeamLacksCaptain_ReturnsWarning()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var config = new ValidationConfiguration
|
||||||
|
{
|
||||||
|
RequireTeamCaptain = true,
|
||||||
|
MissingCaptainSeverity = ValidationSeverity.Warning
|
||||||
|
};
|
||||||
|
var rule = new MissingCaptainRule();
|
||||||
|
|
||||||
|
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 warning = rule.Validate(team, config);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.That(warning, Is.Not.Null);
|
||||||
|
Assert.That(warning!.Code, Is.EqualTo("MISSING_CAPTAIN"));
|
||||||
|
Assert.That(warning.Message, Does.Contain("no captain assigned"));
|
||||||
|
Assert.That(warning.Severity, Is.EqualTo(ValidationSeverity.Warning));
|
||||||
|
Assert.That(warning.IconIdentifier, Is.EqualTo("Captain"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void MissingCaptainRule_RequirementDisabled_ReturnsNull()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var config = new ValidationConfiguration { RequireTeamCaptain = false };
|
||||||
|
var rule = new MissingCaptainRule();
|
||||||
|
|
||||||
|
var robotics = EventDefinitionBuilder.Team("Robotics", 2, 4).Build();
|
||||||
|
var team = TeamBuilder.Default()
|
||||||
|
.ForEvent(robotics)
|
||||||
|
.WithStudent(StudentBuilder.Default().Build())
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var warning = rule.Validate(team, config);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.That(warning, Is.Null);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void MissingCaptainRule_IndividualEvent_ReturnsNull()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var config = new ValidationConfiguration { RequireTeamCaptain = true };
|
||||||
|
var rule = new MissingCaptainRule();
|
||||||
|
|
||||||
|
var flight = EventDefinitionBuilder.Individual("Flight").Build();
|
||||||
|
var team = TeamBuilder.Default()
|
||||||
|
.ForEvent(flight)
|
||||||
|
.WithStudent(StudentBuilder.Default().Build())
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var warning = rule.Validate(team, config);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.That(warning, Is.Null, "Individual events should not require a captain");
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region TeamSizeTooSmallRule Tests
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TeamSizeTooSmallRule_MeetsMinimum_ReturnsNull()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var config = new ValidationConfiguration { TeamSizeSeverity = ValidationSeverity.Warning };
|
||||||
|
var rule = new TeamSizeTooSmallRule();
|
||||||
|
|
||||||
|
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 warning = rule.Validate(team, config);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.That(warning, Is.Null);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TeamSizeTooSmallRule_BelowMinimum_ReturnsWarning()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var config = new ValidationConfiguration { TeamSizeSeverity = ValidationSeverity.Error };
|
||||||
|
var rule = new TeamSizeTooSmallRule();
|
||||||
|
|
||||||
|
var robotics = EventDefinitionBuilder.Team("Robotics", 3, 5).Build();
|
||||||
|
var team = TeamBuilder.Default()
|
||||||
|
.ForEvent(robotics)
|
||||||
|
.WithStudent(StudentBuilder.Default().WithName("Alice", "A").Build())
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var warning = rule.Validate(team, config);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.That(warning, Is.Not.Null);
|
||||||
|
Assert.That(warning!.Code, Is.EqualTo("TEAM_SIZE_TOO_SMALL"));
|
||||||
|
Assert.That(warning.Message, Does.Contain("1 members"));
|
||||||
|
Assert.That(warning.Message, Does.Contain("min: 3"));
|
||||||
|
Assert.That(warning.Severity, Is.EqualTo(ValidationSeverity.Error));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TeamSizeTooSmallRule_IndividualEvent_ReturnsNull()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var config = new ValidationConfiguration { TeamSizeSeverity = ValidationSeverity.Warning };
|
||||||
|
var rule = new TeamSizeTooSmallRule();
|
||||||
|
|
||||||
|
var flight = EventDefinitionBuilder.Individual("Flight").Build();
|
||||||
|
var team = TeamBuilder.Default()
|
||||||
|
.ForEvent(flight)
|
||||||
|
.WithStudent(StudentBuilder.Default().Build())
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var warning = rule.Validate(team, config);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.That(warning, Is.Null);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TeamSizeTooSmallRule_ContainsMetadata()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var config = new ValidationConfiguration { TeamSizeSeverity = ValidationSeverity.Warning };
|
||||||
|
var rule = new TeamSizeTooSmallRule();
|
||||||
|
|
||||||
|
var robotics = EventDefinitionBuilder.Team("Robotics", 4, 6).Build();
|
||||||
|
var team = TeamBuilder.Default()
|
||||||
|
.ForEvent(robotics)
|
||||||
|
.WithStudent(StudentBuilder.Default().Build())
|
||||||
|
.WithStudent(StudentBuilder.Default().Build())
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var warning = rule.Validate(team, config);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.That(warning, Is.Not.Null);
|
||||||
|
Assert.That(warning!.Metadata, Contains.Key("EventName"));
|
||||||
|
Assert.That(warning.Metadata, Contains.Key("ActualSize"));
|
||||||
|
Assert.That(warning.Metadata, Contains.Key("MinSize"));
|
||||||
|
Assert.That(warning.Metadata["ActualSize"], Is.EqualTo(2));
|
||||||
|
Assert.That(warning.Metadata["MinSize"], Is.EqualTo(4));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region TeamSizeTooLargeRule Tests
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TeamSizeTooLargeRule_MeetsMaximum_ReturnsNull()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var config = new ValidationConfiguration { TeamSizeSeverity = ValidationSeverity.Warning };
|
||||||
|
var rule = new TeamSizeTooLargeRule();
|
||||||
|
|
||||||
|
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())
|
||||||
|
.WithStudent(StudentBuilder.Default().WithName("Carol", "C").Build())
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var warning = rule.Validate(team, config);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.That(warning, Is.Null);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TeamSizeTooLargeRule_ExceedsMaximum_ReturnsWarning()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var config = new ValidationConfiguration { TeamSizeSeverity = ValidationSeverity.Warning };
|
||||||
|
var rule = new TeamSizeTooLargeRule();
|
||||||
|
|
||||||
|
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())
|
||||||
|
.WithStudent(StudentBuilder.Default().WithName("Eve", "E").Build())
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var warning = rule.Validate(team, config);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.That(warning, Is.Not.Null);
|
||||||
|
Assert.That(warning!.Code, Is.EqualTo("TEAM_SIZE_TOO_LARGE"));
|
||||||
|
Assert.That(warning.Message, Does.Contain("5 members"));
|
||||||
|
Assert.That(warning.Message, Does.Contain("max: 3"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TeamSizeTooLargeRule_IndividualEvent_ReturnsNull()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var config = new ValidationConfiguration { TeamSizeSeverity = ValidationSeverity.Warning };
|
||||||
|
var rule = new TeamSizeTooLargeRule();
|
||||||
|
|
||||||
|
var flight = EventDefinitionBuilder.Individual("Flight").Build();
|
||||||
|
var team = TeamBuilder.Default()
|
||||||
|
.ForEvent(flight)
|
||||||
|
.WithStudent(StudentBuilder.Default().Build())
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var warning = rule.Validate(team, config);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.That(warning, Is.Null);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region AppliesTo Tests
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TeamRules_ApplyToCorrectContexts()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var rules = new IValidationRule<Team>[]
|
||||||
|
{
|
||||||
|
new MissingCaptainRule(),
|
||||||
|
new TeamSizeTooSmallRule(),
|
||||||
|
new TeamSizeTooLargeRule()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act & Assert - all team rules should apply to Team context only
|
||||||
|
foreach (var rule in rules)
|
||||||
|
{
|
||||||
|
Assert.That(rule.AppliesTo(ValidationContext.Team), Is.True,
|
||||||
|
$"{rule.GetType().Name} should apply to Team");
|
||||||
|
Assert.That(rule.AppliesTo(ValidationContext.StudentRanking), Is.False,
|
||||||
|
$"{rule.GetType().Name} should not apply to StudentRanking");
|
||||||
|
Assert.That(rule.AppliesTo(ValidationContext.StudentAssignment), Is.False,
|
||||||
|
$"{rule.GetType().Name} should not apply to StudentAssignment");
|
||||||
|
Assert.That(rule.AppliesTo(ValidationContext.StudentRegistration), Is.False,
|
||||||
|
$"{rule.GetType().Name} should not apply to StudentRegistration");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@@ -0,0 +1,229 @@
|
|||||||
|
using Core.Entities;
|
||||||
|
using Core.Validation;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace Tests.Validation;
|
||||||
|
|
||||||
|
[TestFixture]
|
||||||
|
public class ValidationConfigurationTests
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void Default_ReturnsExpectedValues()
|
||||||
|
{
|
||||||
|
// Arrange & Act
|
||||||
|
var config = ValidationConfiguration.Default;
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.That(config.MinRecommendedEvents, Is.EqualTo(2));
|
||||||
|
Assert.That(config.MaxRecommendedEvents, Is.EqualTo(4));
|
||||||
|
Assert.That(config.MinCriticalEvents, Is.EqualTo(1));
|
||||||
|
Assert.That(config.MaxCriticalEvents, Is.EqualTo(6));
|
||||||
|
Assert.That(config.MaxRegionalEvents, Is.EqualTo(3));
|
||||||
|
Assert.That(config.RequireRegionalEvent, Is.True);
|
||||||
|
Assert.That(config.RequireOnSiteActivity, Is.True);
|
||||||
|
Assert.That(config.RequireIndividualEvent, Is.False);
|
||||||
|
Assert.That(config.RequireTeamCaptain, Is.True);
|
||||||
|
Assert.That(config.NoRegionalEventSeverity, Is.EqualTo(ValidationSeverity.Warning));
|
||||||
|
Assert.That(config.EventCountSeverity, Is.EqualTo(ValidationSeverity.Warning));
|
||||||
|
Assert.That(config.MissingCaptainSeverity, Is.EqualTo(ValidationSeverity.Warning));
|
||||||
|
Assert.That(config.TooManyRegionalEventsSeverity, Is.EqualTo(ValidationSeverity.Warning));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void FromAssignmentParameters_CreatesCorrectConfiguration()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var parameters = new AssignmentParameters(
|
||||||
|
effortLowerBound: 5,
|
||||||
|
effortUpperBound: 8,
|
||||||
|
eventsLowerBound: 2,
|
||||||
|
eventsUpperBound: 4,
|
||||||
|
requireRegional: true,
|
||||||
|
requireOnSite: false
|
||||||
|
);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var config = ValidationConfiguration.FromAssignmentParameters(parameters);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.That(config.MinRecommendedEvents, Is.EqualTo(2));
|
||||||
|
Assert.That(config.MaxRecommendedEvents, Is.EqualTo(4));
|
||||||
|
Assert.That(config.RequireRegionalEvent, Is.True);
|
||||||
|
Assert.That(config.RequireOnSiteActivity, Is.False);
|
||||||
|
Assert.That(config.MinCriticalEvents, Is.EqualTo(1));
|
||||||
|
Assert.That(config.MaxCriticalEvents, Is.EqualTo(6));
|
||||||
|
Assert.That(config.RequireTeamCaptain, Is.True);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void FromJson_ValidJson_ReturnsConfiguration()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var json = @"{
|
||||||
|
""MinRecommendedEvents"": 3,
|
||||||
|
""MaxRecommendedEvents"": 5,
|
||||||
|
""RequireRegionalEvent"": false,
|
||||||
|
""NoRegionalEventSeverity"": ""Error""
|
||||||
|
}";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var config = ValidationConfiguration.FromJson(json);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.That(config.MinRecommendedEvents, Is.EqualTo(3));
|
||||||
|
Assert.That(config.MaxRecommendedEvents, Is.EqualTo(5));
|
||||||
|
Assert.That(config.RequireRegionalEvent, Is.False);
|
||||||
|
Assert.That(config.NoRegionalEventSeverity, Is.EqualTo(ValidationSeverity.Error));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void FromJson_InvalidJson_ReturnsDefault()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var invalidJson = "{ invalid json }";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var config = ValidationConfiguration.FromJson(invalidJson);
|
||||||
|
|
||||||
|
// Assert - should return Default config on error
|
||||||
|
Assert.That(config, Is.Not.Null);
|
||||||
|
Assert.That(config.MinRecommendedEvents, Is.EqualTo(ValidationConfiguration.Default.MinRecommendedEvents));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task LoadFromFileAsync_NonExistentFile_ReturnsDefault()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var nonExistentPath = Path.Combine(Path.GetTempPath(), $"non_existent_{Guid.NewGuid()}.json");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var config = await ValidationConfiguration.LoadFromFileAsync(nonExistentPath);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.That(config, Is.Not.Null);
|
||||||
|
Assert.That(config.MinRecommendedEvents, Is.EqualTo(ValidationConfiguration.Default.MinRecommendedEvents));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task SaveToFileAsync_And_LoadFromFileAsync_RoundTrip()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var tempPath = Path.Combine(Path.GetTempPath(), $"test_config_{Guid.NewGuid()}.json");
|
||||||
|
var originalConfig = new ValidationConfiguration
|
||||||
|
{
|
||||||
|
MinRecommendedEvents = 3,
|
||||||
|
MaxRecommendedEvents = 5,
|
||||||
|
RequireRegionalEvent = false,
|
||||||
|
NoRegionalEventSeverity = ValidationSeverity.Error,
|
||||||
|
MaxRegionalEvents = 2
|
||||||
|
};
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Act
|
||||||
|
await originalConfig.SaveToFileAsync(tempPath);
|
||||||
|
var loadedConfig = await ValidationConfiguration.LoadFromFileAsync(tempPath);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.That(loadedConfig.MinRecommendedEvents, Is.EqualTo(3));
|
||||||
|
Assert.That(loadedConfig.MaxRecommendedEvents, Is.EqualTo(5));
|
||||||
|
Assert.That(loadedConfig.RequireRegionalEvent, Is.False);
|
||||||
|
Assert.That(loadedConfig.NoRegionalEventSeverity, Is.EqualTo(ValidationSeverity.Error));
|
||||||
|
Assert.That(loadedConfig.MaxRegionalEvents, Is.EqualTo(2));
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
// Cleanup
|
||||||
|
if (File.Exists(tempPath))
|
||||||
|
File.Delete(tempPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task SaveToFileAsync_CreatesReadableJson()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var tempPath = Path.Combine(Path.GetTempPath(), $"test_config_{Guid.NewGuid()}.json");
|
||||||
|
var config = new ValidationConfiguration
|
||||||
|
{
|
||||||
|
MinRecommendedEvents = 2,
|
||||||
|
MaxRecommendedEvents = 4
|
||||||
|
};
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Act
|
||||||
|
await config.SaveToFileAsync(tempPath);
|
||||||
|
var jsonContent = await File.ReadAllTextAsync(tempPath);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.That(jsonContent, Does.Contain("MinRecommendedEvents"));
|
||||||
|
Assert.That(jsonContent, Does.Contain("MaxRecommendedEvents"));
|
||||||
|
|
||||||
|
// Verify it's valid JSON
|
||||||
|
var doc = JsonDocument.Parse(jsonContent);
|
||||||
|
Assert.That(doc, Is.Not.Null);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
// Cleanup
|
||||||
|
if (File.Exists(tempPath))
|
||||||
|
File.Delete(tempPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ValidationConfiguration_CanBeModified()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var config = ValidationConfiguration.Default;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
config.MinRecommendedEvents = 5;
|
||||||
|
config.RequireRegionalEvent = false;
|
||||||
|
config.NoRegionalEventSeverity = ValidationSeverity.Error;
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.That(config.MinRecommendedEvents, Is.EqualTo(5));
|
||||||
|
Assert.That(config.RequireRegionalEvent, Is.False);
|
||||||
|
Assert.That(config.NoRegionalEventSeverity, Is.EqualTo(ValidationSeverity.Error));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ValidationConfiguration_AllPropertiesSerializable()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var config = new ValidationConfiguration
|
||||||
|
{
|
||||||
|
MinRecommendedEvents = 1,
|
||||||
|
MaxRecommendedEvents = 2,
|
||||||
|
MinCriticalEvents = 3,
|
||||||
|
MaxCriticalEvents = 4,
|
||||||
|
MaxRegionalEvents = 5,
|
||||||
|
RequireRegionalEvent = true,
|
||||||
|
RequireOnSiteActivity = false,
|
||||||
|
RequireIndividualEvent = true,
|
||||||
|
RequireTeamCaptain = false,
|
||||||
|
NoRegionalEventSeverity = ValidationSeverity.Error,
|
||||||
|
NoOnSiteActivitySeverity = ValidationSeverity.Warning,
|
||||||
|
NoIndividualEventSeverity = ValidationSeverity.Error,
|
||||||
|
TeamSizeSeverity = ValidationSeverity.Warning,
|
||||||
|
EventCountSeverity = ValidationSeverity.Error,
|
||||||
|
MissingCaptainSeverity = ValidationSeverity.Warning,
|
||||||
|
TooManyRegionalEventsSeverity = ValidationSeverity.Error
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var json = JsonSerializer.Serialize(config);
|
||||||
|
var deserialized = JsonSerializer.Deserialize<ValidationConfiguration>(json);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.That(deserialized, Is.Not.Null);
|
||||||
|
Assert.That(deserialized!.MinRecommendedEvents, Is.EqualTo(config.MinRecommendedEvents));
|
||||||
|
Assert.That(deserialized.MaxRecommendedEvents, Is.EqualTo(config.MaxRecommendedEvents));
|
||||||
|
Assert.That(deserialized.RequireRegionalEvent, Is.EqualTo(config.RequireRegionalEvent));
|
||||||
|
Assert.That(deserialized.NoRegionalEventSeverity, Is.EqualTo(config.NoRegionalEventSeverity));
|
||||||
|
Assert.That(deserialized.MaxRegionalEvents, Is.EqualTo(config.MaxRegionalEvents));
|
||||||
|
Assert.That(deserialized.TooManyRegionalEventsSeverity, Is.EqualTo(config.TooManyRegionalEventsSeverity));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,425 @@
|
|||||||
|
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"));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user