Create WireMock.Net.MimePart project (#1300)

* Create WireMock.Net.MimePart project

* .

* REFACTOR

* ILRepack

* --

* ...

* x

* x

* .

* fix

* public class MimePartMatcher

* shared

* min

* .

* <!--<DelaySign>true</DelaySign>-->

* Update README.md

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Stef Heyenrath
2025-05-24 12:17:42 +02:00
committed by GitHub
parent c15206ecd8
commit 96eca4262a
306 changed files with 9746 additions and 9285 deletions

View File

@@ -0,0 +1,255 @@
// Copyright © WireMock.Net
using System;
using System.Collections.Generic;
using System.Net;
using WireMock.Models;
using WireMock.ResponseBuilders;
using WireMock.ResponseProviders;
using WireMock.Settings;
using WireMock.Types;
namespace WireMock.Server;
/// <summary>
/// IRespondWithAProvider
/// </summary>
public interface IRespondWithAProvider
{
/// <summary>
/// Gets the unique identifier for this mapping.
/// </summary>
Guid Guid { get; }
/// <summary>
/// Define a unique identifier for this mapping.
/// </summary>
/// <param name="guid">The unique identifier.</param>
/// <returns>The <see cref="IRespondWithAProvider"/>.</returns>
IRespondWithAProvider WithGuid(Guid guid);
/// <summary>
/// Define a unique identifier for this mapping.
/// </summary>
/// <param name="guid">The unique identifier.</param>
/// <returns>The <see cref="IRespondWithAProvider"/>.</returns>
IRespondWithAProvider WithGuid(string guid);
/// <summary>
/// Define a unique identifier for this mapping.
/// </summary>
/// <param name="guid">The unique identifier.</param>
/// <returns>The <see cref="IRespondWithAProvider"/>.</returns>
IRespondWithAProvider DefineGuid(Guid guid);
/// <summary>
/// Define a unique identifier for this mapping.
/// </summary>
/// <param name="guid">The unique identifier.</param>
/// <returns>The <see cref="IRespondWithAProvider"/>.</returns>
IRespondWithAProvider DefineGuid(string guid);
/// <summary>
/// Define the TimeSettings for this mapping.
/// </summary>
/// <param name="timeSettings">The TimeSettings.</param>
/// <returns>The <see cref="IRespondWithAProvider"/>.</returns>
IRespondWithAProvider WithTimeSettings(ITimeSettings timeSettings);
/// <summary>
/// Define a unique title for this mapping.
/// </summary>
/// <param name="title">The unique title.</param>
/// <returns>The <see cref="IRespondWithAProvider"/>.</returns>
IRespondWithAProvider WithTitle(string title);
/// <summary>
/// Define a description for this mapping.
/// </summary>
/// <param name="description">The description.</param>
/// <returns>The <see cref="IRespondWithAProvider"/>.</returns>
IRespondWithAProvider WithDescription(string description);
/// <summary>
/// Define the full filepath for this mapping.
/// </summary>
/// <param name="path">The full filepath.</param>
/// <returns>The <see cref="IRespondWithAProvider"/>.</returns>
IRespondWithAProvider WithPath(string path);
/// <summary>
/// Define the priority for this mapping.
/// </summary>
/// <param name="priority">The priority. (A lower value means a higher priority.)</param>
/// <returns>The <see cref="IRespondWithAProvider"/>.</returns>
IRespondWithAProvider AtPriority(int priority);
/// <summary>
/// RespondWith
/// </summary>
/// <param name="provider">The provider.</param>
void RespondWith(IResponseProvider provider);
/// <summary>
/// RespondWith
/// </summary>
/// <param name="action">The action to use the fluent <see cref="IResponseBuilder"/>.</param>
void ThenRespondWith(Action<IResponseBuilder> action);
/// <summary>
/// RespondWith a status code 200 (OK);
/// </summary>
void ThenRespondWithOK();
/// <summary>
/// RespondWith a 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>
void ThenRespondWithStatusCode(int code);
/// <summary>
/// RespondWith a 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>
void ThenRespondWithStatusCode(string code);
/// <summary>
/// RespondWith a 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>
void ThenRespondWithStatusCode(HttpStatusCode code);
/// <summary>
/// Sets the scenario.
/// </summary>
/// <param name="scenario">The scenario.</param>
/// <returns>The <see cref="IRespondWithAProvider"/>.</returns>
IRespondWithAProvider InScenario(string scenario);
/// <summary>
/// Sets the scenario with an integer value.
/// </summary>
/// <param name="scenario">The scenario.</param>
/// <returns>The <see cref="IRespondWithAProvider"/>.</returns>
IRespondWithAProvider InScenario(int scenario);
/// <summary>
/// Execute this respond only in case the current state is equal to specified one.
/// </summary>
/// <param name="state">Any object which identifies the current state</param>
/// <returns>The <see cref="IRespondWithAProvider"/>.</returns>
IRespondWithAProvider WhenStateIs(string state);
/// <summary>
/// Execute this respond only in case the current state is equal to specified one.
/// </summary>
/// <param name="state">Any object which identifies the current state</param>
/// <returns>The <see cref="IRespondWithAProvider"/>.</returns>
IRespondWithAProvider WhenStateIs(int state);
/// <summary>
/// Once this mapping is executed the state will be changed to specified one.
/// </summary>
/// <param name="state">Any object which identifies the new state</param>
/// <param name="times">The number of times this match should be matched before the state will be changed to the specified one. Default value is 1.</param>
/// <returns>The <see cref="IRespondWithAProvider"/>.</returns>
IRespondWithAProvider WillSetStateTo(string state, int? times = 1);
/// <summary>
/// Once this mapping is executed the state will be changed to specified one.
/// </summary>
/// <param name="state">Any object which identifies the new state</param>
/// <param name="times">The number of times this match should be matched before the state will be changed to the specified one. Default value is 1.</param>
/// <returns>The <see cref="IRespondWithAProvider"/>.</returns>
IRespondWithAProvider WillSetStateTo(int state, int? times = 1);
/// <summary>
/// Add (multiple) Webhook(s) to call after the response has been generated.
/// </summary>
/// <param name="webhooks">The Webhooks</param>
/// <returns>The <see cref="IRespondWithAProvider"/>.</returns>
IRespondWithAProvider WithWebhook(params IWebhook[] webhooks);
/// <summary>
/// Support FireAndForget for any configured Webhooks
/// </summary>
/// <param name="useWebhooksFireAndForget"></param>
/// <returns></returns>
IRespondWithAProvider WithWebhookFireAndForget(bool useWebhooksFireAndForget);
/// <summary>
/// Add a Webhook to call after the response has been generated.
/// </summary>
/// <param name="url">The Webhook Url</param>
/// <param name="method">The method to use. [optional]</param>
/// <param name="headers">The Headers to send. [optional]</param>
/// <param name="body">The body (as string) to send. [optional]</param>
/// <param name="useTransformer">Use Transformer. [optional]</param>
/// <param name="transformerType">The transformer type. [optional]</param>
/// <returns>The <see cref="IRespondWithAProvider"/>.</returns>
IRespondWithAProvider WithWebhook(
string url,
string method = "post",
IDictionary<string, WireMockList<string>>? headers = null,
string? body = null,
bool useTransformer = true,
TransformerType transformerType = TransformerType.Handlebars
);
/// <summary>
/// Add a Webhook to call after the response has been generated.
/// </summary>
/// <param name="url">The Webhook Url</param>
/// <param name="method">The method to use. [optional]</param>
/// <param name="headers">The Headers to send. [optional]</param>
/// <param name="body">The body (as json) to send. [optional]</param>
/// <param name="useTransformer">Use Transformer. [optional]</param>
/// <param name="transformerType">The transformer type. [optional]</param>
/// <returns>The <see cref="IRespondWithAProvider"/>.</returns>
IRespondWithAProvider WithWebhook(
string url,
string method = "post",
IDictionary<string, WireMockList<string>>? headers = null,
object? body = null,
bool useTransformer = true,
TransformerType transformerType = TransformerType.Handlebars
);
/// <summary>
/// Data Object which can be used when WithTransformer is used.
/// e.g. lookup a path in this object using
/// <param name="data">The data dictionary object.</param>
/// <example>
/// lookup data "1"
/// </example>
/// </summary>
/// <returns>The <see cref="IRespondWithAProvider"/>.</returns>
IRespondWithAProvider WithData(object data);
/// <summary>
/// Define the probability when this request should be matched. Value is between 0 and 1.
/// </summary>
/// <param name="probability">The probability when this request should be matched. Value is between 0 and 1.</param>
/// <returns>The <see cref="IRespondWithAProvider"/>.</returns>
IRespondWithAProvider WithProbability(double probability);
/// <summary>
/// Define a Grpc ProtoDefinition which is used for the request and the response.
/// This can be a ProtoDefinition as a string, or an id when the ProtoDefinitions are defined at the WireMockServer.
/// </summary>
/// <param name="protoDefinitionOrId">The proto definition as text or as id.</param>
/// <returns>The <see cref="IRespondWithAProvider"/>.</returns>
IRespondWithAProvider WithProtoDefinition(params string[] protoDefinitionOrId);
/// <summary>
/// Define a GraphQL Schema which is used for the request and the response.
/// This can be a GraphQL Schema as a string, or an id when the GraphQL Schema are defined at the WireMockServer.
/// </summary>
/// <param name="graphQLSchemaOrId">The GraphQL Schema as text or as id.</param>
/// <param name="customScalars">A dictionary defining the custom scalars used in this schema. [optional]</param>
/// <returns>The <see cref="IRespondWithAProvider"/>.</returns>
IRespondWithAProvider WithGraphQLSchema(string graphQLSchemaOrId, IDictionary<string, Type>? customScalars = null);
}

View File

@@ -0,0 +1,404 @@
// Copyright © WireMock.Net and mock4net by Alexandre Victoor
// This source file is based on mock4net by Alexandre Victoor which is licensed under the Apache 2.0 License.
// For more details see 'mock4net/LICENSE.txt' and 'mock4net/readme.md' in this project root.
using System;
using System.Collections.Generic;
using System.Net;
using Stef.Validation;
using WireMock.Matchers.Request;
using WireMock.Models;
using WireMock.ResponseBuilders;
using WireMock.ResponseProviders;
using WireMock.Settings;
using WireMock.Types;
using WireMock.Util;
namespace WireMock.Server;
/// <summary>
/// The RespondWithAProvider.
/// </summary>
internal class RespondWithAProvider : IRespondWithAProvider
{
private readonly RegistrationCallback _registrationCallback;
private readonly IRequestMatcher _requestMatcher;
private readonly WireMockServerSettings _settings;
private readonly IDateTimeUtils _dateTimeUtils;
private readonly bool _saveToFile;
private int _priority;
private string? _title;
private string? _description;
private string? _path;
private string? _executionConditionState;
private string? _nextState;
private string? _scenario;
private int _timesInSameState = 1;
private bool? _useWebhookFireAndForget;
private double? _probability;
private GraphQLSchemaDetails? _graphQLSchemaDetails;
public Guid Guid { get; private set; }
public IWebhook[]? Webhooks { get; private set; }
public ITimeSettings? TimeSettings { get; private set; }
public object? Data { get; private set; }
public IdOrTexts? ProtoDefinition { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="RespondWithAProvider"/> class.
/// </summary>
/// <param name="registrationCallback">The registration callback.</param>
/// <param name="requestMatcher">The request matcher.</param>
/// <param name="settings">The WireMockServerSettings.</param>
/// <param name="guidUtils">GuidUtils to make unit testing possible.</param>
/// <param name="dateTimeUtils">DateTimeUtils to make unit testing possible.</param>
/// <param name="saveToFile">Optional boolean to indicate if this mapping should be saved as static mapping file.</param>
public RespondWithAProvider(
RegistrationCallback registrationCallback,
IRequestMatcher requestMatcher,
WireMockServerSettings settings,
IGuidUtils guidUtils,
IDateTimeUtils dateTimeUtils,
bool saveToFile = false
)
{
_registrationCallback = Guard.NotNull(registrationCallback);
_requestMatcher = Guard.NotNull(requestMatcher);
_settings = Guard.NotNull(settings);
_dateTimeUtils = Guard.NotNull(dateTimeUtils);
_saveToFile = saveToFile;
Guid = guidUtils.NewGuid();
}
/// <inheritdoc />
public void RespondWith(IResponseProvider provider)
{
var mapping = new Mapping
(
Guid,
_dateTimeUtils.UtcNow,
_title,
_description,
_path,
_settings,
_requestMatcher,
provider,
_priority,
_scenario,
_executionConditionState,
_nextState,
_timesInSameState,
Webhooks,
_useWebhookFireAndForget,
TimeSettings,
Data
);
if (_probability != null)
{
mapping.WithProbability(_probability.Value);
}
if (ProtoDefinition != null)
{
mapping.WithProtoDefinition(ProtoDefinition.Value);
}
_registrationCallback(mapping, _saveToFile);
}
/// <inheritdoc />
public void ThenRespondWith(Action<IResponseBuilder> action)
{
var responseBuilder = Response.Create();
action(responseBuilder);
RespondWith(responseBuilder);
}
/// <inheritdoc />
public void ThenRespondWithOK()
{
var responseBuilder = Response.Create();
RespondWith(responseBuilder);
}
/// <inheritdoc />
public void ThenRespondWithStatusCode(int code)
{
var responseBuilder = Response.Create().WithStatusCode(code);
RespondWith(responseBuilder);
}
/// <inheritdoc />
public void ThenRespondWithStatusCode(string code)
{
var responseBuilder = Response.Create().WithStatusCode(code);
RespondWith(responseBuilder);
}
/// <inheritdoc />
public void ThenRespondWithStatusCode(HttpStatusCode code)
{
var responseBuilder = Response.Create().WithStatusCode(code);
RespondWith(responseBuilder);
}
/// <inheritdoc />
public IRespondWithAProvider WithData(object data)
{
Data = data;
return this;
}
/// <inheritdoc />
public IRespondWithAProvider WithGuid(string guid)
{
return WithGuid(Guid.Parse(guid));
}
/// <inheritdoc />
public IRespondWithAProvider WithGuid(Guid guid)
{
Guid = guid;
return this;
}
/// <inheritdoc />
public IRespondWithAProvider DefineGuid(Guid guid)
{
return WithGuid(guid);
}
/// <inheritdoc />
public IRespondWithAProvider DefineGuid(string guid)
{
return WithGuid(guid);
}
/// <inheritdoc />
public IRespondWithAProvider WithTitle(string title)
{
_title = title;
return this;
}
/// <inheritdoc />
public IRespondWithAProvider WithDescription(string description)
{
_description = description;
return this;
}
/// <inheritdoc />
public IRespondWithAProvider WithPath(string path)
{
_path = path;
return this;
}
/// <inheritdoc />
public IRespondWithAProvider AtPriority(int priority)
{
_priority = priority;
return this;
}
/// <inheritdoc />
public IRespondWithAProvider InScenario(string scenario)
{
_scenario = Guard.NotNullOrWhiteSpace(scenario);
return this;
}
/// <inheritdoc />
public IRespondWithAProvider InScenario(int scenario)
{
return InScenario(scenario.ToString());
}
/// <inheritdoc />
public IRespondWithAProvider WhenStateIs(string state)
{
if (string.IsNullOrEmpty(_scenario))
{
throw new NotSupportedException("Unable to set state condition when no scenario is defined.");
}
_executionConditionState = state;
return this;
}
/// <inheritdoc />
public IRespondWithAProvider WhenStateIs(int state)
{
return WhenStateIs(state.ToString());
}
/// <inheritdoc />
public IRespondWithAProvider WillSetStateTo(string state, int? times = 1)
{
if (string.IsNullOrEmpty(_scenario))
{
throw new NotSupportedException("Unable to set next state when no scenario is defined.");
}
_nextState = state;
_timesInSameState = times ?? 1;
return this;
}
/// <inheritdoc />
public IRespondWithAProvider WillSetStateTo(int state, int? times = 1)
{
return WillSetStateTo(state.ToString(), times);
}
/// <inheritdoc />
public IRespondWithAProvider WithTimeSettings(ITimeSettings timeSettings)
{
TimeSettings = Guard.NotNull(timeSettings);
return this;
}
/// <inheritdoc />
public IRespondWithAProvider WithWebhook(params IWebhook[] webhooks)
{
Guard.HasNoNulls(webhooks);
Webhooks = webhooks;
return this;
}
/// <inheritdoc />
public IRespondWithAProvider WithWebhook(
string url,
string method = "post",
IDictionary<string, WireMockList<string>>? headers = null,
string? body = null,
bool useTransformer = true,
TransformerType transformerType = TransformerType.Handlebars)
{
Guard.NotNull(url);
Guard.NotNull(method);
Webhooks = [InitWebhook(url, method, headers, useTransformer, transformerType)];
if (body != null)
{
Webhooks[0].Request.BodyData = new BodyData
{
BodyAsString = body,
DetectedBodyType = BodyType.String,
DetectedBodyTypeFromContentType = BodyType.String
};
}
return this;
}
/// <inheritdoc />
public IRespondWithAProvider WithWebhook(
string url,
string method = "post",
IDictionary<string, WireMockList<string>>? headers = null,
object? body = null,
bool useTransformer = true,
TransformerType transformerType = TransformerType.Handlebars)
{
Guard.NotNull(url);
Guard.NotNull(method);
Webhooks = [InitWebhook(url, method, headers, useTransformer, transformerType)];
if (body != null)
{
Webhooks[0].Request.BodyData = new BodyData
{
BodyAsJson = body,
DetectedBodyType = BodyType.Json,
DetectedBodyTypeFromContentType = BodyType.Json
};
}
return this;
}
public IRespondWithAProvider WithWebhookFireAndForget(bool useWebhooksFireAndForget)
{
_useWebhookFireAndForget = useWebhooksFireAndForget;
return this;
}
public IRespondWithAProvider WithProbability(double probability)
{
_probability = Guard.Condition(probability, p => p is >= 0 and <= 1.0);
return this;
}
/// <inheritdoc />
public IRespondWithAProvider WithProtoDefinition(params string[] protoDefinitionOrId)
{
Guard.NotNull(protoDefinitionOrId);
#if PROTOBUF
ProtoDefinition = ProtoDefinitionHelper.GetIdOrTexts(_settings, protoDefinitionOrId);
return this;
#else
throw new NotSupportedException("The WithProtoDefinition method can not be used for .NETStandard1.3 or .NET Framework 4.6.1 or lower.");
#endif
}
/// <inheritdoc />
public IRespondWithAProvider WithGraphQLSchema(string graphQLSchemaOrId, IDictionary<string, Type>? customScalars = null)
{
Guard.NotNullOrWhiteSpace(graphQLSchemaOrId);
if (_settings.GraphQLSchemas?.TryGetValue(graphQLSchemaOrId, out _graphQLSchemaDetails) != true)
{
_graphQLSchemaDetails = new GraphQLSchemaDetails
{
SchemaAsString = graphQLSchemaOrId,
CustomScalars = customScalars
};
}
return this;
}
private static IWebhook InitWebhook(
string url,
string method,
IDictionary<string, WireMockList<string>>? headers,
bool useTransformer,
TransformerType transformerType
)
{
return new Webhook
{
Request = new WebhookRequest
{
Url = url,
Method = method,
Headers = headers,
UseTransformer = useTransformer,
TransformerType = transformerType
}
};
}
}

View File

@@ -0,0 +1,886 @@
// Copyright © WireMock.Net
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using JetBrains.Annotations;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Stef.Validation;
using WireMock.Admin.Mappings;
using WireMock.Admin.Scenarios;
using WireMock.Admin.Settings;
using WireMock.Constants;
using WireMock.Http;
using WireMock.Logging;
using WireMock.Matchers;
using WireMock.Matchers.Request;
using WireMock.Owin;
using WireMock.RequestBuilders;
using WireMock.ResponseProviders;
using WireMock.Serialization;
using WireMock.Settings;
using WireMock.Types;
using WireMock.Util;
namespace WireMock.Server;
/// <summary>
/// The fluent mock server.
/// </summary>
public partial class WireMockServer
{
private const int EnhancedFileSystemWatcherTimeoutMs = 1000;
private const string DefaultAdminPathPrefix = "/__admin";
private const string QueryParamReloadStaticMappings = "reloadStaticMappings";
private static readonly Guid ProxyMappingGuid = new("e59914fd-782e-428e-91c1-4810ffb86567");
private static readonly RegexMatcher AdminRequestContentTypeJson = new ContentTypeMatcher(WireMockConstants.ContentTypeJson, true);
private EnhancedFileSystemWatcher? _enhancedFileSystemWatcher;
private AdminPaths? _adminPaths;
private sealed class AdminPaths
{
private readonly string _prefix;
private readonly string _prefixEscaped;
public AdminPaths(WireMockServerSettings settings)
{
_prefix = settings.AdminPath ?? DefaultAdminPathPrefix;
_prefixEscaped = _prefix.Replace("/", "\\/");
}
public string Files => $"{_prefix}/files";
public string Health => $"{_prefix}/health";
public string Mappings => $"{_prefix}/mappings";
public string MappingsCode => $"{_prefix}/mappings/code";
public string MappingsWireMockOrg => $"{_prefix}mappings/wiremock.org";
public string Requests => $"{_prefix}/requests";
public string Settings => $"{_prefix}/settings";
public string Scenarios => $"{_prefix}/scenarios";
public string OpenApi => $"{_prefix}/openapi";
public RegexMatcher MappingsGuidPathMatcher => new($"^{_prefixEscaped}\\/mappings\\/([0-9A-Fa-f]{{8}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{12}})$");
public RegexMatcher MappingsCodeGuidPathMatcher => new($"^{_prefixEscaped}\\/mappings\\/code\\/([0-9A-Fa-f]{{8}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{12}})$");
public RegexMatcher RequestsGuidPathMatcher => new($"^{_prefixEscaped}\\/requests\\/([0-9A-Fa-f]{{8}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{12}})$");
public RegexMatcher ScenariosNameMatcher => new($"^{_prefixEscaped}\\/scenarios\\/.+$");
public RegexMatcher ScenariosNameWithResetMatcher => new($"^{_prefixEscaped}\\/scenarios\\/.+\\/reset$");
public RegexMatcher FilesFilenamePathMatcher => new($"^{_prefixEscaped}\\/files\\/.+$");
public RegexMatcher ProtoDefinitionsIdPathMatcher => new($"^{_prefixEscaped}\\/protodefinitions\\/.+$");
}
#region InitAdmin
private void InitAdmin()
{
_adminPaths = new AdminPaths(_settings);
// __admin/health
Given(Request.Create().WithPath(_adminPaths.Health).UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(HealthGet));
// __admin/settings
Given(Request.Create().WithPath(_adminPaths.Settings).UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(SettingsGet));
Given(Request.Create().WithPath(_adminPaths.Settings).UsingMethod("PUT", "POST").WithHeader(HttpKnownHeaderNames.ContentType, AdminRequestContentTypeJson)).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(SettingsUpdate));
// __admin/mappings
Given(Request.Create().WithPath(_adminPaths.Mappings).UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingsGet));
Given(Request.Create().WithPath(_adminPaths.Mappings).UsingPost().WithHeader(HttpKnownHeaderNames.ContentType, AdminRequestContentTypeJson)).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingsPost));
Given(Request.Create().WithPath(_adminPaths.Mappings).UsingDelete()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingsDelete));
// __admin/mappings/code
Given(Request.Create().WithPath(_adminPaths.MappingsCode).UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingsCodeGet));
// __admin/mappings/wiremock.org
Given(Request.Create().WithPath(_adminPaths.MappingsWireMockOrg).UsingPost().WithHeader(HttpKnownHeaderNames.ContentType, AdminRequestContentTypeJson)).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingsPostWireMockOrg));
// __admin/mappings/reset
Given(Request.Create().WithPath(_adminPaths.Mappings + "/reset").UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingsReset));
// __admin/mappings/reloadStaticMappings
Given(Request.Create().WithPath(_adminPaths.Mappings + "/reloadStaticMappings").UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(ReloadStaticMappings));
// __admin/mappings/{guid}
Given(Request.Create().WithPath(_adminPaths.MappingsGuidPathMatcher).UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingGet));
Given(Request.Create().WithPath(_adminPaths.MappingsGuidPathMatcher).UsingPut().WithHeader(HttpKnownHeaderNames.ContentType, AdminRequestContentTypeJson)).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingPut));
Given(Request.Create().WithPath(_adminPaths.MappingsGuidPathMatcher).UsingDelete()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingDelete));
// __admin/mappings/code/{guid}
Given(Request.Create().WithPath(_adminPaths.MappingsCodeGuidPathMatcher).UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingCodeGet));
// __admin/mappings/save
Given(Request.Create().WithPath($"{_adminPaths.Mappings}/save").UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingsSave));
// __admin/mappings/swagger
Given(Request.Create().WithPath($"{_adminPaths.Mappings}/swagger").UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(SwaggerGet));
// __admin/requests
Given(Request.Create().WithPath(_adminPaths.Requests).UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(RequestsGet));
Given(Request.Create().WithPath(_adminPaths.Requests).UsingDelete()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(RequestsDelete));
// __admin/requests/reset
Given(Request.Create().WithPath(_adminPaths.Requests + "/reset").UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(RequestsDelete));
// __admin/request/{guid}
Given(Request.Create().WithPath(_adminPaths.RequestsGuidPathMatcher).UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(RequestGet));
Given(Request.Create().WithPath(_adminPaths.RequestsGuidPathMatcher).UsingDelete()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(RequestDelete));
// __admin/requests/find
Given(Request.Create().WithPath(_adminPaths.Requests + "/find").UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(RequestsFind));
Given(Request.Create().WithPath(_adminPaths.Requests + "/find").UsingGet().WithParam("mappingGuid", new NotNullOrEmptyMatcher())).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(RequestsFindByMappingGuid));
// __admin/scenarios
Given(Request.Create().WithPath(_adminPaths.Scenarios).UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(ScenariosGet));
Given(Request.Create().WithPath(_adminPaths.Scenarios).UsingDelete()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(ScenariosReset));
Given(Request.Create().WithPath(_adminPaths.ScenariosNameMatcher).UsingDelete()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(ScenarioReset));
// __admin/scenarios/reset
Given(Request.Create().WithPath(_adminPaths.Scenarios + "/reset").UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(ScenariosReset));
Given(Request.Create().WithPath(_adminPaths.ScenariosNameWithResetMatcher).UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(ScenarioReset));
// __admin/files/{filename}
Given(Request.Create().WithPath(_adminPaths.FilesFilenamePathMatcher).UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(FilePost));
Given(Request.Create().WithPath(_adminPaths.FilesFilenamePathMatcher).UsingPut()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(FilePut));
Given(Request.Create().WithPath(_adminPaths.FilesFilenamePathMatcher).UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(FileGet));
Given(Request.Create().WithPath(_adminPaths.FilesFilenamePathMatcher).UsingHead()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(FileHead));
Given(Request.Create().WithPath(_adminPaths.FilesFilenamePathMatcher).UsingDelete()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(FileDelete));
// __admin/openapi
Given(Request.Create().WithPath($"{_adminPaths.OpenApi}/convert").UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(OpenApiConvertToMappings));
Given(Request.Create().WithPath($"{_adminPaths.OpenApi}/save").UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(OpenApiSaveToMappings));
// __admin/protodefinitions/{id}
Given(Request.Create().WithPath(_adminPaths.ProtoDefinitionsIdPathMatcher).UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(ProtoDefinitionAdd));
}
#endregion
#region StaticMappings
/// <inheritdoc cref="IWireMockServer.SaveStaticMappings" />
[PublicAPI]
public void SaveStaticMappings(string? folder = null)
{
_mappingBuilder.SaveMappingsToFolder(folder);
}
/// <inheritdoc cref="IWireMockServer.ReadStaticMappings" />
[PublicAPI]
public void ReadStaticMappings(string? folder = null)
{
if (folder == null)
{
folder = _settings.FileSystemHandler.GetMappingFolder();
}
if (!_settings.FileSystemHandler.FolderExists(folder))
{
_settings.Logger.Info("The Static Mapping folder '{0}' does not exist, reading Static MappingFiles will be skipped.", folder);
return;
}
foreach (var filename in _settings.FileSystemHandler.EnumerateFiles(folder, _settings.WatchStaticMappingsInSubdirectories == true).OrderBy(f => f))
{
_settings.Logger.Info("Reading Static MappingFile : '{0}'.", filename);
try
{
ReadStaticMappingAndAddOrUpdate(filename);
}
catch (Exception exception)
{
_settings.Logger.Error($"Static MappingFile : '{filename}' could not be read. This file will be skipped.", exception);
}
}
}
/// <inheritdoc cref="IWireMockServer.WatchStaticMappings" />
[PublicAPI]
public void WatchStaticMappings(string? folder = null)
{
if (folder == null)
{
folder = _settings.FileSystemHandler.GetMappingFolder();
}
if (!_settings.FileSystemHandler.FolderExists(folder))
{
return;
}
bool includeSubdirectories = _settings.WatchStaticMappingsInSubdirectories == true;
string includeSubdirectoriesText = includeSubdirectories ? " and Subdirectories" : string.Empty;
_settings.Logger.Info($"Watching folder '{folder}'{includeSubdirectoriesText} for new, updated and deleted MappingFiles.");
DisposeEnhancedFileSystemWatcher();
_enhancedFileSystemWatcher = new EnhancedFileSystemWatcher(folder, "*.json", EnhancedFileSystemWatcherTimeoutMs)
{
IncludeSubdirectories = includeSubdirectories
};
_enhancedFileSystemWatcher.Created += EnhancedFileSystemWatcherCreated;
_enhancedFileSystemWatcher.Changed += EnhancedFileSystemWatcherChanged;
_enhancedFileSystemWatcher.Deleted += EnhancedFileSystemWatcherDeleted;
_enhancedFileSystemWatcher.EnableRaisingEvents = true;
}
/// <inheritdoc cref="IWireMockServer.WatchStaticMappings" />
[PublicAPI]
public bool ReadStaticMappingAndAddOrUpdate(string path)
{
Guard.NotNull(path);
string filenameWithoutExtension = Path.GetFileNameWithoutExtension(path);
if (FileHelper.TryReadMappingFileWithRetryAndDelay(_settings.FileSystemHandler, path, out var value))
{
var mappingModels = DeserializeJsonToArray<MappingModel>(value);
if (mappingModels.Length == 1 && Guid.TryParse(filenameWithoutExtension, out var guidFromFilename))
{
ConvertMappingAndRegisterAsRespondProvider(mappingModels[0], guidFromFilename, path);
}
else
{
ConvertMappingsAndRegisterAsRespondProvider(mappingModels, path);
}
return true;
}
return false;
}
#endregion
#region Health
private static IResponseMessage HealthGet(IRequestMessage requestMessage)
{
return new ResponseMessage
{
BodyData = new BodyData
{
DetectedBodyType = BodyType.String,
BodyAsString = "Healthy"
},
StatusCode = (int)HttpStatusCode.OK,
Headers = new Dictionary<string, WireMockList<string>> { { HttpKnownHeaderNames.ContentType, new WireMockList<string>(WireMockConstants.ContentTypeTextPlain) } }
};
}
#endregion
#region Settings
private IResponseMessage SettingsGet(IRequestMessage requestMessage)
{
var model = new SettingsModel
{
AllowBodyForAllHttpMethods = _settings.AllowBodyForAllHttpMethods,
AllowOnlyDefinedHttpStatusCodeInResponse = _settings.AllowOnlyDefinedHttpStatusCodeInResponse,
AllowPartialMapping = _settings.AllowPartialMapping,
DisableDeserializeFormUrlEncoded = _settings.DisableDeserializeFormUrlEncoded,
DisableJsonBodyParsing = _settings.DisableJsonBodyParsing,
DisableRequestBodyDecompressing = _settings.DisableRequestBodyDecompressing,
DoNotSaveDynamicResponseInLogEntry = _settings.DoNotSaveDynamicResponseInLogEntry,
GlobalProcessingDelay = (int?)_options.RequestProcessingDelay?.TotalMilliseconds,
// GraphQLSchemas TODO
HandleRequestsSynchronously = _settings.HandleRequestsSynchronously,
HostingScheme = _settings.HostingScheme,
MaxRequestLogCount = _settings.MaxRequestLogCount,
ProtoDefinitions = _settings.ProtoDefinitions,
QueryParameterMultipleValueSupport = _settings.QueryParameterMultipleValueSupport,
ReadStaticMappings = _settings.ReadStaticMappings,
RequestLogExpirationDuration = _settings.RequestLogExpirationDuration,
SaveUnmatchedRequests = _settings.SaveUnmatchedRequests,
UseRegexExtended = _settings.UseRegexExtended,
WatchStaticMappings = _settings.WatchStaticMappings,
WatchStaticMappingsInSubdirectories = _settings.WatchStaticMappingsInSubdirectories,
#if USE_ASPNETCORE
AcceptAnyClientCertificate = _settings.AcceptAnyClientCertificate,
ClientCertificateMode = _settings.ClientCertificateMode,
CorsPolicyOptions = _settings.CorsPolicyOptions?.ToString()
#endif
};
model.ProxyAndRecordSettings = TinyMapperUtils.Instance.Map(_settings.ProxyAndRecordSettings);
return ToJson(model);
}
private IResponseMessage SettingsUpdate(IRequestMessage requestMessage)
{
var settings = DeserializeObject<SettingsModel>(requestMessage);
// _settings
_settings.AllowBodyForAllHttpMethods = settings.AllowBodyForAllHttpMethods;
_settings.AllowOnlyDefinedHttpStatusCodeInResponse = settings.AllowOnlyDefinedHttpStatusCodeInResponse;
_settings.AllowPartialMapping = settings.AllowPartialMapping;
_settings.DisableDeserializeFormUrlEncoded = settings.DisableDeserializeFormUrlEncoded;
_settings.DisableJsonBodyParsing = settings.DisableJsonBodyParsing;
_settings.DisableRequestBodyDecompressing = settings.DisableRequestBodyDecompressing;
_settings.DoNotSaveDynamicResponseInLogEntry = settings.DoNotSaveDynamicResponseInLogEntry;
_settings.HandleRequestsSynchronously = settings.HandleRequestsSynchronously;
_settings.MaxRequestLogCount = settings.MaxRequestLogCount;
_settings.ProtoDefinitions = settings.ProtoDefinitions;
_settings.ProxyAndRecordSettings = TinyMapperUtils.Instance.Map(settings.ProxyAndRecordSettings);
_settings.QueryParameterMultipleValueSupport = settings.QueryParameterMultipleValueSupport;
_settings.ReadStaticMappings = settings.ReadStaticMappings;
_settings.RequestLogExpirationDuration = settings.RequestLogExpirationDuration;
_settings.SaveUnmatchedRequests = settings.SaveUnmatchedRequests;
_settings.UseRegexExtended = settings.UseRegexExtended;
_settings.WatchStaticMappings = settings.WatchStaticMappings;
_settings.WatchStaticMappingsInSubdirectories = settings.WatchStaticMappingsInSubdirectories;
InitSettings(_settings);
#if USE_ASPNETCORE
if (Enum.TryParse<CorsPolicyOptions>(settings.CorsPolicyOptions, true, out var corsPolicyOptions))
{
_settings.CorsPolicyOptions = corsPolicyOptions;
}
#endif
WireMockMiddlewareOptionsHelper.InitFromSettings(_settings, _options, o =>
{
if (settings.GlobalProcessingDelay != null)
{
o.RequestProcessingDelay = TimeSpan.FromMilliseconds(settings.GlobalProcessingDelay.Value);
}
#if USE_ASPNETCORE
o.CorsPolicyOptions = corsPolicyOptions;
o.ClientCertificateMode = _settings.ClientCertificateMode;
o.AcceptAnyClientCertificate = _settings.AcceptAnyClientCertificate;
#endif
});
return ResponseMessageBuilder.Create(200, "Settings updated");
}
#endregion Settings
#region Mapping/{guid}
private IResponseMessage MappingGet(IRequestMessage requestMessage)
{
var mapping = FindMappingByGuid(requestMessage);
if (mapping == null)
{
_settings.Logger.Warn("HttpStatusCode set to 404 : Mapping not found");
return ResponseMessageBuilder.Create(HttpStatusCode.NotFound, "Mapping not found");
}
var model = _mappingConverter.ToMappingModel(mapping);
return ToJson(model);
}
private IResponseMessage MappingCodeGet(IRequestMessage requestMessage)
{
if (TryParseGuidFromRequestMessage(requestMessage, out var guid))
{
var code = _mappingBuilder.ToCSharpCode(guid, GetEnumFromQuery(requestMessage, MappingConverterType.Server));
if (code is null)
{
_settings.Logger.Warn("HttpStatusCode set to 404 : Mapping not found");
return ResponseMessageBuilder.Create(HttpStatusCode.NotFound, "Mapping not found");
}
return ToResponseMessage(code);
}
_settings.Logger.Warn("HttpStatusCode set to 400");
return ResponseMessageBuilder.Create(HttpStatusCode.BadRequest, "GUID is missing");
}
private static TEnum GetEnumFromQuery<TEnum>(IRequestMessage requestMessage, TEnum defaultValue)
where TEnum : struct
{
if (requestMessage.QueryIgnoreCase?.TryGetValue(typeof(TEnum).Name, out var values) == true &&
Enum.TryParse<TEnum>(values.FirstOrDefault(), true, out var parsed))
{
return parsed;
}
return defaultValue;
}
private IMapping? FindMappingByGuid(IRequestMessage requestMessage)
{
return TryParseGuidFromRequestMessage(requestMessage, out var guid) ? Mappings.FirstOrDefault(m => !m.IsAdminInterface && m.Guid == guid) : null;
}
private IResponseMessage MappingPut(IRequestMessage requestMessage)
{
if (TryParseGuidFromRequestMessage(requestMessage, out var guid))
{
var mappingModel = DeserializeObject<MappingModel>(requestMessage);
var guidFromPut = ConvertMappingAndRegisterAsRespondProvider(mappingModel, guid);
return ResponseMessageBuilder.Create(HttpStatusCode.OK, "Mapping added or updated", guidFromPut);
}
_settings.Logger.Warn("HttpStatusCode set to 404 : Mapping not found");
return ResponseMessageBuilder.Create(HttpStatusCode.NotFound, "Mapping not found");
}
private IResponseMessage MappingDelete(IRequestMessage requestMessage)
{
if (TryParseGuidFromRequestMessage(requestMessage, out var guid) && DeleteMapping(guid))
{
return ResponseMessageBuilder.Create(HttpStatusCode.OK, "Mapping removed", guid);
}
_settings.Logger.Warn("HttpStatusCode set to 404 : Mapping not found");
return ResponseMessageBuilder.Create(HttpStatusCode.NotFound, "Mapping not found");
}
private static bool TryParseGuidFromRequestMessage(IRequestMessage requestMessage, out Guid guid)
{
var lastPart = requestMessage.Path.Split('/').LastOrDefault();
return Guid.TryParse(lastPart, out guid);
}
#endregion Mapping/{guid}
#region Mappings
private IResponseMessage SwaggerGet(IRequestMessage requestMessage)
{
return new ResponseMessage
{
BodyData = new BodyData
{
DetectedBodyType = BodyType.String,
BodyAsString = SwaggerMapper.ToSwagger(this)
},
StatusCode = (int)HttpStatusCode.OK,
Headers = new Dictionary<string, WireMockList<string>> { { HttpKnownHeaderNames.ContentType, new WireMockList<string>(WireMockConstants.ContentTypeJson) } }
};
}
private IResponseMessage MappingsSave(IRequestMessage requestMessage)
{
SaveStaticMappings();
return ResponseMessageBuilder.Create(200, "Mappings saved to disk");
}
private MappingModel[] ToMappingModels()
{
return _mappingBuilder.GetMappings();
}
private IResponseMessage MappingsGet(IRequestMessage requestMessage)
{
return ToJson(ToMappingModels());
}
private IResponseMessage MappingsCodeGet(IRequestMessage requestMessage)
{
var converterType = GetEnumFromQuery(requestMessage, MappingConverterType.Server);
var code = _mappingBuilder.ToCSharpCode(converterType);
return ToResponseMessage(code);
}
private IResponseMessage MappingsPost(IRequestMessage requestMessage)
{
try
{
var mappingModels = DeserializeRequestMessageToArray<MappingModel>(requestMessage);
if (mappingModels.Length == 1)
{
var guid = ConvertMappingAndRegisterAsRespondProvider(mappingModels[0]);
return ResponseMessageBuilder.Create(201, "Mapping added", guid);
}
ConvertMappingsAndRegisterAsRespondProvider(mappingModels);
return ResponseMessageBuilder.Create(201, "Mappings added");
}
catch (ArgumentException a)
{
_settings.Logger.Error("HttpStatusCode set to 400 {0}", a);
return ResponseMessageBuilder.Create(400, a.Message);
}
catch (Exception e)
{
_settings.Logger.Error("HttpStatusCode set to 500 {0}", e);
return ResponseMessageBuilder.Create(500, e.ToString());
}
}
private IResponseMessage MappingsDelete(IRequestMessage requestMessage)
{
if (!string.IsNullOrEmpty(requestMessage.Body))
{
var deletedGuids = MappingsDeleteMappingFromBody(requestMessage);
if (deletedGuids != null)
{
return ResponseMessageBuilder.Create(200, $"Mappings deleted. Affected GUIDs: [{string.Join(", ", deletedGuids.ToArray())}]");
}
// return bad request
return ResponseMessageBuilder.Create(400, "Poorly formed mapping JSON.");
}
ResetMappings();
ResetScenarios();
return ResponseMessageBuilder.Create(200, "Mappings deleted");
}
private IEnumerable<Guid>? MappingsDeleteMappingFromBody(IRequestMessage requestMessage)
{
var deletedGuids = new List<Guid>();
try
{
var mappingModels = DeserializeRequestMessageToArray<MappingModel>(requestMessage);
foreach (var guid in mappingModels.Where(mm => mm.Guid.HasValue).Select(mm => mm.Guid!.Value))
{
if (DeleteMapping(guid))
{
deletedGuids.Add(guid);
}
else
{
_settings.Logger.Debug($"Did not find/delete mapping with GUID: {guid}.");
}
}
}
catch (ArgumentException a)
{
_settings.Logger.Error("ArgumentException: {0}", a);
return null;
}
catch (Exception e)
{
_settings.Logger.Error("Exception: {0}", e);
return null;
}
return deletedGuids;
}
private IResponseMessage MappingsReset(IRequestMessage requestMessage)
{
ResetMappings();
ResetScenarios();
var message = "Mappings reset";
if (requestMessage.Query != null &&
requestMessage.Query.ContainsKey(QueryParamReloadStaticMappings) &&
bool.TryParse(requestMessage.Query[QueryParamReloadStaticMappings].ToString(), out var reloadStaticMappings) &&
reloadStaticMappings)
{
ReadStaticMappings();
message += " and static mappings reloaded";
}
return ResponseMessageBuilder.Create(200, message);
}
private IResponseMessage ReloadStaticMappings(IRequestMessage _)
{
ReadStaticMappings();
return ResponseMessageBuilder.Create(200, "Static Mappings reloaded");
}
#endregion Mappings
#region Request/{guid}
private IResponseMessage RequestGet(IRequestMessage requestMessage)
{
if (TryParseGuidFromRequestMessage(requestMessage, out var guid))
{
var entry = LogEntries.SingleOrDefault(r => !r.RequestMessage.Path.StartsWith("/__admin/") && r.Guid == guid);
if (entry is { })
{
var model = new LogEntryMapper(_options).Map(entry);
return ToJson(model);
}
}
_settings.Logger.Warn("HttpStatusCode set to 404 : Request not found");
return ResponseMessageBuilder.Create(HttpStatusCode.NotFound, "Request not found");
}
private IResponseMessage RequestDelete(IRequestMessage requestMessage)
{
if (TryParseGuidFromRequestMessage(requestMessage, out var guid) && DeleteLogEntry(guid))
{
return ResponseMessageBuilder.Create(200, "Request removed");
}
_settings.Logger.Warn("HttpStatusCode set to 404 : Request not found");
return ResponseMessageBuilder.Create(HttpStatusCode.NotFound, "Request not found");
}
#endregion Request/{guid}
#region Requests
private IResponseMessage RequestsGet(IRequestMessage requestMessage)
{
var logEntryMapper = new LogEntryMapper(_options);
var result = LogEntries
.Where(r => !r.RequestMessage.Path.StartsWith("/__admin/"))
.Select(logEntryMapper.Map);
return ToJson(result);
}
private IResponseMessage RequestsDelete(IRequestMessage requestMessage)
{
ResetLogEntries();
return ResponseMessageBuilder.Create(200, "Requests deleted");
}
#endregion Requests
#region Requests/find
private IResponseMessage RequestsFind(IRequestMessage requestMessage)
{
var requestModel = DeserializeObject<RequestModel>(requestMessage);
var request = (Request)InitRequestBuilder(requestModel);
var dict = new Dictionary<ILogEntry, RequestMatchResult>();
foreach (var logEntry in LogEntries.Where(le => !le.RequestMessage.Path.StartsWith("/__admin/")))
{
var requestMatchResult = new RequestMatchResult();
if (request.GetMatchingScore(logEntry.RequestMessage, requestMatchResult) > MatchScores.AlmostPerfect)
{
dict.Add(logEntry, requestMatchResult);
}
}
var logEntryMapper = new LogEntryMapper(_options);
var result = dict.OrderBy(x => x.Value.AverageTotalScore).Select(x => x.Key).Select(logEntryMapper.Map);
return ToJson(result);
}
private IResponseMessage RequestsFindByMappingGuid(IRequestMessage requestMessage)
{
if (requestMessage.Query != null &&
requestMessage.Query.TryGetValue("mappingGuid", out var value) &&
Guid.TryParse(value.ToString(), out var mappingGuid)
)
{
var logEntries = LogEntries.Where(le => !le.RequestMessage.Path.StartsWith("/__admin/") && le.MappingGuid == mappingGuid);
var logEntryMapper = new LogEntryMapper(_options);
var result = logEntries.Select(logEntryMapper.Map);
return ToJson(result);
}
return ResponseMessageBuilder.Create(HttpStatusCode.BadRequest);
}
#endregion Requests/find
#region Scenarios
private IResponseMessage ScenariosGet(IRequestMessage requestMessage)
{
var scenariosStates = Scenarios.Values.Select(s => new ScenarioStateModel
{
Name = s.Name,
NextState = s.NextState,
Started = s.Started,
Finished = s.Finished,
Counter = s.Counter
});
return ToJson(scenariosStates, true);
}
private IResponseMessage ScenariosReset(IRequestMessage requestMessage)
{
ResetScenarios();
return ResponseMessageBuilder.Create(200, "Scenarios reset");
}
private IResponseMessage ScenarioReset(IRequestMessage requestMessage)
{
var name = string.Equals(HttpRequestMethod.DELETE, requestMessage.Method, StringComparison.OrdinalIgnoreCase) ?
requestMessage.Path.Substring(_adminPaths!.Scenarios.Length + 1) :
requestMessage.Path.Split('/').Reverse().Skip(1).First();
return ResetScenario(name) ?
ResponseMessageBuilder.Create(200, "Scenario reset") :
ResponseMessageBuilder.Create(HttpStatusCode.NotFound, $"No scenario found by name '{name}'.");
}
#endregion
#region Pact
/// <summary>
/// Save the mappings as a Pact Json file V2.
/// </summary>
/// <param name="folder">The folder to save the pact file.</param>
/// <param name="filename">The filename for the .json file [optional].</param>
[PublicAPI]
public void SavePact(string folder, string? filename = null)
{
var (filenameUpdated, bytes) = PactMapper.ToPact(this, filename);
_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>
/// This stores details about the consumer of the interaction.
/// </summary>
/// <param name="consumer">the consumer</param>
[PublicAPI]
public WireMockServer WithConsumer(string consumer)
{
Consumer = consumer;
return this;
}
/// <summary>
/// This stores details about the provider of the interaction.
/// </summary>
/// <param name="provider">the provider</param>
[PublicAPI]
public WireMockServer WithProvider(string provider)
{
Provider = provider;
return this;
}
#endregion
private void DisposeEnhancedFileSystemWatcher()
{
if (_enhancedFileSystemWatcher != null)
{
_enhancedFileSystemWatcher.EnableRaisingEvents = false;
_enhancedFileSystemWatcher.Created -= EnhancedFileSystemWatcherCreated;
_enhancedFileSystemWatcher.Changed -= EnhancedFileSystemWatcherChanged;
_enhancedFileSystemWatcher.Deleted -= EnhancedFileSystemWatcherDeleted;
_enhancedFileSystemWatcher.Dispose();
}
}
private void EnhancedFileSystemWatcherCreated(object sender, FileSystemEventArgs args)
{
_settings.Logger.Info("MappingFile created : '{0}', reading file.", args.FullPath);
if (!ReadStaticMappingAndAddOrUpdate(args.FullPath))
{
_settings.Logger.Error("Unable to read MappingFile '{0}'.", args.FullPath);
}
}
private void EnhancedFileSystemWatcherChanged(object sender, FileSystemEventArgs args)
{
_settings.Logger.Info("MappingFile updated : '{0}', reading file.", args.FullPath);
if (!ReadStaticMappingAndAddOrUpdate(args.FullPath))
{
_settings.Logger.Error("Unable to read MappingFile '{0}'.", args.FullPath);
}
}
private void EnhancedFileSystemWatcherDeleted(object sender, FileSystemEventArgs args)
{
_settings.Logger.Info("MappingFile deleted : '{0}'", args.FullPath);
var filenameWithoutExtension = Path.GetFileNameWithoutExtension(args.FullPath);
if (Guid.TryParse(filenameWithoutExtension, out var guidFromFilename))
{
DeleteMapping(guidFromFilename);
}
else
{
DeleteMapping(args.FullPath);
}
}
private static Encoding? ToEncoding(EncodingModel? encodingModel)
{
return encodingModel != null ? Encoding.GetEncoding(encodingModel.CodePage) : null;
}
private static ResponseMessage ToJson<T>(T result, bool keepNullValues = false, object? statusCode = null)
{
return new ResponseMessage
{
BodyData = new BodyData
{
DetectedBodyType = BodyType.String,
BodyAsString = JsonConvert.SerializeObject(result, keepNullValues ? JsonSerializationConstants.JsonSerializerSettingsIncludeNullValues : JsonSerializationConstants.JsonSerializerSettingsDefault)
},
StatusCode = statusCode ?? (int)HttpStatusCode.OK,
Headers = new Dictionary<string, WireMockList<string>> { { HttpKnownHeaderNames.ContentType, new WireMockList<string>(WireMockConstants.ContentTypeJson) } }
};
}
private static ResponseMessage ToResponseMessage(string text)
{
return new ResponseMessage
{
BodyData = new BodyData
{
DetectedBodyType = BodyType.String,
BodyAsString = text
},
StatusCode = (int)HttpStatusCode.OK,
Headers = new Dictionary<string, WireMockList<string>> { { HttpKnownHeaderNames.ContentType, new WireMockList<string>(WireMockConstants.ContentTypeTextPlain) } }
};
}
private static T DeserializeObject<T>(IRequestMessage requestMessage) where T : new()
{
switch (requestMessage.BodyData?.DetectedBodyType)
{
case BodyType.String:
case BodyType.FormUrlEncoded:
return JsonUtils.DeserializeObject<T>(requestMessage.BodyData.BodyAsString!);
case BodyType.Json when requestMessage.BodyData?.BodyAsJson != null:
return ((JObject)requestMessage.BodyData.BodyAsJson).ToObject<T>()!;
default:
throw new NotSupportedException();
}
}
private static T[] DeserializeRequestMessageToArray<T>(IRequestMessage requestMessage)
{
if (requestMessage.BodyData?.DetectedBodyType == BodyType.Json && requestMessage.BodyData.BodyAsJson != null)
{
var bodyAsJson = requestMessage.BodyData.BodyAsJson;
return DeserializeObjectToArray<T>(bodyAsJson);
}
throw new NotSupportedException();
}
private static T[] DeserializeJsonToArray<T>(string value)
{
return DeserializeObjectToArray<T>(JsonUtils.DeserializeObject(value));
}
private static T[] DeserializeObjectToArray<T>(object value)
{
if (value is JArray jArray)
{
return jArray.ToObject<T[]>()!;
}
var singleResult = ((JObject)value).ToObject<T>();
return new[] { singleResult! };
}
}

