Compare commits

...
Author SHA1 Message Date
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
103 changed files with 1701 additions and 51701 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
+43 -1
View File
@@ -4,6 +4,7 @@ permissions:
contents: write
on:
workflow_dispatch:
push:
tags:
- "*.*.*"
@@ -15,7 +16,48 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
- name: Setup .NET 8 and 10
uses: actions/setup-dotnet@v4
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/*
+75 -22
View File
@@ -8,6 +8,9 @@ on:
branches:
- 'main'
permissions:
contents: read
jobs:
windows-build-and-run:
name: Run Tests on Windows
@@ -19,22 +22,33 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Setup .NET 8
- name: Setup .NET 8 and 10
uses: actions/setup-dotnet@v4
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
@@ -46,22 +60,61 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Setup .NET 8
- name: Setup .NET 8 and 10
uses: actions/setup-dotnet@v4
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.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 (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)'
run: dotnet test --project './test/WireMock.Net.Middleware.Tests/WireMock.Net.Middleware.Tests.csproj' -c Release --framework net10.0
# - 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)'
run: dotnet test --project './test/WireMock.Net.Aspire.Tests/WireMock.Net.Aspire.Tests.csproj' -c Release --framework net10.0
- 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.*
+18
View File
@@ -1,3 +1,21 @@
# 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)
+3 -3
View File
@@ -4,7 +4,7 @@
</PropertyGroup>
<PropertyGroup>
<VersionPrefix>2.19.0</VersionPrefix>
<VersionPrefix>2.30.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.19.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%
+5 -4
View File
@@ -1,6 +1,7 @@
# 2.19.0 (07 October 2026)
- #1512 Use IDateTimeUtils in WireMockWebSocketContext [bug]
- #1514 Fix DateTime in Response [bug]
- #1511 Incorrect response datetime for mapped response [bug]
# 2.30.0 (07 November 2026)
- #1527 Publish standalone single-file binaries in GitHub Releases [feature]
- #1528 Add .NET 10 target framework [feature]
- #1530 Use 10.x.x NuGets for .NET 10 [dependencies]
- #1532 Switch to IHost for .NET 10 + update dotnet-WireMock [feature]
The full release notes can be found here: https://github.com/wiremock/WireMock.Net/blob/master/CHANGELOG.md
+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}
+75 -64
View File
@@ -20,6 +20,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
@@ -48,30 +54,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 +116,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.4" />
<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.4" />
</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.4" />
<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.4" />
</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>
@@ -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>
@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<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>
</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>
<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>
<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; }
}
@@ -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>
@@ -5,7 +5,7 @@
<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.4" />
</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>
@@ -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);
}
@@ -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}");
@@ -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:
@@ -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>
@@ -2,7 +2,7 @@
<PropertyGroup>
<Description>An OpenApi (swagger) parser to generate MappingModel or mapping.json file.</Description>
<TargetFrameworks>netstandard2.0;net8.0</TargetFrameworks>
<TargetFrameworks>netstandard2.0;net8.0;net10.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PackageTags>wiremock;openapi;OAS;raml;converter;parser;openapiparser</PackageTags>
<ProjectGuid>{E5B03EEF-822C-4295-952B-4479AD30082B}</ProjectGuid>
@@ -1,10 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>OpenTelemetry exporter support for WireMock.Net</Description>
<AssemblyTitle>WireMock.Net.OpenTelemetry</AssemblyTitle>
<Authors>Petr Houška</Authors>
<TargetFrameworks>netstandard2.0;net8.0</TargetFrameworks>
<TargetFrameworks>netstandard2.0;net8.0;net10.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<RootNamespace>WireMock.OpenTelemetry</RootNamespace>
<PackageTags>wiremock;opentelemetry;otel;tracing;telemetry</PackageTags>
@@ -32,7 +32,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AwesomeAssertions" Version="9.4.0" />
<PackageReference Include="AwesomeAssertions" Version="9.6.0" />
</ItemGroup>
<ItemGroup>
@@ -250,6 +250,39 @@ public interface IWireMockAdminApi
[Get("requests/find")]
Task<IList<LogEntryModel>> FindRequestsByMappingGuidAsync([Query] Guid mappingGuid, CancellationToken cancellationToken = default);
/// <summary>
/// Count the requests which match the criteria (<see cref="RequestModel"/>).
/// </summary>
/// <param name="model">The RequestModel</param>
/// <param name="cancellationToken">The optional cancellationToken.</param>
[Post("requests/count")]
[Header("Content-Type", "application/json")]
Task<RequestCountModel> CountRequestsAsync([Body] RequestModel model, CancellationToken cancellationToken = default);
/// <summary>
/// Get the requests which did not fully match any mapping.
/// </summary>
/// <param name="cancellationToken">The optional cancellationToken.</param>
[Get("requests/unmatched")]
Task<IList<LogEntryModel>> GetUnmatchedRequestsAsync(CancellationToken cancellationToken = default);
/// <summary>
/// Get the near misses (the mappings which partially match) for all requests which did not fully match any mapping.
/// </summary>
/// <param name="maxResults">The maximum number of near misses per request. Default is 3.</param>
/// <param name="cancellationToken">The optional cancellationToken.</param>
[Get("requests/unmatched/near-misses")]
Task<IList<NearMissModel>> GetNearMissesForUnmatchedRequestsAsync([Query] int? maxResults = null, CancellationToken cancellationToken = default);
/// <summary>
/// Get the near misses (the mappings which partially match) for a request.
/// </summary>
/// <param name="guid">The Guid of the request</param>
/// <param name="maxResults">The maximum number of near misses. Default is 3.</param>
/// <param name="cancellationToken">The optional cancellationToken.</param>
[Get("requests/{guid}/near-misses")]
Task<IList<NearMissModel>> GetNearMissesForRequestAsync([Path] Guid guid, [Query] int? maxResults = null, CancellationToken cancellationToken = default);
/// <summary>
/// Get all scenarios
/// </summary>
@@ -1,10 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>A RestClient using RestEase to access the admin interface.</Description>
<AssemblyTitle>WireMock.Net.RestClient</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.RestClient</AssemblyName>
<PackageId>WireMock.Net.RestClient</PackageId>
@@ -2,7 +2,7 @@
<PropertyGroup>
<Description>Shared interfaces, models, enumerations and types.</Description>
<Authors>Stef Heyenrath</Authors>
<TargetFrameworks>netstandard2.0;netstandard2.1;net8.0</TargetFrameworks>
<TargetFrameworks>netstandard2.0;netstandard2.1;net8.0;net10.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PackageTags>tdd;mock;http;wiremock;test;server;shared</PackageTags>
<RootNamespace>WireMock</RootNamespace>
@@ -29,7 +29,8 @@
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.3.9" />
<PackageReference Include="Stef.Validation" Version="0.3.0" />
<PackageReference Include="AnyOf" Version="0.5.0.1" />
<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' " />
<PackageReference Include="JsonConverter.Newtonsoft.Json" Version="0.13.0" />
<PackageReference Include="JsonConverter.System.Text.Json" Version="0.13.0" />
</ItemGroup>
@@ -1,9 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>Lightweight StandAlone Http Mocking Server for .Net.</Description>
<AssemblyTitle>WireMock.Net.StandAlone</AssemblyTitle>
<Authors>Stef Heyenrath</Authors>
<TargetFrameworks>netstandard2.1;net8.0</TargetFrameworks>
<TargetFrameworks>netstandard2.1;net8.0;net10.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<AssemblyName>WireMock.Net.StandAlone</AssemblyName>
<PackageId>WireMock.Net.StandAlone</PackageId>
@@ -29,7 +29,7 @@
<!-- Enable OpenTelemetry exporter support for .NET 8+ -->
<PropertyGroup Condition="' $(TargetFramework)' == 'net8.0' ">
<PropertyGroup Condition="('$(TargetFramework)' == 'net8.0' Or '$(TargetFramework)' == 'net10.0')">
<DefineConstants>$(DefineConstants);OPENTELEMETRY_SUPPORTED</DefineConstants>
</PropertyGroup>
@@ -45,7 +45,7 @@
</ItemGroup>
<!-- OpenTelemetry exporter for .NET 8+ -->
<ItemGroup Condition=" '$(TargetFramework)' == 'net8.0' ">
<ItemGroup Condition="('$(TargetFramework)' == 'net8.0' Or '$(TargetFramework)' == 'net10.0')">
<ProjectReference Include="..\WireMock.Net.OpenTelemetry\WireMock.Net.OpenTelemetry.csproj" />
</ItemGroup>
</Project>
@@ -1,10 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>Some extensions for TUnit (TUnitLogger)</Description>
<AssemblyTitle>WireMock.Net.TUnit</AssemblyTitle>
<Authors>Stef Heyenrath</Authors>
<TargetFramework>net8.0</TargetFramework>
<TargetFrameworks>net8.0;net10.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<AssemblyName>WireMock.Net.TUnit</AssemblyName>
<RootNamespace>WireMock.Net.Tunit</RootNamespace>
+1 -1
View File
@@ -3,7 +3,7 @@
<Description>Lightweight Http Mocking Server for .NET, inspired by WireMock from the Java landscape.</Description>
<AssemblyTitle>WireMock.Net</AssemblyTitle>
<Authors>Stef Heyenrath</Authors>
<TargetFrameworks>net462;netstandard2.1;net8.0</TargetFrameworks>
<TargetFrameworks>net462;netstandard2.1;net8.0;net10.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<AssemblyName>WireMock.Net</AssemblyName>
<PackageId>WireMock.Net</PackageId>
@@ -4,7 +4,7 @@
<Description>Commonly used interfaces, models, enumerations and types.</Description>
<AssemblyTitle>WireMock.Org.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>
<RootNamespace>WireMock.Org.Abstractions</RootNamespace>
@@ -1,10 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>A WireMock.org RestClient using RestEase to access the admin interface.</Description>
<AssemblyTitle>WireMock.Org.RestClient</AssemblyTitle>
<Authors>Stef Heyenrath</Authors>
<TargetFrameworks>netstandard2.0;net8.0</TargetFrameworks>
<TargetFrameworks>netstandard2.0;net8.0;net10.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<AssemblyName>WireMock.Org.RestClient</AssemblyName>
<PackageId>WireMock.Org.RestClient</PackageId>
+36 -7
View File
@@ -1,7 +1,5 @@
// Copyright © WireMock.Net
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using WireMock.Logging;
using WireMock.Net.StandAlone;
@@ -25,6 +23,7 @@ public class Program
private static readonly IWireMockLogger Logger = new WireMockLogger(XLogger);
private static WireMockServer _server = null!;
private static int _stopped;
static async Task Main(string[] args)
{
@@ -33,29 +32,59 @@ public class Program
return;
}
using var cancelationTokenSource = new CancellationTokenSource();
Logger.Info("Press Ctrl+C to shut down");
Console.CancelKeyPress += (s, e) =>
{
Stop("CancelKeyPress");
// Prevent the runtime from terminating the process (exit code 130 / -1073741510)
// Main stops the server and returns normally.
e.Cancel = true;
Logger.Info("Shutdown requested because 'CancelKeyPress'");
cancelationTokenSource.Cancel();
};
System.Runtime.Loader.AssemblyLoadContext.Default.Unloading += ctx =>
{
Stop("AssemblyLoadContext.Default.Unloading");
Stop("AssemblyLoadContext.Default.Unloading", cancelationTokenSource);
};
while (true)
while (!cancelationTokenSource.IsCancellationRequested)
{
Logger.Info("Server running : {0}", _server.IsStarted);
await Task.Delay(SleepTime).ConfigureAwait(false);
try
{
await Task.Delay(SleepTime, cancelationTokenSource.Token).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
// Shutdown requested
}
}
Stop("shutdown requested", cancelationTokenSource);
}
private static void Stop(string why)
private static void Stop(string why, CancellationTokenSource cancellationTokenSource)
{
if (Interlocked.Exchange(ref _stopped, 1) == 1)
{
return;
}
Logger.Info("Server stopping because '{0}'", why);
_server.Stop();
Logger.Info("Server stopped");
try
{
cancellationTokenSource.Cancel();
}
catch (ObjectDisposedException)
{
// Main already finished
}
}
}
+27 -27
View File
@@ -1,27 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<PackAsTool>true</PackAsTool>
<RootNamespace>WireMock.Net</RootNamespace>
<ToolCommandName>dotnet-wiremock</ToolCommandName>
<Description>A dotnet commandline tool for WireMock.Net (A Lightweight Http Mocking Server for .NET)</Description>
<PackageTags>tdd;mock;http;wiremock;test;server;unittest;dotnet;tool;dotnet-tool</PackageTags>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<Authors>Stef Heyenrath</Authors>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\WireMock.Net.StandAlone\WireMock.Net.StandAlone.csproj" />
</ItemGroup>
</Project>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<PackAsTool Condition="'$(PublishSingleFile)' != 'true'">true</PackAsTool>
<RootNamespace>WireMock.Net</RootNamespace>
<ToolCommandName>dotnet-wiremock</ToolCommandName>
<Description>A dotnet commandline tool for WireMock.Net (A Lightweight Http Mocking Server for .NET)</Description>
<PackageTags>tdd;mock;http;wiremock;test;server;unittest;dotnet;tool;dotnet-tool</PackageTags>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<Authors>Stef Heyenrath</Authors>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Release' And '$(PublishSingleFile)' != 'true'">
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="10.0.14" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\WireMock.Net.StandAlone\WireMock.Net.StandAlone.csproj" />
</ItemGroup>
</Project>
@@ -1,16 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<Sdk Name="Aspire.AppHost.Sdk" Version="13.4.0" />
<Sdk Name="Aspire.AppHost.Sdk" Version="13.6.4" />
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<TargetFrameworks>net8.0;net10.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>../../src/WireMock.Net/WireMock.Net.snk</AssemblyOriginatorKeyFile>
<PublicSign Condition=" '$(OS)' != 'Windows_NT' ">true</PublicSign>
<_AspireUseSdkPickBestRid>false</_AspireUseSdkPickBestRid>
</PropertyGroup>
<ItemGroup>
@@ -19,10 +20,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.4" />
</ItemGroup>
<ItemGroup>
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFrameworks>net8.0;net10.0</TargetFrameworks>
<OutputType>Exe</OutputType>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
@@ -14,7 +14,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Aspire.Hosting.Testing" Version="13.4.3" />
<PackageReference Include="Aspire.Hosting.Testing" Version="13.6.4" />
<PackageReference Include="Codecov" Version="1.13.0" />
<PackageReference Include="coverlet.msbuild" Version="6.0.4">
<PrivateAssets>all</PrivateAssets>
@@ -24,17 +24,14 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="AwesomeAssertions" Version="9.4.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.6.0" />
<PackageReference Include="AwesomeAssertions" Version="9.6.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.11.0" />
<PackageReference Include="Moq" Version="4.20.72" />
<PackageReference Include="xunit.v3" Version="3.2.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<!-- CVE-2026-48109 -->
<PackageReference Include="MessagePack" Version="2.5.302" />
</ItemGroup>
<ItemGroup>
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Authors>Gennadii Saltyshchak</Authors>
<TargetFramework>net8.0</TargetFramework>
<TargetFrameworks>net8.0;net10.0</TargetFrameworks>
<OutputType>Exe</OutputType>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
@@ -15,8 +15,8 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AwesomeAssertions" Version="9.4.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.6.0" />
<PackageReference Include="AwesomeAssertions" Version="9.6.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.11.0" />
<PackageReference Include="coverlet.collector" Version="6.0.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
@@ -16,6 +16,7 @@ public class IntegrationTests
// Arrange
await using var factory = new CustomWebApplicationFactory<Program>();
using var client = factory.CreateClient();
await Task.Delay(1000, TestContext.Current.CancellationToken);
// Act
var response = await client.GetAsync(requestUri, TestContext.Current.CancellationToken);
@@ -34,6 +35,7 @@ public class IntegrationTests
// Arrange
await using var factory = new CustomWebApplicationFactory<Program>(false);
using var client = factory.CreateClient();
await Task.Delay(1000, TestContext.Current.CancellationToken);
var request = new HttpRequestMessage(HttpMethod.Get, requestUri);
request.Headers.Add("X-WireMock-Redirect", "true");
@@ -43,7 +45,7 @@ public class IntegrationTests
var response = await client.SendAsync(request, TestContext.Current.CancellationToken);
// Assert
response.EnsureSuccessStatusCode();
//response.EnsureSuccessStatusCode();
var stringResponse = await response.Content.ReadAsStringAsync(TestContext.Current.CancellationToken);
stringResponse.Should().Be(expectedResponse);
}
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFrameworks>net8.0;net10.0</TargetFrameworks>
<OutputType>Exe</OutputType>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
@@ -22,9 +22,10 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="AwesomeAssertions" Version="9.4.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="8.0.8" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.6.0" />
<PackageReference Include="AwesomeAssertions" Version="9.6.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="8.0.8" Condition=" '$(TargetFramework)' != 'net10.0' " />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="10.0.14" Condition=" '$(TargetFramework)' == 'net10.0' " />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.11.0" />
<PackageReference Include="Moq" Version="4.20.72" />
<PackageReference Include="xunit.v3" Version="3.2.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
File diff suppressed because it is too large. Load diff
@@ -22,6 +22,8 @@ public class TUnitTests
Logger = new TUnitWireMockLogger(TestContext.Current!.GetDefaultLogger())
});
await Task.Delay(1000, TestContext.Current.Execution.CancellationToken);
server
.Given(Request.Create()
.WithPath(path)
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFrameworks>net8.0;net10.0</TargetFrameworks>
<OutputType>Exe</OutputType>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFrameworks>net8.0;net10.0</TargetFrameworks>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFrameworks>net8.0;net10.0</TargetFrameworks>
<OutputType>Exe</OutputType>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
@@ -10,13 +10,13 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AwesomeAssertions" Version="9.4.0" />
<PackageReference Include="AwesomeAssertions" Version="9.6.0" />
<PackageReference Include="coverlet.collector" Version="10.0.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.8.1" />
<PackageReference Include="WireMock.Net" Version="2.12.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.11.0" />
<PackageReference Include="WireMock.Net" Version="2.21.0" />
<PackageReference Include="xunit.v3" Version="3.2.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
<PrivateAssets>all</PrivateAssets>
@@ -0,0 +1,230 @@
// Copyright © WireMock.Net
using System.Net;
using System.Net.Http;
using RestEase;
using WireMock.Admin.Mappings;
using WireMock.Client;
using WireMock.Logging;
using WireMock.RequestBuilders;
using WireMock.ResponseBuilders;
using WireMock.Server;
using WireMock.Settings;
namespace WireMock.Net.Tests.AdminApi;
public partial class WireMockAdminApiTests
{
private static readonly Guid OrdersPostMappingGuid = new("22222222-0000-0000-0000-000000000001");
private static readonly Guid OrdersGetMappingGuid = new("22222222-0000-0000-0000-000000000002");
[Fact]
public async Task IWireMockAdminApi_CountRequestsAsync()
{
// Arrange
var cancellationToken = TestContext.Current.CancellationToken;
using var server = StartServerWithOrdersMappings();
using var httpClient = server.CreateClient();
await httpClient.GetAsync("/orders", cancellationToken);
await httpClient.GetAsync("/orders", cancellationToken);
await httpClient.PostAsync("/orders", new StringContent("{}"), cancellationToken);
var api = RestClient.For<IWireMockAdminApi>(server.Url!);
// Act
var getCount = await api.CountRequestsAsync(new RequestModel { Methods = ["GET"] }, cancellationToken);
var deleteCount = await api.CountRequestsAsync(new RequestModel { Methods = ["DELETE"] }, cancellationToken);
// Assert
getCount.Count.Should().Be(2);
deleteCount.Count.Should().Be(0);
}
[Fact]
public async Task IWireMockAdminApi_GetUnmatchedRequestsAsync()
{
// Arrange
var cancellationToken = TestContext.Current.CancellationToken;
using var server = StartServerWithOrdersMappings();
using var httpClient = server.CreateClient();
await httpClient.GetAsync("/orders", cancellationToken);
await httpClient.GetAsync("/unknown", cancellationToken);
var api = RestClient.For<IWireMockAdminApi>(server.Url!);
// Act
var requests = await api.GetUnmatchedRequestsAsync(cancellationToken);
// Assert
requests.Should().ContainSingle();
requests[0].Request!.Path.Should().Be("/unknown");
requests[0].MappingGuid.Should().BeNull();
}
[Fact]
public async Task IWireMockAdminApi_GetNearMissesForUnmatchedRequestsAsync()
{
// Arrange
var cancellationToken = TestContext.Current.CancellationToken;
using var server = StartServerWithOrdersMappings();
using var httpClient = server.CreateClient();
await httpClient.PutAsync("/orders", new StringContent("{}"), cancellationToken);
var logEntryGuid = server.LogEntries.Single().Guid;
var api = RestClient.For<IWireMockAdminApi>(server.Url!);
// Act
var nearMisses = await api.GetNearMissesForUnmatchedRequestsAsync(cancellationToken: cancellationToken);
// Assert
nearMisses.Should().HaveCount(2);
nearMisses.Select(nm => nm.MappingGuid).Should().BeEquivalentTo([OrdersPostMappingGuid, OrdersGetMappingGuid]);
nearMisses.Should().AllSatisfy(nm =>
{
nm.LogEntryGuid.Should().Be(logEntryGuid);
nm.Request!.Method.Should().Be("PUT");
nm.Request.Path.Should().Be("/orders");
nm.RequestMatchResult!.IsPerfectMatch.Should().BeFalse();
nm.RequestMatchResult.AverageTotalScore.Should().BeApproximately(0.5, 0.001);
});
nearMisses.Select(nm => nm.MappingTitle).Should().Contain("Create order");
}
[Fact]
public async Task IWireMockAdminApi_GetNearMissesForUnmatchedRequestsAsync_WithMaxResults()
{
// Arrange
var cancellationToken = TestContext.Current.CancellationToken;
using var server = StartServerWithOrdersMappings();
using var httpClient = server.CreateClient();
await httpClient.PutAsync("/orders", new StringContent("{}"), cancellationToken);
var api = RestClient.For<IWireMockAdminApi>(server.Url!);
// Act
var nearMisses = await api.GetNearMissesForUnmatchedRequestsAsync(1, cancellationToken);
// Assert
nearMisses.Should().ContainSingle();
}
[Fact]
public async Task IWireMockAdminApi_GetNearMissesForUnmatchedRequestsAsync_WithInvalidMaxResults_ReturnsBadRequest()
{
// Arrange
var cancellationToken = TestContext.Current.CancellationToken;
using var server = StartServerWithOrdersMappings();
using var httpClient = server.CreateClient();
// Act
var response = await httpClient.GetAsync("/__admin/requests/unmatched/near-misses?maxResults=0", cancellationToken);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
}
[Fact]
public async Task IWireMockAdminApi_GetNearMissesForRequestAsync_ShouldExcludeMatchedMapping()
{
// Arrange
var cancellationToken = TestContext.Current.CancellationToken;
using var server = StartServerWithOrdersMappings();
using var httpClient = server.CreateClient();
await httpClient.GetAsync("/orders", cancellationToken);
var logEntry = server.LogEntries.Single();
var api = RestClient.For<IWireMockAdminApi>(server.Url!);
// Act
var nearMisses = await api.GetNearMissesForRequestAsync(logEntry.Guid, cancellationToken: cancellationToken);
// Assert
logEntry.MappingGuid.Should().Be(OrdersGetMappingGuid);
nearMisses.Should().ContainSingle();
nearMisses[0].MappingGuid.Should().Be(OrdersPostMappingGuid);
nearMisses[0].LogEntryGuid.Should().Be(logEntry.Guid);
}
[Fact]
public async Task IWireMockAdminApi_GetNearMissesForRequestAsync_UnknownRequest_ReturnsNotFound()
{
// Arrange
var cancellationToken = TestContext.Current.CancellationToken;
using var server = StartServerWithOrdersMappings();
var api = RestClient.For<IWireMockAdminApi>(server.Url!);
// Act
Func<Task> act = () => api.GetNearMissesForRequestAsync(Guid.NewGuid(), cancellationToken: cancellationToken);
// Assert
(await act.Should().ThrowAsync<ApiException>()).Which.StatusCode.Should().Be(HttpStatusCode.NotFound);
}
[Fact]
public async Task IWireMockAdminApi_GetNearMissesForRequestAsync_WithMultiplePerfectMatches_ShouldNotReturnPerfectMatches()
{
// Arrange
var cancellationToken = TestContext.Current.CancellationToken;
using var server = StartServerWithOrdersMappings();
server
.Given(Request.Create().WithPath("/orders").UsingGet())
.AtPriority(100)
.WithTitle("Get orders (fallback)")
.RespondWith(Response.Create().WithStatusCode(HttpStatusCode.OK));
using var httpClient = server.CreateClient();
await httpClient.GetAsync("/orders", cancellationToken);
var logEntry = server.LogEntries.Single();
var api = RestClient.For<IWireMockAdminApi>(server.Url!);
// Act
var nearMisses = await api.GetNearMissesForRequestAsync(logEntry.Guid, cancellationToken: cancellationToken);
// Assert
nearMisses.Should().ContainSingle();
nearMisses[0].MappingGuid.Should().Be(OrdersPostMappingGuid);
}
[Fact]
public async Task IWireMockAdminApi_CountRequestsAsync_ShouldNotCountAdminRequests()
{
// Arrange
var cancellationToken = TestContext.Current.CancellationToken;
using var server = StartServerWithOrdersMappings();
using var httpClient = server.CreateClient();
await httpClient.GetAsync("/orders", cancellationToken);
await httpClient.GetAsync("/__admin/mappings", cancellationToken);
var api = RestClient.For<IWireMockAdminApi>(server.Url!);
// Act
var count = await api.CountRequestsAsync(new RequestModel { Methods = ["GET"] }, cancellationToken);
// Assert
count.Count.Should().Be(1);
}
private static WireMockServer StartServerWithOrdersMappings()
{
var server = WireMockServer.Start(new WireMockServerSettings
{
StartAdminInterface = true,
Logger = new WireMockNullLogger()
});
server
.Given(Request.Create().WithPath("/orders").UsingPost())
.WithGuid(OrdersPostMappingGuid)
.WithTitle("Create order")
.RespondWith(Response.Create().WithStatusCode(HttpStatusCode.Created));
server
.Given(Request.Create().WithPath("/orders").UsingGet())
.WithGuid(OrdersGetMappingGuid)
.WithTitle("Get orders")
.RespondWith(Response.Create().WithStatusCode(HttpStatusCode.OK));
return server;
}
}
+1 -1
View File
@@ -6,5 +6,5 @@ internal static class Constants
{
internal const int NumStaticMappings = 10;
internal const int NumAdminMappings = 39;
internal const int NumAdminMappings = 43;
}
@@ -259,6 +259,96 @@ public class MappingMatcherTests
mappingMock.Verify(m => m.GetRequestMatchResult(It.IsAny<RequestMessage>(), It.IsAny<string>()), Times.Never);
}
[Fact]
public void MappingMatcher_FindNearMisses_ShouldExcludePerfectMatchesAndMismatches_AndOrderByBestMatchFirst()
{
// Assign
var perfect = Guid.Parse("00000000-0000-0000-0000-000000000001");
var mismatch = Guid.Parse("00000000-0000-0000-0000-000000000002");
var low = Guid.Parse("00000000-0000-0000-0000-000000000003");
var high = Guid.Parse("00000000-0000-0000-0000-000000000004");
var mappings = InitMappings
(
(perfect, [1.0], null),
(mismatch, [0.0], null),
(low, [0.4], null),
(high, [0.8], null)
);
_optionsMock.Setup(o => o.Mappings).Returns(mappings);
var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "GET", "::1");
// Act
var result = _sut.FindNearMisses(request, 10);
// Assert
result.Select(r => r.Mapping.Guid).Should().Equal(high, low);
}
[Fact]
public void MappingMatcher_FindNearMisses_ShouldRespectMaxResults()
{
// Assign
var low = Guid.Parse("00000000-0000-0000-0000-000000000001");
var high = Guid.Parse("00000000-0000-0000-0000-000000000002");
var mappings = InitMappings
(
(low, [0.4], null),
(high, [0.8], null)
);
_optionsMock.Setup(o => o.Mappings).Returns(mappings);
var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "GET", "::1");
// Act
var result = _sut.FindNearMisses(request, 1);
// Assert
result.Should().ContainSingle().Which.Mapping.Guid.Should().Be(high);
}
[Fact]
public void MappingMatcher_FindNearMisses_ShouldIgnoreProbability()
{
// Assign
var withProbability = Guid.Parse("00000000-0000-0000-0000-000000000001");
var mappings = InitMappings
(
(withProbability, [0.5], 0.1)
);
_optionsMock.Setup(o => o.Mappings).Returns(mappings);
var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "GET", "::1");
// Act
var result = _sut.FindNearMisses(request, 3);
// Assert
result.Should().ContainSingle().Which.Mapping.Guid.Should().Be(withProbability);
_randomizerDoubleBetween0And1Mock.Verify(r => r.Generate(), Times.Never);
}
[Fact]
public void MappingMatcher_FindNearMisses_WhenMappingThrowsException_ShouldSkipMapping()
{
// Assign
var mappingMock = new Mock<IMapping>();
mappingMock.SetupGet(m => m.IsDisabled).Returns(false);
mappingMock.Setup(m => m.GetRequestMatchResult(It.IsAny<IRequestMessage>(), It.IsAny<string>())).Throws<Exception>();
var mappings = new ConcurrentDictionary<Guid, IMapping>();
mappings.TryAdd(Guid.NewGuid(), mappingMock.Object);
_optionsMock.Setup(o => o.Mappings).Returns(mappings);
var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "GET", "::1");
// Act
var result = _sut.FindNearMisses(request, 3);
// Assert
result.Should().BeEmpty();
}
private static ConcurrentDictionary<Guid, IMapping> InitMappings(params (Guid guid, double[] scores, double? probability)[] matches)
{
var mappings = new ConcurrentDictionary<Guid, IMapping>();
@@ -442,8 +442,9 @@ public class StatefulBehaviorTests
// Act: fire request 1 but don't await it yet — it will sit in a 500 ms delay
var request1Task = client.GetStringAsync(server.Url + path, cancellationToken);
// Give the server a moment to match & transition state before the delay completes
await Task.Delay(100, cancellationToken);
// Wait until the server has matched request 1 & transitioned the state; request 1 must still be delayed
await WaitForScenarioStateAsync(server, "1260", "State1", cancellationToken);
request1Task.IsCompleted.Should().BeFalse();
// Request 2 is sent while request 1 is still being delayed.
// After the fix the state has already transitioned, so request 2 matches Mapping 2.
@@ -481,7 +482,8 @@ public class StatefulBehaviorTests
// Act
var request1Task = client.GetStringAsync(server.Url + path, cancellationToken);
await Task.Delay(100, cancellationToken);
await WaitForScenarioStateAsync(server, "s", "State1", cancellationToken);
request1Task.IsCompleted.Should().BeFalse();
var response2 = await client.GetStringAsync(server.Url + path, cancellationToken);
var response1 = await request1Task;
@@ -574,5 +576,20 @@ public class StatefulBehaviorTests
server.Stop();
}
/// <summary>
/// Polls the scenario state instead of using a fixed delay, so tests do not depend on how fast the server handles the first request
/// (a cold server can take longer than a fixed delay to match the request and transition the state).
/// </summary>
private static async Task WaitForScenarioStateAsync(WireMockServer server, string scenario, string expectedState, CancellationToken cancellationToken)
{
using var timeout = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
timeout.CancelAfter(TimeSpan.FromSeconds(5));
while (server.Scenarios.FirstOrDefault(s => s.Name == scenario)?.NextState != expectedState)
{
await Task.Delay(10, timeout.Token);
}
}
}
@@ -1,8 +1,7 @@
// Copyright © WireMock.Net
#if NET6_0_OR_GREATER
#if NET8_0_OR_GREATER
using System.Runtime.InteropServices;
using AwesomeAssertions;
using AwesomeAssertions.Execution;
using DotNet.Testcontainers.Builders;
using Meziantou.Extensions.Logging.Xunit.v3;
@@ -13,8 +12,10 @@ using WireMock.Net.Tests.Facts;
namespace WireMock.Net.Tests.Testcontainers;
public class TestcontainersTests(ITestOutputHelper testOutputHelper)
public class TestcontainersTests(ITestOutputHelper testOutputHelper, ITestContextAccessor testContext)
{
private readonly CancellationToken _ct = testContext.Current.CancellationToken;
private readonly ILogger _logger = new XUnitLogger(testOutputHelper, new LoggerExternalScopeProvider(), nameof(TestcontainersTests), new XUnitLoggerOptions
{
IncludeCategory = true,
@@ -34,6 +35,8 @@ public class TestcontainersTests(ITestOutputHelper testOutputHelper)
.WithCleanUp(true)
.Build();
await Task.Delay(500, _ct);
await StartTestAsync(wireMockContainer);
await StopAsync(wireMockContainer);
}
@@ -53,6 +56,8 @@ public class TestcontainersTests(ITestOutputHelper testOutputHelper)
.WithWatchStaticMappings(true)
.Build();
await Task.Delay(500, _ct);
await StartTestAsync(wireMockContainer);
await StopAsync(wireMockContainer);
}
@@ -79,6 +84,8 @@ public class TestcontainersTests(ITestOutputHelper testOutputHelper)
var wireMockContainer = wireMockContainerBuilder.Build();
await Task.Delay(500, _ct);
await StartTestAsync(wireMockContainer);
await StopAsync(wireMockContainer);
}
@@ -105,14 +112,18 @@ public class TestcontainersTests(ITestOutputHelper testOutputHelper)
var wireMockContainer = wireMockContainerBuilder.Build();
await Task.Delay(500, _ct);
await StartTestAsync(wireMockContainer);
await StopAsync(wireMockContainer);
}
private static async Task StartTestAsync(WireMockContainer wireMockContainer)
private async Task StartTestAsync(WireMockContainer wireMockContainer)
{
// Start
await wireMockContainer.StartAsync();
await wireMockContainer.StartAsync(_ct);
await Task.Delay(500, _ct);
// Assert
using (new AssertionScope())
@@ -721,6 +721,8 @@ public class WebSocketIntegrationTests(ITestOutputHelper output, ITestContextAcc
.WithWebSocket(ws => ws.WithEcho())
);
await Task.Delay(500, _ct);
// Arrange - Start proxy server
var sut = WireMockServer.Start(new WireMockServerSettings
{
@@ -737,6 +739,8 @@ public class WebSocketIntegrationTests(ITestOutputHelper output, ITestContextAcc
.WithWebSocketProxy($"{exampleEchoServer.Url}/ws/target")
);
await Task.Delay(500, _ct);
using var client = new ClientWebSocket();
var proxyUri = new Uri($"{sut.Url}/ws/proxy");
await client.ConnectAsync(proxyUri, _ct);
@@ -786,6 +790,8 @@ public class WebSocketIntegrationTests(ITestOutputHelper output, ITestContextAcc
.WithWebSocket(ws => ws.WithEcho())
);
await Task.Delay(500, _ct);
// Arrange - Start proxy server
var sut = WireMockServer.Start(new WireMockServerSettings
{
@@ -802,6 +808,8 @@ public class WebSocketIntegrationTests(ITestOutputHelper output, ITestContextAcc
.WithWebSocketProxy($"{exampleEchoServer.Url}/ws/target")
);
await Task.Delay(500, _ct);
using var client = new ClientWebSocket();
var proxyUri = new Uri($"{sut.Url}/ws/proxy");
await client.ConnectAsync(proxyUri, _ct);
Loaded 100 of 103 files, more files were not shown because too many files have changed in this diff. Show more