#534 fixed failing tests for this issue (#575)

This commit is contained in:
Tom Pallister 2018-08-25 12:32:56 +01:00 committed by GitHub
parent 00a600064d
commit b0bdeb9402
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 676 additions and 596 deletions

View File

@ -1,84 +1,93 @@
using System.Collections.Generic; using System.Collections.Generic;
using Ocelot.Configuration.File; using Ocelot.Configuration.File;
using Ocelot.Values; using Ocelot.Values;
namespace Ocelot.Configuration.Creator namespace Ocelot.Configuration.Creator
{ {
public class UpstreamTemplatePatternCreator : IUpstreamTemplatePatternCreator public class UpstreamTemplatePatternCreator : IUpstreamTemplatePatternCreator
{ {
private const string RegExMatchOneOrMoreOfEverything = ".+"; private const string RegExMatchOneOrMoreOfEverything = ".+";
private const string RegExMatchEndString = "$"; private const string RegExMatchOneOrMoreOfEverythingUntilNextForwardSlash = "[^/]+";
private const string RegExIgnoreCase = "(?i)"; private const string RegExMatchEndString = "$";
private const string RegExForwardSlashOnly = "^/$"; private const string RegExIgnoreCase = "(?i)";
private const string RegExForwardSlashAndOnePlaceHolder = "^/.*"; private const string RegExForwardSlashOnly = "^/$";
private const string RegExForwardSlashAndOnePlaceHolder = "^/.*";
public UpstreamPathTemplate Create(IReRoute reRoute)
{ public UpstreamPathTemplate Create(IReRoute reRoute)
{
var upstreamTemplate = reRoute.UpstreamPathTemplate; var upstreamTemplate = reRoute.UpstreamPathTemplate;
var placeholders = new List<string>();
var placeholders = new List<string>();
for (var i = 0; i < upstreamTemplate.Length; i++)
for (var i = 0; i < upstreamTemplate.Length; i++) {
{ if (IsPlaceHolder(upstreamTemplate, i))
if (IsPlaceHolder(upstreamTemplate, i)) {
{ var postitionOfPlaceHolderClosingBracket = upstreamTemplate.IndexOf('}', i);
var postitionOfPlaceHolderClosingBracket = upstreamTemplate.IndexOf('}', i); var difference = postitionOfPlaceHolderClosingBracket - i + 1;
var difference = postitionOfPlaceHolderClosingBracket - i + 1; var placeHolderName = upstreamTemplate.Substring(i, difference);
var placeHolderName = upstreamTemplate.Substring(i, difference); placeholders.Add(placeHolderName);
placeholders.Add(placeHolderName);
//hack to handle /{url} case
//hack to handle /{url} case if(ForwardSlashAndOnePlaceHolder(upstreamTemplate, placeholders, postitionOfPlaceHolderClosingBracket))
if(ForwardSlashAndOnePlaceHolder(upstreamTemplate, placeholders, postitionOfPlaceHolderClosingBracket)) {
{ return new UpstreamPathTemplate(RegExForwardSlashAndOnePlaceHolder, 0, false);
return new UpstreamPathTemplate(RegExForwardSlashAndOnePlaceHolder, 0, false); }
} }
}
} }
var containsQueryString = false; var containsQueryString = false;
if (upstreamTemplate.Contains("?")) if (upstreamTemplate.Contains("?"))
{ {
containsQueryString = true; containsQueryString = true;
upstreamTemplate = upstreamTemplate.Replace("?", "\\?"); upstreamTemplate = upstreamTemplate.Replace("?", "\\?");
} }
foreach (var placeholder in placeholders) for (int i = 0; i < placeholders.Count; i++)
{ {
upstreamTemplate = upstreamTemplate.Replace(placeholder, RegExMatchOneOrMoreOfEverything); var indexOfPlaceholder = upstreamTemplate.IndexOf(placeholders[i]);
} var indexOfNextForwardSlash = upstreamTemplate.IndexOf("/", indexOfPlaceholder);
if(indexOfNextForwardSlash < indexOfPlaceholder || (containsQueryString && upstreamTemplate.IndexOf("?") < upstreamTemplate.IndexOf(placeholders[i])))
if (upstreamTemplate == "/") {
{ upstreamTemplate = upstreamTemplate.Replace(placeholders[i], RegExMatchOneOrMoreOfEverything);
return new UpstreamPathTemplate(RegExForwardSlashOnly, reRoute.Priority, containsQueryString); }
} else
{
if(upstreamTemplate.EndsWith("/")) upstreamTemplate = upstreamTemplate.Replace(placeholders[i], RegExMatchOneOrMoreOfEverythingUntilNextForwardSlash);
{ }
upstreamTemplate = upstreamTemplate.Remove(upstreamTemplate.Length -1, 1) + "(/|)"; }
}
if (upstreamTemplate == "/")
var route = reRoute.ReRouteIsCaseSensitive {
? $"^{upstreamTemplate}{RegExMatchEndString}" return new UpstreamPathTemplate(RegExForwardSlashOnly, reRoute.Priority, containsQueryString);
: $"^{RegExIgnoreCase}{upstreamTemplate}{RegExMatchEndString}"; }
return new UpstreamPathTemplate(route, reRoute.Priority, containsQueryString); if(upstreamTemplate.EndsWith("/"))
} {
upstreamTemplate = upstreamTemplate.Remove(upstreamTemplate.Length -1, 1) + "(/|)";
private bool ForwardSlashAndOnePlaceHolder(string upstreamTemplate, List<string> placeholders, int postitionOfPlaceHolderClosingBracket) }
{
if(upstreamTemplate.Substring(0, 2) == "/{" && placeholders.Count == 1 && upstreamTemplate.Length == postitionOfPlaceHolderClosingBracket + 1) var route = reRoute.ReRouteIsCaseSensitive
{ ? $"^{upstreamTemplate}{RegExMatchEndString}"
return true; : $"^{RegExIgnoreCase}{upstreamTemplate}{RegExMatchEndString}";
}
return new UpstreamPathTemplate(route, reRoute.Priority, containsQueryString);
return false; }
}
private bool ForwardSlashAndOnePlaceHolder(string upstreamTemplate, List<string> placeholders, int postitionOfPlaceHolderClosingBracket)
private bool IsPlaceHolder(string upstreamTemplate, int i) {
{ if(upstreamTemplate.Substring(0, 2) == "/{" && placeholders.Count == 1 && upstreamTemplate.Length == postitionOfPlaceHolderClosingBracket + 1)
return upstreamTemplate[i] == '{'; {
} return true;
} }
}
return false;
}
private bool IsPlaceHolder(string upstreamTemplate, int i)
{
return upstreamTemplate[i] == '{';
}
}
}

