Merge branch 'master' into webapp

This commit is contained in:
Stef Heyenrath
2019-05-04 10:42:25 +02:00
31 changed files with 1206 additions and 68 deletions

View File

@@ -167,5 +167,42 @@ namespace WireMock.Client
/// </summary>
[Post("__admin/scenarios")]
Task<StatusModel> ResetScenariosAsync();
/// <summary>
/// Create a new File
/// </summary>
/// <param name="filename">The filename</param>
/// <param name="body">The body</param>
[Post("__admin/files/{filename}")]
Task<StatusModel> PostFileAsync([Path] string filename, [Body] string body);
/// <summary>
/// Update an existing File
/// </summary>
/// <param name="filename">The filename</param>
/// <param name="body">The body</param>
[Put("__admin/files/{filename}")]
Task<StatusModel> PutFileAsync([Path] string filename, [Body] string body);
/// <summary>
/// Get the content of an existing File
/// </summary>
/// <param name="filename">The filename</param>
[Get("__admin/files/{filename}")]
Task<string> GetFileAsync([Path] string filename);
/// <summary>
/// Delete an existing File
/// </summary>
/// <param name="filename">The filename</param>
[Delete("__admin/files/{filename}")]
Task<StatusModel> DeleteFileAsync([Path] string filename);
/// <summary>
/// Check if a file exists
/// </summary>
/// <param name="filename">The filename</param>
[Head("__admin/files/{filename}")]
Task FileExistsAsync([Path] string filename);
}
}

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using JetBrains.Annotations;
using System.Collections.Generic;
namespace WireMock.Handlers
{
@@ -18,40 +19,67 @@ namespace WireMock.Handlers
/// </summary>
/// <param name="path">The path.</param>
/// <returns>true if path refers to an existing directory; false if the directory does not exist or an error occurs when trying to determine if the specified directory exists.</returns>
bool FolderExists(string path);
bool FolderExists([NotNull] string path);
/// <summary>
/// Creates all directories and subdirectories in the specified path unless they already exist.
/// </summary>
/// <param name="path">The path.</param>
void CreateFolder(string path);
void CreateFolder([NotNull] string path);
/// <summary>
/// Returns an enumerable collection of file names in a specified path.
/// </summary>
/// <param name="path">The path.</param>
/// <returns>An enumerable collection of the full names (including paths) for the files in the directory specified by path.</returns>
IEnumerable<string> EnumerateFiles(string path);
IEnumerable<string> EnumerateFiles([NotNull] string path);
/// <summary>
/// Read a static mapping file as text.
/// </summary>
/// <param name="path">The path (folder + filename with .json extension).</param>
/// <returns>The file content as text.</returns>
string ReadMappingFile(string path);
string ReadMappingFile([NotNull] string path);
/// <summary>
/// Write the static mapping.
/// Write the static mapping file.
/// </summary>
/// <param name="path">The path (folder + filename with .json extension).</param>
/// <param name="text">The text.</param>
void WriteMappingFile(string path, string text);
void WriteMappingFile([NotNull] string path, [NotNull] string text);
/// <summary>
/// Read a response body file as text.
/// </summary>
/// <param name="path">The path or filename from the file to read.</param>
/// <returns>The file content as bytes.</returns>
byte[] ReadResponseBodyAsFile(string path);
byte[] ReadResponseBodyAsFile([NotNull] string path);
/// <summary>
/// Delete a file.
/// </summary>
/// <param name="filename">The filename.</param>
void DeleteFile([NotNull] string filename);
/// <summary>
/// Determines whether the given path refers to an existing file on disk.
/// </summary>
/// <param name="filename">The filename.</param>
/// <returns>true if path refers to an existing file; false if the file does not exist.</returns>
bool FileExists([NotNull] string filename);
/// <summary>
/// Write a file.
/// </summary>
/// <param name="filename">The filename.</param>
/// <param name="bytes">The bytes.</param>
void WriteFile([NotNull] string filename, [NotNull] byte[] bytes);
/// <summary>
/// Read a file as bytes.
/// </summary>
/// <param name="filename">The filename.</param>
/// <returns>The file content as bytes.</returns>
byte[] ReadFile([NotNull] string filename);
}
}

