2020-12-20 08:36:11 +01:00
|
|
|
// Copyright (c) Umbraco.
|
|
|
|
|
// See LICENSE for more details.
|
|
|
|
|
|
2025-03-25 12:58:01 +01:00
|
|
|
using Microsoft.Extensions.Options;
|
2019-03-28 23:59:49 +11:00
|
|
|
using Moq;
|
|
|
|
|
using NUnit.Framework;
|
2021-02-23 12:24:51 +01:00
|
|
|
using Umbraco.Cms.Core;
|
2021-08-24 11:02:51 +02:00
|
|
|
using Umbraco.Cms.Core.Cache;
|
2025-03-25 12:58:01 +01:00
|
|
|
using Umbraco.Cms.Core.Configuration.Models;
|
2024-03-04 12:50:24 +01:00
|
|
|
using Umbraco.Cms.Core.Dictionary;
|
2021-05-18 12:40:24 +02:00
|
|
|
using Umbraco.Cms.Core.IO;
|
2021-02-09 10:22:42 +01:00
|
|
|
using Umbraco.Cms.Core.Models;
|
2025-09-18 08:55:58 +02:00
|
|
|
using Umbraco.Cms.Core.Models.Validation;
|
2021-02-09 10:22:42 +01:00
|
|
|
using Umbraco.Cms.Core.PropertyEditors;
|
|
|
|
|
using Umbraco.Cms.Core.PropertyEditors.Validators;
|
|
|
|
|
using Umbraco.Cms.Core.Serialization;
|
|
|
|
|
using Umbraco.Cms.Core.Services;
|
|
|
|
|
using Umbraco.Cms.Core.Strings;
|
2021-02-15 12:42:26 +01:00
|
|
|
using Umbraco.Cms.Infrastructure.Serialization;
|
2019-03-28 23:59:49 +11:00
|
|
|
|
2022-06-21 08:09:38 +02:00
|
|
|
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Services;
|
|
|
|
|
|
|
|
|
|
[TestFixture]
|
|
|
|
|
public class PropertyValidationServiceTests
|
2019-03-28 23:59:49 +11:00
|
|
|
{
|
2022-06-21 08:09:38 +02:00
|
|
|
private IShortStringHelper ShortStringHelper => new DefaultShortStringHelper(new DefaultShortStringHelperConfig());
|
|
|
|
|
|
|
|
|
|
private void MockObjects(out PropertyValidationService validationService, out IDataType dt)
|
|
|
|
|
{
|
|
|
|
|
var dataTypeService = new Mock<IDataTypeService>();
|
2025-09-18 08:55:58 +02:00
|
|
|
var dataType = Mock.Of<IDataType>(x => x.ConfigurationObject == string.Empty // irrelevant but needs a value
|
|
|
|
|
&& x.DatabaseType == ValueStorageType.Nvarchar
|
|
|
|
|
&& x.EditorAlias == Constants.PropertyEditors.Aliases.TextBox);
|
2022-06-21 08:09:38 +02:00
|
|
|
dataTypeService.Setup(x => x.GetDataType(It.IsAny<int>())).Returns(() => dataType);
|
|
|
|
|
dt = dataType;
|
|
|
|
|
|
|
|
|
|
// new data editor that returns a TextOnlyValueEditor which will do the validation for the properties
|
2025-09-18 08:55:58 +02:00
|
|
|
var dataEditor = Mock.Of<IDataEditor>(x => x.Alias == Constants.PropertyEditors.Aliases.TextBox);
|
2022-06-21 08:09:38 +02:00
|
|
|
Mock.Get(dataEditor).Setup(x => x.GetValueEditor(It.IsAny<object>()))
|
|
|
|
|
.Returns(new CustomTextOnlyValueEditor(
|
2024-02-15 12:36:23 +01:00
|
|
|
new DataEditorAttribute(Constants.PropertyEditors.Aliases.TextBox),
|
2022-06-21 08:09:38 +02:00
|
|
|
Mock.Of<IShortStringHelper>(),
|
2025-08-07 09:08:09 +02:00
|
|
|
new SystemTextJsonSerializer(new DefaultJsonSerializerEncoderFactory()),
|
2022-06-21 08:09:38 +02:00
|
|
|
Mock.Of<IIOHelper>()));
|
|
|
|
|
|
2025-09-18 08:55:58 +02:00
|
|
|
var languageService = new Mock<ILanguageService>();
|
|
|
|
|
languageService
|
|
|
|
|
.Setup(s => s.GetDefaultIsoCodeAsync())
|
|
|
|
|
.ReturnsAsync(() => "en-US");
|
|
|
|
|
|
|
|
|
|
var propEditors = new PropertyEditorCollection(new DataEditorCollection(() => [dataEditor]));
|
|
|
|
|
|
|
|
|
|
var contentSettings = new Mock<IOptions<ContentSettings>>();
|
|
|
|
|
contentSettings.Setup(x => x.Value).Returns(new ContentSettings());
|
2022-06-21 08:09:38 +02:00
|
|
|
|
2025-03-25 12:58:01 +01:00
|
|
|
validationService = new PropertyValidationService(
|
|
|
|
|
propEditors,
|
|
|
|
|
dataTypeService.Object,
|
|
|
|
|
Mock.Of<ILocalizedTextService>(),
|
|
|
|
|
new ValueEditorCache(),
|
|
|
|
|
Mock.Of<ICultureDictionary>(),
|
2025-09-18 08:55:58 +02:00
|
|
|
languageService.Object,
|
|
|
|
|
contentSettings.Object);
|
2022-06-21 08:09:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Test]
|
|
|
|
|
public void Validate_Invariant_Properties_On_Variant_Default_Culture()
|
|
|
|
|
{
|
|
|
|
|
MockObjects(out var validationService, out var dataType);
|
|
|
|
|
|
|
|
|
|
var p1 = new Property(
|
|
|
|
|
new PropertyType(ShortStringHelper, dataType, "test1")
|
|
|
|
|
{
|
|
|
|
|
Mandatory = true,
|
|
|
|
|
Variations = ContentVariation.Culture,
|
|
|
|
|
});
|
|
|
|
|
p1.SetValue("Hello", "en-US");
|
|
|
|
|
var p2 = new Property(
|
|
|
|
|
new PropertyType(ShortStringHelper, dataType, "test2")
|
|
|
|
|
{
|
|
|
|
|
Mandatory = true,
|
|
|
|
|
Variations = ContentVariation.Nothing,
|
|
|
|
|
});
|
|
|
|
|
p2.SetValue("Hello");
|
|
|
|
|
var p3 = new Property(
|
|
|
|
|
new PropertyType(ShortStringHelper, dataType, "test3")
|
|
|
|
|
{
|
|
|
|
|
Mandatory = true,
|
|
|
|
|
Variations = ContentVariation.Culture,
|
|
|
|
|
});
|
|
|
|
|
p3.SetValue(null, "en-US"); // invalid
|
|
|
|
|
var p4 = new Property(
|
|
|
|
|
new PropertyType(ShortStringHelper, dataType, "test4")
|
|
|
|
|
{
|
|
|
|
|
Mandatory = true,
|
|
|
|
|
Variations = ContentVariation.Nothing,
|
|
|
|
|
});
|
|
|
|
|
p4.SetValue(null); // invalid
|
|
|
|
|
|
|
|
|
|
var content = Mock.Of<IContent>(
|
|
|
|
|
x => x.Published == true // set to published, the default culture will validate invariant anyways
|
|
|
|
|
&& x.Properties == new PropertyCollection(new[] { p1, p2, p3, p4 }));
|
|
|
|
|
|
|
|
|
|
var result =
|
|
|
|
|
validationService.IsPropertyDataValid(content, out var invalid, CultureImpact.Explicit("en-US", true));
|
|
|
|
|
|
|
|
|
|
Assert.IsFalse(result);
|
|
|
|
|
Assert.AreEqual(2, invalid.Length);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Test]
|
|
|
|
|
public void Validate_Invariant_Properties_On_Variant_Non_Default_Culture()
|
|
|
|
|
{
|
|
|
|
|
MockObjects(out var validationService, out var dataType);
|
|
|
|
|
|
|
|
|
|
var p1 = new Property(
|
|
|
|
|
new PropertyType(ShortStringHelper, dataType, "test1")
|
|
|
|
|
{
|
|
|
|
|
Mandatory = true,
|
|
|
|
|
Variations = ContentVariation.Culture,
|
|
|
|
|
});
|
|
|
|
|
p1.SetValue("Hello", "en-US");
|
|
|
|
|
var p2 = new Property(
|
|
|
|
|
new PropertyType(ShortStringHelper, dataType, "test2")
|
|
|
|
|
{
|
|
|
|
|
Mandatory = true,
|
|
|
|
|
Variations = ContentVariation.Nothing,
|
|
|
|
|
});
|
|
|
|
|
p2.SetValue("Hello");
|
|
|
|
|
var p3 = new Property(
|
|
|
|
|
new PropertyType(ShortStringHelper, dataType, "test3")
|
|
|
|
|
{
|
|
|
|
|
Mandatory = true,
|
|
|
|
|
Variations = ContentVariation.Culture,
|
|
|
|
|
});
|
|
|
|
|
p3.SetValue(null, "en-US"); // invalid
|
|
|
|
|
var p4 = new Property(
|
|
|
|
|
new PropertyType(ShortStringHelper, dataType, "test4")
|
|
|
|
|
{
|
|
|
|
|
Mandatory = true,
|
|
|
|
|
Variations = ContentVariation.Nothing,
|
|
|
|
|
});
|
|
|
|
|
p4.SetValue(null); // invalid
|
|
|
|
|
|
|
|
|
|
var content = Mock.Of<IContent>(
|
|
|
|
|
x =>
|
|
|
|
|
x.Published ==
|
|
|
|
|
false // set to not published, the non default culture will need to validate invariant too
|
|
|
|
|
&& x.Properties == new PropertyCollection(new[] { p1, p2, p3, p4 }));
|
|
|
|
|
|
|
|
|
|
var result =
|
|
|
|
|
validationService.IsPropertyDataValid(content, out var invalid, CultureImpact.Explicit("en-US", false));
|
|
|
|
|
|
|
|
|
|
Assert.IsFalse(result);
|
|
|
|
|
Assert.AreEqual(2, invalid.Length);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Test]
|
|
|
|
|
public void Validate_Variant_Properties_On_Variant()
|
2019-03-28 23:59:49 +11:00
|
|
|
{
|
2022-06-21 08:09:38 +02:00
|
|
|
MockObjects(out var validationService, out var dataType);
|
|
|
|
|
|
|
|
|
|
var p1 = new Property(
|
|
|
|
|
new PropertyType(ShortStringHelper, dataType, "test1")
|
|
|
|
|
{
|
|
|
|
|
Mandatory = true,
|
|
|
|
|
Variations = ContentVariation.Culture,
|
|
|
|
|
});
|
|
|
|
|
p1.SetValue(null, "en-US"); // invalid
|
|
|
|
|
var p2 = new Property(
|
|
|
|
|
new PropertyType(ShortStringHelper, dataType, "test2")
|
|
|
|
|
{
|
|
|
|
|
Mandatory = true,
|
|
|
|
|
Variations = ContentVariation.Nothing,
|
|
|
|
|
});
|
|
|
|
|
p2.SetValue(null); // invalid
|
|
|
|
|
var p3 = new Property(
|
|
|
|
|
new PropertyType(ShortStringHelper, dataType, "test3")
|
|
|
|
|
{
|
|
|
|
|
Mandatory = true,
|
|
|
|
|
Variations = ContentVariation.Culture,
|
|
|
|
|
});
|
|
|
|
|
p3.SetValue(null, "en-US"); // ignored because the impact isn't the default lang + the content is published
|
|
|
|
|
var p4 = new Property(
|
|
|
|
|
new PropertyType(ShortStringHelper, dataType, "test4")
|
|
|
|
|
{
|
|
|
|
|
Mandatory = true,
|
|
|
|
|
Variations = ContentVariation.Nothing,
|
|
|
|
|
});
|
|
|
|
|
p4.SetValue(null); // ignored because the impact isn't the default lang + the content is published
|
|
|
|
|
|
|
|
|
|
var content = Mock.Of<IContent>(
|
|
|
|
|
x => x.Published == true // set to published
|
|
|
|
|
&& x.Properties == new PropertyCollection(new[] { p1, p2, p3, p4 }));
|
|
|
|
|
|
|
|
|
|
var result =
|
|
|
|
|
validationService.IsPropertyDataValid(content, out var invalid, CultureImpact.Explicit("en-US", false));
|
|
|
|
|
|
|
|
|
|
Assert.IsFalse(result);
|
|
|
|
|
Assert.AreEqual(2, invalid.Length);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Test]
|
|
|
|
|
public void Validate_Invariant_Properties_On_Invariant()
|
|
|
|
|
{
|
|
|
|
|
MockObjects(out var validationService, out var dataType);
|
|
|
|
|
|
|
|
|
|
var p1 = new Property(
|
|
|
|
|
new PropertyType(ShortStringHelper, dataType, "test1")
|
|
|
|
|
{
|
|
|
|
|
Mandatory = true,
|
|
|
|
|
Variations = ContentVariation.Culture,
|
|
|
|
|
});
|
|
|
|
|
p1.SetValue(null, "en-US"); // ignored since this is variant
|
|
|
|
|
var p2 = new Property(
|
|
|
|
|
new PropertyType(ShortStringHelper, dataType, "test2")
|
|
|
|
|
{
|
|
|
|
|
Mandatory = true,
|
|
|
|
|
Variations = ContentVariation.Nothing,
|
|
|
|
|
});
|
|
|
|
|
p2.SetValue(null); // invalid
|
|
|
|
|
var p3 = new Property(
|
|
|
|
|
new PropertyType(ShortStringHelper, dataType, "test3")
|
|
|
|
|
{
|
|
|
|
|
Mandatory = true,
|
|
|
|
|
Variations = ContentVariation.Culture,
|
|
|
|
|
});
|
|
|
|
|
p3.SetValue("Hello", "en-US"); // ignored since this is variant
|
|
|
|
|
var p4 = new Property(
|
|
|
|
|
new PropertyType(ShortStringHelper, dataType, "test4")
|
|
|
|
|
{
|
|
|
|
|
Mandatory = true,
|
|
|
|
|
Variations = ContentVariation.Nothing,
|
|
|
|
|
});
|
|
|
|
|
p4.SetValue(null); // invalid
|
|
|
|
|
|
|
|
|
|
var content = Mock.Of<IContent>(
|
|
|
|
|
x => x.Properties == new PropertyCollection(new[] { p1, p2, p3, p4 }));
|
|
|
|
|
|
|
|
|
|
var result = validationService.IsPropertyDataValid(content, out var invalid, CultureImpact.Invariant);
|
|
|
|
|
|
|
|
|
|
Assert.IsFalse(result);
|
|
|
|
|
Assert.AreEqual(2, invalid.Length);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Test]
|
|
|
|
|
public void Validate_Properties_On_All()
|
|
|
|
|
{
|
|
|
|
|
MockObjects(out var validationService, out var dataType);
|
|
|
|
|
|
|
|
|
|
var p1 = new Property(
|
|
|
|
|
new PropertyType(ShortStringHelper, dataType, "test1")
|
|
|
|
|
{
|
|
|
|
|
Mandatory = true,
|
|
|
|
|
Variations = ContentVariation.Culture,
|
|
|
|
|
});
|
|
|
|
|
p1.SetValue(null, "en-US"); // invalid
|
|
|
|
|
var p2 = new Property(
|
|
|
|
|
new PropertyType(ShortStringHelper, dataType, "test2")
|
|
|
|
|
{
|
|
|
|
|
Mandatory = true,
|
|
|
|
|
Variations = ContentVariation.Nothing,
|
|
|
|
|
});
|
|
|
|
|
p2.SetValue(null); // invalid
|
|
|
|
|
var p3 = new Property(
|
|
|
|
|
new PropertyType(ShortStringHelper, dataType, "test3")
|
|
|
|
|
{
|
|
|
|
|
Mandatory = true,
|
|
|
|
|
Variations = ContentVariation.Culture,
|
|
|
|
|
});
|
|
|
|
|
p3.SetValue(null, "en-US"); // invalid
|
|
|
|
|
var p4 = new Property(
|
|
|
|
|
new PropertyType(ShortStringHelper, dataType, "test4")
|
|
|
|
|
{
|
|
|
|
|
Mandatory = true,
|
|
|
|
|
Variations = ContentVariation.Nothing,
|
|
|
|
|
});
|
|
|
|
|
p4.SetValue(null); // invalid
|
|
|
|
|
|
|
|
|
|
var content = Mock.Of<IContent>(
|
|
|
|
|
x => x.Properties == new PropertyCollection(new[] { p1, p2, p3, p4 }));
|
|
|
|
|
|
|
|
|
|
var result = validationService.IsPropertyDataValid(content, out var invalid, CultureImpact.All);
|
|
|
|
|
|
|
|
|
|
Assert.IsFalse(result);
|
|
|
|
|
Assert.AreEqual(4, invalid.Length);
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-18 08:55:58 +02:00
|
|
|
[TestCase(null)]
|
|
|
|
|
[TestCase(24)]
|
|
|
|
|
[TestCase("test")]
|
|
|
|
|
[TestCase("{\"test\": true}")]
|
|
|
|
|
public void ValidatePropertyValue_Always_Returns_No_Validation_Errors_For_Missing_Editor(object? value)
|
|
|
|
|
{
|
|
|
|
|
MockObjects(out var validationService, out _);
|
|
|
|
|
|
|
|
|
|
var p1 = new PropertyType(ShortStringHelper, "Missing.Alias", ValueStorageType.Ntext)
|
|
|
|
|
{
|
|
|
|
|
Variations = ContentVariation.Nothing,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var result = validationService.ValidatePropertyValue(p1, value, PropertyValidationContext.Empty());
|
|
|
|
|
Assert.AreEqual(0, result.Count());
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-21 08:09:38 +02:00
|
|
|
// used so we can inject a mock - we should fix the base class DataValueEditor to be able to have the ILocalizedTextField passed
|
|
|
|
|
// in to create the Requried and Regex validators so we aren't using singletons
|
|
|
|
|
private class CustomTextOnlyValueEditor : TextOnlyValueEditor
|
|
|
|
|
{
|
|
|
|
|
public CustomTextOnlyValueEditor(
|
|
|
|
|
DataEditorAttribute attribute,
|
|
|
|
|
IShortStringHelper shortStringHelper,
|
|
|
|
|
IJsonSerializer jsonSerializer,
|
|
|
|
|
IIOHelper ioHelper)
|
2025-03-06 13:19:28 +01:00
|
|
|
: base(attribute, Mock.Of<ILocalizedTextService>(), shortStringHelper, jsonSerializer, ioHelper)
|
2024-01-31 10:40:58 +01:00
|
|
|
{
|
|
|
|
|
}
|
2022-06-21 08:09:38 +02:00
|
|
|
|
2024-01-31 10:40:58 +01:00
|
|
|
public override IValueRequiredValidator RequiredValidator => new RequiredValidator();
|
2022-06-21 08:09:38 +02:00
|
|
|
|
2024-01-31 10:40:58 +01:00
|
|
|
public override IValueFormatValidator FormatValidator => new RegexValidator();
|
2019-03-28 23:59:49 +11:00
|
|
|
}
|
|
|
|
|
}
|