From b9a8ee4145264a3a72cdd46e4baad157968eec5a Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Thu, 21 Sep 2023 11:38:01 +0200 Subject: [PATCH] Fixed logic for SaveUnmatchedRequests (#1002) * Fixed logic for SaveUnmatchedRequests * fix --- .../WireMock.Net.Console.NET6.csproj | 13 ----- .../__admin/mappings/issue-1001.json | 48 +++++++++++++++++ .../MainApp.cs | 1 + src/WireMock.Net/Owin/AspNetCoreSelfHost.cs | 1 + src/WireMock.Net/Owin/OwinSelfHost.cs | 8 +-- src/WireMock.Net/Owin/WireMockMiddleware.cs | 28 ++++++++-- src/WireMock.Net/RequestMessage.cs | 18 ++----- src/WireMock.Net/Util/MimeKitUtils.cs | 10 +++- .../Owin/WireMockMiddlewareTests.cs | 53 +++++++++++++++---- 9 files changed, 132 insertions(+), 48 deletions(-) create mode 100644 examples/WireMock.Net.Console.NET6/__admin/mappings/issue-1001.json diff --git a/examples/WireMock.Net.Console.NET6/WireMock.Net.Console.NET6.csproj b/examples/WireMock.Net.Console.NET6/WireMock.Net.Console.NET6.csproj index 8834be81..c6a5a69c 100644 --- a/examples/WireMock.Net.Console.NET6/WireMock.Net.Console.NET6.csproj +++ b/examples/WireMock.Net.Console.NET6/WireMock.Net.Console.NET6.csproj @@ -22,10 +22,6 @@ - - - - @@ -39,15 +35,6 @@ PreserveNewest - - PreserveNewest - - - Never - - - PreserveNewest - \ No newline at end of file diff --git a/examples/WireMock.Net.Console.NET6/__admin/mappings/issue-1001.json b/examples/WireMock.Net.Console.NET6/__admin/mappings/issue-1001.json new file mode 100644 index 00000000..99242bd5 --- /dev/null +++ b/examples/WireMock.Net.Console.NET6/__admin/mappings/issue-1001.json @@ -0,0 +1,48 @@ +[ + { + "Guid": "12343f31-6946-4ce7-8e6f-0237c7001000", + "Title": "1", + "Request": { + "Path": { + "Matchers": [ + { + "Name": "WildcardMatcher", + "Pattern": "/mappings_static_1" + } + ] + }, + "Methods": [ + "get" + ] + }, + "Response": { + "BodyAsJson": { "result": "mappings static_1" }, + "Headers": { + "Content-Type": "application/json" + } + } + }, + { + "Guid": "12343f31-6946-4ce7-8e6f-0237c7002000", + "Title": "2", + "Request": { + "Path": { + "Matchers": [ + { + "Name": "WildcardMatcher", + "Pattern": "/mappings_static_2" + } + ] + }, + "Methods": [ + "get" + ] + }, + "Response": { + "BodyAsJson": { "result": "mappings static_2" }, + "Headers": { + "Content-Type": "application/json" + } + } + } +] \ No newline at end of file diff --git a/examples/WireMock.Net.Console.Net452.Classic/MainApp.cs b/examples/WireMock.Net.Console.Net452.Classic/MainApp.cs index 71aeb551..0b721808 100644 --- a/examples/WireMock.Net.Console.Net452.Classic/MainApp.cs +++ b/examples/WireMock.Net.Console.Net452.Classic/MainApp.cs @@ -137,6 +137,7 @@ namespace WireMock.Net.ConsoleApplication Urls = new[] { url1, url2, url3 }, StartAdminInterface = true, ReadStaticMappings = true, + SaveUnmatchedRequests = true, WatchStaticMappings = true, WatchStaticMappingsInSubdirectories = true, //ProxyAndRecordSettings = new ProxyAndRecordSettings diff --git a/src/WireMock.Net/Owin/AspNetCoreSelfHost.cs b/src/WireMock.Net/Owin/AspNetCoreSelfHost.cs index 60fefacd..ea63b821 100644 --- a/src/WireMock.Net/Owin/AspNetCoreSelfHost.cs +++ b/src/WireMock.Net/Owin/AspNetCoreSelfHost.cs @@ -70,6 +70,7 @@ namespace WireMock.Owin services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); #if NETCOREAPP3_1 || NET5_0 || NET6_0 || NET7_0 AddCors(services); diff --git a/src/WireMock.Net/Owin/OwinSelfHost.cs b/src/WireMock.Net/Owin/OwinSelfHost.cs index 7f4dbd3e..f8065f69 100644 --- a/src/WireMock.Net/Owin/OwinSelfHost.cs +++ b/src/WireMock.Net/Owin/OwinSelfHost.cs @@ -9,9 +9,8 @@ using JetBrains.Annotations; using WireMock.Logging; using WireMock.Owin.Mappers; using Stef.Validation; -using RandomDataGenerator.FieldOptions; -using RandomDataGenerator.Randomizers; using WireMock.Services; +using WireMock.Util; namespace WireMock.Owin; @@ -63,7 +62,7 @@ internal class OwinSelfHost : IOwinSelfHost private void StartServers() { #if NET46 - _logger.Info("Server using .net 4.6.1 or higher"); + _logger.Info("Server using .net 4.6"); #else _logger.Info("Server using .net 4.5.x"); #endif @@ -74,12 +73,13 @@ internal class OwinSelfHost : IOwinSelfHost var requestMapper = new OwinRequestMapper(); var responseMapper = new OwinResponseMapper(_options); var matcher = new MappingMatcher(_options, new RandomizerDoubleBetween0And1()); + var guidUtils = new GuidUtils(); Action startup = app => { app.Use(_options, responseMapper); _options.PreWireMockMiddlewareInit?.Invoke(app); - app.Use(_options, requestMapper, responseMapper, matcher); + app.Use(_options, requestMapper, responseMapper, matcher, guidUtils); _options.PostWireMockMiddlewareInit?.Invoke(app); }; diff --git a/src/WireMock.Net/Owin/WireMockMiddleware.cs b/src/WireMock.Net/Owin/WireMockMiddleware.cs index 3a438d9f..d183aa0b 100644 --- a/src/WireMock.Net/Owin/WireMockMiddleware.cs +++ b/src/WireMock.Net/Owin/WireMockMiddleware.cs @@ -30,29 +30,47 @@ namespace WireMock.Owin { private readonly object _lock = new(); private static readonly Task CompletedTask = Task.FromResult(false); + private readonly IWireMockMiddlewareOptions _options; private readonly IOwinRequestMapper _requestMapper; private readonly IOwinResponseMapper _responseMapper; private readonly IMappingMatcher _mappingMatcher; private readonly LogEntryMapper _logEntryMapper; + private readonly IGuidUtils _guidUtils; #if !USE_ASPNETCORE - public WireMockMiddleware(Next next, IWireMockMiddlewareOptions options, IOwinRequestMapper requestMapper, IOwinResponseMapper responseMapper, IMappingMatcher mappingMatcher) : base(next) + public WireMockMiddleware( + Next next, + IWireMockMiddlewareOptions options, + IOwinRequestMapper requestMapper, + IOwinResponseMapper responseMapper, + IMappingMatcher mappingMatcher, + IGuidUtils guidUtils + ) : base(next) { _options = Guard.NotNull(options); _requestMapper = Guard.NotNull(requestMapper); _responseMapper = Guard.NotNull(responseMapper); _mappingMatcher = Guard.NotNull(mappingMatcher); _logEntryMapper = new LogEntryMapper(options); + _guidUtils = Guard.NotNull(guidUtils); } #else - public WireMockMiddleware(Next next, IWireMockMiddlewareOptions options, IOwinRequestMapper requestMapper, IOwinResponseMapper responseMapper, IMappingMatcher mappingMatcher) + public WireMockMiddleware( + Next next, + IWireMockMiddlewareOptions options, + IOwinRequestMapper requestMapper, + IOwinResponseMapper responseMapper, + IMappingMatcher mappingMatcher, + IGuidUtils guidUtils + ) { _options = Guard.NotNull(options); _requestMapper = Guard.NotNull(requestMapper); _responseMapper = Guard.NotNull(responseMapper); _mappingMatcher = Guard.NotNull(mappingMatcher); _logEntryMapper = new LogEntryMapper(options); + _guidUtils = Guard.NotNull(guidUtils); } #endif @@ -170,7 +188,7 @@ namespace WireMock.Owin { var log = new LogEntry { - Guid = Guid.NewGuid(), + Guid = _guidUtils.NewGuid(), RequestMessage = request, ResponseMessage = response, @@ -187,10 +205,10 @@ namespace WireMock.Owin try { - if (_options.SaveUnmatchedRequests == true && result.Match?.RequestMatchResult.IsPerfectMatch != true) + if (_options.SaveUnmatchedRequests == true && result.Match?.RequestMatchResult is not { IsPerfectMatch: true }) { var filename = $"{log.Guid}.LogEntry.json"; - _options.FileSystemHandler?.WriteUnmatchedRequest(filename, Util.JsonUtils.Serialize(log)); + _options.FileSystemHandler?.WriteUnmatchedRequest(filename, JsonUtils.Serialize(log)); } } catch diff --git a/src/WireMock.Net/RequestMessage.cs b/src/WireMock.Net/RequestMessage.cs index ab1a6c0e..087fb592 100644 --- a/src/WireMock.Net/RequestMessage.cs +++ b/src/WireMock.Net/RequestMessage.cs @@ -4,11 +4,11 @@ using System; using System.Collections.Generic; using System.Linq; using System.Net; +using Newtonsoft.Json; #if USE_ASPNETCORE using System.Security.Cryptography.X509Certificates; #endif using Stef.Validation; -using WireMock.Http; using WireMock.Models; using WireMock.Owin; using WireMock.Types; @@ -80,6 +80,7 @@ public class RequestMessage : IRequestMessage #if MIMEKIT /// + [JsonIgnore] // Issue 1001 public object? BodyAsMimeMessage { get; } #endif @@ -112,7 +113,7 @@ public class RequestMessage : IRequestMessage /// /// Used for Unit Testing /// - public RequestMessage( + internal RequestMessage( UrlDetails urlDetails, string method, string clientIP, @@ -122,19 +123,6 @@ public class RequestMessage : IRequestMessage { } - /// - /// Initializes a new instance of the class. - /// - /// The. - /// The original url details. - /// The HTTP method. - /// The client IP Address. - /// The BodyData. - /// The headers. - /// The cookies. -#if USE_ASPNETCORE - /// The client certificate -#endif internal RequestMessage( IWireMockMiddlewareOptions? options, UrlDetails urlDetails, string method, diff --git a/src/WireMock.Net/Util/MimeKitUtils.cs b/src/WireMock.Net/Util/MimeKitUtils.cs index 37eac472..9bb5126e 100644 --- a/src/WireMock.Net/Util/MimeKitUtils.cs +++ b/src/WireMock.Net/Util/MimeKitUtils.cs @@ -17,7 +17,10 @@ internal static class MimeKitUtils { Guard.NotNull(requestMessage); - if (requestMessage.BodyData != null && requestMessage.Headers?.TryGetValue(HttpKnownHeaderNames.ContentType, out var contentTypeHeader) == true && contentTypeHeader.Any()) + if (requestMessage.BodyData != null && + requestMessage.Headers?.TryGetValue(HttpKnownHeaderNames.ContentType, out var contentTypeHeader) == true && + StartsWithMultiPart(contentTypeHeader) // Only parse when "multipart/mixed" + ) { var bytes = requestMessage.BodyData?.DetectedBodyType switch { @@ -40,6 +43,11 @@ internal static class MimeKitUtils return false; } + private static bool StartsWithMultiPart(WireMockList contentTypeHeader) + { + return contentTypeHeader.Any(ct => ct.TrimStart().StartsWith("multipart/", StringComparison.OrdinalIgnoreCase)); + } + private static byte[] FixBytes(byte[] bytes, WireMockList contentType) { var contentTypeBytes = Encoding.UTF8.GetBytes($"{HttpKnownHeaderNames.ContentType}: {contentType}\r\n\r\n"); diff --git a/test/WireMock.Net.Tests/Owin/WireMockMiddlewareTests.cs b/test/WireMock.Net.Tests/Owin/WireMockMiddlewareTests.cs index d42e1eaa..7bd2913f 100644 --- a/test/WireMock.Net.Tests/Owin/WireMockMiddlewareTests.cs +++ b/test/WireMock.Net.Tests/Owin/WireMockMiddlewareTests.cs @@ -34,9 +34,10 @@ namespace WireMock.Net.Tests.Owin; public class WireMockMiddlewareTests { - private readonly DateTime _updatedAt = new(2022, 12, 4); - private readonly ConcurrentDictionary _mappings = new(); + private static readonly Guid NewGuid = new("98fae52e-76df-47d9-876f-2ee32e931d9b"); + private static readonly DateTime UpdatedAt = new(2022, 12, 4); + private readonly ConcurrentDictionary _mappings = new(); private readonly Mock _optionsMock; private readonly Mock _requestMapperMock; private readonly Mock _responseMapperMock; @@ -48,6 +49,9 @@ public class WireMockMiddlewareTests public WireMockMiddlewareTests() { + var guidUtilsMock = new Mock(); + guidUtilsMock.Setup(g => g.NewGuid()).Returns(NewGuid); + _optionsMock = new Mock(); _optionsMock.SetupAllProperties(); _optionsMock.Setup(o => o.Mappings).Returns(_mappings); @@ -74,7 +78,14 @@ public class WireMockMiddlewareTests _mappingMock = new Mock(); - _sut = new WireMockMiddleware(null, _optionsMock.Object, _requestMapperMock.Object, _responseMapperMock.Object, _matcherMock.Object); + _sut = new WireMockMiddleware( + null, + _optionsMock.Object, + _requestMapperMock.Object, + _responseMapperMock.Object, + _matcherMock.Object, + guidUtilsMock.Object + ); } [Fact] @@ -86,10 +97,32 @@ public class WireMockMiddlewareTests // Assert and Verify _optionsMock.Verify(o => o.Logger.Warn(It.IsAny(), It.IsAny()), Times.Once); - Expression> match = r => (int)r.StatusCode == 404 && ((StatusModel)r.BodyData.BodyAsJson).Status == "No matching mapping found"; + Expression> match = r => (int)r.StatusCode! == 404 && ((StatusModel)r.BodyData!.BodyAsJson!).Status == "No matching mapping found"; _responseMapperMock.Verify(m => m.MapAsync(It.Is(match), It.IsAny()), Times.Once); } + [Fact] + public async Task WireMockMiddleware_Invoke_NoMatch_When_SaveUnmatchedRequestsIsTrue_Should_Call_LocalFileSystemHandler_WriteUnmatchedRequest() + { + // Arrange + var fileSystemHandlerMock = new Mock(); + _optionsMock.Setup(o => o.FileSystemHandler).Returns(fileSystemHandlerMock.Object); + _optionsMock.Setup(o => o.SaveUnmatchedRequests).Returns(true); + + // Act + await _sut.Invoke(_contextMock.Object).ConfigureAwait(false); + + // Assert + _optionsMock.Verify(o => o.Logger.Warn(It.IsAny(), It.IsAny()), Times.Once); + + Expression> match = r => (int)r.StatusCode! == 404 && ((StatusModel)r.BodyData!.BodyAsJson!).Status == "No matching mapping found"; + _responseMapperMock.Verify(m => m.MapAsync(It.Is(match), It.IsAny()), Times.Once); + + // Verify + fileSystemHandlerMock.Verify(f => f.WriteUnmatchedRequest("98fae52e-76df-47d9-876f-2ee32e931d9b.LogEntry.json", It.IsAny())); + fileSystemHandlerMock.VerifyNoOtherCalls(); + } + [Fact] public async Task WireMockMiddleware_Invoke_IsAdminInterface_EmptyHeaders_401() { @@ -109,7 +142,7 @@ public class WireMockMiddlewareTests // Assert and Verify _optionsMock.Verify(o => o.Logger.Error(It.IsAny(), It.IsAny()), Times.Once); - Expression> match = r => (int)r.StatusCode == 401; + Expression> match = r => (int?)r.StatusCode == 401; _responseMapperMock.Verify(m => m.MapAsync(It.Is(match), It.IsAny()), Times.Once); } @@ -132,7 +165,7 @@ public class WireMockMiddlewareTests // Assert and Verify _optionsMock.Verify(o => o.Logger.Error(It.IsAny(), It.IsAny()), Times.Once); - Expression> match = r => (int)r.StatusCode == 401; + Expression> match = r => (int?)r.StatusCode == 401; _responseMapperMock.Verify(m => m.MapAsync(It.Is(match), It.IsAny()), Times.Once); } @@ -177,7 +210,7 @@ public class WireMockMiddlewareTests _mappingMock.SetupGet(m => m.Provider).Returns(responseBuilder); _mappingMock.SetupGet(m => m.Settings).Returns(settings); - var newMappingFromProxy = new Mapping(Guid.NewGuid(), _updatedAt, string.Empty, string.Empty, null, settings, Request.Create(), Response.Create(), 0, null, null, null, null, null, false, null, null, null); + var newMappingFromProxy = new Mapping(NewGuid, UpdatedAt, string.Empty, string.Empty, null, settings, Request.Create(), Response.Create(), 0, null, null, null, null, null, false, null, null, null); _mappingMock.Setup(m => m.ProvideResponseAsync(It.IsAny())).ReturnsAsync((new ResponseMessage(), newMappingFromProxy)); var requestBuilder = Request.Create().UsingAnyMethod(); @@ -231,7 +264,7 @@ public class WireMockMiddlewareTests _mappingMock.SetupGet(m => m.Provider).Returns(responseBuilder); _mappingMock.SetupGet(m => m.Settings).Returns(settings); - var newMappingFromProxy = new Mapping(Guid.NewGuid(), _updatedAt, "my-title", "my-description", null, settings, Request.Create(), Response.Create(), 0, null, null, null, null, null, false, null, data: null, probability: null); + var newMappingFromProxy = new Mapping(NewGuid, UpdatedAt, "my-title", "my-description", null, settings, Request.Create(), Response.Create(), 0, null, null, null, null, null, false, null, data: null, probability: null); _mappingMock.Setup(m => m.ProvideResponseAsync(It.IsAny())).ReturnsAsync((new ResponseMessage(), newMappingFromProxy)); var requestBuilder = Request.Create().UsingAnyMethod(); @@ -246,6 +279,6 @@ public class WireMockMiddlewareTests // Assert and Verify fileSystemHandlerMock.Verify(f => f.WriteMappingFile(It.IsAny(), It.IsAny()), Times.Once); - _mappings.Count.Should().Be(1); + _mappings.Should().HaveCount(1); } -} +} \ No newline at end of file