From faaabbe7a75f1006ea055243c4fd63c868aae137 Mon Sep 17 00:00:00 2001 From: Abolfazl Date: Mon, 4 Feb 2019 15:51:50 +0330 Subject: [PATCH] fix issue #661 for Advanced aggregations (#704) * Add Advanced Aggregation Feature * fix overwrite error * distinct data for better performance * remove constructor parameter * fix tests issue * fix tests * fix tests issue * Add UnitTest and AcceptanceTest * fix responseKeys typo * Update SimpleJsonResponseAggregator.cs * change port --- .../Configuration/Builder/ReRouteBuilder.cs | 14 ++- .../Creator/AggregatesCreator.cs | 17 ++- .../File/AggregateReRouteConfig.cs | 13 ++ .../File/FileAggregateReRoute.cs | 3 +- src/Ocelot/Configuration/ReRoute.cs | 8 +- .../InMemoryResponseAggregatorFactory.cs | 6 +- .../Middleware/Multiplexer/Multiplexer.cs | 114 ++++++++++++++--- .../SimpleJsonResponseAggregator.cs | 62 ++++++++-- test/Ocelot.AcceptanceTests/AggregateTests.cs | 115 ++++++++++++++++-- .../ResponseAggregatorFactoryTests.cs | 3 +- .../SimpleJsonResponseAggregatorTests.cs | 56 ++++++++- 11 files changed, 352 insertions(+), 59 deletions(-) create mode 100644 src/Ocelot/Configuration/File/AggregateReRouteConfig.cs diff --git a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs index 824fe09d..dae8c11e 100644 --- a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs +++ b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs @@ -4,18 +4,21 @@ using System.Net.Http; using Ocelot.Values; using System.Linq; - + using Ocelot.Configuration.File; + public class ReRouteBuilder { private UpstreamPathTemplate _upstreamTemplatePattern; private List _upstreamHttpMethod; private string _upstreamHost; private List _downstreamReRoutes; + private List _downstreamReRoutesConfig; private string _aggregator; public ReRouteBuilder() { _downstreamReRoutes = new List(); + _downstreamReRoutesConfig = new List(); } public ReRouteBuilder WithDownstreamReRoute(DownstreamReRoute value) @@ -48,6 +51,12 @@ return this; } + public ReRouteBuilder WithAggregateReRouteConfig(List aggregateReRouteConfigs) + { + _downstreamReRoutesConfig = aggregateReRouteConfigs; + return this; + } + public ReRouteBuilder WithAggregator(string aggregator) { _aggregator = aggregator; @@ -57,7 +66,8 @@ public ReRoute Build() { return new ReRoute( - _downstreamReRoutes, + _downstreamReRoutes, + _downstreamReRoutesConfig, _upstreamHttpMethod, _upstreamTemplatePattern, _upstreamHost, diff --git a/src/Ocelot/Configuration/Creator/AggregatesCreator.cs b/src/Ocelot/Configuration/Creator/AggregatesCreator.cs index ed43f598..9acef561 100644 --- a/src/Ocelot/Configuration/Creator/AggregatesCreator.cs +++ b/src/Ocelot/Configuration/Creator/AggregatesCreator.cs @@ -24,14 +24,18 @@ namespace Ocelot.Configuration.Creator private ReRoute SetUpAggregateReRoute(IEnumerable reRoutes, FileAggregateReRoute aggregateReRoute, FileGlobalConfiguration globalConfiguration) { - var applicableReRoutes = reRoutes - .SelectMany(x => x.DownstreamReRoute) - .Where(r => aggregateReRoute.ReRouteKeys.Contains(r.Key)) - .ToList(); + var applicableReRoutes = new List(); + var allReRoutes = reRoutes.SelectMany(x => x.DownstreamReRoute); - if (applicableReRoutes.Count != aggregateReRoute.ReRouteKeys.Count) + foreach (var reRouteKey in aggregateReRoute.ReRouteKeys) { - return null; + var selec = allReRoutes.FirstOrDefault(q => q.Key == reRouteKey); + if (selec == null) + { + return null; + } + + applicableReRoutes.Add(selec); } var upstreamTemplatePattern = _creator.Create(aggregateReRoute); @@ -40,6 +44,7 @@ namespace Ocelot.Configuration.Creator .WithUpstreamHttpMethod(aggregateReRoute.UpstreamHttpMethod) .WithUpstreamPathTemplate(upstreamTemplatePattern) .WithDownstreamReRoutes(applicableReRoutes) + .WithAggregateReRouteConfig(aggregateReRoute.ReRouteKeysConfig) .WithUpstreamHost(aggregateReRoute.UpstreamHost) .WithAggregator(aggregateReRoute.Aggregator) .Build(); diff --git a/src/Ocelot/Configuration/File/AggregateReRouteConfig.cs b/src/Ocelot/Configuration/File/AggregateReRouteConfig.cs new file mode 100644 index 00000000..8a155c1b --- /dev/null +++ b/src/Ocelot/Configuration/File/AggregateReRouteConfig.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Ocelot.Configuration.File +{ + public class AggregateReRouteConfig + { + public string ReRouteKey { get; set; } + public string Parameter { get; set; } + public string JsonPath { get; set; } + } +} diff --git a/src/Ocelot/Configuration/File/FileAggregateReRoute.cs b/src/Ocelot/Configuration/File/FileAggregateReRoute.cs index c862094a..5c3b551c 100644 --- a/src/Ocelot/Configuration/File/FileAggregateReRoute.cs +++ b/src/Ocelot/Configuration/File/FileAggregateReRoute.cs @@ -5,6 +5,7 @@ namespace Ocelot.Configuration.File public class FileAggregateReRoute : IReRoute { public List ReRouteKeys { get;set; } + public List ReRouteKeysConfig { get;set; } public string UpstreamPathTemplate { get;set; } public string UpstreamHost { get; set; } public bool ReRouteIsCaseSensitive { get; set; } @@ -17,5 +18,5 @@ namespace Ocelot.Configuration.File } public int Priority {get;set;} = 1; - } + } } diff --git a/src/Ocelot/Configuration/ReRoute.cs b/src/Ocelot/Configuration/ReRoute.cs index f1faf975..31392770 100644 --- a/src/Ocelot/Configuration/ReRoute.cs +++ b/src/Ocelot/Configuration/ReRoute.cs @@ -2,11 +2,13 @@ { using System.Collections.Generic; using System.Net.Http; + using Ocelot.Configuration.File; using Ocelot.Values; - + public class ReRoute { - public ReRoute(List downstreamReRoute, + public ReRoute(List downstreamReRoute, + List downstreamReRouteConfig, List upstreamHttpMethod, UpstreamPathTemplate upstreamTemplatePattern, string upstreamHost, @@ -14,6 +16,7 @@ { UpstreamHost = upstreamHost; DownstreamReRoute = downstreamReRoute; + DownstreamReRouteConfig = downstreamReRouteConfig; UpstreamHttpMethod = upstreamHttpMethod; UpstreamTemplatePattern = upstreamTemplatePattern; Aggregator = aggregator; @@ -23,6 +26,7 @@ public List UpstreamHttpMethod { get; private set; } public string UpstreamHost { get; private set; } public List DownstreamReRoute { get; private set; } + public List DownstreamReRouteConfig { get; private set; } public string Aggregator {get; private set;} } } diff --git a/src/Ocelot/Middleware/Multiplexer/InMemoryResponseAggregatorFactory.cs b/src/Ocelot/Middleware/Multiplexer/InMemoryResponseAggregatorFactory.cs index a74386db..b9721e1b 100644 --- a/src/Ocelot/Middleware/Multiplexer/InMemoryResponseAggregatorFactory.cs +++ b/src/Ocelot/Middleware/Multiplexer/InMemoryResponseAggregatorFactory.cs @@ -5,12 +5,12 @@ namespace Ocelot.Middleware.Multiplexer public class InMemoryResponseAggregatorFactory : IResponseAggregatorFactory { private readonly UserDefinedResponseAggregator _userDefined; - private readonly SimpleJsonResponseAggregator _simple; + private readonly IResponseAggregator _simple; - public InMemoryResponseAggregatorFactory(IDefinedAggregatorProvider provider) + public InMemoryResponseAggregatorFactory(IDefinedAggregatorProvider provider, IResponseAggregator responseAggregator) { _userDefined = new UserDefinedResponseAggregator(provider); - _simple = new SimpleJsonResponseAggregator(); + _simple = responseAggregator; } public IResponseAggregator Get(ReRoute reRoute) diff --git a/src/Ocelot/Middleware/Multiplexer/Multiplexer.cs b/src/Ocelot/Middleware/Multiplexer/Multiplexer.cs index 351f8471..d61678e3 100644 --- a/src/Ocelot/Middleware/Multiplexer/Multiplexer.cs +++ b/src/Ocelot/Middleware/Multiplexer/Multiplexer.cs @@ -1,7 +1,9 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Newtonsoft.Json.Linq; using Ocelot.Configuration; +using Ocelot.DownstreamRouteFinder.UrlMatcher; namespace Ocelot.Middleware.Multiplexer { @@ -16,31 +18,105 @@ namespace Ocelot.Middleware.Multiplexer public async Task Multiplex(DownstreamContext context, ReRoute reRoute, OcelotRequestDelegate next) { - var tasks = new Task[reRoute.DownstreamReRoute.Count]; - - for (var i = 0; i < reRoute.DownstreamReRoute.Count; i++) + var reRouteKeysConfigs = reRoute.DownstreamReRouteConfig; + if (reRouteKeysConfigs == null || !reRouteKeysConfigs.Any()) { - var downstreamContext = new DownstreamContext(context.HttpContext) + var tasks = new Task[reRoute.DownstreamReRoute.Count]; + + for (var i = 0; i < reRoute.DownstreamReRoute.Count; i++) + { + var downstreamContext = new DownstreamContext(context.HttpContext) + { + TemplatePlaceholderNameAndValues = context.TemplatePlaceholderNameAndValues, + Configuration = context.Configuration, + DownstreamReRoute = reRoute.DownstreamReRoute[i], + }; + + tasks[i] = Fire(downstreamContext, next); + } + + await Task.WhenAll(tasks); + + var contexts = new List(); + + foreach (var task in tasks) + { + var finished = await task; + contexts.Add(finished); + } + + await Map(reRoute, context, contexts); + } + else + { + var downstreamContextMain = new DownstreamContext(context.HttpContext) { TemplatePlaceholderNameAndValues = context.TemplatePlaceholderNameAndValues, Configuration = context.Configuration, - DownstreamReRoute = reRoute.DownstreamReRoute[i], + DownstreamReRoute = reRoute.DownstreamReRoute[0], }; + var mainResponse = await Fire(downstreamContextMain, next); - tasks[i] = Fire(downstreamContext, next); + if (reRoute.DownstreamReRoute.Count == 1) + { + MapNotAggregate(context, new List() { mainResponse }); + return; + } + + var tasks = new List>(); + if (mainResponse.DownstreamResponse == null) + { + return; + } + + var content = await mainResponse.DownstreamResponse.Content.ReadAsStringAsync(); + var jObject = Newtonsoft.Json.Linq.JToken.Parse(content); + + for (var i = 1; i < reRoute.DownstreamReRoute.Count; i++) + { + var templatePlaceholderNameAndValues = context.TemplatePlaceholderNameAndValues; + var downstreamReRoute = reRoute.DownstreamReRoute[i]; + var matchAdvancedAgg = reRouteKeysConfigs.FirstOrDefault(q => q.ReRouteKey == downstreamReRoute.Key); + if (matchAdvancedAgg != null) + { + var values = jObject.SelectTokens(matchAdvancedAgg.JsonPath).Select(s => s.ToString()).Distinct().ToList(); + + foreach (var value in values) + { + var downstreamContext = new DownstreamContext(context.HttpContext) + { + TemplatePlaceholderNameAndValues = new List(templatePlaceholderNameAndValues), + Configuration = context.Configuration, + DownstreamReRoute = downstreamReRoute, + }; + downstreamContext.TemplatePlaceholderNameAndValues.Add(new PlaceholderNameAndValue("{" + matchAdvancedAgg.Parameter + "}", value.ToString())); + tasks.Add(Fire(downstreamContext, next)); + } + } + else + { + var downstreamContext = new DownstreamContext(context.HttpContext) + { + TemplatePlaceholderNameAndValues = new List(templatePlaceholderNameAndValues), + Configuration = context.Configuration, + DownstreamReRoute = downstreamReRoute, + }; + tasks.Add(Fire(downstreamContext, next)); + } + } + + await Task.WhenAll(tasks); + + var contexts = new List() { mainResponse }; + + foreach (var task in tasks) + { + var finished = await task; + contexts.Add(finished); + } + + await Map(reRoute, context, contexts); } - - await Task.WhenAll(tasks); - - var contexts = new List(); - - foreach (var task in tasks) - { - var finished = await task; - contexts.Add(finished); - } - - await Map(reRoute, context, contexts); } private async Task Map(ReRoute reRoute, DownstreamContext context, List contexts) @@ -55,7 +131,7 @@ namespace Ocelot.Middleware.Multiplexer 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 diff --git a/src/Ocelot/Middleware/Multiplexer/SimpleJsonResponseAggregator.cs b/src/Ocelot/Middleware/Multiplexer/SimpleJsonResponseAggregator.cs index ef2a00f5..d505e7b8 100644 --- a/src/Ocelot/Middleware/Multiplexer/SimpleJsonResponseAggregator.cs +++ b/src/Ocelot/Middleware/Multiplexer/SimpleJsonResponseAggregator.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Headers; @@ -21,19 +22,54 @@ namespace Ocelot.Middleware.Multiplexer contentBuilder.Append("{"); - for (var i = 0; i < downstreamContexts.Count; i++) + var responseKeys = downstreamContexts.Select(s => s.DownstreamReRoute.Key).Distinct().ToList(); + + for (var k = 0; k < responseKeys.Count; k++) { - if (downstreamContexts[i].IsError) + var contexts = downstreamContexts.Where(w => w.DownstreamReRoute.Key == responseKeys[k]).ToList(); + if (contexts.Count == 1) { - MapAggregateError(originalContext, downstreamContexts, i); - return; + if (contexts[0].IsError) + { + MapAggregateError(originalContext, contexts[0]); + return; + } + + var content = await contexts[0].DownstreamResponse.Content.ReadAsStringAsync(); + contentBuilder.Append($"\"{responseKeys[k]}\":{content}"); + + } + else + { + contentBuilder.Append($"\"{responseKeys[k]}\":"); + contentBuilder.Append("["); + + for (var i = 0; i < contexts.Count; i++) + { + if (contexts[i].IsError) + { + MapAggregateError(originalContext, contexts[i]); + return; + } + + var content = await contexts[i].DownstreamResponse.Content.ReadAsStringAsync(); + if (string.IsNullOrWhiteSpace(content)) + { + continue; + } + + contentBuilder.Append($"{content}"); + + if (i + 1 < contexts.Count) + { + contentBuilder.Append(","); + } + } + + contentBuilder.Append("]"); } - var content = await downstreamContexts[i].DownstreamResponse.Content.ReadAsStringAsync(); - - contentBuilder.Append($"\"{downstreamContexts[i].DownstreamReRoute.Key}\":{content}"); - - if (i + 1 < downstreamContexts.Count) + if (k + 1 < responseKeys.Count) { contentBuilder.Append(","); } @@ -43,16 +79,16 @@ namespace Ocelot.Middleware.Multiplexer var stringContent = 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>>(), "cannot return from aggregate..which reason phrase would you use?"); } - private static void MapAggregateError(DownstreamContext originalContext, List downstreamContexts, int i) + private static void MapAggregateError(DownstreamContext originalContext, DownstreamContext downstreamContext) { - originalContext.Errors.AddRange(downstreamContexts[i].Errors); - originalContext.DownstreamResponse = downstreamContexts[i].DownstreamResponse; + originalContext.Errors.AddRange(downstreamContext.Errors); + originalContext.DownstreamResponse = downstreamContext.DownstreamResponse; } } } diff --git a/test/Ocelot.AcceptanceTests/AggregateTests.cs b/test/Ocelot.AcceptanceTests/AggregateTests.cs index a98fd3ef..e3d99854 100644 --- a/test/Ocelot.AcceptanceTests/AggregateTests.cs +++ b/test/Ocelot.AcceptanceTests/AggregateTests.cs @@ -141,6 +141,100 @@ namespace Ocelot.AcceptanceTests .BDDfy(); } + [Fact] + public void should_return_response_200_with_advanced_aggregate_configs() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51889, + } + }, + UpstreamPathTemplate = "/Comments", + UpstreamHttpMethod = new List { "Get" }, + Key = "Comments" + }, + new FileReRoute + { + DownstreamPathTemplate = "/users/{userId}", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51890, + } + }, + UpstreamPathTemplate = "/UserDetails", + UpstreamHttpMethod = new List { "Get" }, + Key = "UserDetails" + }, + new FileReRoute + { + DownstreamPathTemplate = "/posts/{postId}", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51887, + } + }, + UpstreamPathTemplate = "/PostDetails", + UpstreamHttpMethod = new List { "Get" }, + Key = "PostDetails" + } + }, + Aggregates = new List + { + new FileAggregateReRoute + { + UpstreamPathTemplate = "/", + UpstreamHost = "localhost", + ReRouteKeys = new List + { + "Comments", + "UserDetails", + "PostDetails" + }, + ReRouteKeysConfig = new List() + { + new AggregateReRouteConfig(){ReRouteKey = "UserDetails",JsonPath = "$[*].writerId",Parameter = "userId"}, + new AggregateReRouteConfig(){ReRouteKey = "PostDetails",JsonPath = "$[*].postId",Parameter = "postId"} + }, + } + } + }; + + var userDetailsResponseContent = @"{""id"":1,""firstName"":""abolfazl"",""lastName"":""rajabpour""}"; + var postDetailsResponseContent = @"{""id"":1,""title"":""post1""}"; + var commentsResponseContent = @"[{""id"":1,""writerId"":1,""postId"":2,""text"":""text1""},{""id"":2,""writerId"":1,""postId"":2,""text"":""text2""}]"; + + var expected = "{\"Comments\":" + commentsResponseContent + ",\"UserDetails\":" + userDetailsResponseContent + ",\"PostDetails\":" + postDetailsResponseContent + "}"; + + this.Given(x => x.GivenServiceOneIsRunning("http://localhost:51889", "/", 200, commentsResponseContent)) + .Given(x => x.GivenServiceTwoIsRunning("http://localhost:51890", "/users/1", 200, userDetailsResponseContent)) + .Given(x => x.GivenServiceTwoIsRunning("http://localhost:51887", "/posts/2", 200, postDetailsResponseContent)) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe(expected)) + .BDDfy(); + } + [Fact] public void should_return_response_200_with_simple_url_user_defined_aggregate() { @@ -189,8 +283,8 @@ namespace Ocelot.AcceptanceTests UpstreamHost = "localhost", ReRouteKeys = new List { - "Tom", - "Laura" + "Laura", + "Tom" }, Aggregator = "FakeDefinedAggregator" } @@ -258,8 +352,8 @@ namespace Ocelot.AcceptanceTests UpstreamHost = "localhost", ReRouteKeys = new List { - "Tom", - "Laura" + "Laura", + "Tom" } } } @@ -326,8 +420,9 @@ namespace Ocelot.AcceptanceTests UpstreamHost = "localhost", ReRouteKeys = new List { - "Tom", - "Laura" + "Laura", + "Tom" + } } } @@ -394,8 +489,8 @@ namespace Ocelot.AcceptanceTests UpstreamHost = "localhost", ReRouteKeys = new List { - "Tom", - "Laura" + "Laura", + "Tom" } } } @@ -462,8 +557,8 @@ namespace Ocelot.AcceptanceTests UpstreamHost = "localhost", ReRouteKeys = new List { - "Tom", - "Laura" + "Laura", + "Tom" } } } diff --git a/test/Ocelot.UnitTests/Middleware/ResponseAggregatorFactoryTests.cs b/test/Ocelot.UnitTests/Middleware/ResponseAggregatorFactoryTests.cs index b4413b45..16a682ce 100644 --- a/test/Ocelot.UnitTests/Middleware/ResponseAggregatorFactoryTests.cs +++ b/test/Ocelot.UnitTests/Middleware/ResponseAggregatorFactoryTests.cs @@ -18,7 +18,8 @@ namespace Ocelot.UnitTests.Middleware public ResponseAggregatorFactoryTests() { _provider = new Mock(); - _factory = new InMemoryResponseAggregatorFactory(_provider.Object); + _aggregator = new SimpleJsonResponseAggregator(); + _factory = new InMemoryResponseAggregatorFactory(_provider.Object, _aggregator); } [Fact] diff --git a/test/Ocelot.UnitTests/Middleware/SimpleJsonResponseAggregatorTests.cs b/test/Ocelot.UnitTests/Middleware/SimpleJsonResponseAggregatorTests.cs index 7d2e5edd..a391ce59 100644 --- a/test/Ocelot.UnitTests/Middleware/SimpleJsonResponseAggregatorTests.cs +++ b/test/Ocelot.UnitTests/Middleware/SimpleJsonResponseAggregatorTests.cs @@ -7,11 +7,11 @@ using Castle.Components.DictionaryAdapter; using Microsoft.AspNetCore.Http; using Ocelot.Configuration; using Ocelot.Configuration.Builder; -using Ocelot.Errors; +using Ocelot.Configuration.File; using Ocelot.Middleware; using Ocelot.Middleware.Multiplexer; -using Ocelot.Request.Middleware; using Ocelot.UnitTests.Responder; +using Ocelot.Values; using Shouldly; using TestStack.BDDfy; using Xunit; @@ -30,6 +30,58 @@ namespace Ocelot.UnitTests.Middleware _aggregator = new SimpleJsonResponseAggregator(); } + [Fact] + public void should_aggregate_n_responses_and_set_response_content_on_upstream_context_withConfig() + { + var commentsDownstreamReRoute = new DownstreamReRouteBuilder().WithKey("Comments").Build(); + + var userDetailsDownstreamReRoute = new DownstreamReRouteBuilder().WithKey("UserDetails") + .WithUpstreamPathTemplate(new UpstreamPathTemplate("", 0, false, "/v1/users/{userId}")) + .Build(); + + var downstreamReRoutes = new List + { + commentsDownstreamReRoute, + userDetailsDownstreamReRoute + }; + + var reRoute = new ReRouteBuilder() + .WithDownstreamReRoutes(downstreamReRoutes) + .WithAggregateReRouteConfig(new List() + { + new AggregateReRouteConfig(){ReRouteKey = "UserDetails",JsonPath = "$[*].writerId",Parameter = "userId"} + }) + .Build(); + + var commentsResponseContent = @"[{""id"":1,""writerId"":1,""postId"":1,""text"":""text1""},{""id"":2,""writerId"":2,""postId"":2,""text"":""text2""},{""id"":3,""writerId"":2,""postId"":1,""text"":""text21""}]"; + var commentsDownstreamContext = new DownstreamContext(new DefaultHttpContext()) + { + DownstreamResponse = new DownstreamResponse(new StringContent(commentsResponseContent, Encoding.UTF8, "application/json"), HttpStatusCode.OK, new EditableList>>(), "some reason"), + DownstreamReRoute = commentsDownstreamReRoute + }; + + var userDetailsResponseContent = @"[{""id"":1,""firstName"":""abolfazl"",""lastName"":""rajabpour""},{""id"":2,""firstName"":""reza"",""lastName"":""rezaei""}]"; + var userDetailsDownstreamContext = new DownstreamContext(new DefaultHttpContext()) + { + DownstreamResponse = new DownstreamResponse(new StringContent(userDetailsResponseContent, Encoding.UTF8, "application/json"), HttpStatusCode.OK, new List>>(), "some reason"), + DownstreamReRoute = userDetailsDownstreamReRoute + }; + + var downstreamContexts = new List { commentsDownstreamContext, userDetailsDownstreamContext }; + + var expected = "{\"Comments\":" + commentsResponseContent + ",\"UserDetails\":" + userDetailsResponseContent + "}"; + + this.Given(x => GivenTheUpstreamContext(new DownstreamContext(new DefaultHttpContext()))) + .And(x => GivenTheReRoute(reRoute)) + .And(x => GivenTheDownstreamContext(downstreamContexts)) + .When(x => WhenIAggregate()) + .Then(x => ThenTheContentIs(expected)) + .And(x => ThenTheContentTypeIs("application/json")) + .And(x => ThenTheReasonPhraseIs("cannot return from aggregate..which reason phrase would you use?")) + .BDDfy(); + } + + [Fact] public void should_aggregate_n_responses_and_set_response_content_on_upstream_context() {