diff --git a/src/WireMock.Net.Abstractions/IRequestMessage.cs b/src/WireMock.Net.Abstractions/IRequestMessage.cs index ca80ec89e..d7a0d301d 100644 --- a/src/WireMock.Net.Abstractions/IRequestMessage.cs +++ b/src/WireMock.Net.Abstractions/IRequestMessage.cs @@ -63,12 +63,12 @@ public interface IRequestMessage /// /// Gets the headers. /// - IDictionary>? Headers { get; } + IDictionary> Headers { get; } /// /// Gets the cookies. /// - IDictionary? Cookies { get; } + IDictionary Cookies { get; } /// /// Gets the query. diff --git a/src/WireMock.Net/Owin/AspNetCoreSelfHost.NETCore.cs b/src/WireMock.Net/Owin/AspNetCoreSelfHost.NETCore.cs index 3c505e161..1a5c85e98 100644 --- a/src/WireMock.Net/Owin/AspNetCoreSelfHost.NETCore.cs +++ b/src/WireMock.Net/Owin/AspNetCoreSelfHost.NETCore.cs @@ -3,45 +3,44 @@ using Microsoft.Extensions.DependencyInjection; using WireMock.Types; -namespace WireMock.Owin +namespace WireMock.Owin; + +internal partial class AspNetCoreSelfHost { - internal partial class AspNetCoreSelfHost + public void AddCors(IServiceCollection services) { - public void AddCors(IServiceCollection services) + if (_wireMockMiddlewareOptions.CorsPolicyOptions > CorsPolicyOptions.None) { - if (_wireMockMiddlewareOptions.CorsPolicyOptions > CorsPolicyOptions.None) - { - /* https://stackoverflow.com/questions/31942037/how-to-enable-cors-in-asp-net-core */ - /* Enable Cors */ - services.AddCors(corsOptions => corsOptions - .AddPolicy(CorsPolicyName, - corsPolicyBuilder => + /* https://stackoverflow.com/questions/31942037/how-to-enable-cors-in-asp-net-core */ + /* Enable Cors */ + services.AddCors(corsOptions => corsOptions + .AddPolicy(CorsPolicyName, + corsPolicyBuilder => + { + if (_wireMockMiddlewareOptions.CorsPolicyOptions.Value.HasFlag(CorsPolicyOptions.AllowAnyHeader)) { - if (_wireMockMiddlewareOptions.CorsPolicyOptions.Value.HasFlag(CorsPolicyOptions.AllowAnyHeader)) - { - corsPolicyBuilder.AllowAnyHeader(); - } + corsPolicyBuilder.AllowAnyHeader(); + } - if (_wireMockMiddlewareOptions.CorsPolicyOptions.Value.HasFlag(CorsPolicyOptions.AllowAnyMethod)) - { - corsPolicyBuilder.AllowAnyMethod(); - } + if (_wireMockMiddlewareOptions.CorsPolicyOptions.Value.HasFlag(CorsPolicyOptions.AllowAnyMethod)) + { + corsPolicyBuilder.AllowAnyMethod(); + } - if (_wireMockMiddlewareOptions.CorsPolicyOptions.Value.HasFlag(CorsPolicyOptions.AllowAnyOrigin)) - { - corsPolicyBuilder.AllowAnyOrigin(); - } - })); - } + if (_wireMockMiddlewareOptions.CorsPolicyOptions.Value.HasFlag(CorsPolicyOptions.AllowAnyOrigin)) + { + corsPolicyBuilder.AllowAnyOrigin(); + } + })); } + } - public void UseCors(IApplicationBuilder appBuilder) + public void UseCors(IApplicationBuilder appBuilder) + { + if (_wireMockMiddlewareOptions.CorsPolicyOptions > CorsPolicyOptions.None) { - if (_wireMockMiddlewareOptions.CorsPolicyOptions > CorsPolicyOptions.None) - { - /* Use Cors */ - appBuilder.UseCors(CorsPolicyName); - } + /* Use Cors */ + appBuilder.UseCors(CorsPolicyName); } } } diff --git a/src/WireMock.Net/Owin/AspNetCoreSelfHost.cs b/src/WireMock.Net/Owin/AspNetCoreSelfHost.cs index 097a21592..74d860888 100644 --- a/src/WireMock.Net/Owin/AspNetCoreSelfHost.cs +++ b/src/WireMock.Net/Owin/AspNetCoreSelfHost.cs @@ -36,10 +36,10 @@ internal partial class AspNetCoreSelfHost : IOwinSelfHost public Exception RunningException => _runningException; - public AspNetCoreSelfHost([NotNull] IWireMockMiddlewareOptions wireMockMiddlewareOptions, [NotNull] HostUrlOptions urlOptions) + public AspNetCoreSelfHost(IWireMockMiddlewareOptions wireMockMiddlewareOptions, HostUrlOptions urlOptions) { - Guard.NotNull(wireMockMiddlewareOptions, nameof(wireMockMiddlewareOptions)); - Guard.NotNull(urlOptions, nameof(urlOptions)); + Guard.NotNull(wireMockMiddlewareOptions); + Guard.NotNull(urlOptions); _logger = wireMockMiddlewareOptions.Logger ?? new WireMockConsoleLogger(); @@ -119,7 +119,7 @@ private Task RunHost(CancellationToken token) { Urls.Add(address.Replace("0.0.0.0", "localhost").Replace("[::]", "localhost")); - PortUtils.TryExtract(address, out bool isHttps, out string protocol, out string host, out int port); + PortUtils.TryExtract(address, out _, out _, out _, out int port); Ports.Add(port); } diff --git a/src/WireMock.Net/Owin/HostUrlDetails.cs b/src/WireMock.Net/Owin/HostUrlDetails.cs index 6bd8826ca..e988ae433 100644 --- a/src/WireMock.Net/Owin/HostUrlDetails.cs +++ b/src/WireMock.Net/Owin/HostUrlDetails.cs @@ -1,15 +1,14 @@ -namespace WireMock.Owin +namespace WireMock.Owin; + +internal struct HostUrlDetails { - internal class HostUrlDetails - { - public bool IsHttps { get; set; } + public bool IsHttps { get; set; } - public string Url { get; set; } + public string Url { get; set; } - public string Protocol { get; set; } + public string Protocol { get; set; } - public string Host { get; set; } + public string Host { get; set; } - public int Port { get; set; } - } + public int Port { get; set; } } \ No newline at end of file diff --git a/src/WireMock.Net/Owin/HostUrlOptions.cs b/src/WireMock.Net/Owin/HostUrlOptions.cs index dd94ce331..77dcf1e0d 100644 --- a/src/WireMock.Net/Owin/HostUrlOptions.cs +++ b/src/WireMock.Net/Owin/HostUrlOptions.cs @@ -1,46 +1,47 @@ -using System.Collections.Generic; +using System.Collections.Generic; using WireMock.Util; -namespace WireMock.Owin +namespace WireMock.Owin; + +internal class HostUrlOptions { - internal class HostUrlOptions - { - private const string LOCALHOST = "localhost"; + private const string LOCALHOST = "localhost"; - public ICollection Urls { get; set; } + public ICollection? Urls { get; set; } - public int? Port { get; set; } + public int? Port { get; set; } - public bool UseSSL { get; set; } + public bool UseSSL { get; set; } - public ICollection GetDetails() + public ICollection GetDetails() + { + var list = new List(); + if (Urls == null) { - var list = new List(); - if (Urls == null) - { - int port = Port > 0 ? Port.Value : FindFreeTcpPort(); - string protocol = UseSSL ? "https" : "http"; - list.Add(new HostUrlDetails { IsHttps = UseSSL, Url = $"{protocol}://{LOCALHOST}:{port}", Protocol = protocol, Host = LOCALHOST, Port = port }); - } - else + int port = Port > 0 ? Port.Value : FindFreeTcpPort(); + string protocol = UseSSL ? "https" : "http"; + list.Add(new HostUrlDetails { IsHttps = UseSSL, Url = $"{protocol}://{LOCALHOST}:{port}", Protocol = protocol, Host = LOCALHOST, Port = port }); + } + else + { + foreach (string url in Urls) { - foreach (string url in Urls) + if (PortUtils.TryExtract(url, out bool isHttps, out var protocol, out var host, out int port)) { - PortUtils.TryExtract(url, out bool isHttps, out string protocol, out string host, out int port); list.Add(new HostUrlDetails { IsHttps = isHttps, Url = url, Protocol = protocol, Host = host, Port = port }); } } - - return list; } - private int FindFreeTcpPort() - { + return list; + } + + private static int FindFreeTcpPort() + { #if USE_ASPNETCORE || NETSTANDARD2_0 || NETSTANDARD2_1 - return 0; + return 0; #else - return PortUtils.FindFreeTcpPort(); + return PortUtils.FindFreeTcpPort(); #endif - } } } \ No newline at end of file diff --git a/src/WireMock.Net/Owin/IMappingMatcher.cs b/src/WireMock.Net/Owin/IMappingMatcher.cs index a1e54e7b7..ffcecd749 100644 --- a/src/WireMock.Net/Owin/IMappingMatcher.cs +++ b/src/WireMock.Net/Owin/IMappingMatcher.cs @@ -1,7 +1,6 @@ -namespace WireMock.Owin +namespace WireMock.Owin; + +internal interface IMappingMatcher { - internal interface IMappingMatcher - { - (MappingMatcherResult Match, MappingMatcherResult Partial) FindBestMatch(RequestMessage request); - } + (MappingMatcherResult? Match, MappingMatcherResult? Partial) FindBestMatch(RequestMessage request); } \ No newline at end of file diff --git a/src/WireMock.Net/Owin/IOwinSelfHost.cs b/src/WireMock.Net/Owin/IOwinSelfHost.cs index 81828ef63..781445fcb 100644 --- a/src/WireMock.Net/Owin/IOwinSelfHost.cs +++ b/src/WireMock.Net/Owin/IOwinSelfHost.cs @@ -1,36 +1,35 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Threading.Tasks; using System; -namespace WireMock.Owin +namespace WireMock.Owin; + +interface IOwinSelfHost { - interface IOwinSelfHost - { - /// - /// Gets a value indicating whether this server is started. - /// - /// - /// true if this server is started; otherwise, false. - /// - bool IsStarted { get; } + /// + /// Gets a value indicating whether this server is started. + /// + /// + /// true if this server is started; otherwise, false. + /// + bool IsStarted { get; } - /// - /// Gets the urls. - /// - List Urls { get; } + /// + /// Gets the urls. + /// + List Urls { get; } - /// - /// Gets the ports. - /// - List Ports { get; } + /// + /// Gets the ports. + /// + List Ports { get; } - /// - /// The exception occurred when the host is running - /// - Exception RunningException { get; } + /// + /// The exception occurred when the host is running. + /// + Exception? RunningException { get; } - Task StartAsync(); + Task StartAsync(); - Task StopAsync(); - } + Task StopAsync(); } \ No newline at end of file diff --git a/src/WireMock.Net/Owin/Mappers/OwinRequestMapper.cs b/src/WireMock.Net/Owin/Mappers/OwinRequestMapper.cs index dbdcafde7..29afe592e 100644 --- a/src/WireMock.Net/Owin/Mappers/OwinRequestMapper.cs +++ b/src/WireMock.Net/Owin/Mappers/OwinRequestMapper.cs @@ -27,7 +27,7 @@ public async Task MapAsync(IRequest request, IWireMockMiddleware string method = request.Method; - Dictionary? headers = null; + var headers = new Dictionary(); IEnumerable? contentEncodingHeader = null; if (request.Headers.Any()) { @@ -43,7 +43,7 @@ public async Task MapAsync(IRequest request, IWireMockMiddleware } } - IDictionary? cookies = null; + var cookies = new Dictionary(); if (request.Cookies.Any()) { cookies = new Dictionary(); @@ -75,13 +75,24 @@ private static (UrlDetails UrlDetails, string ClientIP) ParseRequest(IRequest re { #if !USE_ASPNETCORE var urlDetails = UrlUtils.Parse(request.Uri, request.PathBase); - string clientIP = request.RemoteIpAddress; + var clientIP = request.RemoteIpAddress; #else var urlDetails = UrlUtils.Parse(new Uri(request.GetEncodedUrl()), request.PathBase); + var connection = request.HttpContext.Connection; - string clientIP = connection.RemoteIpAddress.IsIPv4MappedToIPv6 - ? connection.RemoteIpAddress.MapToIPv4().ToString() - : connection.RemoteIpAddress.ToString(); + string clientIP; + if (connection.RemoteIpAddress is null) + { + clientIP = string.Empty; + } + else if (connection.RemoteIpAddress.IsIPv4MappedToIPv6) + { + clientIP = connection.RemoteIpAddress.MapToIPv4().ToString(); + } + else + { + clientIP = connection.RemoteIpAddress.ToString(); + } #endif return (urlDetails, clientIP); } diff --git a/src/WireMock.Net/Owin/MappingMatcher.cs b/src/WireMock.Net/Owin/MappingMatcher.cs index de78afc63..704af303f 100644 --- a/src/WireMock.Net/Owin/MappingMatcher.cs +++ b/src/WireMock.Net/Owin/MappingMatcher.cs @@ -4,72 +4,71 @@ using WireMock.Extensions; using Stef.Validation; -namespace WireMock.Owin +namespace WireMock.Owin; + +internal class MappingMatcher : IMappingMatcher { - internal class MappingMatcher : IMappingMatcher + private readonly IWireMockMiddlewareOptions _options; + + public MappingMatcher(IWireMockMiddlewareOptions options) { - private readonly IWireMockMiddlewareOptions _options; + Guard.NotNull(options, nameof(options)); - public MappingMatcher(IWireMockMiddlewareOptions options) - { - Guard.NotNull(options, nameof(options)); + _options = options; + } - _options = options; - } + public (MappingMatcherResult? Match, MappingMatcherResult? Partial) FindBestMatch(RequestMessage request) + { + var possibleMappings = new List(); - public (MappingMatcherResult Match, MappingMatcherResult Partial) FindBestMatch(RequestMessage request) + foreach (var mapping in _options.Mappings.Values.Where(m => m.TimeSettings.IsValid())) { - var mappings = new List(); - - foreach (var mapping in _options.Mappings.Values.Where(m => m.TimeSettings.IsValid())) + try { - try - { - var nextState = GetNextState(mapping); + var nextState = GetNextState(mapping); - mappings.Add(new MappingMatcherResult - { - Mapping = mapping, - RequestMatchResult = mapping.GetRequestMatchResult(request, nextState) - }); - } - catch (Exception ex) + possibleMappings.Add(new MappingMatcherResult { - _options.Logger.Error($"Getting a Request MatchResult for Mapping '{mapping.Guid}' failed. This mapping will not be evaluated. Exception: {ex}"); - } + Mapping = mapping, + RequestMatchResult = mapping.GetRequestMatchResult(request, nextState) + }); } - - var partialMappings = mappings - .Where(pm => (pm.Mapping.IsAdminInterface && pm.RequestMatchResult.IsPerfectMatch) || !pm.Mapping.IsAdminInterface) - .OrderBy(m => m.RequestMatchResult) - .ThenBy(m => m.Mapping.Priority) - .ToList(); - var partialMatch = partialMappings.FirstOrDefault(pm => pm.RequestMatchResult.AverageTotalScore > 0.0); - - if (_options.AllowPartialMapping == true) + catch (Exception ex) { - return (partialMatch, partialMatch); + _options.Logger.Error($"Getting a Request MatchResult for Mapping '{mapping.Guid}' failed. This mapping will not be evaluated. Exception: {ex}"); } + } - var match = mappings - .Where(m => m.RequestMatchResult.IsPerfectMatch) - .OrderBy(m => m.Mapping.Priority).ThenBy(m => m.RequestMatchResult) - .FirstOrDefault(); + var partialMappings = possibleMappings + .Where(pm => (pm.Mapping.IsAdminInterface && pm.RequestMatchResult.IsPerfectMatch) || !pm.Mapping.IsAdminInterface) + .OrderBy(m => m.RequestMatchResult) + .ThenBy(m => m.Mapping.Priority) + .ToList(); + var partialMatch = partialMappings.FirstOrDefault(pm => pm.RequestMatchResult.AverageTotalScore > 0.0); - return (match, partialMatch); + if (_options.AllowPartialMapping == true) + { + return (partialMatch, partialMatch); } - private string? GetNextState(IMapping mapping) - { - // If the mapping does not have a scenario or _options.Scenarios does not contain this scenario from the mapping, - // just return null to indicate that there is no next state. - if (mapping.Scenario == null || !_options.Scenarios.ContainsKey(mapping.Scenario)) - { - return null; - } + var match = possibleMappings + .Where(m => m.RequestMatchResult.IsPerfectMatch) + .OrderBy(m => m.Mapping.Priority).ThenBy(m => m.RequestMatchResult) + .FirstOrDefault(); - // Else just return the next state - return _options.Scenarios[mapping.Scenario].NextState; + return (match, partialMatch); + } + + private string? GetNextState(IMapping mapping) + { + // If the mapping does not have a scenario or _options.Scenarios does not contain this scenario from the mapping, + // just return null to indicate that there is no next state. + if (mapping.Scenario == null || !_options.Scenarios.ContainsKey(mapping.Scenario)) + { + return null; } + + // Else just return the next state + return _options.Scenarios[mapping.Scenario].NextState; } } \ No newline at end of file diff --git a/src/WireMock.Net/Owin/MappingMatcherResult.cs b/src/WireMock.Net/Owin/MappingMatcherResult.cs index 509327d61..344483036 100644 --- a/src/WireMock.Net/Owin/MappingMatcherResult.cs +++ b/src/WireMock.Net/Owin/MappingMatcherResult.cs @@ -1,11 +1,10 @@ using WireMock.Matchers.Request; -namespace WireMock.Owin +namespace WireMock.Owin; + +internal class MappingMatcherResult { - internal class MappingMatcherResult - { - public IMapping Mapping { get; set; } + public IMapping Mapping { get; set; } - public IRequestMatchResult RequestMatchResult { get; set; } - } + public IRequestMatchResult RequestMatchResult { get; set; } } \ No newline at end of file diff --git a/src/WireMock.Net/Owin/OwinSelfHost.cs b/src/WireMock.Net/Owin/OwinSelfHost.cs index d0740a9f3..58992ff16 100644 --- a/src/WireMock.Net/Owin/OwinSelfHost.cs +++ b/src/WireMock.Net/Owin/OwinSelfHost.cs @@ -1,110 +1,109 @@ #if !USE_ASPNETCORE -using JetBrains.Annotations; using Microsoft.Owin.Hosting; using Owin; using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using JetBrains.Annotations; using WireMock.Logging; using WireMock.Owin.Mappers; using Stef.Validation; -namespace WireMock.Owin +namespace WireMock.Owin; + +internal class OwinSelfHost : IOwinSelfHost { - internal class OwinSelfHost : IOwinSelfHost - { - private readonly IWireMockMiddlewareOptions _options; - private readonly CancellationTokenSource _cts = new CancellationTokenSource(); - private readonly IWireMockLogger _logger; + private readonly IWireMockMiddlewareOptions _options; + private readonly CancellationTokenSource _cts = new(); + private readonly IWireMockLogger _logger; - private Exception _runningException; + private Exception? _runningException; - public OwinSelfHost([NotNull] IWireMockMiddlewareOptions options, [NotNull] HostUrlOptions urlOptions) - { - Guard.NotNull(options, nameof(options)); - Guard.NotNull(urlOptions, nameof(urlOptions)); + public OwinSelfHost(IWireMockMiddlewareOptions options, HostUrlOptions urlOptions) + { + Guard.NotNull(options, nameof(options)); + Guard.NotNull(urlOptions, nameof(urlOptions)); - _options = options; - _logger = options.Logger ?? new WireMockConsoleLogger(); + _options = options; + _logger = options.Logger ?? new WireMockConsoleLogger(); - foreach (var detail in urlOptions.GetDetails()) - { - Urls.Add(detail.Url); - Ports.Add(detail.Port); - } + foreach (var detail in urlOptions.GetDetails()) + { + Urls.Add(detail.Url); + Ports.Add(detail.Port); } + } - public bool IsStarted { get; private set; } + public bool IsStarted { get; private set; } - public List Urls { get; } = new List(); + public List Urls { get; } = new(); - public List Ports { get; } = new List(); + public List Ports { get; } = new(); - public Exception RunningException => _runningException; + public Exception? RunningException => _runningException; - [PublicAPI] - public Task StartAsync() - { - return Task.Run(StartServers, _cts.Token); - } + [PublicAPI] + public Task StartAsync() + { + return Task.Run(StartServers, _cts.Token); + } - [PublicAPI] - public Task StopAsync() - { - _cts.Cancel(); + [PublicAPI] + public Task StopAsync() + { + _cts.Cancel(); - return Task.FromResult(true); - } + return Task.FromResult(true); + } - private void StartServers() - { + private void StartServers() + { #if NET46 - _logger.Info("Server using .net 4.6.1 or higher"); + _logger.Info("Server using .net 4.6.1 or higher"); #else - _logger.Info("Server using .net 4.5.x"); + _logger.Info("Server using .net 4.5.x"); #endif - var servers = new List(); + var servers = new List(); - try - { - var requestMapper = new OwinRequestMapper(); - var responseMapper = new OwinResponseMapper(_options); - var matcher = new MappingMatcher(_options); - - Action startup = app => - { - app.Use(_options, responseMapper); - _options.PreWireMockMiddlewareInit?.Invoke(app); - app.Use(_options, requestMapper, responseMapper, matcher); - _options.PostWireMockMiddlewareInit?.Invoke(app); - }; - - foreach (var url in Urls) - { - servers.Add(WebApp.Start(url, startup)); - } - - IsStarted = true; - - // WaitHandle is signaled when the token is cancelled, - // which will be more efficient than Thread.Sleep in while loop - _cts.Token.WaitHandle.WaitOne(); - } - catch (Exception e) + try + { + var requestMapper = new OwinRequestMapper(); + var responseMapper = new OwinResponseMapper(_options); + var matcher = new MappingMatcher(_options); + + Action startup = app => { - // Expose exception of starting host, otherwise it's hard to be troubleshooting if keeping quiet - // For example, WebApp.Start will fail with System.MissingMemberException if Microsoft.Owin.Host.HttpListener.dll is being located - // https://stackoverflow.com/questions/25090211/owin-httplistener-not-located/31369857 - _runningException = e; - _logger.Error(e.ToString()); - } - finally + app.Use(_options, responseMapper); + _options.PreWireMockMiddlewareInit?.Invoke(app); + app.Use(_options, requestMapper, responseMapper, matcher); + _options.PostWireMockMiddlewareInit?.Invoke(app); + }; + + foreach (var url in Urls) { - IsStarted = false; - // Dispose all servers in finally block to make sure clean up allocated resource on error happening - servers.ForEach(s => s.Dispose()); + servers.Add(WebApp.Start(url, startup)); } + + IsStarted = true; + + // WaitHandle is signaled when the token is cancelled, + // which will be more efficient than Thread.Sleep in while loop + _cts.Token.WaitHandle.WaitOne(); + } + catch (Exception e) + { + // Expose exception of starting host, otherwise it's hard to be troubleshooting if keeping quiet + // For example, WebApp.Start will fail with System.MissingMemberException if Microsoft.Owin.Host.HttpListener.dll is being located + // https://stackoverflow.com/questions/25090211/owin-httplistener-not-located/31369857 + _runningException = e; + _logger.Error(e.ToString()); + } + finally + { + IsStarted = false; + // Dispose all servers in finally block to make sure clean up allocated resource on error happening + servers.ForEach(s => s.Dispose()); } } } diff --git a/src/WireMock.Net/Owin/WireMockMiddleware.cs b/src/WireMock.Net/Owin/WireMockMiddleware.cs index 5c79eecf6..5bbb90976 100644 --- a/src/WireMock.Net/Owin/WireMockMiddleware.cs +++ b/src/WireMock.Net/Owin/WireMockMiddleware.cs @@ -1,6 +1,7 @@ using System; using System.Threading.Tasks; using System.Linq; +using System.Net; using Stef.Validation; using WireMock.Logging; using WireMock.Matchers; @@ -74,10 +75,16 @@ private async Task InvokeInternalAsync(IContext ctx) var logRequest = false; IResponseMessage? response = null; (MappingMatcherResult? Match, MappingMatcherResult? Partial) result = (null, null); + try { - foreach (var mapping in _options.Mappings.Values.Where(m => m?.Scenario != null)) + foreach (var mapping in _options.Mappings.Values) { + if (mapping.Scenario is null) + { + continue; + } + // Set scenario start if (!_options.Scenarios.ContainsKey(mapping.Scenario) && mapping.IsStartState) { @@ -107,7 +114,7 @@ private async Task InvokeInternalAsync(IContext ctx) if (!present || _options.AuthenticationMatcher.IsMatch(authorization.ToString()) < MatchScores.Perfect) { _options.Logger.Error("HttpStatusCode set to 401"); - response = ResponseMessageBuilder.Create(null, 401); + response = ResponseMessageBuilder.Create(null, HttpStatusCode.Unauthorized); return; } } @@ -194,7 +201,7 @@ private async Task InvokeInternalAsync(IContext ctx) private async Task SendToWebhooksAsync(IMapping mapping, IRequestMessage request, IResponseMessage response) { - for (int index = 0; index < mapping.Webhooks.Length; index++) + for (int index = 0; index < mapping.Webhooks?.Length; index++) { var httpClientForWebhook = HttpClientBuilder.Build(mapping.Settings.WebhookSettings ?? new WebhookSettings()); var webhookSender = new WebhookSender(mapping.Settings); @@ -212,7 +219,7 @@ private async Task SendToWebhooksAsync(IMapping mapping, IRequestMessage request private void UpdateScenarioState(IMapping mapping) { - var scenario = _options.Scenarios[mapping.Scenario]; + var scenario = _options.Scenarios[mapping.Scenario!]; // Increase the number of times this state has been executed scenario.Counter++; diff --git a/src/WireMock.Net/RequestMessage.cs b/src/WireMock.Net/RequestMessage.cs index 2470ee0f9..9c67444fe 100644 --- a/src/WireMock.Net/RequestMessage.cs +++ b/src/WireMock.Net/RequestMessage.cs @@ -47,10 +47,10 @@ public class RequestMessage : IRequestMessage public string Method { get; } /// - public IDictionary>? Headers { get; } + public IDictionary> Headers { get; } /// - public IDictionary? Cookies { get; } + public IDictionary Cookies { get; } /// public IDictionary>? Query { get; } diff --git a/test/WireMock.Net.Tests/Owin/MappingMatcherTests.cs b/test/WireMock.Net.Tests/Owin/MappingMatcherTests.cs index 5c9377d72..3ed1ba9b7 100644 --- a/test/WireMock.Net.Tests/Owin/MappingMatcherTests.cs +++ b/test/WireMock.Net.Tests/Owin/MappingMatcherTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Concurrent; using FluentAssertions; using Moq; @@ -9,183 +9,195 @@ using WireMock.Util; using Xunit; -namespace WireMock.Net.Tests.Owin +namespace WireMock.Net.Tests.Owin; + +public class MappingMatcherTests { - public class MappingMatcherTests + private readonly Mock _optionsMock; + private readonly MappingMatcher _sut; + + public MappingMatcherTests() { - private readonly Mock _optionsMock; - private readonly MappingMatcher _sut; + _optionsMock = new Mock(); + _optionsMock.SetupAllProperties(); + _optionsMock.Setup(o => o.Mappings).Returns(new ConcurrentDictionary()); + _optionsMock.Setup(o => o.LogEntries).Returns(new ConcurrentObservableCollection()); + _optionsMock.Setup(o => o.Scenarios).Returns(new ConcurrentDictionary()); + + var loggerMock = new Mock(); + loggerMock.SetupAllProperties(); + loggerMock.Setup(l => l.Error(It.IsAny())); + _optionsMock.Setup(o => o.Logger).Returns(loggerMock.Object); + + _sut = new MappingMatcher(_optionsMock.Object); + } - public MappingMatcherTests() - { - _optionsMock = new Mock(); - _optionsMock.SetupAllProperties(); - _optionsMock.Setup(o => o.Mappings).Returns(new ConcurrentDictionary()); - _optionsMock.Setup(o => o.LogEntries).Returns(new ConcurrentObservableCollection()); - _optionsMock.Setup(o => o.Scenarios).Returns(new ConcurrentDictionary()); - - var loggerMock = new Mock(); - loggerMock.SetupAllProperties(); - loggerMock.Setup(l => l.Error(It.IsAny())); - _optionsMock.Setup(o => o.Logger).Returns(loggerMock.Object); - - _sut = new MappingMatcher(_optionsMock.Object); - } + [Fact] + public void MappingMatcher_FindBestMatch_WhenNoMappingsDefined_ShouldReturnNull() + { + // Assign + var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "GET", "::1"); - [Fact] - public void MappingMatcher_FindBestMatch_WhenNoMappingsDefined_ShouldReturnNull() - { - // Assign - var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "GET", "::1"); + // Act + var result = _sut.FindBestMatch(request); - // Act - var result = _sut.FindBestMatch(request); + // Assert + result.Match.Should().BeNull(); + result.Partial.Should().BeNull(); + } - // Assert - result.Match.Should().BeNull(); - result.Partial.Should().BeNull(); - } + [Fact] + public void MappingMatcher_FindBestMatch_WhenMappingThrowsException_ShouldReturnNull() + { + // Assign + var mappingMock = new Mock(); + mappingMock.Setup(m => m.GetRequestMatchResult(It.IsAny(), It.IsAny())).Throws(); - [Fact] - public void MappingMatcher_FindBestMatch_WhenMappingThrowsException_ShouldReturnNull() - { - // Assign - var mappingMock = new Mock(); - mappingMock.Setup(m => m.GetRequestMatchResult(It.IsAny(), It.IsAny())).Throws(); + var mappings = new ConcurrentDictionary(); + mappings.TryAdd(Guid.NewGuid(), mappingMock.Object); - var mappings = new ConcurrentDictionary(); - mappings.TryAdd(Guid.NewGuid(), mappingMock.Object); + _optionsMock.Setup(o => o.Mappings).Returns(mappings); - _optionsMock.Setup(o => o.Mappings).Returns(mappings); + var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "GET", "::1"); - var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "GET", "::1"); + // Act + var result = _sut.FindBestMatch(request); - // Act - var result = _sut.FindBestMatch(request); + // Assert + result.Match.Should().BeNull(); + result.Partial.Should().BeNull(); + } - // Assert - result.Match.Should().BeNull(); - result.Partial.Should().BeNull(); - } + [Fact] + public void MappingMatcher_FindBestMatch_WhenAllowPartialMappingIsFalse_ShouldReturnExactMatch() + { + // Assign + var guid1 = Guid.Parse("00000000-0000-0000-0000-000000000001"); + var guid2 = Guid.Parse("00000000-0000-0000-0000-000000000002"); + var mappings = InitMappings + ( + (guid1, new[] { 0.1 }), + (guid2, new[] { 1.0 }) + ); + _optionsMock.Setup(o => o.Mappings).Returns(mappings); + + var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "GET", "::1"); + + // Act + var result = _sut.FindBestMatch(request); + + // Assert + result.Match.Should().NotBeNull(); + result.Match!.Mapping.Guid.Should().Be(guid2); + result.Match.RequestMatchResult.AverageTotalScore.Should().Be(1.0); + + result.Partial.Should().NotBeNull(); + result.Partial!.Mapping.Guid.Should().Be(guid2); + result.Partial.RequestMatchResult.AverageTotalScore.Should().Be(1.0); + } - [Fact] - public void MappingMatcher_FindBestMatch_WhenAllowPartialMappingIsFalse_ShouldReturnExactMatch() - { - // Assign - var guid1 = Guid.Parse("00000000-0000-0000-0000-000000000001"); - var guid2 = Guid.Parse("00000000-0000-0000-0000-000000000002"); - var mappings = InitMappings( - (guid1, new[] { 0.1 }), - (guid2, new[] { 1.0 }) - ); - _optionsMock.Setup(o => o.Mappings).Returns(mappings); - - var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "GET", "::1"); - - // Act - var result = _sut.FindBestMatch(request); - - // Assert - result.Match.Mapping.Guid.Should().Be(guid2); - result.Match.RequestMatchResult.AverageTotalScore.Should().Be(1.0); - result.Partial.Mapping.Guid.Should().Be(guid2); - result.Partial.RequestMatchResult.AverageTotalScore.Should().Be(1.0); - } + [Fact] + public void MappingMatcher_FindBestMatch_WhenAllowPartialMappingIsFalse_AndNoExactMatch_ShouldReturnNullExactMatch_And_PartialMatch() + { + // Assign + var guid1 = Guid.Parse("00000000-0000-0000-0000-000000000001"); + var guid2 = Guid.Parse("00000000-0000-0000-0000-000000000002"); + var mappings = InitMappings + ( + (guid1, new[] { 0.1 }), + (guid2, new[] { 0.9 }) + ); + _optionsMock.Setup(o => o.Mappings).Returns(mappings); + + var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "GET", "::1"); + + // Act + var result = _sut.FindBestMatch(request); + + // Assert + result.Match.Should().BeNull(); + + result.Partial.Should().NotBeNull(); + result.Partial!.Mapping.Guid.Should().Be(guid2); + result.Partial.RequestMatchResult.AverageTotalScore.Should().Be(0.9); + } - [Fact] - public void MappingMatcher_FindBestMatch_WhenAllowPartialMappingIsFalse_AndNoExactmatch_ShouldReturnNullExactMatch_And_PartialMatch() - { - // Assign - var guid1 = Guid.Parse("00000000-0000-0000-0000-000000000001"); - var guid2 = Guid.Parse("00000000-0000-0000-0000-000000000002"); - var mappings = InitMappings( - (guid1, new[] { 0.1 }), - (guid2, new[] { 0.9 }) - ); - _optionsMock.Setup(o => o.Mappings).Returns(mappings); - - var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "GET", "::1"); - - // Act - var result = _sut.FindBestMatch(request); - - // Assert - result.Match.Should().BeNull(); - result.Partial.Mapping.Guid.Should().Be(guid2); - result.Partial.RequestMatchResult.AverageTotalScore.Should().Be(0.9); - } + [Fact] + public void MappingMatcher_FindBestMatch_WhenAllowPartialMappingIsTrue_ShouldReturnAnyMatch() + { + // Assign + var guid1 = Guid.Parse("00000000-0000-0000-0000-000000000001"); + var guid2 = Guid.Parse("00000000-0000-0000-0000-000000000002"); + + _optionsMock.SetupGet(o => o.AllowPartialMapping).Returns(true); + var mappings = InitMappings( + (guid1, new[] { 0.1 }), + (guid2, new[] { 0.9 }) + ); + _optionsMock.Setup(o => o.Mappings).Returns(mappings); + + var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "GET", "::1"); + + // Act + var result = _sut.FindBestMatch(request); + + // Assert + result.Match.Should().NotBeNull(); + result.Match!.Mapping.Guid.Should().Be(guid2); + result.Match.RequestMatchResult.AverageTotalScore.Should().Be(0.9); + + result.Partial.Should().NotBeNull(); + result.Partial!.Mapping.Guid.Should().Be(guid2); + result.Partial.RequestMatchResult.AverageTotalScore.Should().Be(0.9); + } - [Fact] - public void MappingMatcher_FindBestMatch_WhenAllowPartialMappingIsTrue_ShouldReturnAnyMatch() - { - // Assign - var guid1 = Guid.Parse("00000000-0000-0000-0000-000000000001"); - var guid2 = Guid.Parse("00000000-0000-0000-0000-000000000002"); - - _optionsMock.SetupGet(o => o.AllowPartialMapping).Returns(true); - var mappings = InitMappings( - (guid1, new[] { 0.1 }), - (guid2, new[] { 0.9 }) - ); - _optionsMock.Setup(o => o.Mappings).Returns(mappings); - - var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "GET", "::1"); - - // Act - var result = _sut.FindBestMatch(request); - - // Assert - result.Match.Mapping.Guid.Should().Be(guid2); - result.Match.RequestMatchResult.AverageTotalScore.Should().Be(0.9); - result.Partial.Mapping.Guid.Should().Be(guid2); - result.Partial.RequestMatchResult.AverageTotalScore.Should().Be(0.9); - } + [Fact] + public void MappingMatcher_FindBestMatch_WhenAllowPartialMappingIsFalse_And_WithSameAverageScoreButMoreMatchers_ReturnsMatchWithMoreMatchers() + { + // Assign + var guid1 = Guid.Parse("00000000-0000-0000-0000-000000000001"); + var guid2 = Guid.Parse("00000000-0000-0000-0000-000000000002"); + var mappings = InitMappings( + (guid1, new[] { 1.0 }), + (guid2, new[] { 1.0, 1.0 }) + ); + _optionsMock.Setup(o => o.Mappings).Returns(mappings); + + var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "GET", "::1"); + + // Act + var result = _sut.FindBestMatch(request); + + // Assert and Verify + result.Match.Should().NotBeNull(); + result.Match!.Mapping.Guid.Should().Be(guid2); + result.Match.RequestMatchResult.AverageTotalScore.Should().Be(1.0); + + result.Partial.Should().NotBeNull(); + result.Partial!.Mapping.Guid.Should().Be(guid2); + result.Partial.RequestMatchResult.AverageTotalScore.Should().Be(1.0); + } - [Fact] - public void MappingMatcher_FindBestMatch_WhenAllowPartialMappingIsFalse_And_WithSameAverageScoreButMoreMatchers_ReturnsMatchWithMoreMatchers() - { - // Assign - var guid1 = Guid.Parse("00000000-0000-0000-0000-000000000001"); - var guid2 = Guid.Parse("00000000-0000-0000-0000-000000000002"); - var mappings = InitMappings( - (guid1, new[] { 1.0 }), - (guid2, new[] { 1.0, 1.0 }) - ); - _optionsMock.Setup(o => o.Mappings).Returns(mappings); - - var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "GET", "::1"); - - // Act - var result = _sut.FindBestMatch(request); - - // Assert and Verify - result.Match.Mapping.Guid.Should().Be(guid2); - result.Match.RequestMatchResult.AverageTotalScore.Should().Be(1.0); - result.Partial.Mapping.Guid.Should().Be(guid2); - result.Partial.RequestMatchResult.AverageTotalScore.Should().Be(1.0); - } + private static ConcurrentDictionary InitMappings(params (Guid guid, double[] scores)[] matches) + { + var mappings = new ConcurrentDictionary(); - private ConcurrentDictionary InitMappings(params (Guid guid, double[] scores)[] matches) + foreach (var match in matches) { - var mappings = new ConcurrentDictionary(); + var mappingMock = new Mock(); + mappingMock.SetupGet(m => m.Guid).Returns(match.guid); - foreach (var match in matches) + var requestMatchResult = new RequestMatchResult(); + foreach (var score in match.scores) { - var mappingMock = new Mock(); - mappingMock.SetupGet(m => m.Guid).Returns(match.guid); - - var requestMatchResult = new RequestMatchResult(); - foreach (var score in match.scores) - { - requestMatchResult.AddScore(typeof(object), score); - } - - mappingMock.Setup(m => m.GetRequestMatchResult(It.IsAny(), It.IsAny())).Returns(requestMatchResult); - - mappings.TryAdd(match.guid, mappingMock.Object); + requestMatchResult.AddScore(typeof(object), score); } - return mappings; + mappingMock.Setup(m => m.GetRequestMatchResult(It.IsAny(), It.IsAny())).Returns(requestMatchResult); + + mappings.TryAdd(match.guid, mappingMock.Object); } + + return mappings; } } \ No newline at end of file diff --git a/test/WireMock.Net.Tests/WireMockServer.Proxy.cs b/test/WireMock.Net.Tests/WireMockServer.Proxy.cs index 58b317c32..def80c602 100644 --- a/test/WireMock.Net.Tests/WireMockServer.Proxy.cs +++ b/test/WireMock.Net.Tests/WireMockServer.Proxy.cs @@ -63,7 +63,8 @@ public async Task WireMockServer_Proxy_AdminFalse_With_SaveMapping_Is_True_And_S { Url = "http://www.google.com", SaveMapping = true, - SaveMappingToFile = false + SaveMappingToFile = false, + ExcludedHeaders = new[] { "Connection" } // Needed for .NET 4.5.x and 4.6.x } }; var server = WireMockServer.Start(settings); @@ -82,7 +83,7 @@ public async Task WireMockServer_Proxy_AdminFalse_With_SaveMapping_Is_True_And_S } // Assert - server.Mappings.Should().HaveCountGreaterOrEqualTo(2); // For .NET 4.5.2 and 4.6.x this is 3? + server.Mappings.Should().HaveCount(2); } [Fact] @@ -95,7 +96,8 @@ public async Task WireMockServer_Proxy_AdminTrue_With_SaveMapping_Is_True_And_Sa { Url = "http://www.google.com", SaveMapping = true, - SaveMappingToFile = false + SaveMappingToFile = false, + ExcludedHeaders = new[] { "Connection" } // Needed for .NET 4.5.x and 4.6.x }, StartAdminInterface = true }; @@ -114,7 +116,7 @@ public async Task WireMockServer_Proxy_AdminTrue_With_SaveMapping_Is_True_And_Sa } // Assert - server.Mappings.Should().HaveCountGreaterOrEqualTo(28); // For .NET 4.5.2 and 4.6.x this is 29? + server.Mappings.Should().HaveCount(28); } [Fact]