Support for string and object in JsonMatcher. (#157)

* JsonMatcher (#154)

* small update in example code
This commit is contained in:
Stef Heyenrath
2018-06-26 23:20:30 +02:00
committed by GitHub
parent b2690017a8
commit 4283732b6c
13 changed files with 135 additions and 42 deletions

View File

@@ -57,12 +57,22 @@ namespace WireMock.Net.ConsoleApplication
server server
.Given(Request .Given(Request
.Create() .Create()
.WithPath("/jsonbodytest") .WithPath("/jsonbodytest1")
.WithBody(new JsonMatcher("{ \"x\": 42, \"s\": \"s\" }")) .WithBody(new JsonMatcher("{ \"x\": 42, \"s\": \"s\" }"))
.UsingPost()) .UsingPost())
.WithGuid("debaf408-3b23-4c04-9d18-ef1c020e79f2") .WithGuid("debaf408-3b23-4c04-9d18-ef1c020e79f2")
.RespondWith(Response.Create() .RespondWith(Response.Create()
.WithBody(@"{ ""result"": ""jsonbodytest"" }")); .WithBody(@"{ ""result"": ""jsonbodytest1"" }"));
server
.Given(Request
.Create()
.WithPath("/jsonbodytest2")
.WithBody(new JsonMatcher(new { x = 42, s = "s" }))
.UsingPost())
.WithGuid("debaf408-3b23-4c04-9d18-ef1c020e79f3")
.RespondWith(Response.Create()
.WithBody(@"{ ""result"": ""jsonbodytest2"" }"));
server server
.Given(Request .Given(Request
@@ -77,7 +87,6 @@ namespace WireMock.Net.ConsoleApplication
.Given(Request.Create().WithPath("/headers", "/headers_test").UsingPost().WithHeader("Content-Type", "application/json*")) .Given(Request.Create().WithPath("/headers", "/headers_test").UsingPost().WithHeader("Content-Type", "application/json*"))
.RespondWith(Response.Create() .RespondWith(Response.Create()
.WithStatusCode(201) .WithStatusCode(201)
//.WithHeader("MyHeader", "application/json", "application/json2")
.WithHeader("Content-Type", "application/json") .WithHeader("Content-Type", "application/json")
.WithBodyAsJson(new { result = "data:headers posted with 201" })); .WithBodyAsJson(new { result = "data:headers posted with 201" }));
@@ -184,8 +193,6 @@ namespace WireMock.Net.ConsoleApplication
.WithStatusCode(HttpStatusCode.Unauthorized) .WithStatusCode(HttpStatusCode.Unauthorized)
.WithBody(@"{ ""result"": ""api-key missing""}")); .WithBody(@"{ ""result"": ""api-key missing""}"));
server server
.Given(Request.Create().WithPath("/nobody").UsingGet()) .Given(Request.Create().WithPath("/nobody").UsingGet())
.RespondWith(Response.Create().WithDelay(TimeSpan.FromSeconds(1)) .RespondWith(Response.Create().WithDelay(TimeSpan.FromSeconds(1))
@@ -195,7 +202,7 @@ namespace WireMock.Net.ConsoleApplication
.Given(Request.Create().WithPath("/partial").UsingPost().WithBody(new SimMetricsMatcher(new[] { "cat", "dog" }))) .Given(Request.Create().WithPath("/partial").UsingPost().WithBody(new SimMetricsMatcher(new[] { "cat", "dog" })))
.RespondWith(Response.Create().WithStatusCode(200).WithBody("partial = 200")); .RespondWith(Response.Create().WithStatusCode(200).WithBody("partial = 200"));
// http://localhost:8080/any/any?start=1000&stop=1&stop=2 // http://localhost:8080/trans?start=1000&stop=1&stop=2
server server
.Given(Request.Create().WithPath("/trans").UsingGet()) .Given(Request.Create().WithPath("/trans").UsingGet())
.WithGuid("90356dba-b36c-469a-a17e-669cd84f1f05") .WithGuid("90356dba-b36c-469a-a17e-669cd84f1f05")
@@ -203,6 +210,7 @@ namespace WireMock.Net.ConsoleApplication
.WithStatusCode(200) .WithStatusCode(200)
.WithHeader("Content-Type", "application/json") .WithHeader("Content-Type", "application/json")
.WithHeader("Transformed-Postman-Token", "token is {{request.headers.Postman-Token}}") .WithHeader("Transformed-Postman-Token", "token is {{request.headers.Postman-Token}}")
.WithHeader("xyz_{{request.headers.Postman-Token}}", "token is {{request.headers.Postman-Token}}")
.WithBody(@"{""msg"": ""Hello world CATCH-ALL on /*, {{request.path}}, bykey={{request.query.start}}, bykey={{request.query.stop}}, byidx0={{request.query.stop.[0]}}, byidx1={{request.query.stop.[1]}}"" }") .WithBody(@"{""msg"": ""Hello world CATCH-ALL on /*, {{request.path}}, bykey={{request.query.start}}, bykey={{request.query.stop}}, byidx0={{request.query.stop.[0]}}, byidx1={{request.query.stop.[1]}}"" }")
.WithTransformer() .WithTransformer()
.WithDelay(TimeSpan.FromMilliseconds(100)) .WithDelay(TimeSpan.FromMilliseconds(100))

View File

@@ -42,8 +42,18 @@
<Reference Include="log4net, Version=2.0.8.0, Culture=neutral, PublicKeyToken=669e0ddf0bb1aa2a, processorArchitecture=MSIL"> <Reference Include="log4net, Version=2.0.8.0, Culture=neutral, PublicKeyToken=669e0ddf0bb1aa2a, processorArchitecture=MSIL">
<HintPath>..\..\packages\log4net.2.0.8\lib\net45-full\log4net.dll</HintPath> <HintPath>..\..\packages\log4net.2.0.8\lib\net45-full\log4net.dll</HintPath>
</Reference> </Reference>
<Reference Include="Microsoft.Owin.Host.HttpListener, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.Owin.Host.HttpListener.4.0.0\lib\net451\Microsoft.Owin.Host.HttpListener.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json, Version=10.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Core" /> <Reference Include="System.Core" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Net.Http.Formatting, Version=5.2.4.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.4\lib\net45\System.Net.Http.Formatting.dll</HintPath>
</Reference>
<Reference Include="System.Numerics" /> <Reference Include="System.Numerics" />
<Reference Include="System.Xml" /> <Reference Include="System.Xml" />
<Reference Include="System.Xml.Linq" /> <Reference Include="System.Xml.Linq" />
@@ -53,6 +63,7 @@
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="app.config" />
<None Include="log4net.config"> <None Include="log4net.config">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None> </None>

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-10.0.0.0" newVersion="10.0.0.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>

View File

@@ -1,4 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <packages>
<package id="log4net" version="2.0.8" targetFramework="net452" /> <package id="log4net" version="2.0.8" targetFramework="net452" />
<package id="Microsoft.AspNet.WebApi.Client" version="5.2.4" targetFramework="net452" />
<package id="Microsoft.Owin.Host.HttpListener" version="4.0.0" targetFramework="net452" />
<package id="Newtonsoft.Json" version="10.0.3" targetFramework="net452" />
</packages> </packages>

View File

@@ -11,19 +11,14 @@
public string Name { get; set; } public string Name { get; set; }
/// <summary> /// <summary>
/// Gets or sets the value. Used by <see cref="Matchers.JsonMatcher"/>. /// Gets or sets the pattern. Can be a string (default) or an object;
/// </summary> /// </summary>
public string Value { get; set; } public object Pattern { get; set; }
/// <summary> /// <summary>
/// Gets or sets the pattern. /// Gets or sets the patterns. Can be array of strings (default) or an array of objects;
/// </summary> /// </summary>
public string Pattern { get; set; } public object[] Patterns { get; set; }
/// <summary>
/// Gets or sets the patterns.
/// </summary>
public string[] Patterns { get; set; }
/// <summary> /// <summary>
/// Gets or sets the ignore case. /// Gets or sets the ignore case.

View File

@@ -7,9 +7,9 @@
public interface IValueMatcher: IObjectMatcher public interface IValueMatcher: IObjectMatcher
{ {
/// <summary> /// <summary>
/// Gets the value. /// Gets the value (can be a string or an obejct).
/// </summary> /// </summary>
/// <returns>Value</returns> /// <returns>Value</returns>
string GetValue(); object Value { get; }
} }
} }

View File

@@ -10,7 +10,8 @@ namespace WireMock.Matchers
/// </summary> /// </summary>
public class JsonMatcher : IValueMatcher public class JsonMatcher : IValueMatcher
{ {
private readonly string _value; /// <inheritdoc cref="IValueMatcher.Value"/>
public object Value { get; }
/// <inheritdoc cref="IMatcher.Name"/> /// <inheritdoc cref="IMatcher.Name"/>
public string Name => "JsonMatcher"; public string Name => "JsonMatcher";
@@ -21,22 +22,43 @@ namespace WireMock.Matchers
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="JsonMatcher"/> class. /// Initializes a new instance of the <see cref="JsonMatcher"/> class.
/// </summary> /// </summary>
/// <param name="value">The value to check for equality.</param> /// <param name="value">The string value to check for equality.</param>
public JsonMatcher([NotNull] string value) : this(MatchBehaviour.AcceptOnMatch, value) public JsonMatcher([NotNull] string value) : this(MatchBehaviour.AcceptOnMatch, value)
{ {
} }
/// <summary>
/// Initializes a new instance of the <see cref="JsonMatcher"/> class.
/// </summary>
/// <param name="value">The object value to check for equality.</param>
public JsonMatcher([NotNull] object value) : this(MatchBehaviour.AcceptOnMatch, value)
{
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="JsonMatcher"/> class. /// Initializes a new instance of the <see cref="JsonMatcher"/> class.
/// </summary> /// </summary>
/// <param name="matchBehaviour">The match behaviour.</param> /// <param name="matchBehaviour">The match behaviour.</param>
/// <param name="value">The value to check for equality.</param> /// <param name="value">The string value to check for equality.</param>
public JsonMatcher(MatchBehaviour matchBehaviour, [NotNull] string value) public JsonMatcher(MatchBehaviour matchBehaviour, [NotNull] string value)
{ {
Check.NotNull(value, nameof(value)); Check.NotNull(value, nameof(value));
MatchBehaviour = matchBehaviour; MatchBehaviour = matchBehaviour;
_value = value; Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="JsonMatcher"/> class.
/// </summary>
/// <param name="matchBehaviour">The match behaviour.</param>
/// <param name="value">The object value to check for equality.</param>
public JsonMatcher(MatchBehaviour matchBehaviour, [NotNull] object value)
{
Check.NotNull(value, nameof(value));
MatchBehaviour = matchBehaviour;
Value = value;
} }
/// <inheritdoc cref="IObjectMatcher.IsMatch"/> /// <inheritdoc cref="IObjectMatcher.IsMatch"/>
@@ -48,9 +70,15 @@ namespace WireMock.Matchers
try try
{ {
// Check if JToken or object // Check if JToken or object
JToken jtoken = input is JToken token ? token : JObject.FromObject(input); JToken jtokenInput = input is JToken tokenInput ? tokenInput : JObject.FromObject(input);
match = JToken.DeepEquals(JToken.Parse(_value), jtoken); // Check if JToken or string or object
JToken jtokenValue =
Value is JToken tokenValue ? tokenValue :
Value is string stringValue ? JToken.Parse(stringValue) :
JObject.FromObject(input);
match = JToken.DeepEquals(jtokenValue, jtokenInput);
} }
catch (JsonException) catch (JsonException)
{ {
@@ -60,8 +88,5 @@ namespace WireMock.Matchers
return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.ToScore(match)); return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.ToScore(match));
} }
/// <inheritdoc cref="IValueMatcher.GetValue"/>
public string GetValue() => _value;
} }
} }

