Fix WithBody when using Pact and added more nullable annotations (#783)

* More nullable annotations

* .

* .

* FIX

* pact

* .

* p

* xxx

* ...

* auth

* array

* ...
This commit is contained in:
Stef Heyenrath
2022-08-11 10:57:33 +02:00
committed by GitHub
parent b1af37f044
commit d2a1d0f069
87 changed files with 2578 additions and 2455 deletions
+1
View File
@@ -25,6 +25,7 @@
<s:Boolean x:Key="/Default/UserDictionary/Words/=Victoor/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=Victoor/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Webhook/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=Webhook/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Webhooks/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=Webhooks/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=wiremock/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=wiremockserver/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=wiremockserver/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=xunit/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=xunit/@EntryIndexedValue">True</s:Boolean>
</wpf:ResourceDictionary> </wpf:ResourceDictionary>
@@ -1,24 +1,23 @@
namespace WireMock.Admin.Mappings namespace WireMock.Admin.Mappings;
/// <summary>
/// WebProxy settings
/// </summary>
[FluentBuilder.AutoGenerateBuilder]
public class WebProxyModel
{ {
/// <summary> /// <summary>
/// WebProxy settings /// A string instance that contains the address of the proxy server.
/// </summary> /// </summary>
[FluentBuilder.AutoGenerateBuilder] public string Address { get; set; } = null!;
public class WebProxyModel
{
/// <summary>
/// A string instance that contains the address of the proxy server.
/// </summary>
public string Address { get; set; }
/// <summary> /// <summary>
/// The user name associated with the credentials. /// The user name associated with the credentials. [optional]
/// </summary> /// </summary>
public string UserName { get; set; } public string? UserName { get; set; }
/// <summary> /// <summary>
/// The password for the user name associated with the credentials. /// The password for the user name associated with the credentials. [optional]
/// </summary> /// </summary>
public string Password { get; set; } public string? Password { get; set; }
}
} }
@@ -1,52 +1,50 @@
using System.Collections.Generic; using System.Collections.Generic;
using WireMock.Types;
namespace WireMock.Admin.Mappings namespace WireMock.Admin.Mappings;
/// <summary>
/// RequestModel
/// </summary>
[FluentBuilder.AutoGenerateBuilder]
public class WebhookRequestModel
{ {
/// <summary> /// <summary>
/// RequestModel /// Gets or sets the Url.
/// </summary> /// </summary>
[FluentBuilder.AutoGenerateBuilder] public string Url { get; set; }
public class WebhookRequestModel
{
/// <summary>
/// Gets or sets the Url.
/// </summary>
public string Url { get; set; }
/// <summary> /// <summary>
/// The method /// The method
/// </summary> /// </summary>
public string Method { get; set; } public string Method { get; set; }
/// <summary> /// <summary>
/// Gets or sets the headers. /// Gets or sets the headers.
/// </summary> /// </summary>
public IDictionary<string, string>? Headers { get; set; } public IDictionary<string, string>? Headers { get; set; }
/// <summary> /// <summary>
/// Gets or sets the body. /// Gets or sets the body.
/// </summary> /// </summary>
public string? Body { get; set; } public string? Body { get; set; }
/// <summary> /// <summary>
/// Gets or sets the body (as JSON object). /// Gets or sets the body (as JSON object).
/// </summary> /// </summary>
public object? BodyAsJson { get; set; } public object? BodyAsJson { get; set; }
/// <summary> /// <summary>
/// Use ResponseMessage Transformer. /// Use ResponseMessage Transformer.
/// </summary> /// </summary>
public bool? UseTransformer { get; set; } public bool? UseTransformer { get; set; }
/// <summary> /// <summary>
/// Gets the type of the transformer. /// Gets the type of the transformer.
/// </summary> /// </summary>
public string TransformerType { get; set; } public string? TransformerType { get; set; }
/// <summary> /// <summary>
/// The ReplaceNodeOptions to use when transforming a JSON node. /// The ReplaceNodeOptions to use when transforming a JSON node.
/// </summary> /// </summary>
public string TransformerReplaceNodeOptions { get; set; } public string? TransformerReplaceNodeOptions { get; set; }
}
} }
@@ -63,12 +63,12 @@ public interface IRequestMessage
/// <summary> /// <summary>
/// Gets the headers. /// Gets the headers.
/// </summary> /// </summary>
IDictionary<string, WireMockList<string>> Headers { get; } IDictionary<string, WireMockList<string>>? Headers { get; }
/// <summary> /// <summary>
/// Gets the cookies. /// Gets the cookies.
/// </summary> /// </summary>
IDictionary<string, string> Cookies { get; } IDictionary<string, string>? Cookies { get; }
/// <summary> /// <summary>
/// Gets the query. /// Gets the query.
@@ -3,60 +3,59 @@ using WireMock.ResponseBuilders;
using WireMock.Types; using WireMock.Types;
using WireMock.Util; using WireMock.Util;
namespace WireMock namespace WireMock;
/// <summary>
/// IResponseMessage
/// </summary>
public interface IResponseMessage
{ {
/// <summary> /// <summary>
/// IResponseMessage /// The Body.
/// </summary> /// </summary>
public interface IResponseMessage IBodyData? BodyData { get; }
{
/// <summary>
/// The Body.
/// </summary>
IBodyData? BodyData { get; }
/// <summary> /// <summary>
/// Gets the body destination (SameAsSource, String or Bytes). /// Gets the body destination (Null, SameAsSource, String or Bytes).
/// </summary> /// </summary>
string BodyDestination { get; } string? BodyDestination { get; }
/// <summary> /// <summary>
/// Gets or sets the body. /// Gets or sets the body.
/// </summary> /// </summary>
string BodyOriginal { get; } string? BodyOriginal { get; }
/// <summary> /// <summary>
/// Gets the Fault percentage. /// Gets the Fault percentage.
/// </summary> /// </summary>
double? FaultPercentage { get; } double? FaultPercentage { get; }
/// <summary> /// <summary>
/// The FaultType. /// The FaultType.
/// </summary> /// </summary>
FaultType FaultType { get; } FaultType FaultType { get; }
/// <summary> /// <summary>
/// Gets the headers. /// Gets the headers.
/// </summary> /// </summary>
IDictionary<string, WireMockList<string>>? Headers { get; } IDictionary<string, WireMockList<string>>? Headers { get; }
/// <summary> /// <summary>
/// Gets or sets the status code. /// Gets or sets the status code.
/// </summary> /// </summary>
object StatusCode { get; } object? StatusCode { get; }
/// <summary> /// <summary>
/// Adds the header. /// Adds the header.
/// </summary> /// </summary>
/// <param name="name">The name.</param> /// <param name="name">The name.</param>
/// <param name="value">The value.</param> /// <param name="value">The value.</param>
void AddHeader(string name, string value); void AddHeader(string name, string value);
/// <summary> /// <summary>
/// Adds the header. /// Adds the header.
/// </summary> /// </summary>
/// <param name="name">The name.</param> /// <param name="name">The name.</param>
/// <param name="values">The values.</param> /// <param name="values">The values.</param>
void AddHeader(string name, params string[] values); void AddHeader(string name, params string[] values);
}
} }
@@ -1,56 +1,55 @@
using System; using System;
using WireMock.Matchers.Request; using WireMock.Matchers.Request;
namespace WireMock.Logging namespace WireMock.Logging;
/// <summary>
/// ILogEntry
/// </summary>
public interface ILogEntry
{ {
/// <summary> /// <summary>
/// ILogEntry /// Gets the unique identifier.
/// </summary> /// </summary>
public interface ILogEntry Guid Guid { get; }
{
/// <summary>
/// Gets the unique identifier.
/// </summary>
Guid Guid { get; }
/// <summary> /// <summary>
/// Gets the mapping unique identifier. /// Gets the mapping unique identifier.
/// </summary> /// </summary>
Guid? MappingGuid { get; } Guid? MappingGuid { get; }
/// <summary> /// <summary>
/// Gets the mapping unique title. /// Gets the mapping unique title.
/// </summary> /// </summary>
string MappingTitle { get; } string? MappingTitle { get; }
/// <summary> /// <summary>
/// Gets the partial mapping unique identifier. /// Gets the partial mapping unique identifier.
/// </summary> /// </summary>
Guid? PartialMappingGuid { get; } Guid? PartialMappingGuid { get; }
/// <summary> /// <summary>
/// Gets the partial mapping unique title. /// Gets the partial mapping unique title.
/// </summary> /// </summary>
string PartialMappingTitle { get; } string? PartialMappingTitle { get; }
/// <summary> /// <summary>
/// Gets the partial match result. /// Gets the partial match result.
/// </summary> /// </summary>
IRequestMatchResult PartialMatchResult { get; } IRequestMatchResult PartialMatchResult { get; }
/// <summary> /// <summary>
/// Gets the request match result. /// Gets the request match result.
/// </summary> /// </summary>
IRequestMatchResult RequestMatchResult { get; } IRequestMatchResult RequestMatchResult { get; }
/// <summary> /// <summary>
/// Gets the request message. /// Gets the request message.
/// </summary> /// </summary>
IRequestMessage RequestMessage { get; } IRequestMessage RequestMessage { get; }
/// <summary> /// <summary>
/// Gets the response message. /// Gets the response message.
/// </summary> /// </summary>
IResponseMessage ResponseMessage { get; } IResponseMessage ResponseMessage { get; }
}
} }
@@ -1,13 +1,12 @@
namespace WireMock.Models namespace WireMock.Models;
/// <summary>
/// IWebhook
/// </summary>
public interface IWebhook
{ {
/// <summary> /// <summary>
/// IWebhook /// Request
/// </summary> /// </summary>
public interface IWebhook IWebhookRequest Request { get; set; }
{
/// <summary>
/// Request
/// </summary>
IWebhookRequest Request { get; set; }
}
} }
@@ -59,7 +59,7 @@ internal class CSharpCodeMatcher : ICSharpCodeMatcher
MatchOperator = matchOperator; MatchOperator = matchOperator;
} }
public double IsMatch(string input) public double IsMatch(string? input)
{ {
return IsMatchInternal(input); return IsMatchInternal(input);
} }
@@ -1,4 +1,5 @@
#if !NETSTANDARD1_3 #if !NETSTANDARD1_3
using System;
using System.Globalization; using System.Globalization;
using System.IdentityModel.Tokens.Jwt; using System.IdentityModel.Tokens.Jwt;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
@@ -6,67 +7,72 @@ using AnyOfTypes;
using Microsoft.IdentityModel.Protocols; using Microsoft.IdentityModel.Protocols;
using Microsoft.IdentityModel.Protocols.OpenIdConnect; using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.IdentityModel.Tokens; using Microsoft.IdentityModel.Tokens;
using Stef.Validation;
using WireMock.Matchers; using WireMock.Matchers;
using WireMock.Models; using WireMock.Models;
namespace WireMock.Authentication namespace WireMock.Authentication;
/// <summary>
/// https://www.c-sharpcorner.com/article/how-to-validate-azure-ad-token-using-console-application/
/// https://stackoverflow.com/questions/38684865/validation-of-an-azure-ad-bearer-token-in-a-console-application
/// </summary>
internal class AzureADAuthenticationMatcher : IStringMatcher
{ {
/// <summary> private const string BearerPrefix = "Bearer ";
/// https://www.c-sharpcorner.com/article/how-to-validate-azure-ad-token-using-console-application/
/// https://stackoverflow.com/questions/38684865/validation-of-an-azure-ad-bearer-token-in-a-console-application private readonly string _audience;
/// </summary> private readonly string _stsDiscoveryEndpoint;
internal class AzureADAuthenticationMatcher : IStringMatcher
public AzureADAuthenticationMatcher(string tenant, string audience)
{ {
private const string BearerPrefix = "Bearer "; _audience = Guard.NotNullOrEmpty(audience);
_stsDiscoveryEndpoint = string.Format(CultureInfo.InvariantCulture, "https://login.microsoftonline.com/{0}/.well-known/openid-configuration", Guard.NotNullOrEmpty(tenant));
}
private readonly string _audience; public string Name => nameof(AzureADAuthenticationMatcher);
private readonly string _stsDiscoveryEndpoint;
public AzureADAuthenticationMatcher(string tenant, string audience) public MatchBehaviour MatchBehaviour => MatchBehaviour.AcceptOnMatch;
public bool ThrowException => false;
public AnyOf<string, StringPattern>[] GetPatterns()
{
return EmptyArray<AnyOf<string, StringPattern>>.Value;
}
public MatchOperator MatchOperator { get; } = MatchOperator.Or;
public double IsMatch(string? input)
{
if (string.IsNullOrEmpty(input))
{ {
_audience = audience; return MatchScores.Mismatch;
_stsDiscoveryEndpoint = string.Format(CultureInfo.InvariantCulture, "https://login.microsoftonline.com/{0}/.well-known/openid-configuration", tenant);
} }
public string Name => nameof(AzureADAuthenticationMatcher); var token = Regex.Replace(input, BearerPrefix, string.Empty, RegexOptions.IgnoreCase);
public MatchBehaviour MatchBehaviour => MatchBehaviour.AcceptOnMatch; try
public bool ThrowException => false;
public AnyOf<string, StringPattern>[] GetPatterns()
{ {
return new AnyOf<string, StringPattern>[0]; var configManager = new ConfigurationManager<OpenIdConnectConfiguration>(_stsDiscoveryEndpoint, new OpenIdConnectConfigurationRetriever());
var config = configManager.GetConfigurationAsync().ConfigureAwait(false).GetAwaiter().GetResult();
var validationParameters = new TokenValidationParameters
{
ValidAudience = _audience,
ValidIssuer = config.Issuer,
IssuerSigningKeys = config.SigningKeys,
ValidateLifetime = true
};
// Throws an Exception as the token is invalid (expired, invalid-formatted, etc.)
new JwtSecurityTokenHandler().ValidateToken(token, validationParameters, out var _);
return MatchScores.Perfect;
} }
catch
public MatchOperator MatchOperator { get; } = MatchOperator.Or;
public double IsMatch(string input)
{ {
var token = Regex.Replace(input, BearerPrefix, string.Empty, RegexOptions.IgnoreCase); return MatchScores.Mismatch;
try
{
var configManager = new ConfigurationManager<OpenIdConnectConfiguration>(_stsDiscoveryEndpoint, new OpenIdConnectConfigurationRetriever());
var config = configManager.GetConfigurationAsync().ConfigureAwait(false).GetAwaiter().GetResult();
var validationParameters = new TokenValidationParameters
{
ValidAudience = _audience,
ValidIssuer = config.Issuer,
IssuerSigningKeys = config.SigningKeys,
ValidateLifetime = true
};
// Throws an Exception as the token is invalid (expired, invalid-formatted, etc.)
new JwtSecurityTokenHandler().ValidateToken(token, validationParameters, out var _);
return MatchScores.Perfect;
}
catch
{
return MatchScores.Mismatch;
}
} }
} }
} }
@@ -2,19 +2,18 @@ using System;
using System.Text; using System.Text;
using WireMock.Matchers; using WireMock.Matchers;
namespace WireMock.Authentication namespace WireMock.Authentication;
internal class BasicAuthenticationMatcher : RegexMatcher
{ {
internal class BasicAuthenticationMatcher : RegexMatcher public BasicAuthenticationMatcher(string username, string password) : base(BuildPattern(username, password))
{ {
public BasicAuthenticationMatcher(string username, string password) : base(BuildPattern(username, password)) }
{
}
public override string Name => nameof(BasicAuthenticationMatcher); public override string Name => nameof(BasicAuthenticationMatcher);
private static string BuildPattern(string username, string password) private static string BuildPattern(string username, string password)
{ {
return "^(?i)BASIC " + Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes(username + ":" + password)) + "$"; return "^(?i)BASIC " + Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes(username + ":" + password)) + "$";
}
} }
} }
@@ -0,0 +1,11 @@
// ReSharper disable once CheckNamespace
namespace System;
internal static class EmptyArray<T>
{
#if NET451 || NET452
public static readonly T[] Value = new T[0];
#else
public static readonly T[] Value = Array.Empty<T>();
#endif
}
+18 -20
View File
@@ -1,28 +1,26 @@
#if NETSTANDARD1_3 #if NETSTANDARD1_3
using System;
using System.Net;
namespace System.Net // ReSharper disable once CheckNamespace
namespace System.Net;
internal class WebProxy : IWebProxy
{ {
internal class WebProxy : IWebProxy private readonly string _proxy;
public ICredentials? Credentials { get; set; }
public WebProxy(string proxy)
{ {
private readonly string _proxy; _proxy = proxy;
public ICredentials Credentials { get; set; } }
public WebProxy(string proxy) public Uri GetProxy(Uri destination)
{ {
_proxy = proxy; return new Uri(_proxy);
} }
public Uri GetProxy(Uri destination) public bool IsBypassed(Uri host)
{ {
return new Uri(_proxy); return true;
}
public bool IsBypassed(Uri host)
{
return true;
}
} }
} }
#endif #endif
+13 -15
View File
@@ -1,26 +1,24 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using AnyOfTypes; using AnyOfTypes;
using JetBrains.Annotations;
using WireMock.Models; using WireMock.Models;
namespace WireMock.Extensions namespace WireMock.Extensions;
internal static class AnyOfExtensions
{ {
internal static class AnyOfExtensions public static string GetPattern(this AnyOf<string, StringPattern> value)
{ {
public static string GetPattern([NotNull] this AnyOf<string, StringPattern> value) return value.IsFirst ? value.First : value.Second.Pattern;
{ }
return value.IsFirst ? value.First : value.Second.Pattern;
}
public static AnyOf<string, StringPattern>[] ToAnyOfPatterns([NotNull] this IEnumerable<string> patterns) public static AnyOf<string, StringPattern>[] ToAnyOfPatterns(this IEnumerable<string> patterns)
{ {
return patterns.Select(p => p.ToAnyOfPattern()).ToArray(); return patterns.Select(p => p.ToAnyOfPattern()).ToArray();
} }
public static AnyOf<string, StringPattern> ToAnyOfPattern([CanBeNull] this string pattern) public static AnyOf<string, StringPattern> ToAnyOfPattern(this string pattern)
{ {
return new AnyOf<string, StringPattern>(pattern); return new AnyOf<string, StringPattern>(pattern);
}
} }
} }
+19 -21
View File
@@ -1,30 +1,28 @@
using System.Net.Http; using System.Net.Http;
using System.Net.Http.Headers; using System.Net.Http.Headers;
using JetBrains.Annotations;
using Stef.Validation; using Stef.Validation;
namespace WireMock.Http namespace WireMock.Http;
internal static class ByteArrayContentHelper
{ {
internal static class ByteArrayContentHelper /// <summary>
/// Creates a ByteArrayContent object.
/// </summary>
/// <param name="content">The byte[] content (cannot be null)</param>
/// <param name="contentType">The ContentType (can be null)</param>
/// <returns>ByteArrayContent</returns>
internal static ByteArrayContent Create(byte[] content, MediaTypeHeaderValue? contentType)
{ {
/// <summary> Guard.NotNull(content);
/// Creates a ByteArrayContent object.
/// </summary> var byteContent = new ByteArrayContent(content);
/// <param name="content">The byte[] content (cannot be null)</param> if (contentType != null)
/// <param name="contentType">The ContentType (can be null)</param>
/// <returns>ByteArrayContent</returns>
internal static ByteArrayContent Create([NotNull] byte[] content, [CanBeNull] MediaTypeHeaderValue contentType)
{ {
Guard.NotNull(content, nameof(content)); byteContent.Headers.Remove(HttpKnownHeaderNames.ContentType);
byteContent.Headers.ContentType = contentType;
var byteContent = new ByteArrayContent(content);
if (contentType != null)
{
byteContent.Headers.Remove(HttpKnownHeaderNames.ContentType);
byteContent.Headers.ContentType = contentType;
}
return byteContent;
} }
return byteContent;
} }
} }
+110 -111
View File
@@ -1,122 +1,121 @@
// Licensed to the .NET Foundation under one or more agreements. // Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System; using System;
using System.Linq; using System.Linq;
namespace WireMock.Http namespace WireMock.Http;
/// <summary>
/// Copied from https://raw.githubusercontent.com/dotnet/corefx/master/src/Common/src/System/Net/HttpKnownHeaderNames.cs
/// </summary>
internal static class HttpKnownHeaderNames
{ {
/// <summary> // https://docs.microsoft.com/en-us/dotnet/api/system.net.webheadercollection.isrestricted
/// Copied from https://raw.githubusercontent.com/dotnet/corefx/master/src/Common/src/System/Net/HttpKnownHeaderNames.cs private static readonly string[] RestrictedResponseHeaders =
/// </summary>
internal static class HttpKnownHeaderNames
{ {
// https://docs.microsoft.com/en-us/dotnet/api/system.net.webheadercollection.isrestricted Accept,
private static readonly string[] RestrictedResponseHeaders = Connection,
{ ContentLength,
Accept, ContentType,
Connection, Date, // RFC1123Pattern
ContentLength, Expect,
ContentType, Host,
Date, // RFC1123Pattern IfModifiedSince,
Expect, Range,
Host, Referer,
IfModifiedSince, TransferEncoding,
Range, UserAgent,
Referer, ProxyConnection
TransferEncoding, };
UserAgent,
ProxyConnection
};
/// <summary>Tests whether the specified HTTP header can be set for the response.</summary> /// <summary>Tests whether the specified HTTP header can be set for the response.</summary>
/// <param name="headerName">The header to test.</param> /// <param name="headerName">The header to test.</param>
/// <returns>true if the header is restricted; otherwise, false.</returns> /// <returns>true if the header is restricted; otherwise, false.</returns>
public static bool IsRestrictedResponseHeader(string headerName) => RestrictedResponseHeaders.Contains(headerName, StringComparer.OrdinalIgnoreCase); public static bool IsRestrictedResponseHeader(string headerName) => RestrictedResponseHeaders.Contains(headerName, StringComparer.OrdinalIgnoreCase);
public const string Accept = "Accept"; public const string Accept = "Accept";
public const string AcceptCharset = "Accept-Charset"; public const string AcceptCharset = "Accept-Charset";
public const string AcceptEncoding = "Accept-Encoding"; public const string AcceptEncoding = "Accept-Encoding";
public const string AcceptLanguage = "Accept-Language"; public const string AcceptLanguage = "Accept-Language";
public const string AcceptPatch = "Accept-Patch"; public const string AcceptPatch = "Accept-Patch";
public const string AcceptRanges = "Accept-Ranges"; public const string AcceptRanges = "Accept-Ranges";
public const string AccessControlAllowCredentials = "Access-Control-Allow-Credentials"; public const string AccessControlAllowCredentials = "Access-Control-Allow-Credentials";
public const string AccessControlAllowHeaders = "Access-Control-Allow-Headers"; public const string AccessControlAllowHeaders = "Access-Control-Allow-Headers";
public const string AccessControlAllowMethods = "Access-Control-Allow-Methods"; public const string AccessControlAllowMethods = "Access-Control-Allow-Methods";
public const string AccessControlAllowOrigin = "Access-Control-Allow-Origin"; public const string AccessControlAllowOrigin = "Access-Control-Allow-Origin";
public const string AccessControlExposeHeaders = "Access-Control-Expose-Headers"; public const string AccessControlExposeHeaders = "Access-Control-Expose-Headers";
public const string AccessControlMaxAge = "Access-Control-Max-Age"; public const string AccessControlMaxAge = "Access-Control-Max-Age";
public const string Age = "Age"; public const string Age = "Age";
public const string Allow = "Allow"; public const string Allow = "Allow";
public const string AltSvc = "Alt-Svc"; public const string AltSvc = "Alt-Svc";
public const string Authorization = "Authorization"; public const string Authorization = "Authorization";
public const string CacheControl = "Cache-Control"; public const string CacheControl = "Cache-Control";
public const string Connection = "Connection"; public const string Connection = "Connection";
public const string ContentDisposition = "Content-Disposition"; public const string ContentDisposition = "Content-Disposition";
public const string ContentEncoding = "Content-Encoding"; public const string ContentEncoding = "Content-Encoding";
public const string ContentLanguage = "Content-Language"; public const string ContentLanguage = "Content-Language";
public const string ContentLength = "Content-Length"; public const string ContentLength = "Content-Length";
public const string ContentLocation = "Content-Location"; public const string ContentLocation = "Content-Location";
public const string ContentMD5 = "Content-MD5"; public const string ContentMD5 = "Content-MD5";
public const string ContentRange = "Content-Range"; public const string ContentRange = "Content-Range";
public const string ContentSecurityPolicy = "Content-Security-Policy"; public const string ContentSecurityPolicy = "Content-Security-Policy";
public const string ContentType = "Content-Type"; public const string ContentType = "Content-Type";
public const string Cookie = "Cookie"; public const string Cookie = "Cookie";
public const string Cookie2 = "Cookie2"; public const string Cookie2 = "Cookie2";
public const string Date = "Date"; public const string Date = "Date";
public const string ETag = "ETag"; public const string ETag = "ETag";
public const string Expect = "Expect"; public const string Expect = "Expect";
public const string Expires = "Expires"; public const string Expires = "Expires";
public const string From = "From"; public const string From = "From";
public const string Host = "Host"; public const string Host = "Host";
public const string IfMatch = "If-Match"; public const string IfMatch = "If-Match";
public const string IfModifiedSince = "If-Modified-Since"; public const string IfModifiedSince = "If-Modified-Since";
public const string IfNoneMatch = "If-None-Match"; public const string IfNoneMatch = "If-None-Match";
public const string IfRange = "If-Range"; public const string IfRange = "If-Range";
public const string IfUnmodifiedSince = "If-Unmodified-Since"; public const string IfUnmodifiedSince = "If-Unmodified-Since";
public const string KeepAlive = "Keep-Alive"; public const string KeepAlive = "Keep-Alive";
public const string LastModified = "Last-Modified"; public const string LastModified = "Last-Modified";
public const string Link = "Link"; public const string Link = "Link";
public const string Location = "Location"; public const string Location = "Location";
public const string MaxForwards = "Max-Forwards"; public const string MaxForwards = "Max-Forwards";
public const string Origin = "Origin"; public const string Origin = "Origin";
public const string P3P = "P3P"; public const string P3P = "P3P";
public const string Pragma = "Pragma"; public const string Pragma = "Pragma";
public const string ProxyAuthenticate = "Proxy-Authenticate"; public const string ProxyAuthenticate = "Proxy-Authenticate";
public const string ProxyAuthorization = "Proxy-Authorization"; public const string ProxyAuthorization = "Proxy-Authorization";
public const string ProxyConnection = "Proxy-Connection"; public const string ProxyConnection = "Proxy-Connection";
public const string PublicKeyPins = "Public-Key-Pins"; public const string PublicKeyPins = "Public-Key-Pins";
public const string Range = "Range"; public const string Range = "Range";
public const string Referer = "Referer"; // NB: The spelling-mistake "Referer" for "Referrer" must be matched. public const string Referer = "Referer"; // NB: The spelling-mistake "Referer" for "Referrer" must be matched.
public const string RetryAfter = "Retry-After"; public const string RetryAfter = "Retry-After";
public const string SecWebSocketAccept = "Sec-WebSocket-Accept"; public const string SecWebSocketAccept = "Sec-WebSocket-Accept";
public const string SecWebSocketExtensions = "Sec-WebSocket-Extensions"; public const string SecWebSocketExtensions = "Sec-WebSocket-Extensions";
public const string SecWebSocketKey = "Sec-WebSocket-Key"; public const string SecWebSocketKey = "Sec-WebSocket-Key";
public const string SecWebSocketProtocol = "Sec-WebSocket-Protocol"; public const string SecWebSocketProtocol = "Sec-WebSocket-Protocol";
public const string SecWebSocketVersion = "Sec-WebSocket-Version"; public const string SecWebSocketVersion = "Sec-WebSocket-Version";
public const string Server = "Server"; public const string Server = "Server";
public const string SetCookie = "Set-Cookie"; public const string SetCookie = "Set-Cookie";
public const string SetCookie2 = "Set-Cookie2"; public const string SetCookie2 = "Set-Cookie2";
public const string StrictTransportSecurity = "Strict-Transport-Security"; public const string StrictTransportSecurity = "Strict-Transport-Security";
public const string TE = "TE"; public const string TE = "TE";
public const string TSV = "TSV"; public const string TSV = "TSV";
public const string Trailer = "Trailer"; public const string Trailer = "Trailer";
public const string TransferEncoding = "Transfer-Encoding"; public const string TransferEncoding = "Transfer-Encoding";
public const string Upgrade = "Upgrade"; public const string Upgrade = "Upgrade";
public const string UpgradeInsecureRequests = "Upgrade-Insecure-Requests"; public const string UpgradeInsecureRequests = "Upgrade-Insecure-Requests";
public const string UserAgent = "User-Agent"; public const string UserAgent = "User-Agent";
public const string Vary = "Vary"; public const string Vary = "Vary";
public const string Via = "Via"; public const string Via = "Via";
public const string WWWAuthenticate = "WWW-Authenticate"; public const string WWWAuthenticate = "WWW-Authenticate";
public const string Warning = "Warning"; public const string Warning = "Warning";
public const string XAspNetVersion = "X-AspNet-Version"; public const string XAspNetVersion = "X-AspNet-Version";
public const string XContentDuration = "X-Content-Duration"; public const string XContentDuration = "X-Content-Duration";
public const string XContentTypeOptions = "X-Content-Type-Options"; public const string XContentTypeOptions = "X-Content-Type-Options";
public const string XFrameOptions = "X-Frame-Options"; public const string XFrameOptions = "X-Frame-Options";
public const string XMSEdgeRef = "X-MSEdge-Ref"; public const string XMSEdgeRef = "X-MSEdge-Ref";
public const string XPoweredBy = "X-Powered-By"; public const string XPoweredBy = "X-Powered-By";
public const string XRequestID = "X-Request-ID"; public const string XRequestID = "X-Request-ID";
public const string XUACompatible = "X-UA-Compatible"; public const string XUACompatible = "X-UA-Compatible";
}
} }
@@ -3,89 +3,87 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Net.Http.Headers; using System.Net.Http.Headers;
using JetBrains.Annotations;
using Newtonsoft.Json; using Newtonsoft.Json;
using WireMock.Types;
using Stef.Validation; using Stef.Validation;
using WireMock.Types;
namespace WireMock.Http namespace WireMock.Http;
internal static class HttpRequestMessageHelper
{ {
internal static class HttpRequestMessageHelper internal static HttpRequestMessage Create(IRequestMessage requestMessage, string url)
{ {
internal static HttpRequestMessage Create([NotNull] IRequestMessage requestMessage, [NotNull] string url) Guard.NotNull(requestMessage);
Guard.NotNullOrEmpty(url);
var httpRequestMessage = new HttpRequestMessage(new HttpMethod(requestMessage.Method), url);
MediaTypeHeaderValue? contentType = null;
if (requestMessage.Headers != null && requestMessage.Headers.ContainsKey(HttpKnownHeaderNames.ContentType))
{ {
Guard.NotNull(requestMessage, nameof(requestMessage)); var value = requestMessage.Headers[HttpKnownHeaderNames.ContentType].FirstOrDefault();
Guard.NotNullOrEmpty(url, nameof(url)); MediaTypeHeaderValue.TryParse(value, out contentType);
}
var httpRequestMessage = new HttpRequestMessage(new HttpMethod(requestMessage.Method), url); switch (requestMessage.BodyData?.DetectedBodyType)
{
case BodyType.Bytes:
httpRequestMessage.Content = ByteArrayContentHelper.Create(requestMessage.BodyData.BodyAsBytes, contentType);
break;
MediaTypeHeaderValue contentType = null; case BodyType.Json:
if (requestMessage.Headers != null && requestMessage.Headers.ContainsKey(HttpKnownHeaderNames.ContentType)) httpRequestMessage.Content = StringContentHelper.Create(JsonConvert.SerializeObject(requestMessage.BodyData.BodyAsJson), contentType);
{ break;
var value = requestMessage.Headers[HttpKnownHeaderNames.ContentType].FirstOrDefault();
MediaTypeHeaderValue.TryParse(value, out contentType);
}
switch (requestMessage.BodyData?.DetectedBodyType) case BodyType.String:
{ httpRequestMessage.Content = StringContentHelper.Create(requestMessage.BodyData.BodyAsString, contentType);
case BodyType.Bytes: break;
httpRequestMessage.Content = ByteArrayContentHelper.Create(requestMessage.BodyData.BodyAsBytes, contentType); }
break;
case BodyType.Json: // Overwrite the host header
httpRequestMessage.Content = StringContentHelper.Create(JsonConvert.SerializeObject(requestMessage.BodyData.BodyAsJson), contentType); httpRequestMessage.Headers.Host = new Uri(url).Authority;
break;
case BodyType.String:
httpRequestMessage.Content = StringContentHelper.Create(requestMessage.BodyData.BodyAsString, contentType);
break;
}
// Overwrite the host header
httpRequestMessage.Headers.Host = new Uri(url).Authority;
// Set other headers if present
if (requestMessage.Headers == null || requestMessage.Headers.Count == 0)
{
return httpRequestMessage;
}
var excludeHeaders = new List<string> { HttpKnownHeaderNames.Host, HttpKnownHeaderNames.ContentLength };
if (contentType != null)
{
// Content-Type should be set on the content
excludeHeaders.Add(HttpKnownHeaderNames.ContentType);
}
foreach (var header in requestMessage.Headers.Where(h => !excludeHeaders.Contains(h.Key, StringComparer.OrdinalIgnoreCase)))
{
// Skip if already added. We need to ToList() else calling httpRequestMessage.Headers.Contains() with a header starting with a ":" throws an exception.
if (httpRequestMessage.Headers.ToList().Any(h => string.Equals(h.Key, header.Key, StringComparison.OrdinalIgnoreCase)))
{
continue;
}
// Skip if already added. We need to ToList() else calling httpRequestMessage.Content.Headers.Contains(...) with a header starting with a ":" throws an exception.
if (httpRequestMessage.Content != null && httpRequestMessage.Content.Headers.ToList().Any(h => string.Equals(h.Key, header.Key, StringComparison.OrdinalIgnoreCase)))
{
continue;
}
// Try to add to request headers. If failed - try to add to content headers. If still fails, just ignore this header.
try
{
if (!httpRequestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value))
{
httpRequestMessage.Content?.Headers.TryAddWithoutValidation(header.Key, header.Value);
}
}
catch
{
// Just continue
}
}
// Set other headers if present
if (requestMessage.Headers == null || requestMessage.Headers.Count == 0)
{
return httpRequestMessage; return httpRequestMessage;
} }
var excludeHeaders = new List<string> { HttpKnownHeaderNames.Host, HttpKnownHeaderNames.ContentLength };
if (contentType != null)
{
// Content-Type should be set on the content
excludeHeaders.Add(HttpKnownHeaderNames.ContentType);
}
foreach (var header in requestMessage.Headers.Where(h => !excludeHeaders.Contains(h.Key, StringComparer.OrdinalIgnoreCase)))
{
// Skip if already added. We need to ToList() else calling httpRequestMessage.Headers.Contains() with a header starting with a ":" throws an exception.
if (httpRequestMessage.Headers.ToList().Any(h => string.Equals(h.Key, header.Key, StringComparison.OrdinalIgnoreCase)))
{
continue;
}
// Skip if already added. We need to ToList() else calling httpRequestMessage.Content.Headers.Contains(...) with a header starting with a ":" throws an exception.
if (httpRequestMessage.Content != null && httpRequestMessage.Content.Headers.ToList().Any(h => string.Equals(h.Key, header.Key, StringComparison.OrdinalIgnoreCase)))
{
continue;
}
// Try to add to request headers. If failed - try to add to content headers. If still fails, just ignore this header.
try
{
if (!httpRequestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value))
{
httpRequestMessage.Content?.Headers.TryAddWithoutValidation(header.Key, header.Value);
}
}
catch
{
// Just continue
}
}
return httpRequestMessage;
} }
} }
+15 -16
View File
@@ -1,18 +1,17 @@
namespace WireMock.Http namespace WireMock.Http;
/// <summary>
/// https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods
/// </summary>
internal static class HttpRequestMethods
{ {
/// <summary> public const string CONNECT = "CONNECT";
/// https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods public const string DELETE = "DELETE";
/// </summary> public const string GET = "GET";
internal static class HttpRequestMethods public const string HEAD = "HEAD";
{ public const string OPTIONS = "OPTIONS";
public const string CONNECT = "CONNECT"; public const string PATCH = "PATCH";
public const string DELETE = "DELETE"; public const string POST = "POST";
public const string GET = "GET"; public const string PUT = "PUT";
public const string HEAD = "HEAD"; public const string TRACE = "TRACE";
public const string OPTIONS = "OPTIONS";
public const string PATCH = "PATCH";
public const string POST = "POST";
public const string PUT = "PUT";
public const string TRACE = "TRACE";
}
} }
+17 -19
View File
@@ -1,25 +1,23 @@
using System.Net.Http; using System.Net.Http;
using System.Net.Http.Headers; using System.Net.Http.Headers;
using JetBrains.Annotations;
using Stef.Validation; using Stef.Validation;
namespace WireMock.Http namespace WireMock.Http;
{
internal static class StringContentHelper
{
/// <summary>
/// Creates a StringContent object.
/// </summary>
/// <param name="content">The string content (cannot be null)</param>
/// <param name="contentType">The ContentType (can be null)</param>
/// <returns>StringContent</returns>
internal static StringContent Create([NotNull] string content, [CanBeNull] MediaTypeHeaderValue contentType)
{
Guard.NotNull(content, nameof(content));
var stringContent = new StringContent(content); internal static class StringContentHelper
stringContent.Headers.ContentType = contentType; {
return stringContent; /// <summary>
} /// Creates a StringContent object.
/// </summary>
/// <param name="content">The string content (cannot be null)</param>
/// <param name="contentType">The ContentType (can be null)</param>
/// <returns>StringContent</returns>
internal static StringContent Create(string content, MediaTypeHeaderValue? contentType)
{
Guard.NotNull(content);
var stringContent = new StringContent(content);
stringContent.Headers.ContentType = contentType;
return stringContent;
} }
} }
+57 -59
View File
@@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using JetBrains.Annotations; using Stef.Validation;
using WireMock.Models; using WireMock.Models;
using WireMock.Settings; using WireMock.Settings;
using WireMock.Transformers; using WireMock.Transformers;
@@ -11,75 +11,73 @@ using WireMock.Transformers.Handlebars;
using WireMock.Transformers.Scriban; using WireMock.Transformers.Scriban;
using WireMock.Types; using WireMock.Types;
using WireMock.Util; using WireMock.Util;
using Stef.Validation;
namespace WireMock.Http namespace WireMock.Http;
internal class WebhookSender
{ {
internal class WebhookSender private const string ClientIp = "::1";
private readonly WireMockServerSettings _settings;
public WebhookSender(WireMockServerSettings settings)
{ {
private const string ClientIp = "::1"; _settings = Guard.NotNull(settings);
}
private readonly WireMockServerSettings _settings; public Task<HttpResponseMessage> SendAsync(HttpClient client, IWebhookRequest request, IRequestMessage originalRequestMessage, IResponseMessage originalResponseMessage)
{
Guard.NotNull(client);
Guard.NotNull(request);
Guard.NotNull(originalRequestMessage);
Guard.NotNull(originalResponseMessage);
public WebhookSender(WireMockServerSettings settings) IBodyData? bodyData;
IDictionary<string, WireMockList<string>>? headers;
if (request.UseTransformer == true)
{ {
_settings = settings ?? throw new ArgumentNullException(nameof(settings)); ITransformer responseMessageTransformer;
} switch (request.TransformerType)
public Task<HttpResponseMessage> SendAsync(HttpClient client, IWebhookRequest request, IRequestMessage originalRequestMessage, IResponseMessage originalResponseMessage)
{
Guard.NotNull(client);
Guard.NotNull(request);
Guard.NotNull(originalRequestMessage);
Guard.NotNull(originalResponseMessage);
IBodyData? bodyData;
IDictionary<string, WireMockList<string>>? headers;
if (request.UseTransformer == true)
{ {
ITransformer responseMessageTransformer; case TransformerType.Handlebars:
switch (request.TransformerType) var factoryHandlebars = new HandlebarsContextFactory(_settings.FileSystemHandler, _settings.HandlebarsRegistrationCallback);
{ responseMessageTransformer = new Transformer(factoryHandlebars);
case TransformerType.Handlebars: break;
var factoryHandlebars = new HandlebarsContextFactory(_settings.FileSystemHandler, _settings.HandlebarsRegistrationCallback);
responseMessageTransformer = new Transformer(factoryHandlebars);
break;
case TransformerType.Scriban: case TransformerType.Scriban:
case TransformerType.ScribanDotLiquid: case TransformerType.ScribanDotLiquid:
var factoryDotLiquid = new ScribanContextFactory(_settings.FileSystemHandler, request.TransformerType); var factoryDotLiquid = new ScribanContextFactory(_settings.FileSystemHandler, request.TransformerType);
responseMessageTransformer = new Transformer(factoryDotLiquid); responseMessageTransformer = new Transformer(factoryDotLiquid);
break; break;
default: default:
throw new NotImplementedException($"TransformerType '{request.TransformerType}' is not supported."); throw new NotImplementedException($"TransformerType '{request.TransformerType}' is not supported.");
}
(bodyData, headers) = responseMessageTransformer.Transform(originalRequestMessage, originalResponseMessage, request.BodyData, request.Headers, request.TransformerReplaceNodeOptions);
}
else
{
bodyData = request.BodyData;
headers = request.Headers;
} }
// Create RequestMessage (bodyData, headers) = responseMessageTransformer.Transform(originalRequestMessage, originalResponseMessage, request.BodyData, request.Headers, request.TransformerReplaceNodeOptions);
var requestMessage = new RequestMessage(
new UrlDetails(request.Url),
request.Method,
ClientIp,
bodyData,
headers?.ToDictionary(x => x.Key, x => x.Value.ToArray())
)
{
DateTime = DateTime.UtcNow
};
// Create HttpRequestMessage
var httpRequestMessage = HttpRequestMessageHelper.Create(requestMessage, request.Url);
// Call the URL
return client.SendAsync(httpRequestMessage);
} }
else
{
bodyData = request.BodyData;
headers = request.Headers;
}
// Create RequestMessage
var requestMessage = new RequestMessage(
new UrlDetails(request.Url),
request.Method,
ClientIp,
bodyData,
headers?.ToDictionary(x => x.Key, x => x.Value.ToArray())
)
{
DateTime = DateTime.UtcNow
};
// Create HttpRequestMessage
var httpRequestMessage = HttpRequestMessageHelper.Create(requestMessage, request.Url);
// Call the URL
return client.SendAsync(httpRequestMessage);
} }
} }
@@ -2,90 +2,40 @@ using System;
using System.IO; using System.IO;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
namespace WireMock.HttpsCertificate namespace WireMock.HttpsCertificate;
internal static class CertificateLoader
{ {
internal static class CertificateLoader /// <summary>
/// Used by the WireMock.Net server
/// </summary>
public static X509Certificate2 LoadCertificate(
string storeName,
string storeLocation,
string thumbprintOrSubjectName,
string filePath,
string password,
string host)
{ {
/// <summary> if (!string.IsNullOrEmpty(storeName) && !string.IsNullOrEmpty(storeLocation))
/// Used by the WireMock.Net server
/// </summary>
public static X509Certificate2 LoadCertificate(
string storeName,
string storeLocation,
string thumbprintOrSubjectName,
string filePath,
string password,
string host)
{ {
if (!string.IsNullOrEmpty(storeName) && !string.IsNullOrEmpty(storeLocation)) var thumbprintOrSubjectNameOrHost = thumbprintOrSubjectName ?? host;
{
var thumbprintOrSubjectNameOrHost = thumbprintOrSubjectName ?? host;
var certStore = new X509Store((StoreName)Enum.Parse(typeof(StoreName), storeName), (StoreLocation)Enum.Parse(typeof(StoreLocation), storeLocation)); var certStore = new X509Store((StoreName)Enum.Parse(typeof(StoreName), storeName), (StoreLocation)Enum.Parse(typeof(StoreLocation), storeLocation));
try
{
certStore.Open(OpenFlags.ReadOnly);
// Attempt to find by Thumbprint first
var matchingCertificates = certStore.Certificates.Find(X509FindType.FindByThumbprint, thumbprintOrSubjectNameOrHost, false);
if (matchingCertificates.Count == 0)
{
// Fallback to SubjectName
matchingCertificates = certStore.Certificates.Find(X509FindType.FindBySubjectName, thumbprintOrSubjectNameOrHost, false);
if (matchingCertificates.Count == 0)
{
// No certificates matched the search criteria.
throw new FileNotFoundException($"No Certificate found with in store '{storeName}', location '{storeLocation}' for Thumbprint or SubjectName '{thumbprintOrSubjectNameOrHost}'.");
}
}
// Use the first matching certificate.
return matchingCertificates[0];
}
finally
{
#if NETSTANDARD || NET46
certStore.Dispose();
#else
certStore.Close();
#endif
}
}
if (!string.IsNullOrEmpty(filePath) && !string.IsNullOrEmpty(password))
{
return new X509Certificate2(filePath, password);
}
if (!string.IsNullOrEmpty(filePath))
{
return new X509Certificate2(filePath);
}
throw new InvalidOperationException("X509StoreName and X509StoreLocation OR X509CertificateFilePath are mandatory. Note that X509CertificatePassword is optional.");
}
/// <summary>
/// Used for Proxy
/// </summary>
public static X509Certificate2 LoadCertificate(string thumbprintOrSubjectName)
{
var certStore = new X509Store(StoreName.My, StoreLocation.LocalMachine);
try try
{ {
// Certificate must be in the local machine store
certStore.Open(OpenFlags.ReadOnly); certStore.Open(OpenFlags.ReadOnly);
// Attempt to find by Thumbprint first // Attempt to find by Thumbprint first
var matchingCertificates = certStore.Certificates.Find(X509FindType.FindByThumbprint, thumbprintOrSubjectName, false); var matchingCertificates = certStore.Certificates.Find(X509FindType.FindByThumbprint, thumbprintOrSubjectNameOrHost, false);
if (matchingCertificates.Count == 0) if (matchingCertificates.Count == 0)
{ {
// Fallback to SubjectName // Fallback to SubjectName
matchingCertificates = certStore.Certificates.Find(X509FindType.FindBySubjectName, thumbprintOrSubjectName, false); matchingCertificates = certStore.Certificates.Find(X509FindType.FindBySubjectName, thumbprintOrSubjectNameOrHost, false);
if (matchingCertificates.Count == 0) if (matchingCertificates.Count == 0)
{ {
// No certificates matched the search criteria. // No certificates matched the search criteria.
throw new FileNotFoundException("No certificate found with specified Thumbprint or SubjectName.", thumbprintOrSubjectName); throw new FileNotFoundException($"No Certificate found with in store '{storeName}', location '{storeLocation}' for Thumbprint or SubjectName '{thumbprintOrSubjectNameOrHost}'.");
} }
} }
@@ -97,9 +47,58 @@ namespace WireMock.HttpsCertificate
#if NETSTANDARD || NET46 #if NETSTANDARD || NET46
certStore.Dispose(); certStore.Dispose();
#else #else
certStore.Close(); certStore.Close();
#endif #endif
} }
} }
if (!string.IsNullOrEmpty(filePath) && !string.IsNullOrEmpty(password))
{
return new X509Certificate2(filePath, password);
}
if (!string.IsNullOrEmpty(filePath))
{
return new X509Certificate2(filePath);
}
throw new InvalidOperationException("X509StoreName and X509StoreLocation OR X509CertificateFilePath are mandatory. Note that X509CertificatePassword is optional.");
}
/// <summary>
/// Used for Proxy
/// </summary>
public static X509Certificate2 LoadCertificate(string thumbprintOrSubjectName)
{
var certStore = new X509Store(StoreName.My, StoreLocation.LocalMachine);
try
{
// Certificate must be in the local machine store
certStore.Open(OpenFlags.ReadOnly);
// Attempt to find by Thumbprint first
var matchingCertificates = certStore.Certificates.Find(X509FindType.FindByThumbprint, thumbprintOrSubjectName, false);
if (matchingCertificates.Count == 0)
{
// Fallback to SubjectName
matchingCertificates = certStore.Certificates.Find(X509FindType.FindBySubjectName, thumbprintOrSubjectName, false);
if (matchingCertificates.Count == 0)
{
// No certificates matched the search criteria.
throw new FileNotFoundException("No certificate found with specified Thumbprint or SubjectName.", thumbprintOrSubjectName);
}
}
// Use the first matching certificate.
return matchingCertificates[0];
}
finally
{
#if NETSTANDARD || NET46
certStore.Dispose();
#else
certStore.Close();
#endif
}
} }
} }
@@ -1,16 +1,16 @@
using System; using System;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
namespace WireMock.HttpsCertificate namespace WireMock.HttpsCertificate;
/// <summary>
/// Only used for NetStandard 1.3
/// </summary>
internal static class PublicCertificateHelper
{ {
/// <summary> // 1] Generate using https://www.pluralsight.com/blog/software-development/selfcert-create-a-self-signed-certificate-interactively-gui-or-programmatically-in-net
/// Only used for NetStandard 1.3 // 2] Converted to Base64
/// </summary> private const string Data = @"MIIQMgIBAzCCD+4GCSqGSIb3DQEHAaCCD98Egg/bMIIP1zCCCogGCSqGSIb3DQEHAaCCCnkEggp1
internal static class PublicCertificateHelper
{
// 1] Generate using https://www.pluralsight.com/blog/software-development/selfcert-create-a-self-signed-certificate-interactively-gui-or-programmatically-in-net
// 2] Converted to Base64
private const string Data = @"MIIQMgIBAzCCD+4GCSqGSIb3DQEHAaCCD98Egg/bMIIP1zCCCogGCSqGSIb3DQEHAaCCCnkEggp1
MIIKcTCCCm0GCyqGSIb3DQEMCgECoIIJfjCCCXowHAYKKoZIhvcNAQwBAzAOBAi1j9x1jTfUewIC MIIKcTCCCm0GCyqGSIb3DQEMCgECoIIJfjCCCXowHAYKKoZIhvcNAQwBAzAOBAi1j9x1jTfUewIC
B9AEgglYa48lP16+isiGEVT7zwN3XwaPwPOHZcQ7tRA/DA8LZnZbwU7XhtPObF5bZcHn4engX2An B9AEgglYa48lP16+isiGEVT7zwN3XwaPwPOHZcQ7tRA/DA8LZnZbwU7XhtPObF5bZcHn4engX2An
ISFpe2S5XJ7BfHmsGOO7Bxj6C2IcZIPTefvAd9vWE0WUAGN11SLhJ3fB/ZRt3Nys7JCJzywQCkYK ISFpe2S5XJ7BfHmsGOO7Bxj6C2IcZIPTefvAd9vWE0WUAGN11SLhJ3fB/ZRt3Nys7JCJzywQCkYK
@@ -85,10 +85,9 @@ TLNGa+UmMnPsnBjlAJ6l9VPsa4uJM2DIQKtZXWq4DkhSAYKF6joIP7nKMDswHzAHBgUrDgMCGgQU
wTM1Z+CJZG9xAcf1zAVGl4ggYyYEFGBFyJ8VBwijS2zy1qwN1WYGtcWoAgIH0A== wTM1Z+CJZG9xAcf1zAVGl4ggYyYEFGBFyJ8VBwijS2zy1qwN1WYGtcWoAgIH0A==
"; ";
public static X509Certificate2 GetX509Certificate2() public static X509Certificate2 GetX509Certificate2()
{ {
byte[] data = Convert.FromBase64String(Data); byte[] data = Convert.FromBase64String(Data);
return new X509Certificate2(data); return new X509Certificate2(data);
}
} }
} }
+1 -1
View File
@@ -117,7 +117,7 @@ public interface IMapping
/// </summary> /// </summary>
/// <param name="requestMessage">The request message.</param> /// <param name="requestMessage">The request message.</param>
/// <returns>The <see cref="ResponseMessage"/> including a new (optional) <see cref="IMapping"/>.</returns> /// <returns>The <see cref="ResponseMessage"/> including a new (optional) <see cref="IMapping"/>.</returns>
Task<(IResponseMessage Message, IMapping Mapping)> ProvideResponseAsync(IRequestMessage requestMessage); Task<(IResponseMessage Message, IMapping? Mapping)> ProvideResponseAsync(IRequestMessage requestMessage);
/// <summary> /// <summary>
/// Gets the RequestMatchResult based on the RequestMessage. /// Gets the RequestMatchResult based on the RequestMessage.
+25 -26
View File
@@ -1,38 +1,37 @@
using System; using System;
using WireMock.Matchers.Request; using WireMock.Matchers.Request;
namespace WireMock.Logging namespace WireMock.Logging;
/// <summary>
/// LogEntry
/// </summary>
public class LogEntry : ILogEntry
{ {
/// <summary> /// <inheritdoc cref="ILogEntry.Guid" />
/// LogEntry public Guid Guid { get; set; }
/// </summary>
public class LogEntry : ILogEntry
{
/// <inheritdoc cref="ILogEntry.Guid" />
public Guid Guid { get; set; }
/// <inheritdoc cref="ILogEntry.RequestMessage" /> /// <inheritdoc cref="ILogEntry.RequestMessage" />
public IRequestMessage RequestMessage { get; set; } public IRequestMessage RequestMessage { get; set; }
/// <inheritdoc cref="ILogEntry.ResponseMessage" /> /// <inheritdoc cref="ILogEntry.ResponseMessage" />
public IResponseMessage ResponseMessage { get; set; } public IResponseMessage ResponseMessage { get; set; }
/// <inheritdoc cref="ILogEntry.RequestMatchResult" /> /// <inheritdoc cref="ILogEntry.RequestMatchResult" />
public IRequestMatchResult RequestMatchResult { get; set; } public IRequestMatchResult RequestMatchResult { get; set; }
/// <inheritdoc cref="ILogEntry.MappingGuid" /> /// <inheritdoc cref="ILogEntry.MappingGuid" />
public Guid? MappingGuid { get; set; } public Guid? MappingGuid { get; set; }
/// <inheritdoc cref="ILogEntry.MappingTitle" /> /// <inheritdoc cref="ILogEntry.MappingTitle" />
public string MappingTitle { get; set; } public string? MappingTitle { get; set; }
/// <inheritdoc cref="ILogEntry.PartialMappingGuid" /> /// <inheritdoc cref="ILogEntry.PartialMappingGuid" />
public Guid? PartialMappingGuid { get; set; } public Guid? PartialMappingGuid { get; set; }
/// <inheritdoc cref="ILogEntry.PartialMappingTitle" /> /// <inheritdoc cref="ILogEntry.PartialMappingTitle" />
public string PartialMappingTitle { get; set; } public string? PartialMappingTitle { get; set; }
/// <inheritdoc cref="ILogEntry.PartialMatchResult" /> /// <inheritdoc cref="ILogEntry.PartialMatchResult" />
public IRequestMatchResult PartialMatchResult { get; set; } public IRequestMatchResult PartialMatchResult { get; set; }
}
} }
+15 -15
View File
@@ -15,14 +15,14 @@ public class Mapping : IMapping
/// <inheritdoc /> /// <inheritdoc />
public Guid Guid { get; } public Guid Guid { get; }
/// <inheritdoc /> /// <inheritdoc />
public string? Title { get; } public string? Title { get; }
/// <inheritdoc /> /// <inheritdoc />
public string? Description { get; } public string? Description { get; }
/// <inheritdoc /> /// <inheritdoc />
public string? Path { get; set; } public string? Path { get; set; }
/// <inheritdoc /> /// <inheritdoc />
public int Priority { get; } public int Priority { get; }
@@ -115,16 +115,16 @@ public class Mapping : IMapping
TimeSettings = timeSettings; TimeSettings = timeSettings;
} }
/// <inheritdoc cref="IMapping.ProvideResponseAsync" /> /// <inheritdoc cref="IMapping.ProvideResponseAsync" />
public Task<(IResponseMessage Message, IMapping Mapping)> ProvideResponseAsync(IRequestMessage requestMessage) public Task<(IResponseMessage Message, IMapping? Mapping)> ProvideResponseAsync(IRequestMessage requestMessage)
{ {
return Provider.ProvideResponseAsync(requestMessage, Settings); return Provider.ProvideResponseAsync(requestMessage, Settings);
} }
/// <inheritdoc cref="IMapping.GetRequestMatchResult" /> /// <inheritdoc cref="IMapping.GetRequestMatchResult" />
public IRequestMatchResult GetRequestMatchResult(IRequestMessage requestMessage, string? nextState) public IRequestMatchResult GetRequestMatchResult(IRequestMessage requestMessage, string? nextState)
{ {
var result = new RequestMatchResult(); var result = new RequestMatchResult();
RequestMatcher.GetMatchingScore(requestMessage, result); RequestMatcher.GetMatchingScore(requestMessage, result);
@@ -18,7 +18,7 @@ public class ContentTypeMatcher : WildcardMatcher
/// </summary> /// </summary>
/// <param name="pattern">The pattern.</param> /// <param name="pattern">The pattern.</param>
/// <param name="ignoreCase">IgnoreCase (default false)</param> /// <param name="ignoreCase">IgnoreCase (default false)</param>
public ContentTypeMatcher([NotNull] AnyOf<string, StringPattern> pattern, bool ignoreCase = false) : this(new[] { pattern }, ignoreCase) public ContentTypeMatcher(AnyOf<string, StringPattern> pattern, bool ignoreCase = false) : this(new[] { pattern }, ignoreCase)
{ {
} }
@@ -28,7 +28,7 @@ public class ContentTypeMatcher : WildcardMatcher
/// <param name="matchBehaviour">The match behaviour.</param> /// <param name="matchBehaviour">The match behaviour.</param>
/// <param name="pattern">The pattern.</param> /// <param name="pattern">The pattern.</param>
/// <param name="ignoreCase">IgnoreCase (default false)</param> /// <param name="ignoreCase">IgnoreCase (default false)</param>
public ContentTypeMatcher(MatchBehaviour matchBehaviour, [NotNull] AnyOf<string, StringPattern> pattern, bool ignoreCase = false) : this(matchBehaviour, new[] { pattern }, ignoreCase) public ContentTypeMatcher(MatchBehaviour matchBehaviour, AnyOf<string, StringPattern> pattern, bool ignoreCase = false) : this(matchBehaviour, new[] { pattern }, ignoreCase)
{ {
} }
@@ -57,7 +57,7 @@ public class ContentTypeMatcher : WildcardMatcher
/// <inheritdoc cref="RegexMatcher.IsMatch"/> /// <inheritdoc cref="RegexMatcher.IsMatch"/>
public override double IsMatch(string? input) public override double IsMatch(string? input)
{ {
if (string.IsNullOrEmpty(input) || !MediaTypeHeaderValue.TryParse(input, out MediaTypeHeaderValue contentType)) if (string.IsNullOrEmpty(input) || !MediaTypeHeaderValue.TryParse(input, out var contentType))
{ {
return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.Mismatch); return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.Mismatch);
} }
@@ -1,11 +1,10 @@
namespace WireMock.Matchers namespace WireMock.Matchers;
/// <summary>
/// CSharpCode / CS-Script Matcher
/// </summary>
/// <inheritdoc cref="IObjectMatcher"/>
/// <inheritdoc cref="IStringMatcher"/>
public interface ICSharpCodeMatcher : IObjectMatcher, IStringMatcher
{ {
/// <summary>
/// CSharpCode / CS-Script Matcher
/// </summary>
/// <inheritdoc cref="IObjectMatcher"/>
/// <inheritdoc cref="IStringMatcher"/>
public interface ICSharpCodeMatcher : IObjectMatcher, IStringMatcher
{
}
} }
@@ -1,14 +1,13 @@
namespace WireMock.Matchers namespace WireMock.Matchers;
/// <summary>
/// IIgnoreCaseMatcher
/// </summary>
/// <inheritdoc cref="IMatcher"/>
public interface IIgnoreCaseMatcher : IMatcher
{ {
/// <summary> /// <summary>
/// IIgnoreCaseMatcher /// Ignore the case from the pattern.
/// </summary> /// </summary>
/// <inheritdoc cref="IMatcher"/> bool IgnoreCase { get; }
public interface IIgnoreCaseMatcher : IMatcher
{
/// <summary>
/// Ignore the case from the pattern.
/// </summary>
bool IgnoreCase { get; }
}
} }
+16 -17
View File
@@ -1,23 +1,22 @@
namespace WireMock.Matchers namespace WireMock.Matchers;
/// <summary>
/// IMatcher
/// </summary>
public interface IMatcher
{ {
/// <summary> /// <summary>
/// IMatcher /// Gets the name.
/// </summary> /// </summary>
public interface IMatcher string Name { get; }
{
/// <summary>
/// Gets the name.
/// </summary>
string Name { get; }
/// <summary> /// <summary>
/// Gets the match behaviour. /// Gets the match behaviour.
/// </summary> /// </summary>
MatchBehaviour MatchBehaviour { get; } MatchBehaviour MatchBehaviour { get; }
/// <summary> /// <summary>
/// Should this matcher throw an exception? /// Should this matcher throw an exception?
/// </summary> /// </summary>
bool ThrowException { get; } bool ThrowException { get; }
}
} }
+10 -11
View File
@@ -1,15 +1,14 @@
namespace WireMock.Matchers namespace WireMock.Matchers;
/// <summary>
/// IObjectMatcher
/// </summary>
public interface IObjectMatcher : IMatcher
{ {
/// <summary> /// <summary>
/// IObjectMatcher /// Determines whether the specified input is match.
/// </summary> /// </summary>
public interface IObjectMatcher : IMatcher /// <param name="input">The input.</param>
{ /// <returns>A value between 0.0 - 1.0 of the similarity.</returns>
/// <summary> double IsMatch(object? input);
/// Determines whether the specified input is match.
/// </summary>
/// <param name="input">The input.</param>
/// <returns>A value between 0.0 - 1.0 of the similarity.</returns>
double IsMatch(object? input);
}
} }
+10 -11
View File
@@ -1,15 +1,14 @@
namespace WireMock.Matchers namespace WireMock.Matchers;
/// <summary>
/// IValueMatcher
/// </summary>
/// <seealso cref="IObjectMatcher" />
public interface IValueMatcher : IObjectMatcher
{ {
/// <summary> /// <summary>
/// IValueMatcher /// Gets the value (can be a string or an object).
/// </summary> /// </summary>
/// <seealso cref="IObjectMatcher" /> /// <returns>Value</returns>
public interface IValueMatcher : IObjectMatcher object Value { get; }
{
/// <summary>
/// Gets the value (can be a string or an object).
/// </summary>
/// <returns>Value</returns>
object Value { get; }
}
} }
+105 -106
View File
@@ -6,115 +6,114 @@ using Stef.Validation;
using WireMock.Extensions; using WireMock.Extensions;
using WireMock.Models; using WireMock.Models;
namespace WireMock.Matchers namespace WireMock.Matchers;
/// <summary>
/// http://jmespath.org/
/// </summary>
public class JmesPathMatcher : IStringMatcher, IObjectMatcher
{ {
private readonly AnyOf<string, StringPattern>[] _patterns;
/// <inheritdoc cref="IMatcher.MatchBehaviour"/>
public MatchBehaviour MatchBehaviour { get; }
/// <inheritdoc cref="IMatcher.ThrowException"/>
public bool ThrowException { get; }
/// <summary> /// <summary>
/// http://jmespath.org/ /// Initializes a new instance of the <see cref="JmesPathMatcher"/> class.
/// </summary> /// </summary>
public class JmesPathMatcher : IStringMatcher, IObjectMatcher /// <param name="patterns">The patterns.</param>
public JmesPathMatcher(params string[] patterns) : this(MatchBehaviour.AcceptOnMatch, false, MatchOperator.Or, patterns.ToAnyOfPatterns())
{ {
private readonly AnyOf<string, StringPattern>[] _patterns;
/// <inheritdoc cref="IMatcher.MatchBehaviour"/>
public MatchBehaviour MatchBehaviour { get; }
/// <inheritdoc cref="IMatcher.ThrowException"/>
public bool ThrowException { get; }
/// <summary>
/// Initializes a new instance of the <see cref="JmesPathMatcher"/> class.
/// </summary>
/// <param name="patterns">The patterns.</param>
public JmesPathMatcher(params string[] patterns) : this(MatchBehaviour.AcceptOnMatch, false, MatchOperator.Or, patterns.ToAnyOfPatterns())
{
}
/// <summary>
/// Initializes a new instance of the <see cref="JmesPathMatcher"/> class.
/// </summary>
/// <param name="patterns">The patterns.</param>
public JmesPathMatcher(params AnyOf<string, StringPattern>[] patterns) : this(MatchBehaviour.AcceptOnMatch, false, MatchOperator.Or, patterns)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="JmesPathMatcher"/> class.
/// </summary>
/// <param name="throwException">Throw an exception when the internal matching fails because of invalid input.</param>
/// <param name="matchOperator">The <see cref="Matchers.MatchOperator"/> to use.</param>
/// <param name="patterns">The patterns.</param>
public JmesPathMatcher(bool throwException = false, MatchOperator matchOperator = MatchOperator.Or, params AnyOf<string, StringPattern>[] patterns) :
this(MatchBehaviour.AcceptOnMatch, throwException, matchOperator, patterns)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="JmesPathMatcher"/> class.
/// </summary>
/// <param name="matchBehaviour">The match behaviour.</param>
/// <param name="throwException">Throw an exception when the internal matching fails because of invalid input.</param>
/// <param name="matchOperator">The <see cref="Matchers.MatchOperator"/> to use.</param>
/// <param name="patterns">The patterns.</param>
public JmesPathMatcher(
MatchBehaviour matchBehaviour,
bool throwException = false,
MatchOperator matchOperator = MatchOperator.Or,
params AnyOf<string, StringPattern>[] patterns)
{
_patterns = Guard.NotNull(patterns);
MatchBehaviour = matchBehaviour;
ThrowException = throwException;
MatchOperator = matchOperator;
}
/// <inheritdoc cref="IStringMatcher.IsMatch"/>
public double IsMatch(string? input)
{
double match = MatchScores.Mismatch;
if (input != null)
{
try
{
var results = _patterns.Select(pattern => bool.Parse(new JmesPath().Transform(input, pattern.GetPattern()))).ToArray();
match = MatchScores.ToScore(results, MatchOperator);
}
catch (JsonException)
{
if (ThrowException)
{
throw;
}
}
}
return MatchBehaviourHelper.Convert(MatchBehaviour, match);
}
/// <inheritdoc cref="IObjectMatcher.IsMatch"/>
public double IsMatch(object? input)
{
double match = MatchScores.Mismatch;
// When input is null or byte[], return Mismatch.
if (input != null && !(input is byte[]))
{
string inputAsString = JsonConvert.SerializeObject(input);
return IsMatch(inputAsString);
}
return MatchBehaviourHelper.Convert(MatchBehaviour, match);
}
/// <inheritdoc cref="IStringMatcher.GetPatterns"/>
public AnyOf<string, StringPattern>[] GetPatterns()
{
return _patterns;
}
/// <inheritdoc />
public MatchOperator MatchOperator { get; }
/// <inheritdoc cref="IMatcher.Name"/>
public string Name => "JmesPathMatcher";
} }
/// <summary>
/// Initializes a new instance of the <see cref="JmesPathMatcher"/> class.
/// </summary>
/// <param name="patterns">The patterns.</param>
public JmesPathMatcher(params AnyOf<string, StringPattern>[] patterns) : this(MatchBehaviour.AcceptOnMatch, false, MatchOperator.Or, patterns)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="JmesPathMatcher"/> class.
/// </summary>
/// <param name="throwException">Throw an exception when the internal matching fails because of invalid input.</param>
/// <param name="matchOperator">The <see cref="Matchers.MatchOperator"/> to use.</param>
/// <param name="patterns">The patterns.</param>
public JmesPathMatcher(bool throwException = false, MatchOperator matchOperator = MatchOperator.Or, params AnyOf<string, StringPattern>[] patterns) :
this(MatchBehaviour.AcceptOnMatch, throwException, matchOperator, patterns)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="JmesPathMatcher"/> class.
/// </summary>
/// <param name="matchBehaviour">The match behaviour.</param>
/// <param name="throwException">Throw an exception when the internal matching fails because of invalid input.</param>
/// <param name="matchOperator">The <see cref="Matchers.MatchOperator"/> to use.</param>
/// <param name="patterns">The patterns.</param>
public JmesPathMatcher(
MatchBehaviour matchBehaviour,
bool throwException = false,
MatchOperator matchOperator = MatchOperator.Or,
params AnyOf<string, StringPattern>[] patterns)
{
_patterns = Guard.NotNull(patterns);
MatchBehaviour = matchBehaviour;
ThrowException = throwException;
MatchOperator = matchOperator;
}
/// <inheritdoc cref="IStringMatcher.IsMatch"/>
public double IsMatch(string? input)
{
double match = MatchScores.Mismatch;
if (input != null)
{
try
{
var results = _patterns.Select(pattern => bool.Parse(new JmesPath().Transform(input, pattern.GetPattern()))).ToArray();
match = MatchScores.ToScore(results, MatchOperator);
}
catch (JsonException)
{
if (ThrowException)
{
throw;
}
}
}
return MatchBehaviourHelper.Convert(MatchBehaviour, match);
}
/// <inheritdoc cref="IObjectMatcher.IsMatch"/>
public double IsMatch(object? input)
{
double match = MatchScores.Mismatch;
// When input is null or byte[], return Mismatch.
if (input != null && !(input is byte[]))
{
string inputAsString = JsonConvert.SerializeObject(input);
return IsMatch(inputAsString);
}
return MatchBehaviourHelper.Convert(MatchBehaviour, match);
}
/// <inheritdoc cref="IStringMatcher.GetPatterns"/>
public AnyOf<string, StringPattern>[] GetPatterns()
{
return _patterns;
}
/// <inheritdoc />
public MatchOperator MatchOperator { get; }
/// <inheritdoc cref="IMatcher.Name"/>
public string Name => "JmesPathMatcher";
} }
+1 -1
View File
@@ -119,7 +119,7 @@ public class JsonMatcher : IValueMatcher, IIgnoreCaseMatcher
return tokenValue; return tokenValue;
case string stringValue: case string stringValue:
return JsonUtils.Parse(stringValue)!; return JsonUtils.Parse(stringValue);
case IEnumerable enumerableValue: case IEnumerable enumerableValue:
return JArray.FromObject(enumerableValue); return JArray.FromObject(enumerableValue);
+8 -4
View File
@@ -1,3 +1,4 @@
using System;
using System.Linq; using System.Linq;
using System.Linq.Dynamic.Core; using System.Linq.Dynamic.Core;
using AnyOfTypes; using AnyOfTypes;
@@ -68,7 +69,7 @@ public class LinqMatcher : IObjectMatcher, IStringMatcher
} }
/// <inheritdoc cref="IStringMatcher.IsMatch"/> /// <inheritdoc cref="IStringMatcher.IsMatch"/>
public double IsMatch(string input) public double IsMatch(string? input)
{ {
double match = MatchScores.Mismatch; double match = MatchScores.Mismatch;
@@ -94,7 +95,7 @@ public class LinqMatcher : IObjectMatcher, IStringMatcher
} }
/// <inheritdoc cref="IObjectMatcher.IsMatch"/> /// <inheritdoc cref="IObjectMatcher.IsMatch"/>
public double IsMatch(object input) public double IsMatch(object? input)
{ {
double match = MatchScores.Mismatch; double match = MatchScores.Mismatch;
@@ -105,9 +106,12 @@ public class LinqMatcher : IObjectMatcher, IStringMatcher
value = valueAsJObject; value = valueAsJObject;
break; break;
default: case { } valueAsObject:
value = JObject.FromObject(input); value = JObject.FromObject(valueAsObject);
break; break;
default:
return MatchScores.Mismatch;
} }
// Convert a single object to a Queryable JObject-list with 1 entry. // Convert a single object to a Queryable JObject-list with 1 entry.
+12 -13
View File
@@ -1,18 +1,17 @@
namespace WireMock.Matchers namespace WireMock.Matchers;
/// <summary>
/// MatchBehaviour
/// </summary>
public enum MatchBehaviour
{ {
/// <summary> /// <summary>
/// MatchBehaviour /// Accept on match (default)
/// </summary> /// </summary>
public enum MatchBehaviour AcceptOnMatch,
{
/// <summary>
/// Accept on match (default)
/// </summary>
AcceptOnMatch,
/// <summary> /// <summary>
/// Reject on match /// Reject on match
/// </summary> /// </summary>
RejectOnMatch RejectOnMatch
}
} }
@@ -1,27 +1,26 @@
namespace WireMock.Matchers namespace WireMock.Matchers;
{
internal static class MatchBehaviourHelper
{
/// <summary>
/// Converts the specified match behaviour and match value to a new match value.
///
/// if AcceptOnMatch --> return match (default)
/// if RejectOnMatch and match = 0.0 --> return 1.0
/// if RejectOnMatch and match = 0.? --> return 0.0
/// if RejectOnMatch and match = 1.0 --> return 0.0
/// </summary>
///
/// <param name="matchBehaviour">The match behaviour.</param>
/// <param name="match">The match.</param>
/// <returns>match value</returns>
internal static double Convert(MatchBehaviour matchBehaviour, double match)
{
if (matchBehaviour == MatchBehaviour.AcceptOnMatch)
{
return match;
}
return match <= MatchScores.Tolerance ? MatchScores.Perfect : MatchScores.Mismatch; internal static class MatchBehaviourHelper
{
/// <summary>
/// Converts the specified match behaviour and match value to a new match value.
///
/// if AcceptOnMatch --> return match (default)
/// if RejectOnMatch and match = 0.0 --> return 1.0
/// if RejectOnMatch and match = 0.? --> return 0.0
/// if RejectOnMatch and match = 1.0 --> return 0.0
/// </summary>
///
/// <param name="matchBehaviour">The match behaviour.</param>
/// <param name="match">The match.</param>
/// <returns>match value</returns>
internal static double Convert(MatchBehaviour matchBehaviour, double match)
{
if (matchBehaviour == MatchBehaviour.AcceptOnMatch)
{
return match;
} }
return match <= MatchScores.Tolerance ? MatchScores.Perfect : MatchScores.Mismatch;
} }
} }
@@ -1,18 +1,17 @@
namespace WireMock.Matchers.Request namespace WireMock.Matchers.Request;
/// <summary>
/// CompositeMatcherType
/// </summary>
public enum CompositeMatcherType
{ {
/// <summary> /// <summary>
/// CompositeMatcherType /// And
/// </summary> /// </summary>
public enum CompositeMatcherType And = 0,
{
/// <summary>
/// And
/// </summary>
And = 0,
/// <summary> /// <summary>
/// Or /// Or
/// </summary> /// </summary>
Or = 1 Or = 1
}
} }
@@ -1,57 +1,56 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
namespace WireMock.Matchers.Request namespace WireMock.Matchers.Request;
/// <summary>
/// RequestMatchResult
/// </summary>
public class RequestMatchResult : IRequestMatchResult
{ {
/// <inheritdoc cref="IRequestMatchResult.TotalScore" />
public double TotalScore => MatchDetails.Sum(md => md.Score);
/// <inheritdoc cref="IRequestMatchResult.TotalNumber" />
public int TotalNumber => MatchDetails.Count;
/// <inheritdoc cref="IRequestMatchResult.IsPerfectMatch" />
public bool IsPerfectMatch => Math.Abs(TotalScore - TotalNumber) < MatchScores.Tolerance;
/// <inheritdoc cref="IRequestMatchResult.AverageTotalScore" />
public double AverageTotalScore => TotalNumber == 0 ? 0.0 : TotalScore / TotalNumber;
/// <inheritdoc cref="IRequestMatchResult.MatchDetails" />
public IList<MatchDetail> MatchDetails { get; } = new List<MatchDetail>();
/// <summary> /// <summary>
/// RequestMatchResult /// Adds the score.
/// </summary> /// </summary>
public class RequestMatchResult : IRequestMatchResult /// <param name="matcherType">The matcher Type.</param>
/// <param name="score">The score.</param>
/// <returns>The score.</returns>
public double AddScore(Type matcherType, double score)
{ {
/// <inheritdoc cref="IRequestMatchResult.TotalScore" /> MatchDetails.Add(new MatchDetail { MatcherType = matcherType, Score = score });
public double TotalScore => MatchDetails.Sum(md => md.Score);
/// <inheritdoc cref="IRequestMatchResult.TotalNumber" /> return score;
public int TotalNumber => MatchDetails.Count; }
/// <inheritdoc cref="IRequestMatchResult.IsPerfectMatch" /> /// <summary>
public bool IsPerfectMatch => Math.Abs(TotalScore - TotalNumber) < MatchScores.Tolerance; /// Compares the current instance with another object of the same type and returns an integer that indicates whether the current instance precedes, follows, or occurs in the same position in the sort order as the other object.
/// </summary>
/// <param name="obj">An object to compare with this instance.</param>
/// <returns>
/// A value that indicates the relative order of the objects being compared. The return value has these meanings: Value Meaning Less than zero This instance precedes <paramref name="obj" /> in the sort order. Zero This instance occurs in the same position in the sort order as <paramref name="obj" />. Greater than zero This instance follows <paramref name="obj" /> in the sort order.
/// </returns>
public int CompareTo(object obj)
{
var compareObj = (RequestMatchResult)obj;
/// <inheritdoc cref="IRequestMatchResult.AverageTotalScore" /> int averageTotalScoreResult = compareObj.AverageTotalScore.CompareTo(AverageTotalScore);
public double AverageTotalScore => TotalNumber == 0 ? 0.0 : TotalScore / TotalNumber;
/// <inheritdoc cref="IRequestMatchResult.MatchDetails" /> // In case the score is equal, prefer the one with the most matchers.
public IList<MatchDetail> MatchDetails { get; } = new List<MatchDetail>(); return averageTotalScoreResult == 0 ? compareObj.TotalNumber.CompareTo(TotalNumber) : averageTotalScoreResult;
/// <summary>
/// Adds the score.
/// </summary>
/// <param name="matcherType">The matcher Type.</param>
/// <param name="score">The score.</param>
/// <returns>The score.</returns>
public double AddScore(Type matcherType, double score)
{
MatchDetails.Add(new MatchDetail { MatcherType = matcherType, Score = score });
return score;
}
/// <summary>
/// Compares the current instance with another object of the same type and returns an integer that indicates whether the current instance precedes, follows, or occurs in the same position in the sort order as the other object.
/// </summary>
/// <param name="obj">An object to compare with this instance.</param>
/// <returns>
/// A value that indicates the relative order of the objects being compared. The return value has these meanings: Value Meaning Less than zero This instance precedes <paramref name="obj" /> in the sort order. Zero This instance occurs in the same position in the sort order as <paramref name="obj" />. Greater than zero This instance follows <paramref name="obj" /> in the sort order.
/// </returns>
public int CompareTo(object obj)
{
var compareObj = (RequestMatchResult)obj;
int averageTotalScoreResult = compareObj.AverageTotalScore.CompareTo(AverageTotalScore);
// In case the score is equal, prefer the one with the most matchers.
return averageTotalScoreResult == 0 ? compareObj.TotalNumber.CompareTo(TotalNumber) : averageTotalScoreResult;
}
} }
} }
@@ -3,50 +3,49 @@ using System.Linq;
using JetBrains.Annotations; using JetBrains.Annotations;
using Stef.Validation; using Stef.Validation;
namespace WireMock.Matchers.Request namespace WireMock.Matchers.Request;
/// <summary>
/// The composite request matcher.
/// </summary>
public abstract class RequestMessageCompositeMatcher : IRequestMatcher
{ {
private readonly CompositeMatcherType _type;
/// <summary> /// <summary>
/// The composite request matcher. /// Gets the request matchers.
/// </summary> /// </summary>
public abstract class RequestMessageCompositeMatcher : IRequestMatcher /// <value>
/// The request matchers.
/// </value>
private IEnumerable<IRequestMatcher> RequestMatchers { get; }
/// <summary>
/// Initializes a new instance of the <see cref="RequestMessageCompositeMatcher"/> class.
/// </summary>
/// <param name="requestMatchers">The request matchers.</param>
/// <param name="type">The CompositeMatcherType type (Defaults to 'And')</param>
protected RequestMessageCompositeMatcher([NotNull] IEnumerable<IRequestMatcher> requestMatchers, CompositeMatcherType type = CompositeMatcherType.And)
{ {
private readonly CompositeMatcherType _type; Guard.NotNull(requestMatchers, nameof(requestMatchers));
/// <summary> _type = type;
/// Gets the request matchers. RequestMatchers = requestMatchers;
/// </summary> }
/// <value>
/// The request matchers.
/// </value>
private IEnumerable<IRequestMatcher> RequestMatchers { get; }
/// <summary> /// <inheritdoc />
/// Initializes a new instance of the <see cref="RequestMessageCompositeMatcher"/> class. public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
/// </summary> {
/// <param name="requestMatchers">The request matchers.</param> if (!RequestMatchers.Any())
/// <param name="type">The CompositeMatcherType type (Defaults to 'And')</param>
protected RequestMessageCompositeMatcher([NotNull] IEnumerable<IRequestMatcher> requestMatchers, CompositeMatcherType type = CompositeMatcherType.And)
{ {
Guard.NotNull(requestMatchers, nameof(requestMatchers)); return MatchScores.Mismatch;
_type = type;
RequestMatchers = requestMatchers;
} }
/// <inheritdoc /> if (_type == CompositeMatcherType.And)
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
{ {
if (!RequestMatchers.Any()) return RequestMatchers.Average(requestMatcher => requestMatcher.GetMatchingScore(requestMessage, requestMatchResult));
{
return MatchScores.Mismatch;
}
if (_type == CompositeMatcherType.And)
{
return RequestMatchers.Average(requestMatcher => requestMatcher.GetMatchingScore(requestMessage, requestMatchResult));
}
return RequestMatchers.Max(requestMatcher => requestMatcher.GetMatchingScore(requestMessage, requestMatchResult));
} }
return RequestMatchers.Max(requestMatcher => requestMatcher.GetMatchingScore(requestMessage, requestMatchResult));
} }
} }
@@ -4,126 +4,125 @@ using System.Linq;
using JetBrains.Annotations; using JetBrains.Annotations;
using Stef.Validation; using Stef.Validation;
namespace WireMock.Matchers.Request namespace WireMock.Matchers.Request;
/// <summary>
/// The request cookie matcher.
/// </summary>
/// <inheritdoc cref="IRequestMatcher"/>
public class RequestMessageCookieMatcher : IRequestMatcher
{ {
private readonly MatchBehaviour _matchBehaviour;
private readonly bool _ignoreCase;
/// <summary> /// <summary>
/// The request cookie matcher. /// The functions
/// </summary> /// </summary>
/// <inheritdoc cref="IRequestMatcher"/> public Func<IDictionary<string, string>, bool>[] Funcs { get; }
public class RequestMessageCookieMatcher : IRequestMatcher
/// <summary>
/// The name
/// </summary>
public string Name { get; }
/// <value>
/// The matchers.
/// </value>
public IStringMatcher[] Matchers { get; }
/// <summary>
/// Initializes a new instance of the <see cref="RequestMessageCookieMatcher"/> class.
/// </summary>
/// <param name="name">The name.</param>
/// <param name="pattern">The pattern.</param>
/// <param name="ignoreCase">Ignore the case from the pattern.</param>
/// <param name="matchBehaviour">The match behaviour.</param>
public RequestMessageCookieMatcher(MatchBehaviour matchBehaviour, [NotNull] string name, [NotNull] string pattern, bool ignoreCase)
{ {
private readonly MatchBehaviour _matchBehaviour; Guard.NotNull(name, nameof(name));
private readonly bool _ignoreCase; Guard.NotNull(pattern, nameof(pattern));
/// <summary> _matchBehaviour = matchBehaviour;
/// The functions _ignoreCase = ignoreCase;
/// </summary> Name = name;
public Func<IDictionary<string, string>, bool>[] Funcs { get; } Matchers = new IStringMatcher[] { new WildcardMatcher(matchBehaviour, pattern, ignoreCase) };
}
/// <summary> /// <summary>
/// The name /// Initializes a new instance of the <see cref="RequestMessageCookieMatcher"/> class.
/// </summary> /// </summary>
public string Name { get; } /// <param name="matchBehaviour">The match behaviour.</param>
/// <param name="name">The name.</param>
/// <param name="patterns">The patterns.</param>
/// <param name="ignoreCase">Ignore the case from the pattern.</param>
public RequestMessageCookieMatcher(MatchBehaviour matchBehaviour, [NotNull] string name, bool ignoreCase, [NotNull] params string[] patterns) :
this(matchBehaviour, name, ignoreCase, patterns.Select(pattern => new WildcardMatcher(matchBehaviour, pattern, ignoreCase)).Cast<IStringMatcher>().ToArray())
{
Guard.NotNull(patterns, nameof(patterns));
}
/// <value> /// <summary>
/// The matchers. /// Initializes a new instance of the <see cref="RequestMessageCookieMatcher"/> class.
/// </value> /// </summary>
public IStringMatcher[] Matchers { get; } /// <param name="matchBehaviour">The match behaviour.</param>
/// <param name="name">The name.</param>
/// <param name="matchers">The matchers.</param>
/// <param name="ignoreCase">Ignore the case from the pattern.</param>
public RequestMessageCookieMatcher(MatchBehaviour matchBehaviour, [NotNull] string name, bool ignoreCase, [NotNull] params IStringMatcher[] matchers)
{
Guard.NotNull(name, nameof(name));
Guard.NotNull(matchers, nameof(matchers));
/// <summary> _matchBehaviour = matchBehaviour;
/// Initializes a new instance of the <see cref="RequestMessageCookieMatcher"/> class. Name = name;
/// </summary> Matchers = matchers;
/// <param name="name">The name.</param> _ignoreCase = ignoreCase;
/// <param name="pattern">The pattern.</param> }
/// <param name="ignoreCase">Ignore the case from the pattern.</param>
/// <param name="matchBehaviour">The match behaviour.</param> /// <summary>
public RequestMessageCookieMatcher(MatchBehaviour matchBehaviour, [NotNull] string name, [NotNull] string pattern, bool ignoreCase) /// Initializes a new instance of the <see cref="RequestMessageCookieMatcher"/> class.
/// </summary>
/// <param name="funcs">The funcs.</param>
public RequestMessageCookieMatcher([NotNull] params Func<IDictionary<string, string>, bool>[] funcs)
{
Guard.NotNull(funcs, nameof(funcs));
Funcs = funcs;
}
/// <inheritdoc />
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
{
double score = IsMatch(requestMessage);
return requestMatchResult.AddScore(GetType(), score);
}
private double IsMatch(IRequestMessage requestMessage)
{
if (requestMessage.Cookies == null)
{ {
Guard.NotNull(name, nameof(name)); return MatchBehaviourHelper.Convert(_matchBehaviour, MatchScores.Mismatch);
Guard.NotNull(pattern, nameof(pattern));
_matchBehaviour = matchBehaviour;
_ignoreCase = ignoreCase;
Name = name;
Matchers = new IStringMatcher[] { new WildcardMatcher(matchBehaviour, pattern, ignoreCase) };
} }
/// <summary> // Check if we want to use IgnoreCase to compare the Cookie-Name and Cookie-Value
/// Initializes a new instance of the <see cref="RequestMessageCookieMatcher"/> class. var cookies = !_ignoreCase ? requestMessage.Cookies : new Dictionary<string, string>(requestMessage.Cookies, StringComparer.OrdinalIgnoreCase);
/// </summary>
/// <param name="matchBehaviour">The match behaviour.</param> if (Funcs != null)
/// <param name="name">The name.</param>
/// <param name="patterns">The patterns.</param>
/// <param name="ignoreCase">Ignore the case from the pattern.</param>
public RequestMessageCookieMatcher(MatchBehaviour matchBehaviour, [NotNull] string name, bool ignoreCase, [NotNull] params string[] patterns) :
this(matchBehaviour, name, ignoreCase, patterns.Select(pattern => new WildcardMatcher(matchBehaviour, pattern, ignoreCase)).Cast<IStringMatcher>().ToArray())
{ {
Guard.NotNull(patterns, nameof(patterns)); return MatchScores.ToScore(Funcs.Any(f => f(cookies)));
} }
/// <summary> if (Matchers == null)
/// Initializes a new instance of the <see cref="RequestMessageCookieMatcher"/> class.
/// </summary>
/// <param name="matchBehaviour">The match behaviour.</param>
/// <param name="name">The name.</param>
/// <param name="matchers">The matchers.</param>
/// <param name="ignoreCase">Ignore the case from the pattern.</param>
public RequestMessageCookieMatcher(MatchBehaviour matchBehaviour, [NotNull] string name, bool ignoreCase, [NotNull] params IStringMatcher[] matchers)
{ {
Guard.NotNull(name, nameof(name)); return MatchScores.Mismatch;
Guard.NotNull(matchers, nameof(matchers));
_matchBehaviour = matchBehaviour;
Name = name;
Matchers = matchers;
_ignoreCase = ignoreCase;
} }
/// <summary> if (!cookies.ContainsKey(Name))
/// Initializes a new instance of the <see cref="RequestMessageCookieMatcher"/> class.
/// </summary>
/// <param name="funcs">The funcs.</param>
public RequestMessageCookieMatcher([NotNull] params Func<IDictionary<string, string>, bool>[] funcs)
{ {
Guard.NotNull(funcs, nameof(funcs)); return MatchBehaviourHelper.Convert(_matchBehaviour, MatchScores.Mismatch);
Funcs = funcs;
} }
/// <inheritdoc /> string value = cookies[Name];
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult) return Matchers.Max(m => m.IsMatch(value));
{
double score = IsMatch(requestMessage);
return requestMatchResult.AddScore(GetType(), score);
}
private double IsMatch(IRequestMessage requestMessage)
{
if (requestMessage.Cookies == null)
{
return MatchBehaviourHelper.Convert(_matchBehaviour, MatchScores.Mismatch);
}
// Check if we want to use IgnoreCase to compare the Cookie-Name and Cookie-Value
var cookies = !_ignoreCase ? requestMessage.Cookies : new Dictionary<string, string>(requestMessage.Cookies, StringComparer.OrdinalIgnoreCase);
if (Funcs != null)
{
return MatchScores.ToScore(Funcs.Any(f => f(cookies)));
}
if (Matchers == null)
{
return MatchScores.Mismatch;
}
if (!cookies.ContainsKey(Name))
{
return MatchBehaviourHelper.Convert(_matchBehaviour, MatchScores.Mismatch);
}
string value = cookies[Name];
return Matchers.Max(m => m.IsMatch(value));
}
} }
} }
+2 -3
View File
@@ -5,7 +5,6 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
@@ -30,9 +29,9 @@ namespace WireMock.Owin
public bool IsStarted { get; private set; } public bool IsStarted { get; private set; }
public List<string> Urls { get; } = new List<string>(); public List<string> Urls { get; } = new();
public List<int> Ports { get; } = new List<int>(); public List<int> Ports { get; } = new();
public Exception RunningException => _runningException; public Exception RunningException => _runningException;
@@ -71,7 +71,6 @@ namespace WireMock.Owin.Mappers
{ {
bytes = bytes.Take(bytes.Length / 2).Union(_randomizerBytes.Generate()).ToArray(); bytes = bytes.Take(bytes.Length / 2).Union(_randomizerBytes.Generate()).ToArray();
} }
break; break;
default: default:
@@ -80,11 +79,10 @@ namespace WireMock.Owin.Mappers
} }
var statusCodeType = responseMessage.StatusCode?.GetType(); var statusCodeType = responseMessage.StatusCode?.GetType();
switch (statusCodeType) switch (statusCodeType)
{ {
case Type typeAsIntOrEnum when typeAsIntOrEnum == typeof(int) || typeAsIntOrEnum == typeof(int?) || typeAsIntOrEnum.GetTypeInfo().IsEnum: case Type typeAsIntOrEnum when typeAsIntOrEnum == typeof(int) || typeAsIntOrEnum == typeof(int?) || typeAsIntOrEnum.GetTypeInfo().IsEnum:
response.StatusCode = MapStatusCode((int)responseMessage.StatusCode); response.StatusCode = MapStatusCode((int)responseMessage.StatusCode!);
break; break;
case Type typeAsString when typeAsString == typeof(string): case Type typeAsString when typeAsString == typeof(string):
@@ -138,7 +136,7 @@ namespace WireMock.Owin.Mappers
return responseMessage.BodyData.BodyAsBytes; return responseMessage.BodyData.BodyAsBytes;
case BodyType.File: case BodyType.File:
return _options.FileSystemHandler.ReadResponseBodyAsFile(responseMessage.BodyData.BodyAsFile); return _options.FileSystemHandler?.ReadResponseBodyAsFile(responseMessage.BodyData.BodyAsFile);
} }
return null; return null;
@@ -2,11 +2,11 @@ namespace WireMock.Pact.Models.V2;
public class Interaction public class Interaction
{ {
public string Description { get; set; } = string.Empty; public string? Description { get; set; }
public string ProviderState { get; set; } public string? ProviderState { get; set; }
public PactRequest Request { get; set; } = new PactRequest(); public PactRequest Request { get; set; } = new();
public PactResponse Response { get; set; } = new PactResponse(); public PactResponse Response { get; set; } = new();
} }
+34 -35
View File
@@ -1,57 +1,56 @@
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
namespace WireMock.Plugin namespace WireMock.Plugin;
internal static class PluginLoader
{ {
internal static class PluginLoader private static readonly ConcurrentDictionary<Type, Type> Assemblies = new();
public static T Load<T>(params object[] args) where T : class
{ {
private static readonly ConcurrentDictionary<Type, Type> Assemblies = new ConcurrentDictionary<Type, Type>(); var foundType = Assemblies.GetOrAdd(typeof(T), (type) =>
public static T Load<T>(params object[] args) where T : class
{ {
var foundType = Assemblies.GetOrAdd(typeof(T), (type) => var files = Directory.GetFiles(Directory.GetCurrentDirectory(), "*.dll");
Type? pluginType = null;
foreach (var file in files)
{ {
var files = Directory.GetFiles(Directory.GetCurrentDirectory(), "*.dll"); try
Type pluginType = null;
foreach (var file in files)
{ {
try var assembly = Assembly.Load(new AssemblyName
{ {
var assembly = Assembly.Load(new AssemblyName Name = Path.GetFileNameWithoutExtension(file)
{ });
Name = Path.GetFileNameWithoutExtension(file)
});
pluginType = GetImplementationTypeByInterface<T>(assembly); pluginType = GetImplementationTypeByInterface<T>(assembly);
if (pluginType != null) if (pluginType != null)
{
break;
}
}
catch
{ {
// no-op: just try next .dll break;
} }
} }
catch
if (pluginType != null)
{ {
return pluginType; // no-op: just try next .dll
} }
}
throw new DllNotFoundException($"No dll found which implements type '{type}'"); if (pluginType != null)
}); {
return pluginType;
}
return (T)Activator.CreateInstance(foundType, args); throw new DllNotFoundException($"No dll found which implements type '{type}'");
} });
private static Type GetImplementationTypeByInterface<T>(Assembly assembly) return (T)Activator.CreateInstance(foundType, args);
{ }
return assembly.GetTypes().FirstOrDefault(t => typeof(T).IsAssignableFrom(t) && !t.GetTypeInfo().IsInterface);
} private static Type? GetImplementationTypeByInterface<T>(Assembly assembly)
{
return assembly.GetTypes().FirstOrDefault(t => typeof(T).IsAssignableFrom(t) && !t.GetTypeInfo().IsInterface);
} }
} }
@@ -77,7 +77,7 @@ internal class RegexExtended : Regex
/// <param name="pattern">Pattern to replace token for.</param> /// <param name="pattern">Pattern to replace token for.</param>
private static string ReplaceGuidPattern(string pattern) private static string ReplaceGuidPattern(string pattern)
{ {
Guard.NotNull(pattern, nameof(pattern)); Guard.NotNull(pattern);
foreach (var tokenPattern in GuidTokenPatterns) foreach (var tokenPattern in GuidTokenPatterns)
{ {
+2 -2
View File
@@ -47,10 +47,10 @@ public class RequestMessage : IRequestMessage
public string Method { get; } public string Method { get; }
/// <inheritdoc cref="IRequestMessage.Headers" /> /// <inheritdoc cref="IRequestMessage.Headers" />
public IDictionary<string, WireMockList<string>> Headers { get; } public IDictionary<string, WireMockList<string>>? Headers { get; }
/// <inheritdoc cref="IRequestMessage.Cookies" /> /// <inheritdoc cref="IRequestMessage.Cookies" />
public IDictionary<string, string> Cookies { get; } public IDictionary<string, string>? Cookies { get; }
/// <inheritdoc cref="IRequestMessage.Query" /> /// <inheritdoc cref="IRequestMessage.Query" />
public IDictionary<string, WireMockList<string>>? Query { get; } public IDictionary<string, WireMockList<string>>? Query { get; }
@@ -1,28 +1,27 @@
namespace WireMock.ResponseBuilders namespace WireMock.ResponseBuilders;
/// <summary>
/// Defines the BodyDestinationFormat
/// </summary>
public static class BodyDestinationFormat
{ {
/// <summary> /// <summary>
/// Defines the BodyDestinationFormat /// Same as source (no conversion)
/// </summary> /// </summary>
public static class BodyDestinationFormat public const string SameAsSource = "SameAsSource";
{
/// <summary>
/// Same as source (no conversion)
/// </summary>
public const string SameAsSource = "SameAsSource";
/// <summary> /// <summary>
/// Convert to string /// Convert to string
/// </summary> /// </summary>
public const string String = "String"; public const string String = "String";
/// <summary> /// <summary>
/// Convert to bytes /// Convert to bytes
/// </summary> /// </summary>
public const string Bytes = "Bytes"; public const string Bytes = "Bytes";
/// <summary> /// <summary>
/// Convert to Json object /// Convert to Json object
/// </summary> /// </summary>
public const string Json = "Json"; public const string Json = "Json";
}
} }
@@ -2,72 +2,71 @@ using System;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace WireMock.ResponseBuilders namespace WireMock.ResponseBuilders;
/// <summary>
/// The BodyResponseBuilder interface.
/// </summary>
public interface IBodyResponseBuilder : IFaultResponseBuilder
{ {
/// <summary> /// <summary>
/// The BodyResponseBuilder interface. /// WithBody : Create a ... response based on a string.
/// </summary> /// </summary>
public interface IBodyResponseBuilder : IFaultResponseBuilder /// <param name="body">The body.</param>
{ /// <param name="destination">The Body Destination format (SameAsSource, String or Bytes).</param>
/// <summary> /// <param name="encoding">The body encoding.</param>
/// WithBody : Create a ... response based on a string. /// <returns>A <see cref="IResponseBuilder"/>.</returns>
/// </summary> IResponseBuilder WithBody(string body, string? destination = BodyDestinationFormat.SameAsSource, Encoding? encoding = null);
/// <param name="body">The body.</param>
/// <param name="destination">The Body Destination format (SameAsSource, String or Bytes).</param>
/// <param name="encoding">The body encoding.</param>
/// <returns>A <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithBody(string body, string? destination = BodyDestinationFormat.SameAsSource, Encoding? encoding = null);
/// <summary> /// <summary>
/// WithBody : Create a ... response based on a callback function. /// WithBody : Create a ... response based on a callback function.
/// </summary> /// </summary>
/// <param name="bodyFactory">The delegate to build the body.</param> /// <param name="bodyFactory">The delegate to build the body.</param>
/// <param name="destination">The Body Destination format (SameAsSource, String or Bytes).</param> /// <param name="destination">The Body Destination format (SameAsSource, String or Bytes).</param>
/// <param name="encoding">The body encoding.</param> /// <param name="encoding">The body encoding.</param>
/// <returns>A <see cref="IResponseBuilder"/>.</returns> /// <returns>A <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithBody(Func<IRequestMessage, string> bodyFactory, string? destination = BodyDestinationFormat.SameAsSource, Encoding? encoding = null); IResponseBuilder WithBody(Func<IRequestMessage, string> bodyFactory, string? destination = BodyDestinationFormat.SameAsSource, Encoding? encoding = null);
/// <summary> /// <summary>
/// WithBody : Create a ... response based on a callback function. /// WithBody : Create a ... response based on a callback function.
/// </summary> /// </summary>
/// <param name="bodyFactory">The async delegate to build the body.</param> /// <param name="bodyFactory">The async delegate to build the body.</param>
/// <param name="destination">The Body Destination format (SameAsSource, String or Bytes).</param> /// <param name="destination">The Body Destination format (SameAsSource, String or Bytes).</param>
/// <param name="encoding">The body encoding.</param> /// <param name="encoding">The body encoding.</param>
/// <returns>A <see cref="IResponseBuilder"/>.</returns> /// <returns>A <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithBody(Func<IRequestMessage, Task<string>> bodyFactory, string? destination = BodyDestinationFormat.SameAsSource, Encoding? encoding = null); IResponseBuilder WithBody(Func<IRequestMessage, Task<string>> bodyFactory, string? destination = BodyDestinationFormat.SameAsSource, Encoding? encoding = null);
/// <summary> /// <summary>
/// WithBody : Create a ... response based on a bytearray. /// WithBody : Create a ... response based on a bytearray.
/// </summary> /// </summary>
/// <param name="body">The body.</param> /// <param name="body">The body.</param>
/// <param name="destination">The Body Destination format (SameAsSource, String or Bytes).</param> /// <param name="destination">The Body Destination format (SameAsSource, String or Bytes).</param>
/// <param name="encoding">The body encoding.</param> /// <param name="encoding">The body encoding.</param>
/// <returns>A <see cref="IResponseBuilder"/>.</returns> /// <returns>A <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithBody(byte[] body, string? destination = BodyDestinationFormat.SameAsSource, Encoding? encoding = null); IResponseBuilder WithBody(byte[] body, string? destination = BodyDestinationFormat.SameAsSource, Encoding? encoding = null);
/// <summary> /// <summary>
/// WithBody : Create a string response based on a object (which will be converted to a JSON string). /// WithBody : Create a string response based on a object (which will be converted to a JSON string).
/// </summary> /// </summary>
/// <param name="body">The body.</param> /// <param name="body">The body.</param>
/// <param name="encoding">The body encoding.</param> /// <param name="encoding">The body encoding.</param>
/// <param name="indented">Use JSON indented.</param> /// <param name="indented">Use JSON indented.</param>
/// <returns>A <see cref="IResponseBuilder"/>.</returns> /// <returns>A <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithBodyAsJson(object body, Encoding? encoding = null, bool? indented = null); IResponseBuilder WithBodyAsJson(object body, Encoding? encoding = null, bool? indented = null);
/// <summary> /// <summary>
/// WithBody : Create a string response based on a object (which will be converted to a JSON string). /// WithBody : Create a string response based on a object (which will be converted to a JSON string).
/// </summary> /// </summary>
/// <param name="body">The body.</param> /// <param name="body">The body.</param>
/// <param name="indented">Define whether child objects to be indented according to the Newtonsoft.Json.JsonTextWriter.Indentation and Newtonsoft.Json.JsonTextWriter.IndentChar settings.</param> /// <param name="indented">Define whether child objects to be indented according to the Newtonsoft.Json.JsonTextWriter.Indentation and Newtonsoft.Json.JsonTextWriter.IndentChar settings.</param>
/// <returns>A <see cref="IResponseBuilder"/>.</returns> /// <returns>A <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithBodyAsJson(object body, bool indented); IResponseBuilder WithBodyAsJson(object body, bool indented);
/// <summary> /// <summary>
/// WithBodyFromFile : Create a ... response based on a File. /// WithBodyFromFile : Create a ... response based on a File.
/// </summary> /// </summary>
/// <param name="filename">The filename.</param> /// <param name="filename">The filename.</param>
/// <param name="cache">Defines if this file is cached in memory or retrieved from disk every time the response is created.</param> /// <param name="cache">Defines if this file is cached in memory or retrieved from disk every time the response is created.</param>
/// <returns>A <see cref="IResponseBuilder"/>.</returns> /// <returns>A <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithBodyFromFile(string filename, bool cache = true); IResponseBuilder WithBodyFromFile(string filename, bool cache = true);
}
} }
@@ -3,25 +3,24 @@ using System.Threading.Tasks;
using JetBrains.Annotations; using JetBrains.Annotations;
using WireMock.ResponseProviders; using WireMock.ResponseProviders;
namespace WireMock.ResponseBuilders namespace WireMock.ResponseBuilders;
/// <summary>
/// The CallbackResponseBuilder interface.
/// </summary>
public interface ICallbackResponseBuilder : IResponseProvider
{ {
/// <summary> /// <summary>
/// The CallbackResponseBuilder interface. /// The callback builder
/// </summary> /// </summary>
public interface ICallbackResponseBuilder : IResponseProvider /// <returns>The <see cref="IResponseBuilder"/>.</returns>
{ [PublicAPI]
/// <summary> IResponseBuilder WithCallback(Func<IRequestMessage, ResponseMessage> callbackHandler);
/// The callback builder
/// </summary>
/// <returns>The <see cref="IResponseBuilder"/>.</returns>
[PublicAPI]
IResponseBuilder WithCallback([NotNull] Func<IRequestMessage, ResponseMessage> callbackHandler);
/// <summary> /// <summary>
/// The async callback builder /// The async callback builder
/// </summary> /// </summary>
/// <returns>The <see cref="IResponseBuilder"/>.</returns> /// <returns>The <see cref="IResponseBuilder"/>.</returns>
[PublicAPI] [PublicAPI]
IResponseBuilder WithCallback([NotNull] Func<IRequestMessage, Task<ResponseMessage>> callbackHandler); IResponseBuilder WithCallback(Func<IRequestMessage, Task<ResponseMessage>> callbackHandler);
}
} }
@@ -1,32 +1,31 @@
using System; using System;
namespace WireMock.ResponseBuilders namespace WireMock.ResponseBuilders;
/// <summary>
/// The DelayResponseBuilder interface.
/// </summary>
public interface IDelayResponseBuilder : ICallbackResponseBuilder
{ {
/// <summary> /// <summary>
/// The DelayResponseBuilder interface. /// The delay defined as a <see cref="TimeSpan"/>.
/// </summary> /// </summary>
public interface IDelayResponseBuilder : ICallbackResponseBuilder /// <param name="delay">The TimeSpan to delay.</param>
{ /// <returns>The <see cref="IResponseBuilder"/>.</returns>
/// <summary> IResponseBuilder WithDelay(TimeSpan delay);
/// The delay defined as a <see cref="TimeSpan"/>.
/// </summary>
/// <param name="delay">The TimeSpan to delay.</param>
/// <returns>The <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithDelay(TimeSpan delay);
/// <summary> /// <summary>
/// The delay defined as milliseconds. /// The delay defined as milliseconds.
/// </summary> /// </summary>
/// <param name="milliseconds">The milliseconds to delay.</param> /// <param name="milliseconds">The milliseconds to delay.</param>
/// <returns>The <see cref="IResponseBuilder"/>.</returns> /// <returns>The <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithDelay(int milliseconds); IResponseBuilder WithDelay(int milliseconds);
/// <summary> /// <summary>
/// Introduce random delay /// Introduce random delay
/// </summary> /// </summary>
/// <param name="minimumMilliseconds">Minimum milliseconds to delay</param> /// <param name="minimumMilliseconds">Minimum milliseconds to delay</param>
/// <param name="maximumMilliseconds">Maximum milliseconds to delay</param> /// <param name="maximumMilliseconds">Maximum milliseconds to delay</param>
/// <returns>The <see cref="IResponseBuilder"/>.</returns> /// <returns>The <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithRandomDelay(int minimumMilliseconds = 0, int maximumMilliseconds = 60_000); IResponseBuilder WithRandomDelay(int minimumMilliseconds = 0, int maximumMilliseconds = 60_000);
}
} }
@@ -1,18 +1,17 @@
using JetBrains.Annotations; using JetBrains.Annotations;
namespace WireMock.ResponseBuilders namespace WireMock.ResponseBuilders;
/// <summary>
/// The FaultRequestBuilder interface.
/// </summary>
public interface IFaultResponseBuilder : ITransformResponseBuilder
{ {
/// <summary> /// <summary>
/// The FaultRequestBuilder interface. /// WithBody : Create a fault response.
/// </summary> /// </summary>
public interface IFaultResponseBuilder : ITransformResponseBuilder /// <param name="faultType">The FaultType.</param>
{ /// <param name="percentage">The percentage when this fault should occur. When null, it's always a fault.</param>
/// <summary> /// <returns>A <see cref="IResponseBuilder"/>.</returns>
/// WithBody : Create a fault response. IResponseBuilder WithFault(FaultType faultType, double? percentage = null);
/// </summary>
/// <param name="faultType">The FaultType.</param>
/// <param name="percentage">The percentage when this fault should occur. When null, it's always a fault.</param>
/// <returns>A <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithFault(FaultType faultType, [CanBeNull] double? percentage = null);
}
} }
@@ -1,4 +1,3 @@
using JetBrains.Annotations;
using System.Collections.Generic; using System.Collections.Generic;
using WireMock.Types; using WireMock.Types;
@@ -1,26 +1,25 @@
using JetBrains.Annotations; using JetBrains.Annotations;
using WireMock.Settings; using WireMock.Settings;
namespace WireMock.ResponseBuilders namespace WireMock.ResponseBuilders;
/// <summary>
/// The ProxyResponseBuilder interface.
/// </summary>
public interface IProxyResponseBuilder : IStatusCodeResponseBuilder
{ {
/// <summary> /// <summary>
/// The ProxyResponseBuilder interface. /// WithProxy URL using Client X509Certificate2.
/// </summary> /// </summary>
public interface IProxyResponseBuilder : IStatusCodeResponseBuilder /// <param name="proxyUrl">The proxy url.</param>
{ /// <param name="clientX509Certificate2ThumbprintOrSubjectName">The X509Certificate2 file to use for client authentication.</param>
/// <summary> /// <returns>A <see cref="IResponseBuilder"/>.</returns>
/// WithProxy URL using Client X509Certificate2. IResponseBuilder WithProxy(string proxyUrl, string? clientX509Certificate2ThumbprintOrSubjectName = null);
/// </summary>
/// <param name="proxyUrl">The proxy url.</param>
/// <param name="clientX509Certificate2ThumbprintOrSubjectName">The X509Certificate2 file to use for client authentication.</param>
/// <returns>A <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithProxy([NotNull] string proxyUrl, [CanBeNull] string clientX509Certificate2ThumbprintOrSubjectName = null);
/// <summary> /// <summary>
/// WithProxy using IProxyAndRecordSettings. /// WithProxy using IProxyAndRecordSettings.
/// </summary> /// </summary>
/// <param name="settings">The IProxyAndRecordSettings.</param> /// <param name="settings">The IProxyAndRecordSettings.</param>
/// <returns>A <see cref="IResponseBuilder"/>.</returns> /// <returns>A <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithProxy([NotNull] ProxyAndRecordSettings settings); IResponseBuilder WithProxy([NotNull] ProxyAndRecordSettings settings);
}
} }
@@ -1,9 +1,8 @@
namespace WireMock.ResponseBuilders namespace WireMock.ResponseBuilders;
/// <summary>
/// The ResponseBuilder interface.
/// </summary>
public interface IResponseBuilder : IProxyResponseBuilder
{ {
/// <summary>
/// The ResponseBuilder interface.
/// </summary>
public interface IResponseBuilder : IProxyResponseBuilder
{
}
} }
@@ -1,47 +1,46 @@
using System.Net; using System.Net;
using WireMock.Settings; using WireMock.Settings;
namespace WireMock.ResponseBuilders namespace WireMock.ResponseBuilders;
/// <summary>
/// The StatusCodeResponseBuilder interface.
/// </summary>
public interface IStatusCodeResponseBuilder : IHeadersResponseBuilder
{ {
/// <summary> /// <summary>
/// The StatusCodeResponseBuilder interface. /// The with status code.
/// By default all status codes are allowed, to change this behaviour, see <inheritdoc cref="WireMockServerSettings.AllowOnlyDefinedHttpStatusCodeInResponse"/>.
/// </summary> /// </summary>
public interface IStatusCodeResponseBuilder : IHeadersResponseBuilder /// <param name="code">The code.</param>
{ /// <returns>The <see cref="IResponseBuilder"/>.</returns>
/// <summary> IResponseBuilder WithStatusCode(int code);
/// The with status code.
/// By default all status codes are allowed, to change this behaviour, see <inheritdoc cref="WireMockServerSettings.AllowOnlyDefinedHttpStatusCodeInResponse"/>.
/// </summary>
/// <param name="code">The code.</param>
/// <returns>The <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithStatusCode(int code);
/// <summary> /// <summary>
/// The with status code. /// The with status code.
/// By default all status codes are allowed, to change this behaviour, see <inheritdoc cref="WireMockServerSettings.AllowOnlyDefinedHttpStatusCodeInResponse"/>. /// By default all status codes are allowed, to change this behaviour, see <inheritdoc cref="WireMockServerSettings.AllowOnlyDefinedHttpStatusCodeInResponse"/>.
/// </summary> /// </summary>
/// <param name="code">The code.</param> /// <param name="code">The code.</param>
/// <returns>The <see cref="IResponseBuilder"/>.</returns> /// <returns>The <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithStatusCode(string code); IResponseBuilder WithStatusCode(string code);
/// <summary> /// <summary>
/// The with status code. /// The with status code.
/// By default all status codes are allowed, to change this behaviour, see <inheritdoc cref="WireMockServerSettings.AllowOnlyDefinedHttpStatusCodeInResponse"/>. /// By default all status codes are allowed, to change this behaviour, see <inheritdoc cref="WireMockServerSettings.AllowOnlyDefinedHttpStatusCodeInResponse"/>.
/// </summary> /// </summary>
/// <param name="code">The code.</param> /// <param name="code">The code.</param>
/// <returns>The <see cref="IResponseBuilder"/>.</returns> /// <returns>The <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithStatusCode(HttpStatusCode code); IResponseBuilder WithStatusCode(HttpStatusCode code);
/// <summary> /// <summary>
/// The with Success status code (200). /// The with Success status code (200).
/// </summary> /// </summary>
/// <returns>The <see cref="IResponseBuilder"/>.</returns> /// <returns>The <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithSuccess(); IResponseBuilder WithSuccess();
/// <summary> /// <summary>
/// The with NotFound status code (404). /// The with NotFound status code (404).
/// </summary> /// </summary>
/// <returns>The <see cref="IResponseBuilder"/>.</returns> /// <returns>The <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithNotFound(); IResponseBuilder WithNotFound();
}
} }
@@ -1,34 +1,33 @@
using WireMock.Types; using WireMock.Types;
namespace WireMock.ResponseBuilders namespace WireMock.ResponseBuilders;
/// <summary>
/// The TransformResponseBuilder interface.
/// </summary>
public interface ITransformResponseBuilder : IDelayResponseBuilder
{ {
/// <summary> /// <summary>
/// The TransformResponseBuilder interface. /// Use the Handlebars.Net ResponseMessage transformer.
/// </summary> /// </summary>
public interface ITransformResponseBuilder : IDelayResponseBuilder /// <returns>
{ /// The <see cref="IResponseBuilder"/>.
/// <summary> /// </returns>
/// Use the Handlebars.Net ResponseMessage transformer. IResponseBuilder WithTransformer(bool transformContentFromBodyAsFile);
/// </summary>
/// <returns>
/// The <see cref="IResponseBuilder"/>.
/// </returns>
IResponseBuilder WithTransformer(bool transformContentFromBodyAsFile);
/// <summary> /// <summary>
/// Use the Handlebars.Net ResponseMessage transformer. /// Use the Handlebars.Net ResponseMessage transformer.
/// </summary> /// </summary>
/// <returns> /// <returns>
/// The <see cref="IResponseBuilder"/>. /// The <see cref="IResponseBuilder"/>.
/// </returns> /// </returns>
IResponseBuilder WithTransformer(ReplaceNodeOptions options); IResponseBuilder WithTransformer(ReplaceNodeOptions options);
/// <summary> /// <summary>
/// Use a specific ResponseMessage transformer. /// Use a specific ResponseMessage transformer.
/// </summary> /// </summary>
/// <returns> /// <returns>
/// The <see cref="IResponseBuilder"/>. /// The <see cref="IResponseBuilder"/>.
/// </returns> /// </returns>
IResponseBuilder WithTransformer(TransformerType transformerType = TransformerType.Handlebars, bool transformContentFromBodyAsFile = false, ReplaceNodeOptions options = ReplaceNodeOptions.None); IResponseBuilder WithTransformer(TransformerType transformerType = TransformerType.Handlebars, bool transformContentFromBodyAsFile = false, ReplaceNodeOptions options = ReplaceNodeOptions.None);
}
} }
@@ -1,60 +1,62 @@
using System; using System;
using System.Diagnostics.CodeAnalysis;
using System.Threading.Tasks; using System.Threading.Tasks;
using Stef.Validation; using Stef.Validation;
namespace WireMock.ResponseBuilders namespace WireMock.ResponseBuilders;
public partial class Response
{ {
public partial class Response /// <summary>
/// A delegate to execute to generate the response.
/// </summary>
[MemberNotNullWhen(true, nameof(WithCallbackUsed))]
public Func<IRequestMessage, ResponseMessage>? Callback { get; private set; }
/// <summary>
/// A delegate to execute to generate the response async.
/// </summary>
[MemberNotNullWhen(true, nameof(WithCallbackUsed))]
public Func<IRequestMessage, Task<ResponseMessage>>? CallbackAsync { get; private set; }
/// <summary>
/// Defines if the method WithCallback(...) is used.
/// </summary>
public bool WithCallbackUsed { get; private set; }
/// <inheritdoc />
public IResponseBuilder WithCallback(Func<IRequestMessage, ResponseMessage> callbackHandler)
{ {
/// <summary> Guard.NotNull(callbackHandler);
/// A delegate to execute to generate the response.
/// </summary>
public Func<IRequestMessage, ResponseMessage> Callback { get; private set; }
/// <summary> return WithCallbackInternal(true, callbackHandler);
/// A delegate to execute to generate the response async. }
/// </summary>
public Func<IRequestMessage, Task<ResponseMessage>> CallbackAsync { get; private set; }
/// <summary> /// <inheritdoc />
/// Defines if the method WithCallback(...) is used. public IResponseBuilder WithCallback(Func<IRequestMessage, Task<ResponseMessage>> callbackHandler)
/// </summary> {
public bool WithCallbackUsed { get; private set; } Guard.NotNull(callbackHandler);
/// <inheritdoc /> return WithCallbackInternal(true, callbackHandler);
public IResponseBuilder WithCallback(Func<IRequestMessage, ResponseMessage> callbackHandler) }
{
Guard.NotNull(callbackHandler, nameof(callbackHandler));
return WithCallbackInternal(true, callbackHandler); private IResponseBuilder WithCallbackInternal(bool withCallbackUsed, Func<IRequestMessage, ResponseMessage> callbackHandler)
} {
Guard.NotNull(callbackHandler);
/// <inheritdoc /> WithCallbackUsed = withCallbackUsed;
public IResponseBuilder WithCallback(Func<IRequestMessage, Task<ResponseMessage>> callbackHandler) Callback = callbackHandler;
{
Guard.NotNull(callbackHandler, nameof(callbackHandler));
return WithCallbackInternal(true, callbackHandler); return this;
} }
private IResponseBuilder WithCallbackInternal(bool withCallbackUsed, Func<IRequestMessage, ResponseMessage> callbackHandler) private IResponseBuilder WithCallbackInternal(bool withCallbackUsed, Func<IRequestMessage, Task<ResponseMessage>> callbackHandler)
{ {
Guard.NotNull(callbackHandler, nameof(callbackHandler)); Guard.NotNull(callbackHandler);
WithCallbackUsed = withCallbackUsed; WithCallbackUsed = withCallbackUsed;
Callback = callbackHandler; CallbackAsync = callbackHandler;
return this; return this;
}
private IResponseBuilder WithCallbackInternal(bool withCallbackUsed, Func<IRequestMessage, Task<ResponseMessage>> callbackHandler)
{
Guard.NotNull(callbackHandler, nameof(callbackHandler));
WithCallbackUsed = withCallbackUsed;
CallbackAsync = callbackHandler;
return this;
}
} }
} }
@@ -1,14 +1,13 @@
namespace WireMock.ResponseBuilders namespace WireMock.ResponseBuilders;
{
public partial class Response
{
/// <inheritdoc cref="IFaultResponseBuilder.WithFault(FaultType, double?)"/>
public IResponseBuilder WithFault(FaultType faultType, double? percentage = null)
{
ResponseMessage.FaultType = faultType;
ResponseMessage.FaultPercentage = percentage;
return this; public partial class Response
} {
/// <inheritdoc cref="IFaultResponseBuilder.WithFault(FaultType, double?)"/>
public IResponseBuilder WithFault(FaultType faultType, double? percentage = null)
{
ResponseMessage.FaultType = faultType;
ResponseMessage.FaultPercentage = percentage;
return this;
} }
} }
@@ -7,7 +7,7 @@ namespace WireMock.ResponseBuilders;
public partial class Response public partial class Response
{ {
private HttpClient _httpClientForProxy; private HttpClient? _httpClientForProxy;
/// <summary> /// <summary>
/// The WebProxy settings. /// The WebProxy settings.
+414 -415
View File
@@ -10,7 +10,6 @@ using System.Threading.Tasks;
using JetBrains.Annotations; using JetBrains.Annotations;
using Stef.Validation; using Stef.Validation;
using WireMock.Proxy; using WireMock.Proxy;
using WireMock.ResponseProviders;
using WireMock.Settings; using WireMock.Settings;
using WireMock.Transformers; using WireMock.Transformers;
using WireMock.Transformers.Handlebars; using WireMock.Transformers.Handlebars;
@@ -18,464 +17,464 @@ using WireMock.Transformers.Scriban;
using WireMock.Types; using WireMock.Types;
using WireMock.Util; using WireMock.Util;
namespace WireMock.ResponseBuilders namespace WireMock.ResponseBuilders;
/// <summary>
/// The Response.
/// </summary>
public partial class Response : IResponseBuilder
{ {
private static readonly ThreadLocal<Random> Random = new(() => new Random(DateTime.UtcNow.Millisecond));
private TimeSpan? _delay;
/// <summary> /// <summary>
/// The Response. /// The minimum random delay in milliseconds.
/// </summary> /// </summary>
public partial class Response : IResponseBuilder public int? MinimumDelayMilliseconds { get; private set; }
/// <summary>
/// The maximum random delay in milliseconds.
/// </summary>
public int? MaximumDelayMilliseconds { get; private set; }
/// <summary>
/// The delay
/// </summary>
public TimeSpan? Delay
{ {
private static readonly ThreadLocal<Random> Random = new ThreadLocal<Random>(() => new Random(DateTime.UtcNow.Millisecond)); get
private TimeSpan? _delay;
/// <summary>
/// The minimum random delay in milliseconds.
/// </summary>
public int? MinimumDelayMilliseconds { get; private set; }
/// <summary>
/// The maximum random delay in milliseconds.
/// </summary>
public int? MaximumDelayMilliseconds { get; private set; }
/// <summary>
/// The delay
/// </summary>
public TimeSpan? Delay
{ {
get if (MinimumDelayMilliseconds != null && MaximumDelayMilliseconds != null)
{ {
if (MinimumDelayMilliseconds != null && MaximumDelayMilliseconds != null) return TimeSpan.FromMilliseconds(Random.Value!.Next(MinimumDelayMilliseconds.Value, MaximumDelayMilliseconds.Value));
{
return TimeSpan.FromMilliseconds(Random.Value.Next(MinimumDelayMilliseconds.Value, MaximumDelayMilliseconds.Value));
}
return _delay;
} }
private set => _delay = value; return _delay;
} }
/// <summary> private set => _delay = value;
/// Gets a value indicating whether [use transformer]. }
/// </summary>
public bool UseTransformer { get; private set; }
/// <summary> /// <summary>
/// Gets the type of the transformer. /// Gets a value indicating whether [use transformer].
/// </summary> /// </summary>
public TransformerType TransformerType { get; private set; } public bool UseTransformer { get; private set; }
/// <summary> /// <summary>
/// Gets a value indicating whether to use the Handlebars transformer for the content from the referenced BodyAsFile. /// Gets the type of the transformer.
/// </summary> /// </summary>
public bool UseTransformerForBodyAsFile { get; private set; } public TransformerType TransformerType { get; private set; }
/// <summary> /// <summary>
/// Gets the ReplaceNodeOptions to use when transforming a JSON node. /// Gets a value indicating whether to use the Handlebars transformer for the content from the referenced BodyAsFile.
/// </summary> /// </summary>
public ReplaceNodeOptions TransformerReplaceNodeOptions { get; private set; } public bool UseTransformerForBodyAsFile { get; private set; }
/// <summary> /// <summary>
/// Gets the response message. /// Gets the ReplaceNodeOptions to use when transforming a JSON node.
/// </summary> /// </summary>
public ResponseMessage ResponseMessage { get; } public ReplaceNodeOptions TransformerReplaceNodeOptions { get; private set; }
/// <summary> /// <summary>
/// Creates this instance. /// Gets the response message.
/// </summary> /// </summary>
/// <param name="responseMessage">ResponseMessage</param> public ResponseMessage ResponseMessage { get; }
/// <returns>A <see cref="IResponseBuilder"/>.</returns>
[PublicAPI] /// <summary>
public static IResponseBuilder Create([CanBeNull] ResponseMessage responseMessage = null) /// Creates this instance.
/// </summary>
/// <param name="responseMessage">ResponseMessage</param>
/// <returns>A <see cref="IResponseBuilder"/>.</returns>
[PublicAPI]
public static IResponseBuilder Create(ResponseMessage? responseMessage = null)
{
var message = responseMessage ?? new ResponseMessage();
return new Response(message);
}
/// <summary>
/// Creates this instance with the specified function.
/// </summary>
/// <param name="func">The callback function.</param>
/// <returns>A <see cref="IResponseBuilder"/>.</returns>
[PublicAPI]
public static IResponseBuilder Create(Func<ResponseMessage> func)
{
Guard.NotNull(func);
return new Response(func());
}
/// <summary>
/// Initializes a new instance of the <see cref="Response"/> class.
/// </summary>
/// <param name="responseMessage">
/// The response.
/// </param>
private Response(ResponseMessage responseMessage)
{
ResponseMessage = responseMessage;
}
/// <inheritdoc cref="IStatusCodeResponseBuilder.WithStatusCode(int)"/>
[PublicAPI]
public IResponseBuilder WithStatusCode(int code)
{
ResponseMessage.StatusCode = code;
return this;
}
/// <inheritdoc cref="IStatusCodeResponseBuilder.WithStatusCode(string)"/>
[PublicAPI]
public IResponseBuilder WithStatusCode(string code)
{
ResponseMessage.StatusCode = code;
return this;
}
/// <inheritdoc cref="IStatusCodeResponseBuilder.WithStatusCode(HttpStatusCode)"/>
[PublicAPI]
public IResponseBuilder WithStatusCode(HttpStatusCode code)
{
return WithStatusCode((int)code);
}
/// <summary>
/// The with Success status code (200).
/// </summary>
/// <returns>A <see cref="IResponseBuilder"/>.</returns>
[PublicAPI]
public IResponseBuilder WithSuccess()
{
return WithStatusCode((int)HttpStatusCode.OK);
}
/// <summary>
/// The with NotFound status code (404).
/// </summary>
/// <returns>The <see cref="IResponseBuilder"/>.</returns>
[PublicAPI]
public IResponseBuilder WithNotFound()
{
return WithStatusCode((int)HttpStatusCode.NotFound);
}
/// <inheritdoc cref="IHeadersResponseBuilder.WithHeader(string, string[])"/>
public IResponseBuilder WithHeader(string name, params string[] values)
{
Guard.NotNull(name);
ResponseMessage.AddHeader(name, values);
return this;
}
/// <inheritdoc cref="IHeadersResponseBuilder.WithHeaders(IDictionary{string, string})"/>
public IResponseBuilder WithHeaders(IDictionary<string, string> headers)
{
Guard.NotNull(headers);
ResponseMessage.Headers = headers.ToDictionary(header => header.Key, header => new WireMockList<string>(header.Value));
return this;
}
/// <inheritdoc cref="IHeadersResponseBuilder.WithHeaders(IDictionary{string, string[]})"/>
public IResponseBuilder WithHeaders(IDictionary<string, string[]> headers)
{
Guard.NotNull(headers);
ResponseMessage.Headers = headers.ToDictionary(header => header.Key, header => new WireMockList<string>(header.Value));
return this;
}
/// <inheritdoc cref="IHeadersResponseBuilder.WithHeaders(IDictionary{string, WireMockList{string}})"/>
public IResponseBuilder WithHeaders(IDictionary<string, WireMockList<string>> headers)
{
Guard.NotNull(headers);
ResponseMessage.Headers = headers;
return this;
}
/// <inheritdoc />
public IResponseBuilder WithBody(Func<IRequestMessage, string> bodyFactory, string? destination = BodyDestinationFormat.SameAsSource, Encoding? encoding = null)
{
Guard.NotNull(bodyFactory, nameof(bodyFactory));
return WithCallbackInternal(true, req => new ResponseMessage
{ {
var message = responseMessage ?? new ResponseMessage(); BodyData = new BodyData
return new Response(message);
}
/// <summary>
/// Creates this instance with the specified function.
/// </summary>
/// <param name="func">The callback function.</param>
/// <returns>A <see cref="IResponseBuilder"/>.</returns>
[PublicAPI]
public static IResponseBuilder Create([NotNull] Func<ResponseMessage> func)
{
Guard.NotNull(func, nameof(func));
return new Response(func());
}
/// <summary>
/// Initializes a new instance of the <see cref="Response"/> class.
/// </summary>
/// <param name="responseMessage">
/// The response.
/// </param>
private Response(ResponseMessage responseMessage)
{
ResponseMessage = responseMessage;
}
/// <inheritdoc cref="IStatusCodeResponseBuilder.WithStatusCode(int)"/>
[PublicAPI]
public IResponseBuilder WithStatusCode(int code)
{
ResponseMessage.StatusCode = code;
return this;
}
/// <inheritdoc cref="IStatusCodeResponseBuilder.WithStatusCode(string)"/>
[PublicAPI]
public IResponseBuilder WithStatusCode(string code)
{
ResponseMessage.StatusCode = code;
return this;
}
/// <inheritdoc cref="IStatusCodeResponseBuilder.WithStatusCode(HttpStatusCode)"/>
[PublicAPI]
public IResponseBuilder WithStatusCode(HttpStatusCode code)
{
return WithStatusCode((int)code);
}
/// <summary>
/// The with Success status code (200).
/// </summary>
/// <returns>A <see cref="IResponseBuilder"/>.</returns>
[PublicAPI]
public IResponseBuilder WithSuccess()
{
return WithStatusCode((int)HttpStatusCode.OK);
}
/// <summary>
/// The with NotFound status code (404).
/// </summary>
/// <returns>The <see cref="IResponseBuilder"/>.</returns>
[PublicAPI]
public IResponseBuilder WithNotFound()
{
return WithStatusCode((int)HttpStatusCode.NotFound);
}
/// <inheritdoc cref="IHeadersResponseBuilder.WithHeader(string, string[])"/>
public IResponseBuilder WithHeader(string name, params string[] values)
{
Guard.NotNull(name, nameof(name));
ResponseMessage.AddHeader(name, values);
return this;
}
/// <inheritdoc cref="IHeadersResponseBuilder.WithHeaders(IDictionary{string, string})"/>
public IResponseBuilder WithHeaders(IDictionary<string, string> headers)
{
Guard.NotNull(headers, nameof(headers));
ResponseMessage.Headers = headers.ToDictionary(header => header.Key, header => new WireMockList<string>(header.Value));
return this;
}
/// <inheritdoc cref="IHeadersResponseBuilder.WithHeaders(IDictionary{string, string[]})"/>
public IResponseBuilder WithHeaders(IDictionary<string, string[]> headers)
{
Guard.NotNull(headers, nameof(headers));
ResponseMessage.Headers = headers.ToDictionary(header => header.Key, header => new WireMockList<string>(header.Value));
return this;
}
/// <inheritdoc cref="IHeadersResponseBuilder.WithHeaders(IDictionary{string, WireMockList{string}})"/>
public IResponseBuilder WithHeaders(IDictionary<string, WireMockList<string>> headers)
{
ResponseMessage.Headers = headers;
return this;
}
/// <inheritdoc />
public IResponseBuilder WithBody(Func<IRequestMessage, string> bodyFactory, string destination = BodyDestinationFormat.SameAsSource, Encoding encoding = null)
{
Guard.NotNull(bodyFactory, nameof(bodyFactory));
return WithCallbackInternal(true, req => new ResponseMessage
{ {
BodyData = new BodyData DetectedBodyType = BodyType.String,
{ BodyAsString = bodyFactory(req),
DetectedBodyType = BodyType.String, Encoding = encoding ?? Encoding.UTF8
BodyAsString = bodyFactory(req),
Encoding = encoding ?? Encoding.UTF8
}
});
}
/// <inheritdoc />
public IResponseBuilder WithBody(Func<IRequestMessage, Task<string>> bodyFactory, string destination = BodyDestinationFormat.SameAsSource, Encoding encoding = null)
{
Guard.NotNull(bodyFactory, nameof(bodyFactory));
return WithCallbackInternal(true, async req => new ResponseMessage
{
BodyData = new BodyData
{
DetectedBodyType = BodyType.String,
BodyAsString = await bodyFactory(req).ConfigureAwait(false),
Encoding = encoding ?? Encoding.UTF8
}
});
}
/// <inheritdoc cref="IBodyResponseBuilder.WithBody(byte[], string, Encoding)"/>
public IResponseBuilder WithBody(byte[] body, string destination = BodyDestinationFormat.SameAsSource, Encoding encoding = null)
{
Guard.NotNull(body, nameof(body));
ResponseMessage.BodyDestination = destination;
ResponseMessage.BodyData = new BodyData();
switch (destination)
{
case BodyDestinationFormat.String:
var enc = encoding ?? Encoding.UTF8;
ResponseMessage.BodyData.DetectedBodyType = BodyType.String;
ResponseMessage.BodyData.BodyAsString = enc.GetString(body);
ResponseMessage.BodyData.Encoding = enc;
break;
default:
ResponseMessage.BodyData.DetectedBodyType = BodyType.Bytes;
ResponseMessage.BodyData.BodyAsBytes = body;
break;
} }
});
}
return this; /// <inheritdoc />
} public IResponseBuilder WithBody(Func<IRequestMessage, Task<string>> bodyFactory, string? destination = BodyDestinationFormat.SameAsSource, Encoding? encoding = null)
{
Guard.NotNull(bodyFactory, nameof(bodyFactory));
/// <inheritdoc cref="IBodyResponseBuilder.WithBodyFromFile"/> return WithCallbackInternal(true, async req => new ResponseMessage
public IResponseBuilder WithBodyFromFile(string filename, bool cache = true)
{ {
Guard.NotNull(filename, nameof(filename)); BodyData = new BodyData
ResponseMessage.BodyData = new BodyData
{ {
BodyAsFileIsCached = cache, DetectedBodyType = BodyType.String,
BodyAsFile = filename BodyAsString = await bodyFactory(req).ConfigureAwait(false),
}; Encoding = encoding ?? Encoding.UTF8
}
});
}
if (cache && !UseTransformer) /// <inheritdoc />
{ public IResponseBuilder WithBody(byte[] body, string? destination = BodyDestinationFormat.SameAsSource, Encoding? encoding = null)
{
Guard.NotNull(body);
ResponseMessage.BodyDestination = destination;
ResponseMessage.BodyData = new BodyData();
switch (destination)
{
case BodyDestinationFormat.String:
var enc = encoding ?? Encoding.UTF8;
ResponseMessage.BodyData.DetectedBodyType = BodyType.String;
ResponseMessage.BodyData.BodyAsString = enc.GetString(body);
ResponseMessage.BodyData.Encoding = enc;
break;
default:
ResponseMessage.BodyData.DetectedBodyType = BodyType.Bytes; ResponseMessage.BodyData.DetectedBodyType = BodyType.Bytes;
ResponseMessage.BodyData.BodyAsBytes = body;
break;
}
return this;
}
/// <inheritdoc cref="IBodyResponseBuilder.WithBodyFromFile"/>
public IResponseBuilder WithBodyFromFile(string filename, bool cache = true)
{
Guard.NotNull(filename);
ResponseMessage.BodyData = new BodyData
{
BodyAsFileIsCached = cache,
BodyAsFile = filename
};
if (cache && !UseTransformer)
{
ResponseMessage.BodyData.DetectedBodyType = BodyType.Bytes;
}
else
{
ResponseMessage.BodyData.DetectedBodyType = BodyType.File;
}
return this;
}
/// <inheritdoc />
public IResponseBuilder WithBody(string body, string? destination = BodyDestinationFormat.SameAsSource, Encoding? encoding = null)
{
Guard.NotNull(body);
encoding ??= Encoding.UTF8;
ResponseMessage.BodyDestination = destination;
ResponseMessage.BodyData = new BodyData
{
Encoding = encoding
};
switch (destination)
{
case BodyDestinationFormat.Bytes:
ResponseMessage.BodyData.DetectedBodyType = BodyType.Bytes;
ResponseMessage.BodyData.BodyAsBytes = encoding.GetBytes(body);
break;
case BodyDestinationFormat.Json:
ResponseMessage.BodyData.DetectedBodyType = BodyType.Json;
ResponseMessage.BodyData.BodyAsJson = JsonUtils.DeserializeObject(body);
break;
default:
ResponseMessage.BodyData.DetectedBodyType = BodyType.String;
ResponseMessage.BodyData.BodyAsString = body;
break;
}
return this;
}
/// <inheritdoc cref="IBodyResponseBuilder.WithBodyAsJson(object, Encoding, bool?)"/>
public IResponseBuilder WithBodyAsJson(object body, Encoding? encoding = null, bool? indented = null)
{
Guard.NotNull(body);
ResponseMessage.BodyDestination = null;
ResponseMessage.BodyData = new BodyData
{
Encoding = encoding,
DetectedBodyType = BodyType.Json,
BodyAsJson = body,
BodyAsJsonIndented = indented
};
return this;
}
/// <inheritdoc cref="IBodyResponseBuilder.WithBodyAsJson(object, bool)"/>
public IResponseBuilder WithBodyAsJson(object body, bool indented)
{
return WithBodyAsJson(body, null, indented);
}
/// <inheritdoc cref="ITransformResponseBuilder.WithTransformer(bool)"/>
public IResponseBuilder WithTransformer(bool transformContentFromBodyAsFile)
{
return WithTransformer(TransformerType.Handlebars, transformContentFromBodyAsFile);
}
/// <inheritdoc cref="ITransformResponseBuilder.WithTransformer(ReplaceNodeOptions)"/>
public IResponseBuilder WithTransformer(ReplaceNodeOptions options)
{
return WithTransformer(TransformerType.Handlebars, false, options);
}
/// <inheritdoc />
public IResponseBuilder WithTransformer(TransformerType transformerType, bool transformContentFromBodyAsFile = false, ReplaceNodeOptions options = ReplaceNodeOptions.None)
{
UseTransformer = true;
TransformerType = transformerType;
UseTransformerForBodyAsFile = transformContentFromBodyAsFile;
TransformerReplaceNodeOptions = options;
return this;
}
/// <inheritdoc />
public IResponseBuilder WithDelay(TimeSpan delay)
{
Guard.Condition(delay, d => d == Timeout.InfiniteTimeSpan || d > TimeSpan.Zero);
Delay = delay;
return this;
}
/// <inheritdoc />
public IResponseBuilder WithDelay(int milliseconds)
{
return WithDelay(TimeSpan.FromMilliseconds(milliseconds));
}
/// <inheritdoc />
public IResponseBuilder WithRandomDelay(int minimumMilliseconds = 0, int maximumMilliseconds = 60_000)
{
Guard.Condition(minimumMilliseconds, min => min >= 0);
Guard.Condition(maximumMilliseconds, max => max > minimumMilliseconds);
MinimumDelayMilliseconds = minimumMilliseconds;
MaximumDelayMilliseconds = maximumMilliseconds;
return this;
}
/// <inheritdoc />
public async Task<(IResponseMessage Message, IMapping? Mapping)> ProvideResponseAsync(IRequestMessage requestMessage, WireMockServerSettings settings)
{
Guard.NotNull(requestMessage);
Guard.NotNull(settings);
if (Delay != null)
{
await Task.Delay(Delay.Value).ConfigureAwait(false);
}
if (ProxyAndRecordSettings != null && _httpClientForProxy != null)
{
string RemoveFirstOccurrence(string source, string find)
{
int place = source.IndexOf(find, StringComparison.OrdinalIgnoreCase);
return place >= 0 ? source.Remove(place, find.Length) : source;
}
var requestUri = new Uri(requestMessage.Url);
// Build the proxy url and skip duplicates
string extra = RemoveFirstOccurrence(requestUri.LocalPath.TrimEnd('/'), new Uri(ProxyAndRecordSettings.Url).LocalPath.TrimEnd('/'));
requestMessage.ProxyUrl = ProxyAndRecordSettings.Url + extra + requestUri.Query;
var proxyHelper = new ProxyHelper(settings);
return await proxyHelper.SendAsync(
ProxyAndRecordSettings,
_httpClientForProxy,
requestMessage,
requestMessage.ProxyUrl
).ConfigureAwait(false);
}
ResponseMessage responseMessage;
if (!WithCallbackUsed)
{
responseMessage = ResponseMessage;
}
else
{
if (Callback != null)
{
responseMessage = Callback(requestMessage);
} }
else else
{ {
ResponseMessage.BodyData.DetectedBodyType = BodyType.File; responseMessage = await CallbackAsync!(requestMessage).ConfigureAwait(false);
} }
return this; // Copy StatusCode from ResponseMessage (if defined)
if (ResponseMessage.StatusCode != null)
{
responseMessage.StatusCode = ResponseMessage.StatusCode;
}
// Copy Headers from ResponseMessage (if defined)
if (ResponseMessage.Headers?.Count > 0)
{
responseMessage.Headers = ResponseMessage.Headers;
}
} }
/// <inheritdoc cref="IBodyResponseBuilder.WithBody(string, string, Encoding)"/> if (UseTransformer)
public IResponseBuilder WithBody(string body, string destination = BodyDestinationFormat.SameAsSource, Encoding encoding = null)
{ {
Guard.NotNull(body, nameof(body)); ITransformer responseMessageTransformer;
switch (TransformerType)
encoding = encoding ?? Encoding.UTF8;
ResponseMessage.BodyDestination = destination;
ResponseMessage.BodyData = new BodyData
{ {
Encoding = encoding case TransformerType.Handlebars:
}; var factoryHandlebars = new HandlebarsContextFactory(settings.FileSystemHandler, settings.HandlebarsRegistrationCallback);
responseMessageTransformer = new Transformer(factoryHandlebars);
switch (destination)
{
case BodyDestinationFormat.Bytes:
ResponseMessage.BodyData.DetectedBodyType = BodyType.Bytes;
ResponseMessage.BodyData.BodyAsBytes = encoding.GetBytes(body);
break; break;
case BodyDestinationFormat.Json: case TransformerType.Scriban:
ResponseMessage.BodyData.DetectedBodyType = BodyType.Json; case TransformerType.ScribanDotLiquid:
ResponseMessage.BodyData.BodyAsJson = JsonUtils.DeserializeObject(body); var factoryDotLiquid = new ScribanContextFactory(settings.FileSystemHandler, TransformerType);
responseMessageTransformer = new Transformer(factoryDotLiquid);
break; break;
default: default:
ResponseMessage.BodyData.DetectedBodyType = BodyType.String; throw new NotImplementedException($"TransformerType '{TransformerType}' is not supported.");
ResponseMessage.BodyData.BodyAsString = body;
break;
} }
return this; return (responseMessageTransformer.Transform(requestMessage, responseMessage, UseTransformerForBodyAsFile, TransformerReplaceNodeOptions), null);
} }
/// <inheritdoc cref="IBodyResponseBuilder.WithBodyAsJson(object, Encoding, bool?)"/> if (!UseTransformer && ResponseMessage.BodyData?.BodyAsFileIsCached == true)
public IResponseBuilder WithBodyAsJson(object body, Encoding encoding = null, bool? indented = null)
{ {
Guard.NotNull(body, nameof(body)); ResponseMessage.BodyData.BodyAsBytes = settings.FileSystemHandler.ReadResponseBodyAsFile(responseMessage.BodyData!.BodyAsFile);
ResponseMessage.BodyDestination = null;
ResponseMessage.BodyData = new BodyData
{
Encoding = encoding,
DetectedBodyType = BodyType.Json,
BodyAsJson = body,
BodyAsJsonIndented = indented
};
return this;
} }
/// <inheritdoc cref="IBodyResponseBuilder.WithBodyAsJson(object, bool)"/> return (responseMessage, null);
public IResponseBuilder WithBodyAsJson(object body, bool indented)
{
return WithBodyAsJson(body, null, indented);
}
/// <inheritdoc cref="ITransformResponseBuilder.WithTransformer(bool)"/>
public IResponseBuilder WithTransformer(bool transformContentFromBodyAsFile)
{
return WithTransformer(TransformerType.Handlebars, transformContentFromBodyAsFile);
}
/// <inheritdoc cref="ITransformResponseBuilder.WithTransformer(ReplaceNodeOptions)"/>
public IResponseBuilder WithTransformer(ReplaceNodeOptions options)
{
return WithTransformer(TransformerType.Handlebars, false, options);
}
/// <inheritdoc />
public IResponseBuilder WithTransformer(TransformerType transformerType, bool transformContentFromBodyAsFile = false, ReplaceNodeOptions options = ReplaceNodeOptions.None)
{
UseTransformer = true;
TransformerType = transformerType;
UseTransformerForBodyAsFile = transformContentFromBodyAsFile;
TransformerReplaceNodeOptions = options;
return this;
}
/// <inheritdoc />
public IResponseBuilder WithDelay(TimeSpan delay)
{
Guard.Condition(delay, d => d == Timeout.InfiniteTimeSpan || d > TimeSpan.Zero);
Delay = delay;
return this;
}
/// <inheritdoc />
public IResponseBuilder WithDelay(int milliseconds)
{
return WithDelay(TimeSpan.FromMilliseconds(milliseconds));
}
/// <inheritdoc />
public IResponseBuilder WithRandomDelay(int minimumMilliseconds = 0, int maximumMilliseconds = 60_000)
{
Guard.Condition(minimumMilliseconds, min => min >= 0);
Guard.Condition(maximumMilliseconds, max => max > minimumMilliseconds);
MinimumDelayMilliseconds = minimumMilliseconds;
MaximumDelayMilliseconds = maximumMilliseconds;
return this;
}
/// <inheritdoc />
public async Task<(IResponseMessage Message, IMapping Mapping)> ProvideResponseAsync(IRequestMessage requestMessage, WireMockServerSettings settings)
{
Guard.NotNull(requestMessage, nameof(requestMessage));
Guard.NotNull(settings, nameof(settings));
if (Delay != null)
{
await Task.Delay(Delay.Value).ConfigureAwait(false);
}
if (ProxyAndRecordSettings != null && _httpClientForProxy != null)
{
string RemoveFirstOccurrence(string source, string find)
{
int place = source.IndexOf(find, StringComparison.OrdinalIgnoreCase);
return place >= 0 ? source.Remove(place, find.Length) : source;
}
var requestUri = new Uri(requestMessage.Url);
// Build the proxy url and skip duplicates
string extra = RemoveFirstOccurrence(requestUri.LocalPath.TrimEnd('/'), new Uri(ProxyAndRecordSettings.Url).LocalPath.TrimEnd('/'));
requestMessage.ProxyUrl = ProxyAndRecordSettings.Url + extra + requestUri.Query;
var proxyHelper = new ProxyHelper(settings);
return await proxyHelper.SendAsync(
ProxyAndRecordSettings,
_httpClientForProxy,
requestMessage,
requestMessage.ProxyUrl
).ConfigureAwait(false);
}
ResponseMessage responseMessage;
if (!WithCallbackUsed)
{
responseMessage = ResponseMessage;
}
else
{
if (Callback != null)
{
responseMessage = Callback(requestMessage);
}
else
{
responseMessage = await CallbackAsync(requestMessage).ConfigureAwait(false);
}
// Copy StatusCode from ResponseMessage (if defined)
if (ResponseMessage.StatusCode != null)
{
responseMessage.StatusCode = ResponseMessage.StatusCode;
}
// Copy Headers from ResponseMessage (if defined)
if (ResponseMessage.Headers?.Count > 0)
{
responseMessage.Headers = ResponseMessage.Headers;
}
}
if (UseTransformer)
{
ITransformer responseMessageTransformer;
switch (TransformerType)
{
case TransformerType.Handlebars:
var factoryHandlebars = new HandlebarsContextFactory(settings.FileSystemHandler, settings.HandlebarsRegistrationCallback);
responseMessageTransformer = new Transformer(factoryHandlebars);
break;
case TransformerType.Scriban:
case TransformerType.ScribanDotLiquid:
var factoryDotLiquid = new ScribanContextFactory(settings.FileSystemHandler, TransformerType);
responseMessageTransformer = new Transformer(factoryDotLiquid);
break;
default:
throw new NotImplementedException($"TransformerType '{TransformerType}' is not supported.");
}
return (responseMessageTransformer.Transform(requestMessage, responseMessage, UseTransformerForBodyAsFile, TransformerReplaceNodeOptions), null);
}
if (!UseTransformer && ResponseMessage.BodyData?.BodyAsFileIsCached == true)
{
ResponseMessage.BodyData.BodyAsBytes = settings.FileSystemHandler.ReadResponseBodyAsFile(responseMessage.BodyData.BodyAsFile);
}
return (responseMessage, null);
}
} }
} }
+41 -40
View File
@@ -7,50 +7,51 @@ using WireMock.Types;
using WireMock.Util; using WireMock.Util;
using Stef.Validation; using Stef.Validation;
namespace WireMock namespace WireMock;
/// <summary>
/// The ResponseMessage.
/// </summary>
public class ResponseMessage : IResponseMessage
{ {
/// <summary> /// <inheritdoc cref="IResponseMessage.Headers" />
/// The ResponseMessage. public IDictionary<string, WireMockList<string>>? Headers { get; set; } = new Dictionary<string, WireMockList<string>>();
/// </summary>
public class ResponseMessage : IResponseMessage /// <inheritdoc cref="IResponseMessage.StatusCode" />
public object? StatusCode { get; set; }
/// <inheritdoc cref="IResponseMessage.BodyOriginal" />
public string? BodyOriginal { get; set; }
/// <inheritdoc cref="IResponseMessage.BodyDestination" />
public string? BodyDestination { get; set; }
/// <inheritdoc cref="IResponseMessage.BodyData" />
public IBodyData? BodyData { get; set; }
/// <inheritdoc cref="IResponseMessage.FaultType" />
public FaultType FaultType { get; set; }
/// <inheritdoc cref="IResponseMessage.FaultPercentage" />
public double? FaultPercentage { get; set; }
/// <inheritdoc cref="IResponseMessage.AddHeader(string, string)" />
public void AddHeader(string name, string value)
{ {
/// <inheritdoc cref="IResponseMessage.Headers" /> Headers ??= new Dictionary<string, WireMockList<string>>();
public IDictionary<string, WireMockList<string>>? Headers { get; set; } = new Dictionary<string, WireMockList<string>>(); Headers.Add(name, new WireMockList<string>(value));
}
/// <inheritdoc cref="IResponseMessage.StatusCode" /> /// <inheritdoc cref="IResponseMessage.AddHeader(string, string[])" />
public object StatusCode { get; set; } public void AddHeader(string name, params string[] values)
{
Guard.NotNullOrEmpty(values);
/// <inheritdoc cref="IResponseMessage.BodyOriginal" /> Headers ??= new Dictionary<string, WireMockList<string>>();
public string BodyOriginal { get; set; } var newHeaderValues = Headers.TryGetValue(name, out WireMockList<string>? existingValues)
? values.Union(existingValues).ToArray()
: values;
/// <inheritdoc cref="IResponseMessage.BodyDestination" /> Headers[name] = new WireMockList<string>(newHeaderValues);
public string BodyDestination { get; set; }
/// <inheritdoc cref="IResponseMessage.BodyData" />
public IBodyData? BodyData { get; set; }
/// <inheritdoc cref="IResponseMessage.FaultType" />
public FaultType FaultType { get; set; }
/// <inheritdoc cref="IResponseMessage.FaultPercentage" />
public double? FaultPercentage { get; set; }
/// <inheritdoc cref="IResponseMessage.AddHeader(string, string)" />
public void AddHeader(string name, string value)
{
Headers.Add(name, new WireMockList<string>(value));
}
/// <inheritdoc cref="IResponseMessage.AddHeader(string, string[])" />
public void AddHeader(string name, params string[] values)
{
Guard.NotNullOrEmpty(values, nameof(values));
var newHeaderValues = Headers.TryGetValue(name, out WireMockList<string> existingValues)
? values.Union(existingValues).ToArray()
: values;
Headers[name] = new WireMockList<string>(newHeaderValues);
}
} }
} }
@@ -2,20 +2,19 @@ using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using WireMock.Settings; using WireMock.Settings;
namespace WireMock.ResponseProviders namespace WireMock.ResponseProviders;
internal class DynamicAsyncResponseProvider : IResponseProvider
{ {
internal class DynamicAsyncResponseProvider : IResponseProvider private readonly Func<IRequestMessage, Task<IResponseMessage>> _responseMessageFunc;
public DynamicAsyncResponseProvider(Func<IRequestMessage, Task<IResponseMessage>> responseMessageFunc)
{ {
private readonly Func<IRequestMessage, Task<IResponseMessage>> _responseMessageFunc; _responseMessageFunc = responseMessageFunc;
}
public DynamicAsyncResponseProvider(Func<IRequestMessage, Task<IResponseMessage>> responseMessageFunc) public async Task<(IResponseMessage Message, IMapping? Mapping)> ProvideResponseAsync(IRequestMessage requestMessage, WireMockServerSettings settings)
{ {
_responseMessageFunc = responseMessageFunc; return (await _responseMessageFunc(requestMessage).ConfigureAwait(false), null);
}
public async Task<(IResponseMessage Message, IMapping Mapping)> ProvideResponseAsync(IRequestMessage requestMessage, WireMockServerSettings settings)
{
return (await _responseMessageFunc(requestMessage).ConfigureAwait(false), null);
}
} }
} }
@@ -2,21 +2,20 @@ using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using WireMock.Settings; using WireMock.Settings;
namespace WireMock.ResponseProviders namespace WireMock.ResponseProviders;
internal class DynamicResponseProvider : IResponseProvider
{ {
internal class DynamicResponseProvider : IResponseProvider private readonly Func<IRequestMessage, IResponseMessage> _responseMessageFunc;
public DynamicResponseProvider(Func<IRequestMessage, IResponseMessage> responseMessageFunc)
{ {
private readonly Func<IRequestMessage, IResponseMessage> _responseMessageFunc; _responseMessageFunc = responseMessageFunc;
}
public DynamicResponseProvider(Func<IRequestMessage, IResponseMessage> responseMessageFunc) public Task<(IResponseMessage Message, IMapping? Mapping)> ProvideResponseAsync(IRequestMessage requestMessage, WireMockServerSettings settings)
{ {
_responseMessageFunc = responseMessageFunc; (IResponseMessage responseMessage, IMapping? mapping) result = (_responseMessageFunc(requestMessage), null);
} return Task.FromResult(result);
public Task<(IResponseMessage Message, IMapping Mapping)> ProvideResponseAsync(IRequestMessage requestMessage, WireMockServerSettings settings)
{
(IResponseMessage responseMessage, IMapping mapping) result = (_responseMessageFunc(requestMessage), null);
return Task.FromResult(result);
}
} }
} }
@@ -1,22 +1,20 @@
// This source file is based on mock4net by Alexandre Victoor which is licensed under the Apache 2.0 License. // 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. // For more details see 'mock4net/LICENSE.txt' and 'mock4net/readme.md' in this project root.
using System.Threading.Tasks; using System.Threading.Tasks;
using JetBrains.Annotations;
using WireMock.Settings; using WireMock.Settings;
namespace WireMock.ResponseProviders namespace WireMock.ResponseProviders;
/// <summary>
/// The Response Provider interface.
/// </summary>
public interface IResponseProvider
{ {
/// <summary> /// <summary>
/// The Response Provider interface. /// The provide response.
/// </summary> /// </summary>
public interface IResponseProvider /// <param name="requestMessage">The request.</param>
{ /// <param name="settings">The WireMockServerSettings.</param>
/// <summary> /// <returns>The <see cref="ResponseMessage"/> including a new (optional) <see cref="IMapping"/>.</returns>
/// The provide response. Task<(IResponseMessage Message, IMapping? Mapping)> ProvideResponseAsync(IRequestMessage requestMessage, WireMockServerSettings settings);
/// </summary>
/// <param name="requestMessage">The request.</param>
/// <param name="settings">The WireMockServerSettings.</param>
/// <returns>The <see cref="ResponseMessage"/> including a new (optional) <see cref="IMapping"/>.</returns>
Task<(IResponseMessage Message, IMapping Mapping)> ProvideResponseAsync([NotNull] IRequestMessage requestMessage, [NotNull] WireMockServerSettings settings);
}
} }
@@ -2,22 +2,21 @@ using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using WireMock.Settings; using WireMock.Settings;
namespace WireMock.ResponseProviders namespace WireMock.ResponseProviders;
internal class ProxyAsyncResponseProvider : IResponseProvider
{ {
internal class ProxyAsyncResponseProvider : IResponseProvider private readonly Func<IRequestMessage, WireMockServerSettings, Task<IResponseMessage>> _responseMessageFunc;
private readonly WireMockServerSettings _settings;
public ProxyAsyncResponseProvider(Func<IRequestMessage, WireMockServerSettings, Task<IResponseMessage>> responseMessageFunc, WireMockServerSettings settings)
{ {
private readonly Func<IRequestMessage, WireMockServerSettings, Task<IResponseMessage>> _responseMessageFunc; _responseMessageFunc = responseMessageFunc;
private readonly WireMockServerSettings _settings; _settings = settings;
}
public ProxyAsyncResponseProvider(Func<IRequestMessage, WireMockServerSettings, Task<IResponseMessage>> responseMessageFunc, WireMockServerSettings settings) public async Task<(IResponseMessage Message, IMapping? Mapping)> ProvideResponseAsync(IRequestMessage requestMessage, WireMockServerSettings settings)
{ {
_responseMessageFunc = responseMessageFunc; return (await _responseMessageFunc(requestMessage, _settings).ConfigureAwait(false), null);
_settings = settings;
}
public async Task<(IResponseMessage Message, IMapping Mapping)> ProvideResponseAsync(IRequestMessage requestMessage, WireMockServerSettings settings)
{
return (await _responseMessageFunc(requestMessage, _settings).ConfigureAwait(false), null);
}
} }
} }
@@ -140,7 +140,7 @@ internal static class LogEntryMapper
MatchDetails = matchResult.MatchDetails.Select(md => new MatchDetails = matchResult.MatchDetails.Select(md => new
{ {
Name = md.MatcherType.Name.Replace("RequestMessage", string.Empty), Name = md.MatcherType.Name.Replace("RequestMessage", string.Empty),
Score = md.Score md.Score
} as object).ToList() } as object).ToList()
}; };
} }
@@ -6,6 +6,7 @@ using Stef.Validation;
using WireMock.Admin.Mappings; using WireMock.Admin.Mappings;
using WireMock.Matchers; using WireMock.Matchers;
using WireMock.Matchers.Request; using WireMock.Matchers.Request;
using WireMock.Models;
using WireMock.RequestBuilders; using WireMock.RequestBuilders;
using WireMock.ResponseBuilders; using WireMock.ResponseBuilders;
using WireMock.Settings; using WireMock.Settings;
@@ -116,9 +117,10 @@ internal class MappingConverter
mappingModel.Response.Delay = (int?)(response.Delay == Timeout.InfiniteTimeSpan ? TimeSpan.MaxValue.TotalMilliseconds : response.Delay?.TotalMilliseconds); mappingModel.Response.Delay = (int?)(response.Delay == Timeout.InfiniteTimeSpan ? TimeSpan.MaxValue.TotalMilliseconds : response.Delay?.TotalMilliseconds);
} }
if (mapping.Webhooks?.Length == 1) var nonNullableWebHooks = mapping.Webhooks?.Where(wh => wh != null).ToArray() ?? new IWebhook[0];
if (nonNullableWebHooks.Length == 1)
{ {
mappingModel.Webhook = WebhookMapper.Map(mapping.Webhooks[0]); mappingModel.Webhook = WebhookMapper.Map(nonNullableWebHooks[0]);
} }
else if (mapping.Webhooks?.Length > 1) else if (mapping.Webhooks?.Length > 1)
{ {
@@ -209,7 +211,7 @@ internal class MappingConverter
break; break;
} }
if (response.ResponseMessage.BodyData.Encoding != null && response.ResponseMessage.BodyData.Encoding.WebName != "utf-8") if (response.ResponseMessage.BodyData?.Encoding != null && response.ResponseMessage.BodyData.Encoding.WebName != "utf-8")
{ {
mappingModel.Response.BodyEncoding = new EncodingModel mappingModel.Response.BodyEncoding = new EncodingModel
{ {
@@ -22,10 +22,7 @@ internal class MappingToFileSaver
public void SaveMappingToFile(IMapping mapping, string? folder = null) public void SaveMappingToFile(IMapping mapping, string? folder = null)
{ {
if (folder == null) folder ??= _settings.FileSystemHandler.GetMappingFolder();
{
folder = _settings.FileSystemHandler.GetMappingFolder();
}
if (!_settings.FileSystemHandler.FolderExists(folder)) if (!_settings.FileSystemHandler.FolderExists(folder))
{ {
@@ -208,7 +208,7 @@ internal class MatcherMapper
return patternAsStringArray.ToAnyOfPatterns(); return patternAsStringArray.ToAnyOfPatterns();
} }
if (matcher.Patterns?.OfType<string>() is IEnumerable<string> patternsAsStringArray) if (matcher.Patterns?.OfType<string>() is { } patternsAsStringArray)
{ {
return patternsAsStringArray.ToAnyOfPatterns(); return patternsAsStringArray.ToAnyOfPatterns();
} }
+25 -9
View File
@@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using DevLab.JmesPath.Interop;
using WireMock.Admin.Mappings; using WireMock.Admin.Mappings;
using WireMock.Extensions; using WireMock.Extensions;
using WireMock.Matchers; using WireMock.Matchers;
@@ -76,10 +77,25 @@ internal static class PactMapper
{ {
Status = MapStatusCode(response.StatusCode), Status = MapStatusCode(response.StatusCode),
Headers = MapResponseHeaders(response.Headers), Headers = MapResponseHeaders(response.Headers),
Body = response.BodyAsJson Body = MapBody(response)
}; };
} }
private static object? MapBody(ResponseModel? response)
{
if (response?.BodyAsJson != null)
{
return response.BodyAsJson;
}
if (response?.Body != null) // In case the body is a string, try to deserialize into object, else just return the string
{
return JsonUtils.TryDeserializeObject<object?>(response.Body) ?? response.Body;
}
return null;
}
private static int MapStatusCode(object? statusCode) private static int MapStatusCode(object? statusCode)
{ {
if (statusCode is string statusCodeAsString) if (statusCode is string statusCodeAsString)
@@ -138,13 +154,13 @@ internal static class PactMapper
return jsonMatcher?.Pattern; return jsonMatcher?.Pattern;
} }
private static string GetPatternAsStringFromMatchers(MatcherModel[]? matchers, string defaultValue) //private static string GetPatternAsStringFromMatchers(MatcherModel[]? matchers, string defaultValue)
{ //{
if (matchers != null && matchers.Any() && matchers[0].Pattern is string patternAsString) // if (matchers != null && matchers.Any() && matchers[0].Pattern is string patternAsString)
{ // {
return patternAsString; // return patternAsString;
} // }
return defaultValue; // return defaultValue;
} //}
} }
+85 -88
View File
@@ -1,112 +1,109 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Stef.Validation;
using WireMock.Admin.Mappings; using WireMock.Admin.Mappings;
using WireMock.Http; using WireMock.Http;
using WireMock.Models; using WireMock.Models;
using WireMock.Types; using WireMock.Types;
using WireMock.Util; using WireMock.Util;
namespace WireMock.Serialization namespace WireMock.Serialization;
internal static class WebhookMapper
{ {
internal static class WebhookMapper public static IWebhook Map(WebhookModel model)
{ {
public static IWebhook Map(WebhookModel model) var webhook = new Webhook
{ {
var webhook = new Webhook Request = new WebhookRequest
{ {
Request = new WebhookRequest Url = model.Request.Url,
{ Method = model.Request.Method,
Url = model.Request.Url, Headers = model.Request.Headers?.ToDictionary(x => x.Key, x => new WireMockList<string>(x.Value)) ?? new Dictionary<string, WireMockList<string>>()
Method = model.Request.Method, }
Headers = model.Request.Headers?.ToDictionary(x => x.Key, x => new WireMockList<string>(x.Value)) ?? new Dictionary<string, WireMockList<string>>() };
}
};
if (model.Request.UseTransformer == true) if (model.Request.UseTransformer == true)
{
webhook.Request.UseTransformer = true;
if (!Enum.TryParse<TransformerType>(model.Request.TransformerType, out var transformerType))
{ {
webhook.Request.UseTransformer = true; transformerType = TransformerType.Handlebars;
if (!Enum.TryParse<TransformerType>(model.Request.TransformerType, out var transformerType)) }
{ webhook.Request.TransformerType = transformerType;
transformerType = TransformerType.Handlebars;
}
webhook.Request.TransformerType = transformerType;
if (!Enum.TryParse<ReplaceNodeOptions>(model.Request.TransformerReplaceNodeOptions, out var option)) if (!Enum.TryParse<ReplaceNodeOptions>(model.Request.TransformerReplaceNodeOptions, out var option))
{ {
option = ReplaceNodeOptions.None; option = ReplaceNodeOptions.None;
}
webhook.Request.TransformerReplaceNodeOptions = option;
} }
IEnumerable<string> contentTypeHeader = null; webhook.Request.TransformerReplaceNodeOptions = option;
if (webhook.Request.Headers.Any(header => string.Equals(header.Key, HttpKnownHeaderNames.ContentType, StringComparison.OrdinalIgnoreCase)))
{
contentTypeHeader = webhook.Request.Headers.First(header => string.Equals(header.Key, HttpKnownHeaderNames.ContentType, StringComparison.OrdinalIgnoreCase)).Value;
}
if (model.Request.Body != null)
{
webhook.Request.BodyData = new BodyData
{
BodyAsString = model.Request.Body,
DetectedBodyType = BodyType.String,
DetectedBodyTypeFromContentType = BodyParser.DetectBodyTypeFromContentType(contentTypeHeader?.FirstOrDefault())
};
}
else if (model.Request.BodyAsJson != null)
{
webhook.Request.BodyData = new BodyData
{
BodyAsJson = model.Request.BodyAsJson,
DetectedBodyType = BodyType.Json,
DetectedBodyTypeFromContentType = BodyParser.DetectBodyTypeFromContentType(contentTypeHeader?.FirstOrDefault())
};
}
return webhook;
} }
public static WebhookModel Map(IWebhook webhook) IEnumerable<string>? contentTypeHeader = null;
if (webhook.Request.Headers != null && webhook.Request.Headers.Any(header => string.Equals(header.Key, HttpKnownHeaderNames.ContentType, StringComparison.OrdinalIgnoreCase)))
{ {
if (webhook?.Request == null) contentTypeHeader = webhook.Request.Headers.First(header => string.Equals(header.Key, HttpKnownHeaderNames.ContentType, StringComparison.OrdinalIgnoreCase)).Value;
{
return null;
}
var model = new WebhookModel
{
Request = new WebhookRequestModel
{
Url = webhook.Request.Url,
Method = webhook.Request.Method,
Headers = webhook.Request.Headers?.ToDictionary(x => x.Key, x => x.Value.ToString()),
UseTransformer = webhook.Request.UseTransformer,
TransformerType = webhook.Request.UseTransformer == true ? webhook.Request.TransformerType.ToString() : null,
TransformerReplaceNodeOptions = webhook.Request.TransformerReplaceNodeOptions.ToString()
}
};
if (webhook.Request.BodyData != null)
{
switch (webhook.Request.BodyData.DetectedBodyType)
{
case BodyType.String:
model.Request.Body = webhook.Request.BodyData.BodyAsString;
break;
case BodyType.Json:
model.Request.BodyAsJson = webhook.Request.BodyData.BodyAsJson;
break;
default:
// Empty
break;
}
}
return model;
} }
if (model.Request.Body != null)
{
webhook.Request.BodyData = new BodyData
{
BodyAsString = model.Request.Body,
DetectedBodyType = BodyType.String,
DetectedBodyTypeFromContentType = BodyParser.DetectBodyTypeFromContentType(contentTypeHeader?.FirstOrDefault())
};
}
else if (model.Request.BodyAsJson != null)
{
webhook.Request.BodyData = new BodyData
{
BodyAsJson = model.Request.BodyAsJson,
DetectedBodyType = BodyType.Json,
DetectedBodyTypeFromContentType = BodyParser.DetectBodyTypeFromContentType(contentTypeHeader?.FirstOrDefault())
};
}
return webhook;
}
public static WebhookModel Map(IWebhook webhook)
{
Guard.NotNull(webhook);
var model = new WebhookModel
{
Request = new WebhookRequestModel
{
Url = webhook.Request.Url,
Method = webhook.Request.Method,
Headers = webhook.Request.Headers?.ToDictionary(x => x.Key, x => x.Value.ToString()),
UseTransformer = webhook.Request.UseTransformer,
TransformerType = webhook.Request.UseTransformer == true ? webhook.Request.TransformerType.ToString() : null,
TransformerReplaceNodeOptions = webhook.Request.TransformerReplaceNodeOptions.ToString()
}
};
if (webhook.Request.BodyData != null)
{
switch (webhook.Request.BodyData.DetectedBodyType)
{
case BodyType.String:
model.Request.Body = webhook.Request.BodyData.BodyAsString;
break;
case BodyType.Json:
model.Request.BodyAsJson = webhook.Request.BodyData.BodyAsJson;
break;
default:
// Empty
break;
}
}
return model;
} }
} }
+28 -14
View File
@@ -292,7 +292,7 @@ public partial class WireMockServer
private IResponseMessage SettingsUpdate(IRequestMessage requestMessage) private IResponseMessage SettingsUpdate(IRequestMessage requestMessage)
{ {
var settings = DeserializeObject<SettingsModel>(requestMessage)!; var settings = DeserializeObject<SettingsModel>(requestMessage);
// _settings // _settings
_settings.AllowBodyForAllHttpMethods = settings.AllowBodyForAllHttpMethods; _settings.AllowBodyForAllHttpMethods = settings.AllowBodyForAllHttpMethods;
@@ -571,7 +571,7 @@ public partial class WireMockServer
{ {
var requestModel = DeserializeObject<RequestModel>(requestMessage); var requestModel = DeserializeObject<RequestModel>(requestMessage);
var request = (Request)InitRequestBuilder(requestModel, false); var request = (Request)InitRequestBuilder(requestModel, false)!;
var dict = new Dictionary<ILogEntry, RequestMatchResult>(); var dict = new Dictionary<ILogEntry, RequestMatchResult>();
foreach (var logEntry in LogEntries.Where(le => !le.RequestMessage.Path.StartsWith("/__admin/"))) foreach (var logEntry in LogEntries.Where(le => !le.RequestMessage.Path.StartsWith("/__admin/")))
@@ -625,6 +625,23 @@ public partial class WireMockServer
_settings.FileSystemHandler.WriteFile(folder, filenameUpdated, bytes); _settings.FileSystemHandler.WriteFile(folder, filenameUpdated, bytes);
} }
/// <summary>
/// Save the mappings as a Pact Json file V2.
/// </summary>
/// <param name="stream">The (file) stream.</param>
[PublicAPI]
public void SavePact(Stream stream)
{
var (_, bytes) = PactMapper.ToPact(this);
using var writer = new BinaryWriter(stream);
writer.Write(bytes);
if (stream.CanSeek)
{
stream.Seek(0, SeekOrigin.Begin);
}
}
/// <summary> /// <summary>
/// This stores details about the consumer of the interaction. /// This stores details about the consumer of the interaction.
/// </summary> /// </summary>
@@ -714,31 +731,28 @@ public partial class WireMockServer
}; };
} }
private static T? DeserializeObject<T>(IRequestMessage requestMessage) private static T DeserializeObject<T>(IRequestMessage requestMessage) where T : new()
{ {
if (requestMessage?.BodyData?.DetectedBodyType == BodyType.String) return requestMessage.BodyData?.DetectedBodyType switch
{ {
return JsonUtils.DeserializeObject<T>(requestMessage.BodyData.BodyAsString); BodyType.String => JsonUtils.DeserializeObject<T>(requestMessage.BodyData.BodyAsString),
}
if (requestMessage?.BodyData?.DetectedBodyType == BodyType.Json) BodyType.Json when requestMessage.BodyData?.BodyAsJson != null => ((JObject)requestMessage.BodyData.BodyAsJson).ToObject<T>()!,
{
return ((JObject)requestMessage.BodyData.BodyAsJson).ToObject<T>();
}
return default(T); _ => throw new NotSupportedException()
};
} }
private static T[] DeserializeRequestMessageToArray<T>(IRequestMessage requestMessage) private static T[] DeserializeRequestMessageToArray<T>(IRequestMessage requestMessage)
{ {
if (requestMessage.BodyData?.DetectedBodyType == BodyType.Json) if (requestMessage.BodyData?.DetectedBodyType == BodyType.Json && requestMessage.BodyData.BodyAsJson != null)
{ {
var bodyAsJson = requestMessage.BodyData.BodyAsJson; var bodyAsJson = requestMessage.BodyData.BodyAsJson;
return DeserializeObjectToArray<T>(bodyAsJson); return DeserializeObjectToArray<T>(bodyAsJson);
} }
return default(T[]); throw new NotSupportedException();
} }
private static T[] DeserializeJsonToArray<T>(string value) private static T[] DeserializeJsonToArray<T>(string value)
@@ -754,6 +768,6 @@ public partial class WireMockServer
} }
var singleResult = ((JObject)value).ToObject<T>(); var singleResult = ((JObject)value).ToObject<T>();
return new[] { singleResult }; return new[] { singleResult! };
} }
} }
@@ -275,7 +275,7 @@ public partial class WireMockServer
} }
else else
{ {
var headers = JsonUtils.ParseJTokenToObject<string[]>(entry.Value) ?? new string[0]; var headers = JsonUtils.ParseJTokenToObject<string[]>(entry.Value);
responseBuilder.WithHeader(entry.Key, headers); responseBuilder.WithHeader(entry.Key, headers);
} }
} }
@@ -2,91 +2,105 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
namespace WireMock.Settings namespace WireMock.Settings;
// Based on http://blog.gauffin.org/2014/12/simple-command-line-parser/
internal class SimpleCommandLineParser
{ {
// Based on http://blog.gauffin.org/2014/12/simple-command-line-parser/ private const string Sigil = "--";
internal class SimpleCommandLineParser
private IDictionary<string, string[]> Arguments { get; } = new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase);
public void Parse(string[] arguments)
{ {
private const string Sigil = "--"; string currentName = string.Empty;
private IDictionary<string, string[]> Arguments { get; } = new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase); var values = new List<string>();
public void Parse(string[] arguments) // Split a single argument on a space character to fix issue (e.g. Azure Service Fabric) when an argument is supplied like "--x abc" or '--x abc'
foreach (string arg in arguments.SelectMany(arg => arg.Split(' ')))
{ {
string currentName = null; if (arg.StartsWith(Sigil))
var values = new List<string>();
// Split a single argument on a space character to fix issue (e.g. Azure Service Fabric) when an argument is supplied like "--x abc" or '--x abc'
foreach (string arg in arguments.SelectMany(arg => arg.Split(' ')))
{ {
if (arg.StartsWith(Sigil)) if (!string.IsNullOrEmpty(currentName))
{ {
if (!string.IsNullOrEmpty(currentName)) Arguments[currentName] = values.ToArray();
{ }
Arguments[currentName] = values.ToArray();
}
values.Clear(); values.Clear();
currentName = arg.Substring(Sigil.Length); currentName = arg.Substring(Sigil.Length);
}
else if (string.IsNullOrEmpty(currentName))
{
Arguments[arg] = new string[0];
}
else
{
values.Add(arg);
}
} }
else if (string.IsNullOrEmpty(currentName))
if (!string.IsNullOrEmpty(currentName))
{ {
Arguments[currentName] = values.ToArray(); Arguments[arg] = new string[0];
}
else
{
values.Add(arg);
} }
} }
public bool Contains(string name) if (!string.IsNullOrEmpty(currentName))
{ {
return Arguments.ContainsKey(name); Arguments[currentName] = values.ToArray();
}
public string[] GetValues(string name, string[] defaultValue = null)
{
return Contains(name) ? Arguments[name] : defaultValue;
}
public T GetValue<T>(string name, Func<string[], T> func, T defaultValue = default(T))
{
return Contains(name) ? func(Arguments[name]) : defaultValue;
}
public bool GetBoolValue(string name, bool defaultValue = false)
{
return GetValue(name, values =>
{
string value = values.FirstOrDefault();
return !string.IsNullOrEmpty(value) ? bool.Parse(value) : defaultValue;
}, defaultValue);
}
public bool GetBoolSwitchValue(string name)
{
return Contains(name);
}
public int? GetIntValue(string name, int? defaultValue = null)
{
return GetValue(name, values =>
{
string value = values.FirstOrDefault();
return !string.IsNullOrEmpty(value) ? int.Parse(value) : defaultValue;
}, defaultValue);
}
public string GetStringValue(string name, string defaultValue = null)
{
return GetValue(name, values => values.FirstOrDefault() ?? defaultValue, defaultValue);
} }
} }
public bool Contains(string name)
{
return Arguments.ContainsKey(name);
}
public string[] GetValues(string name, string[] defaultValue)
{
return Contains(name) ? Arguments[name] : defaultValue;
}
public string[]? GetValues(string name)
{
return Contains(name) ? Arguments[name] : default;
}
public T GetValue<T>(string name, Func<string[], T> func, T defaultValue)
{
return Contains(name) ? func(Arguments[name]) : defaultValue;
}
public T? GetValue<T>(string name, Func<string[], T> func)
{
return Contains(name) ? func(Arguments[name]) : default;
}
public bool GetBoolValue(string name, bool defaultValue = false)
{
return GetValue(name, values =>
{
var value = values.FirstOrDefault();
return !string.IsNullOrEmpty(value) ? bool.Parse(value) : defaultValue;
}, defaultValue);
}
public bool GetBoolSwitchValue(string name)
{
return Contains(name);
}
public int? GetIntValue(string name, int? defaultValue = null)
{
return GetValue(name, values =>
{
var value = values.FirstOrDefault();
return !string.IsNullOrEmpty(value) ? int.Parse(value) : defaultValue;
}, defaultValue);
}
public string GetStringValue(string name, string defaultValue)
{
return GetValue(name, values => values.FirstOrDefault() ?? defaultValue, defaultValue);
}
public string? GetStringValue(string name)
{
return GetValue(name, values => values.FirstOrDefault());
}
} }
+6 -7
View File
@@ -1,9 +1,8 @@
namespace WireMock.Settings namespace WireMock.Settings;
/// <summary>
/// WebhookSettings
/// </summary>
public class WebhookSettings : HttpClientSettings
{ {
/// <summary>
/// WebhookSettings
/// </summary>
public class WebhookSettings : HttpClientSettings
{
}
} }
@@ -251,5 +251,11 @@ namespace WireMock.Settings
/// </summary> /// </summary>
[PublicAPI, JsonIgnore] [PublicAPI, JsonIgnore]
public IDictionary<string, Func<MatcherModel, IMatcher>>? CustomMatcherMappings { get; set; } public IDictionary<string, Func<MatcherModel, IMatcher>>? CustomMatcherMappings { get; set; }
/// <summary>
/// The <see cref="JsonSerializerSettings"/> used when the a JSON response is generated.
/// </summary>
[PublicAPI, JsonIgnore]
public JsonSerializerSettings? JsonSerializerSettings { get; set; }
} }
} }
@@ -1,129 +1,129 @@
using System; using System;
using System.Diagnostics.CodeAnalysis;
using JetBrains.Annotations; using JetBrains.Annotations;
using Stef.Validation; using Stef.Validation;
using WireMock.Logging; using WireMock.Logging;
using WireMock.Types; using WireMock.Types;
namespace WireMock.Settings namespace WireMock.Settings;
/// <summary>
/// A static helper class to parse commandline arguments into WireMockServerSettings.
/// </summary>
public static class WireMockServerSettingsParser
{ {
/// <summary> /// <summary>
/// A static helper class to parse commandline arguments into WireMockServerSettings. /// Parse commandline arguments into WireMockServerSettings.
/// </summary> /// </summary>
public static class WireMockServerSettingsParser /// <param name="args">The commandline arguments</param>
/// <param name="logger">The logger (optional, can be null)</param>
/// <param name="settings">The parsed settings</param>
[PublicAPI]
public static bool TryParseArguments(string[] args, [NotNullWhen(true)] out WireMockServerSettings? settings, IWireMockLogger? logger = null)
{ {
/// <summary> Guard.HasNoNulls(args, nameof(args));
/// Parse commandline arguments into WireMockServerSettings.
/// </summary> var parser = new SimpleCommandLineParser();
/// <param name="args">The commandline arguments</param> parser.Parse(args);
/// <param name="logger">The logger (optional, can be null)</param>
/// <param name="settings">The parsed settings</param> if (parser.GetBoolSwitchValue("help"))
[PublicAPI]
public static bool TryParseArguments([NotNull] string[] args, out WireMockServerSettings settings, [CanBeNull] IWireMockLogger logger = null)
{ {
Guard.HasNoNulls(args, nameof(args)); (logger ?? new WireMockConsoleLogger()).Info("See https://github.com/WireMock-Net/WireMock.Net/wiki/WireMock-commandline-parameters for details on all commandline options.");
settings = null;
return false;
}
var parser = new SimpleCommandLineParser(); settings = new WireMockServerSettings
parser.Parse(args); {
AdminAzureADAudience = parser.GetStringValue(nameof(WireMockServerSettings.AdminAzureADAudience)),
if (parser.GetBoolSwitchValue("help")) AdminAzureADTenant = parser.GetStringValue(nameof(WireMockServerSettings.AdminAzureADTenant)),
{ AdminPassword = parser.GetStringValue("AdminPassword"),
(logger ?? new WireMockConsoleLogger()).Info("See https://github.com/WireMock-Net/WireMock.Net/wiki/WireMock-commandline-parameters for details on all commandline options."); AdminUsername = parser.GetStringValue("AdminUsername"),
settings = null; AllowBodyForAllHttpMethods = parser.GetBoolValue("AllowBodyForAllHttpMethods"),
return false; AllowCSharpCodeMatcher = parser.GetBoolValue("AllowCSharpCodeMatcher"),
} AllowOnlyDefinedHttpStatusCodeInResponse = parser.GetBoolValue("AllowOnlyDefinedHttpStatusCodeInResponse"),
AllowPartialMapping = parser.GetBoolValue("AllowPartialMapping"),
settings = new WireMockServerSettings DisableJsonBodyParsing = parser.GetBoolValue("DisableJsonBodyParsing"),
{ HandleRequestsSynchronously = parser.GetBoolValue("HandleRequestsSynchronously"),
AdminAzureADAudience = parser.GetStringValue(nameof(WireMockServerSettings.AdminAzureADAudience)), MaxRequestLogCount = parser.GetIntValue("MaxRequestLogCount"),
AdminAzureADTenant = parser.GetStringValue(nameof(WireMockServerSettings.AdminAzureADTenant)), ReadStaticMappings = parser.GetBoolValue("ReadStaticMappings"),
AdminPassword = parser.GetStringValue("AdminPassword"), RequestLogExpirationDuration = parser.GetIntValue("RequestLogExpirationDuration"),
AdminUsername = parser.GetStringValue("AdminUsername"), SaveUnmatchedRequests = parser.GetBoolValue(nameof(WireMockServerSettings.SaveUnmatchedRequests)),
AllowBodyForAllHttpMethods = parser.GetBoolValue("AllowBodyForAllHttpMethods"), StartAdminInterface = parser.GetBoolValue("StartAdminInterface", true),
AllowCSharpCodeMatcher = parser.GetBoolValue("AllowCSharpCodeMatcher"), ThrowExceptionWhenMatcherFails = parser.GetBoolValue("ThrowExceptionWhenMatcherFails"),
AllowOnlyDefinedHttpStatusCodeInResponse = parser.GetBoolValue("AllowOnlyDefinedHttpStatusCodeInResponse"), UseRegexExtended = parser.GetBoolValue(nameof(WireMockServerSettings.UseRegexExtended), true),
AllowPartialMapping = parser.GetBoolValue("AllowPartialMapping"), WatchStaticMappings = parser.GetBoolValue("WatchStaticMappings"),
DisableJsonBodyParsing = parser.GetBoolValue("DisableJsonBodyParsing"), WatchStaticMappingsInSubdirectories = parser.GetBoolValue("WatchStaticMappingsInSubdirectories"),
HandleRequestsSynchronously = parser.GetBoolValue("HandleRequestsSynchronously"), };
MaxRequestLogCount = parser.GetIntValue("MaxRequestLogCount"),
ReadStaticMappings = parser.GetBoolValue("ReadStaticMappings"),
RequestLogExpirationDuration = parser.GetIntValue("RequestLogExpirationDuration"),
SaveUnmatchedRequests = parser.GetBoolValue(nameof(WireMockServerSettings.SaveUnmatchedRequests)),
StartAdminInterface = parser.GetBoolValue("StartAdminInterface", true),
ThrowExceptionWhenMatcherFails = parser.GetBoolValue("ThrowExceptionWhenMatcherFails"),
UseRegexExtended = parser.GetBoolValue(nameof(WireMockServerSettings.UseRegexExtended), true),
WatchStaticMappings = parser.GetBoolValue("WatchStaticMappings"),
WatchStaticMappingsInSubdirectories = parser.GetBoolValue("WatchStaticMappingsInSubdirectories"),
};
#if USE_ASPNETCORE #if USE_ASPNETCORE
settings.CorsPolicyOptions = parser.GetValue(nameof(WireMockServerSettings.CorsPolicyOptions), values => settings.CorsPolicyOptions = parser.GetValue(nameof(WireMockServerSettings.CorsPolicyOptions), values =>
{ {
var value = string.Join(string.Empty, values); var value = string.Join(string.Empty, values);
return Enum.TryParse<CorsPolicyOptions>(value, true, out var corsPolicyOptions) ? corsPolicyOptions : CorsPolicyOptions.None; return Enum.TryParse<CorsPolicyOptions>(value, true, out var corsPolicyOptions) ? corsPolicyOptions : CorsPolicyOptions.None;
}); });
#endif #endif
if (logger != null) if (logger != null)
{ {
settings.Logger = logger; settings.Logger = logger;
}
if (parser.GetStringValue("WireMockLogger") == "WireMockConsoleLogger")
{
settings.Logger = new WireMockConsoleLogger();
}
if (parser.Contains(nameof(WireMockServerSettings.Port)))
{
settings.Port = parser.GetIntValue(nameof(WireMockServerSettings.Port));
}
else
{
settings.Urls = parser.GetValues("Urls", new[] { "http://*:9091/" });
}
string proxyUrl = parser.GetStringValue("ProxyURL") ?? parser.GetStringValue("ProxyUrl");
if (!string.IsNullOrEmpty(proxyUrl))
{
settings.ProxyAndRecordSettings = new ProxyAndRecordSettings
{
AllowAutoRedirect = parser.GetBoolValue("AllowAutoRedirect"),
ClientX509Certificate2ThumbprintOrSubjectName = parser.GetStringValue("ClientX509Certificate2ThumbprintOrSubjectName"),
ExcludedCookies = parser.GetValues("ExcludedCookies"),
ExcludedHeaders = parser.GetValues("ExcludedHeaders"),
// PreferProxyMapping = parser.GetBoolValue(nameof(ProxyAndRecordSettings.PreferProxyMapping)),
SaveMapping = parser.GetBoolValue("SaveMapping"),
SaveMappingForStatusCodePattern = parser.GetStringValue("SaveMappingForStatusCodePattern"),
SaveMappingToFile = parser.GetBoolValue("SaveMappingToFile"),
Url = proxyUrl
};
string proxyAddress = parser.GetStringValue("WebProxyAddress");
if (!string.IsNullOrEmpty(proxyAddress))
{
settings.ProxyAndRecordSettings.WebProxySettings = new WebProxySettings
{
Address = proxyAddress,
UserName = parser.GetStringValue("WebProxyUserName"),
Password = parser.GetStringValue("WebProxyPassword")
};
}
}
var certificateSettings = new WireMockCertificateSettings
{
X509StoreName = parser.GetStringValue("X509StoreName"),
X509StoreLocation = parser.GetStringValue("X509StoreLocation"),
X509StoreThumbprintOrSubjectName = parser.GetStringValue("X509StoreThumbprintOrSubjectName"),
X509CertificateFilePath = parser.GetStringValue("X509CertificateFilePath"),
X509CertificatePassword = parser.GetStringValue("X509CertificatePassword")
};
if (certificateSettings.IsDefined)
{
settings.CertificateSettings = certificateSettings;
}
return true;
} }
if (parser.GetStringValue("WireMockLogger") == "WireMockConsoleLogger")
{
settings.Logger = new WireMockConsoleLogger();
}
if (parser.Contains(nameof(WireMockServerSettings.Port)))
{
settings.Port = parser.GetIntValue(nameof(WireMockServerSettings.Port));
}
else
{
settings.Urls = parser.GetValues("Urls", new[] { "http://*:9091/" });
}
var proxyUrl = parser.GetStringValue("ProxyURL") ?? parser.GetStringValue("ProxyUrl");
if (!string.IsNullOrEmpty(proxyUrl))
{
settings.ProxyAndRecordSettings = new ProxyAndRecordSettings
{
AllowAutoRedirect = parser.GetBoolValue("AllowAutoRedirect"),
ClientX509Certificate2ThumbprintOrSubjectName = parser.GetStringValue("ClientX509Certificate2ThumbprintOrSubjectName"),
ExcludedCookies = parser.GetValues("ExcludedCookies"),
ExcludedHeaders = parser.GetValues("ExcludedHeaders"),
// PreferProxyMapping = parser.GetBoolValue(nameof(ProxyAndRecordSettings.PreferProxyMapping)),
SaveMapping = parser.GetBoolValue("SaveMapping"),
SaveMappingForStatusCodePattern = parser.GetStringValue("SaveMappingForStatusCodePattern", "*"),
SaveMappingToFile = parser.GetBoolValue("SaveMappingToFile"),
Url = proxyUrl
};
string? proxyAddress = parser.GetStringValue("WebProxyAddress");
if (!string.IsNullOrEmpty(proxyAddress))
{
settings.ProxyAndRecordSettings.WebProxySettings = new WebProxySettings
{
Address = proxyAddress,
UserName = parser.GetStringValue("WebProxyUserName"),
Password = parser.GetStringValue("WebProxyPassword")
};
}
}
var certificateSettings = new WireMockCertificateSettings
{
X509StoreName = parser.GetStringValue("X509StoreName"),
X509StoreLocation = parser.GetStringValue("X509StoreLocation"),
X509StoreThumbprintOrSubjectName = parser.GetStringValue("X509StoreThumbprintOrSubjectName"),
X509CertificateFilePath = parser.GetStringValue("X509CertificateFilePath"),
X509CertificatePassword = parser.GetStringValue("X509CertificatePassword")
};
if (certificateSettings.IsDefined)
{
settings.CertificateSettings = certificateSettings;
}
return true;
} }
} }
@@ -17,7 +17,7 @@ namespace WireMock.Util
private const int DefaultWatchInterval = 100; private const int DefaultWatchInterval = 100;
// This Dictionary keeps the track of when an event occurred last for a particular file // This Dictionary keeps the track of when an event occurred last for a particular file
private ConcurrentDictionary<string, DateTime> _lastFileEvent; private ConcurrentDictionary<string, DateTime> _lastFileEvent = new();
// Watch Interval in Milliseconds // Watch Interval in Milliseconds
private int _interval; private int _interval;
@@ -58,7 +58,7 @@ namespace WireMock.Util
/// <param name="interval">The interval.</param> /// <param name="interval">The interval.</param>
public EnhancedFileSystemWatcher(int interval = DefaultWatchInterval) public EnhancedFileSystemWatcher(int interval = DefaultWatchInterval)
{ {
Guard.Condition(interval, i => i >= 0, nameof(interval)); Guard.Condition(interval, i => i >= 0);
InitializeMembers(interval); InitializeMembers(interval);
} }
@@ -68,10 +68,10 @@ namespace WireMock.Util
/// </summary> /// </summary>
/// <param name="path">The directory to monitor, in standard or Universal Naming Convention (UNC) notation.</param> /// <param name="path">The directory to monitor, in standard or Universal Naming Convention (UNC) notation.</param>
/// <param name="interval">The interval.</param> /// <param name="interval">The interval.</param>
public EnhancedFileSystemWatcher([NotNull] string path, int interval = DefaultWatchInterval) : base(path) public EnhancedFileSystemWatcher(string path, int interval = DefaultWatchInterval) : base(path)
{ {
Guard.NotNullOrEmpty(path, nameof(path)); Guard.NotNullOrEmpty(path);
Guard.Condition(interval, i => i >= 0, nameof(interval)); Guard.Condition(interval, i => i >= 0);
InitializeMembers(interval); InitializeMembers(interval);
} }
@@ -82,11 +82,11 @@ namespace WireMock.Util
/// <param name="path">The directory to monitor, in standard or Universal Naming Convention (UNC) notation.</param> /// <param name="path">The directory to monitor, in standard or Universal Naming Convention (UNC) notation.</param>
/// <param name="filter">The type of files to watch. For example, "*.txt" watches for changes to all text files.</param> /// <param name="filter">The type of files to watch. For example, "*.txt" watches for changes to all text files.</param>
/// <param name="interval">The interval.</param> /// <param name="interval">The interval.</param>
public EnhancedFileSystemWatcher([NotNull] string path, [NotNull] string filter, int interval = DefaultWatchInterval) : base(path, filter) public EnhancedFileSystemWatcher(string path, string filter, int interval = DefaultWatchInterval) : base(path, filter)
{ {
Guard.NotNullOrEmpty(path, nameof(path)); Guard.NotNullOrEmpty(path);
Guard.NotNullOrEmpty(filter, nameof(filter)); Guard.NotNullOrEmpty(filter);
Guard.Condition(interval, i => i >= 0, nameof(interval)); Guard.Condition(interval, i => i >= 0);
InitializeMembers(interval); InitializeMembers(interval);
} }
@@ -100,22 +100,22 @@ namespace WireMock.Util
/// <summary> /// <summary>
/// Occurs when a file or directory in the specified <see cref="P:System.IO.FileSystemWatcher.Path" /> is changed. /// Occurs when a file or directory in the specified <see cref="P:System.IO.FileSystemWatcher.Path" /> is changed.
/// </summary> /// </summary>
public new event FileSystemEventHandler Changed; public new event FileSystemEventHandler? Changed;
/// <summary> /// <summary>
/// Occurs when a file or directory in the specified <see cref="P:System.IO.FileSystemWatcher.Path" /> is created. /// Occurs when a file or directory in the specified <see cref="P:System.IO.FileSystemWatcher.Path" /> is created.
/// </summary> /// </summary>
public new event FileSystemEventHandler Created; public new event FileSystemEventHandler? Created;
/// <summary> /// <summary>
/// Occurs when a file or directory in the specified <see cref="P:System.IO.FileSystemWatcher.Path" /> is deleted. /// Occurs when a file or directory in the specified <see cref="P:System.IO.FileSystemWatcher.Path" /> is deleted.
/// </summary> /// </summary>
public new event FileSystemEventHandler Deleted; public new event FileSystemEventHandler? Deleted;
/// <summary> /// <summary>
/// Occurs when a file or directory in the specified <see cref="P:System.IO.FileSystemWatcher.Path" /> is renamed. /// Occurs when a file or directory in the specified <see cref="P:System.IO.FileSystemWatcher.Path" /> is renamed.
/// </summary> /// </summary>
public new event RenamedEventHandler Renamed; public new event RenamedEventHandler? Renamed;
#endregion #endregion
#region Protected Methods to raise the Events for this class #region Protected Methods to raise the Events for this class
@@ -16,7 +16,7 @@ internal static class HttpStatusRangeParser
/// <param name="pattern">The pattern. (Can be null, in that case it's allowed.)</param> /// <param name="pattern">The pattern. (Can be null, in that case it's allowed.)</param>
/// <param name="httpStatusCode">The value.</param> /// <param name="httpStatusCode">The value.</param>
/// <exception cref="ArgumentException"><paramref name="pattern"/> is invalid.</exception> /// <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) switch (httpStatusCode)
{ {
+27 -9
View File
@@ -87,9 +87,9 @@ internal static class JsonUtils
/// </summary> /// </summary>
/// <param name="json">A System.String that contains JSON.</param> /// <param name="json">A System.String that contains JSON.</param>
/// <returns>A Newtonsoft.Json.Linq.JToken populated from the string that contains JSON.</returns> /// <returns>A Newtonsoft.Json.Linq.JToken populated from the string that contains JSON.</returns>
public static JToken? Parse(string json) public static JToken Parse(string json)
{ {
return JsonConvert.DeserializeObject<JToken>(json, JsonSerializationConstants.JsonDeserializerSettingsWithDateParsingNone); return JsonConvert.DeserializeObject<JToken>(json, JsonSerializationConstants.JsonDeserializerSettingsWithDateParsingNone)!;
} }
/// <summary> /// <summary>
@@ -98,9 +98,9 @@ internal static class JsonUtils
/// </summary> /// </summary>
/// <param name="json">A System.String that contains JSON.</param> /// <param name="json">A System.String that contains JSON.</param>
/// <returns>The deserialized object from the JSON string.</returns> /// <returns>The deserialized object from the JSON string.</returns>
public static object? DeserializeObject(string json) public static object DeserializeObject(string json)
{ {
return JsonConvert.DeserializeObject(json, JsonSerializationConstants.JsonDeserializerSettingsWithDateParsingNone); return JsonConvert.DeserializeObject(json, JsonSerializationConstants.JsonDeserializerSettingsWithDateParsingNone)!;
} }
/// <summary> /// <summary>
@@ -109,17 +109,35 @@ internal static class JsonUtils
/// </summary> /// </summary>
/// <param name="json">A System.String that contains JSON.</param> /// <param name="json">A System.String that contains JSON.</param>
/// <returns>The deserialized object from the JSON string.</returns> /// <returns>The deserialized object from the JSON string.</returns>
public static T? DeserializeObject<T>(string json) public static T DeserializeObject<T>(string json)
{ {
return JsonConvert.DeserializeObject<T>(json, JsonSerializationConstants.JsonDeserializerSettingsWithDateParsingNone); return JsonConvert.DeserializeObject<T>(json, JsonSerializationConstants.JsonDeserializerSettingsWithDateParsingNone)!;
} }
public static T? ParseJTokenToObject<T>(object value) public static T? TryDeserializeObject<T>(string json)
{ {
try
{
return JsonConvert.DeserializeObject<T>(json);
}
catch
{
return default;
}
}
public static T ParseJTokenToObject<T>(object? value)
{
if (value != null && value.GetType() == typeof(T))
{
return (T)value;
}
return value switch return value switch
{ {
JToken tokenValue => tokenValue.ToObject<T>(), JToken tokenValue => tokenValue.ToObject<T>()!,
_ => default
_ => throw new NotSupportedException($"Unable to convert value to {typeof(T)}.")
}; };
} }
+32 -29
View File
@@ -1,36 +1,39 @@
using System.IO; using System.IO;
using Stef.Validation;
namespace WireMock.Util namespace WireMock.Util;
internal static class PathUtils
{ {
internal static class PathUtils /// <summary>
/// Robust handling of the user defined path.
/// Also supports Unix and Windows platforms
/// </summary>
/// <param name="path">The path to clean</param>
public static string? CleanPath(string? path)
{ {
/// <summary> return path?.Replace('/', Path.DirectorySeparatorChar).Replace('\\', Path.DirectorySeparatorChar);
/// Robust handling of the user defined path. }
/// Also supports Unix and Windows platforms
/// </summary>
/// <param name="path">The path to clean</param>
public static string CleanPath(string path)
{
return path?.Replace('/', Path.DirectorySeparatorChar).Replace('\\', Path.DirectorySeparatorChar);
}
/// <summary> /// <summary>
/// Removes leading directory separator chars from the filepath, which could break Path.Combine /// Removes leading directory separator chars from the filepath, which could break Path.Combine
/// </summary> /// </summary>
/// <param name="path">The path to remove the loading DirectorySeparatorChars</param> /// <param name="path">The path to remove the loading DirectorySeparatorChars</param>
public static string RemoveLeadingDirectorySeparators(string path) public static string? RemoveLeadingDirectorySeparators(string? path)
{ {
return path?.TrimStart(new[] { Path.DirectorySeparatorChar }); return path?.TrimStart(new[] { Path.DirectorySeparatorChar });
} }
/// <summary> /// <summary>
/// Combine two paths /// Combine two paths
/// </summary> /// </summary>
/// <param name="root">The root path</param> /// <param name="root">The root path</param>
/// <param name="path">The path</param> /// <param name="path">The path</param>
public static string Combine(string root, string path) public static string Combine(string root, string? path)
{ {
return Path.Combine(root, RemoveLeadingDirectorySeparators(path)); Guard.NotNull(root);
}
var result = RemoveLeadingDirectorySeparators(path);
return result == null ? root : Path.Combine(root, result);
} }
} }
+1 -1
View File
@@ -4,5 +4,5 @@ namespace WireMock.Util;
internal static class SystemUtils internal static class SystemUtils
{ {
public static readonly string Version = typeof(SystemUtils).GetTypeInfo().Assembly.GetName().Version.ToString(); public static readonly string Version = typeof(SystemUtils).GetTypeInfo().Assembly.GetName().Version!.ToString();
} }
+5
View File
@@ -140,6 +140,11 @@
<PackageReference Include="Scriban.Signed" Version="5.5.0" /> <PackageReference Include="Scriban.Signed" Version="5.5.0" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netcoreapp3.1' ">
<!--<PackageReference Include="System.Diagnostics.CodeAnalysis" Version="4.3.0" />-->
<PackageReference Include="Nullable" Version="1.3.0" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Update="Server\WireMockServer.*.cs"> <Compile Update="Server\WireMockServer.*.cs">
<DependentUpon>WireMockServer.cs</DependentUpon> <DependentUpon>WireMockServer.cs</DependentUpon>
+61 -1
View File
@@ -1,5 +1,9 @@
using System.IO; using System.IO;
using System.Net; using System.Net;
using System.Text;
using FluentAssertions;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using WireMock.Matchers; using WireMock.Matchers;
using WireMock.RequestBuilders; using WireMock.RequestBuilders;
using WireMock.ResponseBuilders; using WireMock.ResponseBuilders;
@@ -11,7 +15,7 @@ namespace WireMock.Net.Tests.Pact;
public class PactTests public class PactTests
{ {
[Fact] [Fact]
public void SavePact_Get_Request() public void SavePact_Get_Request_And_Response_WithBodyAsJson()
{ {
var server = WireMockServer.Start(); var server = WireMockServer.Start();
server server
@@ -41,6 +45,62 @@ public class PactTests
server.SavePact(Path.Combine("../../../", "Pact", "files"), "pact-get.json"); server.SavePact(Path.Combine("../../../", "Pact", "files"), "pact-get.json");
} }
[Fact]
public void SavePact_Get_Request_And_Response_WithBody_StringIsJson()
{
// Act
var server = WireMockServer.Start();
server
.Given(Request.Create()
.UsingGet()
.WithPath("/tester")
)
.RespondWith(
Response.Create()
.WithStatusCode(HttpStatusCode.OK)
.WithBody(@"{ foo: ""bar"" }")
);
var memoryStream = new MemoryStream();
server.SavePact(memoryStream);
var json = Encoding.UTF8.GetString(memoryStream.ToArray());
var pact = JsonConvert.DeserializeObject<WireMock.Pact.Models.V2.Pact>(json)!;
// Assert
pact.Interactions.Should().HaveCount(1);
var expectedBody = new JObject { { "foo", "bar" } };
pact.Interactions[0].Response.Body.Should().BeEquivalentTo(expectedBody);
}
[Fact]
public void SavePact_Get_Request_And_Response_WithBody_StringIsString()
{
// Act
var server = WireMockServer.Start();
server
.Given(Request.Create()
.UsingGet()
.WithPath("/tester")
)
.RespondWith(
Response.Create()
.WithStatusCode(HttpStatusCode.OK)
.WithBody("test")
);
var memoryStream = new MemoryStream();
server.SavePact(memoryStream);
var json = Encoding.UTF8.GetString(memoryStream.ToArray());
var pact = JsonConvert.DeserializeObject<WireMock.Pact.Models.V2.Pact>(json)!;
// Assert
pact.Interactions.Should().HaveCount(1);
pact.Interactions[0].Response.Body.Should().Be("test");
}
[Fact] [Fact]
public void SavePact_Multiple_Requests() public void SavePact_Multiple_Requests()
{ {
@@ -2,102 +2,101 @@ using NFluent;
using WireMock.Settings; using WireMock.Settings;
using Xunit; using Xunit;
namespace WireMock.Net.Tests.Settings namespace WireMock.Net.Tests.Settings;
public class SimpleCommandLineParserTests
{ {
public class SimpleCommandLineParserTests private readonly SimpleCommandLineParser _parser;
public SimpleCommandLineParserTests()
{ {
private readonly SimpleCommandLineParser _parser; _parser = new SimpleCommandLineParser();
}
public SimpleCommandLineParserTests() [Fact]
{ public void SimpleCommandLineParser_Parse_Arguments()
_parser = new SimpleCommandLineParser(); {
} // Assign
_parser.Parse(new[] { "--test1", "one", "--test2", "two", "--test3", "three" });
[Fact] // Act
public void SimpleCommandLineParser_Parse_Arguments() string? value1 = _parser.GetStringValue("test1");
{ string? value2 = _parser.GetStringValue("test2");
// Assign string? value3 = _parser.GetStringValue("test3");
_parser.Parse(new[] { "--test1", "one", "--test2", "two", "--test3", "three" });
// Act // Assert
string value1 = _parser.GetStringValue("test1"); Check.That(value1).IsEqualTo("one");
string value2 = _parser.GetStringValue("test2"); Check.That(value2).IsEqualTo("two");
string value3 = _parser.GetStringValue("test3"); Check.That(value3).IsEqualTo("three");
}
// Assert [Fact]
Check.That(value1).IsEqualTo("one"); public void SimpleCommandLineParser_Parse_ArgumentsAsCombinedKeyAndValue()
Check.That(value2).IsEqualTo("two"); {
Check.That(value3).IsEqualTo("three"); // Assign
} _parser.Parse(new[] { "--test1 one", "--test2 two", "--test3 three" });
[Fact] // Act
public void SimpleCommandLineParser_Parse_ArgumentsAsCombinedKeyAndValue() string? value1 = _parser.GetStringValue("test1");
{ string? value2 = _parser.GetStringValue("test2");
// Assign string? value3 = _parser.GetStringValue("test3");
_parser.Parse(new[] { "--test1 one", "--test2 two", "--test3 three" });
// Act // Assert
string value1 = _parser.GetStringValue("test1"); Check.That(value1).IsEqualTo("one");
string value2 = _parser.GetStringValue("test2"); Check.That(value2).IsEqualTo("two");
string value3 = _parser.GetStringValue("test3"); Check.That(value3).IsEqualTo("three");
}
// Assert [Fact]
Check.That(value1).IsEqualTo("one"); public void SimpleCommandLineParser_Parse_ArgumentsMixed()
Check.That(value2).IsEqualTo("two"); {
Check.That(value3).IsEqualTo("three"); // Assign
} _parser.Parse(new[] { "--test1 one", "--test2", "two", "--test3 three" });
[Fact] // Act
public void SimpleCommandLineParser_Parse_ArgumentsMixed() string? value1 = _parser.GetStringValue("test1");
{ string? value2 = _parser.GetStringValue("test2");
// Assign string? value3 = _parser.GetStringValue("test3");
_parser.Parse(new[] { "--test1 one", "--test2", "two", "--test3 three" });
// Act // Assert
string value1 = _parser.GetStringValue("test1"); Check.That(value1).IsEqualTo("one");
string value2 = _parser.GetStringValue("test2"); Check.That(value2).IsEqualTo("two");
string value3 = _parser.GetStringValue("test3"); Check.That(value3).IsEqualTo("three");
}
// Assert [Fact]
Check.That(value1).IsEqualTo("one"); public void SimpleCommandLineParser_Parse_GetBoolValue()
Check.That(value2).IsEqualTo("two"); {
Check.That(value3).IsEqualTo("three"); // Assign
} _parser.Parse(new[] { "'--test1", "false'", "--test2 true" });
[Fact] // Act
public void SimpleCommandLineParser_Parse_GetBoolValue() bool value1 = _parser.GetBoolValue("test1");
{ bool value2 = _parser.GetBoolValue("test2");
// Assign bool value3 = _parser.GetBoolValue("test3", true);
_parser.Parse(new[] { "'--test1", "false'", "--test2 true" });
// Act // Assert
bool value1 = _parser.GetBoolValue("test1"); Check.That(value1).IsEqualTo(false);
bool value2 = _parser.GetBoolValue("test2"); Check.That(value2).IsEqualTo(true);
bool value3 = _parser.GetBoolValue("test3", true); Check.That(value3).IsEqualTo(true);
}
// Assert [Fact]
Check.That(value1).IsEqualTo(false); public void SimpleCommandLineParser_Parse_GetIntValue()
Check.That(value2).IsEqualTo(true); {
Check.That(value3).IsEqualTo(true); // Assign
} _parser.Parse(new[] { "--test1", "42", "--test2 55" });
[Fact] // Act
public void SimpleCommandLineParser_Parse_GetIntValue() int? value1 = _parser.GetIntValue("test1");
{ int? value2 = _parser.GetIntValue("test2");
// Assign int? value3 = _parser.GetIntValue("test3", 100);
_parser.Parse(new[] { "--test1", "42", "--test2 55" }); int? value4 = _parser.GetIntValue("test4");
// Act // Assert
int? value1 = _parser.GetIntValue("test1"); Check.That(value1).IsEqualTo(42);
int? value2 = _parser.GetIntValue("test2"); Check.That(value2).IsEqualTo(55);
int? value3 = _parser.GetIntValue("test3", 100); Check.That(value3).IsEqualTo(100);
int? value4 = _parser.GetIntValue("test4"); Check.That(value4).IsNull();
// Assert
Check.That(value1).IsEqualTo(42);
Check.That(value2).IsEqualTo(55);
Check.That(value3).IsEqualTo(100);
Check.That(value4).IsNull();
}
} }
} }
+28 -2
View File
@@ -13,7 +13,7 @@ namespace WireMock.Net.Tests.Util;
public class JsonUtilsTests public class JsonUtilsTests
{ {
[Fact] [Fact]
public void JsonUtils_ParseJTokenToObject() public void JsonUtils_ParseJTokenToObject_For_String()
{ {
// Assign // Assign
object value = "test"; object value = "test";
@@ -22,7 +22,33 @@ public class JsonUtilsTests
string result = JsonUtils.ParseJTokenToObject<string>(value); string result = JsonUtils.ParseJTokenToObject<string>(value);
// Assert // Assert
Check.That(result).IsEqualTo(default(string)); result.Should().Be("test");
}
[Fact]
public void JsonUtils_ParseJTokenToObject_For_Int()
{
// Assign
object value = 123;
// Act
var result = JsonUtils.ParseJTokenToObject<int>(value);
// Assert
result.Should().Be(123);
}
[Fact]
public void JsonUtils_ParseJTokenToObject_For_Invalid_Throws()
{
// Assign
object value = "{ }";
// Act
Action action = () => JsonUtils.ParseJTokenToObject<int>(value);
// Assert
action.Should().Throw<NotSupportedException>();
} }
[Fact] [Fact]
+31 -32
View File
@@ -1,44 +1,43 @@
using NFluent; using NFluent;
using System.IO; using System.IO;
using WireMock.Util; using WireMock.Util;
using Xunit; using Xunit;
namespace WireMock.Net.Tests.Util namespace WireMock.Net.Tests.Util;
public class PathUtilsTests
{ {
public class PathUtilsTests [Theory]
[InlineData(@"subdirectory/MyXmlResponse.xml")]
[InlineData(@"subdirectory\MyXmlResponse.xml")]
public void PathUtils_CleanPath(string path)
{ {
[Theory] // Act
[InlineData(@"subdirectory/MyXmlResponse.xml")] var cleanPath = PathUtils.CleanPath(path);
[InlineData(@"subdirectory\MyXmlResponse.xml")]
public void PathUtils_CleanPath(string path)
{
// Act
var cleanPath = PathUtils.CleanPath(path);
// Assert // Assert
Check.That(cleanPath).Equals("subdirectory" + Path.DirectorySeparatorChar + "MyXmlResponse.xml"); Check.That(cleanPath).Equals("subdirectory" + Path.DirectorySeparatorChar + "MyXmlResponse.xml");
} }
[Theory] [Theory]
[InlineData(null, null)] [InlineData(null, null)]
[InlineData("", "")] [InlineData("", "")]
[InlineData("a", "a")] [InlineData("a", "a")]
[InlineData(@"/", "")] [InlineData(@"/", "")]
[InlineData(@"//", "")] [InlineData(@"//", "")]
[InlineData(@"//a", "a")] [InlineData(@"//a", "a")]
[InlineData(@"\", "")] [InlineData(@"\", "")]
[InlineData(@"\\", "")] [InlineData(@"\\", "")]
[InlineData(@"\\a", "a")] [InlineData(@"\\a", "a")]
public void PathUtils_CleanPath_RemoveLeadingDirectorySeparators(string path, string expected) public void PathUtils_CleanPath_RemoveLeadingDirectorySeparators(string path, string expected)
{ {
// Arrange // Arrange
var cleanPath = PathUtils.CleanPath(path); var cleanPath = PathUtils.CleanPath(path);
// Act // Act
var withoutDirectorySeparators = PathUtils.RemoveLeadingDirectorySeparators(cleanPath); var withoutDirectorySeparators = PathUtils.RemoveLeadingDirectorySeparators(cleanPath);
// Assert // Assert
Check.That(withoutDirectorySeparators).Equals(expected); Check.That(withoutDirectorySeparators).Equals(expected);
}
} }
} }
+71 -71
View File
@@ -3,92 +3,92 @@ using NFluent;
using WireMock.Util; using WireMock.Util;
using Xunit; using Xunit;
namespace WireMock.Net.Tests.Util namespace WireMock.Net.Tests.Util;
public class PortUtilsTests
{ {
public class PortUtilsTests [Fact]
public void PortUtils_TryExtract_InvalidUrl_Returns_False()
{ {
[Fact] // Assign
public void PortUtils_TryExtract_InvalidUrl_Returns_False() string url = "test";
{
// Assign
string url = "test";
// Act // Act
bool result = PortUtils.TryExtract(url, out bool isHttps, out string proto, out string host, out int port); bool result = PortUtils.TryExtract(url, out bool isHttps, out string proto, out string host, out int port);
// Assert // Assert
result.Should().BeFalse(); result.Should().BeFalse();
isHttps.Should().BeFalse(); isHttps.Should().BeFalse();
proto.Should().BeNull(); proto.Should().BeNull();
host.Should().BeNull(); host.Should().BeNull();
port.Should().Be(default(int)); port.Should().Be(default(int));
} }
[Fact] [Fact]
public void PortUtils_TryExtract_UrlIsMissingPort_Returns_False() public void PortUtils_TryExtract_UrlIsMissingPort_Returns_False()
{ {
// Assign // Assign
string url = "http://0.0.0.0"; string url = "http://0.0.0.0";
// Act // Act
bool result = PortUtils.TryExtract(url, out bool isHttps, out string proto, out string host, out int port); bool result = PortUtils.TryExtract(url, out bool isHttps, out string proto, out string host, out int port);
// Assert // Assert
result.Should().BeFalse(); result.Should().BeFalse();
isHttps.Should().BeFalse(); isHttps.Should().BeFalse();
proto.Should().BeNull(); proto.Should().BeNull();
host.Should().BeNull(); host.Should().BeNull();
port.Should().Be(default(int)); port.Should().Be(default(int));
} }
[Fact] [Fact]
public void PortUtils_TryExtract_Http_Returns_True() public void PortUtils_TryExtract_Http_Returns_True()
{ {
// Assign // Assign
string url = "http://wiremock.net:1234"; string url = "http://wiremock.net:1234";
// Act // Act
bool result = PortUtils.TryExtract(url, out bool isHttps, out string proto, out string host, out int port); bool result = PortUtils.TryExtract(url, out bool isHttps, out string proto, out string host, out int port);
// Assert // Assert
result.Should().BeTrue(); result.Should().BeTrue();
isHttps.Should().BeFalse(); isHttps.Should().BeFalse();
proto.Should().Be("http"); proto.Should().Be("http");
host.Should().Be("wiremock.net"); host.Should().Be("wiremock.net");
port.Should().Be(1234); port.Should().Be(1234);
} }
[Fact] [Fact]
public void PortUtils_TryExtract_Https_Returns_True() public void PortUtils_TryExtract_Https_Returns_True()
{ {
// Assign // Assign
string url = "https://wiremock.net:5000"; string url = "https://wiremock.net:5000";
// Act // Act
bool result = PortUtils.TryExtract(url, out bool isHttps, out string proto, out string host, out int port); bool result = PortUtils.TryExtract(url, out bool isHttps, out string proto, out string host, out int port);
// Assert // Assert
result.Should().BeTrue(); result.Should().BeTrue();
isHttps.Should().BeTrue(); isHttps.Should().BeTrue();
proto.Should().Be("https"); proto.Should().Be("https");
host.Should().Be("wiremock.net"); host.Should().Be("wiremock.net");
port.Should().Be(5000); port.Should().Be(5000);
} }
[Fact] [Fact]
public void PortUtils_TryExtract_Https0_0_0_0_Returns_True() public void PortUtils_TryExtract_Https0_0_0_0_Returns_True()
{ {
// Assign // Assign
string url = "https://0.0.0.0:5000"; string url = "https://0.0.0.0:5000";
// Act // Act
bool result = PortUtils.TryExtract(url, out bool isHttps, out string proto, out string host, out int port); bool result = PortUtils.TryExtract(url, out bool isHttps, out string proto, out string host, out int port);
// Assert // Assert
Check.That(result).IsTrue(); result.Should().BeTrue();
Check.That(proto).IsEqualTo("https"); isHttps.Should().BeTrue();
Check.That(host).IsEqualTo("0.0.0.0"); proto.Should().Be("https");
Check.That(port).IsEqualTo(5000); host.Should().Be("0.0.0.0");
} port.Should().Be(5000);
} }
} }