Browse Source

More tests and fixes to make the tests greeen.

pull/297/head
Sebastian 8 years ago
parent
commit
511d364109
  1. 15
      src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ContentValidator.cs
  2. 28
      src/Squidex.Domain.Apps.Core.Operations/ValidateContent/JsonValueConverter.cs
  3. 52
      src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ObjectPath.cs
  4. 19
      src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ValidationContext.cs
  5. 2
      src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AllowedValuesValidator.cs
  6. 31
      src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AssetsValidator.cs
  7. 6
      src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/CollectionItemValidator.cs
  8. 6
      src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/CollectionValidator.cs
  9. 12
      src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/FieldValidator.cs
  10. 46
      src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/Formatter.cs
  11. 5
      src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/IValidator.cs
  12. 16
      src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ObjectValidator.cs
  13. 6
      src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/PatternValidator.cs
  14. 4
      src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/RangeValidator.cs
  15. 2
      src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ReferencesValidator.cs
  16. 2
      src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/RequiredStringValidator.cs
  17. 2
      src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/RequiredValidator.cs
  18. 4
      src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/StringLengthValidator.cs
  19. 6
      src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ValidatorsFactory.cs
  20. 2
      tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/ArrayFieldTests.cs
  21. 2
      tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/SchemaTests.cs
  22. 116
      tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ArrayFieldTests.cs
  23. 51
      tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ContentValidationTests.cs
  24. 4
      tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ReferencesFieldTests.cs
  25. 10
      tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/TagsFieldTests.cs
  26. 8
      tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ValidationTestExtensions.cs

15
src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ContentValidator.cs

@ -5,8 +5,11 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Contents;
@ -42,9 +45,11 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
this.partitionResolver = partitionResolver;
}
private void AddError(string field, string message)
private void AddError(IEnumerable<string> path, string message)
{
errors.Add(new ValidationError($"{field}: {message}", field));
var pathString = path.ToPathString();
errors.Add(new ValidationError($"{pathString}: {message}", pathString));
}
public Task ValidatePartialAsync(NamedContentData data)
@ -74,14 +79,14 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
fieldsValidators[field.Key] = (!field.Value.RawProperties.IsRequired, CreateFieldValidator(field.Value, isPartial));
}
return new ObjectValidator<ContentFieldData>(fieldsValidators, isPartial, "field", DefaultFieldData, Formatter.CombineForLanguage);
return new ObjectValidator<ContentFieldData>(fieldsValidators, isPartial, "field", DefaultFieldData);
}
private IValidator CreateFieldValidator(IRootField field, bool isPartial)
{
var partitioning = partitionResolver(field.Partitioning);
var fieldValidator = new FieldValidator(ValidatorsFactory.CreateValidators(field), field);
var fieldValidator = new FieldValidator(ValidatorsFactory.CreateValidators(field).ToArray(), field);
var fieldsValidators = new Dictionary<string, (bool IsOptional, IValidator Validator)>();
foreach (var partition in partitioning)
@ -93,7 +98,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
var type = isLanguage ? "language" : "invariant value";
return new ObjectValidator<JToken>(fieldsValidators, isPartial, type, DefaultValue, Formatter.Combine);
return new ObjectValidator<JToken>(fieldsValidators, isPartial, type, DefaultValue);
}
}
}

28
src/Squidex.Domain.Apps.Core.Operations/ValidateContent/JsonValueConverter.cs