View File

@@ -21,28 +21,28 @@ namespace WireMock.Serialization
string matcherName = parts[0]; string matcherName = parts[0];
string matcherType = parts.Length > 1 ? parts[1] : null; string matcherType = parts.Length > 1 ? parts[1] : null;
string[] patterns = matcher.Patterns ?? new[] { matcher.Pattern }; string[] stringPatterns = matcher.Patterns != null ? matcher.Patterns.Cast<string>().ToArray() : new [] { matcher.Pattern as string };
MatchBehaviour matchBehaviour = matcher.RejectOnMatch == true ? MatchBehaviour.RejectOnMatch : MatchBehaviour.AcceptOnMatch; MatchBehaviour matchBehaviour = matcher.RejectOnMatch == true ? MatchBehaviour.RejectOnMatch : MatchBehaviour.AcceptOnMatch;
switch (matcherName) switch (matcherName)
{ {
case "ExactMatcher": case "ExactMatcher":
return new ExactMatcher(matchBehaviour, patterns); return new ExactMatcher(matchBehaviour, stringPatterns);
case "RegexMatcher": case "RegexMatcher":
return new RegexMatcher(matchBehaviour, patterns, matcher.IgnoreCase == true); return new RegexMatcher(matchBehaviour, stringPatterns, matcher.IgnoreCase == true);
case "JsonMatcher": case "JsonMatcher":
return new JsonMatcher(matchBehaviour, matcher.Pattern); return new JsonMatcher(matchBehaviour, matcher.Pattern);
case "JsonPathMatcher": case "JsonPathMatcher":
return new JsonPathMatcher(matchBehaviour, patterns); return new JsonPathMatcher(matchBehaviour, stringPatterns);
case "XPathMatcher": case "XPathMatcher":
return new XPathMatcher(matchBehaviour, matcher.Pattern); return new XPathMatcher(matchBehaviour, (string) matcher.Pattern);
case "WildcardMatcher": case "WildcardMatcher":
return new WildcardMatcher(matchBehaviour, patterns, matcher.IgnoreCase == true); return new WildcardMatcher(matchBehaviour, stringPatterns, matcher.IgnoreCase == true);
case "SimMetricsMatcher": case "SimMetricsMatcher":
SimMetricType type = SimMetricType.Levenstein; SimMetricType type = SimMetricType.Levenstein;
@@ -51,7 +51,7 @@ namespace WireMock.Serialization
throw new NotSupportedException($"Matcher '{matcherName}' with Type '{matcherType}' is not supported."); throw new NotSupportedException($"Matcher '{matcherName}' with Type '{matcherType}' is not supported.");
} }
return new SimMetricsMatcher(matchBehaviour, matcher.Pattern, type); return new SimMetricsMatcher(matchBehaviour, (string) matcher.Pattern, type);
default: default:
throw new NotSupportedException($"Matcher '{matcherName}' is not supported."); throw new NotSupportedException($"Matcher '{matcherName}' is not supported.");
@@ -70,9 +70,13 @@ namespace WireMock.Serialization
return null; return null;
} }
string[] patterns = matcher is IStringMatcher stringMatcher ? // If the matcher is a IStringMatcher, get the patterns.
stringMatcher.GetPatterns() : // If the matcher is a IValueMatcher, get the value (can be string or object).
matcher is IValueMatcher valueMatcher ? new[] { valueMatcher.GetValue() } : new string[0]; // Else empty array
object[] patterns = matcher is IStringMatcher stringMatcher ?
stringMatcher.GetPatterns().Cast<object>().ToArray() :
matcher is IValueMatcher valueMatcher ? new[] { valueMatcher.Value } :
new object[0];
bool? ignorecase = matcher is IIgnoreCaseMatcher ignoreCaseMatcher ? ignoreCaseMatcher.IgnoreCase : (bool?)null; bool? ignorecase = matcher is IIgnoreCaseMatcher ignoreCaseMatcher ? ignoreCaseMatcher.IgnoreCase : (bool?)null;
bool? rejectOnMatch = matcher.MatchBehaviour == MatchBehaviour.RejectOnMatch ? true : (bool?)null; bool? rejectOnMatch = matcher.MatchBehaviour == MatchBehaviour.RejectOnMatch ? true : (bool?)null;

