From 4688f556b561dc834941ba0df5136def1e84c53f Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Thu, 3 Aug 2023 15:55:46 +0200 Subject: [PATCH] Add MultiPart/MimePart Request Matcher (#981) * wip * . * mm * x * . * . * . * tests * . * more tests * trans * x * win * fix * . * tests --- WireMock.Net Solution.sln | 11 +- .../WireMock.Net.Console.NET6.csproj | 2 + .../MainApp.cs | 48 +- .../Admin/Mappings/MatcherModel.cs | 28 +- .../IRequestMessage.cs | 19 +- .../WireMock.Net.Abstractions.csproj | 4 + .../Http/HttpRequestMessageHelper.cs | 21 +- src/WireMock.Net/Matchers/GraphQLMatcher.cs | 2 +- .../Helpers/BodyDataMatchScoreCalculator.cs | 78 ++++ src/WireMock.Net/Matchers/LinqMatcher.cs | 3 +- src/WireMock.Net/Matchers/MatchBehaviour.cs | 2 +- src/WireMock.Net/Matchers/MimePartMatcher.cs | 125 +++++ .../Request/RequestMessageBodyMatcher.cs | 62 +-- .../Request/RequestMessageGraphQLMatcher.cs | 1 + .../Request/RequestMessageMultiPartMatcher.cs | 99 ++++ .../Owin/Mappers/OwinRequestMapper.cs | 14 +- .../RequestBuilders/IGraphQLRequestBuilder.cs | 3 +- .../RequestBuilders/IMethodRequestBuilder.cs | 2 - .../IMultiPartRequestBuilder.cs | 34 ++ .../RequestBuilders/Request.WithMultiPart.cs | 28 ++ src/WireMock.Net/RequestMessage.cs | 90 ++-- .../Serialization/MappingConverter.cs | 34 +- .../Serialization/MatcherMapper.cs | 35 +- src/WireMock.Net/Util/MimeKitUtils.cs | 42 ++ src/WireMock.Net/Util/StreamUtils.cs | 12 + src/WireMock.Net/WireMock.Net.csproj | 4 +- .../Matchers/MimePartMatcherTests.cs | 99 ++++ .../RequestBuilderTests.cs} | 4 +- .../RequestBuilderWithBodyTests.cs | 420 ++++++++++++++++- .../RequestBuilderWithClientIPTests.cs | 67 +++ .../RequestBuilderWithMultiPartTests.cs | 47 ++ .../RequestBuilderWithPathTests.cs | 238 ++++++++++ .../RequestMessageMultiPartMatcher.cs | 84 ++++ .../RequestWithBodyTests.cs | 428 ------------------ .../RequestWithClientIPTests.cs | 68 --- .../RequestWithPathTests.cs | 239 ---------- .../ResponseWithTransformerTests.cs | 53 +++ .../Serialization/MatcherMapperTests.cs | 94 ++++ .../WireMock.Net.Tests.csproj | 5 +- .../WireMockServerTests.WithMultiPart.cs | 75 +++ tools/MultipartUploader/Form1.Designer.cs | 39 ++ tools/MultipartUploader/Form1.cs | 10 + tools/MultipartUploader/Form1.resx | 120 +++++ .../MultipartUploader.csproj | 15 + tools/MultipartUploader/Program.cs | 17 + 45 files changed, 2022 insertions(+), 903 deletions(-) create mode 100644 src/WireMock.Net/Matchers/Helpers/BodyDataMatchScoreCalculator.cs create mode 100644 src/WireMock.Net/Matchers/MimePartMatcher.cs create mode 100644 src/WireMock.Net/Matchers/Request/RequestMessageMultiPartMatcher.cs create mode 100644 src/WireMock.Net/RequestBuilders/IMultiPartRequestBuilder.cs create mode 100644 src/WireMock.Net/RequestBuilders/Request.WithMultiPart.cs create mode 100644 src/WireMock.Net/Util/MimeKitUtils.cs create mode 100644 src/WireMock.Net/Util/StreamUtils.cs create mode 100644 test/WireMock.Net.Tests/Matchers/MimePartMatcherTests.cs rename test/WireMock.Net.Tests/{RequestTests.cs => RequestBuilders/RequestBuilderTests.cs} (99%) create mode 100644 test/WireMock.Net.Tests/RequestBuilders/RequestBuilderWithClientIPTests.cs create mode 100644 test/WireMock.Net.Tests/RequestBuilders/RequestBuilderWithMultiPartTests.cs create mode 100644 test/WireMock.Net.Tests/RequestBuilders/RequestBuilderWithPathTests.cs create mode 100644 test/WireMock.Net.Tests/RequestMatchers/RequestMessageMultiPartMatcher.cs delete mode 100644 test/WireMock.Net.Tests/RequestWithBodyTests.cs delete mode 100644 test/WireMock.Net.Tests/RequestWithClientIPTests.cs delete mode 100644 test/WireMock.Net.Tests/RequestWithPathTests.cs create mode 100644 test/WireMock.Net.Tests/WireMockServerTests.WithMultiPart.cs create mode 100644 tools/MultipartUploader/Form1.Designer.cs create mode 100644 tools/MultipartUploader/Form1.cs create mode 100644 tools/MultipartUploader/Form1.resx create mode 100644 tools/MultipartUploader/MultipartUploader.csproj create mode 100644 tools/MultipartUploader/Program.cs diff --git a/WireMock.Net Solution.sln b/WireMock.Net Solution.sln index 9e767796..e74b95b4 100644 --- a/WireMock.Net Solution.sln +++ b/WireMock.Net Solution.sln @@ -111,10 +111,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMockAzureQueueExample", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMockAzureQueueProxy", "examples\WireMockAzureQueueProxy\WireMockAzureQueueProxy.csproj", "{ADB557D8-D66B-4387-912B-3F73E290B478}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.Testcontainers", "src\WireMock.Net.Testcontainers\WireMock.Net.Testcontainers.csproj", "{12B016A5-9D8B-4EFE-96C2-CA51BE43367D}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.Testcontainers", "src\WireMock.Net.Testcontainers\WireMock.Net.Testcontainers.csproj", "{12B016A5-9D8B-4EFE-96C2-CA51BE43367D}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.TestcontainersExample", "examples\WireMock.Net.TestcontainersExample\WireMock.Net.TestcontainersExample.csproj", "{56A38798-C48B-4A4A-B805-071E05C02CE1}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{0147029F-FA4A-44B3-B79A-3C3574054EE4}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MultipartUploader", "tools\MultipartUploader\MultipartUploader.csproj", "{07C30227-ADEC-4BDE-8CDC-849D85A690BB}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -277,6 +281,10 @@ Global {56A38798-C48B-4A4A-B805-071E05C02CE1}.Debug|Any CPU.Build.0 = Debug|Any CPU {56A38798-C48B-4A4A-B805-071E05C02CE1}.Release|Any CPU.ActiveCfg = Release|Any CPU {56A38798-C48B-4A4A-B805-071E05C02CE1}.Release|Any CPU.Build.0 = Release|Any CPU + {07C30227-ADEC-4BDE-8CDC-849D85A690BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {07C30227-ADEC-4BDE-8CDC-849D85A690BB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {07C30227-ADEC-4BDE-8CDC-849D85A690BB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {07C30227-ADEC-4BDE-8CDC-849D85A690BB}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -323,6 +331,7 @@ Global {ADB557D8-D66B-4387-912B-3F73E290B478} = {985E0ADB-D4B4-473A-AA40-567E279B7946} {12B016A5-9D8B-4EFE-96C2-CA51BE43367D} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2} {56A38798-C48B-4A4A-B805-071E05C02CE1} = {985E0ADB-D4B4-473A-AA40-567E279B7946} + {07C30227-ADEC-4BDE-8CDC-849D85A690BB} = {0147029F-FA4A-44B3-B79A-3C3574054EE4} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {DC539027-9852-430C-B19F-FD035D018458} 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 8954b1f8..c74188d2 100644 --- a/examples/WireMock.Net.Console.NET6/WireMock.Net.Console.NET6.csproj +++ b/examples/WireMock.Net.Console.NET6/WireMock.Net.Console.NET6.csproj @@ -3,6 +3,7 @@ Exe net6.0 + $(DefineConstants);GRAPHQL;MIMEKIT @@ -29,6 +30,7 @@ + diff --git a/examples/WireMock.Net.Console.Net452.Classic/MainApp.cs b/examples/WireMock.Net.Console.Net452.Classic/MainApp.cs index f7c60bfe..71aeb551 100644 --- a/examples/WireMock.Net.Console.Net452.Classic/MainApp.cs +++ b/examples/WireMock.Net.Console.Net452.Classic/MainApp.cs @@ -166,7 +166,7 @@ namespace WireMock.Net.ConsoleApplication //server.SetAzureADAuthentication("6c2a4722-f3b9-4970-b8fc-fac41e29stef", "8587fde1-7824-42c7-8592-faf92b04stef"); // server.AllowPartialMapping(); - +#if GRAPHQL server .Given(Request.Create() .WithPath("/graphql") @@ -176,7 +176,41 @@ namespace WireMock.Net.ConsoleApplication .RespondWith(Response.Create() .WithBody("GraphQL is ok") ); +#endif +#if MIMEKIT + var textPlainContentTypeMatcher = new ContentTypeMatcher("text/plain"); + var textPlainContentMatcher = new ExactMatcher("This is some plain text"); + var textPlainMatcher = new MimePartMatcher(MatchBehaviour.AcceptOnMatch, textPlainContentTypeMatcher, null, null, textPlainContentMatcher); + + var textJsonContentTypeMatcher = new ContentTypeMatcher("text/json"); + var textJsonContentMatcher = new JsonMatcher(new { Key = "Value" }, true); + var textJsonMatcher = new MimePartMatcher(MatchBehaviour.AcceptOnMatch, textJsonContentTypeMatcher, null, null, textJsonContentMatcher); + + var imagePngContentTypeMatcher = new ContentTypeMatcher("image/png"); + var imagePngContentDispositionMatcher = new ExactMatcher("attachment; filename=\"image.png\""); + var imagePngContentTransferEncodingMatcher = new ExactMatcher("base64"); + var imagePngContentMatcher = new ExactObjectMatcher(Convert.FromBase64String("iVBORw0KGgoAAAANSUhEUgAAAAIAAAACAgMAAAAP2OW3AAAADFBMVEX/tID/vpH/pWX/sHidUyjlAAAADElEQVR4XmMQYNgAAADkAMHebX3mAAAAAElFTkSuQmCC")); + var imagePngMatcher = new MimePartMatcher(MatchBehaviour.AcceptOnMatch, imagePngContentTypeMatcher, imagePngContentDispositionMatcher, imagePngContentTransferEncodingMatcher, imagePngContentMatcher); + + var matchers = new IMatcher[] + { + textPlainMatcher, + textJsonMatcher, + imagePngMatcher + }; + + server + .Given(Request.Create() + .WithPath("/multipart") + .UsingPost() + .WithMultiPart(matchers) + ) + .WithGuid("b9c82182-e469-41da-bcaf-b6e3157fefdb") + .RespondWith(Response.Create() + .WithBody("MultiPart is ok") + ); +#endif // 400 ms server .Given(Request.Create() @@ -716,8 +750,9 @@ namespace WireMock.Net.ConsoleApplication })); server.Given(Request.Create().WithPath(new WildcardMatcher("/multi-webhook", true)).UsingPost()) - .WithWebhook(new[] { - new Webhook() + .WithWebhook + ( + new Webhook { Request = new WebhookRequest { @@ -725,12 +760,13 @@ namespace WireMock.Net.ConsoleApplication Method = "post", BodyData = new BodyData { - BodyAsString = "OK 1!", DetectedBodyType = BodyType.String + BodyAsString = "OK 1!", + DetectedBodyType = BodyType.String }, Delay = 1000 } }, - new Webhook() + new Webhook { Request = new WebhookRequest { @@ -745,7 +781,7 @@ namespace WireMock.Net.ConsoleApplication MaximumRandomDelay = 7000 } } - }) + ) .WithWebhookFireAndForget(true) .RespondWith(Response.Create().WithBody("a-response")); diff --git a/src/WireMock.Net.Abstractions/Admin/Mappings/MatcherModel.cs b/src/WireMock.Net.Abstractions/Admin/Mappings/MatcherModel.cs index 9ef8604c..1e624f64 100644 --- a/src/WireMock.Net.Abstractions/Admin/Mappings/MatcherModel.cs +++ b/src/WireMock.Net.Abstractions/Admin/Mappings/MatcherModel.cs @@ -39,14 +39,38 @@ public class MatcherModel /// /// The Operator to use when multiple patterns are defined. Optional. /// - null = Same as "or". - /// - "or" = Only one pattern should match. + /// - "or" = Only one pattern is required to match. /// - "and" = All patterns should match. /// - "average" = The average value from all patterns. /// public string? MatchOperator { get; set; } + #region JsonPartialMatcher and JsonPartialWildcardMatcher /// - /// Support Regex, only used for JsonPartialMatcher. + /// Support Regex. /// public bool? Regex { get; set; } + #endregion + + #region MimePartMatcher + /// + /// ContentType Matcher (image/png; name=image.png) + /// + public MatcherModel? ContentTypeMatcher { get; set; } + + /// + /// ContentDisposition Matcher (attachment; filename=image.png) + /// + public MatcherModel? ContentDispositionMatcher { get; set; } + + /// + /// ContentTransferEncoding Matcher (base64) + /// + public MatcherModel? ContentTransferEncodingMatcher { get; set; } + + /// + /// Content Matcher + /// + public MatcherModel? ContentMatcher { get; set; } + #endregion } \ No newline at end of file diff --git a/src/WireMock.Net.Abstractions/IRequestMessage.cs b/src/WireMock.Net.Abstractions/IRequestMessage.cs index 7f259b95..f87b9ad6 100644 --- a/src/WireMock.Net.Abstractions/IRequestMessage.cs +++ b/src/WireMock.Net.Abstractions/IRequestMessage.cs @@ -96,32 +96,39 @@ public interface IRequestMessage /// /// The original body as string. Convenience getter for Handlebars. /// - string Body { get; } + string? Body { get; } /// /// The body (as JSON object). Convenience getter for Handlebars. /// - object BodyAsJson { get; } + object? BodyAsJson { get; } /// /// The body (as bytearray). Convenience getter for Handlebars. /// - byte[] BodyAsBytes { get; } + byte[]? BodyAsBytes { get; } + +#if MIMEKIT + /// + /// The original body as MimeMessage. Convenience getter for Handlebars. + /// + object? BodyAsMimeMessage { get; } +#endif /// /// The detected body type. Convenience getter for Handlebars. /// - string DetectedBodyType { get; } + string? DetectedBodyType { get; } /// /// The detected body type from the Content-Type header. Convenience getter for Handlebars. /// - string DetectedBodyTypeFromContentType { get; } + string? DetectedBodyTypeFromContentType { get; } /// /// The detected compression from the Content-Encoding header. Convenience getter for Handlebars. /// - string DetectedCompression { get; } + string? DetectedCompression { get; } /// /// Gets the Host diff --git a/src/WireMock.Net.Abstractions/WireMock.Net.Abstractions.csproj b/src/WireMock.Net.Abstractions/WireMock.Net.Abstractions.csproj index 01bca530..8658637a 100644 --- a/src/WireMock.Net.Abstractions/WireMock.Net.Abstractions.csproj +++ b/src/WireMock.Net.Abstractions/WireMock.Net.Abstractions.csproj @@ -30,6 +30,10 @@ true + + $(DefineConstants);GRAPHQL;MIMEKIT + + diff --git a/src/WireMock.Net/Http/HttpRequestMessageHelper.cs b/src/WireMock.Net/Http/HttpRequestMessageHelper.cs index 778fb7a1..b2ec4d9a 100644 --- a/src/WireMock.Net/Http/HttpRequestMessageHelper.cs +++ b/src/WireMock.Net/Http/HttpRequestMessageHelper.cs @@ -31,21 +31,14 @@ internal static class HttpRequestMessageHelper MediaTypeHeaderValue.TryParse(value, out contentType); } - switch (requestMessage.BodyData?.DetectedBodyType) + httpRequestMessage.Content = requestMessage.BodyData?.DetectedBodyType switch { - case BodyType.Bytes: - httpRequestMessage.Content = ByteArrayContentHelper.Create(requestMessage.BodyData.BodyAsBytes!, contentType); - break; - - case BodyType.Json: - httpRequestMessage.Content = StringContentHelper.Create(JsonConvert.SerializeObject(requestMessage.BodyData.BodyAsJson), contentType); - break; - - case BodyType.String: - case BodyType.FormUrlEncoded: - httpRequestMessage.Content = StringContentHelper.Create(requestMessage.BodyData.BodyAsString!, contentType); - break; - } + BodyType.Bytes => ByteArrayContentHelper.Create(requestMessage.BodyData.BodyAsBytes!, contentType), + BodyType.Json => StringContentHelper.Create(JsonConvert.SerializeObject(requestMessage.BodyData.BodyAsJson), contentType), + BodyType.String => StringContentHelper.Create(requestMessage.BodyData.BodyAsString!, contentType), + BodyType.FormUrlEncoded => StringContentHelper.Create(requestMessage.BodyData.BodyAsString!, contentType), + _ => httpRequestMessage.Content + }; // Overwrite the host header httpRequestMessage.Headers.Host = new Uri(url).Authority; diff --git a/src/WireMock.Net/Matchers/GraphQLMatcher.cs b/src/WireMock.Net/Matchers/GraphQLMatcher.cs index 92eca1ff..02b3b58f 100644 --- a/src/WireMock.Net/Matchers/GraphQLMatcher.cs +++ b/src/WireMock.Net/Matchers/GraphQLMatcher.cs @@ -128,7 +128,7 @@ public class GraphQLMatcher : IStringMatcher /// public MatchOperator MatchOperator { get; } - /// + /// public string Name => nameof(GraphQLMatcher); private static ISchema BuildSchema(string schema) diff --git a/src/WireMock.Net/Matchers/Helpers/BodyDataMatchScoreCalculator.cs b/src/WireMock.Net/Matchers/Helpers/BodyDataMatchScoreCalculator.cs new file mode 100644 index 00000000..2050285a --- /dev/null +++ b/src/WireMock.Net/Matchers/Helpers/BodyDataMatchScoreCalculator.cs @@ -0,0 +1,78 @@ +using Stef.Validation; +using WireMock.Types; +using WireMock.Util; + +namespace WireMock.Matchers.Helpers; + +internal static class BodyDataMatchScoreCalculator +{ + public static double CalculateMatchScore(IBodyData? requestMessage, IMatcher matcher) + { + Guard.NotNull(matcher); + + if (matcher is NotNullOrEmptyMatcher notNullOrEmptyMatcher) + { + switch (requestMessage?.DetectedBodyType) + { + case BodyType.Json: + case BodyType.String: + case BodyType.FormUrlEncoded: + return notNullOrEmptyMatcher.IsMatch(requestMessage.BodyAsString); + + case BodyType.Bytes: + return notNullOrEmptyMatcher.IsMatch(requestMessage.BodyAsBytes); + + default: + return MatchScores.Mismatch; + } + } + + if (matcher is ExactObjectMatcher exactObjectMatcher) + { + // If the body is a byte array, try to match. + var detectedBodyType = requestMessage?.DetectedBodyType; + if (detectedBodyType is BodyType.Bytes or BodyType.String or BodyType.FormUrlEncoded) + { + return exactObjectMatcher.IsMatch(requestMessage?.BodyAsBytes); + } + } + + // Check if the matcher is a IObjectMatcher + if (matcher is IObjectMatcher objectMatcher) + { + // If the body is a JSON object, try to match. + if (requestMessage?.DetectedBodyType == BodyType.Json) + { + return objectMatcher.IsMatch(requestMessage.BodyAsJson); + } + + // If the body is a byte array, try to match. + if (requestMessage?.DetectedBodyType == BodyType.Bytes) + { + return objectMatcher.IsMatch(requestMessage.BodyAsBytes); + } + } + + // Check if the matcher is a IStringMatcher + if (matcher is IStringMatcher stringMatcher) + { + // If the body is a Json or a String, use the BodyAsString to match on. + if (requestMessage?.DetectedBodyType is BodyType.Json or BodyType.String or BodyType.FormUrlEncoded) + { + return stringMatcher.IsMatch(requestMessage.BodyAsString); + } + } + +#if MIMEKIT_XXX + if (matcher is MultiPartMatcher multiPartMatcher) + { + // If the body is a String or MultiPart, use the BodyAsString to match on. + if (requestMessage?.DetectedBodyType is BodyType.String or BodyType.MultiPart) + { + return multiPartMatcher.IsMatch(requestMessage.BodyAsString); + } + } +#endif + return MatchScores.Mismatch; + } +} \ No newline at end of file diff --git a/src/WireMock.Net/Matchers/LinqMatcher.cs b/src/WireMock.Net/Matchers/LinqMatcher.cs index 34577b60..48f17dbc 100644 --- a/src/WireMock.Net/Matchers/LinqMatcher.cs +++ b/src/WireMock.Net/Matchers/LinqMatcher.cs @@ -1,4 +1,3 @@ -using System; using System.Linq; using System.Linq.Dynamic.Core; using AnyOfTypes; @@ -122,7 +121,7 @@ public class LinqMatcher : IObjectMatcher, IStringMatcher return MatchBehaviourHelper.Convert(MatchBehaviour, match); } - catch (Exception e) + catch { if (ThrowException) { diff --git a/src/WireMock.Net/Matchers/MatchBehaviour.cs b/src/WireMock.Net/Matchers/MatchBehaviour.cs index f703ff07..6235c5ee 100644 --- a/src/WireMock.Net/Matchers/MatchBehaviour.cs +++ b/src/WireMock.Net/Matchers/MatchBehaviour.cs @@ -1,7 +1,7 @@ namespace WireMock.Matchers; /// -/// MatchBehaviour +/// MatchBehaviour (Accept or Reject) /// public enum MatchBehaviour { diff --git a/src/WireMock.Net/Matchers/MimePartMatcher.cs b/src/WireMock.Net/Matchers/MimePartMatcher.cs new file mode 100644 index 00000000..410efd7a --- /dev/null +++ b/src/WireMock.Net/Matchers/MimePartMatcher.cs @@ -0,0 +1,125 @@ +#if MIMEKIT +using System; +using MimeKit; +using WireMock.Matchers.Helpers; +using WireMock.Util; + +namespace WireMock.Matchers; + +/// +/// MimePartMatcher +/// +public class MimePartMatcher : IMatcher +{ + private readonly Func[] _funcs; + + /// + public string Name => nameof(MimePartMatcher); + + /// + /// ContentType Matcher (image/png; name=image.png.) + /// + public IStringMatcher? ContentTypeMatcher { get; } + + /// + /// ContentDisposition Matcher (attachment; filename=image.png) + /// + public IStringMatcher? ContentDispositionMatcher { get; } + + /// + /// ContentTransferEncoding Matcher (base64) + /// + public IStringMatcher? ContentTransferEncodingMatcher { get; } + + /// + /// Content Matcher + /// + public IMatcher? ContentMatcher { get; } + + /// + public MatchBehaviour MatchBehaviour { get; } + + /// + public bool ThrowException { get; } + + /// + /// Initializes a new instance of the class. + /// + public MimePartMatcher( + MatchBehaviour matchBehaviour, + IStringMatcher? contentTypeMatcher, + IStringMatcher? contentDispositionMatcher, + IStringMatcher? contentTransferEncodingMatcher, + IMatcher? contentMatcher, + bool throwException = false + ) + { + MatchBehaviour = matchBehaviour; + ContentTypeMatcher = contentTypeMatcher; + ContentDispositionMatcher = contentDispositionMatcher; + ContentTransferEncodingMatcher = contentTransferEncodingMatcher; + ContentMatcher = contentMatcher; + ThrowException = throwException; + + _funcs = new[] + { + mp => ContentTypeMatcher?.IsMatch(GetContentTypeAsString(mp.ContentType)) ?? MatchScores.Perfect, + mp => ContentDispositionMatcher?.IsMatch(mp.ContentDisposition.ToString().Replace("Content-Disposition: ", string.Empty)) ?? MatchScores.Perfect, + mp => ContentTransferEncodingMatcher?.IsMatch(mp.ContentTransferEncoding.ToString().ToLowerInvariant()) ?? MatchScores.Perfect, + MatchOnContent + }; + } + + /// + /// Determines whether the specified MimePart is match. + /// + /// The MimePart. + /// A value between 0.0 - 1.0 of the similarity. + public double IsMatch(MimePart mimePart) + { + var match = MatchScores.Mismatch; + + try + { + if (Array.TrueForAll(_funcs, func => MatchScores.IsPerfect(func(mimePart)))) + { + match = MatchScores.Perfect; + } + } + catch + { + if (ThrowException) + { + throw; + } + } + + return MatchBehaviourHelper.Convert(MatchBehaviour, match); + } + + private double MatchOnContent(MimePart mimePart) + { + if (ContentMatcher == null) + { + return MatchScores.Perfect; + } + + var bodyParserSettings = new BodyParserSettings + { + Stream = mimePart.Content.Open(), + ContentType = GetContentTypeAsString(mimePart.ContentType), + DeserializeJson = true, + ContentEncoding = null, // mimePart.ContentType.CharsetEncoding.ToString(), + DecompressGZipAndDeflate = true + }; + + var bodyData = BodyParser.ParseAsync(bodyParserSettings).ConfigureAwait(false).GetAwaiter().GetResult(); + return BodyDataMatchScoreCalculator.CalculateMatchScore(bodyData, ContentMatcher); + } + + private static string? GetContentTypeAsString(ContentType? contentType) + { + return contentType?.ToString().Replace("Content-Type: ", string.Empty); + } +} +#endif \ No newline at end of file diff --git a/src/WireMock.Net/Matchers/Request/RequestMessageBodyMatcher.cs b/src/WireMock.Net/Matchers/Request/RequestMessageBodyMatcher.cs index 580902f2..7bcb4138 100644 --- a/src/WireMock.Net/Matchers/Request/RequestMessageBodyMatcher.cs +++ b/src/WireMock.Net/Matchers/Request/RequestMessageBodyMatcher.cs @@ -2,7 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; using Stef.Validation; -using WireMock.Types; +using WireMock.Matchers.Helpers; using WireMock.Util; namespace WireMock.Matchers.Request; @@ -149,69 +149,11 @@ public class RequestMessageBodyMatcher : IRequestMatcher return requestMatchResult.AddScore(GetType(), score); } - private static double CalculateMatchScore(IRequestMessage requestMessage, IMatcher matcher) - { - if (matcher is NotNullOrEmptyMatcher notNullOrEmptyMatcher) - { - switch (requestMessage.BodyData?.DetectedBodyType) - { - case BodyType.Json: - case BodyType.String: - case BodyType.FormUrlEncoded: - return notNullOrEmptyMatcher.IsMatch(requestMessage.BodyData.BodyAsString); - - case BodyType.Bytes: - return notNullOrEmptyMatcher.IsMatch(requestMessage.BodyData.BodyAsBytes); - - default: - return MatchScores.Mismatch; - } - } - - if (matcher is ExactObjectMatcher exactObjectMatcher) - { - // If the body is a byte array, try to match. - var detectedBodyType = requestMessage.BodyData?.DetectedBodyType; - if (detectedBodyType is BodyType.Bytes or BodyType.String or BodyType.FormUrlEncoded) - { - return exactObjectMatcher.IsMatch(requestMessage.BodyData?.BodyAsBytes); - } - } - - // Check if the matcher is a IObjectMatcher - if (matcher is IObjectMatcher objectMatcher) - { - // If the body is a JSON object, try to match. - if (requestMessage?.BodyData?.DetectedBodyType == BodyType.Json) - { - return objectMatcher.IsMatch(requestMessage.BodyData.BodyAsJson); - } - - // If the body is a byte array, try to match. - if (requestMessage?.BodyData?.DetectedBodyType == BodyType.Bytes) - { - return objectMatcher.IsMatch(requestMessage.BodyData.BodyAsBytes); - } - } - - // Check if the matcher is a IStringMatcher - if (matcher is IStringMatcher stringMatcher) - { - // If the body is a Json or a String, use the BodyAsString to match on. - if (requestMessage?.BodyData?.DetectedBodyType is BodyType.Json or BodyType.String or BodyType.FormUrlEncoded) - { - return stringMatcher.IsMatch(requestMessage.BodyData.BodyAsString); - } - } - - return MatchScores.Mismatch; - } - private double CalculateMatchScore(IRequestMessage requestMessage) { if (Matchers != null) { - var matchersResult = Matchers.Select(matcher => CalculateMatchScore(requestMessage, matcher)).ToArray(); + var matchersResult = Matchers.Select(matcher => BodyDataMatchScoreCalculator.CalculateMatchScore(requestMessage.BodyData, matcher)).ToArray(); return MatchScores.ToScore(matchersResult, MatchOperator); } diff --git a/src/WireMock.Net/Matchers/Request/RequestMessageGraphQLMatcher.cs b/src/WireMock.Net/Matchers/Request/RequestMessageGraphQLMatcher.cs index 21719ca9..653d2d90 100644 --- a/src/WireMock.Net/Matchers/Request/RequestMessageGraphQLMatcher.cs +++ b/src/WireMock.Net/Matchers/Request/RequestMessageGraphQLMatcher.cs @@ -40,6 +40,7 @@ public class RequestMessageGraphQLMatcher : IRequestMatcher { } #endif + /// /// Initializes a new instance of the class. /// diff --git a/src/WireMock.Net/Matchers/Request/RequestMessageMultiPartMatcher.cs b/src/WireMock.Net/Matchers/Request/RequestMessageMultiPartMatcher.cs new file mode 100644 index 00000000..92c43694 --- /dev/null +++ b/src/WireMock.Net/Matchers/Request/RequestMessageMultiPartMatcher.cs @@ -0,0 +1,99 @@ +using System.Collections.Generic; +using System.Linq; +using Stef.Validation; +using WireMock.Http; +using WireMock.Util; + +namespace WireMock.Matchers.Request; + +/// +/// The request body MultiPart matcher. +/// +public class RequestMessageMultiPartMatcher : IRequestMatcher +{ + /// + /// The matchers. + /// + public IMatcher[]? Matchers { get; } + + /// + /// The + /// + public MatchOperator MatchOperator { get; } = MatchOperator.Or; + + /// + /// The + /// + public MatchBehaviour MatchBehaviour { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The matchers. + public RequestMessageMultiPartMatcher(params IMatcher[] matchers) + { + Matchers = Guard.NotNull(matchers); + } + + /// + /// Initializes a new instance of the class. + /// + /// The match behaviour. + /// The to use. + /// The matchers. + public RequestMessageMultiPartMatcher(MatchBehaviour matchBehaviour, MatchOperator matchOperator, params IMatcher[] matchers) + { + Matchers = Guard.NotNull(matchers); + MatchBehaviour = matchBehaviour; + MatchOperator = matchOperator; + } + + /// + public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult) + { +#if !MIMEKIT + throw new System.NotSupportedException("The MultiPartMatcher can not be used for .NETStandard1.3 or .NET Framework 4.6.1 or lower."); +#else + var match = MatchScores.Mismatch; + + if (Matchers?.Any() != true) + { + return requestMatchResult.AddScore(GetType(), match); + } + + try + { + var message = MimeKitUtils.GetMimeMessage(requestMessage.BodyData!, requestMessage.Headers![HttpKnownHeaderNames.ContentType].ToString()); + + var mimePartMatchers = Matchers.OfType().ToArray(); + + foreach (var mimePart in message.BodyParts.OfType()) + { + var matchesForMimePart = new List { MatchScores.Mismatch }; + matchesForMimePart.AddRange(mimePartMatchers.Select(matcher => matcher.IsMatch(mimePart))); + + match = matchesForMimePart.Max(); + + if (MatchScores.IsPerfect(match)) + { + if (MatchOperator == MatchOperator.Or) + { + break; + } + } + else + { + match = MatchScores.Mismatch; + break; + } + } + } + catch + { + // Empty + } + + return requestMatchResult.AddScore(GetType(), match); +#endif + } +} \ 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 d3848e53..c49dc785 100644 --- a/src/WireMock.Net/Owin/Mappers/OwinRequestMapper.cs +++ b/src/WireMock.Net/Owin/Mappers/OwinRequestMapper.cs @@ -29,17 +29,13 @@ namespace WireMock.Owin.Mappers var headers = new Dictionary(); IEnumerable? contentEncodingHeader = null; - if (request.Headers.Any()) + foreach (var header in request.Headers) { - headers = new Dictionary(); - foreach (var header in request.Headers) - { - headers.Add(header.Key, header.Value); + headers.Add(header.Key, header.Value!); - if (string.Equals(header.Key, HttpKnownHeaderNames.ContentEncoding, StringComparison.OrdinalIgnoreCase)) - { - contentEncodingHeader = header.Value; - } + if (string.Equals(header.Key, HttpKnownHeaderNames.ContentEncoding, StringComparison.OrdinalIgnoreCase)) + { + contentEncodingHeader = header.Value; } } diff --git a/src/WireMock.Net/RequestBuilders/IGraphQLRequestBuilder.cs b/src/WireMock.Net/RequestBuilders/IGraphQLRequestBuilder.cs index a90b78ae..6d23d2dd 100644 --- a/src/WireMock.Net/RequestBuilders/IGraphQLRequestBuilder.cs +++ b/src/WireMock.Net/RequestBuilders/IGraphQLRequestBuilder.cs @@ -1,12 +1,11 @@ using WireMock.Matchers; -using WireMock.Matchers.Request; namespace WireMock.RequestBuilders; /// /// The GraphQLRequestBuilder interface. /// -public interface IGraphQLRequestBuilder : IRequestMatcher +public interface IGraphQLRequestBuilder : IMultiPartRequestBuilder { /// /// WithGraphQLSchema: The GraphQL schema as a string. diff --git a/src/WireMock.Net/RequestBuilders/IMethodRequestBuilder.cs b/src/WireMock.Net/RequestBuilders/IMethodRequestBuilder.cs index 59dd1675..7e21a6d8 100644 --- a/src/WireMock.Net/RequestBuilders/IMethodRequestBuilder.cs +++ b/src/WireMock.Net/RequestBuilders/IMethodRequestBuilder.cs @@ -1,5 +1,3 @@ -using System; -using JetBrains.Annotations; using WireMock.Matchers; namespace WireMock.RequestBuilders; diff --git a/src/WireMock.Net/RequestBuilders/IMultiPartRequestBuilder.cs b/src/WireMock.Net/RequestBuilders/IMultiPartRequestBuilder.cs new file mode 100644 index 00000000..75a07be6 --- /dev/null +++ b/src/WireMock.Net/RequestBuilders/IMultiPartRequestBuilder.cs @@ -0,0 +1,34 @@ +using WireMock.Matchers; +using WireMock.Matchers.Request; + +namespace WireMock.RequestBuilders; + +/// +/// The MultiPartRequestBuilder interface. +/// +public interface IMultiPartRequestBuilder : IRequestMatcher +{ + /// + /// WithMultiPart: IMatcher + /// + /// The matcher. + /// The . + IRequestBuilder WithMultiPart(IMatcher matcher); + + /// + /// WithMultiPart: IMatcher[], MatchBehaviour and MatchOperator + /// + /// The matchers. + /// The to use. + /// The to use. + /// The . + IRequestBuilder WithMultiPart(IMatcher[] matchers, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch, MatchOperator matchOperator = MatchOperator.Or); + + /// + /// WithMultiPart: MatchBehaviour and IMatcher[] + /// + /// The to use. + /// The matchers. + /// The . + IRequestBuilder WithMultiPart(MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch, params IMatcher[] matchers); +} \ No newline at end of file diff --git a/src/WireMock.Net/RequestBuilders/Request.WithMultiPart.cs b/src/WireMock.Net/RequestBuilders/Request.WithMultiPart.cs new file mode 100644 index 00000000..64011c30 --- /dev/null +++ b/src/WireMock.Net/RequestBuilders/Request.WithMultiPart.cs @@ -0,0 +1,28 @@ +using WireMock.Matchers; +using WireMock.Matchers.Request; + +namespace WireMock.RequestBuilders; + +public partial class Request +{ + /// + public IRequestBuilder WithMultiPart(IMatcher matcher) + { + _requestMatchers.Add(new RequestMessageMultiPartMatcher(matcher)); + return this; + } + + /// + public IRequestBuilder WithMultiPart(IMatcher[] matchers, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch, MatchOperator matchOperator = MatchOperator.Or) + { + _requestMatchers.Add(new RequestMessageMultiPartMatcher(matchBehaviour, matchOperator, matchers)); + return this; + } + + /// + public IRequestBuilder WithMultiPart(MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch, params IMatcher[] matchers) + { + _requestMatchers.Add(new RequestMessageMultiPartMatcher(matchBehaviour, MatchOperator.Or, matchers)); + return this; + } +} \ No newline at end of file diff --git a/src/WireMock.Net/RequestMessage.cs b/src/WireMock.Net/RequestMessage.cs index d250b0c2..069d7801 100644 --- a/src/WireMock.Net/RequestMessage.cs +++ b/src/WireMock.Net/RequestMessage.cs @@ -8,6 +8,7 @@ using System.Net; using System.Security.Cryptography.X509Certificates; #endif using Stef.Validation; +using WireMock.Http; using WireMock.Models; using WireMock.Owin; using WireMock.Types; @@ -20,40 +21,40 @@ namespace WireMock; /// public class RequestMessage : IRequestMessage { - /// + /// public string ClientIP { get; } - /// + /// public string Url { get; } - /// + /// public string AbsoluteUrl { get; } - /// + /// public string? ProxyUrl { get; set; } - /// + /// public DateTime DateTime { get; set; } - /// + /// public string Path { get; } - /// + /// public string AbsolutePath { get; } - /// + /// public string[] PathSegments { get; } - /// + /// public string[] AbsolutePathSegments { get; } - /// + /// public string Method { get; } - /// + /// public IDictionary>? Headers { get; } - /// + /// public IDictionary? Cookies { get; } /// @@ -61,45 +62,50 @@ public class RequestMessage : IRequestMessage /// public IDictionary>? QueryIgnoreCase { get; } - - /// + + /// public string RawQuery { get; } - /// + /// public IBodyData? BodyData { get; } - /// - public string Body { get; } + /// + public string? Body { get; } - /// - public object BodyAsJson { get; } + /// + public object? BodyAsJson { get; } - /// - public byte[] BodyAsBytes { get; } + /// + public byte[]? BodyAsBytes { get; } - /// - public string DetectedBodyType { get; } +#if MIMEKIT + /// + public object? BodyAsMimeMessage { get; } +#endif - /// - public string DetectedBodyTypeFromContentType { get; } + /// + public string? DetectedBodyType { get; } - /// - public string DetectedCompression { get; } + /// + public string? DetectedBodyTypeFromContentType { get; } - /// + /// + public string? DetectedCompression { get; } + + /// public string Host { get; } - /// + /// public string Protocol { get; } - /// + /// public int Port { get; } - /// + /// public string Origin { get; } #if USE_ASPNETCORE - /// + /// public X509Certificate2? ClientCertificate { get; } #endif @@ -139,11 +145,11 @@ public class RequestMessage : IRequestMessage #if USE_ASPNETCORE , X509Certificate2? clientCertificate = null #endif - ) + ) { - Guard.NotNull(urlDetails, nameof(urlDetails)); - Guard.NotNull(method, nameof(method)); - Guard.NotNull(clientIP, nameof(clientIP)); + Guard.NotNull(urlDetails); + Guard.NotNull(method); + Guard.NotNull(clientIP); AbsoluteUrl = urlDetails.AbsoluteUrl.ToString(); Url = urlDetails.Url.ToString(); @@ -166,10 +172,22 @@ public class RequestMessage : IRequestMessage Body = BodyData?.BodyAsString; BodyAsJson = BodyData?.BodyAsJson; BodyAsBytes = BodyData?.BodyAsBytes; + DetectedBodyType = BodyData?.DetectedBodyType.ToString(); DetectedBodyTypeFromContentType = BodyData?.DetectedBodyTypeFromContentType.ToString(); DetectedCompression = BodyData?.DetectedCompression; +#if MIMEKIT + try + { + BodyAsMimeMessage = MimeKitUtils.GetMimeMessage(BodyData, headers![HttpKnownHeaderNames.ContentType].First()); + } + catch + { + // Ignore exception from MimeMessage.Load + } +#endif + Headers = headers?.ToDictionary(header => header.Key, header => new WireMockList(header.Value)); Cookies = cookies; RawQuery = urlDetails.Url.Query; diff --git a/src/WireMock.Net/Serialization/MappingConverter.cs b/src/WireMock.Net/Serialization/MappingConverter.cs index 95a346ee..3a356b4c 100644 --- a/src/WireMock.Net/Serialization/MappingConverter.cs +++ b/src/WireMock.Net/Serialization/MappingConverter.cs @@ -19,6 +19,7 @@ using WireMock.Types; using WireMock.Util; using static WireMock.Util.CSharpFormatter; + namespace WireMock.Serialization; internal class MappingConverter @@ -48,6 +49,7 @@ internal class MappingConverter var methodMatcher = request.GetRequestMessageMatcher(); var requestMessageBodyMatcher = request.GetRequestMessageMatcher(); var requestMessageGraphQLMatcher = request.GetRequestMessageMatcher(); + var requestMessageMultiPartMatcher = request.GetRequestMessageMatcher(); var sb = new StringBuilder(); @@ -114,8 +116,18 @@ internal class MappingConverter sb.AppendLine($" .WithGraphQLSchema({GetString(graphQLMatcher)})"); } } - else #endif + +#if MIMEKIT + if (requestMessageMultiPartMatcher is { Matchers: { } }) + { + if (requestMessageMultiPartMatcher.Matchers.OfType().Any()) + { + sb.AppendLine(" // .WithMultiPart() is not yet supported"); + } + } +#endif + if (requestMessageBodyMatcher is { Matchers: { } }) { if (requestMessageBodyMatcher.Matchers.OfType().FirstOrDefault() is { } wildcardMatcher && wildcardMatcher.GetPatterns().Any()) @@ -185,7 +197,7 @@ internal class MappingConverter { sb.AppendLine($" .WithBody({ToCSharpStringLiteral(bodyStringValue)})"); } - else if(bodyData.BodyAsJson is {} jsonBody) + else if (bodyData.BodyAsJson is { } jsonBody) { var anonymousObjectDefinition = ConvertToAnonymousObjectDefinition(jsonBody); sb.AppendLine($" .WithBodyAsJson({anonymousObjectDefinition})"); @@ -228,6 +240,7 @@ internal class MappingConverter var methodMatcher = request.GetRequestMessageMatcher(); var bodyMatcher = request.GetRequestMessageMatcher(); var graphQLMatcher = request.GetRequestMessageMatcher(); + var multiPartMatcher = request.GetRequestMessageMatcher(); var mappingModel = new MappingModel { @@ -323,19 +336,20 @@ internal class MappingConverter mappingModel.Webhooks = mapping.Webhooks.Select(WebhookMapper.Map).ToArray(); } - var graphQLOrBodyMatchers = graphQLMatcher?.Matchers ?? bodyMatcher?.Matchers; - var matchOperator = graphQLMatcher?.MatchOperator ?? bodyMatcher?.MatchOperator; - if (graphQLOrBodyMatchers != null && matchOperator != null) + var bodyMatchers = multiPartMatcher?.Matchers ?? graphQLMatcher?.Matchers ?? bodyMatcher?.Matchers; + var matchOperator = multiPartMatcher?.MatchOperator ?? graphQLMatcher?.MatchOperator ?? bodyMatcher?.MatchOperator; + + if (bodyMatchers != null && matchOperator != null) { mappingModel.Request.Body = new BodyModel(); - if (graphQLOrBodyMatchers.Length == 1) + if (bodyMatchers.Length == 1) { - mappingModel.Request.Body.Matcher = _mapper.Map(graphQLOrBodyMatchers[0]); + mappingModel.Request.Body.Matcher = _mapper.Map(bodyMatchers[0]); } - else if (graphQLOrBodyMatchers.Length > 1) + else if (bodyMatchers.Length > 1) { - mappingModel.Request.Body.Matchers = _mapper.Map(graphQLOrBodyMatchers); + mappingModel.Request.Body.Matchers = _mapper.Map(bodyMatchers); mappingModel.Request.Body.MatchOperator = matchOperator.ToString(); } } @@ -520,6 +534,4 @@ internal class MappingConverter return newDictionary; } - - } \ No newline at end of file diff --git a/src/WireMock.Net/Serialization/MatcherMapper.cs b/src/WireMock.Net/Serialization/MatcherMapper.cs index ffd2cb44..3ed1bc2a 100644 --- a/src/WireMock.Net/Serialization/MatcherMapper.cs +++ b/src/WireMock.Net/Serialization/MatcherMapper.cs @@ -75,6 +75,11 @@ internal class MatcherMapper case nameof(GraphQLMatcher): return new GraphQLMatcher(stringPatterns[0].GetPattern(), matchBehaviour, throwExceptionWhenMatcherFails, matchOperator); #endif + +#if MIMEKIT + case nameof(MimePartMatcher): + return CreateMimePartMatcher(matchBehaviour, matcher, throwExceptionWhenMatcherFails); +#endif case nameof(RegexMatcher): return new RegexMatcher(matchBehaviour, stringPatterns, ignoreCase, throwExceptionWhenMatcherFails, useRegexExtended, matchOperator); @@ -126,12 +131,7 @@ internal class MatcherMapper public MatcherModel[]? Map(IEnumerable? matchers) { - if (matchers == null) - { - return null; - } - - return matchers.Where(m => m != null).Select(Map).ToArray()!; + return matchers?.Where(m => m != null).Select(Map).ToArray(); } public MatcherModel? Map(IMatcher? matcher) @@ -195,6 +195,15 @@ internal class MatcherMapper case ExactObjectMatcher exactObjectMatcher: model.Pattern = exactObjectMatcher.ValueAsObject ?? exactObjectMatcher.ValueAsBytes; break; + +#if MIMEKIT + case MimePartMatcher mimePartMatcher: + model.ContentDispositionMatcher = Map(mimePartMatcher.ContentDispositionMatcher); + model.ContentMatcher = Map(mimePartMatcher.ContentMatcher); + model.ContentTransferEncodingMatcher = Map(mimePartMatcher.ContentTransferEncodingMatcher); + model.ContentTypeMatcher = Map(mimePartMatcher.ContentTypeMatcher); + break; +#endif } return model; @@ -224,7 +233,7 @@ internal class MatcherMapper return new[] { new AnyOf(new StringPattern { Pattern = pattern, PatternAsFile = patternAsFile }) }; } - return new AnyOf[0]; + return EmptyArray>.Value; } private static ExactObjectMatcher CreateExactObjectMatcher(MatchBehaviour matchBehaviour, AnyOf stringPattern, bool throwException) @@ -241,4 +250,16 @@ internal class MatcherMapper return new ExactObjectMatcher(matchBehaviour, bytePattern, throwException); } + +#if MIMEKIT + private MimePartMatcher CreateMimePartMatcher(MatchBehaviour matchBehaviour, MatcherModel? matcher, bool throwExceptionWhenMatcherFails) + { + var contentTypeMatcher = Map(matcher?.ContentTypeMatcher) as IStringMatcher; + var contentDispositionMatcher = Map(matcher?.ContentDispositionMatcher) as IStringMatcher; + var contentTransferEncodingMatcher = Map(matcher?.ContentTransferEncodingMatcher) as IStringMatcher; + var contentMatcher = Map(matcher?.ContentMatcher); + + return new MimePartMatcher(matchBehaviour, contentTypeMatcher, contentDispositionMatcher, contentTransferEncodingMatcher, contentMatcher, throwExceptionWhenMatcherFails); + } +#endif } \ No newline at end of file diff --git a/src/WireMock.Net/Util/MimeKitUtils.cs b/src/WireMock.Net/Util/MimeKitUtils.cs new file mode 100644 index 00000000..ca62465c --- /dev/null +++ b/src/WireMock.Net/Util/MimeKitUtils.cs @@ -0,0 +1,42 @@ +#if MIMEKIT +using System; +using System.IO; +using System.Text; +using MimeKit; +using WireMock.Http; +using WireMock.Types; + +namespace WireMock.Util; + +internal static class MimeKitUtils +{ + public static MimeMessage GetMimeMessage(IBodyData? bodyData, string contentTypeHeaderValue) + { + var bytes = bodyData?.DetectedBodyType switch + { + // If the body is bytes, use the BodyAsBytes to match on. + BodyType.Bytes => bodyData.BodyAsBytes!, + + // If the body is a String or MultiPart, use the BodyAsString to match on. + BodyType.String or BodyType.MultiPart => Encoding.UTF8.GetBytes(bodyData.BodyAsString!), + + _ => throw new NotSupportedException() + }; + + var fixedBytes = FixBytes(bytes, contentTypeHeaderValue); + return MimeMessage.Load(new MemoryStream(fixedBytes)); + } + + private static byte[] FixBytes(byte[] bytes, WireMockList contentType) + { + var contentTypeBytes = Encoding.UTF8.GetBytes($"{HttpKnownHeaderNames.ContentType}: {contentType}\r\n\r\n"); + + var result = new byte[contentTypeBytes.Length + bytes.Length]; + + Buffer.BlockCopy(contentTypeBytes, 0, result, 0, contentTypeBytes.Length); + Buffer.BlockCopy(bytes, 0, result, contentTypeBytes.Length, bytes.Length); + + return result; + } +} +#endif \ No newline at end of file diff --git a/src/WireMock.Net/Util/StreamUtils.cs b/src/WireMock.Net/Util/StreamUtils.cs new file mode 100644 index 00000000..df0b2865 --- /dev/null +++ b/src/WireMock.Net/Util/StreamUtils.cs @@ -0,0 +1,12 @@ +using System.IO; +using System.Text; + +namespace WireMock.Util; + +internal static class StreamUtils +{ + public static Stream CreateStream(string s) + { + return new MemoryStream(Encoding.UTF8.GetBytes(s)); + } +} \ No newline at end of file diff --git a/src/WireMock.Net/WireMock.Net.csproj b/src/WireMock.Net/WireMock.Net.csproj index 46f0e268..1e581877 100644 --- a/src/WireMock.Net/WireMock.Net.csproj +++ b/src/WireMock.Net/WireMock.Net.csproj @@ -51,10 +51,11 @@ - $(DefineConstants);GRAPHQL + $(DefineConstants);GRAPHQL;MIMEKIT + @@ -160,6 +161,7 @@ + diff --git a/test/WireMock.Net.Tests/Matchers/MimePartMatcherTests.cs b/test/WireMock.Net.Tests/Matchers/MimePartMatcherTests.cs new file mode 100644 index 00000000..467e72f0 --- /dev/null +++ b/test/WireMock.Net.Tests/Matchers/MimePartMatcherTests.cs @@ -0,0 +1,99 @@ +#if MIMEKIT +using System; +using System.Linq; +using FluentAssertions; +using MimeKit; +using WireMock.Matchers; +using WireMock.Util; +using Xunit; + +namespace WireMock.Net.Tests.Matchers; + +public class MimePartMatcherTests +{ + private const string TestMultiPart = @"From: +Date: Sun, 23 Jul 2023 16:13:13 +0200 +Subject: +Message-Id: +MIME-Version: 1.0 +Content-Type: multipart/mixed; boundary=""=-5XgmpXt0XOfzdtcgNJc2ZQ=="" + +--=-5XgmpXt0XOfzdtcgNJc2ZQ== +Content-Type: text/plain; charset=utf-8 + +This is some plain text +--=-5XgmpXt0XOfzdtcgNJc2ZQ== +Content-Type: text/json; charset=utf-8 + +{ + ""Key"": ""Value"" + } +--=-5XgmpXt0XOfzdtcgNJc2ZQ== +Content-Type: image/png; name=image.png +Content-Disposition: attachment; filename=image.png +Content-Transfer-Encoding: base64 + +iVBORw0KGgoAAAANSUhEUgAAAAIAAAACAgMAAAAP2OW3AAAADFBMVEX/tID/vpH/pWX/sHidUyjl +AAAADElEQVR4XmMQYNgAAADkAMHebX3mAAAAAElFTkSuQmCC + +--=-5XgmpXt0XOfzdtcgNJc2ZQ==-- +"; + + [Fact] + public void MimePartMatcher_IsMatch_Part_TextPlain() + { + // Arrange + var message = MimeMessage.Load(StreamUtils.CreateStream(TestMultiPart)); + var part = (MimePart)message.BodyParts.ToArray()[0]; + + // Act + var contentTypeMatcher = new ContentTypeMatcher("text/plain"); + var contentMatcher = new ExactMatcher("This is some plain text"); + + var matcher = new MimePartMatcher(MatchBehaviour.AcceptOnMatch, contentTypeMatcher, null, null, contentMatcher); + var result = matcher.IsMatch(part); + + // Assert + matcher.Name.Should().Be("MimePartMatcher"); + result.Should().Be(MatchScores.Perfect); + } + + [Fact] + public void MimePartMatcher_IsMatch_Part_TextJson() + { + // Arrange + var message = MimeMessage.Load(StreamUtils.CreateStream(TestMultiPart)); + var part = (MimePart)message.BodyParts.ToArray()[1]; + + // Act + var contentTypeMatcher = new ContentTypeMatcher("text/json"); + var contentMatcher = new JsonMatcher(new { Key = "Value" }, true); + + var matcher = new MimePartMatcher(MatchBehaviour.AcceptOnMatch, contentTypeMatcher, null, null, contentMatcher); + var result = matcher.IsMatch(part); + + // Assert + result.Should().Be(MatchScores.Perfect); + } + + [Fact] + public void MimePartMatcher_IsMatch_Part_ImagePng() + { + // Arrange + var message = MimeMessage.Load(StreamUtils.CreateStream(TestMultiPart)); + var part = (MimePart)message.BodyParts.ToArray()[2]; + + // Act + var contentTypeMatcher = new ContentTypeMatcher("image/png"); + var contentDispositionMatcher = new ExactMatcher("attachment; filename=\"image.png\""); + var contentTransferEncodingMatcher = new ExactMatcher("base64"); + var contentMatcher = new ExactObjectMatcher(Convert.FromBase64String("iVBORw0KGgoAAAANSUhEUgAAAAIAAAACAgMAAAAP2OW3AAAADFBMVEX/tID/vpH/pWX/sHidUyjlAAAADElEQVR4XmMQYNgAAADkAMHebX3mAAAAAElFTkSuQmCC")); + + var matcher = new MimePartMatcher(MatchBehaviour.AcceptOnMatch, contentTypeMatcher, contentDispositionMatcher, contentTransferEncodingMatcher, contentMatcher); + var result = matcher.IsMatch(part); + + // Assert + result.Should().Be(MatchScores.Perfect); + } +} +#endif \ No newline at end of file diff --git a/test/WireMock.Net.Tests/RequestTests.cs b/test/WireMock.Net.Tests/RequestBuilders/RequestBuilderTests.cs similarity index 99% rename from test/WireMock.Net.Tests/RequestTests.cs rename to test/WireMock.Net.Tests/RequestBuilders/RequestBuilderTests.cs index 00e604c7..73094505 100644 --- a/test/WireMock.Net.Tests/RequestTests.cs +++ b/test/WireMock.Net.Tests/RequestBuilders/RequestBuilderTests.cs @@ -8,9 +8,9 @@ using WireMock.Types; using WireMock.Util; using Xunit; -namespace WireMock.Net.Tests; +namespace WireMock.Net.Tests.RequestBuilders; -public class RequestTests +public class RequestBuilderTests { private const string ClientIp = "::1"; diff --git a/test/WireMock.Net.Tests/RequestBuilders/RequestBuilderWithBodyTests.cs b/test/WireMock.Net.Tests/RequestBuilders/RequestBuilderWithBodyTests.cs index d4a7c704..e12014af 100644 --- a/test/WireMock.Net.Tests/RequestBuilders/RequestBuilderWithBodyTests.cs +++ b/test/WireMock.Net.Tests/RequestBuilders/RequestBuilderWithBodyTests.cs @@ -1,17 +1,28 @@ +using System; using FluentAssertions; using System.Collections.Generic; using System.Linq; +using System.Text; +using JsonConverter.Abstractions; +using Moq; +using Newtonsoft.Json; +using NFluent; using WireMock.Matchers; using WireMock.Matchers.Request; +using WireMock.Models; using WireMock.RequestBuilders; +using WireMock.Types; +using WireMock.Util; using Xunit; namespace WireMock.Net.Tests.RequestBuilders; public class RequestBuilderWithBodyTests { + private const string ClientIp = "::1"; + [Fact] - public void RequestBuilder_WithBody_IMatcher() + public void Request_WithBody_IMatcher() { // Assign var matcher = new WildcardMatcher("x"); @@ -26,7 +37,7 @@ public class RequestBuilderWithBodyTests } [Fact] - public void RequestBuilder_WithBody_IMatchers() + public void Request_WithBody_IMatchers() { // Assign var matcher1 = new WildcardMatcher("x"); @@ -40,4 +51,409 @@ public class RequestBuilderWithBodyTests matchers.Should().HaveCount(1); ((RequestMessageBodyMatcher)matchers[0]).Matchers.Should().Contain(new[] { matcher1, matcher2 }); } + + [Fact] + public void Request_WithBody_FuncString() + { + // Assign + var requestBuilder = Request.Create().UsingAnyMethod().WithBody(b => b != null && b.Contains("b")); + + // Act + var body = new BodyData + { + BodyAsString = "b", + DetectedBodyType = BodyType.String + }; + var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "POST", ClientIp, body); + + // Assert + var requestMatchResult = new RequestMatchResult(); + Check.That(requestBuilder.GetMatchingScore(request, requestMatchResult)).IsEqualTo(1.0); + } + + [Fact] + public void Request_WithBody_FuncJson() + { + // Assign + var requestBuilder = Request.Create().UsingAnyMethod().WithBody(b => b != null); + + // Act + var body = new BodyData + { + BodyAsJson = 123, + DetectedBodyType = BodyType.Json + }; + var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "POST", ClientIp, body); + + // Assert + var requestMatchResult = new RequestMatchResult(); + Check.That(requestBuilder.GetMatchingScore(request, requestMatchResult)).IsEqualTo(1.0); + } + + [Fact] + public void Request_WithBody_FuncFormUrlEncoded() + { + // Assign + var requestBuilder = Request.Create().UsingAnyMethod().WithBody((IDictionary? values) => values != null); + + // Act + var body = new BodyData + { + BodyAsFormUrlEncoded = new Dictionary(), + DetectedBodyTypeFromContentType = BodyType.FormUrlEncoded, + DetectedBodyType = BodyType.FormUrlEncoded + }; + var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "POST", ClientIp, body); + + // Assert + var requestMatchResult = new RequestMatchResult(); + Check.That(requestBuilder.GetMatchingScore(request, requestMatchResult)).IsEqualTo(1.0); + } + + [Fact] + public void Request_WithBody_FuncBodyData() + { + // Assign + var requestBuilder = Request.Create().UsingAnyMethod().WithBody((IBodyData? b) => b != null); + + // Act + var body = new BodyData + { + BodyAsJson = 123, + DetectedBodyType = BodyType.Json + }; + var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "POST", ClientIp, body); + + // Assert + var requestMatchResult = new RequestMatchResult(); + Check.That(requestBuilder.GetMatchingScore(request, requestMatchResult)).IsEqualTo(1.0); + } + + [Fact] + public void Request_WithBody_FuncByteArray() + { + // Assign + var requestBuilder = Request.Create().UsingAnyMethod().WithBody((byte[]? b) => b != null); + + // Act + var body = new BodyData + { + BodyAsBytes = new byte[0], + DetectedBodyType = BodyType.Bytes + }; + var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "POST", ClientIp, body); + + // Assert + var requestMatchResult = new RequestMatchResult(); + Check.That(requestBuilder.GetMatchingScore(request, requestMatchResult)).IsEqualTo(1.0); + } + + [Fact] + public void Request_WithBodyExactMatcher() + { + // Arrange + var requestBuilder = Request.Create().UsingAnyMethod().WithBody(new ExactMatcher("cat")); + + // Act + var body = new BodyData + { + BodyAsString = "cat", + DetectedBodyType = BodyType.String + }; + var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "POST", ClientIp, body); + + // Assert + var requestMatchResult = new RequestMatchResult(); + Check.That(requestBuilder.GetMatchingScore(request, requestMatchResult)).IsEqualTo(1.0); + } + + [Fact] + public void Request_WithBody_BodyDataAsString_Using_WildcardMatcher() + { + // Arrange + var spec = Request.Create().WithPath("/foo").UsingAnyMethod().WithBody(new WildcardMatcher("H*o*")); + + // Act + var body = new BodyData + { + BodyAsString = "Hello world!", + DetectedBodyType = BodyType.String + }; + var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "PUT", ClientIp, body); + + // Assert + var requestMatchResult = new RequestMatchResult(); + spec.GetMatchingScore(request, requestMatchResult).Should().Be(1.0); + } + + [Fact] + public void Request_WithBody_BodyDataAsJson_Using_WildcardMatcher() + { + // Arrange + var spec = Request.Create().WithPath("/foo").UsingAnyMethod().WithBody(new WildcardMatcher("*Hello*")); + + // Act + var body = new BodyData + { + BodyAsJson = new { Hi = "Hello world!" }, + BodyAsString = "{ Hi = \"Hello world!\" }", + DetectedBodyType = BodyType.Json + }; + var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "PUT", ClientIp, body); + + // Assert + var requestMatchResult = new RequestMatchResult(); + spec.GetMatchingScore(request, requestMatchResult).Should().Be(1.0); + } + + [Fact] + public void Request_WithBody_XPathMatcher_true() + { + // Arrange + var spec = Request.Create().UsingAnyMethod().WithBody(new XPathMatcher("/todo-list[count(todo-item) = 3]")); + + // Act + var body = new BodyData + { + BodyAsString = @" + + abc + def + xyz + ", + DetectedBodyType = BodyType.String + }; + var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "PUT", ClientIp, body); + + // Assert + var requestMatchResult = new RequestMatchResult(); + Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsEqualTo(1.0); + } + + [Fact] + public void Request_WithBody_XPathMatcher_false() + { + // Arrange + var spec = Request.Create().UsingAnyMethod().WithBody(new XPathMatcher("/todo-list[count(todo-item) = 99]")); + + // Act + var body = new BodyData + { + BodyAsString = @" + + abc + def + xyz + ", + DetectedBodyType = BodyType.String + }; + var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "PUT", ClientIp, body); + + // Assert + var requestMatchResult = new RequestMatchResult(); + Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsNotEqualTo(1.0); + } + + [Fact] + public void Request_WithBody_JsonPathMatcher_true() + { + // Arrange + var spec = Request.Create().UsingAnyMethod().WithBody(new JsonPathMatcher("$..things[?(@.name == 'RequiredThing')]")); + + // Act + var body = new BodyData + { + BodyAsString = "{ \"things\": [ { \"name\": \"RequiredThing\" }, { \"name\": \"Wiremock\" } ] }", + DetectedBodyType = BodyType.String + }; + var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "PUT", ClientIp, body); + + // Assert + var requestMatchResult = new RequestMatchResult(); + Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsEqualTo(1.0); + } + + [Fact] + public void Request_WithBodyJson_PathMatcher_false() + { + // Arrange + var spec = Request.Create().UsingAnyMethod().WithBody(new JsonPathMatcher("$.things[?(@.name == 'RequiredThing')]")); + + // Act + var body = new BodyData + { + BodyAsString = "{ \"things\": { \"name\": \"Wiremock\" } }", + DetectedBodyType = BodyType.String + }; + var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "PUT", ClientIp, body); + + // Assert + var requestMatchResult = new RequestMatchResult(); + Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsNotEqualTo(1.0); + } + + [Fact] + public void Request_WithBodyAsJson_Object_JsonPathMatcher_true() + { + // Arrange + var spec = Request.Create().UsingAnyMethod().WithBody(new JsonPathMatcher("$..things[?(@.name == 'RequiredThing')]")); + + // Act + string jsonString = "{ \"things\": [ { \"name\": \"RequiredThing\" }, { \"name\": \"Wiremock\" } ] }"; + var bodyData = new BodyData + { + BodyAsJson = JsonConvert.DeserializeObject(jsonString), + BodyAsString = jsonString, + Encoding = Encoding.UTF8, + DetectedBodyType = BodyType.Json + }; + + var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "PUT", ClientIp, bodyData); + + // Assert + var requestMatchResult = new RequestMatchResult(); + Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsEqualTo(1.0); + } + + [Fact] + public void Request_WithBodyAsJson_Array_JsonPathMatcher_1() + { + // Arrange + var spec = Request.Create().UsingAnyMethod().WithBody(new JsonPathMatcher("$..books[?(@.price < 10)]")); + + // Act + string jsonString = "{ \"books\": [ { \"category\": \"test1\", \"price\": 8.95 }, { \"category\": \"test2\", \"price\": 20 } ] }"; + var bodyData = new BodyData + { + BodyAsJson = JsonConvert.DeserializeObject(jsonString), + BodyAsString = jsonString, + Encoding = Encoding.UTF8, + DetectedBodyType = BodyType.Json + }; + + var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "PUT", ClientIp, bodyData); + + // Assert + var requestMatchResult = new RequestMatchResult(); + Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsEqualTo(1.0); + } + + [Fact] + public void Request_WithBodyAsJson_Array_JsonPathMatcher_2() + { + // Arrange + var spec = Request.Create().UsingAnyMethod().WithBody(new JsonPathMatcher("$..[?(@.Id == 1)]")); + + // Act + string jsonString = "{ \"Id\": 1, \"Name\": \"Test\" }"; + var bodyData = new BodyData + { + BodyAsJson = JsonConvert.DeserializeObject(jsonString), + BodyAsString = jsonString, + Encoding = Encoding.UTF8, + DetectedBodyType = BodyType.Json + }; + + var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "PUT", ClientIp, bodyData); + + // Assert + var requestMatchResult = new RequestMatchResult(); + double result = spec.GetMatchingScore(request, requestMatchResult); + Check.That(result).IsEqualTo(1.0); + } + + [Fact] + public void Request_WithBodyAsObject_ExactObjectMatcher_true() + { + // Assign + object body = DateTime.MinValue; + var requestBuilder = Request.Create().UsingAnyMethod().WithBody(body); + + var bodyData = new BodyData + { + BodyAsJson = DateTime.MinValue, + DetectedBodyType = BodyType.Json + }; + + // Act + var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "PUT", ClientIp, bodyData); + + // Assert + var requestMatchResult = new RequestMatchResult(); + Check.That(requestBuilder.GetMatchingScore(request, requestMatchResult)).IsEqualTo(1.0); + } + + [Fact] + public void Request_WithBodyAsJson_UsingObject() + { + // Assign + object body = new + { + Test = "abc" + }; + var requestBuilder = Request.Create().UsingAnyMethod().WithBodyAsJson(body); + + var bodyData = new BodyData + { + BodyAsString = JsonConvert.SerializeObject(body), + DetectedBodyType = BodyType.String + }; + + // Act + var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "POST", ClientIp, bodyData); + + // Assert + var requestMatchResult = new RequestMatchResult(); + Check.That(requestBuilder.GetMatchingScore(request, requestMatchResult)).IsEqualTo(1.0); + } + + [Fact] + public void Request_WithBodyAsJson_WithIJsonConverter_UsingObject() + { + // Assign + var jsonConverterMock = new Mock(); + jsonConverterMock.Setup(j => j.Serialize(It.IsAny(), It.IsAny())).Returns("test"); + object body = new + { + Any = "key" + }; + var requestBuilder = Request.Create().UsingAnyMethod().WithBodyAsJson(body, jsonConverterMock.Object); + + var bodyData = new BodyData + { + BodyAsString = "test", + DetectedBodyType = BodyType.String + }; + + // Act + var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "POST", ClientIp, bodyData); + + // Assert + var requestMatchResult = new RequestMatchResult(); + Check.That(requestBuilder.GetMatchingScore(request, requestMatchResult)).IsEqualTo(1.0); + } + + [Theory] + [InlineData(new byte[] { 1 }, BodyType.Bytes)] + [InlineData(new byte[] { 48, 49, 50 }, BodyType.Bytes)] + [InlineData(new byte[] { 48, 49, 50 }, BodyType.String)] + public void Request_WithBodyAsBytes_ExactObjectMatcher_true(byte[] bytes, BodyType detectedBodyType) + { + // Assign + byte[] body = bytes; + var requestBuilder = Request.Create().UsingAnyMethod().WithBody(body); + + var bodyData = new BodyData + { + BodyAsBytes = bytes, + DetectedBodyType = detectedBodyType + }; + + // Act + var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "PUT", ClientIp, bodyData); + + // Assert + var requestMatchResult = new RequestMatchResult(); + requestBuilder.GetMatchingScore(request, requestMatchResult).Should().Be(1.0); + } } \ No newline at end of file diff --git a/test/WireMock.Net.Tests/RequestBuilders/RequestBuilderWithClientIPTests.cs b/test/WireMock.Net.Tests/RequestBuilders/RequestBuilderWithClientIPTests.cs new file mode 100644 index 00000000..3cf66657 --- /dev/null +++ b/test/WireMock.Net.Tests/RequestBuilders/RequestBuilderWithClientIPTests.cs @@ -0,0 +1,67 @@ +using NFluent; +using WireMock.Matchers; +using WireMock.Matchers.Request; +using WireMock.Models; +using WireMock.RequestBuilders; +using Xunit; + +namespace WireMock.Net.Tests.RequestBuilders; + +public class RequestBuilderWithClientIPTests +{ + [Fact] + public void Request_WithClientIP_Match_Ok() + { + // given + var spec = Request.Create().WithClientIP("127.0.0.2", "1.1.1.1"); + + // when + var request = new RequestMessage(new UrlDetails("http://localhost"), "GET", "127.0.0.2"); + + // then + var requestMatchResult = new RequestMatchResult(); + Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsEqualTo(1.0); + } + + [Fact] + public void Request_WithClientIP_Match_Fail() + { + // given + var spec = Request.Create().WithClientIP("127.0.0.2"); + + // when + var request = new RequestMessage(new UrlDetails("http://localhost"), "GET", "192.1.1.1"); + + // then + var requestMatchResult = new RequestMatchResult(); + Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsEqualTo(0.0); + } + + [Fact] + public void Request_WithClientIP_WildcardMatcher() + { + // given + var spec = Request.Create().WithClientIP(new WildcardMatcher("127.0.0.2")); + + // when + var request = new RequestMessage(new UrlDetails("http://localhost"), "GET", "127.0.0.2"); + + // then + var requestMatchResult = new RequestMatchResult(); + Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsEqualTo(1.0); + } + + [Fact] + public void Request_WithClientIP_Func() + { + // given + var spec = Request.Create().WithClientIP(c => c.Contains(".")); + + // when + var request = new RequestMessage(new UrlDetails("http://localhost"), "GET", "127.0.0.2"); + + // then + var requestMatchResult = new RequestMatchResult(); + Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsEqualTo(1.0); + } +} \ No newline at end of file diff --git a/test/WireMock.Net.Tests/RequestBuilders/RequestBuilderWithMultiPartTests.cs b/test/WireMock.Net.Tests/RequestBuilders/RequestBuilderWithMultiPartTests.cs new file mode 100644 index 00000000..aa1d5537 --- /dev/null +++ b/test/WireMock.Net.Tests/RequestBuilders/RequestBuilderWithMultiPartTests.cs @@ -0,0 +1,47 @@ +#if MIMEKIT +using System.Collections.Generic; +using FluentAssertions; +using WireMock.Matchers; +using WireMock.Matchers.Request; +using WireMock.RequestBuilders; +using Xunit; + +namespace WireMock.Net.Tests.RequestBuilders; + +public class RequestBuilderWithMultiPartTests +{ + [Fact] + public void RequestBuilder_WithMultiPart_MimePartMatcher() + { + // Arrange + var matcher = new MimePartMatcher(MatchBehaviour.AcceptOnMatch, null, null, null, null); + + // Act + var requestBuilder = (Request)Request.Create().WithMultiPart(matcher); + + // Assert + var matchers = requestBuilder.GetPrivateFieldValue>("_requestMatchers"); + matchers.Should().HaveCount(1); + ((RequestMessageMultiPartMatcher)matchers[0]).Matchers.Should().HaveCount(1).And.ContainItemsAssignableTo(); + } + + [Fact] + public void RequestBuilder_WithMultiPart_MimePartMatchers() + { + // Arrange + var matcher1 = new MimePartMatcher(MatchBehaviour.AcceptOnMatch, null, null, null, null); + var matcher2 = new MimePartMatcher(MatchBehaviour.AcceptOnMatch, null, null, null, null); + + // Act + var requestBuilder = (Request)Request.Create().WithMultiPart(MatchBehaviour.RejectOnMatch, matcher1, matcher2); + + // Assert + var matchers = requestBuilder.GetPrivateFieldValue>("_requestMatchers"); + matchers.Should().HaveCount(1); + + var x = ((RequestMessageMultiPartMatcher)matchers[0]); + x.MatchBehaviour.Should().Be(MatchBehaviour.RejectOnMatch); + x.Matchers.Should().HaveCount(2).And.ContainItemsAssignableTo(); + } +} +#endif \ No newline at end of file diff --git a/test/WireMock.Net.Tests/RequestBuilders/RequestBuilderWithPathTests.cs b/test/WireMock.Net.Tests/RequestBuilders/RequestBuilderWithPathTests.cs new file mode 100644 index 00000000..2da23c50 --- /dev/null +++ b/test/WireMock.Net.Tests/RequestBuilders/RequestBuilderWithPathTests.cs @@ -0,0 +1,238 @@ +using System.Collections.Generic; +using NFluent; +using WireMock.Matchers; +using Xunit; +using WireMock.RequestBuilders; +using WireMock.Matchers.Request; +using WireMock.Models; +using WireMock.Util; + +namespace WireMock.Net.Tests.RequestBuilders; + +public class RequestBuilderWithPathTests +{ + private const string ClientIp = "::1"; + + [Fact] + public void Request_WithPath_Spaces() + { + // Assign + var spec = Request.Create().WithPath("/path/a b").UsingAnyMethod(); + + // when + var body = new BodyData(); + var request = new RequestMessage(new UrlDetails("http://localhost/path/a b"), "GET", ClientIp, body); + + // then + var requestMatchResult = new RequestMatchResult(); + Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsEqualTo(1.0); + } + + [Fact] + public void Request_WithPath_WithHeader_Match() + { + // given + var spec = Request.Create().WithPath("/foo").UsingAnyMethod().WithHeader("X-toto", "tata"); + + // when + var body = new BodyData + { + BodyAsString = "abc" + }; + var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "PUT", ClientIp, body, new Dictionary { { "X-toto", new[] { "tata" } } }); + + // then + var requestMatchResult = new RequestMatchResult(); + Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsEqualTo(1.0); + } + + [Fact] + public void Request_WithPath() + { + // given + var spec = Request.Create().WithPath("/foo"); + + // when + var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "blabla", ClientIp); + + // then + var requestMatchResult = new RequestMatchResult(); + Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsEqualTo(1.0); + } + + [Fact] + public void Request_WithPaths() + { + var requestBuilder = Request.Create().WithPath("/x1", "/x2"); + + var request1 = new RequestMessage(new UrlDetails("http://localhost/x1"), "blabla", ClientIp); + var request2 = new RequestMessage(new UrlDetails("http://localhost/x2"), "blabla", ClientIp); + + var requestMatchResult = new RequestMatchResult(); + Check.That(requestBuilder.GetMatchingScore(request1, requestMatchResult)).IsEqualTo(1.0); + Check.That(requestBuilder.GetMatchingScore(request2, requestMatchResult)).IsEqualTo(1.0); + } + + [Fact] + public void Request_WithPathFunc() + { + // given + var spec = Request.Create().WithPath(url => url.EndsWith("/foo")); + + // when + var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "blabla", ClientIp); + + // then + var requestMatchResult = new RequestMatchResult(); + Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsEqualTo(1.0); + } + + [Fact] + public void Request_WithPath_RegexMatcher_HasMatch() + { + // given + var spec = Request.Create().WithPath(new RegexMatcher("^/foo")); + + // when + var request = new RequestMessage(new UrlDetails("http://localhost/foo/bar"), "blabla", ClientIp); + + // then + var requestMatchResult = new RequestMatchResult(); + Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsEqualTo(1.0); + } + + [Fact] + public void Request_WithPathRegexMatcher_HasNoMatch() + { + // given + var spec = Request.Create().WithPath("/foo"); + + // when + var request = new RequestMessage(new UrlDetails("http://localhost/bar"), "blabla", ClientIp); + + // then + var requestMatchResult = new RequestMatchResult(); + Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsNotEqualTo(1.0); + } + + [Fact] + public void Request_WithPath_RegexMatcher_WithPatternAsFile_HasMatch() + { + // Arrange + var pattern = new StringPattern + { + Pattern = "^/foo", + PatternAsFile = "c:\\x.txt" + }; + var spec = Request.Create().WithPath(new RegexMatcher(pattern)); + + // when + var request = new RequestMessage(new UrlDetails("http://localhost/foo/bar"), "blabla", ClientIp); + + // then + var requestMatchResult = new RequestMatchResult(); + Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsEqualTo(1.0); + } + + [Fact] + public void Request_WithPath_Should_specify_requests_matching_given_path_and_method_delete() + { + // given + var spec = Request.Create().WithPath("/foo").UsingDelete(); + + // when + var body = new BodyData + { + BodyAsString = "whatever" + }; + var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "Delete", ClientIp, body); + + // then + var requestMatchResult = new RequestMatchResult(); + Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsEqualTo(1.0); + } + + [Fact] + public void Request_WithPath_Should_specify_requests_matching_given_path_and_method_get() + { + // given + var spec = Request.Create().WithPath("/foo").UsingGet(); + + // when + var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "GET", ClientIp); + + // then + var requestMatchResult = new RequestMatchResult(); + Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsEqualTo(1.0); + } + + [Fact] + public void Request_WithPath_Should_specify_requests_matching_given_path_and_method_head() + { + // given + var spec = Request.Create().WithPath("/foo").UsingHead(); + + // when + var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "HEAD", ClientIp); + + // then + var requestMatchResult = new RequestMatchResult(); + Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsEqualTo(1.0); + } + + [Fact] + public void Request_WithPath_Should_specify_requests_matching_given_path_and_method_post() + { + // given + var spec = Request.Create().WithPath("/foo").UsingPost(); + + // when + var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "POST", ClientIp); + + // then + var requestMatchResult = new RequestMatchResult(); + Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsEqualTo(1.0); + } + + [Fact] + public void Request_WithPath_Should_specify_requests_matching_given_path_and_method_put() + { + // given + var spec = Request.Create().WithPath("/foo").UsingPut(); + + // when + var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "PUT", ClientIp); + + // then + var requestMatchResult = new RequestMatchResult(); + Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsEqualTo(1.0); + } + + [Fact] + public void Request_WithPath_Should_specify_requests_matching_given_path_and_method_patch() + { + // given + var spec = Request.Create().WithPath("/foo").UsingPatch(); + + // when + var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "PATCH", ClientIp); + + // then + var requestMatchResult = new RequestMatchResult(); + Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsEqualTo(1.0); + } + + [Fact] + public void Request_WithPath_Should_exclude_requests_matching_given_path_but_not_http_method() + { + // given + var spec = Request.Create().WithPath("/foo").UsingPut(); + + // when + var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "HEAD", ClientIp); + + // then + var requestMatchResult = new RequestMatchResult(); + Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsNotEqualTo(1.0); + } +} \ No newline at end of file diff --git a/test/WireMock.Net.Tests/RequestMatchers/RequestMessageMultiPartMatcher.cs b/test/WireMock.Net.Tests/RequestMatchers/RequestMessageMultiPartMatcher.cs new file mode 100644 index 00000000..7f1dff0b --- /dev/null +++ b/test/WireMock.Net.Tests/RequestMatchers/RequestMessageMultiPartMatcher.cs @@ -0,0 +1,84 @@ +#if MIMEKIT +using System; +using System.Collections.Generic; +using FluentAssertions; +using WireMock.Matchers; +using WireMock.Matchers.Request; +using WireMock.Models; +using WireMock.Types; +using WireMock.Util; +using Xunit; + +namespace WireMock.Net.Tests.RequestMatchers; + +public class RequestMessageMultiPartMatcherTests +{ + private const string TestMultiPart = @"--=-5XgmpXt0XOfzdtcgNJc2ZQ== +Content-Type: text/plain; charset=utf-8 + +This is some plain text +--=-5XgmpXt0XOfzdtcgNJc2ZQ== +Content-Type: text/json; charset=utf-8 + +{ + ""Key"": ""Value"" + } +--=-5XgmpXt0XOfzdtcgNJc2ZQ== +Content-Type: image/png; name=image.png +Content-Disposition: attachment; filename=image.png +Content-Transfer-Encoding: base64 + +iVBORw0KGgoAAAANSUhEUgAAAAIAAAACAgMAAAAP2OW3AAAADFBMVEX/tID/vpH/pWX/sHidUyjl +AAAADElEQVR4XmMQYNgAAADkAMHebX3mAAAAAElFTkSuQmCC + +--=-5XgmpXt0XOfzdtcgNJc2ZQ==-- +"; + + [Fact] + public void RequestMessageBodyMatcher_GetMatchingScore_BodyAsMultiPart() + { + // Assign + var body = new BodyData + { + BodyAsString = TestMultiPart, + DetectedBodyType = BodyType.MultiPart + }; + + var textPlainContentTypeMatcher = new ContentTypeMatcher("text/plain"); + var textPlainContentMatcher = new ExactMatcher("This is some plain text"); + var textPlainMatcher = new MimePartMatcher(MatchBehaviour.AcceptOnMatch, textPlainContentTypeMatcher, null, null, textPlainContentMatcher); + + var partTextJsonContentTypeMatcher = new ContentTypeMatcher("text/json"); + var partTextJsonContentMatcher = new JsonMatcher(new { Key = "Value" }, true); + var partTextMatcher = new MimePartMatcher(MatchBehaviour.AcceptOnMatch, partTextJsonContentTypeMatcher, null, null, partTextJsonContentMatcher); + + var imagePngContentTypeMatcher = new ContentTypeMatcher("image/png"); + var imagePngContentDispositionMatcher = new ExactMatcher("attachment; filename=\"image.png\""); + var imagePngContentTransferEncodingMatcher = new ExactMatcher("base64"); + var imagePngContentMatcher = new ExactObjectMatcher(Convert.FromBase64String("iVBORw0KGgoAAAANSUhEUgAAAAIAAAACAgMAAAAP2OW3AAAADFBMVEX/tID/vpH/pWX/sHidUyjlAAAADElEQVR4XmMQYNgAAADkAMHebX3mAAAAAElFTkSuQmCC")); + var imagePngMatcher = new MimePartMatcher(MatchBehaviour.AcceptOnMatch, imagePngContentTypeMatcher, imagePngContentDispositionMatcher, imagePngContentTransferEncodingMatcher, imagePngContentMatcher); + + var matchers = new IMatcher[] + { + textPlainMatcher, + partTextMatcher, + imagePngMatcher + }; + + var headers = new Dictionary + { + { "Content-Type", new[] { @"multipart/mixed; boundary=""=-5XgmpXt0XOfzdtcgNJc2ZQ==""" } } + }; + var requestMessage = new RequestMessage(new UrlDetails("http://localhost"), "GET", "127.0.0.1", body, headers); + + var matcher = new RequestMessageMultiPartMatcher(matchers); + + // Act + var result = new RequestMatchResult(); + var score = matcher.GetMatchingScore(requestMessage, result); + + // Assert + score.Should().Be(MatchScores.Perfect); + } +} +#endif \ No newline at end of file diff --git a/test/WireMock.Net.Tests/RequestWithBodyTests.cs b/test/WireMock.Net.Tests/RequestWithBodyTests.cs deleted file mode 100644 index 561febe1..00000000 --- a/test/WireMock.Net.Tests/RequestWithBodyTests.cs +++ /dev/null @@ -1,428 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using FluentAssertions; -using JsonConverter.Abstractions; -using Moq; -using Newtonsoft.Json; -using NFluent; -using WireMock.Matchers; -using WireMock.Matchers.Request; -using WireMock.Models; -using WireMock.RequestBuilders; -using WireMock.Types; -using WireMock.Util; -using Xunit; - -namespace WireMock.Net.Tests -{ - public class RequestWithBodyTests - { - private const string ClientIp = "::1"; - - [Fact] - public void Request_WithBody_FuncString() - { - // Assign - var requestBuilder = Request.Create().UsingAnyMethod().WithBody(b => b.Contains("b")); - - // Act - var body = new BodyData - { - BodyAsString = "b", - DetectedBodyType = BodyType.String - }; - var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "POST", ClientIp, body); - - // Assert - var requestMatchResult = new RequestMatchResult(); - Check.That(requestBuilder.GetMatchingScore(request, requestMatchResult)).IsEqualTo(1.0); - } - - [Fact] - public void Request_WithBody_FuncJson() - { - // Assign - var requestBuilder = Request.Create().UsingAnyMethod().WithBody(b => b != null); - - // Act - var body = new BodyData - { - BodyAsJson = 123, - DetectedBodyType = BodyType.Json - }; - var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "POST", ClientIp, body); - - // Assert - var requestMatchResult = new RequestMatchResult(); - Check.That(requestBuilder.GetMatchingScore(request, requestMatchResult)).IsEqualTo(1.0); - } - - [Fact] - public void Request_WithBody_FuncFormUrlEncoded() - { - // Assign - var requestBuilder = Request.Create().UsingAnyMethod().WithBody((IDictionary? values) => values != null); - - // Act - var body = new BodyData - { - BodyAsFormUrlEncoded = new Dictionary(), - DetectedBodyTypeFromContentType = BodyType.FormUrlEncoded, - DetectedBodyType = BodyType.FormUrlEncoded - }; - var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "POST", ClientIp, body); - - // Assert - var requestMatchResult = new RequestMatchResult(); - Check.That(requestBuilder.GetMatchingScore(request, requestMatchResult)).IsEqualTo(1.0); - } - - [Fact] - public void Request_WithBody_FuncBodyData() - { - // Assign - var requestBuilder = Request.Create().UsingAnyMethod().WithBody((IBodyData? b) => b != null); - - // Act - var body = new BodyData - { - BodyAsJson = 123, - DetectedBodyType = BodyType.Json - }; - var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "POST", ClientIp, body); - - // Assert - var requestMatchResult = new RequestMatchResult(); - Check.That(requestBuilder.GetMatchingScore(request, requestMatchResult)).IsEqualTo(1.0); - } - - [Fact] - public void Request_WithBody_FuncByteArray() - { - // Assign - var requestBuilder = Request.Create().UsingAnyMethod().WithBody((byte[]? b) => b != null); - - // Act - var body = new BodyData - { - BodyAsBytes = new byte[0], - DetectedBodyType = BodyType.Bytes - }; - var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "POST", ClientIp, body); - - // Assert - var requestMatchResult = new RequestMatchResult(); - Check.That(requestBuilder.GetMatchingScore(request, requestMatchResult)).IsEqualTo(1.0); - } - - [Fact] - public void Request_WithBodyExactMatcher() - { - // Arrange - var requestBuilder = Request.Create().UsingAnyMethod().WithBody(new ExactMatcher("cat")); - - // Act - var body = new BodyData - { - BodyAsString = "cat", - DetectedBodyType = BodyType.String - }; - var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "POST", ClientIp, body); - - // Assert - var requestMatchResult = new RequestMatchResult(); - Check.That(requestBuilder.GetMatchingScore(request, requestMatchResult)).IsEqualTo(1.0); - } - - [Fact] - public void Request_WithBody_BodyDataAsString_Using_WildcardMatcher() - { - // Arrange - var spec = Request.Create().WithPath("/foo").UsingAnyMethod().WithBody(new WildcardMatcher("H*o*")); - - // Act - var body = new BodyData - { - BodyAsString = "Hello world!", - DetectedBodyType = BodyType.String - }; - var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "PUT", ClientIp, body); - - // Assert - var requestMatchResult = new RequestMatchResult(); - spec.GetMatchingScore(request, requestMatchResult).Should().Be(1.0); - } - - [Fact] - public void Request_WithBody_BodyDataAsJson_Using_WildcardMatcher() - { - // Arrange - var spec = Request.Create().WithPath("/foo").UsingAnyMethod().WithBody(new WildcardMatcher("*Hello*")); - - // Act - var body = new BodyData - { - BodyAsJson = new { Hi = "Hello world!" }, - BodyAsString = "{ Hi = \"Hello world!\" }", - DetectedBodyType = BodyType.Json - }; - var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "PUT", ClientIp, body); - - // Assert - var requestMatchResult = new RequestMatchResult(); - spec.GetMatchingScore(request, requestMatchResult).Should().Be(1.0); - } - - [Fact] - public void Request_WithBodyXPathMatcher_true() - { - // Arrange - var spec = Request.Create().UsingAnyMethod().WithBody(new XPathMatcher("/todo-list[count(todo-item) = 3]")); - - // Act - var body = new BodyData - { - BodyAsString = @" - - abc - def - xyz - ", - DetectedBodyType = BodyType.String - }; - var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "PUT", ClientIp, body); - - // Assert - var requestMatchResult = new RequestMatchResult(); - Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsEqualTo(1.0); - } - - [Fact] - public void Request_WithBodyXPathMatcher_false() - { - // Arrange - var spec = Request.Create().UsingAnyMethod().WithBody(new XPathMatcher("/todo-list[count(todo-item) = 99]")); - - // Act - var body = new BodyData - { - BodyAsString = @" - - abc - def - xyz - ", - DetectedBodyType = BodyType.String - }; - var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "PUT", ClientIp, body); - - // Assert - var requestMatchResult = new RequestMatchResult(); - Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsNotEqualTo(1.0); - } - - [Fact] - public void Request_WithBodyJsonPathMatcher_true() - { - // Arrange - var spec = Request.Create().UsingAnyMethod().WithBody(new JsonPathMatcher("$..things[?(@.name == 'RequiredThing')]")); - - // Act - var body = new BodyData - { - BodyAsString = "{ \"things\": [ { \"name\": \"RequiredThing\" }, { \"name\": \"Wiremock\" } ] }", - DetectedBodyType = BodyType.String - }; - var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "PUT", ClientIp, body); - - // Assert - var requestMatchResult = new RequestMatchResult(); - Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsEqualTo(1.0); - } - - [Fact] - public void Request_WithBodyJsonPathMatcher_false() - { - // Arrange - var spec = Request.Create().UsingAnyMethod().WithBody(new JsonPathMatcher("$.things[?(@.name == 'RequiredThing')]")); - - // Act - var body = new BodyData - { - BodyAsString = "{ \"things\": { \"name\": \"Wiremock\" } }", - DetectedBodyType = BodyType.String - }; - var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "PUT", ClientIp, body); - - // Assert - var requestMatchResult = new RequestMatchResult(); - Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsNotEqualTo(1.0); - } - - [Fact] - public void Request_WithBodyAsJson_Object_JsonPathMatcher_true() - { - // Arrange - var spec = Request.Create().UsingAnyMethod().WithBody(new JsonPathMatcher("$..things[?(@.name == 'RequiredThing')]")); - - // Act - string jsonString = "{ \"things\": [ { \"name\": \"RequiredThing\" }, { \"name\": \"Wiremock\" } ] }"; - var bodyData = new BodyData - { - BodyAsJson = JsonConvert.DeserializeObject(jsonString), - BodyAsString = jsonString, - Encoding = Encoding.UTF8, - DetectedBodyType = BodyType.Json - }; - - var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "PUT", ClientIp, bodyData); - - // Assert - var requestMatchResult = new RequestMatchResult(); - Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsEqualTo(1.0); - } - - [Fact] - public void Request_WithBodyAsJson_Array_JsonPathMatcher_1() - { - // Arrange - var spec = Request.Create().UsingAnyMethod().WithBody(new JsonPathMatcher("$..books[?(@.price < 10)]")); - - // Act - string jsonString = "{ \"books\": [ { \"category\": \"test1\", \"price\": 8.95 }, { \"category\": \"test2\", \"price\": 20 } ] }"; - var bodyData = new BodyData - { - BodyAsJson = JsonConvert.DeserializeObject(jsonString), - BodyAsString = jsonString, - Encoding = Encoding.UTF8, - DetectedBodyType = BodyType.Json - }; - - var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "PUT", ClientIp, bodyData); - - // Assert - var requestMatchResult = new RequestMatchResult(); - Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsEqualTo(1.0); - } - - [Fact] - public void Request_WithBodyAsJson_Array_JsonPathMatcher_2() - { - // Arrange - var spec = Request.Create().UsingAnyMethod().WithBody(new JsonPathMatcher("$..[?(@.Id == 1)]")); - - // Act - string jsonString = "{ \"Id\": 1, \"Name\": \"Test\" }"; - var bodyData = new BodyData - { - BodyAsJson = JsonConvert.DeserializeObject(jsonString), - BodyAsString = jsonString, - Encoding = Encoding.UTF8, - DetectedBodyType = BodyType.Json - }; - - var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "PUT", ClientIp, bodyData); - - // Assert - var requestMatchResult = new RequestMatchResult(); - double result = spec.GetMatchingScore(request, requestMatchResult); - Check.That(result).IsEqualTo(1.0); - } - - [Fact] - public void Request_WithBodyAsObject_ExactObjectMatcher_true() - { - // Assign - object body = DateTime.MinValue; - var requestBuilder = Request.Create().UsingAnyMethod().WithBody(body); - - var bodyData = new BodyData - { - BodyAsJson = DateTime.MinValue, - DetectedBodyType = BodyType.Json - }; - - // Act - var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "PUT", ClientIp, bodyData); - - // Assert - var requestMatchResult = new RequestMatchResult(); - Check.That(requestBuilder.GetMatchingScore(request, requestMatchResult)).IsEqualTo(1.0); - } - - [Fact] - public void Request_WithBodyAsJson_UsingObject() - { - // Assign - object body = new - { - Test = "abc" - }; - var requestBuilder = Request.Create().UsingAnyMethod().WithBodyAsJson(body); - - var bodyData = new BodyData - { - BodyAsString = JsonConvert.SerializeObject(body), - DetectedBodyType = BodyType.String - }; - - // Act - var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "POST", ClientIp, bodyData); - - // Assert - var requestMatchResult = new RequestMatchResult(); - Check.That(requestBuilder.GetMatchingScore(request, requestMatchResult)).IsEqualTo(1.0); - } - - [Fact] - public void Request_WithBodyAsJson_WithIJsonConverter_UsingObject() - { - // Assign - var jsonConverterMock = new Mock(); - jsonConverterMock.Setup(j => j.Serialize(It.IsAny(), It.IsAny())).Returns("test"); - object body = new - { - Any = "key" - }; - var requestBuilder = Request.Create().UsingAnyMethod().WithBodyAsJson(body, jsonConverterMock.Object); - - var bodyData = new BodyData - { - BodyAsString = "test", - DetectedBodyType = BodyType.String - }; - - // Act - var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "POST", ClientIp, bodyData); - - // Assert - var requestMatchResult = new RequestMatchResult(); - Check.That(requestBuilder.GetMatchingScore(request, requestMatchResult)).IsEqualTo(1.0); - } - - [Theory] - [InlineData(new byte[] { 1 }, BodyType.Bytes)] - [InlineData(new byte[] { 48, 49, 50 }, BodyType.Bytes)] - [InlineData(new byte[] { 48, 49, 50 }, BodyType.String)] - public void Request_WithBodyAsBytes_ExactObjectMatcher_true(byte[] bytes, BodyType detectedBodyType) - { - // Assign - byte[] body = bytes; - var requestBuilder = Request.Create().UsingAnyMethod().WithBody(body); - - var bodyData = new BodyData - { - BodyAsBytes = bytes, - DetectedBodyType = detectedBodyType - }; - - // Act - var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "PUT", ClientIp, bodyData); - - // Assert - var requestMatchResult = new RequestMatchResult(); - requestBuilder.GetMatchingScore(request, requestMatchResult).Should().Be(1.0); - } - } -} \ No newline at end of file diff --git a/test/WireMock.Net.Tests/RequestWithClientIPTests.cs b/test/WireMock.Net.Tests/RequestWithClientIPTests.cs deleted file mode 100644 index f2d1a5b5..00000000 --- a/test/WireMock.Net.Tests/RequestWithClientIPTests.cs +++ /dev/null @@ -1,68 +0,0 @@ -using NFluent; -using WireMock.Matchers; -using WireMock.Matchers.Request; -using WireMock.Models; -using WireMock.RequestBuilders; -using Xunit; - -namespace WireMock.Net.Tests -{ - public class RequestWithClientIPTests - { - [Fact] - public void Request_WithClientIP_Match_Ok() - { - // given - var spec = Request.Create().WithClientIP("127.0.0.2", "1.1.1.1"); - - // when - var request = new RequestMessage(new UrlDetails("http://localhost"), "GET", "127.0.0.2"); - - // then - var requestMatchResult = new RequestMatchResult(); - Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsEqualTo(1.0); - } - - [Fact] - public void Request_WithClientIP_Match_Fail() - { - // given - var spec = Request.Create().WithClientIP("127.0.0.2"); - - // when - var request = new RequestMessage(new UrlDetails("http://localhost"), "GET", "192.1.1.1"); - - // then - var requestMatchResult = new RequestMatchResult(); - Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsEqualTo(0.0); - } - - [Fact] - public void Request_WithClientIP_WildcardMatcher() - { - // given - var spec = Request.Create().WithClientIP(new WildcardMatcher("127.0.0.2")); - - // when - var request = new RequestMessage(new UrlDetails("http://localhost"), "GET", "127.0.0.2"); - - // then - var requestMatchResult = new RequestMatchResult(); - Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsEqualTo(1.0); - } - - [Fact] - public void Request_WithClientIP_Func() - { - // given - var spec = Request.Create().WithClientIP(c => c.Contains(".")); - - // when - var request = new RequestMessage(new UrlDetails("http://localhost"), "GET", "127.0.0.2"); - - // then - var requestMatchResult = new RequestMatchResult(); - Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsEqualTo(1.0); - } - } -} \ No newline at end of file diff --git a/test/WireMock.Net.Tests/RequestWithPathTests.cs b/test/WireMock.Net.Tests/RequestWithPathTests.cs deleted file mode 100644 index 8d3122e9..00000000 --- a/test/WireMock.Net.Tests/RequestWithPathTests.cs +++ /dev/null @@ -1,239 +0,0 @@ -using System.Collections.Generic; -using NFluent; -using WireMock.Matchers; -using Xunit; -using WireMock.RequestBuilders; -using WireMock.Matchers.Request; -using WireMock.Models; -using WireMock.Util; - -namespace WireMock.Net.Tests -{ - public class RequestWithPathTests - { - private const string ClientIp = "::1"; - - [Fact] - public void Request_WithPath_Spaces() - { - // Assign - var spec = Request.Create().WithPath("/path/a b").UsingAnyMethod(); - - // when - var body = new BodyData(); - var request = new RequestMessage(new UrlDetails("http://localhost/path/a b"), "GET", ClientIp, body); - - // then - var requestMatchResult = new RequestMatchResult(); - Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsEqualTo(1.0); - } - - [Fact] - public void Request_WithPath_WithHeader_Match() - { - // given - var spec = Request.Create().WithPath("/foo").UsingAnyMethod().WithHeader("X-toto", "tata"); - - // when - var body = new BodyData - { - BodyAsString = "abc" - }; - var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "PUT", ClientIp, body, new Dictionary { { "X-toto", new[] { "tata" } } }); - - // then - var requestMatchResult = new RequestMatchResult(); - Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsEqualTo(1.0); - } - - [Fact] - public void Request_WithPath() - { - // given - var spec = Request.Create().WithPath("/foo"); - - // when - var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "blabla", ClientIp); - - // then - var requestMatchResult = new RequestMatchResult(); - Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsEqualTo(1.0); - } - - [Fact] - public void Request_WithPaths() - { - var requestBuilder = Request.Create().WithPath("/x1", "/x2"); - - var request1 = new RequestMessage(new UrlDetails("http://localhost/x1"), "blabla", ClientIp); - var request2 = new RequestMessage(new UrlDetails("http://localhost/x2"), "blabla", ClientIp); - - var requestMatchResult = new RequestMatchResult(); - Check.That(requestBuilder.GetMatchingScore(request1, requestMatchResult)).IsEqualTo(1.0); - Check.That(requestBuilder.GetMatchingScore(request2, requestMatchResult)).IsEqualTo(1.0); - } - - [Fact] - public void Request_WithPathFunc() - { - // given - var spec = Request.Create().WithPath(url => url.EndsWith("/foo")); - - // when - var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "blabla", ClientIp); - - // then - var requestMatchResult = new RequestMatchResult(); - Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsEqualTo(1.0); - } - - [Fact] - public void Request_WithPathRegexMatcher_HasMatch() - { - // given - var spec = Request.Create().WithPath(new RegexMatcher("^/foo")); - - // when - var request = new RequestMessage(new UrlDetails("http://localhost/foo/bar"), "blabla", ClientIp); - - // then - var requestMatchResult = new RequestMatchResult(); - Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsEqualTo(1.0); - } - - [Fact] - public void Request_WithPathRegexMatcher_HasNoMatch() - { - // given - var spec = Request.Create().WithPath("/foo"); - - // when - var request = new RequestMessage(new UrlDetails("http://localhost/bar"), "blabla", ClientIp); - - // then - var requestMatchResult = new RequestMatchResult(); - Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsNotEqualTo(1.0); - } - - [Fact] - public void Request_WithPathRegexMatcher_WithPatternAsFile_HasMatch() - { - // Arrange - var pattern = new StringPattern - { - Pattern = "^/foo", - PatternAsFile = "c:\\x.txt" - }; - var spec = Request.Create().WithPath(new RegexMatcher(pattern)); - - // when - var request = new RequestMessage(new UrlDetails("http://localhost/foo/bar"), "blabla", ClientIp); - - // then - var requestMatchResult = new RequestMatchResult(); - Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsEqualTo(1.0); - } - - [Fact] - public void Should_specify_requests_matching_given_path_and_method_delete() - { - // given - var spec = Request.Create().WithPath("/foo").UsingDelete(); - - // when - var body = new BodyData - { - BodyAsString = "whatever" - }; - var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "Delete", ClientIp, body); - - // then - var requestMatchResult = new RequestMatchResult(); - Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsEqualTo(1.0); - } - - [Fact] - public void Should_specify_requests_matching_given_path_and_method_get() - { - // given - var spec = Request.Create().WithPath("/foo").UsingGet(); - - // when - var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "GET", ClientIp); - - // then - var requestMatchResult = new RequestMatchResult(); - Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsEqualTo(1.0); - } - - [Fact] - public void Should_specify_requests_matching_given_path_and_method_head() - { - // given - var spec = Request.Create().WithPath("/foo").UsingHead(); - - // when - var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "HEAD", ClientIp); - - // then - var requestMatchResult = new RequestMatchResult(); - Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsEqualTo(1.0); - } - - [Fact] - public void Should_specify_requests_matching_given_path_and_method_post() - { - // given - var spec = Request.Create().WithPath("/foo").UsingPost(); - - // when - var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "POST", ClientIp); - - // then - var requestMatchResult = new RequestMatchResult(); - Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsEqualTo(1.0); - } - - [Fact] - public void Should_specify_requests_matching_given_path_and_method_put() - { - // given - var spec = Request.Create().WithPath("/foo").UsingPut(); - - // when - var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "PUT", ClientIp); - - // then - var requestMatchResult = new RequestMatchResult(); - Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsEqualTo(1.0); - } - - [Fact] - public void Should_specify_requests_matching_given_path_and_method_patch() - { - // given - var spec = Request.Create().WithPath("/foo").UsingPatch(); - - // when - var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "PATCH", ClientIp); - - // then - var requestMatchResult = new RequestMatchResult(); - Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsEqualTo(1.0); - } - - [Fact] - public void Should_exclude_requests_matching_given_path_but_not_http_method() - { - // given - var spec = Request.Create().WithPath("/foo").UsingPut(); - - // when - var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "HEAD", ClientIp); - - // then - var requestMatchResult = new RequestMatchResult(); - Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsNotEqualTo(1.0); - } - } -} \ No newline at end of file diff --git a/test/WireMock.Net.Tests/ResponseBuilders/ResponseWithTransformerTests.cs b/test/WireMock.Net.Tests/ResponseBuilders/ResponseWithTransformerTests.cs index f8ba854b..e520df3e 100644 --- a/test/WireMock.Net.Tests/ResponseBuilders/ResponseWithTransformerTests.cs +++ b/test/WireMock.Net.Tests/ResponseBuilders/ResponseWithTransformerTests.cs @@ -783,6 +783,59 @@ public class ResponseWithTransformerTests response.Message.BodyData.Encoding.Should().Be(enc); } +#if MIMEKIT + [Theory] + [InlineData(TransformerType.Handlebars)] + // [InlineData(TransformerType.Scriban)] + // [InlineData(TransformerType.ScribanDotLiquid)] + public async Task Response_ProvideResponse_Transformer_WithBodyAsMimeMessage(TransformerType transformerType) + { + // Assign + var multiPart = @"--=-5XgmpXt0XOfzdtcgNJc2ZQ== +Content-Type: text/plain; charset=utf-8 + +This is some plain text +--=-5XgmpXt0XOfzdtcgNJc2ZQ== +Content-Type: text/json; charset=utf-8 + +{ + ""Key"": ""Value"" + } +--=-5XgmpXt0XOfzdtcgNJc2ZQ== +Content-Type: image/png; name=image.png +Content-Disposition: attachment; filename=image.png +Content-Transfer-Encoding: base64 + +iVBORw0KGgoAAAANSUhEUgAAAAIAAAACAgMAAAAP2OW3AAAADFBMVEX/tID/vpH/pWX/sHidUyjl +AAAADElEQVR4XmMQYNgAAADkAMHebX3mAAAAAElFTkSuQmCC + +--=-5XgmpXt0XOfzdtcgNJc2ZQ==-- +"; + + var bodyData = new BodyData + { + BodyAsString = multiPart, + DetectedBodyType = BodyType.MultiPart + }; + + var headers = new Dictionary + { + { "Content-Type", new[] { @"multipart/mixed; boundary=""=-5XgmpXt0XOfzdtcgNJc2ZQ=="""} } + }; + var request = new RequestMessage(new UrlDetails("http://localhost/foo_object"), "POST", ClientIp, bodyData, headers); + + var responseBuilder = Response.Create() + .WithBody("{{request.BodyAsMimeMessage.BodyParts.[0].ContentType.MimeType}} {{request.BodyAsMimeMessage.BodyParts.[1].ContentType.MimeType}} {{request.BodyAsMimeMessage.BodyParts.[2].FileName}}") + .WithTransformer(transformerType); + + // Act + var response = await responseBuilder.ProvideResponseAsync(_mappingMock.Object, request, _settings).ConfigureAwait(false); + + // Assert + response.Message.BodyData!.BodyAsString.Should().Be("text/plain text/json image.png"); + } +#endif + [Theory] [InlineData("/wiremock-data/1", "one")] [InlineData("/wiremock-data/2", "two")] diff --git a/test/WireMock.Net.Tests/Serialization/MatcherMapperTests.cs b/test/WireMock.Net.Tests/Serialization/MatcherMapperTests.cs index f72299a8..d73da131 100644 --- a/test/WireMock.Net.Tests/Serialization/MatcherMapperTests.cs +++ b/test/WireMock.Net.Tests/Serialization/MatcherMapperTests.cs @@ -56,6 +56,40 @@ public class MatcherMapperTests models.Should().HaveCount(2); } +#if MIMEKIT + [Fact] + public void MatcherMapper_Map_MimePartMatcher() + { + // Arrange + var bytes = Convert.FromBase64String("c3RlZg=="); + var imagePngContentTypeMatcher = new ContentTypeMatcher("image/png"); + var imagePngContentDispositionMatcher = new ExactMatcher("attachment; filename=\"image.png\""); + var imagePngContentTransferEncodingMatcher = new ExactMatcher("base64"); + var imagePngContentMatcher = new ExactObjectMatcher(bytes); + var imagePngMatcher = new MimePartMatcher(MatchBehaviour.AcceptOnMatch, imagePngContentTypeMatcher, imagePngContentDispositionMatcher, imagePngContentTransferEncodingMatcher, imagePngContentMatcher); + + // Act + var model = _sut.Map(imagePngMatcher)!; + + // Assert + model.Name.Should().Be(nameof(MimePartMatcher)); + model.MatchOperator.Should().BeNull(); + model.RejectOnMatch.Should().BeNull(); + + model.ContentTypeMatcher!.Name.Should().Be(nameof(ContentTypeMatcher)); + model.ContentTypeMatcher.Pattern.Should().Be("image/png"); + + model.ContentDispositionMatcher!.Name.Should().Be(nameof(ExactMatcher)); + model.ContentDispositionMatcher.Pattern.Should().Be("attachment; filename=\"image.png\""); + + model.ContentTransferEncodingMatcher!.Name.Should().Be(nameof(ExactMatcher)); + model.ContentTransferEncodingMatcher.Pattern.Should().Be("base64"); + + model.ContentMatcher!.Name.Should().Be(nameof(ExactObjectMatcher)); + model.ContentMatcher.Pattern.Should().Be(bytes); + } +#endif + [Fact] public void MatcherMapper_Map_IStringMatcher() { @@ -428,4 +462,64 @@ public class MatcherMapperTests matcher.GetPatterns().Should().Contain("p"); matcher.IgnoreCase.Should().BeTrue(); } + + [Fact] + public void MatcherMapper_Map_MatcherModel_NotNullOrEmptyMatcher() + { + // Assign + var model = new MatcherModel + { + Name = "NotNullOrEmptyMatcher", + RejectOnMatch = true + }; + + // Act + var matcher = _sut.Map(model)!; + + // Assert + matcher.Should().BeAssignableTo(); + matcher.MatchBehaviour.Should().Be(MatchBehaviour.RejectOnMatch); + } + +#if MIMEKIT + [Fact] + public void MatcherMapper_Map_MatcherModel_MimePartMatcher() + { + // Assign + var model = new MatcherModel + { + Name = "MimePartMatcher", + ContentMatcher = new MatcherModel + { + Name = "ExactMatcher", + Pattern = "x" + }, + ContentDispositionMatcher = new MatcherModel + { + Name = "WildcardMatcher", + Pattern = "y" + }, + ContentTransferEncodingMatcher = new MatcherModel + { + Name = "RegexMatcher", + Pattern = "z" + }, + ContentTypeMatcher = new MatcherModel + { + Name = "ContentTypeMatcher", + Pattern = "text/json" + } + }; + + // Act + var matcher = (MimePartMatcher)_sut.Map(model)!; + + // Assert + matcher.MatchBehaviour.Should().Be(MatchBehaviour.AcceptOnMatch); + matcher.ContentMatcher.Should().BeAssignableTo().Which.GetPatterns().Should().ContainSingle("x"); + matcher.ContentDispositionMatcher.Should().BeAssignableTo().Which.GetPatterns().Should().ContainSingle("y"); + matcher.ContentTransferEncodingMatcher.Should().BeAssignableTo().Which.GetPatterns().Should().ContainSingle("z"); + matcher.ContentTypeMatcher.Should().BeAssignableTo().Which.GetPatterns().Should().ContainSingle("text/json"); + } +#endif } \ No newline at end of file diff --git a/test/WireMock.Net.Tests/WireMock.Net.Tests.csproj b/test/WireMock.Net.Tests/WireMock.Net.Tests.csproj index 833e2419..b8e288da 100644 --- a/test/WireMock.Net.Tests/WireMock.Net.Tests.csproj +++ b/test/WireMock.Net.Tests/WireMock.Net.Tests.csproj @@ -25,7 +25,7 @@ - $(DefineConstants);GRAPHQL + $(DefineConstants);GRAPHQL;MIMEKIT @@ -95,12 +95,12 @@ runtime; build; native; contentfiles; analyzers; buildtransitive + - @@ -138,5 +138,4 @@ - \ No newline at end of file diff --git a/test/WireMock.Net.Tests/WireMockServerTests.WithMultiPart.cs b/test/WireMock.Net.Tests/WireMockServerTests.WithMultiPart.cs new file mode 100644 index 00000000..fc11fd3d --- /dev/null +++ b/test/WireMock.Net.Tests/WireMockServerTests.WithMultiPart.cs @@ -0,0 +1,75 @@ +#if MIMEKIT +using System; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Threading.Tasks; +using FluentAssertions; +using WireMock.Matchers; +using WireMock.RequestBuilders; +using WireMock.ResponseBuilders; +using WireMock.Server; +using Xunit; + +namespace WireMock.Net.Tests; + +public partial class WireMockServerTests +{ + [Fact] + public async Task WireMockServer_WithMultiPartBody_Using_MimePartMatchers() + { + // Arrange + var server = WireMockServer.Start(); + + var textPlainContent = "This is some plain text"; + var textPlainContentType = "text/plain"; + var textPlainContentTypeMatcher = new ContentTypeMatcher(textPlainContentType); + var textPlainContentMatcher = new ExactMatcher(textPlainContent); + var textPlainMatcher = new MimePartMatcher(MatchBehaviour.AcceptOnMatch, textPlainContentTypeMatcher, null, null, textPlainContentMatcher); + + var textJson = "{ \"Key\" : \"Value\" }"; + var textJsonContentType = "text/json"; + var textJsonContentTypeMatcher = new ContentTypeMatcher(textJsonContentType); + var textJsonContentMatcher = new JsonMatcher(new { Key = "Value" }, true); + var jsonMatcher = new MimePartMatcher(MatchBehaviour.AcceptOnMatch, textJsonContentTypeMatcher, null, null, textJsonContentMatcher); + + var imagePngBytes = Convert.FromBase64String("iVBORw0KGgoAAAANSUhEUgAAAAIAAAACAgMAAAAP2OW3AAAADFBMVEX/tID/vpH/pWX/sHidUyjlAAAADElEQVR4XmMQYNgAAADkAMHebX3mAAAAAElFTkSuQmCC"); + var imagePngContentMatcher = new ExactObjectMatcher(imagePngBytes); + var imagePngMatcher = new MimePartMatcher(MatchBehaviour.AcceptOnMatch, null, null, null, imagePngContentMatcher); + + var matchers = new IMatcher[] + { + textPlainMatcher, + jsonMatcher, + imagePngMatcher + }; + + server + .Given( + Request.Create() + .UsingPost() + .WithPath("/multipart") + .WithMultiPart(matchers)) + .RespondWith(Response.Create()); + + // Act + var formDataContent = new MultipartFormDataContent(); + formDataContent.Add(new StringContent(textPlainContent, Encoding.UTF8, textPlainContentType), "text"); + formDataContent.Add(new StringContent(textJson, Encoding.UTF8, textJsonContentType), "json"); + + var fileContent = new ByteArrayContent(imagePngBytes); + fileContent.Headers.ContentType = new MediaTypeHeaderValue("image/png"); + formDataContent.Add(fileContent, "somefile", "image.png"); + + var client = server.CreateClient(); + + var response = await client.PostAsync("/multipart", formDataContent); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.OK); + + server.Stop(); + } +} +#endif \ No newline at end of file diff --git a/tools/MultipartUploader/Form1.Designer.cs b/tools/MultipartUploader/Form1.Designer.cs new file mode 100644 index 00000000..118fb985 --- /dev/null +++ b/tools/MultipartUploader/Form1.Designer.cs @@ -0,0 +1,39 @@ +namespace MultipartUploader +{ + partial class Form1 + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(800, 450); + this.Text = "Form1"; + } + + #endregion + } +} \ No newline at end of file diff --git a/tools/MultipartUploader/Form1.cs b/tools/MultipartUploader/Form1.cs new file mode 100644 index 00000000..9e8e8821 --- /dev/null +++ b/tools/MultipartUploader/Form1.cs @@ -0,0 +1,10 @@ +namespace MultipartUploader +{ + public partial class Form1 : Form + { + public Form1() + { + InitializeComponent(); + } + } +} \ No newline at end of file diff --git a/tools/MultipartUploader/Form1.resx b/tools/MultipartUploader/Form1.resx new file mode 100644 index 00000000..1af7de15 --- /dev/null +++ b/tools/MultipartUploader/Form1.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/tools/MultipartUploader/MultipartUploader.csproj b/tools/MultipartUploader/MultipartUploader.csproj new file mode 100644 index 00000000..9021a012 --- /dev/null +++ b/tools/MultipartUploader/MultipartUploader.csproj @@ -0,0 +1,15 @@ + + + + WinExe + net6.0-windows + enable + true + enable + + + + + + + \ No newline at end of file diff --git a/tools/MultipartUploader/Program.cs b/tools/MultipartUploader/Program.cs new file mode 100644 index 00000000..d365eab7 --- /dev/null +++ b/tools/MultipartUploader/Program.cs @@ -0,0 +1,17 @@ +namespace MultipartUploader +{ + internal static class Program + { + /// + /// The main entry point for the application. + /// + [STAThread] + static void Main() + { + // To customize application configuration such as set high DPI settings or default font, + // see https://aka.ms/applicationconfiguration. + ApplicationConfiguration.Initialize(); + Application.Run(new Form1()); + } + } +} \ No newline at end of file