Browse Source

Refactoring for Enrichment and Validation

pull/1/head
Sebastian 9 years ago
parent
commit
a20edd9675
  1. 79
      src/Squidex.Core/ContentEnricher.cs
  2. 52
      src/Squidex.Core/ContentExtensions.cs
  3. 190
      src/Squidex.Core/ContentValidator.cs
  4. 39
      src/Squidex.Core/FieldExtensions.cs
  5. 2
      src/Squidex.Core/Schemas/BooleanField.cs
  6. 8
      src/Squidex.Core/Schemas/DateTimeField.cs
  7. 63
      src/Squidex.Core/Schemas/Field.cs
  8. 2
      src/Squidex.Core/Schemas/GeolocationField.cs
  9. 2
      src/Squidex.Core/Schemas/JsonField.cs
  10. 2
      src/Squidex.Core/Schemas/NumberField.cs
  11. 163
      src/Squidex.Core/Schemas/Schema.cs
  12. 2
      src/Squidex.Core/Schemas/StringField.cs
  13. 6
      src/Squidex.Core/Schemas/Validators/AllowedValuesValidator.cs
  14. 4
      src/Squidex.Core/Schemas/Validators/IValidator.cs
  15. 8
      src/Squidex.Core/Schemas/Validators/PatternValidator.cs
  16. 7
      src/Squidex.Core/Schemas/Validators/RangeValidator.cs
  17. 6
      src/Squidex.Core/Schemas/Validators/RequiredStringValidator.cs
  18. 6
      src/Squidex.Core/Schemas/Validators/RequiredValidator.cs
  19. 7
      src/Squidex.Core/Schemas/Validators/StringLengthValidator.cs
  20. 5
      src/Squidex.Write/Contents/ContentCommandHandler.cs
  21. 2
      src/Squidex/Controllers/ContentApi/Generator/SchemasSwaggerGenerator.cs
  22. 64
      tests/Squidex.Core.Tests/ContentEnrichmentTests.cs
  23. 102
      tests/Squidex.Core.Tests/ContentValidationTests.cs
  24. 12
      tests/Squidex.Core.Tests/Schemas/BooleanFieldTests.cs
  25. 22
      tests/Squidex.Core.Tests/Schemas/DateTimeFieldTests.cs
  26. 24
      tests/Squidex.Core.Tests/Schemas/GeolocationFieldTests.cs
  27. 6
      tests/Squidex.Core.Tests/Schemas/JsonFieldTests.cs
  28. 22
      tests/Squidex.Core.Tests/Schemas/NumberFieldTests.cs
  29. 2
      tests/Squidex.Core.Tests/Schemas/SchemaTests.cs
  30. 24
      tests/Squidex.Core.Tests/Schemas/StringFieldTests.cs
  31. 22
      tests/Squidex.Core.Tests/Schemas/ValidationTestExtensions.cs
  32. 6
      tests/Squidex.Core.Tests/Schemas/Validators/AllowedValuesValidatorTests.cs
  33. 8
      tests/Squidex.Core.Tests/Schemas/Validators/PatternValidatorTests.cs
  34. 8
      tests/Squidex.Core.Tests/Schemas/Validators/RangeValidatorTests.cs
  35. 8
      tests/Squidex.Core.Tests/Schemas/Validators/RequiredStringValidatorTests.cs
  36. 6
      tests/Squidex.Core.Tests/Schemas/Validators/RequiredValidatorTests.cs
  37. 8
      tests/Squidex.Core.Tests/Schemas/Validators/StringLengthValidatorTests.cs

79
src/Squidex.Core/ContentEnricher.cs

@ -0,0 +1,79 @@
// ==========================================================================
// ContentEnricher.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using Newtonsoft.Json.Linq;
using Squidex.Core.Contents;
using Squidex.Core.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json;
using System.Collections.Generic;
namespace Squidex.Core
{
public sealed class ContentEnricher
{
private readonly Schema schema;
private readonly HashSet<Language> languages;
public ContentEnricher(HashSet<Language> languages, Schema schema)
{
Guard.NotNull(schema, nameof(schema));
Guard.NotNull(languages, nameof(languages));
this.schema = schema;
this.languages = languages;
}
public void Enrich(ContentData data)
{
Guard.NotNull(data, nameof(data));
Guard.NotEmpty(languages, nameof(languages));
foreach (var field in schema.FieldsByName.Values)
{
var fieldData = data.GetOrCreate(field.Name, k => new ContentFieldData());
if (field.RawProperties.IsLocalizable)
{
foreach (var language in languages)
{
Enrich(field, fieldData, language);
}
}
else
{
Enrich(field, fieldData, Language.Invariant);
}
if (fieldData.Count > 0)
{
data.AddField(field.Name, fieldData);
}
}
}
private void Enrich(Field field, ContentFieldData fieldData, Language language)
{
Guard.NotNull(fieldData, nameof(fieldData));
Guard.NotNull(language, nameof(language));
var defaultValue = field.RawProperties.GetDefaultValue();
if (field.RawProperties.IsRequired || defaultValue.IsNull())
{
return;
}
if (!fieldData.TryGetValue(language.Iso2Code, out JToken value) || value == null || value.Type == JTokenType.Null)
{
fieldData.AddValue(language.Iso2Code, defaultValue);
}
}
}
}

52
src/Squidex.Core/ContentExtensions.cs

@ -0,0 +1,52 @@
// ==========================================================================
// ContentExtensions.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System.Collections.Generic;
using System.Threading.Tasks;
using Squidex.Core.Contents;
using Squidex.Core.Schemas;
using Squidex.Infrastructure;
namespace Squidex.Core
{
public static class ContentExtensions
{
public static ContentData Enrich(this ContentData data, Schema schema, HashSet<Language> languages)
{
var validator = new ContentEnricher(languages, schema);
validator.Enrich(data);
return data;
}
public static async Task ValidateAsync(this ContentData data, Schema schema, HashSet<Language> languages, IList<ValidationError> errors)
{
var validator = new ContentValidator(schema, languages);
await validator.ValidateAsync(data);
foreach (var error in validator.Errors)
{
errors.Add(error);
}
}
public static async Task ValidatePartialAsync(this ContentData data, Schema schema, HashSet<Language> languages, IList<ValidationError> errors)
{
var validator = new ContentValidator(schema, languages);
await validator.ValidatePartialAsync(data);
foreach (var error in validator.Errors)
{
errors.Add(error);
}
}
}
}

190
src/Squidex.Core/ContentValidator.cs