View File

@@ -238,7 +238,11 @@ namespace WireMock.Server
} }
}); });
if (requestMessage.Body != null) if (requestMessage.BodyAsJson != null)
{
request.WithBody(new JsonMatcher(MatchBehaviour.AcceptOnMatch, requestMessage.BodyAsJson));
}
else if (requestMessage.Body != null)
{ {
request.WithBody(new ExactMatcher(MatchBehaviour.AcceptOnMatch, requestMessage.Body)); request.WithBody(new ExactMatcher(MatchBehaviour.AcceptOnMatch, requestMessage.Body));
} }

View File

@@ -88,7 +88,7 @@ namespace WireMock.Net.Tests
string folder = Path.Combine(GetCurrentFolder(), "__admin", "mappings", guid + ".json"); string folder = Path.Combine(GetCurrentFolder(), "__admin", "mappings", guid + ".json");
string json = File.ReadAllText(folder); string json = File.ReadAllText(folder);
string responseBodyFilePath = Path.Combine(GetCurrentFolder(), "ResponseBodyFiles", "responsebody.json"); string responseBodyFilePath = Path.Combine(GetCurrentFolder(), "responsebody.json");
dynamic jsonObj = JsonConvert.DeserializeObject(json); dynamic jsonObj = JsonConvert.DeserializeObject(json);
jsonObj["Response"]["BodyAsFile"] = responseBodyFilePath; jsonObj["Response"]["BodyAsFile"] = responseBodyFilePath;

