From 9c2c5e036f5071bd541c7811b6aa438cf36450a1 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Thu, 30 Nov 2017 21:43:02 +0100 Subject: [PATCH] UI updated. --- .../Schemas/AssetsFieldProperties.cs | 151 ++++++++++++++ .../ValidateContent/IAssetInfo.cs | 27 +++ .../ValidateContent/ValidationContext.cs | 8 +- .../Validators/AssetsValidator.cs | 96 ++++++++- .../ValidateContent/ValidatorsFactory.cs | 2 +- .../Assets/MongoAssetEntity.cs | 6 + .../Assets/MongoAssetRepository.cs | 9 - .../History/MongoHistoryEventEntity.cs | 1 + .../Assets/IAssetEntity.cs | 20 +- .../Assets/Repositories/IAssetRepository.cs | 2 - .../Contents/IContentEntity.cs | 7 +- .../Contents/QueryContext.cs | 16 +- .../History/IHistoryEventEntity.cs | 4 +- .../IEntityWithAppRef.cs | 2 +- .../Rules/IRuleEntity.cs | 7 +- .../Schemas/ISchemaEntity.cs | 7 +- .../Contents/ContentOperationContext.cs | 14 +- .../Guards/FieldPropertiesValidator.cs | 28 +++ src/Squidex.Infrastructure/FileExtensions.cs | 5 + .../States/StateFactory.cs | 12 +- .../Converters/FieldPropertiesDtoFactory.cs | 24 +-- .../Models/Fields/AssetsFieldPropertiesDto.cs | 60 +++++- .../pages/users/user-page.component.html | 4 +- .../schemas/pages/schema/field.component.html | 4 +- .../types/assets-validation.component.html | 90 ++++++++- .../types/assets-validation.component.scss | 6 +- .../types/assets-validation.component.ts | 34 +++- .../types/number-validation.component.html | 4 +- .../types/number-validation.component.scss | 2 +- .../references-validation.component.html | 4 +- .../references-validation.component.scss | 2 +- .../types/string-validation.component.html | 4 +- .../types/string-validation.component.scss | 2 +- .../types/tags-validation.component.html | 4 +- .../types/tags-validation.component.scss | 2 +- .../pages/schemas/schema-form.component.html | 4 +- .../angular/control-errors.component.ts | 2 +- .../angular/dialog-renderer.component.html | 2 +- .../angular/dialog-renderer.component.scss | 8 +- .../framework/angular/http-extensions-impl.ts | 26 +-- .../app/shared/services/schemas.service.ts | 12 +- .../ValidateContent/AssetsFieldTests.cs | 190 ++++++++++++++++-- .../ValidateContent/BooleanFieldTests.cs | 17 +- .../ValidateContent/DateTimeFieldTests.cs | 19 +- .../ValidateContent/GeolocationFieldTests.cs | 19 +- .../ValidateContent/JsonFieldTests.cs | 11 +- .../ValidateContent/NumberFieldTests.cs | 19 +- .../ValidateContent/ReferencesFieldTests.cs | 25 ++- .../ValidateContent/StringFieldTests.cs | 21 +- .../ValidateContent/TagsFieldTests.cs | 21 +- .../ValidationTestExtensions.cs | 19 +- .../Contents/TestData/FakeAssetEntity.cs | 2 + .../AssetsFieldPropertiesTests.cs | 72 ++++++- 53 files changed, 966 insertions(+), 193 deletions(-) create mode 100644 src/Squidex.Domain.Apps.Core.Operations/ValidateContent/IAssetInfo.cs diff --git a/src/Squidex.Domain.Apps.Core.Model/Schemas/AssetsFieldProperties.cs b/src/Squidex.Domain.Apps.Core.Model/Schemas/AssetsFieldProperties.cs index c4de1e7f1..0c23e5153 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Schemas/AssetsFieldProperties.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Schemas/AssetsFieldProperties.cs @@ -6,6 +6,7 @@ // All rights reserved. // ========================================================================== +using System.Collections.Immutable; using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Core.Schemas @@ -13,8 +14,32 @@ namespace Squidex.Domain.Apps.Core.Schemas [TypeName(nameof(AssetsField))] public sealed class AssetsFieldProperties : FieldProperties { + private bool mustBeImage; private int? minItems; private int? maxItems; + private int? minWidth; + private int? maxWidth; + private int? minHeight; + private int? maxHeight; + private int? minSize; + private int? maxSize; + private int? aspectWidth; + private int? aspectHeight; + private ImmutableList allowedExtensions; + + public bool MustBeImage + { + get + { + return mustBeImage; + } + set + { + ThrowIfFrozen(); + + mustBeImage = value; + } + } public int? MinItems { @@ -44,6 +69,132 @@ namespace Squidex.Domain.Apps.Core.Schemas } } + public int? MinWidth + { + get + { + return minWidth; + } + set + { + ThrowIfFrozen(); + + minWidth = value; + } + } + + public int? MaxWidth + { + get + { + return maxWidth; + } + set + { + ThrowIfFrozen(); + + maxWidth = value; + } + } + + public int? MinHeight + { + get + { + return minHeight; + } + set + { + ThrowIfFrozen(); + + minHeight = value; + } + } + + public int? MaxHeight + { + get + { + return maxHeight; + } + set + { + ThrowIfFrozen(); + + maxHeight = value; + } + } + + public int? MinSize + { + get + { + return minSize; + } + set + { + ThrowIfFrozen(); + + minSize = value; + } + } + + public int? MaxSize + { + get + { + return maxSize; + } + set + { + ThrowIfFrozen(); + + maxSize = value; + } + } + + public int? AspectWidth + { + get + { + return aspectWidth; + } + set + { + ThrowIfFrozen(); + + aspectWidth = value; + } + } + + public int? AspectHeight + { + get + { + return aspectHeight; + } + set + { + ThrowIfFrozen(); + + aspectHeight = value; + } + } + + public ImmutableList AllowedExtensions + { + get + { + return allowedExtensions; + } + set + { + ThrowIfFrozen(); + + allowedExtensions = value; + } + } + public override T Accept(IFieldPropertiesVisitor visitor) { return visitor.Visit(this); diff --git a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/IAssetInfo.cs b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/IAssetInfo.cs new file mode 100644 index 000000000..3efa212ff --- /dev/null +++ b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/IAssetInfo.cs @@ -0,0 +1,27 @@ +// ========================================================================== +// IAssetInfo.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; + +namespace Squidex.Domain.Apps.Core.ValidateContent +{ + public interface IAssetInfo + { + Guid AssetId { get; } + + long FileSize { get; } + + bool IsImage { get; } + + int? PixelWidth { get; } + + int? PixelHeight { get; } + + string FileName { get; } + } +} diff --git a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ValidationContext.cs b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ValidationContext.cs index 2d6c9925d..1e6c0807d 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ValidationContext.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ValidationContext.cs @@ -16,20 +16,20 @@ namespace Squidex.Domain.Apps.Core.ValidateContent public sealed class ValidationContext { private readonly Func, Guid, Task>> checkContent; - private readonly Func, Task>> checkAsset; + private readonly Func, Task>> checkAsset; public bool IsOptional { get; } public ValidationContext( Func, Guid, Task>> checkContent, - Func, Task>> checkAsset) + Func, Task>> checkAsset) : this(checkContent, checkAsset, false) { } private ValidationContext( Func, Guid, Task>> checkContent, - Func, Task>> checkAsset, + Func, Task>> checkAsset, bool isOptional) { Guard.NotNull(checkAsset, nameof(checkAsset)); @@ -51,7 +51,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent return checkContent(contentIds, schemaId); } - public Task> GetInvalidAssetIdsAsync(IEnumerable assetId) + public Task> GetAssetInfosAsync(IEnumerable assetId) { return checkAsset(assetId); } diff --git a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AssetsValidator.cs b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AssetsValidator.cs index e7c504577..ce6db7f00 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AssetsValidator.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AssetsValidator.cs @@ -8,21 +8,111 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; +using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Core.ValidateContent.Validators { public sealed class AssetsValidator : IValidator { + private readonly AssetsFieldProperties properties; + + public AssetsValidator(AssetsFieldProperties properties) + { + this.properties = properties; + } + public async Task ValidateAsync(object value, ValidationContext context, Action addError) { if (value is ICollection assetIds) { - var invalidIds = await context.GetInvalidAssetIdsAsync(assetIds); + var assets = await context.GetAssetInfosAsync(assetIds); + var i = 0; - foreach (var invalidId in invalidIds) + foreach (var assetId in assetIds) { - addError($" contains invalid asset '{invalidId}'."); + i++; + + var asset = assets.FirstOrDefault(x => x.AssetId == assetId); + + void Error(string message) + { + addError($" has invalid asset #{i}: {message}"); + } + + if (asset == null) + { + Error($"Id '{assetId}' not found."); + continue; + } + + if (properties.MinSize.HasValue && asset.FileSize < properties.MinSize) + { + Error($"'{asset.FileSize.ToReadableSize()}' less than minimum of '{properties.MinSize.Value.ToReadableSize()}'."); + } + + if (properties.MaxSize.HasValue && asset.FileSize > properties.MaxSize) + { + Error($"'{asset.FileSize.ToReadableSize()}' greater than maximum of '{properties.MaxSize.Value.ToReadableSize()}'."); + } + + if (properties.AllowedExtensions != null && + properties.AllowedExtensions.Count > 0 && + !properties.AllowedExtensions.Any(x => asset.FileName.EndsWith("." + x, StringComparison.OrdinalIgnoreCase))) + { + Error("Invalid file extension."); + } + + if (!asset.IsImage) + { + if (properties.MustBeImage) + { + Error("Not an image."); + } + + continue; + } + + if (asset.PixelWidth.HasValue && + asset.PixelHeight.HasValue) + { + var w = asset.PixelWidth.Value; + var h = asset.PixelHeight.Value; + + var actualRatio = (double)w / h; + + if (properties.MinWidth.HasValue && w < properties.MinWidth) + { + Error($"Width '{w}px' less than minimum of '{properties.MinWidth}px'."); + } + + if (properties.MaxWidth.HasValue && w > properties.MaxWidth) + { + Error($"Width '{w}px' greater than maximum of '{properties.MaxWidth}px'."); + } + + if (properties.MinHeight.HasValue && h < properties.MinHeight) + { + Error($"Height '{h}px' less than minimum of '{properties.MinHeight}px'."); + } + + if (properties.MaxHeight.HasValue && h > properties.MaxHeight) + { + Error($"Height '{h}px' greater than maximum of '{properties.MaxHeight}px'."); + } + + if (properties.AspectHeight.HasValue && properties.AspectWidth.HasValue) + { + var expectedRatio = (double)properties.AspectWidth.Value / properties.AspectHeight.Value; + + if (Math.Abs(expectedRatio - actualRatio) > double.Epsilon) + { + Error($"Aspect ratio not '{properties.AspectWidth}:{properties.AspectHeight}'."); + } + } + } } } } diff --git a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ValidatorsFactory.cs b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ValidatorsFactory.cs index 735b079ad..256d3554c 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ValidatorsFactory.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ValidatorsFactory.cs @@ -38,7 +38,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent yield return new CollectionValidator(properties.IsRequired, properties.MinItems, properties.MaxItems); } - yield return new AssetsValidator(); + yield return new AssetsValidator(properties); } public IEnumerable Visit(BooleanFieldProperties properties) diff --git a/src/Squidex.Domain.Apps.Read.MongoDb/Assets/MongoAssetEntity.cs b/src/Squidex.Domain.Apps.Read.MongoDb/Assets/MongoAssetEntity.cs index e0703dc59..dae9ddcfc 100644 --- a/src/Squidex.Domain.Apps.Read.MongoDb/Assets/MongoAssetEntity.cs +++ b/src/Squidex.Domain.Apps.Read.MongoDb/Assets/MongoAssetEntity.cs @@ -8,6 +8,7 @@ using System; using MongoDB.Bson.Serialization.Attributes; +using Squidex.Domain.Apps.Core.ValidateContent; using Squidex.Domain.Apps.Read.Assets; using Squidex.Infrastructure; using Squidex.Infrastructure.MongoDb; @@ -65,5 +66,10 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Assets [BsonRequired] [BsonElement] public RefToken LastModifiedBy { get; set; } + + Guid IAssetInfo.AssetId + { + get { return Id; } + } } } diff --git a/src/Squidex.Domain.Apps.Read.MongoDb/Assets/MongoAssetRepository.cs b/src/Squidex.Domain.Apps.Read.MongoDb/Assets/MongoAssetRepository.cs index 2caca0755..73bb48ae8 100644 --- a/src/Squidex.Domain.Apps.Read.MongoDb/Assets/MongoAssetRepository.cs +++ b/src/Squidex.Domain.Apps.Read.MongoDb/Assets/MongoAssetRepository.cs @@ -39,15 +39,6 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Assets .Descending(x => x.LastModified)); } - public async Task> QueryNotFoundAsync(Guid appId, IList assetIds) - { - var assetEntities = - await Collection.Find(x => assetIds.Contains(x.Id) && x.AppId == appId).Project(Project.Include(x => x.Id)) - .ToListAsync(); - - return assetIds.Except(assetEntities.Select(x => Guid.Parse(x["_id"].AsString))).ToList(); - } - public async Task> QueryAsync(Guid appId, HashSet mimeTypes = null, HashSet ids = null, string query = null, int take = 10, int skip = 0) { var filter = CreateFilter(appId, mimeTypes, ids, query); diff --git a/src/Squidex.Domain.Apps.Read.MongoDb/History/MongoHistoryEventEntity.cs b/src/Squidex.Domain.Apps.Read.MongoDb/History/MongoHistoryEventEntity.cs index 13cac817e..4990fd528 100644 --- a/src/Squidex.Domain.Apps.Read.MongoDb/History/MongoHistoryEventEntity.cs +++ b/src/Squidex.Domain.Apps.Read.MongoDb/History/MongoHistoryEventEntity.cs @@ -15,6 +15,7 @@ using Squidex.Infrastructure.MongoDb; namespace Squidex.Domain.Apps.Read.MongoDb.History { public sealed class MongoHistoryEventEntity : MongoEntity, + IEntity, IEntityWithAppRef, IUpdateableEntityWithVersion, IUpdateableEntityWithCreatedBy, diff --git a/src/Squidex.Domain.Apps.Read/Assets/IAssetEntity.cs b/src/Squidex.Domain.Apps.Read/Assets/IAssetEntity.cs index ae175abb6..9a502ed7b 100644 --- a/src/Squidex.Domain.Apps.Read/Assets/IAssetEntity.cs +++ b/src/Squidex.Domain.Apps.Read/Assets/IAssetEntity.cs @@ -6,22 +6,20 @@ // All rights reserved. // ========================================================================== +using Squidex.Domain.Apps.Core.ValidateContent; + namespace Squidex.Domain.Apps.Read.Assets { - public interface IAssetEntity : IEntityWithAppRef, IEntityWithCreatedBy, IEntityWithLastModifiedBy, IEntityWithVersion + public interface IAssetEntity : + IEntity, + IEntityWithAppRef, + IEntityWithCreatedBy, + IEntityWithLastModifiedBy, + IEntityWithVersion, + IAssetInfo { string MimeType { get; } - string FileName { get; } - - long FileSize { get; } - long FileVersion { get; } - - bool IsImage { get; } - - int? PixelWidth { get; } - - int? PixelHeight { get; } } } diff --git a/src/Squidex.Domain.Apps.Read/Assets/Repositories/IAssetRepository.cs b/src/Squidex.Domain.Apps.Read/Assets/Repositories/IAssetRepository.cs index c610a3585..48c2ce66e 100644 --- a/src/Squidex.Domain.Apps.Read/Assets/Repositories/IAssetRepository.cs +++ b/src/Squidex.Domain.Apps.Read/Assets/Repositories/IAssetRepository.cs @@ -16,8 +16,6 @@ namespace Squidex.Domain.Apps.Read.Assets.Repositories { Task> QueryAsync(Guid appId, HashSet mimeTypes = null, HashSet ids = null, string query = null, int take = 10, int skip = 0); - Task> QueryNotFoundAsync(Guid appId, IList assetIds); - Task FindAssetAsync(Guid id); Task CountAsync(Guid appId, HashSet mimeTypes = null, HashSet ids = null, string query = null); diff --git a/src/Squidex.Domain.Apps.Read/Contents/IContentEntity.cs b/src/Squidex.Domain.Apps.Read/Contents/IContentEntity.cs index fb704d635..380335382 100644 --- a/src/Squidex.Domain.Apps.Read/Contents/IContentEntity.cs +++ b/src/Squidex.Domain.Apps.Read/Contents/IContentEntity.cs @@ -11,7 +11,12 @@ using Squidex.Domain.Apps.Core.Contents; namespace Squidex.Domain.Apps.Read.Contents { - public interface IContentEntity : IEntityWithAppRef, IEntityWithCreatedBy, IEntityWithLastModifiedBy, IEntityWithVersion + public interface IContentEntity : + IEntity, + IEntityWithAppRef, + IEntityWithCreatedBy, + IEntityWithLastModifiedBy, + IEntityWithVersion { Status Status { get; } diff --git a/src/Squidex.Domain.Apps.Read/Contents/QueryContext.cs b/src/Squidex.Domain.Apps.Read/Contents/QueryContext.cs index 81cc281a7..195808e11 100644 --- a/src/Squidex.Domain.Apps.Read/Contents/QueryContext.cs +++ b/src/Squidex.Domain.Apps.Read/Contents/QueryContext.cs @@ -53,7 +53,7 @@ namespace Squidex.Domain.Apps.Read.Contents if (asset == null) { - asset = await assetRepository.FindAssetAsync(id).ConfigureAwait(false); + asset = await assetRepository.FindAssetAsync(id); if (asset != null) { @@ -70,7 +70,7 @@ namespace Squidex.Domain.Apps.Read.Contents if (content == null) { - content = (await contentQuery.FindContentAsync(app, schemaId.ToString(), user, id).ConfigureAwait(false)).Content; + content = (await contentQuery.FindContentAsync(app, schemaId.ToString(), user, id)).Content; if (content != null) { @@ -95,14 +95,14 @@ namespace Squidex.Domain.Apps.Read.Contents public async Task> QueryContentsAsync(string schemaIdOrName, string query) { - var contents = (await contentQuery.QueryWithCountAsync(app, schemaIdOrName, user, false, query).ConfigureAwait(false)).Items; + var contents = await contentQuery.QueryWithCountAsync(app, schemaIdOrName, user, false, query); - foreach (var content in contents) + foreach (var content in contents.Items) { cachedContents[content.Id] = content; } - return contents; + return contents.Items; } public async Task> GetReferencedAssetsAsync(ICollection ids) @@ -113,7 +113,7 @@ namespace Squidex.Domain.Apps.Read.Contents if (notLoadedAssets.Count > 0) { - var assets = await assetRepository.QueryAsync(app.Id, null, notLoadedAssets, null, int.MaxValue).ConfigureAwait(false); + var assets = await assetRepository.QueryAsync(app.Id, null, notLoadedAssets, null, int.MaxValue); foreach (var asset in assets) { @@ -132,9 +132,9 @@ namespace Squidex.Domain.Apps.Read.Contents if (notLoadedContents.Count > 0) { - var contents = (await contentQuery.QueryWithCountAsync(app, schemaId.ToString(), user, false, notLoadedContents).ConfigureAwait(false)).Items; + var contents = await contentQuery.QueryWithCountAsync(app, schemaId.ToString(), user, false, notLoadedContents); - foreach (var content in contents) + foreach (var content in contents.Items) { cachedContents[content.Id] = content; } diff --git a/src/Squidex.Domain.Apps.Read/History/IHistoryEventEntity.cs b/src/Squidex.Domain.Apps.Read/History/IHistoryEventEntity.cs index ab35d743b..a9b213b5c 100644 --- a/src/Squidex.Domain.Apps.Read/History/IHistoryEventEntity.cs +++ b/src/Squidex.Domain.Apps.Read/History/IHistoryEventEntity.cs @@ -15,10 +15,10 @@ namespace Squidex.Domain.Apps.Read.History { Guid EventId { get; } + RefToken Actor { get; } + string Message { get; } long Version { get; } - - RefToken Actor { get; } } } diff --git a/src/Squidex.Domain.Apps.Read/IEntityWithAppRef.cs b/src/Squidex.Domain.Apps.Read/IEntityWithAppRef.cs index 1070538d1..fdd81b0e1 100644 --- a/src/Squidex.Domain.Apps.Read/IEntityWithAppRef.cs +++ b/src/Squidex.Domain.Apps.Read/IEntityWithAppRef.cs @@ -10,7 +10,7 @@ using System; namespace Squidex.Domain.Apps.Read { - public interface IEntityWithAppRef : IEntity + public interface IEntityWithAppRef { Guid AppId { get; } } diff --git a/src/Squidex.Domain.Apps.Read/Rules/IRuleEntity.cs b/src/Squidex.Domain.Apps.Read/Rules/IRuleEntity.cs index 424febe38..b9f1aeaa2 100644 --- a/src/Squidex.Domain.Apps.Read/Rules/IRuleEntity.cs +++ b/src/Squidex.Domain.Apps.Read/Rules/IRuleEntity.cs @@ -10,7 +10,12 @@ using Squidex.Domain.Apps.Core.Rules; namespace Squidex.Domain.Apps.Read.Rules { - public interface IRuleEntity : IEntityWithAppRef, IEntityWithCreatedBy, IEntityWithLastModifiedBy, IEntityWithVersion + public interface IRuleEntity : + IEntity, + IEntityWithAppRef, + IEntityWithCreatedBy, + IEntityWithLastModifiedBy, + IEntityWithVersion { Rule RuleDef { get; } } diff --git a/src/Squidex.Domain.Apps.Read/Schemas/ISchemaEntity.cs b/src/Squidex.Domain.Apps.Read/Schemas/ISchemaEntity.cs index 90caafd74..3cb98ef78 100644 --- a/src/Squidex.Domain.Apps.Read/Schemas/ISchemaEntity.cs +++ b/src/Squidex.Domain.Apps.Read/Schemas/ISchemaEntity.cs @@ -10,7 +10,12 @@ using Squidex.Domain.Apps.Core.Schemas; namespace Squidex.Domain.Apps.Read.Schemas { - public interface ISchemaEntity : IEntityWithAppRef, IEntityWithCreatedBy, IEntityWithLastModifiedBy, IEntityWithVersion + public interface ISchemaEntity : + IEntity, + IEntityWithAppRef, + IEntityWithCreatedBy, + IEntityWithLastModifiedBy, + IEntityWithVersion { string Name { get; } diff --git a/src/Squidex.Domain.Apps.Write/Contents/ContentOperationContext.cs b/src/Squidex.Domain.Apps.Write/Contents/ContentOperationContext.cs index 0d1ab8077..530c7505e 100644 --- a/src/Squidex.Domain.Apps.Write/Contents/ContentOperationContext.cs +++ b/src/Squidex.Domain.Apps.Write/Contents/ContentOperationContext.cs @@ -84,11 +84,11 @@ namespace Squidex.Domain.Apps.Write.Contents new ValidationContext( (contentIds, schemaId) => { - return contentRepository.QueryNotFoundAsync(appId, schemaId, contentIds.ToList()); + return QueryContentsAsync(appId, schemaId, contentIds); }, assetIds => { - return assetRepository.QueryNotFoundAsync(appId, assetIds.ToList()); + return QueryAssetsAsync(appId, assetIds); }); if (partial) @@ -107,6 +107,16 @@ namespace Squidex.Domain.Apps.Write.Contents } } + private async Task> QueryAssetsAsync(Guid appId, IEnumerable assetIds) + { + return await assetRepository.QueryAsync(appId, null, new HashSet(assetIds), null, int.MaxValue, 0); + } + + private async Task> QueryContentsAsync(Guid appId, Guid schemaId, IEnumerable contentIds) + { + return await contentRepository.QueryNotFoundAsync(appId, schemaId, contentIds.ToList()); + } + public Task ExecuteScriptAndTransformAsync(Func script, object operation) { if (command is ContentDataCommand dataCommand) diff --git a/src/Squidex.Domain.Apps.Write/Schemas/Guards/FieldPropertiesValidator.cs b/src/Squidex.Domain.Apps.Write/Schemas/Guards/FieldPropertiesValidator.cs index a52793ab7..f08405643 100644 --- a/src/Squidex.Domain.Apps.Write/Schemas/Guards/FieldPropertiesValidator.cs +++ b/src/Squidex.Domain.Apps.Write/Schemas/Guards/FieldPropertiesValidator.cs @@ -34,6 +34,34 @@ namespace Squidex.Domain.Apps.Write.Schemas.Guards nameof(properties.MinItems), nameof(properties.MaxItems)); } + + if (properties.MaxHeight.HasValue && properties.MinHeight.HasValue && properties.MinHeight.Value >= properties.MaxHeight.Value) + { + yield return new ValidationError("Max height must be greater than min height.", + nameof(properties.MaxHeight), + nameof(properties.MinHeight)); + } + + if (properties.MaxWidth.HasValue && properties.MinWidth.HasValue && properties.MinWidth.Value >= properties.MaxWidth.Value) + { + yield return new ValidationError("Max width must be greater than min width.", + nameof(properties.MaxWidth), + nameof(properties.MinWidth)); + } + + if (properties.MaxSize.HasValue && properties.MinSize.HasValue && properties.MinSize.Value >= properties.MaxSize.Value) + { + yield return new ValidationError("Max size must be greater than min size.", + nameof(properties.MaxSize), + nameof(properties.MinSize)); + } + + if (properties.AspectWidth.HasValue != properties.AspectHeight.HasValue) + { + yield return new ValidationError("Aspect width and height must be defined.", + nameof(properties.AspectWidth), + nameof(properties.AspectHeight)); + } } public IEnumerable Visit(BooleanFieldProperties properties) diff --git a/src/Squidex.Infrastructure/FileExtensions.cs b/src/Squidex.Infrastructure/FileExtensions.cs index 956d93312..1a0a3e764 100644 --- a/src/Squidex.Infrastructure/FileExtensions.cs +++ b/src/Squidex.Infrastructure/FileExtensions.cs @@ -36,6 +36,11 @@ namespace Squidex.Infrastructure } } + public static string ToReadableSize(this int value) + { + return ToReadableSize((long)value); + } + public static string ToReadableSize(this long value) { if (value < 0) diff --git a/src/Squidex.Infrastructure/States/StateFactory.cs b/src/Squidex.Infrastructure/States/StateFactory.cs index 518ad8f31..73b98ac8f 100644 --- a/src/Squidex.Infrastructure/States/StateFactory.cs +++ b/src/Squidex.Infrastructure/States/StateFactory.cs @@ -88,25 +88,25 @@ namespace Squidex.Infrastructure.States lock (lockObject) { - if (statesCache.TryGetValue(key, out var state)) + if (statesCache.TryGetValue>(key, out var stateObj)) { - return Task.FromResult(state); + return stateObj.ActivateAsync(); } - state = (T)services.GetService(typeof(T)); + var state = (T)services.GetService(typeof(T)); var stateHolder = new StateHolder(key, () => { pubSub.Publish(new InvalidateMessage { Key = key }, false); }, store); + stateObj = new ObjectHolder(state, stateHolder); + statesCache.CreateEntry(key) - .SetValue(state) + .SetValue(stateObj) .SetAbsoluteExpiration(CacheDuration) .Dispose(); - var stateObj = new ObjectHolder(state, stateHolder); - return stateObj.ActivateAsync(); } } diff --git a/src/Squidex/Areas/Api/Controllers/Schemas/Models/Converters/FieldPropertiesDtoFactory.cs b/src/Squidex/Areas/Api/Controllers/Schemas/Models/Converters/FieldPropertiesDtoFactory.cs index 33581dd9f..6e6bc695c 100644 --- a/src/Squidex/Areas/Api/Controllers/Schemas/Models/Converters/FieldPropertiesDtoFactory.cs +++ b/src/Squidex/Areas/Api/Controllers/Schemas/Models/Converters/FieldPropertiesDtoFactory.cs @@ -26,11 +26,6 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models.Converters return properties.Accept(Instance); } - public FieldPropertiesDto Visit(AssetsFieldProperties properties) - { - return SimpleMapper.Map(properties, new AssetsFieldPropertiesDto()); - } - public FieldPropertiesDto Visit(BooleanFieldProperties properties) { return SimpleMapper.Map(properties, new BooleanFieldPropertiesDto()); @@ -61,14 +56,20 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models.Converters return SimpleMapper.Map(properties, new TagsFieldPropertiesDto()); } + public FieldPropertiesDto Visit(AssetsFieldProperties properties) + { + var result = SimpleMapper.Map(properties, new AssetsFieldPropertiesDto()); + + result.AllowedExtensions = properties.AllowedExtensions?.ToArray(); + + return result; + } + public FieldPropertiesDto Visit(NumberFieldProperties properties) { var result = SimpleMapper.Map(properties, new NumberFieldPropertiesDto()); - if (properties.AllowedValues != null) - { - result.AllowedValues = properties.AllowedValues.ToArray(); - } + result.AllowedValues = properties.AllowedValues?.ToArray(); return result; } @@ -77,10 +78,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models.Converters { var result = SimpleMapper.Map(properties, new StringFieldPropertiesDto()); - if (properties.AllowedValues != null) - { - result.AllowedValues = properties.AllowedValues.ToArray(); - } + result.AllowedValues = properties.AllowedValues?.ToArray(); return result; } diff --git a/src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/AssetsFieldPropertiesDto.cs b/src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/AssetsFieldPropertiesDto.cs index c9d91f8a6..923f7e692 100644 --- a/src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/AssetsFieldPropertiesDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/AssetsFieldPropertiesDto.cs @@ -6,6 +6,7 @@ // All rights reserved. // ========================================================================== +using System.Collections.Immutable; using NJsonSchema.Annotations; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure.Reflection; @@ -25,9 +26,66 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models.Fields /// public int? MaxItems { get; set; } + /// + /// The minimum file size in bytes. + /// + public int? MinSize { get; set; } + + /// + /// The maximum file size in bytes. + /// + public int? MaxSize { get; set; } + + /// + /// The minimum image width in pixels. + /// + public int? MinWidth { get; set; } + + /// + /// The maximum image width in pixels. + /// + public int? MaxWidth { get; set; } + + /// + /// The minimum image height in pixels. + /// + public int? MinHeight { get; set; } + + /// + /// The maximum image height in pixels. + /// + public int? MaxHeight { get; set; } + + /// + /// The image aspect width in pixels. + /// + public int? AspectWidth { get; set; } + + /// + /// The image aspect height in pixels. + /// + public int? AspectHeight { get; set; } + + /// + /// Defines if the asset must be an image. + /// + public bool MustBeImage { get; set; } + + /// + /// The allowed file extensions. + /// + public string[] AllowedExtensions { get; set; } + public override FieldProperties ToProperties() { - return SimpleMapper.Map(this, new AssetsFieldProperties()); + var result = SimpleMapper.Map(this, new AssetsFieldProperties()); + + if (AllowedExtensions != null) + { + result.AllowedExtensions = ImmutableList.Create(AllowedExtensions); + } + + return result; } } } diff --git a/src/Squidex/app/features/administration/pages/users/user-page.component.html b/src/Squidex/app/features/administration/pages/users/user-page.component.html index 39a96fe6d..8a019c60e 100644 --- a/src/Squidex/app/features/administration/pages/users/user-page.component.html +++ b/src/Squidex/app/features/administration/pages/users/user-page.component.html @@ -32,9 +32,7 @@
-
- {{userFormError}} -
+
diff --git a/src/Squidex/app/features/schemas/pages/schema/field.component.html b/src/Squidex/app/features/schemas/pages/schema/field.component.html index 046421aa7..07203613f 100644 --- a/src/Squidex/app/features/schemas/pages/schema/field.component.html +++ b/src/Squidex/app/features/schemas/pages/schema/field.component.html @@ -74,8 +74,8 @@
- - + +
diff --git a/src/Squidex/app/features/schemas/pages/schema/types/assets-validation.component.html b/src/Squidex/app/features/schemas/pages/schema/types/assets-validation.component.html index 35be4c232..0dea43cb6 100644 --- a/src/Squidex/app/features/schemas/pages/schema/types/assets-validation.component.html +++ b/src/Squidex/app/features/schemas/pages/schema/types/assets-validation.component.html @@ -10,13 +10,95 @@
-
- +
+ - +
- + +
+
+ +
+ + +
+ + + +
+
+ +
+
+ +
+
+ +
+ + +
+ +
+
+ +
+ + +
+ + + +
+
+ +
+
+ +
+
+ +
+ + +
+ + + +
+
+ +
+
+ +
+
+ +
+ + +
+ + + +
+
+ +
+
+ +
+
+ +
+ + +
+
\ No newline at end of file diff --git a/src/Squidex/app/features/schemas/pages/schema/types/assets-validation.component.scss b/src/Squidex/app/features/schemas/pages/schema/types/assets-validation.component.scss index 2edff0687..0f39d55a9 100644 --- a/src/Squidex/app/features/schemas/pages/schema/types/assets-validation.component.scss +++ b/src/Squidex/app/features/schemas/pages/schema/types/assets-validation.component.scss @@ -1,7 +1,7 @@ @import '_vars'; @import '_mixins'; -.minitems { +.minmax { &-col { position: relative; } @@ -17,4 +17,8 @@ .form-group { margin-top: .5rem; +} + +.form-group2 { + margin-top: 3rem; } \ No newline at end of file diff --git a/src/Squidex/app/features/schemas/pages/schema/types/assets-validation.component.ts b/src/Squidex/app/features/schemas/pages/schema/types/assets-validation.component.ts index c0cdb7cce..bf4c15f23 100644 --- a/src/Squidex/app/features/schemas/pages/schema/types/assets-validation.component.ts +++ b/src/Squidex/app/features/schemas/pages/schema/types/assets-validation.component.ts @@ -24,10 +24,40 @@ export class AssetsValidationComponent implements OnInit { public properties: AssetsFieldPropertiesDto; public ngOnInit() { + this.editForm.setControl('minItems', + new FormControl(this.properties.minItems)); + this.editForm.setControl('maxItems', new FormControl(this.properties.maxItems)); - this.editForm.setControl('minItems', - new FormControl(this.properties.minItems)); + this.editForm.setControl('minSize', + new FormControl(this.properties.minSize)); + + this.editForm.setControl('maxSize', + new FormControl(this.properties.maxSize)); + + this.editForm.setControl('allowedExtensions', + new FormControl(this.properties.allowedExtensions)); + + this.editForm.setControl('mustBeImage', + new FormControl(this.properties.mustBeImage)); + + this.editForm.setControl('minWidth', + new FormControl(this.properties.minWidth)); + + this.editForm.setControl('maxWidth', + new FormControl(this.properties.maxWidth)); + + this.editForm.setControl('minHeight', + new FormControl(this.properties.minHeight)); + + this.editForm.setControl('maxHeight', + new FormControl(this.properties.maxHeight)); + + this.editForm.setControl('aspectWidth', + new FormControl(this.properties.aspectWidth)); + + this.editForm.setControl('aspectHeight', + new FormControl(this.properties.aspectHeight)); } } \ No newline at end of file diff --git a/src/Squidex/app/features/schemas/pages/schema/types/number-validation.component.html b/src/Squidex/app/features/schemas/pages/schema/types/number-validation.component.html index 6e3373313..af854f032 100644 --- a/src/Squidex/app/features/schemas/pages/schema/types/number-validation.component.html +++ b/src/Squidex/app/features/schemas/pages/schema/types/number-validation.component.html @@ -10,10 +10,10 @@
-
+
- +
diff --git a/src/Squidex/app/features/schemas/pages/schema/types/number-validation.component.scss b/src/Squidex/app/features/schemas/pages/schema/types/number-validation.component.scss index b9d07d654..db5ebf320 100644 --- a/src/Squidex/app/features/schemas/pages/schema/types/number-validation.component.scss +++ b/src/Squidex/app/features/schemas/pages/schema/types/number-validation.component.scss @@ -1,7 +1,7 @@ @import '_vars'; @import '_mixins'; -.minlength { +.minmax { &-col { position: relative; } diff --git a/src/Squidex/app/features/schemas/pages/schema/types/references-validation.component.html b/src/Squidex/app/features/schemas/pages/schema/types/references-validation.component.html index 66ed2a9d4..011526d3f 100644 --- a/src/Squidex/app/features/schemas/pages/schema/types/references-validation.component.html +++ b/src/Squidex/app/features/schemas/pages/schema/types/references-validation.component.html @@ -20,10 +20,10 @@
-
+
- +
diff --git a/src/Squidex/app/features/schemas/pages/schema/types/references-validation.component.scss b/src/Squidex/app/features/schemas/pages/schema/types/references-validation.component.scss index 2edff0687..26b5045d2 100644 --- a/src/Squidex/app/features/schemas/pages/schema/types/references-validation.component.scss +++ b/src/Squidex/app/features/schemas/pages/schema/types/references-validation.component.scss @@ -1,7 +1,7 @@ @import '_vars'; @import '_mixins'; -.minitems { +.minmax { &-col { position: relative; } diff --git a/src/Squidex/app/features/schemas/pages/schema/types/string-validation.component.html b/src/Squidex/app/features/schemas/pages/schema/types/string-validation.component.html index 09f659250..a81166ef9 100644 --- a/src/Squidex/app/features/schemas/pages/schema/types/string-validation.component.html +++ b/src/Squidex/app/features/schemas/pages/schema/types/string-validation.component.html @@ -10,10 +10,10 @@
-
+
- +
diff --git a/src/Squidex/app/features/schemas/pages/schema/types/string-validation.component.scss b/src/Squidex/app/features/schemas/pages/schema/types/string-validation.component.scss index a1a14fec0..37aba9242 100644 --- a/src/Squidex/app/features/schemas/pages/schema/types/string-validation.component.scss +++ b/src/Squidex/app/features/schemas/pages/schema/types/string-validation.component.scss @@ -1,7 +1,7 @@ @import '_vars'; @import '_mixins'; -.minlength { +.minmax { &-col { position: relative; } diff --git a/src/Squidex/app/features/schemas/pages/schema/types/tags-validation.component.html b/src/Squidex/app/features/schemas/pages/schema/types/tags-validation.component.html index 35be4c232..030045a82 100644 --- a/src/Squidex/app/features/schemas/pages/schema/types/tags-validation.component.html +++ b/src/Squidex/app/features/schemas/pages/schema/types/tags-validation.component.html @@ -10,10 +10,10 @@
-
+
- +
diff --git a/src/Squidex/app/features/schemas/pages/schema/types/tags-validation.component.scss b/src/Squidex/app/features/schemas/pages/schema/types/tags-validation.component.scss index 2edff0687..26b5045d2 100644 --- a/src/Squidex/app/features/schemas/pages/schema/types/tags-validation.component.scss +++ b/src/Squidex/app/features/schemas/pages/schema/types/tags-validation.component.scss @@ -1,7 +1,7 @@ @import '_vars'; @import '_mixins'; -.minitems { +.minmax { &-col { position: relative; } diff --git a/src/Squidex/app/features/schemas/pages/schemas/schema-form.component.html b/src/Squidex/app/features/schemas/pages/schemas/schema-form.component.html index 2dfe312bc..2be21849d 100644 --- a/src/Squidex/app/features/schemas/pages/schemas/schema-form.component.html +++ b/src/Squidex/app/features/schemas/pages/schemas/schema-form.component.html @@ -1,8 +1,6 @@
-
- {{createFormError}} -
+
diff --git a/src/Squidex/app/framework/angular/control-errors.component.ts b/src/Squidex/app/framework/angular/control-errors.component.ts index d1f4b0fbf..4fd2b061f 100644 --- a/src/Squidex/app/framework/angular/control-errors.component.ts +++ b/src/Squidex/app/framework/angular/control-errors.component.ts @@ -16,7 +16,7 @@ const DEFAULT_ERRORS: { [key: string]: string } = { patternmessage: '{message}', minvalue: '{field} must be larger than {minValue}.', maxvalue: '{field} must be smaller than {maxValue}.', - minlength: '{field} must have a length of more than {requiredLength}.', + minmax: '{field} must have a length of more than {requiredLength}.', maxlength: '{field} must have a length of less than {requiredLength}.', match: '{message}', validdatetime: '{field} is not a valid date time', diff --git a/src/Squidex/app/framework/angular/dialog-renderer.component.html b/src/Squidex/app/framework/angular/dialog-renderer.component.html index a708ffc84..c6a6aa204 100644 --- a/src/Squidex/app/framework/angular/dialog-renderer.component.html +++ b/src/Squidex/app/framework/angular/dialog-renderer.component.html @@ -24,6 +24,6 @@
- {{notification.message}} +
\ No newline at end of file diff --git a/src/Squidex/app/framework/angular/dialog-renderer.component.scss b/src/Squidex/app/framework/angular/dialog-renderer.component.scss index f17a55323..a990f3756 100644 --- a/src/Squidex/app/framework/angular/dialog-renderer.component.scss +++ b/src/Squidex/app/framework/angular/dialog-renderer.component.scss @@ -4,12 +4,16 @@ .notification-container { & { margin: .625rem; - max-width: 20rem; - min-width: 20rem; + max-width: 30rem; + min-width: 30rem; position: fixed; z-index: 100000; } + .alert { + max-height: 20rem; + } + &-topright { @include fixed(0, 0, auto, auto); } diff --git a/src/Squidex/app/framework/angular/http-extensions-impl.ts b/src/Squidex/app/framework/angular/http-extensions-impl.ts index 189e5395f..ae3aba1a4 100644 --- a/src/Squidex/app/framework/angular/http-extensions-impl.ts +++ b/src/Squidex/app/framework/angular/http-extensions-impl.ts @@ -19,29 +19,29 @@ export class Versioned { } function formatMessage(message: string, details?: string[]) { - const parts: string[] = []; + const format = (row: string) => { + const last = row[row.length - 1]; - const addPart = (p: string) => { - p = p.trim(); - - const c = p[p.length - 1]; - - if (c !== '.') { - p += '.'; + if (last !== '.') { + return row + '.'; + } else { + return row; } - - parts.push(p); }; - addPart(message); + let result = format(message); if (details) { + result = result + '
    '; + for (let d of details) { - addPart(d); + result += `
  • ${format(d)}
  • `; } + + result = result + '
'; } - return parts.join(' '); + return result; } export class ErrorDto { diff --git a/src/Squidex/app/shared/services/schemas.service.ts b/src/Squidex/app/shared/services/schemas.service.ts index 730dccd5f..0bde5f358 100644 --- a/src/Squidex/app/shared/services/schemas.service.ts +++ b/src/Squidex/app/shared/services/schemas.service.ts @@ -579,7 +579,17 @@ export class AssetsFieldPropertiesDto extends FieldPropertiesDto { isRequired: boolean, isListField: boolean, public readonly minItems?: number, - public readonly maxItems?: number + public readonly maxItems?: number, + public readonly minSize?: number, + public readonly maxSize?: number, + public readonly allowedExtensions?: string[], + public readonly mustBeImage?: boolean, + public readonly minWidth?: number, + public readonly maxWidth?: number, + public readonly minHeight?: number, + public readonly maxHeight?: number, + public readonly aspectWidth?: number, + public readonly aspectHeight?: number ) { super('Assets', label, hints, placeholder, isRequired, isListField); } diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/AssetsFieldTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/AssetsFieldTests.cs index 6b1b43e6b..9a9b51221 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/AssetsFieldTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/AssetsFieldTests.cs @@ -8,11 +8,13 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using System.Threading.Tasks; using FluentAssertions; using Newtonsoft.Json.Linq; using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Domain.Apps.Core.ValidateContent; using Xunit; namespace Squidex.Domain.Apps.Core.Operations.ValidateContent @@ -21,22 +23,62 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent { private readonly List errors = new List(); + public sealed class AssetInfo : IAssetInfo + { + public Guid AssetId { get; set; } + + public string FileName { get; set; } + + public long FileSize { get; set; } + + public bool IsImage { get; set; } + + public int? PixelWidth { get; set; } + + public int? PixelHeight { get; set; } + } + + private readonly AssetInfo document = new AssetInfo + { + AssetId = Guid.NewGuid(), + FileName = "MyDocument.pdf", + FileSize = 1024 * 4, + IsImage = false, + PixelWidth = null, + PixelHeight = null + }; + + private readonly AssetInfo image = new AssetInfo + { + AssetId = Guid.NewGuid(), + FileName = "MyImage.png", + FileSize = 1024 * 8, + IsImage = true, + PixelWidth = 800, + PixelHeight = 600 + }; + + private readonly ValidationContext ctx; + + public AssetsFieldTests() + { + ctx = ValidationTestExtensions.Assets(image, document); + } + [Fact] public void Should_instantiate_field() { - var sut = new AssetsField(1, "my-asset", Partitioning.Invariant); + var sut = Field(new AssetsFieldProperties()); - Assert.Equal("my-asset", sut.Name); + Assert.Equal("my-assets", sut.Name); } [Fact] public async Task Should_not_add_error_if_assets_are_valid() { - var assetId = Guid.NewGuid(); - - var sut = new AssetsField(1, "my-asset", Partitioning.Invariant); + var sut = Field(new AssetsFieldProperties()); - await sut.ValidateAsync(CreateValue(assetId), errors); + await sut.ValidateAsync(CreateValue(document.AssetId), errors, ctx); Assert.Empty(errors); } @@ -44,9 +86,9 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent [Fact] public async Task Should_not_add_error_if_assets_are_null_and_valid() { - var sut = new AssetsField(1, "my-asset", Partitioning.Invariant); + var sut = Field(new AssetsFieldProperties()); - await sut.ValidateAsync(CreateValue(null), errors); + await sut.ValidateAsync(CreateValue(null), errors, ctx); Assert.Empty(errors); } @@ -54,9 +96,9 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent [Fact] public async Task Should_add_errors_if_assets_are_required_and_null() { - var sut = new AssetsField(1, "my-asset", Partitioning.Invariant, new AssetsFieldProperties { IsRequired = true }); + var sut = Field(new AssetsFieldProperties { IsRequired = true }); - await sut.ValidateAsync(CreateValue(null), errors); + await sut.ValidateAsync(CreateValue(null), errors, ctx); errors.ShouldBeEquivalentTo( new[] { " is required." }); @@ -65,9 +107,9 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent [Fact] public async Task Should_add_errors_if_assets_are_required_and_empty() { - var sut = new AssetsField(1, "my-asset", Partitioning.Invariant, new AssetsFieldProperties { IsRequired = true }); + var sut = Field(new AssetsFieldProperties { IsRequired = true }); - await sut.ValidateAsync(CreateValue(), errors); + await sut.ValidateAsync(CreateValue(), errors, ctx); errors.ShouldBeEquivalentTo( new[] { " is required." }); @@ -76,7 +118,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent [Fact] public async Task Should_add_errors_if_value_is_not_valid() { - var sut = new AssetsField(1, "my-asset", Partitioning.Invariant); + var sut = Field(new AssetsFieldProperties()); await sut.ValidateAsync("invalid", errors); @@ -87,9 +129,9 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent [Fact] public async Task Should_add_errors_if_value_has_not_enough_items() { - var sut = new AssetsField(1, "my-asset", Partitioning.Invariant, new AssetsFieldProperties { MinItems = 3 }); + var sut = Field(new AssetsFieldProperties { MinItems = 3 }); - await sut.ValidateAsync(CreateValue(Guid.NewGuid(), Guid.NewGuid()), errors); + await sut.ValidateAsync(CreateValue(document.AssetId, document.AssetId), errors, ctx); errors.ShouldBeEquivalentTo( new[] { " must have at least 3 item(s)." }); @@ -98,9 +140,9 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent [Fact] public async Task Should_add_errors_if_value_has_too_much_items() { - var sut = new AssetsField(1, "my-asset", Partitioning.Invariant, new AssetsFieldProperties { MaxItems = 1 }); + var sut = Field(new AssetsFieldProperties { MaxItems = 1 }); - await sut.ValidateAsync(CreateValue(Guid.NewGuid(), Guid.NewGuid()), errors); + await sut.ValidateAsync(CreateValue(document.AssetId, document.AssetId), errors, ctx); errors.ShouldBeEquivalentTo( new[] { " must have not more than 1 item(s)." }); @@ -111,17 +153,125 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent { var assetId = Guid.NewGuid(); - var sut = new AssetsField(1, "my-asset", Partitioning.Invariant); + var sut = Field(new AssetsFieldProperties()); + + await sut.ValidateAsync(CreateValue(assetId), errors, ctx); + + errors.ShouldBeEquivalentTo( + new[] { $" has invalid asset #1: Id '{assetId}' not found." }); + } + + [Fact] + public async Task Should_add_error_if_document_is_too_small() + { + var sut = Field(new AssetsFieldProperties { MinSize = 5 * 1024 }); + + await sut.ValidateAsync(CreateValue(document.AssetId, image.AssetId), errors, ctx); + + errors.ShouldBeEquivalentTo( + new[] { $" has invalid asset #1: '4 kB' less than minimum of '5 kB'." }); + } + + [Fact] + public async Task Should_add_error_if_document_is_too_big() + { + var sut = Field(new AssetsFieldProperties { MaxSize = 5 * 1024 }); + + await sut.ValidateAsync(CreateValue(document.AssetId, image.AssetId), errors, ctx); + + errors.ShouldBeEquivalentTo( + new[] { $" has invalid asset #2: '8 kB' greater than maximum of '5 kB'." }); + } + + [Fact] + public async Task Should_add_error_if_document_is_not_an_image() + { + var sut = Field(new AssetsFieldProperties { MustBeImage = true }); + + await sut.ValidateAsync(CreateValue(document.AssetId, image.AssetId), errors, ctx); + + errors.ShouldBeEquivalentTo( + new[] { $" has invalid asset #1: Not an image." }); + } + + [Fact] + public async Task Should_add_error_if_image_width_is_too_small() + { + var sut = Field(new AssetsFieldProperties { MinWidth = 1000 }); + + await sut.ValidateAsync(CreateValue(document.AssetId, image.AssetId), errors, ctx); + + errors.ShouldBeEquivalentTo( + new[] { $" has invalid asset #2: Width '800px' less than minimum of '1000px'." }); + } + + [Fact] + public async Task Should_add_error_if_image_width_is_too_big() + { + var sut = Field(new AssetsFieldProperties { MaxWidth = 700 }); + + await sut.ValidateAsync(CreateValue(document.AssetId, image.AssetId), errors, ctx); + + errors.ShouldBeEquivalentTo( + new[] { $" has invalid asset #2: Width '800px' greater than maximum of '700px'." }); + } + + [Fact] + public async Task Should_add_error_if_image_height_is_too_small() + { + var sut = Field(new AssetsFieldProperties { MinHeight = 800 }); + + await sut.ValidateAsync(CreateValue(document.AssetId, image.AssetId), errors, ctx); + + errors.ShouldBeEquivalentTo( + new[] { $" has invalid asset #2: Height '600px' less than minimum of '800px'." }); + } + + [Fact] + public async Task Should_add_error_if_image_height_is_too_big() + { + var sut = Field(new AssetsFieldProperties { MaxHeight = 500 }); + + await sut.ValidateAsync(CreateValue(document.AssetId, image.AssetId), errors, ctx); + + errors.ShouldBeEquivalentTo( + new[] { $" has invalid asset #2: Height '600px' greater than maximum of '500px'." }); + } + + [Fact] + public async Task Should_add_error_if_image_has_invalid_aspect_ratio() + { + var sut = Field(new AssetsFieldProperties { AspectWidth = 1, AspectHeight = 1 }); - await sut.ValidateAsync(CreateValue(assetId), errors, ValidationTestExtensions.InvalidContext(assetId)); + await sut.ValidateAsync(CreateValue(document.AssetId, image.AssetId), errors, ctx); errors.ShouldBeEquivalentTo( - new[] { $" contains invalid asset '{assetId}'." }); + new[] { " has invalid asset #2: Aspect ratio not '1:1'." }); + } + + [Fact] + public async Task Should_add_error_if_image_has_invalid_extension() + { + var sut = Field(new AssetsFieldProperties { AllowedExtensions = ImmutableList.Create("mp4") }); + + await sut.ValidateAsync(CreateValue(document.AssetId, image.AssetId), errors, ctx); + + errors.ShouldBeEquivalentTo( + new[] + { + $" has invalid asset #1: Invalid file extension.", + $" has invalid asset #2: Invalid file extension." + }); } private static JToken CreateValue(params Guid[] ids) { return ids == null ? JValue.CreateNull() : (JToken)new JArray(ids.OfType().ToArray()); } + + private static AssetsField Field(AssetsFieldProperties properties) + { + return new AssetsField(1, "my-assets", Partitioning.Invariant, properties); + } } } diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/BooleanFieldTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/BooleanFieldTests.cs index aa96be3b7..b34ec41a5 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/BooleanFieldTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/BooleanFieldTests.cs @@ -22,15 +22,15 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent [Fact] public void Should_instantiate_field() { - var sut = new BooleanField(1, "my-bolean", Partitioning.Invariant); + var sut = Field(new BooleanFieldProperties()); - Assert.Equal("my-bolean", sut.Name); + Assert.Equal("my-boolean", sut.Name); } [Fact] public async Task Should_not_add_error_if_null_boolean_is_valid() { - var sut = new BooleanField(1, "my-bolean", Partitioning.Invariant); + var sut = Field(new BooleanFieldProperties()); await sut.ValidateAsync(CreateValue(null), errors); @@ -40,7 +40,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent [Fact] public async Task Should_not_add_error_if_boolean_is_valid() { - var sut = new BooleanField(1, "my-bolean", Partitioning.Invariant); + var sut = Field(new BooleanFieldProperties()); await sut.ValidateAsync(CreateValue(true), errors); @@ -50,7 +50,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent [Fact] public async Task Should_add_errors_if_boolean_is_required() { - var sut = new BooleanField(1, "my-bolean", Partitioning.Invariant, new BooleanFieldProperties { IsRequired = true }); + var sut = Field(new BooleanFieldProperties { IsRequired = true }); await sut.ValidateAsync(CreateValue(null), errors); @@ -61,7 +61,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent [Fact] public async Task Should_add_errors_if_value_is_not_valid() { - var sut = new BooleanField(1, "my-bolean", Partitioning.Invariant); + var sut = Field(new BooleanFieldProperties()); await sut.ValidateAsync(CreateValue("Invalid"), errors); @@ -73,5 +73,10 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent { return new JValue(v); } + + private static BooleanField Field(BooleanFieldProperties properties) + { + return new BooleanField(1, "my-boolean", Partitioning.Invariant, properties); + } } } diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/DateTimeFieldTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/DateTimeFieldTests.cs index 5952cdfb1..5580c1883 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/DateTimeFieldTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/DateTimeFieldTests.cs @@ -24,7 +24,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent [Fact] public void Should_instantiate_field() { - var sut = new DateTimeField(1, "my-datetime", Partitioning.Invariant); + var sut = Field(new DateTimeFieldProperties()); Assert.Equal("my-datetime", sut.Name); } @@ -32,7 +32,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent [Fact] public async Task Should_not_add_error_if_datetime_is_valid() { - var sut = new DateTimeField(1, "my-datetime", Partitioning.Invariant); + var sut = Field(new DateTimeFieldProperties()); await sut.ValidateAsync(CreateValue(null), errors); @@ -42,7 +42,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent [Fact] public async Task Should_add_errors_if_datetime_is_required() { - var sut = new DateTimeField(1, "my-datetime", Partitioning.Invariant, new DateTimeFieldProperties { IsRequired = true }); + var sut = Field(new DateTimeFieldProperties { IsRequired = true }); await sut.ValidateAsync(CreateValue(null), errors); @@ -53,7 +53,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent [Fact] public async Task Should_add_errors_if_datetime_is_less_than_min() { - var sut = new DateTimeField(1, "my-datetime", Partitioning.Invariant, new DateTimeFieldProperties { MinValue = FutureDays(10) }); + var sut = Field(new DateTimeFieldProperties { MinValue = FutureDays(10) }); await sut.ValidateAsync(CreateValue(FutureDays(0)), errors); @@ -64,7 +64,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent [Fact] public async Task Should_add_errors_if_datetime_is_greater_than_max() { - var sut = new DateTimeField(1, "my-datetime", Partitioning.Invariant, new DateTimeFieldProperties { MaxValue = FutureDays(10) }); + var sut = Field(new DateTimeFieldProperties { MaxValue = FutureDays(10) }); await sut.ValidateAsync(CreateValue(FutureDays(20)), errors); @@ -75,7 +75,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent [Fact] public async Task Should_add_errors_if_value_is_not_valid() { - var sut = new DateTimeField(1, "my-datetime", Partitioning.Invariant); + var sut = Field(new DateTimeFieldProperties()); await sut.ValidateAsync(CreateValue("Invalid"), errors); @@ -86,7 +86,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent [Fact] public async Task Should_add_errors_if_value_is_another_type() { - var sut = new DateTimeField(1, "my-datetime", Partitioning.Invariant); + var sut = Field(new DateTimeFieldProperties()); await sut.ValidateAsync(CreateValue(123), errors); @@ -103,5 +103,10 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent { return v is Instant ? new JValue(v.ToString()) : new JValue(v); } + + private static DateTimeField Field(DateTimeFieldProperties properties) + { + return new DateTimeField(1, "my-datetime", Partitioning.Invariant, properties); + } } } diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/GeolocationFieldTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/GeolocationFieldTests.cs index 4421d93d7..c8c275fe2 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/GeolocationFieldTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/GeolocationFieldTests.cs @@ -22,7 +22,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent [Fact] public void Should_instantiate_field() { - var sut = new GeolocationField(1, "my-geolocation", Partitioning.Invariant); + var sut = Field(new GeolocationFieldProperties()); Assert.Equal("my-geolocation", sut.Name); } @@ -30,7 +30,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent [Fact] public async Task Should_not_add_error_if_geolocation_is_valid_null() { - var sut = new GeolocationField(1, "my-geolocation", Partitioning.Invariant); + var sut = Field(new GeolocationFieldProperties()); await sut.ValidateAsync(CreateValue(JValue.CreateNull()), errors); @@ -40,7 +40,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent [Fact] public async Task Should_not_add_error_if_geolocation_is_valid() { - var sut = new GeolocationField(1, "my-geolocation", Partitioning.Invariant); + var sut = Field(new GeolocationFieldProperties()); var geolocation = new JObject( new JProperty("latitude", 0), @@ -54,7 +54,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent [Fact] public async Task Should_add_errors_if_geolocation_has_invalid_latitude() { - var sut = new GeolocationField(1, "my-geolocation", Partitioning.Invariant, new GeolocationFieldProperties { IsRequired = true }); + var sut = Field(new GeolocationFieldProperties { IsRequired = true }); var geolocation = new JObject( new JProperty("latitude", 200), @@ -69,7 +69,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent [Fact] public async Task Should_add_errors_if_geolocation_has_invalid_longitude() { - var sut = new GeolocationField(1, "my-geolocation", Partitioning.Invariant, new GeolocationFieldProperties { IsRequired = true }); + var sut = Field(new GeolocationFieldProperties { IsRequired = true }); var geolocation = new JObject( new JProperty("latitude", 0), @@ -84,7 +84,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent [Fact] public async Task Should_add_errors_if_geolocation_has_too_many_properties() { - var sut = new GeolocationField(1, "my-geolocation", Partitioning.Invariant, new GeolocationFieldProperties { IsRequired = true }); + var sut = Field(new GeolocationFieldProperties { IsRequired = true }); var geolocation = new JObject( new JProperty("invalid", 0), @@ -100,7 +100,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent [Fact] public async Task Should_add_errors_if_geolocation_is_required() { - var sut = new GeolocationField(1, "my-geolocation", Partitioning.Invariant, new GeolocationFieldProperties { IsRequired = true }); + var sut = Field(new GeolocationFieldProperties { IsRequired = true }); await sut.ValidateAsync(CreateValue(JValue.CreateNull()), errors); @@ -112,5 +112,10 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent { return v; } + + private static GeolocationField Field(GeolocationFieldProperties properties) + { + return new GeolocationField(1, "my-geolocation", Partitioning.Invariant, properties); + } } } diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/JsonFieldTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/JsonFieldTests.cs index 4e7b963f0..1229c2140 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/JsonFieldTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/JsonFieldTests.cs @@ -22,7 +22,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent [Fact] public void Should_instantiate_field() { - var sut = new JsonField(1, "my-json", Partitioning.Invariant); + var sut = Field(new JsonFieldProperties()); Assert.Equal("my-json", sut.Name); } @@ -30,7 +30,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent [Fact] public async Task Should_not_add_error_if_json_is_valid() { - var sut = new JsonField(1, "my-json", Partitioning.Invariant); + var sut = Field(new JsonFieldProperties()); await sut.ValidateAsync(CreateValue(new JValue(1)), errors); @@ -40,7 +40,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent [Fact] public async Task Should_add_errors_if_json_is_required() { - var sut = new JsonField(1, "my-json", Partitioning.Invariant, new JsonFieldProperties { IsRequired = true }); + var sut = Field(new JsonFieldProperties { IsRequired = true }); await sut.ValidateAsync(CreateValue(JValue.CreateNull()), errors); @@ -52,5 +52,10 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent { return v; } + + private static JsonField Field(JsonFieldProperties properties) + { + return new JsonField(1, "my-json", Partitioning.Invariant, properties); + } } } diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/NumberFieldTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/NumberFieldTests.cs index 803ee9062..c2ba5433e 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/NumberFieldTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/NumberFieldTests.cs @@ -23,7 +23,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent [Fact] public void Should_instantiate_field() { - var sut = new NumberField(1, "my-number", Partitioning.Invariant); + var sut = Field(new NumberFieldProperties()); Assert.Equal("my-number", sut.Name); } @@ -31,7 +31,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent [Fact] public async Task Should_not_add_error_if_number_is_valid() { - var sut = new NumberField(1, "my-number", Partitioning.Invariant); + var sut = Field(new NumberFieldProperties()); await sut.ValidateAsync(CreateValue(null), errors); @@ -41,7 +41,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent [Fact] public async Task Should_add_errors_if_number_is_required() { - var sut = new NumberField(1, "my-number", Partitioning.Invariant, new NumberFieldProperties { IsRequired = true }); + var sut = Field(new NumberFieldProperties { IsRequired = true }); await sut.ValidateAsync(CreateValue(null), errors); @@ -52,7 +52,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent [Fact] public async Task Should_add_errors_if_number_is_less_than_min() { - var sut = new NumberField(1, "my-number", Partitioning.Invariant, new NumberFieldProperties { MinValue = 10 }); + var sut = Field(new NumberFieldProperties { MinValue = 10 }); await sut.ValidateAsync(CreateValue(5), errors); @@ -63,7 +63,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent [Fact] public async Task Should_add_errors_if_number_is_greater_than_max() { - var sut = new NumberField(1, "my-number", Partitioning.Invariant, new NumberFieldProperties { MaxValue = 10 }); + var sut = Field(new NumberFieldProperties { MaxValue = 10 }); await sut.ValidateAsync(CreateValue(20), errors); @@ -74,7 +74,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent [Fact] public async Task Should_add_errors_if_number_is_not_allowed() { - var sut = new NumberField(1, "my-number", Partitioning.Invariant, new NumberFieldProperties { AllowedValues = ImmutableList.Create(10d) }); + var sut = Field(new NumberFieldProperties { AllowedValues = ImmutableList.Create(10d) }); await sut.ValidateAsync(CreateValue(20), errors); @@ -85,7 +85,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent [Fact] public async Task Should_add_errors_if_value_is_not_valid() { - var sut = new NumberField(1, "my-number", Partitioning.Invariant); + var sut = Field(new NumberFieldProperties()); await sut.ValidateAsync(CreateValue("Invalid"), errors); @@ -97,5 +97,10 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent { return new JValue(v); } + + private static NumberField Field(NumberFieldProperties properties) + { + return new NumberField(1, "my-number", Partitioning.Invariant, properties); + } } } diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ReferencesFieldTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ReferencesFieldTests.cs index cbeb72435..90429b7a6 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ReferencesFieldTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ReferencesFieldTests.cs @@ -25,7 +25,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent [Fact] public void Should_instantiate_field() { - var sut = new ReferencesField(1, "my-refs", Partitioning.Invariant); + var sut = Field(new ReferencesFieldProperties()); Assert.Equal("my-refs", sut.Name); } @@ -35,7 +35,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent { var referenceId = Guid.NewGuid(); - var sut = new ReferencesField(1, "my-refs", Partitioning.Invariant); + var sut = Field(new ReferencesFieldProperties()); await sut.ValidateAsync(CreateValue(referenceId), errors, ValidationTestExtensions.ValidContext); @@ -45,7 +45,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent [Fact] public async Task Should_not_add_error_if_references_are_null_and_valid() { - var sut = new ReferencesField(1, "my-refs", Partitioning.Invariant); + var sut = Field(new ReferencesFieldProperties()); await sut.ValidateAsync(CreateValue(null), errors); @@ -55,7 +55,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent [Fact] public async Task Should_add_errors_if_references_are_required_and_null() { - var sut = new ReferencesField(1, "my-refs", Partitioning.Invariant, new ReferencesFieldProperties { SchemaId = schemaId, IsRequired = true }); + var sut = Field(new ReferencesFieldProperties { SchemaId = schemaId, IsRequired = true }); await sut.ValidateAsync(CreateValue(null), errors); @@ -66,7 +66,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent [Fact] public async Task Should_add_errors_if_references_are_required_and_empty() { - var sut = new ReferencesField(1, "my-refs", Partitioning.Invariant, new ReferencesFieldProperties { SchemaId = schemaId, IsRequired = true }); + var sut = Field(new ReferencesFieldProperties { SchemaId = schemaId, IsRequired = true }); await sut.ValidateAsync(CreateValue(), errors); @@ -77,7 +77,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent [Fact] public async Task Should_add_errors_if_value_is_not_valid() { - var sut = new ReferencesField(1, "my-refs", Partitioning.Invariant); + var sut = Field(new ReferencesFieldProperties()); await sut.ValidateAsync("invalid", errors); @@ -88,7 +88,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent [Fact] public async Task Should_add_errors_if_value_has_not_enough_items() { - var sut = new ReferencesField(1, "my-refs", Partitioning.Invariant, new ReferencesFieldProperties { SchemaId = schemaId, MinItems = 3 }); + var sut = Field(new ReferencesFieldProperties { SchemaId = schemaId, MinItems = 3 }); await sut.ValidateAsync(CreateValue(Guid.NewGuid(), Guid.NewGuid()), errors); @@ -99,7 +99,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent [Fact] public async Task Should_add_errors_if_value_has_too_much_items() { - var sut = new ReferencesField(1, "my-refs", Partitioning.Invariant, new ReferencesFieldProperties { SchemaId = schemaId, MaxItems = 1 }); + var sut = Field(new ReferencesFieldProperties { SchemaId = schemaId, MaxItems = 1 }); await sut.ValidateAsync(CreateValue(Guid.NewGuid(), Guid.NewGuid()), errors); @@ -112,9 +112,9 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent { var referenceId = Guid.NewGuid(); - var sut = new ReferencesField(1, "my-refs", Partitioning.Invariant, new ReferencesFieldProperties { SchemaId = schemaId }); + var sut = Field(new ReferencesFieldProperties { SchemaId = schemaId }); - await sut.ValidateAsync(CreateValue(referenceId), errors, ValidationTestExtensions.InvalidContext(referenceId)); + await sut.ValidateAsync(CreateValue(referenceId), errors, ValidationTestExtensions.InvalidReferences(referenceId)); errors.ShouldBeEquivalentTo( new[] { $" contains invalid reference '{referenceId}'." }); @@ -124,5 +124,10 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent { return ids == null ? JValue.CreateNull() : (JToken)new JArray(ids.OfType().ToArray()); } + + private static ReferencesField Field(ReferencesFieldProperties properties) + { + return new ReferencesField(1, "my-refs", Partitioning.Invariant, properties); + } } } diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/StringFieldTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/StringFieldTests.cs index fa776d68d..742c3c973 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/StringFieldTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/StringFieldTests.cs @@ -23,7 +23,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent [Fact] public void Should_instantiate_field() { - var sut = new StringField(1, "my-string", Partitioning.Invariant); + var sut = Field(new StringFieldProperties()); Assert.Equal("my-string", sut.Name); } @@ -31,7 +31,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent [Fact] public async Task Should_not_add_error_if_string_is_valid() { - var sut = new StringField(1, "my-string", Partitioning.Invariant, new StringFieldProperties { Label = "" }); + var sut = Field(new StringFieldProperties { Label = "" }); await sut.ValidateAsync(CreateValue(null), errors); @@ -41,7 +41,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent [Fact] public async Task Should_add_errors_if_string_is_required() { - var sut = new StringField(1, "my-string", Partitioning.Invariant, new StringFieldProperties { IsRequired = true }); + var sut = Field(new StringFieldProperties { IsRequired = true }); await sut.ValidateAsync(CreateValue(null), errors); @@ -52,7 +52,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent [Fact] public async Task Should_add_errors_if_string_is_shorter_than_min_length() { - var sut = new StringField(1, "my-string", Partitioning.Invariant, new StringFieldProperties { MinLength = 10 }); + var sut = Field(new StringFieldProperties { MinLength = 10 }); await sut.ValidateAsync(CreateValue("123"), errors); @@ -63,7 +63,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent [Fact] public async Task Should_add_errors_if_string_is_longer_than_max_length() { - var sut = new StringField(1, "my-string", Partitioning.Invariant, new StringFieldProperties { MaxLength = 5 }); + var sut = Field(new StringFieldProperties { MaxLength = 5 }); await sut.ValidateAsync(CreateValue("12345678"), errors); @@ -74,7 +74,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent [Fact] public async Task Should_add_errors_if_string_not_allowed() { - var sut = new StringField(1, "my-string", Partitioning.Invariant, new StringFieldProperties { AllowedValues = ImmutableList.Create("Foo") }); + var sut = Field(new StringFieldProperties { AllowedValues = ImmutableList.Create("Foo") }); await sut.ValidateAsync(CreateValue("Bar"), errors); @@ -85,7 +85,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent [Fact] public async Task Should_add_errors_if_number_is_not_valid_pattern() { - var sut = new StringField(1, "my-string", Partitioning.Invariant, new StringFieldProperties { Pattern = "[0-9]{3}" }); + var sut = Field(new StringFieldProperties { Pattern = "[0-9]{3}" }); await sut.ValidateAsync(CreateValue("abc"), errors); @@ -96,7 +96,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent [Fact] public async Task Should_add_errors_if_number_is_not_valid_pattern_with_message() { - var sut = new StringField(1, "my-string", Partitioning.Invariant, new StringFieldProperties { Pattern = "[0-9]{3}", PatternMessage = "Custom Error Message." }); + var sut = Field(new StringFieldProperties { Pattern = "[0-9]{3}", PatternMessage = "Custom Error Message." }); await sut.ValidateAsync(CreateValue("abc"), errors); @@ -108,5 +108,10 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent { return new JValue(v); } + + private static StringField Field(StringFieldProperties properties) + { + return new StringField(1, "my-string", Partitioning.Invariant, properties); + } } } diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/TagsFieldTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/TagsFieldTests.cs index 946ddc64b..35dc1cb87 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/TagsFieldTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/TagsFieldTests.cs @@ -24,7 +24,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent [Fact] public void Should_instantiate_field() { - var sut = new TagsField(1, "my-tags", Partitioning.Invariant); + var sut = Field(new TagsFieldProperties()); Assert.Equal("my-tags", sut.Name); } @@ -34,7 +34,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent { var referenceId = Guid.NewGuid(); - var sut = new TagsField(1, "my-tags", Partitioning.Invariant); + var sut = Field(new TagsFieldProperties()); await sut.ValidateAsync(CreateValue(referenceId), errors, ValidationTestExtensions.ValidContext); @@ -44,7 +44,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent [Fact] public async Task Should_not_add_error_if_tags_are_null_and_valid() { - var sut = new TagsField(1, "my-tags", Partitioning.Invariant); + var sut = Field(new TagsFieldProperties()); await sut.ValidateAsync(CreateValue(null), errors); @@ -54,7 +54,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent [Fact] public async Task Should_add_errors_if_tags_are_required_and_null() { - var sut = new TagsField(1, "my-tags", Partitioning.Invariant, new TagsFieldProperties { IsRequired = true }); + var sut = Field(new TagsFieldProperties { IsRequired = true }); await sut.ValidateAsync(CreateValue(null), errors); @@ -65,7 +65,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent [Fact] public async Task Should_add_errors_if_tags_are_required_and_empty() { - var sut = new TagsField(1, "my-tags", Partitioning.Invariant, new TagsFieldProperties { IsRequired = true }); + var sut = Field(new TagsFieldProperties { IsRequired = true }); await sut.ValidateAsync(CreateValue(), errors); @@ -76,7 +76,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent [Fact] public async Task Should_add_errors_if_value_is_not_valid() { - var sut = new TagsField(1, "my-tags", Partitioning.Invariant); + var sut = Field(new TagsFieldProperties()); await sut.ValidateAsync("invalid", errors); @@ -87,7 +87,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent [Fact] public async Task Should_add_errors_if_value_has_not_enough_items() { - var sut = new TagsField(1, "my-tags", Partitioning.Invariant, new TagsFieldProperties { MinItems = 3 }); + var sut = Field(new TagsFieldProperties { MinItems = 3 }); await sut.ValidateAsync(CreateValue(Guid.NewGuid(), Guid.NewGuid()), errors); @@ -98,7 +98,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent [Fact] public async Task Should_add_errors_if_value_has_too_much_items() { - var sut = new TagsField(1, "my-tags", Partitioning.Invariant, new TagsFieldProperties { MaxItems = 1 }); + var sut = Field(new TagsFieldProperties { MaxItems = 1 }); await sut.ValidateAsync(CreateValue(Guid.NewGuid(), Guid.NewGuid()), errors); @@ -110,5 +110,10 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent { return ids == null ? JValue.CreateNull() : (JToken)new JArray(ids.OfType().ToArray()); } + + private static TagsField Field(TagsFieldProperties properties) + { + return new TagsField(1, "my-tags", Partitioning.Invariant, properties); + } } } diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ValidationTestExtensions.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ValidationTestExtensions.cs index 054d1c6ec..c08bef25f 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ValidationTestExtensions.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ValidationTestExtensions.cs @@ -8,6 +8,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Newtonsoft.Json.Linq; using Squidex.Domain.Apps.Core.Schemas; @@ -18,9 +19,10 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent { public static class ValidationTestExtensions { - private static readonly Task> ValidIds = Task.FromResult>(new List()); + private static readonly Task> ValidReferences = Task.FromResult>(new List()); + private static readonly Task> ValidAssets = Task.FromResult>(new List()); - public static readonly ValidationContext ValidContext = new ValidationContext((x, y) => ValidIds, x => ValidIds); + public static readonly ValidationContext ValidContext = new ValidationContext((x, y) => ValidReferences, x => ValidAssets); public static Task ValidateAsync(this IValidator validator, object value, IList errors, ValidationContext context = null) { @@ -42,11 +44,18 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent return field.ValidateAsync(value, (context ?? ValidContext).Optional(true), errors.Add); } - public static ValidationContext InvalidContext(Guid assetId) + public static ValidationContext Assets(params IAssetInfo[] assets) { - var invalidIds = Task.FromResult>(new List { assetId }); + var actual = Task.FromResult>(assets.ToList()); - return new ValidationContext((x, y) => invalidIds, x => invalidIds); + return new ValidationContext((x, y) => ValidReferences, x => actual); + } + + public static ValidationContext InvalidReferences(Guid referencesIds) + { + var actual = Task.FromResult>(new List { referencesIds }); + + return new ValidationContext((x, y) => actual, x => ValidAssets); } } } diff --git a/tests/Squidex.Domain.Apps.Read.Tests/Contents/TestData/FakeAssetEntity.cs b/tests/Squidex.Domain.Apps.Read.Tests/Contents/TestData/FakeAssetEntity.cs index 5c70ddbfc..e1373ad67 100644 --- a/tests/Squidex.Domain.Apps.Read.Tests/Contents/TestData/FakeAssetEntity.cs +++ b/tests/Squidex.Domain.Apps.Read.Tests/Contents/TestData/FakeAssetEntity.cs @@ -19,6 +19,8 @@ namespace Squidex.Domain.Apps.Read.Contents.TestData public Guid AppId { get; set; } + public Guid AssetId { get; set; } + public Instant Created { get; set; } public Instant LastModified { get; set; } diff --git a/tests/Squidex.Domain.Apps.Write.Tests/Schemas/Guards/FieldProperties/AssetsFieldPropertiesTests.cs b/tests/Squidex.Domain.Apps.Write.Tests/Schemas/Guards/FieldProperties/AssetsFieldPropertiesTests.cs index b296a5ccd..c8c1a46c1 100644 --- a/tests/Squidex.Domain.Apps.Write.Tests/Schemas/Guards/FieldProperties/AssetsFieldPropertiesTests.cs +++ b/tests/Squidex.Domain.Apps.Write.Tests/Schemas/Guards/FieldProperties/AssetsFieldPropertiesTests.cs @@ -18,7 +18,7 @@ namespace Squidex.Domain.Apps.Write.Schemas.Guards.FieldProperties public class AssetsFieldPropertiesTests { [Fact] - public void Should_add_error_if_min_greater_than_max() + public void Should_add_error_if_min_items_greater_than_max_items() { var sut = new AssetsFieldProperties { MinItems = 10, MaxItems = 5 }; @@ -30,5 +30,75 @@ namespace Squidex.Domain.Apps.Write.Schemas.Guards.FieldProperties new ValidationError("Max items must be greater than min items.", "MinItems", "MaxItems") }); } + + [Fact] + public void Should_add_error_if_min_width_greater_than_max_width() + { + var sut = new AssetsFieldProperties { MinWidth = 10, MaxWidth = 5 }; + + var errors = FieldPropertiesValidator.Validate(sut).ToList(); + + errors.ShouldBeEquivalentTo( + new List + { + new ValidationError("Max width must be greater than min width.", "MinWidth", "MaxWidth") + }); + } + + [Fact] + public void Should_add_error_if_min_height_greater_than_max_height() + { + var sut = new AssetsFieldProperties { MinHeight = 10, MaxHeight = 5 }; + + var errors = FieldPropertiesValidator.Validate(sut).ToList(); + + errors.ShouldBeEquivalentTo( + new List + { + new ValidationError("Max height must be greater than min height.", "MinHeight", "MaxHeight") + }); + } + + [Fact] + public void Should_add_error_if_min_size_greater_than_max_size() + { + var sut = new AssetsFieldProperties { MinSize = 10, MaxSize = 5 }; + + var errors = FieldPropertiesValidator.Validate(sut).ToList(); + + errors.ShouldBeEquivalentTo( + new List + { + new ValidationError("Max size must be greater than min size.", "MinSize", "MaxSize") + }); + } + + [Fact] + public void Should_add_error_if_only_aspect_width_is_defined() + { + var sut = new AssetsFieldProperties { AspectWidth = 10 }; + + var errors = FieldPropertiesValidator.Validate(sut).ToList(); + + errors.ShouldBeEquivalentTo( + new List + { + new ValidationError("Aspect width and height must be defined.", "AspectWidth", "AspectHeight") + }); + } + + [Fact] + public void Should_add_error_if_only_aspect_height_is_defined() + { + var sut = new AssetsFieldProperties { AspectHeight = 10 }; + + var errors = FieldPropertiesValidator.Validate(sut).ToList(); + + errors.ShouldBeEquivalentTo( + new List + { + new ValidationError("Aspect width and height must be defined.", "AspectWidth", "AspectHeight") + }); + } } }