Skip to content

Commit 8a9b85c

Browse files
committed
Improve test coverage for client ID generation modes
1 parent 28e8917 commit 8a9b85c

File tree

7 files changed

+345
-44
lines changed

7 files changed

+345
-44
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using JetBrains.Annotations;
2+
using JsonApiDotNetCore.Configuration;
3+
using JsonApiDotNetCore.Middleware;
4+
5+
namespace JsonApiDotNetCoreTests.IntegrationTests.AtomicOperations.Creating;
6+
7+
[UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]
8+
public sealed class AssignIdToTextLanguageDefinition(IResourceGraph resourceGraph, ResourceDefinitionHitCounter hitCounter, OperationsDbContext dbContext)
9+
: ImplicitlyChangingTextLanguageDefinition(resourceGraph, hitCounter, dbContext)
10+
{
11+
public override Task OnWritingAsync(TextLanguage resource, WriteOperationKind writeOperation, CancellationToken cancellationToken)
12+
{
13+
if (writeOperation == WriteOperationKind.CreateResource && resource.Id == Guid.Empty)
14+
{
15+
resource.Id = Guid.NewGuid();
16+
}
17+
18+
return Task.CompletedTask;
19+
}
20+
}

test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceWithClientGeneratedIdTests.cs

Lines changed: 163 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -25,20 +25,22 @@ public AtomicCreateResourceWithClientGeneratedIdTests(IntegrationTestContext<Tes
2525

2626
testContext.ConfigureServices(services =>
2727
{
28-
services.AddResourceDefinition<ImplicitlyChangingTextLanguageDefinition>();
28+
services.AddResourceDefinition<AssignIdToTextLanguageDefinition>();
2929

3030
services.AddSingleton<ResourceDefinitionHitCounter>();
3131
services.AddSingleton<ISystemClock, FrozenSystemClock>();
3232
});
33-
34-
var options = (JsonApiOptions)testContext.Factory.Services.GetRequiredService<IJsonApiOptions>();
35-
options.ClientIdGeneration = ClientIdGenerationMode.Required;
3633
}
3734

38-
[Fact]
39-
public async Task Can_create_resource_with_client_generated_guid_ID_having_side_effects()
35+
[Theory]
36+
[InlineData(ClientIdGenerationMode.Allowed)]
37+
[InlineData(ClientIdGenerationMode.Required)]
38+
public async Task Can_create_resource_with_client_generated_guid_ID_having_side_effects(ClientIdGenerationMode mode)
4039
{
4140
// Arrange
41+
var options = (JsonApiOptions)_testContext.Factory.Services.GetRequiredService<IJsonApiOptions>();
42+
options.ClientIdGeneration = mode;
43+
4244
TextLanguage newLanguage = _fakers.TextLanguage.Generate();
4345
newLanguage.Id = Guid.NewGuid();
4446

@@ -90,10 +92,15 @@ await _testContext.RunOnDatabaseAsync(async dbContext =>
9092
});
9193
}
9294

93-
[Fact]
94-
public async Task Can_create_resource_with_client_generated_string_ID_having_no_side_effects()
95+
[Theory]
96+
[InlineData(ClientIdGenerationMode.Allowed)]
97+
[InlineData(ClientIdGenerationMode.Required)]
98+
public async Task Can_create_resource_with_client_generated_guid_ID_having_no_side_effects(ClientIdGenerationMode mode)
9599
{
96100
// Arrange
101+
var options = (JsonApiOptions)_testContext.Factory.Services.GetRequiredService<IJsonApiOptions>();
102+
options.ClientIdGeneration = mode;
103+
97104
MusicTrack newTrack = _fakers.MusicTrack.Generate();
98105
newTrack.Id = Guid.NewGuid();
99106

@@ -138,10 +145,72 @@ await _testContext.RunOnDatabaseAsync(async dbContext =>
138145
});
139146
}
140147

