// Copyright (c) Umbraco. // See LICENSE for more details. using Microsoft.Extensions.Options; using Moq; using NUnit.Framework; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Dictionary; 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.PropertyEditors.Validators; using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Strings; using Umbraco.Cms.Infrastructure.Serialization; namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Services; [TestFixture] public class PropertyValidationServiceTests { private IShortStringHelper ShortStringHelper => new DefaultShortStringHelper(new DefaultShortStringHelperConfig()); private void MockObjects(out PropertyValidationService validationService, out IDataType dt) { var dataTypeService = new Mock(); var dataType = Mock.Of(x => x.ConfigurationObject == string.Empty // irrelevant but needs a value && x.DatabaseType == ValueStorageType.Nvarchar && x.EditorAlias == Constants.PropertyEditors.Aliases.TextBox); dataTypeService.Setup(x => x.GetDataType(It.IsAny())).Returns(() => dataType); dt = dataType; // new data editor that returns a TextOnlyValueEditor which will do the validation for the properties var dataEditor = Mock.Of(x => x.Alias == Constants.PropertyEditors.Aliases.TextBox); Mock.Get(dataEditor).Setup(x => x.GetValueEditor(It.IsAny())) .Returns(new CustomTextOnlyValueEditor( new DataEditorAttribute(Constants.PropertyEditors.Aliases.TextBox), Mock.Of(), new SystemTextJsonSerializer(new DefaultJsonSerializerEncoderFactory()), Mock.Of())); var languageService = new Mock(); languageService .Setup(s => s.GetDefaultIsoCodeAsync()) .ReturnsAsync(() => "en-US"); var propEditors = new PropertyEditorCollection(new DataEditorCollection(() => [dataEditor])); var contentSettings = new Mock>(); contentSettings.Setup(x => x.Value).Returns(new ContentSettings()); validationService = new PropertyValidationService( propEditors, dataTypeService.Object, Mock.Of(), new ValueEditorCache(), Mock.Of(), languageService.Object, contentSettings.Object); } [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( 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( 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() { 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( 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( 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( 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); } [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()); } // 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) : base(attribute, Mock.Of(), shortStringHelper, jsonSerializer, ioHelper) { } public override IValueRequiredValidator RequiredValidator => new RequiredValidator(); public override IValueFormatValidator FormatValidator => new RegexValidator(); } }