Add WireMock.org RestClient (#631)

* wip...

* x

* .

* .

* .

* r

* 1.4.21-preview-02

* 1.4.21-preview-03

* .

* usings

* wip

* .

* ut

* .

* .

* .

* tests

* .

* comments

* readme
This commit is contained in:
Stef Heyenrath
2021-09-16 14:35:08 +02:00
committed by GitHub
parent fd28ebdffa
commit cb66c04199
34 changed files with 7553 additions and 180 deletions

View File

@@ -1,167 +1,167 @@
using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using SimMetrics.Net;
using WireMock.Admin.Mappings;
using WireMock.Matchers;
using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using SimMetrics.Net;
using WireMock.Admin.Mappings;
using WireMock.Matchers;
using WireMock.Plugin;
using WireMock.Settings;
using WireMock.Validation;
namespace WireMock.Serialization
{
internal class MatcherMapper
{
private readonly IWireMockServerSettings _settings;
public MatcherMapper(IWireMockServerSettings settings)
{
Check.NotNull(settings, nameof(settings));
_settings = settings;
}
public IMatcher[] Map([CanBeNull] IEnumerable<MatcherModel> matchers)
{
return matchers?.Select(Map).Where(m => m != null).ToArray();
}
public IMatcher Map([CanBeNull] MatcherModel matcher)
{
if (matcher == null)
{
return null;
}
string[] parts = matcher.Name.Split('.');
string matcherName = parts[0];
string matcherType = parts.Length > 1 ? parts[1] : null;
string[] stringPatterns = (matcher.Patterns != null ? matcher.Patterns : new[] { matcher.Pattern }).OfType<string>().ToArray();
MatchBehaviour matchBehaviour = matcher.RejectOnMatch == true ? MatchBehaviour.RejectOnMatch : MatchBehaviour.AcceptOnMatch;
bool ignoreCase = matcher.IgnoreCase == true;
bool throwExceptionWhenMatcherFails = _settings.ThrowExceptionWhenMatcherFails == true;
switch (matcherName)
using WireMock.Settings;
using WireMock.Validation;
namespace WireMock.Serialization
{
internal class MatcherMapper
{
private readonly IWireMockServerSettings _settings;
public MatcherMapper(IWireMockServerSettings settings)
{
Check.NotNull(settings, nameof(settings));
_settings = settings;
}
public IMatcher[] Map([CanBeNull] IEnumerable<MatcherModel> matchers)
{
return matchers?.Select(Map).Where(m => m != null).ToArray();
}
public IMatcher Map([CanBeNull] MatcherModel matcher)
{
if (matcher == null)
{
case "NotNullOrEmptyMatcher":
return null;
}
string[] parts = matcher.Name.Split('.');
string matcherName = parts[0];
string matcherType = parts.Length > 1 ? parts[1] : null;
string[] stringPatterns = (matcher.Patterns != null ? matcher.Patterns : new[] { matcher.Pattern }).OfType<string>().ToArray();
MatchBehaviour matchBehaviour = matcher.RejectOnMatch == true ? MatchBehaviour.RejectOnMatch : MatchBehaviour.AcceptOnMatch;
bool ignoreCase = matcher.IgnoreCase == true;
bool throwExceptionWhenMatcherFails = _settings.ThrowExceptionWhenMatcherFails == true;
switch (matcherName)
{
case "NotNullOrEmptyMatcher":
return new NotNullOrEmptyMatcher(matchBehaviour);
case "CSharpCodeMatcher":
if (_settings.AllowCSharpCodeMatcher == true)
{
return PluginLoader.Load<ICSharpCodeMatcher>(matchBehaviour, stringPatterns);
}
throw new NotSupportedException("It's not allowed to use the 'CSharpCodeMatcher' because IWireMockServerSettings.AllowCSharpCodeMatcher is not set to 'true'.");
case "LinqMatcher":
return new LinqMatcher(matchBehaviour, throwExceptionWhenMatcherFails, stringPatterns);
case "ExactMatcher":
return new ExactMatcher(matchBehaviour, throwExceptionWhenMatcherFails, stringPatterns);
case "ExactObjectMatcher":
return CreateExactObjectMatcher(matchBehaviour, stringPatterns[0], throwExceptionWhenMatcherFails);
case "RegexMatcher":
return new RegexMatcher(matchBehaviour, stringPatterns, ignoreCase, throwExceptionWhenMatcherFails);
case "JsonMatcher":
object value = matcher.Pattern ?? matcher.Patterns;
return new JsonMatcher(matchBehaviour, value, ignoreCase, throwExceptionWhenMatcherFails);
case "JsonPartialMatcher":
object matcherValue = matcher.Pattern ?? matcher.Patterns;
return new JsonPartialMatcher(matchBehaviour, matcherValue, ignoreCase, throwExceptionWhenMatcherFails);
case "JsonPathMatcher":
return new JsonPathMatcher(matchBehaviour, throwExceptionWhenMatcherFails, stringPatterns);
case "JmesPathMatcher":
return new JmesPathMatcher(matchBehaviour, throwExceptionWhenMatcherFails, stringPatterns);
case "XPathMatcher":
return new XPathMatcher(matchBehaviour, throwExceptionWhenMatcherFails, stringPatterns);
case "WildcardMatcher":
return new WildcardMatcher(matchBehaviour, stringPatterns, ignoreCase, throwExceptionWhenMatcherFails);
case "ContentTypeMatcher":
return new ContentTypeMatcher(matchBehaviour, stringPatterns, ignoreCase, throwExceptionWhenMatcherFails);
case "SimMetricsMatcher":
SimMetricType type = SimMetricType.Levenstein;
if (!string.IsNullOrEmpty(matcherType) && !Enum.TryParse(matcherType, out type))
{
throw new NotSupportedException($"Matcher '{matcherName}' with Type '{matcherType}' is not supported.");
}
return new SimMetricsMatcher(matchBehaviour, stringPatterns, type, throwExceptionWhenMatcherFails);
default:
throw new NotSupportedException($"Matcher '{matcherName}' is not supported.");
}
}
public MatcherModel[] Map([CanBeNull] IEnumerable<IMatcher> matchers)
{
return matchers?.Select(Map).Where(m => m != null).ToArray();
}
public MatcherModel Map([CanBeNull] IMatcher matcher)
{
if (matcher == null)
{
return null;
}
object[] patterns = new object[0]; // Default empty array
switch (matcher)
{
// If the matcher is a IStringMatcher, get the patterns.
case IStringMatcher stringMatcher:
patterns = stringMatcher.GetPatterns().Cast<object>().ToArray();
break;
// If the matcher is a IValueMatcher, get the value (can be string or object).
case IValueMatcher valueMatcher:
patterns = new[] { valueMatcher.Value };
break;
// If the matcher is a ExactObjectMatcher, get the ValueAsObject or ValueAsBytes.
case ExactObjectMatcher exactObjectMatcher:
patterns = new[] { exactObjectMatcher.ValueAsObject ?? exactObjectMatcher.ValueAsBytes };
break;
}
bool? ignoreCase = matcher is IIgnoreCaseMatcher ignoreCaseMatcher ? ignoreCaseMatcher.IgnoreCase : (bool?)null;
bool? rejectOnMatch = matcher.MatchBehaviour == MatchBehaviour.RejectOnMatch ? true : (bool?)null;
return new MatcherModel
{
RejectOnMatch = rejectOnMatch,
IgnoreCase = ignoreCase,
Name = matcher.Name,
Pattern = patterns.Length == 1 ? patterns.First() : null,
Patterns = patterns.Length > 1 ? patterns : null
};
}
private ExactObjectMatcher CreateExactObjectMatcher(MatchBehaviour matchBehaviour, string stringPattern, bool throwException)
{
byte[] bytePattern;
try
{
bytePattern = Convert.FromBase64String(stringPattern);
}
catch
{
throw new ArgumentException($"Matcher 'ExactObjectMatcher' has invalid pattern. The pattern value '{stringPattern}' is not a Base64String.", nameof(stringPattern));
}
return new ExactObjectMatcher(matchBehaviour, bytePattern, throwException);
}
}
case "CSharpCodeMatcher":
if (_settings.AllowCSharpCodeMatcher == true)
{
return PluginLoader.Load<ICSharpCodeMatcher>(matchBehaviour, stringPatterns);
}
throw new NotSupportedException("It's not allowed to use the 'CSharpCodeMatcher' because IWireMockServerSettings.AllowCSharpCodeMatcher is not set to 'true'.");
case "LinqMatcher":
return new LinqMatcher(matchBehaviour, throwExceptionWhenMatcherFails, stringPatterns);
case "ExactMatcher":
return new ExactMatcher(matchBehaviour, throwExceptionWhenMatcherFails, stringPatterns);
case "ExactObjectMatcher":
return CreateExactObjectMatcher(matchBehaviour, stringPatterns[0], throwExceptionWhenMatcherFails);
case "RegexMatcher":
return new RegexMatcher(matchBehaviour, stringPatterns, ignoreCase, throwExceptionWhenMatcherFails);
case "JsonMatcher":
object value = matcher.Pattern ?? matcher.Patterns;
return new JsonMatcher(matchBehaviour, value, ignoreCase, throwExceptionWhenMatcherFails);
case "JsonPartialMatcher":
object matcherValue = matcher.Pattern ?? matcher.Patterns;
return new JsonPartialMatcher(matchBehaviour, matcherValue, ignoreCase, throwExceptionWhenMatcherFails);
case "JsonPathMatcher":
return new JsonPathMatcher(matchBehaviour, throwExceptionWhenMatcherFails, stringPatterns);
case "JmesPathMatcher":
return new JmesPathMatcher(matchBehaviour, throwExceptionWhenMatcherFails, stringPatterns);
case "XPathMatcher":
return new XPathMatcher(matchBehaviour, throwExceptionWhenMatcherFails, stringPatterns);
case "WildcardMatcher":
return new WildcardMatcher(matchBehaviour, stringPatterns, ignoreCase, throwExceptionWhenMatcherFails);
case "ContentTypeMatcher":
return new ContentTypeMatcher(matchBehaviour, stringPatterns, ignoreCase, throwExceptionWhenMatcherFails);
case "SimMetricsMatcher":
SimMetricType type = SimMetricType.Levenstein;
if (!string.IsNullOrEmpty(matcherType) && !Enum.TryParse(matcherType, out type))
{
throw new NotSupportedException($"Matcher '{matcherName}' with Type '{matcherType}' is not supported.");
}
return new SimMetricsMatcher(matchBehaviour, stringPatterns, type, throwExceptionWhenMatcherFails);
default:
throw new NotSupportedException($"Matcher '{matcherName}' is not supported.");
}
}
public MatcherModel[] Map([CanBeNull] IEnumerable<IMatcher> matchers)
{
return matchers?.Select(Map).Where(m => m != null).ToArray();
}
public MatcherModel Map([CanBeNull] IMatcher matcher)
{
if (matcher == null)
{
return null;
}
object[] patterns = new object[0]; // Default empty array
switch (matcher)
{
// If the matcher is a IStringMatcher, get the patterns.
case IStringMatcher stringMatcher:
patterns = stringMatcher.GetPatterns().Cast<object>().ToArray();
break;
// If the matcher is a IValueMatcher, get the value (can be string or object).
case IValueMatcher valueMatcher:
patterns = new[] { valueMatcher.Value };
break;
// If the matcher is a ExactObjectMatcher, get the ValueAsObject or ValueAsBytes.
case ExactObjectMatcher exactObjectMatcher:
patterns = new[] { exactObjectMatcher.ValueAsObject ?? exactObjectMatcher.ValueAsBytes };
break;
}
bool? ignoreCase = matcher is IIgnoreCaseMatcher ignoreCaseMatcher ? ignoreCaseMatcher.IgnoreCase : (bool?)null;
bool? rejectOnMatch = matcher.MatchBehaviour == MatchBehaviour.RejectOnMatch ? true : (bool?)null;
return new MatcherModel
{
RejectOnMatch = rejectOnMatch,
IgnoreCase = ignoreCase,
Name = matcher.Name,
Pattern = patterns.Length == 1 ? patterns.First() : null,
Patterns = patterns.Length > 1 ? patterns : null
};
}
private ExactObjectMatcher CreateExactObjectMatcher(MatchBehaviour matchBehaviour, string stringPattern, bool throwException)
{
byte[] bytePattern;
try
{
bytePattern = Convert.FromBase64String(stringPattern);
}
catch
{
throw new ArgumentException($"Matcher 'ExactObjectMatcher' has invalid pattern. The pattern value '{stringPattern}' is not a Base64String.", nameof(stringPattern));
}
return new ExactObjectMatcher(matchBehaviour, bytePattern, throwException);
}
}
}

View File

@@ -39,6 +39,7 @@ namespace WireMock.Server
private const string ContentTypeJson = "application/json";
private const string AdminFiles = "/__admin/files";
private const string AdminMappings = "/__admin/mappings";
private const string AdminMappingsWireMockOrg = "/__admin/mappings/wiremock.org";
private const string AdminRequests = "/__admin/requests";
private const string AdminSettings = "/__admin/settings";
private const string AdminScenarios = "/__admin/scenarios";
@@ -58,6 +59,7 @@ namespace WireMock.Server
// __admin/mappings
Given(Request.Create().WithPath(AdminMappings).UsingGet()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(MappingsGet));
Given(Request.Create().WithPath(AdminMappings).UsingPost().WithHeader(HttpKnownHeaderNames.ContentType, _adminRequestContentTypeJson)).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(MappingsPost));
Given(Request.Create().WithPath(AdminMappingsWireMockOrg).UsingPost().WithHeader(HttpKnownHeaderNames.ContentType, _adminRequestContentTypeJson)).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(MappingsPostWireMockOrg));
Given(Request.Create().WithPath(AdminMappings).UsingDelete()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(MappingsDelete));
// __admin/mappings/reset

