Browse Source

More tests

pull/1/head
Sebastian 9 years ago
parent
commit
8f4730778d
  1. 12
      src/Squidex.Core/Schemas/BooleanField.cs
  2. 24
      src/Squidex.Core/Schemas/Cloneable.cs
  3. 41
      src/Squidex.Core/Schemas/Field.cs
  4. 12
      src/Squidex.Core/Schemas/FieldProperties.cs
  5. 2
      src/Squidex.Core/Schemas/Field_Generic.cs
  6. 4
      src/Squidex.Core/Schemas/Json/SchemaJsonSerializer.cs
  7. 14
      src/Squidex.Core/Schemas/NumberField.cs
  8. 132
      src/Squidex.Core/Schemas/Schema.cs
  9. 11
      src/Squidex.Core/Schemas/StringField.cs
  10. 14
      src/Squidex.Infrastructure/Language.cs
  11. 20
      tests/Squidex.Core.Tests/Schemas/BooleanFieldTests.cs
  12. 8
      tests/Squidex.Core.Tests/Schemas/NumberFieldTests.cs
  13. 49
      tests/Squidex.Core.Tests/Schemas/SchemaTests.cs
  14. 148
      tests/Squidex.Core.Tests/Schemas/SchemaValidationTests.cs
  15. 8
      tests/Squidex.Core.Tests/Schemas/StringFieldTests.cs
  16. 16
      tests/Squidex.Infrastructure.Tests/CQRS/Commands/CommandContextTests.cs
  17. 74
      tests/Squidex.Infrastructure.Tests/CQRS/Commands/LogExceptionHandlerTests.cs
  18. 61
      tests/Squidex.Infrastructure.Tests/CQRS/Commands/LogExecutingHandlerTests.cs
  19. 82
      tests/Squidex.Infrastructure.Tests/CQRS/Replay/ReplayGeneratorTests.cs

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

@ -7,9 +7,8 @@
// ========================================================================== // ==========================================================================
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using Newtonsoft.Json.Linq;
using Squidex.Core.Schemas.Validators; using Squidex.Core.Schemas.Validators;
using Squidex.Infrastructure;
namespace Squidex.Core.Schemas namespace Squidex.Core.Schemas
{ {
@ -28,14 +27,9 @@ namespace Squidex.Core.Schemas
} }
} }
protected override object ConvertValue(PropertyValue property) protected override object ConvertValue(JToken value)
{ {
return property.ToNullableBoolean(CultureInfo.InvariantCulture); return (bool?)value;
}
protected override Field Clone()
{
return new BooleanField(Id, Name, Properties);
} }
} }
} }

24
src/Squidex.Core/Schemas/Cloneable.cs

@ -0,0 +1,24 @@
// ==========================================================================
// Cloneable.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
namespace Squidex.Core.Schemas
{
public abstract class Cloneable
{
protected T Clone<T>(Action<T> updater) where T : Cloneable
{
var clone = (T)MemberwiseClone();
updater(clone);
return clone;
}
}
}

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