141-
[Fact]
142-
public async Task Cannot_create_resource_for_missing_client_generated_ID()
148+
[Theory]
149+
[InlineData(ClientIdGenerationMode.Allowed)]
150+
public async Task Can_create_resource_for_missing_client_generated_ID_having_side_effects(ClientIdGenerationMode mode)
143151
{
144152
// Arrange
153+
var options = (JsonApiOptions)_testContext.Factory.Services.GetRequiredService<IJsonApiOptions>();
154+
options.ClientIdGeneration = mode;
155+
156+
string? newIsoCode = _fakers.TextLanguage.Generate().IsoCode;
157+
158+
var requestBody = new
159+
{
160+
atomic__operations = new[]
161+
{
162+
new
163+
{
164+
op = "add",
165+
data = new
166+
{
167+
type = "textLanguages",
168+
attributes = new
169+
{
170+
isoCode = newIsoCode
171+
}
172+
}
173+
}
174+
}
175+
};
176+
177+
const string route = "/operations";
178+
179+
// Act
180+
(HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecutePostAtomicAsync<Document>(route, requestBody);
181+
182+
// Assert
183+
httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK);
184+
185+
string isoCode = $"{newIsoCode}{ImplicitlyChangingTextLanguageDefinition.Suffix}";
186+
187+
responseDocument.Results.ShouldHaveCount(1);
188+
189+
responseDocument.Results[0].Data.SingleValue.ShouldNotBeNull().With(resource =>
190+
{
191+
resource.Type.Should().Be("textLanguages");
192+
resource.Attributes.ShouldContainKey("isoCode").With(value => value.Should().Be(isoCode));
193+
resource.Relationships.ShouldNotBeEmpty();
194+
});
195+
196+
Guid newLanguageId = Guid.Parse(responseDocument.Results[0].Data.SingleValue!.Id.ShouldNotBeNull());
197+
198+
await _testContext.RunOnDatabaseAsync(async dbContext =>
199+
{
200+
TextLanguage languageInDatabase = await dbContext.TextLanguages.FirstWithIdAsync(newLanguageId);
201+
202+
languageInDatabase.IsoCode.Should().Be(isoCode);
203+
});
204+
}
205+
206+
[Theory]
207+
[InlineData(ClientIdGenerationMode.Required)]
208+
public async Task Cannot_create_resource_for_missing_client_generated_ID(ClientIdGenerationMode mode)
209+
{
210+
// Arrange
211+
var options = (JsonApiOptions)_testContext.Factory.Services.GetRequiredService<IJsonApiOptions>();
212+
options.ClientIdGeneration = mode;
213+
145214
string? newIsoCode = _fakers.TextLanguage.Generate().IsoCode;
146215

147216
var requestBody = new
@@ -182,10 +251,15 @@ public async Task Cannot_create_resource_for_missing_client_generated_ID()
182251
error.Meta.ShouldContainKey("requestBody").With(value => value.ShouldNotBeNull().ToString().ShouldNotBeEmpty());
183252
}
184253