View File

@@ -1,5 +1,4 @@
using JetBrains.Annotations;
using System.Collections.Generic;
using System.Collections.Generic;
using System.IO;
using WireMock.Validation;
@@ -13,7 +12,7 @@ namespace WireMock.Handlers
private static readonly string AdminMappingsFolder = Path.Combine("__admin", "mappings");
/// <inheritdoc cref="IFileSystemHandler.FolderExists"/>
public bool FolderExists([NotNull] string path)
public bool FolderExists(string path)
{
Check.NotNullOrEmpty(path, nameof(path));
@@ -21,7 +20,7 @@ namespace WireMock.Handlers
}
/// <inheritdoc cref="IFileSystemHandler.CreateFolder"/>
public void CreateFolder([NotNull] string path)
public void CreateFolder(string path)
{
Check.NotNullOrEmpty(path, nameof(path));
@@ -29,7 +28,7 @@ namespace WireMock.Handlers
}
/// <inheritdoc cref="IFileSystemHandler.EnumerateFiles"/>
public IEnumerable<string> EnumerateFiles([NotNull] string path)
public IEnumerable<string> EnumerateFiles(string path)
{
Check.NotNullOrEmpty(path, nameof(path));
@@ -43,15 +42,15 @@ namespace WireMock.Handlers
}
/// <inheritdoc cref="IFileSystemHandler.ReadMappingFile"/>
public string ReadMappingFile([NotNull] string path)
public string ReadMappingFile(string path)
{
Check.NotNullOrEmpty(path, nameof(path));
return File.ReadAllText(path);
}
/// <inheritdoc cref="IFileSystemHandler.WriteMappingFile"/>
public void WriteMappingFile([NotNull] string path, [NotNull] string text)
/// <inheritdoc cref="IFileSystemHandler.WriteMappingFile(string, string)"/>
public void WriteMappingFile(string path, string text)
{
Check.NotNullOrEmpty(path, nameof(path));
Check.NotNull(text, nameof(text));
@@ -68,5 +67,48 @@ namespace WireMock.Handlers
// Else the path will just be as-is.
return File.ReadAllBytes(Path.GetFileName(path) == path ? Path.Combine(GetMappingFolder(), path) : path);
}
/// <inheritdoc cref="IFileSystemHandler.FileExists"/>
public bool FileExists(string filename)
{
Check.NotNullOrEmpty(filename, nameof(filename));
return File.Exists(AdjustPath(filename));
}
/// <inheritdoc cref="IFileSystemHandler.WriteFile(string, byte[])"/>
public void WriteFile(string filename, byte[] bytes)
{
Check.NotNullOrEmpty(filename, nameof(filename));
Check.NotNull(bytes, nameof(bytes));
File.WriteAllBytes(AdjustPath(filename), bytes);
}
/// <inheritdoc cref="IFileSystemHandler.DeleteFile"/>
public void DeleteFile(string filename)
{
Check.NotNullOrEmpty(filename, nameof(filename));
File.Delete(AdjustPath(filename));
}
/// <inheritdoc cref="IFileSystemHandler.ReadFile"/>
public byte[] ReadFile(string filename)
{
Check.NotNullOrEmpty(filename, nameof(filename));
return File.ReadAllBytes(AdjustPath(filename));
}
/// <summary>
/// Adjusts the path to the MappingFolder.
/// </summary>
/// <param name="filename">The path.</param>
/// <returns>Adjusted path</returns>
private string AdjustPath(string filename)
{
return Path.Combine(GetMappingFolder(), filename);
}
}
}

View File

