Fixed logic for SaveUnmatchedRequests (#1002)

* Fixed logic for SaveUnmatchedRequests

* fix
This commit is contained in:
Stef Heyenrath
2023-09-21 11:38:01 +02:00
committed by GitHub
parent 59aab9e1c3
commit b9a8ee4145
9 changed files with 132 additions and 48 deletions

View File

@@ -22,10 +22,6 @@
<Compile Remove="__admin\mappings\1.cs" /> <Compile Remove="__admin\mappings\1.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<None Remove="__admin\mappings\array.json" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\src\WireMock.Net\WireMock.Net.csproj" /> <ProjectReference Include="..\..\src\WireMock.Net\WireMock.Net.csproj" />
<PackageReference Include="log4net" Version="2.0.15" /> <PackageReference Include="log4net" Version="2.0.15" />
@@ -39,15 +35,6 @@
<None Update="nlog.config"> <None Update="nlog.config">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None> </None>
<None Update="__admin\mappings\791a3f31-6946-4ce7-8e6f-0237c7443275.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="__admin\mappings\791a3f31-6946-4ce7-8e6f-0237c7443275.json">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</None>
<None Update="__admin\mappings\MyXmlResponse.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -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"
}
}
}
]

View File

@@ -137,6 +137,7 @@ namespace WireMock.Net.ConsoleApplication
Urls = new[] { url1, url2, url3 }, Urls = new[] { url1, url2, url3 },
StartAdminInterface = true, StartAdminInterface = true,
ReadStaticMappings = true, ReadStaticMappings = true,
SaveUnmatchedRequests = true,
WatchStaticMappings = true, WatchStaticMappings = true,
WatchStaticMappingsInSubdirectories = true, WatchStaticMappingsInSubdirectories = true,
//ProxyAndRecordSettings = new ProxyAndRecordSettings //ProxyAndRecordSettings = new ProxyAndRecordSettings

View File

@@ -70,6 +70,7 @@ namespace WireMock.Owin
services.AddSingleton<IRandomizerDoubleBetween0And1, RandomizerDoubleBetween0And1>(); services.AddSingleton<IRandomizerDoubleBetween0And1, RandomizerDoubleBetween0And1>();
services.AddSingleton<IOwinRequestMapper, OwinRequestMapper>(); services.AddSingleton<IOwinRequestMapper, OwinRequestMapper>();
services.AddSingleton<IOwinResponseMapper, OwinResponseMapper>(); services.AddSingleton<IOwinResponseMapper, OwinResponseMapper>();
services.AddSingleton<IGuidUtils, GuidUtils>();
#if NETCOREAPP3_1 || NET5_0 || NET6_0 || NET7_0 #if NETCOREAPP3_1 || NET5_0 || NET6_0 || NET7_0
AddCors(services); AddCors(services);

View File