185-
[Fact]
186-
public async Task Cannot_create_resource_for_existing_client_generated_ID()
254+
[Theory]
255+
[InlineData(ClientIdGenerationMode.Allowed)]
256+
[InlineData(ClientIdGenerationMode.Required)]
257+
public async Task Cannot_create_resource_for_existing_client_generated_ID(ClientIdGenerationMode mode)
187258
{
188259
// Arrange
260+
var options = (JsonApiOptions)_testContext.Factory.Services.GetRequiredService<IJsonApiOptions>();
261+
options.ClientIdGeneration = mode;
262+
189263
TextLanguage existingLanguage = _fakers.TextLanguage.Generate();
190264
existingLanguage.Id = Guid.NewGuid();
191265

@@ -237,10 +311,15 @@ await _testContext.RunOnDatabaseAsync(async dbContext =>
237311
error.Meta.Should().NotContainKey("requestBody");
238312
}
239313

240-
[Fact]
241-
public async Task Cannot_create_resource_for_incompatible_ID()
314+
[Theory]
315+
[InlineData(ClientIdGenerationMode.Allowed)]
316+
[InlineData(ClientIdGenerationMode.Required)]
317+
public async Task Cannot_create_resource_for_incompatible_ID(ClientIdGenerationMode mode)
242318
{
243319
// Arrange
320+
var options = (JsonApiOptions)_testContext.Factory.Services.GetRequiredService<IJsonApiOptions>();
321+
options.ClientIdGeneration = mode;
322+
244323
string guid = Unknown.StringId.Guid;
245324

246325
var requestBody = new
@@ -281,10 +360,71 @@ public async Task Cannot_create_resource_for_incompatible_ID()
281360
error.Meta.ShouldContainKey("requestBody").With(value => value.ShouldNotBeNull().ToString().ShouldNotBeEmpty());
282361
}
283362

284-
[Fact]
285-
public async Task Cannot_create_resource_with_local_ID()
363+
[Theory]
364+
[InlineData(ClientIdGenerationMode.Allowed)]
365+
public async Task Can_create_resource_with_local_ID(ClientIdGenerationMode mode)
366+
{
367+
// Arrange
368+
var options = (JsonApiOptions)_testContext.Factory.Services.GetRequiredService<IJsonApiOptions>();
369+
options.ClientIdGeneration = mode;
370+
371+
string newTitle = _fakers.MusicTrack.Generate().Title;
372+
373+
var requestBody = new
374+
{
375+
atomic__operations = new[]
376+
{
377+
new
378+
{
379+
op = "add",
380+
data = new
381+
{
382+
type = "musicTracks",
383+
lid = "new-server-id",
384+
attributes = new
385+
{
386+
title = newTitle
387+
}
388+
}
389+
}
390+
}
391+
};
392+
393+
const string route = "/operations";
394+
395+
// Act
396+
(HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecutePostAtomicAsync<Document>(route, requestBody);
397+
398+
// Assert
399+
httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK);
400+
401+
responseDocument.Results.ShouldHaveCount(1);
402+
403+
responseDocument.Results[0].Data.SingleValue.ShouldNotBeNull().With(resource =>
404+
{
405+
resource.Type.Should().Be("musicTracks");
406+
resource.Attributes.ShouldContainKey("title").With(value => value.Should().Be(newTitle));
407+
resource.Relationships.Should().BeNull();
408+
});
409+
410+
Guid newTrackId = Guid.Parse(responseDocument.Results[0].Data.SingleValue!.Id.ShouldNotBeNull());
411+
412+
await _testContext.RunOnDatabaseAsync(async dbContext =>
413+
{
414+
MusicTrack languageInDatabase = await dbContext.MusicTracks.FirstWithIdAsync(newTrackId);
415+
416+
languageInDatabase.Title.Should().Be(newTitle);
417+
});
418+
}
419+
420+
[Theory]
421+
[InlineData(ClientIdGenerationMode.Required)]
422+
public async Task Cannot_create_resource_with_local_ID(ClientIdGenerationMode mode)
286423
{
287424
// Arrange
425+
var options = (JsonApiOptions)_testContext.Factory.Services.GetRequiredService<IJsonApiOptions>();
426+
options.ClientIdGeneration = mode;
427+
288428
var requestBody = new
289429
{
290430
atomic__operations = new[]
@@ -320,10 +460,15 @@ public async Task Cannot_create_resource_with_local_ID()
320460
error.Meta.ShouldContainKey("requestBody").With(value => value.ShouldNotBeNull().ToString().ShouldNotBeEmpty());
321461
}
322462

323-
[Fact]
324-
public async Task Cannot_create_resource_for_ID_and_local_ID()
463+
[Theory]
464+
[InlineData(ClientIdGenerationMode.Allowed)]
465+
[InlineData(ClientIdGenerationMode.Required)]
466+
public async Task Cannot_create_resource_for_ID_and_local_ID(ClientIdGenerationMode mode)
325467
{
326468
// Arrange
469+
var options = (JsonApiOptions)_testContext.Factory.Services.GetRequiredService<IJsonApiOptions>();
470+
options.ClientIdGeneration = mode;
471+
327472
var requestBody = new
328473
{
329474
atomic__operations = new[]
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using JetBrains.Annotations;
2+
using JsonApiDotNetCore.Configuration;
3+
using JsonApiDotNetCore.Middleware;
4+
using JsonApiDotNetCore.Resources;
5+
6+
namespace JsonApiDotNetCoreTests.IntegrationTests.ReadWrite.Creating;
7+
8+
[UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]
9+
internal sealed class AssignIdToRgbColorDefinition(IResourceGraph resourceGraph) : JsonApiResourceDefinition<RgbColor, string?>(resourceGraph)
10+
{
11+
internal const string DefaultId = "0x000000";
12+
internal const string DefaultName = "Black";
13+
14+
public override Task OnWritingAsync(RgbColor resource, WriteOperationKind writeOperation, CancellationToken cancellationToken)
15+
{
16+
if (writeOperation == WriteOperationKind.CreateResource && resource.Id == null)
17+
{
18+
SetDefaultColor(resource);
19+
}
20+
21+
return Task.CompletedTask;
22+
}
23+
24+
private static void SetDefaultColor(RgbColor resource)
25+
{
26+
resource.Id = DefaultId;
27+
resource.DisplayName = DefaultName;
28+
}
29+
}

0 commit comments

Comments
 (0)