Additional tests

This commit is contained in:
Ryan Brandenburg 2019-06-13 10:58:37 -07:00
parent f575677c95
commit 6bd4b87d2c
6 changed files with 182 additions and 30 deletions

View File

@ -83,6 +83,11 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
path = path.Substring(2);
}
if (path.StartsWith("$[", StringComparison.Ordinal))
{
path = path.Substring(1);
}
// Handle path combinations such as ""+"Property", "Parent"+"Property", or "Parent"+"[12]".
var key = ModelNames.CreatePropertyModelName(context.ModelName, path);

View File

@ -3,16 +3,20 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.IO;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.Extensions.Logging.Testing;
using Newtonsoft.Json;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.Formatters
{
public abstract class JsonInputFormatterTestBase
public abstract class JsonInputFormatterTestBase : LoggedTest
{
[Theory]
[InlineData("application/json", true)]
@ -199,6 +203,26 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
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]
public virtual async Task ReadAsync_AddsModelValidationErrorsToModelState()
{
@ -215,10 +239,8 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
var result = await formatter.ReadAsync(formatterContext);
// Assert
Assert.True(result.HasError);
Assert.Equal(
"Could not convert string to decimal: not-an-age. Path 'Age', line 1, position 44.",
formatterContext.ModelState["Age"].Errors[0].ErrorMessage);
Assert.True(result.HasError, "Model should have had an error!");
Assert.Single(formatterContext.ModelState["Age"].Errors);
}
[Fact]
@ -227,19 +249,18 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
// Arrange
var formatter = GetInputFormatter();
var content = "[0, 23, 300]";
var content = "[0, 23, 33767]";
var contentBytes = Encoding.UTF8.GetBytes(content);
var httpContext = GetHttpContext(contentBytes);
var formatterContext = CreateInputFormatterContext(typeof(byte[]), httpContext);
var formatterContext = CreateInputFormatterContext(typeof(short[]), httpContext);
// Act
var result = await formatter.ReadAsync(formatterContext);
// Assert
Assert.True(result.HasError);
Assert.Equal("The supplied value is invalid.", formatterContext.ModelState["[2]"].Errors[0].ErrorMessage);
Assert.Null(formatterContext.ModelState["[2]"].Errors[0].Exception);
Assert.True(result.HasError, "Model should have produced an error!");
Assert.True(formatterContext.ModelState.ContainsKey("[2]"), "Should have contained key '[2]'");
}
[Fact]
@ -259,9 +280,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
// Assert
Assert.True(result.HasError);
Assert.Equal(
"Error converting value 300 to type 'System.Byte'. Path '[1].Small', line 1, position 69.",
formatterContext.ModelState["names[1].Small"].Errors[0].ErrorMessage);
Assert.Single(formatterContext.ModelState["names[1].Small"].Errors);
}
[Fact]
@ -318,6 +337,45 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
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 static HttpContext GetHttpContext(
@ -356,6 +414,20 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
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
{
public string Name { get; set; }

View File

@ -1,40 +1,80 @@
// 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.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.Formatters
{
public class SystemTextJsonInputFormatterTest : JsonInputFormatterTestBase
{
[Fact(Skip = "https://github.com/aspnet/AspNetCore/issues/8474")]
[Fact]
public override Task ReadAsync_AddsModelValidationErrorsToModelState()
{
return base.ReadAsync_AddsModelValidationErrorsToModelState();
}
[Fact(Skip = "https://github.com/aspnet/AspNetCore/issues/8474")]
[Fact]
public override Task ReadAsync_InvalidArray_AddsOverflowErrorsToModelState()
{
return base.ReadAsync_InvalidArray_AddsOverflowErrorsToModelState();
}
[Fact(Skip = "https://github.com/aspnet/AspNetCore/issues/8474")]
[Fact]
public override Task ReadAsync_InvalidComplexArray_AddsOverflowErrorsToModelState()
{
return base.ReadAsync_InvalidComplexArray_AddsOverflowErrorsToModelState();
}
[Fact(Skip = "https://github.com/aspnet/AspNetCore/issues/8474")]
[Fact]
public override Task 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()
{
return new SystemTextJsonInputFormatter(new JsonOptions());
return new SystemTextJsonInputFormatter(new JsonOptions(), LoggerFactory.CreateLogger<SystemTextJsonInputFormatter>());
}
}
}

View File

@ -226,7 +226,8 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
}
else
{
addMember = !path.EndsWith("." + member, StringComparison.Ordinal);
addMember = !path.EndsWith($".{member}", StringComparison.Ordinal)
&& !path.EndsWith($"[{member}]", StringComparison.Ordinal);
}
}
}

View File

@ -3,7 +3,7 @@
using System;
using System.Buffers;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
@ -235,6 +235,45 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
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]
public async Task ReadAsync_DoNotAllowInputFormatterExceptionMessages_DoesNotWrapJsonInputExceptions()
{

View File

@ -5,19 +5,14 @@ using Microsoft.AspNetCore.Mvc;
namespace MvcSandbox.Controllers
{
[ApiController]
public class HomeController : Controller
{
[HttpPost("/")]
public IActionResult Index(Person person)
[ModelBinder]
public string Id { get; set; }
public IActionResult Index()
{
return Ok(person);
return View();
}
}
public class Person
{
public int Id { get; set; }
}
}