// 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.Collections.Generic; using System.IO; using System.Net.Http.Formatting; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Testing.xunit; using Newtonsoft.Json.Linq; using Xunit; namespace System.Web.Http.Dispatcher { public class HttpErrorTest { public static IEnumerable ErrorKeyValue { get { var httpError = new HttpError(); yield return new object[] { httpError, (Func)(() => httpError.Message), "Message", "Message_Value" }; yield return new object[] { httpError, (Func)(() => httpError.MessageDetail), "MessageDetail", "MessageDetail_Value" }; yield return new object[] { httpError, (Func)(() => httpError.ExceptionMessage), "ExceptionMessage", "ExceptionMessage_Value" }; yield return new object[] { httpError, (Func)(() => httpError.ExceptionType), "ExceptionType", "ExceptionType_Value" }; yield return new object[] { httpError, (Func)(() => httpError.StackTrace), "StackTrace", "StackTrace_Value" }; } } public static IEnumerable HttpErrors { get { yield return new[] { new HttpError() }; yield return new[] { new HttpError("error") }; yield return new[] { new HttpError(new NotImplementedException(), true) }; var modelState = new ModelStateDictionary(); modelState.AddModelError("key", "error"); yield return new[] { new HttpError(modelState, true) }; } } [Fact] public void StringConstructor_AddsCorrectDictionaryItems() { HttpError error = new HttpError("something bad happened"); Assert.Contains(new KeyValuePair("Message", "something bad happened"), error); } [Fact] public void ExceptionConstructorWithDetail_AddsCorrectDictionaryItems() { HttpError error = new HttpError(new ArgumentException("error", new Exception()), true); Assert.Contains(new KeyValuePair("Message", "An error has occurred."), error); Assert.Contains(new KeyValuePair("ExceptionMessage", "error"), error); Assert.Contains(new KeyValuePair("ExceptionType", "System.ArgumentException"), error); Assert.True(error.ContainsKey("StackTrace")); Assert.True(error.ContainsKey("InnerException")); Assert.IsType(error["InnerException"]); } [Fact] public void ModelStateConstructorWithDetail_AddsCorrectDictionaryItems() { // Arrange ModelStateDictionary modelState = new ModelStateDictionary(); var provider = new EmptyModelMetadataProvider(); var metadata = provider.GetMetadataForProperty(typeof(string), nameof(string.Length)); modelState.AddModelError("[0].Name", "error1"); modelState.AddModelError("[0].Name", "error2"); modelState.AddModelError("[0].Address", "error"); modelState.AddModelError("[2].Name", new Exception("OH NO"), metadata); // Act HttpError error = new HttpError(modelState, true); // Assert HttpError modelStateError = error["ModelState"] as HttpError; Assert.Contains(new KeyValuePair("Message", "The request is invalid."), error); Assert.Contains("error1", modelStateError["[0].Name"] as IEnumerable); Assert.Contains("error2", modelStateError["[0].Name"] as IEnumerable); Assert.Contains("error", modelStateError["[0].Address"] as IEnumerable); Assert.True(modelStateError.ContainsKey("[2].Name")); Assert.Contains("OH NO", modelStateError["[2].Name"] as IEnumerable); } [Fact] public void ExceptionConstructorWithoutDetail_AddsCorrectDictionaryItems() { HttpError error = new HttpError(new ArgumentException("error", new Exception()), false); Assert.Contains(new KeyValuePair("Message", "An error has occurred."), error); Assert.False(error.ContainsKey("ExceptionMessage")); Assert.False(error.ContainsKey("ExceptionType")); Assert.False(error.ContainsKey("StackTrace")); Assert.False(error.ContainsKey("InnerException")); } [Fact] public void ModelStateConstructorWithoutDetail_AddsCorrectDictionaryItems() { // Arrange ModelStateDictionary modelState = new ModelStateDictionary(); var provider = new EmptyModelMetadataProvider(); var metadata = provider.GetMetadataForProperty(typeof(string), nameof(string.Length)); modelState.AddModelError("[0].Name", "error1"); modelState.AddModelError("[0].Name", "error2"); modelState.AddModelError("[0].Address", "error"); modelState.AddModelError("[2].Name", new Exception("OH NO"), metadata); // Act HttpError error = new HttpError(modelState, false); // Assert HttpError modelStateError = error["ModelState"] as HttpError; Assert.Contains(new KeyValuePair("Message", "The request is invalid."), error); Assert.Contains("error1", modelStateError["[0].Name"] as IEnumerable); Assert.Contains("error2", modelStateError["[0].Name"] as IEnumerable); Assert.Contains("error", modelStateError["[0].Address"] as IEnumerable); Assert.True(modelStateError.ContainsKey("[2].Name")); Assert.DoesNotContain("OH NO", modelStateError["[2].Name"] as IEnumerable); } [Fact] public void HttpError_Roundtrips_WithJsonFormatter() { HttpError error = new HttpError("error") { { "ErrorCode", 42 }, { "Data", new[] { "a", "b", "c" } } }; MediaTypeFormatter formatter = new JsonMediaTypeFormatter(); MemoryStream stream = new MemoryStream(); formatter.WriteToStreamAsync(typeof(HttpError), error, stream, content: null, transportContext: null).Wait(); stream.Position = 0; HttpError roundtrippedError = formatter.ReadFromStreamAsync(typeof(HttpError), stream, content: null, formatterLogger: null).Result as HttpError; Assert.NotNull(roundtrippedError); Assert.Equal("error", roundtrippedError.Message); Assert.Equal(42L, roundtrippedError["ErrorCode"]); JArray data = roundtrippedError["Data"] as JArray; Assert.Equal(3, data.Count); Assert.Contains("a", data); Assert.Contains("b", data); Assert.Contains("c", data); } [ConditionalFact] // Mono issue - https://github.com/aspnet/External/issues/25 [FrameworkSkipCondition(RuntimeFrameworks.Mono)] public void HttpError_Roundtrips_WithXmlFormatter() { HttpError error = new HttpError("error") { { "ErrorCode", 42 }, { "Data", new[] { "a", "b", "c" } } }; MediaTypeFormatter formatter = new XmlMediaTypeFormatter(); MemoryStream stream = new MemoryStream(); formatter.WriteToStreamAsync(typeof(HttpError), error, stream, content: null, transportContext: null).Wait(); stream.Position = 0; HttpError roundtrippedError = formatter.ReadFromStreamAsync(typeof(HttpError), stream, content: null, formatterLogger: null).Result as HttpError; Assert.NotNull(roundtrippedError); Assert.Equal("error", roundtrippedError.Message); Assert.Equal("42", roundtrippedError["ErrorCode"]); Assert.Equal("a b c", roundtrippedError["Data"]); } [Fact] public void HttpErrorWithWhitespace_Roundtrips_WithXmlFormatter() { string message = " foo\n bar \n "; HttpError error = new HttpError(message); MediaTypeFormatter formatter = new XmlMediaTypeFormatter(); MemoryStream stream = new MemoryStream(); formatter.WriteToStreamAsync(typeof(HttpError), error, stream, content: null, transportContext: null).Wait(); stream.Position = 0; HttpError roundtrippedError = formatter.ReadFromStreamAsync(typeof(HttpError), stream, content: null, formatterLogger: null).Result as HttpError; Assert.NotNull(roundtrippedError); Assert.Equal(message, roundtrippedError.Message); } [Fact] public void HttpError_Roundtrips_WithXmlSerializer() { HttpError error = new HttpError("error") { { "ErrorCode", 42 }, { "Data", new[] { "a", "b", "c" } } }; MediaTypeFormatter formatter = new XmlMediaTypeFormatter() { UseXmlSerializer = true }; MemoryStream stream = new MemoryStream(); formatter.WriteToStreamAsync(typeof(HttpError), error, stream, content: null, transportContext: null).Wait(); stream.Position = 0; HttpError roundtrippedError = formatter.ReadFromStreamAsync(typeof(HttpError), stream, content: null, formatterLogger: null).Result as HttpError; Assert.NotNull(roundtrippedError); Assert.Equal("error", roundtrippedError.Message); Assert.Equal("42", roundtrippedError["ErrorCode"]); Assert.Equal("a b c", roundtrippedError["Data"]); } [Fact] public void HttpErrorForInnerException_Serializes_WithXmlSerializer() { HttpError error = new HttpError(new ArgumentException("error", new Exception("innerError")), includeErrorDetail: true); MediaTypeFormatter formatter = new XmlMediaTypeFormatter() { UseXmlSerializer = true }; MemoryStream stream = new MemoryStream(); formatter.WriteToStreamAsync(typeof(HttpError), error, stream, content: null, transportContext: null).Wait(); stream.Position = 0; string serializedError = new StreamReader(stream).ReadToEnd(); Assert.NotNull(serializedError); Assert.Equal( "An error has occurred.errorSystem.ArgumentExceptionAn error has occurred.innerErrorSystem.Exception", serializedError); } [Fact] public void GetPropertyValue_GetsValue_IfTypeMatches() { HttpError error = new HttpError(); error["key"] = "x"; Assert.Equal("x", error.GetPropertyValue("key")); Assert.Equal("x", error.GetPropertyValue("key")); } [Fact] public void GetPropertyValue_GetsDefault_IfTypeDoesNotMatch() { HttpError error = new HttpError(); error["key"] = "x"; Assert.Null(error.GetPropertyValue("key")); Assert.Equal(0, error.GetPropertyValue("key")); } [Fact] public void GetPropertyValue_GetsDefault_IfPropertyMissing() { HttpError error = new HttpError(); Assert.Null(error.GetPropertyValue("key")); Assert.Equal(0, error.GetPropertyValue("key")); } [Theory] [MemberData(nameof(ErrorKeyValue))] public void HttpErrorStringProperties_UseCorrectHttpErrorKey(HttpError httpError, Func productUnderTest, string key, string actualValue) { // Arrange httpError[key] = actualValue; // Act string expectedValue = productUnderTest.Invoke(); // Assert Assert.Equal(expectedValue, actualValue); } [Fact] public void HttpErrorProperty_InnerException_UsesCorrectHttpErrorKey() { // Arrange HttpError error = new HttpError(new ArgumentException("error", new Exception()), true); // Act HttpError innerException = error.InnerException; // Assert Assert.Same(error["InnerException"], innerException); } [Fact] public void HttpErrorProperty_ModelState_UsesCorrectHttpErrorKey() { // Arrange ModelStateDictionary modelState = new ModelStateDictionary(); modelState.AddModelError("[0].Name", "error1"); HttpError error = new HttpError(modelState, true); // Act HttpError actualModelStateError = error.ModelState; // Assert Assert.Same(error["ModelState"], actualModelStateError); } [Theory] [MemberData(nameof(HttpErrors))] public void HttpErrors_UseCaseInsensitiveComparer(HttpError httpError) { // Arrange var lowercaseKey = "abcd"; var uppercaseKey = "ABCD"; httpError[lowercaseKey] = "error"; // Act & Assert Assert.True(httpError.ContainsKey(lowercaseKey)); Assert.True(httpError.ContainsKey(uppercaseKey)); } } }