Skip to content

Commit e9435cf

Browse files
committed
Assert on obfuscated links, mention sqids
1 parent eca6d76 commit e9435cf

File tree

2 files changed

+112
-18
lines changed

2 files changed

+112
-18
lines changed

test/JsonApiDotNetCoreTests/IntegrationTests/IdObfuscation/HexadecimalCodec.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.IdObfuscation;
88

99
internal sealed class HexadecimalCodec
1010
{
11+
// This implementation is deliberately simple for demonstration purposes.
12+
// Consider using something more robust, such as https://github.com/sqids/sqids-dotnet.
13+
1114
public int Decode(string? value)
1215
{
1316
if (value == null)

test/JsonApiDotNetCoreTests/IntegrationTests/IdObfuscation/IdObfuscationTests.cs

Lines changed: 109 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
using System.Net;
22
using FluentAssertions;
3+
using JsonApiDotNetCore.Configuration;
34
using JsonApiDotNetCore.Serialization.Objects;
45
using Microsoft.EntityFrameworkCore;
6+
using Microsoft.Extensions.DependencyInjection;
57
using TestBuildingBlocks;
68
using Xunit;
79

@@ -11,13 +13,45 @@ public sealed class IdObfuscationTests : IClassFixture<IntegrationTestContext<Te
1113
{
1214
private readonly IntegrationTestContext<TestableStartup<ObfuscationDbContext>, ObfuscationDbContext> _testContext;
1315
private readonly ObfuscationFakers _fakers = new();
16+
private readonly HexadecimalCodec _codec = new();
1417

1518
public IdObfuscationTests(IntegrationTestContext<TestableStartup<ObfuscationDbContext>, ObfuscationDbContext> testContext)
1619
{
1720
_testContext = testContext;
1821

1922
testContext.UseController<BankAccountsController>();
2023
testContext.UseController<DebitCardsController>();
24+
25+
var options = (JsonApiOptions)testContext.Factory.Services.GetRequiredService<IJsonApiOptions>();
26+
options.UseRelativeLinks = true;
27+
}
28+
29+
[Fact]
30+
public void Encodes_resource_ID()
31+
{
32+
// Arrange
33+
BankAccount account = _fakers.BankAccount.GenerateOne();
34+
account.Id = 123;
35+
36+
// Act
37+
string? stringId = _codec.Encode(account.Id);
38+
39+
// Assert
40+
stringId.Should().Be(account.StringId);
41+
}
42+
43+
[Fact]
44+
public void Decodes_resource_ID()
45+
{
46+
// Arrange
47+
BankAccount account = _fakers.BankAccount.GenerateOne();
48+
account.Id = 123;
49+
50+
// Act
51+
int id = _codec.Decode(account.StringId);
52+
53+
// Assert
54+
id.Should().Be(account.Id);
2155
}
2256

2357
[Fact]
@@ -41,8 +75,16 @@ await _testContext.RunOnDatabaseAsync(async dbContext =>
4175
// Assert
4276
httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK);
4377

44-
responseDocument.Data.ManyValue.Should().HaveCount(1);
45-
responseDocument.Data.ManyValue[0].Id.Should().Be(accounts[1].StringId);
78+
responseDocument.Links.Should().NotBeNull();
79+
responseDocument.Links.Self.Should().Be(route);
80+
responseDocument.Links.First.Should().Be($"/bankAccounts?filter=equals(id,%27{accounts[1].StringId}%27)");
81+
82+
responseDocument.Data.ManyValue.Should().ContainSingle().Which.With(resource =>
83+
{
84+
resource.Id.Should().Be(accounts[1].StringId);
85+
resource.Links.Should().NotBeNull();
86+
resource.Links.Self.Should().Be($"/bankAccounts/{accounts[1].StringId}");
87+
});
4688
}
4789

4890
[Fact]
@@ -81,17 +123,15 @@ await _testContext.RunOnDatabaseAsync(async dbContext =>
81123
await dbContext.SaveChangesAsync();
82124
});
83125

84-
var codec = new HexadecimalCodec();
85-
string route = $"/bankAccounts?filter=any(id,'{accounts[1].StringId}','{codec.Encode(Unknown.TypedId.Int32)}')";
126+
string route = $"/bankAccounts?filter=any(id,'{accounts[1].StringId}','{_codec.Encode(Unknown.TypedId.Int32)}')";
86127

87128
// Act
88129
(HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync<Document>(route);
89130

90131
// Assert
91132
httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK);
92133

93-
responseDocument.Data.ManyValue.Should().HaveCount(1);
94-
responseDocument.Data.ManyValue[0].Id.Should().Be(accounts[1].StringId);
134+
responseDocument.Data.ManyValue.Should().ContainSingle().Which.Id.Should().Be(accounts[1].StringId);
95135
}
96136

97137
[Fact]
@@ -135,8 +175,12 @@ await _testContext.RunOnDatabaseAsync(async dbContext =>
135175
// Assert
136176
httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK);
137177

178+
responseDocument.Links.Should().NotBeNull();
179+
responseDocument.Links.Self.Should().Be(route);
180+
138181
responseDocument.Data.SingleValue.Should().NotBeNull();
139182
responseDocument.Data.SingleValue.Id.Should().Be(card.StringId);
183+
responseDocument.Data.SingleValue.Links.RefShould().NotBeNull().And.Subject.Self.Should().Be(route);
140184
}
141185

142186
[Fact]
@@ -160,9 +204,25 @@ await _testContext.RunOnDatabaseAsync(async dbContext =>
160204
// Assert
161205
httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK);
162206