@ -9,13 +9,14 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using Squidex.Infrastructure; using Squidex.Infrastructure;
// ReSharper disable InvertIf // ReSharper disable InvertIf
// ReSharper disable ConvertIfStatementToReturnStatement // ReSharper disable ConvertIfStatementToReturnStatement
namespace Squidex.Core.Schemas namespace Squidex.Core.Schemas
{ {
public abstract class Field public abstract class Field : Cloneable
{ {
private readonly Lazy<List<IValidator>> validators; private readonly Lazy<List<IValidator>> validators;
private readonly long id; private readonly long id;
@ -59,21 +60,21 @@ namespace Squidex.Core.Schemas
public abstract Field Update(FieldProperties newProperties); public abstract Field Update(FieldProperties newProperties);
public async Task ValidateAsync(PropertyValue property, ICollection<string> errors) public async Task ValidateAsync(JToken value, ICollection<string> errors, Language language = null)
{ {
Guard.NotNull(property, nameof(property)); Guard.NotNull(value, nameof(value));
var rawErrors = new List<string>(); var rawErrors = new List<string>();
try try
{ {
var value = ConvertValue(property); var typedValue = value.Type == JTokenType.Null ? null : ConvertValue(value);
foreach (var validator in validators.Value) foreach (var validator in validators.Value)
{ {
await validator.ValidateAsync(value, rawErrors); await validator.ValidateAsync(typedValue, rawErrors);
} }
} }
catch (InvalidCastException) catch
{ {
rawErrors.Add("<FIELD> is not a valid value"); rawErrors.Add("<FIELD> is not a valid value");
} }
@ -82,6 +83,11 @@ namespace Squidex.Core.Schemas
{ {
var displayName = !string.IsNullOrWhiteSpace(RawProperties.Label) ? RawProperties.Label : name; var displayName = !string.IsNullOrWhiteSpace(RawProperties.Label) ? RawProperties.Label : name;
if (language != null)
{
displayName += $" ({language.Iso2Code})";
}
foreach (var error in rawErrors) foreach (var error in rawErrors)
{ {
errors.Add(error.Replace("<FIELD>", displayName)); errors.Add(error.Replace("<FIELD>", displayName));
@ -91,22 +97,22 @@ namespace Squidex.Core.Schemas
public Field Hide() public Field Hide()
{ {
return Update<Field>(clone => clone.isHidden = true); return Clone<Field>(clone => clone.isHidden = true);
} }
public Field Show() public Field Show()
{ {
return Update<Field>(clone => clone.isHidden = false); return Clone<Field>(clone => clone.isHidden = false);
} }
public Field Disable() public Field Disable()
{ {
return Update<Field>(clone => clone.isDisabled = true); return Clone<Field>(clone => clone.isDisabled = true);
} }
public Field Enable() public Field Enable()
{ {
return Update<Field>(clone => clone.isDisabled = false); return Clone<Field>(clone => clone.isDisabled = false);
} }
public Field Rename(string newName) public Field Rename(string newName)
@ -118,22 +124,11 @@ namespace Squidex.Core.Schemas
throw new ValidationException($"Cannot rename the field '{name}' ({id})", error); throw new ValidationException($"Cannot rename the field '{name}' ({id})", error);
} }
return Update<Field>(clone => clone.name = newName); return Clone<Field>(clone => clone.name = newName);
}
protected T Update<T>(Action<T> updater) where T : Field
{
var clone = (T)Clone();
updater(clone);
return clone;
} }
protected abstract IEnumerable<IValidator> CreateValidators(); protected abstract IEnumerable<IValidator> CreateValidators();
protected abstract object ConvertValue(PropertyValue property); protected abstract object ConvertValue(JToken value);
protected abstract Field Clone();
} }
} }

12
src/Squidex.Core/Schemas/FieldProperties.cs

@ -14,6 +14,7 @@ namespace Squidex.Core.Schemas
public abstract class FieldProperties : NamedElementProperties, IValidatable public abstract class FieldProperties : NamedElementProperties, IValidatable
{ {
private bool isRequired; private bool isRequired;
private bool isLocalizable;
private string placeholder; private string placeholder;
public bool IsRequired public bool IsRequired
@ -27,6 +28,17 @@ namespace Squidex.Core.Schemas
} }
} }
public bool IsLocalizable
{
get { return isLocalizable; }
set
{
ThrowIfFrozen();
isLocalizable = value;
}
}
public string Placeholder public string Placeholder
{ {
get { return placeholder; } get { return placeholder; }

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

@ -37,7 +37,7 @@ namespace Squidex.Core.Schemas
{ {
var typedProperties = ValidateProperties(newProperties); var typedProperties = ValidateProperties(newProperties);
return Update<Field<T>>(clone => clone.properties = typedProperties); return Clone<Field<T>>(clone => clone.properties = typedProperties);
} }
private T ValidateProperties(FieldProperties newProperties) private T ValidateProperties(FieldProperties newProperties)

4
src/Squidex.Core/Schemas/Json/SchemaJsonSerializer.cs

@ -100,9 +100,7 @@ namespace Squidex.Core.Schemas.Json
var schema = var schema =
new Schema( new Schema(
model.Name, model.Name,
model.Properties, model.IsPublished, model.Properties, fields);
model.IsPublished,
fields);
return schema; return schema;
} }

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

