From 529fa67c2ba23198b87c5734275a8fe8dbb7c690 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Mon, 21 Mar 2016 15:23:23 -0700 Subject: [PATCH] Adding tests for PrefixContainer Fixes #4045 --- .../Internal/PrefixContainer.cs | 10 +- .../ModelBinding/DictionaryModelBinder.cs | 2 +- .../Internal/PrefixContainerTest.cs | 160 +++++++++++++++++- 3 files changed, 157 insertions(+), 15 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/PrefixContainer.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/PrefixContainer.cs index 1bdb186b88..e5c8bfe12a 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/PrefixContainer.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/PrefixContainer.cs @@ -48,16 +48,16 @@ namespace Microsoft.AspNetCore.Mvc.Internal throw new ArgumentNullException(nameof(prefix)); } - if (prefix.Length == 0) - { - return _sortedValues.Length > 0; // only match empty string when we have some value - } - if (_sortedValues.Length == 0) { return false; } + if (prefix.Length == 0) + { + return true; // Empty prefix matches all elements. + } + return BinarySearch(prefix) > -1; } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/DictionaryModelBinder.cs b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/DictionaryModelBinder.cs index cfd75211d8..b6acd20735 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/DictionaryModelBinder.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/DictionaryModelBinder.cs @@ -56,7 +56,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding // Attempt to bind dictionary from a set of prefix[key]=value entries. Get the short and long keys first. var keys = enumerableValueProvider.GetKeysFromPrefix(bindingContext.ModelName); - if (!keys.Any()) + if (keys.Count == 0) { // No entries with the expected keys. return; diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/PrefixContainerTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/PrefixContainerTest.cs index b373c378e0..39fef73219 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/PrefixContainerTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/PrefixContainerTest.cs @@ -1,6 +1,8 @@ // 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.Linq; using Xunit; namespace Microsoft.AspNetCore.Mvc.Internal @@ -37,13 +39,13 @@ namespace Microsoft.AspNetCore.Mvc.Internal [Theory] [InlineData("a")] - [InlineData("b")] - [InlineData("c")] + [InlineData("abc")] + [InlineData("bc")] [InlineData("d")] - public void ContainsPrefix_HasEntries_ExactMatch(string prefix) + public void ContainsPrefix_ReturnsTrue_IfTheContainerHasAnExactMatch(string prefix) { // Arrange - var keys = new string[] { "a", "b", "c", "d" }; + var keys = new string[] { "bc", "a", "abc", "d" }; var container = new PrefixContainer(keys); // Act @@ -71,15 +73,21 @@ namespace Microsoft.AspNetCore.Mvc.Internal Assert.False(result); } + [Theory] [InlineData("a")] [InlineData("b")] + [InlineData("b.xy")] [InlineData("c")] + [InlineData("c.x")] + [InlineData("c.x.y")] [InlineData("d")] + [InlineData("d.x")] + [InlineData("d.x.z")] public void ContainsPrefix_HasEntries_PrefixMatch_WithDot(string prefix) { // Arrange - var keys = new string[] { "a.x", "b.x", "c.x", "d.x" }; + var keys = new string[] { "a.x", "b.xy", "c.x.y", "d.x.z[0]" }; var container = new PrefixContainer(keys); // Act @@ -89,15 +97,37 @@ namespace Microsoft.AspNetCore.Mvc.Internal Assert.True(result); } + [Theory] + [InlineData("ab")] + [InlineData("a.b.c")] + [InlineData("a.b[1]")] + [InlineData("a.b0")] + public void ContainsPrefix_ReturnsFalse_IfPrefixDoesNotMatch(string prefix) + { + // Arrange + var keys = new string[] { "a.b", "a.bc", "a.b[c]", "a.b[0]" }; + var container = new PrefixContainer(keys); + + // Act + var result = container.ContainsPrefix(prefix); + + // Assert + Assert.False(result); + } + [Theory] [InlineData("a")] - [InlineData("b")] - [InlineData("c")] - [InlineData("d")] + [InlineData("a[x]")] + [InlineData("d[x]")] + [InlineData("d[x].y")] + [InlineData("e")] + [InlineData("e.a.b")] + [InlineData("e.a.b[foo]")] + [InlineData("e.a.b[foo].bar")] public void ContainsPrefix_HasEntries_PrefixMatch_WithSquareBrace(string prefix) { // Arrange - var keys = new string[] { "a[x", "b[x", "c[x", "d[x" }; + var keys = new string[] { "a[x]", "d[x].y", "e.a.b[foo].bar" }; var container = new PrefixContainer(keys); // Act @@ -106,5 +136,117 @@ namespace Microsoft.AspNetCore.Mvc.Internal // Assert Assert.True(result); } + + [Theory] + [InlineData("")] + [InlineData("foo")] + public void GetKeysFromPrefix_ReturnsEmptySequenceWhenContainerIsEmpty(string prefix) + { + // Arrange + var keys = new string[0]; + var container = new PrefixContainer(keys); + + // Act + var result = container.GetKeysFromPrefix(prefix); + + // Assert + Assert.Empty(result); + } + + [Fact] + public void GetKeysFromPrefix_ReturnsUniqueTopLevelEntries_WhenPrefixIsEmpty() + { + // Arrange + var keys = new[] { "[0].name", "[0].address.street", "[item1].name", "[item1].age", "foo", "foo.bar" }; + var container = new PrefixContainer(keys); + + // Act + var result = container.GetKeysFromPrefix(prefix: string.Empty); + + // Assert + Assert.Collection(result.OrderBy(k => k.Key, StringComparer.OrdinalIgnoreCase), + item => + { + Assert.Equal("0", item.Key); + Assert.Equal("[0]", item.Value); + }, + item => + { + Assert.Equal("foo", item.Key); + Assert.Equal("foo", item.Value); + }, + item => + { + Assert.Equal("item1", item.Key); + Assert.Equal("[item1]", item.Value); + }); + } + + [Fact] + public void GetKeysFromPrefix_ReturnsEmptyDictionaryWhenNoKeysStartWithPrefix() + { + // Arrange + var keys = new[] { "foo[0].name", "foo.age", "[1].name", "[item].age" }; + var container = new PrefixContainer(keys); + + // Act + var result = container.GetKeysFromPrefix("baz"); + + // Assert + Assert.Empty(result); + } + + [Fact] + public void GetKeysFromPrefix_ReturnsSubKeysThatStartWithPrefix() + { + // Arrange + var keys = new[] { "foo[0].name", "foo.age", "foo[1].name", "food[item].spice" }; + var container = new PrefixContainer(keys); + + // Act + var result = container.GetKeysFromPrefix("foo"); + + // Assert + Assert.Collection(result.OrderBy(k => k.Key, StringComparer.OrdinalIgnoreCase), + item => + { + Assert.Equal("0", item.Key); + Assert.Equal("foo[0]", item.Value); + }, + item => + { + Assert.Equal("1", item.Key); + Assert.Equal("foo[1]", item.Value); + }, + item => + { + Assert.Equal("age", item.Key); + Assert.Equal("foo.age", item.Value); + }); + } + + [Fact] + public void GetKeysFromPrefix_ReturnsSubKeysThatStartWithPrefix_ForNestedSubKeys() + { + // Arrange + var keys = new[] { "person[0].address[0].street", "person[0].address[1].street", "person[0].address[1].zip" }; + var container = new PrefixContainer(keys); + + // Act + var result = container.GetKeysFromPrefix("person[0].address"); + + // Assert + Assert.Collection(result.OrderBy(k => k.Key, StringComparer.OrdinalIgnoreCase), + item => + { + Assert.Equal("0", item.Key); + Assert.Equal("person[0].address[0]", item.Value); + }, + item => + { + Assert.Equal("1", item.Key); + Assert.Equal("person[0].address[1]", item.Value); + }); + } } }