mirror of
https://github.com/wiremock/WireMock.Net.git
synced 2026-11-13 12:23:48 +01:00
Add near-miss diagnostics and request count/unmatched endpoints to the admin API (#1521)
* Add near-miss diagnostics and request count/unmatched admin endpoints (#171) Add IWireMockServer.FindUnmatchedLogEntries, FindNearMisses and FindNearMissesForUnmatchedRequests, which rank the mappings that partially match a request by score. Expose them through the admin API (POST requests/count, GET requests/unmatched, GET requests/unmatched/near-misses and GET requests/{guid}/near-misses), the RestClient and the OpenAPI document. * Make scenario delay tests independent of server warm-up time The two *_StateTransitions_BeforeDelayCompletes tests waited a fixed 100 ms for the scenario state to change, which fails on a cold server. Poll server.Scenarios until the expected state is reached instead. --------- Co-authored-by: Mattias <13809209+Quattordici@users.noreply.github.com>
This commit is contained in:
1 parent
c94824bd64
commit
dc14136492
18 files changed
+1104
-62
No files matched your search
@@ -0,0 +1,230 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using RestEase;
|
||||
using WireMock.Admin.Mappings;
|
||||
using WireMock.Client;
|
||||
using WireMock.Logging;
|
||||
using WireMock.RequestBuilders;
|
||||
using WireMock.ResponseBuilders;
|
||||
using WireMock.Server;
|
||||
using WireMock.Settings;
|
||||
|
||||
namespace WireMock.Net.Tests.AdminApi;
|
||||
|
||||
public partial class WireMockAdminApiTests
|
||||
{
|
||||
private static readonly Guid OrdersPostMappingGuid = new("22222222-0000-0000-0000-000000000001");
|
||||
private static readonly Guid OrdersGetMappingGuid = new("22222222-0000-0000-0000-000000000002");
|
||||
|
||||
[Fact]
|
||||
public async Task IWireMockAdminApi_CountRequestsAsync()
|
||||
{
|
||||
// Arrange
|
||||
var cancellationToken = TestContext.Current.CancellationToken;
|
||||
using var server = StartServerWithOrdersMappings();
|
||||
using var httpClient = server.CreateClient();
|
||||
await httpClient.GetAsync("/orders", cancellationToken);
|
||||
await httpClient.GetAsync("/orders", cancellationToken);
|
||||
await httpClient.PostAsync("/orders", new StringContent("{}"), cancellationToken);
|
||||
|
||||
var api = RestClient.For<IWireMockAdminApi>(server.Url!);
|
||||
|
||||
// Act
|
||||
var getCount = await api.CountRequestsAsync(new RequestModel { Methods = ["GET"] }, cancellationToken);
|
||||
var deleteCount = await api.CountRequestsAsync(new RequestModel { Methods = ["DELETE"] }, cancellationToken);
|
||||
|
||||
// Assert
|
||||
getCount.Count.Should().Be(2);
|
||||
deleteCount.Count.Should().Be(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task IWireMockAdminApi_GetUnmatchedRequestsAsync()
|
||||
{
|
||||
// Arrange
|
||||
var cancellationToken = TestContext.Current.CancellationToken;
|
||||
using var server = StartServerWithOrdersMappings();
|
||||
using var httpClient = server.CreateClient();
|
||||
await httpClient.GetAsync("/orders", cancellationToken);
|
||||
await httpClient.GetAsync("/unknown", cancellationToken);
|
||||
|
||||
var api = RestClient.For<IWireMockAdminApi>(server.Url!);
|
||||
|
||||
// Act
|
||||
var requests = await api.GetUnmatchedRequestsAsync(cancellationToken);
|
||||
|
||||
// Assert
|
||||
requests.Should().ContainSingle();
|
||||
requests[0].Request!.Path.Should().Be("/unknown");
|
||||
requests[0].MappingGuid.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task IWireMockAdminApi_GetNearMissesForUnmatchedRequestsAsync()
|
||||
{
|
||||
// Arrange
|
||||
var cancellationToken = TestContext.Current.CancellationToken;
|
||||
using var server = StartServerWithOrdersMappings();
|
||||
using var httpClient = server.CreateClient();
|
||||
await httpClient.PutAsync("/orders", new StringContent("{}"), cancellationToken);
|
||||
var logEntryGuid = server.LogEntries.Single().Guid;
|
||||
|
||||
var api = RestClient.For<IWireMockAdminApi>(server.Url!);
|
||||
|
||||
// Act
|
||||
var nearMisses = await api.GetNearMissesForUnmatchedRequestsAsync(cancellationToken: cancellationToken);
|
||||
|
||||
// Assert
|
||||
nearMisses.Should().HaveCount(2);
|
||||
nearMisses.Select(nm => nm.MappingGuid).Should().BeEquivalentTo([OrdersPostMappingGuid, OrdersGetMappingGuid]);
|
||||
nearMisses.Should().AllSatisfy(nm =>
|
||||
{
|
||||
nm.LogEntryGuid.Should().Be(logEntryGuid);
|
||||
nm.Request!.Method.Should().Be("PUT");
|
||||
nm.Request.Path.Should().Be("/orders");
|
||||
nm.RequestMatchResult!.IsPerfectMatch.Should().BeFalse();
|
||||
nm.RequestMatchResult.AverageTotalScore.Should().BeApproximately(0.5, 0.001);
|
||||
});
|
||||
nearMisses.Select(nm => nm.MappingTitle).Should().Contain("Create order");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task IWireMockAdminApi_GetNearMissesForUnmatchedRequestsAsync_WithMaxResults()
|
||||
{
|
||||
// Arrange
|
||||
var cancellationToken = TestContext.Current.CancellationToken;
|
||||
using var server = StartServerWithOrdersMappings();
|
||||
using var httpClient = server.CreateClient();
|
||||
await httpClient.PutAsync("/orders", new StringContent("{}"), cancellationToken);
|
||||
|
||||
var api = RestClient.For<IWireMockAdminApi>(server.Url!);
|
||||
|
||||
// Act
|
||||
var nearMisses = await api.GetNearMissesForUnmatchedRequestsAsync(1, cancellationToken);
|
||||
|
||||
// Assert
|
||||
nearMisses.Should().ContainSingle();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task IWireMockAdminApi_GetNearMissesForUnmatchedRequestsAsync_WithInvalidMaxResults_ReturnsBadRequest()
|
||||
{
|
||||
// Arrange
|
||||
var cancellationToken = TestContext.Current.CancellationToken;
|
||||
using var server = StartServerWithOrdersMappings();
|
||||
using var httpClient = server.CreateClient();
|
||||
|
||||
// Act
|
||||
var response = await httpClient.GetAsync("/__admin/requests/unmatched/near-misses?maxResults=0", cancellationToken);
|
||||
|
||||
// Assert
|
||||
response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task IWireMockAdminApi_GetNearMissesForRequestAsync_ShouldExcludeMatchedMapping()
|
||||
{
|
||||
// Arrange
|
||||
var cancellationToken = TestContext.Current.CancellationToken;
|
||||
using var server = StartServerWithOrdersMappings();
|
||||
using var httpClient = server.CreateClient();
|
||||
await httpClient.GetAsync("/orders", cancellationToken);
|
||||
var logEntry = server.LogEntries.Single();
|
||||
|
||||
var api = RestClient.For<IWireMockAdminApi>(server.Url!);
|
||||
|
||||
// Act
|
||||
var nearMisses = await api.GetNearMissesForRequestAsync(logEntry.Guid, cancellationToken: cancellationToken);
|
||||
|
||||
// Assert
|
||||
logEntry.MappingGuid.Should().Be(OrdersGetMappingGuid);
|
||||
nearMisses.Should().ContainSingle();
|
||||
nearMisses[0].MappingGuid.Should().Be(OrdersPostMappingGuid);
|
||||
nearMisses[0].LogEntryGuid.Should().Be(logEntry.Guid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task IWireMockAdminApi_GetNearMissesForRequestAsync_UnknownRequest_ReturnsNotFound()
|
||||
{
|
||||
// Arrange
|
||||
var cancellationToken = TestContext.Current.CancellationToken;
|
||||
using var server = StartServerWithOrdersMappings();
|
||||
|
||||
var api = RestClient.For<IWireMockAdminApi>(server.Url!);
|
||||
|
||||
// Act
|
||||
Func<Task> act = () => api.GetNearMissesForRequestAsync(Guid.NewGuid(), cancellationToken: cancellationToken);
|
||||
|
||||
// Assert
|
||||
(await act.Should().ThrowAsync<ApiException>()).Which.StatusCode.Should().Be(HttpStatusCode.NotFound);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task IWireMockAdminApi_GetNearMissesForRequestAsync_WithMultiplePerfectMatches_ShouldNotReturnPerfectMatches()
|
||||
{
|
||||
// Arrange
|
||||
var cancellationToken = TestContext.Current.CancellationToken;
|
||||
using var server = StartServerWithOrdersMappings();
|
||||
server
|
||||
.Given(Request.Create().WithPath("/orders").UsingGet())
|
||||
.AtPriority(100)
|
||||
.WithTitle("Get orders (fallback)")
|
||||
.RespondWith(Response.Create().WithStatusCode(HttpStatusCode.OK));
|
||||
using var httpClient = server.CreateClient();
|
||||
await httpClient.GetAsync("/orders", cancellationToken);
|
||||
var logEntry = server.LogEntries.Single();
|
||||
|
||||
var api = RestClient.For<IWireMockAdminApi>(server.Url!);
|
||||
|
||||
// Act
|
||||
var nearMisses = await api.GetNearMissesForRequestAsync(logEntry.Guid, cancellationToken: cancellationToken);
|
||||
|
||||
// Assert
|
||||
nearMisses.Should().ContainSingle();
|
||||
nearMisses[0].MappingGuid.Should().Be(OrdersPostMappingGuid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task IWireMockAdminApi_CountRequestsAsync_ShouldNotCountAdminRequests()
|
||||
{
|
||||
// Arrange
|
||||
var cancellationToken = TestContext.Current.CancellationToken;
|
||||
using var server = StartServerWithOrdersMappings();
|
||||
using var httpClient = server.CreateClient();
|
||||
await httpClient.GetAsync("/orders", cancellationToken);
|
||||
await httpClient.GetAsync("/__admin/mappings", cancellationToken);
|
||||
|
||||
var api = RestClient.For<IWireMockAdminApi>(server.Url!);
|
||||
|
||||
// Act
|
||||
var count = await api.CountRequestsAsync(new RequestModel { Methods = ["GET"] }, cancellationToken);
|
||||
|
||||
// Assert
|
||||
count.Count.Should().Be(1);
|
||||
}
|
||||
|
||||
private static WireMockServer StartServerWithOrdersMappings()
|
||||
{
|
||||
var server = WireMockServer.Start(new WireMockServerSettings
|
||||
{
|
||||
StartAdminInterface = true,
|
||||
Logger = new WireMockNullLogger()
|
||||
});
|
||||
|
||||
server
|
||||
.Given(Request.Create().WithPath("/orders").UsingPost())
|
||||
.WithGuid(OrdersPostMappingGuid)
|
||||
.WithTitle("Create order")
|
||||
.RespondWith(Response.Create().WithStatusCode(HttpStatusCode.Created));
|
||||
|
||||
server
|
||||
.Given(Request.Create().WithPath("/orders").UsingGet())
|
||||
.WithGuid(OrdersGetMappingGuid)
|
||||
.WithTitle("Get orders")
|
||||
.RespondWith(Response.Create().WithStatusCode(HttpStatusCode.OK));
|
||||
|
||||
return server;
|
||||
}
|
||||
}
|
||||
@@ -6,5 +6,5 @@ internal static class Constants
|
||||
{
|
||||
internal const int NumStaticMappings = 10;
|
||||
|
||||
internal const int NumAdminMappings = 39;
|
||||
internal const int NumAdminMappings = 43;
|
||||
}
|
||||
@@ -259,6 +259,96 @@ public class MappingMatcherTests
|
||||
mappingMock.Verify(m => m.GetRequestMatchResult(It.IsAny<RequestMessage>(), It.IsAny<string>()), Times.Never);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MappingMatcher_FindNearMisses_ShouldExcludePerfectMatchesAndMismatches_AndOrderByBestMatchFirst()
|
||||
{
|
||||
// Assign
|
||||
var perfect = Guid.Parse("00000000-0000-0000-0000-000000000001");
|
||||
var mismatch = Guid.Parse("00000000-0000-0000-0000-000000000002");
|
||||
var low = Guid.Parse("00000000-0000-0000-0000-000000000003");
|
||||
var high = Guid.Parse("00000000-0000-0000-0000-000000000004");
|
||||
var mappings = InitMappings
|
||||
(
|
||||
(perfect, [1.0], null),
|
||||
(mismatch, [0.0], null),
|
||||
(low, [0.4], null),
|
||||
(high, [0.8], null)
|
||||
);
|
||||
_optionsMock.Setup(o => o.Mappings).Returns(mappings);
|
||||
|
||||
var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "GET", "::1");
|
||||
|
||||
// Act
|
||||
var result = _sut.FindNearMisses(request, 10);
|
||||
|
||||
// Assert
|
||||
result.Select(r => r.Mapping.Guid).Should().Equal(high, low);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MappingMatcher_FindNearMisses_ShouldRespectMaxResults()
|
||||
{
|
||||
// Assign
|
||||
var low = Guid.Parse("00000000-0000-0000-0000-000000000001");
|
||||
var high = Guid.Parse("00000000-0000-0000-0000-000000000002");
|
||||
var mappings = InitMappings
|
||||
(
|
||||
(low, [0.4], null),
|
||||
(high, [0.8], null)
|
||||
);
|
||||
_optionsMock.Setup(o => o.Mappings).Returns(mappings);
|
||||
|
||||
var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "GET", "::1");
|
||||
|
||||
// Act
|
||||
var result = _sut.FindNearMisses(request, 1);
|
||||
|
||||
// Assert
|
||||
result.Should().ContainSingle().Which.Mapping.Guid.Should().Be(high);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MappingMatcher_FindNearMisses_ShouldIgnoreProbability()
|
||||
{
|
||||
// Assign
|
||||
var withProbability = Guid.Parse("00000000-0000-0000-0000-000000000001");
|
||||
var mappings = InitMappings
|
||||
(
|
||||
(withProbability, [0.5], 0.1)
|
||||
);
|
||||
_optionsMock.Setup(o => o.Mappings).Returns(mappings);
|
||||
|
||||
var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "GET", "::1");
|
||||
|
||||
// Act
|
||||
var result = _sut.FindNearMisses(request, 3);
|
||||
|
||||
// Assert
|
||||
result.Should().ContainSingle().Which.Mapping.Guid.Should().Be(withProbability);
|
||||
_randomizerDoubleBetween0And1Mock.Verify(r => r.Generate(), Times.Never);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MappingMatcher_FindNearMisses_WhenMappingThrowsException_ShouldSkipMapping()
|
||||
{
|
||||
// Assign
|
||||
var mappingMock = new Mock<IMapping>();
|
||||
mappingMock.SetupGet(m => m.IsDisabled).Returns(false);
|
||||
mappingMock.Setup(m => m.GetRequestMatchResult(It.IsAny<IRequestMessage>(), It.IsAny<string>())).Throws<Exception>();
|
||||
|
||||
var mappings = new ConcurrentDictionary<Guid, IMapping>();
|
||||
mappings.TryAdd(Guid.NewGuid(), mappingMock.Object);
|
||||
_optionsMock.Setup(o => o.Mappings).Returns(mappings);
|
||||
|
||||
var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "GET", "::1");
|
||||
|
||||
// Act
|
||||
var result = _sut.FindNearMisses(request, 3);
|
||||
|
||||
// Assert
|
||||
result.Should().BeEmpty();
|
||||
}
|
||||
|
||||
private static ConcurrentDictionary<Guid, IMapping> InitMappings(params (Guid guid, double[] scores, double? probability)[] matches)
|
||||
{
|
||||
var mappings = new ConcurrentDictionary<Guid, IMapping>();
|
||||
|
||||
@@ -442,8 +442,9 @@ public class StatefulBehaviorTests
|
||||
// Act: fire request 1 but don't await it yet — it will sit in a 500 ms delay
|
||||
var request1Task = client.GetStringAsync(server.Url + path, cancellationToken);
|
||||
|
||||
// Give the server a moment to match & transition state before the delay completes
|
||||
await Task.Delay(100, cancellationToken);
|
||||
// Wait until the server has matched request 1 & transitioned the state; request 1 must still be delayed
|
||||
await WaitForScenarioStateAsync(server, "1260", "State1", cancellationToken);
|
||||
request1Task.IsCompleted.Should().BeFalse();
|
||||
|
||||
// Request 2 is sent while request 1 is still being delayed.
|
||||
// After the fix the state has already transitioned, so request 2 matches Mapping 2.
|
||||
@@ -481,7 +482,8 @@ public class StatefulBehaviorTests
|
||||
|
||||
// Act
|
||||
var request1Task = client.GetStringAsync(server.Url + path, cancellationToken);
|
||||
await Task.Delay(100, cancellationToken);
|
||||
await WaitForScenarioStateAsync(server, "s", "State1", cancellationToken);
|
||||
request1Task.IsCompleted.Should().BeFalse();
|
||||
var response2 = await client.GetStringAsync(server.Url + path, cancellationToken);
|
||||
var response1 = await request1Task;
|
||||
|
||||
@@ -574,5 +576,20 @@ public class StatefulBehaviorTests
|
||||
|
||||
server.Stop();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Polls the scenario state instead of using a fixed delay, so tests do not depend on how fast the server handles the first request
|
||||
/// (a cold server can take longer than a fixed delay to match the request and transition the state).
|
||||
/// </summary>
|
||||
private static async Task WaitForScenarioStateAsync(WireMockServer server, string scenario, string expectedState, CancellationToken cancellationToken)
|
||||
{
|
||||
using var timeout = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||
timeout.CancelAfter(TimeSpan.FromSeconds(5));
|
||||
|
||||
while (server.Scenarios.FirstOrDefault(s => s.Name == scenario)?.NextState != expectedState)
|
||||
{
|
||||
await Task.Delay(10, timeout.Token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,226 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System.Net;
|
||||
using WireMock.Models;
|
||||
using WireMock.RequestBuilders;
|
||||
using WireMock.ResponseBuilders;
|
||||
using WireMock.Server;
|
||||
|
||||
namespace WireMock.Net.Tests;
|
||||
|
||||
public partial class WireMockServerTests
|
||||
{
|
||||
private static readonly Guid NearMissPostGuid = new("11111111-0000-0000-0000-000000000001");
|
||||
private static readonly Guid NearMissGetGuid = new("11111111-0000-0000-0000-000000000002");
|
||||
private static readonly Guid NearMissOtherGuid = new("11111111-0000-0000-0000-000000000003");
|
||||
|
||||
[Fact]
|
||||
public async Task WireMockServer_FindUnmatchedLogEntries_ShouldOnlyReturnRequestsWithoutMatchingMapping()
|
||||
{
|
||||
// Arrange
|
||||
var cancellationToken = TestContext.Current.CancellationToken;
|
||||
using var server = StartServerWithNearMissMappings();
|
||||
using var client = server.CreateClient();
|
||||
|
||||
await client.GetAsync("/orders", cancellationToken);
|
||||
await client.GetAsync("/order", cancellationToken);
|
||||
|
||||
// Act
|
||||
var unmatched = server.FindUnmatchedLogEntries();
|
||||
|
||||
// Assert
|
||||
unmatched.Should().ContainSingle();
|
||||
unmatched[0].RequestMessage!.Path.Should().Be("/order");
|
||||
unmatched[0].ResponseMessage!.StatusCode.Should().Be((int)HttpStatusCode.NotFound);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WireMockServer_FindNearMisses_ShouldReturnClosestMappingsFirst()
|
||||
{
|
||||
// Arrange
|
||||
var cancellationToken = TestContext.Current.CancellationToken;
|
||||
using var server = StartServerWithNearMissMappings();
|
||||
using var client = server.CreateClient();
|
||||
|
||||
// Path matches the POST and GET mappings, but the method is wrong for both.
|
||||
await client.PutAsync("/orders", new StringContent("{}"), cancellationToken);
|
||||
var requestMessage = server.LogEntries.Single().RequestMessage!;
|
||||
|
||||
// Act
|
||||
var nearMisses = server.FindNearMisses(requestMessage);
|
||||
|
||||
// Assert
|
||||
nearMisses.Should().HaveCount(2);
|
||||
nearMisses.Select(nm => nm.MappingGuid).Should().BeEquivalentTo([NearMissPostGuid, NearMissGetGuid]);
|
||||
nearMisses.Should().AllSatisfy(nm =>
|
||||
{
|
||||
nm.LogEntryGuid.Should().BeNull();
|
||||
nm.RequestMessage.Should().BeSameAs(requestMessage);
|
||||
nm.RequestMatchResult.IsPerfectMatch.Should().BeFalse();
|
||||
nm.RequestMatchResult.MatchDetails.Should().Contain(md => md.Name == "MethodMatcher" && md.Score < 1.0);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WireMockServer_FindNearMisses_ShouldRespectMaxResults()
|
||||
{
|
||||
// Arrange
|
||||
var cancellationToken = TestContext.Current.CancellationToken;
|
||||
using var server = StartServerWithNearMissMappings();
|
||||
using var client = server.CreateClient();
|
||||
|
||||
await client.PutAsync("/orders", new StringContent("{}"), cancellationToken);
|
||||
|
||||
// Act
|
||||
var nearMisses = server.FindNearMisses(server.LogEntries.Single().RequestMessage!, maxResults: 1);
|
||||
|
||||
// Assert
|
||||
nearMisses.Should().ContainSingle();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WireMockServer_FindNearMisses_ShouldIgnoreDisabledMappings()
|
||||
{
|
||||
// Arrange
|
||||
var cancellationToken = TestContext.Current.CancellationToken;
|
||||
using var server = StartServerWithNearMissMappings();
|
||||
server.Mappings.Single(m => m.Guid == NearMissPostGuid).IsDisabled = true;
|
||||
using var client = server.CreateClient();
|
||||
|
||||
await client.PutAsync("/orders", new StringContent("{}"), cancellationToken);
|
||||
|
||||
// Act
|
||||
var nearMisses = server.FindNearMisses(server.LogEntries.Single().RequestMessage!);
|
||||
|
||||
// Assert
|
||||
nearMisses.Select(nm => nm.MappingGuid).Should().NotContain(NearMissPostGuid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WireMockServer_FindNearMisses_WithInvalidMaxResults_ShouldThrow()
|
||||
{
|
||||
// Arrange
|
||||
using var server = WireMockServer.Start();
|
||||
var requestMessage = new RequestMessage(new UrlDetails("http://localhost/orders"), "GET", "::1");
|
||||
|
||||
// Act
|
||||
Action act = () => server.FindNearMisses(requestMessage, maxResults: 0);
|
||||
|
||||
// Assert
|
||||
act.Should().Throw<ArgumentException>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WireMockServer_FindNearMissesForUnmatchedRequests_ShouldReturnNearMissesPerUnmatchedRequest()
|
||||
{
|
||||
// Arrange
|
||||
var cancellationToken = TestContext.Current.CancellationToken;
|
||||
using var server = StartServerWithNearMissMappings();
|
||||
using var client = server.CreateClient();
|
||||
|
||||
await client.GetAsync("/orders", cancellationToken);
|
||||
await client.PutAsync("/orders", new StringContent("{}"), cancellationToken);
|
||||
await client.DeleteAsync("/customers", cancellationToken);
|
||||
var unmatchedGuids = server.FindUnmatchedLogEntries().Select(le => le.Guid).ToArray();
|
||||
|
||||
// Act
|
||||
var nearMisses = server.FindNearMissesForUnmatchedRequests(maxResultsPerRequest: 1);
|
||||
|
||||
// Assert
|
||||
unmatchedGuids.Should().HaveCount(2);
|
||||
nearMisses.Should().HaveCount(2);
|
||||
nearMisses.Select(nm => nm.LogEntryGuid!.Value).Should().BeEquivalentTo(unmatchedGuids);
|
||||
nearMisses.Single(nm => nm.RequestMessage.Path == "/customers").MappingGuid.Should().Be(NearMissOtherGuid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WireMockServer_FindNearMisses_ShouldNotReturnPerfectMatches()
|
||||
{
|
||||
// Arrange
|
||||
var cancellationToken = TestContext.Current.CancellationToken;
|
||||
using var server = StartServerWithNearMissMappings();
|
||||
server
|
||||
.Given(Request.Create().WithPath("/orders").UsingGet())
|
||||
.AtPriority(100)
|
||||
.WithTitle("Get orders (fallback)")
|
||||
.RespondWith(Response.Create().WithStatusCode(HttpStatusCode.OK));
|
||||
using var client = server.CreateClient();
|
||||
|
||||
await client.GetAsync("/orders", cancellationToken);
|
||||
|
||||
// Act
|
||||
var nearMisses = server.FindNearMisses(server.LogEntries.Single().RequestMessage!);
|
||||
|
||||
// Assert
|
||||
nearMisses.Should().ContainSingle();
|
||||
nearMisses[0].MappingGuid.Should().Be(NearMissPostGuid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WireMockServer_FindNearMisses_ForLogEntry_ShouldSetLogEntryGuid()
|
||||
{
|
||||
// Arrange
|
||||
var cancellationToken = TestContext.Current.CancellationToken;
|
||||
using var server = StartServerWithNearMissMappings();
|
||||
using var client = server.CreateClient();
|
||||
|
||||
await client.PutAsync("/orders", new StringContent("{}"), cancellationToken);
|
||||
var logEntry = server.LogEntries.Single();
|
||||
|
||||
// Act
|
||||
var nearMisses = server.FindNearMisses(logEntry, maxResults: 1);
|
||||
|
||||
// Assert
|
||||
nearMisses.Should().ContainSingle();
|
||||
nearMisses[0].LogEntryGuid.Should().Be(logEntry.Guid);
|
||||
nearMisses[0].RequestMessage.Should().BeSameAs(logEntry.RequestMessage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WireMockServer_FindUnmatchedLogEntries_WithAllowPartialMapping_ShouldIncludeRequestsServedByAPartialMatch()
|
||||
{
|
||||
// Arrange
|
||||
var cancellationToken = TestContext.Current.CancellationToken;
|
||||
using var server = StartServerWithNearMissMappings();
|
||||
server.AllowPartialMapping();
|
||||
using var client = server.CreateClient();
|
||||
|
||||
await client.GetAsync("/orders", cancellationToken);
|
||||
await client.PutAsync("/orders", new StringContent("{}"), cancellationToken);
|
||||
|
||||
// Act
|
||||
var unmatched = server.FindUnmatchedLogEntries();
|
||||
var nearMisses = server.FindNearMissesForUnmatchedRequests();
|
||||
|
||||
// Assert
|
||||
unmatched.Should().ContainSingle();
|
||||
unmatched[0].RequestMessage!.Method.Should().Be("PUT");
|
||||
unmatched[0].MappingGuid.Should().NotBeNull("the request was served by a partial match");
|
||||
nearMisses.Select(nm => nm.MappingGuid).Should().Contain(unmatched[0].MappingGuid!.Value);
|
||||
}
|
||||
|
||||
private static WireMockServer StartServerWithNearMissMappings()
|
||||
{
|
||||
var server = WireMockServer.Start();
|
||||
|
||||
server
|
||||
.Given(Request.Create().WithPath("/orders").UsingPost())
|
||||
.WithGuid(NearMissPostGuid)
|
||||
.WithTitle("Create order")
|
||||
.RespondWith(Response.Create().WithStatusCode(HttpStatusCode.Created));
|
||||
|
||||
server
|
||||
.Given(Request.Create().WithPath("/orders").UsingGet())
|
||||
.WithGuid(NearMissGetGuid)
|
||||
.WithTitle("Get orders")
|
||||
.RespondWith(Response.Create().WithStatusCode(HttpStatusCode.OK));
|
||||
|
||||
server
|
||||
.Given(Request.Create().WithPath("/customers").UsingDelete().WithHeader("X-Api-Key", "secret"))
|
||||
.WithGuid(NearMissOtherGuid)
|
||||
.WithTitle("Delete customers")
|
||||
.RespondWith(Response.Create().WithStatusCode(HttpStatusCode.NoContent));
|
||||
|
||||
return server;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user