Compare commits

..

32 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
Stef Heyenrath
dd1a6fa508 1.5.8 2022-10-16 09:13:18 +02:00
Stef Heyenrath
36037627bc WebHook - Transform Url (#824)
* WebHook - Url

* .
2022-10-15 08:55:05 +02:00
Stef Heyenrath
55afc8041f ExactMatcher : IgnoreCase (#817)
* ...

* mm

* fix some null warnings

* fx
2022-10-15 08:23:58 +02:00
Stef Heyenrath
b523ab9125 Some fixes to WireMock.Net.Assertions (#816)
* Add extra unit test for UsingPost

* .X

* ok

* ok2

* header
2022-10-15 08:21:48 +02:00
Stef Heyenrath
14dd619763 1.5.7 2022-10-11 09:57:11 +02:00
Stef Heyenrath
430c01a461 Add implicit operators to WireMockList (#823)
* Add implicit operators to WireMockList

* .
2022-10-01 10:50:18 +02:00
Stef Heyenrath
f7b04f3234 Add UseDefinedRequestMatchers to ProxyAndRecordSettings (#821)
* .

* UseDefinedRequestMatchers

* ok

* .

* ClientIP

* t

* fix ut

* .

* cf

* cf2
2022-09-30 11:25:11 +02:00
Stef Heyenrath
c0b18631a3 Add option to run the server on http & https (#818)
* HostingProtocol.HttpAndHttps

* .

* .

* .

* HostingScheme
2022-09-26 14:24:45 +02:00
150 changed files with 5254 additions and 3082 deletions

4
.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
@@ -9,4 +9,4 @@ community_bridge: # Replace with a single Community Bridge project-name e.g., cl
liberapay: # Replace with a single Liberapay username liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username otechie: # Replace with a single Otechie username
custom: https://www.paypal.me/stefheyenrath custom: https://www.paypal.me/stefheyenrath

View File

@@ -1,3 +1,34 @@
# 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)
- [#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)
- [#824](https://github.com/WireMock-Net/WireMock.Net/pull/824) - WebHook - Transform Url [feature] contributed by [StefH](https://github.com/StefH)
- [#814](https://github.com/WireMock-Net/WireMock.Net/issues/814) - WithHeader cannot handle multiple requests with the same header key values [bug]
- [#815](https://github.com/WireMock-Net/WireMock.Net/issues/815) - Why does UsingMethod check _callscount? [bug]
- [#822](https://github.com/WireMock-Net/WireMock.Net/issues/822) - Webhook with generic url, body and custom header values [feature]
# 1.5.7 (11 October 2022)
- [#818](https://github.com/WireMock-Net/WireMock.Net/pull/818) - Add option to run the server on http &amp; https [feature] contributed by [StefH](https://github.com/StefH)
- [#821](https://github.com/WireMock-Net/WireMock.Net/pull/821) - Add UseDefinedRequestMatchers to ProxyAndRecordSettings [feature] contributed by [StefH](https://github.com/StefH)
- [#823](https://github.com/WireMock-Net/WireMock.Net/pull/823) - Add implicit operators to WireMockList contributed by [StefH](https://github.com/StefH)
- [#819](https://github.com/WireMock-Net/WireMock.Net/issues/819) - Can I preserve Mapping title and matchers for proxy response? [feature]
# 1.5.6 (12 September 2022) # 1.5.6 (12 September 2022)
- [#803](https://github.com/WireMock-Net/WireMock.Net/pull/803) - WebHook : UseFireAndForget + Delay [feature] contributed by [StefH](https://github.com/StefH) - [#803](https://github.com/WireMock-Net/WireMock.Net/pull/803) - WebHook : UseFireAndForget + Delay [feature] contributed by [StefH](https://github.com/StefH)
- [#804](https://github.com/WireMock-Net/WireMock.Net/pull/804) - Change nuget to package reference for WireMock.Net.Console.Net472.Cla&#8230; [feature] contributed by [mattisking](https://github.com/mattisking) - [#804](https://github.com/WireMock-Net/WireMock.Net/pull/804) - Change nuget to package reference for WireMock.Net.Console.Net472.Cla&#8230; [feature] contributed by [mattisking](https://github.com/mattisking)

View File

@@ -4,7 +4,7 @@
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
<VersionPrefix>1.5.6</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.6 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,7 +1,6 @@
# 1.5.6 (12 September 2022) # 1.5.11 (24 November 2022)
- #803 WebHook : UseFireAndForget + Delay [feature] - #836 Add Settings.QueryParameterMultipleValueSupport [feature]
- #804 Change nuget to package reference for WireMock.Net.Console.Net472.Cla&#8230; [feature] - #848 Use try-catch when adding or removing logEntry [bug]
- #806 Tweak middleware and fix bug in example [feature] - #846 Exception ArgumentOutOfRangeException [bug]
- #801 Webhook Delays [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

@@ -43,14 +43,12 @@ jobs:
command: 'build' command: 'build'
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'
condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest')) # Do not run for PullRequests condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest')) # Do not run for PullRequests
@@ -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

@@ -0,0 +1,8 @@
{
"profiles": {
"WSL": {
"commandName": "WSL2",
"distributionName": ""
}
}
}

View File

@@ -40,6 +40,19 @@ namespace WireMock.Net.ConsoleApplication
var s = WireMockServer.Start(); var s = WireMockServer.Start();
s.Stop(); s.Stop();
var httpAndHttpsWithPort = WireMockServer.Start(new WireMockServerSettings
{
HostingScheme = HostingScheme.HttpAndHttps,
Port = 12399
});
httpAndHttpsWithPort.Stop();
var httpAndHttpsFree = WireMockServer.Start(new WireMockServerSettings
{
HostingScheme = HostingScheme.HttpAndHttps
});
httpAndHttpsFree.Stop();
string url1 = "http://localhost:9091/"; string url1 = "http://localhost:9091/";
string url2 = "http://localhost:9092/"; string url2 = "http://localhost:9092/";
string url3 = "https://localhost:9443/"; string url3 = "https://localhost:9443/";

View File

@@ -1,14 +1,14 @@
<?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>
</configuration> </configuration>

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

@@ -1,8 +1,16 @@
{ {
"profiles": { "profiles": {
"WireMock.Net.StandAlone.NETCoreApp": { "WireMock.Net.StandAlone.NETCoreApp": {
"commandName": "Project", "commandName": "Project",
"commandLineArgs": "--Urls http://localhost:9091 --CorsPolicyOptions AllowAll --WireMockLogger WireMockConsoleLogger" "commandLineArgs": "--Urls http://localhost:9091 --CorsPolicyOptions AllowAll --WireMockLogger WireMockConsoleLogger"
},
"WireMock.Net.StandAlone.NETCoreAppNoPort": {
"commandName": "Project",
"commandLineArgs": "--CorsPolicyOptions AllowAll --WireMockLogger WireMockConsoleLogger"
},
"WireMock.Net.StandAlone.NETCoreAppWithHostingProtocol": {
"commandName": "Project",
"commandLineArgs": "--HostingProtocol HttpAndHttps --CorsPolicyOptions AllowAll --WireMockLogger WireMockConsoleLogger"
}
} }
}
} }

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -1,31 +1,39 @@
using System.Collections.Generic; using System.Collections.Generic;
namespace WireMock.Admin.Mappings namespace WireMock.Admin.Mappings;
/// <summary>
/// Cookie Model
/// </summary>
[FluentBuilder.AutoGenerateBuilder]
public class CookieModel
{ {
/// <summary> /// <summary>
/// Cookie Model /// Gets or sets the name.
/// </summary> /// </summary>
[FluentBuilder.AutoGenerateBuilder] public string Name { get; set; } = null!;
public class CookieModel
{
/// <summary>
/// Gets or sets the name.
/// </summary>
public string Name { get; set; } = null!;
/// <summary> /// <summary>
/// Gets or sets the matchers. /// Gets or sets the matchers.
/// </summary> /// </summary>
public IList<MatcherModel>? Matchers { get; set; } public IList<MatcherModel>? Matchers { get; set; }
/// <summary> /// <summary>
/// Gets or sets the ignore case. /// Gets or sets the ignore case.
/// </summary> /// </summary>
public bool? IgnoreCase { get; set; } public bool? IgnoreCase { get; set; }
/// <summary> /// <summary>
/// Reject on match. /// Reject on match.
/// </summary> /// </summary>
public bool? RejectOnMatch { get; set; } public bool? RejectOnMatch { get; set; }
}
/// <summary>
/// The Operator to use when matchers are defined. [Optional]
/// - null = Same as "or".
/// - "or" = Only one pattern should match.
/// - "and" = All patterns should match.
/// - "average" = The average value from all patterns.
/// </summary>
public string? MatchOperator { get; set; }
} }

View File

@@ -1,58 +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>
/// 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>true</c>, the defined mappings in the request wil be used for the new mapping.
///
/// Default value is false.
/// </summary>
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;
@@ -13,12 +11,12 @@ public interface IBodyData
/// <summary> /// <summary>
/// The body (as bytearray). /// The body (as bytearray).
/// </summary> /// </summary>
byte[] BodyAsBytes { get; set; } byte[]? BodyAsBytes { get; set; }
/// <summary> /// <summary>
/// Gets or sets the body as a file. /// Gets or sets the body as a file.
/// </summary> /// </summary>
string BodyAsFile { get; set; } string? BodyAsFile { get; set; }
/// <summary> /// <summary>
/// Is the body as file cached? /// Is the body as file cached?
@@ -38,7 +36,7 @@ public interface IBodyData
/// <summary> /// <summary>
/// The body as string, this is defined when BodyAsString or BodyAsJson are not null. /// The body as string, this is defined when BodyAsString or BodyAsJson are not null.
/// </summary> /// </summary>
string BodyAsString { get; set; } string? BodyAsString { get; set; }
/// <summary> /// <summary>
/// The detected body type (detection based on body content). /// The detected body type (detection based on body content).
@@ -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,15 @@
using System;
namespace WireMock.Types;
[Flags]
public enum HostingScheme
{
None = 0x0,
Http = 0x1,
Https = 0x2,
HttpAndHttps = Http | Https
}

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

@@ -1,44 +1,69 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
namespace WireMock.Types namespace WireMock.Types;
/// <summary>
/// A special List which overrides the ToString() to return first value.
/// </summary>
/// <typeparam name="T">The generic type</typeparam>
/// <seealso cref="List{T}" />
public class WireMockList<T> : List<T>
{ {
/// <summary> /// <summary>
/// A special List which overrides the ToString() to return first value. /// Initializes a new instance of the <see cref="WireMockList{T}"/> class.
/// </summary> /// </summary>
/// <typeparam name="T">The generic type</typeparam> public WireMockList()
/// <seealso cref="List{T}" />
public class WireMockList<T> : List<T>
{ {
/// <summary> }
/// Initializes a new instance of the <see cref="WireMockList{T}"/> class.
/// </summary>
public WireMockList()
{
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="WireMockList{T}"/> class. /// Initializes a new instance of the <see cref="WireMockList{T}"/> class.
/// </summary> /// </summary>
/// <param name="collection">The collection whose elements are copied to the new list.</param> /// <param name="collection">The collection whose elements are copied to the new list.</param>
public WireMockList(params T[] collection) : base(collection) public WireMockList(params T[] collection) : base(collection)
{ {
} }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="WireMockList{T}"/> class. /// Initializes a new instance of the <see cref="WireMockList{T}"/> class.
/// </summary> /// </summary>
/// <param name="collection">The collection whose elements are copied to the new list.</param> /// <param name="collection">The collection whose elements are copied to the new list.</param>
public WireMockList(IEnumerable<T> collection) : base(collection) public WireMockList(IEnumerable<T> collection) : base(collection)
{ {
} }
/// <summary> /// <summary>
/// Returns a <see cref="string" /> that represents this instance. /// Operator for setting T
/// </summary> /// </summary>
public override string ToString() /// <param name="value">The value to set.</param>
public static implicit operator WireMockList<T>(T value) => new(value);
/// <summary>
/// Operator for setting T[]
/// </summary>
/// <param name="values">The values to set.</param>
public static implicit operator WireMockList<T>(T[] values) => new(values);
/// <summary>
/// Returns a <see cref="string" /> that represents this instance.
/// </summary>
public override string ToString()
{
switch (Count)
{ {
return this.Any() ? this.First().ToString() : base.ToString(); case 0:
return string.Empty;
case 1:
if (this[0] is string strValue)
{
return strValue;
}
return this[0]?.ToString();
default:
return base.ToString();
} }
} }
} }

View File

@@ -1,97 +1,138 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
using System;
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;
// ReSharper disable once CheckNamespace // ReSharper disable once CheckNamespace
namespace WireMock.FluentAssertions; namespace WireMock.FluentAssertions;
public class WireMockAssertions public class WireMockAssertions
{ {
private readonly IWireMockServer _subject; private const string Any = "*";
private readonly int? _callsCount; private readonly int? _callsCount;
private IReadOnlyList<IRequestMessage> _requestMessages;
private readonly IReadOnlyList<KeyValuePair<string, WireMockList<string>>> _headers;
public WireMockAssertions(IWireMockServer subject, int? callsCount) public WireMockAssertions(IWireMockServer subject, int? callsCount)
{ {
_subject = subject;
_callsCount = callsCount; _callsCount = callsCount;
_requestMessages = subject.LogEntries.Select(logEntry => logEntry.RequestMessage).ToList();
_headers = _requestMessages.SelectMany(req => req.Headers).ToList();
} }
[CustomAssertion] [CustomAssertion]
public AndConstraint<WireMockAssertions> 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);
var (filter, condition) = BuildFilterAndCondition(predicate);
Execute.Assertion Execute.Assertion
.BecauseOf(because, becauseArgs) .BecauseOf(because, becauseArgs)
.Given(() => _subject.LogEntries.Select(x => x.RequestMessage).ToList()) .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
)
.Then .Then
.ForCondition(x => (_callsCount == null && x.Any(y => y.AbsoluteUrl == absoluteUrl)) || (_callsCount == x.Count(y => y.AbsoluteUrl == absoluteUrl))) .ForCondition(condition)
.FailWith( .FailWith(
"Expected {context:wiremockserver} to have been called at address matching the absolute url {0}{reason}, but didn't find it among the calls to {1}.", "Expected {context:wiremockserver} to have been called at address matching the absolute url {0}{reason}, but didn't find it among the calls to {1}.",
_ => absoluteUrl, requests => requests.Select(request => request.AbsoluteUrl)); _ => absoluteUrl, requests => requests.Select(request => request.AbsoluteUrl)
);
return new AndConstraint<WireMockAssertions>(this); _requestMessages = filter(_requestMessages).ToList();
return new AndWhichConstraint<WireMockAssertions, string>(this, absoluteUrl);
} }
[CustomAssertion] [CustomAssertion]
public AndConstraint<WireMockAssertions> 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);
var (filter, condition) = BuildFilterAndCondition(predicate);
Execute.Assertion Execute.Assertion
.BecauseOf(because, becauseArgs) .BecauseOf(because, becauseArgs)
.Given(() => _subject.LogEntries.Select(x => x.RequestMessage).ToList()) .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(x => (_callsCount == null && x.Any(y => y.Url == url)) || (_callsCount == x.Count(y => y.Url == url))) .ForCondition(condition)
.FailWith( .FailWith(
"Expected {context:wiremockserver} to have been called at address matching the url {0}{reason}, but didn't find it among the calls to {1}.", "Expected {context:wiremockserver} to have been called at address matching the url {0}{reason}, but didn't find it among the calls to {1}.",
_ => url, requests => requests.Select(request => request.Url)); _ => url,
requests => requests.Select(request => request.Url)
);
return new AndConstraint<WireMockAssertions>(this); _requestMessages = filter(_requestMessages).ToList();
return new AndWhichConstraint<WireMockAssertions, string>(this, url);
} }
[CustomAssertion] [CustomAssertion]
public AndConstraint<WireMockAssertions> 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);
var (filter, condition) = BuildFilterAndCondition(predicate);
Execute.Assertion Execute.Assertion
.BecauseOf(because, becauseArgs) .BecauseOf(because, becauseArgs)
.Given(() => _subject.LogEntries.Select(x => x.RequestMessage).ToList()) .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
)
.Then .Then
.ForCondition(x => (_callsCount == null && x.Any(y => y.ProxyUrl == proxyUrl)) || (_callsCount == x.Count(y => y.ProxyUrl == proxyUrl))) .ForCondition(condition)
.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, requests => requests.Select(request => request.ProxyUrl)); _ => proxyUrl,
requests => requests.Select(request => request.ProxyUrl)
);
return new AndConstraint<WireMockAssertions>(this); _requestMessages = filter(_requestMessages).ToList();
return new AndWhichConstraint<WireMockAssertions, string>(this, proxyUrl);
} }
[CustomAssertion] [CustomAssertion]
public AndConstraint<WireMockAssertions> 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);
var (filter, condition) = BuildFilterAndCondition(predicate);
Execute.Assertion Execute.Assertion
.BecauseOf(because, becauseArgs) .BecauseOf(because, becauseArgs)
.Given(() => _subject.LogEntries.Select(x => x.RequestMessage).ToList()) .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 .Then
.ForCondition(x => (_callsCount == null && x.Any(y => y.ClientIP == clientIP)) || (_callsCount == x.Count(y => y.ClientIP == clientIP))) .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)
);
return new AndConstraint<WireMockAssertions>(this); _requestMessages = filter(_requestMessages).ToList();
return new AndWhichConstraint<WireMockAssertions, string>(this, clientIP);
} }
[CustomAssertion] [CustomAssertion]
@@ -101,28 +142,25 @@ public class WireMockAssertions
[CustomAssertion] [CustomAssertion]
public AndConstraint<WireMockAssertions> WithHeader(string expectedKey, string[] expectedValues, string because = "", params object[] becauseArgs) public AndConstraint<WireMockAssertions> WithHeader(string expectedKey, string[] expectedValues, string because = "", params object[] becauseArgs)
{ {
var headersDictionary = _subject.LogEntries.SelectMany(x => x.RequestMessage.Headers)
.ToDictionary(x => x.Key, x => x.Value);
using (new AssertionScope("headers from requests sent")) using (new AssertionScope("headers from requests sent"))
{ {
headersDictionary.Should().ContainKey(expectedKey, because, becauseArgs); _headers.Select(h => h.Key).Should().Contain(expectedKey, because, becauseArgs);
} }
using (new AssertionScope($"header \"{expectedKey}\" from requests sent with value(s)")) using (new AssertionScope($"header \"{expectedKey}\" from requests sent with value(s)"))
{ {
var headerValues = _headers.First(h => h.Key == expectedKey).Value;
if (expectedValues.Length == 1) if (expectedValues.Length == 1)
{ {
headersDictionary[expectedKey].Should().Contain(expectedValues.First()); headerValues.Should().Contain(expectedValues.First(), because, becauseArgs);
} }
else else
{ {
var trimmedHeaderValues = string.Join(",", headersDictionary[expectedKey].Select(x => x)).Split(',') var trimmedHeaderValues = string.Join(",", headerValues.Select(x => x)).Split(',').Select(x => x.Trim()).ToList();
.Select(x => x.Trim())
.ToList();
foreach (var expectedValue in expectedValues) foreach (var expectedValue in expectedValues)
{ {
trimmedHeaderValues.Should().Contain(expectedValue); trimmedHeaderValues.Should().Contain(expectedValue, because, becauseArgs);
} }
} }
} }
@@ -132,56 +170,78 @@ 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)
{ {
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);
Execute.Assertion Execute.Assertion
.BecauseOf(because, becauseArgs) .BecauseOf(because, becauseArgs)
.Given(() => _subject.LogEntries.Select(x => x.RequestMessage).ToList()) .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
)
.Then .Then
.ForCondition(x => (_callsCount == null && x.Any(y => y.Method == method)) || (_callsCount == x.Count(y => y.Method == method))) .ForCondition(condition)
.FailWith( .FailWith(
"Expected {context:wiremockserver} to have been called using method {0}{reason}, but didn't find it among the methods {1}.", "Expected {context:wiremockserver} to have been called using method {0}{reason}, but didn't find it among the methods {1}.",
_ => method, requests => requests.Select(request => request.Method)); _ => method,
requests => requests.Select(request => request.Method)
);
_requestMessages = filter(_requestMessages).ToList();
return new AndConstraint<WireMockAssertions>(this); return new AndConstraint<WireMockAssertions>(this);
} }
private (Func<IReadOnlyList<IRequestMessage>, IReadOnlyList<IRequestMessage>> Filter, Func<IReadOnlyList<IRequestMessage>, bool> Condition) BuildFilterAndCondition(Func<IRequestMessage, bool> predicate)
{
Func<IReadOnlyList<IRequestMessage>, IReadOnlyList<IRequestMessage>> filter = requests => requests.Where(predicate).ToList();
return (filter, requests => (_callsCount is null && filter(requests).Any()) || _callsCount == filter(requests).Count);
}
} }