View File

@ -21,6 +21,41 @@ namespace Ocelot.AcceptanceTests
_steps = new Steps(); _steps = new Steps();
} }
[Fact]
public void should_not_match_forward_slash_in_pattern_before_next_forward_slash()
{
var port = 31879;
var configuration = new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
DownstreamPathTemplate = "/api/v{apiVersion}/cards",
DownstreamScheme = "http",
UpstreamPathTemplate = "/api/v{apiVersion}/cards",
UpstreamHttpMethod = new List<string> { "Get" },
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = "localhost",
Port = port,
}
},
Priority = 1
}
}
};
this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}/", "/api/v1/aaaaaaaaa/cards", 200, "Hello from Laura"))
.And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunning())
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/api/v1/aaaaaaaaa/cards"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.NotFound))
.BDDfy();
}
[Fact] [Fact]
public void should_return_response_404_when_no_configuration_at_all() public void should_return_response_404_when_no_configuration_at_all()
{ {

View File

@ -1,244 +1,260 @@
using Ocelot.Configuration.Creator; using Ocelot.Configuration.Creator;
using Ocelot.Configuration.File; using Ocelot.Configuration.File;
using Ocelot.Values; using Ocelot.Values;
using Shouldly; using Shouldly;
using TestStack.BDDfy; using TestStack.BDDfy;
using Xunit; using Xunit;
namespace Ocelot.UnitTests.Configuration namespace Ocelot.UnitTests.Configuration
{ {
public class UpstreamTemplatePatternCreatorTests public class UpstreamTemplatePatternCreatorTests
{ {
private FileReRoute _fileReRoute; private FileReRoute _fileReRoute;
private readonly UpstreamTemplatePatternCreator _creator; private readonly UpstreamTemplatePatternCreator _creator;
private UpstreamPathTemplate _result; private UpstreamPathTemplate _result;
public UpstreamTemplatePatternCreatorTests() public UpstreamTemplatePatternCreatorTests()
{ {
_creator = new UpstreamTemplatePatternCreator(); _creator = new UpstreamTemplatePatternCreator();
} }
[Fact] [Fact]
public void should_use_re_route_priority() public void should_match_up_to_next_slash()
{ {
var fileReRoute = new FileReRoute var fileReRoute = new FileReRoute
{ {
UpstreamPathTemplate = "/orders/{catchAll}", UpstreamPathTemplate = "/api/v{apiVersion}/cards",
Priority = 0 Priority = 0
}; };
this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute))
.When(x => x.WhenICreateTheTemplatePattern()) .When(x => x.WhenICreateTheTemplatePattern())
.Then(x => x.ThenTheFollowingIsReturned("^(?i)/orders/.+$")) .Then(x => x.ThenTheFollowingIsReturned("^(?i)/api/v[^/]+/cards$"))
.And(x => ThenThePriorityIs(0)) .And(x => ThenThePriorityIs(0))
.BDDfy(); .BDDfy();
} }
[Fact] [Fact]
public void should_use_zero_priority() public void should_use_re_route_priority()
{ {
var fileReRoute = new FileReRoute var fileReRoute = new FileReRoute
{ {
UpstreamPathTemplate = "/{catchAll}", UpstreamPathTemplate = "/orders/{catchAll}",
Priority = 1 Priority = 0
}; };
this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute))
.When(x => x.WhenICreateTheTemplatePattern()) .When(x => x.WhenICreateTheTemplatePattern())
.Then(x => x.ThenTheFollowingIsReturned("^/.*")) .Then(x => x.ThenTheFollowingIsReturned("^(?i)/orders/.+$"))
.And(x => ThenThePriorityIs(0)) .And(x => ThenThePriorityIs(0))
.BDDfy(); .BDDfy();
} }
[Fact] [Fact]
public void should_set_upstream_template_pattern_to_ignore_case_sensitivity() public void should_use_zero_priority()
{ {
var fileReRoute = new FileReRoute var fileReRoute = new FileReRoute
{ {
UpstreamPathTemplate = "/PRODUCTS/{productId}", UpstreamPathTemplate = "/{catchAll}",
ReRouteIsCaseSensitive = false Priority = 1
}; };
this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute))
.When(x => x.WhenICreateTheTemplatePattern()) .When(x => x.WhenICreateTheTemplatePattern())
.Then(x => x.ThenTheFollowingIsReturned("^(?i)/PRODUCTS/.+$")) .Then(x => x.ThenTheFollowingIsReturned("^/.*"))
.And(x => ThenThePriorityIs(1)) .And(x => ThenThePriorityIs(0))
.BDDfy(); .BDDfy();
} }
[Fact] [Fact]
public void should_match_forward_slash_or_no_forward_slash_if_template_end_with_forward_slash() public void should_set_upstream_template_pattern_to_ignore_case_sensitivity()
{ {
var fileReRoute = new FileReRoute var fileReRoute = new FileReRoute
{ {
UpstreamPathTemplate = "/PRODUCTS/", UpstreamPathTemplate = "/PRODUCTS/{productId}",
ReRouteIsCaseSensitive = false ReRouteIsCaseSensitive = false
}; };
this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute))
.When(x => x.WhenICreateTheTemplatePattern()) .When(x => x.WhenICreateTheTemplatePattern())
.Then(x => x.ThenTheFollowingIsReturned("^(?i)/PRODUCTS(/|)$")) .Then(x => x.ThenTheFollowingIsReturned("^(?i)/PRODUCTS/.+$"))
.And(x => ThenThePriorityIs(1)) .And(x => ThenThePriorityIs(1))
.BDDfy(); .BDDfy();
} }
[Fact] [Fact]
public void should_set_upstream_template_pattern_to_respect_case_sensitivity() public void should_match_forward_slash_or_no_forward_slash_if_template_end_with_forward_slash()
{ {
var fileReRoute = new FileReRoute var fileReRoute = new FileReRoute
{ {
UpstreamPathTemplate = "/PRODUCTS/{productId}", UpstreamPathTemplate = "/PRODUCTS/",
ReRouteIsCaseSensitive = true ReRouteIsCaseSensitive = false
}; };
this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute))
.When(x => x.WhenICreateTheTemplatePattern()) this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute))
.Then(x => x.ThenTheFollowingIsReturned("^/PRODUCTS/.+$")) .When(x => x.WhenICreateTheTemplatePattern())
.And(x => ThenThePriorityIs(1)) .Then(x => x.ThenTheFollowingIsReturned("^(?i)/PRODUCTS(/|)$"))
.BDDfy(); .And(x => ThenThePriorityIs(1))
} .BDDfy();
}
[Fact]
public void should_create_template_pattern_that_matches_anything_to_end_of_string() [Fact]
{ public void should_set_upstream_template_pattern_to_respect_case_sensitivity()
var fileReRoute = new FileReRoute {
{ var fileReRoute = new FileReRoute
UpstreamPathTemplate = "/api/products/{productId}", {
ReRouteIsCaseSensitive = true UpstreamPathTemplate = "/PRODUCTS/{productId}",
}; ReRouteIsCaseSensitive = true
};
this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute))
.When(x => x.WhenICreateTheTemplatePattern()) .When(x => x.WhenICreateTheTemplatePattern())
.Then(x => x.ThenTheFollowingIsReturned("^/api/products/.+$")) .Then(x => x.ThenTheFollowingIsReturned("^/PRODUCTS/.+$"))
.And(x => ThenThePriorityIs(1)) .And(x => ThenThePriorityIs(1))
.BDDfy(); .BDDfy();
} }
[Fact] [Fact]
public void should_create_template_pattern_that_matches_more_than_one_placeholder() public void should_create_template_pattern_that_matches_anything_to_end_of_string()
{ {
var fileReRoute = new FileReRoute var fileReRoute = new FileReRoute
{ {
UpstreamPathTemplate = "/api/products/{productId}/variants/{variantId}", UpstreamPathTemplate = "/api/products/{productId}",
ReRouteIsCaseSensitive = true ReRouteIsCaseSensitive = true
}; };
this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute))
.When(x => x.WhenICreateTheTemplatePattern()) .When(x => x.WhenICreateTheTemplatePattern())
.Then(x => x.ThenTheFollowingIsReturned("^/api/products/.+/variants/.+$")) .Then(x => x.ThenTheFollowingIsReturned("^/api/products/.+$"))
.And(x => ThenThePriorityIs(1)) .And(x => ThenThePriorityIs(1))
.BDDfy(); .BDDfy();
} }
[Fact] [Fact]
public void should_create_template_pattern_that_matches_more_than_one_placeholder_with_trailing_slash() public void should_create_template_pattern_that_matches_more_than_one_placeholder()
{ {
var fileReRoute = new FileReRoute var fileReRoute = new FileReRoute
{ {
UpstreamPathTemplate = "/api/products/{productId}/variants/{variantId}/", UpstreamPathTemplate = "/api/products/{productId}/variants/{variantId}",
ReRouteIsCaseSensitive = true ReRouteIsCaseSensitive = true
}; };
this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute))
.When(x => x.WhenICreateTheTemplatePattern()) .When(x => x.WhenICreateTheTemplatePattern())
.Then(x => x.ThenTheFollowingIsReturned("^/api/products/.+/variants/.+(/|)$")) .Then(x => x.ThenTheFollowingIsReturned("^/api/products/[^/]+/variants/.+$"))
.And(x => ThenThePriorityIs(1)) .And(x => ThenThePriorityIs(1))
.BDDfy(); .BDDfy();
} }
[Fact] [Fact]
public void should_create_template_pattern_that_matches_to_end_of_string() public void should_create_template_pattern_that_matches_more_than_one_placeholder_with_trailing_slash()
{ {
var fileReRoute = new FileReRoute var fileReRoute = new FileReRoute
{ {
UpstreamPathTemplate = "/" UpstreamPathTemplate = "/api/products/{productId}/variants/{variantId}/",
}; ReRouteIsCaseSensitive = true
};
this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute))
.When(x => x.WhenICreateTheTemplatePattern()) this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute))
.Then(x => x.ThenTheFollowingIsReturned("^/$")) .When(x => x.WhenICreateTheTemplatePattern())
.And(x => ThenThePriorityIs(1)) .Then(x => x.ThenTheFollowingIsReturned("^/api/products/[^/]+/variants/[^/]+(/|)$"))
.BDDfy(); .And(x => ThenThePriorityIs(1))
} .BDDfy();
}
[Fact]
public void should_create_template_pattern_that_matches_to_end_of_string_when_slash_and_placeholder() [Fact]
{ public void should_create_template_pattern_that_matches_to_end_of_string()
var fileReRoute = new FileReRoute {
{ var fileReRoute = new FileReRoute
UpstreamPathTemplate = "/{url}" {
}; UpstreamPathTemplate = "/"
};
this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute))
.When(x => x.WhenICreateTheTemplatePattern()) this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute))
.Then(x => x.ThenTheFollowingIsReturned("^/.*")) .When(x => x.WhenICreateTheTemplatePattern())
.And(x => ThenThePriorityIs(0)) .Then(x => x.ThenTheFollowingIsReturned("^/$"))
.BDDfy(); .And(x => ThenThePriorityIs(1))
} .BDDfy();
}
[Fact]
public void should_create_template_pattern_that_starts_with_placeholder_then_has_another_later() [Fact]
{ public void should_create_template_pattern_that_matches_to_end_of_string_when_slash_and_placeholder()
var fileReRoute = new FileReRoute {
{ var fileReRoute = new FileReRoute
UpstreamPathTemplate = "/{productId}/products/variants/{variantId}/", {
ReRouteIsCaseSensitive = true UpstreamPathTemplate = "/{url}"
}; };
this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute))
.When(x => x.WhenICreateTheTemplatePattern()) .When(x => x.WhenICreateTheTemplatePattern())
.Then(x => x.ThenTheFollowingIsReturned("^/.+/products/variants/.+(/|)$")) .Then(x => x.ThenTheFollowingIsReturned("^/.*"))
.And(x => ThenThePriorityIs(1)) .And(x => ThenThePriorityIs(0))
.BDDfy(); .BDDfy();
} }
[Fact] [Fact]
public void should_create_template_pattern_that_matches_query_string() public void should_create_template_pattern_that_starts_with_placeholder_then_has_another_later()
{ {
var fileReRoute = new FileReRoute var fileReRoute = new FileReRoute
{ {
UpstreamPathTemplate = "/api/subscriptions/{subscriptionId}/updates?unitId={unitId}" UpstreamPathTemplate = "/{productId}/products/variants/{variantId}/",
}; ReRouteIsCaseSensitive = true
};
this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute))
.When(x => x.WhenICreateTheTemplatePattern()) this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute))
.Then(x => x.ThenTheFollowingIsReturned("^(?i)/api/subscriptions/.+/updates\\?unitId=.+$")) .When(x => x.WhenICreateTheTemplatePattern())
.And(x => ThenThePriorityIs(1)) .Then(x => x.ThenTheFollowingIsReturned("^/[^/]+/products/variants/[^/]+(/|)$"))
.BDDfy(); .And(x => ThenThePriorityIs(1))
} .BDDfy();
}
[Fact]
public void should_create_template_pattern_that_matches_query_string_with_multiple_params() [Fact]
{ public void should_create_template_pattern_that_matches_query_string()
var fileReRoute = new FileReRoute {
{ var fileReRoute = new FileReRoute
UpstreamPathTemplate = "/api/subscriptions/{subscriptionId}/updates?unitId={unitId}&productId={productId}" {
}; UpstreamPathTemplate = "/api/subscriptions/{subscriptionId}/updates?unitId={unitId}"
};
this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute))
.When(x => x.WhenICreateTheTemplatePattern()) this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute))
.Then(x => x.ThenTheFollowingIsReturned("^(?i)/api/subscriptions/.+/updates\\?unitId=.+&productId=.+$")) .When(x => x.WhenICreateTheTemplatePattern())
.And(x => ThenThePriorityIs(1)) .Then(x => x.ThenTheFollowingIsReturned("^(?i)/api/subscriptions/[^/]+/updates\\?unitId=.+$"))
.BDDfy(); .And(x => ThenThePriorityIs(1))
} .BDDfy();
}
private void GivenTheFollowingFileReRoute(FileReRoute fileReRoute)
{ [Fact]
_fileReRoute = fileReRoute; public void should_create_template_pattern_that_matches_query_string_with_multiple_params()
} {
var fileReRoute = new FileReRoute
private void WhenICreateTheTemplatePattern() {
{ UpstreamPathTemplate = "/api/subscriptions/{subscriptionId}/updates?unitId={unitId}&productId={productId}"
_result = _creator.Create(_fileReRoute); };
}
this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute))
private void ThenTheFollowingIsReturned(string expected) .When(x => x.WhenICreateTheTemplatePattern())
{ .Then(x => x.ThenTheFollowingIsReturned("^(?i)/api/subscriptions/[^/]+/updates\\?unitId=.+&productId=.+$"))
_result.Template.ShouldBe(expected); .And(x => ThenThePriorityIs(1))
} .BDDfy();
}
private void ThenThePriorityIs(int v)
{ private void GivenTheFollowingFileReRoute(FileReRoute fileReRoute)
_result.Priority.ShouldBe(v); {
} _fileReRoute = fileReRoute;
} }
}
private void WhenICreateTheTemplatePattern()
{
_result = _creator.Create(_fileReRoute);
}
private void ThenTheFollowingIsReturned(string expected)
{
_result.Template.ShouldBe(expected);
}
private void ThenThePriorityIs(int v)
{
_result.Priority.ShouldBe(v);
}
}
}

