Fix MappingConverter to support Body with JsonMatcher (#1101)

* Fix MappingBuilder for Body

* .

* .

* Fix MappingConverter
This commit is contained in:
Stef Heyenrath
2024-05-08 17:40:53 +02:00
committed by GitHub
parent 9210957e55
commit e20a90b615
11 changed files with 300 additions and 70 deletions

View File

@@ -143,25 +143,21 @@ internal class MappingConverter
if (requestMessageBodyMatcher is { Matchers: { } }) if (requestMessageBodyMatcher is { Matchers: { } })
{ {
if (requestMessageBodyMatcher.Matchers.OfType<WildcardMatcher>().FirstOrDefault() is { } wildcardMatcher && wildcardMatcher.GetPatterns().Any()) var firstMatcher = requestMessageBodyMatcher.Matchers.FirstOrDefault();
if (firstMatcher is WildcardMatcher wildcardMatcher && wildcardMatcher.GetPatterns().Any())
{ {
sb.AppendLine($" .WithBody({GetString(wildcardMatcher)})"); sb.AppendLine($" .WithBody({GetString(wildcardMatcher)})");
} }
else if (requestMessageBodyMatcher.Matchers.OfType<JsonPartialMatcher>().FirstOrDefault() is { Value: { } } jsonPartialMatcher)
if (firstMatcher is JsonMatcher jsonMatcher)
{ {
sb.AppendLine(@$" .WithBody(new JsonPartialMatcher( var matcherType = jsonMatcher.GetType().Name;
value: {ToCSharpStringLiteral(jsonPartialMatcher.Value.ToString())}, sb.AppendLine($" .WithBody(new {matcherType}(");
ignoreCase: {ToCSharpBooleanLiteral(jsonPartialMatcher.IgnoreCase)}, sb.AppendLine($" value: {ConvertToAnonymousObjectDefinition(jsonMatcher.Value, 3)},");
regex: {ToCSharpBooleanLiteral(jsonPartialMatcher.Regex)} sb.AppendLine($" ignoreCase: {ToCSharpBooleanLiteral(jsonMatcher.IgnoreCase)},");
))"); sb.AppendLine($" regex: {ToCSharpBooleanLiteral(jsonMatcher.Regex)}");
} sb.AppendLine(@" ))");
else if (requestMessageBodyMatcher.Matchers.OfType<JsonPartialWildcardMatcher>().FirstOrDefault() is { Value: { } } jsonPartialWildcardMatcher)
{
sb.AppendLine(@$" .WithBody(new JsonPartialWildcardMatcher(
value: {ToCSharpStringLiteral(jsonPartialWildcardMatcher.Value.ToString())},
ignoreCase: {ToCSharpBooleanLiteral(jsonPartialWildcardMatcher.IgnoreCase)},
regex: {ToCSharpBooleanLiteral(jsonPartialWildcardMatcher.Regex)}
))");
} }
} }

View File

@@ -344,15 +344,13 @@ public partial class WireMockServer
private static MappingConverterType GetMappingConverterType(IRequestMessage requestMessage) private static MappingConverterType GetMappingConverterType(IRequestMessage requestMessage)
{ {
var mappingConverterType = MappingConverterType.Server;
if (requestMessage.QueryIgnoreCase?.TryGetValue(nameof(MappingConverterType), out var values) == true && if (requestMessage.QueryIgnoreCase?.TryGetValue(nameof(MappingConverterType), out var values) == true &&
Enum.TryParse(values.FirstOrDefault(), true, out MappingConverterType parsed)) Enum.TryParse(values.FirstOrDefault(), true, out MappingConverterType parsed))
{ {
mappingConverterType = parsed; return parsed;
} }
return mappingConverterType; return MappingConverterType.Server;
} }
private IMapping? FindMappingByGuid(IRequestMessage requestMessage) private IMapping? FindMappingByGuid(IRequestMessage requestMessage)

View File

