Compare commits

..

24 Commits

Author SHA1 Message Date
Stef Heyenrath
88ff2a72db select id, name, value from table where id in (1, 2, 3, 4, 5) 2022-11-30 18:53:50 +01:00
Stef Heyenrath
d5026e83b5 cf 2022-11-30 15:39:09 +01:00
Stef Heyenrath
b6b17d80cc Merge branch 'master' into stef-849 2022-11-30 15:35:38 +01:00
Stef Heyenrath
c8ad9f49e4 Fix 2022-11-30 15:23:36 +01:00
Stef Heyenrath
0a4aca9ae9 cf 2022-11-30 11:55:31 +01:00
Stef Heyenrath
e5ed6036ee fx 2022-11-30 10:03:41 +01:00
Stef Heyenrath
7b534635b9 fix tests 2022-11-30 08:19:10 +00:00
Stef Heyenrath
1562958172 Add more QueryParameterMultipleValueSupport NoComma tests 2022-11-30 07:36:13 +00:00
Stef Heyenrath
35d42a5c0d Fix Linux CI build + Fix opencover (#851)
* Fix Linux CI Test (opencover.xml)

* 2

* Build & Execute Unit tests

* ,cmd

* cout

* 12

* b

* server

* b =b

* /p:CoverletOutput=./test/WireMock.Net.Tests/WireMock.Net.Tests

* co?

* 2p

* 2?

* failOnStderr: false

* e0

* cc

* pub

* sc

* coverlet

* props

* pt

* coverage.net6.0.opencover.xml
2022-11-25 18:13:02 +01:00
Stef Heyenrath
429d6830ae 1.5.11 2022-11-24 21:47:56 +01:00
Stef Heyenrath
38634ac65a Use try-catch when adding or removing logEntry (#848)
* Use try-catch when removing logEntry

* .

* try catch add

* Add extra check

* ...
2022-11-21 07:30:27 +01:00
Stef Heyenrath
ef5f988786 Add Settings.QueryParameterMultipleValueSupport (#836)
* QueryParameterMultipleValueSupport

* .

* ,

* ,
2022-11-08 19:27:44 +01:00
Stef Heyenrath
1e44f52ad6 1.5.10 2022-11-06 13:29:12 +01:00
Stef Heyenrath
7fd1d30d0e Add WireMockNullLogger as valid commandline logger option (#845)
* Add WireMockNullLogger as valid commandline logger option

* .
2022-11-06 13:25:26 +01:00
Gerhard Gradnig
49b29d74dc Webhook: Use the transformed URL to create the HttpRequestMessage (#843)
Co-authored-by: Gerhard Gradnig <gerhard.gradnig@admiral.at>
2022-11-05 10:54:39 +01:00
Stef Heyenrath
80931e9fb5 Update Azure.Storage NuGets 2022-10-29 14:06:43 +02:00
Stef Heyenrath
cce344ff83 1.5.9 2022-10-29 14:02:30 +02:00
Stef Heyenrath
0972d2cb8f Add option to ProxySettings to append guid to mapping file (#838)
* Add option to ProxySettings to append guid to mapping file

* .

* .

* .
2022-10-29 13:58:29 +02:00
dependabot[bot]
a39b7fc633 Bump Microsoft.AspNetCore.Server.Kestrel.Core (#837)
Bumps [Microsoft.AspNetCore.Server.Kestrel.Core](https://github.com/aspnet/AspNetCore) from 2.1.7 to 2.1.25.
- [Release notes](https://github.com/aspnet/AspNetCore/releases)
- [Changelog](https://github.com/dotnet/aspnetcore/blob/main/docs/ReleasePlanning.md)
- [Commits](https://github.com/aspnet/AspNetCore/compare/v2.1.7...v2.1.25)

---
updated-dependencies:
- dependency-name: Microsoft.AspNetCore.Server.Kestrel.Core
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-27 07:46:01 +02:00
Stef Heyenrath
31298d281d Support deleting / resetting a single scenario (#834)
* Support deleting / resetting a single scenario

* move files
2022-10-26 08:43:51 +02:00
Stef Heyenrath
b4c32dd66b WireMock.Net.WebApplication.NET6 2022-10-25 17:19:13 +02:00
Stef Heyenrath
57115f1a3d Add setting to skip saving the string-response in the logging when using WithBody(Func...) (#828)
* Add extra unit-test for Response WithBody (dynamic code)

* DoNotSaveDynamicResponseInLogEntry

* update SettingsModel

* fix

* .
2022-10-21 14:47:26 +02:00
Stef Heyenrath
306c69f478 Fixes for WireMock.Net.FluentAssertions (callcount behaviour) (#832)
* UsingAnyMethod

* fix

* .
2022-10-17 21:50:24 +02:00
Stef Heyenrath
fb8fec0376 Update funding.yml 2022-10-16 09:17:41 +02:00
117 changed files with 3404 additions and 2037 deletions

2
.github/FUNDING.yml vendored
View File

@@ -1,6 +1,6 @@
# These are supported funding model platforms # These are supported funding model platforms
github: # [StefH] github: [StefH]
patreon: # Replace with a single Patreon username patreon: # Replace with a single Patreon username
open_collective: # wiremocknet open_collective: # wiremocknet
ko_fi: # Replace with a single Ko-fi username ko_fi: # Replace with a single Ko-fi username

View File

@@ -1,3 +1,20 @@
# 1.5.11 (24 November 2022)
- [#836](https://github.com/WireMock-Net/WireMock.Net/pull/836) - Add Settings.QueryParameterMultipleValueSupport [feature] contributed by [StefH](https://github.com/StefH)
- [#848](https://github.com/WireMock-Net/WireMock.Net/pull/848) - Use try-catch when adding or removing logEntry [bug] contributed by [StefH](https://github.com/StefH)
- [#846](https://github.com/WireMock-Net/WireMock.Net/issues/846) - Exception ArgumentOutOfRangeException [bug]
# 1.5.10 (06 November 2022)
- [#843](https://github.com/WireMock-Net/WireMock.Net/pull/843) - Webhook Templating: Use the transformed URL to create the HttpRequestMessage contributed by [ggradnig](https://github.com/ggradnig)
- [#845](https://github.com/WireMock-Net/WireMock.Net/pull/845) - Add WireMockNullLogger as valid commandline logger option [feature] contributed by [StefH](https://github.com/StefH)
# 1.5.9 (29 October 2022)
- [#828](https://github.com/WireMock-Net/WireMock.Net/pull/828) - Add setting to skip saving the string-response in the logging when using WithBody(Func...) [feature] contributed by [StefH](https://github.com/StefH)
- [#832](https://github.com/WireMock-Net/WireMock.Net/pull/832) - Fixes for WireMock.Net.FluentAssertions (callcount behaviour) [feature] contributed by [StefH](https://github.com/StefH)
- [#834](https://github.com/WireMock-Net/WireMock.Net/pull/834) - Support deleting / resetting a single scenario [feature] contributed by [StefH](https://github.com/StefH)
- [#837](https://github.com/WireMock-Net/WireMock.Net/pull/837) - Bump Microsoft.AspNetCore.Server.Kestrel.Core from 2.1.7 to 2.1.25 in /examples/WireMock.Net.StandAlone.Net461 [dependencies] contributed by [dependabot[bot]](https://github.com/apps/dependabot)
- [#838](https://github.com/WireMock-Net/WireMock.Net/pull/838) - Add option to ProxySettings to append guid to mapping file contributed by [StefH](https://github.com/StefH)
- [#826](https://github.com/WireMock-Net/WireMock.Net/issues/826) - Dynamic Body not to be cached when a Func is used to created the body [feature]
# 1.5.8 (16 October 2022) # 1.5.8 (16 October 2022)
- [#816](https://github.com/WireMock-Net/WireMock.Net/pull/816) - Some fixes to WireMock.Net.Assertions [feature] contributed by [StefH](https://github.com/StefH) - [#816](https://github.com/WireMock-Net/WireMock.Net/pull/816) - Some fixes to WireMock.Net.Assertions [feature] contributed by [StefH](https://github.com/StefH)
- [#817](https://github.com/WireMock-Net/WireMock.Net/pull/817) - ExactMatcher : IgnoreCase [feature] contributed by [StefH](https://github.com/StefH) - [#817](https://github.com/WireMock-Net/WireMock.Net/pull/817) - ExactMatcher : IgnoreCase [feature] contributed by [StefH](https://github.com/StefH)

View File

@@ -4,7 +4,7 @@
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
<VersionPrefix>1.5.8</VersionPrefix> <VersionPrefix>1.5.11</VersionPrefix>
<PackageIcon>WireMock.Net-Logo.png</PackageIcon> <PackageIcon>WireMock.Net-Logo.png</PackageIcon>
<PackageProjectUrl>https://github.com/WireMock-Net/WireMock.Net</PackageProjectUrl> <PackageProjectUrl>https://github.com/WireMock-Net/WireMock.Net</PackageProjectUrl>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression> <PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
@@ -20,6 +20,13 @@
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild> <ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
</PropertyGroup> </PropertyGroup>
<!-- https://github.com/coverlet-coverage/coverlet/issues/1391 -->
<PropertyGroup Condition="$(MSBuildProjectName.Contains('.Tests'))">
<CollectCoverage>true</CollectCoverage>
<ExcludeByAttribute>GeneratedCodeAttribute</ExcludeByAttribute>
<CoverletOutputFormat>opencover</CoverletOutputFormat>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<None Include="../../resources/WireMock.Net-Logo.png" Pack="true" PackagePath="" /> <None Include="../../resources/WireMock.Net-Logo.png" Pack="true" PackagePath="" />
<!--<None Include="../../PackageReadme.md" Pack="true" PackagePath=""/>--> <!--<None Include="../../PackageReadme.md" Pack="true" PackagePath=""/>-->

View File

@@ -1,6 +1,6 @@
rem https://github.com/StefH/GitHubReleaseNotes rem https://github.com/StefH/GitHubReleaseNotes
SET version=1.5.8 SET version=1.5.11
GitHubReleaseNotes --output CHANGELOG.md --skip-empty-releases --exclude-labels question invalid doc duplicate --version %version% --token %GH_TOKEN% GitHubReleaseNotes --output CHANGELOG.md --skip-empty-releases --exclude-labels question invalid doc duplicate --version %version% --token %GH_TOKEN%

View File

@@ -1,9 +1,6 @@
# 1.5.8 (16 October 2022) # 1.5.11 (24 November 2022)
- #816 Some fixes to WireMock.Net.Assertions [feature] - #836 Add Settings.QueryParameterMultipleValueSupport [feature]
- #817 ExactMatcher : IgnoreCase [feature] - #848 Use try-catch when adding or removing logEntry [bug]
- #824 WebHook - Transform Url [feature] - #846 Exception ArgumentOutOfRangeException [bug]
- #814 WithHeader cannot handle multiple requests with the same header key values [bug]
- #815 Why does UsingMethod check _callscount? [bug]
- #822 Webhook with generic url, body and custom header values [feature]
The full release notes can be found here: https://github.com/WireMock-Net/WireMock.Net/blob/master/CHANGELOG.md The full release notes can be found here: https://github.com/WireMock-Net/WireMock.Net/blob/master/CHANGELOG.md

View File

@@ -25,6 +25,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
.gitignore = .gitignore .gitignore = .gitignore
CHANGELOG.md = CHANGELOG.md CHANGELOG.md = CHANGELOG.md
Directory.Build.props = Directory.Build.props Directory.Build.props = Directory.Build.props
.github\FUNDING.yml = .github\FUNDING.yml
Generate-ReleaseNotes.cmd = Generate-ReleaseNotes.cmd Generate-ReleaseNotes.cmd = Generate-ReleaseNotes.cmd
nuget.config = nuget.config nuget.config = nuget.config
PackageReleaseNotes.template = PackageReleaseNotes.template PackageReleaseNotes.template = PackageReleaseNotes.template
@@ -105,6 +106,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.xUnit", "src\W
EndProject 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}" 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 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 Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@@ -251,6 +256,14 @@ Global
{7C2A9DE8-C89F-4841-9058-6B9BF81E5E34}.Debug|Any CPU.Build.0 = Debug|Any CPU {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.ActiveCfg = Release|Any CPU
{7C2A9DE8-C89F-4841-9058-6B9BF81E5E34}.Release|Any CPU.Build.0 = 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 EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@@ -293,6 +306,8 @@ Global
{670C7562-C154-442E-A249-7D26849BCD13} = {985E0ADB-D4B4-473A-AA40-567E279B7946} {670C7562-C154-442E-A249-7D26849BCD13} = {985E0ADB-D4B4-473A-AA40-567E279B7946}
{0DE0954F-8C00-4E8D-B94A-4361FC1CBE44} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2} {0DE0954F-8C00-4E8D-B94A-4361FC1CBE44} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2}
{7C2A9DE8-C89F-4841-9058-6B9BF81E5E34} = {985E0ADB-D4B4-473A-AA40-567E279B7946} {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 EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {DC539027-9852-430C-B19F-FD035D018458} SolutionGuid = {DC539027-9852-430C-B19F-FD035D018458}

View File

@@ -1,10 +1,18 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation"> <wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=AD/@EntryIndexedValue">AD</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=AD/@EntryIndexedValue">AD</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=CONNECT/@EntryIndexedValue">CONNECT</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=CS/@EntryIndexedValue">CS</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=CS/@EntryIndexedValue">CS</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=DELETE/@EntryIndexedValue">DELETE</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=EC/@EntryIndexedValue">EC</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=EC/@EntryIndexedValue">EC</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=GET/@EntryIndexedValue">GET</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=HEAD/@EntryIndexedValue">HEAD</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=ID/@EntryIndexedValue">ID</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=ID/@EntryIndexedValue">ID</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=IP/@EntryIndexedValue">IP</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=IP/@EntryIndexedValue">IP</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=MD/@EntryIndexedValue">MD5</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=MD/@EntryIndexedValue">MD5</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=OPTIONS/@EntryIndexedValue">OPTIONS</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=PATCH/@EntryIndexedValue">PATCH</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=POST/@EntryIndexedValue">POST</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=PUT/@EntryIndexedValue">PUT</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=RSA/@EntryIndexedValue">RSA</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=RSA/@EntryIndexedValue">RSA</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=SSL/@EntryIndexedValue">SSL</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=SSL/@EntryIndexedValue">SSL</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=TE/@EntryIndexedValue">TE</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=TE/@EntryIndexedValue">TE</s:String>

View File

@@ -44,12 +44,10 @@ jobs:
projects: './test/WireMock.Net.Tests/WireMock.Net.Tests.csproj' projects: './test/WireMock.Net.Tests/WireMock.Net.Tests.csproj'
arguments: '--configuration Debug --framework net6.0' arguments: '--configuration Debug --framework net6.0'
- task: DotNetCoreCLI@2 - task: CmdLine@2
displayName: 'Execute Unit tests'
inputs: inputs:
command: 'test' script: 'dotnet test ./test/WireMock.Net.Tests/WireMock.Net.Tests.csproj --no-build --configuration Debug --framework net6.0'
projects: './test/WireMock.Net.Tests/WireMock.Net.Tests.csproj' displayName: 'Execute Unit Tests with Coverage'
arguments: '--no-build --configuration Debug --framework net6.0 --collect:"XPlat Code Coverage" --logger trx /p:CollectCoverage=true /p:CoverletOutputFormat=opencover'
- task: SonarCloudAnalyze@1 - task: SonarCloudAnalyze@1
displayName: 'SonarCloud: Run Code Analysis' displayName: 'SonarCloud: Run Code Analysis'
@@ -76,7 +74,7 @@ jobs:
- task: PublishBuildArtifacts@1 - task: PublishBuildArtifacts@1
displayName: Publish coverage file displayName: Publish coverage file
inputs: inputs:
PathtoPublish: '/home/vsts/work/1/s/test/WireMock.Net.Tests/coverage.net6.0.opencover.xml' PathtoPublish: './test/WireMock.Net.Tests/coverage.net6.0.opencover.xml'
- job: Windows_Build_Test - job: Windows_Build_Test

View File

@@ -1,13 +1,13 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<configuration> <configuration>
<startup> <startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" /> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8"/>
</startup> </startup>
<runtime> <runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly> <dependentAssembly>
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" /> <assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-13.0.0.0" newVersion="13.0.0.0" /> <bindingRedirect oldVersion="0.0.0.0-13.0.0.0" newVersion="13.0.0.0"/>
</dependentAssembly> </dependentAssembly>
</assemblyBinding> </assemblyBinding>
</runtime> </runtime>

View File

@@ -8,9 +8,10 @@
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<RootNamespace>WireMock.Net.Service</RootNamespace> <RootNamespace>WireMock.Net.Service</RootNamespace>
<AssemblyName>WireMock.Net.Service</AssemblyName> <AssemblyName>WireMock.Net.Service</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion> <TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment> <FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects> <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<TargetFrameworkProfile />
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget> <PlatformTarget>AnyCPU</PlatformTarget>

View File

@@ -11,6 +11,6 @@
<package id="Newtonsoft.Json" version="13.0.1" targetFramework="net452" /> <package id="Newtonsoft.Json" version="13.0.1" targetFramework="net452" />
<package id="Owin" version="1.0" targetFramework="net452" /> <package id="Owin" version="1.0" targetFramework="net452" />
<package id="SimMetrics.Net" version="1.0.5" targetFramework="net452" /> <package id="SimMetrics.Net" version="1.0.5" targetFramework="net452" />
<package id="System.Net.Http" version="4.3.4" targetFramework="net452" /> <package id="System.Net.Http" version="4.3.4" targetFramework="net452" requireReinstallation="true" />
<package id="XPath2" version="1.1.3" targetFramework="net452" /> <package id="XPath2" version="1.1.3" targetFramework="net452" />
</packages> </packages>

View File

@@ -24,7 +24,7 @@
<package id="Microsoft.AspNetCore.Routing.Abstractions" version="2.1.1" targetFramework="net461" /> <package id="Microsoft.AspNetCore.Routing.Abstractions" version="2.1.1" targetFramework="net461" />
<package id="Microsoft.AspNetCore.Server.IISIntegration" version="2.1.2" targetFramework="net461" /> <package id="Microsoft.AspNetCore.Server.IISIntegration" version="2.1.2" targetFramework="net461" />
<package id="Microsoft.AspNetCore.Server.Kestrel" version="2.1.3" targetFramework="net461" /> <package id="Microsoft.AspNetCore.Server.Kestrel" version="2.1.3" targetFramework="net461" />
<package id="Microsoft.AspNetCore.Server.Kestrel.Core" version="2.1.7" targetFramework="net461" /> <package id="Microsoft.AspNetCore.Server.Kestrel.Core" version="2.1.25" targetFramework="net461" />
<package id="Microsoft.AspNetCore.Server.Kestrel.Https" version="2.1.3" targetFramework="net461" /> <package id="Microsoft.AspNetCore.Server.Kestrel.Https" version="2.1.3" targetFramework="net461" />
<package id="Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions" version="2.1.3" targetFramework="net461" /> <package id="Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions" version="2.1.3" targetFramework="net461" />
<package id="Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets" version="2.1.3" targetFramework="net461" /> <package id="Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets" version="2.1.3" targetFramework="net461" />

View File

@@ -0,0 +1,173 @@
{
"$schema": "https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"metadata": {
"_dependencyType": "compute.function.linux.appService"
},
"parameters": {
"resourceGroupName": {
"type": "string",
"defaultValue": "linux-app-service",
"metadata": {
"description": "Name of the resource group for the resource. It is recommended to put resources under same resource group for better tracking."
}
},
"resourceGroupLocation": {
"type": "string",
"defaultValue": "westeurope",
"metadata": {
"description": "Location of the resource group. Resource groups could have different location than resources, however by default we use API versions from latest hybrid profile which support all locations for resource types we support."
}
},
"resourceName": {
"type": "string",
"defaultValue": "WireMockNetWebApplicationNET6Linux",
"metadata": {
"description": "Name of the main resource to be created by this template."
}
},
"resourceLocation": {
"type": "string",
"defaultValue": "[parameters('resourceGroupLocation')]",
"metadata": {
"description": "Location of the resource. By default use resource group's location, unless the resource provider is not supported there."
}
}
},
"resources": [
{
"type": "Microsoft.Resources/resourceGroups",
"name": "[parameters('resourceGroupName')]",
"location": "[parameters('resourceGroupLocation')]",
"apiVersion": "2019-10-01"
},
{
"type": "Microsoft.Resources/deployments",
"name": "[concat(parameters('resourceGroupName'), 'Deployment', uniqueString(concat(parameters('resourceName'), subscription().subscriptionId)))]",
"resourceGroup": "[parameters('resourceGroupName')]",
"apiVersion": "2019-10-01",
"dependsOn": [
"[parameters('resourceGroupName')]"
],
"properties": {
"mode": "Incremental",
"expressionEvaluationOptions": {
"scope": "inner"
},
"parameters": {
"resourceGroupName": {
"value": "[parameters('resourceGroupName')]"
},
"resourceGroupLocation": {
"value": "[parameters('resourceGroupLocation')]"
},
"resourceName": {
"value": "[parameters('resourceName')]"
},
"resourceLocation": {
"value": "[parameters('resourceLocation')]"
}
},
"template": {
"$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"resourceGroupName": {
"type": "string"
},
"resourceGroupLocation": {
"type": "string"
},
"resourceName": {
"type": "string"
},
"resourceLocation": {
"type": "string"
}
},
"variables": {
"storage_name": "[toLower(concat('storage', uniqueString(concat(parameters('resourceName'), subscription().subscriptionId))))]",
"appServicePlan_name": "[concat('Plan', uniqueString(concat(parameters('resourceName'), subscription().subscriptionId)))]",
"storage_ResourceId": "[concat('/subscriptions/', subscription().subscriptionId, '/resourceGroups/', parameters('resourceGroupName'), '/providers/Microsoft.Storage/storageAccounts/', variables('storage_name'))]",
"appServicePlan_ResourceId": "[concat('/subscriptions/', subscription().subscriptionId, '/resourceGroups/', parameters('resourceGroupName'), '/providers/Microsoft.Web/serverFarms/', variables('appServicePlan_name'))]",
"function_ResourceId": "[concat('/subscriptions/', subscription().subscriptionId, '/resourceGroups/', parameters('resourceGroupName'), '/providers/Microsoft.Web/sites/', parameters('resourceName'))]"
},
"resources": [
{
"location": "[parameters('resourceLocation')]",
"name": "[parameters('resourceName')]",
"type": "Microsoft.Web/sites",
"apiVersion": "2015-08-01",
"tags": {
"[concat('hidden-related:', variables('appServicePlan_ResourceId'))]": "empty"
},
"dependsOn": [
"[variables('appServicePlan_ResourceId')]",
"[variables('storage_ResourceId')]"
],
"kind": "functionapp",
"properties": {
"name": "[parameters('resourceName')]",
"kind": "functionapp",
"httpsOnly": true,
"reserved": false,
"serverFarmId": "[variables('appServicePlan_ResourceId')]",
"siteConfig": {
"alwaysOn": true,
"linuxFxVersion": "dotnet|3.1"
}
},
"identity": {
"type": "SystemAssigned"
},
"resources": [
{
"name": "appsettings",
"type": "config",
"apiVersion": "2015-08-01",
"dependsOn": [
"[variables('function_ResourceId')]"
],
"properties": {
"AzureWebJobsStorage": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storage_name'), ';AccountKey=', listKeys(variables('storage_ResourceId'), '2017-10-01').keys[0].value, ';EndpointSuffix=', 'core.windows.net')]",
"FUNCTIONS_EXTENSION_VERSION": "~3",
"FUNCTIONS_WORKER_RUNTIME": "dotnet"
}
}
]
},
{
"location": "[parameters('resourceGroupLocation')]",
"name": "[variables('storage_name')]",
"type": "Microsoft.Storage/storageAccounts",
"apiVersion": "2017-10-01",
"tags": {
"[concat('hidden-related:', concat('/providers/Microsoft.Web/sites/', parameters('resourceName')))]": "empty"
},
"properties": {
"supportsHttpsTrafficOnly": true
},
"sku": {
"name": "Standard_LRS"
},
"kind": "Storage"
},
{
"location": "[parameters('resourceGroupLocation')]",
"name": "[variables('appServicePlan_name')]",
"type": "Microsoft.Web/serverFarms",
"apiVersion": "2015-02-01",
"kind": "linux",
"properties": {
"name": "[variables('appServicePlan_name')]",
"sku": "Standard",
"workerSizeId": "0",
"reserved": true
}
}
]
}
}
}
]
}

View File

@@ -1,33 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk.Web"> <Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6</TargetFramework> <TargetFramework>net6</TargetFramework>
<RuntimeIdentifiers>win10-x64</RuntimeIdentifiers> <!--<RuntimeIdentifiers>win10-x64</RuntimeIdentifiers>-->
<StartupObject>WireMock.Net.WebApplication.Program</StartupObject> <StartupObject>WireMock.Net.WebApplication.Program</StartupObject>
<AssemblyName>WireMock.Net.WebApplication</AssemblyName> <AssemblyName>WireMock.Net.WebApplication</AssemblyName>
<RootNamespace>WireMock.Net.WebApplication</RootNamespace> <RootNamespace>WireMock.Net.WebApplication</RootNamespace>
<UserSecretsId>efcf4a18-fd7c-4622-1111-336d65290599</UserSecretsId> <UserSecretsId>efcf4a18-fd7c-4622-1111-336d65290599</UserSecretsId>
<AspNetCoreHostingModel>OutOfProcess</AspNetCoreHostingModel> <AspNetCoreHostingModel>OutOfProcess</AspNetCoreHostingModel>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Content Remove="Properties\1.launchSettings.json" /> <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="6.0.0" />
</ItemGroup> <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="6.0.0" /> <ProjectReference Include="..\..\src\WireMock.Net.Abstractions\WireMock.Net.Abstractions.csproj" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> <ProjectReference Include="..\..\src\WireMock.Net\WireMock.Net.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\src\WireMock.Net.Abstractions\WireMock.Net.Abstractions.csproj" /> <Content Update="appsettings.json">
<ProjectReference Include="..\..\src\WireMock.Net\WireMock.Net.csproj" /> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</ItemGroup> </Content>
</ItemGroup>
<ItemGroup>
<Content Update="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project> </Project>

View File

@@ -15,15 +15,7 @@
"WireMockServerSettings": { "WireMockServerSettings": {
"StartAdminInterface": true, "StartAdminInterface": true,
"Urls": [ "Urls": [
"https://localhost:8081/" "http://localhost"
], ]
"AllowPartialMapping": false,
"HandleRequestsSynchronously": true,
"ThrowExceptionWhenMatcherFails": true,
"ProxyAndRecordSettings": {
"Url": "http://postman-echo.com/post",
"SaveMapping": true,
"SaveMappingToFile": true
}
} }
} }

View File

@@ -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

View File

@@ -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}");
}
}

View File

@@ -0,0 +1,9 @@
{
"profiles": {
"WireMockAzureQueueExample": {
"commandName": "Project",
"commandLineArgs": "--port 7290",
"launchBrowser": false
}
}
}

View File

@@ -0,0 +1,14 @@
{
"dependencies": {
"appInsights1": {
"type": "appInsights"
},
"secrets1": {
"type": "secrets"
},
"storage1": {
"type": "storage",
"connectionId": "AzureWebJobsStorage"
}
}
}

View File

@@ -0,0 +1,16 @@
{
"dependencies": {
"appInsights1": {
"type": "appInsights.sdk"
},
"secrets1": {
"type": "secrets.user"
},
"storage1": {
"secretStore": null,
"resourceId": null,
"type": "storage.emulator",
"connectionId": "AzureWebJobsStorage"
}
}
}

View File

@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<AzureFunctionsVersion>v4</AzureFunctionsVersion>
<UserSecretsId>bb7d8355-68c4-4f81-8c2d-6cdd80cd7602</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.Storage.Blobs" Version="12.14.1" />
<PackageReference Include="Azure.Storage.Files.Shares" Version="12.12.1" />
<PackageReference Include="Azure.Storage.Queues" Version="12.12.0" />
<PackageReference Include="Microsoft.Azure.WebJobs.Extensions.Storage" Version="4.0.5" />
<PackageReference Include="Microsoft.Extensions.Azure" Version="1.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="6.0.1" />
<PackageReference Include="Microsoft.NET.Sdk.Functions" Version="4.1.3" />
</ItemGroup>
<ItemGroup>
<None Update="host.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="local.settings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
</None>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,11 @@
{
"version": "2.0",
"logging": {
"applicationInsights": {
"samplingSettings": {
"isEnabled": true,
"excludedTypes": "Request"
}
}
}
}

View File

@@ -0,0 +1,7 @@
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"FUNCTIONS_WORKER_RUNTIME": "dotnet"
}
}

View File

@@ -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();
}
}

View File

@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\WireMock.Net.Abstractions\WireMock.Net.Abstractions.csproj" />
<ProjectReference Include="..\..\src\WireMock.Net\WireMock.Net.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="__admin\mappings\*.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

View File

@@ -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
}
}

View File

@@ -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": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?><QueueMessagesList/>",
"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
}
}

View File

@@ -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": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?><QueueMessagesList><QueueMessage><MessageId>41b2aadc-d6ea-4c3c-ae20-2ae72eb08d88</MessageId><InsertionTime>Sat, 29 Oct 2022 07:11:40 GMT</InsertionTime><ExpirationTime>Sat, 31 Dec 2022 07:11:40 GMT</ExpirationTime><PopReceipt>MjlPY3QyMDIyMDc6MTE6NDAyMjU2</PopReceipt><TimeNextVisible>Sat, 29 Oct 2022 07:21:40 GMT</TimeNextVisible><DequeueCount>1</DequeueCount><MessageText>c3RlZg==</MessageText></QueueMessage></QueueMessagesList>",
"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
}
}

View File

@@ -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
}
}

View File

@@ -1,67 +1,71 @@
namespace WireMock.Admin.Settings namespace WireMock.Admin.Settings;
[FluentBuilder.AutoGenerateBuilder]
public class ProxyAndRecordSettingsModel
{ {
[FluentBuilder.AutoGenerateBuilder] /// <summary>
public class ProxyAndRecordSettingsModel /// The clientCertificate thumbprint or subject name fragment to use.
{ /// Example thumbprint : "D2DBF135A8D06ACCD0E1FAD9BFB28678DF7A9818". Example subject name: "www.google.com""
/// <summary> /// </summary>
/// The clientCertificate thumbprint or subject name fragment to use. public string ClientX509Certificate2ThumbprintOrSubjectName { get; set; }
/// Example thumbprint : "D2DBF135A8D06ACCD0E1FAD9BFB28678DF7A9818". Example subject name: "www.google.com""
/// </summary>
public string ClientX509Certificate2ThumbprintOrSubjectName { get; set; }
/// <summary> /// <summary>
/// Defines the WebProxySettings. /// Defines the WebProxySettings.
/// </summary> /// </summary>
public WebProxySettingsModel WebProxySettings { get; set; } public WebProxySettingsModel WebProxySettings { get; set; }
/// <summary> /// <summary>
/// Proxy requests should follow redirection (30x). /// Proxy requests should follow redirection (30x).
/// </summary> /// </summary>
public bool? AllowAutoRedirect { get; set; } public bool? AllowAutoRedirect { get; set; }
/// <summary> /// <summary>
/// The URL to proxy. /// The URL to proxy.
/// </summary> /// </summary>
public string Url { get; set; } public string Url { get; set; }
/// <summary> /// <summary>
/// Save the mapping for each request/response to the internal Mappings. /// Save the mapping for each request/response to the internal Mappings.
/// </summary> /// </summary>
public bool SaveMapping { get; set; } public bool SaveMapping { get; set; }
/// <summary> /// <summary>
/// Save the mapping for each request/response also to a file. (Note that SaveMapping must also be set to true.) /// Save the mapping for each request/response also to a file. (Note that SaveMapping must also be set to true.)
/// </summary> /// </summary>
public bool SaveMappingToFile { get; set; } public bool SaveMappingToFile { get; set; }
/// <summary> /// <summary>
/// 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.) /// 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. /// The pattern can contain a single value like "200", but also ranges like "2xx", "100,300,600" or "100-299,6xx" are supported.
/// </summary> /// </summary>
public string SaveMappingForStatusCodePattern { get; set; } = "*"; public string SaveMappingForStatusCodePattern { get; set; } = "*";
/// <summary> /// <summary>
/// Defines a list from headers which will be excluded from the saved mappings. /// Defines a list from headers which will be excluded from the saved mappings.
/// </summary> /// </summary>
public string[] ExcludedHeaders { get; set; } public string[] ExcludedHeaders { get; set; }
/// <summary> /// <summary>
/// Defines a list of cookies which will be excluded from the saved mappings. /// Defines a list of cookies which will be excluded from the saved mappings.
/// </summary> /// </summary>
public string[] ExcludedCookies { get; set; } public string[] ExcludedCookies { get; set; }
/// <summary> /// <summary>
/// Prefer the Proxy Mapping over the saved Mapping (in case SaveMapping is set to <c>true</c>). /// Prefer the Proxy Mapping over the saved Mapping (in case SaveMapping is set to <c>true</c>).
/// </summary> /// </summary>
// public bool PreferProxyMapping { get; set; } // public bool PreferProxyMapping { get; set; }
/// <summary> /// <summary>
/// When SaveMapping is set to <c>true</c>, this setting can be used to control the behavior of the generated request matchers for the new mapping. /// When SaveMapping is set to <c>true</c>, this setting can be used to control the behavior of the generated request matchers for the new mapping.
/// - <c>false</c>, the default matchers will be used. /// - <c>false</c>, the default matchers will be used.
/// - <c>true</c>, the defined mappings in the request wil be used for the new mapping. /// - <c>true</c>, the defined mappings in the request wil be used for the new mapping.
/// ///
/// Default value is false. /// Default value is false.
/// </summary> /// </summary>
public bool UseDefinedRequestMatchers { get; set; } public bool UseDefinedRequestMatchers { get; set; }
}
/// <summary>
/// Append an unique GUID to the filename from the saved mapping file.
/// </summary>
public bool AppendGuidToSavedMappingFile { get; set; }
} }

View File

@@ -1,82 +1,99 @@
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using WireMock.Handlers; using WireMock.Handlers;
using WireMock.Types;
namespace WireMock.Admin.Settings namespace WireMock.Admin.Settings;
/// <summary>
/// Settings
/// </summary>
[FluentBuilder.AutoGenerateBuilder]
public class SettingsModel
{ {
/// <summary> /// <summary>
/// Settings /// Gets or sets the global delay in milliseconds.
/// </summary> /// </summary>
[FluentBuilder.AutoGenerateBuilder] public int? GlobalProcessingDelay { get; set; }
public class SettingsModel
{
/// <summary>
/// Gets or sets the global delay in milliseconds.
/// </summary>
public int? GlobalProcessingDelay { get; set; }
/// <summary> /// <summary>
/// Gets or sets if partial mapping is allowed. /// Gets or sets if partial mapping is allowed.
/// </summary> /// </summary>
public bool? AllowPartialMapping { get; set; } public bool? AllowPartialMapping { get; set; }
/// <summary> /// <summary>
/// Gets or sets the RequestLog expiration in hours /// Gets or sets the RequestLog expiration in hours
/// </summary> /// </summary>
public int? RequestLogExpirationDuration { get; set; } public int? RequestLogExpirationDuration { get; set; }
/// <summary> /// <summary>
/// Gets or sets the MaxRequestLog count. /// Gets or sets the MaxRequestLog count.
/// </summary> /// </summary>
public int? MaxRequestLogCount { get; set; } public int? MaxRequestLogCount { get; set; }
/// <summary> /// <summary>
/// Allow a Body for all HTTP Methods. (default set to false). /// Allow a Body for all HTTP Methods. (default set to false).
/// </summary> /// </summary>
public bool? AllowBodyForAllHttpMethods { get; set; } public bool? AllowBodyForAllHttpMethods { get; set; }
/// <summary> /// <summary>
/// Handle all requests synchronously. (default set to false). /// Handle all requests synchronously. (default set to false).
/// </summary> /// </summary>
public bool? HandleRequestsSynchronously { get; set; } public bool? HandleRequestsSynchronously { get; set; }
/// <summary> /// <summary>
/// Throw an exception when the Matcher fails because of invalid input. (default set to false). /// Throw an exception when the Matcher fails because of invalid input. (default set to false).
/// </summary> /// </summary>
public bool? ThrowExceptionWhenMatcherFails { get; set; } public bool? ThrowExceptionWhenMatcherFails { get; set; }
/// <summary> /// <summary>
/// Use the RegexExtended instead of the default <see cref="Regex"/>. (default set to true). /// Use the RegexExtended instead of the default <see cref="Regex"/>. (default set to true).
/// </summary> /// </summary>
public bool? UseRegexExtended { get; set; } public bool? UseRegexExtended { get; set; }
/// <summary> /// <summary>
/// Save unmatched requests to a file using the <see cref="IFileSystemHandler"/>. (default set to false). /// Save unmatched requests to a file using the <see cref="IFileSystemHandler"/>. (default set to false).
/// </summary> /// </summary>
public bool? SaveUnmatchedRequests { get; set; } public bool? SaveUnmatchedRequests { get; set; }
/// <summary> /// <summary>
/// Gets or sets if the static mappings should be read at startup. /// Gets or sets if the static mappings should be read at startup.
/// </summary> /// </summary>
public bool? ReadStaticMappings { get; set; } public bool? ReadStaticMappings { get; set; }
/// <summary> /// <summary>
/// Watch the static mapping files + folder for changes when running. /// Watch the static mapping files + folder for changes when running.
/// </summary> /// </summary>
public bool? WatchStaticMappings { get; set; } public bool? WatchStaticMappings { get; set; }
/// <summary> /// <summary>
/// A value indicating whether subdirectories within the static mappings path should be monitored. /// A value indicating whether subdirectories within the static mappings path should be monitored.
/// </summary> /// </summary>
public bool? WatchStaticMappingsInSubdirectories { get; set; } public bool? WatchStaticMappingsInSubdirectories { get; set; }
/// <summary> /// <summary>
/// Policies to use when using CORS. By default CORS is disabled. [Optional] /// Policies to use when using CORS. By default CORS is disabled. [Optional]
/// </summary> /// </summary>
public string? CorsPolicyOptions { get; set; } public string? CorsPolicyOptions { get; set; }
/// <summary> /// <summary>
/// The proxy and record settings. /// The proxy and record settings.
/// </summary> /// </summary>
public ProxyAndRecordSettingsModel? ProxyAndRecordSettings { get; set; } public ProxyAndRecordSettingsModel? ProxyAndRecordSettings { get; set; }
}
/// <summary>
/// Defines on which scheme (http/https) to host. (This overrides the <c>UseSSL</c> value).
/// </summary>
public HostingScheme? HostingScheme { get; set; }
/// <summary>
/// Don't save the response-string in the LogEntry when WithBody(Func{IRequestMessage, string}) or WithBody(Func{IRequestMessage, Task{string}}) is used. (default set to false).
/// </summary>
public bool? DoNotSaveDynamicResponseInLogEntry { get; set; }
/// <summary>
/// See <seealso cref="QueryParameterMultipleValueSupport"/>.
///
/// Default value = "All".
/// </summary>
public QueryParameterMultipleValueSupport? QueryParameterMultipleValueSupport { get; set; }
} }

View File

@@ -1,9 +1,9 @@
namespace WireMock.Http; namespace WireMock.Constants;
/// <summary> /// <summary>
/// https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods /// https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods
/// </summary> /// </summary>
internal static class HttpRequestMethods public static class HttpRequestMethod
{ {
public const string CONNECT = "CONNECT"; public const string CONNECT = "CONNECT";
public const string DELETE = "DELETE"; public const string DELETE = "DELETE";

View File

@@ -28,7 +28,7 @@ public interface IRequestMessage
/// <summary> /// <summary>
/// The ProxyUrl (if a proxy is used). /// The ProxyUrl (if a proxy is used).
/// </summary> /// </summary>
string ProxyUrl { get; set; } string? ProxyUrl { get; set; }
/// <summary> /// <summary>
/// Gets the DateTime. /// Gets the DateTime.

View File

@@ -1,6 +1,4 @@
using System.Diagnostics.CodeAnalysis;
using System.Text; using System.Text;
using JetBrains.Annotations;
using WireMock.Types; using WireMock.Types;
namespace WireMock.Util; namespace WireMock.Util;
@@ -59,4 +57,9 @@ public interface IBodyData
/// The body encoding. /// The body encoding.
/// </summary> /// </summary>
Encoding? Encoding { get; set; } Encoding? Encoding { get; set; }
/// <summary>
/// Defines if this BodyData is the result of a dynamically created response-string. (
/// </summary>
public string? IsFuncUsed { get; set; }
} }

View File

@@ -1,10 +1,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Specialized; using System.Collections.Specialized;
using JetBrains.Annotations;
using WireMock.Admin.Mappings; using WireMock.Admin.Mappings;
using WireMock.Logging; using WireMock.Logging;
using WireMock.Matchers.Request;
namespace WireMock.Server namespace WireMock.Server
{ {
@@ -144,6 +142,11 @@ namespace WireMock.Server
/// </summary> /// </summary>
void ResetScenarios(); void ResetScenarios();
/// <summary>
/// Resets a specific Scenario by the name.
/// </summary>
bool ResetScenario(string name);
/// <summary> /// <summary>
/// Resets the LogEntries. /// Resets the LogEntries.
/// </summary> /// </summary>

View File

@@ -0,0 +1,28 @@
using System;
namespace WireMock.Types;
[Flags]
public enum QueryParameterMultipleValueSupport
{
// Support none
None = 0x0,
// Support "&" as multi-value-separator --> "key=value&key=anotherValue"
Ampersand = 0x1,
// Support ";" as multi-value-separator --> "key=value;key=anotherValue"
SemiColon = 0x2,
// Support "," as multi-value-separator --> "key=1,2"
Comma = 0x4,
// Support "&" and ";" as multi-value-separator --> "key=value&key=anotherValue" and also "key=value;key=anotherValue"
AmpersandAndSemiColon = Ampersand | SemiColon,
// Support "&" and ";" as multi-value-separator
NoComma = AmpersandAndSemiColon,
// Support all multi-value-separators ("&" and ";" and ",")
All = Ampersand | SemiColon | Comma
}

View File

@@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using FluentAssertions; using FluentAssertions;
using FluentAssertions.Execution; using FluentAssertions.Execution;
using WireMock.Constants;
using WireMock.Server; using WireMock.Server;
using WireMock.Types; using WireMock.Types;
@@ -12,9 +13,10 @@ namespace WireMock.FluentAssertions;
public class WireMockAssertions public class WireMockAssertions
{ {
private const string Any = "*";
private readonly int? _callsCount; private readonly int? _callsCount;
private IReadOnlyList<IRequestMessage> _requestMessages; private IReadOnlyList<IRequestMessage> _requestMessages;
private IReadOnlyList<KeyValuePair<string, WireMockList<string>>> _headers; private readonly IReadOnlyList<KeyValuePair<string, WireMockList<string>>> _headers;
public WireMockAssertions(IWireMockServer subject, int? callsCount) public WireMockAssertions(IWireMockServer subject, int? callsCount)
{ {
@@ -27,12 +29,13 @@ public class WireMockAssertions
public AndWhichConstraint<WireMockAssertions, string> AtAbsoluteUrl(string absoluteUrl, string because = "", params object[] becauseArgs) public AndWhichConstraint<WireMockAssertions, string> AtAbsoluteUrl(string absoluteUrl, string because = "", params object[] becauseArgs)
{ {
Func<IRequestMessage, bool> predicate = request => string.Equals(request.AbsoluteUrl, absoluteUrl, StringComparison.OrdinalIgnoreCase); Func<IRequestMessage, bool> predicate = request => string.Equals(request.AbsoluteUrl, absoluteUrl, StringComparison.OrdinalIgnoreCase);
var (filter, condition) = BuildFilterAndCondition(predicate); var (filter, condition) = BuildFilterAndCondition(predicate);
Execute.Assertion Execute.Assertion
.BecauseOf(because, becauseArgs) .BecauseOf(because, becauseArgs)
.Given(() => _requestMessages) .Given(() => _requestMessages)
.ForCondition(requests => requests.Any()) .ForCondition(requests => _callsCount == 0 || requests.Any())
.FailWith( .FailWith(
"Expected {context:wiremockserver} to have been called at address matching the absolute url {0}{reason}, but no calls were made.", "Expected {context:wiremockserver} to have been called at address matching the absolute url {0}{reason}, but no calls were made.",
absoluteUrl absoluteUrl
@@ -53,15 +56,17 @@ public class WireMockAssertions
public AndWhichConstraint<WireMockAssertions, string> AtUrl(string url, string because = "", params object[] becauseArgs) public AndWhichConstraint<WireMockAssertions, string> AtUrl(string url, string because = "", params object[] becauseArgs)
{ {
Func<IRequestMessage, bool> predicate = request => string.Equals(request.Url, url, StringComparison.OrdinalIgnoreCase); Func<IRequestMessage, bool> predicate = request => string.Equals(request.Url, url, StringComparison.OrdinalIgnoreCase);
var (filter, condition) = BuildFilterAndCondition(predicate); var (filter, condition) = BuildFilterAndCondition(predicate);
Execute.Assertion Execute.Assertion
.BecauseOf(because, becauseArgs) .BecauseOf(because, becauseArgs)
.Given(() => _requestMessages) .Given(() => _requestMessages)
.ForCondition(requests => requests.Any()) .ForCondition(requests => _callsCount == 0 || requests.Any())
.FailWith( .FailWith(
"Expected {context:wiremockserver} to have been called at address matching the url {0}{reason}, but no calls were made.", "Expected {context:wiremockserver} to have been called at address matching the url {0}{reason}, but no calls were made.",
url) url
)
.Then .Then
.ForCondition(condition) .ForCondition(condition)
.FailWith( .FailWith(
@@ -79,12 +84,13 @@ public class WireMockAssertions
public AndWhichConstraint<WireMockAssertions, string> WithProxyUrl(string proxyUrl, string because = "", params object[] becauseArgs) public AndWhichConstraint<WireMockAssertions, string> WithProxyUrl(string proxyUrl, string because = "", params object[] becauseArgs)
{ {
Func<IRequestMessage, bool> predicate = request => string.Equals(request.ProxyUrl, proxyUrl, StringComparison.OrdinalIgnoreCase); Func<IRequestMessage, bool> predicate = request => string.Equals(request.ProxyUrl, proxyUrl, StringComparison.OrdinalIgnoreCase);
var (filter, condition) = BuildFilterAndCondition(predicate); var (filter, condition) = BuildFilterAndCondition(predicate);
Execute.Assertion Execute.Assertion
.BecauseOf(because, becauseArgs) .BecauseOf(because, becauseArgs)
.Given(() => _requestMessages) .Given(() => _requestMessages)
.ForCondition(requests => requests.Any()) .ForCondition(requests => _callsCount == 0 || requests.Any())
.FailWith( .FailWith(
"Expected {context:wiremockserver} to have been called with proxy url {0}{reason}, but no calls were made.", "Expected {context:wiremockserver} to have been called with proxy url {0}{reason}, but no calls were made.",
proxyUrl proxyUrl
@@ -94,8 +100,7 @@ public class WireMockAssertions
.FailWith( .FailWith(
"Expected {context:wiremockserver} to have been called with proxy url {0}{reason}, but didn't find it among the calls with {1}.", "Expected {context:wiremockserver} to have been called with proxy url {0}{reason}, but didn't find it among the calls with {1}.",
_ => proxyUrl, _ => proxyUrl,
requests => requests requests => requests.Select(request => request.ProxyUrl)
.Select(request => request.ProxyUrl)
); );
_requestMessages = filter(_requestMessages).ToList(); _requestMessages = filter(_requestMessages).ToList();
@@ -107,23 +112,23 @@ public class WireMockAssertions
public AndWhichConstraint<WireMockAssertions, string> FromClientIP(string clientIP, string because = "", params object[] becauseArgs) public AndWhichConstraint<WireMockAssertions, string> FromClientIP(string clientIP, string because = "", params object[] becauseArgs)
{ {
Func<IRequestMessage, bool> predicate = request => string.Equals(request.ClientIP, clientIP, StringComparison.OrdinalIgnoreCase); Func<IRequestMessage, bool> predicate = request => string.Equals(request.ClientIP, clientIP, StringComparison.OrdinalIgnoreCase);
var (filter, condition) = BuildFilterAndCondition(predicate); var (filter, condition) = BuildFilterAndCondition(predicate);
Execute.Assertion Execute.Assertion
.BecauseOf(because, becauseArgs) .BecauseOf(because, becauseArgs)
.Given(() => _requestMessages) .Given(() => _requestMessages)
.ForCondition(requests => requests.Any()) .ForCondition(requests => _callsCount == 0 || requests.Any())
.FailWith( .FailWith(
"Expected {context:wiremockserver} to have been called from client IP {0}{reason}, but no calls were made.", "Expected {context:wiremockserver} to have been called from client IP {0}{reason}, but no calls were made.",
clientIP) clientIP
.Then
.ForCondition(requests =>
(_callsCount == null && requests.Any(req => req.ClientIP == clientIP)) ||
(_callsCount == requests.Count(req => req.ClientIP == clientIP))
) )
.Then
.ForCondition(condition)
.FailWith( .FailWith(
"Expected {context:wiremockserver} to have been called from client IP {0}{reason}, but didn't find it among the calls from IP(s) {1}.", "Expected {context:wiremockserver} to have been called from client IP {0}{reason}, but didn't find it among the calls from IP(s) {1}.",
_ => clientIP, requests => requests.Select(request => request.ClientIP)); _ => clientIP, requests => requests.Select(request => request.ClientIP)
);
_requestMessages = filter(_requestMessages).ToList(); _requestMessages = filter(_requestMessages).ToList();
@@ -165,50 +170,57 @@ public class WireMockAssertions
[CustomAssertion] [CustomAssertion]
public AndConstraint<WireMockAssertions> UsingConnect(string because = "", params object[] becauseArgs) public AndConstraint<WireMockAssertions> UsingConnect(string because = "", params object[] becauseArgs)
=> UsingMethod("CONNECT", because, becauseArgs); => UsingMethod(HttpRequestMethod.CONNECT, because, becauseArgs);
[CustomAssertion] [CustomAssertion]
public AndConstraint<WireMockAssertions> UsingDelete(string because = "", params object[] becauseArgs) public AndConstraint<WireMockAssertions> UsingDelete(string because = "", params object[] becauseArgs)
=> UsingMethod("DELETE", because, becauseArgs); => UsingMethod(HttpRequestMethod.DELETE, because, becauseArgs);
[CustomAssertion] [CustomAssertion]
public AndConstraint<WireMockAssertions> UsingGet(string because = "", params object[] becauseArgs) public AndConstraint<WireMockAssertions> UsingGet(string because = "", params object[] becauseArgs)
=> UsingMethod("GET", because, becauseArgs); => UsingMethod(HttpRequestMethod.GET, because, becauseArgs);
[CustomAssertion] [CustomAssertion]
public AndConstraint<WireMockAssertions> UsingHead(string because = "", params object[] becauseArgs) public AndConstraint<WireMockAssertions> UsingHead(string because = "", params object[] becauseArgs)
=> UsingMethod("HEAD", because, becauseArgs); => UsingMethod(HttpRequestMethod.HEAD, because, becauseArgs);
[CustomAssertion] [CustomAssertion]
public AndConstraint<WireMockAssertions> UsingOptions(string because = "", params object[] becauseArgs) public AndConstraint<WireMockAssertions> UsingOptions(string because = "", params object[] becauseArgs)
=> UsingMethod("OPTIONS", because, becauseArgs); => UsingMethod(HttpRequestMethod.OPTIONS, because, becauseArgs);
[CustomAssertion] [CustomAssertion]
public AndConstraint<WireMockAssertions> UsingPost(string because = "", params object[] becauseArgs) public AndConstraint<WireMockAssertions> UsingPost(string because = "", params object[] becauseArgs)
=> UsingMethod("POST", because, becauseArgs); => UsingMethod(HttpRequestMethod.POST, because, becauseArgs);
[CustomAssertion] [CustomAssertion]
public AndConstraint<WireMockAssertions> UsingPatch(string because = "", params object[] becauseArgs) public AndConstraint<WireMockAssertions> UsingPatch(string because = "", params object[] becauseArgs)
=> UsingMethod("PATCH", because, becauseArgs); => UsingMethod(HttpRequestMethod.PATCH, because, becauseArgs);
[CustomAssertion] [CustomAssertion]
public AndConstraint<WireMockAssertions> UsingPut(string because = "", params object[] becauseArgs) public AndConstraint<WireMockAssertions> UsingPut(string because = "", params object[] becauseArgs)
=> UsingMethod("PUT", because, becauseArgs); => UsingMethod(HttpRequestMethod.PUT, because, becauseArgs);
[CustomAssertion] [CustomAssertion]
public AndConstraint<WireMockAssertions> UsingTrace(string because = "", params object[] becauseArgs) public AndConstraint<WireMockAssertions> UsingTrace(string because = "", params object[] becauseArgs)
=> UsingMethod("TRACE", because, becauseArgs); => UsingMethod(HttpRequestMethod.TRACE, because, becauseArgs);
[CustomAssertion]
public AndConstraint<WireMockAssertions> UsingAnyMethod(string because = "", params object[] becauseArgs)
=> UsingMethod(Any, because, becauseArgs);
[CustomAssertion] [CustomAssertion]
public AndConstraint<WireMockAssertions> UsingMethod(string method, string because = "", params object[] becauseArgs) public AndConstraint<WireMockAssertions> UsingMethod(string method, string because = "", params object[] becauseArgs)
{ {
Func<IRequestMessage, bool> predicate = request => string.Equals(request.Method, method, StringComparison.OrdinalIgnoreCase); var any = method == Any;
Func<IRequestMessage, bool> predicate = request => (any && !string.IsNullOrEmpty(request.Method)) ||
string.Equals(request.Method, method, StringComparison.OrdinalIgnoreCase);
var (filter, condition) = BuildFilterAndCondition(predicate); var (filter, condition) = BuildFilterAndCondition(predicate);
Execute.Assertion Execute.Assertion
.BecauseOf(because, becauseArgs) .BecauseOf(because, becauseArgs)
.Given(() => _requestMessages) .Given(() => _requestMessages)
.ForCondition(requests => requests.Any()) .ForCondition(requests => _callsCount == 0 || requests.Any())
.FailWith( .FailWith(
"Expected {context:wiremockserver} to have been called using method {0}{reason}, but no calls were made.", "Expected {context:wiremockserver} to have been called using method {0}{reason}, but no calls were made.",
method method
@@ -230,12 +242,6 @@ public class WireMockAssertions
{ {
Func<IReadOnlyList<IRequestMessage>, IReadOnlyList<IRequestMessage>> filter = requests => requests.Where(predicate).ToList(); Func<IReadOnlyList<IRequestMessage>, IReadOnlyList<IRequestMessage>> filter = requests => requests.Where(predicate).ToList();
return return (filter, requests => (_callsCount is null && filter(requests).Any()) || _callsCount == filter(requests).Count);
(
filter,
requests =>
(_callsCount == null && filter(_requestMessages).Any()) ||
(_callsCount == filter(_requestMessages).Count())
);
} }
} }

View File

@@ -1,21 +1,20 @@
#if NET46 || NETSTANDARD2_0 #if NET46 || NETSTANDARD2_0
using System.Collections.Generic; using System.Collections.Generic;
namespace WireMock.Net.OpenApiParser.Extensions namespace WireMock.Net.OpenApiParser.Extensions;
internal static class DictionaryExtensions
{ {
internal static class DictionaryExtensions public static bool TryAdd<TKey, TValue>(this Dictionary<TKey, TValue>? dictionary, TKey key, TValue value)
{ {
public static bool TryAdd<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, TKey key, TValue value) if (dictionary is null || dictionary.ContainsKey(key))
{ {
if (dictionary is null || dictionary.ContainsKey(key)) return false;
{
return false;
}
dictionary[key] = value;
return true;
} }
dictionary[key] = value;
return true;
} }
} }
#endif #endif

View File

@@ -1,92 +1,91 @@
using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Interfaces;
using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Models;
using WireMock.Net.OpenApiParser.Types; using WireMock.Net.OpenApiParser.Types;
namespace WireMock.Net.OpenApiParser.Extensions namespace WireMock.Net.OpenApiParser.Extensions;
internal static class OpenApiSchemaExtensions
{ {
internal static class OpenApiSchemaExtensions /// <summary>
/// https://stackoverflow.com/questions/48111459/how-to-define-a-property-that-can-be-string-or-null-in-openapi-swagger
/// </summary>
public static bool TryGetXNullable(this OpenApiSchema schema, out bool value)
{ {
/// <summary> value = false;
/// https://stackoverflow.com/questions/48111459/how-to-define-a-property-that-can-be-string-or-null-in-openapi-swagger
/// </summary> if (schema.Extensions.TryGetValue("x-nullable", out IOpenApiExtension e) && e is OpenApiBoolean openApiBoolean)
public static bool TryGetXNullable(this OpenApiSchema schema, out bool value)
{ {
value = false; value = openApiBoolean.Value;
return true;
if (schema.Extensions.TryGetValue("x-nullable", out IOpenApiExtension e) && e is OpenApiBoolean openApiBoolean)
{
value = openApiBoolean.Value;
return true;
}
return false;
} }
public static SchemaType GetSchemaType(this OpenApiSchema schema) return false;
}
public static SchemaType GetSchemaType(this OpenApiSchema? schema)
{
switch (schema?.Type)
{ {
switch (schema?.Type) case "object":
{ return SchemaType.Object;
case "object":
return SchemaType.Object;
case "array": case "array":
return SchemaType.Array; return SchemaType.Array;
case "integer": case "integer":
return SchemaType.Integer; return SchemaType.Integer;
case "number": case "number":
return SchemaType.Number; return SchemaType.Number;
case "boolean": case "boolean":
return SchemaType.Boolean; return SchemaType.Boolean;
case "string": case "string":
return SchemaType.String; return SchemaType.String;
case "file": case "file":
return SchemaType.File; return SchemaType.File;
default: default:
return SchemaType.Unknown; return SchemaType.Unknown;
}
} }
}
public static SchemaFormat GetSchemaFormat(this OpenApiSchema schema) public static SchemaFormat GetSchemaFormat(this OpenApiSchema? schema)
{
switch (schema?.Format)
{ {
switch (schema?.Format) case "float":
{ return SchemaFormat.Float;
case "float":
return SchemaFormat.Float;
case "double": case "double":
return SchemaFormat.Double; return SchemaFormat.Double;
case "int32": case "int32":
return SchemaFormat.Int32; return SchemaFormat.Int32;
case "int64": case "int64":
return SchemaFormat.Int64; return SchemaFormat.Int64;
case "date": case "date":
return SchemaFormat.Date; return SchemaFormat.Date;
case "date-time": case "date-time":
return SchemaFormat.DateTime; return SchemaFormat.DateTime;
case "password": case "password":
return SchemaFormat.Password; return SchemaFormat.Password;
case "byte": case "byte":
return SchemaFormat.Byte; return SchemaFormat.Byte;
case "binary": case "binary":
return SchemaFormat.Binary; return SchemaFormat.Binary;
default: default:
return SchemaFormat.Undefined; return SchemaFormat.Undefined;
}
} }
} }
} }

View File

@@ -1,97 +1,94 @@
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices.ComTypes;
using JetBrains.Annotations; using JetBrains.Annotations;
using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Readers; using Microsoft.OpenApi.Readers;
using SharpYaml.Model;
using Stef.Validation; using Stef.Validation;
using WireMock.Net.OpenApiParser.Settings; using WireMock.Net.OpenApiParser.Settings;
using WireMock.Server; using WireMock.Server;
namespace WireMock.Net.OpenApiParser.Extensions namespace WireMock.Net.OpenApiParser.Extensions;
/// <summary>
/// Some extension methods for <see cref="IWireMockServer"/>.
/// </summary>
public static class WireMockServerExtensions
{ {
/// <summary> /// <summary>
/// Some extension methods for <see cref="IWireMockServer"/>. /// Register the mappings via an OpenAPI (swagger) V2 or V3 file.
/// </summary> /// </summary>
public static class WireMockServerExtensions /// <param name="server">The WireMockServer instance</param>
/// <param name="path">Path containing OpenAPI file to parse and use the mappings.</param>
/// <param name="diagnostic">Returns diagnostic object containing errors detected during parsing</param>
[PublicAPI]
public static IWireMockServer WithMappingFromOpenApiFile(this IWireMockServer server, string path, out OpenApiDiagnostic diagnostic)
{ {
/// <summary> return WithMappingFromOpenApiFile(server, path, new WireMockOpenApiParserSettings(), out diagnostic);
/// Register the mappings via an OpenAPI (swagger) V2 or V3 file. }
/// </summary>
/// <param name="server">The WireMockServer instance</param>
/// <param name="path">Path containing OpenAPI file to parse and use the mappings.</param>
/// <param name="diagnostic">Returns diagnostic object containing errors detected during parsing</param>
[PublicAPI]
public static IWireMockServer WithMappingFromOpenApiFile(this IWireMockServer server, string path, out OpenApiDiagnostic diagnostic)
{
return WithMappingFromOpenApiFile(server, path, null, out diagnostic);
}
/// <summary> /// <summary>
/// Register the mappings via an OpenAPI (swagger) V2 or V3 file. /// Register the mappings via an OpenAPI (swagger) V2 or V3 file.
/// </summary> /// </summary>
/// <param name="server">The WireMockServer instance</param> /// <param name="server">The WireMockServer instance</param>
/// <param name="path">Path containing OpenAPI file to parse and use the mappings.</param> /// <param name="path">Path containing OpenAPI file to parse and use the mappings.</param>
/// <param name="diagnostic">Returns diagnostic object containing errors detected during parsing</param> /// <param name="diagnostic">Returns diagnostic object containing errors detected during parsing</param>
/// <param name="settings">Additional settings</param> /// <param name="settings">Additional settings</param>
[PublicAPI] [PublicAPI]
public static IWireMockServer WithMappingFromOpenApiFile(this IWireMockServer server, string path, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic) public static IWireMockServer WithMappingFromOpenApiFile(this IWireMockServer server, string path, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic)
{ {
Guard.NotNull(server, nameof(server)); Guard.NotNull(server, nameof(server));
Guard.NotNullOrEmpty(path, nameof(path)); Guard.NotNullOrEmpty(path, nameof(path));
var mappings = new WireMockOpenApiParser().FromFile(path, settings, out diagnostic); var mappings = new WireMockOpenApiParser().FromFile(path, settings, out diagnostic);
return server.WithMapping(mappings.ToArray()); return server.WithMapping(mappings.ToArray());
} }
/// <summary> /// <summary>
/// Register the mappings via an OpenAPI (swagger) V2 or V3 stream. /// Register the mappings via an OpenAPI (swagger) V2 or V3 stream.
/// </summary> /// </summary>
/// <param name="server">The WireMockServer instance</param> /// <param name="server">The WireMockServer instance</param>
/// <param name="stream">Stream containing OpenAPI description to parse and use the mappings.</param> /// <param name="stream">Stream containing OpenAPI description to parse and use the mappings.</param>
/// <param name="diagnostic">Returns diagnostic object containing errors detected during parsing</param> /// <param name="diagnostic">Returns diagnostic object containing errors detected during parsing</param>
[PublicAPI] [PublicAPI]
public static IWireMockServer WithMappingFromOpenApiStream(this IWireMockServer server, Stream stream, out OpenApiDiagnostic diagnostic) public static IWireMockServer WithMappingFromOpenApiStream(this IWireMockServer server, Stream stream, out OpenApiDiagnostic diagnostic)
{ {
return WithMappingFromOpenApiStream(server, stream, null, out diagnostic); return WithMappingFromOpenApiStream(server, stream, new WireMockOpenApiParserSettings(), out diagnostic);
} }
/// <summary> /// <summary>
/// Register the mappings via an OpenAPI (swagger) V2 or V3 stream. /// Register the mappings via an OpenAPI (swagger) V2 or V3 stream.
/// </summary> /// </summary>
/// <param name="server">The WireMockServer instance</param> /// <param name="server">The WireMockServer instance</param>
/// <param name="stream">Stream containing OpenAPI description to parse and use the mappings.</param> /// <param name="stream">Stream containing OpenAPI description to parse and use the mappings.</param>
/// <param name="settings">Additional settings</param> /// <param name="settings">Additional settings</param>
/// <param name="diagnostic">Returns diagnostic object containing errors detected during parsing</param> /// <param name="diagnostic">Returns diagnostic object containing errors detected during parsing</param>
[PublicAPI] [PublicAPI]
public static IWireMockServer WithMappingFromOpenApiStream(this IWireMockServer server, Stream stream, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic) public static IWireMockServer WithMappingFromOpenApiStream(this IWireMockServer server, Stream stream, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic)
{ {
Guard.NotNull(server, nameof(server)); Guard.NotNull(server);
Guard.NotNull(stream, nameof(stream)); Guard.NotNull(stream);
Guard.NotNull(settings, nameof(settings)); Guard.NotNull(settings);
var mappings = new WireMockOpenApiParser().FromStream(stream, settings, out diagnostic); var mappings = new WireMockOpenApiParser().FromStream(stream, settings, out diagnostic);
return server.WithMapping(mappings.ToArray()); return server.WithMapping(mappings.ToArray());
} }
/// <summary> /// <summary>
/// Register the mappings via an OpenAPI (swagger) V2 or V3 document. /// Register the mappings via an OpenAPI (swagger) V2 or V3 document.
/// </summary> /// </summary>
/// <param name="server">The WireMockServer instance</param> /// <param name="server">The WireMockServer instance</param>
/// <param name="document">The OpenAPI document to use as mappings.</param> /// <param name="document">The OpenAPI document to use as mappings.</param>
/// <param name="settings">Additional settings [optional]</param> /// <param name="settings">Additional settings [optional]</param>
[PublicAPI] [PublicAPI]
public static IWireMockServer WithMappingFromOpenApiDocument(this IWireMockServer server, OpenApiDocument document, WireMockOpenApiParserSettings settings = null) public static IWireMockServer WithMappingFromOpenApiDocument(this IWireMockServer server, OpenApiDocument document, WireMockOpenApiParserSettings settings)
{ {
Guard.NotNull(server, nameof(server)); Guard.NotNull(server);
Guard.NotNull(document, nameof(document)); Guard.NotNull(document);
var mappings = new WireMockOpenApiParser().FromDocument(document, settings); var mappings = new WireMockOpenApiParser().FromDocument(document, settings);
return server.WithMapping(mappings.ToArray()); return server.WithMapping(mappings.ToArray());
}
} }
} }

View File

@@ -1,4 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Readers; using Microsoft.OpenApi.Readers;
@@ -35,7 +35,7 @@ namespace WireMock.Net.OpenApiParser
/// <param name="document">The source OpenApiDocument</param> /// <param name="document">The source OpenApiDocument</param>
/// <param name="settings">Additional settings [optional]</param> /// <param name="settings">Additional settings [optional]</param>
/// <returns>MappingModel</returns> /// <returns>MappingModel</returns>
IEnumerable<MappingModel> FromDocument(OpenApiDocument document, WireMockOpenApiParserSettings settings = null); IEnumerable<MappingModel> FromDocument(OpenApiDocument document, WireMockOpenApiParserSettings? settings = null);
/// <summary> /// <summary>
/// Generate <see cref="IEnumerable{MappingModel}"/> from a <seealso cref="Stream"/>. /// Generate <see cref="IEnumerable{MappingModel}"/> from a <seealso cref="Stream"/>.

View File

@@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using Microsoft.OpenApi; using Microsoft.OpenApi;
@@ -15,371 +16,365 @@ using WireMock.Net.OpenApiParser.Settings;
using WireMock.Net.OpenApiParser.Types; using WireMock.Net.OpenApiParser.Types;
using WireMock.Net.OpenApiParser.Utils; using WireMock.Net.OpenApiParser.Utils;
namespace WireMock.Net.OpenApiParser.Mappers namespace WireMock.Net.OpenApiParser.Mappers;
internal class OpenApiPathsMapper
{ {
internal class OpenApiPathsMapper private const string HeaderContentType = "Content-Type";
private readonly WireMockOpenApiParserSettings _settings;
private readonly ExampleValueGenerator _exampleValueGenerator;
public OpenApiPathsMapper(WireMockOpenApiParserSettings settings)
{ {
private const string HeaderContentType = "Content-Type"; _settings = Guard.NotNull(settings);
_exampleValueGenerator = new ExampleValueGenerator(settings);
}
private readonly WireMockOpenApiParserSettings _settings; public IEnumerable<MappingModel> ToMappingModels(OpenApiPaths paths, IList<OpenApiServer> servers)
private readonly ExampleValueGenerator _exampleValueGenerator; {
return paths.Select(p => MapPath(p.Key, p.Value, servers)).SelectMany(x => x);
}
public OpenApiPathsMapper(WireMockOpenApiParserSettings settings) private IEnumerable<MappingModel> MapPaths(OpenApiPaths paths, IList<OpenApiServer> servers)
{
return paths.Select(p => MapPath(p.Key, p.Value, servers)).SelectMany(x => x);
}
private IEnumerable<MappingModel> MapPath(string path, OpenApiPathItem pathItem, IList<OpenApiServer> servers)
{
return pathItem.Operations.Select(o => MapOperationToMappingModel(path, o.Key.ToString().ToUpperInvariant(), o.Value, servers));
}
private MappingModel MapOperationToMappingModel(string path, string httpMethod, OpenApiOperation operation, IList<OpenApiServer> servers)
{
var queryParameters = operation.Parameters.Where(p => p.In == ParameterLocation.Query);
var pathParameters = operation.Parameters.Where(p => p.In == ParameterLocation.Path);
var headers = operation.Parameters.Where(p => p.In == ParameterLocation.Header);
var response = operation.Responses.FirstOrDefault();
TryGetContent(response.Value?.Content, out OpenApiMediaType? responseContent, out string? responseContentType);
var responseSchema = response.Value?.Content?.FirstOrDefault().Value?.Schema;
var responseExample = responseContent?.Example;
var responseSchemaExample = responseContent?.Schema?.Example;
var body = responseExample != null ? MapOpenApiAnyToJToken(responseExample) :
responseSchemaExample != null ? MapOpenApiAnyToJToken(responseSchemaExample) :
MapSchemaToObject(responseSchema);
var requestBodyModel = new BodyModel();
if (operation.RequestBody != null && operation.RequestBody.Content != null && operation.RequestBody.Required)
{ {
_settings = Guard.NotNull(settings, nameof(settings)); var request = operation.RequestBody.Content;
_exampleValueGenerator = new ExampleValueGenerator(settings); TryGetContent(request, out OpenApiMediaType? requestContent, out _);
var requestBodySchema = operation.RequestBody.Content.First().Value?.Schema;
var requestBodyExample = requestContent!.Example;
var requestBodySchemaExample = requestContent.Schema?.Example;
var requestBodyMapped = requestBodyExample != null ? MapOpenApiAnyToJToken(requestBodyExample) :
requestBodySchemaExample != null ? MapOpenApiAnyToJToken(requestBodySchemaExample) :
MapSchemaToObject(requestBodySchema);
requestBodyModel = MapRequestBody(requestBodyMapped);
} }
public IEnumerable<MappingModel> ToMappingModels(OpenApiPaths paths, IList<OpenApiServer> servers) if (!int.TryParse(response.Key, out var httpStatusCode))
{ {
return paths.Select(p => MapPath(p.Key, p.Value, servers)).SelectMany(x => x); httpStatusCode = 200;
} }
private IEnumerable<MappingModel> MapPaths(OpenApiPaths paths, IList<OpenApiServer> servers) return new MappingModel
{ {
return paths.Select(p => MapPath(p.Key, p.Value, servers)).SelectMany(x => x); Guid = Guid.NewGuid(),
} Request = new RequestModel
private IEnumerable<MappingModel> MapPath(string path, OpenApiPathItem pathItem, IList<OpenApiServer> servers)
{
return pathItem.Operations.Select(o => MapOperationToMappingModel(path, o.Key.ToString().ToUpperInvariant(), o.Value, servers));
}
private MappingModel MapOperationToMappingModel(string path, string httpMethod, OpenApiOperation operation, IList<OpenApiServer> servers)
{
var queryParameters = operation.Parameters.Where(p => p.In == ParameterLocation.Query);
var pathParameters = operation.Parameters.Where(p => p.In == ParameterLocation.Path);
var headers = operation.Parameters.Where(p => p.In == ParameterLocation.Header);
var response = operation.Responses.FirstOrDefault();
TryGetContent(response.Value?.Content, out OpenApiMediaType responseContent, out string responseContentType);
var responseSchema = response.Value?.Content?.FirstOrDefault().Value?.Schema;
var responseExample = responseContent?.Example;
var responseSchemaExample = responseContent?.Schema?.Example;
var body = responseExample != null ? MapOpenApiAnyToJToken(responseExample) :
responseSchemaExample != null ? MapOpenApiAnyToJToken(responseSchemaExample) :
MapSchemaToObject(responseSchema);
var requestBodyModel = new BodyModel();
if (operation.RequestBody != null && operation.RequestBody.Content != null && operation.RequestBody.Required)
{ {
var request = operation.RequestBody.Content; Methods = new[] { httpMethod },
TryGetContent(request, out OpenApiMediaType requestContent, out string requestContentType); Path = MapBasePath(servers) + MapPathWithParameters(path, pathParameters),
Params = MapQueryParameters(queryParameters),
var requestBodySchema = operation.RequestBody.Content.First().Value?.Schema; Headers = MapRequestHeaders(headers),
var requestBodyExample = requestContent.Example; Body = requestBodyModel
var requestBodySchemaExample = requestContent.Schema?.Example; },
Response = new ResponseModel
var requestBodyMapped = requestBodyExample != null ? MapOpenApiAnyToJToken(requestBodyExample) : {
requestBodySchemaExample != null ? MapOpenApiAnyToJToken(requestBodySchemaExample) : StatusCode = httpStatusCode,
MapSchemaToObject(requestBodySchema); Headers = MapHeaders(responseContentType, response.Value?.Headers),
BodyAsJson = body
requestBodyModel = MapRequestBody(requestBodyMapped);
} }
};
}
if (!int.TryParse(response.Key, out var httpStatusCode)) private BodyModel? MapRequestBody(object? requestBody)
{
if (requestBody == null)
{
return null;
}
return new BodyModel
{
Matcher = new MatcherModel
{ {
httpStatusCode = 200; Name = "JsonMatcher",
Pattern = JsonConvert.SerializeObject(requestBody, Formatting.Indented),
IgnoreCase = _settings.RequestBodyIgnoreCase
} }
};
}
return new MappingModel private bool TryGetContent(IDictionary<string, OpenApiMediaType>? contents, [NotNullWhen(true)] out OpenApiMediaType? openApiMediaType, [NotNullWhen(true)] out string? contentType)
{ {
Guid = Guid.NewGuid(), openApiMediaType = null;
Request = new RequestModel contentType = null;
if (contents == null || contents.Values.Count == 0)
{
return false;
}
if (contents.TryGetValue("application/json", out var content))
{
openApiMediaType = content;
contentType = "application/json";
}
else
{
var first = contents.FirstOrDefault();
openApiMediaType = first.Value;
contentType = first.Key;
}
return true;
}
private object? MapSchemaToObject(OpenApiSchema? schema, string? name = null)
{
if (schema == null)
{
return null;
}
switch (schema.GetSchemaType())
{
case SchemaType.Array:
var jArray = new JArray();
for (int i = 0; i < _settings.NumberOfArrayItems; i++)
{ {
Methods = new[] { httpMethod }, if (schema.Items.Properties.Count > 0)
Path = MapBasePath(servers) + MapPathWithParameters(path, pathParameters),
Params = MapQueryParameters(queryParameters),
Headers = MapRequestHeaders(headers),
Body = requestBodyModel
},
Response = new ResponseModel
{
StatusCode = httpStatusCode,
Headers = MapHeaders(responseContentType, response.Value?.Headers),
BodyAsJson = body
}
};
}
private BodyModel MapRequestBody(object requestBody)
{
if (requestBody == null)
{
return null;
}
return new BodyModel
{
Matcher = new MatcherModel
{
Name = "JsonMatcher",
Pattern = JsonConvert.SerializeObject(requestBody, Formatting.Indented),
IgnoreCase = _settings.RequestBodyIgnoreCase
}
};
}
private bool TryGetContent(IDictionary<string, OpenApiMediaType> contents, out OpenApiMediaType openApiMediaType, out string contentType)
{
openApiMediaType = null;
contentType = null;
if (contents == null || contents.Values.Count == 0)
{
return false;
}
if (contents.TryGetValue("application/json", out var content))
{
openApiMediaType = content;
contentType = "application/json";
}
else
{
var first = contents.FirstOrDefault();
openApiMediaType = first.Value;
contentType = first.Key;
}
return true;
}
private object MapSchemaToObject(OpenApiSchema schema, string name = null)
{
if (schema == null)
{
return null;
}
switch (schema.GetSchemaType())
{
case SchemaType.Array:
var jArray = new JArray();
for (int i = 0; i < _settings.NumberOfArrayItems; i++)
{ {
if (schema.Items.Properties.Count > 0) var arrayItem = new JObject();
foreach (var property in schema.Items.Properties)
{ {
var arrayItem = new JObject(); var objectValue = MapSchemaToObject(property.Value, property.Key);
foreach (var property in schema.Items.Properties) if (objectValue is JProperty jp)
{ {
var objectValue = MapSchemaToObject(property.Value, property.Key); arrayItem.Add(jp);
if (objectValue is JProperty jp)
{
arrayItem.Add(jp);
}
else
{
arrayItem.Add(new JProperty(property.Key, objectValue));
}
} }
else
jArray.Add(arrayItem);
}
else
{
jArray.Add(MapSchemaToObject(schema.Items, name));
}
}
if (schema.AllOf.Count > 0)
{
jArray.Add(MapSchemaAllOfToObject(schema));
}
return jArray;
case SchemaType.Boolean:
case SchemaType.Integer:
case SchemaType.Number:
case SchemaType.String:
return _exampleValueGenerator.GetExampleValue(schema);
case SchemaType.Object:
var propertyAsJObject = new JObject();
foreach (var schemaProperty in schema.Properties)
{
propertyAsJObject.Add(MapPropertyAsJObject(schemaProperty.Value, schemaProperty.Key));
}
if (schema.AllOf.Count > 0)
{
foreach (var property in schema.AllOf)
{
foreach (var item in property.Properties)
{ {
propertyAsJObject.Add(MapPropertyAsJObject(item.Value, item.Key)); arrayItem.Add(new JProperty(property.Key, objectValue));
} }
} }
jArray.Add(arrayItem);
}
else
{
jArray.Add(MapSchemaToObject(schema.Items, name));
} }
return name != null ? new JProperty(name, propertyAsJObject) : (JToken)propertyAsJObject;
default:
return null;
}
}
private JObject MapSchemaAllOfToObject(OpenApiSchema schema)
{
var arrayItem = new JObject();
foreach (var property in schema.AllOf)
{
foreach (var item in property.Properties)
{
arrayItem.Add(MapPropertyAsJObject(item.Value, item.Key));
} }
}
return arrayItem;
}
private object MapPropertyAsJObject(OpenApiSchema openApiSchema, string key) if (schema.AllOf.Count > 0)
{
if (openApiSchema.GetSchemaType() == SchemaType.Object || openApiSchema.GetSchemaType() == SchemaType.Array)
{
var mapped = MapSchemaToObject(openApiSchema, key);
if (mapped is JProperty jp)
{ {
return jp; jArray.Add(MapSchemaAllOfToObject(schema));
} }
else
return jArray;
case SchemaType.Boolean:
case SchemaType.Integer:
case SchemaType.Number:
case SchemaType.String:
return _exampleValueGenerator.GetExampleValue(schema);
case SchemaType.Object:
var propertyAsJObject = new JObject();
foreach (var schemaProperty in schema.Properties)
{ {
return new JProperty(key, mapped); propertyAsJObject.Add(MapPropertyAsJObject(schemaProperty.Value, schemaProperty.Key));
} }
}
else if (schema.AllOf.Count > 0)
{
foreach (var property in schema.AllOf)
{
foreach (var item in property.Properties)
{
propertyAsJObject.Add(MapPropertyAsJObject(item.Value, item.Key));
}
}
}
return name != null ? new JProperty(name, propertyAsJObject) : propertyAsJObject;
default:
return null;
}
}
private JObject MapSchemaAllOfToObject(OpenApiSchema schema)
{
var arrayItem = new JObject();
foreach (var property in schema.AllOf)
{
foreach (var item in property.Properties)
{ {
// bool propertyIsNullable = openApiSchema.Nullable || (openApiSchema.TryGetXNullable(out bool x) && x); arrayItem.Add(MapPropertyAsJObject(item.Value, item.Key));
return new JProperty(key, _exampleValueGenerator.GetExampleValue(openApiSchema));
} }
} }
return arrayItem;
}
private string MapPathWithParameters(string path, IEnumerable<OpenApiParameter> parameters) private object MapPropertyAsJObject(OpenApiSchema openApiSchema, string key)
{
if (openApiSchema.GetSchemaType() == SchemaType.Object || openApiSchema.GetSchemaType() == SchemaType.Array)
{ {
if (parameters == null) var mapped = MapSchemaToObject(openApiSchema, key);
if (mapped is JProperty jp)
{ {
return path; return jp;
} }
string newPath = path; return new JProperty(key, mapped);
foreach (var parameter in parameters)
{
var exampleMatcherModel = GetExampleMatcherModel(parameter.Schema, _settings.PathPatternToUse);
newPath = newPath.Replace($"{{{parameter.Name}}}", exampleMatcherModel.Pattern as string);
}
return newPath;
} }
private string MapBasePath(IList<OpenApiServer> servers) // bool propertyIsNullable = openApiSchema.Nullable || (openApiSchema.TryGetXNullable(out bool x) && x);
return new JProperty(key, _exampleValueGenerator.GetExampleValue(openApiSchema));
}
private string MapPathWithParameters(string path, IEnumerable<OpenApiParameter>? parameters)
{
if (parameters == null)
{ {
if (servers == null || servers.Count == 0) return path;
{ }
return string.Empty;
}
OpenApiServer server = servers.First(); string newPath = path;
if (Uri.TryCreate(server.Url, UriKind.RelativeOrAbsolute, out Uri uriResult)) foreach (var parameter in parameters)
{ {
return uriResult.IsAbsoluteUri ? uriResult.AbsolutePath : uriResult.ToString(); var exampleMatcherModel = GetExampleMatcherModel(parameter.Schema, _settings.PathPatternToUse);
} newPath = newPath.Replace($"{{{parameter.Name}}}", exampleMatcherModel.Pattern as string);
}
return newPath;
}
private string MapBasePath(IList<OpenApiServer>? servers)
{
if (servers == null || servers.Count == 0)
{
return string.Empty; return string.Empty;
} }
private JToken MapOpenApiAnyToJToken(IOpenApiAny any) OpenApiServer server = servers.First();
if (Uri.TryCreate(server.Url, UriKind.RelativeOrAbsolute, out Uri uriResult))
{ {
if (any == null) return uriResult.IsAbsoluteUri ? uriResult.AbsolutePath : uriResult.ToString();
{
return null;
}
using var outputString = new StringWriter();
var writer = new OpenApiJsonWriter(outputString);
any.Write(writer, OpenApiSpecVersion.OpenApi3_0);
if (any.AnyType == AnyType.Array)
{
return JArray.Parse(outputString.ToString());
}
else
{
return JObject.Parse(outputString.ToString());
}
} }
private IDictionary<string, object> MapHeaders(string responseContentType, IDictionary<string, OpenApiHeader> headers) return string.Empty;
}
private JToken? MapOpenApiAnyToJToken(IOpenApiAny? any)
{
if (any == null)
{ {
var mappedHeaders = headers.ToDictionary( return null;
item => item.Key,
item => GetExampleMatcherModel(null, _settings.HeaderPatternToUse).Pattern
);
if (!string.IsNullOrEmpty(responseContentType))
{
mappedHeaders.TryAdd(HeaderContentType, responseContentType);
}
return mappedHeaders.Keys.Any() ? mappedHeaders : null;
} }
private IList<ParamModel> MapQueryParameters(IEnumerable<OpenApiParameter> queryParameters) using var outputString = new StringWriter();
var writer = new OpenApiJsonWriter(outputString);
any.Write(writer, OpenApiSpecVersion.OpenApi3_0);
if (any.AnyType == AnyType.Array)
{ {
var list = queryParameters return JArray.Parse(outputString.ToString());
.Where(req => req.Required) }
.Select(qp => new ParamModel
return JObject.Parse(outputString.ToString());
}
private IDictionary<string, object?>? MapHeaders(string responseContentType, IDictionary<string, OpenApiHeader> headers)
{
var mappedHeaders = headers.ToDictionary(
item => item.Key,
_ => GetExampleMatcherModel(null, _settings.HeaderPatternToUse).Pattern
);
if (!string.IsNullOrEmpty(responseContentType))
{
mappedHeaders.TryAdd(HeaderContentType, responseContentType);
}
return mappedHeaders.Keys.Any() ? mappedHeaders : null;
}
private IList<ParamModel>? MapQueryParameters(IEnumerable<OpenApiParameter> queryParameters)
{
var list = queryParameters
.Where(req => req.Required)
.Select(qp => new ParamModel
{
Name = qp.Name,
IgnoreCase = _settings.QueryParameterPatternIgnoreCase,
Matchers = new[]
{ {
Name = qp.Name, GetExampleMatcherModel(qp.Schema, _settings.QueryParameterPatternToUse)
IgnoreCase = _settings.QueryParameterPatternIgnoreCase, }
Matchers = new[] })
{ .ToList();
GetExampleMatcherModel(qp.Schema, _settings.QueryParameterPatternToUse)
}
})
.ToList();
return list.Any() ? list : null; return list.Any() ? list : null;
} }
private IList<HeaderModel> MapRequestHeaders(IEnumerable<OpenApiParameter> headers) private IList<HeaderModel>? MapRequestHeaders(IEnumerable<OpenApiParameter> headers)
{ {
var list = headers var list = headers
.Where(req => req.Required) .Where(req => req.Required)
.Select(qp => new HeaderModel .Select(qp => new HeaderModel
{
Name = qp.Name,
IgnoreCase = _settings.HeaderPatternIgnoreCase,
Matchers = new[]
{ {
Name = qp.Name, GetExampleMatcherModel(qp.Schema, _settings.HeaderPatternToUse)
IgnoreCase = _settings.HeaderPatternIgnoreCase, }
Matchers = new[] })
{ .ToList();
GetExampleMatcherModel(qp.Schema, _settings.HeaderPatternToUse)
}
})
.ToList();
return list.Any() ? list : null; return list.Any() ? list : null;
} }
private MatcherModel GetExampleMatcherModel(OpenApiSchema schema, ExampleValueType type) private MatcherModel GetExampleMatcherModel(OpenApiSchema? schema, ExampleValueType type)
{
return type switch
{ {
return type switch ExampleValueType.Value => new MatcherModel { Name = "ExactMatcher", Pattern = GetExampleValueAsStringForSchemaType(schema), IgnoreCase = _settings.IgnoreCaseExampleValues },
{
ExampleValueType.Value => new MatcherModel { Name = "ExactMatcher", Pattern = GetExampleValueAsStringForSchemaType(schema), IgnoreCase = _settings.IgnoreCaseExampleValues },
_ => new MatcherModel { Name = "WildcardMatcher", Pattern = "*" } _ => new MatcherModel { Name = "WildcardMatcher", Pattern = "*" }
}; };
} }
private string GetExampleValueAsStringForSchemaType(OpenApiSchema schema) private string GetExampleValueAsStringForSchemaType(OpenApiSchema? schema)
{
var value = _exampleValueGenerator.GetExampleValue(schema);
return value switch
{ {
var value = _exampleValueGenerator.GetExampleValue(schema); string valueAsString => valueAsString,
return value switch _ => value.ToString(),
{ };
string valueAsString => valueAsString,
_ => value.ToString(),
};
}
} }
} }

View File

@@ -1,58 +1,60 @@
using System; using System;
using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Models;
namespace WireMock.Net.OpenApiParser.Settings namespace WireMock.Net.OpenApiParser.Settings;
/// <summary>
/// A interface defining the example values to use for the different types.
/// </summary>
public interface IWireMockOpenApiParserExampleValues
{ {
/// <summary> /// <summary>
/// A interface defining the example values to use for the different types. /// An example value for a Boolean.
/// </summary> /// </summary>
public interface IWireMockOpenApiParserExampleValues bool Boolean { get; set; }
{
/// <summary>
/// An example value for a Boolean.
/// </summary>
bool Boolean { get; set; }
/// <summary>
/// An example value for an Integer.
/// </summary>
int Integer { get; set; }
/// <summary>
/// An example value for a Float.
/// </summary>
float Float { get; set; }
/// <summary>
/// An example value for a Double.
/// </summary>
double Double { get; set; }
/// <summary> /// <summary>
/// An example value for a Date. /// An example value for an Integer.
/// </summary> /// </summary>
Func<DateTime> Date { get; set; } int Integer { get; set; }
/// <summary> /// <summary>
/// An example value for a DateTime. /// An example value for a Float.
/// </summary> /// </summary>
Func<DateTime> DateTime { get; set; } float Float { get; set; }
/// <summary> /// <summary>
/// An example value for Bytes. /// An example value for a Double.
/// </summary> /// </summary>
byte[] Bytes { get; set; } double Double { get; set; }
/// <summary> /// <summary>
/// An example value for a Object. /// An example value for a Date.
/// </summary> /// </summary>
object Object { get; set; } Func<DateTime> Date { get; set; }
/// <summary> /// <summary>
/// An example value for a String. /// An example value for a DateTime.
/// </summary> /// </summary>
string String { get; set; } Func<DateTime> DateTime { get; set; }
/// <summary> /// <summary>
/// OpenApi Schema to generate dynamic examples more accurate /// An example value for Bytes.
/// </summary> /// </summary>
OpenApiSchema Schema { get; set; } byte[] Bytes { get; set; }
}
/// <summary>
/// An example value for a Object.
/// </summary>
object Object { get; set; }
/// <summary>
/// An example value for a String.
/// </summary>
string String { get; set; }
/// <summary>
/// OpenApi Schema to generate dynamic examples more accurate
/// </summary>
OpenApiSchema? Schema { get; set; }
} }

View File

@@ -1,34 +1,42 @@
using System; using System;
using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Models;
using RandomDataGenerator.FieldOptions; using RandomDataGenerator.FieldOptions;
using RandomDataGenerator.Randomizers; using RandomDataGenerator.Randomizers;
namespace WireMock.Net.OpenApiParser.Settings namespace WireMock.Net.OpenApiParser.Settings;
/// <summary>
/// A class defining the random example values to use for the different types.
/// </summary>
public class WireMockOpenApiParserDynamicExampleValues : IWireMockOpenApiParserExampleValues
{ {
/// <summary> /// <inheritdoc />
/// A class defining the random example values to use for the different types. public virtual bool Boolean { get => RandomizerFactory.GetRandomizer(new FieldOptionsBoolean()).Generate() ?? true; set { } }
/// </summary>
public class WireMockOpenApiParserDynamicExampleValues : IWireMockOpenApiParserExampleValues /// <inheritdoc />
{ public virtual int Integer { get => RandomizerFactory.GetRandomizer(new FieldOptionsInteger()).Generate() ?? 42; set { } }
/// <inheritdoc />
public virtual bool Boolean { get { return RandomizerFactory.GetRandomizer(new FieldOptionsBoolean()).Generate() ?? true; } set { } } /// <inheritdoc />
/// <inheritdoc /> public virtual float Float { get => RandomizerFactory.GetRandomizer(new FieldOptionsFloat()).Generate() ?? 4.2f; set { } }
public virtual int Integer { get { return RandomizerFactory.GetRandomizer(new FieldOptionsInteger()).Generate() ?? 42; } set { } }
/// <inheritdoc /> /// <inheritdoc />
public virtual float Float { get { return RandomizerFactory.GetRandomizer(new FieldOptionsFloat()).Generate() ?? 4.2f; } set { } } public virtual double Double { get => RandomizerFactory.GetRandomizer(new FieldOptionsDouble()).Generate() ?? 4.2d; set { } }
/// <inheritdoc />
public virtual double Double { get { return RandomizerFactory.GetRandomizer(new FieldOptionsDouble()).Generate() ?? 4.2d; } set { } } /// <inheritdoc />
/// <inheritdoc /> public virtual Func<DateTime> Date { get { return () => RandomizerFactory.GetRandomizer(new FieldOptionsDateTime()).Generate() ?? System.DateTime.UtcNow.Date; } set { } }
public virtual Func<DateTime> Date { get { return () => RandomizerFactory.GetRandomizer(new FieldOptionsDateTime()).Generate() ?? System.DateTime.UtcNow.Date; } set { } }
/// <inheritdoc /> /// <inheritdoc />
public virtual Func<DateTime> DateTime { get { return () => RandomizerFactory.GetRandomizer(new FieldOptionsDateTime()).Generate() ?? System.DateTime.UtcNow; } set { } } public virtual Func<DateTime> DateTime { get { return () => RandomizerFactory.GetRandomizer(new FieldOptionsDateTime()).Generate() ?? System.DateTime.UtcNow; } set { } }
/// <inheritdoc />
public virtual byte[] Bytes { get { return RandomizerFactory.GetRandomizer(new FieldOptionsBytes()).Generate(); } set { } } /// <inheritdoc />
/// <inheritdoc /> public virtual byte[] Bytes { get => RandomizerFactory.GetRandomizer(new FieldOptionsBytes()).Generate(); set { } }
public virtual object Object { get; set; } = "example-object";
/// <inheritdoc /> /// <inheritdoc />
public virtual string String { get { return RandomizerFactory.GetRandomizer(new FieldOptionsTextRegex { Pattern = @"^[0-9]{2}[A-Z]{5}[0-9]{2}" }).Generate() ?? "example-string"; } set { } } public virtual object Object { get; set; } = "example-object";
/// <inheritdoc />
public virtual OpenApiSchema Schema { get; set; } /// <inheritdoc />
} public virtual string String { get => RandomizerFactory.GetRandomizer(new FieldOptionsTextRegex { Pattern = @"^[0-9]{2}[A-Z]{5}[0-9]{2}" }).Generate() ?? "example-string"; set { } }
/// <inheritdoc />
public virtual OpenApiSchema? Schema { get; set; }
} }

View File

@@ -1,32 +1,40 @@
using System; using System;
using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Models;
namespace WireMock.Net.OpenApiParser.Settings namespace WireMock.Net.OpenApiParser.Settings;
/// <summary>
/// A class defining the example values to use for the different types.
/// </summary>
public class WireMockOpenApiParserExampleValues : IWireMockOpenApiParserExampleValues
{ {
/// <summary> /// <inheritdoc />
/// A class defining the example values to use for the different types. public virtual bool Boolean { get; set; } = true;
/// </summary>
public class WireMockOpenApiParserExampleValues : IWireMockOpenApiParserExampleValues /// <inheritdoc />
{ public virtual int Integer { get; set; } = 42;
/// <inheritdoc />
public virtual bool Boolean { get; set; } = true; /// <inheritdoc />
/// <inheritdoc /> public virtual float Float { get; set; } = 4.2f;
public virtual int Integer { get; set; } = 42;
/// <inheritdoc /> /// <inheritdoc />
public virtual float Float { get; set; } = 4.2f; public virtual double Double { get; set; } = 4.2d;
/// <inheritdoc />
public virtual double Double { get; set; } = 4.2d; /// <inheritdoc />
/// <inheritdoc /> public virtual Func<DateTime> Date { get; set; } = () => System.DateTime.UtcNow.Date;
public virtual Func<DateTime> Date { get; set; } = () => System.DateTime.UtcNow.Date;
/// <inheritdoc /> /// <inheritdoc />
public virtual Func<DateTime> DateTime { get; set; } = () => System.DateTime.UtcNow; public virtual Func<DateTime> DateTime { get; set; } = () => System.DateTime.UtcNow;
/// <inheritdoc />
public virtual byte[] Bytes { get; set; } = { 48, 49, 50 }; /// <inheritdoc />
/// <inheritdoc /> public virtual byte[] Bytes { get; set; } = { 48, 49, 50 };
public virtual object Object { get; set; } = "example-object";
/// <inheritdoc /> /// <inheritdoc />
public virtual string String { get; set; } = "example-string"; public virtual object Object { get; set; } = "example-object";
/// <inheritdoc />
public virtual OpenApiSchema Schema { get; set; } = new OpenApiSchema(); /// <inheritdoc />
} public virtual string String { get; set; } = "example-string";
/// <inheritdoc />
public virtual OpenApiSchema? Schema { get; set; } = new OpenApiSchema();
} }

View File

@@ -1,64 +1,63 @@
using WireMock.Net.OpenApiParser.Types; using WireMock.Net.OpenApiParser.Types;
namespace WireMock.Net.OpenApiParser.Settings namespace WireMock.Net.OpenApiParser.Settings;
/// <summary>
/// The WireMockOpenApiParser Settings
/// </summary>
public class WireMockOpenApiParserSettings
{ {
/// <summary> /// <summary>
/// The WireMockOpenApiParser Settings /// The number of array items to generate (default is 3).
/// </summary> /// </summary>
public class WireMockOpenApiParserSettings public int NumberOfArrayItems { get; set; } = 3;
{
/// <summary>
/// The number of array items to generate (default is 3).
/// </summary>
public int NumberOfArrayItems { get; set; } = 3;
/// <summary> /// <summary>
/// The example value type to use when generating a Path /// The example value type to use when generating a Path
/// </summary> /// </summary>
public ExampleValueType PathPatternToUse { get; set; } = ExampleValueType.Value; public ExampleValueType PathPatternToUse { get; set; } = ExampleValueType.Value;
/// <summary> /// <summary>
/// The example value type to use when generating a Header /// The example value type to use when generating a Header
/// </summary> /// </summary>
public ExampleValueType HeaderPatternToUse { get; set; } = ExampleValueType.Value; public ExampleValueType HeaderPatternToUse { get; set; } = ExampleValueType.Value;
/// <summary> /// <summary>
/// The example value type to use when generating a Query Parameter /// The example value type to use when generating a Query Parameter
/// </summary> /// </summary>
public ExampleValueType QueryParameterPatternToUse { get; set; } = ExampleValueType.Value; public ExampleValueType QueryParameterPatternToUse { get; set; } = ExampleValueType.Value;
/// <summary> /// <summary>
/// The example values to use. /// The example values to use.
/// ///
/// Default implementations are: /// Default implementations are:
/// - <see cref="WireMockOpenApiParserExampleValues"/> /// - <see cref="WireMockOpenApiParserExampleValues"/>
/// - <see cref="WireMockOpenApiParserDynamicExampleValues"/> /// - <see cref="WireMockOpenApiParserDynamicExampleValues"/>
/// </summary> /// </summary>
public IWireMockOpenApiParserExampleValues ExampleValues { get; set; } public IWireMockOpenApiParserExampleValues? ExampleValues { get; set; }
/// <summary> /// <summary>
/// Is a Header match case insensitive? (default is true). /// Is a Header match case insensitive? (default is true).
/// </summary> /// </summary>
public bool HeaderPatternIgnoreCase { get; set; } = true; public bool HeaderPatternIgnoreCase { get; set; } = true;
/// <summary> /// <summary>
/// Is a Query Parameter match case insensitive? (default is true). /// Is a Query Parameter match case insensitive? (default is true).
/// </summary> /// </summary>
public bool QueryParameterPatternIgnoreCase { get; set; } = true; public bool QueryParameterPatternIgnoreCase { get; set; } = true;
/// <summary> /// <summary>
/// Is a Request Body match case insensitive? (default is true). /// Is a Request Body match case insensitive? (default is true).
/// </summary> /// </summary>
public bool RequestBodyIgnoreCase { get; set; } = true; public bool RequestBodyIgnoreCase { get; set; } = true;
/// <summary> /// <summary>
/// Is a ExampleValue match case insensitive? (default is true). /// Is a ExampleValue match case insensitive? (default is true).
/// </summary> /// </summary>
public bool IgnoreCaseExampleValues { get; set; } = true; public bool IgnoreCaseExampleValues { get; set; } = true;
/// <summary> /// <summary>
/// Are examples generated dynamically? (default is false). /// Are examples generated dynamically? (default is false).
/// </summary> /// </summary>
public bool DynamicExamples { get; set; } = false; public bool DynamicExamples { get; set; } = false;
}
} }

View File

@@ -1,20 +1,19 @@
namespace WireMock.Net.OpenApiParser.Types namespace WireMock.Net.OpenApiParser.Types;
/// <summary>
/// The example value to use
/// </summary>
public enum ExampleValueType
{ {
/// <summary> /// <summary>
/// The example value to use /// 1. Use a generated example value based on the SchemaType (default).
/// 2. If there is no example value defined in the schema,
/// then the <see cref="Settings.IWireMockOpenApiParserExampleValues"/> will be used (custom, fixed or dynamic).
/// </summary> /// </summary>
public enum ExampleValueType Value,
{
/// <summary>
/// 1. Use a generated example value based on the SchemaType (default).
/// 2. If there is no example value defined in the schema,
/// then the <see cref="Settings.IWireMockOpenApiParserExampleValues"/> will be used (custom, fixed or dynamic).
/// </summary>
Value,
/// <summary> /// <summary>
/// Just use a Wildcard (*) character. /// Just use a Wildcard (*) character.
/// </summary> /// </summary>
Wildcard Wildcard
}
} }

View File

@@ -1,25 +1,24 @@
namespace WireMock.Net.OpenApiParser.Types namespace WireMock.Net.OpenApiParser.Types;
internal enum SchemaFormat
{ {
internal enum SchemaFormat Float,
{
Float,
Double, Double,
Int32, Int32,
Int64, Int64,
Date, Date,
DateTime, DateTime,
Password, Password,
Byte, Byte,
Binary, Binary,
Undefined Undefined
}
} }

View File

@@ -1,21 +1,20 @@
namespace WireMock.Net.OpenApiParser.Types namespace WireMock.Net.OpenApiParser.Types;
internal enum SchemaType
{ {
internal enum SchemaType Object,
{
Object,
Array, Array,
String, String,
Integer, Integer,
Number, Number,
Boolean, Boolean,
File, File,
Unknown Unknown
}
} }

View File

@@ -1,18 +1,17 @@
using System; using System;
using System.Globalization; using System.Globalization;
namespace WireMock.Net.OpenApiParser.Utils namespace WireMock.Net.OpenApiParser.Utils;
{
internal static class DateTimeUtils
{
public static string ToRfc3339DateTime(DateTime dateTime)
{
return dateTime.ToString("yyyy-MM-dd'T'HH:mm:ss.fffzzz", DateTimeFormatInfo.InvariantInfo);
}
public static string ToRfc3339Date(DateTime dateTime) internal static class DateTimeUtils
{ {
return dateTime.ToString("yyyy-MM-dd", DateTimeFormatInfo.InvariantInfo); public static string ToRfc3339DateTime(DateTime dateTime)
} {
return dateTime.ToString("yyyy-MM-dd'T'HH:mm:ss.fffzzz", DateTimeFormatInfo.InvariantInfo);
}
public static string ToRfc3339Date(DateTime dateTime)
{
return dateTime.ToString("yyyy-MM-dd", DateTimeFormatInfo.InvariantInfo);
} }
} }

View File

@@ -7,121 +7,120 @@ using WireMock.Net.OpenApiParser.Extensions;
using WireMock.Net.OpenApiParser.Settings; using WireMock.Net.OpenApiParser.Settings;
using WireMock.Net.OpenApiParser.Types; using WireMock.Net.OpenApiParser.Types;
namespace WireMock.Net.OpenApiParser.Utils namespace WireMock.Net.OpenApiParser.Utils;
internal class ExampleValueGenerator
{ {
internal class ExampleValueGenerator private readonly WireMockOpenApiParserSettings _settings;
public ExampleValueGenerator(WireMockOpenApiParserSettings settings)
{ {
private readonly WireMockOpenApiParserSettings _settings; _settings = Guard.NotNull(settings);
public ExampleValueGenerator(WireMockOpenApiParserSettings settings) // Check if user provided an own implementation
if (settings.ExampleValues is null)
{ {
_settings = Guard.NotNull(settings, nameof(settings)); if (_settings.DynamicExamples)
// Check if user provided an own implementation
if (settings.ExampleValues is null)
{ {
if (_settings.DynamicExamples) _settings.ExampleValues = new WireMockOpenApiParserDynamicExampleValues();
{
_settings.ExampleValues = new WireMockOpenApiParserDynamicExampleValues();
}
else
{
_settings.ExampleValues = new WireMockOpenApiParserExampleValues();
}
} }
} else
public object GetExampleValue(OpenApiSchema schema)
{
var schemaExample = schema?.Example;
var schemaEnum = GetRandomEnumValue(schema?.Enum);
_settings.ExampleValues.Schema = schema;
switch (schema?.GetSchemaType())
{ {
case SchemaType.Boolean: _settings.ExampleValues = new WireMockOpenApiParserExampleValues();
var exampleBoolean = (OpenApiBoolean)schemaExample;
return exampleBoolean is null ? _settings.ExampleValues.Boolean : exampleBoolean.Value;
case SchemaType.Integer:
switch (schema?.GetSchemaFormat())
{
case SchemaFormat.Int64:
var exampleLong = (OpenApiLong)schemaExample;
var enumLong = (OpenApiLong)schemaEnum;
var valueLongEnumOrExample = enumLong is null ? exampleLong?.Value : enumLong?.Value;
return valueLongEnumOrExample ?? _settings.ExampleValues.Integer;
default:
var exampleInteger = (OpenApiInteger)schemaExample;
var enumInteger = (OpenApiInteger)schemaEnum;
var valueIntegerEnumOrExample = enumInteger is null ? exampleInteger?.Value : enumInteger?.Value;
return valueIntegerEnumOrExample ?? _settings.ExampleValues.Integer;
}
case SchemaType.Number:
switch (schema?.GetSchemaFormat())
{
case SchemaFormat.Float:
var exampleFloat = (OpenApiFloat)schemaExample;
var enumFloat = (OpenApiFloat)schemaEnum;
var valueFloatEnumOrExample = enumFloat is null ? exampleFloat?.Value : enumFloat?.Value;
return valueFloatEnumOrExample ?? _settings.ExampleValues.Float;
default:
var exampleDouble = (OpenApiDouble)schemaExample;
var enumDouble = (OpenApiDouble)schemaEnum;
var valueDoubleEnumOrExample = enumDouble is null ? exampleDouble?.Value : enumDouble?.Value;
return valueDoubleEnumOrExample ?? _settings.ExampleValues.Double;
}
default:
switch (schema?.GetSchemaFormat())
{
case SchemaFormat.Date:
var exampleDate = (OpenApiDate)schemaExample;
var enumDate = (OpenApiDate)schemaEnum;
var valueDateEnumOrExample = enumDate is null ? exampleDate?.Value : enumDate?.Value;
return DateTimeUtils.ToRfc3339Date(valueDateEnumOrExample ?? _settings.ExampleValues.Date());
case SchemaFormat.DateTime:
var exampleDateTime = (OpenApiDateTime)schemaExample;
var enumDateTime = (OpenApiDateTime)schemaEnum;
var valueDateTimeEnumOrExample = enumDateTime is null ? exampleDateTime?.Value : enumDateTime?.Value;
return DateTimeUtils.ToRfc3339DateTime(valueDateTimeEnumOrExample?.DateTime ?? _settings.ExampleValues.DateTime());
case SchemaFormat.Byte:
var exampleByte = (OpenApiByte)schemaExample;
var enumByte = (OpenApiByte)schemaEnum;
var valueByteEnumOrExample = enumByte is null ? exampleByte?.Value : enumByte?.Value;
return valueByteEnumOrExample ?? _settings.ExampleValues.Bytes;
case SchemaFormat.Binary:
var exampleBinary = (OpenApiBinary)schemaExample;
var enumBinary = (OpenApiBinary)schemaEnum;
var valueBinaryEnumOrExample = enumBinary is null ? exampleBinary?.Value : enumBinary?.Value;
return valueBinaryEnumOrExample ?? _settings.ExampleValues.Object;
default:
var exampleString = (OpenApiString)schemaExample;
var enumString = (OpenApiString)schemaEnum;
var valueStringEnumOrExample = enumString is null ? exampleString?.Value : enumString?.Value;
return valueStringEnumOrExample ?? _settings.ExampleValues.String;
}
} }
} }
}
private static IOpenApiAny GetRandomEnumValue(IList<IOpenApiAny> schemaEnum)
{ public object GetExampleValue(OpenApiSchema? schema)
if (schemaEnum?.Count > 0) {
{ var schemaExample = schema?.Example;
int maxValue = schemaEnum.Count - 1; var schemaEnum = GetRandomEnumValue(schema?.Enum);
int randomEnum = new Random().Next(0, maxValue);
return schemaEnum[randomEnum]; _settings.ExampleValues.Schema = schema;
}
switch (schema?.GetSchemaType())
return null; {
} case SchemaType.Boolean:
var exampleBoolean = schemaExample as OpenApiBoolean;
return exampleBoolean is null ? _settings.ExampleValues.Boolean : exampleBoolean.Value;
case SchemaType.Integer:
switch (schema?.GetSchemaFormat())
{
case SchemaFormat.Int64:
var exampleLong = (OpenApiLong)schemaExample;
var enumLong = (OpenApiLong)schemaEnum;
var valueLongEnumOrExample = enumLong is null ? exampleLong?.Value : enumLong?.Value;
return valueLongEnumOrExample ?? _settings.ExampleValues.Integer;
default:
var exampleInteger = (OpenApiInteger)schemaExample;
var enumInteger = (OpenApiInteger)schemaEnum;
var valueIntegerEnumOrExample = enumInteger is null ? exampleInteger?.Value : enumInteger?.Value;
return valueIntegerEnumOrExample ?? _settings.ExampleValues.Integer;
}
case SchemaType.Number:
switch (schema?.GetSchemaFormat())
{
case SchemaFormat.Float:
var exampleFloat = (OpenApiFloat)schemaExample;
var enumFloat = (OpenApiFloat)schemaEnum;
var valueFloatEnumOrExample = enumFloat is null ? exampleFloat?.Value : enumFloat?.Value;
return valueFloatEnumOrExample ?? _settings.ExampleValues.Float;
default:
var exampleDouble = (OpenApiDouble)schemaExample;
var enumDouble = (OpenApiDouble)schemaEnum;
var valueDoubleEnumOrExample = enumDouble is null ? exampleDouble?.Value : enumDouble?.Value;
return valueDoubleEnumOrExample ?? _settings.ExampleValues.Double;
}
default:
switch (schema?.GetSchemaFormat())
{
case SchemaFormat.Date:
var exampleDate = (OpenApiDate)schemaExample;
var enumDate = (OpenApiDate)schemaEnum;
var valueDateEnumOrExample = enumDate is null ? exampleDate?.Value : enumDate?.Value;
return DateTimeUtils.ToRfc3339Date(valueDateEnumOrExample ?? _settings.ExampleValues.Date());
case SchemaFormat.DateTime:
var exampleDateTime = (OpenApiDateTime)schemaExample;
var enumDateTime = (OpenApiDateTime)schemaEnum;
var valueDateTimeEnumOrExample = enumDateTime is null ? exampleDateTime?.Value : enumDateTime?.Value;
return DateTimeUtils.ToRfc3339DateTime(valueDateTimeEnumOrExample?.DateTime ?? _settings.ExampleValues.DateTime());
case SchemaFormat.Byte:
var exampleByte = (OpenApiByte)schemaExample;
var enumByte = (OpenApiByte)schemaEnum;
var valueByteEnumOrExample = enumByte is null ? exampleByte?.Value : enumByte?.Value;
return valueByteEnumOrExample ?? _settings.ExampleValues.Bytes;
case SchemaFormat.Binary:
var exampleBinary = (OpenApiBinary)schemaExample;
var enumBinary = (OpenApiBinary)schemaEnum;
var valueBinaryEnumOrExample = enumBinary is null ? exampleBinary?.Value : enumBinary?.Value;
return valueBinaryEnumOrExample ?? _settings.ExampleValues.Object;
default:
var exampleString = (OpenApiString)schemaExample;
var enumString = (OpenApiString)schemaEnum;
var valueStringEnumOrExample = enumString is null ? exampleString?.Value : enumString?.Value;
return valueStringEnumOrExample ?? _settings.ExampleValues.String;
}
}
}
private static IOpenApiAny? GetRandomEnumValue(IList<IOpenApiAny>? schemaEnum)
{
if (schemaEnum?.Count > 0)
{
int maxValue = schemaEnum.Count - 1;
int randomEnum = new Random().Next(0, maxValue);
return schemaEnum[randomEnum];
}
return null;
} }
} }

View File

@@ -12,7 +12,7 @@
<AssemblyOriginatorKeyFile>../WireMock.Net/WireMock.Net.snk</AssemblyOriginatorKeyFile> <AssemblyOriginatorKeyFile>../WireMock.Net/WireMock.Net.snk</AssemblyOriginatorKeyFile>
<PublicSign Condition=" '$(OS)' != 'Windows_NT' ">true</PublicSign> <PublicSign Condition=" '$(OS)' != 'Windows_NT' ">true</PublicSign>
<PackageLicenseExpression>MIT</PackageLicenseExpression> <PackageLicenseExpression>MIT</PackageLicenseExpression>
<LangVersion>8.0</LangVersion> <LangVersion>10</LangVersion>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Release'"> <PropertyGroup Condition="'$(Configuration)' == 'Release'">
@@ -22,6 +22,10 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="Microsoft.OpenApi.Readers" Version="1.2.3" /> <PackageReference Include="Microsoft.OpenApi.Readers" Version="1.2.3" />
<PackageReference Include="Nullable" Version="1.3.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="RamlToOpenApiConverter" Version="0.6.1" /> <PackageReference Include="RamlToOpenApiConverter" Version="0.6.1" />
<PackageReference Include="JetBrains.Annotations" Version="2022.1.0" PrivateAssets="All" /> <PackageReference Include="JetBrains.Annotations" Version="2022.1.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" /> <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />

View File

@@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using JetBrains.Annotations; using JetBrains.Annotations;
@@ -9,60 +9,59 @@ using WireMock.Admin.Mappings;
using WireMock.Net.OpenApiParser.Mappers; using WireMock.Net.OpenApiParser.Mappers;
using WireMock.Net.OpenApiParser.Settings; using WireMock.Net.OpenApiParser.Settings;
namespace WireMock.Net.OpenApiParser namespace WireMock.Net.OpenApiParser;
/// <summary>
/// Parse a OpenApi/Swagger/V2/V3 or Raml to WireMock.Net MappingModels.
/// </summary>
public class WireMockOpenApiParser : IWireMockOpenApiParser
{ {
/// <summary> private readonly OpenApiStreamReader _reader = new OpenApiStreamReader();
/// Parse a OpenApi/Swagger/V2/V3 or Raml to WireMock.Net MappingModels.
/// </summary> /// <inheritdoc cref="IWireMockOpenApiParser.FromFile(string, out OpenApiDiagnostic)" />
public class WireMockOpenApiParser : IWireMockOpenApiParser [PublicAPI]
public IEnumerable<MappingModel> FromFile(string path, out OpenApiDiagnostic diagnostic)
{ {
private readonly OpenApiStreamReader _reader = new OpenApiStreamReader(); return FromFile(path, new WireMockOpenApiParserSettings(), out diagnostic);
}
/// <inheritdoc cref="IWireMockOpenApiParser.FromFile(string, out OpenApiDiagnostic)" /> /// <inheritdoc cref="IWireMockOpenApiParser.FromFile(string, WireMockOpenApiParserSettings, out OpenApiDiagnostic)" />
[PublicAPI] [PublicAPI]
public IEnumerable<MappingModel> FromFile(string path, out OpenApiDiagnostic diagnostic) public IEnumerable<MappingModel> FromFile(string path, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic)
{
OpenApiDocument document;
if (Path.GetExtension(path).EndsWith("raml", StringComparison.OrdinalIgnoreCase))
{ {
return FromFile(path, new WireMockOpenApiParserSettings(), out diagnostic); diagnostic = new OpenApiDiagnostic();
document = new RamlConverter().ConvertToOpenApiDocument(path);
}
else
{
var reader = new OpenApiStreamReader();
document = reader.Read(File.OpenRead(path), out diagnostic);
} }
/// <inheritdoc cref="IWireMockOpenApiParser.FromFile(string, WireMockOpenApiParserSettings, out OpenApiDiagnostic)" /> return FromDocument(document, settings);
[PublicAPI] }
public IEnumerable<MappingModel> FromFile(string path, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic)
{
OpenApiDocument document;
if (Path.GetExtension(path).EndsWith("raml", StringComparison.OrdinalIgnoreCase))
{
diagnostic = new OpenApiDiagnostic();
document = new RamlConverter().ConvertToOpenApiDocument(path);
}
else
{
var reader = new OpenApiStreamReader();
document = reader.Read(File.OpenRead(path), out diagnostic);
}
return FromDocument(document, settings); /// <inheritdoc cref="IWireMockOpenApiParser.FromStream(Stream, out OpenApiDiagnostic)" />
} [PublicAPI]
public IEnumerable<MappingModel> FromStream(Stream stream, out OpenApiDiagnostic diagnostic)
{
return FromDocument(_reader.Read(stream, out diagnostic));
}
/// <inheritdoc cref="IWireMockOpenApiParser.FromStream(Stream, out OpenApiDiagnostic)" /> /// <inheritdoc cref="IWireMockOpenApiParser.FromStream(Stream, WireMockOpenApiParserSettings, out OpenApiDiagnostic)" />
[PublicAPI] [PublicAPI]
public IEnumerable<MappingModel> FromStream(Stream stream, out OpenApiDiagnostic diagnostic) public IEnumerable<MappingModel> FromStream(Stream stream, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic)
{ {
return FromDocument(_reader.Read(stream, out diagnostic)); return FromDocument(_reader.Read(stream, out diagnostic), settings);
} }
/// <inheritdoc cref="IWireMockOpenApiParser.FromStream(Stream, WireMockOpenApiParserSettings, out OpenApiDiagnostic)" /> /// <inheritdoc cref="IWireMockOpenApiParser.FromDocument(OpenApiDocument, WireMockOpenApiParserSettings)" />
[PublicAPI] [PublicAPI]
public IEnumerable<MappingModel> FromStream(Stream stream, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic) public IEnumerable<MappingModel> FromDocument(OpenApiDocument openApiDocument, WireMockOpenApiParserSettings? settings = null)
{ {
return FromDocument(_reader.Read(stream, out diagnostic), settings); return new OpenApiPathsMapper(settings).ToMappingModels(openApiDocument.Paths, openApiDocument.Servers);
}
/// <inheritdoc cref="IWireMockOpenApiParser.FromDocument(OpenApiDocument, WireMockOpenApiParserSettings)" />
[PublicAPI]
public IEnumerable<MappingModel> FromDocument(OpenApiDocument openApiDocument, WireMockOpenApiParserSettings settings = null)
{
return new OpenApiPathsMapper(settings).ToMappingModels(openApiDocument.Paths, openApiDocument.Servers);
}
} }
} }

View File

@@ -1,4 +1,4 @@
using RestEase; using RestEase;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Net.Http.Headers; using System.Net.Http.Headers;
@@ -179,6 +179,20 @@ namespace WireMock.Client
[Post("scenarios")] [Post("scenarios")]
Task<StatusModel> ResetScenariosAsync(); Task<StatusModel> ResetScenariosAsync();
/// <summary>
/// Delete (reset) a specific scenario
/// </summary>
[Delete("scenarios/{name}")]
[AllowAnyStatusCode]
Task<StatusModel> DeleteScenarioAsync([Path] string name);
/// <summary>
/// Delete (reset) all scenarios
/// </summary>
[Post("scenarios/{name}/reset")]
[AllowAnyStatusCode]
Task<StatusModel> ResetScenarioAsync([Path] string name);
/// <summary> /// <summary>
/// Create a new File /// Create a new File
/// </summary> /// </summary>

View File

@@ -15,7 +15,7 @@ namespace WireMock.Net.StandAlone;
/// </summary> /// </summary>
public static class StandAloneApp public static class StandAloneApp
{ {
private static readonly string Version = typeof(StandAloneApp).GetTypeInfo().Assembly.GetName().Version.ToString(); private static readonly string Version = typeof(StandAloneApp).GetTypeInfo().Assembly.GetName().Version!.ToString();
/// <summary> /// <summary>
/// Start WireMock.Net standalone Server based on the WireMockServerSettings. /// Start WireMock.Net standalone Server based on the WireMockServerSettings.

View File

@@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using WireMock.Util; using WireMock.Util;
@@ -35,15 +36,18 @@ internal static class HttpResponseMessageHelper
contentEncodingHeader = headers.First(header => string.Equals(header.Key, HttpKnownHeaderNames.ContentEncoding, StringComparison.OrdinalIgnoreCase)).Value; 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, var bodyParserSettings = new BodyParserSettings
ContentType = contentTypeHeader?.FirstOrDefault(), {
DeserializeJson = deserializeJson, Stream = stream,
ContentEncoding = contentEncodingHeader?.FirstOrDefault(), ContentType = contentTypeHeader?.FirstOrDefault(),
DecompressGZipAndDeflate = decompressGzipAndDeflate DeserializeJson = deserializeJson,
}; ContentEncoding = contentEncodingHeader?.FirstOrDefault(),
responseMessage.BodyData = await BodyParser.ParseAsync(bodyParserSettings).ConfigureAwait(false); DecompressGZipAndDeflate = decompressGzipAndDeflate
};
responseMessage.BodyData = await BodyParser.ParseAsync(bodyParserSettings).ConfigureAwait(false);
}
} }
foreach (var header in headers) foreach (var header in headers)

View File

@@ -89,7 +89,7 @@ internal class WebhookSender
}; };
// Create HttpRequestMessage // Create HttpRequestMessage
var httpRequestMessage = HttpRequestMessageHelper.Create(requestMessage, webhookRequest.Url); var httpRequestMessage = HttpRequestMessageHelper.Create(requestMessage, webhookRequestUrl);
// Delay (if required) // Delay (if required)
if (TryGetDelay(webhookRequest, out var delay)) if (TryGetDelay(webhookRequest, out var delay))

View File

@@ -12,13 +12,13 @@ public class LogEntry : ILogEntry
public Guid Guid { get; set; } public Guid Guid { get; set; }
/// <inheritdoc cref="ILogEntry.RequestMessage" /> /// <inheritdoc cref="ILogEntry.RequestMessage" />
public IRequestMessage RequestMessage { get; set; } public IRequestMessage RequestMessage { get; set; } = null!;
/// <inheritdoc cref="ILogEntry.ResponseMessage" /> /// <inheritdoc cref="ILogEntry.ResponseMessage" />
public IResponseMessage ResponseMessage { get; set; } public IResponseMessage ResponseMessage { get; set; } = null!;
/// <inheritdoc cref="ILogEntry.RequestMatchResult" /> /// <inheritdoc cref="ILogEntry.RequestMatchResult" />
public IRequestMatchResult RequestMatchResult { get; set; } public IRequestMatchResult RequestMatchResult { get; set; } = null!;
/// <inheritdoc cref="ILogEntry.MappingGuid" /> /// <inheritdoc cref="ILogEntry.MappingGuid" />
public Guid? MappingGuid { get; set; } public Guid? MappingGuid { get; set; }
@@ -33,5 +33,5 @@ public class LogEntry : ILogEntry
public string? PartialMappingTitle { get; set; } public string? PartialMappingTitle { get; set; }
/// <inheritdoc cref="ILogEntry.PartialMatchResult" /> /// <inheritdoc cref="ILogEntry.PartialMatchResult" />
public IRequestMatchResult PartialMatchResult { get; set; } public IRequestMatchResult PartialMatchResult { get; set; } = null!;
} }

View File

@@ -2,73 +2,72 @@ using Newtonsoft.Json;
using System; using System;
using WireMock.Admin.Requests; using WireMock.Admin.Requests;
namespace WireMock.Logging namespace WireMock.Logging;
/// <summary>
/// WireMockConsoleLogger which logs to Console
/// </summary>
/// <seealso cref="IWireMockLogger" />
public class WireMockConsoleLogger : IWireMockLogger
{ {
/// <summary> /// <summary>
/// WireMockConsoleLogger which logs to Console /// Initializes a new instance of the <see cref="WireMockConsoleLogger"/> class.
/// </summary> /// </summary>
/// <seealso cref="IWireMockLogger" /> public WireMockConsoleLogger()
public class WireMockConsoleLogger : IWireMockLogger
{ {
/// <summary> Console.OutputEncoding = System.Text.Encoding.UTF8;
/// Initializes a new instance of the <see cref="WireMockConsoleLogger"/> class. }
/// </summary>
public WireMockConsoleLogger()
{
Console.OutputEncoding = System.Text.Encoding.UTF8;
}
/// <see cref="IWireMockLogger.Debug"/> /// <see cref="IWireMockLogger.Debug"/>
public void Debug(string formatString, params object[] args) public void Debug(string formatString, params object[] args)
{ {
Console.WriteLine(Format("Debug", formatString, args)); Console.WriteLine(Format("Debug", formatString, args));
} }
/// <see cref="IWireMockLogger.Info"/> /// <see cref="IWireMockLogger.Info"/>
public void Info(string formatString, params object[] args) public void Info(string formatString, params object[] args)
{ {
Console.WriteLine(Format("Info", formatString, args)); Console.WriteLine(Format("Info", formatString, args));
} }
/// <see cref="IWireMockLogger.Warn"/> /// <see cref="IWireMockLogger.Warn"/>
public void Warn(string formatString, params object[] args) public void Warn(string formatString, params object[] args)
{ {
Console.WriteLine(Format("Warn", formatString, args)); Console.WriteLine(Format("Warn", formatString, args));
} }
/// <see cref="IWireMockLogger.Error(string, object[])"/> /// <see cref="IWireMockLogger.Error(string, object[])"/>
public void Error(string formatString, params object[] args) public void Error(string formatString, params object[] args)
{ {
Console.WriteLine(Format("Error", formatString, args)); Console.WriteLine(Format("Error", formatString, args));
} }
/// <see cref="IWireMockLogger.Error(string, Exception)"/> /// <see cref="IWireMockLogger.Error(string, Exception)"/>
public void Error(string formatString, Exception exception) public void Error(string formatString, Exception exception)
{ {
Console.WriteLine(Format("Error", formatString, exception.Message)); Console.WriteLine(Format("Error", formatString, exception.Message));
if (exception is AggregateException ae) if (exception is AggregateException ae)
{
ae.Handle(ex =>
{ {
ae.Handle(ex => Console.WriteLine(Format("Error", "Exception {0}", ex.Message));
{ return true;
Console.WriteLine(Format("Error", "Exception {0}", ex.Message)); });
return true;
});
}
}
/// <see cref="IWireMockLogger.DebugRequestResponse"/>
public void DebugRequestResponse(LogEntryModel logEntryModel, bool isAdminRequest)
{
string message = JsonConvert.SerializeObject(logEntryModel, Formatting.Indented);
Console.WriteLine(Format("DebugRequestResponse", "Admin[{0}] {1}", isAdminRequest, message));
}
private static string Format(string level, string formatString, params object[] args)
{
var message = args.Length > 0 ? string.Format(formatString, args) : formatString;
return $"{DateTime.UtcNow} [{level}] : {message}";
} }
} }
/// <see cref="IWireMockLogger.DebugRequestResponse"/>
public void DebugRequestResponse(LogEntryModel logEntryModel, bool isAdminRequest)
{
string message = JsonConvert.SerializeObject(logEntryModel, Formatting.Indented);
Console.WriteLine(Format("DebugRequestResponse", "Admin[{0}] {1}", isAdminRequest, message));
}
private static string Format(string level, string formatString, params object[] args)
{
var message = args.Length > 0 ? string.Format(formatString, args) : formatString;
return $"{DateTime.UtcNow} [{level}] : {message}";
}
} }

View File

@@ -1,48 +1,47 @@
using System; using System;
using WireMock.Admin.Requests; using WireMock.Admin.Requests;
namespace WireMock.Logging namespace WireMock.Logging;
/// <summary>
/// WireMockNullLogger which does not log.
/// </summary>
/// <seealso cref="IWireMockLogger" />
public class WireMockNullLogger : IWireMockLogger
{ {
/// <summary> /// <see cref="IWireMockLogger.Debug"/>
/// WireMockNullLogger which does not log. public void Debug(string formatString, params object[] args)
/// </summary>
/// <seealso cref="IWireMockLogger" />
public class WireMockNullLogger : IWireMockLogger
{ {
/// <see cref="IWireMockLogger.Debug"/> // Log nothing
public void Debug(string formatString, params object[] args) }
{
// Log nothing
}
/// <see cref="IWireMockLogger.Info"/> /// <see cref="IWireMockLogger.Info"/>
public void Info(string formatString, params object[] args) public void Info(string formatString, params object[] args)
{ {
// Log nothing // Log nothing
} }
/// <see cref="IWireMockLogger.Warn"/> /// <see cref="IWireMockLogger.Warn"/>
public void Warn(string formatString, params object[] args) public void Warn(string formatString, params object[] args)
{ {
// Log nothing // Log nothing
} }
/// <see cref="IWireMockLogger.Error(string, object[])"/> /// <see cref="IWireMockLogger.Error(string, object[])"/>
public void Error(string formatString, params object[] args) public void Error(string formatString, params object[] args)
{ {
// Log nothing // Log nothing
} }
/// <see cref="IWireMockLogger.Error(string, Exception)"/> /// <see cref="IWireMockLogger.Error(string, Exception)"/>
public void Error(string formatString, Exception exception) public void Error(string formatString, Exception exception)
{ {
// Log nothing // Log nothing
} }
/// <see cref="IWireMockLogger.DebugRequestResponse"/> /// <see cref="IWireMockLogger.DebugRequestResponse"/>
public void DebugRequestResponse(LogEntryModel logEntryModel, bool isAdminRequest) public void DebugRequestResponse(LogEntryModel logEntryModel, bool isAdminRequest)
{ {
// Log nothing // Log nothing
}
} }
} }

View File

@@ -21,6 +21,7 @@ public class LinqMatcher : IObjectMatcher, IStringMatcher
/// <inheritdoc cref="IMatcher.MatchBehaviour"/> /// <inheritdoc cref="IMatcher.MatchBehaviour"/>
public MatchBehaviour MatchBehaviour { get; } public MatchBehaviour MatchBehaviour { get; }
/// <inheritdoc cref="IMatcher.ThrowException"/> /// <inheritdoc cref="IMatcher.ThrowException"/>
public bool ThrowException { get; } public bool ThrowException { get; }

View File

@@ -104,7 +104,7 @@ public class RequestMessageBodyMatcher : IRequestMatcher
/// Initializes a new instance of the <see cref="RequestMessageBodyMatcher"/> class. /// Initializes a new instance of the <see cref="RequestMessageBodyMatcher"/> class.
/// </summary> /// </summary>
/// <param name="func">The function.</param> /// <param name="func">The function.</param>
public RequestMessageBodyMatcher(Func<IBodyData, bool> func) public RequestMessageBodyMatcher(Func<IBodyData?, bool> func)
{ {
BodyDataFunc = Guard.NotNull(func); BodyDataFunc = Guard.NotNull(func);
} }

View File

@@ -53,7 +53,8 @@ public class RequestMessageParamMatcher : IRequestMatcher
/// <param name="key">The key.</param> /// <param name="key">The key.</param>
/// <param name="ignoreCase">Defines if the key should be matched using case-ignore.</param> /// <param name="ignoreCase">Defines if the key should be matched using case-ignore.</param>
/// <param name="values">The values.</param> /// <param name="values">The values.</param>
public RequestMessageParamMatcher(MatchBehaviour matchBehaviour, string key, bool ignoreCase, string[]? values) : this(matchBehaviour, key, ignoreCase, values?.Select(value => new ExactMatcher(matchBehaviour, ignoreCase, false, MatchOperator.And, value)).Cast<IStringMatcher>().ToArray()) public RequestMessageParamMatcher(MatchBehaviour matchBehaviour, string key, bool ignoreCase, params string[]? values) :
this(matchBehaviour, key, ignoreCase, values?.Select(value => new ExactMatcher(matchBehaviour, ignoreCase, false, MatchOperator.And, value)).Cast<IStringMatcher>().ToArray())
{ {
} }
@@ -64,7 +65,7 @@ public class RequestMessageParamMatcher : IRequestMatcher
/// <param name="key">The key.</param> /// <param name="key">The key.</param>
/// <param name="ignoreCase">Defines if the key should be matched using case-ignore.</param> /// <param name="ignoreCase">Defines if the key should be matched using case-ignore.</param>
/// <param name="matchers">The matchers.</param> /// <param name="matchers">The matchers.</param>
public RequestMessageParamMatcher(MatchBehaviour matchBehaviour, string key, bool ignoreCase, IStringMatcher[]? matchers) public RequestMessageParamMatcher(MatchBehaviour matchBehaviour, string key, bool ignoreCase, params IStringMatcher[]? matchers)
{ {
MatchBehaviour = matchBehaviour; MatchBehaviour = matchBehaviour;
Key = Guard.NotNull(key); Key = Guard.NotNull(key);
@@ -95,7 +96,7 @@ public class RequestMessageParamMatcher : IRequestMatcher
return MatchScores.ToScore(requestMessage.Query != null && Funcs.Any(f => f(requestMessage.Query))); return MatchScores.ToScore(requestMessage.Query != null && Funcs.Any(f => f(requestMessage.Query)));
} }
var valuesPresentInRequestMessage = ((RequestMessage)requestMessage).GetParameter(Key!, IgnoreCase ?? false); var valuesPresentInRequestMessage = ((RequestMessage)requestMessage).GetParameter(Key, IgnoreCase ?? false);
if (valuesPresentInRequestMessage == null) if (valuesPresentInRequestMessage == null)
{ {
// Key is not present at all, just return Mismatch // Key is not present at all, just return Mismatch

View File

@@ -1,41 +1,43 @@
using System.Text; using System.Text;
using WireMock.Types; using WireMock.Types;
namespace WireMock.Util namespace WireMock.Util;
/// <summary>
/// BodyData
/// </summary>
public class BodyData : IBodyData
{ {
/// <summary> /// <inheritdoc cref="IBodyData.Encoding" />
/// BodyData public Encoding? Encoding { get; set; }
/// </summary>
public class BodyData : IBodyData
{
/// <inheritdoc cref="IBodyData.Encoding" />
public Encoding? Encoding { get; set; }
/// <inheritdoc /> /// <inheritdoc />
public string? BodyAsString { get; set; } public string? BodyAsString { get; set; }
/// <inheritdoc cref="IBodyData.BodyAsJson" /> /// <inheritdoc cref="IBodyData.BodyAsJson" />
public object? BodyAsJson { get; set; } public object? BodyAsJson { get; set; }
/// <inheritdoc cref="IBodyData.BodyAsBytes" /> /// <inheritdoc cref="IBodyData.BodyAsBytes" />
public byte[]? BodyAsBytes { get; set; } public byte[]? BodyAsBytes { get; set; }
/// <inheritdoc cref="IBodyData.BodyAsJsonIndented" /> /// <inheritdoc cref="IBodyData.BodyAsJsonIndented" />
public bool? BodyAsJsonIndented { get; set; } public bool? BodyAsJsonIndented { get; set; }
/// <inheritdoc cref="IBodyData.BodyAsFile" /> /// <inheritdoc cref="IBodyData.BodyAsFile" />
public string? BodyAsFile { get; set; } public string? BodyAsFile { get; set; }
/// <inheritdoc cref="IBodyData.BodyAsFileIsCached" /> /// <inheritdoc cref="IBodyData.BodyAsFileIsCached" />
public bool? BodyAsFileIsCached { get; set; } public bool? BodyAsFileIsCached { get; set; }
/// <inheritdoc cref="IBodyData.DetectedBodyType" /> /// <inheritdoc cref="IBodyData.DetectedBodyType" />
public BodyType? DetectedBodyType { get; set; } public BodyType? DetectedBodyType { get; set; }
/// <inheritdoc cref="IBodyData.DetectedBodyTypeFromContentType" /> /// <inheritdoc cref="IBodyData.DetectedBodyTypeFromContentType" />
public BodyType? DetectedBodyTypeFromContentType { get; set; } public BodyType? DetectedBodyTypeFromContentType { get; set; }
/// <inheritdoc cref="IRequestMessage.DetectedCompression" /> /// <inheritdoc cref="IRequestMessage.DetectedCompression" />
public string? DetectedCompression { get; set; } public string? DetectedCompression { get; set; }
}
/// <inheritdoc />
public string? IsFuncUsed { get; set; }
} }

View File

@@ -3,13 +3,13 @@ using System.Collections.Concurrent;
using WireMock.Handlers; using WireMock.Handlers;
using WireMock.Logging; using WireMock.Logging;
using WireMock.Matchers; using WireMock.Matchers;
using WireMock.Types;
using WireMock.Util; using WireMock.Util;
#if !USE_ASPNETCORE #if !USE_ASPNETCORE
using Owin; using Owin;
#else #else
using IAppBuilder = Microsoft.AspNetCore.Builder.IApplicationBuilder; using IAppBuilder = Microsoft.AspNetCore.Builder.IApplicationBuilder;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using WireMock.Types;
#endif #endif
namespace WireMock.Owin; namespace WireMock.Owin;
@@ -69,4 +69,8 @@ internal interface IWireMockMiddlewareOptions
bool CustomCertificateDefined { get; } bool CustomCertificateDefined { get; }
bool? SaveUnmatchedRequests { get; set; } bool? SaveUnmatchedRequests { get; set; }
bool? DoNotSaveDynamicResponseInLogEntry { get; set; }
QueryParameterMultipleValueSupport? QueryParameterMultipleValueSupport { get; set; }
} }

View File

@@ -68,7 +68,7 @@ namespace WireMock.Owin.Mappers
body = await BodyParser.ParseAsync(bodyParserSettings).ConfigureAwait(false); body = await BodyParser.ParseAsync(bodyParserSettings).ConfigureAwait(false);
} }
return new RequestMessage(urlDetails, method, clientIP, body, headers, cookies) { DateTime = DateTime.UtcNow }; return new RequestMessage(options, urlDetails, method, clientIP, body, headers, cookies) { DateTime = DateTime.UtcNow };
} }
private static (UrlDetails UrlDetails, string ClientIP) ParseRequest(IRequest request) private static (UrlDetails UrlDetails, string ClientIP) ParseRequest(IRequest request)

View File

@@ -81,11 +81,11 @@ namespace WireMock.Owin.Mappers
var statusCodeType = responseMessage.StatusCode?.GetType(); var statusCodeType = responseMessage.StatusCode?.GetType();
switch (statusCodeType) switch (statusCodeType)
{ {
case Type typeAsIntOrEnum when typeAsIntOrEnum == typeof(int) || typeAsIntOrEnum == typeof(int?) || typeAsIntOrEnum.GetTypeInfo().IsEnum: case { } typeAsIntOrEnum when typeAsIntOrEnum == typeof(int) || typeAsIntOrEnum == typeof(int?) || typeAsIntOrEnum.GetTypeInfo().IsEnum:
response.StatusCode = MapStatusCode((int)responseMessage.StatusCode!); response.StatusCode = MapStatusCode((int)responseMessage.StatusCode!);
break; break;
case Type typeAsString when typeAsString == typeof(string): case { } typeAsString when typeAsString == typeof(string):
// Note: this case will also match on null // Note: this case will also match on null
int.TryParse(responseMessage.StatusCode as string, out int result); int.TryParse(responseMessage.StatusCode as string, out int result);
response.StatusCode = MapStatusCode(result); response.StatusCode = MapStatusCode(result);
@@ -99,7 +99,14 @@ namespace WireMock.Owin.Mappers
if (bytes != null) 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);
}
} }
} }
@@ -123,7 +130,7 @@ namespace WireMock.Owin.Mappers
switch (responseMessage.BodyData?.DetectedBodyType) switch (responseMessage.BodyData?.DetectedBodyType)
{ {
case BodyType.String: case BodyType.String:
return (responseMessage.BodyData.Encoding ?? _utf8NoBom).GetBytes(responseMessage.BodyData.BodyAsString); return (responseMessage.BodyData.Encoding ?? _utf8NoBom).GetBytes(responseMessage.BodyData.BodyAsString!);
case BodyType.Json: case BodyType.Json:
var formatting = responseMessage.BodyData.BodyAsJsonIndented == true var formatting = responseMessage.BodyData.BodyAsJsonIndented == true
@@ -136,7 +143,7 @@ namespace WireMock.Owin.Mappers
return responseMessage.BodyData.BodyAsBytes; return responseMessage.BodyData.BodyAsBytes;
case BodyType.File: case BodyType.File:
return _options.FileSystemHandler?.ReadResponseBodyAsFile(responseMessage.BodyData.BodyAsFile); return _options.FileSystemHandler?.ReadResponseBodyAsFile(responseMessage.BodyData.BodyAsFile!);
} }
return null; return null;
@@ -154,7 +161,7 @@ namespace WireMock.Owin.Mappers
}); });
// Set other headers // Set other headers
foreach (var item in responseMessage.Headers) foreach (var item in responseMessage.Headers!)
{ {
var headerName = item.Key; var headerName = item.Key;
var value = item.Value; var value = item.Value;

View File

@@ -32,22 +32,25 @@ namespace WireMock.Owin
private readonly IOwinRequestMapper _requestMapper; private readonly IOwinRequestMapper _requestMapper;
private readonly IOwinResponseMapper _responseMapper; private readonly IOwinResponseMapper _responseMapper;
private readonly IMappingMatcher _mappingMatcher; private readonly IMappingMatcher _mappingMatcher;
private readonly LogEntryMapper _logEntryMapper;
#if !USE_ASPNETCORE #if !USE_ASPNETCORE
public WireMockMiddleware(Next next, IWireMockMiddlewareOptions options, IOwinRequestMapper requestMapper, IOwinResponseMapper responseMapper, IMappingMatcher mappingMatcher) : base(next) public WireMockMiddleware(Next next, IWireMockMiddlewareOptions options, IOwinRequestMapper requestMapper, IOwinResponseMapper responseMapper, IMappingMatcher mappingMatcher) : base(next)
{ {
_options = Guard.NotNull(options, nameof(options)); _options = Guard.NotNull(options);
_requestMapper = Guard.NotNull(requestMapper, nameof(requestMapper)); _requestMapper = Guard.NotNull(requestMapper);
_responseMapper = Guard.NotNull(responseMapper, nameof(responseMapper)); _responseMapper = Guard.NotNull(responseMapper);
_mappingMatcher = Guard.NotNull(mappingMatcher, nameof(mappingMatcher)); _mappingMatcher = Guard.NotNull(mappingMatcher);
_logEntryMapper = new LogEntryMapper(options);
} }
#else #else
public WireMockMiddleware(Next next, IWireMockMiddlewareOptions options, IOwinRequestMapper requestMapper, IOwinResponseMapper responseMapper, IMappingMatcher mappingMatcher) public WireMockMiddleware(Next next, IWireMockMiddlewareOptions options, IOwinRequestMapper requestMapper, IOwinResponseMapper responseMapper, IMappingMatcher mappingMatcher)
{ {
_options = Guard.NotNull(options, nameof(options)); _options = Guard.NotNull(options);
_requestMapper = Guard.NotNull(requestMapper, nameof(requestMapper)); _requestMapper = Guard.NotNull(requestMapper);
_responseMapper = Guard.NotNull(responseMapper, nameof(responseMapper)); _responseMapper = Guard.NotNull(responseMapper);
_mappingMatcher = Guard.NotNull(mappingMatcher, nameof(mappingMatcher)); _mappingMatcher = Guard.NotNull(mappingMatcher);
_logEntryMapper = new LogEntryMapper(options);
} }
#endif #endif
@@ -264,31 +267,57 @@ namespace WireMock.Owin
private void LogRequest(LogEntry entry, bool addRequest) private void LogRequest(LogEntry entry, bool addRequest)
{ {
_options.Logger.DebugRequestResponse(LogEntryMapper.Map(entry), entry.RequestMessage.Path.StartsWith("/__admin/")); _options.Logger.DebugRequestResponse(_logEntryMapper.Map(entry), entry.RequestMessage.Path.StartsWith("/__admin/"));
if (addRequest) // If addRequest is set to true and MaxRequestLogCount is null or does have a value greater than 0, try to add a new request log.
if (addRequest && _options.MaxRequestLogCount is null or > 0)
{ {
_options.LogEntries.Add(entry); TryAddLogEntry(entry);
} }
if (_options.MaxRequestLogCount != null) // In case MaxRequestLogCount has a value greater than 0, try to delete existing request logs based on the count.
if (_options.MaxRequestLogCount is > 0)
{ {
var logEntries = _options.LogEntries.ToList(); var logEntries = _options.LogEntries.ToList();
foreach (var logEntry in logEntries.OrderBy(le => le.RequestMessage.DateTime).Take(logEntries.Count - _options.MaxRequestLogCount.Value)) foreach (var logEntry in logEntries.OrderBy(le => le.RequestMessage.DateTime).Take(logEntries.Count - _options.MaxRequestLogCount.Value))
{ {
_options.LogEntries.Remove(logEntry); TryRemoveLogEntry(logEntry);
} }
} }
if (_options.RequestLogExpirationDuration != null) // In case RequestLogExpirationDuration has a value greater than 0, try to delete existing request logs based on the date.
if (_options.RequestLogExpirationDuration is > 0)
{ {
var checkTime = DateTime.UtcNow.AddHours(-_options.RequestLogExpirationDuration.Value); var checkTime = DateTime.UtcNow.AddHours(-_options.RequestLogExpirationDuration.Value);
foreach (var logEntry in _options.LogEntries.ToList().Where(le => le.RequestMessage.DateTime < checkTime)) foreach (var logEntry in _options.LogEntries.ToList().Where(le => le.RequestMessage.DateTime < checkTime))
{ {
_options.LogEntries.Remove(logEntry); TryRemoveLogEntry(logEntry);
} }
} }
} }
private void TryAddLogEntry(LogEntry logEntry)
{
try
{
_options.LogEntries.Add(logEntry);
}
catch
{
// Ignore exception (can happen during stress testing)
}
}
private void TryRemoveLogEntry(LogEntry logEntry)
{
try
{
_options.LogEntries.Remove(logEntry);
}
catch
{
// Ignore exception (can happen during stress testing)
}
}
} }
} }

View File

@@ -26,7 +26,7 @@ internal class WireMockMiddlewareOptions : IWireMockMiddlewareOptions
public ConcurrentDictionary<Guid, IMapping> Mappings { get; } = new ConcurrentDictionary<Guid, IMapping>(); public ConcurrentDictionary<Guid, IMapping> Mappings { get; } = new ConcurrentDictionary<Guid, IMapping>();
public ConcurrentDictionary<string, ScenarioState> Scenarios { get; } = new(); public ConcurrentDictionary<string, ScenarioState> Scenarios { get; } = new(StringComparer.OrdinalIgnoreCase);
public ConcurrentObservableCollection<LogEntry> LogEntries { get; } = new(); public ConcurrentObservableCollection<LogEntry> LogEntries { get; } = new();
@@ -84,4 +84,10 @@ internal class WireMockMiddlewareOptions : IWireMockMiddlewareOptions
/// <inheritdoc cref="IWireMockMiddlewareOptions.SaveUnmatchedRequests"/> /// <inheritdoc cref="IWireMockMiddlewareOptions.SaveUnmatchedRequests"/>
public bool? SaveUnmatchedRequests { get; set; } public bool? SaveUnmatchedRequests { get; set; }
/// <inheritdoc />
public bool? DoNotSaveDynamicResponseInLogEntry { get; set; }
/// <inheritdoc />
public QueryParameterMultipleValueSupport? QueryParameterMultipleValueSupport { get; set; }
} }

View File

@@ -1,3 +1,4 @@
#pragma warning disable CS1591
namespace WireMock.Pact.Models.V2; namespace WireMock.Pact.Models.V2;
public class Interaction public class Interaction

View File

@@ -1,25 +1,25 @@
namespace WireMock.Pact.Models.V2 #pragma warning disable CS1591
namespace WireMock.Pact.Models.V2;
public class MatchingRule
{ {
public class MatchingRule /// <summary>
{ /// type or regex
/// <summary> /// </summary>
/// type or regex public string Match { get; set; } = "type";
/// </summary>
public string Match { get; set; } = "type";
/// <summary> /// <summary>
/// Used for Match = "type" /// Used for Match = "type"
/// </summary> /// </summary>
public string Min { get; set; } public string Min { get; set; }
/// <summary> /// <summary>
/// Used for Match = "type" /// Used for Match = "type"
/// </summary> /// </summary>
public string Max { get; set; } public string Max { get; set; }
/// <summary> /// <summary>
/// Used for Match = "regex" /// Used for Match = "regex"
/// </summary> /// </summary>
public string Regex { get; set; } public string Regex { get; set; }
}
} }

View File

@@ -1,9 +1,9 @@
namespace WireMock.Pact.Models.V2 #pragma warning disable CS1591
{ namespace WireMock.Pact.Models.V2;
public class Metadata
{
public string PactSpecificationVersion { get; set; }
public PactSpecification PactSpecification { get; set; } = new PactSpecification(); public class Metadata
} {
public string PactSpecificationVersion { get; set; }
public PactSpecification PactSpecification { get; set; } = new PactSpecification();
} }

View File

@@ -1,15 +1,15 @@
#pragma warning disable CS1591
using System.Collections.Generic; using System.Collections.Generic;
namespace WireMock.Pact.Models.V2 namespace WireMock.Pact.Models.V2;
public class Pact
{ {
public class Pact public Pacticipant Consumer { get; set; }
{
public Pacticipant Consumer { get; set; }
public List<Interaction> Interactions { get; set; } = new List<Interaction>(); public List<Interaction> Interactions { get; set; } = new List<Interaction>();
public Metadata Metadata { get; set; } public Metadata Metadata { get; set; }
public Pacticipant Provider { get; set; } public Pacticipant Provider { get; set; }
}
} }

View File

@@ -1,4 +1,6 @@
#pragma warning disable CS1591
using System.Collections.Generic; using System.Collections.Generic;
using WireMock.Constants;
namespace WireMock.Pact.Models.V2; namespace WireMock.Pact.Models.V2;
@@ -6,7 +8,7 @@ public class PactRequest
{ {
public IDictionary<string, string>? Headers { get; set; } public IDictionary<string, string>? Headers { get; set; }
public string Method { get; set; } = "GET"; public string Method { get; set; } = HttpRequestMethod.GET;
public string? Path { get; set; } = "/"; public string? Path { get; set; } = "/";

View File

@@ -1,3 +1,4 @@
#pragma warning disable CS1591
using System.Collections.Generic; using System.Collections.Generic;
namespace WireMock.Pact.Models.V2; namespace WireMock.Pact.Models.V2;

View File

@@ -1,11 +1,11 @@
namespace WireMock.Pact.Models.V2 #pragma warning disable CS1591
namespace WireMock.Pact.Models.V2;
public class PactRust
{ {
public class PactRust public string Ffi { get; set; }
{
public string Ffi { get; set; }
public string Mockserver { get; set; } public string Mockserver { get; set; }
public string Models { get; set; } public string Models { get; set; }
}
} }

View File

@@ -1,7 +1,7 @@
namespace WireMock.Pact.Models.V2 #pragma warning disable CS1591
namespace WireMock.Pact.Models.V2;
public class PactSpecification
{ {
public class PactSpecification public string Version { get; set; } = "2.0";
{
public string Version { get; set; } = "2.0";
}
} }

View File

@@ -1,7 +1,7 @@
namespace WireMock.Pact.Models.V2 #pragma warning disable CS1591
namespace WireMock.Pact.Models.V2;
public class Pacticipant
{ {
public class Pacticipant public string Name { get; set; }
{
public string Name { get; set; }
}
} }

View File

@@ -1,11 +1,11 @@
#pragma warning disable CS1591
using System.Collections.Generic; using System.Collections.Generic;
namespace WireMock.Pact.Models.V2 namespace WireMock.Pact.Models.V2;
{
public class ProviderState
{
public string Name { get; set; }
public IDictionary<string, string> Params { get; set; } public class ProviderState
} {
public string Name { get; set; }
public IDictionary<string, string> Params { get; set; }
} }

View File

@@ -1,17 +1,10 @@
using Stef.Validation;
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using WireMock.Constants; using Stef.Validation;
using WireMock.Http; using WireMock.Http;
using WireMock.Matchers;
using WireMock.RequestBuilders;
using WireMock.ResponseBuilders;
using WireMock.Serialization; using WireMock.Serialization;
using WireMock.Settings; using WireMock.Settings;
using WireMock.Types;
using WireMock.Util; using WireMock.Util;
namespace WireMock.Proxy; namespace WireMock.Proxy;
@@ -62,69 +55,4 @@ internal class ProxyHelper
return (responseMessage, newMapping); return (responseMessage, newMapping);
} }
private IMapping ToMapping(ProxyAndRecordSettings proxyAndRecordSettings, IRequestMessage requestMessage, ResponseMessage responseMessage)
{
var excludedHeaders = proxyAndRecordSettings.ExcludedHeaders ?? new string[] { };
var excludedCookies = proxyAndRecordSettings.ExcludedCookies ?? new string[] { };
var request = Request.Create();
request.WithPath(requestMessage.Path);
request.UsingMethod(requestMessage.Method);
requestMessage.Query?.Loop((key, value) => request.WithParam(key, false, value.ToArray()));
requestMessage.Cookies?.Loop((key, value) =>
{
if (!excludedCookies.Contains(key, StringComparer.OrdinalIgnoreCase))
{
request.WithCookie(key, value);
}
});
var allExcludedHeaders = new List<string>(excludedHeaders) { "Cookie" };
requestMessage.Headers?.Loop((key, value) =>
{
if (!allExcludedHeaders.Contains(key, StringComparer.OrdinalIgnoreCase))
{
request.WithHeader(key, value.ToArray());
}
});
bool throwExceptionWhenMatcherFails = _settings.ThrowExceptionWhenMatcherFails == true;
switch (requestMessage.BodyData?.DetectedBodyType)
{
case BodyType.Json:
request.WithBody(new JsonMatcher(MatchBehaviour.AcceptOnMatch, requestMessage.BodyData.BodyAsJson!, true, throwExceptionWhenMatcherFails));
break;
case BodyType.String:
request.WithBody(new ExactMatcher(MatchBehaviour.AcceptOnMatch, false, throwExceptionWhenMatcherFails, MatchOperator.Or, requestMessage.BodyData.BodyAsString));
break;
case BodyType.Bytes:
request.WithBody(new ExactObjectMatcher(MatchBehaviour.AcceptOnMatch, requestMessage.BodyData.BodyAsBytes, throwExceptionWhenMatcherFails));
break;
}
var response = Response.Create(responseMessage);
return new Mapping
(
guid: Guid.NewGuid(),
title: $"Proxy Mapping for {requestMessage.Method} {requestMessage.Path}",
description: string.Empty,
path: null,
settings: _settings,
request,
response,
priority: WireMockConstants.ProxyPriority, // This was 0
scenario: null,
executionConditionState: null,
nextState: null,
stateTimes: null,
webhooks: null,
useWebhooksFireAndForget: null,
timeSettings: null
);
}
} }

View File

@@ -1,10 +1,10 @@
// This source file is based on mock4net by Alexandre Victoor which is licensed under the Apache 2.0 License. // This source file is based on mock4net by Alexandre Victoor which is licensed under the Apache 2.0 License.
// For more details see 'mock4net/LICENSE.txt' and 'mock4net/readme.md' in this project root. // For more details see 'mock4net/LICENSE.txt' and 'mock4net/readme.md' in this project root.
using System.Linq; using System.Linq;
using WireMock.Http; using Stef.Validation;
using WireMock.Constants;
using WireMock.Matchers; using WireMock.Matchers;
using WireMock.Matchers.Request; using WireMock.Matchers.Request;
using Stef.Validation;
namespace WireMock.RequestBuilders; namespace WireMock.RequestBuilders;
@@ -13,63 +13,63 @@ public partial class Request
/// <inheritdoc /> /// <inheritdoc />
public IRequestBuilder UsingConnect(MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch) public IRequestBuilder UsingConnect(MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{ {
_requestMatchers.Add(new RequestMessageMethodMatcher(matchBehaviour, MatchOperator.Or, HttpRequestMethods.CONNECT)); _requestMatchers.Add(new RequestMessageMethodMatcher(matchBehaviour, MatchOperator.Or, HttpRequestMethod.CONNECT));
return this; return this;
} }
/// <inheritdoc /> /// <inheritdoc />
public IRequestBuilder UsingDelete(MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch) public IRequestBuilder UsingDelete(MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{ {
_requestMatchers.Add(new RequestMessageMethodMatcher(matchBehaviour, MatchOperator.Or, HttpRequestMethods.DELETE)); _requestMatchers.Add(new RequestMessageMethodMatcher(matchBehaviour, MatchOperator.Or, HttpRequestMethod.DELETE));
return this; return this;
} }
/// <inheritdoc /> /// <inheritdoc />
public IRequestBuilder UsingGet(MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch) public IRequestBuilder UsingGet(MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{ {
_requestMatchers.Add(new RequestMessageMethodMatcher(matchBehaviour, MatchOperator.Or, HttpRequestMethods.GET)); _requestMatchers.Add(new RequestMessageMethodMatcher(matchBehaviour, MatchOperator.Or, HttpRequestMethod.GET));
return this; return this;
} }
/// <inheritdoc /> /// <inheritdoc />
public IRequestBuilder UsingHead(MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch) public IRequestBuilder UsingHead(MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{ {
_requestMatchers.Add(new RequestMessageMethodMatcher(matchBehaviour, MatchOperator.Or, HttpRequestMethods.HEAD)); _requestMatchers.Add(new RequestMessageMethodMatcher(matchBehaviour, MatchOperator.Or, HttpRequestMethod.HEAD));
return this; return this;
} }
/// <inheritdoc /> /// <inheritdoc />
public IRequestBuilder UsingOptions(MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch) public IRequestBuilder UsingOptions(MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{ {
_requestMatchers.Add(new RequestMessageMethodMatcher(matchBehaviour, MatchOperator.Or, HttpRequestMethods.OPTIONS)); _requestMatchers.Add(new RequestMessageMethodMatcher(matchBehaviour, MatchOperator.Or, HttpRequestMethod.OPTIONS));
return this; return this;
} }
/// <inheritdoc /> /// <inheritdoc />
public IRequestBuilder UsingPost(MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch) public IRequestBuilder UsingPost(MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{ {
_requestMatchers.Add(new RequestMessageMethodMatcher(matchBehaviour, MatchOperator.Or, HttpRequestMethods.POST)); _requestMatchers.Add(new RequestMessageMethodMatcher(matchBehaviour, MatchOperator.Or, HttpRequestMethod.POST));
return this; return this;
} }
/// <inheritdoc /> /// <inheritdoc />
public IRequestBuilder UsingPatch(MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch) public IRequestBuilder UsingPatch(MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{ {
_requestMatchers.Add(new RequestMessageMethodMatcher(matchBehaviour, MatchOperator.Or, HttpRequestMethods.PATCH)); _requestMatchers.Add(new RequestMessageMethodMatcher(matchBehaviour, MatchOperator.Or, HttpRequestMethod.PATCH));
return this; return this;
} }
/// <inheritdoc /> /// <inheritdoc />
public IRequestBuilder UsingPut(MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch) public IRequestBuilder UsingPut(MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{ {
_requestMatchers.Add(new RequestMessageMethodMatcher(matchBehaviour, MatchOperator.Or, HttpRequestMethods.PUT)); _requestMatchers.Add(new RequestMessageMethodMatcher(matchBehaviour, MatchOperator.Or, HttpRequestMethod.PUT));
return this; return this;
} }
/// <inheritdoc /> /// <inheritdoc />
public IRequestBuilder UsingTrace(MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch) public IRequestBuilder UsingTrace(MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{ {
_requestMatchers.Add(new RequestMessageMethodMatcher(matchBehaviour, MatchOperator.Or, HttpRequestMethods.TRACE)); _requestMatchers.Add(new RequestMessageMethodMatcher(matchBehaviour, MatchOperator.Or, HttpRequestMethod.TRACE));
return this; return this;
} }

View File

@@ -6,6 +6,7 @@ using System.Linq;
using System.Net; using System.Net;
using Stef.Validation; using Stef.Validation;
using WireMock.Models; using WireMock.Models;
using WireMock.Owin;
using WireMock.Types; using WireMock.Types;
using WireMock.Util; using WireMock.Util;
@@ -26,7 +27,7 @@ public class RequestMessage : IRequestMessage
public string AbsoluteUrl { get; } public string AbsoluteUrl { get; }
/// <inheritdoc cref="IRequestMessage.ProxyUrl" /> /// <inheritdoc cref="IRequestMessage.ProxyUrl" />
public string ProxyUrl { get; set; } public string? ProxyUrl { get; set; }
/// <inheritdoc cref="IRequestMessage.DateTime" /> /// <inheritdoc cref="IRequestMessage.DateTime" />
public DateTime DateTime { get; set; } public DateTime DateTime { get; set; }
@@ -91,16 +92,36 @@ public class RequestMessage : IRequestMessage
/// <inheritdoc cref="IRequestMessage.Origin" /> /// <inheritdoc cref="IRequestMessage.Origin" />
public string Origin { get; } public string Origin { get; }
/// <summary>
/// Used for Unit Testing
/// </summary>
public RequestMessage(
UrlDetails urlDetails,
string method,
string clientIP,
IBodyData? bodyData = null,
IDictionary<string, string[]>? headers = null,
IDictionary<string, string>? cookies = null) : this(null, urlDetails, method, clientIP, bodyData, headers, cookies)
{
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="RequestMessage"/> class. /// Initializes a new instance of the <see cref="RequestMessage"/> class.
/// </summary> /// </summary>
/// <param name="options">The<seealso cref="IWireMockMiddlewareOptions"/>.</param>
/// <param name="urlDetails">The original url details.</param> /// <param name="urlDetails">The original url details.</param>
/// <param name="method">The HTTP method.</param> /// <param name="method">The HTTP method.</param>
/// <param name="clientIP">The client IP Address.</param> /// <param name="clientIP">The client IP Address.</param>
/// <param name="bodyData">The BodyData.</param> /// <param name="bodyData">The BodyData.</param>
/// <param name="headers">The headers.</param> /// <param name="headers">The headers.</param>
/// <param name="cookies">The cookies.</param> /// <param name="cookies">The cookies.</param>
public RequestMessage(UrlDetails urlDetails, string method, string clientIP, IBodyData? bodyData = null, IDictionary<string, string[]>? headers = null, IDictionary<string, string>? cookies = null) internal RequestMessage(
IWireMockMiddlewareOptions? options,
UrlDetails urlDetails, string method,
string clientIP,
IBodyData? bodyData = null,
IDictionary<string, string[]>? headers = null,
IDictionary<string, string>? cookies = null)
{ {
Guard.NotNull(urlDetails, nameof(urlDetails)); Guard.NotNull(urlDetails, nameof(urlDetails));
Guard.NotNull(method, nameof(method)); Guard.NotNull(method, nameof(method));
@@ -134,7 +155,7 @@ public class RequestMessage : IRequestMessage
Headers = headers?.ToDictionary(header => header.Key, header => new WireMockList<string>(header.Value)); Headers = headers?.ToDictionary(header => header.Key, header => new WireMockList<string>(header.Value));
Cookies = cookies; Cookies = cookies;
RawQuery = urlDetails.Url.Query; RawQuery = urlDetails.Url.Query;
Query = QueryStringParser.Parse(RawQuery); Query = QueryStringParser.Parse(RawQuery, options?.QueryParameterMultipleValueSupport);
} }
/// <summary> /// <summary>

View File

@@ -21,7 +21,8 @@ public partial class Response
{ {
DetectedBodyType = BodyType.String, DetectedBodyType = BodyType.String,
BodyAsString = bodyFactory(req), BodyAsString = bodyFactory(req),
Encoding = encoding ?? Encoding.UTF8 Encoding = encoding ?? Encoding.UTF8,
IsFuncUsed = "Func<IRequestMessage, string>"
} }
}); });
} }
@@ -37,7 +38,8 @@ public partial class Response
{ {
DetectedBodyType = BodyType.String, DetectedBodyType = BodyType.String,
BodyAsString = await bodyFactory(req).ConfigureAwait(false), BodyAsString = await bodyFactory(req).ConfigureAwait(false),
Encoding = encoding ?? Encoding.UTF8 Encoding = encoding ?? Encoding.UTF8,
IsFuncUsed = "Func<IRequestMessage, Task<string>>"
} }
}); });
} }

View File

@@ -325,15 +325,15 @@ public partial class Response : IResponseBuilder
break; break;
default: default:
throw new NotImplementedException($"TransformerType '{TransformerType}' is not supported."); throw new NotSupportedException($"TransformerType '{TransformerType}' is not supported.");
} }
return (responseMessageTransformer.Transform(mapping, requestMessage, responseMessage, UseTransformerForBodyAsFile, TransformerReplaceNodeOptions), null); return (responseMessageTransformer.Transform(mapping, requestMessage, responseMessage, UseTransformerForBodyAsFile, TransformerReplaceNodeOptions), null);
} }
if (!UseTransformer && ResponseMessage.BodyData?.BodyAsFileIsCached == true) if (!UseTransformer && ResponseMessage.BodyData?.BodyAsFileIsCached == true && responseMessage.BodyData?.BodyAsFile is not null)
{ {
ResponseMessage.BodyData.BodyAsBytes = settings.FileSystemHandler.ReadResponseBodyAsFile(responseMessage.BodyData!.BodyAsFile); ResponseMessage.BodyData.BodyAsBytes = settings.FileSystemHandler.ReadResponseBodyAsFile(responseMessage.BodyData.BodyAsFile);
} }
return (responseMessage, null); return (responseMessage, null);

