diff --git a/Directory.Build.props b/Directory.Build.props index 6b11e1818..781ae51fd 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -13,6 +13,8 @@ git https://github.com/WireMock-Net/WireMock.Net ../../resources/WireMock.Net-Logo.ico + Latest + enable @@ -24,6 +26,10 @@ + + + + diff --git a/WireMock.Net Solution.sln b/WireMock.Net Solution.sln index 9c41a64cc..5420b0234 100644 --- a/WireMock.Net Solution.sln +++ b/WireMock.Net Solution.sln @@ -40,8 +40,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.Client", "exam EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.Console.NETCoreApp", "examples\WireMock.Net.Console.NETCoreApp\WireMock.Net.Console.NETCoreApp.csproj", "{FE281639-B014-4C8A-96FA-141164A74713}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.Console.Proxy.NETCoreApp", "examples\WireMock.Net.Console.Record.NETCoreApp\WireMock.Net.Console.Proxy.NETCoreApp.csproj", "{1995E414-F197-4AB4-90C2-68D806B5AF59}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.StandAlone.NETCoreApp", "examples\WireMock.Net.StandAlone.NETCoreApp\WireMock.Net.StandAlone.NETCoreApp.csproj", "{10E16614-61CA-48D8-8BDD-664C13913DED}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.StandAlone.Net452", "examples\WireMock.Net.StandAlone.Net452\WireMock.Net.StandAlone.Net452.csproj", "{668F689E-57B4-422E-8846-C0FF643CA999}" @@ -101,6 +99,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.Console.NET6", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.WebApplication.NET6", "examples\WireMock.Net.WebApplication.NET6\WireMock.Net.WebApplication.NET6.csproj", "{3F7AA023-6833-4856-A08A-4B5717B592B8}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.Console.Proxy.NETCoreApp", "examples\WireMock.Net.Console.Proxy.NETCoreApp\WireMock.Net.Console.Proxy.NETCoreApp.csproj", "{670C7562-C154-442E-A249-7D26849BCD13}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -143,10 +143,6 @@ Global {FE281639-B014-4C8A-96FA-141164A74713}.Debug|Any CPU.Build.0 = Debug|Any CPU {FE281639-B014-4C8A-96FA-141164A74713}.Release|Any CPU.ActiveCfg = Release|Any CPU {FE281639-B014-4C8A-96FA-141164A74713}.Release|Any CPU.Build.0 = Release|Any CPU - {1995E414-F197-4AB4-90C2-68D806B5AF59}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1995E414-F197-4AB4-90C2-68D806B5AF59}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1995E414-F197-4AB4-90C2-68D806B5AF59}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1995E414-F197-4AB4-90C2-68D806B5AF59}.Release|Any CPU.Build.0 = Release|Any CPU {10E16614-61CA-48D8-8BDD-664C13913DED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {10E16614-61CA-48D8-8BDD-664C13913DED}.Debug|Any CPU.Build.0 = Debug|Any CPU {10E16614-61CA-48D8-8BDD-664C13913DED}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -239,6 +235,10 @@ Global {3F7AA023-6833-4856-A08A-4B5717B592B8}.Debug|Any CPU.Build.0 = Debug|Any CPU {3F7AA023-6833-4856-A08A-4B5717B592B8}.Release|Any CPU.ActiveCfg = Release|Any CPU {3F7AA023-6833-4856-A08A-4B5717B592B8}.Release|Any CPU.Build.0 = Release|Any CPU + {670C7562-C154-442E-A249-7D26849BCD13}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {670C7562-C154-442E-A249-7D26849BCD13}.Debug|Any CPU.Build.0 = Debug|Any CPU + {670C7562-C154-442E-A249-7D26849BCD13}.Release|Any CPU.ActiveCfg = Release|Any CPU + {670C7562-C154-442E-A249-7D26849BCD13}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -253,7 +253,6 @@ Global {41C19451-E980-4ED4-A011-DA7A1C23FC05} = {985E0ADB-D4B4-473A-AA40-567E279B7946} {74D91AD0-D96D-4FD2-AEC5-CC49D38346C0} = {985E0ADB-D4B4-473A-AA40-567E279B7946} {FE281639-B014-4C8A-96FA-141164A74713} = {985E0ADB-D4B4-473A-AA40-567E279B7946} - {1995E414-F197-4AB4-90C2-68D806B5AF59} = {985E0ADB-D4B4-473A-AA40-567E279B7946} {10E16614-61CA-48D8-8BDD-664C13913DED} = {985E0ADB-D4B4-473A-AA40-567E279B7946} {668F689E-57B4-422E-8846-C0FF643CA999} = {985E0ADB-D4B4-473A-AA40-567E279B7946} {26433A8F-BF01-4962-97EB-81BFFBB61096} = {985E0ADB-D4B4-473A-AA40-567E279B7946} @@ -279,6 +278,7 @@ Global {3BA5109E-5F30-4CC2-B699-02EC82560AA6} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2} {2215055B-594E-4C2F-99B2-6DF337F02893} = {985E0ADB-D4B4-473A-AA40-567E279B7946} {3F7AA023-6833-4856-A08A-4B5717B592B8} = {985E0ADB-D4B4-473A-AA40-567E279B7946} + {670C7562-C154-442E-A249-7D26849BCD13} = {985E0ADB-D4B4-473A-AA40-567E279B7946} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {DC539027-9852-430C-B19F-FD035D018458} diff --git a/examples/WireMock.Net.Console.Proxy.NETCoreApp/501d5907-7f73-46dc-a1c6-1a48f39b103a.json b/examples/WireMock.Net.Console.Proxy.NETCoreApp/501d5907-7f73-46dc-a1c6-1a48f39b103a.json new file mode 100644 index 000000000..00eaf3e83 --- /dev/null +++ b/examples/WireMock.Net.Console.Proxy.NETCoreApp/501d5907-7f73-46dc-a1c6-1a48f39b103a.json @@ -0,0 +1,169 @@ +{ + "Guid": "501d5907-7f73-46dc-a1c6-1a48f39b103a", + "Title": "", + "Request": { + "Path": { + "Matchers": [ + { + "Name": "WildcardMatcher", + "Pattern": "/post", + "IgnoreCase": false + } + ] + }, + "Methods": [ + "POST" + ], + "Headers": [ + { + "Name": "Accept", + "Matchers": [ + { + "Name": "WildcardMatcher", + "Pattern": "*/*", + "IgnoreCase": true + } + ] + }, + { + "Name": "Connection", + "Matchers": [ + { + "Name": "WildcardMatcher", + "Pattern": "keep-alive", + "IgnoreCase": true + } + ] + }, + { + "Name": "Host", + "Matchers": [ + { + "Name": "WildcardMatcher", + "Pattern": "localhost:9091", + "IgnoreCase": true + } + ] + }, + { + "Name": "User-Agent", + "Matchers": [ + { + "Name": "WildcardMatcher", + "Pattern": "PostmanRuntime/7.29.0", + "IgnoreCase": true + } + ] + }, + { + "Name": "Accept-Encoding", + "Matchers": [ + { + "Name": "WildcardMatcher", + "Pattern": "gzip, deflate, br", + "IgnoreCase": true + } + ] + }, + { + "Name": "Authorization", + "Matchers": [ + { + "Name": "WildcardMatcher", + "Pattern": "BASIC 1234", + "IgnoreCase": true + } + ] + }, + { + "Name": "Content-Type", + "Matchers": [ + { + "Name": "WildcardMatcher", + "Pattern": "application/vnd.contoso+json", + "IgnoreCase": true + } + ] + }, + { + "Name": "Content-Length", + "Matchers": [ + { + "Name": "WildcardMatcher", + "Pattern": "16", + "IgnoreCase": true + } + ] + }, + { + "Name": "postmanecho", + "Matchers": [ + { + "Name": "WildcardMatcher", + "Pattern": "post", + "IgnoreCase": true + } + ] + }, + { + "Name": "Test123", + "Matchers": [ + { + "Name": "WildcardMatcher", + "Pattern": "application/vnd.contoso+json", + "IgnoreCase": true + } + ] + } + ], + "Body": { + "Matcher": { + "Name": "JsonMatcher", + "Pattern": { + "test": 42 + }, + "IgnoreCase": true + } + } + }, + "Response": { + "StatusCode": 200, + "BodyAsJson": { + "args": {}, + "data": { + "test": "Stef" + }, + "files": {}, + "form": {}, + "headers": { + "x-forwarded-proto": "http", + "x-forwarded-port": "80", + "host": "postman-echo.com", + "x-amzn-trace-id": "Root=1-624a96e3-023cf5a15966164e7156c20d", + "content-length": "11", + "accept": "*/*", + "user-agent": "PostmanRuntime/7.29.0", + "accept-encoding": "gzip, deflate, br", + "authorization": "BASIC 1234", + "cookie": "sails.sid=s%3AMpD3hNdjm1Ilte_ml7vgmEopThEhwAbr.HDn7RFcO3qK7oVXIxK3Pxk23g1qHDrNZIN4NozK5oog", + "postmanecho": "post", + "test123": "application/vnd.contoso+json", + "postman-token": "050b1136-19ed-4fbe-ad2a-338a20237321", + "content-type": "application/vnd.contoso+json" + }, + "json": { + "test": "Stef" + }, + "url": "http://postman-echo.com/post" + }, + "Headers": { + "Content-Type": "application/json; charset=utf-8", + "Content-Length": "663", + "Date": "Mon, 04 Apr 2022 06:57:39 GMT", + "Connection": "keep-alive", + "ETag": "W/\"297-FimzXopdNUy6DBi49iOW6Tm9q0o\"", + "Vary": "Accept-Encoding", + "Set-Cookie": "sails.sid=s%3AhtpOsTOJqbHuXmMAZT6cyHLR6FBx1XhV.0loKIsdu5GJKgjJeha16NVWYl%2B0BvPeEQYHcLBCLJ20; Path=/; HttpOnly" + } + } +} \ No newline at end of file diff --git a/examples/WireMock.Net.Console.Proxy.NETCoreApp/Program.cs b/examples/WireMock.Net.Console.Proxy.NETCoreApp/Program.cs new file mode 100644 index 000000000..0d9090957 --- /dev/null +++ b/examples/WireMock.Net.Console.Proxy.NETCoreApp/Program.cs @@ -0,0 +1,53 @@ +using Newtonsoft.Json; +using WireMock.Logging; +using WireMock.RequestBuilders; +using WireMock.ResponseBuilders; +using WireMock.Server; +using WireMock.Settings; + +namespace WireMock.Net.Console.Proxy.NETCoreApp; + +static class Program +{ + static void Main(params string[] args) + { + var server = WireMockServer.Start(new WireMockServerSettings + { + Logger = new WireMockConsoleLogger(), + Urls = new[] { "http://localhost:9091/", "https://localhost:9443/" }, + StartAdminInterface = true, + ReadStaticMappings = true, + WatchStaticMappings = true, + WatchStaticMappingsInSubdirectories = true, + ProxyAndRecordSettings = new ProxyAndRecordSettings + { + Url = "http://postman-echo.com/post", + SaveMapping = true, + SaveMappingToFile = true, + ExcludedHeaders = new[] { "Postman-Token" }, + ExcludedCookies = new[] { "sails.sid" } + } + }); + + //server + // .Given(Request.Create().UsingGet()) + // .RespondWith(Response.Create() + // .WithProxy(new ProxyAndRecordSettings + // { + // Url = "http://postman-echo.com/post", + // SaveMapping = true, + // SaveMappingToFile = true + // })); + + System.Console.WriteLine("Press any key to stop the server"); + System.Console.ReadKey(); + server.Stop(); + + System.Console.WriteLine("Displaying all requests"); + var allRequests = server.LogEntries; + System.Console.WriteLine(JsonConvert.SerializeObject(allRequests, Formatting.Indented)); + + System.Console.WriteLine("Press any key to quit"); + System.Console.ReadKey(); + } +} \ No newline at end of file diff --git a/examples/WireMock.Net.Console.Record.NETCoreApp/WireMock.Net.Console.Proxy.NETCoreApp.csproj b/examples/WireMock.Net.Console.Proxy.NETCoreApp/WireMock.Net.Console.Proxy.NETCoreApp.csproj similarity index 86% rename from examples/WireMock.Net.Console.Record.NETCoreApp/WireMock.Net.Console.Proxy.NETCoreApp.csproj rename to examples/WireMock.Net.Console.Proxy.NETCoreApp/WireMock.Net.Console.Proxy.NETCoreApp.csproj index acdce24f4..c56aae2c6 100644 --- a/examples/WireMock.Net.Console.Record.NETCoreApp/WireMock.Net.Console.Proxy.NETCoreApp.csproj +++ b/examples/WireMock.Net.Console.Proxy.NETCoreApp/WireMock.Net.Console.Proxy.NETCoreApp.csproj @@ -1,8 +1,8 @@ - + Exe - net5.0 + net6.0 ../../resources/WireMock.Net-Logo.ico diff --git a/examples/WireMock.Net.Console.Record.NETCoreApp/Program.cs b/examples/WireMock.Net.Console.Record.NETCoreApp/Program.cs deleted file mode 100644 index 604b66157..000000000 --- a/examples/WireMock.Net.Console.Record.NETCoreApp/Program.cs +++ /dev/null @@ -1,50 +0,0 @@ -using Newtonsoft.Json; -using WireMock.RequestBuilders; -using WireMock.ResponseBuilders; -using WireMock.Server; -using WireMock.Settings; - -namespace WireMock.Net.Console.Proxy.NETCoreApp -{ - static class Program - { - static void Main(params string[] args) - { - var server = WireMockServer.Start(new WireMockServerSettings - { - Urls = new[] { "http://localhost:9091/", "https://localhost:9443/" }, - StartAdminInterface = true, - ReadStaticMappings = false, - //ProxyAndRecordSettings = new ProxyAndRecordSettings - //{ - // Url = "https://www.google.com", - // //ClientX509Certificate2ThumbprintOrSubjectName = "www.yourclientcertname.com OR yourcertificatethumbprint (only if the service you're proxying to requires it)", - // SaveMapping = true, - // SaveMappingToFile = false, - // ExcludedHeaders = new [] { "dnt", "Content-Length" } - //} - }); - - server - .Given(Request.Create().UsingGet()) - .RespondWith(Response.Create() - .WithProxy(new ProxyAndRecordSettings - { - Url = "http://postman-echo.com/post", - SaveMapping = true, - SaveMappingToFile = true - })); - - System.Console.WriteLine("Press any key to stop the server"); - System.Console.ReadKey(); - server.Stop(); - - System.Console.WriteLine("Displaying all requests"); - var allRequests = server.LogEntries; - System.Console.WriteLine(JsonConvert.SerializeObject(allRequests, Formatting.Indented)); - - System.Console.WriteLine("Press any key to quit"); - System.Console.ReadKey(); - } - } -} \ No newline at end of file diff --git a/examples/WireMock.Net.WebApplication.NET6/App.cs b/examples/WireMock.Net.WebApplication.NET6/App.cs index 1274605be..e4454f070 100644 --- a/examples/WireMock.Net.WebApplication.NET6/App.cs +++ b/examples/WireMock.Net.WebApplication.NET6/App.cs @@ -1,28 +1,27 @@ -using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Hosting; using System.Threading; using System.Threading.Tasks; -namespace WireMock.Net.WebApplication +namespace WireMock.Net.WebApplication; + +public class App : IHostedService { - public class App : IHostedService - { - private readonly IWireMockService _service; + private readonly IWireMockService _service; - public App(IWireMockService service) - { - _service = service; - } + public App(IWireMockService service) + { + _service = service; + } - public Task StartAsync(CancellationToken cancellationToken) - { - _service.Start(); - return Task.CompletedTask; - } + public Task StartAsync(CancellationToken cancellationToken) + { + _service.Start(); + return Task.CompletedTask; + } - public Task StopAsync(CancellationToken cancellationToken) - { - _service.Stop(); - return Task.CompletedTask; - } + public Task StopAsync(CancellationToken cancellationToken) + { + _service.Stop(); + return Task.CompletedTask; } } \ No newline at end of file diff --git a/examples/WireMock.Net.WebApplication.NET6/IWireMockService.cs b/examples/WireMock.Net.WebApplication.NET6/IWireMockService.cs index 02e489d38..764dd202c 100644 --- a/examples/WireMock.Net.WebApplication.NET6/IWireMockService.cs +++ b/examples/WireMock.Net.WebApplication.NET6/IWireMockService.cs @@ -1,9 +1,8 @@ -namespace WireMock.Net.WebApplication +namespace WireMock.Net.WebApplication; + +public interface IWireMockService { - public interface IWireMockService - { - void Start(); + void Start(); - void Stop(); - } + void Stop(); } \ No newline at end of file diff --git a/examples/WireMock.Net.WebApplication.NET6/Program.cs b/examples/WireMock.Net.WebApplication.NET6/Program.cs index 1effb357c..69561724e 100644 --- a/examples/WireMock.Net.WebApplication.NET6/Program.cs +++ b/examples/WireMock.Net.WebApplication.NET6/Program.cs @@ -1,30 +1,29 @@ -using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using WireMock.Settings; -namespace WireMock.Net.WebApplication +namespace WireMock.Net.WebApplication; + +public class Program { - public class Program + public static void Main(string[] args) { - public static void Main(string[] args) - { - CreateHostBuilder(args).Build().Run(); - } + CreateHostBuilder(args).Build().Run(); + } - private static IHostBuilder CreateHostBuilder(string[] args) - => Host.CreateDefaultBuilder(args) - .ConfigureServices((host, services) => ConfigureServices(services, host.Configuration)); + private static IHostBuilder CreateHostBuilder(string[] args) + => Host.CreateDefaultBuilder(args) + .ConfigureServices((host, services) => ConfigureServices(services, host.Configuration)); - private static void ConfigureServices(IServiceCollection services, IConfiguration configuration) - { - services.AddLogging(logging => logging.AddConsole().AddDebug()); + private static void ConfigureServices(IServiceCollection services, IConfiguration configuration) + { + services.AddLogging(logging => logging.AddConsole().AddDebug()); - services.AddTransient(); - services.Configure(configuration.GetSection("WireMockServerSettings")); + services.AddTransient(); + services.Configure(configuration.GetSection("WireMockServerSettings")); - services.AddHostedService(); - } + services.AddHostedService(); } } \ No newline at end of file diff --git a/examples/WireMock.Net.WebApplication.NET6/WireMockService.cs b/examples/WireMock.Net.WebApplication.NET6/WireMockService.cs index 1bbe763c7..818e2a8dd 100644 --- a/examples/WireMock.Net.WebApplication.NET6/WireMockService.cs +++ b/examples/WireMock.Net.WebApplication.NET6/WireMockService.cs @@ -1,83 +1,81 @@ -using System; +using System; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Newtonsoft.Json; - using WireMock.Admin.Requests; using WireMock.Logging; using WireMock.Server; using WireMock.Settings; -namespace WireMock.Net.WebApplication +namespace WireMock.Net.WebApplication; + +public class WireMockService : IWireMockService { - public class WireMockService : IWireMockService + private WireMockServer? _server; + private readonly ILogger _logger; + private readonly WireMockServerSettings _settings; + + private class Logger : IWireMockLogger { - private WireMockServer _server; private readonly ILogger _logger; - private readonly WireMockServerSettings _settings; - private class Logger : IWireMockLogger + public Logger(ILogger logger) { - private readonly ILogger _logger; - - public Logger(ILogger logger) - { - _logger = logger; - } - - public void Debug(string formatString, params object[] args) - { - _logger.LogDebug(formatString, args); - } - - public void Info(string formatString, params object[] args) - { - _logger.LogInformation(formatString, args); - } - - public void Warn(string formatString, params object[] args) - { - _logger.LogWarning(formatString, args); - } - - public void Error(string formatString, params object[] args) - { - _logger.LogError(formatString, args); - } - - public void DebugRequestResponse(LogEntryModel logEntryModel, bool isAdminrequest) - { - string message = JsonConvert.SerializeObject(logEntryModel, Formatting.Indented); - _logger.LogDebug("Admin[{0}] {1}", isAdminrequest, message); - } - - public void Error(string formatString, Exception exception) - { - _logger.LogError(formatString, exception.Message); - } + _logger = logger; } - public WireMockService(ILogger logger, IOptions settings) + public void Debug(string formatString, params object[] args) { - _logger = logger; - _settings = settings.Value; + _logger.LogDebug(formatString, args); + } - _settings.Logger = new Logger(logger); + public void Info(string formatString, params object[] args) + { + _logger.LogInformation(formatString, args); } - public void Start() + public void Warn(string formatString, params object[] args) { - _logger.LogInformation("WireMock.Net server starting"); + _logger.LogWarning(formatString, args); + } - _server = WireMockServer.Start(_settings); + public void Error(string formatString, params object[] args) + { + _logger.LogError(formatString, args); + } - _logger.LogInformation($"WireMock.Net server settings {JsonConvert.SerializeObject(_settings)}"); + public void DebugRequestResponse(LogEntryModel logEntryModel, bool isAdminrequest) + { + string message = JsonConvert.SerializeObject(logEntryModel, Formatting.Indented); + _logger.LogDebug("Admin[{0}] {1}", isAdminrequest, message); } - public void Stop() + public void Error(string formatString, Exception exception) { - _logger.LogInformation("WireMock.Net server stopping"); - _server?.Stop(); + _logger.LogError(formatString, exception.Message); } } -} + + public WireMockService(ILogger logger, IOptions settings) + { + _logger = logger; + _settings = settings.Value; + + _settings.Logger = new Logger(logger); + } + + public void Start() + { + _logger.LogInformation("WireMock.Net server starting"); + + _server = WireMockServer.Start(_settings); + + _logger.LogInformation($"WireMock.Net server settings {JsonConvert.SerializeObject(_settings)}"); + } + + public void Stop() + { + _logger.LogInformation("WireMock.Net server stopping"); + _server?.Stop(); + } +} \ No newline at end of file diff --git a/examples/WireMock.Net.WebApplication.NET6/appsettings.json b/examples/WireMock.Net.WebApplication.NET6/appsettings.json index 2ab0a2c6a..fb3d6edbe 100644 --- a/examples/WireMock.Net.WebApplication.NET6/appsettings.json +++ b/examples/WireMock.Net.WebApplication.NET6/appsettings.json @@ -1,29 +1,29 @@ -{ - "Logging": { - "IncludeScopes": false, - "Debug": { - "LogLevel": { - "Default": "Debug" - } +{ + "Logging": { + "IncludeScopes": false, + "Debug": { + "LogLevel": { + "Default": "Debug" + } + }, + "Console": { + "LogLevel": { + "Default": "Debug" + } + } }, - "Console": { - "LogLevel": { - "Default": "Debug" - } + "WireMockServerSettings": { + "StartAdminInterface": true, + "Urls": [ + "https://localhost:8081/" + ], + "AllowPartialMapping": false, + "HandleRequestsSynchronously": true, + "ThrowExceptionWhenMatcherFails": true, + "ProxyAndRecordSettings": { + "Url": "http://postman-echo.com/post", + "SaveMapping": true, + "SaveMappingToFile": true + } } - }, - "WireMockServerSettings": { - "StartAdminInterface": true, - "Urls": [ - "https://localhost:8081/" - ], - "AllowPartialMapping": false, - "HandleRequestsSynchronously": true, - "ThrowExceptionWhenMatcherFails": true, - "ProxyAndRecordSettings": { - "Url": "https://support.smartbear.com/", - "SaveMapping": true, - "SaveMappingToFile": true - } - } } diff --git a/examples/WireMock.Net.WebApplication.NETCore2/Properties/launchSettings.json b/examples/WireMock.Net.WebApplication.NETCore2/Properties/launchSettings.json index 31f0369b3..1ff6dab6a 100644 --- a/examples/WireMock.Net.WebApplication.NETCore2/Properties/launchSettings.json +++ b/examples/WireMock.Net.WebApplication.NETCore2/Properties/launchSettings.json @@ -2,13 +2,9 @@ "iisSettings": { "windowsAuthentication": false, "anonymousAuthentication": true, - "iis": { - "applicationUrl": "http://localhost//wiremock", - "sslPort": 0 - }, "iisExpress": { - "applicationUrl": "http://localhost:56513/", - "sslPort": 0 + "applicationUrl": "http://localhost:60097/", + "sslPort": 44321 } }, "profiles": { diff --git a/examples/WireMock.Net.WebApplication.NETCore3/WireMock.Net.WebApplication.NETCore3.csproj b/examples/WireMock.Net.WebApplication.NETCore3/WireMock.Net.WebApplication.NETCore3.csproj index 69570667e..bdec0b04e 100644 --- a/examples/WireMock.Net.WebApplication.NETCore3/WireMock.Net.WebApplication.NETCore3.csproj +++ b/examples/WireMock.Net.WebApplication.NETCore3/WireMock.Net.WebApplication.NETCore3.csproj @@ -1,8 +1,8 @@ - + netcoreapp3.1 - win10-x64 + WireMock.Net.WebApplication.Program WireMock.Net.WebApplication WireMock.Net.WebApplication diff --git a/src/WireMock.Net.Abstractions/Admin/Mappings/MappingModel.cs b/src/WireMock.Net.Abstractions/Admin/Mappings/MappingModel.cs index 58ee73434..e889fbc5c 100644 --- a/src/WireMock.Net.Abstractions/Admin/Mappings/MappingModel.cs +++ b/src/WireMock.Net.Abstractions/Admin/Mappings/MappingModel.cs @@ -25,7 +25,7 @@ public class MappingModel public string Title { get; set; } /// - /// The priority. + /// The priority. (A low value means higher priority.) /// public int? Priority { get; set; } diff --git a/src/WireMock.Net.Abstractions/Admin/Settings/ProxyAndRecordSettingsModel.cs b/src/WireMock.Net.Abstractions/Admin/Settings/ProxyAndRecordSettingsModel.cs new file mode 100644 index 000000000..d8f9281ef --- /dev/null +++ b/src/WireMock.Net.Abstractions/Admin/Settings/ProxyAndRecordSettingsModel.cs @@ -0,0 +1,59 @@ +using JetBrains.Annotations; + +namespace WireMock.Admin.Settings; + +[FluentBuilder.AutoGenerateBuilder] +public class ProxyAndRecordSettingsModel +{ + /// + /// The clientCertificate thumbprint or subject name fragment to use. + /// Example thumbprint : "D2DBF135A8D06ACCD0E1FAD9BFB28678DF7A9818". Example subject name: "www.google.com"" + /// + public string ClientX509Certificate2ThumbprintOrSubjectName { get; set; } + + /// + /// Defines the WebProxySettings. + /// + public WebProxySettingsModel WebProxySettings { get; set; } + + /// + /// Proxy requests should follow redirection (30x). + /// + public bool? AllowAutoRedirect { get; set; } + + /// + /// The URL to proxy. + /// + public string Url { get; set; } + + /// + /// Save the mapping for each request/response to the internal Mappings. + /// + public bool SaveMapping { get; set; } + + /// + /// Save the mapping for each request/response also to a file. (Note that SaveMapping must also be set to true.) + /// + public bool SaveMappingToFile { get; set; } + + /// + /// Only save request/response to the internal Mappings if the status code is included in this pattern. (Note that SaveMapping must also be set to true.) + /// The pattern can contain a single value like "200", but also ranges like "2xx", "100,300,600" or "100-299,6xx" are supported. + /// + public string SaveMappingForStatusCodePattern { get; set; } = "*"; + + /// + /// Defines a list from headers which will be excluded from the saved mappings. + /// + public string[] ExcludedHeaders { get; set; } + + /// + /// Defines a list of cookies which will be excluded from the saved mappings. + /// + public string[] ExcludedCookies { get; set; } + + /// + /// Prefer the Proxy Mapping over the saved Mapping (in case SaveMapping is set to true). + /// + // public bool PreferProxyMapping { get; set; } +} \ No newline at end of file diff --git a/src/WireMock.Net.Abstractions/Admin/Settings/SettingsModel.cs b/src/WireMock.Net.Abstractions/Admin/Settings/SettingsModel.cs index 16736557b..84e6cd81e 100644 --- a/src/WireMock.Net.Abstractions/Admin/Settings/SettingsModel.cs +++ b/src/WireMock.Net.Abstractions/Admin/Settings/SettingsModel.cs @@ -1,6 +1,6 @@ using System.Text.RegularExpressions; +using JetBrains.Annotations; using WireMock.Handlers; -using WireMock.Types; namespace WireMock.Admin.Settings { @@ -55,9 +55,29 @@ public class SettingsModel /// public bool? SaveUnmatchedRequests { get; set; } + /// + /// Gets or sets if the static mappings should be read at startup. + /// + public bool? ReadStaticMappings { get; set; } + + /// + /// Watch the static mapping files + folder for changes when running. + /// + public bool? WatchStaticMappings { get; set; } + + /// + /// A value indicating whether subdirectories within the static mappings path should be monitored. + /// + public bool? WatchStaticMappingsInSubdirectories { get; set; } + /// /// Policies to use when using CORS. By default CORS is disabled. [Optional] /// public string CorsPolicyOptions { get; set; } + + /// + /// The proxy and record settings. + /// + public ProxyAndRecordSettingsModel ProxyAndRecordSettings { get; set; } } } \ No newline at end of file diff --git a/src/WireMock.Net.Abstractions/Admin/Settings/WebProxySettings.cs b/src/WireMock.Net.Abstractions/Admin/Settings/WebProxySettings.cs new file mode 100644 index 000000000..3f7ce1c8b --- /dev/null +++ b/src/WireMock.Net.Abstractions/Admin/Settings/WebProxySettings.cs @@ -0,0 +1,24 @@ +namespace WireMock.Admin.Settings +{ + /// + /// WebProxySettings + /// + [FluentBuilder.AutoGenerateBuilder] + public class WebProxySettingsModel + { + /// + /// A string instance that contains the address of the proxy server. + /// + public string Address { get; set; } + + /// + /// The user name associated with the credentials. + /// + public string UserName { get; set; } + + /// + /// The password for the user name associated with the credentials. + /// + public string Password { get; set; } + } +} \ No newline at end of file diff --git a/src/WireMock.Net.Abstractions/WireMock.Net.Abstractions.csproj b/src/WireMock.Net.Abstractions/WireMock.Net.Abstractions.csproj index ee4072162..e45c93737 100644 --- a/src/WireMock.Net.Abstractions/WireMock.Net.Abstractions.csproj +++ b/src/WireMock.Net.Abstractions/WireMock.Net.Abstractions.csproj @@ -41,4 +41,11 @@ + + \ No newline at end of file diff --git a/src/WireMock.Net/Constants/WireMockConstants.cs b/src/WireMock.Net/Constants/WireMockConstants.cs new file mode 100644 index 000000000..f4b3bae81 --- /dev/null +++ b/src/WireMock.Net/Constants/WireMockConstants.cs @@ -0,0 +1,9 @@ +namespace WireMock.Constants +{ + internal static class WireMockConstants + { + public const int AdminPriority = int.MinValue; + public const int MinPriority = -1_000_000; + public const int ProxyPriority = -2_000_000; + } +} \ No newline at end of file diff --git a/src/WireMock.Net/IMapping.cs b/src/WireMock.Net/IMapping.cs index cb39f320b..b4bbaff91 100644 --- a/src/WireMock.Net/IMapping.cs +++ b/src/WireMock.Net/IMapping.cs @@ -34,7 +34,7 @@ public interface IMapping string Path { get; set; } /// - /// Gets the priority. + /// Gets the priority. (A low value means higher priority.) /// int Priority { get; } @@ -91,6 +91,14 @@ public interface IMapping /// bool IsAdminInterface { get; } + /// + /// Gets a value indicating whether this mapping is a Proxy Mapping. + /// + /// + /// true if this mapping is a Proxy Mapping; otherwise, false. + /// + bool IsProxy { get; } + /// /// Gets a value indicating whether this mapping to be logged. /// diff --git a/src/WireMock.Net/Mapping.cs b/src/WireMock.Net/Mapping.cs index 65fe03c9e..a43b2389c 100644 --- a/src/WireMock.Net/Mapping.cs +++ b/src/WireMock.Net/Mapping.cs @@ -52,6 +52,9 @@ public class Mapping : IMapping /// public bool IsAdminInterface => Provider is DynamicResponseProvider || Provider is DynamicAsyncResponseProvider || Provider is ProxyAsyncResponseProvider; + /// + public bool IsProxy => Provider is ProxyAsyncResponseProvider; + /// public bool LogMapping => !(Provider is DynamicResponseProvider || Provider is DynamicAsyncResponseProvider); diff --git a/src/WireMock.Net/Serialization/MappingToFileSaver.cs b/src/WireMock.Net/Serialization/MappingToFileSaver.cs index a4e3b068e..708d2661d 100644 --- a/src/WireMock.Net/Serialization/MappingToFileSaver.cs +++ b/src/WireMock.Net/Serialization/MappingToFileSaver.cs @@ -1,5 +1,6 @@ -using System.IO; +using System.IO; using System.Linq; +using JetBrains.Annotations; using Newtonsoft.Json; using WireMock.Settings; using Stef.Validation; @@ -13,13 +14,14 @@ internal class MappingToFileSaver public MappingToFileSaver(WireMockServerSettings settings, MappingConverter mappingConverter) { - Guard.NotNull(settings, nameof(settings)); + Guard.NotNull(settings); + Guard.NotNull(mappingConverter); _settings = settings; _mappingConverter = mappingConverter; } - public void SaveMappingToFile(IMapping mapping, string folder = null) + public void SaveMappingToFile(IMapping mapping, [CanBeNull] string folder = null) { if (folder == null) { diff --git a/src/WireMock.Net/Server/RespondWithAProvider.cs b/src/WireMock.Net/Server/RespondWithAProvider.cs index c755156c8..05f39b2af 100644 --- a/src/WireMock.Net/Server/RespondWithAProvider.cs +++ b/src/WireMock.Net/Server/RespondWithAProvider.cs @@ -10,6 +10,7 @@ using WireMock.Types; using WireMock.Util; using Stef.Validation; +using WireMock.Constants; namespace WireMock.Server { diff --git a/src/WireMock.Net/Server/WireMockServer.Admin.cs b/src/WireMock.Net/Server/WireMockServer.Admin.cs index 503fc2ce7..da1569cfc 100644 --- a/src/WireMock.Net/Server/WireMockServer.Admin.cs +++ b/src/WireMock.Net/Server/WireMockServer.Admin.cs @@ -13,6 +13,7 @@ using WireMock.Admin.Mappings; using WireMock.Admin.Scenarios; using WireMock.Admin.Settings; +using WireMock.Constants; using WireMock.Http; using WireMock.Logging; using WireMock.Matchers; @@ -26,940 +27,968 @@ using WireMock.Types; using WireMock.Util; -namespace WireMock.Server +namespace WireMock.Server; + +/// +/// The fluent mock server. +/// +public partial class WireMockServer { - /// - /// The fluent mock server. - /// - public partial class WireMockServer - { - private const int EnhancedFileSystemWatcherTimeoutMs = 1000; - 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 AdminMappingsWireMockOrg = "/__admin/mappings/wiremock.org"; - private const string AdminRequests = "/__admin/requests"; - private const string AdminSettings = "/__admin/settings"; - private const string AdminScenarios = "/__admin/scenarios"; - private const string QueryParamReloadStaticMappings = "reloadStaticMappings"; - - private readonly RegexMatcher _adminRequestContentTypeJson = new ContentTypeMatcher(ContentTypeJson, true); - private readonly RegexMatcher _adminMappingsGuidPathMatcher = new RegexMatcher(@"^\/__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(@"^\/__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 EnhancedFileSystemWatcher _enhancedFileSystemWatcher; - - #region InitAdmin - private void InitAdmin() - { - // __admin/settings - Given(Request.Create().WithPath(AdminSettings).UsingGet()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(SettingsGet)); - Given(Request.Create().WithPath(AdminSettings).UsingMethod("PUT", "POST").WithHeader(HttpKnownHeaderNames.ContentType, _adminRequestContentTypeJson)).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(SettingsUpdate)); - - // __admin/mappings - Given(Request.Create().WithPath(AdminMappings).UsingGet()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(MappingsGet)); - Given(Request.Create().WithPath(AdminMappings).UsingPost().WithHeader(HttpKnownHeaderNames.ContentType, _adminRequestContentTypeJson)).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(MappingsPost)); - Given(Request.Create().WithPath(AdminMappingsWireMockOrg).UsingPost().WithHeader(HttpKnownHeaderNames.ContentType, _adminRequestContentTypeJson)).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(MappingsPostWireMockOrg)); - Given(Request.Create().WithPath(AdminMappings).UsingDelete()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(MappingsDelete)); - - // __admin/mappings/reset - Given(Request.Create().WithPath(AdminMappings + "/reset").UsingPost()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(MappingsReset)); - - // __admin/mappings/{guid} - Given(Request.Create().WithPath(_adminMappingsGuidPathMatcher).UsingGet()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(MappingGet)); - Given(Request.Create().WithPath(_adminMappingsGuidPathMatcher).UsingPut().WithHeader(HttpKnownHeaderNames.ContentType, _adminRequestContentTypeJson)).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(MappingPut)); - Given(Request.Create().WithPath(_adminMappingsGuidPathMatcher).UsingDelete()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(MappingDelete)); - - // __admin/mappings/save - Given(Request.Create().WithPath(AdminMappings + "/save").UsingPost()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(MappingsSave)); - - // __admin/requests - Given(Request.Create().WithPath(AdminRequests).UsingGet()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(RequestsGet)); - Given(Request.Create().WithPath(AdminRequests).UsingDelete()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(RequestsDelete)); - - // __admin/requests/reset - Given(Request.Create().WithPath(AdminRequests + "/reset").UsingPost()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(RequestsDelete)); - - // __admin/request/{guid} - Given(Request.Create().WithPath(_adminRequestsGuidPathMatcher).UsingGet()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(RequestGet)); - Given(Request.Create().WithPath(_adminRequestsGuidPathMatcher).UsingDelete()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(RequestDelete)); - - // __admin/requests/find - Given(Request.Create().WithPath(AdminRequests + "/find").UsingPost()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(RequestsFind)); - - // __admin/scenarios - Given(Request.Create().WithPath(AdminScenarios).UsingGet()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(ScenariosGet)); - Given(Request.Create().WithPath(AdminScenarios).UsingDelete()).AtPriority(AdminPriority).RespondWith(new DynamicResponseProvider(ScenariosReset)); - - // __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 - /// - [PublicAPI] - public void SaveStaticMappings([CanBeNull] string folder = null) - { - foreach (var mapping in Mappings.Where(m => !m.IsAdminInterface)) - { - _mappingToFileSaver.SaveMappingToFile(mapping, folder); - } + private const int EnhancedFileSystemWatcherTimeoutMs = 1000; + private const string ContentTypeJson = "application/json"; + private const string AdminFiles = "/__admin/files"; + private const string AdminMappings = "/__admin/mappings"; + private const string AdminMappingsWireMockOrg = "/__admin/mappings/wiremock.org"; + private const string AdminRequests = "/__admin/requests"; + private const string AdminSettings = "/__admin/settings"; + private const string AdminScenarios = "/__admin/scenarios"; + private const string QueryParamReloadStaticMappings = "reloadStaticMappings"; + + private readonly Guid _proxyMappingGuid = new Guid("e59914fd-782e-428e-91c1-4810ffb86567"); + private readonly RegexMatcher _adminRequestContentTypeJson = new ContentTypeMatcher(ContentTypeJson, true); + private readonly RegexMatcher _adminMappingsGuidPathMatcher = new RegexMatcher(@"^\/__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(@"^\/__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 EnhancedFileSystemWatcher _enhancedFileSystemWatcher; + + #region InitAdmin + private void InitAdmin() + { + // __admin/settings + Given(Request.Create().WithPath(AdminSettings).UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(SettingsGet)); + Given(Request.Create().WithPath(AdminSettings).UsingMethod("PUT", "POST").WithHeader(HttpKnownHeaderNames.ContentType, _adminRequestContentTypeJson)).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(SettingsUpdate)); + + // __admin/mappings + Given(Request.Create().WithPath(AdminMappings).UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingsGet)); + Given(Request.Create().WithPath(AdminMappings).UsingPost().WithHeader(HttpKnownHeaderNames.ContentType, _adminRequestContentTypeJson)).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingsPost)); + Given(Request.Create().WithPath(AdminMappingsWireMockOrg).UsingPost().WithHeader(HttpKnownHeaderNames.ContentType, _adminRequestContentTypeJson)).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingsPostWireMockOrg)); + Given(Request.Create().WithPath(AdminMappings).UsingDelete()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingsDelete)); + + // __admin/mappings/reset + Given(Request.Create().WithPath(AdminMappings + "/reset").UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingsReset)); + + // __admin/mappings/{guid} + Given(Request.Create().WithPath(_adminMappingsGuidPathMatcher).UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingGet)); + Given(Request.Create().WithPath(_adminMappingsGuidPathMatcher).UsingPut().WithHeader(HttpKnownHeaderNames.ContentType, _adminRequestContentTypeJson)).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingPut)); + Given(Request.Create().WithPath(_adminMappingsGuidPathMatcher).UsingDelete()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingDelete)); + + // __admin/mappings/save + Given(Request.Create().WithPath(AdminMappings + "/save").UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingsSave)); + + // __admin/requests + Given(Request.Create().WithPath(AdminRequests).UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(RequestsGet)); + Given(Request.Create().WithPath(AdminRequests).UsingDelete()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(RequestsDelete)); + + // __admin/requests/reset + Given(Request.Create().WithPath(AdminRequests + "/reset").UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(RequestsDelete)); + + // __admin/request/{guid} + Given(Request.Create().WithPath(_adminRequestsGuidPathMatcher).UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(RequestGet)); + Given(Request.Create().WithPath(_adminRequestsGuidPathMatcher).UsingDelete()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(RequestDelete)); + + // __admin/requests/find + Given(Request.Create().WithPath(AdminRequests + "/find").UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(RequestsFind)); + + // __admin/scenarios + Given(Request.Create().WithPath(AdminScenarios).UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(ScenariosGet)); + Given(Request.Create().WithPath(AdminScenarios).UsingDelete()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(ScenariosReset)); + + // __admin/scenarios/reset + Given(Request.Create().WithPath(AdminScenarios + "/reset").UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(ScenariosReset)); + + // __admin/files/{filename} + Given(Request.Create().WithPath(_adminFilesFilenamePathMatcher).UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(FilePost)); + Given(Request.Create().WithPath(_adminFilesFilenamePathMatcher).UsingPut()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(FilePut)); + Given(Request.Create().WithPath(_adminFilesFilenamePathMatcher).UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(FileGet)); + Given(Request.Create().WithPath(_adminFilesFilenamePathMatcher).UsingHead()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(FileHead)); + Given(Request.Create().WithPath(_adminFilesFilenamePathMatcher).UsingDelete()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(FileDelete)); + } + #endregion + + #region StaticMappings + /// + [PublicAPI] + public void SaveStaticMappings([CanBeNull] string folder = null) + { + foreach (var mapping in Mappings.Where(m => !m.IsAdminInterface)) + { + _mappingToFileSaver.SaveMappingToFile(mapping, folder); } + } - /// - [PublicAPI] - public void ReadStaticMappings([CanBeNull] string folder = null) + /// + [PublicAPI] + public void ReadStaticMappings([CanBeNull] string folder = null) + { + if (folder == null) { - if (folder == null) - { - folder = _settings.FileSystemHandler.GetMappingFolder(); - } + folder = _settings.FileSystemHandler.GetMappingFolder(); + } - if (!_settings.FileSystemHandler.FolderExists(folder)) + if (!_settings.FileSystemHandler.FolderExists(folder)) + { + _settings.Logger.Info("The Static Mapping folder '{0}' does not exist, reading Static MappingFiles will be skipped.", folder); + return; + } + + foreach (string filename in _settings.FileSystemHandler.EnumerateFiles(folder, _settings.WatchStaticMappingsInSubdirectories == true).OrderBy(f => f)) + { + _settings.Logger.Info("Reading Static MappingFile : '{0}'", filename); + + try { - _settings.Logger.Info("The Static Mapping folder '{0}' does not exist, reading Static MappingFiles will be skipped.", folder); - return; + ReadStaticMappingAndAddOrUpdate(filename); } - - foreach (string filename in _settings.FileSystemHandler.EnumerateFiles(folder, _settings.WatchStaticMappingsInSubdirectories == true).OrderBy(f => f)) + catch { - _settings.Logger.Info("Reading Static MappingFile : '{0}'", filename); - - try - { - ReadStaticMappingAndAddOrUpdate(filename); - } - catch - { - _settings.Logger.Error("Static MappingFile : '{0}' could not be read. This file will be skipped.", filename); - } + _settings.Logger.Error("Static MappingFile : '{0}' could not be read. This file will be skipped.", filename); } } + } - /// - [PublicAPI] - public void WatchStaticMappings([CanBeNull] string folder = null) + /// + [PublicAPI] + public void WatchStaticMappings([CanBeNull] string folder = null) + { + if (folder == null) { - if (folder == null) - { - folder = _settings.FileSystemHandler.GetMappingFolder(); - } + folder = _settings.FileSystemHandler.GetMappingFolder(); + } - if (!_settings.FileSystemHandler.FolderExists(folder)) - { - return; - } + if (!_settings.FileSystemHandler.FolderExists(folder)) + { + return; + } - bool includeSubdirectories = _settings.WatchStaticMappingsInSubdirectories == true; - string includeSubdirectoriesText = includeSubdirectories ? " and Subdirectories" : string.Empty; + bool includeSubdirectories = _settings.WatchStaticMappingsInSubdirectories == true; + string includeSubdirectoriesText = includeSubdirectories ? " and Subdirectories" : string.Empty; - _settings.Logger.Info($"Watching folder '{folder}'{includeSubdirectoriesText} for new, updated and deleted MappingFiles."); + _settings.Logger.Info($"Watching folder '{folder}'{includeSubdirectoriesText} for new, updated and deleted MappingFiles."); - DisposeEnhancedFileSystemWatcher(); - _enhancedFileSystemWatcher = new EnhancedFileSystemWatcher(folder, "*.json", EnhancedFileSystemWatcherTimeoutMs) - { - IncludeSubdirectories = includeSubdirectories - }; - _enhancedFileSystemWatcher.Created += EnhancedFileSystemWatcherCreated; - _enhancedFileSystemWatcher.Changed += EnhancedFileSystemWatcherChanged; - _enhancedFileSystemWatcher.Deleted += EnhancedFileSystemWatcherDeleted; - _enhancedFileSystemWatcher.EnableRaisingEvents = true; - } - - /// - [PublicAPI] - public bool ReadStaticMappingAndAddOrUpdate([NotNull] string path) + DisposeEnhancedFileSystemWatcher(); + _enhancedFileSystemWatcher = new EnhancedFileSystemWatcher(folder, "*.json", EnhancedFileSystemWatcherTimeoutMs) { - Guard.NotNull(path, nameof(path)); + IncludeSubdirectories = includeSubdirectories + }; + _enhancedFileSystemWatcher.Created += EnhancedFileSystemWatcherCreated; + _enhancedFileSystemWatcher.Changed += EnhancedFileSystemWatcherChanged; + _enhancedFileSystemWatcher.Deleted += EnhancedFileSystemWatcherDeleted; + _enhancedFileSystemWatcher.EnableRaisingEvents = true; + } + + /// + [PublicAPI] + public bool ReadStaticMappingAndAddOrUpdate(string path) + { + Guard.NotNull(path); - string filenameWithoutExtension = Path.GetFileNameWithoutExtension(path); + string filenameWithoutExtension = Path.GetFileNameWithoutExtension(path); - if (FileHelper.TryReadMappingFileWithRetryAndDelay(_settings.FileSystemHandler, path, out string value)) + if (FileHelper.TryReadMappingFileWithRetryAndDelay(_settings.FileSystemHandler, path, out string value)) + { + var mappingModels = DeserializeJsonToArray(value); + foreach (var mappingModel in mappingModels) { - var mappingModels = DeserializeJsonToArray(value); - foreach (var mappingModel in mappingModels) + if (mappingModels.Length == 1 && Guid.TryParse(filenameWithoutExtension, out Guid guidFromFilename)) { - if (mappingModels.Length == 1 && Guid.TryParse(filenameWithoutExtension, out Guid guidFromFilename)) - { - ConvertMappingAndRegisterAsRespondProvider(mappingModel, guidFromFilename, path); - } - else - { - ConvertMappingAndRegisterAsRespondProvider(mappingModel, null, path); - } + ConvertMappingAndRegisterAsRespondProvider(mappingModel, guidFromFilename, path); + } + else + { + ConvertMappingAndRegisterAsRespondProvider(mappingModel, null, path); } - - return true; } - return false; + return true; } - #endregion - #region Proxy and Record - private HttpClient _httpClientForProxy; + return false; + } + #endregion + + #region Proxy and Record + [CanBeNull] + private HttpClient _httpClientForProxy; - private void InitProxyAndRecord(WireMockServerSettings settings) + private void InitProxyAndRecord(WireMockServerSettings settings) + { + if (settings.ProxyAndRecordSettings == null) { - _httpClientForProxy = HttpClientBuilder.Build(settings.ProxyAndRecordSettings); + _httpClientForProxy = null; + DeleteMapping(_proxyMappingGuid); + return; + } - var respondProvider = Given(Request.Create().WithPath("/*").UsingAnyMethod()); - if (settings.StartAdminInterface == true) - { - respondProvider.AtPriority(ProxyPriority); - } + _httpClientForProxy = HttpClientBuilder.Build(settings.ProxyAndRecordSettings); - respondProvider.RespondWith(new ProxyAsyncResponseProvider(ProxyAndRecordAsync, settings)); + var proxyRespondProvider = Given(Request.Create().WithPath("/*").UsingAnyMethod()).WithGuid(_proxyMappingGuid); + if (settings.StartAdminInterface == true) + { + proxyRespondProvider.AtPriority(WireMockConstants.ProxyPriority); } - private async Task ProxyAndRecordAsync(RequestMessage requestMessage, WireMockServerSettings settings) - { - var requestUri = new Uri(requestMessage.Url); - var proxyUri = new Uri(settings.ProxyAndRecordSettings.Url); - var proxyUriWithRequestPathAndQuery = new Uri(proxyUri, requestUri.PathAndQuery); + proxyRespondProvider.RespondWith(new ProxyAsyncResponseProvider(ProxyAndRecordAsync, settings)); + } - var proxyHelper = new ProxyHelper(settings); + private async Task ProxyAndRecordAsync(RequestMessage requestMessage, WireMockServerSettings settings) + { + var requestUri = new Uri(requestMessage.Url); + var proxyUri = new Uri(settings.ProxyAndRecordSettings.Url); + var proxyUriWithRequestPathAndQuery = new Uri(proxyUri, requestUri.PathAndQuery); - var (responseMessage, mapping) = await proxyHelper.SendAsync( - _settings.ProxyAndRecordSettings, - _httpClientForProxy, - requestMessage, - proxyUriWithRequestPathAndQuery.AbsoluteUri - ).ConfigureAwait(false); + var proxyHelper = new ProxyHelper(settings); - if (mapping != null) - { - if (settings.ProxyAndRecordSettings.SaveMapping) - { - _options.Mappings.TryAdd(mapping.Guid, mapping); - } + var (responseMessage, mapping) = await proxyHelper.SendAsync( + _settings.ProxyAndRecordSettings, + _httpClientForProxy, + requestMessage, + proxyUriWithRequestPathAndQuery.AbsoluteUri + ).ConfigureAwait(false); - if (settings.ProxyAndRecordSettings.SaveMappingToFile) - { - _mappingToFileSaver.SaveMappingToFile(mapping); - } + if (mapping != null) + { + if (settings.ProxyAndRecordSettings.SaveMapping) + { + _options.Mappings.TryAdd(mapping.Guid, mapping); } - return responseMessage; + if (settings.ProxyAndRecordSettings.SaveMappingToFile) + { + _mappingToFileSaver.SaveMappingToFile(mapping); + } } - #endregion - #region Settings - private ResponseMessage SettingsGet(RequestMessage requestMessage) - { - var model = new SettingsModel - { - AllowPartialMapping = _settings.AllowPartialMapping, - MaxRequestLogCount = _settings.MaxRequestLogCount, - RequestLogExpirationDuration = _settings.RequestLogExpirationDuration, - GlobalProcessingDelay = (int?)_options.RequestProcessingDelay?.TotalMilliseconds, - AllowBodyForAllHttpMethods = _settings.AllowBodyForAllHttpMethods, - HandleRequestsSynchronously = _settings.HandleRequestsSynchronously, - ThrowExceptionWhenMatcherFails = _settings.ThrowExceptionWhenMatcherFails, - UseRegexExtended = _settings.UseRegexExtended, - SaveUnmatchedRequests = _settings.SaveUnmatchedRequests, + return responseMessage; + } + #endregion + + #region Settings + private ResponseMessage SettingsGet(RequestMessage requestMessage) + { + var model = new SettingsModel + { + AllowBodyForAllHttpMethods = _settings.AllowBodyForAllHttpMethods, + AllowPartialMapping = _settings.AllowPartialMapping, + GlobalProcessingDelay = (int?)_options.RequestProcessingDelay?.TotalMilliseconds, + HandleRequestsSynchronously = _settings.HandleRequestsSynchronously, + MaxRequestLogCount = _settings.MaxRequestLogCount, + ReadStaticMappings = _settings.ReadStaticMappings, + RequestLogExpirationDuration = _settings.RequestLogExpirationDuration, + SaveUnmatchedRequests = _settings.SaveUnmatchedRequests, + ThrowExceptionWhenMatcherFails = _settings.ThrowExceptionWhenMatcherFails, + UseRegexExtended = _settings.UseRegexExtended, + WatchStaticMappings = _settings.WatchStaticMappings, + WatchStaticMappingsInSubdirectories = _settings.WatchStaticMappingsInSubdirectories, #if USE_ASPNETCORE - CorsPolicyOptions = _settings.CorsPolicyOptions?.ToString() + CorsPolicyOptions = _settings.CorsPolicyOptions?.ToString() #endif - }; + }; - return ToJson(model); - } + model.ProxyAndRecordSettings = TinyMapperUtils.Instance.Map(_settings.ProxyAndRecordSettings); - private ResponseMessage SettingsUpdate(RequestMessage requestMessage) - { - var settings = DeserializeObject(requestMessage); - _options.MaxRequestLogCount = settings.MaxRequestLogCount; - _options.RequestLogExpirationDuration = settings.RequestLogExpirationDuration; - _options.AllowPartialMapping = settings.AllowPartialMapping; - if (settings.GlobalProcessingDelay != null) - { - _options.RequestProcessingDelay = TimeSpan.FromMilliseconds(settings.GlobalProcessingDelay.Value); - } - _options.AllowBodyForAllHttpMethods = settings.AllowBodyForAllHttpMethods; - _options.HandleRequestsSynchronously = settings.HandleRequestsSynchronously; - _settings.ThrowExceptionWhenMatcherFails = settings.ThrowExceptionWhenMatcherFails; - _settings.UseRegexExtended = settings.UseRegexExtended; - _settings.SaveUnmatchedRequests = settings.SaveUnmatchedRequests; + return ToJson(model); + } + private ResponseMessage SettingsUpdate(RequestMessage requestMessage) + { + var settings = DeserializeObject(requestMessage); + + // _settings + _settings.AllowBodyForAllHttpMethods = settings.AllowBodyForAllHttpMethods; + _settings.AllowPartialMapping = settings.AllowPartialMapping; + _settings.HandleRequestsSynchronously = settings.HandleRequestsSynchronously; + _settings.MaxRequestLogCount = settings.MaxRequestLogCount; + _settings.ProxyAndRecordSettings = TinyMapperUtils.Instance.Map(settings.ProxyAndRecordSettings); + _settings.ReadStaticMappings = settings.ReadStaticMappings; + _settings.RequestLogExpirationDuration = settings.RequestLogExpirationDuration; + _settings.SaveUnmatchedRequests = settings.SaveUnmatchedRequests; + _settings.ThrowExceptionWhenMatcherFails = settings.ThrowExceptionWhenMatcherFails; + _settings.UseRegexExtended = settings.UseRegexExtended; + _settings.WatchStaticMappings = settings.WatchStaticMappings; + _settings.WatchStaticMappingsInSubdirectories = settings.WatchStaticMappingsInSubdirectories; + + InitSettings(_settings); + + // _options + if (settings.GlobalProcessingDelay != null) + { + _options.RequestProcessingDelay = TimeSpan.FromMilliseconds(settings.GlobalProcessingDelay.Value); + } + _options.AllowBodyForAllHttpMethods = settings.AllowBodyForAllHttpMethods; + _options.AllowPartialMapping = settings.AllowPartialMapping; + _options.HandleRequestsSynchronously = settings.HandleRequestsSynchronously; + _options.MaxRequestLogCount = settings.MaxRequestLogCount; + _options.RequestLogExpirationDuration = settings.RequestLogExpirationDuration; + + // _settings & _options #if USE_ASPNETCORE - if (Enum.TryParse(settings.CorsPolicyOptions, true, out var corsPolicyOptions)) - { - _settings.CorsPolicyOptions = corsPolicyOptions; - } + if (Enum.TryParse(settings.CorsPolicyOptions, true, out var corsPolicyOptions)) + { + _settings.CorsPolicyOptions = corsPolicyOptions; + _options.CorsPolicyOptions = corsPolicyOptions; + } #endif - return ResponseMessageBuilder.Create("Settings updated"); - } - #endregion Settings + return ResponseMessageBuilder.Create("Settings updated"); + } + #endregion Settings + + #region Mapping/{guid} + private ResponseMessage MappingGet(RequestMessage requestMessage) + { + Guid guid = ParseGuidFromRequestMessage(requestMessage); + var mapping = Mappings.FirstOrDefault(m => !m.IsAdminInterface && m.Guid == guid); - #region Mapping/{guid} - private ResponseMessage MappingGet(RequestMessage requestMessage) + if (mapping == null) { - Guid guid = ParseGuidFromRequestMessage(requestMessage); - var mapping = Mappings.FirstOrDefault(m => !m.IsAdminInterface && m.Guid == guid); + _settings.Logger.Warn("HttpStatusCode set to 404 : Mapping not found"); + return ResponseMessageBuilder.Create("Mapping not found", 404); + } - if (mapping == null) - { - _settings.Logger.Warn("HttpStatusCode set to 404 : Mapping not found"); - return ResponseMessageBuilder.Create("Mapping not found", 404); - } + var model = _mappingConverter.ToMappingModel(mapping); - var model = _mappingConverter.ToMappingModel(mapping); + return ToJson(model); + } - return ToJson(model); - } + private ResponseMessage MappingPut(RequestMessage requestMessage) + { + Guid guid = ParseGuidFromRequestMessage(requestMessage); - private ResponseMessage MappingPut(RequestMessage requestMessage) - { - Guid guid = ParseGuidFromRequestMessage(requestMessage); + var mappingModel = DeserializeObject(requestMessage); + Guid? guidFromPut = ConvertMappingAndRegisterAsRespondProvider(mappingModel, guid); - var mappingModel = DeserializeObject(requestMessage); - Guid? guidFromPut = ConvertMappingAndRegisterAsRespondProvider(mappingModel, guid); + return ResponseMessageBuilder.Create("Mapping added or updated", 200, guidFromPut); + } - return ResponseMessageBuilder.Create("Mapping added or updated", 200, guidFromPut); + private ResponseMessage MappingDelete(RequestMessage requestMessage) + { + Guid guid = ParseGuidFromRequestMessage(requestMessage); + + if (DeleteMapping(guid)) + { + return ResponseMessageBuilder.Create("Mapping removed", 200, guid); } - private ResponseMessage MappingDelete(RequestMessage requestMessage) + return ResponseMessageBuilder.Create("Mapping not found", 404); + } + + private Guid ParseGuidFromRequestMessage(RequestMessage requestMessage) + { + return Guid.Parse(requestMessage.Path.Substring(AdminMappings.Length + 1)); + } + #endregion Mapping/{guid} + + #region Mappings + private ResponseMessage MappingsSave(RequestMessage requestMessage) + { + SaveStaticMappings(); + + return ResponseMessageBuilder.Create("Mappings saved to disk"); + } + + private IEnumerable ToMappingModels() + { + return Mappings.Where(m => !m.IsAdminInterface).Select(_mappingConverter.ToMappingModel); + } + + private ResponseMessage MappingsGet(RequestMessage requestMessage) + { + return ToJson(ToMappingModels()); + } + + private ResponseMessage MappingsPost(RequestMessage requestMessage) + { + try { - Guid guid = ParseGuidFromRequestMessage(requestMessage); + var mappingModels = DeserializeRequestMessageToArray(requestMessage); + if (mappingModels.Length == 1) + { + Guid? guid = ConvertMappingAndRegisterAsRespondProvider(mappingModels[0]); + return ResponseMessageBuilder.Create("Mapping added", 201, guid); + } - if (DeleteMapping(guid)) + foreach (var mappingModel in mappingModels) { - return ResponseMessageBuilder.Create("Mapping removed", 200, guid); + ConvertMappingAndRegisterAsRespondProvider(mappingModel); } - return ResponseMessageBuilder.Create("Mapping not found", 404); + return ResponseMessageBuilder.Create("Mappings added", 201); } - - private Guid ParseGuidFromRequestMessage(RequestMessage requestMessage) + catch (ArgumentException a) { - return Guid.Parse(requestMessage.Path.Substring(AdminMappings.Length + 1)); + _settings.Logger.Error("HttpStatusCode set to 400 {0}", a); + return ResponseMessageBuilder.Create(a.Message, 400); } - #endregion Mapping/{guid} - - #region Mappings - private ResponseMessage MappingsSave(RequestMessage requestMessage) + catch (Exception e) { - SaveStaticMappings(); - - return ResponseMessageBuilder.Create("Mappings saved to disk"); + _settings.Logger.Error("HttpStatusCode set to 500 {0}", e); + return ResponseMessageBuilder.Create(e.ToString(), 500); } + } - private IEnumerable ToMappingModels() - { - return Mappings.Where(m => !m.IsAdminInterface).Select(_mappingConverter.ToMappingModel); - } + private Guid? ConvertMappingAndRegisterAsRespondProvider(MappingModel mappingModel, Guid? guid = null, string path = null) + { + Guard.NotNull(mappingModel, nameof(mappingModel)); + Guard.NotNull(mappingModel.Request, nameof(mappingModel.Request)); + Guard.NotNull(mappingModel.Response, nameof(mappingModel.Response)); - private ResponseMessage MappingsGet(RequestMessage requestMessage) + var requestBuilder = InitRequestBuilder(mappingModel.Request, true); + if (requestBuilder == null) { - return ToJson(ToMappingModels()); + return null; } - private ResponseMessage MappingsPost(RequestMessage requestMessage) - { - try - { - var mappingModels = DeserializeRequestMessageToArray(requestMessage); - if (mappingModels.Length == 1) - { - Guid? guid = ConvertMappingAndRegisterAsRespondProvider(mappingModels[0]); - return ResponseMessageBuilder.Create("Mapping added", 201, guid); - } + var responseBuilder = InitResponseBuilder(mappingModel.Response); - foreach (var mappingModel in mappingModels) - { - ConvertMappingAndRegisterAsRespondProvider(mappingModel); - } + var respondProvider = Given(requestBuilder, mappingModel.SaveToFile == true); - return ResponseMessageBuilder.Create("Mappings added", 201); - } - catch (ArgumentException a) - { - _settings.Logger.Error("HttpStatusCode set to 400 {0}", a); - return ResponseMessageBuilder.Create(a.Message, 400); - } - catch (Exception e) - { - _settings.Logger.Error("HttpStatusCode set to 500 {0}", e); - return ResponseMessageBuilder.Create(e.ToString(), 500); - } + if (guid != null) + { + respondProvider = respondProvider.WithGuid(guid.Value); } - - private Guid? ConvertMappingAndRegisterAsRespondProvider(MappingModel mappingModel, Guid? guid = null, string path = null) + else if (mappingModel.Guid != null && mappingModel.Guid != Guid.Empty) { - Guard.NotNull(mappingModel, nameof(mappingModel)); - Guard.NotNull(mappingModel.Request, nameof(mappingModel.Request)); - Guard.NotNull(mappingModel.Response, nameof(mappingModel.Response)); - - var requestBuilder = InitRequestBuilder(mappingModel.Request, true); - if (requestBuilder == null) - { - return null; - } + respondProvider = respondProvider.WithGuid(mappingModel.Guid.Value); + } - var responseBuilder = InitResponseBuilder(mappingModel.Response); + if (mappingModel.TimeSettings != null) + { + respondProvider = respondProvider.WithTimeSettings(TimeSettingsMapper.Map(mappingModel.TimeSettings)); + } - var respondProvider = Given(requestBuilder, mappingModel.SaveToFile == true); + if (path != null) + { + respondProvider = respondProvider.WithPath(path); + } - if (guid != null) - { - respondProvider = respondProvider.WithGuid(guid.Value); - } - else if (mappingModel.Guid != null && mappingModel.Guid != Guid.Empty) - { - respondProvider = respondProvider.WithGuid(mappingModel.Guid.Value); - } + if (!string.IsNullOrEmpty(mappingModel.Title)) + { + respondProvider = respondProvider.WithTitle(mappingModel.Title); + } - if (mappingModel.TimeSettings != null) - { - respondProvider = respondProvider.WithTimeSettings(TimeSettingsMapper.Map(mappingModel.TimeSettings)); - } + if (mappingModel.Priority != null) + { + respondProvider = respondProvider.AtPriority(mappingModel.Priority.Value); + } - if (path != null) - { - respondProvider = respondProvider.WithPath(path); - } + if (mappingModel.Scenario != null) + { + respondProvider = respondProvider.InScenario(mappingModel.Scenario); + respondProvider = respondProvider.WhenStateIs(mappingModel.WhenStateIs); + respondProvider = respondProvider.WillSetStateTo(mappingModel.SetStateTo); + } - if (!string.IsNullOrEmpty(mappingModel.Title)) - { - respondProvider = respondProvider.WithTitle(mappingModel.Title); - } + if (mappingModel.Webhook != null) + { + respondProvider = respondProvider.WithWebhook(WebhookMapper.Map(mappingModel.Webhook)); + } + else if (mappingModel.Webhooks?.Length > 1) + { + var webhooks = mappingModel.Webhooks.Select(WebhookMapper.Map).ToArray(); + respondProvider = respondProvider.WithWebhook(webhooks); + } - if (mappingModel.Priority != null) - { - respondProvider = respondProvider.AtPriority(mappingModel.Priority.Value); - } + respondProvider.RespondWith(responseBuilder); - if (mappingModel.Scenario != null) - { - respondProvider = respondProvider.InScenario(mappingModel.Scenario); - respondProvider = respondProvider.WhenStateIs(mappingModel.WhenStateIs); - respondProvider = respondProvider.WillSetStateTo(mappingModel.SetStateTo); - } + return respondProvider.Guid; + } - if (mappingModel.Webhook != null) + private ResponseMessage MappingsDelete(RequestMessage requestMessage) + { + if (!string.IsNullOrEmpty(requestMessage.Body)) + { + var deletedGuids = MappingsDeleteMappingFromBody(requestMessage); + if (deletedGuids != null) { - respondProvider = respondProvider.WithWebhook(WebhookMapper.Map(mappingModel.Webhook)); + return ResponseMessageBuilder.Create($"Mappings deleted. Affected GUIDs: [{string.Join(", ", deletedGuids.ToArray())}]"); } - else if (mappingModel.Webhooks?.Length > 1) + else { - var webhooks = mappingModel.Webhooks.Select(WebhookMapper.Map).ToArray(); - respondProvider = respondProvider.WithWebhook(webhooks); + // return bad request + return ResponseMessageBuilder.Create("Poorly formed mapping JSON.", 400); } - - respondProvider.RespondWith(responseBuilder); - - return respondProvider.Guid; } - - private ResponseMessage MappingsDelete(RequestMessage requestMessage) + else { - if (!string.IsNullOrEmpty(requestMessage.Body)) - { - var deletedGuids = MappingsDeleteMappingFromBody(requestMessage); - if (deletedGuids != null) - { - return ResponseMessageBuilder.Create($"Mappings deleted. Affected GUIDs: [{string.Join(", ", deletedGuids.ToArray())}]"); - } - else - { - // return bad request - return ResponseMessageBuilder.Create("Poorly formed mapping JSON.", 400); - } - } - else - { - ResetMappings(); + ResetMappings(); - ResetScenarios(); + ResetScenarios(); - return ResponseMessageBuilder.Create("Mappings deleted"); - } + return ResponseMessageBuilder.Create("Mappings deleted"); } + } - private IEnumerable MappingsDeleteMappingFromBody(RequestMessage requestMessage) - { - var deletedGuids = new List(); + private IEnumerable MappingsDeleteMappingFromBody(RequestMessage requestMessage) + { + var deletedGuids = new List(); - try + try + { + var mappingModels = DeserializeRequestMessageToArray(requestMessage); + foreach (var mappingModel in mappingModels) { - var mappingModels = DeserializeRequestMessageToArray(requestMessage); - foreach (var mappingModel in mappingModels) + if (mappingModel.Guid.HasValue) { - if (mappingModel.Guid.HasValue) + if (DeleteMapping(mappingModel.Guid.Value)) + { + deletedGuids.Add(mappingModel.Guid.Value); + } + else { - if (DeleteMapping(mappingModel.Guid.Value)) - { - deletedGuids.Add(mappingModel.Guid.Value); - } - else - { - _settings.Logger.Debug($"Did not find/delete mapping with GUID: {mappingModel.Guid.Value}."); - } + _settings.Logger.Debug($"Did not find/delete mapping with GUID: {mappingModel.Guid.Value}."); } } } - catch (ArgumentException a) - { - _settings.Logger.Error("ArgumentException: {0}", a); - return null; - } - catch (Exception e) - { - _settings.Logger.Error("Exception: {0}", e); - return null; - } - - return deletedGuids; } - - private ResponseMessage MappingsReset(RequestMessage requestMessage) + catch (ArgumentException a) { - ResetMappings(); + _settings.Logger.Error("ArgumentException: {0}", a); + return null; + } + catch (Exception e) + { + _settings.Logger.Error("Exception: {0}", e); + return null; + } - ResetScenarios(); + return deletedGuids; + } - string message = "Mappings reset"; - if (requestMessage.Query.ContainsKey(QueryParamReloadStaticMappings) && - bool.TryParse(requestMessage.Query[QueryParamReloadStaticMappings].ToString(), out bool reloadStaticMappings) - && reloadStaticMappings) - { - ReadStaticMappings(); - message = $"{message} and static mappings reloaded"; - } + private ResponseMessage MappingsReset(RequestMessage requestMessage) + { + ResetMappings(); - return ResponseMessageBuilder.Create(message); - } - #endregion Mappings + ResetScenarios(); - #region Request/{guid} - private ResponseMessage RequestGet(RequestMessage requestMessage) + string message = "Mappings reset"; + if (requestMessage.Query.ContainsKey(QueryParamReloadStaticMappings) && + bool.TryParse(requestMessage.Query[QueryParamReloadStaticMappings].ToString(), out bool reloadStaticMappings) + && reloadStaticMappings) { - Guid guid = ParseGuidFromRequestMessage(requestMessage); - var entry = LogEntries.FirstOrDefault(r => !r.RequestMessage.Path.StartsWith("/__admin/") && r.Guid == guid); + ReadStaticMappings(); + message = $"{message} and static mappings reloaded"; + } - if (entry == null) - { - _settings.Logger.Warn("HttpStatusCode set to 404 : Request not found"); - return ResponseMessageBuilder.Create("Request not found", 404); - } + return ResponseMessageBuilder.Create(message); + } + #endregion Mappings - var model = LogEntryMapper.Map(entry); + #region Request/{guid} + private ResponseMessage RequestGet(RequestMessage requestMessage) + { + Guid guid = ParseGuidFromRequestMessage(requestMessage); + var entry = LogEntries.FirstOrDefault(r => !r.RequestMessage.Path.StartsWith("/__admin/") && r.Guid == guid); - return ToJson(model); + if (entry == null) + { + _settings.Logger.Warn("HttpStatusCode set to 404 : Request not found"); + return ResponseMessageBuilder.Create("Request not found", 404); } - private ResponseMessage RequestDelete(RequestMessage requestMessage) - { - Guid guid = ParseGuidFromRequestMessage(requestMessage); + var model = LogEntryMapper.Map(entry); - if (DeleteLogEntry(guid)) - { - return ResponseMessageBuilder.Create("Request removed"); - } + return ToJson(model); + } - return ResponseMessageBuilder.Create("Request not found", 404); - } - #endregion Request/{guid} + private ResponseMessage RequestDelete(RequestMessage requestMessage) + { + Guid guid = ParseGuidFromRequestMessage(requestMessage); - #region Requests - private ResponseMessage RequestsGet(RequestMessage requestMessage) + if (DeleteLogEntry(guid)) { - var result = LogEntries - .Where(r => !r.RequestMessage.Path.StartsWith("/__admin/")) - .Select(LogEntryMapper.Map); - - return ToJson(result); + return ResponseMessageBuilder.Create("Request removed"); } - private ResponseMessage RequestsDelete(RequestMessage requestMessage) - { - ResetLogEntries(); + return ResponseMessageBuilder.Create("Request not found", 404); + } + #endregion Request/{guid} - return ResponseMessageBuilder.Create("Requests deleted"); - } - #endregion Requests + #region Requests + private ResponseMessage RequestsGet(RequestMessage requestMessage) + { + var result = LogEntries + .Where(r => !r.RequestMessage.Path.StartsWith("/__admin/")) + .Select(LogEntryMapper.Map); - #region Requests/find - private ResponseMessage RequestsFind(RequestMessage requestMessage) - { - var requestModel = DeserializeObject(requestMessage); + return ToJson(result); + } - var request = (Request)InitRequestBuilder(requestModel, false); + private ResponseMessage RequestsDelete(RequestMessage requestMessage) + { + ResetLogEntries(); - var dict = new Dictionary(); - foreach (var logEntry in LogEntries.Where(le => !le.RequestMessage.Path.StartsWith("/__admin/"))) - { - var requestMatchResult = new RequestMatchResult(); - if (request.GetMatchingScore(logEntry.RequestMessage, requestMatchResult) > MatchScores.AlmostPerfect) - { - dict.Add(logEntry, requestMatchResult); - } - } + return ResponseMessageBuilder.Create("Requests deleted"); + } + #endregion Requests - var result = dict.OrderBy(x => x.Value.AverageTotalScore).Select(x => x.Key).Select(LogEntryMapper.Map); + #region Requests/find + private ResponseMessage RequestsFind(RequestMessage requestMessage) + { + var requestModel = DeserializeObject(requestMessage); - return ToJson(result); - } - #endregion Requests/find + var request = (Request)InitRequestBuilder(requestModel, false); - #region Scenarios - private ResponseMessage ScenariosGet(RequestMessage requestMessage) + var dict = new Dictionary(); + foreach (var logEntry in LogEntries.Where(le => !le.RequestMessage.Path.StartsWith("/__admin/"))) { - var scenariosStates = Scenarios.Values.Select(s => new ScenarioStateModel + var requestMatchResult = new RequestMatchResult(); + if (request.GetMatchingScore(logEntry.RequestMessage, requestMatchResult) > MatchScores.AlmostPerfect) { - Name = s.Name, - NextState = s.NextState, - Started = s.Started, - Finished = s.Finished, - Counter = s.Counter - }); - - return ToJson(scenariosStates, true); + dict.Add(logEntry, requestMatchResult); + } } - private ResponseMessage ScenariosReset(RequestMessage requestMessage) - { - ResetScenarios(); + var result = dict.OrderBy(x => x.Value.AverageTotalScore).Select(x => x.Key).Select(LogEntryMapper.Map); - return ResponseMessageBuilder.Create("Scenarios reset"); - } - #endregion + return ToJson(result); + } + #endregion Requests/find - private IRequestBuilder InitRequestBuilder(RequestModel requestModel, bool pathOrUrlRequired) + #region Scenarios + private ResponseMessage ScenariosGet(RequestMessage requestMessage) + { + var scenariosStates = Scenarios.Values.Select(s => new ScenarioStateModel { - IRequestBuilder requestBuilder = Request.Create(); + Name = s.Name, + NextState = s.NextState, + Started = s.Started, + Finished = s.Finished, + Counter = s.Counter + }); - if (requestModel.ClientIP != null) - { - if (requestModel.ClientIP is string clientIP) - { - requestBuilder = requestBuilder.WithClientIP(clientIP); - } - else - { - var clientIPModel = JsonUtils.ParseJTokenToObject(requestModel.ClientIP); - if (clientIPModel?.Matchers != null) - { - requestBuilder = requestBuilder.WithPath(clientIPModel.Matchers.Select(_matcherMapper.Map).OfType().ToArray()); - } - } - } + return ToJson(scenariosStates, true); + } - bool pathOrUrlMatchersValid = false; - if (requestModel.Path != null) - { - if (requestModel.Path is string path) - { - requestBuilder = requestBuilder.WithPath(path); - pathOrUrlMatchersValid = true; - } - else - { - var pathModel = JsonUtils.ParseJTokenToObject(requestModel.Path); - if (pathModel?.Matchers != null) - { - requestBuilder = requestBuilder.WithPath(pathModel.Matchers.Select(_matcherMapper.Map).OfType().ToArray()); - pathOrUrlMatchersValid = true; - } - } - } - else if (requestModel.Url != null) - { - if (requestModel.Url is string url) - { - requestBuilder = requestBuilder.WithUrl(url); - pathOrUrlMatchersValid = true; - } - else - { - var urlModel = JsonUtils.ParseJTokenToObject(requestModel.Url); - if (urlModel?.Matchers != null) - { - requestBuilder = requestBuilder.WithUrl(urlModel.Matchers.Select(_matcherMapper.Map).OfType().ToArray()); - pathOrUrlMatchersValid = true; - } - } - } + private ResponseMessage ScenariosReset(RequestMessage requestMessage) + { + ResetScenarios(); - if (pathOrUrlRequired && !pathOrUrlMatchersValid) - { - _settings.Logger.Error("Path or Url matcher is missing for this mapping, this mapping will not be added."); - return null; - } + return ResponseMessageBuilder.Create("Scenarios reset"); + } + #endregion + + private IRequestBuilder InitRequestBuilder(RequestModel requestModel, bool pathOrUrlRequired) + { + IRequestBuilder requestBuilder = Request.Create(); - if (requestModel.Methods != null) + if (requestModel.ClientIP != null) + { + if (requestModel.ClientIP is string clientIP) { - requestBuilder = requestBuilder.UsingMethod(requestModel.Methods); + requestBuilder = requestBuilder.WithClientIP(clientIP); } - - if (requestModel.Headers != null) + else { - foreach (var headerModel in requestModel.Headers.Where(h => h.Matchers != null)) + var clientIPModel = JsonUtils.ParseJTokenToObject(requestModel.ClientIP); + if (clientIPModel?.Matchers != null) { - requestBuilder = requestBuilder.WithHeader( - headerModel.Name, - headerModel.IgnoreCase == true, - headerModel.RejectOnMatch == true ? MatchBehaviour.RejectOnMatch : MatchBehaviour.AcceptOnMatch, - headerModel.Matchers.Select(_matcherMapper.Map).OfType().ToArray() - ); + requestBuilder = requestBuilder.WithPath(clientIPModel.Matchers.Select(_matcherMapper.Map).OfType().ToArray()); } } + } - if (requestModel.Cookies != null) + bool pathOrUrlMatchersValid = false; + if (requestModel.Path != null) + { + if (requestModel.Path is string path) { - foreach (var cookieModel in requestModel.Cookies.Where(c => c.Matchers != null)) - { - requestBuilder = requestBuilder.WithCookie( - cookieModel.Name, - cookieModel.IgnoreCase == true, - cookieModel.RejectOnMatch == true ? MatchBehaviour.RejectOnMatch : MatchBehaviour.AcceptOnMatch, - cookieModel.Matchers.Select(_matcherMapper.Map).OfType().ToArray()); - } + requestBuilder = requestBuilder.WithPath(path); + pathOrUrlMatchersValid = true; } - - if (requestModel.Params != null) + else { - foreach (var paramModel in requestModel.Params.Where(p => p != null && p.Matchers != null)) + var pathModel = JsonUtils.ParseJTokenToObject(requestModel.Path); + if (pathModel?.Matchers != null) { - bool ignoreCase = paramModel.IgnoreCase == true; - requestBuilder = requestBuilder.WithParam(paramModel.Name, ignoreCase, paramModel.Matchers.Select(_matcherMapper.Map).OfType().ToArray()); + requestBuilder = requestBuilder.WithPath(pathModel.Matchers.Select(_matcherMapper.Map).OfType().ToArray()); + pathOrUrlMatchersValid = true; } } - - if (requestModel.Body?.Matcher != null) + } + else if (requestModel.Url != null) + { + if (requestModel.Url is string url) { - requestBuilder = requestBuilder.WithBody(_matcherMapper.Map(requestModel.Body.Matcher)); + requestBuilder = requestBuilder.WithUrl(url); + pathOrUrlMatchersValid = true; } - else if (requestModel.Body?.Matchers != null) + else { - requestBuilder = requestBuilder.WithBody(_matcherMapper.Map(requestModel.Body.Matchers)); + var urlModel = JsonUtils.ParseJTokenToObject(requestModel.Url); + if (urlModel?.Matchers != null) + { + requestBuilder = requestBuilder.WithUrl(urlModel.Matchers.Select(_matcherMapper.Map).OfType().ToArray()); + pathOrUrlMatchersValid = true; + } } + } - return requestBuilder; + if (pathOrUrlRequired && !pathOrUrlMatchersValid) + { + _settings.Logger.Error("Path or Url matcher is missing for this mapping, this mapping will not be added."); + return null; } - private IResponseBuilder InitResponseBuilder(ResponseModel responseModel) + if (requestModel.Methods != null) { - IResponseBuilder responseBuilder = Response.Create(); + requestBuilder = requestBuilder.UsingMethod(requestModel.Methods); + } - if (responseModel.Delay > 0) + if (requestModel.Headers != null) + { + foreach (var headerModel in requestModel.Headers.Where(h => h.Matchers != null)) { - responseBuilder = responseBuilder.WithDelay(responseModel.Delay.Value); + requestBuilder = requestBuilder.WithHeader( + headerModel.Name, + headerModel.IgnoreCase == true, + headerModel.RejectOnMatch == true ? MatchBehaviour.RejectOnMatch : MatchBehaviour.AcceptOnMatch, + headerModel.Matchers.Select(_matcherMapper.Map).OfType().ToArray() + ); } - else if (responseModel.MinimumRandomDelay >= 0 || responseModel.MaximumRandomDelay > 0) + } + + if (requestModel.Cookies != null) + { + foreach (var cookieModel in requestModel.Cookies.Where(c => c.Matchers != null)) { - responseBuilder = responseBuilder.WithRandomDelay(responseModel.MinimumRandomDelay ?? 0, responseModel.MaximumRandomDelay ?? 60_000); + requestBuilder = requestBuilder.WithCookie( + cookieModel.Name, + cookieModel.IgnoreCase == true, + cookieModel.RejectOnMatch == true ? MatchBehaviour.RejectOnMatch : MatchBehaviour.AcceptOnMatch, + cookieModel.Matchers.Select(_matcherMapper.Map).OfType().ToArray()); } + } - if (responseModel.UseTransformer == true) + if (requestModel.Params != null) + { + foreach (var paramModel in requestModel.Params.Where(p => p != null && p.Matchers != null)) { - if (!Enum.TryParse(responseModel.TransformerType, out var transformerType)) - { - transformerType = TransformerType.Handlebars; - } - - if (!Enum.TryParse(responseModel.TransformerReplaceNodeOptions, out var option)) - { - option = ReplaceNodeOptions.None; - } - responseBuilder = responseBuilder.WithTransformer( - transformerType, - responseModel.UseTransformerForBodyAsFile == true, - option); + bool ignoreCase = paramModel.IgnoreCase == true; + requestBuilder = requestBuilder.WithParam(paramModel.Name, ignoreCase, paramModel.Matchers.Select(_matcherMapper.Map).OfType().ToArray()); } + } - if (!string.IsNullOrEmpty(responseModel.ProxyUrl)) - { - var proxyAndRecordSettings = new ProxyAndRecordSettings - { - Url = responseModel.ProxyUrl, - ClientX509Certificate2ThumbprintOrSubjectName = responseModel.X509Certificate2ThumbprintOrSubjectName, - WebProxySettings = responseModel.WebProxy != null ? new WebProxySettings - { - Address = responseModel.WebProxy.Address, - UserName = responseModel.WebProxy.UserName, - Password = responseModel.WebProxy.Password - } : null - }; + if (requestModel.Body?.Matcher != null) + { + requestBuilder = requestBuilder.WithBody(_matcherMapper.Map(requestModel.Body.Matcher)); + } + else if (requestModel.Body?.Matchers != null) + { + requestBuilder = requestBuilder.WithBody(_matcherMapper.Map(requestModel.Body.Matchers)); + } - return responseBuilder.WithProxy(proxyAndRecordSettings); - } + return requestBuilder; + } - if (responseModel.StatusCode is string statusCodeAsString) - { - responseBuilder = responseBuilder.WithStatusCode(statusCodeAsString); - } - else if (responseModel.StatusCode != null) - { - // Convert to Int32 because Newtonsoft deserializes an 'object' with a number value to a long. - responseBuilder = responseBuilder.WithStatusCode(Convert.ToInt32(responseModel.StatusCode)); - } + private IResponseBuilder InitResponseBuilder(ResponseModel responseModel) + { + IResponseBuilder responseBuilder = Response.Create(); - if (responseModel.Headers != null) - { - foreach (var entry in responseModel.Headers) - { - responseBuilder = entry.Value is string value ? - responseBuilder.WithHeader(entry.Key, value) : - responseBuilder.WithHeader(entry.Key, JsonUtils.ParseJTokenToObject(entry.Value)); - } - } - else if (responseModel.HeadersRaw != null) - { - foreach (string headerLine in responseModel.HeadersRaw.Split(new[] { "\n", "\r\n" }, StringSplitOptions.RemoveEmptyEntries)) - { - int indexColon = headerLine.IndexOf(":", StringComparison.Ordinal); - string key = headerLine.Substring(0, indexColon).TrimStart(' ', '\t'); - string value = headerLine.Substring(indexColon + 1).TrimStart(' ', '\t'); - responseBuilder = responseBuilder.WithHeader(key, value); - } - } + if (responseModel.Delay > 0) + { + responseBuilder = responseBuilder.WithDelay(responseModel.Delay.Value); + } + else if (responseModel.MinimumRandomDelay >= 0 || responseModel.MaximumRandomDelay > 0) + { + responseBuilder = responseBuilder.WithRandomDelay(responseModel.MinimumRandomDelay ?? 0, responseModel.MaximumRandomDelay ?? 60_000); + } - if (responseModel.BodyAsBytes != null) - { - responseBuilder = responseBuilder.WithBody(responseModel.BodyAsBytes, responseModel.BodyDestination, ToEncoding(responseModel.BodyEncoding)); - } - else if (responseModel.Body != null) - { - responseBuilder = responseBuilder.WithBody(responseModel.Body, responseModel.BodyDestination, ToEncoding(responseModel.BodyEncoding)); - } - else if (responseModel.BodyAsJson != null) - { - responseBuilder = responseBuilder.WithBodyAsJson(responseModel.BodyAsJson, ToEncoding(responseModel.BodyEncoding), responseModel.BodyAsJsonIndented == true); - } - else if (responseModel.BodyAsFile != null) + if (responseModel.UseTransformer == true) + { + if (!Enum.TryParse(responseModel.TransformerType, out var transformerType)) { - responseBuilder = responseBuilder.WithBodyFromFile(responseModel.BodyAsFile, responseModel.BodyAsFileIsCached == true); + transformerType = TransformerType.Handlebars; } - if (responseModel.Fault != null && Enum.TryParse(responseModel.Fault.Type, out FaultType faultType)) + if (!Enum.TryParse(responseModel.TransformerReplaceNodeOptions, out var option)) { - responseBuilder.WithFault(faultType, responseModel.Fault.Percentage); + option = ReplaceNodeOptions.None; } - - return responseBuilder; + responseBuilder = responseBuilder.WithTransformer( + transformerType, + responseModel.UseTransformerForBodyAsFile == true, + option); } - private ResponseMessage ToJson(T result, bool keepNullValues = false) + if (!string.IsNullOrEmpty(responseModel.ProxyUrl)) { - return new ResponseMessage + var proxyAndRecordSettings = new ProxyAndRecordSettings { - BodyData = new BodyData + Url = responseModel.ProxyUrl, + ClientX509Certificate2ThumbprintOrSubjectName = responseModel.X509Certificate2ThumbprintOrSubjectName, + WebProxySettings = responseModel.WebProxy != null ? new WebProxySettings { - DetectedBodyType = BodyType.String, - BodyAsString = JsonConvert.SerializeObject(result, keepNullValues ? JsonSerializationConstants.JsonSerializerSettingsIncludeNullValues : JsonSerializationConstants.JsonSerializerSettingsDefault) - }, - StatusCode = (int)HttpStatusCode.OK, - Headers = new Dictionary> { { HttpKnownHeaderNames.ContentType, new WireMockList(ContentTypeJson) } } + Address = responseModel.WebProxy.Address, + UserName = responseModel.WebProxy.UserName, + Password = responseModel.WebProxy.Password + } : null }; + + return responseBuilder.WithProxy(proxyAndRecordSettings); } - private Encoding ToEncoding(EncodingModel encodingModel) + if (responseModel.StatusCode is string statusCodeAsString) + { + responseBuilder = responseBuilder.WithStatusCode(statusCodeAsString); + } + else if (responseModel.StatusCode != null) { - return encodingModel != null ? Encoding.GetEncoding(encodingModel.CodePage) : null; + // Convert to Int32 because Newtonsoft deserializes an 'object' with a number value to a long. + responseBuilder = responseBuilder.WithStatusCode(Convert.ToInt32(responseModel.StatusCode)); } - private T DeserializeObject(RequestMessage requestMessage) + if (responseModel.Headers != null) { - if (requestMessage?.BodyData?.DetectedBodyType == BodyType.String) + foreach (var entry in responseModel.Headers) { - return JsonUtils.DeserializeObject(requestMessage.BodyData.BodyAsString); + responseBuilder = entry.Value is string value ? + responseBuilder.WithHeader(entry.Key, value) : + responseBuilder.WithHeader(entry.Key, JsonUtils.ParseJTokenToObject(entry.Value)); } - - if (requestMessage?.BodyData?.DetectedBodyType == BodyType.Json) + } + else if (responseModel.HeadersRaw != null) + { + foreach (string headerLine in responseModel.HeadersRaw.Split(new[] { "\n", "\r\n" }, StringSplitOptions.RemoveEmptyEntries)) { - return ((JObject)requestMessage.BodyData.BodyAsJson).ToObject(); + int indexColon = headerLine.IndexOf(":", StringComparison.Ordinal); + string key = headerLine.Substring(0, indexColon).TrimStart(' ', '\t'); + string value = headerLine.Substring(indexColon + 1).TrimStart(' ', '\t'); + responseBuilder = responseBuilder.WithHeader(key, value); } + } + + if (responseModel.BodyAsBytes != null) + { + responseBuilder = responseBuilder.WithBody(responseModel.BodyAsBytes, responseModel.BodyDestination, ToEncoding(responseModel.BodyEncoding)); + } + else if (responseModel.Body != null) + { + responseBuilder = responseBuilder.WithBody(responseModel.Body, responseModel.BodyDestination, ToEncoding(responseModel.BodyEncoding)); + } + else if (responseModel.BodyAsJson != null) + { + responseBuilder = responseBuilder.WithBodyAsJson(responseModel.BodyAsJson, ToEncoding(responseModel.BodyEncoding), responseModel.BodyAsJsonIndented == true); + } + else if (responseModel.BodyAsFile != null) + { + responseBuilder = responseBuilder.WithBodyFromFile(responseModel.BodyAsFile, responseModel.BodyAsFileIsCached == true); + } - return default(T); + if (responseModel.Fault != null && Enum.TryParse(responseModel.Fault.Type, out FaultType faultType)) + { + responseBuilder.WithFault(faultType, responseModel.Fault.Percentage); } - private T[] DeserializeRequestMessageToArray(RequestMessage requestMessage) + return responseBuilder; + } + + private ResponseMessage ToJson(T result, bool keepNullValues = false) + { + return new ResponseMessage { - if (requestMessage?.BodyData?.DetectedBodyType == BodyType.Json) + BodyData = new BodyData { - var bodyAsJson = requestMessage.BodyData.BodyAsJson; + DetectedBodyType = BodyType.String, + BodyAsString = JsonConvert.SerializeObject(result, keepNullValues ? JsonSerializationConstants.JsonSerializerSettingsIncludeNullValues : JsonSerializationConstants.JsonSerializerSettingsDefault) + }, + StatusCode = (int)HttpStatusCode.OK, + Headers = new Dictionary> { { HttpKnownHeaderNames.ContentType, new WireMockList(ContentTypeJson) } } + }; + } - return DeserializeObjectToArray(bodyAsJson); - } + private Encoding ToEncoding(EncodingModel encodingModel) + { + return encodingModel != null ? Encoding.GetEncoding(encodingModel.CodePage) : null; + } + + private T DeserializeObject(RequestMessage requestMessage) + { + if (requestMessage?.BodyData?.DetectedBodyType == BodyType.String) + { + return JsonUtils.DeserializeObject(requestMessage.BodyData.BodyAsString); + } - return default(T[]); + if (requestMessage?.BodyData?.DetectedBodyType == BodyType.Json) + { + return ((JObject)requestMessage.BodyData.BodyAsJson).ToObject(); } - private T[] DeserializeObjectToArray(object value) + return default(T); + } + + private T[] DeserializeRequestMessageToArray(RequestMessage requestMessage) + { + if (requestMessage?.BodyData?.DetectedBodyType == BodyType.Json) { - if (value is JArray jArray) - { - return jArray.ToObject(); - } + var bodyAsJson = requestMessage.BodyData.BodyAsJson; - var singleResult = ((JObject)value).ToObject(); - return new[] { singleResult }; + return DeserializeObjectToArray(bodyAsJson); } - private T[] DeserializeJsonToArray(string value) + return default(T[]); + } + + private T[] DeserializeObjectToArray(object value) + { + if (value is JArray jArray) { - return DeserializeObjectToArray(JsonUtils.DeserializeObject(value)); + return jArray.ToObject(); } - private void DisposeEnhancedFileSystemWatcher() + var singleResult = ((JObject)value).ToObject(); + return new[] { singleResult }; + } + + private T[] DeserializeJsonToArray(string value) + { + return DeserializeObjectToArray(JsonUtils.DeserializeObject(value)); + } + + private void DisposeEnhancedFileSystemWatcher() + { + if (_enhancedFileSystemWatcher != null) { - if (_enhancedFileSystemWatcher != null) - { - _enhancedFileSystemWatcher.EnableRaisingEvents = false; + _enhancedFileSystemWatcher.EnableRaisingEvents = false; - _enhancedFileSystemWatcher.Created -= EnhancedFileSystemWatcherCreated; - _enhancedFileSystemWatcher.Changed -= EnhancedFileSystemWatcherChanged; - _enhancedFileSystemWatcher.Deleted -= EnhancedFileSystemWatcherDeleted; + _enhancedFileSystemWatcher.Created -= EnhancedFileSystemWatcherCreated; + _enhancedFileSystemWatcher.Changed -= EnhancedFileSystemWatcherChanged; + _enhancedFileSystemWatcher.Deleted -= EnhancedFileSystemWatcherDeleted; - _enhancedFileSystemWatcher.Dispose(); - } + _enhancedFileSystemWatcher.Dispose(); } + } - private void EnhancedFileSystemWatcherCreated(object sender, FileSystemEventArgs args) + private void EnhancedFileSystemWatcherCreated(object sender, FileSystemEventArgs args) + { + _settings.Logger.Info("MappingFile created : '{0}', reading file.", args.FullPath); + if (!ReadStaticMappingAndAddOrUpdate(args.FullPath)) { - _settings.Logger.Info("MappingFile created : '{0}', reading file.", args.FullPath); - if (!ReadStaticMappingAndAddOrUpdate(args.FullPath)) - { - _settings.Logger.Error("Unable to read MappingFile '{0}'.", args.FullPath); - } + _settings.Logger.Error("Unable to read MappingFile '{0}'.", args.FullPath); } + } - private void EnhancedFileSystemWatcherChanged(object sender, FileSystemEventArgs args) + private void EnhancedFileSystemWatcherChanged(object sender, FileSystemEventArgs args) + { + _settings.Logger.Info("MappingFile updated : '{0}', reading file.", args.FullPath); + if (!ReadStaticMappingAndAddOrUpdate(args.FullPath)) { - _settings.Logger.Info("MappingFile updated : '{0}', reading file.", args.FullPath); - if (!ReadStaticMappingAndAddOrUpdate(args.FullPath)) - { - _settings.Logger.Error("Unable to read MappingFile '{0}'.", args.FullPath); - } + _settings.Logger.Error("Unable to read MappingFile '{0}'.", args.FullPath); } + } - private void EnhancedFileSystemWatcherDeleted(object sender, FileSystemEventArgs args) - { - _settings.Logger.Info("MappingFile deleted : '{0}'", args.FullPath); - string filenameWithoutExtension = Path.GetFileNameWithoutExtension(args.FullPath); + private void EnhancedFileSystemWatcherDeleted(object sender, FileSystemEventArgs args) + { + _settings.Logger.Info("MappingFile deleted : '{0}'", args.FullPath); + string filenameWithoutExtension = Path.GetFileNameWithoutExtension(args.FullPath); - if (Guid.TryParse(filenameWithoutExtension, out Guid guidFromFilename)) - { - DeleteMapping(guidFromFilename); - } - else - { - DeleteMapping(args.FullPath); - } + if (Guid.TryParse(filenameWithoutExtension, out Guid guidFromFilename)) + { + DeleteMapping(guidFromFilename); + } + else + { + DeleteMapping(args.FullPath); } } } \ No newline at end of file diff --git a/src/WireMock.Net/Server/WireMockServer.cs b/src/WireMock.Net/Server/WireMockServer.cs index 2bd3dc735..d586f6853 100644 --- a/src/WireMock.Net/Server/WireMockServer.cs +++ b/src/WireMock.Net/Server/WireMockServer.cs @@ -20,511 +20,512 @@ using WireMock.Serialization; using WireMock.Settings; -namespace WireMock.Server +namespace WireMock.Server; + +/// +/// The fluent mock server. +/// +public partial class WireMockServer : IWireMockServer { + private const int ServerStartDelayInMs = 100; + + private readonly WireMockServerSettings _settings; + private readonly IOwinSelfHost _httpServer; + private readonly IWireMockMiddlewareOptions _options = new WireMockMiddlewareOptions(); + private readonly MappingConverter _mappingConverter; + private readonly MatcherMapper _matcherMapper; + private readonly MappingToFileSaver _mappingToFileSaver; + + /// + [PublicAPI] + public bool IsStarted => _httpServer != null && _httpServer.IsStarted; + + /// + [PublicAPI] + public List Ports { get; } + + /// + [PublicAPI] + public int Port => Ports?.FirstOrDefault() ?? default(int); + + /// + [PublicAPI] + public string[] Urls { get; } + + /// + [PublicAPI] + public string Url => Urls?.FirstOrDefault(); + + /// + /// Gets the mappings. + /// + [PublicAPI] + public IEnumerable Mappings => _options.Mappings.Values.ToArray(); + + /// + [PublicAPI] + public IEnumerable MappingModels => ToMappingModels(); + /// - /// The fluent mock server. + /// Gets the scenarios. /// - public partial class WireMockServer : IWireMockServer + [PublicAPI] + public ConcurrentDictionary Scenarios => new ConcurrentDictionary(_options.Scenarios); + + #region IDisposable Members + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() { - private const int ServerStartDelayInMs = 100; - - private readonly WireMockServerSettings _settings; - private readonly IOwinSelfHost _httpServer; - private readonly IWireMockMiddlewareOptions _options = new WireMockMiddlewareOptions(); - private readonly MappingConverter _mappingConverter; - private readonly MatcherMapper _matcherMapper; - private readonly MappingToFileSaver _mappingToFileSaver; - - /// - [PublicAPI] - public bool IsStarted => _httpServer != null && _httpServer.IsStarted; - - /// - [PublicAPI] - public List Ports { get; } - - /// - [PublicAPI] - public int Port => Ports?.FirstOrDefault() ?? default(int); - - /// - [PublicAPI] - public string[] Urls { get; } - - /// - [PublicAPI] - public string Url => Urls?.FirstOrDefault(); - - /// - /// Gets the mappings. - /// - [PublicAPI] - public IEnumerable Mappings => _options.Mappings.Values.ToArray(); - - /// - [PublicAPI] - public IEnumerable MappingModels => ToMappingModels(); - - /// - /// Gets the scenarios. - /// - [PublicAPI] - public ConcurrentDictionary Scenarios => new ConcurrentDictionary(_options.Scenarios); - - #region IDisposable Members - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } + Dispose(true); + GC.SuppressFinalize(this); + } - /// - /// Releases unmanaged and - optionally - managed resources. - /// - /// true to release both managed and unmanaged resources; false to release only unmanaged resources. - protected virtual void Dispose(bool disposing) - { - DisposeEnhancedFileSystemWatcher(); - _httpServer?.StopAsync(); - } - #endregion - - #region Start/Stop - /// - /// Starts this WireMockServer with the specified settings. - /// - /// The WireMockServerSettings. - /// The . - [PublicAPI] - public static WireMockServer Start([NotNull] WireMockServerSettings settings) - { - Guard.NotNull(settings, nameof(settings)); + /// + /// Releases unmanaged and - optionally - managed resources. + /// + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + protected virtual void Dispose(bool disposing) + { + DisposeEnhancedFileSystemWatcher(); + _httpServer?.StopAsync(); + } + #endregion - return new WireMockServer(settings); - } + #region Start/Stop + /// + /// Starts this WireMockServer with the specified settings. + /// + /// The WireMockServerSettings. + /// The . + [PublicAPI] + public static WireMockServer Start([NotNull] WireMockServerSettings settings) + { + Guard.NotNull(settings, nameof(settings)); - /// - /// Start this WireMockServer. - /// - /// The port. - /// The SSL support. - /// The . - [PublicAPI] - public static WireMockServer Start([CanBeNull] int? port = 0, bool ssl = false) - { - return new WireMockServer(new WireMockServerSettings - { - Port = port, - UseSSL = ssl - }); - } + return new WireMockServer(settings); + } - /// - /// Start this WireMockServer. - /// - /// The urls to listen on. - /// The . - [PublicAPI] - public static WireMockServer Start(params string[] urls) + /// + /// Start this WireMockServer. + /// + /// The port. + /// The SSL support. + /// The . + [PublicAPI] + public static WireMockServer Start([CanBeNull] int? port = 0, bool ssl = false) + { + return new WireMockServer(new WireMockServerSettings { - Guard.NotNullOrEmpty(urls, nameof(urls)); + Port = port, + UseSSL = ssl + }); + } - return new WireMockServer(new WireMockServerSettings - { - Urls = urls - }); - } + /// + /// Start this WireMockServer. + /// + /// The urls to listen on. + /// The . + [PublicAPI] + public static WireMockServer Start(params string[] urls) + { + Guard.NotNullOrEmpty(urls, nameof(urls)); - /// - /// Start this WireMockServer with the admin interface. - /// - /// The port. - /// The SSL support. - /// The . - [PublicAPI] - public static WireMockServer StartWithAdminInterface(int? port = 0, bool ssl = false) + return new WireMockServer(new WireMockServerSettings { - return new WireMockServer(new WireMockServerSettings - { - Port = port, - UseSSL = ssl, - StartAdminInterface = true - }); - } + Urls = urls + }); + } - /// - /// Start this WireMockServer with the admin interface. - /// - /// The urls. - /// The . - [PublicAPI] - public static WireMockServer StartWithAdminInterface(params string[] urls) + /// + /// Start this WireMockServer with the admin interface. + /// + /// The port. + /// The SSL support. + /// The . + [PublicAPI] + public static WireMockServer StartWithAdminInterface(int? port = 0, bool ssl = false) + { + return new WireMockServer(new WireMockServerSettings { - Guard.NotNullOrEmpty(urls, nameof(urls)); + Port = port, + UseSSL = ssl, + StartAdminInterface = true + }); + } - return new WireMockServer(new WireMockServerSettings - { - Urls = urls, - StartAdminInterface = true - }); - } + /// + /// Start this WireMockServer with the admin interface. + /// + /// The urls. + /// The . + [PublicAPI] + public static WireMockServer StartWithAdminInterface(params string[] urls) + { + Guard.NotNullOrEmpty(urls, nameof(urls)); - /// - /// Start this WireMockServer with the admin interface and read static mappings. - /// - /// The urls. - /// The . - [PublicAPI] - public static WireMockServer StartWithAdminInterfaceAndReadStaticMappings(params string[] urls) + return new WireMockServer(new WireMockServerSettings { - Guard.NotNullOrEmpty(urls, nameof(urls)); + Urls = urls, + StartAdminInterface = true + }); + } - return new WireMockServer(new WireMockServerSettings - { - Urls = urls, - StartAdminInterface = true, - ReadStaticMappings = true - }); - } + /// + /// Start this WireMockServer with the admin interface and read static mappings. + /// + /// The urls. + /// The . + [PublicAPI] + public static WireMockServer StartWithAdminInterfaceAndReadStaticMappings(params string[] urls) + { + Guard.NotNullOrEmpty(urls, nameof(urls)); - /// - /// Initializes a new instance of the class. - /// - /// The settings. - /// - /// Service start failed with error: {_httpServer.RunningException.Message} - /// or - /// Service start failed with error: {startTask.Exception.Message} - /// - /// Service start timed out after {TimeSpan.FromMilliseconds(settings.StartTimeout)} - protected WireMockServer(WireMockServerSettings settings) + return new WireMockServer(new WireMockServerSettings { - _settings = settings; + Urls = urls, + StartAdminInterface = true, + ReadStaticMappings = true + }); + } - // Set default values if not provided - _settings.Logger = settings.Logger ?? new WireMockNullLogger(); - _settings.FileSystemHandler = settings.FileSystemHandler ?? new LocalFileSystemHandler(); + /// + /// Initializes a new instance of the class. + /// + /// The settings. + /// + /// Service start failed with error: {_httpServer.RunningException.Message} + /// or + /// Service start failed with error: {startTask.Exception.Message} + /// + /// Service start timed out after {TimeSpan.FromMilliseconds(settings.StartTimeout)} + protected WireMockServer(WireMockServerSettings settings) + { + _settings = settings; + + // Set default values if not provided + _settings.Logger = settings.Logger ?? new WireMockNullLogger(); + _settings.FileSystemHandler = settings.FileSystemHandler ?? new LocalFileSystemHandler(); - _settings.Logger.Info("By Stef Heyenrath (https://github.com/WireMock-Net/WireMock.Net)"); - _settings.Logger.Debug("Server settings {0}", JsonConvert.SerializeObject(settings, Formatting.Indented)); + _settings.Logger.Info("By Stef Heyenrath (https://github.com/WireMock-Net/WireMock.Net)"); + _settings.Logger.Debug("Server settings {0}", JsonConvert.SerializeObject(settings, Formatting.Indented)); - HostUrlOptions urlOptions; - if (settings.Urls != null) + HostUrlOptions urlOptions; + if (settings.Urls != null) + { + urlOptions = new HostUrlOptions { - urlOptions = new HostUrlOptions - { - Urls = settings.Urls - }; - } - else + Urls = settings.Urls + }; + } + else + { + urlOptions = new HostUrlOptions { - urlOptions = new HostUrlOptions - { - UseSSL = settings.UseSSL == true, - Port = settings.Port - }; - } + UseSSL = settings.UseSSL == true, + Port = settings.Port + }; + } - _options.FileSystemHandler = _settings.FileSystemHandler; - _options.PreWireMockMiddlewareInit = _settings.PreWireMockMiddlewareInit; - _options.PostWireMockMiddlewareInit = _settings.PostWireMockMiddlewareInit; - _options.Logger = _settings.Logger; - _options.DisableJsonBodyParsing = _settings.DisableJsonBodyParsing; - _options.HandleRequestsSynchronously = settings.HandleRequestsSynchronously; - _options.SaveUnmatchedRequests = settings.SaveUnmatchedRequests; + _options.FileSystemHandler = _settings.FileSystemHandler; + _options.PreWireMockMiddlewareInit = _settings.PreWireMockMiddlewareInit; + _options.PostWireMockMiddlewareInit = _settings.PostWireMockMiddlewareInit; + _options.Logger = _settings.Logger; + _options.DisableJsonBodyParsing = _settings.DisableJsonBodyParsing; + _options.HandleRequestsSynchronously = settings.HandleRequestsSynchronously; + _options.SaveUnmatchedRequests = settings.SaveUnmatchedRequests; - if (settings.CustomCertificateDefined) - { - _options.X509StoreName = settings.CertificateSettings.X509StoreName; - _options.X509StoreLocation = settings.CertificateSettings.X509StoreLocation; - _options.X509ThumbprintOrSubjectName = settings.CertificateSettings.X509StoreThumbprintOrSubjectName; - _options.X509CertificateFilePath = settings.CertificateSettings.X509CertificateFilePath; - _options.X509CertificatePassword = settings.CertificateSettings.X509CertificatePassword; - } + if (settings.CustomCertificateDefined) + { + _options.X509StoreName = settings.CertificateSettings.X509StoreName; + _options.X509StoreLocation = settings.CertificateSettings.X509StoreLocation; + _options.X509ThumbprintOrSubjectName = settings.CertificateSettings.X509StoreThumbprintOrSubjectName; + _options.X509CertificateFilePath = settings.CertificateSettings.X509CertificateFilePath; + _options.X509CertificatePassword = settings.CertificateSettings.X509CertificatePassword; + } - _matcherMapper = new MatcherMapper(_settings); - _mappingConverter = new MappingConverter(_matcherMapper); - _mappingToFileSaver = new MappingToFileSaver(_settings, _mappingConverter); + _matcherMapper = new MatcherMapper(_settings); + _mappingConverter = new MappingConverter(_matcherMapper); + _mappingToFileSaver = new MappingToFileSaver(_settings, _mappingConverter); #if USE_ASPNETCORE - _options.AdditionalServiceRegistration = _settings.AdditionalServiceRegistration; - _options.CorsPolicyOptions = _settings.CorsPolicyOptions; + _options.AdditionalServiceRegistration = _settings.AdditionalServiceRegistration; + _options.CorsPolicyOptions = _settings.CorsPolicyOptions; - _httpServer = new AspNetCoreSelfHost(_options, urlOptions); + _httpServer = new AspNetCoreSelfHost(_options, urlOptions); #else - _httpServer = new OwinSelfHost(_options, urlOptions); + _httpServer = new OwinSelfHost(_options, urlOptions); #endif - var startTask = _httpServer.StartAsync(); + var startTask = _httpServer.StartAsync(); - using (var ctsStartTimeout = new CancellationTokenSource(settings.StartTimeout)) + using (var ctsStartTimeout = new CancellationTokenSource(settings.StartTimeout)) + { + while (!_httpServer.IsStarted) { - while (!_httpServer.IsStarted) + // Throw exception if service start fails + if (_httpServer.RunningException != null) { - // Throw exception if service start fails - if (_httpServer.RunningException != null) - { - throw new WireMockException($"Service start failed with error: {_httpServer.RunningException.Message}", _httpServer.RunningException); - } + throw new WireMockException($"Service start failed with error: {_httpServer.RunningException.Message}", _httpServer.RunningException); + } - if (ctsStartTimeout.IsCancellationRequested) + if (ctsStartTimeout.IsCancellationRequested) + { + // In case of an aggregate exception, throw the exception. + if (startTask.Exception != null) { - // In case of an aggregate exception, throw the exception. - if (startTask.Exception != null) - { - throw new WireMockException($"Service start failed with error: {startTask.Exception.Message}", startTask.Exception); - } - - // Else throw TimeoutException - throw new TimeoutException($"Service start timed out after {TimeSpan.FromMilliseconds(settings.StartTimeout)}"); + throw new WireMockException($"Service start failed with error: {startTask.Exception.Message}", startTask.Exception); } - ctsStartTimeout.Token.WaitHandle.WaitOne(ServerStartDelayInMs); + // Else throw TimeoutException + throw new TimeoutException($"Service start timed out after {TimeSpan.FromMilliseconds(settings.StartTimeout)}"); } - Urls = _httpServer.Urls.ToArray(); - Ports = _httpServer.Ports; + ctsStartTimeout.Token.WaitHandle.WaitOne(ServerStartDelayInMs); } - if (settings.AllowBodyForAllHttpMethods == true) - { - _options.AllowBodyForAllHttpMethods = _settings.AllowBodyForAllHttpMethods; - _settings.Logger.Info("AllowBodyForAllHttpMethods is set to True"); - } + Urls = _httpServer.Urls.ToArray(); + Ports = _httpServer.Ports; + } - if (settings.AllowOnlyDefinedHttpStatusCodeInResponse == true) - { - _options.AllowOnlyDefinedHttpStatusCodeInResponse = _settings.AllowOnlyDefinedHttpStatusCodeInResponse; - _settings.Logger.Info("AllowOnlyDefinedHttpStatusCodeInResponse is set to True"); - } + InitSettings(settings); + } - if (settings.AllowPartialMapping == true) - { - AllowPartialMapping(); - } + /// + [PublicAPI] + public void Stop() + { + var result = _httpServer?.StopAsync(); + result?.Wait(); // wait for stop to actually happen + } + #endregion - if (settings.StartAdminInterface == true) - { - if (!string.IsNullOrEmpty(settings.AdminUsername) && !string.IsNullOrEmpty(settings.AdminPassword)) - { - SetBasicAuthentication(settings.AdminUsername, settings.AdminPassword); - } + /// + [PublicAPI] + public void AddCatchAllMapping() + { + Given(Request.Create().WithPath("/*").UsingAnyMethod()) + .WithGuid(Guid.Parse("90008000-0000-4444-a17e-669cd84f1f05")) + .AtPriority(1000) + .RespondWith(new DynamicResponseProvider(request => ResponseMessageBuilder.Create("No matching mapping found", 404))); + } - if (!string.IsNullOrEmpty(settings.AdminAzureADTenant) && !string.IsNullOrEmpty(settings.AdminAzureADAudience)) - { - SetAzureADAuthentication(settings.AdminAzureADTenant, settings.AdminAzureADAudience); - } + /// + [PublicAPI] + public void Reset() + { + ResetLogEntries(); - InitAdmin(); - } + ResetMappings(); + } - if (settings.ReadStaticMappings == true) - { - ReadStaticMappings(); - } + /// + [PublicAPI] + public void ResetMappings() + { + foreach (var nonAdmin in _options.Mappings.ToArray().Where(m => !m.Value.IsAdminInterface)) + { + _options.Mappings.TryRemove(nonAdmin.Key, out _); + } + } - if (settings.WatchStaticMappings == true) - { - WatchStaticMappings(); - } + /// + [PublicAPI] + public bool DeleteMapping(Guid guid) + { + // Check a mapping exists with the same GUID, if so, remove it. + if (_options.Mappings.ContainsKey(guid)) + { + return _options.Mappings.TryRemove(guid, out _); + } - if (settings.ProxyAndRecordSettings != null) - { - InitProxyAndRecord(settings); - } + return false; + } - if (settings.RequestLogExpirationDuration != null) - { - SetRequestLogExpirationDuration(settings.RequestLogExpirationDuration); - } + private bool DeleteMapping(string path) + { + // Check a mapping exists with the same path, if so, remove it. + var mapping = _options.Mappings.ToArray().FirstOrDefault(entry => string.Equals(entry.Value.Path, path, StringComparison.OrdinalIgnoreCase)); + return DeleteMapping(mapping.Key); + } - if (settings.MaxRequestLogCount != null) - { - SetMaxRequestLogCount(settings.MaxRequestLogCount); - } - } + /// + [PublicAPI] + public void AddGlobalProcessingDelay(TimeSpan delay) + { + _options.RequestProcessingDelay = delay; + } - /// - [PublicAPI] - public void Stop() - { - var result = _httpServer?.StopAsync(); - result?.Wait(); // wait for stop to actually happen - } - #endregion + /// + [PublicAPI] + public void AllowPartialMapping(bool allow = true) + { + _settings.Logger.Info("AllowPartialMapping is set to {0}", allow); + _options.AllowPartialMapping = allow; + } - /// - [PublicAPI] - public void AddCatchAllMapping() - { - Given(Request.Create().WithPath("/*").UsingAnyMethod()) - .WithGuid(Guid.Parse("90008000-0000-4444-a17e-669cd84f1f05")) - .AtPriority(1000) - .RespondWith(new DynamicResponseProvider(request => ResponseMessageBuilder.Create("No matching mapping found", 404))); - } + /// + [PublicAPI] + public void SetAzureADAuthentication([NotNull] string tenant, [NotNull] string audience) + { + Guard.NotNull(tenant, nameof(tenant)); + Guard.NotNull(audience, nameof(audience)); - /// - [PublicAPI] - public void Reset() - { - ResetLogEntries(); +#if NETSTANDARD1_3 + throw new NotSupportedException("AzureADAuthentication is not supported for NETStandard 1.3"); +#else + _options.AuthenticationMatcher = new AzureADAuthenticationMatcher(tenant, audience); +#endif + } - ResetMappings(); - } + /// + [PublicAPI] + public void SetBasicAuthentication([NotNull] string username, [NotNull] string password) + { + Guard.NotNull(username, nameof(username)); + Guard.NotNull(password, nameof(password)); - /// - [PublicAPI] - public void ResetMappings() - { - foreach (var nonAdmin in _options.Mappings.ToArray().Where(m => !m.Value.IsAdminInterface)) - { - _options.Mappings.TryRemove(nonAdmin.Key, out _); - } - } + _options.AuthenticationMatcher = new BasicAuthenticationMatcher(username, password); + } - /// - [PublicAPI] - public bool DeleteMapping(Guid guid) - { - // Check a mapping exists with the same GUID, if so, remove it. - if (_options.Mappings.ContainsKey(guid)) - { - return _options.Mappings.TryRemove(guid, out _); - } + /// + [PublicAPI] + public void RemoveAuthentication() + { + _options.AuthenticationMatcher = null; + } - return false; - } + /// + [PublicAPI] + public void SetMaxRequestLogCount([CanBeNull] int? maxRequestLogCount) + { + _options.MaxRequestLogCount = maxRequestLogCount; + } - private bool DeleteMapping(string path) - { - // Check a mapping exists with the same path, if so, remove it. - var mapping = _options.Mappings.ToArray().FirstOrDefault(entry => string.Equals(entry.Value.Path, path, StringComparison.OrdinalIgnoreCase)); - return DeleteMapping(mapping.Key); - } + /// + [PublicAPI] + public void SetRequestLogExpirationDuration([CanBeNull] int? requestLogExpirationDuration) + { + _options.RequestLogExpirationDuration = requestLogExpirationDuration; + } + + /// + [PublicAPI] + public void ResetScenarios() + { + _options.Scenarios.Clear(); + } - /// - [PublicAPI] - public void AddGlobalProcessingDelay(TimeSpan delay) + /// + [PublicAPI] + public IWireMockServer WithMapping(params MappingModel[] mappings) + { + foreach (var mapping in mappings) { - _options.RequestProcessingDelay = delay; + ConvertMappingAndRegisterAsRespondProvider(mapping, mapping.Guid ?? Guid.NewGuid()); } - /// - [PublicAPI] - public void AllowPartialMapping(bool allow = true) + return this; + } + + /// + [PublicAPI] + public IWireMockServer WithMapping(string mappings) + { + var mappingModels = DeserializeJsonToArray(mappings); + foreach (var mappingModel in mappingModels) { - _settings.Logger.Info("AllowPartialMapping is set to {0}", allow); - _options.AllowPartialMapping = allow; + ConvertMappingAndRegisterAsRespondProvider(mappingModel, mappingModel.Guid ?? Guid.NewGuid()); } - /// - [PublicAPI] - public void SetAzureADAuthentication([NotNull] string tenant, [NotNull] string audience) - { - Guard.NotNull(tenant, nameof(tenant)); - Guard.NotNull(audience, nameof(audience)); + return this; + } -#if NETSTANDARD1_3 - throw new NotSupportedException("AzureADAuthentication is not supported for NETStandard 1.3"); -#else - _options.AuthenticationMatcher = new AzureADAuthenticationMatcher(tenant, audience); -#endif - } + /// + /// The given. + /// + /// The request matcher. + /// Optional boolean to indicate if this mapping should be saved as static mapping file. + /// The . + [PublicAPI] + public IRespondWithAProvider Given(IRequestMatcher requestMatcher, bool saveToFile = false) + { + return new RespondWithAProvider(RegisterMapping, requestMatcher, _settings, saveToFile); + } - /// - [PublicAPI] - public void SetBasicAuthentication([NotNull] string username, [NotNull] string password) + private void RegisterMapping(IMapping mapping, bool saveToFile) + { + // Check a mapping exists with the same Guid, if so, replace it. + if (_options.Mappings.ContainsKey(mapping.Guid)) { - Guard.NotNull(username, nameof(username)); - Guard.NotNull(password, nameof(password)); - - _options.AuthenticationMatcher = new BasicAuthenticationMatcher(username, password); + _options.Mappings[mapping.Guid] = mapping; + } + else + { + _options.Mappings.TryAdd(mapping.Guid, mapping); } - /// - [PublicAPI] - public void RemoveAuthentication() + if (saveToFile) { - _options.AuthenticationMatcher = null; + _mappingToFileSaver.SaveMappingToFile(mapping); } + } - /// - [PublicAPI] - public void SetMaxRequestLogCount([CanBeNull] int? maxRequestLogCount) + private void InitSettings(WireMockServerSettings settings) + { + if (settings.AllowBodyForAllHttpMethods == true) { - _options.MaxRequestLogCount = maxRequestLogCount; + _options.AllowBodyForAllHttpMethods = _settings.AllowBodyForAllHttpMethods; + _settings.Logger.Info("AllowBodyForAllHttpMethods is set to True"); } - /// - [PublicAPI] - public void SetRequestLogExpirationDuration([CanBeNull] int? requestLogExpirationDuration) + if (settings.AllowOnlyDefinedHttpStatusCodeInResponse == true) { - _options.RequestLogExpirationDuration = requestLogExpirationDuration; + _options.AllowOnlyDefinedHttpStatusCodeInResponse = _settings.AllowOnlyDefinedHttpStatusCodeInResponse; + _settings.Logger.Info("AllowOnlyDefinedHttpStatusCodeInResponse is set to True"); } - /// - [PublicAPI] - public void ResetScenarios() + if (settings.AllowPartialMapping == true) { - _options.Scenarios.Clear(); + AllowPartialMapping(); } - /// - [PublicAPI] - public IWireMockServer WithMapping(params MappingModel[] mappings) + if (settings.StartAdminInterface == true) { - foreach (var mapping in mappings) + if (!string.IsNullOrEmpty(settings.AdminUsername) && !string.IsNullOrEmpty(settings.AdminPassword)) { - ConvertMappingAndRegisterAsRespondProvider(mapping, mapping.Guid ?? Guid.NewGuid()); + SetBasicAuthentication(settings.AdminUsername, settings.AdminPassword); } - return this; - } - - /// - [PublicAPI] - public IWireMockServer WithMapping(string mappings) - { - var mappingModels = DeserializeJsonToArray(mappings); - foreach (var mappingModel in mappingModels) + if (!string.IsNullOrEmpty(settings.AdminAzureADTenant) && !string.IsNullOrEmpty(settings.AdminAzureADAudience)) { - ConvertMappingAndRegisterAsRespondProvider(mappingModel, mappingModel.Guid ?? Guid.NewGuid()); + SetAzureADAuthentication(settings.AdminAzureADTenant, settings.AdminAzureADAudience); } - return this; + InitAdmin(); } - /// - /// The given. - /// - /// The request matcher. - /// Optional boolean to indicate if this mapping should be saved as static mapping file. - /// The . - [PublicAPI] - public IRespondWithAProvider Given(IRequestMatcher requestMatcher, bool saveToFile = false) + if (settings.ReadStaticMappings == true) { - return new RespondWithAProvider(RegisterMapping, requestMatcher, _settings, saveToFile); + ReadStaticMappings(); } - private void RegisterMapping(IMapping mapping, bool saveToFile) + if (settings.WatchStaticMappings == true) { - // Check a mapping exists with the same Guid, if so, replace it. - if (_options.Mappings.ContainsKey(mapping.Guid)) - { - _options.Mappings[mapping.Guid] = mapping; - } - else - { - _options.Mappings.TryAdd(mapping.Guid, mapping); - } + WatchStaticMappings(); + } - if (saveToFile) - { - _mappingToFileSaver.SaveMappingToFile(mapping); - } + InitProxyAndRecord(settings); + + if (settings.RequestLogExpirationDuration != null) + { + SetRequestLogExpirationDuration(settings.RequestLogExpirationDuration); + } + + if (settings.MaxRequestLogCount != null) + { + SetMaxRequestLogCount(settings.MaxRequestLogCount); } } } \ No newline at end of file diff --git a/src/WireMock.Net/Settings/ProxyAndRecordSettings.cs b/src/WireMock.Net/Settings/ProxyAndRecordSettings.cs index 1e9d95186..a31cd1c61 100644 --- a/src/WireMock.Net/Settings/ProxyAndRecordSettings.cs +++ b/src/WireMock.Net/Settings/ProxyAndRecordSettings.cs @@ -43,5 +43,11 @@ public class ProxyAndRecordSettings : HttpClientSettings /// [PublicAPI] public string[] ExcludedCookies { get; set; } + + /// + /// Prefer the Proxy Mapping over the saved Mapping (in case SaveMapping is set to true). + /// + //[PublicAPI] + //public bool PreferProxyMapping { get; set; } } } \ No newline at end of file diff --git a/src/WireMock.Net/Settings/WireMockServerSettingsParser.cs b/src/WireMock.Net/Settings/WireMockServerSettingsParser.cs index 8c8539cbf..122a62359 100644 --- a/src/WireMock.Net/Settings/WireMockServerSettingsParser.cs +++ b/src/WireMock.Net/Settings/WireMockServerSettingsParser.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using JetBrains.Annotations; using Stef.Validation; using WireMock.Logging; @@ -35,34 +34,33 @@ public static bool TryParseArguments([NotNull] string[] args, out WireMockServer settings = new WireMockServerSettings { - StartAdminInterface = parser.GetBoolValue("StartAdminInterface", true), - ReadStaticMappings = parser.GetBoolValue("ReadStaticMappings"), - WatchStaticMappings = parser.GetBoolValue("WatchStaticMappings"), - AllowPartialMapping = parser.GetBoolValue("AllowPartialMapping"), - WatchStaticMappingsInSubdirectories = parser.GetBoolValue("WatchStaticMappingsInSubdirectories"), - AdminUsername = parser.GetStringValue("AdminUsername"), - AdminPassword = parser.GetStringValue("AdminPassword"), - AdminAzureADTenant = parser.GetStringValue(nameof(WireMockServerSettings.AdminAzureADTenant)), AdminAzureADAudience = parser.GetStringValue(nameof(WireMockServerSettings.AdminAzureADAudience)), - MaxRequestLogCount = parser.GetIntValue("MaxRequestLogCount"), - RequestLogExpirationDuration = parser.GetIntValue("RequestLogExpirationDuration"), - AllowCSharpCodeMatcher = parser.GetBoolValue("AllowCSharpCodeMatcher"), + AdminAzureADTenant = parser.GetStringValue(nameof(WireMockServerSettings.AdminAzureADTenant)), + AdminPassword = parser.GetStringValue("AdminPassword"), + AdminUsername = parser.GetStringValue("AdminUsername"), AllowBodyForAllHttpMethods = parser.GetBoolValue("AllowBodyForAllHttpMethods"), + AllowCSharpCodeMatcher = parser.GetBoolValue("AllowCSharpCodeMatcher"), AllowOnlyDefinedHttpStatusCodeInResponse = parser.GetBoolValue("AllowOnlyDefinedHttpStatusCodeInResponse"), + AllowPartialMapping = parser.GetBoolValue("AllowPartialMapping"), DisableJsonBodyParsing = parser.GetBoolValue("DisableJsonBodyParsing"), HandleRequestsSynchronously = parser.GetBoolValue("HandleRequestsSynchronously"), + MaxRequestLogCount = parser.GetIntValue("MaxRequestLogCount"), + ReadStaticMappings = parser.GetBoolValue("ReadStaticMappings"), + RequestLogExpirationDuration = parser.GetIntValue("RequestLogExpirationDuration"), + SaveUnmatchedRequests = parser.GetBoolValue(nameof(WireMockServerSettings.SaveUnmatchedRequests)), + StartAdminInterface = parser.GetBoolValue("StartAdminInterface", true), ThrowExceptionWhenMatcherFails = parser.GetBoolValue("ThrowExceptionWhenMatcherFails"), UseRegexExtended = parser.GetBoolValue(nameof(WireMockServerSettings.UseRegexExtended), true), - SaveUnmatchedRequests = parser.GetBoolValue(nameof(WireMockServerSettings.SaveUnmatchedRequests)), + WatchStaticMappings = parser.GetBoolValue("WatchStaticMappings"), + WatchStaticMappingsInSubdirectories = parser.GetBoolValue("WatchStaticMappingsInSubdirectories"), }; #if USE_ASPNETCORE - settings.CorsPolicyOptions = parser.GetValue( - nameof(WireMockServerSettings.CorsPolicyOptions), values => - { - var value = string.Join(string.Empty, values); - return Enum.TryParse(value, true, out var corsPolicyOptions) ? corsPolicyOptions : CorsPolicyOptions.None; - }); + settings.CorsPolicyOptions = parser.GetValue(nameof(WireMockServerSettings.CorsPolicyOptions), values => + { + var value = string.Join(string.Empty, values); + return Enum.TryParse(value, true, out var corsPolicyOptions) ? corsPolicyOptions : CorsPolicyOptions.None; + }); #endif if (logger != null) @@ -75,9 +73,9 @@ public static bool TryParseArguments([NotNull] string[] args, out WireMockServer settings.Logger = new WireMockConsoleLogger(); } - if (parser.Contains("Port")) + if (parser.Contains(nameof(WireMockServerSettings.Port))) { - settings.Port = parser.GetIntValue("Port"); + settings.Port = parser.GetIntValue(nameof(WireMockServerSettings.Port)); } else { @@ -89,14 +87,15 @@ public static bool TryParseArguments([NotNull] string[] args, out WireMockServer { settings.ProxyAndRecordSettings = new ProxyAndRecordSettings { - Url = proxyUrl, - SaveMapping = parser.GetBoolValue("SaveMapping"), - SaveMappingToFile = parser.GetBoolValue("SaveMappingToFile"), - SaveMappingForStatusCodePattern = parser.GetStringValue("SaveMappingForStatusCodePattern"), + AllowAutoRedirect = parser.GetBoolValue("AllowAutoRedirect"), ClientX509Certificate2ThumbprintOrSubjectName = parser.GetStringValue("ClientX509Certificate2ThumbprintOrSubjectName"), - ExcludedHeaders = parser.GetValues("ExcludedHeaders"), ExcludedCookies = parser.GetValues("ExcludedCookies"), - AllowAutoRedirect = parser.GetBoolValue("AllowAutoRedirect") + ExcludedHeaders = parser.GetValues("ExcludedHeaders"), + // PreferProxyMapping = parser.GetBoolValue(nameof(ProxyAndRecordSettings.PreferProxyMapping)), + SaveMapping = parser.GetBoolValue("SaveMapping"), + SaveMappingForStatusCodePattern = parser.GetStringValue("SaveMappingForStatusCodePattern"), + SaveMappingToFile = parser.GetBoolValue("SaveMappingToFile"), + Url = proxyUrl }; string proxyAddress = parser.GetStringValue("WebProxyAddress"); diff --git a/src/WireMock.Net/Util/TinyMapperUtils.cs b/src/WireMock.Net/Util/TinyMapperUtils.cs new file mode 100644 index 000000000..e5f89ed68 --- /dev/null +++ b/src/WireMock.Net/Util/TinyMapperUtils.cs @@ -0,0 +1,30 @@ +using Nelibur.ObjectMapper; +using WireMock.Admin.Settings; +using WireMock.Settings; + +namespace WireMock.Util +{ + internal sealed class TinyMapperUtils + { + public static TinyMapperUtils Instance { get; } = new TinyMapperUtils(); + + private TinyMapperUtils() + { + TinyMapper.Bind(); + TinyMapper.Bind(); + + TinyMapper.Bind(); + TinyMapper.Bind(); + } + + public ProxyAndRecordSettingsModel Map(ProxyAndRecordSettings instance) + { + return instance == null ? null : TinyMapper.Map(instance); + } + + public ProxyAndRecordSettings Map(ProxyAndRecordSettingsModel model) + { + return model == null ? null : TinyMapper.Map(model); + } + } +} \ No newline at end of file diff --git a/src/WireMock.Net/WireMock.Net.csproj b/src/WireMock.Net/WireMock.Net.csproj index 759e778ac..4cd6df9f4 100644 --- a/src/WireMock.Net/WireMock.Net.csproj +++ b/src/WireMock.Net/WireMock.Net.csproj @@ -51,7 +51,7 @@ - + @@ -64,7 +64,7 @@ - + diff --git a/test/WireMock.Net.Tests/WireMockServer.Settings.cs b/test/WireMock.Net.Tests/WireMockServer.Settings.cs index c5b9573fa..d5b43cdab 100644 --- a/test/WireMock.Net.Tests/WireMockServer.Settings.cs +++ b/test/WireMock.Net.Tests/WireMockServer.Settings.cs @@ -3,6 +3,7 @@ using Moq; using NFluent; using WireMock.Authentication; +using WireMock.Constants; using WireMock.Logging; using WireMock.Owin; using WireMock.Server; @@ -79,13 +80,13 @@ public void WireMockServer_WireMockServerSettings_PriorityFromAllAdminMappingsIs }); // Assert - var mappings = server.Mappings; - Check.That(mappings.Count()).IsEqualTo(25); - Check.That(mappings.All(m => m.Priority == int.MinValue)).IsTrue(); + server.Mappings.Should().NotBeNull(); + server.Mappings.Should().HaveCount(25); + server.Mappings.All(m => m.Priority == WireMockConstants.AdminPriority).Should().BeTrue(); } [Fact] - public void WireMockServer_WireMockServerSettings_ProxyAndRecordSettings_ProxyPriority_Is1000_When_StartAdminInterface_IsTrue() + public void WireMockServer_WireMockServerSettings_ProxyAndRecordSettings_ProxyPriority_IsMinus2000000_When_StartAdminInterface_IsTrue() { // Assign and Act var server = WireMockServer.Start(new WireMockServerSettings @@ -98,10 +99,11 @@ public void WireMockServer_WireMockServerSettings_ProxyAndRecordSettings_ProxyPr }); // Assert - var mappings = server.Mappings; - Check.That(mappings.Count()).IsEqualTo(26); - Check.That(mappings.Count(m => m.Priority == int.MinValue)).IsEqualTo(25); - Check.That(mappings.Count(m => m.Priority == 1000)).IsEqualTo(1); + server.Mappings.Should().NotBeNull(); + server.Mappings.Should().HaveCount(26); + + server.Mappings.Count(m => m.Priority == WireMockConstants.AdminPriority).Should().Be(25); + server.Mappings.Count(m => m.Priority == WireMockConstants.ProxyPriority).Should().Be(1); } [Fact]