@@ -0,0 +1,83 @@
using DevLab.JmesPath;
using JetBrains.Annotations;
using Newtonsoft.Json;
using System.Linq;
using WireMock.Validation;
namespace WireMock.Matchers
{
/// <summary>
/// http://jmespath.org/
/// </summary>
public class JmesPathMatcher : IStringMatcher, IObjectMatcher
{
private readonly string[] _patterns;
/// <inheritdoc cref="IMatcher.MatchBehaviour"/>
public MatchBehaviour MatchBehaviour { get; }
/// <summary>
/// Initializes a new instance of the <see cref="JmesPathMatcher"/> class.
/// </summary>
/// <param name="patterns">The patterns.</param>
public JmesPathMatcher([NotNull] params string[] patterns) : this(MatchBehaviour.AcceptOnMatch, patterns)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="JmesPathMatcher"/> class.
/// </summary>
/// <param name="matchBehaviour">The match behaviour.</param>
/// <param name="patterns">The patterns.</param>
public JmesPathMatcher(MatchBehaviour matchBehaviour, [NotNull] params string[] patterns)
{
Check.NotNull(patterns, nameof(patterns));
MatchBehaviour = matchBehaviour;
_patterns = patterns;
}
/// <inheritdoc cref="IStringMatcher.IsMatch"/>
public double IsMatch(string input)
{
double match = MatchScores.Mismatch;
if (input != null)
{
try
{
match = MatchScores.ToScore(_patterns.Select(pattern => bool.Parse(new JmesPath().Transform(input, pattern))));
}
catch (JsonException)
{
// just ignore JsonException
}
}
return MatchBehaviourHelper.Convert(MatchBehaviour, match);
}
/// <inheritdoc cref="IObjectMatcher.IsMatch"/>
public double IsMatch(object input)
{
double match = MatchScores.Mismatch;
// When input is null or byte[], return Mismatch.
if (input != null && !(input is byte[]))
{
string inputAsString = JsonConvert.SerializeObject(input);
return IsMatch(inputAsString);
}
return MatchBehaviourHelper.Convert(MatchBehaviour, match);
}
/// <inheritdoc cref="IStringMatcher.GetPatterns"/>
public string[] GetPatterns()
{
return _patterns;
}
/// <inheritdoc cref="IMatcher.Name"/>
public string Name => "JmesPathMatcher";
}
}

View File

@@ -224,7 +224,7 @@ namespace WireMock.ResponseBuilders
BodyAsFileIsCached = cache
};
if (cache)
if (cache && !UseTransformer)
{
ResponseMessage.BodyData.DetectedBodyType = BodyType.Bytes;
ResponseMessage.BodyData.BodyAsBytes = _fileSystemHandler.ReadResponseBodyAsFile(filename);

View File

@@ -33,5 +33,13 @@ namespace WireMock
return response;
}
internal static ResponseMessage Create(int statusCode)
{
return new ResponseMessage
{
StatusCode = statusCode
};
}
}
}

View File

@@ -77,7 +77,7 @@ namespace WireMock.Serialization
},
Response = new ResponseModel
{
Delay = (int?) response.Delay?.TotalMilliseconds
Delay = (int?)response.Delay?.TotalMilliseconds
}
};

View File

@@ -45,6 +45,9 @@ namespace WireMock.Serialization
case "JsonPathMatcher":
return new JsonPathMatcher(matchBehaviour, stringPatterns);
case "JmesPathMatcher":
return new JmesPathMatcher(matchBehaviour, stringPatterns);
case "XPathMatcher":
return new XPathMatcher(matchBehaviour, (string)matcher.Pattern);

View File

