mirror of
https://github.com/wiremock/WireMock.Net.git
synced 2026-01-14 22:33:35 +01:00
Compare commits
53 Commits
1.15.0
...
mime-part-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
403fc01867 | ||
|
|
83922fd543 | ||
|
|
041d16e658 | ||
|
|
95d3978c7c | ||
|
|
960399127e | ||
|
|
525f41f23b | ||
|
|
e0ca9a1223 | ||
|
|
f5d53453e5 | ||
|
|
0e60e3f3f9 | ||
|
|
9cee6dde00 | ||
|
|
c88e7378a7 | ||
|
|
b090296559 | ||
|
|
e5afd69f7c | ||
|
|
f3c988e198 | ||
|
|
f38133d7a4 | ||
|
|
5c359c5aa7 | ||
|
|
597c95000e | ||
|
|
022dfd0cd3 | ||
|
|
4617b99c30 | ||
|
|
3291c43632 | ||
|
|
ffd4d89946 | ||
|
|
2d46c86f47 | ||
|
|
75f4fbe9d0 | ||
|
|
e64d7d763a | ||
|
|
16e3872402 | ||
|
|
8b2f0b8fad | ||
|
|
4c797c328f | ||
|
|
9a2488afe7 | ||
|
|
a5e75a7278 | ||
|
|
56f65c19e2 | ||
|
|
0407bcd423 | ||
|
|
6aef4816a5 | ||
|
|
fe2622dcf2 | ||
|
|
197a211a52 | ||
|
|
3cfeec6035 | ||
|
|
b57d5e7548 | ||
|
|
36b89afce5 | ||
|
|
e2acac55a4 | ||
|
|
ceabd27ce0 | ||
|
|
b6fc7d2e3c | ||
|
|
846da9d4f6 | ||
|
|
f8e2c7ee90 | ||
|
|
c25d8f33d2 | ||
|
|
6da190e596 | ||
|
|
44388ce80d | ||
|
|
5e25ca767d | ||
|
|
0cc583a4a3 | ||
|
|
f9633adac1 | ||
|
|
37bad618a3 | ||
|
|
8e69f36f04 | ||
|
|
21601889e0 | ||
|
|
dfeabf228e | ||
|
|
1feb0ade70 |
46
CHANGELOG.md
46
CHANGELOG.md
@@ -1,3 +1,49 @@
|
||||
# 1.23.0 (05 January 2026)
|
||||
- [#1414](https://github.com/wiremock/WireMock.Net/pull/1414) - Pass the parameter matchOperator in Request.WithPath to its inner calls [bug] contributed by [gbamqzkdyg](https://github.com/gbamqzkdyg)
|
||||
- [#1416](https://github.com/wiremock/WireMock.Net/pull/1416) - Fix: Pass AllowedHandlebarsHelpers configuration to Handlebars.Net.Helpers library contributed by [samlatham](https://github.com/samlatham)
|
||||
- [#1413](https://github.com/wiremock/WireMock.Net/issues/1413) - Parameter `matchOperator` is not respected in the method Request.WithPath [bug]
|
||||
- [#1415](https://github.com/wiremock/WireMock.Net/issues/1415) - HandlebarsSettings AllowedHandlebarsHelpers Configuration Not Applied [bug]
|
||||
|
||||
# 1.22.0 (02 January 2026)
|
||||
- [#1412](https://github.com/wiremock/WireMock.Net/pull/1412) - chore(testcontainers): bump up Testcontainers to version 4.10.0 [feature] contributed by [vhatsura](https://github.com/vhatsura)
|
||||
- [#1411](https://github.com/wiremock/WireMock.Net/issues/1411) - WireMock.Net.Testcontainers isn't compatible with Testcontainers 4.10.0 [bug]
|
||||
|
||||
# 1.21.0 (25 December 2025)
|
||||
- [#1408](https://github.com/wiremock/WireMock.Net/pull/1408) - Fix readyness-check for Testcontainers [bug] contributed by [StefH](https://github.com/StefH)
|
||||
|
||||
# 1.20.0 (24 December 2025)
|
||||
- [#1399](https://github.com/wiremock/WireMock.Net/pull/1399) - Upgrade RamlToOpenApiConverter and YamlDotNet [feature] contributed by [StefH](https://github.com/StefH)
|
||||
- [#1400](https://github.com/wiremock/WireMock.Net/pull/1400) - Add WireMock.Net.NUnit project [feature] contributed by [StefH](https://github.com/StefH)
|
||||
- [#1405](https://github.com/wiremock/WireMock.Net/pull/1405) - Fix Testcontainers AddProtoDefinition [bug] contributed by [StefH](https://github.com/StefH)
|
||||
- [#1398](https://github.com/wiremock/WireMock.Net/issues/1398) - Upgrade YamlDotNet dependency [feature]
|
||||
- [#1404](https://github.com/wiremock/WireMock.Net/issues/1404) - An exception occurs when adding multiple proto definitions in the TestContainer. [bug]
|
||||
|
||||
# 1.19.0 (12 December 2025)
|
||||
- [#1391](https://github.com/wiremock/WireMock.Net/pull/1391) - Update WireMockContainerBuilder (WithImage and WithCustomImage) [feature] contributed by [StefH](https://github.com/StefH)
|
||||
- [#1392](https://github.com/wiremock/WireMock.Net/pull/1392) - WireMockContainerBuilder: allow all docker images named wiremock [feature] contributed by [StefH](https://github.com/StefH)
|
||||
- [#1390](https://github.com/wiremock/WireMock.Net/issues/1390) - Unable to build WireMockContainerBuilder with custom image [feature]
|
||||
|
||||
# 1.18.0 (09 December 2025)
|
||||
- [#1388](https://github.com/wiremock/WireMock.Net/pull/1388) - Add WithBodyAsType to RequestMatcher [feature] contributed by [StefH](https://github.com/StefH)
|
||||
|
||||
# 1.17.0 (07 December 2025)
|
||||
- [#1383](https://github.com/wiremock/WireMock.Net/pull/1383) - Aspire: Add WithProtoDefinition to support proto definition at server level [feature] contributed by [StefH](https://github.com/StefH)
|
||||
- [#1386](https://github.com/wiremock/WireMock.Net/pull/1386) - Fix random delay in mapping json file [bug] contributed by [StefH](https://github.com/StefH)
|
||||
- [#1274](https://github.com/wiremock/WireMock.Net/issues/1274) - .WithMappings to mount volume is not working for GRPC [bug]
|
||||
- [#1381](https://github.com/wiremock/WireMock.Net/issues/1381) - Downstream dependencies missing after 1.16.0 release [bug]
|
||||
- [#1382](https://github.com/wiremock/WireMock.Net/issues/1382) - Does Aspire support enabling HTTP/2? [feature]
|
||||
- [#1385](https://github.com/wiremock/WireMock.Net/issues/1385) - Do delays and probabilities show in saved static mappings? [bug]
|
||||
- [#1387](https://github.com/wiremock/WireMock.Net/issues/1387) - Tests failing with TaskCanceledException on Windows Server 2025 Build 7171 [bug]
|
||||
|
||||
# 1.16.0 (18 November 2025)
|
||||
- [#1366](https://github.com/wiremock/WireMock.Net/pull/1366) - WireMock.Net.OpenApiParser : support Examples [feature] contributed by [StefH](https://github.com/StefH)
|
||||
- [#1375](https://github.com/wiremock/WireMock.Net/pull/1375) - Add WireMockHealthCheck in WireMock.Net.Aspire [feature] contributed by [Zguy](https://github.com/Zguy)
|
||||
- [#1377](https://github.com/wiremock/WireMock.Net/pull/1377) - Check if the path is valid when using WithPath(...) [feature] contributed by [StefH](https://github.com/StefH)
|
||||
- [#1380](https://github.com/wiremock/WireMock.Net/pull/1380) - Add WireMock.Net.xUnit.v3 project [feature] contributed by [StefH](https://github.com/StefH)
|
||||
- [#1364](https://github.com/wiremock/WireMock.Net/issues/1364) - Choosing examples from open api specification for responses. [feature]
|
||||
- [#1376](https://github.com/wiremock/WireMock.Net/issues/1376) - AdminApiMappingBuilder `WithPath` should add the starting `/` if missing [feature]
|
||||
- [#1379](https://github.com/wiremock/WireMock.Net/issues/1379) - xUnit v3 [feature]
|
||||
|
||||
# 1.15.0 (22 October 2025)
|
||||
- [#1367](https://github.com/wiremock/WireMock.Net/pull/1367) - Fix WithProbability logic [bug] contributed by [StefH](https://github.com/StefH)
|
||||
- [#1370](https://github.com/wiremock/WireMock.Net/pull/1370) - Support Testcontainers 4.8.0 [bug] contributed by [MD-V](https://github.com/MD-V)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<Version>1.15.0</Version>
|
||||
<VersionPrefix>1.23.0</VersionPrefix>
|
||||
<PackageIcon>WireMock.Net-Logo.png</PackageIcon>
|
||||
<PackageProjectUrl>https://github.com/wiremock/WireMock.Net</PackageProjectUrl>
|
||||
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
rem https://github.com/StefH/GitHubReleaseNotes
|
||||
|
||||
SET version=1.15.0
|
||||
SET version=1.23.0
|
||||
|
||||
GitHubReleaseNotes --output CHANGELOG.md --skip-empty-releases --exclude-labels wontfix test question invalid doc duplicate example environment --version %version% --token %GH_TOKEN%
|
||||
|
||||
|
||||
@@ -15,48 +15,48 @@ Lightweight Http Mocking Server for .NET, inspired by WireMock.org (from the Jav
|
||||
|
||||
### :star: Stubbing
|
||||
A core feature of WireMock.Net is the ability to return predefined HTTP responses for requests matching criteria.
|
||||
See [Wiki : Stubbing](https://github.com/wiremock/WireMock.Net/wiki/Stubbing).
|
||||
See [Stubbing](https://wiremock.org/dotnet/stubbing).
|
||||
|
||||
### :star: Request Matching
|
||||
WireMock.Net support advanced request-matching logic, see [Wiki : Request Matching](https://github.com/wiremock/WireMock.Net/wiki/Request-Matching).
|
||||
WireMock.Net support advanced request-matching logic, see [Request Matching](https://wiremock.org/dotnet/request-matching).
|
||||
|
||||
### :star: Response Templating
|
||||
The response which is returned WireMock.Net can be changed using templating. This is described here [Wiki : Response Templating](https://github.com/wiremock/WireMock.Net/wiki/Response-Templating).
|
||||
The response which is returned WireMock.Net can be changed using templating. This is described here [Response Templating](https://wiremock.org/dotnet/response-templating).
|
||||
|
||||
### :star: Admin API Reference
|
||||
The WireMock admin API provides functionality to define the mappings via a http interface see [Wiki : Admin API Reference](https://github.com/StefH/WireMock.Net/wiki/Admin-API-Reference).
|
||||
The WireMock admin API provides functionality to define the mappings via a http interface see [Admin API Reference](https://wiremock.org/dotnet/admin-api-reference).
|
||||
|
||||
### :star: Using
|
||||
WireMock.Net can be used in several ways:
|
||||
|
||||
#### UnitTesting
|
||||
You can use your favorite test framework and use WireMock within your tests, see
|
||||
[Wiki : UnitTesting](https://github.com/StefH/WireMock.Net/wiki/Using-WireMock-in-UnitTests).
|
||||
[UnitTesting](https://wiremock.org/dotnet/using-wiremock-in-unittests).
|
||||
|
||||
### Unit/Integration Testing using Testcontainers.DotNet
|
||||
See [Wiki : WireMock.Net.Testcontainers](https://github.com/wiremock/WireMock.Net/wiki/Using-WireMock.Net.Testcontainers) on how to build a WireMock.Net Docker container which can be used in Unit/Integration testing.
|
||||
See [WireMock.Net.Testcontainers](https://wiremock.org/dotnet/using-wiremock-net-testcontainers/) on how to build a WireMock.Net Docker container which can be used in Unit/Integration testing.
|
||||
|
||||
### Unit/Integration Testing using an an Aspire Distributed Application
|
||||
See [Wiki : WireMock.Net.Aspire](https://github.com/wiremock/WireMock.Net/wiki/Using-WireMock.Net.Aspire) on how to use WireMock.Net as an Aspire Hosted application to do Unit/Integration testing.
|
||||
See [WireMock.Net.Aspire](https://wiremock.org/dotnet/using-wiremock-net-Aspire) on how to use WireMock.Net as an Aspire Hosted application to do Unit/Integration testing.
|
||||
|
||||
#### As a dotnet tool
|
||||
It's simple to install WireMock.Net as (global) dotnet tool, see [Wiki : dotnet tool](https://github.com/StefH/WireMock.Net/wiki/WireMock-as-dotnet-tool).
|
||||
It's simple to install WireMock.Net as (global) dotnet tool, see [dotnet tool](https://wiremock.org/dotnet/wiremock-as-dotnet-tool).
|
||||
|
||||
#### As standalone process / console application
|
||||
This is quite straight forward to launch a mock server within a console application, see [Wiki : Standalone Process](https://github.com/StefH/WireMock.Net/wiki/WireMock-as-a-standalone-process).
|
||||
This is quite straight forward to launch a mock server within a console application, see [Standalone Process](https://wiremock.org/dotnet/wiremock-as-a-standalone-process).
|
||||
|
||||
#### As a Windows Service
|
||||
You can also run WireMock.Net as a Windows Service, follow this [WireMock-as-a-Windows-Service](https://github.com/wiremock/WireMock.Net/wiki/WireMock-as-a-Windows-Service).
|
||||
You can also run WireMock.Net as a Windows Service, follow this [Windows Service](https://wiremock.org/dotnet/wiremock-as-a-windows-service).
|
||||
|
||||
#### As a Web Job in Azure or application in IIS
|
||||
See this link [WireMock-as-a-(Azure)-Web-App](https://github.com/wiremock/WireMock.Net/wiki/WireMock-as-a-(Azure)-Web-App)
|
||||
See this link [WireMock-as-a-(Azure)-Web-App](https://wiremock.org/dotnet/wiremock-as-a-azure-web-app/)
|
||||
|
||||
#### In a docker container
|
||||
There is also a Linux and Windows-Nano container available at [hub.docker.com](https://hub.docker.com/r/sheyenrath).
|
||||
For more details see also [Docker](https://github.com/wiremock/WireMock.Net-docker).
|
||||
|
||||
#### HTTPS / SSL
|
||||
More details on using HTTPS (SSL) can be found here [Wiki : HTTPS](https://github.com/wiremock/WireMock.Net/wiki/Using-HTTPS-(SSL))
|
||||
More details on using HTTPS (SSL) can be found here [HTTPS](https://wiremock.org/dotnet/using-https-ssl/)
|
||||
|
||||
## :books: Documentation
|
||||
For more info, see also this WIKI page: [What is WireMock.Net](https://github.com/wiremock/WireMock.Net/wiki/What-Is-WireMock.Net).
|
||||
For more info, see also this documentation page: [What is WireMock.Net](https://wiremock.org/dotnet/what-is-wiremock-net/).
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# 1.15.0 (22 October 2025)
|
||||
- #1367 Fix WithProbability logic [bug]
|
||||
- #1370 Support Testcontainers 4.8.0 [bug]
|
||||
- #1126 Request matching WithProbability strange behaviour [bug]
|
||||
# 1.23.0 (05 January 2026)
|
||||
- #1414 Pass the parameter matchOperator in Request.WithPath to its inner calls [bug]
|
||||
- #1416 Fix: Pass AllowedHandlebarsHelpers configuration to Handlebars.Net.Helpers library
|
||||
- #1413 Parameter `matchOperator` is not respected in the method Request.WithPath [bug]
|
||||
- #1415 HandlebarsSettings AllowedHandlebarsHelpers Configuration Not Applied [bug]
|
||||
|
||||
The full release notes can be found here: https://github.com/wiremock/WireMock.Net/blob/master/CHANGELOG.md
|
||||
39
README.md
39
README.md
@@ -3,7 +3,7 @@ A C# .NET version based on [mock4net](https://github.com/alexvictoor/mock4net) w
|
||||
|
||||
---
|
||||
|
||||
:books: <strong>Full documentation can now be found at <a href="https://wiremock.org/dotnet/" title="WireMock.Net docs">wiremock.org</a>
|
||||
### :books: Full documentation can now be found at [wiremock.org](https://wiremock.org/dotnet)
|
||||
|
||||
---
|
||||
|
||||
@@ -33,7 +33,7 @@ A C# .NET version based on [mock4net](https://github.com/alexvictoor/mock4net) w
|
||||
| **Issues** | [](https://github.com/wiremock/WireMock.Net/issues) |
|
||||
| | |
|
||||
| ***Quality*** | |
|
||||
| **Build Azure** | [](https://stef.visualstudio.com/WireMock.Net/_build/latest?definitionId=7) |
|
||||
| **Build Azure** | [](https://stef.visualstudio.com/WireMock.Net/_build/latest?definitionId=61) |
|
||||
| **Quality** | [](https://sonarcloud.io/project/issues?id=WireMock-Net_WireMock.Net) [](https://www.codefactor.io/repository/github/wiremock/wiremock.net) |
|
||||
| **Sonar Bugs** | [](https://sonarcloud.io/project/issues?id=WireMock-Net_WireMock.Net&resolved=false&types=BUG) [](https://sonarcloud.io/project/issues?id=WireMock-Net_WireMock.Net&resolved=false&types=CODE_SMELL) |
|
||||
| **Coverage** | [](https://sonarcloud.io/component_measures?id=WireMock-Net_WireMock.Net&metric=coverage) [](https://codecov.io/gh/wiremock/WireMock.Net)|
|
||||
@@ -41,7 +41,7 @@ A C# .NET version based on [mock4net](https://github.com/alexvictoor/mock4net) w
|
||||
|
||||
### :package: NuGet packages
|
||||
|
||||
| | Official | Preview [:information_source:](https://github.com/wiremock/WireMock.Net/wiki/MyGet-preview-versions) |
|
||||
| | Official | Preview [:information_source:](https://wiremock.org/dotnet/MyGet-preview-versions) |
|
||||
| - | - | - |
|
||||
| **WireMock.Net** | [](https://www.nuget.org/packages/WireMock.Net) | [](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net)
|
||||
| **WireMock.Net.Minimal** 🔺| [](https://www.nuget.org/packages/WireMock.Net.Minimal) | [](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.Minimal)
|
||||
@@ -53,7 +53,9 @@ A C# .NET version based on [mock4net](https://github.com/alexvictoor/mock4net) w
|
||||
| **WireMock.Net.AwesomeAssertions** | [](https://www.nuget.org/packages/WireMock.Net.AwesomeAssertions) | [](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.AwesomeAssertions)
|
||||
| **WireMock.Net.FluentAssertions** | [](https://www.nuget.org/packages/WireMock.Net.FluentAssertions) | [](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.FluentAssertions)
|
||||
| **WireMock.Net.xUnit** | [](https://www.nuget.org/packages/WireMock.Net.xUnit) | [](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.xUnit)
|
||||
| **WireMock.Net.xUnit.v3** | [](https://www.nuget.org/packages/WireMock.Net.xUnit.v3) | [](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.xUnit.v3)
|
||||
| **WireMock.Net.TUnit** | [](https://www.nuget.org/packages/WireMock.Net.TUnit) | [](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.TUnit)
|
||||
| **WireMock.Net.NUnit** | [](https://www.nuget.org/packages/WireMock.Net.NUnit) | [](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.NUnit)
|
||||
| | | |
|
||||
| **WireMock.Net.Extensions.Routing** | [](https://www.nuget.org/packages/WireMock.Net.Extensions.Routing) | [](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.Extensions.Routing)
|
||||
| **WireMock.Net.Matchers.CSharpCode** | [](https://www.nuget.org/packages/WireMock.Net.Matchers.CSharpCode) | [](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.Matchers.CSharpCode)
|
||||
@@ -93,52 +95,55 @@ To still enable this feature, you need to add the `Environment` category to the
|
||||
---
|
||||
|
||||
## :memo: Development
|
||||
For the supported frameworks and build information, see [this](https://github.com/wiremock/WireMock.Net/wiki/Development-Information) page.
|
||||
For the supported frameworks and build information, see [this](https://wiremock.org/dotnet/development-information) page.
|
||||
|
||||
## :star: Stubbing
|
||||
A core feature of WireMock.Net is the ability to return predefined HTTP responses for requests matching criteria.
|
||||
See [Wiki : Stubbing](https://github.com/wiremock/WireMock.Net/wiki/Stubbing).
|
||||
See [Stubbing](https://wiremock.org/dotnet/stubbing).
|
||||
|
||||
## :star: Request Matching
|
||||
WireMock.Net support advanced request-matching logic, see [Wiki : Request Matching](https://github.com/wiremock/WireMock.Net/wiki/Request-Matching).
|
||||
WireMock.Net support advanced request-matching logic, see [Request Matching](https://wiremock.org/dotnet/request-matching).
|
||||
|
||||
## :star: Response Templating
|
||||
The response which is returned WireMock.Net can be changed using templating. This is described here [Wiki : Response Templating](https://github.com/wiremock/WireMock.Net/wiki/Response-Templating).
|
||||
The response which is returned WireMock.Net can be changed using templating. This is described here [Response Templating](https://wiremock.org/dotnet/response-templating).
|
||||
|
||||
## :star: Admin API Reference
|
||||
The WireMock admin API provides functionality to define the mappings via a http interface see [Wiki : Admin API Reference](https://github.com/StefH/WireMock.Net/wiki/Admin-API-Reference).
|
||||
The WireMock admin API provides functionality to define the mappings via a http interface see [Admin API Reference](https://wiremock.org/dotnet/admin-api-reference).
|
||||
|
||||
## :star: Using
|
||||
WireMock.Net can be used in several ways:
|
||||
|
||||
### UnitTesting
|
||||
You can use your favorite test framework and use WireMock within your tests, see
|
||||
[Wiki : UnitTesting](https://github.com/StefH/WireMock.Net/wiki/Using-WireMock-in-UnitTests).
|
||||
[UnitTesting](https://wiremock.org/dotnet/using-wiremock-in-unittests).
|
||||
|
||||
### Unit/Integration Testing using Testcontainers.DotNet
|
||||
See [Wiki : WireMock.Net.Testcontainers](https://github.com/wiremock/WireMock.Net/wiki/Using-WireMock.Net.Testcontainers) on how to build a WireMock.Net Docker container which can be used in Unit/Integration testing.
|
||||
See [WireMock.Net.Testcontainers](https://wiremock.org/dotnet/using-wiremock-net-testcontainers/) on how to build a WireMock.Net Docker container which can be used in Unit/Integration testing.
|
||||
|
||||
### Unit/Integration Testing using an an Aspire Distributed Application
|
||||
See [Wiki : WireMock.Net.Aspire](https://github.com/wiremock/WireMock.Net/wiki/Using-WireMock.Net.Aspire) on how to use WireMock.Net as an Aspire Hosted application to do Unit/Integration testing.
|
||||
See [WireMock.Net.Aspire](https://wiremock.org/dotnet/using-wiremock-net-Aspire) on how to use WireMock.Net as an Aspire Hosted application to do Unit/Integration testing.
|
||||
|
||||
### As a dotnet tool
|
||||
It's simple to install WireMock.Net as (global) dotnet tool, see [Wiki : dotnet tool](https://github.com/StefH/WireMock.Net/wiki/WireMock-as-dotnet-tool).
|
||||
It's simple to install WireMock.Net as (global) dotnet tool, see [dotnet tool](https://wiremock.org/dotnet/wiremock-as-dotnet-tool).
|
||||
|
||||
### As standalone process / console application
|
||||
This is quite straight forward to launch a mock server within a console application, see [Wiki : Standalone Process](https://github.com/StefH/WireMock.Net/wiki/WireMock-as-a-standalone-process).
|
||||
This is quite straight forward to launch a mock server within a console application, see [Standalone Process](https://wiremock.org/dotnet/wiremock-as-a-standalone-process).
|
||||
|
||||
### As a Windows Service
|
||||
You can also run WireMock.Net as a Windows Service, follow this [WireMock-as-a-Windows-Service](https://github.com/wiremock/WireMock.Net/wiki/WireMock-as-a-Windows-Service).
|
||||
You can also run WireMock.Net as a Windows Service, follow this [Windows Service](https://wiremock.org/dotnet/wiremock-as-a-windows-service).
|
||||
|
||||
### As a Web Job in Azure or application in IIS
|
||||
See this link [WireMock-as-a-(Azure)-Web-App](https://github.com/wiremock/WireMock.Net/wiki/WireMock-as-a-(Azure)-Web-App)
|
||||
See this link [WireMock-as-a-(Azure)-Web-App](https://wiremock.org/dotnet/wiremock-as-a-azure-web-app/)
|
||||
|
||||
### In a docker container
|
||||
There is also a Linux and Windows-Nano container available at [hub.docker.com](https://hub.docker.com/r/sheyenrath).
|
||||
For more details see also [Docker](https://github.com/wiremock/WireMock.Net-docker).
|
||||
|
||||
#### HTTPS / SSL
|
||||
More details on using HTTPS (SSL) can be found here [Wiki : HTTPS](https://github.com/wiremock/WireMock.Net/wiki/Using-HTTPS-(SSL))
|
||||
### HTTPS / SSL
|
||||
More details on using HTTPS (SSL) can be found here [HTTPS](https://wiremock.org/dotnet/using-https-ssl/)
|
||||
|
||||
## :books: Documentation
|
||||
For more info, see also this documentation page: [What is WireMock.Net](https://wiremock.org/dotnet/what-is-wiremock-net/).
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.0.31521.260
|
||||
# Visual Studio Version 18
|
||||
VisualStudioVersion = 18.0.11205.157
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{8F890C6F-9ACC-438D-928A-AD61CDA862F2}"
|
||||
EndProject
|
||||
@@ -144,6 +144,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.Extensions.Rou
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.ProtoBuf", "src\WireMock.Net.ProtoBuf\WireMock.Net.ProtoBuf.csproj", "{B47413AA-55D3-49A7-896A-17ADBFF72407}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.xUnit.v3", "src\WireMock.Net.xUnit.v3\WireMock.Net.xUnit.v3.csproj", "{4F46BD02-BEBC-4B2D-B857-4169AD1FB067}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.NUnit", "src\WireMock.Net.NUnit\WireMock.Net.NUnit.csproj", "{2DBBD70D-8051-441F-92BB-FF9B8B4B4982}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.Console.MimePart", "examples\WireMock.Net.Console.MimePart\WireMock.Net.Console.MimePart.csproj", "{4005E20C-D42B-138A-79BE-B3F5420C563F}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -350,6 +356,18 @@ Global
|
||||
{B47413AA-55D3-49A7-896A-17ADBFF72407}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B47413AA-55D3-49A7-896A-17ADBFF72407}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B47413AA-55D3-49A7-896A-17ADBFF72407}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{4F46BD02-BEBC-4B2D-B857-4169AD1FB067}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{4F46BD02-BEBC-4B2D-B857-4169AD1FB067}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4F46BD02-BEBC-4B2D-B857-4169AD1FB067}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{4F46BD02-BEBC-4B2D-B857-4169AD1FB067}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{2DBBD70D-8051-441F-92BB-FF9B8B4B4982}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{2DBBD70D-8051-441F-92BB-FF9B8B4B4982}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{2DBBD70D-8051-441F-92BB-FF9B8B4B4982}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{2DBBD70D-8051-441F-92BB-FF9B8B4B4982}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{4005E20C-D42B-138A-79BE-B3F5420C563F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{4005E20C-D42B-138A-79BE-B3F5420C563F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4005E20C-D42B-138A-79BE-B3F5420C563F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{4005E20C-D42B-138A-79BE-B3F5420C563F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -407,6 +425,9 @@ Global
|
||||
{3FCBCA9C-9DB0-4A96-B47E-30470764CC9C} = {0BB8B634-407A-4610-A91F-11586990767A}
|
||||
{1E874C8F-08A2-493B-8421-619F9A6E9E77} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2}
|
||||
{B47413AA-55D3-49A7-896A-17ADBFF72407} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2}
|
||||
{4F46BD02-BEBC-4B2D-B857-4169AD1FB067} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2}
|
||||
{2DBBD70D-8051-441F-92BB-FF9B8B4B4982} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2}
|
||||
{4005E20C-D42B-138A-79BE-B3F5420C563F} = {985E0ADB-D4B4-473A-AA40-567E279B7946}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {DC539027-9852-430C-B19F-FD035D018458}
|
||||
|
||||
@@ -100,7 +100,7 @@ jobs:
|
||||
- job: Windows_Build_Test
|
||||
|
||||
pool:
|
||||
vmImage: 'windows-2022'
|
||||
vmImage: 'windows-2025'
|
||||
|
||||
steps:
|
||||
- task: UseDotNet@2
|
||||
@@ -141,9 +141,13 @@ jobs:
|
||||
dependsOn: Windows_Build_Test
|
||||
|
||||
pool:
|
||||
vmImage: 'windows-2022'
|
||||
vmImage: 'windows-2025'
|
||||
|
||||
steps:
|
||||
- script: |
|
||||
echo "BuildId = $(buildId)"
|
||||
displayName: 'Print buildId'
|
||||
|
||||
- task: UseDotNet@2
|
||||
displayName: Use .NET 8.0
|
||||
inputs:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
pool:
|
||||
vmImage: 'windows-2022'
|
||||
vmImage: 'windows-2025'
|
||||
|
||||
variables:
|
||||
Prerelease: ''
|
||||
|
||||
@@ -21,4 +21,13 @@
|
||||
<PackageReference Include="Aspire.Hosting.AppHost" Version="9.2.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
<ItemGroup>
|
||||
<None Update="__admin\mappings\*.proto">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="__admin\mappings\*.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -4,12 +4,25 @@ var builder = DistributedApplication.CreateBuilder(args);
|
||||
|
||||
// IResourceBuilder<ProjectResource> apiService = builder.AddProject<Projects.AspireApp1_ApiService>("apiservice");
|
||||
|
||||
var mappingsPath = Path.Combine(Directory.GetCurrentDirectory(), "WireMockMappings");
|
||||
var mappingsPath = Path.Combine(Directory.GetCurrentDirectory(), "__admin", "mappings");
|
||||
|
||||
IResourceBuilder<WireMockServerResource> apiService = builder
|
||||
.AddWireMock("apiservice", WireMockServerArguments.DefaultPort)
|
||||
//IResourceBuilder<WireMockServerResource> apiService1 = builder
|
||||
// //.AddWireMock("apiservice", WireMockServerArguments.DefaultPort)
|
||||
// .AddWireMock("apiservice1", "http://*:8081", "grpc://*:9091")
|
||||
// .AsHttp2Service()
|
||||
// .WithMappingsPath(mappingsPath)
|
||||
// .WithReadStaticMappings()
|
||||
// .WithWatchStaticMappings()
|
||||
// .WithApiMappingBuilder(WeatherForecastApiMock.BuildAsync);
|
||||
|
||||
IResourceBuilder<WireMockServerResource> apiService2 = builder
|
||||
.AddWireMock("apiservice", async args =>
|
||||
{
|
||||
args.WithAdditionalUrls("http://*:8081", "grpc://*:9093");
|
||||
args.WithProtoDefinition("my-greeter", await File.ReadAllTextAsync(Path.Combine(mappingsPath, "greet.proto")));
|
||||
})
|
||||
.AsHttp2Service()
|
||||
.WithMappingsPath(mappingsPath)
|
||||
.WithReadStaticMappings()
|
||||
.WithWatchStaticMappings()
|
||||
.WithApiMappingBuilder(WeatherForecastApiMock.BuildAsync);
|
||||
|
||||
@@ -45,6 +58,7 @@ IResourceBuilder<WireMockServerResource> apiService = builder
|
||||
|
||||
builder.AddProject<Projects.AspireApp1_Web>("webfrontend")
|
||||
.WithExternalHttpEndpoints()
|
||||
.WithReference(apiService);
|
||||
.WithReference(apiService2)
|
||||
.WaitFor(apiService2);
|
||||
|
||||
builder.Build().Run();
|
||||
await builder.Build().RunAsync();
|
||||
@@ -0,0 +1,21 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package greet;
|
||||
|
||||
service Greeter {
|
||||
rpc SayHello (HelloRequest) returns (HelloReply);
|
||||
}
|
||||
|
||||
message HelloRequest {
|
||||
string name = 1;
|
||||
}
|
||||
|
||||
message HelloReply {
|
||||
string message = 1;
|
||||
enum PhoneType {
|
||||
none = 0;
|
||||
mobile = 1;
|
||||
home = 2;
|
||||
}
|
||||
PhoneType phoneType = 2;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"Guid": "351f0240-bba0-4bcb-93c6-1feba0fe0004",
|
||||
"Title": "ProtoBuf Mapping 4",
|
||||
"Request": {
|
||||
"Path": {
|
||||
"Matchers": [
|
||||
{
|
||||
"Name": "WildcardMatcher",
|
||||
"Pattern": "/greet.Greeter/SayHello",
|
||||
"IgnoreCase": false
|
||||
}
|
||||
]
|
||||
},
|
||||
"Methods": [
|
||||
"POST"
|
||||
],
|
||||
"Body": {
|
||||
"Matcher": {
|
||||
"Name": "ProtoBufMatcher",
|
||||
"ProtoBufMessageType": "greet.HelloRequest"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Response": {
|
||||
"BodyAsJson": {
|
||||
"message": "hello {{request.BodyAsJson.name}} {{request.method}}"
|
||||
},
|
||||
"UseTransformer": true,
|
||||
"TransformerType": "Handlebars",
|
||||
"TransformerReplaceNodeOptions": "EvaluateAndTryToConvert",
|
||||
"Headers": {
|
||||
"Content-Type": "application/grpc"
|
||||
},
|
||||
"TrailingHeaders": {
|
||||
"grpc-status": "0"
|
||||
},
|
||||
"ProtoBufMessageType": "greet.HelloReply"
|
||||
},
|
||||
"ProtoDefinition": "my-greeter"
|
||||
}
|
||||
82
examples/WireMock.Net.Console.MimePart/MainApp.cs
Normal file
82
examples/WireMock.Net.Console.MimePart/MainApp.cs
Normal file
@@ -0,0 +1,82 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using Newtonsoft.Json;
|
||||
using WireMock.Logging;
|
||||
using WireMock.Matchers;
|
||||
using WireMock.RequestBuilders;
|
||||
using WireMock.ResponseBuilders;
|
||||
using WireMock.Server;
|
||||
using WireMock.Settings;
|
||||
|
||||
namespace WireMock.Net.Console.MimePart;
|
||||
|
||||
// Test this CURL:
|
||||
// curl -X POST http://localhost:9091/multipart -F "plainText=This is some plain text;type=text/plain" -F "jsonData={ `"Key`": `"Value`" };type=application/json" -F "image=@image.png;type=image/png"
|
||||
//
|
||||
// curl -X POST http://localhost:9091/multipart2 -F "plainText=This is some plain text;type=text/plain" -F "jsonData={ `"Key`": `"Value`" };type=application/json" -F "image=@image.png;type=image/png"
|
||||
|
||||
public static class MainApp
|
||||
{
|
||||
public static async Task RunAsync()
|
||||
{
|
||||
using var server = WireMockServer.Start(new WireMockServerSettings
|
||||
{
|
||||
Port = 9091,
|
||||
StartAdminInterface = true,
|
||||
|
||||
ReadStaticMappings = true,
|
||||
//WatchStaticMappings = true,
|
||||
//WatchStaticMappingsInSubdirectories = true,
|
||||
|
||||
Logger = new WireMockConsoleLogger()
|
||||
});
|
||||
System.Console.WriteLine("WireMockServer listening at {0}", string.Join(",", server.Urls));
|
||||
|
||||
var textPlainContentTypeMatcher = new ContentTypeMatcher("text/plain");
|
||||
var textPlainContentMatcher = new ExactMatcher("This is some plain text");
|
||||
var textPlainMatcher = new MimePartMatcher(MatchBehaviour.AcceptOnMatch, textPlainContentTypeMatcher, null, null, textPlainContentMatcher);
|
||||
|
||||
var textJsonContentTypeMatcher = new ContentTypeMatcher("application/json");
|
||||
var textJsonContentMatcher = new JsonMatcher(new { Key = "Value" }, true);
|
||||
var textJsonMatcher = new MimePartMatcher(MatchBehaviour.AcceptOnMatch, textJsonContentTypeMatcher, null, null, textJsonContentMatcher);
|
||||
|
||||
var imagePngContentTypeMatcher = new ContentTypeMatcher("image/png");
|
||||
var imagePngContentDispositionMatcher = new ExactMatcher("form-data; name=\"image\"; filename=\"image.png\"");
|
||||
var imagePngContentTransferEncodingMatcher = new ExactMatcher("default");
|
||||
var imagePngContentMatcher = new ExactObjectMatcher(Convert.FromBase64String("iVBORw0KGgoAAAANSUhEUgAAAAIAAAACAgMAAAAP2OW3AAAADFBMVEX/tID/vpH/pWX/sHidUyjlAAAADElEQVR4XmMQYNgAAADkAMHebX3mAAAAAElFTkSuQmCC"));
|
||||
var imagePngMatcher = new MimePartMatcher(MatchBehaviour.AcceptOnMatch, imagePngContentTypeMatcher, imagePngContentDispositionMatcher, imagePngContentTransferEncodingMatcher, imagePngContentMatcher);
|
||||
|
||||
var matchers = new IMatcher[]
|
||||
{
|
||||
textPlainMatcher,
|
||||
textJsonMatcher,
|
||||
imagePngMatcher
|
||||
};
|
||||
|
||||
server
|
||||
.Given(Request.Create()
|
||||
.WithPath("/multipart")
|
||||
.UsingPost()
|
||||
.WithMultiPart(matchers)
|
||||
)
|
||||
.WithGuid("b9c82182-e469-41da-bcaf-b6e3157fefdb")
|
||||
.RespondWith(Response.Create()
|
||||
.WithBody("MultiPart is ok")
|
||||
);
|
||||
|
||||
// server.SaveStaticMappings();
|
||||
|
||||
System.Console.WriteLine(JsonConvert.SerializeObject(server.MappingModels, Formatting.Indented));
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
23
examples/WireMock.Net.Console.MimePart/Program.cs
Normal file
23
examples/WireMock.Net.Console.MimePart/Program.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System.Reflection;
|
||||
using log4net;
|
||||
using log4net.Config;
|
||||
using log4net.Repository;
|
||||
|
||||
namespace WireMock.Net.Console.MimePart;
|
||||
|
||||
static class Program
|
||||
{
|
||||
private static readonly ILoggerRepository LogRepository = LogManager.GetRepository(Assembly.GetEntryAssembly());
|
||||
private static readonly ILog Log = LogManager.GetLogger(typeof(Program));
|
||||
|
||||
static async Task Main(params string[] args)
|
||||
{
|
||||
Log.Info("Starting WireMock.Net.Console.MimePart...");
|
||||
|
||||
XmlConfigurator.Configure(LogRepository, new FileInfo("log4net.config"));
|
||||
|
||||
await MainApp.RunAsync();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<DefineConstants>$(DefineConstants);GRAPHQL;MIMEKIT;PROTOBUF</DefineConstants>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="__admin\mappings\*.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\WireMock.Net\WireMock.Net.csproj" />
|
||||
<PackageReference Include="log4net" Version="2.0.15" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="log4net.config">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,79 @@
|
||||
{
|
||||
"Guid": "b9c82182-e469-41da-bcaf-b6e3157fefdc",
|
||||
"UpdatedAt": "2025-12-18T17:21:57.3879723Z",
|
||||
"Request": {
|
||||
"Path": {
|
||||
"Matchers": [
|
||||
{
|
||||
"Name": "WildcardMatcher",
|
||||
"Pattern": "/multipart2",
|
||||
"IgnoreCase": false
|
||||
}
|
||||
]
|
||||
},
|
||||
"Methods": [
|
||||
"POST"
|
||||
],
|
||||
"Body": {
|
||||
"MatcherName": "MultiPartMatcher",
|
||||
"Matchers": [
|
||||
{
|
||||
"Name": "MimePartMatcher",
|
||||
"ContentTypeMatcher": {
|
||||
"Name": "ContentTypeMatcher",
|
||||
"Pattern": "text/plain",
|
||||
"IgnoreCase": false
|
||||
},
|
||||
"ContentMatcher": {
|
||||
"Name": "ExactMatcher",
|
||||
"Pattern": "This is some plain text",
|
||||
"IgnoreCase": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "MimePartMatcher",
|
||||
"ContentTypeMatcher": {
|
||||
"Name": "ContentTypeMatcher",
|
||||
"Pattern": "application/json",
|
||||
"IgnoreCase": false
|
||||
},
|
||||
"ContentMatcher": {
|
||||
"Name": "JsonMatcher",
|
||||
"Pattern": {
|
||||
"Key": "Value"
|
||||
},
|
||||
"IgnoreCase": true,
|
||||
"Regex": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "MimePartMatcher",
|
||||
"ContentTypeMatcher": {
|
||||
"Name": "ContentTypeMatcher",
|
||||
"Pattern": "image/png",
|
||||
"IgnoreCase": false
|
||||
},
|
||||
"ContentDispositionMatcher": {
|
||||
"Name": "ExactMatcher",
|
||||
"Pattern": "form-data; name=\"image\"; filename=\"image.png\"",
|
||||
"IgnoreCase": false
|
||||
},
|
||||
"ContentTransferEncodingMatcher": {
|
||||
"Name": "ExactMatcher",
|
||||
"Pattern": "default",
|
||||
"IgnoreCase": false
|
||||
},
|
||||
"ContentMatcher": {
|
||||
"Name": "ExactObjectMatcher",
|
||||
"Pattern": "iVBORw0KGgoAAAANSUhEUgAAAAIAAAACAgMAAAAP2OW3AAAADFBMVEX/tID/vpH/pWX/sHidUyjlAAAADElEQVR4XmMQYNgAAADkAMHebX3mAAAAAElFTkSuQmCC"
|
||||
}
|
||||
}
|
||||
],
|
||||
"MatchOperator": "Or"
|
||||
}
|
||||
},
|
||||
"Response": {
|
||||
"BodyDestination": "SameAsSource",
|
||||
"Body": "MultiPart2 is ok"
|
||||
}
|
||||
}
|
||||
20
examples/WireMock.Net.Console.MimePart/log4net.config
Normal file
20
examples/WireMock.Net.Console.MimePart/log4net.config
Normal file
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<configuration>
|
||||
<configSections>
|
||||
<section name="log4net" type="System.Configuration.IgnoreSectionHandler" />
|
||||
</configSections>
|
||||
<appSettings>
|
||||
<add key="log4net.Internal.Debug" value="true"/>
|
||||
</appSettings>
|
||||
<log4net>
|
||||
<appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender">
|
||||
<layout type="log4net.Layout.PatternLayout">
|
||||
<conversionPattern value="%date [%thread] %-5level %logger{1} - %message%newline" />
|
||||
</layout>
|
||||
</appender>
|
||||
<root>
|
||||
<level value="DEBUG" />
|
||||
<appender-ref ref="ConsoleAppender" />
|
||||
</root>
|
||||
</log4net>
|
||||
</configuration>
|
||||
@@ -339,7 +339,7 @@ namespace WireMock.Net.ConsoleApplication
|
||||
}
|
||||
}
|
||||
System.Console.WriteLine("X = {0} ; default = {1} ; pX = {2:0.00} ; valueX = {3:0.00}", xCount, defaultCount, pX, 1.0 * xCount / tot);
|
||||
return;
|
||||
|
||||
using var httpAndHttpsWithPort = WireMockServer.Start(new WireMockServerSettings
|
||||
{
|
||||
HostingScheme = HostingScheme.HttpAndHttps,
|
||||
@@ -556,39 +556,6 @@ namespace WireMock.Net.ConsoleApplication
|
||||
);
|
||||
#endif
|
||||
|
||||
#if MIMEKIT
|
||||
var textPlainContentTypeMatcher = new ContentTypeMatcher("text/plain");
|
||||
var textPlainContentMatcher = new ExactMatcher("This is some plain text");
|
||||
var textPlainMatcher = new MimePartMatcher(MatchBehaviour.AcceptOnMatch, textPlainContentTypeMatcher, null, null, textPlainContentMatcher);
|
||||
|
||||
var textJsonContentTypeMatcher = new ContentTypeMatcher("text/json");
|
||||
var textJsonContentMatcher = new JsonMatcher(new { Key = "Value" }, true);
|
||||
var textJsonMatcher = new MimePartMatcher(MatchBehaviour.AcceptOnMatch, textJsonContentTypeMatcher, null, null, textJsonContentMatcher);
|
||||
|
||||
var imagePngContentTypeMatcher = new ContentTypeMatcher("image/png");
|
||||
var imagePngContentDispositionMatcher = new ExactMatcher("attachment; filename=\"image.png\"");
|
||||
var imagePngContentTransferEncodingMatcher = new ExactMatcher("base64");
|
||||
var imagePngContentMatcher = new ExactObjectMatcher(Convert.FromBase64String("iVBORw0KGgoAAAANSUhEUgAAAAIAAAACAgMAAAAP2OW3AAAADFBMVEX/tID/vpH/pWX/sHidUyjlAAAADElEQVR4XmMQYNgAAADkAMHebX3mAAAAAElFTkSuQmCC"));
|
||||
var imagePngMatcher = new MimePartMatcher(MatchBehaviour.AcceptOnMatch, imagePngContentTypeMatcher, imagePngContentDispositionMatcher, imagePngContentTransferEncodingMatcher, imagePngContentMatcher);
|
||||
|
||||
var matchers = new IMatcher[]
|
||||
{
|
||||
textPlainMatcher,
|
||||
textJsonMatcher,
|
||||
imagePngMatcher
|
||||
};
|
||||
|
||||
server
|
||||
.Given(Request.Create()
|
||||
.WithPath("/multipart")
|
||||
.UsingPost()
|
||||
.WithMultiPart(matchers)
|
||||
)
|
||||
.WithGuid("b9c82182-e469-41da-bcaf-b6e3157fefdb")
|
||||
.RespondWith(Response.Create()
|
||||
.WithBody("MultiPart is ok")
|
||||
);
|
||||
#endif
|
||||
// 400 ms
|
||||
server
|
||||
.Given(Request.Create()
|
||||
|
||||
@@ -8,6 +8,12 @@ namespace WireMock.Admin.Mappings;
|
||||
[FluentBuilder.AutoGenerateBuilder]
|
||||
public class BodyModel
|
||||
{
|
||||
/// <summary>
|
||||
/// The name of the body matcher.
|
||||
/// Currently only "MultiPartMatcher" is supported.
|
||||
/// </summary>
|
||||
public string? MatcherName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the matcher.
|
||||
/// </summary>
|
||||
|
||||
@@ -22,7 +22,7 @@ public class MatcherModel
|
||||
public object? Pattern { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the patterns. Can be array of strings (default) or an array of objects.
|
||||
/// Gets or sets the patterns. Can be an array of strings (default) or an array of objects.
|
||||
/// </summary>
|
||||
public object[]? Patterns { get; set; }
|
||||
|
||||
|
||||
@@ -24,4 +24,13 @@ public class StatusModel
|
||||
/// The error message.
|
||||
/// </summary>
|
||||
public string? Error { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string that represents the current status model, including its unique identifier, status, and error information.
|
||||
/// </summary>
|
||||
/// <returns>A string containing the values of the Guid, Status, and Error properties formatted for display.</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"StatusModel [Guid={Guid}, Status={Status}, Error={Error}]";
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System.Collections.Generic;
|
||||
using WireMock.Matchers.Request;
|
||||
|
||||
namespace WireMock.Admin.Requests;
|
||||
|
||||
@@ -47,5 +48,5 @@ public class LogRequestMatchModel
|
||||
/// <value>
|
||||
/// The match details.
|
||||
/// </value>
|
||||
public IList<object> MatchDetails { get; set; }
|
||||
public IList<MatchDetail> MatchDetails { get; set; } = [];
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System;
|
||||
using WireMock.Validators;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace WireMock.Admin.Mappings;
|
||||
@@ -94,9 +95,14 @@ public partial class RequestModelBuilder
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the Path.
|
||||
/// Set the Path. Must start with a forward slash (/).
|
||||
/// </summary>
|
||||
public RequestModelBuilder WithPath(string value) => WithPath(() => value);
|
||||
public RequestModelBuilder WithPath(string value)
|
||||
{
|
||||
PathValidator.ValidateAndThrow(value);
|
||||
|
||||
return WithPath(() => value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the Path.
|
||||
|
||||
@@ -55,4 +55,11 @@ public interface IRequestMatchResult : IComparable
|
||||
/// <param name="exception">The exception [Optional].</param>
|
||||
/// <returns>The score.</returns>
|
||||
double AddScore(Type matcherType, double score, Exception? exception);
|
||||
|
||||
/// <summary>
|
||||
/// Adds the score.
|
||||
/// </summary>
|
||||
/// <param name="matchDetail">The matchDetail.</param>
|
||||
/// <returns>The score.</returns>
|
||||
double AddMatchDetail(MatchDetail matchDetail);
|
||||
}
|
||||
@@ -12,7 +12,12 @@ public class MatchDetail
|
||||
/// <summary>
|
||||
/// Gets or sets the type of the matcher.
|
||||
/// </summary>
|
||||
public Type MatcherType { get; set; } = null!;
|
||||
public required Type MatcherType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the type of the matcher.
|
||||
/// </summary>
|
||||
public required string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the score between 0.0 and 1.0
|
||||
@@ -24,4 +29,9 @@ public class MatchDetail
|
||||
/// [Optional]
|
||||
/// </summary>
|
||||
public Exception? Exception { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The child MatchResults in case of multiple matchers.
|
||||
/// </summary>
|
||||
public MatchDetail[]? MatchDetails { get; set; }
|
||||
}
|
||||
19
src/WireMock.Net.Abstractions/Validators/PathValidator.cs
Normal file
19
src/WireMock.Net.Abstractions/Validators/PathValidator.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System;
|
||||
|
||||
namespace WireMock.Validators;
|
||||
|
||||
public static class PathValidator
|
||||
{
|
||||
/// <summary>
|
||||
/// A valid path must start with a '/' and cannot be null, empty or whitespace.
|
||||
/// </summary>
|
||||
public static void ValidateAndThrow(string? path, string? paramName = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path) || path?.StartsWith("/") == false)
|
||||
{
|
||||
throw new ArgumentException("Path must start with a '/' and cannot be null, empty or whitespace.", paramName ?? nameof(path));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,10 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="..\WireMock.Net.Minimal\Util\EnhancedFileSystemWatcher.cs" Link="Utils\EnhancedFileSystemWatcher.cs" />
|
||||
<Compile Include="..\WireMock.Net.Minimal\Util\EnhancedFileSystemWatcher.cs" Link="Util\EnhancedFileSystemWatcher.cs" />
|
||||
<Compile Include="..\WireMock.Net.Minimal\Constants\WireMockConstants.cs" Link="Constants\WireMockConstants.cs" />
|
||||
<Compile Include="..\WireMock.Net.Shared\Constants\RegexConstants.cs" Link="Constants\RegexConstants.cs" />
|
||||
<Compile Include="..\WireMock.Net.Minimal\Util\PortUtils.cs" Link="Util\PortUtils.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)' == 'Debug - Sonar'">
|
||||
|
||||
44
src/WireMock.Net.Aspire/WireMockHealthCheck.cs
Normal file
44
src/WireMock.Net.Aspire/WireMockHealthCheck.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using Aspire.Hosting.ApplicationModel;
|
||||
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
||||
using WireMock.Client;
|
||||
|
||||
namespace WireMock.Net.Aspire;
|
||||
|
||||
/// <summary>
|
||||
/// WireMockHealthCheck
|
||||
/// </summary>
|
||||
public class WireMockHealthCheck(WireMockServerResource resource) : IHealthCheck
|
||||
{
|
||||
private const string HealthStatusHealthy = "Healthy";
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (!await IsHealthyAsync(resource.AdminApi.Value, cancellationToken))
|
||||
{
|
||||
return HealthCheckResult.Unhealthy("WireMock.Net is not healthy");
|
||||
}
|
||||
|
||||
if (resource.ApiMappingState == WireMockMappingState.NotSubmitted)
|
||||
{
|
||||
return HealthCheckResult.Unhealthy("WireMock.Net has not received mappings");
|
||||
}
|
||||
|
||||
return HealthCheckResult.Healthy();
|
||||
}
|
||||
|
||||
private static async Task<bool> IsHealthyAsync(IWireMockAdminApi adminApi, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
var status = await adminApi.GetHealthAsync(cancellationToken);
|
||||
return string.Equals(status, HealthStatusHealthy, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
12
src/WireMock.Net.Aspire/WireMockMappingState.cs
Normal file
12
src/WireMock.Net.Aspire/WireMockMappingState.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
namespace WireMock.Net.Aspire;
|
||||
|
||||
internal enum WireMockMappingState
|
||||
{
|
||||
NoMappings = 0,
|
||||
|
||||
NotSubmitted = 1,
|
||||
|
||||
Submitted = 2
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Stef.Validation;
|
||||
using WireMock.Client.Builders;
|
||||
using WireMock.Util;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace Aspire.Hosting;
|
||||
@@ -21,10 +23,15 @@ public class WireMockServerArguments
|
||||
private const string DefaultLogger = "WireMockConsoleLogger";
|
||||
|
||||
/// <summary>
|
||||
/// The HTTP port where WireMock.Net is listening.
|
||||
/// The HTTP ports where WireMock.Net is listening on.
|
||||
/// If not defined, .NET Aspire automatically assigns a random port.
|
||||
/// </summary>
|
||||
public int? HttpPort { get; set; }
|
||||
public List<int> HttpPorts { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Additional Urls on which WireMock listens.
|
||||
/// </summary>
|
||||
public List<string> AdditionalUrls { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// The admin username.
|
||||
@@ -67,6 +74,42 @@ public class WireMockServerArguments
|
||||
/// </summary>
|
||||
public Func<AdminApiMappingBuilder, CancellationToken, Task>? ApiMappingBuilder { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Grpc ProtoDefinitions.
|
||||
/// </summary>
|
||||
public Dictionary<string, string[]> ProtoDefinitions { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Add an additional Urls on which WireMock should listen.
|
||||
/// </summary>
|
||||
/// <param name="additionalUrls">The additional urls which the WireMock Server should listen on.</param>
|
||||
public void WithAdditionalUrls(params string[] additionalUrls)
|
||||
{
|
||||
foreach (var url in additionalUrls)
|
||||
{
|
||||
if (!PortUtils.TryExtract(Guard.NotNullOrEmpty(url), out _, out _, out _, out _, out var port))
|
||||
{
|
||||
throw new ArgumentException($"The URL '{url}' is not valid.");
|
||||
}
|
||||
|
||||
AdditionalUrls.Add(Guard.NotNullOrWhiteSpace(url));
|
||||
HttpPorts.Add(port);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a Grpc ProtoDefinition at server-level.
|
||||
/// </summary>
|
||||
/// <param name="id">Unique identifier for the ProtoDefinition.</param>
|
||||
/// <param name="protoDefinitions">The ProtoDefinition as text.</param>
|
||||
public void WithProtoDefinition(string id, params string[] protoDefinitions)
|
||||
{
|
||||
Guard.NotNullOrWhiteSpace(id);
|
||||
Guard.NotNullOrEmpty(protoDefinitions);
|
||||
|
||||
ProtoDefinitions[id] = protoDefinitions;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the current instance's properties to an array of command-line arguments for starting the WireMock.Net server.
|
||||
/// </summary>
|
||||
@@ -95,6 +138,11 @@ public class WireMockServerArguments
|
||||
Add(args, "--WatchStaticMappingsInSubdirectories", "true");
|
||||
}
|
||||
|
||||
if (AdditionalUrls.Count > 0)
|
||||
{
|
||||
Add(args, "--Urls", $"http://*:{HttpContainerPort} {string.Join(' ', AdditionalUrls)}");
|
||||
}
|
||||
|
||||
return args
|
||||
.SelectMany(k => new[] { k.Key, k.Value })
|
||||
.ToArray();
|
||||
|
||||
@@ -3,10 +3,12 @@
|
||||
using Aspire.Hosting.ApplicationModel;
|
||||
using Aspire.Hosting.Lifecycle;
|
||||
using Aspire.Hosting.WireMock;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
||||
using Stef.Validation;
|
||||
using WireMock.Client.Builders;
|
||||
using WireMock.Net.Aspire;
|
||||
using WireMock.Util;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace Aspire.Hosting;
|
||||
@@ -33,9 +35,31 @@ public static class WireMockServerBuilderExtensions
|
||||
Guard.NotNullOrWhiteSpace(name);
|
||||
Guard.Condition(port, p => p is null or > 0 and <= ushort.MaxValue);
|
||||
|
||||
return builder.AddWireMock(name, callback =>
|
||||
return builder.AddWireMock(name, serverArguments =>
|
||||
{
|
||||
callback.HttpPort = port;
|
||||
if (port != null)
|
||||
{
|
||||
serverArguments.HttpPorts = [port.Value];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a WireMock.Net Server resource to the application model.
|
||||
/// </summary>
|
||||
/// <param name="builder">The <see cref="IDistributedApplicationBuilder"/>.</param>
|
||||
/// <param name="name">The name of the resource. This name will be used as the connection string name when referenced in a dependency.</param>
|
||||
/// <param name="additionalUrls">The additional urls which the WireMock Server should listen on.</param>
|
||||
/// <returns>A reference to the <see cref="IResourceBuilder{WireMockServerResource}"/>.</returns>
|
||||
public static IResourceBuilder<WireMockServerResource> AddWireMock(this IDistributedApplicationBuilder builder, string name, params string[] additionalUrls)
|
||||
{
|
||||
Guard.NotNull(builder);
|
||||
Guard.NotNullOrWhiteSpace(name);
|
||||
Guard.NotNull(additionalUrls);
|
||||
|
||||
return builder.AddWireMock(name, serverArguments =>
|
||||
{
|
||||
serverArguments.WithAdditionalUrls(additionalUrls);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -53,13 +77,50 @@ public static class WireMockServerBuilderExtensions
|
||||
Guard.NotNull(arguments);
|
||||
|
||||
var wireMockContainerResource = new WireMockServerResource(name, arguments);
|
||||
|
||||
var healthCheckKey = $"{name}_check";
|
||||
var healthCheckRegistration = new HealthCheckRegistration(
|
||||
healthCheckKey,
|
||||
_ => new WireMockHealthCheck(wireMockContainerResource),
|
||||
failureStatus: null,
|
||||
tags: null);
|
||||
builder.Services.AddHealthChecks().Add(healthCheckRegistration);
|
||||
|
||||
var resourceBuilder = builder
|
||||
.AddResource(wireMockContainerResource)
|
||||
.WithImage(DefaultLinuxImage)
|
||||
.WithEnvironment(ctx => ctx.EnvironmentVariables.Add("DOTNET_USE_POLLING_FILE_WATCHER", "1")) // https://khalidabuhakmeh.com/aspnet-docker-gotchas-and-workarounds#configuration-reloads-and-filesystemwatcher
|
||||
.WithHttpEndpoint(port: arguments.HttpPort, targetPort: WireMockServerArguments.HttpContainerPort)
|
||||
.WithHealthCheck(healthCheckKey)
|
||||
.WithWireMockInspectorCommand();
|
||||
|
||||
if (arguments.HttpPorts.Count == 0)
|
||||
{
|
||||
resourceBuilder = resourceBuilder.WithHttpEndpoint(port: null, targetPort: WireMockServerArguments.HttpContainerPort);
|
||||
}
|
||||
else if (arguments.HttpPorts.Count == 1)
|
||||
{
|
||||
resourceBuilder = resourceBuilder.WithHttpEndpoint(port: arguments.HttpPorts[0], targetPort: WireMockServerArguments.HttpContainerPort);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Required for the default admin endpoint and health checks
|
||||
resourceBuilder = resourceBuilder.WithHttpEndpoint(port: null, targetPort: WireMockServerArguments.HttpContainerPort);
|
||||
|
||||
var anyIsHttp2 = false;
|
||||
foreach (var url in arguments.AdditionalUrls)
|
||||
{
|
||||
PortUtils.TryExtract(url, out _, out var isHttp2, out var scheme, out _, out var httpPort);
|
||||
anyIsHttp2 |= isHttp2;
|
||||
|
||||
resourceBuilder = resourceBuilder.WithEndpoint(port: httpPort, targetPort: httpPort, scheme: scheme, name: $"{scheme}-{httpPort}");
|
||||
}
|
||||
|
||||
if (anyIsHttp2)
|
||||
{
|
||||
resourceBuilder = resourceBuilder.AsHttp2Service();
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(arguments.MappingsPath))
|
||||
{
|
||||
resourceBuilder = resourceBuilder.WithBindMount(arguments.MappingsPath, DefaultLinuxMappingsPath);
|
||||
@@ -73,6 +134,9 @@ public static class WireMockServerBuilderExtensions
|
||||
}
|
||||
});
|
||||
|
||||
// Always add the lifecycle hook to support dynamic mappings and proto definitions
|
||||
resourceBuilder.ApplicationBuilder.Services.TryAddLifecycleHook<WireMockServerLifecycleHook>();
|
||||
|
||||
return resourceBuilder;
|
||||
}
|
||||
|
||||
@@ -83,7 +147,10 @@ public static class WireMockServerBuilderExtensions
|
||||
/// <param name="name">The name of the resource. This name will be used as the connection string name when referenced in a dependency.</param>
|
||||
/// <param name="callback">A callback that allows for setting the <see cref="WireMockServerArguments"/>.</param>
|
||||
/// <returns>A reference to the <see cref="IResourceBuilder{WireMockServerResource}"/>.</returns>
|
||||
public static IResourceBuilder<WireMockServerResource> AddWireMock(this IDistributedApplicationBuilder builder, string name, Action<WireMockServerArguments> callback)
|
||||
public static IResourceBuilder<WireMockServerResource> AddWireMock(
|
||||
this IDistributedApplicationBuilder builder,
|
||||
string name,
|
||||
Action<WireMockServerArguments> callback)
|
||||
{
|
||||
Guard.NotNull(builder);
|
||||
Guard.NotNullOrWhiteSpace(name);
|
||||
@@ -154,7 +221,7 @@ public static class WireMockServerBuilderExtensions
|
||||
/// </summary>
|
||||
/// <param name="wiremock">The <see cref="IResourceBuilder{WireMockServerResource}"/>.</param>
|
||||
/// <param name="configure">Delegate that will be invoked to configure the WireMock.Net resource.</param>
|
||||
/// <returns></returns>
|
||||
/// <returns>A reference to the <see cref="IResourceBuilder{WireMockServerResource}"/>.</returns>
|
||||
public static IResourceBuilder<WireMockServerResource> WithApiMappingBuilder(this IResourceBuilder<WireMockServerResource> wiremock, Func<AdminApiMappingBuilder, Task> configure)
|
||||
{
|
||||
return wiremock.WithApiMappingBuilder((adminApiMappingBuilder, _) => configure.Invoke(adminApiMappingBuilder));
|
||||
@@ -165,13 +232,27 @@ public static class WireMockServerBuilderExtensions
|
||||
/// </summary>
|
||||
/// <param name="wiremock">The <see cref="IResourceBuilder{WireMockServerResource}"/>.</param>
|
||||
/// <param name="configure">Delegate that will be invoked to configure the WireMock.Net resource.</param>
|
||||
/// <returns></returns>
|
||||
/// <returns>A reference to the <see cref="IResourceBuilder{WireMockServerResource}"/>.</returns>
|
||||
public static IResourceBuilder<WireMockServerResource> WithApiMappingBuilder(this IResourceBuilder<WireMockServerResource> wiremock, Func<AdminApiMappingBuilder, CancellationToken, Task> configure)
|
||||
{
|
||||
Guard.NotNull(wiremock);
|
||||
|
||||
wiremock.ApplicationBuilder.Services.TryAddLifecycleHook<WireMockServerLifecycleHook>();
|
||||
wiremock.Resource.Arguments.ApiMappingBuilder = configure;
|
||||
wiremock.Resource.ApiMappingState = WireMockMappingState.NotSubmitted;
|
||||
|
||||
return wiremock;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a Grpc ProtoDefinition at server-level.
|
||||
/// </summary>
|
||||
/// <param name="wiremock">The <see cref="IResourceBuilder{WireMockServerResource}"/>.</param>
|
||||
/// <param name="id">Unique identifier for the ProtoDefinition.</param>
|
||||
/// <param name="protoDefinitions">The ProtoDefinition as text.</param>
|
||||
/// <returns>A reference to the <see cref="IResourceBuilder{WireMockServerResource}"/>.</returns>
|
||||
public static IResourceBuilder<WireMockServerResource> WithProtoDefinition(this IResourceBuilder<WireMockServerResource> wiremock, string id, params string[] protoDefinitions)
|
||||
{
|
||||
Guard.NotNull(wiremock).Resource.Arguments.WithProtoDefinition(id, protoDefinitions);
|
||||
|
||||
return wiremock;
|
||||
}
|
||||
@@ -183,11 +264,11 @@ public static class WireMockServerBuilderExtensions
|
||||
/// dotnet tool install WireMockInspector --global --no-cache --ignore-failed-sources
|
||||
/// </code>
|
||||
/// </summary>
|
||||
/// <param name="builder">The <see cref="IResourceBuilder{WireMockNetResource}"/>.</param>
|
||||
/// <returns></returns>
|
||||
public static IResourceBuilder<WireMockServerResource> WithWireMockInspectorCommand(this IResourceBuilder<WireMockServerResource> builder)
|
||||
/// <param name="wiremock">The <see cref="IResourceBuilder{WireMockNetResource}"/>.</param>
|
||||
/// <returns>A reference to the <see cref="IResourceBuilder{WireMockServerResource}"/>.</returns>
|
||||
public static IResourceBuilder<WireMockServerResource> WithWireMockInspectorCommand(this IResourceBuilder<WireMockServerResource> wiremock)
|
||||
{
|
||||
Guard.NotNull(builder);
|
||||
Guard.NotNull(wiremock);
|
||||
|
||||
CommandOptions commandOptions = new()
|
||||
{
|
||||
@@ -197,13 +278,13 @@ public static class WireMockServerBuilderExtensions
|
||||
IconVariant = IconVariant.Filled
|
||||
};
|
||||
|
||||
builder.WithCommand(
|
||||
wiremock.WithCommand(
|
||||
name: "wiremock-inspector",
|
||||
displayName: "WireMock Inspector",
|
||||
executeCommand: _ => OnRunOpenInspectorCommandAsync(builder),
|
||||
executeCommand: _ => OnRunOpenInspectorCommandAsync(wiremock),
|
||||
commandOptions: commandOptions);
|
||||
|
||||
return builder;
|
||||
return wiremock;
|
||||
}
|
||||
|
||||
private static Task<ExecuteCommandResult> OnRunOpenInspectorCommandAsync(IResourceBuilder<WireMockServerResource> builder)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System.Diagnostics;
|
||||
using Aspire.Hosting.ApplicationModel;
|
||||
using Aspire.Hosting.Lifecycle;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -10,32 +11,49 @@ internal class WireMockServerLifecycleHook(ILoggerFactory loggerFactory) : IDist
|
||||
{
|
||||
private readonly CancellationTokenSource _shutdownCts = new();
|
||||
|
||||
public async Task AfterResourcesCreatedAsync(DistributedApplicationModel appModel, CancellationToken cancellationToken = default)
|
||||
private CancellationTokenSource? _linkedCts;
|
||||
private Task? _mappingTask;
|
||||
|
||||
public Task AfterEndpointsAllocatedAsync(DistributedApplicationModel appModel, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var cts = CancellationTokenSource.CreateLinkedTokenSource(_shutdownCts.Token, cancellationToken);
|
||||
_linkedCts = CancellationTokenSource.CreateLinkedTokenSource(_shutdownCts.Token, cancellationToken);
|
||||
|
||||
var wireMockServerResources = appModel.Resources
|
||||
.OfType<WireMockServerResource>()
|
||||
.ToArray();
|
||||
|
||||
foreach (var wireMockServerResource in wireMockServerResources)
|
||||
_mappingTask = Task.Run(async () =>
|
||||
{
|
||||
wireMockServerResource.SetLogger(loggerFactory.CreateLogger<WireMockServerResource>());
|
||||
var wireMockServerResources = appModel.Resources
|
||||
.OfType<WireMockServerResource>()
|
||||
.ToArray();
|
||||
|
||||
var endpoint = wireMockServerResource.GetEndpoint();
|
||||
if (endpoint.IsAllocated)
|
||||
foreach (var wireMockServerResource in wireMockServerResources)
|
||||
{
|
||||
await wireMockServerResource.WaitForHealthAsync(cts.Token);
|
||||
wireMockServerResource.SetLogger(loggerFactory.CreateLogger<WireMockServerResource>());
|
||||
|
||||
await wireMockServerResource.CallApiMappingBuilderActionAsync(cts.Token);
|
||||
var endpoint = wireMockServerResource.GetEndpoint();
|
||||
Debug.Assert(endpoint.IsAllocated);
|
||||
|
||||
wireMockServerResource.StartWatchingStaticMappings(cts.Token);
|
||||
await wireMockServerResource.WaitForHealthAsync(_linkedCts.Token);
|
||||
|
||||
await wireMockServerResource.CallAddProtoDefinitionsAsync(_linkedCts.Token);
|
||||
|
||||
await wireMockServerResource.CallApiMappingBuilderActionAsync(_linkedCts.Token);
|
||||
|
||||
wireMockServerResource.StartWatchingStaticMappings(_linkedCts.Token);
|
||||
}
|
||||
}
|
||||
}, _linkedCts.Token);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
await _shutdownCts.CancelAsync();
|
||||
|
||||
_linkedCts?.Dispose();
|
||||
_shutdownCts.Dispose();
|
||||
|
||||
if (_mappingTask is not null)
|
||||
{
|
||||
await _mappingTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ using RestEase;
|
||||
using Stef.Validation;
|
||||
using WireMock.Client;
|
||||
using WireMock.Client.Extensions;
|
||||
using WireMock.Net.Aspire;
|
||||
using WireMock.Util;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
@@ -19,6 +20,7 @@ public class WireMockServerResource : ContainerResource, IResourceWithServiceDis
|
||||
|
||||
internal WireMockServerArguments Arguments { get; }
|
||||
internal Lazy<IWireMockAdminApi> AdminApi => new(CreateWireMockAdminApi);
|
||||
internal WireMockMappingState ApiMappingState { get; set; } = WireMockMappingState.NoMappings;
|
||||
|
||||
private ILogger? _logger;
|
||||
private EnhancedFileSystemWatcher? _enhancedFileSystemWatcher;
|
||||
@@ -64,6 +66,36 @@ public class WireMockServerResource : ContainerResource, IResourceWithServiceDis
|
||||
|
||||
var mappingBuilder = AdminApi.Value.GetMappingBuilder();
|
||||
await Arguments.ApiMappingBuilder.Invoke(mappingBuilder, cancellationToken);
|
||||
|
||||
ApiMappingState = WireMockMappingState.Submitted;
|
||||
}
|
||||
|
||||
internal async Task CallAddProtoDefinitionsAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_logger?.LogInformation("Calling AdminApi to add GRPC ProtoDefinition at server level to WireMock.Net");
|
||||
|
||||
foreach (var (id, protoDefinitions) in Arguments.ProtoDefinitions)
|
||||
{
|
||||
_logger?.LogInformation("Adding ProtoDefinition {Id}", id);
|
||||
foreach (var protoDefinition in protoDefinitions)
|
||||
{
|
||||
try
|
||||
{
|
||||
var status = await AdminApi.Value.AddProtoDefinitionAsync(id, protoDefinition, cancellationToken);
|
||||
_logger?.LogInformation("ProtoDefinition '{Id}' added with status: {Status}.", id, status.Status);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.LogWarning(ex, "Error adding ProtoDefinition '{Id}'.", id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Force a reload of static mappings when ProtoDefinitions are added at server-level to fix #1382
|
||||
if (Arguments.ProtoDefinitions.Count > 0)
|
||||
{
|
||||
await ReloadStaticMappingsAsync(default);
|
||||
}
|
||||
}
|
||||
|
||||
internal void StartWatchingStaticMappings(CancellationToken cancellationToken)
|
||||
@@ -109,10 +141,17 @@ public class WireMockServerResource : ContainerResource, IResourceWithServiceDis
|
||||
|
||||
private async void FileCreatedChangedOrDeleted(object sender, FileSystemEventArgs args)
|
||||
{
|
||||
_logger?.LogInformation("MappingFile created, changed or deleted: '{0}'. Triggering ReloadStaticMappings.", args.FullPath);
|
||||
_logger?.LogInformation("MappingFile created, changed or deleted: '{FullPath}'. Triggering ReloadStaticMappings.", args.FullPath);
|
||||
|
||||
await ReloadStaticMappingsAsync(default);
|
||||
}
|
||||
|
||||
private async Task ReloadStaticMappingsAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
await AdminApi.Value.ReloadStaticMappingsAsync();
|
||||
var status = await AdminApi.Value.ReloadStaticMappingsAsync(cancellationToken);
|
||||
_logger?.LogInformation("ReloadStaticMappings called with status: {Status}.", status);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -140,7 +140,7 @@ public class GraphQLMatcher : IGraphQLMatcher
|
||||
}
|
||||
}
|
||||
|
||||
return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score), exception);
|
||||
return MatchResult.From(Name, MatchBehaviourHelper.Convert(MatchBehaviour, score), exception);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -97,7 +97,7 @@ public class CSharpCodeMatcher : ICSharpCodeMatcher
|
||||
}
|
||||
}
|
||||
|
||||
return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score), exception);
|
||||
return MatchResult.From(Name, MatchBehaviourHelper.Convert(MatchBehaviour, score), exception);
|
||||
}
|
||||
|
||||
private bool IsMatch(dynamic input, string pattern)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using WireMock.Matchers.Helpers;
|
||||
using WireMock.Models.Mime;
|
||||
using WireMock.Util;
|
||||
@@ -12,7 +13,7 @@ namespace WireMock.Matchers;
|
||||
/// </summary>
|
||||
public class MimePartMatcher : IMimePartMatcher
|
||||
{
|
||||
private readonly Func<IMimePartData, MatchResult>[] _funcs;
|
||||
private readonly IList<(string Name, Func<IMimePartData, MatchResult> func)> _matcherFunctions;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Name => nameof(MimePartMatcher);
|
||||
@@ -49,34 +50,47 @@ public class MimePartMatcher : IMimePartMatcher
|
||||
ContentTransferEncodingMatcher = contentTransferEncodingMatcher;
|
||||
ContentMatcher = contentMatcher;
|
||||
|
||||
_funcs =
|
||||
[
|
||||
mp => ContentTypeMatcher?.IsMatch(GetContentTypeAsString(mp.ContentType)) ?? MatchScores.Perfect,
|
||||
mp => ContentDispositionMatcher?.IsMatch(mp.ContentDisposition?.ToString()?.Replace("Content-Disposition: ", string.Empty)) ?? MatchScores.Perfect,
|
||||
mp => ContentTransferEncodingMatcher?.IsMatch(mp.ContentTransferEncoding.ToLowerInvariant()) ?? MatchScores.Perfect,
|
||||
MatchOnContent
|
||||
];
|
||||
_matcherFunctions = [];
|
||||
if (ContentTypeMatcher != null)
|
||||
{
|
||||
_matcherFunctions.Add((nameof(ContentTypeMatcher), mp => ContentTypeMatcher.IsMatch(GetContentTypeAsString(mp.ContentType))));
|
||||
}
|
||||
|
||||
if (ContentDispositionMatcher != null)
|
||||
{
|
||||
_matcherFunctions.Add((nameof(ContentDispositionMatcher), mp => ContentDispositionMatcher.IsMatch(mp.ContentDisposition?.ToString()?.Replace("Content-Disposition: ", string.Empty))));
|
||||
}
|
||||
|
||||
if (ContentTransferEncodingMatcher != null)
|
||||
{
|
||||
_matcherFunctions.Add((nameof(ContentTransferEncodingMatcher), mp => ContentTransferEncodingMatcher.IsMatch(mp.ContentTransferEncoding.ToLowerInvariant())));
|
||||
}
|
||||
|
||||
if (ContentMatcher != null)
|
||||
{
|
||||
_matcherFunctions.Add((ContentMatcher.Name, MatchOnContent));
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public MatchResult IsMatch(IMimePartData value)
|
||||
{
|
||||
var score = MatchScores.Mismatch;
|
||||
Exception? exception = null;
|
||||
var results = new List<MatchResult>();
|
||||
|
||||
try
|
||||
foreach (var matcherFunction in _matcherFunctions)
|
||||
{
|
||||
if (Array.TrueForAll(_funcs, func => func(value).IsPerfect()))
|
||||
try
|
||||
{
|
||||
score = MatchScores.Perfect;
|
||||
var matchResult = matcherFunction.func(value);
|
||||
results.Add(MatchResult.From(matcherFunction.Name, matchResult.Score));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
results.Add(MatchResult.From(matcherFunction.Name, MatchScores.Mismatch, ex));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
exception = ex;
|
||||
}
|
||||
|
||||
return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score), exception);
|
||||
return MatchResult.From(nameof(MimePartMatcher), results, MatchOperator.And);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -87,11 +101,6 @@ public class MimePartMatcher : IMimePartMatcher
|
||||
|
||||
private MatchResult MatchOnContent(IMimePartData mimePart)
|
||||
{
|
||||
if (ContentMatcher == null)
|
||||
{
|
||||
return MatchScores.Perfect;
|
||||
}
|
||||
|
||||
var bodyParserSettings = new BodyParserSettings
|
||||
{
|
||||
Stream = mimePart.Open(),
|
||||
@@ -102,7 +111,7 @@ public class MimePartMatcher : IMimePartMatcher
|
||||
};
|
||||
|
||||
var bodyData = BodyParser.ParseAsync(bodyParserSettings).ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
return BodyDataMatchScoreCalculator.CalculateMatchScore(bodyData, ContentMatcher);
|
||||
return BodyDataMatchScoreCalculator.CalculateMatchScore(bodyData, ContentMatcher!);
|
||||
}
|
||||
|
||||
private static string? GetContentTypeAsString(IContentTypeData? contentType)
|
||||
|
||||
@@ -52,7 +52,7 @@ internal class MimeKitUtils : IMimeKitUtils
|
||||
return false;
|
||||
}
|
||||
|
||||
var fixedBytes = FixBytes(bytes, contentTypeHeader[0]);
|
||||
var fixedBytes = PrependContentTypeHeader(bytes, contentTypeHeader[0]);
|
||||
|
||||
mimeMessageData = LoadFromStream(new MemoryStream(fixedBytes));
|
||||
return true;
|
||||
@@ -68,7 +68,10 @@ internal class MimeKitUtils : IMimeKitUtils
|
||||
return contentTypeHeader.Any(ct => ct.TrimStart().StartsWith("multipart/", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
private static byte[] FixBytes(byte[] bytes, WireMockList<string> contentType)
|
||||
/// <summary>
|
||||
/// Prepends the Content-Type header to the byte array to make it a valid MIME message for MimeKit.
|
||||
/// </summary>
|
||||
private static byte[] PrependContentTypeHeader(byte[] bytes, WireMockList<string> contentType)
|
||||
{
|
||||
var contentTypeBytes = Encoding.UTF8.GetBytes($"{HttpKnownHeaderNames.ContentType}: {contentType}\r\n\r\n");
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ internal class AzureADAuthenticationMatcher : IStringMatcher
|
||||
{
|
||||
if (string.IsNullOrEmpty(input))
|
||||
{
|
||||
return MatchScores.Mismatch;
|
||||
return MatchResult.From(Name);
|
||||
}
|
||||
|
||||
var token = Regex.Replace(input, BearerPrefix, string.Empty, RegexOptions.IgnoreCase, RegexConstants.DefaultTimeout);
|
||||
@@ -83,11 +83,11 @@ internal class AzureADAuthenticationMatcher : IStringMatcher
|
||||
// Throws an Exception as the token is invalid (expired, invalid-formatted, tenant mismatch, etc.)
|
||||
_jwtSecurityTokenHandler.ValidateToken(token, validationParameters, out _);
|
||||
|
||||
return MatchScores.Perfect;
|
||||
return MatchResult.From(Name, MatchScores.Perfect);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new MatchResult(MatchScores.Mismatch, ex);
|
||||
return MatchResult.From(Name, ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ public class WireMockConsoleLogger : IWireMockLogger
|
||||
/// <see cref="IWireMockLogger.DebugRequestResponse"/>
|
||||
public void DebugRequestResponse(LogEntryModel logEntryModel, bool isAdminRequest)
|
||||
{
|
||||
string message = JsonConvert.SerializeObject(logEntryModel, Formatting.Indented);
|
||||
string message = JsonConvert.SerializeObject(logEntryModel, Formatting.Indented, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
|
||||
Console.WriteLine(Format("DebugRequestResponse", "Admin[{0}] {1}", isAdminRequest, message));
|
||||
}
|
||||
|
||||
|
||||
46
src/WireMock.Net.Minimal/Matchers/CompositeMatcher.cs
Normal file
46
src/WireMock.Net.Minimal/Matchers/CompositeMatcher.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System;
|
||||
|
||||
namespace WireMock.Matchers;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a matcher that combines multiple matching strategies into a single composite operation.
|
||||
/// </summary>
|
||||
public class CompositeMatcher : IMatcher
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string Name => nameof(CompositeMatcher);
|
||||
|
||||
/// <summary>
|
||||
/// The logical operator used to combine the results of the matchers.
|
||||
/// </summary>
|
||||
public MatchOperator MatchOperator { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public MatchBehaviour MatchBehaviour { get; }
|
||||
|
||||
/// <summary>
|
||||
/// All matchers.
|
||||
/// </summary>
|
||||
public IMatcher[] Matchers { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the CompositeMatcher class with the specified matchers, operator, and match behaviour.
|
||||
/// </summary>
|
||||
/// <param name="matchers">An array of matchers to be combined. Cannot be null or contain null elements.</param>
|
||||
/// <param name="matchOperator">The logical operator used to combine the results of the matchers.</param>
|
||||
/// <param name="matchBehaviour">The behaviour that determines how the composite matcher interprets the combined results.</param>
|
||||
public CompositeMatcher(IMatcher[] matchers, MatchOperator matchOperator, MatchBehaviour matchBehaviour)
|
||||
{
|
||||
Matchers = matchers;
|
||||
MatchOperator = matchOperator;
|
||||
MatchBehaviour = matchBehaviour;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetCSharpCodeArguments()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
@@ -62,7 +62,7 @@ public class ContentTypeMatcher : WildcardMatcher
|
||||
{
|
||||
if (string.IsNullOrEmpty(input) || !MediaTypeHeaderValue.TryParse(input, out var contentType))
|
||||
{
|
||||
return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.Mismatch);
|
||||
return MatchResult.From(Name, MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.Mismatch));
|
||||
}
|
||||
|
||||
return base.IsMatch(contentType.MediaType);
|
||||
|
||||
@@ -75,7 +75,7 @@ public class ExactMatcher : IStringMatcher, IIgnoreCaseMatcher
|
||||
: pattern => pattern == input;
|
||||
|
||||
var score = MatchScores.ToScore(_values.Select(v => equals(v)).ToArray(), MatchOperator);
|
||||
return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score));
|
||||
return MatchResult.From(Name, MatchBehaviourHelper.Convert(MatchBehaviour, score));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -106,18 +106,18 @@ public class FormUrlEncodedMatcher : IStringMatcher, IIgnoreCaseMatcher
|
||||
// Input is null or empty and if no patterns defined, return Perfect match.
|
||||
if (string.IsNullOrEmpty(input) && _patterns.Length == 0)
|
||||
{
|
||||
return new MatchResult(MatchScores.Perfect);
|
||||
return MatchResult.From(Name, MatchScores.Perfect);
|
||||
}
|
||||
|
||||
if (!QueryStringParser.TryParse(input, IgnoreCase, out var inputNameValueCollection))
|
||||
{
|
||||
return new MatchResult(MatchScores.Mismatch);
|
||||
return MatchResult.From(Name, MatchScores.Mismatch);
|
||||
}
|
||||
|
||||
var matches = GetMatches(inputNameValueCollection);
|
||||
|
||||
var score = MatchScores.ToScore(matches, MatchOperator);
|
||||
return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score));
|
||||
return MatchResult.From(Name, MatchBehaviourHelper.Convert(MatchBehaviour, score));
|
||||
}
|
||||
|
||||
private bool[] GetMatches(IDictionary<string, string> inputNameValueCollection)
|
||||
|
||||
@@ -80,7 +80,7 @@ public class JsonPathMatcher : IStringMatcher, IObjectMatcher
|
||||
}
|
||||
}
|
||||
|
||||
return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score), exception);
|
||||
return MatchResult.From(Name, MatchBehaviourHelper.Convert(MatchBehaviour, score), exception);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -104,7 +104,7 @@ public class JsonPathMatcher : IStringMatcher, IObjectMatcher
|
||||
}
|
||||
}
|
||||
|
||||
return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score), exception);
|
||||
return MatchResult.From(Name, MatchBehaviourHelper.Convert(MatchBehaviour, score), exception);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -87,7 +87,7 @@ public class JmesPathMatcher : IStringMatcher, IObjectMatcher
|
||||
}
|
||||
}
|
||||
|
||||
return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score), exception);
|
||||
return MatchResult.From(Name, MatchBehaviourHelper.Convert(MatchBehaviour, score), exception);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -102,7 +102,7 @@ public class JmesPathMatcher : IStringMatcher, IObjectMatcher
|
||||
return IsMatch(inputAsString);
|
||||
}
|
||||
|
||||
return MatchBehaviourHelper.Convert(MatchBehaviour, score);
|
||||
return MatchResult.From(Name, MatchBehaviourHelper.Convert(MatchBehaviour, score));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -96,7 +96,7 @@ public class JsonMatcher : IJsonMatcher
|
||||
}
|
||||
}
|
||||
|
||||
return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score), error);
|
||||
return MatchResult.From(Name, MatchBehaviourHelper.Convert(MatchBehaviour, score), error);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -24,14 +24,26 @@ public class RequestMatchResult : IRequestMatchResult
|
||||
public double AverageTotalScore => TotalNumber == 0 ? MatchScores.Mismatch : TotalScore / TotalNumber;
|
||||
|
||||
/// <inheritdoc />
|
||||
public IList<MatchDetail> MatchDetails { get; } = new List<MatchDetail>();
|
||||
public IList<MatchDetail> MatchDetails { get; } = [];
|
||||
|
||||
/// <inheritdoc />
|
||||
public double AddScore(Type matcherType, double score, Exception? exception)
|
||||
{
|
||||
MatchDetails.Add(new MatchDetail { MatcherType = matcherType, Score = score, Exception = exception });
|
||||
return AddMatchDetail(new MatchDetail
|
||||
{
|
||||
Name = matcherType.Name.Replace("RequestMessage", string.Empty),
|
||||
MatcherType = matcherType,
|
||||
Score = score,
|
||||
Exception = exception
|
||||
});
|
||||
}
|
||||
|
||||
return score;
|
||||
/// <inheritdoc />
|
||||
public double AddMatchDetail(MatchDetail matchDetail)
|
||||
{
|
||||
MatchDetails.Add(matchDetail);
|
||||
|
||||
return matchDetail.Score;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -17,27 +17,27 @@ public class RequestMessageBodyMatcher : IRequestMatcher
|
||||
/// <summary>
|
||||
/// The body function
|
||||
/// </summary>
|
||||
public Func<string?, bool>? Func { get; }
|
||||
public Func<string?, bool>? MatchOnBodyAsStringFunc { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The body data function for byte[]
|
||||
/// </summary>
|
||||
public Func<byte[]?, bool>? DataFunc { get; }
|
||||
public Func<byte[]?, bool>? MatchOnBodyAsBytesFunc { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The body data function for json
|
||||
/// </summary>
|
||||
public Func<object?, bool>? JsonFunc { get; }
|
||||
public Func<object?, bool>? MatchOnBodyAsJsonFunc { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The body data function for BodyData
|
||||
/// </summary>
|
||||
public Func<IBodyData?, bool>? BodyDataFunc { get; }
|
||||
public Func<IBodyData?, bool>? MatchOnBodyAsBodyDataFunc { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The body data function for FormUrlEncoded
|
||||
/// </summary>
|
||||
public Func<IDictionary<string, string>?, bool>? FormUrlEncodedFunc { get; }
|
||||
public Func<IDictionary<string, string>?, bool>? MatchOnBodyAsFormUrlEncodedFunc { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The matchers.
|
||||
@@ -85,7 +85,7 @@ public class RequestMessageBodyMatcher : IRequestMatcher
|
||||
/// <param name="func">The function.</param>
|
||||
public RequestMessageBodyMatcher(Func<string?, bool> func)
|
||||
{
|
||||
Func = Guard.NotNull(func);
|
||||
MatchOnBodyAsStringFunc = Guard.NotNull(func);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -94,7 +94,7 @@ public class RequestMessageBodyMatcher : IRequestMatcher
|
||||
/// <param name="func">The function.</param>
|
||||
public RequestMessageBodyMatcher(Func<byte[]?, bool> func)
|
||||
{
|
||||
DataFunc = Guard.NotNull(func);
|
||||
MatchOnBodyAsBytesFunc = Guard.NotNull(func);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -103,7 +103,7 @@ public class RequestMessageBodyMatcher : IRequestMatcher
|
||||
/// <param name="func">The function.</param>
|
||||
public RequestMessageBodyMatcher(Func<object?, bool> func)
|
||||
{
|
||||
JsonFunc = Guard.NotNull(func);
|
||||
MatchOnBodyAsJsonFunc = Guard.NotNull(func);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -112,7 +112,7 @@ public class RequestMessageBodyMatcher : IRequestMatcher
|
||||
/// <param name="func">The function.</param>
|
||||
public RequestMessageBodyMatcher(Func<IBodyData?, bool> func)
|
||||
{
|
||||
BodyDataFunc = Guard.NotNull(func);
|
||||
MatchOnBodyAsBodyDataFunc = Guard.NotNull(func);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -121,7 +121,7 @@ public class RequestMessageBodyMatcher : IRequestMatcher
|
||||
/// <param name="func">The function.</param>
|
||||
public RequestMessageBodyMatcher(Func<IDictionary<string, string>?, bool> func)
|
||||
{
|
||||
FormUrlEncodedFunc = Guard.NotNull(func);
|
||||
MatchOnBodyAsFormUrlEncodedFunc = Guard.NotNull(func);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -147,43 +147,43 @@ public class RequestMessageBodyMatcher : IRequestMatcher
|
||||
/// <inheritdoc />
|
||||
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
|
||||
{
|
||||
var (score, exception) = CalculateMatchScore(requestMessage).Expand();
|
||||
var (score, exception) = CalculateMatchResult(requestMessage).Expand();
|
||||
return requestMatchResult.AddScore(GetType(), score, exception);
|
||||
}
|
||||
|
||||
private MatchResult CalculateMatchScore(IRequestMessage requestMessage)
|
||||
private MatchResult CalculateMatchResult(IRequestMessage requestMessage)
|
||||
{
|
||||
if (Matchers != null && Matchers.Any())
|
||||
{
|
||||
var results = Matchers.Select(matcher => BodyDataMatchScoreCalculator.CalculateMatchScore(requestMessage.BodyData, matcher)).ToArray();
|
||||
return MatchResult.From(results, MatchOperator);
|
||||
return MatchResult.From(nameof(RequestMessageBodyMatcher), results, MatchOperator);
|
||||
}
|
||||
|
||||
if (Func != null)
|
||||
if (MatchOnBodyAsStringFunc != null)
|
||||
{
|
||||
return MatchScores.ToScore(Func(requestMessage.BodyData?.BodyAsString));
|
||||
return MatchResult.From($"{nameof(RequestMessageBodyMatcher)}:{nameof(MatchOnBodyAsStringFunc)}", MatchScores.ToScore(MatchOnBodyAsStringFunc(requestMessage.BodyData?.BodyAsString)));
|
||||
}
|
||||
|
||||
if (FormUrlEncodedFunc != null)
|
||||
if (MatchOnBodyAsFormUrlEncodedFunc != null)
|
||||
{
|
||||
return MatchScores.ToScore(FormUrlEncodedFunc(requestMessage.BodyData?.BodyAsFormUrlEncoded));
|
||||
return MatchResult.From($"{nameof(RequestMessageBodyMatcher)}:{nameof(MatchOnBodyAsFormUrlEncodedFunc)}", MatchScores.ToScore(MatchOnBodyAsFormUrlEncodedFunc(requestMessage.BodyData?.BodyAsFormUrlEncoded)));
|
||||
}
|
||||
|
||||
if (JsonFunc != null)
|
||||
if (MatchOnBodyAsJsonFunc != null)
|
||||
{
|
||||
return MatchScores.ToScore(JsonFunc(requestMessage.BodyData?.BodyAsJson));
|
||||
return MatchResult.From($"{nameof(RequestMessageBodyMatcher)}:{nameof(MatchOnBodyAsJsonFunc)}", MatchScores.ToScore(MatchOnBodyAsJsonFunc(requestMessage.BodyData?.BodyAsJson)));
|
||||
}
|
||||
|
||||
if (DataFunc != null)
|
||||
if (MatchOnBodyAsBytesFunc != null)
|
||||
{
|
||||
return MatchScores.ToScore(DataFunc(requestMessage.BodyData?.BodyAsBytes));
|
||||
return MatchResult.From($"{nameof(RequestMessageBodyMatcher)}:{nameof(MatchOnBodyAsBytesFunc)}", MatchScores.ToScore(MatchOnBodyAsBytesFunc(requestMessage.BodyData?.BodyAsBytes)));
|
||||
}
|
||||
|
||||
if (BodyDataFunc != null)
|
||||
if (MatchOnBodyAsBodyDataFunc != null)
|
||||
{
|
||||
return MatchScores.ToScore(BodyDataFunc(requestMessage.BodyData));
|
||||
return MatchResult.From($"{nameof(RequestMessageBodyMatcher)}:{nameof(MatchOnBodyAsBodyDataFunc)}", MatchScores.ToScore(MatchOnBodyAsBodyDataFunc(requestMessage.BodyData)));
|
||||
}
|
||||
|
||||
return default;
|
||||
return MatchResult.From(nameof(RequestMessageBodyMatcher));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Stef.Validation;
|
||||
|
||||
namespace WireMock.Matchers.Request;
|
||||
|
||||
/// <summary>
|
||||
/// The request body matcher.
|
||||
/// </summary>
|
||||
public class RequestMessageBodyMatcher<T> : IRequestMatcher
|
||||
{
|
||||
private const string _name = nameof(RequestMessageBodyMatcher<T>);
|
||||
|
||||
/// <summary>
|
||||
/// The body data function for type T
|
||||
/// </summary>
|
||||
public Func<T?, bool>? Func { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="MatchOperator"/>
|
||||
/// </summary>
|
||||
public MatchOperator MatchOperator { get; } = MatchOperator.Or;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RequestMessageBodyMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="func">The function.</param>
|
||||
public RequestMessageBodyMatcher(Func<T?, bool> func)
|
||||
{
|
||||
Func = Guard.NotNull(func);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
|
||||
{
|
||||
var (score, exception) = CalculateMatchScore(requestMessage).Expand();
|
||||
return requestMatchResult.AddScore(GetType(), score, exception);
|
||||
}
|
||||
|
||||
private MatchResult CalculateMatchScore(IRequestMessage requestMessage)
|
||||
{
|
||||
if (Func != null)
|
||||
{
|
||||
if (requestMessage.BodyData?.BodyAsJson is JObject jsonObject)
|
||||
{
|
||||
try
|
||||
{
|
||||
var bodyAsT = jsonObject.ToObject<T>();
|
||||
return MatchResult.From(_name, MatchScores.ToScore(Func(bodyAsT)));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return MatchResult.From(_name, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return MatchResult.From(_name);
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,8 @@ namespace WireMock.Matchers.Request;
|
||||
/// </summary>
|
||||
public class RequestMessageClientIPMatcher : IRequestMatcher
|
||||
{
|
||||
private const string _name = nameof(RequestMessageClientIPMatcher);
|
||||
|
||||
/// <summary>
|
||||
/// The matchers
|
||||
/// </summary>
|
||||
@@ -77,8 +79,8 @@ public class RequestMessageClientIPMatcher : IRequestMatcher
|
||||
/// <inheritdoc />
|
||||
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
|
||||
{
|
||||
var (score, exception) = GetMatchResult(requestMessage).Expand();
|
||||
return requestMatchResult.AddScore(GetType(), score, exception);
|
||||
var matchDetail = GetMatchResult(requestMessage).ToMatchDetail();
|
||||
return requestMatchResult.AddMatchDetail(matchDetail);
|
||||
}
|
||||
|
||||
private MatchResult GetMatchResult(IRequestMessage requestMessage)
|
||||
@@ -86,15 +88,15 @@ public class RequestMessageClientIPMatcher : IRequestMatcher
|
||||
if (Matchers != null)
|
||||
{
|
||||
var results = Matchers.Select(m => m.IsMatch(requestMessage.ClientIP)).ToArray();
|
||||
return MatchResult.From(results, MatchOperator);
|
||||
return MatchResult.From(_name, results, MatchOperator);
|
||||
}
|
||||
|
||||
if (Funcs != null)
|
||||
{
|
||||
var results = Funcs.Select(func => func(requestMessage.ClientIP)).ToArray();
|
||||
return MatchScores.ToScore(results, MatchOperator);
|
||||
return MatchResult.From(_name, MatchScores.ToScore(results, MatchOperator));
|
||||
}
|
||||
|
||||
return default;
|
||||
return MatchResult.From(_name);
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,8 @@ namespace WireMock.Matchers.Request;
|
||||
/// <inheritdoc cref="IRequestMatcher"/>
|
||||
public class RequestMessageCookieMatcher : IRequestMatcher
|
||||
{
|
||||
private const string _name = nameof(RequestMessageCookieMatcher);
|
||||
|
||||
/// <summary>
|
||||
/// MatchBehaviour
|
||||
/// </summary>
|
||||
@@ -104,7 +106,7 @@ public class RequestMessageCookieMatcher : IRequestMatcher
|
||||
{
|
||||
if (requestMessage.Cookies == null)
|
||||
{
|
||||
return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.Mismatch);
|
||||
return MatchResult.From(_name, MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.Mismatch));
|
||||
}
|
||||
|
||||
// Check if we want to use IgnoreCase to compare the Cookie-Name and Cookie-Value
|
||||
@@ -112,19 +114,19 @@ public class RequestMessageCookieMatcher : IRequestMatcher
|
||||
|
||||
if (Funcs != null)
|
||||
{
|
||||
return MatchScores.ToScore(Funcs.Any(f => f(cookies)));
|
||||
return MatchResult.From(_name, MatchScores.ToScore(Funcs.Any(f => f(cookies))));
|
||||
}
|
||||
|
||||
if (Matchers == null)
|
||||
{
|
||||
return default;
|
||||
return MatchResult.From(_name);
|
||||
}
|
||||
|
||||
if (!cookies.ContainsKey(Name))
|
||||
{
|
||||
return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.Mismatch);
|
||||
return MatchResult.From(_name, MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.Mismatch));
|
||||
}
|
||||
|
||||
return Matchers.Max(m => m.IsMatch(cookies[Name]));
|
||||
return MatchResult.From(_name, Matchers.Max(m => m.IsMatch(cookies[Name]))?.Score ?? MatchScores.Mismatch);
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,8 @@ namespace WireMock.Matchers.Request;
|
||||
/// <inheritdoc cref="IRequestMatcher"/>
|
||||
public class RequestMessageHeaderMatcher : IRequestMatcher
|
||||
{
|
||||
private const string _name = nameof(RequestMessageCookieMatcher);
|
||||
|
||||
/// <summary>
|
||||
/// MatchBehaviour
|
||||
/// </summary>
|
||||
@@ -117,7 +119,7 @@ public class RequestMessageHeaderMatcher : IRequestMatcher
|
||||
{
|
||||
if (requestMessage.Headers == null)
|
||||
{
|
||||
return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.Mismatch);
|
||||
return MatchResult.From(_name, MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.Mismatch));
|
||||
}
|
||||
|
||||
// Check if we want to use IgnoreCase to compare the Header-Name and Header-Value(s)
|
||||
@@ -126,14 +128,14 @@ public class RequestMessageHeaderMatcher : IRequestMatcher
|
||||
if (Funcs != null)
|
||||
{
|
||||
var funcResults = Funcs.Select(f => f(headers.ToDictionary(entry => entry.Key, entry => entry.Value.ToArray()))).ToArray();
|
||||
return MatchScores.ToScore(funcResults, MatchOperator);
|
||||
return MatchResult.From(_name, MatchScores.ToScore(funcResults, MatchOperator));
|
||||
}
|
||||
|
||||
if (Matchers != null)
|
||||
{
|
||||
if (!headers.ContainsKey(Name))
|
||||
{
|
||||
return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.Mismatch);
|
||||
return MatchResult.From(_name, MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.Mismatch));
|
||||
}
|
||||
|
||||
var results = new List<MatchResult>();
|
||||
@@ -141,12 +143,12 @@ public class RequestMessageHeaderMatcher : IRequestMatcher
|
||||
{
|
||||
var resultsPerMatcher = headers[Name].Select(matcher.IsMatch).ToArray();
|
||||
|
||||
results.Add(MatchResult.From(resultsPerMatcher, MatchOperator.And));
|
||||
results.Add(MatchResult.From(_name, resultsPerMatcher, MatchOperator.And));
|
||||
}
|
||||
|
||||
return MatchResult.From(results, MatchOperator);
|
||||
return MatchResult.From(_name, results, MatchOperator);
|
||||
}
|
||||
|
||||
return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.Mismatch);
|
||||
return MatchResult.From(_name, MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.Mismatch));
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,8 @@ namespace WireMock.Matchers.Request;
|
||||
/// </summary>
|
||||
public class RequestMessageHttpVersionMatcher : IRequestMatcher
|
||||
{
|
||||
private const string _name = nameof(RequestMessageHttpVersionMatcher);
|
||||
|
||||
/// <summary>
|
||||
/// The matcher.
|
||||
/// </summary>
|
||||
@@ -19,7 +21,7 @@ public class RequestMessageHttpVersionMatcher : IRequestMatcher
|
||||
/// <summary>
|
||||
/// The func.
|
||||
/// </summary>
|
||||
public Func<string, bool>? Func { get; }
|
||||
public Func<string, bool>? MatcherOnStringFunc { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="MatchBehaviour"/>
|
||||
@@ -61,7 +63,7 @@ public class RequestMessageHttpVersionMatcher : IRequestMatcher
|
||||
/// <param name="func">The function.</param>
|
||||
public RequestMessageHttpVersionMatcher(Func<string, bool> func)
|
||||
{
|
||||
Func = Guard.NotNull(func);
|
||||
MatcherOnStringFunc = Guard.NotNull(func);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -78,11 +80,11 @@ public class RequestMessageHttpVersionMatcher : IRequestMatcher
|
||||
return Matcher.IsMatch(requestMessage.HttpVersion);
|
||||
}
|
||||
|
||||
if (Func != null)
|
||||
if (MatcherOnStringFunc != null)
|
||||
{
|
||||
return MatchScores.ToScore(Func(requestMessage.HttpVersion));
|
||||
return MatchResult.From($"{_name}:{nameof(MatcherOnStringFunc)}", MatchScores.ToScore(MatcherOnStringFunc(requestMessage.HttpVersion)));
|
||||
}
|
||||
|
||||
return default;
|
||||
return MatchResult.From(_name);
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,11 @@ namespace WireMock.Matchers.Request;
|
||||
/// </summary>
|
||||
public class RequestMessageMultiPartMatcher : IRequestMatcher
|
||||
{
|
||||
/// <summary>
|
||||
/// The name of this matcher.
|
||||
/// </summary>
|
||||
public const string Name = "MultiPartMatcher";
|
||||
|
||||
private readonly IMimeKitUtils _mimeKitUtils = LoadMimeKitUtils();
|
||||
|
||||
/// <summary>
|
||||
@@ -22,7 +27,7 @@ public class RequestMessageMultiPartMatcher : IRequestMatcher
|
||||
/// <summary>
|
||||
/// The <see cref="MatchOperator"/>
|
||||
/// </summary>
|
||||
public MatchOperator MatchOperator { get; } = MatchOperator.Or;
|
||||
public MatchOperator MatchOperator { get; } = MatchOperator.And;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="MatchBehaviour"/>
|
||||
@@ -54,19 +59,20 @@ public class RequestMessageMultiPartMatcher : IRequestMatcher
|
||||
/// <inheritdoc />
|
||||
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
|
||||
{
|
||||
var score = MatchScores.Mismatch;
|
||||
var matchDetail = MatchResult.From(Name).ToMatchDetail();
|
||||
Exception? exception = null;
|
||||
|
||||
if (Matchers?.Any() != true)
|
||||
if (Matchers == null)
|
||||
{
|
||||
return requestMatchResult.AddScore(GetType(), score, null);
|
||||
return requestMatchResult.AddMatchDetail(matchDetail);
|
||||
}
|
||||
|
||||
if (!_mimeKitUtils.TryGetMimeMessage(requestMessage, out var message))
|
||||
{
|
||||
return requestMatchResult.AddScore(GetType(), score, null);
|
||||
return requestMatchResult.AddMatchDetail(matchDetail);
|
||||
}
|
||||
|
||||
double score = MatchScores.Mismatch;
|
||||
try
|
||||
{
|
||||
foreach (var mimePartMatcher in Matchers.OfType<IMimePartMatcher>().ToArray())
|
||||
@@ -94,7 +100,7 @@ public class RequestMessageMultiPartMatcher : IRequestMatcher
|
||||
exception = ex;
|
||||
}
|
||||
|
||||
return requestMatchResult.AddScore(GetType(), score, exception);
|
||||
return requestMatchResult.AddMatchDetail(MatchResult.From(Name, score, exception).ToMatchDetail());
|
||||
}
|
||||
|
||||
private static IMimeKitUtils LoadMimeKitUtils()
|
||||
|
||||
@@ -77,8 +77,8 @@ public class RequestMessagePathMatcher : IRequestMatcher
|
||||
/// <inheritdoc />
|
||||
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
|
||||
{
|
||||
var (score, exception) = GetMatchResult(requestMessage).Expand();
|
||||
return requestMatchResult.AddScore(GetType(), score, exception);
|
||||
var matchDetail = GetMatchResult(requestMessage).ToMatchDetail();
|
||||
return requestMatchResult.AddMatchDetail(matchDetail);
|
||||
}
|
||||
|
||||
private MatchResult GetMatchResult(IRequestMessage requestMessage)
|
||||
@@ -86,15 +86,16 @@ public class RequestMessagePathMatcher : IRequestMatcher
|
||||
if (Matchers != null)
|
||||
{
|
||||
var results = Matchers.Select(m => m.IsMatch(requestMessage.Path)).ToArray();
|
||||
return MatchResult.From(results, MatchOperator);
|
||||
return MatchResult.From(nameof(RequestMessagePathMatcher), results, MatchOperator);
|
||||
}
|
||||
|
||||
if (Funcs != null)
|
||||
{
|
||||
var results = Funcs.Select(func => func(requestMessage.Path)).ToArray();
|
||||
return MatchScores.ToScore(results, MatchOperator);
|
||||
var score = MatchScores.ToScore(results, MatchOperator);
|
||||
return MatchResult.From(nameof(RequestMessagePathMatcher), score);
|
||||
}
|
||||
|
||||
return default;
|
||||
return MatchResult.From(nameof(RequestMessagePathMatcher));
|
||||
}
|
||||
}
|
||||
@@ -86,15 +86,16 @@ public class RequestMessageUrlMatcher : IRequestMatcher
|
||||
if (Matchers != null)
|
||||
{
|
||||
var results = Matchers.Select(m => m.IsMatch(requestMessage.Url)).ToArray();
|
||||
return MatchResult.From(results, MatchOperator);
|
||||
return MatchResult.From(nameof(RequestMessageUrlMatcher), results, MatchOperator);
|
||||
}
|
||||
|
||||
if (Funcs != null)
|
||||
{
|
||||
var results = Funcs.Select(func => func(requestMessage.Url)).ToArray();
|
||||
return MatchScores.ToScore(results, MatchOperator);
|
||||
var score = MatchScores.ToScore(results, MatchOperator);
|
||||
return MatchResult.From(nameof(RequestMessageUrlMatcher), score);
|
||||
}
|
||||
|
||||
return default;
|
||||
return MatchResult.From(nameof(RequestMessageUrlMatcher));
|
||||
}
|
||||
}
|
||||
@@ -86,7 +86,7 @@ public class SimMetricsMatcher : IStringMatcher
|
||||
IStringMetric stringMetricType = GetStringMetricType();
|
||||
|
||||
var score = MatchScores.ToScore(_patterns.Select(p => stringMetricType.GetSimilarity(p.GetPattern(), input)).ToArray(), MatchOperator);
|
||||
return MatchBehaviourHelper.Convert(MatchBehaviour, score);
|
||||
return MatchResult.From(Name, MatchBehaviourHelper.Convert(MatchBehaviour, score));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -116,7 +116,7 @@ public class XPathMatcher : IStringMatcher
|
||||
|
||||
private MatchResult CreateMatchResult(double score, Exception? exception = null)
|
||||
{
|
||||
return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score), exception);
|
||||
return MatchResult.From(Name, MatchBehaviourHelper.Convert(MatchBehaviour, score), exception);
|
||||
}
|
||||
|
||||
private sealed class XPathEvaluator
|
||||
|
||||
@@ -34,13 +34,6 @@ public partial class Request
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IRequestBuilder WithBodyAsJson(object body, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
|
||||
{
|
||||
var matcher = body as IMatcher ?? new JsonMatcher(matchBehaviour, body);
|
||||
return WithBody([matcher]);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IRequestBuilder WithBody(IMatcher matcher)
|
||||
{
|
||||
@@ -98,4 +91,20 @@ public partial class Request
|
||||
_requestMatchers.Add(new RequestMessageBodyMatcher(Guard.NotNull(func)));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IRequestBuilder WithBodyAsJson(object body, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
|
||||
{
|
||||
var matcher = body as IMatcher ?? new JsonMatcher(matchBehaviour, body);
|
||||
return WithBody([matcher]);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IRequestBuilder WithBodyAsType<T>(Func<T?, bool> func)
|
||||
{
|
||||
Guard.NotNull(func);
|
||||
|
||||
_requestMatchers.Add(new RequestMessageBodyMatcher<T>(func));
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ public partial class Request
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IRequestBuilder WithMultiPart(IMatcher[] matchers, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch, MatchOperator matchOperator = MatchOperator.Or)
|
||||
public IRequestBuilder WithMultiPart(IMatcher[] matchers, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch, MatchOperator matchOperator = MatchOperator.And)
|
||||
{
|
||||
_requestMatchers.Add(new RequestMessageMultiPartMatcher(matchBehaviour, matchOperator, matchers));
|
||||
return this;
|
||||
|
||||
@@ -4,6 +4,7 @@ using System;
|
||||
using Stef.Validation;
|
||||
using WireMock.Matchers;
|
||||
using WireMock.Matchers.Request;
|
||||
using WireMock.Validators;
|
||||
|
||||
namespace WireMock.RequestBuilders;
|
||||
|
||||
@@ -20,7 +21,7 @@ public partial class Request
|
||||
{
|
||||
Guard.NotNullOrEmpty(matchers);
|
||||
|
||||
_requestMatchers.Add(new RequestMessagePathMatcher(MatchBehaviour.AcceptOnMatch, MatchOperator.Or, matchers));
|
||||
_requestMatchers.Add(new RequestMessagePathMatcher(MatchBehaviour.AcceptOnMatch, matchOperator, matchers));
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -34,6 +35,10 @@ public partial class Request
|
||||
public IRequestBuilder WithPath(MatchOperator matchOperator, params string[] paths)
|
||||
{
|
||||
Guard.NotNullOrEmpty(paths);
|
||||
foreach (var path in paths)
|
||||
{
|
||||
PathValidator.ValidateAndThrow(path, nameof(paths));
|
||||
}
|
||||
|
||||
_requestMatchers.Add(new RequestMessagePathMatcher(MatchBehaviour.AcceptOnMatch, matchOperator, paths));
|
||||
return this;
|
||||
|
||||
@@ -29,7 +29,7 @@ public partial class Request : RequestMessageCompositeMatcher, IRequestBuilder
|
||||
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
|
||||
public static IRequestBuilder Create()
|
||||
{
|
||||
return new Request(new List<IRequestMatcher>());
|
||||
return new Request([]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -166,11 +166,12 @@ internal class LogEntryMapper
|
||||
TotalScore = matchResult.TotalScore,
|
||||
TotalNumber = matchResult.TotalNumber,
|
||||
AverageTotalScore = matchResult.AverageTotalScore,
|
||||
MatchDetails = matchResult.MatchDetails.Select(md => new
|
||||
{
|
||||
Name = md.MatcherType.Name.Replace("RequestMessage", string.Empty),
|
||||
md.Score
|
||||
} as object).ToList()
|
||||
MatchDetails = matchResult.MatchDetails
|
||||
//MatchDetails = matchResult.MatchDetails.Select(md => new
|
||||
//{
|
||||
// Name = md.MatcherType.Name.Replace("RequestMessage", string.Empty),
|
||||
// md.Score
|
||||
//} as object).ToList()
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -226,14 +226,14 @@ internal class MappingConverter(MatcherMapper mapper)
|
||||
}
|
||||
}
|
||||
|
||||
if (response.Delay is { })
|
||||
{
|
||||
sb.AppendLine($" .WithDelay({response.Delay.Value.TotalMilliseconds})");
|
||||
}
|
||||
else if (response is { MinimumDelayMilliseconds: > 0, MaximumDelayMilliseconds: > 0 })
|
||||
if (response is { MinimumDelayMilliseconds: > 0, MaximumDelayMilliseconds: > 0 })
|
||||
{
|
||||
sb.AppendLine($" .WithRandomDelay({response.MinimumDelayMilliseconds}, {response.MaximumDelayMilliseconds})");
|
||||
}
|
||||
else if (response.Delay is { })
|
||||
{
|
||||
sb.AppendLine($" .WithDelay({response.Delay.Value.TotalMilliseconds})");
|
||||
}
|
||||
|
||||
if (response.UseTransformer)
|
||||
{
|
||||
|
||||
@@ -25,9 +25,9 @@ internal class MatcherMapper
|
||||
_settings = Guard.NotNull(settings);
|
||||
}
|
||||
|
||||
public IMatcher[]? Map(IEnumerable<MatcherModel>? matchers)
|
||||
public IMatcher[] Map(IEnumerable<MatcherModel>? matchers)
|
||||
{
|
||||
return matchers?.Select(Map).OfType<IMatcher>().ToArray();
|
||||
return matchers?.Select(Map).OfType<IMatcher>().ToArray() ?? [];
|
||||
}
|
||||
|
||||
public IMatcher? Map(MatcherModel? matcherModel)
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Linq;
|
||||
using Stef.Validation;
|
||||
using WireMock.Admin.Mappings;
|
||||
using WireMock.Matchers;
|
||||
using WireMock.Matchers.Request;
|
||||
using WireMock.RequestBuilders;
|
||||
using WireMock.ResponseBuilders;
|
||||
using WireMock.Serialization;
|
||||
@@ -253,7 +254,15 @@ public partial class WireMockServer
|
||||
else if (requestModel.Body?.Matchers != null)
|
||||
{
|
||||
var matchOperator = StringUtils.ParseMatchOperator(requestModel.Body.MatchOperator);
|
||||
requestBuilder = requestBuilder.WithBody(_matcherMapper.Map(requestModel.Body.Matchers)!, matchOperator);
|
||||
|
||||
if (requestModel.Body.MatcherName == RequestMessageMultiPartMatcher.Name)
|
||||
{
|
||||
requestBuilder = requestBuilder.WithMultiPart(_matcherMapper.Map(requestModel.Body.Matchers), matchOperator: matchOperator);
|
||||
}
|
||||
else
|
||||
{
|
||||
requestBuilder = requestBuilder.WithBody(_matcherMapper.Map(requestModel.Body.Matchers), matchOperator);
|
||||
}
|
||||
}
|
||||
|
||||
return requestBuilder;
|
||||
|
||||
@@ -55,7 +55,7 @@ internal class SimpleSettingsParser
|
||||
// Now also parse environment
|
||||
if (environment != null)
|
||||
{
|
||||
foreach (string key in environment.Keys)
|
||||
foreach (var key in environment.Keys.OfType<string>())
|
||||
{
|
||||
if (key.StartsWith(Prefix, StringComparison.OrdinalIgnoreCase) && environment.TryGetStringValue(key, out var value))
|
||||
{
|
||||
|
||||
@@ -36,7 +36,7 @@ public static class WireMockServerSettingsParser
|
||||
|
||||
if (parser.GetBoolSwitchValue("help"))
|
||||
{
|
||||
(logger ?? new WireMockConsoleLogger()).Info("See https://github.com/wiremock/WireMock.Net/wiki/WireMock-commandline-parameters for details on all commandline options.");
|
||||
(logger ?? new WireMockConsoleLogger()).Info("See https://wiremock.org/dotnet/wiremock-commandline-parameters/ for details on all commandline options.");
|
||||
settings = null;
|
||||
return false;
|
||||
}
|
||||
@@ -153,7 +153,7 @@ public static class WireMockServerSettingsParser
|
||||
}
|
||||
else if (settings.HostingScheme is null)
|
||||
{
|
||||
settings.Urls = parser.GetValues("Urls", ["http://*:9091/"]);
|
||||
settings.Urls = parser.GetValues(nameof(WireMockServerSettings.Urls), defaultValue: ["http://*:9091/"]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,8 @@ internal static class WireMockHandlebarsHelpers
|
||||
#endif
|
||||
o.CustomHelperPaths = paths;
|
||||
|
||||
o.Categories = settings.HandlebarsSettings?.AllowedHandlebarsHelpers ?? HandlebarsSettings.DefaultAllowedHandlebarsHelpers;
|
||||
|
||||
o.CustomHelpers = new Dictionary<string, IHelpers>();
|
||||
if (settings.HandlebarsSettings?.AllowedCustomHandlebarsHelpers.HasFlag(CustomHandlebarsHelpers.File) == true)
|
||||
{
|
||||
|
||||
@@ -84,22 +84,22 @@ internal static class PortUtils
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extract the isHttps, isHttp2, protocol, host and port from a URL.
|
||||
/// Extract the isHttps, isHttp2, scheme, host and port from a URL.
|
||||
/// </summary>
|
||||
public static bool TryExtract(string url, out bool isHttps, out bool isHttp2, [NotNullWhen(true)] out string? protocol, [NotNullWhen(true)] out string? host, out int port)
|
||||
public static bool TryExtract(string url, out bool isHttps, out bool isHttp2, [NotNullWhen(true)] out string? scheme, [NotNullWhen(true)] out string? host, out int port)
|
||||
{
|
||||
isHttps = false;
|
||||
isHttp2 = false;
|
||||
protocol = null;
|
||||
scheme = null;
|
||||
host = null;
|
||||
port = 0;
|
||||
|
||||
var match = UrlDetailsRegex.Match(url);
|
||||
if (match.Success)
|
||||
{
|
||||
protocol = match.Groups["proto"].Value;
|
||||
isHttps = protocol.StartsWith("https", StringComparison.OrdinalIgnoreCase) || protocol.StartsWith("grpcs", StringComparison.OrdinalIgnoreCase);
|
||||
isHttp2 = protocol.StartsWith("grpc", StringComparison.OrdinalIgnoreCase);
|
||||
scheme = match.Groups["proto"].Value;
|
||||
isHttps = scheme.StartsWith("https", StringComparison.OrdinalIgnoreCase) || scheme.StartsWith("grpcs", StringComparison.OrdinalIgnoreCase);
|
||||
isHttp2 = scheme.StartsWith("grpc", StringComparison.OrdinalIgnoreCase);
|
||||
host = match.Groups["host"].Value;
|
||||
|
||||
return int.TryParse(match.Groups["port"].Value, out port);
|
||||
|
||||
71
src/WireMock.Net.NUnit/TestContextWireMockLogger.cs
Normal file
71
src/WireMock.Net.NUnit/TestContextWireMockLogger.cs
Normal file
@@ -0,0 +1,71 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System;
|
||||
using JsonConverter.Abstractions;
|
||||
using JsonConverter.Newtonsoft.Json;
|
||||
using NUnit.Framework;
|
||||
using WireMock.Admin.Requests;
|
||||
using WireMock.Logging;
|
||||
|
||||
namespace WireMock.Net.NUnit;
|
||||
|
||||
/// <summary>
|
||||
/// When using NUnit, this class enables to log the output from WireMock.Net using the <see cref="TestContext"/>.
|
||||
/// </summary>
|
||||
public sealed class TestContextWireMockLogger(IJsonConverter? jsonConverter = null) : IWireMockLogger
|
||||
{
|
||||
private readonly JsonConverterOptions _jsonConverterOptions = new() { WriteIndented = true, IgnoreNullValues = true };
|
||||
private readonly IJsonConverter _jsonConverter = jsonConverter ?? new NewtonsoftJsonConverter();
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Debug(string formatString, params object[] args)
|
||||
{
|
||||
TestContext.WriteLine(Format("Debug", formatString, args));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Info(string formatString, params object[] args)
|
||||
{
|
||||
TestContext.WriteLine(Format("Info", formatString, args));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Warn(string formatString, params object[] args)
|
||||
{
|
||||
TestContext.WriteLine(Format("Warning", formatString, args));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Error(string formatString, params object[] args)
|
||||
{
|
||||
TestContext.WriteLine(Format("Error", formatString, args));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Error(string message, Exception exception)
|
||||
{
|
||||
TestContext.WriteLine(Format("Error", $"{message} {{0}}", exception));
|
||||
|
||||
if (exception is AggregateException ae)
|
||||
{
|
||||
ae.Handle(ex =>
|
||||
{
|
||||
TestContext.WriteLine(Format("Error", "Exception {0}", ex));
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void DebugRequestResponse(LogEntryModel logEntryModel, bool isAdminRequest)
|
||||
{
|
||||
var message = _jsonConverter.Serialize(logEntryModel, _jsonConverterOptions);
|
||||
TestContext.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}";
|
||||
}
|
||||
}
|
||||
36
src/WireMock.Net.NUnit/WireMock.Net.NUnit.csproj
Normal file
36
src/WireMock.Net.NUnit/WireMock.Net.NUnit.csproj
Normal file
@@ -0,0 +1,36 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<Description>Some extensions for NUnit</Description>
|
||||
<AssemblyTitle>WireMock.Net.NUnit</AssemblyTitle>
|
||||
<Authors>Stef Heyenrath</Authors>
|
||||
<TargetFrameworks>net462;net6.0;net8.0</TargetFrameworks>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<AssemblyName>WireMock.Net.NUnit</AssemblyName>
|
||||
<RootNamespace>WireMock.Net.NUnit</RootNamespace>
|
||||
<PackageId>WireMock.Net.NUnit</PackageId>
|
||||
<PackageTags>tdd;wiremock;test;unittest;nunit</PackageTags>
|
||||
<ProjectGuid>{2DBBD70D-8051-441F-92BB-3F9B8B4B4983}</ProjectGuid>
|
||||
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
|
||||
<EmbedUntrackedSources>true</EmbedUntrackedSources>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
<GenerateBindingRedirectsOutputType>true</GenerateBindingRedirectsOutputType>
|
||||
<SignAssembly>true</SignAssembly>
|
||||
<AssemblyOriginatorKeyFile>../WireMock.Net/WireMock.Net.snk</AssemblyOriginatorKeyFile>
|
||||
<PublicSign Condition=" '$(OS)' != 'Windows_NT' ">true</PublicSign>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="JsonConverter.Newtonsoft.Json" Version="0.8.0" />
|
||||
<PackageReference Include="NUnit" Version="4.4.0" />
|
||||
<PackageReference Include="Stef.Validation" Version="0.1.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\WireMock.Net.Abstractions\WireMock.Net.Abstractions.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -5,9 +5,7 @@ using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using Microsoft.OpenApi.Interfaces;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Microsoft.OpenApi.Models.Interfaces;
|
||||
using Microsoft.OpenApi;
|
||||
using WireMock.Net.OpenApiParser.Types;
|
||||
|
||||
namespace WireMock.Net.OpenApiParser.Extensions;
|
||||
|
||||
@@ -6,8 +6,7 @@ using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Microsoft.OpenApi.Models.Interfaces;
|
||||
using Microsoft.OpenApi;
|
||||
using Newtonsoft.Json;
|
||||
using Stef.Validation;
|
||||
using WireMock.Admin.Mappings;
|
||||
@@ -19,18 +18,12 @@ using SystemTextJsonSerializer = System.Text.Json.JsonSerializer;
|
||||
|
||||
namespace WireMock.Net.OpenApiParser.Mappers;
|
||||
|
||||
internal class OpenApiPathsMapper
|
||||
internal class OpenApiPathsMapper(WireMockOpenApiParserSettings settings)
|
||||
{
|
||||
private const string HeaderContentType = "Content-Type";
|
||||
|
||||
private readonly WireMockOpenApiParserSettings _settings;
|
||||
private readonly ExampleValueGenerator _exampleValueGenerator;
|
||||
|
||||
public OpenApiPathsMapper(WireMockOpenApiParserSettings settings)
|
||||
{
|
||||
_settings = Guard.NotNull(settings);
|
||||
_exampleValueGenerator = new ExampleValueGenerator(settings);
|
||||
}
|
||||
private readonly WireMockOpenApiParserSettings _settings = Guard.NotNull(settings);
|
||||
private readonly ExampleValueGenerator _exampleValueGenerator = new(settings);
|
||||
|
||||
public IReadOnlyList<MappingModel> ToMappingModels(OpenApiPaths? paths, IList<OpenApiServer> servers)
|
||||
{
|
||||
@@ -41,7 +34,7 @@ internal class OpenApiPathsMapper
|
||||
.ToArray() ?? [];
|
||||
}
|
||||
|
||||
private IReadOnlyList<MappingModel> MapPath(string path, IOpenApiPathItem pathItem, IList<OpenApiServer> servers)
|
||||
private MappingModel[] MapPath(string path, IOpenApiPathItem pathItem, IList<OpenApiServer> servers)
|
||||
{
|
||||
return pathItem.Operations?.Select(o => MapOperationToMappingModel(path, o.Key.ToString().ToUpperInvariant(), o.Value, servers)).ToArray() ?? [];
|
||||
}
|
||||
@@ -50,35 +43,7 @@ internal class OpenApiPathsMapper
|
||||
{
|
||||
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() ?? new KeyValuePair<string, IOpenApiResponse>();
|
||||
|
||||
TryGetContent(response.Value?.Content, out OpenApiMediaType? responseContent, out var responseContentType);
|
||||
var responseSchema = response.Value?.Content?.FirstOrDefault().Value?.Schema;
|
||||
var responseExample = responseContent?.Example;
|
||||
var responseSchemaExample = responseContent?.Schema?.Example;
|
||||
|
||||
var responseBody = responseExample ?? responseSchemaExample ?? MapSchemaToObject(responseSchema);
|
||||
|
||||
var requestBodyModel = new BodyModel();
|
||||
if (operation.RequestBody != null && operation.RequestBody.Content != null && operation.RequestBody.Required)
|
||||
{
|
||||
var request = operation.RequestBody.Content;
|
||||
TryGetContent(request, out var requestContent, out _);
|
||||
|
||||
var requestBodySchema = operation.RequestBody.Content.First().Value?.Schema;
|
||||
var requestBodyExample = requestContent!.Example;
|
||||
var requestBodySchemaExample = requestContent.Schema?.Example;
|
||||
|
||||
var requestBodyMapped = requestBodyExample ?? requestBodySchemaExample ?? MapSchemaToObject(requestBodySchema);
|
||||
requestBodyModel = MapRequestBody(requestBodyMapped);
|
||||
}
|
||||
|
||||
if (!int.TryParse(response.Key, out var httpStatusCode))
|
||||
{
|
||||
httpStatusCode = 200;
|
||||
}
|
||||
var requestHeaders = operation.Parameters?.Where(p => p.In == ParameterLocation.Header) ?? [];
|
||||
|
||||
return new MappingModel
|
||||
{
|
||||
@@ -88,15 +53,94 @@ internal class OpenApiPathsMapper
|
||||
Methods = [httpMethod],
|
||||
Path = PathUtils.Combine(MapBasePath(servers), MapPathWithParameters(path, pathParameters)),
|
||||
Params = MapQueryParameters(queryParameters),
|
||||
Headers = MapRequestHeaders(headers),
|
||||
Body = requestBodyModel
|
||||
Headers = MapRequestHeaders(requestHeaders),
|
||||
Body = GetRequestBodyModel(operation.RequestBody)
|
||||
},
|
||||
Response = new ResponseModel
|
||||
{
|
||||
StatusCode = httpStatusCode,
|
||||
Headers = MapHeaders(responseContentType, response.Value?.Headers),
|
||||
BodyAsJson = responseBody != null ? JsonConvert.DeserializeObject(SystemTextJsonSerializer.Serialize(responseBody)) : null
|
||||
}
|
||||
Response = GetResponseModel(operation.Responses?.FirstOrDefault())
|
||||
};
|
||||
}
|
||||
|
||||
private BodyModel GetRequestBodyModel(IOpenApiRequestBody? openApiRequestBody)
|
||||
{
|
||||
if (openApiRequestBody is not { Content: not null, Required: true })
|
||||
{
|
||||
return new BodyModel();
|
||||
}
|
||||
|
||||
var content = openApiRequestBody.Content;
|
||||
|
||||
TryGetContent(content, out var requestContent, out _);
|
||||
|
||||
var requestExample = requestContent?.Example;
|
||||
var requestExamples = requestContent?.Examples;
|
||||
var requestSchemaExample = requestContent?.Schema?.Example;
|
||||
var requestSchemaExamples = requestContent?.Schema?.Examples;
|
||||
|
||||
JsonNode? request;
|
||||
if (requestExample != null)
|
||||
{
|
||||
request = requestExample;
|
||||
}
|
||||
else if (requestSchemaExample != null)
|
||||
{
|
||||
request = requestSchemaExample;
|
||||
}
|
||||
else if (requestExamples != null)
|
||||
{
|
||||
request = requestExamples.FirstOrDefault().Value.Value;
|
||||
}
|
||||
else if (requestSchemaExamples != null)
|
||||
{
|
||||
request = requestSchemaExamples.FirstOrDefault();
|
||||
}
|
||||
else
|
||||
{
|
||||
var requestSchema = content?.FirstOrDefault().Value.Schema;
|
||||
request = MapSchemaToObject(requestSchema);
|
||||
}
|
||||
|
||||
return MapRequestBody(request) ?? new BodyModel();
|
||||
}
|
||||
|
||||
private ResponseModel GetResponseModel(KeyValuePair<string, IOpenApiResponse>? openApiResponse)
|
||||
{
|
||||
var content = openApiResponse?.Value.Content;
|
||||
|
||||
TryGetContent(content, out var responseContent, out var contentType);
|
||||
|
||||
var responseExample = responseContent?.Example;
|
||||
var responseExamples = responseContent?.Examples;
|
||||
var responseSchemaExample = responseContent?.Schema?.Example;
|
||||
var responseSchemaExamples = responseContent?.Schema?.Examples;
|
||||
|
||||
JsonNode? response;
|
||||
if (responseExample != null)
|
||||
{
|
||||
response = responseExample;
|
||||
}
|
||||
else if (responseSchemaExample != null)
|
||||
{
|
||||
response = responseSchemaExample;
|
||||
}
|
||||
else if (responseExamples != null)
|
||||
{
|
||||
response = responseExamples.FirstOrDefault().Value.Value;
|
||||
}
|
||||
else if (responseSchemaExamples != null)
|
||||
{
|
||||
response = responseSchemaExamples.FirstOrDefault();
|
||||
}
|
||||
else
|
||||
{
|
||||
var responseSchema = content?.FirstOrDefault().Value?.Schema;
|
||||
response = MapSchemaToObject(responseSchema);
|
||||
}
|
||||
|
||||
return new ResponseModel
|
||||
{
|
||||
StatusCode = int.TryParse(openApiResponse?.Key, out var httpStatusCode) ? httpStatusCode : 200,
|
||||
Headers = MapHeaders(contentType, openApiResponse?.Value.Headers),
|
||||
BodyAsJson = response != null ? JsonConvert.DeserializeObject(SystemTextJsonSerializer.Serialize(response)) : null
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System;
|
||||
using Microsoft.OpenApi.Models.Interfaces;
|
||||
using Microsoft.OpenApi;
|
||||
|
||||
namespace WireMock.Net.OpenApiParser.Settings;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System;
|
||||
using Microsoft.OpenApi.Models.Interfaces;
|
||||
using Microsoft.OpenApi;
|
||||
using RandomDataGenerator.FieldOptions;
|
||||
using RandomDataGenerator.Randomizers;
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Microsoft.OpenApi.Models.Interfaces;
|
||||
using Microsoft.OpenApi;
|
||||
|
||||
namespace WireMock.Net.OpenApiParser.Settings;
|
||||
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text.Json.Nodes;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Microsoft.OpenApi.Models.Interfaces;
|
||||
using Microsoft.OpenApi;
|
||||
using Stef.Validation;
|
||||
using WireMock.Net.OpenApiParser.Extensions;
|
||||
using WireMock.Net.OpenApiParser.Settings;
|
||||
|
||||
@@ -27,22 +27,22 @@
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="RamlToOpenApiConverter.SourceOnly" Version="0.8.0" />
|
||||
<PackageReference Include="YamlDotNet" Version="8.1.0" />
|
||||
<PackageReference Include="RamlToOpenApiConverter.SourceOnly" Version="0.11.0" />
|
||||
<PackageReference Include="YamlDotNet" Version="16.3.0" />
|
||||
<PackageReference Include="RandomDataGenerator.Net" Version="1.0.19" />
|
||||
<PackageReference Include="Stef.Validation" Version="0.1.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(Configuration)' == 'Debug'">
|
||||
<PackageReference Include="Microsoft.OpenApi.YamlReader" Version="2.0.0-preview.17" />
|
||||
<PackageReference Include="Microsoft.OpenApi.YamlReader" Version="2.3.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(Configuration)' == 'Release'">
|
||||
<PackageReference Include="ILRepack.Lib.MSBuild.Task" Version="2.0.40" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.OpenApi" Version="2.0.0-preview.17" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.OpenApi.YamlReader" Version="2.0.0-preview.17" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.OpenApi" Version="2.3.0" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.OpenApi.YamlReader" Version="2.3.0" PrivateAssets="All" />
|
||||
<PackageReference Include="System.Text.Json" Version="8.0.5" />
|
||||
<PackageReference Include="SharpYaml" Version="2.1.1" />
|
||||
<PackageReference Include="SharpYaml" Version="2.1.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -5,7 +5,7 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Microsoft.OpenApi;
|
||||
using Microsoft.OpenApi.Reader;
|
||||
using Microsoft.OpenApi.YamlReader;
|
||||
using RamlToOpenApiConverter;
|
||||
|
||||
@@ -57,7 +57,7 @@ public class ProtoBufMatcher : IProtoBufMatcher
|
||||
/// <inheritdoc />
|
||||
public async Task<MatchResult> IsMatchAsync(byte[]? input, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var result = new MatchResult();
|
||||
var result = MatchResult.From(Name);
|
||||
|
||||
if (input != null)
|
||||
{
|
||||
@@ -65,11 +65,11 @@ public class ProtoBufMatcher : IProtoBufMatcher
|
||||
{
|
||||
var instance = await DecodeAsync(input, true, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
result = Matcher?.IsMatch(instance) ?? new MatchResult(MatchScores.Perfect);
|
||||
result = Matcher?.IsMatch(instance) ?? MatchResult.From(Name, MatchScores.Perfect);
|
||||
}
|
||||
catch (Exception e)
|
||||
catch (Exception ex)
|
||||
{
|
||||
result = new MatchResult(MatchScores.Mismatch, e);
|
||||
result = MatchResult.From(Name, ex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,14 +96,14 @@ public class ProtoBufMatcher : IProtoBufMatcher
|
||||
}
|
||||
|
||||
var protoDefinitions = ProtoDefinition().Texts;
|
||||
|
||||
|
||||
var resolver = new WireMockProtoFileResolver(protoDefinitions);
|
||||
var request = new ConvertToObjectRequest(protoDefinitions[0], MessageType, input)
|
||||
.WithProtoFileResolver(resolver);
|
||||
|
||||
try
|
||||
{
|
||||
return await ProtoBufToJsonConverter.ConvertAsync(request, cancellationToken).ConfigureAwait(false);
|
||||
return await ProtoBufToJsonConverter.ConvertAsync(request, cancellationToken);
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ProtoBufJsonConverter" Version="0.10.0" />
|
||||
<PackageReference Include="ProtoBufJsonConverter" Version="0.11.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -68,7 +68,7 @@ public class ExactObjectMatcher : IObjectMatcher
|
||||
equals = Equals(Value, input);
|
||||
}
|
||||
|
||||
return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.ToScore(equals));
|
||||
return MatchResult.From(Name, MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.ToScore(equals)));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -8,66 +8,68 @@ namespace WireMock.Matchers.Helpers;
|
||||
|
||||
internal static class BodyDataMatchScoreCalculator
|
||||
{
|
||||
internal static MatchResult CalculateMatchScore(IBodyData? requestMessage, IMatcher matcher)
|
||||
private static string _name = nameof(BodyDataMatchScoreCalculator);
|
||||
|
||||
internal static MatchResult CalculateMatchScore(IBodyData? bodyData, IMatcher matcher)
|
||||
{
|
||||
Guard.NotNull(matcher);
|
||||
|
||||
if (requestMessage == null)
|
||||
if (bodyData == null)
|
||||
{
|
||||
return default;
|
||||
return MatchResult.From(_name);
|
||||
}
|
||||
|
||||
if (matcher is NotNullOrEmptyMatcher notNullOrEmptyMatcher)
|
||||
{
|
||||
switch (requestMessage.DetectedBodyType)
|
||||
switch (bodyData.DetectedBodyType)
|
||||
{
|
||||
case BodyType.Json:
|
||||
case BodyType.String:
|
||||
case BodyType.FormUrlEncoded:
|
||||
return notNullOrEmptyMatcher.IsMatch(requestMessage.BodyAsString);
|
||||
return notNullOrEmptyMatcher.IsMatch(bodyData.BodyAsString);
|
||||
|
||||
case BodyType.Bytes:
|
||||
return notNullOrEmptyMatcher.IsMatch(requestMessage.BodyAsBytes);
|
||||
return notNullOrEmptyMatcher.IsMatch(bodyData.BodyAsBytes);
|
||||
|
||||
default:
|
||||
return default;
|
||||
return MatchResult.From(_name);
|
||||
}
|
||||
}
|
||||
|
||||
if (matcher is ExactObjectMatcher { Value: byte[] } exactObjectMatcher)
|
||||
{
|
||||
// If the body is a byte array, try to match.
|
||||
return exactObjectMatcher.IsMatch(requestMessage.BodyAsBytes);
|
||||
return exactObjectMatcher.IsMatch(bodyData.BodyAsBytes);
|
||||
}
|
||||
|
||||
// Check if the matcher is a IObjectMatcher
|
||||
if (matcher is IObjectMatcher objectMatcher)
|
||||
{
|
||||
// If the body is a JSON object, try to match.
|
||||
if (requestMessage.DetectedBodyType == BodyType.Json)
|
||||
if (bodyData.DetectedBodyType == BodyType.Json)
|
||||
{
|
||||
return objectMatcher.IsMatch(requestMessage.BodyAsJson);
|
||||
return objectMatcher.IsMatch(bodyData.BodyAsJson);
|
||||
}
|
||||
|
||||
// If the body is a byte array, try to match.
|
||||
if (requestMessage.DetectedBodyType == BodyType.Bytes)
|
||||
if (bodyData.DetectedBodyType == BodyType.Bytes)
|
||||
{
|
||||
return objectMatcher.IsMatch(requestMessage.BodyAsBytes);
|
||||
return objectMatcher.IsMatch(bodyData.BodyAsBytes);
|
||||
}
|
||||
}
|
||||
|
||||
// In case the matcher is a IStringMatcher and If body is a Json or a String, use the BodyAsString to match on.
|
||||
if (matcher is IStringMatcher stringMatcher && requestMessage.DetectedBodyType is BodyType.Json or BodyType.String or BodyType.FormUrlEncoded)
|
||||
// In case the matcher is a IStringMatcher and if body is a Json or a String, use the BodyAsString to match on.
|
||||
if (matcher is IStringMatcher stringMatcher && bodyData.DetectedBodyType is BodyType.Json or BodyType.String or BodyType.FormUrlEncoded)
|
||||
{
|
||||
return stringMatcher.IsMatch(requestMessage.BodyAsString);
|
||||
return stringMatcher.IsMatch(bodyData.BodyAsString);
|
||||
}
|
||||
|
||||
// In case the matcher is a IProtoBufMatcher, use the BodyAsBytes to match on.
|
||||
if (matcher is IProtoBufMatcher protoBufMatcher)
|
||||
{
|
||||
return protoBufMatcher.IsMatchAsync(requestMessage.BodyAsBytes).GetAwaiter().GetResult();
|
||||
return protoBufMatcher.IsMatchAsync(bodyData.BodyAsBytes).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
return default;
|
||||
return MatchResult.From(_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,6 @@ internal static class MatchBehaviourHelper
|
||||
/// <returns>match result</returns>
|
||||
internal static MatchResult Convert(MatchBehaviour matchBehaviour, MatchResult result)
|
||||
{
|
||||
return matchBehaviour == MatchBehaviour.AcceptOnMatch ? result : new MatchResult(Convert(matchBehaviour, result.Score), result.Exception);
|
||||
return matchBehaviour == MatchBehaviour.AcceptOnMatch ? result : MatchResult.From(result.Name, Convert(matchBehaviour, result.Score), result.Exception);
|
||||
}
|
||||
}
|
||||
@@ -5,13 +5,14 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Stef.Validation;
|
||||
using WireMock.Extensions;
|
||||
using WireMock.Matchers.Request;
|
||||
|
||||
namespace WireMock.Matchers;
|
||||
|
||||
/// <summary>
|
||||
/// The MatchResult which contains the score (value between 0.0 - 1.0 of the similarity) and an optional error message.
|
||||
/// </summary>
|
||||
public struct MatchResult
|
||||
public class MatchResult
|
||||
{
|
||||
/// <summary>
|
||||
/// A value between 0.0 - 1.0 of the similarity.
|
||||
@@ -25,46 +26,55 @@ public struct MatchResult
|
||||
public Exception? Exception { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a MatchResult
|
||||
/// The name or description of the matcher.
|
||||
/// </summary>
|
||||
/// <param name="score">A value between 0.0 - 1.0 of the similarity.</param>
|
||||
/// <param name="exception">The exception in case the matching fails. [Optional]</param>
|
||||
public MatchResult(double score, Exception? exception = null)
|
||||
{
|
||||
Score = score;
|
||||
Exception = exception;
|
||||
}
|
||||
public required string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a MatchResult
|
||||
/// The child MatchResults in case of multiple matchers.
|
||||
/// </summary>
|
||||
/// <param name="exception">The exception in case the matching fails.</param>
|
||||
public MatchResult(Exception exception)
|
||||
{
|
||||
Exception = Guard.NotNull(exception);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implicitly converts a double to a MatchResult.
|
||||
/// </summary>
|
||||
/// <param name="score">The score</param>
|
||||
public static implicit operator MatchResult(double score)
|
||||
{
|
||||
return new MatchResult(score);
|
||||
}
|
||||
public MatchResult[]? MatchResults { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Is the value a perfect match?
|
||||
/// </summary>
|
||||
public bool IsPerfect() => MatchScores.IsPerfect(Score);
|
||||
|
||||
/// <summary>
|
||||
/// Create a MatchResult.
|
||||
/// </summary>
|
||||
/// <param name="name">The name or description of the matcher.</param>
|
||||
/// <param name="score">A value between 0.0 - 1.0 of the similarity.</param>
|
||||
/// <param name="exception">The exception in case the matching fails. [Optional]</param>
|
||||
public static MatchResult From(string name, double score = 0, Exception? exception = null)
|
||||
{
|
||||
return new MatchResult
|
||||
{
|
||||
Name = name,
|
||||
Score = score,
|
||||
Exception = exception
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a MatchResult from exception.
|
||||
/// </summary>
|
||||
/// <param name="name">The name or description of the matcher.</param>
|
||||
/// <param name="exception">The exception in case the matching fails.</param>
|
||||
/// <returns>MatchResult</returns>
|
||||
public static MatchResult From(string name, Exception exception)
|
||||
{
|
||||
return From(name, MatchScores.Mismatch, exception);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a MatchResult from multiple MatchResults.
|
||||
/// </summary>
|
||||
/// <param name="name">The name or description of the matcher.</param>
|
||||
/// <param name="matchResults">A list of MatchResults.</param>
|
||||
/// <param name="matchOperator">The MatchOperator</param>
|
||||
/// <returns>MatchResult</returns>
|
||||
public static MatchResult From(IReadOnlyList<MatchResult> matchResults, MatchOperator matchOperator)
|
||||
public static MatchResult From(string name, IReadOnlyList<MatchResult> matchResults, MatchOperator matchOperator)
|
||||
{
|
||||
Guard.NotNullOrEmpty(matchResults);
|
||||
|
||||
@@ -75,6 +85,8 @@ public struct MatchResult
|
||||
|
||||
return new MatchResult
|
||||
{
|
||||
Name = name,
|
||||
MatchResults = matchResults.ToArray(),
|
||||
Score = MatchScores.ToScore(matchResults.Select(r => r.Score).ToArray(), matchOperator),
|
||||
Exception = matchResults.Select(m => m.Exception).OfType<Exception>().ToArray().ToException()
|
||||
};
|
||||
@@ -88,4 +100,19 @@ public struct MatchResult
|
||||
{
|
||||
return (Score, Exception);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert to <see cref="MatchResult"/>.
|
||||
/// </summary>
|
||||
public MatchDetail ToMatchDetail()
|
||||
{
|
||||
return new MatchDetail
|
||||
{
|
||||
Name = Name,
|
||||
MatcherType = typeof(MatchResult),
|
||||
Score = Score,
|
||||
Exception = Exception,
|
||||
MatchDetails = MatchResults?.Select(mr => mr.ToMatchDetail()).ToArray()
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using AnyOfTypes;
|
||||
using WireMock.Extensions;
|
||||
@@ -53,7 +52,7 @@ public class NotNullOrEmptyMatcher : IObjectMatcher, IStringMatcher
|
||||
break;
|
||||
}
|
||||
|
||||
return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.ToScore(match));
|
||||
return MatchResult.From(Name, MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.ToScore(match)));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -61,7 +60,7 @@ public class NotNullOrEmptyMatcher : IObjectMatcher, IStringMatcher
|
||||
{
|
||||
var match = !string.IsNullOrEmpty(input);
|
||||
|
||||
return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.ToScore(match));
|
||||
return MatchResult.From(Name, MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.ToScore(match)));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -111,7 +111,7 @@ public class RegexMatcher : IStringMatcher, IIgnoreCaseMatcher
|
||||
}
|
||||
}
|
||||
|
||||
return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score), exception);
|
||||
return MatchResult.From(Name, MatchBehaviourHelper.Convert(MatchBehaviour, score), exception);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -73,7 +73,7 @@ public class RequestMessageGraphQLMatcher : IRequestMatcher
|
||||
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
|
||||
{
|
||||
var results = CalculateMatchResults(requestMessage);
|
||||
var (score, exception) = MatchResult.From(results, MatchOperator).Expand();
|
||||
var (score, exception) = MatchResult.From(nameof(RequestMessageGraphQLMatcher), results, MatchOperator).Expand();
|
||||
|
||||
return requestMatchResult.AddScore(GetType(), score, exception);
|
||||
}
|
||||
@@ -86,12 +86,12 @@ public class RequestMessageGraphQLMatcher : IRequestMatcher
|
||||
return stringMatcher.IsMatch(requestMessage.BodyData.BodyAsString);
|
||||
}
|
||||
|
||||
return default;
|
||||
return MatchResult.From(nameof(RequestMessageGraphQLMatcher));
|
||||
}
|
||||
|
||||
private IReadOnlyList<MatchResult> CalculateMatchResults(IRequestMessage requestMessage)
|
||||
{
|
||||
return Matchers == null ? [new MatchResult()] : Matchers.Select(matcher => CalculateMatchResult(requestMessage, matcher)).ToArray();
|
||||
return Matchers == null ? [MatchResult.From(nameof(RequestMessageGraphQLMatcher))] : Matchers.Select(matcher => CalculateMatchResult(requestMessage, matcher)).ToArray();
|
||||
}
|
||||
|
||||
private static IMatcher[] CreateMatcherArray(
|
||||
|
||||
@@ -80,6 +80,14 @@ public interface IBodyRequestBuilder : IMultiPartRequestBuilder
|
||||
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
|
||||
IRequestBuilder WithBody(Func<object?, bool> func);
|
||||
|
||||
/// <summary>
|
||||
/// WithBody: func (type)
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type.</typeparam>
|
||||
/// <param name="func">The function.</param>
|
||||
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
|
||||
IRequestBuilder WithBodyAsType<T>(Func<T?, bool> func);
|
||||
|
||||
/// <summary>
|
||||
/// WithBody: func (BodyData object)
|
||||
/// </summary>
|
||||
|
||||
@@ -23,7 +23,7 @@ public interface IMultiPartRequestBuilder : IHttpVersionBuilder
|
||||
/// <param name="matchBehaviour">The <see cref="MatchBehaviour"/> to use.</param>
|
||||
/// <param name="matchOperator">The <see cref="MatchOperator"/> to use.</param>
|
||||
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
|
||||
IRequestBuilder WithMultiPart(IMatcher[] matchers, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch, MatchOperator matchOperator = MatchOperator.Or);
|
||||
IRequestBuilder WithMultiPart(IMatcher[] matchers, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch, MatchOperator matchOperator = MatchOperator.And);
|
||||
|
||||
/// <summary>
|
||||
/// WithMultiPart: MatchBehaviour and IMatcher[]
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using WireMock.Net.Testcontainers;
|
||||
|
||||
namespace DotNet.Testcontainers.Configurations;
|
||||
|
||||
internal static class HttpWaitStrategyExtensions
|
||||
{
|
||||
internal static HttpWaitStrategy WithBasicAuthentication(this HttpWaitStrategy strategy, WireMockConfiguration configuration)
|
||||
{
|
||||
if (configuration.HasBasicAuthentication)
|
||||
{
|
||||
return strategy.WithBasicAuthentication(configuration.Username, configuration.Password);
|
||||
}
|
||||
|
||||
return strategy;
|
||||
}
|
||||
}
|
||||
20
src/WireMock.Net.Testcontainers/Utils/CombineUtils.cs
Normal file
20
src/WireMock.Net.Testcontainers/Utils/CombineUtils.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace WireMock.Net.Testcontainers.Utils;
|
||||
|
||||
internal static class CombineUtils
|
||||
{
|
||||
internal static List<T> Combine<T>(List<T> oldValue, List<T> newValue)
|
||||
{
|
||||
return oldValue.Union(newValue).ToList();
|
||||
}
|
||||
|
||||
internal static Dictionary<TKey, TValue> Combine<TKey, TValue>(Dictionary<TKey, TValue> oldValue, Dictionary<TKey, TValue> newValue)
|
||||
where TKey : notnull
|
||||
{
|
||||
return oldValue.Union(newValue).ToDictionary(item => item.Key, item => item.Value);
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,7 @@
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Stef.Validation" Version="0.1.1" />
|
||||
<PackageReference Include="Testcontainers" Version="4.8.0" />
|
||||
<PackageReference Include="Testcontainers" Version="4.10.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Docker.DotNet.Models;
|
||||
using DotNet.Testcontainers.Builders;
|
||||
using DotNet.Testcontainers.Configurations;
|
||||
using JetBrains.Annotations;
|
||||
using Stef.Validation;
|
||||
using WireMock.Net.Testcontainers.Utils;
|
||||
|
||||
namespace WireMock.Net.Testcontainers;
|
||||
|
||||
@@ -77,8 +77,8 @@ public sealed class WireMockConfiguration : ContainerConfiguration
|
||||
StaticMappingsPath = BuildConfiguration.Combine(oldValue.StaticMappingsPath, newValue.StaticMappingsPath);
|
||||
WatchStaticMappings = BuildConfiguration.Combine(oldValue.WatchStaticMappings, newValue.WatchStaticMappings);
|
||||
WatchStaticMappingsInSubdirectories = BuildConfiguration.Combine(oldValue.WatchStaticMappingsInSubdirectories, newValue.WatchStaticMappingsInSubdirectories);
|
||||
AdditionalUrls = Combine(oldValue.AdditionalUrls, newValue.AdditionalUrls);
|
||||
ProtoDefinitions = Combine(oldValue.ProtoDefinitions, newValue.ProtoDefinitions);
|
||||
AdditionalUrls = CombineUtils.Combine(oldValue.AdditionalUrls, newValue.AdditionalUrls);
|
||||
ProtoDefinitions = CombineUtils.Combine(oldValue.ProtoDefinitions, newValue.ProtoDefinitions);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -130,16 +130,4 @@ public sealed class WireMockConfiguration : ContainerConfiguration
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private static List<T> Combine<T>(List<T> oldValue, List<T> newValue)
|
||||
{
|
||||
return oldValue.Concat(newValue).ToList();
|
||||
}
|
||||
|
||||
private static Dictionary<TKey, TValue> Combine<TKey, TValue>(Dictionary<TKey, TValue> oldValue, Dictionary<TKey, TValue> newValue)
|
||||
{
|
||||
return newValue
|
||||
.Concat(oldValue.Where(item => !newValue.Keys.Contains(item.Key)))
|
||||
.ToDictionary(item => item.Key, item => item.Value);
|
||||
}
|
||||
}
|
||||
@@ -24,28 +24,21 @@ namespace WireMock.Net.Testcontainers;
|
||||
/// <summary>
|
||||
/// A container for running WireMock in a docker environment.
|
||||
/// </summary>
|
||||
public sealed class WireMockContainer : DockerContainer
|
||||
/// <remarks>
|
||||
/// Initializes a new instance of the <see cref="WireMockContainer" /> class.
|
||||
/// </remarks>
|
||||
/// <param name="configuration">The container configuration.</param>
|
||||
public sealed class WireMockContainer(WireMockConfiguration configuration) : DockerContainer(configuration)
|
||||
{
|
||||
private const int EnhancedFileSystemWatcherTimeoutMs = 2000;
|
||||
internal const int ContainerPort = 80;
|
||||
|
||||
private readonly WireMockConfiguration _configuration;
|
||||
private readonly WireMockConfiguration _configuration = Guard.NotNull(configuration);
|
||||
|
||||
private IWireMockAdminApi? _adminApi;
|
||||
private EnhancedFileSystemWatcher? _enhancedFileSystemWatcher;
|
||||
private IDictionary<int, Uri>? _publicUris;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="WireMockContainer" /> class.
|
||||
/// </summary>
|
||||
/// <param name="configuration">The container configuration.</param>
|
||||
public WireMockContainer(WireMockConfiguration configuration) : base(configuration)
|
||||
{
|
||||
_configuration = Guard.NotNull(configuration);
|
||||
|
||||
Started += async (sender, eventArgs) => await WireMockContainerStartedAsync(sender, eventArgs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the public Url.
|
||||
/// </summary>
|
||||
@@ -156,14 +149,29 @@ public sealed class WireMockContainer : DockerContainer
|
||||
|
||||
try
|
||||
{
|
||||
await _adminApi.ReloadStaticMappingsAsync(cancellationToken);
|
||||
var result = await _adminApi.ReloadStaticMappingsAsync(cancellationToken);
|
||||
Logger.LogInformation("WireMock.Net -> ReloadStaticMappings result: {Result}", result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogWarning(ex, "Error calling /__admin/mappings/reloadStaticMappings");
|
||||
Logger.LogWarning(ex, "WireMock.Net -> Error calling /__admin/mappings/reloadStaticMappings");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs additional actions after the container is ready.
|
||||
/// </summary>
|
||||
public Task CallAdditionalActionsAfterReadyAsync()
|
||||
{
|
||||
Logger.LogInformation("WireMock.Net -> Calling additional actions.");
|
||||
|
||||
_adminApi = CreateWireMockAdminClient();
|
||||
|
||||
RegisterEnhancedFileSystemWatcher();
|
||||
|
||||
return AddProtoDefinitionsAsync();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override ValueTask DisposeAsyncCore()
|
||||
{
|
||||
@@ -196,15 +204,6 @@ public sealed class WireMockContainer : DockerContainer
|
||||
}
|
||||
}
|
||||
|
||||
private async Task WireMockContainerStartedAsync(object sender, EventArgs e)
|
||||
{
|
||||
_adminApi = CreateWireMockAdminClient();
|
||||
|
||||
RegisterEnhancedFileSystemWatcher();
|
||||
|
||||
await CallAdditionalActionsAfterStartedAsync();
|
||||
}
|
||||
|
||||
private void RegisterEnhancedFileSystemWatcher()
|
||||
{
|
||||
if (!_configuration.WatchStaticMappings || string.IsNullOrEmpty(_configuration.StaticMappingsPath))
|
||||
@@ -222,23 +221,31 @@ public sealed class WireMockContainer : DockerContainer
|
||||
_enhancedFileSystemWatcher.EnableRaisingEvents = true;
|
||||
}
|
||||
|
||||
private async Task CallAdditionalActionsAfterStartedAsync()
|
||||
private async Task AddProtoDefinitionsAsync()
|
||||
{
|
||||
foreach (var kvp in _configuration.ProtoDefinitions)
|
||||
{
|
||||
Logger.LogInformation("Adding ProtoDefinition {Id}", kvp.Key);
|
||||
Logger.LogInformation("WireMock.Net -> Adding ProtoDefinition '{Id}'", kvp.Key);
|
||||
|
||||
foreach (var protoDefinition in kvp.Value)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _adminApi!.AddProtoDefinitionAsync(kvp.Key, protoDefinition);
|
||||
var result = await _adminApi!.AddProtoDefinitionAsync(kvp.Key, protoDefinition);
|
||||
Logger.LogInformation("WireMock.Net -> AddProtoDefinition '{Id}' result: {Result}", kvp.Key, result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogWarning(ex, "Error adding ProtoDefinition '{Id}'.", kvp.Key);
|
||||
Logger.LogWarning(ex, "WireMock.Net -> Error adding ProtoDefinition '{Id}'.", kvp.Key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Force a reload of static mappings when ProtoDefinitions are added at server-level to fix #1382
|
||||
if (_configuration.ProtoDefinitions.Count > 0)
|
||||
{
|
||||
await ReloadStaticMappingsAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private async void FileCreatedChangedOrDeleted(object sender, FileSystemEventArgs args)
|
||||
@@ -246,16 +253,17 @@ public sealed class WireMockContainer : DockerContainer
|
||||
try
|
||||
{
|
||||
await ReloadStaticMappingsAsync(args.FullPath);
|
||||
Logger.LogInformation("WireMock.Net -> ReloadStaticMappings triggered from file change: '{FullPath}'.", args.FullPath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogWarning(ex, "Error reloading static mappings from '{FullPath}'.", args.FullPath);
|
||||
Logger.LogWarning(ex, "WireMock.Net -> Error reloading static mappings from '{FullPath}'.", args.FullPath);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ReloadStaticMappingsAsync(string path, CancellationToken cancellationToken = default)
|
||||
{
|
||||
Logger.LogInformation("MappingFile created, changed or deleted: '{Path}'. Triggering ReloadStaticMappings.", path);
|
||||
Logger.LogInformation("WireMock.Net -> MappingFile created, changed or deleted: '{Path}'. Triggering ReloadStaticMappings.", path);
|
||||
await ReloadStaticMappingsAsync(cancellationToken);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Runtime.InteropServices;
|
||||
using Docker.DotNet.Models;
|
||||
using DotNet.Testcontainers.Builders;
|
||||
@@ -62,6 +64,29 @@ public sealed class WireMockContainerBuilder : ContainerBuilder<WireMockContaine
|
||||
return WithImage(OSPlatform.Windows);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a custom WireMock.Net image for which to create the container.
|
||||
/// </summary>
|
||||
/// <param name="image">The image name.</param>
|
||||
/// <returns>A configured instance of <see cref="WireMockContainerBuilder"/></returns>
|
||||
[PublicAPI]
|
||||
public new WireMockContainerBuilder WithImage(string image)
|
||||
{
|
||||
return WithCustomImage(image);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a custom WireMock.Net image for which to create the container.
|
||||
/// </summary>
|
||||
/// <param name="image">The image name.</param>
|
||||
/// <returns>A configured instance of <see cref="WireMockContainerBuilder"/></returns>
|
||||
[PublicAPI]
|
||||
public WireMockContainerBuilder WithCustomImage(string image)
|
||||
{
|
||||
_imageOS ??= TestcontainersUtils.GetImageOSAsync.Value.GetAwaiter().GetResult();
|
||||
return base.WithImage(image);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the admin username and password for the container (basic authentication).
|
||||
/// </summary>
|
||||
@@ -112,6 +137,7 @@ public sealed class WireMockContainerBuilder : ContainerBuilder<WireMockContaine
|
||||
{
|
||||
DockerResourceConfiguration.WithWatchStaticMappings(includeSubDirectories);
|
||||
return
|
||||
WithCommand("--ReadStaticMappings true").
|
||||
WithCommand("--WatchStaticMappings true").
|
||||
WithCommand("--WatchStaticMappingsInSubdirectories", includeSubDirectories);
|
||||
}
|
||||
@@ -129,9 +155,7 @@ public sealed class WireMockContainerBuilder : ContainerBuilder<WireMockContaine
|
||||
|
||||
DockerResourceConfiguration.WithStaticMappingsPath(path);
|
||||
|
||||
return
|
||||
WithReadStaticMappings().
|
||||
WithCommand("--WatchStaticMappingsInSubdirectories", includeSubDirectories);
|
||||
return WithWatchStaticMappings(includeSubDirectories);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -208,9 +232,9 @@ public sealed class WireMockContainerBuilder : ContainerBuilder<WireMockContaine
|
||||
// In case the _imageOS is not set, determine it from the Image FullName.
|
||||
if (_imageOS == null)
|
||||
{
|
||||
if (builder.DockerResourceConfiguration.Image.FullName.IndexOf("wiremock.net", StringComparison.OrdinalIgnoreCase) < 0)
|
||||
if (builder.DockerResourceConfiguration.Image.FullName.IndexOf("wiremock", StringComparison.OrdinalIgnoreCase) < 0)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
throw new InvalidOperationException("It's only possible to use a wiremock docker image.");
|
||||
}
|
||||
|
||||
_imageOS = builder.DockerResourceConfiguration.Image.FullName.IndexOf("windows", StringComparison.OrdinalIgnoreCase) >= 0 ? OSPlatform.Windows : OSPlatform.Linux;
|
||||
@@ -228,6 +252,25 @@ public sealed class WireMockContainerBuilder : ContainerBuilder<WireMockContaine
|
||||
|
||||
builder.Validate();
|
||||
|
||||
var waitForContainerOS = _imageOS == OSPlatform.Windows ? Wait.ForWindowsContainer() : Wait.ForUnixContainer();
|
||||
builder = builder
|
||||
.WithWaitStrategy(waitForContainerOS
|
||||
.UntilMessageIsLogged("WireMock.Net server running", waitStrategy => waitStrategy.WithTimeout(TimeSpan.FromSeconds(30)))
|
||||
.UntilHttpRequestIsSucceeded(httpWaitStrategy => httpWaitStrategy
|
||||
.ForPort(WireMockContainer.ContainerPort)
|
||||
.WithMethod(HttpMethod.Get)
|
||||
.WithBasicAuthentication(DockerResourceConfiguration)
|
||||
.ForPath("/__admin/health")
|
||||
.ForStatusCode(HttpStatusCode.OK)
|
||||
.ForResponseMessageMatching(async httpResponseMessage =>
|
||||
{
|
||||
var content = await httpResponseMessage.Content.ReadAsStringAsync();
|
||||
return content?.Contains("Healthy") == true;
|
||||
})
|
||||
)
|
||||
.AddCustomWaitStrategy(new WireMockWaitStrategy())
|
||||
);
|
||||
|
||||
return new WireMockContainer(builder.DockerResourceConfiguration);
|
||||
}
|
||||
|
||||
@@ -236,11 +279,9 @@ public sealed class WireMockContainerBuilder : ContainerBuilder<WireMockContaine
|
||||
{
|
||||
var builder = base.Init();
|
||||
|
||||
var waitForContainerOS = _imageOS == OSPlatform.Windows ? Wait.ForWindowsContainer() : Wait.ForUnixContainer();
|
||||
return builder
|
||||
.WithPortBinding(WireMockContainer.ContainerPort, true)
|
||||
.WithCommand($"--WireMockLogger {DefaultLogger}")
|
||||
.WithWaitStrategy(waitForContainerOS.UntilMessageIsLogged("WireMock.Net server running"));
|
||||
.WithCommand($"--WireMockLogger {DefaultLogger}");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
23
src/WireMock.Net.Testcontainers/WireMockWaitStrategy.cs
Normal file
23
src/WireMock.Net.Testcontainers/WireMockWaitStrategy.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using DotNet.Testcontainers.Configurations;
|
||||
using DotNet.Testcontainers.Containers;
|
||||
|
||||
namespace WireMock.Net.Testcontainers;
|
||||
|
||||
internal class WireMockWaitStrategy : IWaitUntil
|
||||
{
|
||||
public async Task<bool> UntilAsync(IContainer container)
|
||||
{
|
||||
if (container is not WireMockContainer wireMockContainer)
|
||||
{
|
||||
throw new InvalidOperationException("The passed container is not a WireMockContainer.");
|
||||
|
||||
}
|
||||
|
||||
await wireMockContainer.CallAdditionalActionsAfterReadyAsync();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user