V15: Serverside Media Picker Validation (#18429)
* Add TypedJsonValidator to avoid duplicate serialization * Add allowed type validator * Validate multiple media toggle * Add startnode validator * Fix tests * Add validation tests * Apply suggestions from code review Co-authored-by: Andy Butland <abutland73@gmail.com> * Add XML docs * Remove unnecessary obsolete constructor * Avoid multiple checks * Use value instead of specific member names * Remove test * Optimize StartNodeValidator * Clarify Validates_Allowed_Type --------- Co-authored-by: Andy Butland <abutland73@gmail.com>
This commit is contained in:
@@ -113,6 +113,9 @@ Mange hilsner fra Umbraco robotten
|
||||
<key alias="entriesShort"><![CDATA[Minimum %0% element(er), tilføj <strong>%1%</strong> mere.]]></key>
|
||||
<key alias="entriesExceed"><![CDATA[Maksimum %0% element(er), <strong>%1%</strong> for mange.]]></key>
|
||||
<key alias="entriesAreasMismatch">Ét eller flere områder lever ikke op til kravene for antal indholdselementer.</key>
|
||||
<key alias="invalidMediaType">Den valgte medie type er ugyldig.</key>
|
||||
<key alias="multipleMediaNotAllowed">Det er kun tilladt at vælge ét medie.</key>
|
||||
<key alias="invalidStartNode">Valgt medie kommer fra en ugyldig mappe.</key>
|
||||
</area>
|
||||
<area alias="recycleBin">
|
||||
<key alias="contentTrashed">Slettet indhold med Id: {0} Relateret til original "parent" med id: {1}</key>
|
||||
@@ -124,4 +127,4 @@ Mange hilsner fra Umbraco robotten
|
||||
<key alias="FileWriting">Filskrivning</key>
|
||||
<key alias="MediaFolderCreation">Mediemappeoprettelse</key>
|
||||
</area>
|
||||
</language>
|
||||
</language>
|
||||
|
||||
@@ -387,6 +387,9 @@
|
||||
<key alias="duplicateUserGroupName">User group name '%0%' is already taken</key>
|
||||
<key alias="duplicateMemberGroupName">Member group name '%0%' is already taken</key>
|
||||
<key alias="duplicateUsername">Username '%0%' is already taken</key>
|
||||
<key alias="invalidMediaType">The chosen media type is invalid.</key>
|
||||
<key alias="multipleMediaNotAllowed">Multiple selected media is not allowed.</key>
|
||||
<key alias="invalidStartNode">The selected media is from the wrong folder.</key>
|
||||
</area>
|
||||
<area alias="healthcheck">
|
||||
<!-- The following keys get these tokens passed in:
|
||||
|
||||
@@ -388,6 +388,9 @@
|
||||
<key alias="entriesShort"><![CDATA[Minimum %0% entries, requires <strong>%1%</strong> more.]]></key>
|
||||
<key alias="entriesExceed"><![CDATA[Maximum %0% entries, <strong>%1%</strong> too many.]]></key>
|
||||
<key alias="entriesAreasMismatch">The content amount requirements are not met for one or more areas.</key>
|
||||
<key alias="invalidMediaType">The chosen media type is invalid.</key>
|
||||
<key alias="multipleMediaNotAllowed">Multiple selected media is not allowed.</key>
|
||||
<key alias="invalidStartNode">The selected media is from the wrong folder.</key>
|
||||
</area>
|
||||
<area alias="healthcheck">
|
||||
<!-- The following keys get these tokens passed in:
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Umbraco.Cms.Core.Models.Validation;
|
||||
|
||||
namespace Umbraco.Cms.Core.PropertyEditors.Validation;
|
||||
|
||||
/// <summary>
|
||||
/// A specific validator used for JSON based value editors, to avoid doing multiple deserialization.
|
||||
/// <remarks>Is used together with <see cref="TypedJsonValidatorRunner{TValue,TConfiguration}"/></remarks>
|
||||
/// </summary>
|
||||
/// <typeparam name="TValue">The type of the value consumed by the validator.</typeparam>
|
||||
/// <typeparam name="TConfiguration">The type of the configuration consumed by validator.</typeparam>
|
||||
public interface ITypedJsonValidator<TValue, TConfiguration>
|
||||
{
|
||||
public abstract IEnumerable<ValidationResult> Validate(
|
||||
TValue? value,
|
||||
TConfiguration? configuration,
|
||||
string? valueType,
|
||||
PropertyValidationContext validationContext);
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Umbraco.Cms.Core.Models.Validation;
|
||||
using Umbraco.Cms.Core.Serialization;
|
||||
|
||||
namespace Umbraco.Cms.Core.PropertyEditors.Validation;
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// An aggregate validator for JSON based value editors, to avoid doing multiple deserialization.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Will deserialize once, and cast the configuration once, and pass those values to each <see cref="ITypedJsonValidator{TValue,TConfiguration}"/>, aggregating the results.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <typeparam name="TValue">The type of the expected value.</typeparam>
|
||||
/// <typeparam name="TConfiguration">The type of the expected configuration</typeparam>
|
||||
public class TypedJsonValidatorRunner<TValue, TConfiguration> : IValueValidator
|
||||
where TValue : class
|
||||
{
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
private readonly ITypedJsonValidator<TValue, TConfiguration>[] _validators;
|
||||
|
||||
public TypedJsonValidatorRunner(IJsonSerializer jsonSerializer, params ITypedJsonValidator<TValue, TConfiguration>[] validators)
|
||||
{
|
||||
_jsonSerializer = jsonSerializer;
|
||||
_validators = validators;
|
||||
}
|
||||
|
||||
public IEnumerable<ValidationResult> Validate(object? value, string? valueType, object? dataTypeConfiguration,
|
||||
PropertyValidationContext validationContext)
|
||||
{
|
||||
var validationResults = new List<ValidationResult>();
|
||||
|
||||
if (dataTypeConfiguration is not TConfiguration configuration)
|
||||
{
|
||||
return validationResults;
|
||||
}
|
||||
|
||||
if (value is null || _jsonSerializer.TryDeserialize(value, out TValue? deserializedValue) is false)
|
||||
{
|
||||
return validationResults;
|
||||
}
|
||||
|
||||
foreach (ITypedJsonValidator<TValue, TConfiguration> validator in _validators)
|
||||
{
|
||||
validationResults.AddRange(validator.Validate(deserializedValue, configuration, valueType, validationContext));
|
||||
}
|
||||
|
||||
return validationResults;
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,16 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Cache;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.IO;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Editors;
|
||||
using Umbraco.Cms.Core.Models.TemporaryFile;
|
||||
using Umbraco.Cms.Core.Models.Validation;
|
||||
using Umbraco.Cms.Core.PropertyEditors.Validation;
|
||||
using Umbraco.Cms.Core.PropertyEditors.ValueConverters;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Serialization;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Services.Navigation;
|
||||
using Umbraco.Cms.Core.Strings;
|
||||
using Umbraco.Cms.Infrastructure.Scoping;
|
||||
using Umbraco.Extensions;
|
||||
@@ -71,7 +71,9 @@ public class MediaPicker3PropertyEditor : DataEditor
|
||||
IScopeProvider scopeProvider,
|
||||
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
|
||||
IDataTypeConfigurationCache dataTypeReadCache,
|
||||
ILocalizedTextService localizedTextService)
|
||||
ILocalizedTextService localizedTextService,
|
||||
IMediaTypeService mediaTypeService,
|
||||
IMediaNavigationQueryService mediaNavigationQueryService)
|
||||
: base(shortStringHelper, jsonSerializer, ioHelper, attribute)
|
||||
{
|
||||
_jsonSerializer = jsonSerializer;
|
||||
@@ -81,34 +83,13 @@ public class MediaPicker3PropertyEditor : DataEditor
|
||||
_scopeProvider = scopeProvider;
|
||||
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
|
||||
_dataTypeReadCache = dataTypeReadCache;
|
||||
Validators.Add(new MinMaxValidator(jsonSerializer, localizedTextService));
|
||||
}
|
||||
|
||||
[Obsolete("Use non obsoleted constructor instead. Scheduled for removal in v17")]
|
||||
public MediaPicker3PropertyValueEditor(
|
||||
IShortStringHelper shortStringHelper,
|
||||
IJsonSerializer jsonSerializer,
|
||||
IIOHelper ioHelper,
|
||||
DataEditorAttribute attribute,
|
||||
IMediaImportService mediaImportService,
|
||||
IMediaService mediaService,
|
||||
ITemporaryFileService temporaryFileService,
|
||||
IScopeProvider scopeProvider,
|
||||
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
|
||||
IDataTypeConfigurationCache dataTypeReadCache)
|
||||
: this(
|
||||
shortStringHelper,
|
||||
var validators = new TypedJsonValidatorRunner<List<MediaWithCropsDto>, MediaPicker3Configuration>(
|
||||
jsonSerializer,
|
||||
ioHelper,
|
||||
attribute,
|
||||
mediaImportService,
|
||||
mediaService,
|
||||
temporaryFileService,
|
||||
scopeProvider,
|
||||
backOfficeSecurityAccessor,
|
||||
dataTypeReadCache,
|
||||
StaticServiceProvider.Instance.GetRequiredService<ILocalizedTextService>())
|
||||
{
|
||||
new MinMaxValidator(localizedTextService),
|
||||
new AllowedTypeValidator(localizedTextService, mediaTypeService),
|
||||
new StartNodeValidator(localizedTextService, mediaNavigationQueryService));
|
||||
|
||||
Validators.Add(validators);
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
@@ -328,34 +309,30 @@ public class MediaPicker3PropertyEditor : DataEditor
|
||||
}
|
||||
}
|
||||
|
||||
private class MinMaxValidator : IValueValidator
|
||||
internal class MinMaxValidator : ITypedJsonValidator<List<MediaWithCropsDto>, MediaPicker3Configuration>
|
||||
{
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
private readonly ILocalizedTextService _localizedTextService;
|
||||
|
||||
public MinMaxValidator(IJsonSerializer jsonSerializer, ILocalizedTextService localizedTextService)
|
||||
{
|
||||
_jsonSerializer = jsonSerializer;
|
||||
_localizedTextService = localizedTextService;
|
||||
}
|
||||
public MinMaxValidator(ILocalizedTextService localizedTextService) => _localizedTextService = localizedTextService;
|
||||
|
||||
public IEnumerable<ValidationResult> Validate(
|
||||
object? value,
|
||||
List<MediaWithCropsDto>? mediaWithCropsDtos,
|
||||
MediaPicker3Configuration? mediaPickerConfiguration,
|
||||
string? valueType,
|
||||
object? dataTypeConfiguration,
|
||||
PropertyValidationContext validationContext)
|
||||
{
|
||||
var validationResults = new List<ValidationResult>();
|
||||
|
||||
if (dataTypeConfiguration is not MediaPicker3Configuration mediaPickerConfiguration)
|
||||
if (mediaWithCropsDtos is null || mediaPickerConfiguration is null)
|
||||
{
|
||||
return validationResults;
|
||||
}
|
||||
|
||||
if (value is null ||
|
||||
_jsonSerializer.TryDeserialize(value, out List<MediaWithCropsDto>? mediaWithCropsDtos) is false)
|
||||
if (mediaPickerConfiguration.Multiple is false && mediaWithCropsDtos.Count > 1)
|
||||
{
|
||||
return validationResults;
|
||||
validationResults.Add(new ValidationResult(
|
||||
_localizedTextService.Localize("validation", "multipleMediaNotAllowed"),
|
||||
["value"]));
|
||||
}
|
||||
|
||||
if (mediaPickerConfiguration.ValidationLimit.Min is not null
|
||||
@@ -365,8 +342,9 @@ public class MediaPicker3PropertyEditor : DataEditor
|
||||
_localizedTextService.Localize(
|
||||
"validation",
|
||||
"entriesShort",
|
||||
new[] { mediaPickerConfiguration.ValidationLimit.Min.ToString(), (mediaPickerConfiguration.ValidationLimit.Min - mediaWithCropsDtos.Count).ToString(), }),
|
||||
new[] { "validationLimit" }));
|
||||
[mediaPickerConfiguration.ValidationLimit.Min.ToString(), (mediaPickerConfiguration.ValidationLimit.Min - mediaWithCropsDtos.Count).ToString()
|
||||
]),
|
||||
["value"]));
|
||||
}
|
||||
|
||||
if (mediaPickerConfiguration.ValidationLimit.Max is not null
|
||||
@@ -376,12 +354,141 @@ public class MediaPicker3PropertyEditor : DataEditor
|
||||
_localizedTextService.Localize(
|
||||
"validation",
|
||||
"entriesExceed",
|
||||
new[] { mediaPickerConfiguration.ValidationLimit.Max.ToString(), (mediaWithCropsDtos.Count - mediaPickerConfiguration.ValidationLimit.Max).ToString(), }),
|
||||
new[] { "validationLimit" }));
|
||||
[mediaPickerConfiguration.ValidationLimit.Max.ToString(), (mediaWithCropsDtos.Count - mediaPickerConfiguration.ValidationLimit.Max).ToString()
|
||||
]),
|
||||
["value"]));
|
||||
}
|
||||
|
||||
return validationResults;
|
||||
}
|
||||
}
|
||||
|
||||
internal class AllowedTypeValidator : ITypedJsonValidator<List<MediaWithCropsDto>, MediaPicker3Configuration>
|
||||
{
|
||||
private readonly ILocalizedTextService _localizedTextService;
|
||||
private readonly IMediaTypeService _mediaTypeService;
|
||||
|
||||
public AllowedTypeValidator(ILocalizedTextService localizedTextService, IMediaTypeService mediaTypeService)
|
||||
{
|
||||
_localizedTextService = localizedTextService;
|
||||
_mediaTypeService = mediaTypeService;
|
||||
}
|
||||
|
||||
public IEnumerable<ValidationResult> Validate(
|
||||
List<MediaWithCropsDto>? value,
|
||||
MediaPicker3Configuration? configuration,
|
||||
string? valueType,
|
||||
PropertyValidationContext validationContext)
|
||||
{
|
||||
if (value is null || configuration is null)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
var allowedTypes = configuration.Filter?.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
// No allowed types = all types are allowed
|
||||
if (allowedTypes is null || allowedTypes.Length == 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
IEnumerable<string> distinctTypeAliases = value.DistinctBy(x => x.MediaTypeAlias).Select(x => x.MediaTypeAlias);
|
||||
|
||||
foreach (var typeAlias in distinctTypeAliases)
|
||||
{
|
||||
IMediaType? type = _mediaTypeService.Get(typeAlias);
|
||||
|
||||
if (type is null || allowedTypes.Contains(type.Key.ToString()) is false)
|
||||
{
|
||||
return
|
||||
[
|
||||
new ValidationResult(
|
||||
_localizedTextService.Localize("validation", "invalidMediaType"),
|
||||
["value"])
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
internal class StartNodeValidator : ITypedJsonValidator<List<MediaWithCropsDto>, MediaPicker3Configuration>
|
||||
{
|
||||
private readonly ILocalizedTextService _localizedTextService;
|
||||
private readonly IMediaNavigationQueryService _mediaNavigationQueryService;
|
||||
|
||||
public StartNodeValidator(
|
||||
ILocalizedTextService localizedTextService,
|
||||
IMediaNavigationQueryService mediaNavigationQueryService)
|
||||
{
|
||||
_localizedTextService = localizedTextService;
|
||||
_mediaNavigationQueryService = mediaNavigationQueryService;
|
||||
}
|
||||
|
||||
public IEnumerable<ValidationResult> Validate(
|
||||
List<MediaWithCropsDto>? value,
|
||||
MediaPicker3Configuration? configuration,
|
||||
string? valueType,
|
||||
PropertyValidationContext validationContext)
|
||||
{
|
||||
if (value is null || configuration?.StartNodeId is null)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
|
||||
List<Guid> uniqueParentKeys = [];
|
||||
foreach (Guid distinctMediaKey in value.DistinctBy(x => x.MediaKey).Select(x => x.MediaKey))
|
||||
{
|
||||
if (_mediaNavigationQueryService.TryGetParentKey(distinctMediaKey, out Guid? parentKey) is false)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// If there is a start node, the media must have a parent.
|
||||
if (parentKey is null)
|
||||
{
|
||||
return
|
||||
[
|
||||
new ValidationResult(
|
||||
_localizedTextService.Localize("validation", "invalidStartNode"),
|
||||
["value"])
|
||||
];
|
||||
}
|
||||
|
||||
uniqueParentKeys.Add(parentKey.Value);
|
||||
}
|
||||
|
||||
IEnumerable<Guid> parentKeysNotInStartNode = uniqueParentKeys.Where(x => x != configuration.StartNodeId.Value);
|
||||
foreach (Guid parentKey in parentKeysNotInStartNode)
|
||||
{
|
||||
if (_mediaNavigationQueryService.TryGetAncestorsKeys(parentKey, out IEnumerable<Guid> foundAncestorKeys) is false)
|
||||
{
|
||||
// We couldn't find the parent node, so we fail.
|
||||
return
|
||||
[
|
||||
new ValidationResult(
|
||||
_localizedTextService.Localize("validation", "invalidStartNode"),
|
||||
["value"])
|
||||
];
|
||||
}
|
||||
|
||||
Guid[] ancestorKeys = foundAncestorKeys.ToArray();
|
||||
if (ancestorKeys.Length == 0 || ancestorKeys.Contains(configuration.StartNodeId.Value) is false)
|
||||
{
|
||||
return
|
||||
[
|
||||
new ValidationResult(
|
||||
_localizedTextService.Localize("validation", "invalidStartNode"),
|
||||
["value"])
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ using Umbraco.Cms.Core.PropertyEditors;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Serialization;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Services.Navigation;
|
||||
using Umbraco.Cms.Core.Strings;
|
||||
using Umbraco.Cms.Infrastructure.Scoping;
|
||||
using Umbraco.Cms.Infrastructure.Serialization;
|
||||
@@ -37,7 +38,9 @@ public class DataValueReferenceFactoryCollectionTests
|
||||
Mock.Of<IScopeProvider>(),
|
||||
Mock.Of<IBackOfficeSecurityAccessor>(),
|
||||
Mock.Of<IDataTypeConfigurationCache>(),
|
||||
Mock.Of<ILocalizedTextService>()));
|
||||
Mock.Of<ILocalizedTextService>(),
|
||||
Mock.Of<IMediaTypeService>(),
|
||||
Mock.Of<IMediaNavigationQueryService>()));
|
||||
|
||||
private IIOHelper IOHelper { get; } = Mock.Of<IIOHelper>();
|
||||
|
||||
|
||||
@@ -0,0 +1,215 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core.Cache;
|
||||
using Umbraco.Cms.Core.IO;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Validation;
|
||||
using Umbraco.Cms.Core.PropertyEditors;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Services.Navigation;
|
||||
using Umbraco.Cms.Core.Strings;
|
||||
using Umbraco.Cms.Infrastructure.Scoping;
|
||||
using Umbraco.Cms.Infrastructure.Serialization;
|
||||
|
||||
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors;
|
||||
|
||||
[TestFixture]
|
||||
internal class MediaPicker3ValueEditorValidationTests
|
||||
{
|
||||
[TestCase(true, true)]
|
||||
[TestCase(false, false)]
|
||||
public void Validates_Start_Node_Immediate_Parent(bool shouldSucceed, bool hasValidParentKey)
|
||||
{
|
||||
var (valueEditor, mediaTypeServiceMock, mediaNavigationQueryServiceMock) = CreateValueEditor();
|
||||
|
||||
Guid? validParentKey = Guid.NewGuid();
|
||||
var mediaKey = Guid.NewGuid();
|
||||
|
||||
if (hasValidParentKey)
|
||||
{
|
||||
mediaNavigationQueryServiceMock.Setup(x => x.TryGetParentKey(mediaKey, out validParentKey)).Returns(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
Guid? invalidParentKey = Guid.NewGuid();
|
||||
mediaNavigationQueryServiceMock.Setup(x => x.TryGetParentKey(mediaKey, out invalidParentKey)).Returns(true);
|
||||
}
|
||||
|
||||
valueEditor.ConfigurationObject = new MediaPicker3Configuration { StartNodeId = validParentKey };
|
||||
|
||||
var value = "[ {\n \" key\" : \"20266ebe-1f7e-4cf3-a694-7a5fb210223b\",\n \"mediaKey\" : \"" + mediaKey + "\",\n \"mediaTypeAlias\" : \"\",\n \"crops\" : [ ],\n \"focalPoint\" : null\n} ]";
|
||||
|
||||
var result = valueEditor.Validate(value, false, null, PropertyValidationContext.Empty());
|
||||
|
||||
ValidateResult(shouldSucceed, result);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Validates_Start_Node_Parent_Not_Found()
|
||||
{
|
||||
var (valueEditor, mediaTypeServiceMock, mediaNavigationQueryServiceMock) = CreateValueEditor();
|
||||
|
||||
Guid? parentKey = null;
|
||||
var mediaKey = Guid.NewGuid();
|
||||
|
||||
mediaNavigationQueryServiceMock.Setup(x => x.TryGetParentKey(mediaKey, out parentKey)).Returns(true);
|
||||
|
||||
valueEditor.ConfigurationObject = new MediaPicker3Configuration { StartNodeId = Guid.NewGuid() };
|
||||
|
||||
var value = "[ {\n \" key\" : \"20266ebe-1f7e-4cf3-a694-7a5fb210223b\",\n \"mediaKey\" : \"" + mediaKey + "\",\n \"mediaTypeAlias\" : \"\",\n \"crops\" : [ ],\n \"focalPoint\" : null\n} ]";
|
||||
|
||||
var result = valueEditor.Validate(value, false, null, PropertyValidationContext.Empty());
|
||||
|
||||
ValidateResult(false, result);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(true, true, true)]
|
||||
[TestCase(false, false, true)]
|
||||
[TestCase(false, true, false)]
|
||||
public void Validates_Start_Node_Ancestor(bool shouldSucceed, bool findsAncestor, bool hasValidAncestorKey)
|
||||
{
|
||||
var (valueEditor, mediaTypeServiceMock, mediaNavigationQueryServiceMock) = CreateValueEditor();
|
||||
|
||||
Guid ancestorKey = Guid.NewGuid();
|
||||
Guid? parentKey = Guid.NewGuid();
|
||||
var mediaKey = Guid.NewGuid();
|
||||
IEnumerable<Guid> ancestorKeys = findsAncestor is false ? [] : hasValidAncestorKey ? [ancestorKey] : [Guid.NewGuid()];
|
||||
|
||||
mediaNavigationQueryServiceMock.Setup(x => x.TryGetParentKey(mediaKey, out parentKey)).Returns(true);
|
||||
mediaNavigationQueryServiceMock.Setup(x => x.TryGetAncestorsKeys(parentKey.Value, out ancestorKeys)).Returns(true);
|
||||
|
||||
valueEditor.ConfigurationObject = new MediaPicker3Configuration { StartNodeId = ancestorKey };
|
||||
|
||||
var value = "[ {\n \" key\" : \"20266ebe-1f7e-4cf3-a694-7a5fb210223b\",\n \"mediaKey\" : \"" + mediaKey + "\",\n \"mediaTypeAlias\" : \"\",\n \"crops\" : [ ],\n \"focalPoint\" : null\n} ]";
|
||||
|
||||
var result = valueEditor.Validate(value, false, null, PropertyValidationContext.Empty());
|
||||
|
||||
ValidateResult(shouldSucceed, result);
|
||||
}
|
||||
|
||||
[TestCase(true, true, true)]
|
||||
[TestCase(false, true, false)]
|
||||
[TestCase(false, false, true)]
|
||||
public void Validates_Allowed_Type(bool shouldSucceed, bool hasAllowedType, bool findsMediaType)
|
||||
{
|
||||
var (valueEditor, mediaTypeServiceMock, mediaNavigationQueryServiceMock) = CreateValueEditor();
|
||||
|
||||
var mediaKey = Guid.NewGuid();
|
||||
var mediaTypeKey = Guid.NewGuid();
|
||||
var mediaTypeAlias = "Alias";
|
||||
valueEditor.ConfigurationObject = new MediaPicker3Configuration() { Filter = $"{mediaTypeKey}" };
|
||||
var mediaTypeMock = new Mock<IMediaType>();
|
||||
|
||||
if (hasAllowedType)
|
||||
{
|
||||
mediaTypeMock.Setup(x => x.Key).Returns(mediaTypeKey);
|
||||
}
|
||||
else
|
||||
{
|
||||
mediaTypeMock.Setup(x => x.Key).Returns(Guid.NewGuid());
|
||||
}
|
||||
|
||||
if (findsMediaType)
|
||||
{
|
||||
mediaTypeServiceMock.Setup(x => x.Get(mediaTypeAlias)).Returns(mediaTypeMock.Object);
|
||||
}
|
||||
else
|
||||
{
|
||||
mediaTypeServiceMock.Setup(x => x.Get(It.IsAny<string>())).Returns((IMediaType)null);
|
||||
}
|
||||
|
||||
var value = "[ {\n \" key\" : \"20266ebe-1f7e-4cf3-a694-7a5fb210223b\",\n \"mediaKey\" : \"" + mediaKey + "\",\n \"mediaTypeAlias\" : \"" + mediaTypeAlias + "\",\n \"crops\" : [ ],\n \"focalPoint\" : null\n} ]";
|
||||
var result = valueEditor.Validate(value, false, null, PropertyValidationContext.Empty());
|
||||
|
||||
ValidateResult(shouldSucceed, result);
|
||||
}
|
||||
|
||||
[TestCase("[ {\n \" key\" : \"20266ebe-1f7e-4cf3-a694-7a5fb210223b\",\n \"mediaKey\" : \"7AD39018-0920-4818-89D3-26F47DBCE62E\",\n \"mediaTypeAlias\" : \"\",\n \"crops\" : [ ],\n \"focalPoint\" : null\n} ]", false, true)]
|
||||
[TestCase("[ {\n \" key\" : \"20266ebe-1f7e-4cf3-a694-7a5fb210223b\",\n \"mediaKey\" : \"7AD39018-0920-4818-89D3-26F47DBCE62E\",\n \"mediaTypeAlias\" : \"\",\n \"crops\" : [ ],\n \"focalPoint\" : null\n}, {\n \" key\" : \"1C70519E-C3AE-4D45-8E48-30B3D02E455E\",\n \"mediaKey\" : \"E243A7E2-8D2E-4DC9-88FB-822350A40142\",\n \"mediaTypeAlias\" : \"\",\n \"crops\" : [ ],\n \"focalPoint\" : null\n} ]", false, false)]
|
||||
[TestCase("[ {\n \" key\" : \"20266ebe-1f7e-4cf3-a694-7a5fb210223b\",\n \"mediaKey\" : \"7AD39018-0920-4818-89D3-26F47DBCE62E\",\n \"mediaTypeAlias\" : \"\",\n \"crops\" : [ ],\n \"focalPoint\" : null\n}, {\n \" key\" : \"1C70519E-C3AE-4D45-8E48-30B3D02E455E\",\n \"mediaKey\" : \"E243A7E2-8D2E-4DC9-88FB-822350A40142\",\n \"mediaTypeAlias\" : \"\",\n \"crops\" : [ ],\n \"focalPoint\" : null\n} ]", true, true)]
|
||||
[TestCase("[]", true, true)]
|
||||
[TestCase("[]", false, true)]
|
||||
public void Validates_Multiple(string value, bool multiple, bool succeed)
|
||||
{
|
||||
var (valueEditor, mediaTypeServiceMock, mediaNavigationQueryServiceMock) = CreateValueEditor();
|
||||
|
||||
valueEditor.ConfigurationObject = new MediaPicker3Configuration() { Multiple = multiple };
|
||||
|
||||
var result = valueEditor.Validate(value, false, null, PropertyValidationContext.Empty());
|
||||
ValidateResult(succeed, result);
|
||||
}
|
||||
|
||||
[TestCase("[ {\n \" key\" : \"20266ebe-1f7e-4cf3-a694-7a5fb210223b\",\n \"mediaKey\" : \"7AD39018-0920-4818-89D3-26F47DBCE62E\",\n \"mediaTypeAlias\" : \"\",\n \"crops\" : [ ],\n \"focalPoint\" : null\n} ]", 2, false)]
|
||||
[TestCase("[ {\n \" key\" : \"20266ebe-1f7e-4cf3-a694-7a5fb210223b\",\n \"mediaKey\" : \"7AD39018-0920-4818-89D3-26F47DBCE62E\",\n \"mediaTypeAlias\" : \"\",\n \"crops\" : [ ],\n \"focalPoint\" : null\n}, {\n \" key\" : \"1C70519E-C3AE-4D45-8E48-30B3D02E455E\",\n \"mediaKey\" : \"E243A7E2-8D2E-4DC9-88FB-822350A40142\",\n \"mediaTypeAlias\" : \"\",\n \"crops\" : [ ],\n \"focalPoint\" : null\n} ]", 1, true)]
|
||||
[TestCase("[ {\n \" key\" : \"20266ebe-1f7e-4cf3-a694-7a5fb210223b\",\n \"mediaKey\" : \"7AD39018-0920-4818-89D3-26F47DBCE62E\",\n \"mediaTypeAlias\" : \"\",\n \"crops\" : [ ],\n \"focalPoint\" : null\n}, {\n \" key\" : \"1C70519E-C3AE-4D45-8E48-30B3D02E455E\",\n \"mediaKey\" : \"E243A7E2-8D2E-4DC9-88FB-822350A40142\",\n \"mediaTypeAlias\" : \"\",\n \"crops\" : [ ],\n \"focalPoint\" : null\n} ]", 2, true)]
|
||||
[TestCase("[ {\n \" key\" : \"20266ebe-1f7e-4cf3-a694-7a5fb210223b\",\n \"mediaKey\" : \"7AD39018-0920-4818-89D3-26F47DBCE62E\",\n \"mediaTypeAlias\" : \"\",\n \"crops\" : [ ],\n \"focalPoint\" : null\n}, {\n \" key\" : \"1C70519E-C3AE-4D45-8E48-30B3D02E455E\",\n \"mediaKey\" : \"E243A7E2-8D2E-4DC9-88FB-822350A40142\",\n \"mediaTypeAlias\" : \"\",\n \"crops\" : [ ],\n \"focalPoint\" : null\n} ]", 3, false)]
|
||||
[TestCase("[]", 1, false)]
|
||||
[TestCase("[]", 0, true)]
|
||||
public void Validates_Min_Limit(string value, int min, bool succeed)
|
||||
{
|
||||
var (valueEditor, mediaTypeServiceMock, mediaNavigationQueryServiceMock) = CreateValueEditor();
|
||||
|
||||
valueEditor.ConfigurationObject = new MediaPicker3Configuration() { Multiple = true, ValidationLimit = new MediaPicker3Configuration.NumberRange { Min = min } };
|
||||
|
||||
var result = valueEditor.Validate(value, false, null, PropertyValidationContext.Empty());
|
||||
|
||||
ValidateResult(succeed, result);
|
||||
}
|
||||
|
||||
[TestCase("[ {\n \" key\" : \"20266ebe-1f7e-4cf3-a694-7a5fb210223b\",\n \"mediaKey\" : \"7AD39018-0920-4818-89D3-26F47DBCE62E\",\n \"mediaTypeAlias\" : \"\",\n \"crops\" : [ ],\n \"focalPoint\" : null\n} ]", 1, true)]
|
||||
[TestCase("[ {\n \" key\" : \"20266ebe-1f7e-4cf3-a694-7a5fb210223b\",\n \"mediaKey\" : \"7AD39018-0920-4818-89D3-26F47DBCE62E\",\n \"mediaTypeAlias\" : \"\",\n \"crops\" : [ ],\n \"focalPoint\" : null\n} ]", 0, false)]
|
||||
[TestCase("[ {\n \" key\" : \"20266ebe-1f7e-4cf3-a694-7a5fb210223b\",\n \"mediaKey\" : \"7AD39018-0920-4818-89D3-26F47DBCE62E\",\n \"mediaTypeAlias\" : \"\",\n \"crops\" : [ ],\n \"focalPoint\" : null\n}, {\n \" key\" : \"1C70519E-C3AE-4D45-8E48-30B3D02E455E\",\n \"mediaKey\" : \"E243A7E2-8D2E-4DC9-88FB-822350A40142\",\n \"mediaTypeAlias\" : \"\",\n \"crops\" : [ ],\n \"focalPoint\" : null\n} ]", 1, false)]
|
||||
[TestCase("[ {\n \" key\" : \"20266ebe-1f7e-4cf3-a694-7a5fb210223b\",\n \"mediaKey\" : \"7AD39018-0920-4818-89D3-26F47DBCE62E\",\n \"mediaTypeAlias\" : \"\",\n \"crops\" : [ ],\n \"focalPoint\" : null\n}, {\n \" key\" : \"1C70519E-C3AE-4D45-8E48-30B3D02E455E\",\n \"mediaKey\" : \"E243A7E2-8D2E-4DC9-88FB-822350A40142\",\n \"mediaTypeAlias\" : \"\",\n \"crops\" : [ ],\n \"focalPoint\" : null\n} ]", 2, true)]
|
||||
[TestCase("[ {\n \" key\" : \"20266ebe-1f7e-4cf3-a694-7a5fb210223b\",\n \"mediaKey\" : \"7AD39018-0920-4818-89D3-26F47DBCE62E\",\n \"mediaTypeAlias\" : \"\",\n \"crops\" : [ ],\n \"focalPoint\" : null\n}, {\n \" key\" : \"1C70519E-C3AE-4D45-8E48-30B3D02E455E\",\n \"mediaKey\" : \"E243A7E2-8D2E-4DC9-88FB-822350A40142\",\n \"mediaTypeAlias\" : \"\",\n \"crops\" : [ ],\n \"focalPoint\" : null\n} ]", 3, true)]
|
||||
[TestCase("[]", 1, true)]
|
||||
[TestCase("[]", 0, true)]
|
||||
public void Validates_Max_Limit(string value, int max, bool succeed)
|
||||
{
|
||||
var (valueEditor, mediaTypeServiceMock, mediaNavigationQueryServiceMock) = CreateValueEditor();
|
||||
|
||||
valueEditor.ConfigurationObject = new MediaPicker3Configuration() { Multiple = true, ValidationLimit = new MediaPicker3Configuration.NumberRange { Max = max } };
|
||||
|
||||
var result = valueEditor.Validate(value, false, null, PropertyValidationContext.Empty());
|
||||
ValidateResult(succeed, result);
|
||||
}
|
||||
|
||||
private static void ValidateResult(bool succeed, IEnumerable<ValidationResult> result)
|
||||
{
|
||||
if (succeed)
|
||||
{
|
||||
Assert.IsEmpty(result);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.That(result.Count(), Is.EqualTo(1));
|
||||
}
|
||||
}
|
||||
|
||||
private static (MediaPicker3PropertyEditor.MediaPicker3PropertyValueEditor ValueEditor, Mock<IMediaTypeService> MediaTypeServiceMock, Mock<IMediaNavigationQueryService> MediaNavigationQueryServiceMock) CreateValueEditor()
|
||||
{
|
||||
var mediaTypeServiceMock = new Mock<IMediaTypeService>();
|
||||
var mediaNavigationQueryServiceMock = new Mock<IMediaNavigationQueryService>();
|
||||
var valueEditor = new MediaPicker3PropertyEditor.MediaPicker3PropertyValueEditor(
|
||||
Mock.Of<IShortStringHelper>(),
|
||||
new SystemTextJsonSerializer(),
|
||||
Mock.Of<IIOHelper>(),
|
||||
new DataEditorAttribute("alias"),
|
||||
Mock.Of<IMediaImportService>(),
|
||||
Mock.Of<IMediaService>(),
|
||||
Mock.Of<ITemporaryFileService>(),
|
||||
Mock.Of<IScopeProvider>(),
|
||||
Mock.Of<IBackOfficeSecurityAccessor>(),
|
||||
Mock.Of<IDataTypeConfigurationCache>(),
|
||||
Mock.Of<ILocalizedTextService>(),
|
||||
mediaTypeServiceMock.Object,
|
||||
mediaNavigationQueryServiceMock.Object)
|
||||
{
|
||||
ConfigurationObject = new MediaPicker3Configuration()
|
||||
};
|
||||
|
||||
return (valueEditor, mediaTypeServiceMock, mediaNavigationQueryServiceMock);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user