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