diff --git a/src/WireMock.Net.Abstractions/Admin/Mappings/ResponseModel.cs b/src/WireMock.Net.Abstractions/Admin/Mappings/ResponseModel.cs index f054487b..c3acc208 100644 --- a/src/WireMock.Net.Abstractions/Admin/Mappings/ResponseModel.cs +++ b/src/WireMock.Net.Abstractions/Admin/Mappings/ResponseModel.cs @@ -83,6 +83,16 @@ namespace WireMock.Admin.Mappings /// 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; } + /// /// Gets or sets the Proxy URL. /// diff --git a/src/WireMock.Net/ResponseBuilders/IDelayResponseBuilder.cs b/src/WireMock.Net/ResponseBuilders/IDelayResponseBuilder.cs index fe0d3b1c..3682f90e 100644 --- a/src/WireMock.Net/ResponseBuilders/IDelayResponseBuilder.cs +++ b/src/WireMock.Net/ResponseBuilders/IDelayResponseBuilder.cs @@ -20,5 +20,13 @@ namespace WireMock.ResponseBuilders /// The milliseconds to delay. /// The . IResponseBuilder WithDelay(int milliseconds); + + /// + /// Introduce random delay + /// + /// Minimum milliseconds to delay + /// Maximum milliseconds to delay + /// The . + IResponseBuilder WithRandomDelay(int minimumMilliseconds = 0, int maximumMilliseconds = 60_000); } } \ No newline at end of file diff --git a/src/WireMock.Net/ResponseBuilders/Response.cs b/src/WireMock.Net/ResponseBuilders/Response.cs index 33f90bc4..9fe97fac 100644 --- a/src/WireMock.Net/ResponseBuilders/Response.cs +++ b/src/WireMock.Net/ResponseBuilders/Response.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using System.Net; using System.Text; +using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; using WireMock.Proxy; @@ -24,10 +25,40 @@ namespace WireMock.ResponseBuilders /// public partial class Response : IResponseBuilder { + private static readonly ThreadLocal Random = new ThreadLocal(() => new Random(DateTime.UtcNow.Millisecond)); + + private TimeSpan? _delay; + + /// + /// The minimum random delay in milliseconds. + /// + public int? MinimumDelayMilliseconds { get; private set; } + + /// + /// The maximum random delay in milliseconds. + /// + public int? MaximumDelayMilliseconds { get; private set; } + /// /// The delay /// - public TimeSpan? Delay { get; private set; } + public TimeSpan? Delay + { + get + { + if (MinimumDelayMilliseconds != null && MaximumDelayMilliseconds != null) + { + return TimeSpan.FromMilliseconds(Random.Value.Next(MinimumDelayMilliseconds.Value, MaximumDelayMilliseconds.Value)); + } + + return _delay; + } + + private set + { + _delay = value; + } + } /// /// Gets a value indicating whether [use transformer]. @@ -334,6 +365,18 @@ namespace WireMock.ResponseBuilders return WithDelay(TimeSpan.FromMilliseconds(milliseconds)); } + /// + public IResponseBuilder WithRandomDelay(int minimumMilliseconds = 0, int maximumMilliseconds = 60_000) + { + Check.Condition(minimumMilliseconds, min => min >= 0, nameof(minimumMilliseconds)); + Check.Condition(maximumMilliseconds, max => max > minimumMilliseconds, nameof(maximumMilliseconds)); + + MinimumDelayMilliseconds = minimumMilliseconds; + MaximumDelayMilliseconds = maximumMilliseconds; + + return this; + } + /// public async Task<(ResponseMessage Message, IMapping Mapping)> ProvideResponseAsync(RequestMessage requestMessage, IWireMockServerSettings settings) { diff --git a/src/WireMock.Net/Serialization/MappingConverter.cs b/src/WireMock.Net/Serialization/MappingConverter.cs index 229d4ad4..d74fe17d 100644 --- a/src/WireMock.Net/Serialization/MappingConverter.cs +++ b/src/WireMock.Net/Serialization/MappingConverter.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using WireMock.Admin.Mappings; using WireMock.Matchers.Request; @@ -6,7 +7,6 @@ using WireMock.RequestBuilders; using WireMock.ResponseBuilders; using WireMock.Settings; using WireMock.Types; -using WireMock.Validation; namespace WireMock.Serialization { @@ -16,9 +16,7 @@ namespace WireMock.Serialization public MappingConverter(MatcherMapper mapper) { - Check.NotNull(mapper, nameof(mapper)); - - _mapper = mapper; + _mapper = mapper ?? throw new ArgumentNullException(nameof(mapper)); } public MappingModel ToMappingModel(IMapping mapping) @@ -81,12 +79,19 @@ namespace WireMock.Serialization Matchers = _mapper.Map(pm.Matchers) }).ToList() : null }, - Response = new ResponseModel - { - Delay = (int?)response.Delay?.TotalMilliseconds - } + Response = new ResponseModel() }; + if (response.MinimumDelayMilliseconds >= 0 || response.MaximumDelayMilliseconds > 0) + { + mappingModel.Response.MinimumRandomDelay = response.MinimumDelayMilliseconds; + mappingModel.Response.MaximumRandomDelay = response.MaximumDelayMilliseconds; + } + else + { + mappingModel.Response.Delay = (int?)response.Delay?.TotalMilliseconds; + } + if (mapping.Webhooks?.Length == 1) { mappingModel.Webhook = WebhookMapper.Map(mapping.Webhooks[0]); diff --git a/src/WireMock.Net/Server/WireMockServer.Admin.cs b/src/WireMock.Net/Server/WireMockServer.Admin.cs index eda99b6b..0c6034ec 100644 --- a/src/WireMock.Net/Server/WireMockServer.Admin.cs +++ b/src/WireMock.Net/Server/WireMockServer.Admin.cs @@ -782,6 +782,10 @@ namespace WireMock.Server { responseBuilder = responseBuilder.WithDelay(responseModel.Delay.Value); } + else if (responseModel.MinimumRandomDelay >= 0 || responseModel.MaximumRandomDelay > 0) + { + responseBuilder = responseBuilder.WithRandomDelay(responseModel.MinimumRandomDelay ?? 0, responseModel.MaximumRandomDelay ?? 60_000); + } if (responseModel.UseTransformer == true) { diff --git a/test/WireMock.Net.Tests/Serialization/MappingConverterTests.cs b/test/WireMock.Net.Tests/Serialization/MappingConverterTests.cs index de2a4776..c820a41a 100644 --- a/test/WireMock.Net.Tests/Serialization/MappingConverterTests.cs +++ b/test/WireMock.Net.Tests/Serialization/MappingConverterTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using FluentAssertions; using WireMock.Models; @@ -163,5 +163,61 @@ namespace WireMock.Net.Tests.Serialization model.Priority.Should().Be(42); model.Response.UseTransformer.Should().BeTrue(); } + + [Fact] + public void ToMappingModel_WithDelay_ReturnsCorrectModel() + { + // Assign + int delay = 1000; + var request = Request.Create(); + var response = Response.Create().WithDelay(delay); + var mapping = new Mapping(Guid.NewGuid(), "", null, _settings, request, response, 42, null, null, null, null, null); + + // Act + var model = _sut.ToMappingModel(mapping); + + // Assert + model.Should().NotBeNull(); + model.Response.Delay.Should().Be(delay); + } + + [Fact] + public void ToMappingModel_WithRandomMininumDelay_ReturnsCorrectModel() + { + // Assign + int minimumDelay = 1000; + var request = Request.Create(); + var response = Response.Create().WithRandomDelay(minimumDelay); + var mapping = new Mapping(Guid.NewGuid(), "", null, _settings, request, response, 42, 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(), "", null, _settings, request, response, 42, 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); + } } } \ No newline at end of file diff --git a/test/WireMock.Net.Tests/WireMockServerTests.cs b/test/WireMock.Net.Tests/WireMockServerTests.cs index 5cb939b8..be1123f3 100644 --- a/test/WireMock.Net.Tests/WireMockServerTests.cs +++ b/test/WireMock.Net.Tests/WireMockServerTests.cs @@ -115,6 +115,38 @@ namespace WireMock.Net.Tests server.Stop(); } + [Fact] + public async Task WireMockServer_Should_randomly_delay_responses_for_a_given_route() + { + // Arrange + var server = WireMockServer.Start(); + + server + .Given(Request.Create() + .WithPath("/*")) + .RespondWith(Response.Create() + .WithBody(@"{ msg: ""Hello world!""}") + .WithRandomDelay(10, 1000)); + + var watch = new Stopwatch(); + watch.Start(); + + var httClient = new HttpClient(); + async Task ExecuteTimedRequestAsync() + { + watch.Reset(); + await httClient.GetStringAsync("http://localhost:" + server.Ports[0] + "/foo"); + return watch.ElapsedMilliseconds; + } + + // Act + await ExecuteTimedRequestAsync(); + await ExecuteTimedRequestAsync(); + await ExecuteTimedRequestAsync(); + + server.Stop(); + } + [Fact] public async Task WireMockServer_Should_delay_responses() {