View File

@@ -8,7 +8,7 @@ public class ScenarioState
/// <summary> /// <summary>
/// Gets or sets the Name (from the Scenario). /// Gets or sets the Name (from the Scenario).
/// </summary> /// </summary>
public string? Name { get; set; } public string Name { get; set; } = null!;
/// <summary> /// <summary>
/// Gets or sets the NextState. /// Gets or sets the NextState.

View File

@@ -1,16 +1,25 @@
using System.Linq; using System.Linq;
using Stef.Validation;
using WireMock.Admin.Mappings; using WireMock.Admin.Mappings;
using WireMock.Admin.Requests; using WireMock.Admin.Requests;
using WireMock.Logging; using WireMock.Logging;
using WireMock.Matchers.Request; using WireMock.Matchers.Request;
using WireMock.Owin;
using WireMock.ResponseBuilders; using WireMock.ResponseBuilders;
using WireMock.Types; using WireMock.Types;
namespace WireMock.Serialization; namespace WireMock.Serialization;
internal static class LogEntryMapper internal class LogEntryMapper
{ {
public static LogEntryModel Map(ILogEntry logEntry) private readonly IWireMockMiddlewareOptions _options;
public LogEntryMapper(IWireMockMiddlewareOptions options)
{
_options = Guard.NotNull(options);
}
public LogEntryModel Map(ILogEntry logEntry)
{ {
var logRequestModel = new LogRequestModel var logRequestModel = new LogRequestModel
{ {
@@ -78,25 +87,7 @@ internal static class LogEntryMapper
logResponseModel.DetectedBodyType = logEntry.ResponseMessage.BodyData.DetectedBodyType; logResponseModel.DetectedBodyType = logEntry.ResponseMessage.BodyData.DetectedBodyType;
logResponseModel.DetectedBodyTypeFromContentType = logEntry.ResponseMessage.BodyData.DetectedBodyTypeFromContentType; logResponseModel.DetectedBodyTypeFromContentType = logEntry.ResponseMessage.BodyData.DetectedBodyTypeFromContentType;
switch (logEntry.ResponseMessage.BodyData.DetectedBodyType) MapBody(logEntry, logResponseModel);
{
case BodyType.String:
logResponseModel.Body = logEntry.ResponseMessage.BodyData.BodyAsString;
break;
case BodyType.Json:
logResponseModel.BodyAsJson = logEntry.ResponseMessage.BodyData.BodyAsJson;
break;
case BodyType.Bytes:
logResponseModel.BodyAsBytes = logEntry.ResponseMessage.BodyData.BodyAsBytes;
break;
case BodyType.File:
logResponseModel.BodyAsFile = logEntry.ResponseMessage.BodyData.BodyAsFile;
logResponseModel.BodyAsFileIsCached = logEntry.ResponseMessage.BodyData.BodyAsFileIsCached;
break;
}
logResponseModel.BodyEncoding = logEntry.ResponseMessage.BodyData.Encoding != null logResponseModel.BodyEncoding = logEntry.ResponseMessage.BodyData.Encoding != null
? new EncodingModel ? new EncodingModel
@@ -124,6 +115,36 @@ internal static class LogEntryMapper
}; };
} }
private void MapBody(ILogEntry logEntry, LogResponseModel logResponseModel)
{
switch (logEntry.ResponseMessage.BodyData!.DetectedBodyType)
{
case BodyType.String:
if (!string.IsNullOrEmpty(logEntry.ResponseMessage.BodyData.IsFuncUsed) && _options.DoNotSaveDynamicResponseInLogEntry == true)
{
logResponseModel.Body = logEntry.ResponseMessage.BodyData.IsFuncUsed;
}
else
{
logResponseModel.Body = logEntry.ResponseMessage.BodyData.BodyAsString;
}
break;
case BodyType.Json:
logResponseModel.BodyAsJson = logEntry.ResponseMessage.BodyData.BodyAsJson;
break;
case BodyType.Bytes:
logResponseModel.BodyAsBytes = logEntry.ResponseMessage.BodyData.BodyAsBytes;
break;
case BodyType.File:
logResponseModel.BodyAsFile = logEntry.ResponseMessage.BodyData.BodyAsFile;
logResponseModel.BodyAsFileIsCached = logEntry.ResponseMessage.BodyData.BodyAsFileIsCached;
break;
}
}
private static LogRequestMatchModel? Map(IRequestMatchResult? matchResult) private static LogRequestMatchModel? Map(IRequestMatchResult? matchResult)
{ {
if (matchResult == null) if (matchResult == null)

View File

@@ -13,11 +13,8 @@ internal class MappingToFileSaver
public MappingToFileSaver(WireMockServerSettings settings, MappingConverter mappingConverter) public MappingToFileSaver(WireMockServerSettings settings, MappingConverter mappingConverter)
{ {
Guard.NotNull(settings); _settings = Guard.NotNull(settings);
Guard.NotNull(mappingConverter); _mappingConverter = Guard.NotNull(mappingConverter);
_settings = settings;
_mappingConverter = mappingConverter;
} }
public void SaveMappingToFile(IMapping mapping, string? folder = null) public void SaveMappingToFile(IMapping mapping, string? folder = null)
@@ -30,17 +27,31 @@ internal class MappingToFileSaver
} }
var model = _mappingConverter.ToMappingModel(mapping); 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)); _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";
} }
} }

