WebHook : UseFireAndForget + Delay (#803)

* UseFireAndForget

* ...

* delay

* async

* updated code accorsing to proposal

* Change nuget to package reference for WireMock.Net.Console.Net472.Classic, move the new FireAndForget into the main mapping, out of individual webhook mappings making it all or nothing, update tests, change Middleware to await or not the firing of all webhooks. Update models as needed. (#804)

Co-authored-by: Matt Philmon <Matt_Philmon@carmax.com>

* small update

* Tweak middleware and fix bug in example (#806)

Co-authored-by: Matt Philmon <Matt_Philmon@carmax.com>

* .ConfigureAwait(false)

Co-authored-by: mattisking <mattisking@gmail.com>
Co-authored-by: Matt Philmon <Matt_Philmon@carmax.com>
This commit is contained in:
Stef Heyenrath
2022-09-12 20:30:40 +02:00
committed by GitHub
parent 13a06b9b38
commit 98a0f2fa28
22 changed files with 670 additions and 460 deletions
@@ -6,6 +6,7 @@ using System.Threading.Tasks;
using Newtonsoft.Json; using Newtonsoft.Json;
using WireMock.Logging; using WireMock.Logging;
using WireMock.Matchers; using WireMock.Matchers;
using WireMock.Models;
using WireMock.RequestBuilders; using WireMock.RequestBuilders;
using WireMock.ResponseBuilders; using WireMock.ResponseBuilders;
using WireMock.Server; using WireMock.Server;
@@ -576,6 +577,40 @@ namespace WireMock.Net.ConsoleApplication
}; };
})); }));
server.Given(Request.Create().WithPath(new WildcardMatcher("/multi-webhook", true)).UsingPost())
.WithWebhook(new[] {
new Webhook()
{
Request = new WebhookRequest
{
Url = "http://localhost:12345/foo1",
Method = "post",
BodyData = new BodyData
{
BodyAsString = "OK 1!", DetectedBodyType = BodyType.String
},
Delay = 1000
}
},
new Webhook()
{
Request = new WebhookRequest
{
Url = "http://localhost:12345/foo2",
Method = "post",
BodyData = new BodyData
{
BodyAsString = "OK 2!",
DetectedBodyType = BodyType.String
},
MinimumRandomDelay = 3000,
MaximumRandomDelay = 7000
}
}
})
.WithWebhookFireAndForget(true)
.RespondWith(Response.Create().WithBody("a-response"));
System.Console.WriteLine(JsonConvert.SerializeObject(server.MappingModels, Formatting.Indented)); System.Console.WriteLine(JsonConvert.SerializeObject(server.MappingModels, Formatting.Indented));
System.Console.WriteLine("Press any key to stop the server"); System.Console.WriteLine("Press any key to stop the server");
@@ -342,15 +342,6 @@
<Reference Include="TinyMapper, Version=3.0.1.0, Culture=neutral, processorArchitecture=MSIL"> <Reference Include="TinyMapper, Version=3.0.1.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\TinyMapper.3.0.3\lib\net40\TinyMapper.dll</HintPath> <HintPath>..\..\packages\TinyMapper.3.0.3\lib\net40\TinyMapper.dll</HintPath>
</Reference> </Reference>
<Reference Include="WireMock.Net, Version=1.5.3.0, Culture=neutral, PublicKeyToken=c8d65537854e1f03, processorArchitecture=MSIL">
<HintPath>..\..\packages\WireMock.Net.1.5.3\lib\net461\WireMock.Net.dll</HintPath>
</Reference>
<Reference Include="WireMock.Net.Abstractions, Version=1.5.3.0, Culture=neutral, PublicKeyToken=c8d65537854e1f03, processorArchitecture=MSIL">
<HintPath>..\..\packages\WireMock.Net.Abstractions.1.5.3\lib\net451\WireMock.Net.Abstractions.dll</HintPath>
</Reference>
<Reference Include="WireMock.Org.Abstractions, Version=1.5.3.0, Culture=neutral, PublicKeyToken=c8d65537854e1f03, processorArchitecture=MSIL">
<HintPath>..\..\packages\WireMock.Org.Abstractions.1.5.3\lib\net45\WireMock.Org.Abstractions.dll</HintPath>
</Reference>
<Reference Include="XPath2, Version=1.1.3.0, Culture=neutral, PublicKeyToken=463c6d7fb740c7e5, processorArchitecture=MSIL"> <Reference Include="XPath2, Version=1.1.3.0, Culture=neutral, PublicKeyToken=463c6d7fb740c7e5, processorArchitecture=MSIL">
<HintPath>..\..\packages\XPath2.1.1.3\lib\net452\XPath2.dll</HintPath> <HintPath>..\..\packages\XPath2.1.1.3\lib\net452\XPath2.dll</HintPath>
</Reference> </Reference>
@@ -388,6 +379,16 @@
<None Include="App.config" /> <None Include="App.config" />
<None Include="packages.config" /> <None Include="packages.config" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\WireMock.Net.Abstractions\WireMock.Net.Abstractions.csproj">
<Project>{b6269aac-170a-4346-8b9a-579ded3d9a94}</Project>
<Name>WireMock.Net.Abstractions</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\WireMock.Net\WireMock.Net.csproj">
<Project>{d3804228-91f4-4502-9595-39584e5a01ad}</Project>
<Name>WireMock.Net</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild"> <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup> <PropertyGroup>
@@ -147,9 +147,6 @@
<package id="System.Threading.Tasks.Extensions" version="4.5.1" targetFramework="net472" /> <package id="System.Threading.Tasks.Extensions" version="4.5.1" targetFramework="net472" />
<package id="System.ValueTuple" version="4.5.0" targetFramework="net472" /> <package id="System.ValueTuple" version="4.5.0" targetFramework="net472" />
<package id="TinyMapper" version="3.0.3" targetFramework="net472" /> <package id="TinyMapper" version="3.0.3" targetFramework="net472" />
<package id="WireMock.Net" version="1.5.3" targetFramework="net472" />
<package id="WireMock.Net.Abstractions" version="1.5.3" targetFramework="net472" />
<package id="WireMock.Org.Abstractions" version="1.5.3" targetFramework="net472" />
<package id="XPath2" version="1.1.3" targetFramework="net472" /> <package id="XPath2" version="1.1.3" targetFramework="net472" />
<package id="XPath2.Extensions" version="1.1.3" targetFramework="net472" /> <package id="XPath2.Extensions" version="1.1.3" targetFramework="net472" />
</packages> </packages>
@@ -74,4 +74,9 @@ public class MappingModel
/// The Webhooks. /// The Webhooks.
/// </summary> /// </summary>
public WebhookModel[]? Webhooks { get; set; } public WebhookModel[]? Webhooks { get; set; }
/// <summary>
/// Fire and forget for webhooks.
/// </summary>
public bool? UseWebhooksFireAndForget { get; set; }
} }
@@ -1,14 +1,13 @@
namespace WireMock.Admin.Mappings namespace WireMock.Admin.Mappings;
/// <summary>
/// The Webhook
/// </summary>
[FluentBuilder.AutoGenerateBuilder]
public class WebhookModel
{ {
/// <summary> /// <summary>
/// The Webhook /// The Webhook Request.
/// </summary> /// </summary>
[FluentBuilder.AutoGenerateBuilder] public WebhookRequestModel Request { get; set; } = null!;
public class WebhookModel
{
/// <summary>
/// The Webhook Request.
/// </summary>
public WebhookRequestModel Request { get; set; }
}
} }
@@ -11,12 +11,12 @@ public class WebhookRequestModel
/// <summary> /// <summary>
/// Gets or sets the Url. /// Gets or sets the Url.
/// </summary> /// </summary>
public string Url { get; set; } public string Url { get; set; } = null!;
/// <summary> /// <summary>
/// The method /// The method
/// </summary> /// </summary>
public string Method { get; set; } public string Method { get; set; } = null!;
/// <summary> /// <summary>
/// Gets or sets the headers. /// Gets or sets the headers.
@@ -47,4 +47,19 @@ public class WebhookRequestModel
/// The ReplaceNodeOptions to use when transforming a JSON node. /// The ReplaceNodeOptions to use when transforming a JSON node.
/// </summary> /// </summary>
public string? TransformerReplaceNodeOptions { get; set; } public string? TransformerReplaceNodeOptions { get; set; }
/// <summary>
/// Gets or sets the delay in milliseconds.
/// </summary>
public int? Delay { get; set; }
/// <summary>
/// Gets or sets the minimum random delay in milliseconds.
/// </summary>
public int? MinimumRandomDelay { get; set; }
/// <summary>
/// Gets or sets the maximum random delay in milliseconds.
/// </summary>
public int? MaximumRandomDelay { get; set; }
} }
@@ -2,46 +2,60 @@ using System.Collections.Generic;
using WireMock.Types; using WireMock.Types;
using WireMock.Util; using WireMock.Util;
namespace WireMock.Models namespace WireMock.Models;
/// <summary>
/// IWebhookRequest
/// </summary>
public interface IWebhookRequest
{ {
/// <summary> /// <summary>
/// IWebhookRequest /// The Webhook Url.
/// </summary> /// </summary>
public interface IWebhookRequest string Url { get; set; }
{
/// <summary>
/// The Webhook Url.
/// </summary>
string Url { get; set; }
/// <summary> /// <summary>
/// The method to use. /// The method to use.
/// </summary> /// </summary>
string Method { get; set; } string Method { get; set; }
/// <summary> /// <summary>
/// The Headers to send. /// The Headers to send.
/// </summary> /// </summary>
IDictionary<string, WireMockList<string>>? Headers { get; } IDictionary<string, WireMockList<string>>? Headers { get; }
/// <summary> /// <summary>
/// The body to send. /// The body to send.
/// </summary> /// </summary>
IBodyData? BodyData { get; set; } IBodyData? BodyData { get; set; }
/// <summary> /// <summary>
/// Use Transformer. /// Use Transformer.
/// </summary> /// </summary>
bool? UseTransformer { get; set; } bool? UseTransformer { get; set; }
/// <summary> /// <summary>
/// The transformer type. /// The transformer type.
/// </summary> /// </summary>
TransformerType TransformerType { get; set; } TransformerType TransformerType { get; set; }
/// <summary> /// <summary>
/// The ReplaceNodeOptions to use when transforming a JSON node. /// The ReplaceNodeOptions to use when transforming a JSON node.
/// </summary> /// </summary>
ReplaceNodeOptions TransformerReplaceNodeOptions { get; set; } ReplaceNodeOptions TransformerReplaceNodeOptions { get; set; }
}
/// <summary>
/// Gets or sets the delay in milliseconds.
/// </summary>
int? Delay { get; set; }
/// <summary>
/// Gets or sets the minimum random delay in milliseconds.
/// </summary>
int? MinimumRandomDelay { get; set; }
/// <summary>
/// Gets or sets the maximum random delay in milliseconds.
/// </summary>
int? MaximumRandomDelay { get; set; }
} }
+43 -13
View File
@@ -1,7 +1,9 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Stef.Validation; using Stef.Validation;
using WireMock.Models; using WireMock.Models;
@@ -17,6 +19,7 @@ namespace WireMock.Http;
internal class WebhookSender internal class WebhookSender
{ {
private const string ClientIp = "::1"; private const string ClientIp = "::1";
private static readonly ThreadLocal<Random> Random = new(() => new Random(DateTime.UtcNow.Millisecond));
private readonly WireMockServerSettings _settings; private readonly WireMockServerSettings _settings;
@@ -25,20 +28,26 @@ internal class WebhookSender
_settings = Guard.NotNull(settings); _settings = Guard.NotNull(settings);
} }
public Task<HttpResponseMessage> SendAsync(HttpClient client, IMapping mapping, IWebhookRequest request, IRequestMessage originalRequestMessage, IResponseMessage originalResponseMessage) public async Task<HttpResponseMessage> SendAsync(
HttpClient client,
IMapping mapping,
IWebhookRequest webhookRequest,
IRequestMessage originalRequestMessage,
IResponseMessage originalResponseMessage
)
{ {
Guard.NotNull(client); Guard.NotNull(client);
Guard.NotNull(mapping); Guard.NotNull(mapping);
Guard.NotNull(request); Guard.NotNull(webhookRequest);
Guard.NotNull(originalRequestMessage); Guard.NotNull(originalRequestMessage);
Guard.NotNull(originalResponseMessage); Guard.NotNull(originalResponseMessage);
IBodyData? bodyData; IBodyData? bodyData;
IDictionary<string, WireMockList<string>>? headers; IDictionary<string, WireMockList<string>>? headers;
if (request.UseTransformer == true) if (webhookRequest.UseTransformer == true)
{ {
ITransformer responseMessageTransformer; ITransformer responseMessageTransformer;
switch (request.TransformerType) switch (webhookRequest.TransformerType)
{ {
case TransformerType.Handlebars: case TransformerType.Handlebars:
var factoryHandlebars = new HandlebarsContextFactory(_settings.FileSystemHandler, _settings.HandlebarsRegistrationCallback); var factoryHandlebars = new HandlebarsContextFactory(_settings.FileSystemHandler, _settings.HandlebarsRegistrationCallback);
@@ -47,26 +56,26 @@ internal class WebhookSender
case TransformerType.Scriban: case TransformerType.Scriban:
case TransformerType.ScribanDotLiquid: case TransformerType.ScribanDotLiquid:
var factoryDotLiquid = new ScribanContextFactory(_settings.FileSystemHandler, request.TransformerType); var factoryDotLiquid = new ScribanContextFactory(_settings.FileSystemHandler, webhookRequest.TransformerType);
responseMessageTransformer = new Transformer(factoryDotLiquid); responseMessageTransformer = new Transformer(factoryDotLiquid);
break; break;
default: default:
throw new NotImplementedException($"TransformerType '{request.TransformerType}' is not supported."); throw new NotImplementedException($"TransformerType '{webhookRequest.TransformerType}' is not supported.");
} }
(bodyData, headers) = responseMessageTransformer.Transform(mapping, originalRequestMessage, originalResponseMessage, request.BodyData, request.Headers, request.TransformerReplaceNodeOptions); (bodyData, headers) = responseMessageTransformer.Transform(mapping, originalRequestMessage, originalResponseMessage, webhookRequest.BodyData, webhookRequest.Headers, webhookRequest.TransformerReplaceNodeOptions);
} }
else else
{ {
bodyData = request.BodyData; bodyData = webhookRequest.BodyData;
headers = request.Headers; headers = webhookRequest.Headers;
} }
// Create RequestMessage // Create RequestMessage
var requestMessage = new RequestMessage( var requestMessage = new RequestMessage(
new UrlDetails(request.Url), new UrlDetails(webhookRequest.Url),
request.Method, webhookRequest.Method,
ClientIp, ClientIp,
bodyData, bodyData,
headers?.ToDictionary(x => x.Key, x => x.Value.ToArray()) headers?.ToDictionary(x => x.Key, x => x.Value.ToArray())
@@ -76,9 +85,30 @@ internal class WebhookSender
}; };
// Create HttpRequestMessage // Create HttpRequestMessage
var httpRequestMessage = HttpRequestMessageHelper.Create(requestMessage, request.Url); var httpRequestMessage = HttpRequestMessageHelper.Create(requestMessage, webhookRequest.Url);
// Delay (if required)
if (TryGetDelay(webhookRequest, out var delay))
{
await Task.Delay(delay.Value).ConfigureAwait(false);
}
// Call the URL // Call the URL
return client.SendAsync(httpRequestMessage); return await client.SendAsync(httpRequestMessage).ConfigureAwait(false);
}
private static bool TryGetDelay(IWebhookRequest webhookRequest, [NotNullWhen(true)] out int? delay)
{
delay = webhookRequest.Delay;
var minimumDelay = webhookRequest.MinimumRandomDelay;
var maximumDelay = webhookRequest.MaximumRandomDelay;
if (minimumDelay is not null && maximumDelay is not null && maximumDelay >= minimumDelay)
{
delay = Random.Value!.Next(minimumDelay.Value, maximumDelay.Value);
return true;
}
return delay is not null;
} }
} }
+5
View File
@@ -112,6 +112,11 @@ public interface IMapping
/// </summary> /// </summary>
IWebhook[]? Webhooks { get; } IWebhook[]? Webhooks { get; }
/// <summary>
/// Use Fire and Forget for the defined webhook(s). [Optional]
/// </summary>
public bool? UseWebhooksFireAndForget { get; set; }
/// <summary> /// <summary>
/// ProvideResponseAsync /// ProvideResponseAsync
/// </summary> /// </summary>
+6
View File
@@ -63,6 +63,9 @@ public class Mapping : IMapping
/// <inheritdoc /> /// <inheritdoc />
public IWebhook[]? Webhooks { get; } public IWebhook[]? Webhooks { get; }
/// <inheritdoc />
public bool? UseWebhooksFireAndForget { get; set; }
/// <inheritdoc /> /// <inheritdoc />
public ITimeSettings? TimeSettings { get; } public ITimeSettings? TimeSettings { get; }
@@ -82,6 +85,7 @@ public class Mapping : IMapping
/// <param name="nextState">The next state which will occur after the current mapping execution. [Optional]</param> /// <param name="nextState">The next state which will occur after the current mapping execution. [Optional]</param>
/// <param name="stateTimes">Only when the current state is executed this number, the next state which will occur. [Optional]</param> /// <param name="stateTimes">Only when the current state is executed this number, the next state which will occur. [Optional]</param>
/// <param name="webhooks">The Webhooks. [Optional]</param> /// <param name="webhooks">The Webhooks. [Optional]</param>
/// <param name="useWebhooksFireAndForget">Use Fire and Forget for the defined webhook(s). [Optional]</param>
/// <param name="timeSettings">The TimeSettings. [Optional]</param> /// <param name="timeSettings">The TimeSettings. [Optional]</param>
public Mapping( public Mapping(
Guid guid, Guid guid,
@@ -97,6 +101,7 @@ public class Mapping : IMapping
string? nextState, string? nextState,
int? stateTimes, int? stateTimes,
IWebhook[]? webhooks, IWebhook[]? webhooks,
bool? useWebhooksFireAndForget,
ITimeSettings? timeSettings) ITimeSettings? timeSettings)
{ {
Guid = guid; Guid = guid;
@@ -112,6 +117,7 @@ public class Mapping : IMapping
NextState = nextState; NextState = nextState;
StateTimes = stateTimes; StateTimes = stateTimes;
Webhooks = webhooks; Webhooks = webhooks;
UseWebhooksFireAndForget = useWebhooksFireAndForget;
TimeSettings = timeSettings; TimeSettings = timeSettings;
} }
@@ -21,7 +21,7 @@ public class RequestMessageParamMatcher : IRequestMatcher
/// <summary> /// <summary>
/// The key /// The key
/// </summary> /// </summary>
public string? Key { get; } public string Key { get; } = string.Empty;
/// <summary> /// <summary>
/// Defines if the key should be matched using case-ignore. /// Defines if the key should be matched using case-ignore.
+8 -9
View File
@@ -1,11 +1,10 @@
namespace WireMock.Models namespace WireMock.Models;
/// <summary>
/// Webhook
/// </summary>
public class Webhook : IWebhook
{ {
/// <summary> /// <inheritdoc />
/// Webhook public IWebhookRequest Request { get; set; } = null!;
/// </summary>
public class Webhook : IWebhook
{
/// <inheritdoc cref="IWebhook.Request"/>
public IWebhookRequest Request { get; set; }
}
} }
+29 -21
View File
@@ -2,32 +2,40 @@ using System.Collections.Generic;
using WireMock.Types; using WireMock.Types;
using WireMock.Util; using WireMock.Util;
namespace WireMock.Models namespace WireMock.Models;
/// <summary>
/// WebhookRequest
/// </summary>
public class WebhookRequest : IWebhookRequest
{ {
/// <summary> /// <inheritdoc />
/// WebhookRequest public string Url { get; set; } = null!;
/// </summary>
public class WebhookRequest : IWebhookRequest
{
/// <inheritdoc cref="IWebhookRequest.Url"/>
public string Url { get; set; }
/// <inheritdoc cref="IWebhookRequest.Method"/> /// <inheritdoc />
public string Method { get; set; } public string Method { get; set; } = null!;
/// <inheritdoc cref="IWebhookRequest.Headers"/> /// <inheritdoc />
public IDictionary<string, WireMockList<string>>? Headers { get; set; } public IDictionary<string, WireMockList<string>>? Headers { get; set; }
/// <inheritdoc cref="IWebhookRequest.BodyData"/> /// <inheritdoc />
public IBodyData? BodyData { get; set; } public IBodyData? BodyData { get; set; }
/// <inheritdoc cref="IWebhookRequest.UseTransformer"/> /// <inheritdoc />
public bool? UseTransformer { get; set; } public bool? UseTransformer { get; set; }
/// <inheritdoc cref="IWebhookRequest.TransformerType"/> /// <inheritdoc />
public TransformerType TransformerType { get; set; } public TransformerType TransformerType { get; set; }
/// <inheritdoc cref="IWebhookRequest.TransformerReplaceNodeOptions"/> /// <inheritdoc />
public ReplaceNodeOptions TransformerReplaceNodeOptions { get; set; } public ReplaceNodeOptions TransformerReplaceNodeOptions { get; set; }
}
/// <inheritdoc />
public int? Delay { get; set; }
/// <inheritdoc />
public int? MinimumRandomDelay { get; set; }
/// <inheritdoc />
public int? MaximumRandomDelay { get; set; }
} }
+31 -4
View File
@@ -11,8 +11,8 @@ using WireMock.Serialization;
using WireMock.Types; using WireMock.Types;
using WireMock.ResponseBuilders; using WireMock.ResponseBuilders;
using WireMock.Settings; using WireMock.Settings;
using System.Collections.Generic;
#if !USE_ASPNETCORE #if !USE_ASPNETCORE
using Microsoft.Owin;
using IContext = Microsoft.Owin.IOwinContext; using IContext = Microsoft.Owin.IOwinContext;
using OwinMiddleware = Microsoft.Owin.OwinMiddleware; using OwinMiddleware = Microsoft.Owin.OwinMiddleware;
using Next = Microsoft.Owin.OwinMiddleware; using Next = Microsoft.Owin.OwinMiddleware;
@@ -161,6 +161,7 @@ namespace WireMock.Owin
_options.Logger.Error($"Providing a Response for Mapping '{result.Match?.Mapping?.Guid}' failed. HttpStatusCode set to 500. Exception: {ex}"); _options.Logger.Error($"Providing a Response for Mapping '{result.Match?.Mapping?.Guid}' failed. HttpStatusCode set to 500. Exception: {ex}");
response = ResponseMessageBuilder.Create(ex.Message, 500); response = ResponseMessageBuilder.Create(ex.Message, 500);
} }
finally finally
{ {
var log = new LogEntry var log = new LogEntry
@@ -201,20 +202,46 @@ namespace WireMock.Owin
private async Task SendToWebhooksAsync(IMapping mapping, IRequestMessage request, IResponseMessage response) private async Task SendToWebhooksAsync(IMapping mapping, IRequestMessage request, IResponseMessage response)
{ {
var tasks = new List<Func<Task>>();
for (int index = 0; index < mapping.Webhooks?.Length; index++) for (int index = 0; index < mapping.Webhooks?.Length; index++)
{ {
var httpClientForWebhook = HttpClientBuilder.Build(mapping.Settings.WebhookSettings ?? new WebhookSettings()); var httpClientForWebhook = HttpClientBuilder.Build(mapping.Settings.WebhookSettings ?? new WebhookSettings());
var webhookSender = new WebhookSender(mapping.Settings); var webhookSender = new WebhookSender(mapping.Settings);
var webhookRequest = mapping.Webhooks[index].Request;
var webHookIndex = index;
tasks.Add(async () =>
{
try
{
await webhookSender.SendAsync(httpClientForWebhook, mapping, webhookRequest, request, response).ConfigureAwait(false);
}
catch (Exception ex)
{
_options.Logger.Error($"Sending message to Webhook [{webHookIndex}] from Mapping '{mapping.Guid}' failed. Exception: {ex}");
}
});
}
if (mapping.UseWebhooksFireAndForget == true)
{
try try
{ {
await webhookSender.SendAsync(httpClientForWebhook, mapping, mapping.Webhooks[index].Request, request, response).ConfigureAwait(false); // Do not wait
await Task.Run(() =>
{
Task.WhenAll(tasks.Select(async task => await task.Invoke())).ConfigureAwait(false);
});
} }
catch (Exception ex) catch
{ {
_options.Logger.Error($"Sending message to Webhook [{index}] from Mapping '{mapping.Guid}' failed. Exception: {ex}"); // Ignore
} }
} }
else
{
await Task.WhenAll(tasks.Select(async task => await task.Invoke())).ConfigureAwait(false);
}
} }
private void UpdateScenarioState(IMapping mapping) private void UpdateScenarioState(IMapping mapping)
+1
View File
@@ -119,6 +119,7 @@ internal class ProxyHelper
nextState: null, nextState: null,
stateTimes: null, stateTimes: null,
webhooks: null, webhooks: null,
useWebhooksFireAndForget: null,
timeSettings: null timeSettings: null
); );
} }
@@ -43,6 +43,7 @@ internal class MappingConverter
TimeSettings = TimeSettingsMapper.Map(mapping.TimeSettings), TimeSettings = TimeSettingsMapper.Map(mapping.TimeSettings),
Title = mapping.Title, Title = mapping.Title,
Description = mapping.Description, Description = mapping.Description,
UseWebhooksFireAndForget = mapping.UseWebhooksFireAndForget,
Priority = mapping.Priority != 0 ? mapping.Priority : null, Priority = mapping.Priority != 0 ? mapping.Priority : null,
Scenario = mapping.Scenario, Scenario = mapping.Scenario,
WhenStateIs = mapping.ExecutionConditionState, WhenStateIs = mapping.ExecutionConditionState,
@@ -168,7 +169,7 @@ internal class MappingConverter
mappingModel.Response.BodyDestination = response.ResponseMessage.BodyDestination; mappingModel.Response.BodyDestination = response.ResponseMessage.BodyDestination;
mappingModel.Response.StatusCode = response.ResponseMessage.StatusCode; mappingModel.Response.StatusCode = response.ResponseMessage.StatusCode;
if (response.ResponseMessage.Headers != null && response.ResponseMessage.Headers.Count > 0) if (response.ResponseMessage.Headers is { Count: > 0 })
{ {
mappingModel.Response.Headers = MapHeaders(response.ResponseMessage.Headers); mappingModel.Response.Headers = MapHeaders(response.ResponseMessage.Headers);
} }
@@ -20,6 +20,9 @@ internal static class WebhookMapper
{ {
Url = model.Request.Url, Url = model.Request.Url,
Method = model.Request.Method, Method = model.Request.Method,
Delay = model.Request.Delay,
MinimumRandomDelay = model.Request.MinimumRandomDelay,
MaximumRandomDelay = model.Request.MaximumRandomDelay,
Headers = model.Request.Headers?.ToDictionary(x => x.Key, x => new WireMockList<string>(x.Value)) ?? new Dictionary<string, WireMockList<string>>() Headers = model.Request.Headers?.ToDictionary(x => x.Key, x => new WireMockList<string>(x.Value)) ?? new Dictionary<string, WireMockList<string>>()
} }
}; };
@@ -27,6 +30,7 @@ internal static class WebhookMapper
if (model.Request.UseTransformer == true) if (model.Request.UseTransformer == true)
{ {
webhook.Request.UseTransformer = true; webhook.Request.UseTransformer = true;
if (!Enum.TryParse<TransformerType>(model.Request.TransformerType, out var transformerType)) if (!Enum.TryParse<TransformerType>(model.Request.TransformerType, out var transformerType))
{ {
transformerType = TransformerType.Handlebars; transformerType = TransformerType.Handlebars;
@@ -37,7 +41,6 @@ internal static class WebhookMapper
{ {
option = ReplaceNodeOptions.None; option = ReplaceNodeOptions.None;
} }
webhook.Request.TransformerReplaceNodeOptions = option; webhook.Request.TransformerReplaceNodeOptions = option;
} }
@@ -82,7 +85,10 @@ internal static class WebhookMapper
Headers = webhook.Request.Headers?.ToDictionary(x => x.Key, x => x.Value.ToString()), Headers = webhook.Request.Headers?.ToDictionary(x => x.Key, x => x.Value.ToString()),
UseTransformer = webhook.Request.UseTransformer, UseTransformer = webhook.Request.UseTransformer,
TransformerType = webhook.Request.UseTransformer == true ? webhook.Request.TransformerType.ToString() : null, TransformerType = webhook.Request.UseTransformer == true ? webhook.Request.TransformerType.ToString() : null,
TransformerReplaceNodeOptions = webhook.Request.TransformerReplaceNodeOptions.ToString() TransformerReplaceNodeOptions = webhook.Request.TransformerReplaceNodeOptions.ToString(),
Delay = webhook.Request.Delay,
MinimumRandomDelay = webhook.Request.MinimumRandomDelay,
MaximumRandomDelay = webhook.Request.MaximumRandomDelay,
} }
}; };
@@ -123,6 +123,13 @@ namespace WireMock.Server
/// <returns>The <see cref="IRespondWithAProvider"/>.</returns> /// <returns>The <see cref="IRespondWithAProvider"/>.</returns>
IRespondWithAProvider WithWebhook(params IWebhook[] webhooks); IRespondWithAProvider WithWebhook(params IWebhook[] webhooks);
/// <summary>
/// Support FireAndForget for any configured Webhooks
/// </summary>
/// <param name="UseWebhooksFireAndForget"></param>
/// <returns></returns>
IRespondWithAProvider WithWebhookFireAndForget(bool UseWebhooksFireAndForget);
/// <summary> /// <summary>
/// Add a Webhook to call after the response has been generated. /// Add a Webhook to call after the response has been generated.
/// </summary> /// </summary>
@@ -30,6 +30,8 @@ internal class RespondWithAProvider : IRespondWithAProvider
private readonly WireMockServerSettings _settings; private readonly WireMockServerSettings _settings;
private readonly bool _saveToFile; private readonly bool _saveToFile;
private bool _useWebhookFireAndForget = false;
public Guid Guid { get; private set; } = Guid.NewGuid(); public Guid Guid { get; private set; } = Guid.NewGuid();
public IWebhook[]? Webhooks { get; private set; } public IWebhook[]? Webhooks { get; private set; }
@@ -57,7 +59,7 @@ internal class RespondWithAProvider : IRespondWithAProvider
/// <param name="provider">The provider.</param> /// <param name="provider">The provider.</param>
public void RespondWith(IResponseProvider provider) public void RespondWith(IResponseProvider provider)
{ {
_registrationCallback(new Mapping(Guid, _title, _description, _path, _settings, _requestMatcher, provider, _priority, _scenario, _executionConditionState, _nextState, _timesInSameState, Webhooks, TimeSettings), _saveToFile); _registrationCallback(new Mapping(Guid, _title, _description, _path, _settings, _requestMatcher, provider, _priority, _scenario, _executionConditionState, _nextState, _timesInSameState, Webhooks, _useWebhookFireAndForget, TimeSettings), _saveToFile);
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -233,6 +235,13 @@ internal class RespondWithAProvider : IRespondWithAProvider
return this; return this;
} }
public IRespondWithAProvider WithWebhookFireAndForget(bool useWebhooksFireAndForget)
{
_useWebhookFireAndForget = useWebhooksFireAndForget;
return this;
}
private static IWebhook InitWebhook( private static IWebhook InitWebhook(
string url, string url,
string method, string method,
@@ -176,7 +176,7 @@ namespace WireMock.Net.Tests.Owin
_mappingMock.SetupGet(m => m.Provider).Returns(responseBuilder); _mappingMock.SetupGet(m => m.Provider).Returns(responseBuilder);
_mappingMock.SetupGet(m => m.Settings).Returns(settings); _mappingMock.SetupGet(m => m.Settings).Returns(settings);
var newMappingFromProxy = new Mapping(Guid.NewGuid(), string.Empty, string.Empty, null, settings, Request.Create(), Response.Create(), 0, null, null, null, null, null, null); var newMappingFromProxy = new Mapping(Guid.NewGuid(), string.Empty, string.Empty, null, settings, Request.Create(), Response.Create(), 0, null, null, null, null, null, false, null);
_mappingMock.Setup(m => m.ProvideResponseAsync(It.IsAny<RequestMessage>())).ReturnsAsync((new ResponseMessage(), newMappingFromProxy)); _mappingMock.Setup(m => m.ProvideResponseAsync(It.IsAny<RequestMessage>())).ReturnsAsync((new ResponseMessage(), newMappingFromProxy));
var requestBuilder = Request.Create().UsingAnyMethod(); var requestBuilder = Request.Create().UsingAnyMethod();
@@ -230,7 +230,7 @@ namespace WireMock.Net.Tests.Owin
_mappingMock.SetupGet(m => m.Provider).Returns(responseBuilder); _mappingMock.SetupGet(m => m.Provider).Returns(responseBuilder);
_mappingMock.SetupGet(m => m.Settings).Returns(settings); _mappingMock.SetupGet(m => m.Settings).Returns(settings);
var newMappingFromProxy = new Mapping(Guid.NewGuid(), "my-title", "my-description", null, settings, Request.Create(), Response.Create(), 0, null, null, null, null, null, null); var newMappingFromProxy = new Mapping(Guid.NewGuid(), "my-title", "my-description", null, settings, Request.Create(), Response.Create(), 0, null, null, null, null, null, false, null);
_mappingMock.Setup(m => m.ProvideResponseAsync(It.IsAny<RequestMessage>())).ReturnsAsync((new ResponseMessage(), newMappingFromProxy)); _mappingMock.Setup(m => m.ProvideResponseAsync(It.IsAny<RequestMessage>())).ReturnsAsync((new ResponseMessage(), newMappingFromProxy));
var requestBuilder = Request.Create().UsingAnyMethod(); var requestBuilder = Request.Create().UsingAnyMethod();
@@ -11,286 +11,287 @@ using WireMock.Types;
using WireMock.Util; using WireMock.Util;
using Xunit; using Xunit;
namespace WireMock.Net.Tests.Serialization namespace WireMock.Net.Tests.Serialization;
public class MappingConverterTests
{ {
public class MappingConverterTests private readonly WireMockServerSettings _settings = new();
private readonly MappingConverter _sut;
public MappingConverterTests()
{ {
private readonly WireMockServerSettings _settings = new WireMockServerSettings(); _sut = new MappingConverter(new MatcherMapper(_settings));
}
private readonly MappingConverter _sut; [Fact]
public void ToMappingModel_With_SingleWebHook()
public MappingConverterTests() {
// Assign
var request = Request.Create();
var response = Response.Create();
var webhooks = new IWebhook[]
{ {
_sut = new MappingConverter(new MatcherMapper(_settings)); new Webhook
}
[Fact]
public void ToMappingModel_With_SingleWebHook()
{
// Assign
var request = Request.Create();
var response = Response.Create();
var webhooks = new IWebhook[]
{ {
new Webhook Request = new WebhookRequest
{ {
Request = new WebhookRequest Url = "https://test.com",
Headers = new Dictionary<string, WireMockList<string>>
{ {
Url = "https://test.com", { "Single", new WireMockList<string>("x") },
Headers = new Dictionary<string, WireMockList<string>> { "Multi", new WireMockList<string>("a", "b") }
{ },
{ "Single", new WireMockList<string>("x") }, Method = "post",
{ "Multi", new WireMockList<string>("a", "b") } BodyData = new BodyData
}, {
Method = "post", BodyAsString = "b",
BodyData = new BodyData DetectedBodyType = BodyType.String,
{ DetectedBodyTypeFromContentType = BodyType.String
BodyAsString = "b",
DetectedBodyType = BodyType.String,
DetectedBodyTypeFromContentType = BodyType.String
}
} }
} }
};
var mapping = new Mapping(Guid.NewGuid(), string.Empty, string.Empty, null, _settings, request, response, 0, null, null, null, null, webhooks, null);
// Act
var model = _sut.ToMappingModel(mapping);
// Assert
model.Should().NotBeNull();
model.Priority.Should().BeNull();
model.Response.BodyAsJsonIndented.Should().BeNull();
model.Response.UseTransformer.Should().BeNull();
model.Response.Headers.Should().BeNull();
model.Webhooks.Should().BeNull();
model.Webhook.Request.Method.Should().Be("post");
model.Webhook.Request.Url.Should().Be("https://test.com");
model.Webhook.Request.Headers.Should().HaveCount(2);
model.Webhook.Request.Body.Should().Be("b");
model.Webhook.Request.BodyAsJson.Should().BeNull();
}
[Fact]
public void ToMappingModel_With_MultipleWebHooks()
{
// Assign
var request = Request.Create();
var response = Response.Create();
var webhooks = new IWebhook[]
{
new Webhook
{
Request = new WebhookRequest
{
Url = "https://test1.com",
Headers = new Dictionary<string, WireMockList<string>>
{
{ "One", new WireMockList<string>("x") }
},
Method = "post",
BodyData = new BodyData
{
BodyAsString = "1",
DetectedBodyType = BodyType.String,
DetectedBodyTypeFromContentType = BodyType.String
}
}
},
new Webhook
{
Request = new WebhookRequest
{
Url = "https://test2.com",
Headers = new Dictionary<string, WireMockList<string>>
{
{ "First", new WireMockList<string>("x") },
{ "Second", new WireMockList<string>("a", "b") }
},
Method = "post",
BodyData = new BodyData
{
BodyAsString = "2",
DetectedBodyType = BodyType.String,
DetectedBodyTypeFromContentType = BodyType.String
}
}
}
};
var mapping = new Mapping(Guid.NewGuid(), string.Empty, string.Empty, null, _settings, request, response, 0, null, null, null, null, webhooks, null);
// Act
var model = _sut.ToMappingModel(mapping);
// Assert
model.Should().NotBeNull();
model.Priority.Should().BeNull();
model.Response.BodyAsJsonIndented.Should().BeNull();
model.Response.UseTransformer.Should().BeNull();
model.Response.Headers.Should().BeNull();
model.Webhook.Should().BeNull();
model.Webhooks[0].Request.Method.Should().Be("post");
model.Webhooks[0].Request.Url.Should().Be("https://test1.com");
model.Webhooks[0].Request.Headers.Should().HaveCount(1);
model.Webhooks[0].Request.Body.Should().Be("1");
model.Webhooks[1].Request.Method.Should().Be("post");
model.Webhooks[1].Request.Url.Should().Be("https://test2.com");
model.Webhooks[1].Request.Headers.Should().HaveCount(2);
model.Webhooks[1].Request.Body.Should().Be("2");
}
[Fact]
public void ToMappingModel_WithTitle_And_Description_ReturnsCorrectModel()
{
// Assign
var title = "my-title";
var description = "my-description";
var request = Request.Create();
var response = Response.Create();
var mapping = new Mapping(Guid.NewGuid(), title, description, null, _settings, request, response, 0, null, null, null, null, null, null);
// Act
var model = _sut.ToMappingModel(mapping);
// Assert
model.Should().NotBeNull();
model.Title.Should().Be(title);
model.Description.Should().Be(description);
}
[Fact]
public void ToMappingModel_WithPriority_ReturnsPriority()
{
// Assign
var request = Request.Create();
var response = Response.Create().WithBodyAsJson(new { x = "x" }).WithTransformer();
var mapping = new Mapping(Guid.NewGuid(), string.Empty, string.Empty, null, _settings, request, response, 42, null, null, null, null, null, null);
// Act
var model = _sut.ToMappingModel(mapping);
// Assert
model.Should().NotBeNull();
model.Priority.Should().Be(42);
model.Response.UseTransformer.Should().BeTrue();
}
[Fact]
public void ToMappingModel_WithTimeSettings_ReturnsCorrectTimeSettings()
{
// Assign
var start = DateTime.Now;
var ttl = 100;
var end = start.AddSeconds(ttl);
var request = Request.Create();
var response = Response.Create();
var timeSettings = new TimeSettings
{
Start = start,
End = end,
TTL = ttl
};
var mapping = new Mapping(Guid.NewGuid(), string.Empty, string.Empty, null, _settings, request, response, 42, null, null, null, null, null, timeSettings);
// Act
var model = _sut.ToMappingModel(mapping);
// Assert
model.Should().NotBeNull();
model.TimeSettings.Should().NotBeNull();
model.TimeSettings.Start.Should().Be(start);
model.TimeSettings.End.Should().Be(end);
model.TimeSettings.TTL.Should().Be(ttl);
}
[Fact]
public void ToMappingModel_WithDelayAsTimeSpan_ReturnsCorrectModel()
{
// Arrange
var tests = new[]
{
new { Delay = Timeout.InfiniteTimeSpan, Expected = (int) TimeSpan.MaxValue.TotalMilliseconds },
new { Delay = TimeSpan.FromSeconds(1), Expected = 1000},
new { Delay = TimeSpan.MaxValue, Expected = (int) TimeSpan.MaxValue.TotalMilliseconds }
};
foreach (var test in tests)
{
var request = Request.Create();
var response = Response.Create().WithDelay(test.Delay);
var mapping = new Mapping(Guid.NewGuid(), string.Empty, string.Empty, string.Empty, _settings, request, response, 42, null, null, null, null, null, null);
// Act
var model = _sut.ToMappingModel(mapping);
// Assert
model.Should().NotBeNull();
model.Response.Delay.Should().Be(test.Expected);
} }
} };
var mapping = new Mapping(Guid.NewGuid(), string.Empty, string.Empty, null, _settings, request, response, 0, null, null, null, null, webhooks, false, null);
[Fact] // Act
public void ToMappingModel_WithDelayAsMilleSeconds_ReturnsCorrectModel() var model = _sut.ToMappingModel(mapping);
// Assert
model.Should().NotBeNull();
model.Priority.Should().BeNull();
model.Response.BodyAsJsonIndented.Should().BeNull();
model.Response.UseTransformer.Should().BeNull();
model.Response.Headers.Should().BeNull();
model.UseWebhooksFireAndForget.Should().BeFalse();
model.Webhooks.Should().BeNull();
model.Webhook!.Request.Method.Should().Be("post");
model.Webhook.Request.Url.Should().Be("https://test.com");
model.Webhook.Request.Headers.Should().HaveCount(2);
model.Webhook.Request.Body.Should().Be("b");
model.Webhook.Request.BodyAsJson.Should().BeNull();
}
[Fact]
public void ToMappingModel_With_MultipleWebHooks()
{
// Assign
var request = Request.Create();
var response = Response.Create();
var webhooks = new IWebhook[]
{
new Webhook
{
Request = new WebhookRequest
{
Url = "https://test1.com",
Headers = new Dictionary<string, WireMockList<string>>
{
{ "One", new WireMockList<string>("x") }
},
Method = "post",
BodyData = new BodyData
{
BodyAsString = "1",
DetectedBodyType = BodyType.String,
DetectedBodyTypeFromContentType = BodyType.String
}
}
},
new Webhook
{
Request = new WebhookRequest
{
Url = "https://test2.com",
Headers = new Dictionary<string, WireMockList<string>>
{
{ "First", new WireMockList<string>("x") },
{ "Second", new WireMockList<string>("a", "b") }
},
Method = "post",
BodyData = new BodyData
{
BodyAsString = "2",
DetectedBodyType = BodyType.String,
DetectedBodyTypeFromContentType = BodyType.String
}
}
}
};
var mapping = new Mapping(Guid.NewGuid(), string.Empty, string.Empty, null, _settings, request, response, 0, null, null, null, null, webhooks, true, null);
// Act
var model = _sut.ToMappingModel(mapping);
// Assert
model.Should().NotBeNull();
model.Priority.Should().BeNull();
model.Response.BodyAsJsonIndented.Should().BeNull();
model.Response.UseTransformer.Should().BeNull();
model.Response.Headers.Should().BeNull();
model.UseWebhooksFireAndForget.Should().BeTrue();
model.Webhook.Should().BeNull();
model.Webhooks![0].Request.Method.Should().Be("post");
model.Webhooks[0].Request.Url.Should().Be("https://test1.com");
model.Webhooks[0].Request.Headers.Should().HaveCount(1);
model.Webhooks[0].Request.Body.Should().Be("1");
model.Webhooks[1].Request.Method.Should().Be("post");
model.Webhooks[1].Request.Url.Should().Be("https://test2.com");
model.Webhooks[1].Request.Headers.Should().HaveCount(2);
model.Webhooks[1].Request.Body.Should().Be("2");
}
[Fact]
public void ToMappingModel_WithTitle_And_Description_ReturnsCorrectModel()
{
// Assign
var title = "my-title";
var description = "my-description";
var request = Request.Create();
var response = Response.Create();
var mapping = new Mapping(Guid.NewGuid(), title, description, null, _settings, request, response, 0, null, null, null, null, null, false, null);
// Act
var model = _sut.ToMappingModel(mapping);
// Assert
model.Should().NotBeNull();
model.Title.Should().Be(title);
model.Description.Should().Be(description);
}
[Fact]
public void ToMappingModel_WithPriority_ReturnsPriority()
{
// Assign
var request = Request.Create();
var response = Response.Create().WithBodyAsJson(new { x = "x" }).WithTransformer();
var mapping = new Mapping(Guid.NewGuid(), string.Empty, string.Empty, null, _settings, request, response, 42, null, null, null, null, null, false, null);
// Act
var model = _sut.ToMappingModel(mapping);
// Assert
model.Should().NotBeNull();
model.Priority.Should().Be(42);
model.Response.UseTransformer.Should().BeTrue();
}
[Fact]
public void ToMappingModel_WithTimeSettings_ReturnsCorrectTimeSettings()
{
// Assign
var start = DateTime.Now;
var ttl = 100;
var end = start.AddSeconds(ttl);
var request = Request.Create();
var response = Response.Create();
var timeSettings = new TimeSettings
{
Start = start,
End = end,
TTL = ttl
};
var mapping = new Mapping(Guid.NewGuid(), string.Empty, string.Empty, null, _settings, request, response, 42, null, null, null, null, null, false, timeSettings);
// Act
var model = _sut.ToMappingModel(mapping);
// Assert
model.Should().NotBeNull();
model.TimeSettings!.Should().NotBeNull();
model.TimeSettings!.Start.Should().Be(start);
model.TimeSettings.End.Should().Be(end);
model.TimeSettings.TTL.Should().Be(ttl);
}
[Fact]
public void ToMappingModel_WithDelayAsTimeSpan_ReturnsCorrectModel()
{
// Arrange
var tests = new[]
{
new { Delay = Timeout.InfiniteTimeSpan, Expected = (int) TimeSpan.MaxValue.TotalMilliseconds },
new { Delay = TimeSpan.FromSeconds(1), Expected = 1000 },
new { Delay = TimeSpan.MaxValue, Expected = (int) TimeSpan.MaxValue.TotalMilliseconds }
};
foreach (var test in tests)
{ {
// Assign
var delay = 1000;
var request = Request.Create(); var request = Request.Create();
var response = Response.Create().WithDelay(delay); var response = Response.Create().WithDelay(test.Delay);
var mapping = new Mapping(Guid.NewGuid(), string.Empty, string.Empty, null, _settings, request, response, 42, null, null, null, null, null, null); var mapping = new Mapping(Guid.NewGuid(), string.Empty, string.Empty, string.Empty, _settings, request, response, 42, null, null, null, null, null, false, null);
// Act // Act
var model = _sut.ToMappingModel(mapping); var model = _sut.ToMappingModel(mapping);
// Assert // Assert
model.Should().NotBeNull(); model.Should().NotBeNull();
model.Response.Delay.Should().Be(delay); model.Response.Delay.Should().Be(test.Expected);
}
[Fact]
public void ToMappingModel_WithRandomMinimumDelay_ReturnsCorrectModel()
{
// Assign
int minimumDelay = 1000;
var request = Request.Create();
var response = Response.Create().WithRandomDelay(minimumDelay);
var mapping = new Mapping(Guid.NewGuid(), string.Empty, string.Empty, null, _settings, request, response, 42, null, null, null, null, null, null);
// Act
var model = _sut.ToMappingModel(mapping);
// Assert
model.Should().NotBeNull();
model.Response.Delay.Should().BeNull();
model.Response.MinimumRandomDelay.Should().Be(minimumDelay);
model.Response.MaximumRandomDelay.Should().Be(60_000);
}
[Fact]
public void ToMappingModel_WithRandomDelay_ReturnsCorrectModel()
{
// Assign
int minimumDelay = 1000;
int maximumDelay = 2000;
var request = Request.Create();
var response = Response.Create().WithRandomDelay(minimumDelay, maximumDelay);
var mapping = new Mapping(Guid.NewGuid(), string.Empty, string.Empty, null, _settings, request, response, 42, null, null, null, null, null, null);
// Act
var model = _sut.ToMappingModel(mapping);
// Assert
model.Should().NotBeNull();
model.Response.Delay.Should().BeNull();
model.Response.MinimumRandomDelay.Should().Be(minimumDelay);
model.Response.MaximumRandomDelay.Should().Be(maximumDelay);
} }
} }
[Fact]
public void ToMappingModel_WithDelayAsMilleSeconds_ReturnsCorrectModel()
{
// Assign
var delay = 1000;
var request = Request.Create();
var response = Response.Create().WithDelay(delay);
var mapping = new Mapping(Guid.NewGuid(), string.Empty, string.Empty, null, _settings, request, response, 42, null, null, null, null, null, false, null);
// Act
var model = _sut.ToMappingModel(mapping);
// Assert
model.Should().NotBeNull();
model.Response.Delay.Should().Be(delay);
}
[Fact]
public void ToMappingModel_WithRandomMinimumDelay_ReturnsCorrectModel()
{
// Assign
int minimumDelay = 1000;
var request = Request.Create();
var response = Response.Create().WithRandomDelay(minimumDelay);
var mapping = new Mapping(Guid.NewGuid(), string.Empty, string.Empty, null, _settings, request, response, 42, null, null, null, null, null, false, null);
// Act
var model = _sut.ToMappingModel(mapping);
// Assert
model.Should().NotBeNull();
model.Response.Delay.Should().BeNull();
model.Response.MinimumRandomDelay.Should().Be(minimumDelay);
model.Response.MaximumRandomDelay.Should().Be(60_000);
}
[Fact]
public void ToMappingModel_WithRandomDelay_ReturnsCorrectModel()
{
// Assign
int minimumDelay = 1000;
int maximumDelay = 2000;
var request = Request.Create();
var response = Response.Create().WithRandomDelay(minimumDelay, maximumDelay);
var mapping = new Mapping(Guid.NewGuid(), string.Empty, string.Empty, null, _settings, request, response, 42, null, null, null, null, null, false, null);
// Act
var model = _sut.ToMappingModel(mapping);
// Assert
model.Should().NotBeNull();
model.Response.Delay.Should().BeNull();
model.Response.MinimumRandomDelay.Should().Be(minimumDelay);
model.Response.MaximumRandomDelay.Should().Be(maximumDelay);
}
} }
@@ -1,101 +1,145 @@
using System.Collections.Generic; using System.Collections.Generic;
using FluentAssertions; using FluentAssertions;
using WireMock.Admin.Mappings; using WireMock.Admin.Mappings;
using WireMock.Models;
using WireMock.Serialization; using WireMock.Serialization;
using WireMock.Types; using WireMock.Types;
using WireMock.Util;
using Xunit; using Xunit;
namespace WireMock.Net.Tests.Serialization namespace WireMock.Net.Tests.Serialization;
public class WebhookMapperTests
{ {
public class WebhookMapperTests [Fact]
public void WebhookMapper_Map_WebhookModel_BodyAsString_And_UseTransformerIsFalse()
{ {
[Fact] // Assign
public void WebhookMapper_Map_Model_BodyAsString_And_UseTransformerIsFalse() var model = new WebhookModel
{ {
// Assign Request = new WebhookRequestModel
var model = new WebhookModel
{ {
Request = new WebhookRequestModel Url = "https://localhost",
Method = "get",
Headers = new Dictionary<string, string>
{ {
Url = "https://localhost", { "x", "y" }
Method = "get", },
Headers = new Dictionary<string, string> Body = "test",
{ UseTransformer = false
{ "x", "y" } }
}, };
Body = "test",
UseTransformer = false
}
};
var result = WebhookMapper.Map(model); var result = WebhookMapper.Map(model);
result.Request.Url.Should().Be("https://localhost"); result.Request.Url.Should().Be("https://localhost");
result.Request.Method.Should().Be("get"); result.Request.Method.Should().Be("get");
result.Request.Headers.Should().HaveCount(1); result.Request.Headers.Should().HaveCount(1);
result.Request.BodyData.BodyAsJson.Should().BeNull(); result.Request.BodyData!.BodyAsJson.Should().BeNull();
result.Request.BodyData.BodyAsString.Should().Be("test"); result.Request.BodyData.BodyAsString.Should().Be("test");
result.Request.BodyData.DetectedBodyType.Should().Be(BodyType.String); result.Request.BodyData.DetectedBodyType.Should().Be(BodyType.String);
result.Request.UseTransformer.Should().BeNull(); result.Request.UseTransformer.Should().BeNull();
} }
[Fact] [Fact]
public void WebhookMapper_Map_Model_BodyAsString_And_UseTransformerIsTrue() public void WebhookMapper_Map_WebhookModel_BodyAsString_And_UseTransformerIsTrue()
{
// Assign
var model = new WebhookModel
{ {
// Assign Request = new WebhookRequestModel
var model = new WebhookModel
{ {
Request = new WebhookRequestModel Url = "https://localhost",
Method = "get",
Headers = new Dictionary<string, string>
{ {
Url = "https://localhost", { "x", "y" }
Method = "get", },
Headers = new Dictionary<string, string> Body = "test",
{ UseTransformer = true
{ "x", "y" } }
}, };
Body = "test",
UseTransformer = true
}
};
var result = WebhookMapper.Map(model); var result = WebhookMapper.Map(model);
result.Request.Url.Should().Be("https://localhost"); result.Request.Url.Should().Be("https://localhost");
result.Request.Method.Should().Be("get"); result.Request.Method.Should().Be("get");
result.Request.Headers.Should().HaveCount(1); result.Request.Headers.Should().HaveCount(1);
result.Request.BodyData.BodyAsJson.Should().BeNull(); result.Request.BodyData!.BodyAsJson.Should().BeNull();
result.Request.BodyData.BodyAsString.Should().Be("test"); result.Request.BodyData.BodyAsString.Should().Be("test");
result.Request.BodyData.DetectedBodyType.Should().Be(BodyType.String); result.Request.BodyData.DetectedBodyType.Should().Be(BodyType.String);
result.Request.UseTransformer.Should().BeTrue(); result.Request.UseTransformer.Should().BeTrue();
result.Request.TransformerType.Should().Be(TransformerType.Handlebars); result.Request.TransformerType.Should().Be(TransformerType.Handlebars);
} }
[Fact] [Fact]
public void WebhookMapper_Map_Model_BodyAsJson() public void WebhookMapper_Map_WebhookModel_BodyAsJson()
{
// Assign
var model = new WebhookModel
{ {
// Assign Request = new WebhookRequestModel
var model = new WebhookModel
{ {
Request = new WebhookRequestModel Url = "https://localhost",
Method = "get",
Headers = new Dictionary<string, string>
{ {
Url = "https://localhost", { "x", "y" }
Method = "get", },
Headers = new Dictionary<string, string> BodyAsJson = new { n = 12345 },
{ Delay = 4,
{ "x", "y" } MinimumRandomDelay = 5,
}, MaximumRandomDelay = 6
BodyAsJson = new { n = 12345 } },
} };
};
var result = WebhookMapper.Map(model); var result = WebhookMapper.Map(model);
result.Request.Url.Should().Be("https://localhost"); result.Request.Url.Should().Be("https://localhost");
result.Request.Method.Should().Be("get"); result.Request.Method.Should().Be("get");
result.Request.Headers.Should().HaveCount(1); result.Request.Headers.Should().HaveCount(1);
result.Request.BodyData.BodyAsString.Should().BeNull(); result.Request.BodyData!.BodyAsString.Should().BeNull();
result.Request.BodyData.BodyAsJson.Should().NotBeNull(); result.Request.BodyData.BodyAsJson.Should().NotBeNull();
result.Request.BodyData.DetectedBodyType.Should().Be(BodyType.Json); result.Request.BodyData.DetectedBodyType.Should().Be(BodyType.Json);
} result.Request.Delay.Should().Be(4);
result.Request.MinimumRandomDelay.Should().Be(5);
result.Request.MaximumRandomDelay.Should().Be(6);
}
[Fact]
public void WebhookMapper_Map_Webhook_To_Model()
{
// Assign
var webhook = new Webhook
{
Request = new WebhookRequest
{
Url = "https://localhost",
Method = "get",
Headers = new Dictionary<string, WireMockList<string>>
{
{ "x", new WireMockList<string>("y") }
},
BodyData = new BodyData
{
BodyAsJson = new { n = 12345 },
DetectedBodyType = BodyType.Json,
DetectedBodyTypeFromContentType = BodyType.Json
},
Delay = 4,
MinimumRandomDelay = 5,
MaximumRandomDelay = 6
}
};
var result = WebhookMapper.Map(webhook);
result.Request.Url.Should().Be("https://localhost");
result.Request.Method.Should().Be("get");
result.Request.Headers.Should().HaveCount(1);
result.Request.BodyAsJson.Should().NotBeNull();
result.Request.Delay.Should().Be(4);
result.Request.MinimumRandomDelay.Should().Be(5);
result.Request.MaximumRandomDelay.Should().Be(6);
} }
} }