From 368fdd4c7d1973b64e8ab4f47f78999917289713 Mon Sep 17 00:00:00 2001 From: Kashif Jamal Soofi Date: Thu, 9 Jan 2020 16:51:44 +0000 Subject: [PATCH] Feature/xpath transformer (#398) * XPath transformer, added handlerbars helper to select nodes using xpath and setting the outerxml value in response * Added test to select attribute value and node text * Removed extra empty lines --- .../Transformers/HandleBarsHelpers.cs | 2 + .../Transformers/HandleBarsXPath.cs | 102 +++++++++++ .../ResponseWithHandlebarsXPathTests.cs | 166 ++++++++++++++++++ 3 files changed, 270 insertions(+) create mode 100644 src/WireMock.Net/Transformers/HandleBarsXPath.cs create mode 100644 test/WireMock.Net.Tests/ResponseBuilders/ResponseWithHandlebarsXPathTests.cs diff --git a/src/WireMock.Net/Transformers/HandleBarsHelpers.cs b/src/WireMock.Net/Transformers/HandleBarsHelpers.cs index e7f63af5..2bb64cc6 100644 --- a/src/WireMock.Net/Transformers/HandleBarsHelpers.cs +++ b/src/WireMock.Net/Transformers/HandleBarsHelpers.cs @@ -17,6 +17,8 @@ namespace WireMock.Transformers HandleBarsXeger.Register(handlebarsContext); + HandleBarsXPath.Register(handlebarsContext); + HandleBarsFile.Register(handlebarsContext, fileSystemHandler); } } diff --git a/src/WireMock.Net/Transformers/HandleBarsXPath.cs b/src/WireMock.Net/Transformers/HandleBarsXPath.cs new file mode 100644 index 00000000..6118e70a --- /dev/null +++ b/src/WireMock.Net/Transformers/HandleBarsXPath.cs @@ -0,0 +1,102 @@ +using HandlebarsDotNet; +using System; +using System.Text; +using System.Xml; +using System.Xml.XPath; +using WireMock.Validation; +#if !NETSTANDARD1_3 +using Wmhelp.XPath2; +#endif + +namespace WireMock.Transformers +{ + internal static class HandleBarsXPath + { + public static void Register(IHandlebars handlebarsContext) + { + handlebarsContext.RegisterHelper("XPath.SelectSingleNode", (writer, context, arguments) => + { + (XPathNavigator nav, string xpath) = ParseArguments(arguments); + + try + { +#if NETSTANDARD1_3 + var result = nav.SelectSingleNode(xpath); +#else + var result = nav.XPath2SelectSingleNode(xpath); +#endif + writer.WriteSafeString(result.OuterXml); + } + catch (Exception) + { + // Ignore Exception + } + }); + + handlebarsContext.RegisterHelper("XPath.SelectNodes", (writer, context, arguments) => + { + (XPathNavigator nav, string xpath) = ParseArguments(arguments); + + try + { +#if NETSTANDARD1_3 + var result = nav.Select(xpath); +#else + var result = nav.XPath2SelectNodes(xpath); +#endif + var resultXml = new StringBuilder(); + foreach (XPathNavigator node in result) + { + resultXml.Append(node.OuterXml); + } + + writer.WriteSafeString(resultXml); + } + catch (Exception) + { + // Ignore Exception + } + }); + + handlebarsContext.RegisterHelper("XPath.Evaluate", (writer, context, arguments) => + { + (XPathNavigator nav, string xpath) = ParseArguments(arguments); + + try + { +#if NETSTANDARD1_3 + var result = nav.Evaluate(xpath); +#else + var result = nav.XPath2Evaluate(xpath); +#endif + writer.WriteSafeString(result); + } + catch (Exception) + { + // Ignore Exception + } + }); + } + + private static (XPathNavigator nav, string xpath) ParseArguments(object[] arguments) + { + Check.Condition(arguments, args => args.Length == 2, nameof(arguments)); + Check.NotNull(arguments[0], "arguments[0]"); + Check.NotNullOrEmpty(arguments[1] as string, "arguments[1]"); + + XPathNavigator nav; + + switch (arguments[0]) + { + case string stringValue: + nav = new XmlDocument { InnerXml = stringValue }.CreateNavigator(); + break; + + default: + throw new NotSupportedException($"The value '{arguments[0]}' with type '{arguments[0]?.GetType()}' cannot be used in Handlebars XPath."); + } + + return (nav, (string)arguments[1]); + } + } +} \ No newline at end of file diff --git a/test/WireMock.Net.Tests/ResponseBuilders/ResponseWithHandlebarsXPathTests.cs b/test/WireMock.Net.Tests/ResponseBuilders/ResponseWithHandlebarsXPathTests.cs new file mode 100644 index 00000000..df9e96d2 --- /dev/null +++ b/test/WireMock.Net.Tests/ResponseBuilders/ResponseWithHandlebarsXPathTests.cs @@ -0,0 +1,166 @@ +using System.Threading.Tasks; +using System.Xml; +using NFluent; +using WireMock.Models; +using WireMock.ResponseBuilders; +using WireMock.Settings; +using WireMock.Types; +using WireMock.Util; +using Xunit; +#if !NETSTANDARD1_3 +using Wmhelp.XPath2; +#endif + +namespace WireMock.Net.Tests.ResponseBuilders +{ + public class ResponseWithHandlebarsXPathTests + { + private readonly WireMockServerSettings _settings = new WireMockServerSettings(); + private const string ClientIp = "::1"; + + [Fact] + public async Task Response_ProvideResponse_Handlebars_XPath_SelectSingleNode_Request_BodyAsString() + { + // Assign + var body = new BodyData + { + BodyAsString = @" + abc + def + xyz + ", + DetectedBodyType = BodyType.String + }; + + var request = new RequestMessage(new UrlDetails("http://localhost:1234"), "POST", ClientIp, body); + + var response = Response.Create() + .WithHeader("Content-Type", "application/xml") + .WithBody("{{XPath.SelectSingleNode request.body \"/todo-list/todo-item[1]\"}}") + .WithTransformer(); + + // Act + var responseMessage = await response.ProvideResponseAsync(request, _settings); + + // Assert + var nav = new XmlDocument { InnerXml = responseMessage.BodyData.BodyAsString }.CreateNavigator(); + var node = nav.XPath2SelectSingleNode("/response/todo-item"); + Check.That(node.Value).Equals("abc"); + Check.That(node.GetAttribute("id", "")).Equals("a1"); + } + + [Fact] + public async Task Response_ProvideResponse_Handlebars_XPath_SelectSingleNode_Text_Request_BodyAsString() + { + // Assign + var body = new BodyData + { + BodyAsString = @" + abc + def + xyz + ", + DetectedBodyType = BodyType.String + }; + + var request = new RequestMessage(new UrlDetails("http://localhost:1234"), "POST", ClientIp, body); + + var response = Response.Create() + .WithHeader("Content-Type", "application/xml") + .WithBody("{{XPath.SelectSingleNode request.body \"/todo-list/todo-item[1]/text()\"}}") + .WithTransformer(); + + // Act + var responseMessage = await response.ProvideResponseAsync(request, _settings); + + // Assert + Check.That(responseMessage.BodyData.BodyAsString).IsEqualTo("abc"); + } + + [Fact] + public async Task Response_ProvideResponse_Handlebars_XPath_SelectNodes_Request_BodyAsString() + { + // Assign + var body = new BodyData + { + BodyAsString = @" + abc + def + xyz + ", + DetectedBodyType = BodyType.String + }; + + var request = new RequestMessage(new UrlDetails("http://localhost:1234"), "POST", ClientIp, body); + + var response = Response.Create() + .WithHeader("Content-Type", "application/xml") + .WithBody("{{XPath.SelectNodes request.body \"/todo-list/todo-item\"}}") + .WithTransformer(); + + // Act + var responseMessage = await response.ProvideResponseAsync(request, _settings); + + // Assert + var nav = new XmlDocument { InnerXml = responseMessage.BodyData.BodyAsString }.CreateNavigator(); + var nodes = nav.XPath2SelectNodes("/response/todo-item"); + Check.That(nodes.Count + 1).IsEqualTo(3); + } + + [Fact] + public async Task Response_ProvideResponse_Handlebars_XPath_Evaluate_Request_BodyAsString() + { + // Assign + var body = new BodyData + { + BodyAsString = @" + abc + def + xyz + ", + DetectedBodyType = BodyType.String + }; + + var request = new RequestMessage(new UrlDetails("http://localhost:1234"), "POST", ClientIp, body); + + var response = Response.Create() + .WithHeader("Content-Type", "application/xml") + .WithBody("{{XPath.Evaluate request.body \"boolean(/todo-list[count(todo-item) = 3])\"}}") + .WithTransformer(); + + // Act + var responseMessage = await response.ProvideResponseAsync(request, _settings); + + // Assert + Check.That(responseMessage.BodyData.BodyAsString).IsEqualIgnoringCase("True"); + } + + [Fact] + public async Task Response_ProvideResponse_Handlebars_XPath_Evaluate_Attribute_Request_BodyAsString() + { + // Assign + var body = new BodyData + { + BodyAsString = @" + abc + def + xyz + ", + DetectedBodyType = BodyType.String + }; + + var request = new RequestMessage(new UrlDetails("http://localhost:1234"), "POST", ClientIp, body); + + var response = Response.Create() + .WithHeader("Content-Type", "application/xml") + .WithBody("{{XPath.Evaluate request.body \"string(/todo-list/todo-item[1]/@id)\"}}") + .WithTransformer(); + + // Act + var responseMessage = await response.ProvideResponseAsync(request, _settings); + + // Assert + Check.That(responseMessage.BodyData.BodyAsString).IsEqualTo("a1"); + } + } +}