-
-
Notifications
You must be signed in to change notification settings - Fork 158
/
Copy pathSourcePointerInExceptionTests.cs
143 lines (118 loc) · 5.15 KB
/
SourcePointerInExceptionTests.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
using System.Net;
using System.Text.Json;
using FluentAssertions;
using JetBrains.Annotations;
using JsonApiDotNetCore.Configuration;
using JsonApiDotNetCore.Errors;
using JsonApiDotNetCore.Resources;
using JsonApiDotNetCore.Serialization.JsonConverters;
using JsonApiDotNetCore.Serialization.Objects;
using JsonApiDotNetCore.Serialization.Request;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging.Abstractions;
using TestBuildingBlocks;
using Xunit;
namespace JsonApiDotNetCoreTests.UnitTests.Serialization.Extensions;
public sealed class SourcePointerInExceptionTests
{
private const string RequestBody = """
{
"data": {
"type": "testResources",
"attributes": {
"ext-namespace:ext-name": "ignored"
}
}
}
""";
[Fact]
public async Task Adds_source_pointer_to_JsonApiException_thrown_from_JsonConverter()
{
// Arrange
const string? relativeSourcePointer = null;
var options = new JsonApiOptions();
IResourceGraph resourceGraph = new ResourceGraphBuilder(options, NullLoggerFactory.Instance).Add<TestResource, long>().Build();
var converter = new ThrowingResourceObjectConverter(resourceGraph, relativeSourcePointer);
var reader = new FakeJsonApiReader(RequestBody, options, converter);
var httpContext = new DefaultHttpContext();
// Act
Func<Task> action = async () => await reader.ReadAsync(httpContext.Request);
// Assert
JsonApiException? exception = (await action.Should().ThrowExactlyAsync<JsonApiException>()).Which;
exception.StackTrace.Should().Contain(nameof(ThrowingResourceObjectConverter));
exception.Errors.ShouldHaveCount(1);
ErrorObject error = exception.Errors[0];
error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity);
error.Title.Should().Be("Extension error");
error.Source.ShouldNotBeNull();
error.Source.Pointer.Should().Be("/data");
}
[Fact]
public async Task Makes_source_pointer_absolute_in_JsonApiException_thrown_from_JsonConverter()
{
// Arrange
const string relativeSourcePointer = "relative/path";
var options = new JsonApiOptions();
IResourceGraph resourceGraph = new ResourceGraphBuilder(options, NullLoggerFactory.Instance).Add<TestResource, long>().Build();
var converter = new ThrowingResourceObjectConverter(resourceGraph, relativeSourcePointer);
var reader = new FakeJsonApiReader(RequestBody, options, converter);
var httpContext = new DefaultHttpContext();
// Act
Func<Task> action = async () => await reader.ReadAsync(httpContext.Request);
// Assert
JsonApiException? exception = (await action.Should().ThrowExactlyAsync<JsonApiException>()).Which;
exception.StackTrace.Should().Contain(nameof(ThrowingResourceObjectConverter));
exception.Errors.ShouldHaveCount(1);
ErrorObject error = exception.Errors[0];
error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity);
error.Title.Should().Be("Extension error");
error.Source.ShouldNotBeNull();
error.Source.Pointer.Should().Be("/data/relative/path");
}
[UsedImplicitly(ImplicitUseTargetFlags.Members)]
private sealed class TestResource : Identifiable<long>;
private sealed class ThrowingResourceObjectConverter(IResourceGraph resourceGraph, string? relativeSourcePointer)
: ResourceObjectConverter(resourceGraph)
{
private readonly string? _relativeSourcePointer = relativeSourcePointer;
private protected override void ValidateExtensionInAttributes(string extensionNamespace, string extensionName, ResourceType resourceType,
Utf8JsonReader reader)
{
var exception = new JsonApiException(new ErrorObject(HttpStatusCode.UnprocessableEntity)
{
Title = "Extension error"
});
if (_relativeSourcePointer != null)
{
exception.Errors[0].Source = new ErrorSource
{
Pointer = _relativeSourcePointer
};
}
CapturedThrow(exception);
}
}
private sealed class FakeJsonApiReader : IJsonApiReader
{
private readonly string _requestBody;
private readonly JsonSerializerOptions _serializerOptions;
public FakeJsonApiReader(string requestBody, JsonApiOptions options, ResourceObjectConverter converter)
{
_requestBody = requestBody;
_serializerOptions = new JsonSerializerOptions(options.SerializerOptions);
_serializerOptions.Converters.Add(converter);
}
public Task<object?> ReadAsync(HttpRequest httpRequest)
{
try
{
JsonSerializer.Deserialize<Document>(_requestBody, _serializerOptions);
}
catch (NotSupportedException exception) when (exception.HasJsonApiException())
{
throw exception.EnrichSourcePointer();
}
return Task.FromResult<object?>(null);
}
}
}