From 98a0f2fa2862d41df8858755174024d35217327f Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Mon, 12 Sep 2022 20:30:40 +0200 Subject: [PATCH] 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 * small update * Tweak middleware and fix bug in example (#806) Co-authored-by: Matt Philmon * .ConfigureAwait(false) Co-authored-by: mattisking Co-authored-by: Matt Philmon --- .../MainApp.cs | 35 ++ ...WireMock.Net.Console.Net472.Classic.csproj | 19 +- .../packages.config | 3 - .../Admin/Mappings/MappingModel.cs | 5 + .../Admin/Mappings/WebhookModel.cs | 19 +- .../Admin/Mappings/WebhookRequestModel.cs | 19 +- .../Models/IWebhookRequest.cs | 80 +-- src/WireMock.Net/Http/WebhookSender.cs | 56 +- src/WireMock.Net/IMapping.cs | 5 + src/WireMock.Net/Mapping.cs | 6 + .../Request/RequestMessageParamMatcher.cs | 2 +- src/WireMock.Net/Models/Webhook.cs | 17 +- src/WireMock.Net/Models/WebhookRequest.cs | 50 +- src/WireMock.Net/Owin/WireMockMiddleware.cs | 35 +- src/WireMock.Net/Proxy/ProxyHelper.cs | 1 + .../Serialization/MappingConverter.cs | 3 +- .../Serialization/WebhookMapper.cs | 12 +- .../Server/IRespondWithAProvider.cs | 7 + .../Server/RespondWithAProvider.cs | 11 +- .../Owin/WireMockMiddlewareTests.cs | 4 +- .../Serialization/MappingConverterTests.cs | 523 +++++++++--------- .../Serialization/WebhookMapperTests.cs | 218 +++++--- 22 files changed, 670 insertions(+), 460 deletions(-) diff --git a/examples/WireMock.Net.Console.Net452.Classic/MainApp.cs b/examples/WireMock.Net.Console.Net452.Classic/MainApp.cs index 9ea422e8..bfcce9dc 100644 --- a/examples/WireMock.Net.Console.Net452.Classic/MainApp.cs +++ b/examples/WireMock.Net.Console.Net452.Classic/MainApp.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using Newtonsoft.Json; using WireMock.Logging; using WireMock.Matchers; +using WireMock.Models; using WireMock.RequestBuilders; using WireMock.ResponseBuilders; 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("Press any key to stop the server"); diff --git a/examples/WireMock.Net.Console.Net472.Classic/WireMock.Net.Console.Net472.Classic.csproj b/examples/WireMock.Net.Console.Net472.Classic/WireMock.Net.Console.Net472.Classic.csproj index 2aa610e9..5c01887b 100644 --- a/examples/WireMock.Net.Console.Net472.Classic/WireMock.Net.Console.Net472.Classic.csproj +++ b/examples/WireMock.Net.Console.Net472.Classic/WireMock.Net.Console.Net472.Classic.csproj @@ -342,15 +342,6 @@ ..\..\packages\TinyMapper.3.0.3\lib\net40\TinyMapper.dll - - ..\..\packages\WireMock.Net.1.5.3\lib\net461\WireMock.Net.dll - - - ..\..\packages\WireMock.Net.Abstractions.1.5.3\lib\net451\WireMock.Net.Abstractions.dll - - - ..\..\packages\WireMock.Org.Abstractions.1.5.3\lib\net45\WireMock.Org.Abstractions.dll - ..\..\packages\XPath2.1.1.3\lib\net452\XPath2.dll @@ -388,6 +379,16 @@ + + + {b6269aac-170a-4346-8b9a-579ded3d9a94} + WireMock.Net.Abstractions + + + {d3804228-91f4-4502-9595-39584e5a01ad} + WireMock.Net + + diff --git a/examples/WireMock.Net.Console.Net472.Classic/packages.config b/examples/WireMock.Net.Console.Net472.Classic/packages.config index 47984c9d..4f0f76ae 100644 --- a/examples/WireMock.Net.Console.Net472.Classic/packages.config +++ b/examples/WireMock.Net.Console.Net472.Classic/packages.config @@ -147,9 +147,6 @@ - - - \ No newline at end of file diff --git a/src/WireMock.Net.Abstractions/Admin/Mappings/MappingModel.cs b/src/WireMock.Net.Abstractions/Admin/Mappings/MappingModel.cs index 693143a7..aaddafc8 100644 --- a/src/WireMock.Net.Abstractions/Admin/Mappings/MappingModel.cs +++ b/src/WireMock.Net.Abstractions/Admin/Mappings/MappingModel.cs @@ -74,4 +74,9 @@ public class MappingModel /// The Webhooks. /// public WebhookModel[]? Webhooks { get; set; } + + /// + /// Fire and forget for webhooks. + /// + public bool? UseWebhooksFireAndForget { get; set; } } \ No newline at end of file diff --git a/src/WireMock.Net.Abstractions/Admin/Mappings/WebhookModel.cs b/src/WireMock.Net.Abstractions/Admin/Mappings/WebhookModel.cs index 01f7fb19..514c13f9 100644 --- a/src/WireMock.Net.Abstractions/Admin/Mappings/WebhookModel.cs +++ b/src/WireMock.Net.Abstractions/Admin/Mappings/WebhookModel.cs @@ -1,14 +1,13 @@ -namespace WireMock.Admin.Mappings +namespace WireMock.Admin.Mappings; + +/// +/// The Webhook +/// +[FluentBuilder.AutoGenerateBuilder] +public class WebhookModel { /// - /// The Webhook + /// The Webhook Request. /// - [FluentBuilder.AutoGenerateBuilder] - public class WebhookModel - { - /// - /// The Webhook Request. - /// - public WebhookRequestModel Request { get; set; } - } + public WebhookRequestModel Request { get; set; } = null!; } \ No newline at end of file diff --git a/src/WireMock.Net.Abstractions/Admin/Mappings/WebhookRequestModel.cs b/src/WireMock.Net.Abstractions/Admin/Mappings/WebhookRequestModel.cs index 021f7023..43c811f0 100644 --- a/src/WireMock.Net.Abstractions/Admin/Mappings/WebhookRequestModel.cs +++ b/src/WireMock.Net.Abstractions/Admin/Mappings/WebhookRequestModel.cs @@ -11,12 +11,12 @@ public class WebhookRequestModel /// /// Gets or sets the Url. /// - public string Url { get; set; } + public string Url { get; set; } = null!; /// /// The method /// - public string Method { get; set; } + public string Method { get; set; } = null!; /// /// Gets or sets the headers. @@ -47,4 +47,19 @@ public class WebhookRequestModel /// The ReplaceNodeOptions to use when transforming a JSON node. /// public string? TransformerReplaceNodeOptions { get; set; } + + /// + /// Gets or sets the delay in milliseconds. + /// + public int? Delay { get; set; } + + /// + /// Gets or sets the minimum random delay in milliseconds. + /// + public int? MinimumRandomDelay { get; set; } + + /// + /// Gets or sets the maximum random delay in milliseconds. + /// + public int? MaximumRandomDelay { get; set; } } \ No newline at end of file diff --git a/src/WireMock.Net.Abstractions/Models/IWebhookRequest.cs b/src/WireMock.Net.Abstractions/Models/IWebhookRequest.cs index caa6df46..c671ccd8 100644 --- a/src/WireMock.Net.Abstractions/Models/IWebhookRequest.cs +++ b/src/WireMock.Net.Abstractions/Models/IWebhookRequest.cs @@ -2,46 +2,60 @@ using System.Collections.Generic; using WireMock.Types; using WireMock.Util; -namespace WireMock.Models +namespace WireMock.Models; + +/// +/// IWebhookRequest +/// +public interface IWebhookRequest { /// - /// IWebhookRequest + /// The Webhook Url. /// - public interface IWebhookRequest - { - /// - /// The Webhook Url. - /// - string Url { get; set; } + string Url { get; set; } - /// - /// The method to use. - /// - string Method { get; set; } + /// + /// The method to use. + /// + string Method { get; set; } - /// - /// The Headers to send. - /// - IDictionary>? Headers { get; } + /// + /// The Headers to send. + /// + IDictionary>? Headers { get; } - /// - /// The body to send. - /// - IBodyData? BodyData { get; set; } + /// + /// The body to send. + /// + IBodyData? BodyData { get; set; } - /// - /// Use Transformer. - /// - bool? UseTransformer { get; set; } + /// + /// Use Transformer. + /// + bool? UseTransformer { get; set; } - /// - /// The transformer type. - /// - TransformerType TransformerType { get; set; } + /// + /// The transformer type. + /// + TransformerType TransformerType { get; set; } - /// - /// The ReplaceNodeOptions to use when transforming a JSON node. - /// - ReplaceNodeOptions TransformerReplaceNodeOptions { get; set; } - } + /// + /// The ReplaceNodeOptions to use when transforming a JSON node. + /// + ReplaceNodeOptions TransformerReplaceNodeOptions { get; set; } + + /// + /// Gets or sets the delay in milliseconds. + /// + int? Delay { get; set; } + + /// + /// Gets or sets the minimum random delay in milliseconds. + /// + int? MinimumRandomDelay { get; set; } + + /// + /// Gets or sets the maximum random delay in milliseconds. + /// + int? MaximumRandomDelay { get; set; } } \ No newline at end of file diff --git a/src/WireMock.Net/Http/WebhookSender.cs b/src/WireMock.Net/Http/WebhookSender.cs index 20c6a949..73295844 100644 --- a/src/WireMock.Net/Http/WebhookSender.cs +++ b/src/WireMock.Net/Http/WebhookSender.cs @@ -1,7 +1,9 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Net.Http; +using System.Threading; using System.Threading.Tasks; using Stef.Validation; using WireMock.Models; @@ -17,6 +19,7 @@ namespace WireMock.Http; internal class WebhookSender { private const string ClientIp = "::1"; + private static readonly ThreadLocal Random = new(() => new Random(DateTime.UtcNow.Millisecond)); private readonly WireMockServerSettings _settings; @@ -25,20 +28,26 @@ internal class WebhookSender _settings = Guard.NotNull(settings); } - public Task SendAsync(HttpClient client, IMapping mapping, IWebhookRequest request, IRequestMessage originalRequestMessage, IResponseMessage originalResponseMessage) + public async Task SendAsync( + HttpClient client, + IMapping mapping, + IWebhookRequest webhookRequest, + IRequestMessage originalRequestMessage, + IResponseMessage originalResponseMessage + ) { Guard.NotNull(client); Guard.NotNull(mapping); - Guard.NotNull(request); + Guard.NotNull(webhookRequest); Guard.NotNull(originalRequestMessage); Guard.NotNull(originalResponseMessage); IBodyData? bodyData; IDictionary>? headers; - if (request.UseTransformer == true) + if (webhookRequest.UseTransformer == true) { ITransformer responseMessageTransformer; - switch (request.TransformerType) + switch (webhookRequest.TransformerType) { case TransformerType.Handlebars: var factoryHandlebars = new HandlebarsContextFactory(_settings.FileSystemHandler, _settings.HandlebarsRegistrationCallback); @@ -47,26 +56,26 @@ internal class WebhookSender case TransformerType.Scriban: case TransformerType.ScribanDotLiquid: - var factoryDotLiquid = new ScribanContextFactory(_settings.FileSystemHandler, request.TransformerType); + var factoryDotLiquid = new ScribanContextFactory(_settings.FileSystemHandler, webhookRequest.TransformerType); responseMessageTransformer = new Transformer(factoryDotLiquid); break; 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 { - bodyData = request.BodyData; - headers = request.Headers; + bodyData = webhookRequest.BodyData; + headers = webhookRequest.Headers; } // Create RequestMessage var requestMessage = new RequestMessage( - new UrlDetails(request.Url), - request.Method, + new UrlDetails(webhookRequest.Url), + webhookRequest.Method, ClientIp, bodyData, headers?.ToDictionary(x => x.Key, x => x.Value.ToArray()) @@ -76,9 +85,30 @@ internal class WebhookSender }; // 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 - 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; } } \ No newline at end of file diff --git a/src/WireMock.Net/IMapping.cs b/src/WireMock.Net/IMapping.cs index b95b2ef1..3af0f136 100644 --- a/src/WireMock.Net/IMapping.cs +++ b/src/WireMock.Net/IMapping.cs @@ -112,6 +112,11 @@ public interface IMapping /// IWebhook[]? Webhooks { get; } + /// + /// Use Fire and Forget for the defined webhook(s). [Optional] + /// + public bool? UseWebhooksFireAndForget { get; set; } + /// /// ProvideResponseAsync /// diff --git a/src/WireMock.Net/Mapping.cs b/src/WireMock.Net/Mapping.cs index 3c404a21..1e978ef5 100644 --- a/src/WireMock.Net/Mapping.cs +++ b/src/WireMock.Net/Mapping.cs @@ -63,6 +63,9 @@ public class Mapping : IMapping /// public IWebhook[]? Webhooks { get; } + /// + public bool? UseWebhooksFireAndForget { get; set; } + /// public ITimeSettings? TimeSettings { get; } @@ -82,6 +85,7 @@ public class Mapping : IMapping /// The next state which will occur after the current mapping execution. [Optional] /// Only when the current state is executed this number, the next state which will occur. [Optional] /// The Webhooks. [Optional] + /// Use Fire and Forget for the defined webhook(s). [Optional] /// The TimeSettings. [Optional] public Mapping( Guid guid, @@ -97,6 +101,7 @@ public class Mapping : IMapping string? nextState, int? stateTimes, IWebhook[]? webhooks, + bool? useWebhooksFireAndForget, ITimeSettings? timeSettings) { Guid = guid; @@ -112,6 +117,7 @@ public class Mapping : IMapping NextState = nextState; StateTimes = stateTimes; Webhooks = webhooks; + UseWebhooksFireAndForget = useWebhooksFireAndForget; TimeSettings = timeSettings; } diff --git a/src/WireMock.Net/Matchers/Request/RequestMessageParamMatcher.cs b/src/WireMock.Net/Matchers/Request/RequestMessageParamMatcher.cs index e15b4323..dc4e34f8 100644 --- a/src/WireMock.Net/Matchers/Request/RequestMessageParamMatcher.cs +++ b/src/WireMock.Net/Matchers/Request/RequestMessageParamMatcher.cs @@ -21,7 +21,7 @@ public class RequestMessageParamMatcher : IRequestMatcher /// /// The key /// - public string? Key { get; } + public string Key { get; } = string.Empty; /// /// Defines if the key should be matched using case-ignore. diff --git a/src/WireMock.Net/Models/Webhook.cs b/src/WireMock.Net/Models/Webhook.cs index 7e2fef8b..db896bcc 100644 --- a/src/WireMock.Net/Models/Webhook.cs +++ b/src/WireMock.Net/Models/Webhook.cs @@ -1,11 +1,10 @@ -namespace WireMock.Models +namespace WireMock.Models; + +/// +/// Webhook +/// +public class Webhook : IWebhook { - /// - /// Webhook - /// - public class Webhook : IWebhook - { - /// - public IWebhookRequest Request { get; set; } - } + /// + public IWebhookRequest Request { get; set; } = null!; } \ No newline at end of file diff --git a/src/WireMock.Net/Models/WebhookRequest.cs b/src/WireMock.Net/Models/WebhookRequest.cs index 9dd1401c..96d49dd9 100644 --- a/src/WireMock.Net/Models/WebhookRequest.cs +++ b/src/WireMock.Net/Models/WebhookRequest.cs @@ -2,32 +2,40 @@ using System.Collections.Generic; using WireMock.Types; using WireMock.Util; -namespace WireMock.Models +namespace WireMock.Models; + +/// +/// WebhookRequest +/// +public class WebhookRequest : IWebhookRequest { - /// - /// WebhookRequest - /// - public class WebhookRequest : IWebhookRequest - { - /// - public string Url { get; set; } + /// + public string Url { get; set; } = null!; - /// - public string Method { get; set; } + /// + public string Method { get; set; } = null!; - /// - public IDictionary>? Headers { get; set; } + /// + public IDictionary>? Headers { get; set; } - /// - public IBodyData? BodyData { get; set; } + /// + public IBodyData? BodyData { get; set; } - /// - public bool? UseTransformer { get; set; } + /// + public bool? UseTransformer { get; set; } - /// - public TransformerType TransformerType { get; set; } + /// + public TransformerType TransformerType { get; set; } - /// - public ReplaceNodeOptions TransformerReplaceNodeOptions { get; set; } - } + /// + public ReplaceNodeOptions TransformerReplaceNodeOptions { get; set; } + + /// + public int? Delay { get; set; } + + /// + public int? MinimumRandomDelay { get; set; } + + /// + public int? MaximumRandomDelay { get; set; } } \ No newline at end of file diff --git a/src/WireMock.Net/Owin/WireMockMiddleware.cs b/src/WireMock.Net/Owin/WireMockMiddleware.cs index c00dd708..e0e088bf 100644 --- a/src/WireMock.Net/Owin/WireMockMiddleware.cs +++ b/src/WireMock.Net/Owin/WireMockMiddleware.cs @@ -11,8 +11,8 @@ using WireMock.Serialization; using WireMock.Types; using WireMock.ResponseBuilders; using WireMock.Settings; +using System.Collections.Generic; #if !USE_ASPNETCORE -using Microsoft.Owin; using IContext = Microsoft.Owin.IOwinContext; using OwinMiddleware = 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}"); response = ResponseMessageBuilder.Create(ex.Message, 500); } + finally { var log = new LogEntry @@ -201,20 +202,46 @@ namespace WireMock.Owin private async Task SendToWebhooksAsync(IMapping mapping, IRequestMessage request, IResponseMessage response) { + var tasks = new List>(); for (int index = 0; index < mapping.Webhooks?.Length; index++) { var httpClientForWebhook = HttpClientBuilder.Build(mapping.Settings.WebhookSettings ?? new WebhookSettings()); 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 { - 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) diff --git a/src/WireMock.Net/Proxy/ProxyHelper.cs b/src/WireMock.Net/Proxy/ProxyHelper.cs index c1847355..600d628a 100644 --- a/src/WireMock.Net/Proxy/ProxyHelper.cs +++ b/src/WireMock.Net/Proxy/ProxyHelper.cs @@ -119,6 +119,7 @@ internal class ProxyHelper nextState: null, stateTimes: null, webhooks: null, + useWebhooksFireAndForget: null, timeSettings: null ); } diff --git a/src/WireMock.Net/Serialization/MappingConverter.cs b/src/WireMock.Net/Serialization/MappingConverter.cs index 2f8c08d0..96dcf870 100644 --- a/src/WireMock.Net/Serialization/MappingConverter.cs +++ b/src/WireMock.Net/Serialization/MappingConverter.cs @@ -43,6 +43,7 @@ internal class MappingConverter TimeSettings = TimeSettingsMapper.Map(mapping.TimeSettings), Title = mapping.Title, Description = mapping.Description, + UseWebhooksFireAndForget = mapping.UseWebhooksFireAndForget, Priority = mapping.Priority != 0 ? mapping.Priority : null, Scenario = mapping.Scenario, WhenStateIs = mapping.ExecutionConditionState, @@ -168,7 +169,7 @@ internal class MappingConverter mappingModel.Response.BodyDestination = response.ResponseMessage.BodyDestination; 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); } diff --git a/src/WireMock.Net/Serialization/WebhookMapper.cs b/src/WireMock.Net/Serialization/WebhookMapper.cs index ce049e2b..ba72e9b1 100644 --- a/src/WireMock.Net/Serialization/WebhookMapper.cs +++ b/src/WireMock.Net/Serialization/WebhookMapper.cs @@ -20,6 +20,9 @@ internal static class WebhookMapper { Url = model.Request.Url, 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(x.Value)) ?? new Dictionary>() } }; @@ -27,6 +30,7 @@ internal static class WebhookMapper if (model.Request.UseTransformer == true) { webhook.Request.UseTransformer = true; + if (!Enum.TryParse(model.Request.TransformerType, out var transformerType)) { transformerType = TransformerType.Handlebars; @@ -37,7 +41,6 @@ internal static class WebhookMapper { option = ReplaceNodeOptions.None; } - webhook.Request.TransformerReplaceNodeOptions = option; } @@ -72,7 +75,7 @@ internal static class WebhookMapper public static WebhookModel Map(IWebhook webhook) { Guard.NotNull(webhook); - + var model = new WebhookModel { Request = new WebhookRequestModel @@ -82,7 +85,10 @@ internal static class WebhookMapper Headers = webhook.Request.Headers?.ToDictionary(x => x.Key, x => x.Value.ToString()), UseTransformer = webhook.Request.UseTransformer, TransformerType = webhook.Request.UseTransformer == true ? webhook.Request.TransformerType.ToString() : null, - TransformerReplaceNodeOptions = webhook.Request.TransformerReplaceNodeOptions.ToString() + TransformerReplaceNodeOptions = webhook.Request.TransformerReplaceNodeOptions.ToString(), + Delay = webhook.Request.Delay, + MinimumRandomDelay = webhook.Request.MinimumRandomDelay, + MaximumRandomDelay = webhook.Request.MaximumRandomDelay, } }; diff --git a/src/WireMock.Net/Server/IRespondWithAProvider.cs b/src/WireMock.Net/Server/IRespondWithAProvider.cs index 327f397b..0efa2a92 100644 --- a/src/WireMock.Net/Server/IRespondWithAProvider.cs +++ b/src/WireMock.Net/Server/IRespondWithAProvider.cs @@ -123,6 +123,13 @@ namespace WireMock.Server /// The . IRespondWithAProvider WithWebhook(params IWebhook[] webhooks); + /// + /// Support FireAndForget for any configured Webhooks + /// + /// + /// + IRespondWithAProvider WithWebhookFireAndForget(bool UseWebhooksFireAndForget); + /// /// Add a Webhook to call after the response has been generated. /// diff --git a/src/WireMock.Net/Server/RespondWithAProvider.cs b/src/WireMock.Net/Server/RespondWithAProvider.cs index 508d8478..d2125bee 100644 --- a/src/WireMock.Net/Server/RespondWithAProvider.cs +++ b/src/WireMock.Net/Server/RespondWithAProvider.cs @@ -30,6 +30,8 @@ internal class RespondWithAProvider : IRespondWithAProvider private readonly WireMockServerSettings _settings; private readonly bool _saveToFile; + private bool _useWebhookFireAndForget = false; + public Guid Guid { get; private set; } = Guid.NewGuid(); public IWebhook[]? Webhooks { get; private set; } @@ -57,7 +59,7 @@ internal class RespondWithAProvider : IRespondWithAProvider /// The 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); } /// @@ -233,6 +235,13 @@ internal class RespondWithAProvider : IRespondWithAProvider return this; } + public IRespondWithAProvider WithWebhookFireAndForget(bool useWebhooksFireAndForget) + { + _useWebhookFireAndForget = useWebhooksFireAndForget; + + return this; + } + private static IWebhook InitWebhook( string url, string method, diff --git a/test/WireMock.Net.Tests/Owin/WireMockMiddlewareTests.cs b/test/WireMock.Net.Tests/Owin/WireMockMiddlewareTests.cs index 0b57c5d1..c070fc54 100644 --- a/test/WireMock.Net.Tests/Owin/WireMockMiddlewareTests.cs +++ b/test/WireMock.Net.Tests/Owin/WireMockMiddlewareTests.cs @@ -176,7 +176,7 @@ namespace WireMock.Net.Tests.Owin _mappingMock.SetupGet(m => m.Provider).Returns(responseBuilder); _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())).ReturnsAsync((new ResponseMessage(), newMappingFromProxy)); 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.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())).ReturnsAsync((new ResponseMessage(), newMappingFromProxy)); var requestBuilder = Request.Create().UsingAnyMethod(); diff --git a/test/WireMock.Net.Tests/Serialization/MappingConverterTests.cs b/test/WireMock.Net.Tests/Serialization/MappingConverterTests.cs index 5e02ad43..b54a513a 100644 --- a/test/WireMock.Net.Tests/Serialization/MappingConverterTests.cs +++ b/test/WireMock.Net.Tests/Serialization/MappingConverterTests.cs @@ -11,286 +11,287 @@ using WireMock.Types; using WireMock.Util; 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; - - public MappingConverterTests() + [Fact] + public void ToMappingModel_With_SingleWebHook() + { + // Assign + var request = Request.Create(); + var response = Response.Create(); + var webhooks = new IWebhook[] { - _sut = new MappingConverter(new MatcherMapper(_settings)); - } - - [Fact] - public void ToMappingModel_With_SingleWebHook() - { - // Assign - var request = Request.Create(); - var response = Response.Create(); - var webhooks = new IWebhook[] + new Webhook { - new Webhook + Request = new WebhookRequest { - Request = new WebhookRequest + Url = "https://test.com", + Headers = new Dictionary> { - Url = "https://test.com", - Headers = new Dictionary> - { - { "Single", new WireMockList("x") }, - { "Multi", new WireMockList("a", "b") } - }, - Method = "post", - BodyData = new BodyData - { - BodyAsString = "b", - DetectedBodyType = BodyType.String, - DetectedBodyTypeFromContentType = BodyType.String - } + { "Single", new WireMockList("x") }, + { "Multi", new WireMockList("a", "b") } + }, + Method = "post", + BodyData = new BodyData + { + 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> - { - { "One", new WireMockList("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> - { - { "First", new WireMockList("x") }, - { "Second", new WireMockList("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] - public void ToMappingModel_WithDelayAsMilleSeconds_ReturnsCorrectModel() + // 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().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> + { + { "One", new WireMockList("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> + { + { "First", new WireMockList("x") }, + { "Second", new WireMockList("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 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, null); + 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, 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, 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); + model.Response.Delay.Should().Be(test.Expected); } } + + [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); + } } \ No newline at end of file diff --git a/test/WireMock.Net.Tests/Serialization/WebhookMapperTests.cs b/test/WireMock.Net.Tests/Serialization/WebhookMapperTests.cs index dc6df4e1..54d35956 100644 --- a/test/WireMock.Net.Tests/Serialization/WebhookMapperTests.cs +++ b/test/WireMock.Net.Tests/Serialization/WebhookMapperTests.cs @@ -1,101 +1,145 @@ -using System.Collections.Generic; +using System.Collections.Generic; using FluentAssertions; using WireMock.Admin.Mappings; +using WireMock.Models; using WireMock.Serialization; using WireMock.Types; +using WireMock.Util; 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] - public void WebhookMapper_Map_Model_BodyAsString_And_UseTransformerIsFalse() + // Assign + var model = new WebhookModel { - // Assign - var model = new WebhookModel + Request = new WebhookRequestModel { - Request = new WebhookRequestModel + Url = "https://localhost", + Method = "get", + Headers = new Dictionary { - Url = "https://localhost", - Method = "get", - Headers = new Dictionary - { - { "x", "y" } - }, - 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.Method.Should().Be("get"); - result.Request.Headers.Should().HaveCount(1); - result.Request.BodyData.BodyAsJson.Should().BeNull(); - result.Request.BodyData.BodyAsString.Should().Be("test"); - result.Request.BodyData.DetectedBodyType.Should().Be(BodyType.String); - result.Request.UseTransformer.Should().BeNull(); - } - - [Fact] - public void WebhookMapper_Map_Model_BodyAsString_And_UseTransformerIsTrue() - { - // Assign - var model = new WebhookModel - { - Request = new WebhookRequestModel - { - Url = "https://localhost", - Method = "get", - Headers = new Dictionary - { - { "x", "y" } - }, - Body = "test", - UseTransformer = true - } - }; - - var result = WebhookMapper.Map(model); - - result.Request.Url.Should().Be("https://localhost"); - result.Request.Method.Should().Be("get"); - result.Request.Headers.Should().HaveCount(1); - result.Request.BodyData.BodyAsJson.Should().BeNull(); - result.Request.BodyData.BodyAsString.Should().Be("test"); - result.Request.BodyData.DetectedBodyType.Should().Be(BodyType.String); - result.Request.UseTransformer.Should().BeTrue(); - result.Request.TransformerType.Should().Be(TransformerType.Handlebars); - } - - [Fact] - public void WebhookMapper_Map_Model_BodyAsJson() - { - // Assign - var model = new WebhookModel - { - Request = new WebhookRequestModel - { - Url = "https://localhost", - Method = "get", - Headers = new Dictionary - { - { "x", "y" } - }, - BodyAsJson = new { n = 12345 } - } - }; - - var result = WebhookMapper.Map(model); - - result.Request.Url.Should().Be("https://localhost"); - result.Request.Method.Should().Be("get"); - result.Request.Headers.Should().HaveCount(1); - result.Request.BodyData.BodyAsString.Should().BeNull(); - result.Request.BodyData.BodyAsJson.Should().NotBeNull(); - result.Request.BodyData.DetectedBodyType.Should().Be(BodyType.Json); - } + result.Request.Url.Should().Be("https://localhost"); + result.Request.Method.Should().Be("get"); + result.Request.Headers.Should().HaveCount(1); + result.Request.BodyData!.BodyAsJson.Should().BeNull(); + result.Request.BodyData.BodyAsString.Should().Be("test"); + result.Request.BodyData.DetectedBodyType.Should().Be(BodyType.String); + result.Request.UseTransformer.Should().BeNull(); } -} + + [Fact] + public void WebhookMapper_Map_WebhookModel_BodyAsString_And_UseTransformerIsTrue() + { + // Assign + var model = new WebhookModel + { + Request = new WebhookRequestModel + { + Url = "https://localhost", + Method = "get", + Headers = new Dictionary + { + { "x", "y" } + }, + Body = "test", + UseTransformer = true + } + }; + + var result = WebhookMapper.Map(model); + + result.Request.Url.Should().Be("https://localhost"); + result.Request.Method.Should().Be("get"); + result.Request.Headers.Should().HaveCount(1); + result.Request.BodyData!.BodyAsJson.Should().BeNull(); + result.Request.BodyData.BodyAsString.Should().Be("test"); + result.Request.BodyData.DetectedBodyType.Should().Be(BodyType.String); + result.Request.UseTransformer.Should().BeTrue(); + result.Request.TransformerType.Should().Be(TransformerType.Handlebars); + } + + [Fact] + public void WebhookMapper_Map_WebhookModel_BodyAsJson() + { + // Assign + var model = new WebhookModel + { + Request = new WebhookRequestModel + { + Url = "https://localhost", + Method = "get", + Headers = new Dictionary + { + { "x", "y" } + }, + BodyAsJson = new { n = 12345 }, + Delay = 4, + MinimumRandomDelay = 5, + MaximumRandomDelay = 6 + }, + }; + + var result = WebhookMapper.Map(model); + + result.Request.Url.Should().Be("https://localhost"); + result.Request.Method.Should().Be("get"); + result.Request.Headers.Should().HaveCount(1); + result.Request.BodyData!.BodyAsString.Should().BeNull(); + result.Request.BodyData.BodyAsJson.Should().NotBeNull(); + 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> + { + { "x", new WireMockList("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); + } +} \ No newline at end of file