@ -7,10 +7,9 @@
// ========================================================================== // ==========================================================================
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using Squidex.Core.Schemas.Validators;
using Squidex.Infrastructure;
using System.Linq; using System.Linq;
using Newtonsoft.Json.Linq;
using Squidex.Core.Schemas.Validators;
namespace Squidex.Core.Schemas namespace Squidex.Core.Schemas
{ {
@ -39,14 +38,9 @@ namespace Squidex.Core.Schemas
} }
} }
protected override object ConvertValue(PropertyValue property) protected override object ConvertValue(JToken value)
{
return property.ToNullableDouble(CultureInfo.InvariantCulture);
}
protected override Field Clone()
{ {
return new NumberField(Id, Name, Properties); return (double?)value;
} }
} }
} }

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

@ -11,19 +11,20 @@ using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using Squidex.Infrastructure; using Squidex.Infrastructure;
// ReSharper disable InvertIf // ReSharper disable InvertIf
namespace Squidex.Core.Schemas namespace Squidex.Core.Schemas
{ {
public sealed class Schema public sealed class Schema : Cloneable
{ {
private readonly string name; private readonly string name;
private readonly bool isPublished;
private readonly SchemaProperties properties; private readonly SchemaProperties properties;
private readonly ImmutableDictionary<long, Field> fieldsById; private readonly ImmutableDictionary<long, Field> fieldsById;
private readonly Dictionary<string, Field> fieldsByName; private readonly ImmutableDictionary<string, Field> fieldsByName;
private readonly bool isPublished;
public string Name public string Name
{ {
@ -45,21 +46,21 @@ namespace Squidex.Core.Schemas
get { return properties; } get { return properties; }
} }
public Schema(string name, SchemaProperties properties, bool isPublished, ImmutableDictionary<long, Field> fields) public Schema(string name, bool isPublished, SchemaProperties properties, ImmutableDictionary<long, Field> fields)
{ {
Guard.NotNull(fields, nameof(fields)); Guard.NotNull(fields, nameof(fields));
Guard.NotNull(properties, nameof(properties)); Guard.NotNull(properties, nameof(properties));
Guard.ValidSlug(name, nameof(name)); Guard.ValidSlug(name, nameof(name));
fieldsById = fields;
fieldsByName = fields.Values.ToImmutableDictionary(x => x.Name, StringComparer.OrdinalIgnoreCase);
this.name = name; this.name = name;
this.properties = properties; this.properties = properties;
this.isPublished = isPublished; this.properties.Freeze();
fieldsById = fields; this.isPublished = isPublished;
fieldsByName = fields.Values.ToDictionary(x => x.Name, StringComparer.OrdinalIgnoreCase);
properties.Freeze();
} }
public static Schema Create(string name, SchemaProperties newProperties) public static Schema Create(string name, SchemaProperties newProperties)
@ -71,26 +72,14 @@ namespace Squidex.Core.Schemas
throw new ValidationException("Cannot create a new schema", error); throw new ValidationException("Cannot create a new schema", error);
} }
return new Schema(name, newProperties, false, ImmutableDictionary<long, Field>.Empty); return new Schema(name, false, newProperties, ImmutableDictionary<long, Field>.Empty);
} }
public Schema Update(SchemaProperties newProperties) public Schema Update(SchemaProperties newProperties)
{ {
Guard.NotNull(newProperties, nameof(newProperties)); Guard.NotNull(newProperties, nameof(newProperties));
return new Schema(name, newProperties, isPublished, fieldsById); return new Schema(name, isPublished, newProperties, fieldsById);
}
public Schema AddOrUpdateField(Field field)
{
Guard.NotNull(field, nameof(field));
if (fieldsById.Values.Any(f => f.Name == field.Name && f.Id != field.Id))
{
throw new ValidationException($"A field with name '{field.Name}' already exists.");
}
return new Schema(name, properties, isPublished, fieldsById.SetItem(field.Id, field));
} }
public Schema UpdateField(long fieldId, FieldProperties newProperties) public Schema UpdateField(long fieldId, FieldProperties newProperties)
@ -125,7 +114,7 @@ namespace Squidex.Core.Schemas
public Schema DeleteField(long fieldId) public Schema DeleteField(long fieldId)
{ {
return new Schema(name, properties, isPublished, fieldsById.Remove(fieldId)); return new Schema(name, isPublished, properties, fieldsById.Remove(fieldId));
} }
public Schema Publish() public Schema Publish()
@ -135,7 +124,7 @@ namespace Squidex.Core.Schemas
throw new DomainException("Schema is already published"); throw new DomainException("Schema is already published");
} }
return new Schema(name, properties, true, fieldsById); return new Schema(name, true, properties, fieldsById);
} }
public Schema Unpublish() public Schema Unpublish()
@ -145,7 +134,19 @@ namespace Squidex.Core.Schemas
throw new DomainException("Schema is not published"); throw new DomainException("Schema is not published");
} }
return new Schema(name, properties, false, fieldsById); return new Schema(name, false, properties, fieldsById);
}
public Schema AddOrUpdateField(Field field)
{
Guard.NotNull(field, nameof(field));
if (fieldsById.Values.Any(f => f.Name == field.Name && f.Id != field.Id))
{
throw new ValidationException($"A field with name '{field.Name}' already exists.");
}
return new Schema(name, isPublished, properties, fieldsById.SetItem(field.Id, field));
} }
public Schema UpdateField(long fieldId, Func<Field, Field> updater) public Schema UpdateField(long fieldId, Func<Field, Field> updater)
@ -164,37 +165,100 @@ namespace Squidex.Core.Schemas
return AddOrUpdateField(newField); return AddOrUpdateField(newField);
} }
public async Task ValidateAsync(PropertiesBag data, IList<ValidationError> errors) public async Task ValidateAsync(JObject data, IList<ValidationError> errors, HashSet<Language> languages)
{ {
Guard.NotNull(data, nameof(data)); Guard.NotNull(data, nameof(data));
Guard.NotNull(errors, nameof(errors)); Guard.NotNull(errors, nameof(errors));
Guard.NotEmpty(languages, nameof(languages));
foreach (var kvp in data.Properties) AppendEmptyFields(data, languages);
foreach (var property in data.Properties())
{ {
var fieldErrors = new List<string>(); var fieldErrors = new List<string>();
Field field; Field field;
if (fieldsByName.TryGetValue(kvp.Key, out field)) if (fieldsByName.TryGetValue(property.Name, out field))
{
if (field.RawProperties.IsLocalizable)
{
var languageObject = property.Value as JObject;
if (languageObject == null)
{ {
await field.ValidateAsync(kvp.Value, fieldErrors); fieldErrors.Add($"{property.Name} is localizable and must be an object");
} }
else else
{ {
fieldErrors.Add($"{kvp.Key} is not a known field"); AppendEmptyLanguages(languageObject, languages);
foreach (var languageProperty in languageObject.Properties())
{
Language language;
if (!Language.TryGetLanguage(languageProperty.Name, out language))
{
fieldErrors.Add($"{property.Name} has an invalid language '{languageProperty.Name}'");
continue;
}
if (!languages.Contains(language))
{
fieldErrors.Add($"{property.Name} has an unsupported language '{languageProperty.Name}'");
continue;
}
await field.ValidateAsync(languageProperty.Value, fieldErrors, language);
}
}
}
else
{
await field.ValidateAsync(property.Value, fieldErrors);
}
}
else
{
fieldErrors.Add($"{property.Name} is not a known field");
} }
foreach (var error in fieldErrors) foreach (var error in fieldErrors)
{ {
errors.Add(new ValidationError(error, kvp.Key)); errors.Add(new ValidationError(error, property.Name));
}
}
}
private void AppendEmptyLanguages(JObject data, IEnumerable<Language> languages)
{
var nullJson = JValue.CreateNull();
foreach (var language in languages)
{
if (data.GetValue(language.Iso2Code, StringComparison.OrdinalIgnoreCase) == null)
{
data.Add(new JProperty(language.Iso2Code, nullJson));
}
} }
} }
private void AppendEmptyFields(JObject data, HashSet<Language> languages)
{
var nullJson = JValue.CreateNull();
foreach (var field in fieldsByName.Values) foreach (var field in fieldsByName.Values)
{ {
if (field.RawProperties.IsRequired && !data.Contains(field.Name)) if (data.GetValue(field.Name, StringComparison.OrdinalIgnoreCase) == null)
{ {
errors.Add(new ValidationError($"{field.Name} is required", field.Name)); JToken value = nullJson;
if (field.RawProperties.IsLocalizable)
{
value = new JObject(languages.Select(x => new JProperty(x.Iso2Code, nullJson)).OfType<object>().ToArray());
}
data.Add(new JProperty(field.Name, value));
} }
} }
} }

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

