tests to handle some error cases and docs

This commit is contained in:
TomPallister
2020-04-13 12:05:55 +01:00
parent b300ed9aec
commit c9483cdad6
9 changed files with 281 additions and 5 deletions

View File

@ -6,7 +6,13 @@ namespace Ocelot.AcceptanceTests
using Shouldly;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Configuration;
using Middleware;
using Responses;
using ServiceDiscovery.Providers;
using TestStack.BDDfy;
using Values;
using Xunit;
public class LoadBalancerTests : IDisposable
@ -122,6 +128,88 @@ namespace Ocelot.AcceptanceTests
.BDDfy();
}
[Fact]
public void should_load_balance_request_with_custom_load_balancer()
{
var downstreamPortOne = RandomPortFinder.GetRandomPort();
var downstreamPortTwo = RandomPortFinder.GetRandomPort();
var downstreamServiceOneUrl = $"http://localhost:{downstreamPortOne}";
var downstreamServiceTwoUrl = $"http://localhost:{downstreamPortTwo}";
var configuration = new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
DownstreamPathTemplate = "/",
DownstreamScheme = "http",
UpstreamPathTemplate = "/",
UpstreamHttpMethod = new List<string> { "Get" },
LoadBalancerOptions = new FileLoadBalancerOptions { Type = nameof(CustomLoadBalancer) },
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = "localhost",
Port = downstreamPortOne,
},
new FileHostAndPort
{
Host = "localhost",
Port = downstreamPortTwo,
},
},
},
},
GlobalConfiguration = new FileGlobalConfiguration(),
};
Func<IServiceProvider, DownstreamReRoute, IServiceDiscoveryProvider, CustomLoadBalancer> loadBalancerFactoryFunc = (serviceProvider, reRoute, serviceDiscoveryProvider) => new CustomLoadBalancer(serviceDiscoveryProvider.Get);
this.Given(x => x.GivenProductServiceOneIsRunning(downstreamServiceOneUrl, 200))
.And(x => x.GivenProductServiceTwoIsRunning(downstreamServiceTwoUrl, 200))
.And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunningWithCustomLoadBalancer(loadBalancerFactoryFunc))
.When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimes("/", 50))
.Then(x => x.ThenTheTwoServicesShouldHaveBeenCalledTimes(50))
.And(x => x.ThenBothServicesCalledRealisticAmountOfTimes(24, 26))
.BDDfy();
}
private class CustomLoadBalancer : ILoadBalancer
{
private readonly Func<Task<List<Service>>> _services;
private readonly object _lock = new object();
private int _last;
public CustomLoadBalancer(Func<Task<List<Service>>> services)
{
_services = services;
}
public async Task<Response<ServiceHostAndPort>> Lease(DownstreamContext downstreamContext)
{
var services = await _services();
lock (_lock)
{
if (_last >= services.Count)
{
_last = 0;
}
var next = services[_last];
_last++;
return new OkResponse<ServiceHostAndPort>(next.HostAndPort);
}
}
public void Release(ServiceHostAndPort hostAndPort)
{
}
}
private void ThenBothServicesCalledRealisticAmountOfTimes(int bottom, int top)
{
_counterOne.ShouldBeInRange(bottom, top);

View File

@ -39,6 +39,9 @@ namespace Ocelot.AcceptanceTests
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Configuration;
using LoadBalancer.LoadBalancers;
using ServiceDiscovery.Providers;
using static Ocelot.AcceptanceTests.HttpDelegatingHandlersTests;
using ConfigurationBuilder = Microsoft.Extensions.Configuration.ConfigurationBuilder;
using CookieHeaderValue = Microsoft.Net.Http.Headers.CookieHeaderValue;
@ -255,6 +258,39 @@ namespace Ocelot.AcceptanceTests
_ocelotClient = _ocelotServer.CreateClient();
}
/// <summary>
/// This is annoying cos it should be in the constructor but we need to set up the file before calling startup so its a step.
/// </summary>
public void GivenOcelotIsRunningWithCustomLoadBalancer<T>(Func<IServiceProvider, DownstreamReRoute, IServiceDiscoveryProvider, T> loadBalancerFactoryFunc)
where T : ILoadBalancer
{
_webHostBuilder = new WebHostBuilder();
_webHostBuilder
.ConfigureAppConfiguration((hostingContext, config) =>
{
config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath);
var env = hostingContext.HostingEnvironment;
config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: false);
config.AddJsonFile("ocelot.json", false, false);
config.AddEnvironmentVariables();
})
.ConfigureServices(s =>
{
s.AddOcelot()
.AddCustomLoadBalancer(loadBalancerFactoryFunc);
})
.Configure(app =>
{
app.UseOcelot().Wait();
});
_ocelotServer = new TestServer(_webHostBuilder);
_ocelotClient = _ocelotServer.CreateClient();
}
public void GivenOcelotIsRunningWithConsul()
{
_webHostBuilder = new WebHostBuilder();

View File

@ -69,7 +69,24 @@ namespace Ocelot.UnitTests.LoadBalancer
.Then(x => x.ThenTheLoadBalancerIsReturned<FakeLoadBalancerTwo>())
.BDDfy();
}
[Fact]
public void should_return_error_response_if_cannot_find_load_balancer_creator()
{
var reRoute = new DownstreamReRouteBuilder()
.WithLoadBalancerOptions(new LoadBalancerOptions("DoesntExistLoadBalancer", "", 0))
.WithUpstreamHttpMethod(new List<string> { "Get" })
.Build();
this.Given(x => x.GivenAReRoute(reRoute))
.And(x => GivenAServiceProviderConfig(new ServiceProviderConfigurationBuilder().Build()))
.And(x => x.GivenTheServiceProviderFactoryReturns())
.When(x => x.WhenIGetTheLoadBalancer())
.Then(x => x.ThenAnErrorResponseIsReturned())
.And(x => x.ThenTheErrorMessageIsCorrect())
.BDDfy();
}
[Fact]
public void should_call_service_provider()
{
@ -147,6 +164,11 @@ namespace Ocelot.UnitTests.LoadBalancer
_result.IsError.ShouldBeTrue();
}
private void ThenTheErrorMessageIsCorrect()
{
_result.Errors[0].Message.ShouldBe("Could not find load balancer creator for Type: DoesntExistLoadBalancer, please check your config specified the correct load balancer and that you have registered a class with the same name.");
}
private class FakeLoadBalancerCreator<T> : ILoadBalancerCreator
where T : ILoadBalancer, new()
{

View File

@ -47,6 +47,7 @@ namespace Ocelot.UnitTests.Responder
[Theory]
[InlineData(OcelotErrorCode.UnableToCompleteRequestError)]
[InlineData(OcelotErrorCode.CouldNotFindLoadBalancerCreator)]
public void should_return_internal_server_error(OcelotErrorCode errorCode)
{
ShouldMapErrorToStatusCode(errorCode, HttpStatusCode.InternalServerError);
@ -120,7 +121,7 @@ namespace Ocelot.UnitTests.Responder
var errors = new List<OcelotErrorCode>
{
OcelotErrorCode.CannotAddDataError,
OcelotErrorCode.RequestTimedOutError
OcelotErrorCode.RequestTimedOutError,
};
ShouldMapErrorsToStatusCode(errors, HttpStatusCode.ServiceUnavailable);
@ -132,7 +133,7 @@ namespace Ocelot.UnitTests.Responder
// If this test fails then it's because the number of error codes has changed.
// You should make the appropriate changes to the test cases here to ensure
// they cover all the error codes, and then modify this assertion.
Enum.GetNames(typeof(OcelotErrorCode)).Length.ShouldBe(39, "Looks like the number of error codes has changed. Do you need to modify ErrorsToHttpStatusCodeMapper?");
Enum.GetNames(typeof(OcelotErrorCode)).Length.ShouldBe(40, "Looks like the number of error codes has changed. Do you need to modify ErrorsToHttpStatusCodeMapper?");
}
private void ShouldMapErrorToStatusCode(OcelotErrorCode errorCode, HttpStatusCode expectedHttpStatusCode)