View File

@@ -0,0 +1,141 @@
// Copyright © WireMock.Net
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using WireMock.Types;
using WireMock.Util;
namespace WireMock.Server;
public partial class WireMockServer
{
private static readonly Encoding[] FileBodyIsString = [Encoding.UTF8, Encoding.ASCII];
#region ProtoDefinitions/{id}
private IResponseMessage ProtoDefinitionAdd(IRequestMessage requestMessage)
{
if (requestMessage.Body is null)
{
return ResponseMessageBuilder.Create(HttpStatusCode.BadRequest, "Body is null");
}
var id = requestMessage.Path.Split('/').Last();
AddProtoDefinition(id, requestMessage.Body);
return ResponseMessageBuilder.Create(HttpStatusCode.OK, "ProtoDefinition added");
}
#endregion
#region Files/{filename}
private IResponseMessage FilePost(IRequestMessage requestMessage)
{
if (requestMessage.BodyAsBytes is null)
{
return ResponseMessageBuilder.Create(HttpStatusCode.BadRequest, "Body is null");
}
var filename = GetFileNameFromRequestMessage(requestMessage);
var mappingFolder = _settings.FileSystemHandler.GetMappingFolder();
if (!_settings.FileSystemHandler.FolderExists(mappingFolder))
{
_settings.FileSystemHandler.CreateFolder(mappingFolder);
}
_settings.FileSystemHandler.WriteFile(filename, requestMessage.BodyAsBytes);
return ResponseMessageBuilder.Create(HttpStatusCode.OK, "File created");
}
private IResponseMessage FilePut(IRequestMessage requestMessage)
{
if (requestMessage.BodyAsBytes is null)
{
return ResponseMessageBuilder.Create(HttpStatusCode.BadRequest, "Body is null");
}
var filename = GetFileNameFromRequestMessage(requestMessage);
if (!_settings.FileSystemHandler.FileExists(filename))
{
_settings.Logger.Info("The file '{0}' does not exist, updating file will be skipped.", filename);
return ResponseMessageBuilder.Create(HttpStatusCode.NotFound, "File is not found");
}
_settings.FileSystemHandler.WriteFile(filename, requestMessage.BodyAsBytes);
return ResponseMessageBuilder.Create(HttpStatusCode.OK, "File updated");
}
private IResponseMessage FileGet(IRequestMessage requestMessage)
{
var filename = GetFileNameFromRequestMessage(requestMessage);
if (!_settings.FileSystemHandler.FileExists(filename))
{
_settings.Logger.Info("The file '{0}' does not exist.", filename);
return ResponseMessageBuilder.Create(HttpStatusCode.NotFound, "File is not found");
}
var bytes = _settings.FileSystemHandler.ReadFile(filename);
var response = new ResponseMessage
{
StatusCode = 200,
BodyData = new BodyData
{
BodyAsBytes = bytes,
DetectedBodyType = BodyType.Bytes,
DetectedBodyTypeFromContentType = BodyType.None
}
};
if (BytesEncodingUtils.TryGetEncoding(bytes, out var encoding) && FileBodyIsString.Select(x => x.Equals(encoding)).Any())
{
response.BodyData.DetectedBodyType = BodyType.String;
response.BodyData.BodyAsString = encoding.GetString(bytes);
}
return response;
}
/// <summary>
/// Checks if file exists.
/// Note: Response is returned with no body as a head request doesn't accept a body, only the status code.
/// </summary>
/// <param name="requestMessage">The request message.</param>
private IResponseMessage FileHead(IRequestMessage requestMessage)
{
var filename = GetFileNameFromRequestMessage(requestMessage);
if (!_settings.FileSystemHandler.FileExists(filename))
{
_settings.Logger.Info("The file '{0}' does not exist.", filename);
return ResponseMessageBuilder.Create(HttpStatusCode.NotFound);
}
return ResponseMessageBuilder.Create(HttpStatusCode.NoContent);
}
private IResponseMessage FileDelete(IRequestMessage requestMessage)
{
var filename = GetFileNameFromRequestMessage(requestMessage);
if (!_settings.FileSystemHandler.FileExists(filename))
{
_settings.Logger.Info("The file '{0}' does not exist.", filename);
return ResponseMessageBuilder.Create(HttpStatusCode.NotFound, "File is not deleted");
}
_settings.FileSystemHandler.DeleteFile(filename);
return ResponseMessageBuilder.Create(HttpStatusCode.OK, "File deleted.");
}
private string GetFileNameFromRequestMessage(IRequestMessage requestMessage)
{
return Path.GetFileName(requestMessage.Path.Substring(_adminPaths!.Files.Length + 1));
}
#endregion
}

View File

@@ -0,0 +1,397 @@
// Copyright © WireMock.Net
using System;
using System.Collections.Generic;
using System.Linq;
using Stef.Validation;
using WireMock.Admin.Mappings;
using WireMock.Matchers;
using WireMock.RequestBuilders;
using WireMock.ResponseBuilders;
using WireMock.Serialization;
using WireMock.Settings;
using WireMock.Types;
using WireMock.Util;
namespace WireMock.Server;
public partial class WireMockServer
{
private void ConvertMappingsAndRegisterAsRespondProvider(IReadOnlyList<MappingModel> mappingModels, string? path = null)
{
var duplicateGuids = mappingModels
.Where(m => m.Guid != null)
.GroupBy(m => m.Guid)
.Where(g => g.Count() > 1)
.Select(g => $"'{g.Key}'")
.ToArray();
if (duplicateGuids.Any())
{
throw new ArgumentException($"The following Guids are duplicate : {string.Join(",", duplicateGuids)}", nameof(mappingModels));
}
foreach (var mappingModel in mappingModels)
{
ConvertMappingAndRegisterAsRespondProvider(mappingModel, null, path);
}
}
private Guid ConvertMappingAndRegisterAsRespondProvider(MappingModel mappingModel, Guid? guid = null, string? path = null)
{
Guard.NotNull(mappingModel);
Guard.NotNull(mappingModel.Request);
Guard.NotNull(mappingModel.Response);
var request = (Request)InitRequestBuilder(mappingModel.Request, mappingModel);
var respondProvider = Given(request, mappingModel.SaveToFile == true);
if (guid != null)
{
respondProvider = respondProvider.WithGuid(guid.Value);
}
else if (mappingModel.Guid != null && mappingModel.Guid != Guid.Empty)
{
respondProvider = respondProvider.WithGuid(mappingModel.Guid.Value);
}
if (mappingModel.Data != null)
{
respondProvider = respondProvider.WithData(mappingModel.Data);
}
var timeSettings = TimeSettingsMapper.Map(mappingModel.TimeSettings);
if (timeSettings != null)
{
respondProvider = respondProvider.WithTimeSettings(timeSettings);
}
if (path != null)
{
respondProvider = respondProvider.WithPath(path);
}
if (!string.IsNullOrEmpty(mappingModel.Title))
{
respondProvider = respondProvider.WithTitle(mappingModel.Title!);
}
if (mappingModel.Priority != null)
{
respondProvider = respondProvider.AtPriority(mappingModel.Priority.Value);
}
if (mappingModel.Scenario != null)
{
respondProvider = respondProvider.InScenario(mappingModel.Scenario);
if (!string.IsNullOrEmpty(mappingModel.WhenStateIs))
{
respondProvider = respondProvider.WhenStateIs(mappingModel.WhenStateIs!);
}
if (!string.IsNullOrEmpty(mappingModel.SetStateTo))
{
respondProvider = respondProvider.WillSetStateTo(mappingModel.SetStateTo!);
}
}
if (mappingModel.Webhook != null)
{
respondProvider = respondProvider.WithWebhook(WebhookMapper.Map(mappingModel.Webhook));
}
else if (mappingModel.Webhooks?.Length > 1)
{
var webhooks = mappingModel.Webhooks.Select(WebhookMapper.Map).ToArray();
respondProvider = respondProvider.WithWebhook(webhooks);
}
if (mappingModel.UseWebhooksFireAndForget == true)
{
respondProvider.WithWebhookFireAndForget(mappingModel.UseWebhooksFireAndForget.Value);
}
if (mappingModel.Probability != null)
{
respondProvider.WithProbability(mappingModel.Probability.Value);
}
// ProtoDefinition is defined at Mapping level
if (mappingModel.ProtoDefinition != null)
{
respondProvider.WithProtoDefinition(mappingModel.ProtoDefinition);
}
else if (mappingModel.ProtoDefinitions != null)
{
respondProvider.WithProtoDefinition(mappingModel.ProtoDefinitions);
}
var responseBuilder = InitResponseBuilder(mappingModel.Response);
respondProvider.RespondWith(responseBuilder);
return respondProvider.Guid;
}
private IRequestBuilder InitRequestBuilder(RequestModel requestModel, MappingModel? mappingModel = null)
{
var requestBuilder = Request.Create();
if (requestModel.ClientIP != null)
{
if (requestModel.ClientIP is string clientIP)
{
requestBuilder = requestBuilder.WithClientIP(clientIP);
}
else
{
var clientIPModel = JsonUtils.ParseJTokenToObject<ClientIPModel>(requestModel.ClientIP);
if (clientIPModel.Matchers != null)
{
requestBuilder = requestBuilder.WithPath(clientIPModel.Matchers.Select(_matcherMapper.Map).OfType<IStringMatcher>().ToArray());
}
}
}
if (requestModel.Path != null)
{
if (requestModel.Path is string path)
{
requestBuilder = requestBuilder.WithPath(path);
}
else
{
var pathModel = JsonUtils.ParseJTokenToObject<PathModel>(requestModel.Path);
if (pathModel.Matchers != null)
{
var matchOperator = StringUtils.ParseMatchOperator(pathModel.MatchOperator);
requestBuilder = requestBuilder.WithPath(matchOperator, pathModel.Matchers.Select(_matcherMapper.Map).OfType<IStringMatcher>().ToArray());
}
}
}
else if (requestModel.Url != null)
{
if (requestModel.Url is string url)
{
requestBuilder = requestBuilder.WithUrl(url);
}
else
{
var urlModel = JsonUtils.ParseJTokenToObject<UrlModel>(requestModel.Url);
if (urlModel.Matchers != null)
{
var matchOperator = StringUtils.ParseMatchOperator(urlModel.MatchOperator);
requestBuilder = requestBuilder.WithUrl(matchOperator, urlModel.Matchers.Select(_matcherMapper.Map).OfType<IStringMatcher>().ToArray());
}
}
}
if (requestModel.Methods != null)
{
var matchBehaviour = requestModel.MethodsRejectOnMatch == true ? MatchBehaviour.RejectOnMatch : MatchBehaviour.AcceptOnMatch;
var matchOperator = StringUtils.ParseMatchOperator(requestModel.MethodsMatchOperator);
requestBuilder = requestBuilder.UsingMethod(matchBehaviour, matchOperator, requestModel.Methods);
}
if (requestModel.HttpVersion != null)
{
requestBuilder = requestBuilder.WithHttpVersion(requestModel.HttpVersion);
}
if (requestModel.Headers != null)
{
foreach (var headerModel in requestModel.Headers.Where(h => h.Matchers != null))
{
var matchOperator = StringUtils.ParseMatchOperator(headerModel.MatchOperator);
requestBuilder = requestBuilder.WithHeader(
headerModel.Name,
headerModel.IgnoreCase == true,
headerModel.RejectOnMatch == true ? MatchBehaviour.RejectOnMatch : MatchBehaviour.AcceptOnMatch,
matchOperator,
headerModel.Matchers!.Select(_matcherMapper.Map).OfType<IStringMatcher>().ToArray()
);
}
}
if (requestModel.Cookies != null)
{
foreach (var cookieModel in requestModel.Cookies.Where(c => c.Matchers != null))
{
requestBuilder = requestBuilder.WithCookie(
cookieModel.Name,
cookieModel.IgnoreCase == true,
cookieModel.RejectOnMatch == true ? MatchBehaviour.RejectOnMatch : MatchBehaviour.AcceptOnMatch,
cookieModel.Matchers!.Select(_matcherMapper.Map).OfType<IStringMatcher>().ToArray());
}
}
if (requestModel.Params != null)
{
foreach (var paramModel in requestModel.Params.Where(p => p is { Matchers: not null }))
{
var ignoreCase = paramModel.IgnoreCase == true;
requestBuilder = requestBuilder.WithParam(paramModel.Name, ignoreCase, paramModel.Matchers!.Select(_matcherMapper.Map).OfType<IStringMatcher>().ToArray());
}
}
if (requestModel.Body?.Matcher != null)
{
var bodyMatcher = _matcherMapper.Map(requestModel.Body.Matcher)!;
#if PROTOBUF
// If the BodyMatcher is a ProtoBufMatcher, and if ProtoDefinition is defined on Mapping-level, set the ProtoDefinition from that Mapping.
if (bodyMatcher is ProtoBufMatcher protoBufMatcher && mappingModel?.ProtoDefinition != null)
{
protoBufMatcher.ProtoDefinition = () => ProtoDefinitionHelper.GetIdOrTexts(_settings, mappingModel.ProtoDefinition);
}
#endif
requestBuilder = requestBuilder.WithBody(bodyMatcher);
}
else if (requestModel.Body?.Matchers != null)
{
var matchOperator = StringUtils.ParseMatchOperator(requestModel.Body.MatchOperator);
requestBuilder = requestBuilder.WithBody(_matcherMapper.Map(requestModel.Body.Matchers)!, matchOperator);
}
return requestBuilder;
}
private static IResponseBuilder InitResponseBuilder(ResponseModel responseModel)
{
var responseBuilder = Response.Create();
if (responseModel.Delay > 0)
{
responseBuilder = responseBuilder.WithDelay(responseModel.Delay.Value);
}
else if (responseModel.MinimumRandomDelay >= 0 || responseModel.MaximumRandomDelay > 0)
{
responseBuilder = responseBuilder.WithRandomDelay(responseModel.MinimumRandomDelay ?? 0, responseModel.MaximumRandomDelay ?? 60_000);
}
if (responseModel.UseTransformer == true)
{
if (!Enum.TryParse<TransformerType>(responseModel.TransformerType, out var transformerType))
{
transformerType = TransformerType.Handlebars;
}
if (!Enum.TryParse<ReplaceNodeOptions>(responseModel.TransformerReplaceNodeOptions, out var replaceNodeOptions))
{
replaceNodeOptions = ReplaceNodeOptions.EvaluateAndTryToConvert;
}
responseBuilder = responseBuilder.WithTransformer(
transformerType,
responseModel.UseTransformerForBodyAsFile == true,
replaceNodeOptions
);
}
if (!string.IsNullOrEmpty(responseModel.ProxyUrl))
{
var proxyAndRecordSettings = new ProxyAndRecordSettings
{
Url = responseModel.ProxyUrl!,
ClientX509Certificate2ThumbprintOrSubjectName = responseModel.X509Certificate2ThumbprintOrSubjectName,
WebProxySettings = TinyMapperUtils.Instance.Map(responseModel.WebProxy),
ReplaceSettings = TinyMapperUtils.Instance.Map(responseModel.ProxyUrlReplaceSettings)
};
return responseBuilder.WithProxy(proxyAndRecordSettings);
}
if (responseModel.StatusCode is string statusCodeAsString)
{
responseBuilder = responseBuilder.WithStatusCode(statusCodeAsString);
}
else if (responseModel.StatusCode != null)
{
// Convert to Int32 because Newtonsoft deserializes an 'object' with a number value to a long.
responseBuilder = responseBuilder.WithStatusCode(Convert.ToInt32(responseModel.StatusCode));
}
if (responseModel.Headers != null)
{
foreach (var entry in responseModel.Headers)
{
if (entry.Value is string value)
{
responseBuilder.WithHeader(entry.Key, value);
}
else
{
var headers = JsonUtils.ParseJTokenToObject<string[]>(entry.Value);
responseBuilder.WithHeader(entry.Key, headers);
}
}
}
else if (responseModel.HeadersRaw != null)
{
foreach (var headerLine in responseModel.HeadersRaw.Split(["\n", "\r\n"], StringSplitOptions.RemoveEmptyEntries))
{
int indexColon = headerLine.IndexOf(":", StringComparison.Ordinal);
string key = headerLine.Substring(0, indexColon).TrimStart(' ', '\t');
string value = headerLine.Substring(indexColon + 1).TrimStart(' ', '\t');
responseBuilder = responseBuilder.WithHeader(key, value);
}
}
if (responseModel.TrailingHeaders != null)
{
foreach (var entry in responseModel.TrailingHeaders)
{
if (entry.Value is string value)
{
responseBuilder.WithTrailingHeader(entry.Key, value);
}
else
{
var headers = JsonUtils.ParseJTokenToObject<string[]>(entry.Value);
responseBuilder.WithTrailingHeader(entry.Key, headers);
}
}
}
if (responseModel.BodyAsBytes != null)
{
responseBuilder = responseBuilder.WithBody(responseModel.BodyAsBytes, responseModel.BodyDestination, ToEncoding(responseModel.BodyEncoding));
}
else if (responseModel.Body != null)
{
responseBuilder = responseBuilder.WithBody(responseModel.Body, responseModel.BodyDestination, ToEncoding(responseModel.BodyEncoding));
}
else if (responseModel.BodyAsJson != null)
{
if (responseModel.ProtoBufMessageType != null)
{
if (responseModel.ProtoDefinition != null)
{
responseBuilder = responseBuilder.WithBodyAsProtoBuf(responseModel.ProtoDefinition, responseModel.ProtoBufMessageType, responseModel.BodyAsJson);
}
else if (responseModel.ProtoDefinitions != null)
{
responseBuilder = responseBuilder.WithBodyAsProtoBuf(responseModel.ProtoDefinitions, responseModel.ProtoBufMessageType, responseModel.BodyAsJson);
}
else
{
// ProtoDefinition(s) is/are defined at Mapping/Server level
responseBuilder = responseBuilder.WithBodyAsProtoBuf(responseModel.ProtoBufMessageType, responseModel.BodyAsJson);
}
}
else
{
responseBuilder = responseBuilder.WithBodyAsJson(responseModel.BodyAsJson, ToEncoding(responseModel.BodyEncoding), responseModel.BodyAsJsonIndented == true);
}
}
else if (responseModel.BodyAsFile != null)
{
responseBuilder = responseBuilder.WithBodyFromFile(responseModel.BodyAsFile, responseModel.BodyAsFileIsCached == true);
}
if (responseModel.Fault != null && Enum.TryParse(responseModel.Fault.Type, out FaultType faultType))
{
responseBuilder.WithFault(faultType, responseModel.Fault.Percentage);
}
return responseBuilder;
}
}

View File

@@ -0,0 +1,42 @@
// Copyright © WireMock.Net
using System;
using JetBrains.Annotations;
using Stef.Validation;
using WireMock.Matchers.Request;
using WireMock.RequestBuilders;
namespace WireMock.Server;
public partial class WireMockServer
{
/// <summary>
/// Given
/// </summary>
/// <param name="requestMatcher">The request matcher.</param>
/// <param name="saveToFile">Optional boolean to indicate if this mapping should be saved as static mapping file.</param>
/// <returns>The <see cref="IRespondWithAProvider"/>.</returns>
[PublicAPI]
public IRespondWithAProvider Given(IRequestMatcher requestMatcher, bool saveToFile = false)
{
return _mappingBuilder.Given(requestMatcher, saveToFile);
}
/// <summary>
/// WhenRequest
/// </summary>
/// <param name="action">The action to use the fluent <see cref="IRequestBuilder"/>.</param>
/// <param name="saveToFile">Optional boolean to indicate if this mapping should be saved as static mapping file.</param>
/// <returns>The <see cref="IRespondWithAProvider"/>.</returns>
[PublicAPI]
public IRespondWithAProvider WhenRequest(Action<IRequestBuilder> action, bool saveToFile = false)
{
Guard.NotNull(action);
var requestBuilder = Request.Create();
action(requestBuilder);
return Given(requestBuilder, saveToFile);
}
}

