Compare commits

...

14 Commits

Author SHA1 Message Date
Stef Heyenrath
60eb519ae2 1.9.1 2025-08-17 10:11:48 +02:00
Stef Heyenrath
22ed94918a Fix generating source code for Scenario and State (#1347)
* Fix generating source code for Scenario and State

* Update src/WireMock.Net.Minimal/Serialization/MappingConverter.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/WireMock.Net.Minimal/Serialization/MappingConverter.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* .

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-17 10:06:39 +02:00
Stef Heyenrath
faffc56484 Add TimesInSameState to MappingModel (#1345)
* Add TimesInSameState to MappingModel

* fix tests
2025-08-11 08:46:18 +02:00
Stef Heyenrath
a5558777e2 1.9.0 2025-08-10 19:07:24 +02:00
Stef Heyenrath
6722ca40ba Update feature_request.md 2025-08-10 19:02:24 +02:00
Stef Heyenrath
0597a73e0e Create GraphQL project (#1334)
* Create new project for GraphQL

* ...

* .

* ok?

* Update src/WireMock.Net.Shared/Extensions/AnyOfExtensions.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* --

* ...

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-10 19:00:22 +02:00
Stef Heyenrath
0d510cdde8 1.8.18 2025-08-04 18:03:44 +02:00
Stef Heyenrath
52a396beef 1.8.18 2025-08-04 18:03:23 +02:00
Sam Fields
6ccfe68686 Fixes an issue with matching JSON bodies as bytes (#1339)
* Fixes an issue with matching JSON bodies as bytes

* Adding tests for exact object matching

* Simplify the check for byte data
2025-08-02 20:11:13 +02:00
Stef Heyenrath
e400e92452 1.8.17 2025-07-23 09:40:55 +02:00
Stef Heyenrath
7a187dfb78 Add more examples for WithGraphQLSchema (#1335) 2025-07-23 09:34:31 +02:00
Stef Heyenrath
e6ff8776fb Make CSharpCodeMatcher public (#1337) 2025-07-23 09:29:53 +02:00
Stef Heyenrath
c32e904f4d 1.8.16 2025-07-19 08:16:34 +02:00
Stef Heyenrath
e80d436dd6 Use corerct Handlebars.Net.Helpers.Xslt (2.5.2) (#1332) 2025-07-18 10:57:58 +02:00
60 changed files with 614 additions and 304 deletions

View File

@@ -8,7 +8,7 @@ assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
A clear and concise description of what the problem is.
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
@@ -17,7 +17,7 @@ A clear and concise description of what you want to happen.
A clear and concise description of any alternative solutions or features you've considered.
**Is your feature request supported by [WireMock (java version)](https://www.wiremock.org)? Please provide details.**
Provide relevant information if requested feature is supported in [Handlebarsjs](https://handlebarsjs.com/) but is missing in our implementation.
Provide relevant information if requested feature is supported but is missing in this implementation.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@@ -1,3 +1,24 @@
# 1.9.1 (17 August 2025)
- [#1345](https://github.com/wiremock/WireMock.Net/pull/1345) - Add TimesInSameState to MappingModel [feature] contributed by [StefH](https://github.com/StefH)
- [#1347](https://github.com/wiremock/WireMock.Net/pull/1347) - Fix generating source code for Scenario and State [bug] contributed by [StefH](https://github.com/StefH)
- [#1343](https://github.com/wiremock/WireMock.Net/issues/1343) - MappingModel allows to configure the times for WillSetStateTo [feature]
- [#1346](https://github.com/wiremock/WireMock.Net/issues/1346) - Mapping: generated C# code is missing InScenario and WillSetStateTo [bug]
# 1.9.0 (10 August 2025)
- [#1334](https://github.com/wiremock/WireMock.Net/pull/1334) - Create GraphQL project [feature] contributed by [StefH](https://github.com/StefH)
# 1.8.18 (04 August 2025)
- [#1339](https://github.com/wiremock/WireMock.Net/pull/1339) - Fixes an issue with matching JSON bodies as bytes [bug] contributed by [smfields](https://github.com/smfields)
- [#1338](https://github.com/wiremock/WireMock.Net/issues/1338) - Specifying .WithBody(byte[]) fails to match for JSON bodies [bug]
# 1.8.17 (23 July 2025)
- [#1337](https://github.com/wiremock/WireMock.Net/pull/1337) - Make CSharpCodeMatcher public [bug] contributed by [StefH](https://github.com/StefH)
- [#1336](https://github.com/wiremock/WireMock.Net/issues/1336) - Is CSharpCodeMatcher actually usable? [bug]
# 1.8.16 (19 July 2025)
- [#1332](https://github.com/wiremock/WireMock.Net/pull/1332) - Use correct Handlebars.Net.Helpers.Xslt (2.5.2) [bug] contributed by [StefH](https://github.com/StefH)
- [#1328](https://github.com/wiremock/WireMock.Net/issues/1328) - ReflectionTypeLoadException occurs when running multiple unit tests (Nunit 3) [bug]
# 1.8.15 (18 July 2025)
- [#1331](https://github.com/wiremock/WireMock.Net/pull/1331) - Correctly map the Pact Interaction Description property [bug] contributed by [StefH](https://github.com/StefH)
- [#1330](https://github.com/wiremock/WireMock.Net/issues/1330) - Generated Pact-compatible consumer contract does not contain description [bug]

View File

@@ -4,7 +4,7 @@
</PropertyGroup>
<PropertyGroup>
<VersionPrefix>1.8.15</VersionPrefix>
<VersionPrefix>1.9.1</VersionPrefix>
<PackageIcon>WireMock.Net-Logo.png</PackageIcon>
<PackageProjectUrl>https://github.com/wiremock/WireMock.Net</PackageProjectUrl>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>

View File

@@ -1,6 +1,6 @@
rem https://github.com/StefH/GitHubReleaseNotes
SET version=1.8.15
SET version=1.9.1
GitHubReleaseNotes --output CHANGELOG.md --skip-empty-releases --exclude-labels wontfix test question invalid doc duplicate example environment --version %version% --token %GH_TOKEN%

View File

@@ -1,5 +1,7 @@
# 1.8.15 (18 July 2025)
- #1331 Correctly map the Pact Interaction Description property [bug]
- #1330 Generated Pact-compatible consumer contract does not contain description [bug]
# 1.9.1 (17 August 2025)
- #1345 Add TimesInSameState to MappingModel [feature]
- #1347 Fix generating source code for Scenario and State [bug]
- #1343 MappingModel allows to configure the times for WillSetStateTo [feature]
- #1346 Mapping: generated C# code is missing InScenario and WillSetStateTo [bug]
The full release notes can be found here: https://github.com/wiremock/WireMock.Net/blob/master/CHANGELOG.md

View File

@@ -54,13 +54,14 @@ For more info, see also this WIKI page: [What is WireMock.Net](https://github.co
| &nbsp;&nbsp;**WireMock.Net.Matchers.CSharpCode** | [![NuGet Badge WireMock.Net.Matchers.CSharpCode](https://img.shields.io/nuget/v/WireMock.Net.Matchers.CSharpCode)](https://www.nuget.org/packages/WireMock.Net.Matchers.CSharpCode) | [![MyGet Badge WireMock.Net.Matchers.CSharpCode](https://img.shields.io/myget/wiremock-net/vpre/WireMock.Net.Matchers.CSharpCode?includePreReleases=true&label=MyGet)](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.Matchers.CSharpCode)
| &nbsp;&nbsp;**WireMock.Net.OpenApiParser** | [![NuGet Badge WireMock.Net.OpenApiParser](https://img.shields.io/nuget/v/WireMock.Net.OpenApiParser)](https://www.nuget.org/packages/WireMock.Net.OpenApiParser) | [![MyGet Badge WireMock.Net.OpenApiParser](https://img.shields.io/myget/wiremock-net/vpre/WireMock.Net.OpenApiParser?includePreReleases=true&label=MyGet)](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.OpenApiParser)
| &nbsp;&nbsp;**WireMock.Net.MimePart** | [![NuGet Badge WireMock.Net.MimePart](https://img.shields.io/nuget/v/WireMock.Net.MimePart)](https://www.nuget.org/packages/WireMock.Net.MimePart) | [![MyGet Badge WireMock.Net.MimePart](https://img.shields.io/myget/wiremock-net/vpre/WireMock.Net.MimePart?includePreReleases=true&label=MyGet)](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.MimePart)
| &nbsp;&nbsp;**WireMock.Net.GraphQL** | [![NuGet Badge WireMock.Net.GraphQL](https://img.shields.io/nuget/v/WireMock.Net.GraphQL)](https://www.nuget.org/packages/WireMock.Net.GraphQL) | [![MyGet Badge WireMock.Net.GraphQL](https://img.shields.io/myget/wiremock-net/vpre/WireMock.Net.GraphQL?includePreReleases=true&label=MyGet)](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.GraphQL)
| | | |
| &nbsp;&nbsp;**WireMock.Net.RestClient** | [![NuGet Badge WireMock.Net.RestClient](https://img.shields.io/nuget/v/WireMock.Net.RestClient)](https://www.nuget.org/packages/WireMock.Net.RestClient) | [![MyGet Badge WireMock.Net.RestClient](https://img.shields.io/myget/wiremock-net/vpre/WireMock.Net.RestClient?includePreReleases=true&label=MyGet)](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.RestClient)
| &nbsp;&nbsp;**WireMock.Org.RestClient** | [![NuGet Badge WireMock.Org.RestClient](https://img.shields.io/nuget/v/WireMock.Org.RestClient)](https://www.nuget.org/packages/WireMock.Org.RestClient) | [![MyGet Badge WireMock.Org.RestClient](https://img.shields.io/myget/wiremock-net/vpre/WireMock.Org.RestClient?includePreReleases=true&label=MyGet)](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Org.RestClient)
<br />
🔺 **WireMock.Net.Minimal** does not include: **WireMock.Net.MimePart**
🔺 **WireMock.Net.Minimal** does not include *WireMock.Net.MimePart* and *WireMock.Net.GraphQL*.
---

View File

@@ -136,6 +136,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.ConsoleApp.Usi
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.Tests.UsingNuGet", "test\WireMock.Net.Tests.UsingNuGet\WireMock.Net.Tests.UsingNuGet.csproj", "{BBA332C6-28A9-42E7-9C4D-A0816E52A198}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.GraphQL", "src\WireMock.Net.GraphQL\WireMock.Net.GraphQL.csproj", "{B6269AAC-170A-4346-8B9A-444DED3D9A45}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -326,6 +328,10 @@ Global
{BBA332C6-28A9-42E7-9C4D-A0816E52A198}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BBA332C6-28A9-42E7-9C4D-A0816E52A198}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BBA332C6-28A9-42E7-9C4D-A0816E52A198}.Release|Any CPU.Build.0 = Release|Any CPU
{B6269AAC-170A-4346-8B9A-444DED3D9A45}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B6269AAC-170A-4346-8B9A-444DED3D9A45}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B6269AAC-170A-4346-8B9A-444DED3D9A45}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B6269AAC-170A-4346-8B9A-444DED3D9A45}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -379,6 +385,7 @@ Global
{BFEF8990-65B3-4274-310F-7355F0B84035} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2}
{1F80A6E6-D146-4E40-9EA8-49DB8494239F} = {985E0ADB-D4B4-473A-AA40-567E279B7946}
{BBA332C6-28A9-42E7-9C4D-A0816E52A198} = {0BB8B634-407A-4610-A91F-11586990767A}
{B6269AAC-170A-4346-8B9A-444DED3D9A45} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {DC539027-9852-430C-B19F-FD035D018458}

View File

@@ -132,7 +132,42 @@ namespace WireMock.Net.ConsoleApplication
}
""";
private const string TestSchema =
private const string TestSchemaQueryStudents =
"""
type Query {
students:[Student]
}
type Student {
id:ID!
firstName:String
lastName:String
fullName:String
}
""";
private const string TestSchemaQueryStudentById =
"""
type Query {
studentById(id:ID!):Student
}
type Student {
id:ID!
firstName:String
lastName:String
fullName:String
}
""";
private const string TestSchemaQueryGreeting =
"""
type Query {
greeting:String
}
""";
private const string TestSchemaMutationMessage =
"""
scalar DateTime
scalar MyCustomScalar
@@ -153,19 +188,6 @@ namespace WireMock.Net.ConsoleApplication
createAnotherMessage(x: MyCustomScalar, dt: DateTime): 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
}
""";
private static void RunSse()
@@ -433,10 +455,73 @@ namespace WireMock.Net.ConsoleApplication
.Given(Request.Create()
.WithPath("/graphql")
.UsingPost()
.WithBodyAsGraphQL(TestSchema, customScalars)
.WithGraphQLSchema(TestSchemaQueryStudents)
)
.RespondWith(Response.Create()
.WithBody("GraphQL is ok")
.WithHeader("Content-Type", "application/json")
.WithBody(
"""
{
"data": {
"students": [
{
"id": "1",
"firstName": "Alice",
"lastName": "Johnson",
"fullName": "Alice Johnson"
},
{
"id": "2",
"firstName": "Bob",
"lastName": "Smith",
"fullName": "Bob Smith"
}
]
}
}
""")
);
server
.Given(Request.Create()
.WithPath("/graphql")
.UsingPost()
.WithGraphQLSchema(TestSchemaQueryStudentById)
.WithBody(new JsonPartialWildcardMatcher("{ \"variables\": { \"sid\": \"1\" } }"))
)
.WithTitle("Student found")
.RespondWith(Response.Create()
.WithHeader("Content-Type", "application/json")
.WithBody(
"""
{
"data": {
"studentById": {
"id": "123",
"firstName": "John",
"lastName": "Doe",
"fullName": "John Doe"
}
}
}
""")
);
server
.Given(Request.Create()
.WithPath("/graphql")
.UsingPost()
.WithGraphQLSchema(TestSchemaQueryStudentById)
)
.WithTitle("Student not found")
.RespondWith(Response.Create()
.WithHeader("Content-Type", "application/json")
.WithBody(
"""
{
"data": null
}
""")
);
#endif

View File

@@ -56,6 +56,11 @@ public class MappingModel
/// In case the value is null state will not be changed.
/// </summary>
public string? SetStateTo { get; set; }
/// <summary>
/// The number of times this match should be matched before the state will be changed to the specified one.
/// </summary>
public int? TimesInSameState { get; set; }
/// <summary>
/// The request model.
@@ -86,7 +91,7 @@ public class MappingModel
/// Fire and forget for webhooks.
/// </summary>
public bool? UseWebhooksFireAndForget { get; set; }
/// <summary>
/// Data Object which can be used when WithTransformer is used.
/// e.g. lookup a path in this object using

View File

@@ -0,0 +1,5 @@
// Copyright © WireMock.Net
namespace WireMock.Models.GraphQL;
public interface ISchemaData;

View File

@@ -1,6 +1,5 @@
// Copyright © WireMock.Net
#if GRAPHQL
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
@@ -14,17 +13,17 @@ using Newtonsoft.Json;
using Stef.Validation;
using WireMock.Exceptions;
using WireMock.Extensions;
using WireMock.Matchers.Models;
using WireMock.GraphQL.Models;
using WireMock.Models;
using WireMock.Util;
using WireMock.Models.GraphQL;
using WireMock.Utils;
namespace WireMock.Matchers;
/// <summary>
/// GrapQLMatcher Schema Matcher
/// </summary>
/// <inheritdoc cref="IStringMatcher"/>
public class GraphQLMatcher : IStringMatcher
public class GraphQLMatcher : IGraphQLMatcher
{
private sealed class GraphQLRequest
{
@@ -54,7 +53,7 @@ public class GraphQLMatcher : IStringMatcher
/// <param name="matchBehaviour">The match behaviour. (default = "AcceptOnMatch")</param>
/// <param name="matchOperator">The <see cref="Matchers.MatchOperator"/> to use. (default = "Or")</param>
public GraphQLMatcher(
AnyOf<string, StringPattern, ISchema> schema,
AnyOf<string, StringPattern, ISchemaData> schema,
MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch,
MatchOperator matchOperator = MatchOperator.Or
) : this(schema, null, matchBehaviour, matchOperator)
@@ -69,7 +68,7 @@ public class GraphQLMatcher : IStringMatcher
/// <param name="matchBehaviour">The match behaviour. (default = "AcceptOnMatch")</param>
/// <param name="matchOperator">The <see cref="Matchers.MatchOperator"/> to use. (default = "Or")</param>
public GraphQLMatcher(
AnyOf<string, StringPattern, ISchema> schema,
AnyOf<string, StringPattern, ISchemaData> schema,
IDictionary<string, Type>? customScalars,
MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch,
MatchOperator matchOperator = MatchOperator.Or
@@ -94,7 +93,7 @@ public class GraphQLMatcher : IStringMatcher
break;
case AnyOfType.Third:
_schema = schema.Third;
_schema = ((SchemaDataWrapper)schema.Third).Schema;
break;
default:
@@ -201,7 +200,7 @@ public class GraphQLMatcher : IStringMatcher
throw new WireMockException($"The GraphQL Scalar type '{scalarTypeDefinitionName}' is not defined in the CustomScalars dictionary.");
}
// Create a this custom Scalar GraphType (extending the WireMockCustomScalarGraphType<{clrType}> class)
// Create a custom Scalar GraphType (extending the WireMockCustomScalarGraphType<{clrType}> class)
var customScalarGraphType = ReflectionUtils.CreateGenericType(customScalarGraphTypeName, typeof(WireMockCustomScalarGraphType<>), clrType);
schema.RegisterType(customScalarGraphType);
}
@@ -209,5 +208,4 @@ public class GraphQLMatcher : IStringMatcher
return schema;
}
}
#endif
}

View File

@@ -0,0 +1,18 @@
// Copyright © WireMock.Net
using GraphQL.Types;
using WireMock.Models.GraphQL;
namespace WireMock.Models;
/// <summary>
/// Represents a wrapper for schema data, providing access to the associated schema.
/// </summary>
/// <param name="schema"></param>
public class SchemaDataWrapper(ISchema schema) : ISchemaData
{
/// <summary>
/// Gets the schema associated with the current instance.
/// </summary>
public ISchema Schema { get; } = schema;
}

View File

@@ -1,10 +1,10 @@
// Copyright © WireMock.Net
#if GRAPHQL
using System;
using GraphQL.Types;
namespace WireMock.Matchers.Models;
// ReSharper disable once CheckNamespace
namespace WireMock.GraphQL.Models;
/// <inheritdoc />
public abstract class WireMockCustomScalarGraphType<T> : ScalarGraphType
@@ -28,5 +28,4 @@ public abstract class WireMockCustomScalarGraphType<T> : ScalarGraphType
return (T)Convert.ChangeType(value, typeof(T));
}
}
#endif
}

View File

@@ -0,0 +1,8 @@
// Copyright © WireMock.Net
using System.Runtime.CompilerServices;
// [assembly: InternalsVisibleTo("WireMock.Net.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e138ec44d93acac565953052636eb8d5e7e9f27ddb030590055cd1a0ab2069a5623f1f77ca907d78e0b37066ca0f6d63da7eecc3fcb65b76aa8ebeccf7ebe1d11264b8404cd9b1cbbf2c83f566e033b3e54129f6ef28daffff776ba7aebbc53c0d635ebad8f45f78eb3f7e0459023c218f003416e080f96a1a3c5ffeb56bee9e")]
// Needed for Moq in the UnitTest project
// [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")]

View File

@@ -0,0 +1,94 @@
// Copyright © WireMock.Net
using System;
using System.Collections.Generic;
using GraphQL.Types;
using Stef.Validation;
using WireMock.Matchers;
using WireMock.Matchers.Request;
using WireMock.Models;
using WireMock.Models.GraphQL;
namespace WireMock.RequestBuilders;
/// <summary>
/// IRequestBuilderExtensions extensions for GraphQL.
/// </summary>
// ReSharper disable once InconsistentNaming
public static class IRequestBuilderExtensions
{
/// <summary>
/// WithBodyAsGraphQL: The GraphQL body as a string.
/// </summary>
/// <param name="requestBuilder">The <see cref="IRequestBuilder"/>.</param>
/// <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>
public static IRequestBuilder WithGraphQLSchema(this IRequestBuilder requestBuilder, string schema, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{
return Guard.NotNull(requestBuilder).Add(new RequestMessageGraphQLMatcher(matchBehaviour, schema));
}
/// <summary>
/// WithBodyAsGraphQL: The GraphQL schema as a string.
/// </summary>
/// <param name="requestBuilder">The <see cref="IRequestBuilder"/>.</param>
/// <param name="schema">The GraphQL schema.</param>
/// <param name="customScalars">A dictionary defining the custom scalars used in this schema. (optional)</param>
/// <param name="matchBehaviour">The match behaviour. (Default is <c>MatchBehaviour.AcceptOnMatch</c>).</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
public static IRequestBuilder WithGraphQLSchema(this IRequestBuilder requestBuilder, string schema, IDictionary<string, Type>? customScalars, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{
return Guard.NotNull(requestBuilder).Add(new RequestMessageGraphQLMatcher(matchBehaviour, schema, customScalars));
}
/// <summary>
/// WithBodyAsGraphQL: The GraphQL schema as a <see cref="ISchema"/>.
/// </summary>
/// <param name="requestBuilder">The <see cref="IRequestBuilder"/>.</param>
/// <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>
public static IRequestBuilder WithGraphQLSchema(this IRequestBuilder requestBuilder, ISchema schema, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{
return Guard.NotNull(requestBuilder).Add(new RequestMessageGraphQLMatcher(matchBehaviour, new SchemaDataWrapper(schema)));
}
/// <summary>
/// WithBodyAsGraphQL: The GraphQL schema as a <see cref="ISchema"/>.
/// </summary>
/// <param name="requestBuilder">The <see cref="IRequestBuilder"/>.</param>
/// <param name="schema">The GraphQL schema.</param>
/// <param name="customScalars">A dictionary defining the custom scalars used in this schema. (optional)</param>
/// <param name="matchBehaviour">The match behaviour. (Default is <c>MatchBehaviour.AcceptOnMatch</c>).</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
public static IRequestBuilder WithGraphQLSchema(this IRequestBuilder requestBuilder, ISchema schema, IDictionary<string, Type>? customScalars, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{
return Guard.NotNull(requestBuilder).Add(new RequestMessageGraphQLMatcher(matchBehaviour, new SchemaDataWrapper(schema), customScalars));
}
/// <summary>
/// WithBodyAsGraphQL: The GraphQL schema as a <see cref="ISchemaData"/>.
/// </summary>
/// <param name="requestBuilder">The <see cref="IRequestBuilder"/>.</param>
/// <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>
public static IRequestBuilder WithGraphQLSchema(this IRequestBuilder requestBuilder, ISchemaData schema, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{
return Guard.NotNull(requestBuilder).Add(new RequestMessageGraphQLMatcher(matchBehaviour, schema));
}
/// <summary>
/// WithBodyAsGraphQL: The GraphQL schema as a <see cref="ISchemaData"/>.
/// </summary>
/// <param name="requestBuilder">The <see cref="IRequestBuilder"/>.</param>
/// <param name="schema">The GraphQL schema.</param>
/// <param name="customScalars">A dictionary defining the custom scalars used in this schema. (optional)</param>
/// <param name="matchBehaviour">The match behaviour. (Default is <c>MatchBehaviour.AcceptOnMatch</c>).</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
public static IRequestBuilder WithGraphQLSchema(this IRequestBuilder requestBuilder, ISchemaData schema, IDictionary<string, Type>? customScalars, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{
return Guard.NotNull(requestBuilder).Add(new RequestMessageGraphQLMatcher(matchBehaviour, schema, customScalars));
}
}

View File

@@ -6,7 +6,7 @@ using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
namespace WireMock.Util;
namespace WireMock.Utils;
internal static class ReflectionUtils
{

View File

@@ -0,0 +1,47 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>GraphQL support for WireMock.Net</Description>
<AssemblyTitle>WireMock.Net.Matchers.GraphQL</AssemblyTitle>
<Authors>Stef Heyenrath</Authors>
<TargetFrameworks>netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net8.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PackageTags>wiremock;matchers;matcher;graphql</PackageTags>
<RootNamespace>WireMock</RootNamespace>
<ProjectGuid>{B6269AAC-170A-4346-8B9A-444DED3D9A45}</ProjectGuid>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<GenerateBindingRedirectsOutputType>true</GenerateBindingRedirectsOutputType>
<CodeAnalysisRuleSet>../WireMock.Net/WireMock.Net.ruleset</CodeAnalysisRuleSet>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>../WireMock.Net/WireMock.Net.snk</AssemblyOriginatorKeyFile>
<!--<DelaySign>true</DelaySign>-->
<PublicSign Condition=" '$(OS)' != 'Windows_NT' ">true</PublicSign>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="GraphQL.NewtonsoftJson" Version="8.2.1" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' ">
<PackageReference Include="System.Reflection.Emit" Version="4.3.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\WireMock.Net.Shared\WireMock.Net.Shared.csproj" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' ">
<PackageReference Include="Nullable" Version="1.3.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>

View File

@@ -18,7 +18,7 @@ namespace WireMock.Matchers;
/// CSharpCode / CS-Script Matcher
/// </summary>
/// <inheritdoc cref="ICSharpCodeMatcher"/>
internal class CSharpCodeMatcher : ICSharpCodeMatcher
public class CSharpCodeMatcher : ICSharpCodeMatcher
{
private const string TemplateForIsMatchWithString = "public class CodeHelper {{ public bool IsMatch(string it) {{ {0} }} }}";
@@ -63,17 +63,24 @@ internal class CSharpCodeMatcher : ICSharpCodeMatcher
Value = patterns;
}
public MatchResult IsMatch(string? input)
/// <inheritdoc />
public MatchResult IsMatch(string? input) => IsMatchInternal(input);
/// <inheritdoc />
public MatchResult IsMatch(object? input) => IsMatchInternal(input);
/// <inheritdoc />
public string GetCSharpCodeArguments()
{
return IsMatchInternal(input);
return $"new {Name}" +
$"(" +
$"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " +
$"{MatchOperator.GetFullyQualifiedEnumValue()}, " +
$"{MappingConverterUtils.ToCSharpCodeArguments(_patterns)}" +
$")";
}
public MatchResult IsMatch(object? input)
{
return IsMatchInternal(input);
}
public MatchResult IsMatchInternal(object? input)
private MatchResult IsMatchInternal(object? input)
{
var score = MatchScores.Mismatch;
Exception? exception = null;
@@ -93,17 +100,6 @@ internal class CSharpCodeMatcher : ICSharpCodeMatcher
return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score), exception);
}
/// <inheritdoc />
public string GetCSharpCodeArguments()
{
return $"new {Name}" +
$"(" +
$"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " +
$"{MatchOperator.GetFullyQualifiedEnumValue()}, " +
$"{MappingConverterUtils.ToCSharpCodeArguments(_patterns)}" +
$")";
}
private bool IsMatch(dynamic input, string pattern)
{
var isMatchWithString = input is string;

View File

@@ -68,7 +68,7 @@ public interface IMapping
/// <summary>
/// The number of times this match should be matched before the state will be changed to the next state.
/// </summary>
int? StateTimes { get; }
int? TimesInSameState { get; }
/// <summary>
/// The RequestMatcher.

View File

@@ -43,7 +43,7 @@ public class Mapping : IMapping
public string? NextState { get; }
/// <inheritdoc />
public int? StateTimes { get; }
public int? TimesInSameState { get; }
/// <inheritdoc />
public IRequestMatcher RequestMatcher { get; }
@@ -137,7 +137,7 @@ public class Mapping : IMapping
Scenario = scenario;
ExecutionConditionState = executionConditionState;
NextState = nextState;
StateTimes = stateTimes;
TimesInSameState = stateTimes;
Webhooks = webhooks;
UseWebhooksFireAndForget = useWebhooksFireAndForget;
TimeSettings = timeSettings;

View File

@@ -4,6 +4,7 @@ using System;
using System.Collections.Generic;
using AnyOfTypes;
using Newtonsoft.Json;
using WireMock.Models.GraphQL;
namespace WireMock.Models;
@@ -22,17 +23,16 @@ public class GraphQLSchemaDetails
/// </summary>
public StringPattern? SchemaAsStringPattern { get; set; }
#if GRAPHQL
/// <summary>
/// The GraphQL schema as a <seealso cref="GraphQL.Types.ISchema"/>.
/// The GraphQL schema as a <seealso cref="ISchemaData"/>.
/// </summary>
public GraphQL.Types.ISchema? SchemaAsISchema { get; set; }
public ISchemaData? SchemaAsISchemaData { get; set; }
/// <summary>
/// The GraphQL Schema.
/// </summary>
[JsonIgnore]
public AnyOf<string, StringPattern, GraphQL.Types.ISchema>? Schema
public AnyOf<string, StringPattern, ISchemaData>? Schema
{
get
{
@@ -46,15 +46,14 @@ public class GraphQLSchemaDetails
return SchemaAsStringPattern;
}
if (SchemaAsISchema != null)
if (SchemaAsISchemaData != null)
{
return new AnyOf<string, StringPattern, GraphQL.Types.ISchema>(SchemaAsISchema);
return new AnyOf<string, StringPattern, ISchemaData>(SchemaAsISchemaData);
}
return null;
}
}
#endif
/// <summary>
/// The custom Scalars to define for this schema.

View File

@@ -299,7 +299,7 @@ namespace WireMock.Owin
scenario.Counter++;
// Only if the number of times this state is executed equals the required StateTimes, proceed to next state and reset the counter to 0
if (scenario.Counter == (mapping.StateTimes ?? 1))
if (scenario.Counter == (mapping.TimesInSameState ?? 1))
{
scenario.NextState = mapping.NextState;
scenario.Counter = 0;

View File

@@ -1,83 +0,0 @@
// Copyright © WireMock.Net
using System;
using System.Collections.Generic;
using WireMock.Matchers;
namespace WireMock.RequestBuilders;
/// <summary>
/// The GraphQLRequestBuilder interface.
/// </summary>
public interface IGraphQLRequestBuilder : IMultiPartRequestBuilder
{
/// <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);
/// <summary>
/// WithGraphQLSchema: The GraphQL schema as a string.
/// </summary>
/// <param name="schema">The GraphQL schema.</param>
/// <param name="customScalars">A dictionary defining the custom scalars used in this schema. (optional)</param>
/// <param name="matchBehaviour">The match behaviour. (Default is <c>MatchBehaviour.AcceptOnMatch</c>).</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithGraphQLSchema(string schema, IDictionary<string, Type>? customScalars, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch);
/// <summary>
/// WithBodyAsGraphQL: 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 WithBodyAsGraphQL(string schema, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch);
/// <summary>
/// WithBodyAsGraphQL: The GraphQL schema as a string.
/// </summary>
/// <param name="schema">The GraphQL schema.</param>
/// <param name="customScalars">A dictionary defining the custom scalars used in this schema. (optional)</param>
/// <param name="matchBehaviour">The match behaviour. (Default is <c>MatchBehaviour.AcceptOnMatch</c>).</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithBodyAsGraphQL(string schema, IDictionary<string, Type>? customScalars, 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);
/// <summary>
/// WithGraphQLSchema: The GraphQL schema as a ISchema.
/// </summary>
/// <param name="schema">The GraphQL schema.</param>
/// <param name="customScalars">A dictionary defining the custom scalars used in this schema. (optional)</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, IDictionary<string, Type>? customScalars, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch);
/// <summary>
/// WithBodyAsGraphQL: 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 WithBodyAsGraphQL(GraphQL.Types.ISchema schema, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch);
/// <summary>
/// WithBodyAsGraphQL: The GraphQL schema as a ISchema.
/// </summary>
/// <param name="schema">The GraphQL schema.</param>
/// <param name="customScalars">A dictionary defining the custom scalars used in this schema. (optional)</param>
/// <param name="matchBehaviour">The match behaviour. (Default is <c>MatchBehaviour.AcceptOnMatch</c>).</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithBodyAsGraphQL(GraphQL.Types.ISchema schema, IDictionary<string, Type>? customScalars, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch);
#endif
}

View File

@@ -98,10 +98,4 @@ public partial class Request
_requestMatchers.Add(new RequestMessageBodyMatcher(Guard.NotNull(func)));
return this;
}
/// <inheritdoc />
public IRequestBuilder WithBodyAsGraphQLSchema(string body, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{
return WithGraphQLSchema(body, matchBehaviour);
}
}

View File

@@ -1,61 +0,0 @@
// Copyright © WireMock.Net
using System.Collections.Generic;
using System;
using WireMock.Matchers;
using WireMock.Matchers.Request;
namespace WireMock.RequestBuilders;
public partial class Request
{
/// <inheritdoc />
public IRequestBuilder WithGraphQLSchema(string schema, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{
return WithBodyAsGraphQL(schema, matchBehaviour);
}
/// <inheritdoc />
public IRequestBuilder WithGraphQLSchema(string schema, IDictionary<string, Type>? customScalars, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{
return WithBodyAsGraphQL(schema, customScalars, matchBehaviour);
}
/// <inheritdoc />
public IRequestBuilder WithBodyAsGraphQL(string schema, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{
return Add(new RequestMessageGraphQLMatcher(matchBehaviour, schema));
}
/// <inheritdoc />
public IRequestBuilder WithBodyAsGraphQL(string schema, IDictionary<string, Type>? customScalars, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{
return Add(new RequestMessageGraphQLMatcher(matchBehaviour, schema, customScalars));
}
#if GRAPHQL
/// <inheritdoc />
public IRequestBuilder WithGraphQLSchema(GraphQL.Types.ISchema schema, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{
return WithBodyAsGraphQL(schema, matchBehaviour);
}
/// <inheritdoc />
public IRequestBuilder WithGraphQLSchema(GraphQL.Types.ISchema schema, IDictionary<string, Type>? customScalars, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{
return WithBodyAsGraphQL(schema, customScalars, matchBehaviour);
}
/// <inheritdoc />
public IRequestBuilder WithBodyAsGraphQL(GraphQL.Types.ISchema schema, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{
return Add(new RequestMessageGraphQLMatcher(matchBehaviour, schema));
}
/// <inheritdoc />
public IRequestBuilder WithBodyAsGraphQL(GraphQL.Types.ISchema schema, IDictionary<string, Type>? customScalars, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{
return Add(new RequestMessageGraphQLMatcher(matchBehaviour, schema, customScalars));
}
#endif
}

View File

@@ -73,6 +73,17 @@ public partial class Request : RequestMessageCompositeMatcher, IRequestBuilder
return _requestMatchers.OfType<T>().FirstOrDefault(func);
}
public IRequestBuilder Add<T>(T requestMatcher) where T : IRequestMatcher
{
foreach (var existing in _requestMatchers.OfType<T>().ToArray())
{
_requestMatchers.Remove(existing);
}
_requestMatchers.Add(requestMatcher);
return this;
}
internal bool TryGetProtoBufMatcher([NotNullWhen(true)] out IProtoBufMatcher? protoBufMatcher)
{
protoBufMatcher = GetRequestMessageMatcher<RequestMessageProtoBufMatcher>()?.Matcher;
@@ -85,15 +96,4 @@ public partial class Request : RequestMessageCompositeMatcher, IRequestBuilder
protoBufMatcher = bodyMatcher?.Matchers?.OfType<IProtoBufMatcher>().FirstOrDefault();
return protoBufMatcher != null;
}
private IRequestBuilder Add<T>(T requestMatcher) where T : IRequestMatcher
{
foreach (var existing in _requestMatchers.OfType<T>().ToArray())
{
_requestMatchers.Remove(existing);
}
_requestMatchers.Add(requestMatcher);
return this;
}
}

View File

@@ -13,7 +13,6 @@ using WireMock.Constants;
using WireMock.Extensions;
using WireMock.Matchers;
using WireMock.Matchers.Request;
using WireMock.Models;
using WireMock.RequestBuilders;
using WireMock.ResponseBuilders;
using WireMock.Types;
@@ -111,15 +110,13 @@ internal class MappingConverter(MatcherMapper mapper)
sb.AppendLine($" .WithHttpVersion({requestMessageHttpVersionMatcher.HttpVersion})");
}
#if GRAPHQL
if (requestMessageGraphQLMatcher?.Matchers != null)
{
if (requestMessageGraphQLMatcher.Matchers.OfType<GraphQLMatcher>().FirstOrDefault() is { } graphQLMatcher && graphQLMatcher.GetPatterns().Any())
if (requestMessageGraphQLMatcher.Matchers.OfType<IGraphQLMatcher>().FirstOrDefault() is { } graphQLMatcher && graphQLMatcher.GetPatterns().Any())
{
sb.AppendLine($" .WithGraphQLSchema({GetString(graphQLMatcher)})");
}
}
#endif
if (requestMessageMultiPartMatcher?.Matchers != null)
{
@@ -164,6 +161,16 @@ internal class MappingConverter(MatcherMapper mapper)
// Guid
sb.AppendLine($" .WithGuid(\"{mapping.Guid}\")");
// Scenario + State
if (!string.IsNullOrEmpty(mapping.Scenario))
{
sb.AppendLine($" .InScenario({ToCSharpStringLiteral(mapping.Scenario)})");
}
if (!string.IsNullOrEmpty(mapping.NextState))
{
sb.AppendLine($" .WhenStateIs({ToCSharpStringLiteral(mapping.NextState)}, {ToCSharpIntLiteral(mapping.TimesInSameState)})");
}
if (mapping.Probability != null)
{
sb.AppendLine($" .WithProbability({mapping.Probability.Value.ToString(CultureInfoUtils.CultureInfoEnUS)})");
@@ -270,6 +277,7 @@ internal class MappingConverter(MatcherMapper mapper)
Scenario = mapping.Scenario,
WhenStateIs = mapping.ExecutionConditionState,
SetStateTo = mapping.NextState,
TimesInSameState = !string.IsNullOrWhiteSpace(mapping.NextState) ? mapping.TimesInSameState : null,
Data = mapping.Data,
Probability = mapping.Probability,
Request = new RequestModel

View File

@@ -10,6 +10,7 @@ using WireMock.Admin.Mappings;
using WireMock.Extensions;
using WireMock.Matchers;
using WireMock.Models;
using WireMock.Models.GraphQL;
using WireMock.Settings;
using WireMock.Util;
@@ -70,10 +71,11 @@ internal class MatcherMapper
case nameof(ExactObjectMatcher):
return CreateExactObjectMatcher(matchBehaviour, stringPatterns[0]);
#if GRAPHQL
case nameof(GraphQLMatcher):
return new GraphQLMatcher(stringPatterns[0].GetPattern(), matcherModel.CustomScalars, matchBehaviour, matchOperator);
#endif
case "GraphQLMatcher":
var patternAsString = stringPatterns[0].GetPattern();
var schema = new AnyOf<string, StringPattern, ISchemaData>(patternAsString);
return TypeLoader.LoadNewInstance<IGraphQLMatcher>(schema, matcherModel.CustomScalars, matchBehaviour, matchOperator);
case "MimePartMatcher":
return CreateMimePartMatcher(matchBehaviour, matcherModel);
@@ -165,11 +167,10 @@ internal class MatcherMapper
case XPathMatcher xpathMatcher:
model.XmlNamespaceMap = xpathMatcher.XmlNamespaceMap;
break;
#if GRAPHQL
case GraphQLMatcher graphQLMatcher:
case IGraphQLMatcher graphQLMatcher:
model.CustomScalars = graphQLMatcher.CustomScalars;
break;
#endif
}
switch (matcher)
@@ -277,10 +278,10 @@ internal class MatcherMapper
private IMimePartMatcher CreateMimePartMatcher(MatchBehaviour matchBehaviour, MatcherModel matcher)
{
var contentTypeMatcher = Map(matcher?.ContentTypeMatcher) as IStringMatcher;
var contentDispositionMatcher = Map(matcher?.ContentDispositionMatcher) as IStringMatcher;
var contentTransferEncodingMatcher = Map(matcher?.ContentTransferEncodingMatcher) as IStringMatcher;
var contentMatcher = Map(matcher?.ContentMatcher);
var contentTypeMatcher = Map(matcher.ContentTypeMatcher) as IStringMatcher;
var contentDispositionMatcher = Map(matcher.ContentDispositionMatcher) as IStringMatcher;
var contentTransferEncodingMatcher = Map(matcher.ContentTransferEncodingMatcher) as IStringMatcher;
var contentMatcher = Map(matcher.ContentMatcher);
return TypeLoader.LoadNewInstance<IMimePartMatcher>(matchBehaviour, contentTypeMatcher, contentDispositionMatcher, contentTransferEncodingMatcher, contentMatcher);
}

View File

@@ -97,7 +97,7 @@ public partial class WireMockServer
if (!string.IsNullOrEmpty(mappingModel.SetStateTo))
{
respondProvider = respondProvider.WillSetStateTo(mappingModel.SetStateTo!);
respondProvider = respondProvider.WillSetStateTo(mappingModel.SetStateTo!, mappingModel.TimesInSameState);
}
}

View File

@@ -126,7 +126,6 @@
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' != 'netstandard1.3' and '$(TargetFramework)' != 'net451' and '$(TargetFramework)' != 'net452' and '$(TargetFramework)' != 'net46' and '$(TargetFramework)' != 'net461'">
<PackageReference Include="GraphQL.NewtonsoftJson" Version="8.2.1" />
<PackageReference Include="ProtoBufJsonConverter" Version="0.10.0" />
</ItemGroup>
@@ -167,7 +166,7 @@
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' != 'netstandard1.3' and '$(TargetFramework)' != 'net451' and '$(TargetFramework)' != 'net452' ">
<PackageReference Include="Handlebars.Net.Helpers.Xslt" Version="2.4.6" />
<PackageReference Include="Handlebars.Net.Helpers.Xslt" Version="2.5.2" />
</ItemGroup>
<!--<ItemGroup>

View File

@@ -13,10 +13,10 @@ namespace WireMock.Extensions;
public static class AnyOfExtensions
{
/// <summary>
/// Gets the pattern.
/// Gets the pattern as string value.
/// </summary>
/// <param name="value">AnyOf type</param>
/// <returns>string value</returns>
/// <returns>The string value</returns>
public static string GetPattern(this AnyOf<string, StringPattern> value)
{
return value.IsFirst ? value.First : value.Second.Pattern;

View File

@@ -34,14 +34,10 @@ internal static class BodyDataMatchScoreCalculator
}
}
if (matcher is ExactObjectMatcher exactObjectMatcher)
if (matcher is ExactObjectMatcher { Value: byte[] } exactObjectMatcher)
{
// If the body is a byte array, try to match.
var detectedBodyType = requestMessage.DetectedBodyType;
if (detectedBodyType is BodyType.Bytes or BodyType.String or BodyType.FormUrlEncoded)
{
return exactObjectMatcher.IsMatch(requestMessage.BodyAsBytes);
}
return exactObjectMatcher.IsMatch(requestMessage.BodyAsBytes);
}
// Check if the matcher is a IObjectMatcher
@@ -74,4 +70,4 @@ internal static class BodyDataMatchScoreCalculator
return default;
}
}
}

View File

@@ -7,6 +7,4 @@ namespace WireMock.Matchers;
/// </summary>
/// <inheritdoc cref="IObjectMatcher"/>
/// <inheritdoc cref="IStringMatcher"/>
public interface ICSharpCodeMatcher : IObjectMatcher, IStringMatcher
{
}
public interface ICSharpCodeMatcher : IObjectMatcher, IStringMatcher;

View File

@@ -0,0 +1,18 @@
// Copyright © WireMock.Net
using System;
using System.Collections.Generic;
namespace WireMock.Matchers;
/// <summary>
/// GraphQLMatcher
/// </summary>
/// <inheritdoc cref="IStringMatcher"/>
public interface IGraphQLMatcher : IStringMatcher
{
/// <summary>
/// An optional dictionary defining the custom Scalar and the type.
/// </summary>
public IDictionary<string, Type>? CustomScalars { get; }
}

View File

@@ -3,8 +3,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using AnyOfTypes;
using Stef.Validation;
using WireMock.Models;
using WireMock.Models.GraphQL;
using WireMock.Types;
using WireMock.Util;
namespace WireMock.Matchers.Request;
@@ -34,18 +38,16 @@ public class RequestMessageGraphQLMatcher : IRequestMatcher
{
}
#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>
/// <param name="customScalars">A dictionary defining the custom scalars used in this schema. [optional]</param>
public RequestMessageGraphQLMatcher(MatchBehaviour matchBehaviour, GraphQL.Types.ISchema schema, IDictionary<string, Type>? customScalars = null) :
this(CreateMatcherArray(matchBehaviour, new AnyOfTypes.AnyOf<string, WireMock.Models.StringPattern, GraphQL.Types.ISchema>(schema), customScalars))
public RequestMessageGraphQLMatcher(MatchBehaviour matchBehaviour, ISchemaData schema, IDictionary<string, Type>? customScalars = null) :
this(CreateMatcherArray(matchBehaviour, new AnyOf<string, StringPattern, ISchemaData>(schema), customScalars))
{
}
#endif
/// <summary>
/// Initializes a new instance of the <see cref="RequestMessageGraphQLMatcher"/> class.
@@ -89,22 +91,16 @@ public class RequestMessageGraphQLMatcher : IRequestMatcher
private IReadOnlyList<MatchResult> CalculateMatchResults(IRequestMessage requestMessage)
{
return Matchers == null ? new[] { new MatchResult() } : Matchers.Select(matcher => CalculateMatchResult(requestMessage, matcher)).ToArray();
return Matchers == null ? [new MatchResult()] : Matchers.Select(matcher => CalculateMatchResult(requestMessage, matcher)).ToArray();
}
#if GRAPHQL
private static IMatcher[] CreateMatcherArray(
MatchBehaviour matchBehaviour,
AnyOfTypes.AnyOf<string, WireMock.Models.StringPattern, GraphQL.Types.ISchema> schema,
AnyOf<string, StringPattern, ISchemaData> schema,
IDictionary<string, Type>? customScalars
)
{
return new[] { new GraphQLMatcher(schema, customScalars, matchBehaviour) }.Cast<IMatcher>().ToArray();
var graphQLMatcher = TypeLoader.LoadNewInstance<IGraphQLMatcher>(schema, customScalars, matchBehaviour, MatchOperator.Or);
return [graphQLMatcher];
}
#else
private static IMatcher[] CreateMatcherArray(MatchBehaviour matchBehaviour, object schema, IDictionary<string, Type>? customScalars)
{
throw new System.NotSupportedException("The GrapQLMatcher can not be used for .NETStandard1.3 or .NET Framework 4.6.1 or lower.");
}
#endif
}

View File

@@ -4,6 +4,7 @@ using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("WireMock.Net.Minimal, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e138ec44d93acac565953052636eb8d5e7e9f27ddb030590055cd1a0ab2069a5623f1f77ca907d78e0b37066ca0f6d63da7eecc3fcb65b76aa8ebeccf7ebe1d11264b8404cd9b1cbbf2c83f566e033b3e54129f6ef28daffff776ba7aebbc53c0d635ebad8f45f78eb3f7e0459023c218f003416e080f96a1a3c5ffeb56bee9e")]
[assembly: InternalsVisibleTo("WireMock.Net.MimePart, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e138ec44d93acac565953052636eb8d5e7e9f27ddb030590055cd1a0ab2069a5623f1f77ca907d78e0b37066ca0f6d63da7eecc3fcb65b76aa8ebeccf7ebe1d11264b8404cd9b1cbbf2c83f566e033b3e54129f6ef28daffff776ba7aebbc53c0d635ebad8f45f78eb3f7e0459023c218f003416e080f96a1a3c5ffeb56bee9e")]
[assembly: InternalsVisibleTo("WireMock.Net.GraphQL, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e138ec44d93acac565953052636eb8d5e7e9f27ddb030590055cd1a0ab2069a5623f1f77ca907d78e0b37066ca0f6d63da7eecc3fcb65b76aa8ebeccf7ebe1d11264b8404cd9b1cbbf2c83f566e033b3e54129f6ef28daffff776ba7aebbc53c0d635ebad8f45f78eb3f7e0459023c218f003416e080f96a1a3c5ffeb56bee9e")]
[assembly: InternalsVisibleTo("WireMock.Net.Matchers.CSharpCode, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e138ec44d93acac565953052636eb8d5e7e9f27ddb030590055cd1a0ab2069a5623f1f77ca907d78e0b37066ca0f6d63da7eecc3fcb65b76aa8ebeccf7ebe1d11264b8404cd9b1cbbf2c83f566e033b3e54129f6ef28daffff776ba7aebbc53c0d635ebad8f45f78eb3f7e0459023c218f003416e080f96a1a3c5ffeb56bee9e")]
// [assembly: InternalsVisibleTo("WireMock.Net.StandAlone, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e138ec44d93acac565953052636eb8d5e7e9f27ddb030590055cd1a0ab2069a5623f1f77ca907d78e0b37066ca0f6d63da7eecc3fcb65b76aa8ebeccf7ebe1d11264b8404cd9b1cbbf2c83f566e033b3e54129f6ef28daffff776ba7aebbc53c0d635ebad8f45f78eb3f7e0459023c218f003416e080f96a1a3c5ffeb56bee9e")]
[assembly: InternalsVisibleTo("WireMock.Net.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e138ec44d93acac565953052636eb8d5e7e9f27ddb030590055cd1a0ab2069a5623f1f77ca907d78e0b37066ca0f6d63da7eecc3fcb65b76aa8ebeccf7ebe1d11264b8404cd9b1cbbf2c83f566e033b3e54129f6ef28daffff776ba7aebbc53c0d635ebad8f45f78eb3f7e0459023c218f003416e080f96a1a3c5ffeb56bee9e")]

View File

@@ -52,7 +52,7 @@ public interface IBodyRequestBuilder : IProtoBufRequestBuilder
IRequestBuilder WithBody(object body, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch);
/// <summary>
/// WithBodyAsJson: A <see cref="JsonMatcher"/> will be used to match this object.
/// WithBodyAsJson: A JsonMatcher will be used to match this object.
/// </summary>
/// <param name="body">The body.</param>
/// <param name="matchBehaviour">The match behaviour [default is AcceptOnMatch].</param>
@@ -93,12 +93,4 @@ public interface IBodyRequestBuilder : IProtoBufRequestBuilder
/// <param name="func">The form-urlencoded values.</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
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);
}

View File

@@ -1,7 +1,6 @@
// Copyright © WireMock.Net
using WireMock.Matchers;
using WireMock.Matchers.Request;
namespace WireMock.RequestBuilders;

View File

@@ -8,7 +8,7 @@ namespace WireMock.RequestBuilders;
/// <summary>
/// The ProtoBufRequestBuilder interface.
/// </summary>
public interface IProtoBufRequestBuilder : IGraphQLRequestBuilder
public interface IProtoBufRequestBuilder : IMultiPartRequestBuilder
{
/// <summary>
/// WithBodyAsProtoBuf

View File

@@ -1,5 +1,7 @@
// Copyright © WireMock.Net
using WireMock.Matchers.Request;
namespace WireMock.RequestBuilders;
/// <summary>
@@ -7,4 +9,5 @@ namespace WireMock.RequestBuilders;
/// </summary>
public interface IRequestBuilder : IClientIPRequestBuilder
{
public IRequestBuilder Add<T>(T requestMatcher) where T : IRequestMatcher;
}

View File

@@ -134,6 +134,8 @@ internal static class CSharpFormatter
};
}
public static string ToCSharpIntLiteral(int? value) => value != null ? string.Format(CultureInfo.InvariantCulture, "{0}", value) : Null;
public static string ToCSharpBooleanLiteral(bool value) => value ? "true" : "false";
public static string ToCSharpStringLiteral(string? value)

View File

@@ -39,6 +39,10 @@
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard1.3' ">
<PackageReference Include="System.Reflection.TypeExtensions" Version="4.7.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\WireMock.Net.Abstractions\WireMock.Net.Abstractions.csproj" />
</ItemGroup>

View File

@@ -34,5 +34,6 @@
<ItemGroup Condition="'$(TargetFramework)' != 'netstandard1.3' and '$(TargetFramework)' != 'net451' and '$(TargetFramework)' != 'net452' and '$(TargetFramework)' != 'net46' and '$(TargetFramework)' != 'net461'">
<ProjectReference Include="../WireMock.Net.MimePart/WireMock.Net.MimePart.csproj" />
<ProjectReference Include="../WireMock.Net.GraphQL/WireMock.Net.GraphQL.csproj" />
</ItemGroup>
</Project>

View File

@@ -165,5 +165,30 @@
]
},
Response: {}
},
{
Guid: 98fae52e-76df-47d9-876f-2ee32e931005,
UpdatedAt: 2023-01-14 15:16:17,
Scenario: To do list,
SetStateTo: TodoList State Started,
TimesInSameState: 2,
Request: {
Path: {
Matchers: [
{
Name: WildcardMatcher,
Pattern: /todo/items,
IgnoreCase: false
}
]
},
Methods: [
GET
]
},
Response: {
BodyDestination: SameAsSource,
Body: Buy milk
}
}
]

View File

@@ -66,3 +66,15 @@ builder
.RespondWith(Response.Create()
);
builder
.Given(Request.Create()
.UsingMethod("GET")
.WithPath(new WildcardMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, "/todo/items", false, WireMock.Matchers.MatchOperator.Or))
)
.WithGuid("98fae52e-76df-47d9-876f-2ee32e931005")
.InScenario("To do list")
.WhenStateIs("TodoList State Started", 2)
.RespondWith(Response.Create()
.WithBody("Buy milk")
);

View File

@@ -66,3 +66,15 @@ server
.RespondWith(Response.Create()
);
server
.Given(Request.Create()
.UsingMethod("GET")
.WithPath(new WildcardMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, "/todo/items", false, WireMock.Matchers.MatchOperator.Or))
)
.WithGuid("98fae52e-76df-47d9-876f-2ee32e931005")
.InScenario("To do list")
.WhenStateIs("TodoList State Started", 2)
.RespondWith(Response.Create()
.WithBody("Buy milk")
);

View File

@@ -161,5 +161,30 @@
}
]
}
},
{
Guid: 98fae52e-76df-47d9-876f-2ee32e931005,
UpdatedAt: 2023-01-14T15:16:17,
Scenario: To do list,
SetStateTo: TodoList State Started,
TimesInSameState: 2,
Request: {
Path: {
Matchers: [
{
Name: WildcardMatcher,
Pattern: /todo/items,
IgnoreCase: false
}
]
},
Methods: [
GET
]
},
Response: {
BodyDestination: SameAsSource,
Body: Buy milk
}
}
]

View File

@@ -34,6 +34,7 @@ public class MappingBuilderTests
private static readonly DateTime UtcNow = new(2023, 1, 14, 15, 16, 17);
private readonly Mock<IFileSystemHandler> _fileSystemHandlerMock;
private readonly int _numMappings;
private readonly MappingBuilder _sut;
@@ -104,11 +105,20 @@ public class MappingBuilderTests
).RespondWith(Response.Create());
_sut.Given(Request.Create()
.WithPath("/regex")
.WithParam("foo", new RegexMatcher(".*"))
.UsingGet()
)
.RespondWith(Response.Create());
.WithPath("/regex")
.WithParam("foo", new RegexMatcher(".*"))
.UsingGet()
).RespondWith(Response.Create());
_sut.Given(Request.Create()
.WithPath("/todo/items")
.UsingGet())
.InScenario("To do list")
.WillSetStateTo("TodoList State Started", 2)
.RespondWith(Response.Create()
.WithBody("Buy milk"));
_numMappings = _sut.GetMappings().Length;
}
[Fact]
@@ -197,9 +207,9 @@ public class MappingBuilderTests
_sut.SaveMappingsToFolder(null);
// Verify
_fileSystemHandlerMock.Verify(fs => fs.GetMappingFolder(), Times.Exactly(5));
_fileSystemHandlerMock.Verify(fs => fs.FolderExists(mappingFolder), Times.Exactly(5));
_fileSystemHandlerMock.Verify(fs => fs.WriteMappingFile(It.IsAny<string>(), It.IsAny<string>()), Times.Exactly(5));
_fileSystemHandlerMock.Verify(fs => fs.GetMappingFolder(), Times.Exactly(_numMappings));
_fileSystemHandlerMock.Verify(fs => fs.FolderExists(mappingFolder), Times.Exactly(_numMappings));
_fileSystemHandlerMock.Verify(fs => fs.WriteMappingFile(It.IsAny<string>(), It.IsAny<string>()), Times.Exactly(_numMappings));
_fileSystemHandlerMock.VerifyNoOtherCalls();
}
@@ -215,8 +225,8 @@ public class MappingBuilderTests
// Verify
_fileSystemHandlerMock.Verify(fs => fs.GetMappingFolder(), Times.Never);
_fileSystemHandlerMock.Verify(fs => fs.FolderExists(path), Times.Exactly(5));
_fileSystemHandlerMock.Verify(fs => fs.WriteMappingFile(It.IsAny<string>(), It.IsAny<string>()), Times.Exactly(5));
_fileSystemHandlerMock.Verify(fs => fs.FolderExists(path), Times.Exactly(_numMappings));
_fileSystemHandlerMock.Verify(fs => fs.WriteMappingFile(It.IsAny<string>(), It.IsAny<string>()), Times.Exactly(_numMappings));
_fileSystemHandlerMock.VerifyNoOtherCalls();
}
}

View File

@@ -3,7 +3,7 @@
#if GRAPHQL
using System;
using FluentAssertions;
using WireMock.Matchers.Models;
using WireMock.GraphQL.Models;
using Xunit;
namespace WireMock.Net.Tests.Matchers.Models;

View File

@@ -404,6 +404,79 @@ public class RequestMessageBodyMatcherTests
objectMatcherMock.Verify(m => m.IsMatch(It.IsAny<byte[]>()), Times.Once);
}
[Theory]
[InlineData(new byte[] { 1 })]
[InlineData(new byte[] { 48 })]
public void RequestMessageBodyMatcher_GetMatchingScore_BodyTypeBytes_BodyAsBytes_ExactObjectMapper(byte[] bytes)
{
// Assign
var body = new BodyData
{
BodyAsBytes = bytes,
DetectedBodyType = BodyType.Bytes
};
var exactObjectMapper = new ExactObjectMatcher(bytes);
var requestMessage = new RequestMessage(new UrlDetails("http://localhost"), "GET", "127.0.0.1", body);
var matcher = new RequestMessageBodyMatcher(exactObjectMapper);
// Act
var result = new RequestMatchResult();
double score = matcher.GetMatchingScore(requestMessage, result);
// Assert
Check.That(score).IsEqualTo(1.0d);
}
[Fact]
public void RequestMessageBodyMatcher_GetMatchingScore_BodyTypeString_BodyAsBytes_ExactObjectMapper()
{
// Assign
var bytes = Encoding.UTF8.GetBytes("hello");
var body = new BodyData
{
BodyAsBytes = bytes,
DetectedBodyType = BodyType.String
};
var exactObjectMapper = new ExactObjectMatcher(bytes);
var requestMessage = new RequestMessage(new UrlDetails("http://localhost"), "GET", "127.0.0.1", body);
var matcher = new RequestMessageBodyMatcher(exactObjectMapper);
// Act
var result = new RequestMatchResult();
double score = matcher.GetMatchingScore(requestMessage, result);
// Assert
Check.That(score).IsEqualTo(1.0d);
}
[Fact]
public void RequestMessageBodyMatcher_GetMatchingScore_BodyTypeJson_BodyAsBytes_ExactObjectMapper()
{
// Assign
var bytes = Encoding.UTF8.GetBytes("""{"value":42}""");
var body = new BodyData
{
BodyAsBytes = bytes,
DetectedBodyType = BodyType.Json
};
var exactObjectMapper = new ExactObjectMatcher(bytes);
var requestMessage = new RequestMessage(new UrlDetails("http://localhost"), "GET", "127.0.0.1", body);
var matcher = new RequestMessageBodyMatcher(exactObjectMapper);
// Act
var result = new RequestMatchResult();
double score = matcher.GetMatchingScore(requestMessage, result);
// Assert
Check.That(score).IsEqualTo(1.0d);
}
[Theory]
[MemberData(nameof(MatchingScoreData))]
public async Task RequestMessageBodyMatcher_GetMatchingScore_Funcs_Matching(object body, RequestMessageBodyMatcher matcher, bool shouldMatch)
@@ -459,13 +532,13 @@ public class RequestMessageBodyMatcherTests
{json, new RequestMessageBodyMatcher((object? o) => ((dynamic) o!).a == "b"), true},
{json, new RequestMessageBodyMatcher((string? s) => s == json), true},
{json, new RequestMessageBodyMatcher((byte[]? b) => b?.SequenceEqual(Encoding.UTF8.GetBytes(json)) == true), true},
// JSON no match ---
{json, new RequestMessageBodyMatcher((object? o) => false), false},
{json, new RequestMessageBodyMatcher((string? s) => false), false},
{json, new RequestMessageBodyMatcher((byte[]? b) => false), false},
{json, new RequestMessageBodyMatcher(), false },
// string match +++
{str, new RequestMessageBodyMatcher((object? o) => o == null), true},
{str, new RequestMessageBodyMatcher((string? s) => s == str), true},
@@ -476,7 +549,7 @@ public class RequestMessageBodyMatcherTests
{str, new RequestMessageBodyMatcher((string? s) => false), false},
{str, new RequestMessageBodyMatcher((byte[]? b) => false), false},
{str, new RequestMessageBodyMatcher(), false },
// binary match +++
{bytes, new RequestMessageBodyMatcher((object? o) => o == null), true},
{bytes, new RequestMessageBodyMatcher((string? s) => s == null), true},

View File

@@ -549,7 +549,7 @@ message HelloReply {
lastName:String
fullName:String
}";
var request = Request.Create().WithBodyAsGraphQLSchema(schema);
var request = Request.Create().WithGraphQLSchema(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);

View File

@@ -1076,7 +1076,7 @@ message HelloReply {
};
// Act
var matcher = (GraphQLMatcher)_sut.Map(model)!;
var matcher = (IGraphQLMatcher)_sut.Map(model)!;
// Assert
matcher.GetPatterns().Should().HaveElementAt(0, testSchema);