mirror of
https://github.com/wiremock/WireMock.Net.git
synced 2026-05-05 14:43:21 +02:00
Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4bb378bdce | ||
|
|
f9df0d0ee9 | ||
|
|
f8d3b51fbc | ||
|
|
be9864461d | ||
|
|
4bb7e8af45 | ||
|
|
8bf42904ab | ||
|
|
0a48b40021 | ||
|
|
1962437dcd | ||
|
|
85d61a1877 | ||
|
|
885911203b | ||
|
|
1e591d5f8a | ||
|
|
02b7e3744e | ||
|
|
6e2a4d7e04 | ||
|
|
479bb0b8ec | ||
|
|
a453e00fdb | ||
|
|
3214c2ebc7 | ||
|
|
6c6a42979e | ||
|
|
b4f5b9256c | ||
|
|
070e4b6ab9 | ||
|
|
f919929cb7 | ||
|
|
cdd33695e5 | ||
|
|
c4caa25eb6 | ||
|
|
ca788cb9b0 | ||
|
|
d08ce944b6 | ||
|
|
0a9f37e857 |
4
.github/copilot-instructions.md
vendored
4
.github/copilot-instructions.md
vendored
@@ -1,4 +0,0 @@
|
|||||||
# Copilot Instructions
|
|
||||||
|
|
||||||
## Project Guidelines
|
|
||||||
- All new byte[xx] calls should use using var data = ArrayPool<byte>.Shared.Lease(xx); instead of directly allocating byte arrays
|
|
||||||
15
.github/instructions/instructions.md
vendored
Normal file
15
.github/instructions/instructions.md
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
---
|
||||||
|
description: 'Guidelines for this solution'
|
||||||
|
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.
|
||||||
|
|
||||||
|
|
||||||
|
# C# Guidelines
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
- When a new ByteArray is needed, do not use `var data = new byte[bufferSize];`. Always use `var data = ArrayPool<byte>.Shared.Lease(bufferSize);`.
|
||||||
36
CHANGELOG.md
36
CHANGELOG.md
@@ -1,3 +1,35 @@
|
|||||||
|
# 2.5.0 (04 May 2026)
|
||||||
|
- [#1451](https://github.com/wiremock/WireMock.Net/pull/1451) - Feature/early mismatch [feature] contributed by [Stepami](https://github.com/Stepami)
|
||||||
|
- [#1452](https://github.com/wiremock/WireMock.Net/pull/1452) - Bump log4net from 2.0.15 to 3.3.0 in example console app [dependencies, .NET] contributed by [dependabot[bot]](https://github.com/apps/dependabot)
|
||||||
|
- [#1453](https://github.com/wiremock/WireMock.Net/pull/1453) - Fix CVE-2026-40021: upgrade log4net to 3.3.0 in examples/WireMock.Net.Service/packages.config [dependencies] contributed by [Copilot](https://github.com/apps/copilot-swe-agent)
|
||||||
|
- [#1442](https://github.com/wiremock/WireMock.Net/issues/1442) - Bug: [grpc] WithBodyAsProtoBuf exception on match [bug]
|
||||||
|
|
||||||
|
# 2.4.0 (24 April 2026)
|
||||||
|
- [#1437](https://github.com/wiremock/WireMock.Net/pull/1437) - Added feature to enable and disable mappings [feature] contributed by [jayaraman-venkatesan](https://github.com/jayaraman-venkatesan)
|
||||||
|
- [#1450](https://github.com/wiremock/WireMock.Net/pull/1450) - Bump OpenTelemetry.Exporter.OpenTelemetryProtocol from 1.14.0 to 1.15.x [dependencies, .NET] contributed by [dependabot[bot]](https://github.com/apps/dependabot)
|
||||||
|
- [#1421](https://github.com/wiremock/WireMock.Net/issues/1421) - Deactivate mapping without deleting it [feature]
|
||||||
|
|
||||||
|
# 2.3.0 (20 April 2026)
|
||||||
|
- [#1436](https://github.com/wiremock/WireMock.Net/pull/1436) - Moving Scenario state change before global response delay is set [feature] contributed by [jayaraman-venkatesan](https://github.com/jayaraman-venkatesan)
|
||||||
|
- [#1440](https://github.com/wiremock/WireMock.Net/pull/1440) - Bump log4net from 2.0.15 to 3.3.0 [dependencies] contributed by [dependabot[bot]](https://github.com/apps/dependabot)
|
||||||
|
- [#1443](https://github.com/wiremock/WireMock.Net/pull/1443) - Fix ExactMatcher and JsonMatcher not working for ISO dates as string [bug] contributed by [StefH](https://github.com/StefH)
|
||||||
|
- [#1444](https://github.com/wiremock/WireMock.Net/pull/1444) - Update instructions.md [refactor] contributed by [StefH](https://github.com/StefH)
|
||||||
|
- [#1448](https://github.com/wiremock/WireMock.Net/pull/1448) - Use DefaultJsonSerializer for BodyAsJson-Response [bug] contributed by [StefH](https://github.com/StefH)
|
||||||
|
- [#1205](https://github.com/wiremock/WireMock.Net/issues/1205) - System.Private.Uri 4.3.0 Blackduck security High vulnerability [bug]
|
||||||
|
- [#1260](https://github.com/wiremock/WireMock.Net/issues/1260) - Scenario state change before a response delay timeout ends [bug]
|
||||||
|
- [#1441](https://github.com/wiremock/WireMock.Net/issues/1441) - ExactMatcher and JsonMatcher not working for ISO dates in v2.2.0 [bug]
|
||||||
|
- [#1446](https://github.com/wiremock/WireMock.Net/issues/1446) - WithBodyAsJson does not return correctly formatted json when using SystemTextJsonConverter [bug]
|
||||||
|
|
||||||
|
# 2.2.0 (30 March 2026)
|
||||||
|
- [#1433](https://github.com/wiremock/WireMock.Net/pull/1433) - Add comments for ScenarioStateStore related code [refactor] contributed by [StefH](https://github.com/StefH)
|
||||||
|
- [#1434](https://github.com/wiremock/WireMock.Net/pull/1434) - Upgrade Scriban.Signed [security] contributed by [StefH](https://github.com/StefH)
|
||||||
|
|
||||||
|
# 2.1.0 (29 March 2026)
|
||||||
|
- [#1425](https://github.com/wiremock/WireMock.Net/pull/1425) - Add helpers for query params fluent MappingModelBuilder [feature] contributed by [biltongza](https://github.com/biltongza)
|
||||||
|
- [#1430](https://github.com/wiremock/WireMock.Net/pull/1430) - Add injectable IScenarioStateStore for distributed scenario state [feature] contributed by [m4tchl0ck](https://github.com/m4tchl0ck)
|
||||||
|
- [#1431](https://github.com/wiremock/WireMock.Net/pull/1431) - Fix WireMockLogger implementation in dotnet-WireMock [bug] contributed by [StefH](https://github.com/StefH)
|
||||||
|
- [#1432](https://github.com/wiremock/WireMock.Net/pull/1432) - Add WireMockAspNetCoreLogger to log Kestrel warnings/errors [feature] contributed by [StefH](https://github.com/StefH)
|
||||||
|
|
||||||
# 2.0.0 (11 March 2026)
|
# 2.0.0 (11 March 2026)
|
||||||
- [#1359](https://github.com/wiremock/WireMock.Net/pull/1359) - Version 2.x contributed by [StefH](https://github.com/StefH)
|
- [#1359](https://github.com/wiremock/WireMock.Net/pull/1359) - Version 2.x contributed by [StefH](https://github.com/StefH)
|
||||||
- [#1394](https://github.com/wiremock/WireMock.Net/pull/1394) - MappingSerializer (Newtonsoft or SystemText)-Json [feature] contributed by [StefH](https://github.com/StefH)
|
- [#1394](https://github.com/wiremock/WireMock.Net/pull/1394) - MappingSerializer (Newtonsoft or SystemText)-Json [feature] contributed by [StefH](https://github.com/StefH)
|
||||||
@@ -1356,11 +1388,11 @@
|
|||||||
- [#145](https://github.com/wiremock/WireMock.Net/pull/145) - Cancellation token not passed to server instance in .NET Core 2 [bug] contributed by [Bob11327](https://github.com/Bob11327)
|
- [#145](https://github.com/wiremock/WireMock.Net/pull/145) - Cancellation token not passed to server instance in .NET Core 2 [bug] contributed by [Bob11327](https://github.com/Bob11327)
|
||||||
|
|
||||||
# 1.0.3.18 (25 May 2018)
|
# 1.0.3.18 (25 May 2018)
|
||||||
- [#142](https://github.com/wiremock/WireMock.Net/pull/142) - Allow all headers to be set as Response headers contributed by [StefH](https://github.com/StefH)
|
- [#142](https://github.com/wiremock/WireMock.Net/pull/142) - Allow all headers to be set as Response headers [bug] contributed by [StefH](https://github.com/StefH)
|
||||||
- [#122](https://github.com/wiremock/WireMock.Net/issues/122) - WireMock.Net not responding in unit tests - same works in console application
|
- [#122](https://github.com/wiremock/WireMock.Net/issues/122) - WireMock.Net not responding in unit tests - same works in console application
|
||||||
- [#126](https://github.com/wiremock/WireMock.Net/issues/126) - Question: UsingHead always returns 0 for Content-Length header even when explicitly specified
|
- [#126](https://github.com/wiremock/WireMock.Net/issues/126) - Question: UsingHead always returns 0 for Content-Length header even when explicitly specified
|
||||||
- [#132](https://github.com/wiremock/WireMock.Net/issues/132) - LogEntries not being recorded on subsequent tests
|
- [#132](https://github.com/wiremock/WireMock.Net/issues/132) - LogEntries not being recorded on subsequent tests
|
||||||
- [#136](https://github.com/wiremock/WireMock.Net/issues/136) - Question: Does the WireMock send Content-Length response header
|
- [#136](https://github.com/wiremock/WireMock.Net/issues/136) - Question: Does the WireMock send Content-Length response header [bug]
|
||||||
- [#137](https://github.com/wiremock/WireMock.Net/issues/137) - Question: How to specify Transfer-Encoding response header?
|
- [#137](https://github.com/wiremock/WireMock.Net/issues/137) - Question: How to specify Transfer-Encoding response header?
|
||||||
- [#139](https://github.com/wiremock/WireMock.Net/issues/139) - Wiki link https://github.com/StefH/WireMock.Net/wiki/Record-(via-proxy)-and-Save is dead
|
- [#139](https://github.com/wiremock/WireMock.Net/issues/139) - Wiki link https://github.com/StefH/WireMock.Net/wiki/Record-(via-proxy)-and-Save is dead
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<VersionPrefix>2.0.0</VersionPrefix>
|
<VersionPrefix>2.5.0</VersionPrefix>
|
||||||
<PackageIcon>WireMock.Net-Logo.png</PackageIcon>
|
<PackageIcon>WireMock.Net-Logo.png</PackageIcon>
|
||||||
<PackageProjectUrl>https://github.com/wiremock/WireMock.Net</PackageProjectUrl>
|
<PackageProjectUrl>https://github.com/wiremock/WireMock.Net</PackageProjectUrl>
|
||||||
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
|
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
rem https://github.com/StefH/GitHubReleaseNotes
|
rem https://github.com/StefH/GitHubReleaseNotes
|
||||||
|
|
||||||
SET version=2.0.0
|
SET version=2.5.0
|
||||||
|
|
||||||
GitHubReleaseNotes --output CHANGELOG.md --skip-empty-releases --exclude-labels wontfix test question invalid doc duplicate example environment --version %version% --token %GH_TOKEN%
|
GitHubReleaseNotes --output CHANGELOG.md --skip-empty-releases --exclude-labels wontfix test question invalid doc duplicate example environment --version %version% --token %GH_TOKEN%
|
||||||
|
|
||||||
GitHubReleaseNotes --output PackageReleaseNotes.txt --skip-empty-releases --exclude-labels 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%
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
# 2.0.0 (11 March 2026)
|
# 2.5.0 (04 May 2026)
|
||||||
- #1359 Version 2.x
|
- #1451 Feature/early mismatch [feature]
|
||||||
- #1394 MappingSerializer (Newtonsoft or SystemText)-Json [feature]
|
- #1452 Bump log4net from 2.0.15 to 3.3.0 in example console app [dependencies, .NET]
|
||||||
- #1341 Configurable JSON serialization support (Newtonsoft.Json vs System.Text.Json) [feature]
|
- #1453 Fix CVE-2026-40021: upgrade log4net to 3.3.0 in examples/WireMock.Net.Service/packages.config [dependencies]
|
||||||
- #1422 WireMock.Net seems to be incompatible with Microsoft.Owin.Security.Interop [bug]
|
- #1442 Bug: [grpc] WithBodyAsProtoBuf exception on match [bug]
|
||||||
- #1424 WireMock.Net.FluentAssertions is incompatible with WireMock.Net.Aspire [feature]
|
|
||||||
|
|
||||||
The full release notes can be found here: https://github.com/wiremock/WireMock.Net/blob/master/CHANGELOG.md
|
The full release notes can be found here: https://github.com/wiremock/WireMock.Net/blob/master/CHANGELOG.md
|
||||||
@@ -71,6 +71,7 @@ A C# .NET version based on [mock4net](https://github.com/alexvictoor/mock4net) w
|
|||||||
| **WireMock.Net.OpenTelemetry** | [](https://www.nuget.org/packages/WireMock.Net.ProtoBuf) | [](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.OpenTelemetry)
|
| **WireMock.Net.OpenTelemetry** | [](https://www.nuget.org/packages/WireMock.Net.ProtoBuf) | [](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.OpenTelemetry)
|
||||||
| | | |
|
| | | |
|
||||||
| **WireMock.Net.RestClient** | [](https://www.nuget.org/packages/WireMock.Net.RestClient) | [](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.RestClient)
|
| **WireMock.Net.RestClient** | [](https://www.nuget.org/packages/WireMock.Net.RestClient) | [](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.RestClient)
|
||||||
|
| **WireMock.Net.RestClient.AwesomeAssertions** | [](https://www.nuget.org/packages/WireMock.Net.RestClient.AwesomeAssertions) | [](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.RestClient.AwesomeAssertions)
|
||||||
| **WireMock.Org.RestClient** | [](https://www.nuget.org/packages/WireMock.Org.RestClient) | [](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Org.RestClient)
|
| **WireMock.Org.RestClient** | [](https://www.nuget.org/packages/WireMock.Org.RestClient) | [](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Org.RestClient)
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
|
|||||||
@@ -12,11 +12,11 @@
|
|||||||
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Http.Resilience" Version="8.4.0" />
|
<PackageReference Include="Microsoft.Extensions.Http.Resilience" Version="8.4.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.ServiceDiscovery" Version="8.0.0" />
|
<PackageReference Include="Microsoft.Extensions.ServiceDiscovery" Version="8.0.0" />
|
||||||
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.8.1" />
|
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.15.3" />
|
||||||
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.8.1" />
|
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.15.3" />
|
||||||
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.8.1" />
|
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.15.2" />
|
||||||
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.8.1" />
|
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.15.1" />
|
||||||
<PackageReference Include="OpenTelemetry.Instrumentation.Runtime" Version="1.8.0" />
|
<PackageReference Include="OpenTelemetry.Instrumentation.Runtime" Version="1.15.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -82,6 +82,51 @@ class Program
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
mappingBuilder.Given(m => m
|
||||||
|
.WithRequest(req => req
|
||||||
|
.WithPath("/testRequestWithQueryParams")
|
||||||
|
.UsingGet()
|
||||||
|
.WithParams(p => p.WithParam("param1", pb => pb.WithExactMatcher("value1")))
|
||||||
|
).WithResponse(rsp => rsp
|
||||||
|
.WithHeaders(h => h.Add("Content-Type", "application/json"))
|
||||||
|
.WithStatusCode(200)
|
||||||
|
.WithBodyAsJson(new
|
||||||
|
{
|
||||||
|
status = "ok"
|
||||||
|
}, true)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
mappingBuilder.Given(m => m
|
||||||
|
.WithRequest(req => req
|
||||||
|
.WithPath("/testRequestWithHeaders")
|
||||||
|
.UsingGet()
|
||||||
|
.WithHeaders(h => h.WithHeader("Accept", hb => hb.WithExactMatcher("application/json")))
|
||||||
|
).WithResponse(rsp => rsp
|
||||||
|
.WithHeaders(h => h.Add("Content-Type", "application/json"))
|
||||||
|
.WithStatusCode(200)
|
||||||
|
.WithBodyAsJson(new
|
||||||
|
{
|
||||||
|
status = "ok"
|
||||||
|
}, true)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
mappingBuilder.Given(m => m
|
||||||
|
.WithRequest(req => req
|
||||||
|
.WithPath("/testRequestWithCookie")
|
||||||
|
.UsingGet()
|
||||||
|
.WithCookies(c => c.WithCookie("cookie1", cb => cb.WithExactMatcher("cookievalue1")))
|
||||||
|
).WithResponse(rsp => rsp
|
||||||
|
.WithHeaders(h => h.Add("Content-Type", "application/json"))
|
||||||
|
.WithStatusCode(200)
|
||||||
|
.WithBodyAsJson(new
|
||||||
|
{
|
||||||
|
status = "ok"
|
||||||
|
}, true)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
var result = await mappingBuilder.BuildAndPostAsync().ConfigureAwait(false);
|
var result = await mappingBuilder.BuildAndPostAsync().ConfigureAwait(false);
|
||||||
Console.WriteLine($"result = {JsonConvert.SerializeObject(result)}");
|
Console.WriteLine($"result = {JsonConvert.SerializeObject(result)}");
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\src\WireMock.Net\WireMock.Net.csproj" />
|
<ProjectReference Include="..\..\src\WireMock.Net\WireMock.Net.csproj" />
|
||||||
<PackageReference Include="log4net" Version="2.0.15" />
|
<PackageReference Include="log4net" Version="3.3.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ using System.Linq;
|
|||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
using SharpYaml.Model;
|
||||||
using WireMock.Logging;
|
using WireMock.Logging;
|
||||||
using WireMock.Matchers;
|
using WireMock.Matchers;
|
||||||
using WireMock.Models;
|
using WireMock.Models;
|
||||||
@@ -288,7 +289,24 @@ namespace WireMock.Net.ConsoleApplication
|
|||||||
|
|
||||||
var todos = new Dictionary<int, Todo>();
|
var todos = new Dictionary<int, Todo>();
|
||||||
|
|
||||||
var server = WireMockServer.Start();
|
var server = WireMockServer.Start(new WireMockServerSettings
|
||||||
|
{
|
||||||
|
Logger = new WireMockConsoleLogger(),
|
||||||
|
|
||||||
|
Port = 9091
|
||||||
|
});
|
||||||
|
|
||||||
|
server
|
||||||
|
.WhenRequest(r => r
|
||||||
|
.WithPath("/Content-Length")
|
||||||
|
.UsingAnyMethod()
|
||||||
|
)
|
||||||
|
.ThenRespondWith(r => r
|
||||||
|
.WithStatusCode(HttpStatusCode.OK)
|
||||||
|
.WithHeader("Content-Length", "42")
|
||||||
|
);
|
||||||
|
|
||||||
|
System.Console.ReadLine();
|
||||||
|
|
||||||
//server
|
//server
|
||||||
// .Given(Request.Create()
|
// .Given(Request.Create()
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\src\WireMock.Net\WireMock.Net.csproj" />
|
<ProjectReference Include="..\..\src\WireMock.Net\WireMock.Net.csproj" />
|
||||||
<PackageReference Include="log4net" Version="2.0.15" />
|
<PackageReference Include="log4net" Version="3.3.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -8,9 +8,9 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="OpenTelemetry.Exporter.Console" Version="1.14.0" />
|
<PackageReference Include="OpenTelemetry.Exporter.Console" Version="1.15.3" />
|
||||||
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.14.0" />
|
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.15.1" />
|
||||||
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.14.0" />
|
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.15.3" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<packages>
|
<packages>
|
||||||
<package id="log4net" version="2.0.15" targetFramework="net48" />
|
<package id="log4net" version="3.3.0" targetFramework="net48" />
|
||||||
<package id="Microsoft.AspNet.WebApi.Client" version="5.2.3" targetFramework="net452" />
|
<package id="Microsoft.AspNet.WebApi.Client" version="5.2.3" targetFramework="net452" />
|
||||||
<package id="Microsoft.AspNet.WebApi.Core" version="5.2.3" targetFramework="net452" />
|
<package id="Microsoft.AspNet.WebApi.Core" version="5.2.3" targetFramework="net452" />
|
||||||
<package id="Microsoft.AspNet.WebApi.Owin" version="5.2.3" targetFramework="net452" />
|
<package id="Microsoft.AspNet.WebApi.Owin" version="5.2.3" targetFramework="net452" />
|
||||||
|
|||||||
@@ -1,29 +1,18 @@
|
|||||||
// Copyright © WireMock.Net
|
// Copyright © WireMock.Net
|
||||||
|
|
||||||
using Microsoft.Extensions.Hosting;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace WireMock.Net.WebApplication;
|
namespace WireMock.Net.WebApplication;
|
||||||
|
|
||||||
public class App : IHostedService
|
public class App(IWireMockService service) : IHostedService
|
||||||
{
|
{
|
||||||
private readonly IWireMockService _service;
|
|
||||||
|
|
||||||
public App(IWireMockService service)
|
|
||||||
{
|
|
||||||
_service = service;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task StartAsync(CancellationToken cancellationToken)
|
public Task StartAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
_service.Start();
|
service.Start();
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task StopAsync(CancellationToken cancellationToken)
|
public Task StopAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
_service.Stop();
|
service.Stop();
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,5 @@
|
|||||||
// Copyright © WireMock.Net
|
// Copyright © WireMock.Net
|
||||||
|
|
||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Microsoft.Extensions.Hosting;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using WireMock.Settings;
|
using WireMock.Settings;
|
||||||
|
|
||||||
namespace WireMock.Net.WebApplication;
|
namespace WireMock.Net.WebApplication;
|
||||||
|
|||||||
@@ -15,7 +15,6 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\src\WireMock.Net.Abstractions\WireMock.Net.Abstractions.csproj" />
|
|
||||||
<ProjectReference Include="..\..\src\WireMock.Net\WireMock.Net.csproj" />
|
<ProjectReference Include="..\..\src\WireMock.Net\WireMock.Net.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
// Copyright © WireMock.Net
|
// Copyright © WireMock.Net
|
||||||
|
|
||||||
using System;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using WireMock.Admin.Requests;
|
using WireMock.Admin.Requests;
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
"WireMockServerSettings": {
|
"WireMockServerSettings": {
|
||||||
"StartAdminInterface": true,
|
"StartAdminInterface": true,
|
||||||
"Urls": [
|
"Urls": [
|
||||||
"http://localhost"
|
"http://localhost:0"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<configuration>
|
<configuration>
|
||||||
<!--
|
<!--
|
||||||
Configure your application settings in appsettings.json. Learn more at http://go.microsoft.com/fwlink/?LinkId=786380
|
Configure your application settings in appsettings.json. Learn more at http://go.microsoft.com/fwlink/?LinkId=786380
|
||||||
-->
|
-->
|
||||||
<system.webServer>
|
<system.webServer>
|
||||||
<handlers>
|
<handlers>
|
||||||
<add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" />
|
<add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" />
|
||||||
</handlers>
|
</handlers>
|
||||||
<aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" forwardWindowsAuthToken="false" />
|
<httpProtocol>
|
||||||
</system.webServer>
|
<customHeaders>
|
||||||
|
<add name="X-Frame-Options" value="SAMEORIGIN" />
|
||||||
|
</customHeaders>
|
||||||
|
</httpProtocol>
|
||||||
|
<aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" forwardWindowsAuthToken="false" />
|
||||||
|
</system.webServer>
|
||||||
</configuration>
|
</configuration>
|
||||||
@@ -55,12 +55,17 @@ public class MappingModel
|
|||||||
/// In case the value is null state will not be changed.
|
/// In case the value is null state will not be changed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string? SetStateTo { get; set; }
|
public string? SetStateTo { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The number of times this match should be matched before the state will be changed to the specified one.
|
/// The number of times this match should be matched before the state will be changed to the specified one.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int? TimesInSameState { get; set; }
|
public int? TimesInSameState { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Value to determine if the mapping is disabled. Defaults to <c>null</c> (not disabled).
|
||||||
|
/// </summary>
|
||||||
|
public bool? IsDisabled { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The request model.
|
/// The request model.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -100,7 +105,7 @@ public class MappingModel
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public object? Data { get; set; }
|
public object? Data { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The probability when this request should be matched. Value is between 0 and 1. [Optional]
|
/// The probability when this request should be matched. Value is between 0 and 1. [Optional]
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public double? Probability { get; set; }
|
public double? Probability { get; set; }
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
// Copyright © WireMock.Net
|
// Copyright © WireMock.Net
|
||||||
|
|
||||||
|
using WireMock.Matchers.Request;
|
||||||
|
|
||||||
namespace WireMock.Admin.Mappings;
|
namespace WireMock.Admin.Mappings;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -61,9 +63,15 @@ public class RequestModel
|
|||||||
/// Gets or sets the Params.
|
/// Gets or sets the Params.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IList<ParamModel>? Params { get; set; }
|
public IList<ParamModel>? Params { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the body.
|
/// Gets or sets the body.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public BodyModel? Body { get; set; }
|
public BodyModel? Body { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Type of the request matcher to return an immediate mismatch during mapping processing.
|
||||||
|
/// Optional.
|
||||||
|
/// </summary>
|
||||||
|
public RequestMatcherType? EarlyMatcherType { get; set; }
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
// Copyright © WireMock.Net
|
||||||
|
|
||||||
|
namespace WireMock.Admin.Mappings;
|
||||||
|
|
||||||
|
public partial class ArrayMatcherModelBuilder
|
||||||
|
{
|
||||||
|
public ArrayMatcherModelBuilder WithExactMatcher(object pattern, bool rejectOnMatch = false, bool ignoreCase = false)
|
||||||
|
{
|
||||||
|
return WithMatcher("ExactMatcher", pattern, rejectOnMatch, ignoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArrayMatcherModelBuilder WithWildcardMatcher(object pattern, bool rejectOnMatch = false, bool ignoreCase = false)
|
||||||
|
{
|
||||||
|
return WithMatcher("WildcardMatcher", pattern, rejectOnMatch, ignoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArrayMatcherModelBuilder WithRegexMatcher(object pattern, bool rejectOnMatch = false, bool ignoreCase = false)
|
||||||
|
{
|
||||||
|
return WithMatcher("RegexMatcher", pattern, rejectOnMatch, ignoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArrayMatcherModelBuilder WithNotNullOrEmptyMatcher(bool rejectOnMatch = false)
|
||||||
|
{
|
||||||
|
return Add(mb => mb
|
||||||
|
.WithName("NotNullOrEmptyMatcher")
|
||||||
|
.WithRejectOnMatch(rejectOnMatch)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ArrayMatcherModelBuilder WithMatcher(string name, object pattern, bool rejectOnMatch, bool ignoreCase = false)
|
||||||
|
{
|
||||||
|
return Add(mb => mb
|
||||||
|
.WithName(name)
|
||||||
|
.WithPattern(pattern)
|
||||||
|
.WithRejectOnMatch(rejectOnMatch)
|
||||||
|
.WithIgnoreCase(ignoreCase)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
// Copyright © WireMock.Net
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace WireMock.Admin.Mappings;
|
||||||
|
|
||||||
|
public partial class IListCookieModelBuilder
|
||||||
|
{
|
||||||
|
public IListCookieModelBuilder WithCookie(string name, Action<IListMatcherModelBuilder> action, bool rejectOnMatch = false)
|
||||||
|
{
|
||||||
|
return Add(cookieBuilder => cookieBuilder
|
||||||
|
.WithName(name)
|
||||||
|
.WithRejectOnMatch(rejectOnMatch)
|
||||||
|
.WithMatchers(matchersBuilder => action(matchersBuilder))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
// Copyright © WireMock.Net
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace WireMock.Admin.Mappings;
|
||||||
|
|
||||||
|
public partial class IListHeaderModelBuilder
|
||||||
|
{
|
||||||
|
public IListHeaderModelBuilder WithHeader(string name, Action<IListMatcherModelBuilder> action, bool rejectOnMatch = false)
|
||||||
|
{
|
||||||
|
return Add(headerBuilder => headerBuilder
|
||||||
|
.WithName(name)
|
||||||
|
.WithRejectOnMatch(rejectOnMatch)
|
||||||
|
.WithMatchers(matchersBuilder => action(matchersBuilder))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
// Copyright © WireMock.Net
|
||||||
|
|
||||||
|
namespace WireMock.Admin.Mappings;
|
||||||
|
|
||||||
|
public partial class IListMatcherModelBuilder
|
||||||
|
{
|
||||||
|
public IListMatcherModelBuilder WithExactMatcher(object pattern, bool rejectOnMatch = false, bool ignoreCase = false)
|
||||||
|
{
|
||||||
|
return WithMatcher("ExactMatcher", pattern, rejectOnMatch, ignoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IListMatcherModelBuilder WithWildcardMatcher(object pattern, bool rejectOnMatch = false, bool ignoreCase = false)
|
||||||
|
{
|
||||||
|
return WithMatcher("WildcardMatcher", pattern, rejectOnMatch, ignoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IListMatcherModelBuilder WithRegexMatcher(object pattern, bool rejectOnMatch = false, bool ignoreCase = false)
|
||||||
|
{
|
||||||
|
return WithMatcher("RegexMatcher", pattern, rejectOnMatch, ignoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IListMatcherModelBuilder WithNotNullOrEmptyMatcher(bool rejectOnMatch = false)
|
||||||
|
{
|
||||||
|
return Add(mb => mb
|
||||||
|
.WithName("NotNullOrEmptyMatcher")
|
||||||
|
.WithRejectOnMatch(rejectOnMatch)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IListMatcherModelBuilder WithMatcher(string name, object pattern, bool rejectOnMatch, bool ignoreCase = false)
|
||||||
|
{
|
||||||
|
return Add(mb => mb
|
||||||
|
.WithName(name)
|
||||||
|
.WithPattern(pattern)
|
||||||
|
.WithRejectOnMatch(rejectOnMatch)
|
||||||
|
.WithIgnoreCase(ignoreCase)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
// Copyright © WireMock.Net
|
||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace WireMock.Admin.Mappings;
|
||||||
|
|
||||||
|
public partial class IListParamModelBuilder
|
||||||
|
{
|
||||||
|
public IListParamModelBuilder WithParam(string name, Action<ArrayMatcherModelBuilder> action, bool rejectOnMatch = false)
|
||||||
|
{
|
||||||
|
return Add(paramBuilder => paramBuilder
|
||||||
|
.WithName(name)
|
||||||
|
.WithRejectOnMatch(rejectOnMatch)
|
||||||
|
.WithMatchers(matchersBuilder => action(matchersBuilder))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
// Copyright © WireMock.Net
|
||||||
|
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
|
namespace WireMock.Handlers;
|
||||||
|
|
||||||
|
public interface IScenarioStateStore
|
||||||
|
{
|
||||||
|
bool TryGet(string name, [NotNullWhen(true)] out ScenarioState? state);
|
||||||
|
|
||||||
|
IReadOnlyList<ScenarioState> GetAll();
|
||||||
|
|
||||||
|
bool ContainsKey(string name);
|
||||||
|
|
||||||
|
bool TryAdd(string name, ScenarioState scenarioState);
|
||||||
|
|
||||||
|
ScenarioState AddOrUpdate(string name, Func<string, ScenarioState> addFactory, Func<string, ScenarioState, ScenarioState> updateFactory);
|
||||||
|
|
||||||
|
ScenarioState? Update(string name, Action<ScenarioState> updateAction);
|
||||||
|
|
||||||
|
bool TryRemove(string name);
|
||||||
|
|
||||||
|
void Clear();
|
||||||
|
}
|
||||||
@@ -7,6 +7,11 @@ namespace WireMock.Matchers.Request;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IRequestMatcher
|
public interface IRequestMatcher
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the request matcher's type.
|
||||||
|
/// </summary>
|
||||||
|
public RequestMatcherType Type { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Determines whether the specified RequestMessage is match.
|
/// Determines whether the specified RequestMessage is match.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ public class MatchDetail
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the type of the matcher.
|
/// Gets or sets the type of the matcher.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public required Type MatcherType { get; set; }
|
public required string MatcherType { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the type of the matcher.
|
/// Gets or sets the type of the matcher.
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
// Copyright © WireMock.Net
|
||||||
|
|
||||||
|
namespace WireMock.Matchers.Request;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// List of predefined request matcher types.
|
||||||
|
/// </summary>
|
||||||
|
public enum RequestMatcherType
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// RequestMessageBodyMatcher
|
||||||
|
/// </summary>
|
||||||
|
Body = 0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// RequestMessageBodyMatcher{T}
|
||||||
|
/// </summary>
|
||||||
|
BodyOfT = 1,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// RequestMessageClientIPMatcher
|
||||||
|
/// </summary>
|
||||||
|
ClientIP = 2,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// RequestMessageCookieMatcher
|
||||||
|
/// </summary>
|
||||||
|
Cookie = 3,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// RequestMessageGraphQLMatcher
|
||||||
|
/// </summary>
|
||||||
|
GraphQL = 4,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// RequestMessageHeaderMatcher
|
||||||
|
/// </summary>
|
||||||
|
Header = 5,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// RequestMessageHttpVersionMatcher
|
||||||
|
/// </summary>
|
||||||
|
HttpVersion = 6,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// RequestMessageMethodMatcher
|
||||||
|
/// </summary>
|
||||||
|
Method = 7,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// RequestMessageMultiPartMatcher
|
||||||
|
/// </summary>
|
||||||
|
MultiPart = 8,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// RequestMessageParamMatcher
|
||||||
|
/// </summary>
|
||||||
|
Param = 9,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// RequestMessagePathMatcher
|
||||||
|
/// </summary>
|
||||||
|
Path = 10,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// RequestMessageProtoBufMatcher
|
||||||
|
/// </summary>
|
||||||
|
ProtoBuf = 11,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// RequestMessageScenarioAndStateMatcher
|
||||||
|
/// </summary>
|
||||||
|
ScenarioAndState = 12,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// RequestMessageUrlMatcher
|
||||||
|
/// </summary>
|
||||||
|
Url = 13,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// RequestMessageCompositeMatcher
|
||||||
|
/// </summary>
|
||||||
|
Composite = 14
|
||||||
|
}
|
||||||
@@ -31,4 +31,4 @@ public class ScenarioState
|
|||||||
/// Gets or sets the state counter.
|
/// Gets or sets the state counter.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int Counter { get; set; }
|
public int Counter { get; set; }
|
||||||
}
|
}
|
||||||
@@ -33,12 +33,6 @@ public interface IWireMockServer : IDisposable
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
IReadOnlyList<MappingModel> MappingModels { get; }
|
IReadOnlyList<MappingModel> MappingModels { get; }
|
||||||
|
|
||||||
// <summary>
|
|
||||||
// Gets the mappings.
|
|
||||||
// </summary>
|
|
||||||
//[PublicAPI]
|
|
||||||
//IEnumerable<IMapping> Mappings { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the ports.
|
/// Gets the ports.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -69,8 +63,6 @@ public interface IWireMockServer : IDisposable
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
string? Provider { get; }
|
string? Provider { get; }
|
||||||
|
|
||||||
//ConcurrentDictionary<string, ScenarioState> Scenarios { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Occurs when [log entries changed].
|
/// Occurs when [log entries changed].
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -115,8 +107,6 @@ public interface IWireMockServer : IDisposable
|
|||||||
/// <returns>The <see cref="IReadOnlyList{ILogEntry}"/>.</returns>
|
/// <returns>The <see cref="IReadOnlyList{ILogEntry}"/>.</returns>
|
||||||
IReadOnlyList<ILogEntry> FindLogEntries(params IRequestMatcher[] matchers);
|
IReadOnlyList<ILogEntry> FindLogEntries(params IRequestMatcher[] matchers);
|
||||||
|
|
||||||
// IRespondWithAProvider Given(IRequestMatcher requestMatcher, bool saveToFile = false);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reads a static mapping file and adds or updates a single mapping.
|
/// Reads a static mapping file and adds or updates a single mapping.
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="JsonConverter.Abstractions" Version="0.8.0" />
|
<PackageReference Include="JsonConverter.Abstractions" Version="0.9.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
145
src/WireMock.Net.Minimal/Handlers/FileBasedScenarioStateStore.cs
Normal file
145
src/WireMock.Net.Minimal/Handlers/FileBasedScenarioStateStore.cs
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
// Copyright © WireMock.Net
|
||||||
|
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Stef.Validation;
|
||||||
|
|
||||||
|
namespace WireMock.Handlers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides a file-based implementation of <see cref="IScenarioStateStore" /> that persists scenario states to disk and allows concurrent access.
|
||||||
|
/// </summary>
|
||||||
|
public class FileBasedScenarioStateStore : IScenarioStateStore
|
||||||
|
{
|
||||||
|
private readonly ConcurrentDictionary<string, ScenarioState> _scenarios = new(StringComparer.OrdinalIgnoreCase);
|
||||||
|
private readonly string _scenariosFolder;
|
||||||
|
private readonly object _lock = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the FileBasedScenarioStateStore class using the specified root folder as the base directory for scenario state storage.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="rootFolder">The root directory under which scenario state data will be stored. Must be a valid file system path.</param>
|
||||||
|
public FileBasedScenarioStateStore(string rootFolder)
|
||||||
|
{
|
||||||
|
Guard.NotNullOrEmpty(rootFolder);
|
||||||
|
|
||||||
|
_scenariosFolder = Path.Combine(rootFolder, "__admin", "scenarios");
|
||||||
|
Directory.CreateDirectory(_scenariosFolder);
|
||||||
|
LoadScenariosFromDisk();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool TryGet(string name, [NotNullWhen(true)] out ScenarioState? state)
|
||||||
|
{
|
||||||
|
return _scenarios.TryGetValue(name, out state);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IReadOnlyList<ScenarioState> GetAll()
|
||||||
|
{
|
||||||
|
return _scenarios.Values.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool ContainsKey(string name)
|
||||||
|
{
|
||||||
|
return _scenarios.ContainsKey(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool TryAdd(string name, ScenarioState scenarioState)
|
||||||
|
{
|
||||||
|
if (_scenarios.TryAdd(name, scenarioState))
|
||||||
|
{
|
||||||
|
WriteScenarioToFile(name, scenarioState);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ScenarioState AddOrUpdate(string name, Func<string, ScenarioState> addFactory, Func<string, ScenarioState, ScenarioState> updateFactory)
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
var result = _scenarios.AddOrUpdate(name, addFactory, updateFactory);
|
||||||
|
WriteScenarioToFile(name, result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ScenarioState? Update(string name, Action<ScenarioState> updateAction)
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
if (_scenarios.TryGetValue(name, out var state))
|
||||||
|
{
|
||||||
|
updateAction(state);
|
||||||
|
WriteScenarioToFile(name, state);
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool TryRemove(string name)
|
||||||
|
{
|
||||||
|
if (_scenarios.TryRemove(name, out _))
|
||||||
|
{
|
||||||
|
DeleteScenarioFile(name);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
_scenarios.Clear();
|
||||||
|
|
||||||
|
foreach (var file in Directory.GetFiles(_scenariosFolder, "*.json"))
|
||||||
|
{
|
||||||
|
File.Delete(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetScenarioFilePath(string name)
|
||||||
|
{
|
||||||
|
var sanitized = string.Concat(name.Select(c => Path.GetInvalidFileNameChars().Contains(c) ? '_' : c));
|
||||||
|
return Path.Combine(_scenariosFolder, sanitized + ".json");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteScenarioToFile(string name, ScenarioState state)
|
||||||
|
{
|
||||||
|
var json = JsonConvert.SerializeObject(state, Formatting.Indented);
|
||||||
|
File.WriteAllText(GetScenarioFilePath(name), json);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DeleteScenarioFile(string name)
|
||||||
|
{
|
||||||
|
var path = GetScenarioFilePath(name);
|
||||||
|
if (File.Exists(path))
|
||||||
|
{
|
||||||
|
File.Delete(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadScenariosFromDisk()
|
||||||
|
{
|
||||||
|
foreach (var file in Directory.GetFiles(_scenariosFolder, "*.json"))
|
||||||
|
{
|
||||||
|
var json = File.ReadAllText(file);
|
||||||
|
var state = JsonConvert.DeserializeObject<ScenarioState>(json);
|
||||||
|
if (state != null)
|
||||||
|
{
|
||||||
|
_scenarios.TryAdd(state.Name, state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
38
src/WireMock.Net.Minimal/Logging/WireMockAspNetCoreLogger.cs
Normal file
38
src/WireMock.Net.Minimal/Logging/WireMockAspNetCoreLogger.cs
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
// Copyright © WireMock.Net
|
||||||
|
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace WireMock.Logging;
|
||||||
|
|
||||||
|
internal sealed class WireMockAspNetCoreLogger(IWireMockLogger logger, string categoryName) : ILogger
|
||||||
|
{
|
||||||
|
public IDisposable? BeginScope<TState>(TState state) where TState : notnull => null;
|
||||||
|
|
||||||
|
public bool IsEnabled(LogLevel logLevel) => logLevel >= LogLevel.Warning;
|
||||||
|
|
||||||
|
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
|
||||||
|
{
|
||||||
|
if (!IsEnabled(logLevel))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var message = formatter(state, exception);
|
||||||
|
|
||||||
|
if (exception != null)
|
||||||
|
{
|
||||||
|
message = $"{message} | Exception: {exception}";
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (logLevel)
|
||||||
|
{
|
||||||
|
case LogLevel.Warning:
|
||||||
|
logger.Warn("[{0}] {1}", categoryName, message);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
logger.Error("[{0}] {1}", categoryName, message);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
// Copyright © WireMock.Net
|
||||||
|
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace WireMock.Logging;
|
||||||
|
|
||||||
|
internal sealed class WireMockAspNetCoreLoggerProvider : ILoggerProvider
|
||||||
|
{
|
||||||
|
private readonly IWireMockLogger _logger;
|
||||||
|
|
||||||
|
public WireMockAspNetCoreLoggerProvider(IWireMockLogger logger)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ILogger CreateLogger(string categoryName) => new WireMockAspNetCoreLogger(_logger, categoryName);
|
||||||
|
|
||||||
|
public void Dispose() { }
|
||||||
|
}
|
||||||
@@ -62,6 +62,9 @@ public class Mapping : IMapping
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public bool IsProxy => Provider is ProxyAsyncResponseProvider;
|
public bool IsProxy => Provider is ProxyAsyncResponseProvider;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool IsDisabled { get; set; }
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public bool LogMapping => Provider is not (DynamicResponseProvider or DynamicAsyncResponseProvider);
|
public bool LogMapping => Provider is not (DynamicResponseProvider or DynamicAsyncResponseProvider);
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
// Copyright © WireMock.Net
|
// Copyright © WireMock.Net
|
||||||
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace WireMock.Matchers.Request;
|
namespace WireMock.Matchers.Request;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -30,7 +28,7 @@ public class RequestMatchResult : IRequestMatchResult
|
|||||||
return AddMatchDetail(new MatchDetail
|
return AddMatchDetail(new MatchDetail
|
||||||
{
|
{
|
||||||
Name = matcherType.Name.Replace("RequestMessage", string.Empty),
|
Name = matcherType.Name.Replace("RequestMessage", string.Empty),
|
||||||
MatcherType = matcherType,
|
MatcherType = matcherType.Name,
|
||||||
Score = score,
|
Score = score,
|
||||||
Exception = exception
|
Exception = exception
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -142,6 +142,9 @@ public class RequestMessageBodyMatcher : IRequestMatcher
|
|||||||
MatchOperator = matchOperator;
|
MatchOperator = matchOperator;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public RequestMatcherType Type => RequestMatcherType.Body;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
|
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -31,6 +31,9 @@ public class RequestMessageBodyMatcher<T> : IRequestMatcher
|
|||||||
Func = Guard.NotNull(func);
|
Func = Guard.NotNull(func);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public RequestMatcherType Type => RequestMatcherType.BodyOfT;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
|
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -74,6 +74,9 @@ public class RequestMessageClientIPMatcher : IRequestMatcher
|
|||||||
Funcs = Guard.NotNull(funcs);
|
Funcs = Guard.NotNull(funcs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public RequestMatcherType Type => RequestMatcherType.ClientIP;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
|
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -20,6 +20,11 @@ public abstract class RequestMessageCompositeMatcher : IRequestMatcher
|
|||||||
/// </value>
|
/// </value>
|
||||||
private IEnumerable<IRequestMatcher> RequestMatchers { get; }
|
private IEnumerable<IRequestMatcher> RequestMatchers { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Selected type to choose the matcher from <see cref="RequestMatchers"/> which will immediately return a mismatch.
|
||||||
|
/// </summary>
|
||||||
|
internal RequestMatcherType? EarlyMatcherType { get; private protected set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="RequestMessageCompositeMatcher"/> class.
|
/// Initializes a new instance of the <see cref="RequestMessageCompositeMatcher"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -31,6 +36,9 @@ public abstract class RequestMessageCompositeMatcher : IRequestMatcher
|
|||||||
_type = type;
|
_type = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public RequestMatcherType Type => RequestMatcherType.Composite;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
|
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
|
||||||
{
|
{
|
||||||
@@ -39,6 +47,13 @@ public abstract class RequestMessageCompositeMatcher : IRequestMatcher
|
|||||||
return MatchScores.Mismatch;
|
return MatchScores.Mismatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var earlyMatcher = new RequestMessageEarlyMatcher(EarlyMatcherType, RequestMatchers);
|
||||||
|
var earlyMatchResult = earlyMatcher.GetMatchingScore(requestMessage, requestMatchResult);
|
||||||
|
if (!MatchScores.IsPerfect(earlyMatchResult))
|
||||||
|
{
|
||||||
|
return MatchScores.Mismatch;
|
||||||
|
}
|
||||||
|
|
||||||
if (_type == CompositeMatcherType.And)
|
if (_type == CompositeMatcherType.And)
|
||||||
{
|
{
|
||||||
return RequestMatchers.Average(requestMatcher => requestMatcher.GetMatchingScore(requestMessage, requestMatchResult));
|
return RequestMatchers.Average(requestMatcher => requestMatcher.GetMatchingScore(requestMessage, requestMatchResult));
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
// Copyright © WireMock.Net
|
// Copyright © WireMock.Net
|
||||||
|
|
||||||
using Stef.Validation;
|
using Stef.Validation;
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace WireMock.Matchers.Request;
|
namespace WireMock.Matchers.Request;
|
||||||
|
|
||||||
@@ -93,6 +92,9 @@ public class RequestMessageCookieMatcher : IRequestMatcher
|
|||||||
Name = string.Empty; // Not used when Func, but set to a non-null valid value.
|
Name = string.Empty; // Not used when Func, but set to a non-null valid value.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public RequestMatcherType Type => RequestMatcherType.Cookie;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
|
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
// Copyright © WireMock.Net
|
||||||
|
|
||||||
|
namespace WireMock.Matchers.Request;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Return the mismatch if the matching score of matchers is not perfect.
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class RequestMessageEarlyMatcher(
|
||||||
|
RequestMatcherType? earlyMatcherType,
|
||||||
|
IEnumerable<IRequestMatcher> requestMatchers) : IRequestMatcher
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public RequestMatcherType Type => RequestMatcherType.Composite;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
|
||||||
|
{
|
||||||
|
if (earlyMatcherType is null)
|
||||||
|
{
|
||||||
|
return MatchScores.Perfect;
|
||||||
|
}
|
||||||
|
|
||||||
|
var earlyMatchers = requestMatchers
|
||||||
|
.Where(m => m.Type == earlyMatcherType)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (earlyMatchers.Count is 0)
|
||||||
|
{
|
||||||
|
return MatchScores.Perfect;
|
||||||
|
}
|
||||||
|
|
||||||
|
var compositeMatcher = new RequestBuilders.Request(earlyMatchers);
|
||||||
|
return compositeMatcher.GetMatchingScore(requestMessage, requestMatchResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
// Copyright © WireMock.Net
|
// Copyright © WireMock.Net
|
||||||
|
|
||||||
using System.Linq;
|
|
||||||
using Stef.Validation;
|
using Stef.Validation;
|
||||||
using WireMock.Types;
|
using WireMock.Types;
|
||||||
|
|
||||||
@@ -12,7 +11,7 @@ namespace WireMock.Matchers.Request;
|
|||||||
/// <inheritdoc cref="IRequestMatcher"/>
|
/// <inheritdoc cref="IRequestMatcher"/>
|
||||||
public class RequestMessageHeaderMatcher : IRequestMatcher
|
public class RequestMessageHeaderMatcher : IRequestMatcher
|
||||||
{
|
{
|
||||||
private const string _name = nameof(RequestMessageCookieMatcher);
|
private const string _name = nameof(RequestMessageHeaderMatcher);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// MatchBehaviour
|
/// MatchBehaviour
|
||||||
@@ -106,6 +105,9 @@ public class RequestMessageHeaderMatcher : IRequestMatcher
|
|||||||
Name = string.Empty; // Not used when Func, but set to a non-null valid value.
|
Name = string.Empty; // Not used when Func, but set to a non-null valid value.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public RequestMatcherType Type => RequestMatcherType.Header;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
|
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -65,6 +65,9 @@ public class RequestMessageHttpVersionMatcher : IRequestMatcher
|
|||||||
MatcherOnStringFunc = Guard.NotNull(func);
|
MatcherOnStringFunc = Guard.NotNull(func);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public RequestMatcherType Type => RequestMatcherType.HttpVersion;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
|
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -46,6 +46,9 @@ internal class RequestMessageMethodMatcher : IRequestMatcher
|
|||||||
MatchOperator = matchOperator;
|
MatchOperator = matchOperator;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public RequestMatcherType Type => RequestMatcherType.Method;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
|
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -55,6 +55,9 @@ public class RequestMessageMultiPartMatcher : IRequestMatcher
|
|||||||
MatchOperator = matchOperator;
|
MatchOperator = matchOperator;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public RequestMatcherType Type => RequestMatcherType.MultiPart;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
|
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -82,6 +82,9 @@ public class RequestMessageParamMatcher : IRequestMatcher
|
|||||||
Funcs = Guard.NotNull(funcs);
|
Funcs = Guard.NotNull(funcs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public RequestMatcherType Type => RequestMatcherType.Param;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
|
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -72,6 +72,9 @@ public class RequestMessagePathMatcher : IRequestMatcher
|
|||||||
Funcs = Guard.NotNull(funcs);
|
Funcs = Guard.NotNull(funcs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public RequestMatcherType Type => RequestMatcherType.Path;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
|
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -29,6 +29,9 @@ internal class RequestMessageScenarioAndStateMatcher : IRequestMatcher
|
|||||||
_executionConditionState = executionConditionState;
|
_executionConditionState = executionConditionState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public RequestMatcherType Type => RequestMatcherType.ScenarioAndState;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
|
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -72,6 +72,9 @@ public class RequestMessageUrlMatcher : IRequestMatcher
|
|||||||
Funcs = Guard.NotNull(funcs);
|
Funcs = Guard.NotNull(funcs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public RequestMatcherType Type => RequestMatcherType.Url;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
|
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
// Copyright © WireMock.Net
|
// Copyright © WireMock.Net
|
||||||
|
|
||||||
using System.Linq;
|
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
using Stef.Validation;
|
using Stef.Validation;
|
||||||
using WireMock.Logging;
|
using WireMock.Logging;
|
||||||
using WireMock.Owin.Mappers;
|
using WireMock.Owin.Mappers;
|
||||||
@@ -57,6 +57,12 @@ internal partial class AspNetCoreSelfHost
|
|||||||
_host = builder
|
_host = builder
|
||||||
.UseSetting("suppressStatusMessages", "True") // https://andrewlock.net/suppressing-the-startup-and-shutdown-messages-in-asp-net-core/
|
.UseSetting("suppressStatusMessages", "True") // https://andrewlock.net/suppressing-the-startup-and-shutdown-messages-in-asp-net-core/
|
||||||
.ConfigureAppConfigurationUsingEnvironmentVariables()
|
.ConfigureAppConfigurationUsingEnvironmentVariables()
|
||||||
|
.ConfigureLogging(logging =>
|
||||||
|
{
|
||||||
|
logging.ClearProviders();
|
||||||
|
logging.AddProvider(new WireMockAspNetCoreLoggerProvider(_logger));
|
||||||
|
logging.SetMinimumLevel(LogLevel.Warning);
|
||||||
|
})
|
||||||
.ConfigureServices(services =>
|
.ConfigureServices(services =>
|
||||||
{
|
{
|
||||||
services.AddSingleton(_wireMockMiddlewareOptions);
|
services.AddSingleton(_wireMockMiddlewareOptions);
|
||||||
@@ -169,10 +175,10 @@ internal partial class AspNetCoreSelfHost
|
|||||||
|
|
||||||
return _host.RunAsync(token);
|
return _host.RunAsync(token);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
RunningException = e;
|
RunningException = ex;
|
||||||
_logger.Error(e.ToString());
|
_logger.Error("Error while RunAsync", ex);
|
||||||
|
|
||||||
IsStarted = false;
|
IsStarted = false;
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Security.Cryptography.X509Certificates;
|
using System.Security.Cryptography.X509Certificates;
|
||||||
|
using JsonConverter.Abstractions;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using WireMock.Handlers;
|
using WireMock.Handlers;
|
||||||
@@ -27,7 +28,7 @@ internal interface IWireMockMiddlewareOptions
|
|||||||
|
|
||||||
ConcurrentDictionary<Guid, IMapping> Mappings { get; }
|
ConcurrentDictionary<Guid, IMapping> Mappings { get; }
|
||||||
|
|
||||||
ConcurrentDictionary<string, ScenarioState> Scenarios { get; }
|
IScenarioStateStore ScenarioStateStore { get; set; }
|
||||||
|
|
||||||
ConcurrentObservableCollection<LogEntry> LogEntries { get; }
|
ConcurrentObservableCollection<LogEntry> LogEntries { get; }
|
||||||
|
|
||||||
@@ -99,4 +100,12 @@ internal interface IWireMockMiddlewareOptions
|
|||||||
/// WebSocket settings.
|
/// WebSocket settings.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
WebSocketSettings? WebSocketSettings { get; set; }
|
WebSocketSettings? WebSocketSettings { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the default JSON converter used for serialization.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Set this property to customize how objects are serialized to and deserialized from JSON during mapping.
|
||||||
|
/// </remarks>
|
||||||
|
IJsonConverter DefaultJsonSerializer { get; set; }
|
||||||
}
|
}
|
||||||
@@ -1,257 +1,244 @@
|
|||||||
// Copyright © WireMock.Net
|
// Copyright © WireMock.Net
|
||||||
|
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Reflection;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using JsonConverter.Abstractions;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Newtonsoft.Json;
|
|
||||||
using RandomDataGenerator.FieldOptions;
|
using RandomDataGenerator.FieldOptions;
|
||||||
using RandomDataGenerator.Randomizers;
|
using RandomDataGenerator.Randomizers;
|
||||||
using Stef.Validation;
|
|
||||||
using WireMock.Http;
|
using WireMock.Http;
|
||||||
using WireMock.ResponseBuilders;
|
using WireMock.ResponseBuilders;
|
||||||
using WireMock.ResponseProviders;
|
using WireMock.ResponseProviders;
|
||||||
using WireMock.Types;
|
using WireMock.Types;
|
||||||
using WireMock.Util;
|
using WireMock.Util;
|
||||||
|
|
||||||
namespace WireMock.Owin.Mappers
|
namespace WireMock.Owin.Mappers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// OwinResponseMapper
|
||||||
|
/// </summary>
|
||||||
|
internal class OwinResponseMapper(IWireMockMiddlewareOptions options) : IOwinResponseMapper
|
||||||
{
|
{
|
||||||
/// <summary>
|
private readonly IRandomizerNumber<double> _randomizerDouble = RandomizerFactory.GetRandomizer(new FieldOptionsDouble { Min = 0, Max = 1 });
|
||||||
/// OwinResponseMapper
|
private readonly IRandomizerBytes _randomizerBytes = RandomizerFactory.GetRandomizer(new FieldOptionsBytes { Min = 100, Max = 200 });
|
||||||
/// </summary>
|
private readonly Encoding _utf8NoBom = new UTF8Encoding(false);
|
||||||
internal class OwinResponseMapper : IOwinResponseMapper
|
|
||||||
|
// https://msdn.microsoft.com/en-us/library/78h415ay(v=vs.110).aspx
|
||||||
|
private static readonly IDictionary<string, Action<HttpResponse, bool, WireMockList<string>>> ResponseHeadersToFix =
|
||||||
|
new Dictionary<string, Action<HttpResponse, bool, WireMockList<string>>>(StringComparer.OrdinalIgnoreCase)
|
||||||
|
{
|
||||||
|
{ HttpKnownHeaderNames.ContentType, (r, _, v) => r.ContentType = v.FirstOrDefault() },
|
||||||
|
{ HttpKnownHeaderNames.ContentLength, (r, hasBody, v) =>
|
||||||
|
{
|
||||||
|
// Only set the Content-Length header if the response does not have a body
|
||||||
|
if (!hasBody && long.TryParse(v.FirstOrDefault(), out var contentLength))
|
||||||
|
{
|
||||||
|
r.ContentLength = contentLength;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public async Task MapAsync(IResponseMessage? responseMessage, HttpResponse response)
|
||||||
{
|
{
|
||||||
private readonly IRandomizerNumber<double> _randomizerDouble = RandomizerFactory.GetRandomizer(new FieldOptionsDouble { Min = 0, Max = 1 });
|
if (responseMessage == null || responseMessage is WebSocketHandledResponse)
|
||||||
private readonly IRandomizerBytes _randomizerBytes = RandomizerFactory.GetRandomizer(new FieldOptionsBytes { Min = 100, Max = 200 });
|
|
||||||
private readonly IWireMockMiddlewareOptions _options;
|
|
||||||
private readonly Encoding _utf8NoBom = new UTF8Encoding(false);
|
|
||||||
|
|
||||||
// https://msdn.microsoft.com/en-us/library/78h415ay(v=vs.110).aspx
|
|
||||||
private static readonly IDictionary<string, Action<HttpResponse, bool, WireMockList<string>>> ResponseHeadersToFix =
|
|
||||||
new Dictionary<string, Action<HttpResponse, bool, WireMockList<string>>>(StringComparer.OrdinalIgnoreCase)
|
|
||||||
{
|
|
||||||
{ HttpKnownHeaderNames.ContentType, (r, _, v) => r.ContentType = v.FirstOrDefault() },
|
|
||||||
{ HttpKnownHeaderNames.ContentLength, (r, hasBody, v) =>
|
|
||||||
{
|
|
||||||
// Only set the Content-Length header if the response does not have a body
|
|
||||||
if (!hasBody && long.TryParse(v.FirstOrDefault(), out var contentLength))
|
|
||||||
{
|
|
||||||
r.ContentLength = contentLength;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="options">The IWireMockMiddlewareOptions.</param>
|
|
||||||
public OwinResponseMapper(IWireMockMiddlewareOptions options)
|
|
||||||
{
|
{
|
||||||
_options = Guard.NotNull(options);
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
var bodyData = responseMessage.BodyData;
|
||||||
public async Task MapAsync(IResponseMessage? responseMessage, HttpResponse response)
|
if (bodyData?.GetDetectedBodyType() == BodyType.SseString)
|
||||||
{
|
{
|
||||||
if (responseMessage == null || responseMessage is WebSocketHandledResponse)
|
await HandleSseStringAsync(responseMessage, response, bodyData);
|
||||||
{
|
return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var bodyData = responseMessage.BodyData;
|
|
||||||
if (bodyData?.GetDetectedBodyType() == BodyType.SseString)
|
|
||||||
{
|
|
||||||
await HandleSseStringAsync(responseMessage, response, bodyData);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[]? bytes;
|
|
||||||
switch (responseMessage.FaultType)
|
|
||||||
{
|
|
||||||
case FaultType.EMPTY_RESPONSE:
|
|
||||||
bytes = IsFault(responseMessage) ? [] : await GetNormalBodyAsync(responseMessage).ConfigureAwait(false);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case FaultType.MALFORMED_RESPONSE_CHUNK:
|
|
||||||
bytes = await GetNormalBodyAsync(responseMessage).ConfigureAwait(false) ?? [];
|
|
||||||
if (IsFault(responseMessage))
|
|
||||||
{
|
|
||||||
bytes = bytes.Take(bytes.Length / 2).Union(_randomizerBytes.Generate()).ToArray();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
bytes = await GetNormalBodyAsync(responseMessage).ConfigureAwait(false);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
var statusCodeType = responseMessage.StatusCode?.GetType();
|
|
||||||
if (statusCodeType != null)
|
|
||||||
{
|
|
||||||
if (statusCodeType == typeof(int) || statusCodeType == typeof(int?) || statusCodeType.GetTypeInfo().IsEnum)
|
|
||||||
{
|
|
||||||
response.StatusCode = MapStatusCode((int)responseMessage.StatusCode!);
|
|
||||||
}
|
|
||||||
else if (statusCodeType == typeof(string))
|
|
||||||
{
|
|
||||||
// Note: this case will also match on null
|
|
||||||
int.TryParse(responseMessage.StatusCode as string, out var statusCodeTypeAsInt);
|
|
||||||
response.StatusCode = MapStatusCode(statusCodeTypeAsInt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SetResponseHeaders(responseMessage, bytes != null, response);
|
|
||||||
|
|
||||||
if (bytes != null)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await response.Body.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_options.Logger.Warn("Error writing response body. Exception : {0}", ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SetResponseTrailingHeaders(responseMessage, response);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task HandleSseStringAsync(IResponseMessage responseMessage, HttpResponse response, IBodyData bodyData)
|
byte[]? bytes;
|
||||||
|
switch (responseMessage.FaultType)
|
||||||
{
|
{
|
||||||
if (bodyData.SseStringQueue == null)
|
case FaultType.EMPTY_RESPONSE:
|
||||||
{
|
bytes = IsFault(responseMessage) ? [] : await GetNormalBodyAsync(responseMessage).ConfigureAwait(false);
|
||||||
return;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
SetResponseHeaders(responseMessage, true, response);
|
case FaultType.MALFORMED_RESPONSE_CHUNK:
|
||||||
|
bytes = await GetNormalBodyAsync(responseMessage).ConfigureAwait(false) ?? [];
|
||||||
string? text;
|
if (IsFault(responseMessage))
|
||||||
do
|
|
||||||
{
|
|
||||||
if (bodyData.SseStringQueue.TryRead(out text))
|
|
||||||
{
|
{
|
||||||
await response.WriteAsync(text);
|
bytes = bytes.Take(bytes.Length / 2).Union(_randomizerBytes.Generate()).ToArray();
|
||||||
await response.Body.FlushAsync();
|
|
||||||
}
|
}
|
||||||
} while (text != null);
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
bytes = await GetNormalBodyAsync(responseMessage).ConfigureAwait(false);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int MapStatusCode(int code)
|
if (responseMessage.StatusCode is HttpStatusCode or int)
|
||||||
{
|
{
|
||||||
if (_options.AllowOnlyDefinedHttpStatusCodeInResponse == true && !Enum.IsDefined(typeof(HttpStatusCode), code))
|
response.StatusCode = MapStatusCode((int)responseMessage.StatusCode);
|
||||||
|
}
|
||||||
|
else if (responseMessage.StatusCode is string statusCodeAsString)
|
||||||
|
{
|
||||||
|
// Note: this case will also match on null
|
||||||
|
_ = int.TryParse(statusCodeAsString, out var statusCodeTypeAsInt);
|
||||||
|
response.StatusCode = MapStatusCode(statusCodeTypeAsInt);
|
||||||
|
}
|
||||||
|
|
||||||
|
SetResponseHeaders(responseMessage, bytes != null, response);
|
||||||
|
|
||||||
|
if (bytes != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
return (int)HttpStatusCode.OK;
|
await response.Body.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
return code;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool IsFault(IResponseMessage responseMessage)
|
|
||||||
{
|
|
||||||
return responseMessage.FaultPercentage == null || _randomizerDouble.Generate() <= responseMessage.FaultPercentage;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<byte[]?> GetNormalBodyAsync(IResponseMessage responseMessage)
|
|
||||||
{
|
|
||||||
var bodyData = responseMessage.BodyData;
|
|
||||||
switch (bodyData?.GetDetectedBodyType())
|
|
||||||
{
|
{
|
||||||
case BodyType.String:
|
options.Logger.Warn("Error writing response body. Exception : {0}", ex);
|
||||||
case BodyType.FormUrlEncoded:
|
|
||||||
return (bodyData.Encoding ?? _utf8NoBom).GetBytes(bodyData.BodyAsString!);
|
|
||||||
|
|
||||||
case BodyType.Json:
|
|
||||||
var formatting = bodyData.BodyAsJsonIndented == true ? Formatting.Indented : Formatting.None;
|
|
||||||
var jsonBody = JsonConvert.SerializeObject(bodyData.BodyAsJson, new JsonSerializerSettings { Formatting = formatting, NullValueHandling = NullValueHandling.Ignore });
|
|
||||||
return (bodyData.Encoding ?? _utf8NoBom).GetBytes(jsonBody);
|
|
||||||
|
|
||||||
case BodyType.ProtoBuf:
|
|
||||||
if (TypeLoader.TryLoadStaticInstance<IProtoBufUtils>(out var protoBufUtils))
|
|
||||||
{
|
|
||||||
var protoDefinitions = bodyData.ProtoDefinition?.Invoke().Texts;
|
|
||||||
return await protoBufUtils.GetProtoBufMessageWithHeaderAsync(protoDefinitions, bodyData.ProtoBufMessageType, bodyData.BodyAsJson).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case BodyType.Bytes:
|
|
||||||
return bodyData.BodyAsBytes;
|
|
||||||
|
|
||||||
case BodyType.File:
|
|
||||||
return _options.FileSystemHandler?.ReadResponseBodyAsFile(bodyData.BodyAsFile!);
|
|
||||||
|
|
||||||
case BodyType.MultiPart:
|
|
||||||
_options.Logger.Warn("MultiPart body type is not handled!");
|
|
||||||
break;
|
|
||||||
|
|
||||||
case BodyType.None:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void SetResponseHeaders(IResponseMessage responseMessage, bool hasBody, HttpResponse response)
|
|
||||||
{
|
|
||||||
// Force setting the Date header (#577)
|
|
||||||
AppendResponseHeader(
|
|
||||||
response,
|
|
||||||
HttpKnownHeaderNames.Date,
|
|
||||||
[DateTime.UtcNow.ToString(CultureInfo.InvariantCulture.DateTimeFormat.RFC1123Pattern, CultureInfo.InvariantCulture)]
|
|
||||||
);
|
|
||||||
|
|
||||||
// Set other headers
|
|
||||||
foreach (var item in responseMessage.Headers!)
|
|
||||||
{
|
|
||||||
var headerName = item.Key;
|
|
||||||
var value = item.Value;
|
|
||||||
if (ResponseHeadersToFix.TryGetValue(headerName, out var action))
|
|
||||||
{
|
|
||||||
action.Invoke(response, hasBody, value);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Check if this response header can be added (#148, #227 and #720)
|
|
||||||
if (!HttpKnownHeaderNames.IsRestrictedResponseHeader(headerName))
|
|
||||||
{
|
|
||||||
AppendResponseHeader(response, headerName, value.ToArray());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void SetResponseTrailingHeaders(IResponseMessage responseMessage, HttpResponse response)
|
SetResponseTrailingHeaders(responseMessage, response);
|
||||||
{
|
}
|
||||||
if (responseMessage.TrailingHeaders == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
#if TRAILINGHEADERS
|
private static async Task HandleSseStringAsync(IResponseMessage responseMessage, HttpResponse response, IBodyData bodyData)
|
||||||
foreach (var (headerName, value) in responseMessage.TrailingHeaders)
|
{
|
||||||
{
|
if (bodyData.SseStringQueue == null)
|
||||||
if (ResponseHeadersToFix.TryGetValue(headerName, out var action))
|
{
|
||||||
{
|
return;
|
||||||
action.Invoke(response, false, value);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Check if this trailing header can be added to the response
|
|
||||||
if (response.SupportsTrailers() && !HttpKnownHeaderNames.IsRestrictedResponseHeader(headerName))
|
|
||||||
{
|
|
||||||
response.AppendTrailer(headerName, new Microsoft.Extensions.Primitives.StringValues(value.ToArray()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void AppendResponseHeader(HttpResponse response, string headerName, string[] values)
|
SetResponseHeaders(responseMessage, true, response);
|
||||||
|
|
||||||
|
string? text;
|
||||||
|
do
|
||||||
{
|
{
|
||||||
response.Headers.Append(headerName, values);
|
if (bodyData.SseStringQueue.TryRead(out text))
|
||||||
|
{
|
||||||
|
await response.WriteAsync(text);
|
||||||
|
await response.Body.FlushAsync();
|
||||||
|
}
|
||||||
|
} while (text != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int MapStatusCode(int code)
|
||||||
|
{
|
||||||
|
if (options.AllowOnlyDefinedHttpStatusCodeInResponse == true && !Enum.IsDefined(typeof(HttpStatusCode), code))
|
||||||
|
{
|
||||||
|
return (int)HttpStatusCode.OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsFault(IResponseMessage responseMessage)
|
||||||
|
{
|
||||||
|
return responseMessage.FaultPercentage == null || _randomizerDouble.Generate() <= responseMessage.FaultPercentage;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<byte[]?> GetNormalBodyAsync(IResponseMessage responseMessage)
|
||||||
|
{
|
||||||
|
var bodyData = responseMessage.BodyData;
|
||||||
|
switch (bodyData?.GetDetectedBodyType())
|
||||||
|
{
|
||||||
|
case BodyType.String:
|
||||||
|
case BodyType.FormUrlEncoded:
|
||||||
|
return (bodyData.Encoding ?? _utf8NoBom).GetBytes(bodyData.BodyAsString!);
|
||||||
|
|
||||||
|
case BodyType.Json:
|
||||||
|
var jsonConverterOptions = new JsonConverterOptions
|
||||||
|
{
|
||||||
|
WriteIndented = bodyData.BodyAsJsonIndented == true,
|
||||||
|
IgnoreNullValues = true
|
||||||
|
};
|
||||||
|
|
||||||
|
var jsonBody = options.DefaultJsonSerializer.Serialize(bodyData.BodyAsJson!, jsonConverterOptions);
|
||||||
|
return (bodyData.Encoding ?? _utf8NoBom).GetBytes(jsonBody);
|
||||||
|
|
||||||
|
case BodyType.ProtoBuf:
|
||||||
|
if (TypeLoader.TryLoadStaticInstance<IProtoBufUtils>(out var protoBufUtils))
|
||||||
|
{
|
||||||
|
var protoDefinitions = bodyData.ProtoDefinition?.Invoke().Texts;
|
||||||
|
return await protoBufUtils.GetProtoBufMessageWithHeaderAsync(protoDefinitions, bodyData.ProtoBufMessageType, bodyData.BodyAsJson).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case BodyType.Bytes:
|
||||||
|
return bodyData.BodyAsBytes;
|
||||||
|
|
||||||
|
case BodyType.File:
|
||||||
|
return options.FileSystemHandler?.ReadResponseBodyAsFile(bodyData.BodyAsFile!);
|
||||||
|
|
||||||
|
case BodyType.MultiPart:
|
||||||
|
options.Logger.Warn("MultiPart body type is not handled!");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case BodyType.None:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SetResponseHeaders(IResponseMessage responseMessage, bool hasBody, HttpResponse response)
|
||||||
|
{
|
||||||
|
// Force setting the Date header (#577)
|
||||||
|
AppendResponseHeader(
|
||||||
|
response,
|
||||||
|
HttpKnownHeaderNames.Date,
|
||||||
|
[DateTime.UtcNow.ToString(CultureInfo.InvariantCulture.DateTimeFormat.RFC1123Pattern, CultureInfo.InvariantCulture)]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Set other headers
|
||||||
|
foreach (var item in responseMessage.Headers!)
|
||||||
|
{
|
||||||
|
var headerName = item.Key;
|
||||||
|
var value = item.Value;
|
||||||
|
if (ResponseHeadersToFix.TryGetValue(headerName, out var action))
|
||||||
|
{
|
||||||
|
action.Invoke(response, hasBody, value);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Check if this response header can be added (#148, #227 and #720)
|
||||||
|
if (!HttpKnownHeaderNames.IsRestrictedResponseHeader(headerName))
|
||||||
|
{
|
||||||
|
AppendResponseHeader(response, headerName, value.ToArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void SetResponseTrailingHeaders(IResponseMessage responseMessage, HttpResponse response)
|
||||||
|
{
|
||||||
|
if (responseMessage.TrailingHeaders == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if TRAILINGHEADERS
|
||||||
|
foreach (var (headerName, value) in responseMessage.TrailingHeaders)
|
||||||
|
{
|
||||||
|
if (ResponseHeadersToFix.TryGetValue(headerName, out var action))
|
||||||
|
{
|
||||||
|
action.Invoke(response, false, value);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Check if this trailing header can be added to the response
|
||||||
|
if (response.SupportsTrailers() && !HttpKnownHeaderNames.IsRestrictedResponseHeader(headerName))
|
||||||
|
{
|
||||||
|
response.AppendTrailer(headerName, new Microsoft.Extensions.Primitives.StringValues(value.ToArray()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AppendResponseHeader(HttpResponse response, string headerName, string[] values)
|
||||||
|
{
|
||||||
|
response.Headers.Append(headerName, values);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -19,6 +19,7 @@ internal class MappingMatcher(IWireMockMiddlewareOptions options, IRandomizerDou
|
|||||||
var possibleMappings = new List<MappingMatcherResult>();
|
var possibleMappings = new List<MappingMatcherResult>();
|
||||||
|
|
||||||
var mappings = _options.Mappings.Values
|
var mappings = _options.Mappings.Values
|
||||||
|
.Where(m => !m.IsDisabled)
|
||||||
.Where(m => m.TimeSettings.IsValid())
|
.Where(m => m.TimeSettings.IsValid())
|
||||||
.Where(m => m.Probability is null || _randomizerDoubleBetween0And1.Generate() <= m.Probability)
|
.Where(m => m.Probability is null || _randomizerDoubleBetween0And1.Generate() <= m.Probability)
|
||||||
.ToArray();
|
.ToArray();
|
||||||
@@ -89,14 +90,13 @@ internal class MappingMatcher(IWireMockMiddlewareOptions options, IRandomizerDou
|
|||||||
|
|
||||||
private string? GetNextState(IMapping mapping)
|
private string? GetNextState(IMapping mapping)
|
||||||
{
|
{
|
||||||
// If the mapping does not have a scenario or _options.Scenarios does not contain this scenario from the mapping,
|
// If the mapping does not have a scenario or the store does not contain this scenario,
|
||||||
// just return null to indicate that there is no next state.
|
// just return null to indicate that there is no next state.
|
||||||
if (mapping.Scenario == null || !_options.Scenarios.ContainsKey(mapping.Scenario))
|
if (mapping.Scenario == null)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Else just return the next state
|
return _options.ScenarioStateStore.TryGet(mapping.Scenario, out var state) ? state.NextState : null;
|
||||||
return _options.Scenarios[mapping.Scenario].NextState;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -81,9 +81,9 @@ internal class WireMockMiddleware(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set scenario start
|
// Set scenario start
|
||||||
if (!options.Scenarios.ContainsKey(mapping.Scenario) && mapping.IsStartState)
|
if (!options.ScenarioStateStore.ContainsKey(mapping.Scenario) && mapping.IsStartState)
|
||||||
{
|
{
|
||||||
options.Scenarios.TryAdd(mapping.Scenario, new ScenarioState
|
options.ScenarioStateStore.TryAdd(mapping.Scenario, new ScenarioState
|
||||||
{
|
{
|
||||||
Name = mapping.Scenario
|
Name = mapping.Scenario
|
||||||
});
|
});
|
||||||
@@ -122,6 +122,14 @@ internal class WireMockMiddleware(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Transition scenario state immediately after matching, before any delay (global or
|
||||||
|
// per-mapping) so that concurrent retries arriving during a delay period see the
|
||||||
|
// updated state and match the correct next mapping instead of re-matching this one.
|
||||||
|
if (targetMapping.Scenario != null)
|
||||||
|
{
|
||||||
|
UpdateScenarioState(targetMapping);
|
||||||
|
}
|
||||||
|
|
||||||
if (!targetMapping.IsAdminInterface && options.RequestProcessingDelay > TimeSpan.Zero)
|
if (!targetMapping.IsAdminInterface && options.RequestProcessingDelay > TimeSpan.Zero)
|
||||||
{
|
{
|
||||||
await Task.Delay(options.RequestProcessingDelay.Value).ConfigureAwait(false);
|
await Task.Delay(options.RequestProcessingDelay.Value).ConfigureAwait(false);
|
||||||
@@ -147,11 +155,6 @@ internal class WireMockMiddleware(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (targetMapping.Scenario != null)
|
|
||||||
{
|
|
||||||
UpdateScenarioState(targetMapping);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!targetMapping.IsAdminInterface && targetMapping.Webhooks?.Length > 0)
|
if (!targetMapping.IsAdminInterface && targetMapping.Webhooks?.Length > 0)
|
||||||
{
|
{
|
||||||
await SendToWebhooksAsync(targetMapping, request, response).ConfigureAwait(false);
|
await SendToWebhooksAsync(targetMapping, request, response).ConfigureAwait(false);
|
||||||
@@ -233,20 +236,21 @@ internal class WireMockMiddleware(
|
|||||||
|
|
||||||
private void UpdateScenarioState(IMapping mapping)
|
private void UpdateScenarioState(IMapping mapping)
|
||||||
{
|
{
|
||||||
var scenario = options.Scenarios[mapping.Scenario!];
|
options.ScenarioStateStore.Update(mapping.Scenario!, scenario =>
|
||||||
|
|
||||||
// Increase the number of times this state has been executed
|
|
||||||
scenario.Counter++;
|
|
||||||
|
|
||||||
// Only if the number of times this state is executed equals the required StateTimes, proceed to next state and reset the counter to 0
|
|
||||||
if (scenario.Counter == (mapping.TimesInSameState ?? 1))
|
|
||||||
{
|
{
|
||||||
scenario.NextState = mapping.NextState;
|
// Increase the number of times this state has been executed
|
||||||
scenario.Counter = 0;
|
scenario.Counter++;
|
||||||
}
|
|
||||||
|
|
||||||
// Else just update Started and Finished
|
// Only if the number of times this state is executed equals the required StateTimes, proceed to next state and reset the counter to 0
|
||||||
scenario.Started = true;
|
if (scenario.Counter == (mapping.TimesInSameState ?? 1))
|
||||||
scenario.Finished = mapping.NextState == null;
|
{
|
||||||
|
scenario.NextState = mapping.NextState;
|
||||||
|
scenario.Counter = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Else just update Started and Finished
|
||||||
|
scenario.Started = true;
|
||||||
|
scenario.Finished = mapping.NextState == null;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Security.Cryptography.X509Certificates;
|
using System.Security.Cryptography.X509Certificates;
|
||||||
|
using JsonConverter.Abstractions;
|
||||||
|
using JsonConverter.Newtonsoft.Json;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using WireMock.Handlers;
|
using WireMock.Handlers;
|
||||||
@@ -27,7 +29,7 @@ internal class WireMockMiddlewareOptions : IWireMockMiddlewareOptions
|
|||||||
|
|
||||||
public ConcurrentDictionary<Guid, IMapping> Mappings { get; } = new ConcurrentDictionary<Guid, IMapping>();
|
public ConcurrentDictionary<Guid, IMapping> Mappings { get; } = new ConcurrentDictionary<Guid, IMapping>();
|
||||||
|
|
||||||
public ConcurrentDictionary<string, ScenarioState> Scenarios { get; } = new(StringComparer.OrdinalIgnoreCase);
|
public IScenarioStateStore ScenarioStateStore { get; set; } = new InMemoryScenarioStateStore();
|
||||||
|
|
||||||
public ConcurrentObservableCollection<LogEntry> LogEntries { get; } = new();
|
public ConcurrentObservableCollection<LogEntry> LogEntries { get; } = new();
|
||||||
|
|
||||||
@@ -108,5 +110,9 @@ internal class WireMockMiddlewareOptions : IWireMockMiddlewareOptions
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public ConcurrentDictionary<Guid, WebSocketConnectionRegistry> WebSocketRegistries { get; } = new();
|
public ConcurrentDictionary<Guid, WebSocketConnectionRegistry> WebSocketRegistries { get; } = new();
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public WebSocketSettings? WebSocketSettings { get; set; }
|
public WebSocketSettings? WebSocketSettings { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IJsonConverter DefaultJsonSerializer { get; set; } = new NewtonsoftJsonConverter();
|
||||||
}
|
}
|
||||||
@@ -27,6 +27,7 @@ internal static class WireMockMiddlewareOptionsHelper
|
|||||||
options.FileSystemHandler = settings.FileSystemHandler;
|
options.FileSystemHandler = settings.FileSystemHandler;
|
||||||
options.HandleRequestsSynchronously = settings.HandleRequestsSynchronously;
|
options.HandleRequestsSynchronously = settings.HandleRequestsSynchronously;
|
||||||
options.Logger = settings.Logger;
|
options.Logger = settings.Logger;
|
||||||
|
options.ScenarioStateStore = settings.ScenarioStateStore;
|
||||||
options.MaxRequestLogCount = settings.MaxRequestLogCount;
|
options.MaxRequestLogCount = settings.MaxRequestLogCount;
|
||||||
options.PostWireMockMiddlewareInit = settings.PostWireMockMiddlewareInit;
|
options.PostWireMockMiddlewareInit = settings.PostWireMockMiddlewareInit;
|
||||||
options.PreWireMockMiddlewareInit = settings.PreWireMockMiddlewareInit;
|
options.PreWireMockMiddlewareInit = settings.PreWireMockMiddlewareInit;
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ public partial class Request : RequestMessageCompositeMatcher, IRequestBuilder
|
|||||||
/// Initializes a new instance of the <see cref="Request"/> class.
|
/// Initializes a new instance of the <see cref="Request"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="requestMatchers">The request matchers.</param>
|
/// <param name="requestMatchers">The request matchers.</param>
|
||||||
private Request(IList<IRequestMatcher> requestMatchers) : base(requestMatchers)
|
internal Request(IList<IRequestMatcher> requestMatchers) : base(requestMatchers)
|
||||||
{
|
{
|
||||||
_requestMatchers = Guard.NotNull(requestMatchers);
|
_requestMatchers = Guard.NotNull(requestMatchers);
|
||||||
}
|
}
|
||||||
@@ -81,6 +81,13 @@ public partial class Request : RequestMessageCompositeMatcher, IRequestBuilder
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IRequestBuilder WithEarlyMismatch(RequestMatcherType? earlyMatcherType)
|
||||||
|
{
|
||||||
|
EarlyMatcherType = earlyMatcherType;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
internal bool TryGetProtoBufMatcher([NotNullWhen(true)] out IProtoBufMatcher? protoBufMatcher)
|
internal bool TryGetProtoBufMatcher([NotNullWhen(true)] out IProtoBufMatcher? protoBufMatcher)
|
||||||
{
|
{
|
||||||
protoBufMatcher = GetRequestMessageMatcher<RequestMessageProtoBufMatcher>()?.Matcher;
|
protoBufMatcher = GetRequestMessageMatcher<RequestMessageProtoBufMatcher>()?.Matcher;
|
||||||
|
|||||||
@@ -66,6 +66,12 @@ internal class MappingConverter(MatcherMapper mapper)
|
|||||||
|
|
||||||
// Request
|
// Request
|
||||||
sb.AppendLine(" .Given(Request.Create()");
|
sb.AppendLine(" .Given(Request.Create()");
|
||||||
|
|
||||||
|
if (request.EarlyMatcherType != null)
|
||||||
|
{
|
||||||
|
sb.AppendLine($" .WithEarlyMismatch({request.EarlyMatcherType.Value.GetFullyQualifiedEnumValue()})");
|
||||||
|
}
|
||||||
|
|
||||||
sb.AppendLine($" .UsingMethod({To1Or2Or3Arguments(methodMatcher?.MatchBehaviour, methodMatcher?.MatchOperator, methodMatcher?.Methods, HttpRequestMethod.GET)})");
|
sb.AppendLine($" .UsingMethod({To1Or2Or3Arguments(methodMatcher?.MatchBehaviour, methodMatcher?.MatchOperator, methodMatcher?.Methods, HttpRequestMethod.GET)})");
|
||||||
|
|
||||||
if (pathMatcher?.Matchers != null)
|
if (pathMatcher?.Matchers != null)
|
||||||
@@ -275,6 +281,7 @@ internal class MappingConverter(MatcherMapper mapper)
|
|||||||
TimesInSameState = !string.IsNullOrWhiteSpace(mapping.NextState) ? mapping.TimesInSameState : null,
|
TimesInSameState = !string.IsNullOrWhiteSpace(mapping.NextState) ? mapping.TimesInSameState : null,
|
||||||
Data = mapping.Data,
|
Data = mapping.Data,
|
||||||
Probability = mapping.Probability,
|
Probability = mapping.Probability,
|
||||||
|
IsDisabled = mapping.IsDisabled ? true : null,
|
||||||
Request = new RequestModel
|
Request = new RequestModel
|
||||||
{
|
{
|
||||||
Headers = headerMatchers.Any() ? headerMatchers.Select(hm => new HeaderModel
|
Headers = headerMatchers.Any() ? headerMatchers.Select(hm => new HeaderModel
|
||||||
@@ -299,7 +306,9 @@ internal class MappingConverter(MatcherMapper mapper)
|
|||||||
IgnoreCase = pm.IgnoreCase ? true : null,
|
IgnoreCase = pm.IgnoreCase ? true : null,
|
||||||
RejectOnMatch = pm.MatchBehaviour == MatchBehaviour.RejectOnMatch ? true : null,
|
RejectOnMatch = pm.MatchBehaviour == MatchBehaviour.RejectOnMatch ? true : null,
|
||||||
Matchers = _mapper.Map(pm.Matchers)
|
Matchers = _mapper.Map(pm.Matchers)
|
||||||
}).ToList() : null
|
}).ToList() : null,
|
||||||
|
|
||||||
|
EarlyMatcherType = request.EarlyMatcherType
|
||||||
},
|
},
|
||||||
Response = new ResponseModel()
|
Response = new ResponseModel()
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// Copyright © WireMock.Net
|
// Copyright © WireMock.Net
|
||||||
|
|
||||||
using JsonConverter.Abstractions;
|
using JsonConverter.Abstractions;
|
||||||
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
#if NETSTANDARD2_0_OR_GREATER || NETCOREAPP3_1_OR_GREATER || NET6_0_OR_GREATER || NET461
|
#if NETSTANDARD2_0_OR_GREATER || NETCOREAPP3_1_OR_GREATER || NET6_0_OR_GREATER || NET461
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
@@ -10,9 +11,15 @@ namespace WireMock.Serialization;
|
|||||||
|
|
||||||
internal class MappingSerializer(IJsonConverter jsonConverter)
|
internal class MappingSerializer(IJsonConverter jsonConverter)
|
||||||
{
|
{
|
||||||
|
private static readonly JsonConverterOptions JsonConverterOptions = new JsonConverterOptions
|
||||||
|
{
|
||||||
|
DateParseHandling = (int) DateParseHandling.None
|
||||||
|
};
|
||||||
|
|
||||||
internal T[] DeserializeJsonToArray<T>(string value)
|
internal T[] DeserializeJsonToArray<T>(string value)
|
||||||
{
|
{
|
||||||
return DeserializeObjectToArray<T>(jsonConverter.Deserialize<object>(value)!);
|
// DeserializeObject
|
||||||
|
return DeserializeObjectToArray<T>(jsonConverter.Deserialize<object>(value, JsonConverterOptions)!);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static T[] DeserializeObjectToArray<T>(object value)
|
internal static T[] DeserializeObjectToArray<T>(object value)
|
||||||
|
|||||||
@@ -234,6 +234,13 @@ public interface IRespondWithAProvider
|
|||||||
/// <returns>The <see cref="IRespondWithAProvider"/>.</returns>
|
/// <returns>The <see cref="IRespondWithAProvider"/>.</returns>
|
||||||
IRespondWithAProvider WithProbability(double probability);
|
IRespondWithAProvider WithProbability(double probability);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Define whether this mapping is disabled. Defaults to <c>false</c>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="isDisabled">Whether this mapping is disabled.</param>
|
||||||
|
/// <returns>The <see cref="IRespondWithAProvider"/>.</returns>
|
||||||
|
IRespondWithAProvider WithIsDisabled(bool isDisabled);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Define a Grpc ProtoDefinition which is used for the request and the response.
|
/// Define a Grpc ProtoDefinition which is used for the request and the response.
|
||||||
/// This can be a ProtoDefinition as a string, or an id when the ProtoDefinitions are defined at the WireMockServer.
|
/// This can be a ProtoDefinition as a string, or an id when the ProtoDefinitions are defined at the WireMockServer.
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ internal class RespondWithAProvider : IRespondWithAProvider
|
|||||||
private int _timesInSameState = 1;
|
private int _timesInSameState = 1;
|
||||||
private bool? _useWebhookFireAndForget;
|
private bool? _useWebhookFireAndForget;
|
||||||
private double? _probability;
|
private double? _probability;
|
||||||
|
private bool _isDisabled = false;
|
||||||
private GraphQLSchemaDetails? _graphQLSchemaDetails; // Future Use.
|
private GraphQLSchemaDetails? _graphQLSchemaDetails; // Future Use.
|
||||||
|
|
||||||
public Guid Guid { get; private set; }
|
public Guid Guid { get; private set; }
|
||||||
@@ -108,6 +109,11 @@ internal class RespondWithAProvider : IRespondWithAProvider
|
|||||||
mapping.WithProbability(_probability.Value);
|
mapping.WithProbability(_probability.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_isDisabled)
|
||||||
|
{
|
||||||
|
mapping.IsDisabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (ProtoDefinition != null)
|
if (ProtoDefinition != null)
|
||||||
{
|
{
|
||||||
mapping.WithProtoDefinition(ProtoDefinition.Value);
|
mapping.WithProtoDefinition(ProtoDefinition.Value);
|
||||||
@@ -354,6 +360,13 @@ internal class RespondWithAProvider : IRespondWithAProvider
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IRespondWithAProvider WithIsDisabled(bool isDisabled)
|
||||||
|
{
|
||||||
|
_isDisabled = isDisabled;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public IRespondWithAProvider WithProtoDefinition(params string[] protoDefinitionOrId)
|
public IRespondWithAProvider WithProtoDefinition(params string[] protoDefinitionOrId)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
// Copyright © WireMock.Net
|
// Copyright © WireMock.Net
|
||||||
|
|
||||||
using System.Linq;
|
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
@@ -26,9 +25,6 @@ using WireMock.Util;
|
|||||||
|
|
||||||
namespace WireMock.Server;
|
namespace WireMock.Server;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The fluent mock server.
|
|
||||||
/// </summary>
|
|
||||||
public partial class WireMockServer
|
public partial class WireMockServer
|
||||||
{
|
{
|
||||||
private const int EnhancedFileSystemWatcherTimeoutMs = 1000;
|
private const int EnhancedFileSystemWatcherTimeoutMs = 1000;
|
||||||
@@ -61,6 +57,8 @@ public partial class WireMockServer
|
|||||||
public string OpenApi => $"{_prefix}/openapi";
|
public string OpenApi => $"{_prefix}/openapi";
|
||||||
|
|
||||||
public RegexMatcher MappingsGuidPathMatcher => new($"^{_prefixEscaped}\\/mappings\\/([0-9A-Fa-f]{{8}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{12}})$");
|
public RegexMatcher MappingsGuidPathMatcher => new($"^{_prefixEscaped}\\/mappings\\/([0-9A-Fa-f]{{8}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{12}})$");
|
||||||
|
public RegexMatcher MappingsGuidEnablePathMatcher => new($"^{_prefixEscaped}\\/mappings\\/([0-9A-Fa-f]{{8}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{12}})\\/enable$");
|
||||||
|
public RegexMatcher MappingsGuidDisablePathMatcher => new($"^{_prefixEscaped}\\/mappings\\/([0-9A-Fa-f]{{8}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{12}})\\/disable$");
|
||||||
public RegexMatcher MappingsCodeGuidPathMatcher => new($"^{_prefixEscaped}\\/mappings\\/code\\/([0-9A-Fa-f]{{8}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{12}})$");
|
public RegexMatcher MappingsCodeGuidPathMatcher => new($"^{_prefixEscaped}\\/mappings\\/code\\/([0-9A-Fa-f]{{8}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{12}})$");
|
||||||
public RegexMatcher RequestsGuidPathMatcher => new($"^{_prefixEscaped}\\/requests\\/([0-9A-Fa-f]{{8}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{12}})$");
|
public RegexMatcher RequestsGuidPathMatcher => new($"^{_prefixEscaped}\\/requests\\/([0-9A-Fa-f]{{8}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{12}})$");
|
||||||
public RegexMatcher ScenariosNameMatcher => new($"^{_prefixEscaped}\\/scenarios\\/.+$");
|
public RegexMatcher ScenariosNameMatcher => new($"^{_prefixEscaped}\\/scenarios\\/.+$");
|
||||||
@@ -104,6 +102,12 @@ public partial class WireMockServer
|
|||||||
Given(Request.Create().WithPath(_adminPaths.MappingsGuidPathMatcher).UsingPut().WithHeader(HttpKnownHeaderNames.ContentType, AdminRequestContentTypeJson)).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingPut));
|
Given(Request.Create().WithPath(_adminPaths.MappingsGuidPathMatcher).UsingPut().WithHeader(HttpKnownHeaderNames.ContentType, AdminRequestContentTypeJson)).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingPut));
|
||||||
Given(Request.Create().WithPath(_adminPaths.MappingsGuidPathMatcher).UsingDelete()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingDelete));
|
Given(Request.Create().WithPath(_adminPaths.MappingsGuidPathMatcher).UsingDelete()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingDelete));
|
||||||
|
|
||||||
|
// __admin/mappings/{guid}/enable
|
||||||
|
Given(Request.Create().WithPath(_adminPaths.MappingsGuidEnablePathMatcher).UsingPut()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingEnable));
|
||||||
|
|
||||||
|
// __admin/mappings/{guid}/disable
|
||||||
|
Given(Request.Create().WithPath(_adminPaths.MappingsGuidDisablePathMatcher).UsingPut()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingDisable));
|
||||||
|
|
||||||
// __admin/mappings/code/{guid}
|
// __admin/mappings/code/{guid}
|
||||||
Given(Request.Create().WithPath(_adminPaths.MappingsCodeGuidPathMatcher).UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingCodeGet));
|
Given(Request.Create().WithPath(_adminPaths.MappingsCodeGuidPathMatcher).UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingCodeGet));
|
||||||
|
|
||||||
@@ -430,6 +434,47 @@ public partial class WireMockServer
|
|||||||
var lastPart = requestMessage.Path.Split('/').LastOrDefault();
|
var lastPart = requestMessage.Path.Split('/').LastOrDefault();
|
||||||
return Guid.TryParse(lastPart, out guid);
|
return Guid.TryParse(lastPart, out guid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool TryParseGuidFromSecondToLastSegment(IRequestMessage requestMessage, out Guid guid)
|
||||||
|
{
|
||||||
|
var parts = requestMessage.Path.Split('/');
|
||||||
|
if (parts.Length >= 2 && Guid.TryParse(parts[parts.Length - 2], out guid))
|
||||||
|
return true;
|
||||||
|
guid = Guid.Empty;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IResponseMessage MappingEnable(HttpContext _, IRequestMessage requestMessage)
|
||||||
|
{
|
||||||
|
if (TryParseGuidFromSecondToLastSegment(requestMessage, out var guid))
|
||||||
|
{
|
||||||
|
var mapping = Mappings.FirstOrDefault(m => !m.IsAdminInterface && m.Guid == guid);
|
||||||
|
if (mapping != null)
|
||||||
|
{
|
||||||
|
mapping.IsDisabled = false;
|
||||||
|
return ResponseMessageBuilder.Create(HttpStatusCode.OK, "Mapping enabled", guid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_settings.Logger.Warn("HttpStatusCode set to 404 : Mapping not found");
|
||||||
|
return ResponseMessageBuilder.Create(HttpStatusCode.NotFound, "Mapping not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
private IResponseMessage MappingDisable(HttpContext _, IRequestMessage requestMessage)
|
||||||
|
{
|
||||||
|
if (TryParseGuidFromSecondToLastSegment(requestMessage, out var guid))
|
||||||
|
{
|
||||||
|
var mapping = Mappings.FirstOrDefault(m => !m.IsAdminInterface && m.Guid == guid);
|
||||||
|
if (mapping != null)
|
||||||
|
{
|
||||||
|
mapping.IsDisabled = true;
|
||||||
|
return ResponseMessageBuilder.Create(HttpStatusCode.OK, "Mapping disabled", guid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_settings.Logger.Warn("HttpStatusCode set to 404 : Mapping not found");
|
||||||
|
return ResponseMessageBuilder.Create(HttpStatusCode.NotFound, "Mapping not found");
|
||||||
|
}
|
||||||
#endregion Mapping/{guid}
|
#endregion Mapping/{guid}
|
||||||
|
|
||||||
#region Mappings
|
#region Mappings
|
||||||
@@ -672,7 +717,7 @@ public partial class WireMockServer
|
|||||||
#region Scenarios
|
#region Scenarios
|
||||||
private IResponseMessage ScenariosGet(HttpContext _, IRequestMessage requestMessage)
|
private IResponseMessage ScenariosGet(HttpContext _, IRequestMessage requestMessage)
|
||||||
{
|
{
|
||||||
var scenariosStates = Scenarios.Values.Select(s => new ScenarioStateModel
|
var scenariosStates = Scenarios.Select(s => new ScenarioStateModel
|
||||||
{
|
{
|
||||||
Name = s.Name,
|
Name = s.Name,
|
||||||
NextState = s.NextState,
|
NextState = s.NextState,
|
||||||
@@ -705,7 +750,7 @@ public partial class WireMockServer
|
|||||||
private IResponseMessage ScenariosSetState(HttpContext _, IRequestMessage requestMessage)
|
private IResponseMessage ScenariosSetState(HttpContext _, IRequestMessage requestMessage)
|
||||||
{
|
{
|
||||||
var name = Enumerable.Reverse(requestMessage.Path.Split('/')).Skip(1).First();
|
var name = Enumerable.Reverse(requestMessage.Path.Split('/')).Skip(1).First();
|
||||||
if (!_options.Scenarios.ContainsKey(name))
|
if (!_options.ScenarioStateStore.ContainsKey(name))
|
||||||
{
|
{
|
||||||
ResponseMessageBuilder.Create(HttpStatusCode.NotFound, $"No scenario found by name '{name}'.");
|
ResponseMessageBuilder.Create(HttpStatusCode.NotFound, $"No scenario found by name '{name}'.");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -120,6 +120,11 @@ public partial class WireMockServer
|
|||||||
respondProvider.WithProbability(mappingModel.Probability.Value);
|
respondProvider.WithProbability(mappingModel.Probability.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mappingModel.IsDisabled == true)
|
||||||
|
{
|
||||||
|
respondProvider.WithIsDisabled(true);
|
||||||
|
}
|
||||||
|
|
||||||
// ProtoDefinition is defined at Mapping level
|
// ProtoDefinition is defined at Mapping level
|
||||||
if (mappingModel.ProtoDefinition != null)
|
if (mappingModel.ProtoDefinition != null)
|
||||||
{
|
{
|
||||||
@@ -263,6 +268,8 @@ public partial class WireMockServer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
requestBuilder = requestBuilder.WithEarlyMismatch(requestModel.EarlyMatcherType);
|
||||||
|
|
||||||
return requestBuilder;
|
return requestBuilder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -19,7 +19,7 @@ internal class WireMockListAccessor : IListAccessor, IObjectAccessor
|
|||||||
return target?.ToString() ?? string.Empty;
|
return target?.ToString() ?? string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetValue(TemplateContext context, SourceSpan span, object target, int index, object value)
|
public void SetValue(TemplateContext context, SourceSpan span, object target, int index, object? value)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
@@ -46,7 +46,7 @@ internal class WireMockListAccessor : IListAccessor, IObjectAccessor
|
|||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool TrySetValue(TemplateContext context, SourceSpan span, object target, string member, object value)
|
public bool TrySetValue(TemplateContext context, SourceSpan span, object target, string member, object? value)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
@@ -56,7 +56,7 @@ internal class WireMockListAccessor : IListAccessor, IObjectAccessor
|
|||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool TrySetItem(TemplateContext context, SourceSpan span, object target, object index, object value)
|
public bool TrySetItem(TemplateContext context, SourceSpan span, object target, object index, object? value)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ namespace WireMock.Transformers.Scriban;
|
|||||||
|
|
||||||
internal class WireMockTemplateContext : TemplateContext
|
internal class WireMockTemplateContext : TemplateContext
|
||||||
{
|
{
|
||||||
protected override IObjectAccessor GetMemberAccessorImpl(object target)
|
protected override IObjectAccessor? GetMemberAccessorImpl(object target)
|
||||||
{
|
{
|
||||||
return target?.GetType().GetGenericTypeDefinition() == typeof(WireMockList<>) ?
|
return target.GetType().GetGenericTypeDefinition() == typeof(WireMockList<>) ?
|
||||||
new WireMockListAccessor() :
|
new WireMockListAccessor() :
|
||||||
base.GetMemberAccessorImpl(target);
|
base.GetMemberAccessorImpl(target);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,7 @@
|
|||||||
<PackageReference Include="SimMetrics.Net" Version="1.0.5" />
|
<PackageReference Include="SimMetrics.Net" Version="1.0.5" />
|
||||||
<PackageReference Include="TinyMapper.Signed" Version="4.0.0" />
|
<PackageReference Include="TinyMapper.Signed" Version="4.0.0" />
|
||||||
<PackageReference Include="Microsoft.IdentityModel.Protocols.OpenIdConnect" Version="6.34.0" />
|
<PackageReference Include="Microsoft.IdentityModel.Protocols.OpenIdConnect" Version="6.34.0" />
|
||||||
<PackageReference Include="Scriban.Signed" Version="5.5.0" />
|
<PackageReference Include="Scriban.Signed" Version="7.0.6" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="JsonConverter.Newtonsoft.Json" Version="0.8.0" />
|
<PackageReference Include="JsonConverter.Newtonsoft.Json" Version="0.9.0" />
|
||||||
<PackageReference Include="NUnit" Version="4.4.0" />
|
<PackageReference Include="NUnit" Version="4.4.0" />
|
||||||
<PackageReference Include="Stef.Validation" Version="0.2.0" />
|
<PackageReference Include="Stef.Validation" Version="0.2.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -25,9 +25,9 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.14.0" />
|
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.15.3" />
|
||||||
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.14.0" />
|
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.15.2" />
|
||||||
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.14.0" />
|
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.15.3" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -163,6 +163,22 @@ public interface IWireMockAdminApi
|
|||||||
[Header("Content-Type", "application/json")]
|
[Header("Content-Type", "application/json")]
|
||||||
Task<StatusModel> PutMappingAsync([Path] Guid guid, [Body] MappingModel mapping, CancellationToken cancellationToken = default);
|
Task<StatusModel> PutMappingAsync([Path] Guid guid, [Body] MappingModel mapping, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enable a mapping based on the guid.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="guid">The Guid.</param>
|
||||||
|
/// <param name="cancellationToken">The optional cancellationToken.</param>
|
||||||
|
[Put("mappings/{guid}/enable")]
|
||||||
|
Task<StatusModel> EnableMappingAsync([Path] Guid guid, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Disable a mapping based on the guid.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="guid">The Guid.</param>
|
||||||
|
/// <param name="cancellationToken">The optional cancellationToken.</param>
|
||||||
|
[Put("mappings/{guid}/disable")]
|
||||||
|
Task<StatusModel> DisableMappingAsync([Path] Guid guid, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Delete a mapping based on the guid
|
/// Delete a mapping based on the guid
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="JsonConverter.Newtonsoft.Json" Version="0.8.0" />
|
<PackageReference Include="JsonConverter.Newtonsoft.Json" Version="0.9.0" />
|
||||||
<PackageReference Include="RestEase" Version="1.6.4" />
|
<PackageReference Include="RestEase" Version="1.6.4" />
|
||||||
<PackageReference Include="Stef.Validation" Version="0.2.0" />
|
<PackageReference Include="Stef.Validation" Version="0.2.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
// Copyright © WireMock.Net
|
||||||
|
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
|
namespace WireMock.Handlers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides an in-memory implementation of the <see cref="IScenarioStateStore" /> interface for managing scenario state objects by name.
|
||||||
|
/// </summary>
|
||||||
|
public class InMemoryScenarioStateStore : IScenarioStateStore
|
||||||
|
{
|
||||||
|
private readonly ConcurrentDictionary<string, ScenarioState> _scenarios = new(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool TryGet(string name, [NotNullWhen(true)] out ScenarioState? state)
|
||||||
|
{
|
||||||
|
return _scenarios.TryGetValue(name, out state);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IReadOnlyList<ScenarioState> GetAll()
|
||||||
|
{
|
||||||
|
return _scenarios.Values.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool ContainsKey(string name)
|
||||||
|
{
|
||||||
|
return _scenarios.ContainsKey(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool TryAdd(string name, ScenarioState scenarioState)
|
||||||
|
{
|
||||||
|
return _scenarios.TryAdd(name, scenarioState);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ScenarioState AddOrUpdate(string name, Func<string, ScenarioState> addFactory, Func<string, ScenarioState, ScenarioState> updateFactory)
|
||||||
|
{
|
||||||
|
return _scenarios.AddOrUpdate(name, addFactory, updateFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ScenarioState? Update(string name, Action<ScenarioState> updateAction)
|
||||||
|
{
|
||||||
|
if (_scenarios.TryGetValue(name, out var state))
|
||||||
|
{
|
||||||
|
updateAction(state);
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool TryRemove(string name)
|
||||||
|
{
|
||||||
|
return _scenarios.TryRemove(name, out _);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
_scenarios.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -108,6 +108,14 @@ public interface IMapping
|
|||||||
/// </value>
|
/// </value>
|
||||||
bool IsProxy { get; }
|
bool IsProxy { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether this mapping is disabled.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// <c>true</c> if this mapping is disabled; otherwise, <c>false</c>.
|
||||||
|
/// </value>
|
||||||
|
bool IsDisabled { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether this mapping to be logged.
|
/// Gets a value indicating whether this mapping to be logged.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -135,7 +143,7 @@ public interface IMapping
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
object? Data { get; }
|
object? Data { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The probability when this request should be matched. Value is between 0 and 1. [Optional]
|
/// The probability when this request should be matched. Value is between 0 and 1. [Optional]
|
||||||
/// </summary>
|
/// </summary>
|
||||||
double? Probability { get; }
|
double? Probability { get; }
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
// Copyright © WireMock.Net
|
// Copyright © WireMock.Net
|
||||||
|
|
||||||
using System.Linq;
|
|
||||||
using Stef.Validation;
|
using Stef.Validation;
|
||||||
using WireMock.Extensions;
|
using WireMock.Extensions;
|
||||||
using WireMock.Matchers.Request;
|
using WireMock.Matchers.Request;
|
||||||
@@ -119,7 +118,7 @@ public class MatchResult
|
|||||||
return new MatchDetail
|
return new MatchDetail
|
||||||
{
|
{
|
||||||
Name = Name,
|
Name = Name,
|
||||||
MatcherType = typeof(MatchResult),
|
MatcherType = typeof(MatchResult).Name,
|
||||||
Score = Score,
|
Score = Score,
|
||||||
Exception = Exception,
|
Exception = Exception,
|
||||||
MatchDetails = MatchResults?.Select(mr => mr.ToMatchDetail()).ToArray()
|
MatchDetails = MatchResults?.Select(mr => mr.ToMatchDetail()).ToArray()
|
||||||
|
|||||||
@@ -69,6 +69,9 @@ public class RequestMessageGraphQLMatcher : IRequestMatcher
|
|||||||
MatchOperator = matchOperator;
|
MatchOperator = matchOperator;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public RequestMatcherType Type => RequestMatcherType.GraphQL;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
|
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -30,6 +30,9 @@ public class RequestMessageProtoBufMatcher : IRequestMatcher
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public RequestMatcherType Type => RequestMatcherType.ProtoBuf;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
|
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ namespace WireMock.RequestBuilders;
|
|||||||
public interface IRequestBuilder : IClientIPRequestBuilder
|
public interface IRequestBuilder : IClientIPRequestBuilder
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds a request matcher to the builder.
|
/// Adds a request matcher to the builder.<br/>
|
||||||
|
/// If the request matcher is already present, it will be replaced.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T">The type of the request matcher.</typeparam>
|
/// <typeparam name="T">The type of the request matcher.</typeparam>
|
||||||
/// <param name="requestMatcher">The request matcher to add.</param>
|
/// <param name="requestMatcher">The request matcher to add.</param>
|
||||||
@@ -21,4 +22,11 @@ public interface IRequestBuilder : IClientIPRequestBuilder
|
|||||||
/// The link back to the Mapping.
|
/// The link back to the Mapping.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
IMapping Mapping { get; set; }
|
IMapping Mapping { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Chooses the <see cref="IRequestMatcher"/> which immediately returns a mismatch during mappings enumeration.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="earlyMatcherType">Selected type to choose the matcher from available list.</param>
|
||||||
|
/// <returns>The current <see cref="IRequestBuilder"/> instance.</returns>
|
||||||
|
IRequestBuilder WithEarlyMismatch(RequestMatcherType? earlyMatcherType);
|
||||||
}
|
}
|
||||||
@@ -175,6 +175,17 @@ public class WireMockServerSettings
|
|||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public IFileSystemHandler FileSystemHandler { get; set; } = null!;
|
public IFileSystemHandler FileSystemHandler { get; set; } = null!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the store used to persist scenario state information.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The scenario state store manages the storage and retrieval of state data associated with scenarios.
|
||||||
|
/// By default, an in-memory implementation is used, but this property can be set to a custom implementation to support alternative storage mechanisms such as databases or distributed caches.
|
||||||
|
/// </remarks>
|
||||||
|
[PublicAPI]
|
||||||
|
[JsonIgnore]
|
||||||
|
public IScenarioStateStore ScenarioStateStore { get; set; } = new InMemoryScenarioStateStore();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Action which can be used to add additional Handlebars registrations. [Optional]
|
/// Action which can be used to add additional Handlebars registrations. [Optional]
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -254,7 +265,7 @@ public class WireMockServerSettings
|
|||||||
/// Whether to accept any client certificate
|
/// Whether to accept any client certificate
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool AcceptAnyClientCertificate { get; set; }
|
public bool AcceptAnyClientCertificate { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defines the global IWebhookSettings to use.
|
/// Defines the global IWebhookSettings to use.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
<PackageReference Include="Stef.Validation" Version="0.2.0" />
|
<PackageReference Include="Stef.Validation" Version="0.2.0" />
|
||||||
<PackageReference Include="AnyOf" Version="0.5.0.1" />
|
<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" />
|
||||||
<PackageReference Include="JsonConverter.Newtonsoft.Json" Version="0.8.0" />
|
<PackageReference Include="JsonConverter.Newtonsoft.Json" Version="0.9.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// Copyright © WireMock.Net
|
// Copyright © WireMock.Net
|
||||||
|
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
using WireMock.Admin.Mappings;
|
using WireMock.Admin.Mappings;
|
||||||
using WireMock.Admin.Requests;
|
using WireMock.Admin.Requests;
|
||||||
using WireMock.Types;
|
using WireMock.Types;
|
||||||
@@ -9,10 +10,13 @@ namespace WireMock.Net.Json;
|
|||||||
|
|
||||||
[JsonSourceGenerationOptions(WriteIndented = true)]
|
[JsonSourceGenerationOptions(WriteIndented = true)]
|
||||||
[JsonSerializable(typeof(EncodingModel))]
|
[JsonSerializable(typeof(EncodingModel))]
|
||||||
|
[JsonSerializable(typeof(JArray))]
|
||||||
|
[JsonSerializable(typeof(JObject))]
|
||||||
[JsonSerializable(typeof(LogEntryModel))]
|
[JsonSerializable(typeof(LogEntryModel))]
|
||||||
[JsonSerializable(typeof(LogRequestModel))]
|
[JsonSerializable(typeof(LogRequestModel))]
|
||||||
[JsonSerializable(typeof(LogResponseModel))]
|
[JsonSerializable(typeof(LogResponseModel))]
|
||||||
[JsonSerializable(typeof(LogRequestMatchModel))]
|
[JsonSerializable(typeof(LogRequestMatchModel))]
|
||||||
|
[JsonSerializable(typeof(StatusModel))]
|
||||||
[JsonSerializable(typeof(WireMockList<string>))]
|
[JsonSerializable(typeof(WireMockList<string>))]
|
||||||
internal partial class SourceGenerationContext : JsonSerializerContext
|
internal partial class SourceGenerationContext : JsonSerializerContext
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
// Copyright © WireMock.Net
|
// Copyright © WireMock.Net
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using WireMock.Admin.Requests;
|
using WireMock.Admin.Requests;
|
||||||
@@ -11,11 +10,6 @@ namespace WireMock.Net;
|
|||||||
|
|
||||||
public class WireMockLogger : IWireMockLogger
|
public class WireMockLogger : IWireMockLogger
|
||||||
{
|
{
|
||||||
private readonly JsonSerializerOptions _options = new()
|
|
||||||
{
|
|
||||||
WriteIndented = true
|
|
||||||
};
|
|
||||||
|
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
public WireMockLogger(ILogger logger)
|
public WireMockLogger(ILogger logger)
|
||||||
|
|||||||
@@ -11,12 +11,12 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="AwesomeAssertions" Version="9.4.0" />
|
<PackageReference Include="AwesomeAssertions" Version="9.4.0" />
|
||||||
<PackageReference Include="coverlet.collector" Version="6.0.4">
|
<PackageReference Include="coverlet.collector" Version="8.0.1">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.3.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.3.0" />
|
||||||
<PackageReference Include="WireMock.Net" Version="1.25.0" />
|
<PackageReference Include="WireMock.Net" Version="2.1.0" />
|
||||||
<PackageReference Include="xunit.v3" Version="3.2.2" />
|
<PackageReference Include="xunit.v3" Version="3.2.2" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
|
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
|||||||
@@ -0,0 +1,134 @@
|
|||||||
|
// Copyright © WireMock.Net
|
||||||
|
|
||||||
|
using RestEase;
|
||||||
|
using WireMock.Admin.Mappings;
|
||||||
|
using WireMock.Client;
|
||||||
|
using WireMock.Server;
|
||||||
|
|
||||||
|
namespace WireMock.Net.Tests.AdminApi;
|
||||||
|
|
||||||
|
public partial class WireMockAdminApiTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task IWireMockAdminApi_PostMappingAsync_WithIsDisabledTrue_DoesNotMatchRequests()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var ct = TestContext.Current.CancellationToken;
|
||||||
|
using var server = WireMockServer.StartWithAdminInterface();
|
||||||
|
var api = RestClient.For<IWireMockAdminApi>(server.Urls[0]);
|
||||||
|
var httpClient = server.CreateClient();
|
||||||
|
|
||||||
|
var model = new MappingModel
|
||||||
|
{
|
||||||
|
Request = new RequestModel { Path = "/foo", Methods = ["GET"] },
|
||||||
|
Response = new ResponseModel { Body = "hello", StatusCode = 200 },
|
||||||
|
IsDisabled = true
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act — POST the disabled mapping
|
||||||
|
var postResult = await api.PostMappingAsync(model, ct);
|
||||||
|
postResult.Should().NotBeNull();
|
||||||
|
|
||||||
|
// Assert — request should not be matched (404)
|
||||||
|
var response = await httpClient.GetAsync("/foo", ct);
|
||||||
|
((int)response.StatusCode).Should().Be(404);
|
||||||
|
|
||||||
|
// Assert — mapping exists but IsDisabled is true
|
||||||
|
server.Mappings.Where(m => !m.IsAdminInterface).Should().ContainSingle(m => m.IsDisabled == true);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task IWireMockAdminApi_DisableMappingAsync_PreventsMatching()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var ct = TestContext.Current.CancellationToken;
|
||||||
|
using var server = WireMockServer.StartWithAdminInterface();
|
||||||
|
var api = RestClient.For<IWireMockAdminApi>(server.Urls[0]);
|
||||||
|
var httpClient = server.CreateClient();
|
||||||
|
|
||||||
|
var model = new MappingModel
|
||||||
|
{
|
||||||
|
Request = new RequestModel { Path = "/bar", Methods = ["GET"] },
|
||||||
|
Response = new ResponseModel { Body = "world", StatusCode = 200 }
|
||||||
|
};
|
||||||
|
var postResult = await api.PostMappingAsync(model, ct);
|
||||||
|
var guid = postResult.Guid!.Value;
|
||||||
|
|
||||||
|
// Assert — mapping matches before disable
|
||||||
|
var before = await httpClient.GetAsync("/bar", ct);
|
||||||
|
((int)before.StatusCode).Should().Be(200);
|
||||||
|
|
||||||
|
// Act — disable
|
||||||
|
var disableResult = await api.DisableMappingAsync(guid, ct);
|
||||||
|
disableResult.Status.Should().Be("Mapping disabled");
|
||||||
|
|
||||||
|
// Assert — no match after disable
|
||||||
|
var after = await httpClient.GetAsync("/bar", ct);
|
||||||
|
((int)after.StatusCode).Should().Be(404);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task IWireMockAdminApi_EnableMappingAsync_ResumesMatching()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var ct = TestContext.Current.CancellationToken;
|
||||||
|
using var server = WireMockServer.StartWithAdminInterface();
|
||||||
|
var api = RestClient.For<IWireMockAdminApi>(server.Urls[0]);
|
||||||
|
var httpClient = server.CreateClient();
|
||||||
|
|
||||||
|
var model = new MappingModel
|
||||||
|
{
|
||||||
|
Request = new RequestModel { Path = "/baz", Methods = ["GET"] },
|
||||||
|
Response = new ResponseModel { Body = "re-enabled", StatusCode = 200 },
|
||||||
|
IsDisabled = true
|
||||||
|
};
|
||||||
|
var postResult = await api.PostMappingAsync(model, ct);
|
||||||
|
var guid = postResult.Guid!.Value;
|
||||||
|
|
||||||
|
// Assert — no match while disabled
|
||||||
|
var before = await httpClient.GetAsync("/baz", ct);
|
||||||
|
((int)before.StatusCode).Should().Be(404);
|
||||||
|
|
||||||
|
// Act — enable
|
||||||
|
var enableResult = await api.EnableMappingAsync(guid, ct);
|
||||||
|
enableResult.Status.Should().Be("Mapping enabled");
|
||||||
|
|
||||||
|
// Assert — mapping matches after enable
|
||||||
|
var after = await httpClient.GetAsync("/baz", ct);
|
||||||
|
((int)after.StatusCode).Should().Be(200);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task IWireMockAdminApi_GetMappingAsync_ReturnsIsDisabledTrue_WhenDisabled()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var ct = TestContext.Current.CancellationToken;
|
||||||
|
using var server = WireMockServer.StartWithAdminInterface();
|
||||||
|
var api = RestClient.For<IWireMockAdminApi>(server.Urls[0]);
|
||||||
|
|
||||||
|
var disabledModel = new MappingModel
|
||||||
|
{
|
||||||
|
Request = new RequestModel { Path = "/check-disabled" },
|
||||||
|
Response = new ResponseModel { Body = "x", StatusCode = 200 },
|
||||||
|
IsDisabled = true
|
||||||
|
};
|
||||||
|
var enabledModel = new MappingModel
|
||||||
|
{
|
||||||
|
Request = new RequestModel { Path = "/check-enabled" },
|
||||||
|
Response = new ResponseModel { Body = "y", StatusCode = 200 }
|
||||||
|
};
|
||||||
|
|
||||||
|
var disabledPost = await api.PostMappingAsync(disabledModel, ct);
|
||||||
|
var enabledPost = await api.PostMappingAsync(enabledModel, ct);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var disabledGot = await api.GetMappingAsync(disabledPost.Guid!.Value, ct);
|
||||||
|
var enabledGot = await api.GetMappingAsync(enabledPost.Guid!.Value, ct);
|
||||||
|
|
||||||
|
// Assert — disabled mapping serializes IsDisabled = true
|
||||||
|
disabledGot.IsDisabled.Should().BeTrue();
|
||||||
|
|
||||||
|
// Assert — enabled mapping omits IsDisabled (null = default not disabled)
|
||||||
|
enabledGot.IsDisabled.Should().BeNull();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,5 +6,5 @@ internal static class Constants
|
|||||||
{
|
{
|
||||||
internal const int NumStaticMappings = 10;
|
internal const int NumStaticMappings = 10;
|
||||||
|
|
||||||
internal const int NumAdminMappings = 37;
|
internal const int NumAdminMappings = 39;
|
||||||
}
|
}
|
||||||
@@ -8,8 +8,11 @@ using ExampleIntegrationTest.Lookup;
|
|||||||
using Google.Protobuf.WellKnownTypes;
|
using Google.Protobuf.WellKnownTypes;
|
||||||
using Greet;
|
using Greet;
|
||||||
using Grpc.Net.Client;
|
using Grpc.Net.Client;
|
||||||
|
using Moq;
|
||||||
using WireMock.Constants;
|
using WireMock.Constants;
|
||||||
using WireMock.Matchers;
|
using WireMock.Matchers;
|
||||||
|
using WireMock.Matchers.Request;
|
||||||
|
using WireMock.Net.Xunit;
|
||||||
using WireMock.RequestBuilders;
|
using WireMock.RequestBuilders;
|
||||||
using WireMock.ResponseBuilders;
|
using WireMock.ResponseBuilders;
|
||||||
using WireMock.Server;
|
using WireMock.Server;
|
||||||
@@ -78,7 +81,7 @@ import ""google/protobuf/empty.proto"";
|
|||||||
|
|
||||||
service Greeter {
|
service Greeter {
|
||||||
rpc Nothing (google.protobuf.Empty) returns (google.protobuf.Empty);
|
rpc Nothing (google.protobuf.Empty) returns (google.protobuf.Empty);
|
||||||
|
|
||||||
rpc SayHello (HelloRequest) returns (HelloReply);
|
rpc SayHello (HelloRequest) returns (HelloReply);
|
||||||
|
|
||||||
rpc SayOther (Other) returns (HelloReply);
|
rpc SayOther (Other) returns (HelloReply);
|
||||||
@@ -731,6 +734,93 @@ message Other {
|
|||||||
Then_ReplyMessage_Should_BeCorrect(reply);
|
Then_ReplyMessage_Should_BeCorrect(reply);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(true)]
|
||||||
|
[InlineData(false)]
|
||||||
|
public async Task WireMockServer_WithBodyAsProtoBuf_WithEarlyMismatch_ErrorLogs_Issue1442(
|
||||||
|
bool withEarlyMismatch)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var greeterId = $"test-greeter-{Guid.NewGuid()}";
|
||||||
|
var policyId = $"test-policy-{Guid.NewGuid()}";
|
||||||
|
var ct = TestContext.Current.CancellationToken;
|
||||||
|
|
||||||
|
var greeterProtoDefinition = ProtoDefinition;
|
||||||
|
var policyProtoDefinition = File.ReadAllText("./Grpc/policy.proto");
|
||||||
|
|
||||||
|
var mockTestOutputHelper = new Mock<ITestOutputHelper>();
|
||||||
|
using var server = WireMockServer.Start(new WireMockServerSettings
|
||||||
|
{
|
||||||
|
UseHttp2 = true,
|
||||||
|
Logger = new TestOutputHelperWireMockLogger(mockTestOutputHelper.Object)
|
||||||
|
});
|
||||||
|
|
||||||
|
server
|
||||||
|
.AddProtoDefinition(greeterId, greeterProtoDefinition)
|
||||||
|
.Given(Request.Create()
|
||||||
|
.UsingPost()
|
||||||
|
.WithHttpVersion("2")
|
||||||
|
.WithPath("/greet.Greeter/SayHello")
|
||||||
|
.WithEarlyMismatch(withEarlyMismatch
|
||||||
|
? RequestMatcherType.Path
|
||||||
|
: null)
|
||||||
|
.WithBodyAsProtoBuf("greet.HelloRequest", new JsonMatcher(new { name = "stef" })))
|
||||||
|
.WithProtoDefinition(greeterId)
|
||||||
|
.RespondWith(Response.Create()
|
||||||
|
.WithHeader("Content-Type", "application/grpc")
|
||||||
|
.WithTrailingHeader("grpc-status", "0")
|
||||||
|
.WithBodyAsProtoBuf("greet.HelloReply",
|
||||||
|
new
|
||||||
|
{
|
||||||
|
message = "hello {{request.BodyAsJson.name}} {{request.method}}"
|
||||||
|
})
|
||||||
|
.WithTransformer());
|
||||||
|
|
||||||
|
server
|
||||||
|
.AddProtoDefinition(policyId, policyProtoDefinition)
|
||||||
|
.Given(Request.Create()
|
||||||
|
.UsingPost()
|
||||||
|
.WithHttpVersion("2")
|
||||||
|
.WithPath("/Policy.PolicyService/GetVersion")
|
||||||
|
.WithEarlyMismatch(withEarlyMismatch
|
||||||
|
? RequestMatcherType.Path
|
||||||
|
: null)
|
||||||
|
.WithBodyAsProtoBuf("ExampleIntegrationTest.Lookup.GetVersionRequest", new NotNullOrEmptyMatcher()))
|
||||||
|
.WithProtoDefinition(policyId)
|
||||||
|
.RespondWith(Response.Create()
|
||||||
|
.WithHeader("Content-Type", "application/grpc")
|
||||||
|
.WithTrailingHeader("grpc-status", "0")
|
||||||
|
.WithBodyAsProtoBuf("ExampleIntegrationTest.Lookup.GetVersionResponse",
|
||||||
|
new GetVersionResponse
|
||||||
|
{
|
||||||
|
Version = "test",
|
||||||
|
DateHired = new Timestamp
|
||||||
|
{
|
||||||
|
Seconds = 1722301323,
|
||||||
|
Nanos = 12300
|
||||||
|
},
|
||||||
|
Client = new ExampleIntegrationTest.Lookup.Client
|
||||||
|
{
|
||||||
|
ClientName = ExampleIntegrationTest.Lookup.Client.Types.Clients.Test,
|
||||||
|
CorrelationId = "correlation"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.WithTransformer());
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var channel = GrpcChannel.ForAddress(server.Url!);
|
||||||
|
var policyServiceClient = new PolicyService.PolicyServiceClient(channel);
|
||||||
|
var greeterClient = new Greeter.GreeterClient(channel);
|
||||||
|
|
||||||
|
_ = await policyServiceClient.GetVersionAsync(new GetVersionRequest(), cancellationToken: ct);
|
||||||
|
_ = await greeterClient.SayHelloAsync(new HelloRequest { Name = "stef" }, cancellationToken: ct);
|
||||||
|
|
||||||
|
mockTestOutputHelper.Verify(
|
||||||
|
x => x.WriteLine(
|
||||||
|
It.Is<string>(log => log.Contains("[Error]") && log.Contains("Exception"))),
|
||||||
|
withEarlyMismatch ? Times.Never : Times.AtLeastOnce);
|
||||||
|
}
|
||||||
|
|
||||||
private static WireMockServer Given_When_ServerStarted_And_RunningOnHttpAndGrpc()
|
private static WireMockServer Given_When_ServerStarted_And_RunningOnHttpAndGrpc()
|
||||||
{
|
{
|
||||||
var settings = new WireMockServerSettings
|
var settings = new WireMockServerSettings
|
||||||
|
|||||||
@@ -0,0 +1,273 @@
|
|||||||
|
// Copyright © WireMock.Net
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using WireMock.Handlers;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace WireMock.Net.Tests.Handlers;
|
||||||
|
|
||||||
|
public class FileBasedScenarioStateStoreTests : IDisposable
|
||||||
|
{
|
||||||
|
private readonly string _tempFolder;
|
||||||
|
private readonly string _scenariosFolder;
|
||||||
|
|
||||||
|
public FileBasedScenarioStateStoreTests()
|
||||||
|
{
|
||||||
|
_tempFolder = Path.Combine(Path.GetTempPath(), "WireMock_Tests_" + Guid.NewGuid().ToString("N"));
|
||||||
|
_scenariosFolder = Path.Combine(_tempFolder, "__admin", "scenarios");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (Directory.Exists(_tempFolder))
|
||||||
|
{
|
||||||
|
Directory.Delete(_tempFolder, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private FileBasedScenarioStateStore CreateSut() => new(_tempFolder);
|
||||||
|
|
||||||
|
// --- Mirror tests from InMemoryScenarioStateStoreTests ---
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TryAdd_ShouldAddNewScenario()
|
||||||
|
{
|
||||||
|
var sut = CreateSut();
|
||||||
|
var state = new ScenarioState { Name = "scenario1" };
|
||||||
|
|
||||||
|
sut.TryAdd("scenario1", state).Should().BeTrue();
|
||||||
|
|
||||||
|
sut.ContainsKey("scenario1").Should().BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TryAdd_ShouldReturnFalse_WhenScenarioAlreadyExists()
|
||||||
|
{
|
||||||
|
var sut = CreateSut();
|
||||||
|
var state = new ScenarioState { Name = "scenario1" };
|
||||||
|
sut.TryAdd("scenario1", state);
|
||||||
|
|
||||||
|
sut.TryAdd("scenario1", new ScenarioState { Name = "scenario1" }).Should().BeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TryGet_ShouldReturnTrue_WhenExists()
|
||||||
|
{
|
||||||
|
var sut = CreateSut();
|
||||||
|
var state = new ScenarioState { Name = "scenario1", NextState = "state2" };
|
||||||
|
sut.TryAdd("scenario1", state);
|
||||||
|
|
||||||
|
sut.TryGet("scenario1", out var result).Should().BeTrue();
|
||||||
|
|
||||||
|
result.Should().NotBeNull();
|
||||||
|
result!.NextState.Should().Be("state2");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TryGet_ShouldReturnFalse_WhenNotExists()
|
||||||
|
{
|
||||||
|
var sut = CreateSut();
|
||||||
|
sut.TryGet("nonexistent", out var result).Should().BeFalse();
|
||||||
|
result.Should().BeNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetAll_ShouldReturnAllScenarios()
|
||||||
|
{
|
||||||
|
var sut = CreateSut();
|
||||||
|
sut.TryAdd("scenario1", new ScenarioState { Name = "scenario1" });
|
||||||
|
sut.TryAdd("scenario2", new ScenarioState { Name = "scenario2" });
|
||||||
|
|
||||||
|
var result = sut.GetAll();
|
||||||
|
|
||||||
|
result.Should().HaveCount(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetAll_ShouldReturnEmpty_WhenNoScenarios()
|
||||||
|
{
|
||||||
|
var sut = CreateSut();
|
||||||
|
sut.GetAll().Should().BeEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Update_ShouldModifyExistingScenario()
|
||||||
|
{
|
||||||
|
var sut = CreateSut();
|
||||||
|
sut.TryAdd("scenario1", new ScenarioState { Name = "scenario1", Counter = 0 });
|
||||||
|
|
||||||
|
var result = sut.Update("scenario1", s => { s.Counter = 5; s.NextState = "state2"; });
|
||||||
|
|
||||||
|
result.Should().NotBeNull();
|
||||||
|
result!.Counter.Should().Be(5);
|
||||||
|
result.NextState.Should().Be("state2");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Update_ShouldReturnNull_WhenNotExists()
|
||||||
|
{
|
||||||
|
var sut = CreateSut();
|
||||||
|
sut.Update("nonexistent", s => { s.Counter = 5; }).Should().BeNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AddOrUpdate_ShouldAddNewScenario()
|
||||||
|
{
|
||||||
|
var sut = CreateSut();
|
||||||
|
var result = sut.AddOrUpdate(
|
||||||
|
"scenario1",
|
||||||
|
_ => new ScenarioState { Name = "scenario1", NextState = "added" },
|
||||||
|
(_, current) => { current.NextState = "updated"; return current; }
|
||||||
|
);
|
||||||
|
|
||||||
|
result.NextState.Should().Be("added");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AddOrUpdate_ShouldUpdateExistingScenario()
|
||||||
|
{
|
||||||
|
var sut = CreateSut();
|
||||||
|
sut.TryAdd("scenario1", new ScenarioState { Name = "scenario1", NextState = "initial" });
|
||||||
|
|
||||||
|
var result = sut.AddOrUpdate(
|
||||||
|
"scenario1",
|
||||||
|
_ => new ScenarioState { Name = "scenario1", NextState = "added" },
|
||||||
|
(_, current) => { current.NextState = "updated"; return current; }
|
||||||
|
);
|
||||||
|
|
||||||
|
result.NextState.Should().Be("updated");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TryRemove_ShouldRemoveExistingScenario()
|
||||||
|
{
|
||||||
|
var sut = CreateSut();
|
||||||
|
sut.TryAdd("scenario1", new ScenarioState { Name = "scenario1" });
|
||||||
|
|
||||||
|
sut.TryRemove("scenario1").Should().BeTrue();
|
||||||
|
sut.ContainsKey("scenario1").Should().BeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TryRemove_ShouldReturnFalse_WhenNotExists()
|
||||||
|
{
|
||||||
|
var sut = CreateSut();
|
||||||
|
sut.TryRemove("nonexistent").Should().BeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Clear_ShouldRemoveAllScenarios()
|
||||||
|
{
|
||||||
|
var sut = CreateSut();
|
||||||
|
sut.TryAdd("scenario1", new ScenarioState { Name = "scenario1" });
|
||||||
|
sut.TryAdd("scenario2", new ScenarioState { Name = "scenario2" });
|
||||||
|
|
||||||
|
sut.Clear();
|
||||||
|
|
||||||
|
sut.GetAll().Should().BeEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ContainsKey_ShouldBeCaseInsensitive()
|
||||||
|
{
|
||||||
|
var sut = CreateSut();
|
||||||
|
sut.TryAdd("Scenario1", new ScenarioState { Name = "Scenario1" });
|
||||||
|
|
||||||
|
sut.ContainsKey("scenario1").Should().BeTrue();
|
||||||
|
sut.ContainsKey("SCENARIO1").Should().BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TryGet_ShouldBeCaseInsensitive()
|
||||||
|
{
|
||||||
|
var sut = CreateSut();
|
||||||
|
sut.TryAdd("Scenario1", new ScenarioState { Name = "Scenario1", NextState = "state2" });
|
||||||
|
|
||||||
|
sut.TryGet("scenario1", out var result1).Should().BeTrue();
|
||||||
|
result1!.NextState.Should().Be("state2");
|
||||||
|
|
||||||
|
sut.TryGet("SCENARIO1", out var result2).Should().BeTrue();
|
||||||
|
result2!.NextState.Should().Be("state2");
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- File-persistence-specific tests ---
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TryAdd_ShouldCreateJsonFileOnDisk()
|
||||||
|
{
|
||||||
|
var sut = CreateSut();
|
||||||
|
sut.TryAdd("scenario1", new ScenarioState { Name = "scenario1", NextState = "state2" });
|
||||||
|
|
||||||
|
var filePath = Path.Combine(_scenariosFolder, "scenario1.json");
|
||||||
|
File.Exists(filePath).Should().BeTrue();
|
||||||
|
|
||||||
|
var json = File.ReadAllText(filePath);
|
||||||
|
var deserialized = JsonConvert.DeserializeObject<ScenarioState>(json);
|
||||||
|
deserialized!.Name.Should().Be("scenario1");
|
||||||
|
deserialized.NextState.Should().Be("state2");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TryRemove_ShouldDeleteJsonFileFromDisk()
|
||||||
|
{
|
||||||
|
var sut = CreateSut();
|
||||||
|
sut.TryAdd("scenario1", new ScenarioState { Name = "scenario1" });
|
||||||
|
|
||||||
|
var filePath = Path.Combine(_scenariosFolder, "scenario1.json");
|
||||||
|
File.Exists(filePath).Should().BeTrue();
|
||||||
|
|
||||||
|
sut.TryRemove("scenario1");
|
||||||
|
|
||||||
|
File.Exists(filePath).Should().BeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Clear_ShouldDeleteAllJsonFilesFromDisk()
|
||||||
|
{
|
||||||
|
var sut = CreateSut();
|
||||||
|
sut.TryAdd("scenario1", new ScenarioState { Name = "scenario1" });
|
||||||
|
sut.TryAdd("scenario2", new ScenarioState { Name = "scenario2" });
|
||||||
|
|
||||||
|
Directory.GetFiles(_scenariosFolder, "*.json").Should().HaveCount(2);
|
||||||
|
|
||||||
|
sut.Clear();
|
||||||
|
|
||||||
|
Directory.GetFiles(_scenariosFolder, "*.json").Should().BeEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_ShouldLoadExistingScenariosFromDisk()
|
||||||
|
{
|
||||||
|
// Pre-write JSON files before constructing the store
|
||||||
|
Directory.CreateDirectory(_scenariosFolder);
|
||||||
|
var state1 = new ScenarioState { Name = "scenario1", NextState = "loaded1" };
|
||||||
|
var state2 = new ScenarioState { Name = "scenario2", NextState = "loaded2", Counter = 3 };
|
||||||
|
File.WriteAllText(Path.Combine(_scenariosFolder, "scenario1.json"), JsonConvert.SerializeObject(state1));
|
||||||
|
File.WriteAllText(Path.Combine(_scenariosFolder, "scenario2.json"), JsonConvert.SerializeObject(state2));
|
||||||
|
|
||||||
|
var sut = CreateSut();
|
||||||
|
|
||||||
|
sut.GetAll().Should().HaveCount(2);
|
||||||
|
sut.TryGet("scenario1", out var loaded1).Should().BeTrue();
|
||||||
|
loaded1!.NextState.Should().Be("loaded1");
|
||||||
|
sut.TryGet("scenario2", out var loaded2).Should().BeTrue();
|
||||||
|
loaded2!.Counter.Should().Be(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Update_ShouldPersistChangesToDisk()
|
||||||
|
{
|
||||||
|
var sut = CreateSut();
|
||||||
|
sut.TryAdd("scenario1", new ScenarioState { Name = "scenario1", Counter = 0 });
|
||||||
|
|
||||||
|
sut.Update("scenario1", s => { s.Counter = 10; s.NextState = "persisted"; });
|
||||||
|
|
||||||
|
var filePath = Path.Combine(_scenariosFolder, "scenario1.json");
|
||||||
|
var json = File.ReadAllText(filePath);
|
||||||
|
var deserialized = JsonConvert.DeserializeObject<ScenarioState>(json);
|
||||||
|
deserialized!.Counter.Should().Be(10);
|
||||||
|
deserialized.NextState.Should().Be("persisted");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,157 @@
|
|||||||
|
// Copyright © WireMock.Net
|
||||||
|
|
||||||
|
using WireMock.Handlers;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace WireMock.Net.Tests.Handlers;
|
||||||
|
|
||||||
|
public class InMemoryScenarioStateStoreTests
|
||||||
|
{
|
||||||
|
private readonly InMemoryScenarioStateStore _sut = new();
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TryAdd_ShouldAddNewScenario()
|
||||||
|
{
|
||||||
|
var state = new ScenarioState { Name = "scenario1" };
|
||||||
|
|
||||||
|
_sut.TryAdd("scenario1", state).Should().BeTrue();
|
||||||
|
|
||||||
|
_sut.ContainsKey("scenario1").Should().BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TryAdd_ShouldReturnFalse_WhenScenarioAlreadyExists()
|
||||||
|
{
|
||||||
|
var state = new ScenarioState { Name = "scenario1" };
|
||||||
|
_sut.TryAdd("scenario1", state);
|
||||||
|
|
||||||
|
_sut.TryAdd("scenario1", new ScenarioState { Name = "scenario1" }).Should().BeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TryGet_ShouldReturnTrue_WhenExists()
|
||||||
|
{
|
||||||
|
var state = new ScenarioState { Name = "scenario1", NextState = "state2" };
|
||||||
|
_sut.TryAdd("scenario1", state);
|
||||||
|
|
||||||
|
_sut.TryGet("scenario1", out var result).Should().BeTrue();
|
||||||
|
|
||||||
|
result.Should().NotBeNull();
|
||||||
|
result!.NextState.Should().Be("state2");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TryGet_ShouldReturnFalse_WhenNotExists()
|
||||||
|
{
|
||||||
|
_sut.TryGet("nonexistent", out var result).Should().BeFalse();
|
||||||
|
result.Should().BeNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetAll_ShouldReturnAllScenarios()
|
||||||
|
{
|
||||||
|
_sut.TryAdd("scenario1", new ScenarioState { Name = "scenario1" });
|
||||||
|
_sut.TryAdd("scenario2", new ScenarioState { Name = "scenario2" });
|
||||||
|
|
||||||
|
var result = _sut.GetAll();
|
||||||
|
|
||||||
|
result.Should().HaveCount(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetAll_ShouldReturnEmpty_WhenNoScenarios()
|
||||||
|
{
|
||||||
|
_sut.GetAll().Should().BeEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Update_ShouldModifyExistingScenario()
|
||||||
|
{
|
||||||
|
_sut.TryAdd("scenario1", new ScenarioState { Name = "scenario1", Counter = 0 });
|
||||||
|
|
||||||
|
var result = _sut.Update("scenario1", s => { s.Counter = 5; s.NextState = "state2"; });
|
||||||
|
|
||||||
|
result.Should().NotBeNull();
|
||||||
|
result!.Counter.Should().Be(5);
|
||||||
|
result.NextState.Should().Be("state2");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Update_ShouldReturnNull_WhenNotExists()
|
||||||
|
{
|
||||||
|
_sut.Update("nonexistent", s => { s.Counter = 5; }).Should().BeNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AddOrUpdate_ShouldAddNewScenario()
|
||||||
|
{
|
||||||
|
var result = _sut.AddOrUpdate(
|
||||||
|
"scenario1",
|
||||||
|
_ => new ScenarioState { Name = "scenario1", NextState = "added" },
|
||||||
|
(_, current) => { current.NextState = "updated"; return current; }
|
||||||
|
);
|
||||||
|
|
||||||
|
result.NextState.Should().Be("added");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AddOrUpdate_ShouldUpdateExistingScenario()
|
||||||
|
{
|
||||||
|
_sut.TryAdd("scenario1", new ScenarioState { Name = "scenario1", NextState = "initial" });
|
||||||
|
|
||||||
|
var result = _sut.AddOrUpdate(
|
||||||
|
"scenario1",
|
||||||
|
_ => new ScenarioState { Name = "scenario1", NextState = "added" },
|
||||||
|
(_, current) => { current.NextState = "updated"; return current; }
|
||||||
|
);
|
||||||
|
|
||||||
|
result.NextState.Should().Be("updated");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TryRemove_ShouldRemoveExistingScenario()
|
||||||
|
{
|
||||||
|
_sut.TryAdd("scenario1", new ScenarioState { Name = "scenario1" });
|
||||||
|
|
||||||
|
_sut.TryRemove("scenario1").Should().BeTrue();
|
||||||
|
_sut.ContainsKey("scenario1").Should().BeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TryRemove_ShouldReturnFalse_WhenNotExists()
|
||||||
|
{
|
||||||
|
_sut.TryRemove("nonexistent").Should().BeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Clear_ShouldRemoveAllScenarios()
|
||||||
|
{
|
||||||
|
_sut.TryAdd("scenario1", new ScenarioState { Name = "scenario1" });
|
||||||
|
_sut.TryAdd("scenario2", new ScenarioState { Name = "scenario2" });
|
||||||
|
|
||||||
|
_sut.Clear();
|
||||||
|
|
||||||
|
_sut.GetAll().Should().BeEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ContainsKey_ShouldBeCaseInsensitive()
|
||||||
|
{
|
||||||
|
_sut.TryAdd("Scenario1", new ScenarioState { Name = "Scenario1" });
|
||||||
|
|
||||||
|
_sut.ContainsKey("scenario1").Should().BeTrue();
|
||||||
|
_sut.ContainsKey("SCENARIO1").Should().BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TryGet_ShouldBeCaseInsensitive()
|
||||||
|
{
|
||||||
|
_sut.TryAdd("Scenario1", new ScenarioState { Name = "Scenario1", NextState = "state2" });
|
||||||
|
|
||||||
|
_sut.TryGet("scenario1", out var result1).Should().BeTrue();
|
||||||
|
result1!.NextState.Should().Be("state2");
|
||||||
|
|
||||||
|
_sut.TryGet("SCENARIO1", out var result2).Should().BeTrue();
|
||||||
|
result2!.NextState.Should().Be("state2");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -27,7 +27,7 @@ public class HttpRequestMessageHelperTests
|
|||||||
var message = HttpRequestMessageHelper.Create(request, "http://url");
|
var message = HttpRequestMessageHelper.Create(request, "http://url");
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
message.Headers.GetValues("x").Should().Equal(new[] { "value-1" });
|
message.Headers.GetValues("x").Should().Equal(["value-1"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -101,7 +101,7 @@ public class HttpRequestMessageHelperTests
|
|||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
(await message.Content!.ReadAsStringAsync(_ct)).Should().Be("{\"x\":42}");
|
(await message.Content!.ReadAsStringAsync(_ct)).Should().Be("{\"x\":42}");
|
||||||
message.Content.Headers.GetValues("Content-Type").Should().Equal(new[] { "application/json" });
|
message.Content.Headers.GetValues("Content-Type").Should().Equal(["application/json"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -121,7 +121,7 @@ public class HttpRequestMessageHelperTests
|
|||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
(await message.Content!.ReadAsStringAsync(_ct)).Should().Be("{\"x\":42}");
|
(await message.Content!.ReadAsStringAsync(_ct)).Should().Be("{\"x\":42}");
|
||||||
message.Content.Headers.GetValues("Content-Type").Should().Equal(new[] { "application/json; charset=utf-8" });
|
message.Content.Headers.GetValues("Content-Type").Should().Equal(["application/json; charset=utf-8"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -142,7 +142,7 @@ public class HttpRequestMessageHelperTests
|
|||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
(await message.Content!.ReadAsStringAsync(_ct)).Should().Be("{\"x\":42}");
|
(await message.Content!.ReadAsStringAsync(_ct)).Should().Be("{\"x\":42}");
|
||||||
message.Content.Headers.GetValues("Content-Type").Should().Equal(new[] { "multipart/form-data" });
|
message.Content.Headers.GetValues("Content-Type").Should().Equal(["multipart/form-data"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -162,7 +162,7 @@ public class HttpRequestMessageHelperTests
|
|||||||
var message = HttpRequestMessageHelper.Create(request, "http://url");
|
var message = HttpRequestMessageHelper.Create(request, "http://url");
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
message.Content!.Headers.GetValues("Content-Type").Should().Equal(new[] { "application/xml" });
|
message.Content!.Headers.GetValues("Content-Type").Should().Equal(["application/xml"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -181,7 +181,7 @@ public class HttpRequestMessageHelperTests
|
|||||||
var message = HttpRequestMessageHelper.Create(request, "http://url");
|
var message = HttpRequestMessageHelper.Create(request, "http://url");
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
message.Content!.Headers.GetValues("Content-Type").Should().Equal(new[] { "application/xml; charset=UTF-8" });
|
message.Content!.Headers.GetValues("Content-Type").Should().Equal(["application/xml; charset=UTF-8"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -200,7 +200,7 @@ public class HttpRequestMessageHelperTests
|
|||||||
var message = HttpRequestMessageHelper.Create(request, "http://url");
|
var message = HttpRequestMessageHelper.Create(request, "http://url");
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
message.Content!.Headers.GetValues("Content-Type").Should().Equal(new[] { "application/xml; charset=Ascii" });
|
message.Content!.Headers.GetValues("Content-Type").Should().Equal(["application/xml; charset=Ascii"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -242,7 +242,7 @@ public class HttpRequestMessageHelperTests
|
|||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
(await message.Content!.ReadAsStringAsync(_ct)).Should().Be(body);
|
(await message.Content!.ReadAsStringAsync(_ct)).Should().Be(body);
|
||||||
message.Content.Headers.GetValues("Content-Type").Should().Equal(new[] { "multipart/form-data" });
|
message.Content.Headers.GetValues("Content-Type").Should().Equal(["multipart/form-data"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
@@ -269,7 +269,4 @@ public class HttpRequestMessageHelperTests
|
|||||||
// Assert
|
// Assert
|
||||||
message.Content?.Headers.ContentLength.Should().Be(resultShouldBe ? value : null);
|
message.Content?.Headers.ContentLength.Should().Be(resultShouldBe ? value : null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -9,6 +9,8 @@ using WireMock.Util;
|
|||||||
using WireMock.Owin;
|
using WireMock.Owin;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.Extensions.Primitives;
|
using Microsoft.Extensions.Primitives;
|
||||||
|
using JsonConverter.Newtonsoft.Json;
|
||||||
|
using JsonConverter.System.Text.Json;
|
||||||
|
|
||||||
namespace WireMock.Net.Tests.Owin.Mappers;
|
namespace WireMock.Net.Tests.Owin.Mappers;
|
||||||
|
|
||||||
@@ -34,6 +36,7 @@ public class OwinResponseMapperTests
|
|||||||
_optionsMock = new Mock<IWireMockMiddlewareOptions>();
|
_optionsMock = new Mock<IWireMockMiddlewareOptions>();
|
||||||
_optionsMock.SetupAllProperties();
|
_optionsMock.SetupAllProperties();
|
||||||
_optionsMock.SetupGet(o => o.FileSystemHandler).Returns(_fileSystemHandlerMock.Object);
|
_optionsMock.SetupGet(o => o.FileSystemHandler).Returns(_fileSystemHandlerMock.Object);
|
||||||
|
_optionsMock.SetupGet(o => o.DefaultJsonSerializer).Returns(new NewtonsoftJsonConverter());
|
||||||
|
|
||||||
_headers = new Mock<IHeaderDictionary>();
|
_headers = new Mock<IHeaderDictionary>();
|
||||||
_headers.SetupAllProperties();
|
_headers.SetupAllProperties();
|
||||||
@@ -186,7 +189,7 @@ public class OwinResponseMapperTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task OwinResponseMapper_MapAsync_BodyAsJson()
|
public async Task OwinResponseMapper_MapAsync_BodyAsJson_UsingNewtonsoftJson()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var json = new { t = "x", i = (string?)null };
|
var json = new { t = "x", i = (string?)null };
|
||||||
@@ -203,6 +206,25 @@ public class OwinResponseMapperTests
|
|||||||
_stream.Verify(s => s.WriteAsync(new byte[] { 123, 34, 116, 34, 58, 34, 120, 34, 125 }, 0, 9, It.IsAny<CancellationToken>()), Times.Once);
|
_stream.Verify(s => s.WriteAsync(new byte[] { 123, 34, 116, 34, 58, 34, 120, 34, 125 }, 0, 9, It.IsAny<CancellationToken>()), Times.Once);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task OwinResponseMapper_MapAsync_BodyAsJson_UsingSystemTextJson()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var json = new { t = "x", i = (string?)null };
|
||||||
|
var responseMessage = new ResponseMessage
|
||||||
|
{
|
||||||
|
Headers = new Dictionary<string, WireMockList<string>>(),
|
||||||
|
BodyData = new BodyData { DetectedBodyType = BodyType.Json, BodyAsJson = json, BodyAsJsonIndented = false }
|
||||||
|
};
|
||||||
|
_optionsMock.SetupGet(o => o.DefaultJsonSerializer).Returns(new SystemTextJsonConverter());
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await _sut.MapAsync(responseMessage, _responseMock.Object);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
_stream.Verify(s => s.WriteAsync(new byte[] { 123, 34, 116, 34, 58, 34, 120, 34, 125 }, 0, 9, It.IsAny<CancellationToken>()), Times.Once);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task OwinResponseMapper_MapAsync_SetResponseHeaders()
|
public async Task OwinResponseMapper_MapAsync_SetResponseHeaders()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using Moq;
|
using Moq;
|
||||||
|
using WireMock.Handlers;
|
||||||
using WireMock.Logging;
|
using WireMock.Logging;
|
||||||
using WireMock.Matchers.Request;
|
using WireMock.Matchers.Request;
|
||||||
using WireMock.Models;
|
using WireMock.Models;
|
||||||
@@ -23,7 +24,7 @@ public class MappingMatcherTests
|
|||||||
_optionsMock.SetupAllProperties();
|
_optionsMock.SetupAllProperties();
|
||||||
_optionsMock.Setup(o => o.Mappings).Returns(new ConcurrentDictionary<Guid, IMapping>());
|
_optionsMock.Setup(o => o.Mappings).Returns(new ConcurrentDictionary<Guid, IMapping>());
|
||||||
_optionsMock.Setup(o => o.LogEntries).Returns([]);
|
_optionsMock.Setup(o => o.LogEntries).Returns([]);
|
||||||
_optionsMock.Setup(o => o.Scenarios).Returns(new ConcurrentDictionary<string, ScenarioState>());
|
_optionsMock.Setup(o => o.ScenarioStateStore).Returns(new InMemoryScenarioStateStore());
|
||||||
|
|
||||||
var loggerMock = new Mock<IWireMockLogger>();
|
var loggerMock = new Mock<IWireMockLogger>();
|
||||||
loggerMock.SetupAllProperties();
|
loggerMock.SetupAllProperties();
|
||||||
@@ -55,6 +56,7 @@ public class MappingMatcherTests
|
|||||||
{
|
{
|
||||||
// Assign
|
// Assign
|
||||||
var mappingMock = new Mock<IMapping>();
|
var mappingMock = new Mock<IMapping>();
|
||||||
|
mappingMock.SetupGet(m => m.IsDisabled).Returns(false);
|
||||||
mappingMock.Setup(m => m.GetRequestMatchResult(It.IsAny<RequestMessage>(), It.IsAny<string>())).Throws<Exception>();
|
mappingMock.Setup(m => m.GetRequestMatchResult(It.IsAny<RequestMessage>(), It.IsAny<string>())).Throws<Exception>();
|
||||||
|
|
||||||
var mappings = new ConcurrentDictionary<Guid, IMapping>();
|
var mappings = new ConcurrentDictionary<Guid, IMapping>();
|
||||||
@@ -228,6 +230,35 @@ public class MappingMatcherTests
|
|||||||
result.Match!.Mapping.Guid.Should().Be(withProbability);
|
result.Match!.Mapping.Guid.Should().Be(withProbability);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void MappingMatcher_FindBestMatch_WhenMappingIsDisabled_ShouldReturnNull()
|
||||||
|
{
|
||||||
|
// Assign
|
||||||
|
var guid = Guid.Parse("00000000-0000-0000-0000-000000000001");
|
||||||
|
var mappingMock = new Mock<IMapping>();
|
||||||
|
mappingMock.SetupGet(m => m.Guid).Returns(guid);
|
||||||
|
mappingMock.SetupGet(m => m.IsDisabled).Returns(true);
|
||||||
|
mappingMock.SetupGet(m => m.Probability).Returns((double?)null);
|
||||||
|
|
||||||
|
var matchResult = new RequestMatchResult();
|
||||||
|
matchResult.AddScore(typeof(object), 1.0, null);
|
||||||
|
mappingMock.Setup(m => m.GetRequestMatchResult(It.IsAny<RequestMessage>(), It.IsAny<string>())).Returns(matchResult);
|
||||||
|
|
||||||
|
var mappings = new ConcurrentDictionary<Guid, IMapping>();
|
||||||
|
mappings.TryAdd(guid, mappingMock.Object);
|
||||||
|
_optionsMock.Setup(o => o.Mappings).Returns(mappings);
|
||||||
|
|
||||||
|
var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "GET", "::1");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = _sut.FindBestMatch(request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.Match.Should().BeNull();
|
||||||
|
result.Partial.Should().BeNull();
|
||||||
|
mappingMock.Verify(m => m.GetRequestMatchResult(It.IsAny<RequestMessage>(), It.IsAny<string>()), Times.Never);
|
||||||
|
}
|
||||||
|
|
||||||
private static ConcurrentDictionary<Guid, IMapping> InitMappings(params (Guid guid, double[] scores, double? probability)[] matches)
|
private static ConcurrentDictionary<Guid, IMapping> InitMappings(params (Guid guid, double[] scores, double? probability)[] matches)
|
||||||
{
|
{
|
||||||
var mappings = new ConcurrentDictionary<Guid, IMapping>();
|
var mappings = new ConcurrentDictionary<Guid, IMapping>();
|
||||||
@@ -236,6 +267,7 @@ public class MappingMatcherTests
|
|||||||
{
|
{
|
||||||
var mappingMock = new Mock<IMapping>();
|
var mappingMock = new Mock<IMapping>();
|
||||||
mappingMock.SetupGet(m => m.Guid).Returns(match.guid);
|
mappingMock.SetupGet(m => m.Guid).Returns(match.guid);
|
||||||
|
mappingMock.SetupGet(m => m.IsDisabled).Returns(false);
|
||||||
|
|
||||||
var requestMatchResult = new RequestMatchResult();
|
var requestMatchResult = new RequestMatchResult();
|
||||||
foreach (var score in match.scores)
|
foreach (var score in match.scores)
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ public class WireMockMiddlewareTests
|
|||||||
_optionsMock.SetupAllProperties();
|
_optionsMock.SetupAllProperties();
|
||||||
_optionsMock.Setup(o => o.Mappings).Returns(_mappings);
|
_optionsMock.Setup(o => o.Mappings).Returns(_mappings);
|
||||||
_optionsMock.Setup(o => o.LogEntries).Returns(new ConcurrentObservableCollection<LogEntry>());
|
_optionsMock.Setup(o => o.LogEntries).Returns(new ConcurrentObservableCollection<LogEntry>());
|
||||||
_optionsMock.Setup(o => o.Scenarios).Returns(new ConcurrentDictionary<string, ScenarioState>());
|
_optionsMock.Setup(o => o.ScenarioStateStore).Returns(new InMemoryScenarioStateStore());
|
||||||
_optionsMock.Setup(o => o.Logger.Warn(It.IsAny<string>(), It.IsAny<object[]>()));
|
_optionsMock.Setup(o => o.Logger.Warn(It.IsAny<string>(), It.IsAny<object[]>()));
|
||||||
_optionsMock.Setup(o => o.Logger.Error(It.IsAny<string>(), It.IsAny<object[]>()));
|
_optionsMock.Setup(o => o.Logger.Error(It.IsAny<string>(), It.IsAny<object[]>()));
|
||||||
_optionsMock.Setup(o => o.Logger.DebugRequestResponse(It.IsAny<LogEntryModel>(), It.IsAny<bool>()));
|
_optionsMock.Setup(o => o.Logger.DebugRequestResponse(It.IsAny<LogEntryModel>(), It.IsAny<bool>()));
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
// Copyright © WireMock.Net
|
// Copyright © WireMock.Net
|
||||||
|
|
||||||
using Moq;
|
using Moq;
|
||||||
|
using WireMock.Constants;
|
||||||
|
using WireMock.Matchers;
|
||||||
using WireMock.Matchers.Request;
|
using WireMock.Matchers.Request;
|
||||||
using WireMock.Models;
|
using WireMock.Models;
|
||||||
|
using WireMock.RequestBuilders;
|
||||||
|
|
||||||
namespace WireMock.Net.Tests.RequestMatchers;
|
namespace WireMock.Net.Tests.RequestMatchers;
|
||||||
|
|
||||||
@@ -10,8 +13,12 @@ public class RequestMessageCompositeMatcherTests
|
|||||||
{
|
{
|
||||||
private class Helper : RequestMessageCompositeMatcher
|
private class Helper : RequestMessageCompositeMatcher
|
||||||
{
|
{
|
||||||
public Helper(IEnumerable<IRequestMatcher> requestMatchers, CompositeMatcherType type = CompositeMatcherType.And) : base(requestMatchers, type)
|
public Helper(
|
||||||
|
IEnumerable<IRequestMatcher> requestMatchers,
|
||||||
|
CompositeMatcherType type = CompositeMatcherType.And,
|
||||||
|
RequestMatcherType? earlyMatcherType = null) : base(requestMatchers, type)
|
||||||
{
|
{
|
||||||
|
EarlyMatcherType = earlyMatcherType;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,4 +84,74 @@ public class RequestMessageCompositeMatcherTests
|
|||||||
requestMatcher1Mock.Verify(rm => rm.GetMatchingScore(It.IsAny<RequestMessage>(), It.IsAny<RequestMatchResult>()), Times.Once);
|
requestMatcher1Mock.Verify(rm => rm.GetMatchingScore(It.IsAny<RequestMessage>(), It.IsAny<RequestMatchResult>()), Times.Once);
|
||||||
requestMatcher2Mock.Verify(rm => rm.GetMatchingScore(It.IsAny<RequestMessage>(), It.IsAny<RequestMatchResult>()), Times.Once);
|
requestMatcher2Mock.Verify(rm => rm.GetMatchingScore(It.IsAny<RequestMessage>(), It.IsAny<RequestMatchResult>()), Times.Once);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RequestMessageCompositeMatcher_GetMatchingScore_EarlyMismatch()
|
||||||
|
{
|
||||||
|
// Assign
|
||||||
|
var requestMatcher1Mock = new Mock<IRequestMatcher>();
|
||||||
|
requestMatcher1Mock.Setup(rm => rm.GetMatchingScore(It.IsAny<RequestMessage>(), It.IsAny<RequestMatchResult>())).Returns(1.0d);
|
||||||
|
var requestMatcher2Mock = new Mock<IRequestMatcher>();
|
||||||
|
requestMatcher2Mock.Setup(rm => rm.GetMatchingScore(It.IsAny<RequestMessage>(), It.IsAny<RequestMatchResult>())).Returns(0.8d);
|
||||||
|
var postMatcher = new RequestMessageMethodMatcher(HttpRequestMethod.POST);
|
||||||
|
|
||||||
|
var requestMessage = new RequestMessage(new UrlDetails("http://localhost"), HttpRequestMethod.GET, "127.0.0.1");
|
||||||
|
var matcher = new Helper(
|
||||||
|
[requestMatcher1Mock.Object, requestMatcher2Mock.Object, postMatcher],
|
||||||
|
earlyMatcherType: RequestMatcherType.Method);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = new RequestMatchResult();
|
||||||
|
double score = matcher.GetMatchingScore(requestMessage, result);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
score.Should().Be(0.0d);
|
||||||
|
|
||||||
|
// Verify
|
||||||
|
requestMatcher1Mock.Verify(rm => rm.GetMatchingScore(It.IsAny<RequestMessage>(), It.IsAny<RequestMatchResult>()), Times.Never);
|
||||||
|
requestMatcher2Mock.Verify(rm => rm.GetMatchingScore(It.IsAny<RequestMessage>(), It.IsAny<RequestMatchResult>()), Times.Never);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RequestMessageCompositeMatcher_GetMatchingScore_SeveralHeadersEarlyMismatch()
|
||||||
|
{
|
||||||
|
// Assign
|
||||||
|
var headers = new Dictionary<string, string[]>
|
||||||
|
{
|
||||||
|
{ "teST", new[] { "x" } },
|
||||||
|
{ "teST2", new[] { "z" } }
|
||||||
|
};
|
||||||
|
var requestMessage = new RequestMessage(new UrlDetails("http://localhost"), "GET", "127.0.0.1", null, headers);
|
||||||
|
var request = Request.Create()
|
||||||
|
.WithEarlyMismatch(RequestMatcherType.Header)
|
||||||
|
.UsingAnyMethod()
|
||||||
|
.WithHeader("teST", "x")
|
||||||
|
.WithHeader("teST1", ["xx", "yy"])
|
||||||
|
.WithHeader("teST2", ["y", "z"], matchOperator: MatchOperator.And);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var score = request.GetMatchingScore(requestMessage, new RequestMatchResult());
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
score.Should().Be(0.0d);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RequestMessageCompositeMatcher_GetMatchingScore_SeveralParamEarlyMismatchSuccess()
|
||||||
|
{
|
||||||
|
// Assign
|
||||||
|
var uriWithParams = new Uri("http://localhost?test1=1&test2=2");
|
||||||
|
var requestMessage = new RequestMessage(new UrlDetails(uriWithParams), "GET", "127.0.0.1");
|
||||||
|
var request = Request.Create()
|
||||||
|
.WithEarlyMismatch(RequestMatcherType.Param)
|
||||||
|
.UsingAnyMethod()
|
||||||
|
.WithParam("test1", "1")
|
||||||
|
.WithParam("test2", "2");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var score = request.GetMatchingScore(requestMessage, new RequestMatchResult());
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
score.Should().Be(1.0d);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
// Copyright © WireMock.Net
|
// Copyright © WireMock.Net
|
||||||
|
|
||||||
|
using WireMock.Matchers.Request;
|
||||||
using WireMock.Net.Tests.VerifyExtensions;
|
using WireMock.Net.Tests.VerifyExtensions;
|
||||||
using WireMock.RequestBuilders;
|
using WireMock.RequestBuilders;
|
||||||
using WireMock.ResponseBuilders;
|
using WireMock.ResponseBuilders;
|
||||||
@@ -101,6 +102,7 @@ public partial class MappingConverterTests
|
|||||||
var guid = new Guid("8e7b9ab7-e18e-4502-8bc9-11e6679811cc");
|
var guid = new Guid("8e7b9ab7-e18e-4502-8bc9-11e6679811cc");
|
||||||
var request = Request.Create()
|
var request = Request.Create()
|
||||||
.UsingGet()
|
.UsingGet()
|
||||||
|
.WithEarlyMismatch(RequestMatcherType.Method)
|
||||||
.WithPath("/test_path")
|
.WithPath("/test_path")
|
||||||
.WithParam("q", "42")
|
.WithParam("q", "42")
|
||||||
.WithClientIP("112.123.100.99")
|
.WithClientIP("112.123.100.99")
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
builder
|
builder
|
||||||
.Given(Request.Create()
|
.Given(Request.Create()
|
||||||
|
.WithEarlyMismatch(WireMock.Matchers.Request.RequestMatcherType.Method)
|
||||||
.UsingMethod("GET")
|
.UsingMethod("GET")
|
||||||
.WithPath(new WildcardMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, "/test_path", false, WireMock.Matchers.MatchOperator.Or))
|
.WithPath(new WildcardMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, "/test_path", false, WireMock.Matchers.MatchOperator.Or))
|
||||||
.WithParam("q", new ExactMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, false, WireMock.Matchers.MatchOperator.And, "42"))
|
.WithParam("q", new ExactMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, false, WireMock.Matchers.MatchOperator.And, "42"))
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
var builder = new MappingBuilder();
|
var builder = new MappingBuilder();
|
||||||
builder
|
builder
|
||||||
.Given(Request.Create()
|
.Given(Request.Create()
|
||||||
|
.WithEarlyMismatch(WireMock.Matchers.Request.RequestMatcherType.Method)
|
||||||
.UsingMethod("GET")
|
.UsingMethod("GET")
|
||||||
.WithPath(new WildcardMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, "/test_path", false, WireMock.Matchers.MatchOperator.Or))
|
.WithPath(new WildcardMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, "/test_path", false, WireMock.Matchers.MatchOperator.Or))
|
||||||
.WithParam("q", new ExactMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, false, WireMock.Matchers.MatchOperator.And, "42"))
|
.WithParam("q", new ExactMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, false, WireMock.Matchers.MatchOperator.And, "42"))
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
server
|
server
|
||||||
.Given(Request.Create()
|
.Given(Request.Create()
|
||||||
|
.WithEarlyMismatch(WireMock.Matchers.Request.RequestMatcherType.Method)
|
||||||
.UsingMethod("GET")
|
.UsingMethod("GET")
|
||||||
.WithPath(new WildcardMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, "/test_path", false, WireMock.Matchers.MatchOperator.Or))
|
.WithPath(new WildcardMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, "/test_path", false, WireMock.Matchers.MatchOperator.Or))
|
||||||
.WithParam("q", new ExactMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, false, WireMock.Matchers.MatchOperator.And, "42"))
|
.WithParam("q", new ExactMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, false, WireMock.Matchers.MatchOperator.And, "42"))
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user