From 023118ca703164a9cec06b9c1825a9fee32ae397 Mon Sep 17 00:00:00 2001 From: Zbynek Novotny Date: Wed, 19 Jul 2023 21:37:08 +0200 Subject: [PATCH] Reimplementation of changes for PR 964 (GH ticket #360) for the current code base. --- .../Configuration/Builder/RouteBuilder.cs | 153 +- .../IUpstreamHeaderRoutingOptionsCreator.cs | 8 + .../Configuration/Creator/RoutesCreator.cs | 333 ++-- .../UpstreamHeaderRoutingOptionsCreator.cs | 25 + src/Ocelot/Configuration/File/FileRoute.cs | 122 +- .../File/FileUpstreamHeaderRoutingOptions.cs | 16 + src/Ocelot/Configuration/Route.cs | 5 +- .../UpstreamHeaderRoutingOptions.cs | 18 + .../UpstreamHeaderRoutingTriggerMode.cs | 20 + .../Configuration/UpstreamRoutingHeaders.cs | 81 + .../DependencyInjection/OcelotBuilder.cs | 1 + .../Finder/DownstreamRouteCreator.cs | 264 +-- .../Finder/DownstreamRouteFinder.cs | 150 +- .../Finder/IDownstreamRouteProvider.cs | 27 +- .../DownstreamRouteFinderMiddleware.cs | 4 +- .../Configuration/RoutesCreatorTests.cs | 543 +++--- ...pstreamHeaderRoutingOptionsCreatorTests.cs | 68 + .../UpstreamRoutingHeadersTests.cs | 145 ++ .../DownstreamRouteCreatorTests.cs | 589 +++--- .../DownstreamRouteFinderMiddlewareTests.cs | 2 +- .../DownstreamRouteFinderTests.cs | 1669 +++++++++-------- 21 files changed, 2416 insertions(+), 1827 deletions(-) create mode 100644 src/Ocelot/Configuration/Creator/IUpstreamHeaderRoutingOptionsCreator.cs create mode 100644 src/Ocelot/Configuration/Creator/UpstreamHeaderRoutingOptionsCreator.cs create mode 100644 src/Ocelot/Configuration/File/FileUpstreamHeaderRoutingOptions.cs create mode 100644 src/Ocelot/Configuration/UpstreamHeaderRoutingOptions.cs create mode 100644 src/Ocelot/Configuration/UpstreamHeaderRoutingTriggerMode.cs create mode 100644 src/Ocelot/Configuration/UpstreamRoutingHeaders.cs create mode 100644 test/Ocelot.UnitTests/Configuration/UpstreamHeaderRoutingOptionsCreatorTests.cs create mode 100644 test/Ocelot.UnitTests/Configuration/UpstreamRoutingHeadersTests.cs diff --git a/src/Ocelot/Configuration/Builder/RouteBuilder.cs b/src/Ocelot/Configuration/Builder/RouteBuilder.cs index 8129606cf9..3a943c9e68 100644 --- a/src/Ocelot/Configuration/Builder/RouteBuilder.cs +++ b/src/Ocelot/Configuration/Builder/RouteBuilder.cs @@ -5,76 +5,83 @@ using Ocelot.Configuration.File; using Ocelot.Values; - -namespace Ocelot.Configuration.Builder -{ - public class RouteBuilder - { - private UpstreamPathTemplate _upstreamTemplatePattern; - private List _upstreamHttpMethod; - private string _upstreamHost; - private List _downstreamRoutes; - private List _downstreamRoutesConfig; - private string _aggregator; - - public RouteBuilder() - { - _downstreamRoutes = new List(); - _downstreamRoutesConfig = new List(); - } - - public RouteBuilder WithDownstreamRoute(DownstreamRoute value) - { - _downstreamRoutes.Add(value); - return this; - } - - public RouteBuilder WithDownstreamRoutes(List value) - { - _downstreamRoutes = value; - return this; - } - - public RouteBuilder WithUpstreamHost(string upstreamAddresses) - { - _upstreamHost = upstreamAddresses; - return this; - } - - public RouteBuilder WithUpstreamPathTemplate(UpstreamPathTemplate input) - { - _upstreamTemplatePattern = input; - return this; - } - - public RouteBuilder WithUpstreamHttpMethod(List input) - { - _upstreamHttpMethod = (input.Count == 0) ? new List() : input.Select(x => new HttpMethod(x.Trim())).ToList(); - return this; - } - - public RouteBuilder WithAggregateRouteConfig(List aggregateRouteConfigs) - { - _downstreamRoutesConfig = aggregateRouteConfigs; - return this; - } - - public RouteBuilder WithAggregator(string aggregator) - { - _aggregator = aggregator; - return this; - } - - public Route Build() - { - return new Route( - _downstreamRoutes, - _downstreamRoutesConfig, - _upstreamHttpMethod, - _upstreamTemplatePattern, - _upstreamHost, - _aggregator - ); - } - } -} + +namespace Ocelot.Configuration.Builder +{ + public class RouteBuilder + { + private UpstreamPathTemplate _upstreamTemplatePattern; + private List _upstreamHttpMethod; + private string _upstreamHost; + private List _downstreamRoutes; + private List _downstreamRoutesConfig; + private string _aggregator; + private UpstreamHeaderRoutingOptions _upstreamHeaderRoutingOptions; + + public RouteBuilder() + { + _downstreamRoutes = new List(); + _downstreamRoutesConfig = new List(); + } + + public RouteBuilder WithDownstreamRoute(DownstreamRoute value) + { + _downstreamRoutes.Add(value); + return this; + } + + public RouteBuilder WithDownstreamRoutes(List value) + { + _downstreamRoutes = value; + return this; + } + + public RouteBuilder WithUpstreamHost(string upstreamAddresses) + { + _upstreamHost = upstreamAddresses; + return this; + } + + public RouteBuilder WithUpstreamPathTemplate(UpstreamPathTemplate input) + { + _upstreamTemplatePattern = input; + return this; + } + + public RouteBuilder WithUpstreamHttpMethod(List input) + { + _upstreamHttpMethod = (input.Count == 0) ? new List() : input.Select(x => new HttpMethod(x.Trim())).ToList(); + return this; + } + + public RouteBuilder WithAggregateRouteConfig(List aggregateRouteConfigs) + { + _downstreamRoutesConfig = aggregateRouteConfigs; + return this; + } + + public RouteBuilder WithAggregator(string aggregator) + { + _aggregator = aggregator; + return this; + } + + public RouteBuilder WithUpstreamHeaderRoutingOptions(UpstreamHeaderRoutingOptions routingOptions) + { + _upstreamHeaderRoutingOptions = routingOptions; + return this; + } + + public Route Build() + { + return new Route( + _downstreamRoutes, + _downstreamRoutesConfig, + _upstreamHttpMethod, + _upstreamTemplatePattern, + _upstreamHost, + _aggregator, + _upstreamHeaderRoutingOptions); + } + } +} diff --git a/src/Ocelot/Configuration/Creator/IUpstreamHeaderRoutingOptionsCreator.cs b/src/Ocelot/Configuration/Creator/IUpstreamHeaderRoutingOptionsCreator.cs new file mode 100644 index 0000000000..bd26ad130c --- /dev/null +++ b/src/Ocelot/Configuration/Creator/IUpstreamHeaderRoutingOptionsCreator.cs @@ -0,0 +1,8 @@ +using Ocelot.Configuration.File; + +namespace Ocelot.Configuration.Creator; + +public interface IUpstreamHeaderRoutingOptionsCreator +{ + UpstreamHeaderRoutingOptions Create(FileUpstreamHeaderRoutingOptions options); +} diff --git a/src/Ocelot/Configuration/Creator/RoutesCreator.cs b/src/Ocelot/Configuration/Creator/RoutesCreator.cs index 7161aad78c..7d3f153c17 100644 --- a/src/Ocelot/Configuration/Creator/RoutesCreator.cs +++ b/src/Ocelot/Configuration/Creator/RoutesCreator.cs @@ -5,166 +5,173 @@ using Ocelot.Cache; -using Ocelot.Configuration.File; - -namespace Ocelot.Configuration.Creator -{ - public class RoutesCreator : IRoutesCreator - { - private readonly ILoadBalancerOptionsCreator _loadBalancerOptionsCreator; - private readonly IClaimsToThingCreator _claimsToThingCreator; - private readonly IAuthenticationOptionsCreator _authOptionsCreator; - private readonly IUpstreamTemplatePatternCreator _upstreamTemplatePatternCreator; - private readonly IRequestIdKeyCreator _requestIdKeyCreator; - private readonly IQoSOptionsCreator _qosOptionsCreator; - private readonly IRouteOptionsCreator _fileRouteOptionsCreator; - private readonly IRateLimitOptionsCreator _rateLimitOptionsCreator; - private readonly IRegionCreator _regionCreator; - private readonly IHttpHandlerOptionsCreator _httpHandlerOptionsCreator; - private readonly IHeaderFindAndReplaceCreator _headerFAndRCreator; - private readonly IDownstreamAddressesCreator _downstreamAddressesCreator; - private readonly IRouteKeyCreator _routeKeyCreator; - private readonly ISecurityOptionsCreator _securityOptionsCreator; - private readonly IVersionCreator _versionCreator; - - public RoutesCreator( - IClaimsToThingCreator claimsToThingCreator, - IAuthenticationOptionsCreator authOptionsCreator, - IUpstreamTemplatePatternCreator upstreamTemplatePatternCreator, - IRequestIdKeyCreator requestIdKeyCreator, - IQoSOptionsCreator qosOptionsCreator, - IRouteOptionsCreator fileRouteOptionsCreator, - IRateLimitOptionsCreator rateLimitOptionsCreator, - IRegionCreator regionCreator, - IHttpHandlerOptionsCreator httpHandlerOptionsCreator, - IHeaderFindAndReplaceCreator headerFAndRCreator, - IDownstreamAddressesCreator downstreamAddressesCreator, - ILoadBalancerOptionsCreator loadBalancerOptionsCreator, - IRouteKeyCreator routeKeyCreator, - ISecurityOptionsCreator securityOptionsCreator, - IVersionCreator versionCreator - ) - { - _routeKeyCreator = routeKeyCreator; - _loadBalancerOptionsCreator = loadBalancerOptionsCreator; - _downstreamAddressesCreator = downstreamAddressesCreator; - _headerFAndRCreator = headerFAndRCreator; - _regionCreator = regionCreator; - _rateLimitOptionsCreator = rateLimitOptionsCreator; - _requestIdKeyCreator = requestIdKeyCreator; - _upstreamTemplatePatternCreator = upstreamTemplatePatternCreator; - _authOptionsCreator = authOptionsCreator; - _claimsToThingCreator = claimsToThingCreator; - _qosOptionsCreator = qosOptionsCreator; - _fileRouteOptionsCreator = fileRouteOptionsCreator; - _httpHandlerOptionsCreator = httpHandlerOptionsCreator; - _loadBalancerOptionsCreator = loadBalancerOptionsCreator; - _securityOptionsCreator = securityOptionsCreator; - _versionCreator = versionCreator; - } - - public List Create(FileConfiguration fileConfiguration) - { - return fileConfiguration.Routes - .Select(route => - { - var downstreamRoute = SetUpDownstreamRoute(route, fileConfiguration.GlobalConfiguration); - return SetUpRoute(route, downstreamRoute); - }) - .ToList(); - } - - private DownstreamRoute SetUpDownstreamRoute(FileRoute fileRoute, FileGlobalConfiguration globalConfiguration) - { - var fileRouteOptions = _fileRouteOptionsCreator.Create(fileRoute); - - var requestIdKey = _requestIdKeyCreator.Create(fileRoute, globalConfiguration); - - var routeKey = _routeKeyCreator.Create(fileRoute); - - var upstreamTemplatePattern = _upstreamTemplatePatternCreator.Create(fileRoute); - - var authOptionsForRoute = _authOptionsCreator.Create(fileRoute); - - var claimsToHeaders = _claimsToThingCreator.Create(fileRoute.AddHeadersToRequest); - - var claimsToClaims = _claimsToThingCreator.Create(fileRoute.AddClaimsToRequest); - - var claimsToQueries = _claimsToThingCreator.Create(fileRoute.AddQueriesToRequest); - - var claimsToDownstreamPath = _claimsToThingCreator.Create(fileRoute.ChangeDownstreamPathTemplate); - - var qosOptions = _qosOptionsCreator.Create(fileRoute.QoSOptions, fileRoute.UpstreamPathTemplate, fileRoute.UpstreamHttpMethod); - - var rateLimitOption = _rateLimitOptionsCreator.Create(fileRoute.RateLimitOptions, globalConfiguration); - - var region = _regionCreator.Create(fileRoute); - - var httpHandlerOptions = _httpHandlerOptionsCreator.Create(fileRoute.HttpHandlerOptions); - - var hAndRs = _headerFAndRCreator.Create(fileRoute); - - var downstreamAddresses = _downstreamAddressesCreator.Create(fileRoute); - - var lbOptions = _loadBalancerOptionsCreator.Create(fileRoute.LoadBalancerOptions); - - var securityOptions = _securityOptionsCreator.Create(fileRoute.SecurityOptions); - - var downstreamHttpVersion = _versionCreator.Create(fileRoute.DownstreamHttpVersion); - - var route = new DownstreamRouteBuilder() - .WithKey(fileRoute.Key) - .WithDownstreamPathTemplate(fileRoute.DownstreamPathTemplate) - .WithUpstreamHttpMethod(fileRoute.UpstreamHttpMethod) - .WithUpstreamPathTemplate(upstreamTemplatePattern) - .WithIsAuthenticated(fileRouteOptions.IsAuthenticated) - .WithAuthenticationOptions(authOptionsForRoute) - .WithClaimsToHeaders(claimsToHeaders) - .WithClaimsToClaims(claimsToClaims) - .WithRouteClaimsRequirement(fileRoute.RouteClaimsRequirement) - .WithIsAuthorized(fileRouteOptions.IsAuthorized) - .WithClaimsToQueries(claimsToQueries) - .WithClaimsToDownstreamPath(claimsToDownstreamPath) - .WithRequestIdKey(requestIdKey) - .WithIsCached(fileRouteOptions.IsCached) - .WithCacheOptions(new CacheOptions(fileRoute.FileCacheOptions.TtlSeconds, region)) - .WithDownstreamScheme(fileRoute.DownstreamScheme) - .WithLoadBalancerOptions(lbOptions) - .WithDownstreamAddresses(downstreamAddresses) - .WithLoadBalancerKey(routeKey) - .WithQosOptions(qosOptions) - .WithEnableRateLimiting(fileRouteOptions.EnableRateLimiting) - .WithRateLimitOptions(rateLimitOption) - .WithHttpHandlerOptions(httpHandlerOptions) - .WithServiceName(fileRoute.ServiceName) - .WithServiceNamespace(fileRoute.ServiceNamespace) - .WithUseServiceDiscovery(fileRouteOptions.UseServiceDiscovery) - .WithUpstreamHeaderFindAndReplace(hAndRs.Upstream) - .WithDownstreamHeaderFindAndReplace(hAndRs.Downstream) - .WithDelegatingHandlers(fileRoute.DelegatingHandlers) - .WithAddHeadersToDownstream(hAndRs.AddHeadersToDownstream) - .WithAddHeadersToUpstream(hAndRs.AddHeadersToUpstream) - .WithDangerousAcceptAnyServerCertificateValidator(fileRoute.DangerousAcceptAnyServerCertificateValidator) - .WithSecurityOptions(securityOptions) - .WithDownstreamHttpVersion(downstreamHttpVersion) - .WithDownStreamHttpMethod(fileRoute.DownstreamHttpMethod) - .Build(); - - return route; - } - - private Route SetUpRoute(FileRoute fileRoute, DownstreamRoute downstreamRoutes) - { - var upstreamTemplatePattern = _upstreamTemplatePatternCreator.Create(fileRoute); - - var route = new RouteBuilder() - .WithUpstreamHttpMethod(fileRoute.UpstreamHttpMethod) - .WithUpstreamPathTemplate(upstreamTemplatePattern) - .WithDownstreamRoute(downstreamRoutes) - .WithUpstreamHost(fileRoute.UpstreamHost) - .Build(); - - return route; - } - } -} +using Ocelot.Configuration.File; + +namespace Ocelot.Configuration.Creator +{ + public class RoutesCreator : IRoutesCreator + { + private readonly ILoadBalancerOptionsCreator _loadBalancerOptionsCreator; + private readonly IClaimsToThingCreator _claimsToThingCreator; + private readonly IAuthenticationOptionsCreator _authOptionsCreator; + private readonly IUpstreamTemplatePatternCreator _upstreamTemplatePatternCreator; + private readonly IRequestIdKeyCreator _requestIdKeyCreator; + private readonly IQoSOptionsCreator _qosOptionsCreator; + private readonly IRouteOptionsCreator _fileRouteOptionsCreator; + private readonly IRateLimitOptionsCreator _rateLimitOptionsCreator; + private readonly IRegionCreator _regionCreator; + private readonly IHttpHandlerOptionsCreator _httpHandlerOptionsCreator; + private readonly IHeaderFindAndReplaceCreator _headerFAndRCreator; + private readonly IDownstreamAddressesCreator _downstreamAddressesCreator; + private readonly IRouteKeyCreator _routeKeyCreator; + private readonly ISecurityOptionsCreator _securityOptionsCreator; + private readonly IVersionCreator _versionCreator; + private readonly IUpstreamHeaderRoutingOptionsCreator _upstreamHeaderRoutingOptionsCreator; + + public RoutesCreator( + IClaimsToThingCreator claimsToThingCreator, + IAuthenticationOptionsCreator authOptionsCreator, + IUpstreamTemplatePatternCreator upstreamTemplatePatternCreator, + IRequestIdKeyCreator requestIdKeyCreator, + IQoSOptionsCreator qosOptionsCreator, + IRouteOptionsCreator fileRouteOptionsCreator, + IRateLimitOptionsCreator rateLimitOptionsCreator, + IRegionCreator regionCreator, + IHttpHandlerOptionsCreator httpHandlerOptionsCreator, + IHeaderFindAndReplaceCreator headerFAndRCreator, + IDownstreamAddressesCreator downstreamAddressesCreator, + ILoadBalancerOptionsCreator loadBalancerOptionsCreator, + IRouteKeyCreator routeKeyCreator, + ISecurityOptionsCreator securityOptionsCreator, + IVersionCreator versionCreator, + IUpstreamHeaderRoutingOptionsCreator upstreamHeaderRoutingOptionsCreator + ) + { + _routeKeyCreator = routeKeyCreator; + _loadBalancerOptionsCreator = loadBalancerOptionsCreator; + _downstreamAddressesCreator = downstreamAddressesCreator; + _headerFAndRCreator = headerFAndRCreator; + _regionCreator = regionCreator; + _rateLimitOptionsCreator = rateLimitOptionsCreator; + _requestIdKeyCreator = requestIdKeyCreator; + _upstreamTemplatePatternCreator = upstreamTemplatePatternCreator; + _authOptionsCreator = authOptionsCreator; + _claimsToThingCreator = claimsToThingCreator; + _qosOptionsCreator = qosOptionsCreator; + _fileRouteOptionsCreator = fileRouteOptionsCreator; + _httpHandlerOptionsCreator = httpHandlerOptionsCreator; + _loadBalancerOptionsCreator = loadBalancerOptionsCreator; + _securityOptionsCreator = securityOptionsCreator; + _versionCreator = versionCreator; + _upstreamHeaderRoutingOptionsCreator = upstreamHeaderRoutingOptionsCreator; + } + + public List Create(FileConfiguration fileConfiguration) + { + return fileConfiguration.Routes + .Select(route => + { + var downstreamRoute = SetUpDownstreamRoute(route, fileConfiguration.GlobalConfiguration); + return SetUpRoute(route, downstreamRoute); + }) + .ToList(); + } + + private DownstreamRoute SetUpDownstreamRoute(FileRoute fileRoute, FileGlobalConfiguration globalConfiguration) + { + var fileRouteOptions = _fileRouteOptionsCreator.Create(fileRoute); + + var requestIdKey = _requestIdKeyCreator.Create(fileRoute, globalConfiguration); + + var routeKey = _routeKeyCreator.Create(fileRoute); + + var upstreamTemplatePattern = _upstreamTemplatePatternCreator.Create(fileRoute); + + var authOptionsForRoute = _authOptionsCreator.Create(fileRoute); + + var claimsToHeaders = _claimsToThingCreator.Create(fileRoute.AddHeadersToRequest); + + var claimsToClaims = _claimsToThingCreator.Create(fileRoute.AddClaimsToRequest); + + var claimsToQueries = _claimsToThingCreator.Create(fileRoute.AddQueriesToRequest); + + var claimsToDownstreamPath = _claimsToThingCreator.Create(fileRoute.ChangeDownstreamPathTemplate); + + var qosOptions = _qosOptionsCreator.Create(fileRoute.QoSOptions, fileRoute.UpstreamPathTemplate, fileRoute.UpstreamHttpMethod); + + var rateLimitOption = _rateLimitOptionsCreator.Create(fileRoute.RateLimitOptions, globalConfiguration); + + var region = _regionCreator.Create(fileRoute); + + var httpHandlerOptions = _httpHandlerOptionsCreator.Create(fileRoute.HttpHandlerOptions); + + var hAndRs = _headerFAndRCreator.Create(fileRoute); + + var downstreamAddresses = _downstreamAddressesCreator.Create(fileRoute); + + var lbOptions = _loadBalancerOptionsCreator.Create(fileRoute.LoadBalancerOptions); + + var securityOptions = _securityOptionsCreator.Create(fileRoute.SecurityOptions); + + var downstreamHttpVersion = _versionCreator.Create(fileRoute.DownstreamHttpVersion); + + var route = new DownstreamRouteBuilder() + .WithKey(fileRoute.Key) + .WithDownstreamPathTemplate(fileRoute.DownstreamPathTemplate) + .WithUpstreamHttpMethod(fileRoute.UpstreamHttpMethod) + .WithUpstreamPathTemplate(upstreamTemplatePattern) + .WithIsAuthenticated(fileRouteOptions.IsAuthenticated) + .WithAuthenticationOptions(authOptionsForRoute) + .WithClaimsToHeaders(claimsToHeaders) + .WithClaimsToClaims(claimsToClaims) + .WithRouteClaimsRequirement(fileRoute.RouteClaimsRequirement) + .WithIsAuthorized(fileRouteOptions.IsAuthorized) + .WithClaimsToQueries(claimsToQueries) + .WithClaimsToDownstreamPath(claimsToDownstreamPath) + .WithRequestIdKey(requestIdKey) + .WithIsCached(fileRouteOptions.IsCached) + .WithCacheOptions(new CacheOptions(fileRoute.FileCacheOptions.TtlSeconds, region)) + .WithDownstreamScheme(fileRoute.DownstreamScheme) + .WithLoadBalancerOptions(lbOptions) + .WithDownstreamAddresses(downstreamAddresses) + .WithLoadBalancerKey(routeKey) + .WithQosOptions(qosOptions) + .WithEnableRateLimiting(fileRouteOptions.EnableRateLimiting) + .WithRateLimitOptions(rateLimitOption) + .WithHttpHandlerOptions(httpHandlerOptions) + .WithServiceName(fileRoute.ServiceName) + .WithServiceNamespace(fileRoute.ServiceNamespace) + .WithUseServiceDiscovery(fileRouteOptions.UseServiceDiscovery) + .WithUpstreamHeaderFindAndReplace(hAndRs.Upstream) + .WithDownstreamHeaderFindAndReplace(hAndRs.Downstream) + .WithDelegatingHandlers(fileRoute.DelegatingHandlers) + .WithAddHeadersToDownstream(hAndRs.AddHeadersToDownstream) + .WithAddHeadersToUpstream(hAndRs.AddHeadersToUpstream) + .WithDangerousAcceptAnyServerCertificateValidator(fileRoute.DangerousAcceptAnyServerCertificateValidator) + .WithSecurityOptions(securityOptions) + .WithDownstreamHttpVersion(downstreamHttpVersion) + .WithDownStreamHttpMethod(fileRoute.DownstreamHttpMethod) + .Build(); + + return route; + } + + private Route SetUpRoute(FileRoute fileRoute, DownstreamRoute downstreamRoutes) + { + var upstreamTemplatePattern = _upstreamTemplatePatternCreator.Create(fileRoute); + + var upstreamHeaderRoutingOptions = + _upstreamHeaderRoutingOptionsCreator.Create(fileRoute.UpstreamHeaderRoutingOptions); + + var route = new RouteBuilder() + .WithUpstreamHttpMethod(fileRoute.UpstreamHttpMethod) + .WithUpstreamPathTemplate(upstreamTemplatePattern) + .WithDownstreamRoute(downstreamRoutes) + .WithUpstreamHost(fileRoute.UpstreamHost) + .WithUpstreamHeaderRoutingOptions(upstreamHeaderRoutingOptions) + .Build(); + + return route; + } + } +} diff --git a/src/Ocelot/Configuration/Creator/UpstreamHeaderRoutingOptionsCreator.cs b/src/Ocelot/Configuration/Creator/UpstreamHeaderRoutingOptionsCreator.cs new file mode 100644 index 0000000000..cd87276a82 --- /dev/null +++ b/src/Ocelot/Configuration/Creator/UpstreamHeaderRoutingOptionsCreator.cs @@ -0,0 +1,25 @@ +using System; +using System.Linq; +using System.Collections.Generic; +using Ocelot.Configuration.File; + +namespace Ocelot.Configuration.Creator; + +public class UpstreamHeaderRoutingOptionsCreator : IUpstreamHeaderRoutingOptionsCreator +{ + public UpstreamHeaderRoutingOptions Create(FileUpstreamHeaderRoutingOptions options) + { + UpstreamHeaderRoutingTriggerMode mode = UpstreamHeaderRoutingTriggerMode.Any; + if (options.TriggerOn.Length > 0) + { + mode = (UpstreamHeaderRoutingTriggerMode) + Enum.Parse(typeof(UpstreamHeaderRoutingTriggerMode), options.TriggerOn, true); + } + + Dictionary> headers = options.Headers.ToDictionary( + kv => kv.Key.ToLowerInvariant(), + kv => new HashSet(kv.Value.Select(v => v.ToLowerInvariant()))); + + return new UpstreamHeaderRoutingOptions(headers, mode); + } +} diff --git a/src/Ocelot/Configuration/File/FileRoute.cs b/src/Ocelot/Configuration/File/FileRoute.cs index 6ff9856b2c..1ad0957b77 100644 --- a/src/Ocelot/Configuration/File/FileRoute.cs +++ b/src/Ocelot/Configuration/File/FileRoute.cs @@ -1,61 +1,63 @@ -using System.Collections.Generic; - -namespace Ocelot.Configuration.File +using System.Collections.Generic; + +namespace Ocelot.Configuration.File { - public class FileRoute : IRoute - { - public FileRoute() - { - UpstreamHttpMethod = new List(); - AddHeadersToRequest = new Dictionary(); - AddClaimsToRequest = new Dictionary(); - RouteClaimsRequirement = new Dictionary(); - AddQueriesToRequest = new Dictionary(); - ChangeDownstreamPathTemplate = new Dictionary(); - DownstreamHeaderTransform = new Dictionary(); - FileCacheOptions = new FileCacheOptions(); - QoSOptions = new FileQoSOptions(); - RateLimitOptions = new FileRateLimitRule(); - AuthenticationOptions = new FileAuthenticationOptions(); - HttpHandlerOptions = new FileHttpHandlerOptions(); - UpstreamHeaderTransform = new Dictionary(); - DownstreamHostAndPorts = new List(); - DelegatingHandlers = new List(); - LoadBalancerOptions = new FileLoadBalancerOptions(); - SecurityOptions = new FileSecurityOptions(); - Priority = 1; - } - - public string DownstreamPathTemplate { get; set; } - public string UpstreamPathTemplate { get; set; } - public List UpstreamHttpMethod { get; set; } - public string DownstreamHttpMethod { get; set; } - public Dictionary AddHeadersToRequest { get; set; } - public Dictionary UpstreamHeaderTransform { get; set; } - public Dictionary DownstreamHeaderTransform { get; set; } - public Dictionary AddClaimsToRequest { get; set; } - public Dictionary RouteClaimsRequirement { get; set; } - public Dictionary AddQueriesToRequest { get; set; } - public Dictionary ChangeDownstreamPathTemplate { get; set; } - public string RequestIdKey { get; set; } - public FileCacheOptions FileCacheOptions { get; set; } - public bool RouteIsCaseSensitive { get; set; } - public string ServiceName { get; set; } - public string ServiceNamespace { get; set; } - public string DownstreamScheme { get; set; } - public FileQoSOptions QoSOptions { get; set; } - public FileLoadBalancerOptions LoadBalancerOptions { get; set; } - public FileRateLimitRule RateLimitOptions { get; set; } - public FileAuthenticationOptions AuthenticationOptions { get; set; } - public FileHttpHandlerOptions HttpHandlerOptions { get; set; } - public List DownstreamHostAndPorts { get; set; } - public string UpstreamHost { get; set; } - public string Key { get; set; } - public List DelegatingHandlers { get; set; } - public int Priority { get; set; } - public int Timeout { get; set; } - public bool DangerousAcceptAnyServerCertificateValidator { get; set; } - public FileSecurityOptions SecurityOptions { get; set; } - public string DownstreamHttpVersion { get; set; } - } -} + public class FileRoute : IRoute + { + public FileRoute() + { + UpstreamHttpMethod = new List(); + AddHeadersToRequest = new Dictionary(); + AddClaimsToRequest = new Dictionary(); + RouteClaimsRequirement = new Dictionary(); + AddQueriesToRequest = new Dictionary(); + ChangeDownstreamPathTemplate = new Dictionary(); + DownstreamHeaderTransform = new Dictionary(); + FileCacheOptions = new FileCacheOptions(); + QoSOptions = new FileQoSOptions(); + RateLimitOptions = new FileRateLimitRule(); + AuthenticationOptions = new FileAuthenticationOptions(); + HttpHandlerOptions = new FileHttpHandlerOptions(); + UpstreamHeaderTransform = new Dictionary(); + DownstreamHostAndPorts = new List(); + DelegatingHandlers = new List(); + LoadBalancerOptions = new FileLoadBalancerOptions(); + SecurityOptions = new FileSecurityOptions(); + UpstreamHeaderRoutingOptions = new FileUpstreamHeaderRoutingOptions(); + Priority = 1; + } + + public string DownstreamPathTemplate { get; set; } + public string UpstreamPathTemplate { get; set; } + public List UpstreamHttpMethod { get; set; } + public string DownstreamHttpMethod { get; set; } + public Dictionary AddHeadersToRequest { get; set; } + public Dictionary UpstreamHeaderTransform { get; set; } + public Dictionary DownstreamHeaderTransform { get; set; } + public Dictionary AddClaimsToRequest { get; set; } + public Dictionary RouteClaimsRequirement { get; set; } + public Dictionary AddQueriesToRequest { get; set; } + public Dictionary ChangeDownstreamPathTemplate { get; set; } + public string RequestIdKey { get; set; } + public FileCacheOptions FileCacheOptions { get; set; } + public bool RouteIsCaseSensitive { get; set; } + public string ServiceName { get; set; } + public string ServiceNamespace { get; set; } + public string DownstreamScheme { get; set; } + public FileQoSOptions QoSOptions { get; set; } + public FileLoadBalancerOptions LoadBalancerOptions { get; set; } + public FileRateLimitRule RateLimitOptions { get; set; } + public FileAuthenticationOptions AuthenticationOptions { get; set; } + public FileHttpHandlerOptions HttpHandlerOptions { get; set; } + public List DownstreamHostAndPorts { get; set; } + public string UpstreamHost { get; set; } + public string Key { get; set; } + public List DelegatingHandlers { get; set; } + public int Priority { get; set; } + public int Timeout { get; set; } + public bool DangerousAcceptAnyServerCertificateValidator { get; set; } + public FileSecurityOptions SecurityOptions { get; set; } + public string DownstreamHttpVersion { get; set; } + public FileUpstreamHeaderRoutingOptions UpstreamHeaderRoutingOptions { get; set; } + } +} diff --git a/src/Ocelot/Configuration/File/FileUpstreamHeaderRoutingOptions.cs b/src/Ocelot/Configuration/File/FileUpstreamHeaderRoutingOptions.cs new file mode 100644 index 0000000000..80ea0b2a15 --- /dev/null +++ b/src/Ocelot/Configuration/File/FileUpstreamHeaderRoutingOptions.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; + +namespace Ocelot.Configuration.File; + +public class FileUpstreamHeaderRoutingOptions +{ + public FileUpstreamHeaderRoutingOptions() + { + Headers = new(); + TriggerOn = string.Empty; + } + + public Dictionary> Headers { get; set; } + + public string TriggerOn { get; set; } +} diff --git a/src/Ocelot/Configuration/Route.cs b/src/Ocelot/Configuration/Route.cs index 157c9b1beb..6292aa58d5 100644 --- a/src/Ocelot/Configuration/Route.cs +++ b/src/Ocelot/Configuration/Route.cs @@ -14,7 +14,8 @@ public Route(List downstreamRoute, List upstreamHttpMethod, UpstreamPathTemplate upstreamTemplatePattern, string upstreamHost, - string aggregator) + string aggregator, + UpstreamHeaderRoutingOptions upstreamHeaderRoutingOptions) { UpstreamHost = upstreamHost; DownstreamRoute = downstreamRoute; @@ -22,6 +23,7 @@ public Route(List downstreamRoute, UpstreamHttpMethod = upstreamHttpMethod; UpstreamTemplatePattern = upstreamTemplatePattern; Aggregator = aggregator; + UpstreamHeaderRoutingOptions = upstreamHeaderRoutingOptions; } public UpstreamPathTemplate UpstreamTemplatePattern { get; } @@ -30,5 +32,6 @@ public Route(List downstreamRoute, public List DownstreamRoute { get; } public List DownstreamRouteConfig { get; } public string Aggregator { get; } + public UpstreamHeaderRoutingOptions UpstreamHeaderRoutingOptions { get; } } } diff --git a/src/Ocelot/Configuration/UpstreamHeaderRoutingOptions.cs b/src/Ocelot/Configuration/UpstreamHeaderRoutingOptions.cs new file mode 100644 index 0000000000..04e3e7a8ed --- /dev/null +++ b/src/Ocelot/Configuration/UpstreamHeaderRoutingOptions.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; + +namespace Ocelot.Configuration; + +public class UpstreamHeaderRoutingOptions +{ + public UpstreamHeaderRoutingOptions(Dictionary> headers, UpstreamHeaderRoutingTriggerMode triggerOn) + { + Headers = new UpstreamRoutingHeaders(headers); + TriggerOn = triggerOn; + } + + public bool Enabled() => Headers.Any(); + + public UpstreamRoutingHeaders Headers { get; } + + public UpstreamHeaderRoutingTriggerMode TriggerOn { get; } +} diff --git a/src/Ocelot/Configuration/UpstreamHeaderRoutingTriggerMode.cs b/src/Ocelot/Configuration/UpstreamHeaderRoutingTriggerMode.cs new file mode 100644 index 0000000000..bc78ec8e65 --- /dev/null +++ b/src/Ocelot/Configuration/UpstreamHeaderRoutingTriggerMode.cs @@ -0,0 +1,20 @@ +namespace Ocelot.Configuration; + +/// +/// Specifier for the upstream header routing +/// trigger mode. +/// +public enum UpstreamHeaderRoutingTriggerMode : byte +{ + /// + /// Presence of any of the defined headers will + /// trigger the routing rule. + /// + Any = 0, + + /// + /// All defined headers will need to be present + /// for the routing rule to be triggered. + /// + All = 1, +} diff --git a/src/Ocelot/Configuration/UpstreamRoutingHeaders.cs b/src/Ocelot/Configuration/UpstreamRoutingHeaders.cs new file mode 100644 index 0000000000..3d2f4e5eaa --- /dev/null +++ b/src/Ocelot/Configuration/UpstreamRoutingHeaders.cs @@ -0,0 +1,81 @@ +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Primitives; + +namespace Ocelot.Configuration; + +public class UpstreamRoutingHeaders +{ + public Dictionary> Headers { get; } + + public UpstreamRoutingHeaders(Dictionary> headers) + { + Headers = headers ?? new(); + } + + public bool Any() => Headers.Any(); + + public bool HasAnyOf(IHeaderDictionary requestHeaders) + { + if (!Any()) + { + return false; + } + + IHeaderDictionary lowerCaseHeaders = GetLowerCaseHeaders(requestHeaders); + foreach (KeyValuePair> h in Headers) + { + if (!lowerCaseHeaders.TryGetValue(h.Key, out var values)) + { + continue; + } + + HashSet requestHeaderValues = new(values); + if (h.Value.Overlaps(requestHeaderValues)) + { + return true; + } + } + + return false; + } + + public bool HasAllOf(IHeaderDictionary requestHeaders) + { + if (!Any()) + { + return false; + } + + IHeaderDictionary lowerCaseHeaders = GetLowerCaseHeaders(requestHeaders); + foreach (KeyValuePair> h in Headers) + { + if (!lowerCaseHeaders.TryGetValue(h.Key, out var values)) + { + return false; + } + + HashSet requestHeaderValues = new(values); + if (!h.Value.Overlaps(requestHeaderValues)) + { + return false; + } + } + + return true; + } + + private static IHeaderDictionary GetLowerCaseHeaders(IHeaderDictionary headers) + { + IHeaderDictionary lowerCaseHeaders = new HeaderDictionary(); + foreach (KeyValuePair kv in headers) + { + string key = kv.Key.ToLowerInvariant(); + StringValues values = new(kv.Value.Select(v => v.ToLowerInvariant()).ToArray()); + lowerCaseHeaders.Add(key, values); + } + + return lowerCaseHeaders; + } +} diff --git a/src/Ocelot/DependencyInjection/OcelotBuilder.cs b/src/Ocelot/DependencyInjection/OcelotBuilder.cs index 0e1ae6c67f..c701d8938d 100644 --- a/src/Ocelot/DependencyInjection/OcelotBuilder.cs +++ b/src/Ocelot/DependencyInjection/OcelotBuilder.cs @@ -99,6 +99,7 @@ public OcelotBuilder(IServiceCollection services, IConfiguration configurationRo Services.TryAddSingleton(); Services.TryAddSingleton(); Services.TryAddSingleton(); + Services.TryAddSingleton(); Services.TryAddSingleton(); Services.TryAddSingleton(); Services.TryAddSingleton(); diff --git a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteCreator.cs b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteCreator.cs index b960f7cea7..f4302f23bf 100644 --- a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteCreator.cs +++ b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteCreator.cs @@ -1,7 +1,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; - +using Microsoft.AspNetCore.Http; using Ocelot.Configuration; using Ocelot.Configuration.Builder; using Ocelot.Configuration.Creator; @@ -10,131 +10,137 @@ using Ocelot.Responses; -using Ocelot.DownstreamRouteFinder.UrlMatcher; - -namespace Ocelot.DownstreamRouteFinder.Finder -{ - public class DownstreamRouteCreator : IDownstreamRouteProvider - { - private readonly IQoSOptionsCreator _qoSOptionsCreator; - private readonly ConcurrentDictionary> _cache; - - public DownstreamRouteCreator(IQoSOptionsCreator qoSOptionsCreator) - { - _qoSOptionsCreator = qoSOptionsCreator; - _cache = new ConcurrentDictionary>(); - } - - public Response Get(string upstreamUrlPath, string upstreamQueryString, string upstreamHttpMethod, IInternalConfiguration configuration, string upstreamHost) - { - var serviceName = GetServiceName(upstreamUrlPath); - - var downstreamPath = GetDownstreamPath(upstreamUrlPath); - - if (HasQueryString(downstreamPath)) - { - downstreamPath = RemoveQueryString(downstreamPath); - } - - var downstreamPathForKeys = $"/{serviceName}{downstreamPath}"; - - var loadBalancerKey = CreateLoadBalancerKey(downstreamPathForKeys, upstreamHttpMethod, configuration.LoadBalancerOptions); - - if (_cache.TryGetValue(loadBalancerKey, out var downstreamRouteHolder)) - { - return downstreamRouteHolder; - } - - var qosOptions = _qoSOptionsCreator.Create(configuration.QoSOptions, downstreamPathForKeys, new List { upstreamHttpMethod }); - - var upstreamPathTemplate = new UpstreamPathTemplateBuilder().WithOriginalValue(upstreamUrlPath).Build(); - - var downstreamRouteBuilder = new DownstreamRouteBuilder() - .WithServiceName(serviceName) - .WithLoadBalancerKey(loadBalancerKey) - .WithDownstreamPathTemplate(downstreamPath) - .WithUseServiceDiscovery(true) - .WithHttpHandlerOptions(configuration.HttpHandlerOptions) - .WithQosOptions(qosOptions) - .WithDownstreamScheme(configuration.DownstreamScheme) - .WithLoadBalancerOptions(configuration.LoadBalancerOptions) - .WithDownstreamHttpVersion(configuration.DownstreamHttpVersion) - .WithUpstreamPathTemplate(upstreamPathTemplate); - - var rateLimitOptions = configuration.Routes?.SelectMany(x => x.DownstreamRoute) - .FirstOrDefault(x => x.ServiceName == serviceName); - - if (rateLimitOptions != null) - { - downstreamRouteBuilder - .WithRateLimitOptions(rateLimitOptions.RateLimitOptions) - .WithEnableRateLimiting(true); - } - - var downstreamRoute = downstreamRouteBuilder.Build(); - - var route = new RouteBuilder() - .WithDownstreamRoute(downstreamRoute) - .WithUpstreamHttpMethod(new List { upstreamHttpMethod }) - .WithUpstreamPathTemplate(upstreamPathTemplate) - .Build(); - - downstreamRouteHolder = new OkResponse(new DownstreamRouteHolder(new List(), route)); - - _cache.AddOrUpdate(loadBalancerKey, downstreamRouteHolder, (x, y) => downstreamRouteHolder); - - return downstreamRouteHolder; - } - - private static string RemoveQueryString(string downstreamPath) - { - return downstreamPath - .Substring(0, downstreamPath.IndexOf('?')); - } - - private static bool HasQueryString(string downstreamPath) - { - return downstreamPath.Contains('?'); - } - - private static string GetDownstreamPath(string upstreamUrlPath) - { - if (upstreamUrlPath.IndexOf('/', 1) == -1) - { - return "/"; - } - - return upstreamUrlPath - .Substring(upstreamUrlPath.IndexOf('/', 1)); - } - - private static string GetServiceName(string upstreamUrlPath) - { - if (upstreamUrlPath.IndexOf('/', 1) == -1) - { - return upstreamUrlPath - .Substring(1); - } - - return upstreamUrlPath - .Substring(1, upstreamUrlPath.IndexOf('/', 1)) - .TrimEnd('/'); - } - - private static string CreateLoadBalancerKey(string downstreamTemplatePath, string httpMethod, LoadBalancerOptions loadBalancerOptions) - { - if (!string.IsNullOrEmpty(loadBalancerOptions.Type) && !string.IsNullOrEmpty(loadBalancerOptions.Key) && loadBalancerOptions.Type == nameof(CookieStickySessions)) - { - return $"{nameof(CookieStickySessions)}:{loadBalancerOptions.Key}"; - } - - return CreateQoSKey(downstreamTemplatePath, httpMethod); - } - - private static string CreateQoSKey(string downstreamTemplatePath, string httpMethod) - { - var loadBalancerKey = $"{downstreamTemplatePath}|{httpMethod}"; - return loadBalancerKey; - } - } -} +using Ocelot.DownstreamRouteFinder.UrlMatcher; + +namespace Ocelot.DownstreamRouteFinder.Finder +{ + public class DownstreamRouteCreator : IDownstreamRouteProvider + { + private readonly IQoSOptionsCreator _qoSOptionsCreator; + private readonly ConcurrentDictionary> _cache; + + public DownstreamRouteCreator(IQoSOptionsCreator qoSOptionsCreator) + { + _qoSOptionsCreator = qoSOptionsCreator; + _cache = new ConcurrentDictionary>(); + } + + public Response Get( + string upstreamUrlPath, + string upstreamQueryString, + string upstreamHttpMethod, + IInternalConfiguration configuration, + string upstreamHost, + IHeaderDictionary upstreamHeaders) + { + var serviceName = GetServiceName(upstreamUrlPath); + + var downstreamPath = GetDownstreamPath(upstreamUrlPath); + + if (HasQueryString(downstreamPath)) + { + downstreamPath = RemoveQueryString(downstreamPath); + } + + var downstreamPathForKeys = $"/{serviceName}{downstreamPath}"; + + var loadBalancerKey = CreateLoadBalancerKey(downstreamPathForKeys, upstreamHttpMethod, configuration.LoadBalancerOptions); + + if (_cache.TryGetValue(loadBalancerKey, out var downstreamRouteHolder)) + { + return downstreamRouteHolder; + } + + var qosOptions = _qoSOptionsCreator.Create(configuration.QoSOptions, downstreamPathForKeys, new List { upstreamHttpMethod }); + + var upstreamPathTemplate = new UpstreamPathTemplateBuilder().WithOriginalValue(upstreamUrlPath).Build(); + + var downstreamRouteBuilder = new DownstreamRouteBuilder() + .WithServiceName(serviceName) + .WithLoadBalancerKey(loadBalancerKey) + .WithDownstreamPathTemplate(downstreamPath) + .WithUseServiceDiscovery(true) + .WithHttpHandlerOptions(configuration.HttpHandlerOptions) + .WithQosOptions(qosOptions) + .WithDownstreamScheme(configuration.DownstreamScheme) + .WithLoadBalancerOptions(configuration.LoadBalancerOptions) + .WithDownstreamHttpVersion(configuration.DownstreamHttpVersion) + .WithUpstreamPathTemplate(upstreamPathTemplate); + + var rateLimitOptions = configuration.Routes?.SelectMany(x => x.DownstreamRoute) + .FirstOrDefault(x => x.ServiceName == serviceName); + + if (rateLimitOptions != null) + { + downstreamRouteBuilder + .WithRateLimitOptions(rateLimitOptions.RateLimitOptions) + .WithEnableRateLimiting(true); + } + + var downstreamRoute = downstreamRouteBuilder.Build(); + + var route = new RouteBuilder() + .WithDownstreamRoute(downstreamRoute) + .WithUpstreamHttpMethod(new List { upstreamHttpMethod }) + .WithUpstreamPathTemplate(upstreamPathTemplate) + .Build(); + + downstreamRouteHolder = new OkResponse(new DownstreamRouteHolder(new List(), route)); + + _cache.AddOrUpdate(loadBalancerKey, downstreamRouteHolder, (x, y) => downstreamRouteHolder); + + return downstreamRouteHolder; + } + + private static string RemoveQueryString(string downstreamPath) + { + return downstreamPath + .Substring(0, downstreamPath.IndexOf('?')); + } + + private static bool HasQueryString(string downstreamPath) + { + return downstreamPath.Contains('?'); + } + + private static string GetDownstreamPath(string upstreamUrlPath) + { + if (upstreamUrlPath.IndexOf('/', 1) == -1) + { + return "/"; + } + + return upstreamUrlPath + .Substring(upstreamUrlPath.IndexOf('/', 1)); + } + + private static string GetServiceName(string upstreamUrlPath) + { + if (upstreamUrlPath.IndexOf('/', 1) == -1) + { + return upstreamUrlPath + .Substring(1); + } + + return upstreamUrlPath + .Substring(1, upstreamUrlPath.IndexOf('/', 1)) + .TrimEnd('/'); + } + + private static string CreateLoadBalancerKey(string downstreamTemplatePath, string httpMethod, LoadBalancerOptions loadBalancerOptions) + { + if (!string.IsNullOrEmpty(loadBalancerOptions.Type) && !string.IsNullOrEmpty(loadBalancerOptions.Key) && loadBalancerOptions.Type == nameof(CookieStickySessions)) + { + return $"{nameof(CookieStickySessions)}:{loadBalancerOptions.Key}"; + } + + return CreateQoSKey(downstreamTemplatePath, httpMethod); + } + + private static string CreateQoSKey(string downstreamTemplatePath, string httpMethod) + { + var loadBalancerKey = $"{downstreamTemplatePath}|{httpMethod}"; + return loadBalancerKey; + } + } +} diff --git a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs index 30a9eef5c3..b97ad3d570 100644 --- a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs +++ b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs @@ -1,63 +1,89 @@ -using System.Collections.Generic; -using System.Linq; - -using Ocelot.Configuration; -using Ocelot.DownstreamRouteFinder.UrlMatcher; +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNetCore.Http; +using Ocelot.Configuration; +using Ocelot.DownstreamRouteFinder.UrlMatcher; using Ocelot.Responses; - -namespace Ocelot.DownstreamRouteFinder.Finder -{ - public class DownstreamRouteFinder : IDownstreamRouteProvider - { - private readonly IUrlPathToUrlTemplateMatcher _urlMatcher; - private readonly IPlaceholderNameAndValueFinder _placeholderNameAndValueFinder; - - public DownstreamRouteFinder(IUrlPathToUrlTemplateMatcher urlMatcher, IPlaceholderNameAndValueFinder urlPathPlaceholderNameAndValueFinder) - { - _urlMatcher = urlMatcher; - _placeholderNameAndValueFinder = urlPathPlaceholderNameAndValueFinder; - } - - public Response Get(string upstreamUrlPath, string upstreamQueryString, string httpMethod, IInternalConfiguration configuration, string upstreamHost) - { - var downstreamRoutes = new List(); - - var applicableRoutes = configuration.Routes - .Where(r => RouteIsApplicableToThisRequest(r, httpMethod, upstreamHost)) - .OrderByDescending(x => x.UpstreamTemplatePattern.Priority); - - foreach (var route in applicableRoutes) - { - var urlMatch = _urlMatcher.Match(upstreamUrlPath, upstreamQueryString, route.UpstreamTemplatePattern); - - if (urlMatch.Data.Match) - { - downstreamRoutes.Add(GetPlaceholderNamesAndValues(upstreamUrlPath, upstreamQueryString, route)); - } - } - - if (downstreamRoutes.Any()) - { - var notNullOption = downstreamRoutes.FirstOrDefault(x => !string.IsNullOrEmpty(x.Route.UpstreamHost)); - var nullOption = downstreamRoutes.FirstOrDefault(x => string.IsNullOrEmpty(x.Route.UpstreamHost)); - - return notNullOption != null ? new OkResponse(notNullOption) : new OkResponse(nullOption); - } - - return new ErrorResponse(new UnableToFindDownstreamRouteError(upstreamUrlPath, httpMethod)); - } - - private static bool RouteIsApplicableToThisRequest(Route route, string httpMethod, string upstreamHost) - { - return (route.UpstreamHttpMethod.Count == 0 || route.UpstreamHttpMethod.Select(x => x.Method.ToLower()).Contains(httpMethod.ToLower())) && - (string.IsNullOrEmpty(route.UpstreamHost) || route.UpstreamHost == upstreamHost); - } - - private DownstreamRouteHolder GetPlaceholderNamesAndValues(string path, string query, Route route) - { - var templatePlaceholderNameAndValues = _placeholderNameAndValueFinder.Find(path, query, route.UpstreamTemplatePattern.OriginalValue); - - return new DownstreamRouteHolder(templatePlaceholderNameAndValues.Data, route); - } - } -} + +namespace Ocelot.DownstreamRouteFinder.Finder +{ + public class DownstreamRouteFinder : IDownstreamRouteProvider + { + private readonly IUrlPathToUrlTemplateMatcher _urlMatcher; + private readonly IPlaceholderNameAndValueFinder _placeholderNameAndValueFinder; + + public DownstreamRouteFinder(IUrlPathToUrlTemplateMatcher urlMatcher, IPlaceholderNameAndValueFinder urlPathPlaceholderNameAndValueFinder) + { + _urlMatcher = urlMatcher; + _placeholderNameAndValueFinder = urlPathPlaceholderNameAndValueFinder; + } + + public Response Get( + string upstreamUrlPath, + string upstreamQueryString, + string httpMethod, + IInternalConfiguration configuration, + string upstreamHost, + IHeaderDictionary upstreamHeaders) + { + var downstreamRoutes = new List(); + + var applicableRoutes = configuration.Routes + .Where(r => RouteIsApplicableToThisRequest(r, httpMethod, upstreamHost, upstreamHeaders)) + .OrderByDescending(x => x.UpstreamTemplatePattern.Priority); + + foreach (var route in applicableRoutes) + { + var urlMatch = _urlMatcher.Match(upstreamUrlPath, upstreamQueryString, route.UpstreamTemplatePattern); + + if (urlMatch.Data.Match) + { + downstreamRoutes.Add(GetPlaceholderNamesAndValues(upstreamUrlPath, upstreamQueryString, route)); + } + } + + if (downstreamRoutes.Any()) + { + var notNullOption = downstreamRoutes.FirstOrDefault(x => !string.IsNullOrEmpty(x.Route.UpstreamHost)); + var nullOption = downstreamRoutes.FirstOrDefault(x => string.IsNullOrEmpty(x.Route.UpstreamHost)); + + return notNullOption != null ? new OkResponse(notNullOption) : new OkResponse(nullOption); + } + + return new ErrorResponse(new UnableToFindDownstreamRouteError(upstreamUrlPath, httpMethod)); + } + + private static bool RouteIsApplicableToThisRequest(Route route, string httpMethod, string upstreamHost, IHeaderDictionary requestHeaders) + { + return (route.UpstreamHttpMethod.Count == 0 || RouteHasHttpMethod(route, httpMethod)) && + (string.IsNullOrEmpty(route.UpstreamHost) || route.UpstreamHost == upstreamHost) && + (route.UpstreamHeaderRoutingOptions == null || !route.UpstreamHeaderRoutingOptions.Enabled() || RouteHasRequiredUpstreamHeaders(route, requestHeaders)); + } + + private DownstreamRouteHolder GetPlaceholderNamesAndValues(string path, string query, Route route) + { + var templatePlaceholderNameAndValues = _placeholderNameAndValueFinder.Find(path, query, route.UpstreamTemplatePattern.OriginalValue); + + return new DownstreamRouteHolder(templatePlaceholderNameAndValues.Data, route); + } + + private static bool RouteHasHttpMethod(Route route, string httpMethod) => + route.UpstreamHttpMethod.Select(x => x.Method.ToLower()).Contains(httpMethod.ToLowerInvariant()); + + private static bool RouteHasRequiredUpstreamHeaders(Route route, IHeaderDictionary requestHeaders) + { + bool result = false; + switch (route.UpstreamHeaderRoutingOptions.TriggerOn) + { + case UpstreamHeaderRoutingTriggerMode.Any: + result = route.UpstreamHeaderRoutingOptions.Headers.HasAnyOf(requestHeaders); + break; + case UpstreamHeaderRoutingTriggerMode.All: + result = route.UpstreamHeaderRoutingOptions.Headers.HasAllOf(requestHeaders); + break; + } + + return result; + } + } +} diff --git a/src/Ocelot/DownstreamRouteFinder/Finder/IDownstreamRouteProvider.cs b/src/Ocelot/DownstreamRouteFinder/Finder/IDownstreamRouteProvider.cs index ed2a657ef1..2a449a5b5a 100644 --- a/src/Ocelot/DownstreamRouteFinder/Finder/IDownstreamRouteProvider.cs +++ b/src/Ocelot/DownstreamRouteFinder/Finder/IDownstreamRouteProvider.cs @@ -1,10 +1,17 @@ -using Ocelot.Configuration; -using Ocelot.Responses; - -namespace Ocelot.DownstreamRouteFinder.Finder -{ - public interface IDownstreamRouteProvider - { - Response Get(string upstreamUrlPath, string upstreamQueryString, string upstreamHttpMethod, IInternalConfiguration configuration, string upstreamHost); - } -} +using Microsoft.AspNetCore.Http; +using Ocelot.Configuration; +using Ocelot.Responses; + +namespace Ocelot.DownstreamRouteFinder.Finder +{ + public interface IDownstreamRouteProvider + { + Response Get( + string upstreamUrlPath, + string upstreamQueryString, + string upstreamHttpMethod, + IInternalConfiguration configuration, + string upstreamHost, + IHeaderDictionary upstreamHeaders); + } +} diff --git a/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs b/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs index 4efa4867f9..c2f5937fb9 100644 --- a/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs +++ b/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs @@ -40,9 +40,11 @@ public async Task Invoke(HttpContext httpContext) var internalConfiguration = httpContext.Items.IInternalConfiguration(); + var upstreamHeaders = httpContext.Request.Headers; + var provider = _factory.Get(internalConfiguration); - var response = provider.Get(upstreamUrlPath, upstreamQueryString, httpContext.Request.Method, internalConfiguration, upstreamHost); + var response = provider.Get(upstreamUrlPath, upstreamQueryString, httpContext.Request.Method, internalConfiguration, upstreamHost, upstreamHeaders); if (response.IsError) { diff --git a/test/Ocelot.UnitTests/Configuration/RoutesCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/RoutesCreatorTests.cs index 4b07033edf..df74559852 100644 --- a/test/Ocelot.UnitTests/Configuration/RoutesCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/RoutesCreatorTests.cs @@ -10,273 +10,276 @@ using System.Collections.Generic; using System.Linq; using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.Configuration -{ - public class RoutesCreatorTests - { - private readonly RoutesCreator _creator; - private readonly Mock _cthCreator; - private readonly Mock _aoCreator; - private readonly Mock _utpCreator; - private readonly Mock _ridkCreator; - private readonly Mock _qosoCreator; - private readonly Mock _rroCreator; - private readonly Mock _rloCreator; - private readonly Mock _rCreator; - private readonly Mock _hhoCreator; - private readonly Mock _hfarCreator; - private readonly Mock _daCreator; - private readonly Mock _lboCreator; - private readonly Mock _rrkCreator; - private readonly Mock _soCreator; - private readonly Mock _versionCreator; - private FileConfiguration _fileConfig; - private RouteOptions _rro; - private string _requestId; - private string _rrk; - private UpstreamPathTemplate _upt; - private AuthenticationOptions _ao; - private List _ctt; - private QoSOptions _qoso; - private RateLimitOptions _rlo; - private string _region; - private HttpHandlerOptions _hho; - private HeaderTransformations _ht; - private List _dhp; - private LoadBalancerOptions _lbo; - private List _result; - private Version _expectedVersion; - - public RoutesCreatorTests() - { - _cthCreator = new Mock(); - _aoCreator = new Mock(); - _utpCreator = new Mock(); - _ridkCreator = new Mock(); - _qosoCreator = new Mock(); - _rroCreator = new Mock(); - _rloCreator = new Mock(); - _rCreator = new Mock(); - _hhoCreator = new Mock(); - _hfarCreator = new Mock(); - _daCreator = new Mock(); - _lboCreator = new Mock(); - _rrkCreator = new Mock(); - _soCreator = new Mock(); - _versionCreator = new Mock(); - - _creator = new RoutesCreator( - _cthCreator.Object, - _aoCreator.Object, - _utpCreator.Object, - _ridkCreator.Object, - _qosoCreator.Object, - _rroCreator.Object, - _rloCreator.Object, - _rCreator.Object, - _hhoCreator.Object, - _hfarCreator.Object, - _daCreator.Object, - _lboCreator.Object, - _rrkCreator.Object, - _soCreator.Object, - _versionCreator.Object - ); - } - - [Fact] - public void should_return_nothing() - { - var fileConfig = new FileConfiguration(); - - this.Given(_ => GivenThe(fileConfig)) - .When(_ => WhenICreate()) - .Then(_ => ThenNoRoutesAreReturned()) - .BDDfy(); - } - - [Fact] - public void should_return_re_routes() - { - var fileConfig = new FileConfiguration - { - Routes = new List - { - new() - { - ServiceName = "dave", - DangerousAcceptAnyServerCertificateValidator = true, - AddClaimsToRequest = new Dictionary - { - { "a","b" }, - }, - AddHeadersToRequest = new Dictionary - { - { "c","d" }, - }, - AddQueriesToRequest = new Dictionary - { - { "e","f" }, - }, - UpstreamHttpMethod = new List { "GET", "POST" }, - }, - new() - { - ServiceName = "wave", - DangerousAcceptAnyServerCertificateValidator = false, - AddClaimsToRequest = new Dictionary - { - { "g","h" }, - }, - AddHeadersToRequest = new Dictionary - { - { "i","j" }, - }, - AddQueriesToRequest = new Dictionary - { - { "k","l" }, - }, - UpstreamHttpMethod = new List { "PUT", "DELETE" }, - }, - }, - }; - - this.Given(_ => GivenThe(fileConfig)) - .And(_ => GivenTheDependenciesAreSetUpCorrectly()) - .When(_ => WhenICreate()) - .Then(_ => ThenTheDependenciesAreCalledCorrectly()) - .And(_ => ThenTheRoutesAreCreated()) - .BDDfy(); - } - - private void ThenTheDependenciesAreCalledCorrectly() - { - ThenTheDepsAreCalledFor(_fileConfig.Routes[0], _fileConfig.GlobalConfiguration); - ThenTheDepsAreCalledFor(_fileConfig.Routes[1], _fileConfig.GlobalConfiguration); - } - - private void GivenTheDependenciesAreSetUpCorrectly() - { - _expectedVersion = new Version("1.1"); - _rro = new RouteOptions(false, false, false, false, false); - _requestId = "testy"; - _rrk = "besty"; - _upt = new UpstreamPathTemplateBuilder().Build(); - _ao = new AuthenticationOptionsBuilder().Build(); - _ctt = new List(); - _qoso = new QoSOptionsBuilder().Build(); - _rlo = new RateLimitOptionsBuilder().Build(); - _region = "vesty"; - _hho = new HttpHandlerOptionsBuilder().Build(); - _ht = new HeaderTransformations(new List(), new List(), new List(), new List()); - _dhp = new List(); - _lbo = new LoadBalancerOptionsBuilder().Build(); - - _rroCreator.Setup(x => x.Create(It.IsAny())).Returns(_rro); - _ridkCreator.Setup(x => x.Create(It.IsAny(), It.IsAny())).Returns(_requestId); - _rrkCreator.Setup(x => x.Create(It.IsAny())).Returns(_rrk); - _utpCreator.Setup(x => x.Create(It.IsAny())).Returns(_upt); - _aoCreator.Setup(x => x.Create(It.IsAny())).Returns(_ao); - _cthCreator.Setup(x => x.Create(It.IsAny>())).Returns(_ctt); - _qosoCreator.Setup(x => x.Create(It.IsAny(), It.IsAny(), It.IsAny>())).Returns(_qoso); - _rloCreator.Setup(x => x.Create(It.IsAny(), It.IsAny())).Returns(_rlo); - _rCreator.Setup(x => x.Create(It.IsAny())).Returns(_region); - _hhoCreator.Setup(x => x.Create(It.IsAny())).Returns(_hho); - _hfarCreator.Setup(x => x.Create(It.IsAny())).Returns(_ht); - _daCreator.Setup(x => x.Create(It.IsAny())).Returns(_dhp); - _lboCreator.Setup(x => x.Create(It.IsAny())).Returns(_lbo); - _versionCreator.Setup(x => x.Create(It.IsAny())).Returns(_expectedVersion); - } - - private void ThenTheRoutesAreCreated() - { - _result.Count.ShouldBe(2); - - ThenTheRouteIsSet(_fileConfig.Routes[0], 0); - ThenTheRouteIsSet(_fileConfig.Routes[1], 1); - } - - private void ThenNoRoutesAreReturned() - { - _result.ShouldBeEmpty(); - } - - private void GivenThe(FileConfiguration fileConfig) - { - _fileConfig = fileConfig; - } - - private void WhenICreate() - { - _result = _creator.Create(_fileConfig); - } - - private void ThenTheRouteIsSet(FileRoute expected, int routeIndex) - { - _result[routeIndex].DownstreamRoute[0].DownstreamHttpVersion.ShouldBe(_expectedVersion); - _result[routeIndex].DownstreamRoute[0].IsAuthenticated.ShouldBe(_rro.IsAuthenticated); - _result[routeIndex].DownstreamRoute[0].IsAuthorized.ShouldBe(_rro.IsAuthorized); - _result[routeIndex].DownstreamRoute[0].IsCached.ShouldBe(_rro.IsCached); - _result[routeIndex].DownstreamRoute[0].EnableEndpointEndpointRateLimiting.ShouldBe(_rro.EnableRateLimiting); - _result[routeIndex].DownstreamRoute[0].RequestIdKey.ShouldBe(_requestId); - _result[routeIndex].DownstreamRoute[0].LoadBalancerKey.ShouldBe(_rrk); - _result[routeIndex].DownstreamRoute[0].UpstreamPathTemplate.ShouldBe(_upt); - _result[routeIndex].DownstreamRoute[0].AuthenticationOptions.ShouldBe(_ao); - _result[routeIndex].DownstreamRoute[0].ClaimsToHeaders.ShouldBe(_ctt); - _result[routeIndex].DownstreamRoute[0].ClaimsToQueries.ShouldBe(_ctt); - _result[routeIndex].DownstreamRoute[0].ClaimsToClaims.ShouldBe(_ctt); - _result[routeIndex].DownstreamRoute[0].QosOptions.ShouldBe(_qoso); - _result[routeIndex].DownstreamRoute[0].RateLimitOptions.ShouldBe(_rlo); - _result[routeIndex].DownstreamRoute[0].CacheOptions.Region.ShouldBe(_region); - _result[routeIndex].DownstreamRoute[0].CacheOptions.TtlSeconds.ShouldBe(expected.FileCacheOptions.TtlSeconds); - _result[routeIndex].DownstreamRoute[0].HttpHandlerOptions.ShouldBe(_hho); - _result[routeIndex].DownstreamRoute[0].UpstreamHeadersFindAndReplace.ShouldBe(_ht.Upstream); - _result[routeIndex].DownstreamRoute[0].DownstreamHeadersFindAndReplace.ShouldBe(_ht.Downstream); - _result[routeIndex].DownstreamRoute[0].AddHeadersToUpstream.ShouldBe(_ht.AddHeadersToUpstream); - _result[routeIndex].DownstreamRoute[0].AddHeadersToDownstream.ShouldBe(_ht.AddHeadersToDownstream); - _result[routeIndex].DownstreamRoute[0].DownstreamAddresses.ShouldBe(_dhp); - _result[routeIndex].DownstreamRoute[0].LoadBalancerOptions.ShouldBe(_lbo); - _result[routeIndex].DownstreamRoute[0].UseServiceDiscovery.ShouldBe(_rro.UseServiceDiscovery); - _result[routeIndex].DownstreamRoute[0].DangerousAcceptAnyServerCertificateValidator.ShouldBe(expected.DangerousAcceptAnyServerCertificateValidator); - _result[routeIndex].DownstreamRoute[0].DelegatingHandlers.ShouldBe(expected.DelegatingHandlers); - _result[routeIndex].DownstreamRoute[0].ServiceName.ShouldBe(expected.ServiceName); - _result[routeIndex].DownstreamRoute[0].DownstreamScheme.ShouldBe(expected.DownstreamScheme); - _result[routeIndex].DownstreamRoute[0].RouteClaimsRequirement.ShouldBe(expected.RouteClaimsRequirement); - _result[routeIndex].DownstreamRoute[0].DownstreamPathTemplate.Value.ShouldBe(expected.DownstreamPathTemplate); - _result[routeIndex].DownstreamRoute[0].Key.ShouldBe(expected.Key); - _result[routeIndex].UpstreamHttpMethod - .Select(x => x.Method) - .ToList() - .ShouldContain(x => x == expected.UpstreamHttpMethod[0]); - _result[routeIndex].UpstreamHttpMethod - .Select(x => x.Method) - .ToList() - .ShouldContain(x => x == expected.UpstreamHttpMethod[1]); - _result[routeIndex].UpstreamHost.ShouldBe(expected.UpstreamHost); - _result[routeIndex].DownstreamRoute.Count.ShouldBe(1); - _result[routeIndex].UpstreamTemplatePattern.ShouldBe(_upt); - } - - private void ThenTheDepsAreCalledFor(FileRoute fileRoute, FileGlobalConfiguration globalConfig) - { - _rroCreator.Verify(x => x.Create(fileRoute), Times.Once); - _ridkCreator.Verify(x => x.Create(fileRoute, globalConfig), Times.Once); - _rrkCreator.Verify(x => x.Create(fileRoute), Times.Once); - _utpCreator.Verify(x => x.Create(fileRoute), Times.Exactly(2)); - _aoCreator.Verify(x => x.Create(fileRoute), Times.Once); - _cthCreator.Verify(x => x.Create(fileRoute.AddHeadersToRequest), Times.Once); - _cthCreator.Verify(x => x.Create(fileRoute.AddClaimsToRequest), Times.Once); - _cthCreator.Verify(x => x.Create(fileRoute.AddQueriesToRequest), Times.Once); - _qosoCreator.Verify(x => x.Create(fileRoute.QoSOptions, fileRoute.UpstreamPathTemplate, fileRoute.UpstreamHttpMethod)); - _rloCreator.Verify(x => x.Create(fileRoute.RateLimitOptions, globalConfig), Times.Once); - _rCreator.Verify(x => x.Create(fileRoute), Times.Once); - _hhoCreator.Verify(x => x.Create(fileRoute.HttpHandlerOptions), Times.Once); - _hfarCreator.Verify(x => x.Create(fileRoute), Times.Once); - _daCreator.Verify(x => x.Create(fileRoute), Times.Once); - _lboCreator.Verify(x => x.Create(fileRoute.LoadBalancerOptions), Times.Once); - _soCreator.Verify(x => x.Create(fileRoute.SecurityOptions), Times.Once); - } - } -} +using Xunit; + +namespace Ocelot.UnitTests.Configuration +{ + public class RoutesCreatorTests + { + private readonly RoutesCreator _creator; + private readonly Mock _cthCreator; + private readonly Mock _aoCreator; + private readonly Mock _utpCreator; + private readonly Mock _ridkCreator; + private readonly Mock _qosoCreator; + private readonly Mock _rroCreator; + private readonly Mock _rloCreator; + private readonly Mock _rCreator; + private readonly Mock _hhoCreator; + private readonly Mock _hfarCreator; + private readonly Mock _daCreator; + private readonly Mock _lboCreator; + private readonly Mock _rrkCreator; + private readonly Mock _soCreator; + private readonly Mock _versionCreator; + private readonly Mock _uhroCreator; + private FileConfiguration _fileConfig; + private RouteOptions _rro; + private string _requestId; + private string _rrk; + private UpstreamPathTemplate _upt; + private AuthenticationOptions _ao; + private List _ctt; + private QoSOptions _qoso; + private RateLimitOptions _rlo; + private string _region; + private HttpHandlerOptions _hho; + private HeaderTransformations _ht; + private List _dhp; + private LoadBalancerOptions _lbo; + private List _result; + private Version _expectedVersion; + + public RoutesCreatorTests() + { + _cthCreator = new Mock(); + _aoCreator = new Mock(); + _utpCreator = new Mock(); + _ridkCreator = new Mock(); + _qosoCreator = new Mock(); + _rroCreator = new Mock(); + _rloCreator = new Mock(); + _rCreator = new Mock(); + _hhoCreator = new Mock(); + _hfarCreator = new Mock(); + _daCreator = new Mock(); + _lboCreator = new Mock(); + _rrkCreator = new Mock(); + _soCreator = new Mock(); + _versionCreator = new Mock(); + _uhroCreator = new Mock(); + + _creator = new RoutesCreator( + _cthCreator.Object, + _aoCreator.Object, + _utpCreator.Object, + _ridkCreator.Object, + _qosoCreator.Object, + _rroCreator.Object, + _rloCreator.Object, + _rCreator.Object, + _hhoCreator.Object, + _hfarCreator.Object, + _daCreator.Object, + _lboCreator.Object, + _rrkCreator.Object, + _soCreator.Object, + _versionCreator.Object, + _uhroCreator.Object + ); + } + + [Fact] + public void should_return_nothing() + { + var fileConfig = new FileConfiguration(); + + this.Given(_ => GivenThe(fileConfig)) + .When(_ => WhenICreate()) + .Then(_ => ThenNoRoutesAreReturned()) + .BDDfy(); + } + + [Fact] + public void should_return_re_routes() + { + var fileConfig = new FileConfiguration + { + Routes = new List + { + new() + { + ServiceName = "dave", + DangerousAcceptAnyServerCertificateValidator = true, + AddClaimsToRequest = new Dictionary + { + { "a","b" }, + }, + AddHeadersToRequest = new Dictionary + { + { "c","d" }, + }, + AddQueriesToRequest = new Dictionary + { + { "e","f" }, + }, + UpstreamHttpMethod = new List { "GET", "POST" }, + }, + new() + { + ServiceName = "wave", + DangerousAcceptAnyServerCertificateValidator = false, + AddClaimsToRequest = new Dictionary + { + { "g","h" }, + }, + AddHeadersToRequest = new Dictionary + { + { "i","j" }, + }, + AddQueriesToRequest = new Dictionary + { + { "k","l" }, + }, + UpstreamHttpMethod = new List { "PUT", "DELETE" }, + }, + }, + }; + + this.Given(_ => GivenThe(fileConfig)) + .And(_ => GivenTheDependenciesAreSetUpCorrectly()) + .When(_ => WhenICreate()) + .Then(_ => ThenTheDependenciesAreCalledCorrectly()) + .And(_ => ThenTheRoutesAreCreated()) + .BDDfy(); + } + + private void ThenTheDependenciesAreCalledCorrectly() + { + ThenTheDepsAreCalledFor(_fileConfig.Routes[0], _fileConfig.GlobalConfiguration); + ThenTheDepsAreCalledFor(_fileConfig.Routes[1], _fileConfig.GlobalConfiguration); + } + + private void GivenTheDependenciesAreSetUpCorrectly() + { + _expectedVersion = new Version("1.1"); + _rro = new RouteOptions(false, false, false, false, false); + _requestId = "testy"; + _rrk = "besty"; + _upt = new UpstreamPathTemplateBuilder().Build(); + _ao = new AuthenticationOptionsBuilder().Build(); + _ctt = new List(); + _qoso = new QoSOptionsBuilder().Build(); + _rlo = new RateLimitOptionsBuilder().Build(); + _region = "vesty"; + _hho = new HttpHandlerOptionsBuilder().Build(); + _ht = new HeaderTransformations(new List(), new List(), new List(), new List()); + _dhp = new List(); + _lbo = new LoadBalancerOptionsBuilder().Build(); + + _rroCreator.Setup(x => x.Create(It.IsAny())).Returns(_rro); + _ridkCreator.Setup(x => x.Create(It.IsAny(), It.IsAny())).Returns(_requestId); + _rrkCreator.Setup(x => x.Create(It.IsAny())).Returns(_rrk); + _utpCreator.Setup(x => x.Create(It.IsAny())).Returns(_upt); + _aoCreator.Setup(x => x.Create(It.IsAny())).Returns(_ao); + _cthCreator.Setup(x => x.Create(It.IsAny>())).Returns(_ctt); + _qosoCreator.Setup(x => x.Create(It.IsAny(), It.IsAny(), It.IsAny>())).Returns(_qoso); + _rloCreator.Setup(x => x.Create(It.IsAny(), It.IsAny())).Returns(_rlo); + _rCreator.Setup(x => x.Create(It.IsAny())).Returns(_region); + _hhoCreator.Setup(x => x.Create(It.IsAny())).Returns(_hho); + _hfarCreator.Setup(x => x.Create(It.IsAny())).Returns(_ht); + _daCreator.Setup(x => x.Create(It.IsAny())).Returns(_dhp); + _lboCreator.Setup(x => x.Create(It.IsAny())).Returns(_lbo); + _versionCreator.Setup(x => x.Create(It.IsAny())).Returns(_expectedVersion); + } + + private void ThenTheRoutesAreCreated() + { + _result.Count.ShouldBe(2); + + ThenTheRouteIsSet(_fileConfig.Routes[0], 0); + ThenTheRouteIsSet(_fileConfig.Routes[1], 1); + } + + private void ThenNoRoutesAreReturned() + { + _result.ShouldBeEmpty(); + } + + private void GivenThe(FileConfiguration fileConfig) + { + _fileConfig = fileConfig; + } + + private void WhenICreate() + { + _result = _creator.Create(_fileConfig); + } + + private void ThenTheRouteIsSet(FileRoute expected, int routeIndex) + { + _result[routeIndex].DownstreamRoute[0].DownstreamHttpVersion.ShouldBe(_expectedVersion); + _result[routeIndex].DownstreamRoute[0].IsAuthenticated.ShouldBe(_rro.IsAuthenticated); + _result[routeIndex].DownstreamRoute[0].IsAuthorized.ShouldBe(_rro.IsAuthorized); + _result[routeIndex].DownstreamRoute[0].IsCached.ShouldBe(_rro.IsCached); + _result[routeIndex].DownstreamRoute[0].EnableEndpointEndpointRateLimiting.ShouldBe(_rro.EnableRateLimiting); + _result[routeIndex].DownstreamRoute[0].RequestIdKey.ShouldBe(_requestId); + _result[routeIndex].DownstreamRoute[0].LoadBalancerKey.ShouldBe(_rrk); + _result[routeIndex].DownstreamRoute[0].UpstreamPathTemplate.ShouldBe(_upt); + _result[routeIndex].DownstreamRoute[0].AuthenticationOptions.ShouldBe(_ao); + _result[routeIndex].DownstreamRoute[0].ClaimsToHeaders.ShouldBe(_ctt); + _result[routeIndex].DownstreamRoute[0].ClaimsToQueries.ShouldBe(_ctt); + _result[routeIndex].DownstreamRoute[0].ClaimsToClaims.ShouldBe(_ctt); + _result[routeIndex].DownstreamRoute[0].QosOptions.ShouldBe(_qoso); + _result[routeIndex].DownstreamRoute[0].RateLimitOptions.ShouldBe(_rlo); + _result[routeIndex].DownstreamRoute[0].CacheOptions.Region.ShouldBe(_region); + _result[routeIndex].DownstreamRoute[0].CacheOptions.TtlSeconds.ShouldBe(expected.FileCacheOptions.TtlSeconds); + _result[routeIndex].DownstreamRoute[0].HttpHandlerOptions.ShouldBe(_hho); + _result[routeIndex].DownstreamRoute[0].UpstreamHeadersFindAndReplace.ShouldBe(_ht.Upstream); + _result[routeIndex].DownstreamRoute[0].DownstreamHeadersFindAndReplace.ShouldBe(_ht.Downstream); + _result[routeIndex].DownstreamRoute[0].AddHeadersToUpstream.ShouldBe(_ht.AddHeadersToUpstream); + _result[routeIndex].DownstreamRoute[0].AddHeadersToDownstream.ShouldBe(_ht.AddHeadersToDownstream); + _result[routeIndex].DownstreamRoute[0].DownstreamAddresses.ShouldBe(_dhp); + _result[routeIndex].DownstreamRoute[0].LoadBalancerOptions.ShouldBe(_lbo); + _result[routeIndex].DownstreamRoute[0].UseServiceDiscovery.ShouldBe(_rro.UseServiceDiscovery); + _result[routeIndex].DownstreamRoute[0].DangerousAcceptAnyServerCertificateValidator.ShouldBe(expected.DangerousAcceptAnyServerCertificateValidator); + _result[routeIndex].DownstreamRoute[0].DelegatingHandlers.ShouldBe(expected.DelegatingHandlers); + _result[routeIndex].DownstreamRoute[0].ServiceName.ShouldBe(expected.ServiceName); + _result[routeIndex].DownstreamRoute[0].DownstreamScheme.ShouldBe(expected.DownstreamScheme); + _result[routeIndex].DownstreamRoute[0].RouteClaimsRequirement.ShouldBe(expected.RouteClaimsRequirement); + _result[routeIndex].DownstreamRoute[0].DownstreamPathTemplate.Value.ShouldBe(expected.DownstreamPathTemplate); + _result[routeIndex].DownstreamRoute[0].Key.ShouldBe(expected.Key); + _result[routeIndex].UpstreamHttpMethod + .Select(x => x.Method) + .ToList() + .ShouldContain(x => x == expected.UpstreamHttpMethod[0]); + _result[routeIndex].UpstreamHttpMethod + .Select(x => x.Method) + .ToList() + .ShouldContain(x => x == expected.UpstreamHttpMethod[1]); + _result[routeIndex].UpstreamHost.ShouldBe(expected.UpstreamHost); + _result[routeIndex].DownstreamRoute.Count.ShouldBe(1); + _result[routeIndex].UpstreamTemplatePattern.ShouldBe(_upt); + } + + private void ThenTheDepsAreCalledFor(FileRoute fileRoute, FileGlobalConfiguration globalConfig) + { + _rroCreator.Verify(x => x.Create(fileRoute), Times.Once); + _ridkCreator.Verify(x => x.Create(fileRoute, globalConfig), Times.Once); + _rrkCreator.Verify(x => x.Create(fileRoute), Times.Once); + _utpCreator.Verify(x => x.Create(fileRoute), Times.Exactly(2)); + _aoCreator.Verify(x => x.Create(fileRoute), Times.Once); + _cthCreator.Verify(x => x.Create(fileRoute.AddHeadersToRequest), Times.Once); + _cthCreator.Verify(x => x.Create(fileRoute.AddClaimsToRequest), Times.Once); + _cthCreator.Verify(x => x.Create(fileRoute.AddQueriesToRequest), Times.Once); + _qosoCreator.Verify(x => x.Create(fileRoute.QoSOptions, fileRoute.UpstreamPathTemplate, fileRoute.UpstreamHttpMethod)); + _rloCreator.Verify(x => x.Create(fileRoute.RateLimitOptions, globalConfig), Times.Once); + _rCreator.Verify(x => x.Create(fileRoute), Times.Once); + _hhoCreator.Verify(x => x.Create(fileRoute.HttpHandlerOptions), Times.Once); + _hfarCreator.Verify(x => x.Create(fileRoute), Times.Once); + _daCreator.Verify(x => x.Create(fileRoute), Times.Once); + _lboCreator.Verify(x => x.Create(fileRoute.LoadBalancerOptions), Times.Once); + _soCreator.Verify(x => x.Create(fileRoute.SecurityOptions), Times.Once); + } + } +} diff --git a/test/Ocelot.UnitTests/Configuration/UpstreamHeaderRoutingOptionsCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/UpstreamHeaderRoutingOptionsCreatorTests.cs new file mode 100644 index 0000000000..aee8fc4a21 --- /dev/null +++ b/test/Ocelot.UnitTests/Configuration/UpstreamHeaderRoutingOptionsCreatorTests.cs @@ -0,0 +1,68 @@ +using System.Collections.Generic; +using Ocelot.Configuration; +using Ocelot.Configuration.Creator; +using Ocelot.Configuration.File; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Configuration; + +public class UpstreamHeaderRoutingOptionsCreatorTests +{ + private FileUpstreamHeaderRoutingOptions _fileUpstreamHeaderRoutingOptions; + private IUpstreamHeaderRoutingOptionsCreator _creator; + private UpstreamHeaderRoutingOptions _upstreamHeaderRoutingOptions; + + public UpstreamHeaderRoutingOptionsCreatorTests() + { + _creator = new UpstreamHeaderRoutingOptionsCreator(); + } + + [Fact] + public void should_create_upstream_routing_header_options() + { + UpstreamHeaderRoutingOptions expected = new UpstreamHeaderRoutingOptions( + headers: new Dictionary>() + { + { "header1", new HashSet() { "value1", "value2" } }, + { "header2", new HashSet() { "value3" } }, + }, + triggerOn: UpstreamHeaderRoutingTriggerMode.All + ); + this.Given(_ => GivenTheseFileUpstreamHeaderRoutingOptions()) + .When(_ => WhenICreate()) + .Then(_ => ThenTheCreatedMatches(expected)) + .BDDfy(); + } + + private void GivenTheseFileUpstreamHeaderRoutingOptions() + { + _fileUpstreamHeaderRoutingOptions = new FileUpstreamHeaderRoutingOptions() + { + Headers = new Dictionary>() + { + { "Header1", new List() { "Value1", "Value2" } }, + { "Header2", new List() { "Value3" } }, + }, + TriggerOn = "all", + }; + } + + private void WhenICreate() + { + _upstreamHeaderRoutingOptions = _creator.Create(_fileUpstreamHeaderRoutingOptions); + } + + private void ThenTheCreatedMatches(UpstreamHeaderRoutingOptions expected) + { + _upstreamHeaderRoutingOptions.Headers.Headers.Count.ShouldBe(expected.Headers.Headers.Count); + foreach (KeyValuePair> pair in _upstreamHeaderRoutingOptions.Headers.Headers) + { + expected.Headers.Headers.TryGetValue(pair.Key, out var expectedValue).ShouldBe(true); + expectedValue.SetEquals(pair.Value).ShouldBe(true); + } + + _upstreamHeaderRoutingOptions.TriggerOn.ShouldBe(expected.TriggerOn); + } +} diff --git a/test/Ocelot.UnitTests/Configuration/UpstreamRoutingHeadersTests.cs b/test/Ocelot.UnitTests/Configuration/UpstreamRoutingHeadersTests.cs new file mode 100644 index 0000000000..8700bb4ec0 --- /dev/null +++ b/test/Ocelot.UnitTests/Configuration/UpstreamRoutingHeadersTests.cs @@ -0,0 +1,145 @@ +using System.Collections.Generic; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Primitives; +using Ocelot.Configuration; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Configuration; + +public class UpstreamRoutingHeadersTests +{ + private Dictionary> _headersDictionary; + private UpstreamRoutingHeaders _upstreamRoutingHeaders; + private IHeaderDictionary _requestHeaders; + + [Fact] + public void should_create_empty_headers() + { + this.Given(_ => GivenEmptyHeaderDictionary()) + .When(_ => WhenICreate()) + .Then(_ => ThenEmptyIs(true)) + .BDDfy(); + } + + [Fact] + public void should_create_preset_headers() + { + this.Given(_ => GivenPresetHeaderDictionary()) + .When(_ => WhenICreate()) + .Then(_ => ThenEmptyIs(false)) + .BDDfy(); + } + + [Fact] + public void should_not_match_mismatching_request_headers() + { + this.Given(_ => GivenPresetHeaderDictionary()) + .And(_ => AndGivenMismatchingRequestHeaders()) + .When(_ => WhenICreate()) + .Then(_ => ThenHasAnyOfIs(false)) + .And(_ => ThenHasAllOfIs(false)) + .BDDfy(); + } + + [Fact] + public void should_not_match_matching_header_with_mismatching_value() + { + this.Given(_ => GivenPresetHeaderDictionary()) + .And(_ => AndGivenOneMatchingHeaderWithMismatchingValue()) + .When(_ => WhenICreate()) + .Then(_ => ThenHasAnyOfIs(false)) + .And(_ => ThenHasAllOfIs(false)) + .BDDfy(); + } + + [Fact] + public void should_match_any_header_not_all() + { + this.Given(_ => GivenPresetHeaderDictionary()) + .And(_ => AndGivenOneMatchingHeaderWithMatchingValue()) + .When(_ => WhenICreate()) + .Then(_ => ThenHasAnyOfIs(true)) + .And(_ => ThenHasAllOfIs(false)) + .BDDfy(); + } + + [Fact] + public void should_match_any_and_all_headers() + { + this.Given(_ => GivenPresetHeaderDictionary()) + .And(_ => AndGivenTwoMatchingHeadersWithMatchingValues()) + .When(_ => WhenICreate()) + .Then(_ => ThenHasAnyOfIs(true)) + .And(_ => ThenHasAllOfIs(true)) + .BDDfy(); + } + + private void GivenEmptyHeaderDictionary() + { + _headersDictionary = new Dictionary>(); + } + + private void GivenPresetHeaderDictionary() + { + _headersDictionary = new Dictionary>() + { + { "testheader1", new HashSet() { "testheader1value1", "testheader1value2" } }, + { "testheader2", new HashSet() { "testheader1Value1", "testheader2value2" } }, + }; + } + + private void AndGivenMismatchingRequestHeaders() + { + _requestHeaders = new HeaderDictionary() + { + { "someHeader", new StringValues(new string[] { "someHeaderValue" }) }, + }; + } + + private void AndGivenOneMatchingHeaderWithMismatchingValue() + { + _requestHeaders = new HeaderDictionary() + { + { "testHeader1", new StringValues(new string[] { "mismatchingValue" }) }, + }; + } + + private void AndGivenOneMatchingHeaderWithMatchingValue() + { + _requestHeaders = new HeaderDictionary() + { + { "testHeader1", new StringValues(new string[] { "testHeader1Value1" }) }, + }; + } + + private void AndGivenTwoMatchingHeadersWithMatchingValues() + { + _requestHeaders = new HeaderDictionary() + { + { "testHeader1", new StringValues(new string[] { "testHeader1Value1", "bogusValue" }) }, + { "testHeader2", new StringValues(new string[] { "bogusValue", "testHeader2Value2" }) }, + }; + } + + private void WhenICreate() + { + _upstreamRoutingHeaders = new UpstreamRoutingHeaders(_headersDictionary); + } + + private void ThenEmptyIs(bool expected) + { + _upstreamRoutingHeaders.Any().ShouldBe(expected); + } + + private void ThenHasAnyOfIs(bool expected) + { + _upstreamRoutingHeaders.HasAnyOf(_requestHeaders).ShouldBe(expected); + } + + private void ThenHasAllOfIs(bool expected) + { + _upstreamRoutingHeaders.HasAllOf(_requestHeaders).ShouldBe(expected); + } +} diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteCreatorTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteCreatorTests.cs index ecfa271902..59c16d5c72 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteCreatorTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteCreatorTests.cs @@ -9,298 +9,301 @@ using System; using System.Collections.Generic; using System.Net.Http; +using Microsoft.AspNetCore.Http; using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.DownstreamRouteFinder -{ - public class DownstreamRouteCreatorTests - { - private readonly DownstreamRouteCreator _creator; - private readonly QoSOptions _qoSOptions; - private readonly HttpHandlerOptions _handlerOptions; - private readonly LoadBalancerOptions _loadBalancerOptions; - private Response _result; - private string _upstreamHost; - private string _upstreamUrlPath; - private string _upstreamHttpMethod; - private IInternalConfiguration _configuration; - private readonly Mock _qosOptionsCreator; - private Response _resultTwo; - private readonly string _upstreamQuery; - - public DownstreamRouteCreatorTests() - { - _qosOptionsCreator = new Mock(); - _qoSOptions = new QoSOptionsBuilder().Build(); - _handlerOptions = new HttpHandlerOptionsBuilder().Build(); - _loadBalancerOptions = new LoadBalancerOptionsBuilder().WithType(nameof(NoLoadBalancer)).Build(); - _qosOptionsCreator - .Setup(x => x.Create(It.IsAny(), It.IsAny(), It.IsAny>())) - .Returns(_qoSOptions); +using Xunit; + +namespace Ocelot.UnitTests.DownstreamRouteFinder +{ + public class DownstreamRouteCreatorTests + { + private readonly DownstreamRouteCreator _creator; + private readonly QoSOptions _qoSOptions; + private readonly HttpHandlerOptions _handlerOptions; + private readonly LoadBalancerOptions _loadBalancerOptions; + private Response _result; + private string _upstreamHost; + private string _upstreamUrlPath; + private string _upstreamHttpMethod; + private IInternalConfiguration _configuration; + private readonly Mock _qosOptionsCreator; + private Response _resultTwo; + private readonly string _upstreamQuery; + private readonly IHeaderDictionary _upstreamHeaders; + + public DownstreamRouteCreatorTests() + { + _qosOptionsCreator = new Mock(); + _qoSOptions = new QoSOptionsBuilder().Build(); + _handlerOptions = new HttpHandlerOptionsBuilder().Build(); + _loadBalancerOptions = new LoadBalancerOptionsBuilder().WithType(nameof(NoLoadBalancer)).Build(); + _qosOptionsCreator + .Setup(x => x.Create(It.IsAny(), It.IsAny(), It.IsAny>())) + .Returns(_qoSOptions); _creator = new DownstreamRouteCreator(_qosOptionsCreator.Object); - _upstreamQuery = string.Empty; - } - - [Fact] - public void should_create_downstream_route() - { - var configuration = new InternalConfiguration(null, "doesnt matter", null, "doesnt matter", _loadBalancerOptions, "http", _qoSOptions, _handlerOptions, new Version("1.1")); - - this.Given(_ => GivenTheConfiguration(configuration)) - .When(_ => WhenICreate()) - .Then(_ => ThenTheDownstreamRouteIsCreated()) - .BDDfy(); - } - - [Fact] - public void should_create_downstream_route_with_rate_limit_options() - { - var rateLimitOptions = new RateLimitOptionsBuilder() - .WithEnableRateLimiting(true) - .WithClientIdHeader("test") - .Build(); - - var downstreamRoute = new DownstreamRouteBuilder() - .WithServiceName("auth") - .WithRateLimitOptions(rateLimitOptions) - .Build(); - - var route = new RouteBuilder() - .WithDownstreamRoute(downstreamRoute) - .Build(); - - var routes = new List { route }; - - var configuration = new InternalConfiguration(routes, "doesnt matter", null, "doesnt matter", _loadBalancerOptions, "http", _qoSOptions, _handlerOptions, new Version("1.1")); - - this.Given(_ => GivenTheConfiguration(configuration)) - .When(_ => WhenICreate()) - .Then(_ => ThenTheDownstreamRouteIsCreated()) - .And(_ => WithRateLimitOptions(rateLimitOptions)) - .BDDfy(); - } - - [Fact] - public void should_cache_downstream_route() - { - var configuration = new InternalConfiguration(null, "doesnt matter", null, "doesnt matter", _loadBalancerOptions, "http", _qoSOptions, _handlerOptions, new Version("1.1")); - - this.Given(_ => GivenTheConfiguration(configuration, "/geoffisthebest/")) - .When(_ => WhenICreate()) - .And(_ => GivenTheConfiguration(configuration, "/geoffisthebest/")) - .When(_ => WhenICreateAgain()) - .Then(_ => ThenTheDownstreamRoutesAreTheSameReference()) - .BDDfy(); - } - - [Fact] - public void should_not_cache_downstream_route() - { - var configuration = new InternalConfiguration(null, "doesnt matter", null, "doesnt matter", _loadBalancerOptions, "http", _qoSOptions, _handlerOptions, new Version("1.1")); - - this.Given(_ => GivenTheConfiguration(configuration, "/geoffistheworst/")) - .When(_ => WhenICreate()) - .And(_ => GivenTheConfiguration(configuration, "/geoffisthebest/")) - .When(_ => WhenICreateAgain()) - .Then(_ => ThenTheDownstreamRoutesAreTheNotSameReference()) - .BDDfy(); - } - - [Fact] - public void should_create_downstream_route_with_no_path() - { - var upstreamUrlPath = "/auth/"; - var configuration = new InternalConfiguration(null, "doesnt matter", null, "doesnt matter", _loadBalancerOptions, "http", _qoSOptions, _handlerOptions, new Version("1.1")); - - this.Given(_ => GivenTheConfiguration(configuration, upstreamUrlPath)) - .When(_ => WhenICreate()) - .Then(_ => ThenTheDownstreamPathIsForwardSlash()) - .BDDfy(); - } - - [Fact] - public void should_create_downstream_route_with_only_first_segment_no_traling_slash() - { - var upstreamUrlPath = "/auth"; - var configuration = new InternalConfiguration(null, "doesnt matter", null, "doesnt matter", _loadBalancerOptions, "http", _qoSOptions, _handlerOptions, new Version("1.1")); - - this.Given(_ => GivenTheConfiguration(configuration, upstreamUrlPath)) - .When(_ => WhenICreate()) - .Then(_ => ThenTheDownstreamPathIsForwardSlash()) - .BDDfy(); - } - - [Fact] - public void should_create_downstream_route_with_segments_no_traling_slash() - { - var upstreamUrlPath = "/auth/test"; - var configuration = new InternalConfiguration(null, "doesnt matter", null, "doesnt matter", _loadBalancerOptions, "http", _qoSOptions, _handlerOptions, new Version("1.1")); - - this.Given(_ => GivenTheConfiguration(configuration, upstreamUrlPath)) - .When(_ => WhenICreate()) - .Then(_ => ThenThePathDoesNotHaveTrailingSlash()) - .BDDfy(); - } - - [Fact] - public void should_create_downstream_route_and_remove_query_string() - { - var upstreamUrlPath = "/auth/test?test=1&best=2"; - var configuration = new InternalConfiguration(null, "doesnt matter", null, "doesnt matter", _loadBalancerOptions, "http", _qoSOptions, _handlerOptions, new Version("1.1")); - - this.Given(_ => GivenTheConfiguration(configuration, upstreamUrlPath)) - .When(_ => WhenICreate()) - .Then(_ => ThenTheQueryStringIsRemoved()) - .BDDfy(); - } - - [Fact] - public void should_create_downstream_route_for_sticky_sessions() - { - var loadBalancerOptions = new LoadBalancerOptionsBuilder().WithType(nameof(CookieStickySessions)).WithKey("boom").WithExpiryInMs(1).Build(); - var configuration = new InternalConfiguration(null, "doesnt matter", null, "doesnt matter", loadBalancerOptions, "http", _qoSOptions, _handlerOptions, new Version("1.1")); - - this.Given(_ => GivenTheConfiguration(configuration)) - .When(_ => WhenICreate()) - .Then(_ => ThenTheStickySessionLoadBalancerIsUsed(loadBalancerOptions)) - .BDDfy(); - } - - [Fact] - public void should_create_downstream_route_with_qos() - { - var qoSOptions = new QoSOptionsBuilder() - .WithExceptionsAllowedBeforeBreaking(1) - .WithTimeoutValue(1) - .Build(); - - var configuration = new InternalConfiguration(null, "doesnt matter", null, "doesnt matter", _loadBalancerOptions, "http", qoSOptions, _handlerOptions, new Version("1.1")); - - this.Given(_ => GivenTheConfiguration(configuration)) - .And(_ => GivenTheQosCreatorReturns(qoSOptions)) - .When(_ => WhenICreate()) - .Then(_ => ThenTheQosOptionsAreSet(qoSOptions)) - .BDDfy(); - } - - [Fact] - public void should_create_downstream_route_with_handler_options() - { - var configuration = new InternalConfiguration(null, "doesnt matter", null, "doesnt matter", _loadBalancerOptions, "http", _qoSOptions, _handlerOptions, new Version("1.1")); - - this.Given(_ => GivenTheConfiguration(configuration)) - .When(_ => WhenICreate()) - .Then(_ => ThenTheHandlerOptionsAreSet()) - .BDDfy(); - } - - private void GivenTheQosCreatorReturns(QoSOptions options) - { - _qosOptionsCreator - .Setup(x => x.Create(It.IsAny(), It.IsAny(), It.IsAny>())) - .Returns(options); - } - - private void WithRateLimitOptions(RateLimitOptions expected) - { - _result.Data.Route.DownstreamRoute[0].EnableEndpointEndpointRateLimiting.ShouldBeTrue(); - _result.Data.Route.DownstreamRoute[0].RateLimitOptions.EnableRateLimiting.ShouldBe(expected.EnableRateLimiting); - _result.Data.Route.DownstreamRoute[0].RateLimitOptions.ClientIdHeader.ShouldBe(expected.ClientIdHeader); - } - - private void ThenTheDownstreamRouteIsCreated() - { - _result.Data.Route.DownstreamRoute[0].DownstreamPathTemplate.Value.ShouldBe("/test"); - _result.Data.Route.UpstreamHttpMethod[0].ShouldBe(HttpMethod.Get); - _result.Data.Route.DownstreamRoute[0].ServiceName.ShouldBe("auth"); - _result.Data.Route.DownstreamRoute[0].LoadBalancerKey.ShouldBe("/auth/test|GET"); - _result.Data.Route.DownstreamRoute[0].UseServiceDiscovery.ShouldBeTrue(); - _result.Data.Route.DownstreamRoute[0].HttpHandlerOptions.ShouldNotBeNull(); - _result.Data.Route.DownstreamRoute[0].QosOptions.ShouldNotBeNull(); - _result.Data.Route.DownstreamRoute[0].DownstreamScheme.ShouldBe("http"); - _result.Data.Route.DownstreamRoute[0].LoadBalancerOptions.Type.ShouldBe(nameof(NoLoadBalancer)); - _result.Data.Route.DownstreamRoute[0].HttpHandlerOptions.ShouldBe(_handlerOptions); - _result.Data.Route.DownstreamRoute[0].QosOptions.ShouldBe(_qoSOptions); - _result.Data.Route.UpstreamTemplatePattern.ShouldNotBeNull(); - _result.Data.Route.DownstreamRoute[0].UpstreamPathTemplate.ShouldNotBeNull(); - } - - private void ThenTheDownstreamPathIsForwardSlash() - { - _result.Data.Route.DownstreamRoute[0].DownstreamPathTemplate.Value.ShouldBe("/"); - _result.Data.Route.DownstreamRoute[0].ServiceName.ShouldBe("auth"); - _result.Data.Route.DownstreamRoute[0].LoadBalancerKey.ShouldBe("/auth/|GET"); - } - - private void ThenThePathDoesNotHaveTrailingSlash() - { - _result.Data.Route.DownstreamRoute[0].DownstreamPathTemplate.Value.ShouldBe("/test"); - _result.Data.Route.DownstreamRoute[0].ServiceName.ShouldBe("auth"); - _result.Data.Route.DownstreamRoute[0].LoadBalancerKey.ShouldBe("/auth/test|GET"); - } - - private void ThenTheQueryStringIsRemoved() - { - _result.Data.Route.DownstreamRoute[0].DownstreamPathTemplate.Value.ShouldBe("/test"); - _result.Data.Route.DownstreamRoute[0].ServiceName.ShouldBe("auth"); - _result.Data.Route.DownstreamRoute[0].LoadBalancerKey.ShouldBe("/auth/test|GET"); - } - - private void ThenTheStickySessionLoadBalancerIsUsed(LoadBalancerOptions expected) - { - _result.Data.Route.DownstreamRoute[0].LoadBalancerKey.ShouldBe($"{nameof(CookieStickySessions)}:boom"); - _result.Data.Route.DownstreamRoute[0].LoadBalancerOptions.Type.ShouldBe(nameof(CookieStickySessions)); - _result.Data.Route.DownstreamRoute[0].LoadBalancerOptions.ShouldBe(expected); - } - - private void ThenTheQosOptionsAreSet(QoSOptions expected) - { - _result.Data.Route.DownstreamRoute[0].QosOptions.ShouldBe(expected); - _result.Data.Route.DownstreamRoute[0].QosOptions.UseQos.ShouldBeTrue(); - _qosOptionsCreator - .Verify(x => x.Create(expected, _upstreamUrlPath, It.IsAny>()), Times.Once); - } - - private void GivenTheConfiguration(IInternalConfiguration config) - { - _upstreamHost = "doesnt matter"; - _upstreamUrlPath = "/auth/test"; - _upstreamHttpMethod = "GET"; - _configuration = config; - } - - private void GivenTheConfiguration(IInternalConfiguration config, string upstreamUrlPath) - { - _upstreamHost = "doesnt matter"; - _upstreamUrlPath = upstreamUrlPath; - _upstreamHttpMethod = "GET"; - _configuration = config; - } - - private void ThenTheHandlerOptionsAreSet() - { - _result.Data.Route.DownstreamRoute[0].HttpHandlerOptions.ShouldBe(_handlerOptions); - } - - private void WhenICreate() - { - _result = _creator.Get(_upstreamUrlPath, _upstreamQuery, _upstreamHttpMethod, _configuration, _upstreamHost); - } - - private void WhenICreateAgain() - { - _resultTwo = _creator.Get(_upstreamUrlPath, _upstreamQuery, _upstreamHttpMethod, _configuration, _upstreamHost); - } - - private void ThenTheDownstreamRoutesAreTheSameReference() - { - _result.ShouldBe(_resultTwo); - } - - private void ThenTheDownstreamRoutesAreTheNotSameReference() - { - _result.ShouldNotBe(_resultTwo); - } - } -} + _upstreamQuery = string.Empty; + _upstreamHeaders = new HeaderDictionary(); + } + + [Fact] + public void should_create_downstream_route() + { + var configuration = new InternalConfiguration(null, "doesnt matter", null, "doesnt matter", _loadBalancerOptions, "http", _qoSOptions, _handlerOptions, new Version("1.1")); + + this.Given(_ => GivenTheConfiguration(configuration)) + .When(_ => WhenICreate()) + .Then(_ => ThenTheDownstreamRouteIsCreated()) + .BDDfy(); + } + + [Fact] + public void should_create_downstream_route_with_rate_limit_options() + { + var rateLimitOptions = new RateLimitOptionsBuilder() + .WithEnableRateLimiting(true) + .WithClientIdHeader("test") + .Build(); + + var downstreamRoute = new DownstreamRouteBuilder() + .WithServiceName("auth") + .WithRateLimitOptions(rateLimitOptions) + .Build(); + + var route = new RouteBuilder() + .WithDownstreamRoute(downstreamRoute) + .Build(); + + var routes = new List { route }; + + var configuration = new InternalConfiguration(routes, "doesnt matter", null, "doesnt matter", _loadBalancerOptions, "http", _qoSOptions, _handlerOptions, new Version("1.1")); + + this.Given(_ => GivenTheConfiguration(configuration)) + .When(_ => WhenICreate()) + .Then(_ => ThenTheDownstreamRouteIsCreated()) + .And(_ => WithRateLimitOptions(rateLimitOptions)) + .BDDfy(); + } + + [Fact] + public void should_cache_downstream_route() + { + var configuration = new InternalConfiguration(null, "doesnt matter", null, "doesnt matter", _loadBalancerOptions, "http", _qoSOptions, _handlerOptions, new Version("1.1")); + + this.Given(_ => GivenTheConfiguration(configuration, "/geoffisthebest/")) + .When(_ => WhenICreate()) + .And(_ => GivenTheConfiguration(configuration, "/geoffisthebest/")) + .When(_ => WhenICreateAgain()) + .Then(_ => ThenTheDownstreamRoutesAreTheSameReference()) + .BDDfy(); + } + + [Fact] + public void should_not_cache_downstream_route() + { + var configuration = new InternalConfiguration(null, "doesnt matter", null, "doesnt matter", _loadBalancerOptions, "http", _qoSOptions, _handlerOptions, new Version("1.1")); + + this.Given(_ => GivenTheConfiguration(configuration, "/geoffistheworst/")) + .When(_ => WhenICreate()) + .And(_ => GivenTheConfiguration(configuration, "/geoffisthebest/")) + .When(_ => WhenICreateAgain()) + .Then(_ => ThenTheDownstreamRoutesAreTheNotSameReference()) + .BDDfy(); + } + + [Fact] + public void should_create_downstream_route_with_no_path() + { + var upstreamUrlPath = "/auth/"; + var configuration = new InternalConfiguration(null, "doesnt matter", null, "doesnt matter", _loadBalancerOptions, "http", _qoSOptions, _handlerOptions, new Version("1.1")); + + this.Given(_ => GivenTheConfiguration(configuration, upstreamUrlPath)) + .When(_ => WhenICreate()) + .Then(_ => ThenTheDownstreamPathIsForwardSlash()) + .BDDfy(); + } + + [Fact] + public void should_create_downstream_route_with_only_first_segment_no_traling_slash() + { + var upstreamUrlPath = "/auth"; + var configuration = new InternalConfiguration(null, "doesnt matter", null, "doesnt matter", _loadBalancerOptions, "http", _qoSOptions, _handlerOptions, new Version("1.1")); + + this.Given(_ => GivenTheConfiguration(configuration, upstreamUrlPath)) + .When(_ => WhenICreate()) + .Then(_ => ThenTheDownstreamPathIsForwardSlash()) + .BDDfy(); + } + + [Fact] + public void should_create_downstream_route_with_segments_no_traling_slash() + { + var upstreamUrlPath = "/auth/test"; + var configuration = new InternalConfiguration(null, "doesnt matter", null, "doesnt matter", _loadBalancerOptions, "http", _qoSOptions, _handlerOptions, new Version("1.1")); + + this.Given(_ => GivenTheConfiguration(configuration, upstreamUrlPath)) + .When(_ => WhenICreate()) + .Then(_ => ThenThePathDoesNotHaveTrailingSlash()) + .BDDfy(); + } + + [Fact] + public void should_create_downstream_route_and_remove_query_string() + { + var upstreamUrlPath = "/auth/test?test=1&best=2"; + var configuration = new InternalConfiguration(null, "doesnt matter", null, "doesnt matter", _loadBalancerOptions, "http", _qoSOptions, _handlerOptions, new Version("1.1")); + + this.Given(_ => GivenTheConfiguration(configuration, upstreamUrlPath)) + .When(_ => WhenICreate()) + .Then(_ => ThenTheQueryStringIsRemoved()) + .BDDfy(); + } + + [Fact] + public void should_create_downstream_route_for_sticky_sessions() + { + var loadBalancerOptions = new LoadBalancerOptionsBuilder().WithType(nameof(CookieStickySessions)).WithKey("boom").WithExpiryInMs(1).Build(); + var configuration = new InternalConfiguration(null, "doesnt matter", null, "doesnt matter", loadBalancerOptions, "http", _qoSOptions, _handlerOptions, new Version("1.1")); + + this.Given(_ => GivenTheConfiguration(configuration)) + .When(_ => WhenICreate()) + .Then(_ => ThenTheStickySessionLoadBalancerIsUsed(loadBalancerOptions)) + .BDDfy(); + } + + [Fact] + public void should_create_downstream_route_with_qos() + { + var qoSOptions = new QoSOptionsBuilder() + .WithExceptionsAllowedBeforeBreaking(1) + .WithTimeoutValue(1) + .Build(); + + var configuration = new InternalConfiguration(null, "doesnt matter", null, "doesnt matter", _loadBalancerOptions, "http", qoSOptions, _handlerOptions, new Version("1.1")); + + this.Given(_ => GivenTheConfiguration(configuration)) + .And(_ => GivenTheQosCreatorReturns(qoSOptions)) + .When(_ => WhenICreate()) + .Then(_ => ThenTheQosOptionsAreSet(qoSOptions)) + .BDDfy(); + } + + [Fact] + public void should_create_downstream_route_with_handler_options() + { + var configuration = new InternalConfiguration(null, "doesnt matter", null, "doesnt matter", _loadBalancerOptions, "http", _qoSOptions, _handlerOptions, new Version("1.1")); + + this.Given(_ => GivenTheConfiguration(configuration)) + .When(_ => WhenICreate()) + .Then(_ => ThenTheHandlerOptionsAreSet()) + .BDDfy(); + } + + private void GivenTheQosCreatorReturns(QoSOptions options) + { + _qosOptionsCreator + .Setup(x => x.Create(It.IsAny(), It.IsAny(), It.IsAny>())) + .Returns(options); + } + + private void WithRateLimitOptions(RateLimitOptions expected) + { + _result.Data.Route.DownstreamRoute[0].EnableEndpointEndpointRateLimiting.ShouldBeTrue(); + _result.Data.Route.DownstreamRoute[0].RateLimitOptions.EnableRateLimiting.ShouldBe(expected.EnableRateLimiting); + _result.Data.Route.DownstreamRoute[0].RateLimitOptions.ClientIdHeader.ShouldBe(expected.ClientIdHeader); + } + + private void ThenTheDownstreamRouteIsCreated() + { + _result.Data.Route.DownstreamRoute[0].DownstreamPathTemplate.Value.ShouldBe("/test"); + _result.Data.Route.UpstreamHttpMethod[0].ShouldBe(HttpMethod.Get); + _result.Data.Route.DownstreamRoute[0].ServiceName.ShouldBe("auth"); + _result.Data.Route.DownstreamRoute[0].LoadBalancerKey.ShouldBe("/auth/test|GET"); + _result.Data.Route.DownstreamRoute[0].UseServiceDiscovery.ShouldBeTrue(); + _result.Data.Route.DownstreamRoute[0].HttpHandlerOptions.ShouldNotBeNull(); + _result.Data.Route.DownstreamRoute[0].QosOptions.ShouldNotBeNull(); + _result.Data.Route.DownstreamRoute[0].DownstreamScheme.ShouldBe("http"); + _result.Data.Route.DownstreamRoute[0].LoadBalancerOptions.Type.ShouldBe(nameof(NoLoadBalancer)); + _result.Data.Route.DownstreamRoute[0].HttpHandlerOptions.ShouldBe(_handlerOptions); + _result.Data.Route.DownstreamRoute[0].QosOptions.ShouldBe(_qoSOptions); + _result.Data.Route.UpstreamTemplatePattern.ShouldNotBeNull(); + _result.Data.Route.DownstreamRoute[0].UpstreamPathTemplate.ShouldNotBeNull(); + } + + private void ThenTheDownstreamPathIsForwardSlash() + { + _result.Data.Route.DownstreamRoute[0].DownstreamPathTemplate.Value.ShouldBe("/"); + _result.Data.Route.DownstreamRoute[0].ServiceName.ShouldBe("auth"); + _result.Data.Route.DownstreamRoute[0].LoadBalancerKey.ShouldBe("/auth/|GET"); + } + + private void ThenThePathDoesNotHaveTrailingSlash() + { + _result.Data.Route.DownstreamRoute[0].DownstreamPathTemplate.Value.ShouldBe("/test"); + _result.Data.Route.DownstreamRoute[0].ServiceName.ShouldBe("auth"); + _result.Data.Route.DownstreamRoute[0].LoadBalancerKey.ShouldBe("/auth/test|GET"); + } + + private void ThenTheQueryStringIsRemoved() + { + _result.Data.Route.DownstreamRoute[0].DownstreamPathTemplate.Value.ShouldBe("/test"); + _result.Data.Route.DownstreamRoute[0].ServiceName.ShouldBe("auth"); + _result.Data.Route.DownstreamRoute[0].LoadBalancerKey.ShouldBe("/auth/test|GET"); + } + + private void ThenTheStickySessionLoadBalancerIsUsed(LoadBalancerOptions expected) + { + _result.Data.Route.DownstreamRoute[0].LoadBalancerKey.ShouldBe($"{nameof(CookieStickySessions)}:boom"); + _result.Data.Route.DownstreamRoute[0].LoadBalancerOptions.Type.ShouldBe(nameof(CookieStickySessions)); + _result.Data.Route.DownstreamRoute[0].LoadBalancerOptions.ShouldBe(expected); + } + + private void ThenTheQosOptionsAreSet(QoSOptions expected) + { + _result.Data.Route.DownstreamRoute[0].QosOptions.ShouldBe(expected); + _result.Data.Route.DownstreamRoute[0].QosOptions.UseQos.ShouldBeTrue(); + _qosOptionsCreator + .Verify(x => x.Create(expected, _upstreamUrlPath, It.IsAny>()), Times.Once); + } + + private void GivenTheConfiguration(IInternalConfiguration config) + { + _upstreamHost = "doesnt matter"; + _upstreamUrlPath = "/auth/test"; + _upstreamHttpMethod = "GET"; + _configuration = config; + } + + private void GivenTheConfiguration(IInternalConfiguration config, string upstreamUrlPath) + { + _upstreamHost = "doesnt matter"; + _upstreamUrlPath = upstreamUrlPath; + _upstreamHttpMethod = "GET"; + _configuration = config; + } + + private void ThenTheHandlerOptionsAreSet() + { + _result.Data.Route.DownstreamRoute[0].HttpHandlerOptions.ShouldBe(_handlerOptions); + } + + private void WhenICreate() + { + _result = _creator.Get(_upstreamUrlPath, _upstreamQuery, _upstreamHttpMethod, _configuration, _upstreamHost, _upstreamHeaders); + } + + private void WhenICreateAgain() + { + _resultTwo = _creator.Get(_upstreamUrlPath, _upstreamQuery, _upstreamHttpMethod, _configuration, _upstreamHost, _upstreamHeaders); + } + + private void ThenTheDownstreamRoutesAreTheSameReference() + { + _result.ShouldBe(_resultTwo); + } + + private void ThenTheDownstreamRoutesAreTheNotSameReference() + { + _result.ShouldNotBe(_resultTwo); + } + } +} diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs index 8da8c8ca5f..2434963561 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs @@ -88,7 +88,7 @@ private void GivenTheDownStreamRouteFinderReturns(DownstreamRouteHolder downstre { _downstreamRoute = new OkResponse(downstreamRoute); _finder - .Setup(x => x.Get(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(x => x.Get(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(_downstreamRoute); } diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs index 6d266ed97a..af3ede4913 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs @@ -1,771 +1,912 @@ using Moq; -using Ocelot.Configuration; -using Ocelot.Configuration.Builder; -using Ocelot.DownstreamRouteFinder; -using Ocelot.DownstreamRouteFinder.Finder; -using Ocelot.DownstreamRouteFinder.UrlMatcher; -using Ocelot.Responses; +using Ocelot.Configuration; +using Ocelot.Configuration.Builder; +using Ocelot.DownstreamRouteFinder; +using Ocelot.DownstreamRouteFinder.Finder; +using Ocelot.DownstreamRouteFinder.UrlMatcher; +using Ocelot.Responses; using Ocelot.Values; using Shouldly; -using System; +using System; using System.Collections.Generic; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Primitives; using TestStack.BDDfy; using Xunit; - -namespace Ocelot.UnitTests.DownstreamRouteFinder -{ - public class DownstreamRouteFinderTests - { - private readonly IDownstreamRouteProvider _downstreamRouteFinder; - private readonly Mock _mockMatcher; - private readonly Mock _finder; - private string _upstreamUrlPath; - private Response _result; - private List _routesConfig; - private InternalConfiguration _config; - private Response _match; - private string _upstreamHttpMethod; - private string _upstreamHost; - private string _upstreamQuery; - - public DownstreamRouteFinderTests() - { - _mockMatcher = new Mock(); - _finder = new Mock(); - _downstreamRouteFinder = new Ocelot.DownstreamRouteFinder.Finder.DownstreamRouteFinder(_mockMatcher.Object, _finder.Object); - } - - [Fact] - public void should_return_highest_priority_when_first() - { - var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); - - this.Given(x => x.GivenThereIsAnUpstreamUrlPath("someUpstreamPath")) - .And(x => x.GivenTheTemplateVariableAndNameFinderReturns( - new OkResponse>(new List()))) - .And(x => x.GivenTheConfigurationIs(new List - { - new RouteBuilder() - .WithDownstreamRoute(new DownstreamRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamPathTemplate(new UpstreamPathTemplate("test", 1, false, "someUpstreamPath")) - .Build()) - .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamPathTemplate(new UpstreamPathTemplate("test", 1, false, "someUpstreamPath")) - .Build(), - new RouteBuilder() - .WithDownstreamRoute(new DownstreamRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamPathTemplate(new UpstreamPathTemplate("test", 0, false, "someUpstreamPath")) - .Build()) - .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamPathTemplate(new UpstreamPathTemplate("test", 0, false, "someUpstreamPath")) - .Build(), - }, string.Empty, serviceProviderConfig)) - .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) - .And(x => x.GivenTheUpstreamHttpMethodIs("Post")) - .When(x => x.WhenICallTheFinder()) - .Then(x => x.ThenTheFollowingIsReturned(new DownstreamRouteHolder(new List(), - new RouteBuilder() - .WithDownstreamRoute(new DownstreamRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamPathTemplate(new UpstreamPathTemplate("test", 1, false, "someUpstreamPath")) - .WithUpstreamHttpMethod(new List { "Post" }) - .Build()) - .WithUpstreamPathTemplate(new UpstreamPathTemplate("test", 1, false, "someUpstreamPath")) - .WithUpstreamHttpMethod(new List { "Post" }) - .Build() - ))) - .BDDfy(); - } - - [Fact] - public void should_return_highest_priority_when_lowest() - { - var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); - - this.Given(x => x.GivenThereIsAnUpstreamUrlPath("someUpstreamPath")) - .And(x => x.GivenTheTemplateVariableAndNameFinderReturns( - new OkResponse>(new List()))) - .And(x => x.GivenTheConfigurationIs(new List - { - new RouteBuilder() - .WithDownstreamRoute(new DownstreamRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamPathTemplate(new UpstreamPathTemplate("test", 0, false, "someUpstreamPath")) - .Build()) - .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamPathTemplate(new UpstreamPathTemplate("test", 0, false, "someUpstreamPath")) - .Build(), - new RouteBuilder() - .WithDownstreamRoute(new DownstreamRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamPathTemplate(new UpstreamPathTemplate("test", 1, false, "someUpstreamPath")) - .Build()) - .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamPathTemplate(new UpstreamPathTemplate("test", 1, false, "someUpstreamPath")) - .Build(), - }, string.Empty, serviceProviderConfig)) - .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) - .And(x => x.GivenTheUpstreamHttpMethodIs("Post")) - .When(x => x.WhenICallTheFinder()) - .Then(x => x.ThenTheFollowingIsReturned(new DownstreamRouteHolder(new List(), - new RouteBuilder() - .WithDownstreamRoute(new DownstreamRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamPathTemplate(new UpstreamPathTemplate("test", 1, false, "someUpstreamPath")) - .WithUpstreamHttpMethod(new List { "Post" }) - .Build()) - .WithUpstreamPathTemplate(new UpstreamPathTemplate("test", 1, false, "someUpstreamPath")) - .WithUpstreamHttpMethod(new List { "Post" }) - .Build() - ))) - .BDDfy(); - } - - [Fact] - public void should_return_route() - { - var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); - - this.Given(x => x.GivenThereIsAnUpstreamUrlPath("matchInUrlMatcher/")) - .And(x => x.GivenTheTemplateVariableAndNameFinderReturns( - new OkResponse>( - new List()))) - .And(x => x.GivenTheConfigurationIs(new List - { - new RouteBuilder() - .WithDownstreamRoute(new DownstreamRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) - .Build()) - .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) - .Build(), - }, string.Empty, serviceProviderConfig - )) - .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) - .And(x => x.GivenTheUpstreamHttpMethodIs("Get")) - .When(x => x.WhenICallTheFinder()) - .Then( - x => x.ThenTheFollowingIsReturned(new DownstreamRouteHolder( - new List(), - new RouteBuilder() - .WithDownstreamRoute(new DownstreamRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) - .Build()) - .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) - .Build() - ))) - .And(x => x.ThenTheUrlMatcherIsCalledCorrectly()) - .BDDfy(); - } - - [Fact] - public void should_not_append_slash_to_upstream_url_path() - { - var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); - - this.Given(x => x.GivenThereIsAnUpstreamUrlPath("matchInUrlMatcher")) - .And(x => x.GivenTheTemplateVariableAndNameFinderReturns( - new OkResponse>( - new List()))) - .And(x => x.GivenTheConfigurationIs(new List - { - new RouteBuilder() - .WithDownstreamRoute(new DownstreamRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) - .Build()) - .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) - .Build(), - }, string.Empty, serviceProviderConfig - )) - .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) - .And(x => x.GivenTheUpstreamHttpMethodIs("Get")) - .When(x => x.WhenICallTheFinder()) - .Then( - x => x.ThenTheFollowingIsReturned(new DownstreamRouteHolder( - new List(), - new RouteBuilder() - .WithDownstreamRoute(new DownstreamRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) - .Build()) - .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) - .Build() - ))) - .And(x => x.ThenTheUrlMatcherIsCalledCorrectly("matchInUrlMatcher")) - .BDDfy(); - } - - [Fact] - public void should_return_route_if_upstream_path_and_upstream_template_are_the_same() - { - var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); - - this.Given(x => x.GivenThereIsAnUpstreamUrlPath("someUpstreamPath")) - .And( - x => - x.GivenTheTemplateVariableAndNameFinderReturns( - new OkResponse>(new List()))) - .And(x => x.GivenTheConfigurationIs(new List - { - new RouteBuilder() - .WithDownstreamRoute(new DownstreamRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) - .Build()) - .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) - .Build(), - }, string.Empty, serviceProviderConfig - )) - .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) - .And(x => x.GivenTheUpstreamHttpMethodIs("Get")) - .When(x => x.WhenICallTheFinder()) - .Then( - x => x.ThenTheFollowingIsReturned(new DownstreamRouteHolder(new List(), - new RouteBuilder() - .WithDownstreamRoute(new DownstreamRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) - .Build()) - .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) - .Build() - ))) - .BDDfy(); - } - - [Fact] - public void should_return_correct_route_for_http_verb() - { - var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); - - this.Given(x => x.GivenThereIsAnUpstreamUrlPath("someUpstreamPath")) - .And( - x => - x.GivenTheTemplateVariableAndNameFinderReturns( - new OkResponse>(new List()))) - .And(x => x.GivenTheConfigurationIs(new List - { - new RouteBuilder() - .WithDownstreamRoute(new DownstreamRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamPathTemplate(new UpstreamPathTemplate(string.Empty, 1, false, "someUpstreamPath")) - .Build()) - .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamPathTemplate(new UpstreamPathTemplate(string.Empty, 1, false, "someUpstreamPath")) - .Build(), - new RouteBuilder() - .WithDownstreamRoute(new DownstreamRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPathForAPost") - .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamPathTemplate(new UpstreamPathTemplate(string.Empty, 1, false, "someUpstreamPath")) - .Build()) - .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamPathTemplate(new UpstreamPathTemplate(string.Empty, 1, false, "someUpstreamPath")) - .Build(), - }, string.Empty, serviceProviderConfig - )) - .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) - .And(x => x.GivenTheUpstreamHttpMethodIs("Post")) - .When(x => x.WhenICallTheFinder()) - .Then( - x => x.ThenTheFollowingIsReturned(new DownstreamRouteHolder(new List(), - new RouteBuilder() - .WithDownstreamRoute(new DownstreamRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPathForAPost") - .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamPathTemplate(new UpstreamPathTemplate(string.Empty, 1, false, "someUpstreamPath")) - .Build()) - .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamPathTemplate(new UpstreamPathTemplate(string.Empty, 1, false, "someUpstreamPath")) - .Build() - ))) - .BDDfy(); - } - - [Fact] - public void should_not_return_route() - { - var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); - - this.Given(x => x.GivenThereIsAnUpstreamUrlPath("dontMatchPath/")) - .And(x => x.GivenTheConfigurationIs(new List - { - new RouteBuilder() - .WithDownstreamRoute(new DownstreamRouteBuilder() - .WithDownstreamPathTemplate("somPath") - .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamPathTemplate(new UpstreamPathTemplate("somePath", 1, false, "someUpstreamPath")) - .Build()) - .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamPathTemplate(new UpstreamPathTemplate("somePath", 1, false, "someUpstreamPath")) - .Build(), - }, string.Empty, serviceProviderConfig - )) - .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(false)))) - .And(x => x.GivenTheUpstreamHttpMethodIs("Get")) - .When(x => x.WhenICallTheFinder()) - .Then( - x => x.ThenAnErrorResponseIsReturned()) - .And(x => x.ThenTheUrlMatcherIsCalledCorrectly()) - .BDDfy(); - } - - [Fact] - public void should_return_correct_route_for_http_verb_setting_multiple_upstream_http_method() - { - var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); - - this.Given(x => x.GivenThereIsAnUpstreamUrlPath("someUpstreamPath")) - .And( - x => - x.GivenTheTemplateVariableAndNameFinderReturns( - new OkResponse>(new List()))) - .And(x => x.GivenTheConfigurationIs(new List - { - new RouteBuilder() - .WithDownstreamRoute(new DownstreamRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamHttpMethod(new List { "Get", "Post" }) - .WithUpstreamPathTemplate(new UpstreamPathTemplate(string.Empty, 1, false, "someUpstreamPath")) - .Build()) - .WithUpstreamHttpMethod(new List { "Get", "Post" }) - .WithUpstreamPathTemplate(new UpstreamPathTemplate(string.Empty, 1, false, "someUpstreamPath")) - .Build(), - }, string.Empty, serviceProviderConfig - )) - .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) - .And(x => x.GivenTheUpstreamHttpMethodIs("Post")) - .When(x => x.WhenICallTheFinder()) - .Then( - x => x.ThenTheFollowingIsReturned(new DownstreamRouteHolder(new List(), - new RouteBuilder() - .WithDownstreamRoute(new DownstreamRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamPathTemplate(new UpstreamPathTemplate(string.Empty, 1, false, "someUpstreamPath")) - .Build()) - .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamPathTemplate(new UpstreamPathTemplate(string.Empty, 1, false, "someUpstreamPath")) - .Build() - ))) - .BDDfy(); - } - - [Fact] - public void should_return_correct_route_for_http_verb_setting_all_upstream_http_method() - { - var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); - - this.Given(x => x.GivenThereIsAnUpstreamUrlPath("someUpstreamPath")) - .And( - x => - x.GivenTheTemplateVariableAndNameFinderReturns( - new OkResponse>(new List()))) - .And(x => x.GivenTheConfigurationIs(new List - { - new RouteBuilder() - .WithDownstreamRoute(new DownstreamRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamHttpMethod(new List()) - .WithUpstreamPathTemplate(new UpstreamPathTemplate(string.Empty, 1, false, "someUpstreamPath")) - .Build()) - .WithUpstreamHttpMethod(new List()) - .WithUpstreamPathTemplate(new UpstreamPathTemplate(string.Empty, 1, false, "someUpstreamPath")) - .Build(), - }, string.Empty, serviceProviderConfig - )) - .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) - .And(x => x.GivenTheUpstreamHttpMethodIs("Post")) - .When(x => x.WhenICallTheFinder()) - .Then( - x => x.ThenTheFollowingIsReturned(new DownstreamRouteHolder(new List(), - new RouteBuilder() - .WithDownstreamRoute(new DownstreamRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamPathTemplate(new UpstreamPathTemplate(string.Empty, 1, false, "someUpstreamPath")) - .Build()) - .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamPathTemplate(new UpstreamPathTemplate(string.Empty, 1, false, "someUpstreamPath")) - .Build() - ))) - .BDDfy(); - } - - [Fact] - public void should_not_return_route_for_http_verb_not_setting_in_upstream_http_method() - { - var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); - - this.Given(x => x.GivenThereIsAnUpstreamUrlPath("someUpstreamPath")) - .And( - x => - x.GivenTheTemplateVariableAndNameFinderReturns( - new OkResponse>(new List()))) - .And(x => x.GivenTheConfigurationIs(new List - { - new RouteBuilder() - .WithDownstreamRoute(new DownstreamRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamHttpMethod(new List { "Get", "Patch", "Delete" }) - .WithUpstreamPathTemplate(new UpstreamPathTemplate(string.Empty, 1, false, "someUpstreamPath")) - .Build()) - .WithUpstreamHttpMethod(new List { "Get", "Patch", "Delete" }) - .WithUpstreamPathTemplate(new UpstreamPathTemplate(string.Empty, 1, false, "someUpstreamPath")) - .Build(), - }, string.Empty, serviceProviderConfig - )) - .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) - .And(x => x.GivenTheUpstreamHttpMethodIs("Post")) - .When(x => x.WhenICallTheFinder()) - .Then(x => x.ThenAnErrorResponseIsReturned()) - .And(x => x.ThenTheUrlMatcherIsNotCalled()) - .BDDfy(); - } - - [Fact] - public void should_return_route_when_host_matches() - { - var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); - - this.Given(x => x.GivenThereIsAnUpstreamUrlPath("matchInUrlMatcher/")) - .And(x => GivenTheUpstreamHostIs("MATCH")) - .And(x => x.GivenTheTemplateVariableAndNameFinderReturns( - new OkResponse>( - new List()))) - .And(x => x.GivenTheConfigurationIs(new List - { - new RouteBuilder() - .WithDownstreamRoute(new DownstreamRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) - .Build()) - .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) - .WithUpstreamHost("MATCH") - .Build(), - }, string.Empty, serviceProviderConfig - )) - .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) - .And(x => x.GivenTheUpstreamHttpMethodIs("Get")) - .When(x => x.WhenICallTheFinder()) - .Then( - x => x.ThenTheFollowingIsReturned(new DownstreamRouteHolder( - new List(), - new RouteBuilder() - .WithDownstreamRoute(new DownstreamRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) - .Build()) - .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) - .Build() - ))) - .And(x => x.ThenTheUrlMatcherIsCalledCorrectly()) - .BDDfy(); - } - - [Fact] - public void should_return_route_when_upstreamhost_is_null() - { - var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); - - this.Given(x => x.GivenThereIsAnUpstreamUrlPath("matchInUrlMatcher/")) - .And(x => GivenTheUpstreamHostIs("MATCH")) - .And(x => x.GivenTheTemplateVariableAndNameFinderReturns( - new OkResponse>( - new List()))) - .And(x => x.GivenTheConfigurationIs(new List - { - new RouteBuilder() - .WithDownstreamRoute(new DownstreamRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) - .Build()) - .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) - .Build(), - }, string.Empty, serviceProviderConfig - )) - .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) - .And(x => x.GivenTheUpstreamHttpMethodIs("Get")) - .When(x => x.WhenICallTheFinder()) - .Then( - x => x.ThenTheFollowingIsReturned(new DownstreamRouteHolder( - new List(), - new RouteBuilder() - .WithDownstreamRoute(new DownstreamRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) - .Build()) - .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) - .Build() - ))) - .And(x => x.ThenTheUrlMatcherIsCalledCorrectly()) - .BDDfy(); - } - - [Fact] - public void should_not_return_route_when_host_doesnt_match() - { - var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); - - this.Given(x => x.GivenThereIsAnUpstreamUrlPath("matchInUrlMatcher/")) - .And(x => GivenTheUpstreamHostIs("DONTMATCH")) - .And(x => x.GivenTheTemplateVariableAndNameFinderReturns(new OkResponse>(new List()))) - .And(x => x.GivenTheConfigurationIs(new List - { - new RouteBuilder() - .WithDownstreamRoute(new DownstreamRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) - .Build()) - .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) - .WithUpstreamHost("MATCH") - .Build(), - new RouteBuilder() - .WithDownstreamRoute(new DownstreamRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamHttpMethod(new List()) // empty list of methods - .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) - .Build()) - .WithUpstreamHttpMethod(new List()) // empty list of methods - .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) - .WithUpstreamHost("MATCH") - .Build(), - }, string.Empty, serviceProviderConfig - )) - .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) - .And(x => x.GivenTheUpstreamHttpMethodIs("Get")) - .When(x => x.WhenICallTheFinder()) - .Then(x => x.ThenAnErrorResponseIsReturned()) - .And(x => x.ThenTheUrlMatcherIsNotCalled()) - .BDDfy(); - } - - [Fact] - public void should_not_return_route_when_host_doesnt_match_with_empty_upstream_http_method() - { - var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); - - this.Given(x => x.GivenThereIsAnUpstreamUrlPath("matchInUrlMatcher/")) - .And(x => GivenTheUpstreamHostIs("DONTMATCH")) - .And(x => x.GivenTheTemplateVariableAndNameFinderReturns(new OkResponse>(new List()))) - .And(x => x.GivenTheConfigurationIs(new List - { - new RouteBuilder() - .WithDownstreamRoute(new DownstreamRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamHttpMethod(new List()) - .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) - .Build()) - .WithUpstreamHttpMethod(new List()) - .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) - .WithUpstreamHost("MATCH") - .Build(), - }, string.Empty, serviceProviderConfig - )) - .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) - .And(x => x.GivenTheUpstreamHttpMethodIs("Get")) - .When(x => x.WhenICallTheFinder()) - .Then(x => x.ThenAnErrorResponseIsReturned()) - .And(x => x.ThenTheUrlMatcherIsNotCalled()) - .BDDfy(); - } - - [Fact] - public void should_return_route_when_host_does_match_with_empty_upstream_http_method() - { - var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); - - this.Given(x => x.GivenThereIsAnUpstreamUrlPath("matchInUrlMatcher/")) - .And(x => GivenTheUpstreamHostIs("MATCH")) - .And(x => x.GivenTheTemplateVariableAndNameFinderReturns(new OkResponse>(new List()))) - .And(x => x.GivenTheConfigurationIs(new List - { - new RouteBuilder() - .WithDownstreamRoute(new DownstreamRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamHttpMethod(new List()) - .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) - .Build()) - .WithUpstreamHttpMethod(new List()) - .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) - .WithUpstreamHost("MATCH") - .Build(), - }, string.Empty, serviceProviderConfig - )) - .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) - .And(x => x.GivenTheUpstreamHttpMethodIs("Get")) - .When(x => x.WhenICallTheFinder()) - .And(x => x.ThenTheUrlMatcherIsCalledCorrectly(1, 0)) - .BDDfy(); - } - - [Fact] - public void should_return_route_when_host_matches_but_null_host_on_same_path_first() - { - var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); - - this.Given(x => x.GivenThereIsAnUpstreamUrlPath("matchInUrlMatcher/")) - .And(x => GivenTheUpstreamHostIs("MATCH")) - .And(x => x.GivenTheTemplateVariableAndNameFinderReturns( - new OkResponse>( - new List()))) - .And(x => x.GivenTheConfigurationIs(new List - { - new RouteBuilder() - .WithDownstreamRoute(new DownstreamRouteBuilder() - .WithDownstreamPathTemplate("THENULLPATH") - .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) - .Build()) - .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) - .Build(), - new RouteBuilder() - .WithDownstreamRoute(new DownstreamRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) - .Build()) - .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) - .WithUpstreamHost("MATCH") - .Build(), - }, string.Empty, serviceProviderConfig - )) - .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) - .And(x => x.GivenTheUpstreamHttpMethodIs("Get")) - .When(x => x.WhenICallTheFinder()) - .Then( - x => x.ThenTheFollowingIsReturned(new DownstreamRouteHolder( - new List(), - new RouteBuilder() - .WithDownstreamRoute(new DownstreamRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "test")) - .Build()) - .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "test")) - .Build() - ))) - .And(x => x.ThenTheUrlMatcherIsCalledCorrectly(1, 0)) - .And(x => x.ThenTheUrlMatcherIsCalledCorrectly(1, 1)) - .BDDfy(); - } - - private void GivenTheUpstreamHostIs(string upstreamHost) - { - _upstreamHost = upstreamHost; - } - - private void GivenTheTemplateVariableAndNameFinderReturns(Response> response) - { - _finder - .Setup(x => x.Find(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(response); - } - - private void GivenTheUpstreamHttpMethodIs(string upstreamHttpMethod) - { - _upstreamHttpMethod = upstreamHttpMethod; - } - - private void ThenAnErrorResponseIsReturned() - { - _result.IsError.ShouldBeTrue(); - } - - private void ThenTheUrlMatcherIsCalledCorrectly() - { - _mockMatcher - .Verify(x => x.Match(_upstreamUrlPath, _upstreamQuery, _routesConfig[0].UpstreamTemplatePattern), Times.Once); - } - - private void ThenTheUrlMatcherIsCalledCorrectly(int times, int index = 0) - { - _mockMatcher - .Verify(x => x.Match(_upstreamUrlPath, _upstreamQuery, _routesConfig[index].UpstreamTemplatePattern), Times.Exactly(times)); - } - - private void ThenTheUrlMatcherIsCalledCorrectly(string expectedUpstreamUrlPath) - { - _mockMatcher - .Verify(x => x.Match(expectedUpstreamUrlPath, _upstreamQuery, _routesConfig[0].UpstreamTemplatePattern), Times.Once); - } - - private void ThenTheUrlMatcherIsNotCalled() - { - _mockMatcher - .Verify(x => x.Match(_upstreamUrlPath, _upstreamQuery, _routesConfig[0].UpstreamTemplatePattern), Times.Never); - } - - private void GivenTheUrlMatcherReturns(Response match) - { - _match = match; - _mockMatcher - .Setup(x => x.Match(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(_match); - } - - private void GivenTheConfigurationIs(List routesConfig, string adminPath, ServiceProviderConfiguration serviceProviderConfig) - { - _routesConfig = routesConfig; - _config = new InternalConfiguration(_routesConfig, adminPath, serviceProviderConfig, string.Empty, new LoadBalancerOptionsBuilder().Build(), string.Empty, new QoSOptionsBuilder().Build(), new HttpHandlerOptionsBuilder().Build(), new Version("1.1")); - } - - private void GivenThereIsAnUpstreamUrlPath(string upstreamUrlPath) - { + +namespace Ocelot.UnitTests.DownstreamRouteFinder +{ + public class DownstreamRouteFinderTests + { + private readonly IDownstreamRouteProvider _downstreamRouteFinder; + private readonly Mock _mockMatcher; + private readonly Mock _finder; + private string _upstreamUrlPath; + private Response _result; + private List _routesConfig; + private InternalConfiguration _config; + private Response _match; + private string _upstreamHttpMethod; + private string _upstreamHost; + private string _upstreamQuery; + private UpstreamHeaderRoutingOptions _upstreamHeaderRoutingOptions; + private IHeaderDictionary _requestHeaders; + + public DownstreamRouteFinderTests() + { + _mockMatcher = new Mock(); + _finder = new Mock(); + _downstreamRouteFinder = new Ocelot.DownstreamRouteFinder.Finder.DownstreamRouteFinder(_mockMatcher.Object, _finder.Object); + } + + [Fact] + public void should_return_highest_priority_when_first() + { + var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); + + this.Given(x => x.GivenThereIsAnUpstreamUrlPath("someUpstreamPath")) + .And(x => x.GivenTheTemplateVariableAndNameFinderReturns( + new OkResponse>(new List()))) + .And(x => x.GivenTheConfigurationIs(new List + { + new RouteBuilder() + .WithDownstreamRoute(new DownstreamRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamHttpMethod(new List { "Post" }) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("test", 1, false, "someUpstreamPath")) + .Build()) + .WithUpstreamHttpMethod(new List { "Post" }) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("test", 1, false, "someUpstreamPath")) + .Build(), + new RouteBuilder() + .WithDownstreamRoute(new DownstreamRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamHttpMethod(new List { "Post" }) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("test", 0, false, "someUpstreamPath")) + .Build()) + .WithUpstreamHttpMethod(new List { "Post" }) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("test", 0, false, "someUpstreamPath")) + .Build(), + }, string.Empty, serviceProviderConfig)) + .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) + .And(x => x.GivenTheUpstreamHttpMethodIs("Post")) + .When(x => x.WhenICallTheFinder()) + .Then(x => x.ThenTheFollowingIsReturned(new DownstreamRouteHolder(new List(), + new RouteBuilder() + .WithDownstreamRoute(new DownstreamRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamPathTemplate(new UpstreamPathTemplate("test", 1, false, "someUpstreamPath")) + .WithUpstreamHttpMethod(new List { "Post" }) + .Build()) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("test", 1, false, "someUpstreamPath")) + .WithUpstreamHttpMethod(new List { "Post" }) + .Build() + ))) + .BDDfy(); + } + + [Fact] + public void should_return_highest_priority_when_lowest() + { + var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); + + this.Given(x => x.GivenThereIsAnUpstreamUrlPath("someUpstreamPath")) + .And(x => x.GivenTheTemplateVariableAndNameFinderReturns( + new OkResponse>(new List()))) + .And(x => x.GivenTheConfigurationIs(new List + { + new RouteBuilder() + .WithDownstreamRoute(new DownstreamRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamHttpMethod(new List { "Post" }) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("test", 0, false, "someUpstreamPath")) + .Build()) + .WithUpstreamHttpMethod(new List { "Post" }) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("test", 0, false, "someUpstreamPath")) + .Build(), + new RouteBuilder() + .WithDownstreamRoute(new DownstreamRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamHttpMethod(new List { "Post" }) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("test", 1, false, "someUpstreamPath")) + .Build()) + .WithUpstreamHttpMethod(new List { "Post" }) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("test", 1, false, "someUpstreamPath")) + .Build(), + }, string.Empty, serviceProviderConfig)) + .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) + .And(x => x.GivenTheUpstreamHttpMethodIs("Post")) + .When(x => x.WhenICallTheFinder()) + .Then(x => x.ThenTheFollowingIsReturned(new DownstreamRouteHolder(new List(), + new RouteBuilder() + .WithDownstreamRoute(new DownstreamRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamPathTemplate(new UpstreamPathTemplate("test", 1, false, "someUpstreamPath")) + .WithUpstreamHttpMethod(new List { "Post" }) + .Build()) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("test", 1, false, "someUpstreamPath")) + .WithUpstreamHttpMethod(new List { "Post" }) + .Build() + ))) + .BDDfy(); + } + + [Fact] + public void should_return_route() + { + var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); + + this.Given(x => x.GivenThereIsAnUpstreamUrlPath("matchInUrlMatcher/")) + .And(x => x.GivenTheTemplateVariableAndNameFinderReturns( + new OkResponse>( + new List()))) + .And(x => x.GivenTheConfigurationIs(new List + { + new RouteBuilder() + .WithDownstreamRoute(new DownstreamRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .Build()) + .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .Build(), + }, string.Empty, serviceProviderConfig + )) + .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) + .And(x => x.GivenTheUpstreamHttpMethodIs("Get")) + .When(x => x.WhenICallTheFinder()) + .Then( + x => x.ThenTheFollowingIsReturned(new DownstreamRouteHolder( + new List(), + new RouteBuilder() + .WithDownstreamRoute(new DownstreamRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .Build()) + .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .Build() + ))) + .And(x => x.ThenTheUrlMatcherIsCalledCorrectly()) + .BDDfy(); + } + + [Fact] + public void should_not_append_slash_to_upstream_url_path() + { + var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); + + this.Given(x => x.GivenThereIsAnUpstreamUrlPath("matchInUrlMatcher")) + .And(x => x.GivenTheTemplateVariableAndNameFinderReturns( + new OkResponse>( + new List()))) + .And(x => x.GivenTheConfigurationIs(new List + { + new RouteBuilder() + .WithDownstreamRoute(new DownstreamRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .Build()) + .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .Build(), + }, string.Empty, serviceProviderConfig + )) + .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) + .And(x => x.GivenTheUpstreamHttpMethodIs("Get")) + .When(x => x.WhenICallTheFinder()) + .Then( + x => x.ThenTheFollowingIsReturned(new DownstreamRouteHolder( + new List(), + new RouteBuilder() + .WithDownstreamRoute(new DownstreamRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .Build()) + .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .Build() + ))) + .And(x => x.ThenTheUrlMatcherIsCalledCorrectly("matchInUrlMatcher")) + .BDDfy(); + } + + [Fact] + public void should_return_route_if_upstream_path_and_upstream_template_are_the_same() + { + var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); + + this.Given(x => x.GivenThereIsAnUpstreamUrlPath("someUpstreamPath")) + .And( + x => + x.GivenTheTemplateVariableAndNameFinderReturns( + new OkResponse>(new List()))) + .And(x => x.GivenTheConfigurationIs(new List + { + new RouteBuilder() + .WithDownstreamRoute(new DownstreamRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .Build()) + .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .Build(), + }, string.Empty, serviceProviderConfig + )) + .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) + .And(x => x.GivenTheUpstreamHttpMethodIs("Get")) + .When(x => x.WhenICallTheFinder()) + .Then( + x => x.ThenTheFollowingIsReturned(new DownstreamRouteHolder(new List(), + new RouteBuilder() + .WithDownstreamRoute(new DownstreamRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .Build()) + .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .Build() + ))) + .BDDfy(); + } + + [Fact] + public void should_return_correct_route_for_http_verb() + { + var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); + + this.Given(x => x.GivenThereIsAnUpstreamUrlPath("someUpstreamPath")) + .And( + x => + x.GivenTheTemplateVariableAndNameFinderReturns( + new OkResponse>(new List()))) + .And(x => x.GivenTheConfigurationIs(new List + { + new RouteBuilder() + .WithDownstreamRoute(new DownstreamRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamPathTemplate(new UpstreamPathTemplate(string.Empty, 1, false, "someUpstreamPath")) + .Build()) + .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamPathTemplate(new UpstreamPathTemplate(string.Empty, 1, false, "someUpstreamPath")) + .Build(), + new RouteBuilder() + .WithDownstreamRoute(new DownstreamRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPathForAPost") + .WithUpstreamHttpMethod(new List { "Post" }) + .WithUpstreamPathTemplate(new UpstreamPathTemplate(string.Empty, 1, false, "someUpstreamPath")) + .Build()) + .WithUpstreamHttpMethod(new List { "Post" }) + .WithUpstreamPathTemplate(new UpstreamPathTemplate(string.Empty, 1, false, "someUpstreamPath")) + .Build(), + }, string.Empty, serviceProviderConfig + )) + .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) + .And(x => x.GivenTheUpstreamHttpMethodIs("Post")) + .When(x => x.WhenICallTheFinder()) + .Then( + x => x.ThenTheFollowingIsReturned(new DownstreamRouteHolder(new List(), + new RouteBuilder() + .WithDownstreamRoute(new DownstreamRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPathForAPost") + .WithUpstreamHttpMethod(new List { "Post" }) + .WithUpstreamPathTemplate(new UpstreamPathTemplate(string.Empty, 1, false, "someUpstreamPath")) + .Build()) + .WithUpstreamHttpMethod(new List { "Post" }) + .WithUpstreamPathTemplate(new UpstreamPathTemplate(string.Empty, 1, false, "someUpstreamPath")) + .Build() + ))) + .BDDfy(); + } + + [Fact] + public void should_not_return_route() + { + var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); + + this.Given(x => x.GivenThereIsAnUpstreamUrlPath("dontMatchPath/")) + .And(x => x.GivenTheConfigurationIs(new List + { + new RouteBuilder() + .WithDownstreamRoute(new DownstreamRouteBuilder() + .WithDownstreamPathTemplate("somPath") + .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("somePath", 1, false, "someUpstreamPath")) + .Build()) + .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("somePath", 1, false, "someUpstreamPath")) + .Build(), + }, string.Empty, serviceProviderConfig + )) + .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(false)))) + .And(x => x.GivenTheUpstreamHttpMethodIs("Get")) + .When(x => x.WhenICallTheFinder()) + .Then( + x => x.ThenAnErrorResponseIsReturned()) + .And(x => x.ThenTheUrlMatcherIsCalledCorrectly()) + .BDDfy(); + } + + [Fact] + public void should_return_correct_route_for_http_verb_setting_multiple_upstream_http_method() + { + var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); + + this.Given(x => x.GivenThereIsAnUpstreamUrlPath("someUpstreamPath")) + .And( + x => + x.GivenTheTemplateVariableAndNameFinderReturns( + new OkResponse>(new List()))) + .And(x => x.GivenTheConfigurationIs(new List + { + new RouteBuilder() + .WithDownstreamRoute(new DownstreamRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamHttpMethod(new List { "Get", "Post" }) + .WithUpstreamPathTemplate(new UpstreamPathTemplate(string.Empty, 1, false, "someUpstreamPath")) + .Build()) + .WithUpstreamHttpMethod(new List { "Get", "Post" }) + .WithUpstreamPathTemplate(new UpstreamPathTemplate(string.Empty, 1, false, "someUpstreamPath")) + .Build(), + }, string.Empty, serviceProviderConfig + )) + .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) + .And(x => x.GivenTheUpstreamHttpMethodIs("Post")) + .When(x => x.WhenICallTheFinder()) + .Then( + x => x.ThenTheFollowingIsReturned(new DownstreamRouteHolder(new List(), + new RouteBuilder() + .WithDownstreamRoute(new DownstreamRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamHttpMethod(new List { "Post" }) + .WithUpstreamPathTemplate(new UpstreamPathTemplate(string.Empty, 1, false, "someUpstreamPath")) + .Build()) + .WithUpstreamHttpMethod(new List { "Post" }) + .WithUpstreamPathTemplate(new UpstreamPathTemplate(string.Empty, 1, false, "someUpstreamPath")) + .Build() + ))) + .BDDfy(); + } + + [Fact] + public void should_return_correct_route_for_http_verb_setting_all_upstream_http_method() + { + var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); + + this.Given(x => x.GivenThereIsAnUpstreamUrlPath("someUpstreamPath")) + .And( + x => + x.GivenTheTemplateVariableAndNameFinderReturns( + new OkResponse>(new List()))) + .And(x => x.GivenTheConfigurationIs(new List + { + new RouteBuilder() + .WithDownstreamRoute(new DownstreamRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamHttpMethod(new List()) + .WithUpstreamPathTemplate(new UpstreamPathTemplate(string.Empty, 1, false, "someUpstreamPath")) + .Build()) + .WithUpstreamHttpMethod(new List()) + .WithUpstreamPathTemplate(new UpstreamPathTemplate(string.Empty, 1, false, "someUpstreamPath")) + .Build(), + }, string.Empty, serviceProviderConfig + )) + .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) + .And(x => x.GivenTheUpstreamHttpMethodIs("Post")) + .When(x => x.WhenICallTheFinder()) + .Then( + x => x.ThenTheFollowingIsReturned(new DownstreamRouteHolder(new List(), + new RouteBuilder() + .WithDownstreamRoute(new DownstreamRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamHttpMethod(new List { "Post" }) + .WithUpstreamPathTemplate(new UpstreamPathTemplate(string.Empty, 1, false, "someUpstreamPath")) + .Build()) + .WithUpstreamHttpMethod(new List { "Post" }) + .WithUpstreamPathTemplate(new UpstreamPathTemplate(string.Empty, 1, false, "someUpstreamPath")) + .Build() + ))) + .BDDfy(); + } + + [Fact] + public void should_not_return_route_for_http_verb_not_setting_in_upstream_http_method() + { + var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); + + this.Given(x => x.GivenThereIsAnUpstreamUrlPath("someUpstreamPath")) + .And( + x => + x.GivenTheTemplateVariableAndNameFinderReturns( + new OkResponse>(new List()))) + .And(x => x.GivenTheConfigurationIs(new List + { + new RouteBuilder() + .WithDownstreamRoute(new DownstreamRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamHttpMethod(new List { "Get", "Patch", "Delete" }) + .WithUpstreamPathTemplate(new UpstreamPathTemplate(string.Empty, 1, false, "someUpstreamPath")) + .Build()) + .WithUpstreamHttpMethod(new List { "Get", "Patch", "Delete" }) + .WithUpstreamPathTemplate(new UpstreamPathTemplate(string.Empty, 1, false, "someUpstreamPath")) + .Build(), + }, string.Empty, serviceProviderConfig + )) + .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) + .And(x => x.GivenTheUpstreamHttpMethodIs("Post")) + .When(x => x.WhenICallTheFinder()) + .Then(x => x.ThenAnErrorResponseIsReturned()) + .And(x => x.ThenTheUrlMatcherIsNotCalled()) + .BDDfy(); + } + + [Fact] + public void should_return_route_when_host_matches() + { + var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); + + this.Given(x => x.GivenThereIsAnUpstreamUrlPath("matchInUrlMatcher/")) + .And(x => GivenTheUpstreamHostIs("MATCH")) + .And(x => x.GivenTheTemplateVariableAndNameFinderReturns( + new OkResponse>( + new List()))) + .And(x => x.GivenTheConfigurationIs(new List + { + new RouteBuilder() + .WithDownstreamRoute(new DownstreamRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .Build()) + .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .WithUpstreamHost("MATCH") + .Build(), + }, string.Empty, serviceProviderConfig + )) + .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) + .And(x => x.GivenTheUpstreamHttpMethodIs("Get")) + .When(x => x.WhenICallTheFinder()) + .Then( + x => x.ThenTheFollowingIsReturned(new DownstreamRouteHolder( + new List(), + new RouteBuilder() + .WithDownstreamRoute(new DownstreamRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .Build()) + .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .Build() + ))) + .And(x => x.ThenTheUrlMatcherIsCalledCorrectly()) + .BDDfy(); + } + + [Fact] + public void should_return_route_when_upstreamhost_is_null() + { + var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); + + this.Given(x => x.GivenThereIsAnUpstreamUrlPath("matchInUrlMatcher/")) + .And(x => GivenTheUpstreamHostIs("MATCH")) + .And(x => x.GivenTheTemplateVariableAndNameFinderReturns( + new OkResponse>( + new List()))) + .And(x => x.GivenTheConfigurationIs(new List + { + new RouteBuilder() + .WithDownstreamRoute(new DownstreamRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .Build()) + .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .Build(), + }, string.Empty, serviceProviderConfig + )) + .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) + .And(x => x.GivenTheUpstreamHttpMethodIs("Get")) + .When(x => x.WhenICallTheFinder()) + .Then( + x => x.ThenTheFollowingIsReturned(new DownstreamRouteHolder( + new List(), + new RouteBuilder() + .WithDownstreamRoute(new DownstreamRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .Build()) + .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .Build() + ))) + .And(x => x.ThenTheUrlMatcherIsCalledCorrectly()) + .BDDfy(); + } + + [Fact] + public void should_not_return_route_when_host_doesnt_match() + { + var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); + + this.Given(x => x.GivenThereIsAnUpstreamUrlPath("matchInUrlMatcher/")) + .And(x => GivenTheUpstreamHostIs("DONTMATCH")) + .And(x => x.GivenTheTemplateVariableAndNameFinderReturns(new OkResponse>(new List()))) + .And(x => x.GivenTheConfigurationIs(new List + { + new RouteBuilder() + .WithDownstreamRoute(new DownstreamRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .Build()) + .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .WithUpstreamHost("MATCH") + .Build(), + new RouteBuilder() + .WithDownstreamRoute(new DownstreamRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamHttpMethod(new List()) // empty list of methods + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .Build()) + .WithUpstreamHttpMethod(new List()) // empty list of methods + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .WithUpstreamHost("MATCH") + .Build(), + }, string.Empty, serviceProviderConfig + )) + .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) + .And(x => x.GivenTheUpstreamHttpMethodIs("Get")) + .When(x => x.WhenICallTheFinder()) + .Then(x => x.ThenAnErrorResponseIsReturned()) + .And(x => x.ThenTheUrlMatcherIsNotCalled()) + .BDDfy(); + } + + [Fact] + public void should_not_return_route_when_host_doesnt_match_with_empty_upstream_http_method() + { + var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); + + this.Given(x => x.GivenThereIsAnUpstreamUrlPath("matchInUrlMatcher/")) + .And(x => GivenTheUpstreamHostIs("DONTMATCH")) + .And(x => x.GivenTheTemplateVariableAndNameFinderReturns(new OkResponse>(new List()))) + .And(x => x.GivenTheConfigurationIs(new List + { + new RouteBuilder() + .WithDownstreamRoute(new DownstreamRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamHttpMethod(new List()) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .Build()) + .WithUpstreamHttpMethod(new List()) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .WithUpstreamHost("MATCH") + .Build(), + }, string.Empty, serviceProviderConfig + )) + .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) + .And(x => x.GivenTheUpstreamHttpMethodIs("Get")) + .When(x => x.WhenICallTheFinder()) + .Then(x => x.ThenAnErrorResponseIsReturned()) + .And(x => x.ThenTheUrlMatcherIsNotCalled()) + .BDDfy(); + } + + [Fact] + public void should_return_route_when_host_does_match_with_empty_upstream_http_method() + { + var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); + + this.Given(x => x.GivenThereIsAnUpstreamUrlPath("matchInUrlMatcher/")) + .And(x => GivenTheUpstreamHostIs("MATCH")) + .And(x => x.GivenTheTemplateVariableAndNameFinderReturns(new OkResponse>(new List()))) + .And(x => x.GivenTheConfigurationIs(new List + { + new RouteBuilder() + .WithDownstreamRoute(new DownstreamRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamHttpMethod(new List()) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .Build()) + .WithUpstreamHttpMethod(new List()) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .WithUpstreamHost("MATCH") + .Build(), + }, string.Empty, serviceProviderConfig + )) + .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) + .And(x => x.GivenTheUpstreamHttpMethodIs("Get")) + .When(x => x.WhenICallTheFinder()) + .And(x => x.ThenTheUrlMatcherIsCalledCorrectly(1, 0)) + .BDDfy(); + } + + [Fact] + public void should_return_route_when_host_matches_but_null_host_on_same_path_first() + { + var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); + + this.Given(x => x.GivenThereIsAnUpstreamUrlPath("matchInUrlMatcher/")) + .And(x => GivenTheUpstreamHostIs("MATCH")) + .And(x => x.GivenTheTemplateVariableAndNameFinderReturns( + new OkResponse>( + new List()))) + .And(x => x.GivenTheConfigurationIs(new List + { + new RouteBuilder() + .WithDownstreamRoute(new DownstreamRouteBuilder() + .WithDownstreamPathTemplate("THENULLPATH") + .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .Build()) + .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .Build(), + new RouteBuilder() + .WithDownstreamRoute(new DownstreamRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .Build()) + .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .WithUpstreamHost("MATCH") + .Build(), + }, string.Empty, serviceProviderConfig + )) + .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) + .And(x => x.GivenTheUpstreamHttpMethodIs("Get")) + .When(x => x.WhenICallTheFinder()) + .Then( + x => x.ThenTheFollowingIsReturned(new DownstreamRouteHolder( + new List(), + new RouteBuilder() + .WithDownstreamRoute(new DownstreamRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "test")) + .Build()) + .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "test")) + .Build() + ))) + .And(x => x.ThenTheUrlMatcherIsCalledCorrectly(1, 0)) + .And(x => x.ThenTheUrlMatcherIsCalledCorrectly(1, 1)) + .BDDfy(); + } + + [Fact] + public void should_not_return_route_with_upstream_header_routing_options_enabled_and_no_request_headers() + { + var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); + this.Given(x => x.GivenThereIsAnUpstreamUrlPath("matchInUrlMatcher/")) + .And(x => x.GivenTheTemplateVariableAndNameFinderReturns( + new OkResponse>( + new List()))) + .And(x => x.GivenUpstreamHeaderRoutingOptions()) + .And(x => x.GivenTheConfigurationIs(new List + { + new RouteBuilder() + .WithDownstreamRoute(new DownstreamRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .Build()) + .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .WithUpstreamHeaderRoutingOptions(_upstreamHeaderRoutingOptions) + .Build(), + }, string.Empty, serviceProviderConfig + )) + .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) + .And(x => x.GivenTheUpstreamHttpMethodIs("Get")) + .And(x => x.GivenEmptyRequestHeaders()) + .When(x => x.WhenICallTheFinder()) + .Then(x => x.ThenAnErrorResponseIsReturned()) + .BDDfy(); + } + + [Fact] + public void should_not_return_route_with_upstream_header_routing_options_enabled_and_non_matching_request_headers() + { + var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); + this.Given(x => x.GivenThereIsAnUpstreamUrlPath("matchInUrlMatcher/")) + .And(x => x.GivenTheTemplateVariableAndNameFinderReturns( + new OkResponse>( + new List()))) + .And(x => x.GivenUpstreamHeaderRoutingOptions()) + .And(x => x.GivenTheConfigurationIs(new List + { + new RouteBuilder() + .WithDownstreamRoute(new DownstreamRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .Build()) + .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .WithUpstreamHeaderRoutingOptions(_upstreamHeaderRoutingOptions) + .Build(), + }, string.Empty, serviceProviderConfig + )) + .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) + .And(x => x.GivenTheUpstreamHttpMethodIs("Get")) + .And(x => x.GivenNonEmptyNonMatchingRequestHeaders()) + .When(x => x.WhenICallTheFinder()) + .Then(x => x.ThenAnErrorResponseIsReturned()) + .BDDfy(); + } + + [Fact] + public void should_return_route_with_upstream_header_routing_options_enabled_and_matching_request_headers() + { + var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); + + this.Given(x => x.GivenThereIsAnUpstreamUrlPath("matchInUrlMatcher/")) + .And(x => x.GivenTheTemplateVariableAndNameFinderReturns( + new OkResponse>( + new List()))) + .And(x => x.GivenUpstreamHeaderRoutingOptions()) + .And(x => x.GivenTheConfigurationIs(new List + { + new RouteBuilder() + .WithDownstreamRoute(new DownstreamRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .Build()) + .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .WithUpstreamHeaderRoutingOptions(_upstreamHeaderRoutingOptions) + .Build(), + }, string.Empty, serviceProviderConfig + )) + .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) + .And(x => x.GivenTheUpstreamHttpMethodIs("Get")) + .And(x => x.GivenNonEmptyMatchingRequestHeaders()) + .When(x => x.WhenICallTheFinder()) + .Then( + x => x.ThenTheFollowingIsReturned(new DownstreamRouteHolder( + new List(), + new RouteBuilder() + .WithDownstreamRoute(new DownstreamRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .Build()) + .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .Build() + ))) + .And(x => x.ThenTheUrlMatcherIsCalledCorrectly()) + .BDDfy(); + } + + private void GivenTheUpstreamHostIs(string upstreamHost) + { + _upstreamHost = upstreamHost; + } + + private void GivenTheTemplateVariableAndNameFinderReturns(Response> response) + { + _finder + .Setup(x => x.Find(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(response); + } + + private void GivenTheUpstreamHttpMethodIs(string upstreamHttpMethod) + { + _upstreamHttpMethod = upstreamHttpMethod; + } + + private void GivenUpstreamHeaderRoutingOptions() + { + var headers = new Dictionary>() + { + { "header", new HashSet() { "value" }}, + }; + _upstreamHeaderRoutingOptions = new UpstreamHeaderRoutingOptions(headers, UpstreamHeaderRoutingTriggerMode.All); + } + + private void GivenEmptyRequestHeaders() + { + _requestHeaders = new HeaderDictionary(); + } + + private void GivenNonEmptyNonMatchingRequestHeaders() + { + _requestHeaders = new HeaderDictionary() + { + { "header", new StringValues(new []{ "mismatch" }) }, + }; + } + + private void GivenNonEmptyMatchingRequestHeaders() + { + _requestHeaders = new HeaderDictionary() + { + { "header", new StringValues(new []{ "value" }) }, + }; + } + + private void ThenAnErrorResponseIsReturned() + { + _result.IsError.ShouldBeTrue(); + } + + private void ThenTheUrlMatcherIsCalledCorrectly() + { + _mockMatcher + .Verify(x => x.Match(_upstreamUrlPath, _upstreamQuery, _routesConfig[0].UpstreamTemplatePattern), Times.Once); + } + + private void ThenTheUrlMatcherIsCalledCorrectly(int times, int index = 0) + { + _mockMatcher + .Verify(x => x.Match(_upstreamUrlPath, _upstreamQuery, _routesConfig[index].UpstreamTemplatePattern), Times.Exactly(times)); + } + + private void ThenTheUrlMatcherIsCalledCorrectly(string expectedUpstreamUrlPath) + { + _mockMatcher + .Verify(x => x.Match(expectedUpstreamUrlPath, _upstreamQuery, _routesConfig[0].UpstreamTemplatePattern), Times.Once); + } + + private void ThenTheUrlMatcherIsNotCalled() + { + _mockMatcher + .Verify(x => x.Match(_upstreamUrlPath, _upstreamQuery, _routesConfig[0].UpstreamTemplatePattern), Times.Never); + } + + private void GivenTheUrlMatcherReturns(Response match) + { + _match = match; + _mockMatcher + .Setup(x => x.Match(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(_match); + } + + private void GivenTheConfigurationIs(List routesConfig, string adminPath, ServiceProviderConfiguration serviceProviderConfig) + { + _routesConfig = routesConfig; + _config = new InternalConfiguration(_routesConfig, adminPath, serviceProviderConfig, string.Empty, new LoadBalancerOptionsBuilder().Build(), string.Empty, new QoSOptionsBuilder().Build(), new HttpHandlerOptionsBuilder().Build(), new Version("1.1")); + } + + private void GivenThereIsAnUpstreamUrlPath(string upstreamUrlPath) + { _upstreamUrlPath = upstreamUrlPath; - _upstreamQuery = string.Empty; - } - - private void WhenICallTheFinder() - { - _result = _downstreamRouteFinder.Get(_upstreamUrlPath, _upstreamQuery, _upstreamHttpMethod, _config, _upstreamHost); - } - - private void ThenTheFollowingIsReturned(DownstreamRouteHolder expected) - { - _result.Data.Route.DownstreamRoute[0].DownstreamPathTemplate.Value.ShouldBe(expected.Route.DownstreamRoute[0].DownstreamPathTemplate.Value); - _result.Data.Route.UpstreamTemplatePattern.Priority.ShouldBe(expected.Route.UpstreamTemplatePattern.Priority); - - for (var i = 0; i < _result.Data.TemplatePlaceholderNameAndValues.Count; i++) - { - _result.Data.TemplatePlaceholderNameAndValues[i].Name.ShouldBe(expected.TemplatePlaceholderNameAndValues[i].Name); - _result.Data.TemplatePlaceholderNameAndValues[i].Value.ShouldBe(expected.TemplatePlaceholderNameAndValues[i].Value); - } - - _result.IsError.ShouldBeFalse(); - } - } -} + _upstreamQuery = string.Empty; + } + + private void WhenICallTheFinder() + { + _result = _downstreamRouteFinder.Get(_upstreamUrlPath, _upstreamQuery, _upstreamHttpMethod, _config, _upstreamHost, _requestHeaders); + } + + private void ThenTheFollowingIsReturned(DownstreamRouteHolder expected) + { + _result.Data.Route.DownstreamRoute[0].DownstreamPathTemplate.Value.ShouldBe(expected.Route.DownstreamRoute[0].DownstreamPathTemplate.Value); + _result.Data.Route.UpstreamTemplatePattern.Priority.ShouldBe(expected.Route.UpstreamTemplatePattern.Priority); + + for (var i = 0; i < _result.Data.TemplatePlaceholderNameAndValues.Count; i++) + { + _result.Data.TemplatePlaceholderNameAndValues[i].Name.ShouldBe(expected.TemplatePlaceholderNameAndValues[i].Name); + _result.Data.TemplatePlaceholderNameAndValues[i].Value.ShouldBe(expected.TemplatePlaceholderNameAndValues[i].Value); + } + + _result.IsError.ShouldBeFalse(); + } + } +}