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:
Stef Heyenrath
2023-07-07 21:43:46 +02:00
committed by GitHub
parent 9443e4f071
commit b495eb83b1
22 changed files with 907 additions and 52 deletions

View 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

View File

@@ -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

View File

@@ -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

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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

View File

@@ -24,6 +24,10 @@
<DefineConstants>NETFRAMEWORK</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' != 'netstandard1.3' and '$(TargetFramework)' != 'net451' and '$(TargetFramework)' != 'net452' and '$(TargetFramework)' != 'net46' and '$(TargetFramework)' != 'net461'">
<DefineConstants>$(DefineConstants);GRAPHQL</DefineConstants>
</PropertyGroup>
<ItemGroup>
<Compile Remove="Util\JsonUtilsTests.cs" />
</ItemGroup>
@@ -65,7 +69,6 @@
<PackageReference Include="Moq" Version="4.17.2" />
<PackageReference Include="System.Threading" Version="4.3.0" />
<PackageReference Include="RestEase" Version="1.5.7" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
<PackageReference Include="NFluent" Version="2.8.0" />
<PackageReference Include="SimMetrics.Net" Version="1.0.5" />
<PackageReference Include="AnyOf" Version="0.3.0" />