diff --git a/docs/features/requestaggregation.rst b/docs/features/requestaggregation.rst index b359eb72..c3c7ad13 100644 --- a/docs/features/requestaggregation.rst +++ b/docs/features/requestaggregation.rst @@ -5,12 +5,114 @@ Ocelot allow's you to specify Aggregate ReRoutes that compose multiple normal Re a client that is making multiple requests to a server where it could just be one. This feature allows you to start implementing back end for a front end type architecture with Ocelot. -This feature was requested as part of `Issue 79 `_ . +This feature was requested as part of `Issue 79 `_ and further improvements were made as part of `Issue 298 `_. In order to set this up you must do something like the following in your configuration.json. Here we have specified two normal ReRoutes and each one has a Key property. We then specify an Aggregate that composes the two ReRoutes using their keys in the ReRouteKeys list and says then we have the UpstreamPathTemplate which works like a normal ReRoute. Obviously you cannot have duplicate UpstreamPathTemplates between ReRoutes and Aggregates. You can use all of Ocelot's normal ReRoute options apart from RequestIdKey (explained in gotchas below). +Advanced register your own Aggregators +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Ocelot started with just the basic request aggregation and since then we have added a more advanced method that let's the user take in the responses from the +downstream services and then aggregate them into a response object. + +The configuration.json setup is pretty much the same as the basic aggregation approach apart from you need to add an Aggregator property like below. + +.. code-block:: json + + { + "ReRoutes": [ + { + "DownstreamPathTemplate": "/", + "UpstreamPathTemplate": "/laura", + "UpstreamHttpMethod": [ + "Get" + ], + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 51881 + } + ], + "Key": "Laura" + }, + { + "DownstreamPathTemplate": "/", + "UpstreamPathTemplate": "/tom", + "UpstreamHttpMethod": [ + "Get" + ], + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 51882 + } + ], + "Key": "Tom" + } + ], + "Aggregates": [ + { + "ReRouteKeys": [ + "Tom", + "Laura" + ], + "UpstreamPathTemplate": "/", + "Aggregator": "FakeDefinedAggregator" + } + ] + } + +Here we have added an aggregator called FakeDefinedAggregator. Ocelot is going to look for this aggregator when it tries to aggregate this ReRoute. + +In order to make the aggregator available we must add the FakeDefinedAggregator to the OcelotBuilder like below. + +.. code-block:: csharp + + services + .AddOcelot() + .AddSingletonDefinedAggregator(); + +Now when Ocelot tries to aggregate the ReRoute above it will find the FakeDefinedAggregator in the container and use it to aggregate the ReRoute. +Because the FakeDefinedAggregator is registered in the container you can add any dependencies it needs into the container like below. + +.. code-block:: csharp + + services.AddSingleton(); + + services + .AddOcelot() + .AddSingletonDefinedAggregator(); + +In this example FooAggregator takes a dependency on FooDependency and it will be resolved by the container. + +In addition to this Ocelot lets you add transient aggregators like below. + +.. code-block:: csharp + + services + .AddOcelot() + .AddTransientDefinedAggregator(); + +In order to make an Aggregator you must implement this interface. + +.. code-block:: csharp + + public interface IDefinedAggregator + { + Task Aggregate(List responses); + } + +With this feature you can pretty much do whatever you want because DownstreamResponse contains Content, Headers and Status Code. We can add extra things if needed +just raise an issue on GitHub. Please note if the HttpClient throws an exception when making a request to a ReRoute in the aggregate then you will not get a DownstreamResponse for +it but you would for any that succeed. If it does throw an exception this will be logged. + +Basic expecting JSON from Downstream Services +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + .. code-block:: json { @@ -65,9 +167,6 @@ If the ReRoute /tom returned a body of {"Age": 19} and /laura returned {"Age": 2 {"Tom":{"Age": 19},"Laura":{"Age": 25}} -Gotcha's / Further info -^^^^^^^^^^^^^^^^^^^^^^^ - At the moment the aggregation is very simple. Ocelot just gets the response from your downstream service and sticks it into a json dictionary as above. With the ReRoute key being the key of the dictionary and the value the response body from your downstream service. You can see that the object is just JSON without any pretty spaces etc. @@ -76,20 +175,13 @@ All headers will be lost from the downstream services response. Ocelot will always return content type application/json with an aggregate request. +If you downstream services return a 404 the aggregate will just return nothing for that downstream service. +It will not change the aggregate response into a 404 even if all the downstreams return a 404. + +Gotcha's / Further info +----------------------- + You cannot use ReRoutes with specific RequestIdKeys as this would be crazy complicated to track. Aggregation only supports the GET HTTP Verb. -If you downstream services return a 404 the aggregate will just return nothing for that downstream service. -It will not change the aggregate response into a 404 even if all the downstreams return a 404. - -Future -^^^^^^ - -There are loads of cool ways to enchance this such as.. - -What happens when downstream goes slow..should we timeout? -Can we do something like GraphQL where the user chooses what fields are returned? -Can we handle 404 better etc? -Can we make this not just support a JSON dictionary response? - diff --git a/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs b/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs index 93548f3b..aee3bec3 100644 --- a/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs +++ b/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs @@ -2,12 +2,10 @@ using System.Linq; using System.Net.Http; using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Ocelot.Infrastructure.RequestData; using Ocelot.Logging; using Ocelot.Middleware; using System.IO; -using Ocelot.DownstreamRouteFinder.Middleware; +using Ocelot.Middleware.Multiplexer; namespace Ocelot.Cache.Middleware { @@ -72,38 +70,31 @@ namespace Ocelot.Cache.Middleware Logger.LogDebug($"finished response added to cache for {downstreamUrlKey}"); } - private void SetHttpResponseMessageThisRequest(DownstreamContext context, HttpResponseMessage response) + private void SetHttpResponseMessageThisRequest(DownstreamContext context, DownstreamResponse response) { context.DownstreamResponse = response; } - internal HttpResponseMessage CreateHttpResponseMessage(CachedResponse cached) + internal DownstreamResponse CreateHttpResponseMessage(CachedResponse cached) { if (cached == null) { return null; } - var response = new HttpResponseMessage(cached.StatusCode); - - foreach (var header in cached.Headers) - { - response.Headers.Add(header.Key, header.Value); - } - var content = new MemoryStream(Convert.FromBase64String(cached.Body)); - response.Content = new StreamContent(content); + var streamContent = new StreamContent(content); foreach (var header in cached.ContentHeaders) { - response.Content.Headers.Add(header.Key, header.Value); + streamContent.Headers.Add(header.Key, header.Value); } - return response; + return new DownstreamResponse(streamContent, cached.StatusCode, cached.Headers.ToList()); } - internal async Task CreateCachedResponse(HttpResponseMessage response) + internal async Task CreateCachedResponse(DownstreamResponse response) { if (response == null) { @@ -111,7 +102,7 @@ namespace Ocelot.Cache.Middleware } var statusCode = response.StatusCode; - var headers = response.Headers.ToDictionary(v => v.Key, v => v.Value); + var headers = response.Headers.ToDictionary(v => v.Key, v => v.Values); string body = null; if (response.Content != null) diff --git a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs index 0d059932..31367f26 100644 --- a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs +++ b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs @@ -14,6 +14,7 @@ namespace Ocelot.Configuration.Builder private List _upstreamHttpMethod; private string _upstreamHost; private List _downstreamReRoutes; + private string _aggregator; public ReRouteBuilder() { @@ -56,6 +57,12 @@ namespace Ocelot.Configuration.Builder return this; } + public ReRouteBuilder WithAggregator(string aggregator) + { + _aggregator = aggregator; + return this; + } + public ReRoute Build() { return new ReRoute( @@ -63,7 +70,8 @@ namespace Ocelot.Configuration.Builder new PathTemplate(_upstreamTemplate), _upstreamHttpMethod, _upstreamTemplatePattern, - _upstreamHost + _upstreamHost, + _aggregator ); } } diff --git a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs index 1036f8df..821c0ed7 100644 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs @@ -132,6 +132,7 @@ namespace Ocelot.Configuration.Creator .WithUpstreamTemplatePattern(upstreamTemplatePattern) .WithDownstreamReRoutes(applicableReRoutes) .WithUpstreamHost(aggregateReRoute.UpstreamHost) + .WithAggregator(aggregateReRoute.Aggregator) .Build(); return reRoute; diff --git a/src/Ocelot/Configuration/File/FileAggregateReRoute.cs b/src/Ocelot/Configuration/File/FileAggregateReRoute.cs index ca5013b2..c862094a 100644 --- a/src/Ocelot/Configuration/File/FileAggregateReRoute.cs +++ b/src/Ocelot/Configuration/File/FileAggregateReRoute.cs @@ -8,6 +8,7 @@ namespace Ocelot.Configuration.File public string UpstreamPathTemplate { get;set; } public string UpstreamHost { get; set; } public bool ReRouteIsCaseSensitive { get; set; } + public string Aggregator { get; set; } // Only supports GET..are you crazy!! POST, PUT WOULD BE CRAZY!! :) public List UpstreamHttpMethod diff --git a/src/Ocelot/Configuration/ReRoute.cs b/src/Ocelot/Configuration/ReRoute.cs index 47f26291..c4c3952d 100644 --- a/src/Ocelot/Configuration/ReRoute.cs +++ b/src/Ocelot/Configuration/ReRoute.cs @@ -12,13 +12,15 @@ namespace Ocelot.Configuration PathTemplate upstreamPathTemplate, List upstreamHttpMethod, UpstreamPathTemplate upstreamTemplatePattern, - string upstreamHost) + string upstreamHost, + string aggregator) { UpstreamHost = upstreamHost; DownstreamReRoute = downstreamReRoute; UpstreamPathTemplate = upstreamPathTemplate; UpstreamHttpMethod = upstreamHttpMethod; UpstreamTemplatePattern = upstreamTemplatePattern; + Aggregator = aggregator; } public PathTemplate UpstreamPathTemplate { get; private set; } @@ -26,5 +28,6 @@ namespace Ocelot.Configuration public List UpstreamHttpMethod { get; private set; } public string UpstreamHost { get; private set; } public List DownstreamReRoute { get; private set; } + public string Aggregator {get; private set;} } } diff --git a/src/Ocelot/DependencyInjection/IOcelotBuilder.cs b/src/Ocelot/DependencyInjection/IOcelotBuilder.cs index 37cfa0d8..a513e19e 100644 --- a/src/Ocelot/DependencyInjection/IOcelotBuilder.cs +++ b/src/Ocelot/DependencyInjection/IOcelotBuilder.cs @@ -3,6 +3,7 @@ using CacheManager.Core; using System; using System.Net.Http; using IdentityServer4.AccessTokenValidation; +using Ocelot.Middleware.Multiplexer; namespace Ocelot.DependencyInjection { @@ -23,5 +24,10 @@ namespace Ocelot.DependencyInjection IOcelotBuilder AddTransientDelegatingHandler(bool global = false) where T : DelegatingHandler; + + IOcelotBuilder AddSingletonDefinedAggregator() + where T : class, IDefinedAggregator; + IOcelotBuilder AddTransientDefinedAggregator() + where T : class, IDefinedAggregator; } } diff --git a/src/Ocelot/DependencyInjection/OcelotBuilder.cs b/src/Ocelot/DependencyInjection/OcelotBuilder.cs index c6fa20f4..96e084c1 100644 --- a/src/Ocelot/DependencyInjection/OcelotBuilder.cs +++ b/src/Ocelot/DependencyInjection/OcelotBuilder.cs @@ -154,6 +154,8 @@ namespace Ocelot.DependencyInjection _services.TryAddSingleton(); _services.TryAddSingleton(); _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); } public IOcelotAdministrationBuilder AddAdministration(string path, string secret) @@ -188,6 +190,20 @@ namespace Ocelot.DependencyInjection return new OcelotAdministrationBuilder(_services, _configurationRoot); } + public IOcelotBuilder AddSingletonDefinedAggregator() + where T : class, IDefinedAggregator + { + _services.AddSingleton(); + return this; + } + + public IOcelotBuilder AddTransientDefinedAggregator() + where T : class, IDefinedAggregator + { + _services.AddTransient(); + return this; + } + public IOcelotBuilder AddSingletonDelegatingHandler(bool global = false) where THandler : DelegatingHandler { diff --git a/src/Ocelot/Errors/OcelotErrorCode.cs b/src/Ocelot/Errors/OcelotErrorCode.cs index ec44ae23..8109bb52 100644 --- a/src/Ocelot/Errors/OcelotErrorCode.cs +++ b/src/Ocelot/Errors/OcelotErrorCode.cs @@ -35,6 +35,7 @@ PathTemplateDoesntStartWithForwardSlash, FileValidationFailedError, UnableToFindDelegatingHandlerProviderError, - CouldNotFindPlaceholderError + CouldNotFindPlaceholderError, + CouldNotFindAggregatorError } } diff --git a/src/Ocelot/Headers/AddHeadersToResponse.cs b/src/Ocelot/Headers/AddHeadersToResponse.cs index 76e9912b..6cdf9a22 100644 --- a/src/Ocelot/Headers/AddHeadersToResponse.cs +++ b/src/Ocelot/Headers/AddHeadersToResponse.cs @@ -1,10 +1,10 @@ namespace Ocelot.Headers { using System.Collections.Generic; - using System.Net.Http; using Ocelot.Configuration.Creator; using Ocelot.Infrastructure; using Ocelot.Logging; + using Ocelot.Middleware; public class AddHeadersToResponse : IAddHeadersToResponse { @@ -17,7 +17,7 @@ namespace Ocelot.Headers _placeholders = placeholders; } - public void Add(List addHeaders, HttpResponseMessage response) + public void Add(List addHeaders, DownstreamResponse response) { foreach(var add in addHeaders) { @@ -31,11 +31,11 @@ namespace Ocelot.Headers continue; } - response.Headers.TryAddWithoutValidation(add.Key, value.Data); + response.Headers.Add(new Header(add.Key, new List { value.Data })); } else { - response.Headers.TryAddWithoutValidation(add.Key, add.Value); + response.Headers.Add(new Header(add.Key, new List { add.Value })); } } } diff --git a/src/Ocelot/Headers/HttpResponseHeaderReplacer.cs b/src/Ocelot/Headers/HttpResponseHeaderReplacer.cs index c4763746..2bc43cfe 100644 --- a/src/Ocelot/Headers/HttpResponseHeaderReplacer.cs +++ b/src/Ocelot/Headers/HttpResponseHeaderReplacer.cs @@ -1,10 +1,10 @@ -using System; using System.Collections.Generic; using System.Linq; -using System.Net.Http; using Ocelot.Configuration; using Ocelot.Infrastructure; using Ocelot.Infrastructure.Extensions; +using Ocelot.Middleware; +using Ocelot.Middleware.Multiplexer; using Ocelot.Request.Middleware; using Ocelot.Responses; @@ -12,19 +12,21 @@ namespace Ocelot.Headers { public class HttpResponseHeaderReplacer : IHttpResponseHeaderReplacer { - private IPlaceholders _placeholders; + private readonly IPlaceholders _placeholders; public HttpResponseHeaderReplacer(IPlaceholders placeholders) { _placeholders = placeholders; } - public Response Replace(HttpResponseMessage response, List fAndRs, DownstreamRequest request) + public Response Replace(DownstreamResponse response, List fAndRs, DownstreamRequest request) { foreach (var f in fAndRs) { + var dict = response.Headers.ToDictionary(x => x.Key); + //if the response headers contain a matching find and replace - if(response.Headers.TryGetValues(f.Key, out var values)) + if(dict.TryGetValue(f.Key, out var values)) { //check to see if it is a placeholder in the find... var placeholderValue = _placeholders.Get(f.Find, request); @@ -32,16 +34,17 @@ namespace Ocelot.Headers if(!placeholderValue.IsError) { //if it is we need to get the value of the placeholder - //var find = replacePlaceholder(httpRequestMessage); - var replaced = values.ToList()[f.Index].Replace(placeholderValue.Data, f.Replace.LastCharAsForwardSlash()); - response.Headers.Remove(f.Key); - response.Headers.Add(f.Key, replaced); + var replaced = values.Values.ToList()[f.Index].Replace(placeholderValue.Data, f.Replace.LastCharAsForwardSlash()); + + response.Headers.Remove(response.Headers.First(item => item.Key == f.Key)); + response.Headers.Add(new Header(f.Key, new List { replaced })); } else { - var replaced = values.ToList()[f.Index].Replace(f.Find, f.Replace); - response.Headers.Remove(f.Key); - response.Headers.Add(f.Key, replaced); + var replaced = values.Values.ToList()[f.Index].Replace(f.Find, f.Replace); + + response.Headers.Remove(response.Headers.First(item => item.Key == f.Key)); + response.Headers.Add(new Header(f.Key, new List { replaced })); } } } diff --git a/src/Ocelot/Headers/IAddHeadersToResponse.cs b/src/Ocelot/Headers/IAddHeadersToResponse.cs index 51f23758..210ddd32 100644 --- a/src/Ocelot/Headers/IAddHeadersToResponse.cs +++ b/src/Ocelot/Headers/IAddHeadersToResponse.cs @@ -1,11 +1,13 @@ +using Ocelot.Middleware; + namespace Ocelot.Headers { using System.Collections.Generic; - using System.Net.Http; using Ocelot.Configuration.Creator; + using Ocelot.Middleware.Multiplexer; public interface IAddHeadersToResponse { - void Add(List addHeaders, HttpResponseMessage response); + void Add(List addHeaders, DownstreamResponse response); } } diff --git a/src/Ocelot/Headers/IHttpResponseHeaderReplacer.cs b/src/Ocelot/Headers/IHttpResponseHeaderReplacer.cs index 6c805b86..b871ac19 100644 --- a/src/Ocelot/Headers/IHttpResponseHeaderReplacer.cs +++ b/src/Ocelot/Headers/IHttpResponseHeaderReplacer.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; -using System.Net.Http; using Ocelot.Configuration; +using Ocelot.Middleware; +using Ocelot.Middleware.Multiplexer; using Ocelot.Request.Middleware; using Ocelot.Responses; @@ -8,6 +9,6 @@ namespace Ocelot.Headers { public interface IHttpResponseHeaderReplacer { - Response Replace(HttpResponseMessage response, List fAndRs, DownstreamRequest httpRequestMessage); + Response Replace(DownstreamResponse response, List fAndRs, DownstreamRequest httpRequestMessage); } -} \ No newline at end of file +} diff --git a/src/Ocelot/Headers/IRemoveOutputHeaders.cs b/src/Ocelot/Headers/IRemoveOutputHeaders.cs index 339d0c35..8c081c3b 100644 --- a/src/Ocelot/Headers/IRemoveOutputHeaders.cs +++ b/src/Ocelot/Headers/IRemoveOutputHeaders.cs @@ -1,10 +1,12 @@ -using System.Net.Http.Headers; +using System.Collections.Generic; +using Ocelot.Middleware; +using Ocelot.Middleware.Multiplexer; using Ocelot.Responses; namespace Ocelot.Headers { public interface IRemoveOutputHeaders { - Response Remove(HttpResponseHeaders headers); + Response Remove(List
headers); } } diff --git a/src/Ocelot/Headers/Middleware/HttpHeadersTransformationMiddleware.cs b/src/Ocelot/Headers/Middleware/HttpHeadersTransformationMiddleware.cs index de5c25da..9544fe7f 100644 --- a/src/Ocelot/Headers/Middleware/HttpHeadersTransformationMiddleware.cs +++ b/src/Ocelot/Headers/Middleware/HttpHeadersTransformationMiddleware.cs @@ -1,7 +1,4 @@ using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Ocelot.DownstreamRouteFinder.Middleware; -using Ocelot.Infrastructure.RequestData; using Ocelot.Logging; using Ocelot.Middleware; diff --git a/src/Ocelot/Headers/RemoveOutputHeaders.cs b/src/Ocelot/Headers/RemoveOutputHeaders.cs index 19d863eb..2664b696 100644 --- a/src/Ocelot/Headers/RemoveOutputHeaders.cs +++ b/src/Ocelot/Headers/RemoveOutputHeaders.cs @@ -1,4 +1,7 @@ -using System.Net.Http.Headers; +using System.Collections.Generic; +using System.Linq; +using Ocelot.Middleware; +using Ocelot.Middleware.Multiplexer; using Ocelot.Responses; namespace Ocelot.Headers @@ -14,14 +17,11 @@ namespace Ocelot.Headers { "Transfer-Encoding" }; - public Response Remove(HttpResponseHeaders headers) - { - foreach (var unsupported in _unsupportedRequestHeaders) - { - headers.Remove(unsupported); - } + public Response Remove(List
headers) + { + headers.RemoveAll(x => _unsupportedRequestHeaders.Contains(x.Key)); return new OkResponse(); } } -} \ No newline at end of file +} diff --git a/src/Ocelot/Middleware/DownstreamContext.cs b/src/Ocelot/Middleware/DownstreamContext.cs index 044d1510..1913eea9 100644 --- a/src/Ocelot/Middleware/DownstreamContext.cs +++ b/src/Ocelot/Middleware/DownstreamContext.cs @@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Http; using Ocelot.Configuration; using Ocelot.DownstreamRouteFinder.UrlMatcher; using Ocelot.Errors; +using Ocelot.Middleware.Multiplexer; using Ocelot.Request.Middleware; namespace Ocelot.Middleware @@ -27,9 +28,9 @@ namespace Ocelot.Middleware public DownstreamRequest DownstreamRequest { get; set; } - public HttpResponseMessage DownstreamResponse { get; set; } + public DownstreamResponse DownstreamResponse { get; set; } - public List Errors { get;set; } + public List Errors { get; } public bool IsError => Errors.Count > 0; } diff --git a/src/Ocelot/Middleware/DownstreamResponse.cs b/src/Ocelot/Middleware/DownstreamResponse.cs new file mode 100644 index 00000000..19345735 --- /dev/null +++ b/src/Ocelot/Middleware/DownstreamResponse.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; + +namespace Ocelot.Middleware +{ + public class DownstreamResponse + { + public DownstreamResponse(HttpContent content, HttpStatusCode statusCode, List
headers) + { + Content = content; + StatusCode = statusCode; + Headers = headers ?? new List
(); + } + + public DownstreamResponse(HttpResponseMessage response) + :this(response.Content, response.StatusCode, response.Headers.Select(x => new Header(x.Key, x.Value)).ToList()) + { + } + + public DownstreamResponse(HttpContent content, HttpStatusCode statusCode, IEnumerable>> headers) + :this(content, statusCode, headers.Select(x => new Header(x.Key, x.Value)).ToList()) + { + } + + public HttpContent Content { get; } + public HttpStatusCode StatusCode { get; } + public List
Headers { get; } + } +} diff --git a/src/Ocelot/Middleware/Header.cs b/src/Ocelot/Middleware/Header.cs new file mode 100644 index 00000000..96065699 --- /dev/null +++ b/src/Ocelot/Middleware/Header.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; + +namespace Ocelot.Middleware +{ + public class Header + { + public Header(string key, IEnumerable values) + { + Key = key; + Values = values ?? new List(); + } + + public string Key { get; } + public IEnumerable Values { get; } + } +} diff --git a/src/Ocelot/Middleware/Multiplexer/CouldNotFindAggregatorError.cs b/src/Ocelot/Middleware/Multiplexer/CouldNotFindAggregatorError.cs new file mode 100644 index 00000000..76a19d7c --- /dev/null +++ b/src/Ocelot/Middleware/Multiplexer/CouldNotFindAggregatorError.cs @@ -0,0 +1,12 @@ +using Ocelot.Errors; + +namespace Ocelot.Middleware.Multiplexer +{ + public class CouldNotFindAggregatorError : Error + { + public CouldNotFindAggregatorError(string aggregator) + : base($"Could not find Aggregator: {aggregator}", OcelotErrorCode.CouldNotFindAggregatorError) + { + } + } +} diff --git a/src/Ocelot/Middleware/Multiplexer/IDefinedAggregator.cs b/src/Ocelot/Middleware/Multiplexer/IDefinedAggregator.cs new file mode 100644 index 00000000..b1585165 --- /dev/null +++ b/src/Ocelot/Middleware/Multiplexer/IDefinedAggregator.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Ocelot.Middleware.Multiplexer +{ + public interface IDefinedAggregator + { + Task Aggregate(List responses); + } +} diff --git a/src/Ocelot/Middleware/Multiplexer/IDefinedAggregatorProvider.cs b/src/Ocelot/Middleware/Multiplexer/IDefinedAggregatorProvider.cs new file mode 100644 index 00000000..375ebc33 --- /dev/null +++ b/src/Ocelot/Middleware/Multiplexer/IDefinedAggregatorProvider.cs @@ -0,0 +1,10 @@ +using Ocelot.Configuration; +using Ocelot.Responses; + +namespace Ocelot.Middleware.Multiplexer +{ + public interface IDefinedAggregatorProvider + { + Response Get(ReRoute reRoute); + } +} diff --git a/src/Ocelot/Middleware/Multiplexer/IResponseAggregator.cs b/src/Ocelot/Middleware/Multiplexer/IResponseAggregator.cs index b85c2cc8..89f2b00b 100644 --- a/src/Ocelot/Middleware/Multiplexer/IResponseAggregator.cs +++ b/src/Ocelot/Middleware/Multiplexer/IResponseAggregator.cs @@ -6,6 +6,6 @@ namespace Ocelot.Middleware.Multiplexer { public interface IResponseAggregator { - Task Aggregate(ReRoute reRoute, DownstreamContext originalContext, List downstreamContexts); + Task Aggregate(ReRoute reRoute, DownstreamContext originalContext, List downstreamResponses); } } diff --git a/src/Ocelot/Middleware/Multiplexer/IResponseAggregatorFactory.cs b/src/Ocelot/Middleware/Multiplexer/IResponseAggregatorFactory.cs new file mode 100644 index 00000000..b032ff1f --- /dev/null +++ b/src/Ocelot/Middleware/Multiplexer/IResponseAggregatorFactory.cs @@ -0,0 +1,9 @@ +using Ocelot.Configuration; + +namespace Ocelot.Middleware.Multiplexer +{ + public interface IResponseAggregatorFactory + { + IResponseAggregator Get(ReRoute reRoute); + } +} diff --git a/src/Ocelot/Middleware/Multiplexer/InMemoryResponseAggregatorFactory.cs b/src/Ocelot/Middleware/Multiplexer/InMemoryResponseAggregatorFactory.cs new file mode 100644 index 00000000..a74386db --- /dev/null +++ b/src/Ocelot/Middleware/Multiplexer/InMemoryResponseAggregatorFactory.cs @@ -0,0 +1,26 @@ +using Ocelot.Configuration; + +namespace Ocelot.Middleware.Multiplexer +{ + public class InMemoryResponseAggregatorFactory : IResponseAggregatorFactory + { + private readonly UserDefinedResponseAggregator _userDefined; + private readonly SimpleJsonResponseAggregator _simple; + + public InMemoryResponseAggregatorFactory(IDefinedAggregatorProvider provider) + { + _userDefined = new UserDefinedResponseAggregator(provider); + _simple = new SimpleJsonResponseAggregator(); + } + + public IResponseAggregator Get(ReRoute reRoute) + { + if(!string.IsNullOrEmpty(reRoute.Aggregator)) + { + return _userDefined; + } + + return _simple; + } + } +} diff --git a/src/Ocelot/Middleware/Multiplexer/Multiplexer.cs b/src/Ocelot/Middleware/Multiplexer/Multiplexer.cs index c4ebb08b..ffe530e8 100644 --- a/src/Ocelot/Middleware/Multiplexer/Multiplexer.cs +++ b/src/Ocelot/Middleware/Multiplexer/Multiplexer.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Ocelot.Configuration; @@ -6,11 +7,11 @@ namespace Ocelot.Middleware.Multiplexer { public class Multiplexer : IMultiplexer { - private readonly IResponseAggregator _aggregator; + private readonly IResponseAggregatorFactory _factory; - public Multiplexer(IResponseAggregator aggregator) + public Multiplexer(IResponseAggregatorFactory factory) { - _aggregator = aggregator; + _factory = factory; } public async Task Multiplex(DownstreamContext context, ReRoute reRoute, OcelotRequestDelegate next) @@ -31,15 +32,40 @@ namespace Ocelot.Middleware.Multiplexer await Task.WhenAll(tasks); - var downstreamContexts = new List(); + var contexts = new List(); foreach (var task in tasks) { var finished = await task; - downstreamContexts.Add(finished); + contexts.Add(finished); } - await _aggregator.Aggregate(reRoute, context, downstreamContexts); + await Map(reRoute, context, contexts); + } + + private async Task Map(ReRoute reRoute, DownstreamContext context, List contexts) + { + if (reRoute.DownstreamReRoute.Count > 1) + { + var aggregator = _factory.Get(reRoute); + await aggregator.Aggregate(reRoute, context, contexts); + } + else + { + MapNotAggregate(context, contexts); + } + } + + private void MapNotAggregate(DownstreamContext originalContext, List downstreamContexts) + { + //assume at least one..if this errors then it will be caught by global exception handler + var finished = downstreamContexts.First(); + + originalContext.Errors.AddRange(finished.Errors); + + originalContext.DownstreamRequest = finished.DownstreamRequest; + + originalContext.DownstreamResponse = finished.DownstreamResponse; } private async Task Fire(DownstreamContext context, OcelotRequestDelegate next) diff --git a/src/Ocelot/Middleware/Multiplexer/ServiceLocatorDefinedAggregatorProvider.cs b/src/Ocelot/Middleware/Multiplexer/ServiceLocatorDefinedAggregatorProvider.cs new file mode 100644 index 00000000..b7b85d26 --- /dev/null +++ b/src/Ocelot/Middleware/Multiplexer/ServiceLocatorDefinedAggregatorProvider.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Extensions.DependencyInjection; +using Ocelot.Configuration; +using Ocelot.Responses; + +namespace Ocelot.Middleware.Multiplexer +{ + public class ServiceLocatorDefinedAggregatorProvider : IDefinedAggregatorProvider + { + private readonly Dictionary _aggregators; + + public ServiceLocatorDefinedAggregatorProvider(IServiceProvider services) + { + _aggregators = services.GetServices().ToDictionary(x => x.GetType().Name); + } + + public Response Get(ReRoute reRoute) + { + if(_aggregators.ContainsKey(reRoute.Aggregator)) + { + return new OkResponse(_aggregators[reRoute.Aggregator]); + } + + return new ErrorResponse(new CouldNotFindAggregatorError(reRoute.Aggregator)); + } + } +} diff --git a/src/Ocelot/Middleware/Multiplexer/SimpleJsonResponseAggregator.cs b/src/Ocelot/Middleware/Multiplexer/SimpleJsonResponseAggregator.cs index 3d9a997d..bfc1351f 100644 --- a/src/Ocelot/Middleware/Multiplexer/SimpleJsonResponseAggregator.cs +++ b/src/Ocelot/Middleware/Multiplexer/SimpleJsonResponseAggregator.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Headers; @@ -12,18 +11,6 @@ namespace Ocelot.Middleware.Multiplexer public class SimpleJsonResponseAggregator : IResponseAggregator { public async Task Aggregate(ReRoute reRoute, DownstreamContext originalContext, List downstreamContexts) - { - if (reRoute.DownstreamReRoute.Count > 1) - { - await MapAggregtes(originalContext, downstreamContexts); - } - else - { - MapNotAggregate(originalContext, downstreamContexts); - } - } - - private async Task MapAggregtes(DownstreamContext originalContext, List downstreamContexts) { await MapAggregateContent(originalContext, downstreamContexts); } @@ -34,7 +21,7 @@ namespace Ocelot.Middleware.Multiplexer contentBuilder.Append("{"); - for (int i = 0; i < downstreamContexts.Count; i++) + for (var i = 0; i < downstreamContexts.Count; i++) { if (downstreamContexts[i].IsError) { @@ -54,13 +41,12 @@ namespace Ocelot.Middleware.Multiplexer contentBuilder.Append("}"); - originalContext.DownstreamResponse = new HttpResponseMessage(HttpStatusCode.OK) + var stringContent = new StringContent(contentBuilder.ToString()) { - Content = new StringContent(contentBuilder.ToString()) - { - Headers = {ContentType = new MediaTypeHeaderValue("application/json")} - } + Headers = {ContentType = new MediaTypeHeaderValue("application/json")} }; + + originalContext.DownstreamResponse = new DownstreamResponse(stringContent, HttpStatusCode.OK, new List>>()); } private static void MapAggregateError(DownstreamContext originalContext, List downstreamContexts, int i) @@ -68,17 +54,5 @@ namespace Ocelot.Middleware.Multiplexer originalContext.Errors.AddRange(downstreamContexts[i].Errors); originalContext.DownstreamResponse = downstreamContexts[i].DownstreamResponse; } - - private void MapNotAggregate(DownstreamContext originalContext, List downstreamContexts) - { - //assume at least one..if this errors then it will be caught by global exception handler - var finished = downstreamContexts.First(); - - originalContext.Errors = finished.Errors; - - originalContext.DownstreamRequest = finished.DownstreamRequest; - - originalContext.DownstreamResponse = finished.DownstreamResponse; - } } } diff --git a/src/Ocelot/Middleware/Multiplexer/UserDefinedResponseAggregator.cs b/src/Ocelot/Middleware/Multiplexer/UserDefinedResponseAggregator.cs new file mode 100644 index 00000000..aa44466d --- /dev/null +++ b/src/Ocelot/Middleware/Multiplexer/UserDefinedResponseAggregator.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Ocelot.Configuration; + +namespace Ocelot.Middleware.Multiplexer +{ + public class UserDefinedResponseAggregator : IResponseAggregator + { + private readonly IDefinedAggregatorProvider _provider; + + public UserDefinedResponseAggregator(IDefinedAggregatorProvider provider) + { + _provider = provider; + } + + public async Task Aggregate(ReRoute reRoute, DownstreamContext originalContext, List downstreamResponses) + { + var aggregator = _provider.Get(reRoute); + + if (!aggregator.IsError) + { + var aggregateResponse = await aggregator.Data + .Aggregate(downstreamResponses.Select(x => x.DownstreamResponse).ToList()); + + originalContext.DownstreamResponse = aggregateResponse; + } + else + { + originalContext.Errors.AddRange(aggregator.Errors); + } + } + } +} diff --git a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs index 61d13f52..9d345dc2 100644 --- a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs +++ b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs @@ -22,11 +22,6 @@ public static class OcelotMiddlewareExtensions { - /// - /// Registers the Ocelot default middlewares - /// - /// - /// public static async Task UseOcelot(this IApplicationBuilder builder) { await builder.UseOcelot(new OcelotPipelineConfiguration()); @@ -34,12 +29,6 @@ return builder; } - /// - /// Registers Ocelot with a combination of default middlewares and optional middlewares in the configuration - /// - /// - /// - /// public static async Task UseOcelot(this IApplicationBuilder builder, OcelotPipelineConfiguration pipelineConfiguration) { var configuration = await CreateConfiguration(builder); @@ -230,26 +219,14 @@ }); } } - - private static void UseIfNotNull(this IApplicationBuilder builder, Func, Task> middleware) - { - if (middleware != null) - { - builder.Use(middleware); - } - } - /// - /// Configure a DiagnosticListener to listen for diagnostic events when the middleware starts and ends - /// - /// - private static void ConfigureDiagnosticListener(IApplicationBuilder builder) - { + private static void ConfigureDiagnosticListener(IApplicationBuilder builder) + { var env = (IHostingEnvironment)builder.ApplicationServices.GetService(typeof(IHostingEnvironment)); var listener = (OcelotDiagnosticListener)builder.ApplicationServices.GetService(typeof(OcelotDiagnosticListener)); var diagnosticListener = (DiagnosticListener)builder.ApplicationServices.GetService(typeof(DiagnosticListener)); diagnosticListener.SubscribeWithAdapter(listener); - } + } private static void OnShutdown(IApplicationBuilder app) { diff --git a/src/Ocelot/Request/Mapper/RequestMapper.cs b/src/Ocelot/Request/Mapper/RequestMapper.cs index ee04a551..67afc53d 100644 --- a/src/Ocelot/Request/Mapper/RequestMapper.cs +++ b/src/Ocelot/Request/Mapper/RequestMapper.cs @@ -5,9 +5,7 @@ using System.IO; using System.Linq; using System.Net.Http; - using System.Net.Http.Headers; using System.Threading.Tasks; - using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; using Microsoft.Extensions.Primitives; @@ -83,7 +81,6 @@ { foreach (var header in request.Headers) { - //todo get rid of if.. if (IsSupportedHeader(header)) { requestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray()); diff --git a/src/Ocelot/Requester/Middleware/HttpRequesterMiddleware.cs b/src/Ocelot/Requester/Middleware/HttpRequesterMiddleware.cs index 23ebd9de..4204374b 100644 --- a/src/Ocelot/Requester/Middleware/HttpRequesterMiddleware.cs +++ b/src/Ocelot/Requester/Middleware/HttpRequesterMiddleware.cs @@ -1,10 +1,6 @@ -using Microsoft.AspNetCore.Http; -using Ocelot.Infrastructure.RequestData; using Ocelot.Logging; using Ocelot.Middleware; using System.Threading.Tasks; -using Ocelot.DownstreamRouteFinder.Middleware; -using Ocelot.Requester.QoS; namespace Ocelot.Requester.Middleware { @@ -36,7 +32,7 @@ namespace Ocelot.Requester.Middleware Logger.LogDebug("setting http response message"); - context.DownstreamResponse = response.Data; + context.DownstreamResponse = new DownstreamResponse(response.Data); } } } diff --git a/src/Ocelot/Responder/HttpContextResponder.cs b/src/Ocelot/Responder/HttpContextResponder.cs index 7bc55d94..bd265a61 100644 --- a/src/Ocelot/Responder/HttpContextResponder.cs +++ b/src/Ocelot/Responder/HttpContextResponder.cs @@ -1,17 +1,14 @@ using System.IO; using System.Linq; using System.Net; -using System.Net.Http; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Primitives; using Ocelot.Headers; -using Ocelot.Responses; +using Ocelot.Middleware; namespace Ocelot.Responder { - using System.Collections.Generic; - /// /// Cannot unit test things in this class due to methods not being implemented /// on .net concretes used for testing @@ -25,7 +22,7 @@ namespace Ocelot.Responder _removeOutputHeaders = removeOutputHeaders; } - public async Task SetResponseOnHttpContext(HttpContext context, HttpResponseMessage response) + public async Task SetResponseOnHttpContext(HttpContext context, DownstreamResponse response) { _removeOutputHeaders.Remove(response.Headers); @@ -36,12 +33,12 @@ namespace Ocelot.Responder foreach (var httpResponseHeader in response.Content.Headers) { - AddHeaderIfDoesntExist(context, httpResponseHeader); + AddHeaderIfDoesntExist(context, new Header(httpResponseHeader.Key, httpResponseHeader.Value)); } var content = await response.Content.ReadAsByteArrayAsync(); - AddHeaderIfDoesntExist(context, new KeyValuePair>("Content-Length", new []{ content.Length.ToString() }) ); + AddHeaderIfDoesntExist(context, new Header("Content-Length", new []{ content.Length.ToString() }) ); context.Response.OnStarting(state => { @@ -70,11 +67,11 @@ namespace Ocelot.Responder }, context); } - private static void AddHeaderIfDoesntExist(HttpContext context, KeyValuePair> httpResponseHeader) + private static void AddHeaderIfDoesntExist(HttpContext context, Header httpResponseHeader) { if (!context.Response.Headers.ContainsKey(httpResponseHeader.Key)) { - context.Response.Headers.Add(httpResponseHeader.Key, new StringValues(httpResponseHeader.Value.ToArray())); + context.Response.Headers.Add(httpResponseHeader.Key, new StringValues(httpResponseHeader.Values.ToArray())); } } } diff --git a/src/Ocelot/Responder/IHttpResponder.cs b/src/Ocelot/Responder/IHttpResponder.cs index c5e5a0ca..73722f53 100644 --- a/src/Ocelot/Responder/IHttpResponder.cs +++ b/src/Ocelot/Responder/IHttpResponder.cs @@ -1,12 +1,13 @@ -using System.Net.Http; -using System.Threading.Tasks; +using System.Threading.Tasks; using Microsoft.AspNetCore.Http; +using Ocelot.Middleware; +using Ocelot.Middleware.Multiplexer; namespace Ocelot.Responder { public interface IHttpResponder { - Task SetResponseOnHttpContext(HttpContext context, HttpResponseMessage response); + Task SetResponseOnHttpContext(HttpContext context, DownstreamResponse response); void SetErrorResponseOnContext(HttpContext context, int statusCode); } } diff --git a/src/Ocelot/Responder/Middleware/ResponderMiddleware.cs b/src/Ocelot/Responder/Middleware/ResponderMiddleware.cs index 3e04fa3a..0487d811 100644 --- a/src/Ocelot/Responder/Middleware/ResponderMiddleware.cs +++ b/src/Ocelot/Responder/Middleware/ResponderMiddleware.cs @@ -1,11 +1,9 @@ using Microsoft.AspNetCore.Http; using Ocelot.Errors; -using Ocelot.Infrastructure.RequestData; using Ocelot.Logging; using Ocelot.Middleware; using System.Collections.Generic; using System.Threading.Tasks; -using Ocelot.DownstreamRouteFinder.Middleware; using Ocelot.Infrastructure.Extensions; namespace Ocelot.Responder.Middleware diff --git a/test/Ocelot.AcceptanceTests/AggregateTests.cs b/test/Ocelot.AcceptanceTests/AggregateTests.cs index 3944adfc..8e0e5dfd 100644 --- a/test/Ocelot.AcceptanceTests/AggregateTests.cs +++ b/test/Ocelot.AcceptanceTests/AggregateTests.cs @@ -1,11 +1,17 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Ocelot.Configuration.File; +using Ocelot.Middleware; +using Ocelot.Middleware.Multiplexer; using Shouldly; using TestStack.BDDfy; using Xunit; @@ -15,6 +21,7 @@ namespace Ocelot.AcceptanceTests public class AggregateTests : IDisposable { private IWebHost _serviceOneBuilder; + private IWebHost _serviceTwoBuilder; private readonly Steps _steps; private string _downstreamPathOne; private string _downstreamPathTwo; @@ -24,6 +31,75 @@ namespace Ocelot.AcceptanceTests _steps = new Steps(); } + [Fact] + public void should_return_response_200_with_simple_url_user_defined_aggregate() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51885, + } + }, + UpstreamPathTemplate = "/laura", + UpstreamHttpMethod = new List { "Get" }, + Key = "Laura" + }, + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51886, + } + }, + UpstreamPathTemplate = "/tom", + UpstreamHttpMethod = new List { "Get" }, + Key = "Tom" + } + }, + Aggregates = new List + { + new FileAggregateReRoute + { + UpstreamPathTemplate = "/", + UpstreamHost = "localhost", + ReRouteKeys = new List + { + "Tom", + "Laura" + }, + Aggregator = "FakeDefinedAggregator" + } + } + }; + + var expected = "Bye from Laura, Bye from Tom"; + + this.Given(x => x.GivenServiceOneIsRunning("http://localhost:51885", "/", 200, "{Hello from Laura}")) + .Given(x => x.GivenServiceTwoIsRunning("http://localhost:51886", "/", 200, "{Hello from Tom}")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunningWithSpecficAggregatorsRegisteredInDi()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe(expected)) + .And(x => ThenTheDownstreamUrlPathShouldBe("/", "/")) + .BDDfy(); + } + [Fact] public void should_return_response_200_with_simple_url() { @@ -325,7 +401,7 @@ namespace Ocelot.AcceptanceTests private void GivenServiceTwoIsRunning(string baseUrl, string basePath, int statusCode, string responseBody) { - _serviceOneBuilder = new WebHostBuilder() + _serviceTwoBuilder = new WebHostBuilder() .UseUrls(baseUrl) .UseKestrel() .UseContentRoot(Directory.GetCurrentDirectory()) @@ -351,7 +427,7 @@ namespace Ocelot.AcceptanceTests }) .Build(); - _serviceOneBuilder.Start(); + _serviceTwoBuilder.Start(); } internal void ThenTheDownstreamUrlPathShouldBe(string expectedDownstreamPathOne, string expectedDownstreamPath) @@ -363,7 +439,33 @@ namespace Ocelot.AcceptanceTests public void Dispose() { _serviceOneBuilder?.Dispose(); + _serviceTwoBuilder?.Dispose(); _steps.Dispose(); } } + + public class FakeDepdendency + { + } + + public class FakeDefinedAggregator : IDefinedAggregator + { + private readonly FakeDepdendency _dep; + + public FakeDefinedAggregator(FakeDepdendency dep) + { + _dep = dep; + } + + public async Task Aggregate(List responses) + { + var one = await responses[0].Content.ReadAsStringAsync(); + var two = await responses[1].Content.ReadAsStringAsync(); + + var merge = $"{one}, {two}"; + merge = merge.Replace("Hello", "Bye").Replace("{", "").Replace("}", ""); + var headers = responses.SelectMany(x => x.Headers).ToList(); + return new DownstreamResponse(new StringContent(merge), HttpStatusCode.OK, headers); + } + } } diff --git a/test/Ocelot.AcceptanceTests/ButterflyTracingTests.cs b/test/Ocelot.AcceptanceTests/ButterflyTracingTests.cs index 10cb7929..cde7b3bc 100644 --- a/test/Ocelot.AcceptanceTests/ButterflyTracingTests.cs +++ b/test/Ocelot.AcceptanceTests/ButterflyTracingTests.cs @@ -91,17 +91,17 @@ namespace Ocelot.AcceptanceTests var butterflyUrl = "http://localhost:9618"; - this.Given(x => GivenServiceOneIsRunning("http://localhost:51887", "/api/values", 200, "Hello from Laura", butterflyUrl)) + this.Given(x => GivenFakeButterfly(butterflyUrl)) + .And(x => GivenServiceOneIsRunning("http://localhost:51887", "/api/values", 200, "Hello from Laura", butterflyUrl)) .And(x => GivenServiceTwoIsRunning("http://localhost:51388", "/api/values", 200, "Hello from Tom", butterflyUrl)) - .And(x => GivenFakeButterfly(butterflyUrl)) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunningUsingButterfly(butterflyUrl)) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/api001/values")) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/api002/values")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Tom")) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/api002/values")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Tom")) .BDDfy(); var commandOnAllStateMachines = WaitFor(10000).Until(() => _butterflyCalled == 4); @@ -151,8 +151,8 @@ namespace Ocelot.AcceptanceTests var butterflyUrl = "http://localhost:9618"; - this.Given(x => GivenServiceOneIsRunning("http://localhost:51387", "/api/values", 200, "Hello from Laura", butterflyUrl)) - .And(x => GivenFakeButterfly(butterflyUrl)) + this.Given(x => GivenFakeButterfly(butterflyUrl)) + .And(x => GivenServiceOneIsRunning("http://localhost:51387", "/api/values", 200, "Hello from Laura", butterflyUrl)) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunningUsingButterfly(butterflyUrl)) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/api001/values")) diff --git a/test/Ocelot.AcceptanceTests/Steps.cs b/test/Ocelot.AcceptanceTests/Steps.cs index cca5ac18..ba0da5d1 100644 --- a/test/Ocelot.AcceptanceTests/Steps.cs +++ b/test/Ocelot.AcceptanceTests/Steps.cs @@ -26,6 +26,7 @@ using System.IO.Compression; using System.Text; using static Ocelot.AcceptanceTests.HttpDelegatingHandlersTests; using Ocelot.Requester; +using Ocelot.Middleware.Multiplexer; namespace Ocelot.AcceptanceTests { @@ -178,12 +179,6 @@ namespace Ocelot.AcceptanceTests _ocelotClient = _ocelotServer.CreateClient(); } -/* - public void GivenIHaveAddedXForwardedForHeader(string value) - { - _ocelotClient.DefaultRequestHeaders.TryAddWithoutValidation("X-Forwarded-For", value); - }*/ - public void GivenOcelotIsRunningWithMiddleareBeforePipeline(Func callback) { _webHostBuilder = new WebHostBuilder(); @@ -246,6 +241,39 @@ namespace Ocelot.AcceptanceTests _ocelotClient = _ocelotServer.CreateClient(); } + public void GivenOcelotIsRunningWithSpecficAggregatorsRegisteredInDi() + where TAggregator : class, IDefinedAggregator + where TDepedency : class + { + _webHostBuilder = new WebHostBuilder(); + + _webHostBuilder + .ConfigureAppConfiguration((hostingContext, config) => + { + config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); + var env = hostingContext.HostingEnvironment; + config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); + config.AddJsonFile("configuration.json"); + config.AddEnvironmentVariables(); + }) + .ConfigureServices(s => + { + s.AddSingleton(_webHostBuilder); + s.AddSingleton(); + s.AddOcelot() + .AddSingletonDefinedAggregator(); + }) + .Configure(a => + { + a.UseOcelot().Wait(); + }); + + _ocelotServer = new TestServer(_webHostBuilder); + + _ocelotClient = _ocelotServer.CreateClient(); + } + public void GivenOcelotIsRunningWithGlobalHandlersRegisteredInDi() where TOne : DelegatingHandler where TWo : DelegatingHandler diff --git a/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareRealCacheTests.cs b/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareRealCacheTests.cs index c1bc9b9d..b3bbface 100644 --- a/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareRealCacheTests.cs +++ b/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareRealCacheTests.cs @@ -1,7 +1,4 @@ -using Ocelot.Errors; -using Ocelot.Middleware; - -namespace Ocelot.UnitTests.Cache +namespace Ocelot.UnitTests.Cache { using System.Linq; using System.Net; @@ -17,15 +14,17 @@ namespace Ocelot.UnitTests.Cache using Ocelot.Configuration; using Ocelot.Configuration.Builder; using Ocelot.Logging; + using Ocelot.Middleware; using TestStack.BDDfy; using Xunit; using Microsoft.AspNetCore.Http; + using Ocelot.Middleware.Multiplexer; public class OutputCacheMiddlewareRealCacheTests { - private IOcelotCache _cacheManager; - private OutputCacheMiddleware _middleware; - private DownstreamContext _downstreamContext; + private readonly IOcelotCache _cacheManager; + private readonly OutputCacheMiddleware _middleware; + private readonly DownstreamContext _downstreamContext; private OcelotRequestDelegate _next; private Mock _loggerFactory; private IRegionCreator _regionCreator; @@ -56,14 +55,10 @@ namespace Ocelot.UnitTests.Cache Headers = { ContentType = new MediaTypeHeaderValue("application/json")} }; - var response = new HttpResponseMessage(HttpStatusCode.OK) - { - Content = content, - }; + var response = new DownstreamResponse(content, HttpStatusCode.OK, new List>>()); this.Given(x => x.GivenResponseIsNotCached(response)) .And(x => x.GivenTheDownstreamRouteIs()) - .And(x => x.GivenThereAreNoErrors()) .When(x => x.WhenICallTheMiddleware()) .Then(x => x.ThenTheContentTypeHeaderIsCached()) .BDDfy(); @@ -81,9 +76,9 @@ namespace Ocelot.UnitTests.Cache header.First().ShouldBe("application/json"); } - private void GivenResponseIsNotCached(HttpResponseMessage message) + private void GivenResponseIsNotCached(DownstreamResponse response) { - _downstreamContext.DownstreamResponse = message; + _downstreamContext.DownstreamResponse = response; } private void GivenTheDownstreamRouteIs() @@ -96,10 +91,5 @@ namespace Ocelot.UnitTests.Cache _downstreamContext.DownstreamReRoute = reRoute; } - - private void GivenThereAreNoErrors() - { - _downstreamContext.Errors = new List(); - } } } diff --git a/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs b/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs index 83a4744d..cd0f9719 100644 --- a/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs @@ -1,8 +1,4 @@ -using System.Net; -using Ocelot.Errors; -using Ocelot.Middleware; - -namespace Ocelot.UnitTests.Cache +namespace Ocelot.UnitTests.Cache { using System; using System.Collections.Generic; @@ -19,17 +15,20 @@ namespace Ocelot.UnitTests.Cache using Ocelot.Logging; using TestStack.BDDfy; using Xunit; + using System.Net; + using Ocelot.Middleware; + using Ocelot.Middleware.Multiplexer; public class OutputCacheMiddlewareTests { private readonly Mock> _cacheManager; - private Mock _loggerFactory; + private readonly Mock _loggerFactory; private Mock _logger; private OutputCacheMiddleware _middleware; - private DownstreamContext _downstreamContext; - private OcelotRequestDelegate _next; + private readonly DownstreamContext _downstreamContext; + private readonly OcelotRequestDelegate _next; private CachedResponse _response; - private IRegionCreator _regionCreator; + private readonly IRegionCreator _regionCreator; public OutputCacheMiddlewareTests() { @@ -46,7 +45,17 @@ namespace Ocelot.UnitTests.Cache [Fact] public void should_returned_cached_item_when_it_is_in_cache() { - var cachedResponse = new CachedResponse(HttpStatusCode.OK, new Dictionary>(), "", new Dictionary>()); + var headers = new Dictionary> + { + { "test", new List { "test" } } + }; + + var contentHeaders = new Dictionary> + { + { "content-type", new List { "application/json" } } + }; + + var cachedResponse = new CachedResponse(HttpStatusCode.OK, headers, "", contentHeaders); this.Given(x => x.GivenThereIsACachedResponse(cachedResponse)) .And(x => x.GivenTheDownstreamRouteIs()) .When(x => x.WhenICallTheMiddleware()) @@ -59,7 +68,6 @@ namespace Ocelot.UnitTests.Cache { this.Given(x => x.GivenResponseIsNotCached()) .And(x => x.GivenTheDownstreamRouteIs()) - .And(x => x.GivenThereAreNoErrors()) .When(x => x.WhenICallTheMiddleware()) .Then(x => x.ThenTheCacheAddIsCalledCorrectly()) .BDDfy(); @@ -81,7 +89,7 @@ namespace Ocelot.UnitTests.Cache private void GivenResponseIsNotCached() { - _downstreamContext.DownstreamResponse = new HttpResponseMessage(); + _downstreamContext.DownstreamResponse = new DownstreamResponse(new HttpResponseMessage()); } private void GivenTheDownstreamRouteIs() @@ -101,11 +109,6 @@ namespace Ocelot.UnitTests.Cache _downstreamContext.DownstreamReRoute = downstreamRoute.ReRoute.DownstreamReRoute[0]; } - private void GivenThereAreNoErrors() - { - _downstreamContext.Errors = new List(); - } - private void ThenTheCacheGetIsCalledCorrectly() { _cacheManager diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs index ce1d6989..04dcc0c1 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs @@ -134,7 +134,8 @@ namespace Ocelot.UnitTests.Configuration { "Tom", "Laura" - } + }, + Aggregator = "asdf" } } }; diff --git a/test/Ocelot.UnitTests/Configuration/HeaderFindAndReplaceCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/HeaderFindAndReplaceCreatorTests.cs index 67031879..1ec251cf 100644 --- a/test/Ocelot.UnitTests/Configuration/HeaderFindAndReplaceCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/HeaderFindAndReplaceCreatorTests.cs @@ -149,7 +149,6 @@ namespace Ocelot.UnitTests.Configuration .Then(x => ThenTheFollowingDownstreamIsReturned(downstream)) .BDDfy(); } - [Fact] public void should_add_trace_id_header() diff --git a/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs b/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs index 1cd22388..0b13954a 100644 --- a/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs +++ b/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs @@ -18,6 +18,8 @@ using Shouldly; using IdentityServer4.AccessTokenValidation; using TestStack.BDDfy; using Xunit; +using static Ocelot.UnitTests.Middleware.UserDefinedResponseAggregatorTests; +using Ocelot.Middleware.Multiplexer; namespace Ocelot.UnitTests.DependencyInjection { @@ -177,6 +179,40 @@ namespace Ocelot.UnitTests.DependencyInjection .BDDfy(); } + [Fact] + public void should_add_singleton_defined_aggregators() + { + this.Given(x => WhenISetUpOcelotServices()) + .When(x => AddSingletonDefinedAggregator()) + .When(x => AddSingletonDefinedAggregator()) + .Then(x => ThenTheProviderIsRegisteredAndReturnsSpecificAggregators()) + .And(x => ThenTheAggregatorsAreSingleton()) + .BDDfy(); + } + + [Fact] + public void should_add_transient_defined_aggregators() + { + this.Given(x => WhenISetUpOcelotServices()) + .When(x => AddTransientDefinedAggregator()) + .When(x => AddTransientDefinedAggregator()) + .Then(x => ThenTheProviderIsRegisteredAndReturnsSpecificAggregators()) + .And(x => ThenTheAggregatorsAreTransient()) + .BDDfy(); + } + + private void AddSingletonDefinedAggregator() + where T : class, IDefinedAggregator + { + _ocelotBuilder.AddSingletonDefinedAggregator(); + } + + private void AddTransientDefinedAggregator() + where T : class, IDefinedAggregator + { + _ocelotBuilder.AddTransientDefinedAggregator(); + } + private void ThenTheSpecificHandlersAreSingleton() { var handlers = _serviceProvider.GetServices().ToList(); @@ -258,6 +294,32 @@ namespace Ocelot.UnitTests.DependencyInjection handlers[1].ShouldBeOfType(); } + private void ThenTheProviderIsRegisteredAndReturnsSpecificAggregators() + { + _serviceProvider = _services.BuildServiceProvider(); + var handlers = _serviceProvider.GetServices().ToList(); + handlers[0].ShouldBeOfType(); + handlers[1].ShouldBeOfType(); + } + + private void ThenTheAggregatorsAreTransient() + { + var aggregators = _serviceProvider.GetServices().ToList(); + var first = aggregators[0]; + aggregators = _serviceProvider.GetServices().ToList(); + var second = aggregators[0]; + first.ShouldNotBe(second); + } + + private void ThenTheAggregatorsAreSingleton() + { + var aggregators = _serviceProvider.GetServices().ToList(); + var first = aggregators[0]; + aggregators = _serviceProvider.GetServices().ToList(); + var second = aggregators[0]; + first.ShouldBe(second); + } + private void OnlyOneVersionOfEachCacheIsRegistered() { var outputCache = _services.Single(x => x.ServiceType == typeof(IOcelotCache)); diff --git a/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs b/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs index 636174eb..63797346 100644 --- a/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs @@ -1,7 +1,4 @@ -using Ocelot.Configuration; -using Ocelot.Middleware; - -namespace Ocelot.UnitTests.DownstreamUrlCreator +namespace Ocelot.UnitTests.DownstreamUrlCreator { using System; using System.Collections.Generic; @@ -21,17 +18,19 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator using Shouldly; using Microsoft.AspNetCore.Http; using Ocelot.Request.Middleware; + using Ocelot.Configuration; + using Ocelot.Middleware; public class DownstreamUrlCreatorMiddlewareTests { private readonly Mock _downstreamUrlTemplateVariableReplacer; private OkResponse _downstreamPath; - private Mock _loggerFactory; + private readonly Mock _loggerFactory; private Mock _logger; private DownstreamUrlCreatorMiddleware _middleware; - private DownstreamContext _downstreamContext; - private OcelotRequestDelegate _next; - private HttpRequestMessage _request; + private readonly DownstreamContext _downstreamContext; + private readonly OcelotRequestDelegate _next; + private readonly HttpRequestMessage _request; public DownstreamUrlCreatorMiddlewareTests() { @@ -212,7 +211,6 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator private void GivenTheDownstreamRequestUriIs(string uri) { _request.RequestUri = new Uri(uri); - //todo - not sure if needed _downstreamContext.DownstreamRequest = new DownstreamRequest(_request); } diff --git a/test/Ocelot.UnitTests/Headers/AddHeadersToResponseTests.cs b/test/Ocelot.UnitTests/Headers/AddHeadersToResponseTests.cs index a8eba48e..9c668dd1 100644 --- a/test/Ocelot.UnitTests/Headers/AddHeadersToResponseTests.cs +++ b/test/Ocelot.UnitTests/Headers/AddHeadersToResponseTests.cs @@ -1,29 +1,29 @@ using Xunit; -using Shouldly; using TestStack.BDDfy; using Ocelot.Headers; using System.Net.Http; using System.Collections.Generic; -using Ocelot.Configuration.Creator; using System.Linq; +using Ocelot.Configuration.Creator; using Moq; -using Ocelot.Infrastructure.RequestData; using Ocelot.Responses; using Ocelot.Infrastructure; using Ocelot.UnitTests.Responder; -using System; using Ocelot.Logging; +using Ocelot.Middleware; +using Ocelot.Middleware.Multiplexer; +using Shouldly; namespace Ocelot.UnitTests.Headers { public class AddHeadersToResponseTests { - private IAddHeadersToResponse _adder; - private Mock _placeholders; - private HttpResponseMessage _response; + private readonly IAddHeadersToResponse _adder; + private readonly Mock _placeholders; + private DownstreamResponse _response; private List _addHeaders; private Mock _factory; - private Mock _logger; + private readonly Mock _logger; public AddHeadersToResponseTests() { @@ -111,7 +111,7 @@ namespace Ocelot.UnitTests.Headers private void ThenTheHeaderIsNotAdded(string key) { - _response.Headers.TryGetValues(key, out var values).ShouldBeFalse(); + _response.Headers.Any(x => x.Key == key).ShouldBeFalse(); } private void GivenTheTraceIdIs(string traceId) @@ -126,8 +126,8 @@ namespace Ocelot.UnitTests.Headers private void ThenTheHeaderIsReturned(string key, string value) { - var values = _response.Headers.GetValues(key); - values.First().ShouldBe(value); + var values = _response.Headers.First(x => x.Key == key); + values.Values.First().ShouldBe(value); } private void WhenIAdd() @@ -137,7 +137,7 @@ namespace Ocelot.UnitTests.Headers private void GivenAResponseMessage() { - _response = new HttpResponseMessage(); + _response = new DownstreamResponse(new HttpResponseMessage()); } private void GivenTheAddHeaders(List addHeaders) diff --git a/test/Ocelot.UnitTests/Headers/HttpHeadersTransformationMiddlewareTests.cs b/test/Ocelot.UnitTests/Headers/HttpHeadersTransformationMiddlewareTests.cs index 4992479b..cc65624c 100644 --- a/test/Ocelot.UnitTests/Headers/HttpHeadersTransformationMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Headers/HttpHeadersTransformationMiddlewareTests.cs @@ -1,33 +1,33 @@ -using Xunit; -using Ocelot.Logging; -using Ocelot.Headers.Middleware; -using TestStack.BDDfy; -using Microsoft.AspNetCore.Http; -using System.Collections.Generic; -using Moq; -using Ocelot.Configuration; -using Ocelot.DownstreamRouteFinder; -using Ocelot.Configuration.Builder; -using Ocelot.Headers; -using System.Net.Http; -using Ocelot.Authorisation.Middleware; -using Ocelot.Middleware; - namespace Ocelot.UnitTests.Headers { + using Xunit; + using Ocelot.Logging; + using Ocelot.Headers.Middleware; + using TestStack.BDDfy; + using Microsoft.AspNetCore.Http; + using System.Collections.Generic; + using Moq; + using Ocelot.Configuration; + using Ocelot.DownstreamRouteFinder; + using Ocelot.Configuration.Builder; + using Ocelot.Headers; + using System.Net.Http; + using Ocelot.Authorisation.Middleware; + using Ocelot.Middleware; + using Ocelot.Middleware.Multiplexer; using System.Threading.Tasks; using Ocelot.Request.Middleware; public class HttpHeadersTransformationMiddlewareTests { - private Mock _preReplacer; - private Mock _postReplacer; + private readonly Mock _preReplacer; + private readonly Mock _postReplacer; private Mock _loggerFactory; private Mock _logger; - private HttpHeadersTransformationMiddleware _middleware; - private DownstreamContext _downstreamContext; + private readonly HttpHeadersTransformationMiddleware _middleware; + private readonly DownstreamContext _downstreamContext; private OcelotRequestDelegate _next; - private Mock _addHeaders; + private readonly Mock _addHeaders; public HttpHeadersTransformationMiddlewareTests() { @@ -74,7 +74,7 @@ namespace Ocelot.UnitTests.Headers private void GivenTheHttpResponseMessageIs() { - _downstreamContext.DownstreamResponse = new HttpResponseMessage(); + _downstreamContext.DownstreamResponse = new DownstreamResponse(new HttpResponseMessage()); } private void GivenTheReRouteHasPreFindAndReplaceSetUp() @@ -98,7 +98,7 @@ namespace Ocelot.UnitTests.Headers private void ThenTheIHttpResponseHeaderReplacerIsCalledCorrectly() { - _postReplacer.Verify(x => x.Replace(It.IsAny(), It.IsAny>(), It.IsAny()), Times.Once); + _postReplacer.Verify(x => x.Replace(It.IsAny(), It.IsAny>(), It.IsAny()), Times.Once); } private void GivenTheFollowingRequest() diff --git a/test/Ocelot.UnitTests/Headers/HttpResponseHeaderReplacerTests.cs b/test/Ocelot.UnitTests/Headers/HttpResponseHeaderReplacerTests.cs index 6eefdc4c..3e7bc077 100644 --- a/test/Ocelot.UnitTests/Headers/HttpResponseHeaderReplacerTests.cs +++ b/test/Ocelot.UnitTests/Headers/HttpResponseHeaderReplacerTests.cs @@ -7,19 +7,21 @@ using Ocelot.Configuration; using System.Collections.Generic; using Ocelot.Responses; using System.Linq; +using System.Net; using Moq; using Ocelot.Infrastructure; using Ocelot.Middleware; using Ocelot.Infrastructure.RequestData; +using Ocelot.Middleware.Multiplexer; using Ocelot.Request.Middleware; namespace Ocelot.UnitTests.Headers { public class HttpResponseHeaderReplacerTests { - private HttpResponseMessage _response; + private DownstreamResponse _response; private Placeholders _placeholders; - private HttpResponseHeaderReplacer _replacer; + private readonly HttpResponseHeaderReplacer _replacer; private List _headerFindAndReplaces; private Response _result; private DownstreamRequest _request; @@ -37,11 +39,13 @@ namespace Ocelot.UnitTests.Headers [Fact] public void should_replace_headers() { - var response = new HttpResponseMessage(); - response.Headers.Add("test", "test"); + var response = new DownstreamResponse(new StringContent(string.Empty), HttpStatusCode.Accepted, + new List>>() + { + new KeyValuePair>("test", new List {"test"}) + }); - var fAndRs = new List(); - fAndRs.Add(new HeaderFindAndReplace("test", "test", "chiken", 0)); + var fAndRs = new List {new HeaderFindAndReplace("test", "test", "chiken", 0)}; this.Given(x => GivenTheHttpResponse(response)) .And(x => GivenTheFollowingHeaderReplacements(fAndRs)) @@ -53,8 +57,11 @@ namespace Ocelot.UnitTests.Headers [Fact] public void should_not_replace_headers() { - var response = new HttpResponseMessage(); - response.Headers.Add("test", "test"); + var response = new DownstreamResponse(new StringContent(string.Empty), HttpStatusCode.Accepted, + new List>>() + { + new KeyValuePair>("test", new List {"test"}) + }); var fAndRs = new List(); @@ -68,16 +75,21 @@ namespace Ocelot.UnitTests.Headers [Fact] public void should_replace_downstream_base_url_with_ocelot_base_url() { - var downstreamUrl = "http://downstream.com/"; + const string downstreamUrl = "http://downstream.com/"; - var request = new HttpRequestMessage(HttpMethod.Get, "http://test.com"); - request.RequestUri = new System.Uri(downstreamUrl); + var request = + new HttpRequestMessage(HttpMethod.Get, "http://test.com") {RequestUri = new System.Uri(downstreamUrl)}; - var response = new HttpResponseMessage(); - response.Headers.Add("Location", downstreamUrl); + var response = new DownstreamResponse(new StringContent(string.Empty), HttpStatusCode.Accepted, + new List>>() + { + new KeyValuePair>("Location", new List {downstreamUrl}) + }); - var fAndRs = new List(); - fAndRs.Add(new HeaderFindAndReplace("Location", "{DownstreamBaseUrl}", "http://ocelot.com/", 0)); + var fAndRs = new List + { + new HeaderFindAndReplace("Location", "{DownstreamBaseUrl}", "http://ocelot.com/", 0) + }; this.Given(x => GivenTheHttpResponse(response)) .And(x => GivenTheRequestIs(request)) @@ -90,16 +102,21 @@ namespace Ocelot.UnitTests.Headers [Fact] public void should_replace_downstream_base_url_with_ocelot_base_url_with_port() { - var downstreamUrl = "http://downstream.com/"; + const string downstreamUrl = "http://downstream.com/"; - var request = new HttpRequestMessage(HttpMethod.Get, "http://test.com"); - request.RequestUri = new System.Uri(downstreamUrl); + var request = + new HttpRequestMessage(HttpMethod.Get, "http://test.com") {RequestUri = new System.Uri(downstreamUrl)}; - var response = new HttpResponseMessage(); - response.Headers.Add("Location", downstreamUrl); + var response = new DownstreamResponse(new StringContent(string.Empty), HttpStatusCode.Accepted, + new List>>() + { + new KeyValuePair>("Location", new List {downstreamUrl}) + }); - var fAndRs = new List(); - fAndRs.Add(new HeaderFindAndReplace("Location", "{DownstreamBaseUrl}", "http://ocelot.com:123/", 0)); + var fAndRs = new List + { + new HeaderFindAndReplace("Location", "{DownstreamBaseUrl}", "http://ocelot.com:123/", 0) + }; this.Given(x => GivenTheHttpResponse(response)) .And(x => GivenTheRequestIs(request)) @@ -112,16 +129,21 @@ namespace Ocelot.UnitTests.Headers [Fact] public void should_replace_downstream_base_url_with_ocelot_base_url_and_path() { - var downstreamUrl = "http://downstream.com/test/product"; + const string downstreamUrl = "http://downstream.com/test/product"; - var request = new HttpRequestMessage(HttpMethod.Get, "http://test.com"); - request.RequestUri = new System.Uri(downstreamUrl); + var request = + new HttpRequestMessage(HttpMethod.Get, "http://test.com") {RequestUri = new System.Uri(downstreamUrl)}; - var response = new HttpResponseMessage(); - response.Headers.Add("Location", downstreamUrl); + var response = new DownstreamResponse(new StringContent(string.Empty), HttpStatusCode.Accepted, + new List>>() + { + new KeyValuePair>("Location", new List {downstreamUrl}) + }); - var fAndRs = new List(); - fAndRs.Add(new HeaderFindAndReplace("Location", "{DownstreamBaseUrl}", "http://ocelot.com/", 0)); + var fAndRs = new List + { + new HeaderFindAndReplace("Location", "{DownstreamBaseUrl}", "http://ocelot.com/", 0) + }; this.Given(x => GivenTheHttpResponse(response)) .And(x => GivenTheRequestIs(request)) @@ -134,16 +156,21 @@ namespace Ocelot.UnitTests.Headers [Fact] public void should_replace_downstream_base_url_with_ocelot_base_url_with_path_and_port() { - var downstreamUrl = "http://downstream.com/test/product"; + const string downstreamUrl = "http://downstream.com/test/product"; - var request = new HttpRequestMessage(HttpMethod.Get, "http://test.com"); - request.RequestUri = new System.Uri(downstreamUrl); + var request = + new HttpRequestMessage(HttpMethod.Get, "http://test.com") {RequestUri = new System.Uri(downstreamUrl)}; - var response = new HttpResponseMessage(); - response.Headers.Add("Location", downstreamUrl); + var response = new DownstreamResponse(new StringContent(string.Empty), HttpStatusCode.Accepted, + new List>>() + { + new KeyValuePair>("Location", new List {downstreamUrl}) + }); - var fAndRs = new List(); - fAndRs.Add(new HeaderFindAndReplace("Location", "{DownstreamBaseUrl}", "http://ocelot.com:123/", 0)); + var fAndRs = new List + { + new HeaderFindAndReplace("Location", "{DownstreamBaseUrl}", "http://ocelot.com:123/", 0) + }; this.Given(x => GivenTheHttpResponse(response)) .And(x => GivenTheRequestIs(request)) @@ -156,16 +183,21 @@ namespace Ocelot.UnitTests.Headers [Fact] public void should_replace_downstream_base_url_and_port_with_ocelot_base_url() { - var downstreamUrl = "http://downstream.com:123/test/product"; + const string downstreamUrl = "http://downstream.com:123/test/product"; - var request = new HttpRequestMessage(HttpMethod.Get, "http://test.com"); - request.RequestUri = new System.Uri(downstreamUrl); + var request = + new HttpRequestMessage(HttpMethod.Get, "http://test.com") {RequestUri = new System.Uri(downstreamUrl)}; - var response = new HttpResponseMessage(); - response.Headers.Add("Location", downstreamUrl); + var response = new DownstreamResponse(new StringContent(string.Empty), HttpStatusCode.Accepted, + new List>>() + { + new KeyValuePair>("Location", new List {downstreamUrl}) + }); - var fAndRs = new List(); - fAndRs.Add(new HeaderFindAndReplace("Location", "{DownstreamBaseUrl}", "http://ocelot.com/", 0)); + var fAndRs = new List + { + new HeaderFindAndReplace("Location", "{DownstreamBaseUrl}", "http://ocelot.com/", 0) + }; this.Given(x => GivenTheHttpResponse(response)) .And(x => GivenTheRequestIs(request)) @@ -178,16 +210,21 @@ namespace Ocelot.UnitTests.Headers [Fact] public void should_replace_downstream_base_url_and_port_with_ocelot_base_url_and_port() { - var downstreamUrl = "http://downstream.com:123/test/product"; + const string downstreamUrl = "http://downstream.com:123/test/product"; - var request = new HttpRequestMessage(HttpMethod.Get, "http://test.com"); - request.RequestUri = new System.Uri(downstreamUrl); + var request = + new HttpRequestMessage(HttpMethod.Get, "http://test.com") {RequestUri = new System.Uri(downstreamUrl)}; - var response = new HttpResponseMessage(); - response.Headers.Add("Location", downstreamUrl); + var response = new DownstreamResponse(new StringContent(string.Empty), HttpStatusCode.Accepted, + new List>>() + { + new KeyValuePair>("Location", new List {downstreamUrl}) + }); - var fAndRs = new List(); - fAndRs.Add(new HeaderFindAndReplace("Location", "{DownstreamBaseUrl}", "http://ocelot.com:321/", 0)); + var fAndRs = new List + { + new HeaderFindAndReplace("Location", "{DownstreamBaseUrl}", "http://ocelot.com:321/", 0) + }; this.Given(x => GivenTheHttpResponse(response)) .And(x => GivenTheRequestIs(request)) @@ -207,8 +244,8 @@ namespace Ocelot.UnitTests.Headers _result.ShouldBeOfType(); foreach (var f in _headerFindAndReplaces) { - _response.Headers.TryGetValues(f.Key, out var values); - values.ToList()[f.Index].ShouldBe("test"); + var values = _response.Headers.First(x => x.Key == f.Key); + values.Values.ToList()[f.Index].ShouldBe("test"); } } @@ -217,7 +254,7 @@ namespace Ocelot.UnitTests.Headers _headerFindAndReplaces = fAndRs; } - private void GivenTheHttpResponse(HttpResponseMessage response) + private void GivenTheHttpResponse(DownstreamResponse response) { _response = response; } @@ -229,17 +266,17 @@ namespace Ocelot.UnitTests.Headers private void ThenTheHeaderShouldBe(string key, string value) { - var test = _response.Headers.GetValues(key); - test.First().ShouldBe(value); + var test = _response.Headers.First(x => x.Key == key); + test.Values.First().ShouldBe(value); } - private void ThenTheHeadersAreReplaced() + private void ThenTheHeadersAreReplaced() { _result.ShouldBeOfType(); foreach (var f in _headerFindAndReplaces) { - _response.Headers.TryGetValues(f.Key, out var values); - values.ToList()[f.Index].ShouldBe(f.Replace); + var values = _response.Headers.First(x => x.Key == f.Key); + values.Values.ToList()[f.Index].ShouldBe(f.Replace); } } } diff --git a/test/Ocelot.UnitTests/Headers/RemoveHeadersTests.cs b/test/Ocelot.UnitTests/Headers/RemoveHeadersTests.cs index 35c792aa..9818c944 100644 --- a/test/Ocelot.UnitTests/Headers/RemoveHeadersTests.cs +++ b/test/Ocelot.UnitTests/Headers/RemoveHeadersTests.cs @@ -1,5 +1,5 @@ -using System.Net.Http; -using System.Net.Http.Headers; +using System.Collections.Generic; +using Ocelot.Middleware; using Ocelot.Responses; using Shouldly; using TestStack.BDDfy; @@ -9,7 +9,7 @@ namespace Ocelot.UnitTests.Headers { public class RemoveHeadersTests { - private HttpResponseHeaders _headers; + private List
_headers; private readonly Ocelot.Headers.RemoveOutputHeaders _removeOutputHeaders; private Response _result; @@ -21,18 +21,18 @@ namespace Ocelot.UnitTests.Headers [Fact] public void should_remove_header() { - var httpResponse = new HttpResponseMessage() + var headers = new List
() { - Headers = {{ "Transfer-Encoding", "chunked"}} + new Header("Transfer-Encoding", new List {"chunked"}) }; - this.Given(x => x.GivenAHttpContext(httpResponse.Headers)) + this.Given(x => x.GivenAHttpContext(headers)) .When(x => x.WhenIRemoveTheHeaders()) .Then(x => x.TheHeaderIsNoLongerInTheContext()) .BDDfy(); } - private void GivenAHttpContext(HttpResponseHeaders headers) + private void GivenAHttpContext(List
headers) { _headers = headers; } diff --git a/test/Ocelot.UnitTests/Infrastructure/HttpDataRepositoryTests.cs b/test/Ocelot.UnitTests/Infrastructure/HttpDataRepositoryTests.cs index c9f66009..555cfe32 100644 --- a/test/Ocelot.UnitTests/Infrastructure/HttpDataRepositoryTests.cs +++ b/test/Ocelot.UnitTests/Infrastructure/HttpDataRepositoryTests.cs @@ -9,9 +9,9 @@ namespace Ocelot.UnitTests.Infrastructure { public class HttpDataRepositoryTests { - private HttpContext _httpContext; + private readonly HttpContext _httpContext; private IHttpContextAccessor _httpContextAccessor; - private HttpDataRepository _httpDataRepository; + private readonly HttpDataRepository _httpDataRepository; private object _result; public HttpDataRepositoryTests() diff --git a/test/Ocelot.UnitTests/Infrastructure/PlaceholdersTests.cs b/test/Ocelot.UnitTests/Infrastructure/PlaceholdersTests.cs index 51cbffef..729c4329 100644 --- a/test/Ocelot.UnitTests/Infrastructure/PlaceholdersTests.cs +++ b/test/Ocelot.UnitTests/Infrastructure/PlaceholdersTests.cs @@ -51,7 +51,6 @@ namespace Ocelot.UnitTests.Infrastructure result.Data.ShouldBe("http://www.bbc.co.uk/"); } - [Fact] public void should_return_downstream_base_url_when_port_is_80_or_443() { @@ -80,4 +79,4 @@ namespace Ocelot.UnitTests.Infrastructure result.Data.ShouldBe(traceId); } } -} \ No newline at end of file +} diff --git a/test/Ocelot.UnitTests/Logging/AspDotNetLoggerTests.cs b/test/Ocelot.UnitTests/Logging/AspDotNetLoggerTests.cs index 8487cf8d..8a1fa891 100644 --- a/test/Ocelot.UnitTests/Logging/AspDotNetLoggerTests.cs +++ b/test/Ocelot.UnitTests/Logging/AspDotNetLoggerTests.cs @@ -53,7 +53,6 @@ namespace Ocelot.UnitTests.Logging [Fact] public void should_log_error() { - _logger.LogError($"a message from {_a} to {_b}", _ex); ThenLevelIsLogged("requestId: no request id, previousRequestId: no previous request id, message: a message from tom to laura, exception: System.Exception: oh no", LogLevel.Error); diff --git a/test/Ocelot.UnitTests/Middleware/DefinedAggregatorProviderTests.cs b/test/Ocelot.UnitTests/Middleware/DefinedAggregatorProviderTests.cs new file mode 100644 index 00000000..1874e94b --- /dev/null +++ b/test/Ocelot.UnitTests/Middleware/DefinedAggregatorProviderTests.cs @@ -0,0 +1,86 @@ +using Microsoft.Extensions.DependencyInjection; +using Ocelot.Configuration; +using Ocelot.Configuration.Builder; +using Ocelot.Middleware.Multiplexer; +using Ocelot.Responses; +using Shouldly; +using TestStack.BDDfy; +using Xunit; +using static Ocelot.UnitTests.Middleware.UserDefinedResponseAggregatorTests; + +namespace Ocelot.UnitTests.Middleware +{ + public class DefinedAggregatorProviderTests + { + private ServiceLocatorDefinedAggregatorProvider _provider; + private Response _aggregator; + private ReRoute _reRoute; + + [Fact] + public void should_find_aggregator() + { + var reRoute = new ReRouteBuilder() + .WithAggregator("TestDefinedAggregator") + .Build(); + + this.Given(_ => GivenDefinedAggregator()) + .And(_ => GivenReRoute(reRoute)) + .When(_ => WhenIGet()) + .Then(_ => ThenTheAggregatorIsReturned()) + .BDDfy(); + } + + [Fact] + public void should_not_find_aggregator() + { + var reRoute = new ReRouteBuilder() + .WithAggregator("TestDefinedAggregator") + .Build(); + + this.Given(_ => GivenNoDefinedAggregator()) + .And(_ => GivenReRoute(reRoute)) + .When(_ => WhenIGet()) + .Then(_ => ThenAnErrorIsReturned()) + .BDDfy(); + } + + private void GivenDefinedAggregator() + { + var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton(); + var services = serviceCollection.BuildServiceProvider(); + _provider = new ServiceLocatorDefinedAggregatorProvider(services); + } + + private void ThenTheAggregatorIsReturned() + { + _aggregator.Data.ShouldNotBeNull(); + _aggregator.Data.ShouldBeOfType(); + _aggregator.IsError.ShouldBeFalse(); + } + + private void GivenNoDefinedAggregator() + { + var serviceCollection = new ServiceCollection(); + var services = serviceCollection.BuildServiceProvider(); + _provider = new ServiceLocatorDefinedAggregatorProvider(services); + } + + private void GivenReRoute(ReRoute reRoute) + { + _reRoute = reRoute; + } + + private void WhenIGet() + { + _aggregator = _provider.Get(_reRoute); + } + + private void ThenAnErrorIsReturned() + { + _aggregator.IsError.ShouldBeTrue(); + _aggregator.Errors[0].Message.ShouldBe("Could not find Aggregator: TestDefinedAggregator"); + _aggregator.Errors[0].ShouldBeOfType(); + } + } +} diff --git a/test/Ocelot.UnitTests/Middleware/MultiplexerTests.cs b/test/Ocelot.UnitTests/Middleware/MultiplexerTests.cs index 1584c987..787bfefa 100644 --- a/test/Ocelot.UnitTests/Middleware/MultiplexerTests.cs +++ b/test/Ocelot.UnitTests/Middleware/MultiplexerTests.cs @@ -19,13 +19,16 @@ namespace Ocelot.UnitTests.Middleware private readonly OcelotRequestDelegate _pipeline; private int _count; private Mock _aggregator; + private Mock _factory; public MultiplexerTests() { + _factory = new Mock(); _aggregator = new Mock(); _context = new DownstreamContext(new DefaultHttpContext()); _pipeline = context => Task.FromResult(_count++); - _multiplexer = new Multiplexer(_aggregator.Object); + _factory.Setup(x => x.Get(It.IsAny())).Returns(_aggregator.Object); + _multiplexer = new Multiplexer(_factory.Object); } [Fact] diff --git a/test/Ocelot.UnitTests/Middleware/OcelotMiddlewareTests.cs b/test/Ocelot.UnitTests/Middleware/OcelotMiddlewareTests.cs index fd4e018c..9e13ed1a 100644 --- a/test/Ocelot.UnitTests/Middleware/OcelotMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Middleware/OcelotMiddlewareTests.cs @@ -62,7 +62,6 @@ namespace Ocelot.UnitTests.Middleware { _errors.Add(error); } - } public class FakeMiddleware : OcelotMiddleware @@ -72,4 +71,4 @@ namespace Ocelot.UnitTests.Middleware { } } -} \ No newline at end of file +} diff --git a/test/Ocelot.UnitTests/Middleware/ResponseAggregatorFactoryTests.cs b/test/Ocelot.UnitTests/Middleware/ResponseAggregatorFactoryTests.cs new file mode 100644 index 00000000..b4413b45 --- /dev/null +++ b/test/Ocelot.UnitTests/Middleware/ResponseAggregatorFactoryTests.cs @@ -0,0 +1,64 @@ +namespace Ocelot.UnitTests.Middleware +{ + using Moq; + using Ocelot.Configuration.Builder; + using Ocelot.Middleware.Multiplexer; + using Shouldly; + using Xunit; + using Ocelot.Configuration; + using TestStack.BDDfy; + + public class ResponseAggregatorFactoryTests + { + private readonly InMemoryResponseAggregatorFactory _factory; + private Mock _provider; + private ReRoute _reRoute; + private IResponseAggregator _aggregator; + + public ResponseAggregatorFactoryTests() + { + _provider = new Mock(); + _factory = new InMemoryResponseAggregatorFactory(_provider.Object); + } + + [Fact] + public void should_return_simple_json_aggregator() + { + var reRoute = new ReRouteBuilder() + .Build(); + + this.Given(_ => GivenReRoute(reRoute)) + .When(_ => WhenIGet()) + .Then(_ => ThenTheAggregatorIs()) + .BDDfy(); + } + + [Fact] + public void should_return_user_defined_aggregator() + { + var reRoute = new ReRouteBuilder() + .WithAggregator("doesntmatter") + .Build(); + + this.Given(_ => GivenReRoute(reRoute)) + .When(_ => WhenIGet()) + .Then(_ => ThenTheAggregatorIs()) + .BDDfy(); + } + + private void GivenReRoute(ReRoute reRoute) + { + _reRoute = reRoute; + } + + private void WhenIGet() + { + _aggregator = _factory.Get(_reRoute); + } + + private void ThenTheAggregatorIs() + { + _aggregator.ShouldBeOfType(); + } + } +} diff --git a/test/Ocelot.UnitTests/Middleware/SimpleJsonResponseAggregatorTests.cs b/test/Ocelot.UnitTests/Middleware/SimpleJsonResponseAggregatorTests.cs index f0a2b224..fb2a1c98 100644 --- a/test/Ocelot.UnitTests/Middleware/SimpleJsonResponseAggregatorTests.cs +++ b/test/Ocelot.UnitTests/Middleware/SimpleJsonResponseAggregatorTests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Text; +using Castle.Components.DictionaryAdapter; using Microsoft.AspNetCore.Http; using Ocelot.Configuration; using Ocelot.Configuration.Builder; @@ -29,40 +30,6 @@ namespace Ocelot.UnitTests.Middleware _aggregator = new SimpleJsonResponseAggregator(); } - [Fact] - public void should_map_all_downstream_to_upstream_when_not_aggregate() - { - var billDownstreamReRoute = new DownstreamReRouteBuilder().WithKey("Bill").Build(); - - var downstreamReRoutes = new List - { - billDownstreamReRoute, - }; - - var reRoute = new ReRouteBuilder() - .WithDownstreamReRoutes(downstreamReRoutes) - .Build(); - - var billDownstreamContext = new DownstreamContext(new DefaultHttpContext()) - { - DownstreamResponse = - new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent("Bill says hi") }, - DownstreamReRoute = billDownstreamReRoute, - Errors = new List { new AnyError() }, - DownstreamRequest = new DownstreamRequest(new HttpRequestMessage(HttpMethod.Get, new Uri("http://www.bbc.co.uk"))), - }; - - var downstreamContexts = new List { billDownstreamContext }; - - this.Given(x => GivenTheUpstreamContext(new DownstreamContext(new DefaultHttpContext()))) - .And(x => GivenTheReRoute(reRoute)) - .And(x => GivenTheDownstreamContext(downstreamContexts)) - .When(x => WhenIAggregate()) - .Then(x => ThenTheContentIs("Bill says hi")) - .And(x => ThenTheUpstreamContextIsMappedForNonAggregate()) - .BDDfy(); - } - [Fact] public void should_aggregate_n_responses_and_set_response_content_on_upstream_context() { @@ -82,15 +49,13 @@ namespace Ocelot.UnitTests.Middleware var billDownstreamContext = new DownstreamContext(new DefaultHttpContext()) { - DownstreamResponse = - new HttpResponseMessage(HttpStatusCode.OK) {Content = new StringContent("Bill says hi")}, + DownstreamResponse = new DownstreamResponse(new StringContent("Bill says hi"), HttpStatusCode.OK, new EditableList>>()), DownstreamReRoute = billDownstreamReRoute }; var georgeDownstreamContext = new DownstreamContext(new DefaultHttpContext()) { - DownstreamResponse = - new HttpResponseMessage(HttpStatusCode.OK) {Content = new StringContent("George says hi")}, + DownstreamResponse = new DownstreamResponse(new StringContent("George says hi"), HttpStatusCode.OK, new List>>()), DownstreamReRoute = georgeDownstreamReRoute }; @@ -126,19 +91,18 @@ namespace Ocelot.UnitTests.Middleware var billDownstreamContext = new DownstreamContext(new DefaultHttpContext()) { - DownstreamResponse = - new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent("Bill says hi") }, + DownstreamResponse = new DownstreamResponse(new StringContent("Bill says hi"), HttpStatusCode.OK, new List>>()), DownstreamReRoute = billDownstreamReRoute }; var georgeDownstreamContext = new DownstreamContext(new DefaultHttpContext()) { - DownstreamResponse = - new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent("Error") }, + DownstreamResponse = new DownstreamResponse(new StringContent("Error"), HttpStatusCode.OK, new List>>()), DownstreamReRoute = georgeDownstreamReRoute, - Errors = new List() { new AnyError() } }; + georgeDownstreamContext.Errors.Add(new AnyError()); + var downstreamContexts = new List { billDownstreamContext, georgeDownstreamContext }; var expected = "Error"; diff --git a/test/Ocelot.UnitTests/Middleware/UserDefinedResponseAggregatorTests.cs b/test/Ocelot.UnitTests/Middleware/UserDefinedResponseAggregatorTests.cs new file mode 100644 index 00000000..2b5bd552 --- /dev/null +++ b/test/Ocelot.UnitTests/Middleware/UserDefinedResponseAggregatorTests.cs @@ -0,0 +1,153 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Moq; +using Ocelot.Configuration; +using Ocelot.Configuration.Builder; +using Ocelot.Middleware; +using Ocelot.Middleware.Multiplexer; +using Ocelot.Responses; +using Ocelot.UnitTests.Responder; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Middleware +{ + public class UserDefinedResponseAggregatorTests + { + private readonly UserDefinedResponseAggregator _aggregator; + private readonly Mock _provider; + private ReRoute _reRoute; + private List _contexts; + private DownstreamContext _context; + + public UserDefinedResponseAggregatorTests() + { + _provider = new Mock(); + _aggregator = new UserDefinedResponseAggregator(_provider.Object); + } + + [Fact] + public void should_call_aggregator() + { + var reRoute = new ReRouteBuilder().Build(); + + var context = new DownstreamContext(new DefaultHttpContext()); + + var contexts = new List + { + new DownstreamContext(new DefaultHttpContext()) + { + DownstreamResponse = new DownstreamResponse(new StringContent("Tom"), HttpStatusCode.OK, new List>>()) + }, + new DownstreamContext(new DefaultHttpContext()) + { + DownstreamResponse = new DownstreamResponse(new StringContent("Laura"), HttpStatusCode.OK, new List>>()) + } + }; + + this.Given(_ => GivenTheProviderReturnsAggregator()) + .And(_ => GivenReRoute(reRoute)) + .And(_ => GivenContexts(contexts)) + .And(_ => GivenContext(context)) + .When(_ => WhenIAggregate()) + .Then(_ => ThenTheProviderIsCalled()) + .And(_ => ThenTheContentIsCorrect()) + .BDDfy(); + } + + [Fact] + public void should_not_find_aggregator() + { + var reRoute = new ReRouteBuilder().Build(); + + var context = new DownstreamContext(new DefaultHttpContext()); + + var contexts = new List + { + new DownstreamContext(new DefaultHttpContext()) + { + DownstreamResponse = new DownstreamResponse(new StringContent("Tom"), HttpStatusCode.OK, new List>>()) + }, + new DownstreamContext(new DefaultHttpContext()) + { + DownstreamResponse = new DownstreamResponse(new StringContent("Laura"), HttpStatusCode.OK, new List>>()) + } + }; + + this.Given(_ => GivenTheProviderReturnsError()) + .And(_ => GivenReRoute(reRoute)) + .And(_ => GivenContexts(contexts)) + .And(_ => GivenContext(context)) + .When(_ => WhenIAggregate()) + .Then(_ => ThenTheProviderIsCalled()) + .And(_ => ThenTheErrorIsReturned()) + .BDDfy(); + } + + private void ThenTheErrorIsReturned() + { + _context.IsError.ShouldBeTrue(); + _context.Errors.Count.ShouldBe(1); + } + + private void GivenTheProviderReturnsError() + { + _provider.Setup(x => x.Get(It.IsAny())).Returns(new ErrorResponse(new AnyError())); + } + + private async Task ThenTheContentIsCorrect() + { + var content = await _context.DownstreamResponse.Content.ReadAsStringAsync(); + content.ShouldBe("Tom, Laura"); + } + + private void ThenTheProviderIsCalled() + { + _provider.Verify(x => x.Get(_reRoute), Times.Once); + } + + private void GivenContext(DownstreamContext context) + { + _context = context; + } + + private void GivenContexts(List contexts) + { + _contexts = contexts; + } + + private async Task WhenIAggregate() + { + await _aggregator.Aggregate(_reRoute, _context, _contexts); + } + + private void GivenTheProviderReturnsAggregator() + { + var aggregator = new TestDefinedAggregator(); + _provider.Setup(x => x.Get(It.IsAny())).Returns(new OkResponse(aggregator)); + } + + private void GivenReRoute(ReRoute reRoute) + { + _reRoute = reRoute; + } + + public class TestDefinedAggregator : IDefinedAggregator + { + public async Task Aggregate(List responses) + { + var tom = await responses[0].Content.ReadAsStringAsync(); + var laura = await responses[1].Content.ReadAsStringAsync(); + var content = $"{tom}, {laura}"; + var headers = responses.SelectMany(x => x.Headers).ToList(); + return new DownstreamResponse(new StringContent(content), HttpStatusCode.OK, headers); + } + } + } +} diff --git a/test/Ocelot.UnitTests/QueryStrings/AddQueriesToRequestTests.cs b/test/Ocelot.UnitTests/QueryStrings/AddQueriesToRequestTests.cs index 83c486f2..8672ebf5 100644 --- a/test/Ocelot.UnitTests/QueryStrings/AddQueriesToRequestTests.cs +++ b/test/Ocelot.UnitTests/QueryStrings/AddQueriesToRequestTests.cs @@ -153,8 +153,6 @@ namespace Ocelot.UnitTests.QueryStrings .AddQueryString(_downstreamRequest.ToHttpRequestMessage().RequestUri.OriginalString, key, value); _request.RequestUri = new Uri(newUri); - //todo - might not need to instanciate - _downstreamRequest = new DownstreamRequest(_request); } private void GivenTheClaimParserReturns(Response claimValue) diff --git a/test/Ocelot.UnitTests/Requester/HttpClientBuilderTests.cs b/test/Ocelot.UnitTests/Requester/HttpClientBuilderTests.cs index c5895c1d..45855f65 100644 --- a/test/Ocelot.UnitTests/Requester/HttpClientBuilderTests.cs +++ b/test/Ocelot.UnitTests/Requester/HttpClientBuilderTests.cs @@ -140,25 +140,28 @@ namespace Ocelot.UnitTests.Requester .UseIISIntegration() .Configure(app => { - app.Run(async context => + app.Run(context => { if (_count == 0) { context.Response.Cookies.Append("test", "0"); context.Response.StatusCode = 200; _count++; - return; + return Task.CompletedTask; } + if (_count == 1) { if (context.Request.Cookies.TryGetValue("test", out var cookieValue) || context.Request.Headers.TryGetValue("Set-Cookie", out var headerValue)) { context.Response.StatusCode = 200; - return; + return Task.CompletedTask; } context.Response.StatusCode = 500; } + + return Task.CompletedTask; }); }) .Build(); @@ -200,6 +203,7 @@ namespace Ocelot.UnitTests.Requester .Setup(x => x.Get(It.IsAny())) .Returns(new OkResponse>>(handlers)); } + private void GivenTheFactoryReturnsNothing() { var handlers = new List>(); diff --git a/test/Ocelot.UnitTests/Requester/HttpRequesterMiddlewareTests.cs b/test/Ocelot.UnitTests/Requester/HttpRequesterMiddlewareTests.cs index f4facf5e..910b3be8 100644 --- a/test/Ocelot.UnitTests/Requester/HttpRequesterMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Requester/HttpRequesterMiddlewareTests.cs @@ -1,6 +1,3 @@ -using Ocelot.Configuration.Builder; -using Ocelot.Middleware; - namespace Ocelot.UnitTests.Requester { using Microsoft.AspNetCore.Http; @@ -14,11 +11,16 @@ namespace Ocelot.UnitTests.Requester using Xunit; using Shouldly; using System.Threading.Tasks; + using Ocelot.Configuration.Builder; + using Ocelot.Middleware; + using System; + using System.Linq; + using Ocelot.UnitTests.Responder; public class HttpRequesterMiddlewareTests { private readonly Mock _requester; - private OkResponse _response; + private Response _response; private Mock _loggerFactory; private Mock _logger; private readonly HttpRequesterMiddleware _middleware; @@ -39,12 +41,27 @@ namespace Ocelot.UnitTests.Requester public void should_call_services_correctly() { this.Given(x => x.GivenTheRequestIs()) - .And(x => x.GivenTheRequesterReturns(new HttpResponseMessage())) + .And(x => x.GivenTheRequesterReturns(new OkResponse(new HttpResponseMessage()))) .When(x => x.WhenICallTheMiddleware()) - .Then(x => x.ThenTheScopedRepoIsCalledCorrectly()) + .Then(x => x.ThenTheDownstreamResponseIsSet()) .BDDfy(); } + [Fact] + public void should_set_error() + { + this.Given(x => x.GivenTheRequestIs()) + .And(x => x.GivenTheRequesterReturns(new ErrorResponse(new AnyError()))) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenTheErrorIsSet()) + .BDDfy(); + } + + private void ThenTheErrorIsSet() + { + _downstreamContext.IsError.ShouldBeTrue(); + } + private void WhenICallTheMiddleware() { _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); @@ -52,21 +69,34 @@ namespace Ocelot.UnitTests.Requester private void GivenTheRequestIs() { - _downstreamContext = new DownstreamContext(new DefaultHttpContext()); - _downstreamContext.DownstreamReRoute = new DownstreamReRouteBuilder().Build(); + _downstreamContext = + new DownstreamContext(new DefaultHttpContext()) + { + DownstreamReRoute = new DownstreamReRouteBuilder().Build() + }; } - private void GivenTheRequesterReturns(HttpResponseMessage response) + private void GivenTheRequesterReturns(Response response) { - _response = new OkResponse(response); + _response = response; + _requester .Setup(x => x.GetResponse(It.IsAny())) .ReturnsAsync(_response); } - private void ThenTheScopedRepoIsCalledCorrectly() + private void ThenTheDownstreamResponseIsSet() { - _downstreamContext.DownstreamResponse.ShouldBe(_response.Data); + foreach (var httpResponseHeader in _response.Data.Headers) + { + if (_downstreamContext.DownstreamResponse.Headers.Any(x => x.Key == httpResponseHeader.Key)) + { + throw new Exception("Header in response not in downstreamresponse headers"); + } + } + + _downstreamContext.DownstreamResponse.Content.ShouldBe(_response.Data.Content); + _downstreamContext.DownstreamResponse.StatusCode.ShouldBe(_response.Data.StatusCode); } } } diff --git a/test/Ocelot.UnitTests/Responder/ErrorsToHttpStatusCodeMapperTests.cs b/test/Ocelot.UnitTests/Responder/ErrorsToHttpStatusCodeMapperTests.cs index 8ee2be1f..27417907 100644 --- a/test/Ocelot.UnitTests/Responder/ErrorsToHttpStatusCodeMapperTests.cs +++ b/test/Ocelot.UnitTests/Responder/ErrorsToHttpStatusCodeMapperTests.cs @@ -120,7 +120,7 @@ namespace Ocelot.UnitTests.Responder // If this test fails then it's because the number of error codes has changed. // You should make the appropriate changes to the test cases here to ensure // they cover all the error codes, and then modify this assertion. - Enum.GetNames(typeof(OcelotErrorCode)).Length.ShouldBe(34, "Looks like the number of error codes has changed. Do you need to modify ErrorsToHttpStatusCodeMapper?"); + Enum.GetNames(typeof(OcelotErrorCode)).Length.ShouldBe(35, "Looks like the number of error codes has changed. Do you need to modify ErrorsToHttpStatusCodeMapper?"); } private void ShouldMapErrorToStatusCode(OcelotErrorCode errorCode, HttpStatusCode expectedHttpStatusCode) diff --git a/test/Ocelot.UnitTests/Responder/HttpContextResponderTests.cs b/test/Ocelot.UnitTests/Responder/HttpContextResponderTests.cs index ae465774..6ff956d2 100644 --- a/test/Ocelot.UnitTests/Responder/HttpContextResponderTests.cs +++ b/test/Ocelot.UnitTests/Responder/HttpContextResponderTests.cs @@ -1,10 +1,11 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; +using System.Net; using System.Net.Http; -using System.Text; using Microsoft.AspNetCore.Http; using Ocelot.Headers; +using Ocelot.Middleware; +using Ocelot.Middleware.Multiplexer; using Ocelot.Responder; using Shouldly; using Xunit; @@ -26,9 +27,13 @@ namespace Ocelot.UnitTests.Responder public void should_remove_transfer_encoding_header() { var httpContext = new DefaultHttpContext(); - var httpResponseMessage = new HttpResponseMessage {Content = new StringContent("")}; - httpResponseMessage.Headers.Add("Transfer-Encoding", "woop"); - _responder.SetResponseOnHttpContext(httpContext, httpResponseMessage).GetAwaiter().GetResult(); + var response = new DownstreamResponse(new StringContent(""), HttpStatusCode.OK, + new List>> + { + new KeyValuePair>("Transfer-Encoding", new List {"woop"}) + }); + + _responder.SetResponseOnHttpContext(httpContext, response).GetAwaiter().GetResult(); var header = httpContext.Response.Headers["Transfer-Encoding"]; header.ShouldBeEmpty(); } @@ -37,8 +42,10 @@ namespace Ocelot.UnitTests.Responder public void should_have_content_length() { var httpContext = new DefaultHttpContext(); - var httpResponseMessage = new HttpResponseMessage { Content = new StringContent("test") }; - _responder.SetResponseOnHttpContext(httpContext, httpResponseMessage).GetAwaiter().GetResult(); + var response = new DownstreamResponse(new StringContent("test"), HttpStatusCode.OK, + new List>>()); + + _responder.SetResponseOnHttpContext(httpContext, response).GetAwaiter().GetResult(); var header = httpContext.Response.Headers["Content-Length"]; header.First().ShouldBe("4"); } @@ -47,9 +54,13 @@ namespace Ocelot.UnitTests.Responder public void should_add_header() { var httpContext = new DefaultHttpContext(); - var httpResponseMessage = new HttpResponseMessage { Content = new StringContent("test") }; - httpResponseMessage.Headers.Add("test", "test"); - _responder.SetResponseOnHttpContext(httpContext, httpResponseMessage).GetAwaiter().GetResult(); + var response = new DownstreamResponse(new StringContent(""), HttpStatusCode.OK, + new List>> + { + new KeyValuePair>("test", new List {"test"}) + }); + + _responder.SetResponseOnHttpContext(httpContext, response).GetAwaiter().GetResult(); var header = httpContext.Response.Headers["test"]; header.First().ShouldBe("test"); } diff --git a/test/Ocelot.UnitTests/Responder/ResponderMiddlewareTests.cs b/test/Ocelot.UnitTests/Responder/ResponderMiddlewareTests.cs index 54a77f4d..79262997 100644 --- a/test/Ocelot.UnitTests/Responder/ResponderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Responder/ResponderMiddlewareTests.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using Ocelot.Middleware; +using Ocelot.Middleware.Multiplexer; namespace Ocelot.UnitTests.Responder { @@ -40,8 +41,7 @@ namespace Ocelot.UnitTests.Responder [Fact] public void should_not_return_any_errors() { - this.Given(x => x.GivenTheHttpResponseMessageIs(new HttpResponseMessage())) - .And(x => x.GivenThereAreNoPipelineErrors()) + this.Given(x => x.GivenTheHttpResponseMessageIs(new DownstreamResponse(new HttpResponseMessage()))) .When(x => x.WhenICallTheMiddleware()) .Then(x => x.ThenThereAreNoErrors()) .BDDfy(); @@ -50,7 +50,7 @@ namespace Ocelot.UnitTests.Responder [Fact] public void should_return_any_errors() { - this.Given(x => x.GivenTheHttpResponseMessageIs(new HttpResponseMessage())) + this.Given(x => x.GivenTheHttpResponseMessageIs(new DownstreamResponse(new HttpResponseMessage()))) .And(x => x.GivenThereArePipelineErrors(new UnableToFindDownstreamRouteError("/path", "GET"))) .When(x => x.WhenICallTheMiddleware()) .Then(x => x.ThenThereAreNoErrors()) @@ -62,16 +62,11 @@ namespace Ocelot.UnitTests.Responder _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); } - private void GivenTheHttpResponseMessageIs(HttpResponseMessage response) + private void GivenTheHttpResponseMessageIs(DownstreamResponse response) { _downstreamContext.DownstreamResponse = response; } - private void GivenThereAreNoPipelineErrors() - { - _downstreamContext.Errors = new List(); - } - private void ThenThereAreNoErrors() { //todo a better assert? @@ -79,7 +74,7 @@ namespace Ocelot.UnitTests.Responder private void GivenThereArePipelineErrors(Error error) { - _downstreamContext.Errors = new List(){error}; + _downstreamContext.Errors.Add(error); } } }