Skip to content

Commit e623e84

Browse files
authored
Merge pull request #1141 from json-api-dotnet/self-links-fix
Bugfix: hide Self link in included resources for missing controller
2 parents bc5fbe0 + ab67c86 commit e623e84

File tree

9 files changed

+84
-2
lines changed

9 files changed

+84
-2
lines changed

src/JsonApiDotNetCore/Serialization/Response/LinkBuilder.cs

+7
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,13 @@ private bool ShouldIncludeResourceLink(LinkTypes linkType, ResourceType resource
312312

313313
protected virtual string? RenderLinkForAction(string? controllerName, string actionName, IDictionary<string, object?> routeValues)
314314
{
315+
if (controllerName == null)
316+
{
317+
// When passing null to LinkGenerator, it uses the controller for the current endpoint. This is incorrect for
318+
// included resources of a different resource type: it should hide its links when there's no controller for them.
319+
return null;
320+
}
321+
315322
return _options.UseRelativeLinks
316323
? _linkGenerator.GetPathByAction(HttpContext, actionName, controllerName, routeValues)
317324
: _linkGenerator.GetUriByAction(HttpContext, actionName, controllerName, routeValues);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
using System.Net;
2+
using FluentAssertions;
3+
using JsonApiDotNetCore.Serialization.Objects;
4+
using TestBuildingBlocks;
5+
using Xunit;
6+
7+
namespace JsonApiDotNetCoreTests.IntegrationTests.Links;
8+
9+
public sealed class LinkInclusionIncludeTests : IClassFixture<IntegrationTestContext<TestableStartup<LinksDbContext>, LinksDbContext>>
10+
{
11+
private readonly IntegrationTestContext<TestableStartup<LinksDbContext>, LinksDbContext> _testContext;
12+
private readonly LinksFakers _fakers = new();
13+
14+
public LinkInclusionIncludeTests(IntegrationTestContext<TestableStartup<LinksDbContext>, LinksDbContext> testContext)
15+
{
16+
_testContext = testContext;
17+
18+
testContext.UseController<PhotoLocationsController>();
19+
}
20+
21+
[Fact]
22+
public async Task Hides_links_for_unregistered_controllers()
23+
{
24+
// Arrange
25+
PhotoLocation location = _fakers.PhotoLocation.Generate();
26+
location.Photo = _fakers.Photo.Generate();
27+
location.Album = _fakers.PhotoAlbum.Generate();
28+
29+
await _testContext.RunOnDatabaseAsync(async dbContext =>
30+
{
31+
dbContext.PhotoLocations.Add(location);
32+
await dbContext.SaveChangesAsync();
33+
});
34+
35+
string route = $"/photoLocations/{location.StringId}?include=photo,album";
36+
37+
// Act
38+
(HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync<Document>(route);
39+
40+
// Assert
41+
httpResponse.Should().HaveStatusCode(HttpStatusCode.OK);
42+
43+
responseDocument.Data.SingleValue.ShouldNotBeNull();
44+
45+
responseDocument.Data.SingleValue.Relationships.ShouldContainKey("photo").With(value =>
46+
{
47+
value.ShouldNotBeNull();
48+
value.Links.ShouldNotBeNull();
49+
});
50+
51+
responseDocument.Included.ShouldHaveCount(2);
52+
53+
responseDocument.Included.Should().ContainSingle(resource => resource.Type == "photos").Subject.With(resource =>
54+
{
55+
resource.Links.Should().BeNull();
56+
resource.Relationships.Should().BeNull();
57+
});
58+
59+
responseDocument.Included.Should().ContainSingle(resource => resource.Type == "photoAlbums").Subject.With(resource =>
60+
{
61+
resource.Links.Should().BeNull();
62+
resource.Relationships.Should().BeNull();
63+
});
64+
}
65+
}

test/JsonApiDotNetCoreTests/IntegrationTests/Links/LinkInclusionTests.cs

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ public LinkInclusionTests(IntegrationTestContext<TestableStartup<LinksDbContext>
1717

1818
testContext.UseController<PhotosController>();
1919
testContext.UseController<PhotoLocationsController>();
20+
testContext.UseController<PhotoAlbumsController>();
2021
}
2122

2223
[Fact]

test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Creating/CreateResourceWithToOneRelationshipTests.cs

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ public CreateResourceWithToOneRelationshipTests(IntegrationTestContext<TestableS
2222
testContext.UseController<WorkItemGroupsController>();
2323
testContext.UseController<WorkItemsController>();
2424
testContext.UseController<RgbColorsController>();
25+
testContext.UseController<UserAccountsController>();
2526

2627
var options = (JsonApiOptions)testContext.Factory.Services.GetRequiredService<IJsonApiOptions>();
2728
options.AllowClientGeneratedIds = true;

test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Fetching/FetchResourceTests.cs

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ public FetchResourceTests(IntegrationTestContext<TestableStartup<ReadWriteDbCont
1717

1818
testContext.UseController<WorkItemsController>();
1919
testContext.UseController<UserAccountsController>();
20+
testContext.UseController<WorkTagsController>();
2021
}
2122

2223
[Fact]

test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Updating/Resources/ReplaceToManyRelationshipTests.cs

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ public ReplaceToManyRelationshipTests(IntegrationTestContext<TestableStartup<Rea
1919
_testContext = testContext;
2020

2121
testContext.UseController<WorkItemsController>();
22+
testContext.UseController<UserAccountsController>();
2223

2324
testContext.ConfigureServicesAfterStartup(services =>
2425
{

test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Updating/Resources/UpdateToOneRelationshipTests.cs

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ public UpdateToOneRelationshipTests(IntegrationTestContext<TestableStartup<ReadW
2020
testContext.UseController<WorkItemsController>();
2121
testContext.UseController<WorkItemGroupsController>();
2222
testContext.UseController<RgbColorsController>();
23+
testContext.UseController<UserAccountsController>();
2324

2425
testContext.ConfigureServicesAfterStartup(services =>
2526
{

test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/WorkTag.cs

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace JsonApiDotNetCoreTests.IntegrationTests.ReadWrite;
66

77
[UsedImplicitly(ImplicitUseTargetFlags.Members)]
8+
[Resource(ControllerNamespace = "JsonApiDotNetCoreTests.IntegrationTests.ReadWrite")]
89
public sealed class WorkTag : Identifiable<int>
910
{
1011
[Attr]

test/JsonApiDotNetCoreTests/UnitTests/Links/LinkInclusionTests.cs

+6-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using FluentAssertions;
2+
using Humanizer;
23
using JsonApiDotNetCore.Configuration;
34
using JsonApiDotNetCore.Middleware;
45
using JsonApiDotNetCore.Queries;
@@ -68,7 +69,10 @@ public void Applies_cascading_settings_for_top_level_links(LinkTypes linksInReso
6869
PrimaryId = "1",
6970
IsCollection = true,
7071
Kind = EndpointKind.Relationship,
71-
Relationship = new HasOneAttribute()
72+
Relationship = new HasOneAttribute
73+
{
74+
LeftType = exampleResourceType
75+
}
7276
};
7377

7478
var paginationContext = new PaginationContext
@@ -386,7 +390,7 @@ public ResourceType GetResourceTypeForController(Type? controllerType)
386390

387391
public string? GetControllerNameForResourceType(ResourceType? resourceType)
388392
{
389-
return null;
393+
return resourceType == null ? null : $"{resourceType.PublicName.Pascalize()}Controller";
390394
}
391395
}
392396

0 commit comments

Comments
 (0)