Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handlebars Extension #286

Merged
merged 10 commits into from
Jul 3, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
</PropertyGroup>

<PropertyGroup>
<VersionPrefix>1.0.20</VersionPrefix>
<VersionPrefix>1.0.21</VersionPrefix>
</PropertyGroup>

<Choose>
Expand Down
2 changes: 1 addition & 1 deletion GitHubReleaseNotes.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
https://github.com/StefH/GitHubReleaseNotes

GitHubReleaseNotes.exe --output CHANGELOG.md --skip-empty-releases --version 1.0.20.0
GitHubReleaseNotes.exe --output CHANGELOG.md --skip-empty-releases --version 1.0.21.0
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ A C# .NET version based on [mock4net](https://github.com/alexvictoor/mock4net) w

## Key Features
* 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
* Library can be used in unit tests and integration tests
* Runs as a standalone process, as windows service, as Azure/IIS or as docker
* Configurable via a fluent DotNet API, JSON files and JSON over HTTP
* Record/playback of stubs (proxying)
* Per-request conditional proxying
* Stateful behaviour simulation
* Response templating / transformation using Handlebars and extensions
* Can be used locally or in CI/CD scenarios.

## Info
| | |
Expand Down
20 changes: 19 additions & 1 deletion src/WireMock.Net/Handlers/LocalFileSystemHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,24 @@ public class LocalFileSystemHandler : IFileSystemHandler
{
private static readonly string AdminMappingsFolder = Path.Combine("__admin", "mappings");

private readonly string _rootFolder;

/// <summary>
/// Initializes a new instance of the <see cref="LocalFileSystemHandler"/> class.
/// </summary>
public LocalFileSystemHandler() : this(Directory.GetCurrentDirectory())
{
}

/// <summary>
/// Initializes a new instance of the <see cref="LocalFileSystemHandler"/> class.
/// </summary>
/// <param name="rootFolder">The root folder.</param>
public LocalFileSystemHandler(string rootFolder)
{
_rootFolder = rootFolder;
}

/// <inheritdoc cref="IFileSystemHandler.FolderExists"/>
public bool FolderExists(string path)
{
Expand Down Expand Up @@ -38,7 +56,7 @@ public IEnumerable<string> EnumerateFiles(string path)
/// <inheritdoc cref="IFileSystemHandler.GetMappingFolder"/>
public string GetMappingFolder()
{
return Path.Combine(Directory.GetCurrentDirectory(), AdminMappingsFolder);
return Path.Combine(_rootFolder, AdminMappingsFolder);
}

/// <inheritdoc cref="IFileSystemHandler.ReadMappingFile"/>
Expand Down
6 changes: 3 additions & 3 deletions src/WireMock.Net/IMapping.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
using JetBrains.Annotations;
using System;
using System.Threading.Tasks;
using WireMock.Handlers;
using WireMock.Matchers.Request;
using WireMock.ResponseProviders;
using WireMock.Settings;

namespace WireMock
{
Expand Down Expand Up @@ -62,9 +62,9 @@ public interface IMapping
IResponseProvider Provider { get; }

/// <summary>
/// The FileSystemHandler.
/// The FluentMockServerSettings.
/// </summary>
IFileSystemHandler FileSystemHandler { get; }
IFluentMockServerSettings Settings { get; }

/// <summary>
/// Is State started ?
Expand Down
14 changes: 7 additions & 7 deletions src/WireMock.Net/Mapping.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
using JetBrains.Annotations;
using System;
using System.Threading.Tasks;
using WireMock.Handlers;
using WireMock.Matchers.Request;
using WireMock.ResponseProviders;
using WireMock.Settings;

namespace WireMock
{
Expand Down Expand Up @@ -39,8 +39,8 @@ public class Mapping : IMapping
/// <inheritdoc cref="IMapping.Provider" />
public IResponseProvider Provider { get; }

/// <inheritdoc cref="IMapping.FileSystemHandler" />
public IFileSystemHandler FileSystemHandler { get; }
/// <inheritdoc cref="IMapping.Settings" />
public IFluentMockServerSettings Settings { get; }

/// <inheritdoc cref="IMapping.IsStartState" />
public bool IsStartState => Scenario == null || Scenario != null && NextState != null && ExecutionConditionState == null;
Expand All @@ -54,21 +54,21 @@ public class Mapping : IMapping
/// <param name="guid">The unique identifier.</param>
/// <param name="title">The unique title (can be null).</param>
/// <param name="path">The full file path from this mapping title (can be null).</param>
/// <param name="fileSystemHandler">The fileSystemHandler.</param>
/// <param name="settings">The FluentMockServerSettings.</param>
/// <param name="requestMatcher">The request matcher.</param>
/// <param name="provider">The provider.</param>
/// <param name="priority">The priority for this mapping.</param>
/// <param name="scenario">The scenario. [Optional]</param>
/// <param name="executionConditionState">State in which the current mapping can occur. [Optional]</param>
/// <param name="nextState">The next state which will occur after the current mapping execution. [Optional]</param>
public Mapping(Guid guid, [CanBeNull] string title, [CanBeNull] string path,
[NotNull] IFileSystemHandler fileSystemHandler, [NotNull] IRequestMatcher requestMatcher, [NotNull] IResponseProvider provider,
[NotNull] IFluentMockServerSettings settings, [NotNull] IRequestMatcher requestMatcher, [NotNull] IResponseProvider provider,
int priority, [CanBeNull] string scenario, [CanBeNull] string executionConditionState, [CanBeNull] string nextState)
{
Guid = guid;
Title = title;
Path = path;
FileSystemHandler = fileSystemHandler;
Settings = settings;
RequestMatcher = requestMatcher;
Provider = provider;
Priority = priority;
Expand All @@ -80,7 +80,7 @@ public class Mapping : IMapping
/// <inheritdoc cref="IMapping.ResponseToAsync" />
public async Task<ResponseMessage> ResponseToAsync(RequestMessage requestMessage)
{
return await Provider.ProvideResponseAsync(requestMessage, FileSystemHandler);
return await Provider.ProvideResponseAsync(requestMessage, Settings);
}

/// <inheritdoc cref="IMapping.GetRequestMatchResult" />
Expand Down
6 changes: 6 additions & 0 deletions src/WireMock.Net/Matchers/ExactObjectMatcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,14 @@ namespace WireMock.Matchers
/// <seealso cref="IObjectMatcher" />
public class ExactObjectMatcher : IObjectMatcher
{
/// <summary>
/// Gets the value as object.
/// </summary>
public object ValueAsObject { get; }

/// <summary>
/// Gets the value as byte[].
/// </summary>
public byte[] ValueAsBytes { get; }

/// <inheritdoc cref="IMatcher.MatchBehaviour"/>
Expand Down
3 changes: 2 additions & 1 deletion src/WireMock.Net/RequestBuilders/Request.Params.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ public IRequestBuilder WithParam(string key, MatchBehaviour matchBehaviour, bool
return this;
}

/// <inheritdoc cref="IParamsRequestBuilder.WithParam(string, MatchBehaviour, IStringMatcher[])"/>
public IRequestBuilder WithParam(string key, MatchBehaviour matchBehaviour, params IStringMatcher[] matchers)
{
return WithParam(key, matchBehaviour, false, matchers);
Expand All @@ -77,7 +78,7 @@ public IRequestBuilder WithParam(string key, MatchBehaviour matchBehaviour, bool
return this;
}

/// <inheritdoc cref="IParamsRequestBuilder.WithParam(Func{IDictionary{string, WireMockList{T}}, bool}[])"/>
/// <inheritdoc cref="IParamsRequestBuilder.WithParam(Func{IDictionary{string, WireMockList{string}}, bool}[])"/>
public IRequestBuilder WithParam(params Func<IDictionary<string, WireMockList<string>>, bool>[] funcs)
{
Check.NotNullOrEmpty(funcs, nameof(funcs));
Expand Down
10 changes: 6 additions & 4 deletions src/WireMock.Net/ResponseBuilders/Response.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using WireMock.Handlers;
using WireMock.Http;
using WireMock.ResponseProviders;
using WireMock.Settings;
using WireMock.Transformers;
using WireMock.Util;
Expand Down Expand Up @@ -371,7 +371,8 @@ private IResponseBuilder WithCallbackInternal(bool withCallbackUsed, Func<Reques
return this;
}

public async Task<ResponseMessage> ProvideResponseAsync(RequestMessage requestMessage, IFileSystemHandler fileSystemHandler)
/// <inheritdoc cref="IResponseProvider.ProvideResponseAsync(RequestMessage, IFluentMockServerSettings)"/>
public async Task<ResponseMessage> ProvideResponseAsync(RequestMessage requestMessage, IFluentMockServerSettings settings)
{
Check.NotNull(requestMessage, nameof(requestMessage));

Expand Down Expand Up @@ -410,13 +411,14 @@ public async Task<ResponseMessage> ProvideResponseAsync(RequestMessage requestMe

if (UseTransformer)
{
var responseMessageTransformer = new ResponseMessageTransformer(fileSystemHandler);
var factory = new HandlebarsContextFactory(settings.FileSystemHandler, settings.HandlebarsRegistrationCallback);
var responseMessageTransformer = new ResponseMessageTransformer(factory);
return responseMessageTransformer.Transform(requestMessage, ResponseMessage);
}

if (!UseTransformer && ResponseMessage.BodyData?.BodyAsFileIsCached == true)
{
ResponseMessage.BodyData.BodyAsBytes = fileSystemHandler.ReadResponseBodyAsFile(ResponseMessage.BodyData.BodyAsFile);
ResponseMessage.BodyData.BodyAsBytes = settings.FileSystemHandler.ReadResponseBodyAsFile(ResponseMessage.BodyData.BodyAsFile);
ResponseMessage.BodyData.BodyAsFile = null;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using System;
using System.Threading.Tasks;
using WireMock.Handlers;
using WireMock.Settings;

namespace WireMock.ResponseProviders
{
Expand All @@ -13,7 +13,7 @@ public DynamicAsyncResponseProvider(Func<RequestMessage, Task<ResponseMessage>>
_responseMessageFunc = responseMessageFunc;
}

public Task<ResponseMessage> ProvideResponseAsync(RequestMessage requestMessage, IFileSystemHandler fileSystemHandler)
public Task<ResponseMessage> ProvideResponseAsync(RequestMessage requestMessage, IFluentMockServerSettings settings)
{
return _responseMessageFunc(requestMessage);
}
Expand Down
4 changes: 2 additions & 2 deletions src/WireMock.Net/ResponseProviders/DynamicResponseProvider.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using System;
using System.Threading.Tasks;
using WireMock.Handlers;
using WireMock.Settings;

namespace WireMock.ResponseProviders
{
Expand All @@ -13,7 +13,7 @@ public DynamicResponseProvider(Func<RequestMessage, ResponseMessage> responseMes
_responseMessageFunc = responseMessageFunc;
}

public Task<ResponseMessage> ProvideResponseAsync(RequestMessage requestMessage, IFileSystemHandler fileSystemHandler)
public Task<ResponseMessage> ProvideResponseAsync(RequestMessage requestMessage, IFluentMockServerSettings settings)
{
return Task.FromResult(_responseMessageFunc(requestMessage));
}
Expand Down
10 changes: 5 additions & 5 deletions src/WireMock.Net/ResponseProviders/IResponseProvider.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using System.Threading.Tasks;
using JetBrains.Annotations;
using WireMock.Handlers;
using JetBrains.Annotations;
using System.Threading.Tasks;
using WireMock.Settings;

namespace WireMock.ResponseProviders
{
Expand All @@ -13,8 +13,8 @@ public interface IResponseProvider
/// The provide response.
/// </summary>
/// <param name="requestMessage">The request.</param>
/// <param name="fileSystemHandler">The fileSystemHandler.</param>
/// <param name="settings">The FluentMockServerSettings.</param>
/// <returns>The <see cref="ResponseMessage"/>.</returns>
Task<ResponseMessage> ProvideResponseAsync([NotNull] RequestMessage requestMessage, [NotNull] IFileSystemHandler fileSystemHandler);
Task<ResponseMessage> ProvideResponseAsync([NotNull] RequestMessage requestMessage, [NotNull] IFluentMockServerSettings settings);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System;
using System.Threading.Tasks;
using WireMock.Handlers;
using WireMock.Settings;

namespace WireMock.ResponseProviders
Expand All @@ -16,7 +15,7 @@ public ProxyAsyncResponseProvider(Func<RequestMessage, IFluentMockServerSettings
_settings = settings;
}

public Task<ResponseMessage> ProvideResponseAsync(RequestMessage requestMessage, IFileSystemHandler fileSystemHandler)
public Task<ResponseMessage> ProvideResponseAsync(RequestMessage requestMessage, IFluentMockServerSettings settings)
{
return _responseMessageFunc(requestMessage, _settings);
}
Expand Down
47 changes: 30 additions & 17 deletions src/WireMock.Net/Server/FluentMockServer.Admin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -164,10 +164,10 @@ public void WatchStaticMappings([CanBeNull] string folder = null)
{
if (folder == null)
{
folder = _fileSystemHandler.GetMappingFolder();
folder = _settings.FileSystemHandler.GetMappingFolder();
}

if (!_fileSystemHandler.FolderExists(folder))
if (!_settings.FileSystemHandler.FolderExists(folder))
{
return;
}
Expand All @@ -177,17 +177,23 @@ public void WatchStaticMappings([CanBeNull] string folder = null)
var watcher = new EnhancedFileSystemWatcher(folder, "*.json", EnhancedFileSystemWatcherTimeoutMs);
watcher.Created += (sender, args) =>
{
_logger.Info("New MappingFile created : '{0}'", args.FullPath);
ReadStaticMappingAndAddOrUpdate(args.FullPath);
_logger.Info("MappingFile created : '{0}', reading file.", args.FullPath);
if (!ReadStaticMappingAndAddOrUpdate(args.FullPath))
{
_logger.Error("Unable to read MappingFile '{0}'.", args.FullPath);
}
};
watcher.Changed += (sender, args) =>
{
_logger.Info("New MappingFile updated : '{0}'", args.FullPath);
ReadStaticMappingAndAddOrUpdate(args.FullPath);
_logger.Info("MappingFile updated : '{0}', reading file.", args.FullPath);
if (!ReadStaticMappingAndAddOrUpdate(args.FullPath))
{
_logger.Error("Unable to read MappingFile '{0}'.", args.FullPath);
}
};
watcher.Deleted += (sender, args) =>
{
_logger.Info("New MappingFile deleted : '{0}'", args.FullPath);
_logger.Info("MappingFile deleted : '{0}'", args.FullPath);
string filenameWithoutExtension = Path.GetFileNameWithoutExtension(args.FullPath);

if (Guid.TryParse(filenameWithoutExtension, out Guid guidFromFilename))
Expand All @@ -208,24 +214,31 @@ public void WatchStaticMappings([CanBeNull] string folder = null)
/// </summary>
/// <param name="path">The path.</param>
[PublicAPI]
public void ReadStaticMappingAndAddOrUpdate([NotNull] string path)
public bool ReadStaticMappingAndAddOrUpdate([NotNull] string path)
{
Check.NotNull(path, nameof(path));

string filenameWithoutExtension = Path.GetFileNameWithoutExtension(path);

var mappingModels = DeserializeObjectToArray<MappingModel>(JsonConvert.DeserializeObject(_fileSystemHandler.ReadMappingFile(path)));
foreach (var mappingModel in mappingModels)
if (FileHelper.TryReadMappingFileWithRetryAndDelay(_fileSystemHandler, path, out string value))
{
if (mappingModels.Length == 1 && Guid.TryParse(filenameWithoutExtension, out Guid guidFromFilename))
{
DeserializeAndAddOrUpdateMapping(mappingModel, guidFromFilename, path);
}
else
var mappingModels = DeserializeObjectToArray<MappingModel>(JsonConvert.DeserializeObject(value));
foreach (var mappingModel in mappingModels)
{
DeserializeAndAddOrUpdateMapping(mappingModel, null, path);
if (mappingModels.Length == 1 && Guid.TryParse(filenameWithoutExtension, out Guid guidFromFilename))
{
DeserializeAndAddOrUpdateMapping(mappingModel, guidFromFilename, path);
}
else
{
DeserializeAndAddOrUpdateMapping(mappingModel, null, path);
}
}

return true;
}

return false;
}
#endregion

Expand Down Expand Up @@ -302,7 +315,7 @@ private IMapping ToMapping(RequestMessage requestMessage, ResponseMessage respon

var response = Response.Create(responseMessage);

return new Mapping(Guid.NewGuid(), string.Empty, null, _fileSystemHandler, request, response, 0, null, null, null);
return new Mapping(Guid.NewGuid(), string.Empty, null, _settings, request, response, 0, null, null, null);
}
#endregion

Expand Down
Loading