View File

@@ -22,6 +22,7 @@
<!--<DelaySign>true</DelaySign>--> <!--<DelaySign>true</DelaySign>-->
<PublicSign Condition=" '$(OS)' != 'Windows_NT' ">true</PublicSign> <PublicSign Condition=" '$(OS)' != 'Windows_NT' ">true</PublicSign>
<PackageLicenseExpression>MIT</PackageLicenseExpression> <PackageLicenseExpression>MIT</PackageLicenseExpression>
<LangVersion>10</LangVersion>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' "> <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">

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> /// <summary>
/// A interface defining the example values to use for the different types. /// A interface defining the example values to use for the different types.
/// </summary> /// </summary>
public interface IWireMockOpenApiParserExampleValues public interface IWireMockOpenApiParserExampleValues
{ {
/// <summary> /// <summary>
/// An example value for a Boolean. /// An example value for a Boolean.
/// </summary> /// </summary>
bool Boolean { get; set; } bool Boolean { get; set; }
/// <summary>
/// An example value for an Integer. /// <summary>
/// </summary> /// An example value for an Integer.
int Integer { get; set; } /// </summary>
/// <summary> int Integer { get; set; }
/// An example value for a Float.
/// </summary> /// <summary>
float Float { get; set; } /// An example value for a Float.
/// <summary> /// </summary>
/// An example value for a Double. float Float { get; set; }
/// </summary>
double Double { get; set; } /// <summary>
/// An example value for a Double.
/// <summary> /// </summary>
/// An example value for a Date. double Double { get; set; }
/// </summary>
Func<DateTime> Date { get; set; } /// <summary>
/// An example value for a Date.
/// <summary> /// </summary>
/// An example value for a DateTime. Func<DateTime> Date { get; set; }
/// </summary>
Func<DateTime> DateTime { get; set; } /// <summary>
/// An example value for a DateTime.
/// <summary> /// </summary>
/// An example value for Bytes. Func<DateTime> DateTime { get; set; }
/// </summary>
byte[] Bytes { get; set; } /// <summary>
/// An example value for Bytes.
/// <summary> /// </summary>
/// An example value for a Object. byte[] Bytes { get; set; }
/// </summary>
object Object { get; set; } /// <summary>
/// An example value for a Object.
/// <summary> /// </summary>
/// An example value for a String. object Object { get; set; }
/// </summary>
string String { get; set; } /// <summary>
/// An example value for a String.
/// <summary> /// </summary>
/// OpenApi Schema to generate dynamic examples more accurate string String { get; set; }
/// </summary>
OpenApiSchema Schema { 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> /// <summary>
/// A class defining the random example values to use for the different types. /// A class defining the random example values to use for the different types.
/// </summary> /// </summary>
public class WireMockOpenApiParserDynamicExampleValues : IWireMockOpenApiParserExampleValues public class WireMockOpenApiParserDynamicExampleValues : IWireMockOpenApiParserExampleValues
{ {
/// <inheritdoc /> /// <inheritdoc />
public virtual bool Boolean { get { return RandomizerFactory.GetRandomizer(new FieldOptionsBoolean()).Generate() ?? true; } set { } } public virtual bool Boolean { get => RandomizerFactory.GetRandomizer(new FieldOptionsBoolean()).Generate() ?? true; set { } }
/// <inheritdoc />
public virtual int Integer { get { return RandomizerFactory.GetRandomizer(new FieldOptionsInteger()).Generate() ?? 42; } set { } } /// <inheritdoc />
/// <inheritdoc /> public virtual int Integer { get => RandomizerFactory.GetRandomizer(new FieldOptionsInteger()).Generate() ?? 42; set { } }
public virtual float Float { get { return RandomizerFactory.GetRandomizer(new FieldOptionsFloat()).Generate() ?? 4.2f; } set { } }
/// <inheritdoc /> /// <inheritdoc />
public virtual double Double { get { return RandomizerFactory.GetRandomizer(new FieldOptionsDouble()).Generate() ?? 4.2d; } set { } } public virtual float Float { get => RandomizerFactory.GetRandomizer(new FieldOptionsFloat()).Generate() ?? 4.2f; set { } }
/// <inheritdoc />
public virtual Func<DateTime> Date { get { return () => RandomizerFactory.GetRandomizer(new FieldOptionsDateTime()).Generate() ?? System.DateTime.UtcNow.Date; } set { } } /// <inheritdoc />
/// <inheritdoc /> public virtual double Double { get => RandomizerFactory.GetRandomizer(new FieldOptionsDouble()).Generate() ?? 4.2d; set { } }
public virtual Func<DateTime> DateTime { get { return () => RandomizerFactory.GetRandomizer(new FieldOptionsDateTime()).Generate() ?? System.DateTime.UtcNow; } set { } }
/// <inheritdoc /> /// <inheritdoc />
public virtual byte[] Bytes { get { return RandomizerFactory.GetRandomizer(new FieldOptionsBytes()).Generate(); } set { } } public virtual Func<DateTime> Date { get { return () => RandomizerFactory.GetRandomizer(new FieldOptionsDateTime()).Generate() ?? System.DateTime.UtcNow.Date; } set { } }
/// <inheritdoc />
public virtual object Object { get; set; } = "example-object"; /// <inheritdoc />
/// <inheritdoc /> public virtual Func<DateTime> DateTime { get { return () => RandomizerFactory.GetRandomizer(new FieldOptionsDateTime()).Generate() ?? System.DateTime.UtcNow; } set { } }
public virtual string String { get { return RandomizerFactory.GetRandomizer(new FieldOptionsTextRegex { Pattern = @"^[0-9]{2}[A-Z]{5}[0-9]{2}" }).Generate() ?? "example-string"; } set { } }
/// <inheritdoc /> /// <inheritdoc />
public virtual OpenApiSchema Schema { get; set; } public virtual byte[] Bytes { get => RandomizerFactory.GetRandomizer(new FieldOptionsBytes()).Generate(); set { } }
}
/// <inheritdoc />
public virtual object Object { get; set; } = "example-object";
/// <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> /// <summary>
/// A class defining the example values to use for the different types. /// A class defining the example values to use for the different types.
/// </summary> /// </summary>
public class WireMockOpenApiParserExampleValues : IWireMockOpenApiParserExampleValues public class WireMockOpenApiParserExampleValues : IWireMockOpenApiParserExampleValues
{ {
/// <inheritdoc /> /// <inheritdoc />
public virtual bool Boolean { get; set; } = true; public virtual bool Boolean { get; set; } = true;
/// <inheritdoc />
public virtual int Integer { get; set; } = 42; /// <inheritdoc />
/// <inheritdoc /> public virtual int Integer { get; set; } = 42;
public virtual float Float { get; set; } = 4.2f;
/// <inheritdoc /> /// <inheritdoc />
public virtual double Double { get; set; } = 4.2d; public virtual float Float { get; set; } = 4.2f;
/// <inheritdoc />
public virtual Func<DateTime> Date { get; set; } = () => System.DateTime.UtcNow.Date; /// <inheritdoc />
/// <inheritdoc /> public virtual double Double { get; set; } = 4.2d;
public virtual Func<DateTime> DateTime { get; set; } = () => System.DateTime.UtcNow;
/// <inheritdoc /> /// <inheritdoc />
public virtual byte[] Bytes { get; set; } = { 48, 49, 50 }; public virtual Func<DateTime> Date { get; set; } = () => System.DateTime.UtcNow.Date;
/// <inheritdoc />
public virtual object Object { get; set; } = "example-object"; /// <inheritdoc />
/// <inheritdoc /> public virtual Func<DateTime> DateTime { get; set; } = () => System.DateTime.UtcNow;
public virtual string String { get; set; } = "example-string";
/// <inheritdoc /> /// <inheritdoc />
public virtual OpenApiSchema Schema { get; set; } = new OpenApiSchema(); public virtual byte[] Bytes { get; set; } = { 48, 49, 50 };
}
/// <inheritdoc />
public virtual object Object { get; set; } = "example-object";
/// <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

@@ -44,37 +44,41 @@ internal class WebhookSender
IBodyData? bodyData; IBodyData? bodyData;
IDictionary<string, WireMockList<string>>? headers; IDictionary<string, WireMockList<string>>? headers;
string webhookRequestUrl;
if (webhookRequest.UseTransformer == true) if (webhookRequest.UseTransformer == true)
{ {
ITransformer responseMessageTransformer; ITransformer transformer;
switch (webhookRequest.TransformerType) switch (webhookRequest.TransformerType)
{ {
case TransformerType.Handlebars: case TransformerType.Handlebars:
var factoryHandlebars = new HandlebarsContextFactory(_settings.FileSystemHandler, _settings.HandlebarsRegistrationCallback); var factoryHandlebars = new HandlebarsContextFactory(_settings.FileSystemHandler, _settings.HandlebarsRegistrationCallback);
responseMessageTransformer = new Transformer(factoryHandlebars); transformer = new Transformer(factoryHandlebars);
break; break;
case TransformerType.Scriban: case TransformerType.Scriban:
case TransformerType.ScribanDotLiquid: case TransformerType.ScribanDotLiquid:
var factoryDotLiquid = new ScribanContextFactory(_settings.FileSystemHandler, webhookRequest.TransformerType); var factoryDotLiquid = new ScribanContextFactory(_settings.FileSystemHandler, webhookRequest.TransformerType);
responseMessageTransformer = new Transformer(factoryDotLiquid); transformer = new Transformer(factoryDotLiquid);
break; break;
default: default:
throw new NotImplementedException($"TransformerType '{webhookRequest.TransformerType}' is not supported."); throw new NotImplementedException($"TransformerType '{webhookRequest.TransformerType}' is not supported.");
} }
(bodyData, headers) = responseMessageTransformer.Transform(mapping, originalRequestMessage, originalResponseMessage, webhookRequest.BodyData, webhookRequest.Headers, webhookRequest.TransformerReplaceNodeOptions); bodyData = transformer.TransformBody(mapping, originalRequestMessage, originalResponseMessage, webhookRequest.BodyData, webhookRequest.TransformerReplaceNodeOptions);
headers = transformer.TransformHeaders(mapping, originalRequestMessage, originalResponseMessage, webhookRequest.Headers);
webhookRequestUrl = transformer.TransformString(mapping, originalRequestMessage, originalResponseMessage, webhookRequest.Url);
} }
else else
{ {
bodyData = webhookRequest.BodyData; bodyData = webhookRequest.BodyData;
headers = webhookRequest.Headers; headers = webhookRequest.Headers;
webhookRequestUrl = webhookRequest.Url;
} }
// Create RequestMessage // Create RequestMessage
var requestMessage = new RequestMessage( var requestMessage = new RequestMessage(
new UrlDetails(webhookRequest.Url), new UrlDetails(webhookRequestUrl),
webhookRequest.Method, webhookRequest.Method,
ClientIp, ClientIp,
bodyData, bodyData,
@@ -85,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

@@ -1,7 +1,7 @@
using System;
using System.Linq; using System.Linq;
using AnyOfTypes; using AnyOfTypes;
using Stef.Validation; using Stef.Validation;
using WireMock.Extensions;
using WireMock.Models; using WireMock.Models;
namespace WireMock.Matchers; namespace WireMock.Matchers;
@@ -9,8 +9,8 @@ namespace WireMock.Matchers;
/// <summary> /// <summary>
/// ExactMatcher /// ExactMatcher
/// </summary> /// </summary>
/// <seealso cref="IStringMatcher" /> /// <seealso cref="IStringMatcher" /> and <seealso cref="IIgnoreCaseMatcher" />
public class ExactMatcher : IStringMatcher public class ExactMatcher : IStringMatcher, IIgnoreCaseMatcher
{ {
private readonly AnyOf<string, StringPattern>[] _values; private readonly AnyOf<string, StringPattern>[] _values;
@@ -24,7 +24,16 @@ public class ExactMatcher : IStringMatcher
/// Initializes a new instance of the <see cref="ExactMatcher"/> class. /// Initializes a new instance of the <see cref="ExactMatcher"/> class.
/// </summary> /// </summary>
/// <param name="values">The values.</param> /// <param name="values">The values.</param>
public ExactMatcher(params AnyOf<string, StringPattern>[] values) : this(MatchBehaviour.AcceptOnMatch, false, MatchOperator.Or, values) public ExactMatcher(params AnyOf<string, StringPattern>[] values) : this(MatchBehaviour.AcceptOnMatch, false, false, MatchOperator.Or, values)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ExactMatcher"/> class.
/// </summary>
/// <param name="ignoreCase">Ignore the case from the pattern(s).</param>
/// <param name="values">The values.</param>
public ExactMatcher(bool ignoreCase, params AnyOf<string, StringPattern>[] values) : this(MatchBehaviour.AcceptOnMatch, ignoreCase, false, MatchOperator.Or, values)
{ {
} }
@@ -32,11 +41,13 @@ public class ExactMatcher : IStringMatcher
/// Initializes a new instance of the <see cref="ExactMatcher"/> class. /// Initializes a new instance of the <see cref="ExactMatcher"/> class.
/// </summary> /// </summary>
/// <param name="matchBehaviour">The match behaviour.</param> /// <param name="matchBehaviour">The match behaviour.</param>
/// <param name="ignoreCase">Ignore the case from the pattern(s).</param>
/// <param name="throwException">Throw an exception when the internal matching fails because of invalid input.</param> /// <param name="throwException">Throw an exception when the internal matching fails because of invalid input.</param>
/// <param name="matchOperator">The <see cref="Matchers.MatchOperator"/> to use. (default = "Or")</param> /// <param name="matchOperator">The <see cref="Matchers.MatchOperator"/> to use. (default = "Or")</param>
/// <param name="values">The values.</param> /// <param name="values">The values.</param>
public ExactMatcher( public ExactMatcher(
MatchBehaviour matchBehaviour, MatchBehaviour matchBehaviour,
bool ignoreCase = false,
bool throwException = false, bool throwException = false,
MatchOperator matchOperator = MatchOperator.Or, MatchOperator matchOperator = MatchOperator.Or,
params AnyOf<string, StringPattern>[] values) params AnyOf<string, StringPattern>[] values)
@@ -45,13 +56,18 @@ public class ExactMatcher : IStringMatcher
MatchBehaviour = matchBehaviour; MatchBehaviour = matchBehaviour;
ThrowException = throwException; ThrowException = throwException;
IgnoreCase = ignoreCase;
MatchOperator = matchOperator; MatchOperator = matchOperator;
} }
/// <inheritdoc cref="IStringMatcher.IsMatch"/> /// <inheritdoc cref="IStringMatcher.IsMatch"/>
public double IsMatch(string? input) public double IsMatch(string? input)
{ {
double score = MatchScores.ToScore(_values.Select(v => v.GetPattern() == input).ToArray(), MatchOperator); Func<string?, bool> equals = IgnoreCase
? pattern => string.Equals(pattern, input, StringComparison.OrdinalIgnoreCase)
: pattern => pattern == input;
double score = MatchScores.ToScore(_values.Select(v => equals(v)).ToArray(), MatchOperator);
return MatchBehaviourHelper.Convert(MatchBehaviour, score); return MatchBehaviourHelper.Convert(MatchBehaviour, score);
} }
@@ -66,4 +82,7 @@ public class ExactMatcher : IStringMatcher
/// <inheritdoc cref="IMatcher.Name"/> /// <inheritdoc cref="IMatcher.Name"/>
public string Name => "ExactMatcher"; public string Name => "ExactMatcher";
/// <inheritdoc />
public bool IgnoreCase { get; }
} }

View File

@@ -29,7 +29,7 @@ public class JsonPartialMatcher : AbstractJsonPartialMatcher
/// <inheritdoc /> /// <inheritdoc />
protected override bool IsMatch(string value, string input) protected override bool IsMatch(string value, string input)
{ {
var exactStringMatcher = new ExactMatcher(MatchBehaviour.AcceptOnMatch, ThrowException, MatchOperator.Or, value); var exactStringMatcher = new ExactMatcher(MatchBehaviour.AcceptOnMatch, IgnoreCase, ThrowException, MatchOperator.Or, value);
return MatchScores.IsPerfect(exactStringMatcher.IsMatch(input)); return MatchScores.IsPerfect(exactStringMatcher.IsMatch(input));
} }
} }

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

@@ -18,5 +18,5 @@ public enum MatchOperator
/// <summary> /// <summary>
/// The average value from all patterns. /// The average value from all patterns.
/// </summary> /// </summary>
Average, Average
} }

View File

@@ -16,22 +16,22 @@ public class RequestMessageBodyMatcher : IRequestMatcher
/// <summary> /// <summary>
/// The body function /// The body function
/// </summary> /// </summary>
public Func<string, bool>? Func { get; } public Func<string?, bool>? Func { get; }
/// <summary> /// <summary>
/// The body data function for byte[] /// The body data function for byte[]
/// </summary> /// </summary>
public Func<byte[], bool>? DataFunc { get; } public Func<byte[]?, bool>? DataFunc { get; }
/// <summary> /// <summary>
/// The body data function for json /// The body data function for json
/// </summary> /// </summary>
public Func<object, bool>? JsonFunc { get; } public Func<object?, bool>? JsonFunc { get; }
/// <summary> /// <summary>
/// The body data function for BodyData /// The body data function for BodyData
/// </summary> /// </summary>
public Func<IBodyData, bool>? BodyDataFunc { get; } public Func<IBodyData?, bool>? BodyDataFunc { get; }
/// <summary> /// <summary>
/// The matchers. /// The matchers.
@@ -77,7 +77,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<string, bool> func) public RequestMessageBodyMatcher(Func<string?, bool> func)
{ {
Func = Guard.NotNull(func); Func = Guard.NotNull(func);
} }
@@ -86,7 +86,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<byte[], bool> func) public RequestMessageBodyMatcher(Func<byte[]?, bool> func)
{ {
DataFunc = Guard.NotNull(func); DataFunc = Guard.NotNull(func);
} }
@@ -95,7 +95,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<object, bool> func) public RequestMessageBodyMatcher(Func<object?, bool> func)
{ {
JsonFunc = Guard.NotNull(func); JsonFunc = Guard.NotNull(func);
} }
@@ -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);
} }
@@ -158,9 +158,9 @@ public class RequestMessageBodyMatcher : IRequestMatcher
{ {
// If the body is a byte array, try to match. // If the body is a byte array, try to match.
var detectedBodyType = requestMessage.BodyData?.DetectedBodyType; var detectedBodyType = requestMessage.BodyData?.DetectedBodyType;
if (detectedBodyType == BodyType.Bytes || detectedBodyType == BodyType.String) if (detectedBodyType is BodyType.Bytes or BodyType.String)
{ {
return exactObjectMatcher.IsMatch(requestMessage.BodyData.BodyAsBytes); return exactObjectMatcher.IsMatch(requestMessage.BodyData?.BodyAsBytes);
} }
} }

