Compare commits

..

16 Commits

Author SHA1 Message Date
Stef Heyenrath
27a673953d 1.5.50 2024-03-12 20:31:50 +01:00
Stef Heyenrath
511540a7f1 Make WireMockAssertions extendable (#1082) 2024-03-12 20:27:12 +01:00
Stef Heyenrath
5b609915e1 Fix FluentAssertions on Header(s) (#1080)
* Fix FluentAssertions on Header(s)

* ...
2024-03-09 08:39:52 +01:00
Stef Heyenrath
a7b0d502c6 1.5.49 2024-03-06 13:46:51 +01:00
Stef Heyenrath
fcd67466b5 Upgrade ProtoBufJsonConverter to fix issue with dot(s) in package name (#1078) 2024-03-06 13:44:10 +01:00
Stef Heyenrath
ce833c1f29 Extend TypeLoader (#1069)
* Extend PluginLoader

* rename
2024-02-23 08:51:38 +01:00
Stef Heyenrath
2364866f97 Update README.md - blog 2024-02-22 10:07:00 +01:00
Stef Heyenrath
d4d588c593 1.5.48 2024-02-17 13:37:05 +01:00
Stef Heyenrath
97a749d54a Fix some SonarCloud issues (#1058)
* Fix some SonarCloud issues

* added some xml comments
2024-02-17 13:32:28 +01:00
Stef Heyenrath
6ac95cf57d Add Grpc ProtoBuf support (request-response) (#1047)
* ProtoBuf

* .

* x

* ---

* x

* fx

* ...

* sc

* ...

* .

* groen

* x

* fix tests

* ok!?

* fix tests

* fix tests

* !

* x

* 6

* .

* x

* ivaluematcher

* transformer

* .

* sc

* .

* mapping

* x

* tra

* com

* ...

* .

* .

* .

* AddProtoDefinition

* .

* set

* grpahj

* .

* .

* IdOrText

* ...

* async

* async2

* .

* t

* nuget

* <PackageReference Include="ProtoBufJsonConverter" Version="0.2.0-preview-04" />

* http version

* tests

* .WithHttpVersion("2")

* <PackageReference Include="ProtoBufJsonConverter" Version="0.2.0" />

* HttpVersionParser
2024-02-16 17:16:51 +01:00
Stef Heyenrath
801546fae7 1.5.47 2024-01-25 10:13:54 +01:00
Stef Heyenrath
57c42492bc Pin the version from Testcontainers to 3.7.0 in WireMock.Net.Testcontainers (#1057)
* TestcontainersTests

* WireMockContainer_Build_and_StartAsync_and_StopAsync

* --

* [3.7.0]
2024-01-25 09:54:25 +01:00
dependabot[bot]
057cd6c963 Bump System.IdentityModel.Tokens.Jwt (#1055)
Bumps [System.IdentityModel.Tokens.Jwt](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet) from 6.25.0 to 6.34.0.
- [Release notes](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/releases)
- [Changelog](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/compare/6.25.0...6.34.0)

---
updated-dependencies:
- dependency-name: System.IdentityModel.Tokens.Jwt
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-23 21:48:41 +01:00
Stef Heyenrath
b2b7172043 fix: src/WireMock.Net/WireMock.Net.csproj to reduce vulnerabilities (#1053)
The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/SNYK-DOTNET-MICROSOFTIDENTITYMODELJSONWEBTOKENS-6148656
- https://snyk.io/vuln/SNYK-DOTNET-SYSTEMIDENTITYMODELTOKENSJWT-6148655

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-01-23 21:32:55 +01:00
Stef Heyenrath
13a3dfa9fe Update README.md 2024-01-08 08:12:38 +01:00
Stef Heyenrath
4020a178a6 Add WithoutHeader to WireMock.FluentAssertions (#1049)
* Add WithoutHeader to WireMock.FluentAssertions

* .

* .
2023-12-27 08:18:56 +01:00
162 changed files with 5674 additions and 2222 deletions

View File

@@ -1,3 +1,26 @@
# 1.5.50 (12 March 2024)
- [#1080](https://github.com/WireMock-Net/WireMock.Net/pull/1080) - Fix FluentAssertions on Header(s) [bug] contributed by [StefH](https://github.com/StefH)
- [#1082](https://github.com/WireMock-Net/WireMock.Net/pull/1082) - Make WireMockAssertions extendable [feature] contributed by [StefH](https://github.com/StefH)
- [#1074](https://github.com/WireMock-Net/WireMock.Net/issues/1074) - FluentAssertions extensions do not filter headers correctly [bug]
- [#1075](https://github.com/WireMock-Net/WireMock.Net/issues/1075) - FluentAssertions extensions are not open for extension [feature]
# 1.5.49 (06 March 2024)
- [#1069](https://github.com/WireMock-Net/WireMock.Net/pull/1069) - Extend TypeLoader [feature] contributed by [StefH](https://github.com/StefH)
- [#1078](https://github.com/WireMock-Net/WireMock.Net/pull/1078) - Upgrade ProtoBufJsonConverter to fix issue with dot(s) in package name [bug] contributed by [StefH](https://github.com/StefH)
- [#1077](https://github.com/WireMock-Net/WireMock.Net/issues/1077) - ProtoBufMatcher not working when proto package name contains dots [bug]
# 1.5.48 (22 February 2024)
- [#1047](https://github.com/WireMock-Net/WireMock.Net/pull/1047) - Add Grpc ProtoBuf support (request-response) [feature] contributed by [StefH](https://github.com/StefH)
- [#1058](https://github.com/WireMock-Net/WireMock.Net/pull/1058) - Fix some SonarCloud issues [refactor] contributed by [StefH](https://github.com/StefH)
# 1.5.47 (25 January 2024)
- [#1049](https://github.com/WireMock-Net/WireMock.Net/pull/1049) - Add WithoutHeader to WireMock.FluentAssertions [feature] contributed by [StefH](https://github.com/StefH)
- [#1053](https://github.com/WireMock-Net/WireMock.Net/pull/1053) - [Snyk] Security upgrade Microsoft.IdentityModel.Protocols.OpenIdConnect from 6.12.2 to 6.34.0 [security] contributed by [StefH](https://github.com/StefH)
- [#1057](https://github.com/WireMock-Net/WireMock.Net/pull/1057) - Pin the version from Testcontainers to 3.7.0 in WireMock.Net.Testcontainers [bug] contributed by [StefH](https://github.com/StefH)
- [#1048](https://github.com/WireMock-Net/WireMock.Net/issues/1048) - WithoutHeader fluent assertion [feature]
- [#1054](https://github.com/WireMock-Net/WireMock.Net/issues/1054) - WireMock.Net 1.5.46 is incompatible with TestContainers 3.7.0 (issue 1) [bug]
- [#1059](https://github.com/WireMock-Net/WireMock.Net/issues/1059) - WireMock.Net 1.5.46 is incompatible with TestContainers 3.7.0 (issue 2) [bug]
# 1.5.46 (23 December 2023)
- [#1044](https://github.com/WireMock-Net/WireMock.Net/pull/1044) - WireMockServerSettingsParser [refactor] contributed by [StefH](https://github.com/StefH)
- [#1046](https://github.com/WireMock-Net/WireMock.Net/pull/1046) - Change FindRequestByMappingGuidAsync to return a collection of entries contributed by [tlevesque-ueat](https://github.com/tlevesque-ueat)
@@ -178,7 +201,6 @@
# 1.5.13 (11 December 2022)
- [#858](https://github.com/WireMock-Net/WireMock.Net/pull/858) - Update Transformer functionality to return value instead of string [feature] contributed by [StefH](https://github.com/StefH)
- [#859](https://github.com/WireMock-Net/WireMock.Net/pull/859) - Add UpdatedAt property to Mapping [feature] contributed by [StefH](https://github.com/StefH)
- [#861](https://github.com/WireMock-Net/WireMock.Net/pull/861) - Add extra functionality for issue 55 contributed by [StefH](https://github.com/StefH)
- [#862](https://github.com/WireMock-Net/WireMock.Net/pull/862) - Add client certificate support [feature] contributed by [billybraga](https://github.com/billybraga)
- [#863](https://github.com/WireMock-Net/WireMock.Net/pull/863) - Update WireMockServer.CreateClient/CreateClients to include handlers [feature] contributed by [StefH](https://github.com/StefH)
- [#856](https://github.com/WireMock-Net/WireMock.Net/issues/856) - Inconsistent result with overlapping (duplicate) request [bug]
@@ -222,8 +244,6 @@
# 1.5.6 (12 September 2022)
- [#803](https://github.com/WireMock-Net/WireMock.Net/pull/803) - WebHook : UseFireAndForget + Delay [feature] contributed by [StefH](https://github.com/StefH)
- [#804](https://github.com/WireMock-Net/WireMock.Net/pull/804) - Change nuget to package reference for WireMock.Net.Console.Net472.Cla&#8230; [feature] contributed by [mattisking](https://github.com/mattisking)
- [#806](https://github.com/WireMock-Net/WireMock.Net/pull/806) - Tweak middleware and fix bug in example [feature] contributed by [mattisking](https://github.com/mattisking)
- [#801](https://github.com/WireMock-Net/WireMock.Net/issues/801) - Webhook Delays [feature]
# 1.5.5 (03 September 2022)
@@ -795,7 +815,6 @@
# 1.0.15.0 (04 May 2019)
- [#271](https://github.com/WireMock-Net/WireMock.Net/pull/271) - Support Dynamic response files using Handlebars templating [bug, feature] contributed by [StefH](https://github.com/StefH)
- [#272](https://github.com/WireMock-Net/WireMock.Net/pull/272) - Add unit test for JsonPath and BodyAsFile mapping contributed by [denstorti](https://github.com/denstorti)
- [#273](https://github.com/WireMock-Net/WireMock.Net/pull/273) - Dynamic response handlebars templating (2) [bug, feature] contributed by [StefH](https://github.com/StefH)
# 1.0.14.0 (20 April 2019)
@@ -866,7 +885,6 @@
- [#208](https://github.com/WireMock-Net/WireMock.Net/pull/208) - Refactor contributed by [StefH](https://github.com/StefH)
- [#209](https://github.com/WireMock-Net/WireMock.Net/pull/209) - NET Core 2.1 + support for Service Fabric commandline parameters contributed by [StefH](https://github.com/StefH)
- [#212](https://github.com/WireMock-Net/WireMock.Net/pull/212) - Update BodyParser logic contributed by [StefH](https://github.com/StefH)
- [#216](https://github.com/WireMock-Net/WireMock.Net/pull/216) - ResponseBodyData contributed by [StefH](https://github.com/StefH)
- [#217](https://github.com/WireMock-Net/WireMock.Net/pull/217) - Enable Source Link contributed by [kashifsoofi](https://github.com/kashifsoofi)
- [#218](https://github.com/WireMock-Net/WireMock.Net/pull/218) - remove appveyor contributed by [StefH](https://github.com/StefH)
- [#107](https://github.com/WireMock-Net/WireMock.Net/issues/107) - Feature: increase code coverage [feature]
@@ -984,7 +1002,6 @@
# 1.0.3.17 (16 May 2018)
- [#134](https://github.com/WireMock-Net/WireMock.Net/pull/134) - Stef negate matcher contributed by [alastairtree](https://github.com/alastairtree)
- [#135](https://github.com/WireMock-Net/WireMock.Net/pull/135) - Merge into the stef_negate_matcher branch (solves issue #133) contributed by [StefH](https://github.com/StefH)
- [#138](https://github.com/WireMock-Net/WireMock.Net/pull/138) - Added Negate matcher logic contributed by [StefH](https://github.com/StefH)
- [#130](https://github.com/WireMock-Net/WireMock.Net/issues/130) - ...

View File

@@ -4,7 +4,7 @@
</PropertyGroup>
<PropertyGroup>
<VersionPrefix>1.5.46</VersionPrefix>
<VersionPrefix>1.5.50</VersionPrefix>
<PackageIcon>WireMock.Net-Logo.png</PackageIcon>
<PackageProjectUrl>https://github.com/WireMock-Net/WireMock.Net</PackageProjectUrl>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>

View File

@@ -1,7 +1,7 @@
rem https://github.com/StefH/GitHubReleaseNotes
SET version=1.5.46
SET version=1.5.50
GitHubReleaseNotes --output CHANGELOG.md --skip-empty-releases --exclude-labels question invalid doc duplicate --version %version% --token %GH_TOKEN%
GitHubReleaseNotes --output CHANGELOG.md --skip-empty-releases --exclude-labels question invalid doc duplicate example --version %version% --token %GH_TOKEN%
GitHubReleaseNotes --output PackageReleaseNotes.txt --skip-empty-releases --exclude-labels question invalid doc duplicate --template PackageReleaseNotes.template --version %version% --token %GH_TOKEN%

View File

@@ -1,5 +1,7 @@
# 1.5.46 (23 December 2023)
- #1044 WireMockServerSettingsParser [refactor]
- #1046 Change FindRequestByMappingGuidAsync to return a collection of entries
# 1.5.50 (12 March 2024)
- #1080 Fix FluentAssertions on Header(s) [bug]
- #1082 Make WireMockAssertions extendable [feature]
- #1074 FluentAssertions extensions do not filter headers correctly [bug]
- #1075 FluentAssertions extensions are not open for extension [feature]
The full release notes can be found here: https://github.com/WireMock-Net/WireMock.Net/blob/master/CHANGELOG.md

View File

@@ -15,7 +15,9 @@ For more info, see also this WIKI page: [What is WireMock.Net](https://github.co
* Can be used locally or in CI/CD scenarios
## :memo: Blogs
- [mStack.nl : Generate C# Code from Mapping(s)](https://mstack.nl/blog/20230201-wiremock.net-tocode/)
- [mstack.nl : Generate C# Code from Mapping(s)](https://mstack.nl/blog/20230201-wiremock.net-tocode/)
- [mstack.nl : Chaos Engineering with Fault Injections](https://mstack.nl/blogs/wiremock-net-chaos-engineering-with-fault-injections/)
- [mstack.nl : gRPC / ProtoBuf Support](https://mstack.nl/blogs/wiremock-net-grpc/)
## :computer: Project Info

View File

@@ -112,6 +112,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.Console.NET8",
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMockAzureQueueProxy", "examples\WireMockAzureQueueProxy\WireMockAzureQueueProxy.csproj", "{7FC0B409-2682-40EE-B3B9-3930D6769D01}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.Console.GrpcClient", "examples\WireMock.Net.Console.GrpcClient\WireMock.Net.Console.GrpcClient.csproj", "{B1580A38-84E7-44BE-8FE7-3EE5031D74A1}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -262,6 +264,10 @@ Global
{7FC0B409-2682-40EE-B3B9-3930D6769D01}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7FC0B409-2682-40EE-B3B9-3930D6769D01}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7FC0B409-2682-40EE-B3B9-3930D6769D01}.Release|Any CPU.Build.0 = Release|Any CPU
{B1580A38-84E7-44BE-8FE7-3EE5031D74A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B1580A38-84E7-44BE-8FE7-3EE5031D74A1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B1580A38-84E7-44BE-8FE7-3EE5031D74A1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B1580A38-84E7-44BE-8FE7-3EE5031D74A1}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -305,6 +311,7 @@ Global
{941229D6-191B-4B5E-AC81-0905EBF4F19D} = {985E0ADB-D4B4-473A-AA40-567E279B7946}
{1EA72C0F-92E9-486B-8FFE-53F992BFC4AA} = {985E0ADB-D4B4-473A-AA40-567E279B7946}
{7FC0B409-2682-40EE-B3B9-3930D6769D01} = {985E0ADB-D4B4-473A-AA40-567E279B7946}
{B1580A38-84E7-44BE-8FE7-3EE5031D74A1} = {985E0ADB-D4B4-473A-AA40-567E279B7946}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {DC539027-9852-430C-B19F-FD035D018458}

View File

@@ -23,14 +23,17 @@
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=WWW/@EntryIndexedValue">WWW</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=XMS/@EntryIndexedValue">XMS</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=XUA/@EntryIndexedValue">XUA</s:String>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Dlls/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Flurl/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=funcs/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Grpc/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=guidb/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Guids/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Heyenrath/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Jmes/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=openapi/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Pacticipant/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=protobuf/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Raml/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=randomizer/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Scriban/@EntryIndexedValue">True</s:Boolean>

View File

@@ -0,0 +1,21 @@
using Greet;
using Grpc.Net.Client;
namespace WireMock.Net.Console.GrpcClient;
internal class Program
{
static async Task Main(string[] args)
{
var channel = GrpcChannel.ForAddress("http://localhost:9093/grpc3", new GrpcChannelOptions
{
Credentials = Grpc.Core.ChannelCredentials.Insecure
});
var client = new Greeter.GreeterClient(channel);
var reply = await client.SayHelloAsync(new HelloRequest { Name = "stef" });
System.Console.WriteLine("Greeting: " + reply.Message);
}
}

View File

@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Google.Protobuf" Version="3.25.1" />
<PackageReference Include="Grpc.Net.Client" Version="2.59.0" />
<PackageReference Include="Grpc.Tools" Version="2.60.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<Protobuf Include="greet.proto" GrpcServices="Client" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,33 @@
// Copyright 2019 The gRPC Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
syntax = "proto3";
package greet;
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply);
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}

View File

@@ -3,7 +3,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<DefineConstants>$(DefineConstants);GRAPHQL;MIMEKIT</DefineConstants>
<DefineConstants>$(DefineConstants);GRAPHQL;MIMEKIT;PROTOBUF</DefineConstants>
</PropertyGroup>
<ItemGroup>

View File

@@ -42,6 +42,24 @@ namespace WireMock.Net.ConsoleApplication
public static class MainApp
{
private const string ProtoDefinition = @"
syntax = ""proto3"";
package greet;
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
";
private const string TestSchema = @"
scalar DateTime
scalar MyCustomScalar
@@ -115,17 +133,14 @@ namespace WireMock.Net.ConsoleApplication
.WithBodyAsJson(rm => todos[int.Parse(rm.Query!["id"].ToString())])
);
var httpClient = server.CreateClient();
//server.Stop();
var httpAndHttpsWithPort = WireMockServer.Start(new WireMockServerSettings
using var httpAndHttpsWithPort = WireMockServer.Start(new WireMockServerSettings
{
HostingScheme = HostingScheme.HttpAndHttps,
Port = 12399
});
httpAndHttpsWithPort.Stop();
var httpAndHttpsFree = WireMockServer.Start(new WireMockServerSettings
using var httpAndHttpsFree = WireMockServer.Start(new WireMockServerSettings
{
HostingScheme = HostingScheme.HttpAndHttps
});
@@ -134,11 +149,14 @@ namespace WireMock.Net.ConsoleApplication
string url1 = "http://localhost:9091/";
string url2 = "http://localhost:9092/";
string url3 = "https://localhost:9443/";
string urlGrpc = "grpc://localhost:9093/";
string urlGrpcSSL = "grpcs://localhost:9094/";
server = WireMockServer.Start(new WireMockServerSettings
{
// CorsPolicyOptions = CorsPolicyOptions.AllowAll,
AllowCSharpCodeMatcher = true,
Urls = new[] { url1, url2, url3 },
Urls = new[] { url1, url2, url3, urlGrpc, urlGrpcSSL },
StartAdminInterface = true,
ReadStaticMappings = true,
SaveUnmatchedRequests = true,
@@ -171,17 +189,91 @@ namespace WireMock.Net.ConsoleApplication
//server.SetAzureADAuthentication("6c2a4722-f3b9-4970-b8fc-fac41e29stef", "8587fde1-7824-42c7-8592-faf92b04stef");
// server.AllowPartialMapping();
#if PROTOBUF
var protoBufJsonMatcher = new JsonPartialWildcardMatcher(new { name = "*" });
server
.Given(Request.Create()
.UsingPost()
.WithHttpVersion("2")
.WithPath("/grpc/greet.Greeter/SayHello")
.WithBodyAsProtoBuf(ProtoDefinition, "greet.HelloRequest", protoBufJsonMatcher)
)
.RespondWith(Response.Create()
.WithHeader("Content-Type", "application/grpc")
.WithBodyAsProtoBuf(ProtoDefinition, "greet.HelloReply",
new
{
message = "hello {{request.BodyAsJson.name}}"
}
)
.WithTrailingHeader("grpc-status", "0")
.WithTransformer()
);
server
.Given(Request.Create()
.UsingPost()
.WithHttpVersion("2")
.WithPath("/grpc2/greet.Greeter/SayHello")
.WithBodyAsProtoBuf("greet.HelloRequest", protoBufJsonMatcher)
)
.WithProtoDefinition(ProtoDefinition)
.RespondWith(Response.Create()
.WithHeader("Content-Type", "application/grpc")
.WithBodyAsProtoBuf("greet.HelloReply",
new
{
message = "hello {{request.BodyAsJson.name}}"
}
)
.WithTrailingHeader("grpc-status", "0")
.WithTransformer()
);
server
.AddProtoDefinition("my-greeter", ProtoDefinition)
.Given(Request.Create()
.UsingPost()
.WithPath("/grpc3/greet.Greeter/SayHello")
.WithBodyAsProtoBuf("greet.HelloRequest", protoBufJsonMatcher)
)
.WithProtoDefinition("my-greeter")
.RespondWith(Response.Create()
.WithHeader("Content-Type", "application/grpc")
.WithBodyAsProtoBuf("greet.HelloReply",
new
{
message = "hello {{request.BodyAsJson.name}}"
}
)
.WithTrailingHeader("grpc-status", "0")
.WithTransformer()
);
#endif
#if GRAPHQL
var customScalars = new Dictionary<string, Type> { { "MyCustomScalar", typeof(int) } };
server
.Given(Request.Create()
.WithPath("/graphql")
.UsingPost()
.WithGraphQLSchema(TestSchema, customScalars)
.WithBodyAsGraphQL(TestSchema, customScalars)
)
.RespondWith(Response.Create()
.WithBody("GraphQL is ok")
);
//server
// .AddGraphQLSchema("my-graphql", TestSchema, customScalars)
// .Given(Request.Create()
// .WithPath("/graphql2")
// .UsingPost()
// )
// .WithGraphQLSchema("my-graphql")
// .RespondWith(Response.Create()
// .WithBody("GraphQL is ok")
// );
#endif
#if MIMEKIT
@@ -336,8 +428,8 @@ namespace WireMock.Net.ConsoleApplication
Url = "http://localhost:9999",
ReplaceSettings = new ProxyUrlReplaceSettings
{
OldValue = "old",
NewValue = "new"
OldValue = "old",
NewValue = "new"
}
})
);

View File

@@ -232,14 +232,17 @@
<Reference Include="Microsoft.Extensions.Primitives, Version=5.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.Extensions.Primitives.5.0.0\lib\net461\Microsoft.Extensions.Primitives.dll</HintPath>
</Reference>
<Reference Include="Microsoft.IdentityModel.Abstractions, Version=6.25.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.IdentityModel.Abstractions.6.25.0\lib\net472\Microsoft.IdentityModel.Abstractions.dll</HintPath>
<Reference Include="Microsoft.IdentityModel.Abstractions, Version=7.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
<HintPath>..\..\packages/Microsoft.IdentityModel.Abstractions.7.2.0/lib/net472/Microsoft.IdentityModel.Abstractions.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.IdentityModel.JsonWebTokens, Version=6.25.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.IdentityModel.JsonWebTokens.6.25.0\lib\net472\Microsoft.IdentityModel.JsonWebTokens.dll</HintPath>
<Reference Include="Microsoft.IdentityModel.JsonWebTokens, Version=7.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
<HintPath>..\..\packages/Microsoft.IdentityModel.JsonWebTokens.7.2.0/lib/net472/Microsoft.IdentityModel.JsonWebTokens.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.IdentityModel.Logging, Version=6.25.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.IdentityModel.Logging.6.25.0\lib\net472\Microsoft.IdentityModel.Logging.dll</HintPath>
<Reference Include="Microsoft.IdentityModel.Logging, Version=7.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
<HintPath>..\..\packages/Microsoft.IdentityModel.Logging.7.2.0/lib/net472/Microsoft.IdentityModel.Logging.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.IdentityModel.Protocols, Version=6.12.2.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.IdentityModel.Protocols.6.12.2\lib\net472\Microsoft.IdentityModel.Protocols.dll</HintPath>
@@ -247,8 +250,9 @@
<Reference Include="Microsoft.IdentityModel.Protocols.OpenIdConnect, Version=6.12.2.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.IdentityModel.Protocols.OpenIdConnect.6.12.2\lib\net472\Microsoft.IdentityModel.Protocols.OpenIdConnect.dll</HintPath>
</Reference>
<Reference Include="Microsoft.IdentityModel.Tokens, Version=6.25.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.IdentityModel.Tokens.6.25.0\lib\net472\Microsoft.IdentityModel.Tokens.dll</HintPath>
<Reference Include="Microsoft.IdentityModel.Tokens, Version=7.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
<HintPath>..\..\packages/Microsoft.IdentityModel.Tokens.7.2.0/lib/net472/Microsoft.IdentityModel.Tokens.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.Net.Http.Headers, Version=2.2.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.Net.Http.Headers.2.2.0\lib\netstandard2.0\Microsoft.Net.Http.Headers.dll</HintPath>
@@ -296,8 +300,9 @@
<Reference Include="System.Diagnostics.DiagnosticSource, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Diagnostics.DiagnosticSource.4.5.0\lib\net46\System.Diagnostics.DiagnosticSource.dll</HintPath>
</Reference>
<Reference Include="System.IdentityModel.Tokens.Jwt, Version=6.25.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.IdentityModel.Tokens.Jwt.6.25.0\lib\net472\System.IdentityModel.Tokens.Jwt.dll</HintPath>
<Reference Include="System.IdentityModel.Tokens.Jwt, Version=6.34.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
<HintPath>..\..\packages/System.IdentityModel.Tokens.Jwt.6.34.0/lib/net472/System.IdentityModel.Tokens.Jwt.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.IO.Pipelines, Version=4.0.0.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.IO.Pipelines.4.5.3\lib\netstandard2.0\System.IO.Pipelines.dll</HintPath>
@@ -305,8 +310,9 @@
<Reference Include="System.Linq.Dynamic.Core, Version=1.3.1.0, Culture=neutral, PublicKeyToken=0f07ec44de6ac832, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Linq.Dynamic.Core.1.3.1\lib\net46\System.Linq.Dynamic.Core.dll</HintPath>
</Reference>
<Reference Include="System.Memory, Version=4.0.1.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Memory.4.5.4\lib\net461\System.Memory.dll</HintPath>
<Reference Include="System.Memory, Version=4.0.1.2, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51">
<HintPath>..\..\packages/System.Memory.4.5.5/lib/net461/System.Memory.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Net" />
<Reference Include="System.Net.Http.Formatting, Version=5.2.8.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">

View File

@@ -115,12 +115,12 @@
<package id="Microsoft.Extensions.Options" version="2.2.0" targetFramework="net472" />
<package id="Microsoft.Extensions.Options.ConfigurationExtensions" version="2.2.0" targetFramework="net472" />
<package id="Microsoft.Extensions.Primitives" version="5.0.0" targetFramework="net472" />
<package id="Microsoft.IdentityModel.Abstractions" version="6.25.0" targetFramework="net472" />
<package id="Microsoft.IdentityModel.JsonWebTokens" version="6.25.0" targetFramework="net472" />
<package id="Microsoft.IdentityModel.Logging" version="6.25.0" targetFramework="net472" />
<package id="Microsoft.IdentityModel.Abstractions" version="7.2.0" targetFramework="net472" />
<package id="Microsoft.IdentityModel.JsonWebTokens" version="7.2.0" targetFramework="net472" />
<package id="Microsoft.IdentityModel.Logging" version="7.2.0" targetFramework="net472" />
<package id="Microsoft.IdentityModel.Protocols" version="6.12.2" targetFramework="net472" />
<package id="Microsoft.IdentityModel.Protocols.OpenIdConnect" version="6.12.2" targetFramework="net472" />
<package id="Microsoft.IdentityModel.Tokens" version="6.25.0" targetFramework="net472" />
<package id="Microsoft.IdentityModel.Tokens" version="7.2.0" targetFramework="net472" />
<package id="Microsoft.Net.Http.Headers" version="2.2.0" targetFramework="net472" />
<package id="Namotion.Reflection" version="2.0.10" targetFramework="net472" />
<package id="Newtonsoft.Json" version="13.0.3" targetFramework="net472" />
@@ -136,10 +136,10 @@
<package id="System.Collections.Immutable" version="1.5.0" targetFramework="net472" />
<package id="System.ComponentModel.Annotations" version="4.5.0" targetFramework="net472" />
<package id="System.Diagnostics.DiagnosticSource" version="4.5.0" targetFramework="net472" />
<package id="System.IdentityModel.Tokens.Jwt" version="6.25.0" targetFramework="net472" />
<package id="System.IdentityModel.Tokens.Jwt" version="6.34.0" targetFramework="net472" />
<package id="System.IO.Pipelines" version="4.5.3" targetFramework="net472" />
<package id="System.Linq.Dynamic.Core" version="1.3.1" targetFramework="net472" />
<package id="System.Memory" version="4.5.4" targetFramework="net472" />
<package id="System.Memory" version="4.5.5" targetFramework="net472" />
<package id="System.Numerics.Vectors" version="4.5.0" targetFramework="net472" />
<package id="System.Reflection.Metadata" version="1.6.0" targetFramework="net472" />
<package id="System.Runtime.CompilerServices.Unsafe" version="5.0.0" targetFramework="net472" />

View File

@@ -98,4 +98,9 @@ public class MappingModel
/// The probability when this request should be matched. Value is between 0 and 1. [Optional]
/// </summary>
public double? Probability { get; set; }
/// <summary>
/// The Grpc ProtoDefinition which is used for this mapping (request and response). [Optional]
/// </summary>
public string? ProtoDefinition { get; set; }
}

View File

@@ -70,13 +70,22 @@ public class MatcherModel
/// ContentTransferEncoding Matcher (base64)
/// </summary>
public MatcherModel? ContentTransferEncodingMatcher { get; set; }
#endregion
#region MimePartMatcher + ProtoBufMatcher
/// <summary>
/// Content Matcher
/// </summary>
public MatcherModel? ContentMatcher { get; set; }
#endregion
#region ProtoBufMatcher
/// <summary>
/// The full type of the protobuf (request/response) message object. Format is "{package-name}.{type-name}".
/// </summary>
public string? ProtoBufMessageType { get; set; }
#endregion
#region XPathMatcher
/// <summary>
/// Array of namespace prefix and uri. (optional)
@@ -86,7 +95,7 @@ public class MatcherModel
#region GraphQLMatcher
/// <summary>
/// Mapping of custom GraphQL Scalar name to ClrType. (optional)
/// Mapping of custom GraphQL Scalar name to ClrType. (optional)
/// </summary>
public IDictionary<string, Type>? CustomScalars { get; set; }
#endregion

View File

@@ -28,6 +28,11 @@ public class RequestModel
/// </summary>
public string[]? Methods { get; set; }
/// <summary>
/// The HTTP Version
/// </summary>
public string? HttpVersion { get; set; }
/// <summary>
/// Reject on match for Methods.
/// </summary>

View File

@@ -35,7 +35,7 @@ public class ResponseModel
public bool? BodyAsJsonIndented { get; set; }
/// <summary>
/// Gets or sets the body (as bytearray).
/// Gets or sets the body (as byte array).
/// </summary>
public byte[]? BodyAsBytes { get; set; }
@@ -84,6 +84,11 @@ public class ResponseModel
/// </summary>
public string? HeadersRaw { get; set; }
/// <summary>
/// Gets or sets the Trailing Headers.
/// </summary>
public IDictionary<string, object>? TrailingHeaders { get; set; }
/// <summary>
/// Gets or sets the delay in milliseconds.
/// </summary>
@@ -123,4 +128,16 @@ public class ResponseModel
/// Gets or sets the WebProxy settings.
/// </summary>
public WebProxyModel? WebProxy { get; set; }
#region ProtoBuf
/// <summary>
/// Gets or sets the proto definition.
/// </summary>
public string? ProtoDefinition { get; set; }
/// <summary>
/// Gets or sets the full type of the protobuf (request/response) message object. Format is "{package-name}.{type-name}".
/// </summary>
public string? ProtoBufMessageType { get; set; }
#endregion
}

View File

@@ -55,6 +55,11 @@ public class LogRequestModel
/// </summary>
public string Method { get; set; }
/// <summary>
/// The HTTP Version.
/// </summary>
public string HttpVersion { get; set; } = null!;
/// <summary>
/// The Headers.
/// </summary>

View File

@@ -1,4 +1,6 @@
using System.Collections.Generic;
using System.Text.RegularExpressions;
using JetBrains.Annotations;
using WireMock.Handlers;
using WireMock.Types;
@@ -114,6 +116,11 @@ public class SettingsModel
/// </summary>
public QueryParameterMultipleValueSupport? QueryParameterMultipleValueSupport { get; set; }
/// <summary>
/// A list of Grpc ProtoDefinitions which can be used.
/// </summary>
public Dictionary<string, string>? ProtoDefinitions { get; set; }
#if NETSTANDARD1_3_OR_GREATER || NET461
/// <summary>
/// Server client certificate mode

View File

@@ -63,6 +63,11 @@ public interface IRequestMessage
/// </summary>
string Method { get; }
/// <summary>
/// Gets the HTTP Version.
/// </summary>
string HttpVersion { get; }
/// <summary>
/// Gets the headers.
/// </summary>
@@ -94,23 +99,27 @@ public interface IRequestMessage
IBodyData? BodyData { get; }
/// <summary>
/// The original body as string. Convenience getter for Handlebars and WireMockAssertions.
/// The original body as string.
/// Convenience getter for Handlebars and WireMockAssertions.
/// </summary>
string? Body { get; }
/// <summary>
/// The body (as JSON object). Convenience getter for Handlebars and WireMockAssertions.
/// The body (as JSON object).
/// Convenience getter for Handlebars and WireMockAssertions.
/// </summary>
object? BodyAsJson { get; }
/// <summary>
/// The body (as bytearray). Convenience getter for Handlebars and WireMockAssertions.
/// The body (as bytearray).
/// Convenience getter for Handlebars and WireMockAssertions.
/// </summary>
byte[]? BodyAsBytes { get; }
#if MIMEKIT
/// <summary>
/// The original body as MimeMessage. Convenience getter for Handlebars and WireMockAssertions.
/// The original body as MimeMessage.
/// Convenience getter for Handlebars and WireMockAssertions.
/// </summary>
object? BodyAsMimeMessage { get; }
#endif

View File

@@ -40,11 +40,16 @@ public interface IResponseMessage
/// </summary>
IDictionary<string, WireMockList<string>>? Headers { get; }
/// <summary>
/// Gets the trailing headers.
/// </summary>
IDictionary<string, WireMockList<string>>? TrailingHeaders { get; }
/// <summary>
/// Gets or sets the status code.
/// </summary>
object? StatusCode { get; }
/// <summary>
/// Adds the header.
/// </summary>
@@ -53,9 +58,23 @@ public interface IResponseMessage
void AddHeader(string name, string value);
/// <summary>
/// Adds the header.
/// Adds the trailing header.
/// </summary>
/// <param name="name">The name.</param>
/// <param name="values">The values.</param>
void AddHeader(string name, params string[] values);
/// <summary>
/// Adds the trailing header.
/// </summary>
/// <param name="name">The name.</param>
/// <param name="value">The value.</param>
void AddTrailingHeader(string name, string value);
/// <summary>
/// Adds the header.
/// </summary>
/// <param name="name">The name.</param>
/// <param name="values">The values.</param>
void AddTrailingHeader(string name, params string[] values);
}

View File

@@ -1,7 +1,10 @@
using System;
using System.Collections.Generic;
using System.Text;
using WireMock.Models;
using WireMock.Types;
// ReSharper disable once CheckNamespace
namespace WireMock.Util;
/// <summary>
@@ -10,7 +13,7 @@ namespace WireMock.Util;
public interface IBodyData
{
/// <summary>
/// The body (as bytearray).
/// The body (as byte array).
/// </summary>
byte[]? BodyAsBytes { get; set; }
@@ -26,6 +29,7 @@ public interface IBodyData
/// <summary>
/// The body (as JSON object).
/// Also used for ProtoBuf.
/// </summary>
object? BodyAsJson { get; set; }
@@ -68,4 +72,16 @@ public interface IBodyData
/// Defines if this BodyData is the result of a dynamically created response-string. (
/// </summary>
public string? IsFuncUsed { get; set; }
#region ProtoBuf
/// <summary>
/// Gets or sets the proto definition.
/// </summary>
public Func<IdOrText>? ProtoDefinition { get; set; }
/// <summary>
/// Gets or sets the full type of the protobuf (request/response) message object. Format is "{package-name}.{type-name}".
/// </summary>
public string? ProtoBufMessageType { get; set; }
#endregion
}

View File

@@ -0,0 +1,33 @@
namespace WireMock.Models;
/// <summary>
/// A structure defining an (optional) Id and a Text.
/// </summary>
public readonly struct IdOrText
{
/// <summary>
/// The Id [optional].
/// </summary>
public string? Id { get; }
/// <summary>
/// The Text.
/// </summary>
public string Text { get; }
/// <summary>
/// When Id is defined, return the Id, else the Text.
/// </summary>
public string Value => Id ?? Text;
/// <summary>
/// Create a IdOrText
/// </summary>
/// <param name="id">The Id [optional]</param>
/// <param name="text">The Text.</param>
public IdOrText(string? id, string text)
{
Id = id;
Text = text;
}
}

View File

@@ -38,5 +38,10 @@ public enum BodyType
/// <summary>
/// Body is a String which is x-www-form-urlencoded.
/// </summary>
FormUrlEncoded
FormUrlEncoded,
/// <summary>
/// Body is a ProtoBuf Byte array
/// </summary>
ProtoBuf
}

View File

@@ -31,7 +31,7 @@
</PropertyGroup>
<PropertyGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' or '$(TargetFramework)' == 'netstandard2.1'">
<DefineConstants>$(DefineConstants);GRAPHQL;MIMEKIT</DefineConstants>
<DefineConstants>$(DefineConstants);GRAPHQL;MIMEKIT;PROTOBUF</DefineConstants>
</PropertyGroup>
<ItemGroup>

View File

@@ -2,22 +2,34 @@ using Stef.Validation;
using WireMock.Server;
// ReSharper disable once CheckNamespace
namespace WireMock.FluentAssertions
namespace WireMock.FluentAssertions;
/// <summary>
/// Provides assertion methods to verify the number of calls made to a WireMock server.
/// This class is used in the context of FluentAssertions.
/// </summary>
public class WireMockANumberOfCallsAssertions
{
public class WireMockANumberOfCallsAssertions
private readonly IWireMockServer _server;
private readonly int _callsCount;
/// <summary>
/// Initializes a new instance of the <see cref="WireMockANumberOfCallsAssertions"/> class.
/// </summary>
/// <param name="server">The WireMock server to assert against.</param>
/// <param name="callsCount">The expected number of calls to assert.</param>
public WireMockANumberOfCallsAssertions(IWireMockServer server, int callsCount)
{
private readonly IWireMockServer _server;
private readonly int _callsCount;
_server = Guard.NotNull(server);
_callsCount = callsCount;
}
public WireMockANumberOfCallsAssertions(IWireMockServer server, int callsCount)
{
_server = Guard.NotNull(server);
_callsCount = callsCount;
}
public WireMockAssertions Calls()
{
return new WireMockAssertions(_server, _callsCount);
}
/// <summary>
/// Returns an instance of <see cref="WireMockAssertions"/> which can be used to assert the expected number of calls.
/// </summary>
/// <returns>A <see cref="WireMockAssertions"/> instance for asserting the number of calls to the server.</returns>
public WireMockAssertions Calls()
{
return new WireMockAssertions(_server, _callsCount);
}
}

View File

@@ -0,0 +1,60 @@
#pragma warning disable CS1591
using System;
// ReSharper disable once CheckNamespace
namespace WireMock.FluentAssertions;
public partial class WireMockAssertions
{
[CustomAssertion]
public AndWhichConstraint<WireMockAssertions, string> AtAbsoluteUrl(string absoluteUrl, string because = "", params object[] becauseArgs)
{
var (filter, condition) = BuildFilterAndCondition(request => string.Equals(request.AbsoluteUrl, absoluteUrl, StringComparison.OrdinalIgnoreCase));
Execute.Assertion
.BecauseOf(because, becauseArgs)
.Given(() => RequestMessages)
.ForCondition(requests => CallsCount == 0 || requests.Any())
.FailWith(
"Expected {context:wiremockserver} to have been called at address matching the absolute url {0}{reason}, but no calls were made.",
absoluteUrl
)
.Then
.ForCondition(condition)
.FailWith(
"Expected {context:wiremockserver} to have been called at address matching the absolute url {0}{reason}, but didn't find it among the calls to {1}.",
_ => absoluteUrl,
requests => requests.Select(request => request.AbsoluteUrl)
);
FilterRequestMessages(filter);
return new AndWhichConstraint<WireMockAssertions, string>(this, absoluteUrl);
}
[CustomAssertion]
public AndWhichConstraint<WireMockAssertions, string> AtUrl(string url, string because = "", params object[] becauseArgs)
{
var (filter, condition) = BuildFilterAndCondition(request => string.Equals(request.Url, url, StringComparison.OrdinalIgnoreCase));
Execute.Assertion
.BecauseOf(because, becauseArgs)
.Given(() => RequestMessages)
.ForCondition(requests => CallsCount == 0 || requests.Any())
.FailWith(
"Expected {context:wiremockserver} to have been called at address matching the url {0}{reason}, but no calls were made.",
url
)
.Then
.ForCondition(condition)
.FailWith(
"Expected {context:wiremockserver} to have been called at address matching the url {0}{reason}, but didn't find it among the calls to {1}.",
_ => url,
requests => requests.Select(request => request.Url)
);
FilterRequestMessages(filter);
return new AndWhichConstraint<WireMockAssertions, string>(this, url);
}
}

View File

@@ -0,0 +1,33 @@
#pragma warning disable CS1591
using System;
// ReSharper disable once CheckNamespace
namespace WireMock.FluentAssertions;
public partial class WireMockAssertions
{
[CustomAssertion]
public AndWhichConstraint<WireMockAssertions, string> FromClientIP(string clientIP, string because = "", params object[] becauseArgs)
{
var (filter, condition) = BuildFilterAndCondition(request => string.Equals(request.ClientIP, clientIP, StringComparison.OrdinalIgnoreCase));
Execute.Assertion
.BecauseOf(because, becauseArgs)
.Given(() => RequestMessages)
.ForCondition(requests => CallsCount == 0 || requests.Any())
.FailWith(
"Expected {context:wiremockserver} to have been called from client IP {0}{reason}, but no calls were made.",
clientIP
)
.Then
.ForCondition(condition)
.FailWith(
"Expected {context:wiremockserver} to have been called from client IP {0}{reason}, but didn't find it among the calls from IP(s) {1}.",
_ => clientIP, requests => requests.Select(request => request.ClientIP)
);
FilterRequestMessages(filter);
return new AndWhichConstraint<WireMockAssertions, string>(this, clientIP);
}
}

View File

@@ -58,8 +58,8 @@ public partial class WireMockAssertions
Execute.Assertion
.BecauseOf(because, becauseArgs)
.Given(() => _requestMessages)
.ForCondition(requests => _callsCount == 0 || requests.Any())
.Given(() => RequestMessages)
.ForCondition(requests => CallsCount == 0 || requests.Any())
.FailWith(
"Expected {context:wiremockserver} to have been called using method {0}{reason}, but no calls were made.",
method
@@ -72,7 +72,7 @@ public partial class WireMockAssertions
requests => requests.Select(request => request.Method)
);
_requestMessages = filter(_requestMessages).ToList();
FilterRequestMessages(filter);
return new AndConstraint<WireMockAssertions>(this);
}

View File

@@ -44,11 +44,11 @@ public partial class WireMockAssertions
}
[CustomAssertion]
public AndConstraint<WireMockAssertions> WithBodyAsJson(IValueMatcher matcher, string because = "", params object[] becauseArgs)
public AndConstraint<WireMockAssertions> WithBodyAsJson(IObjectMatcher matcher, string because = "", params object[] becauseArgs)
{
var (filter, condition) = BuildFilterAndCondition(r => r.BodyAsJson, matcher);
return ExecuteAssertionWithBodyAsJsonValueMatcher(matcher, because, becauseArgs, condition, filter, r => r.BodyAsJson);
return ExecuteAssertionWithBodyAsIObjectMatcher(matcher, because, becauseArgs, condition, filter, r => r.BodyAsJson);
}
[CustomAssertion]
@@ -70,8 +70,8 @@ public partial class WireMockAssertions
{
Execute.Assertion
.BecauseOf(because, becauseArgs)
.Given(() => _requestMessages)
.ForCondition(requests => _callsCount == 0 || requests.Any())
.Given(() => RequestMessages)
.ForCondition(requests => CallsCount == 0 || requests.Any())
.FailWith(
MessageFormatNoCalls,
matcher.GetPatterns()
@@ -84,13 +84,13 @@ public partial class WireMockAssertions
requests => requests.Select(expression)
);
_requestMessages = filter(_requestMessages).ToList();
FilterRequestMessages(filter);
return new AndConstraint<WireMockAssertions>(this);
}
private AndConstraint<WireMockAssertions> ExecuteAssertionWithBodyAsJsonValueMatcher(
IValueMatcher matcher,
private AndConstraint<WireMockAssertions> ExecuteAssertionWithBodyAsIObjectMatcher(
IObjectMatcher matcher,
string because,
object[] becauseArgs,
Func<IReadOnlyList<IRequestMessage>, bool> condition,
@@ -100,8 +100,8 @@ public partial class WireMockAssertions
{
Execute.Assertion
.BecauseOf(because, becauseArgs)
.Given(() => _requestMessages)
.ForCondition(requests => _callsCount == 0 || requests.Any())
.Given(() => RequestMessages)
.ForCondition(requests => CallsCount == 0 || requests.Any())
.FailWith(
MessageFormatNoCalls,
matcher.Value
@@ -114,7 +114,7 @@ public partial class WireMockAssertions
requests => requests.Select(expression)
);
_requestMessages = filter(_requestMessages).ToList();
FilterRequestMessages(filter);
return new AndConstraint<WireMockAssertions>(this);
}
@@ -130,21 +130,21 @@ public partial class WireMockAssertions
{
Execute.Assertion
.BecauseOf(because, becauseArgs)
.Given(() => _requestMessages)
.ForCondition(requests => _callsCount == 0 || requests.Any())
.Given(() => RequestMessages)
.ForCondition(requests => CallsCount == 0 || requests.Any())
.FailWith(
MessageFormatNoCalls,
matcher.ValueAsObject ?? matcher.ValueAsBytes
matcher.Value
)
.Then
.ForCondition(condition)
.FailWith(
MessageFormat,
_ => matcher.ValueAsObject ?? matcher.ValueAsBytes,
_ => matcher.Value,
requests => requests.Select(expression)
);
_requestMessages = filter(_requestMessages).ToList();
FilterRequestMessages(filter);
return new AndConstraint<WireMockAssertions>(this);
}

View File

@@ -0,0 +1,157 @@
#pragma warning disable CS1591
using System.Collections.Generic;
using WireMock.Types;
// ReSharper disable once CheckNamespace
namespace WireMock.FluentAssertions;
public partial class WireMockAssertions
{
[CustomAssertion]
public AndWhichConstraint<WireMockAssertions, string> WitHeaderKey(string expectedKey, string because = "", params object[] becauseArgs)
{
var (filter, condition) = BuildFilterAndCondition(request =>
{
return request.Headers?.Any(h => h.Key == expectedKey) == true;
});
Execute.Assertion
.BecauseOf(because, becauseArgs)
.Given(() => RequestMessages)
.ForCondition(requests => CallsCount == 0 || requests.Any())
.FailWith(
"Expected {context:wiremockserver} to have been called with Header {0}{reason}.",
expectedKey
)
.Then
.ForCondition(condition)
.FailWith(
"Expected {context:wiremockserver} to have been called with Header {0}{reason}, but didn't find it among the calls with Header(s) {1}.",
_ => expectedKey,
requests => requests.Select(request => request.Headers)
);
FilterRequestMessages(filter);
return new AndWhichConstraint<WireMockAssertions, string>(this, expectedKey);
}
[CustomAssertion]
public AndConstraint<WireMockAssertions> WithHeader(string expectedKey, string value, string because = "", params object[] becauseArgs)
=> WithHeader(expectedKey, new[] { value }, because, becauseArgs);
[CustomAssertion]
public AndConstraint<WireMockAssertions> WithHeader(string expectedKey, string[] expectedValues, string because = "", params object[] becauseArgs)
{
var (filter, condition) = BuildFilterAndCondition(request =>
{
var headers = request.Headers?.ToArray() ?? new KeyValuePair<string, WireMockList<string>>[0];
var matchingHeaderValues = headers.Where(h => h.Key == expectedKey).SelectMany(h => h.Value.ToArray()).ToArray();
if (expectedValues.Length == 1 && matchingHeaderValues.Length == 1)
{
return matchingHeaderValues[0] == expectedValues[0];
}
var trimmedHeaderValues = string.Join(",", matchingHeaderValues.Select(x => x)).Split(',').Select(x => x.Trim()).ToArray();
return expectedValues.Any(trimmedHeaderValues.Contains);
});
Execute.Assertion
.BecauseOf(because, becauseArgs)
.Given(() => RequestMessages)
.ForCondition(requests => CallsCount == 0 || requests.Any())
.FailWith(
"Expected {context:wiremockserver} to have been called with Header {0} and Values {1}{reason}.",
expectedKey,
expectedValues
)
.Then
.ForCondition(condition)
.FailWith(
"Expected {context:wiremockserver} to have been called with Header {0} and Values {1}{reason}, but didn't find it among the calls with Header(s) {2}.",
_ => expectedKey,
_ => expectedValues,
requests => requests.Select(request => request.Headers)
);
FilterRequestMessages(filter);
return new AndConstraint<WireMockAssertions>(this);
}
[CustomAssertion]
public AndConstraint<WireMockAssertions> WithoutHeaderKey(string unexpectedKey, string because = "", params object[] becauseArgs)
{
var (filter, condition) = BuildFilterAndCondition(request =>
{
return request.Headers?.Any(h => h.Key == unexpectedKey) != true;
});
Execute.Assertion
.BecauseOf(because, becauseArgs)
.Given(() => RequestMessages)
.ForCondition(requests => CallsCount == 0 || requests.Any())
.FailWith(
"Expected {context:wiremockserver} not to have been called with Header {0}{reason}.",
unexpectedKey
)
.Then
.ForCondition(condition)
.FailWith(
"Expected {context:wiremockserver} not to have been called with Header {0}{reason}, but found it among the calls with Header(s) {1}.",
_ => unexpectedKey,
requests => requests.Select(request => request.Headers)
);
FilterRequestMessages(filter);
return new AndConstraint<WireMockAssertions>(this);
}
[CustomAssertion]
public AndConstraint<WireMockAssertions> WithoutHeader(string unexpectedKey, string value, string because = "", params object[] becauseArgs)
=> WithoutHeader(unexpectedKey, new[] { value }, because, becauseArgs);
[CustomAssertion]
public AndConstraint<WireMockAssertions> WithoutHeader(string unexpectedKey, string[] expectedValues, string because = "", params object[] becauseArgs)
{
var (filter, condition) = BuildFilterAndCondition(request =>
{
var headers = request.Headers?.ToArray() ?? new KeyValuePair<string, WireMockList<string>>[0];
var matchingHeaderValues = headers.Where(h => h.Key == unexpectedKey).SelectMany(h => h.Value.ToArray()).ToArray();
if (expectedValues.Length == 1 && matchingHeaderValues.Length == 1)
{
return matchingHeaderValues[0] != expectedValues[0];
}
var trimmedHeaderValues = string.Join(",", matchingHeaderValues.Select(x => x)).Split(',').Select(x => x.Trim()).ToArray();
return !expectedValues.Any(trimmedHeaderValues.Contains);
});
Execute.Assertion
.BecauseOf(because, becauseArgs)
.Given(() => RequestMessages)
.ForCondition(requests => CallsCount == 0 || requests.Any())
.FailWith(
"Expected {context:wiremockserver} not to have been called with Header {0} and Values {1}{reason}.",
unexpectedKey,
expectedValues
)
.Then
.ForCondition(condition)
.FailWith(
"Expected {context:wiremockserver} not to have been called with Header {0} and Values {1}{reason}, but found it among the calls with Header(s) {2}.",
_ => unexpectedKey,
_ => expectedValues,
requests => requests.Select(request => request.Headers)
);
FilterRequestMessages(filter);
return new AndConstraint<WireMockAssertions>(this);
}
}

View File

@@ -0,0 +1,34 @@
#pragma warning disable CS1591
using System;
// ReSharper disable once CheckNamespace
namespace WireMock.FluentAssertions;
public partial class WireMockAssertions
{
[CustomAssertion]
public AndWhichConstraint<WireMockAssertions, string> WithProxyUrl(string proxyUrl, string because = "", params object[] becauseArgs)
{
var (filter, condition) = BuildFilterAndCondition(request => string.Equals(request.ProxyUrl, proxyUrl, StringComparison.OrdinalIgnoreCase));
Execute.Assertion
.BecauseOf(because, becauseArgs)
.Given(() => RequestMessages)
.ForCondition(requests => CallsCount == 0 || requests.Any())
.FailWith(
"Expected {context:wiremockserver} to have been called with proxy url {0}{reason}, but no calls were made.",
proxyUrl
)
.Then
.ForCondition(condition)
.FailWith(
"Expected {context:wiremockserver} to have been called with proxy url {0}{reason}, but didn't find it among the calls with {1}.",
_ => proxyUrl,
requests => requests.Select(request => request.ProxyUrl)
);
FilterRequestMessages(filter);
return new AndWhichConstraint<WireMockAssertions, string>(this, proxyUrl);
}
}

View File

@@ -3,174 +3,42 @@ using System;
using System.Collections.Generic;
using WireMock.Matchers;
using WireMock.Server;
using WireMock.Types;
// ReSharper disable once CheckNamespace
namespace WireMock.FluentAssertions;
public partial class WireMockAssertions
{
private const string Any = "*";
private readonly int? _callsCount;
private IReadOnlyList<IRequestMessage> _requestMessages;
private readonly IReadOnlyList<KeyValuePair<string, WireMockList<string>>> _headers;
public const string Any = "*";
public int? CallsCount { get; }
public IReadOnlyList<IRequestMessage> RequestMessages { get; private set; }
public WireMockAssertions(IWireMockServer subject, int? callsCount)
{
_callsCount = callsCount;
_requestMessages = subject.LogEntries.Select(logEntry => logEntry.RequestMessage).ToList();
_headers = _requestMessages.SelectMany(req => req.Headers).ToList();
CallsCount = callsCount;
RequestMessages = subject.LogEntries.Select(logEntry => logEntry.RequestMessage).ToList();
}
[CustomAssertion]
public AndWhichConstraint<WireMockAssertions, string> AtAbsoluteUrl(string absoluteUrl, string because = "", params object[] becauseArgs)
{
var (filter, condition) = BuildFilterAndCondition(request => string.Equals(request.AbsoluteUrl, absoluteUrl, StringComparison.OrdinalIgnoreCase));
Execute.Assertion
.BecauseOf(because, becauseArgs)
.Given(() => _requestMessages)
.ForCondition(requests => _callsCount == 0 || requests.Any())
.FailWith(
"Expected {context:wiremockserver} to have been called at address matching the absolute url {0}{reason}, but no calls were made.",
absoluteUrl
)
.Then
.ForCondition(condition)
.FailWith(
"Expected {context:wiremockserver} to have been called at address matching the absolute url {0}{reason}, but didn't find it among the calls to {1}.",
_ => absoluteUrl, requests => requests.Select(request => request.AbsoluteUrl)
);
_requestMessages = filter(_requestMessages).ToList();
return new AndWhichConstraint<WireMockAssertions, string>(this, absoluteUrl);
}
[CustomAssertion]
public AndWhichConstraint<WireMockAssertions, string> AtUrl(string url, string because = "", params object[] becauseArgs)
{
var (filter, condition) = BuildFilterAndCondition(request => string.Equals(request.Url, url, StringComparison.OrdinalIgnoreCase));
Execute.Assertion
.BecauseOf(because, becauseArgs)
.Given(() => _requestMessages)
.ForCondition(requests => _callsCount == 0 || requests.Any())
.FailWith(
"Expected {context:wiremockserver} to have been called at address matching the url {0}{reason}, but no calls were made.",
url
)
.Then
.ForCondition(condition)
.FailWith(
"Expected {context:wiremockserver} to have been called at address matching the url {0}{reason}, but didn't find it among the calls to {1}.",
_ => url,
requests => requests.Select(request => request.Url)
);
_requestMessages = filter(_requestMessages).ToList();
return new AndWhichConstraint<WireMockAssertions, string>(this, url);
}
[CustomAssertion]
public AndWhichConstraint<WireMockAssertions, string> WithProxyUrl(string proxyUrl, string because = "", params object[] becauseArgs)
{
var (filter, condition) = BuildFilterAndCondition(request => string.Equals(request.ProxyUrl, proxyUrl, StringComparison.OrdinalIgnoreCase));
Execute.Assertion
.BecauseOf(because, becauseArgs)
.Given(() => _requestMessages)
.ForCondition(requests => _callsCount == 0 || requests.Any())
.FailWith(
"Expected {context:wiremockserver} to have been called with proxy url {0}{reason}, but no calls were made.",
proxyUrl
)
.Then
.ForCondition(condition)
.FailWith(
"Expected {context:wiremockserver} to have been called with proxy url {0}{reason}, but didn't find it among the calls with {1}.",
_ => proxyUrl,
requests => requests.Select(request => request.ProxyUrl)
);
_requestMessages = filter(_requestMessages).ToList();
return new AndWhichConstraint<WireMockAssertions, string>(this, proxyUrl);
}
[CustomAssertion]
public AndWhichConstraint<WireMockAssertions, string> FromClientIP(string clientIP, string because = "", params object[] becauseArgs)
{
var (filter, condition) = BuildFilterAndCondition(request => string.Equals(request.ClientIP, clientIP, StringComparison.OrdinalIgnoreCase));
Execute.Assertion
.BecauseOf(because, becauseArgs)
.Given(() => _requestMessages)
.ForCondition(requests => _callsCount == 0 || requests.Any())
.FailWith(
"Expected {context:wiremockserver} to have been called from client IP {0}{reason}, but no calls were made.",
clientIP
)
.Then
.ForCondition(condition)
.FailWith(
"Expected {context:wiremockserver} to have been called from client IP {0}{reason}, but didn't find it among the calls from IP(s) {1}.",
_ => clientIP, requests => requests.Select(request => request.ClientIP)
);
_requestMessages = filter(_requestMessages).ToList();
return new AndWhichConstraint<WireMockAssertions, string>(this, clientIP);
}
[CustomAssertion]
public AndConstraint<WireMockAssertions> WithHeader(string expectedKey, string value, string because = "", params object[] becauseArgs)
=> WithHeader(expectedKey, new[] { value }, because, becauseArgs);
[CustomAssertion]
public AndConstraint<WireMockAssertions> WithHeader(string expectedKey, string[] expectedValues, string because = "", params object[] becauseArgs)
{
using (new AssertionScope("headers from requests sent"))
{
_headers.Select(h => h.Key).Should().Contain(expectedKey, because, becauseArgs);
}
using (new AssertionScope($"header \"{expectedKey}\" from requests sent with value(s)"))
{
var matchingHeaderValues = _headers.Where(h => h.Key == expectedKey).SelectMany(h => h.Value.ToArray()).ToArray();
if (expectedValues.Length == 1)
{
matchingHeaderValues.Should().Contain(expectedValues.First(), because, becauseArgs);
}
else
{
var trimmedHeaderValues = string.Join(",", matchingHeaderValues.Select(x => x)).Split(',').Select(x => x.Trim()).ToList();
foreach (var expectedValue in expectedValues)
{
trimmedHeaderValues.Should().Contain(expectedValue, because, becauseArgs);
}
}
}
return new AndConstraint<WireMockAssertions>(this);
}
private (Func<IReadOnlyList<IRequestMessage>, IReadOnlyList<IRequestMessage>> Filter, Func<IReadOnlyList<IRequestMessage>, bool> Condition) BuildFilterAndCondition(Func<IRequestMessage, bool> predicate)
public (Func<IReadOnlyList<IRequestMessage>, IReadOnlyList<IRequestMessage>> Filter, Func<IReadOnlyList<IRequestMessage>, bool> Condition) BuildFilterAndCondition(Func<IRequestMessage, bool> predicate)
{
Func<IReadOnlyList<IRequestMessage>, IReadOnlyList<IRequestMessage>> filter = requests => requests.Where(predicate).ToList();
return (filter, requests => (_callsCount is null && filter(requests).Any()) || _callsCount == filter(requests).Count);
return (filter, requests => (CallsCount is null && filter(requests).Any()) || CallsCount == filter(requests).Count);
}
private (Func<IReadOnlyList<IRequestMessage>, IReadOnlyList<IRequestMessage>> Filter, Func<IReadOnlyList<IRequestMessage>, bool> Condition) BuildFilterAndCondition(Func<IRequestMessage, string?> expression, IStringMatcher matcher)
public (Func<IReadOnlyList<IRequestMessage>, IReadOnlyList<IRequestMessage>> Filter, Func<IReadOnlyList<IRequestMessage>, bool> Condition) BuildFilterAndCondition(Func<IRequestMessage, string?> expression, IStringMatcher matcher)
{
return BuildFilterAndCondition(r => matcher.IsMatch(expression(r)).IsPerfect());
}
private (Func<IReadOnlyList<IRequestMessage>, IReadOnlyList<IRequestMessage>> Filter, Func<IReadOnlyList<IRequestMessage>, bool> Condition) BuildFilterAndCondition(Func<IRequestMessage, object?> expression, IObjectMatcher matcher)
public (Func<IReadOnlyList<IRequestMessage>, IReadOnlyList<IRequestMessage>> Filter, Func<IReadOnlyList<IRequestMessage>, bool> Condition) BuildFilterAndCondition(Func<IRequestMessage, object?> expression, IObjectMatcher matcher)
{
return BuildFilterAndCondition(r => matcher.IsMatch(expression(r)).IsPerfect());
}
public void FilterRequestMessages(Func<IReadOnlyList<IRequestMessage>, IReadOnlyList<IRequestMessage>> filter)
{
RequestMessages = filter(RequestMessages).ToList();
}
}

View File

@@ -2,50 +2,49 @@ using FluentAssertions.Primitives;
using WireMock.Server;
// ReSharper disable once CheckNamespace
namespace WireMock.FluentAssertions
namespace WireMock.FluentAssertions;
/// <summary>
/// Contains a number of methods to assert that the <see cref="IWireMockServer"/> is in the expected state.
/// </summary>
public class WireMockReceivedAssertions : ReferenceTypeAssertions<IWireMockServer, WireMockReceivedAssertions>
{
/// <summary>
/// Contains a number of methods to assert that the <see cref="IWireMockServer"/> is in the expected state.
/// Create a WireMockReceivedAssertions.
/// </summary>
public class WireMockReceivedAssertions : ReferenceTypeAssertions<IWireMockServer, WireMockReceivedAssertions>
/// <param name="server">The <see cref="IWireMockServer"/>.</param>
public WireMockReceivedAssertions(IWireMockServer server) : base(server)
{
/// <summary>
/// Create a WireMockReceivedAssertions.
/// </summary>
/// <param name="server">The <see cref="IWireMockServer"/>.</param>
public WireMockReceivedAssertions(IWireMockServer server) : base(server)
{
}
/// <summary>
/// Asserts if <see cref="IWireMockServer"/> has received no calls.
/// </summary>
/// <returns><see cref="WireMockAssertions"/></returns>
public WireMockAssertions HaveReceivedNoCalls()
{
return new WireMockAssertions(Subject, 0);
}
/// <summary>
/// Asserts if <see cref="IWireMockServer"/> has received a call.
/// </summary>
/// <returns><see cref="WireMockAssertions"/></returns>
public WireMockAssertions HaveReceivedACall()
{
return new WireMockAssertions(Subject, null);
}
/// <summary>
/// Asserts if <see cref="IWireMockServer"/> has received n-calls.
/// </summary>
/// <param name="callsCount"></param>
/// <returns><see cref="WireMockANumberOfCallsAssertions"/></returns>
public WireMockANumberOfCallsAssertions HaveReceived(int callsCount)
{
return new WireMockANumberOfCallsAssertions(Subject, callsCount);
}
/// <inheritdoc />
protected override string Identifier => "wiremockserver";
}
/// <summary>
/// Asserts if <see cref="IWireMockServer"/> has received no calls.
/// </summary>
/// <returns><see cref="WireMockAssertions"/></returns>
public WireMockAssertions HaveReceivedNoCalls()
{
return new WireMockAssertions(Subject, 0);
}
/// <summary>
/// Asserts if <see cref="IWireMockServer"/> has received a call.
/// </summary>
/// <returns><see cref="WireMockAssertions"/></returns>
public WireMockAssertions HaveReceivedACall()
{
return new WireMockAssertions(Subject, null);
}
/// <summary>
/// Asserts if <see cref="IWireMockServer"/> has received n-calls.
/// </summary>
/// <param name="callsCount"></param>
/// <returns><see cref="WireMockANumberOfCallsAssertions"/></returns>
public WireMockANumberOfCallsAssertions HaveReceived(int callsCount)
{
return new WireMockANumberOfCallsAssertions(Subject, callsCount);
}
/// <inheritdoc />
protected override string Identifier => "wiremockserver";
}

View File

@@ -33,6 +33,9 @@ internal class CSharpCodeMatcher : ICSharpCodeMatcher
/// <inheritdoc />
public MatchBehaviour MatchBehaviour { get; }
/// <inheritdoc />
public object Value { get; }
private readonly AnyOf<string, StringPattern>[] _patterns;
/// <summary>
@@ -54,6 +57,7 @@ internal class CSharpCodeMatcher : ICSharpCodeMatcher
_patterns = Guard.NotNull(patterns);
MatchBehaviour = matchBehaviour;
MatchOperator = matchOperator;
Value = patterns;
}
public MatchResult IsMatch(string? input)
@@ -160,34 +164,34 @@ internal class CSharpCodeMatcher : ICSharpCodeMatcher
}
#elif (NETSTANDARD2_0 || NETSTANDARD2_1 || NETCOREAPP3_1 || NET5_0_OR_GREATER)
Assembly assembly;
try
{
assembly = CSScriptLib.CSScript.Evaluator.CompileCode(source);
}
catch (Exception ex)
{
throw new WireMockException($"CSharpCodeMatcher: Unable to compile code `{source}` for WireMock.CodeHelper", ex);
}
Assembly assembly;
try
{
assembly = CSScriptLib.CSScript.Evaluator.CompileCode(source);
}
catch (Exception ex)
{
throw new WireMockException($"CSharpCodeMatcher: Unable to compile code `{source}` for WireMock.CodeHelper", ex);
}
dynamic script;
try
{
script = CSScripting.ReflectionExtensions.CreateObject(assembly, "*");
}
catch (Exception ex)
{
throw new WireMockException("CSharpCodeMatcher: Unable to create object from assembly", ex);
}
dynamic script;
try
{
script = CSScripting.ReflectionExtensions.CreateObject(assembly, "*");
}
catch (Exception ex)
{
throw new WireMockException("CSharpCodeMatcher: Unable to create object from assembly", ex);
}
try
{
result = script.IsMatch(inputValue);
}
catch (Exception ex)
{
throw new WireMockException("CSharpCodeMatcher: Problem calling method 'IsMatch' in WireMock.CodeHelper", ex);
}
try
{
result = script.IsMatch(inputValue);
}
catch (Exception ex)
{
throw new WireMockException("CSharpCodeMatcher: Problem calling method 'IsMatch' in WireMock.CodeHelper", ex);
}
#else
throw new NotSupportedException("The 'CSharpCodeMatcher' cannot be used in netstandard 1.3");
#endif

View File

@@ -46,7 +46,7 @@
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' or '$(TargetFramework)' == 'netstandard2.1' or '$(TargetFramework)' == 'netcoreapp3.1' or '$(TargetFramework)' == 'net5.0' or '$(TargetFramework)' == 'net6.0' or '$(TargetFramework)' == 'net7.0' or '$(TargetFramework)' == 'net8.0'">
<PackageReference Include="CS-Script" Version="4.4.2" />
<PackageReference Include="CS-Script" Version="4.8.13" />
</ItemGroup>
</Project>

View File

@@ -11,47 +11,47 @@ public interface IWireMockOpenApiParserExampleValues
/// <summary>
/// An example value for a Boolean.
/// </summary>
bool Boolean { get; set; }
bool Boolean { get; }
/// <summary>
/// An example value for an Integer.
/// </summary>
int Integer { get; set; }
int Integer { get; }
/// <summary>
/// An example value for a Float.
/// </summary>
float Float { get; set; }
float Float { get; }
/// <summary>
/// An example value for a Double.
/// </summary>
double Double { get; set; }
double Double { get; }
/// <summary>
/// An example value for a Date.
/// </summary>
Func<DateTime> Date { get; set; }
Func<DateTime> Date { get; }
/// <summary>
/// An example value for a DateTime.
/// </summary>
Func<DateTime> DateTime { get; set; }
Func<DateTime> DateTime { get; }
/// <summary>
/// An example value for Bytes.
/// </summary>
byte[] Bytes { get; set; }
byte[] Bytes { get; }
/// <summary>
/// An example value for a Object.
/// </summary>
object Object { get; set; }
object Object { get; }
/// <summary>
/// An example value for a String.
/// </summary>
string String { get; set; }
string String { get; }
/// <summary>
/// OpenApi Schema to generate dynamic examples more accurate

View File

@@ -11,31 +11,31 @@ namespace WireMock.Net.OpenApiParser.Settings;
public class WireMockOpenApiParserDynamicExampleValues : IWireMockOpenApiParserExampleValues
{
/// <inheritdoc />
public virtual bool Boolean { get => RandomizerFactory.GetRandomizer(new FieldOptionsBoolean()).Generate() ?? true; set { } }
public virtual bool Boolean => RandomizerFactory.GetRandomizer(new FieldOptionsBoolean()).Generate() ?? true;
/// <inheritdoc />
public virtual int Integer { get => RandomizerFactory.GetRandomizer(new FieldOptionsInteger()).Generate() ?? 42; set { } }
public virtual int Integer => RandomizerFactory.GetRandomizer(new FieldOptionsInteger()).Generate() ?? 42;
/// <inheritdoc />
public virtual float Float { get => RandomizerFactory.GetRandomizer(new FieldOptionsFloat()).Generate() ?? 4.2f; set { } }
public virtual float Float => RandomizerFactory.GetRandomizer(new FieldOptionsFloat()).Generate() ?? 4.2f;
/// <inheritdoc />
public virtual double Double { get => RandomizerFactory.GetRandomizer(new FieldOptionsDouble()).Generate() ?? 4.2d; set { } }
public virtual double Double => RandomizerFactory.GetRandomizer(new FieldOptionsDouble()).Generate() ?? 4.2d;
/// <inheritdoc />
public virtual Func<DateTime> Date { get { return () => RandomizerFactory.GetRandomizer(new FieldOptionsDateTime()).Generate() ?? System.DateTime.UtcNow.Date; } set { } }
public virtual Func<DateTime> Date { get { return () => RandomizerFactory.GetRandomizer(new FieldOptionsDateTime()).Generate() ?? System.DateTime.UtcNow.Date; } }
/// <inheritdoc />
public virtual Func<DateTime> DateTime { get { return () => RandomizerFactory.GetRandomizer(new FieldOptionsDateTime()).Generate() ?? System.DateTime.UtcNow; } set { } }
public virtual Func<DateTime> DateTime { get { return () => RandomizerFactory.GetRandomizer(new FieldOptionsDateTime()).Generate() ?? System.DateTime.UtcNow; } }
/// <inheritdoc />
public virtual byte[] Bytes { get => RandomizerFactory.GetRandomizer(new FieldOptionsBytes()).Generate(); set { } }
public virtual byte[] Bytes => RandomizerFactory.GetRandomizer(new FieldOptionsBytes()).Generate();
/// <inheritdoc />
public virtual object Object { get; set; } = "example-object";
public virtual object Object => "example-object";
/// <inheritdoc />
public virtual string String { get => RandomizerFactory.GetRandomizer(new FieldOptionsTextRegex { Pattern = @"^[0-9]{2}[A-Z]{5}[0-9]{2}" }).Generate() ?? "example-string"; set { } }
public virtual string String => RandomizerFactory.GetRandomizer(new FieldOptionsTextRegex { Pattern = @"^[0-9]{2}[A-Z]{5}[0-9]{2}" }).Generate() ?? "example-string";
/// <inheritdoc />
public virtual OpenApiSchema? Schema { get; set; }

View File

@@ -9,31 +9,31 @@ namespace WireMock.Net.OpenApiParser.Settings;
public class WireMockOpenApiParserExampleValues : IWireMockOpenApiParserExampleValues
{
/// <inheritdoc />
public virtual bool Boolean { get; set; } = true;
public virtual bool Boolean => true;
/// <inheritdoc />
public virtual int Integer { get; set; } = 42;
public virtual int Integer => 42;
/// <inheritdoc />
public virtual float Float { get; set; } = 4.2f;
public virtual float Float => 4.2f;
/// <inheritdoc />
public virtual double Double { get; set; } = 4.2d;
public virtual double Double => 4.2d;
/// <inheritdoc />
public virtual Func<DateTime> Date { get; set; } = () => System.DateTime.UtcNow.Date;
public virtual Func<DateTime> Date { get; } = () => System.DateTime.UtcNow.Date;
/// <inheritdoc />
public virtual Func<DateTime> DateTime { get; set; } = () => System.DateTime.UtcNow;
public virtual Func<DateTime> DateTime { get; } = () => System.DateTime.UtcNow;
/// <inheritdoc />
public virtual byte[] Bytes { get; set; } = { 48, 49, 50 };
public virtual byte[] Bytes { get; } = { 48, 49, 50 };
/// <inheritdoc />
public virtual object Object { get; set; } = "example-object";
public virtual object Object => "example-object";
/// <inheritdoc />
public virtual string String { get; set; } = "example-string";
public virtual string String => "example-string";
/// <inheritdoc />
public virtual OpenApiSchema? Schema { get; set; } = new();

View File

@@ -29,15 +29,12 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Stef.Validation" Version="0.1.1" />
<PackageReference Include="Testcontainers" Version="3.6.0" />
<PackageReference Include="Testcontainers" Version="[3.7.0]" />
<PackageReference Include="JetBrains.Annotations" VersionOverride="2022.3.1" PrivateAssets="All" Version="2023.3.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\WireMock.Net.RestClient\WireMock.Net.RestClient.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Http\" />
</ItemGroup>
</Project>

View File

@@ -16,10 +16,7 @@ public sealed class WireMockConfiguration : ContainerConfiguration
public bool HasBasicAuthentication => !string.IsNullOrEmpty(Username) && !string.IsNullOrEmpty(Password);
public WireMockConfiguration(
string? username = null,
string? password = null
)
public WireMockConfiguration(string? username = null, string? password = null)
{
Username = username;
Password = password;

View File

@@ -25,6 +25,11 @@ public sealed class WireMockContainerBuilder : ContainerBuilder<WireMockContaine
private readonly Lazy<Task<bool>> _isWindowsAsLazy = new(async () =>
{
if (TestcontainersSettings.OS.DockerEndpointAuthConfig == null)
{
throw new InvalidOperationException($"The {nameof(TestcontainersSettings.OS.DockerEndpointAuthConfig)} is null. Check if Docker is started.");
}
using var dockerClientConfig = TestcontainersSettings.OS.DockerEndpointAuthConfig.GetDockerClientConfiguration();
using var dockerClient = dockerClientConfig.CreateClient();

View File

@@ -69,12 +69,12 @@ public interface IMapping
int? StateTimes { get; }
/// <summary>
/// The Request matcher.
/// The RequestMatcher.
/// </summary>
IRequestMatcher RequestMatcher { get; }
/// <summary>
/// The Provider.
/// The ResponseProvider.
/// </summary>
IResponseProvider Provider { get; }
@@ -136,6 +136,11 @@ public interface IMapping
/// </summary>
double? Probability { get; }
/// <summary>
/// The Grpc ProtoDefinition which is used for this mapping (request and response). [Optional]
/// </summary>
IdOrText? ProtoDefinition { get; }
/// <summary>
/// ProvideResponseAsync
/// </summary>
@@ -150,4 +155,44 @@ public interface IMapping
/// <param name="nextState">The Next State.</param>
/// <returns>The <see cref="IRequestMatchResult"/>.</returns>
IRequestMatchResult GetRequestMatchResult(IRequestMessage requestMessage, string? nextState);
}
/// <summary>
/// Define the scenario.
/// </summary>
/// <param name="scenario">The scenario.</param>
/// <returns>The <see cref="IMapping"/>.</returns>
IMapping WithScenario(string scenario);
/// <summary>
/// Define the probability when this request should be matched. [Optional]
/// </summary>
/// <param name="probability">The probability.</param>
/// <returns>The <see cref="IMapping"/>.</returns>
IMapping WithProbability(double probability);
/// <summary>
/// Define a Grpc ProtoDefinition which is used for this mapping (request and response).
/// </summary>
/// <param name="protoDefinition">The proto definition as text.</param>
/// <returns>The <see cref="IMapping"/>.</returns>
IMapping WithProtoDefinition(IdOrText protoDefinition);
}
/*
executionConditionState">State in which the current mapping can occur. [Optional]
nextState">The next state which will occur after the current mapping execution. [Optional]
stateTimes">Only when the current state is executed this number, the next state which will occur. [Optional]
webhooks">The Webhooks. [Optional]
useWebhooksFireAndForget">Use Fire and Forget for the defined webhook(s). [Optional]
timeSettings">The TimeSettings. [Optional]
data">The data object. [Optional]
string? executionConditionState,
string? nextState,
int? stateTimes,
IWebhook[]? webhooks,
bool? useWebhooksFireAndForget,
ITimeSettings? timeSettings,
object? data,
*/

View File

@@ -1,5 +1,6 @@
using System;
using System.Threading.Tasks;
using Stef.Validation;
using WireMock.Matchers.Request;
using WireMock.Models;
using WireMock.ResponseProviders;
@@ -31,7 +32,7 @@ public class Mapping : IMapping
public int Priority { get; }
/// <inheritdoc />
public string? Scenario { get; }
public string? Scenario { get; private set; }
/// <inheritdoc />
public string? ExecutionConditionState { get; }
@@ -76,7 +77,10 @@ public class Mapping : IMapping
public object? Data { get; }
/// <inheritdoc />
public double? Probability { get; }
public double? Probability { get; private set; }
/// <inheritdoc />
public IdOrText? ProtoDefinition { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="Mapping"/> class.
@@ -98,8 +102,8 @@ public class Mapping : IMapping
/// <param name="useWebhooksFireAndForget">Use Fire and Forget for the defined webhook(s). [Optional]</param>
/// <param name="timeSettings">The TimeSettings. [Optional]</param>
/// <param name="data">The data object. [Optional]</param>
/// <param name="probability">Define the probability when this request should be matched. [Optional]</param>
public Mapping(
public Mapping
(
Guid guid,
DateTime updatedAt,
string? title,
@@ -116,8 +120,8 @@ public class Mapping : IMapping
IWebhook[]? webhooks,
bool? useWebhooksFireAndForget,
ITimeSettings? timeSettings,
object? data,
double? probability)
object? data
)
{
Guid = guid;
UpdatedAt = updatedAt;
@@ -136,7 +140,6 @@ public class Mapping : IMapping
UseWebhooksFireAndForget = useWebhooksFireAndForget;
TimeSettings = timeSettings;
Data = data;
Probability = probability;
}
/// <inheritdoc />
@@ -168,4 +171,25 @@ public class Mapping : IMapping
return result;
}
/// <inheritdoc />
public IMapping WithProbability(double probability)
{
Probability = Guard.NotNull(probability);
return this;
}
/// <inheritdoc />
public IMapping WithScenario(string scenario)
{
Scenario = Guard.NotNullOrWhiteSpace(scenario);
return this;
}
/// <inheritdoc />
public IMapping WithProtoDefinition(IdOrText protoDefinition)
{
ProtoDefinition = protoDefinition;
return this;
}
}

View File

@@ -6,6 +6,8 @@ using Stef.Validation;
using WireMock.Admin.Mappings;
using WireMock.Matchers.Request;
using WireMock.Owin;
using WireMock.RequestBuilders;
using WireMock.ResponseBuilders;
using WireMock.Serialization;
using WireMock.Server;
using WireMock.Settings;
@@ -146,6 +148,15 @@ public class MappingBuilder : IMappingBuilder
{
_mappingToFileSaver.SaveMappingToFile(mapping);
}
// Link this mapping to the Request
((Request)mapping.RequestMatcher).Mapping = mapping;
// Link this mapping to the Response
if (mapping.Provider is Response response)
{
response.Mapping = mapping;
}
}
private static string ToJson(object value)

View File

@@ -17,6 +17,15 @@ public class ExactMatcher : IStringMatcher, IIgnoreCaseMatcher
/// <inheritdoc />
public MatchBehaviour MatchBehaviour { get; }
/// <summary>
/// Initializes a new instance of the <see cref="ExactMatcher"/> class.
/// </summary>
/// <param name="matchBehaviour">The match behaviour.</param>
/// <param name="value">The string value.</param>
public ExactMatcher(MatchBehaviour matchBehaviour, string value) : this(matchBehaviour, true, MatchOperator.Or, new AnyOf<string, StringPattern>(value))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ExactMatcher"/> class.
/// </summary>

View File

@@ -9,15 +9,8 @@ namespace WireMock.Matchers;
/// <seealso cref="IObjectMatcher" />
public class ExactObjectMatcher : IObjectMatcher
{
/// <summary>
/// Gets the value as object.
/// </summary>
public object? ValueAsObject { get; }
/// <summary>
/// Gets the value as byte[].
/// </summary>
public byte[]? ValueAsBytes { get; }
/// <inheritdoc />
public object Value { get; }
/// <inheritdoc />
public MatchBehaviour MatchBehaviour { get; }
@@ -37,7 +30,7 @@ public class ExactObjectMatcher : IObjectMatcher
/// <param name="value">The value.</param>
public ExactObjectMatcher(MatchBehaviour matchBehaviour, object value)
{
ValueAsObject = Guard.NotNull(value);
Value = Guard.NotNull(value);
MatchBehaviour = matchBehaviour;
}
@@ -56,21 +49,21 @@ public class ExactObjectMatcher : IObjectMatcher
/// <param name="value">The value.</param>
public ExactObjectMatcher(MatchBehaviour matchBehaviour, byte[] value)
{
ValueAsBytes = Guard.NotNull(value);
Value = Guard.NotNull(value);
MatchBehaviour = matchBehaviour;
}
/// <inheritdoc />
public MatchResult IsMatch(object? input)
{
bool equals = false;
if (ValueAsObject != null)
bool equals;
if (Value is byte[] valueAsBytes && input is byte[] inputAsBytes)
{
equals = Equals(ValueAsObject, input);
equals = valueAsBytes.SequenceEqual(inputAsBytes);
}
else if (input != null)
else
{
equals = ValueAsBytes?.SequenceEqual((byte[])input) == true;
equals = Equals(Value, input);
}
return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.ToScore(equals));

View File

@@ -0,0 +1,18 @@
using System.Threading;
using System.Threading.Tasks;
namespace WireMock.Matchers;
/// <summary>
/// IBytesMatcher
/// </summary>
public interface IBytesMatcher : IMatcher
{
/// <summary>
/// Determines whether the specified input is match.
/// </summary>
/// <param name="input">The input byte array.</param>
/// <param name="cancellationToken">The CancellationToken [optional].</param>
/// <returns>MatchResult</returns>
Task<MatchResult> IsMatchAsync(byte[]? input, CancellationToken cancellationToken = default);
}

View File

@@ -0,0 +1,18 @@
using System.Threading;
using System.Threading.Tasks;
namespace WireMock.Matchers;
/// <summary>
/// IDecodeBytesMatcher
/// </summary>
public interface IDecodeBytesMatcher
{
/// <summary>
/// Decode byte array to an object.
/// </summary>
/// <param name="input">The byte array</param>
/// <param name="cancellationToken">The CancellationToken [optional].</param>
/// <returns>object</returns>
Task<object?> DecodeAsync(byte[]? input, CancellationToken cancellationToken = default);
}

View File

@@ -0,0 +1,9 @@
namespace WireMock.Matchers;
/// <summary>
/// IJsonMatcher
/// <seealso cref="IObjectMatcher"/> and <seealso cref="IIgnoreCaseMatcher"/>.
/// </summary>
public interface IJsonMatcher : IObjectMatcher, IIgnoreCaseMatcher
{
}

View File

@@ -5,6 +5,12 @@ namespace WireMock.Matchers;
/// </summary>
public interface IObjectMatcher : IMatcher
{
/// <summary>
/// Gets the value (can be a string or an object).
/// </summary>
/// <returns>Value</returns>
object Value { get; }
/// <summary>
/// Determines whether the specified input is match.
/// </summary>

View File

@@ -0,0 +1,8 @@
namespace WireMock.Matchers;
/// <summary>
/// IProtoBufMatcher
/// </summary>
public interface IProtoBufMatcher : IDecodeBytesMatcher, IBytesMatcher
{
}

View File

@@ -1,14 +0,0 @@
namespace WireMock.Matchers;
/// <summary>
/// IValueMatcher
/// </summary>
/// <seealso cref="IObjectMatcher" />
public interface IValueMatcher : IObjectMatcher
{
/// <summary>
/// Gets the value (can be a string or an object).
/// </summary>
/// <returns>Value</returns>
object Value { get; }
}

View File

@@ -11,7 +11,7 @@ namespace WireMock.Matchers;
/// <summary>
/// JsonPathMatcher
/// </summary>
/// <seealso cref="IMatcher" />
/// <seealso cref="IStringMatcher" />
/// <seealso cref="IObjectMatcher" />
public class JsonPathMatcher : IStringMatcher, IObjectMatcher
{
@@ -20,6 +20,9 @@ public class JsonPathMatcher : IStringMatcher, IObjectMatcher
/// <inheritdoc />
public MatchBehaviour MatchBehaviour { get; }
/// <inheritdoc />
public object Value { get; }
/// <summary>
/// Initializes a new instance of the <see cref="JsonPathMatcher"/> class.
/// </summary>
@@ -52,6 +55,7 @@ public class JsonPathMatcher : IStringMatcher, IObjectMatcher
_patterns = Guard.NotNull(patterns);
MatchBehaviour = matchBehaviour;
MatchOperator = matchOperator;
Value = patterns;
}
/// <inheritdoc />
@@ -119,7 +123,7 @@ public class JsonPathMatcher : IStringMatcher, IObjectMatcher
// The SelectToken method can accept a string path to a child token ( i.e. "Manufacturers[0].Products[0].Price").
// In that case it will return a JValue (some type) which does not implement the IEnumerable interface.
var values = _patterns.Select(pattern => array.SelectToken(pattern.GetPattern()) != null).ToArray();
return MatchScores.ToScore(values, MatchOperator);
}

View File

@@ -16,6 +16,9 @@ public class JmesPathMatcher : IStringMatcher, IObjectMatcher
{
private readonly AnyOf<string, StringPattern>[] _patterns;
/// <inheritdoc />
public object Value { get; }
/// <inheritdoc />
public MatchBehaviour MatchBehaviour { get; }
@@ -59,6 +62,7 @@ public class JmesPathMatcher : IStringMatcher, IObjectMatcher
_patterns = Guard.NotNull(patterns);
MatchBehaviour = matchBehaviour;
MatchOperator = matchOperator;
Value = patterns;
}
/// <inheritdoc />

View File

@@ -1,5 +1,4 @@
using System;
using System.Collections;
using System.Linq;
using Newtonsoft.Json.Linq;
using Stef.Validation;
@@ -10,12 +9,12 @@ namespace WireMock.Matchers;
/// <summary>
/// JsonMatcher
/// </summary>
public class JsonMatcher : IValueMatcher, IIgnoreCaseMatcher
public class JsonMatcher : IJsonMatcher
{
/// <inheritdoc />
public virtual string Name => "JsonMatcher";
public virtual string Name => nameof(JsonMatcher);
/// <inheritdoc cref="IValueMatcher.Value"/>
/// <inheritdoc />
public object Value { get; }
/// <inheritdoc />
@@ -59,7 +58,7 @@ public class JsonMatcher : IValueMatcher, IIgnoreCaseMatcher
IgnoreCase = ignoreCase;
Value = value;
_valueAsJToken = ConvertValueToJToken(value);
_valueAsJToken = JsonUtils.ConvertValueToJToken(value);
_jTokenConverter = ignoreCase ? Rename : jToken => jToken;
}
@@ -74,7 +73,7 @@ public class JsonMatcher : IValueMatcher, IIgnoreCaseMatcher
{
try
{
var inputAsJToken = ConvertValueToJToken(input);
var inputAsJToken = JsonUtils.ConvertValueToJToken(input);
var match = IsMatch(_jTokenConverter(_valueAsJToken), _jTokenConverter(inputAsJToken));
score = MatchScores.ToScore(match);
@@ -99,25 +98,6 @@ public class JsonMatcher : IValueMatcher, IIgnoreCaseMatcher
return JToken.DeepEquals(value, input);
}
private static JToken ConvertValueToJToken(object value)
{
// Check if JToken, string, IEnumerable or object
switch (value)
{
case JToken tokenValue:
return tokenValue;
case string stringValue:
return JsonUtils.Parse(stringValue);
case IEnumerable enumerableValue:
return JArray.FromObject(enumerableValue);
default:
return JObject.FromObject(value);
}
}
private static string? ToUpper(string? input)
{
return input?.ToUpperInvariant();

View File

@@ -22,6 +22,9 @@ public class LinqMatcher : IObjectMatcher, IStringMatcher
/// <inheritdoc />
public MatchBehaviour MatchBehaviour { get; }
/// <inheritdoc />
public object Value { get; }
/// <summary>
/// Initializes a new instance of the <see cref="LinqMatcher"/> class.
/// </summary>
@@ -61,6 +64,7 @@ public class LinqMatcher : IObjectMatcher, IStringMatcher
_patterns = Guard.NotNull(patterns);
MatchBehaviour = matchBehaviour;
MatchOperator = matchOperator;
Value = patterns;
}
/// <inheritdoc />

View File

@@ -23,4 +23,11 @@ internal static class MatchBehaviourHelper
return match <= MatchScores.Tolerance ? MatchScores.Perfect : MatchScores.Mismatch;
}
internal static MatchResult Convert(MatchBehaviour matchBehaviour, MatchResult result)
{
return matchBehaviour == MatchBehaviour.AcceptOnMatch ?
result :
new MatchResult(Convert(matchBehaviour, result.Score), result.Exception);
}
}

View File

@@ -17,6 +17,9 @@ public class NotNullOrEmptyMatcher : IObjectMatcher, IStringMatcher
/// <inheritdoc />
public MatchBehaviour MatchBehaviour { get; }
/// <inheritdoc />
public object Value { get; }
/// <summary>
/// Initializes a new instance of the <see cref="NotNullOrEmptyMatcher"/> class.
/// </summary>
@@ -24,6 +27,7 @@ public class NotNullOrEmptyMatcher : IObjectMatcher, IStringMatcher
public NotNullOrEmptyMatcher(MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{
MatchBehaviour = matchBehaviour;
Value = string.Empty;
}
/// <inheritdoc />

View File

@@ -0,0 +1,114 @@
#if PROTOBUF
using System;
using System.Threading;
using System.Threading.Tasks;
using ProtoBufJsonConverter;
using ProtoBufJsonConverter.Models;
using Stef.Validation;
using WireMock.Models;
using WireMock.Util;
namespace WireMock.Matchers;
/// <summary>
/// Grpc ProtoBuf Matcher
/// </summary>
/// <inheritdoc cref="IProtoBufMatcher"/>
public class ProtoBufMatcher : IProtoBufMatcher
{
/// <inheritdoc />
public string Name => nameof(ProtoBufMatcher);
/// <inheritdoc />
public MatchBehaviour MatchBehaviour { get; }
/// <summary>
/// The Func to define The proto definition as text.
/// </summary>
public Func<IdOrText> ProtoDefinition { get; }
/// <summary>
/// The full type of the protobuf (request/response) message object. Format is "{package-name}.{type-name}".
/// </summary>
public string MessageType { get; }
/// <summary>
/// The Matcher to use (optional).
/// </summary>
public IObjectMatcher? Matcher { get; }
private static readonly Converter ProtoBufToJsonConverter = SingletonFactory<Converter>.GetInstance();
/// <summary>
/// Initializes a new instance of the <see cref="ProtoBufMatcher"/> class.
/// </summary>
/// <param name="protoDefinition">The proto definition.</param>
/// <param name="messageType">The full type of the protobuf (request/response) message object. Format is "{package-name}.{type-name}".</param>
/// <param name="matchBehaviour">The match behaviour. (default = "AcceptOnMatch")</param>
/// <param name="matcher">The optional jsonMatcher to use to match the ProtoBuf as (json) object.</param>
public ProtoBufMatcher(
Func<IdOrText> protoDefinition,
string messageType,
MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch,
IObjectMatcher? matcher = null
)
{
ProtoDefinition = Guard.NotNull(protoDefinition);
MessageType = Guard.NotNullOrWhiteSpace(messageType);
Matcher = matcher;
MatchBehaviour = matchBehaviour;
}
/// <inheritdoc />
public async Task<MatchResult> IsMatchAsync(byte[]? input, CancellationToken cancellationToken = default)
{
var result = new MatchResult();
if (input != null)
{
try
{
var instance = await DecodeAsync(input, true, cancellationToken).ConfigureAwait(false);
result = Matcher?.IsMatch(instance) ?? new MatchResult(MatchScores.Perfect);
}
catch (Exception e)
{
result = new MatchResult(MatchScores.Mismatch, e);
}
}
return MatchBehaviourHelper.Convert(MatchBehaviour, result);
}
/// <inheritdoc />
public Task<object?> DecodeAsync(byte[]? input, CancellationToken cancellationToken = default)
{
return DecodeAsync(input, false, cancellationToken);
}
private async Task<object?> DecodeAsync(byte[]? input, bool throwException, CancellationToken cancellationToken)
{
if (input == null)
{
return null;
}
var request = new ConvertToObjectRequest(ProtoDefinition().Text, MessageType, input);
try
{
return await ProtoBufToJsonConverter.ConvertAsync(request, cancellationToken).ConfigureAwait(false);
}
catch
{
if (throwException)
{
throw;
}
return null;
}
}
}
#endif

View File

@@ -26,10 +26,8 @@ public abstract class RequestMessageCompositeMatcher : IRequestMatcher
/// <param name="type">The CompositeMatcherType type (Defaults to 'And')</param>
protected RequestMessageCompositeMatcher(IEnumerable<IRequestMatcher> requestMatchers, CompositeMatcherType type = CompositeMatcherType.And)
{
Guard.NotNull(requestMatchers);
RequestMatchers = Guard.NotNull(requestMatchers);
_type = type;
RequestMatchers = requestMatchers;
}
/// <inheritdoc />

View File

@@ -26,7 +26,7 @@ public class RequestMessageGraphQLMatcher : IRequestMatcher
/// </summary>
/// <param name="matchBehaviour">The match behaviour.</param>
/// <param name="schema">The schema.</param>
/// <param name="customScalars">A dictionary defining the custom scalars used in this schema. (optional)</param>
/// <param name="customScalars">A dictionary defining the custom scalars used in this schema. [optional]</param>
public RequestMessageGraphQLMatcher(MatchBehaviour matchBehaviour, string schema, IDictionary<string, Type>? customScalars = null) :
this(CreateMatcherArray(matchBehaviour, schema, customScalars))
{
@@ -38,7 +38,7 @@ public class RequestMessageGraphQLMatcher : IRequestMatcher
/// </summary>
/// <param name="matchBehaviour">The match behaviour.</param>
/// <param name="schema">The schema.</param>
/// <param name="customScalars">A dictionary defining the custom scalars used in this schema. (optional)</param>
/// <param name="customScalars">A dictionary defining the custom scalars used in this schema. [optional]</param>
public RequestMessageGraphQLMatcher(MatchBehaviour matchBehaviour, GraphQL.Types.ISchema schema, IDictionary<string, Type>? customScalars = null) :
this(CreateMatcherArray(matchBehaviour, new AnyOfTypes.AnyOf<string, WireMock.Models.StringPattern, GraphQL.Types.ISchema>(schema), customScalars))
{
@@ -94,8 +94,7 @@ public class RequestMessageGraphQLMatcher : IRequestMatcher
#if GRAPHQL
private static IMatcher[] CreateMatcherArray(
MatchBehaviour matchBehaviour,
AnyOfTypes.AnyOf<string, WireMock.Models.StringPattern,
GraphQL.Types.ISchema> schema,
AnyOfTypes.AnyOf<string, WireMock.Models.StringPattern, GraphQL.Types.ISchema> schema,
IDictionary<string, Type>? customScalars
)
{

View File

@@ -0,0 +1,86 @@
using System;
using System.Linq;
using Stef.Validation;
namespace WireMock.Matchers.Request;
/// <summary>
/// The request HTTP Version matcher.
/// </summary>
public class RequestMessageHttpVersionMatcher : IRequestMatcher
{
/// <summary>
/// The matcher.
/// </summary>
public IStringMatcher? Matcher { get; }
/// <summary>
/// The func.
/// </summary>
public Func<string, bool>? Func { get; }
/// <summary>
/// The <see cref="MatchBehaviour"/>
/// </summary>
public MatchBehaviour Behaviour { get; }
/// <summary>
/// The HTTP Version
/// </summary>
public string? HttpVersion { get; }
/// <summary>
/// Initializes a new instance of the <see cref="RequestMessageHttpVersionMatcher"/> class.
/// </summary>
/// <param name="matchBehaviour">The match behaviour.</param>
/// <param name="httpVersion">The HTTP Version.</param>
public RequestMessageHttpVersionMatcher(MatchBehaviour matchBehaviour, string httpVersion) :
this(matchBehaviour, new ExactMatcher(matchBehaviour, httpVersion))
{
HttpVersion = httpVersion;
Behaviour = matchBehaviour;
}
/// <summary>
/// Initializes a new instance of the <see cref="RequestMessageClientIPMatcher"/> class.
/// </summary>
/// <param name="matchBehaviour">The match behaviour.</param>
/// <param name="matcher">The matcher.</param>
public RequestMessageHttpVersionMatcher(MatchBehaviour matchBehaviour, IStringMatcher matcher)
{
Matcher = Guard.NotNull(matcher);
Behaviour = matchBehaviour;
HttpVersion = matcher.GetPatterns().FirstOrDefault();
}
/// <summary>
/// Initializes a new instance of the <see cref="RequestMessageClientIPMatcher"/> class.
/// </summary>
/// <param name="func">The function.</param>
public RequestMessageHttpVersionMatcher(Func<string, bool> func)
{
Func = Guard.NotNull(func);
}
/// <inheritdoc />
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
{
var (score, exception) = GetMatchResult(requestMessage).Expand();
return requestMatchResult.AddScore(GetType(), score, exception);
}
private MatchResult GetMatchResult(IRequestMessage requestMessage)
{
if (Matcher != null)
{
return Matcher.IsMatch(requestMessage.HttpVersion);
}
if (Func != null)
{
return MatchScores.ToScore(Func(requestMessage.HttpVersion));
}
return default;
}
}

View File

@@ -0,0 +1,43 @@
using System;
using WireMock.Models;
namespace WireMock.Matchers.Request;
/// <summary>
/// The request body Grpc ProtoBuf matcher.
/// </summary>
public class RequestMessageProtoBufMatcher : IRequestMatcher
{
/// <summary>
/// The ProtoBufMatcher.
/// </summary>
public IProtoBufMatcher? Matcher { get; }
/// <summary>
/// Initializes a new instance of the <see cref="RequestMessageProtoBufMatcher"/> class.
/// </summary>
/// <param name="matchBehaviour">The match behaviour. (default = "AcceptOnMatch")</param>
/// <param name="protoDefinition">The Func to define The proto definition as text.</param>
/// <param name="messageType">The full type of the protobuf (request/response) message object. Format is "{package-name}.{type-name}".</param>
/// <param name="matcher">The optional matcher to use to match the ProtoBuf as (json) object.</param>
public RequestMessageProtoBufMatcher(MatchBehaviour matchBehaviour, Func<IdOrText> protoDefinition, string messageType, IObjectMatcher? matcher = null)
{
#if PROTOBUF
Matcher = new ProtoBufMatcher(protoDefinition, messageType, matchBehaviour, matcher);
#else
throw new System.NotSupportedException("The ProtoBufMatcher can not be used for .NETStandard1.3 or .NET Framework 4.6.1 or lower.");
#endif
}
/// <inheritdoc />
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
{
var (score, exception) = GetMatchResult(requestMessage).Expand();
return requestMatchResult.AddScore(GetType(), score, exception);
}
private MatchResult GetMatchResult(IRequestMessage requestMessage)
{
return Matcher?.IsMatchAsync(requestMessage.BodyAsBytes).GetAwaiter().GetResult() ?? default;
}
}

View File

@@ -1,7 +1,10 @@
using System;
using System.Collections.Generic;
using System.Text;
using WireMock.Models;
using WireMock.Types;
// ReSharper disable once CheckNamespace
namespace WireMock.Util;
/// <summary>
@@ -9,7 +12,7 @@ namespace WireMock.Util;
/// </summary>
public class BodyData : IBodyData
{
/// <inheritdoc cref="IBodyData.Encoding" />
/// <inheritdoc />
public Encoding? Encoding { get; set; }
/// <inheritdoc />
@@ -18,30 +21,38 @@ public class BodyData : IBodyData
/// <inheritdoc />
public IDictionary<string, string>? BodyAsFormUrlEncoded { get; set; }
/// <inheritdoc cref="IBodyData.BodyAsJson" />
/// <inheritdoc />
public object? BodyAsJson { get; set; }
/// <inheritdoc cref="IBodyData.BodyAsBytes" />
/// <inheritdoc />
public byte[]? BodyAsBytes { get; set; }
/// <inheritdoc cref="IBodyData.BodyAsJsonIndented" />
/// <inheritdoc />
public bool? BodyAsJsonIndented { get; set; }
/// <inheritdoc cref="IBodyData.BodyAsFile" />
/// <inheritdoc />
public string? BodyAsFile { get; set; }
/// <inheritdoc cref="IBodyData.BodyAsFileIsCached" />
/// <inheritdoc />
public bool? BodyAsFileIsCached { get; set; }
/// <inheritdoc cref="IBodyData.DetectedBodyType" />
/// <inheritdoc />
public BodyType? DetectedBodyType { get; set; }
/// <inheritdoc cref="IBodyData.DetectedBodyTypeFromContentType" />
/// <inheritdoc />
public BodyType? DetectedBodyTypeFromContentType { get; set; }
/// <inheritdoc cref="IRequestMessage.DetectedCompression" />
/// <inheritdoc />
public string? DetectedCompression { get; set; }
/// <inheritdoc />
public string? IsFuncUsed { get; set; }
#region ProtoBuf
/// <inheritdoc />
public Func<IdOrText>? ProtoDefinition { get; set; }
/// <inheritdoc />
public string? ProtoBufMessageType { get; set; }
#endregion
}

View File

@@ -0,0 +1,61 @@
using System;
using System.Collections.Generic;
using AnyOfTypes;
using Newtonsoft.Json;
namespace WireMock.Models;
/// <summary>
/// GraphQLSchemaDetails
/// </summary>
public class GraphQLSchemaDetails
{
/// <summary>
/// The GraphQL schema as a string.
/// </summary>
public string? SchemaAsString { get; set; }
/// <summary>
/// The GraphQL schema as a StringPattern.
/// </summary>
public StringPattern? SchemaAsStringPattern { get; set; }
#if GRAPHQL
/// <summary>
/// The GraphQL schema as a <seealso cref="GraphQL.Types.ISchema"/>.
/// </summary>
public GraphQL.Types.ISchema? SchemaAsISchema { get; set; }
/// <summary>
/// The GraphQL Schema.
/// </summary>
[JsonIgnore]
public AnyOf<string, StringPattern, GraphQL.Types.ISchema>? Schema
{
get
{
if (SchemaAsString != null)
{
return SchemaAsString;
}
if (SchemaAsStringPattern != null)
{
return SchemaAsStringPattern;
}
if (SchemaAsISchema != null)
{
return new AnyOf<string, StringPattern, GraphQL.Types.ISchema>(SchemaAsISchema);
}
return null;
}
}
#endif
/// <summary>
/// The custom Scalars to define for this schema.
/// </summary>
public IDictionary<string, Type>? CustomScalars { get; set; }
}

View File

@@ -46,6 +46,18 @@ namespace WireMock.Owin
options.ClientCertificateValidation = (_, _, _) => true;
}
});
if (urlDetail.IsHttp2)
{
listenOptions.Protocols = HttpProtocols.Http2;
}
});
}
else if (urlDetail.IsHttp2)
{
kestrelOptions.ListenAnyIP(urlDetail.Port, listenOptions =>
{
listenOptions.Protocols = HttpProtocols.Http2;
});
}
else

View File

@@ -14,171 +14,174 @@ using WireMock.Owin.Mappers;
using WireMock.Services;
using WireMock.Util;
namespace WireMock.Owin
namespace WireMock.Owin;
internal partial class AspNetCoreSelfHost : IOwinSelfHost
{
internal partial class AspNetCoreSelfHost : IOwinSelfHost
private const string CorsPolicyName = "WireMock.Net - Policy";
private readonly CancellationTokenSource _cts = new();
private readonly IWireMockMiddlewareOptions _wireMockMiddlewareOptions;
private readonly IWireMockLogger _logger;
private readonly HostUrlOptions _urlOptions;
private Exception _runningException;
private IWebHost _host;
public bool IsStarted { get; private set; }
public List<string> Urls { get; } = new();
public List<int> Ports { get; } = new();
public Exception RunningException => _runningException;
public AspNetCoreSelfHost(IWireMockMiddlewareOptions wireMockMiddlewareOptions, HostUrlOptions urlOptions)
{
private const string CorsPolicyName = "WireMock.Net - Policy";
Guard.NotNull(wireMockMiddlewareOptions);
Guard.NotNull(urlOptions);
private readonly CancellationTokenSource _cts = new CancellationTokenSource();
private readonly IWireMockMiddlewareOptions _wireMockMiddlewareOptions;
private readonly IWireMockLogger _logger;
private readonly HostUrlOptions _urlOptions;
_logger = wireMockMiddlewareOptions.Logger ?? new WireMockConsoleLogger();
private Exception _runningException;
private IWebHost _host;
_wireMockMiddlewareOptions = wireMockMiddlewareOptions;
_urlOptions = urlOptions;
}
public bool IsStarted { get; private set; }
public Task StartAsync()
{
var builder = new WebHostBuilder();
public List<string> Urls { get; } = new();
public List<int> Ports { get; } = new();
public Exception RunningException => _runningException;
public AspNetCoreSelfHost(IWireMockMiddlewareOptions wireMockMiddlewareOptions, HostUrlOptions urlOptions)
// Workaround for https://github.com/WireMock-Net/WireMock.Net/issues/292
// On some platforms, AppContext.BaseDirectory is null, which causes WebHostBuilder to fail if ContentRoot is not
// specified (even though we don't actually use that base path mechanism, since we have our own way of configuring
// a filesystem handler).
if (string.IsNullOrEmpty(AppContext.BaseDirectory))
{
Guard.NotNull(wireMockMiddlewareOptions);
Guard.NotNull(urlOptions);
_logger = wireMockMiddlewareOptions.Logger ?? new WireMockConsoleLogger();
_wireMockMiddlewareOptions = wireMockMiddlewareOptions;
_urlOptions = urlOptions;
builder.UseContentRoot(Directory.GetCurrentDirectory());
}
public Task StartAsync()
{
var builder = new WebHostBuilder();
// Workaround for https://github.com/WireMock-Net/WireMock.Net/issues/292
// On some platforms, AppContext.BaseDirectory is null, which causes WebHostBuilder to fail if ContentRoot is not
// specified (even though we don't actually use that base path mechanism, since we have our own way of configuring
// a filesystem handler).
if (string.IsNullOrEmpty(AppContext.BaseDirectory))
_host = builder
.UseSetting("suppressStatusMessages", "True") // https://andrewlock.net/suppressing-the-startup-and-shutdown-messages-in-asp-net-core/
.ConfigureAppConfigurationUsingEnvironmentVariables()
.ConfigureServices(services =>
{
builder.UseContentRoot(Directory.GetCurrentDirectory());
}
_host = builder
.UseSetting("suppressStatusMessages", "True") // https://andrewlock.net/suppressing-the-startup-and-shutdown-messages-in-asp-net-core/
.ConfigureAppConfigurationUsingEnvironmentVariables()
.ConfigureServices(services =>
{
services.AddSingleton(_wireMockMiddlewareOptions);
services.AddSingleton<IMappingMatcher, MappingMatcher>();
services.AddSingleton<IRandomizerDoubleBetween0And1, RandomizerDoubleBetween0And1>();
services.AddSingleton<IOwinRequestMapper, OwinRequestMapper>();
services.AddSingleton<IOwinResponseMapper, OwinResponseMapper>();
services.AddSingleton<IGuidUtils, GuidUtils>();
services.AddSingleton(_wireMockMiddlewareOptions);
services.AddSingleton<IMappingMatcher, MappingMatcher>();
services.AddSingleton<IRandomizerDoubleBetween0And1, RandomizerDoubleBetween0And1>();
services.AddSingleton<IOwinRequestMapper, OwinRequestMapper>();
services.AddSingleton<IOwinResponseMapper, OwinResponseMapper>();
services.AddSingleton<IGuidUtils, GuidUtils>();
#if NETCOREAPP3_1 || NET5_0_OR_GREATER
AddCors(services);
AddCors(services);
#endif
_wireMockMiddlewareOptions.AdditionalServiceRegistration?.Invoke(services);
})
.Configure(appBuilder =>
{
appBuilder.UseMiddleware<GlobalExceptionMiddleware>();
_wireMockMiddlewareOptions.AdditionalServiceRegistration?.Invoke(services);
})
.Configure(appBuilder =>
{
appBuilder.UseMiddleware<GlobalExceptionMiddleware>();
#if NETCOREAPP3_1 || NET5_0_OR_GREATER
UseCors(appBuilder);
UseCors(appBuilder);
#endif
_wireMockMiddlewareOptions.PreWireMockMiddlewareInit?.Invoke(appBuilder);
_wireMockMiddlewareOptions.PreWireMockMiddlewareInit?.Invoke(appBuilder);
appBuilder.UseMiddleware<WireMockMiddleware>();
appBuilder.UseMiddleware<WireMockMiddleware>();
_wireMockMiddlewareOptions.PostWireMockMiddlewareInit?.Invoke(appBuilder);
})
.UseKestrel(options =>
{
SetKestrelOptionsLimits(options);
_wireMockMiddlewareOptions.PostWireMockMiddlewareInit?.Invoke(appBuilder);
})
.UseKestrel(options =>
{
SetKestrelOptionsLimits(options);
SetHttpsAndUrls(options, _wireMockMiddlewareOptions, _urlOptions.GetDetails());
})
.ConfigureKestrelServerOptions()
SetHttpsAndUrls(options, _wireMockMiddlewareOptions, _urlOptions.GetDetails());
})
.ConfigureKestrelServerOptions()
#if NETSTANDARD1_3
.UseUrls(_urlOptions.GetDetails().Select(u => u.Url).ToArray())
.UseUrls(_urlOptions.GetDetails().Select(u => u.Url).ToArray())
#endif
.Build();
.Build();
return RunHost(_cts.Token);
}
return RunHost(_cts.Token);
}
private Task RunHost(CancellationToken token)
{
try
{
#if NETCOREAPP3_1 || NET5_0_OR_GREATER
var appLifetime = _host.Services.GetRequiredService<Microsoft.Extensions.Hosting.IHostApplicationLifetime>();
#else
var appLifetime = _host.Services.GetRequiredService<IApplicationLifetime>();
#endif
appLifetime.ApplicationStarted.Register(() =>
{
var addresses = _host.ServerFeatures
.Get<Microsoft.AspNetCore.Hosting.Server.Features.IServerAddressesFeature>()
.Get<Microsoft.AspNetCore.Hosting.Server.Features.IServerAddressesFeature>()!
.Addresses;
foreach (string address in addresses)
foreach (var address in addresses)
{
Urls.Add(address.Replace("0.0.0.0", "localhost").Replace("[::]", "localhost"));
PortUtils.TryExtract(address, out _, out _, out _, out int port);
PortUtils.TryExtract(address, out _, out _, out _, out _, out var port);
Ports.Add(port);
}
IsStarted = true;
});
IsStarted = true;
});
#if NETSTANDARD1_3
_logger.Info("Server using netstandard1.3");
_logger.Info("Server using netstandard1.3");
#elif NETSTANDARD2_0
_logger.Info("Server using netstandard2.0");
_logger.Info("Server using netstandard2.0");
#elif NETSTANDARD2_1
_logger.Info("Server using netstandard2.1");
_logger.Info("Server using netstandard2.1");
#elif NETCOREAPP3_1
_logger.Info("Server using .NET Core App 3.1");
_logger.Info("Server using .NET Core App 3.1");
#elif NET5_0
_logger.Info("Server using .NET 5.0");
_logger.Info("Server using .NET 5.0");
#elif NET6_0
_logger.Info("Server using .NET 6.0");
_logger.Info("Server using .NET 6.0");
#elif NET7_0
_logger.Info("Server using .NET 7.0");
_logger.Info("Server using .NET 7.0");
#elif NET8_0
_logger.Info("Server using .NET 8.0");
_logger.Info("Server using .NET 8.0");
#elif NET46
_logger.Info("Server using .NET Framework 4.6.1 or higher");
_logger.Info("Server using .NET Framework 4.6.1 or higher");
#endif
#if NETSTANDARD1_3
return Task.Run(() =>
{
_host.Run(token);
});
#else
return _host.RunAsync(token);
#endif
}
catch (Exception e)
return Task.Run(() =>
{
_runningException = e;
_logger.Error(e.ToString());
IsStarted = false;
return Task.CompletedTask;
}
_host.Run(token);
});
#else
return _host.RunAsync(token);
#endif
}
public Task StopAsync()
catch (Exception e)
{
_cts.Cancel();
_runningException = e;
_logger.Error(e.ToString());
IsStarted = false;
#if NETSTANDARD1_3
return Task.FromResult(true);
#else
return _host.StopAsync();
#endif
return Task.CompletedTask;
}
}
public Task StopAsync()
{
_cts.Cancel();
IsStarted = false;
#if NETSTANDARD1_3
return Task.CompletedTask;
#else
return _host.StopAsync();
#endif
}
}
#endif

View File

@@ -7,6 +7,8 @@ internal struct HostUrlDetails
{
public bool IsHttps { get; set; }
public bool IsHttp2 { get; set; }
public string Url { get; set; }
public string Scheme { get; set; }

View File

@@ -14,6 +14,8 @@ internal class HostUrlOptions
public HostingScheme HostingScheme { get; set; }
public bool? UseHttp2 { get; set; }
public IReadOnlyList<HostUrlDetails> GetDetails()
{
var list = new List<HostUrlDetails>();
@@ -23,25 +25,25 @@ internal class HostUrlOptions
{
var port = Port > 0 ? Port.Value : FindFreeTcpPort();
var scheme = HostingScheme == HostingScheme.Https ? "https" : "http";
list.Add(new HostUrlDetails { IsHttps = HostingScheme == HostingScheme.Https, Url = $"{scheme}://{Localhost}:{port}", Scheme = scheme, Host = Localhost, Port = port });
list.Add(new HostUrlDetails { IsHttps = HostingScheme == HostingScheme.Https, IsHttp2 = UseHttp2 == true, Url = $"{scheme}://{Localhost}:{port}", Scheme = scheme, Host = Localhost, Port = port });
}
if (HostingScheme == HostingScheme.HttpAndHttps)
{
var httpPort = Port > 0 ? Port.Value : FindFreeTcpPort();
list.Add(new HostUrlDetails { IsHttps = false, Url = $"http://{Localhost}:{httpPort}", Scheme = "http", Host = Localhost, Port = httpPort });
list.Add(new HostUrlDetails { IsHttps = false, IsHttp2 = UseHttp2 == true, Url = $"http://{Localhost}:{httpPort}", Scheme = "http", Host = Localhost, Port = httpPort });
var httpsPort = FindFreeTcpPort(); // In this scenario, always get a free port for https.
list.Add(new HostUrlDetails { IsHttps = true, Url = $"https://{Localhost}:{httpsPort}", Scheme = "https", Host = Localhost, Port = httpsPort });
list.Add(new HostUrlDetails { IsHttps = true, IsHttp2 = UseHttp2 == true, Url = $"https://{Localhost}:{httpsPort}", Scheme = "https", Host = Localhost, Port = httpsPort });
}
}
else
{
foreach (string url in Urls)
foreach (var url in Urls)
{
if (PortUtils.TryExtract(url, out var isHttps, out var protocol, out var host, out var port))
if (PortUtils.TryExtract(url, out var isHttps, out var isGrpc, out var protocol, out var host, out var port))
{
list.Add(new HostUrlDetails { IsHttps = isHttps, Url = url, Scheme = protocol, Host = host, Port = port });
list.Add(new HostUrlDetails { IsHttps = isHttps, IsHttp2 = isGrpc, Url = url, Scheme = protocol, Host = host, Port = port });
}
}
}

View File

@@ -5,18 +5,17 @@ using IResponse = Microsoft.Owin.IOwinResponse;
using IResponse = Microsoft.AspNetCore.Http.HttpResponse;
#endif
namespace WireMock.Owin.Mappers
namespace WireMock.Owin.Mappers;
/// <summary>
/// IOwinResponseMapper
/// </summary>
internal interface IOwinResponseMapper
{
/// <summary>
/// IOwinResponseMapper
/// Map ResponseMessage to IResponse.
/// </summary>
internal interface IOwinResponseMapper
{
/// <summary>
/// Map ResponseMessage to IResponse.
/// </summary>
/// <param name="responseMessage">The ResponseMessage</param>
/// <param name="response">The OwinResponse/HttpResponse</param>
Task MapAsync(IResponseMessage? responseMessage, IResponse response);
}
}
/// <param name="responseMessage">The ResponseMessage</param>
/// <param name="response">The OwinResponse/HttpResponse</param>
Task MapAsync(IResponseMessage? responseMessage, IResponse response);
}

View File

@@ -8,103 +8,102 @@ using WireMock.Util;
#if !USE_ASPNETCORE
using IRequest = Microsoft.Owin.IOwinRequest;
#else
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Extensions;
using IRequest = Microsoft.AspNetCore.Http.HttpRequest;
#endif
namespace WireMock.Owin.Mappers
namespace WireMock.Owin.Mappers;
/// <summary>
/// OwinRequestMapper
/// </summary>
internal class OwinRequestMapper : IOwinRequestMapper
{
/// <summary>
/// OwinRequestMapper
/// </summary>
internal class OwinRequestMapper : IOwinRequestMapper
/// <inheritdoc />
public async Task<RequestMessage> MapAsync(IRequest request, IWireMockMiddlewareOptions options)
{
/// <inheritdoc cref="IOwinRequestMapper.MapAsync"/>
public async Task<RequestMessage> MapAsync(IRequest request, IWireMockMiddlewareOptions options)
var (urlDetails, clientIP) = ParseRequest(request);
var method = request.Method;
var httpVersion = HttpVersionParser.Parse(request.Protocol);
var headers = new Dictionary<string, string[]>();
IEnumerable<string>? contentEncodingHeader = null;
foreach (var header in request.Headers)
{
var (urlDetails, clientIP) = ParseRequest(request);
headers.Add(header.Key, header.Value!);
string method = request.Method;
var headers = new Dictionary<string, string[]>();
IEnumerable<string>? contentEncodingHeader = null;
foreach (var header in request.Headers)
if (string.Equals(header.Key, HttpKnownHeaderNames.ContentEncoding, StringComparison.OrdinalIgnoreCase))
{
headers.Add(header.Key, header.Value!);
if (string.Equals(header.Key, HttpKnownHeaderNames.ContentEncoding, StringComparison.OrdinalIgnoreCase))
{
contentEncodingHeader = header.Value;
}
contentEncodingHeader = header.Value;
}
}
var cookies = new Dictionary<string, string>();
if (request.Cookies.Any())
var cookies = new Dictionary<string, string>();
if (request.Cookies.Any())
{
foreach (var cookie in request.Cookies)
{
cookies = new Dictionary<string, string>();
foreach (var cookie in request.Cookies)
{
cookies.Add(cookie.Key, cookie.Value);
}
cookies.Add(cookie.Key, cookie.Value);
}
}
IBodyData? body = null;
if (request.Body != null && BodyParser.ShouldParseBody(method, options.AllowBodyForAllHttpMethods == true))
IBodyData? body = null;
if (request.Body != null && BodyParser.ShouldParseBody(method, options.AllowBodyForAllHttpMethods == true))
{
var bodyParserSettings = new BodyParserSettings
{
var bodyParserSettings = new BodyParserSettings
{
Stream = request.Body,
ContentType = request.ContentType,
DeserializeJson = !options.DisableJsonBodyParsing.GetValueOrDefault(false),
ContentEncoding = contentEncodingHeader?.FirstOrDefault(),
DecompressGZipAndDeflate = !options.DisableRequestBodyDecompressing.GetValueOrDefault(false)
};
body = await BodyParser.ParseAsync(bodyParserSettings).ConfigureAwait(false);
}
return new RequestMessage(
options,
urlDetails,
method,
clientIP,
body,
headers,
cookies
#if USE_ASPNETCORE
, await request.HttpContext.Connection.GetClientCertificateAsync()
#endif
)
{
DateTime = DateTime.UtcNow
Stream = request.Body,
ContentType = request.ContentType,
DeserializeJson = !options.DisableJsonBodyParsing.GetValueOrDefault(false),
ContentEncoding = contentEncodingHeader?.FirstOrDefault(),
DecompressGZipAndDeflate = !options.DisableRequestBodyDecompressing.GetValueOrDefault(false)
};
body = await BodyParser.ParseAsync(bodyParserSettings).ConfigureAwait(false);
}
private static (UrlDetails UrlDetails, string ClientIP) ParseRequest(IRequest request)
{
#if !USE_ASPNETCORE
var urlDetails = UrlUtils.Parse(request.Uri, request.PathBase);
var clientIP = request.RemoteIpAddress;
#else
var urlDetails = UrlUtils.Parse(new Uri(request.GetEncodedUrl()), request.PathBase);
var connection = request.HttpContext.Connection;
string clientIP;
if (connection.RemoteIpAddress is null)
{
clientIP = string.Empty;
}
else if (connection.RemoteIpAddress.IsIPv4MappedToIPv6)
{
clientIP = connection.RemoteIpAddress.MapToIPv4().ToString();
}
else
{
clientIP = connection.RemoteIpAddress.ToString();
}
return new RequestMessage(
options,
urlDetails,
method,
clientIP,
body,
headers,
cookies,
httpVersion
#if USE_ASPNETCORE
, await request.HttpContext.Connection.GetClientCertificateAsync()
#endif
return (urlDetails, clientIP);
)
{
DateTime = DateTime.UtcNow
};
}
private static (UrlDetails UrlDetails, string ClientIP) ParseRequest(IRequest request)
{
#if !USE_ASPNETCORE
var urlDetails = UrlUtils.Parse(request.Uri, request.PathBase);
var clientIP = request.RemoteIpAddress;
#else
var urlDetails = UrlUtils.Parse(new Uri(request.GetEncodedUrl()), request.PathBase);
var connection = request.HttpContext.Connection;
string clientIP;
if (connection.RemoteIpAddress is null)
{
clientIP = string.Empty;
}
else if (connection.RemoteIpAddress.IsIPv4MappedToIPv6)
{
clientIP = connection.RemoteIpAddress.MapToIPv4().ToString();
}
else
{
clientIP = connection.RemoteIpAddress.ToString();
}
#endif
return (urlDetails, clientIP);
}
}

View File

@@ -13,6 +13,8 @@ using WireMock.Http;
using WireMock.ResponseBuilders;
using WireMock.Types;
using Stef.Validation;
using WireMock.Util;
#if !USE_ASPNETCORE
using IResponse = Microsoft.Owin.IOwinResponse;
#else
@@ -62,11 +64,11 @@ namespace WireMock.Owin.Mappers
switch (responseMessage.FaultType)
{
case FaultType.EMPTY_RESPONSE:
bytes = IsFault(responseMessage) ? EmptyArray<byte>.Value : GetNormalBody(responseMessage);
bytes = IsFault(responseMessage) ? EmptyArray<byte>.Value : await GetNormalBodyAsync(responseMessage).ConfigureAwait(false);
break;
case FaultType.MALFORMED_RESPONSE_CHUNK:
bytes = GetNormalBody(responseMessage) ?? EmptyArray<byte>.Value;
bytes = await GetNormalBodyAsync(responseMessage).ConfigureAwait(false) ?? EmptyArray<byte>.Value;
if (IsFault(responseMessage))
{
bytes = bytes.Take(bytes.Length / 2).Union(_randomizerBytes.Generate()).ToArray();
@@ -74,18 +76,18 @@ namespace WireMock.Owin.Mappers
break;
default:
bytes = GetNormalBody(responseMessage);
bytes = await GetNormalBodyAsync(responseMessage).ConfigureAwait(false);
break;
}
var statusCodeType = responseMessage.StatusCode?.GetType();
switch (statusCodeType)
{
case { } typeAsIntOrEnum when typeAsIntOrEnum == typeof(int) || typeAsIntOrEnum == typeof(int?) || typeAsIntOrEnum.GetTypeInfo().IsEnum:
case { } when statusCodeType == typeof(int) || statusCodeType == typeof(int?) || statusCodeType.GetTypeInfo().IsEnum:
response.StatusCode = MapStatusCode((int)responseMessage.StatusCode!);
break;
case { } typeAsString when typeAsString == typeof(string):
case { } when statusCodeType == typeof(string):
// Note: this case will also match on null
int.TryParse(responseMessage.StatusCode as string, out var result);
response.StatusCode = MapStatusCode(result);
@@ -108,6 +110,8 @@ namespace WireMock.Owin.Mappers
_options.Logger.Warn("Error writing response body. Exception : {0}", ex);
}
}
SetResponseTrailingHeaders(responseMessage, response);
}
private int MapStatusCode(int code)
@@ -125,7 +129,7 @@ namespace WireMock.Owin.Mappers
return responseMessage.FaultPercentage == null || _randomizerDouble.Generate() <= responseMessage.FaultPercentage;
}
private byte[]? GetNormalBody(IResponseMessage responseMessage)
private async Task<byte[]?> GetNormalBodyAsync(IResponseMessage responseMessage)
{
switch (responseMessage.BodyData?.DetectedBodyType)
{
@@ -138,6 +142,12 @@ namespace WireMock.Owin.Mappers
var jsonBody = JsonConvert.SerializeObject(responseMessage.BodyData.BodyAsJson, new JsonSerializerSettings { Formatting = formatting, NullValueHandling = NullValueHandling.Ignore });
return (responseMessage.BodyData.Encoding ?? _utf8NoBom).GetBytes(jsonBody);
#if PROTOBUF
case BodyType.ProtoBuf:
var protoDefinition = responseMessage.BodyData.ProtoDefinition?.Invoke().Text;
return await ProtoBufUtils.GetProtoBufMessageWithHeaderAsync(protoDefinition, responseMessage.BodyData.ProtoBufMessageType, responseMessage.BodyData.BodyAsJson).ConfigureAwait(false);
#endif
case BodyType.Bytes:
return responseMessage.BodyData.BodyAsBytes;
@@ -157,16 +167,17 @@ namespace WireMock.Owin.Mappers
new[]
{
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.ContainsKey(headerName))
if (ResponseHeadersToFix.TryGetValue(headerName, out var action))
{
ResponseHeadersToFix[headerName]?.Invoke(response, value);
action?.Invoke(response, value);
}
else
{
@@ -179,6 +190,34 @@ namespace WireMock.Owin.Mappers
}
}
private static void SetResponseTrailingHeaders(IResponseMessage responseMessage, IResponse response)
{
if (responseMessage.TrailingHeaders == null)
{
return;
}
#if TRAILINGHEADERS
foreach (var item in responseMessage.TrailingHeaders)
{
var headerName = item.Key;
var value = item.Value;
if (ResponseHeadersToFix.TryGetValue(headerName, out var action))
{
action?.Invoke(response, 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(IResponse response, string headerName, string[] values)
{
#if !USE_ASPNETCORE

View File

@@ -1,56 +0,0 @@
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Linq;
using System.Reflection;
namespace WireMock.Plugin;
internal static class PluginLoader
{
private static readonly ConcurrentDictionary<Type, Type> Assemblies = new();
public static T Load<T>(params object[] args) where T : class
{
var foundType = Assemblies.GetOrAdd(typeof(T), (type) =>
{
var files = Directory.GetFiles(Directory.GetCurrentDirectory(), "*.dll");
Type? pluginType = null;
foreach (var file in files)
{
try
{
var assembly = Assembly.Load(new AssemblyName
{
Name = Path.GetFileNameWithoutExtension(file)
});
pluginType = GetImplementationTypeByInterface<T>(assembly);
if (pluginType != null)
{
break;
}
}
catch
{
// no-op: just try next .dll
}
}
if (pluginType != null)
{
return pluginType;
}
throw new DllNotFoundException($"No dll found which implements type '{type}'");
});
return (T)Activator.CreateInstance(foundType, args);
}
private static Type? GetImplementationTypeByInterface<T>(Assembly assembly)
{
return assembly.GetTypes().FirstOrDefault(t => typeof(T).IsAssignableFrom(t) && !t.GetTypeInfo().IsInterface);
}
}

View File

@@ -9,7 +9,7 @@ namespace WireMock.RequestBuilders;
/// <summary>
/// The BodyRequestBuilder interface.
/// </summary>
public interface IBodyRequestBuilder : IGraphQLRequestBuilder
public interface IBodyRequestBuilder : IProtoBufRequestBuilder
{
/// <summary>
/// WithBody: IMatcher

View File

@@ -26,6 +26,23 @@ public interface IGraphQLRequestBuilder : IMultiPartRequestBuilder
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithGraphQLSchema(string schema, IDictionary<string, Type>? customScalars, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch);
/// <summary>
/// WithBodyAsGraphQL: The GraphQL schema as a string.
/// </summary>
/// <param name="schema">The GraphQL schema.</param>
/// <param name="matchBehaviour">The match behaviour. (Default is <c>MatchBehaviour.AcceptOnMatch</c>).</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithBodyAsGraphQL(string schema, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch);
/// <summary>
/// WithBodyAsGraphQL: The GraphQL schema as a string.
/// </summary>
/// <param name="schema">The GraphQL schema.</param>
/// <param name="customScalars">A dictionary defining the custom scalars used in this schema. (optional)</param>
/// <param name="matchBehaviour">The match behaviour. (Default is <c>MatchBehaviour.AcceptOnMatch</c>).</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithBodyAsGraphQL(string schema, IDictionary<string, Type>? customScalars, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch);
#if GRAPHQL
/// <summary>
/// WithGraphQLSchema: The GraphQL schema as a ISchema.
@@ -43,5 +60,22 @@ public interface IGraphQLRequestBuilder : IMultiPartRequestBuilder
/// <param name="matchBehaviour">The match behaviour. (Default is <c>MatchBehaviour.AcceptOnMatch</c>).</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithGraphQLSchema(GraphQL.Types.ISchema schema, IDictionary<string, Type>? customScalars, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch);
/// <summary>
/// WithBodyAsGraphQL: The GraphQL schema as a ISchema.
/// </summary>
/// <param name="schema">The GraphQL schema.</param>
/// <param name="matchBehaviour">The match behaviour. (Default is <c>MatchBehaviour.AcceptOnMatch</c>).</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithBodyAsGraphQL(GraphQL.Types.ISchema schema, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch);
/// <summary>
/// WithBodyAsGraphQL: The GraphQL schema as a ISchema.
/// </summary>
/// <param name="schema">The GraphQL schema.</param>
/// <param name="customScalars">A dictionary defining the custom scalars used in this schema. (optional)</param>
/// <param name="matchBehaviour">The match behaviour. (Default is <c>MatchBehaviour.AcceptOnMatch</c>).</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithBodyAsGraphQL(GraphQL.Types.ISchema schema, IDictionary<string, Type>? customScalars, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch);
#endif
}

View File

@@ -0,0 +1,18 @@
using WireMock.Matchers;
using WireMock.Matchers.Request;
namespace WireMock.RequestBuilders;
/// <summary>
/// The HttpVersionBuilder interface.
/// </summary>
public interface IHttpVersionBuilder : IRequestMatcher
{
/// <summary>
/// WithHttpVersion
/// </summary>
/// <param name="version">The HTTP Version to match.</param>
/// <param name="matchBehaviour">The match behaviour. (default = "AcceptOnMatch")</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithHttpVersion(string version, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch);
}

View File

@@ -6,7 +6,7 @@ namespace WireMock.RequestBuilders;
/// <summary>
/// The MultiPartRequestBuilder interface.
/// </summary>
public interface IMultiPartRequestBuilder : IRequestMatcher
public interface IMultiPartRequestBuilder : IHttpVersionBuilder
{
/// <summary>
/// WithMultiPart: IMatcher

View File

@@ -0,0 +1,45 @@
using WireMock.Matchers;
namespace WireMock.RequestBuilders;
/// <summary>
/// The ProtoBufRequestBuilder interface.
/// </summary>
public interface IProtoBufRequestBuilder : IGraphQLRequestBuilder
{
/// <summary>
/// WithGrpcProto
/// </summary>
/// <param name="protoDefinition">The proto definition as text.</param>
/// <param name="messageType">The full type of the protobuf (request/response) message object. Format is "{package-name}.{type-name}".</param>
/// <param name="matchBehaviour">The match behaviour. (default = "AcceptOnMatch")</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithBodyAsProtoBuf(string protoDefinition, string messageType, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch);
/// <summary>
/// WithGrpcProto
/// </summary>
/// <param name="protoDefinition">The proto definition as text.</param>
/// <param name="messageType">The full type of the protobuf (request/response) message object. Format is "{package-name}.{type-name}".</param>
/// <param name="matcher">The matcher to use to match the ProtoBuf as (json) object.</param>
/// <param name="matchBehaviour">The match behaviour. (default = "AcceptOnMatch")</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithBodyAsProtoBuf(string protoDefinition, string messageType, IObjectMatcher matcher, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch);
/// <summary>
/// WithGrpcProto
/// </summary>
/// <param name="messageType">The full type of the protobuf (request/response) message object. Format is "{package-name}.{type-name}".</param>
/// <param name="matchBehaviour">The match behaviour. (default = "AcceptOnMatch")</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithBodyAsProtoBuf(string messageType, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch);
/// <summary>
/// WithGrpcProto
/// </summary>
/// <param name="messageType">The full type of the protobuf (request/response) message object. Format is "{package-name}.{type-name}".</param>
/// <param name="matcher">The matcher to use to match the ProtoBuf as (json) object.</param>
/// <param name="matchBehaviour">The match behaviour. (default = "AcceptOnMatch")</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithBodyAsProtoBuf(string messageType, IObjectMatcher matcher, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch);
}

View File

@@ -23,17 +23,17 @@ public partial class Request
}
/// <inheritdoc />
public IRequestBuilder WithClientIP(params string[] paths)
public IRequestBuilder WithClientIP(params string[] clientIPs)
{
return WithClientIP(MatchOperator.Or, paths);
return WithClientIP(MatchOperator.Or, clientIPs);
}
/// <inheritdoc />
public IRequestBuilder WithClientIP(MatchOperator matchOperator, params string[] paths)
public IRequestBuilder WithClientIP(MatchOperator matchOperator, params string[] clientIPs)
{
Guard.NotNullOrEmpty(paths);
Guard.NotNullOrEmpty(clientIPs);
_requestMatchers.Add(new RequestMessageClientIPMatcher(MatchBehaviour.AcceptOnMatch, matchOperator, paths));
_requestMatchers.Add(new RequestMessageClientIPMatcher(MatchBehaviour.AcceptOnMatch, matchOperator, clientIPs));
return this;
}

View File

@@ -0,0 +1,31 @@
using WireMock.Matchers;
using WireMock.Matchers.Request;
namespace WireMock.RequestBuilders;
public partial class Request
{
/// <inheritdoc />
public IRequestBuilder WithBodyAsProtoBuf(string protoDefinition, string messageType, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{
return Add(new RequestMessageProtoBufMatcher(matchBehaviour, () => new (null, protoDefinition), messageType));
}
/// <inheritdoc />
public IRequestBuilder WithBodyAsProtoBuf(string protoDefinition, string messageType, IObjectMatcher matcher, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{
return Add(new RequestMessageProtoBufMatcher(matchBehaviour, () => new(null, protoDefinition), messageType, matcher));
}
/// <inheritdoc />
public IRequestBuilder WithBodyAsProtoBuf(string messageType, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{
return Add(new RequestMessageProtoBufMatcher(matchBehaviour, () => Mapping.ProtoDefinition!.Value, messageType));
}
/// <inheritdoc />
public IRequestBuilder WithBodyAsProtoBuf(string messageType, IObjectMatcher matcher, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{
return Add(new RequestMessageProtoBufMatcher(matchBehaviour, () => Mapping.ProtoDefinition!.Value, messageType, matcher));
}
}

View File

@@ -1,39 +0,0 @@
using System.Collections.Generic;
using System;
using WireMock.Matchers;
using WireMock.Matchers.Request;
namespace WireMock.RequestBuilders;
public partial class Request
{
/// <inheritdoc />
public IRequestBuilder WithGraphQLSchema(string schema, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{
_requestMatchers.Add(new RequestMessageGraphQLMatcher(matchBehaviour, schema));
return this;
}
/// <inheritdoc />
public IRequestBuilder WithGraphQLSchema(string schema, IDictionary<string, Type>? customScalars, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{
_requestMatchers.Add(new RequestMessageGraphQLMatcher(matchBehaviour, schema, customScalars));
return this;
}
#if GRAPHQL
/// <inheritdoc />
public IRequestBuilder WithGraphQLSchema(GraphQL.Types.ISchema schema, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{
_requestMatchers.Add(new RequestMessageGraphQLMatcher(matchBehaviour, schema));
return this;
}
/// <inheritdoc />
public IRequestBuilder WithGraphQLSchema(GraphQL.Types.ISchema schema, IDictionary<string, Type>? customScalars, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{
_requestMatchers.Add(new RequestMessageGraphQLMatcher(matchBehaviour, schema, customScalars));
return this;
}
#endif
}

View File

@@ -0,0 +1,59 @@
using System.Collections.Generic;
using System;
using WireMock.Matchers;
using WireMock.Matchers.Request;
namespace WireMock.RequestBuilders;
public partial class Request
{
/// <inheritdoc />
public IRequestBuilder WithGraphQLSchema(string schema, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{
return WithBodyAsGraphQL(schema, matchBehaviour);
}
/// <inheritdoc />
public IRequestBuilder WithGraphQLSchema(string schema, IDictionary<string, Type>? customScalars, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{
return WithBodyAsGraphQL(schema, customScalars, matchBehaviour);
}
/// <inheritdoc />
public IRequestBuilder WithBodyAsGraphQL(string schema, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{
return Add(new RequestMessageGraphQLMatcher(matchBehaviour, schema));
}
/// <inheritdoc />
public IRequestBuilder WithBodyAsGraphQL(string schema, IDictionary<string, Type>? customScalars, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{
return Add(new RequestMessageGraphQLMatcher(matchBehaviour, schema, customScalars));
}
#if GRAPHQL
/// <inheritdoc />
public IRequestBuilder WithGraphQLSchema(GraphQL.Types.ISchema schema, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{
return WithBodyAsGraphQL(schema, matchBehaviour);
}
/// <inheritdoc />
public IRequestBuilder WithGraphQLSchema(GraphQL.Types.ISchema schema, IDictionary<string, Type>? customScalars, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{
return WithBodyAsGraphQL(schema, customScalars, matchBehaviour);
}
/// <inheritdoc />
public IRequestBuilder WithBodyAsGraphQL(GraphQL.Types.ISchema schema, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{
return Add(new RequestMessageGraphQLMatcher(matchBehaviour, schema));
}
/// <inheritdoc />
public IRequestBuilder WithBodyAsGraphQL(GraphQL.Types.ISchema schema, IDictionary<string, Type>? customScalars, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{
return Add(new RequestMessageGraphQLMatcher(matchBehaviour, schema, customScalars));
}
#endif
}

View File

@@ -0,0 +1,13 @@
using WireMock.Matchers;
using WireMock.Matchers.Request;
namespace WireMock.RequestBuilders;
public partial class Request
{
/// <inheritdoc />
public IRequestBuilder WithHttpVersion(string version, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{
return Add(new RequestMessageHttpVersionMatcher(matchBehaviour, version));
}
}

View File

@@ -16,6 +16,11 @@ public partial class Request : RequestMessageCompositeMatcher, IRequestBuilder
{
private readonly IList<IRequestMatcher> _requestMatchers;
/// <summary>
/// The link back to the Mapping.
/// </summary>
public IMapping Mapping { get; set; } = null!;
/// <summary>
/// Creates this instance.
/// </summary>
@@ -63,4 +68,15 @@ public partial class Request : RequestMessageCompositeMatcher, IRequestBuilder
{
return _requestMatchers.OfType<T>().FirstOrDefault(func);
}
private IRequestBuilder Add<T>(T requestMatcher) where T : IRequestMatcher
{
foreach (var existing in _requestMatchers.OfType<T>().ToArray())
{
_requestMatchers.Remove(existing);
}
_requestMatchers.Add(requestMatcher);
return this;
}
}

View File

@@ -4,7 +4,6 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using Newtonsoft.Json;
#if USE_ASPNETCORE
using System.Security.Cryptography.X509Certificates;
#endif
@@ -51,6 +50,9 @@ public class RequestMessage : IRequestMessage
/// <inheritdoc />
public string Method { get; }
/// <inheritdoc />
public string HttpVersion { get; }
/// <inheritdoc />
public IDictionary<string, WireMockList<string>>? Headers { get; }
@@ -73,14 +75,14 @@ public class RequestMessage : IRequestMessage
public string? Body { get; }
/// <inheritdoc />
public object? BodyAsJson { get; }
public object? BodyAsJson { get; set; }
/// <inheritdoc />
public byte[]? BodyAsBytes { get; }
#if MIMEKIT
/// <inheritdoc />
[JsonIgnore] // Issue 1001
[Newtonsoft.Json.JsonIgnore] // Issue 1001
public object? BodyAsMimeMessage { get; }
#endif
@@ -125,11 +127,13 @@ public class RequestMessage : IRequestMessage
internal RequestMessage(
IWireMockMiddlewareOptions? options,
UrlDetails urlDetails, string method,
UrlDetails urlDetails,
string method,
string clientIP,
IBodyData? bodyData = null,
IDictionary<string, string[]>? headers = null,
IDictionary<string, string>? cookies = null
IDictionary<string, string>? cookies = null,
string httpVersion = "1.1"
#if USE_ASPNETCORE
, X509Certificate2? clientCertificate = null
#endif
@@ -152,6 +156,7 @@ public class RequestMessage : IRequestMessage
AbsolutePathSegments = AbsolutePath.Split('/').Skip(1).ToArray();
Method = method;
HttpVersion = httpVersion;
ClientIP = clientIP;
BodyData = bodyData;

View File

@@ -91,18 +91,50 @@ public interface IBodyResponseBuilder : IFaultResponseBuilder
/// WithBody : Create a string response based on a object (which will be converted to a JSON string using the <see cref="IJsonConverter"/>).
/// </summary>
/// <param name="body">The body.</param>
/// <param name="converter">The JsonConverter.</param>
/// <param name="jsonConverter">The <see cref="IJsonConverter"/>.</param>
/// <param name="options">The <see cref="JsonConverterOptions"/> [optional].</param>
/// <returns>A <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithBody(object body, IJsonConverter converter, JsonConverterOptions? options = null);
IResponseBuilder WithBody(object body, IJsonConverter jsonConverter, JsonConverterOptions? options = null);
/// <summary>
/// WithBody : Create a string response based on a object (which will be converted to a JSON string using the <see cref="IJsonConverter"/>).
/// </summary>
/// <param name="body">The body.</param>
/// <param name="encoding">The body encoding, can be <c>null</c>.</param>
/// <param name="converter">The JsonConverter.</param>
/// <param name="jsonConverter">The <see cref="IJsonConverter"/>.</param>
/// <param name="options">The <see cref="JsonConverterOptions"/> [optional].</param>
/// <returns>A <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithBody(object body, Encoding? encoding, IJsonConverter converter, JsonConverterOptions? options = null);
IResponseBuilder WithBody(object body, Encoding? encoding, IJsonConverter jsonConverter, JsonConverterOptions? options = null);
/// <summary>
/// WithBody : Create a ProtoBuf byte[] response based on a proto definition, message type and the value.
/// </summary>
/// <param name="protoDefinition">The proto definition as text.</param>
/// <param name="messageType">The full type of the protobuf (request/response) message object. Format is "{package-name}.{type-name}".</param>
/// <param name="value">The object to convert to protobuf byte[].</param>
/// <param name="jsonConverter">The <see cref="IJsonConverter"/> [optional]. Default value is NewtonsoftJsonConverter.</param>
/// <param name="options">The <see cref="JsonConverterOptions"/> [optional].</param>
/// <returns>A <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithBodyAsProtoBuf(
string protoDefinition,
string messageType,
object value,
IJsonConverter? jsonConverter = null,
JsonConverterOptions? options = null
);
/// <summary>
/// WithBody : Create a ProtoBuf byte[] response based on a proto definition, message type and the value.
/// </summary>
/// <param name="messageType">The full type of the protobuf (request/response) message object. Format is "{package-name}.{type-name}".</param>
/// <param name="value">The object to convert to protobuf byte[].</param>
/// <param name="jsonConverter">The <see cref="IJsonConverter"/> [optional]. Default value is NewtonsoftJsonConverter.</param>
/// <param name="options">The <see cref="JsonConverterOptions"/> [optional].</param>
/// <returns>A <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithBodyAsProtoBuf(
string messageType,
object value,
IJsonConverter? jsonConverter = null,
JsonConverterOptions? options = null
);
}

View File

@@ -9,7 +9,7 @@ namespace WireMock.ResponseBuilders;
public interface IHeadersResponseBuilder : IBodyResponseBuilder
{
/// <summary>
/// The with header.
/// The WithHeader.
/// </summary>
/// <param name="name">The name.</param>
/// <param name="values">The values.</param>
@@ -17,23 +17,52 @@ public interface IHeadersResponseBuilder : IBodyResponseBuilder
IResponseBuilder WithHeader(string name, params string[] values);
/// <summary>
/// The with headers.
/// The WithHeaders.
/// </summary>
/// <param name="headers">The headers.</param>
/// <returns>The <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithHeaders(IDictionary<string, string> headers);
/// <summary>
/// The with headers.
/// The WithHeaders.
/// </summary>
/// <param name="headers">The headers.</param>
/// <returns>The <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithHeaders(IDictionary<string, string[]> headers);
/// <summary>
/// The with headers.
/// The WithHeaders.
/// </summary>
/// <param name="headers">The headers.</param>
/// <returns>The <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithHeaders(IDictionary<string, WireMockList<string>> headers);
/// <summary>
/// The WithTrailingHeader.
/// </summary>
/// <param name="name">The name.</param>
/// <param name="values">The values.</param>
/// <returns>The <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithTrailingHeader(string name, params string[] values);
/// <summary>
/// The WithTrailingHeaders.
/// </summary>
/// <param name="headers">The headers.</param>
/// <returns>The <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithTrailingHeaders(IDictionary<string, string> headers);
/// <summary>
/// The WithTrailingHeaders.
/// </summary>
/// <param name="headers">The headers.</param>
/// <returns>The <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithTrailingHeaders(IDictionary<string, string[]> headers);
/// <summary>
/// The WithTrailingHeaders.
/// </summary>
/// <param name="headers">The headers.</param>
/// <returns>The <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithTrailingHeaders(IDictionary<string, WireMockList<string>> headers);
}

View File

@@ -3,6 +3,7 @@ using System.Text;
using System.Threading.Tasks;
using JsonConverter.Abstractions;
using Stef.Validation;
using WireMock.Exceptions;
using WireMock.Types;
using WireMock.Util;
@@ -185,25 +186,79 @@ public partial class Response
}
/// <inheritdoc />
public IResponseBuilder WithBody(object body, IJsonConverter converter, JsonConverterOptions? options = null)
public IResponseBuilder WithBody(object body, IJsonConverter jsonConverter, JsonConverterOptions? options = null)
{
return WithBody(body, null, converter, options);
return WithBody(body, null, jsonConverter, options);
}
/// <inheritdoc />
public IResponseBuilder WithBody(object body, Encoding? encoding, IJsonConverter converter, JsonConverterOptions? options = null)
public IResponseBuilder WithBody(object body, Encoding? encoding, IJsonConverter jsonConverter, JsonConverterOptions? options = null)
{
Guard.NotNull(body);
Guard.NotNull(converter);
Guard.NotNull(jsonConverter);
ResponseMessage.BodyDestination = null;
ResponseMessage.BodyData = new BodyData
{
Encoding = encoding,
DetectedBodyType = BodyType.String,
BodyAsString = converter.Serialize(body, options)
BodyAsString = jsonConverter.Serialize(body, options)
};
return this;
}
/// <inheritdoc />
public IResponseBuilder WithBodyAsProtoBuf(
string protoDefinition,
string messageType,
object value,
IJsonConverter? jsonConverter = null,
JsonConverterOptions? options = null
)
{
Guard.NotNullOrWhiteSpace(protoDefinition);
Guard.NotNullOrWhiteSpace(messageType);
Guard.NotNull(value);
#if !PROTOBUF
throw new System.NotSupportedException("The WithBodyAsProtoBuf method can not be used for .NETStandard1.3 or .NET Framework 4.6.1 or lower.");
#else
ResponseMessage.BodyDestination = null;
ResponseMessage.BodyData = new BodyData
{
DetectedBodyType = BodyType.ProtoBuf,
BodyAsJson = value,
ProtoDefinition = () => new (null, protoDefinition),
ProtoBufMessageType = messageType
};
#endif
return this;
}
/// <inheritdoc />
public IResponseBuilder WithBodyAsProtoBuf(
string messageType,
object value,
IJsonConverter? jsonConverter = null,
JsonConverterOptions? options = null
)
{
Guard.NotNullOrWhiteSpace(messageType);
Guard.NotNull(value);
#if !PROTOBUF
throw new System.NotSupportedException("The WithBodyAsProtoBuf method can not be used for .NETStandard1.3 or .NET Framework 4.6.1 or lower.");
#else
ResponseMessage.BodyDestination = null;
ResponseMessage.BodyData = new BodyData
{
DetectedBodyType = BodyType.ProtoBuf,
BodyAsJson = value,
ProtoDefinition = () => Mapping.ProtoDefinition ?? throw new WireMockException("ProtoDefinition cannot be resolved. You probably forgot to call .WithProtoDefinition(...) on the mapping."),
ProtoBufMessageType = messageType
};
#endif
return this;
}
}

View File

@@ -0,0 +1,101 @@
using System.Collections.Generic;
using System.Linq;
using Stef.Validation;
using WireMock.Types;
namespace WireMock.ResponseBuilders;
public partial class Response
{
/// <inheritdoc />
public IResponseBuilder WithHeader(string name, params string[] values)
{
Guard.NotNull(name);
ResponseMessage.AddHeader(name, values);
return this;
}
/// <inheritdoc />
public IResponseBuilder WithHeaders(IDictionary<string, string> headers)
{
Guard.NotNull(headers);
ResponseMessage.Headers = headers.ToDictionary(header => header.Key, header => new WireMockList<string>(header.Value));
return this;
}
/// <inheritdoc />
public IResponseBuilder WithHeaders(IDictionary<string, string[]> headers)
{
Guard.NotNull(headers);
ResponseMessage.Headers = headers.ToDictionary(header => header.Key, header => new WireMockList<string>(header.Value));
return this;
}
/// <inheritdoc />
public IResponseBuilder WithHeaders(IDictionary<string, WireMockList<string>> headers)
{
Guard.NotNull(headers);
ResponseMessage.Headers = headers;
return this;
}
/// <inheritdoc />
public IResponseBuilder WithTrailingHeader(string name, params string[] values)
{
#if !TRAILINGHEADERS
throw new System.NotSupportedException("The WithBodyAsProtoBuf method can not be used for .NETStandard1.3 or .NET Framework 4.6.1 or lower.");
#else
Guard.NotNull(name);
ResponseMessage.AddTrailingHeader(name, values);
return this;
#endif
}
/// <inheritdoc />
public IResponseBuilder WithTrailingHeaders(IDictionary<string, string> headers)
{
#if !TRAILINGHEADERS
throw new System.NotSupportedException("The WithBodyAsProtoBuf method can not be used for .NETStandard1.3 or .NET Framework 4.6.1 or lower.");
#else
Guard.NotNull(headers);
ResponseMessage.TrailingHeaders = headers.ToDictionary(header => header.Key, header => new WireMockList<string>(header.Value));
return this;
#endif
}
/// <inheritdoc />
public IResponseBuilder WithTrailingHeaders(IDictionary<string, string[]> headers)
{
#if !TRAILINGHEADERS
throw new System.NotSupportedException("The WithBodyAsProtoBuf method can not be used for .NETStandard1.3 or .NET Framework 4.6.1 or lower.");
#else
Guard.NotNull(headers);
ResponseMessage.TrailingHeaders = headers.ToDictionary(header => header.Key, header => new WireMockList<string>(header.Value));
return this;
#endif
}
/// <inheritdoc />
public IResponseBuilder WithTrailingHeaders(IDictionary<string, WireMockList<string>> headers)
{
#if !TRAILINGHEADERS
throw new System.NotSupportedException("The WithBodyAsProtoBuf method can not be used for .NETStandard1.3 or .NET Framework 4.6.1 or lower.");
#else
Guard.NotNull(headers);
ResponseMessage.TrailingHeaders = headers;
return this;
#endif
}
}

View File

@@ -1,19 +1,20 @@
// This source file is based on mock4net by Alexandre Victoor which is licensed under the Apache 2.0 License.
// For more details see 'mock4net/LICENSE.txt' and 'mock4net/readme.md' in this project root.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Stef.Validation;
using WireMock.Matchers.Request;
using WireMock.Proxy;
using WireMock.RequestBuilders;
using WireMock.Settings;
using WireMock.Transformers;
using WireMock.Transformers.Handlebars;
using WireMock.Transformers.Scriban;
using WireMock.Types;
using WireMock.Util;
namespace WireMock.ResponseBuilders;
@@ -26,6 +27,11 @@ public partial class Response : IResponseBuilder
private TimeSpan? _delay;
/// <summary>
/// The link back to the mapping.
/// </summary>
public IMapping Mapping { get; set; } = null!;
/// <summary>
/// The minimum random delay in milliseconds.
/// </summary>
@@ -112,7 +118,7 @@ public partial class Response : IResponseBuilder
{
ResponseMessage = responseMessage;
}
/// <inheritdoc cref="IStatusCodeResponseBuilder.WithStatusCode(int)"/>
[PublicAPI]
public IResponseBuilder WithStatusCode(int code)
@@ -156,42 +162,6 @@ public partial class Response : IResponseBuilder
return WithStatusCode((int)HttpStatusCode.NotFound);
}
/// <inheritdoc cref="IHeadersResponseBuilder.WithHeader(string, string[])"/>
public IResponseBuilder WithHeader(string name, params string[] values)
{
Guard.NotNull(name);
ResponseMessage.AddHeader(name, values);
return this;
}
/// <inheritdoc cref="IHeadersResponseBuilder.WithHeaders(IDictionary{string, string})"/>
public IResponseBuilder WithHeaders(IDictionary<string, string> headers)
{
Guard.NotNull(headers);
ResponseMessage.Headers = headers.ToDictionary(header => header.Key, header => new WireMockList<string>(header.Value));
return this;
}
/// <inheritdoc cref="IHeadersResponseBuilder.WithHeaders(IDictionary{string, string[]})"/>
public IResponseBuilder WithHeaders(IDictionary<string, string[]> headers)
{
Guard.NotNull(headers);
ResponseMessage.Headers = headers.ToDictionary(header => header.Key, header => new WireMockList<string>(header.Value));
return this;
}
/// <inheritdoc cref="IHeadersResponseBuilder.WithHeaders(IDictionary{string, WireMockList{string}})"/>
public IResponseBuilder WithHeaders(IDictionary<string, WireMockList<string>> headers)
{
Guard.NotNull(headers);
ResponseMessage.Headers = headers;
return this;
}
/// <inheritdoc cref="ITransformResponseBuilder.WithTransformer(bool)"/>
public IResponseBuilder WithTransformer(bool transformContentFromBodyAsFile)
{
@@ -304,10 +274,30 @@ public partial class Response : IResponseBuilder
{
responseMessage.Headers = ResponseMessage.Headers;
}
// Copy TrailingHeaders from ResponseMessage (if defined)
if (ResponseMessage.TrailingHeaders?.Count > 0)
{
responseMessage.TrailingHeaders = ResponseMessage.TrailingHeaders;
}
}
if (UseTransformer)
{
// Check if the body matcher is a RequestMessageProtoBufMatcher and try to to decode the byte-array to a BodyAsJson.
if (mapping.RequestMatcher is Request requestMatcher && requestMessage is RequestMessage request)
{
var protoBufMatcher = requestMatcher.GetRequestMessageMatcher<RequestMessageProtoBufMatcher>()?.Matcher;
if (protoBufMatcher != null)
{
var decoded = await protoBufMatcher.DecodeAsync(request.BodyData?.BodyAsBytes).ConfigureAwait(false);
if (decoded != null)
{
request.BodyAsJson = JsonUtils.ConvertValueToJToken(decoded);
}
}
}
ITransformer responseMessageTransformer;
switch (TransformerType)
{

View File

@@ -14,9 +14,12 @@ namespace WireMock;
/// </summary>
public class ResponseMessage : IResponseMessage
{
/// <inheritdoc cref="IResponseMessage.Headers" />
/// <inheritdoc />
public IDictionary<string, WireMockList<string>>? Headers { get; set; } = new Dictionary<string, WireMockList<string>>();
/// <inheritdoc />
public IDictionary<string, WireMockList<string>>? TrailingHeaders { get; set; } = new Dictionary<string, WireMockList<string>>();
/// <inheritdoc cref="IResponseMessage.StatusCode" />
public object? StatusCode { get; set; }
@@ -35,23 +38,43 @@ public class ResponseMessage : IResponseMessage
/// <inheritdoc cref="IResponseMessage.FaultPercentage" />
public double? FaultPercentage { get; set; }
/// <inheritdoc cref="IResponseMessage.AddHeader(string, string)" />
/// <inheritdoc />
public void AddHeader(string name, string value)
{
Headers ??= new Dictionary<string, WireMockList<string>>();
Headers.Add(name, value);
}
/// <inheritdoc cref="IResponseMessage.AddHeader(string, string[])" />
/// <inheritdoc />
public void AddHeader(string name, params string[] values)
{
Guard.NotNullOrEmpty(values);
Headers ??= new Dictionary<string, WireMockList<string>>();
var newHeaderValues = Headers.TryGetValue(name, out WireMockList<string>? existingValues)
var newHeaderValues = Headers.TryGetValue(name, out var existingValues)
? values.Union(existingValues).ToArray()
: values;
Headers[name] = newHeaderValues;
}
/// <inheritdoc />
public void AddTrailingHeader(string name, string value)
{
TrailingHeaders ??= new Dictionary<string, WireMockList<string>>();
TrailingHeaders.Add(name, value);
}
/// <inheritdoc />
public void AddTrailingHeader(string name, params string[] values)
{
Guard.NotNullOrEmpty(values);
TrailingHeaders ??= new Dictionary<string, WireMockList<string>>();
var newHeaderValues = TrailingHeaders.TryGetValue(name, out var existingValues)
? values.Union(existingValues).ToArray()
: values;
TrailingHeaders[name] = newHeaderValues;
}
}

View File

@@ -1,5 +1,6 @@
using System;
using System.Threading.Tasks;
using Stef.Validation;
using WireMock.Settings;
namespace WireMock.ResponseProviders;
@@ -10,7 +11,7 @@ internal class DynamicAsyncResponseProvider : IResponseProvider
public DynamicAsyncResponseProvider(Func<IRequestMessage, Task<IResponseMessage>> responseMessageFunc)
{
_responseMessageFunc = responseMessageFunc;
_responseMessageFunc = Guard.NotNull(responseMessageFunc);
}
public async Task<(IResponseMessage Message, IMapping? Mapping)> ProvideResponseAsync(IMapping mapping, IRequestMessage requestMessage, WireMockServerSettings settings)

View File

@@ -1,5 +1,6 @@
using System;
using System.Threading.Tasks;
using Stef.Validation;
using WireMock.Settings;
namespace WireMock.ResponseProviders;
@@ -10,7 +11,7 @@ internal class DynamicResponseProvider : IResponseProvider
public DynamicResponseProvider(Func<IRequestMessage, IResponseMessage> responseMessageFunc)
{
_responseMessageFunc = responseMessageFunc;
_responseMessageFunc = Guard.NotNull(responseMessageFunc);
}
public Task<(IResponseMessage Message, IMapping? Mapping)> ProvideResponseAsync(IMapping mapping, IRequestMessage requestMessage, WireMockServerSettings settings)

View File

@@ -1,5 +1,6 @@
using System;
using System.Threading.Tasks;
using Stef.Validation;
using WireMock.Settings;
namespace WireMock.ResponseProviders;
@@ -11,8 +12,8 @@ internal class ProxyAsyncResponseProvider : IResponseProvider
public ProxyAsyncResponseProvider(Func<IRequestMessage, WireMockServerSettings, Task<IResponseMessage>> responseMessageFunc, WireMockServerSettings settings)
{
_responseMessageFunc = responseMessageFunc;
_settings = settings;
_responseMessageFunc = Guard.NotNull(responseMessageFunc);
_settings = Guard.NotNull(settings);
}
public async Task<(IResponseMessage Message, IMapping? Mapping)> ProvideResponseAsync(IMapping mapping, IRequestMessage requestMessage, WireMockServerSettings settings)

View File

@@ -32,6 +32,7 @@ internal class LogEntryMapper
ProxyUrl = logEntry.RequestMessage.ProxyUrl,
Query = logEntry.RequestMessage.Query,
Method = logEntry.RequestMessage.Method,
HttpVersion = logEntry.RequestMessage.HttpVersion,
Headers = logEntry.RequestMessage.Headers,
Cookies = logEntry.RequestMessage.Cookies
};

View File

@@ -14,7 +14,6 @@ using WireMock.Matchers.Request;
using WireMock.Models;
using WireMock.RequestBuilders;
using WireMock.ResponseBuilders;
using WireMock.Settings;
using WireMock.Types;
using WireMock.Util;
@@ -48,8 +47,10 @@ internal class MappingConverter
var paramsMatchers = request.GetRequestMessageMatchers<RequestMessageParamMatcher>();
var methodMatcher = request.GetRequestMessageMatcher<RequestMessageMethodMatcher>();
var requestMessageBodyMatcher = request.GetRequestMessageMatcher<RequestMessageBodyMatcher>();
var requestMessageHttpVersionMatcher = request.GetRequestMessageMatcher<RequestMessageHttpVersionMatcher>();
var requestMessageGraphQLMatcher = request.GetRequestMessageMatcher<RequestMessageGraphQLMatcher>();
var requestMessageMultiPartMatcher = request.GetRequestMessageMatcher<RequestMessageMultiPartMatcher>();
var requestMessageProtoBufMatcher = request.GetRequestMessageMatcher<RequestMessageProtoBufMatcher>();
var sb = new StringBuilder();
@@ -108,6 +109,11 @@ internal class MappingConverter
sb.AppendLine($" .WithCookie(\"{cookieMatcher.Name}\", {ToValueArguments(GetStringArray(cookieMatcher.Matchers!))}, true)");
}
if (requestMessageHttpVersionMatcher?.HttpVersion != null)
{
sb.AppendLine($" .WithHttpVersion({requestMessageHttpVersionMatcher.HttpVersion})");
}
#if GRAPHQL
if (requestMessageGraphQLMatcher is { Matchers: { } })
{
@@ -128,6 +134,13 @@ internal class MappingConverter
}
#endif
#if PROTOBUF
if (requestMessageProtoBufMatcher is { Matcher: { } })
{
sb.AppendLine(" // .WithBodyAsProtoBuf() is not yet supported");
}
#endif
if (requestMessageBodyMatcher is { Matchers: { } })
{
if (requestMessageBodyMatcher.Matchers.OfType<WildcardMatcher>().FirstOrDefault() is { } wildcardMatcher && wildcardMatcher.GetPatterns().Any())
@@ -182,6 +195,14 @@ internal class MappingConverter
}
}
if (response.ResponseMessage.TrailingHeaders is { })
{
foreach (var header in response.ResponseMessage.TrailingHeaders)
{
sb.AppendLine($" .WithTrailingHeader(\"{header.Key}\", {ToValueArguments(header.Value.ToArray())})");
}
}
if (response.ResponseMessage.BodyData is { } bodyData)
{
switch (response.ResponseMessage.BodyData.DetectedBodyType)
@@ -190,6 +211,7 @@ internal class MappingConverter
case BodyType.FormUrlEncoded:
sb.AppendLine($" .WithBody({ToCSharpStringLiteral(bodyData.BodyAsString)})");
break;
case BodyType.Json:
if (bodyData.BodyAsJson is string bodyStringValue)
{
@@ -239,6 +261,8 @@ internal class MappingConverter
var bodyMatcher = request.GetRequestMessageMatcher<RequestMessageBodyMatcher>();
var graphQLMatcher = request.GetRequestMessageMatcher<RequestMessageGraphQLMatcher>();
var multiPartMatcher = request.GetRequestMessageMatcher<RequestMessageMultiPartMatcher>();
var protoBufMatcher = request.GetRequestMessageMatcher<RequestMessageProtoBufMatcher>();
var httpVersionMatcher = request.GetRequestMessageMatcher<RequestMessageHttpVersionMatcher>();
var mappingModel = new MappingModel
{
@@ -253,6 +277,7 @@ internal class MappingConverter
WhenStateIs = mapping.ExecutionConditionState,
SetStateTo = mapping.NextState,
Data = mapping.Data,
ProtoDefinition = mapping.ProtoDefinition?.Value,
Probability = mapping.Probability,
Request = new RequestModel
{
@@ -290,6 +315,11 @@ internal class MappingConverter
mappingModel.Request.MethodsMatchOperator = methodMatcher.Methods.Length > 1 ? methodMatcher.MatchOperator.ToString() : null;
}
if (httpVersionMatcher?.HttpVersion != null)
{
mappingModel.Request.HttpVersion = httpVersionMatcher.HttpVersion;
}
if (clientIPMatcher is { Matchers: { } })
{
var clientIPMatchers = _mapper.Map(clientIPMatcher.Matchers);
@@ -329,7 +359,7 @@ internal class MappingConverter
mappingModel.Response.Delay = (int?)(response.Delay == Timeout.InfiniteTimeSpan ? TimeSpan.MaxValue.TotalMilliseconds : response.Delay?.TotalMilliseconds);
}
var nonNullableWebHooks = mapping.Webhooks?.Where(wh => wh != null).ToArray() ?? EmptyArray<IWebhook>.Value;
var nonNullableWebHooks = mapping.Webhooks?.ToArray() ?? EmptyArray<IWebhook>.Value;
if (nonNullableWebHooks.Length == 1)
{
mappingModel.Webhook = WebhookMapper.Map(nonNullableWebHooks[0]);
@@ -339,20 +369,40 @@ internal class MappingConverter
mappingModel.Webhooks = mapping.Webhooks.Select(WebhookMapper.Map).ToArray();
}
var bodyMatchers = multiPartMatcher?.Matchers ?? graphQLMatcher?.Matchers ?? bodyMatcher?.Matchers;
var matchOperator = multiPartMatcher?.MatchOperator ?? graphQLMatcher?.MatchOperator ?? bodyMatcher?.MatchOperator;
var bodyMatchers =
protoBufMatcher?.Matcher != null ? new[] { protoBufMatcher.Matcher } : null ??
multiPartMatcher?.Matchers ??
graphQLMatcher?.Matchers ??
bodyMatcher?.Matchers;
if (bodyMatchers != null && matchOperator != null)
var matchOperator =
multiPartMatcher?.MatchOperator ??
graphQLMatcher?.MatchOperator ??
bodyMatcher?.MatchOperator ??
MatchOperator.Or;
if (bodyMatchers != null)
{
void AfterMap(MatcherModel matcherModel)
{
#if PROTOBUF
// In case the ProtoDefinition is defined at the Mapping level, clear the Pattern at the Matcher level
if (bodyMatchers?.OfType<ProtoBufMatcher>().Any() == true && mappingModel.ProtoDefinition != null)
{
matcherModel.Pattern = null;
}
#endif
}
mappingModel.Request.Body = new BodyModel();
if (bodyMatchers.Length == 1)
{
mappingModel.Request.Body.Matcher = _mapper.Map(bodyMatchers[0]);
mappingModel.Request.Body.Matcher = _mapper.Map(bodyMatchers[0], AfterMap);
}
else if (bodyMatchers.Length > 1)
{
mappingModel.Request.Body.Matchers = _mapper.Map(bodyMatchers);
mappingModel.Request.Body.Matchers = _mapper.Map(bodyMatchers, AfterMap);
mappingModel.Request.Body.MatchOperator = matchOperator.ToString();
}
}
@@ -390,6 +440,11 @@ internal class MappingConverter
mappingModel.Response.Headers = MapHeaders(response.ResponseMessage.Headers);
}
if (response.ResponseMessage.TrailingHeaders is { Count: > 0 })
{
mappingModel.Response.TrailingHeaders = MapHeaders(response.ResponseMessage.TrailingHeaders);
}
if (response.UseTransformer)
{
mappingModel.Response.UseTransformer = response.UseTransformer;
@@ -402,43 +457,7 @@ internal class MappingConverter
mappingModel.Response.UseTransformerForBodyAsFile = response.UseTransformerForBodyAsFile;
}
if (response.ResponseMessage.BodyData != null)
{
switch (response.ResponseMessage.BodyData?.DetectedBodyType)
{
case BodyType.String:
case BodyType.FormUrlEncoded:
mappingModel.Response.Body = response.ResponseMessage.BodyData.BodyAsString;
break;
case BodyType.Json:
mappingModel.Response.BodyAsJson = response.ResponseMessage.BodyData.BodyAsJson;
if (response.ResponseMessage.BodyData.BodyAsJsonIndented == true)
{
mappingModel.Response.BodyAsJsonIndented = response.ResponseMessage.BodyData.BodyAsJsonIndented;
}
break;
case BodyType.Bytes:
mappingModel.Response.BodyAsBytes = response.ResponseMessage.BodyData.BodyAsBytes;
break;
case BodyType.File:
mappingModel.Response.BodyAsFile = response.ResponseMessage.BodyData.BodyAsFile;
mappingModel.Response.BodyAsFileIsCached = response.ResponseMessage.BodyData.BodyAsFileIsCached;
break;
}
if (response.ResponseMessage.BodyData?.Encoding != null && response.ResponseMessage.BodyData.Encoding.WebName != "utf-8")
{
mappingModel.Response.BodyEncoding = new EncodingModel
{
EncodingName = response.ResponseMessage.BodyData.Encoding.EncodingName,
CodePage = response.ResponseMessage.BodyData.Encoding.CodePage,
WebName = response.ResponseMessage.BodyData.Encoding.WebName
};
}
}
MapResponse(response, mappingModel);
if (response.ResponseMessage.FaultType != FaultType.NONE)
{
@@ -453,6 +472,61 @@ internal class MappingConverter
return mappingModel;
}
private static void MapResponse(Response response, MappingModel mappingModel)
{
if (response.ResponseMessage.BodyData == null)
{
return;
}
switch (response.ResponseMessage.BodyData?.DetectedBodyType)
{
case BodyType.String:
case BodyType.FormUrlEncoded:
mappingModel.Response.Body = response.ResponseMessage.BodyData.BodyAsString;
break;
case BodyType.Json:
mappingModel.Response.BodyAsJson = response.ResponseMessage.BodyData.BodyAsJson;
if (response.ResponseMessage.BodyData.BodyAsJsonIndented == true)
{
mappingModel.Response.BodyAsJsonIndented = response.ResponseMessage.BodyData.BodyAsJsonIndented;
}
break;
case BodyType.ProtoBuf:
// If the ProtoDefinition is not defined at the MappingModel, get the ProtoDefinition from the ResponseMessage.
if (mappingModel.ProtoDefinition == null)
{
mappingModel.Response.ProtoDefinition = response.ResponseMessage.BodyData.ProtoDefinition?.Invoke().Value;
}
mappingModel.Response.ProtoBufMessageType = response.ResponseMessage.BodyData.ProtoBufMessageType;
mappingModel.Response.BodyAsBytes = null;
mappingModel.Response.BodyAsJson = response.ResponseMessage.BodyData.BodyAsJson;
break;
case BodyType.Bytes:
mappingModel.Response.BodyAsBytes = response.ResponseMessage.BodyData.BodyAsBytes;
break;
case BodyType.File:
mappingModel.Response.BodyAsFile = response.ResponseMessage.BodyData.BodyAsFile;
mappingModel.Response.BodyAsFileIsCached = response.ResponseMessage.BodyData.BodyAsFileIsCached;
break;
}
if (response.ResponseMessage.BodyData?.Encoding != null && response.ResponseMessage.BodyData.Encoding.WebName != "utf-8")
{
mappingModel.Response.BodyEncoding = new EncodingModel
{
EncodingName = response.ResponseMessage.BodyData.Encoding.EncodingName,
CodePage = response.ResponseMessage.BodyData.Encoding.CodePage,
WebName = response.ResponseMessage.BodyData.Encoding.WebName
};
}
}
private static string GetString(IStringMatcher stringMatcher)
{
return stringMatcher.GetPatterns().Select(p => ToCSharpStringLiteral(p.GetPattern())).First();

View File

@@ -8,7 +8,6 @@ using WireMock.Admin.Mappings;
using WireMock.Extensions;
using WireMock.Matchers;
using WireMock.Models;
using WireMock.Plugin;
using WireMock.Settings;
using WireMock.Util;
@@ -25,29 +24,25 @@ internal class MatcherMapper
public IMatcher[]? Map(IEnumerable<MatcherModel>? matchers)
{
if (matchers == null)
{
return null;
}
return matchers.Select(Map).Where(m => m != null).ToArray()!;
return matchers?.Select(Map).OfType<IMatcher>().ToArray();
}
public IMatcher? Map(MatcherModel? matcher)
public IMatcher? Map(MatcherModel? matcherModel)
{
if (matcher == null)
if (matcherModel == null)
{
return null;
}
string[] parts = matcher.Name.Split('.');
string[] parts = matcherModel.Name.Split('.');
string matcherName = parts[0];
string? matcherType = parts.Length > 1 ? parts[1] : null;
var stringPatterns = ParseStringPatterns(matcher);
var matchBehaviour = matcher.RejectOnMatch == true ? MatchBehaviour.RejectOnMatch : MatchBehaviour.AcceptOnMatch;
var matchOperator = StringUtils.ParseMatchOperator(matcher.MatchOperator);
bool ignoreCase = matcher.IgnoreCase == true;
var stringPatterns = ParseStringPatterns(matcherModel);
var matchBehaviour = matcherModel.RejectOnMatch == true ? MatchBehaviour.RejectOnMatch : MatchBehaviour.AcceptOnMatch;
var matchOperator = StringUtils.ParseMatchOperator(matcherModel.MatchOperator);
bool ignoreCase = matcherModel.IgnoreCase == true;
bool useRegexExtended = _settings.UseRegexExtended == true;
bool useRegex = matcher.Regex == true;
bool useRegex = matcherModel.Regex == true;
switch (matcherName)
{
@@ -57,7 +52,7 @@ internal class MatcherMapper
case "CSharpCodeMatcher":
if (_settings.AllowCSharpCodeMatcher == true)
{
return PluginLoader.Load<ICSharpCodeMatcher>(matchBehaviour, matchOperator, stringPatterns);
return TypeLoader.Load<ICSharpCodeMatcher>(matchBehaviour, matchOperator, stringPatterns);
}
throw new NotSupportedException("It's not allowed to use the 'CSharpCodeMatcher' because WireMockServerSettings.AllowCSharpCodeMatcher is not set to 'true'.");
@@ -72,26 +67,31 @@ internal class MatcherMapper
return CreateExactObjectMatcher(matchBehaviour, stringPatterns[0]);
#if GRAPHQL
case nameof(GraphQLMatcher):
return new GraphQLMatcher(stringPatterns[0].GetPattern(), matcher.CustomScalars, matchBehaviour, matchOperator);
return new GraphQLMatcher(stringPatterns[0].GetPattern(), matcherModel.CustomScalars, matchBehaviour, matchOperator);
#endif
#if MIMEKIT
case nameof(MimePartMatcher):
return CreateMimePartMatcher(matchBehaviour, matcher);
return CreateMimePartMatcher(matchBehaviour, matcherModel);
#endif
#if PROTOBUF
case nameof(ProtoBufMatcher):
return CreateProtoBufMatcher(matchBehaviour, stringPatterns[0].GetPattern(), matcherModel);
#endif
case nameof(RegexMatcher):
return new RegexMatcher(matchBehaviour, stringPatterns, ignoreCase, useRegexExtended, matchOperator);
case nameof(JsonMatcher):
var valueForJsonMatcher = matcher.Pattern ?? matcher.Patterns;
var valueForJsonMatcher = matcherModel.Pattern ?? matcherModel.Patterns;
return new JsonMatcher(matchBehaviour, valueForJsonMatcher!, ignoreCase);
case nameof(JsonPartialMatcher):
var valueForJsonPartialMatcher = matcher.Pattern ?? matcher.Patterns;
var valueForJsonPartialMatcher = matcherModel.Pattern ?? matcherModel.Patterns;
return new JsonPartialMatcher(matchBehaviour, valueForJsonPartialMatcher!, ignoreCase, useRegex);
case nameof(JsonPartialWildcardMatcher):
var valueForJsonPartialWildcardMatcher = matcher.Pattern ?? matcher.Patterns;
var valueForJsonPartialWildcardMatcher = matcherModel.Pattern ?? matcherModel.Patterns;
return new JsonPartialWildcardMatcher(matchBehaviour, valueForJsonPartialWildcardMatcher!, ignoreCase, useRegex);
case nameof(JsonPathMatcher):
@@ -101,7 +101,7 @@ internal class MatcherMapper
return new JmesPathMatcher(matchBehaviour, matchOperator, stringPatterns);
case nameof(XPathMatcher):
return new XPathMatcher(matchBehaviour, matchOperator, matcher.XmlNamespaceMap, stringPatterns);
return new XPathMatcher(matchBehaviour, matchOperator, matcherModel.XmlNamespaceMap, stringPatterns);
case nameof(WildcardMatcher):
return new WildcardMatcher(matchBehaviour, stringPatterns, ignoreCase, matchOperator);
@@ -121,19 +121,19 @@ internal class MatcherMapper
default:
if (_settings.CustomMatcherMappings != null && _settings.CustomMatcherMappings.ContainsKey(matcherName))
{
return _settings.CustomMatcherMappings[matcherName](matcher);
return _settings.CustomMatcherMappings[matcherName](matcherModel);
}
throw new NotSupportedException($"Matcher '{matcherName}' is not supported.");
}
}
public MatcherModel[]? Map(IEnumerable<IMatcher>? matchers)
public MatcherModel[]? Map(IEnumerable<IMatcher>? matchers, Action<MatcherModel>? afterMap = null)
{
return matchers?.Where(m => m != null).Select(Map).ToArray();
return matchers?.Select(m => Map(m, afterMap)).OfType<MatcherModel>().ToArray();
}
public MatcherModel? Map(IMatcher? matcher)
public MatcherModel? Map(IMatcher? matcher, Action<MatcherModel>? afterMap = null)
{
if (matcher == null)
{
@@ -194,14 +194,9 @@ internal class MatcherMapper
}
break;
// If the matcher is a IValueMatcher, get the value (can be string or object).
case IValueMatcher valueMatcher:
model.Pattern = valueMatcher.Value;
break;
// If the matcher is a ExactObjectMatcher, get the ValueAsObject or ValueAsBytes.
case ExactObjectMatcher exactObjectMatcher:
model.Pattern = exactObjectMatcher.ValueAsObject ?? exactObjectMatcher.ValueAsBytes;
// If the matcher is a IObjectMatcher, get the value (can be string or object or byte[]).
case IObjectMatcher objectMatcher:
model.Pattern = objectMatcher.Value;
break;
#if MIMEKIT
@@ -212,8 +207,18 @@ internal class MatcherMapper
model.ContentTypeMatcher = Map(mimePartMatcher.ContentTypeMatcher);
break;
#endif
#if PROTOBUF
case ProtoBufMatcher protoBufMatcher:
model.Pattern = protoBufMatcher.ProtoDefinition().Value;
model.ProtoBufMessageType = protoBufMatcher.MessageType;
model.ContentMatcher = Map(protoBufMatcher.Matcher);
break;
#endif
}
afterMap?.Invoke(model);
return model;
}
@@ -260,7 +265,7 @@ internal class MatcherMapper
}
#if MIMEKIT
private MimePartMatcher CreateMimePartMatcher(MatchBehaviour matchBehaviour, MatcherModel? matcher)
private MimePartMatcher CreateMimePartMatcher(MatchBehaviour matchBehaviour, MatcherModel matcher)
{
var contentTypeMatcher = Map(matcher?.ContentTypeMatcher) as IStringMatcher;
var contentDispositionMatcher = Map(matcher?.ContentDispositionMatcher) as IStringMatcher;
@@ -270,4 +275,28 @@ internal class MatcherMapper
return new MimePartMatcher(matchBehaviour, contentTypeMatcher, contentDispositionMatcher, contentTransferEncodingMatcher, contentMatcher);
}
#endif
#if PROTOBUF
private ProtoBufMatcher CreateProtoBufMatcher(MatchBehaviour? matchBehaviour, string protoDefinitionOrId, MatcherModel matcher)
{
var objectMatcher = Map(matcher.ContentMatcher) as IObjectMatcher;
IdOrText protoDefinition;
if (_settings.ProtoDefinitions?.TryGetValue(protoDefinitionOrId, out var protoDefinitionFromSettings) == true)
{
protoDefinition = new(protoDefinitionOrId, protoDefinitionFromSettings);
}
else
{
protoDefinition = new(null, protoDefinitionOrId);
}
return new ProtoBufMatcher(
() => protoDefinition,
matcher!.ProtoBufMessageType!,
matchBehaviour ?? MatchBehaviour.AcceptOnMatch,
objectMatcher
);
}
#endif
}

View File

@@ -41,6 +41,7 @@ internal class ProxyMappingConverter
var paramMatchers = request?.GetRequestMessageMatchers<RequestMessageParamMatcher>();
var methodMatcher = request?.GetRequestMessageMatcher<RequestMessageMethodMatcher>();
var bodyMatcher = request?.GetRequestMessageMatcher<RequestMessageBodyMatcher>();
var httpVersionMatcher = request?.GetRequestMessageMatcher<RequestMessageHttpVersionMatcher>();
var newRequest = Request.Create();
@@ -70,6 +71,16 @@ internal class ProxyMappingConverter
newRequest.UsingMethod(requestMessage.Method);
}
// HttpVersion
if (useDefinedRequestMatchers && httpVersionMatcher?.HttpVersion is not null)
{
newRequest.WithHttpVersion(httpVersionMatcher.HttpVersion);
}
else
{
newRequest.WithHttpVersion(requestMessage.HttpVersion);
}
// QueryParams
if (useDefinedRequestMatchers && paramMatchers is not null)
{
@@ -188,8 +199,7 @@ internal class ProxyMappingConverter
webhooks: null,
useWebhooksFireAndForget: null,
timeSettings: null,
data: mapping?.Data,
probability: null
data: mapping?.Data
);
}
}

Some files were not shown because too many files have changed in this diff Show More