// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Web.Http.Metadata; using System.Web.Http.Metadata.Providers; using System.Web.Http.ModelBinding; using System.Web.Http.ValueProviders; using Xunit; using Assert = Microsoft.TestCommon.AssertEx; namespace System.Web.Http.Internal { public class CollectionModelBinderUtilTest { [Fact] public void CreateOrReplaceCollection_OriginalModelImmutable_CreatesNewInstance() { // Arrange ModelBindingContext bindingContext = new ModelBindingContext { ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(() => new ReadOnlyCollection(new int[0]), typeof(ICollection)) }; // Act CollectionModelBinderUtil.CreateOrReplaceCollection(bindingContext, new[] { 10, 20, 30 }, () => new List()); // Assert int[] newModel = (bindingContext.Model as ICollection).ToArray(); Assert.Equal(new[] { 10, 20, 30 }, newModel); } [Fact] public void CreateOrReplaceCollection_OriginalModelMutable_UpdatesOriginalInstance() { // Arrange List originalInstance = new List { 10, 20, 30 }; ModelBindingContext bindingContext = new ModelBindingContext { ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(() => originalInstance, typeof(ICollection)) }; // Act CollectionModelBinderUtil.CreateOrReplaceCollection(bindingContext, new[] { 40, 50, 60 }, () => new List()); // Assert Assert.Same(originalInstance, bindingContext.Model); Assert.Equal(new[] { 40, 50, 60 }, originalInstance.ToArray()); } [Fact] public void CreateOrReplaceCollection_OriginalModelNotCollection_CreatesNewInstance() { // Arrange ModelBindingContext bindingContext = new ModelBindingContext { ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(ICollection)) }; // Act CollectionModelBinderUtil.CreateOrReplaceCollection(bindingContext, new[] { 10, 20, 30 }, () => new List()); // Assert int[] newModel = (bindingContext.Model as ICollection).ToArray(); Assert.Equal(new[] { 10, 20, 30 }, newModel); } [Fact] public void CreateOrReplaceDictionary_DisallowsDuplicateKeys() { // Arrange ModelBindingContext bindingContext = new ModelBindingContext { ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(Dictionary)) }; // Act CollectionModelBinderUtil.CreateOrReplaceDictionary( bindingContext, new[] { new KeyValuePair("forty-two", 40), new KeyValuePair("forty-two", 2), new KeyValuePair("forty-two", 42) }, () => new Dictionary()); // Assert IDictionary newModel = bindingContext.Model as IDictionary; Assert.Equal(new[] { "forty-two" }, newModel.Keys.ToArray()); Assert.Equal(42, newModel["forty-two"]); } [Fact] public void CreateOrReplaceDictionary_DisallowsNullKeys() { // Arrange ModelBindingContext bindingContext = new ModelBindingContext { ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(Dictionary)) }; // Act CollectionModelBinderUtil.CreateOrReplaceDictionary( bindingContext, new[] { new KeyValuePair("forty-two", 42), new KeyValuePair(null, 84) }, () => new Dictionary()); // Assert IDictionary newModel = bindingContext.Model as IDictionary; Assert.Equal(new[] { "forty-two" }, newModel.Keys.ToArray()); Assert.Equal(42, newModel["forty-two"]); } [Fact] public void CreateOrReplaceDictionary_OriginalModelImmutable_CreatesNewInstance() { // Arrange ReadOnlyDictionary originalModel = new ReadOnlyDictionary(); ModelBindingContext bindingContext = new ModelBindingContext { ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(() => originalModel, typeof(IDictionary)) }; // Act CollectionModelBinderUtil.CreateOrReplaceDictionary( bindingContext, new Dictionary { { "Hello", "World" } }, () => new Dictionary()); // Assert IDictionary newModel = bindingContext.Model as IDictionary; Assert.NotSame(originalModel, newModel); Assert.Equal(new[] { "Hello" }, newModel.Keys.ToArray()); Assert.Equal("World", newModel["Hello"]); } [Fact] public void CreateOrReplaceDictionary_OriginalModelMutable_UpdatesOriginalInstance() { // Arrange Dictionary originalInstance = new Dictionary { { "dog", "Canidae" }, { "cat", "Felidae" } }; ModelBindingContext bindingContext = new ModelBindingContext { ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(() => originalInstance, typeof(IDictionary)) }; // Act CollectionModelBinderUtil.CreateOrReplaceDictionary( bindingContext, new Dictionary { { "horse", "Equidae" }, { "bear", "Ursidae" } }, () => new Dictionary()); // Assert Assert.Same(originalInstance, bindingContext.Model); Assert.Equal(new[] { "horse", "bear" }, originalInstance.Keys.ToArray()); Assert.Equal("Equidae", originalInstance["horse"]); Assert.Equal("Ursidae", originalInstance["bear"]); } [Fact] public void CreateOrReplaceDictionary_OriginalModelNotDictionary_CreatesNewInstance() { // Arrange ModelBindingContext bindingContext = new ModelBindingContext { ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(IDictionary)) }; // Act CollectionModelBinderUtil.CreateOrReplaceDictionary( bindingContext, new Dictionary { { "horse", "Equidae" }, { "bear", "Ursidae" } }, () => new Dictionary()); // Assert IDictionary newModel = bindingContext.Model as IDictionary; Assert.Equal(new[] { "horse", "bear" }, newModel.Keys.ToArray()); Assert.Equal("Equidae", newModel["horse"]); Assert.Equal("Ursidae", newModel["bear"]); } [Fact] public void GetIndexNamesFromValueProviderResult_ValueProviderResultIsNull_ReturnsNull() { // Act IEnumerable indexNames = CollectionModelBinderUtil.GetIndexNamesFromValueProviderResult(null); // Assert Assert.Null(indexNames); } [Fact] public void GetIndexNamesFromValueProviderResult_ValueProviderResultReturnsEmptyArray_ReturnsNull() { // Arrange ValueProviderResult vpResult = new ValueProviderResult(new string[0], "", null); // Act IEnumerable indexNames = CollectionModelBinderUtil.GetIndexNamesFromValueProviderResult(vpResult); // Assert Assert.Null(indexNames); } [Fact] public void GetIndexNamesFromValueProviderResult_ValueProviderResultReturnsNonEmptyArray_ReturnsArray() { // Arrange ValueProviderResult vpResult = new ValueProviderResult(new[] { "foo", "bar", "baz" }, "foo,bar,baz", null); // Act IEnumerable indexNames = CollectionModelBinderUtil.GetIndexNamesFromValueProviderResult(vpResult); // Assert Assert.NotNull(indexNames); Assert.Equal(new[] { "foo", "bar", "baz" }, indexNames.ToArray()); } [Fact] public void GetIndexNamesFromValueProviderResult_ValueProviderResultReturnsNull_ReturnsNull() { // Arrange ValueProviderResult vpResult = new ValueProviderResult(null, null, null); // Act IEnumerable indexNames = CollectionModelBinderUtil.GetIndexNamesFromValueProviderResult(vpResult); // Assert Assert.Null(indexNames); } [Fact] public void GetTypeArgumentsForUpdatableGenericCollection_ModelTypeNotGeneric_Fail() { // Arrange ModelMetadata modelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(int)); // Act Type[] typeArguments = CollectionModelBinderUtil.GetTypeArgumentsForUpdatableGenericCollection(null, null, modelMetadata); // Assert Assert.Null(typeArguments); } [Fact] public void GetTypeArgumentsForUpdatableGenericCollection_ModelTypeOpenGeneric_Fail() { // Arrange ModelMetadata modelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(IList<>)); // Act Type[] typeArguments = CollectionModelBinderUtil.GetTypeArgumentsForUpdatableGenericCollection(null, null, modelMetadata); // Assert Assert.Null(typeArguments); } [Fact] public void GetTypeArgumentsForUpdatableGenericCollection_ModelTypeWrongNumberOfGenericArguments_Fail() { // Arrange ModelMetadata modelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(KeyValuePair)); // Act Type[] typeArguments = CollectionModelBinderUtil.GetTypeArgumentsForUpdatableGenericCollection(typeof(ICollection<>), null, modelMetadata); // Assert Assert.Null(typeArguments); } [Fact] public void GetTypeArgumentsForUpdatableGenericCollection_ReadOnlyReference_ModelInstanceImmutable_Valid() { // Arrange ModelMetadata modelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(() => new int[0], typeof(IList)); modelMetadata.IsReadOnly = true; // Act Type[] typeArguments = CollectionModelBinderUtil.GetTypeArgumentsForUpdatableGenericCollection(typeof(IList<>), typeof(List<>), modelMetadata); // Assert Assert.Null(typeArguments); } [Fact] public void GetTypeArgumentsForUpdatableGenericCollection_ReadOnlyReference_ModelInstanceMutable_Valid() { // Arrange ModelMetadata modelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(() => new List(), typeof(IList)); modelMetadata.IsReadOnly = true; // Act Type[] typeArguments = CollectionModelBinderUtil.GetTypeArgumentsForUpdatableGenericCollection(typeof(IList<>), typeof(List<>), modelMetadata); // Assert Assert.Equal(new[] { typeof(int) }, typeArguments); } [Fact] public void GetTypeArgumentsForUpdatableGenericCollection_ReadOnlyReference_ModelInstanceOfWrongType_Fail() { // Arrange ModelMetadata modelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(() => new HashSet(), typeof(ICollection)); modelMetadata.IsReadOnly = true; // Act Type[] typeArguments = CollectionModelBinderUtil.GetTypeArgumentsForUpdatableGenericCollection(typeof(IList<>), typeof(List<>), modelMetadata); // Assert // HashSet<> is not an IList<>, so we can't update Assert.Null(typeArguments); } [Fact] public void GetTypeArgumentsForUpdatableGenericCollection_ReadOnlyReference_ModelIsNull_Fail() { // Arrange ModelMetadata modelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(IList)); modelMetadata.IsReadOnly = true; // Act Type[] typeArguments = CollectionModelBinderUtil.GetTypeArgumentsForUpdatableGenericCollection(typeof(ICollection<>), typeof(List<>), modelMetadata); // Assert Assert.Null(typeArguments); } [Fact] public void GetTypeArgumentsForUpdatableGenericCollection_ReadWriteReference_NewInstanceAssignableToModelType_Success() { // Arrange ModelMetadata modelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(IList)); modelMetadata.IsReadOnly = false; // Act Type[] typeArguments = CollectionModelBinderUtil.GetTypeArgumentsForUpdatableGenericCollection(typeof(ICollection<>), typeof(List<>), modelMetadata); // Assert Assert.Equal(new[] { typeof(int) }, typeArguments); } [Fact] public void GetTypeArgumentsForUpdatableGenericCollection_ReadWriteReference_NewInstanceNotAssignableToModelType_MutableInstance_Success() { // Arrange ModelMetadata modelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(() => new Collection(), typeof(Collection)); modelMetadata.IsReadOnly = false; // Act Type[] typeArguments = CollectionModelBinderUtil.GetTypeArgumentsForUpdatableGenericCollection(typeof(ICollection<>), typeof(List<>), modelMetadata); // Assert Assert.Equal(new[] { typeof(int) }, typeArguments); } [Fact] public void GetZeroBasedIndexes() { // Act string[] indexes = CollectionModelBinderUtil.GetZeroBasedIndexes().Take(5).ToArray(); // Assert Assert.Equal(new[] { "0", "1", "2", "3", "4" }, indexes); } private class ReadOnlyDictionary : Dictionary, ICollection> { bool ICollection>.IsReadOnly { get { return true; } } } } }