View File

@@ -1,8 +1,7 @@
using Stef.Validation;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using JetBrains.Annotations;
using Stef.Validation;
namespace WireMock.Matchers.Request; namespace WireMock.Matchers.Request;
@@ -13,12 +12,13 @@ namespace WireMock.Matchers.Request;
public class RequestMessageCookieMatcher : IRequestMatcher public class RequestMessageCookieMatcher : IRequestMatcher
{ {
private readonly MatchBehaviour _matchBehaviour; private readonly MatchBehaviour _matchBehaviour;
private readonly bool _ignoreCase; private readonly bool _ignoreCase;
/// <summary> /// <summary>
/// The functions /// The functions
/// </summary> /// </summary>
public Func<IDictionary<string, string>, bool>[] Funcs { get; } public Func<IDictionary<string, string>, bool>[]? Funcs { get; }
/// <summary> /// <summary>
/// The name /// The name
@@ -28,7 +28,7 @@ public class RequestMessageCookieMatcher : IRequestMatcher
/// <value> /// <value>
/// The matchers. /// The matchers.
/// </value> /// </value>
public IStringMatcher[] Matchers { get; } public IStringMatcher[]? Matchers { get; }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="RequestMessageCookieMatcher"/> class. /// Initializes a new instance of the <see cref="RequestMessageCookieMatcher"/> class.
@@ -37,15 +37,12 @@ public class RequestMessageCookieMatcher : IRequestMatcher
/// <param name="pattern">The pattern.</param> /// <param name="pattern">The pattern.</param>
/// <param name="ignoreCase">Ignore the case from the pattern.</param> /// <param name="ignoreCase">Ignore the case from the pattern.</param>
/// <param name="matchBehaviour">The match behaviour.</param> /// <param name="matchBehaviour">The match behaviour.</param>
public RequestMessageCookieMatcher(MatchBehaviour matchBehaviour, [NotNull] string name, [NotNull] string pattern, bool ignoreCase) public RequestMessageCookieMatcher(MatchBehaviour matchBehaviour, string name, string pattern, bool ignoreCase)
{ {
Guard.NotNull(name, nameof(name));
Guard.NotNull(pattern, nameof(pattern));
_matchBehaviour = matchBehaviour; _matchBehaviour = matchBehaviour;
_ignoreCase = ignoreCase; _ignoreCase = ignoreCase;
Name = name; Name = Guard.NotNull(name);
Matchers = new IStringMatcher[] { new WildcardMatcher(matchBehaviour, pattern, ignoreCase) }; Matchers = new IStringMatcher[] { new WildcardMatcher(matchBehaviour, Guard.NotNull(pattern), ignoreCase) };
} }
/// <summary> /// <summary>
@@ -55,10 +52,10 @@ public class RequestMessageCookieMatcher : IRequestMatcher
/// <param name="name">The name.</param> /// <param name="name">The name.</param>
/// <param name="patterns">The patterns.</param> /// <param name="patterns">The patterns.</param>
/// <param name="ignoreCase">Ignore the case from the pattern.</param> /// <param name="ignoreCase">Ignore the case from the pattern.</param>
public RequestMessageCookieMatcher(MatchBehaviour matchBehaviour, [NotNull] string name, bool ignoreCase, [NotNull] params string[] patterns) : public RequestMessageCookieMatcher(MatchBehaviour matchBehaviour, string name, bool ignoreCase, params string[] patterns) :
this(matchBehaviour, name, ignoreCase, patterns.Select(pattern => new WildcardMatcher(matchBehaviour, pattern, ignoreCase)).Cast<IStringMatcher>().ToArray()) this(matchBehaviour, name, ignoreCase, patterns.Select(pattern => new WildcardMatcher(matchBehaviour, pattern, ignoreCase)).Cast<IStringMatcher>().ToArray())
{ {
Guard.NotNull(patterns, nameof(patterns)); Guard.NotNull(patterns);
} }
/// <summary> /// <summary>
@@ -68,14 +65,11 @@ public class RequestMessageCookieMatcher : IRequestMatcher
/// <param name="name">The name.</param> /// <param name="name">The name.</param>
/// <param name="matchers">The matchers.</param> /// <param name="matchers">The matchers.</param>
/// <param name="ignoreCase">Ignore the case from the pattern.</param> /// <param name="ignoreCase">Ignore the case from the pattern.</param>
public RequestMessageCookieMatcher(MatchBehaviour matchBehaviour, [NotNull] string name, bool ignoreCase, [NotNull] params IStringMatcher[] matchers) public RequestMessageCookieMatcher(MatchBehaviour matchBehaviour, string name, bool ignoreCase, params IStringMatcher[] matchers)
{ {
Guard.NotNull(name, nameof(name));
Guard.NotNull(matchers, nameof(matchers));
_matchBehaviour = matchBehaviour; _matchBehaviour = matchBehaviour;
Name = name; Name = Guard.NotNull(name);
Matchers = matchers; Matchers = Guard.NotNull(matchers);
_ignoreCase = ignoreCase; _ignoreCase = ignoreCase;
} }
@@ -83,11 +77,12 @@ public class RequestMessageCookieMatcher : IRequestMatcher
/// Initializes a new instance of the <see cref="RequestMessageCookieMatcher"/> class. /// Initializes a new instance of the <see cref="RequestMessageCookieMatcher"/> class.
/// </summary> /// </summary>
/// <param name="funcs">The funcs.</param> /// <param name="funcs">The funcs.</param>
public RequestMessageCookieMatcher([NotNull] params Func<IDictionary<string, string>, bool>[] funcs) public RequestMessageCookieMatcher(params Func<IDictionary<string, string>, bool>[] funcs)
{ {
Guard.NotNull(funcs, nameof(funcs)); Guard.NotNull(funcs);
Funcs = funcs; Funcs = funcs;
Name = string.Empty; // Not used when Func, but set to a non-null valid value.
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@@ -11,7 +11,10 @@ namespace WireMock.Matchers.Request;
/// </summary> /// </summary>
public class RequestMessageParamMatcher : IRequestMatcher public class RequestMessageParamMatcher : IRequestMatcher
{ {
private readonly MatchBehaviour _matchBehaviour; /// <summary>
/// MatchBehaviour
/// </summary>
public MatchBehaviour MatchBehaviour { get; }
/// <summary> /// <summary>
/// The funcs /// The funcs
@@ -50,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, 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())
{ {
} }
@@ -61,9 +65,9 @@ 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);
IgnoreCase = ignoreCase; IgnoreCase = ignoreCase;
Matchers = matchers; Matchers = matchers;
@@ -81,7 +85,7 @@ public class RequestMessageParamMatcher : IRequestMatcher
/// <inheritdoc /> /// <inheritdoc />
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult) public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
{ {
double score = MatchBehaviourHelper.Convert(_matchBehaviour, IsMatch(requestMessage)); double score = MatchBehaviourHelper.Convert(MatchBehaviour, IsMatch(requestMessage));
return requestMatchResult.AddScore(GetType(), score); return requestMatchResult.AddScore(GetType(), score);
} }
@@ -92,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 cref="IBodyData.BodyAsBytes" /> /// <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

@@ -107,7 +107,7 @@ namespace WireMock.Owin
{ {
try try
{ {
var appLifetime = (IApplicationLifetime)_host.Services.GetService(typeof(IApplicationLifetime)); var appLifetime = _host.Services.GetRequiredService<IApplicationLifetime>();
appLifetime.ApplicationStarted.Register(() => appLifetime.ApplicationStarted.Register(() =>
{ {
var addresses = _host.ServerFeatures var addresses = _host.ServerFeatures

View File

@@ -1,12 +1,15 @@
namespace WireMock.Owin; namespace WireMock.Owin;
/// <summary>
/// https://en.wikipedia.org/wiki/Uniform_Resource_Identifier
/// </summary>
internal struct HostUrlDetails internal struct HostUrlDetails
{ {
public bool IsHttps { get; set; } public bool IsHttps { get; set; }
public string Url { get; set; } public string Url { get; set; }
public string Protocol { get; set; } public string Scheme { get; set; }
public string Host { get; set; } public string Host { get; set; }

View File

@@ -1,34 +1,49 @@
using System.Collections.Generic; using System.Collections.Generic;
using WireMock.Types;
using WireMock.Util; using WireMock.Util;
namespace WireMock.Owin; namespace WireMock.Owin;
internal class HostUrlOptions internal class HostUrlOptions
{ {
private const string LOCALHOST = "localhost"; private const string Localhost = "localhost";
public ICollection<string>? Urls { get; set; } public ICollection<string>? Urls { get; set; }
public int? Port { get; set; } public int? Port { get; set; }
public bool UseSSL { get; set; } public int? HttpsPort { get; set; }
public ICollection<HostUrlDetails> GetDetails() public HostingScheme HostingScheme { get; set; }
public IReadOnlyList<HostUrlDetails> GetDetails()
{ {
var list = new List<HostUrlDetails>(); var list = new List<HostUrlDetails>();
if (Urls == null) if (Urls == null)
{ {
int port = Port > 0 ? Port.Value : FindFreeTcpPort(); if (HostingScheme is HostingScheme.Http or HostingScheme.Https)
string protocol = UseSSL ? "https" : "http"; {
list.Add(new HostUrlDetails { IsHttps = UseSSL, Url = $"{protocol}://{LOCALHOST}:{port}", Protocol = protocol, Host = LOCALHOST, Port = port }); var port = Port > 0 ? Port.Value : FindFreeTcpPort();
var scheme = HostingScheme == HostingScheme.Https ? "https" : "http";
list.Add(new HostUrlDetails { IsHttps = HostingScheme == HostingScheme.Https, Url = $"{scheme}://{Localhost}:{port}", Scheme = scheme, Host = Localhost, Port = port });
}
if (HostingScheme == HostingScheme.HttpAndHttps)
{
var httpPort = Port > 0 ? Port.Value : FindFreeTcpPort();
list.Add(new HostUrlDetails { IsHttps = false, Url = $"http://{Localhost}:{httpPort}", Scheme = "http", Host = Localhost, Port = httpPort });
var httpsPort = FindFreeTcpPort(); // In this scenario, always get a free port for https.
list.Add(new HostUrlDetails { IsHttps = true, Url = $"https://{Localhost}:{httpsPort}", Scheme = "https", Host = Localhost, Port = httpsPort });
}
} }
else else
{ {
foreach (string url in Urls) foreach (string url in Urls)
{ {
if (PortUtils.TryExtract(url, out bool isHttps, out var protocol, out var host, out int port)) if (PortUtils.TryExtract(url, out var isHttps, out var protocol, out var host, out var port))
{ {
list.Add(new HostUrlDetails { IsHttps = isHttps, Url = url, Protocol = protocol, Host = host, Port = port }); list.Add(new HostUrlDetails { IsHttps = isHttps, Url = url, Scheme = protocol, Host = host, Port = port });
} }
} }
} }

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
@@ -161,7 +164,6 @@ namespace WireMock.Owin
_options.Logger.Error($"Providing a Response for Mapping '{result.Match?.Mapping?.Guid}' failed. HttpStatusCode set to 500. Exception: {ex}"); _options.Logger.Error($"Providing a Response for Mapping '{result.Match?.Mapping?.Guid}' failed. HttpStatusCode set to 500. Exception: {ex}");
response = ResponseMessageBuilder.Create(ex.Message, 500); response = ResponseMessageBuilder.Create(ex.Message, 500);
} }
finally finally
{ {
var log = new LogEntry var log = new LogEntry
@@ -265,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,16 +1,10 @@
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 Stef.Validation; using Stef.Validation;
using WireMock.Constants;
using WireMock.Http; using WireMock.Http;
using WireMock.Matchers; using WireMock.Serialization;
using WireMock.RequestBuilders;
using WireMock.ResponseBuilders;
using WireMock.Settings; using WireMock.Settings;
using WireMock.Types;
using WireMock.Util; using WireMock.Util;
namespace WireMock.Proxy; namespace WireMock.Proxy;
@@ -18,13 +12,16 @@ namespace WireMock.Proxy;
internal class ProxyHelper internal class ProxyHelper
{ {
private readonly WireMockServerSettings _settings; private readonly WireMockServerSettings _settings;
private readonly ProxyMappingConverter _proxyMappingConverter;
public ProxyHelper(WireMockServerSettings settings) public ProxyHelper(WireMockServerSettings settings)
{ {
_settings = Guard.NotNull(settings); _settings = Guard.NotNull(settings);
_proxyMappingConverter = new ProxyMappingConverter(settings, new GuidUtils());
} }
public async Task<(IResponseMessage Message, IMapping? Mapping)> SendAsync( public async Task<(IResponseMessage Message, IMapping? Mapping)> SendAsync(
IMapping? mapping,
ProxyAndRecordSettings proxyAndRecordSettings, ProxyAndRecordSettings proxyAndRecordSettings,
HttpClient client, HttpClient client,
IRequestMessage requestMessage, IRequestMessage requestMessage,
@@ -49,78 +46,13 @@ internal class ProxyHelper
var responseMessage = await HttpResponseMessageHelper.CreateAsync(httpResponseMessage, requiredUri, originalUri, deserializeJson, decompressGzipAndDeflate).ConfigureAwait(false); var responseMessage = await HttpResponseMessageHelper.CreateAsync(httpResponseMessage, requiredUri, originalUri, deserializeJson, decompressGzipAndDeflate).ConfigureAwait(false);
IMapping? mapping = null; IMapping? newMapping = null;
if (HttpStatusRangeParser.IsMatch(proxyAndRecordSettings.SaveMappingForStatusCodePattern, responseMessage.StatusCode) && if (HttpStatusRangeParser.IsMatch(proxyAndRecordSettings.SaveMappingForStatusCodePattern, responseMessage.StatusCode) &&
(proxyAndRecordSettings.SaveMapping || proxyAndRecordSettings.SaveMappingToFile)) (proxyAndRecordSettings.SaveMapping || proxyAndRecordSettings.SaveMappingToFile))
{ {
mapping = ToMapping(proxyAndRecordSettings, requestMessage, responseMessage); newMapping = _proxyMappingConverter.ToMapping(mapping, proxyAndRecordSettings, requestMessage, responseMessage);
} }
return (responseMessage, mapping); 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, 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

@@ -20,7 +20,7 @@ namespace WireMock.RequestBuilders
/// <inheritdoc cref="IParamsRequestBuilder.WithParam(string, bool, MatchBehaviour)"/> /// <inheritdoc cref="IParamsRequestBuilder.WithParam(string, bool, MatchBehaviour)"/>
public IRequestBuilder WithParam(string key, bool ignoreCase, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch) public IRequestBuilder WithParam(string key, bool ignoreCase, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{ {
Guard.NotNull(key, nameof(key)); Guard.NotNull(key);
_requestMatchers.Add(new RequestMessageParamMatcher(matchBehaviour, key, ignoreCase)); _requestMatchers.Add(new RequestMessageParamMatcher(matchBehaviour, key, ignoreCase));
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

@@ -271,6 +271,7 @@ public partial class Response : IResponseBuilder
var proxyHelper = new ProxyHelper(settings); var proxyHelper = new ProxyHelper(settings);
return await proxyHelper.SendAsync( return await proxyHelper.SendAsync(
mapping,
ProxyAndRecordSettings, ProxyAndRecordSettings,
_httpClientForProxy, _httpClientForProxy,
requestMessage, requestMessage,
@@ -324,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);

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