mirror of https://github.com/Squidex/squidex.git
committed by
GitHub
35 changed files with 975 additions and 728 deletions
@ -0,0 +1,107 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
using Squidex.Domain.Apps.Core; |
||||
|
using Squidex.Domain.Apps.Core.Contents; |
||||
|
using Squidex.Domain.Apps.Core.Schemas; |
||||
|
using Squidex.Domain.Apps.Core.ValidateContent; |
||||
|
using Squidex.Domain.Apps.Entities.Contents.Repositories; |
||||
|
using Squidex.Infrastructure.Json.Objects; |
||||
|
using Squidex.Infrastructure.Queries; |
||||
|
|
||||
|
namespace Squidex.Extensions.Validation |
||||
|
{ |
||||
|
internal sealed class CompositeUniqueValidator : IValidator |
||||
|
{ |
||||
|
private readonly string tag; |
||||
|
private readonly IContentRepository contentRepository; |
||||
|
|
||||
|
public CompositeUniqueValidator(string tag, IContentRepository contentRepository) |
||||
|
{ |
||||
|
this.tag = tag; |
||||
|
|
||||
|
this.contentRepository = contentRepository; |
||||
|
} |
||||
|
|
||||
|
public async Task ValidateAsync(object value, ValidationContext context, AddError addError) |
||||
|
{ |
||||
|
if (value is NamedContentData data) |
||||
|
{ |
||||
|
var validateableFields = context.Schema.Fields.Where(IsValidateableField); |
||||
|
|
||||
|
var filters = new List<FilterNode<ClrValue>>(); |
||||
|
|
||||
|
foreach (var field in validateableFields) |
||||
|
{ |
||||
|
var fieldValue = TryGetValue(field, data); |
||||
|
|
||||
|
if (fieldValue != null) |
||||
|
{ |
||||
|
filters.Add(ClrFilter.Eq($"data.{field.Name}.iv", fieldValue)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (filters.Count > 0) |
||||
|
{ |
||||
|
var filter = ClrFilter.And(filters); |
||||
|
|
||||
|
var found = await contentRepository.QueryIdsAsync(context.AppId.Id, context.SchemaId.Id, filter); |
||||
|
|
||||
|
if (found.Any(x => x.Id != context.ContentId)) |
||||
|
{ |
||||
|
addError(Enumerable.Empty<string>(), "A content with the same values already exist."); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static ClrValue? TryGetValue(IRootField field, NamedContentData data) |
||||
|
{ |
||||
|
var value = JsonValue.Null; |
||||
|
|
||||
|
if (data.TryGetValue(field.Name, out var fieldValue)) |
||||
|
{ |
||||
|
if (fieldValue.TryGetValue(InvariantPartitioning.Key, out var temp) && temp != null) |
||||
|
{ |
||||
|
value = temp; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
switch (field.RawProperties) |
||||
|
{ |
||||
|
case BooleanFieldProperties _ when value is JsonBoolean boolean: |
||||
|
return boolean.Value; |
||||
|
case BooleanFieldProperties _ when value is JsonNull _: |
||||
|
return ClrValue.Null; |
||||
|
case NumberFieldProperties _ when value is JsonNumber number: |
||||
|
return number.Value; |
||||
|
case NumberFieldProperties _ when value is JsonNull _: |
||||
|
return ClrValue.Null; |
||||
|
case StringFieldProperties _ when value is JsonString @string: |
||||
|
return @string.Value; |
||||
|
case StringFieldProperties _ when value is JsonNull _: |
||||
|
return ClrValue.Null; |
||||
|
case ReferencesFieldProperties _ when value is JsonArray array && array.FirstOrDefault() is JsonString @string: |
||||
|
return @string.Value; |
||||
|
} |
||||
|
|
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
private bool IsValidateableField(IRootField field) |
||||
|
{ |
||||
|
return field.Partitioning == Partitioning.Invariant && field.RawProperties.Tags.Contains(tag) && |
||||
|
(field.RawProperties is BooleanFieldProperties || |
||||
|
field.RawProperties is NumberFieldProperties || |
||||
|
field.RawProperties is ReferencesFieldProperties || |
||||
|
field.RawProperties is StringFieldProperties); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,47 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using Squidex.Domain.Apps.Core.ValidateContent; |
||||
|
using Squidex.Domain.Apps.Entities.Contents.Repositories; |
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Extensions.Validation |
||||
|
{ |
||||
|
public sealed class CompositeUniqueValidatorFactory : IValidatorsFactory |
||||
|
{ |
||||
|
private const string Prefix = "unique:"; |
||||
|
private readonly IContentRepository contentRepository; |
||||
|
|
||||
|
public CompositeUniqueValidatorFactory(IContentRepository contentRepository) |
||||
|
{ |
||||
|
Guard.NotNull(contentRepository); |
||||
|
|
||||
|
this.contentRepository = contentRepository; |
||||
|
} |
||||
|
|
||||
|
public IEnumerable<IValidator> CreateContentValidators(ValidationContext context, FieldValidatorFactory createFieldValidator) |
||||
|
{ |
||||
|
foreach (var validatorTag in ValidatorTags(context.Schema.Properties.Tags)) |
||||
|
{ |
||||
|
yield return new CompositeUniqueValidator(validatorTag, contentRepository); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static IEnumerable<string> ValidatorTags(IEnumerable<string> tags) |
||||
|
{ |
||||
|
foreach (var tag in tags) |
||||
|
{ |
||||
|
if (tag.StartsWith(Prefix, StringComparison.OrdinalIgnoreCase) && tag.Length > Prefix.Length) |
||||
|
{ |
||||
|
yield return tag; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,23 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Microsoft.Extensions.Configuration; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Squidex.Domain.Apps.Core.ValidateContent; |
||||
|
using Squidex.Infrastructure.Plugins; |
||||
|
|
||||
|
namespace Squidex.Extensions.Validation |
||||
|
{ |
||||
|
public sealed class CompositeUniqueValidatorPlugin : IPlugin |
||||
|
{ |
||||
|
public void ConfigureServices(IServiceCollection services, IConfiguration config) |
||||
|
{ |
||||
|
services.AddSingletonAs<CompositeUniqueValidatorFactory>() |
||||
|
.As<IValidatorsFactory>(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,68 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System; |
|
||||
using System.Collections.Generic; |
|
||||
using System.Linq; |
|
||||
using System.Threading.Tasks; |
|
||||
using Squidex.Domain.Apps.Core.Contents; |
|
||||
using Squidex.Domain.Apps.Core.Schemas; |
|
||||
using Squidex.Infrastructure.Validation; |
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Core.ValidateContent |
|
||||
{ |
|
||||
public static class ContentValidationExtensions |
|
||||
{ |
|
||||
public static async Task ValidateAsync(this NamedContentData data, ValidationContext context, Schema schema, PartitionResolver partitionResolver, IList<ValidationError> errors) |
|
||||
{ |
|
||||
var validator = new ContentValidator(schema, partitionResolver, context); |
|
||||
|
|
||||
await validator.ValidateAsync(data); |
|
||||
|
|
||||
foreach (var error in validator.Errors) |
|
||||
{ |
|
||||
errors.Add(error); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public static async Task ValidateAsync(this NamedContentData data, ValidationContext context, Schema schema, PartitionResolver partitionResolver, Func<string> message) |
|
||||
{ |
|
||||
var validator = new ContentValidator(schema, partitionResolver, context); |
|
||||
|
|
||||
await validator.ValidateAsync(data); |
|
||||
|
|
||||
if (validator.Errors.Count > 0) |
|
||||
{ |
|
||||
throw new ValidationException(message(), validator.Errors.ToList()); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public static async Task ValidatePartialAsync(this NamedContentData data, ValidationContext context, Schema schema, PartitionResolver partitionResolver, IList<ValidationError> errors) |
|
||||
{ |
|
||||
var validator = new ContentValidator(schema, partitionResolver, context); |
|
||||
|
|
||||
await validator.ValidatePartialAsync(data); |
|
||||
|
|
||||
foreach (var error in validator.Errors) |
|
||||
{ |
|
||||
errors.Add(error); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public static async Task ValidatePartialAsync(this NamedContentData data, ValidationContext context, Schema schema, PartitionResolver partitionResolver, Func<string> message) |
|
||||
{ |
|
||||
var validator = new ContentValidator(schema, partitionResolver, context); |
|
||||
|
|
||||
await validator.ValidatePartialAsync(data); |
|
||||
|
|
||||
if (validator.Errors.Count > 0) |
|
||||
{ |
|
||||
throw new ValidationException(message(), validator.Errors.ToList()); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,33 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using Squidex.Domain.Apps.Core.Schemas; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.ValidateContent |
||||
|
{ |
||||
|
public delegate IValidator FieldValidatorFactory(IField field); |
||||
|
|
||||
|
public interface IValidatorsFactory |
||||
|
{ |
||||
|
IEnumerable<IValidator> CreateFieldValidators(ValidationContext context, IField field, FieldValidatorFactory createFieldValidator) |
||||
|
{ |
||||
|
yield break; |
||||
|
} |
||||
|
|
||||
|
IEnumerable<IValidator> CreateValueValidators(ValidationContext context, IField field, FieldValidatorFactory createFieldValidator) |
||||
|
{ |
||||
|
yield break; |
||||
|
} |
||||
|
|
||||
|
IEnumerable<IValidator> CreateContentValidators(ValidationContext context, FieldValidatorFactory createFieldValidator) |
||||
|
{ |
||||
|
yield break; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,76 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using Squidex.Domain.Apps.Core.Schemas; |
||||
|
using Squidex.Domain.Apps.Core.ValidateContent; |
||||
|
using Squidex.Domain.Apps.Core.ValidateContent.Validators; |
||||
|
using Squidex.Domain.Apps.Entities.Assets.Repositories; |
||||
|
using Squidex.Domain.Apps.Entities.Contents.Repositories; |
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Contents.Validation |
||||
|
{ |
||||
|
public sealed class DependencyValidatorsFactory : IValidatorsFactory |
||||
|
{ |
||||
|
private readonly IAssetRepository assetRepository; |
||||
|
private readonly IContentRepository contentRepository; |
||||
|
|
||||
|
public DependencyValidatorsFactory(IAssetRepository assetRepository, IContentRepository contentRepository) |
||||
|
{ |
||||
|
Guard.NotNull(assetRepository); |
||||
|
Guard.NotNull(contentRepository); |
||||
|
|
||||
|
this.assetRepository = assetRepository; |
||||
|
this.contentRepository = contentRepository; |
||||
|
} |
||||
|
|
||||
|
public IEnumerable<IValidator> CreateValueValidators(ValidationContext context, IField field, FieldValidatorFactory createFieldValidator) |
||||
|
{ |
||||
|
if (field is IField<AssetsFieldProperties> assetsField) |
||||
|
{ |
||||
|
var checkAssets = new CheckAssets(async ids => |
||||
|
{ |
||||
|
return await assetRepository.QueryAsync(context.AppId.Id, new HashSet<Guid>(ids)); |
||||
|
}); |
||||
|
|
||||
|
yield return new AssetsValidator(assetsField.Properties, checkAssets); |
||||
|
} |
||||
|
|
||||
|
if (field is IField<ReferencesFieldProperties> referencesField) |
||||
|
{ |
||||
|
var checkReferences = new CheckContentsByIds(async ids => |
||||
|
{ |
||||
|
return await contentRepository.QueryIdsAsync(context.AppId.Id, ids, SearchScope.All); |
||||
|
}); |
||||
|
|
||||
|
yield return new ReferencesValidator(referencesField.Properties.SchemaIds, checkReferences); |
||||
|
} |
||||
|
|
||||
|
if (field is IField<NumberFieldProperties> numberField && numberField.Properties.IsUnique) |
||||
|
{ |
||||
|
var checkUniqueness = new CheckUniqueness(async filter => |
||||
|
{ |
||||
|
return await contentRepository.QueryIdsAsync(context.AppId.Id, context.SchemaId.Id, filter); |
||||
|
}); |
||||
|
|
||||
|
yield return new UniqueValidator(checkUniqueness); |
||||
|
} |
||||
|
|
||||
|
if (field is IField<StringFieldProperties> stringField && stringField.Properties.IsUnique) |
||||
|
{ |
||||
|
var checkUniqueness = new CheckUniqueness(async filter => |
||||
|
{ |
||||
|
return await contentRepository.QueryIdsAsync(context.AppId.Id, context.SchemaId.Id, filter); |
||||
|
}); |
||||
|
|
||||
|
yield return new UniqueValidator(checkUniqueness); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,233 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
using FluentAssertions; |
||||
|
using Squidex.Domain.Apps.Core.Assets; |
||||
|
using Squidex.Domain.Apps.Core.Schemas; |
||||
|
using Squidex.Domain.Apps.Core.ValidateContent; |
||||
|
using Squidex.Domain.Apps.Core.ValidateContent.Validators; |
||||
|
using Squidex.Infrastructure.Collections; |
||||
|
using Squidex.Infrastructure.Json.Objects; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Operations.ValidateContent.Validators |
||||
|
{ |
||||
|
public class AssetsValidatorTests |
||||
|
{ |
||||
|
private readonly List<string> errors = new List<string>(); |
||||
|
|
||||
|
public sealed class AssetInfo : IAssetInfo |
||||
|
{ |
||||
|
public Guid AssetId { get; set; } |
||||
|
|
||||
|
public string FileName { get; set; } |
||||
|
|
||||
|
public string FileHash { get; set; } |
||||
|
|
||||
|
public string Slug { get; set; } |
||||
|
|
||||
|
public long FileSize { get; set; } |
||||
|
|
||||
|
public bool IsImage { get; set; } |
||||
|
|
||||
|
public int? PixelWidth { get; set; } |
||||
|
|
||||
|
public int? PixelHeight { get; set; } |
||||
|
|
||||
|
public AssetMetadata Metadata { get; set; } |
||||
|
|
||||
|
public AssetType Type { get; set; } |
||||
|
} |
||||
|
|
||||
|
private readonly AssetInfo document = new AssetInfo |
||||
|
{ |
||||
|
AssetId = Guid.NewGuid(), |
||||
|
FileName = "MyDocument.pdf", |
||||
|
FileSize = 1024 * 4, |
||||
|
Type = AssetType.Unknown |
||||
|
}; |
||||
|
|
||||
|
private readonly AssetInfo image1 = new AssetInfo |
||||
|
{ |
||||
|
AssetId = Guid.NewGuid(), |
||||
|
FileName = "MyImage.png", |
||||
|
FileSize = 1024 * 8, |
||||
|
Type = AssetType.Image, |
||||
|
Metadata = |
||||
|
new AssetMetadata() |
||||
|
.SetPixelWidth(800) |
||||
|
.SetPixelHeight(600) |
||||
|
}; |
||||
|
|
||||
|
private readonly AssetInfo image2 = new AssetInfo |
||||
|
{ |
||||
|
AssetId = Guid.NewGuid(), |
||||
|
FileName = "MyImage.png", |
||||
|
FileSize = 1024 * 8, |
||||
|
Type = AssetType.Image, |
||||
|
Metadata = |
||||
|
new AssetMetadata() |
||||
|
.SetPixelWidth(800) |
||||
|
.SetPixelHeight(600) |
||||
|
}; |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_not_add_error_if_assets_are_valid() |
||||
|
{ |
||||
|
var sut = Validator(new AssetsFieldProperties()); |
||||
|
|
||||
|
await sut.ValidateAsync(CreateValue(document.AssetId), errors); |
||||
|
|
||||
|
Assert.Empty(errors); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_add_error_if_asset_are_not_valid() |
||||
|
{ |
||||
|
var assetId = Guid.NewGuid(); |
||||
|
|
||||
|
var sut = Validator(new AssetsFieldProperties()); |
||||
|
|
||||
|
await sut.ValidateAsync(CreateValue(assetId), errors); |
||||
|
|
||||
|
errors.Should().BeEquivalentTo( |
||||
|
new[] { $"[1]: Id '{assetId}' not found." }); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_not_add_error_if_asset_are_not_valid_but_in_optimized_mode() |
||||
|
{ |
||||
|
var assetId = Guid.NewGuid(); |
||||
|
|
||||
|
var sut = Validator(new AssetsFieldProperties()); |
||||
|
|
||||
|
await sut.ValidateAsync(CreateValue(assetId), errors, updater: c => c.Optimized()); |
||||
|
|
||||
|
Assert.Empty(errors); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_add_error_if_document_is_too_small() |
||||
|
{ |
||||
|
var sut = Validator(new AssetsFieldProperties { MinSize = 5 * 1024 }); |
||||
|
|
||||
|
await sut.ValidateAsync(CreateValue(document.AssetId, image1.AssetId), errors); |
||||
|
|
||||
|
errors.Should().BeEquivalentTo( |
||||
|
new[] { "[1]: \'4 kB\' less than minimum of \'5 kB\'." }); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_add_error_if_document_is_too_big() |
||||
|
{ |
||||
|
var sut = Validator(new AssetsFieldProperties { MaxSize = 5 * 1024 }); |
||||
|
|
||||
|
await sut.ValidateAsync(CreateValue(document.AssetId, image1.AssetId), errors); |
||||
|
|
||||
|
errors.Should().BeEquivalentTo( |
||||
|
new[] { "[2]: \'8 kB\' greater than maximum of \'5 kB\'." }); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_add_error_if_document_is_not_an_image() |
||||
|
{ |
||||
|
var sut = Validator(new AssetsFieldProperties { MustBeImage = true }); |
||||
|
|
||||
|
await sut.ValidateAsync(CreateValue(document.AssetId, image1.AssetId), errors); |
||||
|
|
||||
|
errors.Should().BeEquivalentTo( |
||||
|
new[] { "[1]: Not an image." }); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_add_error_if_image_width_is_too_small() |
||||
|
{ |
||||
|
var sut = Validator(new AssetsFieldProperties { MinWidth = 1000 }); |
||||
|
|
||||
|
await sut.ValidateAsync(CreateValue(document.AssetId, image1.AssetId), errors); |
||||
|
|
||||
|
errors.Should().BeEquivalentTo( |
||||
|
new[] { "[2]: Width \'800px\' less than minimum of \'1000px\'." }); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_add_error_if_image_width_is_too_big() |
||||
|
{ |
||||
|
var sut = Validator(new AssetsFieldProperties { MaxWidth = 700 }); |
||||
|
|
||||
|
await sut.ValidateAsync(CreateValue(document.AssetId, image1.AssetId), errors); |
||||
|
|
||||
|
errors.Should().BeEquivalentTo( |
||||
|
new[] { "[2]: Width \'800px\' greater than maximum of \'700px\'." }); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_add_error_if_image_height_is_too_small() |
||||
|
{ |
||||
|
var sut = Validator(new AssetsFieldProperties { MinHeight = 800 }); |
||||
|
|
||||
|
await sut.ValidateAsync(CreateValue(document.AssetId, image1.AssetId), errors); |
||||
|
|
||||
|
errors.Should().BeEquivalentTo( |
||||
|
new[] { "[2]: Height \'600px\' less than minimum of \'800px\'." }); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_add_error_if_image_height_is_too_big() |
||||
|
{ |
||||
|
var sut = Validator(new AssetsFieldProperties { MaxHeight = 500 }); |
||||
|
|
||||
|
await sut.ValidateAsync(CreateValue(document.AssetId, image1.AssetId), errors); |
||||
|
|
||||
|
errors.Should().BeEquivalentTo( |
||||
|
new[] { "[2]: Height \'600px\' greater than maximum of \'500px\'." }); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_add_error_if_image_has_invalid_aspect_ratio() |
||||
|
{ |
||||
|
var sut = Validator(new AssetsFieldProperties { AspectWidth = 1, AspectHeight = 1 }); |
||||
|
|
||||
|
await sut.ValidateAsync(CreateValue(document.AssetId, image1.AssetId), errors); |
||||
|
|
||||
|
errors.Should().BeEquivalentTo( |
||||
|
new[] { "[2]: Aspect ratio not '1:1'." }); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_add_error_if_image_has_invalid_extension() |
||||
|
{ |
||||
|
var sut = Validator(new AssetsFieldProperties { AllowedExtensions = ReadOnlyCollection.Create("mp4") }); |
||||
|
|
||||
|
await sut.ValidateAsync(CreateValue(document.AssetId, image1.AssetId), errors); |
||||
|
|
||||
|
errors.Should().BeEquivalentTo( |
||||
|
new[] |
||||
|
{ |
||||
|
"[1]: Invalid file extension.", |
||||
|
"[2]: Invalid file extension." |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private static object CreateValue(params Guid[] ids) |
||||
|
{ |
||||
|
return ids.ToList(); |
||||
|
} |
||||
|
|
||||
|
private IValidator Validator(AssetsFieldProperties properties) |
||||
|
{ |
||||
|
return new AssetsValidator(properties, new CheckAssets(ids => |
||||
|
{ |
||||
|
return Task.FromResult<IReadOnlyList<IAssetInfo>>(new List<IAssetInfo> { document, image1, image2 }); |
||||
|
})); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,78 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
using FluentAssertions; |
||||
|
using Squidex.Domain.Apps.Core.ValidateContent.Validators; |
||||
|
using Squidex.Infrastructure.Json.Objects; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Operations.ValidateContent |
||||
|
{ |
||||
|
public class ReferencesValidatorTests |
||||
|
{ |
||||
|
private readonly List<string> errors = new List<string>(); |
||||
|
private readonly Guid schemaId = Guid.NewGuid(); |
||||
|
private readonly Guid ref1 = Guid.NewGuid(); |
||||
|
private readonly Guid ref2 = Guid.NewGuid(); |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_add_error_if_references_are_not_valid() |
||||
|
{ |
||||
|
var sut = new ReferencesValidator(Enumerable.Repeat(schemaId, 1), FoundReferences()); |
||||
|
|
||||
|
await sut.ValidateAsync(CreateValue(ref1), errors); |
||||
|
|
||||
|
errors.Should().BeEquivalentTo( |
||||
|
new[] { $"Contains invalid reference '{ref1}'." }); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_not_add_error_if_reference_are_not_valid_but_in_optimized_mode() |
||||
|
{ |
||||
|
var sut = new ReferencesValidator(Enumerable.Repeat(schemaId, 1), FoundReferences()); |
||||
|
|
||||
|
await sut.ValidateAsync(CreateValue(ref1), errors, updater: c => c.Optimized()); |
||||
|
|
||||
|
Assert.Empty(errors); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_not_add_error_if_schemas_not_defined() |
||||
|
{ |
||||
|
var sut = new ReferencesValidator(null, FoundReferences((Guid.NewGuid(), ref2))); |
||||
|
|
||||
|
await sut.ValidateAsync(CreateValue(ref2), errors); |
||||
|
|
||||
|
Assert.Empty(errors); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_add_error_if_reference_schema_is_not_valid() |
||||
|
{ |
||||
|
var sut = new ReferencesValidator(Enumerable.Repeat(schemaId, 1), FoundReferences((Guid.NewGuid(), ref2))); |
||||
|
|
||||
|
await sut.ValidateAsync(CreateValue(ref2), errors); |
||||
|
|
||||
|
errors.Should().BeEquivalentTo( |
||||
|
new[] { $"Contains reference '{ref2}' to invalid schema." }); |
||||
|
} |
||||
|
|
||||
|
private static List<Guid> CreateValue(params Guid[] ids) |
||||
|
{ |
||||
|
return ids.ToList(); |
||||
|
} |
||||
|
|
||||
|
private static CheckContentsByIds FoundReferences(params (Guid SchemaId, Guid Id)[] references) |
||||
|
{ |
||||
|
return new CheckContentsByIds(x => Task.FromResult<IReadOnlyList<(Guid SchemaId, Guid Id)>>(references.ToList())); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue