diff --git a/WireMock.Net Solution.sln.DotSettings b/WireMock.Net Solution.sln.DotSettings
index c3564a0f..a0261229 100644
--- a/WireMock.Net Solution.sln.DotSettings
+++ b/WireMock.Net Solution.sln.DotSettings
@@ -14,6 +14,7 @@
PATCH
POST
PUT
+ QL
RSA
SSL
TE
diff --git a/examples/WireMock.Net.Console.NET6/WireMock.Net.Console.NET6.csproj b/examples/WireMock.Net.Console.NET6/WireMock.Net.Console.NET6.csproj
index 32d059a3..8954b1f8 100644
--- a/examples/WireMock.Net.Console.NET6/WireMock.Net.Console.NET6.csproj
+++ b/examples/WireMock.Net.Console.NET6/WireMock.Net.Console.NET6.csproj
@@ -27,10 +27,8 @@
-
-
diff --git a/examples/WireMock.Net.Console.Net452.Classic/MainApp.cs b/examples/WireMock.Net.Console.Net452.Classic/MainApp.cs
index 37d9f08a..f7c60bfe 100644
--- a/examples/WireMock.Net.Console.Net452.Classic/MainApp.cs
+++ b/examples/WireMock.Net.Console.Net452.Classic/MainApp.cs
@@ -42,6 +42,36 @@ namespace WireMock.Net.ConsoleApplication
public static class MainApp
{
+ private const string TestSchema = @"
+ input MessageInput {
+ content: String
+ author: String
+ }
+
+ type Message {
+ id: ID!
+ content: String
+ author: String
+ }
+
+ type Mutation {
+ createMessage(input: MessageInput): Message
+ updateMessage(id: ID!, input: MessageInput): Message
+ }
+
+ type Query {
+ greeting:String
+ students:[Student]
+ studentById(id:ID!):Student
+ }
+
+ type Student {
+ id:ID!
+ firstName:String
+ lastName:String
+ fullName:String
+ }";
+
public static void Run()
{
var mappingBuilder = new MappingBuilder();
@@ -137,6 +167,16 @@ namespace WireMock.Net.ConsoleApplication
// server.AllowPartialMapping();
+ server
+ .Given(Request.Create()
+ .WithPath("/graphql")
+ .UsingPost()
+ .WithGraphQLSchema(TestSchema)
+ )
+ .RespondWith(Response.Create()
+ .WithBody("GraphQL is ok")
+ );
+
// 400 ms
server
.Given(Request.Create()
diff --git a/src/WireMock.Net/Matchers/GraphQLMatcher.cs b/src/WireMock.Net/Matchers/GraphQLMatcher.cs
new file mode 100644
index 00000000..92eca1ff
--- /dev/null
+++ b/src/WireMock.Net/Matchers/GraphQLMatcher.cs
@@ -0,0 +1,139 @@
+#if GRAPHQL
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using AnyOfTypes;
+using GraphQL;
+using GraphQL.Types;
+using Newtonsoft.Json;
+using Stef.Validation;
+using WireMock.Models;
+
+namespace WireMock.Matchers;
+
+///
+/// GrapQLMatcher Schema Matcher
+///
+///
+public class GraphQLMatcher : IStringMatcher
+{
+ private sealed class GraphQLRequest
+ {
+ public string? Query { get; set; }
+
+ public Dictionary? Variables { get; set; }
+ }
+
+ private readonly AnyOf[] _patterns;
+
+ private readonly ISchema _schema;
+
+ ///
+ public MatchBehaviour MatchBehaviour { get; }
+
+ ///
+ public bool ThrowException { get; }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The schema.
+ /// The match behaviour.
+ /// Throw an exception when the internal matching fails because of invalid input.
+ /// The to use. (default = "Or")
+ public GraphQLMatcher(AnyOf schema, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch, bool throwException = false, MatchOperator matchOperator = MatchOperator.Or)
+ {
+ Guard.NotNull(schema);
+ MatchBehaviour = matchBehaviour;
+ ThrowException = throwException;
+ MatchOperator = matchOperator;
+
+ var patterns = new List>();
+ switch (schema.CurrentType)
+ {
+ case AnyOfType.First:
+ patterns.Add(schema.First);
+ _schema = BuildSchema(schema);
+ break;
+
+ case AnyOfType.Second:
+ patterns.Add(schema.Second);
+ _schema = BuildSchema(schema.Second.Pattern);
+ break;
+
+ case AnyOfType.Third:
+ _schema = schema.Third;
+ break;
+
+ default:
+ throw new NotSupportedException();
+ }
+ _patterns = patterns.ToArray();
+ }
+
+ ///
+ public double IsMatch(string? input)
+ {
+ var match = MatchScores.Mismatch;
+
+ try
+ {
+ var graphQLRequest = JsonConvert.DeserializeObject(input!)!;
+
+ var executionResult = new DocumentExecuter().ExecuteAsync(_ =>
+ {
+ _.ThrowOnUnhandledException = true;
+
+ _.Schema = _schema;
+ _.Query = graphQLRequest.Query;
+
+ if (graphQLRequest.Variables != null)
+ {
+ _.Variables = new Inputs(graphQLRequest.Variables);
+ }
+ }).GetAwaiter().GetResult();
+
+ if (executionResult.Errors == null || executionResult.Errors.Count == 0)
+ {
+ match = MatchScores.Perfect;
+ }
+ else
+ {
+ var exceptions = executionResult.Errors.OfType().ToArray();
+ if (exceptions.Length == 1)
+ {
+ throw exceptions[0];
+ }
+
+ throw new AggregateException(exceptions);
+ }
+ }
+ catch
+ {
+ if (ThrowException)
+ {
+ throw;
+ }
+ }
+
+ return MatchBehaviourHelper.Convert(MatchBehaviour, match);
+ }
+
+ ///
+ public AnyOf[] GetPatterns()
+ {
+ return _patterns;
+ }
+
+ ///
+ public MatchOperator MatchOperator { get; }
+
+ ///
+ public string Name => nameof(GraphQLMatcher);
+
+ private static ISchema BuildSchema(string schema)
+ {
+ return Schema.For(schema);
+ }
+}
+#endif
\ No newline at end of file
diff --git a/src/WireMock.Net/Matchers/LinqMatcher.cs b/src/WireMock.Net/Matchers/LinqMatcher.cs
index f8069a8d..34577b60 100644
--- a/src/WireMock.Net/Matchers/LinqMatcher.cs
+++ b/src/WireMock.Net/Matchers/LinqMatcher.cs
@@ -69,7 +69,7 @@ public class LinqMatcher : IObjectMatcher, IStringMatcher
MatchOperator = matchOperator;
}
- ///
+ ///
public double IsMatch(string? input)
{
double match = MatchScores.Mismatch;
@@ -95,7 +95,7 @@ public class LinqMatcher : IObjectMatcher, IStringMatcher
return MatchBehaviourHelper.Convert(MatchBehaviour, match);
}
- ///
+ ///
public double IsMatch(object? input)
{
double match = MatchScores.Mismatch;
@@ -110,41 +110,15 @@ public class LinqMatcher : IObjectMatcher, IStringMatcher
jArray = new JArray { JToken.FromObject(input) };
}
- //enumerable = jArray.ToDynamicClassArray();
-
- //JObject value;
- //switch (input)
- //{
- // case JObject valueAsJObject:
- // value = valueAsJObject;
- // break;
-
- // case { } valueAsObject:
- // value = JObject.FromObject(valueAsObject);
- // break;
-
- // default:
- // return MatchScores.Mismatch;
- //}
-
// Convert a single object to a Queryable JObject-list with 1 entry.
- //var queryable1 = new[] { value }.AsQueryable();
var queryable = jArray.ToDynamicClassArray().AsQueryable();
try
{
- // Generate the DynamicLinq select statement.
- //string dynamicSelect = JsonUtils.GenerateDynamicLinqStatement(value);
-
- // Execute DynamicLinq Select statement.
- //var queryable2 = queryable1.Select(dynamicSelect);
-
- // Use the Any(...) method to check if the result matches.
-
var patternsAsStringArray = _patterns.Select(p => p.GetPattern()).ToArray();
var scores = patternsAsStringArray.Select(p => queryable.Any(p)).ToArray();
- match = MatchScores.ToScore(_patterns.Select(pattern => queryable.Any(pattern.GetPattern())).ToArray(), MatchOperator);
+ match = MatchScores.ToScore(scores, MatchOperator);
return MatchBehaviourHelper.Convert(MatchBehaviour, match);
}
@@ -159,7 +133,7 @@ public class LinqMatcher : IObjectMatcher, IStringMatcher
return MatchBehaviourHelper.Convert(MatchBehaviour, match);
}
- ///
+ ///
public AnyOf[] GetPatterns()
{
return _patterns;
@@ -168,6 +142,6 @@ public class LinqMatcher : IObjectMatcher, IStringMatcher
///
public MatchOperator MatchOperator { get; }
- ///
+ ///
public string Name => "LinqMatcher";
}
\ No newline at end of file
diff --git a/src/WireMock.Net/Matchers/Request/RequestMessageGraphQLMatcher.cs b/src/WireMock.Net/Matchers/Request/RequestMessageGraphQLMatcher.cs
new file mode 100644
index 00000000..21719ca9
--- /dev/null
+++ b/src/WireMock.Net/Matchers/Request/RequestMessageGraphQLMatcher.cs
@@ -0,0 +1,104 @@
+using System.Linq;
+using Stef.Validation;
+using WireMock.Types;
+
+namespace WireMock.Matchers.Request;
+
+///
+/// The request body GraphQL matcher.
+///
+public class RequestMessageGraphQLMatcher : IRequestMatcher
+{
+ ///
+ /// The matchers.
+ ///
+ public IMatcher[]? Matchers { get; }
+
+ ///
+ /// The
+ ///
+ public MatchOperator MatchOperator { get; } = MatchOperator.Or;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The match behaviour.
+ /// The schema.
+ public RequestMessageGraphQLMatcher(MatchBehaviour matchBehaviour, string schema) :
+ this(CreateMatcherArray(matchBehaviour, schema))
+ {
+ }
+
+#if GRAPHQL
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The match behaviour.
+ /// The schema.
+ public RequestMessageGraphQLMatcher(MatchBehaviour matchBehaviour, GraphQL.Types.ISchema schema) :
+ this(CreateMatcherArray(matchBehaviour, new AnyOfTypes.AnyOf(schema)))
+ {
+ }
+#endif
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The matchers.
+ public RequestMessageGraphQLMatcher(params IMatcher[] matchers)
+ {
+ Matchers = Guard.NotNull(matchers);
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The matchers.
+ /// The to use.
+ public RequestMessageGraphQLMatcher(MatchOperator matchOperator, params IMatcher[] matchers)
+ {
+ Matchers = Guard.NotNull(matchers);
+ MatchOperator = matchOperator;
+ }
+
+ ///
+ public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
+ {
+ var score = CalculateMatchScore(requestMessage);
+ return requestMatchResult.AddScore(GetType(), score);
+ }
+
+ private static double CalculateMatchScore(IRequestMessage requestMessage, IMatcher matcher)
+ {
+ // Check if the matcher is a IStringMatcher
+ // If the body is a Json or a String, use the BodyAsString to match on.
+ if (matcher is IStringMatcher stringMatcher && requestMessage.BodyData?.DetectedBodyType is BodyType.Json or BodyType.String or BodyType.FormUrlEncoded)
+ {
+ return stringMatcher.IsMatch(requestMessage.BodyData.BodyAsString);
+ }
+
+ return MatchScores.Mismatch;
+ }
+
+ private double CalculateMatchScore(IRequestMessage requestMessage)
+ {
+ if (Matchers == null)
+ {
+ return MatchScores.Mismatch;
+ }
+
+ var matchersResult = Matchers.Select(matcher => CalculateMatchScore(requestMessage, matcher)).ToArray();
+ return MatchScores.ToScore(matchersResult, MatchOperator);
+ }
+
+#if GRAPHQL
+ private static IMatcher[] CreateMatcherArray(MatchBehaviour matchBehaviour, AnyOfTypes.AnyOf schema)
+ {
+ return new[] { new GraphQLMatcher(schema, matchBehaviour) }.Cast().ToArray();
+ }
+#else
+ private static IMatcher[] CreateMatcherArray(MatchBehaviour matchBehaviour, object schema)
+ {
+ throw new System.NotSupportedException("The GrapQLMatcher can not be used for .NETStandard1.3 or .NET Framework 4.6.1 or lower.");
+ }
+#endif
+}
\ No newline at end of file
diff --git a/src/WireMock.Net/RequestBuilders/IBodyRequestBuilder.cs b/src/WireMock.Net/RequestBuilders/IBodyRequestBuilder.cs
index a6459c45..ae91756a 100644
--- a/src/WireMock.Net/RequestBuilders/IBodyRequestBuilder.cs
+++ b/src/WireMock.Net/RequestBuilders/IBodyRequestBuilder.cs
@@ -10,7 +10,7 @@ namespace WireMock.RequestBuilders;
///
/// The BodyRequestBuilder interface.
///
-public interface IBodyRequestBuilder : IRequestMatcher
+public interface IBodyRequestBuilder : IGraphQLRequestBuilder
{
///
/// WithBody: IMatcher
@@ -103,4 +103,12 @@ public interface IBodyRequestBuilder : IRequestMatcher
/// The form-urlencoded values.
/// The .
IRequestBuilder WithBody(Func?, bool> func);
+
+ ///
+ /// WithBodyAsGraphQLSchema: Body as GraphQL schema as a string.
+ ///
+ /// The GraphQL schema.
+ /// The match behaviour. (Default is MatchBehaviour.AcceptOnMatch).
+ /// The .
+ IRequestBuilder WithBodyAsGraphQLSchema(string body, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch);
}
\ No newline at end of file
diff --git a/src/WireMock.Net/RequestBuilders/IGraphQLRequestBuilder.cs b/src/WireMock.Net/RequestBuilders/IGraphQLRequestBuilder.cs
new file mode 100644
index 00000000..a90b78ae
--- /dev/null
+++ b/src/WireMock.Net/RequestBuilders/IGraphQLRequestBuilder.cs
@@ -0,0 +1,28 @@
+using WireMock.Matchers;
+using WireMock.Matchers.Request;
+
+namespace WireMock.RequestBuilders;
+
+///
+/// The GraphQLRequestBuilder interface.
+///
+public interface IGraphQLRequestBuilder : IRequestMatcher
+{
+ ///
+ /// WithGraphQLSchema: The GraphQL schema as a string.
+ ///
+ /// The GraphQL schema.
+ /// The match behaviour. (Default is MatchBehaviour.AcceptOnMatch).
+ /// The .
+ IRequestBuilder WithGraphQLSchema(string schema, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch);
+
+#if GRAPHQL
+ ///
+ /// WithGraphQLSchema: The GraphQL schema as a ISchema.
+ ///
+ /// The GraphQL schema.
+ /// The match behaviour. (Default is MatchBehaviour.AcceptOnMatch).
+ /// The .
+ IRequestBuilder WithGraphQLSchema(GraphQL.Types.ISchema schema, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch);
+#endif
+}
\ No newline at end of file
diff --git a/src/WireMock.Net/RequestBuilders/Request.WithBody.cs b/src/WireMock.Net/RequestBuilders/Request.WithBody.cs
index 08ffa7b3..884e4a1d 100644
--- a/src/WireMock.Net/RequestBuilders/Request.WithBody.cs
+++ b/src/WireMock.Net/RequestBuilders/Request.WithBody.cs
@@ -109,4 +109,10 @@ public partial class Request
_requestMatchers.Add(new RequestMessageBodyMatcher(Guard.NotNull(func)));
return this;
}
+
+ ///
+ public IRequestBuilder WithBodyAsGraphQLSchema(string body, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
+ {
+ return WithGraphQLSchema(body, matchBehaviour);
+ }
}
\ No newline at end of file
diff --git a/src/WireMock.Net/RequestBuilders/Request.WithGraphQL.cs b/src/WireMock.Net/RequestBuilders/Request.WithGraphQL.cs
new file mode 100644
index 00000000..15a912d8
--- /dev/null
+++ b/src/WireMock.Net/RequestBuilders/Request.WithGraphQL.cs
@@ -0,0 +1,23 @@
+using WireMock.Matchers;
+using WireMock.Matchers.Request;
+
+namespace WireMock.RequestBuilders;
+
+public partial class Request
+{
+ ///
+ public IRequestBuilder WithGraphQLSchema(string schema, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
+ {
+ _requestMatchers.Add(new RequestMessageGraphQLMatcher(matchBehaviour, schema));
+ return this;
+ }
+
+#if GRAPHQL
+ ///
+ public IRequestBuilder WithGraphQLSchema(GraphQL.Types.ISchema schema, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
+ {
+ _requestMatchers.Add(new RequestMessageGraphQLMatcher(matchBehaviour, schema));
+ return this;
+ }
+#endif
+}
\ No newline at end of file
diff --git a/src/WireMock.Net/Serialization/MappingConverter.cs b/src/WireMock.Net/Serialization/MappingConverter.cs
index ff40cbd2..95a346ee 100644
--- a/src/WireMock.Net/Serialization/MappingConverter.cs
+++ b/src/WireMock.Net/Serialization/MappingConverter.cs
@@ -46,7 +46,8 @@ internal class MappingConverter
var cookieMatchers = request.GetRequestMessageMatchers();
var paramsMatchers = request.GetRequestMessageMatchers();
var methodMatcher = request.GetRequestMessageMatcher();
- var bodyMatcher = request.GetRequestMessageMatcher();
+ var requestMessageBodyMatcher = request.GetRequestMessageMatcher();
+ var requestMessageGraphQLMatcher = request.GetRequestMessageMatcher();
var sb = new StringBuilder();
@@ -105,13 +106,23 @@ internal class MappingConverter
sb.AppendLine($" .WithCookie(\"{cookieMatcher.Name}\", {ToValueArguments(GetStringArray(cookieMatcher.Matchers!))}, true)");
}
- if (bodyMatcher is { Matchers: { } })
+#if GRAPHQL
+ if (requestMessageGraphQLMatcher is { Matchers: { } })
{
- if (bodyMatcher.Matchers.OfType().FirstOrDefault() is { } wildcardMatcher && wildcardMatcher.GetPatterns().Any())
+ if (requestMessageGraphQLMatcher.Matchers.OfType().FirstOrDefault() is { } graphQLMatcher && graphQLMatcher.GetPatterns().Any())
+ {
+ sb.AppendLine($" .WithGraphQLSchema({GetString(graphQLMatcher)})");
+ }
+ }
+ else
+#endif
+ if (requestMessageBodyMatcher is { Matchers: { } })
+ {
+ if (requestMessageBodyMatcher.Matchers.OfType().FirstOrDefault() is { } wildcardMatcher && wildcardMatcher.GetPatterns().Any())
{
sb.AppendLine($" .WithBody({GetString(wildcardMatcher)})");
}
- else if (bodyMatcher.Matchers.OfType().FirstOrDefault() is { Value: { } } jsonPartialMatcher)
+ else if (requestMessageBodyMatcher.Matchers.OfType().FirstOrDefault() is { Value: { } } jsonPartialMatcher)
{
sb.AppendLine(@$" .WithBody(new JsonPartialMatcher(
value: {ToCSharpStringLiteral(jsonPartialMatcher.Value.ToString())},
@@ -120,7 +131,7 @@ internal class MappingConverter
regex: {ToCSharpBooleanLiteral(jsonPartialMatcher.Regex)}
))");
}
- else if (bodyMatcher.Matchers.OfType().FirstOrDefault() is { Value: { } } jsonPartialWildcardMatcher)
+ else if (requestMessageBodyMatcher.Matchers.OfType().FirstOrDefault() is { Value: { } } jsonPartialWildcardMatcher)
{
sb.AppendLine(@$" .WithBody(new JsonPartialWildcardMatcher(
value: {ToCSharpStringLiteral(jsonPartialWildcardMatcher.Value.ToString())},
@@ -216,6 +227,7 @@ internal class MappingConverter
var paramsMatchers = request.GetRequestMessageMatchers();
var methodMatcher = request.GetRequestMessageMatcher();
var bodyMatcher = request.GetRequestMessageMatcher();
+ var graphQLMatcher = request.GetRequestMessageMatcher();
var mappingModel = new MappingModel
{
@@ -301,7 +313,7 @@ internal class MappingConverter
mappingModel.Response.Delay = (int?)(response.Delay == Timeout.InfiniteTimeSpan ? TimeSpan.MaxValue.TotalMilliseconds : response.Delay?.TotalMilliseconds);
}
- var nonNullableWebHooks = mapping.Webhooks?.Where(wh => wh != null).ToArray() ?? new IWebhook[0];
+ var nonNullableWebHooks = mapping.Webhooks?.Where(wh => wh != null).ToArray() ?? EmptyArray.Value;
if (nonNullableWebHooks.Length == 1)
{
mappingModel.Webhook = WebhookMapper.Map(nonNullableWebHooks[0]);
@@ -311,18 +323,20 @@ internal class MappingConverter
mappingModel.Webhooks = mapping.Webhooks.Select(WebhookMapper.Map).ToArray();
}
- if (bodyMatcher?.Matchers != null)
+ var graphQLOrBodyMatchers = graphQLMatcher?.Matchers ?? bodyMatcher?.Matchers;
+ var matchOperator = graphQLMatcher?.MatchOperator ?? bodyMatcher?.MatchOperator;
+ if (graphQLOrBodyMatchers != null && matchOperator != null)
{
mappingModel.Request.Body = new BodyModel();
- if (bodyMatcher.Matchers.Length == 1)
+ if (graphQLOrBodyMatchers.Length == 1)
{
- mappingModel.Request.Body.Matcher = _mapper.Map(bodyMatcher.Matchers[0]);
+ mappingModel.Request.Body.Matcher = _mapper.Map(graphQLOrBodyMatchers[0]);
}
- else if (bodyMatcher.Matchers.Length > 1)
+ else if (graphQLOrBodyMatchers.Length > 1)
{
- mappingModel.Request.Body.Matchers = _mapper.Map(bodyMatcher.Matchers);
- mappingModel.Request.Body.MatchOperator = bodyMatcher.MatchOperator.ToString();
+ mappingModel.Request.Body.Matchers = _mapper.Map(graphQLOrBodyMatchers);
+ mappingModel.Request.Body.MatchOperator = matchOperator.ToString();
}
}
diff --git a/src/WireMock.Net/Serialization/MatcherMapper.cs b/src/WireMock.Net/Serialization/MatcherMapper.cs
index df18842e..ffd2cb44 100644
--- a/src/WireMock.Net/Serialization/MatcherMapper.cs
+++ b/src/WireMock.Net/Serialization/MatcherMapper.cs
@@ -71,7 +71,10 @@ internal class MatcherMapper
case nameof(ExactObjectMatcher):
return CreateExactObjectMatcher(matchBehaviour, stringPatterns[0], throwExceptionWhenMatcherFails);
-
+#if GRAPHQL
+ case nameof(GraphQLMatcher):
+ return new GraphQLMatcher(stringPatterns[0].GetPattern(), matchBehaviour, throwExceptionWhenMatcherFails, matchOperator);
+#endif
case nameof(RegexMatcher):
return new RegexMatcher(matchBehaviour, stringPatterns, ignoreCase, throwExceptionWhenMatcherFails, useRegexExtended, matchOperator);
diff --git a/src/WireMock.Net/WireMock.Net.csproj b/src/WireMock.Net/WireMock.Net.csproj
index 2759924e..527b00e6 100644
--- a/src/WireMock.Net/WireMock.Net.csproj
+++ b/src/WireMock.Net/WireMock.Net.csproj
@@ -50,16 +50,19 @@
$(DefineConstants);OPENAPIPARSER
+
+ $(DefineConstants);GRAPHQL
+
+
-
-
+
@@ -154,6 +157,11 @@
+
+
+
+
+
all
diff --git a/test/WireMock.Net.Tests/Matchers/GraphQLMatcherTests.cs b/test/WireMock.Net.Tests/Matchers/GraphQLMatcherTests.cs
new file mode 100644
index 00000000..c83b6249
--- /dev/null
+++ b/test/WireMock.Net.Tests/Matchers/GraphQLMatcherTests.cs
@@ -0,0 +1,142 @@
+#if GRAPHQL
+using System;
+using FluentAssertions;
+using GraphQLParser.Exceptions;
+using WireMock.Matchers;
+using WireMock.Models;
+using Xunit;
+
+namespace WireMock.Net.Tests.Matchers;
+
+public class GraphQLMatcherTests
+{
+ private const string TestSchema = @"
+ input MessageInput {
+ content: String
+ author: String
+ }
+
+ type Message {
+ id: ID!
+ content: String
+ author: String
+ }
+
+ type Mutation {
+ createMessage(input: MessageInput): Message
+ updateMessage(id: ID!, input: MessageInput): Message
+ }
+
+ type Query {
+ greeting:String
+ students:[Student]
+ studentById(id:ID!):Student
+ }
+
+ type Student {
+ id:ID!
+ firstName:String
+ lastName:String
+ fullName:String
+ }";
+
+ [Fact]
+ public void GraphQLMatcher_For_ValidSchema_And_CorrectQuery_IsMatch()
+ {
+ // Arrange
+ var input = "{\"query\":\"{\\r\\n students {\\r\\n fullName\\r\\n id\\r\\n }\\r\\n}\"}";
+
+ // Act
+ var matcher = new GraphQLMatcher(TestSchema);
+ var result = matcher.IsMatch(input);
+
+ // Assert
+ result.Should().Be(MatchScores.Perfect);
+
+ matcher.GetPatterns().Should().Contain(TestSchema);
+ }
+
+ [Fact]
+ public void GraphQLMatcher_For_ValidSchema_And_CorrectGraphQLQuery_IsMatch()
+ {
+ // Arrange
+ var input = @"{
+ ""query"": ""query ($sid: ID!)\r\n{\r\n studentById(id: $sid) {\r\n fullName\r\n id\r\n }\r\n}"",
+ ""variables"": {
+ ""sid"": ""1""
+ }
+}";
+ // Act
+ var matcher = new GraphQLMatcher(TestSchema);
+ var result = matcher.IsMatch(input);
+
+ // Assert
+ result.Should().Be(MatchScores.Perfect);
+
+ matcher.GetPatterns().Should().Contain(TestSchema);
+ }
+
+ [Fact]
+ public void GraphQLMatcher_For_ValidSchema_And_IncorrectQuery_IsMismatch()
+ {
+ // Arrange
+ var input = @"
+{
+ students {
+ fullName
+ id
+ abc
+ }
+}";
+ // Act
+ var matcher = new GraphQLMatcher(TestSchema);
+ var result = matcher.IsMatch(input);
+
+ // Assert
+ result.Should().Be(MatchScores.Mismatch);
+ }
+
+ [Fact]
+ public void GraphQLMatcher_For_ValidSchemaAsStringPattern_And_CorrectQuery_IsMatch()
+ {
+ // Arrange
+ var input = "{\"query\":\"{\\r\\n students {\\r\\n fullName\\r\\n id\\r\\n }\\r\\n}\"}";
+ var schema = new StringPattern
+ {
+ Pattern = TestSchema
+ };
+
+ // Act
+ var matcher = new GraphQLMatcher(schema);
+ var result = matcher.IsMatch(input);
+
+ // Assert
+ result.Should().Be(MatchScores.Perfect);
+ }
+
+ [Fact]
+ public void GraphQLMatcher_For_ValidSchema_And_IncorrectQueryWithError_WithThrowExceptionTrue_ThrowsException()
+ {
+ // Arrange
+ var input = "{\"query\":\"{\\r\\n studentsX {\\r\\n fullName\\r\\n X\\r\\n }\\r\\n}\"}";
+
+ // Act
+ var matcher = new GraphQLMatcher(TestSchema, MatchBehaviour.AcceptOnMatch, true);
+ Action action = () => matcher.IsMatch(input);
+
+ // Assert
+ action.Should().Throw();
+ }
+
+ [Fact]
+ public void GraphQLMatcher_For_InvalidSchema_ThrowsGraphQLSyntaxErrorException()
+ {
+ // Act
+ // ReSharper disable once ObjectCreationAsStatement
+ Action action = () => _ = new GraphQLMatcher("in va lid");
+
+ // Assert
+ action.Should().Throw();
+ }
+}
+#endif
\ No newline at end of file
diff --git a/test/WireMock.Net.Tests/RequestBuilders/RequestBuilderWithGraphQLSchemaTests.cs b/test/WireMock.Net.Tests/RequestBuilders/RequestBuilderWithGraphQLSchemaTests.cs
new file mode 100644
index 00000000..81ed5ef6
--- /dev/null
+++ b/test/WireMock.Net.Tests/RequestBuilders/RequestBuilderWithGraphQLSchemaTests.cs
@@ -0,0 +1,71 @@
+#if GRAPHQL
+using System.Collections.Generic;
+using FluentAssertions;
+using GraphQL.Types;
+using WireMock.Matchers;
+using WireMock.Matchers.Request;
+using WireMock.RequestBuilders;
+using Xunit;
+
+namespace WireMock.Net.Tests.RequestBuilders;
+
+public class RequestBuilderWithGraphQLSchemaTests
+{
+ private const string TestSchema = @"
+ input MessageInput {
+ content: String
+ author: String
+ }
+
+ type Message {
+ id: ID!
+ content: String
+ author: String
+ }
+
+ type Mutation {
+ createMessage(input: MessageInput): Message
+ updateMessage(id: ID!, input: MessageInput): Message
+ }
+
+ type Query {
+ greeting:String
+ students:[Student]
+ studentById(id:ID!):Student
+ }
+
+ type Student {
+ id:ID!
+ firstName:String
+ lastName:String
+ fullName:String
+ }";
+
+ [Fact]
+ public void RequestBuilder_WithGraphQLSchema_SchemaAsString()
+ {
+ // Act
+ var requestBuilder = (Request)Request.Create().WithGraphQLSchema(TestSchema);
+
+ // Assert
+ var matchers = requestBuilder.GetPrivateFieldValue>("_requestMatchers");
+ matchers.Should().HaveCount(1);
+ ((RequestMessageGraphQLMatcher)matchers[0]).Matchers.Should().ContainItemsAssignableTo();
+ }
+
+ [Fact]
+ public void RequestBuilder_WithGraphQLSchema_SchemaAsISchema()
+ {
+ // Arrange
+ var schema = Schema.For(TestSchema);
+
+ // Act
+ var requestBuilder = (Request)Request.Create().WithGraphQLSchema(schema);
+
+ // Assert
+ var matchers = requestBuilder.GetPrivateFieldValue>("_requestMatchers");
+ matchers.Should().HaveCount(1);
+ ((RequestMessageGraphQLMatcher)matchers[0]).Matchers.Should().ContainItemsAssignableTo();
+ }
+}
+#endif
\ No newline at end of file
diff --git a/test/WireMock.Net.Tests/RequestMatchers/RequestMessageGraphQLMatcherTests.cs b/test/WireMock.Net.Tests/RequestMatchers/RequestMessageGraphQLMatcherTests.cs
new file mode 100644
index 00000000..dbddf918
--- /dev/null
+++ b/test/WireMock.Net.Tests/RequestMatchers/RequestMessageGraphQLMatcherTests.cs
@@ -0,0 +1,194 @@
+#if GRAPHQL
+using System.Linq;
+using FluentAssertions;
+using Moq;
+using WireMock.Matchers;
+using WireMock.Matchers.Request;
+using WireMock.Models;
+using WireMock.Types;
+using WireMock.Util;
+using Xunit;
+
+namespace WireMock.Net.Tests.RequestMatchers;
+
+public class RequestMessageGraphQLMatcherTests
+{
+ [Fact]
+ public void RequestMessageGraphQLMatcher_GetMatchingScore_BodyAsString_IStringMatcher()
+ {
+ // Assign
+ var body = new BodyData
+ {
+ BodyAsString = "b",
+ DetectedBodyType = BodyType.String
+ };
+ var stringMatcherMock = new Mock();
+ stringMatcherMock.Setup(m => m.IsMatch(It.IsAny())).Returns(1d);
+
+ var requestMessage = new RequestMessage(new UrlDetails("http://localhost"), "GET", "127.0.0.1", body);
+
+ var matcher = new RequestMessageGraphQLMatcher(stringMatcherMock.Object);
+
+ // Act
+ var result = new RequestMatchResult();
+ double score = matcher.GetMatchingScore(requestMessage, result);
+
+ // Assert
+ score.Should().Be(MatchScores.Perfect);
+
+ // Verify
+ stringMatcherMock.Verify(m => m.GetPatterns(), Times.Never);
+ stringMatcherMock.Verify(m => m.IsMatch("b"), Times.Once);
+ }
+
+ [Theory]
+ [InlineData(1d, 1d, 1d)]
+ [InlineData(0d, 1d, 1d)]
+ [InlineData(1d, 0d, 1d)]
+ [InlineData(0d, 0d, 0d)]
+ public void RequestMessageGraphQLMatcher_GetMatchingScore_BodyAsString_IStringMatchers_Or(double one, double two, double expected)
+ {
+ // Assign
+ var body = new BodyData
+ {
+ BodyAsString = "b",
+ DetectedBodyType = BodyType.String
+ };
+ var stringMatcherMock1 = new Mock();
+ stringMatcherMock1.Setup(m => m.IsMatch(It.IsAny())).Returns(one);
+
+ var stringMatcherMock2 = new Mock();
+ stringMatcherMock2.Setup(m => m.IsMatch(It.IsAny())).Returns(two);
+
+ var matchers = new[] { stringMatcherMock1.Object, stringMatcherMock2.Object };
+
+ var requestMessage = new RequestMessage(new UrlDetails("http://localhost"), "GET", "127.0.0.1", body);
+
+ var matcher = new RequestMessageGraphQLMatcher(MatchOperator.Or, matchers.Cast().ToArray());
+
+ // Act
+ var result = new RequestMatchResult();
+ double score = matcher.GetMatchingScore(requestMessage, result);
+
+ // Assert
+ score.Should().Be(expected);
+
+ // Verify
+ stringMatcherMock1.Verify(m => m.GetPatterns(), Times.Never);
+ stringMatcherMock1.Verify(m => m.IsMatch("b"), Times.Once);
+
+ stringMatcherMock2.Verify(m => m.GetPatterns(), Times.Never);
+ stringMatcherMock2.Verify(m => m.IsMatch("b"), Times.Once);
+ stringMatcherMock2.VerifyNoOtherCalls();
+ }
+
+ [Theory]
+ [InlineData(1d, 1d, 1d)]
+ [InlineData(0d, 1d, 0d)]
+ [InlineData(1d, 0d, 0d)]
+ [InlineData(0d, 0d, 0d)]
+ public void RequestMessageGraphQLMatcher_GetMatchingScore_BodyAsString_IStringMatchers_And(double one, double two, double expected)
+ {
+ // Assign
+ var body = new BodyData
+ {
+ BodyAsString = "b",
+ DetectedBodyType = BodyType.String
+ };
+ var stringMatcherMock1 = new Mock();
+ stringMatcherMock1.Setup(m => m.IsMatch(It.IsAny())).Returns(one);
+
+ var stringMatcherMock2 = new Mock();
+ stringMatcherMock2.Setup(m => m.IsMatch(It.IsAny())).Returns(two);
+
+ var matchers = new[] { stringMatcherMock1.Object, stringMatcherMock2.Object };
+
+ var requestMessage = new RequestMessage(new UrlDetails("http://localhost"), "GET", "127.0.0.1", body);
+
+ var matcher = new RequestMessageGraphQLMatcher(MatchOperator.And, matchers.Cast().ToArray());
+
+ // Act
+ var result = new RequestMatchResult();
+ double score = matcher.GetMatchingScore(requestMessage, result);
+
+ // Assert
+ score.Should().Be(expected);
+
+ // Verify
+ stringMatcherMock1.Verify(m => m.GetPatterns(), Times.Never);
+ stringMatcherMock1.Verify(m => m.IsMatch("b"), Times.Once);
+
+ stringMatcherMock2.Verify(m => m.GetPatterns(), Times.Never);
+ stringMatcherMock2.Verify(m => m.IsMatch("b"), Times.Once);
+ stringMatcherMock2.VerifyNoOtherCalls();
+ }
+
+ [Theory]
+ [InlineData(1d, 1d, 1d)]
+ [InlineData(0d, 1d, 0.5d)]
+ [InlineData(1d, 0d, 0.5d)]
+ [InlineData(0d, 0d, 0d)]
+ public void RequestMessageGraphQLMatcher_GetMatchingScore_BodyAsString_IStringMatchers_Average(double one, double two, double expected)
+ {
+ // Assign
+ var body = new BodyData
+ {
+ BodyAsString = "b",
+ DetectedBodyType = BodyType.String
+ };
+ var stringMatcherMock1 = new Mock();
+ stringMatcherMock1.Setup(m => m.IsMatch(It.IsAny())).Returns(one);
+
+ var stringMatcherMock2 = new Mock();
+ stringMatcherMock2.Setup(m => m.IsMatch(It.IsAny())).Returns(two);
+
+ var matchers = new[] { stringMatcherMock1.Object, stringMatcherMock2.Object };
+
+ var requestMessage = new RequestMessage(new UrlDetails("http://localhost"), "GET", "127.0.0.1", body);
+
+ var matcher = new RequestMessageGraphQLMatcher(MatchOperator.Average, matchers.Cast().ToArray());
+
+ // Act
+ var result = new RequestMatchResult();
+ double score = matcher.GetMatchingScore(requestMessage, result);
+
+ // Assert
+ score.Should().Be(expected);
+
+ // Verify
+ stringMatcherMock1.Verify(m => m.GetPatterns(), Times.Never);
+ stringMatcherMock1.Verify(m => m.IsMatch("b"), Times.Once);
+
+ stringMatcherMock2.Verify(m => m.GetPatterns(), Times.Never);
+ stringMatcherMock2.Verify(m => m.IsMatch("b"), Times.Once);
+ }
+
+ [Fact]
+ public void RequestMessageGraphQLMatcher_GetMatchingScore_BodyAsBytes_IStringMatcher_ReturnMisMatch()
+ {
+ // Assign
+ var body = new BodyData
+ {
+ BodyAsBytes = new byte[] { 1 },
+ DetectedBodyType = BodyType.Bytes
+ };
+ var stringMatcherMock = new Mock();
+ stringMatcherMock.Setup(m => m.IsMatch(It.IsAny())).Returns(0.5d);
+
+ var requestMessage = new RequestMessage(new UrlDetails("http://localhost"), "GET", "127.0.0.1", body);
+
+ var matcher = new RequestMessageGraphQLMatcher(stringMatcherMock.Object);
+
+ // Act
+ var result = new RequestMatchResult();
+ double score = matcher.GetMatchingScore(requestMessage, result);
+
+ // Assert
+ score.Should().Be(MatchScores.Mismatch);
+
+ // Verify
+ stringMatcherMock.Verify(m => m.GetPatterns(), Times.Never);
+ stringMatcherMock.Verify(m => m.IsMatch(It.IsAny()), Times.Never);
+ }
+}
+#endif
\ No newline at end of file
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
new file mode 100644
index 00000000..a0c07a93
--- /dev/null
+++ b/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToMappingModel_Request_WithBodyAsGraphQLSchema_ReturnsCorrectModel.verified.txt
@@ -0,0 +1,29 @@
+{
+ Guid: Guid_1,
+ UpdatedAt: DateTime_1,
+ Title: ,
+ Description: ,
+ Priority: 42,
+ Request: {
+ Body: {
+ Matcher: {
+ Name: GraphQLMatcher,
+ Pattern:
+ type Query {
+ greeting:String
+ students:[Student]
+ studentById(id:ID!):Student
+ }
+
+ type Student {
+ id:ID!
+ firstName:String
+ lastName:String
+ fullName:String
+ }
+ }
+ }
+ },
+ Response: {},
+ UseWebhooksFireAndForget: false
+}
\ No newline at end of file
diff --git a/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToMappingModel_Request_WithClientIP_ReturnsCorrectModel.verified.txt b/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToMappingModel_Request_WithClientIP_ReturnsCorrectModel.verified.txt
new file mode 100644
index 00000000..f3b5f431
--- /dev/null
+++ b/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToMappingModel_Request_WithClientIP_ReturnsCorrectModel.verified.txt
@@ -0,0 +1,20 @@
+{
+ Guid: Guid_1,
+ UpdatedAt: DateTime_1,
+ Title: ,
+ Description: ,
+ Priority: 42,
+ Request: {
+ Path: {
+ Matchers: [
+ {
+ Name: WildcardMatcher,
+ Pattern: 1.2.3.4,
+ IgnoreCase: false
+ }
+ ]
+ }
+ },
+ Response: {},
+ UseWebhooksFireAndForget: false
+}
\ No newline at end of file
diff --git a/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToMappingModel_WithDelayAsMilleSeconds_ReturnsCorrectModel.verified.txt b/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToMappingModel_WithDelayAsMilleSeconds_ReturnsCorrectModel.verified.txt
deleted file mode 100644
index 5f282702..00000000
--- a/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToMappingModel_WithDelayAsMilleSeconds_ReturnsCorrectModel.verified.txt
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToMappingModel_WithDelayAsMilliSeconds_ReturnsCorrectModel.verified.txt b/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToMappingModel_WithDelay_ReturnsCorrectModel.verified.txt
similarity index 100%
rename from test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToMappingModel_WithDelayAsMilliSeconds_ReturnsCorrectModel.verified.txt
rename to test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToMappingModel_WithDelay_ReturnsCorrectModel.verified.txt
diff --git a/test/WireMock.Net.Tests/Serialization/MappingConverterTests.cs b/test/WireMock.Net.Tests/Serialization/MappingConverterTests.cs
index 4e0f47a7..3e648f82 100644
--- a/test/WireMock.Net.Tests/Serialization/MappingConverterTests.cs
+++ b/test/WireMock.Net.Tests/Serialization/MappingConverterTests.cs
@@ -260,7 +260,7 @@ public partial class MappingConverterTests
}
[Fact]
- public Task ToMappingModel_WithDelayAsMilliSeconds_ReturnsCorrectModel()
+ public Task ToMappingModel_WithDelay_ReturnsCorrectModel()
{
// Assign
var delay = 1000;
@@ -343,5 +343,56 @@ public partial class MappingConverterTests
// Verify
return Verifier.Verify(model);
}
+
+ [Fact]
+ public Task ToMappingModel_Request_WithClientIP_ReturnsCorrectModel()
+ {
+ // Arrange
+ var request = Request.Create().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, null);
+
+ // Act
+ var model = _sut.ToMappingModel(mapping);
+
+ // Assert
+ model.Should().NotBeNull();
+
+ // Verify
+ return Verifier.Verify(model);
+ }
+
+#if GRAPHQL
+ [Fact]
+ public Task ToMappingModel_Request_WithBodyAsGraphQLSchema_ReturnsCorrectModel()
+ {
+ // Arrange
+ var schema = @"
+ type Query {
+ greeting:String
+ students:[Student]
+ studentById(id:ID!):Student
+ }
+
+ type Student {
+ id:ID!
+ firstName:String
+ lastName:String
+ fullName:String
+ }";
+ var request = Request.Create().WithBodyAsGraphQLSchema(schema);
+ 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, null);
+
+ // Act
+ var model = _sut.ToMappingModel(mapping);
+
+ // Assert
+ model.Should().NotBeNull();
+
+ // Verify
+ return Verifier.Verify(model);
+ }
+#endif
}
#endif
\ No newline at end of file
diff --git a/test/WireMock.Net.Tests/WireMock.Net.Tests.csproj b/test/WireMock.Net.Tests/WireMock.Net.Tests.csproj
index be56db36..833e2419 100644
--- a/test/WireMock.Net.Tests/WireMock.Net.Tests.csproj
+++ b/test/WireMock.Net.Tests/WireMock.Net.Tests.csproj
@@ -24,6 +24,10 @@
NETFRAMEWORK
+
+ $(DefineConstants);GRAPHQL
+
+
@@ -65,7 +69,6 @@
-