@@ -10,6 +10,8 @@ namespace WireMock.Util;
internal static class CSharpFormatter internal static class CSharpFormatter
{ {
private const string Null = "null";
#region Reserved Keywords #region Reserved Keywords
private static readonly HashSet<string> CSharpReservedKeywords = new(new[] private static readonly HashSet<string> CSharpReservedKeywords = new(new[]
{ {
@@ -92,17 +94,15 @@ internal static class CSharpFormatter
"while" "while"
}); });
#endregion #endregion
private const string Null = "null"; public static object ConvertToAnonymousObjectDefinition(object jsonBody, int ind = 2)
public static object ConvertToAnonymousObjectDefinition(object jsonBody)
{ {
var serializedBody = JsonConvert.SerializeObject(jsonBody); var serializedBody = JsonConvert.SerializeObject(jsonBody);
using var jsonReader = new JsonTextReader(new StringReader(serializedBody)); using var jsonReader = new JsonTextReader(new StringReader(serializedBody));
jsonReader.DateParseHandling = DateParseHandling.None; jsonReader.DateParseHandling = DateParseHandling.None;
var deserializedBody = JObject.Load(jsonReader); var deserializedBody = JObject.Load(jsonReader);
return ConvertJsonToAnonymousObjectDefinition(deserializedBody, 2); return ConvertJsonToAnonymousObjectDefinition(deserializedBody, ind);
} }
public static string ConvertJsonToAnonymousObjectDefinition(JToken token, int ind = 0) public static string ConvertJsonToAnonymousObjectDefinition(JToken token, int ind = 0)

View File

@@ -1,11 +1,49 @@
var server = WireMockServer.Start(); var server = WireMockServer.Start();
server
.Given(Request.Create()
.UsingMethod("POST")
.WithPath("/users/post1")
.WithBody(new JsonMatcher(
value: new
{
city = "Amsterdam",
country = "The Netherlands"
},
ignoreCase: false,
regex: false
))
)
.WithGuid("90356dba-b36c-469a-a17e-669cd84f1f05")
.RespondWith(Response.Create()
);
server
.Given(Request.Create()
.UsingMethod("POST")
.WithPath("/users/post2")
.WithBody(new JsonPartialMatcher(
value: new
{
city = "City",
country = "Country"
},
ignoreCase: false,
regex: false
))
)
.WithGuid("1b731398-4a5b-457f-a6e3-d65e541c428f")
.RespondWith(Response.Create()
.WithBody(@"Line1
Some ""value"" in Line2")
);
server server
.Given(Request.Create() .Given(Request.Create()
.UsingMethod("GET") .UsingMethod("GET")
.WithPath("/foo1") .WithPath("/foo1")
.WithParam("p1", "xyz") .WithParam("p1", "xyz")
) )
.WithGuid("90356dba-b36c-469a-a17e-669cd84f1f05") .WithGuid("f74fd144-df53-404f-8e35-da22a640bd5f")
.RespondWith(Response.Create() .RespondWith(Response.Create()
.WithStatusCode(200) .WithStatusCode(200)
.WithBody("1") .WithBody("1")
@@ -18,7 +56,7 @@ server
.WithParam("p2", "abc") .WithParam("p2", "abc")
.WithHeader("h1", "W/\"234f2q3r\"", true) .WithHeader("h1", "W/\"234f2q3r\"", true)
) )
.WithGuid("1b731398-4a5b-457f-a6e3-d65e541c428f") .WithGuid("4126dec8-470b-4eff-93bb-c24f83b8b1fd")
.RespondWith(Response.Create() .RespondWith(Response.Create()
.WithStatusCode("201") .WithStatusCode("201")
.WithHeader("hk", "hv") .WithHeader("hk", "hv")
@@ -31,7 +69,7 @@ server
.UsingMethod("DELETE") .UsingMethod("DELETE")
.WithUrl("https://localhost/test") .WithUrl("https://localhost/test")
) )
.WithGuid("f74fd144-df53-404f-8e35-da22a640bd5f") .WithGuid("c9929240-7ae8-4a5d-8ed8-0913479f6eeb")
.RespondWith(Response.Create() .RespondWith(Response.Create()
.WithStatusCode(208) .WithStatusCode(208)
.WithBodyAsJson(new .WithBodyAsJson(new
@@ -70,20 +108,3 @@ text
}) })
); );
server
.Given(Request.Create()
.UsingMethod("POST")
.WithPath("/foo3")
.WithBody(new JsonPartialMatcher(
value: "{ a = 1, b = 2 }",
ignoreCase: false,
regex: false
))
)
.WithGuid("4126dec8-470b-4eff-93bb-c24f83b8b1fd")
.RespondWith(Response.Create()
.WithStatusCode(200)
.WithBody(@"Line1
Some ""value"" in Line2")
);

View File

@@ -491,7 +491,7 @@ public partial class WireMockAdminApiTests
server.Stop(); server.Stop();
} }
[Fact] [Fact]
public async Task IWireMockAdminApi_GetRequestsAsync_Json() public async Task IWireMockAdminApi_GetRequestsAsync_Json()
{ {
@@ -862,8 +862,37 @@ public partial class WireMockAdminApiTests
var guid2 = Guid.Parse("1b731398-4a5b-457f-a6e3-d65e541c428f"); var guid2 = Guid.Parse("1b731398-4a5b-457f-a6e3-d65e541c428f");
var guid3 = Guid.Parse("f74fd144-df53-404f-8e35-da22a640bd5f"); var guid3 = Guid.Parse("f74fd144-df53-404f-8e35-da22a640bd5f");
var guid4 = Guid.Parse("4126DEC8-470B-4EFF-93BB-C24F83B8B1FD"); var guid4 = Guid.Parse("4126DEC8-470B-4EFF-93BB-C24F83B8B1FD");
var guid5 = Guid.Parse("c9929240-7ae8-4a5d-8ed8-0913479f6eeb");
var server = WireMockServer.StartWithAdminInterface(); var server = WireMockServer.StartWithAdminInterface();
server
.Given(
Request.Create()
.WithPath("/users/post1")
.UsingPost()
.WithBody(new JsonMatcher(new
{
city = "Amsterdam",
country = "The Netherlands"
}))
)
.WithGuid(guid1)
.RespondWith(Response.Create());
server
.Given(
Request.Create()
.WithPath("/users/post2")
.UsingPost()
.WithBody(new JsonPartialMatcher(new
{
city = "City",
country = "Country"
}))
)
.WithGuid(guid2)
.RespondWith(Response.Create().WithBody("Line1\r\nSome \"value\" in Line2"));
server server
.Given( .Given(
Request.Create() Request.Create()
@@ -871,7 +900,7 @@ public partial class WireMockAdminApiTests
.WithParam("p1", "xyz") .WithParam("p1", "xyz")
.UsingGet() .UsingGet()
) )
.WithGuid(guid1) .WithGuid(guid3)
.RespondWith( .RespondWith(
Response.Create() Response.Create()
.WithStatusCode(200) .WithStatusCode(200)
@@ -886,7 +915,7 @@ public partial class WireMockAdminApiTests
.WithHeader("h1", "W/\"234f2q3r\"") .WithHeader("h1", "W/\"234f2q3r\"")
.UsingPost() .UsingPost()
) )
.WithGuid(guid2) .WithGuid(guid4)
.RespondWith( .RespondWith(
Response.Create() Response.Create()
.WithStatusCode("201") .WithStatusCode("201")
@@ -901,36 +930,43 @@ public partial class WireMockAdminApiTests
.WithUrl("https://localhost/test") .WithUrl("https://localhost/test")
.UsingDelete() .UsingDelete()
) )
.WithGuid(guid3) .WithGuid(guid5)
.RespondWith( .RespondWith(
Response.Create() Response.Create()
.WithStatusCode(HttpStatusCode.AlreadyReported) .WithStatusCode(HttpStatusCode.AlreadyReported)
.WithBodyAsJson(new { @as = 1, b = 1.2, d = true, e = false, f = new[] { 1, 2, 3, 4 }, g = new { z1 = 1, z2 = 2, z3 = new[] { "a", "b", "c" }, z4 = new[] { new { a = 1, b = 2 }, new { a = 2, b = 3 } } }, date_field = new DateTime(2023, 05, 08, 11, 20, 19), string_field_with_date = "2021-03-13T21:04:00Z", multiline_text = @"This .WithBodyAsJson(new
{
@as = 1,
b = 1.2,
d = true,
e = false,
f = new[] { 1, 2, 3, 4 },
g = new
{
z1 = 1,
z2 = 2,
z3 = new[] { "a", "b", "c" },
z4 = new[]
{
new { a = 1, b = 2 },
new { a = 2, b = 3 }
}
},
date_field = new DateTime(2023, 05, 08, 11, 20, 19),
string_field_with_date = "2021-03-13T21:04:00Z",
multiline_text = @"This
is is
multiline multiline
text text
" }) "
); })
server
.Given(
Request.Create()
.WithPath("/foo3")
.WithBody(new JsonPartialMatcher(new { a = 1, b = 2 }))
.UsingPost()
)
.WithGuid(guid4)
.RespondWith(
Response.Create()
.WithStatusCode(200)
.WithBody("Line1\r\nSome \"value\" in Line2")
); );
// Act // Act
var api = RestClient.For<IWireMockAdminApi>(server.Url); var api = RestClient.For<IWireMockAdminApi>(server.Url);
var mappings = await api.GetMappingsAsync().ConfigureAwait(false); var mappings = await api.GetMappingsAsync().ConfigureAwait(false);
mappings.Should().HaveCount(4); mappings.Should().HaveCount(5);
var code = await api.GetMappingsCodeAsync().ConfigureAwait(false); var code = await api.GetMappingsCodeAsync().ConfigureAwait(false);

View File

@@ -31,5 +31,35 @@
BodyDestination: SameAsSource, BodyDestination: SameAsSource,
Body: { msg: "Hello world!"} Body: { msg: "Hello world!"}
} }
},
{
Guid: Guid_2,
UpdatedAt: 2023-01-14 15:16:17,
Request: {
Path: {
Matchers: [
{
Name: WildcardMatcher,
Pattern: /users/post2,
IgnoreCase: false
}
]
},
Methods: [
POST
],
Body: {
Matcher: {
Name: JsonMatcher,
Pattern: {
city: Amsterdam,
country: The Netherlands
},
IgnoreCase: false,
Regex: false
}
}
},
Response: {}
} }
] ]

