mirror of
				https://github.com/nsnail/Ocelot.git
				synced 2025-11-01 06:15: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>(); | ||||
|         } | ||||
|         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 Route() | ||||
|         { | ||||
|  | ||||
|         } | ||||
|         public Route(string downstream, string upstream) | ||||
|         { | ||||
|             Downstream = downstream; | ||||
|             Upstream = upstream; | ||||
|         } | ||||
|  | ||||
|         public string Downstream { get; private set; } | ||||
|         public string Upstream { get; private set; } | ||||
|         public string Downstream { get; set; } | ||||
|         public string Upstream { get; 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.Hosting; | ||||
| using Microsoft.AspNetCore.Http; | ||||
| using Microsoft.Extensions.Configuration; | ||||
| using Microsoft.Extensions.DependencyInjection; | ||||
| using Microsoft.Extensions.Logging; | ||||
|   | ||||
| @@ -15,7 +15,8 @@ | ||||
|     "Microsoft.Extensions.Logging.Console": "1.0.0", | ||||
|     "Microsoft.Extensions.Logging.Debug": "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": { | ||||
|   | ||||
| @@ -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 | ||||
| { | ||||
|     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 Library.Infrastructure.Configuration; | ||||
|     using TestStack.BDDfy; | ||||
|     using YamlDotNet.Serialization; | ||||
|  | ||||
|     public class OcelotTests : IDisposable | ||||
|     { | ||||
|         private readonly FakeService _fakeService; | ||||
|         private readonly TestServer _server; | ||||
|         private readonly HttpClient _client; | ||||
|         private TestServer _server; | ||||
|         private HttpClient _client; | ||||
|         private HttpResponseMessage _response; | ||||
|  | ||||
|         public OcelotTests() | ||||
|         { | ||||
|             _server = new TestServer(new WebHostBuilder() | ||||
|                 .UseStartup<Startup>()); | ||||
|  | ||||
|             _client = _server.CreateClient(); | ||||
|  | ||||
|             _fakeService = new FakeService(); | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
|         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)) | ||||
|                 .BDDfy(); | ||||
|         } | ||||
| @@ -39,13 +38,57 @@ namespace Ocelot.AcceptanceTests | ||||
|         [Fact] | ||||
|         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)) | ||||
|                 .And(x => x.ThenTheResponseBodyShouldBe("Hello from Laura")) | ||||
|                 .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; | ||||
|         } | ||||
|   | ||||
| @@ -1,3 +1,3 @@ | ||||
| routes: | ||||
|      - downstream: "productservice/category/{categoryId}/products/{productId}/variants/{variantId}" | ||||
|        upstream: "https://www.moonpig.com/api/products/{categoryId}/{productId}/{variantId}" | ||||
| Routes: | ||||
| - Downstream: http://localhost:51879/ | ||||
|   Upstream: /heee | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Tom Pallister
					Tom Pallister