mirror of
https://github.com/wiremock/WireMock.Net.git
synced 2026-11-13 20:33:45 +01:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
69ea61e42c | ||
|
|
bc65bccce5 | ||
|
|
f6dc299ecb | ||
|
|
dc14136492 | ||
|
|
c94824bd64 | ||
|
|
54e16fc045 | ||
|
|
7d0de75ab1 | ||
|
|
afb01a0fa6 | ||
|
|
0132ec873a | ||
|
|
64734c4f92 | ||
|
|
a5589e1e2f | ||
|
|
cb4503b1a6 | ||
|
|
dd762f218b | ||
|
|
b0f9924933 | ||
|
|
dc4cdfae50 | ||
|
|
f19d94acae | ||
|
|
90819f031e | ||
|
|
471745f704 | ||
|
|
7db5b562d8 | ||
|
|
e4389333a1 | ||
|
|
f0969e9618 | ||
|
|
84f4109a7c | ||
|
|
ee0f890795 | ||
|
|
2b165eb6a1 | ||
|
|
42353f035e | ||
|
|
7e9718eab7 | ||
|
|
a0df0916f9 | ||
|
|
7a8582fef5 | ||
|
|
05929bdea6 | ||
|
|
84c32d5b2d | ||
|
|
d39f7c771f | ||
|
|
97c8e4a08a | ||
|
|
f5eb01c580 | ||
|
|
8260dba641 | ||
|
|
5e1c126e34 | ||
|
|
ca4eea808e | ||
|
|
5692498ea3 | ||
|
|
d270ad7f4f | ||
|
|
345efe8cfc | ||
|
|
a74468e780 | ||
|
|
041160d824 | ||
|
|
4666757df9 | ||
|
|
49dcbda647 | ||
|
|
cbc28c235c | ||
|
|
dd1aa5139e | ||
|
|
89b3149732 | ||
|
|
d2b794ec5a | ||
|
|
921a3ebc63 | ||
|
|
428657a97e | ||
|
|
fe9745235a | ||
|
|
1264df4a72 | ||
|
|
5b5d68be86 | ||
|
|
e92041783c | ||
|
|
6912d52faf | ||
|
|
3685fccfaf | ||
|
|
36b0a93a6c | ||
|
|
fed1c87663 | ||
|
|
e2e83abeb5 | ||
|
|
3aef0ad7a2 | ||
|
|
e68a73c3d5 | ||
|
|
bd83a630ff | ||
|
|
52ac7e37dc | ||
|
|
0eccf43a8e | ||
|
|
fa423370b1 |
No files matched your search
@@ -0,0 +1,5 @@
|
||||
# Copilot Instructions
|
||||
|
||||
## Project Guidelines
|
||||
- When running tests in this workspace, do not run tests for the net48 target framework.
|
||||
- When changing System.Text.Json code in this workspace, verify API availability for netstandard2.0 and netstandard2.1 instead of assuming newer APIs exist.
|
||||
@@ -0,0 +1,40 @@
|
||||
name: Publish to NuGet
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
name: Build, Pack, and Publish
|
||||
runs-on: windows-2022
|
||||
permissions:
|
||||
id-token: write # enable GitHub OIDC token issuance for this job
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Build projects
|
||||
shell: pwsh
|
||||
run: |
|
||||
Get-ChildItem ./src -Recurse -Filter *.csproj |
|
||||
ForEach-Object {
|
||||
dotnet build $_.FullName -c Release
|
||||
}
|
||||
|
||||
- name: Pack projects
|
||||
shell: pwsh
|
||||
run: |
|
||||
Get-ChildItem ./src -Recurse -Filter *.csproj |
|
||||
ForEach-Object {
|
||||
dotnet pack $_.FullName -c Release --no-build -o ./packages
|
||||
}
|
||||
|
||||
- name: NuGet login (OIDC → temp API key)
|
||||
uses: NuGet/login@v1
|
||||
id: login
|
||||
with:
|
||||
user: ${{ secrets.NUGET_USER }}
|
||||
|
||||
- name: Push to NuGet
|
||||
run: dotnet nuget push "**/packages/*.nupkg" --api-key ${{steps.login.outputs.NUGET_API_KEY}} --source "https://api.nuget.org/v3/index.json" --skip-duplicate
|
||||
@@ -8,6 +8,9 @@ on:
|
||||
branches:
|
||||
- 'main'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
windows-build-and-run:
|
||||
name: Run Tests on Windows
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
name: "Copilot Setup Steps"
|
||||
|
||||
# Automatically run the setup steps when they are changed to allow for easy validation, and
|
||||
# allow manual testing through the repository's "Actions" tab
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
paths:
|
||||
- .github/workflows/copilot-setup-steps.yml
|
||||
pull_request:
|
||||
paths:
|
||||
- .github/workflows/copilot-setup-steps.yml
|
||||
|
||||
jobs:
|
||||
# The job MUST be called `copilot-setup-steps` or it will not be picked up by Copilot.
|
||||
copilot-setup-steps:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
# Set the permissions to the lowest permissions possible needed for your steps.
|
||||
# Copilot will be given its own token for its operations.
|
||||
permissions:
|
||||
# If you want to clone the repository as part of your setup steps, for example to install dependencies, you'll need the `contents: read` permission. If you don't clone the repository in your setup steps, Copilot will do this for you automatically after the steps complete.
|
||||
contents: read
|
||||
|
||||
# You can define any steps you want, and they will run before the agent starts.
|
||||
# If you do not check out your code, Copilot will do this for you.
|
||||
steps:
|
||||
- name: Install .NET 10.x
|
||||
uses: actions/setup-dotnet@v5
|
||||
with:
|
||||
dotnet-version: |
|
||||
10.x
|
||||
dotnet-quality: preview
|
||||
|
||||
- name: dotnet --info
|
||||
run: dotnet --info
|
||||
+85
-2
@@ -1,16 +1,99 @@
|
||||
# 2.21.0 (28 October 2026)
|
||||
- [#1526](https://github.com/wiremock/WireMock.Net/pull/1526) - Update Scriban to 7.5.3 [dependencies] contributed by [StefH](https://github.com/StefH)
|
||||
- [#1525](https://github.com/wiremock/WireMock.Net/issues/1525) - High security vulnerability in Scriban package [security]
|
||||
|
||||
# 2.20.0 (23 October 2026)
|
||||
- [#1516](https://github.com/wiremock/WireMock.Net/pull/1516) - Add ScribanRegistrationCallback to register custom Scriban functions [feature] contributed by [Quattordici](https://github.com/Quattordici)
|
||||
- [#1519](https://github.com/wiremock/WireMock.Net/pull/1519) - Bump MessagePack from 2.5.302 to 2.5.303 [dependencies] contributed by [StefH](https://github.com/StefH)
|
||||
- [#1521](https://github.com/wiremock/WireMock.Net/pull/1521) - Add near-miss diagnostics and request count/unmatched endpoints to the admin API [feature] contributed by [Quattordici](https://github.com/Quattordici)
|
||||
- [#1522](https://github.com/wiremock/WireMock.Net/pull/1522) - Downgrade JsonPath.Net from 3.0.2 to 2.2.0 to avoid Open Source Maintenance Fee issues [dependencies] contributed by [StefH](https://github.com/StefH)
|
||||
- [#1515](https://github.com/wiremock/WireMock.Net/issues/1515) - Allow registering custom Scriban functions (ScribanRegistrationCallback), like HandlebarsRegistrationCallback [feature]
|
||||
- [#1520](https://github.com/wiremock/WireMock.Net/issues/1520) - json-everything sub-dependency now has Open Source Maintenance Fee [dependencies]
|
||||
|
||||
# 2.19.0 (07 October 2026)
|
||||
- [#1512](https://github.com/wiremock/WireMock.Net/pull/1512) - Use IDateTimeUtils in WireMockWebSocketContext [bug] contributed by [StefH](https://github.com/StefH)
|
||||
- [#1514](https://github.com/wiremock/WireMock.Net/pull/1514) - Fix DateTime in Response [bug] contributed by [StefH](https://github.com/StefH)
|
||||
- [#1511](https://github.com/wiremock/WireMock.Net/issues/1511) - Incorrect response datetime for mapped response [bug]
|
||||
|
||||
# 2.18.0 (22 September 2026)
|
||||
- [#1510](https://github.com/wiremock/WireMock.Net/pull/1510) - WireMock.Net.OpenApiParser: fix depencency on RamlToOpenApiConverter.SourceOnly [bug] contributed by [StefH](https://github.com/StefH)
|
||||
- [#1508](https://github.com/wiremock/WireMock.Net/issues/1508) - Transitive Dependency Conflict with Microsoft.OpenApi in 2.16.0 [bug]
|
||||
|
||||
# 2.17.0 (21 September 2026)
|
||||
- [#1509](https://github.com/wiremock/WireMock.Net/pull/1509) - Revert to RamlToOpenApiConverter.SourceOnly [bug] contributed by [StefH](https://github.com/StefH)
|
||||
|
||||
# 2.16.0 (19 September 2026)
|
||||
- [#1502](https://github.com/wiremock/WireMock.Net/pull/1502) - Fix #1501: Generate WhenStateIs/WillSetStateTo correctly in ToCSharpCode contributed by [Bafyn](https://github.com/Bafyn)
|
||||
- [#1503](https://github.com/wiremock/WireMock.Net/pull/1503) - Fix FormUrlEncodedMatcher (+ refactor values / matchers logic) contributed by [StefH](https://github.com/StefH)
|
||||
- [#1504](https://github.com/wiremock/WireMock.Net/pull/1504) - Update SharpYaml package to version 2.5.1 [dependencies] contributed by [johnthcall](https://github.com/johnthcall)
|
||||
- [#1505](https://github.com/wiremock/WireMock.Net/pull/1505) - Upgrade dependency RamlToOpenApiConverter to version 0.40.0 [dependencies] contributed by [StefH](https://github.com/StefH)
|
||||
- [#1500](https://github.com/wiremock/WireMock.Net/issues/1500) - Sending a form-urlencoded body with a repeated field name returns HTTP 500 [bug]
|
||||
- [#1501](https://github.com/wiremock/WireMock.Net/issues/1501) - Generated C# code for stateful (Scenario) mappings does not compile and drops the WhenStateIs condition [bug]
|
||||
|
||||
# 2.15.0 (15 August 2026)
|
||||
- [#1495](https://github.com/wiremock/WireMock.Net/pull/1495) - Fix #1494: `WithClientIP(MatchOperator, params IStringMatcher[])` ignores the MatchOperator argument [bug] contributed by [Bafyn](https://github.com/Bafyn)
|
||||
- [#1497](https://github.com/wiremock/WireMock.Net/pull/1497) - Fix WithParam RejectOnMatch inverting the match result #1496 [bug] contributed by [Bafyn](https://github.com/Bafyn)
|
||||
- [#1499](https://github.com/wiremock/WireMock.Net/pull/1499) - Update Testcontainers [bug] contributed by [StefH](https://github.com/StefH)
|
||||
- [#1494](https://github.com/wiremock/WireMock.Net/issues/1494) - `WithClientIP(MatchOperator, params IStringMatcher[])` ignores the `MatchOperator` argument [bug]
|
||||
- [#1496](https://github.com/wiremock/WireMock.Net/issues/1496) - `WithParam(key, MatchBehaviour.RejectOnMatch, values)` inverts the match result [bug]
|
||||
|
||||
# 2.14.0 (06 August 2026)
|
||||
- [#1493](https://github.com/wiremock/WireMock.Net/pull/1493) - Fix #1492: Serialize WithClientIP matcher to Request.ClientIP [bug] contributed by [Bafyn](https://github.com/Bafyn)
|
||||
- [#1492](https://github.com/wiremock/WireMock.Net/issues/1492) - `WithClientIP` matcher is lost / corrupted when a mapping is serialized [bug]
|
||||
|
||||
# 2.13.0 (19 July 2026)
|
||||
- [#1488](https://github.com/wiremock/WireMock.Net/pull/1488) - feat(xunit logging): TestOutputHelperWireMockLogger [feature] contributed by [Stepami](https://github.com/Stepami)
|
||||
- [#1490](https://github.com/wiremock/WireMock.Net/pull/1490) - Fix: Fully respect configured custom admin path (merge PR #1483) [feature] contributed by [Copilot](https://github.com/apps/copilot-swe-agent)
|
||||
- [#1482](https://github.com/wiremock/WireMock.Net/issues/1482) - WireMock.Minimal.Owin does not respect custom pathing for determining admin paths [bug]
|
||||
|
||||
# 2.12.0 (07 July 2026)
|
||||
- [#1479](https://github.com/wiremock/WireMock.Net/pull/1479) - Upgrade Microsoft.OpenApi to 3.7.0 and YamlDotNet to 18.1.0 [security] contributed by [StefH](https://github.com/StefH)
|
||||
- [#1480](https://github.com/wiremock/WireMock.Net/pull/1480) - Upgrade RamlToOpenApiConverter.SourceOnly to 0.21.0 [feature] contributed by [StefH](https://github.com/StefH)
|
||||
- [#1484](https://github.com/wiremock/WireMock.Net/pull/1484) - Update Scriban.Signed package version to 7.2.5 [security] contributed by [johnthcall](https://github.com/johnthcall)
|
||||
- [#1487](https://github.com/wiremock/WireMock.Net/pull/1487) - Response.Create() sets the DateTime to UtcNow [bug] contributed by [StefH](https://github.com/StefH)
|
||||
- [#1486](https://github.com/wiremock/WireMock.Net/issues/1486) - ResponseMessage.DateTime defaults to DateTime.MinValue, causing premature log entry purging for *matched* requests [bug]
|
||||
|
||||
# 2.11.0 (12 June 2026)
|
||||
- [#1475](https://github.com/wiremock/WireMock.Net/pull/1475) - Update NuGet packages (Aspire, MessagePack and more) [security] contributed by [StefH](https://github.com/StefH)
|
||||
- [#1476](https://github.com/wiremock/WireMock.Net/pull/1476) - Fix BodyParser [bug] contributed by [StefH](https://github.com/StefH)
|
||||
- [#1473](https://github.com/wiremock/WireMock.Net/issues/1473) - JsonPartialWildCardMatcher With DateTime Regex Stopped Matching (Regression?) [bug]
|
||||
|
||||
# 2.10.0 (07 June 2026)
|
||||
- [#1472](https://github.com/wiremock/WireMock.Net/pull/1472) - Update JsonConverter to 0.13.0 [bug] contributed by [StefH](https://github.com/StefH)
|
||||
|
||||
# 2.9.0 (02 June 2026)
|
||||
- [#1470](https://github.com/wiremock/WireMock.Net/pull/1470) - Fix WireMock.Net.Aspire project when using Aspire 13.4.0 [bug] contributed by [StefH](https://github.com/StefH)
|
||||
- [#1469](https://github.com/wiremock/WireMock.Net/issues/1469) - WireMock.Net doesn't play well with Aspire 13.4.0 [bug]
|
||||
|
||||
# 2.8.0 (31 May 2026)
|
||||
- [#1447](https://github.com/wiremock/WireMock.Net/pull/1447) - Add SystemTextJsonMatchers [feature] contributed by [StefH](https://github.com/StefH)
|
||||
- [#1466](https://github.com/wiremock/WireMock.Net/pull/1466) - Bump System.Text.RegularExpressions from 4.3.0 to 4.3.1 in Aspire Example project [dependencies, .NET] contributed by [dependabot[bot]](https://github.com/apps/dependabot)
|
||||
- [#1467](https://github.com/wiremock/WireMock.Net/pull/1467) - Bump System.Net.Http from 4.3.0 to 4.3.4 in Aspire Example project [dependencies, .NET] contributed by [dependabot[bot]](https://github.com/apps/dependabot)
|
||||
- [#1468](https://github.com/wiremock/WireMock.Net/pull/1468) - Update JsonMatcher to support IgnoreArrayOrder [feature] contributed by [StefH](https://github.com/StefH)
|
||||
- [#1439](https://github.com/wiremock/WireMock.Net/issues/1439) - JSON reference properties cannot be used when using System.Text.Json serialization [bug]
|
||||
- [#1445](https://github.com/wiremock/WireMock.Net/issues/1445) - Configurable JSON DE-serialization support (Newtonsoft.Json vs System.Text.Json) [feature]
|
||||
- [#1460](https://github.com/wiremock/WireMock.Net/issues/1460) - Option to ignore array order when comparing json body [feature]
|
||||
|
||||
# 2.7.0 (24 May 2026)
|
||||
- [#1457](https://github.com/wiremock/WireMock.Net/pull/1457) - Update OpenTelemetry.Api from 1.14.0 to 1.15.3 in unit test project [dependencies] contributed by [dependabot[bot]](https://github.com/apps/dependabot)
|
||||
- [#1459](https://github.com/wiremock/WireMock.Net/pull/1459) - chore: update Handlebars 2.5.2 to 2.5.5 [dependencies] contributed by [kamisoft-fr](https://github.com/kamisoft-fr)
|
||||
- [#1461](https://github.com/wiremock/WireMock.Net/pull/1461) - Update Testcontainers nuget package to 4.12.0 [dependencies] contributed by [MD-V](https://github.com/MD-V)
|
||||
- [#1462](https://github.com/wiremock/WireMock.Net/pull/1462) - Update Scriban.Signed to latest [dependencies] contributed by [StefH](https://github.com/StefH)
|
||||
- [#1458](https://github.com/wiremock/WireMock.Net/issues/1458) - update Handlebars Humanizer package version [feature]
|
||||
|
||||
# 2.6.0 (11 May 2026)
|
||||
- [#1455](https://github.com/wiremock/WireMock.Net/pull/1455) - Fix request storing when RequestLogExpirationDuration is set [bug] [bug] contributed by [pbenko-xitaso](https://github.com/pbenko-xitaso)
|
||||
- [#1454](https://github.com/wiremock/WireMock.Net/issues/1454) - No requests stored in Standalone when RequestLogExpirationDuration is set [bug]
|
||||
|
||||
# 2.5.0 (04 May 2026)
|
||||
- [#1451](https://github.com/wiremock/WireMock.Net/pull/1451) - Feature/early mismatch [feature] contributed by [Stepami](https://github.com/Stepami)
|
||||
- [#1452](https://github.com/wiremock/WireMock.Net/pull/1452) - Bump log4net from 2.0.15 to 3.3.0 in example console app [dependencies, .NET] contributed by [dependabot[bot]](https://github.com/apps/dependabot)
|
||||
- [#1452](https://github.com/wiremock/WireMock.Net/pull/1452) - Bump log4net from 2.0.15 to 3.3.0 in example console app [dependencies] contributed by [dependabot[bot]](https://github.com/apps/dependabot)
|
||||
- [#1453](https://github.com/wiremock/WireMock.Net/pull/1453) - Fix CVE-2026-40021: upgrade log4net to 3.3.0 in examples/WireMock.Net.Service/packages.config [dependencies] contributed by [Copilot](https://github.com/apps/copilot-swe-agent)
|
||||
- [#1442](https://github.com/wiremock/WireMock.Net/issues/1442) - Bug: [grpc] WithBodyAsProtoBuf exception on match [bug]
|
||||
|
||||
# 2.4.0 (24 April 2026)
|
||||
- [#1437](https://github.com/wiremock/WireMock.Net/pull/1437) - Added feature to enable and disable mappings [feature] contributed by [jayaraman-venkatesan](https://github.com/jayaraman-venkatesan)
|
||||
- [#1450](https://github.com/wiremock/WireMock.Net/pull/1450) - Bump OpenTelemetry.Exporter.OpenTelemetryProtocol from 1.14.0 to 1.15.x [dependencies, .NET] contributed by [dependabot[bot]](https://github.com/apps/dependabot)
|
||||
- [#1450](https://github.com/wiremock/WireMock.Net/pull/1450) - Bump OpenTelemetry.Exporter.OpenTelemetryProtocol from 1.14.0 to 1.15.x [dependencies] contributed by [dependabot[bot]](https://github.com/apps/dependabot)
|
||||
- [#1421](https://github.com/wiremock/WireMock.Net/issues/1421) - Deactivate mapping without deleting it [feature]
|
||||
|
||||
# 2.3.0 (20 April 2026)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<VersionPrefix>2.6.0</VersionPrefix>
|
||||
<VersionPrefix>2.21.0</VersionPrefix>
|
||||
<PackageIcon>WireMock.Net-Logo.png</PackageIcon>
|
||||
<PackageProjectUrl>https://github.com/wiremock/WireMock.Net</PackageProjectUrl>
|
||||
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
|
||||
@@ -51,8 +51,8 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2025.2.4" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="10.0.103" PrivateAssets="All" />
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2026.2.0" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="10.0.403" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition=" '$(TargetFramework)' == 'net8.0' ">
|
||||
|
||||
@@ -1,7 +1,17 @@
|
||||
rem https://github.com/StefH/GitHubReleaseNotes
|
||||
|
||||
SET version=2.6.0
|
||||
@echo off
|
||||
setlocal
|
||||
|
||||
for /f "usebackq delims=" %%v in (`powershell -NoProfile -Command "[xml]$props = Get-Content 'Directory.Build.props'; $props.Project.PropertyGroup | ForEach-Object { $_.VersionPrefix } | Where-Object { $_ } | Select-Object -First 1"`) do set version=%%v
|
||||
|
||||
if "%version%"=="" (
|
||||
echo Unable to determine VersionPrefix from Directory.Build.props.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
GitHubReleaseNotes --output CHANGELOG.md --skip-empty-releases --exclude-labels wontfix test question invalid doc duplicate example environment --version %version% --token %GH_TOKEN%
|
||||
if errorlevel 1 exit /b %errorlevel%
|
||||
|
||||
GitHubReleaseNotes --output PackageReleaseNotes.txt --skip-empty-releases --exclude-labels wontfix test question invalid doc duplicate example environment --template PackageReleaseNotes.template --version %version% --token %GH_TOKEN%
|
||||
GitHubReleaseNotes --output PackageReleaseNotes.txt --skip-empty-releases --exclude-labels wontfix test question invalid doc duplicate example environment --template PackageReleaseNotes.template --version %version% --token %GH_TOKEN%
|
||||
if errorlevel 1 exit /b %errorlevel%
|
||||
@@ -1,5 +1,5 @@
|
||||
# 2.6.0 (11 May 2026)
|
||||
- #1455 Fix request storing when RequestLogExpirationDuration is set [bug] [bug]
|
||||
- #1454 No requests stored in Standalone when RequestLogExpirationDuration is set [bug]
|
||||
# 2.21.0 (28 October 2026)
|
||||
- #1526 Update Scriban to 7.5.3 [dependencies]
|
||||
- #1525 High security vulnerability in Scriban package [security]
|
||||
|
||||
The full release notes can be found here: https://github.com/wiremock/WireMock.Net/blob/master/CHANGELOG.md
|
||||
@@ -39,8 +39,9 @@ A C# .NET version based on [mock4net](https://github.com/alexvictoor/mock4net) w
|
||||
| | |
|
||||
| ***Quality*** | |
|
||||
| **Build Azure** | [](https://stef.visualstudio.com/WireMock.Net/_build/latest?definitionId=61) |
|
||||
| **Build GitHub** | [](https://github.com/wiremock/WireMock.Net/actions/workflows/ci.yml)
|
||||
| **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) |
|
||||
| **SonarQube** | [](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)|
|
||||
| **TIOBE** | [TIOBE Quality Indicator](https://ticsdemo.tiobe.com/tiobeweb/DEMO/TqiDashboard.html#axes=Project(WireMock.Net),Sub()&metric=tqi)
|
||||
|
||||
@@ -64,6 +65,7 @@ A C# .NET version based on [mock4net](https://github.com/alexvictoor/mock4net) w
|
||||
| | | |
|
||||
| **WireMock.Net.Extensions.Routing** | [](https://www.nuget.org/packages/WireMock.Net.Extensions.Routing) | [](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.Extensions.Routing)
|
||||
| **WireMock.Net.Matchers.CSharpCode** | [](https://www.nuget.org/packages/WireMock.Net.Matchers.CSharpCode) | [](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.Matchers.CSharpCode)
|
||||
| **WireMock.Net.Matchers.SystemTextJsonPath** | [](https://www.nuget.org/packages/WireMock.Net.Matchers.SystemTextJsonPath) | [](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.Matchers.SystemTextJsonPath)
|
||||
| **WireMock.Net.OpenApiParser** | [](https://www.nuget.org/packages/WireMock.Net.OpenApiParser) | [](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.OpenApiParser)
|
||||
| **WireMock.Net.MimePart** | [](https://www.nuget.org/packages/WireMock.Net.MimePart) | [](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.MimePart)
|
||||
| **WireMock.Net.GraphQL** | [](https://www.nuget.org/packages/WireMock.Net.GraphQL) | [](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.GraphQL)
|
||||
@@ -76,7 +78,7 @@ A C# .NET version based on [mock4net](https://github.com/alexvictoor/mock4net) w
|
||||
|
||||
<br />
|
||||
|
||||
🔺 **WireMock.Net.Minimal** does not include *WireMock.Net.MimePart*, *WireMock.Net.GraphQL*, *WireMock.Net.ProtoBuf* and *WireMock.Net.OpenTelemetry*.
|
||||
🔺 **WireMock.Net.Minimal** does not include *WireMock.Net.MimePart*, *WireMock.Net.GraphQL*, *WireMock.Net.ProtoBuf*, *WireMock.Net.OpenTelemetry* and *WireMock.Net.Matchers.SystemTextJsonPath*.
|
||||
|
||||
---
|
||||
|
||||
|
||||
+30
-33
@@ -1,4 +1,3 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 18
|
||||
VisualStudioVersion = 18.0.11205.157
|
||||
@@ -38,8 +37,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.Client", "examples\WireMock.Net.Client\WireMock.Net.Client.csproj", "{74D91AD0-D96D-4FD2-AEC5-CC49D38346C0}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.Service", "examples\Wiremock.Net.Service\WireMock.Net.Service.csproj", "{7F0B2446-0363-4720-AF46-F47F83B557DC}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.Console.RequestLogTest", "examples\WireMock.Net.Console.RequestLogTest\WireMock.Net.Console.RequestLogTest.csproj", "{A9D039B9-7509-4CF1-9EFD-87EB82998575}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.OpenApiParser.ConsoleApp", "examples\WireMock.Net.OpenApiParser.ConsoleApp\WireMock.Net.OpenApiParser.ConsoleApp.csproj", "{5C09FB93-1535-4F92-AF26-21E8A061EE4A}"
|
||||
@@ -76,10 +73,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.Testcontainers
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.TestcontainersExample", "examples\WireMock.Net.TestcontainersExample\WireMock.Net.TestcontainersExample.csproj", "{56A38798-C48B-4A4A-B805-071E05C02CE1}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{0147029F-FA4A-44B3-B79A-3C3574054EE4}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MultipartUploader", "tools\MultipartUploader\MultipartUploader.csproj", "{07C30227-ADEC-4BDE-8CDC-849D85A690BB}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.Console.NET8", "examples\WireMock.Net.Console.NET8\WireMock.Net.Console.NET8.csproj", "{1EA72C0F-92E9-486B-8FFE-53F992BFC4AA}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMockAzureQueueProxy", "examples\WireMockAzureQueueProxy\WireMockAzureQueueProxy.csproj", "{7FC0B409-2682-40EE-B3B9-3930D6769D01}"
|
||||
@@ -156,6 +149,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.TestWebApplica
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.RestClient.AwesomeAssertions", "src\WireMock.Net.RestClient.AwesomeAssertions\WireMock.Net.RestClient.AwesomeAssertions.csproj", "{F4B2B967-98D7-4D93-9A5C-5EF7B84B941A}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.Matchers.SystemTextJsonPath", "src\WireMock.Net.Matchers.SystemTextJsonPath\WireMock.Net.Matchers.SystemTextJsonPath.csproj", "{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.Service", "examples\WireMock.Net.Service\WireMock.Net.Service.csproj", "{7F0B2446-0363-4720-AF46-F47F83B557DC}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -238,18 +235,6 @@ Global
|
||||
{74D91AD0-D96D-4FD2-AEC5-CC49D38346C0}.Release|x64.Build.0 = Release|Any CPU
|
||||
{74D91AD0-D96D-4FD2-AEC5-CC49D38346C0}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{74D91AD0-D96D-4FD2-AEC5-CC49D38346C0}.Release|x86.Build.0 = Release|Any CPU
|
||||
{7F0B2446-0363-4720-AF46-F47F83B557DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{7F0B2446-0363-4720-AF46-F47F83B557DC}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{7F0B2446-0363-4720-AF46-F47F83B557DC}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{7F0B2446-0363-4720-AF46-F47F83B557DC}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{7F0B2446-0363-4720-AF46-F47F83B557DC}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{7F0B2446-0363-4720-AF46-F47F83B557DC}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{7F0B2446-0363-4720-AF46-F47F83B557DC}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{7F0B2446-0363-4720-AF46-F47F83B557DC}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{7F0B2446-0363-4720-AF46-F47F83B557DC}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{7F0B2446-0363-4720-AF46-F47F83B557DC}.Release|x64.Build.0 = Release|Any CPU
|
||||
{7F0B2446-0363-4720-AF46-F47F83B557DC}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{7F0B2446-0363-4720-AF46-F47F83B557DC}.Release|x86.Build.0 = Release|Any CPU
|
||||
{A9D039B9-7509-4CF1-9EFD-87EB82998575}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A9D039B9-7509-4CF1-9EFD-87EB82998575}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A9D039B9-7509-4CF1-9EFD-87EB82998575}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
@@ -394,18 +379,6 @@ Global
|
||||
{56A38798-C48B-4A4A-B805-071E05C02CE1}.Release|x64.Build.0 = Release|Any CPU
|
||||
{56A38798-C48B-4A4A-B805-071E05C02CE1}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{56A38798-C48B-4A4A-B805-071E05C02CE1}.Release|x86.Build.0 = Release|Any CPU
|
||||
{07C30227-ADEC-4BDE-8CDC-849D85A690BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{07C30227-ADEC-4BDE-8CDC-849D85A690BB}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{07C30227-ADEC-4BDE-8CDC-849D85A690BB}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{07C30227-ADEC-4BDE-8CDC-849D85A690BB}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{07C30227-ADEC-4BDE-8CDC-849D85A690BB}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{07C30227-ADEC-4BDE-8CDC-849D85A690BB}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{07C30227-ADEC-4BDE-8CDC-849D85A690BB}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{07C30227-ADEC-4BDE-8CDC-849D85A690BB}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{07C30227-ADEC-4BDE-8CDC-849D85A690BB}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{07C30227-ADEC-4BDE-8CDC-849D85A690BB}.Release|x64.Build.0 = Release|Any CPU
|
||||
{07C30227-ADEC-4BDE-8CDC-849D85A690BB}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{07C30227-ADEC-4BDE-8CDC-849D85A690BB}.Release|x86.Build.0 = Release|Any CPU
|
||||
{1EA72C0F-92E9-486B-8FFE-53F992BFC4AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{1EA72C0F-92E9-486B-8FFE-53F992BFC4AA}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{1EA72C0F-92E9-486B-8FFE-53F992BFC4AA}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
@@ -850,6 +823,30 @@ Global
|
||||
{F4B2B967-98D7-4D93-9A5C-5EF7B84B941A}.Release|x64.Build.0 = Release|Any CPU
|
||||
{F4B2B967-98D7-4D93-9A5C-5EF7B84B941A}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{F4B2B967-98D7-4D93-9A5C-5EF7B84B941A}.Release|x86.Build.0 = Release|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x64.Build.0 = Release|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x86.Build.0 = Release|Any CPU
|
||||
{7F0B2446-0363-4720-AF46-F47F83B557DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{7F0B2446-0363-4720-AF46-F47F83B557DC}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{7F0B2446-0363-4720-AF46-F47F83B557DC}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{7F0B2446-0363-4720-AF46-F47F83B557DC}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{7F0B2446-0363-4720-AF46-F47F83B557DC}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{7F0B2446-0363-4720-AF46-F47F83B557DC}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{7F0B2446-0363-4720-AF46-F47F83B557DC}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{7F0B2446-0363-4720-AF46-F47F83B557DC}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{7F0B2446-0363-4720-AF46-F47F83B557DC}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{7F0B2446-0363-4720-AF46-F47F83B557DC}.Release|x64.Build.0 = Release|Any CPU
|
||||
{7F0B2446-0363-4720-AF46-F47F83B557DC}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{7F0B2446-0363-4720-AF46-F47F83B557DC}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -861,7 +858,6 @@ Global
|
||||
{B6269AAC-170A-43D5-8B9A-579DED3D9A95} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2}
|
||||
{31DC2EF8-C3FE-467D-84BE-FB5D956E612E} = {0BB8B634-407A-4610-A91F-11586990767A}
|
||||
{74D91AD0-D96D-4FD2-AEC5-CC49D38346C0} = {985E0ADB-D4B4-473A-AA40-567E279B7946}
|
||||
{7F0B2446-0363-4720-AF46-F47F83B557DC} = {985E0ADB-D4B4-473A-AA40-567E279B7946}
|
||||
{A9D039B9-7509-4CF1-9EFD-87EB82998575} = {985E0ADB-D4B4-473A-AA40-567E279B7946}
|
||||
{5C09FB93-1535-4F92-AF26-21E8A061EE4A} = {985E0ADB-D4B4-473A-AA40-567E279B7946}
|
||||
{B6269AAC-170A-4346-8B9A-579DED3D9A95} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2}
|
||||
@@ -876,7 +872,6 @@ Global
|
||||
{BAA9EC2A-874B-45CE-8E51-A73622DC7F3D} = {985E0ADB-D4B4-473A-AA40-567E279B7946}
|
||||
{12B016A5-9D8B-4EFE-96C2-CA51BE43367D} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2}
|
||||
{56A38798-C48B-4A4A-B805-071E05C02CE1} = {985E0ADB-D4B4-473A-AA40-567E279B7946}
|
||||
{07C30227-ADEC-4BDE-8CDC-849D85A690BB} = {0147029F-FA4A-44B3-B79A-3C3574054EE4}
|
||||
{1EA72C0F-92E9-486B-8FFE-53F992BFC4AA} = {985E0ADB-D4B4-473A-AA40-567E279B7946}
|
||||
{7FC0B409-2682-40EE-B3B9-3930D6769D01} = {985E0ADB-D4B4-473A-AA40-567E279B7946}
|
||||
{B1580A38-84E7-44BE-8FE7-3EE5031D74A1} = {985E0ADB-D4B4-473A-AA40-567E279B7946}
|
||||
@@ -914,6 +909,8 @@ Global
|
||||
{2CE8E3A6-59CC-FE9C-9399-AD54E1FA862B} = {985E0ADB-D4B4-473A-AA40-567E279B7946}
|
||||
{3B05CC76-C3CB-8667-6B65-3129DFB25681} = {0BB8B634-407A-4610-A91F-11586990767A}
|
||||
{F4B2B967-98D7-4D93-9A5C-5EF7B84B941A} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2}
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2}
|
||||
{7F0B2446-0363-4720-AF46-F47F83B557DC} = {985E0ADB-D4B4-473A-AA40-567E279B7946}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {DC539027-9852-430C-B19F-FD035D018458}
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
parameters:
|
||||
- name: runWindowsReleaseToMyGet
|
||||
displayName: Run Windows Release to MyGet
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
variables:
|
||||
Prerelease: 'ci'
|
||||
buildId: "1$(Build.BuildId)"
|
||||
@@ -131,6 +137,7 @@ jobs:
|
||||
|
||||
- job: Windows_Release_to_MyGet
|
||||
dependsOn: Windows_Build_Test
|
||||
condition: and(succeeded(), ${{ parameters.runWindowsReleaseToMyGet }})
|
||||
|
||||
pool:
|
||||
vmImage: 'windows-2025'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<Sdk Name="Aspire.AppHost.Sdk" Version="13.1.0" />
|
||||
<Sdk Name="Aspire.AppHost.Sdk" Version="13.4.0" />
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
@@ -18,7 +18,10 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Aspire.Hosting.AppHost" Version="13.1.0" />
|
||||
<PackageReference Include="Aspire.Hosting.AppHost" Version="13.4.3" />
|
||||
|
||||
<!-- CVE-2026-48109 / CVE-2026-92707 -->
|
||||
<PackageReference Include="MessagePack" Version="2.5.303" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<Sdk Name="Aspire.AppHost.Sdk" Version="13.1.0" />
|
||||
<Sdk Name="Aspire.AppHost.Sdk" Version="13.4.0" />
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
@@ -15,7 +15,10 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Aspire.Hosting.AppHost" Version="13.1.0" />
|
||||
<PackageReference Include="Aspire.Hosting.AppHost" Version="13.4.3" />
|
||||
|
||||
<!-- CVE-2026-48109 / CVE-2026-92707 -->
|
||||
<PackageReference Include="MessagePack" Version="2.5.303" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -12,6 +12,8 @@
|
||||
<PackageReference Include="Aspire.Hosting.Testing" Version="13.1.0" />
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
|
||||
<PackageReference Include="System.Net.Http" Version="4.3.4" />
|
||||
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
|
||||
<PackageReference Include="xunit" Version="2.5.3" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using RestEase;
|
||||
using WireMock.Admin.Settings;
|
||||
|
||||
@@ -9,9 +9,9 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Google.Protobuf" Version="3.25.1" />
|
||||
<PackageReference Include="Grpc.Net.Client" Version="2.60.0" />
|
||||
<PackageReference Include="Grpc.Tools" Version="2.60.0">
|
||||
<PackageReference Include="Google.Protobuf" Version="3.33.5" />
|
||||
<PackageReference Include="Grpc.Net.Client" Version="2.76.0" />
|
||||
<PackageReference Include="Grpc.Tools" Version="2.78.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace WireMock.Net.Console.MimePart;
|
||||
|
||||
static class Program
|
||||
{
|
||||
private static readonly ILoggerRepository LogRepository = LogManager.GetRepository(Assembly.GetEntryAssembly());
|
||||
private static readonly ILoggerRepository LogRepository = LogManager.GetRepository(Assembly.GetEntryAssembly()!);
|
||||
private static readonly ILog Log = LogManager.GetLogger(typeof(Program));
|
||||
|
||||
static async Task Main(params string[] args)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System.IO;
|
||||
using WireMock.Logging;
|
||||
using WireMock.Server;
|
||||
using WireMock.Settings;
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using SharpYaml.Model;
|
||||
using Scriban.Runtime;
|
||||
using WireMock.Logging;
|
||||
using WireMock.Matchers;
|
||||
using WireMock.Models;
|
||||
@@ -291,11 +286,21 @@ namespace WireMock.Net.ConsoleApplication
|
||||
|
||||
var server = WireMockServer.Start(new WireMockServerSettings
|
||||
{
|
||||
StartAdminInterface = true,
|
||||
Logger = new WireMockConsoleLogger(),
|
||||
|
||||
Port = 9091
|
||||
});
|
||||
|
||||
server
|
||||
.WhenRequest(r => r
|
||||
.WithPath("/test")
|
||||
.UsingAnyMethod()
|
||||
)
|
||||
.ThenRespondWith(r => r
|
||||
.WithStatusCode(HttpStatusCode.OK)
|
||||
.WithBody("OK")
|
||||
);
|
||||
|
||||
server
|
||||
.WhenRequest(r => r
|
||||
.WithPath("/Content-Length")
|
||||
@@ -405,12 +410,26 @@ namespace WireMock.Net.ConsoleApplication
|
||||
// handlebarsContext.RegisterHelper(transformer.Name, transformer.Render); TODO
|
||||
},
|
||||
|
||||
ScribanRegistrationCallback = (scriptObject, fileSystemHandler) =>
|
||||
{
|
||||
var globals = (ScriptObject)scriptObject;
|
||||
globals.Import("greet", new Func<string, string>(name => $"Hello {name}"));
|
||||
},
|
||||
|
||||
// Uncomment below if you want to use the CustomFileSystemFileHandler
|
||||
// FileSystemHandler = new CustomFileSystemFileHandler()
|
||||
});
|
||||
System.Console.WriteLine("WireMockServer listening at {0}", string.Join(",", server.Urls));
|
||||
|
||||
server.SetBasicAuthentication("a", "b");
|
||||
|
||||
server
|
||||
.Given(Request.Create().WithPath("/scriban/greet").UsingGet())
|
||||
.RespondWith(Response.Create()
|
||||
.WithBody("{{ greet 'WireMock.Net' }}")
|
||||
.WithTransformer(TransformerType.Scriban)
|
||||
);
|
||||
|
||||
//server.SetAzureADAuthentication(Environment.GetEnvironmentVariable("WIREMOCK_AAD_TENANT")!, "api://e083d51a-01a6-446c-8ad5-0c5c7f002208");
|
||||
|
||||
//var http = new HttpClient();
|
||||
@@ -1115,7 +1134,7 @@ namespace WireMock.Net.ConsoleApplication
|
||||
{
|
||||
BodyData = new BodyData
|
||||
{
|
||||
BodyAsString = "random200or505:" + code + ", HeadersFromRequest = " + string.Join(",", request.Headers),
|
||||
BodyAsString = "random200or505:" + code + ", HeadersFromRequest = " + request.Headers != null ? string.Join(",", request.Headers!.Select(h => $"{h.Key}:{h.Value}")) : "none",
|
||||
DetectedBodyType = BodyType.String,
|
||||
},
|
||||
StatusCode = code
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using log4net;
|
||||
using log4net.Config;
|
||||
using log4net.Repository;
|
||||
@@ -12,8 +10,7 @@ namespace WireMock.Net.Console.NET8;
|
||||
|
||||
static class Program
|
||||
{
|
||||
private static readonly ILoggerRepository LogRepository = LogManager.GetRepository(Assembly.GetEntryAssembly());
|
||||
private static readonly ILog Log = LogManager.GetLogger(typeof(Program));
|
||||
private static readonly ILoggerRepository LogRepository = LogManager.GetRepository(Assembly.GetEntryAssembly()!);
|
||||
|
||||
static async Task Main(params string[] args)
|
||||
{
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using WireMock.Logging;
|
||||
using WireMock.Net.StandAlone;
|
||||
using WireMock.RequestBuilders;
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="WireMock.Net" Version="1.25.0" />
|
||||
<PackageReference Include="WireMock.Net" Version="2.15.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,7 +1,5 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using WireMock.RequestBuilders;
|
||||
using WireMock.ResponseBuilders;
|
||||
|
||||
|
||||
+1
-6
@@ -6,18 +6,13 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.OpenApi.YamlReader" Version="2.3.0" />
|
||||
|
||||
<ProjectReference Include="..\..\src\WireMock.Net.Abstractions\WireMock.Net.Abstractions.csproj" />
|
||||
<ProjectReference Include="..\..\src\WireMock.Net.OpenApiParser\WireMock.Net.OpenApiParser.csproj" />
|
||||
<ProjectReference Include="..\..\src\WireMock.Net\WireMock.Net.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(Configuration)' == 'Release'">
|
||||
<PackageReference Include="Microsoft.OpenApi" Version="2.3.0" PrivateAssets="All" />
|
||||
<!--<PackageReference Include="Microsoft.OpenApi.YamlReader" Version="2.3.0" PrivateAssets="All" />
|
||||
<PackageReference Include="System.Text.Json" Version="8.0.5" />
|
||||
<PackageReference Include="SharpYaml" Version="2.1.3" />-->
|
||||
<PackageReference Include="Microsoft.OpenApi" Version="3.10.2" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -32,13 +32,10 @@
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<StartupObject>Wiremock.Net.Service.Program</StartupObject>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Newtonsoft.Json.13.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="SimMetrics.Net, Version=1.0.5.0, Culture=neutral, PublicKeyToken=c58dc06d59f3391b, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\SimMetrics.Net.1.0.5\lib\net45\SimMetrics.Net.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Configuration" />
|
||||
<Reference Include="System.Configuration.Install" />
|
||||
@@ -88,13 +85,13 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="log4net">
|
||||
<Version>3.3.0</Version>
|
||||
<Version>3.3.2</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel.Core">
|
||||
<Version>2.3.9</Version>
|
||||
<Version>2.3.11</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="WireMock.Net">
|
||||
<Version>1.25.0</Version>
|
||||
<Version>2.12.0</Version>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
|
||||
@@ -246,8 +246,8 @@ internal class Program
|
||||
throw new InvalidOperationException($"The {nameof(TestcontainersSettings.OS.DockerEndpointAuthConfig)} is null. Check if Docker is started.");
|
||||
}
|
||||
|
||||
using var dockerClientConfig = TestcontainersSettings.OS.DockerEndpointAuthConfig.GetDockerClientConfiguration();
|
||||
using var dockerClient = dockerClientConfig.CreateClient();
|
||||
var dockerClientBuilder = TestcontainersSettings.OS.DockerEndpointAuthConfig.GetDockerClientBuilder();
|
||||
using var dockerClient = dockerClientBuilder.Build();
|
||||
|
||||
var version = await dockerClient.System.GetVersionAsync();
|
||||
return version.Os.IndexOf("Windows", StringComparison.OrdinalIgnoreCase) >= 0 ? OSPlatform.Windows : OSPlatform.Linux;
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
namespace WireMock.Admin.Requests;
|
||||
|
||||
/// <summary>
|
||||
/// NearMiss Model: a mapping which did not (fully) match a request.
|
||||
/// </summary>
|
||||
public class NearMissModel
|
||||
{
|
||||
/// <summary>
|
||||
/// The unique identifier of the log entry for the request. [Optional]
|
||||
/// </summary>
|
||||
public Guid? LogEntryGuid { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The request.
|
||||
/// </summary>
|
||||
public LogRequestModel? Request { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The unique identifier of the mapping.
|
||||
/// </summary>
|
||||
public Guid MappingGuid { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The title of the mapping.
|
||||
/// </summary>
|
||||
public string? MappingTitle { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The request match result, containing the score per matcher.
|
||||
/// </summary>
|
||||
public LogRequestMatchModel? RequestMatchResult { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
namespace WireMock.Admin.Requests;
|
||||
|
||||
/// <summary>
|
||||
/// RequestCount Model
|
||||
/// </summary>
|
||||
public class RequestCountModel
|
||||
{
|
||||
/// <summary>
|
||||
/// The number of requests which match the criteria.
|
||||
/// </summary>
|
||||
public int Count { get; set; }
|
||||
}
|
||||
@@ -32,7 +32,7 @@ public interface IRequestMessage
|
||||
string? ProxyUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the DateTime.
|
||||
/// The DateTime when this request was done.
|
||||
/// </summary>
|
||||
DateTime DateTime { get; }
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ public interface IResponseMessage
|
||||
object? StatusCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the DateTime.
|
||||
/// The DateTime when this response was sent.
|
||||
/// </summary>
|
||||
DateTime DateTime { get; }
|
||||
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
namespace WireMock.Matchers.Request;
|
||||
|
||||
/// <summary>
|
||||
/// A mapping which did not (fully) match a request, together with the match result explaining why.
|
||||
/// </summary>
|
||||
public class NearMiss
|
||||
{
|
||||
/// <summary>
|
||||
/// The request which was evaluated against the mapping.
|
||||
/// </summary>
|
||||
public required IRequestMessage RequestMessage { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The unique identifier of the log entry for the request. [Optional]
|
||||
/// </summary>
|
||||
public Guid? LogEntryGuid { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The unique identifier of the mapping.
|
||||
/// </summary>
|
||||
public required Guid MappingGuid { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The title of the mapping. [Optional]
|
||||
/// </summary>
|
||||
public string? MappingTitle { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The match result, containing the score per matcher.
|
||||
/// </summary>
|
||||
public required IRequestMatchResult RequestMatchResult { get; init; }
|
||||
}
|
||||
@@ -46,7 +46,7 @@ public interface IBodyData
|
||||
/// <summary>
|
||||
/// The body as Form UrlEncoded dictionary.
|
||||
/// </summary>
|
||||
IDictionary<string, string>? BodyAsFormUrlEncoded { get; set; }
|
||||
IDictionary<string, WireMockList<string>>? BodyAsFormUrlEncoded { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The detected body type (detection based on body content).
|
||||
|
||||
@@ -107,6 +107,39 @@ public interface IWireMockServer : IDisposable
|
||||
/// <returns>The <see cref="IReadOnlyList{ILogEntry}"/>.</returns>
|
||||
IReadOnlyList<ILogEntry> FindLogEntries(params IRequestMatcher[] matchers);
|
||||
|
||||
/// <summary>
|
||||
/// Get the log-entries for the requests which did not fully match any mapping.
|
||||
/// This includes the requests which were served by a partial match when AllowPartialMapping is enabled.
|
||||
/// </summary>
|
||||
/// <returns>The <see cref="IReadOnlyList{ILogEntry}"/>.</returns>
|
||||
IReadOnlyList<ILogEntry> FindUnmatchedLogEntries();
|
||||
|
||||
/// <summary>
|
||||
/// Find the mappings which partially match the request (the near misses), ordered by best match first.
|
||||
/// Perfect matches and mappings which do not match at all are excluded.
|
||||
/// Use this to find out why a request did not match the expected mapping.
|
||||
/// </summary>
|
||||
/// <param name="requestMessage">The request to evaluate.</param>
|
||||
/// <param name="maxResults">The maximum number of near misses to return. Default is 3.</param>
|
||||
/// <returns>The <see cref="IReadOnlyList{NearMiss}"/>.</returns>
|
||||
IReadOnlyList<NearMiss> FindNearMisses(IRequestMessage requestMessage, int maxResults = 3);
|
||||
|
||||
/// <summary>
|
||||
/// Find the mappings which partially match the request of a log-entry (the near misses), ordered by best match first.
|
||||
/// Perfect matches and mappings which do not match at all are excluded.
|
||||
/// </summary>
|
||||
/// <param name="logEntry">The log-entry for which to evaluate the request.</param>
|
||||
/// <param name="maxResults">The maximum number of near misses to return. Default is 3.</param>
|
||||
/// <returns>The <see cref="IReadOnlyList{NearMiss}"/>.</returns>
|
||||
IReadOnlyList<NearMiss> FindNearMisses(ILogEntry logEntry, int maxResults = 3);
|
||||
|
||||
/// <summary>
|
||||
/// Find the near misses for all requests which did not fully match any mapping.
|
||||
/// </summary>
|
||||
/// <param name="maxResultsPerRequest">The maximum number of near misses to return per request. Default is 3.</param>
|
||||
/// <returns>The <see cref="IReadOnlyList{NearMiss}"/>.</returns>
|
||||
IReadOnlyList<NearMiss> FindNearMissesForUnmatchedRequests(int maxResultsPerRequest = 3);
|
||||
|
||||
/// <summary>
|
||||
/// Reads a static mapping file and adds or updates a single mapping.
|
||||
///
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System.Linq;
|
||||
|
||||
namespace WireMock.Types;
|
||||
|
||||
/// <summary>
|
||||
/// A special List which overrides the ToString() to return first value.
|
||||
/// A special List which overrides the ToString() to return first value in case of a single element.
|
||||
/// Else it will return a comma separated list of all values.
|
||||
/// If null or empty, it will return an empty string.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The generic type</typeparam>
|
||||
/// <seealso cref="List{T}" />
|
||||
@@ -46,6 +46,98 @@ public class WireMockList<T> : List<T>
|
||||
/// <param name="values">The values to set.</param>
|
||||
public static implicit operator WireMockList<T>(T[] values) => new(values);
|
||||
|
||||
/// <summary>
|
||||
/// Operator for equality comparison from WireMockList to T
|
||||
/// </summary>
|
||||
public static bool operator ==(WireMockList<T>? left, T? right)
|
||||
{
|
||||
if (ReferenceEquals(left, right))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (left?.Count == 1 && Equals(left[0], right))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Operator for equality comparison from T to WireMockList
|
||||
/// </summary>
|
||||
public static bool operator ==(T? left, WireMockList<T>? right)
|
||||
{
|
||||
if (ReferenceEquals(left, right))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (right?.Count == 1 && Equals(left, right[0]))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Operator for inequality comparison from WireMockList to T
|
||||
/// </summary>
|
||||
public static bool operator !=(WireMockList<T>? left, T? right) => !(left == right);
|
||||
|
||||
/// <summary>
|
||||
/// Operator for inequality comparison from T to WireMockList
|
||||
/// </summary>
|
||||
public static bool operator !=(T? left, WireMockList<T>? right) => !(left == right);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified object is equal to the current instance.
|
||||
/// Two <see cref="WireMockList{T}"/> instances are equal if they contain the same elements in the same order.
|
||||
/// </summary>
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if (ReferenceEquals(this, obj))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (obj is WireMockList<T> other)
|
||||
{
|
||||
if (Count != other.Count)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (var i = 0; i < Count; i++)
|
||||
{
|
||||
if (!Equals(this[i], other[i]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a hash code for this instance based on its elements.
|
||||
/// </summary>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
var hashCode = 17;
|
||||
foreach (var item in this)
|
||||
{
|
||||
hashCode = hashCode * 31 + (item?.GetHashCode() ?? 0);
|
||||
}
|
||||
|
||||
return hashCode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="string" /> that represents this instance.
|
||||
/// </summary>
|
||||
@@ -62,10 +154,10 @@ public class WireMockList<T> : List<T>
|
||||
return strValue;
|
||||
}
|
||||
|
||||
return this[0]?.ToString();
|
||||
return this[0]?.ToString() ?? string.Empty;
|
||||
|
||||
default:
|
||||
var strings = this.Select(x => x as string ?? x?.ToString());
|
||||
var strings = this.Select(x => x as string ?? x?.ToString() ?? string.Empty);
|
||||
return string.Join(", ", strings);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
</ItemGroup>-->
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentBuilder" Version="0.14.0">
|
||||
<PackageReference Include="FluentBuilder" Version="0.15.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
||||
@@ -45,7 +45,10 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Aspire.Hosting" Version="13.1.0" />
|
||||
<PackageReference Include="Aspire.Hosting" Version="13.4.3" />
|
||||
|
||||
<!-- CVE-2026-48109 / CVE-2026-92707 -->
|
||||
<PackageReference Include="MessagePack" Version="2.5.303" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -21,11 +21,6 @@ public class WireMockHealthCheck(WireMockServerResource resource) : IHealthCheck
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -41,4 +36,4 @@ public class WireMockHealthCheck(WireMockServerResource resource) : IHealthCheck
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,13 @@ internal class WireMockLifecycleSubscriber(ILoggerFactory loggerFactory) : IDist
|
||||
|
||||
var endpoint = wireMockServerResource.GetEndpoint();
|
||||
Debug.Assert(endpoint.IsAllocated);
|
||||
}
|
||||
});
|
||||
|
||||
eventing.Subscribe<ResourceReadyEvent>(async (@event, ct) =>
|
||||
{
|
||||
if (@event.Resource is WireMockServerResource wireMockServerResource)
|
||||
{
|
||||
await wireMockServerResource.WaitForHealthAsync(ct);
|
||||
|
||||
await wireMockServerResource.CallAddProtoDefinitionsAsync(ct);
|
||||
|
||||
@@ -31,19 +31,19 @@ public partial class WireMockAssertions
|
||||
}
|
||||
|
||||
[CustomAssertion]
|
||||
public AndConstraint<WireMockAssertions> WithBodyAsJson(object body, string because = "", params object[] becauseArgs)
|
||||
public AndConstraint<WireMockAssertions> WithBodyAsJson(object body, IJsonMatcher? jsonMatcher = null, string because = "", params object[] becauseArgs)
|
||||
{
|
||||
return WithBodyAsJson(new JsonMatcher(body), because, becauseArgs);
|
||||
return WithBodyAsJson(jsonMatcher ?? new JsonMatcher(body), because, becauseArgs);
|
||||
}
|
||||
|
||||
[CustomAssertion]
|
||||
public AndConstraint<WireMockAssertions> WithBodyAsJson(string body, string because = "", params object[] becauseArgs)
|
||||
public AndConstraint<WireMockAssertions> WithBodyAsJson(string body, IJsonMatcher? jsonMatcher = null, string because = "", params object[] becauseArgs)
|
||||
{
|
||||
return WithBodyAsJson(new JsonMatcher(body), because, becauseArgs);
|
||||
return WithBodyAsJson(jsonMatcher ?? new JsonMatcher(body), because, becauseArgs);
|
||||
}
|
||||
|
||||
[CustomAssertion]
|
||||
public AndConstraint<WireMockAssertions> WithBodyAsJson(IObjectMatcher matcher, string because = "", params object[] becauseArgs)
|
||||
public AndConstraint<WireMockAssertions> WithBodyAsJson(IJsonMatcher matcher, string because = "", params object[] becauseArgs)
|
||||
{
|
||||
var (filter, condition) = BuildFilterAndCondition(r => r.BodyAsJson, matcher);
|
||||
|
||||
@@ -126,15 +126,44 @@ public partial class WireMockAssertions
|
||||
|
||||
private static string? FormatBody(object? body)
|
||||
{
|
||||
return body switch
|
||||
if (body == null)
|
||||
{
|
||||
null => null,
|
||||
string str => str,
|
||||
AnyOf<string, StringPattern>[] stringPatterns => FormatBodies(stringPatterns.Select(p => p.GetPattern())),
|
||||
byte[] bytes => $"byte[{bytes.Length}] {{...}}",
|
||||
JToken jToken => jToken.ToString(Formatting.None),
|
||||
_ => JToken.FromObject(body).ToString(Formatting.None)
|
||||
};
|
||||
return null;
|
||||
}
|
||||
|
||||
if (body is string str)
|
||||
{
|
||||
return str;
|
||||
}
|
||||
|
||||
if (body is AnyOf<string, StringPattern>[] stringPatterns)
|
||||
{
|
||||
return FormatBodies(stringPatterns.Select(p => p.GetPattern()));
|
||||
}
|
||||
|
||||
if (body is byte[] bytes)
|
||||
{
|
||||
return $"byte[{bytes.Length}] {{...}}";
|
||||
}
|
||||
|
||||
if (body is JToken jToken)
|
||||
{
|
||||
return jToken.ToString(Formatting.None);
|
||||
}
|
||||
|
||||
// System.IO.FileNotFoundException : Could not load file or assembly 'System.Text.Json, Version=10.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51'. The system cannot find the file specified.
|
||||
var typeName = body.GetType().FullName;
|
||||
if (typeName == "System.Text.Json.JsonElement")
|
||||
{
|
||||
return ((dynamic)body).GetRawText();
|
||||
}
|
||||
|
||||
if (typeName == "System.Text.Json.JsonDocument")
|
||||
{
|
||||
return ((dynamic)body).RootElement.GetRawText();
|
||||
}
|
||||
|
||||
return JToken.FromObject(body).ToString(Formatting.None);
|
||||
}
|
||||
|
||||
private static string? FormatBodies(IEnumerable<object?> bodies)
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AwesomeAssertions" Version="9.4.0" />
|
||||
<PackageReference Include="AwesomeAssertions" Version="9.6.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<Description>WireMock.Net.Routing extends WireMock.Net with modern, minimal-API-style routing for .NET</Description>
|
||||
<Authors>Gennadii Saltyshchak</Authors>
|
||||
@@ -25,7 +25,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="JsonConverter.Abstractions" Version="0.9.0" />
|
||||
<PackageReference Include="JsonConverter.Abstractions" Version="0.13.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -31,19 +31,19 @@ public partial class WireMockAssertions
|
||||
}
|
||||
|
||||
[CustomAssertion]
|
||||
public AndConstraint<WireMockAssertions> WithBodyAsJson(object body, string because = "", params object[] becauseArgs)
|
||||
public AndConstraint<WireMockAssertions> WithBodyAsJson(object body, IJsonMatcher? jsonMatcher = null, string because = "", params object[] becauseArgs)
|
||||
{
|
||||
return WithBodyAsJson(new JsonMatcher(body), because, becauseArgs);
|
||||
return WithBodyAsJson(jsonMatcher ?? new JsonMatcher(body), because, becauseArgs);
|
||||
}
|
||||
|
||||
[CustomAssertion]
|
||||
public AndConstraint<WireMockAssertions> WithBodyAsJson(string body, string because = "", params object[] becauseArgs)
|
||||
public AndConstraint<WireMockAssertions> WithBodyAsJson(string body, IJsonMatcher? jsonMatcher = null, string because = "", params object[] becauseArgs)
|
||||
{
|
||||
return WithBodyAsJson(new JsonMatcher(body), because, becauseArgs);
|
||||
return WithBodyAsJson(jsonMatcher ?? new JsonMatcher(body), because, becauseArgs);
|
||||
}
|
||||
|
||||
[CustomAssertion]
|
||||
public AndConstraint<WireMockAssertions> WithBodyAsJson(IObjectMatcher matcher, string because = "", params object[] becauseArgs)
|
||||
public AndConstraint<WireMockAssertions> WithBodyAsJson(IJsonMatcher matcher, string because = "", params object[] becauseArgs)
|
||||
{
|
||||
var (filter, condition) = BuildFilterAndCondition(r => r.BodyAsJson, matcher);
|
||||
|
||||
@@ -126,15 +126,44 @@ public partial class WireMockAssertions
|
||||
|
||||
private static string? FormatBody(object? body)
|
||||
{
|
||||
return body switch
|
||||
if (body == null)
|
||||
{
|
||||
null => null,
|
||||
string str => str,
|
||||
AnyOf<string, StringPattern>[] stringPatterns => FormatBodies(stringPatterns.Select(p => p.GetPattern())),
|
||||
byte[] bytes => $"byte[{bytes.Length}] {{...}}",
|
||||
JToken jToken => jToken.ToString(Formatting.None),
|
||||
_ => JToken.FromObject(body).ToString(Formatting.None)
|
||||
};
|
||||
return null;
|
||||
}
|
||||
|
||||
if (body is string str)
|
||||
{
|
||||
return str;
|
||||
}
|
||||
|
||||
if (body is AnyOf<string, StringPattern>[] stringPatterns)
|
||||
{
|
||||
return FormatBodies(stringPatterns.Select(p => p.GetPattern()));
|
||||
}
|
||||
|
||||
if (body is byte[] bytes)
|
||||
{
|
||||
return $"byte[{bytes.Length}] {{...}}";
|
||||
}
|
||||
|
||||
if (body is JToken jToken)
|
||||
{
|
||||
return jToken.ToString(Formatting.None);
|
||||
}
|
||||
|
||||
// System.IO.FileNotFoundException : Could not load file or assembly 'System.Text.Json, Version=10.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51'. The system cannot find the file specified.
|
||||
var typeName = body.GetType().FullName;
|
||||
if (typeName == "System.Text.Json.JsonElement")
|
||||
{
|
||||
return ((dynamic)body).GetRawText();
|
||||
}
|
||||
|
||||
if (typeName == "System.Text.Json.JsonDocument")
|
||||
{
|
||||
return ((dynamic)body).RootElement.GetRawText();
|
||||
}
|
||||
|
||||
return JToken.FromObject(body).ToString(Formatting.None);
|
||||
}
|
||||
|
||||
private static string? FormatBodies(IEnumerable<object?> bodies)
|
||||
|
||||
@@ -70,7 +70,7 @@ public class CSharpCodeMatcher : ICSharpCodeMatcher
|
||||
/// <inheritdoc />
|
||||
public string GetCSharpCodeArguments()
|
||||
{
|
||||
return $"new {Name}" +
|
||||
return $"new {typeof(CSharpCodeMatcher).FullName}" +
|
||||
$"(" +
|
||||
$"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " +
|
||||
$"{MatchOperator.GetFullyQualifiedEnumValue()}, " +
|
||||
|
||||
@@ -0,0 +1,184 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using AnyOfTypes;
|
||||
using Json.Path;
|
||||
using Stef.Validation;
|
||||
using WireMock.Extensions;
|
||||
using WireMock.Models;
|
||||
using WireMock.Util;
|
||||
|
||||
namespace WireMock.Matchers;
|
||||
|
||||
/// <summary>
|
||||
/// SystemTextJsonPathMatcher - behaves the same as JsonPathMatcher but uses System.Text.Json and Json.Path instead of Newtonsoft.Json.
|
||||
/// </summary>
|
||||
/// <seealso cref="ISystemTextJsonPathMatcher" />
|
||||
public class SystemTextJsonPathMatcher : ISystemTextJsonPathMatcher
|
||||
{
|
||||
private readonly AnyOf<string, StringPattern>[] _patterns;
|
||||
|
||||
/// <inheritdoc />
|
||||
public MatchBehaviour MatchBehaviour { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public object Value { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SystemTextJsonPathMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="patterns">The patterns.</param>
|
||||
public SystemTextJsonPathMatcher(params string[] patterns)
|
||||
: this(MatchBehaviour.AcceptOnMatch, MatchOperator.Or, patterns.ToAnyOfPatterns())
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SystemTextJsonPathMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="patterns">The patterns.</param>
|
||||
public SystemTextJsonPathMatcher(params AnyOf<string, StringPattern>[] patterns)
|
||||
: this(MatchBehaviour.AcceptOnMatch, MatchOperator.Or, patterns)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SystemTextJsonPathMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="matchBehaviour">The match behaviour.</param>
|
||||
/// <param name="matchOperator">The <see cref="Matchers.MatchOperator"/> to use. (default = "Or")</param>
|
||||
/// <param name="patterns">The patterns.</param>
|
||||
public SystemTextJsonPathMatcher(
|
||||
MatchBehaviour matchBehaviour,
|
||||
MatchOperator matchOperator = MatchOperator.Or,
|
||||
params AnyOf<string, StringPattern>[] patterns)
|
||||
{
|
||||
_patterns = Guard.NotNull(patterns);
|
||||
MatchBehaviour = matchBehaviour;
|
||||
MatchOperator = matchOperator;
|
||||
Value = patterns;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public MatchResult IsMatch(string? input)
|
||||
{
|
||||
var score = MatchScores.Mismatch;
|
||||
Exception? exception = null;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(input))
|
||||
{
|
||||
try
|
||||
{
|
||||
var node = JsonNode.Parse(input!);
|
||||
score = IsMatchInternal(node);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
exception = ex;
|
||||
}
|
||||
}
|
||||
|
||||
return MatchResult.From(Name, MatchBehaviourHelper.Convert(MatchBehaviour, score), exception);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public MatchResult IsMatch(object? input)
|
||||
{
|
||||
var score = MatchScores.Mismatch;
|
||||
Exception? exception = null;
|
||||
|
||||
// When input is null or byte[], return Mismatch.
|
||||
if (input != null && input is not byte[])
|
||||
{
|
||||
try
|
||||
{
|
||||
JsonNode? node = input switch
|
||||
{
|
||||
JsonNode jsonNode => jsonNode,
|
||||
string str => JsonNode.Parse(str),
|
||||
_ => JsonNode.Parse(JsonSerializer.Serialize(input))
|
||||
};
|
||||
|
||||
score = IsMatchInternal(node);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
exception = ex;
|
||||
}
|
||||
}
|
||||
|
||||
return MatchResult.From(Name, MatchBehaviourHelper.Convert(MatchBehaviour, score), exception);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public AnyOf<string, StringPattern>[] GetPatterns()
|
||||
{
|
||||
return _patterns;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public MatchOperator MatchOperator { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Name => nameof(SystemTextJsonPathMatcher);
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetCSharpCodeArguments()
|
||||
{
|
||||
return $"new {typeof(SystemTextJsonPathMatcher).FullName}" +
|
||||
$"(" +
|
||||
$"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " +
|
||||
$"{MatchOperator.GetFullyQualifiedEnumValue()}, " +
|
||||
$"{MappingConverterUtils.ToCSharpCodeArguments(_patterns)}" +
|
||||
$")";
|
||||
}
|
||||
|
||||
private double IsMatchInternal(JsonNode? node)
|
||||
{
|
||||
// JsonPath.Net requires the node to be inside an object or array for filter expressions.
|
||||
// Similar to JsonPathMatcher's ConvertJTokenToJArrayIfNeeded, wrap a plain object in an array
|
||||
// when it's an object with a single non-array child property.
|
||||
var evaluationNode = WrapIfNeeded(node);
|
||||
|
||||
var values = _patterns
|
||||
.Select(pattern =>
|
||||
{
|
||||
var path = JsonPath.Parse(pattern.GetPattern());
|
||||
var result = path.Evaluate(evaluationNode);
|
||||
return result.Matches is { Count: > 0 };
|
||||
})
|
||||
.ToArray();
|
||||
|
||||
return MatchScores.ToScore(values, MatchOperator);
|
||||
}
|
||||
|
||||
// Mirrors JsonPathMatcher.ConvertJTokenToJArrayIfNeeded:
|
||||
// If the node is an object with exactly one property whose value is not already an array,
|
||||
// wrap that value in an array so that filter expressions (e.g. [?(@.x == y)]) can match.
|
||||
private static JsonNode? WrapIfNeeded(JsonNode? node)
|
||||
{
|
||||
if (node is not JsonObject obj)
|
||||
{
|
||||
return node;
|
||||
}
|
||||
|
||||
var properties = obj.ToList();
|
||||
if (properties.Count != 1)
|
||||
{
|
||||
return node;
|
||||
}
|
||||
|
||||
var single = properties[0];
|
||||
if (single.Value is JsonArray)
|
||||
{
|
||||
return node;
|
||||
}
|
||||
|
||||
var clonedValue = JsonNode.Parse(single.Value?.ToJsonString() ?? "null");
|
||||
return new JsonObject
|
||||
{
|
||||
[single.Key] = new JsonArray(clonedValue)
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("WireMock.Net.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e138ec44d93acac565953052636eb8d5e7e9f27ddb030590055cd1a0ab2069a5623f1f77ca907d78e0b37066ca0f6d63da7eecc3fcb65b76aa8ebeccf7ebe1d11264b8404cd9b1cbbf2c83f566e033b3e54129f6ef28daffff776ba7aebbc53c0d635ebad8f45f78eb3f7e0459023c218f003416e080f96a1a3c5ffeb56bee9e")]
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<Description>A SystemTextJsonPathMatcher which can be used to match WireMock.Net Requests using JsonPath.Net.</Description>
|
||||
<AssemblyTitle>WireMock.Net.Matchers.SystemTextJsonPath</AssemblyTitle>
|
||||
<Authors>Stef Heyenrath</Authors>
|
||||
<TargetFrameworks>netstandard2.0;net8.0</TargetFrameworks>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<PackageTags>wiremock;matchers;matcher;jsonpath;systemtextjson</PackageTags>
|
||||
<RootNamespace>WireMock</RootNamespace>
|
||||
<PackageId>WireMock.Net.Matchers.SystemTextJsonPath</PackageId>
|
||||
<ProjectGuid>{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}</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>
|
||||
<!--<DelaySign>true</DelaySign>-->
|
||||
<PublicSign Condition=" '$(OS)' != 'Windows_NT' ">true</PublicSign>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)' == 'Debug - Sonar'">
|
||||
<CodeAnalysisRuleSet>../WireMock.Net/WireMock.Net.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\WireMock.Net.Shared\WireMock.Net.Shared.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Downgraded from 3.0.2 to 2.2.0 to avoid Open Source Maintenance Fee issues -->
|
||||
<PackageReference Include="JsonPath.Net" Version="2.2.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,7 +1,8 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using JsonConverter.Abstractions;
|
||||
using JsonConverter.Newtonsoft.Json;
|
||||
using Newtonsoft.Json;
|
||||
using WireMock.Matchers.Helpers;
|
||||
using WireMock.Models.Mime;
|
||||
using WireMock.Util;
|
||||
@@ -15,6 +16,8 @@ public class MimePartMatcher : IMimePartMatcher
|
||||
{
|
||||
private readonly IList<(string Name, Func<IMimePartData, MatchResult> func)> _matcherFunctions;
|
||||
|
||||
private readonly IJsonConverter _jsonConverter;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Name => nameof(MimePartMatcher);
|
||||
|
||||
@@ -41,7 +44,8 @@ public class MimePartMatcher : IMimePartMatcher
|
||||
IStringMatcher? contentTypeMatcher,
|
||||
IStringMatcher? contentDispositionMatcher,
|
||||
IStringMatcher? contentTransferEncodingMatcher,
|
||||
IMatcher? contentMatcher
|
||||
IMatcher? contentMatcher,
|
||||
IJsonConverter? jsonConverter = null
|
||||
)
|
||||
{
|
||||
MatchBehaviour = matchBehaviour;
|
||||
@@ -49,6 +53,7 @@ public class MimePartMatcher : IMimePartMatcher
|
||||
ContentDispositionMatcher = contentDispositionMatcher;
|
||||
ContentTransferEncodingMatcher = contentTransferEncodingMatcher;
|
||||
ContentMatcher = contentMatcher;
|
||||
_jsonConverter = jsonConverter ?? new NewtonsoftJsonConverter();
|
||||
|
||||
_matcherFunctions = [];
|
||||
if (ContentTypeMatcher != null)
|
||||
@@ -107,7 +112,8 @@ public class MimePartMatcher : IMimePartMatcher
|
||||
ContentType = GetContentTypeAsString(mimePart.ContentType),
|
||||
DeserializeJson = true,
|
||||
ContentEncoding = null, // mimePart.ContentType?.CharsetEncoding.ToString(),
|
||||
DecompressGZipAndDeflate = true
|
||||
DecompressGZipAndDeflate = true,
|
||||
DefaultJsonConverter = _jsonConverter
|
||||
};
|
||||
|
||||
var bodyData = BodyParser.ParseAsync(bodyParserSettings).ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Stef.Validation" Version="0.2.0" />
|
||||
<PackageReference Include="Stef.Validation" Version="0.3.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(Configuration)' == 'Debug'">
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace WireMock.Extensions;
|
||||
|
||||
internal static class HttpContextExtensions
|
||||
{
|
||||
internal static bool TryGet<T>(this HttpContext httpContext, [NotNullWhen(true)] out T? value)
|
||||
{
|
||||
return httpContext.Items.TryGetValue(nameof(T), out value);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using JsonConverter.Abstractions;
|
||||
using WireMock.Util;
|
||||
|
||||
namespace WireMock.Http;
|
||||
@@ -15,7 +14,8 @@ internal static class HttpResponseMessageHelper
|
||||
Uri originalUri,
|
||||
bool deserializeJson,
|
||||
bool decompressGzipAndDeflate,
|
||||
bool deserializeFormUrlEncoded)
|
||||
bool deserializeFormUrlEncoded,
|
||||
IJsonConverter jsonConverter)
|
||||
{
|
||||
var responseMessage = new ResponseMessage { StatusCode = (int)httpResponseMessage.StatusCode };
|
||||
|
||||
@@ -45,7 +45,8 @@ internal static class HttpResponseMessageHelper
|
||||
DeserializeJson = deserializeJson,
|
||||
ContentEncoding = contentEncodingHeader?.FirstOrDefault(),
|
||||
DecompressGZipAndDeflate = decompressGzipAndDeflate,
|
||||
DeserializeFormUrlEncoded = deserializeFormUrlEncoded
|
||||
DeserializeFormUrlEncoded = deserializeFormUrlEncoded,
|
||||
DefaultJsonConverter = jsonConverter
|
||||
};
|
||||
responseMessage.BodyData = await BodyParser.ParseAsync(bodyParserSettings).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using WireMock.Util;
|
||||
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System.Text.Json;
|
||||
using WireMock.Util;
|
||||
|
||||
namespace WireMock.Matchers;
|
||||
|
||||
/// <summary>
|
||||
/// Generic AbstractSystemTextJsonPartialMatcher - uses System.Text.Json instead of Newtonsoft.Json.
|
||||
/// </summary>
|
||||
public abstract class AbstractSystemTextJsonPartialMatcher : SystemTextJsonMatcher
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AbstractSystemTextJsonPartialMatcher"/> class.
|
||||
/// </summary>
|
||||
protected AbstractSystemTextJsonPartialMatcher(string value, bool ignoreCase = false, bool regex = false)
|
||||
: base(value, ignoreCase, regex)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AbstractSystemTextJsonPartialMatcher"/> class.
|
||||
/// </summary>
|
||||
protected AbstractSystemTextJsonPartialMatcher(object value, bool ignoreCase = false, bool regex = false)
|
||||
: base(value, ignoreCase, regex)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AbstractSystemTextJsonPartialMatcher"/> class.
|
||||
/// </summary>
|
||||
protected AbstractSystemTextJsonPartialMatcher(MatchBehaviour matchBehaviour, object value, bool ignoreCase = false, bool regex = false)
|
||||
: base(matchBehaviour, value, ignoreCase, regex)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override bool IsMatch(JsonElement value, JsonElement? input)
|
||||
{
|
||||
if (input == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var inputElement = input.Value;
|
||||
|
||||
// Regex on a string value
|
||||
if (Regex && value.ValueKind == JsonValueKind.String)
|
||||
{
|
||||
var valueAsString = value.GetString()!;
|
||||
var inputAsString = GetStringValue(inputElement);
|
||||
|
||||
var (valid, result) = RegexUtils.MatchRegex(valueAsString, inputAsString);
|
||||
if (valid)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// Guid comparison: both strings, both parseable as Guid
|
||||
if (value.ValueKind == JsonValueKind.String && inputElement.ValueKind == JsonValueKind.String)
|
||||
{
|
||||
var valueStr = value.GetString()!;
|
||||
var inputStr = inputElement.GetString()!;
|
||||
if (Guid.TryParse(valueStr, out var vg) && Guid.TryParse(inputStr, out var ig))
|
||||
{
|
||||
return IsMatch(vg.ToString(), ig.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
// Type mismatch (after regex/guid checks)
|
||||
if (value.ValueKind != inputElement.ValueKind)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (value.ValueKind)
|
||||
{
|
||||
case JsonValueKind.Object:
|
||||
{
|
||||
var nestedValues = value.EnumerateObject().ToArray();
|
||||
if (nestedValues.Length == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return nestedValues.All(pair =>
|
||||
{
|
||||
var selected = SelectElement(inputElement, pair.Name);
|
||||
return selected != null && IsMatch(pair.Value, selected.Value);
|
||||
});
|
||||
}
|
||||
|
||||
case JsonValueKind.Array:
|
||||
{
|
||||
var valuesArray = value.EnumerateArray().ToArray();
|
||||
if (valuesArray.Length == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var tokenArray = inputElement.EnumerateArray().ToArray();
|
||||
if (tokenArray.Length == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return valuesArray.All(subFilter => tokenArray.Any(subToken => IsMatch(subFilter, subToken)));
|
||||
}
|
||||
|
||||
default:
|
||||
return IsMatch(GetStringValue(value), GetStringValue(inputElement));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if two strings are a match (matching can be done exact or wildcard).
|
||||
/// </summary>
|
||||
protected abstract bool IsMatch(string value, string input);
|
||||
|
||||
/// <summary>
|
||||
/// Selects a <see cref="JsonElement"/> from an object using a key that may be a plain property name,
|
||||
/// a dotted path (e.g. "a.b.c") or bracket notation (e.g. "['name.with.dot']"),
|
||||
/// mirroring Newtonsoft's <c>SelectToken</c> + direct indexer fallback.
|
||||
/// </summary>
|
||||
private static JsonElement? SelectElement(JsonElement input, string key)
|
||||
{
|
||||
if (input.ValueKind != JsonValueKind.Object)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Direct property access (also handles keys containing colons or dots that are literal property names)
|
||||
if (input.TryGetProperty(key, out var direct))
|
||||
{
|
||||
return direct;
|
||||
}
|
||||
|
||||
// Bracket notation: ['property.name.with.dots']
|
||||
if (key.StartsWith("['") && key.EndsWith("']"))
|
||||
{
|
||||
var propertyName = key.Substring(2, key.Length - 4);
|
||||
return input.TryGetProperty(propertyName, out var bracketValue) ? bracketValue : null;
|
||||
}
|
||||
|
||||
// Dotted path: a.b.c
|
||||
if (key.Contains('.'))
|
||||
{
|
||||
var parts = key.Split('.');
|
||||
var current = input;
|
||||
foreach (var part in parts)
|
||||
{
|
||||
if (current.ValueKind != JsonValueKind.Object || !current.TryGetProperty(part, out var next))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
current = next;
|
||||
}
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string GetStringValue(JsonElement element)
|
||||
{
|
||||
return element.ValueKind == JsonValueKind.String
|
||||
? element.GetString()!
|
||||
: element.GetRawText();
|
||||
}
|
||||
}
|
||||
@@ -80,10 +80,10 @@ public class ContentTypeMatcher : WildcardMatcher
|
||||
/// <inheritdoc />
|
||||
public override string GetCSharpCodeArguments()
|
||||
{
|
||||
return $"new {Name}" +
|
||||
return $"new {typeof(ContentTypeMatcher).FullName}" +
|
||||
$"(" +
|
||||
$"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " +
|
||||
$"{MappingConverterUtils.ToCSharpCodeArguments(_patterns)}, " +
|
||||
$"new AnyOfTypes.AnyOf<string, WireMock.Models.StringPattern>[] {{ {MappingConverterUtils.ToCSharpCodeArguments(_patterns)} }}, " +
|
||||
$"{CSharpFormatter.ToCSharpBooleanLiteral(IgnoreCase)}" +
|
||||
$")";
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ public class ExactMatcher : IStringMatcher, IIgnoreCaseMatcher
|
||||
/// <inheritdoc />
|
||||
public string GetCSharpCodeArguments()
|
||||
{
|
||||
return $"new {Name}" +
|
||||
return $"new {typeof(ExactMatcher).FullName}" +
|
||||
$"(" +
|
||||
$"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " +
|
||||
$"{CSharpFormatter.ToCSharpBooleanLiteral(IgnoreCase)}, " +
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System.Linq;
|
||||
using AnyOfTypes;
|
||||
using Stef.Validation;
|
||||
using WireMock.Extensions;
|
||||
using WireMock.Models;
|
||||
using WireMock.Types;
|
||||
using WireMock.Util;
|
||||
|
||||
namespace WireMock.Matchers;
|
||||
@@ -21,7 +21,7 @@ public class FormUrlEncodedMatcher : IStringMatcher, IIgnoreCaseMatcher
|
||||
/// <inheritdoc />
|
||||
public MatchBehaviour MatchBehaviour { get; }
|
||||
|
||||
private readonly List<(WildcardMatcher Key, WildcardMatcher? Value)> _pairs = [];
|
||||
private readonly List<(WildcardMatcher Key, WildcardMatcher[]? Values)> KeyValueMatchers = [];
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FormUrlEncodedMatcher"/> class.
|
||||
@@ -91,9 +91,10 @@ public class FormUrlEncodedMatcher : IStringMatcher, IIgnoreCaseMatcher
|
||||
{
|
||||
foreach (var nameValue in nameValueCollection)
|
||||
{
|
||||
var keyMatcher = new WildcardMatcher(MatchBehaviour.AcceptOnMatch, [nameValue.Key], ignoreCase, MatchOperator);
|
||||
var valueMatcher = new WildcardMatcher(MatchBehaviour.AcceptOnMatch, [nameValue.Value], ignoreCase, MatchOperator);
|
||||
_pairs.Add((keyMatcher, valueMatcher));
|
||||
var keyMatcher = new WildcardMatcher(MatchBehaviour.AcceptOnMatch, nameValue.Key, ignoreCase);
|
||||
var valueMatchers = nameValue.Value.Select(value => new WildcardMatcher(MatchBehaviour.AcceptOnMatch, value, ignoreCase)).ToArray();
|
||||
|
||||
KeyValueMatchers.Add((keyMatcher, valueMatchers));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -119,34 +120,48 @@ public class FormUrlEncodedMatcher : IStringMatcher, IIgnoreCaseMatcher
|
||||
return MatchResult.From(Name, MatchBehaviourHelper.Convert(MatchBehaviour, score));
|
||||
}
|
||||
|
||||
private bool[] GetMatches(IDictionary<string, string> inputNameValueCollection)
|
||||
private List<double> GetMatches(IDictionary<string, WireMockList<string>> inputNameValueCollection)
|
||||
{
|
||||
var matches = new List<bool>();
|
||||
if (_pairs.Count > inputNameValueCollection.Count)
|
||||
var inputPairs = inputNameValueCollection.ToArray();
|
||||
var rowCount = inputPairs.Length;
|
||||
var columnCount = KeyValueMatchers.Count;
|
||||
|
||||
if (rowCount == 0 && columnCount == 0)
|
||||
{
|
||||
matches.AddRange(Enumerable.Repeat(false, _pairs.Count - inputNameValueCollection.Count));
|
||||
return [];
|
||||
}
|
||||
|
||||
foreach (var inputKeyValuePair in inputNameValueCollection)
|
||||
var matrix = new double[rowCount][];
|
||||
|
||||
for (var row = 0; row < rowCount; row++)
|
||||
{
|
||||
var match = false;
|
||||
foreach (var pair in _pairs)
|
||||
matrix[row] = new double[columnCount];
|
||||
|
||||
var inputKeyValuePair = inputPairs[row];
|
||||
var inputKey = inputKeyValuePair.Key;
|
||||
var inputValues = inputKeyValuePair.Value;
|
||||
|
||||
for (var column = 0; column < columnCount; column++)
|
||||
{
|
||||
var keyMatchResult = pair.Key.IsMatch(inputKeyValuePair.Key).IsPerfect();
|
||||
if (keyMatchResult)
|
||||
{
|
||||
match = pair.Value?.IsMatch(inputKeyValuePair.Value).IsPerfect() ?? false;
|
||||
if (match)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
var (keyMatcher, valuesMatchers) = KeyValueMatchers[column];
|
||||
|
||||
matches.Add(match);
|
||||
var keyScore = keyMatcher.IsMatch(inputKey).Score;
|
||||
var valueScore = valuesMatchers != null ? MatchScores.ToScore(inputValues, valuesMatchers) : MatchScores.Perfect;
|
||||
|
||||
matrix[row][column] = Math.Min(keyScore, valueScore);
|
||||
}
|
||||
}
|
||||
|
||||
return matches.ToArray();
|
||||
var rowScores = rowCount == 0 ? [] : matrix.Select(row => row.Length == 0 ? MatchScores.Mismatch : row.Max()).ToList();
|
||||
|
||||
var columnScores = new List<double>();
|
||||
for (var column = 0; column < columnCount; column++)
|
||||
{
|
||||
columnScores.Add(rowCount == 0 ? MatchScores.Mismatch : matrix.Max(row => row[column]));
|
||||
}
|
||||
|
||||
rowScores.AddRange(columnScores);
|
||||
return rowScores;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -167,10 +182,10 @@ public class FormUrlEncodedMatcher : IStringMatcher, IIgnoreCaseMatcher
|
||||
/// <inheritdoc />
|
||||
public string GetCSharpCodeArguments()
|
||||
{
|
||||
return $"new {Name}" +
|
||||
return $"new {typeof(FormUrlEncodedMatcher).FullName}" +
|
||||
$"(" +
|
||||
$"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " +
|
||||
$"{MappingConverterUtils.ToCSharpCodeArguments(_patterns)}, " +
|
||||
$"new AnyOfTypes.AnyOf<string, WireMock.Models.StringPattern>[] {{ {MappingConverterUtils.ToCSharpCodeArguments(_patterns)} }}, " +
|
||||
$"{CSharpFormatter.ToCSharpBooleanLiteral(IgnoreCase)}, " +
|
||||
$"{MatchOperator.GetFullyQualifiedEnumValue()}" +
|
||||
$")";
|
||||
|
||||
@@ -76,7 +76,7 @@ public class FuncMatcher : IFuncMatcher
|
||||
public string GetCSharpCodeArguments()
|
||||
{
|
||||
var funcType = _stringFunc != null ? "Func<string?, bool>" : "Func<byte[]?, bool>";
|
||||
return $"new {Name}" +
|
||||
return $"new {typeof(FuncMatcher).FullName}" +
|
||||
$"(" +
|
||||
$"/* {funcType} function */, " +
|
||||
$"{MatchBehaviour.GetFullyQualifiedEnumValue()}" +
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System.Linq;
|
||||
using AnyOfTypes;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Stef.Validation;
|
||||
@@ -13,9 +12,8 @@ namespace WireMock.Matchers;
|
||||
/// <summary>
|
||||
/// JsonPathMatcher
|
||||
/// </summary>
|
||||
/// <seealso cref="IStringMatcher" />
|
||||
/// <seealso cref="IObjectMatcher" />
|
||||
public class JsonPathMatcher : IStringMatcher, IObjectMatcher
|
||||
/// <seealso cref="IJsonPathMatcher" />
|
||||
public class JsonPathMatcher : IJsonPathMatcher
|
||||
{
|
||||
private readonly AnyOf<string, StringPattern>[] _patterns;
|
||||
|
||||
@@ -121,7 +119,7 @@ public class JsonPathMatcher : IStringMatcher, IObjectMatcher
|
||||
/// <inheritdoc />
|
||||
public string GetCSharpCodeArguments()
|
||||
{
|
||||
return $"new {Name}" +
|
||||
return $"new {typeof(JsonPathMatcher).FullName}" +
|
||||
$"(" +
|
||||
$"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " +
|
||||
$"{MatchOperator.GetFullyQualifiedEnumValue()}, " +
|
||||
|
||||
@@ -119,7 +119,7 @@ public class JmesPathMatcher : IStringMatcher, IObjectMatcher
|
||||
/// <inheritdoc />
|
||||
public string GetCSharpCodeArguments()
|
||||
{
|
||||
return $"new {Name}" +
|
||||
return $"new {typeof(JmesPathMatcher).FullName}" +
|
||||
$"(" +
|
||||
$"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " +
|
||||
$"{MatchOperator.GetFullyQualifiedEnumValue()}, " +
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System.Linq;
|
||||
using System.Collections;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Stef.Validation;
|
||||
using WireMock.Extensions;
|
||||
using WireMock.Serialization;
|
||||
using WireMock.Util;
|
||||
using JsonUtils = WireMock.Util.JsonUtils;
|
||||
|
||||
namespace WireMock.Matchers;
|
||||
|
||||
@@ -31,6 +32,11 @@ public class JsonMatcher : IJsonMatcher
|
||||
/// </summary>
|
||||
public bool Regex { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Ignore array order when comparing
|
||||
/// </summary>
|
||||
public bool IgnoreArrayOrder { get; }
|
||||
|
||||
private readonly JToken _valueAsJToken;
|
||||
|
||||
/// <summary>
|
||||
@@ -39,7 +45,8 @@ public class JsonMatcher : IJsonMatcher
|
||||
/// <param name="value">The string value to check for equality.</param>
|
||||
/// <param name="ignoreCase">Ignore the case from the PropertyName and PropertyValue (string only).</param>
|
||||
/// <param name="regex">Support Regex.</param>
|
||||
public JsonMatcher(string value, bool ignoreCase = false, bool regex = false) : this(MatchBehaviour.AcceptOnMatch, value, ignoreCase, regex)
|
||||
/// <param name="ignoreArrayOrder">Ignore array element order when comparing.</param>
|
||||
public JsonMatcher(string value, bool ignoreCase = false, bool regex = false, bool ignoreArrayOrder = false) : this(MatchBehaviour.AcceptOnMatch, value, ignoreCase, regex, ignoreArrayOrder)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -49,7 +56,8 @@ public class JsonMatcher : IJsonMatcher
|
||||
/// <param name="value">The object value to check for equality.</param>
|
||||
/// <param name="ignoreCase">Ignore the case from the PropertyName and PropertyValue (string only).</param>
|
||||
/// <param name="regex">Support Regex.</param>
|
||||
public JsonMatcher(object value, bool ignoreCase = false, bool regex = false) : this(MatchBehaviour.AcceptOnMatch, value, ignoreCase, regex)
|
||||
/// <param name="ignoreArrayOrder">Ignore array element order when comparing.</param>
|
||||
public JsonMatcher(object value, bool ignoreCase = false, bool regex = false, bool ignoreArrayOrder = false) : this(MatchBehaviour.AcceptOnMatch, value, ignoreCase, regex, ignoreArrayOrder)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -60,16 +68,18 @@ public class JsonMatcher : IJsonMatcher
|
||||
/// <param name="value">The value to check for equality.</param>
|
||||
/// <param name="ignoreCase">Ignore the case from the PropertyName and PropertyValue (string only).</param>
|
||||
/// <param name="regex">Support Regex.</param>
|
||||
public JsonMatcher(MatchBehaviour matchBehaviour, object value, bool ignoreCase = false, bool regex = false)
|
||||
/// <param name="ignoreArrayOrder">Ignore array element order when comparing.</param>
|
||||
public JsonMatcher(MatchBehaviour matchBehaviour, object value, bool ignoreCase = false, bool regex = false, bool ignoreArrayOrder = false)
|
||||
{
|
||||
Guard.NotNull(value);
|
||||
|
||||
MatchBehaviour = matchBehaviour;
|
||||
IgnoreCase = ignoreCase;
|
||||
Regex = regex;
|
||||
IgnoreArrayOrder = ignoreArrayOrder;
|
||||
|
||||
Value = value;
|
||||
_valueAsJToken = JsonUtils.ConvertValueToJToken(value);
|
||||
_valueAsJToken = ConvertValueToJToken(value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -83,7 +93,7 @@ public class JsonMatcher : IJsonMatcher
|
||||
{
|
||||
try
|
||||
{
|
||||
var inputAsJToken = JsonUtils.ConvertValueToJToken(input);
|
||||
var inputAsJToken = ConvertValueToJToken(input);
|
||||
|
||||
var match = IsMatch(RenameJToken(_valueAsJToken), RenameJToken(inputAsJToken));
|
||||
score = MatchScores.ToScore(match);
|
||||
@@ -100,12 +110,13 @@ public class JsonMatcher : IJsonMatcher
|
||||
/// <inheritdoc />
|
||||
public virtual string GetCSharpCodeArguments()
|
||||
{
|
||||
return $"new {Name}" +
|
||||
return $"new {typeof(JsonMatcher).FullName}" +
|
||||
$"(" +
|
||||
$"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " +
|
||||
$"{CSharpFormatter.ConvertToAnonymousObjectDefinition(Value, 3)}, " +
|
||||
$"{CSharpFormatter.ConvertToAnonymousObjectDefinition(Value, 3)}, " +
|
||||
$"{CSharpFormatter.ToCSharpBooleanLiteral(IgnoreCase)}, " +
|
||||
$"{CSharpFormatter.ToCSharpBooleanLiteral(Regex)}" +
|
||||
$"{CSharpFormatter.ToCSharpBooleanLiteral(Regex)}, " +
|
||||
$"{CSharpFormatter.ToCSharpBooleanLiteral(IgnoreArrayOrder)}" +
|
||||
$")";
|
||||
}
|
||||
|
||||
@@ -182,6 +193,13 @@ public class JsonMatcher : IJsonMatcher
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IgnoreArrayOrder)
|
||||
{
|
||||
// Sort both arrays by their string representation and compare
|
||||
valueArray = valueArray.OrderBy(t => t.ToString()).ToArray();
|
||||
inputArray = inputArray.OrderBy(t => t.ToString()).ToArray();
|
||||
}
|
||||
|
||||
return !valueArray.Where((valueToken, index) => !IsMatch(valueToken, inputArray[index])).Any();
|
||||
|
||||
default:
|
||||
@@ -241,6 +259,18 @@ public class JsonMatcher : IJsonMatcher
|
||||
return new JObject(renamedProperties);
|
||||
}
|
||||
|
||||
private static JToken ConvertValueToJToken(object value)
|
||||
{
|
||||
// Check if JToken, string, IEnumerable or object
|
||||
return value switch
|
||||
{
|
||||
JToken tokenValue => tokenValue,
|
||||
string stringValue => JsonConvert.DeserializeObject<JToken>(stringValue, JsonSerializationConstants.JsonDeserializerSettingsWithDateParsingNone)!,
|
||||
IEnumerable enumerableValue => JArray.FromObject(enumerableValue),
|
||||
_ => JObject.FromObject(value),
|
||||
};
|
||||
}
|
||||
|
||||
private static string? ToUpper(string? input)
|
||||
{
|
||||
return input?.ToUpperInvariant();
|
||||
|
||||
@@ -41,7 +41,7 @@ public class JsonPartialMatcher : AbstractJsonPartialMatcher
|
||||
/// <inheritdoc />
|
||||
public override string GetCSharpCodeArguments()
|
||||
{
|
||||
return $"new {Name}" +
|
||||
return $"new {typeof(JsonPartialMatcher).FullName}" +
|
||||
$"(" +
|
||||
$"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " +
|
||||
$"{CSharpFormatter.ConvertToAnonymousObjectDefinition(Value, 3)}, " +
|
||||
|
||||
@@ -41,7 +41,7 @@ public class JsonPartialWildcardMatcher : AbstractJsonPartialMatcher
|
||||
/// <inheritdoc />
|
||||
public override string GetCSharpCodeArguments()
|
||||
{
|
||||
return $"new {Name}" +
|
||||
return $"new {typeof(JsonPartialWildcardMatcher).FullName}" +
|
||||
$"(" +
|
||||
$"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " +
|
||||
$"{CSharpFormatter.ConvertToAnonymousObjectDefinition(Value, 3)}, " +
|
||||
|
||||
@@ -141,7 +141,7 @@ public class LinqMatcher : IObjectMatcher, IStringMatcher
|
||||
/// <inheritdoc />
|
||||
public string GetCSharpCodeArguments()
|
||||
{
|
||||
return $"new {Name}" +
|
||||
return $"new {typeof(LinqMatcher).FullName}" +
|
||||
$"(" +
|
||||
$"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " +
|
||||
$"{MatchOperator.GetFullyQualifiedEnumValue()}, " +
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System.Linq;
|
||||
using Stef.Validation;
|
||||
using WireMock.Matchers.Helpers;
|
||||
using WireMock.Types;
|
||||
using WireMock.Util;
|
||||
|
||||
namespace WireMock.Matchers.Request;
|
||||
@@ -35,7 +35,7 @@ public class RequestMessageBodyMatcher : IRequestMatcher
|
||||
/// <summary>
|
||||
/// The body data function for FormUrlEncoded
|
||||
/// </summary>
|
||||
public Func<IDictionary<string, string>?, bool>? MatchOnBodyAsFormUrlEncodedFunc { get; }
|
||||
public Func<IDictionary<string, WireMockList<string>>?, bool>? MatchOnBodyAsFormUrlEncodedFunc { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The matchers.
|
||||
@@ -117,7 +117,7 @@ public class RequestMessageBodyMatcher : IRequestMatcher
|
||||
/// Initializes a new instance of the <see cref="RequestMessageBodyMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="func">The function.</param>
|
||||
public RequestMessageBodyMatcher(Func<IDictionary<string, string>?, bool> func)
|
||||
public RequestMessageBodyMatcher(Func<IDictionary<string, WireMockList<string>>?, bool> func)
|
||||
{
|
||||
MatchOnBodyAsFormUrlEncodedFunc = Guard.NotNull(func);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System.Linq;
|
||||
using Stef.Validation;
|
||||
using WireMock.Types;
|
||||
|
||||
@@ -54,7 +53,10 @@ public class RequestMessageParamMatcher : IRequestMatcher
|
||||
/// <param name="ignoreCase">Defines if the key should be matched using case-ignore.</param>
|
||||
/// <param name="values">The values.</param>
|
||||
public RequestMessageParamMatcher(MatchBehaviour matchBehaviour, string key, bool ignoreCase, params string[]? values) :
|
||||
this(matchBehaviour, key, ignoreCase, values?.Select(value => new ExactMatcher(matchBehaviour, ignoreCase, MatchOperator.And, value)).Cast<IStringMatcher>().ToArray())
|
||||
// Note: the inner ExactMatcher must use AcceptOnMatch.
|
||||
// The MatchBehaviour (e.g. RejectOnMatch) is applied once by this matcher's GetMatchingScore.
|
||||
// Passing matchBehaviour here as well would apply the conversion twice and invert RejectOnMatch.
|
||||
this(matchBehaviour, key, ignoreCase, values?.Select(value => new ExactMatcher(MatchBehaviour.AcceptOnMatch, ignoreCase, MatchOperator.And, value)).Cast<IStringMatcher>().ToArray())
|
||||
{
|
||||
}
|
||||
|
||||
@@ -113,36 +115,6 @@ public class RequestMessageParamMatcher : IRequestMatcher
|
||||
}
|
||||
|
||||
// Return the score based on Matchers and valuesPresentInRequestMessage
|
||||
return CalculateScore(Matchers, valuesPresentInRequestMessage);
|
||||
}
|
||||
|
||||
private static double CalculateScore(IReadOnlyList<IStringMatcher> matchers, WireMockList<string> valuesPresentInRequestMessage)
|
||||
{
|
||||
var total = new List<double>();
|
||||
|
||||
// If the total patterns in all matchers > values in message, use the matcher as base
|
||||
if (matchers.Sum(m => m.GetPatterns().Length) > valuesPresentInRequestMessage.Count)
|
||||
{
|
||||
foreach (var matcher in matchers)
|
||||
{
|
||||
double score = 0d;
|
||||
foreach (string valuePresentInRequestMessage in valuesPresentInRequestMessage)
|
||||
{
|
||||
score += matcher.IsMatch(valuePresentInRequestMessage).Score / matcher.GetPatterns().Length;
|
||||
}
|
||||
|
||||
total.Add(score);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (string valuePresentInRequestMessage in valuesPresentInRequestMessage)
|
||||
{
|
||||
var score = matchers.Max(m => m.IsMatch(valuePresentInRequestMessage).Score);
|
||||
total.Add(score);
|
||||
}
|
||||
}
|
||||
|
||||
return total.Any() ? MatchScores.ToScore(total, MatchOperator.Average) : 0;
|
||||
return MatchScores.ToScore(valuesPresentInRequestMessage, Matchers.ToArray());
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System.Linq;
|
||||
using AnyOfTypes;
|
||||
using SimMetrics.Net;
|
||||
using SimMetrics.Net.API;
|
||||
@@ -92,10 +91,10 @@ public class SimMetricsMatcher : IStringMatcher
|
||||
/// <inheritdoc />
|
||||
public virtual string GetCSharpCodeArguments()
|
||||
{
|
||||
return $"new {Name}" +
|
||||
return $"new {typeof(SimMetricsMatcher).FullName}" +
|
||||
$"(" +
|
||||
$"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " +
|
||||
$"{MappingConverterUtils.ToCSharpCodeArguments(_patterns)}, " +
|
||||
$"new AnyOfTypes.AnyOf<string, WireMock.Models.StringPattern>[] {{ {MappingConverterUtils.ToCSharpCodeArguments(_patterns)} }}, " +
|
||||
$"{_simMetricType.GetFullyQualifiedEnumValue()}, " +
|
||||
$"{MatchOperator.GetFullyQualifiedEnumValue()}" +
|
||||
$")";
|
||||
|
||||
@@ -0,0 +1,299 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System.Collections;
|
||||
using System.Text.Json;
|
||||
using Stef.Validation;
|
||||
using WireMock.Extensions;
|
||||
using WireMock.Util;
|
||||
|
||||
namespace WireMock.Matchers;
|
||||
|
||||
/// <summary>
|
||||
/// SystemTextJsonMatcher - behaves the same as <see cref="JsonMatcher"/> but uses System.Text.Json instead of Newtonsoft.Json.
|
||||
/// </summary>
|
||||
public class SystemTextJsonMatcher : IJsonMatcher
|
||||
{
|
||||
private static readonly JsonSerializerOptions DefaultSerializerOptions = new()
|
||||
{
|
||||
PropertyNameCaseInsensitive = false
|
||||
};
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual string Name => nameof(SystemTextJsonMatcher);
|
||||
|
||||
/// <inheritdoc />
|
||||
public object Value { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public MatchBehaviour MatchBehaviour { get; }
|
||||
|
||||
/// <inheritdoc cref="IIgnoreCaseMatcher.IgnoreCase"/>
|
||||
public bool IgnoreCase { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Support Regex
|
||||
/// </summary>
|
||||
public bool Regex { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Ignore array order when comparing
|
||||
/// </summary>
|
||||
public bool IgnoreArrayOrder { get; }
|
||||
|
||||
private readonly JsonElement _valueAsJsonElement;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SystemTextJsonMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="value">The string value to check for equality.</param>
|
||||
/// <param name="ignoreCase">Ignore the case from the PropertyName and PropertyValue (string only).</param>
|
||||
/// <param name="regex">Support Regex.</param>
|
||||
/// <param name="ignoreArrayOrder">Ignore array element order when comparing.</param>
|
||||
public SystemTextJsonMatcher(string value, bool ignoreCase = false, bool regex = false, bool ignoreArrayOrder = false)
|
||||
: this(MatchBehaviour.AcceptOnMatch, value, ignoreCase, regex, ignoreArrayOrder)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SystemTextJsonMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="value">The object value to check for equality.</param>
|
||||
/// <param name="ignoreCase">Ignore the case from the PropertyName and PropertyValue (string only).</param>
|
||||
/// <param name="regex">Support Regex.</param>
|
||||
/// <param name="ignoreArrayOrder">Ignore array element order when comparing.</param>
|
||||
public SystemTextJsonMatcher(object value, bool ignoreCase = false, bool regex = false, bool ignoreArrayOrder = false)
|
||||
: this(MatchBehaviour.AcceptOnMatch, value, ignoreCase, regex, ignoreArrayOrder)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SystemTextJsonMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="matchBehaviour">The match behaviour.</param>
|
||||
/// <param name="value">The value to check for equality.</param>
|
||||
/// <param name="ignoreCase">Ignore the case from the PropertyName and PropertyValue (string only).</param>
|
||||
/// <param name="regex">Support Regex.</param>
|
||||
/// <param name="ignoreArrayOrder">Ignore array element order when comparing.</param>
|
||||
public SystemTextJsonMatcher(MatchBehaviour matchBehaviour, object value, bool ignoreCase = false, bool regex = false, bool ignoreArrayOrder = false)
|
||||
{
|
||||
Guard.NotNull(value);
|
||||
|
||||
MatchBehaviour = matchBehaviour;
|
||||
IgnoreCase = ignoreCase;
|
||||
Regex = regex;
|
||||
IgnoreArrayOrder = ignoreArrayOrder;
|
||||
|
||||
Value = value;
|
||||
_valueAsJsonElement = ConvertToJsonElement(value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public MatchResult IsMatch(object? input)
|
||||
{
|
||||
var score = MatchScores.Mismatch;
|
||||
Exception? error = null;
|
||||
|
||||
// When input is null or byte[], return Mismatch.
|
||||
if (input != null && input is not byte[])
|
||||
{
|
||||
try
|
||||
{
|
||||
var inputAsJsonElement = ConvertToJsonElement(input);
|
||||
|
||||
var match = IsMatch(NormalizeElement(_valueAsJsonElement), NormalizeElement(inputAsJsonElement));
|
||||
score = MatchScores.ToScore(match);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
error = ex;
|
||||
}
|
||||
}
|
||||
|
||||
return MatchResult.From(Name, MatchBehaviourHelper.Convert(MatchBehaviour, score), error);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual string GetCSharpCodeArguments()
|
||||
{
|
||||
return $"new {typeof(SystemTextJsonMatcher).FullName}" +
|
||||
$"(" +
|
||||
$"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " +
|
||||
$"{CSharpFormatter.ConvertToAnonymousObjectDefinition(Value, 3)}, " +
|
||||
$"{CSharpFormatter.ToCSharpBooleanLiteral(IgnoreCase)}, " +
|
||||
$"{CSharpFormatter.ToCSharpBooleanLiteral(Regex)}, " +
|
||||
$"{CSharpFormatter.ToCSharpBooleanLiteral(IgnoreArrayOrder)}" +
|
||||
$")";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares the input against the matcher value
|
||||
/// </summary>
|
||||
protected virtual bool IsMatch(JsonElement value, JsonElement? input)
|
||||
{
|
||||
if (input == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var inputElement = input.Value;
|
||||
|
||||
// If using Regex and the value is a string, use the MatchRegex method.
|
||||
if (Regex && value.ValueKind == JsonValueKind.String)
|
||||
{
|
||||
var valueAsString = value.GetString()!;
|
||||
var inputAsString = inputElement.ValueKind == JsonValueKind.String
|
||||
? inputElement.GetString()!
|
||||
: inputElement.GetRawText();
|
||||
|
||||
var (valid, result) = RegexUtils.MatchRegex(valueAsString, inputAsString);
|
||||
if (valid)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// If the value is a Guid (string) and input is a string, or vice versa, compare as strings.
|
||||
if (value.ValueKind == JsonValueKind.String && inputElement.ValueKind == JsonValueKind.String)
|
||||
{
|
||||
var valueStr = value.GetString()!;
|
||||
var inputStr = inputElement.GetString()!;
|
||||
|
||||
if (Guid.TryParse(valueStr, out var valueGuid) && Guid.TryParse(inputStr, out var inputGuid))
|
||||
{
|
||||
return valueGuid == inputGuid;
|
||||
}
|
||||
}
|
||||
|
||||
switch (value.ValueKind)
|
||||
{
|
||||
case JsonValueKind.Object:
|
||||
{
|
||||
if (inputElement.ValueKind != JsonValueKind.Object)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var valueProperties = value.EnumerateObject().ToDictionary(p => p.Name, p => p.Value);
|
||||
var inputProperties = inputElement.EnumerateObject().ToDictionary(p => p.Name, p => p.Value);
|
||||
|
||||
if (valueProperties.Count != inputProperties.Count)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var pair in valueProperties)
|
||||
{
|
||||
if (!inputProperties.TryGetValue(pair.Key, out var inputPropValue))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsMatch(pair.Value, inputPropValue))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
case JsonValueKind.Array:
|
||||
{
|
||||
if (inputElement.ValueKind != JsonValueKind.Array)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var valueArray = value.EnumerateArray().ToArray();
|
||||
var inputArray = inputElement.EnumerateArray().ToArray();
|
||||
|
||||
if (valueArray.Length != inputArray.Length)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IgnoreArrayOrder)
|
||||
{
|
||||
// Sort both arrays by their string representation and compare
|
||||
valueArray = valueArray.OrderBy(e => e.GetRawText()).ToArray();
|
||||
inputArray = inputArray.OrderBy(e => e.GetRawText()).ToArray();
|
||||
}
|
||||
|
||||
return !valueArray.Where((valueToken, index) => !IsMatch(valueToken, inputArray[index])).Any();
|
||||
}
|
||||
|
||||
default:
|
||||
return value.GetRawText() == inputElement.GetRawText();
|
||||
}
|
||||
}
|
||||
|
||||
private JsonElement NormalizeElement(JsonElement element)
|
||||
{
|
||||
if (!IgnoreCase)
|
||||
{
|
||||
return element;
|
||||
}
|
||||
|
||||
var normalized = NormalizeValue(element);
|
||||
return ConvertToJsonElement(normalized);
|
||||
}
|
||||
|
||||
private object NormalizeValue(JsonElement element)
|
||||
{
|
||||
switch (element.ValueKind)
|
||||
{
|
||||
case JsonValueKind.Object:
|
||||
{
|
||||
var dict = new Dictionary<string, object?>();
|
||||
foreach (var prop in element.EnumerateObject())
|
||||
{
|
||||
var normalizedKey = prop.Name.ToUpperInvariant();
|
||||
dict[normalizedKey] = NormalizeValue(prop.Value);
|
||||
}
|
||||
|
||||
return dict;
|
||||
}
|
||||
|
||||
case JsonValueKind.Array:
|
||||
{
|
||||
if (Regex)
|
||||
{
|
||||
return element.EnumerateArray().Select(e => (object)e.GetRawText()).ToArray();
|
||||
}
|
||||
|
||||
return element.EnumerateArray().Select(NormalizeValue).ToArray();
|
||||
}
|
||||
|
||||
case JsonValueKind.String:
|
||||
{
|
||||
var str = element.GetString()!;
|
||||
return Regex ? str : str.ToUpperInvariant();
|
||||
}
|
||||
|
||||
default:
|
||||
return element.GetRawText();
|
||||
}
|
||||
}
|
||||
|
||||
private static JsonElement ConvertToJsonElement(object value)
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
case JsonElement jsonElement:
|
||||
return jsonElement;
|
||||
|
||||
case JsonDocument jsonDocument:
|
||||
return jsonDocument.RootElement;
|
||||
|
||||
case string stringValue:
|
||||
return JsonDocument.Parse(stringValue).RootElement;
|
||||
|
||||
case IEnumerable enumerableValue when value is not string:
|
||||
return JsonSerializer.SerializeToElement(enumerableValue, DefaultSerializerOptions);
|
||||
|
||||
default:
|
||||
var json = JsonSerializer.Serialize(value, DefaultSerializerOptions);
|
||||
return JsonDocument.Parse(json).RootElement;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using WireMock.Extensions;
|
||||
using WireMock.Util;
|
||||
|
||||
namespace WireMock.Matchers;
|
||||
|
||||
/// <summary>
|
||||
/// SystemTextJsonPartialMatcher - uses System.Text.Json instead of Newtonsoft.Json.
|
||||
/// </summary>
|
||||
public class SystemTextJsonPartialMatcher : AbstractSystemTextJsonPartialMatcher
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override string Name => nameof(SystemTextJsonPartialMatcher);
|
||||
|
||||
/// <inheritdoc />
|
||||
public SystemTextJsonPartialMatcher(string value, bool ignoreCase = false, bool regex = false)
|
||||
: base(value, ignoreCase, regex)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public SystemTextJsonPartialMatcher(object value, bool ignoreCase = false, bool regex = false)
|
||||
: base(value, ignoreCase, regex)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public SystemTextJsonPartialMatcher(MatchBehaviour matchBehaviour, object value, bool ignoreCase = false, bool regex = false)
|
||||
: base(matchBehaviour, value, ignoreCase, regex)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override bool IsMatch(string value, string input)
|
||||
{
|
||||
var exactStringMatcher = new ExactMatcher(MatchBehaviour.AcceptOnMatch, IgnoreCase, MatchOperator.Or, value);
|
||||
return exactStringMatcher.IsMatch(input).IsPerfect();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string GetCSharpCodeArguments()
|
||||
{
|
||||
return $"new {typeof(SystemTextJsonPartialMatcher).FullName}" +
|
||||
$"(" +
|
||||
$"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " +
|
||||
$"{CSharpFormatter.ConvertToAnonymousObjectDefinition(Value, 3)}, " +
|
||||
$"{CSharpFormatter.ToCSharpBooleanLiteral(IgnoreCase)}, " +
|
||||
$"{CSharpFormatter.ToCSharpBooleanLiteral(Regex)}" +
|
||||
$")";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using WireMock.Extensions;
|
||||
using WireMock.Util;
|
||||
|
||||
namespace WireMock.Matchers;
|
||||
|
||||
/// <summary>
|
||||
/// SystemTextJsonPartialWildcardMatcher - uses System.Text.Json instead of Newtonsoft.Json.
|
||||
/// </summary>
|
||||
public class SystemTextJsonPartialWildcardMatcher : AbstractSystemTextJsonPartialMatcher
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override string Name => nameof(SystemTextJsonPartialWildcardMatcher);
|
||||
|
||||
/// <inheritdoc />
|
||||
public SystemTextJsonPartialWildcardMatcher(string value, bool ignoreCase = false, bool regex = false)
|
||||
: base(value, ignoreCase, regex)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public SystemTextJsonPartialWildcardMatcher(object value, bool ignoreCase = false, bool regex = false)
|
||||
: base(value, ignoreCase, regex)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public SystemTextJsonPartialWildcardMatcher(MatchBehaviour matchBehaviour, object value, bool ignoreCase = false, bool regex = false)
|
||||
: base(matchBehaviour, value, ignoreCase, regex)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override bool IsMatch(string value, string input)
|
||||
{
|
||||
var wildcardStringMatcher = new WildcardMatcher(MatchBehaviour.AcceptOnMatch, value, IgnoreCase);
|
||||
return wildcardStringMatcher.IsMatch(input).IsPerfect();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string GetCSharpCodeArguments()
|
||||
{
|
||||
return $"new {typeof(SystemTextJsonPartialWildcardMatcher).FullName}" +
|
||||
$"(" +
|
||||
$"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " +
|
||||
$"{CSharpFormatter.ConvertToAnonymousObjectDefinition(Value, 3)}, " +
|
||||
$"{CSharpFormatter.ToCSharpBooleanLiteral(IgnoreCase)}, " +
|
||||
$"{CSharpFormatter.ToCSharpBooleanLiteral(Regex)}" +
|
||||
$")";
|
||||
}
|
||||
}
|
||||
@@ -101,7 +101,7 @@ public class XPathMatcher : IStringMatcher
|
||||
/// <inheritdoc />
|
||||
public string GetCSharpCodeArguments()
|
||||
{
|
||||
return $"new {Name}" +
|
||||
return $"new {typeof(XPathMatcher).FullName}" +
|
||||
$"(" +
|
||||
$"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " +
|
||||
$"{MatchOperator.GetFullyQualifiedEnumValue()}, " +
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System.Text.RegularExpressions;
|
||||
using WireMock.Matchers;
|
||||
|
||||
namespace WireMock.Owin;
|
||||
|
||||
internal sealed class AdminPaths(string? adminPath) : IAdminPaths
|
||||
{
|
||||
private const string DefaultAdminPathPrefix = "/__admin";
|
||||
|
||||
private readonly string _prefix = adminPath ?? DefaultAdminPathPrefix;
|
||||
|
||||
public string Files => $"{_prefix}/files";
|
||||
|
||||
public string Health => $"{_prefix}/health";
|
||||
|
||||
public string Mappings => $"{_prefix}/mappings";
|
||||
|
||||
public string MappingsCode => $"{_prefix}/mappings/code";
|
||||
|
||||
public string MappingsWireMockOrg => $"{_prefix}/mappings/wiremock.org";
|
||||
|
||||
public string Requests => $"{_prefix}/requests";
|
||||
|
||||
public string Settings => $"{_prefix}/settings";
|
||||
|
||||
public string Scenarios => $"{_prefix}/scenarios";
|
||||
|
||||
public string OpenApi => $"{_prefix}/openapi";
|
||||
|
||||
public string ProtoDefinitions => $"{_prefix}/protodefinitions";
|
||||
|
||||
private string PrefixRegexEscaped => Regex.Escape(_prefix);
|
||||
|
||||
public RegexMatcher MappingsGuidPathMatcher => new($@"^{PrefixRegexEscaped}\/mappings\/([0-9A-Fa-f]{{8}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{12}})$");
|
||||
|
||||
public RegexMatcher MappingsGuidEnablePathMatcher => new($@"^{PrefixRegexEscaped}\/mappings\/([0-9A-Fa-f]{{8}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{12}})\/enable$");
|
||||
|
||||
public RegexMatcher MappingsGuidDisablePathMatcher => new($@"^{PrefixRegexEscaped}\/mappings\/([0-9A-Fa-f]{{8}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{12}})\/disable$");
|
||||
|
||||
public RegexMatcher MappingsCodeGuidPathMatcher => new($@"^{PrefixRegexEscaped}\/mappings\/code\/([0-9A-Fa-f]{{8}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{12}})$");
|
||||
|
||||
public RegexMatcher RequestsGuidPathMatcher => new($@"^{PrefixRegexEscaped}\/requests\/([0-9A-Fa-f]{{8}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{12}})$");
|
||||
|
||||
public RegexMatcher RequestsGuidNearMissesPathMatcher => new($@"^{PrefixRegexEscaped}\/requests\/([0-9A-Fa-f]{{8}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{12}})\/near-misses$");
|
||||
|
||||
public RegexMatcher ScenariosNameMatcher => new($@"^{PrefixRegexEscaped}\/scenarios\/.+$");
|
||||
|
||||
public RegexMatcher ScenariosNameWithStateMatcher => new($@"^{PrefixRegexEscaped}\/scenarios\/.+\/state$");
|
||||
|
||||
public RegexMatcher ScenariosNameWithResetMatcher => new($@"^{PrefixRegexEscaped}\/scenarios\/.+\/reset$");
|
||||
|
||||
public RegexMatcher FilesFilenamePathMatcher => new($@"^{PrefixRegexEscaped}\/files\/.+$");
|
||||
|
||||
public RegexMatcher ProtoDefinitionsIdPathMatcher => new($@"^{PrefixRegexEscaped}\/protodefinitions\/.+$");
|
||||
|
||||
public bool Includes(string? path) => path?.StartsWith($"{_prefix}/") ?? false;
|
||||
|
||||
public override string ToString() => _prefix;
|
||||
}
|
||||
@@ -66,6 +66,7 @@ internal partial class AspNetCoreSelfHost
|
||||
.ConfigureServices(services =>
|
||||
{
|
||||
services.AddSingleton(_wireMockMiddlewareOptions);
|
||||
services.AddSingleton(_wireMockMiddlewareOptions.AdminPaths);
|
||||
services.AddSingleton<IMappingMatcher, MappingMatcher>();
|
||||
services.AddSingleton<IRandomizerDoubleBetween0And1, RandomizerDoubleBetween0And1>();
|
||||
services.AddSingleton<IOwinRequestMapper, OwinRequestMapper>();
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
using WireMock.Matchers;
|
||||
|
||||
namespace WireMock.Owin;
|
||||
|
||||
internal interface IAdminPaths
|
||||
{
|
||||
string Files { get; }
|
||||
|
||||
string Health { get; }
|
||||
|
||||
string Mappings { get; }
|
||||
|
||||
string MappingsCode { get; }
|
||||
|
||||
string MappingsWireMockOrg { get; }
|
||||
|
||||
RegexMatcher MappingsGuidPathMatcher { get; }
|
||||
|
||||
RegexMatcher MappingsGuidEnablePathMatcher { get; }
|
||||
|
||||
RegexMatcher MappingsGuidDisablePathMatcher { get; }
|
||||
|
||||
RegexMatcher MappingsCodeGuidPathMatcher { get; }
|
||||
|
||||
RegexMatcher RequestsGuidPathMatcher { get; }
|
||||
|
||||
RegexMatcher RequestsGuidNearMissesPathMatcher { get; }
|
||||
|
||||
RegexMatcher ScenariosNameMatcher { get; }
|
||||
|
||||
RegexMatcher ScenariosNameWithStateMatcher { get; }
|
||||
|
||||
RegexMatcher ScenariosNameWithResetMatcher { get; }
|
||||
|
||||
RegexMatcher FilesFilenamePathMatcher { get; }
|
||||
|
||||
RegexMatcher ProtoDefinitionsIdPathMatcher { get; }
|
||||
|
||||
string Requests { get; }
|
||||
|
||||
string Settings { get; }
|
||||
|
||||
string Scenarios { get; }
|
||||
|
||||
string OpenApi { get; }
|
||||
|
||||
string ProtoDefinitions { get; }
|
||||
|
||||
bool Includes(string? path);
|
||||
}
|
||||
@@ -5,4 +5,6 @@ namespace WireMock.Owin;
|
||||
internal interface IMappingMatcher
|
||||
{
|
||||
(MappingMatcherResult? Match, MappingMatcherResult? Partial) FindBestMatch(RequestMessage request);
|
||||
|
||||
IReadOnlyList<MappingMatcherResult> FindNearMisses(IRequestMessage request, int maxResults);
|
||||
}
|
||||
@@ -108,4 +108,6 @@ internal interface IWireMockMiddlewareOptions
|
||||
/// Set this property to customize how objects are serialized to and deserialized from JSON during mapping.
|
||||
/// </remarks>
|
||||
IJsonConverter DefaultJsonSerializer { get; set; }
|
||||
|
||||
IAdminPaths AdminPaths { get; set; }
|
||||
}
|
||||
@@ -12,7 +12,7 @@ namespace WireMock.Owin.Mappers;
|
||||
/// <summary>
|
||||
/// OwinRequestMapper
|
||||
/// </summary>
|
||||
internal class OwinRequestMapper : IOwinRequestMapper
|
||||
internal class OwinRequestMapper(IDateTimeUtils dateTimeUtils) : IOwinRequestMapper
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<RequestMessage> MapAsync(HttpContext context, IWireMockMiddlewareOptions options)
|
||||
@@ -53,7 +53,8 @@ internal class OwinRequestMapper : IOwinRequestMapper
|
||||
ContentType = request.ContentType,
|
||||
DeserializeJson = !options.DisableJsonBodyParsing.GetValueOrDefault(false),
|
||||
ContentEncoding = contentEncodingHeader?.FirstOrDefault(),
|
||||
DecompressGZipAndDeflate = !options.DisableRequestBodyDecompressing.GetValueOrDefault(false)
|
||||
DecompressGZipAndDeflate = !options.DisableRequestBodyDecompressing.GetValueOrDefault(false),
|
||||
DefaultJsonConverter = options.DefaultJsonSerializer
|
||||
};
|
||||
|
||||
body = await BodyParser.ParseAsync(bodyParserSettings).ConfigureAwait(false);
|
||||
@@ -71,7 +72,7 @@ internal class OwinRequestMapper : IOwinRequestMapper
|
||||
await request.HttpContext.Connection.GetClientCertificateAsync()
|
||||
)
|
||||
{
|
||||
DateTime = DateTime.UtcNow
|
||||
DateTime = dateTimeUtils.UtcNow
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -83,6 +83,39 @@ internal class MappingMatcher(IWireMockMiddlewareOptions options, IRandomizerDou
|
||||
return (match, partialMatch);
|
||||
}
|
||||
|
||||
public IReadOnlyList<MappingMatcherResult> FindNearMisses(IRequestMessage request, int maxResults)
|
||||
{
|
||||
Guard.NotNull(request);
|
||||
|
||||
var nearMisses = new List<MappingMatcherResult>();
|
||||
|
||||
// Probability is not taken into account here: a mapping which was skipped based on probability can still be a near miss.
|
||||
foreach (var mapping in _options.Mappings.Values.Where(m => !m.IsAdminInterface && !m.IsDisabled && m.TimeSettings.IsValid()))
|
||||
{
|
||||
try
|
||||
{
|
||||
var requestMatchResult = mapping.GetRequestMatchResult(request, GetNextState(mapping));
|
||||
if (requestMatchResult.MatchDetails.All(md => md.Exception == null))
|
||||
{
|
||||
nearMisses.Add(new MappingMatcherResult(mapping, requestMatchResult));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogException(mapping, ex);
|
||||
}
|
||||
}
|
||||
|
||||
// A near miss is a mapping which matches the request partially: perfect matches and complete mismatches are excluded.
|
||||
return nearMisses
|
||||
.Where(nm => !nm.RequestMatchResult.IsPerfectMatch && nm.RequestMatchResult.AverageTotalScore > 0.0)
|
||||
.OrderBy(nm => nm.RequestMatchResult)
|
||||
.ThenBy(nm => nm.Mapping.Priority)
|
||||
.ThenByDescending(nm => nm.Mapping.UpdatedAt)
|
||||
.Take(maxResults)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
private void LogException(IMapping mapping, Exception ex)
|
||||
{
|
||||
_options.Logger.Error($"Getting a Request MatchResult for Mapping '{mapping.Guid}' failed. This mapping will not be evaluated. Exception: {ex}");
|
||||
|
||||
@@ -21,6 +21,7 @@ internal class WireMockMiddleware(
|
||||
RequestDelegate next,
|
||||
#pragma warning restore CS9113 // Parameter is unread.
|
||||
IWireMockMiddlewareOptions options,
|
||||
IAdminPaths adminPaths,
|
||||
IOwinRequestMapper requestMapper,
|
||||
IOwinResponseMapper responseMapper,
|
||||
IMappingMatcher mappingMatcher,
|
||||
@@ -64,7 +65,7 @@ internal class WireMockMiddleware(
|
||||
Activity? activity = null;
|
||||
|
||||
// Check if we should trace this request (optionally exclude admin requests)
|
||||
var shouldTrace = tracingEnabled && !(excludeAdmin && request.Path.StartsWith("/__admin/"));
|
||||
var shouldTrace = tracingEnabled && !(excludeAdmin && adminPaths.Includes(request.Path));
|
||||
|
||||
if (shouldTrace)
|
||||
{
|
||||
@@ -170,6 +171,12 @@ internal class WireMockMiddleware(
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Set the DateTime from the Response to the current DateTime
|
||||
if (response is ResponseMessage responseMessage)
|
||||
{
|
||||
responseMessage.DateTime = dateTimeUtils.UtcNow;
|
||||
}
|
||||
|
||||
logger.LogRequestAndResponse(logRequest, request, response, result.Match, result.Partial, activity);
|
||||
|
||||
try
|
||||
@@ -254,4 +261,4 @@ internal class WireMockMiddleware(
|
||||
scenario.Finished = mapping.NextState == null;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using WireMock.Logging;
|
||||
using WireMock.Owin.ActivityTracing;
|
||||
using WireMock.Serialization;
|
||||
@@ -10,39 +9,44 @@ using WireMock.Util;
|
||||
namespace WireMock.Owin;
|
||||
|
||||
internal class WireMockMiddlewareLogger(
|
||||
IWireMockMiddlewareOptions _options,
|
||||
LogEntryMapper _logEntryMapper,
|
||||
IGuidUtils _guidUtils
|
||||
IWireMockMiddlewareOptions options,
|
||||
LogEntryMapper logEntryMapper,
|
||||
IGuidUtils guidUtils,
|
||||
IAdminPaths adminPaths,
|
||||
IDateTimeUtils dateTimeUtils
|
||||
) : IWireMockMiddlewareLogger
|
||||
{
|
||||
public void LogRequestAndResponse(bool logRequest, RequestMessage request, IResponseMessage? response, MappingMatcherResult? match, MappingMatcherResult? partialMatch, Activity? activity)
|
||||
{
|
||||
var mapping = match?.Mapping;
|
||||
var partialMapping = partialMatch?.Mapping;
|
||||
|
||||
var logEntry = new LogEntry
|
||||
{
|
||||
Guid = _guidUtils.NewGuid(),
|
||||
Guid = guidUtils.NewGuid(),
|
||||
RequestMessage = request,
|
||||
ResponseMessage = response,
|
||||
|
||||
MappingGuid = match?.Mapping?.Guid,
|
||||
MappingTitle = match?.Mapping?.Title,
|
||||
MappingGuid = mapping?.Guid,
|
||||
MappingTitle = mapping?.Title,
|
||||
RequestMatchResult = match?.RequestMatchResult,
|
||||
|
||||
PartialMappingGuid = partialMatch?.Mapping?.Guid,
|
||||
PartialMappingTitle = partialMatch?.Mapping?.Title,
|
||||
PartialMappingGuid = partialMapping?.Guid,
|
||||
PartialMappingTitle = partialMapping?.Title,
|
||||
PartialMatchResult = partialMatch?.RequestMatchResult
|
||||
};
|
||||
|
||||
WireMockActivitySource.EnrichWithLogEntry(activity, logEntry, _options.ActivityTracingOptions);
|
||||
WireMockActivitySource.EnrichWithLogEntry(activity, logEntry, options.ActivityTracingOptions);
|
||||
activity?.Dispose();
|
||||
|
||||
LogLogEntry(logEntry, logRequest);
|
||||
|
||||
try
|
||||
{
|
||||
if (_options.SaveUnmatchedRequests == true && match?.RequestMatchResult is not { IsPerfectMatch: true })
|
||||
if (options.SaveUnmatchedRequests == true && match?.RequestMatchResult is not { IsPerfectMatch: true })
|
||||
{
|
||||
var filename = $"{logEntry.Guid}.LogEntry.json";
|
||||
_options.FileSystemHandler?.WriteUnmatchedRequest(filename, JsonUtils.Serialize(logEntry));
|
||||
options.FileSystemHandler?.WriteUnmatchedRequest(filename, options.DefaultJsonSerializer.Serialize(logEntry));
|
||||
}
|
||||
}
|
||||
catch
|
||||
@@ -53,35 +57,36 @@ internal class WireMockMiddlewareLogger(
|
||||
|
||||
public void LogLogEntry(LogEntry entry, bool addRequest)
|
||||
{
|
||||
_options.Logger.DebugRequestResponse(_logEntryMapper.Map(entry), entry.RequestMessage?.Path.StartsWith("/__admin/") == true);
|
||||
var isAdminRequest = adminPaths.Includes(entry.RequestMessage?.Path);
|
||||
options.Logger.DebugRequestResponse(logEntryMapper.Map(entry), isAdminRequest);
|
||||
|
||||
// If addRequest is set to true and MaxRequestLogCount is null or does have a value greater than 0, try to add a new request log.
|
||||
if (addRequest && _options.MaxRequestLogCount is null or > 0)
|
||||
if (addRequest && options.MaxRequestLogCount is null or > 0)
|
||||
{
|
||||
TryAddLogEntry(entry);
|
||||
}
|
||||
|
||||
|
||||
// In case MaxRequestLogCount has a value greater than 0, try to delete existing request logs based on the count.
|
||||
if (_options.MaxRequestLogCount is > 0)
|
||||
if (options.MaxRequestLogCount is > 0)
|
||||
{
|
||||
var logEntries = _options.LogEntries.ToList();
|
||||
var logEntries = options.LogEntries.ToList();
|
||||
|
||||
foreach (var logEntry in logEntries
|
||||
.OrderBy(le => le.RequestMessage?.DateTime ?? le.ResponseMessage?.DateTime)
|
||||
.Take(logEntries.Count - _options.MaxRequestLogCount.Value))
|
||||
.OrderBy(le => le.RequestMessage?.DateTime)
|
||||
.Take(logEntries.Count - options.MaxRequestLogCount.Value))
|
||||
{
|
||||
TryRemoveLogEntry(logEntry);
|
||||
}
|
||||
}
|
||||
|
||||
// In case RequestLogExpirationDuration has a value greater than 0, try to delete existing request logs based on the date.
|
||||
if (_options.RequestLogExpirationDuration is > 0)
|
||||
// In case RequestLogExpirationDuration has a value greater than 0, try to delete existing request logs based on the RequestMessage date.
|
||||
if (options.RequestLogExpirationDuration is > 0)
|
||||
{
|
||||
var logEntries = _options.LogEntries.ToList();
|
||||
var logEntries = options.LogEntries.ToList();
|
||||
|
||||
var checkTime = DateTime.UtcNow.AddHours(-_options.RequestLogExpirationDuration.Value);
|
||||
foreach (var logEntry in logEntries.Where(le => le.RequestMessage?.DateTime < checkTime || le.ResponseMessage?.DateTime < checkTime))
|
||||
var checkTime = dateTimeUtils.UtcNow.AddHours(-options.RequestLogExpirationDuration.Value);
|
||||
foreach (var logEntry in logEntries.Where(le => le.RequestMessage?.DateTime < checkTime))
|
||||
{
|
||||
TryRemoveLogEntry(logEntry);
|
||||
}
|
||||
@@ -92,7 +97,7 @@ internal class WireMockMiddlewareLogger(
|
||||
{
|
||||
try
|
||||
{
|
||||
_options.LogEntries.Add(logEntry);
|
||||
options.LogEntries.Add(logEntry);
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -104,7 +109,7 @@ internal class WireMockMiddlewareLogger(
|
||||
{
|
||||
try
|
||||
{
|
||||
_options.LogEntries.Remove(logEntry);
|
||||
options.LogEntries.Remove(logEntry);
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
@@ -115,4 +115,7 @@ internal class WireMockMiddlewareOptions : IWireMockMiddlewareOptions
|
||||
|
||||
/// <inheritdoc />
|
||||
public IJsonConverter DefaultJsonSerializer { get; set; } = new NewtonsoftJsonConverter();
|
||||
|
||||
/// <inheritdoc />
|
||||
public IAdminPaths AdminPaths { get; set; } = new AdminPaths(null);
|
||||
}
|
||||
@@ -34,6 +34,7 @@ internal static class WireMockMiddlewareOptionsHelper
|
||||
options.QueryParameterMultipleValueSupport = settings.QueryParameterMultipleValueSupport;
|
||||
options.RequestLogExpirationDuration = settings.RequestLogExpirationDuration;
|
||||
options.SaveUnmatchedRequests = settings.SaveUnmatchedRequests;
|
||||
options.AdminPaths = new AdminPaths(settings.AdminPath);
|
||||
|
||||
#if USE_ASPNETCORE
|
||||
options.AdditionalServiceRegistration = settings.AdditionalServiceRegistration;
|
||||
|
||||
@@ -7,4 +7,4 @@ using System.Runtime.CompilerServices;
|
||||
[assembly: InternalsVisibleTo("WireMock.Net.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e138ec44d93acac565953052636eb8d5e7e9f27ddb030590055cd1a0ab2069a5623f1f77ca907d78e0b37066ca0f6d63da7eecc3fcb65b76aa8ebeccf7ebe1d11264b8404cd9b1cbbf2c83f566e033b3e54129f6ef28daffff776ba7aebbc53c0d635ebad8f45f78eb3f7e0459023c218f003416e080f96a1a3c5ffeb56bee9e")]
|
||||
|
||||
// Needed for Moq in the UnitTest project
|
||||
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")]
|
||||
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")]
|
||||
@@ -48,7 +48,8 @@ internal class ProxyHelper(WireMockServerSettings settings)
|
||||
originalUri,
|
||||
deserializeJson,
|
||||
decompressGzipAndDeflate,
|
||||
deserializeFormUrlEncoded
|
||||
deserializeFormUrlEncoded,
|
||||
_settings.DefaultJsonSerializer
|
||||
).ConfigureAwait(false);
|
||||
|
||||
IMapping? newMapping = null;
|
||||
|
||||
@@ -19,7 +19,7 @@ public partial class Request
|
||||
{
|
||||
Guard.NotNullOrEmpty(matchers);
|
||||
|
||||
_requestMatchers.Add(new RequestMessageClientIPMatcher(MatchBehaviour.AcceptOnMatch, MatchOperator.Or, matchers));
|
||||
_requestMatchers.Add(new RequestMessageClientIPMatcher(MatchBehaviour.AcceptOnMatch, matchOperator, matchers));
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
using Stef.Validation;
|
||||
using WireMock.Matchers;
|
||||
using WireMock.Matchers.Request;
|
||||
using WireMock.Types;
|
||||
using WireMock.Util;
|
||||
|
||||
namespace WireMock.RequestBuilders;
|
||||
@@ -84,7 +85,7 @@ public partial class Request
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IRequestBuilder WithBody(Func<IDictionary<string, string>?, bool> func)
|
||||
public IRequestBuilder WithBody(Func<IDictionary<string, WireMockList<string>>?, bool> func)
|
||||
{
|
||||
_requestMatchers.Add(new RequestMessageBodyMatcher(Guard.NotNull(func)));
|
||||
return this;
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
|
||||
using System.Text;
|
||||
using JsonConverter.Abstractions;
|
||||
using JsonConverter.Newtonsoft.Json;
|
||||
using Stef.Validation;
|
||||
using WireMock.Models;
|
||||
using WireMock.Serialization;
|
||||
using WireMock.Types;
|
||||
using WireMock.Util;
|
||||
|
||||
@@ -119,11 +121,13 @@ public partial class Response
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IResponseBuilder WithBody(string body, string? destination = BodyDestinationFormat.SameAsSource, Encoding? encoding = null)
|
||||
public IResponseBuilder WithBody(string body, string? destination = BodyDestinationFormat.SameAsSource, Encoding? encoding = null, IJsonConverter? jsonConverter = null, JsonConverterOptions? options = null)
|
||||
{
|
||||
Guard.NotNull(body);
|
||||
|
||||
encoding ??= Encoding.UTF8;
|
||||
jsonConverter ??= new NewtonsoftJsonConverter();
|
||||
options ??= JsonSerializationConstants.JsonConverterOptionsWithDateParsingNone;
|
||||
|
||||
ResponseMessage.BodyDestination = destination;
|
||||
ResponseMessage.BodyData = new BodyData
|
||||
@@ -140,7 +144,7 @@ public partial class Response
|
||||
|
||||
case BodyDestinationFormat.Json:
|
||||
ResponseMessage.BodyData.DetectedBodyType = BodyType.Json;
|
||||
ResponseMessage.BodyData.BodyAsJson = JsonUtils.DeserializeObject(body);
|
||||
ResponseMessage.BodyData.BodyAsJson = jsonConverter.Deserialize<object>(body, options);
|
||||
break;
|
||||
|
||||
default:
|
||||
|
||||
@@ -11,7 +11,6 @@ using WireMock.RequestBuilders;
|
||||
using WireMock.Settings;
|
||||
using WireMock.Transformers;
|
||||
using WireMock.Types;
|
||||
using WireMock.Util;
|
||||
|
||||
namespace WireMock.ResponseBuilders;
|
||||
|
||||
@@ -86,7 +85,11 @@ public partial class Response : IResponseBuilder
|
||||
[PublicAPI]
|
||||
public static IResponseBuilder Create(ResponseMessage? responseMessage = null)
|
||||
{
|
||||
var message = responseMessage ?? new ResponseMessage();
|
||||
var message = responseMessage ?? new ResponseMessage
|
||||
{
|
||||
DateTime = DateTime.UtcNow // Initially, this is set to the current time, but will be updated to the real time once the response has been sent.
|
||||
};
|
||||
|
||||
return new Response(message);
|
||||
}
|
||||
|
||||
@@ -197,7 +200,7 @@ public partial class Response : IResponseBuilder
|
||||
|
||||
if (ProxyAndRecordSettings != null && _httpClientForProxy != null)
|
||||
{
|
||||
string RemoveFirstOccurrence(string source, string find)
|
||||
static string RemoveFirstOccurrence(string source, string find)
|
||||
{
|
||||
int place = source.IndexOf(find, StringComparison.OrdinalIgnoreCase);
|
||||
return place >= 0 ? source.Remove(place, find.Length) : source;
|
||||
@@ -265,7 +268,7 @@ public partial class Response : IResponseBuilder
|
||||
var decoded = await protoBufMatcher.DecodeAsync(requestMessage.BodyData?.BodyAsBytes).ConfigureAwait(false);
|
||||
if (decoded != null)
|
||||
{
|
||||
requestMessageImplementation.BodyAsJson = JsonUtils.ConvertValueToJToken(decoded);
|
||||
requestMessageImplementation.BodyAsJson = settings.DefaultJsonSerializer.ToJsonToken(decoded);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
// This source file is based on mock4net by Alexandre Victoor which is licensed under the Apache 2.0 License.
|
||||
// For more details see 'mock4net/LICENSE.txt' and 'mock4net/readme.md' in this project root.
|
||||
using System.Linq;
|
||||
using Stef.Validation;
|
||||
using WireMock.ResponseBuilders;
|
||||
using WireMock.Types;
|
||||
|
||||
@@ -67,7 +67,8 @@ internal class WebSocketResponseProvider(WebSocketBuilder builder, IGuidUtils gu
|
||||
builder,
|
||||
options,
|
||||
logger,
|
||||
guidUtils
|
||||
guidUtils,
|
||||
dateTimeUtils
|
||||
);
|
||||
|
||||
// Add to registry
|
||||
|
||||
@@ -14,60 +14,7 @@ internal class LogEntryMapper(IWireMockMiddlewareOptions options)
|
||||
{
|
||||
public LogEntryModel Map(ILogEntry logEntry)
|
||||
{
|
||||
LogRequestModel? logRequestModel = null;
|
||||
if (logEntry.RequestMessage != null)
|
||||
{
|
||||
logRequestModel = new LogRequestModel
|
||||
{
|
||||
DateTime = logEntry.RequestMessage.DateTime,
|
||||
ClientIP = logEntry.RequestMessage.ClientIP,
|
||||
Path = logEntry.RequestMessage.Path,
|
||||
AbsolutePath = logEntry.RequestMessage.AbsolutePath,
|
||||
Url = logEntry.RequestMessage.Url,
|
||||
AbsoluteUrl = logEntry.RequestMessage.AbsoluteUrl,
|
||||
ProxyUrl = logEntry.RequestMessage.ProxyUrl,
|
||||
Query = logEntry.RequestMessage.Query,
|
||||
Method = logEntry.RequestMessage.Method,
|
||||
HttpVersion = logEntry.RequestMessage.HttpVersion,
|
||||
Headers = logEntry.RequestMessage.Headers,
|
||||
Cookies = logEntry.RequestMessage.Cookies
|
||||
};
|
||||
|
||||
if (logEntry.RequestMessage.BodyData != null)
|
||||
{
|
||||
logRequestModel.DetectedBodyType = logEntry.RequestMessage.BodyData.DetectedBodyType?.ToString();
|
||||
logRequestModel.DetectedBodyTypeFromContentType = logEntry.RequestMessage.BodyData.DetectedBodyTypeFromContentType?.ToString();
|
||||
|
||||
switch (logEntry.RequestMessage.BodyData.DetectedBodyType)
|
||||
{
|
||||
case BodyType.String:
|
||||
case BodyType.FormUrlEncoded:
|
||||
case BodyType.WebSocketText:
|
||||
case BodyType.WebSocketClose:
|
||||
logRequestModel.Body = logEntry.RequestMessage.BodyData.BodyAsString;
|
||||
break;
|
||||
|
||||
case BodyType.Json:
|
||||
logRequestModel.Body = logEntry.RequestMessage.BodyData.BodyAsString; // In case of Json, do also save the Body as string (backwards compatible)
|
||||
logRequestModel.BodyAsJson = logEntry.RequestMessage.BodyData.BodyAsJson;
|
||||
break;
|
||||
|
||||
case BodyType.Bytes:
|
||||
case BodyType.WebSocketBinary:
|
||||
logRequestModel.BodyAsBytes = logEntry.RequestMessage.BodyData.BodyAsBytes;
|
||||
break;
|
||||
}
|
||||
|
||||
logRequestModel.BodyEncoding = logEntry.RequestMessage.BodyData.Encoding != null
|
||||
? new EncodingModel
|
||||
{
|
||||
EncodingName = logEntry.RequestMessage.BodyData.Encoding.EncodingName,
|
||||
CodePage = logEntry.RequestMessage.BodyData.Encoding.CodePage,
|
||||
WebName = logEntry.RequestMessage.BodyData.Encoding.WebName
|
||||
}
|
||||
: null;
|
||||
}
|
||||
}
|
||||
var logRequestModel = logEntry.RequestMessage != null ? MapRequest(logEntry.RequestMessage) : null;
|
||||
|
||||
LogResponseModel? logResponseModel = null;
|
||||
if (logEntry.ResponseMessage != null)
|
||||
@@ -123,6 +70,74 @@ internal class LogEntryMapper(IWireMockMiddlewareOptions options)
|
||||
};
|
||||
}
|
||||
|
||||
public NearMissModel Map(NearMiss nearMiss)
|
||||
{
|
||||
return new NearMissModel
|
||||
{
|
||||
LogEntryGuid = nearMiss.LogEntryGuid,
|
||||
Request = MapRequest(nearMiss.RequestMessage),
|
||||
MappingGuid = nearMiss.MappingGuid,
|
||||
MappingTitle = nearMiss.MappingTitle,
|
||||
RequestMatchResult = Map(nearMiss.RequestMatchResult)
|
||||
};
|
||||
}
|
||||
|
||||
private static LogRequestModel MapRequest(IRequestMessage requestMessage)
|
||||
{
|
||||
var logRequestModel = new LogRequestModel
|
||||
{
|
||||
DateTime = requestMessage.DateTime,
|
||||
ClientIP = requestMessage.ClientIP,
|
||||
Path = requestMessage.Path,
|
||||
AbsolutePath = requestMessage.AbsolutePath,
|
||||
Url = requestMessage.Url,
|
||||
AbsoluteUrl = requestMessage.AbsoluteUrl,
|
||||
ProxyUrl = requestMessage.ProxyUrl,
|
||||
Query = requestMessage.Query,
|
||||
Method = requestMessage.Method,
|
||||
HttpVersion = requestMessage.HttpVersion,
|
||||
Headers = requestMessage.Headers,
|
||||
Cookies = requestMessage.Cookies
|
||||
};
|
||||
|
||||
if (requestMessage.BodyData != null)
|
||||
{
|
||||
logRequestModel.DetectedBodyType = requestMessage.BodyData.DetectedBodyType?.ToString();
|
||||
logRequestModel.DetectedBodyTypeFromContentType = requestMessage.BodyData.DetectedBodyTypeFromContentType?.ToString();
|
||||
|
||||
switch (requestMessage.BodyData.DetectedBodyType)
|
||||
{
|
||||
case BodyType.String:
|
||||
case BodyType.FormUrlEncoded:
|
||||
case BodyType.WebSocketText:
|
||||
case BodyType.WebSocketClose:
|
||||
logRequestModel.Body = requestMessage.BodyData.BodyAsString;
|
||||
break;
|
||||
|
||||
case BodyType.Json:
|
||||
logRequestModel.Body = requestMessage.BodyData.BodyAsString; // In case of Json, do also save the Body as string (backwards compatible)
|
||||
logRequestModel.BodyAsJson = requestMessage.BodyData.BodyAsJson;
|
||||
break;
|
||||
|
||||
case BodyType.Bytes:
|
||||
case BodyType.WebSocketBinary:
|
||||
logRequestModel.BodyAsBytes = requestMessage.BodyData.BodyAsBytes;
|
||||
break;
|
||||
}
|
||||
|
||||
logRequestModel.BodyEncoding = requestMessage.BodyData.Encoding != null
|
||||
? new EncodingModel
|
||||
{
|
||||
EncodingName = requestMessage.BodyData.Encoding.EncodingName,
|
||||
CodePage = requestMessage.BodyData.Encoding.CodePage,
|
||||
WebName = requestMessage.BodyData.Encoding.WebName
|
||||
}
|
||||
: null;
|
||||
}
|
||||
|
||||
return logRequestModel;
|
||||
}
|
||||
|
||||
private void MapBody(ILogEntry logEntry, LogResponseModel logResponseModel)
|
||||
{
|
||||
switch (logEntry.ResponseMessage?.BodyData?.DetectedBodyType)
|
||||
|
||||
@@ -146,7 +146,7 @@ internal class MappingConverter(MatcherMapper mapper)
|
||||
|
||||
case JsonMatcher jsonMatcher:
|
||||
{
|
||||
var matcherType = jsonMatcher.GetType().Name;
|
||||
var matcherType = jsonMatcher.GetType().FullName;
|
||||
sb.AppendLine($" .WithBody(new {matcherType}(");
|
||||
sb.AppendLine($" value: {ConvertToAnonymousObjectDefinition(jsonMatcher.Value, 3)},");
|
||||
sb.AppendLine($" ignoreCase: {ToCSharpBooleanLiteral(jsonMatcher.IgnoreCase)},");
|
||||
@@ -162,14 +162,18 @@ internal class MappingConverter(MatcherMapper mapper)
|
||||
// Guid
|
||||
sb.AppendLine($" .WithGuid(\"{mapping.Guid}\")");
|
||||
|
||||
// Scenario + State
|
||||
// Scenario, ExecutionConditionState, NextState and StateTimes
|
||||
if (!string.IsNullOrEmpty(mapping.Scenario))
|
||||
{
|
||||
sb.AppendLine($" .InScenario({ToCSharpStringLiteral(mapping.Scenario)})");
|
||||
}
|
||||
if (!string.IsNullOrEmpty(mapping.ExecutionConditionState))
|
||||
{
|
||||
sb.AppendLine($" .WhenStateIs({ToCSharpStringLiteral(mapping.ExecutionConditionState)})");
|
||||
}
|
||||
if (!string.IsNullOrEmpty(mapping.NextState))
|
||||
{
|
||||
sb.AppendLine($" .WhenStateIs({ToCSharpStringLiteral(mapping.NextState)}, {ToCSharpIntLiteral(mapping.TimesInSameState)})");
|
||||
sb.AppendLine($" .WillSetStateTo({ToCSharpStringLiteral(mapping.NextState)}, {ToCSharpIntLiteral(mapping.TimesInSameState)})");
|
||||
}
|
||||
|
||||
if (mapping.Probability != null)
|
||||
@@ -342,7 +346,7 @@ internal class MappingConverter(MatcherMapper mapper)
|
||||
if (clientIPMatcher?.Matchers != null)
|
||||
{
|
||||
var clientIPMatchers = _mapper.Map(clientIPMatcher.Matchers);
|
||||
mappingModel.Request.Path = new ClientIPModel
|
||||
mappingModel.Request.ClientIP = new ClientIPModel
|
||||
{
|
||||
Matchers = clientIPMatchers,
|
||||
MatchOperator = clientIPMatchers?.Length > 1 ? clientIPMatcher.MatchOperator.ToString() : null
|
||||
|
||||
@@ -1,55 +1,31 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using JsonConverter.Abstractions;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
#if NETSTANDARD2_0_OR_GREATER || NETCOREAPP3_1_OR_GREATER || NET6_0_OR_GREATER || NET461
|
||||
using System.Text.Json;
|
||||
#endif
|
||||
using JsonConverter.Abstractions.Models;
|
||||
|
||||
namespace WireMock.Serialization;
|
||||
|
||||
internal class MappingSerializer(IJsonConverter jsonConverter)
|
||||
{
|
||||
private static readonly JsonConverterOptions JsonConverterOptions = new JsonConverterOptions
|
||||
{
|
||||
DateParseHandling = (int) DateParseHandling.None
|
||||
};
|
||||
|
||||
internal T[] DeserializeJsonToArray<T>(string value)
|
||||
{
|
||||
// DeserializeObject
|
||||
return DeserializeObjectToArray<T>(jsonConverter.Deserialize<object>(value, JsonConverterOptions)!);
|
||||
switch (JsonTypeHelper.GetJsonType(value))
|
||||
{
|
||||
case JsonType.Array:
|
||||
return jsonConverter.Deserialize<T[]>(value, JsonSerializationConstants.JsonConverterOptionsWithDateParsingNone)!;
|
||||
|
||||
case JsonType.Object:
|
||||
var singleResult = jsonConverter.Deserialize<T>(value, JsonSerializationConstants.JsonConverterOptionsWithDateParsingNone);
|
||||
return [singleResult!];
|
||||
|
||||
default:
|
||||
throw new InvalidOperationException("Cannot deserialize the provided value to an array or object.");
|
||||
}
|
||||
}
|
||||
|
||||
internal static T[] DeserializeObjectToArray<T>(object value)
|
||||
internal T[] DeserializeObjectToArray<T>(object value)
|
||||
{
|
||||
if (value is JArray jArray)
|
||||
{
|
||||
return jArray.ToObject<T[]>()!;
|
||||
}
|
||||
|
||||
if (value is JObject jObject)
|
||||
{
|
||||
var singleResult = jObject.ToObject<T>();
|
||||
return [singleResult!];
|
||||
}
|
||||
|
||||
#if NETSTANDARD2_0_OR_GREATER || NETCOREAPP3_1_OR_GREATER || NET6_0_OR_GREATER || NET461
|
||||
if (value is JsonElement jElement)
|
||||
{
|
||||
if (jElement.ValueKind == JsonValueKind.Array)
|
||||
{
|
||||
return jElement.Deserialize<T[]>()!;
|
||||
}
|
||||
|
||||
if (jElement.ValueKind == JsonValueKind.Object)
|
||||
{
|
||||
var singleResult = jElement.Deserialize<T>();
|
||||
return [singleResult!];
|
||||
}
|
||||
}
|
||||
#endif
|
||||
throw new InvalidOperationException("Cannot deserialize the provided value to an array or object.");
|
||||
var json = jsonConverter.Serialize(value, JsonSerializationConstants.JsonConverterOptionsWithDateParsingNone);
|
||||
return DeserializeJsonToArray<T>(json);
|
||||
}
|
||||
}
|
||||
@@ -106,9 +106,29 @@ internal class MatcherMapper
|
||||
var valueForJsonPartialWildcardMatcher = matcherModel.Pattern ?? matcherModel.Patterns;
|
||||
return new JsonPartialWildcardMatcher(matchBehaviour, valueForJsonPartialWildcardMatcher!, ignoreCase, useRegex);
|
||||
|
||||
case nameof(SystemTextJsonMatcher):
|
||||
var valueForSystemTextJsonMatcher = matcherModel.Pattern ?? matcherModel.Patterns;
|
||||
return new SystemTextJsonMatcher(matchBehaviour, valueForSystemTextJsonMatcher!, ignoreCase, useRegex);
|
||||
|
||||
case nameof(SystemTextJsonPartialMatcher):
|
||||
var valueForSystemTextJsonPartialMatcher = matcherModel.Pattern ?? matcherModel.Patterns;
|
||||
return new SystemTextJsonPartialMatcher(matchBehaviour, valueForSystemTextJsonPartialMatcher!, ignoreCase, useRegex);
|
||||
|
||||
case nameof(SystemTextJsonPartialWildcardMatcher):
|
||||
var valueForSystemTextJsonPartialWildcardMatcher = matcherModel.Pattern ?? matcherModel.Patterns;
|
||||
return new SystemTextJsonPartialWildcardMatcher(matchBehaviour, valueForSystemTextJsonPartialWildcardMatcher!, ignoreCase, useRegex);
|
||||
|
||||
case nameof(JsonPathMatcher):
|
||||
return new JsonPathMatcher(matchBehaviour, matchOperator, stringPatterns);
|
||||
|
||||
case "SystemTextJsonPathMatcher":
|
||||
if (TypeLoader.TryLoadNewInstance<ISystemTextJsonPathMatcher>(out var systemTextJsonPathMatcher, matchBehaviour, matchOperator, stringPatterns))
|
||||
{
|
||||
return systemTextJsonPathMatcher;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("The 'SystemTextJsonPathMatcher' cannot be loaded. Please install the WireMock.Net.Matchers.SystemTextJsonPath package.");
|
||||
|
||||
case nameof(JmesPathMatcher):
|
||||
return new JmesPathMatcher(matchBehaviour, matchOperator, stringPatterns);
|
||||
|
||||
@@ -171,6 +191,10 @@ internal class MatcherMapper
|
||||
model.Regex = jsonMatcher.Regex;
|
||||
break;
|
||||
|
||||
case SystemTextJsonMatcher stjMatcher:
|
||||
model.Regex = stjMatcher.Regex;
|
||||
break;
|
||||
|
||||
case XPathMatcher xpathMatcher:
|
||||
model.XmlNamespaceMap = xpathMatcher.XmlNamespaceMap;
|
||||
break;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Newtonsoft.Json;
|
||||
using WireMock.Admin.Mappings;
|
||||
using WireMock.Extensions;
|
||||
using WireMock.Pact.Models.V2;
|
||||
@@ -49,7 +51,7 @@ internal static class PactMapper
|
||||
pact.Interactions.Add(interaction);
|
||||
}
|
||||
|
||||
return (filename, JsonUtils.SerializeAsPactFile(pact));
|
||||
return (filename, SerializeAsPactFile(pact));
|
||||
}
|
||||
|
||||
private static PactRequest MapRequest(RequestModel request, string path)
|
||||
@@ -152,7 +154,7 @@ internal static class PactMapper
|
||||
/// </summary>
|
||||
private static object? TryDeserializeJsonStringAsObject(string? value)
|
||||
{
|
||||
return value != null ? JsonUtils.TryDeserializeObject<object?>(value) ?? value : null;
|
||||
return value != null ? TryDeserializeObject<object?>(value) ?? value : null;
|
||||
}
|
||||
|
||||
//private static string GetPatternAsStringFromMatchers(MatcherModel[]? matchers, string defaultValue)
|
||||
@@ -164,4 +166,22 @@ internal static class PactMapper
|
||||
|
||||
// return defaultValue;
|
||||
//}
|
||||
|
||||
private static byte[] SerializeAsPactFile(object value)
|
||||
{
|
||||
var json = JsonConvert.SerializeObject(value, JsonSerializationConstants.JsonSerializerSettingsPact);
|
||||
return Encoding.UTF8.GetBytes(json);
|
||||
}
|
||||
|
||||
private static T? TryDeserializeObject<T>(string json)
|
||||
{
|
||||
try
|
||||
{
|
||||
return JsonConvert.DeserializeObject<T>(json);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return default;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System.Linq;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NJsonSchema;
|
||||
using NJsonSchema.Extensions;
|
||||
using NSwag;
|
||||
@@ -281,7 +282,7 @@ internal static class SwaggerMapper
|
||||
if (matcher is { Name: nameof(JsonMatcher) })
|
||||
{
|
||||
var pattern = GetPatternAsStringFromMatcher(matcher);
|
||||
if (JsonUtils.TryParseAsJObject(pattern, out var jObject))
|
||||
if (TryParseAsJObject(pattern, out var jObject))
|
||||
{
|
||||
return jObject;
|
||||
}
|
||||
@@ -292,6 +293,39 @@ internal static class SwaggerMapper
|
||||
return null;
|
||||
}
|
||||
|
||||
private static bool IsJson(string? value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
value = value!.Trim();
|
||||
|
||||
return (value.StartsWith("{") && value.EndsWith("}")) || (value.StartsWith("[") && value.EndsWith("]"));
|
||||
}
|
||||
|
||||
private static bool TryParseAsJObject(string? strInput, [NotNullWhen(true)] out JObject? value)
|
||||
{
|
||||
value = null;
|
||||
|
||||
if (!IsJson(strInput))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Try to convert this string into a JObject
|
||||
value = JObject.Parse(strInput!);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetContentType(RequestModel request)
|
||||
{
|
||||
var contentType = request.Headers?.FirstOrDefault(h => h.Name == "Content-Type");
|
||||
|
||||
@@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Http;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Stef.Validation;
|
||||
using WireMock.Admin.Mappings;
|
||||
using WireMock.Admin.Requests;
|
||||
using WireMock.Admin.Scenarios;
|
||||
using WireMock.Admin.Settings;
|
||||
using WireMock.Constants;
|
||||
@@ -19,7 +20,6 @@ using WireMock.Owin;
|
||||
using WireMock.RequestBuilders;
|
||||
using WireMock.ResponseProviders;
|
||||
using WireMock.Serialization;
|
||||
using WireMock.Settings;
|
||||
using WireMock.Types;
|
||||
using WireMock.Util;
|
||||
|
||||
@@ -28,50 +28,16 @@ namespace WireMock.Server;
|
||||
public partial class WireMockServer
|
||||
{
|
||||
private const int EnhancedFileSystemWatcherTimeoutMs = 1000;
|
||||
private const string DefaultAdminPathPrefix = "/__admin";
|
||||
private const string QueryParamReloadStaticMappings = "reloadStaticMappings";
|
||||
private static readonly Guid ProxyMappingGuid = new("e59914fd-782e-428e-91c1-4810ffb86567");
|
||||
private static readonly RegexMatcher AdminRequestContentTypeJson = new ContentTypeMatcher(WireMockConstants.ContentTypeJson, true);
|
||||
private EnhancedFileSystemWatcher? _enhancedFileSystemWatcher;
|
||||
private AdminPaths? _adminPaths;
|
||||
|
||||
private sealed class AdminPaths
|
||||
{
|
||||
private readonly string _prefix;
|
||||
private readonly string _prefixEscaped;
|
||||
|
||||
public AdminPaths(WireMockServerSettings settings)
|
||||
{
|
||||
_prefix = settings.AdminPath ?? DefaultAdminPathPrefix;
|
||||
_prefixEscaped = _prefix.Replace("/", "\\/");
|
||||
}
|
||||
|
||||
public string Files => $"{_prefix}/files";
|
||||
public string Health => $"{_prefix}/health";
|
||||
public string Mappings => $"{_prefix}/mappings";
|
||||
public string MappingsCode => $"{_prefix}/mappings/code";
|
||||
public string MappingsWireMockOrg => $"{_prefix}mappings/wiremock.org";
|
||||
public string Requests => $"{_prefix}/requests";
|
||||
public string Settings => $"{_prefix}/settings";
|
||||
public string Scenarios => $"{_prefix}/scenarios";
|
||||
public string OpenApi => $"{_prefix}/openapi";
|
||||
|
||||
public RegexMatcher MappingsGuidPathMatcher => new($"^{_prefixEscaped}\\/mappings\\/([0-9A-Fa-f]{{8}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{12}})$");
|
||||
public RegexMatcher MappingsGuidEnablePathMatcher => new($"^{_prefixEscaped}\\/mappings\\/([0-9A-Fa-f]{{8}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{12}})\\/enable$");
|
||||
public RegexMatcher MappingsGuidDisablePathMatcher => new($"^{_prefixEscaped}\\/mappings\\/([0-9A-Fa-f]{{8}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{12}})\\/disable$");
|
||||
public RegexMatcher MappingsCodeGuidPathMatcher => new($"^{_prefixEscaped}\\/mappings\\/code\\/([0-9A-Fa-f]{{8}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{12}})$");
|
||||
public RegexMatcher RequestsGuidPathMatcher => new($"^{_prefixEscaped}\\/requests\\/([0-9A-Fa-f]{{8}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{12}})$");
|
||||
public RegexMatcher ScenariosNameMatcher => new($"^{_prefixEscaped}\\/scenarios\\/.+$");
|
||||
public RegexMatcher ScenariosNameWithStateMatcher => new($"^{_prefixEscaped}\\/scenarios\\/.+\\/state$");
|
||||
public RegexMatcher ScenariosNameWithResetMatcher => new($"^{_prefixEscaped}\\/scenarios\\/.+\\/reset$");
|
||||
public RegexMatcher FilesFilenamePathMatcher => new($"^{_prefixEscaped}\\/files\\/.+$");
|
||||
public RegexMatcher ProtoDefinitionsIdPathMatcher => new($"^{_prefixEscaped}\\/protodefinitions\\/.+$");
|
||||
}
|
||||
private IAdminPaths? _adminPaths;
|
||||
|
||||
#region InitAdmin
|
||||
private void InitAdmin()
|
||||
{
|
||||
_adminPaths = new AdminPaths(_settings);
|
||||
_adminPaths = _options.AdminPaths;
|
||||
|
||||
// __admin/health
|
||||
Given(Request.Create().WithPath(_adminPaths.Health).UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(HealthGet));
|
||||
@@ -128,6 +94,18 @@ public partial class WireMockServer
|
||||
Given(Request.Create().WithPath(_adminPaths.RequestsGuidPathMatcher).UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(RequestGet));
|
||||
Given(Request.Create().WithPath(_adminPaths.RequestsGuidPathMatcher).UsingDelete()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(RequestDelete));
|
||||
|
||||
// __admin/requests/{guid}/near-misses
|
||||
Given(Request.Create().WithPath(_adminPaths.RequestsGuidNearMissesPathMatcher).UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(RequestNearMissesGet));
|
||||
|
||||
// __admin/requests/count
|
||||
Given(Request.Create().WithPath(_adminPaths.Requests + "/count").UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(RequestsCount));
|
||||
|
||||
// __admin/requests/unmatched
|
||||
Given(Request.Create().WithPath(_adminPaths.Requests + "/unmatched").UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(RequestsUnmatchedGet));
|
||||
|
||||
// __admin/requests/unmatched/near-misses
|
||||
Given(Request.Create().WithPath(_adminPaths.Requests + "/unmatched/near-misses").UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(RequestsUnmatchedNearMissesGet));
|
||||
|
||||
// __admin/requests/find
|
||||
Given(Request.Create().WithPath(_adminPaths.Requests + "/find").UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(RequestsFind));
|
||||
Given(Request.Create().WithPath(_adminPaths.Requests + "/find").UsingGet().WithParam("mappingGuid", new NotNullOrEmptyMatcher())).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(RequestsFindByMappingGuid));
|
||||
@@ -346,7 +324,7 @@ public partial class WireMockServer
|
||||
}
|
||||
|
||||
o.CorsPolicyOptions = corsPolicyOptions;
|
||||
o.ClientCertificateMode = (Microsoft.AspNetCore.Server.Kestrel.Https.ClientCertificateMode) _settings.ClientCertificateMode;
|
||||
o.ClientCertificateMode = (Microsoft.AspNetCore.Server.Kestrel.Https.ClientCertificateMode)_settings.ClientCertificateMode;
|
||||
o.AcceptAnyClientCertificate = _settings.AcceptAnyClientCertificate;
|
||||
});
|
||||
|
||||
@@ -631,7 +609,7 @@ public partial class WireMockServer
|
||||
{
|
||||
if (TryParseGuidFromRequestMessage(requestMessage, out var guid))
|
||||
{
|
||||
var entry = LogEntries.SingleOrDefault(r => r.RequestMessage != null && !r.RequestMessage.Path.StartsWith("/__admin/") && r.Guid == guid);
|
||||
var entry = LogEntries.SingleOrDefault(r => r.RequestMessage != null && !_adminPaths!.Includes(r.RequestMessage.Path) && r.Guid == guid);
|
||||
if (entry is { })
|
||||
{
|
||||
var model = new LogEntryMapper(_options).Map(entry);
|
||||
@@ -660,7 +638,7 @@ public partial class WireMockServer
|
||||
{
|
||||
var logEntryMapper = new LogEntryMapper(_options);
|
||||
var result = LogEntries
|
||||
.Where(r => r.RequestMessage != null && !r.RequestMessage.Path.StartsWith("/__admin/"))
|
||||
.Where(r => r.RequestMessage != null && !_adminPaths!.Includes(r.RequestMessage.Path))
|
||||
.Select(logEntryMapper.Map);
|
||||
|
||||
return ToJson(result);
|
||||
@@ -676,13 +654,29 @@ public partial class WireMockServer
|
||||
|
||||
#region Requests/find
|
||||
private IResponseMessage RequestsFind(HttpContext _, IRequestMessage requestMessage)
|
||||
{
|
||||
var logEntryMapper = new LogEntryMapper(_options);
|
||||
var result = FindLogEntriesByRequestModel(requestMessage)
|
||||
.OrderBy(x => x.Value.AverageTotalScore)
|
||||
.Select(x => x.Key)
|
||||
.Select(logEntryMapper.Map);
|
||||
|
||||
return ToJson(result);
|
||||
}
|
||||
|
||||
private IResponseMessage RequestsCount(HttpContext _, IRequestMessage requestMessage)
|
||||
{
|
||||
return ToJson(new RequestCountModel { Count = FindLogEntriesByRequestModel(requestMessage).Count });
|
||||
}
|
||||
|
||||
private Dictionary<ILogEntry, RequestMatchResult> FindLogEntriesByRequestModel(IRequestMessage requestMessage)
|
||||
{
|
||||
var requestModel = DeserializeObject<RequestModel>(requestMessage);
|
||||
|
||||
var request = (Request)InitRequestBuilder(requestModel);
|
||||
|
||||
var dict = new Dictionary<ILogEntry, RequestMatchResult>();
|
||||
foreach (var logEntry in LogEntries.Where(le => le.RequestMessage != null && !le.RequestMessage.Path.StartsWith("/__admin/")))
|
||||
foreach (var logEntry in LogEntries.Where(le => le.RequestMessage != null && !_adminPaths!.Includes(le.RequestMessage.Path)))
|
||||
{
|
||||
var requestMatchResult = new RequestMatchResult();
|
||||
if (request.GetMatchingScore(logEntry.RequestMessage!, requestMatchResult) > MatchScores.AlmostPerfect)
|
||||
@@ -691,10 +685,7 @@ public partial class WireMockServer
|
||||
}
|
||||
}
|
||||
|
||||
var logEntryMapper = new LogEntryMapper(_options);
|
||||
var result = dict.OrderBy(x => x.Value.AverageTotalScore).Select(x => x.Key).Select(logEntryMapper.Map);
|
||||
|
||||
return ToJson(result);
|
||||
return dict;
|
||||
}
|
||||
|
||||
private IResponseMessage RequestsFindByMappingGuid(HttpContext _, IRequestMessage requestMessage)
|
||||
@@ -704,7 +695,7 @@ public partial class WireMockServer
|
||||
Guid.TryParse(value.ToString(), out var mappingGuid)
|
||||
)
|
||||
{
|
||||
var logEntries = LogEntries.Where(le => le.RequestMessage != null && !le.RequestMessage.Path.StartsWith("/__admin/") && le.MappingGuid == mappingGuid);
|
||||
var logEntries = LogEntries.Where(le => le.RequestMessage != null && !_adminPaths!.Includes(le.RequestMessage.Path) && le.MappingGuid == mappingGuid);
|
||||
var logEntryMapper = new LogEntryMapper(_options);
|
||||
var result = logEntries.Select(logEntryMapper.Map);
|
||||
return ToJson(result);
|
||||
@@ -714,6 +705,65 @@ public partial class WireMockServer
|
||||
}
|
||||
#endregion Requests/find
|
||||
|
||||
#region Requests/unmatched
|
||||
private IResponseMessage RequestsUnmatchedGet(HttpContext _, IRequestMessage requestMessage)
|
||||
{
|
||||
var logEntryMapper = new LogEntryMapper(_options);
|
||||
var result = FindUnmatchedLogEntries().Select(logEntryMapper.Map);
|
||||
|
||||
return ToJson(result);
|
||||
}
|
||||
|
||||
private IResponseMessage RequestsUnmatchedNearMissesGet(HttpContext _, IRequestMessage requestMessage)
|
||||
{
|
||||
if (!TryGetMaxResultsFromQuery(requestMessage, out var maxResults))
|
||||
{
|
||||
return _responseMessageBuilder.Create(HttpStatusCode.BadRequest, "Query parameter 'maxResults' must be a positive integer");
|
||||
}
|
||||
|
||||
var logEntryMapper = new LogEntryMapper(_options);
|
||||
var result = FindNearMissesForUnmatchedRequests(maxResults).Select(logEntryMapper.Map);
|
||||
|
||||
return ToJson(result);
|
||||
}
|
||||
#endregion Requests/unmatched
|
||||
|
||||
#region Requests/{guid}/near-misses
|
||||
private IResponseMessage RequestNearMissesGet(HttpContext _, IRequestMessage requestMessage)
|
||||
{
|
||||
if (!TryGetMaxResultsFromQuery(requestMessage, out var maxResults))
|
||||
{
|
||||
return _responseMessageBuilder.Create(HttpStatusCode.BadRequest, "Query parameter 'maxResults' must be a positive integer");
|
||||
}
|
||||
|
||||
if (TryParseGuidFromSecondToLastSegment(requestMessage, out var guid))
|
||||
{
|
||||
var entry = LogEntries.SingleOrDefault(r => !_adminPaths!.Includes(r.RequestMessage?.Path) && r.Guid == guid);
|
||||
if (entry is not null)
|
||||
{
|
||||
var logEntryMapper = new LogEntryMapper(_options);
|
||||
var result = FindNearMisses(entry, maxResults).Select(logEntryMapper.Map);
|
||||
|
||||
return ToJson(result);
|
||||
}
|
||||
}
|
||||
|
||||
_settings.Logger.Warn("HttpStatusCode set to 404 : Request not found");
|
||||
return _responseMessageBuilder.Create(HttpStatusCode.NotFound, "Request not found");
|
||||
}
|
||||
|
||||
private static bool TryGetMaxResultsFromQuery(IRequestMessage requestMessage, out int maxResults)
|
||||
{
|
||||
maxResults = DefaultMaxNearMisses;
|
||||
if (requestMessage.Query == null || !requestMessage.Query.TryGetValue("maxResults", out var value))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return int.TryParse(value.ToString(), out maxResults) && maxResults > 0;
|
||||
}
|
||||
#endregion Requests/{guid}/near-misses
|
||||
|
||||
#region Scenarios
|
||||
private IResponseMessage ScenariosGet(HttpContext _, IRequestMessage requestMessage)
|
||||
{
|
||||
@@ -883,6 +933,18 @@ public partial class WireMockServer
|
||||
};
|
||||
}
|
||||
|
||||
private T[] DeserializeRequestMessageToArray<T>(IRequestMessage requestMessage)
|
||||
{
|
||||
if (requestMessage.BodyData?.DetectedBodyType == BodyType.Json && requestMessage.BodyData.BodyAsJson != null)
|
||||
{
|
||||
var bodyAsJson = requestMessage.BodyData.BodyAsJson!;
|
||||
|
||||
return _mappingSerializer.DeserializeObjectToArray<T>(bodyAsJson);
|
||||
}
|
||||
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
private static Encoding? ToEncoding(EncodingModel? encodingModel)
|
||||
{
|
||||
return encodingModel != null ? Encoding.GetEncoding(encodingModel.CodePage) : null;
|
||||
@@ -902,28 +964,16 @@ public partial class WireMockServer
|
||||
};
|
||||
}
|
||||
|
||||
private static T[] DeserializeRequestMessageToArray<T>(IRequestMessage requestMessage)
|
||||
{
|
||||
if (requestMessage.BodyData?.DetectedBodyType == BodyType.Json && requestMessage.BodyData.BodyAsJson != null)
|
||||
{
|
||||
var bodyAsJson = requestMessage.BodyData.BodyAsJson!;
|
||||
|
||||
return MappingSerializer.DeserializeObjectToArray<T>(bodyAsJson);
|
||||
}
|
||||
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
private static T DeserializeObject<T>(IRequestMessage requestMessage)
|
||||
private T DeserializeObject<T>(IRequestMessage requestMessage)
|
||||
{
|
||||
switch (requestMessage.BodyData?.DetectedBodyType)
|
||||
{
|
||||
case BodyType.String:
|
||||
case BodyType.FormUrlEncoded:
|
||||
return JsonUtils.DeserializeObject<T>(requestMessage.BodyData.BodyAsString!);
|
||||
case BodyType.String when requestMessage.BodyData?.BodyAsString != null:
|
||||
case BodyType.FormUrlEncoded when requestMessage.BodyData?.BodyAsString != null:
|
||||
return _settings.DefaultJsonSerializer.Deserialize<T>(requestMessage.BodyData.BodyAsString)!;
|
||||
|
||||
case BodyType.Json when requestMessage.BodyData?.BodyAsJson != null:
|
||||
return ((JObject)requestMessage.BodyData.BodyAsJson).ToObject<T>()!;
|
||||
return _settings.DefaultJsonSerializer.ParseJsonToken<T>(requestMessage.BodyData.BodyAsJson)!;
|
||||
|
||||
default:
|
||||
throw new NotSupportedException();
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System.Linq;
|
||||
using Stef.Validation;
|
||||
using WireMock.Admin.Mappings;
|
||||
using WireMock.Matchers;
|
||||
@@ -153,10 +152,11 @@ public partial class WireMockServer
|
||||
}
|
||||
else
|
||||
{
|
||||
var clientIPModel = JsonUtils.ParseJTokenToObject<ClientIPModel>(requestModel.ClientIP);
|
||||
var clientIPModel = _settings.DefaultJsonSerializer.ParseJsonToken<ClientIPModel>(requestModel.ClientIP);
|
||||
if (clientIPModel.Matchers != null)
|
||||
{
|
||||
requestBuilder = requestBuilder.WithPath(clientIPModel.Matchers.Select(_matcherMapper.Map).OfType<IStringMatcher>().ToArray());
|
||||
var matchOperator = StringUtils.ParseMatchOperator(clientIPModel.MatchOperator);
|
||||
requestBuilder = requestBuilder.WithClientIP(matchOperator, clientIPModel.Matchers.Select(_matcherMapper.Map).OfType<IStringMatcher>().ToArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -169,7 +169,7 @@ public partial class WireMockServer
|
||||
}
|
||||
else
|
||||
{
|
||||
var pathModel = JsonUtils.ParseJTokenToObject<PathModel>(requestModel.Path);
|
||||
var pathModel = _settings.DefaultJsonSerializer.ParseJsonToken<PathModel>(requestModel.Path);
|
||||
if (pathModel.Matchers != null)
|
||||
{
|
||||
var matchOperator = StringUtils.ParseMatchOperator(pathModel.MatchOperator);
|
||||
@@ -185,7 +185,7 @@ public partial class WireMockServer
|
||||
}
|
||||
else
|
||||
{
|
||||
var urlModel = JsonUtils.ParseJTokenToObject<UrlModel>(requestModel.Url);
|
||||
var urlModel = _settings.DefaultJsonSerializer.ParseJsonToken<UrlModel>(requestModel.Url);
|
||||
if (urlModel.Matchers != null)
|
||||
{
|
||||
var matchOperator = StringUtils.ParseMatchOperator(urlModel.MatchOperator);
|
||||
@@ -273,7 +273,7 @@ public partial class WireMockServer
|
||||
return requestBuilder;
|
||||
}
|
||||
|
||||
private static IResponseBuilder InitResponseBuilder(ResponseModel responseModel)
|
||||
private IResponseBuilder InitResponseBuilder(ResponseModel responseModel)
|
||||
{
|
||||
var responseBuilder = Response.Create();
|
||||
|
||||
@@ -338,7 +338,7 @@ public partial class WireMockServer
|
||||
}
|
||||
else
|
||||
{
|
||||
var headers = JsonUtils.ParseJTokenToObject<string[]>(entry.Value);
|
||||
var headers = _settings.DefaultJsonSerializer.ParseJsonToken<string[]>(entry.Value);
|
||||
responseBuilder.WithHeader(entry.Key, headers);
|
||||
}
|
||||
}
|
||||
@@ -364,7 +364,7 @@ public partial class WireMockServer
|
||||
}
|
||||
else
|
||||
{
|
||||
var headers = JsonUtils.ParseJTokenToObject<string[]>(entry.Value);
|
||||
var headers = _settings.DefaultJsonSerializer.ParseJsonToken<string[]>(entry.Value);
|
||||
responseBuilder.WithTrailingHeader(entry.Key, headers);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ using Stef.Validation;
|
||||
using WireMock.Logging;
|
||||
using WireMock.Matchers;
|
||||
using WireMock.Matchers.Request;
|
||||
using WireMock.Owin;
|
||||
using WireMock.Services;
|
||||
|
||||
namespace WireMock.Server;
|
||||
|
||||
@@ -57,6 +59,68 @@ public partial class WireMockServer
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[PublicAPI]
|
||||
public IReadOnlyList<ILogEntry> FindUnmatchedLogEntries()
|
||||
{
|
||||
return LogEntries
|
||||
.Where(le => le.RequestMessage != null && !_options.AdminPaths.Includes(le.RequestMessage.Path))
|
||||
.Where(le => le.RequestMatchResult is not { IsPerfectMatch: true })
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[PublicAPI]
|
||||
public IReadOnlyList<NearMiss> FindNearMisses(IRequestMessage requestMessage, int maxResults = DefaultMaxNearMisses)
|
||||
{
|
||||
Guard.NotNull(requestMessage);
|
||||
Guard.Condition(maxResults, m => m > 0);
|
||||
|
||||
return FindNearMisses(requestMessage, null, maxResults);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[PublicAPI]
|
||||
public IReadOnlyList<NearMiss> FindNearMisses(ILogEntry logEntry, int maxResults = DefaultMaxNearMisses)
|
||||
{
|
||||
Guard.NotNull(logEntry);
|
||||
Guard.Condition(maxResults, m => m > 0);
|
||||
|
||||
return logEntry.RequestMessage is null ? [] : FindNearMisses(logEntry.RequestMessage, logEntry.Guid, maxResults);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[PublicAPI]
|
||||
public IReadOnlyList<NearMiss> FindNearMissesForUnmatchedRequests(int maxResultsPerRequest = DefaultMaxNearMisses)
|
||||
{
|
||||
Guard.Condition(maxResultsPerRequest, m => m > 0);
|
||||
|
||||
return FindUnmatchedLogEntries()
|
||||
.SelectMany(le => FindNearMisses(le, maxResultsPerRequest))
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
private const int DefaultMaxNearMisses = 3;
|
||||
|
||||
private IMappingMatcher? _nearMissMappingMatcher;
|
||||
|
||||
private IReadOnlyList<NearMiss> FindNearMisses(IRequestMessage requestMessage, Guid? logEntryGuid, int maxResults)
|
||||
{
|
||||
_nearMissMappingMatcher ??= new MappingMatcher(_options, new RandomizerDoubleBetween0And1());
|
||||
|
||||
return _nearMissMappingMatcher
|
||||
.FindNearMisses(requestMessage, maxResults)
|
||||
.Select(nm => new NearMiss
|
||||
{
|
||||
RequestMessage = requestMessage,
|
||||
LogEntryGuid = logEntryGuid,
|
||||
MappingGuid = nm.Mapping.Guid,
|
||||
MappingTitle = nm.Mapping.Title,
|
||||
RequestMatchResult = nm.RequestMatchResult
|
||||
})
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IWireMockServer.ResetLogEntries" />
|
||||
[PublicAPI]
|
||||
public void ResetLogEntries()
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,40 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using JetBrains.Annotations;
|
||||
using Scriban.Runtime;
|
||||
using Stef.Validation;
|
||||
using WireMock.Handlers;
|
||||
|
||||
namespace WireMock.Settings;
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for <see cref="WireMockServerSettings"/>.
|
||||
/// </summary>
|
||||
public static class WireMockServerSettingsExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds a strongly typed Scriban registration to <see cref="WireMockServerSettings.ScribanRegistrationCallback"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Members added to the <see cref="ScriptObject"/> are available as globals in Scriban and Scriban-DotLiquid templates.
|
||||
/// When a callback is already configured, it is kept and invoked before <paramref name="registrationCallback"/>.
|
||||
/// </remarks>
|
||||
/// <param name="settings">The settings.</param>
|
||||
/// <param name="registrationCallback">The action which registers functions, objects or variables on the <see cref="ScriptObject"/>.</param>
|
||||
/// <returns>The same <see cref="WireMockServerSettings"/> instance.</returns>
|
||||
[PublicAPI]
|
||||
public static WireMockServerSettings WithScribanRegistration(this WireMockServerSettings settings, Action<ScriptObject, IFileSystemHandler> registrationCallback)
|
||||
{
|
||||
Guard.NotNull(settings);
|
||||
Guard.NotNull(registrationCallback);
|
||||
|
||||
var existingCallback = settings.ScribanRegistrationCallback;
|
||||
settings.ScribanRegistrationCallback = (scriptObject, fileSystemHandler) =>
|
||||
{
|
||||
existingCallback?.Invoke(scriptObject, fileSystemHandler);
|
||||
registrationCallback((ScriptObject)scriptObject, fileSystemHandler);
|
||||
};
|
||||
|
||||
return settings;
|
||||
}
|
||||
}
|
||||
Loaded 100 of 238 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user