View File

@@ -125,9 +125,9 @@ public interface IRespondWithAProvider
/// <summary> /// <summary>
/// Support FireAndForget for any configured Webhooks /// Support FireAndForget for any configured Webhooks
/// </summary> /// </summary>
/// <param name="UseWebhooksFireAndForget"></param> /// <param name="useWebhooksFireAndForget"></param>
/// <returns></returns> /// <returns></returns>
IRespondWithAProvider WithWebhookFireAndForget(bool UseWebhooksFireAndForget); IRespondWithAProvider WithWebhookFireAndForget(bool useWebhooksFireAndForget);
/// <summary> /// <summary>
/// Add a Webhook to call after the response has been generated. /// Add a Webhook to call after the response has been generated.
@@ -141,7 +141,7 @@ public interface IRespondWithAProvider
/// <returns>The <see cref="IRespondWithAProvider"/>.</returns> /// <returns>The <see cref="IRespondWithAProvider"/>.</returns>
IRespondWithAProvider WithWebhook( IRespondWithAProvider WithWebhook(
string url, string url,
string? method = "post", string method = "post",
IDictionary<string, WireMockList<string>>? headers = null, IDictionary<string, WireMockList<string>>? headers = null,
string? body = null, string? body = null,
bool useTransformer = true, bool useTransformer = true,
@@ -160,7 +160,7 @@ public interface IRespondWithAProvider
/// <returns>The <see cref="IRespondWithAProvider"/>.</returns> /// <returns>The <see cref="IRespondWithAProvider"/>.</returns>
IRespondWithAProvider WithWebhook( IRespondWithAProvider WithWebhook(
string url, string url,
string? method = "post", string method = "post",
IDictionary<string, WireMockList<string>>? headers = null, IDictionary<string, WireMockList<string>>? headers = null,
object? body = null, object? body = null,
bool useTransformer = true, bool useTransformer = true,

View File

@@ -30,7 +30,7 @@ internal class RespondWithAProvider : IRespondWithAProvider
private readonly WireMockServerSettings _settings; private readonly WireMockServerSettings _settings;
private readonly bool _saveToFile; private readonly bool _saveToFile;
private bool _useWebhookFireAndForget = false; private bool _useWebhookFireAndForget;
public Guid Guid { get; private set; } = Guid.NewGuid(); public Guid Guid { get; private set; } = Guid.NewGuid();

View File

@@ -3,9 +3,7 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Net.Http;
using System.Text; using System.Text;
using System.Threading.Tasks;
using JetBrains.Annotations; using JetBrains.Annotations;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
@@ -18,11 +16,9 @@ using WireMock.Http;
using WireMock.Logging; using WireMock.Logging;
using WireMock.Matchers; using WireMock.Matchers;
using WireMock.Matchers.Request; using WireMock.Matchers.Request;
using WireMock.Proxy;
using WireMock.RequestBuilders; using WireMock.RequestBuilders;
using WireMock.ResponseProviders; using WireMock.ResponseProviders;
using WireMock.Serialization; using WireMock.Serialization;
using WireMock.Settings;
using WireMock.Types; using WireMock.Types;
using WireMock.Util; using WireMock.Util;
@@ -46,6 +42,8 @@ public partial class WireMockServer
private static readonly RegexMatcher AdminRequestContentTypeJson = new ContentTypeMatcher(WireMockConstants.ContentTypeJson, true); private static readonly RegexMatcher AdminRequestContentTypeJson = new ContentTypeMatcher(WireMockConstants.ContentTypeJson, true);
private static readonly RegexMatcher AdminMappingsGuidPathMatcher = new(@"^\/__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 static readonly RegexMatcher AdminMappingsGuidPathMatcher = new(@"^\/__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 static readonly RegexMatcher AdminRequestsGuidPathMatcher = new(@"^\/__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 static readonly RegexMatcher AdminRequestsGuidPathMatcher = new(@"^\/__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 static readonly RegexMatcher AdminScenariosNameMatcher = new(@"^\/__admin\/scenarios\/.+$");
private static readonly RegexMatcher AdminScenariosNameWithResetMatcher = new(@"^\/__admin\/scenarios\/.+\/reset$");
private EnhancedFileSystemWatcher? _enhancedFileSystemWatcher; private EnhancedFileSystemWatcher? _enhancedFileSystemWatcher;
@@ -93,9 +91,11 @@ public partial class WireMockServer
// __admin/scenarios // __admin/scenarios
Given(Request.Create().WithPath(AdminScenarios).UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(ScenariosGet)); 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)); Given(Request.Create().WithPath(AdminScenarios).UsingDelete()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(ScenariosReset));
Given(Request.Create().WithPath(AdminScenariosNameMatcher).UsingDelete()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(ScenarioReset));
// __admin/scenarios/reset // __admin/scenarios/reset
Given(Request.Create().WithPath(AdminScenarios + "/reset").UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(ScenariosReset)); Given(Request.Create().WithPath(AdminScenarios + "/reset").UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(ScenariosReset));
Given(Request.Create().WithPath(AdminScenariosNameWithResetMatcher).UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(ScenarioReset));
// __admin/files/{filename} // __admin/files/{filename}
Given(Request.Create().WithPath(_adminFilesFilenamePathMatcher).UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(FilePost)); Given(Request.Create().WithPath(_adminFilesFilenamePathMatcher).UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(FilePost));
@@ -204,62 +204,6 @@ public partial class WireMockServer
} }
#endregion #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<IResponseMessage> 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 #region Settings
private IResponseMessage SettingsGet(IRequestMessage requestMessage) private IResponseMessage SettingsGet(IRequestMessage requestMessage)
{ {
@@ -277,6 +221,9 @@ public partial class WireMockServer
UseRegexExtended = _settings.UseRegexExtended, UseRegexExtended = _settings.UseRegexExtended,
WatchStaticMappings = _settings.WatchStaticMappings, WatchStaticMappings = _settings.WatchStaticMappings,
WatchStaticMappingsInSubdirectories = _settings.WatchStaticMappingsInSubdirectories, WatchStaticMappingsInSubdirectories = _settings.WatchStaticMappingsInSubdirectories,
HostingScheme = _settings.HostingScheme,
DoNotSaveDynamicResponseInLogEntry = _settings.DoNotSaveDynamicResponseInLogEntry,
QueryParameterMultipleValueSupport = _settings.QueryParameterMultipleValueSupport,
#if USE_ASPNETCORE #if USE_ASPNETCORE
CorsPolicyOptions = _settings.CorsPolicyOptions?.ToString() CorsPolicyOptions = _settings.CorsPolicyOptions?.ToString()
@@ -305,6 +252,8 @@ public partial class WireMockServer
_settings.UseRegexExtended = settings.UseRegexExtended; _settings.UseRegexExtended = settings.UseRegexExtended;
_settings.WatchStaticMappings = settings.WatchStaticMappings; _settings.WatchStaticMappings = settings.WatchStaticMappings;
_settings.WatchStaticMappingsInSubdirectories = settings.WatchStaticMappingsInSubdirectories; _settings.WatchStaticMappingsInSubdirectories = settings.WatchStaticMappingsInSubdirectories;
_settings.DoNotSaveDynamicResponseInLogEntry = settings.DoNotSaveDynamicResponseInLogEntry;
_settings.QueryParameterMultipleValueSupport = settings.QueryParameterMultipleValueSupport;
InitSettings(_settings); InitSettings(_settings);
@@ -525,7 +474,7 @@ public partial class WireMockServer
return ResponseMessageBuilder.Create("Request not found", 404); return ResponseMessageBuilder.Create("Request not found", 404);
} }
var model = LogEntryMapper.Map(entry); var model = new LogEntryMapper(_options).Map(entry);
return ToJson(model); return ToJson(model);
} }
@@ -546,9 +495,10 @@ public partial class WireMockServer
#region Requests #region Requests
private IResponseMessage RequestsGet(IRequestMessage requestMessage) private IResponseMessage RequestsGet(IRequestMessage requestMessage)
{ {
var logEntryMapper = new LogEntryMapper(_options);
var result = LogEntries var result = LogEntries
.Where(r => !r.RequestMessage.Path.StartsWith("/__admin/")) .Where(r => !r.RequestMessage.Path.StartsWith("/__admin/"))
.Select(LogEntryMapper.Map); .Select(logEntryMapper.Map);
return ToJson(result); return ToJson(result);
} }
@@ -578,7 +528,8 @@ public partial class WireMockServer
} }
} }
var result = dict.OrderBy(x => x.Value.AverageTotalScore).Select(x => x.Key).Select(LogEntryMapper.Map); var logEntryMapper = new LogEntryMapper(_options);
var result = dict.OrderBy(x => x.Value.AverageTotalScore).Select(x => x.Key).Select(logEntryMapper.Map);
return ToJson(result); return ToJson(result);
} }
@@ -605,6 +556,17 @@ public partial class WireMockServer
return ResponseMessageBuilder.Create("Scenarios reset"); return ResponseMessageBuilder.Create("Scenarios reset");
} }
private IResponseMessage ScenarioReset(IRequestMessage requestMessage)
{
var name = string.Equals(HttpRequestMethod.DELETE, requestMessage.Method, StringComparison.OrdinalIgnoreCase) ?
requestMessage.Path.Substring(AdminScenarios.Length + 1) :
requestMessage.Path.Split('/').Reverse().Skip(1).First();
return ResetScenario(name) ?
ResponseMessageBuilder.Create("Scenario reset") :
ResponseMessageBuilder.Create($"No scenario found by name '{name}'.", 404);
}
#endregion #endregion
#region Pact #region Pact

