using System; using System.Collections.Generic; using System.IO; using System.Net; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Ocelot.Configuration.File; using Shouldly; using TestStack.BDDfy; using Xunit; namespace Ocelot.AcceptanceTests { public class RoutingTests : IDisposable { private IWebHost _builder; private readonly Steps _steps; private string _downstreamPath; public RoutingTests() { _steps = new Steps(); } [Fact] public void should_return_response_404_when_no_configuration_at_all() { this.Given(x => _steps.GivenThereIsAConfiguration(new FileConfiguration())) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.NotFound)) .BDDfy(); } [Fact] public void should_return_response_200_with_simple_url() { var configuration = new FileConfiguration { ReRoutes = new List { new FileReRoute { DownstreamPathTemplate = "/", DownstreamScheme = "http", DownstreamHost = "localhost", DownstreamPort = 51879, UpstreamPathTemplate = "/", UpstreamHttpMethod = new List { "Get" }, } } }; this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", 200, "Hello from Laura")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) .BDDfy(); } [Fact] public void should_return_response_200_when_path_missing_forward_slash_as_first_char() { var configuration = new FileConfiguration { ReRoutes = new List { new FileReRoute { DownstreamPathTemplate = "api/products", DownstreamScheme = "http", DownstreamHost = "localhost", DownstreamPort = 51879, UpstreamPathTemplate = "/", UpstreamHttpMethod = new List { "Get" }, } } }; this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879/api/products", 200, "Hello from Laura")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) .BDDfy(); } [Fact] public void should_return_response_200_when_host_has_trailing_slash() { var configuration = new FileConfiguration { ReRoutes = new List { new FileReRoute { DownstreamPathTemplate = "/api/products", DownstreamScheme = "http", DownstreamHost = "localhost/", DownstreamPort = 51879, UpstreamPathTemplate = "/", UpstreamHttpMethod = new List { "Get" }, } } }; this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879/api/products", 200, "Hello from Laura")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) .BDDfy(); } [Fact] public void should_not_care_about_no_trailing() { var configuration = new FileConfiguration { ReRoutes = new List { new FileReRoute { DownstreamPathTemplate = "/products", DownstreamScheme = "http", DownstreamHost = "localhost", DownstreamPort = 51879, UpstreamPathTemplate = "/products/", UpstreamHttpMethod = new List { "Get" }, } } }; this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879/products", 200, "Hello from Laura")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/products")) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) .BDDfy(); } [Fact] public void should_not_care_about_trailing() { var configuration = new FileConfiguration { ReRoutes = new List { new FileReRoute { DownstreamPathTemplate = "/products", DownstreamScheme = "http", DownstreamHost = "localhost", DownstreamPort = 51879, UpstreamPathTemplate = "/products", UpstreamHttpMethod = new List { "Get" }, } } }; this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879/products", 200, "Hello from Laura")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/products/")) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) .BDDfy(); } [Fact] public void should_return_not_found() { var configuration = new FileConfiguration { ReRoutes = new List { new FileReRoute { DownstreamPathTemplate = "/products", DownstreamScheme = "http", DownstreamHost = "localhost", DownstreamPort = 51879, UpstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = new List { "Get" }, QoSOptions = new FileQoSOptions() { ExceptionsAllowedBeforeBreaking = 3, DurationOfBreak = 5, TimeoutValue = 5000 } } } }; this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879/products", 200, "Hello from Laura")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/products/")) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.NotFound)) .BDDfy(); } [Fact] public void should_return_response_200_with_complex_url() { var configuration = new FileConfiguration { ReRoutes = new List { new FileReRoute { DownstreamPathTemplate = "/api/products/{productId}", DownstreamScheme = "http", DownstreamHost = "localhost", DownstreamPort = 51879, UpstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = new List { "Get" }, } } }; this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879/api/products/1", 200, "Some Product")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/products/1")) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) .And(x => _steps.ThenTheResponseBodyShouldBe("Some Product")) .BDDfy(); } [Fact] public void should_not_add_trailing_slash_to_downstream_url() { var configuration = new FileConfiguration { ReRoutes = new List { new FileReRoute { DownstreamPathTemplate = "/api/products/{productId}", DownstreamScheme = "http", DownstreamHost = "localhost", DownstreamPort = 51879, UpstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = new List { "Get" }, } } }; this.Given(x => GivenThereIsAServiceRunningOn("http://localhost:51879/api/products/1", 200, "Some Product")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/products/1")) .Then(x => ThenTheDownstreamUrlPathShouldBe("/api/products/1")) .BDDfy(); } [Fact] public void should_return_response_201_with_simple_url() { var configuration = new FileConfiguration { ReRoutes = new List { new FileReRoute { DownstreamPathTemplate = "/", DownstreamHost = "localhost", DownstreamPort = 51879, DownstreamScheme = "http", UpstreamPathTemplate = "/", UpstreamHttpMethod = new List { "Post" }, } } }; this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", 201, string.Empty)) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .And(x => _steps.GivenThePostHasContent("postContent")) .When(x => _steps.WhenIPostUrlOnTheApiGateway("/")) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.Created)) .BDDfy(); } [Fact] public void should_return_response_201_with_complex_query_string() { var configuration = new FileConfiguration { ReRoutes = new List { new FileReRoute { DownstreamPathTemplate = "/newThing", UpstreamPathTemplate = "/newThing", DownstreamScheme = "http", DownstreamHost = "localhost", DownstreamPort = 51879, UpstreamHttpMethod = new List { "Get" }, } } }; this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", 200, "Hello from Laura")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/newThing?DeviceType=IphoneApp&Browser=moonpigIphone&BrowserString=-&CountryCode=123&DeviceName=iPhone 5 (GSM+CDMA)&OperatingSystem=iPhone OS 7.1.2&BrowserVersion=3708AdHoc&ipAddress=-")) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) .BDDfy(); } [Fact] public void should_return_response_200_with_placeholder_for_final_url_path() { var configuration = new FileConfiguration { ReRoutes = new List { new FileReRoute { DownstreamPathTemplate = "/api/{urlPath}", DownstreamScheme = "http", DownstreamHost = "localhost", DownstreamPort = 51879, UpstreamPathTemplate = "/myApp1Name/api/{urlPath}", UpstreamHttpMethod = new List { "Get" }, } } }; this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879/myApp1Name/api/products/1", 200, "Some Product")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/myApp1Name/api/products/1")) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) .And(x => _steps.ThenTheResponseBodyShouldBe("Some Product")) .BDDfy(); } [Fact] public void should_return_response_201_with_simple_url_and_multiple_upstream_http_method() { var configuration = new FileConfiguration { ReRoutes = new List { new FileReRoute { DownstreamPathTemplate = "/", DownstreamHost = "localhost", DownstreamPort = 51879, DownstreamScheme = "http", UpstreamPathTemplate = "/", UpstreamHttpMethod = new List { "Get", "Post" }, } } }; this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", 201, string.Empty)) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .And(x => _steps.GivenThePostHasContent("postContent")) .When(x => _steps.WhenIPostUrlOnTheApiGateway("/")) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.Created)) .BDDfy(); } [Fact] public void should_return_response_200_with_simple_url_and_any_upstream_http_method() { var configuration = new FileConfiguration { ReRoutes = new List { new FileReRoute { DownstreamPathTemplate = "/", DownstreamHost = "localhost", DownstreamPort = 51879, DownstreamScheme = "http", UpstreamPathTemplate = "/", UpstreamHttpMethod = new List(), } } }; this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", 200, "Hello from Laura")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) .BDDfy(); } private void GivenThereIsAServiceRunningOn(string url, int statusCode, string responseBody) { _builder = new WebHostBuilder() .UseUrls(url) .UseKestrel() .UseContentRoot(Directory.GetCurrentDirectory()) .UseIISIntegration() .Configure(app => { app.Run(async context => { _downstreamPath = context.Request.PathBase.Value; context.Response.StatusCode = statusCode; await context.Response.WriteAsync(responseBody); }); }) .Build(); _builder.Start(); } internal void ThenTheDownstreamUrlPathShouldBe(string expectedDownstreamPath) { _downstreamPath.ShouldBe(expectedDownstreamPath); } public void Dispose() { _builder?.Dispose(); _steps.Dispose(); } } }