@ -8,8 +8,8 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Newtonsoft.Json.Linq;
using Squidex.Core.Schemas.Validators; using Squidex.Core.Schemas.Validators;
using Squidex.Infrastructure;
namespace Squidex.Core.Schemas namespace Squidex.Core.Schemas
{ {
@ -43,14 +43,9 @@ namespace Squidex.Core.Schemas
} }
} }
protected override object ConvertValue(PropertyValue property) protected override object ConvertValue(JToken value)
{ {
return property.ToString(); return value.ToString();
}
protected override Field Clone()
{
return new StringField(Id, Name, Properties);
} }
} }
} }

14
src/Squidex.Infrastructure/Language.cs

@ -54,6 +54,20 @@ namespace Squidex.Infrastructure
} }
} }
public static bool TryGetLanguage(string iso2Code, out Language language)
{
Guard.NotNullOrEmpty(iso2Code, nameof(iso2Code));
return allLanguages.TryGetValue(iso2Code, out language);
}
public static bool IsValidLanguage(string iso2Code)
{
Guard.NotNullOrEmpty(iso2Code, nameof(iso2Code));
return allLanguages.ContainsKey(iso2Code);
}
public static IEnumerable<Language> AllLanguages public static IEnumerable<Language> AllLanguages
{ {
get { return allLanguages.Values; } get { return allLanguages.Values; }

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

@ -9,7 +9,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using FluentAssertions; using FluentAssertions;
using Squidex.Infrastructure; using Newtonsoft.Json.Linq;
using Xunit; using Xunit;
namespace Squidex.Core.Schemas namespace Squidex.Core.Schemas
@ -35,7 +35,7 @@ namespace Squidex.Core.Schemas
} }
[Fact] [Fact]
public async Task Should_not_add_error_if_valid() public async Task Should_not_add_error_if_valid_null()
{ {
var sut = new BooleanField(1, "name", new BooleanFieldProperties { Label = "Name" }); var sut = new BooleanField(1, "name", new BooleanFieldProperties { Label = "Name" });
@ -44,6 +44,16 @@ namespace Squidex.Core.Schemas
Assert.Empty(errors); Assert.Empty(errors);
} }
[Fact]
public async Task Should_not_add_error_if_valid()
{
var sut = new BooleanField(1, "name", new BooleanFieldProperties { Label = "Name" });
await sut.ValidateAsync(CreateValue(true), errors);
Assert.Empty(errors);
}
[Fact] [Fact]
public async Task Should_add_errors_if_boolean_is_required() public async Task Should_add_errors_if_boolean_is_required()
{ {
@ -66,11 +76,9 @@ namespace Squidex.Core.Schemas
new[] { "Name is not a valid value" }); new[] { "Name is not a valid value" });
} }
private static PropertyValue CreateValue(object v) private static JValue CreateValue(object v)
{ {
var bag = new PropertiesBag().Set("value", v); return new JValue(v);
return bag["value"];
} }
} }
} }

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

