From 36a1e28a88d347409ed003eea0b2f85218c198e2 Mon Sep 17 00:00:00 2001 From: jlukawska Date: Tue, 7 Jul 2020 14:38:11 +0200 Subject: [PATCH 01/49] routing based on headers (all specified headers must match) --- .../Builder/DownstreamReRouteBuilder.cs | 313 +++++++++++++++ .../Configuration/Builder/RouteBuilder.cs | 12 +- .../Configuration/Creator/RoutesCreator.cs | 6 +- src/Ocelot/Configuration/DownstreamRoute.cs | 7 +- src/Ocelot/Configuration/File/FileRoute.cs | 23 +- src/Ocelot/Configuration/Route.cs | 17 +- .../Finder/DownstreamRouteCreator.cs | 6 +- .../Finder/DownstreamRouteFinder.cs | 13 +- .../Finder/IDownstreamRouteProvider.cs | 5 +- .../DownstreamRouteFinderMiddleware.cs | 6 +- .../RoutingBasedOnHeadersTests.cs | 367 ++++++++++++++++++ 11 files changed, 744 insertions(+), 31 deletions(-) create mode 100644 src/Ocelot/Configuration/Builder/DownstreamReRouteBuilder.cs create mode 100644 test/Ocelot.AcceptanceTests/RoutingBasedOnHeadersTests.cs diff --git a/src/Ocelot/Configuration/Builder/DownstreamReRouteBuilder.cs b/src/Ocelot/Configuration/Builder/DownstreamReRouteBuilder.cs new file mode 100644 index 000000000..e60adedbc --- /dev/null +++ b/src/Ocelot/Configuration/Builder/DownstreamReRouteBuilder.cs @@ -0,0 +1,313 @@ +using Ocelot.Configuration.Creator; +using Ocelot.Values; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; + +namespace Ocelot.Configuration.Builder +{ + public class DownstreamRouteBuilder + { + private AuthenticationOptions _authenticationOptions; + private string _loadBalancerKey; + private string _downstreamPathTemplate; + private UpstreamPathTemplate _upstreamTemplatePattern; + private List _upstreamHttpMethod; + private bool _isAuthenticated; + private List _claimsToHeaders; + private List _claimToClaims; + private Dictionary _routeClaimRequirement; + private bool _isAuthorised; + private List _claimToQueries; + private List _claimToDownstreamPath; + private string _requestIdHeaderKey; + private bool _isCached; + private CacheOptions _fileCacheOptions; + private string _downstreamScheme; + private LoadBalancerOptions _loadBalancerOptions; + private QoSOptions _qosOptions; + private HttpHandlerOptions _httpHandlerOptions; + private bool _enableRateLimiting; + private RateLimitOptions _rateLimitOptions; + private bool _useServiceDiscovery; + private string _serviceName; + private string _serviceNamespace; + private List _upstreamHeaderFindAndReplace; + private List _downstreamHeaderFindAndReplace; + private readonly List _downstreamAddresses; + private string _key; + private List _delegatingHandlers; + private List _addHeadersToDownstream; + private List _addHeadersToUpstream; + private bool _dangerousAcceptAnyServerCertificateValidator; + private SecurityOptions _securityOptions; + private string _downstreamHttpMethod; + private Version _downstreamHttpVersion; + private Dictionary _upstreamHeaders; + + public DownstreamRouteBuilder() + { + _downstreamAddresses = new List(); + _delegatingHandlers = new List(); + _addHeadersToDownstream = new List(); + _addHeadersToUpstream = new List(); + } + + public DownstreamRouteBuilder WithDownstreamAddresses(List downstreamAddresses) + { + _downstreamAddresses.AddRange(downstreamAddresses); + return this; + } + + public DownstreamRouteBuilder WithDownStreamHttpMethod(string method) + { + _downstreamHttpMethod = method; + return this; + } + + public DownstreamRouteBuilder WithLoadBalancerOptions(LoadBalancerOptions loadBalancerOptions) + { + _loadBalancerOptions = loadBalancerOptions; + return this; + } + + public DownstreamRouteBuilder WithDownstreamScheme(string downstreamScheme) + { + _downstreamScheme = downstreamScheme; + return this; + } + + public DownstreamRouteBuilder WithDownstreamPathTemplate(string input) + { + _downstreamPathTemplate = input; + return this; + } + + public DownstreamRouteBuilder WithUpstreamPathTemplate(UpstreamPathTemplate input) + { + _upstreamTemplatePattern = input; + return this; + } + + public DownstreamRouteBuilder WithUpstreamHttpMethod(List input) + { + _upstreamHttpMethod = (input.Count == 0) ? new List() : input.Select(x => new HttpMethod(x.Trim())).ToList(); + return this; + } + + public DownstreamRouteBuilder WithIsAuthenticated(bool input) + { + _isAuthenticated = input; + return this; + } + + public DownstreamRouteBuilder WithIsAuthorised(bool input) + { + _isAuthorised = input; + return this; + } + + public DownstreamRouteBuilder WithRequestIdKey(string input) + { + _requestIdHeaderKey = input; + return this; + } + + public DownstreamRouteBuilder WithClaimsToHeaders(List input) + { + _claimsToHeaders = input; + return this; + } + + public DownstreamRouteBuilder WithClaimsToClaims(List input) + { + _claimToClaims = input; + return this; + } + + public DownstreamRouteBuilder WithRouteClaimsRequirement(Dictionary input) + { + _routeClaimRequirement = input; + return this; + } + + public DownstreamRouteBuilder WithClaimsToQueries(List input) + { + _claimToQueries = input; + return this; + } + + public DownstreamRouteBuilder WithClaimsToDownstreamPath(List input) + { + _claimToDownstreamPath = input; + return this; + } + + public DownstreamRouteBuilder WithIsCached(bool input) + { + _isCached = input; + return this; + } + + public DownstreamRouteBuilder WithCacheOptions(CacheOptions input) + { + _fileCacheOptions = input; + return this; + } + + public DownstreamRouteBuilder WithQosOptions(QoSOptions input) + { + _qosOptions = input; + return this; + } + + public DownstreamRouteBuilder WithLoadBalancerKey(string loadBalancerKey) + { + _loadBalancerKey = loadBalancerKey; + return this; + } + + public DownstreamRouteBuilder WithAuthenticationOptions(AuthenticationOptions authenticationOptions) + { + _authenticationOptions = authenticationOptions; + return this; + } + + public DownstreamRouteBuilder WithEnableRateLimiting(bool input) + { + _enableRateLimiting = input; + return this; + } + + public DownstreamRouteBuilder WithRateLimitOptions(RateLimitOptions input) + { + _rateLimitOptions = input; + return this; + } + + public DownstreamRouteBuilder WithHttpHandlerOptions(HttpHandlerOptions input) + { + _httpHandlerOptions = input; + return this; + } + + public DownstreamRouteBuilder WithUseServiceDiscovery(bool useServiceDiscovery) + { + _useServiceDiscovery = useServiceDiscovery; + return this; + } + + public DownstreamRouteBuilder WithServiceName(string serviceName) + { + _serviceName = serviceName; + return this; + } + + public DownstreamRouteBuilder WithServiceNamespace(string serviceNamespace) + { + _serviceNamespace = serviceNamespace; + return this; + } + + public DownstreamRouteBuilder WithUpstreamHeaderFindAndReplace(List upstreamHeaderFindAndReplace) + { + _upstreamHeaderFindAndReplace = upstreamHeaderFindAndReplace; + return this; + } + + public DownstreamRouteBuilder WithDownstreamHeaderFindAndReplace(List downstreamHeaderFindAndReplace) + { + _downstreamHeaderFindAndReplace = downstreamHeaderFindAndReplace; + return this; + } + + public DownstreamRouteBuilder WithKey(string key) + { + _key = key; + return this; + } + + public DownstreamRouteBuilder WithDelegatingHandlers(List delegatingHandlers) + { + _delegatingHandlers = delegatingHandlers; + return this; + } + + public DownstreamRouteBuilder WithAddHeadersToDownstream(List addHeadersToDownstream) + { + _addHeadersToDownstream = addHeadersToDownstream; + return this; + } + + public DownstreamRouteBuilder WithAddHeadersToUpstream(List addHeadersToUpstream) + { + _addHeadersToUpstream = addHeadersToUpstream; + return this; + } + + public DownstreamRouteBuilder WithDangerousAcceptAnyServerCertificateValidator(bool dangerousAcceptAnyServerCertificateValidator) + { + _dangerousAcceptAnyServerCertificateValidator = dangerousAcceptAnyServerCertificateValidator; + return this; + } + + public DownstreamRouteBuilder WithSecurityOptions(SecurityOptions securityOptions) + { + _securityOptions = securityOptions; + return this; + } + + public DownstreamRouteBuilder WithDownstreamHttpVersion(Version downstreamHttpVersion) + { + _downstreamHttpVersion = downstreamHttpVersion; + return this; + } + + public DownstreamRouteBuilder WithUpstreamHeaders(Dictionary input) + { + _upstreamHeaders = input; + return this; + } + + public DownstreamRoute Build() + { + return new DownstreamRoute( + _key, + _upstreamTemplatePattern, + _upstreamHeaderFindAndReplace, + _downstreamHeaderFindAndReplace, + _downstreamAddresses, + _serviceName, + _serviceNamespace, + _httpHandlerOptions, + _useServiceDiscovery, + _enableRateLimiting, + _qosOptions, + _downstreamScheme, + _requestIdHeaderKey, + _isCached, + _fileCacheOptions, + _loadBalancerOptions, + _rateLimitOptions, + _routeClaimRequirement, + _claimToQueries, + _claimsToHeaders, + _claimToClaims, + _claimToDownstreamPath, + _isAuthenticated, + _isAuthorised, + _authenticationOptions, + new DownstreamPathTemplate(_downstreamPathTemplate), + _loadBalancerKey, + _delegatingHandlers, + _addHeadersToDownstream, + _addHeadersToUpstream, + _dangerousAcceptAnyServerCertificateValidator, + _securityOptions, + _downstreamHttpMethod, + _downstreamHttpVersion, + _upstreamHeaders); + } + } +} diff --git a/src/Ocelot/Configuration/Builder/RouteBuilder.cs b/src/Ocelot/Configuration/Builder/RouteBuilder.cs index 8e7614a9c..b505e9a24 100644 --- a/src/Ocelot/Configuration/Builder/RouteBuilder.cs +++ b/src/Ocelot/Configuration/Builder/RouteBuilder.cs @@ -10,7 +10,8 @@ public class RouteBuilder private string _upstreamHost; private List _downstreamRoutes; private List _downstreamRoutesConfig; - private string _aggregator; + private string _aggregator; + private Dictionary _upstreamHeaders; public RouteBuilder() { @@ -58,6 +59,12 @@ public RouteBuilder WithAggregator(string aggregator) { _aggregator = aggregator; return this; + } + + public RouteBuilder WithUpstreamHeaders(Dictionary upstreamHeaders) + { + _upstreamHeaders = upstreamHeaders; + return this; } public Route Build() @@ -68,7 +75,8 @@ public Route Build() _upstreamHttpMethod, _upstreamTemplatePattern, _upstreamHost, - _aggregator + _aggregator, + _upstreamHeaders ); } } diff --git a/src/Ocelot/Configuration/Creator/RoutesCreator.cs b/src/Ocelot/Configuration/Creator/RoutesCreator.cs index 8c1f1de63..160ce809c 100644 --- a/src/Ocelot/Configuration/Creator/RoutesCreator.cs +++ b/src/Ocelot/Configuration/Creator/RoutesCreator.cs @@ -142,7 +142,8 @@ private DownstreamRoute SetUpDownstreamRoute(FileRoute fileRoute, FileGlobalConf .WithDangerousAcceptAnyServerCertificateValidator(fileRoute.DangerousAcceptAnyServerCertificateValidator) .WithSecurityOptions(securityOptions) .WithDownstreamHttpVersion(downstreamHttpVersion) - .WithDownStreamHttpMethod(fileRoute.DownstreamHttpMethod) + .WithDownStreamHttpMethod(fileRoute.DownstreamHttpMethod) + .WithUpstreamHeaders(fileRoute.UpstreamHeaders) .Build(); return route; @@ -156,7 +157,8 @@ private Route SetUpRoute(FileRoute fileRoute, DownstreamRoute downstreamRoutes) .WithUpstreamHttpMethod(fileRoute.UpstreamHttpMethod) .WithUpstreamPathTemplate(upstreamTemplatePattern) .WithDownstreamRoute(downstreamRoutes) - .WithUpstreamHost(fileRoute.UpstreamHost) + .WithUpstreamHost(fileRoute.UpstreamHost) + .WithUpstreamHeaders(fileRoute.UpstreamHeaders) .Build(); return route; diff --git a/src/Ocelot/Configuration/DownstreamRoute.cs b/src/Ocelot/Configuration/DownstreamRoute.cs index 585c2554f..e79fa0605 100644 --- a/src/Ocelot/Configuration/DownstreamRoute.cs +++ b/src/Ocelot/Configuration/DownstreamRoute.cs @@ -39,7 +39,8 @@ public DownstreamRoute( bool dangerousAcceptAnyServerCertificateValidator, SecurityOptions securityOptions, string downstreamHttpMethod, - Version downstreamHttpVersion) + Version downstreamHttpVersion, + Dictionary upstreamHeaders) { DangerousAcceptAnyServerCertificateValidator = dangerousAcceptAnyServerCertificateValidator; AddHeadersToDownstream = addHeadersToDownstream; @@ -75,6 +76,7 @@ public DownstreamRoute( SecurityOptions = securityOptions; DownstreamHttpMethod = downstreamHttpMethod; DownstreamHttpVersion = downstreamHttpVersion; + UpstreamHeaders = upstreamHeaders ?? new Dictionary(); } public string Key { get; } @@ -110,6 +112,7 @@ public DownstreamRoute( public bool DangerousAcceptAnyServerCertificateValidator { get; } public SecurityOptions SecurityOptions { get; } public string DownstreamHttpMethod { get; } - public Version DownstreamHttpVersion { get; } + public Version DownstreamHttpVersion { get; } + public Dictionary UpstreamHeaders { get; } } } diff --git a/src/Ocelot/Configuration/File/FileRoute.cs b/src/Ocelot/Configuration/File/FileRoute.cs index 5823113ad..5ac734899 100644 --- a/src/Ocelot/Configuration/File/FileRoute.cs +++ b/src/Ocelot/Configuration/File/FileRoute.cs @@ -20,20 +20,21 @@ public FileRoute() RateLimitOptions = new FileRateLimitRule(); RouteClaimsRequirement = new Dictionary(); SecurityOptions = new FileSecurityOptions(); + UpstreamHeaderTemplates = new Dictionary(); UpstreamHeaderTransform = new Dictionary(); UpstreamHttpMethod = new List(); - } - + } + public FileRoute(FileRoute from) { DeepCopy(from, this); } - public Dictionary AddClaimsToRequest { get; set; } + public Dictionary AddClaimsToRequest { get; set; } public Dictionary AddHeadersToRequest { get; set; } - public Dictionary AddQueriesToRequest { get; set; } + public Dictionary AddQueriesToRequest { get; set; } public FileAuthenticationOptions AuthenticationOptions { get; set; } - public Dictionary ChangeDownstreamPathTemplate { get; set; } + public Dictionary ChangeDownstreamPathTemplate { get; set; } public bool DangerousAcceptAnyServerCertificateValidator { get; set; } public List DelegatingHandlers { get; set; } public Dictionary DownstreamHeaderTransform { get; set; } @@ -42,7 +43,7 @@ public FileRoute(FileRoute from) public string DownstreamHttpVersion { get; set; } public string DownstreamPathTemplate { get; set; } public string DownstreamScheme { get; set; } - public FileCacheOptions FileCacheOptions { get; set; } + public FileCacheOptions FileCacheOptions { get; set; } public FileHttpHandlerOptions HttpHandlerOptions { get; set; } public string Key { get; set; } public FileLoadBalancerOptions LoadBalancerOptions { get; set; } @@ -51,13 +52,14 @@ public FileRoute(FileRoute from) public FileRateLimitRule RateLimitOptions { get; set; } public string RequestIdKey { get; set; } public Dictionary RouteClaimsRequirement { get; set; } - public bool RouteIsCaseSensitive { get; set; } + public bool RouteIsCaseSensitive { get; set; } public FileSecurityOptions SecurityOptions { get; set; } - public string ServiceName { get; set; } - public string ServiceNamespace { get; set; } + public string ServiceName { get; set; } + public string ServiceNamespace { get; set; } public int Timeout { get; set; } + public Dictionary UpstreamHeaderTemplates { get; set; } public Dictionary UpstreamHeaderTransform { get; set; } - public string UpstreamHost { get; set; } + public string UpstreamHost { get; set; } public List UpstreamHttpMethod { get; set; } public string UpstreamPathTemplate { get; set; } @@ -101,6 +103,7 @@ public static void DeepCopy(FileRoute from, FileRoute to) to.ServiceName = from.ServiceName; to.ServiceNamespace = from.ServiceNamespace; to.Timeout = from.Timeout; + to.UpstreamHeaderTemplates = new(from.UpstreamHeaderTemplates); to.UpstreamHeaderTransform = new(from.UpstreamHeaderTransform); to.UpstreamHost = from.UpstreamHost; to.UpstreamHttpMethod = new(from.UpstreamHttpMethod); diff --git a/src/Ocelot/Configuration/Route.cs b/src/Ocelot/Configuration/Route.cs index 8f9c0992f..e3764a7d8 100644 --- a/src/Ocelot/Configuration/Route.cs +++ b/src/Ocelot/Configuration/Route.cs @@ -10,7 +10,8 @@ public Route(List downstreamRoute, List upstreamHttpMethod, UpstreamPathTemplate upstreamTemplatePattern, string upstreamHost, - string aggregator) + string aggregator, + Dictionary upstreamHeaders) { UpstreamHost = upstreamHost; DownstreamRoute = downstreamRoute; @@ -18,13 +19,15 @@ public Route(List downstreamRoute, UpstreamHttpMethod = upstreamHttpMethod; UpstreamTemplatePattern = upstreamTemplatePattern; Aggregator = aggregator; + UpstreamHeaders = upstreamHeaders; } - public UpstreamPathTemplate UpstreamTemplatePattern { get; } - public List UpstreamHttpMethod { get; } - public string UpstreamHost { get; } - public List DownstreamRoute { get; } - public List DownstreamRouteConfig { get; } - public string Aggregator { get; } + public UpstreamPathTemplate UpstreamTemplatePattern { get; set; } + public List UpstreamHttpMethod { get; set; } + public string UpstreamHost { get; set; } + public List DownstreamRoute { get; set; } + public List DownstreamRouteConfig { get; set; } + public string Aggregator { get; set; } + public Dictionary UpstreamHeaders { get; set; } } } diff --git a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteCreator.cs b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteCreator.cs index be4d5e32b..47ad7a65c 100644 --- a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteCreator.cs +++ b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteCreator.cs @@ -18,7 +18,7 @@ public DownstreamRouteCreator(IQoSOptionsCreator qoSOptionsCreator) _cache = new ConcurrentDictionary>(); } - public Response Get(string upstreamUrlPath, string upstreamQueryString, string upstreamHttpMethod, IInternalConfiguration configuration, string upstreamHost) + public Response Get(string upstreamUrlPath, string upstreamQueryString, string upstreamHttpMethod, IInternalConfiguration configuration, string upstreamHost, Dictionary upstreamHeaders) { var serviceName = GetServiceName(upstreamUrlPath); @@ -52,7 +52,8 @@ public Response Get(string upstreamUrlPath, string upstre .WithDownstreamScheme(configuration.DownstreamScheme) .WithLoadBalancerOptions(configuration.LoadBalancerOptions) .WithDownstreamHttpVersion(configuration.DownstreamHttpVersion) - .WithUpstreamPathTemplate(upstreamPathTemplate); + .WithUpstreamPathTemplate(upstreamPathTemplate) + .WithUpstreamHeaders(upstreamHeaders); var rateLimitOptions = configuration.Routes?.SelectMany(x => x.DownstreamRoute) .FirstOrDefault(x => x.ServiceName == serviceName); @@ -70,6 +71,7 @@ public Response Get(string upstreamUrlPath, string upstre .WithDownstreamRoute(downstreamRoute) .WithUpstreamHttpMethod(new List { upstreamHttpMethod }) .WithUpstreamPathTemplate(upstreamPathTemplate) + .WithUpstreamHeaders(upstreamHeaders) .Build(); downstreamRouteHolder = new OkResponse(new DownstreamRouteHolder(new List(), route)); diff --git a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs index 59cf1f7b7..7a8257c5d 100644 --- a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs +++ b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs @@ -15,7 +15,8 @@ public DownstreamRouteFinder(IUrlPathToUrlTemplateMatcher urlMatcher, IPlacehold _placeholderNameAndValueFinder = urlPathPlaceholderNameAndValueFinder; } - public Response Get(string upstreamUrlPath, string upstreamQueryString, string httpMethod, IInternalConfiguration configuration, string upstreamHost) + public Response Get(string upstreamUrlPath, string upstreamQueryString, string httpMethod, + IInternalConfiguration configuration, string upstreamHost, Dictionary upstreamHeaders) { var downstreamRoutes = new List(); @@ -31,7 +32,9 @@ public Response Get(string upstreamUrlPath, string upstre { downstreamRoutes.Add(GetPlaceholderNamesAndValues(upstreamUrlPath, upstreamQueryString, route)); } - } + } + + downstreamRoutes = downstreamRoutes.Where(r => HeadersMatch(r.Route, upstreamHeaders)).ToList(); if (downstreamRoutes.Any()) { @@ -55,6 +58,12 @@ private DownstreamRouteHolder GetPlaceholderNamesAndValues(string path, string q var templatePlaceholderNameAndValues = _placeholderNameAndValueFinder.Find(path, query, route.UpstreamTemplatePattern.OriginalValue); return new DownstreamRouteHolder(templatePlaceholderNameAndValues.Data, route); + } + + private bool HeadersMatch(Route route, Dictionary upstreamHeaders) + { + return !route.UpstreamHeaders.Any( + h => !upstreamHeaders.ContainsKey(h.Key) || upstreamHeaders[h.Key] != route.UpstreamHeaders[h.Key]); } } } diff --git a/src/Ocelot/DownstreamRouteFinder/Finder/IDownstreamRouteProvider.cs b/src/Ocelot/DownstreamRouteFinder/Finder/IDownstreamRouteProvider.cs index ed2a657ef..b692be7c1 100644 --- a/src/Ocelot/DownstreamRouteFinder/Finder/IDownstreamRouteProvider.cs +++ b/src/Ocelot/DownstreamRouteFinder/Finder/IDownstreamRouteProvider.cs @@ -1,10 +1,11 @@ using Ocelot.Configuration; using Ocelot.Responses; - +using System.Collections.Generic; + namespace Ocelot.DownstreamRouteFinder.Finder { public interface IDownstreamRouteProvider { - Response Get(string upstreamUrlPath, string upstreamQueryString, string upstreamHttpMethod, IInternalConfiguration configuration, string upstreamHost); + Response Get(string upstreamUrlPath, string upstreamQueryString, string upstreamHttpMethod, IInternalConfiguration configuration, string upstreamHost, Dictionary upstreamHeaders); } } diff --git a/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs b/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs index 63c21b76c..d4fc9e433 100644 --- a/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs +++ b/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs @@ -32,13 +32,15 @@ public async Task Invoke(HttpContext httpContext) ? hostHeader.Split(':')[0] : hostHeader; - Logger.LogDebug(() => $"Upstream url path is {upstreamUrlPath}"); + var upstreamHeaders = httpContext.Request.Headers.ToDictionary(h => h.Key, h => string.Join(";", h.Value)); + + Logger.LogDebug(() => $"Upstream URL path is '{upstreamUrlPath}'."); var internalConfiguration = httpContext.Items.IInternalConfiguration(); 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.AcceptanceTests/RoutingBasedOnHeadersTests.cs b/test/Ocelot.AcceptanceTests/RoutingBasedOnHeadersTests.cs new file mode 100644 index 000000000..e691e8524 --- /dev/null +++ b/test/Ocelot.AcceptanceTests/RoutingBasedOnHeadersTests.cs @@ -0,0 +1,367 @@ +using Microsoft.AspNetCore.Http; +using Ocelot.Configuration.File; +using Shouldly; +using System; +using System.Collections.Generic; +using System.Net; +using System.Text; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.AcceptanceTests +{ + public class RoutingBasedOnHeadersTests : IDisposable + { + private readonly Steps _steps; + private string _downstreamPath; + private readonly ServiceHandler _serviceHandler; + + public RoutingBasedOnHeadersTests() + { + _serviceHandler = new ServiceHandler(); + _steps = new Steps(); + } + + [Fact] + public void should_match_one_header_value() + { + var port = RandomPortFinder.GetRandomPort(); + var headerName = "country_code"; + var headerValue = "PL"; + + var configuration = new FileConfiguration + { + Routes = new List + { + new FileRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = port, + }, + }, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + UpstreamHeaders = new Dictionary() + { + [headerName] = headerValue, + }, + }, + }, + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .And(x => _steps.GivenIAddAHeader(headerName, headerValue)) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } + + [Fact] + public void should_match_one_header_value_when_more_headers() + { + var port = RandomPortFinder.GetRandomPort(); + var headerName = "country_code"; + var headerValue = "PL"; + + var configuration = new FileConfiguration + { + Routes = new List + { + new FileRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = port, + }, + }, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + UpstreamHeaders = new Dictionary() + { + [headerName] = headerValue, + }, + }, + }, + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .And(x => _steps.GivenIAddAHeader("other", "otherValue")) + .And(x => _steps.GivenIAddAHeader(headerName, headerValue)) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } + + [Fact] + public void should_match_two_header_values_when_more_headers() + { + var port = RandomPortFinder.GetRandomPort(); + var headerName1 = "country_code"; + var headerValue1 = "PL"; + var headerName2 = "region"; + var headerValue2 = "MAZ"; + + var configuration = new FileConfiguration + { + Routes = new List + { + new FileRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = port, + }, + }, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + UpstreamHeaders = new Dictionary() + { + [headerName1] = headerValue1, + [headerName2] = headerValue2, + }, + }, + }, + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .And(x => _steps.GivenIAddAHeader(headerName1, headerValue1)) + .And(x => _steps.GivenIAddAHeader("other", "otherValue")) + .And(x => _steps.GivenIAddAHeader(headerName2, headerValue2)) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } + + [Fact] + public void should_not_match_one_header_value() + { + var port = RandomPortFinder.GetRandomPort(); + var headerName = "country_code"; + var headerValue = "PL"; + var anotherHeaderValue = "UK"; + + var configuration = new FileConfiguration + { + Routes = new List + { + new FileRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = port, + }, + }, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + UpstreamHeaders = new Dictionary() + { + [headerName] = headerValue, + }, + }, + }, + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .And(x => _steps.GivenIAddAHeader(headerName, anotherHeaderValue)) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.NotFound)) + .BDDfy(); + } + + [Fact] + public void should_not_match_one_header_value_when_no_headers() + { + var port = RandomPortFinder.GetRandomPort(); + var headerName = "country_code"; + var headerValue = "PL"; + + var configuration = new FileConfiguration + { + Routes = new List + { + new FileRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = port, + }, + }, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + UpstreamHeaders = new Dictionary() + { + [headerName] = headerValue, + }, + }, + }, + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.NotFound)) + .BDDfy(); + } + + [Fact] + public void should_not_match_two_header_values_when_one_different() + { + var port = RandomPortFinder.GetRandomPort(); + var headerName1 = "country_code"; + var headerValue1 = "PL"; + var headerName2 = "region"; + var headerValue2 = "MAZ"; + + var configuration = new FileConfiguration + { + Routes = new List + { + new FileRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = port, + }, + }, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + UpstreamHeaders = new Dictionary() + { + [headerName1] = headerValue1, + [headerName2] = headerValue2, + }, + }, + }, + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .And(x => _steps.GivenIAddAHeader(headerName1, headerValue1)) + .And(x => _steps.GivenIAddAHeader("other", "otherValue")) + .And(x => _steps.GivenIAddAHeader(headerName2, "anothervalue")) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.NotFound)) + .BDDfy(); + } + + [Fact] + public void should_not_match_two_header_values_when_one_not_existing() + { + var port = RandomPortFinder.GetRandomPort(); + var headerName1 = "country_code"; + var headerValue1 = "PL"; + var headerName2 = "region"; + var headerValue2 = "MAZ"; + + var configuration = new FileConfiguration + { + Routes = new List + { + new FileRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = port, + }, + }, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + UpstreamHeaders = new Dictionary() + { + [headerName1] = headerValue1, + [headerName2] = headerValue2, + }, + }, + }, + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .And(x => _steps.GivenIAddAHeader(headerName1, headerValue1)) + .And(x => _steps.GivenIAddAHeader("other", "otherValue")) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.NotFound)) + .BDDfy(); + } + + private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode, string responseBody) + { + _serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, basePath, async context => + { + _downstreamPath = !string.IsNullOrEmpty(context.Request.PathBase.Value) ? context.Request.PathBase.Value : context.Request.Path.Value; + + if (_downstreamPath != basePath) + { + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync("downstream path didnt match base path"); + } + else + { + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(responseBody); + } + }); + } + + internal void ThenTheDownstreamUrlPathShouldBe(string expectedDownstreamPath) + { + _downstreamPath.ShouldBe(expectedDownstreamPath); + } + + public void Dispose() + { + _serviceHandler.Dispose(); + _steps.Dispose(); + } + } +} From 3630d500135b66276aee803d0d62495b2750061f Mon Sep 17 00:00:00 2001 From: jlukawska Date: Thu, 9 Jul 2020 13:55:24 +0200 Subject: [PATCH 02/49] routing based on headers for aggregated routes --- .../Creator/AggregatesCreator.cs | 3 +- .../Configuration/Creator/RoutesCreator.cs | 3 +- .../Configuration/File/FileAggregateRoute.cs | 10 +- .../RoutingBasedOnHeadersTests.cs | 147 ++++++++++++++++++ 4 files changed, 158 insertions(+), 5 deletions(-) diff --git a/src/Ocelot/Configuration/Creator/AggregatesCreator.cs b/src/Ocelot/Configuration/Creator/AggregatesCreator.cs index 9a5ae2906..8e148cd94 100644 --- a/src/Ocelot/Configuration/Creator/AggregatesCreator.cs +++ b/src/Ocelot/Configuration/Creator/AggregatesCreator.cs @@ -43,7 +43,8 @@ private Route SetUpAggregateRoute(IEnumerable routes, FileAggregateRoute .WithDownstreamRoutes(applicableRoutes) .WithAggregateRouteConfig(aggregateRoute.RouteKeysConfig) .WithUpstreamHost(aggregateRoute.UpstreamHost) - .WithAggregator(aggregateRoute.Aggregator) + .WithAggregator(aggregateRoute.Aggregator) + .WithUpstreamHeaders(aggregateRoute.UpstreamHeaders) .Build(); return route; diff --git a/src/Ocelot/Configuration/Creator/RoutesCreator.cs b/src/Ocelot/Configuration/Creator/RoutesCreator.cs index 160ce809c..5193d3625 100644 --- a/src/Ocelot/Configuration/Creator/RoutesCreator.cs +++ b/src/Ocelot/Configuration/Creator/RoutesCreator.cs @@ -142,8 +142,7 @@ private DownstreamRoute SetUpDownstreamRoute(FileRoute fileRoute, FileGlobalConf .WithDangerousAcceptAnyServerCertificateValidator(fileRoute.DangerousAcceptAnyServerCertificateValidator) .WithSecurityOptions(securityOptions) .WithDownstreamHttpVersion(downstreamHttpVersion) - .WithDownStreamHttpMethod(fileRoute.DownstreamHttpMethod) - .WithUpstreamHeaders(fileRoute.UpstreamHeaders) + .WithDownStreamHttpMethod(fileRoute.DownstreamHttpMethod) .Build(); return route; diff --git a/src/Ocelot/Configuration/File/FileAggregateRoute.cs b/src/Ocelot/Configuration/File/FileAggregateRoute.cs index ad47d735f..00f04c03e 100644 --- a/src/Ocelot/Configuration/File/FileAggregateRoute.cs +++ b/src/Ocelot/Configuration/File/FileAggregateRoute.cs @@ -7,11 +7,17 @@ public class FileAggregateRoute : IRoute public string UpstreamPathTemplate { get; set; } public string UpstreamHost { get; set; } public bool RouteIsCaseSensitive { get; set; } - public string Aggregator { get; set; } + public string Aggregator { get; set; } + public Dictionary UpstreamHeaders { get; set; } // Only supports GET..are you crazy!! POST, PUT WOULD BE CRAZY!! :) public List UpstreamHttpMethod => new() { "Get" }; - public int Priority { get; set; } = 1; + public int Priority { get; set; } = 1; + + public FileAggregateRoute() + { + UpstreamHeaders = new Dictionary(); + } } } diff --git a/test/Ocelot.AcceptanceTests/RoutingBasedOnHeadersTests.cs b/test/Ocelot.AcceptanceTests/RoutingBasedOnHeadersTests.cs index e691e8524..4c57c18fe 100644 --- a/test/Ocelot.AcceptanceTests/RoutingBasedOnHeadersTests.cs +++ b/test/Ocelot.AcceptanceTests/RoutingBasedOnHeadersTests.cs @@ -334,6 +334,153 @@ public void should_not_match_two_header_values_when_one_not_existing() .BDDfy(); } + [Fact] + public void should_aggregated_route_match_header_value() + { + var port1 = RandomPortFinder.GetRandomPort(); + var port2 = RandomPortFinder.GetRandomPort(); + var headerName = "country_code"; + var headerValue = "PL"; + + var configuration = new FileConfiguration + { + Routes = new List + { + new FileRoute + { + DownstreamPathTemplate = "/a", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = port1, + }, + }, + UpstreamPathTemplate = "/a", + UpstreamHttpMethod = new List { "Get" }, + Key = "Laura", + }, + new FileRoute + { + DownstreamPathTemplate = "/b", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = port2, + }, + }, + UpstreamPathTemplate = "/b", + UpstreamHttpMethod = new List { "Get" }, + Key = "Tom", + }, + }, + Aggregates = new List() + { + new FileAggregateRoute + { + UpstreamPathTemplate = "/", + UpstreamHost = "localhost", + RouteKeys = new List + { + "Laura", + "Tom", + }, + UpstreamHeaders = new Dictionary() + { + [headerName] = headerValue, + }, + }, + }, + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port1}", "/a", 200, "Hello from Laura")) + .And(x => GivenThereIsAServiceRunningOn($"http://localhost:{port2}", "/b", 200, "Hello from Tom")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .And(x => _steps.GivenIAddAHeader(headerName, headerValue)) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .BDDfy(); + } + + [Fact] + public void should_aggregated_route_not_match_header_value() + { + var port1 = RandomPortFinder.GetRandomPort(); + var port2 = RandomPortFinder.GetRandomPort(); + var headerName = "country_code"; + var headerValue = "PL"; + + var configuration = new FileConfiguration + { + Routes = new List + { + new FileRoute + { + DownstreamPathTemplate = "/a", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = port1, + }, + }, + UpstreamPathTemplate = "/a", + UpstreamHttpMethod = new List { "Get" }, + Key = "Laura", + }, + new FileRoute + { + DownstreamPathTemplate = "/b", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = port2, + }, + }, + UpstreamPathTemplate = "/b", + UpstreamHttpMethod = new List { "Get" }, + Key = "Tom", + }, + }, + Aggregates = new List() + { + new FileAggregateRoute + { + UpstreamPathTemplate = "/", + UpstreamHost = "localhost", + RouteKeys = new List + { + "Laura", + "Tom", + }, + UpstreamHeaders = new Dictionary() + { + [headerName] = headerValue, + }, + }, + }, + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port1}", "/a", 200, "Hello from Laura")) + .And(x => GivenThereIsAServiceRunningOn($"http://localhost:{port2}", "/b", 200, "Hello from Tom")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.NotFound)) + .BDDfy(); + } + private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode, string responseBody) { _serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, basePath, async context => From c392809418fed3e405eee1ce512d308ee876ab9d Mon Sep 17 00:00:00 2001 From: jlukawska Date: Fri, 10 Jul 2020 13:18:46 +0200 Subject: [PATCH 03/49] unit tests and small modifications --- .../Finder/DownstreamRouteFinder.cs | 5 +- .../DownstreamRouteCreatorTests.cs | 10 +- .../DownstreamRouteFinderMiddlewareTests.cs | 2 +- .../DownstreamRouteFinderTests.cs | 144 +++++++++++++++++- 4 files changed, 152 insertions(+), 9 deletions(-) diff --git a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs index 7a8257c5d..5e3b5d724 100644 --- a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs +++ b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs @@ -62,8 +62,9 @@ private DownstreamRouteHolder GetPlaceholderNamesAndValues(string path, string q private bool HeadersMatch(Route route, Dictionary upstreamHeaders) { - return !route.UpstreamHeaders.Any( - h => !upstreamHeaders.ContainsKey(h.Key) || upstreamHeaders[h.Key] != route.UpstreamHeaders[h.Key]); + return route.UpstreamHeaders == null || + upstreamHeaders != null && route.UpstreamHeaders.All( + h => upstreamHeaders.ContainsKey(h.Key) && upstreamHeaders[h.Key] == route.UpstreamHeaders[h.Key]); } } } diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteCreatorTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteCreatorTests.cs index 86ba4078a..f4cb323f4 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteCreatorTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteCreatorTests.cs @@ -16,7 +16,8 @@ public class DownstreamRouteCreatorTests : UnitTest private Response _result; private string _upstreamHost; private string _upstreamUrlPath; - private string _upstreamHttpMethod; + private string _upstreamHttpMethod; + private Dictionary _upstreamHeaders; private IInternalConfiguration _configuration; private readonly Mock _qosOptionsCreator; private Response _resultTwo; @@ -259,7 +260,8 @@ private void GivenTheConfiguration(IInternalConfiguration config) { _upstreamHost = "doesnt matter"; _upstreamUrlPath = "/auth/test"; - _upstreamHttpMethod = "GET"; + _upstreamHttpMethod = "GET"; + _upstreamHeaders = new Dictionary(); _configuration = config; } @@ -278,12 +280,12 @@ private void ThenTheHandlerOptionsAreSet() private void WhenICreate() { - _result = _creator.Get(_upstreamUrlPath, _upstreamQuery, _upstreamHttpMethod, _configuration, _upstreamHost); + _result = _creator.Get(_upstreamUrlPath, _upstreamQuery, _upstreamHttpMethod, _configuration, _upstreamHost, _upstreamHeaders); } private void WhenICreateAgain() { - _resultTwo = _creator.Get(_upstreamUrlPath, _upstreamQuery, _upstreamHttpMethod, _configuration, _upstreamHost); + _resultTwo = _creator.Get(_upstreamUrlPath, _upstreamQuery, _upstreamHttpMethod, _configuration, _upstreamHost, _upstreamHeaders); } private void ThenTheDownstreamRoutesAreTheSameReference() diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs index 6d331a36e..784135228 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs @@ -74,7 +74,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 e9b2bf8ed..30bc16bb0 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs @@ -19,7 +19,8 @@ public class DownstreamRouteFinderTests : UnitTest private InternalConfiguration _config; private Response _match; private string _upstreamHttpMethod; - private string _upstreamHost; + private string _upstreamHost; + private Dictionary _upstreamHeaders; private string _upstreamQuery; public DownstreamRouteFinderTests() @@ -675,6 +676,140 @@ public void should_return_route_when_host_matches_but_null_host_on_same_path_fir .And(x => x.ThenTheUrlMatcherIsCalledCorrectly(1, 0)) .And(x => x.ThenTheUrlMatcherIsCalledCorrectly(1, 1)) .BDDfy(); + } + + [Fact] + public void should_return_route_when_upstream_headers_match() + { + var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); + + var upstreamHeaders = new Dictionary() + { + ["header1"] = "headerValue1", + ["header2"] = "headerValue2", + }; + + this.Given(x => x.GivenThereIsAnUpstreamUrlPath("matchInUrlMatcher/")) + .And(x => GivenTheUpstreamHeadersIs(upstreamHeaders)) + .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")) + .WithUpstreamHeaders(upstreamHeaders) + .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_upstream_headers_dont_match() + { + var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); + + var upstreamHeadersConfig1 = new Dictionary() + { + ["header1"] = "headerValue1", + ["header2"] = "headerValue2", + }; + var upstreamHeadersConfig2 = new Dictionary() + { + ["header1"] = "headerValueAnother", + }; + + this.Given(x => x.GivenThereIsAnUpstreamUrlPath("matchInUrlMatcher/")) + .And(x => GivenTheUpstreamHeadersIs(new Dictionary() { { "header1", "headerValue1" } })) + .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")) + .WithUpstreamHeaders(upstreamHeadersConfig1) + .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")) + .WithUpstreamHeaders(upstreamHeadersConfig2) + .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()) + .BDDfy(); + } + + [Fact] + public void should_not_return_route_when_upstream_headers_null() + { + var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); + + var upstreamHeadersConfig1 = new Dictionary() + { + ["header1"] = "headerValue1", + ["header2"] = "headerValue2", + }; + + this.Given(x => x.GivenThereIsAnUpstreamUrlPath("matchInUrlMatcher/")) + .And(x => GivenTheUpstreamHeadersIs(null)) + .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")) + .WithUpstreamHeaders(upstreamHeadersConfig1) + .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()) + .BDDfy(); } private void GivenTheUpstreamHostIs(string upstreamHost) @@ -692,6 +827,11 @@ private void GivenTheTemplateVariableAndNameFinderReturns(Response upstreamHeaders) + { + _upstreamHeaders = upstreamHeaders; } private void ThenAnErrorResponseIsReturned() @@ -745,7 +885,7 @@ private void GivenThereIsAnUpstreamUrlPath(string upstreamUrlPath) private void WhenICallTheFinder() { - _result = _downstreamRouteFinder.Get(_upstreamUrlPath, _upstreamQuery, _upstreamHttpMethod, _config, _upstreamHost); + _result = _downstreamRouteFinder.Get(_upstreamUrlPath, _upstreamQuery, _upstreamHttpMethod, _config, _upstreamHost, _upstreamHeaders); } private void ThenTheFollowingIsReturned(DownstreamRouteHolder expected) From 1b74d53a1205693209e0eabbb193748100a5af37 Mon Sep 17 00:00:00 2001 From: jlukawska Date: Wed, 15 Jul 2020 11:44:39 +0200 Subject: [PATCH 04/49] find placeholders in header templates --- .../Builder/DownstreamReRouteBuilder.cs | 4 +- .../Configuration/Builder/RouteBuilder.cs | 4 +- .../Creator/AggregatesCreator.cs | 16 +++-- .../IUpstreamHeaderTemplatePatternCreator.cs | 11 ++++ .../Configuration/Creator/RoutesCreator.cs | 7 ++- .../UpstreamHeaderTemplatePatternCreator.cs | 53 ++++++++++++++++ src/Ocelot/Configuration/DownstreamRoute.cs | 8 +-- .../Configuration/File/FileAggregateRoute.cs | 4 +- src/Ocelot/Configuration/File/IRoute.cs | 14 +++-- src/Ocelot/Configuration/Route.cs | 18 +++--- .../Finder/DownstreamRouteCreator.cs | 8 ++- .../Finder/DownstreamRouteFinder.cs | 18 +++--- .../HeaderPlaceholderNameAndValueFinder.cs | 16 +++++ .../IHeaderPlaceholderNameAndValueFinder.cs | 14 +++++ src/Ocelot/Values/UpstreamHeaderTemplate.cs | 25 ++++++++ .../RoutingBasedOnHeadersTests.cs | 62 ++++++++++++++++--- ...streamHeaderTemplatePatternCreatorTests.cs | 56 +++++++++++++++++ 17 files changed, 286 insertions(+), 52 deletions(-) create mode 100644 src/Ocelot/Configuration/Creator/IUpstreamHeaderTemplatePatternCreator.cs create mode 100644 src/Ocelot/Configuration/Creator/UpstreamHeaderTemplatePatternCreator.cs create mode 100644 src/Ocelot/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinder.cs create mode 100644 src/Ocelot/DownstreamRouteFinder/HeaderMatcher/IHeaderPlaceholderNameAndValueFinder.cs create mode 100644 src/Ocelot/Values/UpstreamHeaderTemplate.cs create mode 100644 test/Ocelot.UnitTests/Configuration/UpstreamHeaderTemplatePatternCreatorTests.cs diff --git a/src/Ocelot/Configuration/Builder/DownstreamReRouteBuilder.cs b/src/Ocelot/Configuration/Builder/DownstreamReRouteBuilder.cs index e60adedbc..634fd7c9f 100644 --- a/src/Ocelot/Configuration/Builder/DownstreamReRouteBuilder.cs +++ b/src/Ocelot/Configuration/Builder/DownstreamReRouteBuilder.cs @@ -44,7 +44,7 @@ public class DownstreamRouteBuilder private SecurityOptions _securityOptions; private string _downstreamHttpMethod; private Version _downstreamHttpVersion; - private Dictionary _upstreamHeaders; + private Dictionary _upstreamHeaders; public DownstreamRouteBuilder() { @@ -264,7 +264,7 @@ public DownstreamRouteBuilder WithDownstreamHttpVersion(Version downstreamHttpVe return this; } - public DownstreamRouteBuilder WithUpstreamHeaders(Dictionary input) + public DownstreamRouteBuilder WithUpstreamHeaders(Dictionary input) { _upstreamHeaders = input; return this; diff --git a/src/Ocelot/Configuration/Builder/RouteBuilder.cs b/src/Ocelot/Configuration/Builder/RouteBuilder.cs index b505e9a24..00dab1773 100644 --- a/src/Ocelot/Configuration/Builder/RouteBuilder.cs +++ b/src/Ocelot/Configuration/Builder/RouteBuilder.cs @@ -11,7 +11,7 @@ public class RouteBuilder private List _downstreamRoutes; private List _downstreamRoutesConfig; private string _aggregator; - private Dictionary _upstreamHeaders; + private Dictionary _upstreamHeaders; public RouteBuilder() { @@ -61,7 +61,7 @@ public RouteBuilder WithAggregator(string aggregator) return this; } - public RouteBuilder WithUpstreamHeaders(Dictionary upstreamHeaders) + public RouteBuilder WithUpstreamHeaders(Dictionary upstreamHeaders) { _upstreamHeaders = upstreamHeaders; return this; diff --git a/src/Ocelot/Configuration/Creator/AggregatesCreator.cs b/src/Ocelot/Configuration/Creator/AggregatesCreator.cs index 8e148cd94..30e0ea92f 100644 --- a/src/Ocelot/Configuration/Creator/AggregatesCreator.cs +++ b/src/Ocelot/Configuration/Creator/AggregatesCreator.cs @@ -1,5 +1,6 @@ using Ocelot.Configuration.Builder; using Ocelot.Configuration.File; +using Ocelot.Values; namespace Ocelot.Configuration.Creator { @@ -26,17 +27,20 @@ private Route SetUpAggregateRoute(IEnumerable routes, FileAggregateRoute var allRoutes = routes.SelectMany(x => x.DownstreamRoute); var downstreamRoutes = aggregateRoute.RouteKeys.Select(routeKey => allRoutes.FirstOrDefault(q => q.Key == routeKey)); foreach (var downstreamRoute in downstreamRoutes) - { + { if (downstreamRoute == null) - { - return null; - } - + { + return null; + } + applicableRoutes.Add(downstreamRoute); } var upstreamTemplatePattern = _creator.Create(aggregateRoute); + // TODO + var upstreamHeaderTemplates = new Dictionary(); + var route = new RouteBuilder() .WithUpstreamHttpMethod(aggregateRoute.UpstreamHttpMethod) .WithUpstreamPathTemplate(upstreamTemplatePattern) @@ -44,7 +48,7 @@ private Route SetUpAggregateRoute(IEnumerable routes, FileAggregateRoute .WithAggregateRouteConfig(aggregateRoute.RouteKeysConfig) .WithUpstreamHost(aggregateRoute.UpstreamHost) .WithAggregator(aggregateRoute.Aggregator) - .WithUpstreamHeaders(aggregateRoute.UpstreamHeaders) + .WithUpstreamHeaders(upstreamHeaderTemplates) .Build(); return route; diff --git a/src/Ocelot/Configuration/Creator/IUpstreamHeaderTemplatePatternCreator.cs b/src/Ocelot/Configuration/Creator/IUpstreamHeaderTemplatePatternCreator.cs new file mode 100644 index 000000000..ed2b34410 --- /dev/null +++ b/src/Ocelot/Configuration/Creator/IUpstreamHeaderTemplatePatternCreator.cs @@ -0,0 +1,11 @@ +using Ocelot.Configuration.File; +using Ocelot.Values; +using System.Collections.Generic; + +namespace Ocelot.Configuration.Creator +{ + public interface IUpstreamHeaderTemplatePatternCreator + { + Dictionary Create(IRoute route); + } +} diff --git a/src/Ocelot/Configuration/Creator/RoutesCreator.cs b/src/Ocelot/Configuration/Creator/RoutesCreator.cs index 5193d3625..45a27bfda 100644 --- a/src/Ocelot/Configuration/Creator/RoutesCreator.cs +++ b/src/Ocelot/Configuration/Creator/RoutesCreator.cs @@ -2,6 +2,8 @@ using Ocelot.Configuration.Builder; using Ocelot.Configuration.File; +using Ocelot.Values; + namespace Ocelot.Configuration.Creator { public class RoutesCreator : IRoutesCreator @@ -152,12 +154,15 @@ private Route SetUpRoute(FileRoute fileRoute, DownstreamRoute downstreamRoutes) { var upstreamTemplatePattern = _upstreamTemplatePatternCreator.Create(fileRoute); + //TODO + var upstreamHeaderTemplates = new Dictionary(); + var route = new RouteBuilder() .WithUpstreamHttpMethod(fileRoute.UpstreamHttpMethod) .WithUpstreamPathTemplate(upstreamTemplatePattern) .WithDownstreamRoute(downstreamRoutes) .WithUpstreamHost(fileRoute.UpstreamHost) - .WithUpstreamHeaders(fileRoute.UpstreamHeaders) + .WithUpstreamHeaders(upstreamHeaderTemplates) .Build(); return route; diff --git a/src/Ocelot/Configuration/Creator/UpstreamHeaderTemplatePatternCreator.cs b/src/Ocelot/Configuration/Creator/UpstreamHeaderTemplatePatternCreator.cs new file mode 100644 index 000000000..865263e76 --- /dev/null +++ b/src/Ocelot/Configuration/Creator/UpstreamHeaderTemplatePatternCreator.cs @@ -0,0 +1,53 @@ +using Ocelot.Configuration.File; +using Ocelot.Values; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; + +namespace Ocelot.Configuration.Creator +{ + public class UpstreamHeaderTemplatePatternCreator : IUpstreamHeaderTemplatePatternCreator + { + private const string RegExMatchOneOrMoreOfEverything = ".+"; + private const string RegExMatchOneOrMoreOfEverythingUntilNextForwardSlash = "[^/]+"; + private const string RegExMatchEndString = "$"; + private const string RegExIgnoreCase = "(?i)"; + private const string RegExForwardSlashOnly = "^/$"; + private const string RegExForwardSlashAndOnePlaceHolder = "^/.*"; + private const string RegExPlaceholders = @"\{(.*?)\}"; + + public Dictionary Create(IRoute route) + { + var resultHeaderTemplates = new Dictionary(); + + foreach (var headerTemplate in route.UpstreamHeaderTemplates) + { + var headerTemplateValue = headerTemplate.Value; + + var placeholders = new List(); + + Regex expression = new Regex(RegExPlaceholders); + MatchCollection matches = expression.Matches(headerTemplateValue); + if (matches.Count > 0) + { + placeholders.AddRange(matches.Select(m => m.Groups[1].Value)); + } + + for (int i = 0; i < placeholders.Count; i++) + { + var indexOfPlaceholder = headerTemplateValue.IndexOf(placeholders[i]); + + headerTemplateValue = headerTemplateValue.Replace(placeholders[i], RegExMatchOneOrMoreOfEverything); + } + + var template = route.RouteIsCaseSensitive + ? $"^{headerTemplateValue}{RegExMatchEndString}" + : $"^{RegExIgnoreCase}{headerTemplateValue}{RegExMatchEndString}"; + + resultHeaderTemplates.Add(headerTemplate.Key, new UpstreamHeaderTemplate(template, headerTemplate.Value)); + } + + return resultHeaderTemplates; + } + } +} diff --git a/src/Ocelot/Configuration/DownstreamRoute.cs b/src/Ocelot/Configuration/DownstreamRoute.cs index e79fa0605..d3955aafe 100644 --- a/src/Ocelot/Configuration/DownstreamRoute.cs +++ b/src/Ocelot/Configuration/DownstreamRoute.cs @@ -40,7 +40,7 @@ public DownstreamRoute( SecurityOptions securityOptions, string downstreamHttpMethod, Version downstreamHttpVersion, - Dictionary upstreamHeaders) + Dictionary upstreamHeaders) { DangerousAcceptAnyServerCertificateValidator = dangerousAcceptAnyServerCertificateValidator; AddHeadersToDownstream = addHeadersToDownstream; @@ -75,8 +75,8 @@ public DownstreamRoute( AddHeadersToUpstream = addHeadersToUpstream; SecurityOptions = securityOptions; DownstreamHttpMethod = downstreamHttpMethod; - DownstreamHttpVersion = downstreamHttpVersion; - UpstreamHeaders = upstreamHeaders ?? new Dictionary(); + DownstreamHttpVersion = downstreamHttpVersion; + UpstreamHeaders = upstreamHeaders ?? new Dictionary(); } public string Key { get; } @@ -113,6 +113,6 @@ public DownstreamRoute( public SecurityOptions SecurityOptions { get; } public string DownstreamHttpMethod { get; } public Version DownstreamHttpVersion { get; } - public Dictionary UpstreamHeaders { get; } + public Dictionary UpstreamHeaders { get; } } } diff --git a/src/Ocelot/Configuration/File/FileAggregateRoute.cs b/src/Ocelot/Configuration/File/FileAggregateRoute.cs index 00f04c03e..28f2a1263 100644 --- a/src/Ocelot/Configuration/File/FileAggregateRoute.cs +++ b/src/Ocelot/Configuration/File/FileAggregateRoute.cs @@ -8,7 +8,7 @@ public class FileAggregateRoute : IRoute public string UpstreamHost { get; set; } public bool RouteIsCaseSensitive { get; set; } public string Aggregator { get; set; } - public Dictionary UpstreamHeaders { get; set; } + public Dictionary UpstreamHeaderTemplates { get; set; } // Only supports GET..are you crazy!! POST, PUT WOULD BE CRAZY!! :) public List UpstreamHttpMethod => new() { "Get" }; @@ -17,7 +17,7 @@ public class FileAggregateRoute : IRoute public FileAggregateRoute() { - UpstreamHeaders = new Dictionary(); + UpstreamHeaderTemplates = new Dictionary(); } } } diff --git a/src/Ocelot/Configuration/File/IRoute.cs b/src/Ocelot/Configuration/File/IRoute.cs index 74df79b23..5558cd778 100644 --- a/src/Ocelot/Configuration/File/IRoute.cs +++ b/src/Ocelot/Configuration/File/IRoute.cs @@ -1,8 +1,12 @@ -namespace Ocelot.Configuration.File; +using System.Collections.Generic; -public interface IRoute +namespace Ocelot.Configuration.File { - string UpstreamPathTemplate { get; set; } - bool RouteIsCaseSensitive { get; set; } - int Priority { get; set; } + public interface IRoute + { + string UpstreamPathTemplate { get; set; } + bool RouteIsCaseSensitive { get; set; } + int Priority { get; set; } + Dictionary UpstreamHeaderTemplates { get; set; } + } } diff --git a/src/Ocelot/Configuration/Route.cs b/src/Ocelot/Configuration/Route.cs index e3764a7d8..be7e9b8fd 100644 --- a/src/Ocelot/Configuration/Route.cs +++ b/src/Ocelot/Configuration/Route.cs @@ -11,7 +11,7 @@ public Route(List downstreamRoute, UpstreamPathTemplate upstreamTemplatePattern, string upstreamHost, string aggregator, - Dictionary upstreamHeaders) + Dictionary upstreamHeaderTemplates) { UpstreamHost = upstreamHost; DownstreamRoute = downstreamRoute; @@ -19,15 +19,15 @@ public Route(List downstreamRoute, UpstreamHttpMethod = upstreamHttpMethod; UpstreamTemplatePattern = upstreamTemplatePattern; Aggregator = aggregator; - UpstreamHeaders = upstreamHeaders; + UpstreamHeaderTemplates = upstreamHeaderTemplates; } - public UpstreamPathTemplate UpstreamTemplatePattern { get; set; } - public List UpstreamHttpMethod { get; set; } - public string UpstreamHost { get; set; } - public List DownstreamRoute { get; set; } - public List DownstreamRouteConfig { get; set; } - public string Aggregator { get; set; } - public Dictionary UpstreamHeaders { get; set; } + public UpstreamPathTemplate UpstreamTemplatePattern { get; private set; } + public List UpstreamHttpMethod { get; private set; } + public string UpstreamHost { get; private set; } + public List DownstreamRoute { get; private set; } + public List DownstreamRouteConfig { get; private set; } + public string Aggregator { get; private set; } + public Dictionary UpstreamHeaderTemplates { get; private set; } } } diff --git a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteCreator.cs b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteCreator.cs index 47ad7a65c..3df59420e 100644 --- a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteCreator.cs +++ b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteCreator.cs @@ -4,6 +4,7 @@ using Ocelot.DownstreamRouteFinder.UrlMatcher; using Ocelot.LoadBalancer.LoadBalancers; using Ocelot.Responses; +using Ocelot.Values; namespace Ocelot.DownstreamRouteFinder.Finder { @@ -42,6 +43,9 @@ public Response Get(string upstreamUrlPath, string upstre var upstreamPathTemplate = new UpstreamPathTemplateBuilder().WithOriginalValue(upstreamUrlPath).Build(); + //TODO + var upstreamHeaderTemplates = new Dictionary(); + var downstreamRouteBuilder = new DownstreamRouteBuilder() .WithServiceName(serviceName) .WithLoadBalancerKey(loadBalancerKey) @@ -53,7 +57,7 @@ public Response Get(string upstreamUrlPath, string upstre .WithLoadBalancerOptions(configuration.LoadBalancerOptions) .WithDownstreamHttpVersion(configuration.DownstreamHttpVersion) .WithUpstreamPathTemplate(upstreamPathTemplate) - .WithUpstreamHeaders(upstreamHeaders); + .WithUpstreamHeaders(upstreamHeaderTemplates); var rateLimitOptions = configuration.Routes?.SelectMany(x => x.DownstreamRoute) .FirstOrDefault(x => x.ServiceName == serviceName); @@ -71,7 +75,7 @@ public Response Get(string upstreamUrlPath, string upstre .WithDownstreamRoute(downstreamRoute) .WithUpstreamHttpMethod(new List { upstreamHttpMethod }) .WithUpstreamPathTemplate(upstreamPathTemplate) - .WithUpstreamHeaders(upstreamHeaders) + .WithUpstreamHeaders(upstreamHeaderTemplates) .Build(); downstreamRouteHolder = new OkResponse(new DownstreamRouteHolder(new List(), route)); diff --git a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs index 5e3b5d724..2c4851b06 100644 --- a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs +++ b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs @@ -1,6 +1,7 @@ using Ocelot.Configuration; using Ocelot.DownstreamRouteFinder.UrlMatcher; -using Ocelot.Responses; +using Ocelot.Middleware; +using Ocelot.Responses; namespace Ocelot.DownstreamRouteFinder.Finder { @@ -28,14 +29,12 @@ public Response Get(string upstreamUrlPath, string upstre { var urlMatch = _urlMatcher.Match(upstreamUrlPath, upstreamQueryString, route.UpstreamTemplatePattern); - if (urlMatch.Data.Match) + if (urlMatch.Data.Match && HeadersMatch(route, upstreamHeaders)) { - downstreamRoutes.Add(GetPlaceholderNamesAndValues(upstreamUrlPath, upstreamQueryString, route)); + downstreamRoutes.Add(GetPlaceholderNamesAndValues(upstreamUrlPath, upstreamQueryString, route, upstreamHeaders)); } } - downstreamRoutes = downstreamRoutes.Where(r => HeadersMatch(r.Route, upstreamHeaders)).ToList(); - if (downstreamRoutes.Any()) { var notNullOption = downstreamRoutes.FirstOrDefault(x => !string.IsNullOrEmpty(x.Route.UpstreamHost)); @@ -53,7 +52,7 @@ private static bool RouteIsApplicableToThisRequest(Route route, string httpMetho (string.IsNullOrEmpty(route.UpstreamHost) || route.UpstreamHost == upstreamHost); } - private DownstreamRouteHolder GetPlaceholderNamesAndValues(string path, string query, Route route) + private DownstreamRouteHolder GetPlaceholderNamesAndValues(string path, string query, Route route, Dictionary upstreamHeaders) { var templatePlaceholderNameAndValues = _placeholderNameAndValueFinder.Find(path, query, route.UpstreamTemplatePattern.OriginalValue); @@ -62,9 +61,10 @@ private DownstreamRouteHolder GetPlaceholderNamesAndValues(string path, string q private bool HeadersMatch(Route route, Dictionary upstreamHeaders) { - return route.UpstreamHeaders == null || - upstreamHeaders != null && route.UpstreamHeaders.All( - h => upstreamHeaders.ContainsKey(h.Key) && upstreamHeaders[h.Key] == route.UpstreamHeaders[h.Key]); + return true; + //return route.UpstreamHeaders == null || + // upstreamHeaders != null && route.UpstreamHeaders.All( + // h => upstreamHeaders.ContainsKey(h.Key) && upstreamHeaders[h.Key] == route.UpstreamHeaders[h.Key]); } } } diff --git a/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinder.cs b/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinder.cs new file mode 100644 index 000000000..192571e4f --- /dev/null +++ b/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinder.cs @@ -0,0 +1,16 @@ +using Ocelot.DownstreamRouteFinder.UrlMatcher; +using Ocelot.Responses; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Ocelot.DownstreamRouteFinder.HeaderMatcher +{ + public class HeaderPlaceholderNameAndValueFinder : IHeaderPlaceholderNameAndValueFinder + { + public Response Match(Dictionary upstreamHeaders, Dictionary routeHeaders) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/IHeaderPlaceholderNameAndValueFinder.cs b/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/IHeaderPlaceholderNameAndValueFinder.cs new file mode 100644 index 000000000..a6d0fc2e9 --- /dev/null +++ b/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/IHeaderPlaceholderNameAndValueFinder.cs @@ -0,0 +1,14 @@ +using Ocelot.DownstreamRouteFinder.UrlMatcher; +using Ocelot.Responses; +using Ocelot.Values; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Ocelot.DownstreamRouteFinder.HeaderMatcher +{ + interface IHeaderPlaceholderNameAndValueFinder + { + Response Match(Dictionary upstreamHeaders, Dictionary routeHeaders); + } +} diff --git a/src/Ocelot/Values/UpstreamHeaderTemplate.cs b/src/Ocelot/Values/UpstreamHeaderTemplate.cs new file mode 100644 index 000000000..2dc97bd54 --- /dev/null +++ b/src/Ocelot/Values/UpstreamHeaderTemplate.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Text.RegularExpressions; + +namespace Ocelot.Values +{ + public class UpstreamHeaderTemplate + { + public string Template { get; } + + public string OriginalValue { get; } + + public Regex Pattern { get; } + + public UpstreamHeaderTemplate(string template, string originalValue) + { + Template = template; + OriginalValue = originalValue; + Pattern = template == null ? + new Regex("$^", RegexOptions.Compiled | RegexOptions.Singleline) : + new Regex(template, RegexOptions.Compiled | RegexOptions.Singleline); + } + } +} diff --git a/test/Ocelot.AcceptanceTests/RoutingBasedOnHeadersTests.cs b/test/Ocelot.AcceptanceTests/RoutingBasedOnHeadersTests.cs index 4c57c18fe..3e7c912e7 100644 --- a/test/Ocelot.AcceptanceTests/RoutingBasedOnHeadersTests.cs +++ b/test/Ocelot.AcceptanceTests/RoutingBasedOnHeadersTests.cs @@ -47,7 +47,7 @@ public void should_match_one_header_value() }, UpstreamPathTemplate = "/", UpstreamHttpMethod = new List { "Get" }, - UpstreamHeaders = new Dictionary() + UpstreamHeaderTemplates = new Dictionary() { [headerName] = headerValue, }, @@ -90,7 +90,7 @@ public void should_match_one_header_value_when_more_headers() }, UpstreamPathTemplate = "/", UpstreamHttpMethod = new List { "Get" }, - UpstreamHeaders = new Dictionary() + UpstreamHeaderTemplates = new Dictionary() { [headerName] = headerValue, }, @@ -136,7 +136,7 @@ public void should_match_two_header_values_when_more_headers() }, UpstreamPathTemplate = "/", UpstreamHttpMethod = new List { "Get" }, - UpstreamHeaders = new Dictionary() + UpstreamHeaderTemplates = new Dictionary() { [headerName1] = headerValue1, [headerName2] = headerValue2, @@ -183,7 +183,7 @@ public void should_not_match_one_header_value() }, UpstreamPathTemplate = "/", UpstreamHttpMethod = new List { "Get" }, - UpstreamHeaders = new Dictionary() + UpstreamHeaderTemplates = new Dictionary() { [headerName] = headerValue, }, @@ -225,7 +225,7 @@ public void should_not_match_one_header_value_when_no_headers() }, UpstreamPathTemplate = "/", UpstreamHttpMethod = new List { "Get" }, - UpstreamHeaders = new Dictionary() + UpstreamHeaderTemplates = new Dictionary() { [headerName] = headerValue, }, @@ -268,7 +268,7 @@ public void should_not_match_two_header_values_when_one_different() }, UpstreamPathTemplate = "/", UpstreamHttpMethod = new List { "Get" }, - UpstreamHeaders = new Dictionary() + UpstreamHeaderTemplates = new Dictionary() { [headerName1] = headerValue1, [headerName2] = headerValue2, @@ -315,7 +315,7 @@ public void should_not_match_two_header_values_when_one_not_existing() }, UpstreamPathTemplate = "/", UpstreamHttpMethod = new List { "Get" }, - UpstreamHeaders = new Dictionary() + UpstreamHeaderTemplates = new Dictionary() { [headerName1] = headerValue1, [headerName2] = headerValue2, @@ -390,7 +390,7 @@ public void should_aggregated_route_match_header_value() "Laura", "Tom", }, - UpstreamHeaders = new Dictionary() + UpstreamHeaderTemplates = new Dictionary() { [headerName] = headerValue, }, @@ -464,7 +464,7 @@ public void should_aggregated_route_not_match_header_value() "Laura", "Tom", }, - UpstreamHeaders = new Dictionary() + UpstreamHeaderTemplates = new Dictionary() { [headerName] = headerValue, }, @@ -479,7 +479,49 @@ public void should_aggregated_route_not_match_header_value() .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.NotFound)) .BDDfy(); - } + } + + [Fact] + public void should_match_one_header_value_with_placeholder() + { + var port = RandomPortFinder.GetRandomPort(); + var headerName = "country_code"; + + var configuration = new FileConfiguration + { + Routes = new List + { + new FileRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = port, + }, + }, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + UpstreamHeaderTemplates = new Dictionary() + { + [headerName] = "{country_code}", + }, + }, + }, + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/pl", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .And(x => _steps.GivenIAddAHeader(headerName, "pl")) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode, string responseBody) { diff --git a/test/Ocelot.UnitTests/Configuration/UpstreamHeaderTemplatePatternCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/UpstreamHeaderTemplatePatternCreatorTests.cs new file mode 100644 index 000000000..d6f535677 --- /dev/null +++ b/test/Ocelot.UnitTests/Configuration/UpstreamHeaderTemplatePatternCreatorTests.cs @@ -0,0 +1,56 @@ +using Ocelot.Configuration.Creator; +using Ocelot.Configuration.File; +using Ocelot.Values; +using Shouldly; +using System; +using System.Collections.Generic; +using System.Text; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Configuration +{ + public class UpstreamHeaderTemplatePatternCreatorTests + { + private FileRoute _fileRoute; + private readonly UpstreamHeaderTemplatePatternCreator _creator; + private Dictionary _result; + + public UpstreamHeaderTemplatePatternCreatorTests() + { + _creator = new UpstreamHeaderTemplatePatternCreator(); + } + + [Fact] + public void should_match_two_placeholders() + { + var fileRoute = new FileRoute + { + UpstreamHeaderTemplates = new Dictionary + { + ["country"] = "any text {cc} and other {version} and {bob} the end", + }, + }; + + this.Given(x => x.GivenTheFollowingFileRoute(fileRoute)) + .When(x => x.WhenICreateTheTemplatePattern()) + .Then(x => x.ThenTheFollowingIsReturned("country", "^(?i)any text {.+} and other {.+} and {.+} the end$")) + .BDDfy(); + } + + private void GivenTheFollowingFileRoute(FileRoute fileRoute) + { + _fileRoute = fileRoute; + } + + private void WhenICreateTheTemplatePattern() + { + _result = _creator.Create(_fileRoute); + } + + private void ThenTheFollowingIsReturned(string headerKey, string expected) + { + _result[headerKey].Template.ShouldBe(expected); + } + } +} From e121c8a4acbc693e95e43168dbaa0907b4d3d483 Mon Sep 17 00:00:00 2001 From: jlukawska Date: Wed, 15 Jul 2020 13:56:12 +0200 Subject: [PATCH 05/49] match upstream headers to header templates --- .../Creator/AggregatesCreator.cs | 16 ++-- .../Configuration/Creator/RoutesCreator.cs | 14 +-- .../UpstreamHeaderTemplatePatternCreator.cs | 3 - .../DependencyInjection/OcelotBuilder.cs | 3 + .../Finder/DownstreamRouteFinder.cs | 20 ++--- .../HeaderPlaceholderNameAndValueFinder.cs | 16 ---- .../HeadersToHeaderTemplatesMatcher.cs | 21 +++++ ...cs => IHeadersToHeaderTemplatesMatcher.cs} | 4 +- .../Configuration/AggregatesCreatorTests.cs | 8 +- .../Configuration/RoutesCreatorTests.cs | 6 +- .../DownstreamRouteFinderTests.cs | 88 +++++++++---------- .../DownstreamRouteProviderFactoryTests.cs | 3 + 12 files changed, 108 insertions(+), 94 deletions(-) delete mode 100644 src/Ocelot/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinder.cs create mode 100644 src/Ocelot/DownstreamRouteFinder/HeaderMatcher/HeadersToHeaderTemplatesMatcher.cs rename src/Ocelot/DownstreamRouteFinder/HeaderMatcher/{IHeaderPlaceholderNameAndValueFinder.cs => IHeadersToHeaderTemplatesMatcher.cs} (57%) diff --git a/src/Ocelot/Configuration/Creator/AggregatesCreator.cs b/src/Ocelot/Configuration/Creator/AggregatesCreator.cs index 30e0ea92f..4963513f2 100644 --- a/src/Ocelot/Configuration/Creator/AggregatesCreator.cs +++ b/src/Ocelot/Configuration/Creator/AggregatesCreator.cs @@ -6,11 +6,16 @@ namespace Ocelot.Configuration.Creator { public class AggregatesCreator : IAggregatesCreator { - private readonly IUpstreamTemplatePatternCreator _creator; + private readonly IUpstreamTemplatePatternCreator _upstreamTemplatePatternCreator; + private readonly IUpstreamHeaderTemplatePatternCreator _upstreamHeaderTemplatePatternCreator; - public AggregatesCreator(IUpstreamTemplatePatternCreator creator) + public AggregatesCreator( + IUpstreamTemplatePatternCreator upstreamTemplatePatternCreator, + IUpstreamHeaderTemplatePatternCreator upstreamHeaderTemplatePatternCreator + ) { - _creator = creator; + _upstreamTemplatePatternCreator = upstreamTemplatePatternCreator; + _upstreamHeaderTemplatePatternCreator = upstreamHeaderTemplatePatternCreator; } public List Create(FileConfiguration fileConfiguration, List routes) @@ -36,10 +41,9 @@ private Route SetUpAggregateRoute(IEnumerable routes, FileAggregateRoute applicableRoutes.Add(downstreamRoute); } - var upstreamTemplatePattern = _creator.Create(aggregateRoute); + var upstreamTemplatePattern = _upstreamTemplatePatternCreator.Create(aggregateRoute); - // TODO - var upstreamHeaderTemplates = new Dictionary(); + var upstreamHeaderTemplates = _upstreamHeaderTemplatePatternCreator.Create(aggregateRoute); var route = new RouteBuilder() .WithUpstreamHttpMethod(aggregateRoute.UpstreamHttpMethod) diff --git a/src/Ocelot/Configuration/Creator/RoutesCreator.cs b/src/Ocelot/Configuration/Creator/RoutesCreator.cs index 45a27bfda..5b0c107f6 100644 --- a/src/Ocelot/Configuration/Creator/RoutesCreator.cs +++ b/src/Ocelot/Configuration/Creator/RoutesCreator.cs @@ -3,7 +3,7 @@ using Ocelot.Configuration.File; using Ocelot.Values; - + namespace Ocelot.Configuration.Creator { public class RoutesCreator : IRoutesCreator @@ -12,6 +12,7 @@ public class RoutesCreator : IRoutesCreator private readonly IClaimsToThingCreator _claimsToThingCreator; private readonly IAuthenticationOptionsCreator _authOptionsCreator; private readonly IUpstreamTemplatePatternCreator _upstreamTemplatePatternCreator; + private readonly IUpstreamHeaderTemplatePatternCreator _upstreamHeaderTemplatePatternCreator; private readonly IRequestIdKeyCreator _requestIdKeyCreator; private readonly IQoSOptionsCreator _qosOptionsCreator; private readonly IRouteOptionsCreator _fileRouteOptionsCreator; @@ -39,7 +40,8 @@ public RoutesCreator( ILoadBalancerOptionsCreator loadBalancerOptionsCreator, IRouteKeyCreator routeKeyCreator, ISecurityOptionsCreator securityOptionsCreator, - IVersionCreator versionCreator + IVersionCreator versionCreator, + IUpstreamHeaderTemplatePatternCreator upstreamHeaderTemplatePatternCreator ) { _routeKeyCreator = routeKeyCreator; @@ -58,6 +60,7 @@ IVersionCreator versionCreator _loadBalancerOptionsCreator = loadBalancerOptionsCreator; _securityOptionsCreator = securityOptionsCreator; _versionCreator = versionCreator; + _upstreamHeaderTemplatePatternCreator = upstreamHeaderTemplatePatternCreator; } public List Create(FileConfiguration fileConfiguration) @@ -152,10 +155,9 @@ private DownstreamRoute SetUpDownstreamRoute(FileRoute fileRoute, FileGlobalConf private Route SetUpRoute(FileRoute fileRoute, DownstreamRoute downstreamRoutes) { - var upstreamTemplatePattern = _upstreamTemplatePatternCreator.Create(fileRoute); - - //TODO - var upstreamHeaderTemplates = new Dictionary(); + var upstreamTemplatePattern = _upstreamTemplatePatternCreator.Create(fileRoute); + + var upstreamHeaderTemplates = _upstreamHeaderTemplatePatternCreator.Create(fileRoute); var route = new RouteBuilder() .WithUpstreamHttpMethod(fileRoute.UpstreamHttpMethod) diff --git a/src/Ocelot/Configuration/Creator/UpstreamHeaderTemplatePatternCreator.cs b/src/Ocelot/Configuration/Creator/UpstreamHeaderTemplatePatternCreator.cs index 865263e76..00e7bd5a6 100644 --- a/src/Ocelot/Configuration/Creator/UpstreamHeaderTemplatePatternCreator.cs +++ b/src/Ocelot/Configuration/Creator/UpstreamHeaderTemplatePatternCreator.cs @@ -9,11 +9,8 @@ namespace Ocelot.Configuration.Creator public class UpstreamHeaderTemplatePatternCreator : IUpstreamHeaderTemplatePatternCreator { private const string RegExMatchOneOrMoreOfEverything = ".+"; - private const string RegExMatchOneOrMoreOfEverythingUntilNextForwardSlash = "[^/]+"; private const string RegExMatchEndString = "$"; private const string RegExIgnoreCase = "(?i)"; - private const string RegExForwardSlashOnly = "^/$"; - private const string RegExForwardSlashAndOnePlaceHolder = "^/.*"; private const string RegExPlaceholders = @"\{(.*?)\}"; public Dictionary Create(IRoute route) diff --git a/src/Ocelot/DependencyInjection/OcelotBuilder.cs b/src/Ocelot/DependencyInjection/OcelotBuilder.cs index a72ec3cbf..ffb963f62 100644 --- a/src/Ocelot/DependencyInjection/OcelotBuilder.cs +++ b/src/Ocelot/DependencyInjection/OcelotBuilder.cs @@ -15,6 +15,7 @@ using Ocelot.Configuration.Setter; using Ocelot.Configuration.Validator; using Ocelot.DownstreamRouteFinder.Finder; +using Ocelot.DownstreamRouteFinder.HeaderMatcher; using Ocelot.DownstreamRouteFinder.UrlMatcher; using Ocelot.DownstreamUrlCreator; using Ocelot.Headers; @@ -75,6 +76,7 @@ public OcelotBuilder(IServiceCollection services, IConfiguration configurationRo Services.TryAddSingleton(); Services.TryAddSingleton(); Services.TryAddSingleton(); + Services.TryAddSingleton(); Services.TryAddSingleton(); Services.TryAddSingleton(); Services.TryAddSingleton(); @@ -102,6 +104,7 @@ public OcelotBuilder(IServiceCollection services, IConfiguration configurationRo Services.TryAddSingleton(); Services.TryAddSingleton(); Services.TryAddSingleton(); + Services.TryAddSingleton(); Services.TryAddSingleton(); Services.TryAddSingleton(); Services.AddSingleton(); diff --git a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs index 2c4851b06..1b8028bf2 100644 --- a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs +++ b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs @@ -1,4 +1,5 @@ using Ocelot.Configuration; +using Ocelot.DownstreamRouteFinder.HeaderMatcher; using Ocelot.DownstreamRouteFinder.UrlMatcher; using Ocelot.Middleware; using Ocelot.Responses; @@ -9,11 +10,17 @@ public class DownstreamRouteFinder : IDownstreamRouteProvider { private readonly IUrlPathToUrlTemplateMatcher _urlMatcher; private readonly IPlaceholderNameAndValueFinder _placeholderNameAndValueFinder; + private readonly IHeadersToHeaderTemplatesMatcher _headersMatcher; - public DownstreamRouteFinder(IUrlPathToUrlTemplateMatcher urlMatcher, IPlaceholderNameAndValueFinder urlPathPlaceholderNameAndValueFinder) + public DownstreamRouteFinder( + IUrlPathToUrlTemplateMatcher urlMatcher, + IPlaceholderNameAndValueFinder urlPathPlaceholderNameAndValueFinder, + IHeadersToHeaderTemplatesMatcher headersMatcher + ) { _urlMatcher = urlMatcher; _placeholderNameAndValueFinder = urlPathPlaceholderNameAndValueFinder; + _headersMatcher = headersMatcher; } public Response Get(string upstreamUrlPath, string upstreamQueryString, string httpMethod, @@ -28,8 +35,9 @@ public Response Get(string upstreamUrlPath, string upstre foreach (var route in applicableRoutes) { var urlMatch = _urlMatcher.Match(upstreamUrlPath, upstreamQueryString, route.UpstreamTemplatePattern); + var headersMatch = _headersMatcher.Match(upstreamHeaders, route.UpstreamHeaderTemplates); - if (urlMatch.Data.Match && HeadersMatch(route, upstreamHeaders)) + if (urlMatch.Data.Match && headersMatch) { downstreamRoutes.Add(GetPlaceholderNamesAndValues(upstreamUrlPath, upstreamQueryString, route, upstreamHeaders)); } @@ -58,13 +66,5 @@ private DownstreamRouteHolder GetPlaceholderNamesAndValues(string path, string q return new DownstreamRouteHolder(templatePlaceholderNameAndValues.Data, route); } - - private bool HeadersMatch(Route route, Dictionary upstreamHeaders) - { - return true; - //return route.UpstreamHeaders == null || - // upstreamHeaders != null && route.UpstreamHeaders.All( - // h => upstreamHeaders.ContainsKey(h.Key) && upstreamHeaders[h.Key] == route.UpstreamHeaders[h.Key]); - } } } diff --git a/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinder.cs b/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinder.cs deleted file mode 100644 index 192571e4f..000000000 --- a/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinder.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Ocelot.DownstreamRouteFinder.UrlMatcher; -using Ocelot.Responses; -using System; -using System.Collections.Generic; -using System.Text; - -namespace Ocelot.DownstreamRouteFinder.HeaderMatcher -{ - public class HeaderPlaceholderNameAndValueFinder : IHeaderPlaceholderNameAndValueFinder - { - public Response Match(Dictionary upstreamHeaders, Dictionary routeHeaders) - { - throw new NotImplementedException(); - } - } -} diff --git a/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/HeadersToHeaderTemplatesMatcher.cs b/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/HeadersToHeaderTemplatesMatcher.cs new file mode 100644 index 000000000..95c7b2f10 --- /dev/null +++ b/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/HeadersToHeaderTemplatesMatcher.cs @@ -0,0 +1,21 @@ +using Microsoft.AspNetCore.Routing; +using Ocelot.DownstreamRouteFinder.UrlMatcher; +using Ocelot.Responses; +using Ocelot.Values; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Ocelot.DownstreamRouteFinder.HeaderMatcher +{ + public class HeadersToHeaderTemplatesMatcher : IHeadersToHeaderTemplatesMatcher + { + public bool Match(Dictionary upstreamHeaders, Dictionary routeHeaders) + { + return routeHeaders == null || + upstreamHeaders != null && routeHeaders.All( + h => upstreamHeaders.ContainsKey(h.Key) && routeHeaders[h.Key].Pattern.IsMatch(upstreamHeaders[h.Key])); + } + } +} diff --git a/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/IHeaderPlaceholderNameAndValueFinder.cs b/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/IHeadersToHeaderTemplatesMatcher.cs similarity index 57% rename from src/Ocelot/DownstreamRouteFinder/HeaderMatcher/IHeaderPlaceholderNameAndValueFinder.cs rename to src/Ocelot/DownstreamRouteFinder/HeaderMatcher/IHeadersToHeaderTemplatesMatcher.cs index a6d0fc2e9..2b3da66fb 100644 --- a/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/IHeaderPlaceholderNameAndValueFinder.cs +++ b/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/IHeadersToHeaderTemplatesMatcher.cs @@ -7,8 +7,8 @@ namespace Ocelot.DownstreamRouteFinder.HeaderMatcher { - interface IHeaderPlaceholderNameAndValueFinder + public interface IHeadersToHeaderTemplatesMatcher { - Response Match(Dictionary upstreamHeaders, Dictionary routeHeaders); + bool Match(Dictionary upstreamHeaders, Dictionary routeHeaders); } } diff --git a/test/Ocelot.UnitTests/Configuration/AggregatesCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/AggregatesCreatorTests.cs index 469e3d7f2..997206184 100644 --- a/test/Ocelot.UnitTests/Configuration/AggregatesCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/AggregatesCreatorTests.cs @@ -9,7 +9,8 @@ namespace Ocelot.UnitTests.Configuration public class AggregatesCreatorTests : UnitTest { private readonly AggregatesCreator _creator; - private readonly Mock _utpCreator; + private readonly Mock _utpCreator; + private readonly Mock _uhtpCreator; private FileConfiguration _fileConfiguration; private List _routes; private List _result; @@ -18,8 +19,9 @@ public class AggregatesCreatorTests : UnitTest public AggregatesCreatorTests() { - _utpCreator = new Mock(); - _creator = new AggregatesCreator(_utpCreator.Object); + _utpCreator = new Mock(); + _uhtpCreator = new Mock(); + _creator = new AggregatesCreator(_utpCreator.Object, _uhtpCreator.Object); } [Fact] diff --git a/test/Ocelot.UnitTests/Configuration/RoutesCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/RoutesCreatorTests.cs index 382eb3a44..ccf409aee 100644 --- a/test/Ocelot.UnitTests/Configuration/RoutesCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/RoutesCreatorTests.cs @@ -13,6 +13,7 @@ public class RoutesCreatorTests : UnitTest private readonly Mock _cthCreator; private readonly Mock _aoCreator; private readonly Mock _utpCreator; + private readonly Mock _uhtpCreator; private readonly Mock _ridkCreator; private readonly Mock _qosoCreator; private readonly Mock _rroCreator; @@ -40,6 +41,7 @@ public class RoutesCreatorTests : UnitTest private List _dhp; private LoadBalancerOptions _lbo; private List _result; + private SecurityOptions _securityOptions; private Version _expectedVersion; public RoutesCreatorTests() @@ -59,6 +61,7 @@ public RoutesCreatorTests() _rrkCreator = new Mock(); _soCreator = new Mock(); _versionCreator = new Mock(); + _uhtpCreator = new Mock(); _creator = new RoutesCreator( _cthCreator.Object, @@ -75,7 +78,8 @@ public RoutesCreatorTests() _lboCreator.Object, _rrkCreator.Object, _soCreator.Object, - _versionCreator.Object + _versionCreator.Object, + _uhtpCreator.Object ); } diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs index 30bc16bb0..96e9000c1 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs @@ -2,6 +2,7 @@ using Ocelot.Configuration.Builder; using Ocelot.DownstreamRouteFinder; using Ocelot.DownstreamRouteFinder.Finder; +using Ocelot.DownstreamRouteFinder.HeaderMatcher; using Ocelot.DownstreamRouteFinder.UrlMatcher; using Ocelot.Responses; using Ocelot.Values; @@ -12,6 +13,7 @@ public class DownstreamRouteFinderTests : UnitTest { private readonly IDownstreamRouteProvider _downstreamRouteFinder; private readonly Mock _mockMatcher; + private readonly Mock _mockHeadersMatcher; private readonly Mock _finder; private string _upstreamUrlPath; private Response _result; @@ -26,8 +28,9 @@ public class DownstreamRouteFinderTests : UnitTest public DownstreamRouteFinderTests() { _mockMatcher = new Mock(); + _mockHeadersMatcher = new Mock(); _finder = new Mock(); - _downstreamRouteFinder = new Ocelot.DownstreamRouteFinder.Finder.DownstreamRouteFinder(_mockMatcher.Object, _finder.Object); + _downstreamRouteFinder = new Ocelot.DownstreamRouteFinder.Finder.DownstreamRouteFinder(_mockMatcher.Object, _finder.Object, _mockHeadersMatcher.Object); } [Fact] @@ -60,6 +63,7 @@ public void should_return_highest_priority_when_first() .Build(), }, string.Empty, serviceProviderConfig)) .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) + .And(x => x.GivenTheHeadersMatcherReturns(true)) .And(x => x.GivenTheUpstreamHttpMethodIs("Post")) .When(x => x.WhenICallTheFinder()) .Then(x => x.ThenTheFollowingIsReturned(new DownstreamRouteHolder(new List(), @@ -106,6 +110,7 @@ public void should_return_highest_priority_when_lowest() .Build(), }, string.Empty, serviceProviderConfig)) .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) + .And(x => x.GivenTheHeadersMatcherReturns(true)) .And(x => x.GivenTheUpstreamHttpMethodIs("Post")) .When(x => x.WhenICallTheFinder()) .Then(x => x.ThenTheFollowingIsReturned(new DownstreamRouteHolder(new List(), @@ -145,6 +150,7 @@ public void should_return_route() }, string.Empty, serviceProviderConfig )) .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) + .And(x => x.GivenTheHeadersMatcherReturns(true)) .And(x => x.GivenTheUpstreamHttpMethodIs("Get")) .When(x => x.WhenICallTheFinder()) .Then( @@ -187,6 +193,7 @@ public void should_not_append_slash_to_upstream_url_path() }, string.Empty, serviceProviderConfig )) .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) + .And(x => x.GivenTheHeadersMatcherReturns(true)) .And(x => x.GivenTheUpstreamHttpMethodIs("Get")) .When(x => x.WhenICallTheFinder()) .Then( @@ -230,6 +237,7 @@ public void should_return_route_if_upstream_path_and_upstream_template_are_the_s }, string.Empty, serviceProviderConfig )) .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) + .And(x => x.GivenTheHeadersMatcherReturns(true)) .And(x => x.GivenTheUpstreamHttpMethodIs("Get")) .When(x => x.WhenICallTheFinder()) .Then( @@ -280,6 +288,7 @@ public void should_return_correct_route_for_http_verb() }, string.Empty, serviceProviderConfig )) .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) + .And(x => x.GivenTheHeadersMatcherReturns(true)) .And(x => x.GivenTheUpstreamHttpMethodIs("Post")) .When(x => x.WhenICallTheFinder()) .Then( @@ -317,6 +326,7 @@ public void should_not_return_route() }, string.Empty, serviceProviderConfig )) .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(false)))) + .And(x => x.GivenTheHeadersMatcherReturns(true)) .And(x => x.GivenTheUpstreamHttpMethodIs("Get")) .When(x => x.WhenICallTheFinder()) .Then( @@ -349,6 +359,7 @@ public void should_return_correct_route_for_http_verb_setting_multiple_upstream_ }, string.Empty, serviceProviderConfig )) .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) + .And(x => x.GivenTheHeadersMatcherReturns(true)) .And(x => x.GivenTheUpstreamHttpMethodIs("Post")) .When(x => x.WhenICallTheFinder()) .Then( @@ -390,6 +401,7 @@ public void should_return_correct_route_for_http_verb_setting_all_upstream_http_ }, string.Empty, serviceProviderConfig )) .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) + .And(x => x.GivenTheHeadersMatcherReturns(true)) .And(x => x.GivenTheUpstreamHttpMethodIs("Post")) .When(x => x.WhenICallTheFinder()) .Then( @@ -431,6 +443,7 @@ public void should_not_return_route_for_http_verb_not_setting_in_upstream_http_m }, string.Empty, serviceProviderConfig )) .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) + .And(x => x.GivenTheHeadersMatcherReturns(true)) .And(x => x.GivenTheUpstreamHttpMethodIs("Post")) .When(x => x.WhenICallTheFinder()) .Then(x => x.ThenAnErrorResponseIsReturned()) @@ -463,6 +476,7 @@ public void should_return_route_when_host_matches() }, string.Empty, serviceProviderConfig )) .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) + .And(x => x.GivenTheHeadersMatcherReturns(true)) .And(x => x.GivenTheUpstreamHttpMethodIs("Get")) .When(x => x.WhenICallTheFinder()) .Then( @@ -506,6 +520,7 @@ public void should_return_route_when_upstreamhost_is_null() }, string.Empty, serviceProviderConfig )) .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) + .And(x => x.GivenTheHeadersMatcherReturns(true)) .And(x => x.GivenTheUpstreamHttpMethodIs("Get")) .When(x => x.WhenICallTheFinder()) .Then( @@ -558,6 +573,7 @@ public void should_not_return_route_when_host_doesnt_match() }, string.Empty, serviceProviderConfig )) .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) + .And(x => x.GivenTheHeadersMatcherReturns(true)) .And(x => x.GivenTheUpstreamHttpMethodIs("Get")) .When(x => x.WhenICallTheFinder()) .Then(x => x.ThenAnErrorResponseIsReturned()) @@ -588,6 +604,7 @@ public void should_not_return_route_when_host_doesnt_match_with_empty_upstream_h }, string.Empty, serviceProviderConfig )) .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) + .And(x => x.GivenTheHeadersMatcherReturns(true)) .And(x => x.GivenTheUpstreamHttpMethodIs("Get")) .When(x => x.WhenICallTheFinder()) .Then(x => x.ThenAnErrorResponseIsReturned()) @@ -618,6 +635,7 @@ public void should_return_route_when_host_does_match_with_empty_upstream_http_me }, string.Empty, serviceProviderConfig )) .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) + .And(x => x.GivenTheHeadersMatcherReturns(true)) .And(x => x.GivenTheUpstreamHttpMethodIs("Get")) .When(x => x.WhenICallTheFinder()) .And(x => x.ThenTheUrlMatcherIsCalledCorrectly(1, 0)) @@ -658,6 +676,7 @@ public void should_return_route_when_host_matches_but_null_host_on_same_path_fir }, string.Empty, serviceProviderConfig )) .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) + .And(x => x.GivenTheHeadersMatcherReturns(true)) .And(x => x.GivenTheUpstreamHttpMethodIs("Get")) .When(x => x.WhenICallTheFinder()) .Then( @@ -688,6 +707,11 @@ public void should_return_route_when_upstream_headers_match() ["header1"] = "headerValue1", ["header2"] = "headerValue2", }; + var upstreamHeadersConfig = new Dictionary() + { + ["header1"] = new UpstreamHeaderTemplate("headerValue1", "headerValue1"), + ["header2"] = new UpstreamHeaderTemplate("headerValue2", "headerValue2"), + }; this.Given(x => x.GivenThereIsAnUpstreamUrlPath("matchInUrlMatcher/")) .And(x => GivenTheUpstreamHeadersIs(upstreamHeaders)) @@ -704,11 +728,12 @@ public void should_return_route_when_upstream_headers_match() .Build()) .WithUpstreamHttpMethod(new List { "Get" }) .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) - .WithUpstreamHeaders(upstreamHeaders) + .WithUpstreamHeaders(upstreamHeadersConfig) .Build(), }, string.Empty, serviceProviderConfig )) .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) + .And(x => x.GivenTheHeadersMatcherReturns(true)) .And(x => x.GivenTheUpstreamHttpMethodIs("Get")) .When(x => x.WhenICallTheFinder()) .Then( @@ -733,14 +758,10 @@ public void should_not_return_route_when_upstream_headers_dont_match() { var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); - var upstreamHeadersConfig1 = new Dictionary() - { - ["header1"] = "headerValue1", - ["header2"] = "headerValue2", - }; - var upstreamHeadersConfig2 = new Dictionary() + var upstreamHeadersConfig = new Dictionary() { - ["header1"] = "headerValueAnother", + ["header1"] = new UpstreamHeaderTemplate("headerValue1", "headerValue1"), + ["header2"] = new UpstreamHeaderTemplate("headerValue2", "headerValue2"), }; this.Given(x => x.GivenThereIsAnUpstreamUrlPath("matchInUrlMatcher/")) @@ -756,7 +777,7 @@ public void should_not_return_route_when_upstream_headers_dont_match() .Build()) .WithUpstreamHttpMethod(new List { "Get" }) .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) - .WithUpstreamHeaders(upstreamHeadersConfig1) + .WithUpstreamHeaders(upstreamHeadersConfig) .Build(), new RouteBuilder() .WithDownstreamRoute(new DownstreamRouteBuilder() @@ -766,52 +787,18 @@ public void should_not_return_route_when_upstream_headers_dont_match() .Build()) .WithUpstreamHttpMethod(new List { "Get" }) .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) - .WithUpstreamHeaders(upstreamHeadersConfig2) + .WithUpstreamHeaders(upstreamHeadersConfig) .Build(), }, string.Empty, serviceProviderConfig )) .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) + .And(x => x.GivenTheHeadersMatcherReturns(false)) .And(x => x.GivenTheUpstreamHttpMethodIs("Get")) .When(x => x.WhenICallTheFinder()) .Then(x => x.ThenAnErrorResponseIsReturned()) .BDDfy(); - } - - [Fact] - public void should_not_return_route_when_upstream_headers_null() - { - var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); + } - var upstreamHeadersConfig1 = new Dictionary() - { - ["header1"] = "headerValue1", - ["header2"] = "headerValue2", - }; - - this.Given(x => x.GivenThereIsAnUpstreamUrlPath("matchInUrlMatcher/")) - .And(x => GivenTheUpstreamHeadersIs(null)) - .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")) - .WithUpstreamHeaders(upstreamHeadersConfig1) - .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()) - .BDDfy(); - } - private void GivenTheUpstreamHostIs(string upstreamHost) { _upstreamHost = upstreamHost; @@ -871,6 +858,13 @@ private void GivenTheUrlMatcherReturns(Response match) .Returns(_match); } + private void GivenTheHeadersMatcherReturns(bool headersMatch) + { + _mockHeadersMatcher + .Setup(x => x.Match(It.IsAny>(), It.IsAny>())) + .Returns(headersMatch); + } + private void GivenTheConfigurationIs(List routesConfig, string adminPath, ServiceProviderConfiguration serviceProviderConfig) { _routesConfig = routesConfig; diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteProviderFactoryTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteProviderFactoryTests.cs index db89a5eeb..0832abcbf 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteProviderFactoryTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteProviderFactoryTests.cs @@ -2,6 +2,8 @@ using Ocelot.Configuration; using Ocelot.Configuration.Builder; using Ocelot.Configuration.Creator; +using Ocelot.DownstreamRouteFinder.Finder; +using Ocelot.DownstreamRouteFinder.HeaderMatcher; using Ocelot.DownstreamRouteFinder.UrlMatcher; using Ocelot.Logging; @@ -22,6 +24,7 @@ public DownstreamRouteProviderFactoryTests() var services = new ServiceCollection(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); From 073c6fe615db13c85c28cbfa2fa31701ce05cd86 Mon Sep 17 00:00:00 2001 From: jlukawska Date: Thu, 16 Jul 2020 13:49:01 +0200 Subject: [PATCH 06/49] find placeholders name and values, fix regex for finding placeholders values --- .../UpstreamHeaderTemplatePatternCreator.cs | 5 ++-- .../DependencyInjection/OcelotBuilder.cs | 1 + .../Finder/DownstreamRouteFinder.cs | 19 +++++++----- .../HeaderPlaceholderNameAndValueFinder.cs | 29 +++++++++++++++++++ .../IHeaderPlaceholderNameAndValueFinder.cs | 13 +++++++++ .../RoutingBasedOnHeadersTests.cs | 4 +-- .../DownstreamRouteProviderFactoryTests.cs | 1 + 7 files changed, 61 insertions(+), 11 deletions(-) create mode 100644 src/Ocelot/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinder.cs create mode 100644 src/Ocelot/DownstreamRouteFinder/HeaderMatcher/IHeaderPlaceholderNameAndValueFinder.cs diff --git a/src/Ocelot/Configuration/Creator/UpstreamHeaderTemplatePatternCreator.cs b/src/Ocelot/Configuration/Creator/UpstreamHeaderTemplatePatternCreator.cs index 00e7bd5a6..4c8c0f480 100644 --- a/src/Ocelot/Configuration/Creator/UpstreamHeaderTemplatePatternCreator.cs +++ b/src/Ocelot/Configuration/Creator/UpstreamHeaderTemplatePatternCreator.cs @@ -11,7 +11,7 @@ public class UpstreamHeaderTemplatePatternCreator : IUpstreamHeaderTemplatePatte private const string RegExMatchOneOrMoreOfEverything = ".+"; private const string RegExMatchEndString = "$"; private const string RegExIgnoreCase = "(?i)"; - private const string RegExPlaceholders = @"\{(.*?)\}"; + private const string RegExPlaceholders = @"(\{.*?\})"; public Dictionary Create(IRoute route) { @@ -34,7 +34,8 @@ public Dictionary Create(IRoute route) { var indexOfPlaceholder = headerTemplateValue.IndexOf(placeholders[i]); - headerTemplateValue = headerTemplateValue.Replace(placeholders[i], RegExMatchOneOrMoreOfEverything); + var placeholderName = placeholders[i].Substring(1, placeholders[i].Length - 2); // remove { brackets + headerTemplateValue = headerTemplateValue.Replace(placeholders[i], "(?<"+placeholderName+">"+RegExMatchOneOrMoreOfEverything+")"); } var template = route.RouteIsCaseSensitive diff --git a/src/Ocelot/DependencyInjection/OcelotBuilder.cs b/src/Ocelot/DependencyInjection/OcelotBuilder.cs index ffb963f62..f5b3a44e3 100644 --- a/src/Ocelot/DependencyInjection/OcelotBuilder.cs +++ b/src/Ocelot/DependencyInjection/OcelotBuilder.cs @@ -106,6 +106,7 @@ public OcelotBuilder(IServiceCollection services, IConfiguration configurationRo Services.TryAddSingleton(); Services.TryAddSingleton(); Services.TryAddSingleton(); + Services.TryAddSingleton(); Services.TryAddSingleton(); Services.AddSingleton(); Services.AddSingleton(); diff --git a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs index 1b8028bf2..8285d2491 100644 --- a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs +++ b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs @@ -9,18 +9,21 @@ namespace Ocelot.DownstreamRouteFinder.Finder public class DownstreamRouteFinder : IDownstreamRouteProvider { private readonly IUrlPathToUrlTemplateMatcher _urlMatcher; - private readonly IPlaceholderNameAndValueFinder _placeholderNameAndValueFinder; + private readonly IPlaceholderNameAndValueFinder _placeholderNameAndValueFinder; private readonly IHeadersToHeaderTemplatesMatcher _headersMatcher; + private readonly IHeaderPlaceholderNameAndValueFinder _headerPlaceholderNameAndValueFinder; public DownstreamRouteFinder( IUrlPathToUrlTemplateMatcher urlMatcher, IPlaceholderNameAndValueFinder urlPathPlaceholderNameAndValueFinder, - IHeadersToHeaderTemplatesMatcher headersMatcher + IHeadersToHeaderTemplatesMatcher headersMatcher, + IHeaderPlaceholderNameAndValueFinder headerPlaceholderNameAndValueFinder ) { _urlMatcher = urlMatcher; - _placeholderNameAndValueFinder = urlPathPlaceholderNameAndValueFinder; + _placeholderNameAndValueFinder = urlPathPlaceholderNameAndValueFinder; _headersMatcher = headersMatcher; + _headerPlaceholderNameAndValueFinder = headerPlaceholderNameAndValueFinder; } public Response Get(string upstreamUrlPath, string upstreamQueryString, string httpMethod, @@ -34,7 +37,7 @@ public Response Get(string upstreamUrlPath, string upstre foreach (var route in applicableRoutes) { - var urlMatch = _urlMatcher.Match(upstreamUrlPath, upstreamQueryString, route.UpstreamTemplatePattern); + var urlMatch = _urlMatcher.Match(upstreamUrlPath, upstreamQueryString, route.UpstreamTemplatePattern); var headersMatch = _headersMatcher.Match(upstreamHeaders, route.UpstreamHeaderTemplates); if (urlMatch.Data.Match && headersMatch) @@ -62,9 +65,11 @@ private static bool RouteIsApplicableToThisRequest(Route route, string httpMetho private DownstreamRouteHolder GetPlaceholderNamesAndValues(string path, string query, Route route, Dictionary upstreamHeaders) { - var templatePlaceholderNameAndValues = _placeholderNameAndValueFinder.Find(path, query, route.UpstreamTemplatePattern.OriginalValue); + var templatePlaceholderNameAndValues = _placeholderNameAndValueFinder.Find(path, query, route.UpstreamTemplatePattern.OriginalValue).Data; + var headerPlaceholders = _headerPlaceholderNameAndValueFinder.Find(upstreamHeaders, route.UpstreamHeaderTemplates); + templatePlaceholderNameAndValues.AddRange(headerPlaceholders); - return new DownstreamRouteHolder(templatePlaceholderNameAndValues.Data, route); - } + return new DownstreamRouteHolder(templatePlaceholderNameAndValues, route); + } } } diff --git a/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinder.cs b/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinder.cs new file mode 100644 index 000000000..5eac8e83a --- /dev/null +++ b/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinder.cs @@ -0,0 +1,29 @@ +using Ocelot.DownstreamRouteFinder.UrlMatcher; +using Ocelot.Values; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; + +namespace Ocelot.DownstreamRouteFinder.HeaderMatcher +{ + public class HeaderPlaceholderNameAndValueFinder : IHeaderPlaceholderNameAndValueFinder + { + public List Find(Dictionary upstreamHeaders, Dictionary templateHeaders) + { + var placeholderNameAndValuesList = new List(); + + foreach (var templateHeader in templateHeaders) + { + var upstreamHeader = upstreamHeaders[templateHeader.Key]; + var matches = templateHeader.Value.Pattern.Matches(upstreamHeader); + var placeholders = matches.SelectMany(g => g.Groups as IEnumerable).Where(g => g.Index != 0) + .Select(g => new PlaceholderNameAndValue(g.Name, g.Value)); + placeholderNameAndValuesList.AddRange(placeholders); + } + + return placeholderNameAndValuesList; + } + } +} diff --git a/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/IHeaderPlaceholderNameAndValueFinder.cs b/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/IHeaderPlaceholderNameAndValueFinder.cs new file mode 100644 index 000000000..3769b4dd7 --- /dev/null +++ b/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/IHeaderPlaceholderNameAndValueFinder.cs @@ -0,0 +1,13 @@ +using Ocelot.DownstreamRouteFinder.UrlMatcher; +using Ocelot.Values; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Ocelot.DownstreamRouteFinder.HeaderMatcher +{ + public interface IHeaderPlaceholderNameAndValueFinder + { + List Find(Dictionary upstreamHeaders, Dictionary templateHeaders); + } +} diff --git a/test/Ocelot.AcceptanceTests/RoutingBasedOnHeadersTests.cs b/test/Ocelot.AcceptanceTests/RoutingBasedOnHeadersTests.cs index 3e7c912e7..150863c10 100644 --- a/test/Ocelot.AcceptanceTests/RoutingBasedOnHeadersTests.cs +++ b/test/Ocelot.AcceptanceTests/RoutingBasedOnHeadersTests.cs @@ -507,7 +507,7 @@ public void should_match_one_header_value_with_placeholder() UpstreamHttpMethod = new List { "Get" }, UpstreamHeaderTemplates = new Dictionary() { - [headerName] = "{country_code}", + [headerName] = "start_{country_code}_version_{version}_end", }, }, }, @@ -516,7 +516,7 @@ public void should_match_one_header_value_with_placeholder() this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/pl", 200, "Hello from Laura")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) - .And(x => _steps.GivenIAddAHeader(headerName, "pl")) + .And(x => _steps.GivenIAddAHeader(headerName, "start_pl_version_v1_end")) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteProviderFactoryTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteProviderFactoryTests.cs index 0832abcbf..6f43220de 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteProviderFactoryTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteProviderFactoryTests.cs @@ -23,6 +23,7 @@ public DownstreamRouteProviderFactoryTests() { var services = new ServiceCollection(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); From 800a0258e938e2a4590749439c37c7ee22d43c03 Mon Sep 17 00:00:00 2001 From: jlukawska Date: Thu, 23 Jul 2020 13:52:28 +0200 Subject: [PATCH 07/49] fix unit tests --- .../HeaderPlaceholderNameAndValueFinder.cs | 2 +- .../RoutingBasedOnHeadersTests.cs | 8 +-- ...streamHeaderTemplatePatternCreatorTests.cs | 2 +- .../DownstreamRouteFinderTests.cs | 49 ++++++++++++++----- 4 files changed, 44 insertions(+), 17 deletions(-) diff --git a/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinder.cs b/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinder.cs index 5eac8e83a..bdd93395b 100644 --- a/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinder.cs +++ b/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinder.cs @@ -19,7 +19,7 @@ public List Find(Dictionary upstreamHea var upstreamHeader = upstreamHeaders[templateHeader.Key]; var matches = templateHeader.Value.Pattern.Matches(upstreamHeader); var placeholders = matches.SelectMany(g => g.Groups as IEnumerable).Where(g => g.Index != 0) - .Select(g => new PlaceholderNameAndValue(g.Name, g.Value)); + .Select(g => new PlaceholderNameAndValue("{"+g.Name+"}", g.Value)); placeholderNameAndValuesList.AddRange(placeholders); } diff --git a/test/Ocelot.AcceptanceTests/RoutingBasedOnHeadersTests.cs b/test/Ocelot.AcceptanceTests/RoutingBasedOnHeadersTests.cs index 150863c10..af453f7fd 100644 --- a/test/Ocelot.AcceptanceTests/RoutingBasedOnHeadersTests.cs +++ b/test/Ocelot.AcceptanceTests/RoutingBasedOnHeadersTests.cs @@ -493,7 +493,7 @@ public void should_match_one_header_value_with_placeholder() { new FileRoute { - DownstreamPathTemplate = "/", + DownstreamPathTemplate = "/{country_code}/{version}/{aa}", DownstreamScheme = "http", DownstreamHostAndPorts = new List { @@ -503,7 +503,7 @@ public void should_match_one_header_value_with_placeholder() Port = port, }, }, - UpstreamPathTemplate = "/", + UpstreamPathTemplate = "/{aa}", UpstreamHttpMethod = new List { "Get" }, UpstreamHeaderTemplates = new Dictionary() { @@ -513,11 +513,11 @@ public void should_match_one_header_value_with_placeholder() }, }; - this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/pl", 200, "Hello from Laura")) + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/pl/v1/bb", 200, "Hello from Laura")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .And(x => _steps.GivenIAddAHeader(headerName, "start_pl_version_v1_end")) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/bb")) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) .BDDfy(); diff --git a/test/Ocelot.UnitTests/Configuration/UpstreamHeaderTemplatePatternCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/UpstreamHeaderTemplatePatternCreatorTests.cs index d6f535677..d99ebb081 100644 --- a/test/Ocelot.UnitTests/Configuration/UpstreamHeaderTemplatePatternCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/UpstreamHeaderTemplatePatternCreatorTests.cs @@ -34,7 +34,7 @@ public void should_match_two_placeholders() this.Given(x => x.GivenTheFollowingFileRoute(fileRoute)) .When(x => x.WhenICreateTheTemplatePattern()) - .Then(x => x.ThenTheFollowingIsReturned("country", "^(?i)any text {.+} and other {.+} and {.+} the end$")) + .Then(x => x.ThenTheFollowingIsReturned("country", "^(?i)any text (?.+) and other (?.+) and (?.+) the end$")) .BDDfy(); } diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs index 96e9000c1..ff5fc78c3 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs @@ -12,9 +12,10 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder public class DownstreamRouteFinderTests : UnitTest { private readonly IDownstreamRouteProvider _downstreamRouteFinder; - private readonly Mock _mockMatcher; + private readonly Mock _mockUrlMatcher; private readonly Mock _mockHeadersMatcher; - private readonly Mock _finder; + private readonly Mock _urlPlaceholderFinder; + private readonly Mock _headerPlaceholderFinder; private string _upstreamUrlPath; private Response _result; private List _routesConfig; @@ -27,10 +28,11 @@ public class DownstreamRouteFinderTests : UnitTest public DownstreamRouteFinderTests() { - _mockMatcher = new Mock(); + _mockUrlMatcher = new Mock(); _mockHeadersMatcher = new Mock(); - _finder = new Mock(); - _downstreamRouteFinder = new Ocelot.DownstreamRouteFinder.Finder.DownstreamRouteFinder(_mockMatcher.Object, _finder.Object, _mockHeadersMatcher.Object); + _urlPlaceholderFinder = new Mock(); + _headerPlaceholderFinder = new Mock(); + _downstreamRouteFinder = new Ocelot.DownstreamRouteFinder.Finder.DownstreamRouteFinder(_mockUrlMatcher.Object, _urlPlaceholderFinder.Object, _mockHeadersMatcher.Object, _headerPlaceholderFinder.Object); } [Fact] @@ -41,6 +43,7 @@ public void should_return_highest_priority_when_first() this.Given(x => x.GivenThereIsAnUpstreamUrlPath("someUpstreamPath")) .And(x => x.GivenTheTemplateVariableAndNameFinderReturns( new OkResponse>(new List()))) + .And(x => x.GivenTheHeaderPlaceholderAndNameFinderReturns(new List())) .And(x => x.GivenTheConfigurationIs(new List { new RouteBuilder() @@ -88,6 +91,7 @@ public void should_return_highest_priority_when_lowest() this.Given(x => x.GivenThereIsAnUpstreamUrlPath("someUpstreamPath")) .And(x => x.GivenTheTemplateVariableAndNameFinderReturns( new OkResponse>(new List()))) + .And(x => x.GivenTheHeaderPlaceholderAndNameFinderReturns(new List())) .And(x => x.GivenTheConfigurationIs(new List { new RouteBuilder() @@ -136,6 +140,7 @@ public void should_return_route() .And(x => x.GivenTheTemplateVariableAndNameFinderReturns( new OkResponse>( new List()))) + .And(x => x.GivenTheHeaderPlaceholderAndNameFinderReturns(new List())) .And(x => x.GivenTheConfigurationIs(new List { new RouteBuilder() @@ -179,6 +184,8 @@ public void should_not_append_slash_to_upstream_url_path() .And(x => x.GivenTheTemplateVariableAndNameFinderReturns( new OkResponse>( new List()))) + .And(x => x.GivenTheHeaderPlaceholderAndNameFinderReturns(new List())) + .And(x => x.GivenTheHeaderPlaceholderAndNameFinderReturns(new List())) .And(x => x.GivenTheConfigurationIs(new List { new RouteBuilder() @@ -223,6 +230,7 @@ public void should_return_route_if_upstream_path_and_upstream_template_are_the_s x => x.GivenTheTemplateVariableAndNameFinderReturns( new OkResponse>(new List()))) + .And(x => x.GivenTheHeaderPlaceholderAndNameFinderReturns(new List())) .And(x => x.GivenTheConfigurationIs(new List { new RouteBuilder() @@ -265,6 +273,7 @@ public void should_return_correct_route_for_http_verb() x => x.GivenTheTemplateVariableAndNameFinderReturns( new OkResponse>(new List()))) + .And(x => x.GivenTheHeaderPlaceholderAndNameFinderReturns(new List())) .And(x => x.GivenTheConfigurationIs(new List { new RouteBuilder() @@ -345,6 +354,7 @@ public void should_return_correct_route_for_http_verb_setting_multiple_upstream_ x => x.GivenTheTemplateVariableAndNameFinderReturns( new OkResponse>(new List()))) + .And(x => x.GivenTheHeaderPlaceholderAndNameFinderReturns(new List())) .And(x => x.GivenTheConfigurationIs(new List { new RouteBuilder() @@ -387,6 +397,7 @@ public void should_return_correct_route_for_http_verb_setting_all_upstream_http_ x => x.GivenTheTemplateVariableAndNameFinderReturns( new OkResponse>(new List()))) + .And(x => x.GivenTheHeaderPlaceholderAndNameFinderReturns(new List())) .And(x => x.GivenTheConfigurationIs(new List { new RouteBuilder() @@ -429,6 +440,7 @@ public void should_not_return_route_for_http_verb_not_setting_in_upstream_http_m x => x.GivenTheTemplateVariableAndNameFinderReturns( new OkResponse>(new List()))) + .And(x => x.GivenTheHeaderPlaceholderAndNameFinderReturns(new List())) .And(x => x.GivenTheConfigurationIs(new List { new RouteBuilder() @@ -461,6 +473,7 @@ public void should_return_route_when_host_matches() .And(x => x.GivenTheTemplateVariableAndNameFinderReturns( new OkResponse>( new List()))) + .And(x => x.GivenTheHeaderPlaceholderAndNameFinderReturns(new List())) .And(x => x.GivenTheConfigurationIs(new List { new RouteBuilder() @@ -506,6 +519,7 @@ public void should_return_route_when_upstreamhost_is_null() .And(x => x.GivenTheTemplateVariableAndNameFinderReturns( new OkResponse>( new List()))) + .And(x => x.GivenTheHeaderPlaceholderAndNameFinderReturns(new List())) .And(x => x.GivenTheConfigurationIs(new List { new RouteBuilder() @@ -548,6 +562,7 @@ public void should_not_return_route_when_host_doesnt_match() this.Given(x => x.GivenThereIsAnUpstreamUrlPath("matchInUrlMatcher/")) .And(x => GivenTheUpstreamHostIs("DONTMATCH")) .And(x => x.GivenTheTemplateVariableAndNameFinderReturns(new OkResponse>(new List()))) + .And(x => x.GivenTheHeaderPlaceholderAndNameFinderReturns(new List())) .And(x => x.GivenTheConfigurationIs(new List { new RouteBuilder() @@ -589,6 +604,7 @@ public void should_not_return_route_when_host_doesnt_match_with_empty_upstream_h this.Given(x => x.GivenThereIsAnUpstreamUrlPath("matchInUrlMatcher/")) .And(x => GivenTheUpstreamHostIs("DONTMATCH")) .And(x => x.GivenTheTemplateVariableAndNameFinderReturns(new OkResponse>(new List()))) + .And(x => x.GivenTheHeaderPlaceholderAndNameFinderReturns(new List())) .And(x => x.GivenTheConfigurationIs(new List { new RouteBuilder() @@ -620,6 +636,7 @@ public void should_return_route_when_host_does_match_with_empty_upstream_http_me this.Given(x => x.GivenThereIsAnUpstreamUrlPath("matchInUrlMatcher/")) .And(x => GivenTheUpstreamHostIs("MATCH")) .And(x => x.GivenTheTemplateVariableAndNameFinderReturns(new OkResponse>(new List()))) + .And(x => x.GivenTheHeaderPlaceholderAndNameFinderReturns(new List())) .And(x => x.GivenTheConfigurationIs(new List { new RouteBuilder() @@ -652,6 +669,7 @@ public void should_return_route_when_host_matches_but_null_host_on_same_path_fir .And(x => x.GivenTheTemplateVariableAndNameFinderReturns( new OkResponse>( new List()))) + .And(x => x.GivenTheHeaderPlaceholderAndNameFinderReturns(new List())) .And(x => x.GivenTheConfigurationIs(new List { new RouteBuilder() @@ -718,6 +736,7 @@ public void should_return_route_when_upstream_headers_match() .And(x => x.GivenTheTemplateVariableAndNameFinderReturns( new OkResponse>( new List()))) + .And(x => x.GivenTheHeaderPlaceholderAndNameFinderReturns(new List())) .And(x => x.GivenTheConfigurationIs(new List { new RouteBuilder() @@ -767,6 +786,7 @@ public void should_not_return_route_when_upstream_headers_dont_match() this.Given(x => x.GivenThereIsAnUpstreamUrlPath("matchInUrlMatcher/")) .And(x => GivenTheUpstreamHeadersIs(new Dictionary() { { "header1", "headerValue1" } })) .And(x => x.GivenTheTemplateVariableAndNameFinderReturns(new OkResponse>(new List()))) + .And(x => x.GivenTheHeaderPlaceholderAndNameFinderReturns(new List())) .And(x => x.GivenTheConfigurationIs(new List { new RouteBuilder() @@ -806,11 +826,18 @@ private void GivenTheUpstreamHostIs(string upstreamHost) private void GivenTheTemplateVariableAndNameFinderReturns(Response> response) { - _finder + _urlPlaceholderFinder .Setup(x => x.Find(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(response); } + private void GivenTheHeaderPlaceholderAndNameFinderReturns(List placeholders) + { + _headerPlaceholderFinder + .Setup(x => x.Find(It.IsAny>(), It.IsAny>())) + .Returns(placeholders); + } + private void GivenTheUpstreamHttpMethodIs(string upstreamHttpMethod) { _upstreamHttpMethod = upstreamHttpMethod; @@ -828,32 +855,32 @@ private void ThenAnErrorResponseIsReturned() private void ThenTheUrlMatcherIsCalledCorrectly() { - _mockMatcher + _mockUrlMatcher .Verify(x => x.Match(_upstreamUrlPath, _upstreamQuery, _routesConfig[0].UpstreamTemplatePattern), Times.Once); } private void ThenTheUrlMatcherIsCalledCorrectly(int times, int index = 0) { - _mockMatcher + _mockUrlMatcher .Verify(x => x.Match(_upstreamUrlPath, _upstreamQuery, _routesConfig[index].UpstreamTemplatePattern), Times.Exactly(times)); } private void ThenTheUrlMatcherIsCalledCorrectly(string expectedUpstreamUrlPath) { - _mockMatcher + _mockUrlMatcher .Verify(x => x.Match(expectedUpstreamUrlPath, _upstreamQuery, _routesConfig[0].UpstreamTemplatePattern), Times.Once); } private void ThenTheUrlMatcherIsNotCalled() { - _mockMatcher + _mockUrlMatcher .Verify(x => x.Match(_upstreamUrlPath, _upstreamQuery, _routesConfig[0].UpstreamTemplatePattern), Times.Never); } private void GivenTheUrlMatcherReturns(Response match) { _match = match; - _mockMatcher + _mockUrlMatcher .Setup(x => x.Match(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(_match); } From a72a3d9d8df95b3d023e285c747aadbc85a7ba48 Mon Sep 17 00:00:00 2001 From: jlukawska Date: Fri, 24 Jul 2020 13:45:02 +0200 Subject: [PATCH 08/49] change header placeholder pattern --- .../UpstreamHeaderTemplatePatternCreator.cs | 4 +- .../RoutingBasedOnHeadersTests.cs | 46 ++++++++++++++++++- 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/src/Ocelot/Configuration/Creator/UpstreamHeaderTemplatePatternCreator.cs b/src/Ocelot/Configuration/Creator/UpstreamHeaderTemplatePatternCreator.cs index 4c8c0f480..08cc958e9 100644 --- a/src/Ocelot/Configuration/Creator/UpstreamHeaderTemplatePatternCreator.cs +++ b/src/Ocelot/Configuration/Creator/UpstreamHeaderTemplatePatternCreator.cs @@ -11,7 +11,7 @@ public class UpstreamHeaderTemplatePatternCreator : IUpstreamHeaderTemplatePatte private const string RegExMatchOneOrMoreOfEverything = ".+"; private const string RegExMatchEndString = "$"; private const string RegExIgnoreCase = "(?i)"; - private const string RegExPlaceholders = @"(\{.*?\})"; + private const string RegExPlaceholders = @"(\{header:.*?\})"; public Dictionary Create(IRoute route) { @@ -34,7 +34,7 @@ public Dictionary Create(IRoute route) { var indexOfPlaceholder = headerTemplateValue.IndexOf(placeholders[i]); - var placeholderName = placeholders[i].Substring(1, placeholders[i].Length - 2); // remove { brackets + var placeholderName = placeholders[i][8..^1]; // remove "{header:" and "}" headerTemplateValue = headerTemplateValue.Replace(placeholders[i], "(?<"+placeholderName+">"+RegExMatchOneOrMoreOfEverything+")"); } diff --git a/test/Ocelot.AcceptanceTests/RoutingBasedOnHeadersTests.cs b/test/Ocelot.AcceptanceTests/RoutingBasedOnHeadersTests.cs index af453f7fd..aef5d3dd7 100644 --- a/test/Ocelot.AcceptanceTests/RoutingBasedOnHeadersTests.cs +++ b/test/Ocelot.AcceptanceTests/RoutingBasedOnHeadersTests.cs @@ -482,7 +482,7 @@ public void should_aggregated_route_not_match_header_value() } [Fact] - public void should_match_one_header_value_with_placeholder() + public void should_match_header_and_url_placeholders() { var port = RandomPortFinder.GetRandomPort(); var headerName = "country_code"; @@ -507,7 +507,7 @@ public void should_match_one_header_value_with_placeholder() UpstreamHttpMethod = new List { "Get" }, UpstreamHeaderTemplates = new Dictionary() { - [headerName] = "start_{country_code}_version_{version}_end", + [headerName] = "start_{header:country_code}_version_{header:version}_end", }, }, }, @@ -523,6 +523,48 @@ public void should_match_one_header_value_with_placeholder() .BDDfy(); } + [Fact] + public void should_match_header_with_braces() + { + var port = RandomPortFinder.GetRandomPort(); + var headerName = "country_code"; + + var configuration = new FileConfiguration + { + Routes = new List + { + new FileRoute + { + DownstreamPathTemplate = "/aa", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = port, + }, + }, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + UpstreamHeaderTemplates = new Dictionary() + { + [headerName] = "my_{header}", + }, + }, + }, + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/aa", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .And(x => _steps.GivenIAddAHeader(headerName, "my_{header}")) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } + private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode, string responseBody) { _serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, basePath, async context => From c2829e7634e13365f0d9715828d9d4d3fdd356d4 Mon Sep 17 00:00:00 2001 From: jlukawska Date: Tue, 28 Jul 2020 13:03:01 +0200 Subject: [PATCH 09/49] unit tests --- .../Finder/DownstreamRouteCreator.cs | 9 +- .../Configuration/AggregatesCreatorTests.cs | 23 ++++- .../Configuration/RoutesCreatorTests.cs | 12 ++- ...streamHeaderTemplatePatternCreatorTests.cs | 90 ++++++++++++++++++- 4 files changed, 117 insertions(+), 17 deletions(-) diff --git a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteCreator.cs b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteCreator.cs index 3df59420e..4e35a676e 100644 --- a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteCreator.cs +++ b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteCreator.cs @@ -43,9 +43,6 @@ public Response Get(string upstreamUrlPath, string upstre var upstreamPathTemplate = new UpstreamPathTemplateBuilder().WithOriginalValue(upstreamUrlPath).Build(); - //TODO - var upstreamHeaderTemplates = new Dictionary(); - var downstreamRouteBuilder = new DownstreamRouteBuilder() .WithServiceName(serviceName) .WithLoadBalancerKey(loadBalancerKey) @@ -56,8 +53,7 @@ public Response Get(string upstreamUrlPath, string upstre .WithDownstreamScheme(configuration.DownstreamScheme) .WithLoadBalancerOptions(configuration.LoadBalancerOptions) .WithDownstreamHttpVersion(configuration.DownstreamHttpVersion) - .WithUpstreamPathTemplate(upstreamPathTemplate) - .WithUpstreamHeaders(upstreamHeaderTemplates); + .WithUpstreamPathTemplate(upstreamPathTemplate); var rateLimitOptions = configuration.Routes?.SelectMany(x => x.DownstreamRoute) .FirstOrDefault(x => x.ServiceName == serviceName); @@ -74,8 +70,7 @@ public Response Get(string upstreamUrlPath, string upstre var route = new RouteBuilder() .WithDownstreamRoute(downstreamRoute) .WithUpstreamHttpMethod(new List { upstreamHttpMethod }) - .WithUpstreamPathTemplate(upstreamPathTemplate) - .WithUpstreamHeaders(upstreamHeaderTemplates) + .WithUpstreamPathTemplate(upstreamPathTemplate) .Build(); downstreamRouteHolder = new OkResponse(new DownstreamRouteHolder(new List(), route)); diff --git a/test/Ocelot.UnitTests/Configuration/AggregatesCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/AggregatesCreatorTests.cs index 997206184..3d4c3ff63 100644 --- a/test/Ocelot.UnitTests/Configuration/AggregatesCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/AggregatesCreatorTests.cs @@ -15,7 +15,9 @@ public class AggregatesCreatorTests : UnitTest private List _routes; private List _result; private UpstreamPathTemplate _aggregate1Utp; - private UpstreamPathTemplate _aggregate2Utp; + private UpstreamPathTemplate _aggregate2Utp; + private Dictionary _headerTemplates1; + private Dictionary _headerTemplates2; public AggregatesCreatorTests() { @@ -84,7 +86,8 @@ public void should_create_aggregates() this.Given(_ => GivenThe(fileConfig)) .And(_ => GivenThe(routes)) - .And(_ => GivenTheUtpCreatorReturns()) + .And(_ => GivenTheUtpCreatorReturns()) + .And(_ => GivenTheUhtpCreatorReturns()) .When(_ => WhenICreate()) .Then(_ => ThenTheUtpCreatorIsCalledCorrectly()) .And(_ => ThenTheAggregatesAreCreated()) @@ -98,14 +101,16 @@ private void ThenTheAggregatesAreCreated() _result[0].UpstreamHttpMethod.ShouldContain(x => x == HttpMethod.Get); _result[0].UpstreamHost.ShouldBe(_fileConfiguration.Aggregates[0].UpstreamHost); - _result[0].UpstreamTemplatePattern.ShouldBe(_aggregate1Utp); + _result[0].UpstreamTemplatePattern.ShouldBe(_aggregate1Utp); + _result[0].UpstreamHeaderTemplates.ShouldBe(_headerTemplates1); _result[0].Aggregator.ShouldBe(_fileConfiguration.Aggregates[0].Aggregator); _result[0].DownstreamRoute.ShouldContain(x => x == _routes[0].DownstreamRoute[0]); _result[0].DownstreamRoute.ShouldContain(x => x == _routes[1].DownstreamRoute[0]); _result[1].UpstreamHttpMethod.ShouldContain(x => x == HttpMethod.Get); _result[1].UpstreamHost.ShouldBe(_fileConfiguration.Aggregates[1].UpstreamHost); - _result[1].UpstreamTemplatePattern.ShouldBe(_aggregate2Utp); + _result[1].UpstreamTemplatePattern.ShouldBe(_aggregate2Utp); + _result[1].UpstreamHeaderTemplates.ShouldBe(_headerTemplates2); _result[1].Aggregator.ShouldBe(_fileConfiguration.Aggregates[1].Aggregator); _result[1].DownstreamRoute.ShouldContain(x => x == _routes[2].DownstreamRoute[0]); _result[1].DownstreamRoute.ShouldContain(x => x == _routes[3].DownstreamRoute[0]); @@ -125,6 +130,16 @@ private void GivenTheUtpCreatorReturns() _utpCreator.SetupSequence(x => x.Create(It.IsAny())) .Returns(_aggregate1Utp) .Returns(_aggregate2Utp); + } + + private void GivenTheUhtpCreatorReturns() + { + _headerTemplates1 = new Dictionary(); + _headerTemplates2 = new Dictionary(); + + _uhtpCreator.SetupSequence(x => x.Create(It.IsAny())) + .Returns(_headerTemplates1) + .Returns(_headerTemplates2); } private void ThenTheResultIsEmpty() diff --git a/test/Ocelot.UnitTests/Configuration/RoutesCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/RoutesCreatorTests.cs index ccf409aee..f21bd6561 100644 --- a/test/Ocelot.UnitTests/Configuration/RoutesCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/RoutesCreatorTests.cs @@ -42,7 +42,8 @@ public class RoutesCreatorTests : UnitTest private LoadBalancerOptions _lbo; private List _result; private SecurityOptions _securityOptions; - private Version _expectedVersion; + private Version _expectedVersion; + private Dictionary _uht; public RoutesCreatorTests() { @@ -169,7 +170,8 @@ private void GivenTheDependenciesAreSetUpCorrectly() _hho = new HttpHandlerOptionsBuilder().Build(); _ht = new HeaderTransformations(new List(), new List(), new List(), new List()); _dhp = new List(); - _lbo = new LoadBalancerOptionsBuilder().Build(); + _lbo = new LoadBalancerOptionsBuilder().Build(); + _uht = new Dictionary(); _rroCreator.Setup(x => x.Create(It.IsAny())).Returns(_rro); _ridkCreator.Setup(x => x.Create(It.IsAny(), It.IsAny())).Returns(_requestId); @@ -184,7 +186,8 @@ private void GivenTheDependenciesAreSetUpCorrectly() _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); + _versionCreator.Setup(x => x.Create(It.IsAny())).Returns(_expectedVersion); + _uhtpCreator.Setup(x => x.Create(It.IsAny())).Returns(_uht); } private void ThenTheRoutesAreCreated() @@ -253,7 +256,8 @@ private void ThenTheRouteIsSet(FileRoute expected, int routeIndex) .ShouldContain(x => x == expected.UpstreamHttpMethod[1]); _result[routeIndex].UpstreamHost.ShouldBe(expected.UpstreamHost); _result[routeIndex].DownstreamRoute.Count.ShouldBe(1); - _result[routeIndex].UpstreamTemplatePattern.ShouldBe(_upt); + _result[routeIndex].UpstreamTemplatePattern.ShouldBe(_upt); + _result[routeIndex].UpstreamHeaderTemplates.ShouldBe(_uht); } private void ThenTheDepsAreCalledFor(FileRoute fileRoute, FileGlobalConfiguration globalConfig) diff --git a/test/Ocelot.UnitTests/Configuration/UpstreamHeaderTemplatePatternCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/UpstreamHeaderTemplatePatternCreatorTests.cs index d99ebb081..630963589 100644 --- a/test/Ocelot.UnitTests/Configuration/UpstreamHeaderTemplatePatternCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/UpstreamHeaderTemplatePatternCreatorTests.cs @@ -22,13 +22,99 @@ public UpstreamHeaderTemplatePatternCreatorTests() } [Fact] - public void should_match_two_placeholders() + public void should_create_pattern_without_placeholders() { var fileRoute = new FileRoute { UpstreamHeaderTemplates = new Dictionary { - ["country"] = "any text {cc} and other {version} and {bob} the end", + ["country"] = "a text without placeholders", + }, + }; + + this.Given(x => x.GivenTheFollowingFileRoute(fileRoute)) + .When(x => x.WhenICreateTheTemplatePattern()) + .Then(x => x.ThenTheFollowingIsReturned("country", "^(?i)a text without placeholders$")) + .BDDfy(); + } + + [Fact] + public void should_create_pattern_case_sensitive() + { + var fileRoute = new FileRoute + { + RouteIsCaseSensitive = true, + UpstreamHeaderTemplates = new Dictionary + { + ["country"] = "a text without placeholders", + }, + }; + + this.Given(x => x.GivenTheFollowingFileRoute(fileRoute)) + .When(x => x.WhenICreateTheTemplatePattern()) + .Then(x => x.ThenTheFollowingIsReturned("country", "^a text without placeholders$")) + .BDDfy(); + } + + [Fact] + public void should_create_pattern_with_placeholder_in_the_beginning() + { + var fileRoute = new FileRoute + { + UpstreamHeaderTemplates = new Dictionary + { + ["country"] = "{header:start}rest of the text", + }, + }; + + this.Given(x => x.GivenTheFollowingFileRoute(fileRoute)) + .When(x => x.WhenICreateTheTemplatePattern()) + .Then(x => x.ThenTheFollowingIsReturned("country", "^(?i)(?.+)rest of the text$")) + .BDDfy(); + } + + [Fact] + public void should_create_pattern_with_placeholder_at_the_end() + { + var fileRoute = new FileRoute + { + UpstreamHeaderTemplates = new Dictionary + { + ["country"] = "rest of the text{header:end}", + }, + }; + + this.Given(x => x.GivenTheFollowingFileRoute(fileRoute)) + .When(x => x.WhenICreateTheTemplatePattern()) + .Then(x => x.ThenTheFollowingIsReturned("country", "^(?i)rest of the text(?.+)$")) + .BDDfy(); + } + + [Fact] + public void should_create_pattern_with_placeholder_only() + { + var fileRoute = new FileRoute + { + UpstreamHeaderTemplates = new Dictionary + { + ["country"] = "{header:countrycode}", + }, + }; + + this.Given(x => x.GivenTheFollowingFileRoute(fileRoute)) + .When(x => x.WhenICreateTheTemplatePattern()) + .Then(x => x.ThenTheFollowingIsReturned("country", "^(?i)(?.+)$")) + .BDDfy(); + } + + [Fact] + public void should_crate_pattern_with_more_placeholders() + { + var fileRoute = new FileRoute + { + UpstreamHeaderTemplates = new Dictionary + { + ["country"] = "any text {header:cc} and other {header:version} and {header:bob} the end", }, }; From e14ce8e4eaf8b426601938c55538fde1f3577602 Mon Sep 17 00:00:00 2001 From: jlukawska Date: Tue, 28 Jul 2020 13:45:00 +0200 Subject: [PATCH 10/49] unit tests --- .../DownstreamRouteFinderTests.cs | 56 ++++++++++--------- 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs index ff5fc78c3..9cf015a2b 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs @@ -8,7 +8,7 @@ using Ocelot.Values; namespace Ocelot.UnitTests.DownstreamRouteFinder -{ +{ public class DownstreamRouteFinderTests : UnitTest { private readonly IDownstreamRouteProvider _downstreamRouteFinder; @@ -65,7 +65,7 @@ public void should_return_highest_priority_when_first() .WithUpstreamPathTemplate(new UpstreamPathTemplate("test", 0, false, "someUpstreamPath")) .Build(), }, string.Empty, serviceProviderConfig)) - .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) + .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) .And(x => x.GivenTheHeadersMatcherReturns(true)) .And(x => x.GivenTheUpstreamHttpMethodIs("Post")) .When(x => x.WhenICallTheFinder()) @@ -113,7 +113,7 @@ public void should_return_highest_priority_when_lowest() .WithUpstreamPathTemplate(new UpstreamPathTemplate("test", 1, false, "someUpstreamPath")) .Build(), }, string.Empty, serviceProviderConfig)) - .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) + .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) .And(x => x.GivenTheHeadersMatcherReturns(true)) .And(x => x.GivenTheUpstreamHttpMethodIs("Post")) .When(x => x.WhenICallTheFinder()) @@ -154,7 +154,7 @@ public void should_return_route() .Build(), }, string.Empty, serviceProviderConfig )) - .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) + .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) .And(x => x.GivenTheHeadersMatcherReturns(true)) .And(x => x.GivenTheUpstreamHttpMethodIs("Get")) .When(x => x.WhenICallTheFinder()) @@ -199,7 +199,7 @@ public void should_not_append_slash_to_upstream_url_path() .Build(), }, string.Empty, serviceProviderConfig )) - .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) + .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) .And(x => x.GivenTheHeadersMatcherReturns(true)) .And(x => x.GivenTheUpstreamHttpMethodIs("Get")) .When(x => x.WhenICallTheFinder()) @@ -244,7 +244,7 @@ public void should_return_route_if_upstream_path_and_upstream_template_are_the_s .Build(), }, string.Empty, serviceProviderConfig )) - .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) + .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) .And(x => x.GivenTheHeadersMatcherReturns(true)) .And(x => x.GivenTheUpstreamHttpMethodIs("Get")) .When(x => x.WhenICallTheFinder()) @@ -296,7 +296,7 @@ public void should_return_correct_route_for_http_verb() .Build(), }, string.Empty, serviceProviderConfig )) - .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) + .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) .And(x => x.GivenTheHeadersMatcherReturns(true)) .And(x => x.GivenTheUpstreamHttpMethodIs("Post")) .When(x => x.WhenICallTheFinder()) @@ -334,7 +334,7 @@ public void should_not_return_route() .Build(), }, string.Empty, serviceProviderConfig )) - .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(false)))) + .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(false)))) .And(x => x.GivenTheHeadersMatcherReturns(true)) .And(x => x.GivenTheUpstreamHttpMethodIs("Get")) .When(x => x.WhenICallTheFinder()) @@ -368,7 +368,7 @@ public void should_return_correct_route_for_http_verb_setting_multiple_upstream_ .Build(), }, string.Empty, serviceProviderConfig )) - .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) + .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) .And(x => x.GivenTheHeadersMatcherReturns(true)) .And(x => x.GivenTheUpstreamHttpMethodIs("Post")) .When(x => x.WhenICallTheFinder()) @@ -411,7 +411,7 @@ public void should_return_correct_route_for_http_verb_setting_all_upstream_http_ .Build(), }, string.Empty, serviceProviderConfig )) - .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) + .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) .And(x => x.GivenTheHeadersMatcherReturns(true)) .And(x => x.GivenTheUpstreamHttpMethodIs("Post")) .When(x => x.WhenICallTheFinder()) @@ -454,7 +454,7 @@ public void should_not_return_route_for_http_verb_not_setting_in_upstream_http_m .Build(), }, string.Empty, serviceProviderConfig )) - .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) + .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) .And(x => x.GivenTheHeadersMatcherReturns(true)) .And(x => x.GivenTheUpstreamHttpMethodIs("Post")) .When(x => x.WhenICallTheFinder()) @@ -488,7 +488,7 @@ public void should_return_route_when_host_matches() .Build(), }, string.Empty, serviceProviderConfig )) - .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) + .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) .And(x => x.GivenTheHeadersMatcherReturns(true)) .And(x => x.GivenTheUpstreamHttpMethodIs("Get")) .When(x => x.WhenICallTheFinder()) @@ -533,7 +533,7 @@ public void should_return_route_when_upstreamhost_is_null() .Build(), }, string.Empty, serviceProviderConfig )) - .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) + .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) .And(x => x.GivenTheHeadersMatcherReturns(true)) .And(x => x.GivenTheUpstreamHttpMethodIs("Get")) .When(x => x.WhenICallTheFinder()) @@ -587,7 +587,7 @@ public void should_not_return_route_when_host_doesnt_match() .Build(), }, string.Empty, serviceProviderConfig )) - .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) + .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) .And(x => x.GivenTheHeadersMatcherReturns(true)) .And(x => x.GivenTheUpstreamHttpMethodIs("Get")) .When(x => x.WhenICallTheFinder()) @@ -619,7 +619,7 @@ public void should_not_return_route_when_host_doesnt_match_with_empty_upstream_h .Build(), }, string.Empty, serviceProviderConfig )) - .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) + .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) .And(x => x.GivenTheHeadersMatcherReturns(true)) .And(x => x.GivenTheUpstreamHttpMethodIs("Get")) .When(x => x.WhenICallTheFinder()) @@ -651,7 +651,7 @@ public void should_return_route_when_host_does_match_with_empty_upstream_http_me .Build(), }, string.Empty, serviceProviderConfig )) - .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) + .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) .And(x => x.GivenTheHeadersMatcherReturns(true)) .And(x => x.GivenTheUpstreamHttpMethodIs("Get")) .When(x => x.WhenICallTheFinder()) @@ -693,7 +693,7 @@ public void should_return_route_when_host_matches_but_null_host_on_same_path_fir .Build(), }, string.Empty, serviceProviderConfig )) - .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) + .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) .And(x => x.GivenTheHeadersMatcherReturns(true)) .And(x => x.GivenTheUpstreamHttpMethodIs("Get")) .When(x => x.WhenICallTheFinder()) @@ -724,19 +724,21 @@ public void should_return_route_when_upstream_headers_match() { ["header1"] = "headerValue1", ["header2"] = "headerValue2", - }; + ["header3"] = "headerValue3", + }; var upstreamHeadersConfig = new Dictionary() { ["header1"] = new UpstreamHeaderTemplate("headerValue1", "headerValue1"), ["header2"] = new UpstreamHeaderTemplate("headerValue2", "headerValue2"), }; + var urlPlaceholders = new List { new PlaceholderNameAndValue("url", "urlValue") }; + var headerPlaceholders = new List { new PlaceholderNameAndValue("header", "headerValue") }; this.Given(x => x.GivenThereIsAnUpstreamUrlPath("matchInUrlMatcher/")) .And(x => GivenTheUpstreamHeadersIs(upstreamHeaders)) .And(x => x.GivenTheTemplateVariableAndNameFinderReturns( - new OkResponse>( - new List()))) - .And(x => x.GivenTheHeaderPlaceholderAndNameFinderReturns(new List())) + new OkResponse>(urlPlaceholders))) + .And(x => x.GivenTheHeaderPlaceholderAndNameFinderReturns(headerPlaceholders)) .And(x => x.GivenTheConfigurationIs(new List { new RouteBuilder() @@ -751,13 +753,13 @@ public void should_return_route_when_upstream_headers_match() .Build(), }, string.Empty, serviceProviderConfig )) - .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) + .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) .And(x => x.GivenTheHeadersMatcherReturns(true)) .And(x => x.GivenTheUpstreamHttpMethodIs("Get")) .When(x => x.WhenICallTheFinder()) .Then( x => x.ThenTheFollowingIsReturned(new DownstreamRouteHolder( - new List(), + urlPlaceholders.Union(headerPlaceholders).ToList(), new RouteBuilder() .WithDownstreamRoute(new DownstreamRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") @@ -811,14 +813,14 @@ public void should_not_return_route_when_upstream_headers_dont_match() .Build(), }, string.Empty, serviceProviderConfig )) - .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) + .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) .And(x => x.GivenTheHeadersMatcherReturns(false)) .And(x => x.GivenTheUpstreamHttpMethodIs("Get")) .When(x => x.WhenICallTheFinder()) .Then(x => x.ThenAnErrorResponseIsReturned()) .BDDfy(); } - + private void GivenTheUpstreamHostIs(string upstreamHost) { _upstreamHost = upstreamHost; @@ -883,8 +885,8 @@ private void GivenTheUrlMatcherReturns(Response match) _mockUrlMatcher .Setup(x => x.Match(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(_match); - } - + } + private void GivenTheHeadersMatcherReturns(bool headersMatch) { _mockHeadersMatcher From 4ad836ae0eb64e95d885a33a11c7a6376d7fd6cc Mon Sep 17 00:00:00 2001 From: jlukawska Date: Wed, 29 Jul 2020 13:36:57 +0200 Subject: [PATCH 11/49] unit tests --- .../HeaderPlaceholderNameAndValueFinder.cs | 2 +- ...eaderPlaceholderNameAndValueFinderTests.cs | 204 ++++++++++++++++++ 2 files changed, 205 insertions(+), 1 deletion(-) create mode 100644 test/Ocelot.UnitTests/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinderTests.cs diff --git a/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinder.cs b/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinder.cs index bdd93395b..ea1ce8b03 100644 --- a/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinder.cs +++ b/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinder.cs @@ -18,7 +18,7 @@ public List Find(Dictionary upstreamHea { var upstreamHeader = upstreamHeaders[templateHeader.Key]; var matches = templateHeader.Value.Pattern.Matches(upstreamHeader); - var placeholders = matches.SelectMany(g => g.Groups as IEnumerable).Where(g => g.Index != 0) + var placeholders = matches.SelectMany(g => g.Groups as IEnumerable).Where(g => g.Name != "0") .Select(g => new PlaceholderNameAndValue("{"+g.Name+"}", g.Value)); placeholderNameAndValuesList.AddRange(placeholders); } diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinderTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinderTests.cs new file mode 100644 index 000000000..64d2fc1f3 --- /dev/null +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinderTests.cs @@ -0,0 +1,204 @@ +using Ocelot.DownstreamRouteFinder.HeaderMatcher; +using Ocelot.DownstreamRouteFinder.UrlMatcher; +using Ocelot.Values; +using Shouldly; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.DownstreamRouteFinder.HeaderMatcher +{ + public class HeaderPlaceholderNameAndValueFinderTests + { + private readonly IHeaderPlaceholderNameAndValueFinder _finder; + private Dictionary _upstreamHeaders; + private Dictionary _upstreamHeaderTemplates; + private List _result; + + public HeaderPlaceholderNameAndValueFinderTests() + { + _finder = new HeaderPlaceholderNameAndValueFinder(); + } + + [Fact] + public void should_return_no_placeholders() + { + var upstreamHeaderTemplates = new Dictionary(); + var upstreamHeaders = new Dictionary(); + var expected = new List(); + + this.Given(x => x.GivenUpstreamHeaderTemplatesAre(upstreamHeaderTemplates)) + .And(x => x.GivenUpstreamHeadersAre(upstreamHeaders)) + .When(x => x.WhenICallFindPlaceholders()) + .Then(x => x.TheResultIs(expected)) + .BDDfy(); + } + + [Fact] + public void should_return_one_placeholder_with_value_when_no_other_text() + { + var upstreamHeaderTemplates = new Dictionary + { + ["country"] = new UpstreamHeaderTemplate("^(?i)(?.+)$", "{jeader:countrycode}"), + }; + var upstreamHeaders = new Dictionary + { + ["country"] = "PL", + }; + var expected = new List + { + new PlaceholderNameAndValue("{countrycode}", "PL"), + }; + + this.Given(x => x.GivenUpstreamHeaderTemplatesAre(upstreamHeaderTemplates)) + .And(x => x.GivenUpstreamHeadersAre(upstreamHeaders)) + .When(x => x.WhenICallFindPlaceholders()) + .Then(x => x.TheResultIs(expected)) + .BDDfy(); + } + + [Fact] + public void should_return_one_placeholder_with_value_when_other_text_on_the_right() + { + var upstreamHeaderTemplates = new Dictionary + { + ["country"] = new UpstreamHeaderTemplate("^(?.+)-V1$", "{header:countrycode}-V1"), + }; + var upstreamHeaders = new Dictionary + { + ["country"] = "PL-V1", + }; + var expected = new List + { + new PlaceholderNameAndValue("{countrycode}", "PL"), + }; + + this.Given(x => x.GivenUpstreamHeaderTemplatesAre(upstreamHeaderTemplates)) + .And(x => x.GivenUpstreamHeadersAre(upstreamHeaders)) + .When(x => x.WhenICallFindPlaceholders()) + .Then(x => x.TheResultIs(expected)) + .BDDfy(); + } + + [Fact] + public void should_return_one_placeholder_with_value_when_other_text_on_the_left() + { + var upstreamHeaderTemplates = new Dictionary + { + ["country"] = new UpstreamHeaderTemplate("^V1-(?.+)$", "V1-{header:countrycode}"), + }; + var upstreamHeaders = new Dictionary + { + ["country"] = "V1-PL", + }; + var expected = new List + { + new PlaceholderNameAndValue("{countrycode}", "PL"), + }; + + this.Given(x => x.GivenUpstreamHeaderTemplatesAre(upstreamHeaderTemplates)) + .And(x => x.GivenUpstreamHeadersAre(upstreamHeaders)) + .When(x => x.WhenICallFindPlaceholders()) + .Then(x => x.TheResultIs(expected)) + .BDDfy(); + } + + [Fact] + public void should_return_one_placeholder_with_value_when_other_texts_surrounding() + { + var upstreamHeaderTemplates = new Dictionary + { + ["country"] = new UpstreamHeaderTemplate("^cc:(?.+)-V1$", "cc:{header:countrycode}-V1"), + }; + var upstreamHeaders = new Dictionary + { + ["country"] = "cc:PL-V1", + }; + var expected = new List + { + new PlaceholderNameAndValue("{countrycode}", "PL"), + }; + + this.Given(x => x.GivenUpstreamHeaderTemplatesAre(upstreamHeaderTemplates)) + .And(x => x.GivenUpstreamHeadersAre(upstreamHeaders)) + .When(x => x.WhenICallFindPlaceholders()) + .Then(x => x.TheResultIs(expected)) + .BDDfy(); + } + + [Fact] + public void should_return_two_placeholders_with_text_between() + { + var upstreamHeaderTemplates = new Dictionary + { + ["countryAndVersion"] = new UpstreamHeaderTemplate("^(?i)(?.+)-(?.+)$", "{header:countrycode}-{header:version}"), + }; + var upstreamHeaders = new Dictionary + { + ["countryAndVersion"] = "PL-v1", + }; + var expected = new List + { + new PlaceholderNameAndValue("{countrycode}", "PL"), + new PlaceholderNameAndValue("{version}", "v1"), + }; + + this.Given(x => x.GivenUpstreamHeaderTemplatesAre(upstreamHeaderTemplates)) + .And(x => x.GivenUpstreamHeadersAre(upstreamHeaders)) + .When(x => x.WhenICallFindPlaceholders()) + .Then(x => x.TheResultIs(expected)) + .BDDfy(); + } + + [Fact] + public void should_return_placeholders_from_different_headers() + { + var upstreamHeaderTemplates = new Dictionary + { + ["country"] = new UpstreamHeaderTemplate("^(?i)(?.+)$", "{header:countrycode}"), + ["version"] = new UpstreamHeaderTemplate("^(?i)(?.+)$", "{header:version}"), + }; + var upstreamHeaders = new Dictionary + { + ["country"] = "PL", + ["version"] = "v1", + }; + var expected = new List + { + new PlaceholderNameAndValue("{countrycode}", "PL"), + new PlaceholderNameAndValue("{version}", "v1"), + }; + + this.Given(x => x.GivenUpstreamHeaderTemplatesAre(upstreamHeaderTemplates)) + .And(x => x.GivenUpstreamHeadersAre(upstreamHeaders)) + .When(x => x.WhenICallFindPlaceholders()) + .Then(x => x.TheResultIs(expected)) + .BDDfy(); + } + + private void GivenUpstreamHeaderTemplatesAre(Dictionary upstreaHeaderTemplates) + { + _upstreamHeaderTemplates = upstreaHeaderTemplates; + } + + private void GivenUpstreamHeadersAre(Dictionary upstreamHeaders) + { + _upstreamHeaders = upstreamHeaders; + } + + private void WhenICallFindPlaceholders() + { + _result = _finder.Find(_upstreamHeaders, _upstreamHeaderTemplates); + } + + private void TheResultIs(List expected) + { + _result.ShouldNotBeNull(); + _result.Count.ShouldBe(expected.Count); + _result.ForEach(x => expected.Any(e => e.Name == x.Name && e.Value == x.Value).ShouldBeTrue()); + } + } +} From 565e1c21626e3f337ed9facabf20aee5e876e3f4 Mon Sep 17 00:00:00 2001 From: jlukawska Date: Mon, 3 Aug 2020 13:07:34 +0200 Subject: [PATCH 12/49] unit tests --- ...eaderPlaceholderNameAndValueFinderTests.cs | 2 +- .../HeadersToHeaderTemplatesMatcherTests.cs | 277 ++++++++++++++++++ 2 files changed, 278 insertions(+), 1 deletion(-) create mode 100644 test/Ocelot.UnitTests/DownstreamRouteFinder/HeaderMatcher/HeadersToHeaderTemplatesMatcherTests.cs diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinderTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinderTests.cs index 64d2fc1f3..cc6c26173 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinderTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinderTests.cs @@ -42,7 +42,7 @@ public void should_return_one_placeholder_with_value_when_no_other_text() { var upstreamHeaderTemplates = new Dictionary { - ["country"] = new UpstreamHeaderTemplate("^(?i)(?.+)$", "{jeader:countrycode}"), + ["country"] = new UpstreamHeaderTemplate("^(?i)(?.+)$", "{header:countrycode}"), }; var upstreamHeaders = new Dictionary { diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/HeaderMatcher/HeadersToHeaderTemplatesMatcherTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/HeaderMatcher/HeadersToHeaderTemplatesMatcherTests.cs new file mode 100644 index 000000000..73497e87c --- /dev/null +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/HeaderMatcher/HeadersToHeaderTemplatesMatcherTests.cs @@ -0,0 +1,277 @@ +using Ocelot.DownstreamRouteFinder.HeaderMatcher; +using Ocelot.Values; +using Shouldly; +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Text; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.DownstreamRouteFinder.HeaderMatcher +{ + public class HeadersToHeaderTemplatesMatcherTests + { + private readonly IHeadersToHeaderTemplatesMatcher _headerMatcher; + private Dictionary _upstreamHeaders; + private Dictionary _templateHeaders; + private bool _result; + + public HeadersToHeaderTemplatesMatcherTests() + { + _headerMatcher = new HeadersToHeaderTemplatesMatcher(); + } + + [Fact] + public void should_match_when_no_template_headers() + { + var upstreamHeaders = new Dictionary() + { + ["anyHeader"] = "anyHeaderValue", + }; + + var templateHeaders = new Dictionary(); + + this.Given(x => x.GivenIHaveUpstreamHeaders(upstreamHeaders)) + .And(x => x.GivenIHaveTemplateHeadersInRoute(templateHeaders)) + .When(x => x.WhenIMatchTheHeaders()) + .Then(x => x.ThenTheResultIsTrue()) + .BDDfy(); + } + + [Fact] + public void should_match_the_same_headers() + { + var upstreamHeaders = new Dictionary() + { + ["anyHeader"] = "anyHeaderValue", + }; + + var templateHeaders = new Dictionary() + { + ["anyHeader"] = new UpstreamHeaderTemplate("^(?i)anyHeaderValue$", "anyHeaderValue"), + }; + + this.Given(x => x.GivenIHaveUpstreamHeaders(upstreamHeaders)) + .And(x => x.GivenIHaveTemplateHeadersInRoute(templateHeaders)) + .When(x => x.WhenIMatchTheHeaders()) + .Then(x => x.ThenTheResultIsTrue()) + .BDDfy(); + } + + [Fact] + public void should_not_match_the_same_headers_when_differ_case_and_case_sensitive() + { + var upstreamHeaders = new Dictionary() + { + ["anyHeader"] = "ANYHEADERVALUE", + }; + + var templateHeaders = new Dictionary() + { + ["anyHeader"] = new UpstreamHeaderTemplate("^anyHeaderValue$", "anyHeaderValue"), + }; + + this.Given(x => x.GivenIHaveUpstreamHeaders(upstreamHeaders)) + .And(x => x.GivenIHaveTemplateHeadersInRoute(templateHeaders)) + .When(x => x.WhenIMatchTheHeaders()) + .Then(x => x.ThenTheResultIsFalse()) + .BDDfy(); + } + + [Fact] + public void should_match_the_same_headers_when_differ_case_and_case_insensitive() + { + var upstreamHeaders = new Dictionary() + { + ["anyHeader"] = "ANYHEADERVALUE", + }; + + var templateHeaders = new Dictionary() + { + ["anyHeader"] = new UpstreamHeaderTemplate("^(?i)anyHeaderValue$", "anyHeaderValue"), + }; + + this.Given(x => x.GivenIHaveUpstreamHeaders(upstreamHeaders)) + .And(x => x.GivenIHaveTemplateHeadersInRoute(templateHeaders)) + .When(x => x.WhenIMatchTheHeaders()) + .Then(x => x.ThenTheResultIsTrue()) + .BDDfy(); + } + + [Fact] + public void should_not_match_different_headers_values() + { + var upstreamHeaders = new Dictionary() + { + ["anyHeader"] = "anyHeaderValueDifferent", + }; + + var templateHeaders = new Dictionary() + { + ["anyHeader"] = new UpstreamHeaderTemplate("^(?i)anyHeaderValue$", "anyHeaderValue"), + }; + + this.Given(x => x.GivenIHaveUpstreamHeaders(upstreamHeaders)) + .And(x => x.GivenIHaveTemplateHeadersInRoute(templateHeaders)) + .When(x => x.WhenIMatchTheHeaders()) + .Then(x => x.ThenTheResultIsFalse()) + .BDDfy(); + } + + [Fact] + public void should_not_match_the_same_headers_names() + { + var upstreamHeaders = new Dictionary() + { + ["anyHeaderDifferent"] = "anyHeaderValue", + }; + + var templateHeaders = new Dictionary() + { + ["anyHeader"] = new UpstreamHeaderTemplate("^(?i)anyHeaderValue$", "anyHeaderValue"), + }; + + this.Given(x => x.GivenIHaveUpstreamHeaders(upstreamHeaders)) + .And(x => x.GivenIHaveTemplateHeadersInRoute(templateHeaders)) + .When(x => x.WhenIMatchTheHeaders()) + .Then(x => x.ThenTheResultIsFalse()) + .BDDfy(); + } + + [Fact] + public void should_match_all_the_same_headers() + { + var upstreamHeaders = new Dictionary() + { + ["anyHeader"] = "anyHeaderValue", + ["notNeededHeader"] = "notNeededHeaderValue", + ["secondHeader"] = "secondHeaderValue", + ["thirdHeader"] = "thirdHeaderValue", + }; + + var templateHeaders = new Dictionary() + { + ["secondHeader"] = new UpstreamHeaderTemplate("^(?i)secondHeaderValue$", "secondHeaderValue"), + ["thirdHeader"] = new UpstreamHeaderTemplate("^(?i)thirdHeaderValue$", "thirdHeaderValue"), + ["anyHeader"] = new UpstreamHeaderTemplate("^(?i)anyHeaderValue$", "anyHeaderValue"), + }; + + this.Given(x => x.GivenIHaveUpstreamHeaders(upstreamHeaders)) + .And(x => x.GivenIHaveTemplateHeadersInRoute(templateHeaders)) + .When(x => x.WhenIMatchTheHeaders()) + .Then(x => x.ThenTheResultIsTrue()) + .BDDfy(); + } + + [Fact] + public void should_not_match_the_headers_when_one_of_them_different() + { + var upstreamHeaders = new Dictionary() + { + ["anyHeader"] = "anyHeaderValue", + ["notNeededHeader"] = "notNeededHeaderValue", + ["secondHeader"] = "secondHeaderValueDIFFERENT", + ["thirdHeader"] = "thirdHeaderValue", + }; + + var templateHeaders = new Dictionary() + { + ["secondHeader"] = new UpstreamHeaderTemplate("^(?i)secondHeaderValue$", "secondHeaderValue"), + ["thirdHeader"] = new UpstreamHeaderTemplate("^(?i)thirdHeaderValue$", "thirdHeaderValue"), + ["anyHeader"] = new UpstreamHeaderTemplate("^(?i)anyHeaderValue$", "anyHeaderValue"), + }; + + this.Given(x => x.GivenIHaveUpstreamHeaders(upstreamHeaders)) + .And(x => x.GivenIHaveTemplateHeadersInRoute(templateHeaders)) + .When(x => x.WhenIMatchTheHeaders()) + .Then(x => x.ThenTheResultIsFalse()) + .BDDfy(); + } + + [Fact] + public void should_match_the_header_with_placeholder() + { + var upstreamHeaders = new Dictionary() + { + ["anyHeader"] = "PL", + }; + + var templateHeaders = new Dictionary() + { + ["anyHeader"] = new UpstreamHeaderTemplate("^(?i)(?.+)$", "{header:countrycode}"), + }; + + this.Given(x => x.GivenIHaveUpstreamHeaders(upstreamHeaders)) + .And(x => x.GivenIHaveTemplateHeadersInRoute(templateHeaders)) + .When(x => x.WhenIMatchTheHeaders()) + .Then(x => x.ThenTheResultIsTrue()) + .BDDfy(); + } + + [Fact] + public void should_match_the_header_with_placeholders() + { + var upstreamHeaders = new Dictionary() + { + ["anyHeader"] = "PL-V1", + }; + + var templateHeaders = new Dictionary() + { + ["anyHeader"] = new UpstreamHeaderTemplate("^(?i)(?.+)-(?.+)$", "{header:countrycode}-{header:version}"), + }; + + this.Given(x => x.GivenIHaveUpstreamHeaders(upstreamHeaders)) + .And(x => x.GivenIHaveTemplateHeadersInRoute(templateHeaders)) + .When(x => x.WhenIMatchTheHeaders()) + .Then(x => x.ThenTheResultIsTrue()) + .BDDfy(); + } + + [Fact] + public void should_not_match_the_header_with_placeholders() + { + var upstreamHeaders = new Dictionary() + { + ["anyHeader"] = "PL", + }; + + var templateHeaders = new Dictionary() + { + ["anyHeader"] = new UpstreamHeaderTemplate("^(?i)(?.+)-(?.+)$", "{header:countrycode}-{header:version}"), + }; + + this.Given(x => x.GivenIHaveUpstreamHeaders(upstreamHeaders)) + .And(x => x.GivenIHaveTemplateHeadersInRoute(templateHeaders)) + .When(x => x.WhenIMatchTheHeaders()) + .Then(x => x.ThenTheResultIsFalse()) + .BDDfy(); + } + + private void GivenIHaveUpstreamHeaders(Dictionary upstreamHeaders) + { + _upstreamHeaders = upstreamHeaders; + } + + private void GivenIHaveTemplateHeadersInRoute(Dictionary templateHeaders) + { + _templateHeaders = templateHeaders; + } + + private void WhenIMatchTheHeaders() + { + _result = _headerMatcher.Match(_upstreamHeaders, _templateHeaders); + } + + private void ThenTheResultIsTrue() + { + _result.ShouldBeTrue(); + } + + private void ThenTheResultIsFalse() + { + _result.ShouldBeFalse(); + } + } +} From d7976795bd81f43d39f98897d11441ad3bf0762b Mon Sep 17 00:00:00 2001 From: jlukawska Date: Wed, 5 Aug 2020 12:46:38 +0200 Subject: [PATCH 13/49] extend validation with checking upstreamheadertemplates, acceptance tests for cases from the issue --- .../FileConfigurationFluentValidator.cs | 10 +- .../RoutingBasedOnHeadersTests.cs | 141 +++++++++++++ .../FileConfigurationFluentValidatorTests.cs | 195 ++++++++++++++++++ 3 files changed, 345 insertions(+), 1 deletion(-) diff --git a/src/Ocelot/Configuration/Validator/FileConfigurationFluentValidator.cs b/src/Ocelot/Configuration/Validator/FileConfigurationFluentValidator.cs index 7e74251e2..1c1f2f104 100644 --- a/src/Ocelot/Configuration/Validator/FileConfigurationFluentValidator.cs +++ b/src/Ocelot/Configuration/Validator/FileConfigurationFluentValidator.cs @@ -128,7 +128,8 @@ private static bool IsNotDuplicateIn(FileRoute route, { var matchingRoutes = routes .Where(r => r.UpstreamPathTemplate == route.UpstreamPathTemplate - && r.UpstreamHost == route.UpstreamHost) + && r.UpstreamHost == route.UpstreamHost + && IsUpstreamHeaderTemplatesTheSame(r.UpstreamHeaderTemplates, route.UpstreamHeaderTemplates)) .ToList(); if (matchingRoutes.Count == 1) @@ -150,6 +151,13 @@ private static bool IsNotDuplicateIn(FileRoute route, } return true; + } + + private static bool IsUpstreamHeaderTemplatesTheSame(Dictionary upstreamHeaderTemplates, + Dictionary otherHeaderTemplates) + { + return upstreamHeaderTemplates.Count == otherHeaderTemplates.Count && + upstreamHeaderTemplates.All(x => otherHeaderTemplates.ContainsKey(x.Key) && otherHeaderTemplates[x.Key] == x.Value); } private static bool IsNotDuplicateIn(FileRoute route, diff --git a/test/Ocelot.AcceptanceTests/RoutingBasedOnHeadersTests.cs b/test/Ocelot.AcceptanceTests/RoutingBasedOnHeadersTests.cs index aef5d3dd7..d61ee1509 100644 --- a/test/Ocelot.AcceptanceTests/RoutingBasedOnHeadersTests.cs +++ b/test/Ocelot.AcceptanceTests/RoutingBasedOnHeadersTests.cs @@ -481,6 +481,147 @@ public void should_aggregated_route_not_match_header_value() .BDDfy(); } + [Fact] + public void should_match_header_placeholder() + { + var port = RandomPortFinder.GetRandomPort(); + var headerName = "Region"; + + var configuration = new FileConfiguration + { + Routes = new List + { + new FileRoute + { + DownstreamPathTemplate = "/api.internal-{code}/products", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = port, + }, + }, + UpstreamPathTemplate = "/products", + UpstreamHttpMethod = new List { "Get" }, + UpstreamHeaderTemplates = new Dictionary() + { + [headerName] = "{header:code}", + }, + }, + }, + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/api.internal-uk/products", 200, "Hello from UK")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .And(x => _steps.GivenIAddAHeader(headerName, "uk")) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/products")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from UK")) + .BDDfy(); + } + + [Fact] + public void should_match_header_placeholder_not_in_downstream_path() + { + var port = RandomPortFinder.GetRandomPort(); + var headerName = "ProductName"; + + var configuration = new FileConfiguration + { + Routes = new List + { + new FileRoute + { + DownstreamPathTemplate = "/products-info", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = port, + }, + }, + UpstreamPathTemplate = "/products", + UpstreamHttpMethod = new List { "Get" }, + UpstreamHeaderTemplates = new Dictionary() + { + [headerName] = "product-{header:everything}", + }, + }, + }, + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/products-info", 200, "Hello from products")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .And(x => _steps.GivenIAddAHeader(headerName, "product-Camera")) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/products")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from products")) + .BDDfy(); + } + + [Fact] + public void should_distinguish_route_for_different_roles() + { + var port = RandomPortFinder.GetRandomPort(); + var headerName = "Origin"; + + var configuration = new FileConfiguration + { + Routes = new List + { + new FileRoute + { + DownstreamPathTemplate = "/products-admin", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = port, + }, + }, + UpstreamPathTemplate = "/products", + UpstreamHttpMethod = new List { "Get" }, + UpstreamHeaderTemplates = new Dictionary() + { + [headerName] = "admin.xxx.com", + }, + }, + new FileRoute + { + DownstreamPathTemplate = "/products", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = port, + }, + }, + UpstreamPathTemplate = "/products", + UpstreamHttpMethod = new List { "Get" }, + }, + }, + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/products-admin", 200, "Hello from products admin")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .And(x => _steps.GivenIAddAHeader(headerName, "admin.xxx.com")) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/products")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from products admin")) + .BDDfy(); + } + [Fact] public void should_match_header_and_url_placeholders() { diff --git a/test/Ocelot.UnitTests/Configuration/Validation/FileConfigurationFluentValidatorTests.cs b/test/Ocelot.UnitTests/Configuration/Validation/FileConfigurationFluentValidatorTests.cs index 3a87dec2f..3ee3983d7 100644 --- a/test/Ocelot.UnitTests/Configuration/Validation/FileConfigurationFluentValidatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/Validation/FileConfigurationFluentValidatorTests.cs @@ -718,6 +718,201 @@ public void Configuration_is_not_valid_when_host_and_port_is_empty() .Then(x => x.ThenTheResultIsNotValid()) .And(x => x.ThenTheErrorMessageAtPositionIs(0, "When not using service discovery Host must be set on DownstreamHostAndPorts if you are not using Route.Host or Ocelot cannot find your service!")) .BDDfy(); + } + + [Fact] + public void configuration_is_not_valid_when_upstream_headers_the_same() + { + this.Given(x => x.GivenAConfiguration(new FileConfiguration + { + Routes = new List + { + new FileRoute + { + DownstreamPathTemplate = "/api/products/", + UpstreamPathTemplate = "/asdf/", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "bbc.co.uk", + }, + }, + UpstreamHttpMethod = new List {"Get"}, + UpstreamHeaderTemplates = new Dictionary + { + { "header1", "value1" }, + { "header2", "value2" }, + }, + }, + new FileRoute + { + DownstreamPathTemplate = "/www/test/", + UpstreamPathTemplate = "/asdf/", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "bbc.co.uk", + }, + }, + UpstreamHttpMethod = new List {"Get"}, + UpstreamHeaderTemplates = new Dictionary + { + { "header2", "value2" }, + { "header1", "value1" }, + }, + }, + }, + })) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsNotValid()) + .And(x => x.ThenTheErrorMessageAtPositionIs(0, "route /asdf/ has duplicate")) + .BDDfy(); + } + + [Fact] + public void configuration_is_valid_when_upstream_headers_not_the_same() + { + this.Given(x => x.GivenAConfiguration(new FileConfiguration + { + Routes = new List + { + new FileRoute + { + DownstreamPathTemplate = "/api/products/", + UpstreamPathTemplate = "/asdf/", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "bbc.co.uk", + }, + }, + UpstreamHttpMethod = new List {"Get"}, + UpstreamHeaderTemplates = new Dictionary + { + { "header1", "value1" }, + { "header2", "value2" }, + }, + }, + new FileRoute + { + DownstreamPathTemplate = "/www/test/", + UpstreamPathTemplate = "/asdf/", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "bbc.co.uk", + }, + }, + UpstreamHttpMethod = new List {"Get"}, + UpstreamHeaderTemplates = new Dictionary + { + { "header2", "value2" }, + { "header1", "valueDIFFERENT" }, + }, + }, + }, + })) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsValid()) + .BDDfy(); + } + + [Fact] + public void configuration_is_valid_when_upstream_headers_count_not_the_same() + { + this.Given(x => x.GivenAConfiguration(new FileConfiguration + { + Routes = new List + { + new FileRoute + { + DownstreamPathTemplate = "/api/products/", + UpstreamPathTemplate = "/asdf/", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "bbc.co.uk", + }, + }, + UpstreamHttpMethod = new List {"Get"}, + UpstreamHeaderTemplates = new Dictionary + { + { "header1", "value1" }, + { "header2", "value2" }, + }, + }, + new FileRoute + { + DownstreamPathTemplate = "/www/test/", + UpstreamPathTemplate = "/asdf/", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "bbc.co.uk", + }, + }, + UpstreamHttpMethod = new List {"Get"}, + UpstreamHeaderTemplates = new Dictionary + { + { "header2", "value2" }, + }, + }, + }, + })) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsValid()) + .BDDfy(); + } + + [Fact] + public void configuration_is_valid_when_one_upstream_headers_empty_and_other_not_empty() + { + this.Given(x => x.GivenAConfiguration(new FileConfiguration + { + Routes = new List + { + new FileRoute + { + DownstreamPathTemplate = "/api/products/", + UpstreamPathTemplate = "/asdf/", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "bbc.co.uk", + }, + }, + UpstreamHttpMethod = new List {"Get"}, + UpstreamHeaderTemplates = new Dictionary + { + { "header1", "value1" }, + { "header2", "value2" }, + }, + }, + new FileRoute + { + DownstreamPathTemplate = "/www/test/", + UpstreamPathTemplate = "/asdf/", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "bbc.co.uk", + }, + }, + UpstreamHttpMethod = new List {"Get"}, + }, + }, + })) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsValid()) + .BDDfy(); } [Theory] From 8c9cd40f0c780ca37fd1fc13eb58d1ed6e9bc213 Mon Sep 17 00:00:00 2001 From: jlukawska Date: Thu, 13 Aug 2020 22:42:57 +0200 Subject: [PATCH 14/49] update docs and minor changes --- docs/features/configuration.rst | 1 + docs/features/routing.rst | 56 +++++++++++ .../UpstreamHeaderTemplatePatternCreator.cs | 12 +-- src/Ocelot/Configuration/DownstreamRoute.cs | 2 +- .../Finder/DownstreamRouteFinder.cs | 10 +- .../HeaderPlaceholderNameAndValueFinder.cs | 2 +- .../HeadersToHeaderTemplatesMatcher.cs | 2 +- src/Ocelot/Values/UpstreamHeaderTemplate.cs | 4 +- .../RoutingBasedOnHeadersTests.cs | 92 ++++++++++++++++++- ...streamHeaderTemplatePatternCreatorTests.cs | 5 +- .../FileConfigurationFluentValidatorTests.cs | 6 +- .../DownstreamRouteFinderMiddlewareTests.cs | 2 +- .../DownstreamRouteFinderTests.cs | 4 +- ...eaderPlaceholderNameAndValueFinderTests.cs | 2 +- .../HeadersToHeaderTemplatesMatcherTests.cs | 2 +- 15 files changed, 174 insertions(+), 28 deletions(-) diff --git a/docs/features/configuration.rst b/docs/features/configuration.rst index 552767f49..19d669d38 100644 --- a/docs/features/configuration.rst +++ b/docs/features/configuration.rst @@ -24,6 +24,7 @@ Here is an example Route configuration. You don't need to set all of these thing "UpstreamHttpMethod": [ "Get" ], "DownstreamHttpMethod": "", "DownstreamHttpVersion": "", + "UpstreamHeaderTemplates": {}, "AddHeadersToRequest": {}, "AddClaimsToRequest": {}, "RouteClaimsRequirement": {}, diff --git a/docs/features/routing.rst b/docs/features/routing.rst index 6de03d8e2..2387020ed 100644 --- a/docs/features/routing.rst +++ b/docs/features/routing.rst @@ -154,6 +154,62 @@ The Route above will only be matched when the ``Host`` header value is ``somedom If you do not set **UpstreamHost** on a Route then any ``Host`` header will match it. This means that if you have two Routes that are the same, apart from the **UpstreamHost**, where one is null and the other set Ocelot will favour the one that has been set. +Upstream Headers +^^^^^^^^^^^^^^^^ + +Additionally to routing by UpstreamPathTemplate, you can define UpstreamHeaderTemplates. Then to match a route, all the headers from this section must exist in the request headers. + +The example: + +.. code-block:: json + + { + "DownstreamPathTemplate": "/", + "DownstreamScheme": "https", + "DownstreamHostAndPorts": [ + { + "Host": "10.0.10.1", + "Port": 80, + } + ], + "UpstreamPathTemplate": "/", + "UpstreamHttpMethod": [ "Get" ], + "UpstreamHeaderTemplates": { + "country": "uk", + "version": "v1" + } + } + +In this case the route will be matched only if a request has both headers set to the configured values. + +You can use placeholders in your UpstreamHeaderTemplates in the following way: + +.. code-block:: json + + { + "DownstreamPathTemplate": "/{versionnumber}/api", + "DownstreamScheme": "https", + "DownstreamHostAndPorts": [ + { + "Host": "10.0.10.1", + "Port": 80, + } + ], + "UpstreamPathTemplate": "/api", + "UpstreamHttpMethod": [ "Get" ], + "UpstreamHeaderTemplates": { + "version": "{header:versionnumber}" + } + } + +In this case the whole value for the request header "version" will be placed into the DownstreamPathTemplate. If you need, you can specify more complex upstream header template with placeholders like e.g. "version-{header:version}_country-{header:country}". + +Placeholders do not have to exist in DownstreamPathTemplate. This case may be used to require specific header no matter what the value it contains. + +UpstreamHeaderTemplates section can be used also for Aggregates. + +This feature was requested in `Issue 360 `_ . + Priority -------- diff --git a/src/Ocelot/Configuration/Creator/UpstreamHeaderTemplatePatternCreator.cs b/src/Ocelot/Configuration/Creator/UpstreamHeaderTemplatePatternCreator.cs index 08cc958e9..4a6a5c0cd 100644 --- a/src/Ocelot/Configuration/Creator/UpstreamHeaderTemplatePatternCreator.cs +++ b/src/Ocelot/Configuration/Creator/UpstreamHeaderTemplatePatternCreator.cs @@ -9,7 +9,6 @@ namespace Ocelot.Configuration.Creator public class UpstreamHeaderTemplatePatternCreator : IUpstreamHeaderTemplatePatternCreator { private const string RegExMatchOneOrMoreOfEverything = ".+"; - private const string RegExMatchEndString = "$"; private const string RegExIgnoreCase = "(?i)"; private const string RegExPlaceholders = @"(\{header:.*?\})"; @@ -25,9 +24,10 @@ public Dictionary Create(IRoute route) Regex expression = new Regex(RegExPlaceholders); MatchCollection matches = expression.Matches(headerTemplateValue); + if (matches.Count > 0) { - placeholders.AddRange(matches.Select(m => m.Groups[1].Value)); + placeholders.AddRange(matches.Select(m => m.Groups[1].Value)); } for (int i = 0; i < placeholders.Count; i++) @@ -35,17 +35,17 @@ public Dictionary Create(IRoute route) var indexOfPlaceholder = headerTemplateValue.IndexOf(placeholders[i]); var placeholderName = placeholders[i][8..^1]; // remove "{header:" and "}" - headerTemplateValue = headerTemplateValue.Replace(placeholders[i], "(?<"+placeholderName+">"+RegExMatchOneOrMoreOfEverything+")"); + headerTemplateValue = headerTemplateValue.Replace(placeholders[i], "(?<" + placeholderName + ">" + RegExMatchOneOrMoreOfEverything + ")"); } var template = route.RouteIsCaseSensitive - ? $"^{headerTemplateValue}{RegExMatchEndString}" - : $"^{RegExIgnoreCase}{headerTemplateValue}{RegExMatchEndString}"; + ? $"^{headerTemplateValue}$" + : $"^{RegExIgnoreCase}{headerTemplateValue}$"; resultHeaderTemplates.Add(headerTemplate.Key, new UpstreamHeaderTemplate(template, headerTemplate.Value)); } return resultHeaderTemplates; - } + } } } diff --git a/src/Ocelot/Configuration/DownstreamRoute.cs b/src/Ocelot/Configuration/DownstreamRoute.cs index d3955aafe..5484d4f5f 100644 --- a/src/Ocelot/Configuration/DownstreamRoute.cs +++ b/src/Ocelot/Configuration/DownstreamRoute.cs @@ -112,7 +112,7 @@ public DownstreamRoute( public bool DangerousAcceptAnyServerCertificateValidator { get; } public SecurityOptions SecurityOptions { get; } public string DownstreamHttpMethod { get; } - public Version DownstreamHttpVersion { get; } + public Version DownstreamHttpVersion { get; } public Dictionary UpstreamHeaders { get; } } } diff --git a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs index 8285d2491..dbb10a368 100644 --- a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs +++ b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs @@ -9,12 +9,12 @@ namespace Ocelot.DownstreamRouteFinder.Finder public class DownstreamRouteFinder : IDownstreamRouteProvider { private readonly IUrlPathToUrlTemplateMatcher _urlMatcher; - private readonly IPlaceholderNameAndValueFinder _placeholderNameAndValueFinder; - private readonly IHeadersToHeaderTemplatesMatcher _headersMatcher; + private readonly IPlaceholderNameAndValueFinder _placeholderNameAndValueFinder; + private readonly IHeadersToHeaderTemplatesMatcher _headersMatcher; private readonly IHeaderPlaceholderNameAndValueFinder _headerPlaceholderNameAndValueFinder; public DownstreamRouteFinder( - IUrlPathToUrlTemplateMatcher urlMatcher, + IUrlPathToUrlTemplateMatcher urlMatcher, IPlaceholderNameAndValueFinder urlPathPlaceholderNameAndValueFinder, IHeadersToHeaderTemplatesMatcher headersMatcher, IHeaderPlaceholderNameAndValueFinder headerPlaceholderNameAndValueFinder @@ -22,11 +22,11 @@ IHeaderPlaceholderNameAndValueFinder headerPlaceholderNameAndValueFinder { _urlMatcher = urlMatcher; _placeholderNameAndValueFinder = urlPathPlaceholderNameAndValueFinder; - _headersMatcher = headersMatcher; + _headersMatcher = headersMatcher; _headerPlaceholderNameAndValueFinder = headerPlaceholderNameAndValueFinder; } - public Response Get(string upstreamUrlPath, string upstreamQueryString, string httpMethod, + public Response Get(string upstreamUrlPath, string upstreamQueryString, string httpMethod, IInternalConfiguration configuration, string upstreamHost, Dictionary upstreamHeaders) { var downstreamRoutes = new List(); diff --git a/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinder.cs b/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinder.cs index ea1ce8b03..612761514 100644 --- a/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinder.cs +++ b/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinder.cs @@ -19,7 +19,7 @@ public List Find(Dictionary upstreamHea var upstreamHeader = upstreamHeaders[templateHeader.Key]; var matches = templateHeader.Value.Pattern.Matches(upstreamHeader); var placeholders = matches.SelectMany(g => g.Groups as IEnumerable).Where(g => g.Name != "0") - .Select(g => new PlaceholderNameAndValue("{"+g.Name+"}", g.Value)); + .Select(g => new PlaceholderNameAndValue("{" + g.Name + "}", g.Value)); placeholderNameAndValuesList.AddRange(placeholders); } diff --git a/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/HeadersToHeaderTemplatesMatcher.cs b/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/HeadersToHeaderTemplatesMatcher.cs index 95c7b2f10..475a7b7bc 100644 --- a/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/HeadersToHeaderTemplatesMatcher.cs +++ b/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/HeadersToHeaderTemplatesMatcher.cs @@ -12,7 +12,7 @@ namespace Ocelot.DownstreamRouteFinder.HeaderMatcher public class HeadersToHeaderTemplatesMatcher : IHeadersToHeaderTemplatesMatcher { public bool Match(Dictionary upstreamHeaders, Dictionary routeHeaders) - { + { return routeHeaders == null || upstreamHeaders != null && routeHeaders.All( h => upstreamHeaders.ContainsKey(h.Key) && routeHeaders[h.Key].Pattern.IsMatch(upstreamHeaders[h.Key])); diff --git a/src/Ocelot/Values/UpstreamHeaderTemplate.cs b/src/Ocelot/Values/UpstreamHeaderTemplate.cs index 2dc97bd54..c4ed6b10d 100644 --- a/src/Ocelot/Values/UpstreamHeaderTemplate.cs +++ b/src/Ocelot/Values/UpstreamHeaderTemplate.cs @@ -7,7 +7,7 @@ namespace Ocelot.Values { public class UpstreamHeaderTemplate { - public string Template { get; } + public string Template { get; } public string OriginalValue { get; } @@ -15,7 +15,7 @@ public class UpstreamHeaderTemplate public UpstreamHeaderTemplate(string template, string originalValue) { - Template = template; + Template = template; OriginalValue = originalValue; Pattern = template == null ? new Regex("$^", RegexOptions.Compiled | RegexOptions.Singleline) : diff --git a/test/Ocelot.AcceptanceTests/RoutingBasedOnHeadersTests.cs b/test/Ocelot.AcceptanceTests/RoutingBasedOnHeadersTests.cs index d61ee1509..e92c348eb 100644 --- a/test/Ocelot.AcceptanceTests/RoutingBasedOnHeadersTests.cs +++ b/test/Ocelot.AcceptanceTests/RoutingBasedOnHeadersTests.cs @@ -47,7 +47,7 @@ public void should_match_one_header_value() }, UpstreamPathTemplate = "/", UpstreamHttpMethod = new List { "Get" }, - UpstreamHeaderTemplates = new Dictionary() + UpstreamHeaderTemplates = new Dictionary() { [headerName] = headerValue, }, @@ -64,7 +64,7 @@ public void should_match_one_header_value() .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) .BDDfy(); } - + [Fact] public void should_match_one_header_value_when_more_headers() { @@ -334,6 +334,49 @@ public void should_not_match_two_header_values_when_one_not_existing() .BDDfy(); } + [Fact] + public void should_not_match_one_header_value_when_header_duplicated() + { + var port = RandomPortFinder.GetRandomPort(); + var headerName = "country_code"; + var headerValue = "PL"; + + var configuration = new FileConfiguration + { + Routes = new List + { + new FileRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = port, + }, + }, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + UpstreamHeaderTemplates = new Dictionary() + { + [headerName] = headerValue, + }, + }, + }, + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .And(x => _steps.GivenIAddAHeader(headerName, headerValue)) + .And(x => _steps.GivenIAddAHeader(headerName, "othervalue")) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.NotFound)) + .BDDfy(); + } + [Fact] public void should_aggregated_route_match_header_value() { @@ -706,6 +749,51 @@ public void should_match_header_with_braces() .BDDfy(); } + [Fact] + public void should_match_two_headers_with_the_same_name() + { + var port = RandomPortFinder.GetRandomPort(); + var headerName = "country_code"; + var headerValue1 = "PL"; + var headerValue2 = "UK"; + + var configuration = new FileConfiguration + { + Routes = new List + { + new FileRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = port, + }, + }, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + UpstreamHeaderTemplates = new Dictionary() + { + [headerName] = headerValue1 + ";{header:whatever}", + }, + }, + }, + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .And(x => _steps.GivenIAddAHeader(headerName, headerValue1)) + .And(x => _steps.GivenIAddAHeader(headerName, headerValue2)) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } + private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode, string responseBody) { _serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, basePath, async context => diff --git a/test/Ocelot.UnitTests/Configuration/UpstreamHeaderTemplatePatternCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/UpstreamHeaderTemplatePatternCreatorTests.cs index 630963589..8af206aea 100644 --- a/test/Ocelot.UnitTests/Configuration/UpstreamHeaderTemplatePatternCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/UpstreamHeaderTemplatePatternCreatorTests.cs @@ -1,4 +1,5 @@ -using Ocelot.Configuration.Creator; + +using Ocelot.Configuration.Creator; using Ocelot.Configuration.File; using Ocelot.Values; using Shouldly; @@ -114,7 +115,7 @@ public void should_crate_pattern_with_more_placeholders() { UpstreamHeaderTemplates = new Dictionary { - ["country"] = "any text {header:cc} and other {header:version} and {header:bob} the end", + ["country"] = "any text {header:cc} and other {header:version} and {header:bob} the end", }, }; diff --git a/test/Ocelot.UnitTests/Configuration/Validation/FileConfigurationFluentValidatorTests.cs b/test/Ocelot.UnitTests/Configuration/Validation/FileConfigurationFluentValidatorTests.cs index 3ee3983d7..d41f1eb7c 100644 --- a/test/Ocelot.UnitTests/Configuration/Validation/FileConfigurationFluentValidatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/Validation/FileConfigurationFluentValidatorTests.cs @@ -536,7 +536,7 @@ public void Configuration_is_valid_with_duplicate_routes_but_one_upstreamhost_is .When(x => x.WhenIValidateTheConfiguration()) .Then(x => x.ThenTheResultIsValid()) .BDDfy(); - } + } [Fact] public void Configuration_is_invalid_with_invalid_rate_limit_configuration() @@ -758,7 +758,7 @@ public void configuration_is_not_valid_when_upstream_headers_the_same() }, UpstreamHttpMethod = new List {"Get"}, UpstreamHeaderTemplates = new Dictionary - { + { { "header2", "value2" }, { "header1", "value1" }, }, @@ -817,7 +817,7 @@ public void configuration_is_valid_when_upstream_headers_not_the_same() }, })) .When(x => x.WhenIValidateTheConfiguration()) - .Then(x => x.ThenTheResultIsValid()) + .Then(x => x.ThenTheResultIsValid()) .BDDfy(); } diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs index 784135228..eb6ebe68f 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs @@ -74,7 +74,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(), 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 9cf015a2b..5f6cc650f 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs @@ -819,8 +819,8 @@ public void should_not_return_route_when_upstream_headers_dont_match() .When(x => x.WhenICallTheFinder()) .Then(x => x.ThenAnErrorResponseIsReturned()) .BDDfy(); - } - + } + private void GivenTheUpstreamHostIs(string upstreamHost) { _upstreamHost = upstreamHost; diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinderTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinderTests.cs index cc6c26173..1395a84e5 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinderTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinderTests.cs @@ -198,7 +198,7 @@ private void TheResultIs(List expected) { _result.ShouldNotBeNull(); _result.Count.ShouldBe(expected.Count); - _result.ForEach(x => expected.Any(e => e.Name == x.Name && e.Value == x.Value).ShouldBeTrue()); + _result.ForEach(x => expected.Any(e => e.Name == x.Name && e.Value == x.Value).ShouldBeTrue()); } } } diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/HeaderMatcher/HeadersToHeaderTemplatesMatcherTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/HeaderMatcher/HeadersToHeaderTemplatesMatcherTests.cs index 73497e87c..5cab32ac8 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/HeaderMatcher/HeadersToHeaderTemplatesMatcherTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/HeaderMatcher/HeadersToHeaderTemplatesMatcherTests.cs @@ -151,7 +151,7 @@ public void should_match_all_the_same_headers() }; var templateHeaders = new Dictionary() - { + { ["secondHeader"] = new UpstreamHeaderTemplate("^(?i)secondHeaderValue$", "secondHeaderValue"), ["thirdHeader"] = new UpstreamHeaderTemplate("^(?i)thirdHeaderValue$", "thirdHeaderValue"), ["anyHeader"] = new UpstreamHeaderTemplate("^(?i)anyHeaderValue$", "anyHeaderValue"), From cac4c3b4fa190170320c32f4e47a849c29e1fd15 Mon Sep 17 00:00:00 2001 From: raman-m Date: Wed, 2 Aug 2023 19:55:34 +0300 Subject: [PATCH 15/49] SA1649 File name should match first type name --- .../Builder/DownstreamReRouteBuilder.cs | 313 --------- .../Builder/DownstreamRouteBuilder.cs | 597 +++++++++--------- 2 files changed, 303 insertions(+), 607 deletions(-) delete mode 100644 src/Ocelot/Configuration/Builder/DownstreamReRouteBuilder.cs diff --git a/src/Ocelot/Configuration/Builder/DownstreamReRouteBuilder.cs b/src/Ocelot/Configuration/Builder/DownstreamReRouteBuilder.cs deleted file mode 100644 index 634fd7c9f..000000000 --- a/src/Ocelot/Configuration/Builder/DownstreamReRouteBuilder.cs +++ /dev/null @@ -1,313 +0,0 @@ -using Ocelot.Configuration.Creator; -using Ocelot.Values; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; - -namespace Ocelot.Configuration.Builder -{ - public class DownstreamRouteBuilder - { - private AuthenticationOptions _authenticationOptions; - private string _loadBalancerKey; - private string _downstreamPathTemplate; - private UpstreamPathTemplate _upstreamTemplatePattern; - private List _upstreamHttpMethod; - private bool _isAuthenticated; - private List _claimsToHeaders; - private List _claimToClaims; - private Dictionary _routeClaimRequirement; - private bool _isAuthorised; - private List _claimToQueries; - private List _claimToDownstreamPath; - private string _requestIdHeaderKey; - private bool _isCached; - private CacheOptions _fileCacheOptions; - private string _downstreamScheme; - private LoadBalancerOptions _loadBalancerOptions; - private QoSOptions _qosOptions; - private HttpHandlerOptions _httpHandlerOptions; - private bool _enableRateLimiting; - private RateLimitOptions _rateLimitOptions; - private bool _useServiceDiscovery; - private string _serviceName; - private string _serviceNamespace; - private List _upstreamHeaderFindAndReplace; - private List _downstreamHeaderFindAndReplace; - private readonly List _downstreamAddresses; - private string _key; - private List _delegatingHandlers; - private List _addHeadersToDownstream; - private List _addHeadersToUpstream; - private bool _dangerousAcceptAnyServerCertificateValidator; - private SecurityOptions _securityOptions; - private string _downstreamHttpMethod; - private Version _downstreamHttpVersion; - private Dictionary _upstreamHeaders; - - public DownstreamRouteBuilder() - { - _downstreamAddresses = new List(); - _delegatingHandlers = new List(); - _addHeadersToDownstream = new List(); - _addHeadersToUpstream = new List(); - } - - public DownstreamRouteBuilder WithDownstreamAddresses(List downstreamAddresses) - { - _downstreamAddresses.AddRange(downstreamAddresses); - return this; - } - - public DownstreamRouteBuilder WithDownStreamHttpMethod(string method) - { - _downstreamHttpMethod = method; - return this; - } - - public DownstreamRouteBuilder WithLoadBalancerOptions(LoadBalancerOptions loadBalancerOptions) - { - _loadBalancerOptions = loadBalancerOptions; - return this; - } - - public DownstreamRouteBuilder WithDownstreamScheme(string downstreamScheme) - { - _downstreamScheme = downstreamScheme; - return this; - } - - public DownstreamRouteBuilder WithDownstreamPathTemplate(string input) - { - _downstreamPathTemplate = input; - return this; - } - - public DownstreamRouteBuilder WithUpstreamPathTemplate(UpstreamPathTemplate input) - { - _upstreamTemplatePattern = input; - return this; - } - - public DownstreamRouteBuilder WithUpstreamHttpMethod(List input) - { - _upstreamHttpMethod = (input.Count == 0) ? new List() : input.Select(x => new HttpMethod(x.Trim())).ToList(); - return this; - } - - public DownstreamRouteBuilder WithIsAuthenticated(bool input) - { - _isAuthenticated = input; - return this; - } - - public DownstreamRouteBuilder WithIsAuthorised(bool input) - { - _isAuthorised = input; - return this; - } - - public DownstreamRouteBuilder WithRequestIdKey(string input) - { - _requestIdHeaderKey = input; - return this; - } - - public DownstreamRouteBuilder WithClaimsToHeaders(List input) - { - _claimsToHeaders = input; - return this; - } - - public DownstreamRouteBuilder WithClaimsToClaims(List input) - { - _claimToClaims = input; - return this; - } - - public DownstreamRouteBuilder WithRouteClaimsRequirement(Dictionary input) - { - _routeClaimRequirement = input; - return this; - } - - public DownstreamRouteBuilder WithClaimsToQueries(List input) - { - _claimToQueries = input; - return this; - } - - public DownstreamRouteBuilder WithClaimsToDownstreamPath(List input) - { - _claimToDownstreamPath = input; - return this; - } - - public DownstreamRouteBuilder WithIsCached(bool input) - { - _isCached = input; - return this; - } - - public DownstreamRouteBuilder WithCacheOptions(CacheOptions input) - { - _fileCacheOptions = input; - return this; - } - - public DownstreamRouteBuilder WithQosOptions(QoSOptions input) - { - _qosOptions = input; - return this; - } - - public DownstreamRouteBuilder WithLoadBalancerKey(string loadBalancerKey) - { - _loadBalancerKey = loadBalancerKey; - return this; - } - - public DownstreamRouteBuilder WithAuthenticationOptions(AuthenticationOptions authenticationOptions) - { - _authenticationOptions = authenticationOptions; - return this; - } - - public DownstreamRouteBuilder WithEnableRateLimiting(bool input) - { - _enableRateLimiting = input; - return this; - } - - public DownstreamRouteBuilder WithRateLimitOptions(RateLimitOptions input) - { - _rateLimitOptions = input; - return this; - } - - public DownstreamRouteBuilder WithHttpHandlerOptions(HttpHandlerOptions input) - { - _httpHandlerOptions = input; - return this; - } - - public DownstreamRouteBuilder WithUseServiceDiscovery(bool useServiceDiscovery) - { - _useServiceDiscovery = useServiceDiscovery; - return this; - } - - public DownstreamRouteBuilder WithServiceName(string serviceName) - { - _serviceName = serviceName; - return this; - } - - public DownstreamRouteBuilder WithServiceNamespace(string serviceNamespace) - { - _serviceNamespace = serviceNamespace; - return this; - } - - public DownstreamRouteBuilder WithUpstreamHeaderFindAndReplace(List upstreamHeaderFindAndReplace) - { - _upstreamHeaderFindAndReplace = upstreamHeaderFindAndReplace; - return this; - } - - public DownstreamRouteBuilder WithDownstreamHeaderFindAndReplace(List downstreamHeaderFindAndReplace) - { - _downstreamHeaderFindAndReplace = downstreamHeaderFindAndReplace; - return this; - } - - public DownstreamRouteBuilder WithKey(string key) - { - _key = key; - return this; - } - - public DownstreamRouteBuilder WithDelegatingHandlers(List delegatingHandlers) - { - _delegatingHandlers = delegatingHandlers; - return this; - } - - public DownstreamRouteBuilder WithAddHeadersToDownstream(List addHeadersToDownstream) - { - _addHeadersToDownstream = addHeadersToDownstream; - return this; - } - - public DownstreamRouteBuilder WithAddHeadersToUpstream(List addHeadersToUpstream) - { - _addHeadersToUpstream = addHeadersToUpstream; - return this; - } - - public DownstreamRouteBuilder WithDangerousAcceptAnyServerCertificateValidator(bool dangerousAcceptAnyServerCertificateValidator) - { - _dangerousAcceptAnyServerCertificateValidator = dangerousAcceptAnyServerCertificateValidator; - return this; - } - - public DownstreamRouteBuilder WithSecurityOptions(SecurityOptions securityOptions) - { - _securityOptions = securityOptions; - return this; - } - - public DownstreamRouteBuilder WithDownstreamHttpVersion(Version downstreamHttpVersion) - { - _downstreamHttpVersion = downstreamHttpVersion; - return this; - } - - public DownstreamRouteBuilder WithUpstreamHeaders(Dictionary input) - { - _upstreamHeaders = input; - return this; - } - - public DownstreamRoute Build() - { - return new DownstreamRoute( - _key, - _upstreamTemplatePattern, - _upstreamHeaderFindAndReplace, - _downstreamHeaderFindAndReplace, - _downstreamAddresses, - _serviceName, - _serviceNamespace, - _httpHandlerOptions, - _useServiceDiscovery, - _enableRateLimiting, - _qosOptions, - _downstreamScheme, - _requestIdHeaderKey, - _isCached, - _fileCacheOptions, - _loadBalancerOptions, - _rateLimitOptions, - _routeClaimRequirement, - _claimToQueries, - _claimsToHeaders, - _claimToClaims, - _claimToDownstreamPath, - _isAuthenticated, - _isAuthorised, - _authenticationOptions, - new DownstreamPathTemplate(_downstreamPathTemplate), - _loadBalancerKey, - _delegatingHandlers, - _addHeadersToDownstream, - _addHeadersToUpstream, - _dangerousAcceptAnyServerCertificateValidator, - _securityOptions, - _downstreamHttpMethod, - _downstreamHttpVersion, - _upstreamHeaders); - } - } -} diff --git a/src/Ocelot/Configuration/Builder/DownstreamRouteBuilder.cs b/src/Ocelot/Configuration/Builder/DownstreamRouteBuilder.cs index 9dc93008e..711b73fc0 100644 --- a/src/Ocelot/Configuration/Builder/DownstreamRouteBuilder.cs +++ b/src/Ocelot/Configuration/Builder/DownstreamRouteBuilder.cs @@ -1,300 +1,309 @@ using Ocelot.Configuration.Creator; using Ocelot.Values; -namespace Ocelot.Configuration.Builder; - -public class DownstreamRouteBuilder +namespace Ocelot.Configuration.Builder { - private AuthenticationOptions _authenticationOptions; - private string _loadBalancerKey; - private string _downstreamPathTemplate; - private UpstreamPathTemplate _upstreamTemplatePattern; - private List _upstreamHttpMethod; - private bool _isAuthenticated; - private List _claimsToHeaders; - private List _claimToClaims; - private Dictionary _routeClaimRequirement; - private bool _isAuthorized; - private List _claimToQueries; - private List _claimToDownstreamPath; - private string _requestIdHeaderKey; - private bool _isCached; - private CacheOptions _fileCacheOptions; - private string _downstreamScheme; - private LoadBalancerOptions _loadBalancerOptions; - private QoSOptions _qosOptions; - private HttpHandlerOptions _httpHandlerOptions; - private bool _enableRateLimiting; - private RateLimitOptions _rateLimitOptions; - private bool _useServiceDiscovery; - private string _serviceName; - private string _serviceNamespace; - private List _upstreamHeaderFindAndReplace; - private List _downstreamHeaderFindAndReplace; - private readonly List _downstreamAddresses; - private string _key; - private List _delegatingHandlers; - private List _addHeadersToDownstream; - private List _addHeadersToUpstream; - private bool _dangerousAcceptAnyServerCertificateValidator; - private SecurityOptions _securityOptions; - private string _downstreamHttpMethod; - private Version _downstreamHttpVersion; - - public DownstreamRouteBuilder() - { - _downstreamAddresses = new List(); - _delegatingHandlers = new List(); - _addHeadersToDownstream = new List(); - _addHeadersToUpstream = new List(); - } - - public DownstreamRouteBuilder WithDownstreamAddresses(List downstreamAddresses) - { - _downstreamAddresses.AddRange(downstreamAddresses); - return this; - } - - public DownstreamRouteBuilder WithDownStreamHttpMethod(string method) - { - _downstreamHttpMethod = method; - return this; - } - - public DownstreamRouteBuilder WithLoadBalancerOptions(LoadBalancerOptions loadBalancerOptions) - { - _loadBalancerOptions = loadBalancerOptions; - return this; - } - - public DownstreamRouteBuilder WithDownstreamScheme(string downstreamScheme) - { - _downstreamScheme = downstreamScheme; - return this; - } - - public DownstreamRouteBuilder WithDownstreamPathTemplate(string input) - { - _downstreamPathTemplate = input; - return this; - } - - public DownstreamRouteBuilder WithUpstreamPathTemplate(UpstreamPathTemplate input) - { - _upstreamTemplatePattern = input; - return this; - } - - public DownstreamRouteBuilder WithUpstreamHttpMethod(List input) - { - _upstreamHttpMethod = (input.Count == 0) ? new List() : input.Select(x => new HttpMethod(x.Trim())).ToList(); - return this; - } - - public DownstreamRouteBuilder WithIsAuthenticated(bool input) - { - _isAuthenticated = input; - return this; - } - - public DownstreamRouteBuilder WithIsAuthorized(bool input) - { - _isAuthorized = input; - return this; - } - - public DownstreamRouteBuilder WithRequestIdKey(string input) - { - _requestIdHeaderKey = input; - return this; - } - - public DownstreamRouteBuilder WithClaimsToHeaders(List input) - { - _claimsToHeaders = input; - return this; - } - - public DownstreamRouteBuilder WithClaimsToClaims(List input) - { - _claimToClaims = input; - return this; - } - - public DownstreamRouteBuilder WithRouteClaimsRequirement(Dictionary input) - { - _routeClaimRequirement = input; - return this; - } - - public DownstreamRouteBuilder WithClaimsToQueries(List input) - { - _claimToQueries = input; - return this; - } - - public DownstreamRouteBuilder WithClaimsToDownstreamPath(List input) - { - _claimToDownstreamPath = input; - return this; - } - - public DownstreamRouteBuilder WithIsCached(bool input) - { - _isCached = input; - return this; - } - - public DownstreamRouteBuilder WithCacheOptions(CacheOptions input) - { - _fileCacheOptions = input; - return this; - } - - public DownstreamRouteBuilder WithQosOptions(QoSOptions input) - { - _qosOptions = input; - return this; - } - - public DownstreamRouteBuilder WithLoadBalancerKey(string loadBalancerKey) - { - _loadBalancerKey = loadBalancerKey; - return this; - } - - public DownstreamRouteBuilder WithAuthenticationOptions(AuthenticationOptions authenticationOptions) - { - _authenticationOptions = authenticationOptions; - return this; - } - - public DownstreamRouteBuilder WithEnableRateLimiting(bool input) - { - _enableRateLimiting = input; - return this; - } - - public DownstreamRouteBuilder WithRateLimitOptions(RateLimitOptions input) - { - _rateLimitOptions = input; - return this; - } - - public DownstreamRouteBuilder WithHttpHandlerOptions(HttpHandlerOptions input) - { - _httpHandlerOptions = input; - return this; - } - - public DownstreamRouteBuilder WithUseServiceDiscovery(bool useServiceDiscovery) - { - _useServiceDiscovery = useServiceDiscovery; - return this; - } - - public DownstreamRouteBuilder WithServiceName(string serviceName) - { - _serviceName = serviceName; - return this; - } - - public DownstreamRouteBuilder WithServiceNamespace(string serviceNamespace) - { - _serviceNamespace = serviceNamespace; - return this; - } - - public DownstreamRouteBuilder WithUpstreamHeaderFindAndReplace(List upstreamHeaderFindAndReplace) - { - _upstreamHeaderFindAndReplace = upstreamHeaderFindAndReplace; - return this; - } - - public DownstreamRouteBuilder WithDownstreamHeaderFindAndReplace(List downstreamHeaderFindAndReplace) - { - _downstreamHeaderFindAndReplace = downstreamHeaderFindAndReplace; - return this; - } - - public DownstreamRouteBuilder WithKey(string key) - { - _key = key; - return this; - } - - public DownstreamRouteBuilder WithDelegatingHandlers(List delegatingHandlers) - { - _delegatingHandlers = delegatingHandlers; - return this; - } - - public DownstreamRouteBuilder WithAddHeadersToDownstream(List addHeadersToDownstream) - { - _addHeadersToDownstream = addHeadersToDownstream; - return this; - } - - public DownstreamRouteBuilder WithAddHeadersToUpstream(List addHeadersToUpstream) - { - _addHeadersToUpstream = addHeadersToUpstream; - return this; - } - - public DownstreamRouteBuilder WithDangerousAcceptAnyServerCertificateValidator(bool dangerousAcceptAnyServerCertificateValidator) - { - _dangerousAcceptAnyServerCertificateValidator = dangerousAcceptAnyServerCertificateValidator; - return this; - } - - public DownstreamRouteBuilder WithSecurityOptions(SecurityOptions securityOptions) - { - _securityOptions = securityOptions; - return this; - } - - public DownstreamRouteBuilder WithDownstreamHttpVersion(Version downstreamHttpVersion) - { - _downstreamHttpVersion = downstreamHttpVersion; - return this; - } - - public DownstreamRoute Build() - { - return new DownstreamRoute( - _key, - _upstreamTemplatePattern, - _upstreamHeaderFindAndReplace, - _downstreamHeaderFindAndReplace, - _downstreamAddresses, - _serviceName, - _serviceNamespace, - _httpHandlerOptions, - _useServiceDiscovery, - _enableRateLimiting, - _qosOptions, - _downstreamScheme, - _requestIdHeaderKey, - _isCached, - _fileCacheOptions, - _loadBalancerOptions, - _rateLimitOptions, - _routeClaimRequirement, - _claimToQueries, - _claimsToHeaders, - _claimToClaims, - _claimToDownstreamPath, - _isAuthenticated, - _isAuthorized, - _authenticationOptions, - new DownstreamPathTemplate(_downstreamPathTemplate), - _loadBalancerKey, - _delegatingHandlers, - _addHeadersToDownstream, - _addHeadersToUpstream, - _dangerousAcceptAnyServerCertificateValidator, - _securityOptions, - _downstreamHttpMethod, - _downstreamHttpVersion); + public class DownstreamRouteBuilder + { + private AuthenticationOptions _authenticationOptions; + private string _loadBalancerKey; + private string _downstreamPathTemplate; + private UpstreamPathTemplate _upstreamTemplatePattern; + private List _upstreamHttpMethod; + private bool _isAuthenticated; + private List _claimsToHeaders; + private List _claimToClaims; + private Dictionary _routeClaimRequirement; + private bool _isAuthorized; + private List _claimToQueries; + private List _claimToDownstreamPath; + private string _requestIdHeaderKey; + private bool _isCached; + private CacheOptions _fileCacheOptions; + private string _downstreamScheme; + private LoadBalancerOptions _loadBalancerOptions; + private QoSOptions _qosOptions; + private HttpHandlerOptions _httpHandlerOptions; + private bool _enableRateLimiting; + private RateLimitOptions _rateLimitOptions; + private bool _useServiceDiscovery; + private string _serviceName; + private string _serviceNamespace; + private List _upstreamHeaderFindAndReplace; + private List _downstreamHeaderFindAndReplace; + private readonly List _downstreamAddresses; + private string _key; + private List _delegatingHandlers; + private List _addHeadersToDownstream; + private List _addHeadersToUpstream; + private bool _dangerousAcceptAnyServerCertificateValidator; + private SecurityOptions _securityOptions; + private string _downstreamHttpMethod; + private Version _downstreamHttpVersion; + private Dictionary _upstreamHeaders; + + public DownstreamRouteBuilder() + { + _downstreamAddresses = new List(); + _delegatingHandlers = new List(); + _addHeadersToDownstream = new List(); + _addHeadersToUpstream = new List(); + } + + public DownstreamRouteBuilder WithDownstreamAddresses(List downstreamAddresses) + { + _downstreamAddresses.AddRange(downstreamAddresses); + return this; + } + + public DownstreamRouteBuilder WithDownStreamHttpMethod(string method) + { + _downstreamHttpMethod = method; + return this; + } + + public DownstreamRouteBuilder WithLoadBalancerOptions(LoadBalancerOptions loadBalancerOptions) + { + _loadBalancerOptions = loadBalancerOptions; + return this; + } + + public DownstreamRouteBuilder WithDownstreamScheme(string downstreamScheme) + { + _downstreamScheme = downstreamScheme; + return this; + } + + public DownstreamRouteBuilder WithDownstreamPathTemplate(string input) + { + _downstreamPathTemplate = input; + return this; + } + + public DownstreamRouteBuilder WithUpstreamPathTemplate(UpstreamPathTemplate input) + { + _upstreamTemplatePattern = input; + return this; + } + + public DownstreamRouteBuilder WithUpstreamHttpMethod(List input) + { + _upstreamHttpMethod = (input.Count == 0) ? new List() : input.Select(x => new HttpMethod(x.Trim())).ToList(); + return this; + } + + public DownstreamRouteBuilder WithIsAuthenticated(bool input) + { + _isAuthenticated = input; + return this; + } + + public DownstreamRouteBuilder WithIsAuthorized(bool input) + { + _isAuthorized = input; + return this; + } + + public DownstreamRouteBuilder WithRequestIdKey(string input) + { + _requestIdHeaderKey = input; + return this; + } + + public DownstreamRouteBuilder WithClaimsToHeaders(List input) + { + _claimsToHeaders = input; + return this; + } + + public DownstreamRouteBuilder WithClaimsToClaims(List input) + { + _claimToClaims = input; + return this; + } + + public DownstreamRouteBuilder WithRouteClaimsRequirement(Dictionary input) + { + _routeClaimRequirement = input; + return this; + } + + public DownstreamRouteBuilder WithClaimsToQueries(List input) + { + _claimToQueries = input; + return this; + } + + public DownstreamRouteBuilder WithClaimsToDownstreamPath(List input) + { + _claimToDownstreamPath = input; + return this; + } + + public DownstreamRouteBuilder WithIsCached(bool input) + { + _isCached = input; + return this; + } + + public DownstreamRouteBuilder WithCacheOptions(CacheOptions input) + { + _fileCacheOptions = input; + return this; + } + + public DownstreamRouteBuilder WithQosOptions(QoSOptions input) + { + _qosOptions = input; + return this; + } + + public DownstreamRouteBuilder WithLoadBalancerKey(string loadBalancerKey) + { + _loadBalancerKey = loadBalancerKey; + return this; + } + + public DownstreamRouteBuilder WithAuthenticationOptions(AuthenticationOptions authenticationOptions) + { + _authenticationOptions = authenticationOptions; + return this; + } + + public DownstreamRouteBuilder WithEnableRateLimiting(bool input) + { + _enableRateLimiting = input; + return this; + } + + public DownstreamRouteBuilder WithRateLimitOptions(RateLimitOptions input) + { + _rateLimitOptions = input; + return this; + } + + public DownstreamRouteBuilder WithHttpHandlerOptions(HttpHandlerOptions input) + { + _httpHandlerOptions = input; + return this; + } + + public DownstreamRouteBuilder WithUseServiceDiscovery(bool useServiceDiscovery) + { + _useServiceDiscovery = useServiceDiscovery; + return this; + } + + public DownstreamRouteBuilder WithServiceName(string serviceName) + { + _serviceName = serviceName; + return this; + } + + public DownstreamRouteBuilder WithServiceNamespace(string serviceNamespace) + { + _serviceNamespace = serviceNamespace; + return this; + } + + public DownstreamRouteBuilder WithUpstreamHeaderFindAndReplace(List upstreamHeaderFindAndReplace) + { + _upstreamHeaderFindAndReplace = upstreamHeaderFindAndReplace; + return this; + } + + public DownstreamRouteBuilder WithDownstreamHeaderFindAndReplace(List downstreamHeaderFindAndReplace) + { + _downstreamHeaderFindAndReplace = downstreamHeaderFindAndReplace; + return this; + } + + public DownstreamRouteBuilder WithKey(string key) + { + _key = key; + return this; + } + + public DownstreamRouteBuilder WithDelegatingHandlers(List delegatingHandlers) + { + _delegatingHandlers = delegatingHandlers; + return this; + } + + public DownstreamRouteBuilder WithAddHeadersToDownstream(List addHeadersToDownstream) + { + _addHeadersToDownstream = addHeadersToDownstream; + return this; + } + + public DownstreamRouteBuilder WithAddHeadersToUpstream(List addHeadersToUpstream) + { + _addHeadersToUpstream = addHeadersToUpstream; + return this; + } + + public DownstreamRouteBuilder WithDangerousAcceptAnyServerCertificateValidator(bool dangerousAcceptAnyServerCertificateValidator) + { + _dangerousAcceptAnyServerCertificateValidator = dangerousAcceptAnyServerCertificateValidator; + return this; + } + + public DownstreamRouteBuilder WithSecurityOptions(SecurityOptions securityOptions) + { + _securityOptions = securityOptions; + return this; + } + + public DownstreamRouteBuilder WithDownstreamHttpVersion(Version downstreamHttpVersion) + { + _downstreamHttpVersion = downstreamHttpVersion; + return this; + } + + public DownstreamRouteBuilder WithUpstreamHeaders(Dictionary input) + { + _upstreamHeaders = input; + return this; + } + + public DownstreamRoute Build() + { + return new DownstreamRoute( + _key, + _upstreamTemplatePattern, + _upstreamHeaderFindAndReplace, + _downstreamHeaderFindAndReplace, + _downstreamAddresses, + _serviceName, + _serviceNamespace, + _httpHandlerOptions, + _useServiceDiscovery, + _enableRateLimiting, + _qosOptions, + _downstreamScheme, + _requestIdHeaderKey, + _isCached, + _fileCacheOptions, + _loadBalancerOptions, + _rateLimitOptions, + _routeClaimRequirement, + _claimToQueries, + _claimsToHeaders, + _claimToClaims, + _claimToDownstreamPath, + _isAuthenticated, + _isAuthorized, + _authenticationOptions, + new DownstreamPathTemplate(_downstreamPathTemplate), + _loadBalancerKey, + _delegatingHandlers, + _addHeadersToDownstream, + _addHeadersToUpstream, + _dangerousAcceptAnyServerCertificateValidator, + _securityOptions, + _downstreamHttpMethod, + _downstreamHttpVersion, + _upstreamHeaders); + } } } From d2b9cd7a3e91e490bd8db0441c808276bca8cf73 Mon Sep 17 00:00:00 2001 From: raman-m Date: Wed, 2 Aug 2023 20:07:21 +0300 Subject: [PATCH 16/49] Fix compilation errors by code review after resolving conflicts --- .../DownstreamRouteFinder/DownstreamRouteProviderFactoryTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteProviderFactoryTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteProviderFactoryTests.cs index 6f43220de..20426362a 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteProviderFactoryTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteProviderFactoryTests.cs @@ -2,7 +2,6 @@ using Ocelot.Configuration; using Ocelot.Configuration.Builder; using Ocelot.Configuration.Creator; -using Ocelot.DownstreamRouteFinder.Finder; using Ocelot.DownstreamRouteFinder.HeaderMatcher; using Ocelot.DownstreamRouteFinder.UrlMatcher; using Ocelot.Logging; From 30f5b3d2452dd063006ab42e5b7abbcf163e431d Mon Sep 17 00:00:00 2001 From: raman-m Date: Wed, 2 Aug 2023 20:11:55 +0300 Subject: [PATCH 17/49] Fix warnings --- test/Ocelot.UnitTests/Configuration/RoutesCreatorTests.cs | 1 - .../Configuration/UpstreamHeaderTemplatePatternCreatorTests.cs | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/test/Ocelot.UnitTests/Configuration/RoutesCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/RoutesCreatorTests.cs index f21bd6561..2be7ccd2a 100644 --- a/test/Ocelot.UnitTests/Configuration/RoutesCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/RoutesCreatorTests.cs @@ -41,7 +41,6 @@ public class RoutesCreatorTests : UnitTest private List _dhp; private LoadBalancerOptions _lbo; private List _result; - private SecurityOptions _securityOptions; private Version _expectedVersion; private Dictionary _uht; diff --git a/test/Ocelot.UnitTests/Configuration/UpstreamHeaderTemplatePatternCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/UpstreamHeaderTemplatePatternCreatorTests.cs index 8af206aea..2464f18b1 100644 --- a/test/Ocelot.UnitTests/Configuration/UpstreamHeaderTemplatePatternCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/UpstreamHeaderTemplatePatternCreatorTests.cs @@ -1,5 +1,4 @@ - -using Ocelot.Configuration.Creator; +using Ocelot.Configuration.Creator; using Ocelot.Configuration.File; using Ocelot.Values; using Shouldly; From 2a32d1f45c49919a9a47ad9ea232f98acc15db19 Mon Sep 17 00:00:00 2001 From: raman-m Date: Wed, 2 Aug 2023 20:42:44 +0300 Subject: [PATCH 18/49] File-scoped namespaces --- .../IUpstreamHeaderTemplatePatternCreator.cs | 9 +- .../UpstreamHeaderTemplatePatternCreator.cs | 61 +- .../HeaderPlaceholderNameAndValueFinder.cs | 31 +- .../HeadersToHeaderTemplatesMatcher.cs | 22 +- .../IHeaderPlaceholderNameAndValueFinder.cs | 11 +- .../IHeadersToHeaderTemplatesMatcher.cs | 15 +- src/Ocelot/Values/UpstreamHeaderTemplate.cs | 32 +- .../RoutingBasedOnHeadersTests.cs | 1356 ++++++++--------- ...streamHeaderTemplatePatternCreatorTests.cs | 227 ++- ...eaderPlaceholderNameAndValueFinderTests.cs | 379 +++-- .../HeadersToHeaderTemplatesMatcherTests.cs | 482 +++--- 11 files changed, 1295 insertions(+), 1330 deletions(-) diff --git a/src/Ocelot/Configuration/Creator/IUpstreamHeaderTemplatePatternCreator.cs b/src/Ocelot/Configuration/Creator/IUpstreamHeaderTemplatePatternCreator.cs index ed2b34410..428badd45 100644 --- a/src/Ocelot/Configuration/Creator/IUpstreamHeaderTemplatePatternCreator.cs +++ b/src/Ocelot/Configuration/Creator/IUpstreamHeaderTemplatePatternCreator.cs @@ -2,10 +2,9 @@ using Ocelot.Values; using System.Collections.Generic; -namespace Ocelot.Configuration.Creator +namespace Ocelot.Configuration.Creator; + +public interface IUpstreamHeaderTemplatePatternCreator { - public interface IUpstreamHeaderTemplatePatternCreator - { - Dictionary Create(IRoute route); - } + Dictionary Create(IRoute route); } diff --git a/src/Ocelot/Configuration/Creator/UpstreamHeaderTemplatePatternCreator.cs b/src/Ocelot/Configuration/Creator/UpstreamHeaderTemplatePatternCreator.cs index 4a6a5c0cd..3458c2a9c 100644 --- a/src/Ocelot/Configuration/Creator/UpstreamHeaderTemplatePatternCreator.cs +++ b/src/Ocelot/Configuration/Creator/UpstreamHeaderTemplatePatternCreator.cs @@ -4,48 +4,47 @@ using System.Linq; using System.Text.RegularExpressions; -namespace Ocelot.Configuration.Creator +namespace Ocelot.Configuration.Creator; + +public class UpstreamHeaderTemplatePatternCreator : IUpstreamHeaderTemplatePatternCreator { - public class UpstreamHeaderTemplatePatternCreator : IUpstreamHeaderTemplatePatternCreator + private const string RegExMatchOneOrMoreOfEverything = ".+"; + private const string RegExIgnoreCase = "(?i)"; + private const string RegExPlaceholders = @"(\{header:.*?\})"; + + public Dictionary Create(IRoute route) { - private const string RegExMatchOneOrMoreOfEverything = ".+"; - private const string RegExIgnoreCase = "(?i)"; - private const string RegExPlaceholders = @"(\{header:.*?\})"; + var resultHeaderTemplates = new Dictionary(); - public Dictionary Create(IRoute route) + foreach (var headerTemplate in route.UpstreamHeaderTemplates) { - var resultHeaderTemplates = new Dictionary(); - - foreach (var headerTemplate in route.UpstreamHeaderTemplates) - { - var headerTemplateValue = headerTemplate.Value; - - var placeholders = new List(); - - Regex expression = new Regex(RegExPlaceholders); - MatchCollection matches = expression.Matches(headerTemplateValue); + var headerTemplateValue = headerTemplate.Value; - if (matches.Count > 0) - { - placeholders.AddRange(matches.Select(m => m.Groups[1].Value)); - } + var placeholders = new List(); - for (int i = 0; i < placeholders.Count; i++) - { - var indexOfPlaceholder = headerTemplateValue.IndexOf(placeholders[i]); + Regex expression = new Regex(RegExPlaceholders); + MatchCollection matches = expression.Matches(headerTemplateValue); - var placeholderName = placeholders[i][8..^1]; // remove "{header:" and "}" - headerTemplateValue = headerTemplateValue.Replace(placeholders[i], "(?<" + placeholderName + ">" + RegExMatchOneOrMoreOfEverything + ")"); - } + if (matches.Count > 0) + { + placeholders.AddRange(matches.Select(m => m.Groups[1].Value)); + } - var template = route.RouteIsCaseSensitive - ? $"^{headerTemplateValue}$" - : $"^{RegExIgnoreCase}{headerTemplateValue}$"; + for (int i = 0; i < placeholders.Count; i++) + { + var indexOfPlaceholder = headerTemplateValue.IndexOf(placeholders[i]); - resultHeaderTemplates.Add(headerTemplate.Key, new UpstreamHeaderTemplate(template, headerTemplate.Value)); + var placeholderName = placeholders[i][8..^1]; // remove "{header:" and "}" + headerTemplateValue = headerTemplateValue.Replace(placeholders[i], "(?<" + placeholderName + ">" + RegExMatchOneOrMoreOfEverything + ")"); } - return resultHeaderTemplates; + var template = route.RouteIsCaseSensitive + ? $"^{headerTemplateValue}$" + : $"^{RegExIgnoreCase}{headerTemplateValue}$"; + + resultHeaderTemplates.Add(headerTemplate.Key, new UpstreamHeaderTemplate(template, headerTemplate.Value)); } + + return resultHeaderTemplates; } } diff --git a/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinder.cs b/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinder.cs index 612761514..0794916b2 100644 --- a/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinder.cs +++ b/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinder.cs @@ -1,29 +1,26 @@ using Ocelot.DownstreamRouteFinder.UrlMatcher; using Ocelot.Values; -using System; using System.Collections.Generic; using System.Linq; -using System.Text; using System.Text.RegularExpressions; -namespace Ocelot.DownstreamRouteFinder.HeaderMatcher +namespace Ocelot.DownstreamRouteFinder.HeaderMatcher; + +public class HeaderPlaceholderNameAndValueFinder : IHeaderPlaceholderNameAndValueFinder { - public class HeaderPlaceholderNameAndValueFinder : IHeaderPlaceholderNameAndValueFinder + public List Find(Dictionary upstreamHeaders, Dictionary templateHeaders) { - public List Find(Dictionary upstreamHeaders, Dictionary templateHeaders) - { - var placeholderNameAndValuesList = new List(); - - foreach (var templateHeader in templateHeaders) - { - var upstreamHeader = upstreamHeaders[templateHeader.Key]; - var matches = templateHeader.Value.Pattern.Matches(upstreamHeader); - var placeholders = matches.SelectMany(g => g.Groups as IEnumerable).Where(g => g.Name != "0") - .Select(g => new PlaceholderNameAndValue("{" + g.Name + "}", g.Value)); - placeholderNameAndValuesList.AddRange(placeholders); - } + var placeholderNameAndValuesList = new List(); - return placeholderNameAndValuesList; + foreach (var templateHeader in templateHeaders) + { + var upstreamHeader = upstreamHeaders[templateHeader.Key]; + var matches = templateHeader.Value.Pattern.Matches(upstreamHeader); + var placeholders = matches.SelectMany(g => g.Groups as IEnumerable).Where(g => g.Name != "0") + .Select(g => new PlaceholderNameAndValue("{" + g.Name + "}", g.Value)); + placeholderNameAndValuesList.AddRange(placeholders); } + + return placeholderNameAndValuesList; } } diff --git a/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/HeadersToHeaderTemplatesMatcher.cs b/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/HeadersToHeaderTemplatesMatcher.cs index 475a7b7bc..3e0226c89 100644 --- a/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/HeadersToHeaderTemplatesMatcher.cs +++ b/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/HeadersToHeaderTemplatesMatcher.cs @@ -1,21 +1,15 @@ -using Microsoft.AspNetCore.Routing; -using Ocelot.DownstreamRouteFinder.UrlMatcher; -using Ocelot.Responses; -using Ocelot.Values; -using System; +using Ocelot.Values; using System.Collections.Generic; using System.Linq; -using System.Text; -namespace Ocelot.DownstreamRouteFinder.HeaderMatcher +namespace Ocelot.DownstreamRouteFinder.HeaderMatcher; + +public class HeadersToHeaderTemplatesMatcher : IHeadersToHeaderTemplatesMatcher { - public class HeadersToHeaderTemplatesMatcher : IHeadersToHeaderTemplatesMatcher + public bool Match(Dictionary upstreamHeaders, Dictionary routeHeaders) { - public bool Match(Dictionary upstreamHeaders, Dictionary routeHeaders) - { - return routeHeaders == null || - upstreamHeaders != null && routeHeaders.All( - h => upstreamHeaders.ContainsKey(h.Key) && routeHeaders[h.Key].Pattern.IsMatch(upstreamHeaders[h.Key])); - } + return routeHeaders == null || + upstreamHeaders != null && routeHeaders.All( + h => upstreamHeaders.ContainsKey(h.Key) && routeHeaders[h.Key].Pattern.IsMatch(upstreamHeaders[h.Key])); } } diff --git a/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/IHeaderPlaceholderNameAndValueFinder.cs b/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/IHeaderPlaceholderNameAndValueFinder.cs index 3769b4dd7..91a816590 100644 --- a/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/IHeaderPlaceholderNameAndValueFinder.cs +++ b/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/IHeaderPlaceholderNameAndValueFinder.cs @@ -1,13 +1,10 @@ using Ocelot.DownstreamRouteFinder.UrlMatcher; using Ocelot.Values; -using System; using System.Collections.Generic; -using System.Text; -namespace Ocelot.DownstreamRouteFinder.HeaderMatcher +namespace Ocelot.DownstreamRouteFinder.HeaderMatcher; + +public interface IHeaderPlaceholderNameAndValueFinder { - public interface IHeaderPlaceholderNameAndValueFinder - { - List Find(Dictionary upstreamHeaders, Dictionary templateHeaders); - } + List Find(Dictionary upstreamHeaders, Dictionary templateHeaders); } diff --git a/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/IHeadersToHeaderTemplatesMatcher.cs b/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/IHeadersToHeaderTemplatesMatcher.cs index 2b3da66fb..157c67f87 100644 --- a/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/IHeadersToHeaderTemplatesMatcher.cs +++ b/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/IHeadersToHeaderTemplatesMatcher.cs @@ -1,14 +1,9 @@ -using Ocelot.DownstreamRouteFinder.UrlMatcher; -using Ocelot.Responses; -using Ocelot.Values; -using System; +using Ocelot.Values; using System.Collections.Generic; -using System.Text; -namespace Ocelot.DownstreamRouteFinder.HeaderMatcher +namespace Ocelot.DownstreamRouteFinder.HeaderMatcher; + +public interface IHeadersToHeaderTemplatesMatcher { - public interface IHeadersToHeaderTemplatesMatcher - { - bool Match(Dictionary upstreamHeaders, Dictionary routeHeaders); - } + bool Match(Dictionary upstreamHeaders, Dictionary routeHeaders); } diff --git a/src/Ocelot/Values/UpstreamHeaderTemplate.cs b/src/Ocelot/Values/UpstreamHeaderTemplate.cs index c4ed6b10d..2c76c734c 100644 --- a/src/Ocelot/Values/UpstreamHeaderTemplate.cs +++ b/src/Ocelot/Values/UpstreamHeaderTemplate.cs @@ -1,25 +1,21 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Text.RegularExpressions; +using System.Text.RegularExpressions; -namespace Ocelot.Values +namespace Ocelot.Values; + +public class UpstreamHeaderTemplate { - public class UpstreamHeaderTemplate - { - public string Template { get; } + public string Template { get; } - public string OriginalValue { get; } + public string OriginalValue { get; } - public Regex Pattern { get; } + public Regex Pattern { get; } - public UpstreamHeaderTemplate(string template, string originalValue) - { - Template = template; - OriginalValue = originalValue; - Pattern = template == null ? - new Regex("$^", RegexOptions.Compiled | RegexOptions.Singleline) : - new Regex(template, RegexOptions.Compiled | RegexOptions.Singleline); - } + public UpstreamHeaderTemplate(string template, string originalValue) + { + Template = template; + OriginalValue = originalValue; + Pattern = template == null ? + new Regex("$^", RegexOptions.Compiled | RegexOptions.Singleline) : + new Regex(template, RegexOptions.Compiled | RegexOptions.Singleline); } } diff --git a/test/Ocelot.AcceptanceTests/RoutingBasedOnHeadersTests.cs b/test/Ocelot.AcceptanceTests/RoutingBasedOnHeadersTests.cs index e92c348eb..113797be8 100644 --- a/test/Ocelot.AcceptanceTests/RoutingBasedOnHeadersTests.cs +++ b/test/Ocelot.AcceptanceTests/RoutingBasedOnHeadersTests.cs @@ -4,824 +4,822 @@ using System; using System.Collections.Generic; using System.Net; -using System.Text; using TestStack.BDDfy; using Xunit; -namespace Ocelot.AcceptanceTests +namespace Ocelot.AcceptanceTests; + +public class RoutingBasedOnHeadersTests : IDisposable { - public class RoutingBasedOnHeadersTests : IDisposable + private readonly Steps _steps; + private string _downstreamPath; + private readonly ServiceHandler _serviceHandler; + + public RoutingBasedOnHeadersTests() { - private readonly Steps _steps; - private string _downstreamPath; - private readonly ServiceHandler _serviceHandler; + _serviceHandler = new ServiceHandler(); + _steps = new Steps(); + } - public RoutingBasedOnHeadersTests() - { - _serviceHandler = new ServiceHandler(); - _steps = new Steps(); - } + [Fact] + public void should_match_one_header_value() + { + var port = RandomPortFinder.GetRandomPort(); + var headerName = "country_code"; + var headerValue = "PL"; - [Fact] - public void should_match_one_header_value() + var configuration = new FileConfiguration { - var port = RandomPortFinder.GetRandomPort(); - var headerName = "country_code"; - var headerValue = "PL"; - - var configuration = new FileConfiguration - { - Routes = new List + Routes = new List + { + new FileRoute { - new FileRoute + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List + new FileHostAndPort { - new FileHostAndPort - { - Host = "localhost", - Port = port, - }, - }, - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - UpstreamHeaderTemplates = new Dictionary() - { - [headerName] = headerValue, + Host = "localhost", + Port = port, }, }, - }, - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/", 200, "Hello from Laura")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .And(x => _steps.GivenIAddAHeader(headerName, headerValue)) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .BDDfy(); - } - - [Fact] - public void should_match_one_header_value_when_more_headers() - { - var port = RandomPortFinder.GetRandomPort(); - var headerName = "country_code"; - var headerValue = "PL"; - - var configuration = new FileConfiguration - { - Routes = new List - { - new FileRoute + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + UpstreamHeaderTemplates = new Dictionary() { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = port, - }, - }, - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - UpstreamHeaderTemplates = new Dictionary() - { - [headerName] = headerValue, - }, + [headerName] = headerValue, }, }, - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/", 200, "Hello from Laura")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .And(x => _steps.GivenIAddAHeader("other", "otherValue")) - .And(x => _steps.GivenIAddAHeader(headerName, headerValue)) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .BDDfy(); - } - - [Fact] - public void should_match_two_header_values_when_more_headers() - { - var port = RandomPortFinder.GetRandomPort(); - var headerName1 = "country_code"; - var headerValue1 = "PL"; - var headerName2 = "region"; - var headerValue2 = "MAZ"; + }, + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .And(x => _steps.GivenIAddAHeader(headerName, headerValue)) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } - var configuration = new FileConfiguration - { - Routes = new List + [Fact] + public void should_match_one_header_value_when_more_headers() + { + var port = RandomPortFinder.GetRandomPort(); + var headerName = "country_code"; + var headerValue = "PL"; + + var configuration = new FileConfiguration + { + Routes = new List + { + new FileRoute { - new FileRoute + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = port, - }, - }, - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - UpstreamHeaderTemplates = new Dictionary() + new FileHostAndPort { - [headerName1] = headerValue1, - [headerName2] = headerValue2, + Host = "localhost", + Port = port, }, }, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + UpstreamHeaderTemplates = new Dictionary() + { + [headerName] = headerValue, + }, }, - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/", 200, "Hello from Laura")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .And(x => _steps.GivenIAddAHeader(headerName1, headerValue1)) - .And(x => _steps.GivenIAddAHeader("other", "otherValue")) - .And(x => _steps.GivenIAddAHeader(headerName2, headerValue2)) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .BDDfy(); - } - - [Fact] - public void should_not_match_one_header_value() - { - var port = RandomPortFinder.GetRandomPort(); - var headerName = "country_code"; - var headerValue = "PL"; - var anotherHeaderValue = "UK"; + }, + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .And(x => _steps.GivenIAddAHeader("other", "otherValue")) + .And(x => _steps.GivenIAddAHeader(headerName, headerValue)) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } - var configuration = new FileConfiguration - { - Routes = new List + [Fact] + public void should_match_two_header_values_when_more_headers() + { + var port = RandomPortFinder.GetRandomPort(); + var headerName1 = "country_code"; + var headerValue1 = "PL"; + var headerName2 = "region"; + var headerValue2 = "MAZ"; + + var configuration = new FileConfiguration + { + Routes = new List + { + new FileRoute { - new FileRoute + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List + new FileHostAndPort { - new FileHostAndPort - { - Host = "localhost", - Port = port, - }, - }, - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - UpstreamHeaderTemplates = new Dictionary() - { - [headerName] = headerValue, + Host = "localhost", + Port = port, }, }, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + UpstreamHeaderTemplates = new Dictionary() + { + [headerName1] = headerValue1, + [headerName2] = headerValue2, + }, }, - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/", 200, "Hello from Laura")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .And(x => _steps.GivenIAddAHeader(headerName, anotherHeaderValue)) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.NotFound)) - .BDDfy(); - } - - [Fact] - public void should_not_match_one_header_value_when_no_headers() - { - var port = RandomPortFinder.GetRandomPort(); - var headerName = "country_code"; - var headerValue = "PL"; + }, + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .And(x => _steps.GivenIAddAHeader(headerName1, headerValue1)) + .And(x => _steps.GivenIAddAHeader("other", "otherValue")) + .And(x => _steps.GivenIAddAHeader(headerName2, headerValue2)) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } - var configuration = new FileConfiguration - { - Routes = new List + [Fact] + public void should_not_match_one_header_value() + { + var port = RandomPortFinder.GetRandomPort(); + var headerName = "country_code"; + var headerValue = "PL"; + var anotherHeaderValue = "UK"; + + var configuration = new FileConfiguration + { + Routes = new List + { + new FileRoute { - new FileRoute + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = port, - }, - }, - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - UpstreamHeaderTemplates = new Dictionary() + new FileHostAndPort { - [headerName] = headerValue, + Host = "localhost", + Port = port, }, }, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + UpstreamHeaderTemplates = new Dictionary() + { + [headerName] = headerValue, + }, }, - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/", 200, "Hello from Laura")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.NotFound)) - .BDDfy(); - } - - [Fact] - public void should_not_match_two_header_values_when_one_different() - { - var port = RandomPortFinder.GetRandomPort(); - var headerName1 = "country_code"; - var headerValue1 = "PL"; - var headerName2 = "region"; - var headerValue2 = "MAZ"; + }, + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .And(x => _steps.GivenIAddAHeader(headerName, anotherHeaderValue)) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.NotFound)) + .BDDfy(); + } - var configuration = new FileConfiguration - { - Routes = new List + [Fact] + public void should_not_match_one_header_value_when_no_headers() + { + var port = RandomPortFinder.GetRandomPort(); + var headerName = "country_code"; + var headerValue = "PL"; + + var configuration = new FileConfiguration + { + Routes = new List + { + new FileRoute { - new FileRoute + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = port, - }, - }, - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - UpstreamHeaderTemplates = new Dictionary() + new FileHostAndPort { - [headerName1] = headerValue1, - [headerName2] = headerValue2, + Host = "localhost", + Port = port, }, }, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + UpstreamHeaderTemplates = new Dictionary() + { + [headerName] = headerValue, + }, }, - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/", 200, "Hello from Laura")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .And(x => _steps.GivenIAddAHeader(headerName1, headerValue1)) - .And(x => _steps.GivenIAddAHeader("other", "otherValue")) - .And(x => _steps.GivenIAddAHeader(headerName2, "anothervalue")) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.NotFound)) - .BDDfy(); - } - - [Fact] - public void should_not_match_two_header_values_when_one_not_existing() - { - var port = RandomPortFinder.GetRandomPort(); - var headerName1 = "country_code"; - var headerValue1 = "PL"; - var headerName2 = "region"; - var headerValue2 = "MAZ"; + }, + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.NotFound)) + .BDDfy(); + } - var configuration = new FileConfiguration - { - Routes = new List + [Fact] + public void should_not_match_two_header_values_when_one_different() + { + var port = RandomPortFinder.GetRandomPort(); + var headerName1 = "country_code"; + var headerValue1 = "PL"; + var headerName2 = "region"; + var headerValue2 = "MAZ"; + + var configuration = new FileConfiguration + { + Routes = new List + { + new FileRoute { - new FileRoute + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List + new FileHostAndPort { - new FileHostAndPort - { - Host = "localhost", - Port = port, - }, - }, - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - UpstreamHeaderTemplates = new Dictionary() - { - [headerName1] = headerValue1, - [headerName2] = headerValue2, + Host = "localhost", + Port = port, }, }, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + UpstreamHeaderTemplates = new Dictionary() + { + [headerName1] = headerValue1, + [headerName2] = headerValue2, + }, }, - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/", 200, "Hello from Laura")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .And(x => _steps.GivenIAddAHeader(headerName1, headerValue1)) - .And(x => _steps.GivenIAddAHeader("other", "otherValue")) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.NotFound)) - .BDDfy(); - } - - [Fact] - public void should_not_match_one_header_value_when_header_duplicated() - { - var port = RandomPortFinder.GetRandomPort(); - var headerName = "country_code"; - var headerValue = "PL"; + }, + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .And(x => _steps.GivenIAddAHeader(headerName1, headerValue1)) + .And(x => _steps.GivenIAddAHeader("other", "otherValue")) + .And(x => _steps.GivenIAddAHeader(headerName2, "anothervalue")) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.NotFound)) + .BDDfy(); + } - var configuration = new FileConfiguration - { - Routes = new List + [Fact] + public void should_not_match_two_header_values_when_one_not_existing() + { + var port = RandomPortFinder.GetRandomPort(); + var headerName1 = "country_code"; + var headerValue1 = "PL"; + var headerName2 = "region"; + var headerValue2 = "MAZ"; + + var configuration = new FileConfiguration + { + Routes = new List + { + new FileRoute { - new FileRoute + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = port, - }, - }, - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - UpstreamHeaderTemplates = new Dictionary() + new FileHostAndPort { - [headerName] = headerValue, + Host = "localhost", + Port = port, }, }, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + UpstreamHeaderTemplates = new Dictionary() + { + [headerName1] = headerValue1, + [headerName2] = headerValue2, + }, }, - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/", 200, "Hello from Laura")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .And(x => _steps.GivenIAddAHeader(headerName, headerValue)) - .And(x => _steps.GivenIAddAHeader(headerName, "othervalue")) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.NotFound)) - .BDDfy(); - } - - [Fact] - public void should_aggregated_route_match_header_value() - { - var port1 = RandomPortFinder.GetRandomPort(); - var port2 = RandomPortFinder.GetRandomPort(); - var headerName = "country_code"; - var headerValue = "PL"; + }, + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .And(x => _steps.GivenIAddAHeader(headerName1, headerValue1)) + .And(x => _steps.GivenIAddAHeader("other", "otherValue")) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.NotFound)) + .BDDfy(); + } - var configuration = new FileConfiguration - { - Routes = new List + [Fact] + public void should_not_match_one_header_value_when_header_duplicated() + { + var port = RandomPortFinder.GetRandomPort(); + var headerName = "country_code"; + var headerValue = "PL"; + + var configuration = new FileConfiguration + { + Routes = new List + { + new FileRoute { - new FileRoute + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List { - DownstreamPathTemplate = "/a", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List + new FileHostAndPort { - new FileHostAndPort - { - Host = "localhost", - Port = port1, - }, + Host = "localhost", + Port = port, }, - UpstreamPathTemplate = "/a", - UpstreamHttpMethod = new List { "Get" }, - Key = "Laura", }, - new FileRoute + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + UpstreamHeaderTemplates = new Dictionary() { - DownstreamPathTemplate = "/b", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = port2, - }, - }, - UpstreamPathTemplate = "/b", - UpstreamHttpMethod = new List { "Get" }, - Key = "Tom", + [headerName] = headerValue, }, }, - Aggregates = new List() + }, + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .And(x => _steps.GivenIAddAHeader(headerName, headerValue)) + .And(x => _steps.GivenIAddAHeader(headerName, "othervalue")) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.NotFound)) + .BDDfy(); + } + + [Fact] + public void should_aggregated_route_match_header_value() + { + var port1 = RandomPortFinder.GetRandomPort(); + var port2 = RandomPortFinder.GetRandomPort(); + var headerName = "country_code"; + var headerValue = "PL"; + + var configuration = new FileConfiguration + { + Routes = new List { - new FileAggregateRoute + new FileRoute + { + DownstreamPathTemplate = "/a", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List { - UpstreamPathTemplate = "/", - UpstreamHost = "localhost", - RouteKeys = new List + new FileHostAndPort { - "Laura", - "Tom", + Host = "localhost", + Port = port1, }, - UpstreamHeaderTemplates = new Dictionary() + }, + UpstreamPathTemplate = "/a", + UpstreamHttpMethod = new List { "Get" }, + Key = "Laura", + }, + new FileRoute + { + DownstreamPathTemplate = "/b", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort { - [headerName] = headerValue, + Host = "localhost", + Port = port2, }, }, + UpstreamPathTemplate = "/b", + UpstreamHttpMethod = new List { "Get" }, + Key = "Tom", + }, }, - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port1}", "/a", 200, "Hello from Laura")) - .And(x => GivenThereIsAServiceRunningOn($"http://localhost:{port2}", "/b", 200, "Hello from Tom")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .And(x => _steps.GivenIAddAHeader(headerName, headerValue)) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .BDDfy(); - } - - [Fact] - public void should_aggregated_route_not_match_header_value() - { - var port1 = RandomPortFinder.GetRandomPort(); - var port2 = RandomPortFinder.GetRandomPort(); - var headerName = "country_code"; - var headerValue = "PL"; - - var configuration = new FileConfiguration + Aggregates = new List() { - Routes = new List + new FileAggregateRoute { - new FileRoute + UpstreamPathTemplate = "/", + UpstreamHost = "localhost", + RouteKeys = new List { - DownstreamPathTemplate = "/a", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = port1, - }, - }, - UpstreamPathTemplate = "/a", - UpstreamHttpMethod = new List { "Get" }, - Key = "Laura", + "Laura", + "Tom", }, - new FileRoute + UpstreamHeaderTemplates = new Dictionary() { - DownstreamPathTemplate = "/b", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = port2, - }, - }, - UpstreamPathTemplate = "/b", - UpstreamHttpMethod = new List { "Get" }, - Key = "Tom", + [headerName] = headerValue, }, }, - Aggregates = new List() + }, + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port1}", "/a", 200, "Hello from Laura")) + .And(x => GivenThereIsAServiceRunningOn($"http://localhost:{port2}", "/b", 200, "Hello from Tom")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .And(x => _steps.GivenIAddAHeader(headerName, headerValue)) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .BDDfy(); + } + + [Fact] + public void should_aggregated_route_not_match_header_value() + { + var port1 = RandomPortFinder.GetRandomPort(); + var port2 = RandomPortFinder.GetRandomPort(); + var headerName = "country_code"; + var headerValue = "PL"; + + var configuration = new FileConfiguration + { + Routes = new List { - new FileAggregateRoute + new FileRoute + { + DownstreamPathTemplate = "/a", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List { - UpstreamPathTemplate = "/", - UpstreamHost = "localhost", - RouteKeys = new List + new FileHostAndPort { - "Laura", - "Tom", + Host = "localhost", + Port = port1, }, - UpstreamHeaderTemplates = new Dictionary() + }, + UpstreamPathTemplate = "/a", + UpstreamHttpMethod = new List { "Get" }, + Key = "Laura", + }, + new FileRoute + { + DownstreamPathTemplate = "/b", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort { - [headerName] = headerValue, + Host = "localhost", + Port = port2, }, }, + UpstreamPathTemplate = "/b", + UpstreamHttpMethod = new List { "Get" }, + Key = "Tom", + }, }, - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port1}", "/a", 200, "Hello from Laura")) - .And(x => GivenThereIsAServiceRunningOn($"http://localhost:{port2}", "/b", 200, "Hello from Tom")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.NotFound)) - .BDDfy(); - } - - [Fact] - public void should_match_header_placeholder() - { - var port = RandomPortFinder.GetRandomPort(); - var headerName = "Region"; - - var configuration = new FileConfiguration + Aggregates = new List() { - Routes = new List + new FileAggregateRoute { - new FileRoute + UpstreamPathTemplate = "/", + UpstreamHost = "localhost", + RouteKeys = new List { - DownstreamPathTemplate = "/api.internal-{code}/products", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = port, - }, - }, - UpstreamPathTemplate = "/products", - UpstreamHttpMethod = new List { "Get" }, - UpstreamHeaderTemplates = new Dictionary() - { - [headerName] = "{header:code}", - }, + "Laura", + "Tom", + }, + UpstreamHeaderTemplates = new Dictionary() + { + [headerName] = headerValue, }, }, - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/api.internal-uk/products", 200, "Hello from UK")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .And(x => _steps.GivenIAddAHeader(headerName, "uk")) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/products")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from UK")) - .BDDfy(); - } - - [Fact] - public void should_match_header_placeholder_not_in_downstream_path() - { - var port = RandomPortFinder.GetRandomPort(); - var headerName = "ProductName"; + }, + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port1}", "/a", 200, "Hello from Laura")) + .And(x => GivenThereIsAServiceRunningOn($"http://localhost:{port2}", "/b", 200, "Hello from Tom")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.NotFound)) + .BDDfy(); + } - var configuration = new FileConfiguration - { - Routes = new List + [Fact] + public void should_match_header_placeholder() + { + var port = RandomPortFinder.GetRandomPort(); + var headerName = "Region"; + + var configuration = new FileConfiguration + { + Routes = new List + { + new FileRoute { - new FileRoute + DownstreamPathTemplate = "/api.internal-{code}/products", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List { - DownstreamPathTemplate = "/products-info", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List + new FileHostAndPort { - new FileHostAndPort - { - Host = "localhost", - Port = port, - }, - }, - UpstreamPathTemplate = "/products", - UpstreamHttpMethod = new List { "Get" }, - UpstreamHeaderTemplates = new Dictionary() - { - [headerName] = "product-{header:everything}", + Host = "localhost", + Port = port, }, }, + UpstreamPathTemplate = "/products", + UpstreamHttpMethod = new List { "Get" }, + UpstreamHeaderTemplates = new Dictionary() + { + [headerName] = "{header:code}", + }, }, - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/products-info", 200, "Hello from products")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .And(x => _steps.GivenIAddAHeader(headerName, "product-Camera")) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/products")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from products")) - .BDDfy(); - } - - [Fact] - public void should_distinguish_route_for_different_roles() - { - var port = RandomPortFinder.GetRandomPort(); - var headerName = "Origin"; + }, + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/api.internal-uk/products", 200, "Hello from UK")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .And(x => _steps.GivenIAddAHeader(headerName, "uk")) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/products")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from UK")) + .BDDfy(); + } - var configuration = new FileConfiguration - { - Routes = new List + [Fact] + public void should_match_header_placeholder_not_in_downstream_path() + { + var port = RandomPortFinder.GetRandomPort(); + var headerName = "ProductName"; + + var configuration = new FileConfiguration + { + Routes = new List + { + new FileRoute { - new FileRoute + DownstreamPathTemplate = "/products-info", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List { - DownstreamPathTemplate = "/products-admin", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = port, - }, - }, - UpstreamPathTemplate = "/products", - UpstreamHttpMethod = new List { "Get" }, - UpstreamHeaderTemplates = new Dictionary() + new FileHostAndPort { - [headerName] = "admin.xxx.com", + Host = "localhost", + Port = port, }, }, - new FileRoute + UpstreamPathTemplate = "/products", + UpstreamHttpMethod = new List { "Get" }, + UpstreamHeaderTemplates = new Dictionary() { - DownstreamPathTemplate = "/products", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = port, - }, - }, - UpstreamPathTemplate = "/products", - UpstreamHttpMethod = new List { "Get" }, + [headerName] = "product-{header:everything}", }, }, - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/products-admin", 200, "Hello from products admin")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .And(x => _steps.GivenIAddAHeader(headerName, "admin.xxx.com")) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/products")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from products admin")) - .BDDfy(); - } - - [Fact] - public void should_match_header_and_url_placeholders() - { - var port = RandomPortFinder.GetRandomPort(); - var headerName = "country_code"; + }, + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/products-info", 200, "Hello from products")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .And(x => _steps.GivenIAddAHeader(headerName, "product-Camera")) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/products")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from products")) + .BDDfy(); + } - var configuration = new FileConfiguration - { - Routes = new List + [Fact] + public void should_distinguish_route_for_different_roles() + { + var port = RandomPortFinder.GetRandomPort(); + var headerName = "Origin"; + + var configuration = new FileConfiguration + { + Routes = new List + { + new FileRoute { - new FileRoute + DownstreamPathTemplate = "/products-admin", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List { - DownstreamPathTemplate = "/{country_code}/{version}/{aa}", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List + new FileHostAndPort { - new FileHostAndPort - { - Host = "localhost", - Port = port, - }, + Host = "localhost", + Port = port, }, - UpstreamPathTemplate = "/{aa}", - UpstreamHttpMethod = new List { "Get" }, - UpstreamHeaderTemplates = new Dictionary() + }, + UpstreamPathTemplate = "/products", + UpstreamHttpMethod = new List { "Get" }, + UpstreamHeaderTemplates = new Dictionary() + { + [headerName] = "admin.xxx.com", + }, + }, + new FileRoute + { + DownstreamPathTemplate = "/products", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort { - [headerName] = "start_{header:country_code}_version_{header:version}_end", + Host = "localhost", + Port = port, }, }, + UpstreamPathTemplate = "/products", + UpstreamHttpMethod = new List { "Get" }, }, - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/pl/v1/bb", 200, "Hello from Laura")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .And(x => _steps.GivenIAddAHeader(headerName, "start_pl_version_v1_end")) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/bb")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .BDDfy(); - } - - [Fact] - public void should_match_header_with_braces() - { - var port = RandomPortFinder.GetRandomPort(); - var headerName = "country_code"; + }, + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/products-admin", 200, "Hello from products admin")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .And(x => _steps.GivenIAddAHeader(headerName, "admin.xxx.com")) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/products")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from products admin")) + .BDDfy(); + } - var configuration = new FileConfiguration - { - Routes = new List + [Fact] + public void should_match_header_and_url_placeholders() + { + var port = RandomPortFinder.GetRandomPort(); + var headerName = "country_code"; + + var configuration = new FileConfiguration + { + Routes = new List + { + new FileRoute { - new FileRoute + DownstreamPathTemplate = "/{country_code}/{version}/{aa}", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List { - DownstreamPathTemplate = "/aa", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List + new FileHostAndPort { - new FileHostAndPort - { - Host = "localhost", - Port = port, - }, - }, - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - UpstreamHeaderTemplates = new Dictionary() - { - [headerName] = "my_{header}", + Host = "localhost", + Port = port, }, }, + UpstreamPathTemplate = "/{aa}", + UpstreamHttpMethod = new List { "Get" }, + UpstreamHeaderTemplates = new Dictionary() + { + [headerName] = "start_{header:country_code}_version_{header:version}_end", + }, }, - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/aa", 200, "Hello from Laura")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .And(x => _steps.GivenIAddAHeader(headerName, "my_{header}")) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .BDDfy(); - } - - [Fact] - public void should_match_two_headers_with_the_same_name() - { - var port = RandomPortFinder.GetRandomPort(); - var headerName = "country_code"; - var headerValue1 = "PL"; - var headerValue2 = "UK"; + }, + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/pl/v1/bb", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .And(x => _steps.GivenIAddAHeader(headerName, "start_pl_version_v1_end")) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/bb")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } - var configuration = new FileConfiguration - { - Routes = new List + [Fact] + public void should_match_header_with_braces() + { + var port = RandomPortFinder.GetRandomPort(); + var headerName = "country_code"; + + var configuration = new FileConfiguration + { + Routes = new List + { + new FileRoute { - new FileRoute + DownstreamPathTemplate = "/aa", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List + new FileHostAndPort { - new FileHostAndPort - { - Host = "localhost", - Port = port, - }, + Host = "localhost", + Port = port, }, - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - UpstreamHeaderTemplates = new Dictionary() + }, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + UpstreamHeaderTemplates = new Dictionary() + { + [headerName] = "my_{header}", + }, + }, + }, + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/aa", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .And(x => _steps.GivenIAddAHeader(headerName, "my_{header}")) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } + + [Fact] + public void should_match_two_headers_with_the_same_name() + { + var port = RandomPortFinder.GetRandomPort(); + var headerName = "country_code"; + var headerValue1 = "PL"; + var headerValue2 = "UK"; + + var configuration = new FileConfiguration + { + Routes = new List + { + new FileRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort { - [headerName] = headerValue1 + ";{header:whatever}", + Host = "localhost", + Port = port, }, }, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + UpstreamHeaderTemplates = new Dictionary() + { + [headerName] = headerValue1 + ";{header:whatever}", + }, }, - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/", 200, "Hello from Laura")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .And(x => _steps.GivenIAddAHeader(headerName, headerValue1)) - .And(x => _steps.GivenIAddAHeader(headerName, headerValue2)) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .BDDfy(); - } - - private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode, string responseBody) + }, + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .And(x => _steps.GivenIAddAHeader(headerName, headerValue1)) + .And(x => _steps.GivenIAddAHeader(headerName, headerValue2)) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } + + private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode, string responseBody) + { + _serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, basePath, async context => { - _serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, basePath, async context => - { - _downstreamPath = !string.IsNullOrEmpty(context.Request.PathBase.Value) ? context.Request.PathBase.Value : context.Request.Path.Value; + _downstreamPath = !string.IsNullOrEmpty(context.Request.PathBase.Value) ? context.Request.PathBase.Value : context.Request.Path.Value; - if (_downstreamPath != basePath) - { - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync("downstream path didnt match base path"); - } - else - { - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync(responseBody); - } - }); - } + if (_downstreamPath != basePath) + { + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync("downstream path didnt match base path"); + } + else + { + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(responseBody); + } + }); + } - internal void ThenTheDownstreamUrlPathShouldBe(string expectedDownstreamPath) - { - _downstreamPath.ShouldBe(expectedDownstreamPath); - } + internal void ThenTheDownstreamUrlPathShouldBe(string expectedDownstreamPath) + { + _downstreamPath.ShouldBe(expectedDownstreamPath); + } - public void Dispose() - { - _serviceHandler.Dispose(); - _steps.Dispose(); - } + public void Dispose() + { + _serviceHandler.Dispose(); + _steps.Dispose(); } } diff --git a/test/Ocelot.UnitTests/Configuration/UpstreamHeaderTemplatePatternCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/UpstreamHeaderTemplatePatternCreatorTests.cs index 2464f18b1..23d98789b 100644 --- a/test/Ocelot.UnitTests/Configuration/UpstreamHeaderTemplatePatternCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/UpstreamHeaderTemplatePatternCreatorTests.cs @@ -2,141 +2,138 @@ using Ocelot.Configuration.File; using Ocelot.Values; using Shouldly; -using System; using System.Collections.Generic; -using System.Text; using TestStack.BDDfy; using Xunit; -namespace Ocelot.UnitTests.Configuration +namespace Ocelot.UnitTests.Configuration; + +public class UpstreamHeaderTemplatePatternCreatorTests { - public class UpstreamHeaderTemplatePatternCreatorTests - { - private FileRoute _fileRoute; - private readonly UpstreamHeaderTemplatePatternCreator _creator; - private Dictionary _result; + private FileRoute _fileRoute; + private readonly UpstreamHeaderTemplatePatternCreator _creator; + private Dictionary _result; - public UpstreamHeaderTemplatePatternCreatorTests() - { - _creator = new UpstreamHeaderTemplatePatternCreator(); - } + public UpstreamHeaderTemplatePatternCreatorTests() + { + _creator = new UpstreamHeaderTemplatePatternCreator(); + } - [Fact] - public void should_create_pattern_without_placeholders() + [Fact] + public void should_create_pattern_without_placeholders() + { + var fileRoute = new FileRoute { - var fileRoute = new FileRoute + UpstreamHeaderTemplates = new Dictionary { - UpstreamHeaderTemplates = new Dictionary - { - ["country"] = "a text without placeholders", - }, - }; - - this.Given(x => x.GivenTheFollowingFileRoute(fileRoute)) - .When(x => x.WhenICreateTheTemplatePattern()) - .Then(x => x.ThenTheFollowingIsReturned("country", "^(?i)a text without placeholders$")) - .BDDfy(); - } - - [Fact] - public void should_create_pattern_case_sensitive() + ["country"] = "a text without placeholders", + }, + }; + + this.Given(x => x.GivenTheFollowingFileRoute(fileRoute)) + .When(x => x.WhenICreateTheTemplatePattern()) + .Then(x => x.ThenTheFollowingIsReturned("country", "^(?i)a text without placeholders$")) + .BDDfy(); + } + + [Fact] + public void should_create_pattern_case_sensitive() + { + var fileRoute = new FileRoute { - var fileRoute = new FileRoute + RouteIsCaseSensitive = true, + UpstreamHeaderTemplates = new Dictionary { - RouteIsCaseSensitive = true, - UpstreamHeaderTemplates = new Dictionary - { - ["country"] = "a text without placeholders", - }, - }; - - this.Given(x => x.GivenTheFollowingFileRoute(fileRoute)) - .When(x => x.WhenICreateTheTemplatePattern()) - .Then(x => x.ThenTheFollowingIsReturned("country", "^a text without placeholders$")) - .BDDfy(); - } - - [Fact] - public void should_create_pattern_with_placeholder_in_the_beginning() + ["country"] = "a text without placeholders", + }, + }; + + this.Given(x => x.GivenTheFollowingFileRoute(fileRoute)) + .When(x => x.WhenICreateTheTemplatePattern()) + .Then(x => x.ThenTheFollowingIsReturned("country", "^a text without placeholders$")) + .BDDfy(); + } + + [Fact] + public void should_create_pattern_with_placeholder_in_the_beginning() + { + var fileRoute = new FileRoute { - var fileRoute = new FileRoute + UpstreamHeaderTemplates = new Dictionary { - UpstreamHeaderTemplates = new Dictionary - { - ["country"] = "{header:start}rest of the text", - }, - }; - - this.Given(x => x.GivenTheFollowingFileRoute(fileRoute)) - .When(x => x.WhenICreateTheTemplatePattern()) - .Then(x => x.ThenTheFollowingIsReturned("country", "^(?i)(?.+)rest of the text$")) - .BDDfy(); - } - - [Fact] - public void should_create_pattern_with_placeholder_at_the_end() + ["country"] = "{header:start}rest of the text", + }, + }; + + this.Given(x => x.GivenTheFollowingFileRoute(fileRoute)) + .When(x => x.WhenICreateTheTemplatePattern()) + .Then(x => x.ThenTheFollowingIsReturned("country", "^(?i)(?.+)rest of the text$")) + .BDDfy(); + } + + [Fact] + public void should_create_pattern_with_placeholder_at_the_end() + { + var fileRoute = new FileRoute { - var fileRoute = new FileRoute + UpstreamHeaderTemplates = new Dictionary { - UpstreamHeaderTemplates = new Dictionary - { - ["country"] = "rest of the text{header:end}", - }, - }; - - this.Given(x => x.GivenTheFollowingFileRoute(fileRoute)) - .When(x => x.WhenICreateTheTemplatePattern()) - .Then(x => x.ThenTheFollowingIsReturned("country", "^(?i)rest of the text(?.+)$")) - .BDDfy(); - } - - [Fact] - public void should_create_pattern_with_placeholder_only() + ["country"] = "rest of the text{header:end}", + }, + }; + + this.Given(x => x.GivenTheFollowingFileRoute(fileRoute)) + .When(x => x.WhenICreateTheTemplatePattern()) + .Then(x => x.ThenTheFollowingIsReturned("country", "^(?i)rest of the text(?.+)$")) + .BDDfy(); + } + + [Fact] + public void should_create_pattern_with_placeholder_only() + { + var fileRoute = new FileRoute { - var fileRoute = new FileRoute + UpstreamHeaderTemplates = new Dictionary { - UpstreamHeaderTemplates = new Dictionary - { - ["country"] = "{header:countrycode}", - }, - }; - - this.Given(x => x.GivenTheFollowingFileRoute(fileRoute)) - .When(x => x.WhenICreateTheTemplatePattern()) - .Then(x => x.ThenTheFollowingIsReturned("country", "^(?i)(?.+)$")) - .BDDfy(); - } - - [Fact] - public void should_crate_pattern_with_more_placeholders() + ["country"] = "{header:countrycode}", + }, + }; + + this.Given(x => x.GivenTheFollowingFileRoute(fileRoute)) + .When(x => x.WhenICreateTheTemplatePattern()) + .Then(x => x.ThenTheFollowingIsReturned("country", "^(?i)(?.+)$")) + .BDDfy(); + } + + [Fact] + public void should_crate_pattern_with_more_placeholders() + { + var fileRoute = new FileRoute { - var fileRoute = new FileRoute + UpstreamHeaderTemplates = new Dictionary { - UpstreamHeaderTemplates = new Dictionary - { - ["country"] = "any text {header:cc} and other {header:version} and {header:bob} the end", - }, - }; - - this.Given(x => x.GivenTheFollowingFileRoute(fileRoute)) - .When(x => x.WhenICreateTheTemplatePattern()) - .Then(x => x.ThenTheFollowingIsReturned("country", "^(?i)any text (?.+) and other (?.+) and (?.+) the end$")) - .BDDfy(); - } - - private void GivenTheFollowingFileRoute(FileRoute fileRoute) - { - _fileRoute = fileRoute; - } + ["country"] = "any text {header:cc} and other {header:version} and {header:bob} the end", + }, + }; + + this.Given(x => x.GivenTheFollowingFileRoute(fileRoute)) + .When(x => x.WhenICreateTheTemplatePattern()) + .Then(x => x.ThenTheFollowingIsReturned("country", "^(?i)any text (?.+) and other (?.+) and (?.+) the end$")) + .BDDfy(); + } - private void WhenICreateTheTemplatePattern() - { - _result = _creator.Create(_fileRoute); - } + private void GivenTheFollowingFileRoute(FileRoute fileRoute) + { + _fileRoute = fileRoute; + } - private void ThenTheFollowingIsReturned(string headerKey, string expected) - { - _result[headerKey].Template.ShouldBe(expected); - } + private void WhenICreateTheTemplatePattern() + { + _result = _creator.Create(_fileRoute); + } + + private void ThenTheFollowingIsReturned(string headerKey, string expected) + { + _result[headerKey].Template.ShouldBe(expected); } } diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinderTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinderTests.cs index 1395a84e5..86ae67359 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinderTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinderTests.cs @@ -2,203 +2,200 @@ using Ocelot.DownstreamRouteFinder.UrlMatcher; using Ocelot.Values; using Shouldly; -using System; using System.Collections.Generic; using System.Linq; -using System.Text; using TestStack.BDDfy; using Xunit; -namespace Ocelot.UnitTests.DownstreamRouteFinder.HeaderMatcher +namespace Ocelot.UnitTests.DownstreamRouteFinder.HeaderMatcher; + +public class HeaderPlaceholderNameAndValueFinderTests { - public class HeaderPlaceholderNameAndValueFinderTests + private readonly IHeaderPlaceholderNameAndValueFinder _finder; + private Dictionary _upstreamHeaders; + private Dictionary _upstreamHeaderTemplates; + private List _result; + + public HeaderPlaceholderNameAndValueFinderTests() + { + _finder = new HeaderPlaceholderNameAndValueFinder(); + } + + [Fact] + public void should_return_no_placeholders() + { + var upstreamHeaderTemplates = new Dictionary(); + var upstreamHeaders = new Dictionary(); + var expected = new List(); + + this.Given(x => x.GivenUpstreamHeaderTemplatesAre(upstreamHeaderTemplates)) + .And(x => x.GivenUpstreamHeadersAre(upstreamHeaders)) + .When(x => x.WhenICallFindPlaceholders()) + .Then(x => x.TheResultIs(expected)) + .BDDfy(); + } + + [Fact] + public void should_return_one_placeholder_with_value_when_no_other_text() + { + var upstreamHeaderTemplates = new Dictionary + { + ["country"] = new UpstreamHeaderTemplate("^(?i)(?.+)$", "{header:countrycode}"), + }; + var upstreamHeaders = new Dictionary + { + ["country"] = "PL", + }; + var expected = new List + { + new PlaceholderNameAndValue("{countrycode}", "PL"), + }; + + this.Given(x => x.GivenUpstreamHeaderTemplatesAre(upstreamHeaderTemplates)) + .And(x => x.GivenUpstreamHeadersAre(upstreamHeaders)) + .When(x => x.WhenICallFindPlaceholders()) + .Then(x => x.TheResultIs(expected)) + .BDDfy(); + } + + [Fact] + public void should_return_one_placeholder_with_value_when_other_text_on_the_right() + { + var upstreamHeaderTemplates = new Dictionary + { + ["country"] = new UpstreamHeaderTemplate("^(?.+)-V1$", "{header:countrycode}-V1"), + }; + var upstreamHeaders = new Dictionary + { + ["country"] = "PL-V1", + }; + var expected = new List + { + new PlaceholderNameAndValue("{countrycode}", "PL"), + }; + + this.Given(x => x.GivenUpstreamHeaderTemplatesAre(upstreamHeaderTemplates)) + .And(x => x.GivenUpstreamHeadersAre(upstreamHeaders)) + .When(x => x.WhenICallFindPlaceholders()) + .Then(x => x.TheResultIs(expected)) + .BDDfy(); + } + + [Fact] + public void should_return_one_placeholder_with_value_when_other_text_on_the_left() + { + var upstreamHeaderTemplates = new Dictionary + { + ["country"] = new UpstreamHeaderTemplate("^V1-(?.+)$", "V1-{header:countrycode}"), + }; + var upstreamHeaders = new Dictionary + { + ["country"] = "V1-PL", + }; + var expected = new List + { + new PlaceholderNameAndValue("{countrycode}", "PL"), + }; + + this.Given(x => x.GivenUpstreamHeaderTemplatesAre(upstreamHeaderTemplates)) + .And(x => x.GivenUpstreamHeadersAre(upstreamHeaders)) + .When(x => x.WhenICallFindPlaceholders()) + .Then(x => x.TheResultIs(expected)) + .BDDfy(); + } + + [Fact] + public void should_return_one_placeholder_with_value_when_other_texts_surrounding() + { + var upstreamHeaderTemplates = new Dictionary + { + ["country"] = new UpstreamHeaderTemplate("^cc:(?.+)-V1$", "cc:{header:countrycode}-V1"), + }; + var upstreamHeaders = new Dictionary + { + ["country"] = "cc:PL-V1", + }; + var expected = new List + { + new PlaceholderNameAndValue("{countrycode}", "PL"), + }; + + this.Given(x => x.GivenUpstreamHeaderTemplatesAre(upstreamHeaderTemplates)) + .And(x => x.GivenUpstreamHeadersAre(upstreamHeaders)) + .When(x => x.WhenICallFindPlaceholders()) + .Then(x => x.TheResultIs(expected)) + .BDDfy(); + } + + [Fact] + public void should_return_two_placeholders_with_text_between() + { + var upstreamHeaderTemplates = new Dictionary + { + ["countryAndVersion"] = new UpstreamHeaderTemplate("^(?i)(?.+)-(?.+)$", "{header:countrycode}-{header:version}"), + }; + var upstreamHeaders = new Dictionary + { + ["countryAndVersion"] = "PL-v1", + }; + var expected = new List + { + new PlaceholderNameAndValue("{countrycode}", "PL"), + new PlaceholderNameAndValue("{version}", "v1"), + }; + + this.Given(x => x.GivenUpstreamHeaderTemplatesAre(upstreamHeaderTemplates)) + .And(x => x.GivenUpstreamHeadersAre(upstreamHeaders)) + .When(x => x.WhenICallFindPlaceholders()) + .Then(x => x.TheResultIs(expected)) + .BDDfy(); + } + + [Fact] + public void should_return_placeholders_from_different_headers() + { + var upstreamHeaderTemplates = new Dictionary + { + ["country"] = new UpstreamHeaderTemplate("^(?i)(?.+)$", "{header:countrycode}"), + ["version"] = new UpstreamHeaderTemplate("^(?i)(?.+)$", "{header:version}"), + }; + var upstreamHeaders = new Dictionary + { + ["country"] = "PL", + ["version"] = "v1", + }; + var expected = new List + { + new PlaceholderNameAndValue("{countrycode}", "PL"), + new PlaceholderNameAndValue("{version}", "v1"), + }; + + this.Given(x => x.GivenUpstreamHeaderTemplatesAre(upstreamHeaderTemplates)) + .And(x => x.GivenUpstreamHeadersAre(upstreamHeaders)) + .When(x => x.WhenICallFindPlaceholders()) + .Then(x => x.TheResultIs(expected)) + .BDDfy(); + } + + private void GivenUpstreamHeaderTemplatesAre(Dictionary upstreaHeaderTemplates) + { + _upstreamHeaderTemplates = upstreaHeaderTemplates; + } + + private void GivenUpstreamHeadersAre(Dictionary upstreamHeaders) + { + _upstreamHeaders = upstreamHeaders; + } + + private void WhenICallFindPlaceholders() + { + _result = _finder.Find(_upstreamHeaders, _upstreamHeaderTemplates); + } + + private void TheResultIs(List expected) { - private readonly IHeaderPlaceholderNameAndValueFinder _finder; - private Dictionary _upstreamHeaders; - private Dictionary _upstreamHeaderTemplates; - private List _result; - - public HeaderPlaceholderNameAndValueFinderTests() - { - _finder = new HeaderPlaceholderNameAndValueFinder(); - } - - [Fact] - public void should_return_no_placeholders() - { - var upstreamHeaderTemplates = new Dictionary(); - var upstreamHeaders = new Dictionary(); - var expected = new List(); - - this.Given(x => x.GivenUpstreamHeaderTemplatesAre(upstreamHeaderTemplates)) - .And(x => x.GivenUpstreamHeadersAre(upstreamHeaders)) - .When(x => x.WhenICallFindPlaceholders()) - .Then(x => x.TheResultIs(expected)) - .BDDfy(); - } - - [Fact] - public void should_return_one_placeholder_with_value_when_no_other_text() - { - var upstreamHeaderTemplates = new Dictionary - { - ["country"] = new UpstreamHeaderTemplate("^(?i)(?.+)$", "{header:countrycode}"), - }; - var upstreamHeaders = new Dictionary - { - ["country"] = "PL", - }; - var expected = new List - { - new PlaceholderNameAndValue("{countrycode}", "PL"), - }; - - this.Given(x => x.GivenUpstreamHeaderTemplatesAre(upstreamHeaderTemplates)) - .And(x => x.GivenUpstreamHeadersAre(upstreamHeaders)) - .When(x => x.WhenICallFindPlaceholders()) - .Then(x => x.TheResultIs(expected)) - .BDDfy(); - } - - [Fact] - public void should_return_one_placeholder_with_value_when_other_text_on_the_right() - { - var upstreamHeaderTemplates = new Dictionary - { - ["country"] = new UpstreamHeaderTemplate("^(?.+)-V1$", "{header:countrycode}-V1"), - }; - var upstreamHeaders = new Dictionary - { - ["country"] = "PL-V1", - }; - var expected = new List - { - new PlaceholderNameAndValue("{countrycode}", "PL"), - }; - - this.Given(x => x.GivenUpstreamHeaderTemplatesAre(upstreamHeaderTemplates)) - .And(x => x.GivenUpstreamHeadersAre(upstreamHeaders)) - .When(x => x.WhenICallFindPlaceholders()) - .Then(x => x.TheResultIs(expected)) - .BDDfy(); - } - - [Fact] - public void should_return_one_placeholder_with_value_when_other_text_on_the_left() - { - var upstreamHeaderTemplates = new Dictionary - { - ["country"] = new UpstreamHeaderTemplate("^V1-(?.+)$", "V1-{header:countrycode}"), - }; - var upstreamHeaders = new Dictionary - { - ["country"] = "V1-PL", - }; - var expected = new List - { - new PlaceholderNameAndValue("{countrycode}", "PL"), - }; - - this.Given(x => x.GivenUpstreamHeaderTemplatesAre(upstreamHeaderTemplates)) - .And(x => x.GivenUpstreamHeadersAre(upstreamHeaders)) - .When(x => x.WhenICallFindPlaceholders()) - .Then(x => x.TheResultIs(expected)) - .BDDfy(); - } - - [Fact] - public void should_return_one_placeholder_with_value_when_other_texts_surrounding() - { - var upstreamHeaderTemplates = new Dictionary - { - ["country"] = new UpstreamHeaderTemplate("^cc:(?.+)-V1$", "cc:{header:countrycode}-V1"), - }; - var upstreamHeaders = new Dictionary - { - ["country"] = "cc:PL-V1", - }; - var expected = new List - { - new PlaceholderNameAndValue("{countrycode}", "PL"), - }; - - this.Given(x => x.GivenUpstreamHeaderTemplatesAre(upstreamHeaderTemplates)) - .And(x => x.GivenUpstreamHeadersAre(upstreamHeaders)) - .When(x => x.WhenICallFindPlaceholders()) - .Then(x => x.TheResultIs(expected)) - .BDDfy(); - } - - [Fact] - public void should_return_two_placeholders_with_text_between() - { - var upstreamHeaderTemplates = new Dictionary - { - ["countryAndVersion"] = new UpstreamHeaderTemplate("^(?i)(?.+)-(?.+)$", "{header:countrycode}-{header:version}"), - }; - var upstreamHeaders = new Dictionary - { - ["countryAndVersion"] = "PL-v1", - }; - var expected = new List - { - new PlaceholderNameAndValue("{countrycode}", "PL"), - new PlaceholderNameAndValue("{version}", "v1"), - }; - - this.Given(x => x.GivenUpstreamHeaderTemplatesAre(upstreamHeaderTemplates)) - .And(x => x.GivenUpstreamHeadersAre(upstreamHeaders)) - .When(x => x.WhenICallFindPlaceholders()) - .Then(x => x.TheResultIs(expected)) - .BDDfy(); - } - - [Fact] - public void should_return_placeholders_from_different_headers() - { - var upstreamHeaderTemplates = new Dictionary - { - ["country"] = new UpstreamHeaderTemplate("^(?i)(?.+)$", "{header:countrycode}"), - ["version"] = new UpstreamHeaderTemplate("^(?i)(?.+)$", "{header:version}"), - }; - var upstreamHeaders = new Dictionary - { - ["country"] = "PL", - ["version"] = "v1", - }; - var expected = new List - { - new PlaceholderNameAndValue("{countrycode}", "PL"), - new PlaceholderNameAndValue("{version}", "v1"), - }; - - this.Given(x => x.GivenUpstreamHeaderTemplatesAre(upstreamHeaderTemplates)) - .And(x => x.GivenUpstreamHeadersAre(upstreamHeaders)) - .When(x => x.WhenICallFindPlaceholders()) - .Then(x => x.TheResultIs(expected)) - .BDDfy(); - } - - private void GivenUpstreamHeaderTemplatesAre(Dictionary upstreaHeaderTemplates) - { - _upstreamHeaderTemplates = upstreaHeaderTemplates; - } - - private void GivenUpstreamHeadersAre(Dictionary upstreamHeaders) - { - _upstreamHeaders = upstreamHeaders; - } - - private void WhenICallFindPlaceholders() - { - _result = _finder.Find(_upstreamHeaders, _upstreamHeaderTemplates); - } - - private void TheResultIs(List expected) - { - _result.ShouldNotBeNull(); - _result.Count.ShouldBe(expected.Count); - _result.ForEach(x => expected.Any(e => e.Name == x.Name && e.Value == x.Value).ShouldBeTrue()); - } + _result.ShouldNotBeNull(); + _result.Count.ShouldBe(expected.Count); + _result.ForEach(x => expected.Any(e => e.Name == x.Name && e.Value == x.Value).ShouldBeTrue()); } } diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/HeaderMatcher/HeadersToHeaderTemplatesMatcherTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/HeaderMatcher/HeadersToHeaderTemplatesMatcherTests.cs index 5cab32ac8..056b49c39 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/HeaderMatcher/HeadersToHeaderTemplatesMatcherTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/HeaderMatcher/HeadersToHeaderTemplatesMatcherTests.cs @@ -1,277 +1,273 @@ using Ocelot.DownstreamRouteFinder.HeaderMatcher; using Ocelot.Values; using Shouldly; -using System; using System.Collections.Generic; -using System.Runtime.CompilerServices; -using System.Text; using TestStack.BDDfy; using Xunit; -namespace Ocelot.UnitTests.DownstreamRouteFinder.HeaderMatcher +namespace Ocelot.UnitTests.DownstreamRouteFinder.HeaderMatcher; + +public class HeadersToHeaderTemplatesMatcherTests { - public class HeadersToHeaderTemplatesMatcherTests + private readonly IHeadersToHeaderTemplatesMatcher _headerMatcher; + private Dictionary _upstreamHeaders; + private Dictionary _templateHeaders; + private bool _result; + + public HeadersToHeaderTemplatesMatcherTests() + { + _headerMatcher = new HeadersToHeaderTemplatesMatcher(); + } + + [Fact] + public void should_match_when_no_template_headers() + { + var upstreamHeaders = new Dictionary() + { + ["anyHeader"] = "anyHeaderValue", + }; + + var templateHeaders = new Dictionary(); + + this.Given(x => x.GivenIHaveUpstreamHeaders(upstreamHeaders)) + .And(x => x.GivenIHaveTemplateHeadersInRoute(templateHeaders)) + .When(x => x.WhenIMatchTheHeaders()) + .Then(x => x.ThenTheResultIsTrue()) + .BDDfy(); + } + + [Fact] + public void should_match_the_same_headers() + { + var upstreamHeaders = new Dictionary() + { + ["anyHeader"] = "anyHeaderValue", + }; + + var templateHeaders = new Dictionary() + { + ["anyHeader"] = new UpstreamHeaderTemplate("^(?i)anyHeaderValue$", "anyHeaderValue"), + }; + + this.Given(x => x.GivenIHaveUpstreamHeaders(upstreamHeaders)) + .And(x => x.GivenIHaveTemplateHeadersInRoute(templateHeaders)) + .When(x => x.WhenIMatchTheHeaders()) + .Then(x => x.ThenTheResultIsTrue()) + .BDDfy(); + } + + [Fact] + public void should_not_match_the_same_headers_when_differ_case_and_case_sensitive() { - private readonly IHeadersToHeaderTemplatesMatcher _headerMatcher; - private Dictionary _upstreamHeaders; - private Dictionary _templateHeaders; - private bool _result; + var upstreamHeaders = new Dictionary() + { + ["anyHeader"] = "ANYHEADERVALUE", + }; - public HeadersToHeaderTemplatesMatcherTests() + var templateHeaders = new Dictionary() { - _headerMatcher = new HeadersToHeaderTemplatesMatcher(); - } + ["anyHeader"] = new UpstreamHeaderTemplate("^anyHeaderValue$", "anyHeaderValue"), + }; + + this.Given(x => x.GivenIHaveUpstreamHeaders(upstreamHeaders)) + .And(x => x.GivenIHaveTemplateHeadersInRoute(templateHeaders)) + .When(x => x.WhenIMatchTheHeaders()) + .Then(x => x.ThenTheResultIsFalse()) + .BDDfy(); + } - [Fact] - public void should_match_when_no_template_headers() + [Fact] + public void should_match_the_same_headers_when_differ_case_and_case_insensitive() + { + var upstreamHeaders = new Dictionary() { - var upstreamHeaders = new Dictionary() - { - ["anyHeader"] = "anyHeaderValue", - }; - - var templateHeaders = new Dictionary(); - - this.Given(x => x.GivenIHaveUpstreamHeaders(upstreamHeaders)) - .And(x => x.GivenIHaveTemplateHeadersInRoute(templateHeaders)) - .When(x => x.WhenIMatchTheHeaders()) - .Then(x => x.ThenTheResultIsTrue()) - .BDDfy(); - } - - [Fact] - public void should_match_the_same_headers() + ["anyHeader"] = "ANYHEADERVALUE", + }; + + var templateHeaders = new Dictionary() { - var upstreamHeaders = new Dictionary() - { - ["anyHeader"] = "anyHeaderValue", - }; - - var templateHeaders = new Dictionary() - { - ["anyHeader"] = new UpstreamHeaderTemplate("^(?i)anyHeaderValue$", "anyHeaderValue"), - }; - - this.Given(x => x.GivenIHaveUpstreamHeaders(upstreamHeaders)) - .And(x => x.GivenIHaveTemplateHeadersInRoute(templateHeaders)) - .When(x => x.WhenIMatchTheHeaders()) - .Then(x => x.ThenTheResultIsTrue()) - .BDDfy(); - } - - [Fact] - public void should_not_match_the_same_headers_when_differ_case_and_case_sensitive() + ["anyHeader"] = new UpstreamHeaderTemplate("^(?i)anyHeaderValue$", "anyHeaderValue"), + }; + + this.Given(x => x.GivenIHaveUpstreamHeaders(upstreamHeaders)) + .And(x => x.GivenIHaveTemplateHeadersInRoute(templateHeaders)) + .When(x => x.WhenIMatchTheHeaders()) + .Then(x => x.ThenTheResultIsTrue()) + .BDDfy(); + } + + [Fact] + public void should_not_match_different_headers_values() + { + var upstreamHeaders = new Dictionary() { - var upstreamHeaders = new Dictionary() - { - ["anyHeader"] = "ANYHEADERVALUE", - }; - - var templateHeaders = new Dictionary() - { - ["anyHeader"] = new UpstreamHeaderTemplate("^anyHeaderValue$", "anyHeaderValue"), - }; - - this.Given(x => x.GivenIHaveUpstreamHeaders(upstreamHeaders)) - .And(x => x.GivenIHaveTemplateHeadersInRoute(templateHeaders)) - .When(x => x.WhenIMatchTheHeaders()) - .Then(x => x.ThenTheResultIsFalse()) - .BDDfy(); - } - - [Fact] - public void should_match_the_same_headers_when_differ_case_and_case_insensitive() + ["anyHeader"] = "anyHeaderValueDifferent", + }; + + var templateHeaders = new Dictionary() { - var upstreamHeaders = new Dictionary() - { - ["anyHeader"] = "ANYHEADERVALUE", - }; - - var templateHeaders = new Dictionary() - { - ["anyHeader"] = new UpstreamHeaderTemplate("^(?i)anyHeaderValue$", "anyHeaderValue"), - }; - - this.Given(x => x.GivenIHaveUpstreamHeaders(upstreamHeaders)) - .And(x => x.GivenIHaveTemplateHeadersInRoute(templateHeaders)) - .When(x => x.WhenIMatchTheHeaders()) - .Then(x => x.ThenTheResultIsTrue()) - .BDDfy(); - } - - [Fact] - public void should_not_match_different_headers_values() + ["anyHeader"] = new UpstreamHeaderTemplate("^(?i)anyHeaderValue$", "anyHeaderValue"), + }; + + this.Given(x => x.GivenIHaveUpstreamHeaders(upstreamHeaders)) + .And(x => x.GivenIHaveTemplateHeadersInRoute(templateHeaders)) + .When(x => x.WhenIMatchTheHeaders()) + .Then(x => x.ThenTheResultIsFalse()) + .BDDfy(); + } + + [Fact] + public void should_not_match_the_same_headers_names() + { + var upstreamHeaders = new Dictionary() { - var upstreamHeaders = new Dictionary() - { - ["anyHeader"] = "anyHeaderValueDifferent", - }; - - var templateHeaders = new Dictionary() - { - ["anyHeader"] = new UpstreamHeaderTemplate("^(?i)anyHeaderValue$", "anyHeaderValue"), - }; - - this.Given(x => x.GivenIHaveUpstreamHeaders(upstreamHeaders)) - .And(x => x.GivenIHaveTemplateHeadersInRoute(templateHeaders)) - .When(x => x.WhenIMatchTheHeaders()) - .Then(x => x.ThenTheResultIsFalse()) - .BDDfy(); - } - - [Fact] - public void should_not_match_the_same_headers_names() + ["anyHeaderDifferent"] = "anyHeaderValue", + }; + + var templateHeaders = new Dictionary() { - var upstreamHeaders = new Dictionary() - { - ["anyHeaderDifferent"] = "anyHeaderValue", - }; - - var templateHeaders = new Dictionary() - { - ["anyHeader"] = new UpstreamHeaderTemplate("^(?i)anyHeaderValue$", "anyHeaderValue"), - }; - - this.Given(x => x.GivenIHaveUpstreamHeaders(upstreamHeaders)) - .And(x => x.GivenIHaveTemplateHeadersInRoute(templateHeaders)) - .When(x => x.WhenIMatchTheHeaders()) - .Then(x => x.ThenTheResultIsFalse()) - .BDDfy(); - } - - [Fact] - public void should_match_all_the_same_headers() + ["anyHeader"] = new UpstreamHeaderTemplate("^(?i)anyHeaderValue$", "anyHeaderValue"), + }; + + this.Given(x => x.GivenIHaveUpstreamHeaders(upstreamHeaders)) + .And(x => x.GivenIHaveTemplateHeadersInRoute(templateHeaders)) + .When(x => x.WhenIMatchTheHeaders()) + .Then(x => x.ThenTheResultIsFalse()) + .BDDfy(); + } + + [Fact] + public void should_match_all_the_same_headers() + { + var upstreamHeaders = new Dictionary() { - var upstreamHeaders = new Dictionary() - { - ["anyHeader"] = "anyHeaderValue", - ["notNeededHeader"] = "notNeededHeaderValue", - ["secondHeader"] = "secondHeaderValue", - ["thirdHeader"] = "thirdHeaderValue", - }; - - var templateHeaders = new Dictionary() - { - ["secondHeader"] = new UpstreamHeaderTemplate("^(?i)secondHeaderValue$", "secondHeaderValue"), - ["thirdHeader"] = new UpstreamHeaderTemplate("^(?i)thirdHeaderValue$", "thirdHeaderValue"), - ["anyHeader"] = new UpstreamHeaderTemplate("^(?i)anyHeaderValue$", "anyHeaderValue"), - }; - - this.Given(x => x.GivenIHaveUpstreamHeaders(upstreamHeaders)) - .And(x => x.GivenIHaveTemplateHeadersInRoute(templateHeaders)) - .When(x => x.WhenIMatchTheHeaders()) - .Then(x => x.ThenTheResultIsTrue()) - .BDDfy(); - } - - [Fact] - public void should_not_match_the_headers_when_one_of_them_different() + ["anyHeader"] = "anyHeaderValue", + ["notNeededHeader"] = "notNeededHeaderValue", + ["secondHeader"] = "secondHeaderValue", + ["thirdHeader"] = "thirdHeaderValue", + }; + + var templateHeaders = new Dictionary() { - var upstreamHeaders = new Dictionary() - { - ["anyHeader"] = "anyHeaderValue", - ["notNeededHeader"] = "notNeededHeaderValue", - ["secondHeader"] = "secondHeaderValueDIFFERENT", - ["thirdHeader"] = "thirdHeaderValue", - }; - - var templateHeaders = new Dictionary() - { - ["secondHeader"] = new UpstreamHeaderTemplate("^(?i)secondHeaderValue$", "secondHeaderValue"), - ["thirdHeader"] = new UpstreamHeaderTemplate("^(?i)thirdHeaderValue$", "thirdHeaderValue"), - ["anyHeader"] = new UpstreamHeaderTemplate("^(?i)anyHeaderValue$", "anyHeaderValue"), - }; - - this.Given(x => x.GivenIHaveUpstreamHeaders(upstreamHeaders)) - .And(x => x.GivenIHaveTemplateHeadersInRoute(templateHeaders)) - .When(x => x.WhenIMatchTheHeaders()) - .Then(x => x.ThenTheResultIsFalse()) - .BDDfy(); - } - - [Fact] - public void should_match_the_header_with_placeholder() + ["secondHeader"] = new UpstreamHeaderTemplate("^(?i)secondHeaderValue$", "secondHeaderValue"), + ["thirdHeader"] = new UpstreamHeaderTemplate("^(?i)thirdHeaderValue$", "thirdHeaderValue"), + ["anyHeader"] = new UpstreamHeaderTemplate("^(?i)anyHeaderValue$", "anyHeaderValue"), + }; + + this.Given(x => x.GivenIHaveUpstreamHeaders(upstreamHeaders)) + .And(x => x.GivenIHaveTemplateHeadersInRoute(templateHeaders)) + .When(x => x.WhenIMatchTheHeaders()) + .Then(x => x.ThenTheResultIsTrue()) + .BDDfy(); + } + + [Fact] + public void should_not_match_the_headers_when_one_of_them_different() + { + var upstreamHeaders = new Dictionary() { - var upstreamHeaders = new Dictionary() - { - ["anyHeader"] = "PL", - }; - - var templateHeaders = new Dictionary() - { - ["anyHeader"] = new UpstreamHeaderTemplate("^(?i)(?.+)$", "{header:countrycode}"), - }; - - this.Given(x => x.GivenIHaveUpstreamHeaders(upstreamHeaders)) - .And(x => x.GivenIHaveTemplateHeadersInRoute(templateHeaders)) - .When(x => x.WhenIMatchTheHeaders()) - .Then(x => x.ThenTheResultIsTrue()) - .BDDfy(); - } - - [Fact] - public void should_match_the_header_with_placeholders() + ["anyHeader"] = "anyHeaderValue", + ["notNeededHeader"] = "notNeededHeaderValue", + ["secondHeader"] = "secondHeaderValueDIFFERENT", + ["thirdHeader"] = "thirdHeaderValue", + }; + + var templateHeaders = new Dictionary() { - var upstreamHeaders = new Dictionary() - { - ["anyHeader"] = "PL-V1", - }; - - var templateHeaders = new Dictionary() - { - ["anyHeader"] = new UpstreamHeaderTemplate("^(?i)(?.+)-(?.+)$", "{header:countrycode}-{header:version}"), - }; - - this.Given(x => x.GivenIHaveUpstreamHeaders(upstreamHeaders)) - .And(x => x.GivenIHaveTemplateHeadersInRoute(templateHeaders)) - .When(x => x.WhenIMatchTheHeaders()) - .Then(x => x.ThenTheResultIsTrue()) - .BDDfy(); - } - - [Fact] - public void should_not_match_the_header_with_placeholders() + ["secondHeader"] = new UpstreamHeaderTemplate("^(?i)secondHeaderValue$", "secondHeaderValue"), + ["thirdHeader"] = new UpstreamHeaderTemplate("^(?i)thirdHeaderValue$", "thirdHeaderValue"), + ["anyHeader"] = new UpstreamHeaderTemplate("^(?i)anyHeaderValue$", "anyHeaderValue"), + }; + + this.Given(x => x.GivenIHaveUpstreamHeaders(upstreamHeaders)) + .And(x => x.GivenIHaveTemplateHeadersInRoute(templateHeaders)) + .When(x => x.WhenIMatchTheHeaders()) + .Then(x => x.ThenTheResultIsFalse()) + .BDDfy(); + } + + [Fact] + public void should_match_the_header_with_placeholder() + { + var upstreamHeaders = new Dictionary() { - var upstreamHeaders = new Dictionary() - { - ["anyHeader"] = "PL", - }; - - var templateHeaders = new Dictionary() - { - ["anyHeader"] = new UpstreamHeaderTemplate("^(?i)(?.+)-(?.+)$", "{header:countrycode}-{header:version}"), - }; - - this.Given(x => x.GivenIHaveUpstreamHeaders(upstreamHeaders)) - .And(x => x.GivenIHaveTemplateHeadersInRoute(templateHeaders)) - .When(x => x.WhenIMatchTheHeaders()) - .Then(x => x.ThenTheResultIsFalse()) - .BDDfy(); - } - - private void GivenIHaveUpstreamHeaders(Dictionary upstreamHeaders) + ["anyHeader"] = "PL", + }; + + var templateHeaders = new Dictionary() { - _upstreamHeaders = upstreamHeaders; - } + ["anyHeader"] = new UpstreamHeaderTemplate("^(?i)(?.+)$", "{header:countrycode}"), + }; + + this.Given(x => x.GivenIHaveUpstreamHeaders(upstreamHeaders)) + .And(x => x.GivenIHaveTemplateHeadersInRoute(templateHeaders)) + .When(x => x.WhenIMatchTheHeaders()) + .Then(x => x.ThenTheResultIsTrue()) + .BDDfy(); + } - private void GivenIHaveTemplateHeadersInRoute(Dictionary templateHeaders) + [Fact] + public void should_match_the_header_with_placeholders() + { + var upstreamHeaders = new Dictionary() { - _templateHeaders = templateHeaders; - } + ["anyHeader"] = "PL-V1", + }; - private void WhenIMatchTheHeaders() + var templateHeaders = new Dictionary() { - _result = _headerMatcher.Match(_upstreamHeaders, _templateHeaders); - } + ["anyHeader"] = new UpstreamHeaderTemplate("^(?i)(?.+)-(?.+)$", "{header:countrycode}-{header:version}"), + }; + + this.Given(x => x.GivenIHaveUpstreamHeaders(upstreamHeaders)) + .And(x => x.GivenIHaveTemplateHeadersInRoute(templateHeaders)) + .When(x => x.WhenIMatchTheHeaders()) + .Then(x => x.ThenTheResultIsTrue()) + .BDDfy(); + } - private void ThenTheResultIsTrue() + [Fact] + public void should_not_match_the_header_with_placeholders() + { + var upstreamHeaders = new Dictionary() { - _result.ShouldBeTrue(); - } + ["anyHeader"] = "PL", + }; - private void ThenTheResultIsFalse() + var templateHeaders = new Dictionary() { - _result.ShouldBeFalse(); - } + ["anyHeader"] = new UpstreamHeaderTemplate("^(?i)(?.+)-(?.+)$", "{header:countrycode}-{header:version}"), + }; + + this.Given(x => x.GivenIHaveUpstreamHeaders(upstreamHeaders)) + .And(x => x.GivenIHaveTemplateHeadersInRoute(templateHeaders)) + .When(x => x.WhenIMatchTheHeaders()) + .Then(x => x.ThenTheResultIsFalse()) + .BDDfy(); + } + + private void GivenIHaveUpstreamHeaders(Dictionary upstreamHeaders) + { + _upstreamHeaders = upstreamHeaders; + } + + private void GivenIHaveTemplateHeadersInRoute(Dictionary templateHeaders) + { + _templateHeaders = templateHeaders; + } + + private void WhenIMatchTheHeaders() + { + _result = _headerMatcher.Match(_upstreamHeaders, _templateHeaders); + } + + private void ThenTheResultIsTrue() + { + _result.ShouldBeTrue(); + } + + private void ThenTheResultIsFalse() + { + _result.ShouldBeFalse(); } } From dec3145c12d056c4739af7b6aaa9534d27cecf69 Mon Sep 17 00:00:00 2001 From: raman-m Date: Wed, 2 Aug 2023 20:49:09 +0300 Subject: [PATCH 19/49] File-scoped namespace --- src/Ocelot/Configuration/File/IRoute.cs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/Ocelot/Configuration/File/IRoute.cs b/src/Ocelot/Configuration/File/IRoute.cs index 5558cd778..9caa6ae42 100644 --- a/src/Ocelot/Configuration/File/IRoute.cs +++ b/src/Ocelot/Configuration/File/IRoute.cs @@ -1,12 +1,11 @@ using System.Collections.Generic; -namespace Ocelot.Configuration.File +namespace Ocelot.Configuration.File; + +public interface IRoute { - public interface IRoute - { - string UpstreamPathTemplate { get; set; } - bool RouteIsCaseSensitive { get; set; } - int Priority { get; set; } - Dictionary UpstreamHeaderTemplates { get; set; } - } + string UpstreamPathTemplate { get; set; } + bool RouteIsCaseSensitive { get; set; } + int Priority { get; set; } + Dictionary UpstreamHeaderTemplates { get; set; } } From e13f19ac6b45387649d069bbcb726f4aa3cd8e28 Mon Sep 17 00:00:00 2001 From: raman-m Date: Wed, 2 Aug 2023 21:03:06 +0300 Subject: [PATCH 20/49] Target-typed 'new' expressions (C# 9). https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-9.0/target-typed-new --- .../FileConfigurationFluentValidatorTests.cs | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/test/Ocelot.UnitTests/Configuration/Validation/FileConfigurationFluentValidatorTests.cs b/test/Ocelot.UnitTests/Configuration/Validation/FileConfigurationFluentValidatorTests.cs index d41f1eb7c..7f55cad0a 100644 --- a/test/Ocelot.UnitTests/Configuration/Validation/FileConfigurationFluentValidatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/Validation/FileConfigurationFluentValidatorTests.cs @@ -727,13 +727,13 @@ public void configuration_is_not_valid_when_upstream_headers_the_same() { Routes = new List { - new FileRoute + new() { DownstreamPathTemplate = "/api/products/", UpstreamPathTemplate = "/asdf/", DownstreamHostAndPorts = new List { - new FileHostAndPort + new() { Host = "bbc.co.uk", }, @@ -745,13 +745,13 @@ public void configuration_is_not_valid_when_upstream_headers_the_same() { "header2", "value2" }, }, }, - new FileRoute + new() { DownstreamPathTemplate = "/www/test/", UpstreamPathTemplate = "/asdf/", DownstreamHostAndPorts = new List { - new FileHostAndPort + new() { Host = "bbc.co.uk", }, @@ -778,13 +778,13 @@ public void configuration_is_valid_when_upstream_headers_not_the_same() { Routes = new List { - new FileRoute + new() { DownstreamPathTemplate = "/api/products/", UpstreamPathTemplate = "/asdf/", DownstreamHostAndPorts = new List { - new FileHostAndPort + new() { Host = "bbc.co.uk", }, @@ -796,13 +796,13 @@ public void configuration_is_valid_when_upstream_headers_not_the_same() { "header2", "value2" }, }, }, - new FileRoute + new() { DownstreamPathTemplate = "/www/test/", UpstreamPathTemplate = "/asdf/", DownstreamHostAndPorts = new List { - new FileHostAndPort + new() { Host = "bbc.co.uk", }, @@ -828,13 +828,13 @@ public void configuration_is_valid_when_upstream_headers_count_not_the_same() { Routes = new List { - new FileRoute + new() { DownstreamPathTemplate = "/api/products/", UpstreamPathTemplate = "/asdf/", DownstreamHostAndPorts = new List { - new FileHostAndPort + new() { Host = "bbc.co.uk", }, @@ -846,13 +846,13 @@ public void configuration_is_valid_when_upstream_headers_count_not_the_same() { "header2", "value2" }, }, }, - new FileRoute + new() { DownstreamPathTemplate = "/www/test/", UpstreamPathTemplate = "/asdf/", DownstreamHostAndPorts = new List { - new FileHostAndPort + new() { Host = "bbc.co.uk", }, @@ -877,13 +877,13 @@ public void configuration_is_valid_when_one_upstream_headers_empty_and_other_not { Routes = new List { - new FileRoute + new() { DownstreamPathTemplate = "/api/products/", UpstreamPathTemplate = "/asdf/", DownstreamHostAndPorts = new List { - new FileHostAndPort + new() { Host = "bbc.co.uk", }, @@ -895,13 +895,13 @@ public void configuration_is_valid_when_one_upstream_headers_empty_and_other_not { "header2", "value2" }, }, }, - new FileRoute + new() { DownstreamPathTemplate = "/www/test/", UpstreamPathTemplate = "/asdf/", DownstreamHostAndPorts = new List { - new FileHostAndPort + new() { Host = "bbc.co.uk", }, From 39edcaaf605ac039e335c12c5ca4b1ec0510813c Mon Sep 17 00:00:00 2001 From: raman-m Date: Wed, 2 Aug 2023 21:19:49 +0300 Subject: [PATCH 21/49] IDE1006 Naming rule violation: These words must begin with upper case characters: should_* --- .../RoutingBasedOnHeadersTests.cs | 32 +++++++++---------- ...streamHeaderTemplatePatternCreatorTests.cs | 12 +++---- ...eaderPlaceholderNameAndValueFinderTests.cs | 14 ++++---- .../HeadersToHeaderTemplatesMatcherTests.cs | 22 ++++++------- 4 files changed, 40 insertions(+), 40 deletions(-) diff --git a/test/Ocelot.AcceptanceTests/RoutingBasedOnHeadersTests.cs b/test/Ocelot.AcceptanceTests/RoutingBasedOnHeadersTests.cs index 113797be8..d9abce92b 100644 --- a/test/Ocelot.AcceptanceTests/RoutingBasedOnHeadersTests.cs +++ b/test/Ocelot.AcceptanceTests/RoutingBasedOnHeadersTests.cs @@ -22,7 +22,7 @@ public RoutingBasedOnHeadersTests() } [Fact] - public void should_match_one_header_value() + public void Should_match_one_header_value() { var port = RandomPortFinder.GetRandomPort(); var headerName = "country_code"; @@ -65,7 +65,7 @@ public void should_match_one_header_value() } [Fact] - public void should_match_one_header_value_when_more_headers() + public void Should_match_one_header_value_when_more_headers() { var port = RandomPortFinder.GetRandomPort(); var headerName = "country_code"; @@ -109,7 +109,7 @@ public void should_match_one_header_value_when_more_headers() } [Fact] - public void should_match_two_header_values_when_more_headers() + public void Should_match_two_header_values_when_more_headers() { var port = RandomPortFinder.GetRandomPort(); var headerName1 = "country_code"; @@ -157,7 +157,7 @@ public void should_match_two_header_values_when_more_headers() } [Fact] - public void should_not_match_one_header_value() + public void Should_not_match_one_header_value() { var port = RandomPortFinder.GetRandomPort(); var headerName = "country_code"; @@ -200,7 +200,7 @@ public void should_not_match_one_header_value() } [Fact] - public void should_not_match_one_header_value_when_no_headers() + public void Should_not_match_one_header_value_when_no_headers() { var port = RandomPortFinder.GetRandomPort(); var headerName = "country_code"; @@ -241,7 +241,7 @@ public void should_not_match_one_header_value_when_no_headers() } [Fact] - public void should_not_match_two_header_values_when_one_different() + public void Should_not_match_two_header_values_when_one_different() { var port = RandomPortFinder.GetRandomPort(); var headerName1 = "country_code"; @@ -288,7 +288,7 @@ public void should_not_match_two_header_values_when_one_different() } [Fact] - public void should_not_match_two_header_values_when_one_not_existing() + public void Should_not_match_two_header_values_when_one_not_existing() { var port = RandomPortFinder.GetRandomPort(); var headerName1 = "country_code"; @@ -334,7 +334,7 @@ public void should_not_match_two_header_values_when_one_not_existing() } [Fact] - public void should_not_match_one_header_value_when_header_duplicated() + public void Should_not_match_one_header_value_when_header_duplicated() { var port = RandomPortFinder.GetRandomPort(); var headerName = "country_code"; @@ -377,7 +377,7 @@ public void should_not_match_one_header_value_when_header_duplicated() } [Fact] - public void should_aggregated_route_match_header_value() + public void Should_aggregated_route_match_header_value() { var port1 = RandomPortFinder.GetRandomPort(); var port2 = RandomPortFinder.GetRandomPort(); @@ -451,7 +451,7 @@ public void should_aggregated_route_match_header_value() } [Fact] - public void should_aggregated_route_not_match_header_value() + public void Should_aggregated_route_not_match_header_value() { var port1 = RandomPortFinder.GetRandomPort(); var port2 = RandomPortFinder.GetRandomPort(); @@ -524,7 +524,7 @@ public void should_aggregated_route_not_match_header_value() } [Fact] - public void should_match_header_placeholder() + public void Should_match_header_placeholder() { var port = RandomPortFinder.GetRandomPort(); var headerName = "Region"; @@ -566,7 +566,7 @@ public void should_match_header_placeholder() } [Fact] - public void should_match_header_placeholder_not_in_downstream_path() + public void Should_match_header_placeholder_not_in_downstream_path() { var port = RandomPortFinder.GetRandomPort(); var headerName = "ProductName"; @@ -608,7 +608,7 @@ public void should_match_header_placeholder_not_in_downstream_path() } [Fact] - public void should_distinguish_route_for_different_roles() + public void Should_distinguish_route_for_different_roles() { var port = RandomPortFinder.GetRandomPort(); var headerName = "Origin"; @@ -665,7 +665,7 @@ public void should_distinguish_route_for_different_roles() } [Fact] - public void should_match_header_and_url_placeholders() + public void Should_match_header_and_url_placeholders() { var port = RandomPortFinder.GetRandomPort(); var headerName = "country_code"; @@ -707,7 +707,7 @@ public void should_match_header_and_url_placeholders() } [Fact] - public void should_match_header_with_braces() + public void Should_match_header_with_braces() { var port = RandomPortFinder.GetRandomPort(); var headerName = "country_code"; @@ -749,7 +749,7 @@ public void should_match_header_with_braces() } [Fact] - public void should_match_two_headers_with_the_same_name() + public void Should_match_two_headers_with_the_same_name() { var port = RandomPortFinder.GetRandomPort(); var headerName = "country_code"; diff --git a/test/Ocelot.UnitTests/Configuration/UpstreamHeaderTemplatePatternCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/UpstreamHeaderTemplatePatternCreatorTests.cs index 23d98789b..cf049eb03 100644 --- a/test/Ocelot.UnitTests/Configuration/UpstreamHeaderTemplatePatternCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/UpstreamHeaderTemplatePatternCreatorTests.cs @@ -20,7 +20,7 @@ public UpstreamHeaderTemplatePatternCreatorTests() } [Fact] - public void should_create_pattern_without_placeholders() + public void Should_create_pattern_without_placeholders() { var fileRoute = new FileRoute { @@ -37,7 +37,7 @@ public void should_create_pattern_without_placeholders() } [Fact] - public void should_create_pattern_case_sensitive() + public void Should_create_pattern_case_sensitive() { var fileRoute = new FileRoute { @@ -55,7 +55,7 @@ public void should_create_pattern_case_sensitive() } [Fact] - public void should_create_pattern_with_placeholder_in_the_beginning() + public void Should_create_pattern_with_placeholder_in_the_beginning() { var fileRoute = new FileRoute { @@ -72,7 +72,7 @@ public void should_create_pattern_with_placeholder_in_the_beginning() } [Fact] - public void should_create_pattern_with_placeholder_at_the_end() + public void Should_create_pattern_with_placeholder_at_the_end() { var fileRoute = new FileRoute { @@ -89,7 +89,7 @@ public void should_create_pattern_with_placeholder_at_the_end() } [Fact] - public void should_create_pattern_with_placeholder_only() + public void Should_create_pattern_with_placeholder_only() { var fileRoute = new FileRoute { @@ -106,7 +106,7 @@ public void should_create_pattern_with_placeholder_only() } [Fact] - public void should_crate_pattern_with_more_placeholders() + public void Should_crate_pattern_with_more_placeholders() { var fileRoute = new FileRoute { diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinderTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinderTests.cs index 86ae67359..cb316a460 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinderTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinderTests.cs @@ -22,7 +22,7 @@ public HeaderPlaceholderNameAndValueFinderTests() } [Fact] - public void should_return_no_placeholders() + public void Should_return_no_placeholders() { var upstreamHeaderTemplates = new Dictionary(); var upstreamHeaders = new Dictionary(); @@ -36,7 +36,7 @@ public void should_return_no_placeholders() } [Fact] - public void should_return_one_placeholder_with_value_when_no_other_text() + public void Should_return_one_placeholder_with_value_when_no_other_text() { var upstreamHeaderTemplates = new Dictionary { @@ -59,7 +59,7 @@ public void should_return_one_placeholder_with_value_when_no_other_text() } [Fact] - public void should_return_one_placeholder_with_value_when_other_text_on_the_right() + public void Should_return_one_placeholder_with_value_when_other_text_on_the_right() { var upstreamHeaderTemplates = new Dictionary { @@ -82,7 +82,7 @@ public void should_return_one_placeholder_with_value_when_other_text_on_the_righ } [Fact] - public void should_return_one_placeholder_with_value_when_other_text_on_the_left() + public void Should_return_one_placeholder_with_value_when_other_text_on_the_left() { var upstreamHeaderTemplates = new Dictionary { @@ -105,7 +105,7 @@ public void should_return_one_placeholder_with_value_when_other_text_on_the_left } [Fact] - public void should_return_one_placeholder_with_value_when_other_texts_surrounding() + public void Should_return_one_placeholder_with_value_when_other_texts_surrounding() { var upstreamHeaderTemplates = new Dictionary { @@ -128,7 +128,7 @@ public void should_return_one_placeholder_with_value_when_other_texts_surroundin } [Fact] - public void should_return_two_placeholders_with_text_between() + public void Should_return_two_placeholders_with_text_between() { var upstreamHeaderTemplates = new Dictionary { @@ -152,7 +152,7 @@ public void should_return_two_placeholders_with_text_between() } [Fact] - public void should_return_placeholders_from_different_headers() + public void Should_return_placeholders_from_different_headers() { var upstreamHeaderTemplates = new Dictionary { diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/HeaderMatcher/HeadersToHeaderTemplatesMatcherTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/HeaderMatcher/HeadersToHeaderTemplatesMatcherTests.cs index 056b49c39..bd63dfc6e 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/HeaderMatcher/HeadersToHeaderTemplatesMatcherTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/HeaderMatcher/HeadersToHeaderTemplatesMatcherTests.cs @@ -20,7 +20,7 @@ public HeadersToHeaderTemplatesMatcherTests() } [Fact] - public void should_match_when_no_template_headers() + public void Should_match_when_no_template_headers() { var upstreamHeaders = new Dictionary() { @@ -37,7 +37,7 @@ public void should_match_when_no_template_headers() } [Fact] - public void should_match_the_same_headers() + public void Should_match_the_same_headers() { var upstreamHeaders = new Dictionary() { @@ -57,7 +57,7 @@ public void should_match_the_same_headers() } [Fact] - public void should_not_match_the_same_headers_when_differ_case_and_case_sensitive() + public void Should_not_match_the_same_headers_when_differ_case_and_case_sensitive() { var upstreamHeaders = new Dictionary() { @@ -77,7 +77,7 @@ public void should_not_match_the_same_headers_when_differ_case_and_case_sensitiv } [Fact] - public void should_match_the_same_headers_when_differ_case_and_case_insensitive() + public void Should_match_the_same_headers_when_differ_case_and_case_insensitive() { var upstreamHeaders = new Dictionary() { @@ -97,7 +97,7 @@ public void should_match_the_same_headers_when_differ_case_and_case_insensitive( } [Fact] - public void should_not_match_different_headers_values() + public void Should_not_match_different_headers_values() { var upstreamHeaders = new Dictionary() { @@ -117,7 +117,7 @@ public void should_not_match_different_headers_values() } [Fact] - public void should_not_match_the_same_headers_names() + public void Should_not_match_the_same_headers_names() { var upstreamHeaders = new Dictionary() { @@ -137,7 +137,7 @@ public void should_not_match_the_same_headers_names() } [Fact] - public void should_match_all_the_same_headers() + public void Should_match_all_the_same_headers() { var upstreamHeaders = new Dictionary() { @@ -162,7 +162,7 @@ public void should_match_all_the_same_headers() } [Fact] - public void should_not_match_the_headers_when_one_of_them_different() + public void Should_not_match_the_headers_when_one_of_them_different() { var upstreamHeaders = new Dictionary() { @@ -187,7 +187,7 @@ public void should_not_match_the_headers_when_one_of_them_different() } [Fact] - public void should_match_the_header_with_placeholder() + public void Should_match_the_header_with_placeholder() { var upstreamHeaders = new Dictionary() { @@ -207,7 +207,7 @@ public void should_match_the_header_with_placeholder() } [Fact] - public void should_match_the_header_with_placeholders() + public void Should_match_the_header_with_placeholders() { var upstreamHeaders = new Dictionary() { @@ -227,7 +227,7 @@ public void should_match_the_header_with_placeholders() } [Fact] - public void should_not_match_the_header_with_placeholders() + public void Should_not_match_the_header_with_placeholders() { var upstreamHeaders = new Dictionary() { From 2dcb983e519dff43131d79625fe2429286aa7bd3 Mon Sep 17 00:00:00 2001 From: raman-m Date: Wed, 2 Aug 2023 21:54:01 +0300 Subject: [PATCH 22/49] Target-typed 'new' expressions (C# 9). https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-9.0/target-typed-new --- .../RoutingBasedOnHeadersTests.cs | 596 +++++++++--------- ...streamHeaderTemplatePatternCreatorTests.cs | 2 +- ...eaderPlaceholderNameAndValueFinderTests.cs | 30 +- .../HeadersToHeaderTemplatesMatcherTests.cs | 28 +- 4 files changed, 328 insertions(+), 328 deletions(-) diff --git a/test/Ocelot.AcceptanceTests/RoutingBasedOnHeadersTests.cs b/test/Ocelot.AcceptanceTests/RoutingBasedOnHeadersTests.cs index d9abce92b..634740a5d 100644 --- a/test/Ocelot.AcceptanceTests/RoutingBasedOnHeadersTests.cs +++ b/test/Ocelot.AcceptanceTests/RoutingBasedOnHeadersTests.cs @@ -17,8 +17,8 @@ public class RoutingBasedOnHeadersTests : IDisposable public RoutingBasedOnHeadersTests() { - _serviceHandler = new ServiceHandler(); - _steps = new Steps(); + _serviceHandler = new(); + _steps = new(); } [Fact] @@ -31,27 +31,27 @@ public void Should_match_one_header_value() var configuration = new FileConfiguration { Routes = new List + { + new() { - new FileRoute + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = port, - }, - }, - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - UpstreamHeaderTemplates = new Dictionary() + new() { - [headerName] = headerValue, + Host = "localhost", + Port = port, }, }, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + UpstreamHeaderTemplates = new Dictionary() + { + [headerName] = headerValue, + }, }, + }, }; this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/", 200, "Hello from Laura")) @@ -74,27 +74,27 @@ public void Should_match_one_header_value_when_more_headers() var configuration = new FileConfiguration { Routes = new List + { + new() { - new FileRoute + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List + new() { - new FileHostAndPort - { - Host = "localhost", - Port = port, - }, - }, - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - UpstreamHeaderTemplates = new Dictionary() - { - [headerName] = headerValue, + Host = "localhost", + Port = port, }, }, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + UpstreamHeaderTemplates = new Dictionary() + { + [headerName] = headerValue, + }, }, + }, }; this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/", 200, "Hello from Laura")) @@ -120,28 +120,28 @@ public void Should_match_two_header_values_when_more_headers() var configuration = new FileConfiguration { Routes = new List + { + new() { - new FileRoute + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = port, - }, - }, - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - UpstreamHeaderTemplates = new Dictionary() + new() { - [headerName1] = headerValue1, - [headerName2] = headerValue2, + Host = "localhost", + Port = port, }, }, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + UpstreamHeaderTemplates = new Dictionary() + { + [headerName1] = headerValue1, + [headerName2] = headerValue2, + }, }, + }, }; this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/", 200, "Hello from Laura")) @@ -167,27 +167,27 @@ public void Should_not_match_one_header_value() var configuration = new FileConfiguration { Routes = new List + { + new() { - new FileRoute + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = port, - }, - }, - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - UpstreamHeaderTemplates = new Dictionary() + new() { - [headerName] = headerValue, + Host = "localhost", + Port = port, }, }, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + UpstreamHeaderTemplates = new Dictionary() + { + [headerName] = headerValue, + }, }, + }, }; this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/", 200, "Hello from Laura")) @@ -209,27 +209,27 @@ public void Should_not_match_one_header_value_when_no_headers() var configuration = new FileConfiguration { Routes = new List + { + new() { - new FileRoute + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List + new() { - new FileHostAndPort - { - Host = "localhost", - Port = port, - }, - }, - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - UpstreamHeaderTemplates = new Dictionary() - { - [headerName] = headerValue, + Host = "localhost", + Port = port, }, }, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + UpstreamHeaderTemplates = new Dictionary() + { + [headerName] = headerValue, + }, }, + }, }; this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/", 200, "Hello from Laura")) @@ -252,28 +252,28 @@ public void Should_not_match_two_header_values_when_one_different() var configuration = new FileConfiguration { Routes = new List + { + new() { - new FileRoute + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List + new() { - new FileHostAndPort - { - Host = "localhost", - Port = port, - }, - }, - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - UpstreamHeaderTemplates = new Dictionary() - { - [headerName1] = headerValue1, - [headerName2] = headerValue2, + Host = "localhost", + Port = port, }, }, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + UpstreamHeaderTemplates = new Dictionary() + { + [headerName1] = headerValue1, + [headerName2] = headerValue2, + }, }, + }, }; this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/", 200, "Hello from Laura")) @@ -299,28 +299,28 @@ public void Should_not_match_two_header_values_when_one_not_existing() var configuration = new FileConfiguration { Routes = new List + { + new() { - new FileRoute + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = port, - }, - }, - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - UpstreamHeaderTemplates = new Dictionary() + new() { - [headerName1] = headerValue1, - [headerName2] = headerValue2, + Host = "localhost", + Port = port, }, }, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + UpstreamHeaderTemplates = new Dictionary() + { + [headerName1] = headerValue1, + [headerName2] = headerValue2, + }, }, + }, }; this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/", 200, "Hello from Laura")) @@ -343,27 +343,27 @@ public void Should_not_match_one_header_value_when_header_duplicated() var configuration = new FileConfiguration { Routes = new List + { + new() { - new FileRoute + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = port, - }, - }, - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - UpstreamHeaderTemplates = new Dictionary() + new() { - [headerName] = headerValue, + Host = "localhost", + Port = port, }, }, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + UpstreamHeaderTemplates = new Dictionary() + { + [headerName] = headerValue, + }, }, + }, }; this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/", 200, "Hello from Laura")) @@ -387,56 +387,56 @@ public void Should_aggregated_route_match_header_value() var configuration = new FileConfiguration { Routes = new List + { + new() { - new FileRoute + DownstreamPathTemplate = "/a", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List { - DownstreamPathTemplate = "/a", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List + new() { - new FileHostAndPort - { - Host = "localhost", - Port = port1, - }, + Host = "localhost", + Port = port1, }, - UpstreamPathTemplate = "/a", - UpstreamHttpMethod = new List { "Get" }, - Key = "Laura", }, - new FileRoute + UpstreamPathTemplate = "/a", + UpstreamHttpMethod = new List { "Get" }, + Key = "Laura", + }, + new() + { + DownstreamPathTemplate = "/b", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List { - DownstreamPathTemplate = "/b", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List + new() { - new FileHostAndPort - { - Host = "localhost", - Port = port2, - }, + Host = "localhost", + Port = port2, }, - UpstreamPathTemplate = "/b", - UpstreamHttpMethod = new List { "Get" }, - Key = "Tom", }, + UpstreamPathTemplate = "/b", + UpstreamHttpMethod = new List { "Get" }, + Key = "Tom", }, + }, Aggregates = new List() { - new FileAggregateRoute + new() + { + UpstreamPathTemplate = "/", + UpstreamHost = "localhost", + RouteKeys = new List { - UpstreamPathTemplate = "/", - UpstreamHost = "localhost", - RouteKeys = new List - { - "Laura", - "Tom", - }, - UpstreamHeaderTemplates = new Dictionary() - { - [headerName] = headerValue, - }, + "Laura", + "Tom", + }, + UpstreamHeaderTemplates = new Dictionary() + { + [headerName] = headerValue, }, + }, }, }; @@ -461,56 +461,56 @@ public void Should_aggregated_route_not_match_header_value() var configuration = new FileConfiguration { Routes = new List + { + new() { - new FileRoute + DownstreamPathTemplate = "/a", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List { - DownstreamPathTemplate = "/a", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List + new() { - new FileHostAndPort - { - Host = "localhost", - Port = port1, - }, + Host = "localhost", + Port = port1, }, - UpstreamPathTemplate = "/a", - UpstreamHttpMethod = new List { "Get" }, - Key = "Laura", }, - new FileRoute + UpstreamPathTemplate = "/a", + UpstreamHttpMethod = new List { "Get" }, + Key = "Laura", + }, + new() + { + DownstreamPathTemplate = "/b", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List { - DownstreamPathTemplate = "/b", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List + new() { - new FileHostAndPort - { - Host = "localhost", - Port = port2, - }, + Host = "localhost", + Port = port2, }, - UpstreamPathTemplate = "/b", - UpstreamHttpMethod = new List { "Get" }, - Key = "Tom", }, + UpstreamPathTemplate = "/b", + UpstreamHttpMethod = new List { "Get" }, + Key = "Tom", }, + }, Aggregates = new List() { - new FileAggregateRoute + new() + { + UpstreamPathTemplate = "/", + UpstreamHost = "localhost", + RouteKeys = new List { - UpstreamPathTemplate = "/", - UpstreamHost = "localhost", - RouteKeys = new List - { - "Laura", - "Tom", - }, - UpstreamHeaderTemplates = new Dictionary() - { - [headerName] = headerValue, - }, + "Laura", + "Tom", }, + UpstreamHeaderTemplates = new Dictionary() + { + [headerName] = headerValue, + }, + }, }, }; @@ -532,27 +532,27 @@ public void Should_match_header_placeholder() var configuration = new FileConfiguration { Routes = new List + { + new() { - new FileRoute + DownstreamPathTemplate = "/api.internal-{code}/products", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List { - DownstreamPathTemplate = "/api.internal-{code}/products", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List + new() { - new FileHostAndPort - { - Host = "localhost", - Port = port, - }, - }, - UpstreamPathTemplate = "/products", - UpstreamHttpMethod = new List { "Get" }, - UpstreamHeaderTemplates = new Dictionary() - { - [headerName] = "{header:code}", + Host = "localhost", + Port = port, }, }, + UpstreamPathTemplate = "/products", + UpstreamHttpMethod = new List { "Get" }, + UpstreamHeaderTemplates = new Dictionary() + { + [headerName] = "{header:code}", + }, }, + }, }; this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/api.internal-uk/products", 200, "Hello from UK")) @@ -574,27 +574,27 @@ public void Should_match_header_placeholder_not_in_downstream_path() var configuration = new FileConfiguration { Routes = new List + { + new() { - new FileRoute + DownstreamPathTemplate = "/products-info", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List { - DownstreamPathTemplate = "/products-info", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List + new() { - new FileHostAndPort - { - Host = "localhost", - Port = port, - }, - }, - UpstreamPathTemplate = "/products", - UpstreamHttpMethod = new List { "Get" }, - UpstreamHeaderTemplates = new Dictionary() - { - [headerName] = "product-{header:everything}", + Host = "localhost", + Port = port, }, }, + UpstreamPathTemplate = "/products", + UpstreamHttpMethod = new List { "Get" }, + UpstreamHeaderTemplates = new Dictionary() + { + [headerName] = "product-{header:everything}", + }, }, + }, }; this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/products-info", 200, "Hello from products")) @@ -616,42 +616,42 @@ public void Should_distinguish_route_for_different_roles() var configuration = new FileConfiguration { Routes = new List + { + new() { - new FileRoute + DownstreamPathTemplate = "/products-admin", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List { - DownstreamPathTemplate = "/products-admin", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = port, - }, - }, - UpstreamPathTemplate = "/products", - UpstreamHttpMethod = new List { "Get" }, - UpstreamHeaderTemplates = new Dictionary() + new() { - [headerName] = "admin.xxx.com", + Host = "localhost", + Port = port, }, }, - new FileRoute + UpstreamPathTemplate = "/products", + UpstreamHttpMethod = new List { "Get" }, + UpstreamHeaderTemplates = new Dictionary() + { + [headerName] = "admin.xxx.com", + }, + }, + new() + { + DownstreamPathTemplate = "/products", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List { - DownstreamPathTemplate = "/products", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List + new() { - new FileHostAndPort - { - Host = "localhost", - Port = port, - }, + Host = "localhost", + Port = port, }, - UpstreamPathTemplate = "/products", - UpstreamHttpMethod = new List { "Get" }, }, + UpstreamPathTemplate = "/products", + UpstreamHttpMethod = new List { "Get" }, }, + }, }; this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/products-admin", 200, "Hello from products admin")) @@ -673,27 +673,27 @@ public void Should_match_header_and_url_placeholders() var configuration = new FileConfiguration { Routes = new List + { + new() { - new FileRoute + DownstreamPathTemplate = "/{country_code}/{version}/{aa}", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List { - DownstreamPathTemplate = "/{country_code}/{version}/{aa}", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List + new() { - new FileHostAndPort - { - Host = "localhost", - Port = port, - }, - }, - UpstreamPathTemplate = "/{aa}", - UpstreamHttpMethod = new List { "Get" }, - UpstreamHeaderTemplates = new Dictionary() - { - [headerName] = "start_{header:country_code}_version_{header:version}_end", + Host = "localhost", + Port = port, }, }, + UpstreamPathTemplate = "/{aa}", + UpstreamHttpMethod = new List { "Get" }, + UpstreamHeaderTemplates = new Dictionary() + { + [headerName] = "start_{header:country_code}_version_{header:version}_end", + }, }, + }, }; this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/pl/v1/bb", 200, "Hello from Laura")) @@ -715,27 +715,27 @@ public void Should_match_header_with_braces() var configuration = new FileConfiguration { Routes = new List + { + new() { - new FileRoute + DownstreamPathTemplate = "/aa", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List { - DownstreamPathTemplate = "/aa", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = port, - }, - }, - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - UpstreamHeaderTemplates = new Dictionary() + new() { - [headerName] = "my_{header}", + Host = "localhost", + Port = port, }, }, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + UpstreamHeaderTemplates = new Dictionary() + { + [headerName] = "my_{header}", + }, }, + }, }; this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/aa", 200, "Hello from Laura")) @@ -759,27 +759,27 @@ public void Should_match_two_headers_with_the_same_name() var configuration = new FileConfiguration { Routes = new List + { + new() { - new FileRoute + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = port, - }, - }, - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - UpstreamHeaderTemplates = new Dictionary() + new() { - [headerName] = headerValue1 + ";{header:whatever}", + Host = "localhost", + Port = port, }, }, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + UpstreamHeaderTemplates = new Dictionary() + { + [headerName] = headerValue1 + ";{header:whatever}", + }, }, + }, }; this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/", 200, "Hello from Laura")) diff --git a/test/Ocelot.UnitTests/Configuration/UpstreamHeaderTemplatePatternCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/UpstreamHeaderTemplatePatternCreatorTests.cs index cf049eb03..a9478e22d 100644 --- a/test/Ocelot.UnitTests/Configuration/UpstreamHeaderTemplatePatternCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/UpstreamHeaderTemplatePatternCreatorTests.cs @@ -16,7 +16,7 @@ public class UpstreamHeaderTemplatePatternCreatorTests public UpstreamHeaderTemplatePatternCreatorTests() { - _creator = new UpstreamHeaderTemplatePatternCreator(); + _creator = new(); } [Fact] diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinderTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinderTests.cs index cb316a460..13d8f40fd 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinderTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinderTests.cs @@ -40,7 +40,7 @@ public void Should_return_one_placeholder_with_value_when_no_other_text() { var upstreamHeaderTemplates = new Dictionary { - ["country"] = new UpstreamHeaderTemplate("^(?i)(?.+)$", "{header:countrycode}"), + ["country"] = new("^(?i)(?.+)$", "{header:countrycode}"), }; var upstreamHeaders = new Dictionary { @@ -48,7 +48,7 @@ public void Should_return_one_placeholder_with_value_when_no_other_text() }; var expected = new List { - new PlaceholderNameAndValue("{countrycode}", "PL"), + new("{countrycode}", "PL"), }; this.Given(x => x.GivenUpstreamHeaderTemplatesAre(upstreamHeaderTemplates)) @@ -63,7 +63,7 @@ public void Should_return_one_placeholder_with_value_when_other_text_on_the_righ { var upstreamHeaderTemplates = new Dictionary { - ["country"] = new UpstreamHeaderTemplate("^(?.+)-V1$", "{header:countrycode}-V1"), + ["country"] = new("^(?.+)-V1$", "{header:countrycode}-V1"), }; var upstreamHeaders = new Dictionary { @@ -71,7 +71,7 @@ public void Should_return_one_placeholder_with_value_when_other_text_on_the_righ }; var expected = new List { - new PlaceholderNameAndValue("{countrycode}", "PL"), + new("{countrycode}", "PL"), }; this.Given(x => x.GivenUpstreamHeaderTemplatesAre(upstreamHeaderTemplates)) @@ -86,7 +86,7 @@ public void Should_return_one_placeholder_with_value_when_other_text_on_the_left { var upstreamHeaderTemplates = new Dictionary { - ["country"] = new UpstreamHeaderTemplate("^V1-(?.+)$", "V1-{header:countrycode}"), + ["country"] = new("^V1-(?.+)$", "V1-{header:countrycode}"), }; var upstreamHeaders = new Dictionary { @@ -94,7 +94,7 @@ public void Should_return_one_placeholder_with_value_when_other_text_on_the_left }; var expected = new List { - new PlaceholderNameAndValue("{countrycode}", "PL"), + new("{countrycode}", "PL"), }; this.Given(x => x.GivenUpstreamHeaderTemplatesAre(upstreamHeaderTemplates)) @@ -109,7 +109,7 @@ public void Should_return_one_placeholder_with_value_when_other_texts_surroundin { var upstreamHeaderTemplates = new Dictionary { - ["country"] = new UpstreamHeaderTemplate("^cc:(?.+)-V1$", "cc:{header:countrycode}-V1"), + ["country"] = new("^cc:(?.+)-V1$", "cc:{header:countrycode}-V1"), }; var upstreamHeaders = new Dictionary { @@ -117,7 +117,7 @@ public void Should_return_one_placeholder_with_value_when_other_texts_surroundin }; var expected = new List { - new PlaceholderNameAndValue("{countrycode}", "PL"), + new("{countrycode}", "PL"), }; this.Given(x => x.GivenUpstreamHeaderTemplatesAre(upstreamHeaderTemplates)) @@ -132,7 +132,7 @@ public void Should_return_two_placeholders_with_text_between() { var upstreamHeaderTemplates = new Dictionary { - ["countryAndVersion"] = new UpstreamHeaderTemplate("^(?i)(?.+)-(?.+)$", "{header:countrycode}-{header:version}"), + ["countryAndVersion"] = new("^(?i)(?.+)-(?.+)$", "{header:countrycode}-{header:version}"), }; var upstreamHeaders = new Dictionary { @@ -140,8 +140,8 @@ public void Should_return_two_placeholders_with_text_between() }; var expected = new List { - new PlaceholderNameAndValue("{countrycode}", "PL"), - new PlaceholderNameAndValue("{version}", "v1"), + new("{countrycode}", "PL"), + new("{version}", "v1"), }; this.Given(x => x.GivenUpstreamHeaderTemplatesAre(upstreamHeaderTemplates)) @@ -156,8 +156,8 @@ public void Should_return_placeholders_from_different_headers() { var upstreamHeaderTemplates = new Dictionary { - ["country"] = new UpstreamHeaderTemplate("^(?i)(?.+)$", "{header:countrycode}"), - ["version"] = new UpstreamHeaderTemplate("^(?i)(?.+)$", "{header:version}"), + ["country"] = new("^(?i)(?.+)$", "{header:countrycode}"), + ["version"] = new("^(?i)(?.+)$", "{header:version}"), }; var upstreamHeaders = new Dictionary { @@ -166,8 +166,8 @@ public void Should_return_placeholders_from_different_headers() }; var expected = new List { - new PlaceholderNameAndValue("{countrycode}", "PL"), - new PlaceholderNameAndValue("{version}", "v1"), + new("{countrycode}", "PL"), + new("{version}", "v1"), }; this.Given(x => x.GivenUpstreamHeaderTemplatesAre(upstreamHeaderTemplates)) diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/HeaderMatcher/HeadersToHeaderTemplatesMatcherTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/HeaderMatcher/HeadersToHeaderTemplatesMatcherTests.cs index bd63dfc6e..7e00d8ab8 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/HeaderMatcher/HeadersToHeaderTemplatesMatcherTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/HeaderMatcher/HeadersToHeaderTemplatesMatcherTests.cs @@ -46,7 +46,7 @@ public void Should_match_the_same_headers() var templateHeaders = new Dictionary() { - ["anyHeader"] = new UpstreamHeaderTemplate("^(?i)anyHeaderValue$", "anyHeaderValue"), + ["anyHeader"] = new("^(?i)anyHeaderValue$", "anyHeaderValue"), }; this.Given(x => x.GivenIHaveUpstreamHeaders(upstreamHeaders)) @@ -66,7 +66,7 @@ public void Should_not_match_the_same_headers_when_differ_case_and_case_sensitiv var templateHeaders = new Dictionary() { - ["anyHeader"] = new UpstreamHeaderTemplate("^anyHeaderValue$", "anyHeaderValue"), + ["anyHeader"] = new("^anyHeaderValue$", "anyHeaderValue"), }; this.Given(x => x.GivenIHaveUpstreamHeaders(upstreamHeaders)) @@ -86,7 +86,7 @@ public void Should_match_the_same_headers_when_differ_case_and_case_insensitive( var templateHeaders = new Dictionary() { - ["anyHeader"] = new UpstreamHeaderTemplate("^(?i)anyHeaderValue$", "anyHeaderValue"), + ["anyHeader"] = new("^(?i)anyHeaderValue$", "anyHeaderValue"), }; this.Given(x => x.GivenIHaveUpstreamHeaders(upstreamHeaders)) @@ -106,7 +106,7 @@ public void Should_not_match_different_headers_values() var templateHeaders = new Dictionary() { - ["anyHeader"] = new UpstreamHeaderTemplate("^(?i)anyHeaderValue$", "anyHeaderValue"), + ["anyHeader"] = new("^(?i)anyHeaderValue$", "anyHeaderValue"), }; this.Given(x => x.GivenIHaveUpstreamHeaders(upstreamHeaders)) @@ -126,7 +126,7 @@ public void Should_not_match_the_same_headers_names() var templateHeaders = new Dictionary() { - ["anyHeader"] = new UpstreamHeaderTemplate("^(?i)anyHeaderValue$", "anyHeaderValue"), + ["anyHeader"] = new("^(?i)anyHeaderValue$", "anyHeaderValue"), }; this.Given(x => x.GivenIHaveUpstreamHeaders(upstreamHeaders)) @@ -149,9 +149,9 @@ public void Should_match_all_the_same_headers() var templateHeaders = new Dictionary() { - ["secondHeader"] = new UpstreamHeaderTemplate("^(?i)secondHeaderValue$", "secondHeaderValue"), - ["thirdHeader"] = new UpstreamHeaderTemplate("^(?i)thirdHeaderValue$", "thirdHeaderValue"), - ["anyHeader"] = new UpstreamHeaderTemplate("^(?i)anyHeaderValue$", "anyHeaderValue"), + ["secondHeader"] = new("^(?i)secondHeaderValue$", "secondHeaderValue"), + ["thirdHeader"] = new("^(?i)thirdHeaderValue$", "thirdHeaderValue"), + ["anyHeader"] = new("^(?i)anyHeaderValue$", "anyHeaderValue"), }; this.Given(x => x.GivenIHaveUpstreamHeaders(upstreamHeaders)) @@ -174,9 +174,9 @@ public void Should_not_match_the_headers_when_one_of_them_different() var templateHeaders = new Dictionary() { - ["secondHeader"] = new UpstreamHeaderTemplate("^(?i)secondHeaderValue$", "secondHeaderValue"), - ["thirdHeader"] = new UpstreamHeaderTemplate("^(?i)thirdHeaderValue$", "thirdHeaderValue"), - ["anyHeader"] = new UpstreamHeaderTemplate("^(?i)anyHeaderValue$", "anyHeaderValue"), + ["secondHeader"] = new("^(?i)secondHeaderValue$", "secondHeaderValue"), + ["thirdHeader"] = new("^(?i)thirdHeaderValue$", "thirdHeaderValue"), + ["anyHeader"] = new("^(?i)anyHeaderValue$", "anyHeaderValue"), }; this.Given(x => x.GivenIHaveUpstreamHeaders(upstreamHeaders)) @@ -196,7 +196,7 @@ public void Should_match_the_header_with_placeholder() var templateHeaders = new Dictionary() { - ["anyHeader"] = new UpstreamHeaderTemplate("^(?i)(?.+)$", "{header:countrycode}"), + ["anyHeader"] = new("^(?i)(?.+)$", "{header:countrycode}"), }; this.Given(x => x.GivenIHaveUpstreamHeaders(upstreamHeaders)) @@ -216,7 +216,7 @@ public void Should_match_the_header_with_placeholders() var templateHeaders = new Dictionary() { - ["anyHeader"] = new UpstreamHeaderTemplate("^(?i)(?.+)-(?.+)$", "{header:countrycode}-{header:version}"), + ["anyHeader"] = new("^(?i)(?.+)-(?.+)$", "{header:countrycode}-{header:version}"), }; this.Given(x => x.GivenIHaveUpstreamHeaders(upstreamHeaders)) @@ -236,7 +236,7 @@ public void Should_not_match_the_header_with_placeholders() var templateHeaders = new Dictionary() { - ["anyHeader"] = new UpstreamHeaderTemplate("^(?i)(?.+)-(?.+)$", "{header:countrycode}-{header:version}"), + ["anyHeader"] = new("^(?i)(?.+)-(?.+)$", "{header:countrycode}-{header:version}"), }; this.Given(x => x.GivenIHaveUpstreamHeaders(upstreamHeaders)) From d6c86592def528244628bd5b524260606c7c5933 Mon Sep 17 00:00:00 2001 From: Raman Maksimchuk Date: Tue, 2 Apr 2024 20:03:31 +0300 Subject: [PATCH 23/49] Fix build errors --- .../RoutingBasedOnHeadersTests.cs | 43 ++++++++----------- test/Ocelot.Testing/PortFinder.cs | 3 +- 2 files changed, 20 insertions(+), 26 deletions(-) diff --git a/test/Ocelot.AcceptanceTests/RoutingBasedOnHeadersTests.cs b/test/Ocelot.AcceptanceTests/RoutingBasedOnHeadersTests.cs index 634740a5d..690bada91 100644 --- a/test/Ocelot.AcceptanceTests/RoutingBasedOnHeadersTests.cs +++ b/test/Ocelot.AcceptanceTests/RoutingBasedOnHeadersTests.cs @@ -1,11 +1,6 @@ using Microsoft.AspNetCore.Http; using Ocelot.Configuration.File; -using Shouldly; -using System; -using System.Collections.Generic; -using System.Net; -using TestStack.BDDfy; -using Xunit; +using Ocelot.Testing; namespace Ocelot.AcceptanceTests; @@ -24,7 +19,7 @@ public RoutingBasedOnHeadersTests() [Fact] public void Should_match_one_header_value() { - var port = RandomPortFinder.GetRandomPort(); + var port = PortFinder.GetRandomPort(); var headerName = "country_code"; var headerValue = "PL"; @@ -67,7 +62,7 @@ public void Should_match_one_header_value() [Fact] public void Should_match_one_header_value_when_more_headers() { - var port = RandomPortFinder.GetRandomPort(); + var port = PortFinder.GetRandomPort(); var headerName = "country_code"; var headerValue = "PL"; @@ -111,7 +106,7 @@ public void Should_match_one_header_value_when_more_headers() [Fact] public void Should_match_two_header_values_when_more_headers() { - var port = RandomPortFinder.GetRandomPort(); + var port = PortFinder.GetRandomPort(); var headerName1 = "country_code"; var headerValue1 = "PL"; var headerName2 = "region"; @@ -159,7 +154,7 @@ public void Should_match_two_header_values_when_more_headers() [Fact] public void Should_not_match_one_header_value() { - var port = RandomPortFinder.GetRandomPort(); + var port = PortFinder.GetRandomPort(); var headerName = "country_code"; var headerValue = "PL"; var anotherHeaderValue = "UK"; @@ -202,7 +197,7 @@ public void Should_not_match_one_header_value() [Fact] public void Should_not_match_one_header_value_when_no_headers() { - var port = RandomPortFinder.GetRandomPort(); + var port = PortFinder.GetRandomPort(); var headerName = "country_code"; var headerValue = "PL"; @@ -243,7 +238,7 @@ public void Should_not_match_one_header_value_when_no_headers() [Fact] public void Should_not_match_two_header_values_when_one_different() { - var port = RandomPortFinder.GetRandomPort(); + var port = PortFinder.GetRandomPort(); var headerName1 = "country_code"; var headerValue1 = "PL"; var headerName2 = "region"; @@ -290,7 +285,7 @@ public void Should_not_match_two_header_values_when_one_different() [Fact] public void Should_not_match_two_header_values_when_one_not_existing() { - var port = RandomPortFinder.GetRandomPort(); + var port = PortFinder.GetRandomPort(); var headerName1 = "country_code"; var headerValue1 = "PL"; var headerName2 = "region"; @@ -336,7 +331,7 @@ public void Should_not_match_two_header_values_when_one_not_existing() [Fact] public void Should_not_match_one_header_value_when_header_duplicated() { - var port = RandomPortFinder.GetRandomPort(); + var port = PortFinder.GetRandomPort(); var headerName = "country_code"; var headerValue = "PL"; @@ -379,8 +374,8 @@ public void Should_not_match_one_header_value_when_header_duplicated() [Fact] public void Should_aggregated_route_match_header_value() { - var port1 = RandomPortFinder.GetRandomPort(); - var port2 = RandomPortFinder.GetRandomPort(); + var port1 = PortFinder.GetRandomPort(); + var port2 = PortFinder.GetRandomPort(); var headerName = "country_code"; var headerValue = "PL"; @@ -453,8 +448,8 @@ public void Should_aggregated_route_match_header_value() [Fact] public void Should_aggregated_route_not_match_header_value() { - var port1 = RandomPortFinder.GetRandomPort(); - var port2 = RandomPortFinder.GetRandomPort(); + var port1 = PortFinder.GetRandomPort(); + var port2 = PortFinder.GetRandomPort(); var headerName = "country_code"; var headerValue = "PL"; @@ -526,7 +521,7 @@ public void Should_aggregated_route_not_match_header_value() [Fact] public void Should_match_header_placeholder() { - var port = RandomPortFinder.GetRandomPort(); + var port = PortFinder.GetRandomPort(); var headerName = "Region"; var configuration = new FileConfiguration @@ -568,7 +563,7 @@ public void Should_match_header_placeholder() [Fact] public void Should_match_header_placeholder_not_in_downstream_path() { - var port = RandomPortFinder.GetRandomPort(); + var port = PortFinder.GetRandomPort(); var headerName = "ProductName"; var configuration = new FileConfiguration @@ -610,7 +605,7 @@ public void Should_match_header_placeholder_not_in_downstream_path() [Fact] public void Should_distinguish_route_for_different_roles() { - var port = RandomPortFinder.GetRandomPort(); + var port = PortFinder.GetRandomPort(); var headerName = "Origin"; var configuration = new FileConfiguration @@ -667,7 +662,7 @@ public void Should_distinguish_route_for_different_roles() [Fact] public void Should_match_header_and_url_placeholders() { - var port = RandomPortFinder.GetRandomPort(); + var port = PortFinder.GetRandomPort(); var headerName = "country_code"; var configuration = new FileConfiguration @@ -709,7 +704,7 @@ public void Should_match_header_and_url_placeholders() [Fact] public void Should_match_header_with_braces() { - var port = RandomPortFinder.GetRandomPort(); + var port = PortFinder.GetRandomPort(); var headerName = "country_code"; var configuration = new FileConfiguration @@ -751,7 +746,7 @@ public void Should_match_header_with_braces() [Fact] public void Should_match_two_headers_with_the_same_name() { - var port = RandomPortFinder.GetRandomPort(); + var port = PortFinder.GetRandomPort(); var headerName = "country_code"; var headerValue1 = "PL"; var headerValue2 = "UK"; diff --git a/test/Ocelot.Testing/PortFinder.cs b/test/Ocelot.Testing/PortFinder.cs index 4b661ada7..6eb6b64d4 100644 --- a/test/Ocelot.Testing/PortFinder.cs +++ b/test/Ocelot.Testing/PortFinder.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Concurrent; +using System.Collections.Concurrent; using System.Net; using System.Net.Sockets; From 29d58b7b605c4d2d07d5d725723fe7631c7e8a04 Mon Sep 17 00:00:00 2001 From: Raman Maksimchuk Date: Sat, 6 Apr 2024 12:26:58 +0300 Subject: [PATCH 24/49] DownstreamRouteBuilder --- .../Builder/DownstreamRouteBuilder.cs | 607 +++++++++--------- 1 file changed, 304 insertions(+), 303 deletions(-) diff --git a/src/Ocelot/Configuration/Builder/DownstreamRouteBuilder.cs b/src/Ocelot/Configuration/Builder/DownstreamRouteBuilder.cs index 711b73fc0..a3f7e5ec4 100644 --- a/src/Ocelot/Configuration/Builder/DownstreamRouteBuilder.cs +++ b/src/Ocelot/Configuration/Builder/DownstreamRouteBuilder.cs @@ -1,309 +1,310 @@ using Ocelot.Configuration.Creator; using Ocelot.Values; -namespace Ocelot.Configuration.Builder +namespace Ocelot.Configuration.Builder; + +public class DownstreamRouteBuilder { - public class DownstreamRouteBuilder - { - private AuthenticationOptions _authenticationOptions; - private string _loadBalancerKey; - private string _downstreamPathTemplate; - private UpstreamPathTemplate _upstreamTemplatePattern; - private List _upstreamHttpMethod; - private bool _isAuthenticated; - private List _claimsToHeaders; - private List _claimToClaims; - private Dictionary _routeClaimRequirement; - private bool _isAuthorized; - private List _claimToQueries; - private List _claimToDownstreamPath; - private string _requestIdHeaderKey; - private bool _isCached; - private CacheOptions _fileCacheOptions; - private string _downstreamScheme; - private LoadBalancerOptions _loadBalancerOptions; - private QoSOptions _qosOptions; - private HttpHandlerOptions _httpHandlerOptions; - private bool _enableRateLimiting; - private RateLimitOptions _rateLimitOptions; - private bool _useServiceDiscovery; - private string _serviceName; - private string _serviceNamespace; - private List _upstreamHeaderFindAndReplace; - private List _downstreamHeaderFindAndReplace; - private readonly List _downstreamAddresses; - private string _key; - private List _delegatingHandlers; - private List _addHeadersToDownstream; - private List _addHeadersToUpstream; - private bool _dangerousAcceptAnyServerCertificateValidator; - private SecurityOptions _securityOptions; - private string _downstreamHttpMethod; - private Version _downstreamHttpVersion; - private Dictionary _upstreamHeaders; - - public DownstreamRouteBuilder() - { - _downstreamAddresses = new List(); - _delegatingHandlers = new List(); - _addHeadersToDownstream = new List(); - _addHeadersToUpstream = new List(); - } - - public DownstreamRouteBuilder WithDownstreamAddresses(List downstreamAddresses) - { - _downstreamAddresses.AddRange(downstreamAddresses); - return this; - } - - public DownstreamRouteBuilder WithDownStreamHttpMethod(string method) - { - _downstreamHttpMethod = method; - return this; - } - - public DownstreamRouteBuilder WithLoadBalancerOptions(LoadBalancerOptions loadBalancerOptions) - { - _loadBalancerOptions = loadBalancerOptions; - return this; - } - - public DownstreamRouteBuilder WithDownstreamScheme(string downstreamScheme) - { - _downstreamScheme = downstreamScheme; - return this; - } - - public DownstreamRouteBuilder WithDownstreamPathTemplate(string input) - { - _downstreamPathTemplate = input; - return this; - } - - public DownstreamRouteBuilder WithUpstreamPathTemplate(UpstreamPathTemplate input) - { - _upstreamTemplatePattern = input; - return this; - } - - public DownstreamRouteBuilder WithUpstreamHttpMethod(List input) - { - _upstreamHttpMethod = (input.Count == 0) ? new List() : input.Select(x => new HttpMethod(x.Trim())).ToList(); - return this; - } - - public DownstreamRouteBuilder WithIsAuthenticated(bool input) - { - _isAuthenticated = input; - return this; - } - - public DownstreamRouteBuilder WithIsAuthorized(bool input) - { - _isAuthorized = input; - return this; - } - - public DownstreamRouteBuilder WithRequestIdKey(string input) - { - _requestIdHeaderKey = input; - return this; - } - - public DownstreamRouteBuilder WithClaimsToHeaders(List input) - { - _claimsToHeaders = input; - return this; - } - - public DownstreamRouteBuilder WithClaimsToClaims(List input) - { - _claimToClaims = input; - return this; - } - - public DownstreamRouteBuilder WithRouteClaimsRequirement(Dictionary input) - { - _routeClaimRequirement = input; - return this; - } - - public DownstreamRouteBuilder WithClaimsToQueries(List input) - { - _claimToQueries = input; - return this; - } - - public DownstreamRouteBuilder WithClaimsToDownstreamPath(List input) - { - _claimToDownstreamPath = input; - return this; - } - - public DownstreamRouteBuilder WithIsCached(bool input) - { - _isCached = input; - return this; - } - - public DownstreamRouteBuilder WithCacheOptions(CacheOptions input) - { - _fileCacheOptions = input; - return this; - } - - public DownstreamRouteBuilder WithQosOptions(QoSOptions input) - { - _qosOptions = input; - return this; - } - - public DownstreamRouteBuilder WithLoadBalancerKey(string loadBalancerKey) - { - _loadBalancerKey = loadBalancerKey; - return this; - } - - public DownstreamRouteBuilder WithAuthenticationOptions(AuthenticationOptions authenticationOptions) - { - _authenticationOptions = authenticationOptions; - return this; - } - - public DownstreamRouteBuilder WithEnableRateLimiting(bool input) - { - _enableRateLimiting = input; - return this; - } - - public DownstreamRouteBuilder WithRateLimitOptions(RateLimitOptions input) - { - _rateLimitOptions = input; - return this; - } - - public DownstreamRouteBuilder WithHttpHandlerOptions(HttpHandlerOptions input) - { - _httpHandlerOptions = input; - return this; - } - - public DownstreamRouteBuilder WithUseServiceDiscovery(bool useServiceDiscovery) - { - _useServiceDiscovery = useServiceDiscovery; - return this; - } - - public DownstreamRouteBuilder WithServiceName(string serviceName) - { - _serviceName = serviceName; - return this; - } - - public DownstreamRouteBuilder WithServiceNamespace(string serviceNamespace) - { - _serviceNamespace = serviceNamespace; - return this; - } - - public DownstreamRouteBuilder WithUpstreamHeaderFindAndReplace(List upstreamHeaderFindAndReplace) - { - _upstreamHeaderFindAndReplace = upstreamHeaderFindAndReplace; - return this; - } - - public DownstreamRouteBuilder WithDownstreamHeaderFindAndReplace(List downstreamHeaderFindAndReplace) - { - _downstreamHeaderFindAndReplace = downstreamHeaderFindAndReplace; - return this; - } - - public DownstreamRouteBuilder WithKey(string key) - { - _key = key; - return this; - } - - public DownstreamRouteBuilder WithDelegatingHandlers(List delegatingHandlers) - { - _delegatingHandlers = delegatingHandlers; - return this; - } - - public DownstreamRouteBuilder WithAddHeadersToDownstream(List addHeadersToDownstream) - { - _addHeadersToDownstream = addHeadersToDownstream; - return this; - } - - public DownstreamRouteBuilder WithAddHeadersToUpstream(List addHeadersToUpstream) - { - _addHeadersToUpstream = addHeadersToUpstream; - return this; - } - - public DownstreamRouteBuilder WithDangerousAcceptAnyServerCertificateValidator(bool dangerousAcceptAnyServerCertificateValidator) - { - _dangerousAcceptAnyServerCertificateValidator = dangerousAcceptAnyServerCertificateValidator; - return this; - } - - public DownstreamRouteBuilder WithSecurityOptions(SecurityOptions securityOptions) - { - _securityOptions = securityOptions; - return this; - } - - public DownstreamRouteBuilder WithDownstreamHttpVersion(Version downstreamHttpVersion) - { - _downstreamHttpVersion = downstreamHttpVersion; - return this; - } - - public DownstreamRouteBuilder WithUpstreamHeaders(Dictionary input) - { - _upstreamHeaders = input; - return this; - } - - public DownstreamRoute Build() - { - return new DownstreamRoute( - _key, - _upstreamTemplatePattern, - _upstreamHeaderFindAndReplace, - _downstreamHeaderFindAndReplace, - _downstreamAddresses, - _serviceName, - _serviceNamespace, - _httpHandlerOptions, - _useServiceDiscovery, - _enableRateLimiting, - _qosOptions, - _downstreamScheme, - _requestIdHeaderKey, - _isCached, - _fileCacheOptions, - _loadBalancerOptions, - _rateLimitOptions, - _routeClaimRequirement, - _claimToQueries, - _claimsToHeaders, - _claimToClaims, - _claimToDownstreamPath, - _isAuthenticated, - _isAuthorized, - _authenticationOptions, - new DownstreamPathTemplate(_downstreamPathTemplate), - _loadBalancerKey, - _delegatingHandlers, - _addHeadersToDownstream, - _addHeadersToUpstream, - _dangerousAcceptAnyServerCertificateValidator, - _securityOptions, - _downstreamHttpMethod, - _downstreamHttpVersion, - _upstreamHeaders); - } + private AuthenticationOptions _authenticationOptions; + private string _loadBalancerKey; + private string _downstreamPathTemplate; + private UpstreamPathTemplate _upstreamTemplatePattern; + private List _upstreamHttpMethod; + private bool _isAuthenticated; + private List _claimsToHeaders; + private List _claimToClaims; + private Dictionary _routeClaimRequirement; + private bool _isAuthorized; + private List _claimToQueries; + private List _claimToDownstreamPath; + private string _requestIdHeaderKey; + private bool _isCached; + private CacheOptions _fileCacheOptions; + private string _downstreamScheme; + private LoadBalancerOptions _loadBalancerOptions; + private QoSOptions _qosOptions; + private HttpHandlerOptions _httpHandlerOptions; + private bool _enableRateLimiting; + private RateLimitOptions _rateLimitOptions; + private bool _useServiceDiscovery; + private string _serviceName; + private string _serviceNamespace; + private List _upstreamHeaderFindAndReplace; + private List _downstreamHeaderFindAndReplace; + private readonly List _downstreamAddresses; + private string _key; + private List _delegatingHandlers; + private List _addHeadersToDownstream; + private List _addHeadersToUpstream; + private bool _dangerousAcceptAnyServerCertificateValidator; + private SecurityOptions _securityOptions; + private string _downstreamHttpMethod; + private Version _downstreamHttpVersion; + private Dictionary _upstreamHeaders; + + public DownstreamRouteBuilder() + { + _downstreamAddresses = []; + _delegatingHandlers = []; + _addHeadersToDownstream = []; + _addHeadersToUpstream = []; + } + + public DownstreamRouteBuilder WithDownstreamAddresses(List downstreamAddresses) + { + _downstreamAddresses.AddRange(downstreamAddresses); + return this; + } + + public DownstreamRouteBuilder WithDownStreamHttpMethod(string method) + { + _downstreamHttpMethod = method; + return this; + } + + public DownstreamRouteBuilder WithLoadBalancerOptions(LoadBalancerOptions loadBalancerOptions) + { + _loadBalancerOptions = loadBalancerOptions; + return this; + } + + public DownstreamRouteBuilder WithDownstreamScheme(string downstreamScheme) + { + _downstreamScheme = downstreamScheme; + return this; + } + + public DownstreamRouteBuilder WithDownstreamPathTemplate(string input) + { + _downstreamPathTemplate = input; + return this; + } + + public DownstreamRouteBuilder WithUpstreamPathTemplate(UpstreamPathTemplate input) + { + _upstreamTemplatePattern = input; + return this; + } + + public DownstreamRouteBuilder WithUpstreamHttpMethod(List input) + { + _upstreamHttpMethod = input.Count > 0 + ? input.Select(x => new HttpMethod(x.Trim())).ToList() + : []; + return this; + } + + public DownstreamRouteBuilder WithIsAuthenticated(bool input) + { + _isAuthenticated = input; + return this; + } + + public DownstreamRouteBuilder WithIsAuthorized(bool input) + { + _isAuthorized = input; + return this; + } + + public DownstreamRouteBuilder WithRequestIdKey(string input) + { + _requestIdHeaderKey = input; + return this; + } + + public DownstreamRouteBuilder WithClaimsToHeaders(List input) + { + _claimsToHeaders = input; + return this; + } + + public DownstreamRouteBuilder WithClaimsToClaims(List input) + { + _claimToClaims = input; + return this; + } + + public DownstreamRouteBuilder WithRouteClaimsRequirement(Dictionary input) + { + _routeClaimRequirement = input; + return this; + } + + public DownstreamRouteBuilder WithClaimsToQueries(List input) + { + _claimToQueries = input; + return this; + } + + public DownstreamRouteBuilder WithClaimsToDownstreamPath(List input) + { + _claimToDownstreamPath = input; + return this; + } + + public DownstreamRouteBuilder WithIsCached(bool input) + { + _isCached = input; + return this; + } + + public DownstreamRouteBuilder WithCacheOptions(CacheOptions input) + { + _fileCacheOptions = input; + return this; + } + + public DownstreamRouteBuilder WithQosOptions(QoSOptions input) + { + _qosOptions = input; + return this; + } + + public DownstreamRouteBuilder WithLoadBalancerKey(string loadBalancerKey) + { + _loadBalancerKey = loadBalancerKey; + return this; + } + + public DownstreamRouteBuilder WithAuthenticationOptions(AuthenticationOptions authenticationOptions) + { + _authenticationOptions = authenticationOptions; + return this; + } + + public DownstreamRouteBuilder WithEnableRateLimiting(bool input) + { + _enableRateLimiting = input; + return this; + } + + public DownstreamRouteBuilder WithRateLimitOptions(RateLimitOptions input) + { + _rateLimitOptions = input; + return this; + } + + public DownstreamRouteBuilder WithHttpHandlerOptions(HttpHandlerOptions input) + { + _httpHandlerOptions = input; + return this; + } + + public DownstreamRouteBuilder WithUseServiceDiscovery(bool useServiceDiscovery) + { + _useServiceDiscovery = useServiceDiscovery; + return this; + } + + public DownstreamRouteBuilder WithServiceName(string serviceName) + { + _serviceName = serviceName; + return this; + } + + public DownstreamRouteBuilder WithServiceNamespace(string serviceNamespace) + { + _serviceNamespace = serviceNamespace; + return this; + } + + public DownstreamRouteBuilder WithUpstreamHeaderFindAndReplace(List upstreamHeaderFindAndReplace) + { + _upstreamHeaderFindAndReplace = upstreamHeaderFindAndReplace; + return this; + } + + public DownstreamRouteBuilder WithDownstreamHeaderFindAndReplace(List downstreamHeaderFindAndReplace) + { + _downstreamHeaderFindAndReplace = downstreamHeaderFindAndReplace; + return this; + } + + public DownstreamRouteBuilder WithKey(string key) + { + _key = key; + return this; + } + + public DownstreamRouteBuilder WithDelegatingHandlers(List delegatingHandlers) + { + _delegatingHandlers = delegatingHandlers; + return this; + } + + public DownstreamRouteBuilder WithAddHeadersToDownstream(List addHeadersToDownstream) + { + _addHeadersToDownstream = addHeadersToDownstream; + return this; + } + + public DownstreamRouteBuilder WithAddHeadersToUpstream(List addHeadersToUpstream) + { + _addHeadersToUpstream = addHeadersToUpstream; + return this; + } + + public DownstreamRouteBuilder WithDangerousAcceptAnyServerCertificateValidator(bool dangerousAcceptAnyServerCertificateValidator) + { + _dangerousAcceptAnyServerCertificateValidator = dangerousAcceptAnyServerCertificateValidator; + return this; + } + + public DownstreamRouteBuilder WithSecurityOptions(SecurityOptions securityOptions) + { + _securityOptions = securityOptions; + return this; + } + + public DownstreamRouteBuilder WithDownstreamHttpVersion(Version downstreamHttpVersion) + { + _downstreamHttpVersion = downstreamHttpVersion; + return this; + } + + public DownstreamRouteBuilder WithUpstreamHeaders(Dictionary input) + { + _upstreamHeaders = input; + return this; + } + + public DownstreamRoute Build() + { + return new DownstreamRoute( + _key, + _upstreamTemplatePattern, + _upstreamHeaderFindAndReplace, + _downstreamHeaderFindAndReplace, + _downstreamAddresses, + _serviceName, + _serviceNamespace, + _httpHandlerOptions, + _useServiceDiscovery, + _enableRateLimiting, + _qosOptions, + _downstreamScheme, + _requestIdHeaderKey, + _isCached, + _fileCacheOptions, + _loadBalancerOptions, + _rateLimitOptions, + _routeClaimRequirement, + _claimToQueries, + _claimsToHeaders, + _claimToClaims, + _claimToDownstreamPath, + _isAuthenticated, + _isAuthorized, + _authenticationOptions, + new DownstreamPathTemplate(_downstreamPathTemplate), + _loadBalancerKey, + _delegatingHandlers, + _addHeadersToDownstream, + _addHeadersToUpstream, + _dangerousAcceptAnyServerCertificateValidator, + _securityOptions, + _downstreamHttpMethod, + _downstreamHttpVersion, + _upstreamHeaders); } } From 8a6495501a8c0b417714bc7bdd965352dde6398e Mon Sep 17 00:00:00 2001 From: Raman Maksimchuk Date: Sat, 6 Apr 2024 12:42:14 +0300 Subject: [PATCH 25/49] AggregatesCreator --- .../Creator/AggregatesCreator.cs | 29 ++++++++----------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/src/Ocelot/Configuration/Creator/AggregatesCreator.cs b/src/Ocelot/Configuration/Creator/AggregatesCreator.cs index 4963513f2..24d3a58b3 100644 --- a/src/Ocelot/Configuration/Creator/AggregatesCreator.cs +++ b/src/Ocelot/Configuration/Creator/AggregatesCreator.cs @@ -1,21 +1,17 @@ using Ocelot.Configuration.Builder; using Ocelot.Configuration.File; -using Ocelot.Values; namespace Ocelot.Configuration.Creator { public class AggregatesCreator : IAggregatesCreator { - private readonly IUpstreamTemplatePatternCreator _upstreamTemplatePatternCreator; - private readonly IUpstreamHeaderTemplatePatternCreator _upstreamHeaderTemplatePatternCreator; + private readonly IUpstreamTemplatePatternCreator _creator; + private readonly IUpstreamHeaderTemplatePatternCreator _headerCreator; - public AggregatesCreator( - IUpstreamTemplatePatternCreator upstreamTemplatePatternCreator, - IUpstreamHeaderTemplatePatternCreator upstreamHeaderTemplatePatternCreator - ) + public AggregatesCreator(IUpstreamTemplatePatternCreator creator, IUpstreamHeaderTemplatePatternCreator headerCreator) { - _upstreamTemplatePatternCreator = upstreamTemplatePatternCreator; - _upstreamHeaderTemplatePatternCreator = upstreamHeaderTemplatePatternCreator; + _creator = creator; + _headerCreator = headerCreator; } public List Create(FileConfiguration fileConfiguration, List routes) @@ -32,18 +28,17 @@ private Route SetUpAggregateRoute(IEnumerable routes, FileAggregateRoute var allRoutes = routes.SelectMany(x => x.DownstreamRoute); var downstreamRoutes = aggregateRoute.RouteKeys.Select(routeKey => allRoutes.FirstOrDefault(q => q.Key == routeKey)); foreach (var downstreamRoute in downstreamRoutes) - { + { if (downstreamRoute == null) - { - return null; - } - + { + return null; + } + applicableRoutes.Add(downstreamRoute); } - var upstreamTemplatePattern = _upstreamTemplatePatternCreator.Create(aggregateRoute); - - var upstreamHeaderTemplates = _upstreamHeaderTemplatePatternCreator.Create(aggregateRoute); + var upstreamTemplatePattern = _creator.Create(aggregateRoute); + var upstreamHeaderTemplates = _headerCreator.Create(aggregateRoute); var route = new RouteBuilder() .WithUpstreamHttpMethod(aggregateRoute.UpstreamHttpMethod) From 864b63ad6febaac2ee35201fd8f5cabf5a53f63b Mon Sep 17 00:00:00 2001 From: Raman Maksimchuk Date: Sat, 6 Apr 2024 13:01:03 +0300 Subject: [PATCH 26/49] IUpstreamHeaderTemplatePatternCreator, RoutesCreator --- .../Creator/IUpstreamHeaderTemplatePatternCreator.cs | 9 ++++++++- src/Ocelot/Configuration/Creator/RoutesCreator.cs | 6 +----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/Ocelot/Configuration/Creator/IUpstreamHeaderTemplatePatternCreator.cs b/src/Ocelot/Configuration/Creator/IUpstreamHeaderTemplatePatternCreator.cs index 428badd45..7773f27a6 100644 --- a/src/Ocelot/Configuration/Creator/IUpstreamHeaderTemplatePatternCreator.cs +++ b/src/Ocelot/Configuration/Creator/IUpstreamHeaderTemplatePatternCreator.cs @@ -1,10 +1,17 @@ using Ocelot.Configuration.File; using Ocelot.Values; -using System.Collections.Generic; namespace Ocelot.Configuration.Creator; +/// +/// Ocelot feature: Routing based on request header.
+///
public interface IUpstreamHeaderTemplatePatternCreator { + /// + /// Creates upstream templates based on route headers. + /// + /// The route info. + /// A object where TKey is , TValue is . Dictionary Create(IRoute route); } diff --git a/src/Ocelot/Configuration/Creator/RoutesCreator.cs b/src/Ocelot/Configuration/Creator/RoutesCreator.cs index 5b0c107f6..0a374268d 100644 --- a/src/Ocelot/Configuration/Creator/RoutesCreator.cs +++ b/src/Ocelot/Configuration/Creator/RoutesCreator.cs @@ -1,8 +1,6 @@ using Ocelot.Cache; using Ocelot.Configuration.Builder; using Ocelot.Configuration.File; - -using Ocelot.Values; namespace Ocelot.Configuration.Creator { @@ -41,8 +39,7 @@ public RoutesCreator( IRouteKeyCreator routeKeyCreator, ISecurityOptionsCreator securityOptionsCreator, IVersionCreator versionCreator, - IUpstreamHeaderTemplatePatternCreator upstreamHeaderTemplatePatternCreator - ) + IUpstreamHeaderTemplatePatternCreator upstreamHeaderTemplatePatternCreator) { _routeKeyCreator = routeKeyCreator; _loadBalancerOptionsCreator = loadBalancerOptionsCreator; @@ -156,7 +153,6 @@ private DownstreamRoute SetUpDownstreamRoute(FileRoute fileRoute, FileGlobalConf private Route SetUpRoute(FileRoute fileRoute, DownstreamRoute downstreamRoutes) { var upstreamTemplatePattern = _upstreamTemplatePatternCreator.Create(fileRoute); - var upstreamHeaderTemplates = _upstreamHeaderTemplatePatternCreator.Create(fileRoute); var route = new RouteBuilder() From 79adba658c551f47611a80b9abb9198e5200355f Mon Sep 17 00:00:00 2001 From: Raman Maksimchuk Date: Sat, 6 Apr 2024 14:00:19 +0300 Subject: [PATCH 27/49] UpstreamHeaderTemplatePatternCreator --- .../UpstreamHeaderTemplatePatternCreator.cs | 47 +++++++++---------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/src/Ocelot/Configuration/Creator/UpstreamHeaderTemplatePatternCreator.cs b/src/Ocelot/Configuration/Creator/UpstreamHeaderTemplatePatternCreator.cs index 3458c2a9c..3afb3501a 100644 --- a/src/Ocelot/Configuration/Creator/UpstreamHeaderTemplatePatternCreator.cs +++ b/src/Ocelot/Configuration/Creator/UpstreamHeaderTemplatePatternCreator.cs @@ -1,16 +1,21 @@ using Ocelot.Configuration.File; using Ocelot.Values; -using System.Collections.Generic; -using System.Linq; -using System.Text.RegularExpressions; namespace Ocelot.Configuration.Creator; -public class UpstreamHeaderTemplatePatternCreator : IUpstreamHeaderTemplatePatternCreator +/// +/// Default creator of upstream templates based on route headers. +/// +/// Ocelot feature: Routing based on request header. +public partial class UpstreamHeaderTemplatePatternCreator : IUpstreamHeaderTemplatePatternCreator { - private const string RegExMatchOneOrMoreOfEverything = ".+"; - private const string RegExIgnoreCase = "(?i)"; - private const string RegExPlaceholders = @"(\{header:.*?\})"; +#if NET7_0_OR_GREATER + [GeneratedRegex(@"(\{header:.*?\})", RegexOptions.IgnoreCase | RegexOptions.Singleline, "en-US")] + private static partial Regex RegExPlaceholders(); +#else + private static readonly Regex RegExPlaceholdersVar = new(@"(\{header:.*?\})", RegexOptions.IgnoreCase | RegexOptions.Singleline, TimeSpan.FromMilliseconds(1000)); + private static Regex RegExPlaceholders() => RegExPlaceholdersVar; +#endif public Dictionary Create(IRoute route) { @@ -19,30 +24,24 @@ public Dictionary Create(IRoute route) foreach (var headerTemplate in route.UpstreamHeaderTemplates) { var headerTemplateValue = headerTemplate.Value; - - var placeholders = new List(); - - Regex expression = new Regex(RegExPlaceholders); - MatchCollection matches = expression.Matches(headerTemplateValue); + var matches = RegExPlaceholders().Matches(headerTemplateValue); if (matches.Count > 0) { - placeholders.AddRange(matches.Select(m => m.Groups[1].Value)); - } - - for (int i = 0; i < placeholders.Count; i++) - { - var indexOfPlaceholder = headerTemplateValue.IndexOf(placeholders[i]); - - var placeholderName = placeholders[i][8..^1]; // remove "{header:" and "}" - headerTemplateValue = headerTemplateValue.Replace(placeholders[i], "(?<" + placeholderName + ">" + RegExMatchOneOrMoreOfEverything + ")"); + var placeholders = matches.Select(m => m.Groups[1].Value).ToArray(); + for (int i = 0; i < placeholders.Length; i++) + { + var indexOfPlaceholder = headerTemplateValue.IndexOf(placeholders[i]); + var placeholderName = placeholders[i][8..^1]; // remove "{header:" and "}" + headerTemplateValue = headerTemplateValue.Replace(placeholders[i], $"(?<{placeholderName}>.+)"); + } } var template = route.RouteIsCaseSensitive - ? $"^{headerTemplateValue}$" - : $"^{RegExIgnoreCase}{headerTemplateValue}$"; + ? $"^{headerTemplateValue}$" + : $"^(?i){headerTemplateValue}$"; // ignore case - resultHeaderTemplates.Add(headerTemplate.Key, new UpstreamHeaderTemplate(template, headerTemplate.Value)); + resultHeaderTemplates.Add(headerTemplate.Key, new(template, headerTemplate.Value)); } return resultHeaderTemplates; From 9a1d11746382ba975cdf9341d0fb3b55741e6628 Mon Sep 17 00:00:00 2001 From: Raman Maksimchuk Date: Sat, 6 Apr 2024 14:16:28 +0300 Subject: [PATCH 28/49] FileAggregateRoute --- src/Ocelot/Configuration/DownstreamRoute.cs | 2 +- .../Configuration/File/FileAggregateRoute.cs | 16 ++++++---------- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/Ocelot/Configuration/DownstreamRoute.cs b/src/Ocelot/Configuration/DownstreamRoute.cs index 5484d4f5f..f71563cb0 100644 --- a/src/Ocelot/Configuration/DownstreamRoute.cs +++ b/src/Ocelot/Configuration/DownstreamRoute.cs @@ -76,7 +76,7 @@ public DownstreamRoute( SecurityOptions = securityOptions; DownstreamHttpMethod = downstreamHttpMethod; DownstreamHttpVersion = downstreamHttpVersion; - UpstreamHeaders = upstreamHeaders ?? new Dictionary(); + UpstreamHeaders = upstreamHeaders ?? new(); } public string Key { get; } diff --git a/src/Ocelot/Configuration/File/FileAggregateRoute.cs b/src/Ocelot/Configuration/File/FileAggregateRoute.cs index 28f2a1263..8a9aa4389 100644 --- a/src/Ocelot/Configuration/File/FileAggregateRoute.cs +++ b/src/Ocelot/Configuration/File/FileAggregateRoute.cs @@ -1,3 +1,5 @@ +using Microsoft.AspNetCore.Http; + namespace Ocelot.Configuration.File { public class FileAggregateRoute : IRoute @@ -7,17 +9,11 @@ public class FileAggregateRoute : IRoute public string UpstreamPathTemplate { get; set; } public string UpstreamHost { get; set; } public bool RouteIsCaseSensitive { get; set; } - public string Aggregator { get; set; } - public Dictionary UpstreamHeaderTemplates { get; set; } + public string Aggregator { get; set; } // Only supports GET..are you crazy!! POST, PUT WOULD BE CRAZY!! :) - public List UpstreamHttpMethod => new() { "Get" }; - - public int Priority { get; set; } = 1; - - public FileAggregateRoute() - { - UpstreamHeaderTemplates = new Dictionary(); - } + public List UpstreamHttpMethod => [HttpMethods.Get]; + public Dictionary UpstreamHeaderTemplates { get; set; } + public int Priority { get; set; } = 1; } } From 7b2dd43b7250dcffc99c92604c0fa3412f491724 Mon Sep 17 00:00:00 2001 From: Raman Maksimchuk Date: Sat, 6 Apr 2024 14:20:19 +0300 Subject: [PATCH 29/49] FileAggregateRoute --- src/Ocelot/Configuration/File/FileAggregateRoute.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Ocelot/Configuration/File/FileAggregateRoute.cs b/src/Ocelot/Configuration/File/FileAggregateRoute.cs index 8a9aa4389..69eb7d0a6 100644 --- a/src/Ocelot/Configuration/File/FileAggregateRoute.cs +++ b/src/Ocelot/Configuration/File/FileAggregateRoute.cs @@ -14,6 +14,13 @@ public class FileAggregateRoute : IRoute // Only supports GET..are you crazy!! POST, PUT WOULD BE CRAZY!! :) public List UpstreamHttpMethod => [HttpMethods.Get]; public Dictionary UpstreamHeaderTemplates { get; set; } - public int Priority { get; set; } = 1; + public int Priority { get; set; } = 1; + + public FileAggregateRoute() + { + RouteKeys = new(); + RouteKeysConfig = new(); + UpstreamHeaderTemplates = new(); + } } } From 0bf166e48f41d283dd76924bb79a84d6fce19d01 Mon Sep 17 00:00:00 2001 From: Raman Maksimchuk Date: Sat, 6 Apr 2024 14:28:21 +0300 Subject: [PATCH 30/49] FileRoute --- src/Ocelot/Configuration/File/FileRoute.cs | 26 +++++++++++----------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Ocelot/Configuration/File/FileRoute.cs b/src/Ocelot/Configuration/File/FileRoute.cs index 5ac734899..5f61e340f 100644 --- a/src/Ocelot/Configuration/File/FileRoute.cs +++ b/src/Ocelot/Configuration/File/FileRoute.cs @@ -19,22 +19,22 @@ public FileRoute() QoSOptions = new FileQoSOptions(); RateLimitOptions = new FileRateLimitRule(); RouteClaimsRequirement = new Dictionary(); - SecurityOptions = new FileSecurityOptions(); - UpstreamHeaderTemplates = new Dictionary(); + SecurityOptions = new FileSecurityOptions(); + UpstreamHeaderTemplates = new Dictionary(); UpstreamHeaderTransform = new Dictionary(); UpstreamHttpMethod = new List(); - } - + } + public FileRoute(FileRoute from) { DeepCopy(from, this); } - public Dictionary AddClaimsToRequest { get; set; } + public Dictionary AddClaimsToRequest { get; set; } public Dictionary AddHeadersToRequest { get; set; } - public Dictionary AddQueriesToRequest { get; set; } + public Dictionary AddQueriesToRequest { get; set; } public FileAuthenticationOptions AuthenticationOptions { get; set; } - public Dictionary ChangeDownstreamPathTemplate { get; set; } + public Dictionary ChangeDownstreamPathTemplate { get; set; } public bool DangerousAcceptAnyServerCertificateValidator { get; set; } public List DelegatingHandlers { get; set; } public Dictionary DownstreamHeaderTransform { get; set; } @@ -43,7 +43,7 @@ public FileRoute(FileRoute from) public string DownstreamHttpVersion { get; set; } public string DownstreamPathTemplate { get; set; } public string DownstreamScheme { get; set; } - public FileCacheOptions FileCacheOptions { get; set; } + public FileCacheOptions FileCacheOptions { get; set; } public FileHttpHandlerOptions HttpHandlerOptions { get; set; } public string Key { get; set; } public FileLoadBalancerOptions LoadBalancerOptions { get; set; } @@ -52,16 +52,16 @@ public FileRoute(FileRoute from) public FileRateLimitRule RateLimitOptions { get; set; } public string RequestIdKey { get; set; } public Dictionary RouteClaimsRequirement { get; set; } - public bool RouteIsCaseSensitive { get; set; } + public bool RouteIsCaseSensitive { get; set; } public FileSecurityOptions SecurityOptions { get; set; } - public string ServiceName { get; set; } - public string ServiceNamespace { get; set; } + public string ServiceName { get; set; } + public string ServiceNamespace { get; set; } public int Timeout { get; set; } - public Dictionary UpstreamHeaderTemplates { get; set; } public Dictionary UpstreamHeaderTransform { get; set; } - public string UpstreamHost { get; set; } + public string UpstreamHost { get; set; } public List UpstreamHttpMethod { get; set; } public string UpstreamPathTemplate { get; set; } + public Dictionary UpstreamHeaderTemplates { get; set; } /// /// Clones this object by making a deep copy. From a9f3f0eec4fb6960b5bbcd57f3c4544c4eecef0e Mon Sep 17 00:00:00 2001 From: Raman Maksimchuk Date: Sat, 6 Apr 2024 14:40:15 +0300 Subject: [PATCH 31/49] Route, IRoute --- src/Ocelot/Configuration/File/IRoute.cs | 6 ++---- src/Ocelot/Configuration/Route.cs | 14 +++++++------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/Ocelot/Configuration/File/IRoute.cs b/src/Ocelot/Configuration/File/IRoute.cs index 9caa6ae42..6296673bf 100644 --- a/src/Ocelot/Configuration/File/IRoute.cs +++ b/src/Ocelot/Configuration/File/IRoute.cs @@ -1,11 +1,9 @@ -using System.Collections.Generic; - -namespace Ocelot.Configuration.File; +namespace Ocelot.Configuration.File; public interface IRoute { + Dictionary UpstreamHeaderTemplates { get; set; } string UpstreamPathTemplate { get; set; } bool RouteIsCaseSensitive { get; set; } int Priority { get; set; } - Dictionary UpstreamHeaderTemplates { get; set; } } diff --git a/src/Ocelot/Configuration/Route.cs b/src/Ocelot/Configuration/Route.cs index be7e9b8fd..42f272b97 100644 --- a/src/Ocelot/Configuration/Route.cs +++ b/src/Ocelot/Configuration/Route.cs @@ -22,12 +22,12 @@ public Route(List downstreamRoute, UpstreamHeaderTemplates = upstreamHeaderTemplates; } - public UpstreamPathTemplate UpstreamTemplatePattern { get; private set; } - public List UpstreamHttpMethod { get; private set; } - public string UpstreamHost { get; private set; } - public List DownstreamRoute { get; private set; } - public List DownstreamRouteConfig { get; private set; } - public string Aggregator { get; private set; } - public Dictionary UpstreamHeaderTemplates { get; private set; } + public Dictionary UpstreamHeaderTemplates { get; } + public UpstreamPathTemplate UpstreamTemplatePattern { get; } + public List UpstreamHttpMethod { get; } + public string UpstreamHost { get; } + public List DownstreamRoute { get; } + public List DownstreamRouteConfig { get; } + public string Aggregator { get; } } } From 790d590f6543c2ed7630112b9af31cc6c8c1c2d9 Mon Sep 17 00:00:00 2001 From: Raman Maksimchuk Date: Sat, 6 Apr 2024 14:51:36 +0300 Subject: [PATCH 32/49] FileConfigurationFluentValidator --- .../Validator/FileConfigurationFluentValidator.cs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/Ocelot/Configuration/Validator/FileConfigurationFluentValidator.cs b/src/Ocelot/Configuration/Validator/FileConfigurationFluentValidator.cs index 1c1f2f104..e6e9d0b52 100644 --- a/src/Ocelot/Configuration/Validator/FileConfigurationFluentValidator.cs +++ b/src/Ocelot/Configuration/Validator/FileConfigurationFluentValidator.cs @@ -129,7 +129,7 @@ private static bool IsNotDuplicateIn(FileRoute route, var matchingRoutes = routes .Where(r => r.UpstreamPathTemplate == route.UpstreamPathTemplate && r.UpstreamHost == route.UpstreamHost - && IsUpstreamHeaderTemplatesTheSame(r.UpstreamHeaderTemplates, route.UpstreamHeaderTemplates)) + && AreTheSame(r.UpstreamHeaderTemplates, route.UpstreamHeaderTemplates)) .ToList(); if (matchingRoutes.Count == 1) @@ -153,12 +153,9 @@ private static bool IsNotDuplicateIn(FileRoute route, return true; } - private static bool IsUpstreamHeaderTemplatesTheSame(Dictionary upstreamHeaderTemplates, - Dictionary otherHeaderTemplates) - { - return upstreamHeaderTemplates.Count == otherHeaderTemplates.Count && - upstreamHeaderTemplates.All(x => otherHeaderTemplates.ContainsKey(x.Key) && otherHeaderTemplates[x.Key] == x.Value); - } + private static bool AreTheSame(Dictionary upstreamHeaderTemplates, Dictionary otherHeaderTemplates) + => upstreamHeaderTemplates.Count == otherHeaderTemplates.Count && + upstreamHeaderTemplates.All(x => otherHeaderTemplates.ContainsKey(x.Key) && otherHeaderTemplates[x.Key] == x.Value); private static bool IsNotDuplicateIn(FileRoute route, IEnumerable aggregateRoutes) From 38cd00c835f8aee4bf033e0435f407e226efdabe Mon Sep 17 00:00:00 2001 From: Raman Maksimchuk Date: Sat, 6 Apr 2024 15:28:21 +0300 Subject: [PATCH 33/49] OcelotBuilder --- src/Ocelot/DependencyInjection/Features.cs | 18 ++++++++++++++++++ .../DependencyInjection/OcelotBuilder.cs | 7 +++---- 2 files changed, 21 insertions(+), 4 deletions(-) create mode 100644 src/Ocelot/DependencyInjection/Features.cs diff --git a/src/Ocelot/DependencyInjection/Features.cs b/src/Ocelot/DependencyInjection/Features.cs new file mode 100644 index 000000000..b1abdd9b5 --- /dev/null +++ b/src/Ocelot/DependencyInjection/Features.cs @@ -0,0 +1,18 @@ +using Microsoft.Extensions.DependencyInjection; +using Ocelot.Configuration.Creator; +using Ocelot.DownstreamRouteFinder.HeaderMatcher; + +namespace Ocelot.DependencyInjection; + +public static class Features +{ + /// + /// Ocelot feature: Routing based on request header. + /// + /// The services collection to add the feature to. + /// The same object. + public static IServiceCollection AddHeaderRouting(this IServiceCollection services) => services + .AddSingleton() + .AddSingleton() + .AddSingleton(); +} diff --git a/src/Ocelot/DependencyInjection/OcelotBuilder.cs b/src/Ocelot/DependencyInjection/OcelotBuilder.cs index f5b3a44e3..7874fbf13 100644 --- a/src/Ocelot/DependencyInjection/OcelotBuilder.cs +++ b/src/Ocelot/DependencyInjection/OcelotBuilder.cs @@ -15,7 +15,6 @@ using Ocelot.Configuration.Setter; using Ocelot.Configuration.Validator; using Ocelot.DownstreamRouteFinder.Finder; -using Ocelot.DownstreamRouteFinder.HeaderMatcher; using Ocelot.DownstreamRouteFinder.UrlMatcher; using Ocelot.DownstreamUrlCreator; using Ocelot.Headers; @@ -76,7 +75,6 @@ public OcelotBuilder(IServiceCollection services, IConfiguration configurationRo Services.TryAddSingleton(); Services.TryAddSingleton(); Services.TryAddSingleton(); - Services.TryAddSingleton(); Services.TryAddSingleton(); Services.TryAddSingleton(); Services.TryAddSingleton(); @@ -104,9 +102,7 @@ public OcelotBuilder(IServiceCollection services, IConfiguration configurationRo Services.TryAddSingleton(); Services.TryAddSingleton(); Services.TryAddSingleton(); - Services.TryAddSingleton(); Services.TryAddSingleton(); - Services.TryAddSingleton(); Services.TryAddSingleton(); Services.AddSingleton(); Services.AddSingleton(); @@ -148,6 +144,9 @@ public OcelotBuilder(IServiceCollection services, IConfiguration configurationRo Services.TryAddSingleton(); Services.TryAddSingleton(); + // Features + Services.AddHeaderRouting(); + // Add ASP.NET services var assembly = typeof(FileConfigurationController).GetTypeInfo().Assembly; MvcCoreBuilder = (customBuilder ?? AddDefaultAspNetServices) From 0667a930bc82d9a635d805455f8544bb68cf8a17 Mon Sep 17 00:00:00 2001 From: Raman Maksimchuk Date: Sat, 6 Apr 2024 16:09:53 +0300 Subject: [PATCH 34/49] DownstreamRouteCreator --- .../Creator/UpstreamHeaderTemplatePatternCreator.cs | 6 +++--- .../DownstreamRouteFinder/Finder/DownstreamRouteCreator.cs | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Ocelot/Configuration/Creator/UpstreamHeaderTemplatePatternCreator.cs b/src/Ocelot/Configuration/Creator/UpstreamHeaderTemplatePatternCreator.cs index 3afb3501a..5b2e041d3 100644 --- a/src/Ocelot/Configuration/Creator/UpstreamHeaderTemplatePatternCreator.cs +++ b/src/Ocelot/Configuration/Creator/UpstreamHeaderTemplatePatternCreator.cs @@ -19,7 +19,7 @@ public partial class UpstreamHeaderTemplatePatternCreator : IUpstreamHeaderTempl public Dictionary Create(IRoute route) { - var resultHeaderTemplates = new Dictionary(); + var result = new Dictionary(); foreach (var headerTemplate in route.UpstreamHeaderTemplates) { @@ -41,9 +41,9 @@ public Dictionary Create(IRoute route) ? $"^{headerTemplateValue}$" : $"^(?i){headerTemplateValue}$"; // ignore case - resultHeaderTemplates.Add(headerTemplate.Key, new(template, headerTemplate.Value)); + result.Add(headerTemplate.Key, new(template, headerTemplate.Value)); } - return resultHeaderTemplates; + return result; } } diff --git a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteCreator.cs b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteCreator.cs index 4e35a676e..9346d8bda 100644 --- a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteCreator.cs +++ b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteCreator.cs @@ -4,7 +4,6 @@ using Ocelot.DownstreamRouteFinder.UrlMatcher; using Ocelot.LoadBalancer.LoadBalancers; using Ocelot.Responses; -using Ocelot.Values; namespace Ocelot.DownstreamRouteFinder.Finder { From 079f9d266ba24a317871a2e35b90bbc376a33284 Mon Sep 17 00:00:00 2001 From: Raman Maksimchuk Date: Sat, 6 Apr 2024 16:39:05 +0300 Subject: [PATCH 35/49] DownstreamRouteFinder --- .../Finder/DownstreamRouteFinder.cs | 35 +++++++++---------- .../Finder/IDownstreamRouteProvider.cs | 11 +++--- 2 files changed, 22 insertions(+), 24 deletions(-) diff --git a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs index dbb10a368..54f6b983d 100644 --- a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs +++ b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs @@ -1,7 +1,6 @@ using Ocelot.Configuration; using Ocelot.DownstreamRouteFinder.HeaderMatcher; using Ocelot.DownstreamRouteFinder.UrlMatcher; -using Ocelot.Middleware; using Ocelot.Responses; namespace Ocelot.DownstreamRouteFinder.Finder @@ -9,21 +8,20 @@ namespace Ocelot.DownstreamRouteFinder.Finder public class DownstreamRouteFinder : IDownstreamRouteProvider { private readonly IUrlPathToUrlTemplateMatcher _urlMatcher; - private readonly IPlaceholderNameAndValueFinder _placeholderNameAndValueFinder; - private readonly IHeadersToHeaderTemplatesMatcher _headersMatcher; - private readonly IHeaderPlaceholderNameAndValueFinder _headerPlaceholderNameAndValueFinder; + private readonly IPlaceholderNameAndValueFinder _pathPlaceholderFinder; + private readonly IHeadersToHeaderTemplatesMatcher _headerMatcher; + private readonly IHeaderPlaceholderNameAndValueFinder _headerPlaceholderFinder; public DownstreamRouteFinder( IUrlPathToUrlTemplateMatcher urlMatcher, - IPlaceholderNameAndValueFinder urlPathPlaceholderNameAndValueFinder, - IHeadersToHeaderTemplatesMatcher headersMatcher, - IHeaderPlaceholderNameAndValueFinder headerPlaceholderNameAndValueFinder - ) + IPlaceholderNameAndValueFinder pathPlaceholderFinder, + IHeadersToHeaderTemplatesMatcher headerMatcher, + IHeaderPlaceholderNameAndValueFinder headerPlaceholderFinder) { _urlMatcher = urlMatcher; - _placeholderNameAndValueFinder = urlPathPlaceholderNameAndValueFinder; - _headersMatcher = headersMatcher; - _headerPlaceholderNameAndValueFinder = headerPlaceholderNameAndValueFinder; + _pathPlaceholderFinder = pathPlaceholderFinder; + _headerMatcher = headerMatcher; + _headerPlaceholderFinder = headerPlaceholderFinder; } public Response Get(string upstreamUrlPath, string upstreamQueryString, string httpMethod, @@ -38,7 +36,7 @@ public Response Get(string upstreamUrlPath, string upstre foreach (var route in applicableRoutes) { var urlMatch = _urlMatcher.Match(upstreamUrlPath, upstreamQueryString, route.UpstreamTemplatePattern); - var headersMatch = _headersMatcher.Match(upstreamHeaders, route.UpstreamHeaderTemplates); + var headersMatch = _headerMatcher.Match(upstreamHeaders, route.UpstreamHeaderTemplates); if (urlMatch.Data.Match && headersMatch) { @@ -46,12 +44,11 @@ public Response Get(string upstreamUrlPath, string upstre } } - if (downstreamRoutes.Any()) + if (downstreamRoutes.Count != 0) { 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); + var nullOption = downstreamRoutes.FirstOrDefault(x => string.IsNullOrEmpty(x.Route.UpstreamHost)); + return new OkResponse(notNullOption ?? nullOption); } return new ErrorResponse(new UnableToFindDownstreamRouteError(upstreamUrlPath, httpMethod)); @@ -65,8 +62,10 @@ private static bool RouteIsApplicableToThisRequest(Route route, string httpMetho private DownstreamRouteHolder GetPlaceholderNamesAndValues(string path, string query, Route route, Dictionary upstreamHeaders) { - var templatePlaceholderNameAndValues = _placeholderNameAndValueFinder.Find(path, query, route.UpstreamTemplatePattern.OriginalValue).Data; - var headerPlaceholders = _headerPlaceholderNameAndValueFinder.Find(upstreamHeaders, route.UpstreamHeaderTemplates); + var templatePlaceholderNameAndValues = _pathPlaceholderFinder + .Find(path, query, route.UpstreamTemplatePattern.OriginalValue) + .Data; + var headerPlaceholders = _headerPlaceholderFinder.Find(upstreamHeaders, route.UpstreamHeaderTemplates); templatePlaceholderNameAndValues.AddRange(headerPlaceholders); return new DownstreamRouteHolder(templatePlaceholderNameAndValues, route); diff --git a/src/Ocelot/DownstreamRouteFinder/Finder/IDownstreamRouteProvider.cs b/src/Ocelot/DownstreamRouteFinder/Finder/IDownstreamRouteProvider.cs index b692be7c1..81ba875a4 100644 --- a/src/Ocelot/DownstreamRouteFinder/Finder/IDownstreamRouteProvider.cs +++ b/src/Ocelot/DownstreamRouteFinder/Finder/IDownstreamRouteProvider.cs @@ -1,11 +1,10 @@ using Ocelot.Configuration; using Ocelot.Responses; -using System.Collections.Generic; -namespace Ocelot.DownstreamRouteFinder.Finder +namespace Ocelot.DownstreamRouteFinder.Finder; + +public interface IDownstreamRouteProvider { - public interface IDownstreamRouteProvider - { - Response Get(string upstreamUrlPath, string upstreamQueryString, string upstreamHttpMethod, IInternalConfiguration configuration, string upstreamHost, Dictionary upstreamHeaders); - } + Response Get(string upstreamUrlPath, string upstreamQueryString, string upstreamHttpMethod, + IInternalConfiguration configuration, string upstreamHost, Dictionary upstreamHeaders); } From 32d8310594ad27e71f96a06b0adfeeef938dce2b Mon Sep 17 00:00:00 2001 From: Raman Maksimchuk Date: Sat, 6 Apr 2024 16:59:22 +0300 Subject: [PATCH 36/49] HeaderMatcher --- .../IUpstreamHeaderTemplatePatternCreator.cs | 2 +- .../HeaderPlaceholderNameAndValueFinder.cs | 16 +++++++--------- .../HeadersToHeaderTemplatesMatcher.cs | 12 ++++-------- .../IHeaderPlaceholderNameAndValueFinder.cs | 4 +++- .../IHeadersToHeaderTemplatesMatcher.cs | 4 +++- 5 files changed, 18 insertions(+), 20 deletions(-) diff --git a/src/Ocelot/Configuration/Creator/IUpstreamHeaderTemplatePatternCreator.cs b/src/Ocelot/Configuration/Creator/IUpstreamHeaderTemplatePatternCreator.cs index 7773f27a6..7ef43d914 100644 --- a/src/Ocelot/Configuration/Creator/IUpstreamHeaderTemplatePatternCreator.cs +++ b/src/Ocelot/Configuration/Creator/IUpstreamHeaderTemplatePatternCreator.cs @@ -4,7 +4,7 @@ namespace Ocelot.Configuration.Creator; /// -/// Ocelot feature: Routing based on request header.
+/// Ocelot feature: Routing based on request header. ///
public interface IUpstreamHeaderTemplatePatternCreator { diff --git a/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinder.cs b/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinder.cs index 0794916b2..849a99445 100644 --- a/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinder.cs +++ b/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinder.cs @@ -1,8 +1,5 @@ using Ocelot.DownstreamRouteFinder.UrlMatcher; using Ocelot.Values; -using System.Collections.Generic; -using System.Linq; -using System.Text.RegularExpressions; namespace Ocelot.DownstreamRouteFinder.HeaderMatcher; @@ -10,17 +7,18 @@ public class HeaderPlaceholderNameAndValueFinder : IHeaderPlaceholderNameAndValu { public List Find(Dictionary upstreamHeaders, Dictionary templateHeaders) { - var placeholderNameAndValuesList = new List(); - + var result = new List(); foreach (var templateHeader in templateHeaders) { var upstreamHeader = upstreamHeaders[templateHeader.Key]; var matches = templateHeader.Value.Pattern.Matches(upstreamHeader); - var placeholders = matches.SelectMany(g => g.Groups as IEnumerable).Where(g => g.Name != "0") - .Select(g => new PlaceholderNameAndValue("{" + g.Name + "}", g.Value)); - placeholderNameAndValuesList.AddRange(placeholders); + var placeholders = matches + .SelectMany(g => g.Groups as IEnumerable) + .Where(g => g.Name != "0") + .Select(g => new PlaceholderNameAndValue(string.Concat('{', g.Name, '}'), g.Value)); + result.AddRange(placeholders); } - return placeholderNameAndValuesList; + return result; } } diff --git a/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/HeadersToHeaderTemplatesMatcher.cs b/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/HeadersToHeaderTemplatesMatcher.cs index 3e0226c89..b6c68cc86 100644 --- a/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/HeadersToHeaderTemplatesMatcher.cs +++ b/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/HeadersToHeaderTemplatesMatcher.cs @@ -1,15 +1,11 @@ using Ocelot.Values; -using System.Collections.Generic; -using System.Linq; namespace Ocelot.DownstreamRouteFinder.HeaderMatcher; public class HeadersToHeaderTemplatesMatcher : IHeadersToHeaderTemplatesMatcher { - public bool Match(Dictionary upstreamHeaders, Dictionary routeHeaders) - { - return routeHeaders == null || - upstreamHeaders != null && routeHeaders.All( - h => upstreamHeaders.ContainsKey(h.Key) && routeHeaders[h.Key].Pattern.IsMatch(upstreamHeaders[h.Key])); - } + public bool Match(Dictionary upstreamHeaders, Dictionary routeHeaders) => + routeHeaders == null || + upstreamHeaders != null + && routeHeaders.All(h => upstreamHeaders.ContainsKey(h.Key) && routeHeaders[h.Key].Pattern.IsMatch(upstreamHeaders[h.Key])); } diff --git a/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/IHeaderPlaceholderNameAndValueFinder.cs b/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/IHeaderPlaceholderNameAndValueFinder.cs index 91a816590..0c8dcc81f 100644 --- a/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/IHeaderPlaceholderNameAndValueFinder.cs +++ b/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/IHeaderPlaceholderNameAndValueFinder.cs @@ -1,9 +1,11 @@ using Ocelot.DownstreamRouteFinder.UrlMatcher; using Ocelot.Values; -using System.Collections.Generic; namespace Ocelot.DownstreamRouteFinder.HeaderMatcher; +/// +/// Ocelot feature: Routing based on request header. +/// public interface IHeaderPlaceholderNameAndValueFinder { List Find(Dictionary upstreamHeaders, Dictionary templateHeaders); diff --git a/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/IHeadersToHeaderTemplatesMatcher.cs b/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/IHeadersToHeaderTemplatesMatcher.cs index 157c67f87..0a781b9f5 100644 --- a/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/IHeadersToHeaderTemplatesMatcher.cs +++ b/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/IHeadersToHeaderTemplatesMatcher.cs @@ -1,8 +1,10 @@ using Ocelot.Values; -using System.Collections.Generic; namespace Ocelot.DownstreamRouteFinder.HeaderMatcher; +/// +/// Ocelot feature: Routing based on request header. +/// public interface IHeadersToHeaderTemplatesMatcher { bool Match(Dictionary upstreamHeaders, Dictionary routeHeaders); From 36cf99f83a4b57ad3938b5c7e93a87373e342bdd Mon Sep 17 00:00:00 2001 From: Raman Maksimchuk Date: Sat, 6 Apr 2024 17:10:15 +0300 Subject: [PATCH 37/49] DownstreamRouteFinderMiddleware --- .../Middleware/DownstreamRouteFinderMiddleware.cs | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs b/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs index d4fc9e433..38ab1bd36 100644 --- a/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs +++ b/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs @@ -24,28 +24,22 @@ IDownstreamRouteProviderFactory downstreamRouteFinder public async Task Invoke(HttpContext httpContext) { var upstreamUrlPath = httpContext.Request.Path.ToString(); - var upstreamQueryString = httpContext.Request.QueryString.ToString(); - - var hostHeader = httpContext.Request.Headers["Host"].ToString(); + var internalConfiguration = httpContext.Items.IInternalConfiguration(); + var hostHeader = httpContext.Request.Headers.Host.ToString(); var upstreamHost = hostHeader.Contains(':') ? hostHeader.Split(':')[0] : hostHeader; - - var upstreamHeaders = httpContext.Request.Headers.ToDictionary(h => h.Key, h => string.Join(";", h.Value)); + var upstreamHeaders = httpContext.Request.Headers + .ToDictionary(h => h.Key, h => string.Join(';', h.Value)); Logger.LogDebug(() => $"Upstream URL path is '{upstreamUrlPath}'."); - var internalConfiguration = httpContext.Items.IInternalConfiguration(); - var provider = _factory.Get(internalConfiguration); - var response = provider.Get(upstreamUrlPath, upstreamQueryString, httpContext.Request.Method, internalConfiguration, upstreamHost, upstreamHeaders); - if (response.IsError) { Logger.LogWarning(() => $"{MiddlewareName} setting pipeline errors. IDownstreamRouteFinder returned {response.Errors.ToErrorString()}"); - httpContext.Items.UpsertErrors(response.Errors); return; } @@ -54,7 +48,6 @@ public async Task Invoke(HttpContext httpContext) // why set both of these on HttpContext httpContext.Items.UpsertTemplatePlaceholderNameAndValues(response.Data.TemplatePlaceholderNameAndValues); - httpContext.Items.UpsertDownstreamRoute(response.Data); await _next.Invoke(httpContext); From 98fe814395a86b65cdf31534f618d1d7d69225d1 Mon Sep 17 00:00:00 2001 From: Raman Maksimchuk Date: Sat, 6 Apr 2024 17:25:53 +0300 Subject: [PATCH 38/49] UpstreamHeaderTemplate --- src/Ocelot/Values/UpstreamHeaderTemplate.cs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/Ocelot/Values/UpstreamHeaderTemplate.cs b/src/Ocelot/Values/UpstreamHeaderTemplate.cs index 2c76c734c..3151fbdf8 100644 --- a/src/Ocelot/Values/UpstreamHeaderTemplate.cs +++ b/src/Ocelot/Values/UpstreamHeaderTemplate.cs @@ -1,21 +1,19 @@ -using System.Text.RegularExpressions; - -namespace Ocelot.Values; +namespace Ocelot.Values; +/// +/// Upstream template properties of headers and their regular expression. +/// +/// Ocelot feature: Routing based on request header. public class UpstreamHeaderTemplate { public string Template { get; } - public string OriginalValue { get; } - public Regex Pattern { get; } public UpstreamHeaderTemplate(string template, string originalValue) { Template = template; OriginalValue = originalValue; - Pattern = template == null ? - new Regex("$^", RegexOptions.Compiled | RegexOptions.Singleline) : - new Regex(template, RegexOptions.Compiled | RegexOptions.Singleline); + Pattern = new Regex(template ?? "$^", RegexOptions.Compiled | RegexOptions.Singleline); } } From ea55272e83aa0a8517544cb421fc2f589b5a4ea3 Mon Sep 17 00:00:00 2001 From: Raman Maksimchuk Date: Sat, 6 Apr 2024 17:34:13 +0300 Subject: [PATCH 39/49] Routing folder --- .../RoutingBasedOnHeadersTests.cs | 2 +- .../{ => Routing}/RoutingTests.cs | 2 +- .../RoutingWithQueryStringTests.cs | 722 +++++++++--------- 3 files changed, 363 insertions(+), 363 deletions(-) rename test/Ocelot.AcceptanceTests/{ => Routing}/RoutingBasedOnHeadersTests.cs (99%) rename test/Ocelot.AcceptanceTests/{ => Routing}/RoutingTests.cs (97%) rename test/Ocelot.AcceptanceTests/{ => Routing}/RoutingWithQueryStringTests.cs (97%) diff --git a/test/Ocelot.AcceptanceTests/RoutingBasedOnHeadersTests.cs b/test/Ocelot.AcceptanceTests/Routing/RoutingBasedOnHeadersTests.cs similarity index 99% rename from test/Ocelot.AcceptanceTests/RoutingBasedOnHeadersTests.cs rename to test/Ocelot.AcceptanceTests/Routing/RoutingBasedOnHeadersTests.cs index 690bada91..52a926c9a 100644 --- a/test/Ocelot.AcceptanceTests/RoutingBasedOnHeadersTests.cs +++ b/test/Ocelot.AcceptanceTests/Routing/RoutingBasedOnHeadersTests.cs @@ -2,7 +2,7 @@ using Ocelot.Configuration.File; using Ocelot.Testing; -namespace Ocelot.AcceptanceTests; +namespace Ocelot.AcceptanceTests.Routing; public class RoutingBasedOnHeadersTests : IDisposable { diff --git a/test/Ocelot.AcceptanceTests/RoutingTests.cs b/test/Ocelot.AcceptanceTests/Routing/RoutingTests.cs similarity index 97% rename from test/Ocelot.AcceptanceTests/RoutingTests.cs rename to test/Ocelot.AcceptanceTests/Routing/RoutingTests.cs index b881a831f..388e690d1 100644 --- a/test/Ocelot.AcceptanceTests/RoutingTests.cs +++ b/test/Ocelot.AcceptanceTests/Routing/RoutingTests.cs @@ -1,7 +1,7 @@ using Microsoft.AspNetCore.Http; using Ocelot.Configuration.File; -namespace Ocelot.AcceptanceTests +namespace Ocelot.AcceptanceTests.Routing { public sealed class RoutingTests : IDisposable { diff --git a/test/Ocelot.AcceptanceTests/RoutingWithQueryStringTests.cs b/test/Ocelot.AcceptanceTests/Routing/RoutingWithQueryStringTests.cs similarity index 97% rename from test/Ocelot.AcceptanceTests/RoutingWithQueryStringTests.cs rename to test/Ocelot.AcceptanceTests/Routing/RoutingWithQueryStringTests.cs index 5c34167ac..ad4a16ae5 100644 --- a/test/Ocelot.AcceptanceTests/RoutingWithQueryStringTests.cs +++ b/test/Ocelot.AcceptanceTests/Routing/RoutingWithQueryStringTests.cs @@ -1,379 +1,379 @@ using Microsoft.AspNetCore.Http; using Ocelot.Configuration.File; -namespace Ocelot.AcceptanceTests -{ - public class RoutingWithQueryStringTests : IDisposable - { - private readonly Steps _steps; - private readonly ServiceHandler _serviceHandler; - - public RoutingWithQueryStringTests() - { - _serviceHandler = new ServiceHandler(); - _steps = new Steps(); - } - - [Fact] - public void Should_return_response_200_with_query_string_template() - { - var subscriptionId = Guid.NewGuid().ToString(); - var unitId = Guid.NewGuid().ToString(); - var port = PortFinder.GetRandomPort(); - - var configuration = new FileConfiguration - { - Routes = new List - { - new() - { - DownstreamPathTemplate = "/api/subscriptions/{subscriptionId}/updates?unitId={unitId}", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new() - { - Host = "localhost", - Port = port, - }, - }, - UpstreamPathTemplate = "/api/units/{subscriptionId}/{unitId}/updates", - UpstreamHttpMethod = new List { "Get" }, - }, - }, - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", $"/api/subscriptions/{subscriptionId}/updates", $"?unitId={unitId}", "Hello from Laura")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway($"/api/units/{subscriptionId}/{unitId}/updates")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .BDDfy(); - } +namespace Ocelot.AcceptanceTests.Routing +{ + public class RoutingWithQueryStringTests : IDisposable + { + private readonly Steps _steps; + private readonly ServiceHandler _serviceHandler; + + public RoutingWithQueryStringTests() + { + _serviceHandler = new ServiceHandler(); + _steps = new Steps(); + } + + [Fact] + public void Should_return_response_200_with_query_string_template() + { + var subscriptionId = Guid.NewGuid().ToString(); + var unitId = Guid.NewGuid().ToString(); + var port = PortFinder.GetRandomPort(); + + var configuration = new FileConfiguration + { + Routes = new List + { + new() + { + DownstreamPathTemplate = "/api/subscriptions/{subscriptionId}/updates?unitId={unitId}", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new() + { + Host = "localhost", + Port = port, + }, + }, + UpstreamPathTemplate = "/api/units/{subscriptionId}/{unitId}/updates", + UpstreamHttpMethod = new List { "Get" }, + }, + }, + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", $"/api/subscriptions/{subscriptionId}/updates", $"?unitId={unitId}", "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway($"/api/units/{subscriptionId}/{unitId}/updates")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } [Theory(DisplayName = "1182: " + nameof(Should_return_200_with_query_string_template_different_keys))] - [InlineData("")] - [InlineData("&x=xxx")] - public void Should_return_200_with_query_string_template_different_keys(string additionalParams) + [InlineData("")] + [InlineData("&x=xxx")] + public void Should_return_200_with_query_string_template_different_keys(string additionalParams) { - var subscriptionId = Guid.NewGuid().ToString(); - var unitId = Guid.NewGuid().ToString(); - var port = PortFinder.GetRandomPort(); - - var configuration = new FileConfiguration - { - Routes = new List - { - new() - { - DownstreamPathTemplate = "/api/subscriptions/{subscriptionId}/updates?unitId={unit}", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new() - { - Host = "localhost", - Port = port, - }, - }, - UpstreamPathTemplate = "/api/units/{subscriptionId}/updates?unit={unit}", - UpstreamHttpMethod = new List { "Get" }, - }, - }, - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", $"/api/subscriptions/{subscriptionId}/updates", $"?unitId={unitId}{additionalParams}", "Hello from Laura")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway($"/api/units/{subscriptionId}/updates?unit={unitId}{additionalParams}")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .BDDfy(); - } - + var subscriptionId = Guid.NewGuid().ToString(); + var unitId = Guid.NewGuid().ToString(); + var port = PortFinder.GetRandomPort(); + + var configuration = new FileConfiguration + { + Routes = new List + { + new() + { + DownstreamPathTemplate = "/api/subscriptions/{subscriptionId}/updates?unitId={unit}", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new() + { + Host = "localhost", + Port = port, + }, + }, + UpstreamPathTemplate = "/api/units/{subscriptionId}/updates?unit={unit}", + UpstreamHttpMethod = new List { "Get" }, + }, + }, + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", $"/api/subscriptions/{subscriptionId}/updates", $"?unitId={unitId}{additionalParams}", "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway($"/api/units/{subscriptionId}/updates?unit={unitId}{additionalParams}")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } + [Theory(DisplayName = "1174: " + nameof(Should_return_200_and_forward_query_parameters_without_duplicates))] - [InlineData("projectNumber=45&startDate=2019-12-12&endDate=2019-12-12", "endDate=2019-12-12&projectNumber=45&startDate=2019-12-12")] - [InlineData("$filter=ProjectNumber eq 45 and DateOfSale ge 2020-03-01T00:00:00z and DateOfSale le 2020-03-15T00:00:00z", "$filter=ProjectNumber%20eq%2045%20and%20DateOfSale%20ge%202020-03-01T00:00:00z%20and%20DateOfSale%20le%202020-03-15T00:00:00z")] - public void Should_return_200_and_forward_query_parameters_without_duplicates(string everythingelse, string expectedOrdered) + [InlineData("projectNumber=45&startDate=2019-12-12&endDate=2019-12-12", "endDate=2019-12-12&projectNumber=45&startDate=2019-12-12")] + [InlineData("$filter=ProjectNumber eq 45 and DateOfSale ge 2020-03-01T00:00:00z and DateOfSale le 2020-03-15T00:00:00z", "$filter=ProjectNumber%20eq%2045%20and%20DateOfSale%20ge%202020-03-01T00:00:00z%20and%20DateOfSale%20le%202020-03-15T00:00:00z")] + public void Should_return_200_and_forward_query_parameters_without_duplicates(string everythingelse, string expectedOrdered) + { + var port = PortFinder.GetRandomPort(); + var configuration = new FileConfiguration + { + Routes = new List + { + new() + { + DownstreamPathTemplate = "/api/contracts?{everythingelse}", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new() { Host = "localhost", Port = port }, + }, + UpstreamPathTemplate = "/contracts?{everythingelse}", + UpstreamHttpMethod = new() { "Get" }, + }, + }, + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", $"/api/contracts", $"?{expectedOrdered}", "Hello from @sunilk3")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway($"/contracts?{everythingelse}")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from @sunilk3")) + .BDDfy(); + } + + [Fact] + public void Should_return_response_200_with_odata_query_string() + { + var subscriptionId = Guid.NewGuid().ToString(); + var unitId = Guid.NewGuid().ToString(); + var port = PortFinder.GetRandomPort(); + + var configuration = new FileConfiguration + { + Routes = new List + { + new() + { + DownstreamPathTemplate = "/{everything}", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new() + { + Host = "localhost", + Port = port, + }, + }, + UpstreamPathTemplate = "/{everything}", + UpstreamHttpMethod = new List { "Get" }, + }, + }, + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/odata/customers", "?$filter=Name%20eq%20'Sam'", "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/odata/customers?$filter=Name eq 'Sam' ")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } + + [Fact] + public void Should_return_response_200_with_query_string_upstream_template() + { + var subscriptionId = Guid.NewGuid().ToString(); + var unitId = Guid.NewGuid().ToString(); + var port = PortFinder.GetRandomPort(); + + var configuration = new FileConfiguration + { + Routes = new List + { + new() + { + DownstreamPathTemplate = "/api/units/{subscriptionId}/{unitId}/updates", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new() + { + Host = "localhost", + Port = port, + }, + }, + UpstreamPathTemplate = "/api/subscriptions/{subscriptionId}/updates?unitId={unitId}", + UpstreamHttpMethod = new List { "Get" }, + }, + }, + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", $"/api/units/{subscriptionId}/{unitId}/updates", string.Empty, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway($"/api/subscriptions/{subscriptionId}/updates?unitId={unitId}")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } + + [Fact] + public void Should_return_response_404_with_query_string_upstream_template_no_query_string() + { + var subscriptionId = Guid.NewGuid().ToString(); + var unitId = Guid.NewGuid().ToString(); + var port = PortFinder.GetRandomPort(); + + var configuration = new FileConfiguration + { + Routes = new List + { + new() + { + DownstreamPathTemplate = "/api/units/{subscriptionId}/{unitId}/updates", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new() + { + Host = "localhost", + Port = port, + }, + }, + UpstreamPathTemplate = "/api/subscriptions/{subscriptionId}/updates?unitId={unitId}", + UpstreamHttpMethod = new List { "Get" }, + }, + }, + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", $"/api/units/{subscriptionId}/{unitId}/updates", string.Empty, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway($"/api/subscriptions/{subscriptionId}/updates")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.NotFound)) + .BDDfy(); + } + + [Fact] + public void Should_return_response_404_with_query_string_upstream_template_different_query_string() { - var port = PortFinder.GetRandomPort(); - var configuration = new FileConfiguration - { - Routes = new List - { - new() - { - DownstreamPathTemplate = "/api/contracts?{everythingelse}", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new() { Host = "localhost", Port = port }, - }, - UpstreamPathTemplate = "/contracts?{everythingelse}", - UpstreamHttpMethod = new() { "Get" }, - }, - }, - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", $"/api/contracts", $"?{expectedOrdered}", "Hello from @sunilk3")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway($"/contracts?{everythingelse}")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from @sunilk3")) - .BDDfy(); - } + var subscriptionId = Guid.NewGuid().ToString(); + var unitId = Guid.NewGuid().ToString(); + var port = PortFinder.GetRandomPort(); + + var configuration = new FileConfiguration + { + Routes = new List + { + new() + { + DownstreamPathTemplate = "/api/units/{subscriptionId}/{unitId}/updates", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new() + { + Host = "localhost", + Port = port, + }, + }, + UpstreamPathTemplate = "/api/subscriptions/{subscriptionId}/updates?unitId={unitId}", + UpstreamHttpMethod = new List { "Get" }, + }, + }, + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", $"/api/units/{subscriptionId}/{unitId}/updates", string.Empty, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway($"/api/subscriptions/{subscriptionId}/updates?test=1")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.NotFound)) + .BDDfy(); + } + + [Fact] + public void Should_return_response_200_with_query_string_upstream_template_multiple_params() + { + var subscriptionId = Guid.NewGuid().ToString(); + var unitId = Guid.NewGuid().ToString(); + var port = PortFinder.GetRandomPort(); + + var configuration = new FileConfiguration + { + Routes = new List + { + new() + { + DownstreamPathTemplate = "/api/units/{subscriptionId}/{unitId}/updates", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new() + { + Host = "localhost", + Port = port, + }, + }, + UpstreamPathTemplate = "/api/subscriptions/{subscriptionId}/updates?unitId={unitId}", + UpstreamHttpMethod = new List { "Get" }, + }, + }, + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", $"/api/units/{subscriptionId}/{unitId}/updates", "?productId=1", "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway($"/api/subscriptions/{subscriptionId}/updates?unitId={unitId}&productId=1")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } - [Fact] - public void Should_return_response_200_with_odata_query_string() - { - var subscriptionId = Guid.NewGuid().ToString(); - var unitId = Guid.NewGuid().ToString(); - var port = PortFinder.GetRandomPort(); - - var configuration = new FileConfiguration - { - Routes = new List - { - new() - { - DownstreamPathTemplate = "/{everything}", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new() - { - Host = "localhost", - Port = port, - }, - }, - UpstreamPathTemplate = "/{everything}", - UpstreamHttpMethod = new List { "Get" }, - }, - }, - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/odata/customers", "?$filter=Name%20eq%20'Sam'", "Hello from Laura")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/odata/customers?$filter=Name eq 'Sam' ")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .BDDfy(); - } - - [Fact] - public void Should_return_response_200_with_query_string_upstream_template() - { - var subscriptionId = Guid.NewGuid().ToString(); - var unitId = Guid.NewGuid().ToString(); - var port = PortFinder.GetRandomPort(); - - var configuration = new FileConfiguration - { - Routes = new List - { - new() - { - DownstreamPathTemplate = "/api/units/{subscriptionId}/{unitId}/updates", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new() - { - Host = "localhost", - Port = port, - }, - }, - UpstreamPathTemplate = "/api/subscriptions/{subscriptionId}/updates?unitId={unitId}", - UpstreamHttpMethod = new List { "Get" }, - }, - }, - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", $"/api/units/{subscriptionId}/{unitId}/updates", string.Empty, "Hello from Laura")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway($"/api/subscriptions/{subscriptionId}/updates?unitId={unitId}")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .BDDfy(); - } - - [Fact] - public void Should_return_response_404_with_query_string_upstream_template_no_query_string() - { - var subscriptionId = Guid.NewGuid().ToString(); - var unitId = Guid.NewGuid().ToString(); - var port = PortFinder.GetRandomPort(); - - var configuration = new FileConfiguration - { - Routes = new List - { - new() - { - DownstreamPathTemplate = "/api/units/{subscriptionId}/{unitId}/updates", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new() - { - Host = "localhost", - Port = port, - }, - }, - UpstreamPathTemplate = "/api/subscriptions/{subscriptionId}/updates?unitId={unitId}", - UpstreamHttpMethod = new List { "Get" }, - }, - }, - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", $"/api/units/{subscriptionId}/{unitId}/updates", string.Empty, "Hello from Laura")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway($"/api/subscriptions/{subscriptionId}/updates")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.NotFound)) - .BDDfy(); - } - - [Fact] - public void Should_return_response_404_with_query_string_upstream_template_different_query_string() - { - var subscriptionId = Guid.NewGuid().ToString(); - var unitId = Guid.NewGuid().ToString(); - var port = PortFinder.GetRandomPort(); - - var configuration = new FileConfiguration - { - Routes = new List - { - new() - { - DownstreamPathTemplate = "/api/units/{subscriptionId}/{unitId}/updates", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new() - { - Host = "localhost", - Port = port, - }, - }, - UpstreamPathTemplate = "/api/subscriptions/{subscriptionId}/updates?unitId={unitId}", - UpstreamHttpMethod = new List { "Get" }, - }, - }, - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", $"/api/units/{subscriptionId}/{unitId}/updates", string.Empty, "Hello from Laura")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway($"/api/subscriptions/{subscriptionId}/updates?test=1")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.NotFound)) - .BDDfy(); - } - - [Fact] - public void Should_return_response_200_with_query_string_upstream_template_multiple_params() - { - var subscriptionId = Guid.NewGuid().ToString(); - var unitId = Guid.NewGuid().ToString(); - var port = PortFinder.GetRandomPort(); - - var configuration = new FileConfiguration - { - Routes = new List - { - new() - { - DownstreamPathTemplate = "/api/units/{subscriptionId}/{unitId}/updates", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new() - { - Host = "localhost", - Port = port, - }, - }, - UpstreamPathTemplate = "/api/subscriptions/{subscriptionId}/updates?unitId={unitId}", - UpstreamHttpMethod = new List { "Get" }, - }, - }, - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", $"/api/units/{subscriptionId}/{unitId}/updates", "?productId=1", "Hello from Laura")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway($"/api/subscriptions/{subscriptionId}/updates?unitId={unitId}&productId=1")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .BDDfy(); - } - /// /// To reproduce 1288: query string should contain the placeholder name and value. /// - [Fact(DisplayName = "1288: " + nameof(Should_copy_query_string_to_downstream_path))] - public void Should_copy_query_string_to_downstream_path() + [Fact(DisplayName = "1288: " + nameof(Should_copy_query_string_to_downstream_path))] + public void Should_copy_query_string_to_downstream_path() { - var idName = "id"; + var idName = "id"; var idValue = "3"; var queryName = idName + "1"; - var queryValue = "2" + idValue + "12"; - var port = PortFinder.GetRandomPort(); - - var configuration = new FileConfiguration - { - Routes = new List - { - new FileRoute - { - DownstreamPathTemplate = $"/cpx/t1/{{{idName}}}", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new() { Host = "localhost", Port = port }, - }, - UpstreamPathTemplate = $"/safe/{{{idName}}}", - UpstreamHttpMethod = new List { "Get" }, - }, - }, - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", $"/cpx/t1/{idValue}", $"?{queryName}={queryValue}", "Hello from Laura")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway($"/safe/{idValue}?{queryName}={queryValue}")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .BDDfy(); + var queryValue = "2" + idValue + "12"; + var port = PortFinder.GetRandomPort(); + + var configuration = new FileConfiguration + { + Routes = new List + { + new FileRoute + { + DownstreamPathTemplate = $"/cpx/t1/{{{idName}}}", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new() { Host = "localhost", Port = port }, + }, + UpstreamPathTemplate = $"/safe/{{{idName}}}", + UpstreamHttpMethod = new List { "Get" }, + }, + }, + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", $"/cpx/t1/{idValue}", $"?{queryName}={queryValue}", "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway($"/safe/{idValue}?{queryName}={queryValue}")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); } - private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, string queryString, string responseBody) - { - _serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, basePath, async context => - { - if ((context.Request.PathBase.Value != basePath) || context.Request.QueryString.Value != queryString) - { - context.Response.StatusCode = StatusCodes.Status500InternalServerError; - await context.Response.WriteAsync("downstream path didnt match base path"); - } - else - { - context.Response.StatusCode = StatusCodes.Status200OK; - await context.Response.WriteAsync(responseBody); - } - }); - } - - public void Dispose() - { - _serviceHandler?.Dispose(); + private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, string queryString, string responseBody) + { + _serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, basePath, async context => + { + if (context.Request.PathBase.Value != basePath || context.Request.QueryString.Value != queryString) + { + context.Response.StatusCode = StatusCodes.Status500InternalServerError; + await context.Response.WriteAsync("downstream path didnt match base path"); + } + else + { + context.Response.StatusCode = StatusCodes.Status200OK; + await context.Response.WriteAsync(responseBody); + } + }); + } + + public void Dispose() + { + _serviceHandler?.Dispose(); _steps.Dispose(); - GC.SuppressFinalize(this); - } - } -} + GC.SuppressFinalize(this); + } + } +} From 16f6f889ead196560c67370f7148988be5207945 Mon Sep 17 00:00:00 2001 From: Raman Maksimchuk Date: Sat, 6 Apr 2024 18:33:31 +0300 Subject: [PATCH 40/49] RoutingBasedOnHeadersTests --- .../Routing/RoutingBasedOnHeadersTests.cs | 267 +++++++++--------- 1 file changed, 136 insertions(+), 131 deletions(-) diff --git a/test/Ocelot.AcceptanceTests/Routing/RoutingBasedOnHeadersTests.cs b/test/Ocelot.AcceptanceTests/Routing/RoutingBasedOnHeadersTests.cs index 52a926c9a..1d67ee17f 100644 --- a/test/Ocelot.AcceptanceTests/Routing/RoutingBasedOnHeadersTests.cs +++ b/test/Ocelot.AcceptanceTests/Routing/RoutingBasedOnHeadersTests.cs @@ -1,19 +1,22 @@ using Microsoft.AspNetCore.Http; using Ocelot.Configuration.File; -using Ocelot.Testing; namespace Ocelot.AcceptanceTests.Routing; -public class RoutingBasedOnHeadersTests : IDisposable +public sealed class RoutingBasedOnHeadersTests : Steps, IDisposable { - private readonly Steps _steps; private string _downstreamPath; private readonly ServiceHandler _serviceHandler; public RoutingBasedOnHeadersTests() { _serviceHandler = new(); - _steps = new(); + } + + public override void Dispose() + { + _serviceHandler.Dispose(); + base.Dispose(); } [Fact] @@ -49,13 +52,13 @@ public void Should_match_one_header_value() }, }; - this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/", 200, "Hello from Laura")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .And(x => _steps.GivenIAddAHeader(headerName, headerValue)) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + this.Given(x => x.GivenThereIsAServiceRunningOn(port)) + .And(x => GivenThereIsAConfiguration(configuration)) + .And(x => GivenOcelotIsRunning()) + .And(x => GivenIAddAHeader(headerName, headerValue)) + .When(x => WhenIGetUrlOnTheApiGateway("/")) + .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => ThenTheResponseBodyShouldBe(Hello())) .BDDfy(); } @@ -92,14 +95,14 @@ public void Should_match_one_header_value_when_more_headers() }, }; - this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/", 200, "Hello from Laura")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .And(x => _steps.GivenIAddAHeader("other", "otherValue")) - .And(x => _steps.GivenIAddAHeader(headerName, headerValue)) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + this.Given(x => x.GivenThereIsAServiceRunningOn(port)) + .And(x => GivenThereIsAConfiguration(configuration)) + .And(x => GivenOcelotIsRunning()) + .And(x => GivenIAddAHeader("other", "otherValue")) + .And(x => GivenIAddAHeader(headerName, headerValue)) + .When(x => WhenIGetUrlOnTheApiGateway("/")) + .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => ThenTheResponseBodyShouldBe(Hello())) .BDDfy(); } @@ -139,15 +142,15 @@ public void Should_match_two_header_values_when_more_headers() }, }; - this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/", 200, "Hello from Laura")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .And(x => _steps.GivenIAddAHeader(headerName1, headerValue1)) - .And(x => _steps.GivenIAddAHeader("other", "otherValue")) - .And(x => _steps.GivenIAddAHeader(headerName2, headerValue2)) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + this.Given(x => x.GivenThereIsAServiceRunningOn(port)) + .And(x => GivenThereIsAConfiguration(configuration)) + .And(x => GivenOcelotIsRunning()) + .And(x => GivenIAddAHeader(headerName1, headerValue1)) + .And(x => GivenIAddAHeader("other", "otherValue")) + .And(x => GivenIAddAHeader(headerName2, headerValue2)) + .When(x => WhenIGetUrlOnTheApiGateway("/")) + .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => ThenTheResponseBodyShouldBe(Hello())) .BDDfy(); } @@ -185,12 +188,12 @@ public void Should_not_match_one_header_value() }, }; - this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/", 200, "Hello from Laura")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .And(x => _steps.GivenIAddAHeader(headerName, anotherHeaderValue)) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.NotFound)) + this.Given(x => x.GivenThereIsAServiceRunningOn(port)) + .And(x => GivenThereIsAConfiguration(configuration)) + .And(x => GivenOcelotIsRunning()) + .And(x => GivenIAddAHeader(headerName, anotherHeaderValue)) + .When(x => WhenIGetUrlOnTheApiGateway("/")) + .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.NotFound)) .BDDfy(); } @@ -227,11 +230,11 @@ public void Should_not_match_one_header_value_when_no_headers() }, }; - this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/", 200, "Hello from Laura")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.NotFound)) + this.Given(x => x.GivenThereIsAServiceRunningOn(port)) + .And(x => GivenThereIsAConfiguration(configuration)) + .And(x => GivenOcelotIsRunning()) + .When(x => WhenIGetUrlOnTheApiGateway("/")) + .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.NotFound)) .BDDfy(); } @@ -271,14 +274,14 @@ public void Should_not_match_two_header_values_when_one_different() }, }; - this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/", 200, "Hello from Laura")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .And(x => _steps.GivenIAddAHeader(headerName1, headerValue1)) - .And(x => _steps.GivenIAddAHeader("other", "otherValue")) - .And(x => _steps.GivenIAddAHeader(headerName2, "anothervalue")) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.NotFound)) + this.Given(x => x.GivenThereIsAServiceRunningOn(port)) + .And(x => GivenThereIsAConfiguration(configuration)) + .And(x => GivenOcelotIsRunning()) + .And(x => GivenIAddAHeader(headerName1, headerValue1)) + .And(x => GivenIAddAHeader("other", "otherValue")) + .And(x => GivenIAddAHeader(headerName2, "anothervalue")) + .When(x => WhenIGetUrlOnTheApiGateway("/")) + .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.NotFound)) .BDDfy(); } @@ -318,13 +321,13 @@ public void Should_not_match_two_header_values_when_one_not_existing() }, }; - this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/", 200, "Hello from Laura")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .And(x => _steps.GivenIAddAHeader(headerName1, headerValue1)) - .And(x => _steps.GivenIAddAHeader("other", "otherValue")) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.NotFound)) + this.Given(x => x.GivenThereIsAServiceRunningOn(port)) + .And(x => GivenThereIsAConfiguration(configuration)) + .And(x => GivenOcelotIsRunning()) + .And(x => GivenIAddAHeader(headerName1, headerValue1)) + .And(x => GivenIAddAHeader("other", "otherValue")) + .When(x => WhenIGetUrlOnTheApiGateway("/")) + .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.NotFound)) .BDDfy(); } @@ -361,13 +364,13 @@ public void Should_not_match_one_header_value_when_header_duplicated() }, }; - this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/", 200, "Hello from Laura")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .And(x => _steps.GivenIAddAHeader(headerName, headerValue)) - .And(x => _steps.GivenIAddAHeader(headerName, "othervalue")) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.NotFound)) + this.Given(x => x.GivenThereIsAServiceRunningOn(port)) + .And(x => GivenThereIsAConfiguration(configuration)) + .And(x => GivenOcelotIsRunning()) + .And(x => GivenIAddAHeader(headerName, headerValue)) + .And(x => GivenIAddAHeader(headerName, "othervalue")) + .When(x => WhenIGetUrlOnTheApiGateway("/")) + .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.NotFound)) .BDDfy(); } @@ -435,13 +438,13 @@ public void Should_aggregated_route_match_header_value() }, }; - this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port1}", "/a", 200, "Hello from Laura")) - .And(x => GivenThereIsAServiceRunningOn($"http://localhost:{port2}", "/b", 200, "Hello from Tom")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .And(x => _steps.GivenIAddAHeader(headerName, headerValue)) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + this.Given(x => x.GivenThereIsAServiceRunningOn(port1, "/a", HttpStatusCode.OK, Hello("Laura"))) + .And(x => GivenThereIsAServiceRunningOn(port2, "/b", HttpStatusCode.OK, Hello("Tom"))) + .And(x => GivenThereIsAConfiguration(configuration)) + .And(x => GivenOcelotIsRunning()) + .And(x => GivenIAddAHeader(headerName, headerValue)) + .When(x => WhenIGetUrlOnTheApiGateway("/")) + .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) .BDDfy(); } @@ -509,12 +512,12 @@ public void Should_aggregated_route_not_match_header_value() }, }; - this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port1}", "/a", 200, "Hello from Laura")) - .And(x => GivenThereIsAServiceRunningOn($"http://localhost:{port2}", "/b", 200, "Hello from Tom")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.NotFound)) + this.Given(x => x.GivenThereIsAServiceRunningOn(port1, "/a", HttpStatusCode.OK, Hello("Laura"))) + .And(x => x.GivenThereIsAServiceRunningOn(port2, "/b", HttpStatusCode.OK, Hello("Tom"))) + .And(x => GivenThereIsAConfiguration(configuration)) + .And(x => GivenOcelotIsRunning()) + .When(x => WhenIGetUrlOnTheApiGateway("/")) + .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.NotFound)) .BDDfy(); } @@ -550,13 +553,13 @@ public void Should_match_header_placeholder() }, }; - this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/api.internal-uk/products", 200, "Hello from UK")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .And(x => _steps.GivenIAddAHeader(headerName, "uk")) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/products")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from UK")) + this.Given(x => x.GivenThereIsAServiceRunningOn(port, "/api.internal-uk/products", HttpStatusCode.OK, Hello("UK"))) + .And(x => GivenThereIsAConfiguration(configuration)) + .And(x => GivenOcelotIsRunning()) + .And(x => GivenIAddAHeader(headerName, "uk")) + .When(x => WhenIGetUrlOnTheApiGateway("/products")) + .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => ThenTheResponseBodyShouldBe(Hello("UK"))) .BDDfy(); } @@ -592,13 +595,13 @@ public void Should_match_header_placeholder_not_in_downstream_path() }, }; - this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/products-info", 200, "Hello from products")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .And(x => _steps.GivenIAddAHeader(headerName, "product-Camera")) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/products")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from products")) + this.Given(x => x.GivenThereIsAServiceRunningOn(port, "/products-info", HttpStatusCode.OK, Hello("products"))) + .And(x => GivenThereIsAConfiguration(configuration)) + .And(x => GivenOcelotIsRunning()) + .And(x => GivenIAddAHeader(headerName, "product-Camera")) + .When(x => WhenIGetUrlOnTheApiGateway("/products")) + .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => ThenTheResponseBodyShouldBe(Hello("products"))) .BDDfy(); } @@ -649,13 +652,13 @@ public void Should_distinguish_route_for_different_roles() }, }; - this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/products-admin", 200, "Hello from products admin")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .And(x => _steps.GivenIAddAHeader(headerName, "admin.xxx.com")) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/products")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from products admin")) + this.Given(x => x.GivenThereIsAServiceRunningOn(port, "/products-admin", HttpStatusCode.OK, Hello("products admin"))) + .And(x => GivenThereIsAConfiguration(configuration)) + .And(x => GivenOcelotIsRunning()) + .And(x => GivenIAddAHeader(headerName, "admin.xxx.com")) + .When(x => WhenIGetUrlOnTheApiGateway("/products")) + .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => ThenTheResponseBodyShouldBe(Hello("products admin"))) .BDDfy(); } @@ -691,13 +694,13 @@ public void Should_match_header_and_url_placeholders() }, }; - this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/pl/v1/bb", 200, "Hello from Laura")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .And(x => _steps.GivenIAddAHeader(headerName, "start_pl_version_v1_end")) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/bb")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + this.Given(x => x.GivenThereIsAServiceRunningOn(port, "/pl/v1/bb", HttpStatusCode.OK, Hello())) + .And(x => GivenThereIsAConfiguration(configuration)) + .And(x => GivenOcelotIsRunning()) + .And(x => GivenIAddAHeader(headerName, "start_pl_version_v1_end")) + .When(x => WhenIGetUrlOnTheApiGateway("/bb")) + .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => ThenTheResponseBodyShouldBe(Hello())) .BDDfy(); } @@ -733,13 +736,13 @@ public void Should_match_header_with_braces() }, }; - this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/aa", 200, "Hello from Laura")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .And(x => _steps.GivenIAddAHeader(headerName, "my_{header}")) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + this.Given(x => x.GivenThereIsAServiceRunningOn(port, "/aa", HttpStatusCode.OK, Hello())) + .And(x => GivenThereIsAConfiguration(configuration)) + .And(x => GivenOcelotIsRunning()) + .And(x => GivenIAddAHeader(headerName, "my_{header}")) + .When(x => WhenIGetUrlOnTheApiGateway("/")) + .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => ThenTheResponseBodyShouldBe(Hello())) .BDDfy(); } @@ -777,44 +780,46 @@ public void Should_match_two_headers_with_the_same_name() }, }; - this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/", 200, "Hello from Laura")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .And(x => _steps.GivenIAddAHeader(headerName, headerValue1)) - .And(x => _steps.GivenIAddAHeader(headerName, headerValue2)) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + this.Given(x => x.GivenThereIsAServiceRunningOn(port)) + .And(x => GivenThereIsAConfiguration(configuration)) + .And(x => GivenOcelotIsRunning()) + .And(x => GivenIAddAHeader(headerName, headerValue1)) + .And(x => GivenIAddAHeader(headerName, headerValue2)) + .When(x => WhenIGetUrlOnTheApiGateway("/")) + .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => ThenTheResponseBodyShouldBe(Hello())) .BDDfy(); } - private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode, string responseBody) + //private static string HelloFromJolanta = "Hello from Jolanta"; + + private static string Hello() => Hello("Jolanta"); + private static string Hello(string who) => $"Hello from {who}"; + + private void GivenThereIsAServiceRunningOn(int port) + => GivenThereIsAServiceRunningOn(port, "/", HttpStatusCode.OK, Hello()); + + private void GivenThereIsAServiceRunningOn(int port, string basePath, HttpStatusCode statusCode, string responseBody) { + basePath ??= "/"; + responseBody ??= Hello(); + var baseUrl = DownstreamUrl(port); _serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, basePath, async context => { _downstreamPath = !string.IsNullOrEmpty(context.Request.PathBase.Value) ? context.Request.PathBase.Value : context.Request.Path.Value; if (_downstreamPath != basePath) { - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync("downstream path didnt match base path"); + context.Response.StatusCode = (int)HttpStatusCode.NotFound; + await context.Response.WriteAsync($"{nameof(_downstreamPath)} is not equal to {nameof(basePath)}"); } else { - context.Response.StatusCode = statusCode; + context.Response.StatusCode = (int)statusCode; await context.Response.WriteAsync(responseBody); } }); } - internal void ThenTheDownstreamUrlPathShouldBe(string expectedDownstreamPath) - { - _downstreamPath.ShouldBe(expectedDownstreamPath); - } - - public void Dispose() - { - _serviceHandler.Dispose(); - _steps.Dispose(); - } + private void ThenTheDownstreamUrlPathShouldBe(string expected) => _downstreamPath.ShouldBe(expected); } From 7f5bb6770b90f3d751495b6b9ae2d50758b219d9 Mon Sep 17 00:00:00 2001 From: Raman Maksimchuk Date: Sat, 6 Apr 2024 19:59:03 +0300 Subject: [PATCH 41/49] Refactor acceptance tests --- .../Routing/RoutingBasedOnHeadersTests.cs | 597 ++++-------------- 1 file changed, 117 insertions(+), 480 deletions(-) diff --git a/test/Ocelot.AcceptanceTests/Routing/RoutingBasedOnHeadersTests.cs b/test/Ocelot.AcceptanceTests/Routing/RoutingBasedOnHeadersTests.cs index 1d67ee17f..cc8570cad 100644 --- a/test/Ocelot.AcceptanceTests/Routing/RoutingBasedOnHeadersTests.cs +++ b/test/Ocelot.AcceptanceTests/Routing/RoutingBasedOnHeadersTests.cs @@ -25,32 +25,11 @@ public void Should_match_one_header_value() var port = PortFinder.GetRandomPort(); var headerName = "country_code"; var headerValue = "PL"; - - var configuration = new FileConfiguration + var route = GivenRouteWithUpstreamHeaderTemplates(port, new() { - Routes = new List - { - new() - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new() - { - Host = "localhost", - Port = port, - }, - }, - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - UpstreamHeaderTemplates = new Dictionary() - { - [headerName] = headerValue, - }, - }, - }, - }; + [headerName] = headerValue, + }); + var configuration = GivenConfiguration(route); this.Given(x => x.GivenThereIsAServiceRunningOn(port)) .And(x => GivenThereIsAConfiguration(configuration)) @@ -68,32 +47,11 @@ public void Should_match_one_header_value_when_more_headers() var port = PortFinder.GetRandomPort(); var headerName = "country_code"; var headerValue = "PL"; - - var configuration = new FileConfiguration + var route = GivenRouteWithUpstreamHeaderTemplates(port, new() { - Routes = new List - { - new() - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new() - { - Host = "localhost", - Port = port, - }, - }, - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - UpstreamHeaderTemplates = new Dictionary() - { - [headerName] = headerValue, - }, - }, - }, - }; + [headerName] = headerValue, + }); + var configuration = GivenConfiguration(route); this.Given(x => x.GivenThereIsAServiceRunningOn(port)) .And(x => GivenThereIsAConfiguration(configuration)) @@ -114,33 +72,12 @@ public void Should_match_two_header_values_when_more_headers() var headerValue1 = "PL"; var headerName2 = "region"; var headerValue2 = "MAZ"; - - var configuration = new FileConfiguration + var route = GivenRouteWithUpstreamHeaderTemplates(port, new() { - Routes = new List - { - new() - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new() - { - Host = "localhost", - Port = port, - }, - }, - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - UpstreamHeaderTemplates = new Dictionary() - { - [headerName1] = headerValue1, - [headerName2] = headerValue2, - }, - }, - }, - }; + [headerName1] = headerValue1, + [headerName2] = headerValue2, + }); + var configuration = GivenConfiguration(route); this.Given(x => x.GivenThereIsAServiceRunningOn(port)) .And(x => GivenThereIsAConfiguration(configuration)) @@ -161,32 +98,11 @@ public void Should_not_match_one_header_value() var headerName = "country_code"; var headerValue = "PL"; var anotherHeaderValue = "UK"; - - var configuration = new FileConfiguration + var route = GivenRouteWithUpstreamHeaderTemplates(port, new() { - Routes = new List - { - new() - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new() - { - Host = "localhost", - Port = port, - }, - }, - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - UpstreamHeaderTemplates = new Dictionary() - { - [headerName] = headerValue, - }, - }, - }, - }; + [headerName] = headerValue, + }); + var configuration = GivenConfiguration(route); this.Given(x => x.GivenThereIsAServiceRunningOn(port)) .And(x => GivenThereIsAConfiguration(configuration)) @@ -203,32 +119,11 @@ public void Should_not_match_one_header_value_when_no_headers() var port = PortFinder.GetRandomPort(); var headerName = "country_code"; var headerValue = "PL"; - - var configuration = new FileConfiguration + var route = GivenRouteWithUpstreamHeaderTemplates(port, new() { - Routes = new List - { - new() - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new() - { - Host = "localhost", - Port = port, - }, - }, - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - UpstreamHeaderTemplates = new Dictionary() - { - [headerName] = headerValue, - }, - }, - }, - }; + [headerName] = headerValue, + }); + var configuration = GivenConfiguration(route); this.Given(x => x.GivenThereIsAServiceRunningOn(port)) .And(x => GivenThereIsAConfiguration(configuration)) @@ -246,33 +141,12 @@ public void Should_not_match_two_header_values_when_one_different() var headerValue1 = "PL"; var headerName2 = "region"; var headerValue2 = "MAZ"; - - var configuration = new FileConfiguration + var route = GivenRouteWithUpstreamHeaderTemplates(port, new() { - Routes = new List - { - new() - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new() - { - Host = "localhost", - Port = port, - }, - }, - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - UpstreamHeaderTemplates = new Dictionary() - { - [headerName1] = headerValue1, - [headerName2] = headerValue2, - }, - }, - }, - }; + [headerName1] = headerValue1, + [headerName2] = headerValue2, + }); + var configuration = GivenConfiguration(route); this.Given(x => x.GivenThereIsAServiceRunningOn(port)) .And(x => GivenThereIsAConfiguration(configuration)) @@ -293,33 +167,12 @@ public void Should_not_match_two_header_values_when_one_not_existing() var headerValue1 = "PL"; var headerName2 = "region"; var headerValue2 = "MAZ"; - - var configuration = new FileConfiguration + var route = GivenRouteWithUpstreamHeaderTemplates(port, new() { - Routes = new List - { - new() - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new() - { - Host = "localhost", - Port = port, - }, - }, - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - UpstreamHeaderTemplates = new Dictionary() - { - [headerName1] = headerValue1, - [headerName2] = headerValue2, - }, - }, - }, - }; + [headerName1] = headerValue1, + [headerName2] = headerValue2, + }); + var configuration = GivenConfiguration(route); this.Given(x => x.GivenThereIsAServiceRunningOn(port)) .And(x => GivenThereIsAConfiguration(configuration)) @@ -337,32 +190,11 @@ public void Should_not_match_one_header_value_when_header_duplicated() var port = PortFinder.GetRandomPort(); var headerName = "country_code"; var headerValue = "PL"; - - var configuration = new FileConfiguration + var route = GivenRouteWithUpstreamHeaderTemplates(port, new() { - Routes = new List - { - new() - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new() - { - Host = "localhost", - Port = port, - }, - }, - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - UpstreamHeaderTemplates = new Dictionary() - { - [headerName] = headerValue, - }, - }, - }, - }; + [headerName] = headerValue, + }); + var configuration = GivenConfiguration(route); this.Given(x => x.GivenThereIsAServiceRunningOn(port)) .And(x => GivenThereIsAConfiguration(configuration)) @@ -381,62 +213,14 @@ public void Should_aggregated_route_match_header_value() var port2 = PortFinder.GetRandomPort(); var headerName = "country_code"; var headerValue = "PL"; - - var configuration = new FileConfiguration + var routeA = GivenRoute(port1, "/a", "Laura"); + var routeB = GivenRoute(port2, "/b", "Tom"); + var route = GivenAggRouteWithUpstreamHeaderTemplates(new() { - Routes = new List - { - new() - { - DownstreamPathTemplate = "/a", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new() - { - Host = "localhost", - Port = port1, - }, - }, - UpstreamPathTemplate = "/a", - UpstreamHttpMethod = new List { "Get" }, - Key = "Laura", - }, - new() - { - DownstreamPathTemplate = "/b", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new() - { - Host = "localhost", - Port = port2, - }, - }, - UpstreamPathTemplate = "/b", - UpstreamHttpMethod = new List { "Get" }, - Key = "Tom", - }, - }, - Aggregates = new List() - { - new() - { - UpstreamPathTemplate = "/", - UpstreamHost = "localhost", - RouteKeys = new List - { - "Laura", - "Tom", - }, - UpstreamHeaderTemplates = new Dictionary() - { - [headerName] = headerValue, - }, - }, - }, - }; + [headerName] = headerValue, + }); + var configuration = GivenConfiguration(routeA, routeB); + configuration.Aggregates.Add(route); this.Given(x => x.GivenThereIsAServiceRunningOn(port1, "/a", HttpStatusCode.OK, Hello("Laura"))) .And(x => GivenThereIsAServiceRunningOn(port2, "/b", HttpStatusCode.OK, Hello("Tom"))) @@ -455,62 +239,14 @@ public void Should_aggregated_route_not_match_header_value() var port2 = PortFinder.GetRandomPort(); var headerName = "country_code"; var headerValue = "PL"; - - var configuration = new FileConfiguration + var routeA = GivenRoute(port1, "/a", "Laura"); + var routeB = GivenRoute(port2, "/b", "Tom"); + var route = GivenAggRouteWithUpstreamHeaderTemplates(new() { - Routes = new List - { - new() - { - DownstreamPathTemplate = "/a", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new() - { - Host = "localhost", - Port = port1, - }, - }, - UpstreamPathTemplate = "/a", - UpstreamHttpMethod = new List { "Get" }, - Key = "Laura", - }, - new() - { - DownstreamPathTemplate = "/b", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new() - { - Host = "localhost", - Port = port2, - }, - }, - UpstreamPathTemplate = "/b", - UpstreamHttpMethod = new List { "Get" }, - Key = "Tom", - }, - }, - Aggregates = new List() - { - new() - { - UpstreamPathTemplate = "/", - UpstreamHost = "localhost", - RouteKeys = new List - { - "Laura", - "Tom", - }, - UpstreamHeaderTemplates = new Dictionary() - { - [headerName] = headerValue, - }, - }, - }, - }; + [headerName] = headerValue, + }); + var configuration = GivenConfiguration(routeA, routeB); + configuration.Aggregates.Add(route); this.Given(x => x.GivenThereIsAServiceRunningOn(port1, "/a", HttpStatusCode.OK, Hello("Laura"))) .And(x => x.GivenThereIsAServiceRunningOn(port2, "/b", HttpStatusCode.OK, Hello("Tom"))) @@ -526,32 +262,12 @@ public void Should_match_header_placeholder() { var port = PortFinder.GetRandomPort(); var headerName = "Region"; - - var configuration = new FileConfiguration - { - Routes = new List + var route = GivenRouteWithUpstreamHeaderTemplates(port, "/products", "/api.internal-{code}/products", + new() { - new() - { - DownstreamPathTemplate = "/api.internal-{code}/products", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new() - { - Host = "localhost", - Port = port, - }, - }, - UpstreamPathTemplate = "/products", - UpstreamHttpMethod = new List { "Get" }, - UpstreamHeaderTemplates = new Dictionary() - { - [headerName] = "{header:code}", - }, - }, - }, - }; + [headerName] = "{header:code}", + }); + var configuration = GivenConfiguration(route); this.Given(x => x.GivenThereIsAServiceRunningOn(port, "/api.internal-uk/products", HttpStatusCode.OK, Hello("UK"))) .And(x => GivenThereIsAConfiguration(configuration)) @@ -568,32 +284,12 @@ public void Should_match_header_placeholder_not_in_downstream_path() { var port = PortFinder.GetRandomPort(); var headerName = "ProductName"; - - var configuration = new FileConfiguration - { - Routes = new List + var route = GivenRouteWithUpstreamHeaderTemplates(port, "/products", "/products-info", + new() { - new() - { - DownstreamPathTemplate = "/products-info", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new() - { - Host = "localhost", - Port = port, - }, - }, - UpstreamPathTemplate = "/products", - UpstreamHttpMethod = new List { "Get" }, - UpstreamHeaderTemplates = new Dictionary() - { - [headerName] = "product-{header:everything}", - }, - }, - }, - }; + [headerName] = "product-{header:everything}", + }); + var configuration = GivenConfiguration(route); this.Given(x => x.GivenThereIsAServiceRunningOn(port, "/products-info", HttpStatusCode.OK, Hello("products"))) .And(x => GivenThereIsAConfiguration(configuration)) @@ -610,47 +306,13 @@ public void Should_distinguish_route_for_different_roles() { var port = PortFinder.GetRandomPort(); var headerName = "Origin"; - - var configuration = new FileConfiguration - { - Routes = new List + var route = GivenRouteWithUpstreamHeaderTemplates(port, "/products", "/products-admin", + new() { - new() - { - DownstreamPathTemplate = "/products-admin", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new() - { - Host = "localhost", - Port = port, - }, - }, - UpstreamPathTemplate = "/products", - UpstreamHttpMethod = new List { "Get" }, - UpstreamHeaderTemplates = new Dictionary() - { - [headerName] = "admin.xxx.com", - }, - }, - new() - { - DownstreamPathTemplate = "/products", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new() - { - Host = "localhost", - Port = port, - }, - }, - UpstreamPathTemplate = "/products", - UpstreamHttpMethod = new List { "Get" }, - }, - }, - }; + [headerName] = "admin.xxx.com", + }); + var route2 = GivenRouteWithUpstreamHeaderTemplates(port, "/products", "/products", null); + var configuration = GivenConfiguration(route, route2); this.Given(x => x.GivenThereIsAServiceRunningOn(port, "/products-admin", HttpStatusCode.OK, Hello("products admin"))) .And(x => GivenThereIsAConfiguration(configuration)) @@ -667,32 +329,12 @@ public void Should_match_header_and_url_placeholders() { var port = PortFinder.GetRandomPort(); var headerName = "country_code"; - - var configuration = new FileConfiguration - { - Routes = new List + var route = GivenRouteWithUpstreamHeaderTemplates(port, "/{aa}", "/{country_code}/{version}/{aa}", + new() { - new() - { - DownstreamPathTemplate = "/{country_code}/{version}/{aa}", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new() - { - Host = "localhost", - Port = port, - }, - }, - UpstreamPathTemplate = "/{aa}", - UpstreamHttpMethod = new List { "Get" }, - UpstreamHeaderTemplates = new Dictionary() - { - [headerName] = "start_{header:country_code}_version_{header:version}_end", - }, - }, - }, - }; + [headerName] = "start_{header:country_code}_version_{header:version}_end", + }); + var configuration = GivenConfiguration(route); this.Given(x => x.GivenThereIsAServiceRunningOn(port, "/pl/v1/bb", HttpStatusCode.OK, Hello())) .And(x => GivenThereIsAConfiguration(configuration)) @@ -709,32 +351,12 @@ public void Should_match_header_with_braces() { var port = PortFinder.GetRandomPort(); var headerName = "country_code"; - - var configuration = new FileConfiguration - { - Routes = new List + var route = GivenRouteWithUpstreamHeaderTemplates(port, "/", "/aa", + new() { - new() - { - DownstreamPathTemplate = "/aa", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new() - { - Host = "localhost", - Port = port, - }, - }, - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - UpstreamHeaderTemplates = new Dictionary() - { - [headerName] = "my_{header}", - }, - }, - }, - }; + [headerName] = "my_{header}", + }); + var configuration = GivenConfiguration(route); this.Given(x => x.GivenThereIsAServiceRunningOn(port, "/aa", HttpStatusCode.OK, Hello())) .And(x => GivenThereIsAConfiguration(configuration)) @@ -753,32 +375,12 @@ public void Should_match_two_headers_with_the_same_name() var headerName = "country_code"; var headerValue1 = "PL"; var headerValue2 = "UK"; - - var configuration = new FileConfiguration - { - Routes = new List + var route = GivenRouteWithUpstreamHeaderTemplates(port, + new() { - new() - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new() - { - Host = "localhost", - Port = port, - }, - }, - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - UpstreamHeaderTemplates = new Dictionary() - { - [headerName] = headerValue1 + ";{header:whatever}", - }, - }, - }, - }; + [headerName] = headerValue1 + ";{header:whatever}", + }); + var configuration = GivenConfiguration(route); this.Given(x => x.GivenThereIsAServiceRunningOn(port)) .And(x => GivenThereIsAConfiguration(configuration)) @@ -791,8 +393,6 @@ public void Should_match_two_headers_with_the_same_name() .BDDfy(); } - //private static string HelloFromJolanta = "Hello from Jolanta"; - private static string Hello() => Hello("Jolanta"); private static string Hello(string who) => $"Hello from {who}"; @@ -822,4 +422,41 @@ private void GivenThereIsAServiceRunningOn(int port, string basePath, HttpStatus } private void ThenTheDownstreamUrlPathShouldBe(string expected) => _downstreamPath.ShouldBe(expected); + + private static FileRoute GivenRoute(int port, string path = null, string key = null) => new() + { + DownstreamPathTemplate = path ?? "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = + [ + new("localhost", port), + ], + UpstreamPathTemplate = path ?? "/", + UpstreamHttpMethod = ["Get"], + Key = key, + }; + + private static FileRoute GivenRouteWithUpstreamHeaderTemplates(int port, Dictionary templates) + { + var route = GivenRoute(port); + route.UpstreamHeaderTemplates = templates; + return route; + } + + private static FileRoute GivenRouteWithUpstreamHeaderTemplates(int port, string upstream, string downstream, Dictionary templates) + { + var route = GivenRoute(port); + route.UpstreamHeaderTemplates = templates; + route.UpstreamPathTemplate = upstream ?? "/"; + route.DownstreamPathTemplate = downstream ?? "/"; + return route; + } + + private static FileAggregateRoute GivenAggRouteWithUpstreamHeaderTemplates(Dictionary templates) => new() + { + UpstreamPathTemplate = "/", + UpstreamHost = "localhost", + RouteKeys = ["Laura", "Tom"], + UpstreamHeaderTemplates = templates, + }; } From 6e7ab009d673abb76fc3bf266ea45aa80ce1d9cb Mon Sep 17 00:00:00 2001 From: Raman Maksimchuk Date: Fri, 12 Apr 2024 20:28:52 +0300 Subject: [PATCH 42/49] AAA pattern in unit tests --- ...streamHeaderTemplatePatternCreatorTests.cs | 71 +++-- .../FileConfigurationFluentValidatorTests.cs | 268 ++++++------------ .../DownstreamRouteFinderTests.cs | 121 ++++---- ...eaderPlaceholderNameAndValueFinderTests.cs | 5 - 4 files changed, 189 insertions(+), 276 deletions(-) diff --git a/test/Ocelot.UnitTests/Configuration/UpstreamHeaderTemplatePatternCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/UpstreamHeaderTemplatePatternCreatorTests.cs index a9478e22d..52499a6ba 100644 --- a/test/Ocelot.UnitTests/Configuration/UpstreamHeaderTemplatePatternCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/UpstreamHeaderTemplatePatternCreatorTests.cs @@ -1,10 +1,6 @@ using Ocelot.Configuration.Creator; using Ocelot.Configuration.File; using Ocelot.Values; -using Shouldly; -using System.Collections.Generic; -using TestStack.BDDfy; -using Xunit; namespace Ocelot.UnitTests.Configuration; @@ -22,6 +18,7 @@ public UpstreamHeaderTemplatePatternCreatorTests() [Fact] public void Should_create_pattern_without_placeholders() { + // Arrange var fileRoute = new FileRoute { UpstreamHeaderTemplates = new Dictionary @@ -29,16 +26,19 @@ public void Should_create_pattern_without_placeholders() ["country"] = "a text without placeholders", }, }; + GivenTheFollowingFileRoute(fileRoute); - this.Given(x => x.GivenTheFollowingFileRoute(fileRoute)) - .When(x => x.WhenICreateTheTemplatePattern()) - .Then(x => x.ThenTheFollowingIsReturned("country", "^(?i)a text without placeholders$")) - .BDDfy(); + // Act + WhenICreateTheTemplatePattern(); + + // Assert + ThenTheFollowingIsReturned("country", "^(?i)a text without placeholders$"); } [Fact] public void Should_create_pattern_case_sensitive() { + // Arrange var fileRoute = new FileRoute { RouteIsCaseSensitive = true, @@ -47,16 +47,19 @@ public void Should_create_pattern_case_sensitive() ["country"] = "a text without placeholders", }, }; + GivenTheFollowingFileRoute(fileRoute); + + // Act + WhenICreateTheTemplatePattern(); - this.Given(x => x.GivenTheFollowingFileRoute(fileRoute)) - .When(x => x.WhenICreateTheTemplatePattern()) - .Then(x => x.ThenTheFollowingIsReturned("country", "^a text without placeholders$")) - .BDDfy(); + // Assert + ThenTheFollowingIsReturned("country", "^a text without placeholders$"); } [Fact] public void Should_create_pattern_with_placeholder_in_the_beginning() { + // Arrange var fileRoute = new FileRoute { UpstreamHeaderTemplates = new Dictionary @@ -64,16 +67,19 @@ public void Should_create_pattern_with_placeholder_in_the_beginning() ["country"] = "{header:start}rest of the text", }, }; + GivenTheFollowingFileRoute(fileRoute); - this.Given(x => x.GivenTheFollowingFileRoute(fileRoute)) - .When(x => x.WhenICreateTheTemplatePattern()) - .Then(x => x.ThenTheFollowingIsReturned("country", "^(?i)(?.+)rest of the text$")) - .BDDfy(); + // Act + WhenICreateTheTemplatePattern(); + + // Assert + ThenTheFollowingIsReturned("country", "^(?i)(?.+)rest of the text$"); } [Fact] public void Should_create_pattern_with_placeholder_at_the_end() { + // Arrange var fileRoute = new FileRoute { UpstreamHeaderTemplates = new Dictionary @@ -81,16 +87,19 @@ public void Should_create_pattern_with_placeholder_at_the_end() ["country"] = "rest of the text{header:end}", }, }; + GivenTheFollowingFileRoute(fileRoute); + + // Act + WhenICreateTheTemplatePattern(); - this.Given(x => x.GivenTheFollowingFileRoute(fileRoute)) - .When(x => x.WhenICreateTheTemplatePattern()) - .Then(x => x.ThenTheFollowingIsReturned("country", "^(?i)rest of the text(?.+)$")) - .BDDfy(); + // Assert + ThenTheFollowingIsReturned("country", "^(?i)rest of the text(?.+)$"); } [Fact] public void Should_create_pattern_with_placeholder_only() { + // Arrange var fileRoute = new FileRoute { UpstreamHeaderTemplates = new Dictionary @@ -99,15 +108,19 @@ public void Should_create_pattern_with_placeholder_only() }, }; - this.Given(x => x.GivenTheFollowingFileRoute(fileRoute)) - .When(x => x.WhenICreateTheTemplatePattern()) - .Then(x => x.ThenTheFollowingIsReturned("country", "^(?i)(?.+)$")) - .BDDfy(); + GivenTheFollowingFileRoute(fileRoute); + + // Act + WhenICreateTheTemplatePattern(); + + // Assert + ThenTheFollowingIsReturned("country", "^(?i)(?.+)$"); } [Fact] public void Should_crate_pattern_with_more_placeholders() { + // Arrange var fileRoute = new FileRoute { UpstreamHeaderTemplates = new Dictionary @@ -115,11 +128,13 @@ public void Should_crate_pattern_with_more_placeholders() ["country"] = "any text {header:cc} and other {header:version} and {header:bob} the end", }, }; + GivenTheFollowingFileRoute(fileRoute); + + // Act + WhenICreateTheTemplatePattern(); - this.Given(x => x.GivenTheFollowingFileRoute(fileRoute)) - .When(x => x.WhenICreateTheTemplatePattern()) - .Then(x => x.ThenTheFollowingIsReturned("country", "^(?i)any text (?.+) and other (?.+) and (?.+) the end$")) - .BDDfy(); + // Assert + ThenTheFollowingIsReturned("country", "^(?i)any text (?.+) and other (?.+) and (?.+) the end$"); } private void GivenTheFollowingFileRoute(FileRoute fileRoute) diff --git a/test/Ocelot.UnitTests/Configuration/Validation/FileConfigurationFluentValidatorTests.cs b/test/Ocelot.UnitTests/Configuration/Validation/FileConfigurationFluentValidatorTests.cs index 7f55cad0a..26b113d91 100644 --- a/test/Ocelot.UnitTests/Configuration/Validation/FileConfigurationFluentValidatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/Validation/FileConfigurationFluentValidatorTests.cs @@ -721,198 +721,91 @@ public void Configuration_is_not_valid_when_host_and_port_is_empty() } [Fact] - public void configuration_is_not_valid_when_upstream_headers_the_same() - { - this.Given(x => x.GivenAConfiguration(new FileConfiguration - { - Routes = new List - { - new() - { - DownstreamPathTemplate = "/api/products/", - UpstreamPathTemplate = "/asdf/", - DownstreamHostAndPorts = new List - { - new() - { - Host = "bbc.co.uk", - }, - }, - UpstreamHttpMethod = new List {"Get"}, - UpstreamHeaderTemplates = new Dictionary - { - { "header1", "value1" }, - { "header2", "value2" }, - }, - }, - new() - { - DownstreamPathTemplate = "/www/test/", - UpstreamPathTemplate = "/asdf/", - DownstreamHostAndPorts = new List - { - new() - { - Host = "bbc.co.uk", - }, - }, - UpstreamHttpMethod = new List {"Get"}, - UpstreamHeaderTemplates = new Dictionary - { - { "header2", "value2" }, - { "header1", "value1" }, - }, - }, - }, - })) - .When(x => x.WhenIValidateTheConfiguration()) - .Then(x => x.ThenTheResultIsNotValid()) - .And(x => x.ThenTheErrorMessageAtPositionIs(0, "route /asdf/ has duplicate")) - .BDDfy(); + public void Configuration_is_not_valid_when_upstream_headers_the_same() + { + // Arrange + var route1 = GivenRouteWithUpstreamHeaderTemplates("/asdf/", "/api/products/", new() + { + { "header1", "value1" }, + { "header2", "value2" }, + }); + var route2 = GivenRouteWithUpstreamHeaderTemplates("/asdf/", "/www/test/", new() + { + { "header2", "value2" }, + { "header1", "value1" }, + }); + GivenAConfiguration(route1, route2); + + // Act + WhenIValidateTheConfiguration(); + + // Assert + ThenTheResultIsNotValid(); + ThenTheErrorMessageAtPositionIs(0, "route /asdf/ has duplicate"); } [Fact] - public void configuration_is_valid_when_upstream_headers_not_the_same() + public void Configuration_is_valid_when_upstream_headers_not_the_same() { - this.Given(x => x.GivenAConfiguration(new FileConfiguration - { - Routes = new List - { - new() - { - DownstreamPathTemplate = "/api/products/", - UpstreamPathTemplate = "/asdf/", - DownstreamHostAndPorts = new List - { - new() - { - Host = "bbc.co.uk", - }, - }, - UpstreamHttpMethod = new List {"Get"}, - UpstreamHeaderTemplates = new Dictionary - { - { "header1", "value1" }, - { "header2", "value2" }, - }, - }, - new() - { - DownstreamPathTemplate = "/www/test/", - UpstreamPathTemplate = "/asdf/", - DownstreamHostAndPorts = new List - { - new() - { - Host = "bbc.co.uk", - }, - }, - UpstreamHttpMethod = new List {"Get"}, - UpstreamHeaderTemplates = new Dictionary - { - { "header2", "value2" }, - { "header1", "valueDIFFERENT" }, - }, - }, - }, - })) - .When(x => x.WhenIValidateTheConfiguration()) - .Then(x => x.ThenTheResultIsValid()) - .BDDfy(); + // Arrange + var route1 = GivenRouteWithUpstreamHeaderTemplates("/asdf/", "/api/products/", new() + { + { "header1", "value1" }, + { "header2", "value2" }, + }); + var route2 = GivenRouteWithUpstreamHeaderTemplates("/asdf/", "/www/test/", new() + { + { "header2", "value2" }, + { "header1", "valueDIFFERENT" }, + }); + GivenAConfiguration(route1, route2); + + // Act + WhenIValidateTheConfiguration(); + + // Assert + ThenTheResultIsValid(); } [Fact] - public void configuration_is_valid_when_upstream_headers_count_not_the_same() + public void Configuration_is_valid_when_upstream_headers_count_not_the_same() { - this.Given(x => x.GivenAConfiguration(new FileConfiguration - { - Routes = new List - { - new() - { - DownstreamPathTemplate = "/api/products/", - UpstreamPathTemplate = "/asdf/", - DownstreamHostAndPorts = new List - { - new() - { - Host = "bbc.co.uk", - }, - }, - UpstreamHttpMethod = new List {"Get"}, - UpstreamHeaderTemplates = new Dictionary - { - { "header1", "value1" }, - { "header2", "value2" }, - }, - }, - new() - { - DownstreamPathTemplate = "/www/test/", - UpstreamPathTemplate = "/asdf/", - DownstreamHostAndPorts = new List - { - new() - { - Host = "bbc.co.uk", - }, - }, - UpstreamHttpMethod = new List {"Get"}, - UpstreamHeaderTemplates = new Dictionary - { - { "header2", "value2" }, - }, - }, - }, - })) - .When(x => x.WhenIValidateTheConfiguration()) - .Then(x => x.ThenTheResultIsValid()) - .BDDfy(); + // Arrange + var route1 = GivenRouteWithUpstreamHeaderTemplates("/asdf/", "/api/products/", new() + { + { "header1", "value1" }, + { "header2", "value2" }, + }); + var route2 = GivenRouteWithUpstreamHeaderTemplates("/asdf/", "/www/test/", new() + { + { "header2", "value2" }, + }); + GivenAConfiguration(route1, route2); + + // Act + WhenIValidateTheConfiguration(); + + // Assert + ThenTheResultIsValid(); } [Fact] - public void configuration_is_valid_when_one_upstream_headers_empty_and_other_not_empty() + public void Configuration_is_valid_when_one_upstream_headers_empty_and_other_not_empty() { - this.Given(x => x.GivenAConfiguration(new FileConfiguration - { - Routes = new List - { - new() - { - DownstreamPathTemplate = "/api/products/", - UpstreamPathTemplate = "/asdf/", - DownstreamHostAndPorts = new List - { - new() - { - Host = "bbc.co.uk", - }, - }, - UpstreamHttpMethod = new List {"Get"}, - UpstreamHeaderTemplates = new Dictionary - { - { "header1", "value1" }, - { "header2", "value2" }, - }, - }, - new() - { - DownstreamPathTemplate = "/www/test/", - UpstreamPathTemplate = "/asdf/", - DownstreamHostAndPorts = new List - { - new() - { - Host = "bbc.co.uk", - }, - }, - UpstreamHttpMethod = new List {"Get"}, - }, - }, - })) - .When(x => x.WhenIValidateTheConfiguration()) - .Then(x => x.ThenTheResultIsValid()) - .BDDfy(); + // Arrange + var route1 = GivenRouteWithUpstreamHeaderTemplates("/asdf/", "/api/products/", new() + { + { "header1", "value1" }, + { "header2", "value2" }, + }); + var route2 = GivenRouteWithUpstreamHeaderTemplates("/asdf/", "/www/test/", []); + GivenAConfiguration(route1, route2); + + // Act + WhenIValidateTheConfiguration(); + + // Assert + ThenTheResultIsValid(); } [Theory] @@ -999,6 +892,15 @@ public void Configuration_is_invalid_when_placeholder_is_used_twice_in_downstrea DownstreamPathTemplate = "/", DownstreamScheme = Uri.UriSchemeHttp, ServiceName = "test", + }; + + private static FileRoute GivenRouteWithUpstreamHeaderTemplates(string upstream, string downstream, Dictionary templates) => new() + { + UpstreamPathTemplate = upstream, + DownstreamPathTemplate = downstream, + DownstreamHostAndPorts = [new("bbc.co.uk", 123)], + UpstreamHttpMethod = ["Get"], + UpstreamHeaderTemplates = templates, }; private void GivenAConfiguration(FileConfiguration fileConfiguration) => _fileConfiguration = fileConfiguration; @@ -1097,12 +999,10 @@ private class TestHandler : AuthenticationHandler // It can be set directly or by registering a provider in the dependency injection container. #if NET8_0_OR_GREATER public TestHandler(IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder) : base(options, logger, encoder) - { - } + { } #else public TestHandler(IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock) - { - } + { } #endif protected override Task HandleAuthenticateAsync() diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs index 5f6cc650f..0a679d3a8 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs @@ -716,10 +716,10 @@ public void should_return_route_when_host_matches_but_null_host_on_same_path_fir } [Fact] - public void should_return_route_when_upstream_headers_match() - { + public void Should_return_route_when_upstream_headers_match() + { + // Arrange var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); - var upstreamHeaders = new Dictionary() { ["header1"] = "headerValue1", @@ -734,91 +734,94 @@ public void should_return_route_when_upstream_headers_match() var urlPlaceholders = new List { new PlaceholderNameAndValue("url", "urlValue") }; var headerPlaceholders = new List { new PlaceholderNameAndValue("header", "headerValue") }; - this.Given(x => x.GivenThereIsAnUpstreamUrlPath("matchInUrlMatcher/")) - .And(x => GivenTheUpstreamHeadersIs(upstreamHeaders)) - .And(x => x.GivenTheTemplateVariableAndNameFinderReturns( - new OkResponse>(urlPlaceholders))) - .And(x => x.GivenTheHeaderPlaceholderAndNameFinderReturns(headerPlaceholders)) - .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")) - .WithUpstreamHeaders(upstreamHeadersConfig) - .Build(), - }, string.Empty, serviceProviderConfig - )) - .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) - .And(x => x.GivenTheHeadersMatcherReturns(true)) - .And(x => x.GivenTheUpstreamHttpMethodIs("Get")) - .When(x => x.WhenICallTheFinder()) - .Then( - x => x.ThenTheFollowingIsReturned(new DownstreamRouteHolder( - urlPlaceholders.Union(headerPlaceholders).ToList(), - new RouteBuilder() - .WithDownstreamRoute(new DownstreamRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) - .Build()) - .WithUpstreamHttpMethod(new List { "Get" }) + GivenThereIsAnUpstreamUrlPath("matchInUrlMatcher/"); + GivenTheUpstreamHeadersIs(upstreamHeaders); + GivenTheTemplateVariableAndNameFinderReturns(new OkResponse>(urlPlaceholders)); + GivenTheHeaderPlaceholderAndNameFinderReturns(headerPlaceholders); + GivenTheConfigurationIs( + [ + new RouteBuilder() + .WithDownstreamRoute(new DownstreamRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamHttpMethod(["Get"]) .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) - .Build() - ))) - .And(x => x.ThenTheUrlMatcherIsCalledCorrectly()) - .BDDfy(); + .Build()) + .WithUpstreamHttpMethod(["Get"]) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .WithUpstreamHeaders(upstreamHeadersConfig) + .Build(), + ], string.Empty, serviceProviderConfig); + GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true))); + GivenTheHeadersMatcherReturns(true); + GivenTheUpstreamHttpMethodIs("Get"); + + // Act + WhenICallTheFinder(); + + // Assert + ThenTheFollowingIsReturned(new DownstreamRouteHolder( + urlPlaceholders.Union(headerPlaceholders).ToList(), + new RouteBuilder() + .WithDownstreamRoute(new DownstreamRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamHttpMethod(["Get"]) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .Build()) + .WithUpstreamHttpMethod(["Get"]) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .Build() + )); + ThenTheUrlMatcherIsCalledCorrectly(); } [Fact] - public void should_not_return_route_when_upstream_headers_dont_match() + public void Should_not_return_route_when_upstream_headers_dont_match() { + // Arrange var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); - var upstreamHeadersConfig = new Dictionary() { ["header1"] = new UpstreamHeaderTemplate("headerValue1", "headerValue1"), ["header2"] = new UpstreamHeaderTemplate("headerValue2", "headerValue2"), }; - this.Given(x => x.GivenThereIsAnUpstreamUrlPath("matchInUrlMatcher/")) - .And(x => GivenTheUpstreamHeadersIs(new Dictionary() { { "header1", "headerValue1" } })) - .And(x => x.GivenTheTemplateVariableAndNameFinderReturns(new OkResponse>(new List()))) - .And(x => x.GivenTheHeaderPlaceholderAndNameFinderReturns(new List())) - .And(x => x.GivenTheConfigurationIs(new List + GivenThereIsAnUpstreamUrlPath("matchInUrlMatcher/"); + GivenTheUpstreamHeadersIs(new Dictionary() { { "header1", "headerValue1" } }); + GivenTheTemplateVariableAndNameFinderReturns(new OkResponse>(new List())); + GivenTheHeaderPlaceholderAndNameFinderReturns(new List()); + GivenTheConfigurationIs(new List { new RouteBuilder() .WithDownstreamRoute(new DownstreamRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamHttpMethod(["Get"]) .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .Build()) - .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamHttpMethod(["Get"]) .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .WithUpstreamHeaders(upstreamHeadersConfig) .Build(), new RouteBuilder() .WithDownstreamRoute(new DownstreamRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamHttpMethod(["Get"]) .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .Build()) - .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamHttpMethod(["Get"]) .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .WithUpstreamHeaders(upstreamHeadersConfig) .Build(), - }, string.Empty, serviceProviderConfig - )) - .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) - .And(x => x.GivenTheHeadersMatcherReturns(false)) - .And(x => x.GivenTheUpstreamHttpMethodIs("Get")) - .When(x => x.WhenICallTheFinder()) - .Then(x => x.ThenAnErrorResponseIsReturned()) - .BDDfy(); + }, string.Empty, serviceProviderConfig + ); + GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true))); + GivenTheHeadersMatcherReturns(false); + GivenTheUpstreamHttpMethodIs("Get"); + + // Act + WhenICallTheFinder(); + + // Assert + ThenAnErrorResponseIsReturned(); } private void GivenTheUpstreamHostIs(string upstreamHost) diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinderTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinderTests.cs index 13d8f40fd..39d814624 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinderTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinderTests.cs @@ -1,11 +1,6 @@ using Ocelot.DownstreamRouteFinder.HeaderMatcher; using Ocelot.DownstreamRouteFinder.UrlMatcher; using Ocelot.Values; -using Shouldly; -using System.Collections.Generic; -using System.Linq; -using TestStack.BDDfy; -using Xunit; namespace Ocelot.UnitTests.DownstreamRouteFinder.HeaderMatcher; From 6aeef969c6a9e53fe29087bb3d012b5308ac5a2f Mon Sep 17 00:00:00 2001 From: Raman Maksimchuk Date: Fri, 12 Apr 2024 21:24:53 +0300 Subject: [PATCH 43/49] CS8936: Feature 'collection expressions' is not available in C# 10.0. Please use language version 12.0 or greater. --- .../Builder/DownstreamRouteBuilder.cs | 10 ++--- .../Configuration/File/FileAggregateRoute.cs | 2 +- .../Routing/RoutingBasedOnHeadersTests.cs | 10 ++--- .../FileConfigurationFluentValidatorTests.cs | 9 +++-- .../DownstreamRouteFinderTests.cs | 37 ++++++++++--------- 5 files changed, 37 insertions(+), 31 deletions(-) diff --git a/src/Ocelot/Configuration/Builder/DownstreamRouteBuilder.cs b/src/Ocelot/Configuration/Builder/DownstreamRouteBuilder.cs index a3f7e5ec4..25005bd50 100644 --- a/src/Ocelot/Configuration/Builder/DownstreamRouteBuilder.cs +++ b/src/Ocelot/Configuration/Builder/DownstreamRouteBuilder.cs @@ -44,10 +44,10 @@ public class DownstreamRouteBuilder public DownstreamRouteBuilder() { - _downstreamAddresses = []; - _delegatingHandlers = []; - _addHeadersToDownstream = []; - _addHeadersToUpstream = []; + _downstreamAddresses = new(); + _delegatingHandlers = new(); + _addHeadersToDownstream = new(); + _addHeadersToUpstream = new(); } public DownstreamRouteBuilder WithDownstreamAddresses(List downstreamAddresses) @@ -90,7 +90,7 @@ public DownstreamRouteBuilder WithUpstreamHttpMethod(List input) { _upstreamHttpMethod = input.Count > 0 ? input.Select(x => new HttpMethod(x.Trim())).ToList() - : []; + : new(); return this; } diff --git a/src/Ocelot/Configuration/File/FileAggregateRoute.cs b/src/Ocelot/Configuration/File/FileAggregateRoute.cs index 69eb7d0a6..474b87ed1 100644 --- a/src/Ocelot/Configuration/File/FileAggregateRoute.cs +++ b/src/Ocelot/Configuration/File/FileAggregateRoute.cs @@ -12,7 +12,7 @@ public class FileAggregateRoute : IRoute public string Aggregator { get; set; } // Only supports GET..are you crazy!! POST, PUT WOULD BE CRAZY!! :) - public List UpstreamHttpMethod => [HttpMethods.Get]; + public List UpstreamHttpMethod => new() { HttpMethods.Get }; public Dictionary UpstreamHeaderTemplates { get; set; } public int Priority { get; set; } = 1; diff --git a/test/Ocelot.AcceptanceTests/Routing/RoutingBasedOnHeadersTests.cs b/test/Ocelot.AcceptanceTests/Routing/RoutingBasedOnHeadersTests.cs index cc8570cad..790a3f88c 100644 --- a/test/Ocelot.AcceptanceTests/Routing/RoutingBasedOnHeadersTests.cs +++ b/test/Ocelot.AcceptanceTests/Routing/RoutingBasedOnHeadersTests.cs @@ -427,12 +427,12 @@ private void GivenThereIsAServiceRunningOn(int port, string basePath, HttpStatus { DownstreamPathTemplate = path ?? "/", DownstreamScheme = "http", - DownstreamHostAndPorts = - [ + DownstreamHostAndPorts = new() + { new("localhost", port), - ], + }, UpstreamPathTemplate = path ?? "/", - UpstreamHttpMethod = ["Get"], + UpstreamHttpMethod = new() { HttpMethods.Get }, Key = key, }; @@ -456,7 +456,7 @@ private static FileRoute GivenRouteWithUpstreamHeaderTemplates(int port, string { UpstreamPathTemplate = "/", UpstreamHost = "localhost", - RouteKeys = ["Laura", "Tom"], + RouteKeys = new() { "Laura", "Tom" }, UpstreamHeaderTemplates = templates, }; } diff --git a/test/Ocelot.UnitTests/Configuration/Validation/FileConfigurationFluentValidatorTests.cs b/test/Ocelot.UnitTests/Configuration/Validation/FileConfigurationFluentValidatorTests.cs index 26b113d91..6c620cf62 100644 --- a/test/Ocelot.UnitTests/Configuration/Validation/FileConfigurationFluentValidatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/Validation/FileConfigurationFluentValidatorTests.cs @@ -798,7 +798,7 @@ public void Configuration_is_valid_when_one_upstream_headers_empty_and_other_not { "header1", "value1" }, { "header2", "value2" }, }); - var route2 = GivenRouteWithUpstreamHeaderTemplates("/asdf/", "/www/test/", []); + var route2 = GivenRouteWithUpstreamHeaderTemplates("/asdf/", "/www/test/", new()); GivenAConfiguration(route1, route2); // Act @@ -898,8 +898,11 @@ public void Configuration_is_invalid_when_placeholder_is_used_twice_in_downstrea { UpstreamPathTemplate = upstream, DownstreamPathTemplate = downstream, - DownstreamHostAndPorts = [new("bbc.co.uk", 123)], - UpstreamHttpMethod = ["Get"], + DownstreamHostAndPorts = new() + { + new("bbc.co.uk", 123), + }, + UpstreamHttpMethod = new() { HttpMethods.Get }, UpstreamHeaderTemplates = templates, }; diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs index 0a679d3a8..ab880d06c 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs @@ -738,19 +738,22 @@ public void Should_return_route_when_upstream_headers_match() GivenTheUpstreamHeadersIs(upstreamHeaders); GivenTheTemplateVariableAndNameFinderReturns(new OkResponse>(urlPlaceholders)); GivenTheHeaderPlaceholderAndNameFinderReturns(headerPlaceholders); - GivenTheConfigurationIs( - [ + GivenTheConfigurationIs( + new() + { new RouteBuilder() - .WithDownstreamRoute(new DownstreamRouteBuilder() - .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamHttpMethod(["Get"]) - .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) - .Build()) - .WithUpstreamHttpMethod(["Get"]) + .WithDownstreamRoute(new DownstreamRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamHttpMethod(new() {"Get"}) .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) - .WithUpstreamHeaders(upstreamHeadersConfig) - .Build(), - ], string.Empty, serviceProviderConfig); + .Build()) + .WithUpstreamHttpMethod(new() {"Get"}) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .WithUpstreamHeaders(upstreamHeadersConfig) + .Build(), + }, + string.Empty, + serviceProviderConfig); GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true))); GivenTheHeadersMatcherReturns(true); GivenTheUpstreamHttpMethodIs("Get"); @@ -764,10 +767,10 @@ public void Should_return_route_when_upstream_headers_match() new RouteBuilder() .WithDownstreamRoute(new DownstreamRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamHttpMethod(["Get"]) + .WithUpstreamHttpMethod(new() { "Get" }) .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .Build()) - .WithUpstreamHttpMethod(["Get"]) + .WithUpstreamHttpMethod(new() { "Get" }) .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .Build() )); @@ -794,20 +797,20 @@ public void Should_not_return_route_when_upstream_headers_dont_match() new RouteBuilder() .WithDownstreamRoute(new DownstreamRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamHttpMethod(["Get"]) + .WithUpstreamHttpMethod(new() { "Get" }) .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .Build()) - .WithUpstreamHttpMethod(["Get"]) + .WithUpstreamHttpMethod(new() { "Get" }) .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .WithUpstreamHeaders(upstreamHeadersConfig) .Build(), new RouteBuilder() .WithDownstreamRoute(new DownstreamRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamHttpMethod(["Get"]) + .WithUpstreamHttpMethod(new() { "Get" }) .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .Build()) - .WithUpstreamHttpMethod(["Get"]) + .WithUpstreamHttpMethod(new() { "Get" }) .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .WithUpstreamHeaders(upstreamHeadersConfig) .Build(), From c727b934719503710b60671f22b0db25c05207e4 Mon Sep 17 00:00:00 2001 From: Raman Maksimchuk Date: Mon, 15 Apr 2024 15:49:52 +0300 Subject: [PATCH 44/49] Code review by @RaynaldM --- src/Ocelot/Configuration/Builder/RouteBuilder.cs | 4 ++-- .../Creator/IUpstreamHeaderTemplatePatternCreator.cs | 4 ++-- .../Creator/UpstreamHeaderTemplatePatternCreator.cs | 7 ++++--- src/Ocelot/Configuration/File/FileAggregateRoute.cs | 4 ++-- src/Ocelot/Configuration/File/FileRoute.cs | 4 ++-- src/Ocelot/Configuration/File/IRoute.cs | 2 +- src/Ocelot/Configuration/Route.cs | 4 ++-- .../Validator/FileConfigurationFluentValidator.cs | 9 ++++----- .../Finder/DownstreamRouteCreator.cs | 3 ++- .../Finder/DownstreamRouteFinder.cs | 4 ++-- .../Finder/IDownstreamRouteProvider.cs | 2 +- .../HeaderMatcher/HeaderPlaceholderNameAndValueFinder.cs | 2 +- .../HeaderMatcher/HeadersToHeaderTemplatesMatcher.cs | 2 +- .../IHeaderPlaceholderNameAndValueFinder.cs | 2 +- .../HeaderMatcher/IHeadersToHeaderTemplatesMatcher.cs | 2 +- .../UpstreamHeaderTemplatePatternCreatorTests.cs | 2 +- .../HeaderPlaceholderNameAndValueFinderTests.cs | 3 ++- 17 files changed, 31 insertions(+), 29 deletions(-) diff --git a/src/Ocelot/Configuration/Builder/RouteBuilder.cs b/src/Ocelot/Configuration/Builder/RouteBuilder.cs index 00dab1773..c39062829 100644 --- a/src/Ocelot/Configuration/Builder/RouteBuilder.cs +++ b/src/Ocelot/Configuration/Builder/RouteBuilder.cs @@ -11,7 +11,7 @@ public class RouteBuilder private List _downstreamRoutes; private List _downstreamRoutesConfig; private string _aggregator; - private Dictionary _upstreamHeaders; + private IDictionary _upstreamHeaders; public RouteBuilder() { @@ -61,7 +61,7 @@ public RouteBuilder WithAggregator(string aggregator) return this; } - public RouteBuilder WithUpstreamHeaders(Dictionary upstreamHeaders) + public RouteBuilder WithUpstreamHeaders(IDictionary upstreamHeaders) { _upstreamHeaders = upstreamHeaders; return this; diff --git a/src/Ocelot/Configuration/Creator/IUpstreamHeaderTemplatePatternCreator.cs b/src/Ocelot/Configuration/Creator/IUpstreamHeaderTemplatePatternCreator.cs index 7ef43d914..d2fea8004 100644 --- a/src/Ocelot/Configuration/Creator/IUpstreamHeaderTemplatePatternCreator.cs +++ b/src/Ocelot/Configuration/Creator/IUpstreamHeaderTemplatePatternCreator.cs @@ -12,6 +12,6 @@ public interface IUpstreamHeaderTemplatePatternCreator /// Creates upstream templates based on route headers. ///
/// The route info. - /// A object where TKey is , TValue is . - Dictionary Create(IRoute route); + /// An object where TKey is , TValue is . + IDictionary Create(IRoute route); } diff --git a/src/Ocelot/Configuration/Creator/UpstreamHeaderTemplatePatternCreator.cs b/src/Ocelot/Configuration/Creator/UpstreamHeaderTemplatePatternCreator.cs index 5b2e041d3..52c653f5e 100644 --- a/src/Ocelot/Configuration/Creator/UpstreamHeaderTemplatePatternCreator.cs +++ b/src/Ocelot/Configuration/Creator/UpstreamHeaderTemplatePatternCreator.cs @@ -9,15 +9,16 @@ namespace Ocelot.Configuration.Creator; /// Ocelot feature: Routing based on request header. public partial class UpstreamHeaderTemplatePatternCreator : IUpstreamHeaderTemplatePatternCreator { + private const string PlaceHolderPattern = @"(\{header:.*?\})"; #if NET7_0_OR_GREATER - [GeneratedRegex(@"(\{header:.*?\})", RegexOptions.IgnoreCase | RegexOptions.Singleline, "en-US")] + [GeneratedRegex(PlaceHolderPattern, RegexOptions.IgnoreCase | RegexOptions.Singleline, "en-US")] private static partial Regex RegExPlaceholders(); #else - private static readonly Regex RegExPlaceholdersVar = new(@"(\{header:.*?\})", RegexOptions.IgnoreCase | RegexOptions.Singleline, TimeSpan.FromMilliseconds(1000)); + private static readonly Regex RegExPlaceholdersVar = new(PlaceHolderPattern, RegexOptions.IgnoreCase | RegexOptions.Singleline, TimeSpan.FromMilliseconds(1000)); private static Regex RegExPlaceholders() => RegExPlaceholdersVar; #endif - public Dictionary Create(IRoute route) + public IDictionary Create(IRoute route) { var result = new Dictionary(); diff --git a/src/Ocelot/Configuration/File/FileAggregateRoute.cs b/src/Ocelot/Configuration/File/FileAggregateRoute.cs index 474b87ed1..fa0ef305a 100644 --- a/src/Ocelot/Configuration/File/FileAggregateRoute.cs +++ b/src/Ocelot/Configuration/File/FileAggregateRoute.cs @@ -13,14 +13,14 @@ public class FileAggregateRoute : IRoute // Only supports GET..are you crazy!! POST, PUT WOULD BE CRAZY!! :) public List UpstreamHttpMethod => new() { HttpMethods.Get }; - public Dictionary UpstreamHeaderTemplates { get; set; } + public IDictionary UpstreamHeaderTemplates { get; set; } public int Priority { get; set; } = 1; public FileAggregateRoute() { RouteKeys = new(); RouteKeysConfig = new(); - UpstreamHeaderTemplates = new(); + UpstreamHeaderTemplates = new Dictionary(); } } } diff --git a/src/Ocelot/Configuration/File/FileRoute.cs b/src/Ocelot/Configuration/File/FileRoute.cs index 5f61e340f..840e12507 100644 --- a/src/Ocelot/Configuration/File/FileRoute.cs +++ b/src/Ocelot/Configuration/File/FileRoute.cs @@ -61,7 +61,7 @@ public FileRoute(FileRoute from) public string UpstreamHost { get; set; } public List UpstreamHttpMethod { get; set; } public string UpstreamPathTemplate { get; set; } - public Dictionary UpstreamHeaderTemplates { get; set; } + public IDictionary UpstreamHeaderTemplates { get; set; } /// /// Clones this object by making a deep copy. @@ -103,7 +103,7 @@ public static void DeepCopy(FileRoute from, FileRoute to) to.ServiceName = from.ServiceName; to.ServiceNamespace = from.ServiceNamespace; to.Timeout = from.Timeout; - to.UpstreamHeaderTemplates = new(from.UpstreamHeaderTemplates); + to.UpstreamHeaderTemplates = new Dictionary(from.UpstreamHeaderTemplates); to.UpstreamHeaderTransform = new(from.UpstreamHeaderTransform); to.UpstreamHost = from.UpstreamHost; to.UpstreamHttpMethod = new(from.UpstreamHttpMethod); diff --git a/src/Ocelot/Configuration/File/IRoute.cs b/src/Ocelot/Configuration/File/IRoute.cs index 6296673bf..1a70debb3 100644 --- a/src/Ocelot/Configuration/File/IRoute.cs +++ b/src/Ocelot/Configuration/File/IRoute.cs @@ -2,7 +2,7 @@ public interface IRoute { - Dictionary UpstreamHeaderTemplates { get; set; } + IDictionary UpstreamHeaderTemplates { get; set; } string UpstreamPathTemplate { get; set; } bool RouteIsCaseSensitive { get; set; } int Priority { get; set; } diff --git a/src/Ocelot/Configuration/Route.cs b/src/Ocelot/Configuration/Route.cs index 42f272b97..12c57949c 100644 --- a/src/Ocelot/Configuration/Route.cs +++ b/src/Ocelot/Configuration/Route.cs @@ -11,7 +11,7 @@ public Route(List downstreamRoute, UpstreamPathTemplate upstreamTemplatePattern, string upstreamHost, string aggregator, - Dictionary upstreamHeaderTemplates) + IDictionary upstreamHeaderTemplates) { UpstreamHost = upstreamHost; DownstreamRoute = downstreamRoute; @@ -22,7 +22,7 @@ public Route(List downstreamRoute, UpstreamHeaderTemplates = upstreamHeaderTemplates; } - public Dictionary UpstreamHeaderTemplates { get; } + public IDictionary UpstreamHeaderTemplates { get; } public UpstreamPathTemplate UpstreamTemplatePattern { get; } public List UpstreamHttpMethod { get; } public string UpstreamHost { get; } diff --git a/src/Ocelot/Configuration/Validator/FileConfigurationFluentValidator.cs b/src/Ocelot/Configuration/Validator/FileConfigurationFluentValidator.cs index e6e9d0b52..c8596b2d5 100644 --- a/src/Ocelot/Configuration/Validator/FileConfigurationFluentValidator.cs +++ b/src/Ocelot/Configuration/Validator/FileConfigurationFluentValidator.cs @@ -123,16 +123,15 @@ private static bool DoesNotContainRoutesWithSpecificRequestIdKeys(FileAggregateR return routesForAggregate.All(r => string.IsNullOrEmpty(r.RequestIdKey)); } - private static bool IsNotDuplicateIn(FileRoute route, - IEnumerable routes) + private static bool IsNotDuplicateIn(FileRoute route, IEnumerable routes) { var matchingRoutes = routes .Where(r => r.UpstreamPathTemplate == route.UpstreamPathTemplate && r.UpstreamHost == route.UpstreamHost && AreTheSame(r.UpstreamHeaderTemplates, route.UpstreamHeaderTemplates)) - .ToList(); + .ToArray(); - if (matchingRoutes.Count == 1) + if (matchingRoutes.Length == 1) { return true; } @@ -153,7 +152,7 @@ private static bool IsNotDuplicateIn(FileRoute route, return true; } - private static bool AreTheSame(Dictionary upstreamHeaderTemplates, Dictionary otherHeaderTemplates) + private static bool AreTheSame(IDictionary upstreamHeaderTemplates, IDictionary otherHeaderTemplates) => upstreamHeaderTemplates.Count == otherHeaderTemplates.Count && upstreamHeaderTemplates.All(x => otherHeaderTemplates.ContainsKey(x.Key) && otherHeaderTemplates[x.Key] == x.Value); diff --git a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteCreator.cs b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteCreator.cs index 9346d8bda..ef2590ae5 100644 --- a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteCreator.cs +++ b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteCreator.cs @@ -18,7 +18,8 @@ public DownstreamRouteCreator(IQoSOptionsCreator qoSOptionsCreator) _cache = new ConcurrentDictionary>(); } - public Response Get(string upstreamUrlPath, string upstreamQueryString, string upstreamHttpMethod, IInternalConfiguration configuration, string upstreamHost, Dictionary upstreamHeaders) + public Response Get(string upstreamUrlPath, string upstreamQueryString, string upstreamHttpMethod, + IInternalConfiguration configuration, string upstreamHost, IDictionary upstreamHeaders) { var serviceName = GetServiceName(upstreamUrlPath); diff --git a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs index 54f6b983d..fab94d4e1 100644 --- a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs +++ b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs @@ -25,7 +25,7 @@ public DownstreamRouteFinder( } public Response Get(string upstreamUrlPath, string upstreamQueryString, string httpMethod, - IInternalConfiguration configuration, string upstreamHost, Dictionary upstreamHeaders) + IInternalConfiguration configuration, string upstreamHost, IDictionary upstreamHeaders) { var downstreamRoutes = new List(); @@ -60,7 +60,7 @@ private static bool RouteIsApplicableToThisRequest(Route route, string httpMetho (string.IsNullOrEmpty(route.UpstreamHost) || route.UpstreamHost == upstreamHost); } - private DownstreamRouteHolder GetPlaceholderNamesAndValues(string path, string query, Route route, Dictionary upstreamHeaders) + private DownstreamRouteHolder GetPlaceholderNamesAndValues(string path, string query, Route route, IDictionary upstreamHeaders) { var templatePlaceholderNameAndValues = _pathPlaceholderFinder .Find(path, query, route.UpstreamTemplatePattern.OriginalValue) diff --git a/src/Ocelot/DownstreamRouteFinder/Finder/IDownstreamRouteProvider.cs b/src/Ocelot/DownstreamRouteFinder/Finder/IDownstreamRouteProvider.cs index 81ba875a4..c30ba31bc 100644 --- a/src/Ocelot/DownstreamRouteFinder/Finder/IDownstreamRouteProvider.cs +++ b/src/Ocelot/DownstreamRouteFinder/Finder/IDownstreamRouteProvider.cs @@ -6,5 +6,5 @@ namespace Ocelot.DownstreamRouteFinder.Finder; public interface IDownstreamRouteProvider { Response Get(string upstreamUrlPath, string upstreamQueryString, string upstreamHttpMethod, - IInternalConfiguration configuration, string upstreamHost, Dictionary upstreamHeaders); + IInternalConfiguration configuration, string upstreamHost, IDictionary upstreamHeaders); } diff --git a/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinder.cs b/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinder.cs index 849a99445..56e55b2f4 100644 --- a/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinder.cs +++ b/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinder.cs @@ -5,7 +5,7 @@ namespace Ocelot.DownstreamRouteFinder.HeaderMatcher; public class HeaderPlaceholderNameAndValueFinder : IHeaderPlaceholderNameAndValueFinder { - public List Find(Dictionary upstreamHeaders, Dictionary templateHeaders) + public IList Find(IDictionary upstreamHeaders, IDictionary templateHeaders) { var result = new List(); foreach (var templateHeader in templateHeaders) diff --git a/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/HeadersToHeaderTemplatesMatcher.cs b/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/HeadersToHeaderTemplatesMatcher.cs index b6c68cc86..42be8dc7f 100644 --- a/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/HeadersToHeaderTemplatesMatcher.cs +++ b/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/HeadersToHeaderTemplatesMatcher.cs @@ -4,7 +4,7 @@ namespace Ocelot.DownstreamRouteFinder.HeaderMatcher; public class HeadersToHeaderTemplatesMatcher : IHeadersToHeaderTemplatesMatcher { - public bool Match(Dictionary upstreamHeaders, Dictionary routeHeaders) => + public bool Match(IDictionary upstreamHeaders, IDictionary routeHeaders) => routeHeaders == null || upstreamHeaders != null && routeHeaders.All(h => upstreamHeaders.ContainsKey(h.Key) && routeHeaders[h.Key].Pattern.IsMatch(upstreamHeaders[h.Key])); diff --git a/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/IHeaderPlaceholderNameAndValueFinder.cs b/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/IHeaderPlaceholderNameAndValueFinder.cs index 0c8dcc81f..6f641d278 100644 --- a/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/IHeaderPlaceholderNameAndValueFinder.cs +++ b/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/IHeaderPlaceholderNameAndValueFinder.cs @@ -8,5 +8,5 @@ namespace Ocelot.DownstreamRouteFinder.HeaderMatcher; /// public interface IHeaderPlaceholderNameAndValueFinder { - List Find(Dictionary upstreamHeaders, Dictionary templateHeaders); + IList Find(IDictionary upstreamHeaders, IDictionary templateHeaders); } diff --git a/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/IHeadersToHeaderTemplatesMatcher.cs b/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/IHeadersToHeaderTemplatesMatcher.cs index 0a781b9f5..37dcea32a 100644 --- a/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/IHeadersToHeaderTemplatesMatcher.cs +++ b/src/Ocelot/DownstreamRouteFinder/HeaderMatcher/IHeadersToHeaderTemplatesMatcher.cs @@ -7,5 +7,5 @@ namespace Ocelot.DownstreamRouteFinder.HeaderMatcher; /// public interface IHeadersToHeaderTemplatesMatcher { - bool Match(Dictionary upstreamHeaders, Dictionary routeHeaders); + bool Match(IDictionary upstreamHeaders, IDictionary routeHeaders); } diff --git a/test/Ocelot.UnitTests/Configuration/UpstreamHeaderTemplatePatternCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/UpstreamHeaderTemplatePatternCreatorTests.cs index 52499a6ba..bc8eb85d6 100644 --- a/test/Ocelot.UnitTests/Configuration/UpstreamHeaderTemplatePatternCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/UpstreamHeaderTemplatePatternCreatorTests.cs @@ -8,7 +8,7 @@ public class UpstreamHeaderTemplatePatternCreatorTests { private FileRoute _fileRoute; private readonly UpstreamHeaderTemplatePatternCreator _creator; - private Dictionary _result; + private IDictionary _result; public UpstreamHeaderTemplatePatternCreatorTests() { diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinderTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinderTests.cs index 39d814624..6291c262d 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinderTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinderTests.cs @@ -184,7 +184,8 @@ private void GivenUpstreamHeadersAre(Dictionary upstreamHeaders) private void WhenICallFindPlaceholders() { - _result = _finder.Find(_upstreamHeaders, _upstreamHeaderTemplates); + var result = _finder.Find(_upstreamHeaders, _upstreamHeaderTemplates); + _result = new(result); } private void TheResultIs(List expected) From 7e9882466b51511099b126117c71ef8e8fe65963 Mon Sep 17 00:00:00 2001 From: Raman Maksimchuk Date: Mon, 15 Apr 2024 18:42:46 +0300 Subject: [PATCH 45/49] Convert facts to one `Theory` --- ...streamHeaderTemplatePatternCreatorTests.cs | 137 ++---------------- 1 file changed, 14 insertions(+), 123 deletions(-) diff --git a/test/Ocelot.UnitTests/Configuration/UpstreamHeaderTemplatePatternCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/UpstreamHeaderTemplatePatternCreatorTests.cs index bc8eb85d6..cf408dc9d 100644 --- a/test/Ocelot.UnitTests/Configuration/UpstreamHeaderTemplatePatternCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/UpstreamHeaderTemplatePatternCreatorTests.cs @@ -15,140 +15,31 @@ public UpstreamHeaderTemplatePatternCreatorTests() _creator = new(); } - [Fact] - public void Should_create_pattern_without_placeholders() + [Theory(DisplayName = "Should create pattern")] + [InlineData("country", "a text without placeholders", "^(?i)a text without placeholders$", " without placeholders")] + [InlineData("country", "a text without placeholders", "^a text without placeholders$", " Route is case sensitive", true)] + [InlineData("country", "{header:start}rest of the text", "^(?i)(?.+)rest of the text$", " with placeholder in the beginning")] + [InlineData("country", "rest of the text{header:end}", "^(?i)rest of the text(?.+)$", " with placeholder at the end")] + [InlineData("country", "{header:countrycode}", "^(?i)(?.+)$", " with placeholder only")] + [InlineData("country", "any text {header:cc} and other {header:version} and {header:bob} the end", "^(?i)any text (?.+) and other (?.+) and (?.+) the end$", " with more placeholders")] + public void Create_WithUpstreamHeaderTemplates_ShouldCreatePattern(string key, string template, string expected, string withMessage, bool? isCaseSensitive = null) { // Arrange var fileRoute = new FileRoute { + RouteIsCaseSensitive = isCaseSensitive ?? false, UpstreamHeaderTemplates = new Dictionary { - ["country"] = "a text without placeholders", + [key] = template, }, }; - GivenTheFollowingFileRoute(fileRoute); // Act - WhenICreateTheTemplatePattern(); + var actual = _creator.Create(fileRoute); // Assert - ThenTheFollowingIsReturned("country", "^(?i)a text without placeholders$"); - } - - [Fact] - public void Should_create_pattern_case_sensitive() - { - // Arrange - var fileRoute = new FileRoute - { - RouteIsCaseSensitive = true, - UpstreamHeaderTemplates = new Dictionary - { - ["country"] = "a text without placeholders", - }, - }; - GivenTheFollowingFileRoute(fileRoute); - - // Act - WhenICreateTheTemplatePattern(); - - // Assert - ThenTheFollowingIsReturned("country", "^a text without placeholders$"); - } - - [Fact] - public void Should_create_pattern_with_placeholder_in_the_beginning() - { - // Arrange - var fileRoute = new FileRoute - { - UpstreamHeaderTemplates = new Dictionary - { - ["country"] = "{header:start}rest of the text", - }, - }; - GivenTheFollowingFileRoute(fileRoute); - - // Act - WhenICreateTheTemplatePattern(); - - // Assert - ThenTheFollowingIsReturned("country", "^(?i)(?.+)rest of the text$"); - } - - [Fact] - public void Should_create_pattern_with_placeholder_at_the_end() - { - // Arrange - var fileRoute = new FileRoute - { - UpstreamHeaderTemplates = new Dictionary - { - ["country"] = "rest of the text{header:end}", - }, - }; - GivenTheFollowingFileRoute(fileRoute); - - // Act - WhenICreateTheTemplatePattern(); - - // Assert - ThenTheFollowingIsReturned("country", "^(?i)rest of the text(?.+)$"); - } - - [Fact] - public void Should_create_pattern_with_placeholder_only() - { - // Arrange - var fileRoute = new FileRoute - { - UpstreamHeaderTemplates = new Dictionary - { - ["country"] = "{header:countrycode}", - }, - }; - - GivenTheFollowingFileRoute(fileRoute); - - // Act - WhenICreateTheTemplatePattern(); - - // Assert - ThenTheFollowingIsReturned("country", "^(?i)(?.+)$"); - } - - [Fact] - public void Should_crate_pattern_with_more_placeholders() - { - // Arrange - var fileRoute = new FileRoute - { - UpstreamHeaderTemplates = new Dictionary - { - ["country"] = "any text {header:cc} and other {header:version} and {header:bob} the end", - }, - }; - GivenTheFollowingFileRoute(fileRoute); - - // Act - WhenICreateTheTemplatePattern(); - - // Assert - ThenTheFollowingIsReturned("country", "^(?i)any text (?.+) and other (?.+) and (?.+) the end$"); - } - - private void GivenTheFollowingFileRoute(FileRoute fileRoute) - { - _fileRoute = fileRoute; - } - - private void WhenICreateTheTemplatePattern() - { - _result = _creator.Create(_fileRoute); - } - - private void ThenTheFollowingIsReturned(string headerKey, string expected) - { - _result[headerKey].Template.ShouldBe(expected); + var message = nameof(Create_WithUpstreamHeaderTemplates_ShouldCreatePattern).Replace('_', ' ') + withMessage; + actual[key].ShouldNotBeNull() + .Template.ShouldBe(expected, message); } } From 6a6a4173f91f85f03c2dce16d15c73b61825ccf1 Mon Sep 17 00:00:00 2001 From: Raman Maksimchuk Date: Mon, 15 Apr 2024 19:34:47 +0300 Subject: [PATCH 46/49] AAA pattern --- ...streamHeaderTemplatePatternCreatorTests.cs | 2 - ...eaderPlaceholderNameAndValueFinderTests.cs | 93 ++++++---- .../HeadersToHeaderTemplatesMatcherTests.cs | 160 ++++++++++-------- 3 files changed, 146 insertions(+), 109 deletions(-) diff --git a/test/Ocelot.UnitTests/Configuration/UpstreamHeaderTemplatePatternCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/UpstreamHeaderTemplatePatternCreatorTests.cs index cf408dc9d..013a288dd 100644 --- a/test/Ocelot.UnitTests/Configuration/UpstreamHeaderTemplatePatternCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/UpstreamHeaderTemplatePatternCreatorTests.cs @@ -6,9 +6,7 @@ namespace Ocelot.UnitTests.Configuration; public class UpstreamHeaderTemplatePatternCreatorTests { - private FileRoute _fileRoute; private readonly UpstreamHeaderTemplatePatternCreator _creator; - private IDictionary _result; public UpstreamHeaderTemplatePatternCreatorTests() { diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinderTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinderTests.cs index 6291c262d..ca34a10b1 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinderTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinderTests.cs @@ -4,7 +4,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder.HeaderMatcher; -public class HeaderPlaceholderNameAndValueFinderTests +public class HeaderPlaceholderNameAndValueFinderTests : UnitTest { private readonly IHeaderPlaceholderNameAndValueFinder _finder; private Dictionary _upstreamHeaders; @@ -19,20 +19,24 @@ public HeaderPlaceholderNameAndValueFinderTests() [Fact] public void Should_return_no_placeholders() { + // Arrange var upstreamHeaderTemplates = new Dictionary(); var upstreamHeaders = new Dictionary(); var expected = new List(); + GivenUpstreamHeaderTemplatesAre(upstreamHeaderTemplates); + GivenUpstreamHeadersAre(upstreamHeaders); - this.Given(x => x.GivenUpstreamHeaderTemplatesAre(upstreamHeaderTemplates)) - .And(x => x.GivenUpstreamHeadersAre(upstreamHeaders)) - .When(x => x.WhenICallFindPlaceholders()) - .Then(x => x.TheResultIs(expected)) - .BDDfy(); + // Act + WhenICallFindPlaceholders(); + + // Assert + TheResultIs(expected); } [Fact] public void Should_return_one_placeholder_with_value_when_no_other_text() { + // Arrange var upstreamHeaderTemplates = new Dictionary { ["country"] = new("^(?i)(?.+)$", "{header:countrycode}"), @@ -45,17 +49,20 @@ public void Should_return_one_placeholder_with_value_when_no_other_text() { new("{countrycode}", "PL"), }; + GivenUpstreamHeaderTemplatesAre(upstreamHeaderTemplates); + GivenUpstreamHeadersAre(upstreamHeaders); + + // Act + WhenICallFindPlaceholders(); - this.Given(x => x.GivenUpstreamHeaderTemplatesAre(upstreamHeaderTemplates)) - .And(x => x.GivenUpstreamHeadersAre(upstreamHeaders)) - .When(x => x.WhenICallFindPlaceholders()) - .Then(x => x.TheResultIs(expected)) - .BDDfy(); + // Assert + TheResultIs(expected); } [Fact] public void Should_return_one_placeholder_with_value_when_other_text_on_the_right() { + // Arrange var upstreamHeaderTemplates = new Dictionary { ["country"] = new("^(?.+)-V1$", "{header:countrycode}-V1"), @@ -68,17 +75,20 @@ public void Should_return_one_placeholder_with_value_when_other_text_on_the_righ { new("{countrycode}", "PL"), }; + GivenUpstreamHeaderTemplatesAre(upstreamHeaderTemplates); + GivenUpstreamHeadersAre(upstreamHeaders); - this.Given(x => x.GivenUpstreamHeaderTemplatesAre(upstreamHeaderTemplates)) - .And(x => x.GivenUpstreamHeadersAre(upstreamHeaders)) - .When(x => x.WhenICallFindPlaceholders()) - .Then(x => x.TheResultIs(expected)) - .BDDfy(); + // Act + WhenICallFindPlaceholders(); + + // Assert + TheResultIs(expected); } [Fact] public void Should_return_one_placeholder_with_value_when_other_text_on_the_left() { + // Arrange var upstreamHeaderTemplates = new Dictionary { ["country"] = new("^V1-(?.+)$", "V1-{header:countrycode}"), @@ -91,17 +101,20 @@ public void Should_return_one_placeholder_with_value_when_other_text_on_the_left { new("{countrycode}", "PL"), }; + GivenUpstreamHeaderTemplatesAre(upstreamHeaderTemplates); + GivenUpstreamHeadersAre(upstreamHeaders); + + // Act + WhenICallFindPlaceholders(); - this.Given(x => x.GivenUpstreamHeaderTemplatesAre(upstreamHeaderTemplates)) - .And(x => x.GivenUpstreamHeadersAre(upstreamHeaders)) - .When(x => x.WhenICallFindPlaceholders()) - .Then(x => x.TheResultIs(expected)) - .BDDfy(); + // Assert + TheResultIs(expected); } [Fact] public void Should_return_one_placeholder_with_value_when_other_texts_surrounding() { + // Arrange var upstreamHeaderTemplates = new Dictionary { ["country"] = new("^cc:(?.+)-V1$", "cc:{header:countrycode}-V1"), @@ -114,17 +127,20 @@ public void Should_return_one_placeholder_with_value_when_other_texts_surroundin { new("{countrycode}", "PL"), }; + GivenUpstreamHeaderTemplatesAre(upstreamHeaderTemplates); + GivenUpstreamHeadersAre(upstreamHeaders); - this.Given(x => x.GivenUpstreamHeaderTemplatesAre(upstreamHeaderTemplates)) - .And(x => x.GivenUpstreamHeadersAre(upstreamHeaders)) - .When(x => x.WhenICallFindPlaceholders()) - .Then(x => x.TheResultIs(expected)) - .BDDfy(); + // Act + WhenICallFindPlaceholders(); + + // Assert + TheResultIs(expected); } [Fact] public void Should_return_two_placeholders_with_text_between() { + // Arrange var upstreamHeaderTemplates = new Dictionary { ["countryAndVersion"] = new("^(?i)(?.+)-(?.+)$", "{header:countrycode}-{header:version}"), @@ -138,17 +154,20 @@ public void Should_return_two_placeholders_with_text_between() new("{countrycode}", "PL"), new("{version}", "v1"), }; + GivenUpstreamHeaderTemplatesAre(upstreamHeaderTemplates); + GivenUpstreamHeadersAre(upstreamHeaders); + + // Act + WhenICallFindPlaceholders(); - this.Given(x => x.GivenUpstreamHeaderTemplatesAre(upstreamHeaderTemplates)) - .And(x => x.GivenUpstreamHeadersAre(upstreamHeaders)) - .When(x => x.WhenICallFindPlaceholders()) - .Then(x => x.TheResultIs(expected)) - .BDDfy(); + // Assert + TheResultIs(expected); } [Fact] public void Should_return_placeholders_from_different_headers() { + // Arrange var upstreamHeaderTemplates = new Dictionary { ["country"] = new("^(?i)(?.+)$", "{header:countrycode}"), @@ -164,12 +183,14 @@ public void Should_return_placeholders_from_different_headers() new("{countrycode}", "PL"), new("{version}", "v1"), }; + GivenUpstreamHeaderTemplatesAre(upstreamHeaderTemplates); + GivenUpstreamHeadersAre(upstreamHeaders); + + // Act + WhenICallFindPlaceholders(); - this.Given(x => x.GivenUpstreamHeaderTemplatesAre(upstreamHeaderTemplates)) - .And(x => x.GivenUpstreamHeadersAre(upstreamHeaders)) - .When(x => x.WhenICallFindPlaceholders()) - .Then(x => x.TheResultIs(expected)) - .BDDfy(); + // Assert + TheResultIs(expected); } private void GivenUpstreamHeaderTemplatesAre(Dictionary upstreaHeaderTemplates) diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/HeaderMatcher/HeadersToHeaderTemplatesMatcherTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/HeaderMatcher/HeadersToHeaderTemplatesMatcherTests.cs index 7e00d8ab8..0b8a398a6 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/HeaderMatcher/HeadersToHeaderTemplatesMatcherTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/HeaderMatcher/HeadersToHeaderTemplatesMatcherTests.cs @@ -1,13 +1,9 @@ using Ocelot.DownstreamRouteFinder.HeaderMatcher; using Ocelot.Values; -using Shouldly; -using System.Collections.Generic; -using TestStack.BDDfy; -using Xunit; namespace Ocelot.UnitTests.DownstreamRouteFinder.HeaderMatcher; -public class HeadersToHeaderTemplatesMatcherTests +public class HeadersToHeaderTemplatesMatcherTests : UnitTest { private readonly IHeadersToHeaderTemplatesMatcher _headerMatcher; private Dictionary _upstreamHeaders; @@ -22,123 +18,136 @@ public HeadersToHeaderTemplatesMatcherTests() [Fact] public void Should_match_when_no_template_headers() { + // Arrange var upstreamHeaders = new Dictionary() { ["anyHeader"] = "anyHeaderValue", }; - var templateHeaders = new Dictionary(); + GivenIHaveUpstreamHeaders(upstreamHeaders); + GivenIHaveTemplateHeadersInRoute(templateHeaders); + + // Act + WhenIMatchTheHeaders(); - this.Given(x => x.GivenIHaveUpstreamHeaders(upstreamHeaders)) - .And(x => x.GivenIHaveTemplateHeadersInRoute(templateHeaders)) - .When(x => x.WhenIMatchTheHeaders()) - .Then(x => x.ThenTheResultIsTrue()) - .BDDfy(); + // Assert + ThenTheResultIsTrue(); } [Fact] public void Should_match_the_same_headers() { + // Arrange var upstreamHeaders = new Dictionary() { ["anyHeader"] = "anyHeaderValue", }; - var templateHeaders = new Dictionary() { ["anyHeader"] = new("^(?i)anyHeaderValue$", "anyHeaderValue"), }; + GivenIHaveUpstreamHeaders(upstreamHeaders); + GivenIHaveTemplateHeadersInRoute(templateHeaders); - this.Given(x => x.GivenIHaveUpstreamHeaders(upstreamHeaders)) - .And(x => x.GivenIHaveTemplateHeadersInRoute(templateHeaders)) - .When(x => x.WhenIMatchTheHeaders()) - .Then(x => x.ThenTheResultIsTrue()) - .BDDfy(); + // Act + WhenIMatchTheHeaders(); + + // Assert + ThenTheResultIsTrue(); } [Fact] public void Should_not_match_the_same_headers_when_differ_case_and_case_sensitive() { + // Arrange var upstreamHeaders = new Dictionary() { ["anyHeader"] = "ANYHEADERVALUE", }; - var templateHeaders = new Dictionary() { ["anyHeader"] = new("^anyHeaderValue$", "anyHeaderValue"), }; + GivenIHaveUpstreamHeaders(upstreamHeaders); + GivenIHaveTemplateHeadersInRoute(templateHeaders); + + // Act + WhenIMatchTheHeaders(); - this.Given(x => x.GivenIHaveUpstreamHeaders(upstreamHeaders)) - .And(x => x.GivenIHaveTemplateHeadersInRoute(templateHeaders)) - .When(x => x.WhenIMatchTheHeaders()) - .Then(x => x.ThenTheResultIsFalse()) - .BDDfy(); + // Assert + ThenTheResultIsFalse(); } [Fact] public void Should_match_the_same_headers_when_differ_case_and_case_insensitive() { + // Arrange var upstreamHeaders = new Dictionary() { ["anyHeader"] = "ANYHEADERVALUE", }; - var templateHeaders = new Dictionary() { ["anyHeader"] = new("^(?i)anyHeaderValue$", "anyHeaderValue"), }; + GivenIHaveUpstreamHeaders(upstreamHeaders); + GivenIHaveTemplateHeadersInRoute(templateHeaders); + + // Act + WhenIMatchTheHeaders(); - this.Given(x => x.GivenIHaveUpstreamHeaders(upstreamHeaders)) - .And(x => x.GivenIHaveTemplateHeadersInRoute(templateHeaders)) - .When(x => x.WhenIMatchTheHeaders()) - .Then(x => x.ThenTheResultIsTrue()) - .BDDfy(); + // Assert + ThenTheResultIsTrue(); } [Fact] public void Should_not_match_different_headers_values() { + // Arrange var upstreamHeaders = new Dictionary() { ["anyHeader"] = "anyHeaderValueDifferent", }; - var templateHeaders = new Dictionary() { ["anyHeader"] = new("^(?i)anyHeaderValue$", "anyHeaderValue"), }; + GivenIHaveUpstreamHeaders(upstreamHeaders); + GivenIHaveTemplateHeadersInRoute(templateHeaders); - this.Given(x => x.GivenIHaveUpstreamHeaders(upstreamHeaders)) - .And(x => x.GivenIHaveTemplateHeadersInRoute(templateHeaders)) - .When(x => x.WhenIMatchTheHeaders()) - .Then(x => x.ThenTheResultIsFalse()) - .BDDfy(); + // Act + WhenIMatchTheHeaders(); + + // Assert + ThenTheResultIsFalse(); } [Fact] public void Should_not_match_the_same_headers_names() { + // Arrange var upstreamHeaders = new Dictionary() { ["anyHeaderDifferent"] = "anyHeaderValue", }; - var templateHeaders = new Dictionary() { ["anyHeader"] = new("^(?i)anyHeaderValue$", "anyHeaderValue"), }; + GivenIHaveUpstreamHeaders(upstreamHeaders); + GivenIHaveTemplateHeadersInRoute(templateHeaders); - this.Given(x => x.GivenIHaveUpstreamHeaders(upstreamHeaders)) - .And(x => x.GivenIHaveTemplateHeadersInRoute(templateHeaders)) - .When(x => x.WhenIMatchTheHeaders()) - .Then(x => x.ThenTheResultIsFalse()) - .BDDfy(); + // Act + WhenIMatchTheHeaders(); + + // Assert + ThenTheResultIsFalse(); } [Fact] public void Should_match_all_the_same_headers() { + // Arrange var upstreamHeaders = new Dictionary() { ["anyHeader"] = "anyHeaderValue", @@ -146,24 +155,26 @@ public void Should_match_all_the_same_headers() ["secondHeader"] = "secondHeaderValue", ["thirdHeader"] = "thirdHeaderValue", }; - var templateHeaders = new Dictionary() { ["secondHeader"] = new("^(?i)secondHeaderValue$", "secondHeaderValue"), ["thirdHeader"] = new("^(?i)thirdHeaderValue$", "thirdHeaderValue"), ["anyHeader"] = new("^(?i)anyHeaderValue$", "anyHeaderValue"), }; + GivenIHaveUpstreamHeaders(upstreamHeaders); + GivenIHaveTemplateHeadersInRoute(templateHeaders); + + // Act + WhenIMatchTheHeaders(); - this.Given(x => x.GivenIHaveUpstreamHeaders(upstreamHeaders)) - .And(x => x.GivenIHaveTemplateHeadersInRoute(templateHeaders)) - .When(x => x.WhenIMatchTheHeaders()) - .Then(x => x.ThenTheResultIsTrue()) - .BDDfy(); + // Assert + ThenTheResultIsTrue(); } [Fact] public void Should_not_match_the_headers_when_one_of_them_different() { + // Arrange var upstreamHeaders = new Dictionary() { ["anyHeader"] = "anyHeaderValue", @@ -171,79 +182,86 @@ public void Should_not_match_the_headers_when_one_of_them_different() ["secondHeader"] = "secondHeaderValueDIFFERENT", ["thirdHeader"] = "thirdHeaderValue", }; - var templateHeaders = new Dictionary() { ["secondHeader"] = new("^(?i)secondHeaderValue$", "secondHeaderValue"), ["thirdHeader"] = new("^(?i)thirdHeaderValue$", "thirdHeaderValue"), ["anyHeader"] = new("^(?i)anyHeaderValue$", "anyHeaderValue"), }; + GivenIHaveUpstreamHeaders(upstreamHeaders); + GivenIHaveTemplateHeadersInRoute(templateHeaders); + + // Act + WhenIMatchTheHeaders(); - this.Given(x => x.GivenIHaveUpstreamHeaders(upstreamHeaders)) - .And(x => x.GivenIHaveTemplateHeadersInRoute(templateHeaders)) - .When(x => x.WhenIMatchTheHeaders()) - .Then(x => x.ThenTheResultIsFalse()) - .BDDfy(); + // Assert + ThenTheResultIsFalse(); } [Fact] public void Should_match_the_header_with_placeholder() { + // Arrange var upstreamHeaders = new Dictionary() { ["anyHeader"] = "PL", }; - var templateHeaders = new Dictionary() { ["anyHeader"] = new("^(?i)(?.+)$", "{header:countrycode}"), }; + GivenIHaveUpstreamHeaders(upstreamHeaders); + GivenIHaveTemplateHeadersInRoute(templateHeaders); - this.Given(x => x.GivenIHaveUpstreamHeaders(upstreamHeaders)) - .And(x => x.GivenIHaveTemplateHeadersInRoute(templateHeaders)) - .When(x => x.WhenIMatchTheHeaders()) - .Then(x => x.ThenTheResultIsTrue()) - .BDDfy(); + // Act + WhenIMatchTheHeaders(); + + // Assert + ThenTheResultIsTrue(); } [Fact] public void Should_match_the_header_with_placeholders() { + // Arrange var upstreamHeaders = new Dictionary() { ["anyHeader"] = "PL-V1", }; - var templateHeaders = new Dictionary() { ["anyHeader"] = new("^(?i)(?.+)-(?.+)$", "{header:countrycode}-{header:version}"), }; + GivenIHaveUpstreamHeaders(upstreamHeaders); + GivenIHaveTemplateHeadersInRoute(templateHeaders); + + // Act + WhenIMatchTheHeaders(); - this.Given(x => x.GivenIHaveUpstreamHeaders(upstreamHeaders)) - .And(x => x.GivenIHaveTemplateHeadersInRoute(templateHeaders)) - .When(x => x.WhenIMatchTheHeaders()) - .Then(x => x.ThenTheResultIsTrue()) - .BDDfy(); + // Assert + ThenTheResultIsTrue(); } [Fact] public void Should_not_match_the_header_with_placeholders() { + // Arrange var upstreamHeaders = new Dictionary() { ["anyHeader"] = "PL", }; - var templateHeaders = new Dictionary() { ["anyHeader"] = new("^(?i)(?.+)-(?.+)$", "{header:countrycode}-{header:version}"), }; + GivenIHaveUpstreamHeaders(upstreamHeaders); + GivenIHaveTemplateHeadersInRoute(templateHeaders); + + // Act + WhenIMatchTheHeaders(); - this.Given(x => x.GivenIHaveUpstreamHeaders(upstreamHeaders)) - .And(x => x.GivenIHaveTemplateHeadersInRoute(templateHeaders)) - .When(x => x.WhenIMatchTheHeaders()) - .Then(x => x.ThenTheResultIsFalse()) - .BDDfy(); + // Assert + ThenTheResultIsFalse(); } private void GivenIHaveUpstreamHeaders(Dictionary upstreamHeaders) From 75c9d850fda425362d138502cb0adcf5fedefa00 Mon Sep 17 00:00:00 2001 From: Raman Maksimchuk Date: Mon, 15 Apr 2024 19:55:36 +0300 Subject: [PATCH 47/49] Add traits --- .../Routing/RoutingBasedOnHeadersTests.cs | 2 ++ .../UpstreamHeaderTemplatePatternCreatorTests.cs | 2 ++ .../Validation/FileConfigurationFluentValidatorTests.cs | 8 ++++++++ .../DownstreamRouteFinder/DownstreamRouteFinderTests.cs | 4 ++++ .../HeaderPlaceholderNameAndValueFinderTests.cs | 2 ++ .../HeaderMatcher/HeadersToHeaderTemplatesMatcherTests.cs | 2 ++ 6 files changed, 20 insertions(+) diff --git a/test/Ocelot.AcceptanceTests/Routing/RoutingBasedOnHeadersTests.cs b/test/Ocelot.AcceptanceTests/Routing/RoutingBasedOnHeadersTests.cs index 790a3f88c..eea54640f 100644 --- a/test/Ocelot.AcceptanceTests/Routing/RoutingBasedOnHeadersTests.cs +++ b/test/Ocelot.AcceptanceTests/Routing/RoutingBasedOnHeadersTests.cs @@ -3,6 +3,8 @@ namespace Ocelot.AcceptanceTests.Routing; +[Trait("PR", "1312")] +[Trait("Feat", "360")] public sealed class RoutingBasedOnHeadersTests : Steps, IDisposable { private string _downstreamPath; diff --git a/test/Ocelot.UnitTests/Configuration/UpstreamHeaderTemplatePatternCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/UpstreamHeaderTemplatePatternCreatorTests.cs index 013a288dd..49cf841ac 100644 --- a/test/Ocelot.UnitTests/Configuration/UpstreamHeaderTemplatePatternCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/UpstreamHeaderTemplatePatternCreatorTests.cs @@ -13,6 +13,8 @@ public UpstreamHeaderTemplatePatternCreatorTests() _creator = new(); } + [Trait("PR", "1312")] + [Trait("Feat", "360")] [Theory(DisplayName = "Should create pattern")] [InlineData("country", "a text without placeholders", "^(?i)a text without placeholders$", " without placeholders")] [InlineData("country", "a text without placeholders", "^a text without placeholders$", " Route is case sensitive", true)] diff --git a/test/Ocelot.UnitTests/Configuration/Validation/FileConfigurationFluentValidatorTests.cs b/test/Ocelot.UnitTests/Configuration/Validation/FileConfigurationFluentValidatorTests.cs index 6c620cf62..f3998c878 100644 --- a/test/Ocelot.UnitTests/Configuration/Validation/FileConfigurationFluentValidatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/Validation/FileConfigurationFluentValidatorTests.cs @@ -721,6 +721,8 @@ public void Configuration_is_not_valid_when_host_and_port_is_empty() } [Fact] + [Trait("PR", "1312")] + [Trait("Feat", "360")] public void Configuration_is_not_valid_when_upstream_headers_the_same() { // Arrange @@ -745,6 +747,8 @@ public void Configuration_is_not_valid_when_upstream_headers_the_same() } [Fact] + [Trait("PR", "1312")] + [Trait("Feat", "360")] public void Configuration_is_valid_when_upstream_headers_not_the_same() { // Arrange @@ -768,6 +772,8 @@ public void Configuration_is_valid_when_upstream_headers_not_the_same() } [Fact] + [Trait("PR", "1312")] + [Trait("Feat", "360")] public void Configuration_is_valid_when_upstream_headers_count_not_the_same() { // Arrange @@ -790,6 +796,8 @@ public void Configuration_is_valid_when_upstream_headers_count_not_the_same() } [Fact] + [Trait("PR", "1312")] + [Trait("Feat", "360")] public void Configuration_is_valid_when_one_upstream_headers_empty_and_other_not_empty() { // Arrange diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs index ab880d06c..ec6026a5b 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs @@ -716,6 +716,8 @@ public void should_return_route_when_host_matches_but_null_host_on_same_path_fir } [Fact] + [Trait("PR", "1312")] + [Trait("Feat", "360")] public void Should_return_route_when_upstream_headers_match() { // Arrange @@ -778,6 +780,8 @@ public void Should_return_route_when_upstream_headers_match() } [Fact] + [Trait("PR", "1312")] + [Trait("Feat", "360")] public void Should_not_return_route_when_upstream_headers_dont_match() { // Arrange diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinderTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinderTests.cs index ca34a10b1..b1a767602 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinderTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/HeaderMatcher/HeaderPlaceholderNameAndValueFinderTests.cs @@ -4,6 +4,8 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder.HeaderMatcher; +[Trait("PR", "1312")] +[Trait("Feat", "360")] public class HeaderPlaceholderNameAndValueFinderTests : UnitTest { private readonly IHeaderPlaceholderNameAndValueFinder _finder; diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/HeaderMatcher/HeadersToHeaderTemplatesMatcherTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/HeaderMatcher/HeadersToHeaderTemplatesMatcherTests.cs index 0b8a398a6..fae3cc0c5 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/HeaderMatcher/HeadersToHeaderTemplatesMatcherTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/HeaderMatcher/HeadersToHeaderTemplatesMatcherTests.cs @@ -3,6 +3,8 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder.HeaderMatcher; +[Trait("PR", "1312")] +[Trait("Feat", "360")] public class HeadersToHeaderTemplatesMatcherTests : UnitTest { private readonly IHeadersToHeaderTemplatesMatcher _headerMatcher; From 57e2fdbdf121f7e9493fb4b473e5398c61d63fed Mon Sep 17 00:00:00 2001 From: Raman Maksimchuk Date: Mon, 15 Apr 2024 20:24:20 +0300 Subject: [PATCH 48/49] Update routing.rst Check grammar and style --- docs/features/routing.rst | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/features/routing.rst b/docs/features/routing.rst index 2387020ed..7d9826c12 100644 --- a/docs/features/routing.rst +++ b/docs/features/routing.rst @@ -155,11 +155,10 @@ If you do not set **UpstreamHost** on a Route then any ``Host`` header will matc This means that if you have two Routes that are the same, apart from the **UpstreamHost**, where one is null and the other set Ocelot will favour the one that has been set. Upstream Headers -^^^^^^^^^^^^^^^^ +---------------- -Additionally to routing by UpstreamPathTemplate, you can define UpstreamHeaderTemplates. Then to match a route, all the headers from this section must exist in the request headers. - -The example: +In addition to routing by ``UpstreamPathTemplate``, you can also define ``UpstreamHeaderTemplates``. +For a route to match, all headers specified in this section must be present in the request headers. .. code-block:: json @@ -174,20 +173,20 @@ The example: ], "UpstreamPathTemplate": "/", "UpstreamHttpMethod": [ "Get" ], - "UpstreamHeaderTemplates": { + "UpstreamHeaderTemplates": { // ! "country": "uk", "version": "v1" } } -In this case the route will be matched only if a request has both headers set to the configured values. +In this scenario, the route will only match if a request includes both headers with the specified values. -You can use placeholders in your UpstreamHeaderTemplates in the following way: +Placeholders can be utilized in your ``UpstreamHeaderTemplates`` as follows: .. code-block:: json { - "DownstreamPathTemplate": "/{versionnumber}/api", + "DownstreamPathTemplate": "/{versionnumber}/api", // ! "DownstreamScheme": "https", "DownstreamHostAndPorts": [ { @@ -197,18 +196,19 @@ You can use placeholders in your UpstreamHeaderTemplates in the following way: ], "UpstreamPathTemplate": "/api", "UpstreamHttpMethod": [ "Get" ], - "UpstreamHeaderTemplates": { + "UpstreamHeaderTemplates": { // !! "version": "{header:versionnumber}" } } -In this case the whole value for the request header "version" will be placed into the DownstreamPathTemplate. If you need, you can specify more complex upstream header template with placeholders like e.g. "version-{header:version}_country-{header:country}". - -Placeholders do not have to exist in DownstreamPathTemplate. This case may be used to require specific header no matter what the value it contains. +In this scenario, the entire value of the request header "**version**" is inserted into the ``DownstreamPathTemplate``. +If necessary, a more intricate upstream header template can be specified, using placeholders such as ``version-{header:version}_country-{header:country}``. -UpstreamHeaderTemplates section can be used also for Aggregates. +Placeholders are not required in ``DownstreamPathTemplate``. +This scenario can be utilized to mandate a specific header regardless of its value. +Additionally, the ``UpstreamHeaderTemplates`` section is applicable for Aggregates as well. -This feature was requested in `Issue 360 `_ . +This feature was proposed in Issue 360. Priority -------- From dc5a6718eecddd2b0d5d860614f7bf5ffc687c38 Mon Sep 17 00:00:00 2001 From: Raman Maksimchuk Date: Mon, 15 Apr 2024 21:34:45 +0300 Subject: [PATCH 49/49] Update docs --- docs/features/configuration.rst | 10 +++-- docs/features/routing.rst | 79 ++++++++++++++++----------------- 2 files changed, 44 insertions(+), 45 deletions(-) diff --git a/docs/features/configuration.rst b/docs/features/configuration.rst index 19d669d38..d85e1f511 100644 --- a/docs/features/configuration.rst +++ b/docs/features/configuration.rst @@ -19,12 +19,13 @@ Here is an example Route configuration. You don't need to set all of these thing .. code-block:: json { - "DownstreamPathTemplate": "/", "UpstreamPathTemplate": "/", + "UpstreamHeaderTemplates": {}, // dictionary + "UpstreamHost": "", "UpstreamHttpMethod": [ "Get" ], + "DownstreamPathTemplate": "/", "DownstreamHttpMethod": "", "DownstreamHttpVersion": "", - "UpstreamHeaderTemplates": {}, "AddHeadersToRequest": {}, "AddClaimsToRequest": {}, "RouteClaimsRequirement": {}, @@ -38,7 +39,7 @@ Here is an example Route configuration. You don't need to set all of these thing "ServiceName": "", "DownstreamScheme": "http", "DownstreamHostAndPorts": [ - { "Host": "localhost", "Port": 51876 } + { "Host": "localhost", "Port": 12345 } ], "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 0, @@ -71,7 +72,8 @@ Here is an example Route configuration. You don't need to set all of these thing } } -More information on how to use these options is below. +The actual Route schema for properties can be found in the C# `FileRoute `_ class. +If you're interested in learning more about how to utilize these options, read below! Multiple Environments --------------------- diff --git a/docs/features/routing.rst b/docs/features/routing.rst index 7d9826c12..e55b33e5c 100644 --- a/docs/features/routing.rst +++ b/docs/features/routing.rst @@ -154,61 +154,57 @@ The Route above will only be matched when the ``Host`` header value is ``somedom If you do not set **UpstreamHost** on a Route then any ``Host`` header will match it. This means that if you have two Routes that are the same, apart from the **UpstreamHost**, where one is null and the other set Ocelot will favour the one that has been set. -Upstream Headers ----------------- +.. _routing-upstream-headers: + +Upstream Headers [#f3]_ +----------------------- In addition to routing by ``UpstreamPathTemplate``, you can also define ``UpstreamHeaderTemplates``. -For a route to match, all headers specified in this section must be present in the request headers. +For a route to match, all headers specified in this dictionary object must be present in the request headers. .. code-block:: json - { - "DownstreamPathTemplate": "/", - "DownstreamScheme": "https", - "DownstreamHostAndPorts": [ - { - "Host": "10.0.10.1", - "Port": 80, - } - ], - "UpstreamPathTemplate": "/", - "UpstreamHttpMethod": [ "Get" ], - "UpstreamHeaderTemplates": { // ! - "country": "uk", - "version": "v1" - } + { + // ... + "UpstreamPathTemplate": "/", + "UpstreamHttpMethod": [ "Get" ], + "UpstreamHeaderTemplates": { // dictionary + "country": "uk", // 1st header + "version": "v1" // 2nd header } + } In this scenario, the route will only match if a request includes both headers with the specified values. -Placeholders can be utilized in your ``UpstreamHeaderTemplates`` as follows: +Header placeholders +^^^^^^^^^^^^^^^^^^^ + +Let's explore a more intriguing scenario where placeholders can be effectively utilized within your ``UpstreamHeaderTemplates``. + +Consider the following approach using the special placeholder format ``{header:placeholdername}``: .. code-block:: json - { - "DownstreamPathTemplate": "/{versionnumber}/api", // ! - "DownstreamScheme": "https", - "DownstreamHostAndPorts": [ - { - "Host": "10.0.10.1", - "Port": 80, - } - ], - "UpstreamPathTemplate": "/api", - "UpstreamHttpMethod": [ "Get" ], - "UpstreamHeaderTemplates": { // !! - "version": "{header:versionnumber}" - } + { + "DownstreamPathTemplate": "/{versionnumber}/api", // with placeholder + "DownstreamScheme": "https", + "DownstreamHostAndPorts": [ + { "Host": "10.0.10.1", "Port": 80 } + ], + "UpstreamPathTemplate": "/api", + "UpstreamHttpMethod": [ "Get" ], + "UpstreamHeaderTemplates": { + "version": "{header:versionnumber}" // 'header:' prefix vs placeholder } + } In this scenario, the entire value of the request header "**version**" is inserted into the ``DownstreamPathTemplate``. If necessary, a more intricate upstream header template can be specified, using placeholders such as ``version-{header:version}_country-{header:country}``. -Placeholders are not required in ``DownstreamPathTemplate``. -This scenario can be utilized to mandate a specific header regardless of its value. -Additionally, the ``UpstreamHeaderTemplates`` section is applicable for Aggregates as well. + **Note 1**: Placeholders are not required in ``DownstreamPathTemplate``. + This scenario can be utilized to mandate a specific header regardless of its value. -This feature was proposed in Issue 360. + **Note 2**: Additionally, the ``UpstreamHeaderTemplates`` dictionary options are applicable for :doc:`../features/requestaggregation` as well. Priority -------- @@ -350,7 +346,7 @@ Here are two user scenarios. .. _routing-security-options: -Security Options [#f3]_ +Security Options [#f4]_ ----------------------- Ocelot allows you to manage multiple patterns for allowed/blocked IPs using the `IPAddressRange `_ package @@ -382,7 +378,7 @@ The current patterns managed are the following: .. _routing-dynamic: -Dynamic Routing [#f4]_ +Dynamic Routing [#f5]_ ---------------------- The idea is to enable dynamic routing when using a :doc:`../features/servicediscovery` provider so you don't have to provide the Route config. @@ -392,5 +388,6 @@ See the :ref:`sd-dynamic-routing` docs if this sounds interesting to you. .. [#f1] ":ref:`routing-empty-placeholders`" feature is available starting in version `23.0 `_, see issue `748 `_ and the `23.0 `__ release notes for details. .. [#f2] ":ref:`routing-upstream-host`" feature was requested as part of `issue 216 `_. -.. [#f3] ":ref:`routing-security-options`" feature was requested as part of `issue 628 `_ (of `12.0.1 `_ version), then redesigned and improved by `issue 1400 `_, and published in version `20.0 `_ docs. -.. [#f4] ":ref:`routing-dynamic`" feature was requested as part of `issue 340 `_. Complete reference: :ref:`sd-dynamic-routing`. +.. [#f3] ":ref:`routing-upstream-headers`" feature was proposed in `issue 360 `_, and released in version `24.0 `_. +.. [#f4] ":ref:`routing-security-options`" feature was requested as part of `issue 628 `_ (of `12.0.1 `_ version), then redesigned and improved by `issue 1400 `_, and published in version `20.0 `_ docs. +.. [#f5] ":ref:`routing-dynamic`" feature was requested as part of `issue 340 `_. Complete reference: :ref:`sd-dynamic-routing`.