diff --git a/.gitignore b/.gitignore index 476938f0..d1d0ee7d 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,7 @@ *.user *.userosscache *.sln.docstates - +.DS_Store # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs diff --git a/docs/features/administration.rst b/docs/features/administration.rst index 3afcbdee..4563ba5c 100644 --- a/docs/features/administration.rst +++ b/docs/features/administration.rst @@ -98,11 +98,13 @@ This gets the current Ocelot configuration. It is exactly the same JSON we use t **POST {adminPath}/configuration** - This overrwrites the existing configuration (should probably be a put!). I reccomend getting your config from the GET endpoint, making any changes and posting it back...simples. The body of the request is JSON and it is the same format as the FileConfiguration.cs that we use to set up -Ocelot on a file system. +Ocelot on a file system. + +Please note that if you want to use this API then the process running Ocelot must have permission to write to the disk +where your ocelot.json or ocelot.{environment}.json is located. This is because Ocelot will overwrite them on save. **DELETE {adminPath}/outputcache/{region}** diff --git a/src/Ocelot/Configuration/Repository/DiskFileConfigurationRepository.cs b/src/Ocelot/Configuration/Repository/DiskFileConfigurationRepository.cs index a870419c..0141abd7 100644 --- a/src/Ocelot/Configuration/Repository/DiskFileConfigurationRepository.cs +++ b/src/Ocelot/Configuration/Repository/DiskFileConfigurationRepository.cs @@ -9,15 +9,16 @@ namespace Ocelot.Configuration.Repository { public class DiskFileConfigurationRepository : IFileConfigurationRepository { - private readonly string _configFilePath; - + private readonly string _environmentFilePath; + private readonly string _ocelotFilePath; private static readonly object _lock = new object(); - private const string ConfigurationFileName = "ocelot"; public DiskFileConfigurationRepository(IHostingEnvironment hostingEnvironment) { - _configFilePath = $"{AppContext.BaseDirectory}/{ConfigurationFileName}{(string.IsNullOrEmpty(hostingEnvironment.EnvironmentName) ? string.Empty : ".")}{hostingEnvironment.EnvironmentName}.json"; + _environmentFilePath = $"{AppContext.BaseDirectory}{ConfigurationFileName}{(string.IsNullOrEmpty(hostingEnvironment.EnvironmentName) ? string.Empty : ".")}{hostingEnvironment.EnvironmentName}.json"; + + _ocelotFilePath = $"{AppContext.BaseDirectory}{ConfigurationFileName}.json"; } public Task> Get() @@ -26,7 +27,7 @@ namespace Ocelot.Configuration.Repository lock(_lock) { - jsonConfiguration = System.IO.File.ReadAllText(_configFilePath); + jsonConfiguration = System.IO.File.ReadAllText(_environmentFilePath); } var fileConfiguration = JsonConvert.DeserializeObject(jsonConfiguration); @@ -40,12 +41,19 @@ namespace Ocelot.Configuration.Repository lock(_lock) { - if (System.IO.File.Exists(_configFilePath)) + if (System.IO.File.Exists(_environmentFilePath)) { - System.IO.File.Delete(_configFilePath); + System.IO.File.Delete(_environmentFilePath); } - System.IO.File.WriteAllText(_configFilePath, jsonConfiguration); + System.IO.File.WriteAllText(_environmentFilePath, jsonConfiguration); + + if (System.IO.File.Exists(_ocelotFilePath)) + { + System.IO.File.Delete(_ocelotFilePath); + } + + System.IO.File.WriteAllText(_ocelotFilePath, jsonConfiguration); } return Task.FromResult(new OkResponse()); diff --git a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs index d97dd71e..fecf749f 100644 --- a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs +++ b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs @@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Options; using System.Diagnostics; + using DependencyInjection; using Microsoft.AspNetCore.Builder; using Ocelot.Configuration; using Ocelot.Configuration.Creator; @@ -27,18 +28,18 @@ await builder.UseOcelot(new OcelotPipelineConfiguration()); return builder; - } - + } + public static async Task UseOcelot(this IApplicationBuilder builder, Action pipelineConfiguration) - { - var config = new OcelotPipelineConfiguration(); - pipelineConfiguration?.Invoke(config); - return await builder.UseOcelot(config); - } + { + var config = new OcelotPipelineConfiguration(); + pipelineConfiguration?.Invoke(config); + return await builder.UseOcelot(config); + } public static async Task UseOcelot(this IApplicationBuilder builder, OcelotPipelineConfiguration pipelineConfiguration) { - var configuration = await CreateConfiguration(builder); - + var configuration = await CreateConfiguration(builder); + CreateAdministrationArea(builder, configuration); if (UsingRafty(builder)) @@ -105,9 +106,9 @@ { // make configuration from file system? // earlier user needed to add ocelot files in startup configuration stuff, asp.net will map it to this - var fileConfig = (IOptions)builder.ApplicationServices.GetService(typeof(IOptions)); - - // now create the config + var fileConfig = (IOptions)builder.ApplicationServices.GetService(typeof(IOptions)); + + // now create the config var internalConfigCreator = (IInternalConfigurationCreator)builder.ApplicationServices.GetService(typeof(IInternalConfigurationCreator)); var internalConfig = await internalConfigCreator.Create(fileConfig.Value); @@ -115,22 +116,32 @@ var internalConfigRepo = (IInternalConfigurationRepository)builder.ApplicationServices.GetService(typeof(IInternalConfigurationRepository)); internalConfigRepo.AddOrReplace(internalConfig.Data); - var fileConfigSetter = (IFileConfigurationSetter)builder.ApplicationServices.GetService(typeof(IFileConfigurationSetter)); - var fileConfigRepo = (IFileConfigurationRepository)builder.ApplicationServices.GetService(typeof(IFileConfigurationRepository)); + var adminPath = (IAdministrationPath)builder.ApplicationServices.GetService(typeof(IAdministrationPath)); + if (UsingConsul(fileConfigRepo)) - { + { + //Lots of jazz happens in here..check it out if you are using consul to store your config. await SetFileConfigInConsul(builder, fileConfigRepo, fileConfig, internalConfigCreator, internalConfigRepo); } - else - { + else if(AdministrationApiInUse(adminPath)) + { + //We have to make sure the file config is set for the ocelot.env.json and ocelot.json so that if we pull it from the + //admin api it works...boy this is getting a spit spags boll. + var fileConfigSetter = (IFileConfigurationSetter)builder.ApplicationServices.GetService(typeof(IFileConfigurationSetter)); + await SetFileConfig(fileConfigSetter, fileConfig); } return GetOcelotConfigAndReturn(internalConfigRepo); } + private static bool AdministrationApiInUse(IAdministrationPath adminPath) + { + return adminPath.GetType() != typeof(NullAdministrationPath); + } + private static async Task SetFileConfigInConsul(IApplicationBuilder builder, IFileConfigurationRepository fileConfigRepo, IOptions fileConfig, IInternalConfigurationCreator internalConfigCreator, IInternalConfigurationRepository internalConfigRepo) @@ -179,8 +190,7 @@ private static async Task SetFileConfig(IFileConfigurationSetter fileConfigSetter, IOptions fileConfig) { - Response response; - response = await fileConfigSetter.Set(fileConfig.Value); + var response = await fileConfigSetter.Set(fileConfig.Value); if (IsError(response)) { @@ -245,8 +255,8 @@ var listener = (OcelotDiagnosticListener)builder.ApplicationServices.GetService(typeof(OcelotDiagnosticListener)); var diagnosticListener = (DiagnosticListener)builder.ApplicationServices.GetService(typeof(DiagnosticListener)); diagnosticListener.SubscribeWithAdapter(listener); - } - + } + private static void OnShutdown(IApplicationBuilder app) { var node = (INode)app.ApplicationServices.GetService(typeof(INode)); diff --git a/test/Ocelot.AcceptanceTests/StartupTests.cs b/test/Ocelot.AcceptanceTests/StartupTests.cs new file mode 100644 index 00000000..e0dbe0ac --- /dev/null +++ b/test/Ocelot.AcceptanceTests/StartupTests.cs @@ -0,0 +1,100 @@ +namespace Ocelot.AcceptanceTests +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Net; + using System.Threading.Tasks; + using Configuration.Repository; + using Microsoft.AspNetCore.Http; + using Ocelot.Configuration.File; + using Responses; + using TestStack.BDDfy; + using Xunit; + + public class StartupTests : IDisposable + { + private readonly Steps _steps; + private readonly ServiceHandler _serviceHandler; + private string _downstreamPath; + + public StartupTests() + { + _serviceHandler = new ServiceHandler(); + _steps = new Steps(); + } + + [Fact] + public void should_not_try_and_write_to_disk_on_startup_when_not_using_admin_api() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 52179, + } + }, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + } + } + }; + + var fakeRepo = new FakeFileConfigurationRepository(); + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:52179", "/", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunningWithBlowingUpDiskRepo(fakeRepo)) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .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); + } + }); + } + + public void Dispose() + { + _serviceHandler?.Dispose(); + _steps.Dispose(); + } + + class FakeFileConfigurationRepository : IFileConfigurationRepository + { + public Task> Get() + { + throw new NotImplementedException(); + } + + public Task Set(FileConfiguration fileConfiguration) + { + throw new NotImplementedException(); + } + } + } +} diff --git a/test/Ocelot.AcceptanceTests/Steps.cs b/test/Ocelot.AcceptanceTests/Steps.cs index 92f463f1..7f5e296a 100644 --- a/test/Ocelot.AcceptanceTests/Steps.cs +++ b/test/Ocelot.AcceptanceTests/Steps.cs @@ -31,6 +31,7 @@ using static Ocelot.Infrastructure.Wait; namespace Ocelot.AcceptanceTests { + using Configuration.Repository; using Microsoft.Net.Http.Headers; using MediaTypeHeaderValue = System.Net.Http.Headers.MediaTypeHeaderValue; @@ -928,5 +929,35 @@ namespace Ocelot.AcceptanceTests var content = await response.Content.ReadAsStringAsync(); content.ShouldBe(expectedBody); } + + public void GivenOcelotIsRunningWithBlowingUpDiskRepo(IFileConfigurationRepository fake) + { + _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.AddSingleton(fake); + s.AddOcelot(); + }) + .Configure(app => + { + app.UseOcelot().Wait(); + }); + + _ocelotServer = new TestServer(_webHostBuilder); + + _ocelotClient = _ocelotServer.CreateClient(); + + } } } diff --git a/test/Ocelot.IntegrationTests/AdministrationTests.cs b/test/Ocelot.IntegrationTests/AdministrationTests.cs index 94713fbf..cf382b63 100644 --- a/test/Ocelot.IntegrationTests/AdministrationTests.cs +++ b/test/Ocelot.IntegrationTests/AdministrationTests.cs @@ -276,9 +276,23 @@ namespace Ocelot.IntegrationTests .And(x => ThenTheResponseShouldBe(updatedConfiguration)) .When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration")) .And(x => ThenTheResponseShouldBe(updatedConfiguration)) + .And(_ => ThenTheConfigurationIsSavedCorrectly(updatedConfiguration)) .BDDfy(); } + private void ThenTheConfigurationIsSavedCorrectly(FileConfiguration expected) + { + var ocelotJsonPath = $"{AppContext.BaseDirectory}ocelot.json"; + var resultText = File.ReadAllText(ocelotJsonPath); + var expectedText = JsonConvert.SerializeObject(expected, Formatting.Indented); + resultText.ShouldBe(expectedText); + + var environmentSpecificPath = $"{AppContext.BaseDirectory}/ocelot.Production.json"; + resultText = File.ReadAllText(environmentSpecificPath); + expectedText = JsonConvert.SerializeObject(expected, Formatting.Indented); + resultText.ShouldBe(expectedText); + } + [Fact] public void should_get_file_configuration_edit_and_post_updated_version_redirecting_reroute() { diff --git a/test/Ocelot.UnitTests/Configuration/DiskFileConfigurationRepositoryTests.cs b/test/Ocelot.UnitTests/Configuration/DiskFileConfigurationRepositoryTests.cs index c2e77b38..8f99ef3d 100644 --- a/test/Ocelot.UnitTests/Configuration/DiskFileConfigurationRepositoryTests.cs +++ b/test/Ocelot.UnitTests/Configuration/DiskFileConfigurationRepositoryTests.cs @@ -1,242 +1,287 @@ -namespace Ocelot.UnitTests.Configuration -{ - using System; - using System.Collections.Generic; - using Moq; - using Ocelot.Configuration.File; - using Shouldly; - using TestStack.BDDfy; - using Xunit; - using Newtonsoft.Json; - using System.IO; - using Microsoft.AspNetCore.Hosting; - using Ocelot.Configuration.Repository; - - public class DiskFileConfigurationRepositoryTests - { - private readonly Mock _hostingEnvironment = new Mock(); - private IFileConfigurationRepository _repo; - private string _configurationPath; - private FileConfiguration _result; - private FileConfiguration _fileConfiguration; - - // This is a bit dirty and it is dev.dev so that the ConfigurationBuilderExtensionsTests - // cant pick it up if they run in parralel..sigh these are not really unit - // tests but whatever... - private string _environmentName = "DEV.DEV"; - - public DiskFileConfigurationRepositoryTests() - { - _hostingEnvironment.Setup(he => he.EnvironmentName).Returns(_environmentName); - _repo = new DiskFileConfigurationRepository(_hostingEnvironment.Object); - } - - [Fact] - public void should_return_file_configuration() - { - var config = FakeFileConfigurationForGet(); - - this.Given(_ => GivenTheConfigurationIs(config)) - .When(_ => WhenIGetTheReRoutes()) - .Then(_ => ThenTheFollowingIsReturned(config)) - .BDDfy(); - } - - [Fact] - public void should_return_file_configuration_if_environment_name_is_unavailable() - { - var config = FakeFileConfigurationForGet(); - - this.Given(_ => GivenTheEnvironmentNameIsUnavailable()) - .And(_ => GivenTheConfigurationIs(config)) - .When(_ => WhenIGetTheReRoutes()) - .Then(_ => ThenTheFollowingIsReturned(config)) - .BDDfy(); - } - - [Fact] - public void should_set_file_configuration() - { - var config = FakeFileConfigurationForSet(); - - this.Given(_ => GivenIHaveAConfiguration(config)) - .When(_ => WhenISetTheConfiguration()) - .Then(_ => ThenTheConfigurationIsStoredAs(config)) - .And(_ => ThenTheConfigurationJsonIsIndented(config)) - .BDDfy(); - } - - [Fact] - public void should_set_file_configuration_if_environment_name_is_unavailable() - { - var config = FakeFileConfigurationForSet(); - - this.Given(_ => GivenIHaveAConfiguration(config)) - .And(_ => GivenTheEnvironmentNameIsUnavailable()) - .When(_ => WhenISetTheConfiguration()) - .Then(_ => ThenTheConfigurationIsStoredAs(config)) - .And(_ => ThenTheConfigurationJsonIsIndented(config)) - .BDDfy(); - } - - private void GivenTheEnvironmentNameIsUnavailable() - { - _environmentName = null; - _hostingEnvironment.Setup(he => he.EnvironmentName).Returns(_environmentName); - _repo = new DiskFileConfigurationRepository(_hostingEnvironment.Object); - } - - private void GivenIHaveAConfiguration(FileConfiguration fileConfiguration) - { - _fileConfiguration = fileConfiguration; - } - - private void WhenISetTheConfiguration() - { - _repo.Set(_fileConfiguration); - _result = _repo.Get().Result.Data; - } - - private void ThenTheConfigurationIsStoredAs(FileConfiguration expecteds) - { - _result.GlobalConfiguration.RequestIdKey.ShouldBe(expecteds.GlobalConfiguration.RequestIdKey); - _result.GlobalConfiguration.ServiceDiscoveryProvider.Host.ShouldBe(expecteds.GlobalConfiguration.ServiceDiscoveryProvider.Host); - _result.GlobalConfiguration.ServiceDiscoveryProvider.Port.ShouldBe(expecteds.GlobalConfiguration.ServiceDiscoveryProvider.Port); - - for(var i = 0; i < _result.ReRoutes.Count; i++) - { - for (int j = 0; j < _result.ReRoutes[i].DownstreamHostAndPorts.Count; j++) - { - var result = _result.ReRoutes[i].DownstreamHostAndPorts[j]; - var expected = expecteds.ReRoutes[i].DownstreamHostAndPorts[j]; - - result.Host.ShouldBe(expected.Host); - result.Port.ShouldBe(expected.Port); - } - - _result.ReRoutes[i].DownstreamPathTemplate.ShouldBe(expecteds.ReRoutes[i].DownstreamPathTemplate); - _result.ReRoutes[i].DownstreamScheme.ShouldBe(expecteds.ReRoutes[i].DownstreamScheme); - } - } - - private void GivenTheConfigurationIs(FileConfiguration fileConfiguration) - { - _configurationPath = $"{AppContext.BaseDirectory}/ocelot{(string.IsNullOrEmpty(_environmentName) ? string.Empty : ".")}{_environmentName}.json"; - - var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration, Formatting.Indented); - - if (File.Exists(_configurationPath)) - { - File.Delete(_configurationPath); - } - - File.WriteAllText(_configurationPath, jsonConfiguration); - } - - private void ThenTheConfigurationJsonIsIndented(FileConfiguration expecteds) - { - var path = !string.IsNullOrEmpty(_configurationPath) ? _configurationPath : _configurationPath = $"{AppContext.BaseDirectory}/ocelot{(string.IsNullOrEmpty(_environmentName) ? string.Empty : ".")}{_environmentName}.json"; - - var resultText = File.ReadAllText(_configurationPath); - var expectedText = JsonConvert.SerializeObject(expecteds, Formatting.Indented); - resultText.ShouldBe(expectedText); - } - - private void WhenIGetTheReRoutes() - { - _result = _repo.Get().Result.Data; - } - - private void ThenTheFollowingIsReturned(FileConfiguration expecteds) - { - _result.GlobalConfiguration.RequestIdKey.ShouldBe(expecteds.GlobalConfiguration.RequestIdKey); - _result.GlobalConfiguration.ServiceDiscoveryProvider.Host.ShouldBe(expecteds.GlobalConfiguration.ServiceDiscoveryProvider.Host); - _result.GlobalConfiguration.ServiceDiscoveryProvider.Port.ShouldBe(expecteds.GlobalConfiguration.ServiceDiscoveryProvider.Port); - - for(var i = 0; i < _result.ReRoutes.Count; i++) - { - for (int j = 0; j < _result.ReRoutes[i].DownstreamHostAndPorts.Count; j++) - { - var result = _result.ReRoutes[i].DownstreamHostAndPorts[j]; - var expected = expecteds.ReRoutes[i].DownstreamHostAndPorts[j]; - - result.Host.ShouldBe(expected.Host); - result.Port.ShouldBe(expected.Port); - } - - _result.ReRoutes[i].DownstreamPathTemplate.ShouldBe(expecteds.ReRoutes[i].DownstreamPathTemplate); - _result.ReRoutes[i].DownstreamScheme.ShouldBe(expecteds.ReRoutes[i].DownstreamScheme); - } - } - - private FileConfiguration FakeFileConfigurationForSet() - { - var reRoutes = new List - { - new FileReRoute - { - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "123.12.12.12", - Port = 80, - } - }, - DownstreamScheme = "https", - DownstreamPathTemplate = "/asdfs/test/{test}" - } - }; - - var globalConfiguration = new FileGlobalConfiguration - { - ServiceDiscoveryProvider = new FileServiceDiscoveryProvider - { - Port = 198, - Host = "blah" - } - }; - - return new FileConfiguration - { - GlobalConfiguration = globalConfiguration, - ReRoutes = reRoutes - }; - } - - private FileConfiguration FakeFileConfigurationForGet() - { - var reRoutes = new List - { - new FileReRoute - { - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 80, - } - }, - DownstreamScheme = "https", - DownstreamPathTemplate = "/test/test/{test}" - } - }; - - var globalConfiguration = new FileGlobalConfiguration - { - ServiceDiscoveryProvider = new FileServiceDiscoveryProvider - { - Port = 198, - Host = "blah" - } - }; - - return new FileConfiguration - { - GlobalConfiguration = globalConfiguration, - ReRoutes = reRoutes - }; - } - } -} +namespace Ocelot.UnitTests.Configuration +{ + using System; + using System.Collections.Generic; + using Moq; + using Ocelot.Configuration.File; + using Shouldly; + using TestStack.BDDfy; + using Xunit; + using Newtonsoft.Json; + using System.IO; + using System.Threading; + using Microsoft.AspNetCore.Hosting; + using Ocelot.Configuration.Repository; + + public class DiskFileConfigurationRepositoryTests : IDisposable + { + private readonly Mock _hostingEnvironment; + private IFileConfigurationRepository _repo; + private string _environmentSpecificPath; + private string _ocelotJsonPath; + private FileConfiguration _result; + private FileConfiguration _fileConfiguration; + + // This is a bit dirty and it is dev.dev so that the ConfigurationBuilderExtensionsTests + // cant pick it up if they run in parralel..and the semaphore stops them running at the same time...sigh + // these are not really unit tests but whatever... + private string _environmentName = "DEV.DEV"; + private static SemaphoreSlim _semaphore; + + public DiskFileConfigurationRepositoryTests() + { + _semaphore = new SemaphoreSlim(1, 1); + _semaphore.Wait(); + _hostingEnvironment = new Mock(); + _hostingEnvironment.Setup(he => he.EnvironmentName).Returns(_environmentName); + _repo = new DiskFileConfigurationRepository(_hostingEnvironment.Object); + } + + [Fact] + public void should_return_file_configuration() + { + var config = FakeFileConfigurationForGet(); + + this.Given(_ => GivenTheConfigurationIs(config)) + .When(_ => WhenIGetTheReRoutes()) + .Then(_ => ThenTheFollowingIsReturned(config)) + .BDDfy(); + } + + [Fact] + public void should_return_file_configuration_if_environment_name_is_unavailable() + { + var config = FakeFileConfigurationForGet(); + + this.Given(_ => GivenTheEnvironmentNameIsUnavailable()) + .And(_ => GivenTheConfigurationIs(config)) + .When(_ => WhenIGetTheReRoutes()) + .Then(_ => ThenTheFollowingIsReturned(config)) + .BDDfy(); + } + + [Fact] + public void should_set_file_configuration() + { + var config = FakeFileConfigurationForSet(); + + this.Given(_ => GivenIHaveAConfiguration(config)) + .When(_ => WhenISetTheConfiguration()) + .Then(_ => ThenTheConfigurationIsStoredAs(config)) + .And(_ => ThenTheConfigurationJsonIsIndented(config)) + .BDDfy(); + } + + [Fact] + public void should_set_file_configuration_if_environment_name_is_unavailable() + { + var config = FakeFileConfigurationForSet(); + + this.Given(_ => GivenIHaveAConfiguration(config)) + .And(_ => GivenTheEnvironmentNameIsUnavailable()) + .When(_ => WhenISetTheConfiguration()) + .Then(_ => ThenTheConfigurationIsStoredAs(config)) + .And(_ => ThenTheConfigurationJsonIsIndented(config)) + .BDDfy(); + } + + [Fact] + public void should_set_environment_file_configuration_and_ocelot_file_configuration() + { + var config = FakeFileConfigurationForSet(); + + this.Given(_ => GivenIHaveAConfiguration(config)) + .And(_ => GivenTheConfigurationIs(config)) + .And(_ => GivenTheUserAddedOcelotJson()) + .When(_ => WhenISetTheConfiguration()) + .Then(_ => ThenTheConfigurationIsStoredAs(config)) + .And(_ => ThenTheConfigurationJsonIsIndented(config)) + .Then(_ => ThenTheOcelotJsonIsStoredAs(config)) + .BDDfy(); + } + + private void GivenTheUserAddedOcelotJson() + { + _ocelotJsonPath = $"{AppContext.BaseDirectory}/ocelot.json"; + + if (File.Exists(_ocelotJsonPath)) + { + File.Delete(_ocelotJsonPath); + } + + File.WriteAllText(_ocelotJsonPath, "Doesnt matter"); + } + + private void GivenTheEnvironmentNameIsUnavailable() + { + _environmentName = null; + _hostingEnvironment.Setup(he => he.EnvironmentName).Returns(_environmentName); + _repo = new DiskFileConfigurationRepository(_hostingEnvironment.Object); + } + + private void GivenIHaveAConfiguration(FileConfiguration fileConfiguration) + { + _fileConfiguration = fileConfiguration; + } + + private void WhenISetTheConfiguration() + { + _repo.Set(_fileConfiguration); + _result = _repo.Get().Result.Data; + } + + private void ThenTheConfigurationIsStoredAs(FileConfiguration expecteds) + { + _result.GlobalConfiguration.RequestIdKey.ShouldBe(expecteds.GlobalConfiguration.RequestIdKey); + _result.GlobalConfiguration.ServiceDiscoveryProvider.Host.ShouldBe(expecteds.GlobalConfiguration.ServiceDiscoveryProvider.Host); + _result.GlobalConfiguration.ServiceDiscoveryProvider.Port.ShouldBe(expecteds.GlobalConfiguration.ServiceDiscoveryProvider.Port); + + for(var i = 0; i < _result.ReRoutes.Count; i++) + { + for (int j = 0; j < _result.ReRoutes[i].DownstreamHostAndPorts.Count; j++) + { + var result = _result.ReRoutes[i].DownstreamHostAndPorts[j]; + var expected = expecteds.ReRoutes[i].DownstreamHostAndPorts[j]; + + result.Host.ShouldBe(expected.Host); + result.Port.ShouldBe(expected.Port); + } + + _result.ReRoutes[i].DownstreamPathTemplate.ShouldBe(expecteds.ReRoutes[i].DownstreamPathTemplate); + _result.ReRoutes[i].DownstreamScheme.ShouldBe(expecteds.ReRoutes[i].DownstreamScheme); + } + } + + private void ThenTheOcelotJsonIsStoredAs(FileConfiguration expecteds) + { + var resultText = File.ReadAllText(_ocelotJsonPath); + var expectedText = JsonConvert.SerializeObject(expecteds, Formatting.Indented); + resultText.ShouldBe(expectedText); + } + + private void GivenTheConfigurationIs(FileConfiguration fileConfiguration) + { + _environmentSpecificPath = $"{AppContext.BaseDirectory}/ocelot{(string.IsNullOrEmpty(_environmentName) ? string.Empty : ".")}{_environmentName}.json"; + + var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration, Formatting.Indented); + + if (File.Exists(_environmentSpecificPath)) + { + File.Delete(_environmentSpecificPath); + } + + File.WriteAllText(_environmentSpecificPath, jsonConfiguration); + } + + private void ThenTheConfigurationJsonIsIndented(FileConfiguration expecteds) + { + var path = !string.IsNullOrEmpty(_environmentSpecificPath) ? _environmentSpecificPath : _environmentSpecificPath = $"{AppContext.BaseDirectory}/ocelot{(string.IsNullOrEmpty(_environmentName) ? string.Empty : ".")}{_environmentName}.json"; + + var resultText = File.ReadAllText(path); + var expectedText = JsonConvert.SerializeObject(expecteds, Formatting.Indented); + resultText.ShouldBe(expectedText); + } + + private void WhenIGetTheReRoutes() + { + _result = _repo.Get().Result.Data; + } + + private void ThenTheFollowingIsReturned(FileConfiguration expecteds) + { + _result.GlobalConfiguration.RequestIdKey.ShouldBe(expecteds.GlobalConfiguration.RequestIdKey); + _result.GlobalConfiguration.ServiceDiscoveryProvider.Host.ShouldBe(expecteds.GlobalConfiguration.ServiceDiscoveryProvider.Host); + _result.GlobalConfiguration.ServiceDiscoveryProvider.Port.ShouldBe(expecteds.GlobalConfiguration.ServiceDiscoveryProvider.Port); + + for(var i = 0; i < _result.ReRoutes.Count; i++) + { + for (int j = 0; j < _result.ReRoutes[i].DownstreamHostAndPorts.Count; j++) + { + var result = _result.ReRoutes[i].DownstreamHostAndPorts[j]; + var expected = expecteds.ReRoutes[i].DownstreamHostAndPorts[j]; + + result.Host.ShouldBe(expected.Host); + result.Port.ShouldBe(expected.Port); + } + + _result.ReRoutes[i].DownstreamPathTemplate.ShouldBe(expecteds.ReRoutes[i].DownstreamPathTemplate); + _result.ReRoutes[i].DownstreamScheme.ShouldBe(expecteds.ReRoutes[i].DownstreamScheme); + } + } + + private FileConfiguration FakeFileConfigurationForSet() + { + var reRoutes = new List + { + new FileReRoute + { + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "123.12.12.12", + Port = 80, + } + }, + DownstreamScheme = "https", + DownstreamPathTemplate = "/asdfs/test/{test}" + } + }; + + var globalConfiguration = new FileGlobalConfiguration + { + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider + { + Port = 198, + Host = "blah" + } + }; + + return new FileConfiguration + { + GlobalConfiguration = globalConfiguration, + ReRoutes = reRoutes + }; + } + + private FileConfiguration FakeFileConfigurationForGet() + { + var reRoutes = new List + { + new FileReRoute + { + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 80, + } + }, + DownstreamScheme = "https", + DownstreamPathTemplate = "/test/test/{test}" + } + }; + + var globalConfiguration = new FileGlobalConfiguration + { + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider + { + Port = 198, + Host = "blah" + } + }; + + return new FileConfiguration + { + GlobalConfiguration = globalConfiguration, + ReRoutes = reRoutes + }; + } + + public void Dispose() + { + _semaphore.Release(); + } + } +}