diff --git a/Codebelt.Extensions.Newtonsoft.Json.sln b/Codebelt.Extensions.Newtonsoft.Json.sln
index 45af02b..0eb3889 100644
--- a/Codebelt.Extensions.Newtonsoft.Json.sln
+++ b/Codebelt.Extensions.Newtonsoft.Json.sln
@@ -19,6 +19,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Codebelt.Extensions.Newtons
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Codebelt.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.Tests", "test\Codebelt.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.Tests\Codebelt.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.Tests.csproj", "{1CE82D1C-FEC6-4F25-A0FF-94E137F750AF}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Codebelt.Extensions.AspNetCore.Newtonsoft.Json.Tests", "test\Codebelt.Extensions.AspNetCore.Newtonsoft.Json.Tests\Codebelt.Extensions.AspNetCore.Newtonsoft.Json.Tests.csproj", "{BF6A82F1-93DD-4022-9B20-DF83C3FE3C1B}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -49,6 +51,10 @@ Global
{1CE82D1C-FEC6-4F25-A0FF-94E137F750AF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1CE82D1C-FEC6-4F25-A0FF-94E137F750AF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1CE82D1C-FEC6-4F25-A0FF-94E137F750AF}.Release|Any CPU.Build.0 = Release|Any CPU
+ {BF6A82F1-93DD-4022-9B20-DF83C3FE3C1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {BF6A82F1-93DD-4022-9B20-DF83C3FE3C1B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {BF6A82F1-93DD-4022-9B20-DF83C3FE3C1B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {BF6A82F1-93DD-4022-9B20-DF83C3FE3C1B}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -60,6 +66,7 @@ Global
{260BDF91-E7C7-4CB4-A39D-E1A5374C5602} = {0070E83B-2DDD-4537-A83F-1CF8644F2880}
{90DB61D1-5538-49A4-9F8F-E2F67C1EEED0} = {A3C56B2E-55EE-44EC-876E-B03B8DDA3317}
{1CE82D1C-FEC6-4F25-A0FF-94E137F750AF} = {A3C56B2E-55EE-44EC-876E-B03B8DDA3317}
+ {BF6A82F1-93DD-4022-9B20-DF83C3FE3C1B} = {A3C56B2E-55EE-44EC-876E-B03B8DDA3317}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {0CBE2805-F0FF-4D0F-902C-8B9277A5D3F2}
diff --git a/Directory.Build.props b/Directory.Build.props
index c6920ab..a046cf9 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -70,8 +70,8 @@
-
-
+
+
all
@@ -81,7 +81,7 @@
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
diff --git a/Directory.Build.targets b/Directory.Build.targets
index 62e3013..55cf983 100644
--- a/Directory.Build.targets
+++ b/Directory.Build.targets
@@ -14,8 +14,8 @@
- 00000
- $(MinVerMajor).$(MinVerMinor).$(MinVerPatch).$(BUILD_BUILDNUMBER)
+ 0
+ $(MinVerMajor).$(MinVerMinor).$(MinVerPatch).$(GITHUB_RUN_NUMBER)
diff --git a/src/Codebelt.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json/Codebelt.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.csproj b/src/Codebelt.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json/Codebelt.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.csproj
index fbd6c61..2d7beef 100644
--- a/src/Codebelt.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json/Codebelt.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.csproj
+++ b/src/Codebelt.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json/Codebelt.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.csproj
@@ -6,7 +6,7 @@
- The Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json namespace contains both types and extension methods that complements both the Cuemon.Extensions.Newtonsoft.Json/Cuemon.Extensions.AspNetCore.Newtonsoft.Json namespace while being an addition to the Microsoft.AspNetCore.Mvc namespace. Provides JSON formatters for ASP.NET Core MVC that is powered by Newtonsoft.Json.
+ The Codebelt.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json namespace contains both types and extension methods that complements both the Codebelt.Extensions.Newtonsoft.Json/Codebelt.Extensions.AspNetCore.Newtonsoft.Json namespace while being an addition to the Microsoft.AspNetCore.Mvc namespace. Provides JSON formatters for ASP.NET Core MVC that is powered by Newtonsoft.Json.
extension-methods extensions json-converters add-json-serialization-formatters add-json-formatter-options
@@ -23,8 +23,8 @@
-
-
+
+
diff --git a/src/Codebelt.Extensions.AspNetCore.Newtonsoft.Json/Codebelt.Extensions.AspNetCore.Newtonsoft.Json.csproj b/src/Codebelt.Extensions.AspNetCore.Newtonsoft.Json/Codebelt.Extensions.AspNetCore.Newtonsoft.Json.csproj
index ccbbf1d..b62637f 100644
--- a/src/Codebelt.Extensions.AspNetCore.Newtonsoft.Json/Codebelt.Extensions.AspNetCore.Newtonsoft.Json.csproj
+++ b/src/Codebelt.Extensions.AspNetCore.Newtonsoft.Json/Codebelt.Extensions.AspNetCore.Newtonsoft.Json.csproj
@@ -11,7 +11,7 @@
-
+
diff --git a/src/Codebelt.Extensions.Newtonsoft.Json/Codebelt.Extensions.Newtonsoft.Json.csproj b/src/Codebelt.Extensions.Newtonsoft.Json/Codebelt.Extensions.Newtonsoft.Json.csproj
index 3163ebe..e7cd03a 100644
--- a/src/Codebelt.Extensions.Newtonsoft.Json/Codebelt.Extensions.Newtonsoft.Json.csproj
+++ b/src/Codebelt.Extensions.Newtonsoft.Json/Codebelt.Extensions.Newtonsoft.Json.csproj
@@ -10,8 +10,8 @@
-
-
+
+
diff --git a/test/Codebelt.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.Tests/Assets/SampleModel.cs b/test/Codebelt.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.Tests/Assets/SampleModel.cs
new file mode 100644
index 0000000..95822ab
--- /dev/null
+++ b/test/Codebelt.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.Tests/Assets/SampleModel.cs
@@ -0,0 +1,11 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace Codebelt.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.Assets
+{
+ public class SampleModel
+ {
+ [Required(ErrorMessage = "This field is required.")]
+ [StringLength(100)]
+ public string Name { get; set; }
+ }
+}
diff --git a/test/Codebelt.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.Tests/Assets/StatusCodesController.cs b/test/Codebelt.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.Tests/Assets/StatusCodesController.cs
new file mode 100644
index 0000000..46ee4b9
--- /dev/null
+++ b/test/Codebelt.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.Tests/Assets/StatusCodesController.cs
@@ -0,0 +1,116 @@
+using System;
+using System.Reflection;
+using Cuemon.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+
+namespace Codebelt.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.Assets
+{
+ [ApiController]
+ [Route("[controller]")]
+ public class StatusCodesController : ControllerBase
+ {
+ [HttpGet("400")]
+ public IActionResult Get_400()
+ {
+ throw new BadRequestException(new ArgumentNullException());
+ }
+
+ [HttpGet("401")]
+ public IActionResult Get_401()
+ {
+ throw new UnauthorizedException(new AccessViolationException());
+ }
+
+ [HttpGet("403")]
+ public IActionResult Get_403()
+ {
+ throw new ForbiddenException(new UnauthorizedAccessException());
+ }
+
+ [HttpGet("404")]
+ public IActionResult Get_404()
+ {
+ throw new NotFoundException(new NullReferenceException());
+ }
+
+ [HttpGet("405")]
+ public IActionResult Get_405()
+ {
+ throw new MethodNotAllowedException(new ArgumentException());
+ }
+
+ [HttpGet("406")]
+ public IActionResult Get_406()
+ {
+ throw new NotAcceptableException(new ArgumentException());
+ }
+
+ [HttpGet("409")]
+ public IActionResult Get_409()
+ {
+ throw new ConflictException(new AmbiguousMatchException());
+ }
+
+ [HttpGet("410")]
+ public IActionResult Get_410()
+ {
+ throw new GoneException(new NotImplementedException());
+ }
+
+ [HttpGet("412")]
+ public IActionResult Get_412()
+ {
+ throw new PreconditionFailedException(new ArgumentOutOfRangeException());
+ }
+
+ [HttpGet("413")]
+ public IActionResult Get_413()
+ {
+ throw new PayloadTooLargeException(new ArgumentOutOfRangeException());
+ }
+
+ [HttpGet("415")]
+ public IActionResult Get_415()
+ {
+ throw new UnsupportedMediaTypeException(new ArgumentOutOfRangeException());
+ }
+
+ [HttpGet("428")]
+ public IActionResult Get_428()
+ {
+ throw new PreconditionRequiredException(new ArgumentException());
+ }
+
+ [HttpGet("429")]
+ public IActionResult Get_429()
+ {
+ throw new TooManyRequestsException(new OverflowException());
+ }
+
+ [HttpGet("XXX/{app}")]
+ public IActionResult Get_XXX(string app)
+ {
+ try
+ {
+ throw new ArgumentException("This is an inner exception message ...", nameof(app))
+ {
+ Data =
+ {
+ { nameof(app), app }
+ },
+ HelpLink = "https://www.savvyio.net/"
+ };
+ }
+ catch (Exception e)
+ {
+ throw new NotSupportedException("Main exception - look out for inner!", e);
+ }
+ }
+
+ [HttpPost("/")]
+ public IActionResult Post(SampleModel model)
+ {
+ return Ok(model);
+ }
+ }
+}
diff --git a/test/Codebelt.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.Tests/Codebelt.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.Tests.csproj b/test/Codebelt.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.Tests/Codebelt.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.Tests.csproj
index 19a944d..4a0a988 100644
--- a/test/Codebelt.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.Tests/Codebelt.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.Tests.csproj
+++ b/test/Codebelt.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.Tests/Codebelt.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.Tests.csproj
@@ -5,6 +5,10 @@
Codebelt.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json
+
+
+
+
diff --git a/test/Codebelt.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.Tests/MvcBuilderExtensionsTests.cs b/test/Codebelt.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.Tests/MvcBuilderExtensionsTests.cs
new file mode 100644
index 0000000..bc6653d
--- /dev/null
+++ b/test/Codebelt.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.Tests/MvcBuilderExtensionsTests.cs
@@ -0,0 +1,554 @@
+using System.Net.Http.Headers;
+using System.Threading.Tasks;
+using Codebelt.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.Assets;
+using Codebelt.Extensions.Xunit;
+using Codebelt.Extensions.Xunit.Hosting.AspNetCore;
+using Cuemon.AspNetCore.Diagnostics;
+using Cuemon.Diagnostics;
+using Cuemon.Extensions.AspNetCore.Mvc.Filters;
+using Cuemon.Extensions.DependencyInjection;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.Extensions.DependencyInjection;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Codebelt.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json
+{
+ public class MvcBuilderExtensionsTests : Test
+ {
+ public MvcBuilderExtensionsTests(ITestOutputHelper output) : base(output)
+ {
+ }
+
+ [Theory]
+ [InlineData(FaultSensitivityDetails.All)]
+ [InlineData(FaultSensitivityDetails.Evidence)]
+ [InlineData(FaultSensitivityDetails.FailureWithStackTraceAndData)]
+ [InlineData(FaultSensitivityDetails.FailureWithData)]
+ [InlineData(FaultSensitivityDetails.FailureWithStackTrace)]
+ [InlineData(FaultSensitivityDetails.Failure)]
+ [InlineData(FaultSensitivityDetails.None)]
+ public async Task OnException_ShouldCaptureException_RenderAsProblemDetails_UsingNewtonsoftJson(FaultSensitivityDetails sensitivity)
+ {
+ using var response = await WebHostTestFactory.RunAsync(
+ services =>
+ {
+ services
+ .AddControllers(o => o.Filters.AddFaultDescriptor())
+ .AddApplicationPart(typeof(StatusCodesController).Assembly)
+ .AddNewtonsoftJsonFormatters()
+ .AddFaultDescriptorOptions(o => o.FaultDescriptor = PreferredFaultDescriptor.ProblemDetails);
+ services.PostConfigureAllOf(o => o.SensitivityDetails = sensitivity);
+ },
+ app =>
+ {
+ app.UseRouting();
+ app.UseEndpoints(routes => { routes.MapControllers(); });
+ },
+ responseFactory: client =>
+ {
+ client.DefaultRequestHeaders.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/json"));
+ return client.GetAsync("/statuscodes/XXX/serverError");
+ });
+
+ var body = await response.Content.ReadAsStringAsync();
+ TestOutput.WriteLine(body);
+
+ switch (sensitivity)
+ {
+ case FaultSensitivityDetails.All:
+ Assert.True(Match("""
+ {
+ "type": "about:blank",
+ "title": "InternalServerError",
+ "status": 500,
+ "detail": "An unhandled exception was raised by *",
+ "instance": "http://localhost/statuscodes/XXX/serverError",
+ "traceId": "*",
+ "failure": {
+ "type": "System.NotSupportedException",
+ "source": "Codebelt.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.Tests",
+ "message": "Main exception - look out for inner!",
+ "stack": [
+ "at Codebelt.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.Assets.StatusCodesController.Get_XXX(String app) *",
+ "at *",
+ "at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.SyncActionResultExecutor.Execute*",
+ "at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeActionMethodAsync()",
+ "at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)",
+ "at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeNextActionFilterAsync()",
+ "--- End of stack trace from previous location ---",
+ "at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)",
+ "at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)",
+ "at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()",
+ "--- End of stack trace from previous location ---",
+ "at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.*"
+ ],
+ "inner": {
+ "type": "System.ArgumentException",
+ "source": "Codebelt.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.Tests",
+ "message": "This is an inner exception message ... (Parameter 'app')",
+ "stack": [
+ "at Codebelt.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.Assets.StatusCodesController.Get_XXX(String app)*"
+ ],
+ "data": {
+ "key": "serverError"
+ },
+ "paramName": "app"
+ }
+ },
+ "request": {
+ "location": "http://localhost/statuscodes/XXX/serverError",
+ "method": "GET",
+ "headers": {
+ "accept": "application/json",
+ "host": "localhost"
+ },
+ "query": [],
+ "cookies": [],
+ "body": ""
+ }
+ }
+ """.ReplaceLineEndings(), body.ReplaceLineEndings(), o => o.ThrowOnNoMatch = true));
+ break;
+ case FaultSensitivityDetails.Evidence:
+ Assert.True(Match("""
+ {
+ "type": "about:blank",
+ "title": "InternalServerError",
+ "status": 500,
+ "detail": "An unhandled exception was raised by *",
+ "instance": "http://localhost/statuscodes/XXX/serverError",
+ "traceId": "*",
+ "request": {
+ "location": "http://localhost/statuscodes/XXX/serverError",
+ "method": "GET",
+ "headers": {
+ "accept": "application/json",
+ "host": "localhost"
+ },
+ "query": [],
+ "cookies": [],
+ "body": ""
+ }
+ }
+ """.ReplaceLineEndings(), body.ReplaceLineEndings(), o => o.ThrowOnNoMatch = true));
+ break;
+ case FaultSensitivityDetails.FailureWithStackTraceAndData:
+ Assert.True(Match("""
+ {
+ "type": "about:blank",
+ "title": "InternalServerError",
+ "status": 500,
+ "detail": "An unhandled exception was raised by *",
+ "instance": "http://localhost/statuscodes/XXX/serverError",
+ "traceId": "*",
+ "failure": {
+ "type": "System.NotSupportedException",
+ "source": "Codebelt.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.Tests",
+ "message": "Main exception - look out for inner!",
+ "stack": [
+ "at Codebelt.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.Assets.StatusCodesController.Get_XXX(String app) *",
+ "at *",
+ "at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.SyncActionResultExecutor.Execute*",
+ "at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeActionMethodAsync()",
+ "at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)",
+ "at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeNextActionFilterAsync()",
+ "--- End of stack trace from previous location ---",
+ "at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)",
+ "at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)",
+ "at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()",
+ "--- End of stack trace from previous location ---",
+ "at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.*"
+ ],
+ "inner": {
+ "type": "System.ArgumentException",
+ "source": "Codebelt.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.Tests",
+ "message": "This is an inner exception message ... (Parameter 'app')",
+ "stack": [
+ "at Codebelt.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.Assets.StatusCodesController.Get_XXX(String app)*"
+ ],
+ "data": {
+ "key": "serverError"
+ },
+ "paramName": "app"
+ }
+ }
+ }
+ """.ReplaceLineEndings(), body.ReplaceLineEndings(), o => o.ThrowOnNoMatch = true));
+ break;
+ case FaultSensitivityDetails.FailureWithData:
+ Assert.True(Match("""
+ {
+ "type": "about:blank",
+ "title": "InternalServerError",
+ "status": 500,
+ "detail": "An unhandled exception was raised by *",
+ "instance": "http://localhost/statuscodes/XXX/serverError",
+ "traceId": "*",
+ "failure": {
+ "type": "System.NotSupportedException",
+ "source": "Codebelt.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.Tests",
+ "message": "Main exception - look out for inner!",
+ "inner": {
+ "type": "System.ArgumentException",
+ "source": "Codebelt.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.Tests",
+ "message": "This is an inner exception message ... (Parameter 'app')",
+ "data": {
+ "key": "serverError"
+ },
+ "paramName": "app"
+ }
+ }
+ }
+ """.ReplaceLineEndings(), body.ReplaceLineEndings(), o => o.ThrowOnNoMatch = true));
+ break;
+ case FaultSensitivityDetails.FailureWithStackTrace:
+ Assert.True(Match("""
+ {
+ "type": "about:blank",
+ "title": "InternalServerError",
+ "status": 500,
+ "detail": "An unhandled exception was raised by *",
+ "instance": "http://localhost/statuscodes/XXX/serverError",
+ "traceId": "*",
+ "failure": {
+ "type": "System.NotSupportedException",
+ "source": "Codebelt.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.Tests",
+ "message": "Main exception - look out for inner!",
+ "stack": [
+ "at Codebelt.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.Assets.StatusCodesController.Get_XXX(String app) *",
+ "at *",
+ "at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.SyncActionResultExecutor.Execute*",
+ "at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeActionMethodAsync()",
+ "at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)",
+ "at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeNextActionFilterAsync()",
+ "--- End of stack trace from previous location ---",
+ "at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)",
+ "at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)",
+ "at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()",
+ "--- End of stack trace from previous location ---",
+ "at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.*"
+ ],
+ "inner": {
+ "type": "System.ArgumentException",
+ "source": "Codebelt.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.Tests",
+ "message": "This is an inner exception message ... (Parameter 'app')",
+ "stack": [
+ "at Codebelt.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.Assets.StatusCodesController.Get_XXX(String app)*"
+ ],
+ "paramName": "app"
+ }
+ }
+ }
+ """.ReplaceLineEndings(), body.ReplaceLineEndings(), o => o.ThrowOnNoMatch = true));
+ break;
+ case FaultSensitivityDetails.Failure:
+ Assert.True(Match("""
+ {
+ "type": "about:blank",
+ "title": "InternalServerError",
+ "status": 500,
+ "detail": "An unhandled exception was raised by *",
+ "instance": "http://localhost/statuscodes/XXX/serverError",
+ "traceId": "*",
+ "failure": {
+ "type": "System.NotSupportedException",
+ "source": "Codebelt.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.Tests",
+ "message": "Main exception - look out for inner!",
+ "inner": {
+ "type": "System.ArgumentException",
+ "source": "Codebelt.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.Tests",
+ "message": "This is an inner exception message ... (Parameter 'app')",
+ "paramName": "app"
+ }
+ }
+ }
+ """.ReplaceLineEndings(), body.ReplaceLineEndings(), o => o.ThrowOnNoMatch = true));
+ break;
+ case FaultSensitivityDetails.None:
+ Assert.True(Match("""
+ {
+ "type": "about:blank",
+ "title": "InternalServerError",
+ "status": 500,
+ "detail": "An unhandled exception was raised by *",
+ "instance": "http://localhost/statuscodes/XXX/serverError",
+ "traceId": "*"
+ }
+ """.ReplaceLineEndings(), body.ReplaceLineEndings(), o => o.ThrowOnNoMatch = true));
+ break;
+ }
+ }
+
+ [Theory]
+ [InlineData(FaultSensitivityDetails.All)]
+ [InlineData(FaultSensitivityDetails.Evidence)]
+ [InlineData(FaultSensitivityDetails.FailureWithStackTraceAndData)]
+ [InlineData(FaultSensitivityDetails.FailureWithData)]
+ [InlineData(FaultSensitivityDetails.FailureWithStackTrace)]
+ [InlineData(FaultSensitivityDetails.Failure)]
+ [InlineData(FaultSensitivityDetails.None)]
+ public async Task OnException_ShouldCaptureException_RenderAsDefault_UsingNewtonsoftJson(FaultSensitivityDetails sensitivity)
+ {
+ using var response = await WebHostTestFactory.RunAsync(
+ services =>
+ {
+ services
+ .AddControllers(o => o.Filters.AddFaultDescriptor())
+ .AddApplicationPart(typeof(StatusCodesController).Assembly)
+ .AddNewtonsoftJsonFormatters()
+ .AddFaultDescriptorOptions(o => o.FaultDescriptor = PreferredFaultDescriptor.FaultDetails);
+ services.PostConfigureAllOf(o => o.SensitivityDetails = sensitivity);
+ },
+ app =>
+ {
+ app.UseRouting();
+ app.UseEndpoints(routes => { routes.MapControllers(); });
+ },
+ responseFactory: client =>
+ {
+ client.DefaultRequestHeaders.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/json"));
+ return client.GetAsync("/statuscodes/XXX/serverError");
+ });
+
+ var body = await response.Content.ReadAsStringAsync();
+ TestOutput.WriteLine(body);
+
+ switch (sensitivity)
+ {
+ case FaultSensitivityDetails.All:
+ Assert.True(Match("""
+ {
+ "error": {
+ "instance": "http://localhost/statuscodes/XXX/serverError",
+ "status": 500,
+ "code": "InternalServerError",
+ "message": "An unhandled exception was raised by *.",
+ "failure": {
+ "type": "System.NotSupportedException",
+ "source": "Codebelt.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.Tests",
+ "message": "Main exception - look out for inner!",
+ "stack": [
+ "at Codebelt.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.Assets.StatusCodesController.Get_XXX(String app) *",
+ "at *",
+ "at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.SyncActionResultExecutor.Execute*",
+ "at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeActionMethodAsync()",
+ "at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)",
+ "at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeNextActionFilterAsync()",
+ "--- End of stack trace from previous location ---",
+ "at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)",
+ "at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)",
+ "at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()",
+ "--- End of stack trace from previous location ---",
+ "at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.*"
+ ],
+ "inner": {
+ "type": "System.ArgumentException",
+ "source": "Codebelt.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.Tests",
+ "message": "This is an inner exception message ... (Parameter 'app')",
+ "stack": [
+ "at Codebelt.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.Assets.StatusCodesController.Get_XXX(String app)*"
+ ],
+ "data": {
+ "app": "serverError"
+ },
+ "paramName": "app"
+ }
+ }
+ },
+ "evidence": {
+ "request": {
+ "location": "http://localhost/statuscodes/XXX/serverError",
+ "method": "GET",
+ "headers": {
+ "accept": "application/json",
+ "host": "localhost"
+ },
+ "query": [],
+ "cookies": [],
+ "body": ""
+ }
+ },
+ "traceId": "*"
+ }
+ """.ReplaceLineEndings(), body.ReplaceLineEndings(), o => o.ThrowOnNoMatch = true));
+ break;
+ case FaultSensitivityDetails.Evidence:
+ Assert.True(Match("""
+ {
+ "error": {
+ "instance": "http://localhost/statuscodes/XXX/serverError",
+ "status": 500,
+ "code": "InternalServerError",
+ "message": "An unhandled exception was raised by *."
+ },
+ "evidence": {
+ "request": {
+ "location": "http://localhost/statuscodes/XXX/serverError",
+ "method": "GET",
+ "headers": {
+ "accept": "application/json",
+ "host": "localhost"
+ },
+ "query": [],
+ "cookies": [],
+ "body": ""
+ }
+ },
+ "traceId": "*"
+ }
+ """.ReplaceLineEndings(), body.ReplaceLineEndings(), o => o.ThrowOnNoMatch = true));
+ break;
+ case FaultSensitivityDetails.FailureWithStackTraceAndData:
+ Assert.True(Match("""
+ {
+ "error": {
+ "instance": "http://localhost/statuscodes/XXX/serverError",
+ "status": 500,
+ "code": "InternalServerError",
+ "message": "An unhandled exception was raised by *.",
+ "failure": {
+ "type": "System.NotSupportedException",
+ "source": "Codebelt.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.Tests",
+ "message": "Main exception - look out for inner!",
+ "stack": [
+ "at Codebelt.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.Assets.StatusCodesController.Get_XXX(String app) *",
+ "at *",
+ "at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.SyncActionResultExecutor.Execute*",
+ "at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeActionMethodAsync()",
+ "at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)",
+ "at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeNextActionFilterAsync()",
+ "--- End of stack trace from previous location ---",
+ "at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)",
+ "at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)",
+ "at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()",
+ "--- End of stack trace from previous location ---",
+ "at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.*"
+ ],
+ "inner": {
+ "type": "System.ArgumentException",
+ "source": "Codebelt.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.Tests",
+ "message": "This is an inner exception message ... (Parameter 'app')",
+ "stack": [
+ "at Codebelt.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.Assets.StatusCodesController.Get_XXX(String app)*"
+ ],
+ "data": {
+ "app": "serverError"
+ },
+ "paramName": "app"
+ }
+ }
+ },
+ "traceId": "*"
+ }
+ """.ReplaceLineEndings(), body.ReplaceLineEndings(), o => o.ThrowOnNoMatch = true));
+ break;
+ case FaultSensitivityDetails.FailureWithData:
+ Assert.True(Match("""
+ {
+ "error": {
+ "instance": "http://localhost/statuscodes/XXX/serverError",
+ "status": 500,
+ "code": "InternalServerError",
+ "message": "An unhandled exception was raised by *.",
+ "failure": {
+ "type": "System.NotSupportedException",
+ "source": "Codebelt.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.Tests",
+ "message": "Main exception - look out for inner!",
+ "inner": {
+ "type": "System.ArgumentException",
+ "source": "Codebelt.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.Tests",
+ "message": "This is an inner exception message ... (Parameter 'app')",
+ "data": {
+ "app": "serverError"
+ },
+ "paramName": "app"
+ }
+ }
+ },
+ "traceId": "*"
+ }
+ """.ReplaceLineEndings(), body.ReplaceLineEndings(), o => o.ThrowOnNoMatch = true));
+ break;
+ case FaultSensitivityDetails.FailureWithStackTrace:
+ Assert.True(Match("""
+ {
+ "error": {
+ "instance": "http://localhost/statuscodes/XXX/serverError",
+ "status": 500,
+ "code": "InternalServerError",
+ "message": "An unhandled exception was raised by *.",
+ "failure": {
+ "type": "System.NotSupportedException",
+ "source": "Codebelt.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.Tests",
+ "message": "Main exception - look out for inner!",
+ "stack": [
+ "at Codebelt.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.Assets.StatusCodesController.Get_XXX(String app) *",
+ "at *",
+ "at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.SyncActionResultExecutor.Execute*",
+ "at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeActionMethodAsync()",
+ "at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)",
+ "at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeNextActionFilterAsync()",
+ "--- End of stack trace from previous location ---",
+ "at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)",
+ "at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)",
+ "at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()",
+ "--- End of stack trace from previous location ---",
+ "at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.*"
+ ],
+ "inner": {
+ "type": "System.ArgumentException",
+ "source": "Codebelt.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.Tests",
+ "message": "This is an inner exception message ... (Parameter 'app')",
+ "stack": [
+ "at Codebelt.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.Assets.StatusCodesController.Get_XXX(String app)*"
+ ],
+ "paramName": "app"
+ }
+ }
+ },
+ "traceId": "*"
+ }
+ """.ReplaceLineEndings(), body.ReplaceLineEndings(), o => o.ThrowOnNoMatch = true));
+ break;
+ case FaultSensitivityDetails.Failure:
+ Assert.True(Match("""
+ {
+ "error": {
+ "instance": "http://localhost/statuscodes/XXX/serverError",
+ "status": 500,
+ "code": "InternalServerError",
+ "message": "An unhandled exception was raised by *.",
+ "failure": {
+ "type": "System.NotSupportedException",
+ "source": "Codebelt.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.Tests",
+ "message": "Main exception - look out for inner!",
+ "inner": {
+ "type": "System.ArgumentException",
+ "source": "Codebelt.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.Tests",
+ "message": "This is an inner exception message ... (Parameter 'app')",
+ "paramName": "app"
+ }
+ }
+ },
+ "traceId": "*"
+ }
+ """.ReplaceLineEndings(), body.ReplaceLineEndings(), o => o.ThrowOnNoMatch = true));
+ break;
+ case FaultSensitivityDetails.None:
+ Assert.True(Match("""
+ {
+ "error": {
+ "instance": "http://localhost/statuscodes/XXX/serverError",
+ "status": 500,
+ "code": "InternalServerError",
+ "message": "An unhandled exception was raised by *."
+ },
+ "traceId": "*"
+ }
+ """.ReplaceLineEndings(), body.ReplaceLineEndings(), o => o.ThrowOnNoMatch = true));
+ break;
+ }
+ }
+ }
+}
diff --git a/test/Codebelt.Extensions.AspNetCore.Newtonsoft.Json.Tests/Codebelt.Extensions.AspNetCore.Newtonsoft.Json.Tests.csproj b/test/Codebelt.Extensions.AspNetCore.Newtonsoft.Json.Tests/Codebelt.Extensions.AspNetCore.Newtonsoft.Json.Tests.csproj
new file mode 100644
index 0000000..9f8c332
--- /dev/null
+++ b/test/Codebelt.Extensions.AspNetCore.Newtonsoft.Json.Tests/Codebelt.Extensions.AspNetCore.Newtonsoft.Json.Tests.csproj
@@ -0,0 +1,16 @@
+
+
+
+ net9.0;net8.0
+ Codebelt.Extensions.AspNetCore.Newtonsoft.Json
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/Codebelt.Extensions.AspNetCore.Newtonsoft.Json.Tests/Formatters/ServiceCollectionExtensionsTest.cs b/test/Codebelt.Extensions.AspNetCore.Newtonsoft.Json.Tests/Formatters/ServiceCollectionExtensionsTest.cs
new file mode 100644
index 0000000..5534233
--- /dev/null
+++ b/test/Codebelt.Extensions.AspNetCore.Newtonsoft.Json.Tests/Formatters/ServiceCollectionExtensionsTest.cs
@@ -0,0 +1,308 @@
+using System;
+using System.Net;
+using System.Net.Http.Headers;
+using System.Threading.Tasks;
+using Codebelt.Extensions.Xunit;
+using Codebelt.Extensions.Xunit.Hosting.AspNetCore;
+using Cuemon.AspNetCore.Authentication.Basic;
+using Cuemon.AspNetCore.Diagnostics;
+using Cuemon.AspNetCore.Http;
+using Cuemon.Diagnostics;
+using Cuemon.Extensions.AspNetCore.Authentication;
+using Cuemon.Extensions.AspNetCore.Diagnostics;
+using Cuemon.Extensions.DependencyInjection;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.TestHost;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Net.Http.Headers;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Codebelt.Extensions.AspNetCore.Newtonsoft.Json.Formatters
+{
+ public class ServiceCollectionExtensionsTest : Test
+ {
+ public ServiceCollectionExtensionsTest(ITestOutputHelper output) : base(output)
+ {
+ }
+
+ [Fact]
+ public async Task AddNewtonsoftJsonExceptionResponseFormatter_ShouldCaptureException_RenderAsExceptionDescriptor_UsingNewtonsoftJson_WithSensitivityAll()
+ {
+ using var response = await WebHostTestFactory.RunAsync(
+ services =>
+ {
+ services.AddFaultDescriptorOptions(o => o.FaultDescriptor = PreferredFaultDescriptor.FaultDetails);
+ services.AddNewtonsoftJsonExceptionResponseFormatter();
+ services.PostConfigureAllOf(o => o.SensitivityDetails = FaultSensitivityDetails.All);
+ },
+ app =>
+ {
+ app.UseFaultDescriptorExceptionHandler();
+ app.Use(async (context, next) =>
+ {
+ try
+ {
+ throw new ArgumentException("This is an inner exception message ...", nameof(app))
+ {
+ Data =
+ {
+ { "1st", "data value" }
+ },
+ HelpLink = "https://www.savvyio.net/"
+ };
+ }
+ catch (Exception e)
+ {
+ throw new NotFoundException("Main exception - look out for inner!", e);
+ }
+
+ await next(context);
+ });
+ },
+ responseFactory: client =>
+ {
+ client.DefaultRequestHeaders.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/json"));
+ return client.GetAsync("/");
+ });
+
+ var body = await response.Content.ReadAsStringAsync();
+
+ TestOutput.WriteLine(body);
+
+ Assert.True(Match("""
+ {
+ "error": {
+ "instance": "http://localhost/",
+ "status": 404,
+ "code": "NotFound",
+ "message": "Main exception - look out for inner!",
+ "failure": {
+ "type": "Cuemon.AspNetCore.Http.NotFoundException",
+ "source": "Codebelt.Extensions.AspNetCore.Newtonsoft.Json.Tests",
+ "message": "Main exception - look out for inner!",
+ "stack": [
+ "at Codebelt.Extensions.AspNetCore.Newtonsoft.Json.Formatters.ServiceCollectionExtensionsTest.<>c.<*",
+ "--- End of stack trace from previous location ---",
+ "at Microsoft.AspNetCore.Diagnostics.ExceptionHandler*"
+ ],
+ "headers": {},
+ "statusCode": 404,
+ "reasonPhrase": "Not Found",
+ "inner": {
+ "type": "System.ArgumentException",
+ "source": "Codebelt.Extensions.AspNetCore.Newtonsoft.Json.Tests",
+ "message": "This is an inner exception message ... (Parameter 'app')",
+ "stack": [
+ "at Codebelt.Extensions.AspNetCore.Newtonsoft.Json.Formatters.ServiceCollectionExtensionsTest.<>c.<*"
+ ],
+ "data": {
+ "1st": "data value"
+ },
+ "paramName": "app"
+ }
+ }
+ },
+ "evidence": {
+ "request": {
+ "location": "http://localhost/",
+ "method": "GET",
+ "headers": {
+ "accept": "application/json",
+ "host": "localhost"
+ },
+ "query": [],
+ "cookies": [],
+ "body": ""
+ }
+ },
+ "traceId": "*"
+ }
+ """.ReplaceLineEndings(), body.ReplaceLineEndings(), o => o.ThrowOnNoMatch = true));
+ }
+
+ [Fact]
+ public async Task AddNewtonsoftJsonExceptionResponseFormatter_ShouldCaptureException_RenderAsProblemDetails_UsingNewtonsoftJson_WithSensitivityAll()
+ {
+ using var response = await WebHostTestFactory.RunAsync(
+ services =>
+ {
+ services.AddFaultDescriptorOptions(o => o.FaultDescriptor = PreferredFaultDescriptor.ProblemDetails);
+ services.AddNewtonsoftJsonExceptionResponseFormatter();
+ services.PostConfigureAllOf(o => o.SensitivityDetails = FaultSensitivityDetails.All);
+ },
+ app =>
+ {
+ app.UseFaultDescriptorExceptionHandler();
+ app.Use(async (context, next) =>
+ {
+ try
+ {
+ throw new ArgumentException("This is an inner exception message ...", nameof(app))
+ {
+ Data =
+ {
+ { "1st", "data value" }
+ },
+ HelpLink = "https://www.savvyio.net/"
+ };
+ }
+ catch (Exception e)
+ {
+ throw new NotFoundException("Main exception - look out for inner!", e);
+ }
+
+ await next(context);
+ });
+ },
+ responseFactory: client =>
+ {
+ client.DefaultRequestHeaders.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/json"));
+ return client.GetAsync("/");
+ });
+
+ var body = await response.Content.ReadAsStringAsync();
+
+ TestOutput.WriteLine(body);
+
+ Assert.True(Match("""
+ {
+ "type": "about:blank",
+ "title": "NotFound",
+ "status": 404,
+ "detail": "Main exception - look out for inner!",
+ "instance": "http://localhost/",
+ "traceId": "*",
+ "failure": {
+ "type": "Cuemon.AspNetCore.Http.NotFoundException",
+ "source": "Codebelt.Extensions.AspNetCore.Newtonsoft.Json.Tests",
+ "message": "Main exception - look out for inner!",
+ "stack": [
+ "at Codebelt.Extensions.AspNetCore.Newtonsoft.Json.Formatters.ServiceCollectionExtensionsTest.<>c.<*",
+ "--- End of stack trace from previous location ---",
+ "at Microsoft.AspNetCore.Diagnostics.ExceptionHandler*"
+ ],
+ "headers": {},
+ "statusCode": 404,
+ "reasonPhrase": "Not Found",
+ "inner": {
+ "type": "System.ArgumentException",
+ "source": "Codebelt.Extensions.AspNetCore.Newtonsoft.Json.Tests",
+ "message": "This is an inner exception message ... (Parameter 'app')",
+ "stack": [
+ "at Codebelt.Extensions.AspNetCore.Newtonsoft.Json.Formatters.ServiceCollectionExtensionsTest.<>c.<*"
+ ],
+ "data": {
+ "key": "data value"
+ },
+ "paramName": "app"
+ }
+ },
+ "request": {
+ "location": "http://localhost/",
+ "method": "GET",
+ "headers": {
+ "accept": "application/json",
+ "host": "localhost"
+ },
+ "query": [],
+ "cookies": [],
+ "body": ""
+ }
+ }
+ """.ReplaceLineEndings(), body.ReplaceLineEndings(), o => o.ThrowOnNoMatch = true));
+ }
+
+ [Theory]
+ [InlineData(FaultSensitivityDetails.All)]
+ [InlineData(FaultSensitivityDetails.None)]
+ public async void AddNewtonsoftJsonExceptionResponseFormatter_AuthorizationResponseHandler_BasicScheme_ShouldRenderResponseInJsonByNewtonsoft_UsingAspNetBootstrapping(FaultSensitivityDetails sensitivityDetails)
+ {
+ using (var startup = WebHostTestFactory.Create(services =>
+ {
+ services.AddNewtonsoftJsonExceptionResponseFormatter();
+ services.AddAuthorizationResponseHandler();
+ services.AddAuthentication(BasicAuthorizationHeader.Scheme)
+ .AddBasic(o =>
+ {
+ o.RequireSecureConnection = false;
+ o.Authenticator = (username, password) => null;
+ });
+ services.AddAuthorization(o =>
+ {
+ o.FallbackPolicy = new AuthorizationPolicyBuilder()
+ .AddAuthenticationSchemes(BasicAuthorizationHeader.Scheme)
+ .RequireAuthenticatedUser()
+ .Build();
+
+ });
+ services.AddRouting();
+ services.PostConfigureAllExceptionDescriptorOptions(o => o.SensitivityDetails = sensitivityDetails);
+ }, app =>
+ {
+ app.UseRouting();
+ app.UseAuthentication();
+ app.UseAuthorization();
+ app.UseEndpoints(endpoints =>
+ {
+ endpoints.MapGet("/", context => context.Response.WriteAsync($"Hello {context.User.Identity!.Name}"));
+ });
+ }))
+ {
+ var client = startup.Host.GetTestClient();
+ var bb = new BasicAuthorizationHeaderBuilder()
+ .AddUserName("Agent")
+ .AddPassword("Test");
+
+ client.DefaultRequestHeaders.Add(HeaderNames.Authorization, bb.Build().ToString());
+ client.DefaultRequestHeaders.Add(HeaderNames.Accept, "application/json");
+
+ var result = await client.GetAsync("/");
+ var content = await result.Content.ReadAsStringAsync();
+
+ TestOutput.WriteLine(content);
+
+ Assert.Equal(HttpStatusCode.Unauthorized, result.StatusCode);
+ Assert.Equal("Basic realm=\"AuthenticationServer\"", result.Headers.WwwAuthenticate.ToString());
+ if (sensitivityDetails == FaultSensitivityDetails.All)
+ {
+ Assert.Equal("""
+ {
+ "error": {
+ "status": 401,
+ "code": "Unauthorized",
+ "message": "The request has not been applied because it lacks valid authentication credentials for the target resource.",
+ "failure": {
+ "type": "Cuemon.AspNetCore.Http.UnauthorizedException",
+ "message": "The request has not been applied because it lacks valid authentication credentials for the target resource.",
+ "headers": {},
+ "statusCode": 401,
+ "reasonPhrase": "Unauthorized",
+ "inner": {
+ "type": "System.Security.SecurityException",
+ "message": "Unable to authenticate Agent."
+ }
+ }
+ }
+ }
+ """.ReplaceLineEndings(), content.ReplaceLineEndings());
+ }
+ else
+ {
+ Assert.Equal("""
+ {
+ "error": {
+ "status": 401,
+ "code": "Unauthorized",
+ "message": "The request has not been applied because it lacks valid authentication credentials for the target resource."
+ }
+ }
+ """.ReplaceLineEndings(), content.ReplaceLineEndings());
+ }
+ }
+ }
+
+ }
+}
diff --git a/test/Codebelt.Extensions.Newtonsoft.Json.Tests/Codebelt.Extensions.Newtonsoft.Json.Tests.csproj b/test/Codebelt.Extensions.Newtonsoft.Json.Tests/Codebelt.Extensions.Newtonsoft.Json.Tests.csproj
index 82e7553..aff0c3a 100644
--- a/test/Codebelt.Extensions.Newtonsoft.Json.Tests/Codebelt.Extensions.Newtonsoft.Json.Tests.csproj
+++ b/test/Codebelt.Extensions.Newtonsoft.Json.Tests/Codebelt.Extensions.Newtonsoft.Json.Tests.csproj
@@ -9,9 +9,9 @@
-
-
-
+
+
+