Added feature to enable and disable mappings (#1437)

* feat/1421 added feature to enable and disable mappings

* feat/1421 updated test constants to reflect 2 new admin endpoints /enable and /disable

* feat/1421 updated tests to fix flakyness - removed delay before assertion that is causing upstream connection from proxy to teardown prematurely before test ends

* feat/1421 addressing PR comments - Updated logic to represent IsDisable insted of IsEnabled
This commit is contained in:
Jayaraman Venkatesan
2026-04-24 02:07:37 -04:00
committed by GitHub
parent 85d61a1877
commit 1962437dcd
13 changed files with 277 additions and 4 deletions

View File

@@ -0,0 +1,134 @@
// Copyright © WireMock.Net
using RestEase;
using WireMock.Admin.Mappings;
using WireMock.Client;
using WireMock.Server;
namespace WireMock.Net.Tests.AdminApi;
public partial class WireMockAdminApiTests
{
[Fact]
public async Task IWireMockAdminApi_PostMappingAsync_WithIsDisabledTrue_DoesNotMatchRequests()
{
// Arrange
var ct = TestContext.Current.CancellationToken;
using var server = WireMockServer.StartWithAdminInterface();
var api = RestClient.For<IWireMockAdminApi>(server.Urls[0]);
var httpClient = server.CreateClient();
var model = new MappingModel
{
Request = new RequestModel { Path = "/foo", Methods = ["GET"] },
Response = new ResponseModel { Body = "hello", StatusCode = 200 },
IsDisabled = true
};
// Act — POST the disabled mapping
var postResult = await api.PostMappingAsync(model, ct);
postResult.Should().NotBeNull();
// Assert — request should not be matched (404)
var response = await httpClient.GetAsync("/foo", ct);
((int)response.StatusCode).Should().Be(404);
// Assert — mapping exists but IsDisabled is true
server.Mappings.Where(m => !m.IsAdminInterface).Should().ContainSingle(m => m.IsDisabled == true);
}
[Fact]
public async Task IWireMockAdminApi_DisableMappingAsync_PreventsMatching()
{
// Arrange
var ct = TestContext.Current.CancellationToken;
using var server = WireMockServer.StartWithAdminInterface();
var api = RestClient.For<IWireMockAdminApi>(server.Urls[0]);
var httpClient = server.CreateClient();
var model = new MappingModel
{
Request = new RequestModel { Path = "/bar", Methods = ["GET"] },
Response = new ResponseModel { Body = "world", StatusCode = 200 }
};
var postResult = await api.PostMappingAsync(model, ct);
var guid = postResult.Guid!.Value;
// Assert — mapping matches before disable
var before = await httpClient.GetAsync("/bar", ct);
((int)before.StatusCode).Should().Be(200);
// Act — disable
var disableResult = await api.DisableMappingAsync(guid, ct);
disableResult.Status.Should().Be("Mapping disabled");
// Assert — no match after disable
var after = await httpClient.GetAsync("/bar", ct);
((int)after.StatusCode).Should().Be(404);
}
[Fact]
public async Task IWireMockAdminApi_EnableMappingAsync_ResumesMatching()
{
// Arrange
var ct = TestContext.Current.CancellationToken;
using var server = WireMockServer.StartWithAdminInterface();
var api = RestClient.For<IWireMockAdminApi>(server.Urls[0]);
var httpClient = server.CreateClient();
var model = new MappingModel
{
Request = new RequestModel { Path = "/baz", Methods = ["GET"] },
Response = new ResponseModel { Body = "re-enabled", StatusCode = 200 },
IsDisabled = true
};
var postResult = await api.PostMappingAsync(model, ct);
var guid = postResult.Guid!.Value;
// Assert — no match while disabled
var before = await httpClient.GetAsync("/baz", ct);
((int)before.StatusCode).Should().Be(404);
// Act — enable
var enableResult = await api.EnableMappingAsync(guid, ct);
enableResult.Status.Should().Be("Mapping enabled");
// Assert — mapping matches after enable
var after = await httpClient.GetAsync("/baz", ct);
((int)after.StatusCode).Should().Be(200);
}
[Fact]
public async Task IWireMockAdminApi_GetMappingAsync_ReturnsIsDisabledTrue_WhenDisabled()
{
// Arrange
var ct = TestContext.Current.CancellationToken;
using var server = WireMockServer.StartWithAdminInterface();
var api = RestClient.For<IWireMockAdminApi>(server.Urls[0]);
var disabledModel = new MappingModel
{
Request = new RequestModel { Path = "/check-disabled" },
Response = new ResponseModel { Body = "x", StatusCode = 200 },
IsDisabled = true
};
var enabledModel = new MappingModel
{
Request = new RequestModel { Path = "/check-enabled" },
Response = new ResponseModel { Body = "y", StatusCode = 200 }
};
var disabledPost = await api.PostMappingAsync(disabledModel, ct);
var enabledPost = await api.PostMappingAsync(enabledModel, ct);
// Act
var disabledGot = await api.GetMappingAsync(disabledPost.Guid!.Value, ct);
var enabledGot = await api.GetMappingAsync(enabledPost.Guid!.Value, ct);
// Assert — disabled mapping serializes IsDisabled = true
disabledGot.IsDisabled.Should().BeTrue();
// Assert — enabled mapping omits IsDisabled (null = default not disabled)
enabledGot.IsDisabled.Should().BeNull();
}
}

View File

@@ -6,5 +6,5 @@ internal static class Constants
{
internal const int NumStaticMappings = 10;
internal const int NumAdminMappings = 37;
internal const int NumAdminMappings = 39;
}

View File

@@ -56,6 +56,7 @@ public class MappingMatcherTests
{
// Assign
var mappingMock = new Mock<IMapping>();
mappingMock.SetupGet(m => m.IsDisabled).Returns(false);
mappingMock.Setup(m => m.GetRequestMatchResult(It.IsAny<RequestMessage>(), It.IsAny<string>())).Throws<Exception>();
var mappings = new ConcurrentDictionary<Guid, IMapping>();
@@ -229,6 +230,35 @@ public class MappingMatcherTests
result.Match!.Mapping.Guid.Should().Be(withProbability);
}
[Fact]
public void MappingMatcher_FindBestMatch_WhenMappingIsDisabled_ShouldReturnNull()
{
// Assign
var guid = Guid.Parse("00000000-0000-0000-0000-000000000001");
var mappingMock = new Mock<IMapping>();
mappingMock.SetupGet(m => m.Guid).Returns(guid);
mappingMock.SetupGet(m => m.IsDisabled).Returns(true);
mappingMock.SetupGet(m => m.Probability).Returns((double?)null);
var matchResult = new RequestMatchResult();
matchResult.AddScore(typeof(object), 1.0, null);
mappingMock.Setup(m => m.GetRequestMatchResult(It.IsAny<RequestMessage>(), It.IsAny<string>())).Returns(matchResult);
var mappings = new ConcurrentDictionary<Guid, IMapping>();
mappings.TryAdd(guid, mappingMock.Object);
_optionsMock.Setup(o => o.Mappings).Returns(mappings);
var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "GET", "::1");
// Act
var result = _sut.FindBestMatch(request);
// Assert
result.Match.Should().BeNull();
result.Partial.Should().BeNull();
mappingMock.Verify(m => m.GetRequestMatchResult(It.IsAny<RequestMessage>(), It.IsAny<string>()), Times.Never);
}
private static ConcurrentDictionary<Guid, IMapping> InitMappings(params (Guid guid, double[] scores, double? probability)[] matches)
{
var mappings = new ConcurrentDictionary<Guid, IMapping>();
@@ -237,6 +267,7 @@ public class MappingMatcherTests
{
var mappingMock = new Mock<IMapping>();
mappingMock.SetupGet(m => m.Guid).Returns(match.guid);
mappingMock.SetupGet(m => m.IsDisabled).Returns(false);
var requestMatchResult = new RequestMatchResult();
foreach (var score in match.scores)