@ -0,0 +1,190 @@
// ==========================================================================
// ContentValidator.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using Squidex.Core.Contents;
using Squidex.Core.Schemas;
using Squidex.Infrastructure;
namespace Squidex.Core
{
public sealed class ContentValidator
{
private readonly Schema schema;
private readonly HashSet<Language> languages;
private readonly List<ValidationError> errors = new List<ValidationError>();
public ContentValidator(Schema schema, HashSet<Language> languages)
{
Guard.NotNull(schema, nameof(schema));
Guard.NotNull(languages, nameof(languages));
this.schema = schema;
this.languages = languages;
}
public IReadOnlyList<ValidationError> Errors
{
get { return errors; }
}
public async Task ValidatePartialAsync(ContentData data)
{
Guard.NotNull(data, nameof(data));
foreach (var fieldData in data)
{
var fieldName = fieldData.Key;
if (!schema.FieldsByName.TryGetValue(fieldData.Key, out Field field))
{
AddError("<FIELD> is not a known field", fieldName);
}
else
{
if (field.RawProperties.IsLocalizable)
{
await ValidateLocalizableFieldPartialAsync(field, fieldData.Value);
}
else
{
await ValidateNonLocalizableFieldPartialAsync(field, fieldData.Value);
}
}
}
}
private async Task ValidateLocalizableFieldPartialAsync(Field field, ContentFieldData fieldData)
{
foreach (var languageValue in fieldData)
{
if (!Language.TryGetLanguage(languageValue.Key, out Language language))
{
AddError($"<FIELD> has an invalid language '{languageValue.Key}'", field);
}
else if (!languages.Contains(language))
{
AddError($"<FIELD> has an unsupported language '{languageValue.Key}'", field);
}
else
{
await ValidateAsync(field, languageValue.Value, language);
}
}
}
private async Task ValidateNonLocalizableFieldPartialAsync(Field field, ContentFieldData fieldData)
{
if (fieldData.Keys.Any(x => x != Language.Invariant.Iso2Code))
{
AddError($"<FIELD> can only contain a single entry for invariant language ({Language.Invariant.Iso2Code})", field);
}
if (fieldData.TryGetValue(Language.Invariant.Iso2Code, out JToken value))
{
await ValidateAsync(field, value);
}
}
public async Task ValidateAsync(ContentData data)
{
Guard.NotNull(data, nameof(data));
ValidateUnknownFields(data);
foreach (var field in schema.FieldsByName.Values)
{
var fieldData = data.GetOrCreate(field.Name, k => new ContentFieldData());
if (field.RawProperties.IsLocalizable)
{
await ValidateLocalizableFieldAsync(field, fieldData);
}
else
{
await ValidateNonLocalizableField(field, fieldData);
}
}
}
private void ValidateUnknownFields(ContentData data)
{
foreach (var fieldData in data)
{
if (!schema.FieldsByName.ContainsKey(fieldData.Key))
{
AddError("<FIELD> is not a known field", fieldData.Key);
}
}
}
private async Task ValidateLocalizableFieldAsync(Field field, ContentFieldData fieldData)
{
foreach (var valueLanguage in fieldData.Keys)
{
if (!Language.TryGetLanguage(valueLanguage, out Language language))
{
AddError($"<FIELD> has an invalid language '{valueLanguage}'", field);
}
else if (!languages.Contains(language))
{
AddError($"<FIELD> has an unsupported language '{valueLanguage}'", field);
}
}
foreach (var language in languages)
{
var value = fieldData.GetOrCreate(language.Iso2Code, k => JValue.CreateNull());
await ValidateAsync(field, value, language);
}
}
private async Task ValidateNonLocalizableField(Field field, ContentFieldData fieldData)
{
if (fieldData.Keys.Any(x => x != Language.Invariant.Iso2Code))
{
AddError($"<FIELD> can only contain a single entry for invariant language ({Language.Invariant.Iso2Code})", field);
}
var value = fieldData.GetOrCreate(Language.Invariant.Iso2Code, k => JValue.CreateNull());
await ValidateAsync(field, value);
}
private Task ValidateAsync(Field field, JToken value, Language language = null)
{
return field.ValidateAsync(value, m => AddError(m, field, language));
}
private void AddError(string message, Field field, Language language = null)
{
var displayName = !string.IsNullOrWhiteSpace(field.RawProperties.Label) ? field.RawProperties.Label : field.Name;
if (language != null)
{
displayName += $" ({language.Iso2Code})";
}
message = message.Replace("<FIELD>", displayName);
errors.Add(new ValidationError(message, field.Name));
}
private void AddError(string message, string fieldName)
{
message = message.Replace("<FIELD>", fieldName);
errors.Add(new ValidationError(message, fieldName));
}
}
}

39
src/Squidex.Core/FieldExtensions.cs

@ -0,0 +1,39 @@
// ==========================================================================
// FieldExtensions.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using Squidex.Core.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json;
namespace Squidex.Core
{
public static class FieldExtensions
{
public static async Task ValidateAsync(this Field field, JToken value, Action<string> addError)
{
Guard.NotNull(value, nameof(value));
try
{
var typedValue = value.IsNull() ? null : field.ConvertValue(value);
foreach (var validator in field.Validators)
{
await validator.ValidateAsync(typedValue, addError);
}
}
catch
{
addError("<FIELD> is not a valid value");
}
}
}
}

2
src/Squidex.Core/Schemas/BooleanField.cs

@ -31,7 +31,7 @@ namespace Squidex.Core.Schemas
}
}
protected override object ConvertValue(JToken value)
public override object ConvertValue(JToken value)
{
return (bool?)value;
}

8
src/Squidex.Core/Schemas/DateTimeField.cs

@ -16,7 +16,6 @@ using NodaTime;
using NodaTime.Text;
using Squidex.Core.Schemas.Validators;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json;
// ReSharper disable InvertIf
// ReSharper disable ConvertIfStatementToConditionalTernaryExpression
@ -45,13 +44,8 @@ namespace Squidex.Core.Schemas
}
}
protected override object ConvertValue(JToken value)
public override object ConvertValue(JToken value)
{
if (value.IsNull())
{
return null;
}
if (value.Type == JTokenType.String)
{
var parseResult = InstantPattern.General.Parse(value.ToString());

63
src/Squidex.Core/Schemas/Field.cs

@ -8,15 +8,12 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.OData.Edm;
using Microsoft.OData.Edm.Library;
using Newtonsoft.Json.Linq;
using NJsonSchema;
using Squidex.Core.Contents;
using Squidex.Core.Schemas.Validators;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json;
// ReSharper disable InvertIf
// ReSharper disable ConvertIfStatementToReturnStatement
@ -52,6 +49,11 @@ namespace Squidex.Core.Schemas
get { return isDisabled; }
}
public IReadOnlyList<IValidator> Validators
{
get { return validators.Value; }
}
public abstract FieldProperties RawProperties { get; }
protected Field(long id, string name)
@ -68,56 +70,7 @@ namespace Squidex.Core.Schemas
public abstract Field Update(FieldProperties newProperties);
public void Enrich(ContentFieldData fieldData, Language language)
{
Guard.NotNull(fieldData, nameof(fieldData));
Guard.NotNull(language, nameof(language));
var defaultValue = RawProperties.GetDefaultValue();
if (!RawProperties.IsRequired && !defaultValue.IsNull())
{
if (!fieldData.TryGetValue(language.Iso2Code, out JToken value) || value == null || value.Type == JTokenType.Null)
{
fieldData.AddValue(language.Iso2Code, defaultValue);
}
}
}
public async Task ValidateAsync(JToken value, ICollection<string> errors, Language language = null)
{
Guard.NotNull(value, nameof(value));
var rawErrors = new List<string>();
try
{
var typedValue = value.IsNull() ? null : ConvertValue(value);
foreach (var validator in validators.Value)
{
await validator.ValidateAsync(typedValue, rawErrors);
}
}
catch
{
rawErrors.Add("<FIELD> is not a valid value");
}
if (rawErrors.Count > 0)
{
var displayName = !string.IsNullOrWhiteSpace(RawProperties.Label) ? RawProperties.Label : name;
if (language != null)
{
displayName += $" ({language.Iso2Code})";
}
foreach (var error in rawErrors)
{
errors.Add(error.Replace("<FIELD>", displayName));
}
}
}
public abstract object ConvertValue(JToken value);
public Field Hide()
{
@ -179,7 +132,7 @@ namespace Squidex.Core.Schemas
edmType.AddStructuralProperty(Name, new EdmComplexTypeReference(languageType, false));
}
public void AddToSchema(JsonSchema4 schema, IEnumerable<Language> languages, string schemaName, Func<string, JsonSchema4, JsonSchema4> schemaResolver)
public void AddToJsonSchema(JsonSchema4 schema, IEnumerable<Language> languages, string schemaName, Func<string, JsonSchema4, JsonSchema4> schemaResolver)
{
Guard.NotNull(schema, nameof(schema));
Guard.NotNull(languages, nameof(languages));
@ -233,7 +186,5 @@ namespace Squidex.Core.Schemas
protected abstract IEdmTypeReference CreateEdmType();
protected abstract void PrepareJsonSchema(JsonProperty jsonProperty, Func<string, JsonSchema4, JsonSchema4> schemaResolver);
protected abstract object ConvertValue(JToken value);
}
}

2
src/Squidex.Core/Schemas/GeolocationField.cs

@ -31,7 +31,7 @@ namespace Squidex.Core.Schemas
}
}
protected override object ConvertValue(JToken value)
public override object ConvertValue(JToken value)
{
var geolocation = (JObject)value;

2
src/Squidex.Core/Schemas/JsonField.cs

@ -30,7 +30,7 @@ namespace Squidex.Core.Schemas
}
}
protected override object ConvertValue(JToken value)
public override object ConvertValue(JToken value)
{
return value;
}

2
src/Squidex.Core/Schemas/NumberField.cs

@ -62,7 +62,7 @@ namespace Squidex.Core.Schemas
return EdmCoreModel.Instance.GetPrimitive(EdmPrimitiveTypeKind.Double, !Properties.IsRequired);
}
protected override object ConvertValue(JToken value)
public override object ConvertValue(JToken value)
{
return (double?)value;
}

163
src/Squidex.Core/Schemas/Schema.cs

