diff --git a/Squidex.sln b/Squidex.sln index dbbd29dfb..82903287a 100644 --- a/Squidex.sln +++ b/Squidex.sln @@ -34,6 +34,8 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Squidex.Write.Tests", "test EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Squidex.Infrastructure.Tests", "tests\Squidex.Infrastructure.Tests\Squidex.Infrastructure.Tests.xproj", "{7FD0A92B-7862-4BB1-932B-B52A9CACB56B}" EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Squidex.Core.Tests", "tests\Squidex.Core.Tests\Squidex.Core.Tests.xproj", "{FD0AFD44-7A93-4F9E-B5ED-72582392E435}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -76,6 +78,10 @@ Global {7FD0A92B-7862-4BB1-932B-B52A9CACB56B}.Debug|Any CPU.Build.0 = Debug|Any CPU {7FD0A92B-7862-4BB1-932B-B52A9CACB56B}.Release|Any CPU.ActiveCfg = Release|Any CPU {7FD0A92B-7862-4BB1-932B-B52A9CACB56B}.Release|Any CPU.Build.0 = Release|Any CPU + {FD0AFD44-7A93-4F9E-B5ED-72582392E435}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FD0AFD44-7A93-4F9E-B5ED-72582392E435}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FD0AFD44-7A93-4F9E-B5ED-72582392E435}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FD0AFD44-7A93-4F9E-B5ED-72582392E435}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -90,5 +96,6 @@ Global {28F8E9E2-FE24-41F7-A888-9FC244A9E2DD} = {4082AA58-D410-4C52-8E88-3B0A4E860B28} {9A3DEA7E-1681-4D48-AC5C-1F0DE421A203} = {4C6B06C2-6D77-4E0E-AE32-D7050236433A} {7FD0A92B-7862-4BB1-932B-B52A9CACB56B} = {8CF53B92-5EB1-461D-98F8-70DA9B603FBF} + {FD0AFD44-7A93-4F9E-B5ED-72582392E435} = {4C6B06C2-6D77-4E0E-AE32-D7050236433A} EndGlobalSection EndGlobal diff --git a/src/Squidex.Core/Schemas/Field.cs b/src/Squidex.Core/Schemas/Field.cs index 29083c4ef..1a2d93ac5 100644 --- a/src/Squidex.Core/Schemas/Field.cs +++ b/src/Squidex.Core/Schemas/Field.cs @@ -10,8 +10,6 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; using Squidex.Infrastructure; -using Squidex.Infrastructure.Tasks; - // ReSharper disable InvertIf // ReSharper disable ConvertIfStatementToReturnStatement @@ -19,6 +17,7 @@ namespace Squidex.Core.Schemas { public abstract class Field { + private readonly Lazy> validators; private readonly long id; private string name; private bool isDisabled; @@ -34,16 +33,6 @@ namespace Squidex.Core.Schemas get { return name; } } - public string Hints - { - get { return RawProperties.Hints; } - } - - public string Label - { - get { return RawProperties.Label; } - } - public bool IsHidden { get { return isHidden; } @@ -54,46 +43,42 @@ namespace Squidex.Core.Schemas get { return isDisabled; } } - public bool IsRequired - { - get { return RawProperties.IsRequired; } - } - - public abstract FieldProperties RawProperties { get; } - protected Field(long id, string name) { - Guard.GreaterThan(id, 0, nameof(id)); Guard.ValidSlug(name, nameof(name)); + Guard.GreaterThan(id, 0, nameof(id)); this.id = id; this.name = name; + + validators = new Lazy>(() => new List(CreateValidators())); } public abstract Field Update(FieldProperties newProperties); - public Task ValidateAsync(PropertyValue property, ICollection errors) + public async Task ValidateAsync(PropertyValue property, ICollection errors) { Guard.NotNull(property, nameof(property)); - - if (IsRequired && property.RawValue == null) + + var value = ConvertValue(property); + + var tempErrors = new List(); + + foreach (var validator in validators.Value) { - errors.Add("Field is required"); + await validator.ValidateAsync(value, tempErrors); } - if (property.RawValue == null) + foreach (var error in tempErrors) { - return TaskHelper.Done; + errors.Add(error.Replace("", name)); } - - return ValidateCoreAsync(property, errors); } - protected virtual Task ValidateCoreAsync(PropertyValue property, ICollection errors) - { - return TaskHelper.Done; - } + protected abstract IEnumerable CreateValidators(); + + protected abstract object ConvertValue(PropertyValue property); public Field Hide() { @@ -136,6 +121,8 @@ namespace Squidex.Core.Schemas return clone; } - protected abstract Field Clone(); + public abstract Field Clone(); + + public abstract FieldProperties CloneProperties(); } } \ No newline at end of file diff --git a/src/Squidex.Core/Schemas/FieldProperties.cs b/src/Squidex.Core/Schemas/FieldProperties.cs index d08c170d7..18b8be097 100644 --- a/src/Squidex.Core/Schemas/FieldProperties.cs +++ b/src/Squidex.Core/Schemas/FieldProperties.cs @@ -13,21 +13,24 @@ namespace Squidex.Core.Schemas { public abstract class FieldProperties : NamedElementProperties, IValidatable { - public bool IsRequired { get; } + public bool IsRequired { get; set; } - protected FieldProperties(string label, string hints, bool isRequired) - : base(label, hints) + public void Validate(IList errors) { - IsRequired = isRequired; + foreach (var error in ValidateCore()) + { + errors.Add(error); + } } - public void Validate(IList errors) + protected virtual IEnumerable ValidateCore() { - ValidateCore(errors); + yield break; } - protected virtual void ValidateCore(IList errors) + public FieldProperties Clone() { + return (FieldProperties) MemberwiseClone(); } } } \ No newline at end of file diff --git a/src/Squidex.Core/Schemas/Field_Generic.cs b/src/Squidex.Core/Schemas/Field_Generic.cs index eaae6673f..a2a7e36e2 100644 --- a/src/Squidex.Core/Schemas/Field_Generic.cs +++ b/src/Squidex.Core/Schemas/Field_Generic.cs @@ -15,12 +15,7 @@ namespace Squidex.Core.Schemas { private T properties; - public override FieldProperties RawProperties - { - get { return properties; } - } - - public T Properties + protected T Properties { get { return properties; } } @@ -36,7 +31,9 @@ namespace Squidex.Core.Schemas public override Field Update(FieldProperties newProperties) { Guard.NotNull(newProperties, nameof(newProperties)); - + + newProperties = newProperties.Clone(); + var typedProperties = newProperties as T; if (typedProperties == null) @@ -44,9 +41,14 @@ namespace Squidex.Core.Schemas throw new ArgumentException($"Properties must be of type '{typeof(T)}", nameof(newProperties)); } - newProperties.Validate(() => $"Cannot update field with id '{Id}', becase the settings are invalid."); + newProperties.Validate(() => $"Cannot update field with id '{Id}', because the settings are invalid."); return Update>(clone => clone.properties = typedProperties); } + + public override FieldProperties CloneProperties() + { + return properties.Clone(); + } } } diff --git a/src/Squidex.Core/Schemas/IValidator.cs b/src/Squidex.Core/Schemas/IValidator.cs new file mode 100644 index 000000000..405c0bd01 --- /dev/null +++ b/src/Squidex.Core/Schemas/IValidator.cs @@ -0,0 +1,18 @@ +// ========================================================================== +// IValidator.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Squidex.Core.Schemas +{ + public interface IValidator + { + Task ValidateAsync(object value, ICollection errors); + } +} diff --git a/src/Squidex.Core/Schemas/NamedElementProperties.cs b/src/Squidex.Core/Schemas/NamedElementProperties.cs index 14322aabb..9b6308939 100644 --- a/src/Squidex.Core/Schemas/NamedElementProperties.cs +++ b/src/Squidex.Core/Schemas/NamedElementProperties.cs @@ -5,27 +5,13 @@ // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== + namespace Squidex.Core.Schemas { public abstract class NamedElementProperties { - private readonly string label; - private readonly string hints; - - public string Label - { - get { return label; } - } - - public string Hints - { - get { return hints; } - } + public string Label { get; set; } - protected NamedElementProperties(string label, string hints) - { - this.label = label; - this.hints = hints; - } + public string Hints { get; set; } } } \ No newline at end of file diff --git a/src/Squidex.Core/Schemas/NumberField.cs b/src/Squidex.Core/Schemas/NumberField.cs index 94190dcd2..e01b74ac3 100644 --- a/src/Squidex.Core/Schemas/NumberField.cs +++ b/src/Squidex.Core/Schemas/NumberField.cs @@ -6,70 +6,47 @@ // All rights reserved. // ========================================================================== -using System; using System.Collections.Generic; using System.Globalization; -using System.Linq; -using System.Threading.Tasks; +using Squidex.Core.Schemas.Validators; using Squidex.Infrastructure; -using Squidex.Infrastructure.Tasks; +using System.Linq; namespace Squidex.Core.Schemas { public sealed class NumberField : Field { - public double? MaxValue - { - get { return Properties.MaxValue; } - } - - public double? MinValue - { - get { return Properties.MinValue; } - } - - public double[] AllowedValues - { - get { return Properties.AllowedValues; } - } - public NumberField(long id, string name, NumberFieldProperties properties) : base(id, name, properties) { } - protected override Task ValidateCoreAsync(PropertyValue property, ICollection errors) + protected override IEnumerable CreateValidators() { - try + if (Properties.IsRequired) { - var value = property.ToDouble(CultureInfo.InvariantCulture); - - if (MinValue.HasValue && value < MinValue.Value) - { - errors.Add($"Must be greater than {MinValue}"); - } - - if (MaxValue.HasValue && value > MaxValue.Value) - { - errors.Add($"Must be less than {MaxValue}"); - } + yield return new RequiredValidator(); + } - if (AllowedValues != null && !AllowedValues.Contains(value)) - { - errors.Add($"Can only be one of the following value: {string.Join(", ", AllowedValues)}"); - } + if (Properties.MinValue.HasValue || Properties.MaxValue.HasValue) + { + yield return new RangeValidator(Properties.MinValue, Properties.MaxValue); } - catch (InvalidCastException) + + if (Properties.AllowedValues != null) { - errors.Add("Value is not a valid number"); + yield return new AllowedValuesValidator(Properties.AllowedValues.ToArray()); } + } - return TaskHelper.Done; + protected override object ConvertValue(PropertyValue property) + { + return property.ToNullableDouble(CultureInfo.InvariantCulture); } - protected override Field Clone() + public override Field Clone() { - return (Field)MemberwiseClone(); + return new NumberField(Id, Name, Properties); } } } diff --git a/src/Squidex.Core/Schemas/NumberFieldProperties.cs b/src/Squidex.Core/Schemas/NumberFieldProperties.cs index d4df9b991..1d8e75091 100644 --- a/src/Squidex.Core/Schemas/NumberFieldProperties.cs +++ b/src/Squidex.Core/Schemas/NumberFieldProperties.cs @@ -7,6 +7,7 @@ // ========================================================================== using System.Collections.Generic; +using System.Collections.Immutable; using Squidex.Infrastructure; namespace Squidex.Core.Schemas @@ -16,55 +17,42 @@ namespace Squidex.Core.Schemas { public string Placeholder { get; set; } - public double? DefaultValue { get; } + public double? MaxValue { get; set; } - public double? MaxValue { get; } + public double? MinValue { get; set; } - public double? MinValue { get; } + public double? DefaultValue { get; set; } - public double[] AllowedValues { get; } + public ImmutableList AllowedValues { get; set; } - public NumberFieldProperties( - string label, - string hints, - string placeholder, - double? minValue, - double? maxValue, - double? defaultValue, - double[] allowedValues, - bool isRequired) - : base(label, hints, isRequired) - { - Placeholder = placeholder; - - MinValue = minValue; - MaxValue = maxValue; - - AllowedValues = allowedValues; - - DefaultValue = defaultValue; - } - - protected override void ValidateCore(IList errors) + protected override IEnumerable ValidateCore() { if (MaxValue.HasValue && MinValue.HasValue && MinValue.Value >= MaxValue.Value) { - errors.Add(new ValidationError("MinValue cannot be larger than max value", "MinValue", "MaxValue")); + yield return new ValidationError("Max value must be greater than min value.", nameof(MinValue), nameof(MaxValue)); + } + + if (AllowedValues != null && (MinValue.HasValue || MaxValue.HasValue)) + { + yield return new ValidationError("Either or allowed values or range can be defined.", + nameof(AllowedValues), + nameof(MinValue), + nameof(MaxValue)); } if (!DefaultValue.HasValue) { - return; + yield break; } if (MinValue.HasValue && DefaultValue.Value < MinValue.Value) { - errors.Add(new ValidationError("DefaultValue must be larger than the min value.", "DefaultValue")); + yield return new ValidationError("Default value must be greater than min value.", nameof(DefaultValue)); } if (MaxValue.HasValue && DefaultValue.Value > MaxValue.Value) { - errors.Add(new ValidationError("DefaultValue must be smaller than the max value.", "DefaultValue")); + yield return new ValidationError("Default value must be less than max value.", nameof(DefaultValue)); } } } diff --git a/src/Squidex.Core/Schemas/SchemaProperties.cs b/src/Squidex.Core/Schemas/SchemaProperties.cs index 561008f2c..fa76ffbe6 100644 --- a/src/Squidex.Core/Schemas/SchemaProperties.cs +++ b/src/Squidex.Core/Schemas/SchemaProperties.cs @@ -9,11 +9,5 @@ namespace Squidex.Core.Schemas { public sealed class SchemaProperties : NamedElementProperties { - public SchemaProperties( - string label, - string hints) - : base(label, hints) - { - } } } \ No newline at end of file diff --git a/src/Squidex.Core/Schemas/Validators/AllowedValuesValidator.cs b/src/Squidex.Core/Schemas/Validators/AllowedValuesValidator.cs new file mode 100644 index 000000000..1179d56da --- /dev/null +++ b/src/Squidex.Core/Schemas/Validators/AllowedValuesValidator.cs @@ -0,0 +1,45 @@ +// ========================================================================== +// AllowedValuesValidator.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Squidex.Infrastructure; +using Squidex.Infrastructure.Tasks; + +namespace Squidex.Core.Schemas.Validators +{ + public sealed class AllowedValuesValidator : IValidator + { + private readonly T[] allowedValues; + + public AllowedValuesValidator(params T[] allowedValues) + { + Guard.NotNull(allowedValues, nameof(allowedValues)); + + this.allowedValues = allowedValues; + } + + public Task ValidateAsync(object value, ICollection errors) + { + if (value == null) + { + return TaskHelper.Done; + } + + var typedValue = (T)value; + + if (!allowedValues.Contains(typedValue)) + { + errors.Add(" is not an allowed value"); + } + + return TaskHelper.Done; + } + } +} \ No newline at end of file diff --git a/src/Squidex.Core/Schemas/Validators/RangeValidator.cs b/src/Squidex.Core/Schemas/Validators/RangeValidator.cs new file mode 100644 index 000000000..8335525a5 --- /dev/null +++ b/src/Squidex.Core/Schemas/Validators/RangeValidator.cs @@ -0,0 +1,54 @@ +// ========================================================================== +// RangeValidator.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Squidex.Infrastructure.Tasks; + +namespace Squidex.Core.Schemas.Validators +{ + public sealed class RangeValidator : IValidator where T : struct, IComparable + { + private readonly T? min; + private readonly T? max; + + public RangeValidator(T? min, T? max) + { + if (min.HasValue && max.HasValue && min.Value.CompareTo(max.Value) >= 0) + { + throw new ArgumentException("MinValue must larger than max value"); + } + + this.min = min; + this.max = max; + } + + public Task ValidateAsync(object value, ICollection errors) + { + if (value == null) + { + return TaskHelper.Done; + } + + var typedValue = (T)value; + + if (min.HasValue && typedValue.CompareTo(min.Value) < 0) + { + errors.Add($" must be greater than {min}"); + } + + if (max.HasValue && typedValue.CompareTo(max.Value) > 0) + { + errors.Add($" must be greater than {min}"); + } + + return TaskHelper.Done; + } + } +} \ No newline at end of file diff --git a/src/Squidex.Core/Schemas/Validators/RequiredStringValidator.cs b/src/Squidex.Core/Schemas/Validators/RequiredStringValidator.cs new file mode 100644 index 000000000..ca3941f2a --- /dev/null +++ b/src/Squidex.Core/Schemas/Validators/RequiredStringValidator.cs @@ -0,0 +1,41 @@ +// ========================================================================== +// RequiredStringValidator.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System.Collections.Generic; +using System.Threading.Tasks; +using Squidex.Infrastructure.Tasks; + +namespace Squidex.Core.Schemas.Validators +{ + public class RequiredStringValidator : IValidator + { + private readonly bool validateEmptyStrings; + + public RequiredStringValidator(bool validateEmptyStrings = false) + { + this.validateEmptyStrings = validateEmptyStrings; + } + + public Task ValidateAsync(object value, ICollection errors) + { + if (value != null && !(value is string)) + { + return TaskHelper.Done; + } + + var valueAsString = (string) value; + + if (valueAsString == null || (validateEmptyStrings && string.IsNullOrWhiteSpace(valueAsString))) + { + errors.Add(" is required"); + } + + return TaskHelper.Done; + } + } +} diff --git a/src/Squidex.Core/Schemas/Validators/RequiredValidator.cs b/src/Squidex.Core/Schemas/Validators/RequiredValidator.cs new file mode 100644 index 000000000..1938726d9 --- /dev/null +++ b/src/Squidex.Core/Schemas/Validators/RequiredValidator.cs @@ -0,0 +1,27 @@ +// ========================================================================== +// RequiredValidator.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System.Collections.Generic; +using System.Threading.Tasks; +using Squidex.Infrastructure.Tasks; + +namespace Squidex.Core.Schemas.Validators +{ + public class RequiredValidator : IValidator + { + public Task ValidateAsync(object value, ICollection errors) + { + if (value == null) + { + errors.Add(" is required"); + } + + return TaskHelper.Done; + } + } +} diff --git a/src/Squidex.Infrastructure/CQRS/EventStore/DefaultNameResolver.cs b/src/Squidex.Infrastructure/CQRS/EventStore/DefaultNameResolver.cs index 9f832bca4..90338f188 100644 --- a/src/Squidex.Infrastructure/CQRS/EventStore/DefaultNameResolver.cs +++ b/src/Squidex.Infrastructure/CQRS/EventStore/DefaultNameResolver.cs @@ -20,7 +20,7 @@ namespace Squidex.Infrastructure.CQRS.EventStore { Guard.NotNullOrEmpty(prefix, nameof(prefix)); - this.prefix = prefix; + this.prefix = prefix.ToLowerInvariant(); } public string GetStreamName(Type aggregateType, Guid id) diff --git a/src/Squidex.Infrastructure/CQRS/EventStore/EventStoreBus.cs b/src/Squidex.Infrastructure/CQRS/EventStore/EventStoreBus.cs index 87e03b92b..9dcf4dafd 100644 --- a/src/Squidex.Infrastructure/CQRS/EventStore/EventStoreBus.cs +++ b/src/Squidex.Infrastructure/CQRS/EventStore/EventStoreBus.cs @@ -95,7 +95,7 @@ namespace Squidex.Infrastructure.CQRS.EventStore return; } - var @event = formatter.Parse(resolvedEvent); + var @event = formatter.Parse(new EventWrapper(resolvedEvent)); logger.LogInformation("Received event {0} ({1})", @event.Payload.GetType().Name, @event.Headers.AggregateId()); diff --git a/src/Squidex.Infrastructure/CQRS/EventStore/EventStoreDomainObjectRepository.cs b/src/Squidex.Infrastructure/CQRS/EventStore/EventStoreDomainObjectRepository.cs index 570c94761..a8306f0f1 100644 --- a/src/Squidex.Infrastructure/CQRS/EventStore/EventStoreDomainObjectRepository.cs +++ b/src/Squidex.Infrastructure/CQRS/EventStore/EventStoreDomainObjectRepository.cs @@ -83,7 +83,7 @@ namespace Squidex.Infrastructure.CQRS.EventStore foreach (var resolved in currentSlice.Events) { - var envelope = formatter.Parse(resolved); + var envelope = formatter.Parse(new EventWrapper(resolved)); domainObject.ApplyEvent(envelope); } diff --git a/src/Squidex.Infrastructure/CQRS/EventStore/EventStoreFormatter.cs b/src/Squidex.Infrastructure/CQRS/EventStore/EventStoreFormatter.cs index 843ba3315..e94e1911b 100644 --- a/src/Squidex.Infrastructure/CQRS/EventStore/EventStoreFormatter.cs +++ b/src/Squidex.Infrastructure/CQRS/EventStore/EventStoreFormatter.cs @@ -26,17 +26,17 @@ namespace Squidex.Infrastructure.CQRS.EventStore this.serializerSettings = serializerSettings ?? new JsonSerializerSettings(); } - public Envelope Parse(ResolvedEvent @event) + public Envelope Parse(IReceivedEvent @event) { - var headers = ReadJson(@event.Event.Metadata); + var headers = ReadJson(@event.Metadata); - var eventType = TypeNameRegistry.GetType(@event.Event.EventType); - var eventData = ReadJson(@event.Event.Data, eventType); + var eventType = TypeNameRegistry.GetType(@event.EventType); + var eventData = ReadJson(@event.Payload, eventType); var envelope = new Envelope(eventData, headers); - envelope.Headers.Set(CommonHeaders.Timestamp, Instant.FromDateTimeUtc(DateTime.SpecifyKind(@event.Event.Created, DateTimeKind.Utc))); - envelope.Headers.Set(CommonHeaders.EventNumber, @event.OriginalEventNumber); + envelope.Headers.Set(CommonHeaders.Timestamp, Instant.FromDateTimeUtc(DateTime.SpecifyKind(@event.Created, DateTimeKind.Utc))); + envelope.Headers.Set(CommonHeaders.EventNumber, @event.EventNumber); return envelope; } diff --git a/src/Squidex.Infrastructure/CQRS/EventStore/EventWrapper.cs b/src/Squidex.Infrastructure/CQRS/EventStore/EventWrapper.cs new file mode 100644 index 000000000..561e63819 --- /dev/null +++ b/src/Squidex.Infrastructure/CQRS/EventStore/EventWrapper.cs @@ -0,0 +1,48 @@ +// ========================================================================== +// EventWrapper.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using EventStore.ClientAPI; + +namespace Squidex.Infrastructure.CQRS.EventStore +{ + internal sealed class EventWrapper : IReceivedEvent + { + private readonly ResolvedEvent @event; + + public int EventNumber + { + get { return @event.OriginalEventNumber; } + } + + public string EventType + { + get { return @event.Event.EventType; } + } + + public byte[] Metadata + { + get { return @event.Event.Metadata; } + } + + public byte[] Payload + { + get { return @event.Event.Data; } + } + + public DateTime Created + { + get { return @event.Event.Created; } + } + + public EventWrapper(ResolvedEvent @event) + { + this.@event = @event; + } + } +} diff --git a/src/Squidex.Infrastructure/CQRS/EventStore/IReceivedEvent.cs b/src/Squidex.Infrastructure/CQRS/EventStore/IReceivedEvent.cs new file mode 100644 index 000000000..d7ea30e76 --- /dev/null +++ b/src/Squidex.Infrastructure/CQRS/EventStore/IReceivedEvent.cs @@ -0,0 +1,25 @@ +// ========================================================================== +// IReceivedEvent.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; + +namespace Squidex.Infrastructure.CQRS.EventStore +{ + public interface IReceivedEvent + { + int EventNumber { get; } + + string EventType { get; } + + byte[] Metadata { get; } + + byte[] Payload { get; } + + DateTime Created { get; } + } +} diff --git a/src/Squidex.Infrastructure/Json/LanguageConverter.cs b/src/Squidex.Infrastructure/Json/LanguageConverter.cs index ddc9624fd..7e022eb79 100644 --- a/src/Squidex.Infrastructure/Json/LanguageConverter.cs +++ b/src/Squidex.Infrastructure/Json/LanguageConverter.cs @@ -15,16 +15,7 @@ namespace Squidex.Infrastructure.Json { public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { - var language = value as Language; - - if (language != null) - { - writer.WriteValue(language.Iso2Code); - } - else - { - writer.WriteNull(); - } + writer.WriteValue(((Language)value).Iso2Code); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) diff --git a/src/Squidex/app/framework/angular/animations.ts b/src/Squidex/app/framework/angular/animations.ts index 001363375..4887b7f47 100644 --- a/src/Squidex/app/framework/angular/animations.ts +++ b/src/Squidex/app/framework/angular/animations.ts @@ -53,5 +53,5 @@ function buildHeightAnimation(name = 'height', timing = '200ms'): Ng2.AnimationE ); }; -export const fadeAnimation = buildfadeAnimation; +export const fadeAnimation = buildFadeAnimation(); \ No newline at end of file diff --git a/src/Squidex/app/theme/_vendor-overrides.scss b/src/Squidex/app/theme/_vendor-overrides.scss index 9d7519c33..fd660dec7 100644 --- a/src/Squidex/app/theme/_vendor-overrides.scss +++ b/src/Squidex/app/theme/_vendor-overrides.scss @@ -1,8 +1,6 @@ @import '_mixins.scss'; @import '_vars.scss'; -$fa-font-path: '~font-awesome/fonts'; - $brand-primary: $color-theme-blue; $brand-success: $color-theme-green; diff --git a/src/Squidex/app/theme/vendor.scss b/src/Squidex/app/theme/vendor.scss index 9b4a8924d..36bc5e9e8 100644 --- a/src/Squidex/app/theme/vendor.scss +++ b/src/Squidex/app/theme/vendor.scss @@ -1,8 +1,5 @@ @import '_vendor-overrides.scss'; -// Font Awesome -@import './../../node_modules/font-awesome/scss/font-awesome.scss'; - // Bootstrap @import './../../node_modules/bootstrap/scss/bootstrap-flex.scss'; diff --git a/src/Squidex/package.json b/src/Squidex/package.json index c75e01225..ca3da94a8 100644 --- a/src/Squidex/package.json +++ b/src/Squidex/package.json @@ -25,7 +25,6 @@ "babel-polyfill": "^6.16.0", "bootstrap": "^4.0.0-alpha.2", "core-js": "^2.4.1", - "font-awesome": "^4.7.0", "immutable": "^3.8.1", "moment": "^2.17.0", "mousetrap": "^1.6.0", @@ -49,7 +48,6 @@ "exports-loader": "^0.6.3", "extract-text-webpack-plugin": "^2.0.0-beta.4", "file-loader": "^0.9.0", - "font-awesome-sass-loader": "^1.0.2", "html-loader": "^0.4.4", "html-webpack-plugin": "^2.24.1", "image-webpack-loader": "^3.0.0", diff --git a/tests/RunCoverage.bat b/tests/RunCoverage.bat index 237f8d55f..49e385228 100644 --- a/tests/RunCoverage.bat +++ b/tests/RunCoverage.bat @@ -25,6 +25,16 @@ exit /b %errorlevel% -output:"%~dp0\GeneratedReports\Infrastructure.xml" ^ -oldStyle +"%UserProfile%\.nuget\packages\OpenCover\4.6.519\tools\OpenCover.Console.exe" ^ +-register:user ^ +-target:"C:\Program Files\dotnet\dotnet.exe" ^ +-targetargs:"test %~dp0\Squidex.Core.Tests" ^ +-filter:"+[Squidex*]*" ^ +-skipautoprops ^ +-output:"%~dp0\GeneratedReports\Core.xml" ^ +-oldStyle +exit /b %errorlevel% + "%UserProfile%\.nuget\packages\OpenCover\4.6.519\tools\OpenCover.Console.exe" ^ -register:user ^ -target:"C:\Program Files\dotnet\dotnet.exe" ^ diff --git a/tests/Squidex.Core.Tests/Properties/AssemblyInfo.cs b/tests/Squidex.Core.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..a2be84d6e --- /dev/null +++ b/tests/Squidex.Core.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Squidex.Core.Tests")] +[assembly: AssemblyTrademark("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("fd0afd44-7a93-4f9e-b5ed-72582392e435")] diff --git a/tests/Squidex.Core.Tests/Schemas/NumberFieldPropertiesTests.cs b/tests/Squidex.Core.Tests/Schemas/NumberFieldPropertiesTests.cs new file mode 100644 index 000000000..c5601df5e --- /dev/null +++ b/tests/Squidex.Core.Tests/Schemas/NumberFieldPropertiesTests.cs @@ -0,0 +1,107 @@ +// ========================================================================== +// NumberFieldPropertiesTests.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System.Collections.Generic; +using System.Collections.Immutable; +using Squidex.Core.Schemas; +using Squidex.Infrastructure; +using Xunit; +using FluentAssertions; + +namespace Squidex.Core.Tests.Schemas +{ + public class NumberFieldPropertiesTests + { + private readonly List errors = new List(); + + [Fact] + public void Should_not_add_error_if_properties_are_valid() + { + var properties = new NumberFieldProperties + { + MinValue = 0, + MaxValue = 100, + DefaultValue = 5 + }; + + properties.Validate(errors); + + Assert.Equal(0, errors.Count); + } + + [Fact] + public void Should_add_error_if_default_value_is_less_than_min() + { + var properties = new NumberFieldProperties { MinValue = 10, DefaultValue = 5 }; + + properties.Validate(errors); + + errors.ShouldBeEquivalentTo( + new List + { + new ValidationError("Default value must be greater than min value.", "DefaultValue") + }); + } + + [Fact] + public void Should_add_error_if_default_value_is_greater_than_min() + { + var properties = new NumberFieldProperties { MaxValue = 0, DefaultValue = 5 }; + + properties.Validate(errors); + + errors.ShouldBeEquivalentTo( + new List + { + new ValidationError("Default value must be less than max value.", "DefaultValue") + }); + } + + [Fact] + public void Should_add_error_if_min_greater_than_max() + { + var properties = new NumberFieldProperties { MinValue = 10, MaxValue = 5 }; + + properties.Validate(errors); + + errors.ShouldBeEquivalentTo( + new List + { + new ValidationError("Max value must be greater than min value.", "MinValue", "MaxValue") + }); + } + + [Fact] + public void Should_add_error_if_allowed_values_and_max_is_specified() + { + var properties = new NumberFieldProperties { MaxValue = 10, AllowedValues = ImmutableList.Create(4) }; + + properties.Validate(errors); + + errors.ShouldBeEquivalentTo( + new List + { + new ValidationError("Either or allowed values or range can be defined.", "AllowedValues", "MinValue", "MaxValue") + }); + } + + [Fact] + public void Should_add_error_if_allowed_values_and_min_is_specified() + { + var properties = new NumberFieldProperties { MinValue = 10, AllowedValues = ImmutableList.Create(4) }; + + properties.Validate(errors); + + errors.ShouldBeEquivalentTo( + new List + { + new ValidationError("Either or allowed values or range can be defined.", "AllowedValues", "MinValue", "MaxValue") + }); + } + } +} diff --git a/tests/Squidex.Core.Tests/Schemas/Validators/AllowedValuesValidatorTests.cs b/tests/Squidex.Core.Tests/Schemas/Validators/AllowedValuesValidatorTests.cs new file mode 100644 index 000000000..2c6b7a4f0 --- /dev/null +++ b/tests/Squidex.Core.Tests/Schemas/Validators/AllowedValuesValidatorTests.cs @@ -0,0 +1,52 @@ +// ========================================================================== +// AllowedValuesValidatorTests.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System.Collections.Generic; +using System.Threading.Tasks; +using Squidex.Core.Schemas.Validators; +using Xunit; +using FluentAssertions; + +namespace Squidex.Core.Tests.Schemas.Validators +{ + public class AllowedValuesValidatorTests + { + private readonly List errors = new List(); + + [Fact] + public async Task Should_not_error_if_value_null() + { + var sut = new AllowedValuesValidator(100, 200); + + await sut.ValidateAsync(null, errors); + + Assert.Equal(0, errors.Count); + } + + [Fact] + public async Task Should_not_error_if_value_is_allowed() + { + var sut = new AllowedValuesValidator(100, 200); + + await sut.ValidateAsync(100, errors); + + Assert.Equal(0, errors.Count); + } + + [Fact] + public async Task Should_add_error_if_value_is_not_allowed() + { + var sut = new AllowedValuesValidator(100, 200); + + await sut.ValidateAsync(50, errors); + + errors.ShouldBeEquivalentTo( + new[] { " is not an allowed value" }); + } + } +} \ No newline at end of file diff --git a/tests/Squidex.Core.Tests/Schemas/Validators/RangeValidatorTests.cs b/tests/Squidex.Core.Tests/Schemas/Validators/RangeValidatorTests.cs new file mode 100644 index 000000000..950159605 --- /dev/null +++ b/tests/Squidex.Core.Tests/Schemas/Validators/RangeValidatorTests.cs @@ -0,0 +1,76 @@ +// ========================================================================== +// MinMaxValidatorTests.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using FluentAssertions; +using Squidex.Core.Schemas.Validators; +using Xunit; + +namespace Squidex.Core.Tests.Schemas.Validators +{ + public class RangeValidatorTests + { + private readonly List errors = new List(); + + [Theory] + [InlineData(20, 10)] + [InlineData(10, 10)] + public void Should_throw_error_if_min_greater_than_max(int? min, int? max) + { + Assert.Throws(() => new RangeValidator(min, max)); + } + + [Theory] + [InlineData(null, null)] + [InlineData(1000, null)] + [InlineData(1000, 2000)] + [InlineData(null, 2000)] + public async Task Should_not_error_if_value_is_within_range(int? min, int? max) + { + var sut = new RangeValidator(min, max); + + await sut.ValidateAsync(1500, errors); + + Assert.Equal(0, errors.Count); + } + + [Fact] + public async Task Should_not_error_if_value_null() + { + var sut = new RangeValidator(100, 200); + + await sut.ValidateAsync(null, errors); + + Assert.Equal(0, errors.Count); + } + + [Theory] + public async Task Should_add_error_if_value_is_smaller_than_min() + { + var sut = new RangeValidator(2000, null); + + await sut.ValidateAsync(1500, errors); + + errors.ShouldBeEquivalentTo( + new[] { " must be greater than '1500'" }); + } + + [Theory] + public async Task Should_add_error_if_value_is_greater_than_max() + { + var sut = new RangeValidator(null, 1000); + + await sut.ValidateAsync(1500, errors); + + errors.ShouldBeEquivalentTo( + new[] { " must be less than '1000'" }); + } + } +} diff --git a/tests/Squidex.Core.Tests/Schemas/Validators/RequiredStringValidatorTests.cs b/tests/Squidex.Core.Tests/Schemas/Validators/RequiredStringValidatorTests.cs new file mode 100644 index 000000000..d7073f3c9 --- /dev/null +++ b/tests/Squidex.Core.Tests/Schemas/Validators/RequiredStringValidatorTests.cs @@ -0,0 +1,67 @@ +// ========================================================================== +// RequiredValidatorTests.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System.Collections.Generic; +using System.Threading.Tasks; +using Squidex.Core.Schemas.Validators; +using Xunit; +using FluentAssertions; + +namespace Squidex.Core.Tests.Schemas.Validators +{ + public sealed class RequiredStringValidatorTests + { + private readonly List errors = new List(); + + [Theory] + [InlineData("MyString")] + [InlineData("")] + [InlineData(" ")] + [InlineData(" ")] + public async Task Should_not_add_error_if_object_is_valid(string value) + { + var sut = new RequiredStringValidator(); + + await sut.ValidateAsync(value, errors); + + Assert.Equal(0, errors.Count); + } + + [Fact] + public async Task Should_not_add_error_if_object_is_another_type() + { + var sut = new RequiredStringValidator(); + + await sut.ValidateAsync(true, errors); + + Assert.Equal(0, errors.Count); + } + + [Fact] + public async Task Should_add_error_if_empty_strings_are_not_allowed() + { + var sut = new RequiredStringValidator(true); + + await sut.ValidateAsync(string.Empty, errors); + + errors.ShouldBeEquivalentTo( + new[] { " is required" }); + } + + [Fact] + public async Task Should_add_error_if_object_is_null() + { + var sut = new RequiredStringValidator(); + + await sut.ValidateAsync(null, errors); + + errors.ShouldBeEquivalentTo( + new[] { " is required" }); + } + } +} diff --git a/tests/Squidex.Core.Tests/Schemas/Validators/RequiredValidatorTests.cs b/tests/Squidex.Core.Tests/Schemas/Validators/RequiredValidatorTests.cs new file mode 100644 index 000000000..650bf5f6f --- /dev/null +++ b/tests/Squidex.Core.Tests/Schemas/Validators/RequiredValidatorTests.cs @@ -0,0 +1,52 @@ +// ========================================================================== +// RequiredValidatorTests.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System.Collections.Generic; +using System.Threading.Tasks; +using Squidex.Core.Schemas.Validators; +using Xunit; +using FluentAssertions; + +namespace Squidex.Core.Tests.Schemas.Validators +{ + public sealed class RequiredValidatorTests + { + private readonly List errors = new List(); + + [Fact] + public async Task Should_not_add_error_if_object_is_valid() + { + var sut = new RequiredValidator(); + + await sut.ValidateAsync(true, errors); + + Assert.Equal(0, errors.Count); + } + + [Fact] + public async Task Should_not_add_error_for_empty_string() + { + var sut = new RequiredValidator(); + + await sut.ValidateAsync(string.Empty, errors); + + Assert.Equal(0, errors.Count); + } + + [Fact] + public async Task Should_add_error_if_object_is_null() + { + var sut = new RequiredValidator(); + + await sut.ValidateAsync(null, errors); + + errors.ShouldBeEquivalentTo( + new[] { " is required" }); + } + } +} diff --git a/tests/Squidex.Core.Tests/Squidex.Core.Tests.xproj b/tests/Squidex.Core.Tests/Squidex.Core.Tests.xproj new file mode 100644 index 000000000..9ed6faf7e --- /dev/null +++ b/tests/Squidex.Core.Tests/Squidex.Core.Tests.xproj @@ -0,0 +1,22 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + fd0afd44-7a93-4f9e-b5ed-72582392e435 + Squidex.Core.Tests + .\obj + .\bin\ + v4.6.1 + + + 2.0 + + + + + + \ No newline at end of file diff --git a/tests/Squidex.Core.Tests/project.json b/tests/Squidex.Core.Tests/project.json new file mode 100644 index 000000000..fc61cbd80 --- /dev/null +++ b/tests/Squidex.Core.Tests/project.json @@ -0,0 +1,35 @@ +{ + "buildOptions": { + "copyToOutput": { + "include": [ + "xunit.runner.json" + ] + } + }, + "dependencies": { + "FluentAssertions": "4.15.0", + "dotnet-test-xunit": "2.2.0-preview2-build1029", + "Moq": "4.6.38-alpha", + "Squidex.Core": "1.0.0-*", + "Squidex.Infrastructure": "1.0.0-*", + "xunit": "2.2.0-beta3-build3402" + }, + "frameworks": { + "netcoreapp1.0": { + "dependencies": { + "Microsoft.NETCore.App": { + "type": "platform", + "version": "1.0.1" + } + } + } + }, + "testRunner": "xunit", + "tooling": { + "defaultNamespace": "Squidex.Core.Tests" + }, + "tools": { + "Microsoft.DotNet.Watcher.Tools": "1.0.0-preview2-final" + }, + "version": "1.0.0-*" +} \ No newline at end of file diff --git a/tests/Squidex.Infrastructure.Tests/CQRS/Commands/EnrichWithTimestampHandlerTests.cs b/tests/Squidex.Infrastructure.Tests/CQRS/Commands/EnrichWithTimestampHandlerTests.cs index ee0932ead..cd5bd64a5 100644 --- a/tests/Squidex.Infrastructure.Tests/CQRS/Commands/EnrichWithTimestampHandlerTests.cs +++ b/tests/Squidex.Infrastructure.Tests/CQRS/Commands/EnrichWithTimestampHandlerTests.cs @@ -22,9 +22,9 @@ namespace Squidex.Infrastructure.CQRS.Commands { public DateTime Timestamp { get; set; } } - + [Fact] - public async Task Should_set_timestamp_when_is_timestamp_command() + public async Task Should_set_timestamp_for_timestamp_command() { var utc = DateTime.Today; var sut = new EnrichWithTimestampHandler(() => utc); @@ -37,6 +37,20 @@ namespace Squidex.Infrastructure.CQRS.Commands Assert.Equal(utc, command.Timestamp); } + [Fact] + public async Task Should_set_with_now_datetime_for_timestamp_command() + { + var now = DateTime.UtcNow; + var sut = new EnrichWithTimestampHandler(); + + var command = new TimestampCommand(); + + var result = await sut.HandleAsync(new CommandContext(command)); + + Assert.False(result); + Assert.True(command.Timestamp >= now && command.Timestamp <= DateTime.UtcNow); + } + [Fact] public async Task Should_do_nothing_for_normal_command() { diff --git a/tests/Squidex.Infrastructure.Tests/CQRS/EnvelopeHeaderTests.cs b/tests/Squidex.Infrastructure.Tests/CQRS/EnvelopeHeaderTests.cs new file mode 100644 index 000000000..c77ad7af8 --- /dev/null +++ b/tests/Squidex.Infrastructure.Tests/CQRS/EnvelopeHeaderTests.cs @@ -0,0 +1,60 @@ +// ========================================================================== +// EnvelopeHeaderTests.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System.Linq; +using Xunit; + +namespace Squidex.Infrastructure.CQRS +{ + public class EnvelopeHeaderTests + { + [Fact] + public void Should_create_headers() + { + var headers = new EnvelopeHeaders(); + + Assert.Equal(0, headers.Count); + } + + [Fact] + public void Should_create_headers_with_null_properties() + { + var headers = new EnvelopeHeaders(null); + + Assert.Equal(0, headers.Count); + } + + [Fact] + public void Should_create_headers_as_copy() + { + var source = new PropertiesBag().Set("Key1", 123); + var headers = new EnvelopeHeaders(source); + + CompareHeaders(headers, source); + } + + [Fact] + public void Should_clone_headers() + { + var source = new PropertiesBag().Set("Key1", 123); + var headers = new EnvelopeHeaders(source); + + var clone = headers.Clone(); + + CompareHeaders(headers, clone); + } + + private static void CompareHeaders(PropertiesBag lhs, PropertiesBag rhs) + { + foreach (var key in lhs.PropertyNames.Concat(rhs.PropertyNames).Distinct()) + { + Assert.Equal(lhs[key].ToString(), rhs[key].ToString()); + } + } + } +} diff --git a/tests/Squidex.Infrastructure.Tests/CQRS/EventStore/DefaultNameResolverTests.cs b/tests/Squidex.Infrastructure.Tests/CQRS/EventStore/DefaultNameResolverTests.cs new file mode 100644 index 000000000..a576e2484 --- /dev/null +++ b/tests/Squidex.Infrastructure.Tests/CQRS/EventStore/DefaultNameResolverTests.cs @@ -0,0 +1,63 @@ +// ========================================================================== +// DefaultNameResolverTests.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using Squidex.Infrastructure.CQRS.Events; +using Xunit; + +namespace Squidex.Infrastructure.CQRS.EventStore +{ + public class DefaultNameResolverTests + { + private sealed class User : DomainObject + { + public User(Guid id, int version) + : base(id, version) + { + } + + protected override void DispatchEvent(Envelope @event) + { + } + } + + private sealed class UserDomainObject : DomainObject + { + public UserDomainObject(Guid id, int version) + : base(id, version) + { + } + + protected override void DispatchEvent(Envelope @event) + { + } + } + + [Fact] + public void Should_calculate_name() + { + var sut = new DefaultNameResolver("Squidex"); + var user = new User(Guid.NewGuid(), 1); + + var name = sut.GetStreamName(typeof(User), user.Id); + + Assert.Equal($"squidex-user-{user.Id}", name); + } + + [Fact] + public void Should_calculate_name_and_remove_suffix() + { + var sut = new DefaultNameResolver("Squidex"); + var user = new UserDomainObject(Guid.NewGuid(), 1); + + var name = sut.GetStreamName(typeof(User), user.Id); + + Assert.Equal($"squidex-user-{user.Id}", name); + } + } +} diff --git a/tests/Squidex.Infrastructure.Tests/CQRS/EventStore/EventStoreFormatterTests.cs b/tests/Squidex.Infrastructure.Tests/CQRS/EventStore/EventStoreFormatterTests.cs new file mode 100644 index 000000000..1510d3795 --- /dev/null +++ b/tests/Squidex.Infrastructure.Tests/CQRS/EventStore/EventStoreFormatterTests.cs @@ -0,0 +1,92 @@ +// ========================================================================== +// EventStoreFormatterTests.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Linq; +using Newtonsoft.Json; +using NodaTime; +using Squidex.Infrastructure.CQRS.Events; +using Squidex.Infrastructure.Json; +using Xunit; + +namespace Squidex.Infrastructure.CQRS.EventStore +{ + public class EventStoreFormatterTests + { + public sealed class Event : IEvent + { + public string MyProperty { get; set; } + } + + public sealed class ReceivedEvent : IReceivedEvent + { + public int EventNumber { get; set; } + + public string EventType { get; set; } + + public byte[] Metadata { get; set; } + + public byte[] Payload { get; set; } + + public DateTime Created { get; set; } + } + + private static readonly JsonSerializerSettings serializerSettings = new JsonSerializerSettings(); + + static EventStoreFormatterTests() + { + serializerSettings.Converters.Add(new PropertiesBagConverter()); + } + + public EventStoreFormatterTests() + { + TypeNameRegistry.Map(typeof(Event), "Event"); + } + + [Fact] + public void Should_serialize_and_deserialize_envelope() + { + var commitId = Guid.NewGuid(); + var inputEvent = new Envelope(new Event { MyProperty = "My-Property" }); + + inputEvent.SetAggregateId(Guid.NewGuid()); + inputEvent.SetAppId(Guid.NewGuid()); + inputEvent.SetCommitId(commitId); + inputEvent.SetEventId(Guid.NewGuid()); + inputEvent.SetEventNumber(1); + inputEvent.SetTimestamp(SystemClock.Instance.GetCurrentInstant()); + + var sut = new EventStoreFormatter(serializerSettings); + + var eventData = sut.ToEventData(inputEvent.To(), commitId); + + var receivedEvent = new ReceivedEvent + { + Payload = eventData.Data, + Created = inputEvent.Headers.Timestamp().ToDateTimeUtc(), + EventNumber = 1, + EventType = "event", + Metadata = eventData.Metadata + }; + + var outputEvent = sut.Parse(receivedEvent).To(); + + CompareHeaders(outputEvent.Headers, inputEvent.Headers); + + Assert.Equal(inputEvent.Payload.MyProperty, outputEvent.Payload.MyProperty); + } + + private static void CompareHeaders(PropertiesBag lhs, PropertiesBag rhs) + { + foreach (var key in lhs.PropertyNames.Concat(rhs.PropertyNames).Distinct()) + { + Assert.Equal(lhs[key].ToString(), rhs[key].ToString()); + } + } + } +} diff --git a/tests/Squidex.Infrastructure.Tests/PropertiesBagTests.cs b/tests/Squidex.Infrastructure.Tests/PropertiesBagTests.cs index 4e0ea54af..887b24fa4 100644 --- a/tests/Squidex.Infrastructure.Tests/PropertiesBagTests.cs +++ b/tests/Squidex.Infrastructure.Tests/PropertiesBagTests.cs @@ -32,7 +32,7 @@ namespace Squidex.Infrastructure } [Fact] - public void Should_serialize_and_deserialize_empty() + public void Should_serialize_and_deserialize_empty_bag() { var serializerSettings = new JsonSerializerSettings(); diff --git a/tests/Squidex.Infrastructure.Tests/Reflection/PropertiesTypeAccessorTest.cs b/tests/Squidex.Infrastructure.Tests/Reflection/PropertiesTypeAccessorTests.cs similarity index 98% rename from tests/Squidex.Infrastructure.Tests/Reflection/PropertiesTypeAccessorTest.cs rename to tests/Squidex.Infrastructure.Tests/Reflection/PropertiesTypeAccessorTests.cs index 90b1dc482..75acd67d6 100644 --- a/tests/Squidex.Infrastructure.Tests/Reflection/PropertiesTypeAccessorTest.cs +++ b/tests/Squidex.Infrastructure.Tests/Reflection/PropertiesTypeAccessorTests.cs @@ -12,7 +12,7 @@ using Xunit; namespace Squidex.Infrastructure.Reflection { - public class PropertiesTypeAccessorTest + public class PropertiesTypeAccessorTests { public class TestClass { diff --git a/tests/Squidex.Infrastructure.Tests/Reflection/ReflectionExtensionTests.cs b/tests/Squidex.Infrastructure.Tests/Reflection/ReflectionExtensionTests.cs new file mode 100644 index 000000000..f2b467181 --- /dev/null +++ b/tests/Squidex.Infrastructure.Tests/Reflection/ReflectionExtensionTests.cs @@ -0,0 +1,53 @@ +// ========================================================================== +// ReflectionExtensionTests.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System.Linq; +using Xunit; +// ReSharper disable UnusedMember.Local + +namespace Squidex.Infrastructure.Reflection +{ + public class ReflectionExtensionTests + { + private interface IMain : ISub1 + { + string MainProp { get; set; } + } + + private interface ISub1 : ISub2 + { + string Sub1Prop { get; set; } + } + + private interface ISub2 + { + string Sub2Prop { get; set; } + } + + private class Main + { + public string MainProp { get; set; } + } + + [Fact] + public void Should_find_all_public_properties_of_interfaces() + { + var properties = typeof(IMain).GetPublicProperties().Select(x => x.Name).OrderBy(x => x).ToArray(); + + Assert.Equal(new [] { "MainProp", "Sub1Prop", "Sub2Prop" }, properties); + } + + [Fact] + public void Should_find_all_public_properties_of_classes() + { + var properties = typeof(Main).GetPublicProperties().Select(x => x.Name).OrderBy(x => x).ToArray(); + + Assert.Equal(new[] { "MainProp" }, properties); + } + } +} diff --git a/tests/Squidex.Infrastructure.Tests/Reflection/SimpleMapperTests.cs b/tests/Squidex.Infrastructure.Tests/Reflection/SimpleMapperTests.cs index 39c718d14..fcf6fb676 100644 --- a/tests/Squidex.Infrastructure.Tests/Reflection/SimpleMapperTests.cs +++ b/tests/Squidex.Infrastructure.Tests/Reflection/SimpleMapperTests.cs @@ -8,6 +8,7 @@ using System; using Xunit; +// ReSharper disable UnusedParameter.Local namespace Squidex.Infrastructure.Reflection { @@ -68,6 +69,22 @@ namespace Squidex.Infrastructure.Reflection Assert.Throws(() => SimpleMapper.Map(new Class1(), (Class2)null)); } + [Fact] + public void Should_to_type() + { + var class1 = new Class1 + { + UnmappedString = Guid.NewGuid().ToString(), + MappedString = Guid.NewGuid().ToString(), + MappedNumber = 123, + MappedGuid = Guid.NewGuid() + }; + + var class2 = SimpleMapper.Map(class1); + + AssertObjectEquality(class1, class2); + } + [Fact] public void Should_map_between_types() { @@ -82,6 +99,11 @@ namespace Squidex.Infrastructure.Reflection SimpleMapper.Map(class1, class2); + AssertObjectEquality(class1, class2); + } + + private static void AssertObjectEquality(Class1 class1, Class2 class2) + { Assert.Equal(class1.MappedString, class2.MappedString); Assert.Equal(class1.MappedNumber, class2.MappedNumber); Assert.Equal(class1.MappedGuid.ToString(), class2.MappedGuid); diff --git a/tests/Squidex.Write.Tests/project.json b/tests/Squidex.Write.Tests/project.json index a9fb932ba..b92036fab 100644 --- a/tests/Squidex.Write.Tests/project.json +++ b/tests/Squidex.Write.Tests/project.json @@ -27,7 +27,7 @@ }, "testRunner": "xunit", "tooling": { - "defaultNamespace": "Squidex.Core.Tests" + "defaultNamespace": "Squidex.Write.Tests" }, "tools": { "Microsoft.DotNet.Watcher.Tools": "1.0.0-preview2-final"