// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using Microsoft.Extensions.Localization; using Moq; using Xunit; namespace Microsoft.AspNet.Mvc.ModelBinding.Validation { public class DataAnnotationsModelValidatorTest { private static IModelMetadataProvider _metadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); [Fact] public void Constructor_SetsAttribute() { // Arrange var attribute = new RequiredAttribute(); // Act var validator = new DataAnnotationsModelValidator(attribute, stringLocalizer: null); // Assert Assert.Same(attribute, validator.Attribute); } public static IEnumerable Validate_SetsMemberName_OnValidationContext_ForProperties_Data { get { yield return new object[] { _metadataProvider.GetMetadataForType(typeof(string)).Properties["Length"], "Hello", "Hello".Length, "Length", }; yield return new object[] { _metadataProvider.GetMetadataForType(typeof(SampleModel)), null, 15, "SampleModel", }; } } [Theory] [MemberData(nameof(Validate_SetsMemberName_OnValidationContext_ForProperties_Data))] public void Validate_SetsMemberName_OnValidationContext_ForProperties( ModelMetadata metadata, object container, object model, string expectedMemberName) { // Arrange var attribute = new Mock { CallBase = true }; attribute .Setup(p => p.IsValidPublic(It.IsAny(), It.IsAny())) .Callback((object o, ValidationContext context) => { Assert.Equal(expectedMemberName, context.MemberName); }) .Returns(ValidationResult.Success) .Verifiable(); var validator = new DataAnnotationsModelValidator(attribute.Object, stringLocalizer: null); var validationContext = new ModelValidationContext() { Metadata = metadata, Container = container, Model = model, }; // Act var results = validator.Validate(validationContext); // Assert Assert.Empty(results); attribute.VerifyAll(); } [Fact] public void Validate_Valid() { // Arrange var metadata = _metadataProvider.GetMetadataForType(typeof(string)); var container = "Hello"; var model = container.Length; var attribute = new Mock { CallBase = true }; attribute.Setup(a => a.IsValid(model)).Returns(true); var validator = new DataAnnotationsModelValidator(attribute.Object, stringLocalizer: null); var validationContext = new ModelValidationContext() { Metadata = metadata, Container = container, Model = model, }; // Act var result = validator.Validate(validationContext); // Assert Assert.Empty(result); } [Fact] public void Validate_Invalid() { // Arrange var metadata = _metadataProvider.GetMetadataForProperty(typeof(string), "Length"); var container = "Hello"; var model = container.Length; var attribute = new Mock { CallBase = true }; attribute.Setup(a => a.IsValid(model)).Returns(false); var validator = new DataAnnotationsModelValidator(attribute.Object, stringLocalizer: null); var validationContext = new ModelValidationContext() { Metadata = metadata, Container = container, Model = model, }; // Act var result = validator.Validate(validationContext); // Assert var validationResult = result.Single(); Assert.Equal("", validationResult.MemberName); Assert.Equal(attribute.Object.FormatErrorMessage("Length"), validationResult.Message); } [Fact] public void Validatate_ValidationResultSuccess() { // Arrange var metadata = _metadataProvider.GetMetadataForType(typeof(string)); var container = "Hello"; var model = container.Length; var attribute = new Mock { CallBase = true }; attribute .Setup(p => p.IsValidPublic(It.IsAny(), It.IsAny())) .Returns(ValidationResult.Success); var validator = new DataAnnotationsModelValidator(attribute.Object, stringLocalizer: null); var validationContext = new ModelValidationContext() { Metadata = metadata, Container = container, Model = model, }; // Act var result = validator.Validate(validationContext); // Assert Assert.Empty(result); } [Fact] public void Validate_ReturnsSingleValidationResult_IfMemberNameSequenceIsEmpty() { // Arrange const string errorMessage = "Some error message"; var metadata = _metadataProvider.GetMetadataForType(typeof(string)); var container = "Hello"; var model = container.Length; var attribute = new Mock { CallBase = true }; attribute .Setup(p => p.IsValidPublic(It.IsAny(), It.IsAny())) .Returns(new ValidationResult(errorMessage, memberNames: null)); var validator = new DataAnnotationsModelValidator(attribute.Object, stringLocalizer: null); var validationContext = new ModelValidationContext() { Metadata = metadata, Container = container, Model = model, }; // Act var results = validator.Validate(validationContext); // Assert var validationResult = Assert.Single(results); Assert.Equal(errorMessage, validationResult.Message); Assert.Empty(validationResult.MemberName); } [Fact] public void Validate_ReturnsSingleValidationResult_IfOneMemberNameIsSpecified() { // Arrange const string errorMessage = "A different error message"; var metadata = _metadataProvider.GetMetadataForType(typeof(object)); var model = new object(); var attribute = new Mock { CallBase = true }; attribute .Setup(p => p.IsValidPublic(It.IsAny(), It.IsAny())) .Returns(new ValidationResult(errorMessage, new[] { "FirstName" })); var validator = new DataAnnotationsModelValidator(attribute.Object, stringLocalizer: null); var validationContext = new ModelValidationContext() { Metadata = metadata, Model = model, }; // Act var results = validator.Validate(validationContext); // Assert ModelValidationResult validationResult = Assert.Single(results); Assert.Equal(errorMessage, validationResult.Message); Assert.Equal("FirstName", validationResult.MemberName); } [Fact] public void Validate_ReturnsMemberName_IfItIsDifferentFromDisplayName() { // Arrange var metadata = _metadataProvider.GetMetadataForType(typeof(SampleModel)); var model = new SampleModel(); var attribute = new Mock { CallBase = true }; attribute .Setup(p => p.IsValidPublic(It.IsAny(), It.IsAny())) .Returns(new ValidationResult("Name error", new[] { "Name" })); var validator = new DataAnnotationsModelValidator(attribute.Object, stringLocalizer: null); var validationContext = new ModelValidationContext() { Metadata = metadata, Model = model, }; // Act var results = validator.Validate(validationContext); // Assert ModelValidationResult validationResult = Assert.Single(results); Assert.Equal("Name", validationResult.MemberName); } [Fact] public void Validate_IsValidFalse_StringLocalizerReturnsLocalizerErrorMessage() { // Arrange var metadata = _metadataProvider.GetMetadataForType(typeof(string)); var container = "Hello"; var model = container.Length; var attribute = new Mock { CallBase = true }; attribute.Setup(a => a.IsValid(model)).Returns(false); attribute.Object.ErrorMessage = "Length"; var localizedString = new LocalizedString("Length", "Longueur est invalide"); var stringLocalizer = new Mock(); stringLocalizer.Setup(s => s["Length", It.IsAny()]).Returns(localizedString); var validator = new DataAnnotationsModelValidator(attribute.Object, stringLocalizer.Object); var validationContext = new ModelValidationContext() { Metadata = metadata, Container = container, Model = model, }; // Act var result = validator.Validate(validationContext); // Assert var validationResult = result.Single(); Assert.Equal("", validationResult.MemberName); Assert.Equal("Longueur est invalide", validationResult.Message); } private class DerivedRequiredAttribute : RequiredAttribute { } public abstract class TestableValidationAttribute : ValidationAttribute { protected override ValidationResult IsValid(object value, ValidationContext validationContext) { return IsValidPublic(value, validationContext); } public abstract ValidationResult IsValidPublic(object value, ValidationContext validationContext); } private class SampleModel { public string Name { get; set; } } } }