207+
responseDocument.Links.Should().NotBeNull();
208+
responseDocument.Links.Self.Should().Be(route);
209+
responseDocument.Links.First.Should().Be(route);
210+
163211
responseDocument.Data.ManyValue.Should().HaveCount(2);
164-
responseDocument.Data.ManyValue[0].Id.Should().Be(account.Cards[0].StringId);
165-
responseDocument.Data.ManyValue[1].Id.Should().Be(account.Cards[1].StringId);
212+
213+
responseDocument.Data.ManyValue[0].With(resource =>
214+
{
215+
resource.Id.Should().Be(account.Cards[0].StringId);
216+
resource.Links.Should().NotBeNull();
217+
resource.Links.Self.Should().Be($"/debitCards/{account.Cards[0].StringId}");
218+
});
219+
220+
responseDocument.Data.ManyValue[1].With(resource =>
221+
{
222+
resource.Id.Should().Be(account.Cards[1].StringId);
223+
resource.Links.Should().NotBeNull();
224+
resource.Links.Self.Should().Be($"/debitCards/{account.Cards[1].StringId}");
225+
});
166226
}
167227

168228
[Fact]
@@ -186,13 +246,31 @@ await _testContext.RunOnDatabaseAsync(async dbContext =>
186246
// Assert
187247
httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK);
188248

249+
responseDocument.Links.Should().NotBeNull();
250+
responseDocument.Links.Self.Should().Be(route);
251+
189252
responseDocument.Data.SingleValue.Should().NotBeNull();
190253
responseDocument.Data.SingleValue.Id.Should().Be(account.StringId);
191254

192-
responseDocument.Included.Should().HaveCount(1);
193-
responseDocument.Included[0].Id.Should().Be(account.Cards[0].StringId);
194-
responseDocument.Included[0].Attributes.Should().HaveCount(1);
195-
responseDocument.Included[0].Relationships.Should().BeNull();
255+
responseDocument.Data.SingleValue.Relationships.Should().ContainKey("cards").WhoseValue.With(value =>
256+
{
257+
value.Should().NotBeNull();
258+
value.Data.ManyValue.Should().ContainSingle().Which.Id.Should().Be(account.Cards[0].StringId);
259+
260+
value.Links.Should().NotBeNull();
261+
value.Links.Self.Should().Be($"/bankAccounts/{account.StringId}/relationships/cards");
262+
value.Links.Related.Should().Be($"/bankAccounts/{account.StringId}/cards");
263+
});
264+
265+
responseDocument.Included.Should().ContainSingle().Which.With(resource =>
266+
{
267+
resource.Id.Should().Be(account.Cards[0].StringId);
268+
resource.Attributes.Should().HaveCount(1);
269+
resource.Relationships.Should().BeNull();
270+
271+
resource.Links.Should().NotBeNull();
272+
resource.Links.Self.Should().Be($"/debitCards/{account.Cards[0].StringId}");
273+
});
196274
}
197275

198276
[Fact]
@@ -216,8 +294,11 @@ await _testContext.RunOnDatabaseAsync(async dbContext =>
216294
// Assert
217295
httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK);
218296

219-
responseDocument.Data.ManyValue.Should().HaveCount(1);
220-
responseDocument.Data.ManyValue[0].Id.Should().Be(account.Cards[0].StringId);
297+
responseDocument.Links.Should().NotBeNull();
298+
responseDocument.Links.Self.Should().Be(route);
299+
responseDocument.Links.First.Should().Be(route);
300+
301+
responseDocument.Data.ManyValue.Should().ContainSingle().Which.Id.Should().Be(account.Cards[0].StringId);
221302
}
222303

223304
[Fact]
@@ -266,11 +347,22 @@ await _testContext.RunOnDatabaseAsync(async dbContext =>
266347
httpResponse.ShouldHaveStatusCode(HttpStatusCode.Created);
267348

268349
responseDocument.Data.SingleValue.Should().NotBeNull();
350+
351+
string newCardStringId = responseDocument.Data.SingleValue.Id.RefShould().NotBeNull().And.Subject;
352+
353+
responseDocument.Data.SingleValue.Links.RefShould().NotBeNull().And.Subject.Self.Should().Be($"/debitCards/{newCardStringId}");
269354
responseDocument.Data.SingleValue.Attributes.Should().ContainKey("ownerName").WhoseValue.Should().Be(newCard.OwnerName);
270355
responseDocument.Data.SingleValue.Attributes.Should().ContainKey("pinCode").WhoseValue.Should().Be(newCard.PinCode);
271356

272-
var codec = new HexadecimalCodec();
273-
int newCardId = codec.Decode(responseDocument.Data.SingleValue.Id);
357+
responseDocument.Data.SingleValue.Relationships.Should().ContainKey("account").WhoseValue.With(value =>
358+
{
359+
value.Should().NotBeNull();
360+
value.Links.Should().NotBeNull();
361+
value.Links.Self.Should().Be($"/debitCards/{newCardStringId}/relationships/account");
362+
value.Links.Related.Should().Be($"/debitCards/{newCardStringId}/account");
363+
});
364+
365+
int newCardId = _codec.Decode(responseDocument.Data.SingleValue.Id);
274366

275367
await _testContext.RunOnDatabaseAsync(async dbContext =>
276368
{
@@ -476,8 +568,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext =>
476568
public async Task Cannot_delete_unknown_resource()
477569
{
478570
// Arrange
479-
var codec = new HexadecimalCodec();
480-
string? stringId = codec.Encode(Unknown.TypedId.Int32);
571+
string? stringId = _codec.Encode(Unknown.TypedId.Int32);
481572

482573
string route = $"/bankAccounts/{stringId}";
483574

0 commit comments

Comments
 (0)