Add IFileSystemHandler to support Azure for StaticMapping location (#180)

* wip

* CustomStaticMappingFileHandler

* Add unit-tests

* Tests

* IFileSystemHandler

* version
This commit is contained in:
Stef Heyenrath
2018-08-14 18:54:53 +02:00
committed by GitHub
parent c92e733ef9
commit 4b91c05fe7
21 changed files with 505 additions and 59 deletions

View File

@@ -3,7 +3,7 @@
<PropertyGroup>
<Description>Lightweight StandAlone Http Mocking Server for .Net.</Description>
<AssemblyTitle>WireMock.Net.StandAlone</AssemblyTitle>
<Version>1.0.4.9</Version>
<Version>1.0.4.10</Version>
<Authors>Stef Heyenrath</Authors>
<TargetFrameworks>net452;net46;netstandard1.3;netstandard2.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>

View File

@@ -0,0 +1,49 @@
using System.Collections.Generic;
namespace WireMock.Handlers
{
/// <summary>
/// Handler to interact with the file system to handle folders and read and write static mapping files.
/// </summary>
public interface IFileSystemHandler
{
/// <summary>
/// Gets the folder where the static mappings are located. For local file system, this would be `{CurrentFolder}/__admin/mappings`.
/// </summary>
/// <returns>The foldername.</returns>
string GetMappingFolder();
/// <summary>
/// Determines whether the given path refers to an existing directory on disk.
/// </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);
/// <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);
/// <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);
/// <summary>
/// Read a static mapping file as text.
/// </summary>
/// <param name="path">The path (folder + filename with .json extension).</param>
string ReadMappingFile(string path);
/// <summary>
/// Write the static mapping.
/// </summary>
/// <param name="path">The path (folder + filename with .json extension).</param>
/// <param name="text">The text.</param>
void WriteMappingFile(string path, string text);
}
}

View File

@@ -0,0 +1,49 @@
using System.Collections.Generic;
using System.IO;
namespace WireMock.Handlers
{
/// <summary>
/// Default implementation for a handler to interact with the local file system to read and write static mapping files.
/// </summary>
public class LocalFileSystemHandler : IFileSystemHandler
{
private static readonly string AdminMappingsFolder = Path.Combine("__admin", "mappings");
/// <inheritdoc cref="IFileSystemHandler.FolderExists"/>
public bool FolderExists(string path)
{
return Directory.Exists(path);
}
/// <inheritdoc cref="IFileSystemHandler.CreateFolder"/>
public void CreateFolder(string path)
{
Directory.CreateDirectory(path);
}
/// <inheritdoc cref="IFileSystemHandler.EnumerateFiles"/>
public IEnumerable<string> EnumerateFiles(string path)
{
return Directory.EnumerateFiles(path);
}
/// <inheritdoc cref="IFileSystemHandler.GetMappingFolder"/>
public string GetMappingFolder()
{
return Path.Combine(Directory.GetCurrentDirectory(), AdminMappingsFolder);
}
/// <inheritdoc cref="IFileSystemHandler.ReadMappingFile"/>
public string ReadMappingFile(string path)
{
return File.ReadAllText(path);
}
/// <inheritdoc cref="IFileSystemHandler.WriteMappingFile"/>
public void WriteMappingFile(string path, string text)
{
File.WriteAllText(path, text);
}
}
}

View File

@@ -30,12 +30,13 @@ namespace WireMock.Server
/// </summary>
public partial class FluentMockServer
{
private static readonly string AdminMappingsFolder = Path.Combine("__admin", "mappings");
private const string ContentTypeJson = "application/json";
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})$");
@@ -100,25 +101,39 @@ namespace WireMock.Server
}
#endregion
#region StaticMappings
#region StaticMappings
/// <summary>
/// Saves the static mappings.
/// </summary>
/// <param name="folder">The optional folder. If not defined, use {CurrentFolder}/__admin/mappings</param>
[PublicAPI]
public void SaveStaticMappings([CanBeNull] string folder = null)
{
foreach (var mapping in Mappings.Where(m => !m.IsAdminInterface))
{
SaveMappingToFile(mapping, folder);
}
}
/// <summary>
/// Reads the static mappings from a folder.
/// </summary>
/// <param name="folder">The optional folder. If not defined, use \__admin\mappings\</param>
/// <param name="folder">The optional folder. If not defined, use {CurrentFolder}/__admin/mappings</param>
[PublicAPI]
public void ReadStaticMappings([CanBeNull] string folder = null)
{
if (folder == null)
{
folder = Path.Combine(Directory.GetCurrentDirectory(), AdminMappingsFolder);
folder = _fileSystemHandler.GetMappingFolder();
}
if (!Directory.Exists(folder))
if (!_fileSystemHandler.FolderExists(folder))
{
_logger.Info("The Static Mapping folder '{0}' does not exist, reading Static MappingFiles will be skipped.", folder);
return;
}
foreach (string filename in Directory.EnumerateFiles(folder).OrderBy(f => f))
foreach (string filename in _fileSystemHandler.EnumerateFiles(folder).OrderBy(f => f))
{
_logger.Info("Reading Static MappingFile : '{0}'", filename);
@@ -136,16 +151,16 @@ namespace WireMock.Server
/// <summary>
/// Watches the static mappings for changes.
/// </summary>
/// <param name="folder">The optional folder. If not defined, use \__admin\mappings\</param>
/// <param name="folder">The optional folder. If not defined, use {CurrentFolder}/__admin/mappings</param>
[PublicAPI]
public void WatchStaticMappings([CanBeNull] string folder = null)
{
if (folder == null)
{
folder = Path.Combine(Directory.GetCurrentDirectory(), AdminMappingsFolder);
folder = _fileSystemHandler.GetMappingFolder();
}
if (!Directory.Exists(folder))
if (!_fileSystemHandler.FolderExists(folder))
{
return;
}
@@ -192,7 +207,7 @@ namespace WireMock.Server
string filenameWithoutExtension = Path.GetFileNameWithoutExtension(path);
MappingModel mappingModel = JsonConvert.DeserializeObject<MappingModel>(FileHelper.ReadAllText(path));
MappingModel mappingModel = JsonConvert.DeserializeObject<MappingModel>(_fileSystemHandler.ReadMappingFile(path));
if (Guid.TryParse(filenameWithoutExtension, out Guid guidFromFilename))
{
DeserializeAndAddOrUpdateMapping(mappingModel, guidFromFilename, path);
@@ -345,29 +360,31 @@ namespace WireMock.Server
#region Mappings
private ResponseMessage MappingsSave(RequestMessage requestMessage)
{
foreach (var mapping in Mappings.Where(m => !m.IsAdminInterface))
{
SaveMappingToFile(mapping);
}
SaveStaticMappings();
return ResponseMessageBuilder.Create("Mappings saved to disk");
}
private void SaveMappingToFile(Mapping mapping)
private void SaveMappingToFile(Mapping mapping, string folder = null)
{
string folder = Path.Combine(Directory.GetCurrentDirectory(), AdminMappingsFolder);
if (!Directory.Exists(folder))
if (folder == null)
{
Directory.CreateDirectory(folder);
folder = _fileSystemHandler.GetMappingFolder();
}
if (!_fileSystemHandler.FolderExists(folder))
{
_fileSystemHandler.CreateFolder(folder);
}
var model = MappingConverter.ToMappingModel(mapping);
string filename = !string.IsNullOrEmpty(mapping.Title) ? SanitizeFileName(mapping.Title) : mapping.Guid.ToString();
string filename = (!string.IsNullOrEmpty(mapping.Title) ? SanitizeFileName(mapping.Title) : mapping.Guid.ToString()) + ".json";
string filePath = Path.Combine(folder, filename + ".json");
_logger.Info("Saving Mapping to file {0}", filePath);
string path = Path.Combine(folder, filename);
File.WriteAllText(filePath, JsonConvert.SerializeObject(model, _settings));
_logger.Info("Saving Mapping file {0}", filename);
_fileSystemHandler.WriteMappingFile(path, JsonConvert.SerializeObject(model, _settings));
}
private static string SanitizeFileName(string name, char replaceChar = '_')

View File

@@ -1,11 +1,12 @@
using JetBrains.Annotations;
using Newtonsoft.Json;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using Newtonsoft.Json;
using WireMock.Handlers;
using WireMock.Http;
using WireMock.Logging;
using WireMock.Matchers;
@@ -14,7 +15,6 @@ using WireMock.Owin;
using WireMock.RequestBuilders;
using WireMock.ResponseProviders;
using WireMock.Settings;
using WireMock.Transformers;
using WireMock.Validation;
namespace WireMock.Server
@@ -25,6 +25,8 @@ namespace WireMock.Server
public partial class FluentMockServer : IDisposable
{
private readonly IWireMockLogger _logger;
private readonly IFileSystemHandler _fileSystemHandler;
private const int ServerStartDelay = 100;
private readonly IOwinSelfHost _httpServer;
private readonly WireMockMiddlewareOptions _options = new WireMockMiddlewareOptions();
@@ -183,7 +185,9 @@ namespace WireMock.Server
private FluentMockServer(IFluentMockServerSettings settings)
{
settings.Logger = settings.Logger ?? new WireMockConsoleLogger();
_logger = settings.Logger;
_fileSystemHandler = settings.FileSystemHandler ?? new LocalFileSystemHandler();
_logger.Info("WireMock.Net by Stef Heyenrath (https://github.com/WireMock-Net/WireMock.Net)");
_logger.Debug("WireMock.Net server settings {0}", JsonConvert.SerializeObject(settings, Formatting.Indented));

View File

@@ -1,6 +1,7 @@
using System;
using JetBrains.Annotations;
using Newtonsoft.Json;
using WireMock.Handlers;
using WireMock.Logging;
namespace WireMock.Settings
@@ -77,5 +78,10 @@ namespace WireMock.Settings
[PublicAPI]
[JsonIgnore]
public IWireMockLogger Logger { get; set; } = new WireMockNullLogger();
/// <inheritdoc cref="IFluentMockServerSettings.FileSystemHandler"/>
[PublicAPI]
[JsonIgnore]
public IFileSystemHandler FileSystemHandler { get; set; } = new LocalFileSystemHandler();
}
}

View File

@@ -1,5 +1,6 @@
using System;
using JetBrains.Annotations;
using WireMock.Handlers;
using WireMock.Logging;
namespace WireMock.Settings
@@ -105,5 +106,11 @@ namespace WireMock.Settings
/// </summary>
[PublicAPI]
IWireMockLogger Logger { get; set; }
/// <summary>
/// Handler to interact with the file system to read and write static mapping files.
/// </summary>
[PublicAPI]
IFileSystemHandler FileSystemHandler { get; set; }
}
}

View File

@@ -1,5 +1,8 @@
using System.IO;
using System.Threading;
using JetBrains.Annotations;
using WireMock.Handlers;
using WireMock.Validation;
namespace WireMock.Util
{
@@ -8,17 +11,19 @@ namespace WireMock.Util
private const int NumberOfRetries = 3;
private const int DelayOnRetry = 500;
public static string ReadAllText(string path)
public static string ReadAllTextWithRetryAndDelay([NotNull] IFileSystemHandler filehandler, [NotNull] string path)
{
Check.NotNull(filehandler, nameof(filehandler));
Check.NotNullOrEmpty(path, nameof(path));
for (int i = 1; i <= NumberOfRetries; ++i)
{
try
{
return File.ReadAllText(path);
return filehandler.ReadMappingFile(path);
}
catch
{
// You may check error code to filter some exceptions, not every error can be recovered.
Thread.Sleep(DelayOnRetry);
}
}

View File

@@ -3,7 +3,7 @@
<PropertyGroup>
<Description>Lightweight Http Mocking Server for .Net, inspired by WireMock from the Java landscape.</Description>
<AssemblyTitle>WireMock.Net</AssemblyTitle>
<Version>1.0.4.9</Version>
<Version>1.0.4.10</Version>
<Authors>Stef Heyenrath</Authors>
<TargetFrameworks>net452;net46;netstandard1.3;netstandard2.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>