From 4d0373d4ca4fb2fe0004606ebf38a56527f02a62 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Sat, 1 Aug 2020 18:40:35 +0200 Subject: [PATCH] Scenario : stay on current state for a number of times (#495) * state * xml comment --- .../Admin/Scenarios/ScenarioStateModel.cs | 5 ++ .../Server/IWireMockServer.cs | 6 +- src/WireMock.Net/IMapping.cs | 6 ++ src/WireMock.Net/Mapping.cs | 7 +- src/WireMock.Net/Owin/MappingMatcher.cs | 17 +++- src/WireMock.Net/Owin/WireMockMiddleware.cs | 25 +++++- src/WireMock.Net/ScenarioState.cs | 5 ++ .../Server/IRespondWithAProvider.cs | 6 +- .../Server/RespondWithAProvider.cs | 14 ++-- .../Server/WireMockServer.Admin.cs | 5 +- .../Serialization/MappingConverterTests.cs | 4 +- .../StatefulBehaviorTests.cs | 84 +++++++++++++++++++ 12 files changed, 162 insertions(+), 22 deletions(-) diff --git a/src/WireMock.Net.Abstractions/Admin/Scenarios/ScenarioStateModel.cs b/src/WireMock.Net.Abstractions/Admin/Scenarios/ScenarioStateModel.cs index a6b1fa9e..fb43a9f4 100644 --- a/src/WireMock.Net.Abstractions/Admin/Scenarios/ScenarioStateModel.cs +++ b/src/WireMock.Net.Abstractions/Admin/Scenarios/ScenarioStateModel.cs @@ -24,5 +24,10 @@ /// Gets or sets a value indicating whether this is finished. /// public bool Finished { get; set; } + + /// + /// Gets or sets the state counter. + /// + public int Counter { get; set; } } } \ No newline at end of file diff --git a/src/WireMock.Net.Abstractions/Server/IWireMockServer.cs b/src/WireMock.Net.Abstractions/Server/IWireMockServer.cs index ca822beb..7848b3df 100644 --- a/src/WireMock.Net.Abstractions/Server/IWireMockServer.cs +++ b/src/WireMock.Net.Abstractions/Server/IWireMockServer.cs @@ -27,9 +27,9 @@ namespace WireMock.Server /// IEnumerable MappingModels { get; } - /// - /// Gets the mappings. - /// + // + // Gets the mappings. + // //[PublicAPI] //IEnumerable Mappings { get; } diff --git a/src/WireMock.Net/IMapping.cs b/src/WireMock.Net/IMapping.cs index 71d93743..a28bd526 100644 --- a/src/WireMock.Net/IMapping.cs +++ b/src/WireMock.Net/IMapping.cs @@ -51,6 +51,12 @@ namespace WireMock [CanBeNull] string NextState { get; } + /// + /// The number of times this match should be matched before the state will be changed to the next state. + /// + [CanBeNull] + int? StateTimes { get; } + /// /// The Request matcher. /// diff --git a/src/WireMock.Net/Mapping.cs b/src/WireMock.Net/Mapping.cs index 343c5efd..6c215be0 100644 --- a/src/WireMock.Net/Mapping.cs +++ b/src/WireMock.Net/Mapping.cs @@ -33,6 +33,9 @@ namespace WireMock /// public string NextState { get; } + /// + public int? StateTimes { get; } + /// public IRequestMatcher RequestMatcher { get; } @@ -64,9 +67,10 @@ namespace WireMock /// The scenario. [Optional] /// State in which the current mapping can occur. [Optional] /// 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] public Mapping(Guid guid, [CanBeNull] string title, [CanBeNull] string path, [NotNull] IWireMockServerSettings settings, [NotNull] IRequestMatcher requestMatcher, [NotNull] IResponseProvider provider, - int priority, [CanBeNull] string scenario, [CanBeNull] string executionConditionState, [CanBeNull] string nextState) + int priority, [CanBeNull] string scenario, [CanBeNull] string executionConditionState, [CanBeNull] string nextState, [CanBeNull] int? stateTimes) { Guid = guid; Title = title; @@ -78,6 +82,7 @@ namespace WireMock Scenario = scenario; ExecutionConditionState = executionConditionState; NextState = nextState; + StateTimes = stateTimes; } /// diff --git a/src/WireMock.Net/Owin/MappingMatcher.cs b/src/WireMock.Net/Owin/MappingMatcher.cs index 681b1570..71b4d814 100644 --- a/src/WireMock.Net/Owin/MappingMatcher.cs +++ b/src/WireMock.Net/Owin/MappingMatcher.cs @@ -23,12 +23,12 @@ namespace WireMock.Owin { try { - string scenario = mapping.Scenario != null && _options.Scenarios.ContainsKey(mapping.Scenario) ? _options.Scenarios[mapping.Scenario].NextState : null; + string nextState = GetNextState(mapping); mappings.Add(new MappingMatcherResult { Mapping = mapping, - RequestMatchResult = mapping.GetRequestMatchResult(request, scenario) + RequestMatchResult = mapping.GetRequestMatchResult(request, nextState) }); } catch (Exception ex) @@ -56,5 +56,18 @@ namespace WireMock.Owin return (match, partialMatch); } + + private string GetNextState(IMapping mapping) + { + // If the mapping does not have a scenario or _options.Scenarios does not contain this scenario from the mapping, + // just return null to indicate that there is no next state. + if (mapping.Scenario == null || !_options.Scenarios.ContainsKey(mapping.Scenario)) + { + return null; + } + + // Else just return the next state + return _options.Scenarios[mapping.Scenario].NextState; + } } } \ No newline at end of file diff --git a/src/WireMock.Net/Owin/WireMockMiddleware.cs b/src/WireMock.Net/Owin/WireMockMiddleware.cs index 9cf01318..cfc708cf 100644 --- a/src/WireMock.Net/Owin/WireMockMiddleware.cs +++ b/src/WireMock.Net/Owin/WireMockMiddleware.cs @@ -79,7 +79,7 @@ namespace WireMock.Owin { foreach (var mapping in _options.Mappings.Values.Where(m => m?.Scenario != null)) { - // Set start + // Set scenario start if (!_options.Scenarios.ContainsKey(mapping.Scenario) && mapping.IsStartState) { _options.Scenarios.TryAdd(mapping.Scenario, new ScenarioState @@ -122,9 +122,7 @@ namespace WireMock.Owin if (targetMapping.Scenario != null) { - _options.Scenarios[targetMapping.Scenario].NextState = targetMapping.NextState; - _options.Scenarios[targetMapping.Scenario].Started = true; - _options.Scenarios[targetMapping.Scenario].Finished = targetMapping.NextState == null; + UpdateScenarioState(targetMapping); } } catch (Exception ex) @@ -157,6 +155,25 @@ namespace WireMock.Owin await CompletedTask; } + private void UpdateScenarioState(IMapping mapping) + { + var scenario = _options.Scenarios[mapping.Scenario]; + + // Increase the number of times this state has been executed + scenario.Counter++; + + // Only if the number of times this state is executed equals the required StateTimes, proceed to next state and reset the counter to 0 + if (scenario.Counter == (mapping.StateTimes ?? 1)) + { + scenario.NextState = mapping.NextState; + scenario.Counter = 0; + } + + // Else just update Started and Finished + scenario.Started = true; + scenario.Finished = mapping.NextState == null; + } + private void LogRequest(LogEntry entry, bool addRequest) { _options.Logger.DebugRequestResponse(LogEntryMapper.Map(entry), entry.RequestMessage.Path.StartsWith("/__admin/")); diff --git a/src/WireMock.Net/ScenarioState.cs b/src/WireMock.Net/ScenarioState.cs index 8a28da93..d4671471 100644 --- a/src/WireMock.Net/ScenarioState.cs +++ b/src/WireMock.Net/ScenarioState.cs @@ -24,5 +24,10 @@ /// Gets or sets a value indicating whether this is finished. /// public bool Finished { get; set; } + + /// + /// Gets or sets the state counter. + /// + public int Counter { get; set; } } } \ No newline at end of file diff --git a/src/WireMock.Net/Server/IRespondWithAProvider.cs b/src/WireMock.Net/Server/IRespondWithAProvider.cs index 18c09520..50ea594d 100644 --- a/src/WireMock.Net/Server/IRespondWithAProvider.cs +++ b/src/WireMock.Net/Server/IRespondWithAProvider.cs @@ -86,14 +86,16 @@ namespace WireMock.Server /// Once this mapping is executed the state will be changed to specified one. /// /// Any object which identifies the new state + /// The number of times this match should be matched before the state will be changed to the specified one. Default value is 1. /// The . - IRespondWithAProvider WillSetStateTo(string state); + IRespondWithAProvider WillSetStateTo(string state, int? times = 1); /// /// Once this mapping is executed the state will be changed to specified one. /// /// Any object which identifies the new state + /// The number of times this match should be matched before the state will be changed to the specified one. Default value is 1. /// The . - IRespondWithAProvider WillSetStateTo(int state); + IRespondWithAProvider WillSetStateTo(int state, int? times = 1); } } \ No newline at end of file diff --git a/src/WireMock.Net/Server/RespondWithAProvider.cs b/src/WireMock.Net/Server/RespondWithAProvider.cs index 6b18baaa..b2c303d6 100644 --- a/src/WireMock.Net/Server/RespondWithAProvider.cs +++ b/src/WireMock.Net/Server/RespondWithAProvider.cs @@ -18,6 +18,7 @@ namespace WireMock.Server private string _executionConditionState; private string _nextState; private string _scenario; + private int _timesInSameState = 1; private readonly RegistrationCallback _registrationCallback; private readonly IRequestMatcher _requestMatcher; private readonly IWireMockServerSettings _settings; @@ -46,7 +47,7 @@ namespace WireMock.Server /// The provider. public void RespondWith(IResponseProvider provider) { - _registrationCallback(new Mapping(Guid, _title, _path, _settings, _requestMatcher, provider, _priority, _scenario, _executionConditionState, _nextState), _saveToFile); + _registrationCallback(new Mapping(Guid, _title, _path, _settings, _requestMatcher, provider, _priority, _scenario, _executionConditionState, _nextState, _timesInSameState), _saveToFile); } /// @@ -120,8 +121,8 @@ namespace WireMock.Server return WhenStateIs(state.ToString()); } - /// - public IRespondWithAProvider WillSetStateTo(string state) + /// + public IRespondWithAProvider WillSetStateTo(string state, int? times = 1) { if (string.IsNullOrEmpty(_scenario)) { @@ -129,14 +130,15 @@ namespace WireMock.Server } _nextState = state; + _timesInSameState = times ?? 1; return this; } - /// - public IRespondWithAProvider WillSetStateTo(int state) + /// + public IRespondWithAProvider WillSetStateTo(int state, int? times = 1) { - return WillSetStateTo(state.ToString()); + return WillSetStateTo(state.ToString(), times); } } } \ No newline at end of file diff --git a/src/WireMock.Net/Server/WireMockServer.Admin.cs b/src/WireMock.Net/Server/WireMockServer.Admin.cs index d673f02d..85d832d5 100644 --- a/src/WireMock.Net/Server/WireMockServer.Admin.cs +++ b/src/WireMock.Net/Server/WireMockServer.Admin.cs @@ -331,7 +331,7 @@ namespace WireMock.Server var response = Response.Create(responseMessage); - return new Mapping(Guid.NewGuid(), string.Empty, null, _settings, request, response, 0, null, null, null); + return new Mapping(Guid.NewGuid(), string.Empty, null, _settings, request, response, 0, null, null, null, null); } #endregion @@ -706,7 +706,8 @@ namespace WireMock.Server Name = s.Name, NextState = s.NextState, Started = s.Started, - Finished = s.Finished + Finished = s.Finished, + Counter = s.Counter }); return ToJson(scenariosStates, true); diff --git a/test/WireMock.Net.Tests/Serialization/MappingConverterTests.cs b/test/WireMock.Net.Tests/Serialization/MappingConverterTests.cs index c5de40c9..31987032 100644 --- a/test/WireMock.Net.Tests/Serialization/MappingConverterTests.cs +++ b/test/WireMock.Net.Tests/Serialization/MappingConverterTests.cs @@ -25,7 +25,7 @@ namespace WireMock.Net.Tests.Serialization // Assign var request = Request.Create(); var response = Response.Create(); - var mapping = new Mapping(Guid.NewGuid(), "", null, _settings, request, response, 0, null, null, null); + var mapping = new Mapping(Guid.NewGuid(), "", null, _settings, request, response, 0, null, null, null, null); // Act var model = _sut.ToMappingModel(mapping); @@ -44,7 +44,7 @@ namespace WireMock.Net.Tests.Serialization // Assign var request = Request.Create(); var response = Response.Create().WithBodyAsJson(new { x = "x" }).WithTransformer(); - var mapping = new Mapping(Guid.NewGuid(), "", null, _settings, request, response, 42, null, null, null); + var mapping = new Mapping(Guid.NewGuid(), "", null, _settings, request, response, 42, null, null, null, null); // Act var model = _sut.ToMappingModel(mapping); diff --git a/test/WireMock.Net.Tests/StatefulBehaviorTests.cs b/test/WireMock.Net.Tests/StatefulBehaviorTests.cs index 4f5c4e6f..ad23eb5e 100644 --- a/test/WireMock.Net.Tests/StatefulBehaviorTests.cs +++ b/test/WireMock.Net.Tests/StatefulBehaviorTests.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Net; using System.Net.Http; using System.Threading.Tasks; +using FluentAssertions; using NFluent; using WireMock.RequestBuilders; using WireMock.ResponseBuilders; @@ -61,6 +62,89 @@ namespace WireMock.Net.Tests Check.That(responseWithState).Equals("Test state msg"); } + [Fact] + public async Task Scenarios_With_Same_Path_Should_Use_Times_When_Moving_To_Next_State() + { + // given + const int times = 2; + string path = $"/foo_{Guid.NewGuid()}"; + string body1 = "Scenario S1, No State, Setting State T2"; + string body2 = "Scenario S1, State T2, End"; + var server = WireMockServer.Start(); + + server + .Given(Request.Create().WithPath(path).UsingGet()) + .InScenario(1) + .WillSetStateTo(2, times) + .RespondWith(Response.Create().WithBody(body1)); + + server + .Given(Request.Create().WithPath(path).UsingGet()) + .InScenario(1) + .WhenStateIs(2) + .RespondWith(Response.Create().WithBody(body2)); + + // when + var client = new HttpClient(); + var responseScenario1 = await client.GetStringAsync("http://localhost:" + server.Ports[0] + path); + var responseScenario2 = await client.GetStringAsync("http://localhost:" + server.Ports[0] + path); + var responseWithState = await client.GetStringAsync("http://localhost:" + server.Ports[0] + path); + + // then + responseScenario1.Should().Be(body1); + responseScenario2.Should().Be(body1); + responseWithState.Should().Be(body2); + } + + [Fact] + public async Task Scenarios_With_Different_Paths_Should_Use_Times_When_Moving_To_Next_State() + { + // given + const int times = 2; + string path1 = $"/a_{Guid.NewGuid()}"; + string path2 = $"/b_{Guid.NewGuid()}"; + string path3 = $"/c_{Guid.NewGuid()}"; + string body1 = "Scenario S1, No State, Setting State T2"; + string body2 = "Scenario S1, State T2, Setting State T3"; + string body3 = "Scenario S1, State T3, End"; + + var server = WireMockServer.Start(); + + server + .Given(Request.Create().WithPath(path1).UsingGet()) + .InScenario("S1") + .WillSetStateTo("T2", times) + .RespondWith(Response.Create().WithBody(body1)); + + server + .Given(Request.Create().WithPath(path2).UsingGet()) + .InScenario("S1") + .WhenStateIs("T2") + .WillSetStateTo("T3", times) + .RespondWith(Response.Create().WithBody(body2)); + + server + .Given(Request.Create().WithPath(path3).UsingGet()) + .InScenario("S1") + .WhenStateIs("T3") + .RespondWith(Response.Create().WithBody(body3)); + + // when + var client = new HttpClient(); + var t1a = await client.GetStringAsync("http://localhost:" + server.Ports[0] + path1); + var t1b = await client.GetStringAsync("http://localhost:" + server.Ports[0] + path1); + var t2a = await client.GetStringAsync("http://localhost:" + server.Ports[0] + path2); + var t2b = await client.GetStringAsync("http://localhost:" + server.Ports[0] + path2); + var t3 = await client.GetStringAsync("http://localhost:" + server.Ports[0] + path3); + + // then + t1a.Should().Be(body1); + t1b.Should().Be(body1); + t2a.Should().Be(body2); + t2b.Should().Be(body2); + t3.Should().Be(body3); + } + [Fact] public async Task Scenarios_Should_Respect_Int_Valued_Scenarios_and_States() {