Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.
/ corefx Public archive

Add System.ComponentModel.Annotations tests #20934

Merged
merged 4 commits into from
Jun 27, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,9 @@
<data name="MinLengthAttribute_ValidationError" xml:space="preserve">
<value>The field {0} must be a string or array type with a minimum length of '{1}'.</value>
</data>
<data name="LengthAttribute_InvalidValueType" xml:space="preserve">
<value>The field of type {0} must be a string, array or ICollection type.</value>
</data>
<data name="PhoneAttribute_Invalid" xml:space="preserve">
<value>The {0} field is not a valid phone number.</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,12 +184,7 @@ protected override ValidationResult IsValid(object value, ValidationContext vali
}
catch (TargetInvocationException tie)
{
if (tie.InnerException != null)
{
throw tie.InnerException;
}

throw;
throw tie.InnerException;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you know for sure that InnerException will always be non-null in all circumstances?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the try block:

try
{
    // 1-parameter form is ValidationResult Method(object value)
    // 2-parameter form is ValidationResult Method(object value, ValidationContext context),
    var methodParams = _isSingleArgumentMethod
        ? new object[] { convertedValue }
        : new[] { convertedValue, validationContext };

    var result = (ValidationResult)methodInfo.Invoke(null, methodParams);

    // We capture the message they provide us only in the event of failure,
    // otherwise we use the normal message supplied via the ctor
    _lastMessage = null;

    if (result != null)
    {
        _lastMessage = result.ErrorMessage;
    }

    return result;
}
catch (TargetInvocationException tie)
{
    throw tie.InnerException;
}

The only place where the TIE can be thrown is from reflection invoke, and there will always be a non-null inner exception in this case.

}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,24 +72,19 @@ public override bool IsValid(object value)
{
return true;
}
var str = value as string;
if (str != null)
if (value is string str)
{
length = str.Length;
}
else
{
ICollection collection = value as ICollection;

if (collection != null)
if (value is ICollection collection)
{
length = collection.Count;
}
else
{
// A cast exception previously occurred if a non-{string|array} property was passed
// in so preserve this behavior if the value does not implement ICollection
length = ((Array)value).Length;
throw new InvalidCastException(SR.Format(SR.LengthAttribute_InvalidValueType, value.GetType()));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,24 +56,19 @@ public override bool IsValid(object value)
{
return true;
}
var str = value as string;
if (str != null)
if (value is string str)
{
length = str.Length;
}
else
{
ICollection collection = value as ICollection;

if (collection != null)
if (value is ICollection collection)
{
length = collection.Count;
}
else
{
// A cast exception previously occurred if a non-{string|array} property was passed
// in so preserve this behavior if the value does not implement ICollection
length = ((Array)value).Length;
throw new InvalidCastException(SR.Format(SR.LengthAttribute_InvalidValueType, value.GetType()));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

throw new InvalidCastException(SR.Format(SR.LengthAttribute_InvalidValueType, value.GetType())); [](start = 20, length = 96)

As for MaxLengthAttribute: what if it is an array? You're removing functionality here which was added to solve a previous issue.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So that's what I first thought when I saw the code and why I fixed this. All Arrays are ICollections, so this code path will never be hit as they will take the precious if block. We have tests that cover this in the project.
What this is actually doing is simulating an InvalidCastException for comparability with before we added ICollection support.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense - thanks for clarifying.

}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@ private class PropertyStoreItem : StoreItem
internal PropertyStoreItem(Type propertyType, IEnumerable<Attribute> attributes)
: base(attributes)
{
Debug.Assert(propertyType != null);
_propertyType = propertyType;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.

using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Reflection;
Expand Down Expand Up @@ -352,12 +353,9 @@ public static void ValidateValue(object value, ValidationContext validationConte
/// </param>
/// <returns>A new <see cref="ValidationContext" /> for the <paramref name="instance" /> provided.</returns>
/// <exception cref="ArgumentNullException">When <paramref name="validationContext" /> is null.</exception>
internal static ValidationContext CreateValidationContext(object instance, ValidationContext validationContext)
private static ValidationContext CreateValidationContext(object instance, ValidationContext validationContext)
{
if (validationContext == null)
{
throw new ArgumentNullException(nameof(validationContext));
}
Debug.Assert(validationContext != null);

// Create a new context using the existing ValidationContext that acts as an IServiceProvider and contains our existing items.
var context = new ValidationContext(instance, validationContext, validationContext.Items);
Expand All @@ -376,11 +374,6 @@ internal static ValidationContext CreateValidationContext(object instance, Valid
/// <exception cref="ArgumentNullException">When <paramref name="destinationType" /> is null.</exception>
private static bool CanBeAssigned(Type destinationType, object value)
{
if (destinationType == null)
{
throw new ArgumentNullException(nameof(destinationType));
}

if (value == null)
{
// Null can be assigned only to reference types or Nullable or Nullable<>
Expand Down Expand Up @@ -431,10 +424,7 @@ private static void EnsureValidPropertyType(string propertyName, Type propertyTy
private static IEnumerable<ValidationError> GetObjectValidationErrors(object instance,
ValidationContext validationContext, bool validateAllProperties, bool breakOnFirstError)
{
if (instance == null)
{
throw new ArgumentNullException(nameof(instance));
}
Debug.Assert(instance != null);

if (validationContext == null)
{
Expand Down Expand Up @@ -633,10 +623,7 @@ private static IEnumerable<ValidationError> GetValidationErrors(object value,
private static bool TryValidate(object value, ValidationContext validationContext, ValidationAttribute attribute,
out ValidationError validationError)
{
if (validationContext == null)
{
throw new ArgumentNullException(nameof(validationContext));
}
Debug.Assert(validationContext != null);

var validationResult = attribute.GetValidationResult(value, validationContext);
if (validationResult != ValidationResult.Success)
Expand Down Expand Up @@ -669,10 +656,7 @@ internal ValidationError(ValidationAttribute validationAttribute, object value,

internal ValidationResult ValidationResult { get; set; }

internal void ThrowValidationException()
{
throw new ValidationException(ValidationResult, ValidationAttribute, Value);
}
internal Exception ThrowValidationException() => throw new ValidationException(ValidationResult, ValidationAttribute, Value);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,13 @@ public static void Validate_LowerAndUpperPropertyName_Success()
Assert.Equal(nameof(CompareObject.comparepropertycased), attribute.OtherPropertyDisplayName);
}

[Fact]
public static void Validate_PrivateProperty_ThrowsArgumentException()
{
CompareAttribute attribute = new CompareAttribute("PrivateProperty");
Assert.Throws<ValidationException>(() => attribute.Validate("b", s_context));
}

[Fact]
public static void Validate_PropertyHasDisplayName_UpdatesFormatErrorMessageToContainDisplayName()
{
Expand Down Expand Up @@ -105,6 +112,7 @@ private class CompareObject

public string this[int index] { get { return "abc"; } set { } }
public string SetOnlyProperty { set { } }
private string PrivateProperty { get; set; }

public string ComparePropertyCased { get; set; }
public string comparepropertycased { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,26 @@ public static void Constructor(Type validatorType, string method)
Assert.Equal(method, attribute.Method);
}

[Fact]
public void FormatErrorMessage_NotPerformedValidation_ContainsName()
{
CustomValidationAttribute attribute = GetAttribute(nameof(CustomValidator.CorrectValidationMethodOneArg));
string errorMessage = attribute.FormatErrorMessage("name");
Assert.Contains("name", errorMessage);
Assert.Equal(errorMessage, attribute.FormatErrorMessage("name"));
}

[Fact]
public void FormatErrorMessage_PerformedValidation_DoesNotContainName()
{
CustomValidationAttribute attribute = GetAttribute(nameof(CustomValidator.CorrectValidationMethodOneArg));
Assert.False(attribute.IsValid(new TestClass("AnyString")));

string errorMessage = attribute.FormatErrorMessage("name");
Assert.DoesNotContain("name", errorMessage);
Assert.Equal(errorMessage, attribute.FormatErrorMessage("name"));
}

[Theory]
[InlineData(nameof(CustomValidator.CorrectValidationMethodOneArg), false)]
[InlineData(nameof(CustomValidator.CorrectValidationMethodOneArgStronglyTyped), false)]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using Xunit;

namespace System.ComponentModel.DataAnnotations.Tests
{
public class DisplayColumnAttributeTests
{
[Theory]
[InlineData(null)]
[InlineData("")]
[InlineData("DisplayColumn")]
public void Ctor_DisplayColumn(string displayColumn)
{
var attribute = new DisplayColumnAttribute(displayColumn);
Assert.Equal(displayColumn, attribute.DisplayColumn);
Assert.Null(attribute.SortColumn);
Assert.False(attribute.SortDescending);
}

[Theory]
[InlineData(null, null)]
[InlineData("", "")]
[InlineData("DisplayColumn", "SortColumn")]
public void Ctor_DisplayColumn_SortColumn(string displayColumn, string sortColumn)
{
var attribute = new DisplayColumnAttribute(displayColumn, sortColumn);
Assert.Equal(displayColumn, attribute.DisplayColumn);
Assert.Equal(sortColumn, attribute.SortColumn);
Assert.False(attribute.SortDescending);
}

[Theory]
[InlineData(null, null, false)]
[InlineData("", "", false)]
[InlineData("DisplayColumn", "SortColumn", true)]
public void Ctor_DisplayColumn_SortColumn_SortDescending(string displayColumn, string sortColumn, bool sortDescending)
{
var attribute = new DisplayColumnAttribute(displayColumn, sortColumn, sortDescending);
Assert.Equal(displayColumn, attribute.DisplayColumn);
Assert.Equal(sortColumn, attribute.SortColumn);
Assert.Equal(sortDescending, attribute.SortDescending);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
<Compile Include="CreditCardAttributeTests.cs" />
<Compile Include="CustomValidationAttributeTests.cs" />
<Compile Include="DataTypeAttributeTests.cs" />
<Compile Include="DisplayColumnAttributeTests.cs" />
<Compile Include="DisplayFormatAttributeTests.cs" />
<Compile Include="DisplayFormatAttributeTests.netcoreapp.cs" Condition="'$(TargetGroup)' == 'netcoreapp'" />
<Compile Include="EditableAttributeTests.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,32 @@ public static void TestDisplayNameNoDisplayAttribute()
validationContext.DisplayName = "OverriddenDisplayName";
Assert.Equal("OverriddenDisplayName", validationContext.DisplayName);
}

[Fact]
public void DisplayName_NoSuchMemberName_ReturnsMemberName()
{
var validationContext = new ValidationContext(new object()) { MemberName = "test" };
Assert.Equal("test", validationContext.DisplayName);
}

[Fact]
public void GetService_CustomServiceProvider_ReturnsNull()
{
var validationContext = new ValidationContext(new object());
validationContext.InitializeServiceProvider(type =>
{
Assert.Equal(typeof(int), type);
return typeof(bool);
});
Assert.Equal(typeof(bool), validationContext.GetService(typeof(int)));
}

[Fact]
public void GetService_NullServiceProvider_ReturnsNull()
{
var validationContext = new ValidationContext(new object());
Assert.Null(validationContext.GetService(typeof(int)));
}
}

public class TestClass
Expand Down
66 changes: 66 additions & 0 deletions src/System.ComponentModel.Annotations/tests/ValidatorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,72 @@ public static void TryValidateObject_returns_false_if_all_properties_are_valid_b
Assert.Equal("ValidClassAttribute.IsValid failed for class of type " + typeof(InvalidToBeValidated).FullName, validationResults[0].ErrorMessage);
}

[Fact]
public void TryValidateObject_IValidatableObject_Success()
{
var instance = new ValidatableSuccess();
var context = new ValidationContext(instance);

var results = new List<ValidationResult>();
Assert.True(Validator.TryValidateObject(instance, context, results));
Assert.Empty(results);
}

public class ValidatableSuccess : IValidatableObject
{
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
return new ValidationResult[] { ValidationResult.Success };
}
}

[Fact]
public void TryValidateObject_IValidatableObject_Error()
{
var instance = new ValidatableError();
var context = new ValidationContext(instance);

var results = new List<ValidationResult>();
Assert.False(Validator.TryValidateObject(instance, context, results));
Assert.Equal("error", Assert.Single(results).ErrorMessage);
}

public class ValidatableError : IValidatableObject
{
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
return new ValidationResult[] { new ValidationResult("error") };
}
}

[Fact]
public void TryValidateObject_RequiredNonNull_Success()
{
var instance = new RequiredFailure { Required = "Text" };
var context = new ValidationContext(instance);

var results = new List<ValidationResult>();
Assert.True(Validator.TryValidateObject(instance, context, results));
Assert.Empty(results);
}

[Fact]
public void TryValidateObject_RequiredNull_Error()
{
var instance = new RequiredFailure();
var context = new ValidationContext(instance);

var results = new List<ValidationResult>();
Assert.False(Validator.TryValidateObject(instance, context, results));
Assert.Equal("The Required field is required.", Assert.Single(results).ErrorMessage);
}

public class RequiredFailure
{
[Required]
public string Required { get; set; }
}

#endregion TryValidateObject

#region ValidateObject
Expand Down