Files
Umbraco-CMS/src/Umbraco.Tests/Web/Validation/ContentModelValidatorTests.cs
T

321 lines
14 KiB
C#

using Moq;
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Umbraco.Core.Services;
using Umbraco.Core.Logging;
using Umbraco.Web;
using Umbraco.Web.Editors.Filters;
using Umbraco.Tests.TestHelpers.Entities;
using Umbraco.Core.Models;
using Umbraco.Web.Models.ContentEditing;
using Umbraco.Web.Editors.Binders;
using Umbraco.Core;
using Umbraco.Tests.Testing;
using Umbraco.Core.Mapping;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Composing;
using System.Web.Http.ModelBinding;
using Umbraco.Web.PropertyEditors;
using System.ComponentModel.DataAnnotations;
using Umbraco.Tests.TestHelpers;
using System.Globalization;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Umbraco.Web.PropertyEditors.Validation;
namespace Umbraco.Tests.Web.Validation
{
[UmbracoTest(Mapper = true, WithApplication = true, Logger = UmbracoTestOptions.Logger.Console)]
[TestFixture]
public class ContentModelValidatorTests : UmbracoTestBase
{
private const int ComplexDataTypeId = 9999;
private const string ContentTypeAlias = "textPage";
private ContentType _contentType;
public override void SetUp()
{
base.SetUp();
_contentType = MockedContentTypes.CreateTextPageContentType(ContentTypeAlias);
// add complex editor
_contentType.AddPropertyType(
new PropertyType("complexTest", ValueStorageType.Ntext) { Alias = "complex", Name = "Complex", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = ComplexDataTypeId },
"Content");
// make them all validate with a regex rule that will not pass
foreach (var prop in _contentType.PropertyTypes)
{
prop.ValidationRegExp = "^donotmatch$";
prop.ValidationRegExpMessage = "Does not match!";
}
}
protected override void Compose()
{
base.Compose();
var complexEditorConfig = new NestedContentConfiguration
{
ContentTypes = new[]
{
new NestedContentConfiguration.ContentType { Alias = "feature" }
}
};
var dataTypeService = new Mock<IDataTypeService>();
dataTypeService.Setup(x => x.GetDataType(It.IsAny<int>()))
.Returns((int id) => id == ComplexDataTypeId
? Mock.Of<IDataType>(x => x.Configuration == complexEditorConfig)
: Mock.Of<IDataType>());
var contentTypeService = new Mock<IContentTypeService>();
contentTypeService.Setup(x => x.GetAll(It.IsAny<int[]>()))
.Returns(() => new List<IContentType>
{
_contentType
});
var textService = new Mock<ILocalizedTextService>();
textService.Setup(x => x.Localize("validation/invalidPattern", It.IsAny<CultureInfo>(), It.IsAny<IDictionary<string, string>>())).Returns(() => "invalidPattern");
textService.Setup(x => x.Localize("validation/invalidNull", It.IsAny<CultureInfo>(), It.IsAny<IDictionary<string, string>>())).Returns("invalidNull");
textService.Setup(x => x.Localize("validation/invalidEmpty", It.IsAny<CultureInfo>(), It.IsAny<IDictionary<string, string>>())).Returns("invalidEmpty");
Composition.RegisterUnique(x => Mock.Of<IDataTypeService>(x => x.GetDataType(It.IsAny<int>()) == Mock.Of<IDataType>()));
Composition.RegisterUnique(x => dataTypeService.Object);
Composition.RegisterUnique(x => contentTypeService.Object);
Composition.RegisterUnique(x => textService.Object);
Composition.WithCollectionBuilder<DataEditorCollectionBuilder>()
.Add<TestEditor>()
.Add<ComplexTestEditor>();
}
[Test]
public void TestSerializer()
{
var nestedLevel2 = new NestedValidationResults();
var elementTypeResult2 = new ElementTypeValidationResult("type2");
var propertyTypeResult2 = new PropertyTypeValidationResult("prop2");
propertyTypeResult2.ValidationResults.Add(new ValidationResult("error2-1", new[] { "level2" }));
propertyTypeResult2.ValidationResults.Add(new ValidationResult("error2-2", new[] { "level2" }));
elementTypeResult2.ValidationResults.Add(propertyTypeResult2);
nestedLevel2.ValidationResults.Add(elementTypeResult2);
var nestedLevel1 = new NestedValidationResults();
var elementTypeResult1 = new ElementTypeValidationResult("type1");
var propertyTypeResult1 = new PropertyTypeValidationResult("prop1");
propertyTypeResult1.ValidationResults.Add(new ValidationResult("error1-1", new[] { "level1" }));
propertyTypeResult1.ValidationResults.Add(nestedLevel2); // This is a nested result within the level 1
elementTypeResult1.ValidationResults.Add(propertyTypeResult1);
nestedLevel1.ValidationResults.Add(elementTypeResult1);
var serialized = JsonConvert.SerializeObject(nestedLevel1, Formatting.Indented, new ValidationResultConverter());
Console.WriteLine(serialized);
}
[Test]
public void Test()
{
var validator = new ContentSaveModelValidator(
Factory.GetInstance<ILogger>(),
Mock.Of<IUmbracoContextAccessor>(),
Factory.GetInstance<ILocalizedTextService>());
var content = MockedContent.CreateTextpageContent(_contentType, "test", -1);
// TODO: Ok now test with a 4th level complex nested editor
const string complexValue = @"[{
""key"": ""c8df5136-d606-41f0-9134-dea6ae0c2fd9"",
""name"": ""Hello world"",
""ncContentTypeAlias"": """ + ContentTypeAlias + @""",
""title"": ""Hello world"",
""bodyText"": ""The world is round""
}, {
""key"": ""f916104a-4082-48b2-a515-5c4bf2230f38"",
""name"": ""Super nested"",
""ncContentTypeAlias"": """ + ContentTypeAlias + @""",
""title"": ""Hi there!"",
""bodyText"": ""Well hello there"",
""complex"" : [{
""key"": ""77E15DE9-1C79-47B2-BC60-4913BC4D4C6A"",
""name"": ""I am a sub nested content"",
""ncContentTypeAlias"": """ + ContentTypeAlias + @""",
""title"": ""Hello up there :)"",
""bodyText"": ""Hello way up there on a different level""
}]
}
]";
content.SetValue("complex", complexValue);
// map the persisted properties to a model representing properties to save
//var saveProperties = content.Properties.Select(x => Mapper.Map<ContentPropertyBasic>(x)).ToList();
var saveProperties = content.Properties.Select(x =>
{
return new ContentPropertyBasic
{
Alias = x.Alias,
Id = x.Id,
Value = x.GetValue()
};
}).ToList();
var saveVariants = new List<ContentVariantSave>
{
new ContentVariantSave
{
Culture = string.Empty,
Segment = string.Empty,
Name = content.Name,
Save = true,
Properties = saveProperties
}
};
var save = new ContentItemSave
{
Id = content.Id,
Action = ContentSaveAction.Save,
ContentTypeAlias = _contentType.Alias,
ParentId = -1,
PersistedContent = content,
TemplateAlias = null,
Variants = saveVariants
};
// This will map the ContentItemSave.Variants.PropertyCollectionDto and then map the values in the saved model
// back onto the persisted IContent model.
ContentItemBinder.BindModel(save, content);
var modelState = new ModelStateDictionary();
var isValid = validator.ValidatePropertiesData(save, saveVariants[0], saveVariants[0].PropertyCollectionDto, modelState);
// list results for debugging
foreach (var state in modelState)
{
Console.WriteLine(state.Key);
foreach (var error in state.Value.Errors)
{
Console.WriteLine("\t" + error.ErrorMessage);
}
}
// assert
Assert.IsFalse(isValid);
Assert.AreEqual(5, modelState.Keys.Count);
const string complexPropertyKey = "_Properties.complex.invariant.null";
Assert.IsTrue(modelState.Keys.Contains(complexPropertyKey));
foreach (var state in modelState.Where(x => x.Key != complexPropertyKey))
{
foreach (var error in state.Value.Errors)
{
Assert.IsTrue(error.ErrorMessage.DetectIsJson());
var json = JsonConvert.DeserializeObject<JObject>(error.ErrorMessage);
Assert.IsNotEmpty(json["errorMessage"].Value<string>());
Assert.AreEqual(1, json["memberNames"].Value<JArray>().Count);
}
}
var complexEditorErrors = modelState.Single(x => x.Key == complexPropertyKey).Value.Errors;
Assert.AreEqual(3, complexEditorErrors.Count);
var nestedError = complexEditorErrors.Single(x => x.ErrorMessage.Contains("nestedValidation"));
var jsonNestedError = JsonConvert.DeserializeObject<JObject>(nestedError.ErrorMessage);
Assert.AreEqual(JTokenType.Array, jsonNestedError["nestedValidation"].Type);
var nestedValidation = (JArray)jsonNestedError["nestedValidation"];
AssertNestedValidation(nestedValidation, 2); // there are 2 because there are 2 nested content rows
}
private void AssertNestedValidation(JArray nestedValidation, int rows)
{
Assert.AreEqual(rows, nestedValidation.Count);
foreach (var rowErrors in nestedValidation)
{
Assert.AreEqual(JTokenType.Object, rowErrors.Type);
var elementTypeErrors = (JObject)rowErrors; // this is a dictionary of element type alias -> dictionary of errors -> prop alias -> array errors
Assert.AreEqual(1, elementTypeErrors.Count); // there is 1 element type in error
foreach (var elementTypeAliasToErrors in elementTypeErrors)
{
Assert.AreEqual("textPage", elementTypeAliasToErrors.Key);
var propErrors = (JObject)elementTypeAliasToErrors.Value;
foreach (var propAliasToErrors in propErrors)
{
Assert.AreEqual(JTokenType.Array, propAliasToErrors.Value.Type);
var propTypeErrors = (JArray)propAliasToErrors.Value;
foreach (var propError in propTypeErrors)
{
var nested = propError["nestedValidation"];
if (nested != null)
{
// recurse
AssertNestedValidation((JArray)nested, 1); // we know this is 1 row
continue;
}
Assert.IsNotEmpty(propError["errorMessage"].Value<string>());
Assert.AreEqual(1, propError["memberNames"].Value<JArray>().Count);
}
}
}
}
}
[HideFromTypeFinder]
[DataEditor("complexTest", "test", "test")]
public class ComplexTestEditor : NestedContentPropertyEditor
{
public ComplexTestEditor(ILogger logger, Lazy<PropertyEditorCollection> propertyEditors, IDataTypeService dataTypeService, IContentTypeService contentTypeService) : base(logger, propertyEditors, dataTypeService, contentTypeService)
{
}
protected override IDataValueEditor CreateValueEditor()
{
var editor = base.CreateValueEditor();
editor.Validators.Add(new NeverValidateValidator());
return editor;
}
}
[HideFromTypeFinder]
[DataEditor("test", "test", "test")] // This alias aligns with the prop editor alias for all properties created from MockedContentTypes.CreateTextPageContentType
public class TestEditor : DataEditor
{
public TestEditor(ILogger logger)
: base(logger)
{
}
protected override IDataValueEditor CreateValueEditor() => new TestValueEditor(Attribute);
private class TestValueEditor : DataValueEditor
{
public TestValueEditor(DataEditorAttribute attribute)
: base(attribute)
{
Validators.Add(new NeverValidateValidator());
}
}
}
public class NeverValidateValidator : IValueValidator
{
public IEnumerable<ValidationResult> Validate(object value, string valueType, object dataTypeConfiguration)
{
yield return new ValidationResult("WRONG!", new[] { "innerFieldId" });
}
}
}
}