diff --git a/src/WireMock.Net.RestClient/IWireMockAdminApi.cs b/src/WireMock.Net.RestClient/IWireMockAdminApi.cs index 17c67f413..bf9b2d757 100644 --- a/src/WireMock.Net.RestClient/IWireMockAdminApi.cs +++ b/src/WireMock.Net.RestClient/IWireMockAdminApi.cs @@ -74,6 +74,14 @@ public interface IWireMockAdminApi [Delete("mappings")] Task DeleteMappingsAsync(); + /// + /// Delete mappings according to GUIDs + /// + /// MappingModels + [Delete("mappings")] + [Header("Content-Type", "application/json")] + Task DeleteMappingsAsync([Body] IList mappings); + /// /// Delete (reset) all mappings. /// diff --git a/src/WireMock.Net/Server/WireMockServer.Admin.cs b/src/WireMock.Net/Server/WireMockServer.Admin.cs index e0b6a0d19..9755e0234 100644 --- a/src/WireMock.Net/Server/WireMockServer.Admin.cs +++ b/src/WireMock.Net/Server/WireMockServer.Admin.cs @@ -555,11 +555,63 @@ private ResponseMessage MappingsPost(RequestMessage requestMessage) private ResponseMessage MappingsDelete(RequestMessage requestMessage) { - ResetMappings(); + if (!string.IsNullOrEmpty(requestMessage.Body)) + { + var deletedGuids = MappingsDeleteMappingFromBody(requestMessage); + if (deletedGuids != null) + { + return ResponseMessageBuilder.Create($"Mappings deleted. Affected GUIDs: [{string.Join(", ", deletedGuids.ToArray())}]"); + } + else + { + // return bad request + return ResponseMessageBuilder.Create("Poorly formed mapping JSON.", 400); + } + } + else + { + ResetMappings(); - ResetScenarios(); + ResetScenarios(); + + return ResponseMessageBuilder.Create("Mappings deleted"); + } + } + + private IEnumerable MappingsDeleteMappingFromBody(RequestMessage requestMessage) + { + var deletedGuids = new List(); + + try + { + var mappingModels = DeserializeRequestMessageToArray(requestMessage); + foreach (var mappingModel in mappingModels) + { + if (mappingModel.Guid.HasValue) + { + if (DeleteMapping(mappingModel.Guid.Value)) + { + deletedGuids.Add(mappingModel.Guid.Value); + } + else + { + _settings.Logger.Debug($"Did not find/delete mapping with GUID: {mappingModel.Guid.Value}."); + } + } + } + } + catch (ArgumentException a) + { + _settings.Logger.Error("ArgumentException: {0}", a); + return null; + } + catch (Exception e) + { + _settings.Logger.Error("Exception: {0}", e); + return null; + } - return ResponseMessageBuilder.Create("Mappings deleted"); + return deletedGuids; } private ResponseMessage MappingsReset(RequestMessage requestMessage) @@ -873,7 +925,7 @@ private ResponseMessage ToJson(T result, bool keepNullValues = false) DetectedBodyType = BodyType.String, BodyAsString = JsonConvert.SerializeObject(result, keepNullValues ? _settingsIncludeNullValues : _jsonSerializerSettings) }, - StatusCode = (int) HttpStatusCode.OK, + StatusCode = (int)HttpStatusCode.OK, Headers = new Dictionary> { { HttpKnownHeaderNames.ContentType, new WireMockList(ContentTypeJson) } } }; } diff --git a/src/WireMock.Net/Util/BodyParser.cs b/src/WireMock.Net/Util/BodyParser.cs index 19ff326f3..6a6ff102e 100644 --- a/src/WireMock.Net/Util/BodyParser.cs +++ b/src/WireMock.Net/Util/BodyParser.cs @@ -34,7 +34,7 @@ internal static class BodyParser { "GET", false }, { "PUT", true }, { "POST", true }, - { "DELETE", false }, + { "DELETE", true }, { "TRACE", false }, { "OPTIONS", true }, { "CONNECT", false }, diff --git a/test/WireMock.Net.Tests/Util/BodyParserTests.cs b/test/WireMock.Net.Tests/Util/BodyParserTests.cs index b64401654..63b715000 100644 --- a/test/WireMock.Net.Tests/Util/BodyParserTests.cs +++ b/test/WireMock.Net.Tests/Util/BodyParserTests.cs @@ -148,7 +148,7 @@ public async Task BodyParser_Parse_ContentTypeIsNull(string contentType, string [InlineData("GET", false)] [InlineData("PUT", true)] [InlineData("POST", true)] - [InlineData("DELETE", false)] + [InlineData("DELETE", true)] [InlineData("TRACE", false)] [InlineData("OPTIONS", true)] [InlineData("CONNECT", false)] diff --git a/test/WireMock.Net.Tests/WireMockServer.Admin.cs b/test/WireMock.Net.Tests/WireMockServer.Admin.cs index a745399ec..38b8f8879 100644 --- a/test/WireMock.Net.Tests/WireMockServer.Admin.cs +++ b/test/WireMock.Net.Tests/WireMockServer.Admin.cs @@ -1,7 +1,10 @@ using System; +using System.Collections.Generic; using System.IO; using System.Linq; +using System.Net; using System.Net.Http; +using System.Text; using System.Threading.Tasks; using Moq; using Newtonsoft.Json; @@ -42,7 +45,6 @@ public void WireMockServer_Admin_StartStop() public void WireMockServer_Admin_ResetMappings() { var server = WireMockServer.Start(); - string folder = Path.Combine(GetCurrentFolder(), "__admin", "mappings"); server.ReadStaticMappings(folder); @@ -404,5 +406,64 @@ public void WireMockServer_Admin_AddMappingsAndSaveToFile() staticMappingHandlerMock.Verify(m => m.FolderExists("folder"), Times.Once); staticMappingHandlerMock.Verify(m => m.WriteMappingFile(Path.Combine("folder", guid + ".json"), It.IsAny()), Times.Once); } + + [Fact] + public async void WireMockServer_Admin_DeleteMappings() + { + // Arrange + var server = WireMockServer.Start(new WireMockServerSettings + { + StartAdminInterface = true, + ReadStaticMappings = false, + WatchStaticMappings = false, + WatchStaticMappingsInSubdirectories = false + }); + + server + .Given(Request.Create().WithPath("/path1")) + .AtPriority(0) + .RespondWith(Response.Create().WithStatusCode(200)); + server + .Given(Request.Create().WithPath("/path2")) + .AtPriority(1) + .RespondWith(Response.Create().WithStatusCode(200)); + server + .Given(Request.Create().WithPath("/path3")) + .AtPriority(2) + .RespondWith(Response.Create().WithStatusCode(200)); + + Check.That(server.MappingModels.Count()).Equals(3); + + Guid? guid1 = server.MappingModels.ElementAt(0).Guid; + Guid? guid2 = server.MappingModels.ElementAt(1).Guid; + Guid? guid3 = server.MappingModels.ElementAt(2).Guid; + + Check.That(guid1).IsNotNull(); + Check.That(guid2).IsNotNull(); + Check.That(guid3).IsNotNull(); + + string guidsJsonBody = $"[" + + $"{{\"Guid\": \"{guid1}\"}}," + + $"{{\"Guid\": \"{guid2}\"}}" + + $"]"; + + // Act + var request = new HttpRequestMessage() + { + Method = HttpMethod.Delete, + RequestUri = new Uri($"http://localhost:{server.Ports[0]}/__admin/mappings"), + Content = new StringContent(guidsJsonBody, Encoding.UTF8, "application/json") + }; + + var response = await new HttpClient().SendAsync(request); + + // Assert + IEnumerable guids = server.MappingModels.Select(mapping => mapping.Guid.Value); + Check.That(guids.Contains(guid1.Value)).IsFalse(); + Check.That(guids.Contains(guid2.Value)).IsFalse(); + Check.That(guids.Contains(guid3.Value)).IsTrue(); + Check.That(response.StatusCode).Equals(HttpStatusCode.OK); + Check.That(await response.Content.ReadAsStringAsync()).Equals($"{{\"Status\":\"Mappings deleted. Affected GUIDs: [{guid1}, {guid2}]\"}}"); + } } } \ No newline at end of file diff --git a/test/WireMock.Net.Tests/WireMockServerTests.cs b/test/WireMock.Net.Tests/WireMockServerTests.cs index b236f71a8..86ed5f585 100644 --- a/test/WireMock.Net.Tests/WireMockServerTests.cs +++ b/test/WireMock.Net.Tests/WireMockServerTests.cs @@ -160,12 +160,10 @@ public async Task WireMockServer_Should_exclude_restrictedResponseHeader() Check.That(response.Headers.Contains("Transfer-Encoding")).IsFalse(); } - [Theory] - [InlineData("DELETE")] #if !NET452 + [Theory] [InlineData("TRACE")] [InlineData("GET")] -#endif public async Task WireMockServer_Should_exclude_body_for_methods_where_body_is_definitely_disallowed(string method) { // Assign @@ -189,12 +187,14 @@ public async Task WireMockServer_Should_exclude_body_for_methods_where_body_is_d // Assert Check.That(response.StatusCode).Equals(HttpStatusCode.OK); } +#endif [Theory] [InlineData("POST")] [InlineData("PUT")] [InlineData("OPTIONS")] [InlineData("REPORT")] + [InlineData("DELETE")] [InlineData("SOME-UNKNOWN-METHOD")] // default behavior for unknown methods is to allow a body (see BodyParser.ShouldParseBody) public async Task WireMockServer_Should_not_exclude_body_for_supported_methods(string method) {