@ -16,11 +16,11 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
{
public sealed class JsonValueConverter : IFieldVisitor<object>
{
public JToken Value { get; }
private readonly JToken value;
private JsonValueConverter(JToken value)
{
Value = value;
this.value = value;
}
public static object ConvertValue(IField field, JToken json)
@ -30,24 +30,24 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
public object Visit(IArrayField field)
{
return Value.ToObject<List<JObject>>();
return value.ToObject<List<JObject>>();
}
public object Visit(IField<AssetsFieldProperties> field)
{
return Value.ToObject<List<Guid>>();
return value.ToObject<List<Guid>>();
}
public object Visit(IField<BooleanFieldProperties> field)
{
return (bool?)Value;
return (bool?)value;
}
public object Visit(IField<DateTimeFieldProperties> field)
{
if (Value.Type == JTokenType.String)
if (value.Type == JTokenType.String)
{
var parseResult = InstantPattern.General.Parse(Value.ToString());
var parseResult = InstantPattern.General.Parse(value.ToString());
if (!parseResult.Success)
{
@ -62,7 +62,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
public object Visit(IField<GeolocationFieldProperties> field)
{
var geolocation = (JObject)Value;
var geolocation = (JObject)value;
foreach (var property in geolocation.Properties())
{
@ -86,32 +86,32 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
throw new InvalidCastException("Longitude must be between -180 and 180.");
}
return Value;
return value;
}
public object Visit(IField<JsonFieldProperties> field)
{
return Value;
return value;
}
public object Visit(IField<NumberFieldProperties> field)
{
return (double?)Value;
return (double?)value;
}
public object Visit(IField<ReferencesFieldProperties> field)
{
return Value.ToObject<List<Guid>>();
return value.ToObject<List<Guid>>();
}
public object Visit(IField<StringFieldProperties> field)
{
return Value.ToString();
return value.ToString();
}
public object Visit(IField<TagsFieldProperties> field)
{
return Value.ToObject<List<string>>();
return value.ToObject<List<string>>();
}
}
}

52
src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ObjectPath.cs

@ -0,0 +1,52 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Text;
namespace Squidex.Domain.Apps.Core.ValidateContent
{
public static class ObjectPath
{
public static string ToPathString(this IEnumerable<string> path)
{
var sb = new StringBuilder();
var index = 0;
foreach (var property in path)
{
if (index == 0)
{
sb.Append(property);
}
else if (index == 1)
{
if (!property.Equals(InvariantPartitioning.Instance.Master.Key, StringComparison.OrdinalIgnoreCase))
{
sb.Append("(");
sb.Append(property);
sb.Append(")");
}
}
else
{
if (property[0] != '[')
{
sb.Append(".");
}
sb.Append(property);
}
index++;
}
return sb.ToString();
}
}
}

19
src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ValidationContext.cs

@ -7,6 +7,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Threading.Tasks;
using Squidex.Infrastructure;
@ -16,24 +17,33 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
{
private readonly Func<IEnumerable<Guid>, Guid, Task<IReadOnlyList<Guid>>> checkContent;
private readonly Func<IEnumerable<Guid>, Task<IReadOnlyList<IAssetInfo>>> checkAsset;
private readonly ImmutableQueue<string> propertyPath;
public ImmutableQueue<string> Path
{
get { return propertyPath; }
}
public bool IsOptional { get; }
public ValidationContext(
Func<IEnumerable<Guid>, Guid, Task<IReadOnlyList<Guid>>> checkContent,
Func<IEnumerable<Guid>, Task<IReadOnlyList<IAssetInfo>>> checkAsset)
: this(checkContent, checkAsset, false)
: this(checkContent, checkAsset, ImmutableQueue<string>.Empty, false)
{
}
private ValidationContext(
Func<IEnumerable<Guid>, Guid, Task<IReadOnlyList<Guid>>> checkContent,
Func<IEnumerable<Guid>, Task<IReadOnlyList<IAssetInfo>>> checkAsset,
ImmutableQueue<string> propertyPath,
bool isOptional)
{
Guard.NotNull(checkAsset, nameof(checkAsset));
Guard.NotNull(checkContent, nameof(checkAsset));
this.propertyPath = propertyPath;
this.checkContent = checkContent;
this.checkAsset = checkAsset;
@ -42,7 +52,12 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
public ValidationContext Optional(bool isOptional)
{
return isOptional == IsOptional ? this : new ValidationContext(checkContent, checkAsset, isOptional);
return isOptional == IsOptional ? this : new ValidationContext(checkContent, checkAsset, propertyPath, isOptional);
}
public ValidationContext Nested(string property)
{
return new ValidationContext(checkContent, checkAsset, propertyPath.Enqueue(property), IsOptional);
}
public Task<IReadOnlyList<Guid>> GetInvalidContentIdsAsync(IEnumerable<Guid> contentIds, Guid schemaId)

2
src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AllowedValuesValidator.cs

@ -34,7 +34,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
if (!allowedValues.Contains(typedValue))
{
addError(null, "Not an allowed value.");
addError(context.Path, "Not an allowed value.");
}
return TaskHelper.Done;

31
src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AssetsValidator.cs

@ -28,47 +28,44 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
if (value is ICollection<Guid> assetIds && assetIds.Count > 0)
{
var assets = await context.GetAssetInfosAsync(assetIds);
var i = 0;
var index = 0;
foreach (var assetId in assetIds)
{
i++;
index++;
var asset = assets.FirstOrDefault(x => x.AssetId == assetId);
var path = context.Path.Enqueue($"[{index}]");
void Error(string message)
{
addError($"[{i}]", message);
}
var asset = assets.FirstOrDefault(x => x.AssetId == assetId);
if (asset == null)
{
Error($"Id '{assetId}' not found.");
addError(path, $"Id '{assetId}' not found.");
continue;
}
if (properties.MinSize.HasValue && asset.FileSize < properties.MinSize)
{
Error($"'{asset.FileSize.ToReadableSize()}' less than minimum of '{properties.MinSize.Value.ToReadableSize()}'.");
addError(path, $"'{asset.FileSize.ToReadableSize()}' less than minimum of '{properties.MinSize.Value.ToReadableSize()}'.");
}
if (properties.MaxSize.HasValue && asset.FileSize > properties.MaxSize)
{
Error($"'{asset.FileSize.ToReadableSize()}' greater than maximum of '{properties.MaxSize.Value.ToReadableSize()}'.");
addError(path, $"'{asset.FileSize.ToReadableSize()}' greater than maximum of '{properties.MaxSize.Value.ToReadableSize()}'.");
}
if (properties.AllowedExtensions != null &&
properties.AllowedExtensions.Count > 0 &&
!properties.AllowedExtensions.Any(x => asset.FileName.EndsWith("." + x, StringComparison.OrdinalIgnoreCase)))
{
Error("Invalid file extension.");
addError(path, "Invalid file extension.");
}
if (!asset.IsImage)
{
if (properties.MustBeImage)
{
Error("Not an image.");
addError(path, "Not an image.");
}
continue;
@ -84,22 +81,22 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
if (properties.MinWidth.HasValue && w < properties.MinWidth)
{
Error($"Width '{w}px' less than minimum of '{properties.MinWidth}px'.");
addError(path, $"Width '{w}px' less than minimum of '{properties.MinWidth}px'.");
}
if (properties.MaxWidth.HasValue && w > properties.MaxWidth)
{
Error($"Width '{w}px' greater than maximum of '{properties.MaxWidth}px'.");
addError(path, $"Width '{w}px' greater than maximum of '{properties.MaxWidth}px'.");
}
if (properties.MinHeight.HasValue && h < properties.MinHeight)
{
Error($"Height '{h}px' less than minimum of '{properties.MinHeight}px'.");
addError(path, $"Height '{h}px' less than minimum of '{properties.MinHeight}px'.");
}
if (properties.MaxHeight.HasValue && h > properties.MaxHeight)
{
Error($"Height '{h}px' greater than maximum of '{properties.MaxHeight}px'.");
addError(path, $"Height '{h}px' greater than maximum of '{properties.MaxHeight}px'.");
}
if (properties.AspectHeight.HasValue && properties.AspectWidth.HasValue)
@ -108,7 +105,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
if (Math.Abs(expectedRatio - actualRatio) > double.Epsilon)
{
Error($"Aspect ratio not '{properties.AspectWidth}:{properties.AspectHeight}'.");
addError(path, $"Aspect ratio not '{properties.AspectWidth}:{properties.AspectHeight}'.");
}
}
}

6
src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/CollectionItemValidator.cs

@ -29,17 +29,15 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
if (value is ICollection items && items.Count > 0)
{
var innerTasks = new List<Task>();
var innerContext = context.Optional(false);
var index = 1;
foreach (var item in items)
{
var itemFormatter = Formatter.Combine($"[{index}]", addError);
var innerContext = context.Nested($"[{index}]");
foreach (var itemValidator in itemValidators)
{
innerTasks.Add(itemValidator.ValidateAsync(item, innerContext, itemFormatter));
innerTasks.Add(itemValidator.ValidateAsync(item, innerContext, addError));
}
index++;

6
src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/CollectionValidator.cs

@ -30,7 +30,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
{
if (isRequired && !context.IsOptional)
{
addError(null, "Field is required.");
addError(context.Path, "Field is required.");
}
return TaskHelper.Done;
@ -38,12 +38,12 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
if (minItems.HasValue && items.Count < minItems.Value)
{
addError(null, $"Must have at least {minItems} item(s).");
addError(context.Path, $"Must have at least {minItems} item(s).");
}
if (maxItems.HasValue && items.Count > maxItems.Value)
{
addError(null, $"Must have not more than {maxItems} item(s).");
addError(context.Path, $"Must have not more than {maxItems} item(s).");
}
return TaskHelper.Done;

12
src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/FieldValidator.cs

@ -15,10 +15,10 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
{
public sealed class FieldValidator : IValidator
{
private readonly IEnumerable<IValidator> validators;
private readonly IValidator[] validators;
private readonly IField field;
public FieldValidator(IEnumerable<IValidator> validators, IField field)
public FieldValidator(IValidator[] validators, IField field)
{
this.validators = validators;
this.field = field;
@ -35,14 +35,18 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
typedValue = jToken.IsNull() ? null : JsonValueConverter.ConvertValue(field, jToken);
}
var tasks = new List<Task>();
foreach (var validator in ValidatorsFactory.CreateValidators(field))
{
await validator.ValidateAsync(typedValue, context, addError);
tasks.Add(validator.ValidateAsync(typedValue, context, addError));
}
await Task.WhenAll(tasks);
}
catch
{
addError(null, "Not a valid value.");
addError(context.Path, "Not a valid value.");
}
}
}

46
src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/Formatter.cs

@ -1,46 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
{
public static class Formatter
{
private static readonly string IV = InvariantPartitioning.Instance.Master.Key;
public static AddError Combine(string field, AddError formatter)
{
return (f, m) =>
{
if (!string.IsNullOrWhiteSpace(f))
{
formatter($"{field}.{f}", m);
}
else
{
formatter(field, m);
}
};
}
public static AddError CombineForLanguage(string field, AddError formatter)
{
return (f, m) =>
{
if (!string.IsNullOrWhiteSpace(f) && !string.Equals(f, IV, StringComparison.OrdinalIgnoreCase))
{
formatter($"{field}.{f}", m);
}
else
{
formatter(field, m);
}
};
}
}
}

5
src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/IValidator.cs

@ -5,13 +5,12 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
{
public delegate void AddError(string field, string message);
public delegate AddError CombineFields(string field, AddError formatter);
public delegate void AddError(IEnumerable<string> path, string message);
public interface IValidator
{

16
src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ObjectValidator.cs

@ -16,12 +16,10 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
private readonly bool isPartial;
private readonly string fieldType;
private readonly TValue fieldDefault;
private readonly CombineFields combiner;
public ObjectValidator(IDictionary<string, (bool IsOptional, IValidator Validator)> schema, bool isPartial, string fieldType, TValue fieldDefault, CombineFields combiner)
public ObjectValidator(IDictionary<string, (bool IsOptional, IValidator Validator)> schema, bool isPartial, string fieldType, TValue fieldDefault)
{
this.schema = schema;
this.combiner = combiner;
this.fieldDefault = fieldDefault;
this.fieldType = fieldType;
this.isPartial = isPartial;
@ -29,7 +27,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
public async Task ValidateAsync(object value, ValidationContext context, AddError addError)
{
if (value is IReadOnlyDictionary<string, TValue> values)
if (value is IDictionary<string, TValue> values)
{
foreach (var fieldData in values)
{
@ -37,9 +35,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
if (!schema.ContainsKey(name))
{
var fieldFormatter = combiner?.Invoke(name, addError) ?? Formatter.Combine(name, addError);
fieldFormatter(null, $"Not a known {fieldType}.");
addError(context.Path.Enqueue(name), $"Not a known {fieldType}.");
}
}
@ -60,11 +56,9 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
}
var (isOptional, validator) = field.Value;
var fieldContext = context.Nested(name).Optional(isOptional);
var fieldContext = context.Optional(isOptional);
var fieldFormatter = combiner(name, addError);
tasks.Add(validator.ValidateAsync(fieldValue, fieldContext, fieldFormatter));
tasks.Add(validator.ValidateAsync(fieldValue, fieldContext, addError));
}
await Task.WhenAll(tasks);

6
src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/PatternValidator.cs

@ -37,17 +37,17 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
{
if (string.IsNullOrWhiteSpace(errorMessage))
{
addError(null, "Not valid.");
addError(context.Path, "Not valid.");
}
else
{
addError(null, errorMessage);
addError(context.Path, errorMessage);
}
}
}
catch
{
addError(null, "Regex is too slow.");
addError(context.Path, "Regex is too slow.");
}
}
}

4
src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/RangeValidator.cs

@ -38,12 +38,12 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
if (min.HasValue && typedValue.CompareTo(min.Value) < 0)
{
addError(null, $"Must be greater or equals than '{min}'.");
addError(context.Path, $"Must be greater or equals than '{min}'.");
}
if (max.HasValue && typedValue.CompareTo(max.Value) > 0)
{
addError(null, $"Must be less or equals than '{max}'.");
addError(context.Path, $"Must be less or equals than '{max}'.");
}
return TaskHelper.Done;

2
src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ReferencesValidator.cs

@ -28,7 +28,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
foreach (var invalidId in invalidIds)
{
addError(null, $"Contains invalid reference '{invalidId}'.");
addError(context.Path, $"Contains invalid reference '{invalidId}'.");
}
}
}

2
src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/RequiredStringValidator.cs

@ -30,7 +30,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
if (valueAsString == null || (validateEmptyStrings && string.IsNullOrWhiteSpace(valueAsString)))
{
addError(null, "Field is required.");
addError(context.Path, "Field is required.");
}
return TaskHelper.Done;

2
src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/RequiredValidator.cs

@ -16,7 +16,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
{
if (value == null && !context.IsOptional)
{
addError(null, "Field is required.");
addError(context.Path, "Field is required.");
}
return TaskHelper.Done;

4
src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/StringLengthValidator.cs

@ -33,12 +33,12 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
{
if (minLength.HasValue && stringValue.Length < minLength.Value)
{
addError(null, $"Must have more than '{minLength}' characters.");
addError(context.Path, $"Must have more than '{minLength}' characters.");
}
if (maxLength.HasValue && stringValue.Length > maxLength.Value)
{
addError(null, $"Must have less than '{maxLength}' characters.");
addError(context.Path, $"Must have less than '{maxLength}' characters.");
}
}

6
src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ValidatorsFactory.cs

@ -38,14 +38,14 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
yield return new CollectionValidator(field.Properties.IsRequired, field.Properties.MinItems, field.Properties.MaxItems);
}
var fieldsValidators = new Dictionary<string, (bool IsOptional, IValidator Validator)>();
var nestedSchema = new Dictionary<string, (bool IsOptional, IValidator Validator)>();
foreach (var nestedField in field.Fields)
{
fieldsValidators[nestedField.Name] = (false, new FieldValidator(nestedField.Accept(this), nestedField));
nestedSchema[nestedField.Name] = (false, new FieldValidator(nestedField.Accept(this).ToArray(), nestedField));
}
yield return new CollectionItemValidator(new ObjectValidator<JToken>(fieldsValidators, false, "field", JValue.CreateNull(), Formatter.Combine));
yield return new CollectionItemValidator(new ObjectValidator<JToken>(nestedSchema, false, "field", JValue.CreateNull()));
}
public IEnumerable<IValidator> Visit(IField<AssetsFieldProperties> field)

2
tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/ArrayFieldTests.cs

@ -63,7 +63,7 @@ namespace Squidex.Domain.Apps.Core.Model.Schemas
[Fact]
public void Should_return_same_parent_if_field_to_hide_does_not_exist()
{
var parent_1 = parent_0.UpdateField(1, f => f.Hide()); ;
var parent_1 = parent_0.UpdateField(1, f => f.Hide());
Assert.Same(parent_0, parent_1);
}

2
tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/SchemaTests.cs

@ -88,7 +88,7 @@ namespace Squidex.Domain.Apps.Core.Model.Schemas
[Fact]
public void Should_return_same_schema_if_field_to_hide_does_not_exist()
{
var schema_1 = schema_0.UpdateField(1, f => f.Hide()); ;
var schema_1 = schema_0.UpdateField(1, f => f.Hide());
Assert.Same(schema_0, schema_1);
}

116
tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ArrayFieldTests.cs

@ -0,0 +1,116 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using FluentAssertions;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Schemas;
using Xunit;
namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
{
public class ArrayFieldTests
{
private readonly List<string> errors = new List<string>();
[Fact]
public void Should_instantiate_field()
{
var sut = Field(new ArrayFieldProperties());
Assert.Equal("my-array", sut.Name);
}
[Fact]
public async Task Should_not_add_error_if_tags_are_valid()
{
var sut = Field(new ArrayFieldProperties());
await sut.ValidateAsync(CreateValue(new JObject()), errors, ValidationTestExtensions.ValidContext);
Assert.Empty(errors);
}
[Fact]
public async Task Should_not_add_error_if_tags_are_null_and_valid()
{
var sut = Field(new ArrayFieldProperties());
await sut.ValidateAsync(CreateValue(null), errors);
Assert.Empty(errors);
}
[Fact]
public async Task Should_add_errors_if_tags_are_required_and_null()
{
var sut = Field(new ArrayFieldProperties { IsRequired = true });
await sut.ValidateAsync(CreateValue(null), errors);
errors.ShouldBeEquivalentTo(
new[] { "Field is required." });
}
[Fact]
public async Task Should_add_errors_if_tags_are_required_and_empty()
{
var sut = Field(new ArrayFieldProperties { IsRequired = true });
await sut.ValidateAsync(CreateValue(), errors);
errors.ShouldBeEquivalentTo(
new[] { "Field is required." });
}
[Fact]
public async Task Should_add_errors_if_value_is_not_valid()
{
var sut = Field(new ArrayFieldProperties());
await sut.ValidateAsync("invalid", errors);
errors.ShouldBeEquivalentTo(
new[] { "Not a valid value." });
}
[Fact]
public async Task Should_add_errors_if_value_has_not_enough_items()
{
var sut = Field(new ArrayFieldProperties { MinItems = 3 });
await sut.ValidateAsync(CreateValue(new JObject(), new JObject()), errors);
errors.ShouldBeEquivalentTo(
new[] { "Must have at least 3 item(s)." });
}
[Fact]
public async Task Should_add_errors_if_value_has_too_much_items()
{
var sut = Field(new ArrayFieldProperties { MaxItems = 1 });
await sut.ValidateAsync(CreateValue(new JObject(), new JObject()), errors);
errors.ShouldBeEquivalentTo(
new[] { "Must have not more than 1 item(s)." });
}
private static JToken CreateValue(params JObject[] ids)
{
return ids == null ? JValue.CreateNull() : (JToken)new JArray(ids.OfType<object>().ToArray());
}
private static RootField<ArrayFieldProperties> Field(ArrayFieldProperties properties)
{
return Fields.Array(1, "my-array", Partitioning.Invariant, properties);
}
}
}

51
tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ContentValidationTests.cs

@ -8,6 +8,7 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using FluentAssertions;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Schemas;
@ -79,8 +80,8 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
errors.ShouldBeEquivalentTo(
new List<ValidationError>
{
new ValidationError("my-field.es: Not a known invariant value.", "my-field.es"),
new ValidationError("my-field.it: Not a known invariant value.", "my-field.it")
new ValidationError("my-field(es): Not a known invariant value.", "my-field(es)"),
new ValidationError("my-field(it): Not a known invariant value.", "my-field(it)")
});
}
@ -98,8 +99,8 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
errors.ShouldBeEquivalentTo(
new List<ValidationError>
{
new ValidationError("my-field.de: Field is required.", "my-field.de"),
new ValidationError("my-field.en: Field is required.", "my-field.en")
new ValidationError("my-field(de): Field is required.", "my-field(de)"),
new ValidationError("my-field(en): Field is required.", "my-field(en)")
});
}
@ -138,7 +139,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
errors.ShouldBeEquivalentTo(
new List<ValidationError>
{
new ValidationError("my-field.xx: Not a known language.", "my-field.xx")
new ValidationError("my-field(xx): Not a known language.", "my-field(xx)")
});
}
@ -181,8 +182,8 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
errors.ShouldBeEquivalentTo(
new List<ValidationError>
{
new ValidationError("my-field.es: Not a known language.", "my-field.es"),
new ValidationError("my-field.it: Not a known language.", "my-field.it")
new ValidationError("my-field(es): Not a known language.", "my-field(es)"),
new ValidationError("my-field(it): Not a known language.", "my-field(it)")
});
}
@ -241,8 +242,8 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
errors.ShouldBeEquivalentTo(
new List<ValidationError>
{
new ValidationError("my-field.es: Not a known invariant value.", "my-field.es"),
new ValidationError("my-field.it: Not a known invariant value.", "my-field.it")
new ValidationError("my-field(es): Not a known invariant value.", "my-field(es)"),
new ValidationError("my-field(it): Not a known invariant value.", "my-field(it)")
});
}
@ -291,7 +292,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
errors.ShouldBeEquivalentTo(
new List<ValidationError>
{
new ValidationError("my-field.xx: Not a known language.", "my-field.xx")
new ValidationError("my-field(xx): Not a known language.", "my-field(xx)")
});
}
@ -312,8 +313,34 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
errors.ShouldBeEquivalentTo(
new List<ValidationError>
{
new ValidationError("my-field.es: Not a known language.", "my-field.es"),
new ValidationError("my-field.it: Not a known language.", "my-field.it")
new ValidationError("my-field(es): Not a known language.", "my-field(es)"),
new ValidationError("my-field(it): Not a known language.", "my-field(it)")
});
}
[Fact]
public async Task Should_add_error_if_array_field_has_required_nested_field()
{
schema =
schema.AddArray(1, "my-field", Partitioning.Invariant, f => f.
AddNumber(1, "my-nested", new NumberFieldProperties { IsRequired = true }));
var data =
new NamedContentData()
.AddField("my-field",
new ContentFieldData()
.AddValue("iv", new JArray(
new JObject(),
new JObject(new JProperty("my-nested", 1)),
new JObject())));
await data.ValidatePartialAsync(context, schema, languagesConfig.ToResolver(), errors);
errors.ShouldBeEquivalentTo(
new List<ValidationError>
{
new ValidationError("my-field[1].my-nested: Field is required.", "my-field[1].my-nested"),
new ValidationError("my-field[3].my-nested: Field is required.", "my-field[3].my-nested"),
});
}
}

