// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. using System.Collections.Generic; using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Web.Mvc; using Xunit; using Assert = Microsoft.TestCommon.AssertEx; namespace Microsoft.Web.Mvc.ModelBinding.Test { public class ModelValidationNodeTest { [Fact] public void ConstructorSetsCollectionInstance() { // Arrange ModelMetadata metadata = GetModelMetadata(); string modelStateKey = "someKey"; ModelValidationNode[] childNodes = new[] { new ModelValidationNode(metadata, "someKey0"), new ModelValidationNode(metadata, "someKey1") }; // Act ModelValidationNode node = new ModelValidationNode(metadata, modelStateKey, childNodes); // Assert Assert.Equal(childNodes, node.ChildNodes.ToArray()); } [Fact] public void ConstructorThrowsIfModelMetadataIsNull() { // Act & assert Assert.ThrowsArgumentNull( delegate { new ModelValidationNode(null, "someKey"); }, "modelMetadata"); } [Fact] public void ConstructorThrowsIfModelStateKeyIsNull() { // Arrange ModelMetadata metadata = GetModelMetadata(); // Act & assert Assert.ThrowsArgumentNull( delegate { new ModelValidationNode(metadata, null); }, "modelStateKey"); } [Fact] public void PropertiesAreSet() { // Arrange ModelMetadata metadata = GetModelMetadata(); string modelStateKey = "someKey"; // Act ModelValidationNode node = new ModelValidationNode(metadata, modelStateKey); // Assert Assert.Equal(metadata, node.ModelMetadata); Assert.Equal(modelStateKey, node.ModelStateKey); Assert.NotNull(node.ChildNodes); Assert.Empty(node.ChildNodes); } [Fact] public void CombineWith() { // Arrange List log = new List(); ModelValidationNode[] allChildNodes = new[] { new ModelValidationNode(GetModelMetadata(), "key1"), new ModelValidationNode(GetModelMetadata(), "key2"), new ModelValidationNode(GetModelMetadata(), "key3"), }; ModelValidationNode parentNode1 = new ModelValidationNode(GetModelMetadata(), "parent1"); parentNode1.ChildNodes.Add(allChildNodes[0]); parentNode1.Validating += delegate { log.Add("Validating parent1."); }; parentNode1.Validated += delegate { log.Add("Validated parent1."); }; ModelValidationNode parentNode2 = new ModelValidationNode(GetModelMetadata(), "parent2"); parentNode2.ChildNodes.Add(allChildNodes[1]); parentNode2.ChildNodes.Add(allChildNodes[2]); parentNode2.Validating += delegate { log.Add("Validating parent2."); }; parentNode2.Validated += delegate { log.Add("Validated parent2."); }; // Act parentNode1.CombineWith(parentNode2); parentNode1.Validate(new ControllerContext { Controller = new EmptyController() }); // Assert Assert.Equal(new[] { "Validating parent1.", "Validating parent2.", "Validated parent1.", "Validated parent2." }, log.ToArray()); Assert.Equal(allChildNodes, parentNode1.ChildNodes.ToArray()); } [Fact] public void CombineWith_OtherNodeIsSuppressed_DoesNothing() { // Arrange List log = new List(); ModelValidationNode[] allChildNodes = new[] { new ModelValidationNode(GetModelMetadata(), "key1"), new ModelValidationNode(GetModelMetadata(), "key2"), new ModelValidationNode(GetModelMetadata(), "key3"), }; ModelValidationNode[] expectedChildNodes = new[] { allChildNodes[0] }; ModelValidationNode parentNode1 = new ModelValidationNode(GetModelMetadata(), "parent1"); parentNode1.ChildNodes.Add(allChildNodes[0]); parentNode1.Validating += delegate { log.Add("Validating parent1."); }; parentNode1.Validated += delegate { log.Add("Validated parent1."); }; ModelValidationNode parentNode2 = new ModelValidationNode(GetModelMetadata(), "parent2"); parentNode2.ChildNodes.Add(allChildNodes[1]); parentNode2.ChildNodes.Add(allChildNodes[2]); parentNode2.Validating += delegate { log.Add("Validating parent2."); }; parentNode2.Validated += delegate { log.Add("Validated parent2."); }; parentNode2.SuppressValidation = true; // Act parentNode1.CombineWith(parentNode2); parentNode1.Validate(new ControllerContext { Controller = new EmptyController() }); // Assert Assert.Equal(new[] { "Validating parent1.", "Validated parent1." }, log.ToArray()); Assert.Equal(expectedChildNodes, parentNode1.ChildNodes.ToArray()); } [Fact] public void Validate_Ordering() { // Proper order of invocation: // 1. OnValidating() // 2. Child validators // 3. This validator // 4. OnValidated() // Arrange List log = new List(); LoggingDataErrorInfoModel model = new LoggingDataErrorInfoModel(log); ModelMetadata modelMetadata = GetModelMetadata(model); ControllerContext controllerContext = new ControllerContext { Controller = new EmptyController() }; ModelValidationNode node = new ModelValidationNode(modelMetadata, "theKey"); ModelMetadata childMetadata = new EmptyModelMetadataProvider().GetMetadataForProperty(() => model, model.GetType(), "ValidStringProperty"); node.ChildNodes.Add(new ModelValidationNode(childMetadata, "theKey.ValidStringProperty")); node.Validating += delegate { log.Add("In OnValidating()"); }; node.Validated += delegate { log.Add("In OnValidated()"); }; // Act node.Validate(controllerContext); // Assert Assert.Equal(new[] { "In OnValidating()", "In IDataErrorInfo.get_Item('ValidStringProperty')", "In IDataErrorInfo.get_Error()", "In OnValidated()" }, log.ToArray()); } [Fact] public void Validate_PassesNullContainerInstanceIfCannotBeConvertedToProperType() { // Arrange List log1 = new List(); LoggingDataErrorInfoModel model1 = new LoggingDataErrorInfoModel(log1); ModelMetadata modelMetadata1 = GetModelMetadata(model1); List log2 = new List(); LoggingDataErrorInfoModel model2 = new LoggingDataErrorInfoModel(log2); ModelMetadata modelMetadata2 = GetModelMetadata(model2); ControllerContext controllerContext = new ControllerContext { Controller = new EmptyController() }; ModelValidationNode node = new ModelValidationNode(modelMetadata1, "theKey"); node.ChildNodes.Add(new ModelValidationNode(modelMetadata2, "theKey.SomeProperty")); // Act node.Validate(controllerContext); // Assert Assert.Equal(new[] { "In IDataErrorInfo.get_Error()" }, log1.ToArray()); Assert.Equal(new[] { "In IDataErrorInfo.get_Error()" }, log2.ToArray()); } [Fact] public void Validate_SkipsRemainingValidationIfModelStateIsInvalid() { // Because a property validator fails, the model validator shouldn't run // Arrange List log = new List(); LoggingDataErrorInfoModel model = new LoggingDataErrorInfoModel(log); ModelMetadata modelMetadata = GetModelMetadata(model); ControllerContext controllerContext = new ControllerContext { Controller = new EmptyController() }; ModelValidationNode node = new ModelValidationNode(modelMetadata, "theKey"); ModelMetadata childMetadata = new EmptyModelMetadataProvider().GetMetadataForProperty(() => model, model.GetType(), "InvalidStringProperty"); node.ChildNodes.Add(new ModelValidationNode(childMetadata, "theKey.InvalidStringProperty")); node.Validating += delegate { log.Add("In OnValidating()"); }; node.Validated += delegate { log.Add("In OnValidated()"); }; // Act node.Validate(controllerContext); // Assert Assert.Equal(new[] { "In OnValidating()", "In IDataErrorInfo.get_Item('InvalidStringProperty')", "In OnValidated()" }, log.ToArray()); Assert.Equal("Sample error message", controllerContext.Controller.ViewData.ModelState["theKey.InvalidStringProperty"].Errors[0].ErrorMessage); } [Fact] public void Validate_SkipsValidationIfHandlerCancels() { // Arrange List log = new List(); LoggingDataErrorInfoModel model = new LoggingDataErrorInfoModel(log); ModelMetadata modelMetadata = GetModelMetadata(model); ControllerContext controllerContext = new ControllerContext { Controller = new EmptyController() }; ModelValidationNode node = new ModelValidationNode(modelMetadata, "theKey"); node.Validating += (sender, e) => { log.Add("In OnValidating()"); e.Cancel = true; }; node.Validated += delegate { log.Add("In OnValidated()"); }; // Act node.Validate(controllerContext); // Assert Assert.Equal(new[] { "In OnValidating()" }, log.ToArray()); } [Fact] public void Validate_SkipsValidationIfSuppressed() { // Arrange List log = new List(); LoggingDataErrorInfoModel model = new LoggingDataErrorInfoModel(log); ModelMetadata modelMetadata = GetModelMetadata(model); ControllerContext controllerContext = new ControllerContext { Controller = new EmptyController() }; ModelValidationNode node = new ModelValidationNode(modelMetadata, "theKey") { SuppressValidation = true }; node.Validating += (sender, e) => { log.Add("In OnValidating()"); }; node.Validated += delegate { log.Add("In OnValidated()"); }; // Act node.Validate(controllerContext); // Assert Assert.Empty(log); } [Fact] public void Validate_ThrowsIfControllerContextIsNull() { // Arrange ModelValidationNode node = new ModelValidationNode(GetModelMetadata(), "someKey"); // Act & assert Assert.ThrowsArgumentNull( delegate { node.Validate(null); }, "controllerContext"); } [Fact] public void Validate_ValidateAllProperties_AddsValidationErrors() { // Arrange ValidateAllPropertiesModel model = new ValidateAllPropertiesModel { RequiredString = null /* error */, RangedInt = 0 /* error */, ValidString = "dog" }; ModelMetadata modelMetadata = GetModelMetadata(model); ControllerContext controllerContext = new ControllerContext { Controller = new EmptyController() }; ModelValidationNode node = new ModelValidationNode(modelMetadata, "theKey") { ValidateAllProperties = true }; controllerContext.Controller.ViewData.ModelState.AddModelError("theKey.RequiredString.Dummy", "existing Error Text"); // Act node.Validate(controllerContext); // Assert Assert.Null(controllerContext.Controller.ViewData.ModelState["theKey.RequiredString"]); Assert.Equal("existing Error Text", controllerContext.Controller.ViewData.ModelState["theKey.RequiredString.Dummy"].Errors[0].ErrorMessage); Assert.Equal("The field RangedInt must be between 10 and 30.", controllerContext.Controller.ViewData.ModelState["theKey.RangedInt"].Errors[0].ErrorMessage); Assert.Null(controllerContext.Controller.ViewData.ModelState["theKey.ValidString"]); Assert.Null(controllerContext.Controller.ViewData.ModelState["theKey"]); } private static ModelMetadata GetModelMetadata() { EmptyModelMetadataProvider provider = new EmptyModelMetadataProvider(); return provider.GetMetadataForType(null, typeof(object)); } private static ModelMetadata GetModelMetadata(object o) { DataAnnotationsModelMetadataProvider provider = new DataAnnotationsModelMetadataProvider(); return provider.GetMetadataForType(() => o, o.GetType()); } private sealed class EmptyController : Controller { } private sealed class LoggingDataErrorInfoModel : IDataErrorInfo { private readonly IList _log; public LoggingDataErrorInfoModel(IList log) { _log = log; } string IDataErrorInfo.Error { get { _log.Add("In IDataErrorInfo.get_Error()"); return null; } } string IDataErrorInfo.this[string columnName] { get { _log.Add("In IDataErrorInfo.get_Item('" + columnName + "')"); return (columnName == "ValidStringProperty") ? null : "Sample error message"; } } public string ValidStringProperty { get; set; } public string InvalidStringProperty { get; set; } } private class ValidateAllPropertiesModel { [Required] public string RequiredString { get; set; } [Range(10, 30)] public int RangedInt { get; set; } [RegularExpression("dog")] public string ValidString { get; set; } } } }