feat: add request validation
This commit is contained in:
parent
1218d3617a
commit
de2d0e2693
32 changed files with 1829 additions and 1 deletions
326
src/request.validation.tests/RuleBuilderExtensionsTests.cs
Normal file
326
src/request.validation.tests/RuleBuilderExtensionsTests.cs
Normal file
|
|
@ -0,0 +1,326 @@
|
|||
// Copyright (c) The Geekeey Authors
|
||||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Geekeey.Request.Validation.Tests;
|
||||
|
||||
internal sealed class RuleBuilderExtensionsTests
|
||||
{
|
||||
[Test]
|
||||
public async Task I_can_validate_not_null_for_reference_types()
|
||||
{
|
||||
var validator = new PropertyValidator<ReferenceValueModel, object?>(model =>
|
||||
model.Value, rule => rule.NotNull());
|
||||
|
||||
var invalid = validator.Validate(new ReferenceValueModel());
|
||||
var valid = validator.Validate(new ReferenceValueModel { Value = new object() });
|
||||
|
||||
await AssertSingleProblem(invalid, nameof(ReferenceValueModel.Value), "Value is required.");
|
||||
await Assert.That(valid.IsValid).IsTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task I_can_validate_not_null_for_nullable_value_types()
|
||||
{
|
||||
var validator = new PropertyValidator<NullableIntValueModel, int?>(model =>
|
||||
model.Value, rule => rule.NotNull());
|
||||
|
||||
var invalid = validator.Validate(new NullableIntValueModel());
|
||||
var valid = validator.Validate(new NullableIntValueModel { Value = 1 });
|
||||
|
||||
await AssertSingleProblem(invalid, nameof(NullableIntValueModel.Value), "Value is required.");
|
||||
await Assert.That(valid.IsValid).IsTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task I_can_validate_not_empty_for_strings()
|
||||
{
|
||||
var validator = new PropertyValidator<StringValueModel, string?>(model =>
|
||||
model.Value, rule => rule.NotEmpty());
|
||||
|
||||
var invalid = validator.Validate(new StringValueModel { Value = " " });
|
||||
var valid = validator.Validate(new StringValueModel { Value = "abc" });
|
||||
|
||||
await AssertSingleProblem(invalid, nameof(StringValueModel.Value), "Value is required.");
|
||||
await Assert.That(valid.IsValid).IsTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task I_can_validate_not_empty_for_collections()
|
||||
{
|
||||
var validator = new PropertyValidator<CollectionValueModel, IEnumerable<string>?>(model =>
|
||||
model.Value, rule => rule.NotEmpty());
|
||||
|
||||
var invalid = validator.Validate(new CollectionValueModel { Value = [] });
|
||||
var valid = validator.Validate(new CollectionValueModel { Value = ["item"] });
|
||||
|
||||
await AssertSingleProblem(invalid, nameof(CollectionValueModel.Value), "Value is required.");
|
||||
await Assert.That(valid.IsValid).IsTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task I_can_validate_min_length()
|
||||
{
|
||||
var validator =
|
||||
new PropertyValidator<StringValueModel, string?>(model =>
|
||||
model.Value, rule => rule.MinLength(3));
|
||||
|
||||
var invalid = validator.Validate(new StringValueModel { Value = "ab" });
|
||||
var valid = validator.Validate(new StringValueModel { Value = "abc" });
|
||||
var ignoredNull = validator.Validate(new StringValueModel());
|
||||
|
||||
await AssertSingleProblem(invalid, nameof(StringValueModel.Value), "Value must be at least 3 characters long.");
|
||||
await Assert.That(valid.IsValid).IsTrue();
|
||||
await Assert.That(ignoredNull.IsValid).IsTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task I_can_validate_max_length()
|
||||
{
|
||||
var validator = new PropertyValidator<StringValueModel, string?>(model
|
||||
=> model.Value, rule => rule.MaxLength(5));
|
||||
|
||||
var invalid = validator.Validate(new StringValueModel { Value = "abcdef" });
|
||||
var valid = validator.Validate(new StringValueModel { Value = "abcde" });
|
||||
var ignoredNull = validator.Validate(new StringValueModel());
|
||||
|
||||
await AssertSingleProblem(invalid, nameof(StringValueModel.Value), "Value must be at most 5 characters long.");
|
||||
await Assert.That(valid.IsValid).IsTrue();
|
||||
await Assert.That(ignoredNull.IsValid).IsTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task I_can_validate_length()
|
||||
{
|
||||
var validator = new PropertyValidator<StringValueModel, string?>(model
|
||||
=> model.Value, rule => rule.Length(2, 4));
|
||||
|
||||
var invalid = validator.Validate(new StringValueModel { Value = "a" });
|
||||
var valid = validator.Validate(new StringValueModel { Value = "ab" });
|
||||
var ignoredNull = validator.Validate(new StringValueModel());
|
||||
|
||||
await AssertSingleProblem(invalid, nameof(StringValueModel.Value),
|
||||
"Value must be between 2 and 4 characters long.");
|
||||
await Assert.That(valid.IsValid).IsTrue();
|
||||
await Assert.That(ignoredNull.IsValid).IsTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task I_can_validate_greater_than()
|
||||
{
|
||||
var validator = new PropertyValidator<IntValueModel, int>(model
|
||||
=> model.Value, rule => rule.GreaterThan(18));
|
||||
|
||||
var invalid = validator.Validate(new IntValueModel { Value = 18 });
|
||||
var valid = validator.Validate(new IntValueModel { Value = 19 });
|
||||
|
||||
await AssertSingleProblem(invalid, nameof(IntValueModel.Value), "Value must be greater than 18.");
|
||||
await Assert.That(valid.IsValid).IsTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task I_can_validate_greater_than_or_equal_to()
|
||||
{
|
||||
var validator = new PropertyValidator<IntValueModel, int>(model
|
||||
=> model.Value, rule => rule.GreaterThanOrEqualTo(1));
|
||||
|
||||
var invalid = validator.Validate(new IntValueModel { Value = 0 });
|
||||
var valid = validator.Validate(new IntValueModel { Value = 1 });
|
||||
|
||||
await AssertSingleProblem(invalid, nameof(IntValueModel.Value), "Value must be greater than or equal to 1.");
|
||||
await Assert.That(valid.IsValid).IsTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task I_can_validate_less_than()
|
||||
{
|
||||
var validator = new PropertyValidator<IntValueModel, int>(model
|
||||
=> model.Value, rule => rule.LessThan(10));
|
||||
|
||||
var invalid = validator.Validate(new IntValueModel { Value = 10 });
|
||||
var valid = validator.Validate(new IntValueModel { Value = 9 });
|
||||
|
||||
await AssertSingleProblem(invalid, nameof(IntValueModel.Value), "Value must be less than 10.");
|
||||
await Assert.That(valid.IsValid).IsTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task I_can_validate_less_than_or_equal_to()
|
||||
{
|
||||
var validator = new PropertyValidator<IntValueModel, int>(model
|
||||
=> model.Value, rule => rule.LessThanOrEqualTo(5));
|
||||
|
||||
var invalid = validator.Validate(new IntValueModel { Value = 6 });
|
||||
var valid = validator.Validate(new IntValueModel { Value = 5 });
|
||||
|
||||
await AssertSingleProblem(invalid, nameof(IntValueModel.Value), "Value must be less than or equal to 5.");
|
||||
await Assert.That(valid.IsValid).IsTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task I_can_validate_between()
|
||||
{
|
||||
var validator = new PropertyValidator<IntValueModel, int>(model
|
||||
=> model.Value, rule => rule.Between(0, 100));
|
||||
|
||||
var invalid = validator.Validate(new IntValueModel { Value = 101 });
|
||||
var valid = validator.Validate(new IntValueModel { Value = 100 });
|
||||
|
||||
await AssertSingleProblem(invalid, nameof(IntValueModel.Value), "Value must be between 0 and 100.");
|
||||
await Assert.That(valid.IsValid).IsTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task I_can_validate_equal()
|
||||
{
|
||||
var validator = new PropertyValidator<StringValueModel, string?>(model
|
||||
=> model.Value, rule => rule.Equal("ACTIVE"));
|
||||
|
||||
var invalid = validator.Validate(new StringValueModel { Value = "INACTIVE" });
|
||||
var valid = validator.Validate(new StringValueModel { Value = "ACTIVE" });
|
||||
|
||||
await AssertSingleProblem(invalid, nameof(StringValueModel.Value), "Value must be equal to ACTIVE.");
|
||||
await Assert.That(valid.IsValid).IsTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task I_can_validate_not_equal()
|
||||
{
|
||||
var validator = new PropertyValidator<StringValueModel, string?>(model
|
||||
=> model.Value, rule => rule.NotEqual("BANNED"));
|
||||
|
||||
var invalid = validator.Validate(new StringValueModel { Value = "BANNED" });
|
||||
var valid = validator.Validate(new StringValueModel { Value = "ALLOWED" });
|
||||
|
||||
await AssertSingleProblem(invalid, nameof(StringValueModel.Value), "Value must not be equal to BANNED.");
|
||||
await Assert.That(valid.IsValid).IsTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task I_can_validate_equal_with_null_reference_values()
|
||||
{
|
||||
var validator = new PropertyValidator<StringValueModel, string?>(model
|
||||
=> model.Value, rule => rule.Equal("ACTIVE"));
|
||||
|
||||
var invalid = validator.Validate(new StringValueModel { Value = "INACTIVE" });
|
||||
var valid = validator.Validate(new StringValueModel { Value = "ACTIVE" });
|
||||
var nullValue = validator.Validate(new StringValueModel());
|
||||
|
||||
await AssertSingleProblem(invalid, nameof(StringValueModel.Value), "Value must be equal to ACTIVE.");
|
||||
await Assert.That(valid.IsValid).IsTrue();
|
||||
await Assert.That(nullValue.IsValid).IsFalse();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task I_can_validate_not_equal_with_null_reference_values()
|
||||
{
|
||||
var validator = new PropertyValidator<StringValueModel, string?>(model
|
||||
=> model.Value, rule => rule.NotEqual("BANNED"));
|
||||
|
||||
var invalid = validator.Validate(new StringValueModel { Value = "BANNED" });
|
||||
var valid = validator.Validate(new StringValueModel { Value = "ALLOWED" });
|
||||
var nullValue = validator.Validate(new StringValueModel());
|
||||
|
||||
await AssertSingleProblem(invalid, nameof(StringValueModel.Value), "Value must not be equal to BANNED.");
|
||||
await Assert.That(valid.IsValid).IsTrue();
|
||||
await Assert.That(nullValue.IsValid).IsTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task I_can_skip_null_values_for_reference_greater_than()
|
||||
{
|
||||
var validator = new PropertyValidator<StringValueModel, string?>(model
|
||||
=> model.Value, rule => rule.GreaterThan("B"));
|
||||
|
||||
var invalid = validator.Validate(new StringValueModel { Value = "A" });
|
||||
var valid = validator.Validate(new StringValueModel { Value = "C" });
|
||||
var ignoredNull = validator.Validate(new StringValueModel());
|
||||
|
||||
await AssertSingleProblem(invalid, nameof(StringValueModel.Value), "Value must be greater than B.");
|
||||
await Assert.That(valid.IsValid).IsTrue();
|
||||
await Assert.That(ignoredNull.IsValid).IsTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task I_can_skip_null_values_for_reference_between()
|
||||
{
|
||||
var validator = new PropertyValidator<StringValueModel, string?>(model
|
||||
=> model.Value, rule => rule.Between("A", "C"));
|
||||
|
||||
var invalid = validator.Validate(new StringValueModel { Value = "D" });
|
||||
var valid = validator.Validate(new StringValueModel { Value = "C" });
|
||||
var ignoredNull = validator.Validate(new StringValueModel());
|
||||
|
||||
await AssertSingleProblem(invalid, nameof(StringValueModel.Value), "Value must be between A and C.");
|
||||
await Assert.That(valid.IsValid).IsTrue();
|
||||
await Assert.That(ignoredNull.IsValid).IsTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task I_can_validate_matches_with_a_pattern()
|
||||
{
|
||||
var validator = new PropertyValidator<StringValueModel, string?>(model
|
||||
=> model.Value, rule => rule.Matches("^[A-Z]+$"));
|
||||
|
||||
var invalid = validator.Validate(new StringValueModel { Value = "Abc" });
|
||||
var valid = validator.Validate(new StringValueModel { Value = "ABC" });
|
||||
var ignoredNull = validator.Validate(new StringValueModel());
|
||||
|
||||
await AssertSingleProblem(invalid, nameof(StringValueModel.Value), "Value is not in the correct format.");
|
||||
await Assert.That(valid.IsValid).IsTrue();
|
||||
await Assert.That(ignoredNull.IsValid).IsTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task I_can_validate_matches_with_a_regex()
|
||||
{
|
||||
var validator = new PropertyValidator<StringValueModel, string?>(model
|
||||
=> model.Value, rule => rule.Matches(new Regex(@"^\d{4}$", RegexOptions.CultureInvariant)));
|
||||
|
||||
var invalid = validator.Validate(new StringValueModel { Value = "12AB" });
|
||||
var valid = validator.Validate(new StringValueModel { Value = "1234" });
|
||||
var ignoredNull = validator.Validate(new StringValueModel());
|
||||
|
||||
await AssertSingleProblem(invalid, nameof(StringValueModel.Value), "Value is not in the correct format.");
|
||||
await Assert.That(valid.IsValid).IsTrue();
|
||||
await Assert.That(ignoredNull.IsValid).IsTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task I_see_it_throw_for_invalid_length_configuration()
|
||||
{
|
||||
await Assert.That(() => new PropertyValidator<StringValueModel, string?>(model
|
||||
=> model.Value, rule => rule.Length(5, 4)))
|
||||
.Throws<ArgumentOutOfRangeException>();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task I_see_it_throw_for_negative_min_length_configuration()
|
||||
{
|
||||
await Assert.That(() => new PropertyValidator<StringValueModel, string?>(model
|
||||
=> model.Value, rule => rule.MinLength(-1)))
|
||||
.Throws<ArgumentOutOfRangeException>();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task I_see_it_throw_for_invalid_between_configuration()
|
||||
{
|
||||
await Assert.That(() => new PropertyValidator<IntValueModel, int>(model
|
||||
=> model.Value, rule => rule.Between(10, 0)))
|
||||
.Throws<ArgumentOutOfRangeException>();
|
||||
}
|
||||
|
||||
private static async Task AssertSingleProblem(Validation validation, string propertyName, string message)
|
||||
{
|
||||
await Assert.That(validation.Problems).Count().IsEqualTo(1);
|
||||
|
||||
var problem = validation.Problems.Single();
|
||||
|
||||
using (Assert.Multiple())
|
||||
{
|
||||
await Assert.That(problem.PropertyName).IsEqualTo(propertyName);
|
||||
await Assert.That(problem.Message).IsEqualTo(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue