mirror of
https://github.com/wiremock/WireMock.Net.git
synced 2026-04-27 19:27:42 +02:00
Compare commits
18 Commits
1.15.0
...
1341-mappi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
197a211a52 | ||
|
|
3cfeec6035 | ||
|
|
b57d5e7548 | ||
|
|
36b89afce5 | ||
|
|
e2acac55a4 | ||
|
|
ceabd27ce0 | ||
|
|
f8e2c7ee90 | ||
|
|
c25d8f33d2 | ||
|
|
6da190e596 | ||
|
|
44388ce80d | ||
|
|
5e25ca767d | ||
|
|
0cc583a4a3 | ||
|
|
f9633adac1 | ||
|
|
37bad618a3 | ||
|
|
8e69f36f04 | ||
|
|
21601889e0 | ||
|
|
dfeabf228e | ||
|
|
1feb0ade70 |
26
CHANGELOG.md
26
CHANGELOG.md
@@ -1,3 +1,29 @@
|
|||||||
|
# 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)
|
# 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)
|
- [#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)
|
- [#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>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Version>1.15.0</Version>
|
<VersionPrefix>1.19.0</VersionPrefix>
|
||||||
<PackageIcon>WireMock.Net-Logo.png</PackageIcon>
|
<PackageIcon>WireMock.Net-Logo.png</PackageIcon>
|
||||||
<PackageProjectUrl>https://github.com/wiremock/WireMock.Net</PackageProjectUrl>
|
<PackageProjectUrl>https://github.com/wiremock/WireMock.Net</PackageProjectUrl>
|
||||||
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
|
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
rem https://github.com/StefH/GitHubReleaseNotes
|
rem https://github.com/StefH/GitHubReleaseNotes
|
||||||
|
|
||||||
SET version=1.15.0
|
SET version=1.19.0
|
||||||
|
|
||||||
GitHubReleaseNotes --output CHANGELOG.md --skip-empty-releases --exclude-labels wontfix test question invalid doc duplicate example environment --version %version% --token %GH_TOKEN%
|
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
|
### :star: Stubbing
|
||||||
A core feature of WireMock.Net is the ability to return predefined HTTP responses for requests matching criteria.
|
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
|
### :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
|
### :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
|
### :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
|
### :star: Using
|
||||||
WireMock.Net can be used in several ways:
|
WireMock.Net can be used in several ways:
|
||||||
|
|
||||||
#### UnitTesting
|
#### UnitTesting
|
||||||
You can use your favorite test framework and use WireMock within your tests, see
|
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
|
### 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
|
### 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
|
#### 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
|
#### 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
|
#### 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
|
#### 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
|
#### In a docker container
|
||||||
There is also a Linux and Windows-Nano container available at [hub.docker.com](https://hub.docker.com/r/sheyenrath).
|
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).
|
For more details see also [Docker](https://github.com/wiremock/WireMock.Net-docker).
|
||||||
|
|
||||||
#### HTTPS / SSL
|
#### 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
|
## :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,6 @@
|
|||||||
# 1.15.0 (22 October 2025)
|
# 1.19.0 (12 December 2025)
|
||||||
- #1367 Fix WithProbability logic [bug]
|
- #1391 Update WireMockContainerBuilder (WithImage and WithCustomImage) [feature]
|
||||||
- #1370 Support Testcontainers 4.8.0 [bug]
|
- #1392 WireMockContainerBuilder: allow all docker images named wiremock [feature]
|
||||||
- #1126 Request matching WithProbability strange behaviour [bug]
|
- #1390 Unable to build WireMockContainerBuilder with custom image [feature]
|
||||||
|
|
||||||
The full release notes can be found here: https://github.com/wiremock/WireMock.Net/blob/master/CHANGELOG.md
|
The full release notes can be found here: https://github.com/wiremock/WireMock.Net/blob/master/CHANGELOG.md
|
||||||
38
README.md
38
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) |
|
| **Issues** | [](https://github.com/wiremock/WireMock.Net/issues) |
|
||||||
| | |
|
| | |
|
||||||
| ***Quality*** | |
|
| ***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) |
|
| **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) |
|
| **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)|
|
| **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
|
### :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** | [](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)
|
| **WireMock.Net.Minimal** 🔺| [](https://www.nuget.org/packages/WireMock.Net.Minimal) | [](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.Minimal)
|
||||||
@@ -53,6 +53,7 @@ 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.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.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** | [](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.TUnit** | [](https://www.nuget.org/packages/WireMock.Net.TUnit) | [](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.TUnit)
|
||||||
| | | |
|
| | | |
|
||||||
| **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.Extensions.Routing** | [](https://www.nuget.org/packages/WireMock.Net.Extensions.Routing) | [](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.Extensions.Routing)
|
||||||
@@ -93,52 +94,55 @@ To still enable this feature, you need to add the `Environment` category to the
|
|||||||
---
|
---
|
||||||
|
|
||||||
## :memo: Development
|
## :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
|
## :star: Stubbing
|
||||||
A core feature of WireMock.Net is the ability to return predefined HTTP responses for requests matching criteria.
|
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
|
## :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
|
## :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
|
## :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
|
## :star: Using
|
||||||
WireMock.Net can be used in several ways:
|
WireMock.Net can be used in several ways:
|
||||||
|
|
||||||
### UnitTesting
|
### UnitTesting
|
||||||
You can use your favorite test framework and use WireMock within your tests, see
|
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
|
### 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
|
### 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
|
### 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
|
### 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
|
### 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
|
### 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
|
### In a docker container
|
||||||
There is also a Linux and Windows-Nano container available at [hub.docker.com](https://hub.docker.com/r/sheyenrath).
|
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).
|
For more details see also [Docker](https://github.com/wiremock/WireMock.Net-docker).
|
||||||
|
|
||||||
#### HTTPS / SSL
|
### 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 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
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
# Visual Studio Version 17
|
# Visual Studio Version 18
|
||||||
VisualStudioVersion = 17.0.31521.260
|
VisualStudioVersion = 18.0.11205.157 d18.0
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{8F890C6F-9ACC-438D-928A-AD61CDA862F2}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{8F890C6F-9ACC-438D-928A-AD61CDA862F2}"
|
||||||
EndProject
|
EndProject
|
||||||
@@ -144,6 +144,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.Extensions.Rou
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.ProtoBuf", "src\WireMock.Net.ProtoBuf\WireMock.Net.ProtoBuf.csproj", "{B47413AA-55D3-49A7-896A-17ADBFF72407}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.ProtoBuf", "src\WireMock.Net.ProtoBuf\WireMock.Net.ProtoBuf.csproj", "{B47413AA-55D3-49A7-896A-17ADBFF72407}"
|
||||||
EndProject
|
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
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@@ -350,6 +352,10 @@ Global
|
|||||||
{B47413AA-55D3-49A7-896A-17ADBFF72407}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{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.ActiveCfg = Release|Any CPU
|
||||||
{B47413AA-55D3-49A7-896A-17ADBFF72407}.Release|Any CPU.Build.0 = 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
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
@@ -407,6 +413,7 @@ Global
|
|||||||
{3FCBCA9C-9DB0-4A96-B47E-30470764CC9C} = {0BB8B634-407A-4610-A91F-11586990767A}
|
{3FCBCA9C-9DB0-4A96-B47E-30470764CC9C} = {0BB8B634-407A-4610-A91F-11586990767A}
|
||||||
{1E874C8F-08A2-493B-8421-619F9A6E9E77} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2}
|
{1E874C8F-08A2-493B-8421-619F9A6E9E77} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2}
|
||||||
{B47413AA-55D3-49A7-896A-17ADBFF72407} = {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}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {DC539027-9852-430C-B19F-FD035D018458}
|
SolutionGuid = {DC539027-9852-430C-B19F-FD035D018458}
|
||||||
|
|||||||
@@ -144,6 +144,10 @@ jobs:
|
|||||||
vmImage: 'windows-2022'
|
vmImage: 'windows-2022'
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
- script: |
|
||||||
|
echo "BuildId = $(buildId)"
|
||||||
|
displayName: 'Print buildId'
|
||||||
|
|
||||||
- task: UseDotNet@2
|
- task: UseDotNet@2
|
||||||
displayName: Use .NET 8.0
|
displayName: Use .NET 8.0
|
||||||
inputs:
|
inputs:
|
||||||
|
|||||||
@@ -21,4 +21,13 @@
|
|||||||
<PackageReference Include="Aspire.Hosting.AppHost" Version="9.2.0" />
|
<PackageReference Include="Aspire.Hosting.AppHost" Version="9.2.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Update="__admin\mappings\*.proto">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
<None Update="__admin\mappings\*.json">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
@@ -4,12 +4,25 @@ var builder = DistributedApplication.CreateBuilder(args);
|
|||||||
|
|
||||||
// IResourceBuilder<ProjectResource> apiService = builder.AddProject<Projects.AspireApp1_ApiService>("apiservice");
|
// 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
|
//IResourceBuilder<WireMockServerResource> apiService1 = builder
|
||||||
.AddWireMock("apiservice", WireMockServerArguments.DefaultPort)
|
// //.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)
|
.WithMappingsPath(mappingsPath)
|
||||||
.WithReadStaticMappings()
|
|
||||||
.WithWatchStaticMappings()
|
.WithWatchStaticMappings()
|
||||||
.WithApiMappingBuilder(WeatherForecastApiMock.BuildAsync);
|
.WithApiMappingBuilder(WeatherForecastApiMock.BuildAsync);
|
||||||
|
|
||||||
@@ -45,6 +58,7 @@ IResourceBuilder<WireMockServerResource> apiService = builder
|
|||||||
|
|
||||||
builder.AddProject<Projects.AspireApp1_Web>("webfrontend")
|
builder.AddProject<Projects.AspireApp1_Web>("webfrontend")
|
||||||
.WithExternalHttpEndpoints()
|
.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"
|
||||||
|
}
|
||||||
@@ -22,7 +22,7 @@ public class MatcherModel
|
|||||||
public object? Pattern { get; set; }
|
public object? Pattern { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||||
public object[]? Patterns { get; set; }
|
public object[]? Patterns { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -24,4 +24,13 @@ public class StatusModel
|
|||||||
/// The error message.
|
/// The error message.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string? Error { get; set; }
|
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
|
// Copyright © WireMock.Net
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using WireMock.Validators;
|
||||||
|
|
||||||
// ReSharper disable once CheckNamespace
|
// ReSharper disable once CheckNamespace
|
||||||
namespace WireMock.Admin.Mappings;
|
namespace WireMock.Admin.Mappings;
|
||||||
@@ -94,9 +95,14 @@ public partial class RequestModelBuilder
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Set the Path.
|
/// Set the Path. Must start with a forward slash (/).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public RequestModelBuilder WithPath(string value) => WithPath(() => value);
|
public RequestModelBuilder WithPath(string value)
|
||||||
|
{
|
||||||
|
PathValidator.ValidateAndThrow(value);
|
||||||
|
|
||||||
|
return WithPath(() => value);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Set the Path.
|
/// Set the Path.
|
||||||
|
|||||||
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>
|
||||||
|
|
||||||
<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>
|
</ItemGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)' == 'Debug - Sonar'">
|
<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
|
// Copyright © WireMock.Net
|
||||||
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using Stef.Validation;
|
||||||
using WireMock.Client.Builders;
|
using WireMock.Client.Builders;
|
||||||
|
using WireMock.Util;
|
||||||
|
|
||||||
// ReSharper disable once CheckNamespace
|
// ReSharper disable once CheckNamespace
|
||||||
namespace Aspire.Hosting;
|
namespace Aspire.Hosting;
|
||||||
@@ -21,10 +23,15 @@ public class WireMockServerArguments
|
|||||||
private const string DefaultLogger = "WireMockConsoleLogger";
|
private const string DefaultLogger = "WireMockConsoleLogger";
|
||||||
|
|
||||||
/// <summary>
|
/// <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.
|
/// If not defined, .NET Aspire automatically assigns a random port.
|
||||||
/// </summary>
|
/// </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>
|
/// <summary>
|
||||||
/// The admin username.
|
/// The admin username.
|
||||||
@@ -67,6 +74,42 @@ public class WireMockServerArguments
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public Func<AdminApiMappingBuilder, CancellationToken, Task>? ApiMappingBuilder { get; set; }
|
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>
|
/// <summary>
|
||||||
/// Converts the current instance's properties to an array of command-line arguments for starting the WireMock.Net server.
|
/// Converts the current instance's properties to an array of command-line arguments for starting the WireMock.Net server.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -95,6 +138,11 @@ public class WireMockServerArguments
|
|||||||
Add(args, "--WatchStaticMappingsInSubdirectories", "true");
|
Add(args, "--WatchStaticMappingsInSubdirectories", "true");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (AdditionalUrls.Count > 0)
|
||||||
|
{
|
||||||
|
Add(args, "--Urls", $"http://*:{HttpContainerPort} {string.Join(' ', AdditionalUrls)}");
|
||||||
|
}
|
||||||
|
|
||||||
return args
|
return args
|
||||||
.SelectMany(k => new[] { k.Key, k.Value })
|
.SelectMany(k => new[] { k.Key, k.Value })
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|||||||
@@ -3,10 +3,12 @@
|
|||||||
using Aspire.Hosting.ApplicationModel;
|
using Aspire.Hosting.ApplicationModel;
|
||||||
using Aspire.Hosting.Lifecycle;
|
using Aspire.Hosting.Lifecycle;
|
||||||
using Aspire.Hosting.WireMock;
|
using Aspire.Hosting.WireMock;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
||||||
using Stef.Validation;
|
using Stef.Validation;
|
||||||
using WireMock.Client.Builders;
|
using WireMock.Client.Builders;
|
||||||
using WireMock.Net.Aspire;
|
using WireMock.Net.Aspire;
|
||||||
|
using WireMock.Util;
|
||||||
|
|
||||||
// ReSharper disable once CheckNamespace
|
// ReSharper disable once CheckNamespace
|
||||||
namespace Aspire.Hosting;
|
namespace Aspire.Hosting;
|
||||||
@@ -33,9 +35,31 @@ public static class WireMockServerBuilderExtensions
|
|||||||
Guard.NotNullOrWhiteSpace(name);
|
Guard.NotNullOrWhiteSpace(name);
|
||||||
Guard.Condition(port, p => p is null or > 0 and <= ushort.MaxValue);
|
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);
|
Guard.NotNull(arguments);
|
||||||
|
|
||||||
var wireMockContainerResource = new WireMockServerResource(name, 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
|
var resourceBuilder = builder
|
||||||
.AddResource(wireMockContainerResource)
|
.AddResource(wireMockContainerResource)
|
||||||
.WithImage(DefaultLinuxImage)
|
.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
|
.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();
|
.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))
|
if (!string.IsNullOrEmpty(arguments.MappingsPath))
|
||||||
{
|
{
|
||||||
resourceBuilder = resourceBuilder.WithBindMount(arguments.MappingsPath, DefaultLinuxMappingsPath);
|
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;
|
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="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>
|
/// <param name="callback">A callback that allows for setting the <see cref="WireMockServerArguments"/>.</param>
|
||||||
/// <returns>A reference to the <see cref="IResourceBuilder{WireMockServerResource}"/>.</returns>
|
/// <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.NotNull(builder);
|
||||||
Guard.NotNullOrWhiteSpace(name);
|
Guard.NotNullOrWhiteSpace(name);
|
||||||
@@ -154,7 +221,7 @@ public static class WireMockServerBuilderExtensions
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="wiremock">The <see cref="IResourceBuilder{WireMockServerResource}"/>.</param>
|
/// <param name="wiremock">The <see cref="IResourceBuilder{WireMockServerResource}"/>.</param>
|
||||||
/// <param name="configure">Delegate that will be invoked to configure the WireMock.Net resource.</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)
|
public static IResourceBuilder<WireMockServerResource> WithApiMappingBuilder(this IResourceBuilder<WireMockServerResource> wiremock, Func<AdminApiMappingBuilder, Task> configure)
|
||||||
{
|
{
|
||||||
return wiremock.WithApiMappingBuilder((adminApiMappingBuilder, _) => configure.Invoke(adminApiMappingBuilder));
|
return wiremock.WithApiMappingBuilder((adminApiMappingBuilder, _) => configure.Invoke(adminApiMappingBuilder));
|
||||||
@@ -165,13 +232,27 @@ public static class WireMockServerBuilderExtensions
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="wiremock">The <see cref="IResourceBuilder{WireMockServerResource}"/>.</param>
|
/// <param name="wiremock">The <see cref="IResourceBuilder{WireMockServerResource}"/>.</param>
|
||||||
/// <param name="configure">Delegate that will be invoked to configure the WireMock.Net resource.</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)
|
public static IResourceBuilder<WireMockServerResource> WithApiMappingBuilder(this IResourceBuilder<WireMockServerResource> wiremock, Func<AdminApiMappingBuilder, CancellationToken, Task> configure)
|
||||||
{
|
{
|
||||||
Guard.NotNull(wiremock);
|
Guard.NotNull(wiremock);
|
||||||
|
|
||||||
wiremock.ApplicationBuilder.Services.TryAddLifecycleHook<WireMockServerLifecycleHook>();
|
|
||||||
wiremock.Resource.Arguments.ApiMappingBuilder = configure;
|
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;
|
return wiremock;
|
||||||
}
|
}
|
||||||
@@ -183,11 +264,11 @@ public static class WireMockServerBuilderExtensions
|
|||||||
/// dotnet tool install WireMockInspector --global --no-cache --ignore-failed-sources
|
/// dotnet tool install WireMockInspector --global --no-cache --ignore-failed-sources
|
||||||
/// </code>
|
/// </code>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="builder">The <see cref="IResourceBuilder{WireMockNetResource}"/>.</param>
|
/// <param name="wiremock">The <see cref="IResourceBuilder{WireMockNetResource}"/>.</param>
|
||||||
/// <returns></returns>
|
/// <returns>A reference to the <see cref="IResourceBuilder{WireMockServerResource}"/>.</returns>
|
||||||
public static IResourceBuilder<WireMockServerResource> WithWireMockInspectorCommand(this IResourceBuilder<WireMockServerResource> builder)
|
public static IResourceBuilder<WireMockServerResource> WithWireMockInspectorCommand(this IResourceBuilder<WireMockServerResource> wiremock)
|
||||||
{
|
{
|
||||||
Guard.NotNull(builder);
|
Guard.NotNull(wiremock);
|
||||||
|
|
||||||
CommandOptions commandOptions = new()
|
CommandOptions commandOptions = new()
|
||||||
{
|
{
|
||||||
@@ -197,13 +278,13 @@ public static class WireMockServerBuilderExtensions
|
|||||||
IconVariant = IconVariant.Filled
|
IconVariant = IconVariant.Filled
|
||||||
};
|
};
|
||||||
|
|
||||||
builder.WithCommand(
|
wiremock.WithCommand(
|
||||||
name: "wiremock-inspector",
|
name: "wiremock-inspector",
|
||||||
displayName: "WireMock Inspector",
|
displayName: "WireMock Inspector",
|
||||||
executeCommand: _ => OnRunOpenInspectorCommandAsync(builder),
|
executeCommand: _ => OnRunOpenInspectorCommandAsync(wiremock),
|
||||||
commandOptions: commandOptions);
|
commandOptions: commandOptions);
|
||||||
|
|
||||||
return builder;
|
return wiremock;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Task<ExecuteCommandResult> OnRunOpenInspectorCommandAsync(IResourceBuilder<WireMockServerResource> builder)
|
private static Task<ExecuteCommandResult> OnRunOpenInspectorCommandAsync(IResourceBuilder<WireMockServerResource> builder)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Copyright © WireMock.Net
|
// Copyright © WireMock.Net
|
||||||
|
|
||||||
|
using System.Diagnostics;
|
||||||
using Aspire.Hosting.ApplicationModel;
|
using Aspire.Hosting.ApplicationModel;
|
||||||
using Aspire.Hosting.Lifecycle;
|
using Aspire.Hosting.Lifecycle;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
@@ -10,10 +11,15 @@ internal class WireMockServerLifecycleHook(ILoggerFactory loggerFactory) : IDist
|
|||||||
{
|
{
|
||||||
private readonly CancellationTokenSource _shutdownCts = new();
|
private readonly CancellationTokenSource _shutdownCts = new();
|
||||||
|
|
||||||
public async Task AfterResourcesCreatedAsync(DistributedApplicationModel appModel, CancellationToken cancellationToken = default)
|
private CancellationTokenSource? _linkedCts;
|
||||||
{
|
private Task? _mappingTask;
|
||||||
var cts = CancellationTokenSource.CreateLinkedTokenSource(_shutdownCts.Token, cancellationToken);
|
|
||||||
|
|
||||||
|
public Task AfterEndpointsAllocatedAsync(DistributedApplicationModel appModel, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
_linkedCts = CancellationTokenSource.CreateLinkedTokenSource(_shutdownCts.Token, cancellationToken);
|
||||||
|
|
||||||
|
_mappingTask = Task.Run(async () =>
|
||||||
|
{
|
||||||
var wireMockServerResources = appModel.Resources
|
var wireMockServerResources = appModel.Resources
|
||||||
.OfType<WireMockServerResource>()
|
.OfType<WireMockServerResource>()
|
||||||
.ToArray();
|
.ToArray();
|
||||||
@@ -23,19 +29,31 @@ internal class WireMockServerLifecycleHook(ILoggerFactory loggerFactory) : IDist
|
|||||||
wireMockServerResource.SetLogger(loggerFactory.CreateLogger<WireMockServerResource>());
|
wireMockServerResource.SetLogger(loggerFactory.CreateLogger<WireMockServerResource>());
|
||||||
|
|
||||||
var endpoint = wireMockServerResource.GetEndpoint();
|
var endpoint = wireMockServerResource.GetEndpoint();
|
||||||
if (endpoint.IsAllocated)
|
Debug.Assert(endpoint.IsAllocated);
|
||||||
{
|
|
||||||
await wireMockServerResource.WaitForHealthAsync(cts.Token);
|
|
||||||
|
|
||||||
await wireMockServerResource.CallApiMappingBuilderActionAsync(cts.Token);
|
await wireMockServerResource.WaitForHealthAsync(_linkedCts.Token);
|
||||||
|
|
||||||
wireMockServerResource.StartWatchingStaticMappings(cts.Token);
|
await wireMockServerResource.CallAddProtoDefinitionsAsync(_linkedCts.Token);
|
||||||
}
|
|
||||||
|
await wireMockServerResource.CallApiMappingBuilderActionAsync(_linkedCts.Token);
|
||||||
|
|
||||||
|
wireMockServerResource.StartWatchingStaticMappings(_linkedCts.Token);
|
||||||
}
|
}
|
||||||
|
}, _linkedCts.Token);
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async ValueTask DisposeAsync()
|
public async ValueTask DisposeAsync()
|
||||||
{
|
{
|
||||||
await _shutdownCts.CancelAsync();
|
await _shutdownCts.CancelAsync();
|
||||||
|
|
||||||
|
_linkedCts?.Dispose();
|
||||||
|
_shutdownCts.Dispose();
|
||||||
|
|
||||||
|
if (_mappingTask is not null)
|
||||||
|
{
|
||||||
|
await _mappingTask;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,6 +5,7 @@ using RestEase;
|
|||||||
using Stef.Validation;
|
using Stef.Validation;
|
||||||
using WireMock.Client;
|
using WireMock.Client;
|
||||||
using WireMock.Client.Extensions;
|
using WireMock.Client.Extensions;
|
||||||
|
using WireMock.Net.Aspire;
|
||||||
using WireMock.Util;
|
using WireMock.Util;
|
||||||
|
|
||||||
// ReSharper disable once CheckNamespace
|
// ReSharper disable once CheckNamespace
|
||||||
@@ -19,6 +20,7 @@ public class WireMockServerResource : ContainerResource, IResourceWithServiceDis
|
|||||||
|
|
||||||
internal WireMockServerArguments Arguments { get; }
|
internal WireMockServerArguments Arguments { get; }
|
||||||
internal Lazy<IWireMockAdminApi> AdminApi => new(CreateWireMockAdminApi);
|
internal Lazy<IWireMockAdminApi> AdminApi => new(CreateWireMockAdminApi);
|
||||||
|
internal WireMockMappingState ApiMappingState { get; set; } = WireMockMappingState.NoMappings;
|
||||||
|
|
||||||
private ILogger? _logger;
|
private ILogger? _logger;
|
||||||
private EnhancedFileSystemWatcher? _enhancedFileSystemWatcher;
|
private EnhancedFileSystemWatcher? _enhancedFileSystemWatcher;
|
||||||
@@ -64,6 +66,36 @@ public class WireMockServerResource : ContainerResource, IResourceWithServiceDis
|
|||||||
|
|
||||||
var mappingBuilder = AdminApi.Value.GetMappingBuilder();
|
var mappingBuilder = AdminApi.Value.GetMappingBuilder();
|
||||||
await Arguments.ApiMappingBuilder.Invoke(mappingBuilder, cancellationToken);
|
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)
|
internal void StartWatchingStaticMappings(CancellationToken cancellationToken)
|
||||||
@@ -109,10 +141,17 @@ public class WireMockServerResource : ContainerResource, IResourceWithServiceDis
|
|||||||
|
|
||||||
private async void FileCreatedChangedOrDeleted(object sender, FileSystemEventArgs args)
|
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
|
try
|
||||||
{
|
{
|
||||||
await AdminApi.Value.ReloadStaticMappingsAsync();
|
var status = await AdminApi.Value.ReloadStaticMappingsAsync(cancellationToken);
|
||||||
|
_logger?.LogInformation("ReloadStaticMappings called with status: {Status}.", status);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
// 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
|
||||||
|
{
|
||||||
|
/// <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 MatchScores.ToScore(Func(bodyAsT));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return new MatchResult(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -34,13 +34,6 @@ public partial class Request
|
|||||||
return this;
|
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 />
|
/// <inheritdoc />
|
||||||
public IRequestBuilder WithBody(IMatcher matcher)
|
public IRequestBuilder WithBody(IMatcher matcher)
|
||||||
{
|
{
|
||||||
@@ -98,4 +91,20 @@ public partial class Request
|
|||||||
_requestMatchers.Add(new RequestMessageBodyMatcher(Guard.NotNull(func)));
|
_requestMatchers.Add(new RequestMessageBodyMatcher(Guard.NotNull(func)));
|
||||||
return this;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,7 @@ using System;
|
|||||||
using Stef.Validation;
|
using Stef.Validation;
|
||||||
using WireMock.Matchers;
|
using WireMock.Matchers;
|
||||||
using WireMock.Matchers.Request;
|
using WireMock.Matchers.Request;
|
||||||
|
using WireMock.Validators;
|
||||||
|
|
||||||
namespace WireMock.RequestBuilders;
|
namespace WireMock.RequestBuilders;
|
||||||
|
|
||||||
@@ -34,6 +35,10 @@ public partial class Request
|
|||||||
public IRequestBuilder WithPath(MatchOperator matchOperator, params string[] paths)
|
public IRequestBuilder WithPath(MatchOperator matchOperator, params string[] paths)
|
||||||
{
|
{
|
||||||
Guard.NotNullOrEmpty(paths);
|
Guard.NotNullOrEmpty(paths);
|
||||||
|
foreach (var path in paths)
|
||||||
|
{
|
||||||
|
PathValidator.ValidateAndThrow(path, nameof(paths));
|
||||||
|
}
|
||||||
|
|
||||||
_requestMatchers.Add(new RequestMessagePathMatcher(MatchBehaviour.AcceptOnMatch, matchOperator, paths));
|
_requestMatchers.Add(new RequestMessagePathMatcher(MatchBehaviour.AcceptOnMatch, matchOperator, paths));
|
||||||
return this;
|
return this;
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ public partial class Request : RequestMessageCompositeMatcher, IRequestBuilder
|
|||||||
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
|
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
|
||||||
public static IRequestBuilder Create()
|
public static IRequestBuilder Create()
|
||||||
{
|
{
|
||||||
return new Request(new List<IRequestMatcher>());
|
return new Request([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -226,14 +226,14 @@ internal class MappingConverter(MatcherMapper mapper)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.Delay is { })
|
if (response is { MinimumDelayMilliseconds: > 0, MaximumDelayMilliseconds: > 0 })
|
||||||
{
|
|
||||||
sb.AppendLine($" .WithDelay({response.Delay.Value.TotalMilliseconds})");
|
|
||||||
}
|
|
||||||
else if (response is { MinimumDelayMilliseconds: > 0, MaximumDelayMilliseconds: > 0 })
|
|
||||||
{
|
{
|
||||||
sb.AppendLine($" .WithRandomDelay({response.MinimumDelayMilliseconds}, {response.MaximumDelayMilliseconds})");
|
sb.AppendLine($" .WithRandomDelay({response.MinimumDelayMilliseconds}, {response.MaximumDelayMilliseconds})");
|
||||||
}
|
}
|
||||||
|
else if (response.Delay is { })
|
||||||
|
{
|
||||||
|
sb.AppendLine($" .WithDelay({response.Delay.Value.TotalMilliseconds})");
|
||||||
|
}
|
||||||
|
|
||||||
if (response.UseTransformer)
|
if (response.UseTransformer)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ internal class SimpleSettingsParser
|
|||||||
// Now also parse environment
|
// Now also parse environment
|
||||||
if (environment != null)
|
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))
|
if (key.StartsWith(Prefix, StringComparison.OrdinalIgnoreCase) && environment.TryGetStringValue(key, out var value))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ public static class WireMockServerSettingsParser
|
|||||||
|
|
||||||
if (parser.GetBoolSwitchValue("help"))
|
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;
|
settings = null;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -153,7 +153,7 @@ public static class WireMockServerSettingsParser
|
|||||||
}
|
}
|
||||||
else if (settings.HostingScheme is null)
|
else if (settings.HostingScheme is null)
|
||||||
{
|
{
|
||||||
settings.Urls = parser.GetValues("Urls", ["http://*:9091/"]);
|
settings.Urls = parser.GetValues(nameof(WireMockServerSettings.Urls), defaultValue: ["http://*:9091/"]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -84,22 +84,22 @@ internal static class PortUtils
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Extract the isHttps, isHttp2, protocol, host and port from a URL.
|
/// Extract the isHttps, isHttp2, scheme, host and port from a URL.
|
||||||
/// </summary>
|
/// </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;
|
isHttps = false;
|
||||||
isHttp2 = false;
|
isHttp2 = false;
|
||||||
protocol = null;
|
scheme = null;
|
||||||
host = null;
|
host = null;
|
||||||
port = 0;
|
port = 0;
|
||||||
|
|
||||||
var match = UrlDetailsRegex.Match(url);
|
var match = UrlDetailsRegex.Match(url);
|
||||||
if (match.Success)
|
if (match.Success)
|
||||||
{
|
{
|
||||||
protocol = match.Groups["proto"].Value;
|
scheme = match.Groups["proto"].Value;
|
||||||
isHttps = protocol.StartsWith("https", StringComparison.OrdinalIgnoreCase) || protocol.StartsWith("grpcs", StringComparison.OrdinalIgnoreCase);
|
isHttps = scheme.StartsWith("https", StringComparison.OrdinalIgnoreCase) || scheme.StartsWith("grpcs", StringComparison.OrdinalIgnoreCase);
|
||||||
isHttp2 = protocol.StartsWith("grpc", StringComparison.OrdinalIgnoreCase);
|
isHttp2 = scheme.StartsWith("grpc", StringComparison.OrdinalIgnoreCase);
|
||||||
host = match.Groups["host"].Value;
|
host = match.Groups["host"].Value;
|
||||||
|
|
||||||
return int.TryParse(match.Groups["port"].Value, out port);
|
return int.TryParse(match.Groups["port"].Value, out port);
|
||||||
|
|||||||
@@ -19,18 +19,12 @@ using SystemTextJsonSerializer = System.Text.Json.JsonSerializer;
|
|||||||
|
|
||||||
namespace WireMock.Net.OpenApiParser.Mappers;
|
namespace WireMock.Net.OpenApiParser.Mappers;
|
||||||
|
|
||||||
internal class OpenApiPathsMapper
|
internal class OpenApiPathsMapper(WireMockOpenApiParserSettings settings)
|
||||||
{
|
{
|
||||||
private const string HeaderContentType = "Content-Type";
|
private const string HeaderContentType = "Content-Type";
|
||||||
|
|
||||||
private readonly WireMockOpenApiParserSettings _settings;
|
private readonly WireMockOpenApiParserSettings _settings = Guard.NotNull(settings);
|
||||||
private readonly ExampleValueGenerator _exampleValueGenerator;
|
private readonly ExampleValueGenerator _exampleValueGenerator = new(settings);
|
||||||
|
|
||||||
public OpenApiPathsMapper(WireMockOpenApiParserSettings settings)
|
|
||||||
{
|
|
||||||
_settings = Guard.NotNull(settings);
|
|
||||||
_exampleValueGenerator = new ExampleValueGenerator(settings);
|
|
||||||
}
|
|
||||||
|
|
||||||
public IReadOnlyList<MappingModel> ToMappingModels(OpenApiPaths? paths, IList<OpenApiServer> servers)
|
public IReadOnlyList<MappingModel> ToMappingModels(OpenApiPaths? paths, IList<OpenApiServer> servers)
|
||||||
{
|
{
|
||||||
@@ -41,7 +35,7 @@ internal class OpenApiPathsMapper
|
|||||||
.ToArray() ?? [];
|
.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() ?? [];
|
return pathItem.Operations?.Select(o => MapOperationToMappingModel(path, o.Key.ToString().ToUpperInvariant(), o.Value, servers)).ToArray() ?? [];
|
||||||
}
|
}
|
||||||
@@ -50,35 +44,7 @@ internal class OpenApiPathsMapper
|
|||||||
{
|
{
|
||||||
var queryParameters = operation.Parameters?.Where(p => p.In == ParameterLocation.Query) ?? [];
|
var queryParameters = operation.Parameters?.Where(p => p.In == ParameterLocation.Query) ?? [];
|
||||||
var pathParameters = operation.Parameters?.Where(p => p.In == ParameterLocation.Path) ?? [];
|
var pathParameters = operation.Parameters?.Where(p => p.In == ParameterLocation.Path) ?? [];
|
||||||
var headers = operation.Parameters?.Where(p => p.In == ParameterLocation.Header) ?? [];
|
var requestHeaders = 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new MappingModel
|
return new MappingModel
|
||||||
{
|
{
|
||||||
@@ -88,15 +54,94 @@ internal class OpenApiPathsMapper
|
|||||||
Methods = [httpMethod],
|
Methods = [httpMethod],
|
||||||
Path = PathUtils.Combine(MapBasePath(servers), MapPathWithParameters(path, pathParameters)),
|
Path = PathUtils.Combine(MapBasePath(servers), MapPathWithParameters(path, pathParameters)),
|
||||||
Params = MapQueryParameters(queryParameters),
|
Params = MapQueryParameters(queryParameters),
|
||||||
Headers = MapRequestHeaders(headers),
|
Headers = MapRequestHeaders(requestHeaders),
|
||||||
Body = requestBodyModel
|
Body = GetRequestBodyModel(operation.RequestBody)
|
||||||
},
|
},
|
||||||
Response = new ResponseModel
|
Response = GetResponseModel(operation.Responses?.FirstOrDefault())
|
||||||
{
|
};
|
||||||
StatusCode = httpStatusCode,
|
|
||||||
Headers = MapHeaders(responseContentType, response.Value?.Headers),
|
|
||||||
BodyAsJson = responseBody != null ? JsonConvert.DeserializeObject(SystemTextJsonSerializer.Serialize(responseBody)) : null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -80,6 +80,14 @@ public interface IBodyRequestBuilder : IMultiPartRequestBuilder
|
|||||||
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
|
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
|
||||||
IRequestBuilder WithBody(Func<object?, bool> func);
|
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>
|
/// <summary>
|
||||||
/// WithBody: func (BodyData object)
|
/// WithBody: func (BodyData object)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -156,7 +156,8 @@ public sealed class WireMockContainer : DockerContainer
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await _adminApi.ReloadStaticMappingsAsync(cancellationToken);
|
var result = await _adminApi.ReloadStaticMappingsAsync(cancellationToken);
|
||||||
|
Logger.LogInformation("ReloadStaticMappings result: {Result}", result);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -231,7 +232,8 @@ public sealed class WireMockContainer : DockerContainer
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await _adminApi!.AddProtoDefinitionAsync(kvp.Key, protoDefinition);
|
var result = await _adminApi!.AddProtoDefinitionAsync(kvp.Key, protoDefinition);
|
||||||
|
Logger.LogInformation("AddProtoDefinition '{Id}' result: {Result}", kvp.Key, result);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -239,6 +241,12 @@ public sealed class WireMockContainer : DockerContainer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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)
|
private async void FileCreatedChangedOrDeleted(object sender, FileSystemEventArgs args)
|
||||||
@@ -246,6 +254,7 @@ public sealed class WireMockContainer : DockerContainer
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
await ReloadStaticMappingsAsync(args.FullPath);
|
await ReloadStaticMappingsAsync(args.FullPath);
|
||||||
|
Logger.LogInformation("ReloadStaticMappings triggered from file change: '{FullPath}'.", args.FullPath);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -62,6 +62,29 @@ public sealed class WireMockContainerBuilder : ContainerBuilder<WireMockContaine
|
|||||||
return WithImage(OSPlatform.Windows);
|
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>
|
/// <summary>
|
||||||
/// Set the admin username and password for the container (basic authentication).
|
/// Set the admin username and password for the container (basic authentication).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -112,6 +135,7 @@ public sealed class WireMockContainerBuilder : ContainerBuilder<WireMockContaine
|
|||||||
{
|
{
|
||||||
DockerResourceConfiguration.WithWatchStaticMappings(includeSubDirectories);
|
DockerResourceConfiguration.WithWatchStaticMappings(includeSubDirectories);
|
||||||
return
|
return
|
||||||
|
WithCommand("--ReadStaticMappings true").
|
||||||
WithCommand("--WatchStaticMappings true").
|
WithCommand("--WatchStaticMappings true").
|
||||||
WithCommand("--WatchStaticMappingsInSubdirectories", includeSubDirectories);
|
WithCommand("--WatchStaticMappingsInSubdirectories", includeSubDirectories);
|
||||||
}
|
}
|
||||||
@@ -129,9 +153,7 @@ public sealed class WireMockContainerBuilder : ContainerBuilder<WireMockContaine
|
|||||||
|
|
||||||
DockerResourceConfiguration.WithStaticMappingsPath(path);
|
DockerResourceConfiguration.WithStaticMappingsPath(path);
|
||||||
|
|
||||||
return
|
return WithWatchStaticMappings(includeSubDirectories);
|
||||||
WithReadStaticMappings().
|
|
||||||
WithCommand("--WatchStaticMappingsInSubdirectories", includeSubDirectories);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -208,9 +230,9 @@ public sealed class WireMockContainerBuilder : ContainerBuilder<WireMockContaine
|
|||||||
// In case the _imageOS is not set, determine it from the Image FullName.
|
// In case the _imageOS is not set, determine it from the Image FullName.
|
||||||
if (_imageOS == null)
|
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;
|
_imageOS = builder.DockerResourceConfiguration.Image.FullName.IndexOf("windows", StringComparison.OrdinalIgnoreCase) >= 0 ? OSPlatform.Windows : OSPlatform.Linux;
|
||||||
|
|||||||
79
src/WireMock.Net.xUnit.v3/TestOutputHelperWireMockLogger.cs
Normal file
79
src/WireMock.Net.xUnit.v3/TestOutputHelperWireMockLogger.cs
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
// Copyright © WireMock.Net
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Stef.Validation;
|
||||||
|
using WireMock.Admin.Requests;
|
||||||
|
using WireMock.Logging;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace WireMock.Net.Xunit;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// When using xUnit, this class enables to log the output from WireMock.Net to the <see cref="ITestOutputHelper"/>.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class TestOutputHelperWireMockLogger : IWireMockLogger
|
||||||
|
{
|
||||||
|
private readonly ITestOutputHelper _testOutputHelper;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new instance on the <see cref="TestOutputHelperWireMockLogger"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="testOutputHelper">Represents a class which can be used to provide test output.</param>
|
||||||
|
public TestOutputHelperWireMockLogger(ITestOutputHelper testOutputHelper)
|
||||||
|
{
|
||||||
|
_testOutputHelper = Guard.NotNull(testOutputHelper);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Debug(string formatString, params object[] args)
|
||||||
|
{
|
||||||
|
_testOutputHelper.WriteLine(Format("Debug", formatString, args));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Info(string formatString, params object[] args)
|
||||||
|
{
|
||||||
|
_testOutputHelper.WriteLine(Format("Info", formatString, args));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Warn(string formatString, params object[] args)
|
||||||
|
{
|
||||||
|
_testOutputHelper.WriteLine(Format("Warning", formatString, args));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Error(string formatString, params object[] args)
|
||||||
|
{
|
||||||
|
_testOutputHelper.WriteLine(Format("Error", formatString, args));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Error(string message, Exception exception)
|
||||||
|
{
|
||||||
|
_testOutputHelper.WriteLine(Format("Error", $"{message} {{0}}", exception));
|
||||||
|
|
||||||
|
if (exception is AggregateException ae)
|
||||||
|
{
|
||||||
|
ae.Handle(ex =>
|
||||||
|
{
|
||||||
|
_testOutputHelper.WriteLine(Format("Error", "Exception {0}", ex));
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void DebugRequestResponse(LogEntryModel logEntryModel, bool isAdminRequest)
|
||||||
|
{
|
||||||
|
var message = JsonConvert.SerializeObject(logEntryModel, Formatting.Indented);
|
||||||
|
_testOutputHelper.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}";
|
||||||
|
}
|
||||||
|
}
|
||||||
37
src/WireMock.Net.xUnit.v3/WireMock.Net.xUnit.v3.csproj
Normal file
37
src/WireMock.Net.xUnit.v3/WireMock.Net.xUnit.v3.csproj
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<Description>Some extensions for xUnit (ITestOutputHelper)</Description>
|
||||||
|
<AssemblyTitle>WireMock.Net.xUnit.v3</AssemblyTitle>
|
||||||
|
<Authors>Stef Heyenrath</Authors>
|
||||||
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
|
<PackageTags>tdd;wiremock;test;unittest;xunit</PackageTags>
|
||||||
|
<ProjectGuid>{4F46BD02-BEBC-4B2D-B857-4169AD222267}</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="Newtonsoft.Json" Version="13.0.4" />
|
||||||
|
<PackageReference Include="Stef.Validation" Version="0.2.0" />
|
||||||
|
<PackageReference Include="xunit.v3.extensibility.core" Version="3.2.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\WireMock.Net.Abstractions\WireMock.Net.Abstractions.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Update="JetBrains.Annotations" Version="2025.2.2" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
@@ -19,6 +19,7 @@ public class IntegrationTests(ITestOutputHelper output)
|
|||||||
var appHostBuilder = await DistributedApplicationTestingBuilder.CreateAsync<WireMock_Net_Aspire_TestAppHost>();
|
var appHostBuilder = await DistributedApplicationTestingBuilder.CreateAsync<WireMock_Net_Aspire_TestAppHost>();
|
||||||
await using var app = await appHostBuilder.BuildAsync();
|
await using var app = await appHostBuilder.BuildAsync();
|
||||||
await app.StartAsync();
|
await app.StartAsync();
|
||||||
|
await app.ResourceNotifications.WaitForResourceHealthyAsync("wiremock-service");
|
||||||
|
|
||||||
using var httpClient = app.CreateHttpClient("wiremock-service");
|
using var httpClient = app.CreateHttpClient("wiremock-service");
|
||||||
|
|
||||||
@@ -46,6 +47,7 @@ public class IntegrationTests(ITestOutputHelper output)
|
|||||||
var appHostBuilder = await DistributedApplicationTestingBuilder.CreateAsync<WireMock_Net_Aspire_TestAppHost>();
|
var appHostBuilder = await DistributedApplicationTestingBuilder.CreateAsync<WireMock_Net_Aspire_TestAppHost>();
|
||||||
await using var app = await appHostBuilder.BuildAsync();
|
await using var app = await appHostBuilder.BuildAsync();
|
||||||
await app.StartAsync();
|
await app.StartAsync();
|
||||||
|
await app.ResourceNotifications.WaitForResourceHealthyAsync("wiremock-service");
|
||||||
|
|
||||||
var adminClient = app.CreateWireMockAdminClient("wiremock-service");
|
var adminClient = app.CreateWireMockAdminClient("wiremock-service");
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ public class WireMockServerArgumentsTests
|
|||||||
var args = new WireMockServerArguments();
|
var args = new WireMockServerArguments();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
args.HttpPort.Should().BeNull();
|
args.HttpPorts.Should().BeEmpty();
|
||||||
args.AdminUsername.Should().BeNull();
|
args.AdminUsername.Should().BeNull();
|
||||||
args.AdminPassword.Should().BeNull();
|
args.AdminPassword.Should().BeNull();
|
||||||
args.ReadStaticMappings.Should().BeFalse();
|
args.ReadStaticMappings.Should().BeFalse();
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using Moq;
|
using Moq;
|
||||||
|
using WireMock.Util;
|
||||||
|
|
||||||
namespace WireMock.Net.Aspire.Tests;
|
namespace WireMock.Net.Aspire.Tests;
|
||||||
|
|
||||||
@@ -40,7 +41,21 @@ public class WireMockServerBuilderExtensionsTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void AddWireMock()
|
public void AddWireMock_WithInvalidAdditionalUrls_ShouldThrowArgumentException()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
string[] invalidUrls = { "err" };
|
||||||
|
var builder = Mock.Of<IDistributedApplicationBuilder>();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
Action act = () => builder.AddWireMock("ValidName", invalidUrls);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
act.Should().Throw<ArgumentException>().WithMessage("The URL 'err' is not valid.");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AddWireMockWithPort()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var name = $"apiservice{Guid.NewGuid()}";
|
var name = $"apiservice{Guid.NewGuid()}";
|
||||||
@@ -65,9 +80,9 @@ public class WireMockServerBuilderExtensionsTests
|
|||||||
ReadStaticMappings = true,
|
ReadStaticMappings = true,
|
||||||
WatchStaticMappings = false,
|
WatchStaticMappings = false,
|
||||||
MappingsPath = null,
|
MappingsPath = null,
|
||||||
HttpPort = port
|
HttpPorts = [port]
|
||||||
});
|
});
|
||||||
wiremock.Resource.Annotations.Should().HaveCount(5);
|
wiremock.Resource.Annotations.Should().HaveCount(6);
|
||||||
|
|
||||||
var containerImageAnnotation = wiremock.Resource.Annotations.OfType<ContainerImageAnnotation>().FirstOrDefault();
|
var containerImageAnnotation = wiremock.Resource.Annotations.OfType<ContainerImageAnnotation>().FirstOrDefault();
|
||||||
containerImageAnnotation.Should().BeEquivalentTo(new ContainerImageAnnotation
|
containerImageAnnotation.Should().BeEquivalentTo(new ContainerImageAnnotation
|
||||||
@@ -90,9 +105,90 @@ public class WireMockServerBuilderExtensionsTests
|
|||||||
));
|
));
|
||||||
|
|
||||||
wiremock.Resource.Annotations.OfType<EnvironmentCallbackAnnotation>().FirstOrDefault().Should().NotBeNull();
|
wiremock.Resource.Annotations.OfType<EnvironmentCallbackAnnotation>().FirstOrDefault().Should().NotBeNull();
|
||||||
|
|
||||||
wiremock.Resource.Annotations.OfType<CommandLineArgsCallbackAnnotation>().FirstOrDefault().Should().NotBeNull();
|
wiremock.Resource.Annotations.OfType<CommandLineArgsCallbackAnnotation>().FirstOrDefault().Should().NotBeNull();
|
||||||
|
wiremock.Resource.Annotations.OfType<ResourceCommandAnnotation>().FirstOrDefault().Should().NotBeNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AddWireMockWithAdditionalUrls()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var name = $"apiservice{Guid.NewGuid()}";
|
||||||
|
var freePorts = PortUtils.FindFreeTcpPorts(2).ToList();
|
||||||
|
string[] additionalUrls = { $"http://*:{freePorts[0]}", $"grpc://*:{freePorts[1]}" };
|
||||||
|
const string username = "admin";
|
||||||
|
const string password = "test";
|
||||||
|
var builder = DistributedApplication.CreateBuilder();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var wiremock = builder
|
||||||
|
.AddWireMock(name, additionalUrls)
|
||||||
|
.WithAdminUserNameAndPassword(username, password)
|
||||||
|
.WithReadStaticMappings();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
wiremock.Resource.Should().NotBeNull();
|
||||||
|
wiremock.Resource.Name.Should().Be(name);
|
||||||
|
wiremock.Resource.Arguments.Should().BeEquivalentTo(new WireMockServerArguments
|
||||||
|
{
|
||||||
|
AdminPassword = password,
|
||||||
|
AdminUsername = username,
|
||||||
|
ReadStaticMappings = true,
|
||||||
|
WatchStaticMappings = false,
|
||||||
|
MappingsPath = null,
|
||||||
|
HttpPorts = freePorts,
|
||||||
|
AdditionalUrls = additionalUrls.ToList()
|
||||||
|
});
|
||||||
|
wiremock.Resource.Annotations.Should().HaveCount(9);
|
||||||
|
|
||||||
|
var containerImageAnnotation = wiremock.Resource.Annotations.OfType<ContainerImageAnnotation>().FirstOrDefault();
|
||||||
|
containerImageAnnotation.Should().BeEquivalentTo(new ContainerImageAnnotation
|
||||||
|
{
|
||||||
|
Image = "sheyenrath/wiremock.net-alpine",
|
||||||
|
Registry = null,
|
||||||
|
Tag = "latest"
|
||||||
|
});
|
||||||
|
|
||||||
|
var endpointAnnotations = wiremock.Resource.Annotations.OfType<EndpointAnnotation>().ToArray();
|
||||||
|
endpointAnnotations.Should().HaveCount(3);
|
||||||
|
|
||||||
|
var endpointAnnotationForHttp80 = endpointAnnotations[0];
|
||||||
|
endpointAnnotationForHttp80.Should().BeEquivalentTo(new EndpointAnnotation(
|
||||||
|
protocol: ProtocolType.Tcp,
|
||||||
|
uriScheme: "http",
|
||||||
|
transport: null,
|
||||||
|
name: null,
|
||||||
|
port: null,
|
||||||
|
targetPort: 80,
|
||||||
|
isExternal: null,
|
||||||
|
isProxied: true
|
||||||
|
));
|
||||||
|
var endpointAnnotationForHttpFreePort = endpointAnnotations[1];
|
||||||
|
endpointAnnotationForHttpFreePort.Should().BeEquivalentTo(new EndpointAnnotation(
|
||||||
|
protocol: ProtocolType.Tcp,
|
||||||
|
uriScheme: "http",
|
||||||
|
transport: null,
|
||||||
|
name: $"http-{freePorts[0]}",
|
||||||
|
port: freePorts[0],
|
||||||
|
targetPort: freePorts[0],
|
||||||
|
isExternal: null,
|
||||||
|
isProxied: true
|
||||||
|
));
|
||||||
|
|
||||||
|
var endpointAnnotationForGrpcFreePort = endpointAnnotations[2];
|
||||||
|
endpointAnnotationForGrpcFreePort.Should().BeEquivalentTo(new EndpointAnnotation(
|
||||||
|
protocol: ProtocolType.Tcp,
|
||||||
|
uriScheme: "grpc",
|
||||||
|
transport: null,
|
||||||
|
name: $"grpc-{freePorts[1]}",
|
||||||
|
port: freePorts[1],
|
||||||
|
targetPort: freePorts[1],
|
||||||
|
isExternal: null,
|
||||||
|
isProxied: true
|
||||||
|
));
|
||||||
|
|
||||||
|
wiremock.Resource.Annotations.OfType<EnvironmentCallbackAnnotation>().FirstOrDefault().Should().NotBeNull();
|
||||||
|
wiremock.Resource.Annotations.OfType<CommandLineArgsCallbackAnnotation>().FirstOrDefault().Should().NotBeNull();
|
||||||
wiremock.Resource.Annotations.OfType<ResourceCommandAnnotation>().FirstOrDefault().Should().NotBeNull();
|
wiremock.Resource.Annotations.OfType<ResourceCommandAnnotation>().FirstOrDefault().Should().NotBeNull();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -190,5 +190,92 @@
|
|||||||
BodyDestination: SameAsSource,
|
BodyDestination: SameAsSource,
|
||||||
Body: Buy milk
|
Body: Buy milk
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Guid: 98fae52e-76df-47d9-876f-2ee32e931006,
|
||||||
|
UpdatedAt: 2023-01-14 15:16:17,
|
||||||
|
Request: {
|
||||||
|
Path: {
|
||||||
|
Matchers: [
|
||||||
|
{
|
||||||
|
Name: WildcardMatcher,
|
||||||
|
Pattern: /delay,
|
||||||
|
IgnoreCase: false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
Methods: [
|
||||||
|
GET
|
||||||
|
]
|
||||||
|
},
|
||||||
|
Response: {
|
||||||
|
Delay: 1000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Guid: 98fae52e-76df-47d9-876f-2ee32e931007,
|
||||||
|
UpdatedAt: 2023-01-14 15:16:17,
|
||||||
|
Request: {
|
||||||
|
Path: {
|
||||||
|
Matchers: [
|
||||||
|
{
|
||||||
|
Name: WildcardMatcher,
|
||||||
|
Pattern: /random-delay,
|
||||||
|
IgnoreCase: false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
Methods: [
|
||||||
|
GET
|
||||||
|
]
|
||||||
|
},
|
||||||
|
Response: {
|
||||||
|
MinimumRandomDelay: 1234,
|
||||||
|
MaximumRandomDelay: 60000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Guid: 98fae52e-76df-47d9-876f-2ee32e931008,
|
||||||
|
UpdatedAt: 2023-01-14 15:16:17,
|
||||||
|
Request: {
|
||||||
|
Path: {
|
||||||
|
Matchers: [
|
||||||
|
{
|
||||||
|
Name: WildcardMatcher,
|
||||||
|
Pattern: /prob,
|
||||||
|
IgnoreCase: false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
Methods: [
|
||||||
|
GET
|
||||||
|
]
|
||||||
|
},
|
||||||
|
Response: {
|
||||||
|
StatusCode: 300
|
||||||
|
},
|
||||||
|
Probability: 0.1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Guid: 98fae52e-76df-47d9-876f-2ee32e931009,
|
||||||
|
UpdatedAt: 2023-01-14 15:16:17,
|
||||||
|
Request: {
|
||||||
|
Path: {
|
||||||
|
Matchers: [
|
||||||
|
{
|
||||||
|
Name: WildcardMatcher,
|
||||||
|
Pattern: /prob,
|
||||||
|
IgnoreCase: false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
Methods: [
|
||||||
|
GET
|
||||||
|
]
|
||||||
|
},
|
||||||
|
Response: {
|
||||||
|
StatusCode: 201
|
||||||
|
},
|
||||||
|
Probability: 0.9
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -78,3 +78,45 @@ builder
|
|||||||
.WithBody("Buy milk")
|
.WithBody("Buy milk")
|
||||||
);
|
);
|
||||||
|
|
||||||
|
builder
|
||||||
|
.Given(Request.Create()
|
||||||
|
.UsingMethod("GET")
|
||||||
|
.WithPath(new WildcardMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, "/delay", false, WireMock.Matchers.MatchOperator.Or))
|
||||||
|
)
|
||||||
|
.WithGuid("98fae52e-76df-47d9-876f-2ee32e931006")
|
||||||
|
.RespondWith(Response.Create()
|
||||||
|
.WithDelay(1000)
|
||||||
|
);
|
||||||
|
|
||||||
|
builder
|
||||||
|
.Given(Request.Create()
|
||||||
|
.UsingMethod("GET")
|
||||||
|
.WithPath(new WildcardMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, "/random-delay", false, WireMock.Matchers.MatchOperator.Or))
|
||||||
|
)
|
||||||
|
.WithGuid("98fae52e-76df-47d9-876f-2ee32e931007")
|
||||||
|
.RespondWith(Response.Create()
|
||||||
|
.WithRandomDelay(1234, 60000)
|
||||||
|
);
|
||||||
|
|
||||||
|
builder
|
||||||
|
.Given(Request.Create()
|
||||||
|
.UsingMethod("GET")
|
||||||
|
.WithPath(new WildcardMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, "/prob", false, WireMock.Matchers.MatchOperator.Or))
|
||||||
|
)
|
||||||
|
.WithGuid("98fae52e-76df-47d9-876f-2ee32e931008")
|
||||||
|
.WithProbability(0.1)
|
||||||
|
.RespondWith(Response.Create()
|
||||||
|
.WithStatusCode(300)
|
||||||
|
);
|
||||||
|
|
||||||
|
builder
|
||||||
|
.Given(Request.Create()
|
||||||
|
.UsingMethod("GET")
|
||||||
|
.WithPath(new WildcardMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, "/prob", false, WireMock.Matchers.MatchOperator.Or))
|
||||||
|
)
|
||||||
|
.WithGuid("98fae52e-76df-47d9-876f-2ee32e931009")
|
||||||
|
.WithProbability(0.9)
|
||||||
|
.RespondWith(Response.Create()
|
||||||
|
.WithStatusCode(201)
|
||||||
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -78,3 +78,45 @@ server
|
|||||||
.WithBody("Buy milk")
|
.WithBody("Buy milk")
|
||||||
);
|
);
|
||||||
|
|
||||||
|
server
|
||||||
|
.Given(Request.Create()
|
||||||
|
.UsingMethod("GET")
|
||||||
|
.WithPath(new WildcardMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, "/delay", false, WireMock.Matchers.MatchOperator.Or))
|
||||||
|
)
|
||||||
|
.WithGuid("98fae52e-76df-47d9-876f-2ee32e931006")
|
||||||
|
.RespondWith(Response.Create()
|
||||||
|
.WithDelay(1000)
|
||||||
|
);
|
||||||
|
|
||||||
|
server
|
||||||
|
.Given(Request.Create()
|
||||||
|
.UsingMethod("GET")
|
||||||
|
.WithPath(new WildcardMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, "/random-delay", false, WireMock.Matchers.MatchOperator.Or))
|
||||||
|
)
|
||||||
|
.WithGuid("98fae52e-76df-47d9-876f-2ee32e931007")
|
||||||
|
.RespondWith(Response.Create()
|
||||||
|
.WithRandomDelay(1234, 60000)
|
||||||
|
);
|
||||||
|
|
||||||
|
server
|
||||||
|
.Given(Request.Create()
|
||||||
|
.UsingMethod("GET")
|
||||||
|
.WithPath(new WildcardMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, "/prob", false, WireMock.Matchers.MatchOperator.Or))
|
||||||
|
)
|
||||||
|
.WithGuid("98fae52e-76df-47d9-876f-2ee32e931008")
|
||||||
|
.WithProbability(0.1)
|
||||||
|
.RespondWith(Response.Create()
|
||||||
|
.WithStatusCode(300)
|
||||||
|
);
|
||||||
|
|
||||||
|
server
|
||||||
|
.Given(Request.Create()
|
||||||
|
.UsingMethod("GET")
|
||||||
|
.WithPath(new WildcardMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, "/prob", false, WireMock.Matchers.MatchOperator.Or))
|
||||||
|
)
|
||||||
|
.WithGuid("98fae52e-76df-47d9-876f-2ee32e931009")
|
||||||
|
.WithProbability(0.9)
|
||||||
|
.RespondWith(Response.Create()
|
||||||
|
.WithStatusCode(201)
|
||||||
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -186,5 +186,92 @@
|
|||||||
BodyDestination: SameAsSource,
|
BodyDestination: SameAsSource,
|
||||||
Body: Buy milk
|
Body: Buy milk
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Guid: 98fae52e-76df-47d9-876f-2ee32e931006,
|
||||||
|
UpdatedAt: 2023-01-14T15:16:17,
|
||||||
|
Request: {
|
||||||
|
Path: {
|
||||||
|
Matchers: [
|
||||||
|
{
|
||||||
|
Name: WildcardMatcher,
|
||||||
|
Pattern: /delay,
|
||||||
|
IgnoreCase: false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
Methods: [
|
||||||
|
GET
|
||||||
|
]
|
||||||
|
},
|
||||||
|
Response: {
|
||||||
|
Delay: 1000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Guid: 98fae52e-76df-47d9-876f-2ee32e931007,
|
||||||
|
UpdatedAt: 2023-01-14T15:16:17,
|
||||||
|
Request: {
|
||||||
|
Path: {
|
||||||
|
Matchers: [
|
||||||
|
{
|
||||||
|
Name: WildcardMatcher,
|
||||||
|
Pattern: /random-delay,
|
||||||
|
IgnoreCase: false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
Methods: [
|
||||||
|
GET
|
||||||
|
]
|
||||||
|
},
|
||||||
|
Response: {
|
||||||
|
MinimumRandomDelay: 1234,
|
||||||
|
MaximumRandomDelay: 60000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Guid: 98fae52e-76df-47d9-876f-2ee32e931008,
|
||||||
|
UpdatedAt: 2023-01-14T15:16:17,
|
||||||
|
Request: {
|
||||||
|
Path: {
|
||||||
|
Matchers: [
|
||||||
|
{
|
||||||
|
Name: WildcardMatcher,
|
||||||
|
Pattern: /prob,
|
||||||
|
IgnoreCase: false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
Methods: [
|
||||||
|
GET
|
||||||
|
]
|
||||||
|
},
|
||||||
|
Response: {
|
||||||
|
StatusCode: 300
|
||||||
|
},
|
||||||
|
Probability: 0.1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Guid: 98fae52e-76df-47d9-876f-2ee32e931009,
|
||||||
|
UpdatedAt: 2023-01-14T15:16:17,
|
||||||
|
Request: {
|
||||||
|
Path: {
|
||||||
|
Matchers: [
|
||||||
|
{
|
||||||
|
Name: WildcardMatcher,
|
||||||
|
Pattern: /prob,
|
||||||
|
IgnoreCase: false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
Methods: [
|
||||||
|
GET
|
||||||
|
]
|
||||||
|
},
|
||||||
|
Response: {
|
||||||
|
StatusCode: 201
|
||||||
|
},
|
||||||
|
Probability: 0.9
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#if !(NET452 || NET461 || NETCOREAPP3_1)
|
#if !(NET452 || NET461 || NETCOREAPP3_1)
|
||||||
using System;
|
using System;
|
||||||
|
using System.Net;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Moq;
|
using Moq;
|
||||||
using VerifyTests;
|
using VerifyTests;
|
||||||
@@ -118,6 +119,35 @@ public class MappingBuilderTests
|
|||||||
.RespondWith(Response.Create()
|
.RespondWith(Response.Create()
|
||||||
.WithBody("Buy milk"));
|
.WithBody("Buy milk"));
|
||||||
|
|
||||||
|
_sut.Given(Request.Create()
|
||||||
|
.WithPath("/delay")
|
||||||
|
.UsingGet()
|
||||||
|
).RespondWith(Response.Create()
|
||||||
|
.WithDelay(1000)
|
||||||
|
);
|
||||||
|
|
||||||
|
_sut.Given(Request.Create()
|
||||||
|
.WithPath("/random-delay")
|
||||||
|
.UsingGet()
|
||||||
|
).RespondWith(Response.Create()
|
||||||
|
.WithRandomDelay(1234)
|
||||||
|
);
|
||||||
|
|
||||||
|
_sut.Given(Request.Create()
|
||||||
|
.WithPath("/prob")
|
||||||
|
.UsingGet()
|
||||||
|
).WithProbability(0.1)
|
||||||
|
.RespondWith(Response.Create()
|
||||||
|
.WithStatusCode(HttpStatusCode.Ambiguous)
|
||||||
|
);
|
||||||
|
_sut.Given(Request.Create()
|
||||||
|
.WithPath("/prob")
|
||||||
|
.UsingGet()
|
||||||
|
).WithProbability(0.9)
|
||||||
|
.RespondWith(Response.Create()
|
||||||
|
.WithStatusCode(HttpStatusCode.Created)
|
||||||
|
);
|
||||||
|
|
||||||
_numMappings = _sut.GetMappings().Length;
|
_numMappings = _sut.GetMappings().Length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,11 +1,12 @@
|
|||||||
// Copyright © WireMock.Net
|
// Copyright © WireMock.Net
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using FluentAssertions;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using FluentAssertions;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
using NFluent;
|
using NFluent;
|
||||||
using WireMock.Matchers;
|
using WireMock.Matchers;
|
||||||
using WireMock.Matchers.Request;
|
using WireMock.Matchers.Request;
|
||||||
@@ -72,15 +73,17 @@ public class RequestBuilderWithBodyTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Request_WithBody_FuncJson()
|
public void Request_WithBody_FuncObject()
|
||||||
{
|
{
|
||||||
// Assign
|
// Assign
|
||||||
var requestBuilder = Request.Create().UsingAnyMethod().WithBody(b => b != null);
|
var requestBuilder = Request.Create()
|
||||||
|
.UsingAnyMethod()
|
||||||
|
.WithBody(b => b != null);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var body = new BodyData
|
var body = new BodyData
|
||||||
{
|
{
|
||||||
BodyAsJson = 123,
|
BodyAsJson = JObject.Parse("""{ "X": 123, "Y": "a" }"""),
|
||||||
DetectedBodyType = BodyType.Json
|
DetectedBodyType = BodyType.Json
|
||||||
};
|
};
|
||||||
var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "POST", ClientIp, body);
|
var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "POST", ClientIp, body);
|
||||||
@@ -90,6 +93,57 @@ public class RequestBuilderWithBodyTests
|
|||||||
Check.That(requestBuilder.GetMatchingScore(request, requestMatchResult)).IsEqualTo(1.0);
|
Check.That(requestBuilder.GetMatchingScore(request, requestMatchResult)).IsEqualTo(1.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("""{ "X": 123, "Y": "a" }""", 1.0)]
|
||||||
|
[InlineData("""{ "X": 123, "Y": "b" }""", 0.0)]
|
||||||
|
public void Request_WithBodyAsType_Func(string json, double expected)
|
||||||
|
{
|
||||||
|
// Assign
|
||||||
|
var requestBuilder = Request.Create()
|
||||||
|
.UsingAnyMethod()
|
||||||
|
.WithBodyAsType<FuncType>(ft => ft != null && ft.X == 123 && ft.Y == "a");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var body = new BodyData
|
||||||
|
{
|
||||||
|
BodyAsJson = JObject.Parse(json),
|
||||||
|
DetectedBodyType = BodyType.Json
|
||||||
|
};
|
||||||
|
var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "POST", ClientIp, body);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var requestMatchResult = new RequestMatchResult();
|
||||||
|
Check.That(requestBuilder.GetMatchingScore(request, requestMatchResult)).IsEqualTo(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Request_WithBodyAsType_Func_IncorrectType()
|
||||||
|
{
|
||||||
|
// Assign
|
||||||
|
var requestBuilder = Request.Create()
|
||||||
|
.UsingAnyMethod()
|
||||||
|
.WithBodyAsType<Version>(ft => ft != null);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var body = new BodyData
|
||||||
|
{
|
||||||
|
BodyAsJson = JObject.Parse("""{ "X": 123, "Y": "a" }"""),
|
||||||
|
DetectedBodyType = BodyType.Json
|
||||||
|
};
|
||||||
|
var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "POST", ClientIp, body);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var requestMatchResult = new RequestMatchResult();
|
||||||
|
Check.That(requestBuilder.GetMatchingScore(request, requestMatchResult)).IsEqualTo(0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class FuncType
|
||||||
|
{
|
||||||
|
public int X { get; set; } = 42;
|
||||||
|
|
||||||
|
public string Y { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Request_WithBody_FuncFormUrlEncoded()
|
public void Request_WithBody_FuncFormUrlEncoded()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ public partial class MappingConverterTests
|
|||||||
var guid = new Guid("8e7b9ab7-e18e-4502-8bc9-11e6679811cc");
|
var guid = new Guid("8e7b9ab7-e18e-4502-8bc9-11e6679811cc");
|
||||||
var request = Request.Create()
|
var request = Request.Create()
|
||||||
.UsingGet()
|
.UsingGet()
|
||||||
.WithPath("test_path")
|
.WithPath("/test_path")
|
||||||
.WithParam("q", "42")
|
.WithParam("q", "42")
|
||||||
.WithClientIP("112.123.100.99")
|
.WithClientIP("112.123.100.99")
|
||||||
.WithHeader("h-key", "h-value")
|
.WithHeader("h-key", "h-value")
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
builder
|
builder
|
||||||
.Given(Request.Create()
|
.Given(Request.Create()
|
||||||
.UsingMethod("GET")
|
.UsingMethod("GET")
|
||||||
.WithPath(new WildcardMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, "test_path", false, WireMock.Matchers.MatchOperator.Or))
|
.WithPath(new WildcardMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, "/test_path", false, WireMock.Matchers.MatchOperator.Or))
|
||||||
.WithParam("q", new ExactMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, false, WireMock.Matchers.MatchOperator.And, "42"))
|
.WithParam("q", new ExactMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, false, WireMock.Matchers.MatchOperator.And, "42"))
|
||||||
.WithClientIP("112.123.100.99")
|
.WithClientIP("112.123.100.99")
|
||||||
.WithHeader("h-key", "h-value", true)
|
.WithHeader("h-key", "h-value", true)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
builder
|
builder
|
||||||
.Given(Request.Create()
|
.Given(Request.Create()
|
||||||
.UsingMethod("GET")
|
.UsingMethod("GET")
|
||||||
.WithPath(new WildcardMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, "test_path", false, WireMock.Matchers.MatchOperator.Or))
|
.WithPath(new WildcardMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, "/test_path", false, WireMock.Matchers.MatchOperator.Or))
|
||||||
.WithParam("q", new ExactMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, false, WireMock.Matchers.MatchOperator.And, "42"))
|
.WithParam("q", new ExactMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, false, WireMock.Matchers.MatchOperator.And, "42"))
|
||||||
.WithClientIP("112.123.100.99")
|
.WithClientIP("112.123.100.99")
|
||||||
.WithHeader("h-key", "h-value", true)
|
.WithHeader("h-key", "h-value", true)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
server
|
server
|
||||||
.Given(Request.Create()
|
.Given(Request.Create()
|
||||||
.UsingMethod("GET")
|
.UsingMethod("GET")
|
||||||
.WithPath(new WildcardMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, "test_path", false, WireMock.Matchers.MatchOperator.Or))
|
.WithPath(new WildcardMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, "/test_path", false, WireMock.Matchers.MatchOperator.Or))
|
||||||
.WithParam("q", new ExactMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, false, WireMock.Matchers.MatchOperator.And, "42"))
|
.WithParam("q", new ExactMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, false, WireMock.Matchers.MatchOperator.And, "42"))
|
||||||
.WithClientIP("112.123.100.99")
|
.WithClientIP("112.123.100.99")
|
||||||
.WithHeader("h-key", "h-value", true)
|
.WithHeader("h-key", "h-value", true)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
server
|
server
|
||||||
.Given(Request.Create()
|
.Given(Request.Create()
|
||||||
.UsingMethod("GET")
|
.UsingMethod("GET")
|
||||||
.WithPath(new WildcardMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, "test_path", false, WireMock.Matchers.MatchOperator.Or))
|
.WithPath(new WildcardMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, "/test_path", false, WireMock.Matchers.MatchOperator.Or))
|
||||||
.WithParam("q", new ExactMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, false, WireMock.Matchers.MatchOperator.And, "42"))
|
.WithParam("q", new ExactMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, false, WireMock.Matchers.MatchOperator.And, "42"))
|
||||||
.WithClientIP("112.123.100.99")
|
.WithClientIP("112.123.100.99")
|
||||||
.WithHeader("h-key", "h-value", true)
|
.WithHeader("h-key", "h-value", true)
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
Matchers: [
|
Matchers: [
|
||||||
{
|
{
|
||||||
Name: WildcardMatcher,
|
Name: WildcardMatcher,
|
||||||
Pattern: x,
|
Pattern: /x,
|
||||||
IgnoreCase: false
|
IgnoreCase: false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ public class ProxyMappingConverterTests
|
|||||||
|
|
||||||
var request = Request.Create()
|
var request = Request.Create()
|
||||||
.UsingPost()
|
.UsingPost()
|
||||||
.WithPath("x")
|
.WithPath("/x")
|
||||||
.WithParam("p1", "p1-v")
|
.WithParam("p1", "p1-v")
|
||||||
.WithParam("p2", "p2-v")
|
.WithParam("p2", "p2-v")
|
||||||
.WithHeader("Content-Type", new ContentTypeMatcher("text/plain"))
|
.WithHeader("Content-Type", new ContentTypeMatcher("text/plain"))
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ public partial class TestcontainersTests
|
|||||||
var grpcPort = wireMockContainer.GetMappedPublicPort(9090);
|
var grpcPort = wireMockContainer.GetMappedPublicPort(9090);
|
||||||
grpcPort.Should().BeGreaterThan(0);
|
grpcPort.Should().BeGreaterThan(0);
|
||||||
|
|
||||||
var grpcUrl = wireMockContainer.GetMappedPublicUrl(80);
|
var grpcUrl = wireMockContainer.GetMappedPublicUrl(9090);
|
||||||
grpcUrl.Should().StartWith("http://");
|
grpcUrl.Should().StartWith("http://");
|
||||||
|
|
||||||
var adminClient = wireMockContainer.CreateWireMockAdminClient();
|
var adminClient = wireMockContainer.CreateWireMockAdminClient();
|
||||||
@@ -149,6 +149,18 @@ public partial class TestcontainersTests
|
|||||||
await StopAsync(wireMockContainer);
|
await StopAsync(wireMockContainer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task WireMockContainer_Build_Grpc_ProtoDefinitionAtServerLevel_UsingGrpcGeneratedClient_AndWithWatchStaticMappings()
|
||||||
|
{
|
||||||
|
var wireMockContainer = await Given_WireMockContainerWithProtoDefinitionAtServerLevelWithWatchStaticMappingsIsStartedForHttpAndGrpcAsync();
|
||||||
|
|
||||||
|
var reply = await When_GrpcClient_Calls_SayHelloAsync(wireMockContainer);
|
||||||
|
|
||||||
|
Then_ReplyMessage_Should_BeCorrect(reply);
|
||||||
|
|
||||||
|
await StopAsync(wireMockContainer);
|
||||||
|
}
|
||||||
|
|
||||||
private static async Task<WireMockContainer> Given_WireMockContainerIsStartedForHttpAndGrpcAsync()
|
private static async Task<WireMockContainer> Given_WireMockContainerIsStartedForHttpAndGrpcAsync()
|
||||||
{
|
{
|
||||||
var wireMockContainer = new WireMockContainerBuilder()
|
var wireMockContainer = new WireMockContainerBuilder()
|
||||||
@@ -172,6 +184,19 @@ public partial class TestcontainersTests
|
|||||||
return wireMockContainer;
|
return wireMockContainer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static async Task<WireMockContainer> Given_WireMockContainerWithProtoDefinitionAtServerLevelWithWatchStaticMappingsIsStartedForHttpAndGrpcAsync()
|
||||||
|
{
|
||||||
|
var wireMockContainer = new WireMockContainerBuilder()
|
||||||
|
.AddUrl("grpc://*:9090")
|
||||||
|
.AddProtoDefinition("my-greeter", ReadFile("greet.proto"))
|
||||||
|
.WithMappings(Path.Combine(Directory.GetCurrentDirectory(), "__admin", "mappings"))
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
await wireMockContainer.StartAsync();
|
||||||
|
|
||||||
|
return wireMockContainer;
|
||||||
|
}
|
||||||
|
|
||||||
private static async Task Given_ProtoBufMappingIsAddedViaAdminInterfaceAsync(WireMockContainer wireMockContainer, string filename)
|
private static async Task Given_ProtoBufMappingIsAddedViaAdminInterfaceAsync(WireMockContainer wireMockContainer, string filename)
|
||||||
{
|
{
|
||||||
var mappingsJson = ReadFile(filename);
|
var mappingsJson = ReadFile(filename);
|
||||||
@@ -180,6 +205,8 @@ public partial class TestcontainersTests
|
|||||||
|
|
||||||
var result = await httpClient.PostAsync("/__admin/mappings", new StringContent(mappingsJson, Encoding.UTF8, WireMockConstants.ContentTypeJson));
|
var result = await httpClient.PostAsync("/__admin/mappings", new StringContent(mappingsJson, Encoding.UTF8, WireMockConstants.ContentTypeJson));
|
||||||
result.EnsureSuccessStatusCode();
|
result.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
|
await Task.Delay(1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<HelloReply> When_GrpcClient_Calls_SayHelloAsync(WireMockContainer wireMockContainer)
|
private static async Task<HelloReply> When_GrpcClient_Calls_SayHelloAsync(WireMockContainer wireMockContainer)
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ public partial class TestcontainersTests(ITestOutputHelper testOutputHelper)
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
wireMockContainerBuilder = wireMockContainerBuilder.WithImage("sheyenrath/wiremock.net");
|
wireMockContainerBuilder = wireMockContainerBuilder.WithImage("sheyenrath/wiremock.net-alpine");
|
||||||
}
|
}
|
||||||
|
|
||||||
var wireMockContainer = wireMockContainerBuilder.Build();
|
var wireMockContainer = wireMockContainerBuilder.Build();
|
||||||
|
|||||||
@@ -15,13 +15,13 @@ public class PortUtilsTests
|
|||||||
var url = "test";
|
var url = "test";
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = PortUtils.TryExtract(url, out var isHttps, out var isGrpc, out var proto, out var host, out var port);
|
var result = PortUtils.TryExtract(url, out var isHttps, out var isGrpc, out var scheme, out var host, out var port);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
result.Should().BeFalse();
|
result.Should().BeFalse();
|
||||||
isHttps.Should().BeFalse();
|
isHttps.Should().BeFalse();
|
||||||
isGrpc.Should().BeFalse();
|
isGrpc.Should().BeFalse();
|
||||||
proto.Should().BeNull();
|
scheme.Should().BeNull();
|
||||||
host.Should().BeNull();
|
host.Should().BeNull();
|
||||||
port.Should().Be(default(int));
|
port.Should().Be(default(int));
|
||||||
}
|
}
|
||||||
@@ -33,13 +33,13 @@ public class PortUtilsTests
|
|||||||
var url = "http://0.0.0.0";
|
var url = "http://0.0.0.0";
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = PortUtils.TryExtract(url, out var isHttps, out var isGrpc, out var proto, out var host, out var port);
|
var result = PortUtils.TryExtract(url, out var isHttps, out var isGrpc, out var scheme, out var host, out var port);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
result.Should().BeFalse();
|
result.Should().BeFalse();
|
||||||
isHttps.Should().BeFalse();
|
isHttps.Should().BeFalse();
|
||||||
isGrpc.Should().BeFalse();
|
isGrpc.Should().BeFalse();
|
||||||
proto.Should().BeNull();
|
scheme.Should().BeNull();
|
||||||
host.Should().BeNull();
|
host.Should().BeNull();
|
||||||
port.Should().Be(default(int));
|
port.Should().Be(default(int));
|
||||||
}
|
}
|
||||||
@@ -51,13 +51,13 @@ public class PortUtilsTests
|
|||||||
var url = "http://wiremock.net:1234";
|
var url = "http://wiremock.net:1234";
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = PortUtils.TryExtract(url, out var isHttps, out var isGrpc, out var proto, out var host, out var port);
|
var result = PortUtils.TryExtract(url, out var isHttps, out var isGrpc, out var scheme, out var host, out var port);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
result.Should().BeTrue();
|
result.Should().BeTrue();
|
||||||
isHttps.Should().BeFalse();
|
isHttps.Should().BeFalse();
|
||||||
isGrpc.Should().BeFalse();
|
isGrpc.Should().BeFalse();
|
||||||
proto.Should().Be("http");
|
scheme.Should().Be("http");
|
||||||
host.Should().Be("wiremock.net");
|
host.Should().Be("wiremock.net");
|
||||||
port.Should().Be(1234);
|
port.Should().Be(1234);
|
||||||
}
|
}
|
||||||
@@ -69,13 +69,13 @@ public class PortUtilsTests
|
|||||||
var url = "https://wiremock.net:5000";
|
var url = "https://wiremock.net:5000";
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = PortUtils.TryExtract(url, out var isHttps, out var isGrpc, out var proto, out var host, out var port);
|
var result = PortUtils.TryExtract(url, out var isHttps, out var isGrpc, out var scheme, out var host, out var port);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
result.Should().BeTrue();
|
result.Should().BeTrue();
|
||||||
isHttps.Should().BeTrue();
|
isHttps.Should().BeTrue();
|
||||||
isGrpc.Should().BeFalse();
|
isGrpc.Should().BeFalse();
|
||||||
proto.Should().Be("https");
|
scheme.Should().Be("https");
|
||||||
host.Should().Be("wiremock.net");
|
host.Should().Be("wiremock.net");
|
||||||
port.Should().Be(5000);
|
port.Should().Be(5000);
|
||||||
}
|
}
|
||||||
@@ -87,13 +87,13 @@ public class PortUtilsTests
|
|||||||
var url = "grpc://wiremock.net:1234";
|
var url = "grpc://wiremock.net:1234";
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = PortUtils.TryExtract(url, out var isHttps, out var isGrpc, out var proto, out var host, out var port);
|
var result = PortUtils.TryExtract(url, out var isHttps, out var isGrpc, out var scheme, out var host, out var port);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
result.Should().BeTrue();
|
result.Should().BeTrue();
|
||||||
isHttps.Should().BeFalse();
|
isHttps.Should().BeFalse();
|
||||||
isGrpc.Should().BeTrue();
|
isGrpc.Should().BeTrue();
|
||||||
proto.Should().Be("grpc");
|
scheme.Should().Be("grpc");
|
||||||
host.Should().Be("wiremock.net");
|
host.Should().Be("wiremock.net");
|
||||||
port.Should().Be(1234);
|
port.Should().Be(1234);
|
||||||
}
|
}
|
||||||
@@ -105,13 +105,13 @@ public class PortUtilsTests
|
|||||||
var url = "https://0.0.0.0:5000";
|
var url = "https://0.0.0.0:5000";
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = PortUtils.TryExtract(url, out var isHttps, out var isGrpc, out var proto, out var host, out var port);
|
var result = PortUtils.TryExtract(url, out var isHttps, out var isGrpc, out var scheme, out var host, out var port);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
result.Should().BeTrue();
|
result.Should().BeTrue();
|
||||||
isHttps.Should().BeTrue();
|
isHttps.Should().BeTrue();
|
||||||
isGrpc.Should().BeFalse();
|
isGrpc.Should().BeFalse();
|
||||||
proto.Should().Be("https");
|
scheme.Should().Be("https");
|
||||||
host.Should().Be("0.0.0.0");
|
host.Should().Be("0.0.0.0");
|
||||||
port.Should().Be(5000);
|
port.Should().Be(5000);
|
||||||
}
|
}
|
||||||
|
|||||||
42
test/WireMock.Net.Tests/Validators/PathValidatorTests.cs
Normal file
42
test/WireMock.Net.Tests/Validators/PathValidatorTests.cs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
// Copyright © WireMock.Net
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using FluentAssertions;
|
||||||
|
using WireMock.Validators;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace WireMock.Net.Tests.Validators;
|
||||||
|
|
||||||
|
[ExcludeFromCodeCoverage]
|
||||||
|
public class PathValidatorTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void ValidateAndThrow_ValidPath_DoesNotThrow()
|
||||||
|
{
|
||||||
|
Action act = () => PathValidator.ValidateAndThrow("/valid/path");
|
||||||
|
act.Should().NotThrow();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(null)]
|
||||||
|
[InlineData("")]
|
||||||
|
[InlineData("\r")]
|
||||||
|
[InlineData("\n")]
|
||||||
|
[InlineData("\t")]
|
||||||
|
public void ValidateAndThrow_InvalidPath_ThrowsArgumentException_WithDefaultParamName(string? path)
|
||||||
|
{
|
||||||
|
Action act = () => PathValidator.ValidateAndThrow(path);
|
||||||
|
var ex = act.Should().Throw<ArgumentException>().Which;
|
||||||
|
ex.Message.Should().StartWith("Path must start with a '/' and cannot be null, empty or whitespace.");
|
||||||
|
ex.ParamName.Should().Be("path");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ValidateAndThrow_NoLeadingSlash_ThrowsArgumentException_WithProvidedParamName()
|
||||||
|
{
|
||||||
|
Action act = () => PathValidator.ValidateAndThrow("noSlash", "myParam");
|
||||||
|
var ex = act.Should().Throw<ArgumentException>().Which;
|
||||||
|
ex.ParamName.Should().Be("myParam");
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user