From b0f9373a81c8c967dd4a3b6371e38cb3fdda3a41 Mon Sep 17 00:00:00 2001 From: Harro van der Kroft Date: Wed, 27 Nov 2019 16:28:03 +0100 Subject: [PATCH 1/7] feat: set up basiscs for ID fetching --- .../Middleware/CurrentRequestMiddleware.cs | 11 +++++++++++ .../RequestServices/Contracts/ICurrentRequest.cs | 2 ++ test/UnitTests/RequestServices/CurrentRequestTests.cs | 10 ++++++++++ 3 files changed, 23 insertions(+) create mode 100644 test/UnitTests/RequestServices/CurrentRequestTests.cs diff --git a/src/JsonApiDotNetCore/Middleware/CurrentRequestMiddleware.cs b/src/JsonApiDotNetCore/Middleware/CurrentRequestMiddleware.cs index 0bf055cfdb..4689df73c3 100644 --- a/src/JsonApiDotNetCore/Middleware/CurrentRequestMiddleware.cs +++ b/src/JsonApiDotNetCore/Middleware/CurrentRequestMiddleware.cs @@ -45,6 +45,8 @@ public async Task Invoke(HttpContext httpContext, _currentRequest.SetRequestResource(GetCurrentEntity()); _currentRequest.IsRelationshipPath = PathIsRelationship(); _currentRequest.BasePath = GetBasePath(_currentRequest.GetRequestResource().ResourceName); + _currentRequest.BaseId = GetBaseId(httpContext.Request.Path); + _currentRequest.RelationshipId = GetRelationshipId(httpContext.Request.Path); } if (IsValid()) @@ -53,6 +55,15 @@ public async Task Invoke(HttpContext httpContext, } } + private string GetBaseId(PathString pathString) + { + + } + private string GetRelationshipId(PathString pathString) + { + + } + private string GetBasePath(string entityName) { var r = _httpContext.Request; diff --git a/src/JsonApiDotNetCore/RequestServices/Contracts/ICurrentRequest.cs b/src/JsonApiDotNetCore/RequestServices/Contracts/ICurrentRequest.cs index 0260db8c91..15eddf495e 100644 --- a/src/JsonApiDotNetCore/RequestServices/Contracts/ICurrentRequest.cs +++ b/src/JsonApiDotNetCore/RequestServices/Contracts/ICurrentRequest.cs @@ -29,6 +29,8 @@ public interface ICurrentRequest /// is the relationship attribute associated with the targeted relationship /// RelationshipAttribute RequestRelationship { get; set; } + string BaseId { get; set; } + string RelationshipId { get; set; } /// /// Sets the current context entity for this entire request diff --git a/test/UnitTests/RequestServices/CurrentRequestTests.cs b/test/UnitTests/RequestServices/CurrentRequestTests.cs new file mode 100644 index 0000000000..2cd38ac9c0 --- /dev/null +++ b/test/UnitTests/RequestServices/CurrentRequestTests.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace UnitTests.RequestServices +{ + public class CurrentRequestTests + { + } +} From a606857d03566792bdc3ed278754f86ba3af05bd Mon Sep 17 00:00:00 2001 From: Harro van der Kroft Date: Wed, 27 Nov 2019 17:26:02 +0100 Subject: [PATCH 2/7] feat: add fixes for baseId test --- .../Middleware/CurrentRequestMiddleware.cs | 20 +++++--- .../RequestServices/CurrentRequest.cs | 2 + .../CurrentRequestMiddlewareTests.cs | 51 +++++++++++++++++++ 3 files changed, 67 insertions(+), 6 deletions(-) create mode 100644 test/UnitTests/Middleware/CurrentRequestMiddlewareTests.cs diff --git a/src/JsonApiDotNetCore/Middleware/CurrentRequestMiddleware.cs b/src/JsonApiDotNetCore/Middleware/CurrentRequestMiddleware.cs index 4689df73c3..7ab7df3780 100644 --- a/src/JsonApiDotNetCore/Middleware/CurrentRequestMiddleware.cs +++ b/src/JsonApiDotNetCore/Middleware/CurrentRequestMiddleware.cs @@ -6,6 +6,7 @@ using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Managers.Contracts; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Primitives; @@ -21,6 +22,7 @@ public class CurrentRequestMiddleware private ICurrentRequest _currentRequest; private IResourceGraph _resourceGraph; private IJsonApiOptions _options; + private RouteValueDictionary _routeValues; private IControllerResourceMapping _controllerResourceMapping; public CurrentRequestMiddleware(RequestDelegate next) @@ -39,6 +41,7 @@ public async Task Invoke(HttpContext httpContext, _controllerResourceMapping = controllerResourceMapping; _resourceGraph = resourceGraph; _options = options; + _routeValues = httpContext.GetRouteData().Values; var requestResource = GetCurrentEntity(); if (requestResource != null) { @@ -57,11 +60,11 @@ public async Task Invoke(HttpContext httpContext, private string GetBaseId(PathString pathString) { - + return "hello"; } private string GetRelationshipId(PathString pathString) { - + return "hello"; } private string GetBasePath(string entityName) @@ -107,7 +110,7 @@ internal static string GetNamespaceFromPath(string path, string entityName) protected bool PathIsRelationship() { - var actionName = (string)_httpContext.GetRouteData().Values["action"]; + var actionName = (string)_routeValues["action"]; return actionName.ToLower().Contains("relationships"); } @@ -176,16 +179,21 @@ private void FlushResponse(HttpContext context, int statusCode) /// private ResourceContext GetCurrentEntity() { - var controllerName = (string)_httpContext.GetRouteValue("controller"); + var controllerName = (string)_routeValues["controller"]; if (controllerName == null) + { return null; + } var resourceType = _controllerResourceMapping.GetAssociatedResource(controllerName); var requestResource = _resourceGraph.GetResourceContext(resourceType); if (requestResource == null) + { return requestResource; - var rd = _httpContext.GetRouteData().Values; - if (rd.TryGetValue("relationshipName", out object relationshipName)) + } + if (_routeValues.TryGetValue("relationshipName", out object relationshipName)) + { _currentRequest.RequestRelationship = requestResource.Relationships.Single(r => r.PublicRelationshipName == (string)relationshipName); + } return requestResource; } } diff --git a/src/JsonApiDotNetCore/RequestServices/CurrentRequest.cs b/src/JsonApiDotNetCore/RequestServices/CurrentRequest.cs index abb1b5863a..053cf39cfb 100644 --- a/src/JsonApiDotNetCore/RequestServices/CurrentRequest.cs +++ b/src/JsonApiDotNetCore/RequestServices/CurrentRequest.cs @@ -10,6 +10,8 @@ class CurrentRequest : ICurrentRequest public string BasePath { get; set; } public bool IsRelationshipPath { get; set; } public RelationshipAttribute RequestRelationship { get; set; } + public string BaseId { get; set; } + public string RelationshipId { get; set; } /// /// The main resource of the request. diff --git a/test/UnitTests/Middleware/CurrentRequestMiddlewareTests.cs b/test/UnitTests/Middleware/CurrentRequestMiddlewareTests.cs new file mode 100644 index 0000000000..e7785038a3 --- /dev/null +++ b/test/UnitTests/Middleware/CurrentRequestMiddlewareTests.cs @@ -0,0 +1,51 @@ +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Internal.Contracts; +using JsonApiDotNetCore.Managers; +using JsonApiDotNetCore.Middleware; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Routing; +using Moq; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace UnitTests.Middleware +{ + public class CurrentRequestMiddlewareTests + { + [Fact] + public async Task ParseUrl_UrlHasBaseIdSet_ShouldSetCurrentRequestWithSaidId() + { + // Arrange + var middleware = new CurrentRequestMiddleware((context) => + { + return Task.Run(() => Console.WriteLine("finished")); + }); + var mockMapping = new Mock(); + var mockOptions = new Mock(); + var mockGraph = new Mock(); + var currentRequest = new CurrentRequest(); + var context = new DefaultHttpContext(); + var id = 1231; + context.Request.Path = new PathString($"/api/v1/users/{id}"); + context.Response.Body = new MemoryStream(); + var feature = new RouteValuesFeature(); + feature.RouteValues["controller"] = "fake!"; + feature.RouteValues["action"] = "noRel"; + context.Features.Set(feature); + var resourceContext = new ResourceContext(); + + mockGraph.Setup(d => d.GetResourceContext(It.IsAny())).Returns(resourceContext); + // Act + await middleware.Invoke(context, mockMapping.Object, mockOptions.Object, currentRequest, mockGraph.Object); + + // Assert + Assert.Equal(id.ToString(), currentRequest.BaseId); + } + } +} From 8e4749964a1b935a2db8f9794f5fd2eafc7101f2 Mon Sep 17 00:00:00 2001 From: Harro van der Kroft Date: Wed, 27 Nov 2019 17:30:13 +0100 Subject: [PATCH 3/7] chore: thats it for today --- src/JsonApiDotNetCore/Middleware/CurrentRequestMiddleware.cs | 3 ++- test/UnitTests/Middleware/CurrentRequestMiddlewareTests.cs | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/JsonApiDotNetCore/Middleware/CurrentRequestMiddleware.cs b/src/JsonApiDotNetCore/Middleware/CurrentRequestMiddleware.cs index 7ab7df3780..67ec76428a 100644 --- a/src/JsonApiDotNetCore/Middleware/CurrentRequestMiddleware.cs +++ b/src/JsonApiDotNetCore/Middleware/CurrentRequestMiddleware.cs @@ -60,7 +60,8 @@ public async Task Invoke(HttpContext httpContext, private string GetBaseId(PathString pathString) { - return "hello"; + + return ""; } private string GetRelationshipId(PathString pathString) { diff --git a/test/UnitTests/Middleware/CurrentRequestMiddlewareTests.cs b/test/UnitTests/Middleware/CurrentRequestMiddlewareTests.cs index e7785038a3..ff3e73be93 100644 --- a/test/UnitTests/Middleware/CurrentRequestMiddlewareTests.cs +++ b/test/UnitTests/Middleware/CurrentRequestMiddlewareTests.cs @@ -41,6 +41,7 @@ public async Task ParseUrl_UrlHasBaseIdSet_ShouldSetCurrentRequestWithSaidId() var resourceContext = new ResourceContext(); mockGraph.Setup(d => d.GetResourceContext(It.IsAny())).Returns(resourceContext); + // Act await middleware.Invoke(context, mockMapping.Object, mockOptions.Object, currentRequest, mockGraph.Object); From 47c10275a106632c6405ce709725c234ceb27af7 Mon Sep 17 00:00:00 2001 From: Harro van der Kroft Date: Thu, 28 Nov 2019 10:04:56 +0100 Subject: [PATCH 4/7] tests: add test for non-set base id --- .../Middleware/CurrentRequestMiddleware.cs | 30 +++++++++++----- .../CurrentRequestMiddlewareTests.cs | 35 ++++++++++++++++++- 2 files changed, 56 insertions(+), 9 deletions(-) diff --git a/src/JsonApiDotNetCore/Middleware/CurrentRequestMiddleware.cs b/src/JsonApiDotNetCore/Middleware/CurrentRequestMiddleware.cs index 67ec76428a..3be72f14e9 100644 --- a/src/JsonApiDotNetCore/Middleware/CurrentRequestMiddleware.cs +++ b/src/JsonApiDotNetCore/Middleware/CurrentRequestMiddleware.cs @@ -45,11 +45,11 @@ public async Task Invoke(HttpContext httpContext, var requestResource = GetCurrentEntity(); if (requestResource != null) { - _currentRequest.SetRequestResource(GetCurrentEntity()); + _currentRequest.SetRequestResource(requestResource); _currentRequest.IsRelationshipPath = PathIsRelationship(); - _currentRequest.BasePath = GetBasePath(_currentRequest.GetRequestResource().ResourceName); - _currentRequest.BaseId = GetBaseId(httpContext.Request.Path); - _currentRequest.RelationshipId = GetRelationshipId(httpContext.Request.Path); + _currentRequest.BasePath = GetBasePath(requestResource.ResourceName); + _currentRequest.BaseId = GetBaseId(); + _currentRequest.RelationshipId = GetRelationshipId(); } if (IsValid()) @@ -58,12 +58,25 @@ public async Task Invoke(HttpContext httpContext, } } - private string GetBaseId(PathString pathString) + private string GetBaseId() { + var path = _httpContext.Request.Path.Value; + var resourceName = _currentRequest.GetRequestResource().ResourceName; + var ns = GetNamespaceFromPath(path, resourceName); + var nonNameSpaced = path.Replace(ns, ""); + var individualComponents = nonNameSpaced.Split('/'); + + if(individualComponents[1] != resourceName) { + throw new JsonApiException(500, $"Something went wrong in the middleware, we can't find the resourcename:{resourceName} "); + } + if(individualComponents.Length < 3) + { + return null; + } - return ""; + return individualComponents[2]; } - private string GetRelationshipId(PathString pathString) + private string GetRelationshipId() { return "hello"; } @@ -96,7 +109,6 @@ internal static string GetNamespaceFromPath(string path, string entityName) // check to see if it's the last position in the string // or if the next character is a / var lastCharacterPosition = nextPosition + entityNameSpan.Length; - if (lastCharacterPosition == pathSpan.Length || pathSpan.Length >= lastCharacterPosition + 2 && pathSpan[lastCharacterPosition].Equals(delimiter)) { return pathSpan.Slice(0, i).ToString(); @@ -139,7 +151,9 @@ private bool IsValidAcceptHeader(HttpContext context) foreach (var acceptHeader in acceptHeaders) { if (ContainsMediaTypeParameters(acceptHeader) == false) + { continue; + } FlushResponse(context, 406); return false; diff --git a/test/UnitTests/Middleware/CurrentRequestMiddlewareTests.cs b/test/UnitTests/Middleware/CurrentRequestMiddlewareTests.cs index ff3e73be93..0fb50194a0 100644 --- a/test/UnitTests/Middleware/CurrentRequestMiddlewareTests.cs +++ b/test/UnitTests/Middleware/CurrentRequestMiddlewareTests.cs @@ -39,7 +39,7 @@ public async Task ParseUrl_UrlHasBaseIdSet_ShouldSetCurrentRequestWithSaidId() feature.RouteValues["action"] = "noRel"; context.Features.Set(feature); var resourceContext = new ResourceContext(); - + resourceContext.ResourceName = "users"; mockGraph.Setup(d => d.GetResourceContext(It.IsAny())).Returns(resourceContext); // Act @@ -48,5 +48,38 @@ public async Task ParseUrl_UrlHasBaseIdSet_ShouldSetCurrentRequestWithSaidId() // Assert Assert.Equal(id.ToString(), currentRequest.BaseId); } + + [Fact] + public async Task ParseUrl_UrlHasNoBaseIdSet_ShouldHaveBaseIdSetToNull() + { + // Arrange + var middleware = new CurrentRequestMiddleware((context) => + { + return Task.Run(() => Console.WriteLine("finished")); + }); + var mockMapping = new Mock(); + var mockOptions = new Mock(); + var mockGraph = new Mock(); + var currentRequest = new CurrentRequest(); + var context = new DefaultHttpContext(); + var id = 1231; + context.Request.Path = new PathString($"/api/v1/users"); + context.Response.Body = new MemoryStream(); + var feature = new RouteValuesFeature(); + feature.RouteValues["controller"] = "fake!"; + feature.RouteValues["action"] = "noRel"; + context.Features.Set(feature); + var resourceContext = new ResourceContext + { + ResourceName = "users" + }; + mockGraph.Setup(d => d.GetResourceContext(It.IsAny())).Returns(resourceContext); + + // Act + await middleware.Invoke(context, mockMapping.Object, mockOptions.Object, currentRequest, mockGraph.Object); + + // Assert + Assert.Null(currentRequest.BaseId); + } } } From 42101cce18be425a645745409f747f899e50bc70 Mon Sep 17 00:00:00 2001 From: Harro van der Kroft Date: Thu, 28 Nov 2019 11:13:36 +0100 Subject: [PATCH 5/7] chore: refactor setup --- .../Middleware/CurrentRequestMiddleware.cs | 84 ++++++----- .../CurrentRequestMiddlewareTests.cs | 132 ++++++++++++++++++ 2 files changed, 180 insertions(+), 36 deletions(-) diff --git a/src/JsonApiDotNetCore/Middleware/CurrentRequestMiddleware.cs b/src/JsonApiDotNetCore/Middleware/CurrentRequestMiddleware.cs index 3be72f14e9..343f7be3aa 100644 --- a/src/JsonApiDotNetCore/Middleware/CurrentRequestMiddleware.cs +++ b/src/JsonApiDotNetCore/Middleware/CurrentRequestMiddleware.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel; using System.Linq; using System.Threading.Tasks; using JsonApiDotNetCore.Configuration; @@ -61,21 +62,56 @@ public async Task Invoke(HttpContext httpContext, private string GetBaseId() { var path = _httpContext.Request.Path.Value; - var resourceName = _currentRequest.GetRequestResource().ResourceName; - var ns = GetNamespaceFromPath(path, resourceName); + var resource = _currentRequest.GetRequestResource(); + var resourceName = resource.ResourceName; + var ns = $"/{GetNameSpace()}"; var nonNameSpaced = path.Replace(ns, ""); - var individualComponents = nonNameSpaced.Split('/'); + nonNameSpaced = nonNameSpaced.Trim('/'); - if(individualComponents[1] != resourceName) { - throw new JsonApiException(500, $"Something went wrong in the middleware, we can't find the resourcename:{resourceName} "); - } - if(individualComponents.Length < 3) + var individualComponents = nonNameSpaced.Split('/'); + if (individualComponents.Length < 2) { return null; } + var toReturn = individualComponents[1]; + + CheckIdType(toReturn, resource.IdentityType); + + + return individualComponents[2]; } + private void CheckIdType(string value, Type idType) + { + + try + { + var converter = TypeDescriptor.GetConverter(idType); + if (converter != null) + { + if (!converter.IsValid(value)) + { + throw new JsonApiException(500, $"We could not convert the id '{value}'"); + } + else + { + if (idType == typeof(int)) + { + if ((int)converter.ConvertFromString(value) < 0) + { + throw new JsonApiException(500, "The base ID is an integer, and it is negative."); + } + } + } + } + } + catch (NotSupportedException) + { + + } + + } private string GetRelationshipId() { return "hello"; @@ -86,39 +122,15 @@ private string GetBasePath(string entityName) var r = _httpContext.Request; if (_options.RelativeLinks) { - return GetNamespaceFromPath(r.Path, entityName); + return GetNameSpace(); } - return $"{r.Scheme}://{r.Host}{GetNamespaceFromPath(r.Path, entityName)}"; + var ns = GetNameSpace(); + return $"{r.Scheme}://{r.Host}/{ns}"; } - internal static string GetNamespaceFromPath(string path, string entityName) + private string GetNameSpace() { - var entityNameSpan = entityName.AsSpan(); - var pathSpan = path.AsSpan(); - const char delimiter = '/'; - for (var i = 0; i < pathSpan.Length; i++) - { - if (pathSpan[i].Equals(delimiter)) - { - var nextPosition = i + 1; - if (pathSpan.Length > i + entityNameSpan.Length) - { - var possiblePathSegment = pathSpan.Slice(nextPosition, entityNameSpan.Length); - if (entityNameSpan.SequenceEqual(possiblePathSegment)) - { - // check to see if it's the last position in the string - // or if the next character is a / - var lastCharacterPosition = nextPosition + entityNameSpan.Length; - if (lastCharacterPosition == pathSpan.Length || pathSpan.Length >= lastCharacterPosition + 2 && pathSpan[lastCharacterPosition].Equals(delimiter)) - { - return pathSpan.Slice(0, i).ToString(); - } - } - } - } - } - - return string.Empty; + return _options.Namespace; } protected bool PathIsRelationship() diff --git a/test/UnitTests/Middleware/CurrentRequestMiddlewareTests.cs b/test/UnitTests/Middleware/CurrentRequestMiddlewareTests.cs index 0fb50194a0..e0d4024b6e 100644 --- a/test/UnitTests/Middleware/CurrentRequestMiddlewareTests.cs +++ b/test/UnitTests/Middleware/CurrentRequestMiddlewareTests.cs @@ -81,5 +81,137 @@ public async Task ParseUrl_UrlHasNoBaseIdSet_ShouldHaveBaseIdSetToNull() // Assert Assert.Null(currentRequest.BaseId); } + [Fact] + public async Task ParseUrl_UrlHasRelationshipIdSet_ShouldHaveBaseIdAndRelationshipIdSet() + { + // Arrange + var middleware = new CurrentRequestMiddleware((context) => + { + return Task.Run(() => Console.WriteLine("finished")); + }); + var mockMapping = new Mock(); + var mockOptions = new Mock(); + var mockGraph = new Mock(); + var currentRequest = new CurrentRequest(); + var context = new DefaultHttpContext(); + var id = 1231; + var relId = 7654; + context.Request.Path = new PathString($"/api/v1/users/"); + context.Response.Body = new MemoryStream(); + var feature = new RouteValuesFeature(); + feature.RouteValues["controller"] = "fake!"; + feature.RouteValues["action"] = "noRel"; + context.Features.Set(feature); + var resourceContext = new ResourceContext + { + ResourceName = "users" + }; + mockGraph.Setup(d => d.GetResourceContext(It.IsAny())).Returns(resourceContext); + + // Act + await middleware.Invoke(context, mockMapping.Object, mockOptions.Object, currentRequest, mockGraph.Object); + + // Assert + Assert.Equal(id.ToString(), currentRequest.BaseId); + Assert.Equal(relId.ToString(), currentRequest.RelationshipId); + } + + [Theory] + [InlineData("12315K", typeof(int), true)] + [InlineData("12315K", typeof(int), false)] + [InlineData("-5", typeof(int), true)] + [InlineData("-5", typeof(int), false)] + [InlineData("5", typeof(Guid), true)] + [InlineData("5", typeof(Guid), false)] + public async Task ParseUrl_UrlHasIncorrectBaseIdSet_ShouldThrowException(string baseId, Type idType, bool addSlash) + { + // Arrange + var url = addSlash ? $"/users/{baseId}/" : $"/users/{baseId}"; + var configuration = Setup(url, idType: idType); + + // Act + var task = PrepareTask(configuration); + + // Assert + var exception = await Assert.ThrowsAsync(async () => + { + await task; + }); + Assert.Equal(500, exception.GetStatusCode()); + Assert.Contains(baseId, exception.Message); + } + + class InvokeConfiguration + { + public CurrentRequestMiddleware MiddleWare; + public HttpContext HttpContext; + public Mock ControllerResourcemapping; + public Mock Options; + public CurrentRequest CurrentRequest; + public Mock ResourceGraph; + } + private Task PrepareTask(InvokeConfiguration holder) + { + var controllerResourceMapping = holder.ControllerResourcemapping.Object; + var context = holder.HttpContext; + var options = holder.Options.Object; + var currentRequest = holder.CurrentRequest; + var resourceGraph = holder.ResourceGraph.Object; + return holder.MiddleWare.Invoke(context, controllerResourceMapping, options, currentRequest, resourceGraph); + } + private InvokeConfiguration Setup(string path, Type idType = null) + { + idType ??= typeof(int); + var middleware = new CurrentRequestMiddleware((context) => + { + return Task.Run(() => Console.WriteLine("finished")); + }); + var forcedNamespace = "api/v1"; + var mockMapping = new Mock(); + Mock mockOptions = CreateMockOptions(forcedNamespace); + var mockGraph = CreateMockResourceGraph(); + var currentRequest = new CurrentRequest(); + var context = CreateHttpContext(path); + return new InvokeConfiguration + { + MiddleWare = middleware, + ControllerResourcemapping = mockMapping, + Options = mockOptions, + CurrentRequest = currentRequest, + HttpContext = context, + ResourceGraph = mockGraph + }; + } + + private static Mock CreateMockOptions(string forcedNamespace) + { + var mockOptions = new Mock(); + mockOptions.Setup(o => o.Namespace).Returns(forcedNamespace); + return mockOptions; + } + + private static DefaultHttpContext CreateHttpContext(string path) + { + var context = new DefaultHttpContext(); + context.Request.Path = new PathString(path); + context.Response.Body = new MemoryStream(); + var feature = new RouteValuesFeature(); + feature.RouteValues["controller"] = "fake!"; + feature.RouteValues["action"] = "noRel"; + context.Features.Set(feature); + return context; + } + + private Mock CreateMockResourceGraph() + { + var mockGraph = new Mock(); + var resourceContext = new ResourceContext + { + ResourceName = "users" + }; + mockGraph.Setup(d => d.GetResourceContext(It.IsAny())).Returns(resourceContext); + return mockGraph; + } + } } From 9654a2b2ad314209502a93ccc92af16f7a2de5d9 Mon Sep 17 00:00:00 2001 From: Harro van der Kroft Date: Thu, 28 Nov 2019 12:26:13 +0100 Subject: [PATCH 6/7] chore: fix for double /api/v1 --- .../Middleware/CurrentRequestMiddleware.cs | 88 +++++++--- .../Extensibility/CustomControllerTests.cs | 2 +- .../CurrentRequestMiddlewareTests.cs | 162 +++++++++--------- 3 files changed, 144 insertions(+), 108 deletions(-) diff --git a/src/JsonApiDotNetCore/Middleware/CurrentRequestMiddleware.cs b/src/JsonApiDotNetCore/Middleware/CurrentRequestMiddleware.cs index 343f7be3aa..b47e87d636 100644 --- a/src/JsonApiDotNetCore/Middleware/CurrentRequestMiddleware.cs +++ b/src/JsonApiDotNetCore/Middleware/CurrentRequestMiddleware.cs @@ -61,30 +61,54 @@ public async Task Invoke(HttpContext httpContext, private string GetBaseId() { - var path = _httpContext.Request.Path.Value; var resource = _currentRequest.GetRequestResource(); - var resourceName = resource.ResourceName; - var ns = $"/{GetNameSpace()}"; - var nonNameSpaced = path.Replace(ns, ""); - nonNameSpaced = nonNameSpaced.Trim('/'); - - var individualComponents = nonNameSpaced.Split('/'); + var individualComponents = SplitCurrentPath(); if (individualComponents.Length < 2) { return null; } - var toReturn = individualComponents[1]; - - CheckIdType(toReturn, resource.IdentityType); - + var indexOfResource = individualComponents.ToList().FindIndex(c => c == resource.ResourceName); + var baseId = individualComponents.ElementAtOrDefault(indexOfResource + 1); + if (baseId == null) + { + return null; + } + CheckIdType(baseId, resource.IdentityType); + return baseId; + } + private string GetRelationshipId() + { + var resource = _currentRequest.GetRequestResource(); + if (!_currentRequest.IsRelationshipPath) + { + return null; + } + var components = SplitCurrentPath(); + var toReturn = components.ElementAtOrDefault(4); + if (toReturn == null) + { + return null; + } + var relType = _currentRequest.RequestRelationship.RightType; + var relResource = _resourceGraph.GetResourceContext(relType); + var relIdentityType = relResource.IdentityType; + CheckIdType(toReturn, relIdentityType); + return toReturn; + } + private string[] SplitCurrentPath() + { + var path = _httpContext.Request.Path.Value; + var ns = $"/{GetNameSpace()}"; + var nonNameSpaced = path.Replace(ns, ""); + nonNameSpaced = nonNameSpaced.Trim('/'); + var individualComponents = nonNameSpaced.Split('/'); + return individualComponents; + } - return individualComponents[2]; - } private void CheckIdType(string value, Type idType) { - try { var converter = TypeDescriptor.GetConverter(idType); @@ -112,24 +136,44 @@ private void CheckIdType(string value, Type idType) } } - private string GetRelationshipId() - { - return "hello"; - } - private string GetBasePath(string entityName) + private string GetBasePath(string resourceName = null) { var r = _httpContext.Request; if (_options.RelativeLinks) { - return GetNameSpace(); + return GetNameSpace(resourceName); } + var ns = GetNameSpace(resourceName); + var customRoute = GetCustomRoute(r.Path.Value, resourceName); + var toReturn = $"{r.Scheme}://{r.Host}/{ns}"; + if(customRoute != null) + { + toReturn += $"/{customRoute}"; + } + return toReturn; + } + + private object GetCustomRoute(string path, string resourceName) + { var ns = GetNameSpace(); - return $"{r.Scheme}://{r.Host}/{ns}"; + var trimmedComponents = path.Trim('/').Split('/').ToList(); + var resourceNameIndex = trimmedComponents.FindIndex(c => c == resourceName); + var newComponents = trimmedComponents.Take(resourceNameIndex ).ToArray(); + var customRoute = string.Join('/', newComponents); + if(customRoute == ns) + { + return null; + } + else + { + return customRoute; + } } - private string GetNameSpace() + private string GetNameSpace(string resourceName = null) { + return _options.Namespace; } diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/CustomControllerTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/CustomControllerTests.cs index 4797732c6b..a03a213534 100644 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/CustomControllerTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/CustomControllerTests.cs @@ -128,7 +128,7 @@ public async Task CustomRouteControllers_Creates_Proper_Relationship_Links() var deserializedBody = JsonConvert.DeserializeObject(body); var result = deserializedBody["data"]["relationships"]["owner"]["links"]["related"].ToString(); - Assert.EndsWith($"{route}/owner", deserializedBody["data"]["relationships"]["owner"]["links"]["related"].ToString()); + Assert.EndsWith($"{route}/owner", result); } } } diff --git a/test/UnitTests/Middleware/CurrentRequestMiddlewareTests.cs b/test/UnitTests/Middleware/CurrentRequestMiddlewareTests.cs index e0d4024b6e..01b26a69b1 100644 --- a/test/UnitTests/Middleware/CurrentRequestMiddlewareTests.cs +++ b/test/UnitTests/Middleware/CurrentRequestMiddlewareTests.cs @@ -1,8 +1,11 @@ using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Internal; using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Managers; using JsonApiDotNetCore.Middleware; +using JsonApiDotNetCore.Models; +using JsonApiDotNetCoreExample.Models; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Routing; @@ -10,6 +13,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Text; using System.Threading.Tasks; using Xunit; @@ -19,118 +23,81 @@ namespace UnitTests.Middleware public class CurrentRequestMiddlewareTests { [Fact] - public async Task ParseUrl_UrlHasBaseIdSet_ShouldSetCurrentRequestWithSaidId() + public async Task ParseUrlBase_UrlHasBaseIdSet_ShouldSetCurrentRequestWithSaidId() { // Arrange - var middleware = new CurrentRequestMiddleware((context) => - { - return Task.Run(() => Console.WriteLine("finished")); - }); - var mockMapping = new Mock(); - var mockOptions = new Mock(); - var mockGraph = new Mock(); - var currentRequest = new CurrentRequest(); - var context = new DefaultHttpContext(); - var id = 1231; - context.Request.Path = new PathString($"/api/v1/users/{id}"); - context.Response.Body = new MemoryStream(); - var feature = new RouteValuesFeature(); - feature.RouteValues["controller"] = "fake!"; - feature.RouteValues["action"] = "noRel"; - context.Features.Set(feature); - var resourceContext = new ResourceContext(); - resourceContext.ResourceName = "users"; - mockGraph.Setup(d => d.GetResourceContext(It.IsAny())).Returns(resourceContext); + var id = "123"; + var configuration = GetConfiguration($"/users/{id}"); + var currentRequest = configuration.CurrentRequest; // Act - await middleware.Invoke(context, mockMapping.Object, mockOptions.Object, currentRequest, mockGraph.Object); + await RunMiddlewareTask(configuration); // Assert - Assert.Equal(id.ToString(), currentRequest.BaseId); + Assert.Equal(id, currentRequest.BaseId); } [Fact] - public async Task ParseUrl_UrlHasNoBaseIdSet_ShouldHaveBaseIdSetToNull() + public async Task ParseUrlBase_UrlHasNoBaseIdSet_ShouldHaveBaseIdSetToNull() { // Arrange - var middleware = new CurrentRequestMiddleware((context) => - { - return Task.Run(() => Console.WriteLine("finished")); - }); - var mockMapping = new Mock(); - var mockOptions = new Mock(); - var mockGraph = new Mock(); - var currentRequest = new CurrentRequest(); - var context = new DefaultHttpContext(); - var id = 1231; - context.Request.Path = new PathString($"/api/v1/users"); - context.Response.Body = new MemoryStream(); - var feature = new RouteValuesFeature(); - feature.RouteValues["controller"] = "fake!"; - feature.RouteValues["action"] = "noRel"; - context.Features.Set(feature); - var resourceContext = new ResourceContext - { - ResourceName = "users" - }; - mockGraph.Setup(d => d.GetResourceContext(It.IsAny())).Returns(resourceContext); + var configuration = GetConfiguration("/users"); + var currentRequest = configuration.CurrentRequest; // Act - await middleware.Invoke(context, mockMapping.Object, mockOptions.Object, currentRequest, mockGraph.Object); + await RunMiddlewareTask(configuration); // Assert Assert.Null(currentRequest.BaseId); } [Fact] - public async Task ParseUrl_UrlHasRelationshipIdSet_ShouldHaveBaseIdAndRelationshipIdSet() + public async Task ParseUrlRel_UrlHasRelationshipIdSet_ShouldHaveBaseIdAndRelationshipIdSet() { // Arrange - var middleware = new CurrentRequestMiddleware((context) => - { - return Task.Run(() => Console.WriteLine("finished")); - }); - var mockMapping = new Mock(); - var mockOptions = new Mock(); - var mockGraph = new Mock(); - var currentRequest = new CurrentRequest(); - var context = new DefaultHttpContext(); - var id = 1231; - var relId = 7654; - context.Request.Path = new PathString($"/api/v1/users/"); - context.Response.Body = new MemoryStream(); - var feature = new RouteValuesFeature(); - feature.RouteValues["controller"] = "fake!"; - feature.RouteValues["action"] = "noRel"; - context.Features.Set(feature); - var resourceContext = new ResourceContext - { - ResourceName = "users" - }; - mockGraph.Setup(d => d.GetResourceContext(It.IsAny())).Returns(resourceContext); + var baseId = "5"; + var relId = "23"; + var configuration = GetConfiguration($"/users/{baseId}/relationships/books/{relId}", relType: typeof(TodoItem), relIdType: typeof(int)); + var currentRequest = configuration.CurrentRequest; // Act - await middleware.Invoke(context, mockMapping.Object, mockOptions.Object, currentRequest, mockGraph.Object); + await RunMiddlewareTask(configuration); // Assert - Assert.Equal(id.ToString(), currentRequest.BaseId); - Assert.Equal(relId.ToString(), currentRequest.RelationshipId); + Assert.Equal(baseId, currentRequest.BaseId); + Assert.Equal(relId, currentRequest.RelationshipId); + } + + [Fact] + public async Task ParseUrlBase_UrlHasNegativeBaseIdAndTypeIsInt_ShouldThrowJAException() + { + // Arrange + var configuration = GetConfiguration("/users/-5/"); + + // Act + var task = RunMiddlewareTask(configuration); + + // Assert + var exception = await Assert.ThrowsAsync(async () => + { + await task; + }); + Assert.Equal(500, exception.GetStatusCode()); + Assert.Contains("negative", exception.Message); } [Theory] [InlineData("12315K", typeof(int), true)] [InlineData("12315K", typeof(int), false)] - [InlineData("-5", typeof(int), true)] - [InlineData("-5", typeof(int), false)] [InlineData("5", typeof(Guid), true)] [InlineData("5", typeof(Guid), false)] - public async Task ParseUrl_UrlHasIncorrectBaseIdSet_ShouldThrowException(string baseId, Type idType, bool addSlash) + public async Task ParseUrlBase_UrlHasIncorrectBaseIdSet_ShouldThrowException(string baseId, Type idType, bool addSlash) { // Arrange var url = addSlash ? $"/users/{baseId}/" : $"/users/{baseId}"; - var configuration = Setup(url, idType: idType); + var configuration = GetConfiguration(url, idType: idType); // Act - var task = PrepareTask(configuration); + var task = RunMiddlewareTask(configuration); // Assert var exception = await Assert.ThrowsAsync(async () => @@ -150,7 +117,7 @@ class InvokeConfiguration public CurrentRequest CurrentRequest; public Mock ResourceGraph; } - private Task PrepareTask(InvokeConfiguration holder) + private Task RunMiddlewareTask(InvokeConfiguration holder) { var controllerResourceMapping = holder.ControllerResourcemapping.Object; var context = holder.HttpContext; @@ -159,8 +126,16 @@ private Task PrepareTask(InvokeConfiguration holder) var resourceGraph = holder.ResourceGraph.Object; return holder.MiddleWare.Invoke(context, controllerResourceMapping, options, currentRequest, resourceGraph); } - private InvokeConfiguration Setup(string path, Type idType = null) + private InvokeConfiguration GetConfiguration(string path, string resourceName = "users", Type idType = null, Type relType = null, Type relIdType = null) { + if((relType != null) != (relIdType != null)) + { + throw new ArgumentException("Define both reltype and relidType or dont."); + } + if (path.First() != '/') + { + throw new ArgumentException("Path should start with a '/'"); + } idType ??= typeof(int); var middleware = new CurrentRequestMiddleware((context) => { @@ -169,9 +144,16 @@ private InvokeConfiguration Setup(string path, Type idType = null) var forcedNamespace = "api/v1"; var mockMapping = new Mock(); Mock mockOptions = CreateMockOptions(forcedNamespace); - var mockGraph = CreateMockResourceGraph(); + var mockGraph = CreateMockResourceGraph(idType, resourceName, relIdType : relIdType); var currentRequest = new CurrentRequest(); - var context = CreateHttpContext(path); + if (relType != null && relIdType != null) + { + currentRequest.RequestRelationship = new HasManyAttribute + { + RightType = relType + }; + } + var context = CreateHttpContext(path, isRelationship: relType != null); return new InvokeConfiguration { MiddleWare = middleware, @@ -190,26 +172,36 @@ private static Mock CreateMockOptions(string forcedNamespace) return mockOptions; } - private static DefaultHttpContext CreateHttpContext(string path) + private static DefaultHttpContext CreateHttpContext(string path, bool isRelationship = false) { var context = new DefaultHttpContext(); context.Request.Path = new PathString(path); context.Response.Body = new MemoryStream(); var feature = new RouteValuesFeature(); feature.RouteValues["controller"] = "fake!"; - feature.RouteValues["action"] = "noRel"; + feature.RouteValues["action"] = isRelationship ? "relationships" : "noRel"; context.Features.Set(feature); return context; } - private Mock CreateMockResourceGraph() + private Mock CreateMockResourceGraph(Type idType, string resourceName, Type relIdType = null) { var mockGraph = new Mock(); var resourceContext = new ResourceContext { - ResourceName = "users" + ResourceName = resourceName, + IdentityType = idType }; - mockGraph.Setup(d => d.GetResourceContext(It.IsAny())).Returns(resourceContext); + var seq = mockGraph.SetupSequence(d => d.GetResourceContext(It.IsAny())).Returns(resourceContext); + if (relIdType != null) + { + var relResourceContext = new ResourceContext + { + ResourceName = "todoItems", + IdentityType = relIdType + }; + seq.Returns(relResourceContext); + } return mockGraph; } From 59e7d963c442152b0f8ac81a46b74621f6755fa5 Mon Sep 17 00:00:00 2001 From: Harro van der Kroft Date: Thu, 28 Nov 2019 12:27:55 +0100 Subject: [PATCH 7/7] chore: remove current request tests --- test/UnitTests/RequestServices/CurrentRequestTests.cs | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 test/UnitTests/RequestServices/CurrentRequestTests.cs diff --git a/test/UnitTests/RequestServices/CurrentRequestTests.cs b/test/UnitTests/RequestServices/CurrentRequestTests.cs deleted file mode 100644 index 2cd38ac9c0..0000000000 --- a/test/UnitTests/RequestServices/CurrentRequestTests.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace UnitTests.RequestServices -{ - public class CurrentRequestTests - { - } -}