diff --git a/Directory.Build.props b/Directory.Build.props
index 08a194ea..25a48291 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -4,7 +4,7 @@
- 1.0.16
+ 1.0.17
diff --git a/GitHubReleaseNotes.txt b/GitHubReleaseNotes.txt
index 9d19bc80..c48b3317 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.16.0
\ No newline at end of file
+GitHubReleaseNotes.exe --output CHANGELOG.md --skip-empty-releases --version 1.0.17.0
\ No newline at end of file
diff --git a/examples/WireMock.Net.Console.Net452.Classic/CustomFileSystemFileHandler.cs b/examples/WireMock.Net.Console.Net452.Classic/CustomFileSystemFileHandler.cs
index fcad7619..699ec4b4 100644
--- a/examples/WireMock.Net.Console.Net452.Classic/CustomFileSystemFileHandler.cs
+++ b/examples/WireMock.Net.Console.Net452.Classic/CustomFileSystemFileHandler.cs
@@ -50,6 +50,12 @@ namespace WireMock.Net.ConsoleApplication
return File.ReadAllBytes(Path.GetFileName(path) == path ? Path.Combine(GetMappingFolder(), path) : path);
}
+ ///
+ public string ReadResponseBodyAsString(string path)
+ {
+ return File.ReadAllText(Path.GetFileName(path) == path ? Path.Combine(GetMappingFolder(), path) : path);
+ }
+
///
public bool FileExists(string path)
{
diff --git a/src/WireMock.Net/Handlers/IFileSystemHandler.cs b/src/WireMock.Net/Handlers/IFileSystemHandler.cs
index 5ef5ef5c..d01e0469 100644
--- a/src/WireMock.Net/Handlers/IFileSystemHandler.cs
+++ b/src/WireMock.Net/Handlers/IFileSystemHandler.cs
@@ -49,12 +49,19 @@ namespace WireMock.Handlers
void WriteMappingFile([NotNull] string path, [NotNull] string text);
///
- /// Read a response body file as text.
+ /// Read a response body file as byte[].
///
/// The path or filename from the file to read.
/// The file content as bytes.
byte[] ReadResponseBodyAsFile([NotNull] string path);
+ ///
+ /// Read a response body file as text.
+ ///
+ /// The path or filename from the file to read.
+ /// The file content as text.
+ string ReadResponseBodyAsString([NotNull] string path);
+
///
/// Delete a file.
///
diff --git a/src/WireMock.Net/Handlers/LocalFileSystemHandler.cs b/src/WireMock.Net/Handlers/LocalFileSystemHandler.cs
index d70bdc53..eeb957e8 100644
--- a/src/WireMock.Net/Handlers/LocalFileSystemHandler.cs
+++ b/src/WireMock.Net/Handlers/LocalFileSystemHandler.cs
@@ -68,6 +68,16 @@ namespace WireMock.Handlers
return File.ReadAllBytes(Path.GetFileName(path) == path ? Path.Combine(GetMappingFolder(), path) : path);
}
+ ///
+ public string ReadResponseBodyAsString(string path)
+ {
+ Check.NotNullOrEmpty(path, nameof(path));
+
+ // In case the path is a filename, the path will be adjusted to the MappingFolder.
+ // Else the path will just be as-is.
+ return File.ReadAllText(Path.GetFileName(path) == path ? Path.Combine(GetMappingFolder(), path) : path);
+ }
+
///
public bool FileExists(string filename)
{
diff --git a/src/WireMock.Net/ResponseBuilders/Response.cs b/src/WireMock.Net/ResponseBuilders/Response.cs
index 82ba0852..6e2fd7cf 100644
--- a/src/WireMock.Net/ResponseBuilders/Response.cs
+++ b/src/WireMock.Net/ResponseBuilders/Response.cs
@@ -21,7 +21,8 @@ namespace WireMock.ResponseBuilders
///
public class Response : IResponseBuilder
{
- private readonly IFileSystemHandler _fileSystemHandler = new LocalFileSystemHandler();
+ private readonly IFileSystemHandler _fileSystemHandler;
+ private readonly ResponseMessageTransformer _responseMessageTransformer;
private HttpClient _httpClientForProxy;
///
@@ -93,6 +94,9 @@ namespace WireMock.ResponseBuilders
private Response(ResponseMessage responseMessage)
{
ResponseMessage = responseMessage;
+
+ _fileSystemHandler = new LocalFileSystemHandler();
+ _responseMessageTransformer = new ResponseMessageTransformer(_fileSystemHandler);
}
///
@@ -417,7 +421,7 @@ namespace WireMock.ResponseBuilders
if (UseTransformer)
{
- return ResponseMessageTransformer.Transform(requestMessage, ResponseMessage);
+ return _responseMessageTransformer.Transform(requestMessage, ResponseMessage);
}
// Just return normal defined ResponseMessage
diff --git a/src/WireMock.Net/Transformers/HandleBarsFile.cs b/src/WireMock.Net/Transformers/HandleBarsFile.cs
new file mode 100644
index 00000000..51a35ccf
--- /dev/null
+++ b/src/WireMock.Net/Transformers/HandleBarsFile.cs
@@ -0,0 +1,41 @@
+using HandlebarsDotNet;
+using System;
+using WireMock.Handlers;
+using WireMock.Validation;
+
+namespace WireMock.Transformers
+{
+ internal static class HandleBarsFile
+ {
+ public static void Register(IHandlebars handlebarsContext, IFileSystemHandler fileSystemHandler)
+ {
+ handlebarsContext.RegisterHelper("File", (writer, context, arguments) =>
+ {
+ string value = ParseArgumentAndReadFileFragment(handlebarsContext, context, fileSystemHandler, arguments);
+ writer.Write(value);
+ });
+
+ handlebarsContext.RegisterHelper("File", (writer, options, context, arguments) =>
+ {
+ string value = ParseArgumentAndReadFileFragment(handlebarsContext, context, fileSystemHandler, arguments);
+ options.Template(writer, value);
+ });
+ }
+
+ private static string ParseArgumentAndReadFileFragment(IHandlebars handlebarsContext, dynamic context, IFileSystemHandler fileSystemHandler, object[] arguments)
+ {
+ Check.Condition(arguments, args => args.Length == 1, nameof(arguments));
+ Check.NotNull(arguments[0], "arguments[0]");
+
+ switch (arguments[0])
+ {
+ case string path:
+ var templateFunc = handlebarsContext.Compile(path);
+ string transformed = templateFunc(context);
+ return fileSystemHandler.ReadResponseBodyAsString(transformed);
+ }
+
+ throw new NotSupportedException($"The value '{arguments[0]}' with type '{arguments[0]?.GetType()}' cannot be used in Handlebars File.");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/WireMock.Net/Transformers/HandleBarsHelpers.cs b/src/WireMock.Net/Transformers/HandleBarsHelpers.cs
index b40c5bae..e7f63af5 100644
--- a/src/WireMock.Net/Transformers/HandleBarsHelpers.cs
+++ b/src/WireMock.Net/Transformers/HandleBarsHelpers.cs
@@ -1,10 +1,11 @@
using HandlebarsDotNet;
+using WireMock.Handlers;
namespace WireMock.Transformers
{
internal static class HandlebarsHelpers
{
- public static void Register(IHandlebars handlebarsContext)
+ public static void Register(IHandlebars handlebarsContext, IFileSystemHandler fileSystemHandler)
{
HandleBarsRegex.Register(handlebarsContext);
@@ -15,6 +16,8 @@ namespace WireMock.Transformers
HandleBarsRandom.Register(handlebarsContext);
HandleBarsXeger.Register(handlebarsContext);
+
+ HandleBarsFile.Register(handlebarsContext, fileSystemHandler);
}
}
}
\ No newline at end of file
diff --git a/src/WireMock.Net/Transformers/ResponseMessageTransformer.cs b/src/WireMock.Net/Transformers/ResponseMessageTransformer.cs
index e2c742dc..5861e0d8 100644
--- a/src/WireMock.Net/Transformers/ResponseMessageTransformer.cs
+++ b/src/WireMock.Net/Transformers/ResponseMessageTransformer.cs
@@ -1,14 +1,17 @@
using HandlebarsDotNet;
+using JetBrains.Annotations;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
+using WireMock.Handlers;
using WireMock.Util;
+using WireMock.Validation;
namespace WireMock.Transformers
{
- internal static class ResponseMessageTransformer
+ internal class ResponseMessageTransformer
{
private static readonly HandlebarsConfiguration HandlebarsConfiguration = new HandlebarsConfiguration
{
@@ -17,12 +20,14 @@ namespace WireMock.Transformers
private static readonly IHandlebars HandlebarsContext = Handlebars.Create(HandlebarsConfiguration);
- static ResponseMessageTransformer()
+ public ResponseMessageTransformer([NotNull] IFileSystemHandler fileSystemHandler)
{
- HandlebarsHelpers.Register(HandlebarsContext);
+ Check.NotNull(fileSystemHandler, nameof(fileSystemHandler));
+
+ HandlebarsHelpers.Register(HandlebarsContext, fileSystemHandler);
}
- public static ResponseMessage Transform(RequestMessage requestMessage, ResponseMessage original)
+ public ResponseMessage Transform(RequestMessage requestMessage, ResponseMessage original)
{
var responseMessage = new ResponseMessage { StatusCode = original.StatusCode };
@@ -90,14 +95,14 @@ namespace WireMock.Transformers
};
}
- private static void WalkNode(JToken node, object template)
+ private static void WalkNode(JToken node, object context)
{
if (node.Type == JTokenType.Object)
{
// In case of Object, loop all children. Do a ToArray() to avoid `Collection was modified` exceptions.
foreach (JProperty child in node.Children().ToArray())
{
- WalkNode(child.Value, template);
+ WalkNode(child.Value, context);
}
}
else if (node.Type == JTokenType.Array)
@@ -105,7 +110,7 @@ namespace WireMock.Transformers
// In case of Array, loop all items. Do a ToArray() to avoid `Collection was modified` exceptions.
foreach (JToken child in node.Children().ToArray())
{
- WalkNode(child, template);
+ WalkNode(child, context);
}
}
else if (node.Type == JTokenType.String)
@@ -118,7 +123,7 @@ namespace WireMock.Transformers
}
var templateForStringValue = HandlebarsContext.Compile(stringValue);
- string transformedString = templateForStringValue(template);
+ string transformedString = templateForStringValue(context);
if (!string.Equals(stringValue, transformedString))
{
ReplaceNodeValue(node, transformedString);
diff --git a/src/WireMock.Net/WireMock.Net.csproj b/src/WireMock.Net/WireMock.Net.csproj
index 607cf856..355acfcf 100644
--- a/src/WireMock.Net/WireMock.Net.csproj
+++ b/src/WireMock.Net/WireMock.Net.csproj
@@ -62,7 +62,7 @@
runtime; build; native; contentfiles; analyzers
-
+
diff --git a/test/WireMock.Net.Tests/ResponseBuilders/ResponseWithHandlebarsFileTests.cs b/test/WireMock.Net.Tests/ResponseBuilders/ResponseWithHandlebarsFileTests.cs
new file mode 100644
index 00000000..27eff07b
--- /dev/null
+++ b/test/WireMock.Net.Tests/ResponseBuilders/ResponseWithHandlebarsFileTests.cs
@@ -0,0 +1,102 @@
+using Moq;
+using Newtonsoft.Json.Linq;
+using NFluent;
+using System;
+using System.Threading.Tasks;
+using WireMock.Handlers;
+using WireMock.Models;
+using WireMock.ResponseBuilders;
+using WireMock.Transformers;
+using Xunit;
+
+namespace WireMock.Net.Tests.ResponseBuilders
+{
+ public class ResponseWithHandlebarsFileTests
+ {
+ private readonly Mock _filesystemHandlerMock;
+ private const string ClientIp = "::1";
+
+ public ResponseWithHandlebarsFileTests()
+ {
+ _filesystemHandlerMock = new Mock(MockBehavior.Strict);
+ _filesystemHandlerMock.Setup(fs => fs.ReadResponseBodyAsString(It.IsAny())).Returns("abc");
+ }
+
+ [Fact]
+ public async Task Response_ProvideResponseAsync_Handlebars_File()
+ {
+ // Assign
+ var request = new RequestMessage(new UrlDetails("http://localhost:1234"), "GET", ClientIp);
+
+ var response = Response.Create()
+ .WithBodyAsJson(new
+ {
+ Data = "{{File \"x.json\"}}"
+ })
+ .WithTransformer();
+
+ response.SetPrivateFieldValue("_fileSystemHandler", _filesystemHandlerMock.Object);
+ response.SetPrivateFieldValue("_responseMessageTransformer", new ResponseMessageTransformer(_filesystemHandlerMock.Object));
+
+ // Act
+ var responseMessage = await response.ProvideResponseAsync(request);
+
+ // Assert
+ JObject j = JObject.FromObject(responseMessage.BodyData.BodyAsJson);
+ Check.That(j["Data"].Value()).Equals("abc");
+
+ // Verify
+ _filesystemHandlerMock.Verify(fs => fs.ReadResponseBodyAsString("x.json"), Times.Once);
+ _filesystemHandlerMock.VerifyNoOtherCalls();
+ }
+
+ [Fact]
+ public async Task Response_ProvideResponseAsync_Handlebars_File_Replace()
+ {
+ // Assign
+ var request = new RequestMessage(new UrlDetails("http://localhost:1234?id=x"), "GET", ClientIp);
+
+ var response = Response.Create()
+ .WithBodyAsJson(new
+ {
+ Data = "{{File \"{{request.query.id}}.json\"}}"
+ })
+ .WithTransformer();
+
+ response.SetPrivateFieldValue("_fileSystemHandler", _filesystemHandlerMock.Object);
+ response.SetPrivateFieldValue("_responseMessageTransformer", new ResponseMessageTransformer(_filesystemHandlerMock.Object));
+
+ // Act
+ var responseMessage = await response.ProvideResponseAsync(request);
+
+ // Assert
+ JObject j = JObject.FromObject(responseMessage.BodyData.BodyAsJson);
+ Check.That(j["Data"].Value()).Equals("abc");
+
+ // Verify
+ _filesystemHandlerMock.Verify(fs => fs.ReadResponseBodyAsString("x.json"), Times.Once);
+ _filesystemHandlerMock.VerifyNoOtherCalls();
+ }
+
+ [Fact]
+ public void Response_ProvideResponseAsync_Handlebars_File_WithMissingArgument_ThrowsArgumentOutOfRangeException()
+ {
+ // Assign
+ var request = new RequestMessage(new UrlDetails("http://localhost:1234"), "GET", ClientIp);
+
+ var response = Response.Create()
+ .WithBodyAsJson(new
+ {
+ Data = "{{File}}"
+ })
+ .WithTransformer();
+
+ // Act
+ Check.ThatAsyncCode(() => response.ProvideResponseAsync(request)).Throws();
+
+ // Verify
+ _filesystemHandlerMock.Verify(fs => fs.ReadResponseBodyAsString(It.IsAny()), Times.Never);
+ _filesystemHandlerMock.VerifyNoOtherCalls();
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/WireMock.Net.Tests/TestUtils.cs b/test/WireMock.Net.Tests/TestUtils.cs
index 5cd05a10..bd804944 100644
--- a/test/WireMock.Net.Tests/TestUtils.cs
+++ b/test/WireMock.Net.Tests/TestUtils.cs
@@ -1,4 +1,5 @@
-using System.Reflection;
+using System;
+using System.Reflection;
namespace WireMock.Net.Tests
{
@@ -10,5 +11,53 @@ namespace WireMock.Net.Tests
return (T)field.GetValue(obj);
}
+
+ ///
+ /// Set a _private_ Field Value on a given Object
+ ///
+ /// Type of the Property
+ /// Object from where the Property Value is returned
+ /// Property name as string.
+ /// the value to set
+ public static void SetPrivateFieldValue(this object obj, string propertyName, T value)
+ {
+ if (obj == null)
+ {
+ throw new ArgumentNullException(nameof(obj));
+ }
+
+ Type t = obj.GetType();
+ FieldInfo fi = null;
+ while (fi == null && t != null)
+ {
+ fi = t.GetField(propertyName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
+ t = t.BaseType;
+ }
+
+ if (fi == null)
+ {
+ throw new ArgumentOutOfRangeException(nameof(propertyName), $"Field {propertyName} was not found in Type {obj.GetType().FullName}");
+ }
+
+ fi.SetValue(obj, value);
+ }
+
+ ///
+ /// Sets a _private_ Property Value from a given Object.
+ ///
+ /// Type of the Property
+ /// Object from where the Property Value is set
+ /// Property name as string.
+ /// Value to set.
+ public static void SetPrivatePropertyValue(this object obj, string propertyName, T value)
+ {
+ Type t = obj.GetType();
+ if (t.GetProperty(propertyName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) == null)
+ {
+ throw new ArgumentOutOfRangeException(nameof(propertyName), $"Property {propertyName} was not found in Type {obj.GetType().FullName}");
+ }
+
+ t.InvokeMember(propertyName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.SetProperty | BindingFlags.Instance, null, obj, new object[] { value });
+ }
}
}
\ No newline at end of file
diff --git a/test/WireMock.Net.Tests/WireMock.Net.Tests.csproj b/test/WireMock.Net.Tests/WireMock.Net.Tests.csproj
index e9d198e1..6936f5e1 100644
--- a/test/WireMock.Net.Tests/WireMock.Net.Tests.csproj
+++ b/test/WireMock.Net.Tests/WireMock.Net.Tests.csproj
@@ -36,6 +36,8 @@
+
+
@@ -50,6 +52,7 @@
all
runtime; build; native; contentfiles; analyzers
+