mirror of https://github.com/Squidex/squidex.git
28 changed files with 1550 additions and 100 deletions
@ -0,0 +1,50 @@ |
|||
// ==========================================================================
|
|||
// StringField.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Generic; |
|||
using Squidex.Core.Schemas.Validators; |
|||
using Squidex.Infrastructure; |
|||
|
|||
namespace Squidex.Core.Schemas |
|||
{ |
|||
public sealed class StringField : Field<StringFieldProperties> |
|||
{ |
|||
public StringField(long id, string name, StringFieldProperties properties) |
|||
: base(id, name, properties) |
|||
{ |
|||
} |
|||
|
|||
protected override IEnumerable<IValidator> CreateValidators() |
|||
{ |
|||
if (Properties.IsRequired) |
|||
{ |
|||
yield return new RequiredStringValidator(); |
|||
} |
|||
|
|||
if (Properties.MinLength.HasValue || Properties.MaxLength.HasValue) |
|||
{ |
|||
yield return new StringLengthValidator(Properties.MinLength, Properties.MaxLength); |
|||
} |
|||
|
|||
if (!string.IsNullOrWhiteSpace(Properties.Pattern)) |
|||
{ |
|||
yield return new PatternValidator(Properties.Pattern, Properties.PatternMessage); |
|||
} |
|||
} |
|||
|
|||
protected override object ConvertValue(PropertyValue property) |
|||
{ |
|||
return property.ToString(); |
|||
} |
|||
|
|||
protected override Field Clone() |
|||
{ |
|||
return new StringField(Id, Name, Properties); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,97 @@ |
|||
// ==========================================================================
|
|||
// StringFieldProperties.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Text.RegularExpressions; |
|||
using Squidex.Infrastructure; |
|||
// ReSharper disable ObjectCreationAsStatement
|
|||
|
|||
namespace Squidex.Core.Schemas |
|||
{ |
|||
public sealed class StringFieldProperties : FieldProperties |
|||
{ |
|||
private int? minLength; |
|||
private int? maxLength; |
|||
private string pattern; |
|||
private string patternMessage; |
|||
|
|||
public int? MinLength |
|||
{ |
|||
get { return minLength; } |
|||
set |
|||
{ |
|||
ThrowIfFrozen(); |
|||
|
|||
minLength = value; |
|||
} |
|||
} |
|||
|
|||
public int? MaxLength |
|||
{ |
|||
get { return maxLength; } |
|||
set |
|||
{ |
|||
ThrowIfFrozen(); |
|||
|
|||
maxLength = value; |
|||
} |
|||
} |
|||
|
|||
public string Pattern |
|||
{ |
|||
get { return pattern; } |
|||
set |
|||
{ |
|||
ThrowIfFrozen(); |
|||
|
|||
pattern = value; |
|||
} |
|||
} |
|||
|
|||
public string PatternMessage |
|||
{ |
|||
get { return patternMessage; } |
|||
set |
|||
{ |
|||
ThrowIfFrozen(); |
|||
|
|||
patternMessage = value; |
|||
} |
|||
} |
|||
|
|||
protected override IEnumerable<ValidationError> ValidateCore() |
|||
{ |
|||
if (MaxLength.HasValue && MinLength.HasValue && MinLength.Value >= MaxLength.Value) |
|||
{ |
|||
yield return new ValidationError("Max length must be greater than min length", nameof(MinLength), nameof(MaxLength)); |
|||
} |
|||
|
|||
if (Pattern == null) |
|||
{ |
|||
yield break; |
|||
} |
|||
|
|||
var isValidPattern = true; |
|||
|
|||
try |
|||
{ |
|||
new Regex(Pattern); |
|||
} |
|||
catch (ArgumentException) |
|||
{ |
|||
isValidPattern = false; |
|||
} |
|||
|
|||
if (!isValidPattern) |
|||
{ |
|||
yield return new ValidationError("Pattern is not a valid expression", nameof(Pattern)); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,55 @@ |
|||
// ==========================================================================
|
|||
// PatternValidator.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Generic; |
|||
using System.Text.RegularExpressions; |
|||
using System.Threading.Tasks; |
|||
using Squidex.Infrastructure.Tasks; |
|||
|
|||
// ReSharper disable ConvertIfStatementToConditionalTernaryExpression
|
|||
// ReSharper disable InvertIf
|
|||
|
|||
namespace Squidex.Core.Schemas.Validators |
|||
{ |
|||
public class PatternValidator : IValidator |
|||
{ |
|||
private readonly Regex regex; |
|||
private readonly string errorMessage; |
|||
|
|||
public PatternValidator(string pattern, string errorMessage = null) |
|||
{ |
|||
this.errorMessage = errorMessage; |
|||
|
|||
regex = new Regex(pattern); |
|||
} |
|||
|
|||
public Task ValidateAsync(object value, ICollection<string> errors) |
|||
{ |
|||
var stringValue = value as string; |
|||
|
|||
if (stringValue == null) |
|||
{ |
|||
return TaskHelper.Done; |
|||
} |
|||
|
|||
if (!regex.IsMatch(stringValue)) |
|||
{ |
|||
if (string.IsNullOrWhiteSpace(errorMessage)) |
|||
{ |
|||
errors.Add("<FIELD> is not valid"); |
|||
} |
|||
else |
|||
{ |
|||
errors.Add(errorMessage); |
|||
} |
|||
} |
|||
|
|||
return TaskHelper.Done; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,54 @@ |
|||
// ==========================================================================
|
|||
// StringLengthValidator.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using Squidex.Infrastructure.Tasks; |
|||
|
|||
namespace Squidex.Core.Schemas.Validators |
|||
{ |
|||
public class StringLengthValidator : IValidator |
|||
{ |
|||
private readonly int? minLength; |
|||
private readonly int? maxLength; |
|||
|
|||
public StringLengthValidator(int? minLength, int? maxLength) |
|||
{ |
|||
if (minLength.HasValue && maxLength.HasValue && minLength.Value >= maxLength.Value) |
|||
{ |
|||
throw new ArgumentException("Min length must be greater than max length", nameof(minLength)); |
|||
} |
|||
|
|||
this.minLength = minLength; |
|||
this.maxLength = maxLength; |
|||
} |
|||
|
|||
public Task ValidateAsync(object value, ICollection<string> errors) |
|||
{ |
|||
var stringValue = value as string; |
|||
|
|||
if (stringValue == null) |
|||
{ |
|||
return TaskHelper.Done; |
|||
} |
|||
|
|||
if (minLength.HasValue && stringValue.Length < minLength.Value) |
|||
{ |
|||
errors.Add($"<FIELD> must have more than '{minLength}' characters"); |
|||
} |
|||
|
|||
if (maxLength.HasValue && stringValue.Length > maxLength.Value) |
|||
{ |
|||
errors.Add($"<FIELD> must have less than '{maxLength}' characters"); |
|||
} |
|||
|
|||
return TaskHelper.Done; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,209 @@ |
|||
// ==========================================================================
|
|||
// Guard.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Diagnostics; |
|||
using System.IO; |
|||
using System.Linq; |
|||
using System.Runtime.CompilerServices; |
|||
|
|||
// ReSharper disable InvertIf
|
|||
|
|||
namespace Squidex.Infrastructure |
|||
{ |
|||
public static class UpdateProperties |
|||
{ |
|||
[DebuggerStepThrough] |
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void ValidNumber(float target, string parameterName) |
|||
{ |
|||
if (float.IsNaN(target) || float.IsPositiveInfinity(target) || float.IsNegativeInfinity(target)) |
|||
{ |
|||
throw new ArgumentException("Value must be a valid number.", parameterName); |
|||
} |
|||
} |
|||
|
|||
[DebuggerStepThrough] |
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void ValidNumber(double target, string parameterName) |
|||
{ |
|||
if (double.IsNaN(target) || double.IsPositiveInfinity(target) || double.IsNegativeInfinity(target)) |
|||
{ |
|||
throw new ArgumentException("Value must be a valid number.", parameterName); |
|||
} |
|||
} |
|||
|
|||
[DebuggerStepThrough] |
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void ValidSlug(string target, string parameterName) |
|||
{ |
|||
NotNullOrEmpty(target, parameterName); |
|||
|
|||
if (!target.IsSlug()) |
|||
{ |
|||
throw new ArgumentException("Target is not a valid slug.", parameterName); |
|||
} |
|||
} |
|||
|
|||
[DebuggerStepThrough] |
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void HasType<T>(object target, string parameterName) |
|||
{ |
|||
if (target != null && target.GetType() != typeof(T)) |
|||
{ |
|||
throw new ArgumentException($"The parameter must be of type {typeof(T)}", parameterName); |
|||
} |
|||
} |
|||
|
|||
[DebuggerStepThrough] |
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void HasType(object target, Type expectedType, string parameterName) |
|||
{ |
|||
if (target != null && expectedType != null && target.GetType() != expectedType) |
|||
{ |
|||
throw new ArgumentException($"The parameter must be of type {expectedType}", parameterName); |
|||
} |
|||
} |
|||
|
|||
[DebuggerStepThrough] |
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void Between<TValue>(TValue target, TValue lower, TValue upper, string parameterName) where TValue : IComparable |
|||
{ |
|||
if (!target.IsBetween(lower, upper)) |
|||
{ |
|||
throw new ArgumentException($"Value must be between {lower} and {upper}", parameterName); |
|||
} |
|||
} |
|||
|
|||
[DebuggerStepThrough] |
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void Enum<TEnum>(TEnum target, string parameterName) where TEnum : struct |
|||
{ |
|||
if (!target.IsEnumValue()) |
|||
{ |
|||
throw new ArgumentException($"Value must be a valid enum type {typeof(TEnum)}", parameterName); |
|||
} |
|||
} |
|||
|
|||
[DebuggerStepThrough] |
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void GreaterThan<TValue>(TValue target, TValue lower, string parameterName) where TValue : IComparable |
|||
{ |
|||
if (target.CompareTo(lower) <= 0) |
|||
{ |
|||
throw new ArgumentException($"Value must be greater than {lower}", parameterName); |
|||
} |
|||
} |
|||
|
|||
[DebuggerStepThrough] |
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void GreaterEquals<TValue>(TValue target, TValue lower, string parameterName) where TValue : IComparable |
|||
{ |
|||
if (target.CompareTo(lower) < 0) |
|||
{ |
|||
throw new ArgumentException($"Value must be greater or equals than {lower}", parameterName); |
|||
} |
|||
} |
|||
|
|||
[DebuggerStepThrough] |
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void LessThan<TValue>(TValue target, TValue upper, string parameterName) where TValue : IComparable |
|||
{ |
|||
if (target.CompareTo(upper) >= 0) |
|||
{ |
|||
throw new ArgumentException($"Value must be less than {upper}", parameterName); |
|||
} |
|||
} |
|||
|
|||
[DebuggerStepThrough] |
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void LessEquals<TValue>(TValue target, TValue upper, string parameterName) where TValue : IComparable |
|||
{ |
|||
if (target.CompareTo(upper) > 0) |
|||
{ |
|||
throw new ArgumentException($"Value must be less or equals than {upper}", parameterName); |
|||
} |
|||
} |
|||
|
|||
[DebuggerStepThrough] |
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void NotEmpty<TType>(ICollection<TType> enumerable, string parameterName) |
|||
{ |
|||
NotNull(enumerable, parameterName); |
|||
|
|||
if (enumerable.Count == 0) |
|||
{ |
|||
throw new ArgumentException("Collection does not contain an item", parameterName); |
|||
} |
|||
} |
|||
|
|||
[DebuggerStepThrough] |
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void NotEmpty(Guid target, string parameterName) |
|||
{ |
|||
if (target == Guid.Empty) |
|||
{ |
|||
throw new ArgumentException("Value cannot be empty.", parameterName); |
|||
} |
|||
} |
|||
|
|||
[DebuggerStepThrough] |
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void NotNull(object target, string parameterName) |
|||
{ |
|||
if (target == null) |
|||
{ |
|||
throw new ArgumentNullException(parameterName); |
|||
} |
|||
} |
|||
|
|||
[DebuggerStepThrough] |
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void NotDefault<T>(T target, string parameterName) |
|||
{ |
|||
if (Equals(target, default(T))) |
|||
{ |
|||
throw new ArgumentException("Value cannot be an the default value", parameterName); |
|||
} |
|||
} |
|||
|
|||
[DebuggerStepThrough] |
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void NotNullOrEmpty(string target, string parameterName) |
|||
{ |
|||
NotNull(target, parameterName); |
|||
|
|||
if (string.IsNullOrWhiteSpace(target)) |
|||
{ |
|||
throw new ArgumentException("String parameter cannot be null or empty and cannot contain only blanks.", parameterName); |
|||
} |
|||
} |
|||
|
|||
[DebuggerStepThrough] |
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void ValidFileName(string target, string parameterName) |
|||
{ |
|||
NotNullOrEmpty(target, parameterName); |
|||
|
|||
if (target.Intersect(Path.GetInvalidFileNameChars()).Any()) |
|||
{ |
|||
throw new ArgumentException("Value contains an invalid character.", parameterName); |
|||
} |
|||
} |
|||
|
|||
[DebuggerStepThrough] |
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void Valid(IValidatable target, string parameterName, Func<string> message) |
|||
{ |
|||
NotNull(target, parameterName); |
|||
|
|||
target.Validate(message); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,79 @@ |
|||
// ==========================================================================
|
|||
// FieldRegistryTests.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using Squidex.Core.Schemas; |
|||
using Squidex.Infrastructure; |
|||
using Xunit; |
|||
|
|||
namespace Squidex.Core.Tests.Schemas |
|||
{ |
|||
public class FieldRegistryTests |
|||
{ |
|||
private readonly FieldRegistry sut = new FieldRegistry(); |
|||
|
|||
public sealed class InvalidProperties : FieldProperties |
|||
{ |
|||
protected override IEnumerable<ValidationError> ValidateCore() |
|||
{ |
|||
yield break; |
|||
} |
|||
} |
|||
|
|||
static FieldRegistryTests() |
|||
{ |
|||
TypeNameRegistry.Map(typeof(NumberFieldProperties), "number"); |
|||
TypeNameRegistry.Map(typeof(InvalidProperties), "invalid"); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_throw_if_creating_field_and_field_is_not_registered() |
|||
{ |
|||
Assert.Throws<InvalidOperationException>(() => sut.CreateField(1, "name", new InvalidProperties())); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_create_field_by_properties() |
|||
{ |
|||
var properties = new NumberFieldProperties(); |
|||
|
|||
var field = sut.CreateField(1, "name", properties); |
|||
|
|||
Assert.Equal(properties, field.RawProperties); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_throw_if_getting_by_type_and_field_is_not_registered() |
|||
{ |
|||
Assert.Throws<InvalidOperationException>(() => sut.FindByPropertiesType(typeof(InvalidProperties))); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_find_registration_by_properties_type() |
|||
{ |
|||
var registry = sut.FindByPropertiesType(typeof(NumberFieldProperties)); |
|||
|
|||
Assert.Equal(typeof(NumberFieldProperties), registry.PropertiesType); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_throw_if_getting_by_name_and_field_is_not_registered() |
|||
{ |
|||
Assert.Throws<DomainException>(() => sut.FindByTypeName("invalid")); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_find_registration_by_name() |
|||
{ |
|||
var registry = sut.FindByTypeName("number"); |
|||
|
|||
Assert.Equal(typeof(NumberFieldProperties), registry.PropertiesType); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,101 @@ |
|||
// ==========================================================================
|
|||
// NumberFieldTests.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Generic; |
|||
using System.Collections.Immutable; |
|||
using System.Threading.Tasks; |
|||
using Squidex.Core.Schemas; |
|||
using Squidex.Infrastructure; |
|||
using Xunit; |
|||
using FluentAssertions; |
|||
|
|||
namespace Squidex.Core.Tests.Schemas |
|||
{ |
|||
public class NumberFieldTests |
|||
{ |
|||
private readonly List<string> errors = new List<string>(); |
|||
|
|||
[Fact] |
|||
public void Should_instantiate_field() |
|||
{ |
|||
var sut = new NumberField(1, "name", new NumberFieldProperties()); |
|||
|
|||
Assert.Equal("name", sut.Name); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_clone_object() |
|||
{ |
|||
var sut = new NumberField(1, "name", new NumberFieldProperties()); |
|||
|
|||
Assert.NotEqual(sut, sut.Enable()); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_add_errors_if_number_is_required() |
|||
{ |
|||
var sut = new NumberField(1, "name", new NumberFieldProperties { Label = "Name", IsRequired = true }); |
|||
|
|||
await sut.ValidateAsync(CreateValue(null), errors); |
|||
|
|||
errors.ShouldBeEquivalentTo( |
|||
new [] { "Name is required" }); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_add_errors_if_number_is_less_than_min() |
|||
{ |
|||
var sut = new NumberField(1, "name", new NumberFieldProperties { Label = "Name", MinValue = 10 }); |
|||
|
|||
await sut.ValidateAsync(CreateValue(5), errors); |
|||
|
|||
errors.ShouldBeEquivalentTo( |
|||
new[] { "Name must be greater than '10'" }); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_add_errors_if_number_is_greater_than_max() |
|||
{ |
|||
var sut = new NumberField(1, "name", new NumberFieldProperties { Label = "Name", MaxValue = 10 }); |
|||
|
|||
await sut.ValidateAsync(CreateValue(20), errors); |
|||
|
|||
errors.ShouldBeEquivalentTo( |
|||
new[] { "Name must be less than '10'" }); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_add_errors_if_number_is_not_allowed() |
|||
{ |
|||
var sut = new NumberField(1, "name", new NumberFieldProperties { Label = "Name", AllowedValues = ImmutableList.Create(10d) }); |
|||
|
|||
await sut.ValidateAsync(CreateValue(20), errors); |
|||
|
|||
errors.ShouldBeEquivalentTo( |
|||
new[] { "Name is not an allowed value" }); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_add_errors_if_value_is_not_valid() |
|||
{ |
|||
var sut = new NumberField(1, "name", new NumberFieldProperties { Label = "Name" }); |
|||
|
|||
await sut.ValidateAsync(CreateValue("Invalid"), errors); |
|||
|
|||
errors.ShouldBeEquivalentTo( |
|||
new[] { "Name is not a valid value" }); |
|||
} |
|||
|
|||
private static PropertyValue CreateValue(object v) |
|||
{ |
|||
var bag = new PropertiesBag().Set("value", v); |
|||
|
|||
return bag["value"]; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,280 @@ |
|||
// ==========================================================================
|
|||
// SchemaTests.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using Squidex.Core.Schemas; |
|||
using Squidex.Infrastructure; |
|||
using Xunit; |
|||
using FluentAssertions; |
|||
|
|||
namespace Squidex.Core.Tests.Schemas |
|||
{ |
|||
public class SchemaTests |
|||
{ |
|||
private Schema sut = Schema.Create("my-name", new SchemaProperties()); |
|||
|
|||
private sealed class InvalidProperties : FieldProperties |
|||
{ |
|||
protected override IEnumerable<ValidationError> ValidateCore() |
|||
{ |
|||
yield break; |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_instantiate_field() |
|||
{ |
|||
var properties = new SchemaProperties { Hints = "my-hint", Label = "my-label" }; |
|||
|
|||
var schema = Schema.Create("my-name", properties); |
|||
|
|||
Assert.Equal("my-name", schema.Name); |
|||
Assert.Equal(properties, schema.Properties); |
|||
|
|||
Assert.True(properties.IsFrozen); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_throw_if_creating_schema_with_invalid_name() |
|||
{ |
|||
Assert.Throws<ValidationException>(() => Schema.Create("Invalid Name", new SchemaProperties())); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_update_schema() |
|||
{ |
|||
var properties = new SchemaProperties { Hints = "my-hint", Label = "my-label" }; |
|||
|
|||
sut = sut.Update(properties); |
|||
|
|||
Assert.Equal(properties, sut.Properties); |
|||
|
|||
Assert.True(properties.IsFrozen); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_add_field() |
|||
{ |
|||
var field = AddField(); |
|||
|
|||
Assert.Equal(field, sut.Fields[1]); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_throw_if_adding_field_with_name_that_already_exists() |
|||
{ |
|||
AddField(); |
|||
|
|||
Assert.Throws<ValidationException>(() => sut.AddOrUpdateField(new NumberField(2, "my-field", new NumberFieldProperties()))); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_hide_field() |
|||
{ |
|||
AddField(); |
|||
|
|||
sut = sut.HideField(1); |
|||
sut = sut.HideField(1); |
|||
|
|||
Assert.True(sut.Fields[1].IsHidden); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_throw_if_field_to_hide_does_not_exist() |
|||
{ |
|||
Assert.Throws<DomainObjectNotFoundException>(() => sut.HideField(1)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_show_field() |
|||
{ |
|||
AddField(); |
|||
|
|||
sut = sut.HideField(1); |
|||
sut = sut.ShowField(1); |
|||
sut = sut.ShowField(1); |
|||
|
|||
Assert.False(sut.Fields[1].IsHidden); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_throw_if_field_to_show_does_not_exist() |
|||
{ |
|||
Assert.Throws<DomainObjectNotFoundException>(() => sut.ShowField(2)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_disable_field() |
|||
{ |
|||
AddField(); |
|||
|
|||
sut = sut.DisableField(1); |
|||
sut = sut.DisableField(1); |
|||
|
|||
Assert.True(sut.Fields[1].IsDisabled); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_throw_if_field_to_disable_does_not_exist() |
|||
{ |
|||
Assert.Throws<DomainObjectNotFoundException>(() => sut.DisableField(1)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_enable_field() |
|||
{ |
|||
AddField(); |
|||
|
|||
sut = sut.DisableField(1); |
|||
sut = sut.EnableField(1); |
|||
sut = sut.EnableField(1); |
|||
|
|||
Assert.False(sut.Fields[1].IsDisabled); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_throw_if_field_to_enable_does_not_exist() |
|||
{ |
|||
Assert.Throws<DomainObjectNotFoundException>(() => sut.EnableField(1)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_rename_field() |
|||
{ |
|||
AddField(); |
|||
|
|||
sut = sut.RenameField(1, "new-name"); |
|||
|
|||
Assert.Equal("new-name", sut.Fields[1].Name); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_throw_if_new_field_already_exists() |
|||
{ |
|||
AddField(); |
|||
|
|||
sut = sut.AddOrUpdateField(new NumberField(2, "other-field", new NumberFieldProperties())); |
|||
|
|||
Assert.Throws<ValidationException>(() => sut.RenameField(2, "my-field")); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_throw_if_new_field_name_is_not_valid() |
|||
{ |
|||
AddField(); |
|||
|
|||
Assert.Throws<ValidationException>(() => sut.RenameField(1, "new name")); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_throw_if_field_to_rename_does_not_exist() |
|||
{ |
|||
Assert.Throws<DomainObjectNotFoundException>(() => sut.RenameField(1, "new-name")); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_delete_field() |
|||
{ |
|||
AddField(); |
|||
|
|||
sut = sut.DeleteField(1); |
|||
|
|||
Assert.Equal(0, sut.Fields.Count); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_not_throw_if_field_to_delete_does_not_exist() |
|||
{ |
|||
sut.DeleteField(1); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_update_field() |
|||
{ |
|||
AddField(); |
|||
|
|||
sut = sut.UpdateField(1, new NumberFieldProperties { Hints = "my-hints" }); |
|||
|
|||
Assert.Equal("my-hints", sut.Fields[1].RawProperties.Hints); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_throw_if_updating_field_with_invalid_property_type() |
|||
{ |
|||
AddField(); |
|||
|
|||
Assert.Throws<ArgumentException>(() => sut.UpdateField(1, new InvalidProperties())); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_throw_if_field_to_update_does_not_exist() |
|||
{ |
|||
Assert.Throws<DomainObjectNotFoundException>(() => sut.UpdateField(1, new NumberFieldProperties())); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_add_error_if_validating_bag_with_unknown_field() |
|||
{ |
|||
var errors = new List<ValidationError>(); |
|||
var bag = new PropertiesBag().Set("unknown", 123); |
|||
|
|||
await sut.ValidateAsync(bag, errors); |
|||
|
|||
errors.ShouldBeEquivalentTo( |
|||
new List<ValidationError> |
|||
{ |
|||
new ValidationError("unknown is not a known field", "unknown") |
|||
}); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_add_error_if_validating_bag_with_invalid_field() |
|||
{ |
|||
sut = sut.AddOrUpdateField(new NumberField(1, "my-field", new NumberFieldProperties { MaxValue = 100 })); |
|||
|
|||
var errors = new List<ValidationError>(); |
|||
var bag = new PropertiesBag().Set("my-field", 123); |
|||
|
|||
await sut.ValidateAsync(bag, errors); |
|||
|
|||
errors.ShouldBeEquivalentTo( |
|||
new List<ValidationError> |
|||
{ |
|||
new ValidationError("my-field must be less than '100'", "my-field") |
|||
}); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_add_error_if_required_field_is_not_in_bag() |
|||
{ |
|||
sut = sut.AddOrUpdateField(new NumberField(1, "my-field", new NumberFieldProperties { IsRequired = true })); |
|||
|
|||
var errors = new List<ValidationError>(); |
|||
var bag = new PropertiesBag(); |
|||
|
|||
await sut.ValidateAsync(bag, errors); |
|||
|
|||
errors.ShouldBeEquivalentTo( |
|||
new List<ValidationError> |
|||
{ |
|||
new ValidationError("my-field is required", "my-field") |
|||
}); |
|||
} |
|||
|
|||
private NumberField AddField() |
|||
{ |
|||
var field = new NumberField(1, "my-field", new NumberFieldProperties()); |
|||
|
|||
sut = sut.AddOrUpdateField(field); |
|||
|
|||
return field; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,95 @@ |
|||
// ==========================================================================
|
|||
// StringFieldPropertiesTests.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Reflection; |
|||
using FluentAssertions; |
|||
using Squidex.Core.Schemas; |
|||
using Squidex.Core.Schemas.Validators; |
|||
using Squidex.Infrastructure; |
|||
using Xunit; |
|||
|
|||
namespace Squidex.Core.Tests.Schemas |
|||
{ |
|||
public class StringFieldPropertiesTests |
|||
{ |
|||
private readonly List<ValidationError> errors = new List<ValidationError>(); |
|||
|
|||
[Fact] |
|||
public void Should_add_error_if_min_greater_than_max() |
|||
{ |
|||
var sut = new StringFieldProperties { MinLength = 10, MaxLength = 5 }; |
|||
|
|||
sut.Validate(errors); |
|||
|
|||
errors.ShouldBeEquivalentTo( |
|||
new List<ValidationError> |
|||
{ |
|||
new ValidationError("Max length must be greater than min length", "MinLength", "MaxLength") |
|||
}); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_add_error_if_pattern_is_not_valid_regex() |
|||
{ |
|||
var sut = new StringFieldProperties { Pattern = "[0-9{1}"}; |
|||
|
|||
sut.Validate(errors); |
|||
|
|||
errors.ShouldBeEquivalentTo( |
|||
new List<ValidationError> |
|||
{ |
|||
new ValidationError("Pattern is not a valid expression", "Pattern") |
|||
}); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_set_or_freeze_sut() |
|||
{ |
|||
var sut = new StringFieldProperties(); |
|||
|
|||
foreach (var property in sut.GetType().GetRuntimeProperties().Where(x => x.Name != "IsFrozen")) |
|||
{ |
|||
var value = |
|||
property.PropertyType.GetTypeInfo().IsValueType ? |
|||
Activator.CreateInstance(property.PropertyType) : |
|||
null; |
|||
|
|||
property.SetValue(sut, value); |
|||
|
|||
var result = property.GetValue(sut); |
|||
|
|||
Assert.Equal(value, result); |
|||
} |
|||
|
|||
sut.Freeze(); |
|||
|
|||
foreach (var property in sut.GetType().GetRuntimeProperties().Where(x => x.Name != "IsFrozen")) |
|||
{ |
|||
var value = |
|||
property.PropertyType.GetTypeInfo().IsValueType ? |
|||
Activator.CreateInstance(property.PropertyType) : |
|||
null; |
|||
|
|||
Assert.Throws<InvalidOperationException>(() => |
|||
{ |
|||
try |
|||
{ |
|||
property.SetValue(sut, value); |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
throw e.InnerException; |
|||
} |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,100 @@ |
|||
// ==========================================================================
|
|||
// StringFieldTests.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using Squidex.Core.Schemas; |
|||
using Squidex.Infrastructure; |
|||
using Xunit; |
|||
using FluentAssertions; |
|||
|
|||
namespace Squidex.Core.Tests.Schemas |
|||
{ |
|||
public class StringFieldTests |
|||
{ |
|||
private readonly List<string> errors = new List<string>(); |
|||
|
|||
[Fact] |
|||
public void Should_instantiate_field() |
|||
{ |
|||
var sut = new StringField(1, "name", new StringFieldProperties()); |
|||
|
|||
Assert.Equal("name", sut.Name); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_clone_object() |
|||
{ |
|||
var sut = new StringField(1, "name", new StringFieldProperties()); |
|||
|
|||
Assert.NotEqual(sut, sut.Enable()); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_add_errors_if_string_is_required() |
|||
{ |
|||
var sut = new StringField(1, "name", new StringFieldProperties { Label = "Name", IsRequired = true }); |
|||
|
|||
await sut.ValidateAsync(CreateValue(null), errors); |
|||
|
|||
errors.ShouldBeEquivalentTo( |
|||
new[] { "Name is required" }); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_add_errors_if_string_shorter_than_max() |
|||
{ |
|||
var sut = new StringField(1, "name", new StringFieldProperties { Label = "Name", MinLength = 10 }); |
|||
|
|||
await sut.ValidateAsync(CreateValue("123"), errors); |
|||
|
|||
errors.ShouldBeEquivalentTo( |
|||
new[] { "Name must have more than '10' characters" }); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_add_errors_if_number_is_greater_than_max() |
|||
{ |
|||
var sut = new StringField(1, "name", new StringFieldProperties { Label = "Name", MaxLength = 5 }); |
|||
|
|||
await sut.ValidateAsync(CreateValue("12345678"), errors); |
|||
|
|||
errors.ShouldBeEquivalentTo( |
|||
new[] { "Name must have less than '5' characters" }); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_add_errors_if_number_is_not_valid_pattern() |
|||
{ |
|||
var sut = new StringField(1, "name", new StringFieldProperties { Label = "Name", Pattern = "^[0-9]{3}$" }); |
|||
|
|||
await sut.ValidateAsync(CreateValue("abc"), errors); |
|||
|
|||
errors.ShouldBeEquivalentTo( |
|||
new[] { "Name is not valid" }); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_add_errors_if_number_is_not_valid_pattern_with_message() |
|||
{ |
|||
var sut = new StringField(1, "name", new StringFieldProperties { Label = "Name", Pattern = "^[0-9]{3}$", PatternMessage = "Custom Error Message" }); |
|||
|
|||
await sut.ValidateAsync(CreateValue("abc"), errors); |
|||
|
|||
errors.ShouldBeEquivalentTo( |
|||
new[] { "Custom Error Message" }); |
|||
} |
|||
|
|||
private static PropertyValue CreateValue(object v) |
|||
{ |
|||
var bag = new PropertiesBag().Set("value", v); |
|||
|
|||
return bag["value"]; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,63 @@ |
|||
// ==========================================================================
|
|||
// PatternValidatorTests.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using FluentAssertions; |
|||
using Squidex.Core.Schemas.Validators; |
|||
using Xunit; |
|||
|
|||
namespace Squidex.Core.Tests.Schemas.Validators |
|||
{ |
|||
public class PatternValidatorTests |
|||
{ |
|||
private readonly List<string> errors = new List<string>(); |
|||
|
|||
[Fact] |
|||
public async Task Should_not_add_error_if_value_is_valid() |
|||
{ |
|||
var sut = new PatternValidator("^[a-z]{3}:[0-9]{2}$"); |
|||
|
|||
await sut.ValidateAsync("abc:12", errors); |
|||
|
|||
Assert.Equal(0, errors.Count); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_not_add_error_if_value_is_null() |
|||
{ |
|||
var sut = new PatternValidator("^[a-z]{3}:[0-9]{2}$"); |
|||
|
|||
await sut.ValidateAsync(null, errors); |
|||
|
|||
Assert.Equal(0, errors.Count); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_add_error_with_default_message_if_value_is_not_valid() |
|||
{ |
|||
var sut = new PatternValidator("^[a-z]{3}:[0-9]{2}$"); |
|||
|
|||
await sut.ValidateAsync("foo", errors); |
|||
|
|||
errors.ShouldBeEquivalentTo( |
|||
new[] { "<FIELD> is not valid" }); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_add_error_with_custom_message_if_value_is_not_valid() |
|||
{ |
|||
var sut = new PatternValidator("^[a-z]{3}:[0-9]{2}$", "Custom Error Message"); |
|||
|
|||
await sut.ValidateAsync("foo", errors); |
|||
|
|||
errors.ShouldBeEquivalentTo( |
|||
new[] { "Custom Error Message" }); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,89 @@ |
|||
// ==========================================================================
|
|||
// MinMaxValidatorTests.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
using FluentAssertions; |
|||
using Squidex.Core.Schemas.Validators; |
|||
using Xunit; |
|||
|
|||
namespace Squidex.Core.Tests.Schemas.Validators |
|||
{ |
|||
public class StringLengthValidatorTests |
|||
{ |
|||
private readonly List<string> errors = new List<string>(); |
|||
|
|||
[Fact] |
|||
public async Task Should_not_error_if_value_is_null() |
|||
{ |
|||
var sut = new StringLengthValidator(100, 200); |
|||
|
|||
await sut.ValidateAsync(null, errors); |
|||
|
|||
Assert.Equal(0, errors.Count); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(null, null)] |
|||
[InlineData(1000, null)] |
|||
[InlineData(1000, 2000)] |
|||
[InlineData(null, 2000)] |
|||
public async Task Should_not_add_error_if_value_is_within_range(int? min, int? max) |
|||
{ |
|||
var sut = new StringLengthValidator(min, max); |
|||
|
|||
await sut.ValidateAsync(CreateString(1500), errors); |
|||
|
|||
Assert.Equal(0, errors.Count); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(20, 10)] |
|||
[InlineData(10, 10)] |
|||
public void Should_throw_error_if_min_greater_than_max(int? min, int? max) |
|||
{ |
|||
Assert.Throws<ArgumentException>(() => new StringLengthValidator(min, max)); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_add_error_if_value_is_smaller_than_min() |
|||
{ |
|||
var sut = new StringLengthValidator(2000, null); |
|||
|
|||
await sut.ValidateAsync(CreateString(1500), errors); |
|||
|
|||
errors.ShouldBeEquivalentTo( |
|||
new[] { "<FIELD> must have more than '2000' characters" }); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_add_error_if_value_is_greater_than_max() |
|||
{ |
|||
var sut = new StringLengthValidator(null, 1000); |
|||
|
|||
await sut.ValidateAsync(CreateString(1500), errors); |
|||
|
|||
errors.ShouldBeEquivalentTo( |
|||
new[] { "<FIELD> must have less than '1000' characters" }); |
|||
} |
|||
|
|||
private static string CreateString(int size) |
|||
{ |
|||
var sb = new StringBuilder(); |
|||
|
|||
for (var i = 0; i < size; i++) |
|||
{ |
|||
sb.Append("x"); |
|||
} |
|||
|
|||
return sb.ToString(); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue