mirror of
https://github.com/wiremock/WireMock.Net.git
synced 2026-03-28 20:01:46 +01:00
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:
255
src/WireMock.Net.Minimal/Server/IRespondWithAProvider.cs
Normal file
255
src/WireMock.Net.Minimal/Server/IRespondWithAProvider.cs
Normal 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);
|
||||
}
|
||||
404
src/WireMock.Net.Minimal/Server/RespondWithAProvider.cs
Normal file
404
src/WireMock.Net.Minimal/Server/RespondWithAProvider.cs
Normal 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
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
886
src/WireMock.Net.Minimal/Server/WireMockServer.Admin.cs
Normal file
886
src/WireMock.Net.Minimal/Server/WireMockServer.Admin.cs
Normal 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! };
|
||||
}
|
||||
}
|
||||
141
src/WireMock.Net.Minimal/Server/WireMockServer.AdminFiles.cs
Normal file
141
src/WireMock.Net.Minimal/Server/WireMockServer.AdminFiles.cs
Normal 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
|
||||
}
|
||||
397
src/WireMock.Net.Minimal/Server/WireMockServer.ConvertMapping.cs
Normal file
397
src/WireMock.Net.Minimal/Server/WireMockServer.ConvertMapping.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
42
src/WireMock.Net.Minimal/Server/WireMockServer.Fluent.cs
Normal file
42
src/WireMock.Net.Minimal/Server/WireMockServer.Fluent.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
99
src/WireMock.Net.Minimal/Server/WireMockServer.LogEntries.cs
Normal file
99
src/WireMock.Net.Minimal/Server/WireMockServer.LogEntries.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
75
src/WireMock.Net.Minimal/Server/WireMockServer.Proxy.cs
Normal file
75
src/WireMock.Net.Minimal/Server/WireMockServer.Proxy.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
713
src/WireMock.Net.Minimal/Server/WireMockServer.cs
Normal file
713
src/WireMock.Net.Minimal/Server/WireMockServer.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user