@ -10,7 +10,7 @@ using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Threading.Tasks; using System.Threading.Tasks;
using FluentAssertions; using FluentAssertions;
using Squidex.Infrastructure; using Newtonsoft.Json.Linq;
using Xunit; using Xunit;
namespace Squidex.Core.Schemas namespace Squidex.Core.Schemas
@ -100,11 +100,9 @@ namespace Squidex.Core.Schemas
new[] { "Name is not a valid value" }); new[] { "Name is not a valid value" });
} }
private static PropertyValue CreateValue(object v) private static JValue CreateValue(object v)
{ {
var bag = new PropertiesBag().Set("value", v); return new JValue(v);
return bag["value"];
} }
} }
} }

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

@ -214,55 +214,6 @@ namespace Squidex.Core.Schemas
Assert.Throws<DomainObjectNotFoundException>(() => sut.UpdateField(1, new NumberFieldProperties())); Assert.Throws<DomainObjectNotFoundException>(() => sut.UpdateField(1, new NumberFieldProperties()));
} }
[Fact]
public async Task Should_add_error_if_validating_bag_with_unknown_field()
{
var errors = new List<ValidationError>();
var bag = new PropertiesBag().Set("unknown", 123);
await sut.ValidateAsync(bag, errors);
errors.ShouldBeEquivalentTo(
new List<ValidationError>
{
new ValidationError("unknown is not a known field", "unknown")
});
}
[Fact]
public async Task Should_add_error_if_validating_bag_with_invalid_field()
{
sut = sut.AddOrUpdateField(new NumberField(1, "my-field", new NumberFieldProperties { MaxValue = 100 }));
var errors = new List<ValidationError>();
var bag = new PropertiesBag().Set("my-field", 123);
await sut.ValidateAsync(bag, errors);
errors.ShouldBeEquivalentTo(
new List<ValidationError>
{
new ValidationError("my-field must be less than '100'", "my-field")
});
}
[Fact]
public async Task Should_add_error_if_required_field_is_not_in_bag()
{
sut = sut.AddOrUpdateField(new NumberField(1, "my-field", new NumberFieldProperties { IsRequired = true }));
var errors = new List<ValidationError>();
var bag = new PropertiesBag();
await sut.ValidateAsync(bag, errors);
errors.ShouldBeEquivalentTo(
new List<ValidationError>
{
new ValidationError("my-field is required", "my-field")
});
}
[Fact] [Fact]
public void Should_publish_schema() public void Should_publish_schema()
{ {

148
tests/Squidex.Core.Tests/Schemas/SchemaValidationTests.cs

@ -0,0 +1,148 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using FluentAssertions;
using Newtonsoft.Json.Linq;
using Squidex.Infrastructure;
using Xunit;
namespace Squidex.Core.Schemas
{
public class SchemaValidationTests
{
private readonly HashSet<Language> languages = new HashSet<Language>(new[] { Language.GetLanguage("de"), Language.GetLanguage("en") });
private readonly List<ValidationError> errors = new List<ValidationError>();
private Schema sut = Schema.Create("my-name", new SchemaProperties());
[Fact]
public async Task Should_add_error_if_validating_data_with_unknown_field()
{
var data =
new JObject(
new JProperty("unknown", 1));
await sut.ValidateAsync(data, errors, languages);
errors.ShouldBeEquivalentTo(
new List<ValidationError>
{
new ValidationError("unknown is not a known field", "unknown")
});
}
[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 }));
var data =
new JObject(
new JProperty("my-field", 1000));
await sut.ValidateAsync(data, errors, languages);
errors.ShouldBeEquivalentTo(
new List<ValidationError>
{
new ValidationError("my-field must be less than '100'", "my-field")
});
}
[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 }));
var data =
new JObject();
await sut.ValidateAsync(data, errors, languages);
errors.ShouldBeEquivalentTo(
new List<ValidationError>
{
new ValidationError("my-field (de) is required", "my-field"),
new ValidationError("my-field (en) is required", "my-field")
});
}
[Fact]
public async Task Should_add_error_if_required_field_is_not_in_bag()
{
sut = sut.AddOrUpdateField(new NumberField(1, "my-field", new NumberFieldProperties { IsRequired = true }));
var data =
new JObject();
await sut.ValidateAsync(data, errors, languages);
errors.ShouldBeEquivalentTo(
new List<ValidationError>
{
new ValidationError("my-field is required", "my-field")
});
}
[Fact]
public async Task Should_add_error_if_value_is_not_object_for_localizable_field()
{
sut = sut.AddOrUpdateField(new NumberField(1, "my-field", new NumberFieldProperties { IsLocalizable = true }));
var data =
new JObject(
new JProperty("my-field", 1));
await sut.ValidateAsync(data, errors, languages);
errors.ShouldBeEquivalentTo(
new List<ValidationError>
{
new ValidationError("my-field is localizable and must be an object", "my-field")
});
}
[Fact]
public async Task Should_add_error_if_value_contains_invalid_language()
{
sut = sut.AddOrUpdateField(new NumberField(1, "my-field", new NumberFieldProperties { IsLocalizable = true }));
var data =
new JObject(
new JProperty("my-field",
new JObject(
new JProperty("de", 1),
new JProperty("xx", 1))));
await sut.ValidateAsync(data, errors, languages);
errors.ShouldBeEquivalentTo(
new List<ValidationError>
{
new ValidationError("my-field has an invalid language 'xx'", "my-field")
});
}
[Fact]
public async Task Should_add_error_if_value_contains_unsupported_language()
{
sut = sut.AddOrUpdateField(new NumberField(1, "my-field", new NumberFieldProperties { IsLocalizable = true }));
var data =
new JObject(
new JProperty("my-field",
new JObject(
new JProperty("es", 1),
new JProperty("it", 1))));
await sut.ValidateAsync(data, errors, languages);
errors.ShouldBeEquivalentTo(
new List<ValidationError>
{
new ValidationError("my-field has an unsupported language 'es'", "my-field"),
new ValidationError("my-field has an unsupported language 'it'", "my-field"),
});
}
}
}

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