@@ -9,9 +9,8 @@ using JetBrains.Annotations;
using WireMock.Logging; using WireMock.Logging;
using WireMock.Owin.Mappers; using WireMock.Owin.Mappers;
using Stef.Validation; using Stef.Validation;
using RandomDataGenerator.FieldOptions;
using RandomDataGenerator.Randomizers;
using WireMock.Services; using WireMock.Services;
using WireMock.Util;
namespace WireMock.Owin; namespace WireMock.Owin;
@@ -63,7 +62,7 @@ internal class OwinSelfHost : IOwinSelfHost
private void StartServers() private void StartServers()
{ {
#if NET46 #if NET46
_logger.Info("Server using .net 4.6.1 or higher"); _logger.Info("Server using .net 4.6");
#else #else
_logger.Info("Server using .net 4.5.x"); _logger.Info("Server using .net 4.5.x");
#endif #endif
@@ -74,12 +73,13 @@ internal class OwinSelfHost : IOwinSelfHost
var requestMapper = new OwinRequestMapper(); var requestMapper = new OwinRequestMapper();
var responseMapper = new OwinResponseMapper(_options); var responseMapper = new OwinResponseMapper(_options);
var matcher = new MappingMatcher(_options, new RandomizerDoubleBetween0And1()); var matcher = new MappingMatcher(_options, new RandomizerDoubleBetween0And1());
var guidUtils = new GuidUtils();
Action<IAppBuilder> startup = app => Action<IAppBuilder> startup = app =>
{ {
app.Use<GlobalExceptionMiddleware>(_options, responseMapper); app.Use<GlobalExceptionMiddleware>(_options, responseMapper);
_options.PreWireMockMiddlewareInit?.Invoke(app); _options.PreWireMockMiddlewareInit?.Invoke(app);
app.Use<WireMockMiddleware>(_options, requestMapper, responseMapper, matcher); app.Use<WireMockMiddleware>(_options, requestMapper, responseMapper, matcher, guidUtils);
_options.PostWireMockMiddlewareInit?.Invoke(app); _options.PostWireMockMiddlewareInit?.Invoke(app);
}; };

View File

@@ -30,29 +30,47 @@ namespace WireMock.Owin
{ {
private readonly object _lock = new(); private readonly object _lock = new();
private static readonly Task CompletedTask = Task.FromResult(false); private static readonly Task CompletedTask = Task.FromResult(false);
private readonly IWireMockMiddlewareOptions _options; private readonly IWireMockMiddlewareOptions _options;
private readonly IOwinRequestMapper _requestMapper; private readonly IOwinRequestMapper _requestMapper;
private readonly IOwinResponseMapper _responseMapper; private readonly IOwinResponseMapper _responseMapper;
private readonly IMappingMatcher _mappingMatcher; private readonly IMappingMatcher _mappingMatcher;
private readonly LogEntryMapper _logEntryMapper; private readonly LogEntryMapper _logEntryMapper;
private readonly IGuidUtils _guidUtils;
#if !USE_ASPNETCORE #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); _options = Guard.NotNull(options);
_requestMapper = Guard.NotNull(requestMapper); _requestMapper = Guard.NotNull(requestMapper);
_responseMapper = Guard.NotNull(responseMapper); _responseMapper = Guard.NotNull(responseMapper);
_mappingMatcher = Guard.NotNull(mappingMatcher); _mappingMatcher = Guard.NotNull(mappingMatcher);
_logEntryMapper = new LogEntryMapper(options); _logEntryMapper = new LogEntryMapper(options);
_guidUtils = Guard.NotNull(guidUtils);
} }
#else #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); _options = Guard.NotNull(options);
_requestMapper = Guard.NotNull(requestMapper); _requestMapper = Guard.NotNull(requestMapper);
_responseMapper = Guard.NotNull(responseMapper); _responseMapper = Guard.NotNull(responseMapper);
_mappingMatcher = Guard.NotNull(mappingMatcher); _mappingMatcher = Guard.NotNull(mappingMatcher);
_logEntryMapper = new LogEntryMapper(options); _logEntryMapper = new LogEntryMapper(options);
_guidUtils = Guard.NotNull(guidUtils);
} }
#endif #endif
@@ -170,7 +188,7 @@ namespace WireMock.Owin
{ {
var log = new LogEntry var log = new LogEntry
{ {
Guid = Guid.NewGuid(), Guid = _guidUtils.NewGuid(),
RequestMessage = request, RequestMessage = request,
ResponseMessage = response, ResponseMessage = response,
@@ -187,10 +205,10 @@ namespace WireMock.Owin
try 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"; var filename = $"{log.Guid}.LogEntry.json";
_options.FileSystemHandler?.WriteUnmatchedRequest(filename, Util.JsonUtils.Serialize(log)); _options.FileSystemHandler?.WriteUnmatchedRequest(filename, JsonUtils.Serialize(log));
} }
} }
catch catch

View File

