diff --git a/Directory.Build.props b/Directory.Build.props index edfd5fe4d..819729321 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -4,7 +4,7 @@ - 1.8.12 + 1.8.13-preview-02 WireMock.Net-Logo.png https://github.com/wiremock/WireMock.Net Apache-2.0 diff --git a/examples/WireMock.Net.ConsoleApp.UsingNuGet/WireMock.Net.ConsoleApp.UsingNuGet.csproj b/examples/WireMock.Net.ConsoleApp.UsingNuGet/WireMock.Net.ConsoleApp.UsingNuGet.csproj index ba3f5902f..fdc43fac2 100644 --- a/examples/WireMock.Net.ConsoleApp.UsingNuGet/WireMock.Net.ConsoleApp.UsingNuGet.csproj +++ b/examples/WireMock.Net.ConsoleApp.UsingNuGet/WireMock.Net.ConsoleApp.UsingNuGet.csproj @@ -11,6 +11,10 @@ + + + + diff --git a/src/WireMock.Net.Abstractions/Admin/Scenarios/ScenarioStateUpdateModel.cs b/src/WireMock.Net.Abstractions/Admin/Scenarios/ScenarioStateUpdateModel.cs new file mode 100644 index 000000000..149003d41 --- /dev/null +++ b/src/WireMock.Net.Abstractions/Admin/Scenarios/ScenarioStateUpdateModel.cs @@ -0,0 +1,15 @@ +// Copyright © WireMock.Net + +namespace WireMock.Admin.Scenarios; + +/// +/// ScenarioStateModel +/// +[FluentBuilder.AutoGenerateBuilder] +public class ScenarioStateUpdateModel +{ + /// + /// Gets or sets the NextState. + /// + public string? State { 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 f296550e7..03033a7a8 100644 --- a/src/WireMock.Net.Abstractions/Server/IWireMockServer.cs +++ b/src/WireMock.Net.Abstractions/Server/IWireMockServer.cs @@ -161,6 +161,11 @@ public interface IWireMockServer : IDisposable /// bool ResetScenario(string name); + /// + /// Sets a scenario to a state. + /// + bool SetScenarioState(string name, string? state); + /// /// Resets the LogEntries. /// diff --git a/src/WireMock.Net.Minimal/IMapping.cs b/src/WireMock.Net.Minimal/IMapping.cs index c0d70b72b..99fff5a18 100644 --- a/src/WireMock.Net.Minimal/IMapping.cs +++ b/src/WireMock.Net.Minimal/IMapping.cs @@ -86,7 +86,7 @@ public interface IMapping WireMockServerSettings Settings { get; } /// - /// Is State started ? + /// Indicates if the state is started or manually set to a value. /// bool IsStartState { get; } diff --git a/src/WireMock.Net.Minimal/Server/WireMockServer.Admin.cs b/src/WireMock.Net.Minimal/Server/WireMockServer.Admin.cs index f77e189df..5c248a26c 100644 --- a/src/WireMock.Net.Minimal/Server/WireMockServer.Admin.cs +++ b/src/WireMock.Net.Minimal/Server/WireMockServer.Admin.cs @@ -66,6 +66,7 @@ public AdminPaths(WireMockServerSettings settings) public RegexMatcher MappingsCodeGuidPathMatcher => new($"^{_prefixEscaped}\\/mappings\\/code\\/([0-9A-Fa-f]{{8}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{12}})$"); public RegexMatcher RequestsGuidPathMatcher => new($"^{_prefixEscaped}\\/requests\\/([0-9A-Fa-f]{{8}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{12}})$"); public RegexMatcher ScenariosNameMatcher => new($"^{_prefixEscaped}\\/scenarios\\/.+$"); + public RegexMatcher ScenariosNameWithStateMatcher => new($"^{_prefixEscaped}\\/scenarios\\/.+\\/state$"); public RegexMatcher ScenariosNameWithResetMatcher => new($"^{_prefixEscaped}\\/scenarios\\/.+\\/reset$"); public RegexMatcher FilesFilenamePathMatcher => new($"^{_prefixEscaped}\\/files\\/.+$"); public RegexMatcher ProtoDefinitionsIdPathMatcher => new($"^{_prefixEscaped}\\/protodefinitions\\/.+$"); @@ -138,6 +139,9 @@ private void InitAdmin() Given(Request.Create().WithPath(_adminPaths.Scenarios + "/reset").UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(ScenariosReset)); Given(Request.Create().WithPath(_adminPaths.ScenariosNameWithResetMatcher).UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(ScenarioReset)); + // __admin/scenarios/{scenario}/state + Given(Request.Create().WithPath(_adminPaths.ScenariosNameWithStateMatcher).UsingPut()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(ScenariosSetState)); + // __admin/files/{filename} Given(Request.Create().WithPath(_adminPaths.FilesFilenamePathMatcher).UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(FilePost)); Given(Request.Create().WithPath(_adminPaths.FilesFilenamePathMatcher).UsingPut()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(FilePut)); @@ -705,6 +709,21 @@ private IResponseMessage ScenarioReset(IRequestMessage requestMessage) ResponseMessageBuilder.Create(200, "Scenario reset") : ResponseMessageBuilder.Create(HttpStatusCode.NotFound, $"No scenario found by name '{name}'."); } + + private IResponseMessage ScenariosSetState(IRequestMessage requestMessage) + { + var name = requestMessage.Path.Split('/').Reverse().Skip(1).First(); + if (!_options.Scenarios.ContainsKey(name)) + { + ResponseMessageBuilder.Create(HttpStatusCode.NotFound, $"No scenario found by name '{name}'."); + } + + var update = DeserializeObject(requestMessage); + + return SetScenarioState(name, update.State) ? + ResponseMessageBuilder.Create(200, $"Scenario state set to '{update.State}'") : + ResponseMessageBuilder.Create(HttpStatusCode.NotFound, $"No scenario found by name '{name}'."); + } #endregion #region Pact diff --git a/src/WireMock.Net.Minimal/Server/WireMockServer.cs b/src/WireMock.Net.Minimal/Server/WireMockServer.cs index ba88d50df..6b9184a67 100644 --- a/src/WireMock.Net.Minimal/Server/WireMockServer.cs +++ b/src/WireMock.Net.Minimal/Server/WireMockServer.cs @@ -567,6 +567,32 @@ public bool ResetScenario(string name) return _options.Scenarios.ContainsKey(name) && _options.Scenarios.TryRemove(name, out _); } + /// + [PublicAPI] + public bool SetScenarioState(string name, string? state) + { + if (state == null) + { + return ResetScenario(name); + } + + _options.Scenarios.AddOrUpdate( + name, + _ => new ScenarioState + { + Name = name, + NextState = state + }, + (_, current) => + { + current.NextState = state; + return current; + } + ); + + return true; + } + /// [PublicAPI] public IWireMockServer WithMapping(params MappingModel[] mappings) diff --git a/src/WireMock.Net.RestClient/IWireMockAdminApi.cs b/src/WireMock.Net.RestClient/IWireMockAdminApi.cs index 5cd335462..6ac9c7a7f 100644 --- a/src/WireMock.Net.RestClient/IWireMockAdminApi.cs +++ b/src/WireMock.Net.RestClient/IWireMockAdminApi.cs @@ -256,7 +256,7 @@ public interface IWireMockAdminApi /// Delete (reset) all scenarios /// /// The optional cancellationToken. - [Post("scenarios")] + [Post("scenarios/reset")] Task ResetScenariosAsync(CancellationToken cancellationToken = default); /// @@ -269,7 +269,7 @@ public interface IWireMockAdminApi Task DeleteScenarioAsync([Path] string name, CancellationToken cancellationToken = default); /// - /// Delete (reset) all scenarios + /// Delete (reset) a specific scenario /// /// Scenario name. /// The optional cancellationToken. @@ -277,6 +277,16 @@ public interface IWireMockAdminApi [AllowAnyStatusCode] Task ResetScenarioAsync([Path] string name, CancellationToken cancellationToken = default); + /// + /// Update the state for a scenario. + /// + /// Scenario name. + /// Scenario state update model. + /// The optional cancellationToken. + [Put("scenarios/{name}/state")] + [AllowAnyStatusCode] + Task PutScenarioStateAsync([Path] string name, [Body] ScenarioStateUpdateModel updateModel, CancellationToken cancellationToken = default); + /// /// Create a new File /// diff --git a/test/WireMock.Net.Tests/AdminApi/WireMockAdminApiTests.cs b/test/WireMock.Net.Tests/AdminApi/WireMockAdminApiTests.cs index cadfb7f34..f4eebe842 100644 --- a/test/WireMock.Net.Tests/AdminApi/WireMockAdminApiTests.cs +++ b/test/WireMock.Net.Tests/AdminApi/WireMockAdminApiTests.cs @@ -17,6 +17,7 @@ using VerifyTests; using VerifyXunit; using WireMock.Admin.Mappings; +using WireMock.Admin.Scenarios; using WireMock.Admin.Settings; using WireMock.Client; using WireMock.Client.Extensions; @@ -743,6 +744,57 @@ public async Task IWireMockAdminApi_DeleteScenarioUsingPostAsync() status.Status.Should().Be("No scenario found by name 'x'."); } + [Fact] + public async Task IWireMockAdminApi_UpdateNonExistingScenarioState() + { + // Arrange + using var server = WireMockServer.StartWithAdminInterface(); + + var api = RestClient.For(server.Urls[0]); + + // Act + var update = new ScenarioStateUpdateModel + { + State = null + }; + var status = await api.PutScenarioStateAsync("x", update).ConfigureAwait(false); + status.Status.Should().Be("No scenario found by name 'x'."); + } + + [Fact] + public async Task IWireMockAdminApi_UpdateScenarioState() + { + // Arrange + using var server = WireMockServer.StartWithAdminInterface(); + server + .Given(Request.Create() + .WithPath("/state1") + .UsingGet()) + .InScenario("s1") + .WillSetStateTo("Test state 1") + .RespondWith(Response.Create() + .WithBody("No state msg 1")); + + server + .Given(Request.Create() + .WithPath("/foostate1") + .UsingGet()) + .InScenario("s1") + .WhenStateIs("Test state 1") + .RespondWith(Response.Create() + .WithBody("Test state msg 1")); + + var api = RestClient.For(server.Urls[0]); + + // Act + var update = new ScenarioStateUpdateModel + { + State = null + }; + var status = await api.PutScenarioStateAsync("s1", update).ConfigureAwait(false); + status.Status.Should().Be("Scenario state set to ''"); + } + [Fact] public async Task IWireMockAdminApi_GetMappingByGuidAsync() { diff --git a/test/WireMock.Net.Tests/Constants.cs b/test/WireMock.Net.Tests/Constants.cs index a7b36152d..30e4cdc7c 100644 --- a/test/WireMock.Net.Tests/Constants.cs +++ b/test/WireMock.Net.Tests/Constants.cs @@ -6,5 +6,5 @@ internal static class Constants { internal const int NumStaticMappings = 10; - internal const int NumAdminMappings = 36; + internal const int NumAdminMappings = 37; } \ No newline at end of file diff --git a/test/WireMock.Net.Tests/StatefulBehaviorTests.cs b/test/WireMock.Net.Tests/StatefulBehaviorTests.cs index fdbb353a4..15407222f 100644 --- a/test/WireMock.Net.Tests/StatefulBehaviorTests.cs +++ b/test/WireMock.Net.Tests/StatefulBehaviorTests.cs @@ -12,340 +12,447 @@ using WireMock.Server; using Xunit; -namespace WireMock.Net.Tests +namespace WireMock.Net.Tests; + +public class StatefulBehaviorTests { - public class StatefulBehaviorTests + [Fact] + public async Task Scenarios_Should_skip_non_relevant_states() + { + // given + string path = $"/foo_{Guid.NewGuid()}"; + var server = WireMockServer.Start(); + + server + .Given(Request.Create().WithPath(path).UsingGet()) + .InScenario("s") + .WhenStateIs("Test state") + .RespondWith(Response.Create()); + + // when + var response = await new HttpClient().GetAsync("http://localhost:" + server.Ports[0] + path).ConfigureAwait(false); + + // then + Check.That(response.StatusCode).IsEqualTo(HttpStatusCode.NotFound); + + server.Stop(); + } + + [Fact] + public async Task Scenarios_Should_process_request_if_equals_state_and_single_state_defined() + { + // Arrange + var path = $"/foo_{Guid.NewGuid()}"; + using var server = WireMockServer.Start(); + + server + .Given(Request.Create().WithPath(path).UsingGet()) + .InScenario("s") + .WillSetStateTo("Test state") + .RespondWith(Response.Create().WithBody("No state msg")); + + server + .Given(Request.Create().WithPath(path).UsingGet()) + .InScenario("s") + .WhenStateIs("Test state") + .RespondWith(Response.Create().WithBody("Test state msg")); + + // Act + var responseNoState = await new HttpClient().GetStringAsync(server.Url + path).ConfigureAwait(false); + var responseWithState = await new HttpClient().GetStringAsync(server.Url + path).ConfigureAwait(false); + + // Assert + Check.That(responseNoState).Equals("No state msg"); + Check.That(responseWithState).Equals("Test state msg"); + } + + [Theory] + [InlineData(null, "step 1", "step 2")] + [InlineData("step 2", "step 2", "step 3")] + public async Task Scenarios_Should_ContinueOnCorrectState_WhenStateIsUpdated(string? state, string expected1, string expected2) + { + // Arrange + var path = $"/foo_{Guid.NewGuid()}"; + var scenario = "s"; + using var server = WireMockServer.Start(); + + server + .Given(Request.Create().WithPath(path).UsingGet()) + .InScenario(scenario) + .WillSetStateTo("step 2") + .RespondWith(Response.Create().WithBody("step 1")); + + server + .Given(Request.Create().WithPath(path).UsingGet()) + .InScenario(scenario) + .WhenStateIs("step 2") + .WillSetStateTo("step 3") + .RespondWith(Response.Create().WithBody("step 2")); + + server + .Given(Request.Create().WithPath(path).UsingGet()) + .InScenario(scenario) + .WhenStateIs("step 3") + .RespondWith(Response.Create().WithBody("step 3")); + + // Act + var client = server.CreateClient(); + var response1 = await client.GetStringAsync(server.Url + path); + var response2 = await client.GetStringAsync(server.Url + path); + var response3 = await client.GetStringAsync(server.Url + path); + + server.SetScenarioState(scenario, state); + var responseA = await client.GetStringAsync(server.Url + path); + var responseB = await client.GetStringAsync(server.Url + path); + + // Assert + Check.That(response1).Equals("step 1"); + Check.That(response2).Equals("step 2"); + Check.That(response3).Equals("step 3"); + Check.That(responseA).Equals(expected1); + Check.That(responseB).Equals(expected2); + } + + [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).ConfigureAwait(false); + var responseScenario2 = await client.GetStringAsync("http://localhost:" + server.Ports[0] + path).ConfigureAwait(false); + var responseWithState = await client.GetStringAsync("http://localhost:" + server.Ports[0] + path).ConfigureAwait(false); + + // then + responseScenario1.Should().Be(body1); + responseScenario2.Should().Be(body1); + responseWithState.Should().Be(body2); + + server.Stop(); + } + + [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).ConfigureAwait(false); + var t1b = await client.GetStringAsync("http://localhost:" + server.Ports[0] + path1).ConfigureAwait(false); + var t2a = await client.GetStringAsync("http://localhost:" + server.Ports[0] + path2).ConfigureAwait(false); + var t2b = await client.GetStringAsync("http://localhost:" + server.Ports[0] + path2).ConfigureAwait(false); + var t3 = await client.GetStringAsync("http://localhost:" + server.Ports[0] + path3).ConfigureAwait(false); + + // then + t1a.Should().Be(body1); + t1b.Should().Be(body1); + t2a.Should().Be(body2); + t2b.Should().Be(body2); + t3.Should().Be(body3); + + server.Stop(); + } + + [Fact] + public async Task Scenarios_Should_Respect_Int_Valued_Scenarios_and_States() + { + // given + string path = $"/foo_{Guid.NewGuid()}"; + var server = WireMockServer.Start(); + + server + .Given(Request.Create().WithPath(path).UsingGet()) + .InScenario(1) + .WillSetStateTo(2) + .RespondWith(Response.Create().WithBody("Scenario 1, Setting State 2")); + + server + .Given(Request.Create().WithPath(path).UsingGet()) + .InScenario(1) + .WhenStateIs(2) + .RespondWith(Response.Create().WithBody("Scenario 1, State 2")); + + // when + var responseIntScenario = await new HttpClient().GetStringAsync("http://localhost:" + server.Ports[0] + path).ConfigureAwait(false); + var responseWithIntState = await new HttpClient().GetStringAsync("http://localhost:" + server.Ports[0] + path).ConfigureAwait(false); + + // then + Check.That(responseIntScenario).Equals("Scenario 1, Setting State 2"); + Check.That(responseWithIntState).Equals("Scenario 1, State 2"); + + server.Stop(); + } + + [Fact] + public async Task Scenarios_Should_Respect_Mixed_String_Scenario_and_Int_State() + { + // given + string path = $"/foo_{Guid.NewGuid()}"; + var server = WireMockServer.Start(); + + server + .Given(Request.Create().WithPath(path).UsingGet()) + .InScenario("state string") + .WillSetStateTo(1) + .RespondWith(Response.Create().WithBody("string state, Setting State 2")); + + server + .Given(Request.Create().WithPath(path).UsingGet()) + .InScenario("state string") + .WhenStateIs(1) + .RespondWith(Response.Create().WithBody("string state, State 2")); + + // when + var responseIntScenario = await new HttpClient().GetStringAsync("http://localhost:" + server.Ports[0] + path).ConfigureAwait(false); + var responseWithIntState = await new HttpClient().GetStringAsync("http://localhost:" + server.Ports[0] + path).ConfigureAwait(false); + + // then + Check.That(responseIntScenario).Equals("string state, Setting State 2"); + Check.That(responseWithIntState).Equals("string state, State 2"); + + server.Stop(); + } + + [Fact] + public async Task Scenarios_Should_Respect_Mixed_Int_Scenario_and_String_Scenario_and_String_State() + { + // given + string path = $"/foo_{Guid.NewGuid()}"; + var server = WireMockServer.Start(); + + server + .Given(Request.Create().WithPath(path).UsingGet()) + .InScenario(1) + .WillSetStateTo("Next State") + .RespondWith(Response.Create().WithBody("int state, Setting State 2")); + + server + .Given(Request.Create().WithPath(path).UsingGet()) + .InScenario("1") + .WhenStateIs("Next State") + .RespondWith(Response.Create().WithBody("string state, State 2")); + + // when + var responseIntScenario = await new HttpClient().GetStringAsync("http://localhost:" + server.Ports[0] + path).ConfigureAwait(false); + var responseWithIntState = await new HttpClient().GetStringAsync("http://localhost:" + server.Ports[0] + path).ConfigureAwait(false); + + // then + Check.That(responseIntScenario).Equals("int state, Setting State 2"); + Check.That(responseWithIntState).Equals("string state, State 2"); + + server.Stop(); + } + + [Fact] + public async Task Scenarios_TodoList_Example() + { + // Arrange + var server = WireMockServer.Start(); + var client = server.CreateClient(); + + server + .Given(Request.Create().WithPath("/todo/items").UsingGet()) + .InScenario("To do list") + .WillSetStateTo("TodoList State Started") + .RespondWith(Response.Create().WithBody("Buy milk")); + + server + .Given(Request.Create().WithPath("/todo/items").UsingPost()) + .InScenario("To do list") + .WhenStateIs("TodoList State Started") + .WillSetStateTo("Cancel newspaper item added") + .RespondWith(Response.Create().WithStatusCode(201)); + + server + .Given(Request.Create().WithPath("/todo/items").UsingGet()) + .InScenario("To do list") + .WhenStateIs("Cancel newspaper item added") + .RespondWith(Response.Create().WithBody("Buy milk;Cancel newspaper subscription")); + + Check.That(server.Scenarios.Any()).IsFalse(); + + // Act and Assert + var getResponse1 = await client.GetStringAsync("/todo/items").ConfigureAwait(false); + Check.That(getResponse1).Equals("Buy milk"); + + Check.That(server.Scenarios["To do list"].Name).IsEqualTo("To do list"); + Check.That(server.Scenarios["To do list"].NextState).IsEqualTo("TodoList State Started"); + Check.That(server.Scenarios["To do list"].Started).IsTrue(); + Check.That(server.Scenarios["To do list"].Finished).IsFalse(); + + var postResponse = await client.PostAsync("/todo/items", new StringContent("Cancel newspaper subscription")).ConfigureAwait(false); + Check.That(postResponse.StatusCode).Equals(HttpStatusCode.Created); + + Check.That(server.Scenarios["To do list"].Name).IsEqualTo("To do list"); + Check.That(server.Scenarios["To do list"].NextState).IsEqualTo("Cancel newspaper item added"); + Check.That(server.Scenarios["To do list"].Started).IsTrue(); + Check.That(server.Scenarios["To do list"].Finished).IsFalse(); + + string getResponse2 = await client.GetStringAsync("/todo/items").ConfigureAwait(false); + Check.That(getResponse2).Equals("Buy milk;Cancel newspaper subscription"); + + Check.That(server.Scenarios["To do list"].Name).IsEqualTo("To do list"); + Check.That(server.Scenarios["To do list"].NextState).IsNull(); + Check.That(server.Scenarios["To do list"].Started).IsTrue(); + Check.That(server.Scenarios["To do list"].Finished).IsTrue(); + + server.Stop(); + } + + [Fact] + public async Task Scenarios_TodoList_WithSetState() + { + // Arrange + var scenario = "To do list"; + using var server = WireMockServer.Start(); + var client = server.CreateClient(); + + server + .Given(Request.Create().WithPath("/todo/items").UsingGet()) + .InScenario(scenario) + .WhenStateIs("Buy milk") + .RespondWith(Response.Create().WithBody("Buy milk")); + + server + .Given(Request.Create().WithPath("/todo/items").UsingGet()) + .InScenario(scenario) + .WhenStateIs("Cancel newspaper") + .RespondWith(Response.Create().WithBody("Buy milk;Cancel newspaper subscription")); + + // Act and Assert + server.SetScenarioState(scenario, "Buy milk"); + server.Scenarios[scenario].Should().BeEquivalentTo(new { Name = scenario, NextState = "Buy milk" }); + + var getResponse1 = await client.GetStringAsync("/todo/items").ConfigureAwait(false); + getResponse1.Should().Be("Buy milk"); + + server.SetScenarioState(scenario, "Cancel newspaper"); + server.Scenarios[scenario].Name.Should().Be(scenario); + server.Scenarios[scenario].Should().BeEquivalentTo(new { Name = scenario, NextState = "Cancel newspaper" }); + + var getResponse2 = await client.GetStringAsync("/todo/items").ConfigureAwait(false); + getResponse2.Should().Be("Buy milk;Cancel newspaper subscription"); + } + + [Fact] + public void Scenarios_TodoList_WithSetStateToNull_ShouldThrowException() + { + // Arrange + var scenario = "To do list"; + using var server = WireMockServer.Start(); + var client = server.CreateClient(); + + server + .Given(Request.Create().WithPath("/todo/items").UsingGet()) + .InScenario(scenario) + .WhenStateIs("Buy milk") + .RespondWith(Response.Create().WithBody("Buy milk")); + + server + .Given(Request.Create().WithPath("/todo/items").UsingGet()) + .InScenario(scenario) + .WhenStateIs("Cancel newspaper") + .RespondWith(Response.Create().WithBody("Buy milk;Cancel newspaper subscription")); + + // Act + server.SetScenarioState(scenario, null); + var action = async () => await client.GetStringAsync("/todo/items"); + + // Assert + action.Should().ThrowAsync(); + } + + [Fact] + public async Task Scenarios_Should_process_request_if_equals_state_and_multiple_state_defined() { - [Fact] - public async Task Scenarios_Should_skip_non_relevant_states() - { - // given - string path = $"/foo_{Guid.NewGuid()}"; - var server = WireMockServer.Start(); - - server - .Given(Request.Create().WithPath(path).UsingGet()) - .InScenario("s") - .WhenStateIs("Test state") - .RespondWith(Response.Create()); - - // when - var response = await new HttpClient().GetAsync("http://localhost:" + server.Ports[0] + path).ConfigureAwait(false); - - // then - Check.That(response.StatusCode).IsEqualTo(HttpStatusCode.NotFound); - - server.Stop(); - } - - [Fact] - public async Task Scenarios_Should_process_request_if_equals_state_and_single_state_defined() - { - // given - string path = $"/foo_{Guid.NewGuid()}"; - var server = WireMockServer.Start(); - - server - .Given(Request.Create().WithPath(path).UsingGet()) - .InScenario("s") - .WillSetStateTo("Test state") - .RespondWith(Response.Create().WithBody("No state msg")); - - server - .Given(Request.Create().WithPath(path).UsingGet()) - .InScenario("s") - .WhenStateIs("Test state") - .RespondWith(Response.Create().WithBody("Test state msg")); - - // when - var responseNoState = await new HttpClient().GetStringAsync("http://localhost:" + server.Ports[0] + path).ConfigureAwait(false); - var responseWithState = await new HttpClient().GetStringAsync("http://localhost:" + server.Ports[0] + path).ConfigureAwait(false); - - // then - Check.That(responseNoState).Equals("No state msg"); - Check.That(responseWithState).Equals("Test state msg"); - - server.Stop(); - } - - [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).ConfigureAwait(false); - var responseScenario2 = await client.GetStringAsync("http://localhost:" + server.Ports[0] + path).ConfigureAwait(false); - var responseWithState = await client.GetStringAsync("http://localhost:" + server.Ports[0] + path).ConfigureAwait(false); - - // then - responseScenario1.Should().Be(body1); - responseScenario2.Should().Be(body1); - responseWithState.Should().Be(body2); - - server.Stop(); - } - - [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).ConfigureAwait(false); - var t1b = await client.GetStringAsync("http://localhost:" + server.Ports[0] + path1).ConfigureAwait(false); - var t2a = await client.GetStringAsync("http://localhost:" + server.Ports[0] + path2).ConfigureAwait(false); - var t2b = await client.GetStringAsync("http://localhost:" + server.Ports[0] + path2).ConfigureAwait(false); - var t3 = await client.GetStringAsync("http://localhost:" + server.Ports[0] + path3).ConfigureAwait(false); - - // then - t1a.Should().Be(body1); - t1b.Should().Be(body1); - t2a.Should().Be(body2); - t2b.Should().Be(body2); - t3.Should().Be(body3); - - server.Stop(); - } - - [Fact] - public async Task Scenarios_Should_Respect_Int_Valued_Scenarios_and_States() - { - // given - string path = $"/foo_{Guid.NewGuid()}"; - var server = WireMockServer.Start(); - - server - .Given(Request.Create().WithPath(path).UsingGet()) - .InScenario(1) - .WillSetStateTo(2) - .RespondWith(Response.Create().WithBody("Scenario 1, Setting State 2")); - - server - .Given(Request.Create().WithPath(path).UsingGet()) - .InScenario(1) - .WhenStateIs(2) - .RespondWith(Response.Create().WithBody("Scenario 1, State 2")); - - // when - var responseIntScenario = await new HttpClient().GetStringAsync("http://localhost:" + server.Ports[0] + path).ConfigureAwait(false); - var responseWithIntState = await new HttpClient().GetStringAsync("http://localhost:" + server.Ports[0] + path).ConfigureAwait(false); - - // then - Check.That(responseIntScenario).Equals("Scenario 1, Setting State 2"); - Check.That(responseWithIntState).Equals("Scenario 1, State 2"); - - server.Stop(); - } - - [Fact] - public async Task Scenarios_Should_Respect_Mixed_String_Scenario_and_Int_State() - { - // given - string path = $"/foo_{Guid.NewGuid()}"; - var server = WireMockServer.Start(); - - server - .Given(Request.Create().WithPath(path).UsingGet()) - .InScenario("state string") - .WillSetStateTo(1) - .RespondWith(Response.Create().WithBody("string state, Setting State 2")); - - server - .Given(Request.Create().WithPath(path).UsingGet()) - .InScenario("state string") - .WhenStateIs(1) - .RespondWith(Response.Create().WithBody("string state, State 2")); - - // when - var responseIntScenario = await new HttpClient().GetStringAsync("http://localhost:" + server.Ports[0] + path).ConfigureAwait(false); - var responseWithIntState = await new HttpClient().GetStringAsync("http://localhost:" + server.Ports[0] + path).ConfigureAwait(false); - - // then - Check.That(responseIntScenario).Equals("string state, Setting State 2"); - Check.That(responseWithIntState).Equals("string state, State 2"); - - server.Stop(); - } - - [Fact] - public async Task Scenarios_Should_Respect_Mixed_Int_Scenario_and_String_Scenario_and_String_State() - { - // given - string path = $"/foo_{Guid.NewGuid()}"; - var server = WireMockServer.Start(); - - server - .Given(Request.Create().WithPath(path).UsingGet()) - .InScenario(1) - .WillSetStateTo("Next State") - .RespondWith(Response.Create().WithBody("int state, Setting State 2")); - - server - .Given(Request.Create().WithPath(path).UsingGet()) - .InScenario("1") - .WhenStateIs("Next State") - .RespondWith(Response.Create().WithBody("string state, State 2")); - - // when - var responseIntScenario = await new HttpClient().GetStringAsync("http://localhost:" + server.Ports[0] + path).ConfigureAwait(false); - var responseWithIntState = await new HttpClient().GetStringAsync("http://localhost:" + server.Ports[0] + path).ConfigureAwait(false); - - // then - Check.That(responseIntScenario).Equals("int state, Setting State 2"); - Check.That(responseWithIntState).Equals("string state, State 2"); - - server.Stop(); - } - - [Fact] - public async Task Scenarios_TodoList_Example() - { - // Assign - var server = WireMockServer.Start(); - - server - .Given(Request.Create().WithPath("/todo/items").UsingGet()) - .InScenario("To do list") - .WillSetStateTo("TodoList State Started") - .RespondWith(Response.Create().WithBody("Buy milk")); - - server - .Given(Request.Create().WithPath("/todo/items").UsingPost()) - .InScenario("To do list") - .WhenStateIs("TodoList State Started") - .WillSetStateTo("Cancel newspaper item added") - .RespondWith(Response.Create().WithStatusCode(201)); - - server - .Given(Request.Create().WithPath("/todo/items").UsingGet()) - .InScenario("To do list") - .WhenStateIs("Cancel newspaper item added") - .RespondWith(Response.Create().WithBody("Buy milk;Cancel newspaper subscription")); - - Check.That(server.Scenarios.Any()).IsFalse(); - - // Act and Assert - string url = "http://localhost:" + server.Ports[0]; - string getResponse1 = new HttpClient().GetStringAsync(url + "/todo/items").Result; - Check.That(getResponse1).Equals("Buy milk"); - - Check.That(server.Scenarios["To do list"].Name).IsEqualTo("To do list"); - Check.That(server.Scenarios["To do list"].NextState).IsEqualTo("TodoList State Started"); - Check.That(server.Scenarios["To do list"].Started).IsTrue(); - Check.That(server.Scenarios["To do list"].Finished).IsFalse(); - - var postResponse = await new HttpClient().PostAsync(url + "/todo/items", new StringContent("Cancel newspaper subscription")).ConfigureAwait(false); - Check.That(postResponse.StatusCode).Equals(HttpStatusCode.Created); - - Check.That(server.Scenarios["To do list"].Name).IsEqualTo("To do list"); - Check.That(server.Scenarios["To do list"].NextState).IsEqualTo("Cancel newspaper item added"); - Check.That(server.Scenarios["To do list"].Started).IsTrue(); - Check.That(server.Scenarios["To do list"].Finished).IsFalse(); - - string getResponse2 = await new HttpClient().GetStringAsync(url + "/todo/items").ConfigureAwait(false); - Check.That(getResponse2).Equals("Buy milk;Cancel newspaper subscription"); - - Check.That(server.Scenarios["To do list"].Name).IsEqualTo("To do list"); - Check.That(server.Scenarios["To do list"].NextState).IsNull(); - Check.That(server.Scenarios["To do list"].Started).IsTrue(); - Check.That(server.Scenarios["To do list"].Finished).IsTrue(); - - server.Stop(); - } - - [Fact] - public async Task Scenarios_Should_process_request_if_equals_state_and_multiple_state_defined() - { - // Assign - var server = WireMockServer.Start(); - - server - .Given(Request.Create().WithPath("/state1").UsingGet()) - .InScenario("s1") - .WillSetStateTo("Test state 1") - .RespondWith(Response.Create().WithBody("No state msg 1")); - - server - .Given(Request.Create().WithPath("/foo1X").UsingGet()) - .InScenario("s1") - .WhenStateIs("Test state 1") - .RespondWith(Response.Create().WithBody("Test state msg 1")); - - server - .Given(Request.Create().WithPath("/state2").UsingGet()) - .InScenario("s2") - .WillSetStateTo("Test state 2") - .RespondWith(Response.Create().WithBody("No state msg 2")); - - server - .Given(Request.Create().WithPath("/foo2X").UsingGet()) - .InScenario("s2") - .WhenStateIs("Test state 2") - .RespondWith(Response.Create().WithBody("Test state msg 2")); - - // Act and Assert - string url = "http://localhost:" + server.Ports[0]; - var responseNoState1 = await new HttpClient().GetStringAsync(url + "/state1").ConfigureAwait(false); - Check.That(responseNoState1).Equals("No state msg 1"); - - var responseNoState2 = await new HttpClient().GetStringAsync(url + "/state2").ConfigureAwait(false); - Check.That(responseNoState2).Equals("No state msg 2"); - - var responseWithState1 = await new HttpClient().GetStringAsync(url + "/foo1X").ConfigureAwait(false); - Check.That(responseWithState1).Equals("Test state msg 1"); - - var responseWithState2 = await new HttpClient().GetStringAsync(url + "/foo2X").ConfigureAwait(false); - Check.That(responseWithState2).Equals("Test state msg 2"); - - server.Stop(); - } + // Assign + var server = WireMockServer.Start(); + + server + .Given(Request.Create().WithPath("/state1").UsingGet()) + .InScenario("s1") + .WillSetStateTo("Test state 1") + .RespondWith(Response.Create().WithBody("No state msg 1")); + + server + .Given(Request.Create().WithPath("/foo1X").UsingGet()) + .InScenario("s1") + .WhenStateIs("Test state 1") + .RespondWith(Response.Create().WithBody("Test state msg 1")); + + server + .Given(Request.Create().WithPath("/state2").UsingGet()) + .InScenario("s2") + .WillSetStateTo("Test state 2") + .RespondWith(Response.Create().WithBody("No state msg 2")); + + server + .Given(Request.Create().WithPath("/foo2X").UsingGet()) + .InScenario("s2") + .WhenStateIs("Test state 2") + .RespondWith(Response.Create().WithBody("Test state msg 2")); + + // Act and Assert + string url = "http://localhost:" + server.Ports[0]; + var responseNoState1 = await new HttpClient().GetStringAsync(url + "/state1").ConfigureAwait(false); + Check.That(responseNoState1).Equals("No state msg 1"); + + var responseNoState2 = await new HttpClient().GetStringAsync(url + "/state2").ConfigureAwait(false); + Check.That(responseNoState2).Equals("No state msg 2"); + + var responseWithState1 = await new HttpClient().GetStringAsync(url + "/foo1X").ConfigureAwait(false); + Check.That(responseWithState1).Equals("Test state msg 1"); + + var responseWithState2 = await new HttpClient().GetStringAsync(url + "/foo2X").ConfigureAwait(false); + Check.That(responseWithState2).Equals("Test state msg 2"); + + server.Stop(); } } \ No newline at end of file