4
tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ReferencesFieldTests.cs

@ -32,11 +32,9 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
[Fact]
public async Task Should_not_add_error_if_references_are_valid()
{
var referenceId = Guid.NewGuid();
var sut = Field(new ReferencesFieldProperties());
await sut.ValidateAsync(CreateValue(referenceId), errors, ValidationTestExtensions.ValidContext);
await sut.ValidateAsync(CreateValue(Guid.NewGuid()), errors, ValidationTestExtensions.ValidContext);
Assert.Empty(errors);
}

10
tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/TagsFieldTests.cs

@ -31,11 +31,9 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
[Fact]
public async Task Should_not_add_error_if_tags_are_valid()
{
var referenceId = Guid.NewGuid();
var sut = Field(new TagsFieldProperties());
await sut.ValidateAsync(CreateValue(referenceId), errors, ValidationTestExtensions.ValidContext);
await sut.ValidateAsync(CreateValue("tag"), errors, ValidationTestExtensions.ValidContext);
Assert.Empty(errors);
}
@ -88,7 +86,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
{
var sut = Field(new TagsFieldProperties { MinItems = 3 });
await sut.ValidateAsync(CreateValue(Guid.NewGuid(), Guid.NewGuid()), errors);
await sut.ValidateAsync(CreateValue("tag-1", "tag-2"), errors);
errors.ShouldBeEquivalentTo(
new[] { "Must have at least 3 item(s)." });
@ -99,13 +97,13 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
{
var sut = Field(new TagsFieldProperties { MaxItems = 1 });
await sut.ValidateAsync(CreateValue(Guid.NewGuid(), Guid.NewGuid()), errors);
await sut.ValidateAsync(CreateValue("tag-1", "tag-2"), errors);
errors.ShouldBeEquivalentTo(
new[] { "Must have not more than 1 item(s)." });
}
private static JToken CreateValue(params Guid[] ids)
private static JToken CreateValue(params string[] ids)
{
return ids == null ? JValue.CreateNull() : (JToken)new JArray(ids.OfType<object>().ToArray());
}

8
tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ValidationTestExtensions.cs

@ -39,14 +39,14 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
public static Task ValidateAsync(this IField field, JToken value, IList<string> errors, ValidationContext context = null)
{
return new FieldValidator(ValidatorsFactory.CreateValidators(field), field).ValidateAsync(value,
return new FieldValidator(ValidatorsFactory.CreateValidators(field).ToArray(), field).ValidateAsync(value,
CreateContext(context),
CreateFormatter(errors));
}
public static Task ValidateOptionalAsync(this IField field, JToken value, IList<string> errors, ValidationContext context = null)
{
return new FieldValidator(ValidatorsFactory.CreateValidators(field), field).ValidateAsync(value,
return new FieldValidator(ValidatorsFactory.CreateValidators(field).ToArray(), field).ValidateAsync(value,
CreateContext(context).Optional(true),
CreateFormatter(errors));
}
@ -55,13 +55,13 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
{
return (field, message) =>
{
if (field == null)
if (field == null || !field.Any())
{
errors.Add(message);
}
else
{
errors.Add($"{field}: {message}");
errors.Add($"{field.ToPathString()}: {message}");
}
};
}

Loading…
Cancel
Save