@ -10,11 +10,8 @@ using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.OData.Edm.Library;
using Newtonsoft.Json.Linq;
using NJsonSchema;
using Squidex.Core.Contents;
using Squidex.Infrastructure;
// ReSharper disable ConvertIfStatementToConditionalTernaryExpression
@ -189,7 +186,7 @@ namespace Squidex.Core.Schemas
return edmType;
}
public JsonSchema4 BuildSchema(HashSet<Language> languages, Func<string, JsonSchema4, JsonSchema4> schemaResolver)
public JsonSchema4 BuildJsonSchema(HashSet<Language> languages, Func<string, JsonSchema4, JsonSchema4> schemaResolver)
{
Guard.NotEmpty(languages, nameof(languages));
Guard.NotNull(schemaResolver, nameof(schemaResolver));
@ -200,166 +197,10 @@ namespace Squidex.Core.Schemas
foreach (var field in fieldsByName.Values.Where(x => !x.IsHidden))
{
field.AddToSchema(schema, languages, schemaName, schemaResolver);
field.AddToJsonSchema(schema, languages, schemaName, schemaResolver);
}
return schema;
}
public async Task ValidatePartialAsync(ContentData data, IList<ValidationError> errors, HashSet<Language> languages)
{
Guard.NotNull(data, nameof(data));
Guard.NotNull(errors, nameof(errors));
foreach (var fieldData in data)
{
if (!fieldsByName.TryGetValue(fieldData.Key, out Field field))
{
errors.Add(new ValidationError($"{fieldData.Key} is not a known field", fieldData.Key));
}
else
{
var fieldErrors = new List<string>();
if (field.RawProperties.IsLocalizable)
{
foreach (var languageValue in fieldData.Value)
{
if (!Language.TryGetLanguage(languageValue.Key, out Language language))
{
fieldErrors.Add($"{field.Name} has an invalid language '{languageValue.Key}'");
}
else if (!languages.Contains(language))
{
fieldErrors.Add($"{field.Name} has an unsupported language '{languageValue.Key}'");
}
else
{
await field.ValidateAsync(languageValue.Value, fieldErrors, language);
}
}
}
else
{
if (fieldData.Value.Keys.Any(x => x != Language.Invariant.Iso2Code))
{
fieldErrors.Add($"{field.Name} can only contain a single entry for invariant language ({Language.Invariant.Iso2Code})");
}
if (fieldData.Value.TryGetValue(Language.Invariant.Iso2Code, out JToken value))
{
await field.ValidateAsync(value, fieldErrors);
}
}
foreach (var error in fieldErrors)
{
errors.Add(new ValidationError(error, field.Name));
}
}
}
}
public async Task ValidateAsync(ContentData data, IList<ValidationError> errors, HashSet<Language> languages)
{
Guard.NotNull(data, nameof(data));
Guard.NotNull(errors, nameof(errors));
Guard.NotEmpty(languages, nameof(languages));
ValidateUnknownFields(data, errors);
foreach (var field in fieldsByName.Values)
{
var fieldErrors = new List<string>();
var fieldData = data.GetOrCreate(field.Name, k => new ContentFieldData());
if (field.RawProperties.IsLocalizable)
{
await ValidateLocalizableFieldAsync(languages, fieldData, fieldErrors, field);
}
else
{
await ValidateNonLocalizableField(fieldData, fieldErrors, field);
}
foreach (var error in fieldErrors)
{
errors.Add(new ValidationError(error, field.Name));
}
}
}
private void ValidateUnknownFields(ContentData data, ICollection<ValidationError> errors)
{
foreach (var fieldData in data)
{
if (!fieldsByName.ContainsKey(fieldData.Key))
{
errors.Add(new ValidationError($"{fieldData.Key} is not a known field", fieldData.Key));
}
}
}
private static async Task ValidateLocalizableFieldAsync(ICollection<Language> languages, ContentFieldData fieldData, ICollection<string> fieldErrors, Field field)
{
foreach (var valueLanguage in fieldData.Keys)
{
if (!Language.TryGetLanguage(valueLanguage, out Language language))
{
fieldErrors.Add($"{field.Name} has an invalid language '{valueLanguage}'");
}
else if (!languages.Contains(language))
{
fieldErrors.Add($"{field.Name} has an unsupported language '{valueLanguage}'");
}
}
foreach (var language in languages)
{
var value = fieldData.GetOrCreate(language.Iso2Code, k => JValue.CreateNull());
await field.ValidateAsync(value, fieldErrors, language);
}
}
private static async Task ValidateNonLocalizableField(ContentFieldData fieldData, ICollection<string> fieldErrors, Field field)
{
if (fieldData.Keys.Any(x => x != Language.Invariant.Iso2Code))
{
fieldErrors.Add($"{field.Name} can only contain a single entry for invariant language ({Language.Invariant.Iso2Code})");
}
var value = fieldData.GetOrCreate(Language.Invariant.Iso2Code, k => JValue.CreateNull());
await field.ValidateAsync(value, fieldErrors);
}
public void Enrich(ContentData data, HashSet<Language> languages)
{
Guard.NotNull(data, nameof(data));
Guard.NotEmpty(languages, nameof(languages));
foreach (var field in fieldsByName.Values)
{
var fieldData = data.GetOrCreate(field.Name, k => new ContentFieldData());
if (field.RawProperties.IsLocalizable)
{
foreach (var language in languages)
{
field.Enrich(fieldData, language);
}
}
else
{
field.Enrich(fieldData, Language.Invariant);
}
if (fieldData.Count > 0)
{
data.AddField(field.Name, fieldData);
}
}
}
}
}

2
src/Squidex.Core/Schemas/StringField.cs

@ -66,7 +66,7 @@ namespace Squidex.Core.Schemas
return EdmCoreModel.Instance.GetPrimitive(EdmPrimitiveTypeKind.String, !Properties.IsRequired);
}
protected override object ConvertValue(JToken value)
public override object ConvertValue(JToken value)
{
return value.ToString();
}

6
src/Squidex.Core/Schemas/Validators/AllowedValuesValidator.cs

