Additional tests
This commit is contained in:
parent
f575677c95
commit
6bd4b87d2c
|
|
@ -83,6 +83,11 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
||||||
path = path.Substring(2);
|
path = path.Substring(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (path.StartsWith("$[", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
path = path.Substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
// Handle path combinations such as ""+"Property", "Parent"+"Property", or "Parent"+"[12]".
|
// Handle path combinations such as ""+"Property", "Parent"+"Property", or "Parent"+"[12]".
|
||||||
var key = ModelNames.CreatePropertyModelName(context.ModelName, path);
|
var key = ModelNames.CreatePropertyModelName(context.ModelName, path);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,16 +3,20 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
|
using Microsoft.Extensions.Logging.Testing;
|
||||||
|
using Newtonsoft.Json;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.Formatters
|
namespace Microsoft.AspNetCore.Mvc.Formatters
|
||||||
{
|
{
|
||||||
public abstract class JsonInputFormatterTestBase
|
public abstract class JsonInputFormatterTestBase : LoggedTest
|
||||||
{
|
{
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("application/json", true)]
|
[InlineData("application/json", true)]
|
||||||
|
|
@ -199,6 +203,26 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
||||||
Assert.Equal(new int[] { 0, 23, 300 }, (IEnumerable<int>)result.Model);
|
Assert.Equal(new int[] { 0, 23, 300 }, (IEnumerable<int>)result.Model);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public virtual async Task ReadAsync_ArrayOfObjects_HasCorrectKey()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var formatter = GetInputFormatter();
|
||||||
|
|
||||||
|
var content = "[{\"Age\": 5}, {\"Age\": 3}, {\"Age\": \"Cheese\"} ]";
|
||||||
|
var contentBytes = Encoding.UTF8.GetBytes(content);
|
||||||
|
var httpContext = GetHttpContext(contentBytes);
|
||||||
|
|
||||||
|
var formatterContext = CreateInputFormatterContext(typeof(List<ComplexModel>), httpContext);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await formatter.ReadAsync(formatterContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(result.HasError, "Model should have had an error!");
|
||||||
|
Assert.Single(formatterContext.ModelState["[2].Age"].Errors);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public virtual async Task ReadAsync_AddsModelValidationErrorsToModelState()
|
public virtual async Task ReadAsync_AddsModelValidationErrorsToModelState()
|
||||||
{
|
{
|
||||||
|
|
@ -215,10 +239,8 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
||||||
var result = await formatter.ReadAsync(formatterContext);
|
var result = await formatter.ReadAsync(formatterContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(result.HasError);
|
Assert.True(result.HasError, "Model should have had an error!");
|
||||||
Assert.Equal(
|
Assert.Single(formatterContext.ModelState["Age"].Errors);
|
||||||
"Could not convert string to decimal: not-an-age. Path 'Age', line 1, position 44.",
|
|
||||||
formatterContext.ModelState["Age"].Errors[0].ErrorMessage);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -227,19 +249,18 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
||||||
// Arrange
|
// Arrange
|
||||||
var formatter = GetInputFormatter();
|
var formatter = GetInputFormatter();
|
||||||
|
|
||||||
var content = "[0, 23, 300]";
|
var content = "[0, 23, 33767]";
|
||||||
var contentBytes = Encoding.UTF8.GetBytes(content);
|
var contentBytes = Encoding.UTF8.GetBytes(content);
|
||||||
var httpContext = GetHttpContext(contentBytes);
|
var httpContext = GetHttpContext(contentBytes);
|
||||||
|
|
||||||
var formatterContext = CreateInputFormatterContext(typeof(byte[]), httpContext);
|
var formatterContext = CreateInputFormatterContext(typeof(short[]), httpContext);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await formatter.ReadAsync(formatterContext);
|
var result = await formatter.ReadAsync(formatterContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(result.HasError);
|
Assert.True(result.HasError, "Model should have produced an error!");
|
||||||
Assert.Equal("The supplied value is invalid.", formatterContext.ModelState["[2]"].Errors[0].ErrorMessage);
|
Assert.True(formatterContext.ModelState.ContainsKey("[2]"), "Should have contained key '[2]'");
|
||||||
Assert.Null(formatterContext.ModelState["[2]"].Errors[0].Exception);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -259,9 +280,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(result.HasError);
|
Assert.True(result.HasError);
|
||||||
Assert.Equal(
|
Assert.Single(formatterContext.ModelState["names[1].Small"].Errors);
|
||||||
"Error converting value 300 to type 'System.Byte'. Path '[1].Small', line 1, position 69.",
|
|
||||||
formatterContext.ModelState["names[1].Small"].Errors[0].ErrorMessage);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -318,6 +337,45 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
||||||
Assert.Null(result.Model);
|
Assert.Null(result.Model);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ReadAsync_ComplexPoco()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var formatter = GetInputFormatter();
|
||||||
|
|
||||||
|
var content = "{ \"Id\": 5, \"Person\": { \"Name\": \"name\", \"Numbers\": [3, 2, \"Hamburger\"]} }";
|
||||||
|
var contentBytes = Encoding.UTF8.GetBytes(content);
|
||||||
|
var httpContext = GetHttpContext(contentBytes);
|
||||||
|
|
||||||
|
var formatterContext = CreateInputFormatterContext(typeof(ComplexPoco), httpContext);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await formatter.ReadAsync(formatterContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(result.HasError, "Model should have had an error!");
|
||||||
|
Assert.Single(formatterContext.ModelState["Person.Numbers[2]"].Errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public virtual async Task ReadAsync_RequiredAttribute()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var formatter = GetInputFormatter();
|
||||||
|
var content = "{ \"Id\": 5, \"Person\": {\"Numbers\": [3]} }";
|
||||||
|
var contentBytes = Encoding.UTF8.GetBytes(content);
|
||||||
|
var httpContext = GetHttpContext(contentBytes);
|
||||||
|
|
||||||
|
var formatterContext = CreateInputFormatterContext(typeof(ComplexPoco), httpContext);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await formatter.ReadAsync(formatterContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(result.HasError, "Model should have had an error!");
|
||||||
|
Assert.Single(formatterContext.ModelState["Person.Name"].Errors);
|
||||||
|
}
|
||||||
|
|
||||||
protected abstract TextInputFormatter GetInputFormatter();
|
protected abstract TextInputFormatter GetInputFormatter();
|
||||||
|
|
||||||
protected static HttpContext GetHttpContext(
|
protected static HttpContext GetHttpContext(
|
||||||
|
|
@ -356,6 +414,20 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
||||||
treatEmptyInputAsDefaultValue: treatEmptyInputAsDefaultValue);
|
treatEmptyInputAsDefaultValue: treatEmptyInputAsDefaultValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected sealed class ComplexPoco
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public Person Person{ get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
protected sealed class Person
|
||||||
|
{
|
||||||
|
[Required]
|
||||||
|
[JsonProperty(Required = Required.Always)]
|
||||||
|
public string Name { get; set; }
|
||||||
|
public IEnumerable<int> Numbers { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
protected sealed class ComplexModel
|
protected sealed class ComplexModel
|
||||||
{
|
{
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -1,40 +1,80 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// 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.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.Formatters
|
namespace Microsoft.AspNetCore.Mvc.Formatters
|
||||||
{
|
{
|
||||||
public class SystemTextJsonInputFormatterTest : JsonInputFormatterTestBase
|
public class SystemTextJsonInputFormatterTest : JsonInputFormatterTestBase
|
||||||
{
|
{
|
||||||
[Fact(Skip = "https://github.com/aspnet/AspNetCore/issues/8474")]
|
[Fact]
|
||||||
public override Task ReadAsync_AddsModelValidationErrorsToModelState()
|
public override Task ReadAsync_AddsModelValidationErrorsToModelState()
|
||||||
{
|
{
|
||||||
return base.ReadAsync_AddsModelValidationErrorsToModelState();
|
return base.ReadAsync_AddsModelValidationErrorsToModelState();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact(Skip = "https://github.com/aspnet/AspNetCore/issues/8474")]
|
[Fact]
|
||||||
public override Task ReadAsync_InvalidArray_AddsOverflowErrorsToModelState()
|
public override Task ReadAsync_InvalidArray_AddsOverflowErrorsToModelState()
|
||||||
{
|
{
|
||||||
return base.ReadAsync_InvalidArray_AddsOverflowErrorsToModelState();
|
return base.ReadAsync_InvalidArray_AddsOverflowErrorsToModelState();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact(Skip = "https://github.com/aspnet/AspNetCore/issues/8474")]
|
[Fact]
|
||||||
public override Task ReadAsync_InvalidComplexArray_AddsOverflowErrorsToModelState()
|
public override Task ReadAsync_InvalidComplexArray_AddsOverflowErrorsToModelState()
|
||||||
{
|
{
|
||||||
return base.ReadAsync_InvalidComplexArray_AddsOverflowErrorsToModelState();
|
return base.ReadAsync_InvalidComplexArray_AddsOverflowErrorsToModelState();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact(Skip = "https://github.com/aspnet/AspNetCore/issues/8474")]
|
[Fact]
|
||||||
public override Task ReadAsync_UsesTryAddModelValidationErrorsToModelState()
|
public override Task ReadAsync_UsesTryAddModelValidationErrorsToModelState()
|
||||||
{
|
{
|
||||||
return base.ReadAsync_UsesTryAddModelValidationErrorsToModelState();
|
return base.ReadAsync_UsesTryAddModelValidationErrorsToModelState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact(Skip = "https://github.com/dotnet/corefx/issues/38492")]
|
||||||
|
public override Task ReadAsync_RequiredAttribute()
|
||||||
|
{
|
||||||
|
// System.Text.Json does not yet support an equivalent of Required.
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ReadAsync_SingleError()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var failTotal = 0;
|
||||||
|
var formatter = GetInputFormatter();
|
||||||
|
|
||||||
|
var content = "[5, 'seven', 3, notnum ]";
|
||||||
|
var contentBytes = Encoding.UTF8.GetBytes(content);
|
||||||
|
var httpContext = GetHttpContext(contentBytes);
|
||||||
|
|
||||||
|
var formatterContext = CreateInputFormatterContext(typeof(List<int>), httpContext);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await formatter.ReadAsync(formatterContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
foreach(var modelState in formatterContext.ModelState)
|
||||||
|
{
|
||||||
|
foreach(var error in modelState.Value.Errors)
|
||||||
|
{
|
||||||
|
failTotal++;
|
||||||
|
Assert.StartsWith("''' is an invalid start of a value", error.ErrorMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.Equal(1, failTotal);
|
||||||
|
}
|
||||||
|
|
||||||
protected override TextInputFormatter GetInputFormatter()
|
protected override TextInputFormatter GetInputFormatter()
|
||||||
{
|
{
|
||||||
return new SystemTextJsonInputFormatter(new JsonOptions());
|
return new SystemTextJsonInputFormatter(new JsonOptions(), LoggerFactory.CreateLogger<SystemTextJsonInputFormatter>());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -226,7 +226,8 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
addMember = !path.EndsWith("." + member, StringComparison.Ordinal);
|
addMember = !path.EndsWith($".{member}", StringComparison.Ordinal)
|
||||||
|
&& !path.EndsWith($"[{member}]", StringComparison.Ordinal);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
using System.IO;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
@ -235,6 +235,45 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
||||||
Assert.Equal(expectedMessage, modelError.ErrorMessage);
|
Assert.Equal(expectedMessage, modelError.ErrorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("[5,7,3]", 0)]
|
||||||
|
[InlineData("[5, 'seven', 3]", 1)]
|
||||||
|
[InlineData("[5, 'seven', 3, 'notnum']", 2)]
|
||||||
|
public async Task ReadAsync_AllowMultipleErrors(string content, int failCount)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var failTotal = 0;
|
||||||
|
var serializerSettings = new JsonSerializerSettings
|
||||||
|
{
|
||||||
|
Error = delegate (object sender, ErrorEventArgs args)
|
||||||
|
{
|
||||||
|
args.ErrorContext.Handled = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var formatter = CreateFormatter(serializerSettings: serializerSettings, allowInputFormatterExceptionMessages: true);
|
||||||
|
|
||||||
|
var contentBytes = Encoding.UTF8.GetBytes(content);
|
||||||
|
var httpContext = GetHttpContext(contentBytes);
|
||||||
|
|
||||||
|
var formatterContext = CreateInputFormatterContext(typeof(List<int>), httpContext);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await formatter.ReadAsync(formatterContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
foreach (var modelState in formatterContext.ModelState)
|
||||||
|
{
|
||||||
|
foreach (var error in modelState.Value.Errors)
|
||||||
|
{
|
||||||
|
failTotal++;
|
||||||
|
Assert.StartsWith("Could not convert string to integer:", error.ErrorMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.Equal(failCount, failTotal);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task ReadAsync_DoNotAllowInputFormatterExceptionMessages_DoesNotWrapJsonInputExceptions()
|
public async Task ReadAsync_DoNotAllowInputFormatterExceptionMessages_DoesNotWrapJsonInputExceptions()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -5,19 +5,14 @@ using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
namespace MvcSandbox.Controllers
|
namespace MvcSandbox.Controllers
|
||||||
{
|
{
|
||||||
[ApiController]
|
|
||||||
public class HomeController : Controller
|
public class HomeController : Controller
|
||||||
{
|
{
|
||||||
[HttpPost("/")]
|
[ModelBinder]
|
||||||
public IActionResult Index(Person person)
|
public string Id { get; set; }
|
||||||
|
|
||||||
|
public IActionResult Index()
|
||||||
{
|
{
|
||||||
return Ok(person);
|
return View();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Person
|
|
||||||
{
|
|
||||||
public int Id { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue