diff --git a/resources/WireMock.Net-Logo.png b/resources/WireMock.Net-Logo.png index 7325bd13..6e2bcf39 100644 Binary files a/resources/WireMock.Net-Logo.png and b/resources/WireMock.Net-Logo.png differ diff --git a/src/WireMock.Net.Abstractions/Admin/Mappings/CookieModel.cs b/src/WireMock.Net.Abstractions/Admin/Mappings/CookieModel.cs index 5b88bb42..e38c60f1 100644 --- a/src/WireMock.Net.Abstractions/Admin/Mappings/CookieModel.cs +++ b/src/WireMock.Net.Abstractions/Admin/Mappings/CookieModel.cs @@ -1,31 +1,39 @@ using System.Collections.Generic; -namespace WireMock.Admin.Mappings +namespace WireMock.Admin.Mappings; + +/// +/// Cookie Model +/// +[FluentBuilder.AutoGenerateBuilder] +public class CookieModel { /// - /// Cookie Model + /// Gets or sets the name. /// - [FluentBuilder.AutoGenerateBuilder] - public class CookieModel - { - /// - /// Gets or sets the name. - /// - public string Name { get; set; } = null!; + public string Name { get; set; } = null!; - /// - /// Gets or sets the matchers. - /// - public IList? Matchers { get; set; } + /// + /// Gets or sets the matchers. + /// + public IList? Matchers { get; set; } - /// - /// Gets or sets the ignore case. - /// - public bool? IgnoreCase { get; set; } + /// + /// Gets or sets the ignore case. + /// + public bool? IgnoreCase { get; set; } - /// - /// Reject on match. - /// - public bool? RejectOnMatch { get; set; } - } + /// + /// Reject on match. + /// + public bool? RejectOnMatch { get; set; } + + /// + /// The Operator to use when matchers are defined. [Optional] + /// - null = Same as "or". + /// - "or" = Only one pattern should match. + /// - "and" = All patterns should match. + /// - "average" = The average value from all patterns. + /// + public string? MatchOperator { get; set; } } \ No newline at end of file diff --git a/src/WireMock.Net.Abstractions/Admin/Settings/ProxyAndRecordSettingsModel.cs b/src/WireMock.Net.Abstractions/Admin/Settings/ProxyAndRecordSettingsModel.cs index 96ca4181..36d4b03e 100644 --- a/src/WireMock.Net.Abstractions/Admin/Settings/ProxyAndRecordSettingsModel.cs +++ b/src/WireMock.Net.Abstractions/Admin/Settings/ProxyAndRecordSettingsModel.cs @@ -54,5 +54,14 @@ namespace WireMock.Admin.Settings /// Prefer the Proxy Mapping over the saved Mapping (in case SaveMapping is set to true). /// // public bool PreferProxyMapping { get; set; } + + /// + /// When SaveMapping is set to true, this setting can be used to control the behavior of the generated request matchers for the new mapping. + /// - false, the default matchers will be used. + /// - true, the defined mappings in the request wil be used for the new mapping. + /// + /// Default value is false. + /// + public bool UseDefinedRequestMatchers { get; set; } } } \ No newline at end of file diff --git a/src/WireMock.Net/Matchers/Request/RequestMessageCookieMatcher.cs b/src/WireMock.Net/Matchers/Request/RequestMessageCookieMatcher.cs index f4af9a44..576927de 100644 --- a/src/WireMock.Net/Matchers/Request/RequestMessageCookieMatcher.cs +++ b/src/WireMock.Net/Matchers/Request/RequestMessageCookieMatcher.cs @@ -1,8 +1,7 @@ +using Stef.Validation; using System; using System.Collections.Generic; using System.Linq; -using JetBrains.Annotations; -using Stef.Validation; namespace WireMock.Matchers.Request; @@ -13,12 +12,13 @@ namespace WireMock.Matchers.Request; public class RequestMessageCookieMatcher : IRequestMatcher { private readonly MatchBehaviour _matchBehaviour; + private readonly bool _ignoreCase; /// /// The functions /// - public Func, bool>[] Funcs { get; } + public Func, bool>[]? Funcs { get; } /// /// The name @@ -28,7 +28,7 @@ public class RequestMessageCookieMatcher : IRequestMatcher /// /// The matchers. /// - public IStringMatcher[] Matchers { get; } + public IStringMatcher[]? Matchers { get; } /// /// Initializes a new instance of the class. @@ -37,15 +37,12 @@ public class RequestMessageCookieMatcher : IRequestMatcher /// The pattern. /// Ignore the case from the pattern. /// The match behaviour. - public RequestMessageCookieMatcher(MatchBehaviour matchBehaviour, [NotNull] string name, [NotNull] string pattern, bool ignoreCase) + public RequestMessageCookieMatcher(MatchBehaviour matchBehaviour, string name, string pattern, bool ignoreCase) { - Guard.NotNull(name, nameof(name)); - Guard.NotNull(pattern, nameof(pattern)); - _matchBehaviour = matchBehaviour; _ignoreCase = ignoreCase; - Name = name; - Matchers = new IStringMatcher[] { new WildcardMatcher(matchBehaviour, pattern, ignoreCase) }; + Name = Guard.NotNull(name); + Matchers = new IStringMatcher[] { new WildcardMatcher(matchBehaviour, Guard.NotNull(pattern), ignoreCase) }; } /// @@ -55,10 +52,10 @@ public class RequestMessageCookieMatcher : IRequestMatcher /// The name. /// The patterns. /// Ignore the case from the pattern. - public RequestMessageCookieMatcher(MatchBehaviour matchBehaviour, [NotNull] string name, bool ignoreCase, [NotNull] params string[] patterns) : + public RequestMessageCookieMatcher(MatchBehaviour matchBehaviour, string name, bool ignoreCase, params string[] patterns) : this(matchBehaviour, name, ignoreCase, patterns.Select(pattern => new WildcardMatcher(matchBehaviour, pattern, ignoreCase)).Cast().ToArray()) { - Guard.NotNull(patterns, nameof(patterns)); + Guard.NotNull(patterns); } /// @@ -68,14 +65,11 @@ public class RequestMessageCookieMatcher : IRequestMatcher /// The name. /// The matchers. /// Ignore the case from the pattern. - public RequestMessageCookieMatcher(MatchBehaviour matchBehaviour, [NotNull] string name, bool ignoreCase, [NotNull] params IStringMatcher[] matchers) + public RequestMessageCookieMatcher(MatchBehaviour matchBehaviour, string name, bool ignoreCase, params IStringMatcher[] matchers) { - Guard.NotNull(name, nameof(name)); - Guard.NotNull(matchers, nameof(matchers)); - _matchBehaviour = matchBehaviour; - Name = name; - Matchers = matchers; + Name = Guard.NotNull(name); + Matchers = Guard.NotNull(matchers); _ignoreCase = ignoreCase; } @@ -83,11 +77,12 @@ public class RequestMessageCookieMatcher : IRequestMatcher /// Initializes a new instance of the class. /// /// The funcs. - public RequestMessageCookieMatcher([NotNull] params Func, bool>[] funcs) + public RequestMessageCookieMatcher(params Func, bool>[] funcs) { - Guard.NotNull(funcs, nameof(funcs)); + Guard.NotNull(funcs); Funcs = funcs; + Name = string.Empty; // Not used when Func, but set to a non-null valid value. } /// diff --git a/src/WireMock.Net/Matchers/Request/RequestMessageParamMatcher.cs b/src/WireMock.Net/Matchers/Request/RequestMessageParamMatcher.cs index dc4e34f8..8ac113d7 100644 --- a/src/WireMock.Net/Matchers/Request/RequestMessageParamMatcher.cs +++ b/src/WireMock.Net/Matchers/Request/RequestMessageParamMatcher.cs @@ -11,7 +11,10 @@ namespace WireMock.Matchers.Request; /// public class RequestMessageParamMatcher : IRequestMatcher { - private readonly MatchBehaviour _matchBehaviour; + /// + /// MatchBehaviour + /// + public MatchBehaviour MatchBehaviour { get; } /// /// The funcs @@ -63,7 +66,7 @@ public class RequestMessageParamMatcher : IRequestMatcher /// The matchers. public RequestMessageParamMatcher(MatchBehaviour matchBehaviour, string key, bool ignoreCase, IStringMatcher[]? matchers) { - _matchBehaviour = matchBehaviour; + MatchBehaviour = matchBehaviour; Key = Guard.NotNull(key); IgnoreCase = ignoreCase; Matchers = matchers; @@ -81,7 +84,7 @@ public class RequestMessageParamMatcher : IRequestMatcher /// public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult) { - double score = MatchBehaviourHelper.Convert(_matchBehaviour, IsMatch(requestMessage)); + double score = MatchBehaviourHelper.Convert(MatchBehaviour, IsMatch(requestMessage)); return requestMatchResult.AddScore(GetType(), score); } diff --git a/src/WireMock.Net/Owin/WireMockMiddleware.cs b/src/WireMock.Net/Owin/WireMockMiddleware.cs index e0e088bf..23445942 100644 --- a/src/WireMock.Net/Owin/WireMockMiddleware.cs +++ b/src/WireMock.Net/Owin/WireMockMiddleware.cs @@ -161,7 +161,6 @@ namespace WireMock.Owin _options.Logger.Error($"Providing a Response for Mapping '{result.Match?.Mapping?.Guid}' failed. HttpStatusCode set to 500. Exception: {ex}"); response = ResponseMessageBuilder.Create(ex.Message, 500); } - finally { var log = new LogEntry diff --git a/src/WireMock.Net/Proxy/ProxyHelper.cs b/src/WireMock.Net/Proxy/ProxyHelper.cs index 600d628a..6d6178a4 100644 --- a/src/WireMock.Net/Proxy/ProxyHelper.cs +++ b/src/WireMock.Net/Proxy/ProxyHelper.cs @@ -1,16 +1,10 @@ +using Stef.Validation; using System; -using System.Collections.Generic; -using System.Linq; using System.Net.Http; using System.Threading.Tasks; -using Stef.Validation; -using WireMock.Constants; using WireMock.Http; -using WireMock.Matchers; -using WireMock.RequestBuilders; -using WireMock.ResponseBuilders; +using WireMock.Serialization; using WireMock.Settings; -using WireMock.Types; using WireMock.Util; namespace WireMock.Proxy; @@ -18,13 +12,16 @@ namespace WireMock.Proxy; internal class ProxyHelper { private readonly WireMockServerSettings _settings; + private readonly ProxyMappingConverter _proxyMappingConverter; public ProxyHelper(WireMockServerSettings settings) { _settings = Guard.NotNull(settings); + _proxyMappingConverter = new ProxyMappingConverter(settings, new GuidUtils()); } public async Task<(IResponseMessage Message, IMapping? Mapping)> SendAsync( + IMapping? mapping, ProxyAndRecordSettings proxyAndRecordSettings, HttpClient client, IRequestMessage requestMessage, @@ -49,78 +46,13 @@ internal class ProxyHelper var responseMessage = await HttpResponseMessageHelper.CreateAsync(httpResponseMessage, requiredUri, originalUri, deserializeJson, decompressGzipAndDeflate).ConfigureAwait(false); - IMapping? mapping = null; + IMapping? newMapping = null; if (HttpStatusRangeParser.IsMatch(proxyAndRecordSettings.SaveMappingForStatusCodePattern, responseMessage.StatusCode) && (proxyAndRecordSettings.SaveMapping || proxyAndRecordSettings.SaveMappingToFile)) { - mapping = ToMapping(proxyAndRecordSettings, requestMessage, responseMessage); + newMapping = _proxyMappingConverter.ToMapping(mapping, proxyAndRecordSettings, requestMessage, responseMessage); } - return (responseMessage, mapping); - } - - private IMapping ToMapping(ProxyAndRecordSettings proxyAndRecordSettings, IRequestMessage requestMessage, ResponseMessage responseMessage) - { - var excludedHeaders = proxyAndRecordSettings.ExcludedHeaders ?? new string[] { }; - var excludedCookies = proxyAndRecordSettings.ExcludedCookies ?? new string[] { }; - - var request = Request.Create(); - request.WithPath(requestMessage.Path); - request.UsingMethod(requestMessage.Method); - - requestMessage.Query?.Loop((key, value) => request.WithParam(key, false, value.ToArray())); - requestMessage.Cookies?.Loop((key, value) => - { - if (!excludedCookies.Contains(key, StringComparer.OrdinalIgnoreCase)) - { - request.WithCookie(key, value); - } - }); - - var allExcludedHeaders = new List(excludedHeaders) { "Cookie" }; - requestMessage.Headers?.Loop((key, value) => - { - if (!allExcludedHeaders.Contains(key, StringComparer.OrdinalIgnoreCase)) - { - request.WithHeader(key, value.ToArray()); - } - }); - - bool throwExceptionWhenMatcherFails = _settings.ThrowExceptionWhenMatcherFails == true; - switch (requestMessage.BodyData?.DetectedBodyType) - { - case BodyType.Json: - request.WithBody(new JsonMatcher(MatchBehaviour.AcceptOnMatch, requestMessage.BodyData.BodyAsJson!, true, throwExceptionWhenMatcherFails)); - break; - - case BodyType.String: - request.WithBody(new ExactMatcher(MatchBehaviour.AcceptOnMatch, throwExceptionWhenMatcherFails, MatchOperator.Or, requestMessage.BodyData.BodyAsString)); - break; - - case BodyType.Bytes: - request.WithBody(new ExactObjectMatcher(MatchBehaviour.AcceptOnMatch, requestMessage.BodyData.BodyAsBytes, throwExceptionWhenMatcherFails)); - break; - } - - var response = Response.Create(responseMessage); - - 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, // This was 0 - scenario: null, - executionConditionState: null, - nextState: null, - stateTimes: null, - webhooks: null, - useWebhooksFireAndForget: null, - timeSettings: null - ); + return (responseMessage, newMapping); } } \ No newline at end of file diff --git a/src/WireMock.Net/RequestBuilders/Request.WithParam.cs b/src/WireMock.Net/RequestBuilders/Request.WithParam.cs index 0b300f22..06a925e6 100644 --- a/src/WireMock.Net/RequestBuilders/Request.WithParam.cs +++ b/src/WireMock.Net/RequestBuilders/Request.WithParam.cs @@ -20,7 +20,7 @@ namespace WireMock.RequestBuilders /// public IRequestBuilder WithParam(string key, bool ignoreCase, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch) { - Guard.NotNull(key, nameof(key)); + Guard.NotNull(key); _requestMatchers.Add(new RequestMessageParamMatcher(matchBehaviour, key, ignoreCase)); return this; diff --git a/src/WireMock.Net/ResponseBuilders/Response.cs b/src/WireMock.Net/ResponseBuilders/Response.cs index 194e1e69..536feb87 100644 --- a/src/WireMock.Net/ResponseBuilders/Response.cs +++ b/src/WireMock.Net/ResponseBuilders/Response.cs @@ -271,6 +271,7 @@ public partial class Response : IResponseBuilder var proxyHelper = new ProxyHelper(settings); return await proxyHelper.SendAsync( + mapping, ProxyAndRecordSettings, _httpClientForProxy, requestMessage, diff --git a/src/WireMock.Net/Serialization/MappingConverter.cs b/src/WireMock.Net/Serialization/MappingConverter.cs index 96dcf870..5f2460a6 100644 --- a/src/WireMock.Net/Serialization/MappingConverter.cs +++ b/src/WireMock.Net/Serialization/MappingConverter.cs @@ -20,7 +20,7 @@ internal class MappingConverter public MappingConverter(MatcherMapper mapper) { - _mapper = Guard.NotNull(mapper, nameof(mapper)); + _mapper = Guard.NotNull(mapper); } public MappingModel ToMappingModel(IMapping mapping) diff --git a/src/WireMock.Net/Serialization/ProxyMappingConverter.cs b/src/WireMock.Net/Serialization/ProxyMappingConverter.cs new file mode 100644 index 00000000..67446ad5 --- /dev/null +++ b/src/WireMock.Net/Serialization/ProxyMappingConverter.cs @@ -0,0 +1,181 @@ +using Stef.Validation; +using System; +using System.Collections.Generic; +using System.Linq; +using WireMock.Constants; +using WireMock.Matchers; +using WireMock.Matchers.Request; +using WireMock.RequestBuilders; +using WireMock.ResponseBuilders; +using WireMock.Settings; +using WireMock.Types; +using WireMock.Util; + +namespace WireMock.Serialization; + +internal class ProxyMappingConverter +{ + private readonly WireMockServerSettings _settings; + private readonly IGuidUtils _guidUtils; + + public ProxyMappingConverter(WireMockServerSettings settings, IGuidUtils guidUtils) + { + _settings = Guard.NotNull(settings); + _guidUtils = Guard.NotNull(guidUtils); + } + + public IMapping ToMapping(IMapping? mapping, ProxyAndRecordSettings proxyAndRecordSettings, IRequestMessage requestMessage, ResponseMessage responseMessage) + { + var request = (Request?)mapping?.RequestMatcher; + var clientIPMatcher = request?.GetRequestMessageMatcher(); + var pathMatcher = request?.GetRequestMessageMatcher(); + var headerMatchers = request?.GetRequestMessageMatchers(); + var cookieMatchers = request?.GetRequestMessageMatchers(); + var paramMatchers = request?.GetRequestMessageMatchers(); + var methodMatcher = request?.GetRequestMessageMatcher(); + var bodyMatcher = request?.GetRequestMessageMatcher(); + + var useDefinedRequestMatchers = proxyAndRecordSettings.UseDefinedRequestMatchers; + + var excludedHeaders = new List(proxyAndRecordSettings.ExcludedHeaders ?? new string[] { }) { "Cookie" }; + var excludedCookies = proxyAndRecordSettings.ExcludedCookies ?? new string[] { }; + + var newRequest = Request.Create(); + + // ClientIP + if (useDefinedRequestMatchers && clientIPMatcher?.Matchers is not null) + { + newRequest.WithClientIP(clientIPMatcher.MatchOperator, clientIPMatcher.Matchers.ToArray()); + } + + // Path + if (useDefinedRequestMatchers && pathMatcher?.Matchers is not null) + { + newRequest.WithPath(pathMatcher.MatchOperator, pathMatcher.Matchers.ToArray()); + } + else + { + newRequest.WithPath(requestMessage.Path); + } + + // Method + if (useDefinedRequestMatchers && methodMatcher is not null) + { + newRequest.UsingMethod(methodMatcher.Methods); + } + else + { + newRequest.UsingMethod(requestMessage.Method); + } + + // QueryParams + if (useDefinedRequestMatchers && paramMatchers is not null) + { + foreach (var paramMatcher in paramMatchers) + { + newRequest.WithParam(paramMatcher.Key, paramMatcher.MatchBehaviour, paramMatcher.Matchers!.ToArray()); + } + } + else + { + requestMessage.Query?.Loop((key, value) => newRequest.WithParam(key, false, value.ToArray())); + } + + // Cookies + if (useDefinedRequestMatchers && cookieMatchers is not null) + { + foreach (var cookieMatcher in cookieMatchers.Where(hm => hm.Matchers is not null)) + { + if (!excludedCookies.Contains(cookieMatcher.Name, StringComparer.OrdinalIgnoreCase)) + { + newRequest.WithCookie(cookieMatcher.Name, cookieMatcher.Matchers!); + } + } + } + else + { + requestMessage.Cookies?.Loop((key, value) => + { + if (!excludedCookies.Contains(key, StringComparer.OrdinalIgnoreCase)) + { + newRequest.WithCookie(key, value); + } + }); + } + + // Headers + if (useDefinedRequestMatchers && headerMatchers is not null) + { + foreach (var headerMatcher in headerMatchers.Where(hm => hm.Matchers is not null)) + { + if (!excludedHeaders.Contains(headerMatcher.Name, StringComparer.OrdinalIgnoreCase)) + { + newRequest.WithHeader(headerMatcher.Name, headerMatcher.Matchers!); + } + } + } + else + { + requestMessage.Headers?.Loop((key, value) => + { + if (!excludedHeaders.Contains(key, StringComparer.OrdinalIgnoreCase)) + { + newRequest.WithHeader(key, value.ToArray()); + } + }); + } + + // Body + bool throwExceptionWhenMatcherFails = _settings.ThrowExceptionWhenMatcherFails == true; + if (useDefinedRequestMatchers && bodyMatcher?.Matchers is not null) + { + newRequest.WithBody(bodyMatcher.Matchers); + } + else + { + switch (requestMessage.BodyData?.DetectedBodyType) + { + case BodyType.Json: + newRequest.WithBody(new JsonMatcher(MatchBehaviour.AcceptOnMatch, requestMessage.BodyData.BodyAsJson!, true, throwExceptionWhenMatcherFails)); + break; + + case BodyType.String: + newRequest.WithBody(new ExactMatcher(MatchBehaviour.AcceptOnMatch, throwExceptionWhenMatcherFails, MatchOperator.Or, requestMessage.BodyData.BodyAsString)); + break; + + case BodyType.Bytes: + newRequest.WithBody(new ExactObjectMatcher(MatchBehaviour.AcceptOnMatch, requestMessage.BodyData.BodyAsBytes, throwExceptionWhenMatcherFails)); + break; + } + } + + // Title + var title = useDefinedRequestMatchers && !string.IsNullOrEmpty(mapping?.Title) ? + mapping!.Title : + $"Proxy Mapping for {requestMessage.Method} {requestMessage.Path}"; + + // Description + var description = useDefinedRequestMatchers && !string.IsNullOrEmpty(mapping?.Description) ? + mapping!.Description : + $"Proxy Mapping for {requestMessage.Method} {requestMessage.Path}"; + + return new Mapping + ( + guid: _guidUtils.NewGuid(), + title: title, + description: description, + path: null, + settings: _settings, + requestMatcher: newRequest, + provider: Response.Create(responseMessage), + priority: WireMockConstants.ProxyPriority, // This was 0 + scenario: null, + executionConditionState: null, + nextState: null, + stateTimes: null, + webhooks: null, + useWebhooksFireAndForget: null, + timeSettings: 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 52a58d3f..2609a554 100644 --- a/src/WireMock.Net/Server/WireMockServer.Admin.cs +++ b/src/WireMock.Net/Server/WireMockServer.Admin.cs @@ -236,6 +236,7 @@ public partial class WireMockServer var proxyHelper = new ProxyHelper(settings); var (responseMessage, mapping) = await proxyHelper.SendAsync( + null, _settings.ProxyAndRecordSettings!, _httpClientForProxy!, requestMessage, diff --git a/src/WireMock.Net/Server/WireMockServer.ConvertMapping.cs b/src/WireMock.Net/Server/WireMockServer.ConvertMapping.cs index 3c732ff6..1368b06d 100644 --- a/src/WireMock.Net/Server/WireMockServer.ConvertMapping.cs +++ b/src/WireMock.Net/Server/WireMockServer.ConvertMapping.cs @@ -198,7 +198,7 @@ public partial class WireMockServer { foreach (var cookieModel in requestModel.Cookies.Where(c => c.Matchers != null)) { - requestBuilder = requestBuilder.WithCookie( + requestBuilder = requestBuilder.WithCookie( cookieModel.Name, cookieModel.IgnoreCase == true, cookieModel.RejectOnMatch == true ? MatchBehaviour.RejectOnMatch : MatchBehaviour.AcceptOnMatch, diff --git a/src/WireMock.Net/Settings/ProxyAndRecordSettings.cs b/src/WireMock.Net/Settings/ProxyAndRecordSettings.cs index 851eba80..3ae30763 100644 --- a/src/WireMock.Net/Settings/ProxyAndRecordSettings.cs +++ b/src/WireMock.Net/Settings/ProxyAndRecordSettings.cs @@ -49,4 +49,13 @@ public class ProxyAndRecordSettings : HttpClientSettings /// //[PublicAPI] //public bool PreferProxyMapping { get; set; } + + /// + /// When SaveMapping is set to true, this setting can be used to control the behavior of the generated request matchers for the new mapping. + /// - false, the default matchers will be used. + /// - true, the defined mappings in the request wil be used for the new mapping. + /// + /// Default value is false. + /// + public bool UseDefinedRequestMatchers { get; set; } } \ No newline at end of file diff --git a/src/WireMock.Net/Settings/WireMockServerSettingsParser.cs b/src/WireMock.Net/Settings/WireMockServerSettingsParser.cs index 2caca070..32fecedb 100644 --- a/src/WireMock.Net/Settings/WireMockServerSettingsParser.cs +++ b/src/WireMock.Net/Settings/WireMockServerSettingsParser.cs @@ -92,6 +92,7 @@ public static class WireMockServerSettingsParser SaveMapping = parser.GetBoolValue("SaveMapping"), SaveMappingForStatusCodePattern = parser.GetStringValue("SaveMappingForStatusCodePattern", "*"), SaveMappingToFile = parser.GetBoolValue("SaveMappingToFile"), + UseDefinedRequestMatchers = parser.GetBoolValue(nameof(ProxyAndRecordSettings.UseDefinedRequestMatchers)), Url = proxyUrl! }; diff --git a/src/WireMock.Net/Util/GuidUtils.cs b/src/WireMock.Net/Util/GuidUtils.cs new file mode 100644 index 00000000..53892407 --- /dev/null +++ b/src/WireMock.Net/Util/GuidUtils.cs @@ -0,0 +1,16 @@ +using System; + +namespace WireMock.Util; + +internal interface IGuidUtils +{ + Guid NewGuid(); +} + +internal class GuidUtils : IGuidUtils +{ + public Guid NewGuid() + { + return Guid.NewGuid(); + } +} \ No newline at end of file diff --git a/test/WireMock.Net.Tests/Serialization/ProxyMappingConverterTests.cs b/test/WireMock.Net.Tests/Serialization/ProxyMappingConverterTests.cs new file mode 100644 index 00000000..228d63b3 --- /dev/null +++ b/test/WireMock.Net.Tests/Serialization/ProxyMappingConverterTests.cs @@ -0,0 +1,70 @@ +using System; +using Moq; +using Newtonsoft.Json; +using System.IO; +using FluentAssertions; +using WireMock.Matchers; +using WireMock.RequestBuilders; +using WireMock.Serialization; +using WireMock.Settings; +using WireMock.Util; +using Xunit; + +namespace WireMock.Net.Tests.Serialization; + +public class ProxyMappingConverterTests +{ + private readonly WireMockServerSettings _settings = new(); + + private readonly MappingConverter _mappingConverter; + + private readonly ProxyMappingConverter _sut; + + public ProxyMappingConverterTests() + { + var guidUtilsMock = new Mock(); + guidUtilsMock.Setup(g => g.NewGuid()).Returns(Guid.Parse("ff55ac0a-fea9-4d7b-be74-5e483a2c1305")); + + _mappingConverter = new MappingConverter(new MatcherMapper(_settings)); + + _sut = new ProxyMappingConverter(_settings, guidUtilsMock.Object); + } + + [Fact] + public void ToMapping_UseDefinedRequestMatchers_True() + { + // Arrange + var proxyAndRecordSettings = new ProxyAndRecordSettings + { + UseDefinedRequestMatchers = true + }; + + var request = Request.Create() + .UsingPost() + .WithPath("x") + .WithParam("p1", "p1-v") + .WithParam("p2", "p2-v") + .WithHeader("Content-Type", new ContentTypeMatcher("text/plain")) + .WithCookie("c", "x") + .WithBody(new RegexMatcher("Auth(); + mappingMock.SetupGet(m => m.RequestMatcher).Returns(request); + mappingMock.SetupGet(m => m.Title).Returns("my title"); + mappingMock.SetupGet(m => m.Description).Returns("my description"); + + var requestMessageMock = new Mock(); + + var responseMessage = new ResponseMessage(); + + // Act + var proxyMapping = _sut.ToMapping(mappingMock.Object, proxyAndRecordSettings, requestMessageMock.Object, responseMessage); + + // Assert + var model = _mappingConverter.ToMappingModel(proxyMapping); + var json = JsonConvert.SerializeObject(model, JsonSerializationConstants.JsonSerializerSettingsDefault); + var expected = File.ReadAllText(Path.Combine("../../../", "Serialization", "files", "proxy.json")); + + json.Should().Be(expected); + } +} \ No newline at end of file diff --git a/test/WireMock.Net.Tests/Serialization/files/proxy.json b/test/WireMock.Net.Tests/Serialization/files/proxy.json new file mode 100644 index 00000000..a045bc7a --- /dev/null +++ b/test/WireMock.Net.Tests/Serialization/files/proxy.json @@ -0,0 +1,72 @@ +{ + "Guid": "ff55ac0a-fea9-4d7b-be74-5e483a2c1305", + "Title": "my title", + "Description": "my description", + "Priority": -2000000, + "Request": { + "Path": { + "Matchers": [ + { + "Name": "WildcardMatcher", + "Pattern": "x", + "IgnoreCase": false + } + ] + }, + "Methods": [ + "POST" + ], + "Headers": [ + { + "Name": "Content-Type", + "Matchers": [ + { + "Name": "ContentTypeMatcher", + "Pattern": "text/plain", + "IgnoreCase": false + } + ] + } + ], + "Cookies": [ + { + "Name": "c", + "Matchers": [ + { + "Name": "WildcardMatcher", + "Pattern": "x", + "IgnoreCase": true + } + ] + } + ], + "Params": [ + { + "Name": "p1", + "Matchers": [ + { + "Name": "ExactMatcher", + "Pattern": "p1-v" + } + ] + }, + { + "Name": "p2", + "Matchers": [ + { + "Name": "ExactMatcher", + "Pattern": "p2-v" + } + ] + } + ], + "Body": { + "Matcher": { + "Name": "RegexMatcher", + "Pattern": "Auth PreserveNewest + + PreserveNewest + PreserveNewest @@ -105,6 +108,7 @@ + \ 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 def80c60..aa2a03a7 100644 --- a/test/WireMock.Net.Tests/WireMockServer.Proxy.cs +++ b/test/WireMock.Net.Tests/WireMockServer.Proxy.cs @@ -1,3 +1,6 @@ +using FluentAssertions; +using Moq; +using NFluent; using System; using System.Linq; using System.Net; @@ -5,11 +8,9 @@ using System.Net.Http; using System.Net.Http.Headers; using System.Text; using System.Threading.Tasks; -using FluentAssertions; -using Moq; -using NFluent; -using WireMock.Admin.Mappings; +using WireMock.Constants; using WireMock.Handlers; +using WireMock.Matchers; using WireMock.Matchers.Request; using WireMock.RequestBuilders; using WireMock.ResponseBuilders; @@ -119,6 +120,61 @@ public class WireMockServerProxyTests server.Mappings.Should().HaveCount(28); } + [Fact] + public async Task WireMockServer_Proxy_With_SaveMappingToFile_Is_True_ShouldSaveMappingToFile() + { + // Assign + string path = $"/prx_{Guid.NewGuid()}"; + var title = "IndexFile"; + var description = "IndexFile_Test"; + var stringBody = "value"; + var serverForProxyForwarding = WireMockServer.Start(); + var fileSystemHandlerMock = new Mock(); + fileSystemHandlerMock.Setup(f => f.GetMappingFolder()).Returns("m"); + + var settings = new WireMockServerSettings + { + ProxyAndRecordSettings = new ProxyAndRecordSettings + { + Url = serverForProxyForwarding.Urls[0], + SaveMapping = false, + SaveMappingToFile = true + }, + FileSystemHandler = fileSystemHandlerMock.Object + }; + + var server = WireMockServer.Start(settings); + server.Given(Request.Create() + .WithPath("/*") + .WithBody(new RegexMatcher(stringBody))) + .WithTitle(title) + .WithDescription(description) + .AtPriority(WireMockConstants.ProxyPriority) + .RespondWith(Response.Create().WithProxy(new ProxyAndRecordSettings + { + Url = serverForProxyForwarding.Urls[0], + SaveMapping = false, + SaveMappingToFile = true, + UseDefinedRequestMatchers = true, + })); + + // Act + var requestMessage = new HttpRequestMessage + { + Method = HttpMethod.Post, + RequestUri = new Uri($"{server.Urls[0]}{path}"), + Content = new StringContent(stringBody) + }; + var httpClientHandler = new HttpClientHandler { AllowAutoRedirect = false }; + await new HttpClient(httpClientHandler).SendAsync(requestMessage).ConfigureAwait(false); + + // Assert + server.Mappings.Should().HaveCount(2); + + // Verify + fileSystemHandlerMock.Verify(f => f.WriteMappingFile($"m{System.IO.Path.DirectorySeparatorChar}{title}.json", It.IsRegex(stringBody)), Times.Once); + } + [Fact] public async Task WireMockServer_Proxy_With_SaveMapping_Is_False_And_SaveMappingToFile_Is_True_ShouldSaveMappingToFile() { @@ -735,8 +791,6 @@ public class WireMockServerProxyTests content.Should().NotBeEmpty(); server.LogEntries.Should().HaveCount(1); - var status = ((StatusModel)server.LogEntries.First().ResponseMessage.BodyData.BodyAsJson).Status; - server.Stop(); } } \ No newline at end of file