From b50047ce17801114787529c1997e8552c717b636 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Mon, 16 Oct 2023 22:29:20 +0200 Subject: [PATCH 1/7] x --- src/WireMock.Net/Matchers/GraphQLMatcher.cs | 36 ++++++++++++++++++- src/WireMock.Net/Util/ReflectionUtils.cs | 33 +++++++++++++++++ .../Matchers/GraphQLMatcherTests.cs | 31 ++++++++++++++++ 3 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 src/WireMock.Net/Util/ReflectionUtils.cs diff --git a/src/WireMock.Net/Matchers/GraphQLMatcher.cs b/src/WireMock.Net/Matchers/GraphQLMatcher.cs index 6bba41635..bc7744464 100644 --- a/src/WireMock.Net/Matchers/GraphQLMatcher.cs +++ b/src/WireMock.Net/Matchers/GraphQLMatcher.cs @@ -6,10 +6,13 @@ using AnyOfTypes; using GraphQL; using GraphQL.Types; +using GraphQLParser; +using GraphQLParser.AST; using Newtonsoft.Json; using Stef.Validation; using WireMock.Extensions; using WireMock.Models; +using WireMock.Util; namespace WireMock.Matchers; @@ -137,14 +140,45 @@ private static bool TryGetGraphQLRequest(string input, [NotNullWhen(true)] out G } } + /// A textual description of the schema in SDL (Schema Definition Language) format. private static ISchema BuildSchema(string typeDefinitions) { var schema = Schema.For(typeDefinitions); // #984 - schema.RegisterTypes(schema.BuiltInTypeMappings.Select(x => x.graphType).ToArray()); + var graphTypes = schema.BuiltInTypeMappings.Select(tm => tm.graphType).ToList(); + schema.RegisterTypes(graphTypes.ToArray()); + + var doc = Parser.Parse(typeDefinitions); + var scalarTypeDefinitions = doc.Definitions + .Where(d => d.Kind == ASTNodeKind.ScalarTypeDefinition) + .OfType() + .ToArray(); + + foreach (var scalarTypeDefinition in scalarTypeDefinitions) + { + var scalarGraphTypeName = $"{scalarTypeDefinition.Name.StringValue}GraphType"; + if (graphTypes.All(t => t.Name != scalarGraphTypeName)) + { + var dynamicType = ReflectionUtils.CreateType(scalarTypeDefinition.Name.StringValue, typeof(IntGraphType)); + + schema.RegisterType(dynamicType); + + //schema.RegisterTypeMapping(typeof(int), dynamicType); + } + } + + //schema.RegisterType(typeof(MyCustomScalarGraphType)); return schema; } + + internal class MyCustomScalarGraphType : IntGraphType + { + //public override object? ParseValue(object? value) + //{ + // return true; + //} + } } #endif \ No newline at end of file diff --git a/src/WireMock.Net/Util/ReflectionUtils.cs b/src/WireMock.Net/Util/ReflectionUtils.cs new file mode 100644 index 000000000..c5f535c68 --- /dev/null +++ b/src/WireMock.Net/Util/ReflectionUtils.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Concurrent; +using System.Reflection; +using System.Reflection.Emit; + +namespace WireMock.Util; + +internal static class ReflectionUtils +{ + private const string DynamicModuleName = "WireMockDynamicModule"; + private static readonly AssemblyName AssemblyName = new("WireMockDynamicAssembly"); + private static readonly ConcurrentDictionary TypeCache = new(); + + public static Type CreateType(string typeName, Type baseType) + { + // Check if the type has already been created + if (TypeCache.TryGetValue(typeName, out var existingType)) + { + return existingType; + } + + var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(AssemblyName, AssemblyBuilderAccess.Run); + var moduleBuilder = assemblyBuilder.DefineDynamicModule(DynamicModuleName); + + var typeBuilder = moduleBuilder.DefineType(typeName, TypeAttributes.Public, baseType); + + // Create the type and cache it + var newType = typeBuilder.CreateTypeInfo().AsType(); + TypeCache[typeName] = newType; + + return newType; + } +} \ No newline at end of file diff --git a/test/WireMock.Net.Tests/Matchers/GraphQLMatcherTests.cs b/test/WireMock.Net.Tests/Matchers/GraphQLMatcherTests.cs index 3efc32a2a..3ecbe0562 100644 --- a/test/WireMock.Net.Tests/Matchers/GraphQLMatcherTests.cs +++ b/test/WireMock.Net.Tests/Matchers/GraphQLMatcherTests.cs @@ -99,6 +99,37 @@ public void GraphQLMatcher_For_ValidSchema_And_CorrectGraphQL_Mutation_IsMatch() matcher.GetPatterns().Should().Contain(TestSchema); } + [Fact] + public void GraphQLMatcher_For_ValidSchema_And_CorrectGraphQL_UsingCustomType_Mutation_IsMatch() + { + // Arrange + const string testSchema = @" + scalar DateTime + scalar MyCustomScalar + + type Message { + id: ID! + } + + type Mutation { + createMessage(x: MyCustomScalar, dt: DateTime): Message + }"; + + var input = @"{ + ""query"": ""mutation CreateMessage($x: MyCustomScalar!, $dt: DateTime!) { createMessage(x: $x, dt: $dt) { id } }"", + ""variables"": { ""x"": 100, ""dt"": ""2007-12-03T10:15:30Z"" } +}"; + + // Act + var matcher = new GraphQLMatcher(testSchema); + var result = matcher.IsMatch(input); + + // Assert + result.Score.Should().Be(MatchScores.Perfect); + + matcher.GetPatterns().Should().Contain(testSchema); + } + [Fact] public void GraphQLMatcher_For_ValidSchema_And_IncorrectQuery_IsMismatch() { From 106159ff6fae152636caf6de363a81502f20ae41 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Wed, 18 Oct 2023 19:55:10 +0200 Subject: [PATCH 2/7] CustomScalars --- src/WireMock.Net/Matchers/GraphQLMatcher.cs | 62 ++++++++---- .../Models/WireMockCustomScalarGraphType.cs | 30 ++++++ .../Request/RequestMessageGraphQLMatcher.cs | 4 +- src/WireMock.Net/Util/ReflectionUtils.cs | 32 ++++++- .../Matchers/GraphQLMatcherTests.cs | 5 +- .../WireMockCustomScalarGraphTypeTests.cs | 96 +++++++++++++++++++ 6 files changed, 204 insertions(+), 25 deletions(-) create mode 100644 src/WireMock.Net/Matchers/Models/WireMockCustomScalarGraphType.cs create mode 100644 test/WireMock.Net.Tests/Matchers/Models/WireMockCustomScalarGraphTypeTests.cs diff --git a/src/WireMock.Net/Matchers/GraphQLMatcher.cs b/src/WireMock.Net/Matchers/GraphQLMatcher.cs index bc7744464..11e179590 100644 --- a/src/WireMock.Net/Matchers/GraphQLMatcher.cs +++ b/src/WireMock.Net/Matchers/GraphQLMatcher.cs @@ -10,7 +10,9 @@ using GraphQLParser.AST; using Newtonsoft.Json; using Stef.Validation; +using WireMock.Exceptions; using WireMock.Extensions; +using WireMock.Matchers.Models; using WireMock.Models; using WireMock.Util; @@ -39,14 +41,40 @@ private sealed class GraphQLRequest public MatchBehaviour MatchBehaviour { get; } /// - /// Initializes a new instance of the class. + /// An optional dictionary defining the custom Scalar and the type. + /// + public IDictionary? CustomScalars { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The schema. + /// The match behaviour. (default = "AcceptOnMatch") + /// The to use. (default = "Or") + public GraphQLMatcher( + AnyOf schema, + MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch, + MatchOperator matchOperator = MatchOperator.Or + ) : this(schema, null, matchBehaviour, matchOperator) + { + } + + /// + /// Initializes a new instance of the class. /// /// The schema. - /// The match behaviour. + /// A dictionary defining the custom scalars used in this schema. + /// The match behaviour. (default = "AcceptOnMatch") /// The to use. (default = "Or") - public GraphQLMatcher(AnyOf schema, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch, MatchOperator matchOperator = MatchOperator.Or) + public GraphQLMatcher( + AnyOf schema, + IDictionary? customScalars, + MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch, + MatchOperator matchOperator = MatchOperator.Or + ) { Guard.NotNull(schema); + CustomScalars = customScalars; MatchBehaviour = matchBehaviour; MatchOperator = matchOperator; @@ -141,7 +169,7 @@ private static bool TryGetGraphQLRequest(string input, [NotNullWhen(true)] out G } /// A textual description of the schema in SDL (Schema Definition Language) format. - private static ISchema BuildSchema(string typeDefinitions) + private ISchema BuildSchema(string typeDefinitions) { var schema = Schema.For(typeDefinitions); @@ -157,28 +185,22 @@ private static ISchema BuildSchema(string typeDefinitions) foreach (var scalarTypeDefinition in scalarTypeDefinitions) { - var scalarGraphTypeName = $"{scalarTypeDefinition.Name.StringValue}GraphType"; - if (graphTypes.All(t => t.Name != scalarGraphTypeName)) + var customScalarGraphTypeName = $"{scalarTypeDefinition.Name.StringValue}GraphType"; + if (graphTypes.All(t => t.Name != customScalarGraphTypeName)) // Only process when not built-in. { - var dynamicType = ReflectionUtils.CreateType(scalarTypeDefinition.Name.StringValue, typeof(IntGraphType)); - - schema.RegisterType(dynamicType); + // Check if this custom Scalar is defined in the dictionary + if (CustomScalars == null || !CustomScalars.TryGetValue(scalarTypeDefinition.Name.StringValue, out var clrType)) + { + throw new WireMockException($"The GraphQL Scalar type '{scalarTypeDefinition.Name.StringValue}' is not defined in the CustomScalars dictionary."); + } - //schema.RegisterTypeMapping(typeof(int), dynamicType); + // Create a this custom Scalar GraphType (extending the WireMockCustomScalarGraphType<{clrType}> class) + var customScalarGraphType = ReflectionUtils.CreateGenericType(customScalarGraphTypeName, typeof(WireMockCustomScalarGraphType<>), clrType); + schema.RegisterType(customScalarGraphType); } } - - //schema.RegisterType(typeof(MyCustomScalarGraphType)); return schema; } - - internal class MyCustomScalarGraphType : IntGraphType - { - //public override object? ParseValue(object? value) - //{ - // return true; - //} - } } #endif \ No newline at end of file diff --git a/src/WireMock.Net/Matchers/Models/WireMockCustomScalarGraphType.cs b/src/WireMock.Net/Matchers/Models/WireMockCustomScalarGraphType.cs new file mode 100644 index 000000000..44d8bd7fa --- /dev/null +++ b/src/WireMock.Net/Matchers/Models/WireMockCustomScalarGraphType.cs @@ -0,0 +1,30 @@ +#if GRAPHQL +using System; +using GraphQL.Types; + +namespace WireMock.Matchers.Models; + +/// +public abstract class WireMockCustomScalarGraphType : ScalarGraphType +{ + /// + public override object? ParseValue(object? value) + { + switch (value) + { + case null: + return null; + + case T: + return value; + } + + if (value is string && typeof(T) != typeof(string)) + { + throw new InvalidCastException($"Unable to convert value '{value}' of type '{typeof(string)}' to type '{typeof(T)}'."); + } + + return (T)Convert.ChangeType(value, typeof(T)); + } +} +#endif \ No newline at end of file diff --git a/src/WireMock.Net/Matchers/Request/RequestMessageGraphQLMatcher.cs b/src/WireMock.Net/Matchers/Request/RequestMessageGraphQLMatcher.cs index 96c2df0aa..70b9938be 100644 --- a/src/WireMock.Net/Matchers/Request/RequestMessageGraphQLMatcher.cs +++ b/src/WireMock.Net/Matchers/Request/RequestMessageGraphQLMatcher.cs @@ -37,7 +37,7 @@ public RequestMessageGraphQLMatcher(MatchBehaviour matchBehaviour, string schema /// The match behaviour. /// The schema. public RequestMessageGraphQLMatcher(MatchBehaviour matchBehaviour, GraphQL.Types.ISchema schema) : - this(CreateMatcherArray(matchBehaviour, new AnyOfTypes.AnyOf(schema))) + this(CreateMatcherArray(matchBehaviour, new AnyOfTypes.AnyOf(schema))) { } #endif @@ -89,7 +89,7 @@ private IReadOnlyList CalculateMatchResults(IRequestMessage request } #if GRAPHQL - private static IMatcher[] CreateMatcherArray(MatchBehaviour matchBehaviour, AnyOfTypes.AnyOf schema) + private static IMatcher[] CreateMatcherArray(MatchBehaviour matchBehaviour, AnyOfTypes.AnyOf schema) { return new[] { new GraphQLMatcher(schema, matchBehaviour) }.Cast().ToArray(); } diff --git a/src/WireMock.Net/Util/ReflectionUtils.cs b/src/WireMock.Net/Util/ReflectionUtils.cs index c5f535c68..f84d32dec 100644 --- a/src/WireMock.Net/Util/ReflectionUtils.cs +++ b/src/WireMock.Net/Util/ReflectionUtils.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Concurrent; +using System.Linq; using System.Reflection; using System.Reflection.Emit; @@ -9,9 +10,19 @@ internal static class ReflectionUtils { private const string DynamicModuleName = "WireMockDynamicModule"; private static readonly AssemblyName AssemblyName = new("WireMockDynamicAssembly"); + private const TypeAttributes ClassAttributes = + TypeAttributes.Public | + TypeAttributes.Class | + TypeAttributes.AutoClass | + TypeAttributes.AnsiClass | + TypeAttributes.BeforeFieldInit | + TypeAttributes.AutoLayout; private static readonly ConcurrentDictionary TypeCache = new(); + /* + , + */ - public static Type CreateType(string typeName, Type baseType) + public static Type CreateType(string typeName, Type? parentType = null) { // Check if the type has already been created if (TypeCache.TryGetValue(typeName, out var existingType)) @@ -22,7 +33,7 @@ public static Type CreateType(string typeName, Type baseType) var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(AssemblyName, AssemblyBuilderAccess.Run); var moduleBuilder = assemblyBuilder.DefineDynamicModule(DynamicModuleName); - var typeBuilder = moduleBuilder.DefineType(typeName, TypeAttributes.Public, baseType); + var typeBuilder = moduleBuilder.DefineType(typeName, ClassAttributes, parentType); // Create the type and cache it var newType = typeBuilder.CreateTypeInfo().AsType(); @@ -30,4 +41,21 @@ public static Type CreateType(string typeName, Type baseType) return newType; } + + public static Type CreateGenericType(string typeName, Type genericTypeDefinition, params Type[] typeArguments) + { + var key = $"{typeName}_{genericTypeDefinition.Name}_{string.Join(", ", typeArguments.Select(t => t.Name))}"; + if (TypeCache.TryGetValue(key, out var existingType)) + { + return existingType; + } + + var genericType = genericTypeDefinition.MakeGenericType(typeArguments); + + // Create the type based on the genericType and cache it + var newType = CreateType(typeName, genericType); + TypeCache[key] = newType; + + return newType; + } } \ No newline at end of file diff --git a/test/WireMock.Net.Tests/Matchers/GraphQLMatcherTests.cs b/test/WireMock.Net.Tests/Matchers/GraphQLMatcherTests.cs index 3ecbe0562..0bc2a983e 100644 --- a/test/WireMock.Net.Tests/Matchers/GraphQLMatcherTests.cs +++ b/test/WireMock.Net.Tests/Matchers/GraphQLMatcherTests.cs @@ -1,5 +1,6 @@ #if GRAPHQL using System; +using System.Collections.Generic; using FluentAssertions; using GraphQLParser.Exceptions; using WireMock.Matchers; @@ -120,8 +121,10 @@ type Mutation { ""variables"": { ""x"": 100, ""dt"": ""2007-12-03T10:15:30Z"" } }"; + var customScalars = new Dictionary { { "MyCustomScalar", typeof(int) } }; + // Act - var matcher = new GraphQLMatcher(testSchema); + var matcher = new GraphQLMatcher(testSchema, customScalars); var result = matcher.IsMatch(input); // Assert diff --git a/test/WireMock.Net.Tests/Matchers/Models/WireMockCustomScalarGraphTypeTests.cs b/test/WireMock.Net.Tests/Matchers/Models/WireMockCustomScalarGraphTypeTests.cs new file mode 100644 index 000000000..574c1e4f9 --- /dev/null +++ b/test/WireMock.Net.Tests/Matchers/Models/WireMockCustomScalarGraphTypeTests.cs @@ -0,0 +1,96 @@ +#if GRAPHQL +using System; +using FluentAssertions; +using WireMock.Matchers.Models; +using Xunit; + +namespace WireMock.Net.Tests.Matchers.Models; + +public class WireMockCustomScalarGraphTypeTests +{ + private class MyIntScalarGraphType : WireMockCustomScalarGraphType { } + + private class MyStringScalarGraphType : WireMockCustomScalarGraphType { } + + [Fact] + public void ParseValue_ShouldReturnNull_When_ValueIsNull() + { + // Arrange + var intGraphType = new MyIntScalarGraphType(); + + // Act + var result = intGraphType.ParseValue(null); + + // Assert + result.Should().BeNull(); + } + + [Fact] + public void ParseValue_ShouldReturnValue_When_ValueIsOfCorrectType() + { + // Arrange + var intGraphType = new MyIntScalarGraphType(); + + // Act + var result = intGraphType.ParseValue(5); + + // Assert + result.Should().Be(5); + } + + [Theory] + [InlineData("someString")] + [InlineData("100")] + public void ParseValue_ShouldThrowInvalidCastException_When_ValueIsStringAndTargetIsNotString(string stringValue) + { + // Arrange + var intGraphType = new MyIntScalarGraphType(); + + // Act + Action act = () => intGraphType.ParseValue(stringValue); + + // Assert + act.Should().Throw() + .WithMessage("Unable to convert value '*' of type 'System.String' to type 'System.Int32'."); + } + + [Fact] + public void ParseValue_ShouldConvertValue_WhenTypeIsConvertible() + { + // Arrange + var intGraphType = new MyIntScalarGraphType(); + + // Act + var result = intGraphType.ParseValue(5L); + + // Assert + result.Should().Be(5); + } + + [Fact] + public void ParseValue_ShouldThrowException_When_ValueIsMaxLongAndTargetIsInt() + { + // Arrange + var intGraphType = new MyIntScalarGraphType(); + + // Act + Action act = () => intGraphType.ParseValue(long.MaxValue); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void ParseValue_ShouldReturnStringValue_When_TypeIsString() + { + // Arrange + var stringGraphType = new MyStringScalarGraphType(); + + // Act + var result = stringGraphType.ParseValue("someString"); + + // Assert + result.Should().Be("someString"); + } +} +#endif \ No newline at end of file From 19b14ae817aaf2d32ae282eb0c5edf147b4134b2 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Wed, 18 Oct 2023 20:10:43 +0200 Subject: [PATCH 3/7] more tests --- src/WireMock.Net/Matchers/GraphQLMatcher.cs | 2 +- .../Matchers/GraphQLMatcherTests.cs | 25 +++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/WireMock.Net/Matchers/GraphQLMatcher.cs b/src/WireMock.Net/Matchers/GraphQLMatcher.cs index 11e179590..c2463059f 100644 --- a/src/WireMock.Net/Matchers/GraphQLMatcher.cs +++ b/src/WireMock.Net/Matchers/GraphQLMatcher.cs @@ -186,7 +186,7 @@ private ISchema BuildSchema(string typeDefinitions) foreach (var scalarTypeDefinition in scalarTypeDefinitions) { var customScalarGraphTypeName = $"{scalarTypeDefinition.Name.StringValue}GraphType"; - if (graphTypes.All(t => t.Name != customScalarGraphTypeName)) // Only process when not built-in. + if (graphTypes.TrueForAll(t => t.Name != customScalarGraphTypeName)) // Only process when not built-in. { // Check if this custom Scalar is defined in the dictionary if (CustomScalars == null || !CustomScalars.TryGetValue(scalarTypeDefinition.Name.StringValue, out var clrType)) diff --git a/test/WireMock.Net.Tests/Matchers/GraphQLMatcherTests.cs b/test/WireMock.Net.Tests/Matchers/GraphQLMatcherTests.cs index 0bc2a983e..724c47ac1 100644 --- a/test/WireMock.Net.Tests/Matchers/GraphQLMatcherTests.cs +++ b/test/WireMock.Net.Tests/Matchers/GraphQLMatcherTests.cs @@ -1,8 +1,10 @@ #if GRAPHQL using System; using System.Collections.Generic; +using CSScripting; using FluentAssertions; using GraphQLParser.Exceptions; +using WireMock.Exceptions; using WireMock.Matchers; using WireMock.Models; using Xunit; @@ -133,6 +135,29 @@ type Mutation { matcher.GetPatterns().Should().Contain(testSchema); } + [Fact] + public void GraphQLMatcher_For_ValidSchema_And_CorrectGraphQL_UsingCustomType_But_NoDefinedCustomScalars_Mutation_IsNoMatch() + { + // Arrange + const string testSchema = @" + scalar DateTime + scalar MyCustomScalar + + type Message { + id: ID! + } + + type Mutation { + createMessage(x: MyCustomScalar, dt: DateTime): Message + }"; + + // Act + Action action = () => _ = new GraphQLMatcher(testSchema); + + // Assert + action.Should().Throw().WithMessage("The GraphQL Scalar type 'MyCustomScalar' is not defined in the CustomScalars dictionary."); + } + [Fact] public void GraphQLMatcher_For_ValidSchema_And_IncorrectQuery_IsMismatch() { From 10ef12904c9e32e950b477ce7f33523c54ffed68 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Wed, 18 Oct 2023 20:21:31 +0200 Subject: [PATCH 4/7] . --- src/WireMock.Net/Matchers/GraphQLMatcher.cs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/WireMock.Net/Matchers/GraphQLMatcher.cs b/src/WireMock.Net/Matchers/GraphQLMatcher.cs index c2463059f..299db77af 100644 --- a/src/WireMock.Net/Matchers/GraphQLMatcher.cs +++ b/src/WireMock.Net/Matchers/GraphQLMatcher.cs @@ -174,24 +174,23 @@ private ISchema BuildSchema(string typeDefinitions) var schema = Schema.For(typeDefinitions); // #984 - var graphTypes = schema.BuiltInTypeMappings.Select(tm => tm.graphType).ToList(); - schema.RegisterTypes(graphTypes.ToArray()); + var graphTypes = schema.BuiltInTypeMappings.Select(tm => tm.graphType).ToArray(); + schema.RegisterTypes(graphTypes); var doc = Parser.Parse(typeDefinitions); var scalarTypeDefinitions = doc.Definitions .Where(d => d.Kind == ASTNodeKind.ScalarTypeDefinition) - .OfType() - .ToArray(); + .OfType(); - foreach (var scalarTypeDefinition in scalarTypeDefinitions) + foreach (var scalarTypeDefinitionName in scalarTypeDefinitions.Select(s => s.Name.StringValue)) { - var customScalarGraphTypeName = $"{scalarTypeDefinition.Name.StringValue}GraphType"; - if (graphTypes.TrueForAll(t => t.Name != customScalarGraphTypeName)) // Only process when not built-in. + var customScalarGraphTypeName = $"{scalarTypeDefinitionName}GraphType"; + if (graphTypes.All(t => t.Name != customScalarGraphTypeName)) // Only process when not built-in. { // Check if this custom Scalar is defined in the dictionary - if (CustomScalars == null || !CustomScalars.TryGetValue(scalarTypeDefinition.Name.StringValue, out var clrType)) + if (CustomScalars == null || !CustomScalars.TryGetValue(scalarTypeDefinitionName, out var clrType)) { - throw new WireMockException($"The GraphQL Scalar type '{scalarTypeDefinition.Name.StringValue}' is not defined in the CustomScalars dictionary."); + 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) From 4496e124ff6d5ccc141c2394d0b77ab36820e245 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Thu, 19 Oct 2023 08:05:43 +0200 Subject: [PATCH 5/7] add or set --- src/WireMock.Net/Util/ReflectionUtils.cs | 41 ++++++++---------------- 1 file changed, 14 insertions(+), 27 deletions(-) diff --git a/src/WireMock.Net/Util/ReflectionUtils.cs b/src/WireMock.Net/Util/ReflectionUtils.cs index f84d32dec..0fcad1761 100644 --- a/src/WireMock.Net/Util/ReflectionUtils.cs +++ b/src/WireMock.Net/Util/ReflectionUtils.cs @@ -18,44 +18,31 @@ internal static class ReflectionUtils TypeAttributes.BeforeFieldInit | TypeAttributes.AutoLayout; private static readonly ConcurrentDictionary TypeCache = new(); - /* - , - */ public static Type CreateType(string typeName, Type? parentType = null) { - // Check if the type has already been created - if (TypeCache.TryGetValue(typeName, out var existingType)) + return TypeCache.GetOrAdd(typeName, key => { - return existingType; - } + var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(AssemblyName, AssemblyBuilderAccess.Run); + var moduleBuilder = assemblyBuilder.DefineDynamicModule(DynamicModuleName); - var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(AssemblyName, AssemblyBuilderAccess.Run); - var moduleBuilder = assemblyBuilder.DefineDynamicModule(DynamicModuleName); + var typeBuilder = moduleBuilder.DefineType(key, ClassAttributes, parentType); - var typeBuilder = moduleBuilder.DefineType(typeName, ClassAttributes, parentType); - - // Create the type and cache it - var newType = typeBuilder.CreateTypeInfo().AsType(); - TypeCache[typeName] = newType; - - return newType; + // Create the type and cache it + return typeBuilder.CreateTypeInfo()!.AsType(); + }); } public static Type CreateGenericType(string typeName, Type genericTypeDefinition, params Type[] typeArguments) { - var key = $"{typeName}_{genericTypeDefinition.Name}_{string.Join(", ", typeArguments.Select(t => t.Name))}"; - if (TypeCache.TryGetValue(key, out var existingType)) - { - return existingType; - } + var genericKey = $"{typeName}_{genericTypeDefinition.Name}_{string.Join(", ", typeArguments.Select(t => t.Name))}"; - var genericType = genericTypeDefinition.MakeGenericType(typeArguments); - - // Create the type based on the genericType and cache it - var newType = CreateType(typeName, genericType); - TypeCache[key] = newType; + return TypeCache.GetOrAdd(genericKey, _ => + { + var genericType = genericTypeDefinition.MakeGenericType(typeArguments); - return newType; + // Create the type based on the genericType and cache it + return CreateType(typeName, genericType); + }); } } \ No newline at end of file From 14db157d8ca018905e777c4ef547a5ca4c66b848 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Thu, 19 Oct 2023 19:45:52 +0200 Subject: [PATCH 6/7] ... --- .../MainApp.cs | 7 +- .../Admin/Mappings/MatcherModel.cs | 13 + src/WireMock.Net/Matchers/GraphQLMatcher.cs | 2 +- .../Request/RequestMessageGraphQLMatcher.cs | 22 +- .../RequestBuilders/IGraphQLRequestBuilder.cs | 20 + .../RequestBuilders/Request.WithGraphQL.cs | 16 + .../Serialization/MatcherMapper.cs | 10 +- .../RequestMessageGraphQLMatcherTests.cs | 2 +- .../Serialization/MatcherMapperTests.cs | 456 +++++++++++++++++- .../Serialization/MatcherModelMapperTests.cs | 406 ---------------- 10 files changed, 515 insertions(+), 439 deletions(-) delete mode 100644 test/WireMock.Net.Tests/Serialization/MatcherModelMapperTests.cs diff --git a/examples/WireMock.Net.Console.Net452.Classic/MainApp.cs b/examples/WireMock.Net.Console.Net452.Classic/MainApp.cs index 0b7218080..5076cc2ea 100644 --- a/examples/WireMock.Net.Console.Net452.Classic/MainApp.cs +++ b/examples/WireMock.Net.Console.Net452.Classic/MainApp.cs @@ -43,6 +43,9 @@ public class Todo public static class MainApp { private const string TestSchema = @" + scalar DateTime + scalar MyCustomScalar + input MessageInput { content: String author: String @@ -56,6 +59,7 @@ type Message { type Mutation { createMessage(input: MessageInput): Message + createAnotherMessage(x: MyCustomScalar, dt: DateTime): Message updateMessage(id: ID!, input: MessageInput): Message } @@ -168,11 +172,12 @@ public static void Run() // server.AllowPartialMapping(); #if GRAPHQL + var customScalars = new Dictionary { { "MyCustomScalar", typeof(int) } }; server .Given(Request.Create() .WithPath("/graphql") .UsingPost() - .WithGraphQLSchema(TestSchema) + .WithGraphQLSchema(TestSchema, customScalars) ) .RespondWith(Response.Create() .WithBody("GraphQL is ok") diff --git a/src/WireMock.Net.Abstractions/Admin/Mappings/MatcherModel.cs b/src/WireMock.Net.Abstractions/Admin/Mappings/MatcherModel.cs index ca22db85d..994b8d05b 100644 --- a/src/WireMock.Net.Abstractions/Admin/Mappings/MatcherModel.cs +++ b/src/WireMock.Net.Abstractions/Admin/Mappings/MatcherModel.cs @@ -1,3 +1,6 @@ +using System.Collections.Generic; +using System; + namespace WireMock.Admin.Mappings; /// @@ -75,6 +78,16 @@ public class MatcherModel #endregion #region XPathMatcher + /// + /// Array of namespace prefix and uri. (optional) + /// public XmlNamespace[]? XmlNamespaceMap { get; set; } #endregion + + #region GraphQLMatcher + /// + /// Mapping of custom GraphQL Scalar name to ClrType. (optional) + /// + public IDictionary? CustomScalars { get; set; } + #endregion } \ No newline at end of file diff --git a/src/WireMock.Net/Matchers/GraphQLMatcher.cs b/src/WireMock.Net/Matchers/GraphQLMatcher.cs index 299db77af..fb3b1567e 100644 --- a/src/WireMock.Net/Matchers/GraphQLMatcher.cs +++ b/src/WireMock.Net/Matchers/GraphQLMatcher.cs @@ -63,7 +63,7 @@ public GraphQLMatcher( /// Initializes a new instance of the class. /// /// The schema. - /// A dictionary defining the custom scalars used in this schema. + /// A dictionary defining the custom scalars used in this schema. (optional) /// The match behaviour. (default = "AcceptOnMatch") /// The to use. (default = "Or") public GraphQLMatcher( diff --git a/src/WireMock.Net/Matchers/Request/RequestMessageGraphQLMatcher.cs b/src/WireMock.Net/Matchers/Request/RequestMessageGraphQLMatcher.cs index 70b9938be..0250deb97 100644 --- a/src/WireMock.Net/Matchers/Request/RequestMessageGraphQLMatcher.cs +++ b/src/WireMock.Net/Matchers/Request/RequestMessageGraphQLMatcher.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using Stef.Validation; @@ -25,8 +26,9 @@ public class RequestMessageGraphQLMatcher : IRequestMatcher /// /// The match behaviour. /// The schema. - public RequestMessageGraphQLMatcher(MatchBehaviour matchBehaviour, string schema) : - this(CreateMatcherArray(matchBehaviour, schema)) + /// A dictionary defining the custom scalars used in this schema. (optional) + public RequestMessageGraphQLMatcher(MatchBehaviour matchBehaviour, string schema, IDictionary? customScalars = null) : + this(CreateMatcherArray(matchBehaviour, schema, customScalars)) { } @@ -36,8 +38,9 @@ public RequestMessageGraphQLMatcher(MatchBehaviour matchBehaviour, string schema /// /// The match behaviour. /// The schema. - public RequestMessageGraphQLMatcher(MatchBehaviour matchBehaviour, GraphQL.Types.ISchema schema) : - this(CreateMatcherArray(matchBehaviour, new AnyOfTypes.AnyOf(schema))) + /// A dictionary defining the custom scalars used in this schema. (optional) + public RequestMessageGraphQLMatcher(MatchBehaviour matchBehaviour, GraphQL.Types.ISchema schema, IDictionary? customScalars = null) : + this(CreateMatcherArray(matchBehaviour, new AnyOfTypes.AnyOf(schema), customScalars)) { } #endif @@ -89,12 +92,17 @@ private IReadOnlyList CalculateMatchResults(IRequestMessage request } #if GRAPHQL - private static IMatcher[] CreateMatcherArray(MatchBehaviour matchBehaviour, AnyOfTypes.AnyOf schema) + private static IMatcher[] CreateMatcherArray( + MatchBehaviour matchBehaviour, + AnyOfTypes.AnyOf schema, + IDictionary? customScalars + ) { - return new[] { new GraphQLMatcher(schema, matchBehaviour) }.Cast().ToArray(); + return new[] { new GraphQLMatcher(schema, customScalars, matchBehaviour) }.Cast().ToArray(); } #else - private static IMatcher[] CreateMatcherArray(MatchBehaviour matchBehaviour, object schema) + private static IMatcher[] CreateMatcherArray(MatchBehaviour matchBehaviour, object schema, IDictionary? customScalars) { throw new System.NotSupportedException("The GrapQLMatcher can not be used for .NETStandard1.3 or .NET Framework 4.6.1 or lower."); } diff --git a/src/WireMock.Net/RequestBuilders/IGraphQLRequestBuilder.cs b/src/WireMock.Net/RequestBuilders/IGraphQLRequestBuilder.cs index 6d23d2ddd..d22401a41 100644 --- a/src/WireMock.Net/RequestBuilders/IGraphQLRequestBuilder.cs +++ b/src/WireMock.Net/RequestBuilders/IGraphQLRequestBuilder.cs @@ -1,3 +1,5 @@ +using System; +using System.Collections.Generic; using WireMock.Matchers; namespace WireMock.RequestBuilders; @@ -15,6 +17,15 @@ public interface IGraphQLRequestBuilder : IMultiPartRequestBuilder /// The . IRequestBuilder WithGraphQLSchema(string schema, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch); + /// + /// WithGraphQLSchema: The GraphQL schema as a string. + /// + /// The GraphQL schema. + /// A dictionary defining the custom scalars used in this schema. (optional) + /// The match behaviour. (Default is MatchBehaviour.AcceptOnMatch). + /// The . + IRequestBuilder WithGraphQLSchema(string schema, IDictionary? customScalars, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch); + #if GRAPHQL /// /// WithGraphQLSchema: The GraphQL schema as a ISchema. @@ -23,5 +34,14 @@ public interface IGraphQLRequestBuilder : IMultiPartRequestBuilder /// The match behaviour. (Default is MatchBehaviour.AcceptOnMatch). /// The . IRequestBuilder WithGraphQLSchema(GraphQL.Types.ISchema schema, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch); + + /// + /// WithGraphQLSchema: The GraphQL schema as a ISchema. + /// + /// The GraphQL schema. + /// A dictionary defining the custom scalars used in this schema. (optional) + /// The match behaviour. (Default is MatchBehaviour.AcceptOnMatch). + /// The . + IRequestBuilder WithGraphQLSchema(GraphQL.Types.ISchema schema, IDictionary? customScalars, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch); #endif } \ No newline at end of file diff --git a/src/WireMock.Net/RequestBuilders/Request.WithGraphQL.cs b/src/WireMock.Net/RequestBuilders/Request.WithGraphQL.cs index 15a912d82..164ba3327 100644 --- a/src/WireMock.Net/RequestBuilders/Request.WithGraphQL.cs +++ b/src/WireMock.Net/RequestBuilders/Request.WithGraphQL.cs @@ -1,3 +1,5 @@ +using System.Collections.Generic; +using System; using WireMock.Matchers; using WireMock.Matchers.Request; @@ -12,6 +14,13 @@ public IRequestBuilder WithGraphQLSchema(string schema, MatchBehaviour matchBeha return this; } + /// + public IRequestBuilder WithGraphQLSchema(string schema, IDictionary? customScalars, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch) + { + _requestMatchers.Add(new RequestMessageGraphQLMatcher(matchBehaviour, schema, customScalars)); + return this; + } + #if GRAPHQL /// public IRequestBuilder WithGraphQLSchema(GraphQL.Types.ISchema schema, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch) @@ -19,5 +28,12 @@ public IRequestBuilder WithGraphQLSchema(GraphQL.Types.ISchema schema, MatchBeha _requestMatchers.Add(new RequestMessageGraphQLMatcher(matchBehaviour, schema)); return this; } + + /// + public IRequestBuilder WithGraphQLSchema(GraphQL.Types.ISchema schema, IDictionary? customScalars, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch) + { + _requestMatchers.Add(new RequestMessageGraphQLMatcher(matchBehaviour, schema, customScalars)); + return this; + } #endif } \ No newline at end of file diff --git a/src/WireMock.Net/Serialization/MatcherMapper.cs b/src/WireMock.Net/Serialization/MatcherMapper.cs index 0ba6b168a..d70de60e3 100644 --- a/src/WireMock.Net/Serialization/MatcherMapper.cs +++ b/src/WireMock.Net/Serialization/MatcherMapper.cs @@ -72,7 +72,7 @@ public MatcherMapper(WireMockServerSettings settings) return CreateExactObjectMatcher(matchBehaviour, stringPatterns[0]); #if GRAPHQL case nameof(GraphQLMatcher): - return new GraphQLMatcher(stringPatterns[0].GetPattern(), matchBehaviour, matchOperator); + return new GraphQLMatcher(stringPatterns[0].GetPattern(), matcher.CustomScalars, matchBehaviour, matchOperator); #endif #if MIMEKIT @@ -101,8 +101,7 @@ public MatcherMapper(WireMockServerSettings settings) return new JmesPathMatcher(matchBehaviour, matchOperator, stringPatterns); case nameof(XPathMatcher): - var xmlNamespaces = matcher.XmlNamespaceMap; - return new XPathMatcher(matchBehaviour, matchOperator, xmlNamespaces, stringPatterns); + return new XPathMatcher(matchBehaviour, matchOperator, matcher.XmlNamespaceMap, stringPatterns); case nameof(WildcardMatcher): return new WildcardMatcher(matchBehaviour, stringPatterns, ignoreCase, matchOperator); @@ -164,6 +163,11 @@ public MatcherMapper(WireMockServerSettings settings) case XPathMatcher xpathMatcher: model.XmlNamespaceMap = xpathMatcher.XmlNamespaceMap; break; +#if GRAPHQL + case GraphQLMatcher graphQLMatcher: + model.CustomScalars = graphQLMatcher.CustomScalars; + break; +#endif } switch (matcher) diff --git a/test/WireMock.Net.Tests/RequestMatchers/RequestMessageGraphQLMatcherTests.cs b/test/WireMock.Net.Tests/RequestMatchers/RequestMessageGraphQLMatcherTests.cs index dbddf918d..570b54a34 100644 --- a/test/WireMock.Net.Tests/RequestMatchers/RequestMessageGraphQLMatcherTests.cs +++ b/test/WireMock.Net.Tests/RequestMatchers/RequestMessageGraphQLMatcherTests.cs @@ -16,7 +16,7 @@ public class RequestMessageGraphQLMatcherTests [Fact] public void RequestMessageGraphQLMatcher_GetMatchingScore_BodyAsString_IStringMatcher() { - // Assign + // Arrange var body = new BodyData { BodyAsString = "b", diff --git a/test/WireMock.Net.Tests/Serialization/MatcherMapperTests.cs b/test/WireMock.Net.Tests/Serialization/MatcherMapperTests.cs index 8cc7f9e68..092711e2b 100644 --- a/test/WireMock.Net.Tests/Serialization/MatcherMapperTests.cs +++ b/test/WireMock.Net.Tests/Serialization/MatcherMapperTests.cs @@ -1,9 +1,13 @@ using System; +using System.Collections.Generic; using AnyOfTypes; using FluentAssertions; +using FluentAssertions.Execution; using Moq; +using Newtonsoft.Json; using NFluent; using WireMock.Admin.Mappings; +using WireMock.Handlers; using WireMock.Matchers; using WireMock.Models; using WireMock.Serialization; @@ -165,6 +169,36 @@ public void MatcherMapper_Map_XPathMatcher() model.XmlNamespaceMap.Should().BeEquivalentTo(xmlNamespaceMap); } +#if GRAPHQL + [Fact] + public void MatcherMapper_Map_GraphQLMatcher() + { + // Assign + const string testSchema = @" + scalar DateTime + scalar MyCustomScalar + + type Message { + id: ID! + } + + type Mutation { + createMessage(x: MyCustomScalar, dt: DateTime): Message + }"; + + var customScalars = new Dictionary { { "MyCustomScalar", typeof(string) } }; + var matcher = new GraphQLMatcher(testSchema, customScalars); + + // Act + var model = _sut.Map(matcher)!; + + // Assert + model.Name.Should().Be(nameof(GraphQLMatcher)); + model.Pattern.Should().Be(testSchema); + model.CustomScalars.Should().BeEquivalentTo(customScalars); + } +#endif + [Fact] public void MatcherMapper_Map_MatcherModel_Null() { @@ -463,26 +497,6 @@ public void MatcherMapper_Map_MatcherModel_JsonPartialWildcardMatcher_Patterns_A matcher.Regex.Should().BeFalse(); } - [Fact] - public void MatcherMapper_Map_MatcherModel_ExactMatcher_Pattern() - { - // Assign - var model = new MatcherModel - { - Name = "ExactMatcher", - Pattern = "p", - IgnoreCase = true - }; - - // Act - var matcher = (ExactMatcher)_sut.Map(model)!; - - // Assert - matcher.MatchBehaviour.Should().Be(MatchBehaviour.AcceptOnMatch); - matcher.GetPatterns().Should().Contain("p"); - matcher.IgnoreCase.Should().BeTrue(); - } - [Fact] public void MatcherMapper_Map_MatcherModel_NotNullOrEmptyMatcher() { @@ -585,4 +599,406 @@ public void MatcherMapper_Map_MatcherModel_XPathMatcher_WithoutXmlNamespaces_As_ matcher.MatchBehaviour.Should().Be(MatchBehaviour.AcceptOnMatch); matcher.XmlNamespaceMap.Should().BeNull(); } + + [Fact] + public void MatcherMapper_Map_MatcherModel_CSharpCodeMatcher() + { + // Assign + var model = new MatcherModel + { + Name = "CSharpCodeMatcher", + Patterns = new[] { "return it == \"x\";" } + }; + var sut = new MatcherMapper(new WireMockServerSettings { AllowCSharpCodeMatcher = true }); + + // Act 1 + var matcher1 = (ICSharpCodeMatcher)sut.Map(model)!; + + // Assert 1 + matcher1.Should().NotBeNull(); + matcher1.IsMatch("x").Score.Should().Be(1.0d); + + // Act 2 + var matcher2 = (ICSharpCodeMatcher)sut.Map(model)!; + + // Assert 2 + matcher2.Should().NotBeNull(); + matcher2.IsMatch("x").Score.Should().Be(1.0d); + } + + [Fact] + public void MatcherMapper_Map_MatcherModel_CSharpCodeMatcher_NotAllowed_ThrowsException() + { + // Assign + var model = new MatcherModel + { + Name = "CSharpCodeMatcher", + Patterns = new[] { "x" } + }; + var sut = new MatcherMapper(new WireMockServerSettings { AllowCSharpCodeMatcher = false }); + + // Act + Action action = () => sut.Map(model); + + // Assert + action.Should().Throw(); + } + + [Fact] + public void MatcherMapper_Map_MatcherModel_ExactMatcher_Pattern() + { + // Assign + var model = new MatcherModel + { + Name = "ExactMatcher", + Patterns = new[] { "x" } + }; + + // Act + var matcher = (ExactMatcher)_sut.Map(model)!; + + // Assert + matcher.GetPatterns().Should().ContainSingle("x"); + } + + [Fact] + public void MatcherMapper_Map_MatcherModel_ExactMatcher_Patterns() + { + // Assign + var model = new MatcherModel + { + Name = "ExactMatcher", + Patterns = new[] { "x", "y" } + }; + + // Act + var matcher = (ExactMatcher)_sut.Map(model)!; + + // Assert + Check.That(matcher.GetPatterns()).ContainsExactly("x", "y"); + } + + [Fact] + public void MatcherMapper_Map_MatcherModel_JsonPartialMatcher_RegexFalse() + { + // Assign + var pattern = "{ \"x\": 1 }"; + var model = new MatcherModel + { + Name = "JsonPartialMatcher", + Regex = false, + Pattern = pattern + }; + + // Act + var matcher = (JsonPartialMatcher)_sut.Map(model)!; + + // Assert + matcher.MatchBehaviour.Should().Be(MatchBehaviour.AcceptOnMatch); + matcher.IgnoreCase.Should().BeFalse(); + matcher.Value.Should().Be(pattern); + matcher.Regex.Should().BeFalse(); + } + + [Fact] + public void MatcherMapper_Map_MatcherModel_JsonPartialMatcher_RegexTrue() + { + // Assign + var pattern = "{ \"x\": 1 }"; + var model = new MatcherModel + { + Name = "JsonPartialMatcher", + Regex = true, + Pattern = pattern + }; + + // Act + var matcher = (JsonPartialMatcher)_sut.Map(model)!; + + // Assert + matcher.MatchBehaviour.Should().Be(MatchBehaviour.AcceptOnMatch); + matcher.IgnoreCase.Should().BeFalse(); + matcher.Value.Should().Be(pattern); + matcher.Regex.Should().BeTrue(); + } + + [Fact] + public void MatcherMapper_Map_MatcherModel_ExactObjectMatcher_ValidBase64StringPattern() + { + // Assign + var model = new MatcherModel + { + Name = "ExactObjectMatcher", + Patterns = new object[] { "c3RlZg==" } + }; + + // Act + var matcher = (ExactObjectMatcher)_sut.Map(model)!; + + // Assert + Check.That(matcher.ValueAsBytes).ContainsExactly(new byte[] { 115, 116, 101, 102 }); + } + + [Fact] + public void MatcherMapper_Map_MatcherModel_ExactObjectMatcher_InvalidBase64StringPattern() + { + // Assign + var model = new MatcherModel + { + Name = "ExactObjectMatcher", + Patterns = new object[] { "_" } + }; + + // Act & Assert + Check.ThatCode(() => _sut.Map(model)).Throws(); + } + + [Theory] + [InlineData(MatchOperator.Or, 1.0d)] + [InlineData(MatchOperator.And, 0.0d)] + [InlineData(MatchOperator.Average, 0.5d)] + public void MatcherMapper_Map_MatcherModel_RegexMatcher(MatchOperator matchOperator, double expected) + { + // Assign + var model = new MatcherModel + { + Name = "RegexMatcher", + Patterns = new[] { "x", "y" }, + IgnoreCase = true, + MatchOperator = matchOperator.ToString() + }; + + // Act + var matcher = (RegexMatcher)_sut.Map(model)!; + + // Assert + Check.That(matcher.GetPatterns()).ContainsExactly("x", "y"); + + var result = matcher.IsMatch("X"); + result.Score.Should().Be(expected); + } + + [Theory] + [InlineData(MatchOperator.Or, 1.0d)] + [InlineData(MatchOperator.And, 0.0d)] + [InlineData(MatchOperator.Average, 0.5d)] + public void MatcherMapper_Map_MatcherModel_WildcardMatcher_IgnoreCase(MatchOperator matchOperator, double expected) + { + // Assign + var model = new MatcherModel + { + Name = "WildcardMatcher", + Patterns = new[] { "x", "y" }, + IgnoreCase = true, + MatchOperator = matchOperator.ToString() + }; + + // Act + var matcher = (WildcardMatcher)_sut.Map(model)!; + + // Assert + Check.That(matcher.GetPatterns()).ContainsExactly("x", "y"); + + var result = matcher.IsMatch("X"); + result.Score.Should().Be(expected); + } + + [Fact] + public void MatcherMapper_Map_MatcherModel_WildcardMatcher_With_PatternAsFile() + { + // Arrange + var file = "c:\\test.txt"; + var fileContent = "c"; + var stringPattern = new StringPattern + { + Pattern = fileContent, + PatternAsFile = file + }; + var fileSystemHandleMock = new Mock(); + fileSystemHandleMock.Setup(f => f.ReadFileAsString(file)).Returns(fileContent); + + var model = new MatcherModel + { + Name = "WildcardMatcher", + PatternAsFile = file + }; + + var settings = new WireMockServerSettings + { + FileSystemHandler = fileSystemHandleMock.Object + }; + var sut = new MatcherMapper(settings); + + // Act + var matcher = (WildcardMatcher)sut.Map(model)!; + + // Assert + matcher.GetPatterns().Should().HaveCount(1).And.Contain(new AnyOf(stringPattern)); + + var result = matcher.IsMatch("c"); + result.Score.Should().Be(MatchScores.Perfect); + } + + [Fact] + public void MatcherMapper_Map_MatcherModel_SimMetricsMatcher() + { + // Assign + var model = new MatcherModel + { + Name = "SimMetricsMatcher", + Pattern = "x" + }; + + // Act + var matcher = (SimMetricsMatcher)_sut.Map(model)!; + + // Assert + Check.That(matcher.GetPatterns()).ContainsExactly("x"); + } + + [Fact] + public void MatcherMapper_Map_MatcherModel_SimMetricsMatcher_BlockDistance() + { + // Assign + var model = new MatcherModel + { + Name = "SimMetricsMatcher.BlockDistance", + Pattern = "x" + }; + + // Act + var matcher = (SimMetricsMatcher)_sut.Map(model)!; + + // Assert + Check.That(matcher.GetPatterns()).ContainsExactly("x"); + } + + [Fact] + public void MatcherMapper_Map_MatcherModel_SimMetricsMatcher_Throws1() + { + // Assign + var model = new MatcherModel + { + Name = "error", + Pattern = "x" + }; + + // Act + Check.ThatCode(() => _sut.Map(model)).Throws(); + } + + [Fact] + public void MatcherMapper_Map_MatcherModel_SimMetricsMatcher_Throws2() + { + // Assign + var model = new MatcherModel + { + Name = "SimMetricsMatcher.error", + Pattern = "x" + }; + + // Act + Check.ThatCode(() => _sut.Map(model)).Throws(); + } + + [Fact] + public void MatcherMapper_Map_MatcherModel_MatcherModelToCustomMatcher() + { + // Arrange + var patternModel = new CustomPathParamMatcherModel("/customer/{customerId}/document/{documentId}", + new Dictionary(2) + { + { "customerId", @"^[0-9]+$" }, + { "documentId", @"^[0-9a-zA-Z\-\_]+\.[a-zA-Z]+$" } + }); + var model = new MatcherModel + { + Name = nameof(CustomPathParamMatcher), + Pattern = JsonConvert.SerializeObject(patternModel) + }; + + var settings = new WireMockServerSettings(); + settings.CustomMatcherMappings = settings.CustomMatcherMappings ?? new Dictionary>(); + settings.CustomMatcherMappings[nameof(CustomPathParamMatcher)] = matcherModel => + { + var matcherParams = JsonConvert.DeserializeObject((string)matcherModel.Pattern!)!; + return new CustomPathParamMatcher( + matcherModel.RejectOnMatch == true ? MatchBehaviour.RejectOnMatch : MatchBehaviour.AcceptOnMatch, + matcherParams.Path, + matcherParams.PathParams + ); + }; + var sut = new MatcherMapper(settings); + + // Act + var matcher = sut.Map(model) as CustomPathParamMatcher; + + // Assert + matcher.Should().NotBeNull(); + } + + [Fact] + public void MatcherMapper_Map_MatcherModel_CustomMatcherToMatcherModel() + { + // Arrange + var matcher = new CustomPathParamMatcher("/customer/{customerId}/document/{documentId}", + new Dictionary(2) + { + { "customerId", @"^[0-9]+$" }, + { "documentId", @"^[0-9a-zA-Z\-\_]+\.[a-zA-Z]+$" } + }); + + // Act + var model = _sut.Map(matcher)!; + + // Assert + using (new AssertionScope()) + { + model.Should().NotBeNull(); + model.Name.Should().Be(nameof(CustomPathParamMatcher)); + + var matcherParams = JsonConvert.DeserializeObject((string)model.Pattern!)!; + matcherParams.Path.Should().Be("/customer/{customerId}/document/{documentId}"); + matcherParams.PathParams.Should().BeEquivalentTo(new Dictionary(2) + { + { "customerId", @"^[0-9]+$" }, + { "documentId", @"^[0-9a-zA-Z\-\_]+\.[a-zA-Z]+$" } + }); + } + } + +#if GRAPHQL + [Fact] + public void MatcherMapper_Map_MatcherModel_GraphQLMatcher() + { + // Arrange + const string testSchema = @" + scalar DateTime + scalar MyCustomScalar + + type Message { + id: ID! + } + + type Mutation { + createMessage(x: MyCustomScalar, dt: DateTime): Message + }"; + + var customScalars = new Dictionary { { "MyCustomScalar", typeof(string) } }; + var model = new MatcherModel + { + Name = nameof(GraphQLMatcher), + Pattern = testSchema, + CustomScalars = customScalars + }; + + // Act + var matcher = (GraphQLMatcher)_sut.Map(model)!; + + // Assert + matcher.GetPatterns().Should().HaveElementAt(0, testSchema); + matcher.Name.Should().Be(nameof(GraphQLMatcher)); + matcher.CustomScalars.Should().BeEquivalentTo(customScalars); + } +#endif } \ No newline at end of file diff --git a/test/WireMock.Net.Tests/Serialization/MatcherModelMapperTests.cs b/test/WireMock.Net.Tests/Serialization/MatcherModelMapperTests.cs deleted file mode 100644 index 90ef352ca..000000000 --- a/test/WireMock.Net.Tests/Serialization/MatcherModelMapperTests.cs +++ /dev/null @@ -1,406 +0,0 @@ -using System; -using System.Collections.Generic; -using AnyOfTypes; -using FluentAssertions; -using FluentAssertions.Execution; -using Moq; -using Newtonsoft.Json; -using NFluent; -using WireMock.Admin.Mappings; -using WireMock.Handlers; -using WireMock.Matchers; -using WireMock.Models; -using WireMock.Serialization; -using WireMock.Settings; -using Xunit; - -namespace WireMock.Net.Tests.Serialization; - -public class MatcherModelMapperTests -{ - private readonly WireMockServerSettings _settings = new(); - - private readonly MatcherMapper _sut; - - public MatcherModelMapperTests() - { - _sut = new MatcherMapper(_settings); - } - - [Fact] - public void MatcherModelMapper_Map_CSharpCodeMatcher() - { - // Assign - var model = new MatcherModel - { - Name = "CSharpCodeMatcher", - Patterns = new[] { "return it == \"x\";" } - }; - var sut = new MatcherMapper(new WireMockServerSettings { AllowCSharpCodeMatcher = true }); - - // Act 1 - var matcher1 = (ICSharpCodeMatcher)sut.Map(model)!; - - // Assert 1 - matcher1.Should().NotBeNull(); - matcher1.IsMatch("x").Score.Should().Be(1.0d); - - // Act 2 - var matcher2 = (ICSharpCodeMatcher)sut.Map(model)!; - - // Assert 2 - matcher2.Should().NotBeNull(); - matcher2.IsMatch("x").Score.Should().Be(1.0d); - } - - [Fact] - public void MatcherModelMapper_Map_CSharpCodeMatcher_NotAllowed_ThrowsException() - { - // Assign - var model = new MatcherModel - { - Name = "CSharpCodeMatcher", - Patterns = new[] { "x" } - }; - var sut = new MatcherMapper(new WireMockServerSettings { AllowCSharpCodeMatcher = false }); - - // Act - Action action = () => sut.Map(model); - - // Assert - action.Should().Throw(); - } - - [Fact] - public void MatcherModelMapper_Map_Null() - { - // Act - IMatcher matcher = _sut.Map((MatcherModel?)null)!; - - // Assert - Check.That(matcher).IsNull(); - } - - [Fact] - public void MatcherModelMapper_Map_ExactMatcher_Pattern() - { - // Assign - var model = new MatcherModel - { - Name = "ExactMatcher", - Patterns = new[] { "x" } - }; - - // Act - var matcher = (ExactMatcher)_sut.Map(model)!; - - // Assert - matcher.GetPatterns().Should().ContainSingle("x"); - } - - [Fact] - public void MatcherModelMapper_Map_ExactMatcher_Patterns() - { - // Assign - var model = new MatcherModel - { - Name = "ExactMatcher", - Patterns = new[] { "x", "y" } - }; - - // Act - var matcher = (ExactMatcher)_sut.Map(model)!; - - // Assert - Check.That(matcher.GetPatterns()).ContainsExactly("x", "y"); - } - - [Fact] - public void MatcherModelMapper_Map_JsonPartialMatcher_RegexFalse() - { - // Assign - var pattern = "{ \"x\": 1 }"; - var model = new MatcherModel - { - Name = "JsonPartialMatcher", - Regex = false, - Pattern = pattern - }; - - // Act - var matcher = (JsonPartialMatcher)_sut.Map(model)!; - - // Assert - matcher.MatchBehaviour.Should().Be(MatchBehaviour.AcceptOnMatch); - matcher.IgnoreCase.Should().BeFalse(); - matcher.Value.Should().Be(pattern); - matcher.Regex.Should().BeFalse(); - } - - [Fact] - public void MatcherModelMapper_Map_JsonPartialMatcher_RegexTrue() - { - // Assign - var pattern = "{ \"x\": 1 }"; - var model = new MatcherModel - { - Name = "JsonPartialMatcher", - Regex = true, - Pattern = pattern - }; - - // Act - var matcher = (JsonPartialMatcher)_sut.Map(model)!; - - // Assert - matcher.MatchBehaviour.Should().Be(MatchBehaviour.AcceptOnMatch); - matcher.IgnoreCase.Should().BeFalse(); - matcher.Value.Should().Be(pattern); - matcher.Regex.Should().BeTrue(); - } - - [Fact] - public void MatcherModelMapper_Map_ExactObjectMatcher_ValidBase64StringPattern() - { - // Assign - var model = new MatcherModel - { - Name = "ExactObjectMatcher", - Patterns = new object[] { "c3RlZg==" } - }; - - // Act - var matcher = (ExactObjectMatcher)_sut.Map(model)!; - - // Assert - Check.That(matcher.ValueAsBytes).ContainsExactly(new byte[] { 115, 116, 101, 102 }); - } - - [Fact] - public void MatcherModelMapper_Map_ExactObjectMatcher_InvalidBase64StringPattern() - { - // Assign - var model = new MatcherModel - { - Name = "ExactObjectMatcher", - Patterns = new object[] { "_" } - }; - - // Act & Assert - Check.ThatCode(() => _sut.Map(model)).Throws(); - } - - [Theory] - [InlineData(MatchOperator.Or, 1.0d)] - [InlineData(MatchOperator.And, 0.0d)] - [InlineData(MatchOperator.Average, 0.5d)] - public void MatcherModelMapper_Map_RegexMatcher(MatchOperator matchOperator, double expected) - { - // Assign - var model = new MatcherModel - { - Name = "RegexMatcher", - Patterns = new[] { "x", "y" }, - IgnoreCase = true, - MatchOperator = matchOperator.ToString() - }; - - // Act - var matcher = (RegexMatcher)_sut.Map(model)!; - - // Assert - Check.That(matcher.GetPatterns()).ContainsExactly("x", "y"); - - var result = matcher.IsMatch("X"); - result.Score.Should().Be(expected); - } - - [Theory] - [InlineData(MatchOperator.Or, 1.0d)] - [InlineData(MatchOperator.And, 0.0d)] - [InlineData(MatchOperator.Average, 0.5d)] - public void MatcherModelMapper_Map_WildcardMatcher_IgnoreCase(MatchOperator matchOperator, double expected) - { - // Assign - var model = new MatcherModel - { - Name = "WildcardMatcher", - Patterns = new[] { "x", "y" }, - IgnoreCase = true, - MatchOperator = matchOperator.ToString() - }; - - // Act - var matcher = (WildcardMatcher)_sut.Map(model)!; - - // Assert - Check.That(matcher.GetPatterns()).ContainsExactly("x", "y"); - - var result = matcher.IsMatch("X"); - result.Score.Should().Be(expected); - } - - [Fact] - public void MatcherModelMapper_Map_WildcardMatcher_With_PatternAsFile() - { - // Arrange - var file = "c:\\test.txt"; - var fileContent = "c"; - var stringPattern = new StringPattern - { - Pattern = fileContent, - PatternAsFile = file - }; - var fileSystemHandleMock = new Mock(); - fileSystemHandleMock.Setup(f => f.ReadFileAsString(file)).Returns(fileContent); - - var model = new MatcherModel - { - Name = "WildcardMatcher", - PatternAsFile = file - }; - - var settings = new WireMockServerSettings - { - FileSystemHandler = fileSystemHandleMock.Object - }; - var sut = new MatcherMapper(settings); - - // Act - var matcher = (WildcardMatcher)sut.Map(model)!; - - // Assert - matcher.GetPatterns().Should().HaveCount(1).And.Contain(new AnyOf(stringPattern)); - - var result = matcher.IsMatch("c"); - result.Score.Should().Be(MatchScores.Perfect); - } - - [Fact] - public void MatcherModelMapper_Map_SimMetricsMatcher() - { - // Assign - var model = new MatcherModel - { - Name = "SimMetricsMatcher", - Pattern = "x" - }; - - // Act - var matcher = (SimMetricsMatcher)_sut.Map(model)!; - - // Assert - Check.That(matcher.GetPatterns()).ContainsExactly("x"); - } - - [Fact] - public void MatcherModelMapper_Map_SimMetricsMatcher_BlockDistance() - { - // Assign - var model = new MatcherModel - { - Name = "SimMetricsMatcher.BlockDistance", - Pattern = "x" - }; - - // Act - var matcher = (SimMetricsMatcher)_sut.Map(model)!; - - // Assert - Check.That(matcher.GetPatterns()).ContainsExactly("x"); - } - - [Fact] - public void MatcherModelMapper_Map_SimMetricsMatcher_Throws1() - { - // Assign - var model = new MatcherModel - { - Name = "error", - Pattern = "x" - }; - - // Act - Check.ThatCode(() => _sut.Map(model)).Throws(); - } - - [Fact] - public void MatcherModelMapper_Map_SimMetricsMatcher_Throws2() - { - // Assign - var model = new MatcherModel - { - Name = "SimMetricsMatcher.error", - Pattern = "x" - }; - - // Act - Check.ThatCode(() => _sut.Map(model)).Throws(); - } - - [Fact] - public void MatcherModelMapper_Map_MatcherModelToCustomMatcher() - { - // Arrange - var patternModel = new CustomPathParamMatcherModel("/customer/{customerId}/document/{documentId}", - new Dictionary(2) - { - { "customerId", @"^[0-9]+$" }, - { "documentId", @"^[0-9a-zA-Z\-\_]+\.[a-zA-Z]+$" } - }); - var model = new MatcherModel - { - Name = nameof(CustomPathParamMatcher), - Pattern = JsonConvert.SerializeObject(patternModel) - }; - - var settings = new WireMockServerSettings(); - settings.CustomMatcherMappings = settings.CustomMatcherMappings ?? new Dictionary>(); - settings.CustomMatcherMappings[nameof(CustomPathParamMatcher)] = matcherModel => - { - var matcherParams = JsonConvert.DeserializeObject((string)matcherModel.Pattern!)!; - return new CustomPathParamMatcher( - matcherModel.RejectOnMatch == true ? MatchBehaviour.RejectOnMatch : MatchBehaviour.AcceptOnMatch, - matcherParams.Path, - matcherParams.PathParams - ); - }; - var sut = new MatcherMapper(settings); - - // Act - var matcher = sut.Map(model) as CustomPathParamMatcher; - - // Assert - matcher.Should().NotBeNull(); - } - - [Fact] - public void MatcherModelMapper_Map_CustomMatcherToMatcherModel() - { - // Arrange - var matcher = new CustomPathParamMatcher("/customer/{customerId}/document/{documentId}", - new Dictionary(2) - { - { "customerId", @"^[0-9]+$" }, - { "documentId", @"^[0-9a-zA-Z\-\_]+\.[a-zA-Z]+$" } - }); - - // Act - var model = _sut.Map(matcher)!; - - // Assert - using (new AssertionScope()) - { - model.Should().NotBeNull(); - model.Name.Should().Be(nameof(CustomPathParamMatcher)); - - var matcherParams = JsonConvert.DeserializeObject((string)model.Pattern!)!; - matcherParams.Path.Should().Be("/customer/{customerId}/document/{documentId}"); - matcherParams.PathParams.Should().BeEquivalentTo(new Dictionary(2) - { - { "customerId", @"^[0-9]+$" }, - { "documentId", @"^[0-9a-zA-Z\-\_]+\.[a-zA-Z]+$" } - }); - } - } -} \ No newline at end of file From 20f8779d82cf5049dd479ecb3e8ee590e195b444 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Mon, 4 Dec 2023 17:41:50 +0100 Subject: [PATCH 7/7] x --- tools/MultipartUploader/Program.cs | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/tools/MultipartUploader/Program.cs b/tools/MultipartUploader/Program.cs index d365eab7d..590122433 100644 --- a/tools/MultipartUploader/Program.cs +++ b/tools/MultipartUploader/Program.cs @@ -1,17 +1,15 @@ -namespace MultipartUploader +namespace MultipartUploader; + +internal static class Program { - internal static class Program + /// + /// The main entry point for the application. + /// + [STAThread] + static void Main() { - /// - /// The main entry point for the application. - /// - [STAThread] - static void Main() - { - // To customize application configuration such as set high DPI settings or default font, - // see https://aka.ms/applicationconfiguration. - ApplicationConfiguration.Initialize(); - Application.Run(new Form1()); - } + // To customize application configuration such as set high DPI settings or default font, see https://aka.ms/applicationconfiguration. + ApplicationConfiguration.Initialize(); + Application.Run(new Form1()); } } \ No newline at end of file