View File

@@ -0,0 +1,30 @@
var builder = new MappingBuilder();
builder
.Given(Request.Create()
.UsingMethod("GET")
.WithPath("/foo")
.WithParam("test", "it.Length < 10")
)
.WithGuid("41372914-1838-4c67-916b-b9aacdd096ce")
.RespondWith(Response.Create()
.WithBody("{ msg: \"Hello world!\"}")
);
builder
.Given(Request.Create()
.UsingMethod("POST")
.WithPath("/users/post2")
.WithBody(new JsonMatcher(
value: new
{
city = "Amsterdam",
country = "The Netherlands"
},
ignoreCase: false,
regex: false
))
)
.WithGuid("98fae52e-76df-47d9-876f-2ee32e931d9b")
.RespondWith(Response.Create()
);

View File

@@ -0,0 +1,30 @@
var server = WireMockServer.Start();
server
.Given(Request.Create()
.UsingMethod("GET")
.WithPath("/foo")
.WithParam("test", "it.Length < 10")
)
.WithGuid("41372914-1838-4c67-916b-b9aacdd096ce")
.RespondWith(Response.Create()
.WithBody("{ msg: \"Hello world!\"}")
);
server
.Given(Request.Create()
.UsingMethod("POST")
.WithPath("/users/post2")
.WithBody(new JsonMatcher(
value: new
{
city = "Amsterdam",
country = "The Netherlands"
},
ignoreCase: false,
regex: false
))
)
.WithGuid("98fae52e-76df-47d9-876f-2ee32e931d9b")
.RespondWith(Response.Create()
);

