diff --git a/Directory.Build.props b/Directory.Build.props index 0b119cb3..25a3eb8b 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -4,7 +4,7 @@ - 1.0.4.21 + 1.0.5 diff --git a/GitHubReleaseNotes.txt b/GitHubReleaseNotes.txt index e9d9bb2f..1f936d0a 100644 --- a/GitHubReleaseNotes.txt +++ b/GitHubReleaseNotes.txt @@ -1,3 +1,3 @@ https://github.com/StefH/GitHubReleaseNotes -GitHubReleaseNotes.exe --output CHANGELOG.md --skip-empty-releases --version 1.0.4.21 \ No newline at end of file +GitHubReleaseNotes.exe --output CHANGELOG.md --skip-empty-releases --version 1.0.5 \ No newline at end of file diff --git a/README.md b/README.md index 80748659..82988722 100644 --- a/README.md +++ b/README.md @@ -5,10 +5,10 @@ A C# .NET version based on [mock4net](https://github.com/alexvictoor/mock4net) w * HTTP response stubbing, matchable on URL/Path, headers, cookies and body content patterns * Runs in unit tests, as a standalone process, as windows service, as Azure or IIS or as docker * Configurable via a fluent DotNet API, JSON files and JSON over HTTP -* Record/playback of stubs +* Record/playback of stubs (proxying) * Per-request conditional proxying * Stateful behaviour simulation -* Configurable response delays +* Response transformation ## Info | | | diff --git a/examples/WireMock.Net.Console.Net452.Classic/MainApp.cs b/examples/WireMock.Net.Console.Net452.Classic/MainApp.cs index 4ec24693..49d30dc5 100644 --- a/examples/WireMock.Net.Console.Net452.Classic/MainApp.cs +++ b/examples/WireMock.Net.Console.Net452.Classic/MainApp.cs @@ -388,11 +388,10 @@ namespace WireMock.Net.ConsoleApplication .WithStatusCode(500) .WithBody(requestMessage => { - string returnStr = JsonConvert.SerializeObject(new + return JsonConvert.SerializeObject(new { Message = "Test error" }); - return returnStr; }) ); @@ -402,22 +401,28 @@ namespace WireMock.Net.ConsoleApplication .WithHeader("Content-Type", "application/json") .WithBodyAsJson(new { + Xeger1 = "{{Xeger \"\\w{4}\\d{5}\"}}", + Xeger2 = "{{Xeger \"\\d{5}\"}}", + TextRegexPostcode = "{{Random Type=\"TextRegex\" Pattern=\"[1-9][0-9]{3}[A-Z]{2}\"}}", Text = "{{Random Type=\"Text\" Min=8 Max=20}}", TextLipsum = "{{Random Type=\"TextLipsum\"}}", + IBAN = "{{Random Type=\"IBAN\" CountryCode=\"NL\"}}", TimeSpan1 = "{{Random Type=\"TimeSpan\" Format=\"c\" IncludeMilliseconds=false}}", TimeSpan2 = "{{Random Type=\"TimeSpan\"}}", DateTime1 = "{{Random Type=\"DateTime\"}}", DateTimeNow = DateTime.Now, - DateTimeToString = DateTime.Now.ToString("s", CultureInfo.InvariantCulture), + DateTimeNowToString = DateTime.Now.ToString("s", CultureInfo.InvariantCulture), Guid1 = "{{Random Type=\"Guid\" Uppercase=false}}", Guid2 = "{{Random Type=\"Guid\"}}", - Integer1 = "{{Random Type=\"Integer\" Min=1000 Max=9999}}", - Integer2 = "{{#Random Type=\"Integer\" Min=10000000 Max=99999999}}{{this}}{{/Random}}", - Double1 = "{{Random Type=\"Double\" Min=10 Max=99}}", - Double2 = "{{Random Type=\"Double\" Min=100 Max=999}}", + Boolean = "{{Random Type=\"Boolean\"}}", + Integer = "{{Random Type=\"Integer\" Min=1000 Max=9999}}", + Long = "{{#Random Type=\"Long\" Min=10000000 Max=99999999}}{{this}}{{/Random}}", + Double = "{{Random Type=\"Double\" Min=10 Max=99}}", + Float = "{{Random Type=\"Float\" Min=100 Max=999}}", IP4Address = "{{Random Type=\"IPv4Address\" Min=\"10.2.3.4\"}}", IP6Address = "{{Random Type=\"IPv6Address\"}}", - MACAddress = "{{Random Type=\"MACAddress\" Separator=\"-\"}}" + MACAddress = "{{Random Type=\"MACAddress\" Separator=\"-\"}}", + StringListValue = "{{Random Type=\"StringList\" Values=[\"a\", \"b\", \"c\"]}}" }) .WithTransformer() ); diff --git a/src/WireMock.Net/Transformers/HandleBarsHelpers.cs b/src/WireMock.Net/Transformers/HandleBarsHelpers.cs index 3726d5f9..b40c5bae 100644 --- a/src/WireMock.Net/Transformers/HandleBarsHelpers.cs +++ b/src/WireMock.Net/Transformers/HandleBarsHelpers.cs @@ -13,6 +13,8 @@ namespace WireMock.Transformers HandleBarsLinq.Register(handlebarsContext); HandleBarsRandom.Register(handlebarsContext); + + HandleBarsXeger.Register(handlebarsContext); } } } \ No newline at end of file diff --git a/src/WireMock.Net/Transformers/HandleBarsXeger.cs b/src/WireMock.Net/Transformers/HandleBarsXeger.cs new file mode 100644 index 00000000..1928c6ca --- /dev/null +++ b/src/WireMock.Net/Transformers/HandleBarsXeger.cs @@ -0,0 +1,39 @@ +using System; +using Fare; +using HandlebarsDotNet; +using WireMock.Validation; + +namespace WireMock.Transformers +{ + internal static class HandleBarsXeger + { + public static void Register(IHandlebars handlebarsContext) + { + handlebarsContext.RegisterHelper("Xeger", (writer, context, arguments) => + { + string value = ParseArgumentAndGenerate(arguments); + writer.Write(value); + }); + + handlebarsContext.RegisterHelper("Xeger", (writer, options, context, arguments) => + { + string value = ParseArgumentAndGenerate(arguments); + options.Template(writer, value); + }); + } + + private static string ParseArgumentAndGenerate(object[] arguments) + { + Check.Condition(arguments, args => args.Length == 1, nameof(arguments)); + Check.NotNull(arguments[0], "arguments[0]"); + + switch (arguments[0]) + { + case string pattern: + return new Xeger(pattern).Generate(); + } + + throw new NotSupportedException($"The value '{arguments[0]}' with type '{arguments[0]?.GetType()}' cannot be used in Handlebars Xeger."); + } + } +} \ No newline at end of file diff --git a/src/WireMock.Net/Transformers/ResponseMessageTransformer.cs b/src/WireMock.Net/Transformers/ResponseMessageTransformer.cs index 00cd809d..62d012d7 100644 --- a/src/WireMock.Net/Transformers/ResponseMessageTransformer.cs +++ b/src/WireMock.Net/Transformers/ResponseMessageTransformer.cs @@ -120,23 +120,34 @@ namespace WireMock.Transformers string transformedString = templateForStringValue(template); if (!string.Equals(stringValue, transformedString)) { - JToken value; - try - { - // Try to convert this string into a real JsonObject - value = JToken.Parse(transformedString); - } - catch (JsonException) - { - // Ignore JsonException and just convert to JToken - value = transformedString; - } - - node.Replace(value); + ReplaceNodeValue(node, transformedString); } } } + private static void ReplaceNodeValue(JToken node, string stringValue) + { + if (bool.TryParse(stringValue, out bool valueAsBoolean)) + { + node.Replace(valueAsBoolean); + return; + } + + JToken value; + try + { + // Try to convert this string into a JsonObject + value = JToken.Parse(stringValue); + } + catch (JsonException) + { + // Ignore JsonException and just keep string value and convert to JToken + value = stringValue; + } + + node.Replace(value); + } + private static void TransformBodyAsString(object template, ResponseMessage original, ResponseMessage responseMessage) { var templateBody = HandlebarsContext.Compile(original.BodyData.BodyAsString); diff --git a/src/WireMock.Net/WireMock.Net.csproj b/src/WireMock.Net/WireMock.Net.csproj index c8b1cd72..e5751469 100644 --- a/src/WireMock.Net/WireMock.Net.csproj +++ b/src/WireMock.Net/WireMock.Net.csproj @@ -58,7 +58,7 @@ runtime; build; native; contentfiles; analyzers - + diff --git a/test/WireMock.Net.Tests/ResponseBuilders/ResponseWithHandlebarsRandomTests.cs b/test/WireMock.Net.Tests/ResponseBuilders/ResponseWithHandlebarsRandomTests.cs index 3aca51fc..4d119153 100644 --- a/test/WireMock.Net.Tests/ResponseBuilders/ResponseWithHandlebarsRandomTests.cs +++ b/test/WireMock.Net.Tests/ResponseBuilders/ResponseWithHandlebarsRandomTests.cs @@ -34,9 +34,30 @@ namespace WireMock.Net.Tests.ResponseBuilders // Assert JObject j = JObject.FromObject(responseMessage.BodyData.BodyAsJson); - Check.That(j["Text"]).IsNotNull(); + Check.That(j["Text"].Value()).IsNotEmpty(); Check.That(j["Integer"].Value()).IsEqualTo(1000); - Check.That(j["Long"].Value()).IsStrictlyGreaterThan(77777777).And.IsStrictlyLessThan(99999999); + Check.That(j["Long"].Value()).IsStrictlyGreaterThan(77777777).And.IsStrictlyLessThan(99999999); + } + + [Fact] + public async Task Response_ProvideResponseAsync_Handlebars_Random1_Boolean() + { + // Assign + var request = new RequestMessage(new UrlDetails("http://localhost:1234"), "GET", ClientIp); + + var response = Response.Create() + .WithBodyAsJson(new + { + Value = "{{Random Type=\"Boolean\"}}" + }) + .WithTransformer(); + + // Act + var responseMessage = await response.ProvideResponseAsync(request); + + // Assert + JObject j = JObject.FromObject(responseMessage.BodyData.BodyAsJson); + Check.That(j["Value"].Type).IsEqualTo(JTokenType.Boolean); } [Fact] diff --git a/test/WireMock.Net.Tests/ResponseBuilders/ResponseWithHandlebarsXegerTests.cs b/test/WireMock.Net.Tests/ResponseBuilders/ResponseWithHandlebarsXegerTests.cs new file mode 100644 index 00000000..abca4648 --- /dev/null +++ b/test/WireMock.Net.Tests/ResponseBuilders/ResponseWithHandlebarsXegerTests.cs @@ -0,0 +1,60 @@ +using System.Threading.Tasks; +using Newtonsoft.Json.Linq; +using NFluent; +using WireMock.Models; +using WireMock.ResponseBuilders; +using Xunit; + +namespace WireMock.Net.Tests.ResponseBuilders +{ + public class ResponseWithHandlebarsXegerTests + { + private const string ClientIp = "::1"; + + [Fact] + public async Task Response_ProvideResponseAsync_Handlebars_Xeger1() + { + // Assign + var request = new RequestMessage(new UrlDetails("http://localhost:1234"), "GET", ClientIp); + + var response = Response.Create() + .WithBodyAsJson(new + { + Number = "{{Xeger \"[1-9]{1}\\d{3}\"}}", + Postcode = "{{Xeger \"[1-9][0-9]{3}[A-Z]{2}\"}}" + }) + .WithTransformer(); + + // Act + var responseMessage = await response.ProvideResponseAsync(request); + + // Assert + JObject j = JObject.FromObject(responseMessage.BodyData.BodyAsJson); + Check.That(j["Number"].Value()).IsStrictlyGreaterThan(1000).And.IsStrictlyLessThan(9999); + Check.That(j["Postcode"].Value()).IsNotEmpty(); + } + + [Fact] + public async Task Response_ProvideResponseAsync_Handlebars_Xeger2() + { + // Assign + var request = new RequestMessage(new UrlDetails("http://localhost:1234"), "GET", ClientIp); + + var response = Response.Create() + .WithBodyAsJson(new + { + Number = "{{#Xeger \"[1-9]{1}\\d{3}\"}}{{this}}{{/Xeger}}", + Postcode = "{{#Xeger \"[1-9][0-9]{3}[A-Z]{2}\"}}{{this}}{{/Xeger}}" + }) + .WithTransformer(); + + // Act + var responseMessage = await response.ProvideResponseAsync(request); + + // Assert + JObject j = JObject.FromObject(responseMessage.BodyData.BodyAsJson); + Check.That(j["Number"].Value()).IsStrictlyGreaterThan(1000).And.IsStrictlyLessThan(9999); + Check.That(j["Postcode"].Value()).IsNotEmpty(); + } + } +} \ No newline at end of file