From 0972d2cb8fa9872049466d8f8d7df982eb7ec34b Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Sat, 29 Oct 2022 13:58:29 +0200 Subject: [PATCH] Add option to ProxySettings to append guid to mapping file (#838) * Add option to ProxySettings to append guid to mapping file * . * . * . --- WireMock.Net Solution.sln | 14 + examples/WireMockAzureQueueExample/.gitignore | 264 ++++++++++++++++++ .../WireMockAzureQueueExample/Function1.cs | 13 + .../Properties/launchSettings.json | 9 + .../Properties/serviceDependencies.json | 14 + .../Properties/serviceDependencies.local.json | 16 ++ .../WireMockAzureQueueExample.csproj | 25 ++ examples/WireMockAzureQueueExample/host.json | 11 + examples/WireMockAzureQueueProxy/Program.cs | 40 +++ .../WireMockAzureQueueProxy.csproj | 23 ++ ..._41b2aadc-d6ea-4c3c-ae20-2ae72eb08d88.json | 44 +++ ...vstoreaccount1_myqueue-items_messages.json | 60 ++++ ...vstoreaccount1_myqueue-items_messages.json | 59 ++++ .../HEAD _devstoreaccount1_myqueue-items.json | 46 +++ .../Settings/ProxyAndRecordSettingsModel.cs | 114 ++++---- .../Http/HttpResponseMessageHelper.cs | 20 +- .../Owin/Mappers/OwinResponseMapper.cs | 9 +- .../Serialization/MappingToFileSaver.cs | 31 +- .../Server/WireMockServer.Admin.cs | 60 ---- .../Server/WireMockServer.Proxy.cs | 68 +++++ .../Settings/ProxyAndRecordSettings.cs | 5 + .../Settings/WireMockServerSettingsParser.cs | 1 + 22 files changed, 812 insertions(+), 134 deletions(-) create mode 100644 examples/WireMockAzureQueueExample/.gitignore create mode 100644 examples/WireMockAzureQueueExample/Function1.cs create mode 100644 examples/WireMockAzureQueueExample/Properties/launchSettings.json create mode 100644 examples/WireMockAzureQueueExample/Properties/serviceDependencies.json create mode 100644 examples/WireMockAzureQueueExample/Properties/serviceDependencies.local.json create mode 100644 examples/WireMockAzureQueueExample/WireMockAzureQueueExample.csproj create mode 100644 examples/WireMockAzureQueueExample/host.json create mode 100644 examples/WireMockAzureQueueProxy/Program.cs create mode 100644 examples/WireMockAzureQueueProxy/WireMockAzureQueueProxy.csproj create mode 100644 examples/WireMockAzureQueueProxy/__admin/mappings/DELETE _devstoreaccount1_myqueue-items_messages_41b2aadc-d6ea-4c3c-ae20-2ae72eb08d88.json create mode 100644 examples/WireMockAzureQueueProxy/__admin/mappings/GET No Messages _devstoreaccount1_myqueue-items_messages.json create mode 100644 examples/WireMockAzureQueueProxy/__admin/mappings/GET With Messages _devstoreaccount1_myqueue-items_messages.json create mode 100644 examples/WireMockAzureQueueProxy/__admin/mappings/HEAD _devstoreaccount1_myqueue-items.json create mode 100644 src/WireMock.Net/Server/WireMockServer.Proxy.cs diff --git a/WireMock.Net Solution.sln b/WireMock.Net Solution.sln index 40211eb8..49772701 100644 --- a/WireMock.Net Solution.sln +++ b/WireMock.Net Solution.sln @@ -106,6 +106,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.xUnit", "src\W EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.Console.NET6.WithCertificate", "examples\WireMock.Net.Console.NET6.WithCertificate\WireMock.Net.Console.NET6.WithCertificate.csproj", "{7C2A9DE8-C89F-4841-9058-6B9BF81E5E34}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMockAzureQueueExample", "examples\WireMockAzureQueueExample\WireMockAzureQueueExample.csproj", "{BAA9EC2A-874B-45CE-8E51-A73622DC7F3D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMockAzureQueueProxy", "examples\WireMockAzureQueueProxy\WireMockAzureQueueProxy.csproj", "{ADB557D8-D66B-4387-912B-3F73E290B478}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -252,6 +256,14 @@ Global {7C2A9DE8-C89F-4841-9058-6B9BF81E5E34}.Debug|Any CPU.Build.0 = Debug|Any CPU {7C2A9DE8-C89F-4841-9058-6B9BF81E5E34}.Release|Any CPU.ActiveCfg = Release|Any CPU {7C2A9DE8-C89F-4841-9058-6B9BF81E5E34}.Release|Any CPU.Build.0 = Release|Any CPU + {BAA9EC2A-874B-45CE-8E51-A73622DC7F3D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BAA9EC2A-874B-45CE-8E51-A73622DC7F3D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BAA9EC2A-874B-45CE-8E51-A73622DC7F3D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BAA9EC2A-874B-45CE-8E51-A73622DC7F3D}.Release|Any CPU.Build.0 = Release|Any CPU + {ADB557D8-D66B-4387-912B-3F73E290B478}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ADB557D8-D66B-4387-912B-3F73E290B478}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ADB557D8-D66B-4387-912B-3F73E290B478}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ADB557D8-D66B-4387-912B-3F73E290B478}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -294,6 +306,8 @@ Global {670C7562-C154-442E-A249-7D26849BCD13} = {985E0ADB-D4B4-473A-AA40-567E279B7946} {0DE0954F-8C00-4E8D-B94A-4361FC1CBE44} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2} {7C2A9DE8-C89F-4841-9058-6B9BF81E5E34} = {985E0ADB-D4B4-473A-AA40-567E279B7946} + {BAA9EC2A-874B-45CE-8E51-A73622DC7F3D} = {985E0ADB-D4B4-473A-AA40-567E279B7946} + {ADB557D8-D66B-4387-912B-3F73E290B478} = {985E0ADB-D4B4-473A-AA40-567E279B7946} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {DC539027-9852-430C-B19F-FD035D018458} diff --git a/examples/WireMockAzureQueueExample/.gitignore b/examples/WireMockAzureQueueExample/.gitignore new file mode 100644 index 00000000..ff5b00c5 --- /dev/null +++ b/examples/WireMockAzureQueueExample/.gitignore @@ -0,0 +1,264 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# Azure Functions localsettings file +local.settings.json + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +project.fragment.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +#*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignoreable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc \ No newline at end of file diff --git a/examples/WireMockAzureQueueExample/Function1.cs b/examples/WireMockAzureQueueExample/Function1.cs new file mode 100644 index 00000000..630b4866 --- /dev/null +++ b/examples/WireMockAzureQueueExample/Function1.cs @@ -0,0 +1,13 @@ +using Microsoft.Azure.WebJobs; +using Microsoft.Extensions.Logging; + +namespace WireMockAzureQueueExample; + +public class Function1 +{ + [FunctionName("Function1")] + public void Run([QueueTrigger("myqueue-items", Connection = "ConnectionStringToWireMock")]string myQueueItem, ILogger log) + { + log.LogWarning($"C# Queue trigger function processed: {myQueueItem}"); + } +} \ No newline at end of file diff --git a/examples/WireMockAzureQueueExample/Properties/launchSettings.json b/examples/WireMockAzureQueueExample/Properties/launchSettings.json new file mode 100644 index 00000000..c8287fab --- /dev/null +++ b/examples/WireMockAzureQueueExample/Properties/launchSettings.json @@ -0,0 +1,9 @@ +{ + "profiles": { + "WireMockAzureQueueExample": { + "commandName": "Project", + "commandLineArgs": "--port 7290", + "launchBrowser": false + } + } +} \ No newline at end of file diff --git a/examples/WireMockAzureQueueExample/Properties/serviceDependencies.json b/examples/WireMockAzureQueueExample/Properties/serviceDependencies.json new file mode 100644 index 00000000..4003a114 --- /dev/null +++ b/examples/WireMockAzureQueueExample/Properties/serviceDependencies.json @@ -0,0 +1,14 @@ +{ + "dependencies": { + "appInsights1": { + "type": "appInsights" + }, + "secrets1": { + "type": "secrets" + }, + "storage1": { + "type": "storage", + "connectionId": "AzureWebJobsStorage" + } + } +} \ No newline at end of file diff --git a/examples/WireMockAzureQueueExample/Properties/serviceDependencies.local.json b/examples/WireMockAzureQueueExample/Properties/serviceDependencies.local.json new file mode 100644 index 00000000..c02a4e56 --- /dev/null +++ b/examples/WireMockAzureQueueExample/Properties/serviceDependencies.local.json @@ -0,0 +1,16 @@ +{ + "dependencies": { + "appInsights1": { + "type": "appInsights.sdk" + }, + "secrets1": { + "type": "secrets.user" + }, + "storage1": { + "secretStore": null, + "resourceId": null, + "type": "storage.emulator", + "connectionId": "AzureWebJobsStorage" + } + } +} \ No newline at end of file diff --git a/examples/WireMockAzureQueueExample/WireMockAzureQueueExample.csproj b/examples/WireMockAzureQueueExample/WireMockAzureQueueExample.csproj new file mode 100644 index 00000000..e992e558 --- /dev/null +++ b/examples/WireMockAzureQueueExample/WireMockAzureQueueExample.csproj @@ -0,0 +1,25 @@ + + + net6.0 + v4 + bb7d8355-68c4-4f81-8c2d-6cdd80cd7602 + + + + + + + + + + + + + PreserveNewest + + + PreserveNewest + Never + + + diff --git a/examples/WireMockAzureQueueExample/host.json b/examples/WireMockAzureQueueExample/host.json new file mode 100644 index 00000000..beb2e402 --- /dev/null +++ b/examples/WireMockAzureQueueExample/host.json @@ -0,0 +1,11 @@ +{ + "version": "2.0", + "logging": { + "applicationInsights": { + "samplingSettings": { + "isEnabled": true, + "excludedTypes": "Request" + } + } + } +} \ No newline at end of file diff --git a/examples/WireMockAzureQueueProxy/Program.cs b/examples/WireMockAzureQueueProxy/Program.cs new file mode 100644 index 00000000..a91efe53 --- /dev/null +++ b/examples/WireMockAzureQueueProxy/Program.cs @@ -0,0 +1,40 @@ +using Newtonsoft.Json; +using WireMock.Logging; +using WireMock.Server; +using WireMock.Settings; + +namespace WireMockAzureQueueProxy; + +static class Program +{ + static void Main(params string[] args) + { + var server = WireMockServer.Start(new WireMockServerSettings + { + Logger = new WireMockConsoleLogger(), + Urls = new[] { "http://localhost:20001/" }, + StartAdminInterface = false, + ReadStaticMappings = true, + WatchStaticMappings = true, + WatchStaticMappingsInSubdirectories = true, + //ProxyAndRecordSettings = new ProxyAndRecordSettings + //{ + // Url = "http://127.0.0.1:10001", + // SaveMapping = true, + // SaveMappingToFile = true, + // AppendGuidToSavedMappingFile = 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/WireMockAzureQueueProxy/WireMockAzureQueueProxy.csproj b/examples/WireMockAzureQueueProxy/WireMockAzureQueueProxy.csproj new file mode 100644 index 00000000..f44be19a --- /dev/null +++ b/examples/WireMockAzureQueueProxy/WireMockAzureQueueProxy.csproj @@ -0,0 +1,23 @@ + + + + Exe + net6.0 + + + + + + + + + + + + + + PreserveNewest + + + + \ No newline at end of file diff --git a/examples/WireMockAzureQueueProxy/__admin/mappings/DELETE _devstoreaccount1_myqueue-items_messages_41b2aadc-d6ea-4c3c-ae20-2ae72eb08d88.json b/examples/WireMockAzureQueueProxy/__admin/mappings/DELETE _devstoreaccount1_myqueue-items_messages_41b2aadc-d6ea-4c3c-ae20-2ae72eb08d88.json new file mode 100644 index 00000000..2bd09ebe --- /dev/null +++ b/examples/WireMockAzureQueueProxy/__admin/mappings/DELETE _devstoreaccount1_myqueue-items_messages_41b2aadc-d6ea-4c3c-ae20-2ae72eb08d88.json @@ -0,0 +1,44 @@ +{ + "Guid": "b4a2ff02-fb7f-4496-8c04-9aafc4f5f8f7", + "Title": "Proxy Mapping for DELETE /devstoreaccount1/myqueue-items/messages/41b2aadc-d6ea-4c3c-ae20-2ae72eb08d88", + "Description": "Proxy Mapping for DELETE /devstoreaccount1/myqueue-items/messages/41b2aadc-d6ea-4c3c-ae20-2ae72eb08d88", + "Request": { + "Path": { + "Matchers": [ + { + "Name": "WildcardMatcher", + "Pattern": "/devstoreaccount1/myqueue-items/messages/41b2aadc-d6ea-4c3c-ae20-2ae72eb08d88", + "IgnoreCase": false + } + ] + }, + "Methods": [ + "DELETE" + ], + "Params": [ + { + "Name": "popreceipt", + "Matchers": [ + { + "Name": "WildcardMatcher", + "Pattern": "*", + "IgnoreCase": false + } + ] + } + ] + }, + "Response": { + "StatusCode": 204, + "Headers": { + "Server": "Azurite-Queue/3.19.0", + "x-ms-client-request-id": "{{request.headers.x-ms-client-request-id}}", + "x-ms-request-id": "{{Random Type=\"Guid\"}}", + "x-ms-version": "2021-10-04", + "Date": "{{DateTime.Now \"ddd, dd MMM yyy HH’:’mm’:’ss ‘GMT’\"}}", + "Connection": "keep-alive", + "Keep-Alive": "timeout=5" + }, + "UseTransformer": true + } +} \ No newline at end of file diff --git a/examples/WireMockAzureQueueProxy/__admin/mappings/GET No Messages _devstoreaccount1_myqueue-items_messages.json b/examples/WireMockAzureQueueProxy/__admin/mappings/GET No Messages _devstoreaccount1_myqueue-items_messages.json new file mode 100644 index 00000000..e42076cd --- /dev/null +++ b/examples/WireMockAzureQueueProxy/__admin/mappings/GET No Messages _devstoreaccount1_myqueue-items_messages.json @@ -0,0 +1,60 @@ +{ + "Scenario": "AzureQueue Get Messages", + "WhenStateIs": "No more messages", + "SetStateTo": "No more messages", + "Guid": "4c871968-29ee-472b-a548-170444d4cc3e", + "Title": "Proxy Mapping for GET NO MSG /devstoreaccount1/myqueue-items/messages", + "Description": "Proxy Mapping for GET NO MSG /devstoreaccount1/myqueue-items/messages", + "Request": { + "Path": { + "Matchers": [ + { + "Name": "WildcardMatcher", + "Pattern": "/devstoreaccount1/myqueue-items/messages", + "IgnoreCase": false + } + ] + }, + "Methods": [ + "GET" + ], + "Params": [ + { + "Name": "numofmessages", + "Matchers": [ + { + "Name": "ExactMatcher", + "Pattern": "16", + "IgnoreCase": false + } + ] + }, + { + "Name": "visibilitytimeout", + "Matchers": [ + { + "Name": "ExactMatcher", + "Pattern": "600", + "IgnoreCase": false + } + ] + } + ] + }, + "Response": { + "StatusCode": 200, + "Body": "", + "Headers": { + "Content-Type": "application/xml", + "Server": "Azurite-Queue/3.19.0", + "x-ms-client-request-id": "{{request.headers.x-ms-client-request-id}}", + "x-ms-request-id": "{{Random Type=\"Guid\"}}", + "x-ms-version": "2021-10-04", + "Date": "{{DateTime.Now \"ddd, dd MMM yyy HH’:’mm’:’ss ‘GMT’\"}}", + "Connection": "keep-alive", + "Keep-Alive": "timeout=5", + "Transfer-Encoding": "chunked" + }, + "UseTransformer": true + } +} \ No newline at end of file diff --git a/examples/WireMockAzureQueueProxy/__admin/mappings/GET With Messages _devstoreaccount1_myqueue-items_messages.json b/examples/WireMockAzureQueueProxy/__admin/mappings/GET With Messages _devstoreaccount1_myqueue-items_messages.json new file mode 100644 index 00000000..c985d9fc --- /dev/null +++ b/examples/WireMockAzureQueueProxy/__admin/mappings/GET With Messages _devstoreaccount1_myqueue-items_messages.json @@ -0,0 +1,59 @@ +{ + "Scenario": "AzureQueue Get Messages", + "SetStateTo": "No more messages", + "Guid": "da9c6799-fbf8-41b6-8933-0df50f821ebb", + "Title": "Proxy Mapping for GET /devstoreaccount1/myqueue-items/messages", + "Description": "Proxy Mapping for GET /devstoreaccount1/myqueue-items/messages", + "Request": { + "Path": { + "Matchers": [ + { + "Name": "WildcardMatcher", + "Pattern": "/devstoreaccount1/myqueue-items/messages", + "IgnoreCase": false + } + ] + }, + "Methods": [ + "GET" + ], + "Params": [ + { + "Name": "numofmessages", + "Matchers": [ + { + "Name": "WildcardMatcher", + "Pattern": "*", + "IgnoreCase": true + } + ] + }, + { + "Name": "visibilitytimeout", + "Matchers": [ + { + "Name": "WildcardMatcher", + "Pattern": "*", + "IgnoreCase": false + } + ] + } + ] + }, + "Response": { + "StatusCode": 200, + "Body": "41b2aadc-d6ea-4c3c-ae20-2ae72eb08d88Sat, 29 Oct 2022 07:11:40 GMTSat, 31 Dec 2022 07:11:40 GMTMjlPY3QyMDIyMDc6MTE6NDAyMjU2Sat, 29 Oct 2022 07:21:40 GMT1c3RlZg==", + "Headers": { + "Content-Type": "application/xml", + "Server": "Azurite-Queue/3.19.0", + "x-ms-client-request-id": "{{request.headers.x-ms-client-request-id}}", + "x-ms-request-id": "{{Random Type=\"Guid\"}}", + "x-ms-version": "2021-10-04", + "Date": "{{DateTime.Now \"ddd, dd MMM yyy HH’:’mm’:’ss ‘GMT’\"}}", + "Connection": "keep-alive", + "Keep-Alive": "timeout=5", + "Transfer-Encoding": "chunked" + }, + "UseTransformer": true + } +} \ No newline at end of file diff --git a/examples/WireMockAzureQueueProxy/__admin/mappings/HEAD _devstoreaccount1_myqueue-items.json b/examples/WireMockAzureQueueProxy/__admin/mappings/HEAD _devstoreaccount1_myqueue-items.json new file mode 100644 index 00000000..9a2cc355 --- /dev/null +++ b/examples/WireMockAzureQueueProxy/__admin/mappings/HEAD _devstoreaccount1_myqueue-items.json @@ -0,0 +1,46 @@ +{ + "Guid": "17c7a389-98e1-4383-975d-54c82d1e3860", + "Title": "Proxy Mapping for HEAD /devstoreaccount1/myqueue-items", + "Description": "Proxy Mapping for HEAD /devstoreaccount1/myqueue-items", + "Request": { + "Path": { + "Matchers": [ + { + "Name": "WildcardMatcher", + "Pattern": "/devstoreaccount1/myqueue-items", + "IgnoreCase": false + } + ] + }, + "Methods": [ + "HEAD" + ], + "Params": [ + { + "Name": "comp", + "Matchers": [ + { + "Name": "ExactMatcher", + "Pattern": "metadata", + "IgnoreCase": false + } + ] + } + ] + }, + "Response": { + "StatusCode": 200, + "Body": "", + "Headers": { + "Server": "Azurite-Queue/3.19.0", + "x-ms-client-request-id": "{{request.headers.x-ms-client-request-id}}", + "x-ms-approximate-messages-count": "0", + "x-ms-request-id": "{{Random Type=\"Guid\"}}", + "x-ms-version": "2021-10-04", + "Date": "{{DateTime.Now \"ddd, dd MMM yyy HH’:’mm’:’ss ‘GMT’\"}}", + "Connection": "keep-alive", + "Keep-Alive": "timeout=5" + }, + "UseTransformer": true + } +} \ No newline at end of file diff --git a/src/WireMock.Net.Abstractions/Admin/Settings/ProxyAndRecordSettingsModel.cs b/src/WireMock.Net.Abstractions/Admin/Settings/ProxyAndRecordSettingsModel.cs index 36d4b03e..1867dd9d 100644 --- a/src/WireMock.Net.Abstractions/Admin/Settings/ProxyAndRecordSettingsModel.cs +++ b/src/WireMock.Net.Abstractions/Admin/Settings/ProxyAndRecordSettingsModel.cs @@ -1,67 +1,71 @@ -namespace WireMock.Admin.Settings +namespace WireMock.Admin.Settings; + +[FluentBuilder.AutoGenerateBuilder] +public class ProxyAndRecordSettingsModel { - [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; } + /// + /// 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; } + /// + /// Defines the WebProxySettings. + /// + public WebProxySettingsModel WebProxySettings { get; set; } - /// - /// Proxy requests should follow redirection (30x). - /// - public bool? AllowAutoRedirect { get; set; } + /// + /// Proxy requests should follow redirection (30x). + /// + public bool? AllowAutoRedirect { get; set; } - /// - /// The URL to proxy. - /// - public string Url { 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 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; } + /// + /// 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; } = "*"; + /// + /// 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 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; } + /// + /// 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; } + /// + /// Prefer the Proxy Mapping over the saved Mapping (in case SaveMapping is set to true). + /// + // public bool PreferProxyMapping { get; set; } - /// - /// When SaveMapping is set to true, this setting can be used to control the behavior of the generated request matchers for the new mapping. - /// - false, the default matchers will be used. - /// - true, the defined mappings in the request wil be used for the new mapping. - /// - /// Default value is false. - /// - public bool UseDefinedRequestMatchers { get; set; } - } + /// + /// When SaveMapping is set to true, this setting can be used to control the behavior of the generated request matchers for the new mapping. + /// - false, the default matchers will be used. + /// - true, the defined mappings in the request wil be used for the new mapping. + /// + /// Default value is false. + /// + public bool UseDefinedRequestMatchers { get; set; } + + /// + /// Append an unique GUID to the filename from the saved mapping file. + /// + public bool AppendGuidToSavedMappingFile { get; set; } } \ No newline at end of file diff --git a/src/WireMock.Net/Http/HttpResponseMessageHelper.cs b/src/WireMock.Net/Http/HttpResponseMessageHelper.cs index 54934c63..75cf9642 100644 --- a/src/WireMock.Net/Http/HttpResponseMessageHelper.cs +++ b/src/WireMock.Net/Http/HttpResponseMessageHelper.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Net; using System.Net.Http; using System.Threading.Tasks; using WireMock.Util; @@ -35,15 +36,18 @@ internal static class HttpResponseMessageHelper contentEncodingHeader = headers.First(header => string.Equals(header.Key, HttpKnownHeaderNames.ContentEncoding, StringComparison.OrdinalIgnoreCase)).Value; } - var bodyParserSettings = new BodyParserSettings + if (httpResponseMessage.StatusCode != HttpStatusCode.NoContent) // A body is not allowed for 204. { - Stream = stream, - ContentType = contentTypeHeader?.FirstOrDefault(), - DeserializeJson = deserializeJson, - ContentEncoding = contentEncodingHeader?.FirstOrDefault(), - DecompressGZipAndDeflate = decompressGzipAndDeflate - }; - responseMessage.BodyData = await BodyParser.ParseAsync(bodyParserSettings).ConfigureAwait(false); + var bodyParserSettings = new BodyParserSettings + { + Stream = stream, + ContentType = contentTypeHeader?.FirstOrDefault(), + DeserializeJson = deserializeJson, + ContentEncoding = contentEncodingHeader?.FirstOrDefault(), + DecompressGZipAndDeflate = decompressGzipAndDeflate + }; + responseMessage.BodyData = await BodyParser.ParseAsync(bodyParserSettings).ConfigureAwait(false); + } } foreach (var header in headers) diff --git a/src/WireMock.Net/Owin/Mappers/OwinResponseMapper.cs b/src/WireMock.Net/Owin/Mappers/OwinResponseMapper.cs index cbfd92d7..1b6c920f 100644 --- a/src/WireMock.Net/Owin/Mappers/OwinResponseMapper.cs +++ b/src/WireMock.Net/Owin/Mappers/OwinResponseMapper.cs @@ -99,7 +99,14 @@ namespace WireMock.Owin.Mappers if (bytes != null) { - await response.Body.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false); + try + { + await response.Body.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false); + } + catch (Exception ex) + { + _options.Logger.Warn("Error writing response body. Exception : {0}", ex); + } } } diff --git a/src/WireMock.Net/Serialization/MappingToFileSaver.cs b/src/WireMock.Net/Serialization/MappingToFileSaver.cs index 5c4d98f9..ce09a909 100644 --- a/src/WireMock.Net/Serialization/MappingToFileSaver.cs +++ b/src/WireMock.Net/Serialization/MappingToFileSaver.cs @@ -13,11 +13,8 @@ internal class MappingToFileSaver public MappingToFileSaver(WireMockServerSettings settings, MappingConverter mappingConverter) { - Guard.NotNull(settings); - Guard.NotNull(mappingConverter); - - _settings = settings; - _mappingConverter = mappingConverter; + _settings = Guard.NotNull(settings); + _mappingConverter = Guard.NotNull(mappingConverter); } public void SaveMappingToFile(IMapping mapping, string? folder = null) @@ -30,17 +27,31 @@ internal class MappingToFileSaver } var model = _mappingConverter.ToMappingModel(mapping); - string filename = (!string.IsNullOrEmpty(mapping.Title) ? SanitizeFileName(mapping.Title) : mapping.Guid.ToString()) + ".json"; - string path = Path.Combine(folder, filename); + var filename = BuildSanitizedFileName(mapping); + var path = Path.Combine(folder, filename); - _settings.Logger.Info("Saving Mapping file {0}", filename); + _settings.Logger.Info("Saving Mapping file {0}", path); _settings.FileSystemHandler.WriteMappingFile(path, JsonConvert.SerializeObject(model, JsonSerializationConstants.JsonSerializerSettingsDefault)); } - private static string SanitizeFileName(string name, char replaceChar = '_') + private string BuildSanitizedFileName(IMapping mapping, char replaceChar = '_') { - return Path.GetInvalidFileNameChars().Aggregate(name, (current, c) => current.Replace(c, replaceChar)); + string name; + if (!string.IsNullOrEmpty(mapping.Title)) + { + name = mapping.Title!; + if (_settings.ProxyAndRecordSettings?.AppendGuidToSavedMappingFile == true) + { + name += $"{replaceChar}{mapping.Guid}"; + } + } + else + { + name = mapping.Guid.ToString(); + } + + return $"{Path.GetInvalidFileNameChars().Aggregate(name, (current, c) => current.Replace(c, replaceChar))}.json"; } } \ No newline at end of file diff --git a/src/WireMock.Net/Server/WireMockServer.Admin.cs b/src/WireMock.Net/Server/WireMockServer.Admin.cs index 9e0e0424..8fd41124 100644 --- a/src/WireMock.Net/Server/WireMockServer.Admin.cs +++ b/src/WireMock.Net/Server/WireMockServer.Admin.cs @@ -3,9 +3,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; -using System.Net.Http; using System.Text; -using System.Threading.Tasks; using JetBrains.Annotations; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -18,11 +16,9 @@ using WireMock.Http; using WireMock.Logging; using WireMock.Matchers; using WireMock.Matchers.Request; -using WireMock.Proxy; using WireMock.RequestBuilders; using WireMock.ResponseProviders; using WireMock.Serialization; -using WireMock.Settings; using WireMock.Types; using WireMock.Util; @@ -208,62 +204,6 @@ public partial class WireMockServer } #endregion - #region Proxy and Record - private HttpClient? _httpClientForProxy; - - private void InitProxyAndRecord(WireMockServerSettings settings) - { - if (settings.ProxyAndRecordSettings == null) - { - _httpClientForProxy = null; - DeleteMapping(ProxyMappingGuid); - return; - } - - _httpClientForProxy = HttpClientBuilder.Build(settings.ProxyAndRecordSettings); - - var proxyRespondProvider = Given(Request.Create().WithPath("/*").UsingAnyMethod()).WithGuid(ProxyMappingGuid).WithTitle("Default Proxy Mapping on /*"); - if (settings.StartAdminInterface == true) - { - proxyRespondProvider.AtPriority(WireMockConstants.ProxyPriority); - } - - proxyRespondProvider.RespondWith(new ProxyAsyncResponseProvider(ProxyAndRecordAsync, settings)); - } - - private async Task ProxyAndRecordAsync(IRequestMessage requestMessage, WireMockServerSettings settings) - { - var requestUri = new Uri(requestMessage.Url); - var proxyUri = new Uri(settings.ProxyAndRecordSettings!.Url); - var proxyUriWithRequestPathAndQuery = new Uri(proxyUri, requestUri.PathAndQuery); - - var proxyHelper = new ProxyHelper(settings); - - var (responseMessage, mapping) = await proxyHelper.SendAsync( - null, - _settings.ProxyAndRecordSettings!, - _httpClientForProxy!, - requestMessage, - proxyUriWithRequestPathAndQuery.AbsoluteUri - ).ConfigureAwait(false); - - if (mapping != null) - { - if (settings.ProxyAndRecordSettings.SaveMapping) - { - _options.Mappings.TryAdd(mapping.Guid, mapping); - } - - if (settings.ProxyAndRecordSettings.SaveMappingToFile) - { - _mappingToFileSaver.SaveMappingToFile(mapping); - } - } - - return responseMessage; - } - #endregion - #region Settings private IResponseMessage SettingsGet(IRequestMessage requestMessage) { diff --git a/src/WireMock.Net/Server/WireMockServer.Proxy.cs b/src/WireMock.Net/Server/WireMockServer.Proxy.cs new file mode 100644 index 00000000..b3d05599 --- /dev/null +++ b/src/WireMock.Net/Server/WireMockServer.Proxy.cs @@ -0,0 +1,68 @@ +using System; +using System.Net.Http; +using System.Threading.Tasks; +using WireMock.Constants; +using WireMock.Http; +using WireMock.Proxy; +using WireMock.RequestBuilders; +using WireMock.ResponseProviders; +using WireMock.Settings; + +namespace WireMock.Server; + +public partial class WireMockServer +{ + private HttpClient? _httpClientForProxy; + + private void InitProxyAndRecord(WireMockServerSettings settings) + { + if (settings.ProxyAndRecordSettings == null) + { + _httpClientForProxy = null; + DeleteMapping(ProxyMappingGuid); + return; + } + + _httpClientForProxy = HttpClientBuilder.Build(settings.ProxyAndRecordSettings); + + var proxyRespondProvider = Given(Request.Create().WithPath("/*").UsingAnyMethod()).WithGuid(ProxyMappingGuid).WithTitle("Default Proxy Mapping on /*"); + if (settings.StartAdminInterface == true) + { + proxyRespondProvider.AtPriority(WireMockConstants.ProxyPriority); + } + + proxyRespondProvider.RespondWith(new ProxyAsyncResponseProvider(ProxyAndRecordAsync, settings)); + } + + private async Task ProxyAndRecordAsync(IRequestMessage requestMessage, WireMockServerSettings settings) + { + var requestUri = new Uri(requestMessage.Url); + var proxyUri = new Uri(settings.ProxyAndRecordSettings!.Url); + var proxyUriWithRequestPathAndQuery = new Uri(proxyUri, requestUri.PathAndQuery); + + var proxyHelper = new ProxyHelper(settings); + + var (responseMessage, mapping) = await proxyHelper.SendAsync( + null, + _settings.ProxyAndRecordSettings!, + _httpClientForProxy!, + requestMessage, + proxyUriWithRequestPathAndQuery.AbsoluteUri + ).ConfigureAwait(false); + + if (mapping != null) + { + if (settings.ProxyAndRecordSettings.SaveMapping) + { + _options.Mappings.TryAdd(mapping.Guid, mapping); + } + + if (settings.ProxyAndRecordSettings.SaveMappingToFile) + { + _mappingToFileSaver.SaveMappingToFile(mapping); + } + } + + return responseMessage; + } +} \ No newline at end of file diff --git a/src/WireMock.Net/Settings/ProxyAndRecordSettings.cs b/src/WireMock.Net/Settings/ProxyAndRecordSettings.cs index 3ae30763..c9af7fbd 100644 --- a/src/WireMock.Net/Settings/ProxyAndRecordSettings.cs +++ b/src/WireMock.Net/Settings/ProxyAndRecordSettings.cs @@ -58,4 +58,9 @@ public class ProxyAndRecordSettings : HttpClientSettings /// Default value is false. /// public bool UseDefinedRequestMatchers { get; set; } + + /// + /// Append an unique GUID to the filename from the saved mapping file. + /// + public bool AppendGuidToSavedMappingFile { 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 3f875998..1e1b6625 100644 --- a/src/WireMock.Net/Settings/WireMockServerSettingsParser.cs +++ b/src/WireMock.Net/Settings/WireMockServerSettingsParser.cs @@ -94,6 +94,7 @@ public static class WireMockServerSettingsParser SaveMappingForStatusCodePattern = parser.GetStringValue("SaveMappingForStatusCodePattern", "*"), SaveMappingToFile = parser.GetBoolValue("SaveMappingToFile"), UseDefinedRequestMatchers = parser.GetBoolValue(nameof(ProxyAndRecordSettings.UseDefinedRequestMatchers)), + AppendGuidToSavedMappingFile = parser.GetBoolValue(nameof(ProxyAndRecordSettings.AppendGuidToSavedMappingFile)), Url = proxyUrl! };