View File

@@ -27,7 +27,7 @@ namespace WireMock.Net.Tests.Matchers
var matcher = new JsonMatcher("{}"); var matcher = new JsonMatcher("{}");
// Act // Act
string value = matcher.GetValue(); object value = matcher.Value;
// Assert // Assert
Check.That(value).Equals("{}"); Check.That(value).Equals("{}");
@@ -62,7 +62,39 @@ namespace WireMock.Net.Tests.Matchers
} }
[Fact] [Fact]
public void JsonMatcher_IsMatch_JObject() public void JsonMatcher_IsMatch_JObject1()
{
// Assign
var matcher = new JsonMatcher(new { Id = 1, Name = "test" });
// Act
var jobject = new JObject
{
{ "Id", new JValue(1) },
{ "Name", new JValue("Test") }
};
double match = matcher.IsMatch(jobject);
// Assert
Assert.Equal(1.0, match);
}
[Fact]
public void JsonMatcher_IsMatch_JObject2()
{
// Assign
var matcher = new JsonMatcher(new { Id = 1, Name = "test" });
// Act
var jobject = JObject.Parse("{ \"Id\" : 1, \"Name\" : \"Test\" }");
double match = matcher.IsMatch(jobject);
// Assert
Assert.Equal(1.0, match);
}
[Fact]
public void JsonMatcher_IsMatch_JObjectAsString()
{ {
// Assign // Assign
var matcher = new JsonMatcher("{ \"Id\" : 1, \"Name\" : \"Test\" }"); var matcher = new JsonMatcher("{ \"Id\" : 1, \"Name\" : \"Test\" }");
@@ -80,7 +112,7 @@ namespace WireMock.Net.Tests.Matchers
} }
[Fact] [Fact]
public void JsonMatcher_IsMatch_JObject_RejectOnMatch() public void JsonMatcher_IsMatch_JObjectAsString_RejectOnMatch()
{ {
// Assign // Assign
var matcher = new JsonMatcher(MatchBehaviour.RejectOnMatch, "{ \"Id\" : 1, \"Name\" : \"Test\" }"); var matcher = new JsonMatcher(MatchBehaviour.RejectOnMatch, "{ \"Id\" : 1, \"Name\" : \"Test\" }");

View File

@@ -31,7 +31,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Update="ResponseBodyFiles\responsebody.json"> <None Update="responsebody.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None> </None>
<None Update="__admin\mappings\00000002-ee28-4f29-ae63-1ac9b0802d86.json"> <None Update="__admin\mappings\00000002-ee28-4f29-ae63-1ac9b0802d86.json">