Compare commits

..

12 Commits

Author SHA1 Message Date
Stef Heyenrath
6ef116a295 1.5.24 2023-05-07 14:48:20 +02:00
Stef Heyenrath
59195eaed8 Refactor some code (MappingConverter) 2023-05-07 14:42:00 +02:00
Cezary Piątek
7d9e450814 C# code generator improvements (#933)
* Escape quotes in generated C# strings

* Handle response with JSON body in C# code generator
2023-05-07 14:37:48 +02:00
Stef Heyenrath
7019a5a78c Add Package Readme (#932)
* Add Package Readme

* C# .NET API

* <PackageReadmeFile>PackageReadme.md</PackageReadmeFile>
2023-05-07 09:24:48 +02:00
Stef Heyenrath
d29f3e81f3 Add property 'IsStartedWithAdminInterface' to 'IWireMockServer' (#931)
* Add property 'IsStartedWithAdminInterface' to 'IWireMockServer'

* update tests

* .
2023-05-06 13:12:00 +02:00
Stef Heyenrath
ccd8026884 Update C# mapping code generator for WithStatusCode (#930) 2023-05-06 10:24:53 +02:00
Cezary Piątek
1214ba5108 Enrich generated code with status code (#927) 2023-05-06 09:40:41 +02:00
Cezary Piątek
427715a38a Fix csharp mapping code generator (#926) 2023-05-02 16:48:32 +02:00
Stef Heyenrath
d949dfb64c 1.5.23 2023-04-23 12:06:38 +02:00
Stef Heyenrath
0a2763c06e Add IgnoreCase option to ProxyUrlReplaceSettings (#925)
* Add IgnoreCase option to ProxyUrlReplaceSettings

* fix
2023-04-23 11:56:08 +02:00
nudejustin
9ef8bd0b7b Allow removal of prefix when proxying to another server (#630) (#924)
* #630 Allow removal of prefix when proxying to another server

* #630 Rename replace to replace settings and ensure properties used in place of fields

* #630 Update replace settings type name to ProxyUrlReplaceSettings

* #630 Add admin model and update settings parser to parse new values

* Fix formatting issues

* #630 Ensure json mapping between admin model and internal model takes place

* #630 Refactor parsing and structure of extracting new proxy url

* Reduce function complexity

* #630 Fix line length issues and remove try prefix from parser methods
2023-04-23 09:31:38 +02:00
Stef Heyenrath
090e0eb437 Add WithProbability (#922)
* WithProbability

* fix

* x

* ,

* .

* .

* .
2023-04-12 20:48:53 +02:00
58 changed files with 1097 additions and 520 deletions

View File

@@ -1,3 +1,15 @@
# 1.5.24 (07 May 2023)
- [#926](https://github.com/WireMock-Net/WireMock.Net/pull/926) - Fix C# mapping code generator for header names [bug] contributed by [cezarypiatek](https://github.com/cezarypiatek)
- [#927](https://github.com/WireMock-Net/WireMock.Net/pull/927) - Enrich generated code with status code [feature] contributed by [cezarypiatek](https://github.com/cezarypiatek)
- [#930](https://github.com/WireMock-Net/WireMock.Net/pull/930) - Update C# mapping code generator for WithStatusCode [feature] contributed by [StefH](https://github.com/StefH)
- [#931](https://github.com/WireMock-Net/WireMock.Net/pull/931) - Add property 'IsStartedWithAdminInterface' to 'IWireMockServer' [feature] contributed by [StefH](https://github.com/StefH)
- [#933](https://github.com/WireMock-Net/WireMock.Net/pull/933) - C# code generator improvements [feature] contributed by [cezarypiatek](https://github.com/cezarypiatek)
# 1.5.23 (23 April 2023)
- [#922](https://github.com/WireMock-Net/WireMock.Net/pull/922) - Add WithProbability [feature] contributed by [StefH](https://github.com/StefH)
- [#924](https://github.com/WireMock-Net/WireMock.Net/pull/924) - Allow removal of prefix when proxying to another server (#630) [feature] contributed by [nudejustin](https://github.com/nudejustin)
- [#925](https://github.com/WireMock-Net/WireMock.Net/pull/925) - Add IgnoreCase option to ProxyUrlReplaceSettings [feature] contributed by [StefH](https://github.com/StefH)
# 1.5.22 (08 April 2023)
- [#914](https://github.com/WireMock-Net/WireMock.Net/pull/914) - #912 add excluded params to proxy mapping [feature] contributed by [walidhaidarii](https://github.com/walidhaidarii)
- [#916](https://github.com/WireMock-Net/WireMock.Net/pull/916) - Include WireMockOpenApiParser project [feature] contributed by [StefH](https://github.com/StefH)

View File

@@ -4,7 +4,7 @@
</PropertyGroup>
<PropertyGroup>
<VersionPrefix>1.5.22</VersionPrefix>
<VersionPrefix>1.5.24</VersionPrefix>
<PackageIcon>WireMock.Net-Logo.png</PackageIcon>
<PackageProjectUrl>https://github.com/WireMock-Net/WireMock.Net</PackageProjectUrl>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
@@ -12,6 +12,7 @@
<RepositoryType>git</RepositoryType>
<RepositoryUrl>https://github.com/WireMock-Net/WireMock.Net</RepositoryUrl>
<ApplicationIcon>../../resources/WireMock.Net-Logo.ico</ApplicationIcon>
<PackageReadmeFile>PackageReadme.md</PackageReadmeFile>
<LangVersion>Latest</LangVersion>
<Nullable>enable</Nullable>
</PropertyGroup>
@@ -29,7 +30,7 @@
<ItemGroup>
<None Include="../../resources/WireMock.Net-Logo.png" Pack="true" PackagePath="" />
<!--<None Include="../../PackageReadme.md" Pack="true" PackagePath=""/>-->
<None Include="../../PackageReadme.md" Pack="true" PackagePath=""/>
</ItemGroup>
<Choose>

View File

@@ -1,6 +1,6 @@
rem https://github.com/StefH/GitHubReleaseNotes
SET version=1.5.22
SET version=1.5.24
GitHubReleaseNotes --output CHANGELOG.md --skip-empty-releases --exclude-labels question invalid doc duplicate --version %version% --token %GH_TOKEN%

55
PackageReadme.md Normal file
View File

@@ -0,0 +1,55 @@
## WireMock.Net
Lightweight Http Mocking Server for .NET, inspired by [WireMock(http://WireMock.org) from the Java landscape.
### :star: Key Features
* HTTP response stubbing, matchable on URL/Path, headers, cookies and body content patterns
* Library can be used in unit tests and integration tests
* Runs as a standalone process, as windows service, as Azure/IIS or as docker
* Configurable via a fluent C# .NET API, JSON files and JSON over HTTP
* Record/playback of stubs (proxying)
* Per-request conditional proxying
* Stateful behaviour simulation
* Response templating / transformation using Handlebars and extensions
* Can be used locally or in CI/CD scenarios
### :star: Stubbing
A core feature of WireMock.Net is the ability to return predefined HTTP responses for requests matching criteria.
See [Wiki : Stubbing](https://github.com/WireMock-Net/WireMock.Net/wiki/Stubbing).
### :star: Request Matching
WireMock.Net support advanced request-matching logic, see [Wiki : Request Matching](https://github.com/WireMock-Net/WireMock.Net/wiki/Request-Matching).
### :star: Response Templating
The response which is returned WireMock.Net can be changed using templating. This is described here [Wiki : Response Templating](https://github.com/WireMock-Net/WireMock.Net/wiki/Response-Templating).
### :star: Admin API Reference
The WireMock admin API provides functionality to define the mappings via a http interface see [Wiki : Admin API Reference](https://github.com/StefH/WireMock.Net/wiki/Admin-API-Reference).
### :star: Using
WireMock.Net can be used in several ways:
#### UnitTesting
You can use your favorite test framework and use WireMock within your tests, see
[Wiki : UnitTesting](https://github.com/StefH/WireMock.Net/wiki/Using-WireMock-in-UnitTests).
#### As a dotnet tool
It's simple to install WireMock.Net as (global) dotnet tool, see [Wiki : dotnet tool](https://github.com/StefH/WireMock.Net/wiki/WireMock-as-dotnet-tool).
#### As standalone process / console application
This is quite straight forward to launch a mock server within a console application, see [Wiki : Standalone Process](https://github.com/StefH/WireMock.Net/wiki/WireMock-as-a-standalone-process).
#### As a Windows Service
You can also run WireMock.Net as a Windows Service, follow this [WireMock-as-a-Windows-Service](https://github.com/WireMock-Net/WireMock.Net/wiki/WireMock-as-a-Windows-Service).
#### As a Web Job in Azure or application in IIS
See this link [WireMock-as-a-(Azure)-Web-App](https://github.com/WireMock-Net/WireMock.Net/wiki/WireMock-as-a-(Azure)-Web-App)
#### In a docker container
There is also a Linux and Windows-Nano container available at [hub.docker.com](https://hub.docker.com/r/sheyenrath).
For more details see also [Docker](https://github.com/WireMock-Net/WireMock.Net-docker).
#### HTTPS / SSL
More details on using HTTPS (SSL) can be found here [Wiki : HTTPS](https://github.com/WireMock-Net/WireMock.Net/wiki/Using-HTTPS-(SSL))
## :books: Documentation
For more info, see also this WIKI page: [What is WireMock.Net](https://github.com/WireMock-Net/WireMock.Net/wiki/What-Is-WireMock.Net).

View File

@@ -1,6 +1,8 @@
# 1.5.22 (08 April 2023)
- #914 #912 add excluded params to proxy mapping [feature]
- #916 Include WireMockOpenApiParser project [feature]
- #912 Feature: adding excluded params to proxy and records settings [feature]
# 1.5.24 (07 May 2023)
- #926 Fix C# mapping code generator for header names [bug]
- #927 Enrich generated code with status code [feature]
- #930 Update C# mapping code generator for WithStatusCode [feature]
- #931 Add property 'IsStartedWithAdminInterface' to 'IWireMockServer' [feature]
- #933 C# code generator improvements [feature]
The full release notes can be found here: https://github.com/WireMock-Net/WireMock.Net/blob/master/CHANGELOG.md

View File

@@ -1,24 +1,24 @@
# WireMock.Net
A C# .NET version based on [mock4net](https://github.com/alexvictoor/mock4net) which mimics the functionality from the JAVA based [WireMock.org](http://WireMock.org).
A C# .NET version based on [mock4net](https://github.com/alexvictoor/mock4net) which mimics the functionality from the JAVA based [WireMock](http://WireMock.org).
For more info, see also this WIKI page: [What is WireMock.Net](https://github.com/WireMock-Net/WireMock.Net/wiki/What-Is-WireMock.Net).
## Key Features
## :star: Key Features
* HTTP response stubbing, matchable on URL/Path, headers, cookies and body content patterns
* Library can be used in unit tests and integration tests
* Runs as a standalone process, as windows service, as Azure/IIS or as docker
* Configurable via a fluent DotNet API, JSON files and JSON over HTTP
* Configurable via a fluent C# .NET API, JSON files and JSON over HTTP
* Record/playback of stubs (proxying)
* Per-request conditional proxying
* Stateful behaviour simulation
* Response templating / transformation using Handlebars and extensions
* Can be used locally or in CI/CD scenarios
## Blogs
## :memo: Blogs
- [mStack.nl : Generate C# Code from Mapping(s)](https://mstack.nl/blog/20230201-wiremock.net-tocode/)
## Project Info
## :computer: Project Info
| | |
| --- | --- |
| ***Project*** | &nbsp; |
@@ -31,7 +31,7 @@ For more info, see also this WIKI page: [What is WireMock.Net](https://github.co
| &nbsp;&nbsp;**Sonar Bugs** | [![Sonar Bugs](https://sonarcloud.io/api/project_badges/measure?project=WireMock-Net_WireMock.Net&metric=bugs)](https://sonarcloud.io/project/issues?id=WireMock-Net_WireMock.Net&resolved=false&types=BUG) [![Sonar Code Smells](https://sonarcloud.io/api/project_badges/measure?project=WireMock-Net_WireMock.Net&metric=code_smells)](https://sonarcloud.io/project/issues?id=WireMock-Net_WireMock.Net&resolved=false&types=CODE_SMELL) |
| &nbsp;&nbsp;**Coverage** | [![Sonar Coverage](https://sonarcloud.io/api/project_badges/measure?project=WireMock-Net_WireMock.Net&metric=coverage)](https://sonarcloud.io/component_measures?id=WireMock-Net_WireMock.Net&metric=coverage) [![codecov](https://codecov.io/gh/WireMock-Net/WireMock.Net/branch/master/graph/badge.svg)](https://codecov.io/gh/WireMock-Net/WireMock.Net)|
### NuGet packages
### :package: NuGet packages
| | Official | Preview [:information_source:](https://github.com/WireMock-Net/WireMock.Net/wiki/MyGet-preview-versions) |
| - | - | - |
@@ -45,23 +45,23 @@ For more info, see also this WIKI page: [What is WireMock.Net](https://github.co
| &nbsp;&nbsp;**WireMock.Org.RestClient** | [![NuGet Badge WireMock.Org.RestClient](https://buildstats.info/nuget/WireMock.Org.RestClient)](https://www.nuget.org/packages/WireMock.Org.RestClient) | [![MyGet Badge WireMock.Org.RestClient](https://buildstats.info/myget/wiremock-net/WireMock.Org.RestClient?includePreReleases=true)](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Org.RestClient)
## Development
## :memo: Development
For the supported frameworks and build information, see [this](https://github.com/WireMock-Net/WireMock.Net/wiki/Development-Information) page.
## Stubbing
## :star: Stubbing
A core feature of WireMock.Net is the ability to return predefined HTTP responses for requests matching criteria.
See [Wiki : Stubbing](https://github.com/WireMock-Net/WireMock.Net/wiki/Stubbing).
## Request Matching
## :star: Request Matching
WireMock.Net support advanced request-matching logic, see [Wiki : Request Matching](https://github.com/WireMock-Net/WireMock.Net/wiki/Request-Matching).
## Response Templating
## :star: Response Templating
The response which is returned WireMock.Net can be changed using templating. This is described here [Wiki : Response Templating](https://github.com/WireMock-Net/WireMock.Net/wiki/Response-Templating).
## Admin API Reference
## :star: Admin API Reference
The WireMock admin API provides functionality to define the mappings via a http interface see [Wiki : Admin API Reference](https://github.com/StefH/WireMock.Net/wiki/Admin-API-Reference).
## Using
## :star: Using
WireMock.Net can be used in several ways:
### UnitTesting

View File

@@ -28,6 +28,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
.github\FUNDING.yml = .github\FUNDING.yml
Generate-ReleaseNotes.cmd = Generate-ReleaseNotes.cmd
nuget.config = nuget.config
PackageReadme.md = PackageReadme.md
PackageReleaseNotes.template = PackageReleaseNotes.template
PackageReleaseNotes.txt = PackageReleaseNotes.txt
README.md = README.md

View File

@@ -1,19 +1,18 @@
namespace WireMock.Admin.Mappings
namespace WireMock.Admin.Mappings;
/// <summary>
/// Fault Model
/// </summary>
[FluentBuilder.AutoGenerateBuilder]
public class FaultModel
{
/// <summary>
/// Fault Model
/// Gets or sets the fault. Can be null, "", NONE, EMPTY_RESPONSE or MALFORMED_RESPONSE_CHUNK.
/// </summary>
[FluentBuilder.AutoGenerateBuilder]
public class FaultModel
{
/// <summary>
/// Gets or sets the fault. Can be null, "", NONE, EMPTY_RESPONSE, MALFORMED_RESPONSE_CHUNK or RANDOM_DATA_THEN_CLOSE.
/// </summary>
public string Type { get; set; }
public string? Type { get; set; }
/// <summary>
/// Gets or sets the fault percentage.
/// </summary>
public double? Percentage { get; set; }
}
/// <summary>
/// Gets or sets the fault percentage.
/// </summary>
public double? Percentage { get; set; }
}

View File

@@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using WireMock.Models;
namespace WireMock.Admin.Mappings;
@@ -94,4 +93,9 @@ public class MappingModel
/// </example>
/// </summary>
public object? Data { get; set; }
/// <summary>
/// The probability when this request should be matched. Value is between 0 and 1. [Optional]
/// </summary>
public double? Probability { get; set; }
}

View File

@@ -1,71 +1,76 @@
namespace WireMock.Admin.Settings;
[FluentBuilder.AutoGenerateBuilder]
public class ProxyAndRecordSettingsModel
{
/// <summary>
/// The clientCertificate thumbprint or subject name fragment to use.
/// Example thumbprint : "D2DBF135A8D06ACCD0E1FAD9BFB28678DF7A9818". Example subject name: "www.google.com""
/// </summary>
public string ClientX509Certificate2ThumbprintOrSubjectName { get; set; }
/// <summary>
/// Defines the WebProxySettings.
/// </summary>
public WebProxySettingsModel WebProxySettings { get; set; }
/// <summary>
/// Proxy requests should follow redirection (30x).
/// </summary>
public bool? AllowAutoRedirect { get; set; }
/// <summary>
/// The URL to proxy.
/// </summary>
public string Url { get; set; }
/// <summary>
/// Save the mapping for each request/response to the internal Mappings.
/// </summary>
public bool SaveMapping { get; set; }
/// <summary>
/// Save the mapping for each request/response also to a file. (Note that SaveMapping must also be set to true.)
/// </summary>
public bool SaveMappingToFile { get; set; }
/// <summary>
/// Only save request/response to the internal Mappings if the status code is included in this pattern. (Note that SaveMapping must also be set to true.)
/// The pattern can contain a single value like "200", but also ranges like "2xx", "100,300,600" or "100-299,6xx" are supported.
/// </summary>
public string SaveMappingForStatusCodePattern { get; set; } = "*";
/// <summary>
/// Defines a list from headers which will be excluded from the saved mappings.
/// </summary>
public string[] ExcludedHeaders { get; set; }
/// <summary>
/// Defines a list of cookies which will be excluded from the saved mappings.
/// </summary>
public string[] ExcludedCookies { get; set; }
/// <summary>
/// Prefer the Proxy Mapping over the saved Mapping (in case SaveMapping is set to <c>true</c>).
/// </summary>
// public bool PreferProxyMapping { get; set; }
/// <summary>
/// When SaveMapping is set to <c>true</c>, this setting can be used to control the behavior of the generated request matchers for the new mapping.
/// - <c>false</c>, the default matchers will be used.
/// - <c>true</c>, the defined mappings in the request wil be used for the new mapping.
///
/// Default value is false.
/// </summary>
public bool UseDefinedRequestMatchers { get; set; }
/// <summary>
/// Append an unique GUID to the filename from the saved mapping file.
/// </summary>
public bool AppendGuidToSavedMappingFile { get; set; }
namespace WireMock.Admin.Settings;
[FluentBuilder.AutoGenerateBuilder]
public class ProxyAndRecordSettingsModel
{
/// <summary>
/// The clientCertificate thumbprint or subject name fragment to use.
/// Example thumbprint : "D2DBF135A8D06ACCD0E1FAD9BFB28678DF7A9818". Example subject name: "www.google.com""
/// </summary>
public string ClientX509Certificate2ThumbprintOrSubjectName { get; set; }
/// <summary>
/// Defines the WebProxySettings.
/// </summary>
public WebProxySettingsModel WebProxySettings { get; set; }
/// <summary>
/// Proxy requests should follow redirection (30x).
/// </summary>
public bool? AllowAutoRedirect { get; set; }
/// <summary>
/// The URL to proxy.
/// </summary>
public string Url { get; set; }
/// <summary>
/// Save the mapping for each request/response to the internal Mappings.
/// </summary>
public bool SaveMapping { get; set; }
/// <summary>
/// Save the mapping for each request/response also to a file. (Note that SaveMapping must also be set to true.)
/// </summary>
public bool SaveMappingToFile { get; set; }
/// <summary>
/// Only save request/response to the internal Mappings if the status code is included in this pattern. (Note that SaveMapping must also be set to true.)
/// The pattern can contain a single value like "200", but also ranges like "2xx", "100,300,600" or "100-299,6xx" are supported.
/// </summary>
public string SaveMappingForStatusCodePattern { get; set; } = "*";
/// <summary>
/// Defines a list from headers which will be excluded from the saved mappings.
/// </summary>
public string[] ExcludedHeaders { get; set; }
/// <summary>
/// Defines a list of cookies which will be excluded from the saved mappings.
/// </summary>
public string[] ExcludedCookies { get; set; }
/// <summary>
/// Prefer the Proxy Mapping over the saved Mapping (in case SaveMapping is set to <c>true</c>).
/// </summary>
// public bool PreferProxyMapping { get; set; }
/// <summary>
/// When SaveMapping is set to <c>true</c>, this setting can be used to control the behavior of the generated request matchers for the new mapping.
/// - <c>false</c>, the default matchers will be used.
/// - <c>true</c>, the defined mappings in the request wil be used for the new mapping.
///
/// Default value is false.
/// </summary>
public bool UseDefinedRequestMatchers { get; set; }
/// <summary>
/// Append an unique GUID to the filename from the saved mapping file.
/// </summary>
public bool AppendGuidToSavedMappingFile { get; set; }
/// <summary>
/// Defines the Replace Settings
/// </summary>
public ProxyUrlReplaceSettingsModel? ReplaceSettings { get; set; }
}

View File

@@ -0,0 +1,23 @@
namespace WireMock.Admin.Settings;
/// <summary>
/// Defines an old path param and a new path param to be replaced when proxying.
/// </summary>
[FluentBuilder.AutoGenerateBuilder]
public class ProxyUrlReplaceSettingsModel
{
/// <summary>
/// The old path value to be replaced by the new path value
/// </summary>
public string OldValue { get; set; } = null!;
/// <summary>
/// The new path value to replace the old value with
/// </summary>
public string NewValue { get; set; } = null!;
/// <summary>
/// Defines if the case should be ignore when replacing.
/// </summary>
public bool IgnoreCase { get; set; }
}

View File

@@ -1,23 +1,23 @@
namespace WireMock.ResponseBuilders
// ReSharper disable InconsistentNaming
namespace WireMock.ResponseBuilders;
/// <summary>
/// The FaultType enumeration
/// </summary>
public enum FaultType
{
/// <summary>
/// The FaultType enumeration
/// No Fault
/// </summary>
public enum FaultType
{
/// <summary>
/// No Fault
/// </summary>
NONE,
NONE,
/// <summary>
/// Return a completely empty response.
/// </summary>
EMPTY_RESPONSE,
/// <summary>
/// Return a completely empty response.
/// </summary>
EMPTY_RESPONSE,
/// <summary>
/// Send a defined status header, then garbage, then close the connection.
/// </summary>
MALFORMED_RESPONSE_CHUNK
}
/// <summary>
/// Send a defined status header, then garbage, then close the connection.
/// </summary>
MALFORMED_RESPONSE_CHUNK
}

View File

@@ -17,6 +17,11 @@ public interface IWireMockServer : IDisposable
/// </summary>
bool IsStarted { get; }
/// <summary>
/// Gets a value indicating whether this server is started with the admin interface enabled.
/// </summary>
bool IsStartedWithAdminInterface { get; }
/// <summary>
/// Gets the request logs.
/// </summary>

View File

@@ -0,0 +1,15 @@
#if NET451 || NET452 || NET46 || NET451 || NET461 || NETSTANDARD1_3 || NETSTANDARD2_0
using System.Text.RegularExpressions;
// ReSharper disable once CheckNamespace
namespace System;
internal static class StringExtensions
{
public static string Replace(this string text, string oldValue, string newValue, StringComparison stringComparison)
{
var options = stringComparison == StringComparison.OrdinalIgnoreCase ? RegexOptions.IgnoreCase : RegexOptions.None;
return Regex.Replace(text, oldValue, newValue, options);
}
}
#endif

View File

@@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using WireMock.Matchers.Request;
using WireMock.Models;
@@ -132,6 +131,11 @@ public interface IMapping
/// </summary>
object? Data { get; }
/// <summary>
/// The probability when this request should be matched. Value is between 0 and 1. [Optional]
/// </summary>
double? Probability { get; }
/// <summary>
/// ProvideResponseAsync
/// </summary>

View File

@@ -4,7 +4,7 @@ using System.Linq.Dynamic.Core;
namespace WireMock.Json;
public class DynamicPropertyWithValue : DynamicProperty
internal class DynamicPropertyWithValue : DynamicProperty
{
public object? Value { get; }

View File

@@ -75,6 +75,9 @@ public class Mapping : IMapping
/// <inheritdoc />
public object? Data { get; }
/// <inheritdoc />
public double? Probability { get; }
/// <summary>
/// Initializes a new instance of the <see cref="Mapping"/> class.
/// </summary>
@@ -95,6 +98,7 @@ 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(
Guid guid,
DateTime updatedAt,
@@ -112,7 +116,8 @@ public class Mapping : IMapping
IWebhook[]? webhooks,
bool? useWebhooksFireAndForget,
ITimeSettings? timeSettings,
object? data)
object? data,
double? probability)
{
Guid = guid;
UpdatedAt = updatedAt;
@@ -131,6 +136,7 @@ public class Mapping : IMapping
UseWebhooksFireAndForget = useWebhooksFireAndForget;
TimeSettings = timeSettings;
Data = data;
Probability = probability;
}
/// <inheritdoc cref="IMapping.ProvideResponseAsync" />

View File

@@ -69,12 +69,7 @@ public class MappingBuilder : IMappingBuilder
/// <inheritdoc />
public MappingModel[] GetMappings()
{
return GetMappingsInternal().Select(_mappingConverter.ToMappingModel).ToArray();
}
internal IMapping[] GetMappingsInternal()
{
return _options.Mappings.Values.ToArray().Where(m => !m.IsAdminInterface).ToArray();
return GetNonAdminMappings().Select(_mappingConverter.ToMappingModel).ToArray();
}
/// <inheritdoc />
@@ -86,7 +81,7 @@ public class MappingBuilder : IMappingBuilder
/// <inheritdoc />
public string? ToCSharpCode(Guid guid, MappingConverterType converterType)
{
var mapping = GetMappingsInternal().FirstOrDefault(m => m.Guid == guid);
var mapping = GetNonAdminMappings().FirstOrDefault(m => m.Guid == guid);
if (mapping is null)
{
return null;
@@ -101,7 +96,7 @@ public class MappingBuilder : IMappingBuilder
{
var sb = new StringBuilder();
bool addStart = true;
foreach (var mapping in GetMappingsInternal())
foreach (var mapping in GetNonAdminMappings())
{
sb.AppendLine(_mappingConverter.ToCSharpCode(mapping, new MappingConverterSettings { AddStart = addStart, ConverterType = converterType }));
@@ -123,7 +118,7 @@ public class MappingBuilder : IMappingBuilder
/// <inheritdoc />
public void SaveMappingsToFolder(string? folder)
{
foreach (var mapping in GetNonAdminMappings().Where(m => !m.IsAdminInterface))
foreach (var mapping in GetNonAdminMappings())
{
_mappingToFileSaver.SaveMappingToFile(mapping, folder);
}
@@ -131,7 +126,7 @@ public class MappingBuilder : IMappingBuilder
private IMapping[] GetNonAdminMappings()
{
return _options.Mappings.Values.ToArray();
return _options.Mappings.Values.Where(m => !m.IsAdminInterface).OrderBy(m => m.UpdatedAt).ToArray();
}
private void RegisterMapping(IMapping mapping, bool saveToFile)

View File

@@ -1,6 +1,5 @@
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using Stef.Validation;
namespace WireMock.Matchers.Request;
@@ -25,9 +24,9 @@ public abstract class RequestMessageCompositeMatcher : IRequestMatcher
/// </summary>
/// <param name="requestMatchers">The request matchers.</param>
/// <param name="type">The CompositeMatcherType type (Defaults to 'And')</param>
protected RequestMessageCompositeMatcher([NotNull] IEnumerable<IRequestMatcher> requestMatchers, CompositeMatcherType type = CompositeMatcherType.And)
protected RequestMessageCompositeMatcher(IEnumerable<IRequestMatcher> requestMatchers, CompositeMatcherType type = CompositeMatcherType.And)
{
Guard.NotNull(requestMatchers, nameof(requestMatchers));
Guard.NotNull(requestMatchers);
_type = type;
RequestMatchers = requestMatchers;

View File

@@ -11,6 +11,7 @@ using Microsoft.Extensions.DependencyInjection;
using Stef.Validation;
using WireMock.Logging;
using WireMock.Owin.Mappers;
using WireMock.Services;
using WireMock.Util;
namespace WireMock.Owin
@@ -66,6 +67,7 @@ namespace WireMock.Owin
{
services.AddSingleton(_wireMockMiddlewareOptions);
services.AddSingleton<IMappingMatcher, MappingMatcher>();
services.AddSingleton<IRandomizerDoubleBetween0And1, RandomizerDoubleBetween0And1>();
services.AddSingleton<IOwinRequestMapper, OwinRequestMapper>();
services.AddSingleton<IOwinResponseMapper, OwinResponseMapper>();

View File

@@ -62,11 +62,11 @@ namespace WireMock.Owin.Mappers
switch (responseMessage.FaultType)
{
case FaultType.EMPTY_RESPONSE:
bytes = IsFault(responseMessage) ? new byte[0] : GetNormalBody(responseMessage);
bytes = IsFault(responseMessage) ? EmptyArray<byte>.Value : GetNormalBody(responseMessage);
break;
case FaultType.MALFORMED_RESPONSE_CHUNK:
bytes = GetNormalBody(responseMessage) ?? new byte[0];
bytes = GetNormalBody(responseMessage) ?? EmptyArray<byte>.Value;
if (IsFault(responseMessage))
{
bytes = bytes.Take(bytes.Length / 2).Union(_randomizerBytes.Generate()).ToArray();
@@ -87,7 +87,7 @@ namespace WireMock.Owin.Mappers
case { } typeAsString when typeAsString == typeof(string):
// Note: this case will also match on null
int.TryParse(responseMessage.StatusCode as string, out int result);
int.TryParse(responseMessage.StatusCode as string, out var result);
response.StatusCode = MapStatusCode(result);
break;

View File

@@ -1,18 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using WireMock.Extensions;
using Stef.Validation;
using WireMock.Extensions;
using WireMock.Services;
namespace WireMock.Owin;
internal class MappingMatcher : IMappingMatcher
{
private readonly IWireMockMiddlewareOptions _options;
private readonly IRandomizerDoubleBetween0And1 _randomizerDoubleBetween0And1;
public MappingMatcher(IWireMockMiddlewareOptions options)
public MappingMatcher(IWireMockMiddlewareOptions options, IRandomizerDoubleBetween0And1 randomizerDoubleBetween0And1)
{
_options = Guard.NotNull(options);
_randomizerDoubleBetween0And1 = Guard.NotNull(randomizerDoubleBetween0And1);
}
public (MappingMatcherResult? Match, MappingMatcherResult? Partial) FindBestMatch(RequestMessage request)
@@ -21,7 +24,12 @@ internal class MappingMatcher : IMappingMatcher
var possibleMappings = new List<MappingMatcherResult>();
foreach (var mapping in _options.Mappings.Values.Where(m => m.TimeSettings.IsValid()))
var mappings = _options.Mappings.Values
.Where(m => m.TimeSettings.IsValid())
.Where(m => m.Probability is null || m.Probability <= _randomizerDoubleBetween0And1.Generate())
.ToArray();
foreach (var mapping in mappings)
{
try
{

View File

@@ -9,6 +9,9 @@ using JetBrains.Annotations;
using WireMock.Logging;
using WireMock.Owin.Mappers;
using Stef.Validation;
using RandomDataGenerator.FieldOptions;
using RandomDataGenerator.Randomizers;
using WireMock.Services;
namespace WireMock.Owin;
@@ -70,7 +73,7 @@ internal class OwinSelfHost : IOwinSelfHost
{
var requestMapper = new OwinRequestMapper();
var responseMapper = new OwinResponseMapper(_options);
var matcher = new MappingMatcher(_options);
var matcher = new MappingMatcher(_options, new RandomizerDoubleBetween0And1());
Action<IAppBuilder> startup = app =>
{

View File

@@ -37,7 +37,19 @@ internal class ProxyHelper
var requiredUri = new Uri(url);
// Create HttpRequestMessage
var httpRequestMessage = HttpRequestMessageHelper.Create(requestMessage, url);
var replaceSettings = proxyAndRecordSettings.ReplaceSettings;
string proxyUrl;
if (replaceSettings is not null)
{
var stringComparison = replaceSettings.IgnoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal;
proxyUrl = url.Replace(replaceSettings.OldValue, replaceSettings.NewValue, stringComparison);
}
else
{
proxyUrl = url;
}
var httpRequestMessage = HttpRequestMessageHelper.Create(requestMessage, proxyUrl);
// Call the URL
var httpResponseMessage = await client.SendAsync(httpRequestMessage, HttpCompletionOption.ResponseContentRead).ConfigureAwait(false);

View File

@@ -1,9 +1,8 @@
namespace WireMock.RequestBuilders
namespace WireMock.RequestBuilders;
/// <summary>
/// IRequestBuilder
/// </summary>
public interface IRequestBuilder : IClientIPRequestBuilder
{
/// <summary>
/// IRequestBuilder
/// </summary>
public interface IRequestBuilder : IClientIPRequestBuilder
{
}
}

View File

@@ -1,82 +1,81 @@
using System;
using System;
using System.Collections.Generic;
using WireMock.Matchers;
using WireMock.Matchers.Request;
using Stef.Validation;
namespace WireMock.RequestBuilders
namespace WireMock.RequestBuilders;
public partial class Request
{
public partial class Request
/// <inheritdoc cref="ICookiesRequestBuilder.WithCookie(string, string, MatchBehaviour)"/>
public IRequestBuilder WithCookie(string name, string pattern, MatchBehaviour matchBehaviour)
{
/// <inheritdoc cref="ICookiesRequestBuilder.WithCookie(string, string, MatchBehaviour)"/>
public IRequestBuilder WithCookie(string name, string pattern, MatchBehaviour matchBehaviour)
{
return WithCookie(name, pattern, true, matchBehaviour);
}
return WithCookie(name, pattern, true, matchBehaviour);
}
/// <inheritdoc cref="ICookiesRequestBuilder.WithCookie(string, string, bool, MatchBehaviour)"/>
public IRequestBuilder WithCookie(string name, string pattern, bool ignoreCase = true, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{
Guard.NotNull(name, nameof(name));
Guard.NotNull(pattern, nameof(pattern));
/// <inheritdoc cref="ICookiesRequestBuilder.WithCookie(string, string, bool, MatchBehaviour)"/>
public IRequestBuilder WithCookie(string name, string pattern, bool ignoreCase = true, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{
Guard.NotNull(name, nameof(name));
Guard.NotNull(pattern, nameof(pattern));
_requestMatchers.Add(new RequestMessageCookieMatcher(matchBehaviour, name, pattern, ignoreCase));
return this;
}
_requestMatchers.Add(new RequestMessageCookieMatcher(matchBehaviour, name, pattern, ignoreCase));
return this;
}
/// <inheritdoc cref="ICookiesRequestBuilder.WithCookie(string, string[], MatchBehaviour)"/>
public IRequestBuilder WithCookie(string name, string[] patterns, MatchBehaviour matchBehaviour)
{
return WithCookie(name, patterns, true, matchBehaviour);
}
/// <inheritdoc cref="ICookiesRequestBuilder.WithCookie(string, string[], MatchBehaviour)"/>
public IRequestBuilder WithCookie(string name, string[] patterns, MatchBehaviour matchBehaviour)
{
return WithCookie(name, patterns, true, matchBehaviour);
}
/// <inheritdoc cref="ICookiesRequestBuilder.WithCookie(string, string[], bool, MatchBehaviour)"/>
public IRequestBuilder WithCookie(string name, string[] patterns, bool ignoreCase = true, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{
Guard.NotNull(name, nameof(name));
Guard.NotNull(patterns, nameof(patterns));
/// <inheritdoc cref="ICookiesRequestBuilder.WithCookie(string, string[], bool, MatchBehaviour)"/>
public IRequestBuilder WithCookie(string name, string[] patterns, bool ignoreCase = true, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{
Guard.NotNull(name, nameof(name));
Guard.NotNull(patterns, nameof(patterns));
_requestMatchers.Add(new RequestMessageCookieMatcher(matchBehaviour, name, ignoreCase, patterns));
return this;
}
_requestMatchers.Add(new RequestMessageCookieMatcher(matchBehaviour, name, ignoreCase, patterns));
return this;
}
/// <inheritdoc cref="ICookiesRequestBuilder.WithCookie(string, IStringMatcher[])"/>
public IRequestBuilder WithCookie(string name, params IStringMatcher[] matchers)
{
Guard.NotNull(name, nameof(name));
Guard.NotNullOrEmpty(matchers, nameof(matchers));
/// <inheritdoc cref="ICookiesRequestBuilder.WithCookie(string, IStringMatcher[])"/>
public IRequestBuilder WithCookie(string name, params IStringMatcher[] matchers)
{
Guard.NotNull(name, nameof(name));
Guard.NotNullOrEmpty(matchers, nameof(matchers));
_requestMatchers.Add(new RequestMessageCookieMatcher(MatchBehaviour.AcceptOnMatch, name, false, matchers));
return this;
}
_requestMatchers.Add(new RequestMessageCookieMatcher(MatchBehaviour.AcceptOnMatch, name, false, matchers));
return this;
}
/// <inheritdoc cref="ICookiesRequestBuilder.WithCookie(string, bool, IStringMatcher[])"/>
public IRequestBuilder WithCookie(string name, bool ignoreCase, params IStringMatcher[] matchers)
{
Guard.NotNull(name, nameof(name));
Guard.NotNullOrEmpty(matchers, nameof(matchers));
/// <inheritdoc cref="ICookiesRequestBuilder.WithCookie(string, bool, IStringMatcher[])"/>
public IRequestBuilder WithCookie(string name, bool ignoreCase, params IStringMatcher[] matchers)
{
Guard.NotNull(name, nameof(name));
Guard.NotNullOrEmpty(matchers, nameof(matchers));
_requestMatchers.Add(new RequestMessageCookieMatcher(MatchBehaviour.AcceptOnMatch, name, ignoreCase, matchers));
return this;
}
_requestMatchers.Add(new RequestMessageCookieMatcher(MatchBehaviour.AcceptOnMatch, name, ignoreCase, matchers));
return this;
}
/// <inheritdoc cref="ICookiesRequestBuilder.WithCookie(string, IStringMatcher[])"/>
public IRequestBuilder WithCookie(string name, bool ignoreCase, MatchBehaviour matchBehaviour, params IStringMatcher[] matchers)
{
Guard.NotNull(name, nameof(name));
Guard.NotNullOrEmpty(matchers, nameof(matchers));
/// <inheritdoc cref="ICookiesRequestBuilder.WithCookie(string, IStringMatcher[])"/>
public IRequestBuilder WithCookie(string name, bool ignoreCase, MatchBehaviour matchBehaviour, params IStringMatcher[] matchers)
{
Guard.NotNull(name, nameof(name));
Guard.NotNullOrEmpty(matchers, nameof(matchers));
_requestMatchers.Add(new RequestMessageCookieMatcher(matchBehaviour, name, ignoreCase, matchers));
return this;
}
_requestMatchers.Add(new RequestMessageCookieMatcher(matchBehaviour, name, ignoreCase, matchers));
return this;
}
/// <inheritdoc cref="ICookiesRequestBuilder.WithCookie(Func{IDictionary{string, string}, bool}[])"/>
public IRequestBuilder WithCookie(params Func<IDictionary<string, string>, bool>[] funcs)
{
Guard.NotNullOrEmpty(funcs, nameof(funcs));
/// <inheritdoc cref="ICookiesRequestBuilder.WithCookie(Func{IDictionary{string, string}, bool}[])"/>
public IRequestBuilder WithCookie(params Func<IDictionary<string, string>, bool>[] funcs)
{
Guard.NotNullOrEmpty(funcs, nameof(funcs));
_requestMatchers.Add(new RequestMessageCookieMatcher(funcs));
return this;
}
_requestMatchers.Add(new RequestMessageCookieMatcher(funcs));
return this;
}
}

View File

@@ -7,86 +7,85 @@ using WireMock.Matchers.Request;
using WireMock.Types;
using Stef.Validation;
namespace WireMock.RequestBuilders
namespace WireMock.RequestBuilders;
public partial class Request
{
public partial class Request
/// <inheritdoc cref="IParamsRequestBuilder.WithParam(string, MatchBehaviour)"/>
public IRequestBuilder WithParam(string key, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{
/// <inheritdoc cref="IParamsRequestBuilder.WithParam(string, MatchBehaviour)"/>
public IRequestBuilder WithParam(string key, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{
return WithParam(key, false, matchBehaviour);
}
return WithParam(key, false, matchBehaviour);
}
/// <inheritdoc cref="IParamsRequestBuilder.WithParam(string, bool, MatchBehaviour)"/>
public IRequestBuilder WithParam(string key, bool ignoreCase, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{
Guard.NotNull(key);
/// <inheritdoc cref="IParamsRequestBuilder.WithParam(string, bool, MatchBehaviour)"/>
public IRequestBuilder WithParam(string key, bool ignoreCase, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{
Guard.NotNull(key);
_requestMatchers.Add(new RequestMessageParamMatcher(matchBehaviour, key, ignoreCase));
return this;
}
_requestMatchers.Add(new RequestMessageParamMatcher(matchBehaviour, key, ignoreCase));
return this;
}
/// <inheritdoc cref="IParamsRequestBuilder.WithParam(string, string[])"/>
public IRequestBuilder WithParam(string key, params string[] values)
{
return WithParam(key, MatchBehaviour.AcceptOnMatch, false, values);
}
/// <inheritdoc cref="IParamsRequestBuilder.WithParam(string, string[])"/>
public IRequestBuilder WithParam(string key, params string[] values)
{
return WithParam(key, MatchBehaviour.AcceptOnMatch, false, values);
}
/// <inheritdoc cref="IParamsRequestBuilder.WithParam(string, bool, string[])"/>
public IRequestBuilder WithParam(string key, bool ignoreCase, params string[] values)
{
return WithParam(key, MatchBehaviour.AcceptOnMatch, ignoreCase, values);
}
/// <inheritdoc cref="IParamsRequestBuilder.WithParam(string, bool, string[])"/>
public IRequestBuilder WithParam(string key, bool ignoreCase, params string[] values)
{
return WithParam(key, MatchBehaviour.AcceptOnMatch, ignoreCase, values);
}
/// <inheritdoc cref="IParamsRequestBuilder.WithParam(string, IStringMatcher[])"/>
public IRequestBuilder WithParam(string key, params IStringMatcher[] matchers)
{
return WithParam(key, MatchBehaviour.AcceptOnMatch, false, matchers);
}
/// <inheritdoc cref="IParamsRequestBuilder.WithParam(string, IStringMatcher[])"/>
public IRequestBuilder WithParam(string key, params IStringMatcher[] matchers)
{
return WithParam(key, MatchBehaviour.AcceptOnMatch, false, matchers);
}
/// <inheritdoc cref="IParamsRequestBuilder.WithParam(string, bool, IStringMatcher[])"/>
public IRequestBuilder WithParam(string key, bool ignoreCase, params IStringMatcher[] matchers)
{
return WithParam(key, MatchBehaviour.AcceptOnMatch, ignoreCase, matchers);
}
/// <inheritdoc cref="IParamsRequestBuilder.WithParam(string, bool, IStringMatcher[])"/>
public IRequestBuilder WithParam(string key, bool ignoreCase, params IStringMatcher[] matchers)
{
return WithParam(key, MatchBehaviour.AcceptOnMatch, ignoreCase, matchers);
}
/// <inheritdoc cref="IParamsRequestBuilder.WithParam(string, MatchBehaviour, string[])"/>
public IRequestBuilder WithParam(string key, MatchBehaviour matchBehaviour, params string[] values)
{
return WithParam(key, matchBehaviour, false, values);
}
/// <inheritdoc cref="IParamsRequestBuilder.WithParam(string, MatchBehaviour, string[])"/>
public IRequestBuilder WithParam(string key, MatchBehaviour matchBehaviour, params string[] values)
{
return WithParam(key, matchBehaviour, false, values);
}
/// <inheritdoc cref="IParamsRequestBuilder.WithParam(string, MatchBehaviour, bool, string[])"/>
public IRequestBuilder WithParam(string key, MatchBehaviour matchBehaviour, bool ignoreCase = false, params string[] values)
{
Guard.NotNull(key);
/// <inheritdoc cref="IParamsRequestBuilder.WithParam(string, MatchBehaviour, bool, string[])"/>
public IRequestBuilder WithParam(string key, MatchBehaviour matchBehaviour, bool ignoreCase = false, params string[] values)
{
Guard.NotNull(key);
_requestMatchers.Add(new RequestMessageParamMatcher(matchBehaviour, key, ignoreCase, values));
return this;
}
_requestMatchers.Add(new RequestMessageParamMatcher(matchBehaviour, key, ignoreCase, values));
return this;
}
/// <inheritdoc cref="IParamsRequestBuilder.WithParam(string, MatchBehaviour, IStringMatcher[])"/>
public IRequestBuilder WithParam(string key, MatchBehaviour matchBehaviour, params IStringMatcher[] matchers)
{
return WithParam(key, matchBehaviour, false, matchers);
}
/// <inheritdoc cref="IParamsRequestBuilder.WithParam(string, MatchBehaviour, IStringMatcher[])"/>
public IRequestBuilder WithParam(string key, MatchBehaviour matchBehaviour, params IStringMatcher[] matchers)
{
return WithParam(key, matchBehaviour, false, matchers);
}
/// <inheritdoc cref="IParamsRequestBuilder.WithParam(string, MatchBehaviour, bool, IStringMatcher[])"/>
public IRequestBuilder WithParam(string key, MatchBehaviour matchBehaviour, bool ignoreCase, params IStringMatcher[] matchers)
{
Guard.NotNull(key);
/// <inheritdoc cref="IParamsRequestBuilder.WithParam(string, MatchBehaviour, bool, IStringMatcher[])"/>
public IRequestBuilder WithParam(string key, MatchBehaviour matchBehaviour, bool ignoreCase, params IStringMatcher[] matchers)
{
Guard.NotNull(key);
_requestMatchers.Add(new RequestMessageParamMatcher(matchBehaviour, key, ignoreCase, matchers));
return this;
}
_requestMatchers.Add(new RequestMessageParamMatcher(matchBehaviour, key, ignoreCase, matchers));
return this;
}
/// <inheritdoc cref="IParamsRequestBuilder.WithParam(Func{IDictionary{string, WireMockList{string}}, bool}[])"/>
public IRequestBuilder WithParam(params Func<IDictionary<string, WireMockList<string>>, bool>[] funcs)
{
Guard.NotNullOrEmpty(funcs, nameof(funcs));
/// <inheritdoc cref="IParamsRequestBuilder.WithParam(Func{IDictionary{string, WireMockList{string}}, bool}[])"/>
public IRequestBuilder WithParam(params Func<IDictionary<string, WireMockList<string>>, bool>[] funcs)
{
Guard.NotNullOrEmpty(funcs, nameof(funcs));
_requestMatchers.Add(new RequestMessageParamMatcher(funcs));
return this;
}
_requestMatchers.Add(new RequestMessageParamMatcher(funcs));
return this;
}
}

View File

@@ -3,47 +3,46 @@ using Stef.Validation;
using WireMock.Matchers;
using WireMock.Matchers.Request;
namespace WireMock.RequestBuilders
namespace WireMock.RequestBuilders;
public partial class Request
{
public partial class Request
/// <inheritdoc />
public IRequestBuilder WithUrl(params IStringMatcher[] matchers)
{
/// <inheritdoc />
public IRequestBuilder WithUrl(params IStringMatcher[] matchers)
{
return WithUrl(MatchOperator.Or, matchers);
}
/// <inheritdoc />
public IRequestBuilder WithUrl(MatchOperator matchOperator, params IStringMatcher[] matchers)
{
Guard.NotNullOrEmpty(matchers);
_requestMatchers.Add(new RequestMessageUrlMatcher(MatchBehaviour.AcceptOnMatch, matchOperator, matchers));
return this;
}
/// <inheritdoc />
public IRequestBuilder WithUrl(params string[] urls)
{
return WithUrl(MatchOperator.Or, urls);
}
/// <inheritdoc />
public IRequestBuilder WithUrl(MatchOperator matchOperator, params string[] urls)
{
Guard.NotNullOrEmpty(urls);
_requestMatchers.Add(new RequestMessageUrlMatcher(MatchBehaviour.AcceptOnMatch, matchOperator, urls));
return this;
}
/// <inheritdoc cref="IUrlAndPathRequestBuilder.WithUrl(Func{string, bool}[])"/>
public IRequestBuilder WithUrl(params Func<string, bool>[] funcs)
{
Guard.NotNullOrEmpty(funcs);
_requestMatchers.Add(new RequestMessageUrlMatcher(funcs));
return this;
}
return WithUrl(MatchOperator.Or, matchers);
}
}
/// <inheritdoc />
public IRequestBuilder WithUrl(MatchOperator matchOperator, params IStringMatcher[] matchers)
{
Guard.NotNullOrEmpty(matchers);
_requestMatchers.Add(new RequestMessageUrlMatcher(MatchBehaviour.AcceptOnMatch, matchOperator, matchers));
return this;
}
/// <inheritdoc />
public IRequestBuilder WithUrl(params string[] urls)
{
return WithUrl(MatchOperator.Or, urls);
}
/// <inheritdoc />
public IRequestBuilder WithUrl(MatchOperator matchOperator, params string[] urls)
{
Guard.NotNullOrEmpty(urls);
_requestMatchers.Add(new RequestMessageUrlMatcher(MatchBehaviour.AcceptOnMatch, matchOperator, urls));
return this;
}
/// <inheritdoc cref="IUrlAndPathRequestBuilder.WithUrl(Func{string, bool}[])"/>
public IRequestBuilder WithUrl(params Func<string, bool>[] funcs)
{
Guard.NotNullOrEmpty(funcs);
_requestMatchers.Add(new RequestMessageUrlMatcher(funcs));
return this;
}
}

View File

@@ -63,5 +63,4 @@ public partial class Request : RequestMessageCompositeMatcher, IRequestBuilder
{
return _requestMatchers.OfType<T>().FirstOrDefault(func);
}
}

View File

@@ -1,8 +1,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Stef.Validation;
using WireMock.Admin.Mappings;
using WireMock.Constants;
@@ -14,6 +17,7 @@ using WireMock.RequestBuilders;
using WireMock.ResponseBuilders;
using WireMock.Settings;
using WireMock.Types;
using WireMock.Util;
namespace WireMock.Serialization;
@@ -33,7 +37,7 @@ internal class MappingConverter
settings ??= new MappingConverterSettings();
var request = (Request)mapping.RequestMatcher;
var response = (Response) mapping.Provider;
var response = (Response)mapping.Provider;
var clientIPMatcher = request.GetRequestMessageMatcher<RequestMessageClientIPMatcher>();
var pathMatcher = request.GetRequestMessageMatcher<RequestMessagePathMatcher>();
@@ -115,24 +119,51 @@ internal class MappingConverter
// Guid
sb.AppendLine($" .WithGuid(\"{mapping.Guid}\")");
if (mapping.Probability != null)
{
sb.AppendLine($" .WithProbability({mapping.Probability.Value.ToString(CultureInfoUtils.CultureInfoEnUS)})");
}
// Response
sb.AppendLine(" .RespondWith(Response.Create()");
if (response.ResponseMessage.StatusCode is int or string)
{
sb.AppendLine($" .WithStatusCode({JsonConvert.SerializeObject(response.ResponseMessage.StatusCode)})");
}
else if (response.ResponseMessage.StatusCode is HttpStatusCode httpStatusCode)
{
sb.AppendLine($" .WithStatusCode({(int)httpStatusCode})");
}
if (response.ResponseMessage.Headers is { })
{
foreach (var header in response.ResponseMessage.Headers)
{
sb.AppendLine($" .WithHeader(\"{header.Key})\", {ToValueArguments(header.Value.ToArray())})");
sb.AppendLine($" .WithHeader(\"{header.Key}\", {ToValueArguments(header.Value.ToArray())})");
}
}
if (response.ResponseMessage.BodyData is { })
if (response.ResponseMessage.BodyData is { } bodyData)
{
switch (response.ResponseMessage.BodyData.DetectedBodyType)
{
case BodyType.String:
case BodyType.FormUrlEncoded:
sb.AppendLine($" .WithBody(\"{response.ResponseMessage.BodyData.BodyAsString}\")");
sb.AppendLine($" .WithBody(\"{EscapeCSharpString(bodyData.BodyAsString)}\")");
break;
case BodyType.Json:
if (bodyData.BodyAsJson is string bodyStringValue)
{
sb.AppendLine($" .WithBody(\"{EscapeCSharpString(bodyStringValue)}\")");
}
else
{
var serializedBody = JsonConvert.SerializeObject(bodyData.BodyAsJson);
var deserializedBody = JToken.Parse(serializedBody);
sb.AppendLine($" .WithBodyAsJson({ConvertJsonToAnonymousObjectDefinition(deserializedBody, 2)})");
}
break;
}
}
@@ -183,6 +214,7 @@ internal class MappingConverter
WhenStateIs = mapping.ExecutionConditionState,
SetStateTo = mapping.NextState,
Data = mapping.Data,
Probability = mapping.Probability,
Request = new RequestModel
{
Headers = headerMatchers.Any() ? headerMatchers.Select(hm => new HeaderModel
@@ -427,9 +459,11 @@ internal class MappingConverter
private static string ToValueArguments(string[]? values, string defaultValue = "")
{
return values is { } ? string.Join(", ", values.Select(v => $"\"{v}\"")) : $"\"{defaultValue}\"";
return values is { } ? string.Join(", ", values.Select(v => $"\"{EscapeCSharpString(v)}\"")) : $"\"{EscapeCSharpString(defaultValue)}\"";
}
private static string? EscapeCSharpString(string? value) => value?.Replace("\"", "\\\"");
private static WebProxyModel? MapWebProxy(WebProxySettings? settings)
{
return settings != null ? new WebProxyModel
@@ -458,4 +492,49 @@ internal class MappingConverter
return newDictionary;
}
private static string ConvertJsonToAnonymousObjectDefinition(JToken token, int ind = 0)
{
return token switch
{
JArray jArray => FormatArray(jArray, ind),
JObject jObject => FormatObject(jObject, ind),
JProperty jProperty => $"{jProperty.Name} = {ConvertJsonToAnonymousObjectDefinition(jProperty.Value, ind)}",
JValue jValue => jValue.Type switch
{
JTokenType.None => "null",
JTokenType.Integer => jValue.Value?.ToString() ?? "null",
JTokenType.Float => jValue.Value?.ToString() ?? "null",
JTokenType.String => $"\"{EscapeCSharpString(jValue.Value?.ToString())}\"",
JTokenType.Boolean => jValue.Value?.ToString()?.ToLower() ?? "null",
JTokenType.Null => "null",
JTokenType.Undefined => "null",
_ => $"UNHANDLED_CASE: {jValue.Type}"
},
_ => $"UNHANDLED_CASE: {token}"
};
}
private static string FormatObject(JObject jObject, int ind)
{
var indStr = new string(' ', 4 * ind);
var indStrSub = new string(' ', 4 * (ind + 1));
var items = jObject.Properties().Select(x => ConvertJsonToAnonymousObjectDefinition(x, ind + 1));
return $"new\r\n{indStr}{{\r\n{indStrSub}{string.Join($",\r\n{indStrSub}", items)}\r\n{indStr}}}";
}
private static string FormatArray(JArray jArray, int ind)
{
var hasComplexItems = jArray.FirstOrDefault() is JObject or JArray;
var items = jArray.Select(x => ConvertJsonToAnonymousObjectDefinition(x, hasComplexItems ? ind + 1 : ind));
if (hasComplexItems)
{
var indStr = new string(' ', 4 * ind);
var indStrSub = new string(' ', 4 * (ind + 1));
return $"new []\r\n{indStr}{{\r\n{indStrSub}{string.Join($",\r\n{indStrSub}", items)}\r\n{indStr}}}";
}
return $"new [] {{ {string.Join(", ", items)} }}";
}
}

View File

@@ -189,7 +189,8 @@ internal class ProxyMappingConverter
webhooks: null,
useWebhooksFireAndForget: null,
timeSettings: null,
data: mapping?.Data
data: mapping?.Data,
probability: null
);
}
}

View File

@@ -175,5 +175,13 @@ public interface IRespondWithAProvider
/// lookup data "1"
/// </example>
/// </summary>
/// <returns>The <see cref="IRespondWithAProvider"/>.</returns>
IRespondWithAProvider WithData(object data);
/// <summary>
/// Define the probability when this request should be matched. Value is between 0 and 1.
/// </summary>
/// <param name="probability">The probability when this request should be matched. Value is between 0 and 1.</param>
/// <returns>The <see cref="IRespondWithAProvider"/>.</returns>
IRespondWithAProvider WithProbability(double probability);
}

View File

@@ -18,6 +18,12 @@ namespace WireMock.Server;
/// </summary>
internal class RespondWithAProvider : IRespondWithAProvider
{
private readonly RegistrationCallback _registrationCallback;
private readonly IRequestMatcher _requestMatcher;
private readonly WireMockServerSettings _settings;
private readonly IDateTimeUtils _dateTimeUtils;
private readonly bool _saveToFile;
private int _priority;
private string? _title;
private string? _description;
@@ -26,13 +32,8 @@ internal class RespondWithAProvider : IRespondWithAProvider
private string? _nextState;
private string? _scenario;
private int _timesInSameState = 1;
private readonly RegistrationCallback _registrationCallback;
private readonly IRequestMatcher _requestMatcher;
private readonly WireMockServerSettings _settings;
private readonly IDateTimeUtils _dateTimeUtils;
private readonly bool _saveToFile;
private bool? _useWebhookFireAndForget;
private double? _probability;
public Guid Guid { get; private set; }
@@ -92,7 +93,8 @@ internal class RespondWithAProvider : IRespondWithAProvider
Webhooks,
_useWebhookFireAndForget,
TimeSettings,
Data);
Data,
_probability);
_registrationCallback(mapping, _saveToFile);
}
@@ -285,6 +287,13 @@ internal class RespondWithAProvider : IRespondWithAProvider
return this;
}
public IRespondWithAProvider WithProbability(double probability)
{
_probability = Guard.Condition(probability, p => p is >= 0 and <= 1.0);
return this;
}
private static IWebhook InitWebhook(
string url,
string method,

View File

@@ -113,6 +113,11 @@ public partial class WireMockServer
respondProvider.WithWebhookFireAndForget(mappingModel.UseWebhooksFireAndForget.Value);
}
if (mappingModel.Probability != null)
{
respondProvider.WithProbability(mappingModel.Probability.Value);
}
var responseBuilder = InitResponseBuilder(mappingModel.Response);
respondProvider.RespondWith(responseBuilder);

View File

@@ -43,10 +43,14 @@ public partial class WireMockServer : IWireMockServer
private readonly IGuidUtils _guidUtils = new GuidUtils();
private readonly IDateTimeUtils _dateTimeUtils = new DateTimeUtils();
/// <inheritdoc cref="IWireMockServer.IsStarted" />
/// <inheritdoc />
[PublicAPI]
public bool IsStarted => _httpServer is { IsStarted: true };
/// <inheritdoc />
[PublicAPI]
public bool IsStartedWithAdminInterface => IsStarted && _settings.StartAdminInterface.GetValueOrDefault();
/// <inheritdoc />
[PublicAPI]
public List<int> Ports { get; }

View File

@@ -0,0 +1,6 @@
namespace WireMock.Services;
internal interface IRandomizerDoubleBetween0And1
{
double Generate();
}

View File

@@ -0,0 +1,14 @@
using RandomDataGenerator.FieldOptions;
using RandomDataGenerator.Randomizers;
namespace WireMock.Services;
internal class RandomizerDoubleBetween0And1 : IRandomizerDoubleBetween0And1
{
private readonly IRandomizerNumber<double> _randomizerDoubleBetween0And1 = RandomizerFactory.GetRandomizer(new FieldOptionsDouble { Min = 0, Max = 1 });
public double Generate()
{
return _randomizerDoubleBetween0And1.Generate() ?? 0;
}
}

View File

@@ -1,91 +1,97 @@
using JetBrains.Annotations;
namespace WireMock.Settings;
/// <summary>
/// ProxyAndRecordSettings
/// </summary>
public class ProxyAndRecordSettings : HttpClientSettings
{
/// <summary>
/// The URL to proxy.
/// </summary>
[PublicAPI]
public string Url { get; set; } = null!;
/// <summary>
/// Save the mapping for each request/response to the internal Mappings.
/// </summary>
[PublicAPI]
public bool SaveMapping { get; set; }
/// <summary>
/// Save the mapping for each request/response also to a file. (Note that SaveMapping must also be set to true.)
/// </summary>
[PublicAPI]
public bool SaveMappingToFile { get; set; }
/// <summary>
/// Only save request/response to the internal Mappings if the status code is included in this pattern. (Note that SaveMapping must also be set to true.)
/// The pattern can contain a single value like "200", but also ranges like "2xx", "100,300,600" or "100-299,6xx" are supported.
///
/// Deprecated : use SaveMappingSettings.
/// </summary>
[PublicAPI]
public string SaveMappingForStatusCodePattern
{
set
{
if (SaveMappingSettings is null)
{
SaveMappingSettings = new ProxySaveMappingSettings();
}
SaveMappingSettings.StatusCodePattern = value;
}
}
/// <summary>
/// Additional SaveMappingSettings.
/// </summary>
[PublicAPI]
public ProxySaveMappingSettings? SaveMappingSettings { get; set; }
/// <summary>
/// Defines a list from headers which will be excluded from the saved mappings.
/// </summary>
[PublicAPI]
public string[]? ExcludedHeaders { get; set; }
/// <summary>
/// Defines a list of params which will be excluded from the saved mappings.
/// </summary>
[PublicAPI]
public string[]? ExcludedParams { get; set; }
/// <summary>
/// Defines a list of cookies which will be excluded from the saved mappings.
/// </summary>
[PublicAPI]
public string[]? ExcludedCookies { get; set; }
/// <summary>
/// Prefer the Proxy Mapping over the saved Mapping (in case SaveMapping is set to <c>true</c>).
/// </summary>
//[PublicAPI]
//public bool PreferProxyMapping { get; set; }
/// <summary>
/// When SaveMapping is set to <c>true</c>, this setting can be used to control the behavior of the generated request matchers for the new mapping.
/// - <c>false</c>, the default matchers will be used.
/// - <c>true</c>, the defined mappings in the request wil be used for the new mapping.
///
/// Default value is false.
/// </summary>
public bool UseDefinedRequestMatchers { get; set; }
/// <summary>
/// Append an unique GUID to the filename from the saved mapping file.
/// </summary>
public bool AppendGuidToSavedMappingFile { get; set; }
using JetBrains.Annotations;
namespace WireMock.Settings;
/// <summary>
/// ProxyAndRecordSettings
/// </summary>
public class ProxyAndRecordSettings : HttpClientSettings
{
/// <summary>
/// The URL to proxy.
/// </summary>
[PublicAPI]
public string Url { get; set; } = null!;
/// <summary>
/// Save the mapping for each request/response to the internal Mappings.
/// </summary>
[PublicAPI]
public bool SaveMapping { get; set; }
/// <summary>
/// Save the mapping for each request/response also to a file. (Note that SaveMapping must also be set to true.)
/// </summary>
[PublicAPI]
public bool SaveMappingToFile { get; set; }
/// <summary>
/// Only save request/response to the internal Mappings if the status code is included in this pattern. (Note that SaveMapping must also be set to true.)
/// The pattern can contain a single value like "200", but also ranges like "2xx", "100,300,600" or "100-299,6xx" are supported.
///
/// Deprecated : use SaveMappingSettings.
/// </summary>
[PublicAPI]
public string SaveMappingForStatusCodePattern
{
set
{
if (SaveMappingSettings is null)
{
SaveMappingSettings = new ProxySaveMappingSettings();
}
SaveMappingSettings.StatusCodePattern = value;
}
}
/// <summary>
/// Additional SaveMappingSettings.
/// </summary>
[PublicAPI]
public ProxySaveMappingSettings? SaveMappingSettings { get; set; }
/// <summary>
/// Defines a list from headers which will be excluded from the saved mappings.
/// </summary>
[PublicAPI]
public string[]? ExcludedHeaders { get; set; }
/// <summary>
/// Defines a list of params which will be excluded from the saved mappings.
/// </summary>
[PublicAPI]
public string[]? ExcludedParams { get; set; }
/// <summary>
/// Defines a list of cookies which will be excluded from the saved mappings.
/// </summary>
[PublicAPI]
public string[]? ExcludedCookies { get; set; }
/// <summary>
/// Replace Settings
/// </summary>
[PublicAPI]
public ProxyUrlReplaceSettings? ReplaceSettings { get; set; }
/// <summary>
/// Prefer the Proxy Mapping over the saved Mapping (in case SaveMapping is set to <c>true</c>).
/// </summary>
//[PublicAPI]
//public bool PreferProxyMapping { get; set; }
/// <summary>
/// When SaveMapping is set to <c>true</c>, this setting can be used to control the behavior of the generated request matchers for the new mapping.
/// - <c>false</c>, the default matchers will be used.
/// - <c>true</c>, the defined mappings in the request wil be used for the new mapping.
///
/// Default value is false.
/// </summary>
public bool UseDefinedRequestMatchers { get; set; }
/// <summary>
/// Append an unique GUID to the filename from the saved mapping file.
/// </summary>
public bool AppendGuidToSavedMappingFile { get; set; }
}

View File

@@ -0,0 +1,22 @@
namespace WireMock.Settings;
/// <summary>
/// Defines an old path param and a new path param to be replaced when proxying.
/// </summary>
public class ProxyUrlReplaceSettings
{
/// <summary>
/// The old path value to be replaced by the new path value
/// </summary>
public string OldValue { get; set; } = null!;
/// <summary>
/// The new path value to replace the old value with
/// </summary>
public string NewValue { get; set; } = null!;
/// <summary>
/// Defines if the case should be ignore when replacing.
/// </summary>
public bool IgnoreCase { get; set; }
}

View File

@@ -21,7 +21,8 @@ public static class WireMockServerSettingsParser
/// <param name="logger">The logger (optional, can be null)</param>
/// <param name="settings">The parsed settings</param>
[PublicAPI]
public static bool TryParseArguments(string[] args, [NotNullWhen(true)] out WireMockServerSettings? settings, IWireMockLogger? logger = null)
public static bool TryParseArguments(string[] args, [NotNullWhen(true)] out WireMockServerSettings? settings,
IWireMockLogger? logger = null)
{
Guard.HasNoNulls(args);
@@ -70,6 +71,17 @@ public static class WireMockServerSettingsParser
settings.AcceptAnyClientCertificate = parser.GetBoolValue(nameof(WireMockServerSettings.AcceptAnyClientCertificate));
#endif
ParseLoggerSettings(settings, logger, parser);
ParsePortSettings(settings, parser);
ParseProxyAndRecordSettings(settings, parser);
ParseCertificateSettings(settings, parser);
return true;
}
private static void ParseLoggerSettings(WireMockServerSettings settings, IWireMockLogger? logger,
SimpleCommandLineParser parser)
{
var loggerType = parser.GetStringValue("WireMockLogger");
switch (loggerType)
{
@@ -86,22 +98,17 @@ public static class WireMockServerSettingsParser
{
settings.Logger = logger;
}
break;
}
}
if (parser.Contains(nameof(WireMockServerSettings.Port)))
{
settings.Port = parser.GetIntValue(nameof(WireMockServerSettings.Port));
}
else if (settings.HostingScheme is null)
{
settings.Urls = parser.GetValues("Urls", new[] { "http://*:9091/" });
}
private static void ParseProxyAndRecordSettings(WireMockServerSettings settings, SimpleCommandLineParser parser)
{
var proxyUrl = parser.GetStringValue("ProxyURL") ?? parser.GetStringValue("ProxyUrl");
if (!string.IsNullOrEmpty(proxyUrl))
{
settings.ProxyAndRecordSettings = new ProxyAndRecordSettings
var proxyAndRecordSettings = new ProxyAndRecordSettings
{
AllowAutoRedirect = parser.GetBoolValue("AllowAutoRedirect"),
ClientX509Certificate2ThumbprintOrSubjectName = parser.GetStringValue("ClientX509Certificate2ThumbprintOrSubjectName"),
@@ -121,18 +128,27 @@ public static class WireMockServerSettingsParser
}
};
string? proxyAddress = parser.GetStringValue("WebProxyAddress");
if (!string.IsNullOrEmpty(proxyAddress))
{
settings.ProxyAndRecordSettings.WebProxySettings = new WebProxySettings
{
Address = proxyAddress!,
UserName = parser.GetStringValue("WebProxyUserName"),
Password = parser.GetStringValue("WebProxyPassword")
};
}
}
ParseWebProxyAddressSettings(proxyAndRecordSettings, parser);
ParseProxyUrlReplaceSettings(proxyAndRecordSettings, parser);
settings.ProxyAndRecordSettings = proxyAndRecordSettings;
}
}
private static void ParsePortSettings(WireMockServerSettings settings, SimpleCommandLineParser parser)
{
if (parser.Contains(nameof(WireMockServerSettings.Port)))
{
settings.Port = parser.GetIntValue(nameof(WireMockServerSettings.Port));
}
else if (settings.HostingScheme is null)
{
settings.Urls = parser.GetValues("Urls", new[] { "http://*:9091/" });
}
}
private static void ParseCertificateSettings(WireMockServerSettings settings, SimpleCommandLineParser parser)
{
var certificateSettings = new WireMockCertificateSettings
{
X509StoreName = parser.GetStringValue("X509StoreName"),
@@ -145,7 +161,33 @@ public static class WireMockServerSettingsParser
{
settings.CertificateSettings = certificateSettings;
}
}
return true;
private static void ParseWebProxyAddressSettings(ProxyAndRecordSettings settings, SimpleCommandLineParser parser)
{
string? proxyAddress = parser.GetStringValue("WebProxyAddress");
if (!string.IsNullOrEmpty(proxyAddress))
{
settings.WebProxySettings = new WebProxySettings
{
Address = proxyAddress!,
UserName = parser.GetStringValue("WebProxyUserName"),
Password = parser.GetStringValue("WebProxyPassword")
};
}
}
private static void ParseProxyUrlReplaceSettings(ProxyAndRecordSettings settings, SimpleCommandLineParser parser)
{
var proxyUrlReplaceOldValue = parser.GetStringValue("ProxyUrlReplaceOldValue");
var proxyUrlReplaceNewValue = parser.GetStringValue("ProxyUrlReplaceNewValue");
if (!string.IsNullOrEmpty(proxyUrlReplaceOldValue) && proxyUrlReplaceNewValue != null)
{
settings.ReplaceSettings = new ProxyUrlReplaceSettings
{
OldValue = proxyUrlReplaceOldValue!,
NewValue = proxyUrlReplaceNewValue!
};
}
}
}

View File

@@ -5,6 +5,8 @@ namespace WireMock.Util;
internal static class CultureInfoUtils
{
public static readonly CultureInfo CultureInfoEnUS = new("en-US");
public static CultureInfo Parse(string? value)
{
if (value is null)

View File

@@ -1,29 +1,31 @@
using Nelibur.ObjectMapper;
using WireMock.Admin.Settings;
using WireMock.Settings;
namespace WireMock.Util;
internal sealed class TinyMapperUtils
{
public static TinyMapperUtils Instance { get; } = new();
private TinyMapperUtils()
{
TinyMapper.Bind<ProxyAndRecordSettings, ProxyAndRecordSettingsModel>();
TinyMapper.Bind<WebProxySettings, WebProxySettingsModel>();
TinyMapper.Bind<ProxyAndRecordSettingsModel, ProxyAndRecordSettings>();
TinyMapper.Bind<WebProxySettingsModel, WebProxySettings>();
}
public ProxyAndRecordSettingsModel? Map(ProxyAndRecordSettings? instance)
{
return instance == null ? null : TinyMapper.Map<ProxyAndRecordSettingsModel>(instance);
}
public ProxyAndRecordSettings? Map(ProxyAndRecordSettingsModel? model)
{
return model == null ? null : TinyMapper.Map<ProxyAndRecordSettings>(model);
}
using Nelibur.ObjectMapper;
using WireMock.Admin.Settings;
using WireMock.Settings;
namespace WireMock.Util;
internal sealed class TinyMapperUtils
{
public static TinyMapperUtils Instance { get; } = new();
private TinyMapperUtils()
{
TinyMapper.Bind<ProxyAndRecordSettings, ProxyAndRecordSettingsModel>();
TinyMapper.Bind<WebProxySettings, WebProxySettingsModel>();
TinyMapper.Bind<ProxyUrlReplaceSettings, ProxyUrlReplaceSettingsModel>();
TinyMapper.Bind<ProxyAndRecordSettingsModel, ProxyAndRecordSettings>();
TinyMapper.Bind<WebProxySettingsModel, WebProxySettings>();
TinyMapper.Bind<ProxyUrlReplaceSettingsModel, ProxyUrlReplaceSettings>();
}
public ProxyAndRecordSettingsModel? Map(ProxyAndRecordSettings? instance)
{
return instance == null ? null : TinyMapper.Map<ProxyAndRecordSettingsModel>(instance);
}
public ProxyAndRecordSettings? Map(ProxyAndRecordSettingsModel? model)
{
return model == null ? null : TinyMapper.Map<ProxyAndRecordSettings>(model);
}
}

View File

@@ -1,16 +1,11 @@
namespace WireMock.Org.Abstractions
namespace WireMock.Org.Abstractions;
/// <summary>
/// The fault to apply (instead of a full, valid response).
/// </summary>
public static class MappingsResponseFaultConstants
{
/// <summary>
/// The fault to apply (instead of a full, valid response).
/// </summary>
public static class MappingsResponseFaultConstants
{
public const string CONNECTIONRESETBYPEER = "CONNECTION_RESET_BY_PEER";
public const string EMPTYRESPONSE = "EMPTY_RESPONSE";
public const string EMPTYRESPONSE = "EMPTY_RESPONSE";
public const string MALFORMEDRESPONSECHUNK = "MALFORMED_RESPONSE_CHUNK";
public const string RANDOMDATATHENCLOSE = "RANDOM_DATA_THEN_CLOSE";
}
}
public const string MALFORMEDRESPONSECHUNK = "MALFORMED_RESPONSE_CHUNK";
}

View File

@@ -6,6 +6,7 @@ using WireMock.Logging;
using WireMock.Matchers.Request;
using WireMock.Models;
using WireMock.Owin;
using WireMock.Services;
using WireMock.Util;
using Xunit;
@@ -14,6 +15,8 @@ namespace WireMock.Net.Tests.Owin;
public class MappingMatcherTests
{
private readonly Mock<IWireMockMiddlewareOptions> _optionsMock;
private readonly Mock<IRandomizerDoubleBetween0And1> _randomizerDoubleBetween0And1Mock;
private readonly MappingMatcher _sut;
public MappingMatcherTests()
@@ -29,7 +32,10 @@ public class MappingMatcherTests
loggerMock.Setup(l => l.Error(It.IsAny<string>()));
_optionsMock.Setup(o => o.Logger).Returns(loggerMock.Object);
_sut = new MappingMatcher(_optionsMock.Object);
_randomizerDoubleBetween0And1Mock = new Mock<IRandomizerDoubleBetween0And1>();
_randomizerDoubleBetween0And1Mock.Setup(r => r.Generate()).Returns(0.0);
_sut = new MappingMatcher(_optionsMock.Object, _randomizerDoubleBetween0And1Mock.Object);
}
[Fact]
@@ -76,8 +82,8 @@ public class MappingMatcherTests
var guid2 = Guid.Parse("00000000-0000-0000-0000-000000000002");
var mappings = InitMappings
(
(guid1, new[] { 0.1 }),
(guid2, new[] { 1.0 })
(guid1, new[] { 0.1 }, null),
(guid2, new[] { 1.0 }, null)
);
_optionsMock.Setup(o => o.Mappings).Returns(mappings);
@@ -104,8 +110,8 @@ public class MappingMatcherTests
var guid2 = Guid.Parse("00000000-0000-0000-0000-000000000002");
var mappings = InitMappings
(
(guid1, new[] { 0.1 }),
(guid2, new[] { 0.9 })
(guid1, new[] { 0.1 }, null),
(guid2, new[] { 0.9 }, null)
);
_optionsMock.Setup(o => o.Mappings).Returns(mappings);
@@ -131,8 +137,8 @@ public class MappingMatcherTests
_optionsMock.SetupGet(o => o.AllowPartialMapping).Returns(true);
var mappings = InitMappings(
(guid1, new[] { 0.1 }),
(guid2, new[] { 0.9 })
(guid1, new[] { 0.1 }, null),
(guid2, new[] { 0.9 }, null)
);
_optionsMock.Setup(o => o.Mappings).Returns(mappings);
@@ -158,8 +164,8 @@ public class MappingMatcherTests
var guid1 = Guid.Parse("00000000-0000-0000-0000-000000000001");
var guid2 = Guid.Parse("00000000-0000-0000-0000-000000000002");
var mappings = InitMappings(
(guid1, new[] { 1.0 }),
(guid2, new[] { 1.0, 1.0 })
(guid1, new[] { 1.0 }, null),
(guid2, new[] { 1.0, 1.0 }, null)
);
_optionsMock.Setup(o => o.Mappings).Returns(mappings);
@@ -178,7 +184,31 @@ public class MappingMatcherTests
result.Partial.RequestMatchResult.AverageTotalScore.Should().Be(1.0);
}
private static ConcurrentDictionary<Guid, IMapping> InitMappings(params (Guid guid, double[] scores)[] matches)
[Fact]
public void MappingMatcher_FindBestMatch_WhenProbabilityFailsFirst_ShouldReturnSecondMatch()
{
// Assign
var guid1 = Guid.Parse("00000000-0000-0000-0000-000000000001");
var guid2 = Guid.Parse("00000000-0000-0000-0000-000000000002");
var mappings = InitMappings
(
(guid1, new[] { 1.0 }, 1.0),
(guid2, new[] { 1.0 }, null)
);
_optionsMock.Setup(o => o.Mappings).Returns(mappings);
var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "GET", "::1");
// Act
var result = _sut.FindBestMatch(request);
// Assert
result.Match.Should().NotBeNull();
result.Match!.Mapping.Guid.Should().Be(guid2);
result.Match.RequestMatchResult.AverageTotalScore.Should().Be(1.0);
}
private static ConcurrentDictionary<Guid, IMapping> InitMappings(params (Guid guid, double[] scores, double? probability)[] matches)
{
var mappings = new ConcurrentDictionary<Guid, IMapping>();
@@ -193,6 +223,8 @@ public class MappingMatcherTests
requestMatchResult.AddScore(typeof(object), score);
}
mappingMock.SetupGet(m => m.Probability).Returns(match.probability);
mappingMock.Setup(m => m.GetRequestMatchResult(It.IsAny<RequestMessage>(), It.IsAny<string>())).Returns(requestMatchResult);
mappings.TryAdd(match.guid, mappingMock.Object);

View File

@@ -177,7 +177,7 @@ public class WireMockMiddlewareTests
_mappingMock.SetupGet(m => m.Provider).Returns(responseBuilder);
_mappingMock.SetupGet(m => m.Settings).Returns(settings);
var newMappingFromProxy = new Mapping(Guid.NewGuid(), _updatedAt, string.Empty, string.Empty, null, settings, Request.Create(), Response.Create(), 0, null, null, null, null, null, false, null, null);
var newMappingFromProxy = new Mapping(Guid.NewGuid(), _updatedAt, string.Empty, string.Empty, null, settings, Request.Create(), Response.Create(), 0, null, null, null, null, null, false, null, null, null);
_mappingMock.Setup(m => m.ProvideResponseAsync(It.IsAny<RequestMessage>())).ReturnsAsync((new ResponseMessage(), newMappingFromProxy));
var requestBuilder = Request.Create().UsingAnyMethod();
@@ -231,7 +231,7 @@ public class WireMockMiddlewareTests
_mappingMock.SetupGet(m => m.Provider).Returns(responseBuilder);
_mappingMock.SetupGet(m => m.Settings).Returns(settings);
var newMappingFromProxy = new Mapping(Guid.NewGuid(), _updatedAt, "my-title", "my-description", null, settings, Request.Create(), Response.Create(), 0, null, null, null, null, null, false, null, data: null);
var newMappingFromProxy = new Mapping(Guid.NewGuid(), _updatedAt, "my-title", "my-description", null, settings, Request.Create(), Response.Create(), 0, null, null, null, null, null, false, null, data: null, probability: null);
_mappingMock.Setup(m => m.ProvideResponseAsync(It.IsAny<RequestMessage>())).ReturnsAsync((new ResponseMessage(), newMappingFromProxy));
var requestBuilder = Request.Create().UsingAnyMethod();

View File

@@ -136,7 +136,8 @@ public partial class MappingConverterTests
null,
false,
null,
data: null
data: null,
probability: 0.3
);
}
}

View File

@@ -9,8 +9,9 @@
.WithBody("b")
)
.WithGuid("8e7b9ab7-e18e-4502-8bc9-11e6679811cc")
.WithProbability(0.3)
.RespondWith(Response.Create()
.WithHeader("Keep-Alive)", "test")
.WithHeader("Keep-Alive", "test")
.WithBody("bbb")
.WithDelay(12345)
.WithTransformer()

View File

@@ -10,8 +10,9 @@ builder
.WithBody("b")
)
.WithGuid("8e7b9ab7-e18e-4502-8bc9-11e6679811cc")
.WithProbability(0.3)
.RespondWith(Response.Create()
.WithHeader("Keep-Alive)", "test")
.WithHeader("Keep-Alive", "test")
.WithBody("bbb")
.WithDelay(12345)
.WithTransformer()

View File

@@ -9,8 +9,9 @@
.WithBody("b")
)
.WithGuid("8e7b9ab7-e18e-4502-8bc9-11e6679811cc")
.WithProbability(0.3)
.RespondWith(Response.Create()
.WithHeader("Keep-Alive)", "test")
.WithHeader("Keep-Alive", "test")
.WithBody("bbb")
.WithDelay(12345)
.WithTransformer()

View File

@@ -10,8 +10,9 @@ server
.WithBody("b")
)
.WithGuid("8e7b9ab7-e18e-4502-8bc9-11e6679811cc")
.WithProbability(0.3)
.RespondWith(Response.Create()
.WithHeader("Keep-Alive)", "test")
.WithHeader("Keep-Alive", "test")
.WithBody("bbb")
.WithDelay(12345)
.WithTransformer()

View File

@@ -0,0 +1,11 @@
{
Guid: Guid_1,
UpdatedAt: DateTime_1,
Title: ,
Description: ,
Priority: 42,
Request: {},
Response: {},
UseWebhooksFireAndForget: false,
Probability: 0.4
}

View File

@@ -57,7 +57,7 @@ public partial class MappingConverterTests
}
}
};
var mapping = new Mapping(_guid, _updatedAt, string.Empty, string.Empty, null, _settings, request, response, 0, null, null, null, null, webhooks, false, null, data: null);
var mapping = new Mapping(_guid, _updatedAt, string.Empty, string.Empty, null, _settings, request, response, 0, null, null, null, null, webhooks, false, null, data: null, probability: null);
// Act
var model = _sut.ToMappingModel(mapping);
@@ -130,7 +130,7 @@ public partial class MappingConverterTests
}
}
};
var mapping = new Mapping(_guid, _updatedAt, string.Empty, string.Empty, null, _settings, request, response, 0, null, null, null, null, webhooks, true, null, data: null);
var mapping = new Mapping(_guid, _updatedAt, string.Empty, string.Empty, null, _settings, request, response, 0, null, null, null, null, webhooks, true, null, data: null, probability: null);
// Act
var model = _sut.ToMappingModel(mapping);
@@ -168,7 +168,7 @@ public partial class MappingConverterTests
var description = "my-description";
var request = Request.Create();
var response = Response.Create();
var mapping = new Mapping(_guid, _updatedAt, title, description, null, _settings, request, response, 0, null, null, null, null, null, false, null, data: null);
var mapping = new Mapping(_guid, _updatedAt, title, description, null, _settings, request, response, 0, null, null, null, null, null, false, null, data: null, probability: null);
// Act
var model = _sut.ToMappingModel(mapping);
@@ -188,7 +188,7 @@ public partial class MappingConverterTests
// Assign
var request = Request.Create();
var response = Response.Create().WithBodyAsJson(new { x = "x" }).WithTransformer();
var mapping = new Mapping(_guid, _updatedAt, string.Empty, string.Empty, null, _settings, request, response, 42, null, null, null, null, null, false, null, data: null);
var mapping = new Mapping(_guid, _updatedAt, string.Empty, string.Empty, null, _settings, request, response, 42, null, null, null, null, null, false, null, data: null, probability: null);
// Act
var model = _sut.ToMappingModel(mapping);
@@ -217,7 +217,7 @@ public partial class MappingConverterTests
End = end,
TTL = ttl
};
var mapping = new Mapping(_guid, _updatedAt, string.Empty, string.Empty, null, _settings, request, response, 42, null, null, null, null, null, false, timeSettings, data: null);
var mapping = new Mapping(_guid, _updatedAt, string.Empty, string.Empty, null, _settings, request, response, 42, null, null, null, null, null, false, timeSettings, data: null, probability: null);
// Act
var model = _sut.ToMappingModel(mapping);
@@ -248,7 +248,7 @@ public partial class MappingConverterTests
{
var request = Request.Create();
var response = Response.Create().WithDelay(test.Delay);
var mapping = new Mapping(Guid.NewGuid(), _updatedAt, string.Empty, string.Empty, string.Empty, _settings, request, response, 42, null, null, null, null, null, false, null, data: null);
var mapping = new Mapping(Guid.NewGuid(), _updatedAt, string.Empty, string.Empty, string.Empty, _settings, request, response, 42, null, null, null, null, null, false, null, data: null, probability: null);
// Act
var model = _sut.ToMappingModel(mapping);
@@ -266,7 +266,7 @@ public partial class MappingConverterTests
var delay = 1000;
var request = Request.Create();
var response = Response.Create().WithDelay(delay);
var mapping = new Mapping(_guid, _updatedAt, string.Empty, string.Empty, null, _settings, request, response, 42, null, null, null, null, null, false, null, data: null);
var mapping = new Mapping(_guid, _updatedAt, string.Empty, string.Empty, null, _settings, request, response, 42, null, null, null, null, null, false, null, data: null, probability: null);
// Act
var model = _sut.ToMappingModel(mapping);
@@ -286,7 +286,7 @@ public partial class MappingConverterTests
int minimumDelay = 1000;
var request = Request.Create();
var response = Response.Create().WithRandomDelay(minimumDelay);
var mapping = new Mapping(_guid, _updatedAt, string.Empty, string.Empty, null, _settings, request, response, 42, null, null, null, null, null, false, null, data: null);
var mapping = new Mapping(_guid, _updatedAt, string.Empty, string.Empty, null, _settings, request, response, 42, null, null, null, null, null, false, null, data: null, probability: null);
// Act
var model = _sut.ToMappingModel(mapping);
@@ -309,7 +309,7 @@ public partial class MappingConverterTests
int maximumDelay = 2000;
var request = Request.Create();
var response = Response.Create().WithRandomDelay(minimumDelay, maximumDelay);
var mapping = new Mapping(_guid, _updatedAt, string.Empty, string.Empty, null, _settings, request, response, 42, null, null, null, null, null, false, null, data: null);
var mapping = new Mapping(_guid, _updatedAt, string.Empty, string.Empty, null, _settings, request, response, 42, null, null, null, null, null, false, null, data: null, probability: null);
// Act
var model = _sut.ToMappingModel(mapping);
@@ -323,5 +323,25 @@ public partial class MappingConverterTests
// Verify
return Verifier.Verify(model);
}
[Fact]
public Task ToMappingModel_WithProbability_ReturnsCorrectModel()
{
// Assign
double probability = 0.4;
var request = Request.Create();
var response = Response.Create();
var mapping = new Mapping(_guid, _updatedAt, string.Empty, string.Empty, null, _settings, request, response, 42, null, null, null, null, null, false, null, data: null, probability: probability);
// Act
var model = _sut.ToMappingModel(mapping);
// Assert
model.Should().NotBeNull();
model.Probability.Should().Be(0.4);
// Verify
return Verifier.Verify(model);
}
}
#endif

View File

@@ -7,5 +7,6 @@ server
)
.WithGuid("90356dba-b36c-469a-a17e-669cd84f1f05")
.RespondWith(Response.Create()
.WithStatusCode(200)
.WithBody("1")
);

View File

@@ -7,18 +7,59 @@ server
)
.WithGuid("90356dba-b36c-469a-a17e-669cd84f1f05")
.RespondWith(Response.Create()
.WithStatusCode(200)
.WithBody("1")
);
server
.Given(Request.Create()
.UsingMethod("GET")
.UsingMethod("POST")
.WithPath("/foo2")
.WithParam("p2", "abc")
.WithHeader("h1", "W/\"234f2q3r\"", true)
)
.WithGuid("1b731398-4a5b-457f-a6e3-d65e541c428f")
.RespondWith(Response.Create()
.WithHeader("hk)", "hv")
.WithStatusCode("201")
.WithHeader("hk", "hv")
.WithHeader("ETag", "W/\"168d8e\"")
.WithBody("2")
);
server
.Given(Request.Create()
.UsingMethod("DELETE")
.WithUrl("https://localhost/test")
)
.WithGuid("f74fd144-df53-404f-8e35-da22a640bd5f")
.RespondWith(Response.Create()
.WithStatusCode(208)
.WithBodyAsJson(new
{
a = 1,
b = 1.2,
d = true,
e = false,
f = new [] { 1, 2, 3, 4 },
g = new
{
z1 = 1,
z2 = 2,
z3 = new [] { "a", "b", "c" },
z4 = new []
{
new
{
a = 1,
b = 2
},
new
{
a = 2,
b = 3
}
}
}
})
);

View File

@@ -2,6 +2,7 @@
using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
@@ -717,6 +718,7 @@ public class WireMockAdminApiTests
// Arrange
var guid1 = Guid.Parse("90356dba-b36c-469a-a17e-669cd84f1f05");
var guid2 = Guid.Parse("1b731398-4a5b-457f-a6e3-d65e541c428f");
var guid3 = Guid.Parse("f74fd144-df53-404f-8e35-da22a640bd5f");
var server = WireMockServer.StartWithAdminInterface();
server
@@ -738,21 +740,36 @@ public class WireMockAdminApiTests
Request.Create()
.WithPath("/foo2")
.WithParam("p2", "abc")
.UsingGet()
.WithHeader("h1", "W/\"234f2q3r\"")
.UsingPost()
)
.WithGuid(guid2)
.RespondWith(
Response.Create()
.WithStatusCode(201)
.WithStatusCode("201")
.WithHeader("hk", "hv")
.WithHeader("ETag", "W/\"168d8e\"")
.WithBody("2")
);
server
.Given(
Request.Create()
.WithUrl("https://localhost/test")
.UsingDelete()
)
.WithGuid(guid3)
.RespondWith(
Response.Create()
.WithStatusCode(HttpStatusCode.AlreadyReported)
.WithBodyAsJson(new { a = 1, b=1.2, d=true, e=false, f=new[]{1,2,3,4}, g= new{z1=1, z2=2, z3=new []{"a","b","c"}, z4=new[]{new {a=1, b=2},new {a=2, b=3}}} })
);
// Act
var api = RestClient.For<IWireMockAdminApi>(server.Url);
var mappings = await api.GetMappingsAsync().ConfigureAwait(false);
mappings.Should().HaveCount(2);
mappings.Should().HaveCount(3);
var code = await api.GetMappingsCodeAsync().ConfigureAwait(false);

View File

@@ -570,6 +570,52 @@ public class WireMockServerProxyTests
Check.That(matchers).Contains("name");
}
[Fact]
public async Task WireMockServer_Proxy_Should_replace_old_path_value_with_new_path_value_in_replace_settings()
{
// Assign
var replaceSettings = new ProxyUrlReplaceSettings
{
OldValue = "value-to-replace",
NewValue = "new-value"
};
string path = $"/prx_{Guid.NewGuid()}";
var serverForProxyForwarding = WireMockServer.Start();
serverForProxyForwarding
.Given(Request.Create().WithPath($"/{replaceSettings.NewValue}{path}"))
.RespondWith(Response.Create());
var settings = new WireMockServerSettings
{
ProxyAndRecordSettings = new ProxyAndRecordSettings
{
Url = serverForProxyForwarding.Urls[0],
SaveMapping = true,
SaveMappingToFile = false,
ReplaceSettings = replaceSettings
}
};
var server = WireMockServer.Start(settings);
var defaultMapping = server.Mappings.First();
// Act
var requestMessage = new HttpRequestMessage
{
Method = HttpMethod.Post,
RequestUri = new Uri($"{server.Urls[0]}/{replaceSettings.OldValue}{path}"),
Content = new StringContent("stringContent")
};
var handler = new HttpClientHandler();
await new HttpClient(handler).SendAsync(requestMessage).ConfigureAwait(false);
// Assert
var mapping = serverForProxyForwarding.Mappings.FirstOrDefault(m => m.Guid != defaultMapping.Guid);
var score = mapping.RequestMatcher.GetMatchingScore(serverForProxyForwarding.LogEntries.First().RequestMessage,
new RequestMatchResult());
Check.That(score).IsEqualTo(1.0);
}
[Fact]
public async Task WireMockServer_Proxy_Should_preserve_content_header_in_proxied_request_with_empty_content()
{
@@ -701,7 +747,7 @@ public class WireMockServerProxyTests
/// <summary>
/// Send some binary content in a request through the proxy and check that the same content
/// arrived at the target. As example a JPEG/JIFF header is used, which is not representable
/// in UTF8 and breaks if it is not treated as binary content.
/// in UTF8 and breaks if it is not treated as binary content.
/// </summary>
[Fact]
public async Task WireMockServer_Proxy_Should_preserve_binary_request_content()

View File

@@ -0,0 +1,37 @@
using System;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using WireMock.RequestBuilders;
using WireMock.ResponseBuilders;
using WireMock.Server;
using Xunit;
namespace WireMock.Net.Tests;
public partial class WireMockServerTests
{
[Fact]
public async Task WireMockServer_WithProbability()
{
// Arrange
var server = WireMockServer.Start();
server
.Given(Request.Create().UsingGet().WithPath("/foo"))
.WithProbability(0.5)
.RespondWith(Response.Create().WithStatusCode(200));
server
.Given(Request.Create().UsingGet().WithPath("/foo"))
.RespondWith(Response.Create().WithStatusCode(500));
// Act
var requestUri = new Uri($"http://localhost:{server.Port}/foo");
var response = await server.CreateClient().GetAsync(requestUri).ConfigureAwait(false);
// Assert
Assert.True(new[] { HttpStatusCode.OK, HttpStatusCode.InternalServerError }.Contains(response.StatusCode));
server.Stop();
}
}

View File

@@ -34,6 +34,32 @@ public partial class WireMockServerTests
_testOutputHelper = testOutputHelper;
}
[Fact]
public void WireMockServer_Start()
{
// Act
var server = WireMockServer.Start();
// Assert
server.IsStarted.Should().BeTrue();
server.IsStartedWithAdminInterface.Should().BeFalse();
server.Stop();
}
[Fact]
public void WireMockServer_StartWithAdminInterface()
{
// Act
var server = WireMockServer.StartWithAdminInterface();
// Assert
server.IsStarted.Should().BeTrue();
server.IsStartedWithAdminInterface.Should().BeTrue();
server.Stop();
}
[Fact]
public async Task WireMockServer_Should_Reset_LogEntries()
{