Compare commits

...

7 Commits

Author SHA1 Message Date
Stef Heyenrath
da6cb9fe0a 1.5.20 2023-03-19 10:23:24 +01:00
Stef Heyenrath
b30e4faab6 Fix issue with application/x-www-form-urlencoded and ExactMatcher (#907) 2023-03-19 10:21:47 +01:00
Stef Heyenrath
1221d52c69 Add DeserializeFormUrl Encoded to the settings (#905)
* Add DeserializeFormUrlEncoded to Settings

* EmptyArray<>.Value

* .
2023-03-19 09:19:53 +01:00
Stef Heyenrath
52d2109c7e packagereleasenotes 2023-03-17 17:16:35 +01:00
Stef Heyenrath
30064b922b 1.5.19 2023-03-17 17:15:19 +01:00
Stef Heyenrath
78b94d2ebc Add WithBody with IDictionary (form-urlencoded values) (#903)
* .

* x

* fx

* fix

* f

* tests

* fix tests

* add tst
2023-03-17 17:08:45 +01:00
Stef Heyenrath
19701f5260 Update Handlebars.Net.Helpers to 2.3.15 (#904) 2023-03-15 18:30:34 +01:00
46 changed files with 486 additions and 328 deletions

View File

@@ -1,3 +1,13 @@
# 1.5.20 (19 March 2023)
- [#905](https://github.com/WireMock-Net/WireMock.Net/pull/905) - Add DeserializeFormUrl Encoded to the settings [feature] contributed by [StefH](https://github.com/StefH)
- [#907](https://github.com/WireMock-Net/WireMock.Net/pull/907) - Fix issue with application/x-www-form-urlencoded and ExactMatcher [bug] contributed by [StefH](https://github.com/StefH)
- [#906](https://github.com/WireMock-Net/WireMock.Net/issues/906) - Upgrade to 1.5.19 breaks a form data test [bug]
# 1.5.19 (17 March 2023)
- [#903](https://github.com/WireMock-Net/WireMock.Net/pull/903) - Add WithBody with IDictionary (form-urlencoded values) [feature] contributed by [StefH](https://github.com/StefH)
- [#904](https://github.com/WireMock-Net/WireMock.Net/pull/904) - Update Handlebars.Net.Helpers to 2.3.15 [feature] contributed by [StefH](https://github.com/StefH)
- [#901](https://github.com/WireMock-Net/WireMock.Net/issues/901) - Matching one form-urlencoded value [feature]
# 1.5.18 (09 March 2023)
- [#893](https://github.com/WireMock-Net/WireMock.Net/pull/893) - Add 'Data' to response which can be used during transforming the response [feature] contributed by [StefH](https://github.com/StefH)
- [#896](https://github.com/WireMock-Net/WireMock.Net/pull/896) - Bump Microsoft.Owin from 2.0.2 to 4.2.2 in /examples/WireMock.Net.Service [dependencies] contributed by [dependabot[bot]](https://github.com/apps/dependabot)

View File

@@ -4,7 +4,7 @@
</PropertyGroup>
<PropertyGroup>
<VersionPrefix>1.5.18</VersionPrefix>
<VersionPrefix>1.5.20</VersionPrefix>
<PackageIcon>WireMock.Net-Logo.png</PackageIcon>
<PackageProjectUrl>https://github.com/WireMock-Net/WireMock.Net</PackageProjectUrl>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>

View File

@@ -1,6 +1,6 @@
rem https://github.com/StefH/GitHubReleaseNotes
SET version=1.5.18
SET version=1.5.20
GitHubReleaseNotes --output CHANGELOG.md --skip-empty-releases --exclude-labels question invalid doc duplicate --version %version% --token %GH_TOKEN%

View File

@@ -1,8 +1,6 @@
# 1.5.18 (09 March 2023)
- #893 Add 'Data' to response which can be used during transforming the response [feature]
- #896 Bump Microsoft.Owin from 2.0.2 to 4.2.2 in /examples/WireMock.Net.Service [dependencies]
- #900 ProxySettings : Add logic to not save some requests depending on HttpMethods [feature]
- #897 WebHostBuilder.ConfigureServices method not found when using nunit3testadapter 4.4.0 [bug]
- #899 Ignore OPTIONS request when using proxyandrecord [feature]
# 1.5.20 (19 March 2023)
- #905 Add DeserializeFormUrl Encoded to the settings [feature]
- #907 Fix issue with application/x-www-form-urlencoded and ExactMatcher [bug]
- #906 Upgrade to 1.5.19 breaks a form data test [bug]
The full release notes can be found here: https://github.com/WireMock-Net/WireMock.Net/blob/master/CHANGELOG.md

View File

@@ -31,27 +31,49 @@ public class SettingsModel
public int? MaxRequestLogCount { get; set; }
/// <summary>
/// Allow a Body for all HTTP Methods. (default set to false).
/// Allow a Body for all HTTP Methods. (default set to <c>false</c>).
/// </summary>
public bool? AllowBodyForAllHttpMethods { get; set; }
/// <summary>
/// Handle all requests synchronously. (default set to false).
/// Allow only a HttpStatus Code in the response which is defined. (default set to <c>false</c>).
/// - false : also null, 0, empty or invalid HttpStatus codes are allowed.
/// - true : only codes defined in <see cref="System.Net.HttpStatusCode"/> are allowed.
/// </summary>
public bool? AllowOnlyDefinedHttpStatusCodeInResponse { get; set; }
/// <summary>
/// Set to true to disable Json deserialization when processing requests. (default set to <c>false</c>).
/// </summary>
public bool? DisableJsonBodyParsing { get; set; }
/// <summary>
/// Disable support for GZip and Deflate request body decompression. (default set to <c>false</c>).
/// </summary>
public bool? DisableRequestBodyDecompressing { get; set; }
/// <summary>
/// Set to true to disable FormUrlEncoded deserializing when processing requests. (default set to <c>false</c>).
/// </summary>
public bool? DisableDeserializeFormUrlEncoded { get; set; }
/// <summary>
/// Handle all requests synchronously. (default set to <c>false</c>).
/// </summary>
public bool? HandleRequestsSynchronously { get; set; }
/// <summary>
/// Throw an exception when the Matcher fails because of invalid input. (default set to false).
/// Throw an exception when the Matcher fails because of invalid input. (default set to <c>false</c>).
/// </summary>
public bool? ThrowExceptionWhenMatcherFails { get; set; }
/// <summary>
/// Use the RegexExtended instead of the default <see cref="Regex"/>. (default set to true).
/// Use the RegexExtended instead of the default <see cref="Regex"/>. (default set to <c>true</c>).
/// </summary>
public bool? UseRegexExtended { get; set; }
/// <summary>
/// Save unmatched requests to a file using the <see cref="IFileSystemHandler"/>. (default set to false).
/// Save unmatched requests to a file using the <see cref="IFileSystemHandler"/>. (default set to <c>false</c>).
/// </summary>
public bool? SaveUnmatchedRequests { get; set; }
@@ -86,7 +108,7 @@ public class SettingsModel
public HostingScheme? HostingScheme { get; set; }
/// <summary>
/// Don't save the response-string in the LogEntry when WithBody(Func{IRequestMessage, string}) or WithBody(Func{IRequestMessage, Task{string}}) is used. (default set to false).
/// Don't save the response-string in the LogEntry when WithBody(Func{IRequestMessage, string}) or WithBody(Func{IRequestMessage, Task{string}}) is used. (default set to <c>false</c>).
/// </summary>
public bool? DoNotSaveDynamicResponseInLogEntry { get; set; }

View File

@@ -1,3 +1,4 @@
using System.Collections.Generic;
using System.Text;
using WireMock.Types;
@@ -38,6 +39,11 @@ public interface IBodyData
/// </summary>
string? BodyAsString { get; set; }
/// <summary>
/// The body as Form UrlEncoded dictionary.
/// </summary>
IDictionary<string, string>? BodyAsFormUrlEncoded { get; set; }
/// <summary>
/// The detected body type (detection based on body content).
/// </summary>

View File

@@ -1,38 +1,42 @@
namespace WireMock.Types
namespace WireMock.Types;
/// <summary>
/// The BodyType
/// </summary>
public enum BodyType
{
/// <summary>
/// The BodyType
/// No body present
/// </summary>
public enum BodyType
{
/// <summary>
/// No body present
/// </summary>
None,
None,
/// <summary>
/// Body is a String
/// </summary>
String,
/// <summary>
/// Body is a String
/// </summary>
String,
/// <summary>
/// Body is a Json object
/// </summary>
Json,
/// <summary>
/// Body is a Json object
/// </summary>
Json,
/// <summary>
/// Body is a Byte array
/// </summary>
Bytes,
/// <summary>
/// Body is a Byte array
/// </summary>
Bytes,
/// <summary>
/// Body is a File
/// </summary>
File,
/// <summary>
/// Body is a File
/// </summary>
File,
/// <summary>
/// Body is a MultiPart
/// </summary>
MultiPart
}
/// <summary>
/// Body is a MultiPart
/// </summary>
MultiPart,
/// <summary>
/// Body is a String which is x-www-form-urlencoded.
/// </summary>
FormUrlEncoded
}

View File

@@ -28,7 +28,7 @@ internal static class HttpRequestMessageHelper
switch (requestMessage.BodyData?.DetectedBodyType)
{
case BodyType.Bytes:
httpRequestMessage.Content = ByteArrayContentHelper.Create(requestMessage.BodyData.BodyAsBytes, contentType);
httpRequestMessage.Content = ByteArrayContentHelper.Create(requestMessage.BodyData.BodyAsBytes!, contentType);
break;
case BodyType.Json:
@@ -36,7 +36,8 @@ internal static class HttpRequestMessageHelper
break;
case BodyType.String:
httpRequestMessage.Content = StringContentHelper.Create(requestMessage.BodyData.BodyAsString, contentType);
case BodyType.FormUrlEncoded:
httpRequestMessage.Content = StringContentHelper.Create(requestMessage.BodyData.BodyAsString!, contentType);
break;
}

View File

@@ -15,7 +15,8 @@ internal static class HttpResponseMessageHelper
Uri requiredUri,
Uri originalUri,
bool deserializeJson,
bool decompressGzipAndDeflate)
bool decompressGzipAndDeflate,
bool deserializeFormUrlEncoded)
{
var responseMessage = new ResponseMessage { StatusCode = (int)httpResponseMessage.StatusCode };
@@ -44,7 +45,8 @@ internal static class HttpResponseMessageHelper
ContentType = contentTypeHeader?.FirstOrDefault(),
DeserializeJson = deserializeJson,
ContentEncoding = contentEncodingHeader?.FirstOrDefault(),
DecompressGZipAndDeflate = decompressGzipAndDeflate
DecompressGZipAndDeflate = decompressGzipAndDeflate,
DeserializeFormUrlEncoded = deserializeFormUrlEncoded
};
responseMessage.BodyData = await BodyParser.ParseAsync(bodyParserSettings).ConfigureAwait(false);
}
@@ -55,7 +57,7 @@ internal static class HttpResponseMessageHelper
// If Location header contains absolute redirect URL, and base URL is one that we proxy to,
// we need to replace it to original one.
if (string.Equals(header.Key, HttpKnownHeaderNames.Location, StringComparison.OrdinalIgnoreCase)
&& Uri.TryCreate(header.Value.First(), UriKind.Absolute, out Uri absoluteLocationUri)
&& Uri.TryCreate(header.Value.First(), UriKind.Absolute, out var absoluteLocationUri)
&& string.Equals(absoluteLocationUri.Host, requiredUri.Host, StringComparison.OrdinalIgnoreCase))
{
var replacedLocationUri = new Uri(originalUri, absoluteLocationUri.PathAndQuery);

View File

@@ -60,7 +60,7 @@ internal static class JObjectExtensions
{
if (src == null)
{
return new object?[0];
return EmptyArray<object?>.Value;
}
return ConvertJTokenArray(src, options);

View File

@@ -1,3 +1,4 @@
using System;
using System.Linq;
using AnyOfTypes;
using WireMock.Models;
@@ -62,7 +63,7 @@ public class NotNullOrEmptyMatcher : IObjectMatcher, IStringMatcher
/// <inheritdoc cref="IStringMatcher.GetPatterns"/>
public AnyOf<string, StringPattern>[] GetPatterns()
{
return new AnyOf<string, StringPattern>[0];
return EmptyArray<AnyOf<string, StringPattern>>.Value;
}
/// <inheritdoc />

View File

@@ -1,8 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using AnyOfTypes;
using Stef.Validation;
using WireMock.Models;
using WireMock.Types;
using WireMock.Util;
@@ -33,6 +32,11 @@ public class RequestMessageBodyMatcher : IRequestMatcher
/// </summary>
public Func<IBodyData?, bool>? BodyDataFunc { get; }
/// <summary>
/// The body data function for FormUrlEncoded
/// </summary>
public Func<IDictionary<string, string>?, bool>? FormUrlEncodedFunc { get; }
/// <summary>
/// The matchers.
/// </summary>
@@ -109,6 +113,15 @@ public class RequestMessageBodyMatcher : IRequestMatcher
BodyDataFunc = Guard.NotNull(func);
}
/// <summary>
/// Initializes a new instance of the <see cref="RequestMessageBodyMatcher"/> class.
/// </summary>
/// <param name="func">The function.</param>
public RequestMessageBodyMatcher(Func<IDictionary<string, string>?, bool> func)
{
FormUrlEncodedFunc = Guard.NotNull(func);
}
/// <summary>
/// Initializes a new instance of the <see cref="RequestMessageBodyMatcher"/> class.
/// </summary>
@@ -144,6 +157,7 @@ public class RequestMessageBodyMatcher : IRequestMatcher
{
case BodyType.Json:
case BodyType.String:
case BodyType.FormUrlEncoded:
return notNullOrEmptyMatcher.IsMatch(requestMessage.BodyData.BodyAsString);
case BodyType.Bytes:
@@ -158,7 +172,7 @@ public class RequestMessageBodyMatcher : IRequestMatcher
{
// If the body is a byte array, try to match.
var detectedBodyType = requestMessage.BodyData?.DetectedBodyType;
if (detectedBodyType is BodyType.Bytes or BodyType.String)
if (detectedBodyType is BodyType.Bytes or BodyType.String or BodyType.FormUrlEncoded)
{
return exactObjectMatcher.IsMatch(requestMessage.BodyData?.BodyAsBytes);
}
@@ -184,7 +198,7 @@ public class RequestMessageBodyMatcher : IRequestMatcher
if (matcher is IStringMatcher stringMatcher)
{
// If the body is a Json or a String, use the BodyAsString to match on.
if (requestMessage?.BodyData?.DetectedBodyType == BodyType.Json || requestMessage?.BodyData?.DetectedBodyType == BodyType.String)
if (requestMessage?.BodyData?.DetectedBodyType is BodyType.Json or BodyType.String or BodyType.FormUrlEncoded)
{
return stringMatcher.IsMatch(requestMessage.BodyData.BodyAsString);
}
@@ -206,6 +220,11 @@ public class RequestMessageBodyMatcher : IRequestMatcher
return MatchScores.ToScore(Func(requestMessage.BodyData?.BodyAsString));
}
if (FormUrlEncodedFunc != null)
{
return MatchScores.ToScore(FormUrlEncodedFunc(requestMessage.BodyData?.BodyAsFormUrlEncoded));
}
if (JsonFunc != null)
{
return MatchScores.ToScore(JsonFunc(requestMessage.BodyData?.BodyAsJson));

View File

@@ -1,3 +1,4 @@
using System.Collections.Generic;
using System.Text;
using WireMock.Types;
@@ -14,6 +15,9 @@ public class BodyData : IBodyData
/// <inheritdoc />
public string? BodyAsString { get; set; }
/// <inheritdoc />
public IDictionary<string, string>? BodyAsFormUrlEncoded { get; set; }
/// <inheritdoc cref="IBodyData.BodyAsJson" />
public object? BodyAsJson { get; set; }

View File

@@ -130,6 +130,7 @@ namespace WireMock.Owin.Mappers
switch (responseMessage.BodyData?.DetectedBodyType)
{
case BodyType.String:
case BodyType.FormUrlEncoded:
return (responseMessage.BodyData.Encoding ?? _utf8NoBom).GetBytes(responseMessage.BodyData.BodyAsString!);
case BodyType.Json:

View File

@@ -45,8 +45,16 @@ internal class ProxyHelper
// Create ResponseMessage
bool deserializeJson = !_settings.DisableJsonBodyParsing.GetValueOrDefault(false);
bool decompressGzipAndDeflate = !_settings.DisableRequestBodyDecompressing.GetValueOrDefault(false);
bool deserializeFormUrlEncoded = !_settings.DisableDeserializeFormUrlEncoded.GetValueOrDefault(false);
var responseMessage = await HttpResponseMessageHelper.CreateAsync(httpResponseMessage, requiredUri, originalUri, deserializeJson, decompressGzipAndDeflate).ConfigureAwait(false);
var responseMessage = await HttpResponseMessageHelper.CreateAsync(
httpResponseMessage,
requiredUri,
originalUri,
deserializeJson,
decompressGzipAndDeflate,
deserializeFormUrlEncoded
).ConfigureAwait(false);
IMapping? newMapping = null;
@@ -56,11 +64,11 @@ internal class ProxyHelper
if (saveMappingSettings != null)
{
save &= Check(saveMappingSettings.StatusCodePattern,
() => HttpStatusRangeParser.IsMatch(saveMappingSettings.StatusCodePattern, responseMessage.StatusCode)
() => saveMappingSettings.StatusCodePattern != null && HttpStatusRangeParser.IsMatch(saveMappingSettings.StatusCodePattern, responseMessage.StatusCode)
);
save &= Check(saveMappingSettings.HttpMethods,
() => saveMappingSettings.HttpMethods.Value.Contains(requestMessage.Method, StringComparer.OrdinalIgnoreCase)
() => saveMappingSettings.HttpMethods != null && saveMappingSettings.HttpMethods.Value.Contains(requestMessage.Method, StringComparer.OrdinalIgnoreCase)
);
}

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using WireMock.Matchers;
using WireMock.Matchers.Request;
using WireMock.Util;
@@ -54,26 +55,33 @@ public interface IBodyRequestBuilder : IRequestMatcher
/// </summary>
/// <param name="func">The function.</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithBody(Func<string, bool> func);
IRequestBuilder WithBody(Func<string?, bool> func);
/// <summary>
/// WithBody: func (byte[])
/// </summary>
/// <param name="func">The function.</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithBody(Func<byte[], bool> func);
IRequestBuilder WithBody(Func<byte[]?, bool> func);
/// <summary>
/// WithBody: func (json object)
/// </summary>
/// <param name="func">The function.</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithBody(Func<object, bool> func);
IRequestBuilder WithBody(Func<object?, bool> func);
/// <summary>
/// WithBody: func (BodyData object)
/// </summary>
/// <param name="func">The function.</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithBody(Func<IBodyData, bool> func);
IRequestBuilder WithBody(Func<IBodyData?, bool> func);
/// <summary>
/// WithBody: Body as form-urlencoded values.
/// </summary>
/// <param name="func">The form-urlencoded values.</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithBody(Func<IDictionary<string, string>?, bool> func);
}

View File

@@ -1,6 +1,7 @@
// This source file is based on mock4net by Alexandre Victoor which is licensed under the Apache 2.0 License.
// For more details see 'mock4net/LICENSE.txt' and 'mock4net/readme.md' in this project root.
using System;
using System.Collections.Generic;
using WireMock.Matchers;
using WireMock.Matchers.Request;
using WireMock.Util;
@@ -10,21 +11,21 @@ namespace WireMock.RequestBuilders;
public partial class Request
{
/// <inheritdoc cref="IBodyRequestBuilder.WithBody(string, MatchBehaviour)"/>
/// <inheritdoc />
public IRequestBuilder WithBody(string body, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{
_requestMatchers.Add(new RequestMessageBodyMatcher(matchBehaviour, body));
return this;
}
/// <inheritdoc cref="IBodyRequestBuilder.WithBody(byte[], MatchBehaviour)"/>
/// <inheritdoc />
public IRequestBuilder WithBody(byte[] body, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{
_requestMatchers.Add(new RequestMessageBodyMatcher(matchBehaviour, body));
return this;
}
/// <inheritdoc cref="IBodyRequestBuilder.WithBody(object, MatchBehaviour)"/>
/// <inheritdoc />
public IRequestBuilder WithBody(object body, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{
_requestMatchers.Add(new RequestMessageBodyMatcher(matchBehaviour, body));
@@ -46,39 +47,46 @@ public partial class Request
return this;
}
/// <inheritdoc cref="IBodyRequestBuilder.WithBody(Func{string, bool})"/>
public IRequestBuilder WithBody(Func<string, bool> func)
/// <inheritdoc />
public IRequestBuilder WithBody(Func<string?, bool> func)
{
Guard.NotNull(func, nameof(func));
Guard.NotNull(func);
_requestMatchers.Add(new RequestMessageBodyMatcher(func));
return this;
}
/// <inheritdoc cref="IBodyRequestBuilder.WithBody(Func{byte[], bool})"/>
public IRequestBuilder WithBody(Func<byte[], bool> func)
/// <inheritdoc />
public IRequestBuilder WithBody(Func<byte[]?, bool> func)
{
Guard.NotNull(func, nameof(func));
Guard.NotNull(func);
_requestMatchers.Add(new RequestMessageBodyMatcher(func));
return this;
}
/// <inheritdoc cref="IBodyRequestBuilder.WithBody(Func{object, bool})"/>
public IRequestBuilder WithBody(Func<object, bool> func)
/// <inheritdoc />
public IRequestBuilder WithBody(Func<object?, bool> func)
{
Guard.NotNull(func, nameof(func));
Guard.NotNull(func);
_requestMatchers.Add(new RequestMessageBodyMatcher(func));
return this;
}
/// <inheritdoc cref="IBodyRequestBuilder.WithBody(Func{IBodyData, bool})"/>
public IRequestBuilder WithBody(Func<IBodyData, bool> func)
/// <inheritdoc />
public IRequestBuilder WithBody(Func<IBodyData?, bool> func)
{
Guard.NotNull(func, nameof(func));
Guard.NotNull(func);
_requestMatchers.Add(new RequestMessageBodyMatcher(func));
return this;
}
/// <inheritdoc />
public IRequestBuilder WithBody(Func<IDictionary<string, string>?, bool> func)
{
_requestMatchers.Add(new RequestMessageBodyMatcher(Guard.NotNull(func)));
return this;
}
}

View File

@@ -44,6 +44,7 @@ internal class LogEntryMapper
switch (logEntry.RequestMessage.BodyData.DetectedBodyType)
{
case BodyType.String:
case BodyType.FormUrlEncoded:
logRequestModel.Body = logEntry.RequestMessage.BodyData.BodyAsString;
break;
@@ -120,6 +121,7 @@ internal class LogEntryMapper
switch (logEntry.ResponseMessage.BodyData!.DetectedBodyType)
{
case BodyType.String:
case BodyType.FormUrlEncoded:
if (!string.IsNullOrEmpty(logEntry.ResponseMessage.BodyData.IsFuncUsed) && _options.DoNotSaveDynamicResponseInLogEntry == true)
{
logResponseModel.Body = logEntry.ResponseMessage.BodyData.IsFuncUsed;
@@ -142,6 +144,9 @@ internal class LogEntryMapper
logResponseModel.BodyAsFile = logEntry.ResponseMessage.BodyData.BodyAsFile;
logResponseModel.BodyAsFileIsCached = logEntry.ResponseMessage.BodyData.BodyAsFileIsCached;
break;
default:
break;
}
}

View File

@@ -131,6 +131,7 @@ internal class MappingConverter
switch (response.ResponseMessage.BodyData.DetectedBodyType)
{
case BodyType.String:
case BodyType.FormUrlEncoded:
sb.AppendLine($" .WithBody(\"{response.ResponseMessage.BodyData.BodyAsString}\")");
break;
}
@@ -140,7 +141,7 @@ internal class MappingConverter
{
sb.AppendLine($" .WithDelay({response.Delay.Value.TotalMilliseconds})");
}
else if (response.MinimumDelayMilliseconds > 0 && response.MaximumDelayMilliseconds > 0)
else if (response is { MinimumDelayMilliseconds: > 0, MaximumDelayMilliseconds: > 0 })
{
sb.AppendLine($" .WithRandomDelay({response.MinimumDelayMilliseconds}, {response.MaximumDelayMilliseconds})");
}
@@ -325,6 +326,7 @@ internal class MappingConverter
switch (response.ResponseMessage.BodyData?.DetectedBodyType)
{
case BodyType.String:
case BodyType.FormUrlEncoded:
mappingModel.Response.Body = response.ResponseMessage.BodyData.BodyAsString;
break;

View File

@@ -141,6 +141,7 @@ internal class ProxyMappingConverter
break;
case BodyType.String:
case BodyType.FormUrlEncoded:
newRequest.WithBody(new ExactMatcher(MatchBehaviour.AcceptOnMatch, true, throwExceptionWhenMatcherFails, MatchOperator.Or, requestMessage.BodyData.BodyAsString!));
break;

View File

@@ -97,6 +97,7 @@ internal static class WebhookMapper
switch (webhook.Request.BodyData.DetectedBodyType)
{
case BodyType.String:
case BodyType.FormUrlEncoded:
model.Request.Body = webhook.Request.BodyData.BodyAsString;
break;

View File

@@ -217,10 +217,17 @@ public partial class WireMockServer
var model = new SettingsModel
{
AllowBodyForAllHttpMethods = _settings.AllowBodyForAllHttpMethods,
AllowOnlyDefinedHttpStatusCodeInResponse = _settings.AllowOnlyDefinedHttpStatusCodeInResponse,
AllowPartialMapping = _settings.AllowPartialMapping,
DisableDeserializeFormUrlEncoded = _settings.DisableDeserializeFormUrlEncoded,
DisableJsonBodyParsing = _settings.DisableJsonBodyParsing,
DisableRequestBodyDecompressing = _settings.DisableRequestBodyDecompressing,
DoNotSaveDynamicResponseInLogEntry = _settings.DoNotSaveDynamicResponseInLogEntry,
GlobalProcessingDelay = (int?)_options.RequestProcessingDelay?.TotalMilliseconds,
HandleRequestsSynchronously = _settings.HandleRequestsSynchronously,
HostingScheme = _settings.HostingScheme,
MaxRequestLogCount = _settings.MaxRequestLogCount,
QueryParameterMultipleValueSupport = _settings.QueryParameterMultipleValueSupport,
ReadStaticMappings = _settings.ReadStaticMappings,
RequestLogExpirationDuration = _settings.RequestLogExpirationDuration,
SaveUnmatchedRequests = _settings.SaveUnmatchedRequests,
@@ -228,14 +235,11 @@ public partial class WireMockServer
UseRegexExtended = _settings.UseRegexExtended,
WatchStaticMappings = _settings.WatchStaticMappings,
WatchStaticMappingsInSubdirectories = _settings.WatchStaticMappingsInSubdirectories,
HostingScheme = _settings.HostingScheme,
DoNotSaveDynamicResponseInLogEntry = _settings.DoNotSaveDynamicResponseInLogEntry,
QueryParameterMultipleValueSupport = _settings.QueryParameterMultipleValueSupport,
#if USE_ASPNETCORE
CorsPolicyOptions = _settings.CorsPolicyOptions?.ToString(),
AcceptAnyClientCertificate = _settings.AcceptAnyClientCertificate,
ClientCertificateMode = _settings.ClientCertificateMode,
AcceptAnyClientCertificate = _settings.AcceptAnyClientCertificate
CorsPolicyOptions = _settings.CorsPolicyOptions?.ToString()
#endif
};
@@ -250,10 +254,16 @@ public partial class WireMockServer
// _settings
_settings.AllowBodyForAllHttpMethods = settings.AllowBodyForAllHttpMethods;
_settings.AllowOnlyDefinedHttpStatusCodeInResponse = settings.AllowOnlyDefinedHttpStatusCodeInResponse;
_settings.AllowPartialMapping = settings.AllowPartialMapping;
_settings.DisableDeserializeFormUrlEncoded = settings.DisableDeserializeFormUrlEncoded;
_settings.DisableJsonBodyParsing = settings.DisableJsonBodyParsing;
_settings.DisableRequestBodyDecompressing = settings.DisableRequestBodyDecompressing;
_settings.DoNotSaveDynamicResponseInLogEntry = settings.DoNotSaveDynamicResponseInLogEntry;
_settings.HandleRequestsSynchronously = settings.HandleRequestsSynchronously;
_settings.MaxRequestLogCount = settings.MaxRequestLogCount;
_settings.ProxyAndRecordSettings = TinyMapperUtils.Instance.Map(settings.ProxyAndRecordSettings);
_settings.QueryParameterMultipleValueSupport = settings.QueryParameterMultipleValueSupport;
_settings.ReadStaticMappings = settings.ReadStaticMappings;
_settings.RequestLogExpirationDuration = settings.RequestLogExpirationDuration;
_settings.SaveUnmatchedRequests = settings.SaveUnmatchedRequests;
@@ -261,8 +271,6 @@ public partial class WireMockServer
_settings.UseRegexExtended = settings.UseRegexExtended;
_settings.WatchStaticMappings = settings.WatchStaticMappings;
_settings.WatchStaticMappingsInSubdirectories = settings.WatchStaticMappingsInSubdirectories;
_settings.DoNotSaveDynamicResponseInLogEntry = settings.DoNotSaveDynamicResponseInLogEntry;
_settings.QueryParameterMultipleValueSupport = settings.QueryParameterMultipleValueSupport;
InitSettings(_settings);
@@ -759,14 +767,18 @@ public partial class WireMockServer
private static T DeserializeObject<T>(IRequestMessage requestMessage) where T : new()
{
return requestMessage.BodyData?.DetectedBodyType switch
switch (requestMessage.BodyData?.DetectedBodyType)
{
BodyType.String => JsonUtils.DeserializeObject<T>(requestMessage.BodyData.BodyAsString!),
case BodyType.String:
case BodyType.FormUrlEncoded:
return JsonUtils.DeserializeObject<T>(requestMessage.BodyData.BodyAsString!);
BodyType.Json when requestMessage.BodyData?.BodyAsJson != null => ((JObject)requestMessage.BodyData.BodyAsJson).ToObject<T>()!,
case BodyType.Json when requestMessage.BodyData?.BodyAsJson != null:
return ((JObject)requestMessage.BodyData.BodyAsJson).ToObject<T>()!;
_ => throw new NotSupportedException()
};
default:
throw new NotSupportedException();
}
}
private static T[] DeserializeRequestMessageToArray<T>(IRequestMessage requestMessage)

View File

@@ -65,7 +65,7 @@ namespace WireMock.Server
}
};
if (BytesEncodingUtils.TryGetEncoding(bytes, out Encoding encoding) && FileBodyIsString.Select(x => x.Equals(encoding)).Any())
if (BytesEncodingUtils.TryGetEncoding(bytes, out var encoding) && FileBodyIsString.Select(x => x.Equals(encoding)).Any())
{
response.BodyData.DetectedBodyType = BodyType.String;
response.BodyData.BodyAsString = encoding.GetString(bytes);

View File

@@ -6,7 +6,7 @@ public class ProxySaveMappingSetting<T>
{
public MatchBehaviour MatchBehaviour { get; } = MatchBehaviour.AcceptOnMatch;
public T Value { get; private set; }
public T Value { get; }
public ProxySaveMappingSetting(T value, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{
@@ -14,7 +14,7 @@ public class ProxySaveMappingSetting<T>
MatchBehaviour = matchBehaviour;
}
public static implicit operator ProxySaveMappingSetting<T>(T value) => new ProxySaveMappingSetting<T>(value);
public static implicit operator ProxySaveMappingSetting<T>(T value) => new(value);
public static implicit operator T(ProxySaveMappingSetting<T> @this) => @this.Value;
}

View File

@@ -32,7 +32,7 @@ internal class SimpleCommandLineParser
}
else if (string.IsNullOrEmpty(currentName))
{
Arguments[arg] = new string[0];
Arguments[arg] = EmptyArray<string>.Value;
}
else
{

View File

@@ -182,13 +182,13 @@ public class WireMockServerSettings
public bool? AllowCSharpCodeMatcher { get; set; }
/// <summary>
/// Allow a Body for all HTTP Methods. (default set to false).
/// Allow a Body for all HTTP Methods. (default set to <c>false</c>).
/// </summary>
[PublicAPI]
public bool? AllowBodyForAllHttpMethods { get; set; }
/// <summary>
/// Allow only a HttpStatus Code in the response which is defined. (default set to false).
/// Allow only a HttpStatus Code in the response which is defined. (default set to <c>false</c>).
/// - false : also null, 0, empty or invalid HttpStatus codes are allowed.
/// - true : only codes defined in <see cref="System.Net.HttpStatusCode"/> are allowed.
/// </summary>
@@ -196,25 +196,31 @@ public class WireMockServerSettings
public bool? AllowOnlyDefinedHttpStatusCodeInResponse { get; set; }
/// <summary>
/// Set to true to disable Json deserialization when processing requests. (default set to false).
/// Set to true to disable Json deserialization when processing requests. (default set to <c>false</c>).
/// </summary>
[PublicAPI]
public bool? DisableJsonBodyParsing { get; set; }
/// <summary>
/// Disable support for GZip and Deflate request body decompression. (default set to false).
/// Disable support for GZip and Deflate request body decompression. (default set to <c>false</c>).
/// </summary>
[PublicAPI]
public bool? DisableRequestBodyDecompressing { get; set; }
/// <summary>
/// Handle all requests synchronously. (default set to false).
/// Set to true to disable FormUrlEncoded deserializing when processing requests. (default set to <c>false</c>).
/// </summary>
[PublicAPI]
public bool? DisableDeserializeFormUrlEncoded { get; set; }
/// <summary>
/// Handle all requests synchronously. (default set to <c>false</c>).
/// </summary>
[PublicAPI]
public bool? HandleRequestsSynchronously { get; set; }
/// <summary>
/// Throw an exception when the <see cref="IMatcher"/> fails because of invalid input. (default set to false).
/// Throw an exception when the <see cref="IMatcher"/> fails because of invalid input. (default set to <c>false</c>).
/// </summary>
[PublicAPI]
public bool? ThrowExceptionWhenMatcherFails { get; set; }
@@ -255,19 +261,19 @@ public class WireMockServerSettings
public WebhookSettings? WebhookSettings { get; set; }
/// <summary>
/// Use the <see cref="RegexExtended"/> instead of the default <see cref="Regex"/> (default set to true).
/// Use the <see cref="RegexExtended"/> instead of the default <see cref="Regex"/> (default set to <c>true</c>).
/// </summary>
[PublicAPI]
public bool? UseRegexExtended { get; set; } = true;
/// <summary>
/// Save unmatched requests to a file using the <see cref="IFileSystemHandler"/> (default set to false).
/// Save unmatched requests to a file using the <see cref="IFileSystemHandler"/> (default set to <c>false</c>).
/// </summary>
[PublicAPI]
public bool? SaveUnmatchedRequests { get; set; }
/// <summary>
/// Don't save the response-string in the LogEntry when WithBody(Func{IRequestMessage, string}) or WithBody(Func{IRequestMessage, Task{string}}) is used. (default set to false).
/// Don't save the response-string in the LogEntry when WithBody(Func{IRequestMessage, string}) or WithBody(Func{IRequestMessage, Task{string}}) is used. (default set to <c>false</c>).
/// </summary>
[PublicAPI]
public bool? DoNotSaveDynamicResponseInLogEntry { get; set; }

View File

@@ -4,7 +4,6 @@ using System.Linq;
using JetBrains.Annotations;
using Stef.Validation;
using WireMock.Logging;
using WireMock.Matchers;
using WireMock.Types;
using WireMock.Util;
@@ -47,6 +46,8 @@ public static class WireMockServerSettingsParser
AllowOnlyDefinedHttpStatusCodeInResponse = parser.GetBoolValue("AllowOnlyDefinedHttpStatusCodeInResponse"),
AllowPartialMapping = parser.GetBoolValue("AllowPartialMapping"),
DisableJsonBodyParsing = parser.GetBoolValue("DisableJsonBodyParsing"),
DisableRequestBodyDecompressing = parser.GetBoolValue(nameof(WireMockServerSettings.DisableRequestBodyDecompressing)),
DisableDeserializeFormUrlEncoded = parser.GetBoolValue(nameof(WireMockServerSettings.DisableDeserializeFormUrlEncoded)),
HandleRequestsSynchronously = parser.GetBoolValue("HandleRequestsSynchronously"),
MaxRequestLogCount = parser.GetIntValue("MaxRequestLogCount"),
ReadStaticMappings = parser.GetBoolValue("ReadStaticMappings"),

View File

@@ -85,7 +85,7 @@ internal class Transformer : ITransformer
{
responseMessage.BodyData = TransformBodyData(transformerContext, options, model, original.BodyData, useTransformerForBodyAsFile);
if (original.BodyData.DetectedBodyType == BodyType.String)
if (original.BodyData.DetectedBodyType is BodyType.String or BodyType.FormUrlEncoded)
{
responseMessage.BodyOriginal = original.BodyData.BodyAsString;
}
@@ -123,13 +123,21 @@ internal class Transformer : ITransformer
private IBodyData? TransformBodyData(ITransformerContext transformerContext, ReplaceNodeOptions options, TransformModel model, IBodyData original, bool useTransformerForBodyAsFile)
{
return original.DetectedBodyType switch
switch (original.DetectedBodyType)
{
BodyType.Json => TransformBodyAsJson(transformerContext, options, model, original),
BodyType.File => TransformBodyAsFile(transformerContext, model, original, useTransformerForBodyAsFile),
BodyType.String => TransformBodyAsString(transformerContext, model, original),
_ => null
};
case BodyType.Json:
return TransformBodyAsJson(transformerContext, options, model, original);
case BodyType.File:
return TransformBodyAsFile(transformerContext, model, original, useTransformerForBodyAsFile);
case BodyType.String:
case BodyType.FormUrlEncoded:
return TransformBodyAsString(transformerContext, model, original);
default:
return null;
}
}
private static IDictionary<string, WireMockList<string>> TransformHeaders(ITransformerContext transformerContext, TransformModel model, IDictionary<string, WireMockList<string>>? original)

View File

@@ -49,12 +49,14 @@ internal static class BodyParser
new WildcardMatcher("application/vnd.*+json", true)
};
private static readonly IStringMatcher FormUrlEncodedMatcher = new WildcardMatcher("application/x-www-form-urlencoded", true);
private static readonly IStringMatcher[] TextContentTypeMatchers =
{
new WildcardMatcher("text/*", true),
new RegexMatcher("^application\\/(java|type)script$", true),
new WildcardMatcher("application/*xml", true),
new WildcardMatcher("application/x-www-form-urlencoded", true)
FormUrlEncodedMatcher
};
public static bool ShouldParseBody(string? httpMethod, bool allowBodyForAllHttpMethods)
@@ -69,7 +71,7 @@ internal static class BodyParser
return true;
}
if (BodyAllowedForMethods.TryGetValue(httpMethod!.ToUpper(), out bool allowed))
if (BodyAllowedForMethods.TryGetValue(httpMethod!.ToUpper(), out var allowed))
{
return allowed;
}
@@ -88,6 +90,11 @@ internal static class BodyParser
return BodyType.Bytes;
}
if (MatchScores.IsPerfect(FormUrlEncodedMatcher.IsMatch(contentType.MediaType)))
{
return BodyType.FormUrlEncoded;
}
if (TextContentTypeMatchers.Any(matcher => MatchScores.IsPerfect(matcher.IsMatch(contentType.MediaType))))
{
return BodyType.String;
@@ -133,13 +140,30 @@ internal static class BodyParser
return data;
}
// Try to get the body as String or Json
// Try to get the body as String, FormUrlEncoded or Json
try
{
data.BodyAsString = DefaultEncoding.GetString(data.BodyAsBytes);
data.Encoding = DefaultEncoding;
data.DetectedBodyType = BodyType.String;
// If string is not null or empty, try to deserialize the string to a IDictionary<string, string>
if (settings.DeserializeFormUrlEncoded &&
data.DetectedBodyTypeFromContentType == BodyType.FormUrlEncoded &&
QueryStringParser.TryParse(data.BodyAsString, false, out var nameValueCollection)
)
{
try
{
data.BodyAsFormUrlEncoded = nameValueCollection;
data.DetectedBodyType = BodyType.FormUrlEncoded;
}
catch
{
// Deserialize FormUrlEncoded failed, just ignore.
}
}
// If string is not null or empty, try to deserialize the string to a JObject
if (settings.DeserializeJson && !string.IsNullOrEmpty(data.BodyAsString))
{

View File

@@ -13,4 +13,6 @@ internal class BodyParserSettings
public bool DecompressGZipAndDeflate { get; set; } = true;
public bool DeserializeJson { get; set; } = true;
public bool DeserializeFormUrlEncoded { get; set; } = true;
}

View File

@@ -16,18 +16,14 @@ internal static class HttpStatusRangeParser
/// <param name="pattern">The pattern. (Can be null, in that case it's allowed.)</param>
/// <param name="httpStatusCode">The value.</param>
/// <exception cref="ArgumentException"><paramref name="pattern"/> is invalid.</exception>
public static bool IsMatch(string pattern, object? httpStatusCode)
public static bool IsMatch(string? pattern, object? httpStatusCode)
{
switch (httpStatusCode)
return httpStatusCode switch
{
case int statusCodeAsInteger:
return IsMatch(pattern, statusCodeAsInteger);
case string statusCodeAsString:
return IsMatch(pattern, int.Parse(statusCodeAsString));
}
return false;
int statusCodeAsInteger => IsMatch(pattern, statusCodeAsInteger),
string statusCodeAsString => IsMatch(pattern, int.Parse(statusCodeAsString)),
_ => false
};
}
/// <summary>

View File

@@ -1,7 +1,8 @@
using System;
using System.Net;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Net;
using WireMock.Types;
namespace WireMock.Util;
@@ -13,6 +14,31 @@ internal static class QueryStringParser
{
private static readonly Dictionary<string, WireMockList<string>> Empty = new();
public static bool TryParse(string? queryString, bool caseIgnore, [NotNullWhen(true)] out IDictionary<string, string>? nameValueCollection)
{
if (queryString is null)
{
nameValueCollection = default;
return false;
}
var parts = queryString!
.Split(new[] { "&" }, StringSplitOptions.RemoveEmptyEntries)
.Select(parameter => parameter.Split('='))
.Distinct();
nameValueCollection = caseIgnore ? new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase) : new Dictionary<string, string>();
foreach (var part in parts)
{
if (part.Length == 2)
{
nameValueCollection.Add(part[0], part[1]);
}
}
return true;
}
public static IDictionary<string, WireMockList<string>> Parse(string? queryString, QueryParameterMultipleValueSupport? support = null)
{
if (string.IsNullOrEmpty(queryString))

View File

@@ -36,14 +36,12 @@ internal static class RegexUtils
{
if (useRegexExtended)
{
var r = new RegexExtended(pattern, RegexOptions.None, RegexTimeOut);
return (true, r.IsMatch(input));
}
else
{
var r = new Regex(pattern, RegexOptions.None, RegexTimeOut);
return (true, r.IsMatch(input));
var regexExtended = new RegexExtended(pattern, RegexOptions.None, RegexTimeOut);
return (true, regexExtended.IsMatch(input));
}
var regex = new Regex(pattern, RegexOptions.None, RegexTimeOut);
return (true, regex.IsMatch(input));
}
catch
{

View File

@@ -1,119 +0,0 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
namespace WireMock.Util;
/// <summary>
/// Code based on https://stackoverflow.com/questions/40507909/convert-jobject-to-anonymous-object
/// </summary>
internal static class TypeBuilderUtils
{
private static readonly ConcurrentDictionary<IDictionary<string, Type>, Type> Types = new();
private static readonly ModuleBuilder ModuleBuilder = AssemblyBuilder
.DefineDynamicAssembly(new AssemblyName("WireMock.Net.Reflection"), AssemblyBuilderAccess.Run)
.DefineDynamicModule("WireMock.Net.Reflection.Module");
public static Type BuildType(IDictionary<string, Type> properties, string? name = null)
{
var keyExists = Types.Keys.FirstOrDefault(k => Compare(k, properties));
if (keyExists != null)
{
return Types[keyExists];
}
var typeBuilder = GetTypeBuilder(name ?? Guid.NewGuid().ToString());
foreach (var property in properties)
{
CreateGetSetMethods(typeBuilder, property.Key, property.Value);
}
var type = typeBuilder.CreateTypeInfo()!.AsType();
Types.TryAdd(properties, type);
return type;
}
/// <summary>
/// https://stackoverflow.com/questions/3804367/testing-for-equality-between-dictionaries-in-c-sharp
/// </summary>
private static bool Compare<TKey, TValue>(IDictionary<TKey, TValue> dict1, IDictionary<TKey, TValue> dict2)
where TKey : notnull
{
if (dict1 == dict2)
{
return true;
}
if (dict1.Count != dict2.Count)
{
return false;
}
var valueComparer = EqualityComparer<TValue>.Default;
foreach (var kvp in dict1)
{
if (!dict2.TryGetValue(kvp.Key, out var value2))
{
return false;
}
if (!valueComparer.Equals(kvp.Value, value2))
{
return false;
}
}
return true;
}
private static TypeBuilder GetTypeBuilder(string name)
{
return ModuleBuilder.DefineType(name,
TypeAttributes.Public |
TypeAttributes.Class |
TypeAttributes.AutoClass |
TypeAttributes.AnsiClass |
TypeAttributes.BeforeFieldInit |
TypeAttributes.AutoLayout,
null);
}
private static void CreateGetSetMethods(TypeBuilder typeBuilder, string propertyName, Type propertyType)
{
var fieldBuilder = typeBuilder.DefineField("_" + propertyName, propertyType, FieldAttributes.Private);
var propertyBuilder = typeBuilder.DefineProperty(propertyName, PropertyAttributes.HasDefault, propertyType, null);
var getPropertyMethodBuilder = typeBuilder.DefineMethod("get_" + propertyName, MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, propertyType, Type.EmptyTypes);
var getIl = getPropertyMethodBuilder.GetILGenerator();
getIl.Emit(OpCodes.Ldarg_0);
getIl.Emit(OpCodes.Ldfld, fieldBuilder);
getIl.Emit(OpCodes.Ret);
var setPropertyMethodBuilder = typeBuilder.DefineMethod("set_" + propertyName, MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, null, new[] { propertyType });
var setIl = setPropertyMethodBuilder.GetILGenerator();
var modifyProperty = setIl.DefineLabel();
var exitSet = setIl.DefineLabel();
setIl.MarkLabel(modifyProperty);
setIl.Emit(OpCodes.Ldarg_0);
setIl.Emit(OpCodes.Ldarg_1);
setIl.Emit(OpCodes.Stfld, fieldBuilder);
setIl.Emit(OpCodes.Nop);
setIl.MarkLabel(exitSet);
setIl.Emit(OpCodes.Ret);
propertyBuilder.SetGetMethod(getPropertyMethodBuilder);
propertyBuilder.SetSetMethod(setPropertyMethodBuilder);
}
}

View File

@@ -1,11 +0,0 @@
using System;
namespace WireMock.Validation
{
/// <summary>
/// To fix 'xxx' is null on at least one execution path. See also https://rules.sonarsource.com/csharp/RSPEC-3900.
/// </summary>
internal class ValidatedNotNullAttribute : Attribute
{
}
}

View File

@@ -176,13 +176,13 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Handlebars.Net.Helpers" Version="2.3.15-preview-01" />
<PackageReference Include="Handlebars.Net.Helpers.DynamicLinq" Version="2.3.15-preview-01" />
<PackageReference Include="Handlebars.Net.Helpers.Humanizer" Version="2.3.15-preview-01" />
<PackageReference Include="Handlebars.Net.Helpers.Json" Version="2.3.15-preview-01" />
<PackageReference Include="Handlebars.Net.Helpers.Random" Version="2.3.15-preview-01" />
<PackageReference Include="Handlebars.Net.Helpers.XPath" Version="2.3.15-preview-01" />
<PackageReference Include="Handlebars.Net.Helpers.Xeger" Version="2.3.15-preview-01" />
<PackageReference Include="Handlebars.Net.Helpers" Version="2.3.15" />
<PackageReference Include="Handlebars.Net.Helpers.DynamicLinq" Version="2.3.15" />
<PackageReference Include="Handlebars.Net.Helpers.Humanizer" Version="2.3.15" />
<PackageReference Include="Handlebars.Net.Helpers.Json" Version="2.3.15" />
<PackageReference Include="Handlebars.Net.Helpers.Random" Version="2.3.15" />
<PackageReference Include="Handlebars.Net.Helpers.XPath" Version="2.3.15" />
<PackageReference Include="Handlebars.Net.Helpers.Xeger" Version="2.3.15" />
</ItemGroup>
<ItemGroup>

View File

@@ -1,3 +1,4 @@
using System;
using Newtonsoft.Json.Linq;
using NFluent;
using WireMock.Matchers;
@@ -37,7 +38,7 @@ public class JmesPathMatcherTests
public void JmesPathMatcher_IsMatch_ByteArray()
{
// Assign
var bytes = new byte[0];
var bytes = EmptyArray<byte>.Value;
var matcher = new JmesPathMatcher("");
// Act

View File

@@ -111,7 +111,7 @@ public class JsonMatcherTests
public void JsonMatcher_IsMatch_ByteArray()
{
// Assign
var bytes = new byte[0];
var bytes = EmptyArray<byte>.Value;
var matcher = new JsonMatcher("");
// Act

View File

@@ -89,7 +89,7 @@ public class JsonPartialMatcherTests
public void JsonPartialMatcher_IsMatch_ByteArray()
{
// Assign
var bytes = new byte[0];
var bytes = EmptyArray<byte>.Value;
var matcher = new JsonPartialMatcher("");
// Act

View File

@@ -89,7 +89,7 @@ public class JsonPartialWildcardMatcherTests
public void JsonPartialWildcardMatcher_IsMatch_ByteArray()
{
// Assign
var bytes = new byte[0];
var bytes = EmptyArray<byte>.Value;
var matcher = new JsonPartialWildcardMatcher("");
// Act

View File

@@ -1,3 +1,4 @@
using System;
using Newtonsoft.Json.Linq;
using NFluent;
using WireMock.Matchers;
@@ -37,7 +38,7 @@ public class JsonPathMatcherTests
public void JsonPathMatcher_IsMatch_ByteArray()
{
// Assign
var bytes = new byte[0];
var bytes = EmptyArray<byte>.Value;
var matcher = new JsonPathMatcher("");
// Act

View File

@@ -431,7 +431,7 @@ public class RequestMessageBodyMatcherTests
// JSON match +++
{json, new RequestMessageBodyMatcher((object? o) => ((dynamic) o!).a == "b"), true},
{json, new RequestMessageBodyMatcher((string? s) => s == json), true},
{json, new RequestMessageBodyMatcher((byte[]? b) => b.SequenceEqual(Encoding.UTF8.GetBytes(json))), true},
{json, new RequestMessageBodyMatcher((byte[]? b) => b?.SequenceEqual(Encoding.UTF8.GetBytes(json)) == true), true},
// JSON no match ---
{json, new RequestMessageBodyMatcher((object? o) => false), false},
@@ -442,7 +442,7 @@ public class RequestMessageBodyMatcherTests
// string match +++
{str, new RequestMessageBodyMatcher((object? o) => o == null), true},
{str, new RequestMessageBodyMatcher((string? s) => s == str), true},
{str, new RequestMessageBodyMatcher((byte[]? b) => b.SequenceEqual(Encoding.UTF8.GetBytes(str))), true},
{str, new RequestMessageBodyMatcher((byte[]? b) => b?.SequenceEqual(Encoding.UTF8.GetBytes(str)) == true), true},
// string no match ---
{str, new RequestMessageBodyMatcher((object? o) => false), false},
@@ -453,13 +453,13 @@ public class RequestMessageBodyMatcherTests
// binary match +++
{bytes, new RequestMessageBodyMatcher((object? o) => o == null), true},
{bytes, new RequestMessageBodyMatcher((string? s) => s == null), true},
{bytes, new RequestMessageBodyMatcher((byte[]? b) => b.SequenceEqual(bytes)), true},
{bytes, new RequestMessageBodyMatcher((byte[]? b) => b?.SequenceEqual(bytes) == true), true},
// binary no match ---
{bytes, new RequestMessageBodyMatcher((object? o) => false), false},
{bytes, new RequestMessageBodyMatcher((string? s) => false), false},
{bytes, new RequestMessageBodyMatcher((byte[]? b) => false), false},
{bytes, new RequestMessageBodyMatcher(), false },
{bytes, new RequestMessageBodyMatcher(), false }
};
}
}

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Text;
using FluentAssertions;
using Newtonsoft.Json;
@@ -55,11 +56,31 @@ namespace WireMock.Net.Tests
Check.That(requestBuilder.GetMatchingScore(request, requestMatchResult)).IsEqualTo(1.0);
}
[Fact]
public void Request_WithBody_FuncFormUrlEncoded()
{
// Assign
var requestBuilder = Request.Create().UsingAnyMethod().WithBody((IDictionary<string, string>? values) => values != null);
// Act
var body = new BodyData
{
BodyAsFormUrlEncoded = new Dictionary<string, string>(),
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);
var requestBuilder = Request.Create().UsingAnyMethod().WithBody((IBodyData? b) => b != null);
// Act
var body = new BodyData
@@ -78,7 +99,7 @@ namespace WireMock.Net.Tests
public void Request_WithBody_FuncByteArray()
{
// Assign
var requestBuilder = Request.Create().UsingAnyMethod().WithBody((byte[] b) => b != null);
var requestBuilder = Request.Create().UsingAnyMethod().WithBody((byte[]? b) => b != null);
// Act
var body = new BodyData

View File

@@ -1,5 +1,5 @@
using FluentAssertions;
using System.Collections.Generic;
using FluentAssertions;
using WireMock.Types;
using WireMock.Util;
using Xunit;
@@ -8,6 +8,36 @@ namespace WireMock.Net.Tests.Util;
public class QueryStringParserTests
{
public static IEnumerable<object?[]> QueryStringTestData => new List<object?[]>
{
new object?[] { null, false, false, null },
new object?[] { string.Empty, false, true, new Dictionary<string, string>() },
new object?[] { "test", false, true, new Dictionary<string, string>() },
new object?[] { "&", false, true, new Dictionary<string, string>() },
new object?[] { "&&", false, true, new Dictionary<string, string>() },
new object?[] { "a=", false, true, new Dictionary<string, string> { { "a", "" } } },
new object?[] { "&a", false, true, new Dictionary<string, string>() },
new object?[] { "&a=", false, true, new Dictionary<string, string> { { "a", "" } } },
new object?[] { "&key1=value1", false, true, new Dictionary<string, string> { { "key1", "value1" } } },
new object?[] { "key1=value1", false, true, new Dictionary<string, string> { { "key1", "value1" } } },
new object?[] { "key1=value1&key2=value2", false, true, new Dictionary<string, string> { { "key1", "value1" }, { "key2", "value2" } } },
new object?[] { "key1=value1&key2=value2&", false, true, new Dictionary<string, string> { { "key1", "value1" }, { "key2", "value2" } } },
new object?[] { "key1=value1&&key2=value2", false, true, new Dictionary<string, string> { { "key1", "value1" }, { "key2", "value2" } } },
new object?[] { "&key1=value1&key2=value2&&", false, true, new Dictionary<string, string> { { "key1", "value1" }, { "key2", "value2" } } },
};
[Theory]
[MemberData(nameof(QueryStringTestData))]
public void TryParse_Should_Parse_QueryString(string queryString, bool caseIgnore, bool expectedResult, IDictionary<string, string> expectedOutput)
{
// Act
var result = QueryStringParser.TryParse(queryString, caseIgnore, out var actual);
// Assert
result.Should().Be(expectedResult);
actual.Should().BeEquivalentTo(expectedOutput);
}
[Fact]
public void Parse_WithNullString()
{

View File

@@ -64,7 +64,7 @@
<PackageReference Include="Moq" Version="4.17.2" />
<PackageReference Include="System.Linq.Dynamic.Core" Version="1.3.1-preview-02" />
<!--<PackageReference Include="System.Linq.Dynamic.Core" Version="1.3.1" />-->
<PackageReference Include="System.Threading" Version="4.3.0" />
<PackageReference Include="RestEase" Version="1.5.7" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />

View File

@@ -1,7 +1,9 @@
#if !NET452
//using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Net.Http.Json;
//using System.Net.Http.Json;
using System.Threading.Tasks;
using FluentAssertions;
using WireMock.Matchers;
@@ -10,61 +12,120 @@ using WireMock.ResponseBuilders;
using WireMock.Server;
using Xunit;
namespace WireMock.Net.Tests
namespace WireMock.Net.Tests;
public partial class WireMockServerTests
{
public partial class WireMockServerTests
public class DummyClass
{
public class DummyClass
{
public string Hi { get; set; }
}
public string? Hi { get; set; }
}
[Fact]
public async Task WireMockServer_WithBodyAsJson_Using_PostAsJsonAsync_And_WildcardMatcher_ShouldMatch()
[Fact]
public async Task WireMockServer_WithBodyAsJson_Using_PostAsJsonAsync_And_WildcardMatcher_ShouldMatch()
{
// Arrange
var server = WireMockServer.Start();
server.Given(
Request.Create().UsingPost().WithPath("/foo").WithBody(new WildcardMatcher("*Hello*"))
)
.RespondWith(
Response.Create().WithStatusCode(200)
);
var jsonObject = new DummyClass
{
// Arrange
var server = WireMockServer.Start();
server.Given(
Request.Create().UsingPost().WithPath("/foo").WithBody(new WildcardMatcher("*Hello*"))
Hi = "Hello World!"
};
// Act
var response = await new HttpClient().PostAsJsonAsync("http://localhost:" + server.Ports[0] + "/foo", jsonObject).ConfigureAwait(false);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
server.Stop();
}
[Fact]
public async Task WireMockServer_WithBodyAsJson_Using_PostAsync_And_WildcardMatcher_ShouldMatch()
{
// Arrange
var server = WireMockServer.Start();
server.Given(
Request.Create().UsingPost().WithPath("/foo").WithBody(new WildcardMatcher("*Hello*"))
)
.RespondWith(
Response.Create().WithStatusCode(200)
);
// Act
var response = await new HttpClient().PostAsync("http://localhost:" + server.Ports[0] + "/foo", new StringContent("{ Hi = \"Hello World\" }")).ConfigureAwait(false);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
server.Stop();
}
[Fact]
public async Task WireMockServer_WithBodyAsFormUrlEncoded_Using_PostAsync_And_WithFunc()
{
// Arrange
var server = WireMockServer.Start();
server.Given(
Request.Create()
.UsingPost()
.WithPath("/foo")
.WithBody(values => values != null && values["key1"] == "value1")
)
.RespondWith(
Response.Create()
);
// Act
var content = new FormUrlEncodedContent(new[] { new KeyValuePair<string, string>("key1", "value1") });
var response = await new HttpClient()
.PostAsync($"{server.Url}/foo", content)
.ConfigureAwait(false);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
server.Stop();
}
[Fact]
public async Task WireMockServer_WithBodyAsFormUrlEncoded_Using_PostAsync_And_WithExactMatcher()
{
// Arrange
var server = WireMockServer.Start();
server.Given(
Request.Create()
.UsingPost()
.WithPath("/foo")
.WithHeader("Content-Type", "application/x-www-form-urlencoded")
.WithBody(new ExactMatcher("name=John+Doe&email=johndoe%40example.com")
)
.RespondWith(
Response.Create().WithStatusCode(200)
);
)
.RespondWith(
Response.Create()
);
var jsonObject = new DummyClass
{
Hi = "Hello World!"
};
// Act
var response = await new HttpClient().PostAsJsonAsync("http://localhost:" + server.Ports[0] + "/foo", jsonObject).ConfigureAwait(false);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
server.Stop();
}
[Fact]
public async Task WireMockServer_WithBodyAsJson_Using_PostAsync_And_WildcardMatcher_ShouldMatch()
// Act
var content = new FormUrlEncodedContent(new[]
{
// Arrange
var server = WireMockServer.Start();
server.Given(
Request.Create().UsingPost().WithPath("/foo").WithBody(new WildcardMatcher("*Hello*"))
)
.RespondWith(
Response.Create().WithStatusCode(200)
);
new KeyValuePair<string, string>("name", "John Doe"),
new KeyValuePair<string, string>("email", "johndoe@example.com")
});
var response = await new HttpClient()
.PostAsync($"{server.Url}/foo", content)
.ConfigureAwait(false);
// Act
var response = await new HttpClient().PostAsync("http://localhost:" + server.Ports[0] + "/foo", new StringContent("{ Hi = \"Hello World\" }")).ConfigureAwait(false);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
server.Stop();
}
server.Stop();
}
}
#endif