diff --git a/src/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs b/src/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs index 3cfca33693..49a1af57d7 100644 --- a/src/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs +++ b/src/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs @@ -183,7 +183,8 @@ private static void SetupRequest(JsonApiRequest request, ResourceContext primary } } - request.IsCollection = request.PrimaryId == null || request.Relationship is HasManyAttribute; + var isGetAll = request.PrimaryId == null && request.IsReadOnly; + request.IsCollection = isGetAll || request.Relationship is HasManyAttribute; } private static string GetPrimaryRequestId(RouteValueDictionary routeValues) diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ReadWrite/Creating/CreateResourceTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ReadWrite/Creating/CreateResourceTests.cs index 568f389851..07e452d6ae 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ReadWrite/Creating/CreateResourceTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ReadWrite/Creating/CreateResourceTests.cs @@ -56,6 +56,9 @@ public async Task Sets_location_header_for_created_resource() var newWorkItemId = responseDocument.SingleData.Id; httpResponse.Headers.Location.Should().Be("/workItems/" + newWorkItemId); + responseDocument.Links.Self.Should().Be("http://localhost/workItems"); + responseDocument.Links.First.Should().BeNull(); + responseDocument.SingleData.Should().NotBeNull(); responseDocument.SingleData.Links.Self.Should().Be("http://localhost" + httpResponse.Headers.Location); } diff --git a/test/UnitTests/Middleware/JsonApiRequestTests.cs b/test/UnitTests/Middleware/JsonApiRequestTests.cs new file mode 100644 index 0000000000..f3b2a62508 --- /dev/null +++ b/test/UnitTests/Middleware/JsonApiRequestTests.cs @@ -0,0 +1,105 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using FluentAssertions; +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Middleware; +using JsonApiDotNetCoreExample.Models; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.Extensions.Logging.Abstractions; +using Moq; +using Xunit; + +namespace UnitTests.Middleware +{ + public sealed class JsonApiRequestTests + { + [Theory] + [InlineData("GET", "/articles", true, EndpointKind.Primary, true)] + [InlineData("GET", "/articles/1", false, EndpointKind.Primary, true)] + [InlineData("GET", "/articles/1/author", false, EndpointKind.Secondary, true)] + [InlineData("GET", "/articles/1/revisions", true, EndpointKind.Secondary, true)] + [InlineData("GET", "/articles/1/relationships/author", false, EndpointKind.Relationship, true)] + [InlineData("GET", "/articles/1/relationships/revisions", true, EndpointKind.Relationship, true)] + [InlineData("POST", "/articles", false, EndpointKind.Primary, false)] + [InlineData("POST", "/articles/1/relationships/revisions", true, EndpointKind.Relationship, false)] + [InlineData("PATCH", "/articles/1", false, EndpointKind.Primary, false)] + [InlineData("PATCH", "/articles/1/relationships/author", false, EndpointKind.Relationship, false)] + [InlineData("PATCH", "/articles/1/relationships/revisions", true, EndpointKind.Relationship, false)] + [InlineData("DELETE", "/articles/1", false, EndpointKind.Primary, false)] + [InlineData("DELETE", "/articles/1/relationships/revisions", true, EndpointKind.Relationship, false)] + public async Task Sets_request_properties_correctly(string requestMethod, string requestPath, bool expectIsCollection, EndpointKind expectKind, bool expectIsReadOnly) + { + // Arrange + var options = new JsonApiOptions + { + UseRelativeLinks = true + }; + + var graphBuilder = new ResourceGraphBuilder(options, NullLoggerFactory.Instance); + graphBuilder.Add
(); + graphBuilder.Add(); + graphBuilder.Add(); + + var resourceGraph = graphBuilder.Build(); + + var controllerResourceMappingMock = new Mock(); + + controllerResourceMappingMock + .Setup(x => x.GetAssociatedResource(It.IsAny())) + .Returns(typeof(Article)); + + var httpContext = new DefaultHttpContext(); + SetupRoutes(httpContext, requestMethod, requestPath); + + var request = new JsonApiRequest(); + + var middleware = new JsonApiMiddleware(_ => Task.CompletedTask); + + // Act + await middleware.Invoke(httpContext, controllerResourceMappingMock.Object, options, request, resourceGraph); + + // Assert + request.IsCollection.Should().Be(expectIsCollection); + request.Kind.Should().Be(expectKind); + request.IsReadOnly.Should().Be(expectIsReadOnly); + request.BasePath.Should().BeEmpty(); + request.PrimaryResource.Should().NotBeNull(); + request.PrimaryResource.PublicName.Should().Be("articles"); + } + + private static void SetupRoutes(HttpContext httpContext, string requestMethod, string requestPath) + { + httpContext.Request.Method = requestMethod; + + var feature = new RouteValuesFeature + { + RouteValues = + { + ["controller"] = "theController", + ["action"] = "theAction" + } + }; + + var pathSegments = requestPath.Split("/", StringSplitOptions.RemoveEmptyEntries); + if (pathSegments.Length > 1) + { + feature.RouteValues["id"] = pathSegments[1]; + + if (pathSegments.Length >= 3) + { + feature.RouteValues["relationshipName"] = pathSegments.Last(); + } + } + + if (pathSegments.Contains("relationships")) + { + feature.RouteValues["action"] = "Relationship"; + } + + httpContext.Features.Set(feature); + httpContext.SetEndpoint(new Endpoint(null, new EndpointMetadataCollection(), null)); + } + } +}