Compare commits

..
31 Commits
Author SHA1 Message Date
Stef Heyenrath 567f94d70d 2.31.0 2026-11-12 20:58:02 +01:00
Stef HeyenrathandCopilot Autofix powered by AI 22576eb539 Use X509CertificateLoader on .NET 9+ (#1543)
* Use X509CertificateLoader on .NET 9+

Preserve older-target certificate loading and add tests for
supported formats, passwords, and embedded certificates.

* !

* Potential fix for pull request finding 'Missed 'using' opportunity'

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>

* enum

* namespace WireMock.Compatiblity;

---------

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
2026-11-12 20:56:01 +01:00
Stef Heyenrath 384c083a78 Upgrade dependencies 2026-11-12 20:03:03 +01:00
dependabot[bot] 920b5b48f2 Bump Microsoft.AspNetCore.WebUtilities from 2.3.0 to 2.3.14 (#1538)
---
updated-dependencies:
- dependency-name: Microsoft.AspNetCore.WebUtilities
  dependency-version: 2.3.14
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-11-11 08:28:03 +01:00
Stef Heyenrath b7a02e2971 Bump Aspire from 13.6.4 to 13.6.5 2026-11-11 08:21:10 +01:00
Stef Heyenrath dbf90c6888 New SonarCloud project (#1534)
* SonarCloud test

* xxx

* WebSocketIntegrationTests

* move sonar to github

* SonarCloud now runs for every Linux workflow event. The begin step supplies pull-request key/branch/base metadata for PRs and branch metadata for pushes; the end step is no longer gated. Workflow YAML parsing and the solution build both pass.

* SHA
2026-11-11 07:57:54 +01:00
Stef Heyenrath 6ae65ba73e 2.30.0 2026-11-07 15:44:44 +01:00
Stef HeyenrathandCopilot Autofix powered by AI 2760ecbe00 Switch to IHost for .NET 10 + update dotnet-WireMock (#1532)
* Switch to IHost for .NET 10

* 'dotnet-WireMock stops on CTRL-C'

* Potential fix for pull request finding 'Missing Dispose call on local IDisposable'

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>

* .

---------

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
2026-11-07 13:10:21 +01:00
CopilotandStefH f594a7cb19 Use version-aligned DCP for Aspire integration tests (#1531)
* Initial plan

* Fix Aspire integration test DCP version mismatch

Co-authored-by: StefH <249938+StefH@users.noreply.github.com>

* Enable Aspire Docker integration tests

Co-authored-by: StefH <249938+StefH@users.noreply.github.com>

* Use Aspire RID fallback with .NET 10 SDK

Co-authored-by: StefH <249938+StefH@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: StefH <249938+StefH@users.noreply.github.com>
2026-11-07 09:59:48 +01:00
Stef Heyenrath 30cf82fd8d Use 10.x.x NuGets for .NET 10 (#1530)
* Use 10.x.x NuGets for .NET 10

* await Task.Delay(500, _ct);
2026-11-01 13:25:40 +01:00
Stef HeyenrathandCopilot App 3630093d49 Add .NET 10 target framework (#1528)
* Add .NET 10 target framework

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* ...

* add delay

* ok

* TUnit - delay

* 10

* ado

* ado - no nuget tests

* .

* no collect

* await Task.Delay(500, _ct);

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-10-31 18:38:25 +01:00
Tomás Pinto f0965e5650 Create Release Artifacts (#1527) 2026-10-30 18:12:26 +01:00
Stef Heyenrath 69ea61e42c 2.21.0 2026-10-28 17:54:52 +01:00
Stef Heyenrath bc65bccce5 Update Scriban to 7.5.3 (#1526) 2026-10-28 17:52:09 +01:00
Stef Heyenrath f6dc299ecb 2.20.0 2026-10-23 17:54:30 +02:00
Mattias SundströmandMattias dc14136492 Add near-miss diagnostics and request count/unmatched endpoints to the admin API (#1521)
* Add near-miss diagnostics and request count/unmatched admin endpoints (#171)

Add IWireMockServer.FindUnmatchedLogEntries, FindNearMisses and
FindNearMissesForUnmatchedRequests, which rank the mappings that
partially match a request by score.

Expose them through the admin API (POST requests/count,
GET requests/unmatched, GET requests/unmatched/near-misses and
GET requests/{guid}/near-misses), the RestClient and the OpenAPI document.

* Make scenario delay tests independent of server warm-up time

The two *_StateTransitions_BeforeDelayCompletes tests waited a fixed
100 ms for the scenario state to change, which fails on a cold server.
Poll server.Scenarios until the expected state is reached instead.

---------

Co-authored-by: Mattias <13809209+Quattordici@users.noreply.github.com>
2026-10-23 17:48:54 +02:00
Stef Heyenrath c94824bd64 Update Generate-ReleaseNotes.cmd 2026-10-19 19:30:12 +02:00
Stef HeyenrathandCopilot Autofix powered by AI 54e16fc045 Potential fix for code scanning alert no. 8: Workflow does not contain permissions (#1523)
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2026-10-19 19:25:01 +02:00
Stef Heyenrath 7d0de75ab1 Downgrade JsonPath.Net from 3.0.2 to 2.2.0 to avoid Open Source Maintenance Fee issues (#1522) 2026-10-19 18:42:43 +02:00
Stef Heyenrath afb01a0fa6 Bump MessagePack from 2.5.302 to 2.5.303 (#1519) 2026-10-18 10:51:38 +02:00
Mattias SundströmandMattias 0132ec873a Add ScribanRegistrationCallback to register custom Scriban functions (#1515) (#1516)
- Add WireMockServerSettings.ScribanRegistrationCallback
  (Action<object, IFileSystemHandler>?) so code-configured servers can add
  functions, objects or variables to Scriban and Scriban-DotLiquid templates.
- Pass the settings to ScribanContextFactory, which invokes the callback with
  a new ScriptObject per transformer context.
- Push the custom globals beneath the template model in ScribanContext so
  request, response, mapping and data take precedence; keep the original
  Template.Render call when no callback is configured.
- Add the strongly typed WithScribanRegistration extension method.
- Add a Scriban example to the .NET 8 console app.
- Make the proxy tests use a local target server instead of www.google.com,
  and skip the IPv6 loopback test when the OS has no IPv6 support.

Co-authored-by: Mattias <13809209+Quattordici@users.noreply.github.com>
2026-10-18 09:29:11 +02:00
Stef Heyenrath 64734c4f92 <VersionPrefix>2.19.0</VersionPrefix> 2026-10-07 18:25:30 +02:00
Stef Heyenrath a5589e1e2f 2.19.0 2026-10-07 18:22:51 +02:00
Stef HeyenrathandStef Heyenrath cb4503b1a6 Fix DateTime in Response (#1514)
* Fix DateTime in Response

* // Set the DateTime from the Response to the current DateTime

---------

Co-authored-by: Stef Heyenrath <stef.heyenrath@apg.nl>
2026-10-07 17:35:58 +02:00
Stef Heyenrath dd762f218b Use IDateTimeUtils in WireMockWebSocketContext (#1512) 2026-10-06 22:58:33 +02:00
Stef Heyenrath b0f9924933 Add example /test (#1513) 2026-10-06 22:51:17 +02:00
Stef Heyenrath dc4cdfae50 2.18.0 2026-09-22 22:21:50 +02:00
Stef Heyenrath f19d94acae 2.18.0 2026-09-22 22:18:47 +02:00
Stef Heyenrath 90819f031e WireMock.Net.OpenApiParser: fix depencency on RamlToOpenApiConverter.SourceOnly (#1510) 2026-09-22 22:16:13 +02:00
Stef Heyenrath 471745f704 2.17.0 2026-09-21 20:53:22 +02:00
Stef Heyenrath 7db5b562d8 Revert to RamlToOpenApiConverter.SourceOnly (#1509) 2026-09-21 20:50:49 +02:00
153 changed files with 2805 additions and 51843 deletions

No files matched your search

+1 -1
View File
@@ -6,7 +6,7 @@ applyTo: '**/*.csproj, **/*.cs'
# Multi .NET Framework Targeting
## Instructions
- The main project "WireMock.Net.Minimal" targets `netstandard2.0` and `net8.0`. Ensure that any new code or dependencies are compatible with these frameworks.
- The main project "WireMock.Net.Minimal" targets `netstandard2.0`, `net8.0` and `net10.0`. Ensure that any new code or dependencies are compatible with these frameworks.
# C# Guidelines
+44 -2
View File
@@ -4,6 +4,7 @@ permissions:
contents: write
on:
workflow_dispatch:
push:
tags:
- "*.*.*"
@@ -13,9 +14,50 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
- name: Setup .NET 8 and 10
uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5
with:
dotnet-version: |
8.0.x
10.0.x
- name: Publish Standalone Binaries
shell: bash
run: |
mkdir -p ./artifacts
RIDS=("win-x64" "win-arm64" "linux-x64" "linux-arm64" "osx-x64" "osx-arm64")
for RID in "${RIDS[@]}"; do
echo "Publishing for $RID..."
dotnet publish ./src/dotnet-WireMock.Net/dotnet-WireMock.csproj \
-c Release \
-r "$RID" \
--self-contained true \
-p:PublishSingleFile=true \
-p:IncludeNativeLibrariesForSelfExtract=true \
-p:PublishDocumentationFile=false \
-p:DebugType=None \
-p:DebugSymbols=false \
-o "./publish/$RID"
if [[ "$RID" == win-* ]]; then
(cd "./publish/$RID" && zip "../../artifacts/wiremock-net-$RID.zip" dotnet-WireMock.exe)
else
tar -czvf "./artifacts/wiremock-net-$RID.tar.gz" -C "./publish/$RID" dotnet-WireMock
fi
done
- name: Upload Standalone Artifacts
uses: actions/upload-artifact@v4
with:
name: wiremock-standalone
path: ./artifacts/*
- name: Release
if: startsWith(github.ref, 'refs/tags/')
uses: softprops/action-gh-release@v2
with:
generate_release_notes: true
generate_release_notes: true
files: ./artifacts/*
+1 -1
View File
@@ -12,7 +12,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
- name: Build projects
shell: pwsh
+139 -26
View File
@@ -8,6 +8,9 @@ on:
branches:
- 'main'
permissions:
contents: read
jobs:
windows-build-and-run:
name: Run Tests on Windows
@@ -17,24 +20,35 @@ jobs:
IsRunningOnGitHubActions: 'true'
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
- name: Setup .NET 8
uses: actions/setup-dotnet@v4
- name: Setup .NET 8 and 10
uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5
with:
dotnet-version: '8.0.x'
dotnet-version: |
8.0.x
10.0.x
- name: 'WireMock.Net.TUnitTests'
run: dotnet test --project './test/WireMock.Net.TUnitTests/WireMock.Net.TUnitTests.csproj' -c Release
# - name: 'WireMock.Net.TUnitTests (net8.0)'
# run: dotnet test --project './test/WireMock.Net.TUnitTests/WireMock.Net.TUnitTests.csproj' -c Release --framework net8.0
- name: 'WireMock.Net.Tests'
run: dotnet test --project './test/WireMock.Net.Tests/WireMock.Net.Tests.csproj' -c Release --framework net8.0
- name: 'WireMock.Net.TUnitTests (net10.0)'
run: dotnet test --project './test/WireMock.Net.TUnitTests/WireMock.Net.TUnitTests.csproj' -c Release --framework net10.0
- name: 'WireMock.Net.Tests.UsingNuGet'
run: dotnet test --project './test/WireMock.Net.Tests.UsingNuGet/WireMock.Net.Tests.UsingNuGet.csproj' -c Release
# - name: 'WireMock.Net.Tests (net8.0)'
# run: dotnet test --project './test/WireMock.Net.Tests/WireMock.Net.Tests.csproj' -c Release --framework net8.0
- name: 'WireMock.Net.Middleware.Tests'
run: dotnet test --project './test/WireMock.Net.Middleware.Tests/WireMock.Net.Middleware.Tests.csproj' -c Release
- name: 'WireMock.Net.Tests (net10.0)'
run: dotnet test --project './test/WireMock.Net.Tests/WireMock.Net.Tests.csproj' -c Release --framework net10.0
# - name: 'WireMock.Net.Tests.UsingNuGet'
# run: dotnet test --project './test/WireMock.Net.Tests.UsingNuGet/WireMock.Net.Tests.UsingNuGet.csproj' -c Release
# - name: 'WireMock.Net.Middleware.Tests (net8.0)'
# run: dotnet test --project './test/WireMock.Net.Middleware.Tests/WireMock.Net.Middleware.Tests.csproj' -c Release --framework net8.0
- name: 'WireMock.Net.Middleware.Tests (net10.0)'
run: dotnet test --project './test/WireMock.Net.Middleware.Tests/WireMock.Net.Middleware.Tests.csproj' -c Release --framework net10.0
linux-build-and-run:
name: Run Tests on Linux
@@ -44,24 +58,123 @@ jobs:
IsRunningOnGitHubActions: 'true'
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
- name: Setup .NET 8
uses: actions/setup-dotnet@v4
- name: Setup .NET 8 and 10
uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5
with:
dotnet-version: '8.0.x'
dotnet-version: |
8.0.x
10.0.x
- name: 'WireMock.Net.TUnitTests'
run: dotnet test --project './test/WireMock.Net.TUnitTests/WireMock.Net.TUnitTests.csproj' -c Release
- name: Setup Java 17
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5
with:
distribution: temurin
java-version: '17'
- name: 'WireMock.Net.Tests'
run: dotnet test --project './test/WireMock.Net.Tests/WireMock.Net.Tests.csproj' -c Release --framework net8.0
- name: Install .NET coverage and SonarScanner tools
run: |
dotnet tool install --global dotnet-sonarscanner
dotnet tool install --global dotnet-coverage
echo "$HOME/.dotnet/tools" >> "$GITHUB_PATH"
- name: 'WireMock.Net.Tests.UsingNuGet'
run: dotnet test --project './test/WireMock.Net.Tests.UsingNuGet/WireMock.Net.Tests.UsingNuGet.csproj' -c Release
- name: Begin analysis on SonarCloud
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number }}
PR_BRANCH: ${{ github.head_ref }}
PR_BASE: ${{ github.base_ref }}
run: |
sonar_args=(
/k:"wiremock_WireMock.Net"
/o:"wiremock"
/d:sonar.host.url="https://sonarcloud.io"
/d:sonar.token="$SONAR_TOKEN"
/d:sonar.cs.vscoveragexml.reportsPaths="**/wiremock-coverage-*.xml"
/d:sonar.verbose=true
)
if [ "$GITHUB_EVENT_NAME" = "pull_request" ]; then
sonar_args+=(
/d:sonar.pullrequest.provider=github
/d:sonar.pullrequest.key="$PR_NUMBER"
/d:sonar.pullrequest.branch="$PR_BRANCH"
/d:sonar.pullrequest.base="$PR_BASE"
)
else
sonar_args+=(/d:sonar.branch.name="$GITHUB_REF_NAME")
fi
dotnet sonarscanner begin "${sonar_args[@]}"
- name: Build test projects for coverage
run: |
dotnet build ./test/WireMock.Net.Tests/WireMock.Net.Tests.csproj --configuration Release --framework net10.0
dotnet build ./test/WireMock.Net.TUnitTests/WireMock.Net.TUnitTests.csproj --configuration Release --framework net10.0
dotnet build ./test/WireMock.Net.Middleware.Tests/WireMock.Net.Middleware.Tests.csproj --configuration Release --framework net10.0
dotnet build ./test/WireMock.Net.Aspire.Tests/WireMock.Net.Aspire.Tests.csproj --configuration Release --framework net10.0
# - name: 'WireMock.Net.TUnitTests (net8.0)'
# run: dotnet test --project './test/WireMock.Net.TUnitTests/WireMock.Net.TUnitTests.csproj' -c Release --framework net8.0
- name: 'WireMock.Net.TUnitTests (net10.0) with coverage'
run: dotnet-coverage collect "dotnet test --project ./test/WireMock.Net.TUnitTests/WireMock.Net.TUnitTests.csproj --configuration Release --no-build --framework net10.0" -f xml -o "wiremock-coverage-tunit.xml"
# - name: 'WireMock.Net.Tests (net8.0)'
# run: dotnet test --project './test/WireMock.Net.Tests/WireMock.Net.Tests.csproj' -c Release --framework net8.0
- name: 'WireMock.Net.Tests (net10.0) with coverage'
run: dotnet-coverage collect "dotnet test --project ./test/WireMock.Net.Tests/WireMock.Net.Tests.csproj --configuration Release --no-build --framework net10.0" -f xml -o "wiremock-coverage-xunit.xml"
- name: 'WireMock.Net.Tests.UsingNuGet (net8.0)'
run: dotnet test --project './test/WireMock.Net.Tests.UsingNuGet/WireMock.Net.Tests.UsingNuGet.csproj' -c Release --framework net8.0
# - name: 'WireMock.Net.Tests.UsingNuGet (net10.0)'
# run: dotnet test --project './test/WireMock.Net.Tests.UsingNuGet/WireMock.Net.Tests.UsingNuGet.csproj' -c Release --framework net10.0
- name: 'WireMock.Net.Middleware.Tests'
run: dotnet test --project './test/WireMock.Net.Middleware.Tests/WireMock.Net.Middleware.Tests.csproj' -c Release
# - name: 'WireMock.Net.Middleware.Tests (net8.0)'
# run: dotnet test --project './test/WireMock.Net.Middleware.Tests/WireMock.Net.Middleware.Tests.csproj' -c Release --framework net8.0
- name: 'WireMock.Net.Aspire.Tests'
run: dotnet test --project './test/WireMock.Net.Aspire.Tests/WireMock.Net.Aspire.Tests.csproj' -c Release
- name: 'WireMock.Net.Middleware.Tests (net10.0) with coverage'
run: dotnet-coverage collect "dotnet test --project ./test/WireMock.Net.Middleware.Tests/WireMock.Net.Middleware.Tests.csproj --configuration Release --no-build --framework net10.0" -f xml -o "wiremock-coverage-middleware.xml"
# - name: 'WireMock.Net.Aspire.Tests (net8.0)'
# run: dotnet test --project './test/WireMock.Net.Aspire.Tests/WireMock.Net.Aspire.Tests.csproj' -c Release --framework net8.0
- name: 'WireMock.Net.Aspire.Tests (net10.0) with coverage'
run: dotnet-coverage collect "dotnet test --project ./test/WireMock.Net.Aspire.Tests/WireMock.Net.Aspire.Tests.csproj --configuration Release --no-build --framework net10.0" -f xml -o "wiremock-coverage-aspire.xml"
- name: Merge coverage files
run: dotnet coverage merge **/wiremock-coverage-*.xml --output ./test/wiremock-coverage.xml --output-format xml
- name: End analysis on SonarCloud
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: dotnet sonarscanner end /d:sonar.token="$SONAR_TOKEN"
- name: Upload coverage results to Codecov
uses: codecov/codecov-action@0fb7174895f61a3b6b78fc075e0cd60383518dac # v5.5.5
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./test/wiremock-coverage.xml
- name: 'dotnet-WireMock stops on CTRL-C'
run: |
set -m # job control, so the background process does not ignore SIGINT
dotnet build ./src/dotnet-WireMock.Net/dotnet-WireMock.csproj -c Release
dotnet ./src/dotnet-WireMock.Net/bin/Release/net10.0/dotnet-WireMock.dll --Urls http://localhost:0 > wiremock.log 2>&1 & # port 0 = OS assigns a free port
PID=$!
sleep 3
grep -q 'Server listening at' wiremock.log || { cat wiremock.log; kill -9 $PID; exit 1; }
kill -INT $PID
for i in $(seq 1 10); do
if ! kill -0 $PID 2>/dev/null; then
EXIT_CODE=0
wait $PID || EXIT_CODE=$?
echo "dotnet-WireMock stopped (exit code $EXIT_CODE)"
exit $EXIT_CODE
fi
sleep 1
done
echo "dotnet-WireMock did not stop within 10 seconds after CTRL-C"
kill -9 $PID
exit 1
+2 -2
View File
@@ -253,7 +253,7 @@ paket-files/
./report/coverlet/
/test/WireMock.Net.Tests/coverage.opencover.xml
/test/WireMock.Net.Tests/coverage.netcoreapp3.1.opencover.xml
/test/WireMock.Net.Tests/coverage.net8.0.opencover.xml
/test/WireMock.Net.Tests/coverage.*.opencover.xml
/test/WireMock.Net.Middleware.Tests/coverage.opencover.xml
*.received.*
+38
View File
@@ -1,3 +1,41 @@
# 2.31.0 (12 November 2026)
- [#1538](https://github.com/wiremock/WireMock.Net/pull/1538) - Bump Microsoft.AspNetCore.WebUtilities from 2.3.0 to 2.3.14 [dependencies] contributed by [dependabot[bot]](https://github.com/apps/dependabot)
- [#1540](https://github.com/wiremock/WireMock.Net/pull/1540) - Bump Aspire from 13.6.4 to 13.6.5 [dependencies] contributed by [StefH](https://github.com/StefH)
- [#1542](https://github.com/wiremock/WireMock.Net/pull/1542) - Upgrade dependencies [dependencies] contributed by [StefH](https://github.com/StefH)
- [#1543](https://github.com/wiremock/WireMock.Net/pull/1543) - Use X509CertificateLoader on .NET 9+ [bug] contributed by [StefH](https://github.com/StefH)
- [#1365](https://github.com/wiremock/WireMock.Net/issues/1365) - Client certificate authentication works with .NET 8 and fails with .NET 9 [bug]
- [#1541](https://github.com/wiremock/WireMock.Net/issues/1541) - WireMock.Shared depends on Microsoft.AspNetCore.Http 2.3.9 which has a high-severity vulnerability [dependencies]
# 2.30.0 (07 November 2026)
- [#1527](https://github.com/wiremock/WireMock.Net/pull/1527) - Publish standalone single-file binaries in GitHub Releases [feature] contributed by [tomas-pinto](https://github.com/tomas-pinto)
- [#1528](https://github.com/wiremock/WireMock.Net/pull/1528) - Add .NET 10 target framework [feature] contributed by [StefH](https://github.com/StefH)
- [#1530](https://github.com/wiremock/WireMock.Net/pull/1530) - Use 10.x.x NuGets for .NET 10 [dependencies] contributed by [StefH](https://github.com/StefH)
- [#1532](https://github.com/wiremock/WireMock.Net/pull/1532) - Switch to IHost for .NET 10 + update dotnet-WireMock [feature] contributed by [StefH](https://github.com/StefH)
# 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)
+3 -3
View File
@@ -4,7 +4,7 @@
</PropertyGroup>
<PropertyGroup>
<VersionPrefix>2.16.0</VersionPrefix>
<VersionPrefix>2.31.0</VersionPrefix>
<PackageIcon>WireMock.Net-Logo.png</PackageIcon>
<PackageProjectUrl>https://github.com/wiremock/WireMock.Net</PackageProjectUrl>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
@@ -52,10 +52,10 @@
<ItemGroup>
<PackageReference Include="JetBrains.Annotations" Version="2026.2.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="10.0.401" PrivateAssets="All" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="10.0.403" PrivateAssets="All" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net8.0' ">
<ItemGroup Condition="('$(TargetFramework)' == 'net8.0' Or '$(TargetFramework)' == 'net10.0')">
<!--<PackageReference Include="SonarAnalyzer.CSharp" Version="10.15.0.120848">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+12 -2
View File
@@ -1,7 +1,17 @@
rem https://github.com/StefH/GitHubReleaseNotes
SET version=2.16.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%
+7 -7
View File
@@ -1,9 +1,9 @@
# 2.16.0 (19 September 2026)
- #1502 Fix #1501: Generate WhenStateIs/WillSetStateTo correctly in ToCSharpCode
- #1503 Fix FormUrlEncodedMatcher (+ refactor values / matchers logic)
- #1504 Update SharpYaml package to version 2.5.1 [dependencies]
- #1505 Upgrade dependency RamlToOpenApiConverter to version 0.40.0 [dependencies]
- #1500 Sending a form-urlencoded body with a repeated field name returns HTTP 500 [bug]
- #1501 Generated C# code for stateful (Scenario) mappings does not compile and drops the WhenStateIs condition [bug]
# 2.31.0 (12 November 2026)
- #1538 Bump Microsoft.AspNetCore.WebUtilities from 2.3.0 to 2.3.14 [dependencies]
- #1540 Bump Aspire from 13.6.4 to 13.6.5 [dependencies]
- #1542 Upgrade dependencies [dependencies]
- #1543 Use X509CertificateLoader on .NET 9+ [bug]
- #1365 Client certificate authentication works with .NET 8 and fails with .NET 9 [bug]
- #1541 WireMock.Shared depends on Microsoft.AspNetCore.Http 2.3.9 which has a high-severity vulnerability [dependencies]
The full release notes can be found here: https://github.com/wiremock/WireMock.Net/blob/master/CHANGELOG.md
+3 -3
View File
@@ -40,9 +40,9 @@ A C# .NET version based on [mock4net](https://github.com/alexvictoor/mock4net) w
| ***Quality*** | &nbsp; |
| &nbsp;&nbsp;**Build Azure** | [![Build Status Azure](https://stef.visualstudio.com/WireMock.Net/_apis/build/status/WireMock.Net)](https://stef.visualstudio.com/WireMock.Net/_build/latest?definitionId=61) |
| &nbsp;&nbsp;**Build GitHub** | [![Run Tests](https://github.com/wiremock/WireMock.Net/actions/workflows/ci.yml/badge.svg)](https://github.com/wiremock/WireMock.Net/actions/workflows/ci.yml)
| &nbsp;&nbsp;**Quality** | [![Sonar Quality Gate](https://sonarcloud.io/api/project_badges/measure?project=WireMock-Net_WireMock.Net&metric=alert_status)](https://sonarcloud.io/project/issues?id=WireMock-Net_WireMock.Net) [![CodeFactor](https://www.codefactor.io/repository/github/wiremock/wiremock.net/badge)](https://www.codefactor.io/repository/github/wiremock/wiremock.net) |
| &nbsp;&nbsp;**SonarQube** | [![Sonar Bugs](https://sonarcloud.io/api/project_badges/measure?project=WireMock-Net_WireMock.Net&metric=bugs)](https://sonarcloud.io/project/issues?id=WireMock-Net_WireMock.Net&resolved=false&types=BUG) [![Sonar Code Smells](https://sonarcloud.io/api/project_badges/measure?project=WireMock-Net_WireMock.Net&metric=code_smells)](https://sonarcloud.io/project/issues?id=WireMock-Net_WireMock.Net&resolved=false&types=CODE_SMELL) |
| &nbsp;&nbsp;**Coverage** | [![Sonar Coverage](https://sonarcloud.io/api/project_badges/measure?project=WireMock-Net_WireMock.Net&metric=coverage)](https://sonarcloud.io/component_measures?id=WireMock-Net_WireMock.Net&metric=coverage) [![codecov](https://codecov.io/gh/wiremock/WireMock.Net/branch/master/graph/badge.svg)](https://codecov.io/gh/wiremock/WireMock.Net)|
| &nbsp;&nbsp;**Quality** | [![Sonar Quality Gate](https://sonarcloud.io/api/project_badges/measure?project=wiremock_WireMock.Net&metric=alert_status)](https://sonarcloud.io/project/issues?id=wiremock_WireMock.Net) [![CodeFactor](https://www.codefactor.io/repository/github/wiremock/wiremock.net/badge)](https://www.codefactor.io/repository/github/wiremock/wiremock.net) |
| &nbsp;&nbsp;**SonarQube** | [![Sonar Bugs](https://sonarcloud.io/api/project_badges/measure?project=wiremock_WireMock.Net&metric=bugs)](https://sonarcloud.io/project/issues?id=wiremock_WireMock.Net&resolved=false&types=BUG) [![Sonar Code Smells](https://sonarcloud.io/api/project_badges/measure?project=wiremock_WireMock.Net&metric=code_smells)](https://sonarcloud.io/project/issues?id=wiremock_WireMock.Net&resolved=false&types=CODE_SMELL) |
| &nbsp;&nbsp;**Coverage** | [![Sonar Coverage](https://sonarcloud.io/api/project_badges/measure?project=wiremock_WireMock.Net&metric=coverage)](https://sonarcloud.io/component_measures?id=wiremock_WireMock.Net&metric=coverage) [![codecov](https://codecov.io/gh/wiremock/WireMock.Net/branch/master/graph/badge.svg)](https://codecov.io/gh/wiremock/WireMock.Net)|
| &nbsp;&nbsp;**TIOBE** | [TIOBE Quality Indicator](https://ticsdemo.tiobe.com/tiobeweb/DEMO/TqiDashboard.html#axes=Project(WireMock.Net),Sub()&metric=tqi)
### :package: NuGet packages
+16 -15
View File
@@ -1,3 +1,4 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 18
VisualStudioVersion = 18.0.11205.157
@@ -73,8 +74,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("{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}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.Console.GrpcClient", "examples\WireMock.Net.Console.GrpcClient\WireMock.Net.Console.GrpcClient.csproj", "{B1580A38-84E7-44BE-8FE7-3EE5031D74A1}"
@@ -153,6 +152,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.Matchers.Syste
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("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.Console", "examples\WireMock.Net.Console\WireMock.Net.Console.csproj", "{1B2D0EF9-A494-400C-91CF-651249B2BA0B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -379,18 +380,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
{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
{1EA72C0F-92E9-486B-8FFE-53F992BFC4AA}.Debug|x64.Build.0 = Debug|Any CPU
{1EA72C0F-92E9-486B-8FFE-53F992BFC4AA}.Debug|x86.ActiveCfg = Debug|Any CPU
{1EA72C0F-92E9-486B-8FFE-53F992BFC4AA}.Debug|x86.Build.0 = Debug|Any CPU
{1EA72C0F-92E9-486B-8FFE-53F992BFC4AA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1EA72C0F-92E9-486B-8FFE-53F992BFC4AA}.Release|Any CPU.Build.0 = Release|Any CPU
{1EA72C0F-92E9-486B-8FFE-53F992BFC4AA}.Release|x64.ActiveCfg = Release|Any CPU
{1EA72C0F-92E9-486B-8FFE-53F992BFC4AA}.Release|x64.Build.0 = Release|Any CPU
{1EA72C0F-92E9-486B-8FFE-53F992BFC4AA}.Release|x86.ActiveCfg = Release|Any CPU
{1EA72C0F-92E9-486B-8FFE-53F992BFC4AA}.Release|x86.Build.0 = Release|Any CPU
{7FC0B409-2682-40EE-B3B9-3930D6769D01}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7FC0B409-2682-40EE-B3B9-3930D6769D01}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7FC0B409-2682-40EE-B3B9-3930D6769D01}.Debug|x64.ActiveCfg = Debug|Any CPU
@@ -847,6 +836,18 @@ Global
{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
{1B2D0EF9-A494-400C-91CF-651249B2BA0B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1B2D0EF9-A494-400C-91CF-651249B2BA0B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1B2D0EF9-A494-400C-91CF-651249B2BA0B}.Debug|x64.ActiveCfg = Debug|Any CPU
{1B2D0EF9-A494-400C-91CF-651249B2BA0B}.Debug|x64.Build.0 = Debug|Any CPU
{1B2D0EF9-A494-400C-91CF-651249B2BA0B}.Debug|x86.ActiveCfg = Debug|Any CPU
{1B2D0EF9-A494-400C-91CF-651249B2BA0B}.Debug|x86.Build.0 = Debug|Any CPU
{1B2D0EF9-A494-400C-91CF-651249B2BA0B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1B2D0EF9-A494-400C-91CF-651249B2BA0B}.Release|Any CPU.Build.0 = Release|Any CPU
{1B2D0EF9-A494-400C-91CF-651249B2BA0B}.Release|x64.ActiveCfg = Release|Any CPU
{1B2D0EF9-A494-400C-91CF-651249B2BA0B}.Release|x64.Build.0 = Release|Any CPU
{1B2D0EF9-A494-400C-91CF-651249B2BA0B}.Release|x86.ActiveCfg = Release|Any CPU
{1B2D0EF9-A494-400C-91CF-651249B2BA0B}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -872,7 +873,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}
{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}
{CAB42D88-B4E4-4887-B684-9F3E09D085A1} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2}
@@ -911,6 +911,7 @@ Global
{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}
{1B2D0EF9-A494-400C-91CF-651249B2BA0B} = {985E0ADB-D4B4-473A-AA40-567E279B7946}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {DC539027-9852-430C-B19F-FD035D018458}
+79 -65
View File
@@ -1,3 +1,6 @@
trigger: none
pr: none
parameters:
- name: runWindowsReleaseToMyGet
displayName: Run Windows Release to MyGet
@@ -20,6 +23,12 @@ jobs:
echo "BuildId = $(buildId)"
displayName: 'Print buildId'
- task: UseDotNet@2
displayName: Use .NET 10.0
inputs:
packageType: 'sdk'
version: '10.0.x'
- script: |
dotnet tool install --global dotnet-sonarscanner
dotnet tool install --global dotnet-coverage
@@ -39,7 +48,7 @@ jobs:
# See: https://docs.sonarsource.com/sonarcloud/enriching/test-coverage/dotnet-test-coverage
- script: |
dotnet sonarscanner begin /k:"WireMock-Net_WireMock.Net" /o:"wiremock-net" /d:sonar.branch.name=$(Build.SourceBranchName) /d:sonar.host.url="https://sonarcloud.io" /d:sonar.token="$(SONAR_TOKEN)" /d:sonar.pullrequest.provider=github /d:sonar.cs.vscoveragexml.reportsPaths=**/wiremock-coverage-*.xml /d:sonar.verbose=true
dotnet sonarscanner begin /k:"wiremock_WireMock.Net" /o:"wiremock" /d:sonar.branch.name=$(Build.SourceBranchName) /d:sonar.host.url="https://sonarcloud.io" /d:sonar.token="$(SONAR_TOKEN)" /d:sonar.pullrequest.provider=github /d:sonar.cs.vscoveragexml.reportsPaths=**/wiremock-coverage-*.xml /d:sonar.verbose=true
displayName: 'Begin analysis on SonarCloud'
condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest')) # Do not run for PullRequests
@@ -48,30 +57,30 @@ jobs:
inputs:
command: 'build'
projects: '**/test/**/*.csproj'
arguments: '--configuration Debug --framework net8.0'
arguments: '--configuration Debug --framework net10.0'
- task: CmdLine@2
inputs:
script: |
dotnet-coverage collect "dotnet test --project ./test/WireMock.Net.Tests/WireMock.Net.Tests.csproj --configuration Debug --no-build --framework net8.0" -f xml -o "wiremock-coverage-xunit.xml"
dotnet-coverage collect "dotnet test --project ./test/WireMock.Net.Tests/WireMock.Net.Tests.csproj --configuration Debug --no-build --framework net10.0" -f xml -o "wiremock-coverage-xunit.xml"
displayName: 'WireMock.Net.Tests with Coverage'
- task: CmdLine@2
inputs:
script: |
dotnet-coverage collect "dotnet test --project ./test/WireMock.Net.TUnitTests/WireMock.Net.TUnitTests.csproj --configuration Debug --no-build" -f xml -o "wiremock-coverage-tunit.xml"
dotnet-coverage collect "dotnet test --project ./test/WireMock.Net.TUnitTests/WireMock.Net.TUnitTests.csproj --configuration Debug --no-build --framework net10.0" -f xml -o "wiremock-coverage-tunit.xml"
displayName: 'WireMock.Net.TUnitTests with Coverage'
- task: CmdLine@2
inputs:
script: |
dotnet-coverage collect "dotnet test --project ./test/WireMock.Net.Middleware.Tests/WireMock.Net.Middleware.Tests.csproj --configuration Debug --no-build" -f xml -o "wiremock-coverage-middleware.xml"
dotnet-coverage collect "dotnet test --project ./test/WireMock.Net.Middleware.Tests/WireMock.Net.Middleware.Tests.csproj --configuration Debug --no-build --framework net10.0" -f xml -o "wiremock-coverage-middleware.xml"
displayName: 'WireMock.Net.Middleware.Tests with Coverage'
- task: CmdLine@2
inputs:
script: |
dotnet-coverage collect "dotnet test --project ./test/WireMock.Net.Aspire.Tests/WireMock.Net.Aspire.Tests.csproj --configuration Debug --no-build" -f xml -o "wiremock-coverage-aspire.xml"
dotnet-coverage collect "dotnet test --project ./test/WireMock.Net.Aspire.Tests/WireMock.Net.Aspire.Tests.csproj --configuration Debug --no-build --framework net10.0" -f xml -o "wiremock-coverage-aspire.xml"
displayName: 'WireMock.Net.Aspire.Tests with Coverage'
- task: CmdLine@2
@@ -110,77 +119,82 @@ jobs:
steps:
- task: UseDotNet@2
displayName: Use .NET 8.0
displayName: Use .NET 10.0
inputs:
packageType: 'sdk'
version: '8.0.x'
- task: CmdLine@2
displayName: 'WireMock.Net.Tests.UsingNuGet'
inputs:
script: 'dotnet test ./test/WireMock.Net.Tests.UsingNuGet/WireMock.Net.Tests.UsingNuGet.csproj --configuration Release'
- task: CmdLine@2
displayName: 'WireMock.Net.Tests with Coverage'
inputs:
script: 'dotnet test ./test/WireMock.Net.Tests/WireMock.Net.Tests.csproj --configuration Debug --framework net8.0 --collect:"XPlat Code Coverage" --logger trx'
version: '10.0.x'
# - task: CmdLine@2
# displayName: 'WireMock.Net.TUnitTests with Coverage'
# displayName: 'WireMock.Net.Tests.UsingNuGet'
# inputs:
# script: 'dotnet test --project ./test/WireMock.Net.TUnitTests/WireMock.Net.TUnitTests.csproj --configuration Debug --framework net8.0 --collect:"XPlat Code Coverage" --logger trx'
# script: 'dotnet test ./test/WireMock.Net.Tests.UsingNuGet/WireMock.Net.Tests.UsingNuGet.csproj --configuration Release'
# - task: CmdLine@2
# displayName: 'WireMock.Net.Tests'
# inputs:
# script: 'dotnet test ./test/WireMock.Net.Tests/WireMock.Net.Tests.csproj --configuration Debug --framework net8.0'
- task: CmdLine@2
displayName: 'WireMock.Net.Middleware.Tests with Coverage'
displayName: 'WireMock.Net.Tests'
inputs:
script: 'dotnet test ./test/WireMock.Net.Middleware.Tests/WireMock.Net.Middleware.Tests.csproj --configuration Debug --framework net8.0 --collect:"XPlat Code Coverage" --logger trx'
script: 'dotnet test ./test/WireMock.Net.Tests/WireMock.Net.Tests.csproj --configuration Debug --framework net10.0'
- job: Windows_Release_to_MyGet
dependsOn: Windows_Build_Test
condition: and(succeeded(), ${{ parameters.runWindowsReleaseToMyGet }})
# - task: CmdLine@2
# displayName: 'WireMock.Net.TUnitTests'
# inputs:
# script: 'dotnet test --project ./test/WireMock.Net.TUnitTests/WireMock.Net.TUnitTests.csproj --configuration Debug --framework net10.0'
pool:
vmImage: 'windows-2025'
steps:
- script: |
echo "BuildId = $(buildId)"
displayName: 'Print buildId'
- task: UseDotNet@2
displayName: Use .NET 8.0
- task: CmdLine@2
displayName: 'WireMock.Net.Middleware.Tests'
inputs:
packageType: 'sdk'
version: '8.0.x'
script: 'dotnet test ./test/WireMock.Net.Middleware.Tests/WireMock.Net.Middleware.Tests.csproj --configuration Debug --framework net10.0'
- task: DotNetCoreCLI@2
displayName: Build Release
inputs:
command: 'build'
arguments: /p:Configuration=Release
projects: $(buildProjects)
# - job: Windows_Release_to_MyGet
# dependsOn: Windows_Build_Test
# condition: and(succeeded(), ${{ parameters.runWindowsReleaseToMyGet }})
- task: DotNetCoreCLI@2
displayName: Pack
condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest')) # Do not run for PullRequests
inputs:
command: pack
configuration: 'Release'
packagesToPack: $(buildProjects)
nobuild: true
packDirectory: '$(Build.ArtifactStagingDirectory)/packages'
verbosityPack: 'normal'
# pool:
# vmImage: 'windows-2025'
- task: PublishBuildArtifacts@1
displayName: Publish Artifacts
condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest')) # Do not run for PullRequests
inputs:
PathtoPublish: '$(Build.ArtifactStagingDirectory)'
# steps:
# - script: |
# echo "BuildId = $(buildId)"
# displayName: 'Print buildId'
- task: DotNetCoreCLI@2
displayName: Push to MyGet
condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest')) # Do not run for PullRequests
inputs:
command: custom
custom: nuget
arguments: push $(Build.ArtifactStagingDirectory)\packages\*.nupkg -n -s https://www.myget.org/F/wiremock-net/api/v3/index.json -k $(MyGetKey)
# - task: UseDotNet@2
# displayName: Use .NET 10.0
# inputs:
# packageType: 'sdk'
# version: '10.0.x'
# - task: DotNetCoreCLI@2
# displayName: Build Release
# inputs:
# command: 'build'
# arguments: /p:Configuration=Release
# projects: $(buildProjects)
# - task: DotNetCoreCLI@2
# displayName: Pack
# condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest')) # Do not run for PullRequests
# inputs:
# command: pack
# configuration: 'Release'
# packagesToPack: $(buildProjects)
# nobuild: true
# packDirectory: '$(Build.ArtifactStagingDirectory)/packages'
# verbosityPack: 'normal'
# - task: PublishBuildArtifacts@1
# displayName: Publish Artifacts
# condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest')) # Do not run for PullRequests
# inputs:
# PathtoPublish: '$(Build.ArtifactStagingDirectory)'
# - task: DotNetCoreCLI@2
# displayName: Push to MyGet
# condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest')) # Do not run for PullRequests
# inputs:
# command: custom
# custom: nuget
# arguments: push $(Build.ArtifactStagingDirectory)\packages\*.nupkg -n -s https://www.myget.org/F/wiremock-net/api/v3/index.json -k $(MyGetKey)
+6
View File
@@ -18,6 +18,12 @@ steps:
packageType: sdk
version: 8.0.x
- task: UseDotNet@2
displayName: 'Use .NET 10'
inputs:
packageType: sdk
version: 10.0.x
# Based on https://whereslou.com/2018/09/versioning-and-publishing-nuget-packages-automatically-using-azure-devops-pipelines/
- task: DotNetCoreCLI@2
displayName: Build Release
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
@@ -1,12 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<Sdk Name="Aspire.AppHost.Sdk" Version="13.4.0" />
<Sdk Name="Aspire.AppHost.Sdk" Version="13.6.5" />
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AspireUseCliBundle>true</AspireUseCliBundle>
</PropertyGroup>
<ItemGroup>
@@ -18,10 +19,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Aspire.Hosting.AppHost" Version="13.4.3" />
<!-- CVE-2026-48109 -->
<PackageReference Include="MessagePack" Version="2.5.302" />
<PackageReference Include="Aspire.Hosting.AppHost" Version="13.6.5" />
</ItemGroup>
<ItemGroup>
@@ -1,12 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<Sdk Name="Aspire.AppHost.Sdk" Version="13.4.0" />
<Sdk Name="Aspire.AppHost.Sdk" Version="13.6.5" />
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AspireUseCliBundle>true</AspireUseCliBundle>
</PropertyGroup>
<ItemGroup>
@@ -15,10 +16,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Aspire.Hosting.AppHost" Version="13.4.3" />
<!-- CVE-2026-48109 -->
<PackageReference Include="MessagePack" Version="2.5.302" />
<PackageReference Include="Aspire.Hosting.AppHost" Version="13.6.5" />
</ItemGroup>
</Project>
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsAspireSharedProject>true</IsAspireSharedProject>
@@ -10,8 +10,8 @@
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.Extensions.Http.Resilience" Version="8.4.0" />
<PackageReference Include="Microsoft.Extensions.ServiceDiscovery" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Http.Resilience" Version="10.11.0" />
<PackageReference Include="Microsoft.Extensions.ServiceDiscovery" Version="10.11.0" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.15.3" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.15.3" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.15.2" />
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
@@ -1,8 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<ApplicationIcon>../../resources/WireMock.Net-Logo.ico</ApplicationIcon>
</PropertyGroup>
@@ -1,8 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>latest</LangVersion>
@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<DefineConstants>$(DefineConstants);GRAPHQL;MIMEKIT;PROTOBUF</DefineConstants>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
@@ -16,8 +16,8 @@
<ItemGroup>
<ProjectReference Include="..\..\src\WireMock.Net\WireMock.Net.csproj" />
<PackageReference Include="log4net" Version="3.3.0" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
<PackageReference Include="log4net" Version="3.5.0" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="10.0.14" />
</ItemGroup>
<ItemGroup>
@@ -1,8 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
@@ -1,8 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<ApplicationIcon>../../resources/WireMock.Net-Logo.ico</ApplicationIcon>
</PropertyGroup>
@@ -1,8 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
@@ -3,6 +3,7 @@
using System.Globalization;
using System.Net;
using Newtonsoft.Json;
using Scriban.Runtime;
using WireMock.Logging;
using WireMock.Matchers;
using WireMock.Models;
@@ -285,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")
@@ -399,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();
@@ -6,7 +6,7 @@ using log4net.Config;
using log4net.Repository;
using WireMock.Net.ConsoleApplication;
namespace WireMock.Net.Console.NET8;
namespace WireMock.Net.Console;
static class Program
{
@@ -1,8 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<!--<DefineConstants>$(DefineConstants);GRAPHQL;MIMEKIT;PROTOBUF</DefineConstants>-->
</PropertyGroup>
@@ -18,8 +18,8 @@
<ItemGroup>
<ProjectReference Include="..\..\src\WireMock.Net\WireMock.Net.csproj" />
<PackageReference Include="log4net" Version="3.3.0" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
<PackageReference Include="log4net" Version="3.5.0" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="10.0.14" />
</ItemGroup>
<ItemGroup>
@@ -1,22 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<!--<ItemGroup>
<ProjectReference Include="..\..\src\WireMock.Net\WireMock.Net.csproj" />
</ItemGroup>-->
<ItemGroup>
<PackageReference Update="SonarAnalyzer.CSharp" Version="10.12.0.118525" />
</ItemGroup>
<ItemGroup>
<PackageReference Update="SonarAnalyzer.CSharp" Version="10.12.0.118525" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="WireMock.Net" Version="2.30.0" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="WireMock.Net" Version="2.15.0" />
</ItemGroup>
<!-- CVE -->
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.3.14" />
<PackageReference Include="Microsoft.AspNetCore.WebUtilities" Version="2.3.14" />
</ItemGroup>
</Project>
@@ -1,20 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<!--<PackageReference Include="Microsoft.OpenApi.YamlReader" Version="3.7.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="3.7.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.OpenApi" Version="3.10.2" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>
@@ -1,8 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
@@ -90,8 +90,11 @@
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel.Core">
<Version>2.3.11</Version>
</PackageReference>
<PackageReference Include="Microsoft.AspNetCore.WebUtilities">
<Version>2.3.14</Version>
</PackageReference>
<PackageReference Include="WireMock.Net">
<Version>2.12.0</Version>
<Version>2.30.0</Version>
</PackageReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
@@ -1,8 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<!--<RuntimeIdentifiers>win10-x64</RuntimeIdentifiers>-->
<StartupObject>WireMock.Net.WebApplication.Program</StartupObject>
<AssemblyName>WireMock.Net.WebApplication</AssemblyName>
@@ -10,10 +10,6 @@
<AspNetCoreHostingModel>OutOfProcess</AspNetCoreHostingModel>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\WireMock.Net\WireMock.Net.csproj" />
</ItemGroup>
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
@@ -4,7 +4,7 @@ This project demonstrates all the WebSocket capabilities of WireMock.Net.
## Prerequisites
- .NET 8.0 SDK
- .NET 10.0 SDK
- Optional: `wscat` for manual testing (`npm install -g wscat`)
## Running the Examples
@@ -1,8 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<AssemblyName>WireMock.Net.WebSocketExamples</AssemblyName>
<RootNamespace>WireMock.Net.WebSocketExamples</RootNamespace>
</PropertyGroup>
@@ -12,7 +12,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="10.0.14" />
</ItemGroup>
</Project>
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<AzureFunctionsVersion>v4</AzureFunctionsVersion>
<UserSecretsId>bb7d8355-68c4-4f81-8c2d-6cdd80cd7602</UserSecretsId>
</PropertyGroup>
@@ -1,8 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
</PropertyGroup>
<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; }
}
@@ -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.
///
@@ -4,7 +4,7 @@
<Description>Commonly used interfaces, models, enumerations and types.</Description>
<AssemblyTitle>WireMock.Net.Abstractions</AssemblyTitle>
<Authors>Stef Heyenrath</Authors>
<TargetFrameworks>netstandard2.0;net8.0</TargetFrameworks>
<TargetFrameworks>netstandard2.0;net8.0;net10.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);1591;8603</NoWarn>
<AssemblyName>WireMock.Net.Abstractions</AssemblyName>
@@ -1,11 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<ImplicitUsings>enable</ImplicitUsings>
<Description>Middleware which can be used to host WireMock.Net as a Middleware background process in a AspNetCore WebApplication</Description>
<AssemblyTitle>WireMock.Net.AspNetCore.Middleware</AssemblyTitle>
<Authors>Matthew Yost;Stef Heyenrath</Authors>
<TargetFramework>net8.0</TargetFramework>
<TargetFrameworks>net8.0;net10.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<AssemblyName>WireMock.Net.AspNetCore.Middleware</AssemblyName>
<PackageId>WireMock.Net.AspNetCore.Middleware</PackageId>
@@ -31,7 +31,8 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.2" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.2" Condition=" '$(TargetFramework)' != 'net10.0' " />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.14" Condition=" '$(TargetFramework)' == 'net10.0' " />
</ItemGroup>
<ItemGroup>
@@ -1,11 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<ImplicitUsings>enable</ImplicitUsings>
<Description>Aspire extension to start a WireMock.Net server to stub an api.</Description>
<AssemblyTitle>WireMock.Net.Aspire</AssemblyTitle>
<Authors>Stef Heyenrath</Authors>
<TargetFramework>net8.0</TargetFramework>
<TargetFrameworks>net8.0;net10.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<AssemblyName>WireMock.Net.Aspire</AssemblyName>
<PackageId>WireMock.Net.Aspire</PackageId>
@@ -22,6 +22,7 @@
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageIcon>WireMock.Net-LogoAspire.png</PackageIcon>
<ApplicationIcon>../../resources/WireMock.Net-LogoAspire.ico</ApplicationIcon>
<AspireUseCliBundle>true</AspireUseCliBundle>
</PropertyGroup>
<ItemGroup>
@@ -45,10 +46,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Aspire.Hosting" Version="13.4.3" />
<!-- CVE-2026-48109 -->
<PackageReference Include="MessagePack" Version="2.5.302" />
<PackageReference Include="Aspire.Hosting" Version="13.6.5" />
</ItemGroup>
<ItemGroup>
@@ -32,7 +32,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AwesomeAssertions" Version="9.4.0" />
<PackageReference Include="AwesomeAssertions" Version="9.6.0" />
</ItemGroup>
<ItemGroup>
@@ -71,7 +71,7 @@ This leads to more maintainable, scalable, and production-like test code.
- Asynchronous handlers
- Fluent, composable routing extensions
- Easy integration with existing WireMock.Net servers
- .NET 8+ support
- .NET 8 and .NET 10 support
---
@@ -2,7 +2,7 @@
<PropertyGroup>
<Description>WireMock.Net.Routing extends WireMock.Net with modern, minimal-API-style routing for .NET</Description>
<Authors>Gennadii Saltyshchak</Authors>
<TargetFramework>net8.0</TargetFramework>
<TargetFrameworks>net8.0;net10.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PackageTags>tdd;mock;http;wiremock;test;server;unittest;routing;minimalapi</PackageTags>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
@@ -1,10 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>GraphQL support for WireMock.Net</Description>
<AssemblyTitle>WireMock.Net.Matchers.GraphQL</AssemblyTitle>
<Authors>Stef Heyenrath</Authors>
<TargetFrameworks>net462;netstandard2.1;net8.0</TargetFrameworks>
<TargetFrameworks>net462;netstandard2.1;net8.0;net10.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PackageTags>wiremock;matchers;matcher;graphql</PackageTags>
<RootNamespace>WireMock</RootNamespace>
@@ -1,10 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<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>
<TargetFrameworks>netstandard2.0;net8.0;net10.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PackageTags>wiremock;matchers;matcher;jsonpath;systemtextjson</PackageTags>
<RootNamespace>WireMock</RootNamespace>
@@ -35,7 +35,8 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="JsonPath.Net" Version="3.0.2" />
<!-- 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>
</Project>
@@ -1,10 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>MultiPart Mime support for WireMock.Net using MimeKitLite</Description>
<AssemblyTitle>WireMock.Net.MimePart</AssemblyTitle>
<Authors>Stef Heyenrath</Authors>
<TargetFrameworks>netstandard2.0;net8.0</TargetFrameworks>
<TargetFrameworks>netstandard2.0;net8.0;net10.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PackageTags>wiremock;matchers;matcher;mime;multipart;mimekit</PackageTags>
<RootNamespace>WireMock</RootNamespace>
@@ -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);
}
}
@@ -2,6 +2,9 @@
using System.Security.Cryptography.X509Certificates;
using WireMock.Owin;
#if NETSTANDARD2_0
using Enum = WireMock.Compatiblity.EnumCompat;
#endif
namespace WireMock.HttpsCertificate;
@@ -18,32 +21,24 @@ internal static class CertificateLoader
{
var thumbprintOrSubjectNameOrHost = options.X509ThumbprintOrSubjectName ?? host;
var certStore = new X509Store((StoreName)Enum.Parse(typeof(StoreName), options.X509StoreName!), (StoreLocation)Enum.Parse(typeof(StoreLocation), options.X509StoreLocation!));
try
{
certStore.Open(OpenFlags.ReadOnly);
using var certStore = new X509Store(Enum.Parse<StoreName>(options.X509StoreName!), Enum.Parse<StoreLocation>(options.X509StoreLocation!));
certStore.Open(OpenFlags.ReadOnly);
// Attempt to find by Thumbprint first
var matchingCertificates = certStore.Certificates.Find(X509FindType.FindByThumbprint, thumbprintOrSubjectNameOrHost, false);
// Attempt to find by Thumbprint first
var matchingCertificates = certStore.Certificates.Find(X509FindType.FindByThumbprint, thumbprintOrSubjectNameOrHost, false);
if (matchingCertificates.Count == 0)
{
// Fallback to SubjectName
matchingCertificates = certStore.Certificates.Find(X509FindType.FindBySubjectName, thumbprintOrSubjectNameOrHost, false);
if (matchingCertificates.Count == 0)
{
// Fallback to SubjectName
matchingCertificates = certStore.Certificates.Find(X509FindType.FindBySubjectName, thumbprintOrSubjectNameOrHost, false);
if (matchingCertificates.Count == 0)
{
// No certificates matched the search criteria.
throw new FileNotFoundException($"No Certificate found with in store '{options.X509StoreName}', location '{options.X509StoreLocation}' for Thumbprint or SubjectName '{thumbprintOrSubjectNameOrHost}'.");
}
// No certificates matched the search criteria.
throw new FileNotFoundException($"No Certificate found with in store '{options.X509StoreName}', location '{options.X509StoreLocation}' for Thumbprint or SubjectName '{thumbprintOrSubjectNameOrHost}'.");
}
}
// Use the first matching certificate.
return matchingCertificates[0];
}
finally
{
certStore.Dispose();
certStore.Close();
}
// Use the first matching certificate.
return matchingCertificates[0];
}
if (!string.IsNullOrEmpty(options.X509CertificateFilePath))
@@ -55,10 +50,14 @@ internal static class CertificateLoader
if (!string.IsNullOrEmpty(options.X509CertificatePassword))
{
var certPem = File.ReadAllText(options.X509CertificateFilePath);
var cert = X509Certificate2.CreateFromPem(certPem, options.X509CertificatePassword);
using var cert = X509Certificate2.CreateFromPem(certPem, options.X509CertificatePassword);
const string defaultPasswordPem = "WireMock.Net";
#if NET9_0_OR_GREATER
return X509CertificateLoader.LoadPkcs12(cert.Export(X509ContentType.Pfx, defaultPasswordPem), defaultPasswordPem);
#else
return new X509Certificate2(cert.Export(X509ContentType.Pfx, defaultPasswordPem), defaultPasswordPem);
#endif
}
return X509Certificate2.CreateFromPemFile(options.X509CertificateFilePath);
#else
@@ -66,9 +65,29 @@ internal static class CertificateLoader
#endif
}
#if NET9_0_OR_GREATER
var data = File.ReadAllBytes(options.X509CertificateFilePath);
var contentType = X509Certificate2.GetCertContentType(data);
if (contentType == X509ContentType.Pfx)
{
return X509CertificateLoader.LoadPkcs12(data, string.IsNullOrEmpty(options.X509CertificatePassword) ? null : options.X509CertificatePassword);
}
if (contentType == X509ContentType.Cert)
{
return X509CertificateLoader.LoadCertificate(data);
}
// The loader does not support legacy formats such as PKCS#7 and serialized certificates.
#pragma warning disable SYSLIB0057
return !string.IsNullOrEmpty(options.X509CertificatePassword) ?
new X509Certificate2(data, options.X509CertificatePassword) :
new X509Certificate2(data);
#pragma warning restore SYSLIB0057
#else
return !string.IsNullOrEmpty(options.X509CertificatePassword) ?
new X509Certificate2(options.X509CertificateFilePath, options.X509CertificatePassword) :
new X509Certificate2(options.X509CertificateFilePath);
#endif
}
if (options.X509Certificate != null)
@@ -89,6 +89,10 @@ wTM1Z+CJZG9xAcf1zAVGl4ggYyYEFGBFyJ8VBwijS2zy1qwN1WYGtcWoAgIH0A==
public static X509Certificate2 GetX509Certificate2()
{
byte[] data = Convert.FromBase64String(Data);
#if NET9_0_OR_GREATER
return X509CertificateLoader.LoadPkcs12(data, null);
#else
return new X509Certificate2(data);
#endif
}
}
@@ -43,6 +43,8 @@ internal sealed class AdminPaths(string? adminPath) : IAdminPaths
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$");
@@ -1,6 +1,6 @@
// Copyright © WireMock.Net
// Copyright © WireMock.Net
#if NET8_0
#if NET8_0_OR_GREATER
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using WireMock.Types;
@@ -11,6 +11,11 @@ using WireMock.Serialization;
using WireMock.Services;
using WireMock.Util;
#if NET10_0
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.Extensions.Configuration;
#endif
namespace WireMock.Owin;
internal partial class AspNetCoreSelfHost
@@ -20,7 +25,11 @@ internal partial class AspNetCoreSelfHost
private readonly IWireMockLogger _logger;
private readonly HostUrlOptions _urlOptions;
#if NET10_0
private Microsoft.Extensions.Hosting.IHost _host = null!;
#else
private IWebHost _host = null!;
#endif
public bool IsStarted { get; private set; }
@@ -43,6 +52,36 @@ internal partial class AspNetCoreSelfHost
public Task StartAsync()
{
#if NET10_0
var builder = WebApplication.CreateBuilder(new WebApplicationOptions
{
// Workaround for https://github.com/wiremock/WireMock.Net/issues/292
// On some platforms, AppContext.BaseDirectory is null, which causes WebHostBuilder to fail if ContentRoot is not
// specified (even though we don't actually use that base path mechanism, since we have our own way of configuring
// a filesystem handler).
ContentRootPath = string.IsNullOrEmpty(AppContext.BaseDirectory) ? Directory.GetCurrentDirectory() : null
});
builder.Configuration.Sources.Clear();
builder.Configuration.AddEnvironmentVariables();
builder.Logging.ClearProviders();
builder.Logging.AddProvider(new WireMockAspNetCoreLoggerProvider(_logger));
builder.Logging.SetMinimumLevel(LogLevel.Warning);
builder.Services.Configure<KestrelServerOptions>(builder.Configuration.GetSection("Kestrel"));
// The default ConsoleLifetime cancels Ctrl+C and shuts down the host itself, which prevents the consuming application from controlling the process lifetime.
builder.Services.AddSingleton<Microsoft.Extensions.Hosting.IHostLifetime, NoopHostLifetime>();
ConfigureServices(builder.Services);
builder.WebHost.ConfigureKestrel(options =>
{
SetKestrelOptionsLimits(options);
SetHttpsAndUrls(options, _wireMockMiddlewareOptions, _urlOptions.GetDetails());
});
var app = builder.Build();
ConfigureApplication(app);
_host = app;
#else
var builder = new WebHostBuilder();
// Workaround for https://github.com/wiremock/WireMock.Net/issues/292
@@ -63,58 +102,71 @@ internal partial class AspNetCoreSelfHost
logging.AddProvider(new WireMockAspNetCoreLoggerProvider(_logger));
logging.SetMinimumLevel(LogLevel.Warning);
})
.ConfigureServices(services =>
{
services.AddSingleton(_wireMockMiddlewareOptions);
services.AddSingleton(_wireMockMiddlewareOptions.AdminPaths);
services.AddSingleton<IMappingMatcher, MappingMatcher>();
services.AddSingleton<IRandomizerDoubleBetween0And1, RandomizerDoubleBetween0And1>();
services.AddSingleton<IOwinRequestMapper, OwinRequestMapper>();
services.AddSingleton<IOwinResponseMapper, OwinResponseMapper>();
services.AddSingleton<IGuidUtils, GuidUtils>();
services.AddSingleton<IDateTimeUtils, DateTimeUtils>();
services.AddSingleton<IResponseMessageBuilder, ResponseMessageBuilder>();
services.AddSingleton<LogEntryMapper>();
services.AddSingleton<IWireMockMiddlewareLogger, WireMockMiddlewareLogger>();
#if NET8_0_OR_GREATER
AddCors(services);
#endif
_wireMockMiddlewareOptions.AdditionalServiceRegistration?.Invoke(services);
})
.Configure(appBuilder =>
{
appBuilder.UseMiddleware<GlobalExceptionMiddleware>();
#if NET8_0_OR_GREATER
UseCors(appBuilder);
var webSocketOptions = new WebSocketOptions();
if (_wireMockMiddlewareOptions.WebSocketSettings?.KeepAliveIntervalSeconds != null)
{
webSocketOptions.KeepAliveInterval = TimeSpan.FromSeconds(_wireMockMiddlewareOptions.WebSocketSettings.KeepAliveIntervalSeconds);
}
appBuilder.UseWebSockets(webSocketOptions);
#endif
_wireMockMiddlewareOptions.PreWireMockMiddlewareInit?.Invoke(appBuilder);
appBuilder.UseMiddleware<WireMockMiddleware>();
_wireMockMiddlewareOptions.PostWireMockMiddlewareInit?.Invoke(appBuilder);
})
.ConfigureServices(ConfigureServices)
.Configure(ConfigureApplication)
.UseKestrel(options =>
{
SetKestrelOptionsLimits(options);
SetHttpsAndUrls(options, _wireMockMiddlewareOptions, _urlOptions.GetDetails());
})
.ConfigureKestrelServerOptions()
.Build();
#endif
return RunHost(_cts.Token);
}
#if NET10_0
private sealed class NoopHostLifetime : Microsoft.Extensions.Hosting.IHostLifetime
{
public Task WaitForStartAsync(CancellationToken cancellationToken) => Task.CompletedTask;
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}
#endif
private void ConfigureServices(IServiceCollection services)
{
services.AddSingleton(_wireMockMiddlewareOptions);
services.AddSingleton(_wireMockMiddlewareOptions.AdminPaths);
services.AddSingleton<IMappingMatcher, MappingMatcher>();
services.AddSingleton<IRandomizerDoubleBetween0And1, RandomizerDoubleBetween0And1>();
services.AddSingleton<IOwinRequestMapper, OwinRequestMapper>();
services.AddSingleton<IOwinResponseMapper, OwinResponseMapper>();
services.AddSingleton<IGuidUtils, GuidUtils>();
services.AddSingleton<IDateTimeUtils, DateTimeUtils>();
services.AddSingleton<IResponseMessageBuilder, ResponseMessageBuilder>();
services.AddSingleton<LogEntryMapper>();
services.AddSingleton<IWireMockMiddlewareLogger, WireMockMiddlewareLogger>();
#if NET8_0_OR_GREATER
AddCors(services);
#endif
_wireMockMiddlewareOptions.AdditionalServiceRegistration?.Invoke(services);
}
private void ConfigureApplication(IApplicationBuilder appBuilder)
{
appBuilder.UseMiddleware<GlobalExceptionMiddleware>();
#if NET8_0_OR_GREATER
UseCors(appBuilder);
var webSocketOptions = new WebSocketOptions();
if (_wireMockMiddlewareOptions.WebSocketSettings?.KeepAliveIntervalSeconds != null)
{
webSocketOptions.KeepAliveInterval = TimeSpan.FromSeconds(_wireMockMiddlewareOptions.WebSocketSettings.KeepAliveIntervalSeconds);
}
appBuilder.UseWebSockets(webSocketOptions);
#endif
_wireMockMiddlewareOptions.PreWireMockMiddlewareInit?.Invoke(appBuilder);
appBuilder.UseMiddleware<WireMockMiddleware>();
_wireMockMiddlewareOptions.PostWireMockMiddlewareInit?.Invoke(appBuilder);
}
private Task RunHost(CancellationToken token)
{
try
@@ -126,10 +178,18 @@ internal partial class AspNetCoreSelfHost
#endif
appLifetime.ApplicationStarted.Register(() =>
{
#if NET10_0
var addresses = _host.Services.GetRequiredService<Microsoft.AspNetCore.Hosting.Server.IServer>()
.Features
.Get<Microsoft.AspNetCore.Hosting.Server.Features.IServerAddressesFeature>()!
.Addresses
.ToArray();
#else
var addresses = _host.ServerFeatures
.Get<Microsoft.AspNetCore.Hosting.Server.Features.IServerAddressesFeature>()!
.Addresses
.ToArray();
#endif
if (_urlOptions.Urls == null)
{
@@ -169,13 +229,19 @@ internal partial class AspNetCoreSelfHost
IsStarted = true;
});
#if NET8_0
#if NET10_0
_logger.Info("Server using .NET 10.0");
#elif NET8_0
_logger.Info("Server using .NET 8.0");
#else
_logger.Info("Server using .NET Standard 2.0");
#endif
#if NET10_0
return Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.RunAsync(_host, token);
#else
return _host.RunAsync(token);
#endif
}
catch (Exception ex)
{
@@ -24,6 +24,8 @@ internal interface IAdminPaths
RegexMatcher RequestsGuidPathMatcher { get; }
RegexMatcher RequestsGuidNearMissesPathMatcher { get; }
RegexMatcher ScenariosNameMatcher { get; }
RegexMatcher ScenariosNameWithStateMatcher { get; }
@@ -5,4 +5,6 @@ namespace WireMock.Owin;
internal interface IMappingMatcher
{
(MappingMatcherResult? Match, MappingMatcherResult? Partial) FindBestMatch(RequestMessage request);
IReadOnlyList<MappingMatcherResult> FindNearMisses(IRequestMessage request, int maxResults);
}
@@ -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)
@@ -72,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}");
@@ -171,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
@@ -255,4 +261,4 @@ internal class WireMockMiddleware(
scenario.Finished = mapping.NextState == null;
});
}
}
}
@@ -73,20 +73,20 @@ internal class WireMockMiddlewareLogger(
var logEntries = options.LogEntries.ToList();
foreach (var logEntry in logEntries
.OrderBy(le => le.RequestMessage?.DateTime ?? le.ResponseMessage?.DateTime)
.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.
// 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 checkTime = dateTimeUtils.UtcNow.AddHours(-options.RequestLogExpirationDuration.Value);
foreach (var logEntry in logEntries.Where(le => le.RequestMessage?.DateTime < checkTime || le.ResponseMessage?.DateTime < checkTime))
foreach (var logEntry in logEntries.Where(le => le.RequestMessage?.DateTime < checkTime))
{
TryRemoveLogEntry(logEntry);
}
@@ -87,7 +87,7 @@ public partial class Response : IResponseBuilder
{
var message = responseMessage ?? new ResponseMessage
{
DateTime = DateTime.UtcNow // We set it here to the current time.
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);
@@ -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)
@@ -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;
@@ -93,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));
@@ -641,6 +654,22 @@ 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);
@@ -656,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)
@@ -679,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)
{
@@ -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()
@@ -421,6 +421,105 @@ paths:
schema:
$ref: '#/components/schemas/StatusModel'
/__admin/requests/{guid}/near-misses:
get:
summary: Get the near misses (mappings which partially match) for a logged request
parameters:
- name: guid
in: path
required: true
schema:
type: string
format: uuid
- name: maxResults
in: query
required: false
description: The maximum number of near misses per request (default 3).
schema:
type: integer
format: int32
minimum: 1
responses:
'200':
description: OK
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/NearMissModel'
'400':
description: Bad Request
content:
application/json:
schema:
$ref: '#/components/schemas/StatusModel'
'404':
description: Not Found
content:
application/json:
schema:
$ref: '#/components/schemas/StatusModel'
/__admin/requests/count:
post:
summary: Count logged requests matching a request filter
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/RequestModel'
required: true
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/RequestCountModel'
x-codegen-request-body-name: requestFilter
/__admin/requests/unmatched:
get:
summary: Get logged requests which did not fully match any mapping
responses:
'200':
description: OK
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/LogEntryModel'
/__admin/requests/unmatched/near-misses:
get:
summary: Get the near misses (mappings which partially match) for each logged request which did not fully match any mapping
parameters:
- name: maxResults
in: query
required: false
description: The maximum number of near misses per request (default 3).
schema:
type: integer
format: int32
minimum: 1
responses:
'200':
description: OK
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/NearMissModel'
'400':
description: Bad Request
content:
application/json:
schema:
$ref: '#/components/schemas/StatusModel'
/__admin/requests/find:
post:
summary: Find logged requests matching a request filter
@@ -895,6 +994,35 @@ components:
additionalProperties: true
description: Log Request Match Model
NearMissModel:
type: object
properties:
LogEntryGuid:
type: string
description: The unique identifier of the log entry for the request.
format: uuid
Request:
$ref: '#/components/schemas/LogRequestModel'
MappingGuid:
type: string
description: The unique identifier of the mapping.
format: uuid
MappingTitle:
type: string
description: The title of the mapping.
RequestMatchResult:
$ref: '#/components/schemas/LogRequestMatchModel'
description: Near Miss Model, a mapping which did not (fully) match a request
RequestCountModel:
type: object
properties:
Count:
type: integer
description: The number of requests which match the criteria.
format: int32
description: Request Count Model
LogRequestModel:
type: object
properties:
@@ -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;
}
}
@@ -1,6 +1,7 @@
// Copyright © WireMock.Net
using Scriban;
using Scriban.Runtime;
using Stef.Validation;
using WireMock.Handlers;
using WireMock.Types;
@@ -9,21 +10,40 @@ namespace WireMock.Transformers.Scriban;
internal class ScribanContext : ITransformerContext
{
private static readonly MemberRenamerDelegate MemberRenamer = member => member.Name;
private readonly TransformerType _transformerType;
private readonly ScriptObject? _customGlobals;
public IFileSystemHandler FileSystemHandler { get; }
public ScribanContext(IFileSystemHandler fileSystemHandler, TransformerType transformerType)
public ScribanContext(IFileSystemHandler fileSystemHandler, TransformerType transformerType, ScriptObject? customGlobals = null)
{
FileSystemHandler = Guard.NotNull(fileSystemHandler);
_transformerType = transformerType;
_customGlobals = customGlobals;
}
public string ParseAndRender(string text, object model)
{
var template = _transformerType == TransformerType.ScribanDotLiquid ? Template.ParseLiquid(text) : Template.Parse(text);
var isLiquid = _transformerType == TransformerType.ScribanDotLiquid;
var template = isLiquid ? Template.ParseLiquid(text) : Template.Parse(text);
return template.Render(model, member => member.Name);
if (_customGlobals is null)
{
return template.Render(model, MemberRenamer);
}
// Same as Template.Render(model, MemberRenamer), but with the custom globals pushed beneath the model so that the model takes precedence.
var context = isLiquid ? new LiquidTemplateContext() : new TemplateContext();
context.MemberRenamer = MemberRenamer;
context.PushGlobal(_customGlobals);
var modelGlobals = new ScriptObject();
modelGlobals.Import(model, renamer: MemberRenamer);
context.PushGlobal(modelGlobals);
return template.Render(context);
}
public object? ParseAndEvaluate(string text, object model)
@@ -1,24 +1,32 @@
// Copyright © WireMock.Net
using WireMock.Handlers;
using WireMock.Types;
using Scriban.Runtime;
using Stef.Validation;
using WireMock.Settings;
using WireMock.Types;
namespace WireMock.Transformers.Scriban;
internal class ScribanContextFactory : ITransformerContextFactory
{
private readonly IFileSystemHandler _fileSystemHandler;
private readonly WireMockServerSettings _settings;
private readonly TransformerType _transformerType;
public ScribanContextFactory(IFileSystemHandler fileSystemHandler, TransformerType transformerType)
public ScribanContextFactory(WireMockServerSettings settings, TransformerType transformerType)
{
_fileSystemHandler = Guard.NotNull(fileSystemHandler);
_settings = Guard.NotNull(settings);
_transformerType = Guard.Condition(transformerType, t => t is TransformerType.Scriban or TransformerType.ScribanDotLiquid);
}
public ITransformerContext Create()
{
return new ScribanContext(_fileSystemHandler, _transformerType);
ScriptObject? customGlobals = null;
if (_settings.ScribanRegistrationCallback is { } registrationCallback)
{
customGlobals = new ScriptObject();
registrationCallback(customGlobals, _settings.FileSystemHandler);
}
return new ScribanContext(_settings.FileSystemHandler, _transformerType, customGlobals);
}
}
@@ -19,8 +19,8 @@ internal static class TransformerFactory
case TransformerType.Scriban:
case TransformerType.ScribanDotLiquid:
var factoryDotLiquid = new ScribanContextFactory(settings.FileSystemHandler, transformerType);
return new Transformer(settings, factoryDotLiquid);
var factoryScriban = new ScribanContextFactory(settings, transformerType);
return new Transformer(settings, factoryScriban);
default:
throw new NotSupportedException($"{nameof(TransformerType)} '{transformerType}' is not supported.");
@@ -18,6 +18,8 @@ namespace WireMock.WebSockets;
/// </summary>
public class WireMockWebSocketContext : IWebSocketContext
{
private readonly IDateTimeUtils _dateTimeUtils;
/// <inheritdoc />
public Guid ConnectionId { get; }
@@ -50,7 +52,8 @@ public class WireMockWebSocketContext : IWebSocketContext
WebSocketBuilder builder,
IWireMockMiddlewareOptions options,
IWireMockMiddlewareLogger logger,
IGuidUtils guidUtils
IGuidUtils guidUtils,
IDateTimeUtils dateTimeUtils
)
{
HttpContext = httpContext;
@@ -61,6 +64,7 @@ public class WireMockWebSocketContext : IWebSocketContext
Builder = builder;
Options = options;
Logger = logger;
_dateTimeUtils = dateTimeUtils;
ConnectionId = guidUtils.NewGuid();
}
@@ -169,7 +173,7 @@ public class WireMockWebSocketContext : IWebSocketContext
null
)
{
DateTime = DateTime.UtcNow
DateTime = _dateTimeUtils.UtcNow
};
}
else
@@ -179,7 +183,7 @@ public class WireMockWebSocketContext : IWebSocketContext
{
Method = method,
BodyData = bodyData,
DateTime = DateTime.UtcNow
DateTime = _dateTimeUtils.UtcNow
};
}
@@ -1,9 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>Minimal version from the lightweight Http Mocking Server for .NET</Description>
<AssemblyTitle>WireMock.Net.Minimal</AssemblyTitle>
<Authors>Stef Heyenrath</Authors>
<TargetFrameworks>netstandard2.0;net8.0</TargetFrameworks>
<TargetFrameworks>netstandard2.0;net8.0;net10.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<AssemblyName>WireMock.Net.Minimal</AssemblyName>
<PackageId>WireMock.Net.Minimal</PackageId>
@@ -28,7 +28,7 @@
<CodeAnalysisRuleSet>../WireMock.Net/WireMock.Net.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' == 'net8.0' ">
<PropertyGroup Condition="('$(TargetFramework)' == 'net8.0' Or '$(TargetFramework)' == 'net10.0')">
<DefineConstants>$(DefineConstants);TRAILINGHEADERS</DefineConstants>
</PropertyGroup>
@@ -43,7 +43,7 @@
<PackageReference Include="SimMetrics.Net" Version="1.0.5" />
<PackageReference Include="TinyMapper.Signed" Version="4.0.0" />
<PackageReference Include="Microsoft.IdentityModel.Protocols.OpenIdConnect" Version="6.34.0" />
<PackageReference Include="Scriban.Signed" Version="7.2.5" />
<PackageReference Include="Scriban.Signed" Version="7.5.3" />
</ItemGroup>
<ItemGroup>
@@ -52,7 +52,7 @@
</PackageReference>
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net8.0'">
<ItemGroup Condition="('$(TargetFramework)' == 'net8.0' Or '$(TargetFramework)' == 'net10.0')">
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
@@ -3,7 +3,7 @@
<Description>Some extensions for NUnit</Description>
<AssemblyTitle>WireMock.Net.NUnit</AssemblyTitle>
<Authors>Stef Heyenrath</Authors>
<TargetFrameworks>net462;net8.0</TargetFrameworks>
<TargetFrameworks>net462;net8.0;net10.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<AssemblyName>WireMock.Net.NUnit</AssemblyName>
<RootNamespace>WireMock.Net.NUnit</RootNamespace>
@@ -1,8 +1,5 @@
// Copyright © WireMock.Net
using System.Diagnostics.CodeAnalysis;
namespace System.Collections.Generic;
internal static class DictionaryExtensions
@@ -1,8 +1,8 @@
// Copyright © WireMock.Net
using JetBrains.Annotations;
using Microsoft.OpenApi.Reader;
using Stef.Validation;
using WireMock.Net.OpenApiParser.Models;
using WireMock.Net.OpenApiParser.Settings;
using WireMock.Server;
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8" ?>
<!-- See also https://github.com/ravibpatel/ILRepack.Lib.MSBuild.Task/issues/26 -->
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="ILRepacker" AfterTargets="Build" Condition="'$(Configuration)' == 'Release'">
<ItemGroup>
<InputAssemblies Include="$(OutputPath)WireMock.Net.OpenApiParser.dll" />
<InputAssemblies Include="@(ReferencePathWithRefAssemblies)" Condition="'%(filename)' == 'Microsoft.OpenApi.YamlReader'" />
<InputAssemblies Include="@(ReferencePathWithRefAssemblies)" Condition="'%(filename)' == 'Microsoft.OpenApi'" />
<LibraryPath Include="%(ReferencePathWithRefAssemblies.RelativeDir)" />
</ItemGroup>
<ItemGroup>
<DoNotInternalizeAssemblies Include="WireMock.Net.OpenApiParser" />
</ItemGroup>
<ILRepack
Parallel="true"
Internalize="true"
RenameInternalized="true"
InternalizeExclude="@(DoNotInternalizeAssemblies)"
InputAssemblies="@(InputAssemblies)"
LibraryPath="@(LibraryPath)"
TargetKind="Dll"
KeyFile="../../src/WireMock.Net/WireMock.Net.snk"
OutputFile="$(OutputPath)$(AssemblyName).dll"
/>
</Target>
</Project>
@@ -1,7 +1,7 @@
// Copyright © WireMock.Net
using Microsoft.OpenApi.Reader;
using WireMock.Admin.Mappings;
using WireMock.Net.OpenApiParser.Models;
using WireMock.Net.OpenApiParser.Settings;
namespace WireMock.Net.OpenApiParser;
@@ -0,0 +1,25 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
namespace WireMock.Net.OpenApiParser.Models;
/// <summary>
/// Object containing all diagnostic information related to Open API parsing.
/// </summary>
public class OpenApiDiagnostic
{
/// <summary>
/// List of all errors.
/// </summary>
public List<OpenApiError> Errors { get; set; } = [];
/// <summary>
/// List of all warnings
/// </summary>
public List<OpenApiError> Warnings { get; set; } = [];
/// <summary>
/// Open API specification version of the document parsed.
/// </summary>
public OpenApiSpecVersion SpecificationVersion { get; set; }
}
@@ -0,0 +1,46 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
namespace WireMock.Net.OpenApiParser.Models;
/// <summary>
/// Error related to the Open API Document.
/// </summary>
public class OpenApiError
{
/// <summary>
/// Initializes the <see cref="OpenApiError"/> class.
/// </summary>
public OpenApiError(string? pointer, string message)
{
Pointer = pointer;
Message = message;
}
/// <summary>
/// Initializes a copy of an <see cref="OpenApiError"/> object
/// </summary>
public OpenApiError(OpenApiError error)
{
Pointer = error.Pointer;
Message = error.Message;
}
/// <summary>
/// Message explaining the error.
/// </summary>
public string Message { get; set; }
/// <summary>
/// Pointer to the location of the error.
/// </summary>
public string? Pointer { get; set; }
/// <summary>
/// Gets the string representation of <see cref="OpenApiError"/>.
/// </summary>
public override string ToString()
{
return Message + (!string.IsNullOrEmpty(Pointer) ? " [" + Pointer + "]" : "");
}
}
@@ -0,0 +1,23 @@
// Copyright © WireMock.Net
using MicrosoftOpenApiDiagnostic = Microsoft.OpenApi.Reader.OpenApiDiagnostic;
namespace WireMock.Net.OpenApiParser.Models;
internal static class OpenApiMapper
{
internal static OpenApiDiagnostic? Map(MicrosoftOpenApiDiagnostic? openApiDiagnostic)
{
if (openApiDiagnostic == null)
{
return null;
}
return new OpenApiDiagnostic
{
Errors = openApiDiagnostic.Errors.Select(e => new OpenApiError(e.Pointer, e.Message)).ToList(),
Warnings = openApiDiagnostic.Warnings.Select(e => new OpenApiError(e.Pointer, e.Message)).ToList(),
SpecificationVersion = (OpenApiSpecVersion)openApiDiagnostic.SpecificationVersion
};
}
}
@@ -0,0 +1,29 @@
// Copyright © WireMock.Net
namespace WireMock.Net.OpenApiParser.Models;
/// <summary>
/// Represents versions of OpenAPI specification.
/// </summary>
public enum OpenApiSpecVersion
{
/// <summary>
/// Represents OpenAPI V2.0 spec
/// </summary>
OpenApi2_0,
/// <summary>
/// Represents all patches of OpenAPI V3.0 spec (e.g. 3.0.0, 3.0.1)
/// </summary>
OpenApi3_0,
/// <summary>
/// Represents OpenAPI V3.1 spec
/// </summary>
OpenApi3_1,
/// <summary>
/// Represents OpenAPI V3.2 spec
/// </summary>
OpenApi3_2
}
@@ -1,6 +1,5 @@
// Copyright © WireMock.Net
using System;
using Microsoft.OpenApi;
namespace WireMock.Net.OpenApiParser.Settings;
@@ -1,6 +1,5 @@
// Copyright © WireMock.Net
using System;
using Microsoft.OpenApi;
using RandomDataGenerator.FieldOptions;
using RandomDataGenerator.Randomizers;
Loaded 100 of 153 files, more files were not shown because too many files have changed in this diff. Show more