View File

@@ -0,0 +1,313 @@
// Copyright © WireMock.Net
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 Stef.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(string path)
{
Guard.NotNull(path);
var filenameWithoutExtension = Path.GetFileNameWithoutExtension(path);
if (FileHelper.TryReadMappingFileWithRetryAndDelay(_settings.FileSystemHandler, path, out var value))
{
var mappings = DeserializeJsonToArray<OrgMapping>(value);
foreach (var mapping in mappings)
{
if (mappings.Length == 1 && Guid.TryParse(filenameWithoutExtension, out var guidFromFilename))
{
ConvertWireMockOrgMappingAndRegisterAsRespondProvider(mapping, guidFromFilename, path);
}
else
{
ConvertWireMockOrgMappingAndRegisterAsRespondProvider(mapping, null, path);
}
}
}
}
private IResponseMessage MappingsPostWireMockOrg(IRequestMessage requestMessage)
{
try
{
var mappingModels = DeserializeRequestMessageToArray<OrgMapping>(requestMessage);
if (mappingModels.Length == 1)
{
var guid = ConvertWireMockOrgMappingAndRegisterAsRespondProvider(mappingModels[0]);
return ResponseMessageBuilder.Create(201, "Mapping added", guid);
}
foreach (var mappingModel in mappingModels)
{
ConvertWireMockOrgMappingAndRegisterAsRespondProvider(mappingModel);
}
return ResponseMessageBuilder.Create(201, "Mappings added");
}
catch (ArgumentException a)
{
_settings.Logger.Error("HttpStatusCode set to 400 {0}", a);
return ResponseMessageBuilder.Create(400, a.Message);
}
catch (Exception e)
{
_settings.Logger.Error("HttpStatusCode set to 500 {0}", e);
return ResponseMessageBuilder.Create(500, e.ToString());
}
}
private Guid? ConvertWireMockOrgMappingAndRegisterAsRespondProvider(Org.Abstractions.Mapping 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.UrlPath);
}
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);
});
}
if (request.Cookies is JObject cookies)
{
ProcessWireMockOrgJObjectAndUseStringMatcher(cookies, (key, match) =>
{
requestBuilder = requestBuilder.WithCookie(key, match);
});
}
/*
"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);
});
}
}
var responseBuilder = Response.Create();
var response = mapping.Response;
if (response != null)
{
responseBuilder = responseBuilder.WithStatusCode(response.Status);
if (response.Headers is JObject responseHeaders)
{
var rb = responseBuilder;
ProcessWireMockOrgJObjectAndConvertToIDictionary(responseHeaders, headers =>
{
rb = rb.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>();
if (valueAsString == null)
{
// Skip if the item.Value is null or when the string value is null
continue;
}
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;
if (match == null)
{
continue;
}
var valueAsString = match.Value.Value<string>();
if (string.IsNullOrEmpty(valueAsString))
{
continue;
}
var matcher = ProcessAsStringMatcher(match, valueAsString!);
if (matcher != null)
{
action(key, matcher);
}
}
}
private static void ProcessWireMockOrgJObjectAndUseIMatcher(JObject items, Action<IMatcher> action)
{
if (items.First is not JProperty firstItem)
{
return;
}
IMatcher? matcher;
if (firstItem.Name == "equalToJson")
{
matcher = new JsonMatcher(firstItem.Value);
}
else
{
if ((firstItem.Value as JValue)?.Value is not string valueAsString)
{
return;
}
matcher = ProcessAsStringMatcher(firstItem, valueAsString);
}
if (matcher != null)
{
action(matcher);
}
}
private static IStringMatcher? ProcessAsStringMatcher(JProperty match, string valueAsString)
{
return match.Name switch
{
"contains" => new WildcardMatcher(valueAsString),
"matches" => new RegexMatcher(valueAsString),
"equalTo" => new ExactMatcher(valueAsString),
_ => null,
};
}
}

View File

@@ -0,0 +1,99 @@
// Copyright © WireMock.Net
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using JetBrains.Annotations;
using Stef.Validation;
using WireMock.Logging;
using WireMock.Matchers;
using WireMock.Matchers.Request;
namespace WireMock.Server;
public partial class WireMockServer
{
/// <inheritdoc />
[PublicAPI]
public event NotifyCollectionChangedEventHandler LogEntriesChanged
{
add => _logEntriesChanged += value;
remove => _logEntriesChanged -= value;
}
/// <inheritdoc />
[PublicAPI]
public IReadOnlyList<ILogEntry> LogEntries => _options.LogEntries.ToArray();
/// <inheritdoc />
[PublicAPI]
public IReadOnlyList<ILogEntry> FindLogEntries(params IRequestMatcher[] matchers)
{
Guard.NotNull(matchers);
var results = new Dictionary<ILogEntry, RequestMatchResult>();
var allLogEntries = LogEntries;
foreach (var log in allLogEntries)
{
var requestMatchResult = new RequestMatchResult();
foreach (var matcher in matchers)
{
matcher.GetMatchingScore(log.RequestMessage, requestMatchResult);
}
if (requestMatchResult.AverageTotalScore > MatchScores.AlmostPerfect)
{
results.Add(log, requestMatchResult);
}
}
return results
.OrderBy(x => x.Value)
.Select(x => x.Key)
.ToArray();
}
/// <inheritdoc cref="IWireMockServer.ResetLogEntries" />
[PublicAPI]
public void ResetLogEntries()
{
_options.LogEntries.Clear();
}
/// <inheritdoc cref="IWireMockServer.DeleteLogEntry" />
[PublicAPI]
public bool DeleteLogEntry(Guid guid)
{
// Check a LogEntry exists with the same GUID, if so, remove it.
var existing = _options.LogEntries.ToList().FirstOrDefault(m => m.Guid == guid);
if (existing != null)
{
_options.LogEntries.Remove(existing);
return true;
}
return false;
}
private NotifyCollectionChangedEventHandler? _logEntriesChanged;
private void LogEntries_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
if (_logEntriesChanged is { })
{
foreach (var handler in _logEntriesChanged.GetInvocationList())
{
try
{
handler.DynamicInvoke(this, e);
}
catch (Exception exception)
{
_options.Logger.Error("Error calling the LogEntriesChanged event handler: {0}", exception.Message);
}
}
}
}
}

View File

@@ -0,0 +1,56 @@
// Copyright © WireMock.Net
using System.Net;
#if OPENAPIPARSER
using System;
using System.Linq;
using WireMock.Net.OpenApiParser;
#endif
namespace WireMock.Server;
public partial class WireMockServer
{
private IResponseMessage OpenApiConvertToMappings(IRequestMessage requestMessage)
{
#if OPENAPIPARSER
try
{
var mappingModels = new WireMockOpenApiParser().FromText(requestMessage.Body!, out var diagnostic);
return diagnostic.Errors.Any() ? ToJson(diagnostic, false, HttpStatusCode.BadRequest) : ToJson(mappingModels);
}
catch (Exception e)
{
_settings.Logger.Error("HttpStatusCode set to {0} {1}", HttpStatusCode.BadRequest, e);
return ResponseMessageBuilder.Create(HttpStatusCode.BadRequest, e.Message);
}
#else
return ResponseMessageBuilder.Create(HttpStatusCode.BadRequest, "Not supported for .NETStandard 1.3 and .NET 4.6.x or lower.");
#endif
}
private IResponseMessage OpenApiSaveToMappings(IRequestMessage requestMessage)
{
#if OPENAPIPARSER
try
{
var mappingModels = new WireMockOpenApiParser().FromText(requestMessage.Body!, out var diagnostic);
if (diagnostic.Errors.Any())
{
return ToJson(diagnostic, false, HttpStatusCode.BadRequest);
}
ConvertMappingsAndRegisterAsRespondProvider(mappingModels);
return ResponseMessageBuilder.Create(HttpStatusCode.Created, "OpenApi document converted to Mappings");
}
catch (Exception e)
{
_settings.Logger.Error("HttpStatusCode set to {0} {1}", HttpStatusCode.BadRequest, e);
return ResponseMessageBuilder.Create(HttpStatusCode.BadRequest, e.Message);
}
#else
return ResponseMessageBuilder.Create(HttpStatusCode.BadRequest, "Not supported for .NETStandard 1.3 and .NET 4.6.x or lower.");
#endif
}
}

View File

@@ -0,0 +1,75 @@
// Copyright © WireMock.Net
using System;
using System.Net.Http;
using System.Threading.Tasks;
using WireMock.Constants;
using WireMock.Http;
using WireMock.Proxy;
using WireMock.RequestBuilders;
using WireMock.ResponseProviders;
using WireMock.Settings;
namespace WireMock.Server;
public partial class WireMockServer
{
private HttpClient? _httpClientForProxy;
private void InitProxyAndRecord(WireMockServerSettings settings)
{
if (settings.ProxyAndRecordSettings == null)
{
_httpClientForProxy = null;
DeleteMapping(ProxyMappingGuid);
return;
}
_httpClientForProxy = HttpClientBuilder.Build(settings.ProxyAndRecordSettings);
var proxyRespondProvider = Given(Request.Create().WithPath("/*").UsingAnyMethod()).WithGuid(ProxyMappingGuid).WithTitle("Default Proxy Mapping on /*");
if (settings.StartAdminInterface == true)
{
proxyRespondProvider.AtPriority(WireMockConstants.ProxyPriority);
}
if(settings.ProxyAndRecordSettings.ProxyAll)
{
proxyRespondProvider.AtPriority(int.MinValue);
}
proxyRespondProvider.RespondWith(new ProxyAsyncResponseProvider(ProxyAndRecordAsync, settings));
}
private async Task<IResponseMessage> ProxyAndRecordAsync(IRequestMessage requestMessage, WireMockServerSettings settings)
{
var requestUri = new Uri(requestMessage.Url);
var proxyUri = new Uri(settings.ProxyAndRecordSettings!.Url);
var proxyUriWithRequestPathAndQuery = new Uri(proxyUri, requestUri.PathAndQuery);
var proxyHelper = new ProxyHelper(settings);
var (responseMessage, mapping) = await proxyHelper.SendAsync(
null,
_settings.ProxyAndRecordSettings!,
_httpClientForProxy!,
requestMessage,
proxyUriWithRequestPathAndQuery.AbsoluteUri
).ConfigureAwait(false);
if (mapping != null)
{
if (settings.ProxyAndRecordSettings.SaveMapping)
{
_options.Mappings.TryAdd(mapping.Guid, mapping);
}
if (settings.ProxyAndRecordSettings.SaveMappingToFile)
{
_mappingToFileSaver.SaveMappingToFile(mapping);
}
}
return responseMessage;
}
}

View File

@@ -0,0 +1,713 @@
// Copyright © WireMock.Net and mock4net by Alexandre Victoor
// This source file is based on mock4net by Alexandre Victoor which is licensed under the Apache 2.0 License.
// For more details see 'mock4net/LICENSE.txt' and 'mock4net/readme.md' in this project root.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading;
using AnyOfTypes;
using JetBrains.Annotations;
using Newtonsoft.Json;
using Stef.Validation;
using WireMock.Admin.Mappings;
using WireMock.Authentication;
using WireMock.Constants;
using WireMock.Exceptions;
using WireMock.Handlers;
using WireMock.Http;
using WireMock.Logging;
using WireMock.Models;
using WireMock.Owin;
using WireMock.RequestBuilders;
using WireMock.ResponseProviders;
using WireMock.Serialization;
using WireMock.Settings;
using WireMock.Types;
using WireMock.Util;
namespace WireMock.Server;
/// <summary>
/// The fluent mock server.
/// </summary>
public partial class WireMockServer : IWireMockServer
{
private const int ServerStartDelayInMs = 100;
private readonly WireMockServerSettings _settings;
private readonly IOwinSelfHost? _httpServer;
private readonly IWireMockMiddlewareOptions _options = new WireMockMiddlewareOptions();
private readonly MappingConverter _mappingConverter;
private readonly MatcherMapper _matcherMapper;
private readonly MappingToFileSaver _mappingToFileSaver;
private readonly MappingBuilder _mappingBuilder;
private readonly IGuidUtils _guidUtils = new GuidUtils();
private readonly IDateTimeUtils _dateTimeUtils = new DateTimeUtils();
/// <inheritdoc />
[PublicAPI]
public bool IsStarted => _httpServer is { IsStarted: true };
/// <inheritdoc />
[PublicAPI]
public bool IsStartedWithAdminInterface => IsStarted && _settings.StartAdminInterface.GetValueOrDefault();
/// <inheritdoc />
[PublicAPI]
public List<int> Ports { get; }
/// <inheritdoc />
[PublicAPI]
public int Port => Ports?.FirstOrDefault() ?? default;
/// <inheritdoc />
[PublicAPI]
public string[] Urls { get; }
/// <inheritdoc />
[PublicAPI]
public string? Url => Urls?.FirstOrDefault();
/// <inheritdoc />
[PublicAPI]
public string? Consumer { get; private set; }
/// <inheritdoc />
[PublicAPI]
public string? Provider { get; private set; }
/// <summary>
/// Gets the mappings.
/// </summary>
[PublicAPI]
public IReadOnlyList<IMapping> Mappings => _options.Mappings.Values.ToArray();
/// <inheritdoc cref="IWireMockServer.MappingModels" />
[PublicAPI]
public IReadOnlyList<MappingModel> MappingModels => ToMappingModels();
/// <summary>
/// Gets the scenarios.
/// </summary>
[PublicAPI]
public ConcurrentDictionary<string, ScenarioState> Scenarios => new(_options.Scenarios);
#region IDisposable Members
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
_options.LogEntries.CollectionChanged -= LogEntries_CollectionChanged;
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Releases unmanaged and - optionally - managed resources.
/// </summary>
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
protected virtual void Dispose(bool disposing)
{
DisposeEnhancedFileSystemWatcher();
_httpServer?.StopAsync();
}
#endregion
#region HttpClient
/// <summary>
/// Create a <see cref="HttpClient"/> which can be used to call this instance.
/// <param name="handlers">
/// An ordered list of System.Net.Http.DelegatingHandler instances to be invoked
/// as an System.Net.Http.HttpRequestMessage travels from the System.Net.Http.HttpClient
/// to the network and an System.Net.Http.HttpResponseMessage travels from the network
/// back to System.Net.Http.HttpClient. The handlers are invoked in a top-down fashion.
/// That is, the first entry is invoked first for an outbound request message but
/// last for an inbound response message.
/// </param>
/// </summary>
[PublicAPI]
public HttpClient CreateClient(params DelegatingHandler[] handlers)
{
if (!IsStarted)
{
throw new InvalidOperationException("Unable to create HttpClient because the service is not started.");
}
var client = HttpClientFactory2.Create(handlers);
client.BaseAddress = new Uri(Url!);
return client;
}
/// <summary>
/// Create a <see cref="HttpClient"/> which can be used to call this instance.
/// <param name="handlers">
/// <param name="innerHandler">The inner handler represents the destination of the HTTP message channel.</param>
/// An ordered list of System.Net.Http.DelegatingHandler instances to be invoked
/// as an System.Net.Http.HttpRequestMessage travels from the System.Net.Http.HttpClient
/// to the network and an System.Net.Http.HttpResponseMessage travels from the network
/// back to System.Net.Http.HttpClient. The handlers are invoked in a top-down fashion.
/// That is, the first entry is invoked first for an outbound request message but
/// last for an inbound response message.
/// </param>
/// </summary>
[PublicAPI]
public HttpClient CreateClient(HttpMessageHandler innerHandler, params DelegatingHandler[] handlers)
{
if (!IsStarted)
{
throw new InvalidOperationException("Unable to create HttpClient because the service is not started.");
}
var client = HttpClientFactory2.Create(innerHandler, handlers);
client.BaseAddress = new Uri(Url!);
return client;
}
/// <summary>
/// Create <see cref="HttpClient"/>s (one for each URL) which can be used to call this instance.
/// <param name="innerHandler">The inner handler represents the destination of the HTTP message channel.</param>
/// <param name="handlers">
/// An ordered list of System.Net.Http.DelegatingHandler instances to be invoked
/// as an System.Net.Http.HttpRequestMessage travels from the System.Net.Http.HttpClient
/// to the network and an System.Net.Http.HttpResponseMessage travels from the network
/// back to System.Net.Http.HttpClient. The handlers are invoked in a top-down fashion.
/// That is, the first entry is invoked first for an outbound request message but
/// last for an inbound response message.
/// </param>
/// </summary>
[PublicAPI]
public HttpClient[] CreateClients(HttpMessageHandler innerHandler, params DelegatingHandler[] handlers)
{
if (!IsStarted)
{
throw new InvalidOperationException("Unable to create HttpClients because the service is not started.");
}
return Urls.Select(url =>
{
var client = HttpClientFactory2.Create(innerHandler, handlers);
client.BaseAddress = new Uri(url);
return client;
}).ToArray();
}
#endregion
#region Start/Stop
/// <summary>
/// Starts this WireMockServer with the specified settings.
/// </summary>
/// <param name="settings">The WireMockServerSettings.</param>
/// <returns>The <see cref="WireMockServer"/>.</returns>
[PublicAPI]
public static WireMockServer Start(WireMockServerSettings settings)
{
Guard.NotNull(settings);
return new WireMockServer(settings);
}
/// <summary>
/// Starts this WireMockServer with the specified settings.
/// </summary>
/// <param name="action">The action to configure the WireMockServerSettings.</param>
/// <returns>The <see cref="WireMockServer"/>.</returns>
[PublicAPI]
public static WireMockServer Start(Action<WireMockServerSettings> action)
{
Guard.NotNull(action);
var settings = new WireMockServerSettings();
action(settings);
return new WireMockServer(settings);
}
/// <summary>
/// Start this WireMockServer.
/// </summary>
/// <param name="port">The port.</param>
/// <param name="useSSL">The SSL support.</param>
/// <param name="useHttp2">Use HTTP 2 (needed for Grpc).</param>
/// <returns>The <see cref="WireMockServer"/>.</returns>
[PublicAPI]
public static WireMockServer Start(int? port = 0, bool useSSL = false, bool useHttp2 = false)
{
return new WireMockServer(new WireMockServerSettings
{
Port = port,
UseSSL = useSSL,
UseHttp2 = useHttp2
});
}
/// <summary>
/// Start this WireMockServer.
/// </summary>
/// <param name="urls">The urls to listen on.</param>
/// <returns>The <see cref="WireMockServer"/>.</returns>
[PublicAPI]
public static WireMockServer Start(params string[] urls)
{
Guard.NotNullOrEmpty(urls);
return new WireMockServer(new WireMockServerSettings
{
Urls = urls
});
}
/// <summary>
/// Start this WireMockServer with the admin interface.
/// </summary>
/// <param name="port">The port.</param>
/// <param name="useSSL">The SSL support.</param>
/// <param name="useHttp2">Use HTTP 2 (needed for Grpc).</param>
/// <returns>The <see cref="WireMockServer"/>.</returns>
[PublicAPI]
public static WireMockServer StartWithAdminInterface(int? port = 0, bool useSSL = false, bool useHttp2 = false)
{
return new WireMockServer(new WireMockServerSettings
{
Port = port,
UseSSL = useSSL,
UseHttp2 = useHttp2,
StartAdminInterface = true
});
}
/// <summary>
/// Start this WireMockServer with the admin interface.
/// </summary>
/// <param name="urls">The urls.</param>
/// <returns>The <see cref="WireMockServer"/>.</returns>
[PublicAPI]
public static WireMockServer StartWithAdminInterface(params string[] urls)
{
Guard.NotNullOrEmpty(urls);
return new WireMockServer(new WireMockServerSettings
{
Urls = urls,
StartAdminInterface = true
});
}
/// <summary>
/// Start this WireMockServer with the admin interface and read static mappings.
/// </summary>
/// <param name="urls">The urls.</param>
/// <returns>The <see cref="WireMockServer"/>.</returns>
[PublicAPI]
public static WireMockServer StartWithAdminInterfaceAndReadStaticMappings(params string[] urls)
{
Guard.NotNullOrEmpty(urls);
return new WireMockServer(new WireMockServerSettings
{
Urls = urls,
StartAdminInterface = true,
ReadStaticMappings = true
});
}
/// <summary>
/// Initializes a new instance of the <see cref="WireMockServer"/> class.
/// </summary>
/// <param name="settings">The settings.</param>
/// <exception cref="WireMockException">
/// Service start failed with error: {_httpServer.RunningException.Message}
/// or
/// Service start failed with error: {startTask.Exception.Message}
/// </exception>
/// <exception cref="TimeoutException">Service start timed out after {TimeSpan.FromMilliseconds(settings.StartTimeout)}</exception>
protected WireMockServer(WireMockServerSettings settings)
{
_settings = Guard.NotNull(settings);
// Set default values if not provided
_settings.Logger = settings.Logger ?? new WireMockNullLogger();
_settings.FileSystemHandler = settings.FileSystemHandler ?? new LocalFileSystemHandler();
_settings.Logger.Info("By Stef Heyenrath (https://github.com/wiremock/WireMock.Net)");
_settings.Logger.Debug("Server settings {0}", JsonConvert.SerializeObject(settings, Formatting.Indented));
HostUrlOptions urlOptions;
if (settings.Urls != null)
{
urlOptions = new HostUrlOptions
{
Urls = settings.Urls
};
}
else
{
if (settings.HostingScheme is not null)
{
urlOptions = new HostUrlOptions
{
HostingScheme = settings.HostingScheme.Value,
UseHttp2 = settings.UseHttp2,
Port = settings.Port
};
}
else
{
urlOptions = new HostUrlOptions
{
HostingScheme = settings.UseSSL == true ? HostingScheme.Https : HostingScheme.Http,
UseHttp2 = settings.UseHttp2,
Port = settings.Port
};
}
}
WireMockMiddlewareOptionsHelper.InitFromSettings(settings, _options, o =>
{
o.LogEntries.CollectionChanged += LogEntries_CollectionChanged;
});
_matcherMapper = new MatcherMapper(_settings);
_mappingConverter = new MappingConverter(_matcherMapper);
_mappingToFileSaver = new MappingToFileSaver(_settings, _mappingConverter);
_mappingBuilder = new MappingBuilder(
settings,
_options,
_mappingConverter,
_mappingToFileSaver,
_guidUtils,
_dateTimeUtils
);
#if USE_ASPNETCORE
_options.AdditionalServiceRegistration = _settings.AdditionalServiceRegistration;
_options.CorsPolicyOptions = _settings.CorsPolicyOptions;
_options.ClientCertificateMode = _settings.ClientCertificateMode;
_options.AcceptAnyClientCertificate = _settings.AcceptAnyClientCertificate;
_httpServer = new AspNetCoreSelfHost(_options, urlOptions);
#else
_httpServer = new OwinSelfHost(_options, urlOptions);
#endif
var startTask = _httpServer.StartAsync();
using (var ctsStartTimeout = new CancellationTokenSource(settings.StartTimeout))
{
while (!_httpServer.IsStarted)
{
// Throw exception if service start fails
if (_httpServer.RunningException != null)
{
throw new WireMockException($"Service start failed with error: {_httpServer.RunningException.Message}", _httpServer.RunningException);
}
if (ctsStartTimeout.IsCancellationRequested)
{
// In case of an aggregate exception, throw the exception.
if (startTask.Exception != null)
{
throw new WireMockException($"Service start failed with error: {startTask.Exception.Message}", startTask.Exception);
}
// Else throw TimeoutException
throw new TimeoutException($"Service start timed out after {TimeSpan.FromMilliseconds(settings.StartTimeout)}");
}
ctsStartTimeout.Token.WaitHandle.WaitOne(ServerStartDelayInMs);
}
Urls = _httpServer.Urls.ToArray();
Ports = _httpServer.Ports;
}
InitSettings(settings);
}
/// <inheritdoc cref="IWireMockServer.Stop" />
[PublicAPI]
public void Stop()
{
var result = _httpServer?.StopAsync();
result?.Wait(); // wait for stop to actually happen
}
#endregion
/// <inheritdoc cref="IWireMockServer.AddCatchAllMapping" />
[PublicAPI]
public void AddCatchAllMapping()
{
Given(Request.Create().WithPath("/*").UsingAnyMethod())
.WithGuid(Guid.Parse("90008000-0000-4444-a17e-669cd84f1f05"))
.AtPriority(1000)
.RespondWith(new DynamicResponseProvider(_ => ResponseMessageBuilder.Create(HttpStatusCode.NotFound, WireMockConstants.NoMatchingFound)));
}
/// <inheritdoc cref="IWireMockServer.Reset" />
[PublicAPI]
public void Reset()
{
ResetLogEntries();
ResetScenarios();
ResetMappings();
}
/// <inheritdoc cref="IWireMockServer.ResetMappings" />
[PublicAPI]
public void ResetMappings()
{
foreach (var nonAdmin in _options.Mappings.ToArray().Where(m => !m.Value.IsAdminInterface))
{
_options.Mappings.TryRemove(nonAdmin.Key, out _);
}
}
/// <inheritdoc cref="IWireMockServer.DeleteMapping" />
[PublicAPI]
public bool DeleteMapping(Guid guid)
{
// Check a mapping exists with the same GUID, if so, remove it.
if (_options.Mappings.ContainsKey(guid))
{
return _options.Mappings.TryRemove(guid, out _);
}
return false;
}
private bool DeleteMapping(string path)
{
// Check a mapping exists with the same path, if so, remove it.
var mapping = _options.Mappings.ToArray().FirstOrDefault(entry => string.Equals(entry.Value.Path, path, StringComparison.OrdinalIgnoreCase));
return DeleteMapping(mapping.Key);
}
/// <inheritdoc cref="IWireMockServer.AddGlobalProcessingDelay" />
[PublicAPI]
public void AddGlobalProcessingDelay(TimeSpan delay)
{
_options.RequestProcessingDelay = delay;
}
/// <inheritdoc cref="IWireMockServer.AllowPartialMapping" />
[PublicAPI]
public void AllowPartialMapping(bool allow = true)
{
_settings.Logger.Info("AllowPartialMapping is set to {0}", allow);
_options.AllowPartialMapping = allow;
}
/// <inheritdoc cref="IWireMockServer.SetAzureADAuthentication(string, string)" />
[PublicAPI]
public void SetAzureADAuthentication(string tenant, string audience)
{
Guard.NotNull(tenant);
Guard.NotNull(audience);
#if NETSTANDARD1_3
throw new NotSupportedException("AzureADAuthentication is not supported for NETStandard 1.3");
#else
_options.AuthenticationMatcher = new AzureADAuthenticationMatcher(
new System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler(),
new Microsoft.IdentityModel.Protocols.ConfigurationManager<Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectConfiguration>($"https://login.microsoftonline.com/{tenant}/.well-known/openid-configuration", new Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectConfigurationRetriever()),
tenant,
audience);
#endif
}
/// <inheritdoc cref="IWireMockServer.SetBasicAuthentication(string, string)" />
[PublicAPI]
public void SetBasicAuthentication(string username, string password)
{
Guard.NotNull(username);
Guard.NotNull(password);
_options.AuthenticationMatcher = new BasicAuthenticationMatcher(username, password);
}
/// <inheritdoc cref="IWireMockServer.RemoveAuthentication" />
[PublicAPI]
public void RemoveAuthentication()
{
_options.AuthenticationMatcher = null;
}
/// <inheritdoc cref="IWireMockServer.SetMaxRequestLogCount" />
[PublicAPI]
public void SetMaxRequestLogCount(int? maxRequestLogCount)
{
_options.MaxRequestLogCount = maxRequestLogCount;
}
/// <inheritdoc cref="IWireMockServer.SetRequestLogExpirationDuration" />
[PublicAPI]
public void SetRequestLogExpirationDuration(int? requestLogExpirationDuration)
{
_options.RequestLogExpirationDuration = requestLogExpirationDuration;
}
/// <inheritdoc cref="IWireMockServer.ResetScenarios" />
[PublicAPI]
public void ResetScenarios()
{
_options.Scenarios.Clear();
}
/// <inheritdoc />
[PublicAPI]
public bool ResetScenario(string name)
{
return _options.Scenarios.ContainsKey(name) && _options.Scenarios.TryRemove(name, out _);
}
/// <inheritdoc cref="IWireMockServer.WithMapping(MappingModel[])" />
[PublicAPI]
public IWireMockServer WithMapping(params MappingModel[] mappings)
{
foreach (var mapping in mappings)
{
ConvertMappingAndRegisterAsRespondProvider(mapping, mapping.Guid ?? Guid.NewGuid());
}
return this;
}
/// <inheritdoc cref="IWireMockServer.WithMapping(string)" />
[PublicAPI]
public IWireMockServer WithMapping(string mappings)
{
var mappingModels = DeserializeJsonToArray<MappingModel>(mappings);
foreach (var mappingModel in mappingModels)
{
ConvertMappingAndRegisterAsRespondProvider(mappingModel, mappingModel.Guid ?? Guid.NewGuid());
}
return this;
}
/// <summary>
/// Add a Grpc ProtoDefinition at server-level.
/// </summary>
/// <param name="id">Unique identifier for the ProtoDefinition.</param>
/// <param name="protoDefinition">The ProtoDefinition as text.</param>
/// <returns><see cref="WireMockServer"/></returns>
[PublicAPI]
public WireMockServer AddProtoDefinition(string id, params string[] protoDefinition)
{
Guard.NotNullOrWhiteSpace(id);
Guard.NotNullOrEmpty(protoDefinition);
_settings.ProtoDefinitions ??= new Dictionary<string, string[]>();
if (_settings.ProtoDefinitions.TryGetValue(id, out var existingProtoDefinitions))
{
_settings.ProtoDefinitions[id] = existingProtoDefinitions.Union(protoDefinition).ToArray();
}
else
{
_settings.ProtoDefinitions[id] = protoDefinition;
}
return this;
}
/// <summary>
/// Add a GraphQL Schema at server-level.
/// </summary>
/// <param name="id">Unique identifier for the GraphQL Schema.</param>
/// <param name="graphQLSchema">The GraphQL Schema as string or StringPattern.</param>
/// <param name="customScalars">A dictionary defining the custom scalars used in this schema. [optional]</param>
/// <returns><see cref="WireMockServer"/></returns>
[PublicAPI]
public WireMockServer AddGraphQLSchema(string id, AnyOf<string, StringPattern> graphQLSchema, Dictionary<string, Type>? customScalars = null)
{
Guard.NotNullOrWhiteSpace(id);
Guard.NotNullOrWhiteSpace(graphQLSchema);
_settings.GraphQLSchemas ??= new Dictionary<string, GraphQLSchemaDetails>();
_settings.GraphQLSchemas[id] = new GraphQLSchemaDetails
{
SchemaAsString = graphQLSchema,
CustomScalars = customScalars
};
return this;
}
/// <inheritdoc />
[PublicAPI]
public string? MappingToCSharpCode(Guid guid, MappingConverterType converterType)
{
return _mappingBuilder.ToCSharpCode(guid, converterType);
}
/// <inheritdoc />
[PublicAPI]
public string MappingsToCSharpCode(MappingConverterType converterType)
{
return _mappingBuilder.ToCSharpCode(converterType);
}
private void InitSettings(WireMockServerSettings settings)
{
if (settings.AllowBodyForAllHttpMethods == true)
{
_settings.Logger.Info("AllowBodyForAllHttpMethods is set to True");
}
if (settings.AllowOnlyDefinedHttpStatusCodeInResponse == true)
{
_settings.Logger.Info("AllowOnlyDefinedHttpStatusCodeInResponse is set to True");
}
if (settings.AllowPartialMapping == true)
{
AllowPartialMapping();
}
if (settings.StartAdminInterface == true)
{
if (!string.IsNullOrEmpty(settings.AdminUsername) && !string.IsNullOrEmpty(settings.AdminPassword))
{
SetBasicAuthentication(settings.AdminUsername!, settings.AdminPassword!);
}
if (!string.IsNullOrEmpty(settings.AdminAzureADTenant) && !string.IsNullOrEmpty(settings.AdminAzureADAudience))
{
SetAzureADAuthentication(settings.AdminAzureADTenant!, settings.AdminAzureADAudience!);
}
InitAdmin();
}
if (settings.ReadStaticMappings == true)
{
ReadStaticMappings();
}
if (settings.WatchStaticMappings == true)
{
WatchStaticMappings();
}
InitProxyAndRecord(settings);
if (settings.RequestLogExpirationDuration != null)
{
SetRequestLogExpirationDuration(settings.RequestLogExpirationDuration);
}
if (settings.MaxRequestLogCount != null)
{
SetMaxRequestLogCount(settings.MaxRequestLogCount);
}
}
}