From e7754d195e4f9f6052468473715e2ed7021e7864 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Fri, 29 Jul 2022 22:46:38 +0200 Subject: [PATCH 1/4] ProxyHelper fixes --- WireMock.Net Solution.sln.DotSettings | 1 + .../Models/ITimeSettings.cs | 33 +- .../Extensions/TimeSettingsExtensions.cs | 47 +- src/WireMock.Net/IMapping.cs | 8 +- src/WireMock.Net/Mapping.cs | 262 ++-- .../RequestMessageScenarioAndStateMatcher.cs | 67 +- src/WireMock.Net/Owin/MappingMatcher.cs | 4 +- src/WireMock.Net/Proxy/ProxyHelper.cs | 21 +- src/WireMock.Net/ResponseMessageBuilder.cs | 6 + .../Serialization/TimeSettingsMapper.cs | 35 +- .../Server/WireMockServer.Admin.cs | 37 +- src/WireMock.Net/Server/WireMockServer.cs | 16 +- .../WireMockServer.Proxy.cs | 1221 +++++++++-------- 13 files changed, 903 insertions(+), 855 deletions(-) diff --git a/WireMock.Net Solution.sln.DotSettings b/WireMock.Net Solution.sln.DotSettings index b54c5d82d..e6147e2f8 100644 --- a/WireMock.Net Solution.sln.DotSettings +++ b/WireMock.Net Solution.sln.DotSettings @@ -7,6 +7,7 @@ SSL TE TSV + TTL WWW XMS XUA diff --git a/src/WireMock.Net.Abstractions/Models/ITimeSettings.cs b/src/WireMock.Net.Abstractions/Models/ITimeSettings.cs index f9c8dc121..fb4c44f8d 100644 --- a/src/WireMock.Net.Abstractions/Models/ITimeSettings.cs +++ b/src/WireMock.Net.Abstractions/Models/ITimeSettings.cs @@ -1,25 +1,24 @@ using System; -namespace WireMock.Models +namespace WireMock.Models; + +/// +/// TimeSettings: Start, End and TTL +/// +public interface ITimeSettings { /// - /// TimeSettings: Start, End and TTL + /// Gets or sets the DateTime from which this mapping should be used. In case this is not defined, it's used (default behavior). /// - public interface ITimeSettings - { - /// - /// Gets or sets the DateTime from which this mapping should be used. In case this is not defined, it's used (default behavior). - /// - DateTime? Start { get; set; } + DateTime? Start { get; set; } - /// - /// Gets or sets the DateTime from until this mapping should be used. In case this is not defined, it's used forever (default behavior). - /// - DateTime? End { get; set; } + /// + /// Gets or sets the DateTime from until this mapping should be used. In case this is not defined, it's used forever (default behavior). + /// + DateTime? End { get; set; } - /// - /// Gets or sets the TTL (Time To Live) in seconds for this mapping. In case this is not defined, it's used (default behavior). - /// - int? TTL { get; set; } - } + /// + /// Gets or sets the TTL (Time To Live) in seconds for this mapping. In case this is not defined, it's used (default behavior). + /// + int? TTL { get; set; } } \ No newline at end of file diff --git a/src/WireMock.Net/Extensions/TimeSettingsExtensions.cs b/src/WireMock.Net/Extensions/TimeSettingsExtensions.cs index 138ffe1d1..250aeca21 100644 --- a/src/WireMock.Net/Extensions/TimeSettingsExtensions.cs +++ b/src/WireMock.Net/Extensions/TimeSettingsExtensions.cs @@ -1,35 +1,34 @@ using System; -using JetBrains.Annotations; using WireMock.Models; -namespace WireMock.Extensions +namespace WireMock.Extensions; + +internal static class TimeSettingsExtensions { - internal static class TimeSettingsExtensions + public static bool IsValid(this ITimeSettings? settings) { - public static bool IsValid([CanBeNull] this ITimeSettings settings) + if (settings == null) { - if (settings == null) - { - return true; - } + return true; + } - var now = DateTime.Now; - var start = settings.Start != null ? settings.Start.Value : now; - DateTime end; - if (settings.End != null) - { - end = settings.End.Value; - } - else if (settings.TTL != null) - { - end = start.AddSeconds(settings.TTL.Value); - } - else - { - end = DateTime.MaxValue; - } + var now = DateTime.Now; + var start = settings.Start ?? now; + DateTime end; - return now >= start && now <= end; + if (settings.End != null) + { + end = settings.End.Value; + } + else if (settings.TTL != null) + { + end = start.AddSeconds(settings.TTL.Value); + } + else + { + end = DateTime.MaxValue; } + + return now >= start && now <= end; } } \ No newline at end of file diff --git a/src/WireMock.Net/IMapping.cs b/src/WireMock.Net/IMapping.cs index b49dfa934..0b00a8888 100644 --- a/src/WireMock.Net/IMapping.cs +++ b/src/WireMock.Net/IMapping.cs @@ -20,22 +20,22 @@ public interface IMapping /// /// Gets the TimeSettings (Start, End and TTL). /// - ITimeSettings TimeSettings { get; } + ITimeSettings? TimeSettings { get; } /// /// Gets the unique title. /// - string Title { get; } + string? Title { get; } /// /// Gets the description. /// - string Description { get; } + string? Description { get; } /// /// The full filename path for this mapping (only defined for static mappings). /// - string Path { get; set; } + string? Path { get; set; } /// /// Gets the priority. (A low value means higher priority.) diff --git a/src/WireMock.Net/Mapping.cs b/src/WireMock.Net/Mapping.cs index e3fd18c62..108c1244a 100644 --- a/src/WireMock.Net/Mapping.cs +++ b/src/WireMock.Net/Mapping.cs @@ -1,149 +1,147 @@ using System; using System.Threading.Tasks; -using JetBrains.Annotations; using WireMock.Matchers.Request; using WireMock.Models; using WireMock.ResponseProviders; using WireMock.Settings; -namespace WireMock +namespace WireMock; + +/// +/// The Mapping. +/// +public class Mapping : IMapping { + /// + public Guid Guid { get; } + + /// + public string? Title { get; } + + /// + public string? Description { get; } + + /// + public string? Path { get; set; } + + /// + public int Priority { get; } + + /// + public string? Scenario { get; } + + /// + public string? ExecutionConditionState { get; } + + /// + public string? NextState { get; } + + /// + public int? StateTimes { get; } + + /// + public IRequestMatcher RequestMatcher { get; } + + /// + public IResponseProvider Provider { get; } + + /// + public WireMockServerSettings Settings { get; } + + /// + public bool IsStartState => Scenario == null || Scenario != null && NextState != null && ExecutionConditionState == null; + + /// + public bool IsAdminInterface => Provider is DynamicResponseProvider or DynamicAsyncResponseProvider or ProxyAsyncResponseProvider; + + /// + public bool IsProxy => Provider is ProxyAsyncResponseProvider; + + /// + public bool LogMapping => Provider is not (DynamicResponseProvider or DynamicAsyncResponseProvider); + + /// + public IWebhook[]? Webhooks { get; } + + /// + public ITimeSettings? TimeSettings { get; } + /// - /// The Mapping. + /// Initializes a new instance of the class. /// - public class Mapping : IMapping + /// The unique identifier. + /// The unique title (can be null). + /// The description (can be null). + /// The full file path from this mapping title (can be null). + /// The WireMockServerSettings. + /// The request matcher. + /// The provider. + /// The priority for this mapping. + /// The scenario. [Optional] + /// State in which the current mapping can occur. [Optional] + /// The next state which will occur after the current mapping execution. [Optional] + /// Only when the current state is executed this number, the next state which will occur. [Optional] + /// The Webhooks. [Optional] + /// The TimeSettings. [Optional] + public Mapping( + Guid guid, + string? title, + string? description, + string? path, + WireMockServerSettings settings, + IRequestMatcher requestMatcher, + IResponseProvider provider, + int priority, + string? scenario, + string? executionConditionState, + string? nextState, + int? stateTimes, + IWebhook[]? webhooks, + ITimeSettings? timeSettings) { - /// - public Guid Guid { get; } - - /// - public string Title { get; } - - /// - public string Description { get; } - - /// - public string Path { get; set; } - - /// - public int Priority { get; } - - /// - public string Scenario { get; } - - /// - public string ExecutionConditionState { get; } - - /// - public string NextState { get; } - - /// - public int? StateTimes { get; } - - /// - public IRequestMatcher RequestMatcher { get; } - - /// - public IResponseProvider Provider { get; } - - /// - public WireMockServerSettings Settings { get; } - - /// - public bool IsStartState => Scenario == null || Scenario != null && NextState != null && ExecutionConditionState == null; - - /// - public bool IsAdminInterface => Provider is DynamicResponseProvider || Provider is DynamicAsyncResponseProvider || Provider is ProxyAsyncResponseProvider; - - /// - public bool IsProxy => Provider is ProxyAsyncResponseProvider; - - /// - public bool LogMapping => !(Provider is DynamicResponseProvider || Provider is DynamicAsyncResponseProvider); - - /// - public IWebhook[] Webhooks { get; } - - /// - public ITimeSettings TimeSettings { get; } - - /// - /// Initializes a new instance of the class. - /// - /// The unique identifier. - /// The unique title (can be null). - /// The description (can be null). - /// The full file path from this mapping title (can be null). - /// The WireMockServerSettings. - /// The request matcher. - /// The provider. - /// The priority for this mapping. - /// The scenario. [Optional] - /// State in which the current mapping can occur. [Optional] - /// The next state which will occur after the current mapping execution. [Optional] - /// Only when the current state is executed this number, the next state which will occur. [Optional] - /// The Webhooks. [Optional] - /// The TimeSettings. [Optional] - public Mapping( - Guid guid, - string? title, - string? description, - string? path, - WireMockServerSettings settings, - IRequestMatcher requestMatcher, - IResponseProvider provider, - int priority, - string? scenario, - string? executionConditionState, - string? nextState, - int? stateTimes, - IWebhook[]? webhooks, - ITimeSettings? timeSettings) - { - Guid = guid; - Title = title; - Description = description; - Path = path; - Settings = settings; - RequestMatcher = requestMatcher; - Provider = provider; - Priority = priority; - Scenario = scenario; - ExecutionConditionState = executionConditionState; - NextState = nextState; - StateTimes = stateTimes; - Webhooks = webhooks; - TimeSettings = timeSettings; - } + Guid = guid; + Title = title; + Description = description; + Path = path; + Settings = settings; + RequestMatcher = requestMatcher; + Provider = provider; + Priority = priority; + Scenario = scenario; + ExecutionConditionState = executionConditionState; + NextState = nextState; + StateTimes = stateTimes; + Webhooks = webhooks; + TimeSettings = timeSettings; + } - /// - public Task<(IResponseMessage Message, IMapping Mapping)> ProvideResponseAsync(IRequestMessage requestMessage) - { - return Provider.ProvideResponseAsync(requestMessage, Settings); - } + /// + public Task<(IResponseMessage Message, IMapping Mapping)> ProvideResponseAsync(IRequestMessage requestMessage) + { + return Provider.ProvideResponseAsync(requestMessage, Settings); + } - /// - public IRequestMatchResult GetRequestMatchResult(IRequestMessage requestMessage, string nextState) + /// + public IRequestMatchResult GetRequestMatchResult(IRequestMessage requestMessage, string? nextState) + { + var result = new RequestMatchResult(); + + RequestMatcher.GetMatchingScore(requestMessage, result); + + // Only check state if Scenario is defined + if (Scenario != null) { - var result = new RequestMatchResult(); - - RequestMatcher.GetMatchingScore(requestMessage, result); - - // Only check state if Scenario is defined - if (Scenario != null) - { - var matcher = new RequestMessageScenarioAndStateMatcher(nextState, ExecutionConditionState); - matcher.GetMatchingScore(requestMessage, result); - //// If ExecutionConditionState is null, this means that request is the start from a scenario. So just return. - //if (ExecutionConditionState != null) - //{ - // // ExecutionConditionState is not null, so get score for matching with the nextState. - // var matcher = new RequestMessageScenarioAndStateMatcher(nextState, ExecutionConditionState); - // matcher.GetMatchingScore(requestMessage, result); - //} - } - - return result; + var matcher = new RequestMessageScenarioAndStateMatcher(nextState, ExecutionConditionState); + matcher.GetMatchingScore(requestMessage, result); + //// If ExecutionConditionState is null, this means that request is the start from a scenario. So just return. + //if (ExecutionConditionState != null) + //{ + // // ExecutionConditionState is not null, so get score for matching with the nextState. + // var matcher = new RequestMessageScenarioAndStateMatcher(nextState, ExecutionConditionState); + // matcher.GetMatchingScore(requestMessage, result); + //} } + + return result; } } \ No newline at end of file diff --git a/src/WireMock.Net/Matchers/Request/RequestMessageScenarioAndStateMatcher.cs b/src/WireMock.Net/Matchers/Request/RequestMessageScenarioAndStateMatcher.cs index cb374019c..00bb01ee4 100644 --- a/src/WireMock.Net/Matchers/Request/RequestMessageScenarioAndStateMatcher.cs +++ b/src/WireMock.Net/Matchers/Request/RequestMessageScenarioAndStateMatcher.cs @@ -1,46 +1,41 @@ -using JetBrains.Annotations; +namespace WireMock.Matchers.Request; -namespace WireMock.Matchers.Request +/// +/// The scenario and state matcher. +/// +internal class RequestMessageScenarioAndStateMatcher : IRequestMatcher { /// - /// The scenario and state matcher. + /// Execution state condition for the current mapping. /// - internal class RequestMessageScenarioAndStateMatcher : IRequestMatcher - { - /// - /// Execution state condition for the current mapping. - /// - [CanBeNull] - private readonly string _executionConditionState; + private readonly string? _executionConditionState; - /// - /// The next state which will be signaled after the current mapping execution. - /// In case the value is null state will not be changed. - /// - [CanBeNull] - private readonly string _nextState; + /// + /// The next state which will be signaled after the current mapping execution. + /// In case the value is null state will not be changed. + /// + private readonly string? _nextState; - /// - /// Initializes a new instance of the class. - /// - /// The next state. - /// Execution state condition for the current mapping. - public RequestMessageScenarioAndStateMatcher([CanBeNull] string nextState, [CanBeNull] string executionConditionState) - { - _nextState = nextState; - _executionConditionState = executionConditionState; - } + /// + /// Initializes a new instance of the class. + /// + /// The next state. + /// Execution state condition for the current mapping. + public RequestMessageScenarioAndStateMatcher(string? nextState, string? executionConditionState) + { + _nextState = nextState; + _executionConditionState = executionConditionState; + } - /// - public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult) - { - double score = IsMatch(); - return requestMatchResult.AddScore(GetType(), score); - } + /// + public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult) + { + double score = IsMatch(); + return requestMatchResult.AddScore(GetType(), score); + } - private double IsMatch() - { - return Equals(_executionConditionState, _nextState) ? MatchScores.Perfect : MatchScores.Mismatch; - } + private double IsMatch() + { + return Equals(_executionConditionState, _nextState) ? MatchScores.Perfect : MatchScores.Mismatch; } } \ No newline at end of file diff --git a/src/WireMock.Net/Owin/MappingMatcher.cs b/src/WireMock.Net/Owin/MappingMatcher.cs index 303a83489..de78afc63 100644 --- a/src/WireMock.Net/Owin/MappingMatcher.cs +++ b/src/WireMock.Net/Owin/MappingMatcher.cs @@ -25,7 +25,7 @@ public MappingMatcher(IWireMockMiddlewareOptions options) { try { - string nextState = GetNextState(mapping); + var nextState = GetNextState(mapping); mappings.Add(new MappingMatcherResult { @@ -59,7 +59,7 @@ public MappingMatcher(IWireMockMiddlewareOptions options) return (match, partialMatch); } - private string GetNextState(IMapping mapping) + 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. diff --git a/src/WireMock.Net/Proxy/ProxyHelper.cs b/src/WireMock.Net/Proxy/ProxyHelper.cs index a76c655b5..482d049b0 100644 --- a/src/WireMock.Net/Proxy/ProxyHelper.cs +++ b/src/WireMock.Net/Proxy/ProxyHelper.cs @@ -4,6 +4,7 @@ using System.Net.Http; using System.Threading.Tasks; using Stef.Validation; +using WireMock.Constants; using WireMock.Http; using WireMock.Matchers; using WireMock.RequestBuilders; @@ -85,7 +86,7 @@ private IMapping ToMapping(ProxyAndRecordSettings proxyAndRecordSettings, IReque } }); - bool throwExceptionWhenMatcherFails = _settings.ThrowExceptionWhenMatcherFails == true; + var throwExceptionWhenMatcherFails = _settings.ThrowExceptionWhenMatcherFails == true; switch (requestMessage.BodyData?.DetectedBodyType) { case BodyType.Json: @@ -103,6 +104,22 @@ private IMapping ToMapping(ProxyAndRecordSettings proxyAndRecordSettings, IReque var response = Response.Create(responseMessage); - return new Mapping(Guid.NewGuid(), string.Empty, string.Empty, null, _settings, request, response, 0, null, null, null, null, null, null); + return new Mapping + ( + guid: Guid.NewGuid(), + title: $"Proxy Mapping for {requestMessage.Method} {requestMessage.Path}", + description: string.Empty, + path: null, + settings: _settings, + request, + response, + priority: WireMockConstants.ProxyPriority, // was 0 + scenario: null, + executionConditionState: null, + nextState: null, + stateTimes: null, + webhooks: null, + timeSettings: null + ); } } \ No newline at end of file diff --git a/src/WireMock.Net/ResponseMessageBuilder.cs b/src/WireMock.Net/ResponseMessageBuilder.cs index 1b353c0dc..dac1a3602 100644 --- a/src/WireMock.Net/ResponseMessageBuilder.cs +++ b/src/WireMock.Net/ResponseMessageBuilder.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Net; using WireMock.Admin.Mappings; using WireMock.Constants; using WireMock.Http; @@ -15,6 +16,11 @@ internal static class ResponseMessageBuilder { HttpKnownHeaderNames.ContentType, new WireMockList { WireMockConstants.ContentTypeJson } } }; + internal static ResponseMessage Create(string? message, HttpStatusCode statusCode, Guid? guid = null) + { + return Create(message, (int)statusCode, guid); + } + internal static ResponseMessage Create(string? message, int statusCode = 200, Guid? guid = null) { var response = new ResponseMessage diff --git a/src/WireMock.Net/Serialization/TimeSettingsMapper.cs b/src/WireMock.Net/Serialization/TimeSettingsMapper.cs index 6528a78b5..7e78764c2 100644 --- a/src/WireMock.Net/Serialization/TimeSettingsMapper.cs +++ b/src/WireMock.Net/Serialization/TimeSettingsMapper.cs @@ -1,27 +1,26 @@ using WireMock.Models; -namespace WireMock.Serialization +namespace WireMock.Serialization; + +internal static class TimeSettingsMapper { - internal static class TimeSettingsMapper + public static TimeSettingsModel? Map(ITimeSettings? settings) { - public static TimeSettingsModel Map(ITimeSettings settings) + return settings != null ? new TimeSettingsModel { - return settings != null ? new TimeSettingsModel - { - Start = settings.Start, - End = settings.End, - TTL = settings.TTL - } : null; - } + Start = settings.Start, + End = settings.End, + TTL = settings.TTL + } : null; + } - public static ITimeSettings Map(TimeSettingsModel settings) + public static ITimeSettings? Map(TimeSettingsModel? settings) + { + return settings != null ? new TimeSettings { - return settings != null ? new TimeSettings - { - Start = settings.Start, - End = settings.End, - TTL = settings.TTL - } : null; - } + Start = settings.Start, + End = settings.End, + TTL = settings.TTL + } : null; } } \ No newline at end of file diff --git a/src/WireMock.Net/Server/WireMockServer.Admin.cs b/src/WireMock.Net/Server/WireMockServer.Admin.cs index c98b33167..6d8fb11a9 100644 --- a/src/WireMock.Net/Server/WireMockServer.Admin.cs +++ b/src/WireMock.Net/Server/WireMockServer.Admin.cs @@ -20,7 +20,6 @@ using WireMock.Matchers.Request; using WireMock.Proxy; using WireMock.RequestBuilders; -using WireMock.ResponseBuilders; using WireMock.ResponseProviders; using WireMock.Serialization; using WireMock.Settings; @@ -43,10 +42,10 @@ public partial class WireMockServer private const string AdminScenarios = "/__admin/scenarios"; private const string QueryParamReloadStaticMappings = "reloadStaticMappings"; - private readonly Guid _proxyMappingGuid = new("e59914fd-782e-428e-91c1-4810ffb86567"); - private readonly RegexMatcher _adminRequestContentTypeJson = new ContentTypeMatcher(WireMockConstants.ContentTypeJson, true); - private readonly RegexMatcher _adminMappingsGuidPathMatcher = new(@"^\/__admin\/mappings\/([0-9A-Fa-f]{8}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{12})$"); - private readonly RegexMatcher _adminRequestsGuidPathMatcher = new(@"^\/__admin\/requests\/([0-9A-Fa-f]{8}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{12})$"); + private static readonly Guid ProxyMappingGuid = new("e59914fd-782e-428e-91c1-4810ffb86567"); + private static readonly RegexMatcher AdminRequestContentTypeJson = new ContentTypeMatcher(WireMockConstants.ContentTypeJson, true); + private static readonly RegexMatcher AdminMappingsGuidPathMatcher = new(@"^\/__admin\/mappings\/([0-9A-Fa-f]{8}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{12})$"); + private static readonly RegexMatcher AdminRequestsGuidPathMatcher = new(@"^\/__admin\/requests\/([0-9A-Fa-f]{8}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{12})$"); private EnhancedFileSystemWatcher? _enhancedFileSystemWatcher; @@ -55,21 +54,21 @@ private void InitAdmin() { // __admin/settings Given(Request.Create().WithPath(AdminSettings).UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(SettingsGet)); - Given(Request.Create().WithPath(AdminSettings).UsingMethod("PUT", "POST").WithHeader(HttpKnownHeaderNames.ContentType, _adminRequestContentTypeJson)).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(SettingsUpdate)); + Given(Request.Create().WithPath(AdminSettings).UsingMethod("PUT", "POST").WithHeader(HttpKnownHeaderNames.ContentType, AdminRequestContentTypeJson)).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(SettingsUpdate)); // __admin/mappings Given(Request.Create().WithPath(AdminMappings).UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingsGet)); - Given(Request.Create().WithPath(AdminMappings).UsingPost().WithHeader(HttpKnownHeaderNames.ContentType, _adminRequestContentTypeJson)).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingsPost)); - Given(Request.Create().WithPath(AdminMappingsWireMockOrg).UsingPost().WithHeader(HttpKnownHeaderNames.ContentType, _adminRequestContentTypeJson)).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingsPostWireMockOrg)); + Given(Request.Create().WithPath(AdminMappings).UsingPost().WithHeader(HttpKnownHeaderNames.ContentType, AdminRequestContentTypeJson)).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingsPost)); + Given(Request.Create().WithPath(AdminMappingsWireMockOrg).UsingPost().WithHeader(HttpKnownHeaderNames.ContentType, AdminRequestContentTypeJson)).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingsPostWireMockOrg)); Given(Request.Create().WithPath(AdminMappings).UsingDelete()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingsDelete)); // __admin/mappings/reset Given(Request.Create().WithPath(AdminMappings + "/reset").UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingsReset)); // __admin/mappings/{guid} - Given(Request.Create().WithPath(_adminMappingsGuidPathMatcher).UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingGet)); - Given(Request.Create().WithPath(_adminMappingsGuidPathMatcher).UsingPut().WithHeader(HttpKnownHeaderNames.ContentType, _adminRequestContentTypeJson)).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingPut)); - Given(Request.Create().WithPath(_adminMappingsGuidPathMatcher).UsingDelete()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingDelete)); + Given(Request.Create().WithPath(AdminMappingsGuidPathMatcher).UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingGet)); + Given(Request.Create().WithPath(AdminMappingsGuidPathMatcher).UsingPut().WithHeader(HttpKnownHeaderNames.ContentType, AdminRequestContentTypeJson)).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingPut)); + Given(Request.Create().WithPath(AdminMappingsGuidPathMatcher).UsingDelete()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingDelete)); // __admin/mappings/save Given(Request.Create().WithPath($"{AdminMappings}/save").UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingsSave)); @@ -85,8 +84,8 @@ private void InitAdmin() Given(Request.Create().WithPath(AdminRequests + "/reset").UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(RequestsDelete)); // __admin/request/{guid} - Given(Request.Create().WithPath(_adminRequestsGuidPathMatcher).UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(RequestGet)); - Given(Request.Create().WithPath(_adminRequestsGuidPathMatcher).UsingDelete()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(RequestDelete)); + Given(Request.Create().WithPath(AdminRequestsGuidPathMatcher).UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(RequestGet)); + Given(Request.Create().WithPath(AdminRequestsGuidPathMatcher).UsingDelete()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(RequestDelete)); // __admin/requests/find Given(Request.Create().WithPath(AdminRequests + "/find").UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(RequestsFind)); @@ -216,13 +215,13 @@ private void InitProxyAndRecord(WireMockServerSettings settings) if (settings.ProxyAndRecordSettings == null) { _httpClientForProxy = null; - DeleteMapping(_proxyMappingGuid); + DeleteMapping(ProxyMappingGuid); return; } _httpClientForProxy = HttpClientBuilder.Build(settings.ProxyAndRecordSettings); - var proxyRespondProvider = Given(Request.Create().WithPath("/*").UsingAnyMethod()).WithGuid(_proxyMappingGuid); + var proxyRespondProvider = Given(Request.Create().WithPath("/*").UsingAnyMethod()).WithGuid(ProxyMappingGuid).WithTitle("Default Proxy Mapping on /*"); if (settings.StartAdminInterface == true) { proxyRespondProvider.AtPriority(WireMockConstants.ProxyPriority); @@ -359,7 +358,7 @@ private IResponseMessage MappingPut(IRequestMessage requestMessage) var mappingModel = DeserializeObject(requestMessage); Guid? guidFromPut = ConvertMappingAndRegisterAsRespondProvider(mappingModel, guid); - return ResponseMessageBuilder.Create("Mapping added or updated", 200, guidFromPut); + return ResponseMessageBuilder.Create("Mapping added or updated", HttpStatusCode.OK, guidFromPut); } private IResponseMessage MappingDelete(IRequestMessage requestMessage) @@ -368,13 +367,13 @@ private IResponseMessage MappingDelete(IRequestMessage requestMessage) if (DeleteMapping(guid)) { - return ResponseMessageBuilder.Create("Mapping removed", 200, guid); + return ResponseMessageBuilder.Create("Mapping removed", HttpStatusCode.OK, guid); } - return ResponseMessageBuilder.Create("Mapping not found", 404); + return ResponseMessageBuilder.Create("Mapping not found", HttpStatusCode.NotFound); } - private Guid ParseGuidFromRequestMessage(IRequestMessage requestMessage) + private static Guid ParseGuidFromRequestMessage(IRequestMessage requestMessage) { return Guid.Parse(requestMessage.Path.Substring(AdminMappings.Length + 1)); } diff --git a/src/WireMock.Net/Server/WireMockServer.cs b/src/WireMock.Net/Server/WireMockServer.cs index 9cf9d64cb..c44a5ff0c 100644 --- a/src/WireMock.Net/Server/WireMockServer.cs +++ b/src/WireMock.Net/Server/WireMockServer.cs @@ -80,7 +80,7 @@ public partial class WireMockServer : IWireMockServer /// Gets the scenarios. /// [PublicAPI] - public ConcurrentDictionary Scenarios => new ConcurrentDictionary(_options.Scenarios); + public ConcurrentDictionary Scenarios => new(_options.Scenarios); #region IDisposable Members /// @@ -414,10 +414,10 @@ public void AllowPartialMapping(bool allow = true) /// [PublicAPI] - public void SetAzureADAuthentication([NotNull] string tenant, [NotNull] string audience) + public void SetAzureADAuthentication(string tenant, string audience) { - Guard.NotNull(tenant, nameof(tenant)); - Guard.NotNull(audience, nameof(audience)); + Guard.NotNull(tenant); + Guard.NotNull(audience); #if NETSTANDARD1_3 throw new NotSupportedException("AzureADAuthentication is not supported for NETStandard 1.3"); @@ -445,14 +445,14 @@ public void RemoveAuthentication() /// [PublicAPI] - public void SetMaxRequestLogCount([CanBeNull] int? maxRequestLogCount) + public void SetMaxRequestLogCount(int? maxRequestLogCount) { _options.MaxRequestLogCount = maxRequestLogCount; } /// [PublicAPI] - public void SetRequestLogExpirationDuration([CanBeNull] int? requestLogExpirationDuration) + public void SetRequestLogExpirationDuration(int? requestLogExpirationDuration) { _options.RequestLogExpirationDuration = requestLogExpirationDuration; } @@ -542,12 +542,12 @@ private void InitSettings(WireMockServerSettings settings) { if (!string.IsNullOrEmpty(settings.AdminUsername) && !string.IsNullOrEmpty(settings.AdminPassword)) { - SetBasicAuthentication(settings.AdminUsername, settings.AdminPassword); + SetBasicAuthentication(settings.AdminUsername!, settings.AdminPassword!); } if (!string.IsNullOrEmpty(settings.AdminAzureADTenant) && !string.IsNullOrEmpty(settings.AdminAzureADAudience)) { - SetAzureADAuthentication(settings.AdminAzureADTenant, settings.AdminAzureADAudience); + SetAzureADAuthentication(settings.AdminAzureADTenant!, settings.AdminAzureADAudience!); } InitAdmin(); diff --git a/test/WireMock.Net.Tests/WireMockServer.Proxy.cs b/test/WireMock.Net.Tests/WireMockServer.Proxy.cs index 57df7716f..58b317c32 100644 --- a/test/WireMock.Net.Tests/WireMockServer.Proxy.cs +++ b/test/WireMock.Net.Tests/WireMockServer.Proxy.cs @@ -18,688 +18,723 @@ using WireMock.Util; using Xunit; -namespace WireMock.Net.Tests +namespace WireMock.Net.Tests; + +public class WireMockServerProxyTests { - public class WireMockServerProxyTests + [Fact(Skip = "Fails in Linux CI")] + public async Task WireMockServer_ProxySSL_Should_log_proxied_requests() { - [Fact(Skip = "Fails in Linux CI")] - public async Task WireMockServer_ProxySSL_Should_log_proxied_requests() + // Assign + var settings = new WireMockServerSettings { - // Assign - var settings = new WireMockServerSettings + UseSSL = true, + ProxyAndRecordSettings = new ProxyAndRecordSettings { - UseSSL = true, - ProxyAndRecordSettings = new ProxyAndRecordSettings - { - Url = "https://www.google.com", - SaveMapping = true, - SaveMappingToFile = false - } + Url = "https://www.google.com", + SaveMapping = true, + SaveMappingToFile = false + } - }; - var server = WireMockServer.Start(settings); + }; + var server = WireMockServer.Start(settings); - // Act - var requestMessage = new HttpRequestMessage - { - Method = HttpMethod.Get, - RequestUri = new Uri(server.Urls[0]) - }; - var httpClientHandler = new HttpClientHandler { AllowAutoRedirect = false }; - await new HttpClient(httpClientHandler).SendAsync(requestMessage).ConfigureAwait(false); - - // Assert - Check.That(server.Mappings).HasSize(2); - Check.That(server.LogEntries).HasSize(1); - } + // Act + var requestMessage = new HttpRequestMessage + { + Method = HttpMethod.Get, + RequestUri = new Uri(server.Urls[0]) + }; + var httpClientHandler = new HttpClientHandler { AllowAutoRedirect = false }; + await new HttpClient(httpClientHandler).SendAsync(requestMessage).ConfigureAwait(false); + + // Assert + Check.That(server.Mappings).HasSize(2); + Check.That(server.LogEntries).HasSize(1); + } - [Fact] - public async Task WireMockServer_Proxy_With_SaveMapping_Is_True_And_SaveMappingToFile_Is_False_Should_AddInternalMappingOnly() + [Fact] + public async Task WireMockServer_Proxy_AdminFalse_With_SaveMapping_Is_True_And_SaveMappingToFile_Is_False_Should_AddInternalMappingOnly() + { + // Assign + var settings = new WireMockServerSettings { - // Assign - var settings = new WireMockServerSettings + ProxyAndRecordSettings = new ProxyAndRecordSettings { - ProxyAndRecordSettings = new ProxyAndRecordSettings - { - Url = "http://www.google.com", - SaveMapping = true, - SaveMappingToFile = false - } - }; - var server = WireMockServer.Start(settings); - - // Act + Url = "http://www.google.com", + SaveMapping = true, + SaveMappingToFile = false + } + }; + var server = WireMockServer.Start(settings); + + // Act + var httpClientHandler = new HttpClientHandler { AllowAutoRedirect = false }; + var client = new HttpClient(httpClientHandler); + for (int i = 0; i < 5; i++) + { var requestMessage = new HttpRequestMessage { Method = HttpMethod.Get, - RequestUri = new Uri(server.Urls[0]) + RequestUri = new Uri(server.Url!) }; - var httpClientHandler = new HttpClientHandler { AllowAutoRedirect = false }; - await new HttpClient(httpClientHandler).SendAsync(requestMessage).ConfigureAwait(false); - - // Assert - server.Mappings.Should().HaveCount(2); + await client.SendAsync(requestMessage).ConfigureAwait(false); } - [Fact] - public async Task WireMockServer_Proxy_With_SaveMapping_Is_False_And_SaveMappingToFile_Is_True_ShouldSaveMappingToFile() - { - // Assign - var fileSystemHandlerMock = new Mock(); - fileSystemHandlerMock.Setup(f => f.GetMappingFolder()).Returns("m"); + // Assert + server.Mappings.Should().HaveCountGreaterOrEqualTo(2); // For .NET 4.5.2 and 4.6.x this is 3? + } - var settings = new WireMockServerSettings + [Fact] + public async Task WireMockServer_Proxy_AdminTrue_With_SaveMapping_Is_True_And_SaveMappingToFile_Is_False_Should_AddInternalMappingOnly() + { + // Assign + var settings = new WireMockServerSettings + { + ProxyAndRecordSettings = new ProxyAndRecordSettings { - ProxyAndRecordSettings = new ProxyAndRecordSettings - { - Url = "http://www.google.com", - SaveMapping = false, - SaveMappingToFile = true - }, - FileSystemHandler = fileSystemHandlerMock.Object - }; - var server = WireMockServer.Start(settings); - - // Act + Url = "http://www.google.com", + SaveMapping = true, + SaveMappingToFile = false + }, + StartAdminInterface = true + }; + var server = WireMockServer.Start(settings); + + // Act + for (int i = 0; i < 5; i++) + { var requestMessage = new HttpRequestMessage { Method = HttpMethod.Get, - RequestUri = new Uri(server.Urls[0]) + RequestUri = new Uri(server.Url!) }; var httpClientHandler = new HttpClientHandler { AllowAutoRedirect = false }; await new HttpClient(httpClientHandler).SendAsync(requestMessage).ConfigureAwait(false); + } - // Assert - server.Mappings.Should().HaveCount(1); + // Assert + server.Mappings.Should().HaveCountGreaterOrEqualTo(28); // For .NET 4.5.2 and 4.6.x this is 29? + } - // Verify - fileSystemHandlerMock.Verify(f => f.WriteMappingFile(It.IsAny(), It.IsAny()), Times.Once); - } + [Fact] + public async Task WireMockServer_Proxy_With_SaveMapping_Is_False_And_SaveMappingToFile_Is_True_ShouldSaveMappingToFile() + { + // Assign + var fileSystemHandlerMock = new Mock(); + fileSystemHandlerMock.Setup(f => f.GetMappingFolder()).Returns("m"); - [Fact] - public async Task WireMockServer_Proxy_With_SaveMappingForStatusCodePattern_Is_False_Should_Not_SaveMapping() + var settings = new WireMockServerSettings { - // Assign - var fileSystemHandlerMock = new Mock(); - fileSystemHandlerMock.Setup(f => f.GetMappingFolder()).Returns("m"); - - var settings = new WireMockServerSettings + ProxyAndRecordSettings = new ProxyAndRecordSettings { - ProxyAndRecordSettings = new ProxyAndRecordSettings - { - Url = "http://www.google.com", - SaveMapping = true, - SaveMappingToFile = true, - SaveMappingForStatusCodePattern = "999" // Just make sure that we don't want this mapping - }, - FileSystemHandler = fileSystemHandlerMock.Object - }; - var server = WireMockServer.Start(settings); + Url = "http://www.google.com", + SaveMapping = false, + SaveMappingToFile = true + }, + FileSystemHandler = fileSystemHandlerMock.Object + }; + var server = WireMockServer.Start(settings); + + // Act + var requestMessage = new HttpRequestMessage + { + Method = HttpMethod.Get, + RequestUri = new Uri(server.Urls[0]) + }; + var httpClientHandler = new HttpClientHandler { AllowAutoRedirect = false }; + await new HttpClient(httpClientHandler).SendAsync(requestMessage).ConfigureAwait(false); - // Act - var requestMessage = new HttpRequestMessage - { - Method = HttpMethod.Get, - RequestUri = new Uri(server.Urls[0]) - }; - var httpClientHandler = new HttpClientHandler { AllowAutoRedirect = false }; - await new HttpClient(httpClientHandler).SendAsync(requestMessage).ConfigureAwait(false); + // Assert + server.Mappings.Should().HaveCount(1); - // Assert - server.Mappings.Should().HaveCount(1); + // Verify + fileSystemHandlerMock.Verify(f => f.WriteMappingFile(It.IsAny(), It.IsAny()), Times.Once); + } - // Verify - fileSystemHandlerMock.Verify(f => f.WriteMappingFile(It.IsAny(), It.IsAny()), Times.Never); - } + [Fact] + public async Task WireMockServer_Proxy_With_SaveMappingForStatusCodePattern_Is_False_Should_Not_SaveMapping() + { + // Assign + var fileSystemHandlerMock = new Mock(); + fileSystemHandlerMock.Setup(f => f.GetMappingFolder()).Returns("m"); - [Fact] - public async Task WireMockServer_Proxy_Should_log_proxied_requests() + var settings = new WireMockServerSettings { - // Assign - var settings = new WireMockServerSettings + ProxyAndRecordSettings = new ProxyAndRecordSettings { - ProxyAndRecordSettings = new ProxyAndRecordSettings - { - Url = "http://www.google.com", - SaveMapping = true, - SaveMappingToFile = false - } - }; - var server = WireMockServer.Start(settings); - - // Act - var requestMessage = new HttpRequestMessage - { - Method = HttpMethod.Get, - RequestUri = new Uri(server.Urls[0]) - }; - var httpClientHandler = new HttpClientHandler { AllowAutoRedirect = false }; - await new HttpClient(httpClientHandler).SendAsync(requestMessage).ConfigureAwait(false); - - // Assert - server.Mappings.Should().HaveCount(2); - server.LogEntries.Should().HaveCount(1); - } - - [Fact] - public async Task WireMockServer_Proxy_Should_proxy_responses() + Url = "http://www.google.com", + SaveMapping = true, + SaveMappingToFile = true, + SaveMappingForStatusCodePattern = "999" // Just make sure that we don't want this mapping + }, + FileSystemHandler = fileSystemHandlerMock.Object + }; + var server = WireMockServer.Start(settings); + + // Act + var requestMessage = new HttpRequestMessage { - // Assign - string path = $"/prx_{Guid.NewGuid()}"; - var server = WireMockServer.Start(); - server - .Given(Request.Create().WithPath(path)) - .RespondWith(Response.Create().WithProxy("http://www.google.com")); - - // Act - var requestMessage = new HttpRequestMessage - { - Method = HttpMethod.Get, - RequestUri = new Uri($"{server.Urls[0]}{path}") - }; - var httpClientHandler = new HttpClientHandler { AllowAutoRedirect = false }; - var response = await new HttpClient(httpClientHandler).SendAsync(requestMessage).ConfigureAwait(false); - string content = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + Method = HttpMethod.Get, + RequestUri = new Uri(server.Urls[0]) + }; + var httpClientHandler = new HttpClientHandler { AllowAutoRedirect = false }; + await new HttpClient(httpClientHandler).SendAsync(requestMessage).ConfigureAwait(false); - // Assert - Check.That(server.Mappings).HasSize(1); - Check.That(server.LogEntries).HasSize(1); - Check.That(content).Contains("google"); + // Assert + server.Mappings.Should().HaveCount(1); - server.Stop(); - } + // Verify + fileSystemHandlerMock.Verify(f => f.WriteMappingFile(It.IsAny(), It.IsAny()), Times.Never); + } - [Fact] - public async Task WireMockServer_Proxy_Should_preserve_content_header_in_proxied_request() + [Fact] + public async Task WireMockServer_Proxy_Should_log_proxied_requests() + { + // Assign + var settings = new WireMockServerSettings { - // Assign - string path = $"/prx_{Guid.NewGuid()}"; - var serverForProxyForwarding = WireMockServer.Start(); - serverForProxyForwarding - .Given(Request.Create().WithPath(path)) - .RespondWith(Response.Create()); - - var settings = new WireMockServerSettings + ProxyAndRecordSettings = new ProxyAndRecordSettings { - ProxyAndRecordSettings = new ProxyAndRecordSettings - { - Url = serverForProxyForwarding.Urls[0], - SaveMapping = true, - SaveMappingToFile = false - } - }; - var server = WireMockServer.Start(settings); - - // Act - var requestMessage = new HttpRequestMessage - { - Method = HttpMethod.Post, - RequestUri = new Uri($"{server.Urls[0]}{path}"), - Content = new StringContent("stringContent", Encoding.ASCII) - }; - requestMessage.Content.Headers.ContentType = new MediaTypeHeaderValue("text/plain"); - requestMessage.Content.Headers.Add("bbb", "test"); - await new HttpClient().SendAsync(requestMessage).ConfigureAwait(false); - - // Assert - var receivedRequest = serverForProxyForwarding.LogEntries.First().RequestMessage; - Check.That(receivedRequest.BodyData.BodyAsString).IsEqualTo("stringContent"); - Check.That(receivedRequest.Headers).ContainsKey("Content-Type"); - Check.That(receivedRequest.Headers["Content-Type"].First()).Contains("text/plain"); - Check.That(receivedRequest.Headers).ContainsKey("bbb"); - - // check that new proxied mapping is added - Check.That(server.Mappings).HasSize(2); - } - - [Fact] - public async Task WireMockServer_Proxy_Should_preserve_Authorization_header_in_proxied_request() + Url = "http://www.google.com", + SaveMapping = true, + SaveMappingToFile = false + } + }; + var server = WireMockServer.Start(settings); + + // Act + var requestMessage = new HttpRequestMessage { - // Assign - string path = $"/prx_{Guid.NewGuid()}"; - var serverForProxyForwarding = WireMockServer.Start(); - serverForProxyForwarding - .Given(Request.Create().WithPath(path)) - .RespondWith(Response.Create().WithCallback(x => new ResponseMessage - { - BodyData = new BodyData - { - BodyAsString = x.Headers["Authorization"].ToString(), - DetectedBodyType = Types.BodyType.String - } - })); - - var settings = new WireMockServerSettings - { - ProxyAndRecordSettings = new ProxyAndRecordSettings - { - Url = serverForProxyForwarding.Urls[0], - SaveMapping = true, - SaveMappingToFile = false - } - }; - var server = WireMockServer.Start(settings); - - // Act - var requestMessage = new HttpRequestMessage - { - Method = HttpMethod.Post, - RequestUri = new Uri($"{server.Urls[0]}{path}"), - Content = new StringContent("stringContent", Encoding.ASCII) - }; - requestMessage.Headers.Authorization = new AuthenticationHeaderValue("BASIC", "test-A"); - var result = await new HttpClient().SendAsync(requestMessage).ConfigureAwait(false); - - // Assert - (await result.Content.ReadAsStringAsync().ConfigureAwait(false)).Should().Be("BASIC test-A"); - - var receivedRequest = serverForProxyForwarding.LogEntries.First().RequestMessage; - var authorizationHeader = receivedRequest.Headers["Authorization"].ToString().Should().Be("BASIC test-A"); - - server.Mappings.Should().HaveCount(2); - var authorizationRequestMessageHeaderMatcher = ((Request)server.Mappings.Single(m => !m.IsAdminInterface).RequestMatcher) - .GetRequestMessageMatcher(x => x.Matchers.Any(m => m.GetPatterns().Contains("BASIC test-A"))); - authorizationRequestMessageHeaderMatcher.Should().NotBeNull(); - } + Method = HttpMethod.Get, + RequestUri = new Uri(server.Urls[0]) + }; + var httpClientHandler = new HttpClientHandler { AllowAutoRedirect = false }; + await new HttpClient(httpClientHandler).SendAsync(requestMessage).ConfigureAwait(false); + + // Assert + server.Mappings.Should().HaveCount(2); + server.LogEntries.Should().HaveCount(1); + } - [Fact] - public async Task WireMockServer_Proxy_Should_exclude_ExcludedHeaders_in_mapping() + [Fact] + public async Task WireMockServer_Proxy_Should_proxy_responses() + { + // Assign + string path = $"/prx_{Guid.NewGuid()}"; + var server = WireMockServer.Start(); + server + .Given(Request.Create().WithPath(path)) + .RespondWith(Response.Create().WithProxy("http://www.google.com")); + + // Act + var requestMessage = new HttpRequestMessage { - // Assign - string path = $"/prx_{Guid.NewGuid()}"; - var serverForProxyForwarding = WireMockServer.Start(); - serverForProxyForwarding - .Given(Request.Create().WithPath(path)) - .RespondWith(Response.Create()); - - var settings = new WireMockServerSettings - { - ProxyAndRecordSettings = new ProxyAndRecordSettings - { - Url = serverForProxyForwarding.Urls[0], - SaveMapping = true, - SaveMappingToFile = false, - ExcludedHeaders = new[] { "excluded-header-X" } - } - }; - var server = WireMockServer.Start(settings); - var defaultMapping = server.Mappings.First(); + Method = HttpMethod.Get, + RequestUri = new Uri($"{server.Urls[0]}{path}") + }; + var httpClientHandler = new HttpClientHandler { AllowAutoRedirect = false }; + var response = await new HttpClient(httpClientHandler).SendAsync(requestMessage).ConfigureAwait(false); + string content = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + + // Assert + Check.That(server.Mappings).HasSize(1); + Check.That(server.LogEntries).HasSize(1); + Check.That(content).Contains("google"); + + server.Stop(); + } - // Act - var requestMessage = new HttpRequestMessage + [Fact] + public async Task WireMockServer_Proxy_Should_preserve_content_header_in_proxied_request() + { + // Assign + string path = $"/prx_{Guid.NewGuid()}"; + var serverForProxyForwarding = WireMockServer.Start(); + serverForProxyForwarding + .Given(Request.Create().WithPath(path)) + .RespondWith(Response.Create()); + + var settings = new WireMockServerSettings + { + ProxyAndRecordSettings = new ProxyAndRecordSettings { - Method = HttpMethod.Post, - RequestUri = new Uri($"{server.Urls[0]}{path}"), - Content = new StringContent("stringContent") - }; - requestMessage.Headers.Add("foobar", "exact_match"); - requestMessage.Headers.Add("ok", "ok-value"); - await new HttpClient().SendAsync(requestMessage).ConfigureAwait(false); - - // Assert - var mapping = server.Mappings.FirstOrDefault(m => m.Guid != defaultMapping.Guid); - Check.That(mapping).IsNotNull(); - var matchers = ((Request)mapping.RequestMatcher).GetRequestMessageMatchers().Select(m => m.Name).ToList(); - Check.That(matchers).Not.Contains("excluded-header-X"); - Check.That(matchers).Contains("ok"); - } - - [Fact] - public async Task WireMockServer_Proxy_Should_exclude_ExcludedCookies_in_mapping() + Url = serverForProxyForwarding.Urls[0], + SaveMapping = true, + SaveMappingToFile = false + } + }; + var server = WireMockServer.Start(settings); + + // Act + var requestMessage = new HttpRequestMessage { - // Assign - string path = $"/prx_{Guid.NewGuid()}"; - var serverForProxyForwarding = WireMockServer.Start(); - serverForProxyForwarding - .Given(Request.Create().WithPath(path)) - .RespondWith(Response.Create()); + Method = HttpMethod.Post, + RequestUri = new Uri($"{server.Urls[0]}{path}"), + Content = new StringContent("stringContent", Encoding.ASCII) + }; + requestMessage.Content.Headers.ContentType = new MediaTypeHeaderValue("text/plain"); + requestMessage.Content.Headers.Add("bbb", "test"); + await new HttpClient().SendAsync(requestMessage).ConfigureAwait(false); + + // Assert + var receivedRequest = serverForProxyForwarding.LogEntries.First().RequestMessage; + Check.That(receivedRequest.BodyData.BodyAsString).IsEqualTo("stringContent"); + Check.That(receivedRequest.Headers).ContainsKey("Content-Type"); + Check.That(receivedRequest.Headers["Content-Type"].First()).Contains("text/plain"); + Check.That(receivedRequest.Headers).ContainsKey("bbb"); + + // check that new proxied mapping is added + Check.That(server.Mappings).HasSize(2); + } - var settings = new WireMockServerSettings + [Fact] + public async Task WireMockServer_Proxy_Should_preserve_Authorization_header_in_proxied_request() + { + // Assign + string path = $"/prx_{Guid.NewGuid()}"; + var serverForProxyForwarding = WireMockServer.Start(); + serverForProxyForwarding + .Given(Request.Create().WithPath(path)) + .RespondWith(Response.Create().WithCallback(x => new ResponseMessage { - ProxyAndRecordSettings = new ProxyAndRecordSettings + BodyData = new BodyData { - Url = serverForProxyForwarding.Urls[0], - SaveMapping = true, - SaveMappingToFile = false, - ExcludedCookies = new[] { "ASP.NET_SessionId" } + BodyAsString = x.Headers!["Authorization"].ToString(), + DetectedBodyType = Types.BodyType.String } - }; - var server = WireMockServer.Start(settings); - var defaultMapping = server.Mappings.First(); + })); - // Act - var requestMessage = new HttpRequestMessage + var settings = new WireMockServerSettings + { + ProxyAndRecordSettings = new ProxyAndRecordSettings { - Method = HttpMethod.Post, - RequestUri = new Uri($"{server.Urls[0]}{path}"), - Content = new StringContent("stringContent") - }; - - var cookieContainer = new CookieContainer(3); - cookieContainer.Add(new Uri("http://localhost"), new Cookie("ASP.NET_SessionId", "exact_match")); - cookieContainer.Add(new Uri("http://localhost"), new Cookie("AsP.NeT_SessIonID", "case_mismatch")); - cookieContainer.Add(new Uri("http://localhost"), new Cookie("GoodCookie", "I_should_pass")); - - var handler = new HttpClientHandler { CookieContainer = cookieContainer }; - await new HttpClient(handler).SendAsync(requestMessage).ConfigureAwait(false); - - // Assert - var mapping = server.Mappings.FirstOrDefault(m => m.Guid != defaultMapping.Guid); - Check.That(mapping).IsNotNull(); - - var matchers = ((Request)mapping.RequestMatcher).GetRequestMessageMatchers().Select(m => m.Name).ToList(); - Check.That(matchers).Not.Contains("ASP.NET_SessionId"); - Check.That(matchers).Not.Contains("AsP.NeT_SessIonID"); - Check.That(matchers).Contains("GoodCookie"); - } - - [Fact] - public async Task WireMockServer_Proxy_Should_preserve_content_header_in_proxied_request_with_empty_content() + Url = serverForProxyForwarding.Urls[0], + SaveMapping = true, + SaveMappingToFile = false + } + }; + var server = WireMockServer.Start(settings); + + // Act + var requestMessage = new HttpRequestMessage { - // Assign - string path = $"/prx_{Guid.NewGuid()}"; - var serverForProxyForwarding = WireMockServer.Start(); - serverForProxyForwarding - .Given(Request.Create().WithPath(path)) - .RespondWith(Response.Create()); - - var server = WireMockServer.Start(); - server - .Given(Request.Create().WithPath("/*")) - .RespondWith(Response.Create().WithProxy(serverForProxyForwarding.Urls[0])); + Method = HttpMethod.Post, + RequestUri = new Uri($"{server.Urls[0]}{path}"), + Content = new StringContent("stringContent", Encoding.ASCII) + }; + requestMessage.Headers.Authorization = new AuthenticationHeaderValue("BASIC", "test-A"); + var result = await new HttpClient().SendAsync(requestMessage).ConfigureAwait(false); + + // Assert + (await result.Content.ReadAsStringAsync().ConfigureAwait(false)).Should().Be("BASIC test-A"); + + var receivedRequest = serverForProxyForwarding.LogEntries.First().RequestMessage; + receivedRequest.Headers!["Authorization"].ToString().Should().Be("BASIC test-A"); + + server.Mappings.Should().HaveCount(2); + var authorizationRequestMessageHeaderMatcher = ((Request)server.Mappings.Single(m => !m.IsAdminInterface).RequestMatcher) + .GetRequestMessageMatcher(x => x.Matchers.Any(m => m.GetPatterns().Contains("BASIC test-A"))); + authorizationRequestMessageHeaderMatcher.Should().NotBeNull(); + } - // Act - var requestMessage = new HttpRequestMessage + [Fact] + public async Task WireMockServer_Proxy_Should_exclude_ExcludedHeaders_in_mapping() + { + // Assign + string path = $"/prx_{Guid.NewGuid()}"; + var serverForProxyForwarding = WireMockServer.Start(); + serverForProxyForwarding + .Given(Request.Create().WithPath(path)) + .RespondWith(Response.Create()); + + var settings = new WireMockServerSettings + { + ProxyAndRecordSettings = new ProxyAndRecordSettings { - Method = HttpMethod.Post, - RequestUri = new Uri($"{server.Urls[0]}{path}"), - Content = new StringContent("") - }; - requestMessage.Content.Headers.ContentType = new MediaTypeHeaderValue("text/plain"); - await new HttpClient().SendAsync(requestMessage).ConfigureAwait(false); - - // Assert - var receivedRequest = serverForProxyForwarding.LogEntries.First().RequestMessage; - Check.That(receivedRequest.BodyData.BodyAsString).IsEqualTo(""); - Check.That(receivedRequest.Headers).ContainsKey("Content-Type"); - Check.That(receivedRequest.Headers["Content-Type"].First()).Contains("text/plain"); - } - - [Fact] - public async Task WireMockServer_Proxy_Should_preserve_content_header_in_proxied_response() + Url = serverForProxyForwarding.Urls[0], + SaveMapping = true, + SaveMappingToFile = false, + ExcludedHeaders = new[] { "excluded-header-X" } + } + }; + var server = WireMockServer.Start(settings); + var defaultMapping = server.Mappings.First(); + + // Act + var requestMessage = new HttpRequestMessage { - // Assign - string path = $"/prx_{Guid.NewGuid()}"; - var serverForProxyForwarding = WireMockServer.Start(); - serverForProxyForwarding - .Given(Request.Create().WithPath(path)) - .RespondWith(Response.Create() - .WithBody("body") - .WithHeader("Content-Type", "text/plain")); - - var server = WireMockServer.Start(); - server - .Given(Request.Create().WithPath(path)) - .RespondWith(Response.Create().WithProxy(serverForProxyForwarding.Urls[0])); + Method = HttpMethod.Post, + RequestUri = new Uri($"{server.Urls[0]}{path}"), + Content = new StringContent("stringContent") + }; + requestMessage.Headers.Add("foobar", "exact_match"); + requestMessage.Headers.Add("ok", "ok-value"); + await new HttpClient().SendAsync(requestMessage).ConfigureAwait(false); + + // Assert + var mapping = server.Mappings.FirstOrDefault(m => m.Guid != defaultMapping.Guid); + Check.That(mapping).IsNotNull(); + var matchers = ((Request)mapping.RequestMatcher).GetRequestMessageMatchers().Select(m => m.Name).ToList(); + Check.That(matchers).Not.Contains("excluded-header-X"); + Check.That(matchers).Contains("ok"); + } - // Act - var requestMessage = new HttpRequestMessage + [Fact] + public async Task WireMockServer_Proxy_Should_exclude_ExcludedCookies_in_mapping() + { + // Assign + string path = $"/prx_{Guid.NewGuid()}"; + var serverForProxyForwarding = WireMockServer.Start(); + serverForProxyForwarding + .Given(Request.Create().WithPath(path)) + .RespondWith(Response.Create()); + + var settings = new WireMockServerSettings + { + ProxyAndRecordSettings = new ProxyAndRecordSettings { - Method = HttpMethod.Get, - RequestUri = new Uri($"{server.Urls[0]}{path}") - }; - var response = await new HttpClient().SendAsync(requestMessage).ConfigureAwait(false); - - // Assert - Check.That(await response.Content.ReadAsStringAsync().ConfigureAwait(false)).IsEqualTo("body"); - Check.That(response.Content.Headers.Contains("Content-Type")).IsTrue(); - Check.That(response.Content.Headers.GetValues("Content-Type")).ContainsExactly("text/plain"); - } - - [Fact] - public async Task WireMockServer_Proxy_Should_change_absolute_location_header_in_proxied_response() + Url = serverForProxyForwarding.Urls[0], + SaveMapping = true, + SaveMappingToFile = false, + ExcludedCookies = new[] { "ASP.NET_SessionId" } + } + }; + var server = WireMockServer.Start(settings); + var defaultMapping = server.Mappings.First(); + + // Act + var requestMessage = new HttpRequestMessage { - // Assign - string path = $"/prx_{Guid.NewGuid()}"; - var settings = new WireMockServerSettings { AllowPartialMapping = false }; - - var serverForProxyForwarding = WireMockServer.Start(settings); - serverForProxyForwarding - .Given(Request.Create().WithPath(path)) - .RespondWith(Response.Create() - .WithStatusCode(HttpStatusCode.Redirect) - .WithHeader("Location", "/testpath")); + Method = HttpMethod.Post, + RequestUri = new Uri($"{server.Urls[0]}{path}"), + Content = new StringContent("stringContent") + }; + + var cookieContainer = new CookieContainer(3); + cookieContainer.Add(new Uri("http://localhost"), new Cookie("ASP.NET_SessionId", "exact_match")); + cookieContainer.Add(new Uri("http://localhost"), new Cookie("AsP.NeT_SessIonID", "case_mismatch")); + cookieContainer.Add(new Uri("http://localhost"), new Cookie("GoodCookie", "I_should_pass")); + + var handler = new HttpClientHandler { CookieContainer = cookieContainer }; + await new HttpClient(handler).SendAsync(requestMessage).ConfigureAwait(false); + + // Assert + var mapping = server.Mappings.FirstOrDefault(m => m.Guid != defaultMapping.Guid); + Check.That(mapping).IsNotNull(); + + var matchers = ((Request)mapping.RequestMatcher).GetRequestMessageMatchers().Select(m => m.Name).ToList(); + Check.That(matchers).Not.Contains("ASP.NET_SessionId"); + Check.That(matchers).Not.Contains("AsP.NeT_SessIonID"); + Check.That(matchers).Contains("GoodCookie"); + } - var server = WireMockServer.Start(settings); - server - .Given(Request.Create().WithPath(path).UsingAnyMethod()) - .RespondWith(Response.Create().WithProxy(serverForProxyForwarding.Urls[0])); + [Fact] + public async Task WireMockServer_Proxy_Should_preserve_content_header_in_proxied_request_with_empty_content() + { + // Assign + string path = $"/prx_{Guid.NewGuid()}"; + var serverForProxyForwarding = WireMockServer.Start(); + serverForProxyForwarding + .Given(Request.Create().WithPath(path)) + .RespondWith(Response.Create()); + + var server = WireMockServer.Start(); + server + .Given(Request.Create().WithPath("/*")) + .RespondWith(Response.Create().WithProxy(serverForProxyForwarding.Urls[0])); + + // Act + var requestMessage = new HttpRequestMessage + { + Method = HttpMethod.Post, + RequestUri = new Uri($"{server.Urls[0]}{path}"), + Content = new StringContent("") + }; + requestMessage.Content.Headers.ContentType = new MediaTypeHeaderValue("text/plain"); + await new HttpClient().SendAsync(requestMessage).ConfigureAwait(false); + + // Assert + var receivedRequest = serverForProxyForwarding.LogEntries.First().RequestMessage; + Check.That(receivedRequest.BodyData.BodyAsString).IsEqualTo(""); + Check.That(receivedRequest.Headers).ContainsKey("Content-Type"); + Check.That(receivedRequest.Headers["Content-Type"].First()).Contains("text/plain"); + } - // Act - var requestMessage = new HttpRequestMessage - { - Method = HttpMethod.Get, - RequestUri = new Uri($"{server.Urls[0]}{path}") - }; - var httpClientHandler = new HttpClientHandler { AllowAutoRedirect = false }; - var response = await new HttpClient(httpClientHandler).SendAsync(requestMessage).ConfigureAwait(false); + [Fact] + public async Task WireMockServer_Proxy_Should_preserve_content_header_in_proxied_response() + { + // Assign + string path = $"/prx_{Guid.NewGuid()}"; + var serverForProxyForwarding = WireMockServer.Start(); + serverForProxyForwarding + .Given(Request.Create().WithPath(path)) + .RespondWith(Response.Create() + .WithBody("body") + .WithHeader("Content-Type", "text/plain")); + + var server = WireMockServer.Start(); + server + .Given(Request.Create().WithPath(path)) + .RespondWith(Response.Create().WithProxy(serverForProxyForwarding.Urls[0])); + + // Act + var requestMessage = new HttpRequestMessage + { + Method = HttpMethod.Get, + RequestUri = new Uri($"{server.Urls[0]}{path}") + }; + var response = await new HttpClient().SendAsync(requestMessage).ConfigureAwait(false); + + // Assert + Check.That(await response.Content.ReadAsStringAsync().ConfigureAwait(false)).IsEqualTo("body"); + Check.That(response.Content.Headers.Contains("Content-Type")).IsTrue(); + Check.That(response.Content.Headers.GetValues("Content-Type")).ContainsExactly("text/plain"); + } - // Assert - Check.That(response.Headers.Contains("Location")).IsTrue(); - Check.That(response.Headers.GetValues("Location")).ContainsExactly("/testpath"); - } + [Fact] + public async Task WireMockServer_Proxy_Should_change_absolute_location_header_in_proxied_response() + { + // Assign + string path = $"/prx_{Guid.NewGuid()}"; + var settings = new WireMockServerSettings { AllowPartialMapping = false }; + + var serverForProxyForwarding = WireMockServer.Start(settings); + serverForProxyForwarding + .Given(Request.Create().WithPath(path)) + .RespondWith(Response.Create() + .WithStatusCode(HttpStatusCode.Redirect) + .WithHeader("Location", "/testpath")); + + var server = WireMockServer.Start(settings); + server + .Given(Request.Create().WithPath(path).UsingAnyMethod()) + .RespondWith(Response.Create().WithProxy(serverForProxyForwarding.Urls[0])); + + // Act + var requestMessage = new HttpRequestMessage + { + Method = HttpMethod.Get, + RequestUri = new Uri($"{server.Urls[0]}{path}") + }; + var httpClientHandler = new HttpClientHandler { AllowAutoRedirect = false }; + var response = await new HttpClient(httpClientHandler).SendAsync(requestMessage).ConfigureAwait(false); + + // Assert + Check.That(response.Headers.Contains("Location")).IsTrue(); + Check.That(response.Headers.GetValues("Location")).ContainsExactly("/testpath"); + } - [Fact] - public async Task WireMockServer_Proxy_Should_preserve_cookie_header_in_proxied_request() + [Fact] + public async Task WireMockServer_Proxy_Should_preserve_cookie_header_in_proxied_request() + { + // Assign + string path = $"/prx_{Guid.NewGuid()}"; + var serverForProxyForwarding = WireMockServer.Start(); + serverForProxyForwarding + .Given(Request.Create().WithPath(path)) + .RespondWith(Response.Create()); + + var server = WireMockServer.Start(); + server + .Given(Request.Create().WithPath(path)) + .RespondWith(Response.Create().WithProxy(serverForProxyForwarding.Urls[0])); + + // Act + var requestUri = new Uri($"{server.Urls[0]}{path}"); + var requestMessage = new HttpRequestMessage { - // Assign - string path = $"/prx_{Guid.NewGuid()}"; - var serverForProxyForwarding = WireMockServer.Start(); - serverForProxyForwarding - .Given(Request.Create().WithPath(path)) - .RespondWith(Response.Create()); + Method = HttpMethod.Get, + RequestUri = requestUri + }; + var clientHandler = new HttpClientHandler(); + clientHandler.CookieContainer.Add(requestUri, new Cookie("name", "value")); + await new HttpClient(clientHandler).SendAsync(requestMessage).ConfigureAwait(false); + + // then + var receivedRequest = serverForProxyForwarding.LogEntries.First().RequestMessage; + Check.That(receivedRequest.Cookies).IsNotNull(); + Check.That(receivedRequest.Cookies).ContainsPair("name", "value"); + } - var server = WireMockServer.Start(); - server - .Given(Request.Create().WithPath(path)) - .RespondWith(Response.Create().WithProxy(serverForProxyForwarding.Urls[0])); + /// + /// Send some binary content in a request through the proxy and check that the same content + /// arrived at the target. As example a JPEG/JIFF header is used, which is not representable + /// in UTF8 and breaks if it is not treated as binary content. + /// + [Fact] + public async Task WireMockServer_Proxy_Should_preserve_binary_request_content() + { + // arrange + var jpegHeader = new byte[] { 0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x10, 0x4A, 0x46, 0x49, 0x46, 0x00 }; + var brokenJpegHeader = new byte[] + {0xEF, 0xBF, 0xBD, 0xEF, 0xBF, 0xBD, 0xEF, 0xBF, 0xBD, 0xEF, 0xBF, 0xBD, 0x00, 0x10, 0x4A, 0x46, 0x49, 0x46, 0x00}; - // Act - var requestUri = new Uri($"{server.Urls[0]}{path}"); - var requestMessage = new HttpRequestMessage - { - Method = HttpMethod.Get, - RequestUri = requestUri - }; - var clientHandler = new HttpClientHandler(); - clientHandler.CookieContainer.Add(requestUri, new Cookie("name", "value")); - await new HttpClient(clientHandler).SendAsync(requestMessage).ConfigureAwait(false); - - // then - var receivedRequest = serverForProxyForwarding.LogEntries.First().RequestMessage; - Check.That(receivedRequest.Cookies).IsNotNull(); - Check.That(receivedRequest.Cookies).ContainsPair("name", "value"); - } + bool HasCorrectHeader(byte[] bytes) => bytes.SequenceEqual(jpegHeader); + bool HasBrokenHeader(byte[] bytes) => bytes.SequenceEqual(brokenJpegHeader); - /// - /// Send some binary content in a request through the proxy and check that the same content - /// arrived at the target. As example a JPEG/JIFF header is used, which is not representable - /// in UTF8 and breaks if it is not treated as binary content. - /// - [Fact] - public async Task WireMockServer_Proxy_Should_preserve_binary_request_content() - { - // arrange - var jpegHeader = new byte[] { 0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x10, 0x4A, 0x46, 0x49, 0x46, 0x00 }; - var brokenJpegHeader = new byte[] - {0xEF, 0xBF, 0xBD, 0xEF, 0xBF, 0xBD, 0xEF, 0xBF, 0xBD, 0xEF, 0xBF, 0xBD, 0x00, 0x10, 0x4A, 0x46, 0x49, 0x46, 0x00}; - - bool HasCorrectHeader(byte[] bytes) => bytes.SequenceEqual(jpegHeader); - bool HasBrokenHeader(byte[] bytes) => bytes.SequenceEqual(brokenJpegHeader); - - var serverForProxyForwarding = WireMockServer.Start(); - serverForProxyForwarding - .Given(Request.Create().WithBody(HasCorrectHeader)) - .RespondWith(Response.Create().WithSuccess()); - - serverForProxyForwarding - .Given(Request.Create().WithBody(HasBrokenHeader)) - .RespondWith(Response.Create().WithStatusCode(HttpStatusCode.InternalServerError)); - - var server = WireMockServer.Start(); - server - .Given(Request.Create()) - .RespondWith(Response.Create().WithProxy(serverForProxyForwarding.Urls[0])); - - // act - var response = await new HttpClient().PostAsync(server.Urls[0], new ByteArrayContent(jpegHeader)).ConfigureAwait(false); - - // assert - Check.That(response.StatusCode).IsEqualTo(HttpStatusCode.OK); - } + var serverForProxyForwarding = WireMockServer.Start(); + serverForProxyForwarding + .Given(Request.Create().WithBody(HasCorrectHeader)) + .RespondWith(Response.Create().WithSuccess()); - [Fact] - public async Task WireMockServer_Proxy_Should_set_BodyAsJson_in_proxied_response() - { - // Assign - string path = $"/prx_{Guid.NewGuid()}"; - var serverForProxyForwarding = WireMockServer.Start(); - serverForProxyForwarding - .Given(Request.Create().WithPath(path)) - .RespondWith(Response.Create() - .WithBodyAsJson(new { i = 42 }) - .WithHeader("Content-Type", "application/json; charset=utf-8")); + serverForProxyForwarding + .Given(Request.Create().WithBody(HasBrokenHeader)) + .RespondWith(Response.Create().WithStatusCode(HttpStatusCode.InternalServerError)); - var server = WireMockServer.Start(); - server - .Given(Request.Create().WithPath(path)) - .RespondWith(Response.Create().WithProxy(serverForProxyForwarding.Urls[0])); + var server = WireMockServer.Start(); + server + .Given(Request.Create()) + .RespondWith(Response.Create().WithProxy(serverForProxyForwarding.Urls[0])); - // Act - var requestMessage = new HttpRequestMessage - { - Method = HttpMethod.Get, - RequestUri = new Uri($"{server.Urls[0]}{path}") - }; - var response = await new HttpClient().SendAsync(requestMessage).ConfigureAwait(false); + // act + var response = await new HttpClient().PostAsync(server.Urls[0], new ByteArrayContent(jpegHeader)).ConfigureAwait(false); - // Assert - string content = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - Check.That(content).IsEqualTo("{\"i\":42}"); - Check.That(response.Content.Headers.GetValues("Content-Type")).ContainsExactly("application/json; charset=utf-8"); - } + // assert + Check.That(response.StatusCode).IsEqualTo(HttpStatusCode.OK); + } - [Fact] - public async Task WireMockServer_Proxy_Should_set_Body_in_multipart_proxied_response() + [Fact] + public async Task WireMockServer_Proxy_Should_set_BodyAsJson_in_proxied_response() + { + // Assign + string path = $"/prx_{Guid.NewGuid()}"; + var serverForProxyForwarding = WireMockServer.Start(); + serverForProxyForwarding + .Given(Request.Create().WithPath(path)) + .RespondWith(Response.Create() + .WithBodyAsJson(new { i = 42 }) + .WithHeader("Content-Type", "application/json; charset=utf-8")); + + var server = WireMockServer.Start(); + server + .Given(Request.Create().WithPath(path)) + .RespondWith(Response.Create().WithProxy(serverForProxyForwarding.Urls[0])); + + // Act + var requestMessage = new HttpRequestMessage { - // Assign - string path = $"/prx_{Guid.NewGuid()}"; - var serverForProxyForwarding = WireMockServer.Start(); - serverForProxyForwarding - .Given(Request.Create().WithPath(path)) - .RespondWith(Response.Create() - .WithBodyAsJson(new { i = 42 }) - ); - - var server = WireMockServer.Start(); - server - .Given(Request.Create().WithPath(path)) - .RespondWith(Response.Create().WithProxy(serverForProxyForwarding.Urls[0])); + Method = HttpMethod.Get, + RequestUri = new Uri($"{server.Urls[0]}{path}") + }; + var response = await new HttpClient().SendAsync(requestMessage).ConfigureAwait(false); + + // Assert + string content = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + Check.That(content).IsEqualTo("{\"i\":42}"); + Check.That(response.Content.Headers.GetValues("Content-Type")).ContainsExactly("application/json; charset=utf-8"); + } - // Act - var uri = new Uri($"{server.Urls[0]}{path}"); - var form = new MultipartFormDataContent - { - { new StringContent("data"), "test", "test.txt" } - }; - var response = await new HttpClient().PostAsync(uri, form).ConfigureAwait(false); + [Fact] + public async Task WireMockServer_Proxy_Should_set_Body_in_multipart_proxied_response() + { + // Assign + string path = $"/prx_{Guid.NewGuid()}"; + var serverForProxyForwarding = WireMockServer.Start(); + serverForProxyForwarding + .Given(Request.Create().WithPath(path)) + .RespondWith(Response.Create() + .WithBodyAsJson(new { i = 42 }) + ); - // Assert - string content = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - Check.That(content).IsEqualTo("{\"i\":42}"); - } + var server = WireMockServer.Start(); + server + .Given(Request.Create().WithPath(path)) + .RespondWith(Response.Create().WithProxy(serverForProxyForwarding.Urls[0])); - [Fact] - public async Task WireMockServer_Proxy_Should_Not_overrule_AdminMappings() + // Act + var uri = new Uri($"{server.Urls[0]}{path}"); + var form = new MultipartFormDataContent { - // Assign - string path = $"/prx_{Guid.NewGuid()}"; - var serverForProxyForwarding = WireMockServer.Start(); - serverForProxyForwarding - .Given(Request.Create().WithPath(path)) - .RespondWith(Response.Create().WithBody("ok")); + { new StringContent("data"), "test", "test.txt" } + }; + var response = await new HttpClient().PostAsync(uri, form).ConfigureAwait(false); - var server = WireMockServer.Start(new WireMockServerSettings - { - StartAdminInterface = true, - ReadStaticMappings = false, - ProxyAndRecordSettings = new ProxyAndRecordSettings - { - Url = serverForProxyForwarding.Urls[0], - SaveMapping = false, - SaveMappingToFile = false - } - }); + // Assert + string content = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + Check.That(content).IsEqualTo("{\"i\":42}"); + } - // Act 1 - var requestMessage1 = new HttpRequestMessage + [Fact] + public async Task WireMockServer_Proxy_Should_Not_overrule_AdminMappings() + { + // Assign + string path = $"/prx_{Guid.NewGuid()}"; + var serverForProxyForwarding = WireMockServer.Start(); + serverForProxyForwarding + .Given(Request.Create().WithPath(path)) + .RespondWith(Response.Create().WithBody("ok")); + + var server = WireMockServer.Start(new WireMockServerSettings + { + StartAdminInterface = true, + ReadStaticMappings = false, + ProxyAndRecordSettings = new ProxyAndRecordSettings { - Method = HttpMethod.Get, - RequestUri = new Uri($"{server.Urls[0]}{path}") - }; - var response1 = await new HttpClient().SendAsync(requestMessage1).ConfigureAwait(false); - - // Assert 1 - string content1 = await response1.Content.ReadAsStringAsync().ConfigureAwait(false); - Check.That(content1).IsEqualTo("ok"); + Url = serverForProxyForwarding.Urls[0], + SaveMapping = false, + SaveMappingToFile = false + } + }); + + // Act 1 + var requestMessage1 = new HttpRequestMessage + { + Method = HttpMethod.Get, + RequestUri = new Uri($"{server.Urls[0]}{path}") + }; + var response1 = await new HttpClient().SendAsync(requestMessage1).ConfigureAwait(false); - // Act 2 - var requestMessage2 = new HttpRequestMessage - { - Method = HttpMethod.Get, - RequestUri = new Uri($"{server.Urls[0]}/__admin/mappings") - }; - var response2 = await new HttpClient().SendAsync(requestMessage2).ConfigureAwait(false); + // Assert 1 + string content1 = await response1.Content.ReadAsStringAsync().ConfigureAwait(false); + Check.That(content1).IsEqualTo("ok"); - // Assert 2 - string content2 = await response2.Content.ReadAsStringAsync().ConfigureAwait(false); - Check.That(content2).IsEqualTo("[]"); - } + // Act 2 + var requestMessage2 = new HttpRequestMessage + { + Method = HttpMethod.Get, + RequestUri = new Uri($"{server.Urls[0]}/__admin/mappings") + }; + var response2 = await new HttpClient().SendAsync(requestMessage2).ConfigureAwait(false); + + // Assert 2 + string content2 = await response2.Content.ReadAsStringAsync().ConfigureAwait(false); + Check.That(content2).IsEqualTo("[]"); + } - // On Ubuntu latest it's : "Resource temporarily unavailable" - // On Windows-2019 it's : "No such host is known." - [Fact] - public async Task WireMockServer_Proxy_WhenTargetIsNotAvailable_Should_Return_CorrectResponse() + // On Ubuntu latest it's : "Resource temporarily unavailable" + // On Windows-2019 it's : "No such host is known." + [Fact] + public async Task WireMockServer_Proxy_WhenTargetIsNotAvailable_Should_Return_CorrectResponse() + { + // Assign + var settings = new WireMockServerSettings { - // Assign - var settings = new WireMockServerSettings + ProxyAndRecordSettings = new ProxyAndRecordSettings { - ProxyAndRecordSettings = new ProxyAndRecordSettings - { - Url = $"http://error{Guid.NewGuid()}:12345" - } - }; - var server = WireMockServer.Start(settings); + Url = $"http://error{Guid.NewGuid()}:12345" + } + }; + var server = WireMockServer.Start(settings); - // Act - var requestMessage = new HttpRequestMessage - { - Method = HttpMethod.Get, - RequestUri = new Uri(server.Urls[0]) - }; - var httpClientHandler = new HttpClientHandler { AllowAutoRedirect = false }; - var result = await new HttpClient(httpClientHandler).SendAsync(requestMessage).ConfigureAwait(false); + // Act + var requestMessage = new HttpRequestMessage + { + Method = HttpMethod.Get, + RequestUri = new Uri(server.Urls[0]) + }; + var httpClientHandler = new HttpClientHandler { AllowAutoRedirect = false }; + var result = await new HttpClient(httpClientHandler).SendAsync(requestMessage).ConfigureAwait(false); - // Assert - result.StatusCode.Should().Be(HttpStatusCode.InternalServerError); + // Assert + result.StatusCode.Should().Be(HttpStatusCode.InternalServerError); - var content = await result.Content.ReadAsStringAsync().ConfigureAwait(false); - content.Should().NotBeEmpty(); + var content = await result.Content.ReadAsStringAsync().ConfigureAwait(false); + content.Should().NotBeEmpty(); - server.LogEntries.Should().HaveCount(1); - var status = ((StatusModel)server.LogEntries.First().ResponseMessage.BodyData.BodyAsJson).Status; + server.LogEntries.Should().HaveCount(1); + var status = ((StatusModel)server.LogEntries.First().ResponseMessage.BodyData.BodyAsJson).Status; - server.Stop(); - } + server.Stop(); } } \ No newline at end of file From 39a3a20d7d35f13c94435bf9106c81cd7c8ba9bf Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Fri, 29 Jul 2022 22:47:52 +0200 Subject: [PATCH 2/4] . --- src/WireMock.Net/Proxy/ProxyHelper.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/WireMock.Net/Proxy/ProxyHelper.cs b/src/WireMock.Net/Proxy/ProxyHelper.cs index 482d049b0..c18473559 100644 --- a/src/WireMock.Net/Proxy/ProxyHelper.cs +++ b/src/WireMock.Net/Proxy/ProxyHelper.cs @@ -30,9 +30,9 @@ public ProxyHelper(WireMockServerSettings settings) IRequestMessage requestMessage, string url) { - Guard.NotNull(client, nameof(client)); - Guard.NotNull(requestMessage, nameof(requestMessage)); - Guard.NotNull(url, nameof(url)); + Guard.NotNull(client); + Guard.NotNull(requestMessage); + Guard.NotNull(url); var originalUri = new Uri(requestMessage.Url); var requiredUri = new Uri(url); @@ -86,7 +86,7 @@ private IMapping ToMapping(ProxyAndRecordSettings proxyAndRecordSettings, IReque } }); - var throwExceptionWhenMatcherFails = _settings.ThrowExceptionWhenMatcherFails == true; + bool throwExceptionWhenMatcherFails = _settings.ThrowExceptionWhenMatcherFails == true; switch (requestMessage.BodyData?.DetectedBodyType) { case BodyType.Json: @@ -113,7 +113,7 @@ private IMapping ToMapping(ProxyAndRecordSettings proxyAndRecordSettings, IReque settings: _settings, request, response, - priority: WireMockConstants.ProxyPriority, // was 0 + priority: WireMockConstants.ProxyPriority, // This was 0 scenario: null, executionConditionState: null, nextState: null, From ea5563db1330bb6f9fad13718589ab7832c0910e Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Fri, 29 Jul 2022 23:02:40 +0200 Subject: [PATCH 3/4] more reformat --- .../Admin/Requests/LogEntryModel.cs | 8 +- .../Serialization/LogEntryMapper.cs | 233 ++++++------ .../Server/WireMockServer.Admin.cs | 2 +- .../WireMockServer.ImportWireMockOrg.cs | 2 +- src/WireMock.Net/Util/BytesEncodingUtils.cs | 344 +++++++++--------- src/WireMock.Net/Util/CompressionUtils.cs | 62 ++-- src/WireMock.Net/Util/DictionaryExtensions.cs | 38 +- src/WireMock.Net/Util/FileHelper.cs | 47 ++- .../Util/HttpStatusRangeParser.cs | 129 ++++--- src/WireMock.Net/Util/PortUtils.cs | 84 ++--- src/WireMock.Net/Util/QueryStringParser.cs | 45 ++- src/WireMock.Net/Util/TypeBuilderUtils.cs | 4 +- src/WireMock.Net/Util/UrlUtils.cs | 46 ++- .../Util/FileHelperTests.cs | 77 ++-- 14 files changed, 551 insertions(+), 570 deletions(-) diff --git a/src/WireMock.Net.Abstractions/Admin/Requests/LogEntryModel.cs b/src/WireMock.Net.Abstractions/Admin/Requests/LogEntryModel.cs index ac327fa65..9c4ffefa0 100644 --- a/src/WireMock.Net.Abstractions/Admin/Requests/LogEntryModel.cs +++ b/src/WireMock.Net.Abstractions/Admin/Requests/LogEntryModel.cs @@ -30,12 +30,12 @@ public class LogEntryModel /// /// The mapping unique title. /// - public string MappingTitle { get; set; } + public string? MappingTitle { get; set; } /// /// The request match result. /// - public LogRequestMatchModel RequestMatchResult { get; set; } + public LogRequestMatchModel? RequestMatchResult { get; set; } /// /// The partial mapping unique identifier. @@ -45,10 +45,10 @@ public class LogEntryModel /// /// The partial mapping unique title. /// - public string PartialMappingTitle { get; set; } + public string? PartialMappingTitle { get; set; } /// /// The partial request match result. /// - public LogRequestMatchModel PartialRequestMatchResult { get; set; } + public LogRequestMatchModel? PartialRequestMatchResult { get; set; } } \ No newline at end of file diff --git a/src/WireMock.Net/Serialization/LogEntryMapper.cs b/src/WireMock.Net/Serialization/LogEntryMapper.cs index d3f46075e..9299b4fc1 100644 --- a/src/WireMock.Net/Serialization/LogEntryMapper.cs +++ b/src/WireMock.Net/Serialization/LogEntryMapper.cs @@ -6,143 +6,142 @@ using WireMock.ResponseBuilders; using WireMock.Types; -namespace WireMock.Serialization +namespace WireMock.Serialization; + +internal static class LogEntryMapper { - internal static class LogEntryMapper + public static LogEntryModel Map(ILogEntry logEntry) { - public static LogEntryModel Map(ILogEntry logEntry) + var logRequestModel = new LogRequestModel { - var logRequestModel = new LogRequestModel - { - DateTime = logEntry.RequestMessage.DateTime, - ClientIP = logEntry.RequestMessage.ClientIP, - Path = logEntry.RequestMessage.Path, - AbsolutePath = logEntry.RequestMessage.AbsolutePath, - Url = logEntry.RequestMessage.Url, - AbsoluteUrl = logEntry.RequestMessage.AbsoluteUrl, - ProxyUrl = logEntry.RequestMessage.ProxyUrl, - Query = logEntry.RequestMessage.Query, - Method = logEntry.RequestMessage.Method, - Headers = logEntry.RequestMessage.Headers, - Cookies = logEntry.RequestMessage.Cookies - }; - - if (logEntry.RequestMessage.BodyData != null) + DateTime = logEntry.RequestMessage.DateTime, + ClientIP = logEntry.RequestMessage.ClientIP, + Path = logEntry.RequestMessage.Path, + AbsolutePath = logEntry.RequestMessage.AbsolutePath, + Url = logEntry.RequestMessage.Url, + AbsoluteUrl = logEntry.RequestMessage.AbsoluteUrl, + ProxyUrl = logEntry.RequestMessage.ProxyUrl, + Query = logEntry.RequestMessage.Query, + Method = logEntry.RequestMessage.Method, + Headers = logEntry.RequestMessage.Headers, + Cookies = logEntry.RequestMessage.Cookies + }; + + if (logEntry.RequestMessage.BodyData != null) + { + logRequestModel.DetectedBodyType = logEntry.RequestMessage.BodyData.DetectedBodyType?.ToString(); + logRequestModel.DetectedBodyTypeFromContentType = logEntry.RequestMessage.BodyData.DetectedBodyTypeFromContentType?.ToString(); + + switch (logEntry.RequestMessage.BodyData.DetectedBodyType) { - logRequestModel.DetectedBodyType = logEntry.RequestMessage.BodyData.DetectedBodyType?.ToString(); - logRequestModel.DetectedBodyTypeFromContentType = logEntry.RequestMessage.BodyData.DetectedBodyTypeFromContentType?.ToString(); + case BodyType.String: + logRequestModel.Body = logEntry.RequestMessage.BodyData.BodyAsString; + break; + + case BodyType.Json: + logRequestModel.Body = logEntry.RequestMessage.BodyData.BodyAsString; // In case of Json, do also save the Body as string (backwards compatible) + logRequestModel.BodyAsJson = logEntry.RequestMessage.BodyData.BodyAsJson; + break; + + case BodyType.Bytes: + logRequestModel.BodyAsBytes = logEntry.RequestMessage.BodyData.BodyAsBytes; + break; + } - switch (logEntry.RequestMessage.BodyData.DetectedBodyType) + logRequestModel.BodyEncoding = logEntry.RequestMessage.BodyData.Encoding != null + ? new EncodingModel { - case BodyType.String: - logRequestModel.Body = logEntry.RequestMessage.BodyData.BodyAsString; - break; - - case BodyType.Json: - logRequestModel.Body = logEntry.RequestMessage.BodyData.BodyAsString; // In case of Json, do also save the Body as string (backwards compatible) - logRequestModel.BodyAsJson = logEntry.RequestMessage.BodyData.BodyAsJson; - break; - - case BodyType.Bytes: - logRequestModel.BodyAsBytes = logEntry.RequestMessage.BodyData.BodyAsBytes; - break; + EncodingName = logEntry.RequestMessage.BodyData.Encoding.EncodingName, + CodePage = logEntry.RequestMessage.BodyData.Encoding.CodePage, + WebName = logEntry.RequestMessage.BodyData.Encoding.WebName } + : null; + } - logRequestModel.BodyEncoding = logEntry.RequestMessage.BodyData.Encoding != null - ? new EncodingModel - { - EncodingName = logEntry.RequestMessage.BodyData.Encoding.EncodingName, - CodePage = logEntry.RequestMessage.BodyData.Encoding.CodePage, - WebName = logEntry.RequestMessage.BodyData.Encoding.WebName - } - : null; - } + var logResponseModel = new LogResponseModel + { + StatusCode = logEntry.ResponseMessage.StatusCode, + Headers = logEntry.ResponseMessage.Headers + }; - var logResponseModel = new LogResponseModel - { - StatusCode = logEntry.ResponseMessage.StatusCode, - Headers = logEntry.ResponseMessage.Headers - }; + if (logEntry.ResponseMessage.FaultType != FaultType.NONE) + { + logResponseModel.FaultType = logEntry.ResponseMessage.FaultType.ToString(); + logResponseModel.FaultPercentage = logEntry.ResponseMessage.FaultPercentage; + } - if (logEntry.ResponseMessage.FaultType != FaultType.NONE) - { - logResponseModel.FaultType = logEntry.ResponseMessage.FaultType.ToString(); - logResponseModel.FaultPercentage = logEntry.ResponseMessage.FaultPercentage; - } + if (logEntry.ResponseMessage.BodyData != null) + { + logResponseModel.BodyOriginal = logEntry.ResponseMessage.BodyOriginal; + logResponseModel.BodyDestination = logEntry.ResponseMessage.BodyDestination; - if (logEntry.ResponseMessage.BodyData != null) - { - logResponseModel.BodyOriginal = logEntry.ResponseMessage.BodyOriginal; - logResponseModel.BodyDestination = logEntry.ResponseMessage.BodyDestination; + logResponseModel.DetectedBodyType = logEntry.ResponseMessage.BodyData.DetectedBodyType; + logResponseModel.DetectedBodyTypeFromContentType = logEntry.ResponseMessage.BodyData.DetectedBodyTypeFromContentType; - logResponseModel.DetectedBodyType = logEntry.ResponseMessage.BodyData.DetectedBodyType; - logResponseModel.DetectedBodyTypeFromContentType = logEntry.ResponseMessage.BodyData.DetectedBodyTypeFromContentType; + switch (logEntry.ResponseMessage.BodyData.DetectedBodyType) + { + case BodyType.String: + logResponseModel.Body = logEntry.ResponseMessage.BodyData.BodyAsString; + break; + + case BodyType.Json: + logResponseModel.BodyAsJson = logEntry.ResponseMessage.BodyData.BodyAsJson; + break; + + case BodyType.Bytes: + logResponseModel.BodyAsBytes = logEntry.ResponseMessage.BodyData.BodyAsBytes; + break; + + case BodyType.File: + logResponseModel.BodyAsFile = logEntry.ResponseMessage.BodyData.BodyAsFile; + logResponseModel.BodyAsFileIsCached = logEntry.ResponseMessage.BodyData.BodyAsFileIsCached; + break; + } - switch (logEntry.ResponseMessage.BodyData.DetectedBodyType) + logResponseModel.BodyEncoding = logEntry.ResponseMessage.BodyData.Encoding != null + ? new EncodingModel { - case BodyType.String: - logResponseModel.Body = logEntry.ResponseMessage.BodyData.BodyAsString; - break; - - case BodyType.Json: - logResponseModel.BodyAsJson = logEntry.ResponseMessage.BodyData.BodyAsJson; - break; - - case BodyType.Bytes: - logResponseModel.BodyAsBytes = logEntry.ResponseMessage.BodyData.BodyAsBytes; - break; - - case BodyType.File: - logResponseModel.BodyAsFile = logEntry.ResponseMessage.BodyData.BodyAsFile; - logResponseModel.BodyAsFileIsCached = logEntry.ResponseMessage.BodyData.BodyAsFileIsCached; - break; + EncodingName = logEntry.ResponseMessage.BodyData.Encoding.EncodingName, + CodePage = logEntry.ResponseMessage.BodyData.Encoding.CodePage, + WebName = logEntry.ResponseMessage.BodyData.Encoding.WebName } + : null; + } - logResponseModel.BodyEncoding = logEntry.ResponseMessage.BodyData.Encoding != null - ? new EncodingModel - { - EncodingName = logEntry.ResponseMessage.BodyData.Encoding.EncodingName, - CodePage = logEntry.ResponseMessage.BodyData.Encoding.CodePage, - WebName = logEntry.ResponseMessage.BodyData.Encoding.WebName - } - : null; - } + return new LogEntryModel + { + Guid = logEntry.Guid, + Request = logRequestModel, + Response = logResponseModel, + + MappingGuid = logEntry.MappingGuid, + MappingTitle = logEntry.MappingTitle, + RequestMatchResult = Map(logEntry.RequestMatchResult), + + PartialMappingGuid = logEntry.PartialMappingGuid, + PartialMappingTitle = logEntry.PartialMappingTitle, + PartialRequestMatchResult = Map(logEntry.PartialMatchResult) + }; + } - return new LogEntryModel - { - Guid = logEntry.Guid, - Request = logRequestModel, - Response = logResponseModel, - - MappingGuid = logEntry.MappingGuid, - MappingTitle = logEntry.MappingTitle, - RequestMatchResult = Map(logEntry.RequestMatchResult), - - PartialMappingGuid = logEntry.PartialMappingGuid, - PartialMappingTitle = logEntry.PartialMappingTitle, - PartialRequestMatchResult = Map(logEntry.PartialMatchResult) - }; + private static LogRequestMatchModel? Map(IRequestMatchResult? matchResult) + { + if (matchResult == null) + { + return null; } - private static LogRequestMatchModel Map(IRequestMatchResult matchResult) + return new LogRequestMatchModel { - if (matchResult == null) + IsPerfectMatch = matchResult.IsPerfectMatch, + TotalScore = matchResult.TotalScore, + TotalNumber = matchResult.TotalNumber, + AverageTotalScore = matchResult.AverageTotalScore, + MatchDetails = matchResult.MatchDetails.Select(md => new { - return null; - } - - return new LogRequestMatchModel - { - IsPerfectMatch = matchResult.IsPerfectMatch, - TotalScore = matchResult.TotalScore, - TotalNumber = matchResult.TotalNumber, - AverageTotalScore = matchResult.AverageTotalScore, - MatchDetails = matchResult.MatchDetails.Select(md => new - { - Name = md.MatcherType.Name.Replace("RequestMessage", string.Empty), - Score = md.Score - } as object).ToList() - }; - } + Name = md.MatcherType.Name.Replace("RequestMessage", string.Empty), + Score = md.Score + } as object).ToList() + }; } } \ No newline at end of file diff --git a/src/WireMock.Net/Server/WireMockServer.Admin.cs b/src/WireMock.Net/Server/WireMockServer.Admin.cs index 6d8fb11a9..79ef9f334 100644 --- a/src/WireMock.Net/Server/WireMockServer.Admin.cs +++ b/src/WireMock.Net/Server/WireMockServer.Admin.cs @@ -185,7 +185,7 @@ public bool ReadStaticMappingAndAddOrUpdate(string path) string filenameWithoutExtension = Path.GetFileNameWithoutExtension(path); - if (FileHelper.TryReadMappingFileWithRetryAndDelay(_settings.FileSystemHandler, path, out string value)) + if (FileHelper.TryReadMappingFileWithRetryAndDelay(_settings.FileSystemHandler, path, out var value)) { var mappingModels = DeserializeJsonToArray(value); foreach (var mappingModel in mappingModels) diff --git a/src/WireMock.Net/Server/WireMockServer.ImportWireMockOrg.cs b/src/WireMock.Net/Server/WireMockServer.ImportWireMockOrg.cs index fccb0bd8b..4ac38764c 100644 --- a/src/WireMock.Net/Server/WireMockServer.ImportWireMockOrg.cs +++ b/src/WireMock.Net/Server/WireMockServer.ImportWireMockOrg.cs @@ -27,7 +27,7 @@ public void ReadStaticWireMockOrgMappingAndAddOrUpdate(string path) string filenameWithoutExtension = Path.GetFileNameWithoutExtension(path); - if (FileHelper.TryReadMappingFileWithRetryAndDelay(_settings.FileSystemHandler, path, out string value)) + if (FileHelper.TryReadMappingFileWithRetryAndDelay(_settings.FileSystemHandler, path, out var value)) { var mappings = DeserializeJsonToArray(value); foreach (var mapping in mappings) diff --git a/src/WireMock.Net/Util/BytesEncodingUtils.cs b/src/WireMock.Net/Util/BytesEncodingUtils.cs index 8635448b6..97904d918 100644 --- a/src/WireMock.Net/Util/BytesEncodingUtils.cs +++ b/src/WireMock.Net/Util/BytesEncodingUtils.cs @@ -1,230 +1,230 @@ -using System; +using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Text; -namespace WireMock.Util +namespace WireMock.Util; + +/// +/// Based on: +/// http://utf8checker.codeplex.com +/// https://github.com/0x53A/Mvvm/blob/master/src/Mvvm/src/Utf8Checker.cs +/// +/// References: +/// http://anubis.dkuug.dk/JTC1/SC2/WG2/docs/n1335 +/// http://www.cl.cam.ac.uk/~mgk25/ucs/ISO-10646-UTF-8.html +/// http://www.unicode.org/versions/corrigendum1.html +/// http://www.ietf.org/rfc/rfc2279.txt +/// +public static class BytesEncodingUtils { /// - /// Based on: - /// http://utf8checker.codeplex.com - /// https://github.com/0x53A/Mvvm/blob/master/src/Mvvm/src/Utf8Checker.cs - /// - /// References: - /// http://anubis.dkuug.dk/JTC1/SC2/WG2/docs/n1335 - /// http://www.cl.cam.ac.uk/~mgk25/ucs/ISO-10646-UTF-8.html - /// http://www.unicode.org/versions/corrigendum1.html - /// http://www.ietf.org/rfc/rfc2279.txt + /// Tries the get the Encoding from an array of bytes. /// - public static class BytesEncodingUtils + /// The bytes. + /// The output encoding. + public static bool TryGetEncoding(byte[] bytes, [NotNullWhen(true)] out Encoding? encoding) { - /// - /// Tries the get the Encoding from an array of bytes. - /// - /// The bytes. - /// The output encoding. - public static bool TryGetEncoding(byte[] bytes, out Encoding encoding) - { - encoding = null; - if (bytes.All(b => b < 80)) - { - encoding = Encoding.ASCII; - return true; - } + encoding = null; + if (bytes.All(b => b < 80)) + { + encoding = Encoding.ASCII; + return true; + } - if (StartsWith(bytes, new byte[] { 0xff, 0xfe, 0x00, 0x00 })) - { - encoding = Encoding.UTF32; - return true; - } + if (StartsWith(bytes, new byte[] { 0xff, 0xfe, 0x00, 0x00 })) + { + encoding = Encoding.UTF32; + return true; + } - if (StartsWith(bytes, new byte[] { 0xfe, 0xff })) - { - encoding = Encoding.BigEndianUnicode; - return true; - } + if (StartsWith(bytes, new byte[] { 0xfe, 0xff })) + { + encoding = Encoding.BigEndianUnicode; + return true; + } - if (StartsWith(bytes, new byte[] { 0xff, 0xfe })) - { - encoding = Encoding.Unicode; - return true; - } + if (StartsWith(bytes, new byte[] { 0xff, 0xfe })) + { + encoding = Encoding.Unicode; + return true; + } - if (StartsWith(bytes, new byte[] { 0xef, 0xbb, 0xbf })) - { - encoding = Encoding.UTF8; - return true; - } + if (StartsWith(bytes, new byte[] { 0xef, 0xbb, 0xbf })) + { + encoding = Encoding.UTF8; + return true; + } - if (IsUtf8(bytes, bytes.Length)) + if (IsUtf8(bytes, bytes.Length)) + { + encoding = new UTF8Encoding(false); + return true; + } + + return false; + } + + private static bool StartsWith(IEnumerable data, IReadOnlyCollection other) + { + byte[] arraySelf = data.Take(other.Count).ToArray(); + return other.SequenceEqual(arraySelf); + } + + private static bool IsUtf8(IReadOnlyList buffer, int length) + { + int position = 0; + int bytes = 0; + while (position < length) + { + if (!IsValid(buffer, position, length, ref bytes)) { - encoding = new UTF8Encoding(false); - return true; + return false; } + position += bytes; + } + return true; + } - return false; +#pragma warning disable S3776 // Cognitive Complexity of methods should not be too high + private static bool IsValid(IReadOnlyList buffer, int position, int length, ref int bytes) + { + if (length > buffer.Count) + { + throw new ArgumentException("Invalid length"); } - private static bool StartsWith(IEnumerable data, IReadOnlyCollection other) + if (position > length - 1) { - byte[] arraySelf = data.Take(other.Count).ToArray(); - return other.SequenceEqual(arraySelf); + bytes = 0; + return true; } - private static bool IsUtf8(IReadOnlyList buffer, int length) + byte ch = buffer[position]; + if (ch <= 0x7F) { - int position = 0; - int bytes = 0; - while (position < length) + bytes = 1; + return true; + } + + if (ch >= 0xc2 && ch <= 0xdf) + { + if (position >= length - 2) { - if (!IsValid(buffer, position, length, ref bytes)) - { - return false; - } - position += bytes; + bytes = 0; + return false; } + + if (buffer[position + 1] < 0x80 || buffer[position + 1] > 0xbf) + { + bytes = 0; + return false; + } + + bytes = 2; return true; } -#pragma warning disable S3776 // Cognitive Complexity of methods should not be too high - private static bool IsValid(IReadOnlyList buffer, int position, int length, ref int bytes) + if (ch == 0xe0) { - if (length > buffer.Count) + if (position >= length - 3) { - throw new ArgumentException("Invalid length"); + bytes = 0; + return false; } - if (position > length - 1) + if (buffer[position + 1] < 0xa0 || buffer[position + 1] > 0xbf || + buffer[position + 2] < 0x80 || buffer[position + 2] > 0xbf) { bytes = 0; - return true; + return false; } - byte ch = buffer[position]; - if (ch <= 0x7F) + bytes = 3; + return true; + } + + if (ch >= 0xe1 && ch <= 0xef) + { + if (position >= length - 3) { - bytes = 1; - return true; + bytes = 0; + return false; } - if (ch >= 0xc2 && ch <= 0xdf) + if (buffer[position + 1] < 0x80 || buffer[position + 1] > 0xbf || + buffer[position + 2] < 0x80 || buffer[position + 2] > 0xbf) { - if (position >= length - 2) - { - bytes = 0; - return false; - } - - if (buffer[position + 1] < 0x80 || buffer[position + 1] > 0xbf) - { - bytes = 0; - return false; - } - - bytes = 2; - return true; + bytes = 0; + return false; } - if (ch == 0xe0) + bytes = 3; + return true; + } + + if (ch == 0xf0) + { + if (position >= length - 4) + { + bytes = 0; + return false; + } + + if (buffer[position + 1] < 0x90 || buffer[position + 1] > 0xbf || + buffer[position + 2] < 0x80 || buffer[position + 2] > 0xbf || + buffer[position + 3] < 0x80 || buffer[position + 3] > 0xbf) { - if (position >= length - 3) - { - bytes = 0; - return false; - } - - if (buffer[position + 1] < 0xa0 || buffer[position + 1] > 0xbf || - buffer[position + 2] < 0x80 || buffer[position + 2] > 0xbf) - { - bytes = 0; - return false; - } - - bytes = 3; - return true; + bytes = 0; + return false; } - if (ch >= 0xe1 && ch <= 0xef) + bytes = 4; + return true; + } + + if (ch == 0xf4) + { + if (position >= length - 4) { - if (position >= length - 3) - { - bytes = 0; - return false; - } - - if (buffer[position + 1] < 0x80 || buffer[position + 1] > 0xbf || - buffer[position + 2] < 0x80 || buffer[position + 2] > 0xbf) - { - bytes = 0; - return false; - } - - bytes = 3; - return true; + bytes = 0; + return false; } - if (ch == 0xf0) + if (buffer[position + 1] < 0x80 || buffer[position + 1] > 0x8f || + buffer[position + 2] < 0x80 || buffer[position + 2] > 0xbf || + buffer[position + 3] < 0x80 || buffer[position + 3] > 0xbf) { - if (position >= length - 4) - { - bytes = 0; - return false; - } - - if (buffer[position + 1] < 0x90 || buffer[position + 1] > 0xbf || - buffer[position + 2] < 0x80 || buffer[position + 2] > 0xbf || - buffer[position + 3] < 0x80 || buffer[position + 3] > 0xbf) - { - bytes = 0; - return false; - } - - bytes = 4; - return true; + bytes = 0; + return false; } - if (ch == 0xf4) + bytes = 4; + return true; + } + + if (ch >= 0xf1 && ch <= 0xf3) + { + if (position >= length - 4) { - if (position >= length - 4) - { - bytes = 0; - return false; - } - - if (buffer[position + 1] < 0x80 || buffer[position + 1] > 0x8f || - buffer[position + 2] < 0x80 || buffer[position + 2] > 0xbf || - buffer[position + 3] < 0x80 || buffer[position + 3] > 0xbf) - { - bytes = 0; - return false; - } - - bytes = 4; - return true; + bytes = 0; + return false; } - if (ch >= 0xf1 && ch <= 0xf3) + if (buffer[position + 1] < 0x80 || buffer[position + 1] > 0xbf || + buffer[position + 2] < 0x80 || buffer[position + 2] > 0xbf || + buffer[position + 3] < 0x80 || buffer[position + 3] > 0xbf) { - if (position >= length - 4) - { - bytes = 0; - return false; - } - - if (buffer[position + 1] < 0x80 || buffer[position + 1] > 0xbf || - buffer[position + 2] < 0x80 || buffer[position + 2] > 0xbf || - buffer[position + 3] < 0x80 || buffer[position + 3] > 0xbf) - { - bytes = 0; - return false; - } - - bytes = 4; - return true; + bytes = 0; + return false; } - return false; + bytes = 4; + return true; } + + return false; } -#pragma warning restore S3776 // Cognitive Complexity of methods should not be too high -} \ No newline at end of file +} +#pragma warning restore S3776 // Cognitive Complexity of methods should not be too high \ No newline at end of file diff --git a/src/WireMock.Net/Util/CompressionUtils.cs b/src/WireMock.Net/Util/CompressionUtils.cs index 78880ce42..40da363cf 100644 --- a/src/WireMock.Net/Util/CompressionUtils.cs +++ b/src/WireMock.Net/Util/CompressionUtils.cs @@ -1,49 +1,39 @@ -using System; +using System; using System.IO; using System.IO.Compression; -namespace WireMock.Util +namespace WireMock.Util; + +internal static class CompressionUtils { - internal static class CompressionUtils + public static byte[] Compress(string contentEncoding, byte[] data) { - public static byte[] Compress(string contentEncoding, byte[] data) - { - using (var compressedStream = new MemoryStream()) - using (var zipStream = Create(contentEncoding, compressedStream, CompressionMode.Compress)) - { - zipStream.Write(data, 0, data.Length); + using var compressedStream = new MemoryStream(); + using var zipStream = Create(contentEncoding, compressedStream, CompressionMode.Compress); + zipStream.Write(data, 0, data.Length); #if !NETSTANDARD1_3 - zipStream.Close(); + zipStream.Close(); #endif - return compressedStream.ToArray(); - } - } + return compressedStream.ToArray(); + } - public static byte[] Decompress(string contentEncoding, byte[] data) - { - using (var compressedStream = new MemoryStream(data)) - using (var zipStream = Create(contentEncoding, compressedStream, CompressionMode.Decompress)) - using (var resultStream = new MemoryStream()) - { - zipStream.CopyTo(resultStream); - return resultStream.ToArray(); - } - } + public static byte[] Decompress(string contentEncoding, byte[] data) + { + using var compressedStream = new MemoryStream(data); + using var zipStream = Create(contentEncoding, compressedStream, CompressionMode.Decompress); + using var resultStream = new MemoryStream(); + zipStream.CopyTo(resultStream); + return resultStream.ToArray(); + } - private static Stream Create(string contentEncoding, Stream stream, CompressionMode mode) + private static Stream Create(string contentEncoding, Stream stream, CompressionMode mode) + { + return contentEncoding switch { - switch (contentEncoding) - { - case "gzip": - return new GZipStream(stream, mode); - - case "deflate": - return new DeflateStream(stream, mode); - - default: - throw new NotSupportedException($"ContentEncoding '{contentEncoding}' is not supported."); - } - } + "gzip" => new GZipStream(stream, mode), + "deflate" => new DeflateStream(stream, mode), + _ => throw new NotSupportedException($"ContentEncoding '{contentEncoding}' is not supported.") + }; } } \ No newline at end of file diff --git a/src/WireMock.Net/Util/DictionaryExtensions.cs b/src/WireMock.Net/Util/DictionaryExtensions.cs index 58884fa31..aeae8ad85 100644 --- a/src/WireMock.Net/Util/DictionaryExtensions.cs +++ b/src/WireMock.Net/Util/DictionaryExtensions.cs @@ -1,32 +1,30 @@ -using System; +using System; using System.Collections.Generic; -using JetBrains.Annotations; using Stef.Validation; -namespace WireMock.Util +namespace WireMock.Util; + +/// +/// Some IDictionary Extensions +/// +public static class DictionaryExtensions { /// - /// Some IDictionary Extensions + /// Loops the dictionary and executes the specified action. /// - public static class DictionaryExtensions + /// The type of the key. + /// The type of the value. + /// The dictionary to loop (can be null). + /// The action. + public static void Loop(this IDictionary? dictionary, Action action) { - /// - /// Loops the dictionary and executes the specified action. - /// - /// The type of the key. - /// The type of the value. - /// The dictionary to loop (can be null). - /// The action. - public static void Loop(this IDictionary dictionary, [NotNull] Action action) - { - Guard.NotNull(action, nameof(action)); + Guard.NotNull(action); - if (dictionary != null) + if (dictionary != null) + { + foreach (var entry in dictionary) { - foreach (var entry in dictionary) - { - action(entry.Key, entry.Value); - } + action(entry.Key, entry.Value); } } } diff --git a/src/WireMock.Net/Util/FileHelper.cs b/src/WireMock.Net/Util/FileHelper.cs index 82b5c12bd..99a80cbbf 100644 --- a/src/WireMock.Net/Util/FileHelper.cs +++ b/src/WireMock.Net/Util/FileHelper.cs @@ -1,36 +1,35 @@ -using JetBrains.Annotations; +using System.Diagnostics.CodeAnalysis; using System.Threading; -using WireMock.Handlers; using Stef.Validation; +using WireMock.Handlers; + +namespace WireMock.Util; -namespace WireMock.Util +internal static class FileHelper { - internal static class FileHelper - { - private const int NumberOfRetries = 3; - private const int DelayOnRetry = 500; + private const int NumberOfRetries = 3; + private const int DelayOnRetry = 500; - public static bool TryReadMappingFileWithRetryAndDelay([NotNull] IFileSystemHandler handler, [NotNull] string path, out string value) - { - Guard.NotNull(handler, nameof(handler)); - Guard.NotNullOrEmpty(path, nameof(path)); + public static bool TryReadMappingFileWithRetryAndDelay(IFileSystemHandler handler, string path, [NotNullWhen(true)] out string? value) + { + Guard.NotNull(handler); + Guard.NotNullOrEmpty(path); - value = null; + value = null; - for (int i = 1; i <= NumberOfRetries; ++i) + for (int i = 1; i <= NumberOfRetries; ++i) + { + try { - try - { - value = handler.ReadMappingFile(path); - return true; - } - catch - { - Thread.Sleep(DelayOnRetry); - } + value = handler.ReadMappingFile(path); + return true; + } + catch + { + Thread.Sleep(DelayOnRetry); } - - return false; } + + return false; } } \ No newline at end of file diff --git a/src/WireMock.Net/Util/HttpStatusRangeParser.cs b/src/WireMock.Net/Util/HttpStatusRangeParser.cs index e0830acc6..a212f03a6 100644 --- a/src/WireMock.Net/Util/HttpStatusRangeParser.cs +++ b/src/WireMock.Net/Util/HttpStatusRangeParser.cs @@ -1,91 +1,90 @@ -using System; +using System; using System.Linq; using System.Net; using System.Text.RegularExpressions; -namespace WireMock.Util +namespace WireMock.Util; + +/// +/// Based on https://github.com/tmenier/Flurl/blob/129565361e135e639f1d44a35a78aea4302ac6ca/src/Flurl.Http/HttpStatusRangeParser.cs +/// +internal static class HttpStatusRangeParser { /// - /// Based on https://github.com/tmenier/Flurl/blob/129565361e135e639f1d44a35a78aea4302ac6ca/src/Flurl.Http/HttpStatusRangeParser.cs + /// Determines whether the specified pattern is match. /// - internal static class HttpStatusRangeParser + /// The pattern. (Can be null, in that case it's allowed.) + /// The value. + /// is invalid. + public static bool IsMatch(string pattern, object httpStatusCode) { - /// - /// Determines whether the specified pattern is match. - /// - /// The pattern. (Can be null, in that case it's allowed.) - /// The value. - /// is invalid. - public static bool IsMatch(string pattern, object httpStatusCode) + switch (httpStatusCode) { - switch (httpStatusCode) - { - case int statusCodeAsInteger: - return IsMatch(pattern, statusCodeAsInteger); - - case string statusCodeAsString: - return IsMatch(pattern, int.Parse(statusCodeAsString)); - } + case int statusCodeAsInteger: + return IsMatch(pattern, statusCodeAsInteger); - return false; + case string statusCodeAsString: + return IsMatch(pattern, int.Parse(statusCodeAsString)); } - /// - /// Determines whether the specified pattern is match. - /// - /// The pattern. (Can be null, in that case it's allowed.) - /// The value. - /// is invalid. - public static bool IsMatch(string pattern, HttpStatusCode httpStatusCode) + return false; + } + + /// + /// Determines whether the specified pattern is match. + /// + /// The pattern. (Can be null, in that case it's allowed.) + /// The value. + /// is invalid. + public static bool IsMatch(string pattern, HttpStatusCode httpStatusCode) + { + return IsMatch(pattern, (int)httpStatusCode); + } + + /// + /// Determines whether the specified pattern is match. + /// + /// The pattern. (Can be null, in that case it's allowed.) + /// The value. + /// is invalid. + public static bool IsMatch(string? pattern, int httpStatusCode) + { + if (pattern == null) { - return IsMatch(pattern, (int)httpStatusCode); + return true; } - /// - /// Determines whether the specified pattern is match. - /// - /// The pattern. (Can be null, in that case it's allowed.) - /// The value. - /// is invalid. - public static bool IsMatch(string pattern, int httpStatusCode) + foreach (var range in pattern.Split(',').Select(p => p.Trim())) { - if (pattern == null) - { - return true; - } - - foreach (var range in pattern.Split(',').Select(p => p.Trim())) + switch (range) { - switch (range) - { - case "": - continue; - - case "*": - return true; // special case - allow everything - } + case "": + continue; - string[] bounds = range.Split('-'); - int lower = 0; - int upper = 0; + case "*": + return true; // special case - allow everything + } - bool valid = - bounds.Length <= 2 && - int.TryParse(Regex.Replace(bounds.First().Trim(), "[*xX]", "0"), out lower) && - int.TryParse(Regex.Replace(bounds.Last().Trim(), "[*xX]", "9"), out upper); + string[] bounds = range.Split('-'); + int lower = 0; + int upper = 0; - if (!valid) - { - throw new ArgumentException($"Invalid range pattern: \"{pattern}\". Examples of allowed patterns: \"400\", \"4xx\", \"300,400-403\", \"*\"."); - } + bool valid = + bounds.Length <= 2 && + int.TryParse(Regex.Replace(bounds.First().Trim(), "[*xX]", "0"), out lower) && + int.TryParse(Regex.Replace(bounds.Last().Trim(), "[*xX]", "9"), out upper); - if (httpStatusCode >= lower && httpStatusCode <= upper) - { - return true; - } + if (!valid) + { + throw new ArgumentException($"Invalid range pattern: \"{pattern}\". Examples of allowed patterns: \"400\", \"4xx\", \"300,400-403\", \"*\"."); } - return false; + if (httpStatusCode >= lower && httpStatusCode <= upper) + { + return true; + } } + + return false; } } \ No newline at end of file diff --git a/src/WireMock.Net/Util/PortUtils.cs b/src/WireMock.Net/Util/PortUtils.cs index 6bb189984..04fcd70ef 100644 --- a/src/WireMock.Net/Util/PortUtils.cs +++ b/src/WireMock.Net/Util/PortUtils.cs @@ -1,58 +1,58 @@ -using System; +using System; +using System.Diagnostics.CodeAnalysis; using System.Net; using System.Net.Sockets; using System.Text.RegularExpressions; -namespace WireMock.Util +namespace WireMock.Util; + +/// +/// Port Utility class +/// +public static class PortUtils { + private static readonly Regex UrlDetailsRegex = new(@"^((?\w+)://)(?[^/]+?):(?\d+)\/?$", RegexOptions.Compiled); + /// - /// Port Utility class + /// Finds a free TCP port. /// - public static class PortUtils + /// see http://stackoverflow.com/questions/138043/find-the-next-tcp-port-in-net. + public static int FindFreeTcpPort() { - private static readonly Regex UrlDetailsRegex = new Regex(@"^((?\w+)://)(?[^/]+?):(?\d+)\/?$", RegexOptions.Compiled); + TcpListener? tcpListener = null; + try + { + tcpListener = new TcpListener(IPAddress.Loopback, 0); + tcpListener.Start(); - /// - /// Finds a free TCP port. - /// - /// see http://stackoverflow.com/questions/138043/find-the-next-tcp-port-in-net. - public static int FindFreeTcpPort() + return ((IPEndPoint)tcpListener.LocalEndpoint).Port; + } + finally { - TcpListener tcpListener = null; - try - { - tcpListener = new TcpListener(IPAddress.Loopback, 0); - tcpListener.Start(); - - return ((IPEndPoint)tcpListener.LocalEndpoint).Port; - } - finally - { - tcpListener?.Stop(); - } + tcpListener?.Stop(); } + } - /// - /// Extract the if-isHttps, protocol, host and port from a URL. - /// - public static bool TryExtract(string url, out bool isHttps, out string protocol, out string host, out int port) + /// + /// Extract the if-isHttps, protocol, host and port from a URL. + /// + public static bool TryExtract(string url, out bool isHttps, [NotNullWhen(true)] out string? protocol, [NotNullWhen(true)] out string? host, out int port) + { + isHttps = false; + protocol = null; + host = null; + port = default; + + var match = UrlDetailsRegex.Match(url); + if (match.Success) { - isHttps = false; - protocol = null; - host = null; - port = default; - - var match = UrlDetailsRegex.Match(url); - if (match.Success) - { - protocol = match.Groups["proto"].Value; - isHttps = protocol.StartsWith("https", StringComparison.OrdinalIgnoreCase); - host = match.Groups["host"].Value; - - return int.TryParse(match.Groups["port"].Value, out port); - } - - return false; + protocol = match.Groups["proto"].Value; + isHttps = protocol.StartsWith("https", StringComparison.OrdinalIgnoreCase); + host = match.Groups["host"].Value; + + return int.TryParse(match.Groups["port"].Value, out port); } + + return false; } } \ No newline at end of file diff --git a/src/WireMock.Net/Util/QueryStringParser.cs b/src/WireMock.Net/Util/QueryStringParser.cs index 279b62900..293fcd574 100644 --- a/src/WireMock.Net/Util/QueryStringParser.cs +++ b/src/WireMock.Net/Util/QueryStringParser.cs @@ -1,38 +1,37 @@ -using System; +using System; using System.Net; using System.Collections.Generic; using System.Linq; using WireMock.Types; -namespace WireMock.Util +namespace WireMock.Util; + +/// +/// Based on https://stackoverflow.com/questions/659887/get-url-parameters-from-a-string-in-net +/// +internal static class QueryStringParser { - /// - /// Based on https://stackoverflow.com/questions/659887/get-url-parameters-from-a-string-in-net - /// - internal static class QueryStringParser + public static IDictionary> Parse(string queryString) { - public static IDictionary> Parse(string queryString) + if (string.IsNullOrEmpty(queryString)) { - if (string.IsNullOrEmpty(queryString)) - { - return new Dictionary>(); - } + return new Dictionary>(); + } - string[] JoinParts(string[] parts) + string[] JoinParts(string[] parts) + { + if (parts.Length > 1) { - if (parts.Length > 1) - { - return parts[1].Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries); // support "?key=1,2" - } - - return new string[0]; + return parts[1].Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries); // support "?key=1,2" } - return queryString.TrimStart('?') - .Split(new[] { '&', ';' }, StringSplitOptions.RemoveEmptyEntries) // Support "?key=value;key=anotherValue" and "?key=value&key=anotherValue" - .Select(parameter => parameter.Split(new[] { '=' }, 2, StringSplitOptions.RemoveEmptyEntries)) - .GroupBy(parts => parts[0], JoinParts) - .ToDictionary(grouping => grouping.Key, grouping => new WireMockList(grouping.SelectMany(x => x).Select(WebUtility.UrlDecode))); + return new string[0]; } + + return queryString.TrimStart('?') + .Split(new[] { '&', ';' }, StringSplitOptions.RemoveEmptyEntries) // Support "?key=value;key=anotherValue" and "?key=value&key=anotherValue" + .Select(parameter => parameter.Split(new[] { '=' }, 2, StringSplitOptions.RemoveEmptyEntries)) + .GroupBy(parts => parts[0], JoinParts) + .ToDictionary(grouping => grouping.Key, grouping => new WireMockList(grouping.SelectMany(x => x).Select(WebUtility.UrlDecode))); } } \ No newline at end of file diff --git a/src/WireMock.Net/Util/TypeBuilderUtils.cs b/src/WireMock.Net/Util/TypeBuilderUtils.cs index 30fe9fbf2..d78e717fd 100644 --- a/src/WireMock.Net/Util/TypeBuilderUtils.cs +++ b/src/WireMock.Net/Util/TypeBuilderUtils.cs @@ -14,8 +14,8 @@ internal static class TypeBuilderUtils { private static readonly ConcurrentDictionary, Type> Types = new(); - private static readonly ModuleBuilder ModuleBuilder = - AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("WireMock.Net.Reflection"), AssemblyBuilderAccess.Run) + private static readonly ModuleBuilder ModuleBuilder = AssemblyBuilder + .DefineDynamicAssembly(new AssemblyName("WireMock.Net.Reflection"), AssemblyBuilderAccess.Run) .DefineDynamicModule("WireMock.Net.Reflection.Module"); public static Type BuildType(IDictionary properties, string? name = null) diff --git a/src/WireMock.Net/Util/UrlUtils.cs b/src/WireMock.Net/Util/UrlUtils.cs index 7def9ee1d..9f9ca76f9 100644 --- a/src/WireMock.Net/Util/UrlUtils.cs +++ b/src/WireMock.Net/Util/UrlUtils.cs @@ -1,5 +1,4 @@ -using System; -using JetBrains.Annotations; +using System; using WireMock.Models; using Stef.Validation; #if !USE_ASPNETCORE @@ -8,34 +7,33 @@ using Microsoft.AspNetCore.Http; #endif -namespace WireMock.Util +namespace WireMock.Util; + +internal static class UrlUtils { - internal static class UrlUtils + public static UrlDetails Parse(Uri uri, PathString pathBase) { - public static UrlDetails Parse([NotNull] Uri uri, PathString pathBase) - { - Guard.NotNull(uri, nameof(uri)); + Guard.NotNull(uri); - if (!pathBase.HasValue) - { - return new UrlDetails(uri, uri); - } + if (!pathBase.HasValue) + { + return new UrlDetails(uri, uri); + } - var builder = new UriBuilder(uri); - builder.Path = RemoveFirst(builder.Path, pathBase.Value); + var builder = new UriBuilder(uri); + builder.Path = RemoveFirst(builder.Path, pathBase.Value); - return new UrlDetails(uri, builder.Uri); - } + return new UrlDetails(uri, builder.Uri); + } - private static string RemoveFirst(string text, string search) + private static string RemoveFirst(string text, string search) + { + int pos = text.IndexOf(search, StringComparison.Ordinal); + if (pos < 0) { - int pos = text.IndexOf(search, StringComparison.Ordinal); - if (pos < 0) - { - return text; - } - - return text.Substring(0, pos) + text.Substring(pos + search.Length); + return text; } + + return text.Substring(0, pos) + text.Substring(pos + search.Length); } -} +} \ No newline at end of file diff --git a/test/WireMock.Net.Tests/Util/FileHelperTests.cs b/test/WireMock.Net.Tests/Util/FileHelperTests.cs index 97a887e97..c77f201b9 100644 --- a/test/WireMock.Net.Tests/Util/FileHelperTests.cs +++ b/test/WireMock.Net.Tests/Util/FileHelperTests.cs @@ -1,48 +1,47 @@ -using FluentAssertions; +using FluentAssertions; using Moq; using System; using WireMock.Handlers; using WireMock.Util; using Xunit; -namespace WireMock.Net.Tests.Util +namespace WireMock.Net.Tests.Util; + +public class FileHelperTests { - public class FileHelperTests + [Fact] + public void TryReadMappingFileWithRetryAndDelay_WithIFileSystemHandlerOk_ReturnsTrue() + { + // Assign + var staticMappingHandlerMock = new Mock(); + staticMappingHandlerMock.Setup(m => m.ReadMappingFile(It.IsAny())).Returns("text"); + + // Act + bool result = FileHelper.TryReadMappingFileWithRetryAndDelay(staticMappingHandlerMock.Object, @"c:\temp", out var value); + + // Assert + result.Should().BeTrue(); + value.Should().Be("text"); + + // Verify + staticMappingHandlerMock.Verify(m => m.ReadMappingFile(@"c:\temp"), Times.Once); + } + + [Fact] + public void TryReadMappingFileWithRetryAndDelay_WithIFileSystemHandlerThrows_ReturnsFalse() { - [Fact] - public void TryReadMappingFileWithRetryAndDelay_WithIFileSystemHandlerOk_ReturnsTrue() - { - // Assign - var staticMappingHandlerMock = new Mock(); - staticMappingHandlerMock.Setup(m => m.ReadMappingFile(It.IsAny())).Returns("text"); - - // Act - bool result = FileHelper.TryReadMappingFileWithRetryAndDelay(staticMappingHandlerMock.Object, @"c:\temp", out string value); - - // Assert - result.Should().BeTrue(); - value.Should().Be("text"); - - // Verify - staticMappingHandlerMock.Verify(m => m.ReadMappingFile(@"c:\temp"), Times.Once); - } - - [Fact] - public void TryReadMappingFileWithRetryAndDelay_WithIFileSystemHandlerThrows_ReturnsFalse() - { - // Assign - var staticMappingHandlerMock = new Mock(); - staticMappingHandlerMock.Setup(m => m.ReadMappingFile(It.IsAny())).Throws(); - - // Act - bool result = FileHelper.TryReadMappingFileWithRetryAndDelay(staticMappingHandlerMock.Object, @"c:\temp", out string value); - - // Assert - result.Should().BeFalse(); - value.Should().BeNull(); - - // Verify - staticMappingHandlerMock.Verify(m => m.ReadMappingFile(@"c:\temp"), Times.Exactly(3)); - } + // Assign + var staticMappingHandlerMock = new Mock(); + staticMappingHandlerMock.Setup(m => m.ReadMappingFile(It.IsAny())).Throws(); + + // Act + bool result = FileHelper.TryReadMappingFileWithRetryAndDelay(staticMappingHandlerMock.Object, @"c:\temp", out var value); + + // Assert + result.Should().BeFalse(); + value.Should().BeNull(); + + // Verify + staticMappingHandlerMock.Verify(m => m.ReadMappingFile(@"c:\temp"), Times.Exactly(3)); } -} +} \ No newline at end of file From ac8c2a15bb45ff26bc97ac2503a92c906a2e2355 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Sat, 30 Jul 2022 10:15:12 +0200 Subject: [PATCH 4/4] . --- .../IRequestMessage.cs | 4 +- .../Owin/AspNetCoreSelfHost.NETCore.cs | 59 ++-- src/WireMock.Net/Owin/AspNetCoreSelfHost.cs | 8 +- src/WireMock.Net/Owin/HostUrlDetails.cs | 17 +- src/WireMock.Net/Owin/HostUrlOptions.cs | 53 +-- src/WireMock.Net/Owin/IMappingMatcher.cs | 9 +- src/WireMock.Net/Owin/IOwinSelfHost.cs | 51 ++- .../Owin/Mappers/OwinRequestMapper.cs | 23 +- src/WireMock.Net/Owin/MappingMatcher.cs | 97 +++--- src/WireMock.Net/Owin/MappingMatcherResult.cs | 11 +- src/WireMock.Net/Owin/OwinSelfHost.cs | 149 ++++---- src/WireMock.Net/Owin/WireMockMiddleware.cs | 15 +- src/WireMock.Net/RequestMessage.cs | 4 +- .../Owin/MappingMatcherTests.cs | 324 +++++++++--------- .../WireMockServer.Proxy.cs | 10 +- 15 files changed, 430 insertions(+), 404 deletions(-) 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]