// 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; using System.Collections.Generic; using System.Globalization; using Xunit; namespace Microsoft.AspNet.Mvc.ModelBinding { public class ValueProviderResultTest { [Fact] public void ConvertTo_ReturnsNullForReferenceTypes_WhenValueIsNull() { var valueProviderResult = new ValueProviderResult(rawValue: null); var convertedValue = valueProviderResult.ConvertTo(typeof(string)); Assert.Null(convertedValue); } [Fact] public void ConvertTo_ReturnsDefaultForValueTypes_WhenValueIsNull() { var valueProviderResult = new ValueProviderResult(rawValue: null); var convertedValue = valueProviderResult.ConvertTo(typeof(int)); Assert.Equal(0, convertedValue); } [Fact] public void ConvertToCanConvertArraysToSingleElements() { // Arrange var valueProviderResult = new ValueProviderResult( new int[] { 1, 20, 42 }, string.Empty, CultureInfo.InvariantCulture); // Act var converted = (string)valueProviderResult.ConvertTo(typeof(string)); // Assert Assert.Equal("1", converted); } [Fact] public void ConvertToCanConvertSingleElementsToArrays() { // Arrange var valueProviderResult = new ValueProviderResult(42, string.Empty, CultureInfo.InvariantCulture); // Act var converted = (string[])valueProviderResult.ConvertTo(typeof(string[])); // Assert Assert.NotNull(converted); var result = Assert.Single(converted); Assert.Equal("42", result); } [Fact] public void ConvertToCanConvertSingleElementsToSingleElements() { // Arrange var valueProviderResult = new ValueProviderResult(42, string.Empty, CultureInfo.InvariantCulture); // Act var converted = (string)valueProviderResult.ConvertTo(typeof(string)); // Assert Assert.NotNull(converted); Assert.Equal("42", converted); } [Fact] public void ConvertingNullStringToNullableIntReturnsNull() { // Arrange object original = null; var valueProviderResult = new ValueProviderResult(original, string.Empty, CultureInfo.InvariantCulture); // Act var returned = (int?)valueProviderResult.ConvertTo(typeof(int?)); // Assert Assert.Equal(returned, null); } [Fact] public void ConvertingWhiteSpaceStringToNullableIntReturnsNull() { // Arrange var original = " "; var valueProviderResult = new ValueProviderResult(original, string.Empty, CultureInfo.InvariantCulture); // Act var returned = (int?)valueProviderResult.ConvertTo(typeof(int?)); // Assert Assert.Equal(returned, null); } [Fact] public void ConvertToReturnsNullIfArrayElementValueIsNull() { // Arrange var valueProviderResult = new ValueProviderResult(rawValue: new string[] { null }); // Act var outValue = valueProviderResult.ConvertTo(typeof(int)); // Assert Assert.Null(outValue); } [Fact] public void ConvertToReturnsNullIfTryingToConvertEmptyArrayToSingleElement() { // Arrange var valueProviderResult = new ValueProviderResult(new int[0], string.Empty, CultureInfo.InvariantCulture); // Act var outValue = valueProviderResult.ConvertTo(typeof(int)); // Assert Assert.Null(outValue); } [Theory] [InlineData("")] [InlineData(" \t \r\n ")] public void ConvertToReturnsNullIfTrimmedValueIsEmptyString(object value) { // Arrange var valueProviderResult = new ValueProviderResult(rawValue: value); // Act var outValue = valueProviderResult.ConvertTo(typeof(int)); // Assert Assert.Null(outValue); } [Fact] public void ConvertToReturnsNullIfTrimmedValueIsEmptyString() { // Arrange var valueProviderResult = new ValueProviderResult(rawValue: null); // Act var outValue = valueProviderResult.ConvertTo(typeof(int[])); // Assert Assert.Null(outValue); } [Fact] public void ConvertToReturnsValueIfArrayElementIsIntegerAndDestinationTypeIsEnum() { // Arrange var result = new ValueProviderResult(rawValue: new object[] { 1 }); // Act var outValue = result.ConvertTo(typeof(IntEnum)); // Assert Assert.Equal(outValue, IntEnum.Value1); } [Theory] [InlineData(1, typeof(IntEnum), IntEnum.Value1)] [InlineData(1L, typeof(LongEnum), LongEnum.Value1)] [InlineData(long.MaxValue, typeof(LongEnum), LongEnum.MaxValue)] [InlineData(1U, typeof(UnsignedIntEnum), UnsignedIntEnum.Value1)] [InlineData(1UL, typeof(IntEnum), IntEnum.Value1)] [InlineData((byte)1, typeof(ByteEnum), ByteEnum.Value1)] [InlineData(byte.MaxValue, typeof(ByteEnum), ByteEnum.MaxValue)] [InlineData((sbyte)1, typeof(ByteEnum), ByteEnum.Value1)] [InlineData((short)1, typeof(IntEnum), IntEnum.Value1)] [InlineData((ushort)1, typeof(IntEnum), IntEnum.Value1)] [InlineData(int.MaxValue, typeof(IntEnum?), IntEnum.MaxValue)] [InlineData(null, typeof(IntEnum?), null)] [InlineData(1L, typeof(LongEnum?), LongEnum.Value1)] [InlineData(null, typeof(LongEnum?), null)] [InlineData(uint.MaxValue, typeof(UnsignedIntEnum?), UnsignedIntEnum.MaxValue)] [InlineData((byte)1, typeof(ByteEnum?), ByteEnum.Value1)] [InlineData(null, typeof(ByteEnum?), null)] [InlineData((ushort)1, typeof(LongEnum?), LongEnum.Value1)] public void ConvertToReturnsValueIfArrayElementIsAnyIntegerTypeAndDestinationTypeIsEnum( object input, Type enumType, object expected) { // Arrange var result = new ValueProviderResult(rawValue: new object[] { input }); // Act var outValue = result.ConvertTo(enumType); // Assert Assert.Equal(expected, outValue); } [Fact] public void ConvertToReturnsValueIfArrayElementIsStringValueAndDestinationTypeIsEnum() { // Arrange var valueProviderResult = new ValueProviderResult(rawValue: new object[] { "1" }); // Act var outValue = valueProviderResult.ConvertTo(typeof(IntEnum)); // Assert Assert.Equal(outValue, IntEnum.Value1); } [Fact] public void ConvertToReturnsValueIfArrayElementIsStringKeyAndDestinationTypeIsEnum() { // Arrange var valueProviderResult = new ValueProviderResult(rawValue: new object[] { "Value1" }); // Act var outValue = valueProviderResult.ConvertTo(typeof(IntEnum)); // Assert Assert.Equal(outValue, IntEnum.Value1); } [Fact] public void ConvertToReturnsValueIfElementIsStringAndDestinationIsNullableInteger() { // Arrange var valueProviderResult = new ValueProviderResult(rawValue: "12"); // Act var outValue = valueProviderResult.ConvertTo(typeof(int?)); // Assert Assert.Equal(12, outValue); } [Fact] public void ConvertToReturnsValueIfElementIsStringAndDestinationIsNullableDouble() { // Arrange var valueProviderResult = new ValueProviderResult(rawValue: "12.5"); // Act var outValue = valueProviderResult.ConvertTo(typeof(double?)); // Assert Assert.Equal(12.5, outValue); } [Fact] public void ConvertToReturnsValueIfElementIsDecimalAndDestinationIsNullableInteger() { // Arrange var valueProviderResult = new ValueProviderResult(rawValue: 12M); // Act var outValue = valueProviderResult.ConvertTo(typeof(int?)); // Assert Assert.Equal(12, outValue); } [Fact] public void ConvertToReturnsValueIfElementIsDecimalAndDestinationIsNullableDouble() { // Arrange var valueProviderResult = new ValueProviderResult(rawValue: 12.5M); // Act var outValue = valueProviderResult.ConvertTo(typeof(double?)); // Assert Assert.Equal(12.5, outValue); } [Fact] public void ConvertToReturnsValueIfElementIsDecimalDoubleAndDestinationIsNullableInteger() { // Arrange var valueProviderResult = new ValueProviderResult(rawValue: 12.5M); // Act var outValue = valueProviderResult.ConvertTo(typeof(int?)); // Assert Assert.Equal(12, outValue); } [Fact] public void ConvertToReturnsValueIfElementIsDecimalDoubleAndDestinationIsNullableLong() { // Arrange var valueProviderResult = new ValueProviderResult(rawValue: 12.5M); // Act var outValue = valueProviderResult.ConvertTo(typeof(long?)); // Assert Assert.Equal(12L, outValue); } [Fact] public void ConvertToReturnsValueIfArrayElementInstanceOfDestinationType() { // Arrange var valueProviderResult = new ValueProviderResult(rawValue: new object[] { "some string" }); // Act var outValue = valueProviderResult.ConvertTo(typeof(string)); // Assert Assert.Equal("some string", outValue); } [Theory] [InlineData(new object[] { new[] { 1, 0 } })] [InlineData(new object[] { new[] { "Value1", "Value0" } })] [InlineData(new object[] { new[] { "Value1", "value0" } })] public void ConvertTo_ConvertsEnumArrays(object value) { // Arrange var valueProviderResult = new ValueProviderResult(rawValue: value); // Act var outValue = valueProviderResult.ConvertTo(typeof(IntEnum[])); // Assert var result = Assert.IsType(outValue); Assert.Equal(2, result.Length); Assert.Equal(IntEnum.Value1, result[0]); Assert.Equal(IntEnum.Value0, result[1]); } [Theory] [InlineData(new object[] { new[] { 1, 2 }, new[] { FlagsEnum.Value1, FlagsEnum.Value2 } })] [InlineData(new object[] { new[] { "Value1", "Value2" }, new[] { FlagsEnum.Value1, FlagsEnum.Value2 } })] [InlineData(new object[] { new[] { 5, 2 }, new[] { FlagsEnum.Value1 | FlagsEnum.Value4, FlagsEnum.Value2 } })] public void ConvertTo_ConvertsFlagsEnumArrays(object value, FlagsEnum[] expected) { // Arrange var valueProviderResult = new ValueProviderResult(rawValue: value); // Act var outValue = valueProviderResult.ConvertTo(typeof(FlagsEnum[])); // Assert var result = Assert.IsType(outValue); Assert.Equal(2, result.Length); Assert.Equal(expected[0], result[0]); Assert.Equal(expected[1], result[1]); } [Fact] public void ConvertToReturnsValueIfInstanceOfDestinationType() { // Arrange var original = new[] { "some string" }; var valueProviderResult = new ValueProviderResult(rawValue: original); // Act var outValue = valueProviderResult.ConvertTo(typeof(string[])); // Assert Assert.Same(original, outValue); } [Theory] [InlineData(typeof(int))] [InlineData(typeof(double?))] [InlineData(typeof(IntEnum?))] public void ConvertToThrowsIfConverterThrows(Type destinationType) { // Arrange var valueProviderResult = new ValueProviderResult(rawValue: "this-is-not-a-valid-value"); // Act & Assert var ex = Assert.Throws(typeof(FormatException), () => valueProviderResult.ConvertTo(destinationType)); } [Fact] public void ConvertToThrowsIfNoConverterExists() { // Arrange var valueProviderResult = new ValueProviderResult(rawValue: "x"); var destinationType = typeof(MyClassWithoutConverter); // Act & Assert var ex = Assert.Throws(() => valueProviderResult.ConvertTo(destinationType)); Assert.Equal("The parameter conversion from type 'System.String' to type " + "'Microsoft.AspNet.Mvc.ModelBinding.ValueProviderResultTest+MyClassWithoutConverter' " + "failed because no type converter can convert between these types.", ex.Message); } [Fact] public void ConvertToUsesProvidedCulture() { // Arrange var original = "12,5"; var valueProviderResult = new ValueProviderResult( rawValue: original, attemptedValue: null, culture: new CultureInfo("en-GB")); var frCulture = new CultureInfo("fr-FR"); // Act var cultureResult = valueProviderResult.ConvertTo(typeof(decimal), frCulture); // Assert Assert.Equal(12.5M, cultureResult); Assert.Throws(() => valueProviderResult.ConvertTo(typeof(decimal))); } [Fact] public void CulturePropertyDefaultsToInvariantCulture() { // Arrange var result = new ValueProviderResult(rawValue: null, attemptedValue: null, culture: null); // Act & assert Assert.Same(CultureInfo.InvariantCulture, result.Culture); } [Theory] [MemberData(nameof(IntrinsicConversionData))] public void ConvertToCanConvertIntrinsics(object initialValue, T expectedValue) { // Arrange var result = new ValueProviderResult(initialValue, string.Empty, CultureInfo.InvariantCulture); // Act & Assert Assert.Equal(expectedValue, result.ConvertTo(typeof(T))); } public static IEnumerable IntrinsicConversionData { get { yield return new object[] { 42, 42L }; yield return new object[] { 42, (short)42 }; yield return new object[] { 42, (float)42.0 }; yield return new object[] { 42, (double)42.0 }; yield return new object[] { 42M, 42 }; yield return new object[] { 42L, 42 }; yield return new object[] { 42, (byte)42 }; yield return new object[] { (short)42, 42 }; yield return new object[] { (float)42.0, 42 }; yield return new object[] { (double)42.0, 42 }; yield return new object[] { (byte)42, 42 }; yield return new object[] { "2008-01-01", new DateTime(2008, 01, 01) }; yield return new object[] { "00:00:20", TimeSpan.FromSeconds(20) }; yield return new object[] { "c6687d3a-51f9-4159-8771-a66d2b7d7038", Guid.Parse("c6687d3a-51f9-4159-8771-a66d2b7d7038") }; } } [Theory] [InlineData(typeof(TimeSpan))] [InlineData(typeof(DateTime))] [InlineData(typeof(DateTimeOffset))] [InlineData(typeof(Guid))] [InlineData(typeof(IntEnum))] public void ConvertTo_Throws_IfValueIsNotStringData(Type destinationType) { // Arrange var result = new ValueProviderResult( new MyClassWithoutConverter(), string.Empty, CultureInfo.InvariantCulture); // Act var ex = Assert.Throws(() => result.ConvertTo(destinationType)); // Assert var expectedMessage = string.Format("The parameter conversion from type '{0}' to type '{1}' " + "failed because no type converter can convert between these types.", typeof(MyClassWithoutConverter), destinationType); Assert.Equal(expectedMessage, ex.Message); } [Fact] public void ConvertTo_Throws_IfDestinationTypeIsNotConvertible() { // Arrange var value = "Hello world"; var destinationType = typeof(MyClassWithoutConverter); var result = new ValueProviderResult(value, string.Empty, CultureInfo.InvariantCulture); // Act var ex = Assert.Throws(() => result.ConvertTo(destinationType)); // Assert var expectedMessage = string.Format("The parameter conversion from type '{0}' to type '{1}' " + "failed because no type converter can convert between these types.", value.GetType(), typeof(MyClassWithoutConverter)); Assert.Equal(expectedMessage, ex.Message); } [Theory] [InlineData(new object[] { 2, FlagsEnum.Value2 })] [InlineData(new object[] { 5, FlagsEnum.Value1 | FlagsEnum.Value4 })] [InlineData(new object[] { 15, FlagsEnum.Value1 | FlagsEnum.Value2 | FlagsEnum.Value4 | FlagsEnum.Value8 })] [InlineData(new object[] { 16, (FlagsEnum)16 })] [InlineData(new object[] { 0, (FlagsEnum)0 })] [InlineData(new object[] { null, (FlagsEnum)0 })] [InlineData(new object[] { "Value1,Value2", (FlagsEnum)3 })] [InlineData(new object[] { "Value1,Value2,value4, value8", (FlagsEnum)15 })] public void ConvertTo_ConvertsEnumFlags(object value, object expected) { // Arrange var valueProviderResult = new ValueProviderResult(rawValue: value); // Act var outValue = (FlagsEnum)valueProviderResult.ConvertTo(typeof(FlagsEnum)); // Assert Assert.Equal(expected, outValue); } private class MyClassWithoutConverter { } private enum IntEnum { Value0 = 0, Value1 = 1, MaxValue = int.MaxValue } private enum LongEnum : long { Value0 = 0L, Value1 = 1L, MaxValue = long.MaxValue } private enum UnsignedIntEnum : uint { Value0 = 0U, Value1 = 1U, MaxValue = uint.MaxValue } private enum ByteEnum : byte { Value0 = 0, Value1 = 1, MaxValue = byte.MaxValue } [Flags] public enum FlagsEnum { Value1 = 1, Value2 = 2, Value4 = 4, Value8 = 8 } } }