diff --git a/examples/WireMock.Net.Console.MimePart/WireMock.Net.Console.MimePart.csproj b/examples/WireMock.Net.Console.MimePart/WireMock.Net.Console.MimePart.csproj
index 537b650a..0ce124d9 100644
--- a/examples/WireMock.Net.Console.MimePart/WireMock.Net.Console.MimePart.csproj
+++ b/examples/WireMock.Net.Console.MimePart/WireMock.Net.Console.MimePart.csproj
@@ -16,7 +16,7 @@
-
+
diff --git a/examples/WireMock.Net.Service/packages.config b/examples/WireMock.Net.Service/packages.config
index 489a0859..d01e7098 100644
--- a/examples/WireMock.Net.Service/packages.config
+++ b/examples/WireMock.Net.Service/packages.config
@@ -1,6 +1,6 @@
-
+
diff --git a/src/WireMock.Net.Abstractions/Admin/Mappings/RequestModel.cs b/src/WireMock.Net.Abstractions/Admin/Mappings/RequestModel.cs
index a1c5faba..c3370453 100644
--- a/src/WireMock.Net.Abstractions/Admin/Mappings/RequestModel.cs
+++ b/src/WireMock.Net.Abstractions/Admin/Mappings/RequestModel.cs
@@ -1,5 +1,7 @@
// Copyright © WireMock.Net
+using WireMock.Matchers.Request;
+
namespace WireMock.Admin.Mappings;
///
@@ -61,9 +63,15 @@ public class RequestModel
/// Gets or sets the Params.
///
public IList? Params { get; set; }
-
+
///
/// Gets or sets the body.
///
public BodyModel? Body { get; set; }
+
+ ///
+ /// Type of the request matcher to return an immediate mismatch during mapping processing.
+ /// Optional.
+ ///
+ public RequestMatcherType? EarlyMatcherType { get; set; }
}
\ No newline at end of file
diff --git a/src/WireMock.Net.Abstractions/Matchers/Request/IRequestMatcher.cs b/src/WireMock.Net.Abstractions/Matchers/Request/IRequestMatcher.cs
index 8410846b..81b3531f 100644
--- a/src/WireMock.Net.Abstractions/Matchers/Request/IRequestMatcher.cs
+++ b/src/WireMock.Net.Abstractions/Matchers/Request/IRequestMatcher.cs
@@ -7,6 +7,11 @@ namespace WireMock.Matchers.Request;
///
public interface IRequestMatcher
{
+ ///
+ /// Gets the request matcher's type.
+ ///
+ public RequestMatcherType Type { get; }
+
///
/// Determines whether the specified RequestMessage is match.
///
diff --git a/src/WireMock.Net.Abstractions/Matchers/Request/RequestMatcherType.cs b/src/WireMock.Net.Abstractions/Matchers/Request/RequestMatcherType.cs
new file mode 100644
index 00000000..b469fe60
--- /dev/null
+++ b/src/WireMock.Net.Abstractions/Matchers/Request/RequestMatcherType.cs
@@ -0,0 +1,84 @@
+// Copyright © WireMock.Net
+
+namespace WireMock.Matchers.Request;
+
+///
+/// List of predefined request matcher types.
+///
+public enum RequestMatcherType
+{
+ ///
+ /// RequestMessageBodyMatcher
+ ///
+ Body = 0,
+
+ ///
+ /// RequestMessageBodyMatcher{T}
+ ///
+ BodyOfT = 1,
+
+ ///
+ /// RequestMessageClientIPMatcher
+ ///
+ ClientIP = 2,
+
+ ///
+ /// RequestMessageCookieMatcher
+ ///
+ Cookie = 3,
+
+ ///
+ /// RequestMessageGraphQLMatcher
+ ///
+ GraphQL = 4,
+
+ ///
+ /// RequestMessageHeaderMatcher
+ ///
+ Header = 5,
+
+ ///
+ /// RequestMessageHttpVersionMatcher
+ ///
+ HttpVersion = 6,
+
+ ///
+ /// RequestMessageMethodMatcher
+ ///
+ Method = 7,
+
+ ///
+ /// RequestMessageMultiPartMatcher
+ ///
+ MultiPart = 8,
+
+ ///
+ /// RequestMessageParamMatcher
+ ///
+ Param = 9,
+
+ ///
+ /// RequestMessagePathMatcher
+ ///
+ Path = 10,
+
+ ///
+ /// RequestMessageProtoBufMatcher
+ ///
+ ProtoBuf = 11,
+
+ ///
+ /// RequestMessageScenarioAndStateMatcher
+ ///
+ ScenarioAndState = 12,
+
+ ///
+ /// RequestMessageUrlMatcher
+ ///
+ Url = 13,
+
+ ///
+ /// RequestMessageCompositeMatcher
+ ///
+ Composite = 14
+}
\ No newline at end of file
diff --git a/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageBodyMatcher.cs b/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageBodyMatcher.cs
index 083ac4dd..71c01f8e 100644
--- a/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageBodyMatcher.cs
+++ b/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageBodyMatcher.cs
@@ -141,6 +141,9 @@ public class RequestMessageBodyMatcher : IRequestMatcher
MatchOperator = matchOperator;
}
+ ///
+ public RequestMatcherType Type => RequestMatcherType.Body;
+
///
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
{
diff --git a/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageBodyMatcher`1.cs b/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageBodyMatcher`1.cs
index 9699f81f..94209b55 100644
--- a/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageBodyMatcher`1.cs
+++ b/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageBodyMatcher`1.cs
@@ -31,6 +31,9 @@ public class RequestMessageBodyMatcher : IRequestMatcher
Func = Guard.NotNull(func);
}
+ ///
+ public RequestMatcherType Type => RequestMatcherType.BodyOfT;
+
///
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
{
diff --git a/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageClientIPMatcher.cs b/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageClientIPMatcher.cs
index f6b64e25..bcbfddeb 100644
--- a/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageClientIPMatcher.cs
+++ b/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageClientIPMatcher.cs
@@ -74,6 +74,9 @@ public class RequestMessageClientIPMatcher : IRequestMatcher
Funcs = Guard.NotNull(funcs);
}
+ ///
+ public RequestMatcherType Type => RequestMatcherType.ClientIP;
+
///
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
{
diff --git a/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageCompositeMatcher.cs b/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageCompositeMatcher.cs
index fd2587ab..e403df51 100644
--- a/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageCompositeMatcher.cs
+++ b/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageCompositeMatcher.cs
@@ -20,6 +20,11 @@ public abstract class RequestMessageCompositeMatcher : IRequestMatcher
///
private IEnumerable RequestMatchers { get; }
+ ///
+ /// Selected type to choose the matcher from which will immediately return a mismatch.
+ ///
+ internal RequestMatcherType? EarlyMatcherType { get; private protected set; }
+
///
/// Initializes a new instance of the class.
///
@@ -31,6 +36,9 @@ public abstract class RequestMessageCompositeMatcher : IRequestMatcher
_type = type;
}
+ ///
+ public RequestMatcherType Type => RequestMatcherType.Composite;
+
///
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
{
@@ -39,6 +47,13 @@ public abstract class RequestMessageCompositeMatcher : IRequestMatcher
return MatchScores.Mismatch;
}
+ var earlyMatcher = new RequestMessageEarlyMatcher(EarlyMatcherType, RequestMatchers);
+ var earlyMatchResult = earlyMatcher.GetMatchingScore(requestMessage, requestMatchResult);
+ if (!MatchScores.IsPerfect(earlyMatchResult))
+ {
+ return MatchScores.Mismatch;
+ }
+
if (_type == CompositeMatcherType.And)
{
return RequestMatchers.Average(requestMatcher => requestMatcher.GetMatchingScore(requestMessage, requestMatchResult));
diff --git a/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageCookieMatcher.cs b/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageCookieMatcher.cs
index 76deae3d..ab8d7d26 100644
--- a/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageCookieMatcher.cs
+++ b/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageCookieMatcher.cs
@@ -93,6 +93,9 @@ public class RequestMessageCookieMatcher : IRequestMatcher
Name = string.Empty; // Not used when Func, but set to a non-null valid value.
}
+ ///
+ public RequestMatcherType Type => RequestMatcherType.Cookie;
+
///
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
{
diff --git a/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageEarlyMatcher.cs b/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageEarlyMatcher.cs
new file mode 100644
index 00000000..91cbcbd2
--- /dev/null
+++ b/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageEarlyMatcher.cs
@@ -0,0 +1,35 @@
+// Copyright © WireMock.Net
+
+namespace WireMock.Matchers.Request;
+
+///
+/// Return the mismatch if the matching score of matchers is not perfect.
+///
+internal sealed class RequestMessageEarlyMatcher(
+ RequestMatcherType? earlyMatcherType,
+ IEnumerable requestMatchers) : IRequestMatcher
+{
+ ///
+ public RequestMatcherType Type => RequestMatcherType.Composite;
+
+ ///
+ public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
+ {
+ if (earlyMatcherType is null)
+ {
+ return MatchScores.Perfect;
+ }
+
+ var earlyMatchers = requestMatchers
+ .Where(m => m.Type == earlyMatcherType)
+ .ToList();
+
+ if (earlyMatchers.Count is 0)
+ {
+ return MatchScores.Perfect;
+ }
+
+ var compositeMatcher = new RequestBuilders.Request(earlyMatchers);
+ return compositeMatcher.GetMatchingScore(requestMessage, requestMatchResult);
+ }
+}
\ No newline at end of file
diff --git a/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageHeaderMatcher.cs b/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageHeaderMatcher.cs
index 614836c8..a81b50a4 100644
--- a/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageHeaderMatcher.cs
+++ b/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageHeaderMatcher.cs
@@ -106,6 +106,9 @@ public class RequestMessageHeaderMatcher : IRequestMatcher
Name = string.Empty; // Not used when Func, but set to a non-null valid value.
}
+ ///
+ public RequestMatcherType Type => RequestMatcherType.Header;
+
///
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
{
diff --git a/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageHttpVersionMatcher.cs b/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageHttpVersionMatcher.cs
index 474f93c3..4bb482af 100644
--- a/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageHttpVersionMatcher.cs
+++ b/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageHttpVersionMatcher.cs
@@ -65,6 +65,9 @@ public class RequestMessageHttpVersionMatcher : IRequestMatcher
MatcherOnStringFunc = Guard.NotNull(func);
}
+ ///
+ public RequestMatcherType Type => RequestMatcherType.HttpVersion;
+
///
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
{
diff --git a/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageMethodMatcher.cs b/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageMethodMatcher.cs
index 49e6397b..e470d716 100644
--- a/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageMethodMatcher.cs
+++ b/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageMethodMatcher.cs
@@ -46,6 +46,9 @@ internal class RequestMessageMethodMatcher : IRequestMatcher
MatchOperator = matchOperator;
}
+ ///
+ public RequestMatcherType Type => RequestMatcherType.Method;
+
///
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
{
diff --git a/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageMultiPartMatcher.cs b/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageMultiPartMatcher.cs
index ea5e53e7..e63acfa0 100644
--- a/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageMultiPartMatcher.cs
+++ b/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageMultiPartMatcher.cs
@@ -55,6 +55,9 @@ public class RequestMessageMultiPartMatcher : IRequestMatcher
MatchOperator = matchOperator;
}
+ ///
+ public RequestMatcherType Type => RequestMatcherType.MultiPart;
+
///
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
{
diff --git a/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageParamMatcher.cs b/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageParamMatcher.cs
index c9ccdce4..f7be7fb1 100644
--- a/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageParamMatcher.cs
+++ b/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageParamMatcher.cs
@@ -82,6 +82,9 @@ public class RequestMessageParamMatcher : IRequestMatcher
Funcs = Guard.NotNull(funcs);
}
+ ///
+ public RequestMatcherType Type => RequestMatcherType.Param;
+
///
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
{
diff --git a/src/WireMock.Net.Minimal/Matchers/Request/RequestMessagePathMatcher.cs b/src/WireMock.Net.Minimal/Matchers/Request/RequestMessagePathMatcher.cs
index 29cdeec9..49109a28 100644
--- a/src/WireMock.Net.Minimal/Matchers/Request/RequestMessagePathMatcher.cs
+++ b/src/WireMock.Net.Minimal/Matchers/Request/RequestMessagePathMatcher.cs
@@ -72,6 +72,9 @@ public class RequestMessagePathMatcher : IRequestMatcher
Funcs = Guard.NotNull(funcs);
}
+ ///
+ public RequestMatcherType Type => RequestMatcherType.Path;
+
///
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
{
diff --git a/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageScenarioAndStateMatcher.cs b/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageScenarioAndStateMatcher.cs
index f342a767..47a8e6d4 100644
--- a/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageScenarioAndStateMatcher.cs
+++ b/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageScenarioAndStateMatcher.cs
@@ -29,6 +29,9 @@ internal class RequestMessageScenarioAndStateMatcher : IRequestMatcher
_executionConditionState = executionConditionState;
}
+ ///
+ public RequestMatcherType Type => RequestMatcherType.ScenarioAndState;
+
///
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
{
diff --git a/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageUrlMatcher.cs b/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageUrlMatcher.cs
index 0bac8014..466aae11 100644
--- a/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageUrlMatcher.cs
+++ b/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageUrlMatcher.cs
@@ -72,6 +72,9 @@ public class RequestMessageUrlMatcher : IRequestMatcher
Funcs = Guard.NotNull(funcs);
}
+ ///
+ public RequestMatcherType Type => RequestMatcherType.Url;
+
///
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
{
diff --git a/src/WireMock.Net.Minimal/RequestBuilders/Request.cs b/src/WireMock.Net.Minimal/RequestBuilders/Request.cs
index aac67bab..df224d5d 100644
--- a/src/WireMock.Net.Minimal/RequestBuilders/Request.cs
+++ b/src/WireMock.Net.Minimal/RequestBuilders/Request.cs
@@ -34,7 +34,7 @@ public partial class Request : RequestMessageCompositeMatcher, IRequestBuilder
/// Initializes a new instance of the class.
///
/// The request matchers.
- private Request(IList requestMatchers) : base(requestMatchers)
+ internal Request(IList requestMatchers) : base(requestMatchers)
{
_requestMatchers = Guard.NotNull(requestMatchers);
}
@@ -81,6 +81,13 @@ public partial class Request : RequestMessageCompositeMatcher, IRequestBuilder
return this;
}
+ ///
+ public IRequestBuilder WithEarlyMismatch(RequestMatcherType? earlyMatcherType)
+ {
+ EarlyMatcherType = earlyMatcherType;
+ return this;
+ }
+
internal bool TryGetProtoBufMatcher([NotNullWhen(true)] out IProtoBufMatcher? protoBufMatcher)
{
protoBufMatcher = GetRequestMessageMatcher()?.Matcher;
diff --git a/src/WireMock.Net.Minimal/Serialization/MappingConverter.cs b/src/WireMock.Net.Minimal/Serialization/MappingConverter.cs
index f26c4ddf..2981aff7 100644
--- a/src/WireMock.Net.Minimal/Serialization/MappingConverter.cs
+++ b/src/WireMock.Net.Minimal/Serialization/MappingConverter.cs
@@ -66,6 +66,12 @@ internal class MappingConverter(MatcherMapper mapper)
// Request
sb.AppendLine(" .Given(Request.Create()");
+
+ if (request.EarlyMatcherType != null)
+ {
+ sb.AppendLine($" .WithEarlyMismatch({request.EarlyMatcherType.Value.GetFullyQualifiedEnumValue()})");
+ }
+
sb.AppendLine($" .UsingMethod({To1Or2Or3Arguments(methodMatcher?.MatchBehaviour, methodMatcher?.MatchOperator, methodMatcher?.Methods, HttpRequestMethod.GET)})");
if (pathMatcher?.Matchers != null)
@@ -300,7 +306,9 @@ internal class MappingConverter(MatcherMapper mapper)
IgnoreCase = pm.IgnoreCase ? true : null,
RejectOnMatch = pm.MatchBehaviour == MatchBehaviour.RejectOnMatch ? true : null,
Matchers = _mapper.Map(pm.Matchers)
- }).ToList() : null
+ }).ToList() : null,
+
+ EarlyMatcherType = request.EarlyMatcherType
},
Response = new ResponseModel()
};
diff --git a/src/WireMock.Net.Minimal/Server/WireMockServer.ConvertMapping.cs b/src/WireMock.Net.Minimal/Server/WireMockServer.ConvertMapping.cs
index 9c6f80f6..02960a63 100644
--- a/src/WireMock.Net.Minimal/Server/WireMockServer.ConvertMapping.cs
+++ b/src/WireMock.Net.Minimal/Server/WireMockServer.ConvertMapping.cs
@@ -267,6 +267,8 @@ public partial class WireMockServer
}
}
+ requestBuilder = requestBuilder.WithEarlyMismatch(requestModel.EarlyMatcherType);
+
return requestBuilder;
}
diff --git a/src/WireMock.Net.Shared/Matchers/Request/RequestMessageGraphQLMatcher.cs b/src/WireMock.Net.Shared/Matchers/Request/RequestMessageGraphQLMatcher.cs
index 960ad662..9df12bee 100644
--- a/src/WireMock.Net.Shared/Matchers/Request/RequestMessageGraphQLMatcher.cs
+++ b/src/WireMock.Net.Shared/Matchers/Request/RequestMessageGraphQLMatcher.cs
@@ -69,6 +69,9 @@ public class RequestMessageGraphQLMatcher : IRequestMatcher
MatchOperator = matchOperator;
}
+ ///
+ public RequestMatcherType Type => RequestMatcherType.GraphQL;
+
///
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
{
diff --git a/src/WireMock.Net.Shared/Matchers/Request/RequestMessageProtoBufMatcher.cs b/src/WireMock.Net.Shared/Matchers/Request/RequestMessageProtoBufMatcher.cs
index 0f378fd4..9f1d5378 100644
--- a/src/WireMock.Net.Shared/Matchers/Request/RequestMessageProtoBufMatcher.cs
+++ b/src/WireMock.Net.Shared/Matchers/Request/RequestMessageProtoBufMatcher.cs
@@ -30,6 +30,9 @@ public class RequestMessageProtoBufMatcher : IRequestMatcher
}
}
+ ///
+ public RequestMatcherType Type => RequestMatcherType.ProtoBuf;
+
///
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
{
diff --git a/src/WireMock.Net.Shared/RequestBuilders/IRequestBuilder.cs b/src/WireMock.Net.Shared/RequestBuilders/IRequestBuilder.cs
index 9f217d86..fb85f9a3 100644
--- a/src/WireMock.Net.Shared/RequestBuilders/IRequestBuilder.cs
+++ b/src/WireMock.Net.Shared/RequestBuilders/IRequestBuilder.cs
@@ -10,7 +10,8 @@ namespace WireMock.RequestBuilders;
public interface IRequestBuilder : IClientIPRequestBuilder
{
///
- /// Adds a request matcher to the builder.
+ /// Adds a request matcher to the builder.
+ /// If the request matcher is already present, it will be replaced.
///
/// The type of the request matcher.
/// The request matcher to add.
@@ -21,4 +22,11 @@ public interface IRequestBuilder : IClientIPRequestBuilder
/// The link back to the Mapping.
///
IMapping Mapping { get; set; }
+
+ ///
+ /// Chooses the which immediately returns a mismatch during mappings enumeration.
+ ///
+ /// Selected type to choose the matcher from available list.
+ /// The current instance.
+ IRequestBuilder WithEarlyMismatch(RequestMatcherType? earlyMatcherType);
}
\ No newline at end of file
diff --git a/test/WireMock.Net.Tests/Grpc/WireMockServerTests.Grpc.cs b/test/WireMock.Net.Tests/Grpc/WireMockServerTests.Grpc.cs
index 6009e8aa..5c6cf701 100644
--- a/test/WireMock.Net.Tests/Grpc/WireMockServerTests.Grpc.cs
+++ b/test/WireMock.Net.Tests/Grpc/WireMockServerTests.Grpc.cs
@@ -8,8 +8,11 @@ using ExampleIntegrationTest.Lookup;
using Google.Protobuf.WellKnownTypes;
using Greet;
using Grpc.Net.Client;
+using Moq;
using WireMock.Constants;
using WireMock.Matchers;
+using WireMock.Matchers.Request;
+using WireMock.Net.Xunit;
using WireMock.RequestBuilders;
using WireMock.ResponseBuilders;
using WireMock.Server;
@@ -78,7 +81,7 @@ import ""google/protobuf/empty.proto"";
service Greeter {
rpc Nothing (google.protobuf.Empty) returns (google.protobuf.Empty);
-
+
rpc SayHello (HelloRequest) returns (HelloReply);
rpc SayOther (Other) returns (HelloReply);
@@ -731,6 +734,93 @@ message Other {
Then_ReplyMessage_Should_BeCorrect(reply);
}
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public async Task WireMockServer_WithBodyAsProtoBuf_WithEarlyMismatch_ErrorLogs_Issue1442(
+ bool withEarlyMismatch)
+ {
+ // Arrange
+ var greeterId = $"test-greeter-{Guid.NewGuid()}";
+ var policyId = $"test-policy-{Guid.NewGuid()}";
+ var ct = TestContext.Current.CancellationToken;
+
+ var greeterProtoDefinition = ProtoDefinition;
+ var policyProtoDefinition = File.ReadAllText("./Grpc/policy.proto");
+
+ var mockTestOutputHelper = new Mock();
+ using var server = WireMockServer.Start(new WireMockServerSettings
+ {
+ UseHttp2 = true,
+ Logger = new TestOutputHelperWireMockLogger(mockTestOutputHelper.Object)
+ });
+
+ server
+ .AddProtoDefinition(greeterId, greeterProtoDefinition)
+ .Given(Request.Create()
+ .UsingPost()
+ .WithHttpVersion("2")
+ .WithPath("/greet.Greeter/SayHello")
+ .WithEarlyMismatch(withEarlyMismatch
+ ? RequestMatcherType.Path
+ : null)
+ .WithBodyAsProtoBuf("greet.HelloRequest", new JsonMatcher(new { name = "stef" })))
+ .WithProtoDefinition(greeterId)
+ .RespondWith(Response.Create()
+ .WithHeader("Content-Type", "application/grpc")
+ .WithTrailingHeader("grpc-status", "0")
+ .WithBodyAsProtoBuf("greet.HelloReply",
+ new
+ {
+ message = "hello {{request.BodyAsJson.name}} {{request.method}}"
+ })
+ .WithTransformer());
+
+ server
+ .AddProtoDefinition(policyId, policyProtoDefinition)
+ .Given(Request.Create()
+ .UsingPost()
+ .WithHttpVersion("2")
+ .WithPath("/Policy.PolicyService/GetVersion")
+ .WithEarlyMismatch(withEarlyMismatch
+ ? RequestMatcherType.Path
+ : null)
+ .WithBodyAsProtoBuf("ExampleIntegrationTest.Lookup.GetVersionRequest", new NotNullOrEmptyMatcher()))
+ .WithProtoDefinition(policyId)
+ .RespondWith(Response.Create()
+ .WithHeader("Content-Type", "application/grpc")
+ .WithTrailingHeader("grpc-status", "0")
+ .WithBodyAsProtoBuf("ExampleIntegrationTest.Lookup.GetVersionResponse",
+ new GetVersionResponse
+ {
+ Version = "test",
+ DateHired = new Timestamp
+ {
+ Seconds = 1722301323,
+ Nanos = 12300
+ },
+ Client = new ExampleIntegrationTest.Lookup.Client
+ {
+ ClientName = ExampleIntegrationTest.Lookup.Client.Types.Clients.Test,
+ CorrelationId = "correlation"
+ }
+ })
+ .WithTransformer());
+
+ // Act
+ var channel = GrpcChannel.ForAddress(server.Url!);
+ var policyServiceClient = new PolicyService.PolicyServiceClient(channel);
+ var greeterClient = new Greeter.GreeterClient(channel);
+
+ _ = await policyServiceClient.GetVersionAsync(new GetVersionRequest(), cancellationToken: ct);
+ _ = await greeterClient.SayHelloAsync(new HelloRequest { Name = "stef" }, cancellationToken: ct);
+
+ mockTestOutputHelper.Verify(
+ x => x.WriteLine(
+ It.Is(log => log.Contains("[Error]") && log.Contains("Exception"))),
+ withEarlyMismatch ? Times.Never : Times.AtLeastOnce);
+ }
+
private static WireMockServer Given_When_ServerStarted_And_RunningOnHttpAndGrpc()
{
var settings = new WireMockServerSettings
diff --git a/test/WireMock.Net.Tests/RequestMatchers/RequestMessageCompositeMatcherTests.cs b/test/WireMock.Net.Tests/RequestMatchers/RequestMessageCompositeMatcherTests.cs
index 3fcaae75..04d46fdb 100644
--- a/test/WireMock.Net.Tests/RequestMatchers/RequestMessageCompositeMatcherTests.cs
+++ b/test/WireMock.Net.Tests/RequestMatchers/RequestMessageCompositeMatcherTests.cs
@@ -1,8 +1,11 @@
// Copyright © WireMock.Net
using Moq;
+using WireMock.Constants;
+using WireMock.Matchers;
using WireMock.Matchers.Request;
using WireMock.Models;
+using WireMock.RequestBuilders;
namespace WireMock.Net.Tests.RequestMatchers;
@@ -10,8 +13,12 @@ public class RequestMessageCompositeMatcherTests
{
private class Helper : RequestMessageCompositeMatcher
{
- public Helper(IEnumerable requestMatchers, CompositeMatcherType type = CompositeMatcherType.And) : base(requestMatchers, type)
+ public Helper(
+ IEnumerable requestMatchers,
+ CompositeMatcherType type = CompositeMatcherType.And,
+ RequestMatcherType? earlyMatcherType = null) : base(requestMatchers, type)
{
+ EarlyMatcherType = earlyMatcherType;
}
}
@@ -77,4 +84,74 @@ public class RequestMessageCompositeMatcherTests
requestMatcher1Mock.Verify(rm => rm.GetMatchingScore(It.IsAny(), It.IsAny()), Times.Once);
requestMatcher2Mock.Verify(rm => rm.GetMatchingScore(It.IsAny(), It.IsAny()), Times.Once);
}
+
+ [Fact]
+ public void RequestMessageCompositeMatcher_GetMatchingScore_EarlyMismatch()
+ {
+ // Assign
+ var requestMatcher1Mock = new Mock();
+ requestMatcher1Mock.Setup(rm => rm.GetMatchingScore(It.IsAny(), It.IsAny())).Returns(1.0d);
+ var requestMatcher2Mock = new Mock();
+ requestMatcher2Mock.Setup(rm => rm.GetMatchingScore(It.IsAny(), It.IsAny())).Returns(0.8d);
+ var postMatcher = new RequestMessageMethodMatcher(HttpRequestMethod.POST);
+
+ var requestMessage = new RequestMessage(new UrlDetails("http://localhost"), HttpRequestMethod.GET, "127.0.0.1");
+ var matcher = new Helper(
+ [requestMatcher1Mock.Object, requestMatcher2Mock.Object, postMatcher],
+ earlyMatcherType: RequestMatcherType.Method);
+
+ // Act
+ var result = new RequestMatchResult();
+ double score = matcher.GetMatchingScore(requestMessage, result);
+
+ // Assert
+ score.Should().Be(0.0d);
+
+ // Verify
+ requestMatcher1Mock.Verify(rm => rm.GetMatchingScore(It.IsAny(), It.IsAny()), Times.Never);
+ requestMatcher2Mock.Verify(rm => rm.GetMatchingScore(It.IsAny(), It.IsAny()), Times.Never);
+ }
+
+ [Fact]
+ public void RequestMessageCompositeMatcher_GetMatchingScore_SeveralHeadersEarlyMismatch()
+ {
+ // Assign
+ var headers = new Dictionary
+ {
+ { "teST", new[] { "x" } },
+ { "teST2", new[] { "z" } }
+ };
+ var requestMessage = new RequestMessage(new UrlDetails("http://localhost"), "GET", "127.0.0.1", null, headers);
+ var request = Request.Create()
+ .WithEarlyMismatch(RequestMatcherType.Header)
+ .UsingAnyMethod()
+ .WithHeader("teST", "x")
+ .WithHeader("teST1", ["xx", "yy"])
+ .WithHeader("teST2", ["y", "z"], matchOperator: MatchOperator.And);
+
+ // Act
+ var score = request.GetMatchingScore(requestMessage, new RequestMatchResult());
+
+ // Assert
+ score.Should().Be(0.0d);
+ }
+
+ [Fact]
+ public void RequestMessageCompositeMatcher_GetMatchingScore_SeveralParamEarlyMismatchSuccess()
+ {
+ // Assign
+ var uriWithParams = new Uri("http://localhost?test1=1&test2=2");
+ var requestMessage = new RequestMessage(new UrlDetails(uriWithParams), "GET", "127.0.0.1");
+ var request = Request.Create()
+ .WithEarlyMismatch(RequestMatcherType.Param)
+ .UsingAnyMethod()
+ .WithParam("test1", "1")
+ .WithParam("test2", "2");
+
+ // Act
+ var score = request.GetMatchingScore(requestMessage, new RequestMatchResult());
+
+ // Assert
+ score.Should().Be(1.0d);
+ }
}
\ No newline at end of file
diff --git a/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToCSharpCode.cs b/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToCSharpCode.cs
index 3d00b532..811b9113 100644
--- a/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToCSharpCode.cs
+++ b/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToCSharpCode.cs
@@ -1,5 +1,6 @@
// Copyright © WireMock.Net
+using WireMock.Matchers.Request;
using WireMock.Net.Tests.VerifyExtensions;
using WireMock.RequestBuilders;
using WireMock.ResponseBuilders;
@@ -101,6 +102,7 @@ public partial class MappingConverterTests
var guid = new Guid("8e7b9ab7-e18e-4502-8bc9-11e6679811cc");
var request = Request.Create()
.UsingGet()
+ .WithEarlyMismatch(RequestMatcherType.Method)
.WithPath("/test_path")
.WithParam("q", "42")
.WithClientIP("112.123.100.99")
diff --git a/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToCSharpCode_With_Builder_And_AddStartIsFalse.verified.txt b/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToCSharpCode_With_Builder_And_AddStartIsFalse.verified.txt
index 5a4b9e1b..b74e4e6b 100644
--- a/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToCSharpCode_With_Builder_And_AddStartIsFalse.verified.txt
+++ b/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToCSharpCode_With_Builder_And_AddStartIsFalse.verified.txt
@@ -1,5 +1,6 @@
builder
.Given(Request.Create()
+ .WithEarlyMismatch(WireMock.Matchers.Request.RequestMatcherType.Method)
.UsingMethod("GET")
.WithPath(new WildcardMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, "/test_path", false, WireMock.Matchers.MatchOperator.Or))
.WithParam("q", new ExactMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, false, WireMock.Matchers.MatchOperator.And, "42"))
diff --git a/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToCSharpCode_With_Builder_And_AddStartIsTrue.verified.txt b/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToCSharpCode_With_Builder_And_AddStartIsTrue.verified.txt
index b85622a1..6cbf645c 100644
--- a/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToCSharpCode_With_Builder_And_AddStartIsTrue.verified.txt
+++ b/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToCSharpCode_With_Builder_And_AddStartIsTrue.verified.txt
@@ -1,6 +1,7 @@
var builder = new MappingBuilder();
builder
.Given(Request.Create()
+ .WithEarlyMismatch(WireMock.Matchers.Request.RequestMatcherType.Method)
.UsingMethod("GET")
.WithPath(new WildcardMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, "/test_path", false, WireMock.Matchers.MatchOperator.Or))
.WithParam("q", new ExactMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, false, WireMock.Matchers.MatchOperator.And, "42"))
diff --git a/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToCSharpCode_With_Server_And_AddStartIsFalse.verified.txt b/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToCSharpCode_With_Server_And_AddStartIsFalse.verified.txt
index 80754949..f68a1a55 100644
--- a/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToCSharpCode_With_Server_And_AddStartIsFalse.verified.txt
+++ b/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToCSharpCode_With_Server_And_AddStartIsFalse.verified.txt
@@ -1,5 +1,6 @@
server
.Given(Request.Create()
+ .WithEarlyMismatch(WireMock.Matchers.Request.RequestMatcherType.Method)
.UsingMethod("GET")
.WithPath(new WildcardMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, "/test_path", false, WireMock.Matchers.MatchOperator.Or))
.WithParam("q", new ExactMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, false, WireMock.Matchers.MatchOperator.And, "42"))
diff --git a/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToCSharpCode_With_Server_And_AddStartIsTrue.verified.txt b/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToCSharpCode_With_Server_And_AddStartIsTrue.verified.txt
index a2589a27..887b143c 100644
--- a/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToCSharpCode_With_Server_And_AddStartIsTrue.verified.txt
+++ b/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToCSharpCode_With_Server_And_AddStartIsTrue.verified.txt
@@ -1,6 +1,7 @@
var server = WireMockServer.Start();
server
.Given(Request.Create()
+ .WithEarlyMismatch(WireMock.Matchers.Request.RequestMatcherType.Method)
.UsingMethod("GET")
.WithPath(new WildcardMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, "/test_path", false, WireMock.Matchers.MatchOperator.Or))
.WithParam("q", new ExactMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, false, WireMock.Matchers.MatchOperator.And, "42"))
diff --git a/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToMappingModel_Request_WithBodyAsGraphQLSchema_ReturnsCorrectModel.verified.txt b/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToMappingModel_Request_WithBodyAsGraphQLSchema_ReturnsCorrectModel.verified.txt
index a0c07a93..c4f97f09 100644
--- a/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToMappingModel_Request_WithBodyAsGraphQLSchema_ReturnsCorrectModel.verified.txt
+++ b/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToMappingModel_Request_WithBodyAsGraphQLSchema_ReturnsCorrectModel.verified.txt
@@ -19,7 +19,7 @@
id:ID!
firstName:String
lastName:String
- fullName:String
+ fullName:String
}
}
}
diff --git a/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToMappingModel_Request_WithEarlyMismatch_ReturnsCorrectModel.verified.txt b/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToMappingModel_Request_WithEarlyMismatch_ReturnsCorrectModel.verified.txt
new file mode 100644
index 00000000..b15a7c89
--- /dev/null
+++ b/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToMappingModel_Request_WithEarlyMismatch_ReturnsCorrectModel.verified.txt
@@ -0,0 +1,34 @@
+{
+ Guid: Guid_1,
+ UpdatedAt: DateTime_1,
+ Title: ,
+ Description: ,
+ Priority: 42,
+ Request: {
+ Path: {
+ Matchers: [
+ {
+ Name: WildcardMatcher,
+ Pattern: 1.2.3.4,
+ IgnoreCase: false
+ }
+ ]
+ },
+ Headers: [
+ {
+ Name: x1,
+ Matchers: [
+ {
+ Name: WildcardMatcher,
+ Pattern: y,
+ IgnoreCase: true
+ }
+ ],
+ IgnoreCase: true
+ }
+ ],
+ EarlyMatcherType: ClientIP
+ },
+ Response: {},
+ UseWebhooksFireAndForget: false
+}
\ No newline at end of file
diff --git a/test/WireMock.Net.Tests/Serialization/MappingConverterTests.cs b/test/WireMock.Net.Tests/Serialization/MappingConverterTests.cs
index e1dd5e42..0986a637 100644
--- a/test/WireMock.Net.Tests/Serialization/MappingConverterTests.cs
+++ b/test/WireMock.Net.Tests/Serialization/MappingConverterTests.cs
@@ -1,6 +1,7 @@
// Copyright © WireMock.Net
using WireMock.Matchers;
+using WireMock.Matchers.Request;
using WireMock.Models;
using WireMock.RequestBuilders;
using WireMock.ResponseBuilders;
@@ -538,7 +539,7 @@ message HelloReply {
id:ID!
firstName:String
lastName:String
- fullName:String
+ fullName:String
}";
var request = Request.Create().WithGraphQLSchema(schema);
var response = Response.Create();
@@ -640,4 +641,25 @@ message HelloReply {
// Verify
return Verify(model);
}
+
+ [Fact]
+ public Task ToMappingModel_Request_WithEarlyMismatch_ReturnsCorrectModel()
+ {
+ // Arrange
+ var request = Request.Create().WithEarlyMismatch(RequestMatcherType.ClientIP)
+ .WithHeader("x1", "y")
+ .WithClientIP("1.2.3.4");
+ var response = Response.Create();
+ var mapping = new Mapping(_guid, _updatedAt, string.Empty, string.Empty, null, _settings, request, response, 42, null, null, null, null, null, false, null, null);
+
+ // Act
+ var model = _sut.ToMappingModel(mapping);
+
+ // Assert
+ model.Should().NotBeNull();
+ model.Request.EarlyMatcherType.Should().Be(RequestMatcherType.ClientIP);
+
+ // Verify
+ return Verify(model);
+ }
}
\ No newline at end of file