@ -10,7 +10,7 @@ using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Threading.Tasks; using System.Threading.Tasks;
using FluentAssertions; using FluentAssertions;
using Squidex.Infrastructure; using Newtonsoft.Json.Linq;
using Xunit; using Xunit;
namespace Squidex.Core.Schemas namespace Squidex.Core.Schemas
@ -111,11 +111,9 @@ namespace Squidex.Core.Schemas
new[] { "Custom Error Message" }); new[] { "Custom Error Message" });
} }
private static PropertyValue CreateValue(object v) private static JValue CreateValue(object v)
{ {
var bag = new PropertiesBag().Set("value", v); return new JValue(v);
return bag["value"];
} }
} }
} }

16
tests/Squidex.Infrastructure.Tests/CQRS/Commands/CommandContextTests.cs

@ -45,6 +45,22 @@ namespace Squidex.Infrastructure.CQRS.Commands
Assert.False(sut.IsSucceeded); Assert.False(sut.IsSucceeded);
} }
[Fact]
public void Should_not_update_exception_when_failed()
{
var exc1 = new InvalidOperationException();
var exc2 = new InvalidOperationException();
var sut = new CommandContext(command);
sut.Fail(exc1);
sut.Fail(exc2);
Assert.Equal(exc1, sut.Exception);
Assert.True(sut.IsHandled);
Assert.True(sut.IsFailed);
Assert.False(sut.IsSucceeded);
}
[Fact] [Fact]
public void Should_be_handled_when_succeeded() public void Should_be_handled_when_succeeded()
{ {

74
tests/Squidex.Infrastructure.Tests/CQRS/Commands/LogExceptionHandlerTests.cs

@ -0,0 +1,74 @@
// ==========================================================================
// LogExceptionHandlerTests.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Xunit;
namespace Squidex.Infrastructure.CQRS.Commands
{
public class LogExceptionHandlerTests
{
private readonly MyLogger logger = new MyLogger();
private readonly LogExceptionHandler sut;
private sealed class MyLogger : ILogger<LogExceptionHandler>
{
public int LogCount { get; private set; }
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatterr)
{
LogCount++;
}
public bool IsEnabled(LogLevel logLevel)
{
return false;
}
public IDisposable BeginScope<TState>(TState state)
{
return null;
}
}
private sealed class MyCommand : ICommand
{
}
public LogExceptionHandlerTests()
{
sut = new LogExceptionHandler(logger);
}
[Fact]
public async Task Should_do_nothing_if_context_has_no_exception()
{
var context = new CommandContext(new MyCommand());
var isHandled = await sut.HandleAsync(context);
Assert.False(isHandled);
Assert.Equal(0, logger.LogCount);
}
[Fact]
public async Task Should_log_if_context_has_exception2()
{
var context = new CommandContext(new MyCommand());
context.Fail(new InvalidOperationException());
var isHandled = await sut.HandleAsync(context);
Assert.False(isHandled);
Assert.Equal(1, logger.LogCount);
}
}
}

61
tests/Squidex.Infrastructure.Tests/CQRS/Commands/LogExecutingHandlerTests.cs

@ -0,0 +1,61 @@
// ==========================================================================
// LogExecutingHandlerTests.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Xunit;
namespace Squidex.Infrastructure.CQRS.Commands
{
public class LogExecutingHandlerTests
{
private readonly MyLogger logger = new MyLogger();
private readonly LogExecutingHandler sut;
private sealed class MyLogger : ILogger<LogExecutingHandler>
{
public int LogCount { get; private set; }
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatterr)
{
LogCount++;
}
public bool IsEnabled(LogLevel logLevel)
{
return false;
}
public IDisposable BeginScope<TState>(TState state)
{
return null;
}
}
private sealed class MyCommand : ICommand
{
}
public LogExecutingHandlerTests()
{
sut = new LogExecutingHandler(logger);
}
[Fact]
public async Task Should_log_once()
{
var context = new CommandContext(new MyCommand());
var isHandled = await sut.HandleAsync(context);
Assert.False(isHandled);
Assert.Equal(1, logger.LogCount);
}
}
}

