mirror of
https://github.com/nsnail/Ocelot.git
synced 2025-04-22 06:22:50 +08:00
* #268 added waiter to test, altho i wasn't able to replicate flakeyness with wait anyway! Hopefully this will be solid now! * #268 fixed a warning * #268 more code coverage
This commit is contained in:
parent
ed11f3024c
commit
8a2f76d0c5
@ -17,10 +17,16 @@ namespace Ocelot.Configuration.Repository
|
|||||||
private string _previousAsJson;
|
private string _previousAsJson;
|
||||||
private readonly Timer _timer;
|
private readonly Timer _timer;
|
||||||
private bool _polling;
|
private bool _polling;
|
||||||
|
private readonly IConsulPollerConfiguration _config;
|
||||||
|
|
||||||
public ConsulFileConfigurationPoller(IOcelotLoggerFactory factory, IFileConfigurationRepository repo, IFileConfigurationSetter setter)
|
public ConsulFileConfigurationPoller(
|
||||||
|
IOcelotLoggerFactory factory,
|
||||||
|
IFileConfigurationRepository repo,
|
||||||
|
IFileConfigurationSetter setter,
|
||||||
|
IConsulPollerConfiguration config)
|
||||||
{
|
{
|
||||||
_setter = setter;
|
_setter = setter;
|
||||||
|
_config = config;
|
||||||
_logger = factory.CreateLogger<ConsulFileConfigurationPoller>();
|
_logger = factory.CreateLogger<ConsulFileConfigurationPoller>();
|
||||||
_repo = repo;
|
_repo = repo;
|
||||||
_previousAsJson = "";
|
_previousAsJson = "";
|
||||||
@ -34,7 +40,7 @@ namespace Ocelot.Configuration.Repository
|
|||||||
_polling = true;
|
_polling = true;
|
||||||
await Poll();
|
await Poll();
|
||||||
_polling = false;
|
_polling = false;
|
||||||
}, null, 0, 1000);
|
}, null, 0, _config.Delay);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Poll()
|
private async Task Poll()
|
||||||
@ -63,8 +69,7 @@ namespace Ocelot.Configuration.Repository
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// We could do object comparison here but performance isnt really a problem. This might be an issue one day!
|
/// We could do object comparison here but performance isnt really a problem. This might be an issue one day!
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="config"></param>
|
/// <returns>hash of the config</returns>
|
||||||
/// <returns></returns>
|
|
||||||
private string ToJson(FileConfiguration config)
|
private string ToJson(FileConfiguration config)
|
||||||
{
|
{
|
||||||
var currentHash = JsonConvert.SerializeObject(config);
|
var currentHash = JsonConvert.SerializeObject(config);
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
namespace Ocelot.Configuration.Repository
|
||||||
|
{
|
||||||
|
public interface IConsulPollerConfiguration
|
||||||
|
{
|
||||||
|
int Delay { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
namespace Ocelot.Configuration.Repository
|
||||||
|
{
|
||||||
|
public class InMemoryConsulPollerConfiguration : IConsulPollerConfiguration
|
||||||
|
{
|
||||||
|
public int Delay => 1000;
|
||||||
|
}
|
||||||
|
}
|
@ -148,6 +148,7 @@ namespace Ocelot.DependencyInjection
|
|||||||
|
|
||||||
// We add this here so that we can always inject something into the factory for IoC..
|
// We add this here so that we can always inject something into the factory for IoC..
|
||||||
_services.AddSingleton<IServiceTracer, FakeServiceTracer>();
|
_services.AddSingleton<IServiceTracer, FakeServiceTracer>();
|
||||||
|
_services.TryAddSingleton<IConsulPollerConfiguration, InMemoryConsulPollerConfiguration>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public IOcelotAdministrationBuilder AddAdministration(string path, string secret)
|
public IOcelotAdministrationBuilder AddAdministration(string path, string secret)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
namespace Ocelot.UnitTests
|
namespace Ocelot.Infrastructure
|
||||||
{
|
{
|
||||||
public class Wait
|
public class Wait
|
||||||
{
|
{
|
@ -1,7 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
|
||||||
namespace Ocelot.UnitTests
|
namespace Ocelot.Infrastructure
|
||||||
{
|
{
|
||||||
public class Waiter
|
public class Waiter
|
||||||
{
|
{
|
@ -9,8 +9,10 @@ using Microsoft.AspNetCore.Hosting;
|
|||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Ocelot.Configuration.File;
|
using Ocelot.Configuration.File;
|
||||||
|
using Shouldly;
|
||||||
using TestStack.BDDfy;
|
using TestStack.BDDfy;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
using static Ocelot.Infrastructure.Wait;
|
||||||
|
|
||||||
namespace Ocelot.AcceptanceTests
|
namespace Ocelot.AcceptanceTests
|
||||||
{
|
{
|
||||||
@ -261,21 +263,27 @@ namespace Ocelot.AcceptanceTests
|
|||||||
.And(x => _steps.WhenIGetUrlOnTheApiGateway("/cs/status"))
|
.And(x => _steps.WhenIGetUrlOnTheApiGateway("/cs/status"))
|
||||||
.And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
.And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
||||||
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
|
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
|
||||||
.And(x => GivenTheConsulConfigurationIs(secondConsulConfig))
|
.When(x => GivenTheConsulConfigurationIs(secondConsulConfig))
|
||||||
.And(x => GivenIWaitForTheConfigToReplicateToOcelot())
|
.Then(x => ThenTheConfigIsUpdatedInOcelot())
|
||||||
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/cs/status/awesome"))
|
|
||||||
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
|
||||||
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
|
|
||||||
.BDDfy();
|
.BDDfy();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GivenIWaitForTheConfigToReplicateToOcelot()
|
private void ThenTheConfigIsUpdatedInOcelot()
|
||||||
{
|
{
|
||||||
var stopWatch = Stopwatch.StartNew();
|
var result = WaitFor(20000).Until(() => {
|
||||||
while (stopWatch.ElapsedMilliseconds < 10000)
|
try
|
||||||
{
|
{
|
||||||
//do nothing!
|
_steps.WhenIGetUrlOnTheApiGateway("/cs/status/awesome");
|
||||||
}
|
_steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK);
|
||||||
|
_steps.ThenTheResponseBodyShouldBe("Hello from Laura");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
result.ShouldBeTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GivenTheConsulConfigurationIs(FileConfiguration config)
|
private void GivenTheConsulConfigurationIs(FileConfiguration config)
|
||||||
|
@ -1,16 +1,17 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Threading;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Ocelot.Configuration.File;
|
using Ocelot.Configuration.File;
|
||||||
using Ocelot.Configuration.Repository;
|
using Ocelot.Configuration.Repository;
|
||||||
using Ocelot.Configuration.Setter;
|
using Ocelot.Configuration.Setter;
|
||||||
using Ocelot.Logging;
|
using Ocelot.Logging;
|
||||||
using Ocelot.Responses;
|
using Ocelot.Responses;
|
||||||
|
using Ocelot.UnitTests.Responder;
|
||||||
using TestStack.BDDfy;
|
using TestStack.BDDfy;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using Shouldly;
|
using Shouldly;
|
||||||
using static Ocelot.UnitTests.Wait;
|
using static Ocelot.Infrastructure.Wait;
|
||||||
|
|
||||||
namespace Ocelot.UnitTests.Configuration
|
namespace Ocelot.UnitTests.Configuration
|
||||||
{
|
{
|
||||||
@ -21,6 +22,7 @@ namespace Ocelot.UnitTests.Configuration
|
|||||||
private Mock<IFileConfigurationRepository> _repo;
|
private Mock<IFileConfigurationRepository> _repo;
|
||||||
private Mock<IFileConfigurationSetter> _setter;
|
private Mock<IFileConfigurationSetter> _setter;
|
||||||
private FileConfiguration _fileConfig;
|
private FileConfiguration _fileConfig;
|
||||||
|
private Mock<IConsulPollerConfiguration> _config;
|
||||||
|
|
||||||
public ConsulFileConfigurationPollerTests()
|
public ConsulFileConfigurationPollerTests()
|
||||||
{
|
{
|
||||||
@ -30,8 +32,10 @@ namespace Ocelot.UnitTests.Configuration
|
|||||||
_repo = new Mock<IFileConfigurationRepository>();
|
_repo = new Mock<IFileConfigurationRepository>();
|
||||||
_setter = new Mock<IFileConfigurationSetter>();
|
_setter = new Mock<IFileConfigurationSetter>();
|
||||||
_fileConfig = new FileConfiguration();
|
_fileConfig = new FileConfiguration();
|
||||||
|
_config = new Mock<IConsulPollerConfiguration>();
|
||||||
_repo.Setup(x => x.Get()).ReturnsAsync(new OkResponse<FileConfiguration>(_fileConfig));
|
_repo.Setup(x => x.Get()).ReturnsAsync(new OkResponse<FileConfiguration>(_fileConfig));
|
||||||
_poller = new ConsulFileConfigurationPoller(_factory.Object, _repo.Object, _setter.Object);
|
_config.Setup(x => x.Delay).Returns(10);
|
||||||
|
_poller = new ConsulFileConfigurationPoller(_factory.Object, _repo.Object, _setter.Object, _config.Object);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
@ -42,7 +46,7 @@ namespace Ocelot.UnitTests.Configuration
|
|||||||
[Fact]
|
[Fact]
|
||||||
public void should_start()
|
public void should_start()
|
||||||
{
|
{
|
||||||
this.Given(x => ThenTheSetterIsCalled(_fileConfig))
|
this.Given(x => ThenTheSetterIsCalled(_fileConfig, 1))
|
||||||
.BDDfy();
|
.BDDfy();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,22 +69,82 @@ namespace Ocelot.UnitTests.Configuration
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.Given(x => WhenTheConfigIsChangedInConsul(newConfig))
|
this.Given(x => WhenTheConfigIsChangedInConsul(newConfig, 0))
|
||||||
.Then(x => ThenTheSetterIsCalled(newConfig))
|
.Then(x => ThenTheSetterIsCalled(newConfig, 1))
|
||||||
.BDDfy();
|
.BDDfy();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void WhenTheConfigIsChangedInConsul(FileConfiguration newConfig)
|
[Fact]
|
||||||
|
public void should_not_poll_if_already_polling()
|
||||||
{
|
{
|
||||||
_repo.Setup(x => x.Get()).ReturnsAsync(new OkResponse<FileConfiguration>(newConfig));
|
var newConfig = new FileConfiguration
|
||||||
|
{
|
||||||
|
ReRoutes = new List<FileReRoute>
|
||||||
|
{
|
||||||
|
new FileReRoute
|
||||||
|
{
|
||||||
|
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||||
|
{
|
||||||
|
new FileHostAndPort
|
||||||
|
{
|
||||||
|
Host = "test"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.Given(x => WhenTheConfigIsChangedInConsul(newConfig, 10))
|
||||||
|
.Then(x => ThenTheSetterIsCalled(newConfig, 1))
|
||||||
|
.BDDfy();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ThenTheSetterIsCalled(FileConfiguration fileConfig)
|
[Fact]
|
||||||
|
public void should_do_nothing_if_call_to_consul_fails()
|
||||||
|
{
|
||||||
|
var newConfig = new FileConfiguration
|
||||||
|
{
|
||||||
|
ReRoutes = new List<FileReRoute>
|
||||||
|
{
|
||||||
|
new FileReRoute
|
||||||
|
{
|
||||||
|
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||||
|
{
|
||||||
|
new FileHostAndPort
|
||||||
|
{
|
||||||
|
Host = "test"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.Given(x => WhenConsulErrors())
|
||||||
|
.Then(x => ThenTheSetterIsCalled(newConfig, 0))
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WhenConsulErrors()
|
||||||
|
{
|
||||||
|
_repo
|
||||||
|
.Setup(x => x.Get())
|
||||||
|
.ReturnsAsync(new ErrorResponse<FileConfiguration>(new AnyError()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WhenTheConfigIsChangedInConsul(FileConfiguration newConfig, int delay)
|
||||||
|
{
|
||||||
|
_repo
|
||||||
|
.Setup(x => x.Get())
|
||||||
|
.Callback(() => Thread.Sleep(delay))
|
||||||
|
.ReturnsAsync(new OkResponse<FileConfiguration>(newConfig));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ThenTheSetterIsCalled(FileConfiguration fileConfig, int times)
|
||||||
{
|
{
|
||||||
var result = WaitFor(2000).Until(() => {
|
var result = WaitFor(2000).Until(() => {
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_setter.Verify(x => x.Set(fileConfig), Times.Once);
|
_setter.Verify(x => x.Set(fileConfig), Times.Exactly(times));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch(Exception)
|
catch(Exception)
|
||||||
|
@ -1,68 +0,0 @@
|
|||||||
namespace Ocelot.UnitTests
|
|
||||||
{
|
|
||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using System.Net.Http;
|
|
||||||
using Microsoft.AspNetCore.TestHost;
|
|
||||||
using Microsoft.AspNetCore.Hosting;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Microsoft.AspNetCore.Builder;
|
|
||||||
using Moq;
|
|
||||||
using Ocelot.Infrastructure.RequestData;
|
|
||||||
|
|
||||||
public abstract class ServerHostedMiddlewareTest : IDisposable
|
|
||||||
{
|
|
||||||
protected TestServer Server { get; private set; }
|
|
||||||
protected HttpClient Client { get; private set; }
|
|
||||||
protected string Url { get; private set; }
|
|
||||||
protected HttpResponseMessage ResponseMessage { get; private set; }
|
|
||||||
protected Mock<IRequestScopedDataRepository> ScopedRepository { get; private set; }
|
|
||||||
|
|
||||||
public ServerHostedMiddlewareTest()
|
|
||||||
{
|
|
||||||
Url = "http://localhost:51879";
|
|
||||||
ScopedRepository = new Mock<IRequestScopedDataRepository>();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual void GivenTheTestServerIsConfigured()
|
|
||||||
{
|
|
||||||
var builder = new WebHostBuilder()
|
|
||||||
.ConfigureServices(x => GivenTheTestServerServicesAreConfigured(x))
|
|
||||||
.UseUrls(Url)
|
|
||||||
.UseKestrel()
|
|
||||||
.UseContentRoot(Directory.GetCurrentDirectory())
|
|
||||||
.UseIISIntegration()
|
|
||||||
.Configure(app => GivenTheTestServerPipelineIsConfigured(app));
|
|
||||||
|
|
||||||
Server = new TestServer(builder);
|
|
||||||
Client = Server.CreateClient();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual void GivenTheTestServerServicesAreConfigured(IServiceCollection services)
|
|
||||||
{
|
|
||||||
// override this in your test fixture to set up service dependencies
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app)
|
|
||||||
{
|
|
||||||
// override this in your test fixture to set up the test server pipeline
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void WhenICallTheMiddleware()
|
|
||||||
{
|
|
||||||
ResponseMessage = Client.GetAsync(Url).Result;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void WhenICallTheMiddlewareWithTheRequestIdKey(string requestIdKey, string value)
|
|
||||||
{
|
|
||||||
Client.DefaultRequestHeaders.Add(requestIdKey, value);
|
|
||||||
ResponseMessage = Client.GetAsync(Url).Result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
Client.Dispose();
|
|
||||||
Server.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user