Skip to content

Commit cd9fa50

Browse files
committed
Basic boilerplate for writing meta tests
Related to #1500
1 parent 73e9e33 commit cd9fa50

File tree

2 files changed

+186
-0
lines changed

2 files changed

+186
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using JsonApiDotNetCore.AtomicOperations;
2+
using JsonApiDotNetCore.Configuration;
3+
using JsonApiDotNetCore.Controllers;
4+
using JsonApiDotNetCore.Middleware;
5+
using JsonApiDotNetCore.Resources;
6+
using Microsoft.Extensions.Logging;
7+
8+
namespace JsonApiDotNetCoreTests.IntegrationTests.Meta;
9+
10+
public sealed class OperationsController(
11+
IJsonApiOptions options, IResourceGraph resourceGraph, ILoggerFactory loggerFactory, IOperationsProcessor processor, IJsonApiRequest request,
12+
ITargetedFields targetedFields, IAtomicOperationFilter operationFilter)
13+
: JsonApiOperationsController(options, resourceGraph, loggerFactory, processor, request, targetedFields, operationFilter);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
using System.Net;
2+
using System.Text.Json;
3+
using FluentAssertions;
4+
using JsonApiDotNetCore.Serialization.Objects;
5+
using JsonApiDotNetCore.Serialization.Request.Adapters;
6+
using JsonApiDotNetCore.Serialization.Response;
7+
using Microsoft.Extensions.DependencyInjection;
8+
using TestBuildingBlocks;
9+
using Xunit;
10+
11+
namespace JsonApiDotNetCoreTests.IntegrationTests.Meta;
12+
13+
public sealed class RequestMetaTests : IClassFixture<IntegrationTestContext<TestableStartup<MetaDbContext>, MetaDbContext>>
14+
{
15+
private readonly IntegrationTestContext<TestableStartup<MetaDbContext>, MetaDbContext> _testContext;
16+
private readonly MetaFakers _fakers = new();
17+
18+
public RequestMetaTests(IntegrationTestContext<TestableStartup<MetaDbContext>, MetaDbContext> testContext)
19+
{
20+
_testContext = testContext;
21+
22+
testContext.UseController<ProductFamiliesController>();
23+
testContext.UseController<SupportTicketsController>();
24+
testContext.UseController<OperationsController>();
25+
26+
testContext.ConfigureServices(services =>
27+
{
28+
services.AddSingleton<IResponseMeta, SupportResponseMeta>();
29+
services.AddSingleton<RequestDocumentStore>();
30+
services.AddScoped<DocumentAdapter>();
31+
32+
services.AddScoped<IDocumentAdapter>(serviceProvider =>
33+
{
34+
var documentAdapter = serviceProvider.GetRequiredService<DocumentAdapter>();
35+
var requestDocumentStore = serviceProvider.GetRequiredService<RequestDocumentStore>();
36+
return new CapturingDocumentAdapter(documentAdapter, requestDocumentStore);
37+
});
38+
});
39+
}
40+
41+
[Fact]
42+
public async Task Accepts_top_level_meta_in_patch_resource_request()
43+
{
44+
// Arrange
45+
var store = _testContext.Factory.Services.GetRequiredService<RequestDocumentStore>();
46+
47+
SupportTicket existingTicket = _fakers.SupportTicket.GenerateOne();
48+
49+
await _testContext.RunOnDatabaseAsync(async dbContext =>
50+
{
51+
dbContext.SupportTickets.Add(existingTicket);
52+
await dbContext.SaveChangesAsync();
53+
});
54+
55+
var requestBody = new
56+
{
57+
data = new
58+
{
59+
type = "supportTickets",
60+
id = existingTicket.StringId
61+
},
62+
meta = new
63+
{
64+
category = "bug",
65+
priority = 1,
66+
components = new[]
67+
{
68+
"login",
69+
"single-sign-on"
70+
},
71+
relatedTo = new[]
72+
{
73+
new
74+
{
75+
id = 123,
76+
link = "https://www.ticket-system.com/bugs/123"
77+
},
78+
new
79+
{
80+
id = 789,
81+
link = "https://www.ticket-system.com/bugs/789"
82+
}
83+
}
84+
}
85+
};
86+
87+
string route = $"/supportTickets/{existingTicket.StringId}";
88+
89+
// Act
90+
(HttpResponseMessage httpResponse, _) = await _testContext.ExecutePatchAsync<Document>(route, requestBody);
91+
92+
// Assert
93+
httpResponse.ShouldHaveStatusCode(HttpStatusCode.NoContent);
94+
95+
store.Document.Should().NotBeNull();
96+
store.Document.Meta.Should().HaveCount(4);
97+
98+
store.Document.Meta.Should().ContainKey("category").WhoseValue.With(value =>
99+
{
100+
JsonElement element = value.Should().BeOfType<JsonElement>().Subject;
101+
element.GetString().Should().Be("bug");
102+
});
103+
104+
store.Document.Meta.Should().ContainKey("priority").WhoseValue.With(value =>
105+
{
106+
JsonElement element = value.Should().BeOfType<JsonElement>().Subject;
107+
element.GetInt32().Should().Be(1);
108+
});
109+
110+
store.Document.Meta.Should().ContainKey("components").WhoseValue.With(value =>
111+
{
112+
string innerJson = value.Should().BeOfType<JsonElement>().Subject.ToString();
113+
114+
innerJson.Should().BeJson("""
115+
[
116+
"login",
117+
"single-sign-on"
118+
]
119+
""");
120+
});
121+
122+
store.Document.Meta.Should().ContainKey("relatedTo").WhoseValue.With(value =>
123+
{
124+
string innerJson = value.Should().BeOfType<JsonElement>().Subject.ToString();
125+
126+
innerJson.Should().BeJson("""
127+
[
128+
{
129+
"id": 123,
130+
"link": "https://www.ticket-system.com/bugs/123"
131+
},
132+
{
133+
"id": 789,
134+
"link": "https://www.ticket-system.com/bugs/789"
135+
}
136+
]
137+
""");
138+
});
139+
}
140+
141+
// TODO: Add more tests, creating a mixture of:
142+
// - Different endpoints: post resource, patch resource, post relationship, patch relationship, delete relationship, atomic:operations
143+
// - Meta at different depths in the request body
144+
// For example, assert on store.Document.Data.SingleValue.Meta
145+
// See IHasMeta usage at https://github.com/json-api-dotnet/JsonApiDotNetCore/tree/openapi/src/JsonApiDotNetCore.OpenApi.Swashbuckle/JsonApiObjects for where meta can occur
146+
// - Varying data structures: primitive types such as string/int/bool, arrays, dictionaries, and nested combinations of them
147+
148+
private sealed class CapturingDocumentAdapter : IDocumentAdapter
149+
{
150+
private readonly IDocumentAdapter _innerAdapter;
151+
private readonly RequestDocumentStore _requestDocumentStore;
152+
153+
public CapturingDocumentAdapter(IDocumentAdapter innerAdapter, RequestDocumentStore requestDocumentStore)
154+
{
155+
ArgumentNullException.ThrowIfNull(innerAdapter);
156+
ArgumentNullException.ThrowIfNull(requestDocumentStore);
157+
158+
_innerAdapter = innerAdapter;
159+
_requestDocumentStore = requestDocumentStore;
160+
}
161+
162+
public object? Convert(Document document)
163+
{
164+
_requestDocumentStore.Document = document;
165+
return _innerAdapter.Convert(document);
166+
}
167+
}
168+
169+
private sealed class RequestDocumentStore
170+
{
171+
public Document? Document { get; set; }
172+
}
173+
}

0 commit comments

Comments
 (0)