82
tests/Squidex.Infrastructure.Tests/CQRS/Replay/ReplayGeneratorTests.cs

@ -0,0 +1,82 @@
// ==========================================================================
// ReplayGeneratorTests.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Reactive.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Moq;
using Squidex.Infrastructure.CQRS.Events;
using Xunit;
namespace Squidex.Infrastructure.CQRS.Replay
{
public class ReplayGeneratorTests
{
private readonly Mock<IEventStore> eventStore = new Mock<IEventStore>();
private readonly Mock<IEventPublisher> eventPublisher = new Mock<IEventPublisher>();
private readonly Mock<IReplayableStore> store1 = new Mock<IReplayableStore>();
private readonly Mock<IReplayableStore> store2 = new Mock<IReplayableStore>();
private readonly Mock<ILogger<ReplayGenerator>> logger = new Mock<ILogger<ReplayGenerator>>();
private readonly ReplayGenerator sut;
public ReplayGeneratorTests()
{
sut = new ReplayGenerator(logger.Object, eventStore.Object, eventPublisher.Object, new[] { store1.Object, store2.Object });
}
[Fact]
public async Task Should_clear_stores_and_replay_events()
{
var event1 = new EventData();
var event2 = new EventData();
var event3 = new EventData();
eventStore.Setup(x => x.GetEventsAsync()).Returns(new[] { event1, event2, event3 }.ToObservable());
await sut.ReplayAllAsync();
store1.Verify(x => x.ClearAsync(), Times.Once());
store2.Verify(x => x.ClearAsync(), Times.Once());
eventPublisher.Verify(x => x.Publish(event1));
eventPublisher.Verify(x => x.Publish(event2));
eventPublisher.Verify(x => x.Publish(event3));
}
[Fact]
public async Task Should_not_publish_if_clearing_failed()
{
var event1 = new EventData();
var event2 = new EventData();
var event3 = new EventData();
store1.Setup(x => x.ClearAsync()).Throws(new InvalidOperationException());
eventStore.Setup(x => x.GetEventsAsync()).Returns(new[] { event1, event2, event3 }.ToObservable());
await sut.ReplayAllAsync();
store1.Verify(x => x.ClearAsync(), Times.Once());
store2.Verify(x => x.ClearAsync(), Times.Never());
eventStore.Verify(x => x.GetEventsAsync(), Times.Never());
eventPublisher.Verify(x => x.Publish(It.IsAny<EventData>()), Times.Never());
}
[Fact]
public async Task Should_not_throw_if_process_throws()
{
eventStore.Setup(x => x.GetEventsAsync()).Throws(new InvalidOperationException());
await sut.ReplayAllAsync();
eventPublisher.Verify(x => x.Publish(It.IsAny<EventData>()), Times.Never());
}
}
}
Loading…
Cancel
Save