@@ -4,11 +4,11 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using Newtonsoft.Json;
#if USE_ASPNETCORE #if USE_ASPNETCORE
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
#endif #endif
using Stef.Validation; using Stef.Validation;
using WireMock.Http;
using WireMock.Models; using WireMock.Models;
using WireMock.Owin; using WireMock.Owin;
using WireMock.Types; using WireMock.Types;
@@ -80,6 +80,7 @@ public class RequestMessage : IRequestMessage
#if MIMEKIT #if MIMEKIT
/// <inheritdoc /> /// <inheritdoc />
[JsonIgnore] // Issue 1001
public object? BodyAsMimeMessage { get; } public object? BodyAsMimeMessage { get; }
#endif #endif
@@ -112,7 +113,7 @@ public class RequestMessage : IRequestMessage
/// <summary> /// <summary>
/// Used for Unit Testing /// Used for Unit Testing
/// </summary> /// </summary>
public RequestMessage( internal RequestMessage(
UrlDetails urlDetails, UrlDetails urlDetails,
string method, string method,
string clientIP, string clientIP,
@@ -122,19 +123,6 @@ public class RequestMessage : IRequestMessage
{ {
} }
/// <summary>
/// Initializes a new instance of the <see cref="RequestMessage"/> class.
/// </summary>
/// <param name="options">The<seealso cref="IWireMockMiddlewareOptions"/>.</param>
/// <param name="urlDetails">The original url details.</param>
/// <param name="method">The HTTP method.</param>
/// <param name="clientIP">The client IP Address.</param>
/// <param name="bodyData">The BodyData.</param>
/// <param name="headers">The headers.</param>
/// <param name="cookies">The cookies.</param>
#if USE_ASPNETCORE
/// <param name="clientCertificate">The client certificate</param>
#endif
internal RequestMessage( internal RequestMessage(
IWireMockMiddlewareOptions? options, IWireMockMiddlewareOptions? options,
UrlDetails urlDetails, string method, UrlDetails urlDetails, string method,

View File

@@ -17,7 +17,10 @@ internal static class MimeKitUtils
{ {
Guard.NotNull(requestMessage); 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 var bytes = requestMessage.BodyData?.DetectedBodyType switch
{ {
@@ -40,6 +43,11 @@ internal static class MimeKitUtils
return false; return false;
} }
private static bool StartsWithMultiPart(WireMockList<string> contentTypeHeader)
{
return contentTypeHeader.Any(ct => ct.TrimStart().StartsWith("multipart/", StringComparison.OrdinalIgnoreCase));
}
private static byte[] FixBytes(byte[] bytes, WireMockList<string> contentType) private static byte[] FixBytes(byte[] bytes, WireMockList<string> contentType)
{ {
var contentTypeBytes = Encoding.UTF8.GetBytes($"{HttpKnownHeaderNames.ContentType}: {contentType}\r\n\r\n"); var contentTypeBytes = Encoding.UTF8.GetBytes($"{HttpKnownHeaderNames.ContentType}: {contentType}\r\n\r\n");

View File

@@ -34,9 +34,10 @@ namespace WireMock.Net.Tests.Owin;
public class WireMockMiddlewareTests public class WireMockMiddlewareTests
{ {
private readonly DateTime _updatedAt = new(2022, 12, 4); private static readonly Guid NewGuid = new("98fae52e-76df-47d9-876f-2ee32e931d9b");
private readonly ConcurrentDictionary<Guid, IMapping> _mappings = new(); private static readonly DateTime UpdatedAt = new(2022, 12, 4);
private readonly ConcurrentDictionary<Guid, IMapping> _mappings = new();
private readonly Mock<IWireMockMiddlewareOptions> _optionsMock; private readonly Mock<IWireMockMiddlewareOptions> _optionsMock;
private readonly Mock<IOwinRequestMapper> _requestMapperMock; private readonly Mock<IOwinRequestMapper> _requestMapperMock;
private readonly Mock<IOwinResponseMapper> _responseMapperMock; private readonly Mock<IOwinResponseMapper> _responseMapperMock;
@@ -48,6 +49,9 @@ public class WireMockMiddlewareTests
public WireMockMiddlewareTests() public WireMockMiddlewareTests()
{ {
var guidUtilsMock = new Mock<IGuidUtils>();
guidUtilsMock.Setup(g => g.NewGuid()).Returns(NewGuid);
_optionsMock = new Mock<IWireMockMiddlewareOptions>(); _optionsMock = new Mock<IWireMockMiddlewareOptions>();
_optionsMock.SetupAllProperties(); _optionsMock.SetupAllProperties();
_optionsMock.Setup(o => o.Mappings).Returns(_mappings); _optionsMock.Setup(o => o.Mappings).Returns(_mappings);
@@ -74,7 +78,14 @@ public class WireMockMiddlewareTests
_mappingMock = new Mock<IMapping>(); _mappingMock = new Mock<IMapping>();
_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] [Fact]
@@ -86,10 +97,32 @@ public class WireMockMiddlewareTests
// Assert and Verify // Assert and Verify
_optionsMock.Verify(o => o.Logger.Warn(It.IsAny<string>(), It.IsAny<object[]>()), Times.Once); _optionsMock.Verify(o => o.Logger.Warn(It.IsAny<string>(), It.IsAny<object[]>()), Times.Once);
Expression<Func<ResponseMessage, bool>> match = r => (int)r.StatusCode == 404 && ((StatusModel)r.BodyData.BodyAsJson).Status == "No matching mapping found"; Expression<Func<ResponseMessage, bool>> 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<IResponse>()), Times.Once); _responseMapperMock.Verify(m => m.MapAsync(It.Is(match), It.IsAny<IResponse>()), Times.Once);
} }
[Fact]
public async Task WireMockMiddleware_Invoke_NoMatch_When_SaveUnmatchedRequestsIsTrue_Should_Call_LocalFileSystemHandler_WriteUnmatchedRequest()
{
// Arrange
var fileSystemHandlerMock = new Mock<IFileSystemHandler>();
_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<string>(), It.IsAny<object[]>()), Times.Once);
Expression<Func<ResponseMessage, bool>> 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<IResponse>()), Times.Once);
// Verify
fileSystemHandlerMock.Verify(f => f.WriteUnmatchedRequest("98fae52e-76df-47d9-876f-2ee32e931d9b.LogEntry.json", It.IsAny<string>()));
fileSystemHandlerMock.VerifyNoOtherCalls();
}
[Fact] [Fact]
public async Task WireMockMiddleware_Invoke_IsAdminInterface_EmptyHeaders_401() public async Task WireMockMiddleware_Invoke_IsAdminInterface_EmptyHeaders_401()
{ {
@@ -109,7 +142,7 @@ public class WireMockMiddlewareTests
// Assert and Verify // Assert and Verify
_optionsMock.Verify(o => o.Logger.Error(It.IsAny<string>(), It.IsAny<object[]>()), Times.Once); _optionsMock.Verify(o => o.Logger.Error(It.IsAny<string>(), It.IsAny<object[]>()), Times.Once);
Expression<Func<ResponseMessage, bool>> match = r => (int)r.StatusCode == 401; Expression<Func<ResponseMessage, bool>> match = r => (int?)r.StatusCode == 401;
_responseMapperMock.Verify(m => m.MapAsync(It.Is(match), It.IsAny<IResponse>()), Times.Once); _responseMapperMock.Verify(m => m.MapAsync(It.Is(match), It.IsAny<IResponse>()), Times.Once);
} }
@@ -132,7 +165,7 @@ public class WireMockMiddlewareTests
// Assert and Verify // Assert and Verify
_optionsMock.Verify(o => o.Logger.Error(It.IsAny<string>(), It.IsAny<object[]>()), Times.Once); _optionsMock.Verify(o => o.Logger.Error(It.IsAny<string>(), It.IsAny<object[]>()), Times.Once);
Expression<Func<ResponseMessage, bool>> match = r => (int)r.StatusCode == 401; Expression<Func<ResponseMessage, bool>> match = r => (int?)r.StatusCode == 401;
_responseMapperMock.Verify(m => m.MapAsync(It.Is(match), It.IsAny<IResponse>()), Times.Once); _responseMapperMock.Verify(m => m.MapAsync(It.Is(match), It.IsAny<IResponse>()), Times.Once);
} }
@@ -177,7 +210,7 @@ public class WireMockMiddlewareTests
_mappingMock.SetupGet(m => m.Provider).Returns(responseBuilder); _mappingMock.SetupGet(m => m.Provider).Returns(responseBuilder);
_mappingMock.SetupGet(m => m.Settings).Returns(settings); _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<RequestMessage>())).ReturnsAsync((new ResponseMessage(), newMappingFromProxy)); _mappingMock.Setup(m => m.ProvideResponseAsync(It.IsAny<RequestMessage>())).ReturnsAsync((new ResponseMessage(), newMappingFromProxy));
var requestBuilder = Request.Create().UsingAnyMethod(); var requestBuilder = Request.Create().UsingAnyMethod();
@@ -231,7 +264,7 @@ public class WireMockMiddlewareTests
_mappingMock.SetupGet(m => m.Provider).Returns(responseBuilder); _mappingMock.SetupGet(m => m.Provider).Returns(responseBuilder);
_mappingMock.SetupGet(m => m.Settings).Returns(settings); _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<RequestMessage>())).ReturnsAsync((new ResponseMessage(), newMappingFromProxy)); _mappingMock.Setup(m => m.ProvideResponseAsync(It.IsAny<RequestMessage>())).ReturnsAsync((new ResponseMessage(), newMappingFromProxy));
var requestBuilder = Request.Create().UsingAnyMethod(); var requestBuilder = Request.Create().UsingAnyMethod();
@@ -246,6 +279,6 @@ public class WireMockMiddlewareTests
// Assert and Verify // Assert and Verify
fileSystemHandlerMock.Verify(f => f.WriteMappingFile(It.IsAny<string>(), It.IsAny<string>()), Times.Once); fileSystemHandlerMock.Verify(f => f.WriteMappingFile(It.IsAny<string>(), It.IsAny<string>()), Times.Once);
_mappings.Count.Should().Be(1); _mappings.Should().HaveCount(1);
} }
} }