View File

@@ -0,0 +1,306 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using JetBrains.Annotations;
using Newtonsoft.Json.Linq;
using WireMock.Matchers;
using WireMock.RequestBuilders;
using WireMock.ResponseBuilders;
using WireMock.Util;
using WireMock.Validation;
using OrgMapping = WireMock.Org.Abstractions.Mapping;
namespace WireMock.Server
{
public partial class WireMockServer
{
/// <summary>
/// Read WireMock.org mapping json file.
/// </summary>
/// <param name="path">The path to the WireMock.org mapping json file.</param>
[PublicAPI]
public void ReadStaticWireMockOrgMappingAndAddOrUpdate([NotNull] string path)
{
Check.NotNull(path, nameof(path));
string filenameWithoutExtension = Path.GetFileNameWithoutExtension(path);
if (FileHelper.TryReadMappingFileWithRetryAndDelay(_settings.FileSystemHandler, path, out string value))
{
var mappings = DeserializeJsonToArray<OrgMapping>(value);
foreach (var mapping in mappings)
{
if (mappings.Length == 1 && Guid.TryParse(filenameWithoutExtension, out Guid guidFromFilename))
{
ConvertWireMockOrgMappingAndRegisterAsRespondProvider(mapping, guidFromFilename, path);
}
else
{
ConvertWireMockOrgMappingAndRegisterAsRespondProvider(mapping, null, path);
}
}
}
}
private ResponseMessage MappingsPostWireMockOrg(RequestMessage requestMessage)
{
try
{
var mappingModels = DeserializeRequestMessageToArray<OrgMapping>(requestMessage);
if (mappingModels.Length == 1)
{
Guid? guid = ConvertWireMockOrgMappingAndRegisterAsRespondProvider(mappingModels[0]);
return ResponseMessageBuilder.Create("Mapping added", 201, guid);
}
foreach (var mappingModel in mappingModels)
{
ConvertWireMockOrgMappingAndRegisterAsRespondProvider(mappingModel);
}
return ResponseMessageBuilder.Create("Mappings added", 201);
}
catch (ArgumentException a)
{
_settings.Logger.Error("HttpStatusCode set to 400 {0}", a);
return ResponseMessageBuilder.Create(a.Message, 400);
}
catch (Exception e)
{
_settings.Logger.Error("HttpStatusCode set to 500 {0}", e);
return ResponseMessageBuilder.Create(e.ToString(), 500);
}
}
private Guid? ConvertWireMockOrgMappingAndRegisterAsRespondProvider(OrgMapping mapping, Guid? guid = null, string path = null)
{
var requestBuilder = Request.Create();
var request = mapping.Request;
if (request != null)
{
if (request.Url != null)
{
requestBuilder = requestBuilder.WithUrl(request.Url);
}
else if (request.UrlPattern != null)
{
requestBuilder = requestBuilder.WithUrl(new RegexMatcher(request.UrlPattern));
}
else if (request.UrlPath != null)
{
requestBuilder = requestBuilder.WithPath(request.Url);
}
else if (request.UrlPathPattern != null)
{
requestBuilder = requestBuilder.WithPath(new RegexMatcher(request.UrlPathPattern));
}
if (request.Method != null)
{
requestBuilder = requestBuilder.UsingMethod(request.Method);
}
/*
"headers" : {
"Accept" : {
"contains" : "xml"
}
}
*/
if (request.Headers is JObject headers)
{
ProcessWireMockOrgJObjectAndUseStringMatcher(headers, (key, match) =>
{
requestBuilder = requestBuilder.WithHeader(key, match as IStringMatcher);
});
}
if (request.Cookies is JObject cookies)
{
ProcessWireMockOrgJObjectAndUseStringMatcher(cookies, (key, match) =>
{
requestBuilder = requestBuilder.WithCookie(key, match as IStringMatcher);
});
}
/*
"queryParameters" : {
"search_term" : {
"equalTo" : "WireMock"
}
}
*/
if (request.QueryParameters is JObject queryParameters)
{
ProcessWireMockOrgJObjectAndUseStringMatcher(queryParameters, (key, match) =>
{
requestBuilder = requestBuilder.WithParam(key, match);
});
}
/*
"bodyPatterns" : [ {
"equalToJson" : "{ "cityName": "São Paulo", "cityCode": 5001 },
"ignoreArrayOrder" : true,
"ignoreExtraElements" : true
} ]
*/
if (request.BodyPatterns?.Any() == true)
{
var jObjectArray = request.BodyPatterns.Cast<JObject>();
var bodyPattern = jObjectArray.First();
ProcessWireMockOrgJObjectAndUseIMatcher(bodyPattern, (match) =>
{
requestBuilder = requestBuilder.WithBody(match);
});
}
}
IResponseBuilder responseBuilder = Response.Create();
var response = mapping.Response;
if (response != null)
{
responseBuilder = responseBuilder.WithStatusCode(response.Status);
if (response.Headers is JObject responseHeaders)
{
ProcessWireMockOrgJObjectAndConvertToIDictionary(responseHeaders, (headers) =>
{
responseBuilder = responseBuilder.WithHeaders(headers);
});
}
if (response.Transformers != null)
{
responseBuilder = responseBuilder.WithTransformer();
}
if (response.Body != null)
{
responseBuilder = responseBuilder.WithBody(response.Body);
}
if (response.JsonBody != null)
{
responseBuilder = responseBuilder.WithBodyAsJson(response.JsonBody);
}
if (response.Base64Body != null)
{
responseBuilder = responseBuilder.WithBody(Encoding.UTF8.GetString(Convert.FromBase64String(response.Base64Body)));
}
if (response.BodyFileName != null)
{
responseBuilder = responseBuilder.WithBodyFromFile(response.BodyFileName);
}
}
var respondProvider = Given(requestBuilder);
if (guid != null)
{
respondProvider = respondProvider.WithGuid(guid.Value);
}
else if (!string.IsNullOrEmpty(mapping.Uuid))
{
respondProvider = respondProvider.WithGuid(new Guid(mapping.Uuid));
}
if (mapping.Name != null)
{
respondProvider = respondProvider.WithTitle(mapping.Name);
}
if (path != null)
{
respondProvider = respondProvider.WithPath(path);
}
respondProvider.RespondWith(responseBuilder);
return respondProvider.Guid;
}
private void ProcessWireMockOrgJObjectAndConvertToIDictionary(JObject items, Action<IDictionary<string, string>> action)
{
var dict = new Dictionary<string, string>();
foreach (var item in items)
{
var key = item.Key;
var valueAsString = item.Value.Value<string>();
dict.Add(key, valueAsString);
}
action(dict);
}
private void ProcessWireMockOrgJObjectAndUseStringMatcher(JObject items, Action<string, IStringMatcher> action)
{
foreach (var item in items)
{
var key = item.Key;
var match = item.Value.First as JProperty;
var valueAsString = match?.Value.Value<string>();
if (string.IsNullOrEmpty(valueAsString))
{
continue;
}
var matcher = ProcessAsStringMatcher(match, valueAsString);
if (matcher != null)
{
action(key, matcher);
}
}
}
private void ProcessWireMockOrgJObjectAndUseIMatcher(JObject items, Action<IMatcher> action)
{
IMatcher matcher = null;
var firstItem = items.First as JProperty;
if (firstItem?.Name == "equalToJson")
{
matcher = new JsonMatcher(firstItem.Value);
}
else
{
var valueAsString = (firstItem.Value as JValue)?.Value as string;
if (valueAsString == null)
{
return;
}
matcher = ProcessAsStringMatcher(firstItem, valueAsString);
}
if (matcher != null)
{
action(matcher);
}
}
private static IStringMatcher ProcessAsStringMatcher(JProperty match, string valueAsString)
{
switch (match?.Name)
{
case "contains":
return new WildcardMatcher(valueAsString);
case "matches":
return new RegexMatcher(valueAsString);
case "equalTo":
return new ExactMatcher(valueAsString);
default:
return null;
}
}
}
}

View File

@@ -132,5 +132,6 @@
<ItemGroup>
<ProjectReference Include="..\WireMock.Net.Abstractions\WireMock.Net.Abstractions.csproj" />
<ProjectReference Include="..\WireMock.Org.Abstractions\WireMock.Org.Abstractions.csproj" />
</ItemGroup>
</Project>