@@ -34,13 +34,14 @@ namespace WireMock.Server
private const int AdminPriority = int.MinValue;
private const int ProxyPriority = 1000;
private const string ContentTypeJson = "application/json";
private const string AdminFiles = "/__admin/files";
private const string AdminMappings = "/__admin/mappings";
private const string AdminRequests = "/__admin/requests";
private const string AdminSettings = "/__admin/settings";
private const string AdminScenarios = "/__admin/scenarios";
private readonly RegexMatcher _adminMappingsGuidPathMatcher = new RegexMatcher(MatchBehaviour.AcceptOnMatch, @"^\/__admin\/mappings\/(\{{0,1}([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}\}{0,1})$");
private readonly RegexMatcher _adminRequestsGuidPathMatcher = new RegexMatcher(MatchBehaviour.AcceptOnMatch, @"^\/__admin\/requests\/(\{{0,1}([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}\}{0,1})$");
private readonly RegexMatcher _adminMappingsGuidPathMatcher = new RegexMatcher(MatchBehaviour.AcceptOnMatch, @"^\/__admin\/mappings\/([0-9A-Fa-f]{8}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{12})$");
private readonly RegexMatcher _adminRequestsGuidPathMatcher = new RegexMatcher(MatchBehaviour.AcceptOnMatch, @"^\/__admin\/requests\/([0-9A-Fa-f]{8}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{12})$");
private readonly JsonSerializerSettings _settings = new JsonSerializerSettings
{
@@ -97,10 +98,17 @@ namespace WireMock.Server
// __admin/scenarios/reset
Given(Request.Create().WithPath(AdminScenarios + "/reset").UsingPost()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(ScenariosReset));
// __admin/files/{filename}
Given(Request.Create().WithPath(_adminFilesFilenamePathMatcher).UsingPost()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(FilePost));
Given(Request.Create().WithPath(_adminFilesFilenamePathMatcher).UsingPut()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(FilePut));
Given(Request.Create().WithPath(_adminFilesFilenamePathMatcher).UsingGet()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(FileGet));
Given(Request.Create().WithPath(_adminFilesFilenamePathMatcher).UsingHead()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(FileHead));
Given(Request.Create().WithPath(_adminFilesFilenamePathMatcher).UsingDelete()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(FileDelete));
}
#endregion
#region StaticMappings
#region StaticMappings
/// <summary>
/// Saves the static mappings.
/// </summary>
@@ -714,6 +722,11 @@ namespace WireMock.Server
responseBuilder = responseBuilder.WithDelay(responseModel.Delay.Value);
}
if (responseModel.UseTransformer)
{
responseBuilder = responseBuilder.WithTransformer();
}
if (!string.IsNullOrEmpty(responseModel.ProxyUrl))
{
if (string.IsNullOrEmpty(responseModel.X509Certificate2ThumbprintOrSubjectName))
@@ -770,11 +783,6 @@ namespace WireMock.Server
responseBuilder = responseBuilder.WithBodyFromFile(responseModel.BodyAsFile);
}
if (responseModel.UseTransformer)
{
responseBuilder = responseBuilder.WithTransformer();
}
return responseBuilder;
}

View File

@@ -0,0 +1,114 @@
using System.IO;
using System.Linq;
using System.Text;
using WireMock.Matchers;
using WireMock.Util;
namespace WireMock.Server
{
public partial class FluentMockServer
{
private readonly RegexMatcher _adminFilesFilenamePathMatcher = new RegexMatcher(MatchBehaviour.AcceptOnMatch, @"^\/__admin\/files\/.*$");
private static readonly Encoding[] FileBodyIsString = { Encoding.UTF8, Encoding.ASCII };
#region Files/{filename}
private ResponseMessage FilePost(RequestMessage requestMessage)
{
string filename = GetFileNameFromRequestMessage(requestMessage);
string mappingFolder = _fileSystemHandler.GetMappingFolder();
if (!_fileSystemHandler.FolderExists(mappingFolder))
{
_fileSystemHandler.CreateFolder(mappingFolder);
}
_fileSystemHandler.WriteFile(filename, requestMessage.BodyAsBytes);
return ResponseMessageBuilder.Create("File created");
}
private ResponseMessage FilePut(RequestMessage requestMessage)
{
string filename = GetFileNameFromRequestMessage(requestMessage);
if (!_fileSystemHandler.FileExists(filename))
{
_logger.Info("The file '{0}' does not exist, updating file will be skipped.", filename);
return ResponseMessageBuilder.Create("File is not found", 404);
}
_fileSystemHandler.WriteFile(filename, requestMessage.BodyAsBytes);
return ResponseMessageBuilder.Create("File updated");
}
private ResponseMessage FileGet(RequestMessage requestMessage)
{
string filename = GetFileNameFromRequestMessage(requestMessage);
if (!_fileSystemHandler.FileExists(filename))
{
_logger.Info("The file '{0}' does not exist.", filename);
return ResponseMessageBuilder.Create("File is not found", 404);
}
byte[] bytes = _fileSystemHandler.ReadFile(filename);
var response = new ResponseMessage
{
StatusCode = 200,
BodyData = new BodyData
{
BodyAsBytes = bytes,
DetectedBodyType = BodyType.Bytes,
DetectedBodyTypeFromContentType = BodyType.None
}
};
if (BytesEncodingUtils.TryGetEncoding(bytes, out Encoding encoding) && FileBodyIsString.Select(x => x.Equals(encoding)).Any())
{
response.BodyData.DetectedBodyType = BodyType.String;
response.BodyData.BodyAsString = encoding.GetString(bytes);
}
return response;
}
/// <summary>
/// Checks if file exists.
/// Note: Response is returned with no body as a head request doesn't accept a body, only the status code.
/// </summary>
/// <param name="requestMessage">The request message.</param>
private ResponseMessage FileHead(RequestMessage requestMessage)
{
string filename = GetFileNameFromRequestMessage(requestMessage);
if (!_fileSystemHandler.FileExists(filename))
{
_logger.Info("The file '{0}' does not exist.", filename);
return ResponseMessageBuilder.Create(404);
}
return ResponseMessageBuilder.Create(204);
}
private ResponseMessage FileDelete(RequestMessage requestMessage)
{
string filename = GetFileNameFromRequestMessage(requestMessage);
if (!_fileSystemHandler.FileExists(filename))
{
_logger.Info("The file '{0}' does not exist.", filename);
return ResponseMessageBuilder.Create("File is not deleted", 404);
}
_fileSystemHandler.DeleteFile(filename);
return ResponseMessageBuilder.Create("File deleted.");
}
private string GetFileNameFromRequestMessage(RequestMessage requestMessage)
{
return Path.GetFileName(requestMessage.Path.Substring(AdminFiles.Length + 1));
}
#endregion
}
}