@ -6,7 +6,7 @@
// All rights reserved.
// ==========================================================================
using System.Collections.Generic;
using System;
using System.Linq;
using System.Threading.Tasks;
using Squidex.Infrastructure;
@ -25,7 +25,7 @@ namespace Squidex.Core.Schemas.Validators
this.allowedValues = allowedValues;
}
public Task ValidateAsync(object value, ICollection<string> errors)
public Task ValidateAsync(object value, Action<string> addError)
{
if (value == null)
{
@ -36,7 +36,7 @@ namespace Squidex.Core.Schemas.Validators
if (!allowedValues.Contains(typedValue))
{
errors.Add("<FIELD> is not an allowed value");
addError("<FIELD> is not an allowed value");
}
return TaskHelper.Done;

4
src/Squidex.Core/Schemas/Validators/IValidator.cs

@ -6,13 +6,13 @@
// All rights reserved.
// ==========================================================================
using System.Collections.Generic;
using System;
using System.Threading.Tasks;
namespace Squidex.Core.Schemas.Validators
{
public interface IValidator
{
Task ValidateAsync(object value, ICollection<string> errors);
Task ValidateAsync(object value, Action<string> addError);
}
}

8
src/Squidex.Core/Schemas/Validators/PatternValidator.cs

@ -6,7 +6,7 @@
// All rights reserved.
// ==========================================================================
using System.Collections.Generic;
using System;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Squidex.Infrastructure.Tasks;
@ -28,7 +28,7 @@ namespace Squidex.Core.Schemas.Validators
regex = new Regex("^" + pattern + "$");
}
public Task ValidateAsync(object value, ICollection<string> errors)
public Task ValidateAsync(object value, Action<string> addError)
{
if (value is string stringValue)
{
@ -36,11 +36,11 @@ namespace Squidex.Core.Schemas.Validators
{
if (string.IsNullOrWhiteSpace(errorMessage))
{
errors.Add("<FIELD> is not valid");
addError("<FIELD> is not valid");
}
else
{
errors.Add(errorMessage);
addError(errorMessage);
}
}
}

7
src/Squidex.Core/Schemas/Validators/RangeValidator.cs

@ -7,7 +7,6 @@
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Squidex.Infrastructure.Tasks;
@ -29,7 +28,7 @@ namespace Squidex.Core.Schemas.Validators
this.max = max;
}
public Task ValidateAsync(object value, ICollection<string> errors)
public Task ValidateAsync(object value, Action<string> addError)
{
if (value == null)
{
@ -40,12 +39,12 @@ namespace Squidex.Core.Schemas.Validators
if (min.HasValue && typedValue.CompareTo(min.Value) < 0)
{
errors.Add($"<FIELD> must be greater than '{min}'");
addError($"<FIELD> must be greater than '{min}'");
}
if (max.HasValue && typedValue.CompareTo(max.Value) > 0)
{
errors.Add($"<FIELD> must be less than '{max}'");
addError($"<FIELD> must be less than '{max}'");
}
return TaskHelper.Done;

6
src/Squidex.Core/Schemas/Validators/RequiredStringValidator.cs

@ -6,7 +6,7 @@
// All rights reserved.
// ==========================================================================
using System.Collections.Generic;
using System;
using System.Threading.Tasks;
using Squidex.Infrastructure.Tasks;
@ -21,7 +21,7 @@ namespace Squidex.Core.Schemas.Validators
this.validateEmptyStrings = validateEmptyStrings;
}
public Task ValidateAsync(object value, ICollection<string> errors)
public Task ValidateAsync(object value, Action<string> addError)
{
if (value != null && !(value is string))
{
@ -32,7 +32,7 @@ namespace Squidex.Core.Schemas.Validators
if (valueAsString == null || (validateEmptyStrings && string.IsNullOrWhiteSpace(valueAsString)))
{
errors.Add("<FIELD> is required");
addError("<FIELD> is required");
}
return TaskHelper.Done;

6
src/Squidex.Core/Schemas/Validators/RequiredValidator.cs

@ -6,7 +6,7 @@
// All rights reserved.
// ==========================================================================
using System.Collections.Generic;
using System;
using System.Threading.Tasks;
using Squidex.Infrastructure.Tasks;
@ -14,11 +14,11 @@ namespace Squidex.Core.Schemas.Validators
{
public class RequiredValidator : IValidator
{
public Task ValidateAsync(object value, ICollection<string> errors)
public Task ValidateAsync(object value, Action<string> addError)
{
if (value == null)
{
errors.Add("<FIELD> is required");
addError("<FIELD> is required");
}
return TaskHelper.Done;

7
src/Squidex.Core/Schemas/Validators/StringLengthValidator.cs

@ -7,7 +7,6 @@
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Squidex.Infrastructure.Tasks;
@ -31,18 +30,18 @@ namespace Squidex.Core.Schemas.Validators
this.maxLength = maxLength;
}
public Task ValidateAsync(object value, ICollection<string> errors)
public Task ValidateAsync(object value, Action<string> addError)
{
if (value is string stringValue)
{
if (minLength.HasValue && stringValue.Length < minLength.Value)
{
errors.Add($"<FIELD> must have more than '{minLength}' characters");
addError($"<FIELD> must have more than '{minLength}' characters");
}
if (maxLength.HasValue && stringValue.Length > maxLength.Value)
{
errors.Add($"<FIELD> must have less than '{maxLength}' characters");
addError($"<FIELD> must have less than '{maxLength}' characters");
}
}

5
src/Squidex.Write/Contents/ContentCommandHandler.cs

@ -9,6 +9,7 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Squidex.Core;
using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Commands;
using Squidex.Infrastructure.Dispatching;
@ -101,7 +102,7 @@ namespace Squidex.Write.Contents
var schemaObject = taskForSchema.Result.Schema;
var schemaErrors = new List<ValidationError>();
await schemaObject.ValidateAsync(command.Data, schemaErrors, languages);
await command.Data.ValidateAsync(schemaObject, languages, schemaErrors);
if (schemaErrors.Count > 0)
{
@ -110,7 +111,7 @@ namespace Squidex.Write.Contents
if (enrich)
{
schemaObject.Enrich(command.Data, languages);
command.Data.Enrich(schemaObject, languages);
}
}
}

2
src/Squidex/Controllers/ContentApi/Generator/SchemasSwaggerGenerator.cs

@ -181,7 +181,7 @@ namespace Squidex.Controllers.ContentApi.Generator
Name = schemaName, Description = $"API to managed {schemaName} contents."
});
var dataSchema = AppendSchema($"{schemaIdentifier}Dto", schema.BuildSchema(languages, AppendSchema));
var dataSchema = AppendSchema($"{schemaIdentifier}Dto", schema.BuildJsonSchema(languages, AppendSchema));
var schemaOperations = new List<SwaggerOperations>
{

64
tests/Squidex.Core.Tests/ContentEnrichmentTests.cs

@ -0,0 +1,64 @@
// ==========================================================================
// ContentEnrichmentTests.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System.Collections.Generic;
using NodaTime;
using NodaTime.Text;
using Squidex.Core.Contents;
using Squidex.Core.Schemas;
using Squidex.Infrastructure;
using Xunit;
namespace Squidex.Core
{
public class ContentEnrichmentTests
{
private readonly HashSet<Language> languages = new HashSet<Language>(new[] { Language.DE, Language.EN });
[Fact]
private void Should_enrich_with_default_values()
{
var now = Instant.FromUnixTimeSeconds(SystemClock.Instance.GetCurrentInstant().ToUnixTimeSeconds());
var schema =
Schema.Create("my-schema", new SchemaProperties())
.AddOrUpdateField(new JsonField(1, "my-json",
new JsonFieldProperties()))
.AddOrUpdateField(new StringField(2, "my-string",
new StringFieldProperties { DefaultValue = "EN-String", IsLocalizable = true }))
.AddOrUpdateField(new NumberField(3, "my-number",
new NumberFieldProperties { DefaultValue = 123 }))
.AddOrUpdateField(new BooleanField(4, "my-boolean",
new BooleanFieldProperties { DefaultValue = true }))
.AddOrUpdateField(new DateTimeField(5, "my-datetime",
new DateTimeFieldProperties { DefaultValue = now }))
.AddOrUpdateField(new GeolocationField(6, "my-geolocation",
new GeolocationFieldProperties()));
var data =
new ContentData()
.AddField("my-string",
new ContentFieldData()
.AddValue("de", "DE-String"))
.AddField("my-number",
new ContentFieldData()
.AddValue("iv", 456));
data.Enrich(schema, languages);
Assert.Equal(456, (int)data["my-number"]["iv"]);
Assert.Equal("DE-String", (string)data["my-string"]["de"]);
Assert.Equal("EN-String", (string)data["my-string"]["en"]);
Assert.Equal(now, InstantPattern.General.Parse((string)data["my-datetime"]["iv"]).Value);
Assert.Equal(true, (bool)data["my-boolean"]["iv"]);
}
}
}

102
tests/Squidex.Core.Tests/Schemas/SchemaValidationTests.cs → tests/Squidex.Core.Tests/ContentValidationTests.cs

@ -1,5 +1,5 @@
// ==========================================================================
// SchemaValidationTests.cs
// ContentValidationTests.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
@ -9,19 +9,18 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using FluentAssertions;
using NodaTime;
using NodaTime.Text;
using Squidex.Core.Contents;
using Squidex.Core.Schemas;
using Squidex.Infrastructure;
using Xunit;
namespace Squidex.Core.Schemas
namespace Squidex.Core
{
public class SchemaValidationTests
public class ContentValidationTests
{
private readonly HashSet<Language> languages = new HashSet<Language>(new[] { Language.DE, Language.EN });
private readonly List<ValidationError> errors = new List<ValidationError>();
private Schema sut = Schema.Create("my-name", new SchemaProperties());
private Schema schema = Schema.Create("my-name", new SchemaProperties());
[Fact]
public async Task Should_add_error_if_validating_data_with_unknown_field()
@ -31,7 +30,7 @@ namespace Squidex.Core.Schemas
.AddField("unknown",
new ContentFieldData());
await sut.ValidateAsync(data, errors, languages);
await data.ValidateAsync(schema, languages, errors);
errors.ShouldBeEquivalentTo(
new List<ValidationError>
@ -43,7 +42,7 @@ namespace Squidex.Core.Schemas
[Fact]
public async Task Should_add_error_if_validating_data_with_invalid_field()
{
sut = sut.AddOrUpdateField(new NumberField(1, "my-field", new NumberFieldProperties { MaxValue = 100 }));
schema = schema.AddOrUpdateField(new NumberField(1, "my-field", new NumberFieldProperties { MaxValue = 100 }));
var data =
new ContentData()
@ -51,7 +50,7 @@ namespace Squidex.Core.Schemas
new ContentFieldData()
.SetValue(1000));
await sut.ValidateAsync(data, errors, languages);
await data.ValidateAsync(schema, languages, errors);
errors.ShouldBeEquivalentTo(
new List<ValidationError>
@ -63,7 +62,7 @@ namespace Squidex.Core.Schemas
[Fact]
public async Task Should_add_error_non_localizable_data_field_contains_language()
{
sut = sut.AddOrUpdateField(new NumberField(1, "my-field", new NumberFieldProperties()));
schema = schema.AddOrUpdateField(new NumberField(1, "my-field", new NumberFieldProperties()));
var data =
new ContentData()
@ -72,7 +71,7 @@ namespace Squidex.Core.Schemas
.AddValue("es", 1)
.AddValue("it", 1));
await sut.ValidateAsync(data, errors, languages);
await data.ValidateAsync(schema, languages, errors);
errors.ShouldBeEquivalentTo(
new List<ValidationError>
@ -84,12 +83,12 @@ namespace Squidex.Core.Schemas
[Fact]
public async Task Should_add_error_if_validating_data_with_invalid_localizable_field()
{
sut = sut.AddOrUpdateField(new NumberField(1, "my-field", new NumberFieldProperties { IsRequired = true, IsLocalizable = true }));
schema = schema.AddOrUpdateField(new NumberField(1, "my-field", new NumberFieldProperties { IsRequired = true, IsLocalizable = true }));
var data =
new ContentData();
await sut.ValidateAsync(data, errors, languages);
await data.ValidateAsync(schema, languages, errors);
errors.ShouldBeEquivalentTo(
new List<ValidationError>
@ -102,12 +101,12 @@ namespace Squidex.Core.Schemas
[Fact]
public async Task Should_add_error_if_required_data_field_is_not_in_bag()
{
sut = sut.AddOrUpdateField(new NumberField(1, "my-field", new NumberFieldProperties { IsRequired = true }));
schema = schema.AddOrUpdateField(new NumberField(1, "my-field", new NumberFieldProperties { IsRequired = true }));
var data =
new ContentData();
await sut.ValidateAsync(data, errors, languages);
await data.ValidateAsync(schema, languages, errors);
errors.ShouldBeEquivalentTo(
new List<ValidationError>
@ -119,7 +118,7 @@ namespace Squidex.Core.Schemas
[Fact]
public async Task Should_add_error_if_data_contains_invalid_language()
{
sut = sut.AddOrUpdateField(new NumberField(1, "my-field", new NumberFieldProperties { IsLocalizable = true }));
schema = schema.AddOrUpdateField(new NumberField(1, "my-field", new NumberFieldProperties { IsLocalizable = true }));
var data =
new ContentData()
@ -128,7 +127,7 @@ namespace Squidex.Core.Schemas
.AddValue("de", 1)
.AddValue("xx", 1));
await sut.ValidateAsync(data, errors, languages);
await data.ValidateAsync(schema, languages, errors);
errors.ShouldBeEquivalentTo(
new List<ValidationError>
@ -140,7 +139,7 @@ namespace Squidex.Core.Schemas
[Fact]
public async Task Should_add_error_if_data_contains_unsupported_language()
{
sut = sut.AddOrUpdateField(new NumberField(1, "my-field", new NumberFieldProperties { IsLocalizable = true }));
schema = schema.AddOrUpdateField(new NumberField(1, "my-field", new NumberFieldProperties { IsLocalizable = true }));
var data =
new ContentData()
@ -149,7 +148,7 @@ namespace Squidex.Core.Schemas
.AddValue("es", 1)
.AddValue("it", 1));
await sut.ValidateAsync(data, errors, languages);
await data.ValidateAsync(schema, languages, errors);
errors.ShouldBeEquivalentTo(
new List<ValidationError>
@ -167,7 +166,7 @@ namespace Squidex.Core.Schemas
.AddField("unknown",
new ContentFieldData());
await sut.ValidatePartialAsync(data, errors, languages);
await data.ValidatePartialAsync(schema, languages, errors);
errors.ShouldBeEquivalentTo(
new List<ValidationError>
@ -179,7 +178,7 @@ namespace Squidex.Core.Schemas
[Fact]
public async Task Should_add_error_if_validating_partial_data_with_invalid_field()
{
sut = sut.AddOrUpdateField(new NumberField(1, "my-field", new NumberFieldProperties { MaxValue = 100 }));
schema = schema.AddOrUpdateField(new NumberField(1, "my-field", new NumberFieldProperties { MaxValue = 100 }));
var data =
new ContentData()
@ -187,7 +186,7 @@ namespace Squidex.Core.Schemas
new ContentFieldData()
.SetValue(1000));
await sut.ValidatePartialAsync(data, errors, languages);
await data.ValidatePartialAsync(schema, languages, errors);
errors.ShouldBeEquivalentTo(
new List<ValidationError>
@ -199,7 +198,7 @@ namespace Squidex.Core.Schemas
[Fact]
public async Task Should_add_error_non_localizable_partial_data_field_contains_language()
{
sut = sut.AddOrUpdateField(new NumberField(1, "my-field", new NumberFieldProperties()));
schema = schema.AddOrUpdateField(new NumberField(1, "my-field", new NumberFieldProperties()));
var data =
new ContentData()
@ -208,7 +207,7 @@ namespace Squidex.Core.Schemas
.AddValue("es", 1)
.AddValue("it", 1));
await sut.ValidatePartialAsync(data, errors, languages);
await data.ValidatePartialAsync(schema, languages, errors);
errors.ShouldBeEquivalentTo(
new List<ValidationError>
@ -220,12 +219,12 @@ namespace Squidex.Core.Schemas
[Fact]
public async Task Should_not_add_error_if_validating_partial_data_with_invalid_localizable_field()
{
sut = sut.AddOrUpdateField(new NumberField(1, "my-field", new NumberFieldProperties { IsRequired = true, IsLocalizable = true }));
schema = schema.AddOrUpdateField(new NumberField(1, "my-field", new NumberFieldProperties { IsRequired = true, IsLocalizable = true }));
var data =
new ContentData();
await sut.ValidatePartialAsync(data, errors, languages);
await data.ValidatePartialAsync(schema, languages, errors);
Assert.Equal(0, errors.Count);
}
@ -233,12 +232,12 @@ namespace Squidex.Core.Schemas
[Fact]
public async Task Should_not_add_error_if_required_partial_data_field_is_not_in_bag()
{
sut = sut.AddOrUpdateField(new NumberField(1, "my-field", new NumberFieldProperties { IsRequired = true }));
schema = schema.AddOrUpdateField(new NumberField(1, "my-field", new NumberFieldProperties { IsRequired = true }));
var data =
new ContentData();
await sut.ValidatePartialAsync(data, errors, languages);
await data.ValidatePartialAsync(schema, languages, errors);
Assert.Equal(0, errors.Count);
}
@ -246,7 +245,7 @@ namespace Squidex.Core.Schemas
[Fact]
public async Task Should_add_error_if_partial_data_contains_invalid_language()
{
sut = sut.AddOrUpdateField(new NumberField(1, "my-field", new NumberFieldProperties { IsLocalizable = true }));
schema = schema.AddOrUpdateField(new NumberField(1, "my-field", new NumberFieldProperties { IsLocalizable = true }));
var data =
new ContentData()
@ -255,7 +254,7 @@ namespace Squidex.Core.Schemas
.AddValue("de", 1)
.AddValue("xx", 1));
await sut.ValidatePartialAsync(data, errors, languages);
await data.ValidatePartialAsync(schema, languages, errors);
errors.ShouldBeEquivalentTo(
new List<ValidationError>
@ -267,7 +266,7 @@ namespace Squidex.Core.Schemas
[Fact]
public async Task Should_add_error_if_partial_data_contains_unsupported_language()
{
sut = sut.AddOrUpdateField(new NumberField(1, "my-field", new NumberFieldProperties { IsLocalizable = true }));
schema = schema.AddOrUpdateField(new NumberField(1, "my-field", new NumberFieldProperties { IsLocalizable = true }));
var data =
new ContentData()
@ -276,7 +275,7 @@ namespace Squidex.Core.Schemas
.AddValue("es", 1)
.AddValue("it", 1));
await sut.ValidatePartialAsync(data, errors, languages);
await data.ValidatePartialAsync(schema, languages, errors);
errors.ShouldBeEquivalentTo(
new List<ValidationError>
@ -285,44 +284,5 @@ namespace Squidex.Core.Schemas
new ValidationError("my-field has an unsupported language 'it'", "my-field")
});
}
[Fact]
private void Should_enrich_with_default_values()
{
var now = Instant.FromUnixTimeSeconds(SystemClock.Instance.GetCurrentInstant().ToUnixTimeSeconds());
var schema =
Schema.Create("my-schema", new SchemaProperties())
.AddOrUpdateField(new JsonField(1, "my-json",
new JsonFieldProperties()))
.AddOrUpdateField(new StringField(2, "my-string",
new StringFieldProperties { DefaultValue = "EN-String", IsLocalizable = true }))
.AddOrUpdateField(new NumberField(3, "my-number",
new NumberFieldProperties { DefaultValue = 123 }))
.AddOrUpdateField(new BooleanField(4, "my-boolean",
new BooleanFieldProperties { DefaultValue = true }))
.AddOrUpdateField(new DateTimeField(5, "my-datetime",
new DateTimeFieldProperties { DefaultValue = now }));
var data =
new ContentData()
.AddField("my-string",
new ContentFieldData()
.AddValue("de", "DE-String"))
.AddField("my-number",
new ContentFieldData()
.AddValue("iv", 456));
schema.Enrich(data, languages);
Assert.Equal(456, (int)data["my-number"]["iv"]);
Assert.Equal("DE-String", (string)data["my-string"]["de"]);
Assert.Equal("EN-String", (string)data["my-string"]["en"]);
Assert.Equal(now, InstantPattern.General.Parse((string)data["my-datetime"]["iv"]).Value);
Assert.Equal(true, (bool)data["my-boolean"]["iv"]);
}
}
}

12
tests/Squidex.Core.Tests/Schemas/BooleanFieldTests.cs

@ -37,7 +37,7 @@ namespace Squidex.Core.Schemas
[Fact]
public async Task Should_not_add_error_if_null_boolean_is_valid()
{
var sut = new BooleanField(1, "my-bolean", new BooleanFieldProperties { Label = "My-Boolean" });
var sut = new BooleanField(1, "my-bolean", new BooleanFieldProperties());
await sut.ValidateAsync(CreateValue(null), errors);
@ -47,7 +47,7 @@ namespace Squidex.Core.Schemas
[Fact]
public async Task Should_not_add_error_if_boolean_is_valid()
{
var sut = new BooleanField(1, "my-bolean", new BooleanFieldProperties { Label = "My-Boolean" });
var sut = new BooleanField(1, "my-bolean", new BooleanFieldProperties());
await sut.ValidateAsync(CreateValue(true), errors);
@ -57,23 +57,23 @@ namespace Squidex.Core.Schemas
[Fact]
public async Task Should_add_errors_if_boolean_is_required()
{
var sut = new BooleanField(1, "my-bolean", new BooleanFieldProperties { Label = "My-Boolean", IsRequired = true });
var sut = new BooleanField(1, "my-bolean", new BooleanFieldProperties { IsRequired = true });
await sut.ValidateAsync(CreateValue(null), errors);
errors.ShouldBeEquivalentTo(
new[] { "My-Boolean is required" });
new[] { "<FIELD> is required" });
}
[Fact]
public async Task Should_add_errors_if_value_is_not_valid()
{
var sut = new BooleanField(1, "my-bolean", new BooleanFieldProperties { Label = "My-Boolean" });
var sut = new BooleanField(1, "my-bolean", new BooleanFieldProperties());
await sut.ValidateAsync(CreateValue("Invalid"), errors);
errors.ShouldBeEquivalentTo(
new[] { "My-Boolean is not a valid value" });
new[] { "<FIELD> is not a valid value" });
}
private static JValue CreateValue(object v)

22
tests/Squidex.Core.Tests/Schemas/DateTimeFieldTests.cs

@ -39,7 +39,7 @@ namespace Squidex.Core.Schemas
[Fact]
public async Task Should_not_add_error_if_datetime_is_valid()
{
var sut = new DateTimeField(1, "my-datetime", new DateTimeFieldProperties { Label = "My-DateTime" });
var sut = new DateTimeField(1, "my-datetime", new DateTimeFieldProperties());
await sut.ValidateAsync(CreateValue(null), errors);
@ -49,56 +49,56 @@ namespace Squidex.Core.Schemas
[Fact]
public async Task Should_add_errors_if_datetime_is_required()
{
var sut = new DateTimeField(1, "my-datetime", new DateTimeFieldProperties { Label = "My-DateTime", IsRequired = true });
var sut = new DateTimeField(1, "my-datetime", new DateTimeFieldProperties { IsRequired = true });
await sut.ValidateAsync(CreateValue(null), errors);
errors.ShouldBeEquivalentTo(
new[] { "My-DateTime is required" });
new[] { "<FIELD> is required" });
}
[Fact]
public async Task Should_add_errors_if_datetime_is_less_than_min()
{
var sut = new DateTimeField(1, "my-datetime", new DateTimeFieldProperties { Label = "My-DateTime", MinValue = FutureDays(10) });
var sut = new DateTimeField(1, "my-datetime", new DateTimeFieldProperties { MinValue = FutureDays(10) });
await sut.ValidateAsync(CreateValue(FutureDays(0)), errors);
errors.ShouldBeEquivalentTo(
new[] { $"My-DateTime must be greater than '{FutureDays(10)}'" });
new[] { $"<FIELD> must be greater than '{FutureDays(10)}'" });
}
[Fact]
public async Task Should_add_errors_if_datetime_is_greater_than_max()
{
var sut = new DateTimeField(1, "my-datetime", new DateTimeFieldProperties { Label = "My-DateTime", MaxValue = FutureDays(10) });
var sut = new DateTimeField(1, "my-datetime", new DateTimeFieldProperties { MaxValue = FutureDays(10) });
await sut.ValidateAsync(CreateValue(FutureDays(20)), errors);
errors.ShouldBeEquivalentTo(
new[] { $"My-DateTime must be less than '{FutureDays(10)}'" });
new[] { $"<FIELD> must be less than '{FutureDays(10)}'" });
}
[Fact]
public async Task Should_add_errors_if_value_is_not_valid()
{
var sut = new DateTimeField(1, "my-datetime", new DateTimeFieldProperties { Label = "My-DateTime" });
var sut = new DateTimeField(1, "my-datetime", new DateTimeFieldProperties());
await sut.ValidateAsync(CreateValue("Invalid"), errors);
errors.ShouldBeEquivalentTo(
new[] { "My-DateTime is not a valid value" });
new[] { "<FIELD> is not a valid value" });
}
[Fact]
public async Task Should_add_errors_if_value_is_another_type()
{
var sut = new DateTimeField(1, "my-datetime", new DateTimeFieldProperties { Label = "My-DateTime" });
var sut = new DateTimeField(1, "my-datetime", new DateTimeFieldProperties());
await sut.ValidateAsync(CreateValue(123), errors);
errors.ShouldBeEquivalentTo(
new[] { "My-DateTime is not a valid value" });
new[] { "<FIELD> is not a valid value" });
}
private static Instant FutureDays(int days)

24
tests/Squidex.Core.Tests/Schemas/GeolocationFieldTests.cs

@ -34,10 +34,20 @@ namespace Squidex.Core.Schemas
Assert.NotEqual(sut, sut.Enable());
}
[Fact]
public async Task Should_not_add_error_if_geolocation_is_valid_null()
{
var sut = new GeolocationField(1, "my-geolocation", new GeolocationFieldProperties());
await sut.ValidateAsync(CreateValue(JValue.CreateNull()), errors);
Assert.Empty(errors);
}
[Fact]
public async Task Should_not_add_error_if_geolocation_is_valid()
{
var sut = new GeolocationField(1, "my-geolocation", new GeolocationFieldProperties { Label = "my-geolocation" });
var sut = new GeolocationField(1, "my-geolocation", new GeolocationFieldProperties());
var geolocation = new JObject(
new JProperty("latitude", 0),
@ -51,7 +61,7 @@ namespace Squidex.Core.Schemas
[Fact]
public async Task Should_add_errors_if_geolocation_has_invalid_properties()
{
var sut = new GeolocationField(1, "my-geolocation", new GeolocationFieldProperties { Label = "my-geolocation", IsRequired = true });
var sut = new GeolocationField(1, "my-geolocation", new GeolocationFieldProperties { IsRequired = true });
var geolocation = new JObject(
new JProperty("latitude", 200),
@ -60,13 +70,13 @@ namespace Squidex.Core.Schemas
await sut.ValidateAsync(CreateValue(geolocation), errors);
errors.ShouldBeEquivalentTo(
new[] { "my-geolocation is not a valid value" });
new[] { "<FIELD> is not a valid value" });
}
[Fact]
public async Task Should_add_errors_if_geolocation_has_too_many_properties()
{
var sut = new GeolocationField(1, "my-geolocation", new GeolocationFieldProperties { Label = "my-geolocation", IsRequired = true });
var sut = new GeolocationField(1, "my-geolocation", new GeolocationFieldProperties { IsRequired = true });
var geolocation = new JObject(
new JProperty("invalid", 0),
@ -76,18 +86,18 @@ namespace Squidex.Core.Schemas
await sut.ValidateAsync(CreateValue(geolocation), errors);
errors.ShouldBeEquivalentTo(
new[] { "my-geolocation is not a valid value" });
new[] { "<FIELD> is not a valid value" });
}
[Fact]
public async Task Should_add_errors_if_geolocation_is_required()
{
var sut = new GeolocationField(1, "my-geolocation", new GeolocationFieldProperties { Label = "my-geolocation", IsRequired = true });
var sut = new GeolocationField(1, "my-geolocation", new GeolocationFieldProperties { IsRequired = true });
await sut.ValidateAsync(CreateValue(JValue.CreateNull()), errors);
errors.ShouldBeEquivalentTo(
new[] { "my-geolocation is required" });
new[] { "<FIELD> is required" });
}
private static JToken CreateValue(JToken v)

6
tests/Squidex.Core.Tests/Schemas/JsonFieldTests.cs

@ -37,7 +37,7 @@ namespace Squidex.Core.Schemas
[Fact]
public async Task Should_not_add_error_if_json_is_valid()
{
var sut = new JsonField(1, "my-json", new JsonFieldProperties { Label = "My-Json" });
var sut = new JsonField(1, "my-json", new JsonFieldProperties());
await sut.ValidateAsync(CreateValue(new JValue(1)), errors);
@ -47,12 +47,12 @@ namespace Squidex.Core.Schemas
[Fact]
public async Task Should_add_errors_if_json_is_required()
{
var sut = new JsonField(1, "my-json", new JsonFieldProperties { Label = "My-Json", IsRequired = true });
var sut = new JsonField(1, "my-json", new JsonFieldProperties { IsRequired = true });
await sut.ValidateAsync(CreateValue(JValue.CreateNull()), errors);
errors.ShouldBeEquivalentTo(
new[] { "My-Json is required" });
new[] { "<FIELD> is required" });
}
private static JValue CreateValue(JValue v)

22
tests/Squidex.Core.Tests/Schemas/NumberFieldTests.cs

@ -38,7 +38,7 @@ namespace Squidex.Core.Schemas
[Fact]
public async Task Should_not_add_error_if_number_is_valid()
{
var sut = new NumberField(1, "my-number", new NumberFieldProperties { Label = "My-Number" });
var sut = new NumberField(1, "my-number", new NumberFieldProperties());
await sut.ValidateAsync(CreateValue(null), errors);
@ -48,56 +48,56 @@ namespace Squidex.Core.Schemas
[Fact]
public async Task Should_add_errors_if_number_is_required()
{
var sut = new NumberField(1, "my-number", new NumberFieldProperties { Label = "My-Number", IsRequired = true });
var sut = new NumberField(1, "my-number", new NumberFieldProperties { IsRequired = true });
await sut.ValidateAsync(CreateValue(null), errors);
errors.ShouldBeEquivalentTo(
new [] { "My-Number is required" });
new [] { "<FIELD> is required" });
}
[Fact]
public async Task Should_add_errors_if_number_is_less_than_min()
{
var sut = new NumberField(1, "my-number", new NumberFieldProperties { Label = "My-Number", MinValue = 10 });
var sut = new NumberField(1, "my-number", new NumberFieldProperties { MinValue = 10 });
await sut.ValidateAsync(CreateValue(5), errors);
errors.ShouldBeEquivalentTo(
new[] { "My-Number must be greater than '10'" });
new[] { "<FIELD> must be greater than '10'" });
}
[Fact]
public async Task Should_add_errors_if_number_is_greater_than_max()
{
var sut = new NumberField(1, "my-number", new NumberFieldProperties { Label = "My-Number", MaxValue = 10 });
var sut = new NumberField(1, "my-number", new NumberFieldProperties { MaxValue = 10 });
await sut.ValidateAsync(CreateValue(20), errors);
errors.ShouldBeEquivalentTo(
new[] { "My-Number must be less than '10'" });
new[] { "<FIELD> must be less than '10'" });
}
[Fact]
public async Task Should_add_errors_if_number_is_not_allowed()
{
var sut = new NumberField(1, "my-number", new NumberFieldProperties { Label = "My-Number", AllowedValues = ImmutableList.Create(10d) });
var sut = new NumberField(1, "my-number", new NumberFieldProperties { AllowedValues = ImmutableList.Create(10d) });
await sut.ValidateAsync(CreateValue(20), errors);
errors.ShouldBeEquivalentTo(
new[] { "My-Number is not an allowed value" });
new[] { "<FIELD> is not an allowed value" });
}
[Fact]
public async Task Should_add_errors_if_value_is_not_valid()
{
var sut = new NumberField(1, "my-number", new NumberFieldProperties { Label = "My-Number" });
var sut = new NumberField(1, "my-number", new NumberFieldProperties());
await sut.ValidateAsync(CreateValue("Invalid"), errors);
errors.ShouldBeEquivalentTo(
new[] { "My-Number is not a valid value" });
new[] { "<FIELD> is not a valid value" });
}
private static JValue CreateValue(object v)

2
tests/Squidex.Core.Tests/Schemas/SchemaTests.cs

@ -256,7 +256,7 @@ namespace Squidex.Core.Schemas
{
var languages = new HashSet<Language>(new[] { Language.DE, Language.EN });
var jsonSchema = BuildMixedSchema().BuildSchema(languages, (n, s) => new JsonSchema4 { SchemaReference = s });
var jsonSchema = BuildMixedSchema().BuildJsonSchema(languages, (n, s) => new JsonSchema4 { SchemaReference = s });
Assert.NotNull(jsonSchema);
}

24
tests/Squidex.Core.Tests/Schemas/StringFieldTests.cs

@ -38,7 +38,7 @@ namespace Squidex.Core.Schemas
[Fact]
public async Task Should_not_add_error_if_string_is_valid()
{
var sut = new StringField(1, "my-string", new StringFieldProperties { Label = "My-String" });
var sut = new StringField(1, "my-string", new StringFieldProperties { Label = "<FIELD>" });
await sut.ValidateAsync(CreateValue(null), errors);
@ -48,62 +48,62 @@ namespace Squidex.Core.Schemas
[Fact]
public async Task Should_add_errors_if_string_is_required()
{
var sut = new StringField(1, "my-string", new StringFieldProperties { Label = "My-String", IsRequired = true });
var sut = new StringField(1, "my-string", new StringFieldProperties { IsRequired = true });
await sut.ValidateAsync(CreateValue(null), errors);
errors.ShouldBeEquivalentTo(
new[] { "My-String is required" });
new[] { "<FIELD> is required" });
}
[Fact]
public async Task Should_add_errors_if_string_is_shorter_than_min_length()
{
var sut = new StringField(1, "my-string", new StringFieldProperties { Label = "My-String", MinLength = 10 });
var sut = new StringField(1, "my-string", new StringFieldProperties { MinLength = 10 });
await sut.ValidateAsync(CreateValue("123"), errors);
errors.ShouldBeEquivalentTo(
new[] { "My-String must have more than '10' characters" });
new[] { "<FIELD> must have more than '10' characters" });
}
[Fact]
public async Task Should_add_errors_if_string_is_longer_than_max_length()
{
var sut = new StringField(1, "my-string", new StringFieldProperties { Label = "My-String", MaxLength = 5 });
var sut = new StringField(1, "my-string", new StringFieldProperties { MaxLength = 5 });
await sut.ValidateAsync(CreateValue("12345678"), errors);
errors.ShouldBeEquivalentTo(
new[] { "My-String must have less than '5' characters" });
new[] { "<FIELD> must have less than '5' characters" });
}
[Fact]
public async Task Should_add_errors_if_string_not_allowed()
{
var sut = new StringField(1, "my-string", new StringFieldProperties { Label = "My-String", AllowedValues = ImmutableList.Create("Foo") });
var sut = new StringField(1, "my-string", new StringFieldProperties { AllowedValues = ImmutableList.Create("Foo") });
await sut.ValidateAsync(CreateValue("Bar"), errors);
errors.ShouldBeEquivalentTo(
new[] { "My-String is not an allowed value" });
new[] { "<FIELD> is not an allowed value" });
}
[Fact]
public async Task Should_add_errors_if_number_is_not_valid_pattern()
{
var sut = new StringField(1, "my-string", new StringFieldProperties { Label = "My-String", Pattern = "[0-9]{3}" });
var sut = new StringField(1, "my-string", new StringFieldProperties { Pattern = "[0-9]{3}" });
await sut.ValidateAsync(CreateValue("abc"), errors);
errors.ShouldBeEquivalentTo(
new[] { "My-String is not valid" });
new[] { "<FIELD> is not valid" });
}
[Fact]
public async Task Should_add_errors_if_number_is_not_valid_pattern_with_message()
{
var sut = new StringField(1, "my-string", new StringFieldProperties { Label = "My-String", Pattern = "[0-9]{3}", PatternMessage = "Custom Error Message" });
var sut = new StringField(1, "my-string", new StringFieldProperties { Pattern = "[0-9]{3}", PatternMessage = "Custom Error Message" });
await sut.ValidateAsync(CreateValue("abc"), errors);

22
tests/Squidex.Core.Tests/Schemas/ValidationTestExtensions.cs

@ -0,0 +1,22 @@
// ==========================================================================
// ValidationTestExtensions.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System.Collections.Generic;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
namespace Squidex.Core.Schemas
{
public static class ValidationTestExtensions
{
public static Task ValidateAsync(this Field field, JToken value, IList<string> errors)
{
return field.ValidateAsync(value, errors.Add);
}
}
}

6
tests/Squidex.Core.Tests/Schemas/Validators/AllowedValuesValidatorTests.cs

@ -22,7 +22,7 @@ namespace Squidex.Core.Schemas.Validators
{
var sut = new AllowedValuesValidator<int>(100, 200);
await sut.ValidateAsync(null, errors);
await sut.ValidateAsync(null, errors.Add);
Assert.Equal(0, errors.Count);
}
@ -32,7 +32,7 @@ namespace Squidex.Core.Schemas.Validators
{
var sut = new AllowedValuesValidator<int>(100, 200);
await sut.ValidateAsync(100, errors);
await sut.ValidateAsync(100, errors.Add);
Assert.Equal(0, errors.Count);
}
@ -42,7 +42,7 @@ namespace Squidex.Core.Schemas.Validators
{
var sut = new AllowedValuesValidator<int>(100, 200);
await sut.ValidateAsync(50, errors);
await sut.ValidateAsync(50, errors.Add);
errors.ShouldBeEquivalentTo(
new[] { "<FIELD> is not an allowed value" });

8
tests/Squidex.Core.Tests/Schemas/Validators/PatternValidatorTests.cs

@ -22,7 +22,7 @@ namespace Squidex.Core.Schemas.Validators
{
var sut = new PatternValidator("[a-z]{3}:[0-9]{2}");
await sut.ValidateAsync("abc:12", errors);
await sut.ValidateAsync("abc:12", errors.Add);
Assert.Equal(0, errors.Count);
}
@ -32,7 +32,7 @@ namespace Squidex.Core.Schemas.Validators
{
var sut = new PatternValidator("[a-z]{3}:[0-9]{2}");
await sut.ValidateAsync(null, errors);
await sut.ValidateAsync(null, errors.Add);
Assert.Equal(0, errors.Count);
}
@ -42,7 +42,7 @@ namespace Squidex.Core.Schemas.Validators
{
var sut = new PatternValidator("[a-z]{3}:[0-9]{2}");
await sut.ValidateAsync("foo", errors);
await sut.ValidateAsync("foo", errors.Add);
errors.ShouldBeEquivalentTo(
new[] { "<FIELD> is not valid" });
@ -53,7 +53,7 @@ namespace Squidex.Core.Schemas.Validators
{
var sut = new PatternValidator("[a-z]{3}:[0-9]{2}", "Custom Error Message");
await sut.ValidateAsync("foo", errors);
await sut.ValidateAsync("foo", errors.Add);
errors.ShouldBeEquivalentTo(
new[] { "Custom Error Message" });

8
tests/Squidex.Core.Tests/Schemas/Validators/RangeValidatorTests.cs

@ -23,7 +23,7 @@ namespace Squidex.Core.Schemas.Validators
{
var sut = new RangeValidator<int>(100, 200);
await sut.ValidateAsync(null, errors);
await sut.ValidateAsync(null, errors.Add);
Assert.Equal(0, errors.Count);
}
@ -37,7 +37,7 @@ namespace Squidex.Core.Schemas.Validators
{
var sut = new RangeValidator<int>(min, max);
await sut.ValidateAsync(1500, errors);
await sut.ValidateAsync(1500, errors.Add);
Assert.Equal(0, errors.Count);
}
@ -55,7 +55,7 @@ namespace Squidex.Core.Schemas.Validators
{
var sut = new RangeValidator<int>(2000, null);
await sut.ValidateAsync(1500, errors);
await sut.ValidateAsync(1500, errors.Add);
errors.ShouldBeEquivalentTo(
new[] { "<FIELD> must be greater than '2000'" });
@ -66,7 +66,7 @@ namespace Squidex.Core.Schemas.Validators
{
var sut = new RangeValidator<int>(null, 1000);
await sut.ValidateAsync(1500, errors);
await sut.ValidateAsync(1500, errors.Add);
errors.ShouldBeEquivalentTo(
new[] { "<FIELD> must be less than '1000'" });

8
tests/Squidex.Core.Tests/Schemas/Validators/RequiredStringValidatorTests.cs

@ -26,7 +26,7 @@ namespace Squidex.Core.Schemas.Validators
{
var sut = new RequiredStringValidator();
await sut.ValidateAsync(value, errors);
await sut.ValidateAsync(value, errors.Add);
Assert.Equal(0, errors.Count);
}
@ -36,7 +36,7 @@ namespace Squidex.Core.Schemas.Validators
{
var sut = new RequiredStringValidator();
await sut.ValidateAsync(true, errors);
await sut.ValidateAsync(true, errors.Add);
Assert.Equal(0, errors.Count);
}
@ -46,7 +46,7 @@ namespace Squidex.Core.Schemas.Validators
{
var sut = new RequiredStringValidator(true);
await sut.ValidateAsync(string.Empty, errors);
await sut.ValidateAsync(string.Empty, errors.Add);
errors.ShouldBeEquivalentTo(
new[] { "<FIELD> is required" });
@ -57,7 +57,7 @@ namespace Squidex.Core.Schemas.Validators
{
var sut = new RequiredStringValidator();
await sut.ValidateAsync(null, errors);
await sut.ValidateAsync(null, errors.Add);
errors.ShouldBeEquivalentTo(
new[] { "<FIELD> is required" });

6
tests/Squidex.Core.Tests/Schemas/Validators/RequiredValidatorTests.cs

@ -22,7 +22,7 @@ namespace Squidex.Core.Schemas.Validators
{
var sut = new RequiredValidator();
await sut.ValidateAsync(true, errors);
await sut.ValidateAsync(true, errors.Add);
Assert.Equal(0, errors.Count);
}
@ -32,7 +32,7 @@ namespace Squidex.Core.Schemas.Validators
{
var sut = new RequiredValidator();
await sut.ValidateAsync(string.Empty, errors);
await sut.ValidateAsync(string.Empty, errors.Add);
Assert.Equal(0, errors.Count);
}
@ -42,7 +42,7 @@ namespace Squidex.Core.Schemas.Validators
{
var sut = new RequiredValidator();
await sut.ValidateAsync(null, errors);
await sut.ValidateAsync(null, errors.Add);
errors.ShouldBeEquivalentTo(
new[] { "<FIELD> is required" });

8
tests/Squidex.Core.Tests/Schemas/Validators/StringLengthValidatorTests.cs

@ -24,7 +24,7 @@ namespace Squidex.Core.Schemas.Validators
{
var sut = new StringLengthValidator(100, 200);
await sut.ValidateAsync(null, errors);
await sut.ValidateAsync(null, errors.Add);
Assert.Equal(0, errors.Count);
}
@ -38,7 +38,7 @@ namespace Squidex.Core.Schemas.Validators
{
var sut = new StringLengthValidator(min, max);
await sut.ValidateAsync(CreateString(1500), errors);
await sut.ValidateAsync(CreateString(1500), errors.Add);
Assert.Equal(0, errors.Count);
}
@ -56,7 +56,7 @@ namespace Squidex.Core.Schemas.Validators
{
var sut = new StringLengthValidator(2000, null);
await sut.ValidateAsync(CreateString(1500), errors);
await sut.ValidateAsync(CreateString(1500), errors.Add);
errors.ShouldBeEquivalentTo(
new[] { "<FIELD> must have more than '2000' characters" });
@ -67,7 +67,7 @@ namespace Squidex.Core.Schemas.Validators
{
var sut = new StringLengthValidator(null, 1000);
await sut.ValidateAsync(CreateString(1500), errors);
await sut.ValidateAsync(CreateString(1500), errors.Add);
errors.ShouldBeEquivalentTo(
new[] { "<FIELD> must have less than '1000' characters" });

Loading…
Cancel
Save