View File

@@ -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<IResponseMessage> 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;
}
}

View File

@@ -295,6 +295,8 @@ public partial class WireMockServer : IWireMockServer
_options.DisableJsonBodyParsing = _settings.DisableJsonBodyParsing; _options.DisableJsonBodyParsing = _settings.DisableJsonBodyParsing;
_options.HandleRequestsSynchronously = settings.HandleRequestsSynchronously; _options.HandleRequestsSynchronously = settings.HandleRequestsSynchronously;
_options.SaveUnmatchedRequests = settings.SaveUnmatchedRequests; _options.SaveUnmatchedRequests = settings.SaveUnmatchedRequests;
_options.DoNotSaveDynamicResponseInLogEntry = settings.DoNotSaveDynamicResponseInLogEntry;
_options.QueryParameterMultipleValueSupport = settings.QueryParameterMultipleValueSupport;
if (settings.CustomCertificateDefined) if (settings.CustomCertificateDefined)
{ {
@@ -476,6 +478,13 @@ public partial class WireMockServer : IWireMockServer
_options.Scenarios.Clear(); _options.Scenarios.Clear();
} }
/// <inheritdoc />
[PublicAPI]
public bool ResetScenario(string name)
{
return _options.Scenarios.ContainsKey(name) && _options.Scenarios.TryRemove(name, out _);
}
/// <inheritdoc cref="IWireMockServer.WithMapping(MappingModel[])" /> /// <inheritdoc cref="IWireMockServer.WithMapping(MappingModel[])" />
[PublicAPI] [PublicAPI]
public IWireMockServer WithMapping(params MappingModel[] mappings) public IWireMockServer WithMapping(params MappingModel[] mappings)

View File

@@ -58,4 +58,9 @@ public class ProxyAndRecordSettings : HttpClientSettings
/// Default value is false. /// Default value is false.
/// </summary> /// </summary>
public bool UseDefinedRequestMatchers { get; set; } public bool UseDefinedRequestMatchers { get; set; }
/// <summary>
/// Append an unique GUID to the filename from the saved mapping file.
/// </summary>
public bool AppendGuidToSavedMappingFile { get; set; }
} }

View File

@@ -12,7 +12,6 @@ using WireMock.RegularExpressions;
using WireMock.Types; using WireMock.Types;
#if USE_ASPNETCORE #if USE_ASPNETCORE
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using WireMock.Types;
#endif #endif
namespace WireMock.Settings namespace WireMock.Settings
@@ -253,6 +252,20 @@ namespace WireMock.Settings
[PublicAPI] [PublicAPI]
public bool? SaveUnmatchedRequests { get; set; } public bool? SaveUnmatchedRequests { get; set; }
/// <summary>
/// Don't save the response-string in the LogEntry when WithBody(Func{IRequestMessage, string}) or WithBody(Func{IRequestMessage, Task{string}}) is used. (default set to false).
/// </summary>
[PublicAPI]
public bool? DoNotSaveDynamicResponseInLogEntry { get; set; }
/// <summary>
/// See <seealso cref="QueryParameterMultipleValueSupport"/>.
///
/// Default value = "All".
/// </summary>
[PublicAPI]
public QueryParameterMultipleValueSupport? QueryParameterMultipleValueSupport { get; set; }
/// <summary> /// <summary>
/// Custom matcher mappings for static mappings /// Custom matcher mappings for static mappings
/// </summary> /// </summary>

View File

@@ -1,4 +1,3 @@
using System;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using JetBrains.Annotations; using JetBrains.Annotations;
using Stef.Validation; using Stef.Validation;
@@ -54,20 +53,32 @@ public static class WireMockServerSettingsParser
UseRegexExtended = parser.GetBoolValue(nameof(WireMockServerSettings.UseRegexExtended), true), UseRegexExtended = parser.GetBoolValue(nameof(WireMockServerSettings.UseRegexExtended), true),
WatchStaticMappings = parser.GetBoolValue("WatchStaticMappings"), WatchStaticMappings = parser.GetBoolValue("WatchStaticMappings"),
WatchStaticMappingsInSubdirectories = parser.GetBoolValue("WatchStaticMappingsInSubdirectories"), WatchStaticMappingsInSubdirectories = parser.GetBoolValue("WatchStaticMappingsInSubdirectories"),
HostingScheme = parser.GetEnumValue<HostingScheme>(nameof(WireMockServerSettings.HostingScheme)) HostingScheme = parser.GetEnumValue<HostingScheme>(nameof(WireMockServerSettings.HostingScheme)),
DoNotSaveDynamicResponseInLogEntry = parser.GetBoolValue(nameof(WireMockServerSettings.DoNotSaveDynamicResponseInLogEntry)),
QueryParameterMultipleValueSupport = parser.GetEnumValue<QueryParameterMultipleValueSupport>(nameof(WireMockServerSettings.QueryParameterMultipleValueSupport))
}; };
#if USE_ASPNETCORE #if USE_ASPNETCORE
settings.CorsPolicyOptions = parser.GetEnumValue(nameof(WireMockServerSettings.CorsPolicyOptions), CorsPolicyOptions.None); settings.CorsPolicyOptions = parser.GetEnumValue(nameof(WireMockServerSettings.CorsPolicyOptions), CorsPolicyOptions.None);
#endif #endif
if (logger != null) var loggerType = parser.GetStringValue("WireMockLogger");
switch (loggerType)
{ {
settings.Logger = logger; case nameof(WireMockConsoleLogger):
} settings.Logger = new WireMockConsoleLogger();
else if (parser.GetStringValue("WireMockLogger") == "WireMockConsoleLogger") break;
{
settings.Logger = new WireMockConsoleLogger(); case nameof(WireMockNullLogger):
settings.Logger = new WireMockNullLogger();
break;
default:
if (logger != null)
{
settings.Logger = logger;
}
break;
} }
if (parser.Contains(nameof(WireMockServerSettings.Port))) if (parser.Contains(nameof(WireMockServerSettings.Port)))
@@ -93,6 +104,7 @@ public static class WireMockServerSettingsParser
SaveMappingForStatusCodePattern = parser.GetStringValue("SaveMappingForStatusCodePattern", "*"), SaveMappingForStatusCodePattern = parser.GetStringValue("SaveMappingForStatusCodePattern", "*"),
SaveMappingToFile = parser.GetBoolValue("SaveMappingToFile"), SaveMappingToFile = parser.GetBoolValue("SaveMappingToFile"),
UseDefinedRequestMatchers = parser.GetBoolValue(nameof(ProxyAndRecordSettings.UseDefinedRequestMatchers)), UseDefinedRequestMatchers = parser.GetBoolValue(nameof(ProxyAndRecordSettings.UseDefinedRequestMatchers)),
AppendGuidToSavedMappingFile = parser.GetBoolValue(nameof(ProxyAndRecordSettings.AppendGuidToSavedMappingFile)),
Url = proxyUrl! Url = proxyUrl!
}; };

