diff --git a/WireMock.Net Solution.sln b/WireMock.Net Solution.sln
index dcba3a99b..94142f9c0 100644
--- a/WireMock.Net Solution.sln
+++ b/WireMock.Net Solution.sln
@@ -54,6 +54,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.Service", "exa
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.Console.HeadersTest", "examples\WireMock.Net.Console.HeadersTest\WireMock.Net.Console.HeadersTest.csproj", "{B70278E7-A2C6-4A3B-BBA9-1C873CA6F03C}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.Console.NETCoreApp2", "examples\WireMock.Net.Console.NETCoreApp2\WireMock.Net.Console.NETCoreApp2.csproj", "{83645809-9E01-4E81-8733-BA9497554ABF}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -120,6 +122,10 @@ Global
{B70278E7-A2C6-4A3B-BBA9-1C873CA6F03C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B70278E7-A2C6-4A3B-BBA9-1C873CA6F03C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B70278E7-A2C6-4A3B-BBA9-1C873CA6F03C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {83645809-9E01-4E81-8733-BA9497554ABF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {83645809-9E01-4E81-8733-BA9497554ABF}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {83645809-9E01-4E81-8733-BA9497554ABF}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {83645809-9E01-4E81-8733-BA9497554ABF}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -140,6 +146,7 @@ Global
{3C279524-DB73-4DE3-BEF1-F2B2958C9F65} = {F0C22C47-DF71-463C-9B04-B4E0F3B8708A}
{7F0B2446-0363-4720-AF46-F47F83B557DC} = {F0C22C47-DF71-463C-9B04-B4E0F3B8708A}
{B70278E7-A2C6-4A3B-BBA9-1C873CA6F03C} = {F0C22C47-DF71-463C-9B04-B4E0F3B8708A}
+ {83645809-9E01-4E81-8733-BA9497554ABF} = {F0C22C47-DF71-463C-9B04-B4E0F3B8708A}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {BF428BCC-C837-433B-87D2-15C7014B73E9}
diff --git a/examples/WireMock.Net.Console.NETCoreApp/WireMock.Net.Console.NETCoreApp.csproj b/examples/WireMock.Net.Console.NETCoreApp/WireMock.Net.Console.NETCoreApp.csproj
index bbe783117..dc45d93ad 100644
--- a/examples/WireMock.Net.Console.NETCoreApp/WireMock.Net.Console.NETCoreApp.csproj
+++ b/examples/WireMock.Net.Console.NETCoreApp/WireMock.Net.Console.NETCoreApp.csproj
@@ -7,6 +7,7 @@
+
diff --git a/examples/WireMock.Net.Console.NETCoreApp2/WireMock.Net.Console.NETCoreApp2.csproj b/examples/WireMock.Net.Console.NETCoreApp2/WireMock.Net.Console.NETCoreApp2.csproj
new file mode 100644
index 000000000..b3569ec46
--- /dev/null
+++ b/examples/WireMock.Net.Console.NETCoreApp2/WireMock.Net.Console.NETCoreApp2.csproj
@@ -0,0 +1,48 @@
+
+
+
+ Exe
+ netcoreapp2.1
+ ../../WireMock.Net-Logo.ico
+ WireMock.Net.Console.NETCoreApp.Program
+
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ Never
+
+
+
+
\ No newline at end of file
diff --git a/examples/WireMock.Net.Console.NETCoreApp2/__admin/mappings/11111110-a633-40e8-a244-5cb80bc0ab66.json b/examples/WireMock.Net.Console.NETCoreApp2/__admin/mappings/11111110-a633-40e8-a244-5cb80bc0ab66.json
new file mode 100644
index 000000000..9c761369d
--- /dev/null
+++ b/examples/WireMock.Net.Console.NETCoreApp2/__admin/mappings/11111110-a633-40e8-a244-5cb80bc0ab66.json
@@ -0,0 +1,22 @@
+{
+ "Request": {
+ "Path": {
+ "Matchers": [
+ {
+ "Name": "WildcardMatcher",
+ "Pattern": "/static/mapping"
+ }
+ ]
+ },
+ "Methods": [
+ "get"
+ ]
+ },
+ "Response": {
+ "BodyAsJson": { "body": "static mapping" },
+ "Headers": {
+ "Content-Type": "application/json",
+ "Test-X": [ "test 1", "test 2" ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/examples/WireMock.Net.Console.NETCoreApp2/__admin/mappings/791a3f31-6946-4ce7-8e6f-0237c7443275.json b/examples/WireMock.Net.Console.NETCoreApp2/__admin/mappings/791a3f31-6946-4ce7-8e6f-0237c7443275.json
new file mode 100644
index 000000000..d7a6cbdd7
--- /dev/null
+++ b/examples/WireMock.Net.Console.NETCoreApp2/__admin/mappings/791a3f31-6946-4ce7-8e6f-0237c7443275.json
@@ -0,0 +1,29 @@
+{
+ "Guid": "791a3f31-6946-4ce7-8e6f-0237c7443275",
+ "Title": "",
+ "Priority": 0,
+ "Request": {
+ "Path": "/proxy-google-test-post",
+ "Methods": [
+ "post"
+ ],
+ "Body": {}
+ },
+ "Response": {
+ "StatusCode": 404,
+ "Body": "\n\n \n \n
Error 404 (Not Found)!!1\n \n \n 404. That’s an error.\n
The requested URL /proxy-google-test-post
was not found on this server. That’s all we know.\n",
+ "BodyAsBytes": "PCFET0NUWVBFIGh0bWw+CjxodG1sIGxhbmc9ZW4+CiAgPG1ldGEgY2hhcnNldD11dGYtOD4KICA8bWV0YSBuYW1lPXZpZXdwb3J0IGNvbnRlbnQ9ImluaXRpYWwtc2NhbGU9MSwgbWluaW11bS1zY2FsZT0xLCB3aWR0aD1kZXZpY2Utd2lkdGgiPgogIDx0aXRsZT5FcnJvciA0MDQgKE5vdCBGb3VuZCkhITE8L3RpdGxlPgogIDxzdHlsZT4KICAgICp7bWFyZ2luOjA7cGFkZGluZzowfWh0bWwsY29kZXtmb250OjE1cHgvMjJweCBhcmlhbCxzYW5zLXNlcmlmfWh0bWx7YmFja2dyb3VuZDojZmZmO2NvbG9yOiMyMjI7cGFkZGluZzoxNXB4fWJvZHl7bWFyZ2luOjclIGF1dG8gMDttYXgtd2lkdGg6MzkwcHg7bWluLWhlaWdodDoxODBweDtwYWRkaW5nOjMwcHggMCAxNXB4fSogPiBib2R5e2JhY2tncm91bmQ6dXJsKC8vd3d3Lmdvb2dsZS5jb20vaW1hZ2VzL2Vycm9ycy9yb2JvdC5wbmcpIDEwMCUgNXB4IG5vLXJlcGVhdDtwYWRkaW5nLXJpZ2h0OjIwNXB4fXB7bWFyZ2luOjExcHggMCAyMnB4O292ZXJmbG93OmhpZGRlbn1pbnN7Y29sb3I6Izc3Nzt0ZXh0LWRlY29yYXRpb246bm9uZX1hIGltZ3tib3JkZXI6MH1AbWVkaWEgc2NyZWVuIGFuZCAobWF4LXdpZHRoOjc3MnB4KXtib2R5e2JhY2tncm91bmQ6bm9uZTttYXJnaW4tdG9wOjA7bWF4LXdpZHRoOm5vbmU7cGFkZGluZy1yaWdodDowfX0jbG9nb3tiYWNrZ3JvdW5kOnVybCgvL3d3dy5nb29nbGUuY29tL2ltYWdlcy9icmFuZGluZy9nb29nbGVsb2dvLzF4L2dvb2dsZWxvZ29fY29sb3JfMTUweDU0ZHAucG5nKSBuby1yZXBlYXQ7bWFyZ2luLWxlZnQ6LTVweH1AbWVkaWEgb25seSBzY3JlZW4gYW5kIChtaW4tcmVzb2x1dGlvbjoxOTJkcGkpeyNsb2dve2JhY2tncm91bmQ6dXJsKC8vd3d3Lmdvb2dsZS5jb20vaW1hZ2VzL2JyYW5kaW5nL2dvb2dsZWxvZ28vMngvZ29vZ2xlbG9nb19jb2xvcl8xNTB4NTRkcC5wbmcpIG5vLXJlcGVhdCAwJSAwJS8xMDAlIDEwMCU7LW1vei1ib3JkZXItaW1hZ2U6dXJsKC8vd3d3Lmdvb2dsZS5jb20vaW1hZ2VzL2JyYW5kaW5nL2dvb2dsZWxvZ28vMngvZ29vZ2xlbG9nb19jb2xvcl8xNTB4NTRkcC5wbmcpIDB9fUBtZWRpYSBvbmx5IHNjcmVlbiBhbmQgKC13ZWJraXQtbWluLWRldmljZS1waXhlbC1yYXRpbzoyKXsjbG9nb3tiYWNrZ3JvdW5kOnVybCgvL3d3dy5nb29nbGUuY29tL2ltYWdlcy9icmFuZGluZy9nb29nbGVsb2dvLzJ4L2dvb2dsZWxvZ29fY29sb3JfMTUweDU0ZHAucG5nKSBuby1yZXBlYXQ7LXdlYmtpdC1iYWNrZ3JvdW5kLXNpemU6MTAwJSAxMDAlfX0jbG9nb3tkaXNwbGF5OmlubGluZS1ibG9jaztoZWlnaHQ6NTRweDt3aWR0aDoxNTBweH0KICA8L3N0eWxlPgogIDxhIGhyZWY9Ly93d3cuZ29vZ2xlLmNvbS8+PHNwYW4gaWQ9bG9nbyBhcmlhLWxhYmVsPUdvb2dsZT48L3NwYW4+PC9hPgogIDxwPjxiPjQwNC48L2I+IDxpbnM+VGhhdOKAmXMgYW4gZXJyb3IuPC9pbnM+CiAgPHA+VGhlIHJlcXVlc3RlZCBVUkwgPGNvZGU+L3Byb3h5LWdvb2dsZS10ZXN0LXBvc3Q8L2NvZGU+IHdhcyBub3QgZm91bmQgb24gdGhpcyBzZXJ2ZXIuICA8aW5zPlRoYXTigJlzIGFsbCB3ZSBrbm93LjwvaW5zPgo=",
+ "BodyEncoding": {
+ "CodePage": 65001,
+ "EncodingName": "Unicode (UTF-8)",
+ "WebName": "utf-8"
+ },
+ "UseTransformer": false,
+ "Headers": {
+ "Date": "Wed, 27 Oct 2017 18:57:40 GMT",
+ "Alt-Svc": "quic=\":443\"; ma=2592000; v=\"39,38,37,35\"",
+ "Referrer-Policy": "no-referrer",
+ "Connection": "close"
+ }
+ }
+}
\ No newline at end of file
diff --git a/examples/WireMock.Net.Console.NETCoreApp2/__admin/mappings/873d495f-940e-4b86-a1f4-4f0fc7be8b8b.json b/examples/WireMock.Net.Console.NETCoreApp2/__admin/mappings/873d495f-940e-4b86-a1f4-4f0fc7be8b8b.json
new file mode 100644
index 000000000..dd5018000
--- /dev/null
+++ b/examples/WireMock.Net.Console.NETCoreApp2/__admin/mappings/873d495f-940e-4b86-a1f4-4f0fc7be8b8b.json
@@ -0,0 +1,19 @@
+{
+ "Guid": "873d495f-940e-4b86-a1f4-4f0fc7be8b8b",
+ "Priority": 4,
+ "Request": {
+ "Path": {},
+ "Methods": [
+ "get"
+ ]
+ },
+ "Response": {
+ "StatusCode": 200,
+ "BodyDestination": "SameAsSource",
+ "Body": "NO PATH OR URL",
+ "UseTransformer": false,
+ "Headers": {
+ "Content-Type": "application/json"
+ }
+ }
+}
\ No newline at end of file
diff --git a/examples/WireMock.Net.ConsoleApplication/CustomFileSystemFileHandler.cs b/examples/WireMock.Net.ConsoleApplication/CustomFileSystemFileHandler.cs
new file mode 100644
index 000000000..c60ebecef
--- /dev/null
+++ b/examples/WireMock.Net.ConsoleApplication/CustomFileSystemFileHandler.cs
@@ -0,0 +1,47 @@
+using System.Collections.Generic;
+using System.IO;
+using WireMock.Handlers;
+
+namespace WireMock.Net.ConsoleApplication
+{
+ internal class CustomFileSystemFileHandler : IFileSystemHandler
+ {
+ private static readonly string AdminMappingsFolder = Path.Combine("__admin", "mappings");
+
+ ///
+ public bool FolderExists(string path)
+ {
+ return Directory.Exists(path);
+ }
+
+ ///
+ public void CreateFolder(string path)
+ {
+ Directory.CreateDirectory(path);
+ }
+
+ ///
+ public IEnumerable EnumerateFiles(string path)
+ {
+ return Directory.EnumerateFiles(path);
+ }
+
+ ///
+ public string GetMappingFolder()
+ {
+ return Path.Combine(@"c:\temp-wiremock", AdminMappingsFolder);
+ }
+
+ ///
+ public string ReadMappingFile(string path)
+ {
+ return File.ReadAllText(path);
+ }
+
+ ///
+ public void WriteMappingFile(string path, string text)
+ {
+ File.WriteAllText(path, text);
+ }
+ }
+}
\ No newline at end of file
diff --git a/examples/WireMock.Net.ConsoleApplication/MainApp.cs b/examples/WireMock.Net.ConsoleApplication/MainApp.cs
index 965b058b7..09f12274a 100644
--- a/examples/WireMock.Net.ConsoleApplication/MainApp.cs
+++ b/examples/WireMock.Net.ConsoleApplication/MainApp.cs
@@ -30,7 +30,9 @@ public static void Run()
//},
PreWireMockMiddlewareInit = app => { System.Console.WriteLine($"PreWireMockMiddlewareInit : {app.GetType()}"); },
PostWireMockMiddlewareInit = app => { System.Console.WriteLine($"PostWireMockMiddlewareInit : {app.GetType()}"); },
- Logger = new WireMockConsoleLogger()
+ Logger = new WireMockConsoleLogger(),
+
+ FileSystemHandler = new CustomFileSystemFileHandler()
});
System.Console.WriteLine("FluentMockServer listening at {0}", string.Join(",", server.Urls));
diff --git a/examples/WireMock.Net.ConsoleApplication/WireMock.Net.Console.NET452.csproj b/examples/WireMock.Net.ConsoleApplication/WireMock.Net.Console.NET452.csproj
index 86f6fd3ac..4d689ec76 100644
--- a/examples/WireMock.Net.ConsoleApplication/WireMock.Net.Console.NET452.csproj
+++ b/examples/WireMock.Net.ConsoleApplication/WireMock.Net.Console.NET452.csproj
@@ -54,6 +54,7 @@
+
diff --git a/src/WireMock.Net.StandAlone/WireMock.Net.StandAlone.csproj b/src/WireMock.Net.StandAlone/WireMock.Net.StandAlone.csproj
index 09dd215bc..ae57d4d18 100644
--- a/src/WireMock.Net.StandAlone/WireMock.Net.StandAlone.csproj
+++ b/src/WireMock.Net.StandAlone/WireMock.Net.StandAlone.csproj
@@ -3,7 +3,7 @@
Lightweight StandAlone Http Mocking Server for .Net.
WireMock.Net.StandAlone
- 1.0.4.9
+ 1.0.4.10
Stef Heyenrath
net452;net46;netstandard1.3;netstandard2.0
true
diff --git a/src/WireMock.Net/Handlers/IFileSystemHandler.cs b/src/WireMock.Net/Handlers/IFileSystemHandler.cs
new file mode 100644
index 000000000..ad6105c68
--- /dev/null
+++ b/src/WireMock.Net/Handlers/IFileSystemHandler.cs
@@ -0,0 +1,49 @@
+using System.Collections.Generic;
+
+namespace WireMock.Handlers
+{
+ ///
+ /// Handler to interact with the file system to handle folders and read and write static mapping files.
+ ///
+ public interface IFileSystemHandler
+ {
+ ///
+ /// Gets the folder where the static mappings are located. For local file system, this would be `{CurrentFolder}/__admin/mappings`.
+ ///
+ /// The foldername.
+ string GetMappingFolder();
+
+ ///
+ /// Determines whether the given path refers to an existing directory on disk.
+ ///
+ /// The path.
+ /// 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.
+ bool FolderExists(string path);
+
+ ///
+ /// Creates all directories and subdirectories in the specified path unless they already exist.
+ ///
+ /// The path.
+ void CreateFolder(string path);
+
+ ///
+ /// Returns an enumerable collection of file names in a specified path.
+ ///
+ /// The path.
+ /// An enumerable collection of the full names (including paths) for the files in the directory specified by path.
+ IEnumerable EnumerateFiles(string path);
+
+ ///
+ /// Read a static mapping file as text.
+ ///
+ /// The path (folder + filename with .json extension).
+ string ReadMappingFile(string path);
+
+ ///
+ /// Write the static mapping.
+ ///
+ /// The path (folder + filename with .json extension).
+ /// The text.
+ void WriteMappingFile(string path, string text);
+ }
+}
\ No newline at end of file
diff --git a/src/WireMock.Net/Handlers/LocalFileSystemHandler.cs b/src/WireMock.Net/Handlers/LocalFileSystemHandler.cs
new file mode 100644
index 000000000..89ee17c13
--- /dev/null
+++ b/src/WireMock.Net/Handlers/LocalFileSystemHandler.cs
@@ -0,0 +1,49 @@
+using System.Collections.Generic;
+using System.IO;
+
+namespace WireMock.Handlers
+{
+ ///
+ /// Default implementation for a handler to interact with the local file system to read and write static mapping files.
+ ///
+ public class LocalFileSystemHandler : IFileSystemHandler
+ {
+ private static readonly string AdminMappingsFolder = Path.Combine("__admin", "mappings");
+
+ ///
+ public bool FolderExists(string path)
+ {
+ return Directory.Exists(path);
+ }
+
+ ///
+ public void CreateFolder(string path)
+ {
+ Directory.CreateDirectory(path);
+ }
+
+ ///
+ public IEnumerable EnumerateFiles(string path)
+ {
+ return Directory.EnumerateFiles(path);
+ }
+
+ ///
+ public string GetMappingFolder()
+ {
+ return Path.Combine(Directory.GetCurrentDirectory(), AdminMappingsFolder);
+ }
+
+ ///
+ public string ReadMappingFile(string path)
+ {
+ return File.ReadAllText(path);
+ }
+
+ ///
+ public void WriteMappingFile(string path, string text)
+ {
+ File.WriteAllText(path, text);
+ }
+ }
+}
diff --git a/src/WireMock.Net/Server/FluentMockServer.Admin.cs b/src/WireMock.Net/Server/FluentMockServer.Admin.cs
index fd176d739..c987878c0 100644
--- a/src/WireMock.Net/Server/FluentMockServer.Admin.cs
+++ b/src/WireMock.Net/Server/FluentMockServer.Admin.cs
@@ -30,12 +30,13 @@ namespace WireMock.Server
///
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 @@ private void InitAdmin()
}
#endregion
- #region StaticMappings
+ #region StaticMappings
+ ///
+ /// Saves the static mappings.
+ ///
+ /// The optional folder. If not defined, use {CurrentFolder}/__admin/mappings
+ [PublicAPI]
+ public void SaveStaticMappings([CanBeNull] string folder = null)
+ {
+ foreach (var mapping in Mappings.Where(m => !m.IsAdminInterface))
+ {
+ SaveMappingToFile(mapping, folder);
+ }
+ }
+
///
/// Reads the static mappings from a folder.
///
- /// The optional folder. If not defined, use \__admin\mappings\
+ /// The optional folder. If not defined, use {CurrentFolder}/__admin/mappings
[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 @@ public void ReadStaticMappings([CanBeNull] string folder = null)
///
/// Watches the static mappings for changes.
///
- /// The optional folder. If not defined, use \__admin\mappings\
+ /// The optional folder. If not defined, use {CurrentFolder}/__admin/mappings
[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 @@ public void ReadStaticMappingAndAddOrUpdate([NotNull] string path)
string filenameWithoutExtension = Path.GetFileNameWithoutExtension(path);
- MappingModel mappingModel = JsonConvert.DeserializeObject(FileHelper.ReadAllText(path));
+ MappingModel mappingModel = JsonConvert.DeserializeObject(_fileSystemHandler.ReadMappingFile(path));
if (Guid.TryParse(filenameWithoutExtension, out Guid guidFromFilename))
{
DeserializeAndAddOrUpdateMapping(mappingModel, guidFromFilename, path);
@@ -345,29 +360,31 @@ private ResponseMessage MappingDelete(RequestMessage requestMessage)
#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 path = Path.Combine(folder, filename);
- string filePath = Path.Combine(folder, filename + ".json");
- _logger.Info("Saving Mapping to file {0}", filePath);
+ _logger.Info("Saving Mapping file {0}", filename);
- File.WriteAllText(filePath, JsonConvert.SerializeObject(model, _settings));
+ _fileSystemHandler.WriteMappingFile(path, JsonConvert.SerializeObject(model, _settings));
}
private static string SanitizeFileName(string name, char replaceChar = '_')
diff --git a/src/WireMock.Net/Server/FluentMockServer.cs b/src/WireMock.Net/Server/FluentMockServer.cs
index 04ad7e396..f7d261d4f 100644
--- a/src/WireMock.Net/Server/FluentMockServer.cs
+++ b/src/WireMock.Net/Server/FluentMockServer.cs
@@ -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.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 @@ public static FluentMockServer StartWithAdminInterfaceAndReadStaticMappings(para
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));
diff --git a/src/WireMock.Net/Settings/FluentMockServerSettings.cs b/src/WireMock.Net/Settings/FluentMockServerSettings.cs
index 2e45abb40..c773ff9d7 100644
--- a/src/WireMock.Net/Settings/FluentMockServerSettings.cs
+++ b/src/WireMock.Net/Settings/FluentMockServerSettings.cs
@@ -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 @@ public class FluentMockServerSettings : IFluentMockServerSettings
[PublicAPI]
[JsonIgnore]
public IWireMockLogger Logger { get; set; } = new WireMockNullLogger();
+
+ ///
+ [PublicAPI]
+ [JsonIgnore]
+ public IFileSystemHandler FileSystemHandler { get; set; } = new LocalFileSystemHandler();
}
}
\ No newline at end of file
diff --git a/src/WireMock.Net/Settings/IFluentMockServerSettings.cs b/src/WireMock.Net/Settings/IFluentMockServerSettings.cs
index 1030b656c..373eda26c 100644
--- a/src/WireMock.Net/Settings/IFluentMockServerSettings.cs
+++ b/src/WireMock.Net/Settings/IFluentMockServerSettings.cs
@@ -1,5 +1,6 @@
using System;
using JetBrains.Annotations;
+using WireMock.Handlers;
using WireMock.Logging;
namespace WireMock.Settings
@@ -105,5 +106,11 @@ public interface IFluentMockServerSettings
///
[PublicAPI]
IWireMockLogger Logger { get; set; }
+
+ ///
+ /// Handler to interact with the file system to read and write static mapping files.
+ ///
+ [PublicAPI]
+ IFileSystemHandler FileSystemHandler { get; set; }
}
}
\ No newline at end of file
diff --git a/src/WireMock.Net/Util/FileHelper.cs b/src/WireMock.Net/Util/FileHelper.cs
index 18c0aad24..5af196611 100644
--- a/src/WireMock.Net/Util/FileHelper.cs
+++ b/src/WireMock.Net/Util/FileHelper.cs
@@ -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 @@ internal static class FileHelper
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);
}
}
diff --git a/src/WireMock.Net/WireMock.Net.csproj b/src/WireMock.Net/WireMock.Net.csproj
index cd18c438d..6be2c57bd 100644
--- a/src/WireMock.Net/WireMock.Net.csproj
+++ b/src/WireMock.Net/WireMock.Net.csproj
@@ -3,7 +3,7 @@
Lightweight Http Mocking Server for .Net, inspired by WireMock from the Java landscape.
WireMock.Net
- 1.0.4.9
+ 1.0.4.10
Stef Heyenrath
net452;net46;netstandard1.3;netstandard2.0
true
diff --git a/test/WireMock.Net.Tests/FluentMockServerTests.cs b/test/WireMock.Net.Tests/FluentMockServerTests.cs
index 572483daa..c30657957 100644
--- a/test/WireMock.Net.Tests/FluentMockServerTests.cs
+++ b/test/WireMock.Net.Tests/FluentMockServerTests.cs
@@ -7,6 +7,7 @@
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
+using Moq;
using NFluent;
using WireMock.Matchers;
using WireMock.RequestBuilders;
@@ -14,6 +15,10 @@
using WireMock.Server;
using Xunit;
using Newtonsoft.Json;
+using WireMock.Handlers;
+using WireMock.Logging;
+using WireMock.Settings;
+using WireMock.Admin.Mappings;
namespace WireMock.Net.Tests
{
@@ -42,6 +47,35 @@ public void FluentMockServer_StartStop()
server2.Stop();
}
+ [Fact]
+ public void FluentMockServer_SaveStaticMappings()
+ {
+ // Assign
+ string guid = "791a3f31-6946-aaaa-8e6f-0237c7441111";
+ var _staticMappingHandlerMock = new Mock();
+ _staticMappingHandlerMock.Setup(m => m.GetMappingFolder()).Returns("folder");
+ _staticMappingHandlerMock.Setup(m => m.FolderExists(It.IsAny())).Returns(true);
+ _staticMappingHandlerMock.Setup(m => m.WriteMappingFile(It.IsAny(), It.IsAny()));
+
+ _server = FluentMockServer.Start(new FluentMockServerSettings
+ {
+ FileSystemHandler = _staticMappingHandlerMock.Object
+ });
+
+ _server
+ .Given(Request.Create().WithPath($"/foo_{Guid.NewGuid()}"))
+ .WithGuid(guid)
+ .RespondWith(Response.Create().WithBody("save test"));
+
+ // Act
+ _server.SaveStaticMappings();
+
+ // Assert and Verify
+ _staticMappingHandlerMock.Verify(m => m.GetMappingFolder(), Times.Once);
+ _staticMappingHandlerMock.Verify(m => m.FolderExists("folder"), Times.Once);
+ _staticMappingHandlerMock.Verify(m => m.WriteMappingFile(Path.Combine("folder", guid + ".json"), It.IsAny()), Times.Once);
+ }
+
[Fact]
public void FluentMockServer_ReadStaticMapping_WithNonGuidFilename()
{
@@ -108,6 +142,49 @@ public void FluentMockServer_ReadStaticMapping_WithResponseBodyFromFile()
Check.That(mappings.First().Title).IsNullOrEmpty();
}
+ [Fact]
+ public void FluentMockServer_ReadStaticMappings_FolderExistsIsTrue()
+ {
+ // Assign
+ var _staticMappingHandlerMock = new Mock();
+ _staticMappingHandlerMock.Setup(m => m.GetMappingFolder()).Returns("folder");
+ _staticMappingHandlerMock.Setup(m => m.FolderExists(It.IsAny())).Returns(true);
+ _staticMappingHandlerMock.Setup(m => m.EnumerateFiles(It.IsAny())).Returns(new string[0]);
+
+ _server = FluentMockServer.Start(new FluentMockServerSettings
+ {
+ FileSystemHandler = _staticMappingHandlerMock.Object
+ });
+
+ // Act
+ _server.ReadStaticMappings();
+
+ // Assert and Verify
+ _staticMappingHandlerMock.Verify(m => m.GetMappingFolder(), Times.Once);
+ _staticMappingHandlerMock.Verify(m => m.FolderExists("folder"), Times.Once);
+ _staticMappingHandlerMock.Verify(m => m.EnumerateFiles("folder"), Times.Once);
+ }
+
+ [Fact]
+ public void FluentMockServer_ReadStaticMappingAndAddOrUpdate()
+ {
+ // Assign
+ string mapping = "{\"Request\": {\"Path\": {\"Matchers\": [{\"Name\": \"WildcardMatcher\",\"Pattern\": \"/static/mapping\"}]},\"Methods\": [\"get\"]},\"Response\": {\"BodyAsJson\": { \"body\": \"static mapping\" }}}";
+ var _staticMappingHandlerMock = new Mock();
+ _staticMappingHandlerMock.Setup(m => m.ReadMappingFile(It.IsAny())).Returns(mapping);
+
+ _server = FluentMockServer.Start(new FluentMockServerSettings
+ {
+ FileSystemHandler = _staticMappingHandlerMock.Object
+ });
+
+ // Act
+ _server.ReadStaticMappingAndAddOrUpdate(@"c:\test.json");
+
+ // Assert and Verify
+ _staticMappingHandlerMock.Verify(m => m.ReadMappingFile(@"c:\test.json"), Times.Once);
+ }
+
[Fact]
public void FluentMockServer_ReadStaticMappings()
{
@@ -142,7 +219,7 @@ public void FluentMockServer_Admin_Mappings_WithGuidAsString_Get()
string guid = "90356dba-b36c-469a-a17e-669cd84f1f05";
_server = FluentMockServer.Start();
- _server.Given(Request.Create().WithPath("/foo1").UsingGet()).WithGuid(guid)
+ _server.Given(Request.Create().WithPath("/foo100").UsingGet()).WithGuid(guid)
.RespondWith(Response.Create().WithStatusCode(201).WithBody("1"));
var mappings = _server.Mappings.ToArray();
@@ -219,13 +296,14 @@ public async Task FluentMockServer_Admin_Requests_Get()
public async Task FluentMockServer_Should_respond_to_request_methodPatch()
{
// given
+ string path = $"/foo_{Guid.NewGuid()}";
_server = FluentMockServer.Start();
- _server.Given(Request.Create().WithPath("/foo").UsingMethod("patch"))
+ _server.Given(Request.Create().WithPath(path).UsingMethod("patch"))
.RespondWith(Response.Create().WithBody("hello patch"));
// when
- var msg = new HttpRequestMessage(new HttpMethod("patch"), new Uri("http://localhost:" + _server.Ports[0] + "/foo"))
+ var msg = new HttpRequestMessage(new HttpMethod("patch"), new Uri("http://localhost:" + _server.Ports[0] + path))
{
Content = new StringContent("{\"data\": {\"attr\":\"value\"}}")
};
@@ -338,13 +416,14 @@ public async Task FluentMockServer_Should_respond_to_request_bodyAsBase64()
public async Task FluentMockServer_Should_respond_to_request_bodyAsBytes()
{
// given
+ string path = $"/foo_{Guid.NewGuid()}";
_server = FluentMockServer.Start();
- _server.Given(Request.Create().WithPath("/foo").UsingGet()).RespondWith(Response.Create().WithBody(new byte[] { 48, 49 }));
+ _server.Given(Request.Create().WithPath(path).UsingGet()).RespondWith(Response.Create().WithBody(new byte[] { 48, 49 }));
// when
- var responseAsString = await new HttpClient().GetStringAsync("http://localhost:" + _server.Ports[0] + "/foo");
- var responseAsBytes = await new HttpClient().GetByteArrayAsync("http://localhost:" + _server.Ports[0] + "/foo");
+ var responseAsString = await new HttpClient().GetStringAsync("http://localhost:" + _server.Ports[0] + path);
+ var responseAsBytes = await new HttpClient().GetByteArrayAsync("http://localhost:" + _server.Ports[0] + path);
// then
Check.That(responseAsString).IsEqualTo("01");
@@ -371,13 +450,14 @@ public async Task FluentMockServer_Should_respond_to_valid_matchers_when_sent_js
foreach (var item in validMatchersForHelloServerJsonMessage)
{
+ string path = $"/foo_{Guid.NewGuid()}";
_server
- .Given(Request.Create().WithPath("/foo").WithBody((IMatcher)item[0]))
+ .Given(Request.Create().WithPath(path).WithBody((IMatcher)item[0]))
.RespondWith(Response.Create().WithBody("Hello client"));
// Act
var content = new StringContent(jsonRequestMessage, Encoding.UTF8, (string)item[1]);
- var response = await new HttpClient().PostAsync("http://localhost:" + _server.Ports[0] + "/foo", content);
+ var response = await new HttpClient().PostAsync("http://localhost:" + _server.Ports[0] + path, content);
// Assert
var responseString = await response.Content.ReadAsStringAsync();
@@ -392,10 +472,11 @@ public async Task FluentMockServer_Should_respond_to_valid_matchers_when_sent_js
public async Task FluentMockServer_Should_respond_404_for_unexpected_request()
{
// given
+ string path = $"/foo{Guid.NewGuid()}";
_server = FluentMockServer.Start();
// when
- var response = await new HttpClient().GetAsync("http://localhost:" + _server.Ports[0] + "/foo");
+ var response = await new HttpClient().GetAsync("http://localhost:" + _server.Ports[0] + path);
// then
Check.That(response.StatusCode).IsEqualTo(HttpStatusCode.NotFound);
@@ -405,20 +486,21 @@ public async Task FluentMockServer_Should_respond_404_for_unexpected_request()
[Fact]
public async Task FluentMockServer_Should_find_a_request_satisfying_a_request_spec()
{
- // given
+ // Assign
+ string path = $"/bar_{Guid.NewGuid()}";
_server = FluentMockServer.Start();
// when
await new HttpClient().GetAsync("http://localhost:" + _server.Ports[0] + "/foo");
- await new HttpClient().GetAsync("http://localhost:" + _server.Ports[0] + "/bar");
+ await new HttpClient().GetAsync("http://localhost:" + _server.Ports[0] + path);
// then
var result = _server.FindLogEntries(Request.Create().WithPath(new RegexMatcher("^/b.*"))).ToList();
Check.That(result).HasSize(1);
var requestLogged = result.First();
- Check.That(requestLogged.RequestMessage.Path).IsEqualTo("/bar");
- Check.That(requestLogged.RequestMessage.Url).IsEqualTo("http://localhost:" + _server.Ports[0] + "/bar");
+ Check.That(requestLogged.RequestMessage.Path).IsEqualTo(path);
+ Check.That(requestLogged.RequestMessage.Url).IsEqualTo("http://localhost:" + _server.Ports[0] + path);
}
[Fact]
@@ -439,11 +521,12 @@ public async Task FluentMockServer_Should_reset_requestlogs()
public void FluentMockServer_Should_reset_mappings()
{
// given
+ string path = $"/foo_{Guid.NewGuid()}";
_server = FluentMockServer.Start();
_server
.Given(Request.Create()
- .WithPath("/foo")
+ .WithPath(path)
.UsingGet())
.RespondWith(Response.Create()
.WithBody(@"{ msg: ""Hello world!""}"));
@@ -453,35 +536,38 @@ public void FluentMockServer_Should_reset_mappings()
// then
Check.That(_server.Mappings).IsEmpty();
- Check.ThatAsyncCode(() => new HttpClient().GetStringAsync("http://localhost:" + _server.Ports[0] + "/foo"))
+ Check.ThatAsyncCode(() => new HttpClient().GetStringAsync("http://localhost:" + _server.Ports[0] + path))
.ThrowsAny();
}
[Fact]
public async Task FluentMockServer_Should_respond_a_redirect_without_body()
{
- // given
+ // Assign
+ string path = $"/foo_{Guid.NewGuid()}";
+ string pathToRedirect = $"/bar_{Guid.NewGuid()}";
+
_server = FluentMockServer.Start();
_server
.Given(Request.Create()
- .WithPath("/foo")
+ .WithPath(path)
.UsingGet())
.RespondWith(Response.Create()
.WithStatusCode(307)
- .WithHeader("Location", "/bar"));
+ .WithHeader("Location", pathToRedirect));
_server
.Given(Request.Create()
- .WithPath("/bar")
+ .WithPath(pathToRedirect)
.UsingGet())
.RespondWith(Response.Create()
.WithStatusCode(200)
.WithBody("REDIRECT SUCCESSFUL"));
- // when
- var response = await new HttpClient().GetStringAsync("http://localhost:" + _server.Ports[0] + "/foo");
+ // Act
+ var response = await new HttpClient().GetStringAsync($"http://localhost:{_server.Ports[0]}{path}");
- // then
+ // Assert
Check.That(response).IsEqualTo("REDIRECT SUCCESSFUL");
}
diff --git a/test/WireMock.Net.Tests/StatefulBehaviorTests.cs b/test/WireMock.Net.Tests/StatefulBehaviorTests.cs
index 53738249b..3d47ec8e1 100644
--- a/test/WireMock.Net.Tests/StatefulBehaviorTests.cs
+++ b/test/WireMock.Net.Tests/StatefulBehaviorTests.cs
@@ -1,4 +1,5 @@
-using System.Linq;
+using System;
+using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
@@ -16,16 +17,17 @@ public class StatefulBehaviorTests
public async Task Scenarios_Should_skip_non_relevant_states()
{
// given
+ string path = $"/foo_{Guid.NewGuid()}";
var server = FluentMockServer.Start();
server
- .Given(Request.Create().WithPath("/foo").UsingGet())
+ .Given(Request.Create().WithPath(path).UsingGet())
.InScenario("s")
.WhenStateIs("Test state")
.RespondWith(Response.Create());
// when
- var response = await new HttpClient().GetAsync("http://localhost:" + server.Ports[0] + "/foo");
+ var response = await new HttpClient().GetAsync("http://localhost:" + server.Ports[0] + path);
// then
Check.That(response.StatusCode).IsEqualTo(HttpStatusCode.NotFound);
@@ -37,23 +39,24 @@ public async Task Scenarios_Should_skip_non_relevant_states()
public async Task Scenarios_Should_process_request_if_equals_state_and_single_state_defined()
{
// given
+ string path = $"/foo_{Guid.NewGuid()}";
var server = FluentMockServer.Start();
server
- .Given(Request.Create().WithPath("/foo").UsingGet())
+ .Given(Request.Create().WithPath(path).UsingGet())
.InScenario("s")
.WillSetStateTo("Test state")
.RespondWith(Response.Create().WithBody("No state msg"));
server
- .Given(Request.Create().WithPath("/foo").UsingGet())
+ .Given(Request.Create().WithPath(path).UsingGet())
.InScenario("s")
.WhenStateIs("Test state")
.RespondWith(Response.Create().WithBody("Test state msg"));
// when
- var responseNoState = await new HttpClient().GetStringAsync("http://localhost:" + server.Ports[0] + "/foo");
- var responseWithState = await new HttpClient().GetStringAsync("http://localhost:" + server.Ports[0] + "/foo");
+ var responseNoState = await new HttpClient().GetStringAsync("http://localhost:" + server.Ports[0] + path);
+ var responseWithState = await new HttpClient().GetStringAsync("http://localhost:" + server.Ports[0] + path);
// then
Check.That(responseNoState).Equals("No state msg");
diff --git a/test/WireMock.Net.Tests/Util/FileHelperTests.cs b/test/WireMock.Net.Tests/Util/FileHelperTests.cs
new file mode 100644
index 000000000..c5837a7c8
--- /dev/null
+++ b/test/WireMock.Net.Tests/Util/FileHelperTests.cs
@@ -0,0 +1,44 @@
+using System;
+using System.IO;
+using Moq;
+using NFluent;
+using WireMock.Handlers;
+using WireMock.Util;
+using Xunit;
+
+namespace WireMock.Net.Tests.Util
+{
+ public class FileHelperTests
+ {
+ [Fact]
+ public void FileHelper_ReadAllTextWithRetryAndDelay()
+ {
+ // Assign
+ var _staticMappingHandlerMock = new Mock();
+ _staticMappingHandlerMock.Setup(m => m.ReadMappingFile(It.IsAny())).Returns("text");
+
+ // Act
+ string result = FileHelper.ReadAllTextWithRetryAndDelay(_staticMappingHandlerMock.Object, @"c:\temp");
+
+ // Assert
+ Check.That(result).Equals("text");
+
+ // Verify
+ _staticMappingHandlerMock.Verify(m => m.ReadMappingFile(@"c:\temp"), Times.Once);
+ }
+
+ [Fact]
+ public void FileHelper_ReadAllTextWithRetryAndDelay_Throws()
+ {
+ // Assign
+ var _staticMappingHandlerMock = new Mock();
+ _staticMappingHandlerMock.Setup(m => m.ReadMappingFile(It.IsAny())).Throws();
+
+ // Act
+ Check.ThatCode(() => FileHelper.ReadAllTextWithRetryAndDelay(_staticMappingHandlerMock.Object, @"c:\temp")).Throws();
+
+ // Verify
+ _staticMappingHandlerMock.Verify(m => m.ReadMappingFile(@"c:\temp"), Times.Exactly(3));
+ }
+ }
+}