From 00a600064deea0877058d04e6189d7e0278c99a5 Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Mon, 20 Aug 2018 22:28:58 +0100 Subject: [PATCH] Feature/issue with path and query string #458 (#565) * #548 added failing test * #548 fixed failing tests for issue where using /{everything} didnt build path correctly --- .../UrlPathPlaceholderNameAndValueFinder.cs | 300 +++---- src/Ocelot/Responder/HttpContextResponder.cs | 5 +- test/Ocelot.AcceptanceTests/RoutingTests.cs | 35 + .../RoutingWithQueryStringTests.cs | 40 +- ...lPathPlaceholderNameAndValueFinderTests.cs | 745 +++++++++--------- 5 files changed, 616 insertions(+), 509 deletions(-) diff --git a/src/Ocelot/DownstreamRouteFinder/UrlMatcher/UrlPathPlaceholderNameAndValueFinder.cs b/src/Ocelot/DownstreamRouteFinder/UrlMatcher/UrlPathPlaceholderNameAndValueFinder.cs index 6f4fec00d..bb59fac4c 100644 --- a/src/Ocelot/DownstreamRouteFinder/UrlMatcher/UrlPathPlaceholderNameAndValueFinder.cs +++ b/src/Ocelot/DownstreamRouteFinder/UrlMatcher/UrlPathPlaceholderNameAndValueFinder.cs @@ -1,150 +1,150 @@ -using System.Collections.Generic; -using Ocelot.Responses; - -namespace Ocelot.DownstreamRouteFinder.UrlMatcher -{ - public class UrlPathPlaceholderNameAndValueFinder : IPlaceholderNameAndValueFinder - { - public Response> Find(string path, string query, string pathTemplate) - { - var placeHolderNameAndValues = new List(); - - path = $"{path}{query}"; - - int counterForPath = 0; - - var delimiter = '/'; - var nextDelimiter = '/'; - - for (int counterForTemplate = 0; counterForTemplate < pathTemplate.Length; counterForTemplate++) - { - if ((path.Length > counterForPath) && CharactersDontMatch(pathTemplate[counterForTemplate], path[counterForPath]) && ContinueScanningUrl(counterForPath,path.Length)) - { - if (IsPlaceholder(pathTemplate[counterForTemplate])) - { - //should_find_multiple_query_string make test pass - if (PassedQueryString(pathTemplate, counterForTemplate)) - { - delimiter = '&'; - nextDelimiter = '&'; - } - - //should_find_multiple_query_string_and_path makes test pass - if (NotPassedQueryString(pathTemplate, counterForTemplate) && NoMoreForwardSlash(pathTemplate, counterForTemplate)) - { - delimiter = '?'; - nextDelimiter = '?'; - } - - var placeholderName = GetPlaceholderName(pathTemplate, counterForTemplate); - - var placeholderValue = GetPlaceholderValue(pathTemplate, query, placeholderName, path, counterForPath, delimiter); - - placeHolderNameAndValues.Add(new PlaceholderNameAndValue(placeholderName, placeholderValue)); - - counterForTemplate = GetNextCounterPosition(pathTemplate, counterForTemplate, '}'); - - counterForPath = GetNextCounterPosition(path, counterForPath, nextDelimiter); - - continue; - } - - return new OkResponse>(placeHolderNameAndValues); - } - else if(IsCatchAll(path, counterForPath, pathTemplate)) - { - var endOfPlaceholder = GetNextCounterPosition(pathTemplate, counterForTemplate, '}'); - - var placeholderName = GetPlaceholderName(pathTemplate, 1); - - if(NothingAfterFirstForwardSlash(path)) - { - placeHolderNameAndValues.Add(new PlaceholderNameAndValue(placeholderName, "")); - } - else - { - var placeholderValue = GetPlaceholderValue(pathTemplate, query, placeholderName, path, counterForPath + 1, '/'); - placeHolderNameAndValues.Add(new PlaceholderNameAndValue(placeholderName, placeholderValue)); - } - - counterForTemplate = endOfPlaceholder; - } - - counterForPath++; - } - - return new OkResponse>(placeHolderNameAndValues); - } - - private static bool NoMoreForwardSlash(string pathTemplate, int counterForTemplate) - { - return !pathTemplate.Substring(counterForTemplate).Contains("/"); - } - - private static bool NotPassedQueryString(string pathTemplate, int counterForTemplate) - { - return !pathTemplate.Substring(0, counterForTemplate).Contains("?"); - } - - private static bool PassedQueryString(string pathTemplate, int counterForTemplate) - { - return pathTemplate.Substring(0, counterForTemplate).Contains("?"); - } - - private bool IsCatchAll(string path, int counterForPath, string pathTemplate) - { - return string.IsNullOrEmpty(path) || (path.Length > counterForPath && path[counterForPath] == '/') && pathTemplate.Length > 1 - && pathTemplate.Substring(0, 2) == "/{" - && pathTemplate.IndexOf('}') == pathTemplate.Length - 1; - } - - private bool NothingAfterFirstForwardSlash(string path) - { - return path.Length == 1 || path.Length == 0; - } - - private string GetPlaceholderValue(string urlPathTemplate, string query, string variableName, string urlPath, int counterForUrl, char delimiter) - { - var positionOfNextSlash = urlPath.IndexOf(delimiter, counterForUrl); - - if (positionOfNextSlash == -1 || (urlPathTemplate.Trim(delimiter).EndsWith(variableName) && string.IsNullOrEmpty(query))) - { - positionOfNextSlash = urlPath.Length; - } - - var variableValue = urlPath.Substring(counterForUrl, positionOfNextSlash - counterForUrl); - - return variableValue; - } - - private string GetPlaceholderName(string urlPathTemplate, int counterForTemplate) - { - var postitionOfPlaceHolderClosingBracket = urlPathTemplate.IndexOf('}', counterForTemplate) + 1; - - var variableName = urlPathTemplate.Substring(counterForTemplate, postitionOfPlaceHolderClosingBracket - counterForTemplate); - - return variableName; - } - - private int GetNextCounterPosition(string urlTemplate, int counterForTemplate, char delimiter) - { - var closingPlaceHolderPositionOnTemplate = urlTemplate.IndexOf(delimiter, counterForTemplate); - return closingPlaceHolderPositionOnTemplate + 1; - } - - private bool CharactersDontMatch(char characterOne, char characterTwo) - { - return char.ToLower(characterOne) != char.ToLower(characterTwo); - } - - private bool ContinueScanningUrl(int counterForUrl, int urlLength) - { - return counterForUrl < urlLength; - } - - private bool IsPlaceholder(char character) - { - return character == '{'; - } - } -} +using System.Collections.Generic; +using Ocelot.Responses; + +namespace Ocelot.DownstreamRouteFinder.UrlMatcher +{ + public class UrlPathPlaceholderNameAndValueFinder : IPlaceholderNameAndValueFinder + { + public Response> Find(string path, string query, string pathTemplate) + { + var placeHolderNameAndValues = new List(); + + path = $"{path}{query}"; + + int counterForPath = 0; + + var delimiter = '/'; + var nextDelimiter = '/'; + + for (int counterForTemplate = 0; counterForTemplate < pathTemplate.Length; counterForTemplate++) + { + if ((path.Length > counterForPath) && CharactersDontMatch(pathTemplate[counterForTemplate], path[counterForPath]) && ContinueScanningUrl(counterForPath,path.Length)) + { + if (IsPlaceholder(pathTemplate[counterForTemplate])) + { + //should_find_multiple_query_string make test pass + if (PassedQueryString(pathTemplate, counterForTemplate)) + { + delimiter = '&'; + nextDelimiter = '&'; + } + + //should_find_multiple_query_string_and_path makes test pass + if (NotPassedQueryString(pathTemplate, counterForTemplate) && NoMoreForwardSlash(pathTemplate, counterForTemplate)) + { + delimiter = '?'; + nextDelimiter = '?'; + } + + var placeholderName = GetPlaceholderName(pathTemplate, counterForTemplate); + + var placeholderValue = GetPlaceholderValue(pathTemplate, query, placeholderName, path, counterForPath, delimiter); + + placeHolderNameAndValues.Add(new PlaceholderNameAndValue(placeholderName, placeholderValue)); + + counterForTemplate = GetNextCounterPosition(pathTemplate, counterForTemplate, '}'); + + counterForPath = GetNextCounterPosition(path, counterForPath, nextDelimiter); + + continue; + } + + return new OkResponse>(placeHolderNameAndValues); + } + else if(IsCatchAll(path, counterForPath, pathTemplate)) + { + var endOfPlaceholder = GetNextCounterPosition(pathTemplate, counterForTemplate, '}'); + + var placeholderName = GetPlaceholderName(pathTemplate, 1); + + if(NothingAfterFirstForwardSlash(path)) + { + placeHolderNameAndValues.Add(new PlaceholderNameAndValue(placeholderName, "")); + } + else + { + var placeholderValue = GetPlaceholderValue(pathTemplate, query, placeholderName, path, counterForPath + 1, '?'); + placeHolderNameAndValues.Add(new PlaceholderNameAndValue(placeholderName, placeholderValue)); + } + + counterForTemplate = endOfPlaceholder; + } + + counterForPath++; + } + + return new OkResponse>(placeHolderNameAndValues); + } + + private static bool NoMoreForwardSlash(string pathTemplate, int counterForTemplate) + { + return !pathTemplate.Substring(counterForTemplate).Contains("/"); + } + + private static bool NotPassedQueryString(string pathTemplate, int counterForTemplate) + { + return !pathTemplate.Substring(0, counterForTemplate).Contains("?"); + } + + private static bool PassedQueryString(string pathTemplate, int counterForTemplate) + { + return pathTemplate.Substring(0, counterForTemplate).Contains("?"); + } + + private bool IsCatchAll(string path, int counterForPath, string pathTemplate) + { + return string.IsNullOrEmpty(path) || (path.Length > counterForPath && path[counterForPath] == '/') && pathTemplate.Length > 1 + && pathTemplate.Substring(0, 2) == "/{" + && pathTemplate.IndexOf('}') == pathTemplate.Length - 1; + } + + private bool NothingAfterFirstForwardSlash(string path) + { + return path.Length == 1 || path.Length == 0; + } + + private string GetPlaceholderValue(string urlPathTemplate, string query, string variableName, string urlPath, int counterForUrl, char delimiter) + { + var positionOfNextSlash = urlPath.IndexOf(delimiter, counterForUrl); + + if (positionOfNextSlash == -1 || (urlPathTemplate.Trim(delimiter).EndsWith(variableName) && string.IsNullOrEmpty(query))) + { + positionOfNextSlash = urlPath.Length; + } + + var variableValue = urlPath.Substring(counterForUrl, positionOfNextSlash - counterForUrl); + + return variableValue; + } + + private string GetPlaceholderName(string urlPathTemplate, int counterForTemplate) + { + var postitionOfPlaceHolderClosingBracket = urlPathTemplate.IndexOf('}', counterForTemplate) + 1; + + var variableName = urlPathTemplate.Substring(counterForTemplate, postitionOfPlaceHolderClosingBracket - counterForTemplate); + + return variableName; + } + + private int GetNextCounterPosition(string urlTemplate, int counterForTemplate, char delimiter) + { + var closingPlaceHolderPositionOnTemplate = urlTemplate.IndexOf(delimiter, counterForTemplate); + return closingPlaceHolderPositionOnTemplate + 1; + } + + private bool CharactersDontMatch(char characterOne, char characterTwo) + { + return char.ToLower(characterOne) != char.ToLower(characterTwo); + } + + private bool ContinueScanningUrl(int counterForUrl, int urlLength) + { + return counterForUrl < urlLength; + } + + private bool IsPlaceholder(char character) + { + return character == '{'; + } + } +} diff --git a/src/Ocelot/Responder/HttpContextResponder.cs b/src/Ocelot/Responder/HttpContextResponder.cs index 6511590c6..b5f4e69e6 100644 --- a/src/Ocelot/Responder/HttpContextResponder.cs +++ b/src/Ocelot/Responder/HttpContextResponder.cs @@ -38,7 +38,10 @@ public async Task SetResponseOnHttpContext(HttpContext context, DownstreamRespon var content = await response.Content.ReadAsStreamAsync(); - AddHeaderIfDoesntExist(context, new Header("Content-Length", new []{ content.Length.ToString() }) ); + if(response.Content.Headers.ContentLength != null) + { + AddHeaderIfDoesntExist(context, new Header("Content-Length", new []{ response.Content.Headers.ContentLength.ToString() }) ); + } context.Response.OnStarting(state => { diff --git a/test/Ocelot.AcceptanceTests/RoutingTests.cs b/test/Ocelot.AcceptanceTests/RoutingTests.cs index 685c1e00d..19b475757 100644 --- a/test/Ocelot.AcceptanceTests/RoutingTests.cs +++ b/test/Ocelot.AcceptanceTests/RoutingTests.cs @@ -915,6 +915,41 @@ public void should_use_priority() .BDDfy(); } + [Fact] + public void should_match_multiple_paths_with_catch_all() + { + var port = 61999; + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/{everything}", + DownstreamScheme = "http", + UpstreamPathTemplate = "/{everything}", + UpstreamHttpMethod = new List { "Get" }, + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = port, + } + }, + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}/", "/test/toot", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/test/toot")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } + [Fact] public void should_fix_issue_271() { diff --git a/test/Ocelot.AcceptanceTests/RoutingWithQueryStringTests.cs b/test/Ocelot.AcceptanceTests/RoutingWithQueryStringTests.cs index 46919200f..ccb9fa4ac 100644 --- a/test/Ocelot.AcceptanceTests/RoutingWithQueryStringTests.cs +++ b/test/Ocelot.AcceptanceTests/RoutingWithQueryStringTests.cs @@ -56,6 +56,44 @@ public void should_return_response_200_with_query_string_template() .BDDfy(); } + [Fact] + public void should_return_response_200_with_odata_query_string() + { + var subscriptionId = Guid.NewGuid().ToString(); + var unitId = Guid.NewGuid().ToString(); + var port = 57359; + + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/{everything}", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = port, + } + }, + UpstreamPathTemplate = "/{everything}", + UpstreamHttpMethod = new List { "Get" }, + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", $"/odata/customers", "?$filter=Name%20eq%20'Sam'", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway($"/odata/customers?$filter=Name eq 'Sam' ")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } + [Fact] public void should_return_response_200_with_query_string_upstream_template() { @@ -206,7 +244,7 @@ private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, stri { _serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, basePath, async context => { - if (context.Request.PathBase.Value != basePath || context.Request.QueryString.Value != queryString) + if ((context.Request.PathBase.Value != basePath) || context.Request.QueryString.Value != queryString) { context.Response.StatusCode = 500; await context.Response.WriteAsync("downstream path didnt match base path"); diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/UrlMatcher/UrlPathPlaceholderNameAndValueFinderTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/UrlMatcher/UrlPathPlaceholderNameAndValueFinderTests.cs index 7ce2bb1d0..46d6e503e 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/UrlMatcher/UrlPathPlaceholderNameAndValueFinderTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/UrlMatcher/UrlPathPlaceholderNameAndValueFinderTests.cs @@ -1,357 +1,388 @@ -using System.Collections.Generic; -using System.Linq; -using Ocelot.DownstreamRouteFinder.UrlMatcher; -using Ocelot.Responses; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.DownstreamRouteFinder.UrlMatcher -{ - public class UrlPathPlaceholderNameAndValueFinderTests - { - private readonly IPlaceholderNameAndValueFinder _finder; - private string _downstreamUrlPath; - private string _downstreamPathTemplate; - private Response> _result; - private string _query; - - public UrlPathPlaceholderNameAndValueFinderTests() - { - _finder = new UrlPathPlaceholderNameAndValueFinder(); - } - - [Fact] - public void can_match_down_stream_url() - { - this.Given(x => x.GivenIHaveAUpstreamPath("")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplate("")) - .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) - .And(x => x.ThenTheTemplatesVariablesAre(new List())) - .BDDfy(); - } - - [Fact] - public void can_match_down_stream_url_with_nothing_then_placeholder_no_value_is_blank() - { - var expectedTemplates = new List - { - new PlaceholderNameAndValue("{url}", "") - }; - - this.Given(x => x.GivenIHaveAUpstreamPath("")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplate("/{url}")) - .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) - .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) - .BDDfy(); - } - - [Fact] - public void can_match_down_stream_url_with_nothing_then_placeholder_value_is_test() - { - var expectedTemplates = new List - { - new PlaceholderNameAndValue("{url}", "test") - }; - - this.Given(x => x.GivenIHaveAUpstreamPath("/test")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplate("/{url}")) - .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) - .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) - .BDDfy(); - } - - [Fact] - public void can_match_down_stream_url_with_forward_slash_then_placeholder_no_value_is_blank() - { - var expectedTemplates = new List - { - new PlaceholderNameAndValue("{url}", "") - }; - - this.Given(x => x.GivenIHaveAUpstreamPath("/")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplate("/{url}")) - .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) - .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) - .BDDfy(); - } - - [Fact] - public void can_match_down_stream_url_with_forward_slash() - { - var expectedTemplates = new List - { - }; - - this.Given(x => x.GivenIHaveAUpstreamPath("/")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplate("/")) - .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) - .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) - .BDDfy(); - } - - [Fact] - public void can_match_down_stream_url_with_forward_slash_then_placeholder_then_another_value() - { - var expectedTemplates = new List - { - new PlaceholderNameAndValue("{url}", "1") - }; - - this.Given(x => x.GivenIHaveAUpstreamPath("/1/products")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplate("/{url}/products")) - .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) - .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) - .BDDfy(); - } - - [Fact] - public void should_not_find_anything() - { - this.Given(x => x.GivenIHaveAUpstreamPath("/products")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplate("/products/")) - .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) - .And(x => x.ThenTheTemplatesVariablesAre(new List())) - .BDDfy(); - } - - [Fact] - public void should_find_query_string() - { - var expectedTemplates = new List - { - new PlaceholderNameAndValue("{productId}", "1") - }; - - this.Given(x => x.GivenIHaveAUpstreamPath("/products")) - .And(x => x.GivenIHaveAQuery("?productId=1")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplate("/products?productId={productId}")) - .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) - .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) - .BDDfy(); - } - - [Fact] - public void should_find_query_string_dont_include_hardcoded() - { - var expectedTemplates = new List - { - new PlaceholderNameAndValue("{productId}", "1") - }; - - this.Given(x => x.GivenIHaveAUpstreamPath("/products")) - .And(x => x.GivenIHaveAQuery("?productId=1&categoryId=2")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplate("/products?productId={productId}")) - .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) - .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) - .BDDfy(); - } - - [Fact] - public void should_find_multiple_query_string() - { - var expectedTemplates = new List - { - new PlaceholderNameAndValue("{productId}", "1"), - new PlaceholderNameAndValue("{categoryId}", "2") - }; - - this.Given(x => x.GivenIHaveAUpstreamPath("/products")) - .And(x => x.GivenIHaveAQuery("?productId=1&categoryId=2")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplate("/products?productId={productId}&categoryId={categoryId}")) - .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) - .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) - .BDDfy(); - } - - [Fact] - public void should_find_multiple_query_string_and_path() - { - var expectedTemplates = new List - { - new PlaceholderNameAndValue("{productId}", "1"), - new PlaceholderNameAndValue("{categoryId}", "2"), - new PlaceholderNameAndValue("{account}", "3") - }; - - this.Given(x => x.GivenIHaveAUpstreamPath("/products/3")) - .And(x => x.GivenIHaveAQuery("?productId=1&categoryId=2")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplate("/products/{account}?productId={productId}&categoryId={categoryId}")) - .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) - .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) - .BDDfy(); - } - - [Fact] - public void should_find_multiple_query_string_and_path_that_ends_with_slash() - { - var expectedTemplates = new List - { - new PlaceholderNameAndValue("{productId}", "1"), - new PlaceholderNameAndValue("{categoryId}", "2"), - new PlaceholderNameAndValue("{account}", "3") - }; - - this.Given(x => x.GivenIHaveAUpstreamPath("/products/3/")) - .And(x => x.GivenIHaveAQuery("?productId=1&categoryId=2")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplate("/products/{account}/?productId={productId}&categoryId={categoryId}")) - .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) - .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) - .BDDfy(); - } - - [Fact] - public void can_match_down_stream_url_with_no_slash() - { - this.Given(x => x.GivenIHaveAUpstreamPath("api")) - .Given(x => x.GivenIHaveAnUpstreamUrlTemplate("api")) - .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) - .And(x => x.ThenTheTemplatesVariablesAre(new List())) - .BDDfy(); - } - - [Fact] - public void can_match_down_stream_url_with_one_slash() - { - this.Given(x => x.GivenIHaveAUpstreamPath("api/")) - .Given(x => x.GivenIHaveAnUpstreamUrlTemplate("api/")) - .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) - .And(x => x.ThenTheTemplatesVariablesAre(new List())) - .BDDfy(); - } - - [Fact] - public void can_match_down_stream_url_with_downstream_template() - { - this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/")) - .Given(x => x.GivenIHaveAnUpstreamUrlTemplate("api/product/products/")) - .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) - .And(x => x.ThenTheTemplatesVariablesAre(new List())) - .BDDfy(); - } - - [Fact] - public void can_match_down_stream_url_with_downstream_template_with_one_place_holder() - { - var expectedTemplates = new List - { - new PlaceholderNameAndValue("{productId}", "1") - }; - - this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/1")) - .Given(x => x.GivenIHaveAnUpstreamUrlTemplate("api/product/products/{productId}")) - .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) - .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) - .BDDfy(); - } - - [Fact] - public void can_match_down_stream_url_with_downstream_template_with_two_place_holders() - { - var expectedTemplates = new List - { - new PlaceholderNameAndValue("{productId}", "1"), - new PlaceholderNameAndValue("{categoryId}", "2") - }; - - this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/1/2")) - .Given(x => x.GivenIHaveAnUpstreamUrlTemplate("api/product/products/{productId}/{categoryId}")) - .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) - .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) - .BDDfy(); - } - - [Fact] - public void can_match_down_stream_url_with_downstream_template_with_two_place_holders_seperated_by_something() - { - var expectedTemplates = new List - { - new PlaceholderNameAndValue("{productId}", "1"), - new PlaceholderNameAndValue("{categoryId}", "2") - }; - - this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/1/categories/2")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplate("api/product/products/{productId}/categories/{categoryId}")) - .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) - .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) - .BDDfy(); - } - - [Fact] - public void can_match_down_stream_url_with_downstream_template_with_three_place_holders_seperated_by_something() - { - var expectedTemplates = new List - { - new PlaceholderNameAndValue("{productId}", "1"), - new PlaceholderNameAndValue("{categoryId}", "2"), - new PlaceholderNameAndValue("{variantId}", "123") - }; - - this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/1/categories/2/variant/123")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplate("api/product/products/{productId}/categories/{categoryId}/variant/{variantId}")) - .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) - .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) - .BDDfy(); - } - - [Fact] - public void can_match_down_stream_url_with_downstream_template_with_three_place_holders() - { - var expectedTemplates = new List - { - new PlaceholderNameAndValue("{productId}", "1"), - new PlaceholderNameAndValue("{categoryId}", "2") - }; - - this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/1/categories/2/variant/")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplate("api/product/products/{productId}/categories/{categoryId}/variant/")) - .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) - .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) - .BDDfy(); - } - - [Fact] - public void can_match_down_stream_url_with_downstream_template_with_place_holder_to_final_url_path() - { - var expectedTemplates = new List - { - new PlaceholderNameAndValue("{finalUrlPath}", "product/products/categories/"), - }; - - this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/categories/")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplate("api/{finalUrlPath}/")) - .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) - .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) - .BDDfy(); - } - - private void ThenTheTemplatesVariablesAre(List expectedResults) - { - foreach (var expectedResult in expectedResults) - { - var result = _result.Data.First(t => t.Name == expectedResult.Name); - result.Value.ShouldBe(expectedResult.Value); - } - } - - private void GivenIHaveAUpstreamPath(string downstreamPath) - { - _downstreamUrlPath = downstreamPath; - } - - private void GivenIHaveAnUpstreamUrlTemplate(string downstreamUrlTemplate) - { - _downstreamPathTemplate = downstreamUrlTemplate; - } - - private void WhenIFindTheUrlVariableNamesAndValues() - { - _result = _finder.Find(_downstreamUrlPath, _query, _downstreamPathTemplate); - } - - private void GivenIHaveAQuery(string query) - { - _query = query; - } - } -} +using System.Collections.Generic; +using System.Linq; +using Ocelot.DownstreamRouteFinder.UrlMatcher; +using Ocelot.Responses; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.DownstreamRouteFinder.UrlMatcher +{ + public class UrlPathPlaceholderNameAndValueFinderTests + { + private readonly IPlaceholderNameAndValueFinder _finder; + private string _downstreamUrlPath; + private string _downstreamPathTemplate; + private Response> _result; + private string _query; + + public UrlPathPlaceholderNameAndValueFinderTests() + { + _finder = new UrlPathPlaceholderNameAndValueFinder(); + } + + [Fact] + public void can_match_down_stream_url() + { + this.Given(x => x.GivenIHaveAUpstreamPath("")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplate("")) + .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) + .And(x => x.ThenTheTemplatesVariablesAre(new List())) + .BDDfy(); + } + + [Fact] + public void can_match_down_stream_url_with_nothing_then_placeholder_no_value_is_blank() + { + var expectedTemplates = new List + { + new PlaceholderNameAndValue("{url}", "") + }; + + this.Given(x => x.GivenIHaveAUpstreamPath("")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplate("/{url}")) + .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) + .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) + .BDDfy(); + } + + [Fact] + public void can_match_down_stream_url_with_nothing_then_placeholder_value_is_test() + { + var expectedTemplates = new List + { + new PlaceholderNameAndValue("{url}", "test") + }; + + this.Given(x => x.GivenIHaveAUpstreamPath("/test")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplate("/{url}")) + .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) + .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) + .BDDfy(); + } + + [Fact] + public void should_match_everything_in_path_with_query() + { + var expectedTemplates = new List + { + new PlaceholderNameAndValue("{everything}", "test/toot") + }; + + this.Given(x => x.GivenIHaveAUpstreamPath("/test/toot")) + .And(x => GivenIHaveAQuery("?$filter=Name%20eq%20'Sam'")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplate("/{everything}")) + .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) + .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) + .BDDfy(); + } + + [Fact] + public void should_match_everything_in_path() + { + var expectedTemplates = new List + { + new PlaceholderNameAndValue("{everything}", "test/toot") + }; + + this.Given(x => x.GivenIHaveAUpstreamPath("/test/toot")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplate("/{everything}")) + .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) + .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) + .BDDfy(); + } + + [Fact] + public void can_match_down_stream_url_with_forward_slash_then_placeholder_no_value_is_blank() + { + var expectedTemplates = new List + { + new PlaceholderNameAndValue("{url}", "") + }; + + this.Given(x => x.GivenIHaveAUpstreamPath("/")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplate("/{url}")) + .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) + .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) + .BDDfy(); + } + + [Fact] + public void can_match_down_stream_url_with_forward_slash() + { + var expectedTemplates = new List + { + }; + + this.Given(x => x.GivenIHaveAUpstreamPath("/")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplate("/")) + .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) + .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) + .BDDfy(); + } + + [Fact] + public void can_match_down_stream_url_with_forward_slash_then_placeholder_then_another_value() + { + var expectedTemplates = new List + { + new PlaceholderNameAndValue("{url}", "1") + }; + + this.Given(x => x.GivenIHaveAUpstreamPath("/1/products")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplate("/{url}/products")) + .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) + .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) + .BDDfy(); + } + + [Fact] + public void should_not_find_anything() + { + this.Given(x => x.GivenIHaveAUpstreamPath("/products")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplate("/products/")) + .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) + .And(x => x.ThenTheTemplatesVariablesAre(new List())) + .BDDfy(); + } + + [Fact] + public void should_find_query_string() + { + var expectedTemplates = new List + { + new PlaceholderNameAndValue("{productId}", "1") + }; + + this.Given(x => x.GivenIHaveAUpstreamPath("/products")) + .And(x => x.GivenIHaveAQuery("?productId=1")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplate("/products?productId={productId}")) + .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) + .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) + .BDDfy(); + } + + [Fact] + public void should_find_query_string_dont_include_hardcoded() + { + var expectedTemplates = new List + { + new PlaceholderNameAndValue("{productId}", "1") + }; + + this.Given(x => x.GivenIHaveAUpstreamPath("/products")) + .And(x => x.GivenIHaveAQuery("?productId=1&categoryId=2")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplate("/products?productId={productId}")) + .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) + .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) + .BDDfy(); + } + + [Fact] + public void should_find_multiple_query_string() + { + var expectedTemplates = new List + { + new PlaceholderNameAndValue("{productId}", "1"), + new PlaceholderNameAndValue("{categoryId}", "2") + }; + + this.Given(x => x.GivenIHaveAUpstreamPath("/products")) + .And(x => x.GivenIHaveAQuery("?productId=1&categoryId=2")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplate("/products?productId={productId}&categoryId={categoryId}")) + .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) + .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) + .BDDfy(); + } + + [Fact] + public void should_find_multiple_query_string_and_path() + { + var expectedTemplates = new List + { + new PlaceholderNameAndValue("{productId}", "1"), + new PlaceholderNameAndValue("{categoryId}", "2"), + new PlaceholderNameAndValue("{account}", "3") + }; + + this.Given(x => x.GivenIHaveAUpstreamPath("/products/3")) + .And(x => x.GivenIHaveAQuery("?productId=1&categoryId=2")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplate("/products/{account}?productId={productId}&categoryId={categoryId}")) + .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) + .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) + .BDDfy(); + } + + [Fact] + public void should_find_multiple_query_string_and_path_that_ends_with_slash() + { + var expectedTemplates = new List + { + new PlaceholderNameAndValue("{productId}", "1"), + new PlaceholderNameAndValue("{categoryId}", "2"), + new PlaceholderNameAndValue("{account}", "3") + }; + + this.Given(x => x.GivenIHaveAUpstreamPath("/products/3/")) + .And(x => x.GivenIHaveAQuery("?productId=1&categoryId=2")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplate("/products/{account}/?productId={productId}&categoryId={categoryId}")) + .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) + .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) + .BDDfy(); + } + + [Fact] + public void can_match_down_stream_url_with_no_slash() + { + this.Given(x => x.GivenIHaveAUpstreamPath("api")) + .Given(x => x.GivenIHaveAnUpstreamUrlTemplate("api")) + .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) + .And(x => x.ThenTheTemplatesVariablesAre(new List())) + .BDDfy(); + } + + [Fact] + public void can_match_down_stream_url_with_one_slash() + { + this.Given(x => x.GivenIHaveAUpstreamPath("api/")) + .Given(x => x.GivenIHaveAnUpstreamUrlTemplate("api/")) + .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) + .And(x => x.ThenTheTemplatesVariablesAre(new List())) + .BDDfy(); + } + + [Fact] + public void can_match_down_stream_url_with_downstream_template() + { + this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/")) + .Given(x => x.GivenIHaveAnUpstreamUrlTemplate("api/product/products/")) + .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) + .And(x => x.ThenTheTemplatesVariablesAre(new List())) + .BDDfy(); + } + + [Fact] + public void can_match_down_stream_url_with_downstream_template_with_one_place_holder() + { + var expectedTemplates = new List + { + new PlaceholderNameAndValue("{productId}", "1") + }; + + this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/1")) + .Given(x => x.GivenIHaveAnUpstreamUrlTemplate("api/product/products/{productId}")) + .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) + .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) + .BDDfy(); + } + + [Fact] + public void can_match_down_stream_url_with_downstream_template_with_two_place_holders() + { + var expectedTemplates = new List + { + new PlaceholderNameAndValue("{productId}", "1"), + new PlaceholderNameAndValue("{categoryId}", "2") + }; + + this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/1/2")) + .Given(x => x.GivenIHaveAnUpstreamUrlTemplate("api/product/products/{productId}/{categoryId}")) + .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) + .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) + .BDDfy(); + } + + [Fact] + public void can_match_down_stream_url_with_downstream_template_with_two_place_holders_seperated_by_something() + { + var expectedTemplates = new List + { + new PlaceholderNameAndValue("{productId}", "1"), + new PlaceholderNameAndValue("{categoryId}", "2") + }; + + this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/1/categories/2")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplate("api/product/products/{productId}/categories/{categoryId}")) + .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) + .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) + .BDDfy(); + } + + [Fact] + public void can_match_down_stream_url_with_downstream_template_with_three_place_holders_seperated_by_something() + { + var expectedTemplates = new List + { + new PlaceholderNameAndValue("{productId}", "1"), + new PlaceholderNameAndValue("{categoryId}", "2"), + new PlaceholderNameAndValue("{variantId}", "123") + }; + + this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/1/categories/2/variant/123")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplate("api/product/products/{productId}/categories/{categoryId}/variant/{variantId}")) + .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) + .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) + .BDDfy(); + } + + [Fact] + public void can_match_down_stream_url_with_downstream_template_with_three_place_holders() + { + var expectedTemplates = new List + { + new PlaceholderNameAndValue("{productId}", "1"), + new PlaceholderNameAndValue("{categoryId}", "2") + }; + + this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/1/categories/2/variant/")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplate("api/product/products/{productId}/categories/{categoryId}/variant/")) + .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) + .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) + .BDDfy(); + } + + [Fact] + public void can_match_down_stream_url_with_downstream_template_with_place_holder_to_final_url_path() + { + var expectedTemplates = new List + { + new PlaceholderNameAndValue("{finalUrlPath}", "product/products/categories/"), + }; + + this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/categories/")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplate("api/{finalUrlPath}/")) + .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) + .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) + .BDDfy(); + } + + private void ThenTheTemplatesVariablesAre(List expectedResults) + { + foreach (var expectedResult in expectedResults) + { + var result = _result.Data.First(t => t.Name == expectedResult.Name); + result.Value.ShouldBe(expectedResult.Value); + } + } + + private void GivenIHaveAUpstreamPath(string downstreamPath) + { + _downstreamUrlPath = downstreamPath; + } + + private void GivenIHaveAnUpstreamUrlTemplate(string downstreamUrlTemplate) + { + _downstreamPathTemplate = downstreamUrlTemplate; + } + + private void WhenIFindTheUrlVariableNamesAndValues() + { + _result = _finder.Find(_downstreamUrlPath, _query, _downstreamPathTemplate); + } + + private void GivenIHaveAQuery(string query) + { + _query = query; + } + } +}