View File

@@ -10,7 +10,7 @@ interface ITransformer
IBodyData? TransformBody(IMapping mapping, IRequestMessage originalRequestMessage, IResponseMessage originalResponseMessage, IBodyData? bodyData, ReplaceNodeOptions options); IBodyData? TransformBody(IMapping mapping, IRequestMessage originalRequestMessage, IResponseMessage originalResponseMessage, IBodyData? bodyData, ReplaceNodeOptions options);
IDictionary<string, WireMockList<string>>? TransformHeaders(IMapping mapping, IRequestMessage originalRequestMessage, IResponseMessage originalResponseMessage, IDictionary<string, WireMockList<string>>? headers); IDictionary<string, WireMockList<string>> TransformHeaders(IMapping mapping, IRequestMessage originalRequestMessage, IResponseMessage originalResponseMessage, IDictionary<string, WireMockList<string>>? headers);
string TransformString(IMapping mapping, IRequestMessage originalRequestMessage, IResponseMessage originalResponseMessage, string? value); string TransformString(IMapping mapping, IRequestMessage originalRequestMessage, IResponseMessage originalResponseMessage, string? value);
} }

View File

@@ -36,7 +36,7 @@ internal class Transformer : ITransformer
return newBodyData; return newBodyData;
} }
public IDictionary<string, WireMockList<string>>? TransformHeaders( public IDictionary<string, WireMockList<string>> TransformHeaders(
IMapping mapping, IMapping mapping,
IRequestMessage originalRequestMessage, IRequestMessage originalRequestMessage,
IResponseMessage originalResponseMessage, IResponseMessage originalResponseMessage,
@@ -239,6 +239,7 @@ internal class Transformer : ITransformer
} }
} }
// ReSharper disable once UnusedParameter.Local
private static void ReplaceNodeValue(ReplaceNodeOptions options, JToken node, string transformedString) private static void ReplaceNodeValue(ReplaceNodeOptions options, JToken node, string transformedString)
{ {
StringUtils.TryParseQuotedString(transformedString, out var result, out _); StringUtils.TryParseQuotedString(transformedString, out var result, out _);
@@ -270,13 +271,13 @@ internal class Transformer : ITransformer
Encoding = original.Encoding, Encoding = original.Encoding,
DetectedBodyType = original.DetectedBodyType, DetectedBodyType = original.DetectedBodyType,
DetectedBodyTypeFromContentType = original.DetectedBodyTypeFromContentType, DetectedBodyTypeFromContentType = original.DetectedBodyTypeFromContentType,
BodyAsString = handlebarsContext.ParseAndRender(original.BodyAsString, model) BodyAsString = handlebarsContext.ParseAndRender(original.BodyAsString!, model)
}; };
} }
private static IBodyData TransformBodyAsFile(ITransformerContext handlebarsContext, object model, IBodyData original, bool useTransformerForBodyAsFile) private static IBodyData TransformBodyAsFile(ITransformerContext handlebarsContext, object model, IBodyData original, bool useTransformerForBodyAsFile)
{ {
string transformedBodyAsFilename = handlebarsContext.ParseAndRender(original.BodyAsFile, model); string transformedBodyAsFilename = handlebarsContext.ParseAndRender(original.BodyAsFile!, model);
if (!useTransformerForBodyAsFile) if (!useTransformerForBodyAsFile)
{ {

View File

@@ -5,7 +5,7 @@ using System.Net.Http.Headers;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Stef.Validation; using Stef.Validation;
using WireMock.Http; using WireMock.Constants;
using WireMock.Matchers; using WireMock.Matchers;
using WireMock.Types; using WireMock.Types;
@@ -29,15 +29,15 @@ internal static class BodyParser
*/ */
private static readonly IDictionary<string, bool> BodyAllowedForMethods = new Dictionary<string, bool> private static readonly IDictionary<string, bool> BodyAllowedForMethods = new Dictionary<string, bool>
{ {
{ HttpRequestMethods.HEAD, false }, { HttpRequestMethod.HEAD, false },
{ HttpRequestMethods.GET, false }, { HttpRequestMethod.GET, false },
{ HttpRequestMethods.PUT, true }, { HttpRequestMethod.PUT, true },
{ HttpRequestMethods.POST, true }, { HttpRequestMethod.POST, true },
{ HttpRequestMethods.DELETE, true }, { HttpRequestMethod.DELETE, true },
{ HttpRequestMethods.TRACE, false }, { HttpRequestMethod.TRACE, false },
{ HttpRequestMethods.OPTIONS, true }, { HttpRequestMethod.OPTIONS, true },
{ HttpRequestMethods.CONNECT, false }, { HttpRequestMethod.CONNECT, false },
{ HttpRequestMethods.PATCH, true } { HttpRequestMethod.PATCH, true }
}; };
private static readonly IStringMatcher[] MultipartContentTypesMatchers = { private static readonly IStringMatcher[] MultipartContentTypesMatchers = {
@@ -122,7 +122,7 @@ internal static class BodyParser
// In case of MultiPart: check if the BodyAsBytes is a valid UTF8 or ASCII string, in that case read as String else keep as-is // In case of MultiPart: check if the BodyAsBytes is a valid UTF8 or ASCII string, in that case read as String else keep as-is
if (data.DetectedBodyTypeFromContentType == BodyType.MultiPart) if (data.DetectedBodyTypeFromContentType == BodyType.MultiPart)
{ {
if (BytesEncodingUtils.TryGetEncoding(data.BodyAsBytes, out Encoding encoding) && if (BytesEncodingUtils.TryGetEncoding(data.BodyAsBytes, out var encoding) &&
SupportedBodyAsStringEncodingForMultipart.Select(x => x.Equals(encoding)).Any()) SupportedBodyAsStringEncodingForMultipart.Select(x => x.Equals(encoding)).Any())
{ {
data.BodyAsString = encoding.GetString(data.BodyAsBytes); data.BodyAsString = encoding.GetString(data.BodyAsBytes);

View File

@@ -11,25 +11,41 @@ namespace WireMock.Util;
/// </summary> /// </summary>
internal static class QueryStringParser internal static class QueryStringParser
{ {
public static IDictionary<string, WireMockList<string>> Parse(string queryString) private static readonly Dictionary<string, WireMockList<string>> Empty = new();
public static IDictionary<string, WireMockList<string>> Parse(string? queryString, QueryParameterMultipleValueSupport? support = null)
{ {
if (string.IsNullOrEmpty(queryString)) if (string.IsNullOrEmpty(queryString))
{ {
return new Dictionary<string, WireMockList<string>>(); return Empty;
} }
var queryParameterMultipleValueSupport = support ?? QueryParameterMultipleValueSupport.All;
string[] JoinParts(string[] parts) string[] JoinParts(string[] parts)
{ {
if (parts.Length > 1) if (parts.Length > 1)
{ {
return parts[1].Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries); // support "?key=1,2" return queryParameterMultipleValueSupport.HasFlag(QueryParameterMultipleValueSupport.Comma) ?
parts[1].Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries) : // Support "?key=1,2"
new[] { parts[1] };
} }
return new string[0]; return new string[0];
} }
return queryString.TrimStart('?') var splitOn = new List<string>();
.Split(new[] { '&', ';' }, StringSplitOptions.RemoveEmptyEntries) // Support "?key=value;key=anotherValue" and "?key=value&key=anotherValue" if (queryParameterMultipleValueSupport.HasFlag(QueryParameterMultipleValueSupport.Ampersand))
{
splitOn.Add("&"); // Support "?key=value&key=anotherValue"
}
if (queryParameterMultipleValueSupport.HasFlag(QueryParameterMultipleValueSupport.SemiColon))
{
splitOn.Add(";"); // Support "?key=value;key=anotherValue"
}
return queryString!.TrimStart('?')
.Split(splitOn.ToArray(), StringSplitOptions.RemoveEmptyEntries)
.Select(parameter => parameter.Split(new[] { '=' }, 2, StringSplitOptions.RemoveEmptyEntries)) .Select(parameter => parameter.Split(new[] { '=' }, 2, StringSplitOptions.RemoveEmptyEntries))
.GroupBy(parts => parts[0], JoinParts) .GroupBy(parts => parts[0], JoinParts)
.ToDictionary(grouping => grouping.Key, grouping => new WireMockList<string>(grouping.SelectMany(x => x).Select(WebUtility.UrlDecode))); .ToDictionary(grouping => grouping.Key, grouping => new WireMockList<string>(grouping.SelectMany(x => x).Select(WebUtility.UrlDecode)));

View File

@@ -21,10 +21,6 @@
<PublicSign Condition=" '$(OS)' != 'Windows_NT' ">true</PublicSign> <PublicSign Condition=" '$(OS)' != 'Windows_NT' ">true</PublicSign>
</PropertyGroup> </PropertyGroup>
<!--<ItemGroup>
<None Include="WireMock.Net-Logo.png" Pack="true" PackagePath="../../"/>
</ItemGroup>-->
<!-- https://github.com/aspnet/RoslynCodeDomProvider/issues/51 --> <!-- https://github.com/aspnet/RoslynCodeDomProvider/issues/51 -->
<!-- This is needed else we cannot build net452 in Azure DevOps Pipeline --> <!-- This is needed else we cannot build net452 in Azure DevOps Pipeline -->
<!--<Target Name="CheckIfShouldKillVBCSCompiler" />--> <!--<Target Name="CheckIfShouldKillVBCSCompiler" />-->
@@ -145,7 +141,6 @@
</ItemGroup> </ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netcoreapp3.1' "> <ItemGroup Condition=" '$(TargetFramework)' == 'netcoreapp3.1' ">
<!--<PackageReference Include="System.Diagnostics.CodeAnalysis" Version="4.3.0" />-->
<PackageReference Include="Nullable" Version="1.3.0" /> <PackageReference Include="Nullable" Version="1.3.0" />
</ItemGroup> </ItemGroup>

Some files were not shown because too many files have changed in this diff Show More