mirror of
https://github.com/wiremock/WireMock.Net.git
synced 2026-04-21 16:31:46 +02:00
Add GraphQL Schema matching (#964)
* Add GrapQLMatcher * tests * x * . * . * RequestMessageGraphQLMatcher * . * more tests * tests * ... * ms * . * more tests * GraphQL.NET !!! * . * executionResult * nw * sonarcloud
This commit is contained in:
@@ -14,6 +14,7 @@
|
|||||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=PATCH/@EntryIndexedValue">PATCH</s:String>
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=PATCH/@EntryIndexedValue">PATCH</s:String>
|
||||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=POST/@EntryIndexedValue">POST</s:String>
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=POST/@EntryIndexedValue">POST</s:String>
|
||||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=PUT/@EntryIndexedValue">PUT</s:String>
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=PUT/@EntryIndexedValue">PUT</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=QL/@EntryIndexedValue">QL</s:String>
|
||||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=RSA/@EntryIndexedValue">RSA</s:String>
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=RSA/@EntryIndexedValue">RSA</s:String>
|
||||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=SSL/@EntryIndexedValue">SSL</s:String>
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=SSL/@EntryIndexedValue">SSL</s:String>
|
||||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=TE/@EntryIndexedValue">TE</s:String>
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=TE/@EntryIndexedValue">TE</s:String>
|
||||||
|
|||||||
@@ -27,10 +27,8 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\src\WireMock.Net\WireMock.Net.csproj" />
|
<ProjectReference Include="..\..\src\WireMock.Net\WireMock.Net.csproj" />
|
||||||
<!--<PackageReference Include="Handlebars.Net.Helpers" Version="2.*" />-->
|
|
||||||
<PackageReference Include="log4net" Version="2.0.15" />
|
<PackageReference Include="log4net" Version="2.0.15" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="6.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Configuration" Version="6.0.0" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -42,6 +42,36 @@ namespace WireMock.Net.ConsoleApplication
|
|||||||
|
|
||||||
public static class MainApp
|
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()
|
public static void Run()
|
||||||
{
|
{
|
||||||
var mappingBuilder = new MappingBuilder();
|
var mappingBuilder = new MappingBuilder();
|
||||||
@@ -137,6 +167,16 @@ namespace WireMock.Net.ConsoleApplication
|
|||||||
|
|
||||||
// server.AllowPartialMapping();
|
// server.AllowPartialMapping();
|
||||||
|
|
||||||
|
server
|
||||||
|
.Given(Request.Create()
|
||||||
|
.WithPath("/graphql")
|
||||||
|
.UsingPost()
|
||||||
|
.WithGraphQLSchema(TestSchema)
|
||||||
|
)
|
||||||
|
.RespondWith(Response.Create()
|
||||||
|
.WithBody("GraphQL is ok")
|
||||||
|
);
|
||||||
|
|
||||||
// 400 ms
|
// 400 ms
|
||||||
server
|
server
|
||||||
.Given(Request.Create()
|
.Given(Request.Create()
|
||||||
|
|||||||
139
src/WireMock.Net/Matchers/GraphQLMatcher.cs
Normal file
139
src/WireMock.Net/Matchers/GraphQLMatcher.cs
Normal file
@@ -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;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// GrapQLMatcher Schema Matcher
|
||||||
|
/// </summary>
|
||||||
|
/// <inheritdoc cref="IStringMatcher"/>
|
||||||
|
public class GraphQLMatcher : IStringMatcher
|
||||||
|
{
|
||||||
|
private sealed class GraphQLRequest
|
||||||
|
{
|
||||||
|
public string? Query { get; set; }
|
||||||
|
|
||||||
|
public Dictionary<string, object?>? Variables { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly AnyOf<string, StringPattern>[] _patterns;
|
||||||
|
|
||||||
|
private readonly ISchema _schema;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public MatchBehaviour MatchBehaviour { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool ThrowException { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="LinqMatcher"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="schema">The schema.</param>
|
||||||
|
/// <param name="matchBehaviour">The match behaviour.</param>
|
||||||
|
/// <param name="throwException">Throw an exception when the internal matching fails because of invalid input.</param>
|
||||||
|
/// <param name="matchOperator">The <see cref="Matchers.MatchOperator"/> to use. (default = "Or")</param>
|
||||||
|
public GraphQLMatcher(AnyOf<string, StringPattern, ISchema> 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<AnyOf<string, StringPattern>>();
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public double IsMatch(string? input)
|
||||||
|
{
|
||||||
|
var match = MatchScores.Mismatch;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var graphQLRequest = JsonConvert.DeserializeObject<GraphQLRequest>(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<Exception>().ToArray();
|
||||||
|
if (exceptions.Length == 1)
|
||||||
|
{
|
||||||
|
throw exceptions[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new AggregateException(exceptions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
if (ThrowException)
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return MatchBehaviourHelper.Convert(MatchBehaviour, match);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public AnyOf<string, StringPattern>[] GetPatterns()
|
||||||
|
{
|
||||||
|
return _patterns;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public MatchOperator MatchOperator { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc cref="IMatcher.Name"/>
|
||||||
|
public string Name => nameof(GraphQLMatcher);
|
||||||
|
|
||||||
|
private static ISchema BuildSchema(string schema)
|
||||||
|
{
|
||||||
|
return Schema.For(schema);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -69,7 +69,7 @@ public class LinqMatcher : IObjectMatcher, IStringMatcher
|
|||||||
MatchOperator = matchOperator;
|
MatchOperator = matchOperator;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="IStringMatcher.IsMatch"/>
|
/// <inheritdoc />
|
||||||
public double IsMatch(string? input)
|
public double IsMatch(string? input)
|
||||||
{
|
{
|
||||||
double match = MatchScores.Mismatch;
|
double match = MatchScores.Mismatch;
|
||||||
@@ -95,7 +95,7 @@ public class LinqMatcher : IObjectMatcher, IStringMatcher
|
|||||||
return MatchBehaviourHelper.Convert(MatchBehaviour, match);
|
return MatchBehaviourHelper.Convert(MatchBehaviour, match);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="IObjectMatcher.IsMatch"/>
|
/// <inheritdoc />
|
||||||
public double IsMatch(object? input)
|
public double IsMatch(object? input)
|
||||||
{
|
{
|
||||||
double match = MatchScores.Mismatch;
|
double match = MatchScores.Mismatch;
|
||||||
@@ -110,41 +110,15 @@ public class LinqMatcher : IObjectMatcher, IStringMatcher
|
|||||||
jArray = new JArray { JToken.FromObject(input) };
|
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.
|
// Convert a single object to a Queryable JObject-list with 1 entry.
|
||||||
//var queryable1 = new[] { value }.AsQueryable();
|
|
||||||
var queryable = jArray.ToDynamicClassArray().AsQueryable();
|
var queryable = jArray.ToDynamicClassArray().AsQueryable();
|
||||||
|
|
||||||
try
|
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 patternsAsStringArray = _patterns.Select(p => p.GetPattern()).ToArray();
|
||||||
var scores = patternsAsStringArray.Select(p => queryable.Any(p)).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);
|
return MatchBehaviourHelper.Convert(MatchBehaviour, match);
|
||||||
}
|
}
|
||||||
@@ -159,7 +133,7 @@ public class LinqMatcher : IObjectMatcher, IStringMatcher
|
|||||||
return MatchBehaviourHelper.Convert(MatchBehaviour, match);
|
return MatchBehaviourHelper.Convert(MatchBehaviour, match);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="IStringMatcher.GetPatterns"/>
|
/// <inheritdoc />
|
||||||
public AnyOf<string, StringPattern>[] GetPatterns()
|
public AnyOf<string, StringPattern>[] GetPatterns()
|
||||||
{
|
{
|
||||||
return _patterns;
|
return _patterns;
|
||||||
@@ -168,6 +142,6 @@ public class LinqMatcher : IObjectMatcher, IStringMatcher
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public MatchOperator MatchOperator { get; }
|
public MatchOperator MatchOperator { get; }
|
||||||
|
|
||||||
/// <inheritdoc cref="IMatcher.Name"/>
|
/// <inheritdoc />
|
||||||
public string Name => "LinqMatcher";
|
public string Name => "LinqMatcher";
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using Stef.Validation;
|
||||||
|
using WireMock.Types;
|
||||||
|
|
||||||
|
namespace WireMock.Matchers.Request;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The request body GraphQL matcher.
|
||||||
|
/// </summary>
|
||||||
|
public class RequestMessageGraphQLMatcher : IRequestMatcher
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The matchers.
|
||||||
|
/// </summary>
|
||||||
|
public IMatcher[]? Matchers { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The <see cref="MatchOperator"/>
|
||||||
|
/// </summary>
|
||||||
|
public MatchOperator MatchOperator { get; } = MatchOperator.Or;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="RequestMessageGraphQLMatcher"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="matchBehaviour">The match behaviour.</param>
|
||||||
|
/// <param name="schema">The schema.</param>
|
||||||
|
public RequestMessageGraphQLMatcher(MatchBehaviour matchBehaviour, string schema) :
|
||||||
|
this(CreateMatcherArray(matchBehaviour, schema))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
#if GRAPHQL
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="RequestMessageGraphQLMatcher"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="matchBehaviour">The match behaviour.</param>
|
||||||
|
/// <param name="schema">The schema.</param>
|
||||||
|
public RequestMessageGraphQLMatcher(MatchBehaviour matchBehaviour, GraphQL.Types.ISchema schema) :
|
||||||
|
this(CreateMatcherArray(matchBehaviour, new AnyOfTypes.AnyOf<string, Models.StringPattern, GraphQL.Types.ISchema>(schema)))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="RequestMessageGraphQLMatcher"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="matchers">The matchers.</param>
|
||||||
|
public RequestMessageGraphQLMatcher(params IMatcher[] matchers)
|
||||||
|
{
|
||||||
|
Matchers = Guard.NotNull(matchers);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="RequestMessageGraphQLMatcher"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="matchers">The matchers.</param>
|
||||||
|
/// <param name="matchOperator">The <see cref="MatchOperator"/> to use.</param>
|
||||||
|
public RequestMessageGraphQLMatcher(MatchOperator matchOperator, params IMatcher[] matchers)
|
||||||
|
{
|
||||||
|
Matchers = Guard.NotNull(matchers);
|
||||||
|
MatchOperator = matchOperator;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
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<string, Models.StringPattern, GraphQL.Types.ISchema> schema)
|
||||||
|
{
|
||||||
|
return new[] { new GraphQLMatcher(schema, matchBehaviour) }.Cast<IMatcher>().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
|
||||||
|
}
|
||||||
@@ -10,7 +10,7 @@ namespace WireMock.RequestBuilders;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The BodyRequestBuilder interface.
|
/// The BodyRequestBuilder interface.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IBodyRequestBuilder : IRequestMatcher
|
public interface IBodyRequestBuilder : IGraphQLRequestBuilder
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// WithBody: IMatcher
|
/// WithBody: IMatcher
|
||||||
@@ -103,4 +103,12 @@ public interface IBodyRequestBuilder : IRequestMatcher
|
|||||||
/// <param name="func">The form-urlencoded values.</param>
|
/// <param name="func">The form-urlencoded values.</param>
|
||||||
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
|
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
|
||||||
IRequestBuilder WithBody(Func<IDictionary<string, string>?, bool> func);
|
IRequestBuilder WithBody(Func<IDictionary<string, string>?, bool> func);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// WithBodyAsGraphQLSchema: Body as GraphQL schema as a string.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="body">The GraphQL schema.</param>
|
||||||
|
/// <param name="matchBehaviour">The match behaviour. (Default is <c>MatchBehaviour.AcceptOnMatch</c>).</param>
|
||||||
|
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
|
||||||
|
IRequestBuilder WithBodyAsGraphQLSchema(string body, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch);
|
||||||
}
|
}
|
||||||
28
src/WireMock.Net/RequestBuilders/IGraphQLRequestBuilder.cs
Normal file
28
src/WireMock.Net/RequestBuilders/IGraphQLRequestBuilder.cs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
using WireMock.Matchers;
|
||||||
|
using WireMock.Matchers.Request;
|
||||||
|
|
||||||
|
namespace WireMock.RequestBuilders;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The GraphQLRequestBuilder interface.
|
||||||
|
/// </summary>
|
||||||
|
public interface IGraphQLRequestBuilder : IRequestMatcher
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// WithGraphQLSchema: The GraphQL schema as a string.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="schema">The GraphQL schema.</param>
|
||||||
|
/// <param name="matchBehaviour">The match behaviour. (Default is <c>MatchBehaviour.AcceptOnMatch</c>).</param>
|
||||||
|
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
|
||||||
|
IRequestBuilder WithGraphQLSchema(string schema, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch);
|
||||||
|
|
||||||
|
#if GRAPHQL
|
||||||
|
/// <summary>
|
||||||
|
/// WithGraphQLSchema: The GraphQL schema as a ISchema.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="schema">The GraphQL schema.</param>
|
||||||
|
/// <param name="matchBehaviour">The match behaviour. (Default is <c>MatchBehaviour.AcceptOnMatch</c>).</param>
|
||||||
|
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
|
||||||
|
IRequestBuilder WithGraphQLSchema(GraphQL.Types.ISchema schema, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
@@ -109,4 +109,10 @@ public partial class Request
|
|||||||
_requestMatchers.Add(new RequestMessageBodyMatcher(Guard.NotNull(func)));
|
_requestMatchers.Add(new RequestMessageBodyMatcher(Guard.NotNull(func)));
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IRequestBuilder WithBodyAsGraphQLSchema(string body, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
|
||||||
|
{
|
||||||
|
return WithGraphQLSchema(body, matchBehaviour);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
23
src/WireMock.Net/RequestBuilders/Request.WithGraphQL.cs
Normal file
23
src/WireMock.Net/RequestBuilders/Request.WithGraphQL.cs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
using WireMock.Matchers;
|
||||||
|
using WireMock.Matchers.Request;
|
||||||
|
|
||||||
|
namespace WireMock.RequestBuilders;
|
||||||
|
|
||||||
|
public partial class Request
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IRequestBuilder WithGraphQLSchema(string schema, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
|
||||||
|
{
|
||||||
|
_requestMatchers.Add(new RequestMessageGraphQLMatcher(matchBehaviour, schema));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if GRAPHQL
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IRequestBuilder WithGraphQLSchema(GraphQL.Types.ISchema schema, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
|
||||||
|
{
|
||||||
|
_requestMatchers.Add(new RequestMessageGraphQLMatcher(matchBehaviour, schema));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
@@ -46,7 +46,8 @@ internal class MappingConverter
|
|||||||
var cookieMatchers = request.GetRequestMessageMatchers<RequestMessageCookieMatcher>();
|
var cookieMatchers = request.GetRequestMessageMatchers<RequestMessageCookieMatcher>();
|
||||||
var paramsMatchers = request.GetRequestMessageMatchers<RequestMessageParamMatcher>();
|
var paramsMatchers = request.GetRequestMessageMatchers<RequestMessageParamMatcher>();
|
||||||
var methodMatcher = request.GetRequestMessageMatcher<RequestMessageMethodMatcher>();
|
var methodMatcher = request.GetRequestMessageMatcher<RequestMessageMethodMatcher>();
|
||||||
var bodyMatcher = request.GetRequestMessageMatcher<RequestMessageBodyMatcher>();
|
var requestMessageBodyMatcher = request.GetRequestMessageMatcher<RequestMessageBodyMatcher>();
|
||||||
|
var requestMessageGraphQLMatcher = request.GetRequestMessageMatcher<RequestMessageGraphQLMatcher>();
|
||||||
|
|
||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
|
|
||||||
@@ -105,13 +106,23 @@ internal class MappingConverter
|
|||||||
sb.AppendLine($" .WithCookie(\"{cookieMatcher.Name}\", {ToValueArguments(GetStringArray(cookieMatcher.Matchers!))}, true)");
|
sb.AppendLine($" .WithCookie(\"{cookieMatcher.Name}\", {ToValueArguments(GetStringArray(cookieMatcher.Matchers!))}, true)");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bodyMatcher is { Matchers: { } })
|
#if GRAPHQL
|
||||||
|
if (requestMessageGraphQLMatcher is { Matchers: { } })
|
||||||
{
|
{
|
||||||
if (bodyMatcher.Matchers.OfType<WildcardMatcher>().FirstOrDefault() is { } wildcardMatcher && wildcardMatcher.GetPatterns().Any())
|
if (requestMessageGraphQLMatcher.Matchers.OfType<GraphQLMatcher>().FirstOrDefault() is { } graphQLMatcher && graphQLMatcher.GetPatterns().Any())
|
||||||
|
{
|
||||||
|
sb.AppendLine($" .WithGraphQLSchema({GetString(graphQLMatcher)})");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
#endif
|
||||||
|
if (requestMessageBodyMatcher is { Matchers: { } })
|
||||||
|
{
|
||||||
|
if (requestMessageBodyMatcher.Matchers.OfType<WildcardMatcher>().FirstOrDefault() is { } wildcardMatcher && wildcardMatcher.GetPatterns().Any())
|
||||||
{
|
{
|
||||||
sb.AppendLine($" .WithBody({GetString(wildcardMatcher)})");
|
sb.AppendLine($" .WithBody({GetString(wildcardMatcher)})");
|
||||||
}
|
}
|
||||||
else if (bodyMatcher.Matchers.OfType<JsonPartialMatcher>().FirstOrDefault() is { Value: { } } jsonPartialMatcher)
|
else if (requestMessageBodyMatcher.Matchers.OfType<JsonPartialMatcher>().FirstOrDefault() is { Value: { } } jsonPartialMatcher)
|
||||||
{
|
{
|
||||||
sb.AppendLine(@$" .WithBody(new JsonPartialMatcher(
|
sb.AppendLine(@$" .WithBody(new JsonPartialMatcher(
|
||||||
value: {ToCSharpStringLiteral(jsonPartialMatcher.Value.ToString())},
|
value: {ToCSharpStringLiteral(jsonPartialMatcher.Value.ToString())},
|
||||||
@@ -120,7 +131,7 @@ internal class MappingConverter
|
|||||||
regex: {ToCSharpBooleanLiteral(jsonPartialMatcher.Regex)}
|
regex: {ToCSharpBooleanLiteral(jsonPartialMatcher.Regex)}
|
||||||
))");
|
))");
|
||||||
}
|
}
|
||||||
else if (bodyMatcher.Matchers.OfType<JsonPartialWildcardMatcher>().FirstOrDefault() is { Value: { } } jsonPartialWildcardMatcher)
|
else if (requestMessageBodyMatcher.Matchers.OfType<JsonPartialWildcardMatcher>().FirstOrDefault() is { Value: { } } jsonPartialWildcardMatcher)
|
||||||
{
|
{
|
||||||
sb.AppendLine(@$" .WithBody(new JsonPartialWildcardMatcher(
|
sb.AppendLine(@$" .WithBody(new JsonPartialWildcardMatcher(
|
||||||
value: {ToCSharpStringLiteral(jsonPartialWildcardMatcher.Value.ToString())},
|
value: {ToCSharpStringLiteral(jsonPartialWildcardMatcher.Value.ToString())},
|
||||||
@@ -216,6 +227,7 @@ internal class MappingConverter
|
|||||||
var paramsMatchers = request.GetRequestMessageMatchers<RequestMessageParamMatcher>();
|
var paramsMatchers = request.GetRequestMessageMatchers<RequestMessageParamMatcher>();
|
||||||
var methodMatcher = request.GetRequestMessageMatcher<RequestMessageMethodMatcher>();
|
var methodMatcher = request.GetRequestMessageMatcher<RequestMessageMethodMatcher>();
|
||||||
var bodyMatcher = request.GetRequestMessageMatcher<RequestMessageBodyMatcher>();
|
var bodyMatcher = request.GetRequestMessageMatcher<RequestMessageBodyMatcher>();
|
||||||
|
var graphQLMatcher = request.GetRequestMessageMatcher<RequestMessageGraphQLMatcher>();
|
||||||
|
|
||||||
var mappingModel = new MappingModel
|
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);
|
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<IWebhook>.Value;
|
||||||
if (nonNullableWebHooks.Length == 1)
|
if (nonNullableWebHooks.Length == 1)
|
||||||
{
|
{
|
||||||
mappingModel.Webhook = WebhookMapper.Map(nonNullableWebHooks[0]);
|
mappingModel.Webhook = WebhookMapper.Map(nonNullableWebHooks[0]);
|
||||||
@@ -311,18 +323,20 @@ internal class MappingConverter
|
|||||||
mappingModel.Webhooks = mapping.Webhooks.Select(WebhookMapper.Map).ToArray();
|
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();
|
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.Matchers = _mapper.Map(graphQLOrBodyMatchers);
|
||||||
mappingModel.Request.Body.MatchOperator = bodyMatcher.MatchOperator.ToString();
|
mappingModel.Request.Body.MatchOperator = matchOperator.ToString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -71,7 +71,10 @@ internal class MatcherMapper
|
|||||||
|
|
||||||
case nameof(ExactObjectMatcher):
|
case nameof(ExactObjectMatcher):
|
||||||
return CreateExactObjectMatcher(matchBehaviour, stringPatterns[0], throwExceptionWhenMatcherFails);
|
return CreateExactObjectMatcher(matchBehaviour, stringPatterns[0], throwExceptionWhenMatcherFails);
|
||||||
|
#if GRAPHQL
|
||||||
|
case nameof(GraphQLMatcher):
|
||||||
|
return new GraphQLMatcher(stringPatterns[0].GetPattern(), matchBehaviour, throwExceptionWhenMatcherFails, matchOperator);
|
||||||
|
#endif
|
||||||
case nameof(RegexMatcher):
|
case nameof(RegexMatcher):
|
||||||
return new RegexMatcher(matchBehaviour, stringPatterns, ignoreCase, throwExceptionWhenMatcherFails, useRegexExtended, matchOperator);
|
return new RegexMatcher(matchBehaviour, stringPatterns, ignoreCase, throwExceptionWhenMatcherFails, useRegexExtended, matchOperator);
|
||||||
|
|
||||||
|
|||||||
@@ -50,16 +50,19 @@
|
|||||||
<DefineConstants>$(DefineConstants);OPENAPIPARSER</DefineConstants>
|
<DefineConstants>$(DefineConstants);OPENAPIPARSER</DefineConstants>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(TargetFramework)' != 'netstandard1.3' and '$(TargetFramework)' != 'net451' and '$(TargetFramework)' != 'net452' and '$(TargetFramework)' != 'net46' and '$(TargetFramework)' != 'net461'">
|
||||||
|
<DefineConstants>$(DefineConstants);GRAPHQL</DefineConstants>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Remove="Util\FileSystemWatcherExtensions.cs" />
|
<Compile Remove="Util\FileSystemWatcherExtensions.cs" />
|
||||||
<Compile Remove="Matchers\Request\IRequestMatcher.cs" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="JetBrains.Annotations" Version="2022.3.1" PrivateAssets="All" />
|
<PackageReference Include="JetBrains.Annotations" Version="2022.3.1" PrivateAssets="All" />
|
||||||
<PackageReference Include="JsonConverter.Abstractions" Version="0.4.0" />
|
<PackageReference Include="JsonConverter.Abstractions" Version="0.4.0" />
|
||||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
|
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
<PackageReference Include="NJsonSchema.Extensions" Version="0.1.0" />
|
<PackageReference Include="NJsonSchema.Extensions" Version="0.1.0" />
|
||||||
<PackageReference Include="NSwag.Core" Version="13.16.1" />
|
<PackageReference Include="NSwag.Core" Version="13.16.1" />
|
||||||
<PackageReference Include="SimMetrics.Net" Version="1.0.5" />
|
<PackageReference Include="SimMetrics.Net" Version="1.0.5" />
|
||||||
@@ -154,6 +157,11 @@
|
|||||||
<PackageReference Include="Scriban.Signed" Version="5.5.0" />
|
<PackageReference Include="Scriban.Signed" Version="5.5.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup Condition="'$(TargetFramework)' != 'netstandard1.3' and '$(TargetFramework)' != 'net451' and '$(TargetFramework)' != 'net452' and '$(TargetFramework)' != 'net46' and '$(TargetFramework)' != 'net461'">
|
||||||
|
<PackageReference Include="GraphQL" Version="7.5.0" />
|
||||||
|
<PackageReference Include="GraphQL.NewtonsoftJson" Version="7.5.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup Condition=" '$(TargetFramework)' == 'netcoreapp3.1' ">
|
<ItemGroup Condition=" '$(TargetFramework)' == 'netcoreapp3.1' ">
|
||||||
<PackageReference Include="Nullable" Version="1.3.1">
|
<PackageReference Include="Nullable" Version="1.3.1">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
|||||||
142
test/WireMock.Net.Tests/Matchers/GraphQLMatcherTests.cs
Normal file
142
test/WireMock.Net.Tests/Matchers/GraphQLMatcherTests.cs
Normal file
@@ -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<Exception>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GraphQLMatcher_For_InvalidSchema_ThrowsGraphQLSyntaxErrorException()
|
||||||
|
{
|
||||||
|
// Act
|
||||||
|
// ReSharper disable once ObjectCreationAsStatement
|
||||||
|
Action action = () => _ = new GraphQLMatcher("in va lid");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
action.Should().Throw<GraphQLSyntaxErrorException>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -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<IList<IRequestMatcher>>("_requestMatchers");
|
||||||
|
matchers.Should().HaveCount(1);
|
||||||
|
((RequestMessageGraphQLMatcher)matchers[0]).Matchers.Should().ContainItemsAssignableTo<GraphQLMatcher>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[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<IList<IRequestMatcher>>("_requestMatchers");
|
||||||
|
matchers.Should().HaveCount(1);
|
||||||
|
((RequestMessageGraphQLMatcher)matchers[0]).Matchers.Should().ContainItemsAssignableTo<GraphQLMatcher>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -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<IStringMatcher>();
|
||||||
|
stringMatcherMock.Setup(m => m.IsMatch(It.IsAny<string>())).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<IStringMatcher>();
|
||||||
|
stringMatcherMock1.Setup(m => m.IsMatch(It.IsAny<string>())).Returns(one);
|
||||||
|
|
||||||
|
var stringMatcherMock2 = new Mock<IStringMatcher>();
|
||||||
|
stringMatcherMock2.Setup(m => m.IsMatch(It.IsAny<string>())).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<IMatcher>().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<IStringMatcher>();
|
||||||
|
stringMatcherMock1.Setup(m => m.IsMatch(It.IsAny<string>())).Returns(one);
|
||||||
|
|
||||||
|
var stringMatcherMock2 = new Mock<IStringMatcher>();
|
||||||
|
stringMatcherMock2.Setup(m => m.IsMatch(It.IsAny<string>())).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<IMatcher>().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<IStringMatcher>();
|
||||||
|
stringMatcherMock1.Setup(m => m.IsMatch(It.IsAny<string>())).Returns(one);
|
||||||
|
|
||||||
|
var stringMatcherMock2 = new Mock<IStringMatcher>();
|
||||||
|
stringMatcherMock2.Setup(m => m.IsMatch(It.IsAny<string>())).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<IMatcher>().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<IStringMatcher>();
|
||||||
|
stringMatcherMock.Setup(m => m.IsMatch(It.IsAny<string>())).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<string>()), Times.Never);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -1 +0,0 @@
|
|||||||
|
|
||||||
@@ -260,7 +260,7 @@ public partial class MappingConverterTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public Task ToMappingModel_WithDelayAsMilliSeconds_ReturnsCorrectModel()
|
public Task ToMappingModel_WithDelay_ReturnsCorrectModel()
|
||||||
{
|
{
|
||||||
// Assign
|
// Assign
|
||||||
var delay = 1000;
|
var delay = 1000;
|
||||||
@@ -343,5 +343,56 @@ public partial class MappingConverterTests
|
|||||||
// Verify
|
// Verify
|
||||||
return Verifier.Verify(model);
|
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
|
#endif
|
||||||
@@ -24,6 +24,10 @@
|
|||||||
<DefineConstants>NETFRAMEWORK</DefineConstants>
|
<DefineConstants>NETFRAMEWORK</DefineConstants>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(TargetFramework)' != 'netstandard1.3' and '$(TargetFramework)' != 'net451' and '$(TargetFramework)' != 'net452' and '$(TargetFramework)' != 'net46' and '$(TargetFramework)' != 'net461'">
|
||||||
|
<DefineConstants>$(DefineConstants);GRAPHQL</DefineConstants>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Remove="Util\JsonUtilsTests.cs" />
|
<Compile Remove="Util\JsonUtilsTests.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
@@ -65,7 +69,6 @@
|
|||||||
<PackageReference Include="Moq" Version="4.17.2" />
|
<PackageReference Include="Moq" Version="4.17.2" />
|
||||||
<PackageReference Include="System.Threading" Version="4.3.0" />
|
<PackageReference Include="System.Threading" Version="4.3.0" />
|
||||||
<PackageReference Include="RestEase" Version="1.5.7" />
|
<PackageReference Include="RestEase" Version="1.5.7" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
|
|
||||||
<PackageReference Include="NFluent" Version="2.8.0" />
|
<PackageReference Include="NFluent" Version="2.8.0" />
|
||||||
<PackageReference Include="SimMetrics.Net" Version="1.0.5" />
|
<PackageReference Include="SimMetrics.Net" Version="1.0.5" />
|
||||||
<PackageReference Include="AnyOf" Version="0.3.0" />
|
<PackageReference Include="AnyOf" Version="0.3.0" />
|
||||||
|
|||||||
Reference in New Issue
Block a user