mirror of
				https://github.com/nsnail/Ocelot.git
				synced 2025-11-01 05:55:27 +08:00 
			
		
		
		
	Brought in YAML lib for configuration and added failing acceptance test
This commit is contained in:
		| @@ -8,11 +8,7 @@ namespace Ocelot.Library.Infrastructure.Configuration | |||||||
|         { |         { | ||||||
|             Routes = new List<Route>(); |             Routes = new List<Route>(); | ||||||
|         } |         } | ||||||
|         public Configuration(List<Route> routes) |  | ||||||
|         { |  | ||||||
|             Routes = routes; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public List<Route> Routes { get; private set; } |         public List<Route> Routes { get; set; } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,22 +0,0 @@ | |||||||
| namespace Ocelot.Library.Infrastructure.Configuration |  | ||||||
| { |  | ||||||
|     using System.IO; |  | ||||||
|     using YamlDotNet.Serialization; |  | ||||||
|     using YamlDotNet.Serialization.NamingConventions; |  | ||||||
|  |  | ||||||
|     public class ConfigurationReader : IConfigurationReader |  | ||||||
|     { |  | ||||||
|         public Configuration Read(string configurationFilePath) |  | ||||||
|         { |  | ||||||
|             var contents = File.ReadAllText(configurationFilePath); |  | ||||||
|  |  | ||||||
|             var input = new StringReader(contents); |  | ||||||
|  |  | ||||||
|             var deserializer = new Deserializer(namingConvention: new CamelCaseNamingConvention()); |  | ||||||
|  |  | ||||||
|             var configuration = deserializer.Deserialize<Configuration>(input); |  | ||||||
|  |  | ||||||
|             return configuration;; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,7 +0,0 @@ | |||||||
| namespace Ocelot.Library.Infrastructure.Configuration |  | ||||||
| { |  | ||||||
|     public interface IConfigurationReader |  | ||||||
|     { |  | ||||||
|         Configuration Read(string configurationFilePath); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -2,17 +2,7 @@ | |||||||
| { | { | ||||||
|     public class Route |     public class Route | ||||||
|     { |     { | ||||||
|         public Route() |         public string Downstream { get; set; } | ||||||
|         { |         public string Upstream { get; set; } | ||||||
|  |  | ||||||
|         } |  | ||||||
|         public Route(string downstream, string upstream) |  | ||||||
|         { |  | ||||||
|             Downstream = downstream; |  | ||||||
|             Upstream = upstream; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public string Downstream { get; private set; } |  | ||||||
|         public string Upstream { get; private set; } |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,42 +0,0 @@ | |||||||
| using System.IO; |  | ||||||
| using Microsoft.Extensions.Configuration; |  | ||||||
| using Microsoft.Extensions.FileProviders; |  | ||||||
|  |  | ||||||
| namespace Ocelot.Library.Infrastructure.Configuration |  | ||||||
| { |  | ||||||
|     public static class YamlConfigurationExtensions |  | ||||||
|     { |  | ||||||
|         public static IConfigurationBuilder AddYamlFile(this IConfigurationBuilder builder, string path) |  | ||||||
|         { |  | ||||||
|             return AddYamlFile(builder, provider: null, path: path, optional: false, reloadOnChange: false); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public static IConfigurationBuilder AddYamlFile(this IConfigurationBuilder builder, string path, bool optional) |  | ||||||
|         { |  | ||||||
|             return AddYamlFile(builder, provider: null, path: path, optional: optional, reloadOnChange: false); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public static IConfigurationBuilder AddYamlFile(this IConfigurationBuilder builder, string path, bool optional, bool reloadOnChange) |  | ||||||
|         { |  | ||||||
|             return AddYamlFile(builder, provider: null, path: path, optional: optional, reloadOnChange: reloadOnChange); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public static IConfigurationBuilder AddYamlFile(this IConfigurationBuilder builder, IFileProvider provider, string path, bool optional, bool reloadOnChange) |  | ||||||
|         { |  | ||||||
|             if (provider == null && Path.IsPathRooted(path)) |  | ||||||
|             { |  | ||||||
|                 provider = new PhysicalFileProvider(Path.GetDirectoryName(path)); |  | ||||||
|                 path = Path.GetFileName(path); |  | ||||||
|             } |  | ||||||
|             var source = new YamlConfigurationSource |  | ||||||
|             { |  | ||||||
|                 FileProvider = provider, |  | ||||||
|                 Path = path, |  | ||||||
|                 Optional = optional, |  | ||||||
|                 ReloadOnChange = reloadOnChange |  | ||||||
|             }; |  | ||||||
|             builder.Add(source); |  | ||||||
|             return builder; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,121 +0,0 @@ | |||||||
| namespace Ocelot.Library.Infrastructure.Configuration |  | ||||||
| { |  | ||||||
|     using System; |  | ||||||
|     using System.Collections.Generic; |  | ||||||
|     using System.IO; |  | ||||||
|     using System.Linq; |  | ||||||
|     using Microsoft.Extensions.Configuration; |  | ||||||
|     using YamlDotNet.RepresentationModel; |  | ||||||
|  |  | ||||||
|     internal class YamlConfigurationFileParser |  | ||||||
|     { |  | ||||||
|         private readonly IDictionary<string, string> _data = new SortedDictionary<string, string>(StringComparer.OrdinalIgnoreCase); |  | ||||||
|         private readonly Stack<string> _context = new Stack<string>(); |  | ||||||
|         private string _currentPath; |  | ||||||
|  |  | ||||||
|         public IDictionary<string, string> Parse(Stream input) |  | ||||||
|         { |  | ||||||
|             _data.Clear(); |  | ||||||
|             _context.Clear(); |  | ||||||
|  |  | ||||||
|             // https://dotnetfiddle.net/rrR2Bb |  | ||||||
|             var yaml = new YamlStream(); |  | ||||||
|             yaml.Load(new StreamReader(input)); |  | ||||||
|  |  | ||||||
|             if (yaml.Documents.Any()) |  | ||||||
|             { |  | ||||||
|                 var mapping = (YamlMappingNode)yaml.Documents[0].RootNode; |  | ||||||
|  |  | ||||||
|                 // The document node is a mapping node |  | ||||||
|                 VisitYamlMappingNode(mapping); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             return _data; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         private void VisitYamlNodePair(KeyValuePair<YamlNode, YamlNode> yamlNodePair) |  | ||||||
|         { |  | ||||||
|             var context = ((YamlScalarNode)yamlNodePair.Key).Value; |  | ||||||
|             VisitYamlNode(context, yamlNodePair.Value); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         private void VisitYamlNode(string context, YamlNode node) |  | ||||||
|         { |  | ||||||
|             if (node is YamlScalarNode) |  | ||||||
|             { |  | ||||||
|                 VisitYamlScalarNode(context, (YamlScalarNode)node); |  | ||||||
|             } |  | ||||||
|             if (node is YamlMappingNode) |  | ||||||
|             { |  | ||||||
|                 VisitYamlMappingNode(context, (YamlMappingNode)node); |  | ||||||
|             } |  | ||||||
|             if (node is YamlSequenceNode) |  | ||||||
|             { |  | ||||||
|                 VisitYamlSequenceNode(context, (YamlSequenceNode)node); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         private void VisitYamlScalarNode(string context, YamlScalarNode yamlValue) |  | ||||||
|         { |  | ||||||
|             //a node with a single 1-1 mapping  |  | ||||||
|             EnterContext(context); |  | ||||||
|             var currentKey = _currentPath; |  | ||||||
|  |  | ||||||
|             if (_data.ContainsKey(currentKey)) |  | ||||||
|             { |  | ||||||
|                 throw new FormatException("Key is duplicate ${currentKey}"); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             _data[currentKey] = yamlValue.Value; |  | ||||||
|             ExitContext(); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         private void VisitYamlMappingNode(YamlMappingNode node) |  | ||||||
|         { |  | ||||||
|             foreach (var yamlNodePair in node.Children) |  | ||||||
|             { |  | ||||||
|                 VisitYamlNodePair(yamlNodePair); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         private void VisitYamlMappingNode(string context, YamlMappingNode yamlValue) |  | ||||||
|         { |  | ||||||
|             //a node with an associated sub-document |  | ||||||
|             EnterContext(context); |  | ||||||
|  |  | ||||||
|             VisitYamlMappingNode(yamlValue); |  | ||||||
|  |  | ||||||
|             ExitContext(); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         private void VisitYamlSequenceNode(string context, YamlSequenceNode yamlValue) |  | ||||||
|         { |  | ||||||
|             //a node with an associated list |  | ||||||
|             EnterContext(context); |  | ||||||
|  |  | ||||||
|             VisitYamlSequenceNode(yamlValue); |  | ||||||
|  |  | ||||||
|             ExitContext(); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         private void VisitYamlSequenceNode(YamlSequenceNode node) |  | ||||||
|         { |  | ||||||
|             for (int i = 0; i < node.Children.Count; i++) |  | ||||||
|             { |  | ||||||
|                 VisitYamlNode(i.ToString(), node.Children[i]); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         private void EnterContext(string context) |  | ||||||
|         { |  | ||||||
|             _context.Push(context); |  | ||||||
|             _currentPath = ConfigurationPath.Combine(_context.Reverse()); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         private void ExitContext() |  | ||||||
|         { |  | ||||||
|             _context.Pop(); |  | ||||||
|             _currentPath = ConfigurationPath.Combine(_context.Reverse()); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,22 +0,0 @@ | |||||||
| using System; |  | ||||||
| using System.Collections.Generic; |  | ||||||
| using System.Linq; |  | ||||||
| using System.Threading.Tasks; |  | ||||||
|  |  | ||||||
| namespace Ocelot.Library.Infrastructure.Configuration |  | ||||||
| { |  | ||||||
|     using System.IO; |  | ||||||
|     using Microsoft.Extensions.Configuration; |  | ||||||
|  |  | ||||||
|     public class YamlConfigurationProvider : FileConfigurationProvider |  | ||||||
|     { |  | ||||||
|         public YamlConfigurationProvider(YamlConfigurationSource source) : base(source) { } |  | ||||||
|  |  | ||||||
|         public override void Load(Stream stream) |  | ||||||
|         { |  | ||||||
|             var parser = new YamlConfigurationFileParser(); |  | ||||||
|  |  | ||||||
|             Data = parser.Parse(stream); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,13 +0,0 @@ | |||||||
| namespace Ocelot.Library.Infrastructure.Configuration |  | ||||||
| { |  | ||||||
|     using Microsoft.Extensions.Configuration; |  | ||||||
|  |  | ||||||
|     public class YamlConfigurationSource : FileConfigurationSource |  | ||||||
|     { |  | ||||||
|         public override IConfigurationProvider Build(IConfigurationBuilder builder) |  | ||||||
|         { |  | ||||||
|             FileProvider = FileProvider ?? builder.GetFileProvider(); |  | ||||||
|             return new YamlConfigurationProvider(this); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,6 +1,5 @@ | |||||||
| using Microsoft.AspNetCore.Builder; | using Microsoft.AspNetCore.Builder; | ||||||
| using Microsoft.AspNetCore.Hosting; | using Microsoft.AspNetCore.Hosting; | ||||||
| using Microsoft.AspNetCore.Http; |  | ||||||
| using Microsoft.Extensions.Configuration; | using Microsoft.Extensions.Configuration; | ||||||
| using Microsoft.Extensions.DependencyInjection; | using Microsoft.Extensions.DependencyInjection; | ||||||
| using Microsoft.Extensions.Logging; | using Microsoft.Extensions.Logging; | ||||||
|   | |||||||
| @@ -15,7 +15,8 @@ | |||||||
|     "Microsoft.Extensions.Logging.Console": "1.0.0", |     "Microsoft.Extensions.Logging.Console": "1.0.0", | ||||||
|     "Microsoft.Extensions.Logging.Debug": "1.0.0", |     "Microsoft.Extensions.Logging.Debug": "1.0.0", | ||||||
|     "Microsoft.Extensions.Options.ConfigurationExtensions": "1.0.0", |     "Microsoft.Extensions.Options.ConfigurationExtensions": "1.0.0", | ||||||
|     "Ocelot.Library": "1.0.0-*" |     "Ocelot.Library": "1.0.0-*", | ||||||
|  |     "NetEscapades.Configuration.Yaml": "1.1.0" | ||||||
|   }, |   }, | ||||||
|  |  | ||||||
|   "tools": { |   "tools": { | ||||||
|   | |||||||
| @@ -1,54 +0,0 @@ | |||||||
| namespace Ocelot.AcceptanceTests |  | ||||||
| { |  | ||||||
|     using System.Collections.Generic; |  | ||||||
|     using Library.Infrastructure.Configuration; |  | ||||||
|     using Shouldly; |  | ||||||
|     using TestStack.BDDfy; |  | ||||||
|     using Xunit; |  | ||||||
|  |  | ||||||
|     public class ConfigurationReaderTests |  | ||||||
|     { |  | ||||||
|         private readonly IConfigurationReader _configurationReader; |  | ||||||
|         private string _configPath; |  | ||||||
|         private Configuration _result; |  | ||||||
|  |  | ||||||
|         public ConfigurationReaderTests() |  | ||||||
|         { |  | ||||||
|             _configurationReader = new ConfigurationReader(); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         [Fact] |  | ||||||
|         public void can_read_configuration() |  | ||||||
|         { |  | ||||||
|             const string path = "./ConfigurationReaderTests.can_read_configuration.yaml"; |  | ||||||
|  |  | ||||||
|             var expected = |  | ||||||
|                 new Configuration(new List<Route> |  | ||||||
|                 { |  | ||||||
|                     new Route("productservice/category/{categoryId}/products/{productId}/variants/{variantId}", |  | ||||||
|                         "https://www.moonpig.com/api/products/{categoryId}/{productId}/{variantId}") |  | ||||||
|                 }); |  | ||||||
|  |  | ||||||
|             this.Given(x => x.GivenAConfigPathOf(path)) |  | ||||||
|                 .When(x => x.WhenICallTheConfigurationReader()) |  | ||||||
|                 .Then(x => x.ThenTheFollowingConfigurationIsReturned(expected)) |  | ||||||
|                 .BDDfy(); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         private void GivenAConfigPathOf(string configPath) |  | ||||||
|         { |  | ||||||
|             _configPath = configPath; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         private void WhenICallTheConfigurationReader() |  | ||||||
|         { |  | ||||||
|             _result = _configurationReader.Read(_configPath); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         private void ThenTheFollowingConfigurationIsReturned(Configuration expected) |  | ||||||
|         { |  | ||||||
|             _result.Routes[0].Downstream.ShouldBe(expected.Routes[0].Downstream); |  | ||||||
|             _result.Routes[0].Upstream.ShouldBe(expected.Routes[0].Upstream); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,37 +1,36 @@ | |||||||
| using System; |  | ||||||
| using System.Net.Http; |  | ||||||
| using Microsoft.AspNetCore.Hosting; |  | ||||||
| using Microsoft.AspNetCore.TestHost; |  | ||||||
| using Xunit; |  | ||||||
| using Ocelot.AcceptanceTests.Fake; |  | ||||||
| using Shouldly; |  | ||||||
|  |  | ||||||
| namespace Ocelot.AcceptanceTests | namespace Ocelot.AcceptanceTests | ||||||
| { | { | ||||||
|  |     using System; | ||||||
|  |     using System.Net.Http; | ||||||
|  |     using Microsoft.AspNetCore.Hosting; | ||||||
|  |     using Microsoft.AspNetCore.TestHost; | ||||||
|  |     using Xunit; | ||||||
|  |     using Ocelot.AcceptanceTests.Fake; | ||||||
|  |     using Shouldly; | ||||||
|  |     using System.Collections.Generic; | ||||||
|  |     using System.IO; | ||||||
|     using System.Net; |     using System.Net; | ||||||
|  |     using Library.Infrastructure.Configuration; | ||||||
|     using TestStack.BDDfy; |     using TestStack.BDDfy; | ||||||
|  |     using YamlDotNet.Serialization; | ||||||
|  |  | ||||||
|     public class OcelotTests : IDisposable |     public class OcelotTests : IDisposable | ||||||
|     { |     { | ||||||
|         private readonly FakeService _fakeService; |         private readonly FakeService _fakeService; | ||||||
|         private readonly TestServer _server; |         private TestServer _server; | ||||||
|         private readonly HttpClient _client; |         private HttpClient _client; | ||||||
|         private HttpResponseMessage _response; |         private HttpResponseMessage _response; | ||||||
|  |  | ||||||
|         public OcelotTests() |         public OcelotTests() | ||||||
|         { |         { | ||||||
|             _server = new TestServer(new WebHostBuilder() |  | ||||||
|                 .UseStartup<Startup>()); |  | ||||||
|  |  | ||||||
|             _client = _server.CreateClient(); |  | ||||||
|  |  | ||||||
|             _fakeService = new FakeService(); |             _fakeService = new FakeService(); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         [Fact] |         [Fact] | ||||||
|         public void should_return_response_404() |         public void should_return_response_404() | ||||||
|         { |         { | ||||||
|             this.When(x => x.WhenIRequestTheUrl("/")) |             this.Given(x => x.GivenTheApiGatewayIsRunning()) | ||||||
|  |                 .When(x => x.WhenIRequestTheUrlOnTheApiGateway("/")) | ||||||
|                 .Then(x => x.ThenTheStatusCodeShouldBe(HttpStatusCode.NotFound)) |                 .Then(x => x.ThenTheStatusCodeShouldBe(HttpStatusCode.NotFound)) | ||||||
|                 .BDDfy(); |                 .BDDfy(); | ||||||
|         } |         } | ||||||
| @@ -39,13 +38,57 @@ namespace Ocelot.AcceptanceTests | |||||||
|         [Fact] |         [Fact] | ||||||
|         public void should_return_response_200() |         public void should_return_response_200() | ||||||
|         { |         { | ||||||
|             this.When(x => x.WhenIRequestTheUrl("/")) |             this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879")) | ||||||
|  |                 .And(x => x.GivenThereIsAConfiguration(new Configuration | ||||||
|  |                 { | ||||||
|  |                     Routes = new List<Route> | ||||||
|  |                     { | ||||||
|  |                         new Route | ||||||
|  |                         { | ||||||
|  |                             Downstream = "http://localhost:51879/", | ||||||
|  |                             Upstream = "/heee" | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 })) | ||||||
|  |                 .And(x => x.GivenTheApiGatewayIsRunning()) | ||||||
|  |                 .When(x => x.WhenIRequestTheUrlOnTheApiGateway("/")) | ||||||
|                 .Then(x => x.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) |                 .Then(x => x.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) | ||||||
|                 .And(x => x.ThenTheResponseBodyShouldBe("Hello from Laura")) |                 .And(x => x.ThenTheResponseBodyShouldBe("Hello from Laura")) | ||||||
|                 .BDDfy(); |                 .BDDfy(); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         private void WhenIRequestTheUrl(string url) |         /// <summary> | ||||||
|  |         /// This is annoying cos it should be in the constructor but we need to set up the yaml file before calling startup so its a step. | ||||||
|  |         /// </summary> | ||||||
|  |         private void GivenTheApiGatewayIsRunning() | ||||||
|  |         { | ||||||
|  |             _server = new TestServer(new WebHostBuilder() | ||||||
|  |                 .UseStartup<Startup>()); | ||||||
|  |  | ||||||
|  |             _client = _server.CreateClient(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private void GivenThereIsAConfiguration(Configuration configuration) | ||||||
|  |         { | ||||||
|  |             var serializer = new Serializer(); | ||||||
|  |  | ||||||
|  |             if (File.Exists("./configuration.yaml")) | ||||||
|  |             { | ||||||
|  |                 File.Delete("./configuration.yaml"); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             using (TextWriter writer = File.CreateText("./configuration.yaml")) | ||||||
|  |             { | ||||||
|  |                 serializer.Serialize(writer, configuration); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private void GivenThereIsAServiceRunningOn(string url) | ||||||
|  |         { | ||||||
|  |             _fakeService.Start(url); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private void WhenIRequestTheUrlOnTheApiGateway(string url) | ||||||
|         { |         { | ||||||
|             _response = _client.GetAsync("/").Result; |             _response = _client.GetAsync("/").Result; | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -1,3 +1,3 @@ | |||||||
| routes: | Routes: | ||||||
|      - downstream: "productservice/category/{categoryId}/products/{productId}/variants/{variantId}" | - Downstream: http://localhost:51879/ | ||||||
|        upstream: "https://www.moonpig.com/api/products/{categoryId}/{productId}/{variantId}" |   Upstream: /heee | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Tom Pallister
					Tom Pallister