View File

@@ -31,5 +31,34 @@
BodyDestination: SameAsSource, BodyDestination: SameAsSource,
Body: { msg: "Hello world!"} Body: { msg: "Hello world!"}
} }
},
{
Guid: Guid_2,
UpdatedAt: 2023-01-14T15:16:17,
Request: {
Path: {
Matchers: [
{
Name: WildcardMatcher,
Pattern: /users/post2,
IgnoreCase: false
}
]
},
Methods: [
POST
],
Body: {
Matcher: {
Name: JsonMatcher,
Pattern: {
city: Amsterdam,
country: The Netherlands
},
IgnoreCase: false,
Regex: false
}
}
}
} }
] ]

View File

@@ -13,6 +13,7 @@ using WireMock.RequestBuilders;
using WireMock.ResponseBuilders; using WireMock.ResponseBuilders;
using WireMock.Serialization; using WireMock.Serialization;
using WireMock.Settings; using WireMock.Settings;
using WireMock.Types;
using WireMock.Util; using WireMock.Util;
using Xunit; using Xunit;
@@ -73,6 +74,25 @@ public class MappingBuilderTests
.RespondWith(Response.Create() .RespondWith(Response.Create()
.WithBody(@"{ msg: ""Hello world!""}") .WithBody(@"{ msg: ""Hello world!""}")
); );
_sut.Given(Request.Create()
.WithPath("/users/post1")
.UsingPost()
.WithBodyAsJson(new
{
Request = "Hello?"
})
).RespondWith(Response.Create());
_sut.Given(Request.Create()
.WithPath("/users/post2")
.UsingPost()
.WithBody(new JsonMatcher(new
{
city = "Amsterdam",
country = "The Netherlands"
}))
).RespondWith(Response.Create());
} }
[Fact] [Fact]
@@ -95,6 +115,26 @@ public class MappingBuilderTests
return Verifier.VerifyJson(json, VerifySettings); return Verifier.VerifyJson(json, VerifySettings);
} }
[Fact]
public Task ToCSharpCode_Server()
{
// Act
var code = _sut.ToCSharpCode(MappingConverterType.Server);
// Verify
return Verifier.Verify(code, VerifySettings);
}
[Fact]
public Task ToCSharpCode_Builder()
{
// Act
var code = _sut.ToCSharpCode(MappingConverterType.Builder);
// Verify
return Verifier.Verify(code, VerifySettings);
}
[Fact] [Fact]
public void SaveMappingsToFile_FolderExists_IsFalse() public void SaveMappingsToFile_FolderExists_IsFalse()
{ {
@@ -141,9 +181,9 @@ public class MappingBuilderTests
_sut.SaveMappingsToFolder(null); _sut.SaveMappingsToFolder(null);
// Verify // Verify
_fileSystemHandlerMock.Verify(fs => fs.GetMappingFolder(), Times.Once); _fileSystemHandlerMock.Verify(fs => fs.GetMappingFolder(), Times.Exactly(2));
_fileSystemHandlerMock.Verify(fs => fs.FolderExists(mappingFolder), Times.Once); _fileSystemHandlerMock.Verify(fs => fs.FolderExists(mappingFolder), Times.Exactly(2));
_fileSystemHandlerMock.Verify(fs => fs.WriteMappingFile(It.IsAny<string>(), It.IsAny<string>()), Times.Once); _fileSystemHandlerMock.Verify(fs => fs.WriteMappingFile(It.IsAny<string>(), It.IsAny<string>()), Times.Exactly(2));
_fileSystemHandlerMock.VerifyNoOtherCalls(); _fileSystemHandlerMock.VerifyNoOtherCalls();
} }
@@ -159,8 +199,8 @@ public class MappingBuilderTests
// Verify // Verify
_fileSystemHandlerMock.Verify(fs => fs.GetMappingFolder(), Times.Never); _fileSystemHandlerMock.Verify(fs => fs.GetMappingFolder(), Times.Never);
_fileSystemHandlerMock.Verify(fs => fs.FolderExists(path), Times.Once); _fileSystemHandlerMock.Verify(fs => fs.FolderExists(path), Times.Exactly(2));
_fileSystemHandlerMock.Verify(fs => fs.WriteMappingFile(It.IsAny<string>(), It.IsAny<string>()), Times.Once); _fileSystemHandlerMock.Verify(fs => fs.WriteMappingFile(It.IsAny<string>(), It.IsAny<string>()), Times.Exactly(2));
_fileSystemHandlerMock.VerifyNoOtherCalls(); _fileSystemHandlerMock.VerifyNoOtherCalls();
} }
} }

View File

@@ -499,4 +499,24 @@ public class WireMockServerAdminTests
Check.That(response.StatusCode).Equals(HttpStatusCode.OK); Check.That(response.StatusCode).Equals(HttpStatusCode.OK);
Check.That(await response.Content.ReadAsStringAsync().ConfigureAwait(false)).Equals($"{{\"Status\":\"Mappings deleted. Affected GUIDs: [{guid1}, {guid2}]\"}}"); Check.That(await response.Content.ReadAsStringAsync().ConfigureAwait(false)).Equals($"{{\"Status\":\"Mappings deleted. Affected GUIDs: [{guid1}, {guid2}]\"}}");
} }
[Fact]
public async Task WireMockServer_Admin_()
{
// given
var server = WireMockServer.Start();
server.CreateClient();
// when
await new HttpClient().GetAsync("http://localhost:" + server.Ports[0] + "/foo").ConfigureAwait(false);
// then
Check.That(server.LogEntries).HasSize(1);
var requestLogged = server.LogEntries.First();
Check.That(requestLogged.RequestMessage.Method).IsEqualTo("GET");
Check.That(requestLogged.RequestMessage.BodyData).IsNull();
server.Stop();
}
} }