View File

@ -1,271 +1,291 @@
using Ocelot.DownstreamRouteFinder.UrlMatcher; using Ocelot.DownstreamRouteFinder.UrlMatcher;
using Ocelot.Responses; using Ocelot.Responses;
using Shouldly; using Shouldly;
using TestStack.BDDfy; using TestStack.BDDfy;
using Xunit; using Xunit;
namespace Ocelot.UnitTests.DownstreamRouteFinder.UrlMatcher namespace Ocelot.UnitTests.DownstreamRouteFinder.UrlMatcher
{ {
public class RegExUrlMatcherTests public class RegExUrlMatcherTests
{ {
private readonly IUrlPathToUrlTemplateMatcher _urlMatcher; private readonly IUrlPathToUrlTemplateMatcher _urlMatcher;
private string _path; private string _path;
private string _downstreamPathTemplate; private string _downstreamPathTemplate;
private Response<UrlMatch> _result; private Response<UrlMatch> _result;
private string _queryString; private string _queryString;
private bool _containsQueryString; private bool _containsQueryString;
public RegExUrlMatcherTests() public RegExUrlMatcherTests()
{ {
_urlMatcher = new RegExUrlMatcher(); _urlMatcher = new RegExUrlMatcher();
}
[Fact]
public void should_match_path_with_no_query_string()
{
const string regExForwardSlashAndOnePlaceHolder = "^(?i)/newThing$";
this.Given(x => x.GivenIHaveAUpstreamPath("/newThing"))
.And(_ => GivenIHaveAQueryString("?DeviceType=IphoneApp&Browser=moonpigIphone&BrowserString=-&CountryCode=123&DeviceName=iPhone 5 (GSM+CDMA)&OperatingSystem=iPhone OS 7.1.2&BrowserVersion=3708AdHoc&ipAddress=-"))
.And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern(regExForwardSlashAndOnePlaceHolder))
.When(x => x.WhenIMatchThePaths())
.And(x => x.ThenTheResultIsTrue())
.BDDfy();
} }
[Fact] [Fact]
public void should_match_query_string() public void should_not_match()
{ {
const string regExForwardSlashAndOnePlaceHolder = "^(?i)/api/subscriptions/.+/updates\\?unitId=.+$"; this.Given(x => x.GivenIHaveAUpstreamPath("/api/v1/aaaaaaaaa/cards"))
.And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^(?i)/api/v[^/]+/cards$"))
this.Given(x => x.GivenIHaveAUpstreamPath("/api/subscriptions/1/updates")) .When(x => x.WhenIMatchThePaths())
.And(_ => GivenIHaveAQueryString("?unitId=2")) .And(x => x.ThenTheResultIsFalse())
.And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern(regExForwardSlashAndOnePlaceHolder)) .BDDfy();
.And(_ => GivenThereIsAQueryInTemplate()) }
.When(x => x.WhenIMatchThePaths())
.And(x => x.ThenTheResultIsTrue()) [Fact]
.BDDfy(); public void should_match()
} {
this.Given(x => x.GivenIHaveAUpstreamPath("/api/v1/cards"))
[Fact] .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^(?i)/api/v[^/]+/cards$"))
public void should_match_query_string_with_multiple_params() .When(x => x.WhenIMatchThePaths())
{ .And(x => x.ThenTheResultIsTrue())
const string regExForwardSlashAndOnePlaceHolder = "^(?i)/api/subscriptions/.+/updates\\?unitId=.+&productId=.+$"; .BDDfy();
}
this.Given(x => x.GivenIHaveAUpstreamPath("/api/subscriptions/1/updates?unitId=2"))
.And(_ => GivenIHaveAQueryString("?unitId=2&productId=2")) [Fact]
.And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern(regExForwardSlashAndOnePlaceHolder)) public void should_match_path_with_no_query_string()
.And(_ => GivenThereIsAQueryInTemplate()) {
.When(x => x.WhenIMatchThePaths()) const string regExForwardSlashAndOnePlaceHolder = "^(?i)/newThing$";
.And(x => x.ThenTheResultIsTrue())
.BDDfy(); this.Given(x => x.GivenIHaveAUpstreamPath("/newThing"))
} .And(_ => GivenIHaveAQueryString("?DeviceType=IphoneApp&Browser=moonpigIphone&BrowserString=-&CountryCode=123&DeviceName=iPhone 5 (GSM+CDMA)&OperatingSystem=iPhone OS 7.1.2&BrowserVersion=3708AdHoc&ipAddress=-"))
.And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern(regExForwardSlashAndOnePlaceHolder))
[Fact] .When(x => x.WhenIMatchThePaths())
public void should_not_match_slash_becaue_we_need_to_match_something_after_it() .And(x => x.ThenTheResultIsTrue())
{ .BDDfy();
const string regExForwardSlashAndOnePlaceHolder = "^/[0-9a-zA-Z].+"; }
this.Given(x => x.GivenIHaveAUpstreamPath("/")) [Fact]
.And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern(regExForwardSlashAndOnePlaceHolder)) public void should_match_query_string()
.When(x => x.WhenIMatchThePaths()) {
.And(x => x.ThenTheResultIsFalse()) const string regExForwardSlashAndOnePlaceHolder = "^(?i)/api/subscriptions/[^/]+/updates\\?unitId=.+$";
.BDDfy();
} this.Given(x => x.GivenIHaveAUpstreamPath("/api/subscriptions/1/updates"))
.And(_ => GivenIHaveAQueryString("?unitId=2"))
[Fact] .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern(regExForwardSlashAndOnePlaceHolder))
public void should_not_match_forward_slash_only_regex() .And(_ => GivenThereIsAQueryInTemplate())
{ .When(x => x.WhenIMatchThePaths())
this.Given(x => x.GivenIHaveAUpstreamPath("/working/")) .And(x => x.ThenTheResultIsTrue())
.And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^/$")) .BDDfy();
.When(x => x.WhenIMatchThePaths()) }
.And(x => x.ThenTheResultIsFalse())
.BDDfy(); [Fact]
} public void should_match_query_string_with_multiple_params()
{
[Fact] const string regExForwardSlashAndOnePlaceHolder = "^(?i)/api/subscriptions/[^/]+/updates\\?unitId=.+&productId=.+$";
public void should_not_match_issue_134()
{ this.Given(x => x.GivenIHaveAUpstreamPath("/api/subscriptions/1/updates?unitId=2"))
this.Given(x => x.GivenIHaveAUpstreamPath("/api/vacancy/1/")) .And(_ => GivenIHaveAQueryString("?unitId=2&productId=2"))
.And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^(?i)/vacancy/.+/$")) .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern(regExForwardSlashAndOnePlaceHolder))
.When(x => x.WhenIMatchThePaths()) .And(_ => GivenThereIsAQueryInTemplate())
.And(x => x.ThenTheResultIsFalse()) .When(x => x.WhenIMatchThePaths())
.BDDfy(); .And(x => x.ThenTheResultIsTrue())
} .BDDfy();
}
[Fact]
public void should_match_forward_slash_only_regex() [Fact]
{ public void should_not_match_slash_becaue_we_need_to_match_something_after_it()
this.Given(x => x.GivenIHaveAUpstreamPath("/")) {
.And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^/$")) const string regExForwardSlashAndOnePlaceHolder = "^/[0-9a-zA-Z].+";
.When(x => x.WhenIMatchThePaths())
.And(x => x.ThenTheResultIsTrue()) this.Given(x => x.GivenIHaveAUpstreamPath("/"))
.BDDfy(); .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern(regExForwardSlashAndOnePlaceHolder))
} .When(x => x.WhenIMatchThePaths())
.And(x => x.ThenTheResultIsFalse())
[Fact] .BDDfy();
public void should_find_match_when_template_smaller_than_valid_path() }
{
this.Given(x => x.GivenIHaveAUpstreamPath("/api/products/2354325435624623464235")) [Fact]
.And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^/api/products/.+$")) public void should_not_match_forward_slash_only_regex()
.When(x => x.WhenIMatchThePaths()) {
.And(x => x.ThenTheResultIsTrue()) this.Given(x => x.GivenIHaveAUpstreamPath("/working/"))
.BDDfy(); .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^/$"))
} .When(x => x.WhenIMatchThePaths())
.And(x => x.ThenTheResultIsFalse())
[Fact] .BDDfy();
public void should_not_find_match() }
{
this.Given(x => x.GivenIHaveAUpstreamPath("/api/values")) [Fact]
.And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^/$")) public void should_not_match_issue_134()
.When(x => x.WhenIMatchThePaths()) {
.And(x => x.ThenTheResultIsFalse()) this.Given(x => x.GivenIHaveAUpstreamPath("/api/vacancy/1/"))
.BDDfy(); .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^(?i)/vacancy/[^/]+/$"))
} .When(x => x.WhenIMatchThePaths())
.And(x => x.ThenTheResultIsFalse())
[Fact] .BDDfy();
public void can_match_down_stream_url() }
{
this.Given(x => x.GivenIHaveAUpstreamPath("")) [Fact]
.And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^$")) public void should_match_forward_slash_only_regex()
.When(x => x.WhenIMatchThePaths()) {
.And(x => x.ThenTheResultIsTrue()) this.Given(x => x.GivenIHaveAUpstreamPath("/"))
.BDDfy(); .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^/$"))
} .When(x => x.WhenIMatchThePaths())
.And(x => x.ThenTheResultIsTrue())
[Fact] .BDDfy();
public void can_match_down_stream_url_with_no_slash() }
{
this.Given(x => x.GivenIHaveAUpstreamPath("api")) [Fact]
.Given(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api$")) public void should_find_match_when_template_smaller_than_valid_path()
.When(x => x.WhenIMatchThePaths()) {
.Then(x => x.ThenTheResultIsTrue()) this.Given(x => x.GivenIHaveAUpstreamPath("/api/products/2354325435624623464235"))
.BDDfy(); .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^/api/products/.+$"))
} .When(x => x.WhenIMatchThePaths())
.And(x => x.ThenTheResultIsTrue())
[Fact] .BDDfy();
public void can_match_down_stream_url_with_one_slash() }
{
this.Given(x => x.GivenIHaveAUpstreamPath("api/")) [Fact]
.Given(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api/$")) public void should_not_find_match()
.When(x => x.WhenIMatchThePaths()) {
.Then(x => x.ThenTheResultIsTrue()) this.Given(x => x.GivenIHaveAUpstreamPath("/api/values"))
.BDDfy(); .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^/$"))
} .When(x => x.WhenIMatchThePaths())
.And(x => x.ThenTheResultIsFalse())
[Fact] .BDDfy();
public void can_match_down_stream_url_with_downstream_template() }
{
this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/")) [Fact]
.Given(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api/product/products/$")) public void can_match_down_stream_url()
.When(x => x.WhenIMatchThePaths()) {
.Then(x => x.ThenTheResultIsTrue()) this.Given(x => x.GivenIHaveAUpstreamPath(""))
.BDDfy(); .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^$"))
} .When(x => x.WhenIMatchThePaths())
.And(x => x.ThenTheResultIsTrue())
[Fact] .BDDfy();
public void can_match_down_stream_url_with_downstream_template_with_one_place_holder() }
{
this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/1")) [Fact]
.Given(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api/product/products/.+$")) public void can_match_down_stream_url_with_no_slash()
.When(x => x.WhenIMatchThePaths()) {
.Then(x => x.ThenTheResultIsTrue()) this.Given(x => x.GivenIHaveAUpstreamPath("api"))
.BDDfy(); .Given(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api$"))
} .When(x => x.WhenIMatchThePaths())
.Then(x => x.ThenTheResultIsTrue())
[Fact] .BDDfy();
public void can_match_down_stream_url_with_downstream_template_with_two_place_holders() }
{
this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/1/2")) [Fact]
.Given(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api/product/products/.+/.+$")) public void can_match_down_stream_url_with_one_slash()
.When(x => x.WhenIMatchThePaths()) {
.Then(x => x.ThenTheResultIsTrue()) this.Given(x => x.GivenIHaveAUpstreamPath("api/"))
.BDDfy(); .Given(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api/$"))
} .When(x => x.WhenIMatchThePaths())
.Then(x => x.ThenTheResultIsTrue())
[Fact] .BDDfy();
public void can_match_down_stream_url_with_downstream_template_with_two_place_holders_seperated_by_something() }
{
this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/1/categories/2")) [Fact]
.And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api/product/products/.+/categories/.+$")) public void can_match_down_stream_url_with_downstream_template()
.When(x => x.WhenIMatchThePaths()) {
.Then(x => x.ThenTheResultIsTrue()) this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/"))
.BDDfy(); .Given(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api/product/products/$"))
} .When(x => x.WhenIMatchThePaths())
.Then(x => x.ThenTheResultIsTrue())
[Fact] .BDDfy();
public void can_match_down_stream_url_with_downstream_template_with_three_place_holders_seperated_by_something() }
{
this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/1/categories/2/variant/123")) [Fact]
.And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api/product/products/.+/categories/.+/variant/.+$")) public void can_match_down_stream_url_with_downstream_template_with_one_place_holder()
.When(x => x.WhenIMatchThePaths()) {
.Then(x => x.ThenTheResultIsTrue()) this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/1"))
.BDDfy(); .Given(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api/product/products/.+$"))
} .When(x => x.WhenIMatchThePaths())
.Then(x => x.ThenTheResultIsTrue())
[Fact] .BDDfy();
public void can_match_down_stream_url_with_downstream_template_with_three_place_holders() }
{
this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/1/categories/2/variant/")) [Fact]
.And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api/product/products/.+/categories/.+/variant/$")) public void can_match_down_stream_url_with_downstream_template_with_two_place_holders()
.When(x => x.WhenIMatchThePaths()) {
.Then(x => x.ThenTheResultIsTrue()) this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/1/2"))
.BDDfy(); .Given(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api/product/products/[^/]+/.+$"))
} .When(x => x.WhenIMatchThePaths())
.Then(x => x.ThenTheResultIsTrue())
[Fact] .BDDfy();
public void should_ignore_case_sensitivity() }
{
this.Given(x => x.GivenIHaveAUpstreamPath("API/product/products/1/categories/2/variant/")) [Fact]
.And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^(?i)api/product/products/.+/categories/.+/variant/$")) public void can_match_down_stream_url_with_downstream_template_with_two_place_holders_seperated_by_something()
.When(x => x.WhenIMatchThePaths()) {
.Then(x => x.ThenTheResultIsTrue()) this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/1/categories/2"))
.BDDfy(); .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api/product/products/[^/]+/categories/.+$"))
} .When(x => x.WhenIMatchThePaths())
.Then(x => x.ThenTheResultIsTrue())
[Fact] .BDDfy();
public void should_respect_case_sensitivity() }
{
this.Given(x => x.GivenIHaveAUpstreamPath("API/product/products/1/categories/2/variant/")) [Fact]
.And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api/product/products/.+/categories/.+/variant/$")) public void can_match_down_stream_url_with_downstream_template_with_three_place_holders_seperated_by_something()
.When(x => x.WhenIMatchThePaths()) {
.Then(x => x.ThenTheResultIsFalse()) this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/1/categories/2/variant/123"))
.BDDfy(); .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api/product/products/[^/]+/categories/[^/]+/variant/.+$"))
} .When(x => x.WhenIMatchThePaths())
.Then(x => x.ThenTheResultIsTrue())
private void GivenIHaveAUpstreamPath(string path) .BDDfy();
{ }
_path = path;
} [Fact]
public void can_match_down_stream_url_with_downstream_template_with_three_place_holders()
private void GivenIHaveAQueryString(string queryString) {
{ this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/1/categories/2/variant/"))
_queryString = queryString; .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api/product/products/[^/]+/categories/[^/]+/variant/$"))
} .When(x => x.WhenIMatchThePaths())
.Then(x => x.ThenTheResultIsTrue())
private void GivenIHaveAnUpstreamUrlTemplatePattern(string downstreamUrlTemplate) .BDDfy();
{ }
_downstreamPathTemplate = downstreamUrlTemplate;
} [Fact]
public void should_ignore_case_sensitivity()
private void WhenIMatchThePaths() {
{ this.Given(x => x.GivenIHaveAUpstreamPath("API/product/products/1/categories/2/variant/"))
_result = _urlMatcher.Match(_path, _queryString, _downstreamPathTemplate, _containsQueryString); .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^(?i)api/product/products/[^/]+/categories/[^/]+/variant/$"))
} .When(x => x.WhenIMatchThePaths())
.Then(x => x.ThenTheResultIsTrue())
private void ThenTheResultIsTrue() .BDDfy();
{ }
_result.Data.Match.ShouldBeTrue();
} [Fact]
public void should_respect_case_sensitivity()
private void ThenTheResultIsFalse() {
{ this.Given(x => x.GivenIHaveAUpstreamPath("API/product/products/1/categories/2/variant/"))
_result.Data.Match.ShouldBeFalse(); .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api/product/products/[^/]+/categories/[^/]+/variant/$"))
} .When(x => x.WhenIMatchThePaths())
.Then(x => x.ThenTheResultIsFalse())
private void GivenThereIsAQueryInTemplate() .BDDfy();
{ }
_containsQueryString = true;
} private void GivenIHaveAUpstreamPath(string path)
} {
} _path = path;
}
private void GivenIHaveAQueryString(string queryString)
{
_queryString = queryString;
}
private void GivenIHaveAnUpstreamUrlTemplatePattern(string downstreamUrlTemplate)
{
_downstreamPathTemplate = downstreamUrlTemplate;
}
private void WhenIMatchThePaths()
{
_result = _urlMatcher.Match(_path, _queryString, _downstreamPathTemplate, _containsQueryString);
}
private void ThenTheResultIsTrue()
{
_result.Data.Match.ShouldBeTrue();
}
private void ThenTheResultIsFalse()
{
_result.Data.Match.ShouldBeFalse();
}
private void GivenThereIsAQueryInTemplate()
{
_containsQueryString = true;
}
}
}