View File

@@ -1,9 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using HandlebarsDotNet;
using HandlebarsDotNet;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using WireMock.Util;
namespace WireMock.Transformers
@@ -24,23 +24,24 @@ namespace WireMock.Transformers
public static ResponseMessage Transform(RequestMessage requestMessage, ResponseMessage original)
{
bool bodyIsJson = original.BodyData.DetectedBodyType == BodyType.Json;
var responseMessage = new ResponseMessage { StatusCode = original.StatusCode };
if (!bodyIsJson)
{
responseMessage.BodyOriginal = original.BodyData.BodyAsString;
}
var template = new { request = requestMessage };
if (!bodyIsJson)
switch (original.BodyData.DetectedBodyType)
{
TransformBodyAsString(template, original, responseMessage);
}
else
{
TransformBodyAsJson(template, original, responseMessage);
case BodyType.Json:
TransformBodyAsJson(template, original, responseMessage);
break;
case BodyType.File:
TransformBodyAsFile(template, original, responseMessage);
break;
case BodyType.String:
responseMessage.BodyOriginal = original.BodyData.BodyAsString;
TransformBodyAsString(template, original, responseMessage);
break;
}
// Headers
@@ -150,13 +151,25 @@ namespace WireMock.Transformers
private static void TransformBodyAsString(object template, ResponseMessage original, ResponseMessage responseMessage)
{
var templateBody = HandlebarsContext.Compile(original.BodyData.BodyAsString);
var templateBodyAsString = HandlebarsContext.Compile(original.BodyData.BodyAsString);
responseMessage.BodyData = new BodyData
{
DetectedBodyType = original.BodyData.DetectedBodyType,
DetectedBodyTypeFromContentType = original.BodyData.DetectedBodyTypeFromContentType,
BodyAsString = templateBody(template)
BodyAsString = templateBodyAsString(template)
};
}
private static void TransformBodyAsFile(object template, ResponseMessage original, ResponseMessage responseMessage)
{
var templateBodyAsFile = HandlebarsContext.Compile(original.BodyData.BodyAsFile);
responseMessage.BodyData = new BodyData
{
DetectedBodyType = original.BodyData.DetectedBodyType,
DetectedBodyTypeFromContentType = original.BodyData.DetectedBodyTypeFromContentType,
BodyAsFile = templateBodyAsFile(template)
};
}
}

View File

@@ -57,8 +57,9 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="System.Linq.Dynamic.Core" Version="1.0.11" />
<PackageReference Include="System.Linq.Dynamic.Core" Version="1.0.12" />
<PackageReference Include="RandomDataGenerator.Net" Version="1.0.7" />
<PackageReference Include="JmesPath.Net" Version="1.0.125" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net451' ">