Compare commits

..

87 Commits

Author SHA1 Message Date
Stef Heyenrath
02803562c6 1.0.1.2 2017-02-27 11:16:34 +01:00
Stef Heyenrath
7c51b2e73c Merge pull request #24 from sbebrys/Body_Encoding
Body Encoding
2017-02-25 09:57:32 +01:00
Sebastian Bebrys
6513ac9de8 Body Encoding - admin interface 2017-02-23 14:45:43 +01:00
Sebastian Bebrys
c38373eb1f Body Encoding 2017-02-23 10:39:44 +01:00
Stef Heyenrath
3112054f59 /__admin/requests/find 2017-02-15 18:04:18 +01:00
Stef Heyenrath
571f434b9a Score 2017-02-14 21:38:44 +01:00
Stef Heyenrath
b25444d083 __admin/settings 2017-02-13 19:34:04 +01:00
Stef Heyenrath
2944b5392a update tests 2017-02-13 18:18:26 +01:00
Stef Heyenrath
7fa0fbf7da /reset 2017-02-12 10:10:46 +01:00
Stef Heyenrath
b65cf9b61b \__admin\mappings\save (issue #21) 2017-02-12 09:48:51 +01:00
Stef Heyenrath
bb35f55bbb add BASIC Auth (#22) 2017-02-10 20:04:06 +01:00
Stef Heyenrath
ba86d81a17 1.0.1.1 (issue #21) 2017-02-10 11:09:28 +01:00
Stef Heyenrath
4d2205dbd5 Initial support for static mapping files. 2017-02-10 09:15:33 +01:00
Stef Heyenrath
e6c951bb37 Downgrade JSON to 6.0.8 for example project. 2017-02-10 08:40:51 +01:00
Stef Heyenrath
91a76f50a2 issue #21 2017-02-10 08:26:52 +01:00
Stef Heyenrath
8567d28ac3 readme 2017-02-09 08:36:58 +01:00
Stef Heyenrath
abde3cd83f update some tests 2017-02-09 07:26:46 +01:00
Stef Heyenrath
4919e32264 support multiple patterns 2017-02-08 19:55:45 +01:00
Stef Heyenrath
a9a46057be Added some more Checks 2017-02-07 17:13:56 +01:00
Stef Heyenrath
ee77a5edac Update CommandLineArgumentsParser 2017-02-07 16:53:07 +01:00
Stef Heyenrath
3ba29e580c Fix partialMappings 2017-02-06 07:47:34 +01:00
Stef Heyenrath
8ecf7e414a LogEntryModel (Url -> Path) 2017-02-06 07:35:56 +01:00
Stef Heyenrath
f7628fc51e Map : ExactMatcher 2017-02-06 07:25:12 +01:00
Stef Heyenrath
eb463cd6a9 update dependencies 2017-02-05 20:08:15 +01:00
Stef Heyenrath
63536c2ed0 Update WireMock.Net.StandAlone 2017-02-05 19:19:44 +01:00
Stef Heyenrath
51cc74ad20 added WireMock.Net.StandAlone 2017-02-05 18:43:01 +01:00
Stef Heyenrath
c987a59ca8 Add ExactMatcher 2017-02-05 18:04:48 +01:00
Stef Heyenrath
1adc7340d1 . 2017-02-05 11:49:04 +01:00
Stef Heyenrath
8e1ceeb6b2 Fix WildcardMatcher 2017-02-05 11:35:13 +01:00
Stef Heyenrath
4f000f8880 code-coverage 2017-02-05 10:54:27 +01:00
Stef Heyenrath
0d83a4d8a1 IsMatch -> GetMatchingScore 2017-02-05 10:42:57 +01:00
Stef Heyenrath
2bac30b442 readme 2017-02-05 10:31:23 +01:00
Stef Heyenrath
e1b557f875 Fix tests 2017-02-05 10:30:17 +01:00
Stef Heyenrath
8231b25805 Partial 2017-02-05 10:25:19 +01:00
Stef Heyenrath
44f00cb9fa PartialMapping : SimMetrics.Net 2017-02-04 21:32:45 +01:00
Stef Heyenrath
ec2d105db2 Partial matching 2017-02-04 17:30:16 +01:00
Stef Heyenrath
9b99a7f26b Simplify WildcardMatcher 2017-02-04 15:55:11 +01:00
Stef Heyenrath
841da80291 Started with PartialMapping ... 2017-02-03 16:35:37 +01:00
Stef Heyenrath
d1aa517f99 UrlModel + Funcs 2017-02-03 13:54:19 +01:00
Stef Heyenrath
84901ab1e4 AtPriority (#16) 2017-01-31 09:28:04 +01:00
Stef Heyenrath
8a4e5b5790 Fix unit-tests 2017-01-31 07:36:51 +01:00
Stef Heyenrath
de914ef24d Listen on more ip-address/ports (#18) 2017-01-30 22:12:58 +01:00
Stef Heyenrath
517304b999 Delay 2017-01-30 21:51:54 +01:00
Stef Heyenrath
9a413a0940 DELETE /__admin/requests/{guid} 2017-01-30 14:28:17 +01:00
Stef Heyenrath
7f790a5861 GET /__admin/request/{guid} 2017-01-30 12:47:15 +01:00
Stef Heyenrath
0a2cd88b5c Fix GUID for LogEntry 2017-01-30 11:34:11 +01:00
Stef Heyenrath
f6b8986dd6 DELETE /__admin/requests 2017-01-30 08:20:56 +01:00
Stef Heyenrath
dee7c1bd18 DELETE /__admin/mappings/{guid} 2017-01-29 18:07:16 +01:00
Stef Heyenrath
5db727549c DELETE /__admin/mappings 2017-01-29 17:50:12 +01:00
Stef Heyenrath
edb1354986 PUT to /__admin/mappings/{guid} 2017-01-29 17:42:33 +01:00
Stef Heyenrath
fddc85f48d path <> url 2017-01-29 14:24:48 +01:00
Stef Heyenrath
7fe0f41a9d Update Readme 2017-01-29 13:55:22 +01:00
Stef Heyenrath
4fd05b0fd4 Added GET /__admin/mappings/{guid} 2017-01-29 13:34:52 +01:00
Stef Heyenrath
1f33e6a671 Add a new stub mapping 2017-01-27 21:19:30 +01:00
Stef Heyenrath
a334974bef Implement "/__admin/requests" 2017-01-26 12:00:30 +01:00
Stef Heyenrath
1bf543a91e Add Admin to readme 2017-01-25 20:21:24 +01:00
Stef Heyenrath
f56251a483 Fix WireMock.Net.dll 2017-01-24 22:44:46 +01:00
Stef Heyenrath
b036de5442 Fix <Reference Include="WireMock.Net"> 2017-01-24 22:35:45 +01:00
Stef Heyenrath
5761cf560e Fix appveyor after rename 2017-01-24 22:29:07 +01:00
Stef Heyenrath
3cb1a6d2e1 Rename to WireMock.Net 2017-01-24 22:28:08 +01:00
Stef Heyenrath
4809fed513 Downgrade Newtonsoft.Json 2017-01-24 22:19:31 +01:00
Stef Heyenrath
3f84ba8b20 GET "/__admin/mappings" 2017-01-24 22:06:25 +01:00
Stef Heyenrath
45aa83ee91 Cookie #9 2017-01-23 19:43:30 +01:00
Stef Heyenrath
363d96e615 #3 2017-01-23 17:37:06 +01:00
Stef Heyenrath
b4d5eb18d4 WireMockList 2017-01-23 16:00:33 +01:00
Stef Heyenrath
32f9171d01 WildcardMatcher and other fixes 2017-01-20 23:06:59 +01:00
Stef Heyenrath
f4ce2dbeb3 get_routes (#12) 2017-01-20 21:25:18 +01:00
Stef Heyenrath
6c16d45256 Add unit test for Response Handlebars 2017-01-20 17:52:49 +01:00
Stef Heyenrath
1b2d20fd69 Refactor fluent interfaces 2017-01-20 15:01:06 +01:00
Stef Heyenrath
0d046daac5 WithBodyAsBase64 (#14) 2017-01-20 13:04:58 +01:00
Stef Heyenrath
e2552f03b9 Move some classes and restructure. 2017-01-20 12:07:29 +01:00
Stef Heyenrath
847745c256 Added try-catch for return Matchers 2017-01-19 23:37:02 +01:00
Stef Heyenrath
68f3a2ff76 Handle Exception (#13) 2017-01-19 23:33:40 +01:00
Stef Heyenrath
95b93e80ce JsonPathMatcher (#6) 2017-01-19 22:23:10 +01:00
Stef Heyenrath
72335d48d6 Add XmlPath2 / RegEx matchers
Solves issue #5
2017-01-19 21:51:22 +01:00
Stef Heyenrath
1b2e5368a9 * added byte[] body
* updated readme
2017-01-19 14:57:12 +01:00
Stef Heyenrath
8dfcf7288f Handlebars #4 2017-01-18 22:47:40 +01:00
Stef Heyenrath
1d2c7fcf79 appveyor : dotnet pack -c %CONFIGURATION% 2017-01-18 21:24:00 +01:00
Stef Heyenrath
07488cd66b appveyor -PackagesDirectory packages 2017-01-18 21:12:12 +01:00
Stef Heyenrath
7884ee1eda appveyor : nuget restore 2017-01-18 21:06:04 +01:00
Stef Heyenrath
8f4216eb3f appveyor : nuget restore 2017-01-18 21:00:51 +01:00
Stef Heyenrath
984f2a8862 appveyor 2017-01-18 20:57:43 +01:00
Stef Heyenrath
65c17ff519 Add Func<> matchhing. Solves issue #2 2017-01-18 20:45:38 +01:00
Stef Heyenrath
9d1fd8fd51 Solve #10 2017-01-18 08:19:57 +01:00
Stef Heyenrath
8f02e99a00 Rename classes 2017-01-18 07:42:05 +01:00
Stef Heyenrath
c0872995b0 Fixed Issue #1 2017-01-17 23:41:58 +01:00
Stef Heyenrath
eeaeeb2c61 Initial code (copy) 2017-01-17 22:44:21 +01:00
107 changed files with 7478 additions and 1 deletions

29
.runsettings Normal file
View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<RunSettings>
<DataCollectionRunSettings>
<DataCollectors>
<DataCollector friendlyName="Code Coverage"
uri="datacollector://Microsoft/CodeCoverage/2.0"
assemblyQualifiedName="Microsoft.VisualStudio.Coverage.DynamicCoverageDataCollector, Microsoft.VisualStudio.TraceCollector, Version=11.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<Configuration>
<CodeCoverage>
<ModulePaths>
<Include>
<ModulePath>.*\.dll$</ModulePath>
</Include>
<Exclude>
<ModulePath>.*Validation.*</ModulePath>
<ModulePath>.*\.tests.dll$</ModulePath>
<ModulePath>.*simmetrics.*</ModulePath>
</Exclude>
</ModulePaths>
<UseVerifiableInstrumentation>True</UseVerifiableInstrumentation>
<AllowLowIntegrityProcesses>True</AllowLowIntegrityProcesses>
<CollectFromChildProcesses>True</CollectFromChildProcesses>
<CollectAspDotNet>False</CollectAspDotNet>
</CodeCoverage>
</Configuration>
</DataCollector>
</DataCollectors>
</DataCollectionRunSettings>
</RunSettings>

View File

@@ -1,2 +1,29 @@
# WireMock.Net
A C# .NET version based on http://wiremock.org
A C# .NET version based on [mock4net](https://github.com/alexvictoor/mock4net) which mimics the functionality from the JAVA based http://WireMock.org
[![Build status](https://ci.appveyor.com/api/projects/status/b3n6q3ygbww4lyls?svg=true)](https://ci.appveyor.com/project/StefH/wiremock-net)
[![NuGet Badge](https://buildstats.info/nuget/WireMock.Net)](https://www.nuget.org/packages/WireMock.Net)
## Stubbing
A core feature of WireMock.Net is the ability to return canned/predefined HTTP responses for requests matching criteria, see [Wiki : Stubbing](https://github.com/StefH/WireMock.Net/wiki/Stubbing).
## Using WireMock in UnitTest framework
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).
## 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).
## WireMock as a standalone process
This is quite straight forward to launch a mock server within a console application, see [Wiki : standalone](https://github.com/StefH/WireMock.Net/wiki/WireMock-as-a-standalone-process).
### SSL
You can start a standalone mock server listening for HTTPS requests. To do so, there is just a flag to set when creating the server:
```csharp
var server = FluentMockServer.Start(port: 8443, ssl: true);
```
Obviously you need a certificate registered on your box, properly associated with your application and the port number that will be used. This is not really specific to WireMock, not very straightforward and hence the following stackoverflow thread might come handy: [Httplistener with https support](http://stackoverflow.com/questions/11403333/httplistener-with-https-support)
## Simulating faults
Currently not done - need to get rid of HttpListener and use lower level TcpListener in order to be able to implement this properly

View File

@@ -0,0 +1,29 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/PatternsAndTemplates/StructuralSearch/Pattern/=99FF42DE363B4B439A0BF7D1DA299892/@KeyIndexDefined">True</s:Boolean>
<s:Boolean x:Key="/Default/PatternsAndTemplates/StructuralSearch/Pattern/=99FF42DE363B4B439A0BF7D1DA299892/CustomPatternPlaceholder/=args/@KeyIndexDefined">True</s:Boolean>
<s:String x:Key="/Default/PatternsAndTemplates/StructuralSearch/Pattern/=99FF42DE363B4B439A0BF7D1DA299892/CustomPatternPlaceholder/=args/Properties/=Maximal/@EntryIndexedValue">-1</s:String>
<s:String x:Key="/Default/PatternsAndTemplates/StructuralSearch/Pattern/=99FF42DE363B4B439A0BF7D1DA299892/CustomPatternPlaceholder/=args/Properties/=Minimal/@EntryIndexedValue">-1</s:String>
<s:String x:Key="/Default/PatternsAndTemplates/StructuralSearch/Pattern/=99FF42DE363B4B439A0BF7D1DA299892/CustomPatternPlaceholder/=args/Type/@EntryValue">ArgumentPlaceholder</s:String>
<s:Boolean x:Key="/Default/PatternsAndTemplates/StructuralSearch/Pattern/=99FF42DE363B4B439A0BF7D1DA299892/CustomPatternPlaceholder/=ArgumentNullException/@KeyIndexDefined">True</s:Boolean>
<s:String x:Key="/Default/PatternsAndTemplates/StructuralSearch/Pattern/=99FF42DE363B4B439A0BF7D1DA299892/CustomPatternPlaceholder/=ArgumentNullException/Properties/=ExactType/@EntryIndexedValue">False</s:String>
<s:String x:Key="/Default/PatternsAndTemplates/StructuralSearch/Pattern/=99FF42DE363B4B439A0BF7D1DA299892/CustomPatternPlaceholder/=ArgumentNullException/Properties/=Type/@EntryIndexedValue"></s:String>
<s:String x:Key="/Default/PatternsAndTemplates/StructuralSearch/Pattern/=99FF42DE363B4B439A0BF7D1DA299892/CustomPatternPlaceholder/=ArgumentNullException/Type/@EntryValue">TypePlaceholder</s:String>
<s:Boolean x:Key="/Default/PatternsAndTemplates/StructuralSearch/Pattern/=99FF42DE363B4B439A0BF7D1DA299892/CustomPatternPlaceholder/=paramName/@KeyIndexDefined">True</s:Boolean>
<s:String x:Key="/Default/PatternsAndTemplates/StructuralSearch/Pattern/=99FF42DE363B4B439A0BF7D1DA299892/CustomPatternPlaceholder/=paramName/Properties/=ExactType/@EntryIndexedValue">False</s:String>
<s:String x:Key="/Default/PatternsAndTemplates/StructuralSearch/Pattern/=99FF42DE363B4B439A0BF7D1DA299892/CustomPatternPlaceholder/=paramName/Properties/=ExpressionType/@EntryIndexedValue"></s:String>
<s:String x:Key="/Default/PatternsAndTemplates/StructuralSearch/Pattern/=99FF42DE363B4B439A0BF7D1DA299892/CustomPatternPlaceholder/=paramName/Type/@EntryValue">ExpressionPlaceholder</s:String>
<s:Boolean x:Key="/Default/PatternsAndTemplates/StructuralSearch/Pattern/=99FF42DE363B4B439A0BF7D1DA299892/IsReplacePattern/@EntryValue">True</s:Boolean>
<s:String x:Key="/Default/PatternsAndTemplates/StructuralSearch/Pattern/=99FF42DE363B4B439A0BF7D1DA299892/LanguageName/@EntryValue">CSHARP</s:String>
<s:String x:Key="/Default/PatternsAndTemplates/StructuralSearch/Pattern/=99FF42DE363B4B439A0BF7D1DA299892/ReplacePattern/@EntryValue">Check.NotNull($paramName$, nameof($paramName$));</s:String>
<s:String x:Key="/Default/PatternsAndTemplates/StructuralSearch/Pattern/=99FF42DE363B4B439A0BF7D1DA299892/SearchPattern/@EntryValue">if ($paramName$ == null) throw new $ArgumentNullException$($args$);</s:String>
<s:String x:Key="/Default/PatternsAndTemplates/StructuralSearch/Pattern/=99FF42DE363B4B439A0BF7D1DA299892/Severity/@EntryValue">SUGGESTION</s:String></wpf:ResourceDictionary>

68
WireMock.Net Solution.sln Normal file
View File

@@ -0,0 +1,68 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.25420.1
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{EF242EDF-7133-4277-9A0C-18744DE08707}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{197A0EE3-94E5-4807-BBCF-2F1BCA28A6AE}"
ProjectSection(SolutionItems) = preProject
.runsettings = .runsettings
appveyor.yml = appveyor.yml
README.md = README.md
EndProjectSection
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "WireMock.Net", "src\WireMock.Net\WireMock.Net.xproj", "{D3804228-91F4-4502-9595-39584E5A01AD}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "examples", "examples", "{F0C22C47-DF71-463C-9B04-B4E0F3B8708A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.ConsoleApplication", "examples\WireMock.Net.ConsoleApplication\WireMock.Net.ConsoleApplication.csproj", "{668F689E-57B4-422E-8846-C0FF643CA268}"
ProjectSection(ProjectDependencies) = postProject
{D3804228-91F4-4502-9595-39584E5A01AD} = {D3804228-91F4-4502-9595-39584E5A01AD}
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{890A1DED-C229-4FA1-969E-AAC3BBFC05E5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.Tests", "test\WireMock.Net.Tests\WireMock.Net.Tests.csproj", "{D8B56D28-33CE-4BEF-97D4-7DD546E37F25}"
ProjectSection(ProjectDependencies) = postProject
{D3804228-91F4-4502-9595-39584E5A01AD} = {D3804228-91F4-4502-9595-39584E5A01AD}
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.StandAlone", "src\WireMock.Net.StandAlone\WireMock.Net.StandAlone.csproj", "{668F689E-57B4-422E-8846-C0FF643CA999}"
ProjectSection(ProjectDependencies) = postProject
{D3804228-91F4-4502-9595-39584E5A01AD} = {D3804228-91F4-4502-9595-39584E5A01AD}
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{D3804228-91F4-4502-9595-39584E5A01AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D3804228-91F4-4502-9595-39584E5A01AD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D3804228-91F4-4502-9595-39584E5A01AD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D3804228-91F4-4502-9595-39584E5A01AD}.Release|Any CPU.Build.0 = Release|Any CPU
{668F689E-57B4-422E-8846-C0FF643CA268}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{668F689E-57B4-422E-8846-C0FF643CA268}.Debug|Any CPU.Build.0 = Debug|Any CPU
{668F689E-57B4-422E-8846-C0FF643CA268}.Release|Any CPU.ActiveCfg = Release|Any CPU
{668F689E-57B4-422E-8846-C0FF643CA268}.Release|Any CPU.Build.0 = Release|Any CPU
{D8B56D28-33CE-4BEF-97D4-7DD546E37F25}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D8B56D28-33CE-4BEF-97D4-7DD546E37F25}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D8B56D28-33CE-4BEF-97D4-7DD546E37F25}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D8B56D28-33CE-4BEF-97D4-7DD546E37F25}.Release|Any CPU.Build.0 = Release|Any CPU
{668F689E-57B4-422E-8846-C0FF643CA999}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{668F689E-57B4-422E-8846-C0FF643CA999}.Debug|Any CPU.Build.0 = Debug|Any CPU
{668F689E-57B4-422E-8846-C0FF643CA999}.Release|Any CPU.ActiveCfg = Release|Any CPU
{668F689E-57B4-422E-8846-C0FF643CA999}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{D3804228-91F4-4502-9595-39584E5A01AD} = {EF242EDF-7133-4277-9A0C-18744DE08707}
{668F689E-57B4-422E-8846-C0FF643CA268} = {F0C22C47-DF71-463C-9B04-B4E0F3B8708A}
{D8B56D28-33CE-4BEF-97D4-7DD546E37F25} = {890A1DED-C229-4FA1-969E-AAC3BBFC05E5}
{668F689E-57B4-422E-8846-C0FF643CA999} = {EF242EDF-7133-4277-9A0C-18744DE08707}
EndGlobalSection
EndGlobal

View File

@@ -0,0 +1,6 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/Environment/InjectedLayers/FileInjectedLayer/=FBE2016FCEACD34DB66E1CC40B87F9F2/AbsolutePath/@EntryValue">C:\Users\azureuser\Documents\Github\WireMock.Net\ReSharper_WireMock.DotSettings</s:String>
<s:String x:Key="/Default/Environment/InjectedLayers/FileInjectedLayer/=FBE2016FCEACD34DB66E1CC40B87F9F2/RelativePath/@EntryValue">..\ReSharper_WireMock.DotSettings</s:String>
<s:Boolean x:Key="/Default/Environment/InjectedLayers/FileInjectedLayer/=FBE2016FCEACD34DB66E1CC40B87F9F2/@KeyIndexDefined">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/InjectedLayers/InjectedLayerCustomization/=FileFBE2016FCEACD34DB66E1CC40B87F9F2/@KeyIndexDefined">True</s:Boolean>
<s:Double x:Key="/Default/Environment/InjectedLayers/InjectedLayerCustomization/=FileFBE2016FCEACD34DB66E1CC40B87F9F2/RelativePriority/@EntryValue">1</s:Double></wpf:ResourceDictionary>

BIN
WireMock.Net-Logo.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
WireMock.Net-Logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

38
appveyor.yml Normal file
View File

@@ -0,0 +1,38 @@
os: Visual Studio 2015
version: 1.0.0.{build}
configuration:
- Debug
- Release
platform: Any CPU
init:
- ps: $Env:LABEL = "CI" + $Env:APPVEYOR_BUILD_NUMBER.PadLeft(5, "0")
install:
- ps: Start-FileDownload 'https://download.microsoft.com/download/0/A/3/0A372822-205D-4A86-BFA7-084D2CBE9EDF/DotNetCore.1.0.1-SDK.1.0.0.Preview2-003133-x64.exe'
- cmd: DotNetCore.1.0.1-SDK.1.0.0.Preview2-003133-x64 /quiet
environment:
PATH: $(PATH);$(PROGRAMFILES)\dotnet\
before_build:
- nuget restore .\examples\WireMock.Net.ConsoleApplication\WireMock.Net.ConsoleApplication.csproj -PackagesDirectory packages
- nuget restore .\test\WireMock.Net.Tests\WireMock.Net.Tests.csproj -PackagesDirectory packages
build_script:
- appveyor-retry dotnet restore .\src\WireMock.Net -v Minimal
- dotnet build .\src\WireMock.Net\project.json -c %CONFIGURATION%
- cmd: msbuild .\examples\WireMock.Net.ConsoleApplication\WireMock.Net.ConsoleApplication.csproj /p:Configuration=%CONFIGURATION% /p:Platform=AnyCPU
- cmd: msbuild .\test\WireMock.Net.Tests\WireMock.Net.Tests.csproj /p:Configuration=%CONFIGURATION% /p:Platform=AnyCPU
- dotnet pack -c %CONFIGURATION% --no-build --version-suffix %LABEL% -o .\artifacts .\src\WireMock.Net\project.json
artifacts:
- path: artifacts\**\*.*
cache:
- packages

View File

@@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace WireMock.Net.ConsoleApp
{
public class Program
{
public static void Main(string[] args)
{
int port;
if (args.Length == 0 || !int.TryParse(args[0], out port))
port = 8080;
var server = FluentMockServer.Start(port);
Console.WriteLine("FluentMockServer running at {0}", server.Port);
server
.Given(
Requests
.WithUrl("/*")
.UsingGet()
)
.RespondWith(
Responses
.WithStatusCode(200)
.WithHeader("Content-Type", "application/json")
.WithBody(@"{ ""msg"": ""Hello world!""}")
);
Console.WriteLine("Press any key to stop the server");
Console.ReadKey();
Console.WriteLine("Displaying all requests");
var allRequests = server.RequestLogs;
Console.WriteLine(JsonConvert.SerializeObject(allRequests, Formatting.Indented));
Console.WriteLine("Press any key to quit");
Console.ReadKey();
}
}
}

View File

@@ -0,0 +1,19 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("WireMock.Net.ConsoleApp")]
[assembly: AssemblyTrademark("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("bb7ad978-5c03-4d76-a903-3865802b45eb")]

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>bb7ad978-5c03-4d76-a903-3865802b45eb</ProjectGuid>
<RootNamespace>WireMock.Net.ConsoleApp</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

View File

@@ -0,0 +1,19 @@
{
"version": "1.0.0-*",
"buildOptions": {
"emitEntryPoint": true
},
"dependencies": {
"Microsoft.NETCore.App": {
"type": "platform",
"version": "1.1.0"
}
},
"frameworks": {
"netcoreapp1.0": {
"imports": "dnxcore50"
}
}
}

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
</startup>
</configuration>

View File

@@ -0,0 +1,104 @@
using System;
using System.Linq;
using Newtonsoft.Json;
using WireMock.Matchers;
using WireMock.RequestBuilders;
using WireMock.ResponseBuilders;
using WireMock.Server;
namespace WireMock.Net.ConsoleApplication
{
static class Program
{
static void Main(params string[] args)
{
string url1 = "http://localhost:9090/";
string url2 = "http://localhost:9091/";
string url3 = "https://localhost:9443/";
var server = FluentMockServer.StartWithAdminInterface(url1, url2, url3);
Console.WriteLine("FluentMockServer listening at {0}", string.Join(" and ", server.Urls));
server.SetBasicAuthentication("a", "b");
server.AllowPartialMapping();
server
.Given(Request.Create().WithPath(p => p.Contains("x")).UsingGet())
.AtPriority(4)
.RespondWith(Response.Create()
.WithStatusCode(200)
.WithHeader("Content-Type", "application/json")
.WithBody(@"{ ""result"": ""Contains x with FUNC 200""}"));
server
.Given(Request.Create().WithPath("/data").UsingPost().WithBody(b => b.Contains("e")))
.AtPriority(999)
.RespondWith(Response.Create()
.WithStatusCode(201)
.WithHeader("Content-Type", "application/json")
.WithBody(@"{ ""result"": ""data posted with FUNC 201""}"));
server
.Given(Request.Create().WithPath("/data", "/ax").UsingPost().WithHeader("Content-Type", "application/json*"))
.RespondWith(Response.Create()
.WithStatusCode(201)
.WithHeader("Content-Type", "application/json")
.WithBody(@"{ ""result"": ""data posted with 201""}"));
server
.Given(Request.Create().WithPath("/json").UsingPost().WithBody(new JsonPathMatcher("$.things[?(@.name == 'RequiredThing')]")))
.RespondWith(Response.Create()
.WithStatusCode(201)
.WithHeader("Content-Type", "application/json")
.WithBody(@"{ ""result"": ""json posted with 201""}"));
server
.Given(Request.Create().WithPath("/json2").UsingPost().WithBody("x"))
.RespondWith(Response.Create()
.WithStatusCode(201)
.WithHeader("Content-Type", "application/json")
.WithBody(@"{ ""result"": ""json posted with x - 201""}"));
server
.Given(Request.Create().WithPath("/data").UsingDelete())
.RespondWith(Response.Create()
.WithStatusCode(200)
.WithHeader("Content-Type", "application/json")
.WithBody(@"{ ""result"": ""data deleted with 200""}"));
server
.Given(Request.Create().WithPath("/nobody").UsingGet())
.RespondWith(Response.Create().WithDelay(TimeSpan.FromSeconds(1))
.WithStatusCode(200));
server
.Given(Request.Create().WithPath("/partial").UsingPost().WithBody(new SimMetricsMatcher(new [] { "cat", "dog" })))
.RespondWith(Response.Create().WithStatusCode(200).WithBody("partial = 200"));
// http://localhost:8080/any/any?start=1000&stop=1&stop=2
server
.Given(Request.Create().WithPath("/*").UsingGet())
.WithGuid("90356dba-b36c-469a-a17e-669cd84f1f05")
.AtPriority(server.Mappings.Count() + 1)
.RespondWith(Response.Create()
.WithStatusCode(200)
.WithHeader("Content-Type", "application/json")
.WithHeader("Transformed-Postman-Token", "token is {{request.headers.Postman-Token}}")
.WithBody(@"{""msg"": ""Hello world CATCH-ALL on /*, {{request.path}}, bykey={{request.query.start}}, bykey={{request.query.stop}}, byidx0={{request.query.stop.[0]}}, byidx1={{request.query.stop.[1]}}"" }")
.WithTransformer()
.WithDelay(TimeSpan.FromMilliseconds(100))
);
Console.WriteLine("Press any key to stop the server");
Console.ReadKey();
Console.WriteLine("Displaying all requests");
var allRequests = server.LogEntries;
Console.WriteLine(JsonConvert.SerializeObject(allRequests, Formatting.Indented));
Console.WriteLine("Press any key to quit");
Console.ReadKey();
}
}
}

View File

@@ -0,0 +1,35 @@
using System.Reflection;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("WireMock.Net.ConsoleApplication")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("WireMock.Net.ConsoleApplication")]
[assembly: AssemblyCopyright("Copyright © Stef Heyenrath 2017")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("668f689e-57b4-422e-8846-c0ff643ca268")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@@ -0,0 +1,74 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{668F689E-57B4-422E-8846-C0FF643CA268}</ProjectGuid>
<OutputType>Exe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>WireMock.Net.ConsoleApplication</RootNamespace>
<AssemblyName>WireMock.Net.ConsoleApplication</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup>
<ApplicationIcon>..\..\WireMock.Net-Logo.ico</ApplicationIcon>
</PropertyGroup>
<ItemGroup>
<Reference Include="Newtonsoft.Json, Version=6.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\..\packages\Newtonsoft.Json.6.0.8\lib\net45\Newtonsoft.Json.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="SimMetrics.Net, Version=1.0.1.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\SimMetrics.Net.1.0.1.0\lib\net45\SimMetrics.Net.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="WireMock.Net">
<HintPath>..\..\src\WireMock.Net\bin\$(Configuration)\net45\WireMock.Net.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
<None Include="packages.config">
<SubType>Designer</SubType>
</None>
<Content Include="__admin\mappings\11111110-a633-40e8-a244-5cb80bc0ab66.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@@ -0,0 +1,21 @@
{
"Request": {
"Path": {
"Matchers": [
{
"Name": "WildcardMatcher",
"Pattern": "/static/mapping"
}
]
},
"Methods": [
"get"
]
},
"Response": {
"BodyAsJson": { "body": "static mapping" },
"Headers": {
"Content-Type": "application/json"
}
}
}

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Newtonsoft.Json" version="6.0.8" targetFramework="net452" />
<package id="SimMetrics.Net" version="1.0.1.0" targetFramework="net452" />
</packages>

View File

@@ -0,0 +1,51 @@
using System;
using System.Collections.Generic;
using System.Linq;
using CommandLineParser.Arguments;
using CommandLineParser.Exceptions;
using WireMock.Server;
namespace WireMock.Net.StandAlone
{
public class Program
{
private class Options
{
[ValueArgument(typeof(string), 'u', "Urls", Description = "URL(s) to listen on.", Optional = true, AllowMultiple = true)]
public List<string> Urls { get; set; }
[SwitchArgument('p', "AllowPartialMapping", true, Description = "Allow Partial Mapping (default set to true).", Optional = true)]
public bool AllowPartialMapping { get; set; }
}
static void Main(params string[] args)
{
var options = new Options();
var parser = new CommandLineParser.CommandLineParser();
parser.ExtractArgumentAttributes(options);
try
{
parser.ParseCommandLine(args);
if (!options.Urls.Any())
options.Urls.Add("http://localhost:9090/");
var server = FluentMockServer.StartWithAdminInterface(options.Urls.ToArray());
if (options.AllowPartialMapping)
server.AllowPartialMapping();
Console.WriteLine("WireMock.Net server listening at {0}", string.Join(" and ", server.Urls));
}
catch (CommandLineException e)
{
Console.WriteLine(e.Message);
parser.ShowUsage();
}
Console.WriteLine("Press any key to stop the server");
Console.ReadKey();
}
}
}

View File

@@ -0,0 +1,35 @@
using System.Reflection;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("WireMock.Net.StandAlone")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("WireMock.Net.StandAlone")]
[assembly: AssemblyCopyright("Copyright © Stef Heyenrath 2017")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("668f689e-57b4-422e-8846-c0ff643ca999")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@@ -0,0 +1,65 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{668F689E-57B4-422E-8846-C0FF643CA999}</ProjectGuid>
<OutputType>Exe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>WireMock.Net.StandAlone</RootNamespace>
<AssemblyName>WireMock.Net.StandAlone</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup>
<ApplicationIcon>..\..\WireMock.Net-Logo.ico</ApplicationIcon>
</PropertyGroup>
<ItemGroup>
<Reference Include="CommandLineArgumentsParser, Version=3.0.5.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\CommandLineArgumentsParser.3.0.5\lib\net45\CommandLineArgumentsParser.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="WireMock.Net">
<HintPath>..\..\src\WireMock.Net\bin\$(Configuration)\net45\WireMock.Net.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
</configuration>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="CommandLineArgumentsParser" version="3.0.5" targetFramework="net452" />
</packages>

View File

@@ -0,0 +1,32 @@
namespace WireMock.Admin.Mappings
{
/// <summary>
/// Body Model
/// </summary>
public class BodyModel
{
/// <summary>
/// Gets or sets the matcher.
/// </summary>
/// <value>
/// The matcher.
/// </value>
public MatcherModel Matcher { get; set; }
/// <summary>
/// Gets or sets the function.
/// </summary>
/// <value>
/// The function.
/// </value>
public string Func { get; set; }
/// <summary>
/// Gets or sets the data function.
/// </summary>
/// <value>
/// The data function.
/// </value>
public string DataFunc { get; set; }
}
}

View File

@@ -0,0 +1,34 @@
using System.Collections.Generic;
namespace WireMock.Admin.Mappings
{
/// <summary>
/// Cookie Model
/// </summary>
public class CookieModel
{
/// <summary>
/// Gets or sets the name.
/// </summary>
/// <value>
/// The name.
/// </value>
public string Name { get; set; }
/// <summary>
/// Gets or sets the matchers.
/// </summary>
/// <value>
/// The matchers.
/// </value>
public IList<MatcherModel> Matchers { get; set; }
/// <summary>
/// Gets or sets the functions.
/// </summary>
/// <value>
/// The functions.
/// </value>
public string[] Funcs { get; set; }
}
}

View File

@@ -0,0 +1,23 @@
namespace WireMock.Admin.Mappings
{
/// <summary>
/// EncodingModel
/// </summary>
public class EncodingModel
{
/// <summary>
/// Encoding CodePage
/// </summary>
public int CodePage { get; set; }
/// <summary>
/// Encoding EncodingName
/// </summary>
public string EncodingName { get; set; }
/// <summary>
/// Encoding WebName
/// </summary>
public string WebName { get; set; }
}
}

View File

@@ -0,0 +1,34 @@
using System.Collections.Generic;
namespace WireMock.Admin.Mappings
{
/// <summary>
/// Header Model
/// </summary>
public class HeaderModel
{
/// <summary>
/// Gets or sets the name.
/// </summary>
/// <value>
/// The name.
/// </value>
public string Name { get; set; }
/// <summary>
/// Gets or sets the matchers.
/// </summary>
/// <value>
/// The matchers.
/// </value>
public IList<MatcherModel> Matchers { get; set; }
/// <summary>
/// Gets or sets the functions.
/// </summary>
/// <value>
/// The functions.
/// </value>
public string[] Funcs { get; set; }
}
}

View File

@@ -0,0 +1,42 @@
using System;
namespace WireMock.Admin.Mappings
{
/// <summary>
/// MappingModel
/// </summary>
public class MappingModel
{
/// <summary>
/// Gets or sets the unique identifier.
/// </summary>
/// <value>
/// The unique identifier.
/// </value>
public Guid? Guid { get; set; }
/// <summary>
/// Gets or sets the priority.
/// </summary>
/// <value>
/// The priority.
/// </value>
public int? Priority { get; set; }
/// <summary>
/// Gets or sets the request.
/// </summary>
/// <value>
/// The request.
/// </value>
public RequestModel Request { get; set; }
/// <summary>
/// Gets or sets the response.
/// </summary>
/// <value>
/// The response.
/// </value>
public ResponseModel Response { get; set; }
}
}

View File

@@ -0,0 +1,40 @@
namespace WireMock.Admin.Mappings
{
/// <summary>
/// MatcherModel
/// </summary>
public class MatcherModel
{
/// <summary>
/// Gets or sets the name.
/// </summary>
/// <value>
/// The name.
/// </value>
public string Name { get; set; }
/// <summary>
/// Gets or sets the pattern.
/// </summary>
/// <value>
/// The pattern.
/// </value>
public string Pattern { get; set; }
/// <summary>
/// Gets or sets the patterns.
/// </summary>
/// <value>
/// The patterns.
/// </value>
public string[] Patterns { get; set; }
/// <summary>
/// Gets or sets the ignore case.
/// </summary>
/// <value>
/// The ignore case.
/// </value>
public bool? IgnoreCase { get; set; }
}
}

View File

@@ -0,0 +1,34 @@
using System.Collections.Generic;
namespace WireMock.Admin.Mappings
{
/// <summary>
/// Param Model
/// </summary>
public class ParamModel
{
/// <summary>
/// Gets or sets the name.
/// </summary>
/// <value>
/// The name.
/// </value>
public string Name { get; set; }
/// <summary>
/// Gets or sets the values.
/// </summary>
/// <value>
/// The values.
/// </value>
public IList<string> Values { get; set; }
/// <summary>
/// Gets or sets the functions.
/// </summary>
/// <value>
/// The functions.
/// </value>
public string[] Funcs { get; set; }
}
}

View File

@@ -0,0 +1,24 @@
namespace WireMock.Admin.Mappings
{
/// <summary>
/// PathModel
/// </summary>
public class PathModel
{
/// <summary>
/// Gets or sets the matchers.
/// </summary>
/// <value>
/// The matchers.
/// </value>
public MatcherModel[] Matchers { get; set; }
/// <summary>
/// Gets or sets the functions.
/// </summary>
/// <value>
/// The functions.
/// </value>
public string[] Funcs { get; set; }
}
}

View File

@@ -0,0 +1,63 @@
using System.Collections.Generic;
namespace WireMock.Admin.Mappings
{
/// <summary>
/// RequestModel
/// </summary>
public class RequestModel
{
/// <summary>
/// Gets or sets the Path. (Can be a string or a PathModel)
/// </summary>
/// <value>
/// The URL.
/// </value>
public object Path { get; set; }
/// <summary>
/// Gets or sets the Url. (Can be a string or a UrlModel)
/// </summary>
/// <value>
/// The URL.
/// </value>
public object Url { get; set; }
/// <summary>
/// The methods
/// </summary>
public string[] Methods { get; set; }
/// <summary>
/// Gets or sets the Headers.
/// </summary>
/// <value>
/// The Headers.
/// </value>
public IList<HeaderModel> Headers { get; set; }
/// <summary>
/// Gets or sets the Cookies.
/// </summary>
/// <value>
/// The Cookies.
/// </value>
public IList<CookieModel> Cookies { get; set; }
/// <summary>
/// Gets or sets the Params.
/// </summary>
/// <value>
/// The Headers.
/// </value>
public IList<ParamModel> Params { get; set; }
/// <summary>
/// Gets or sets the body.
/// </summary>
/// <value>
/// The body.
/// </value>
public BodyModel Body { get; set; }
}
}

View File

@@ -0,0 +1,74 @@
using System.Collections.Generic;
namespace WireMock.Admin.Mappings
{
/// <summary>
/// ResponseModel
/// </summary>
public class ResponseModel
{
/// <summary>
/// Gets or sets the HTTP status.
/// </summary>
/// <value>
/// The HTTP status.
/// </value>
public int? StatusCode { get; set; }
/// <summary>
/// Gets or sets the body.
/// </summary>
/// <value>
/// The body.
/// </value>
public string Body { get; set; }
/// <summary>
/// Gets or sets the body.
/// </summary>
/// <value>
/// The body.
/// </value>
public string BodyAsBase64 { get; set; }
/// <summary>
/// Gets or sets the body (as JSON object).
/// </summary>
/// <value>
/// The body.
/// </value>
public object BodyAsJson { get; set; }
/// <summary>
/// Gets or sets the body encoding.
/// </summary>
/// <value>
/// The body encoding.
/// </value>
public EncodingModel BodyEncoding { get; set; }
/// <summary>
/// Gets or sets a value indicating whether [use transformer].
/// </summary>
/// <value>
/// <c>true</c> if [use transformer]; otherwise, <c>false</c>.
/// </value>
public bool UseTransformer { get; set; }
/// <summary>
/// Gets or sets the headers.
/// </summary>
/// <value>
/// The headers.
/// </value>
public IDictionary<string, string> Headers { get; set; }
/// <summary>
/// Gets or sets the delay in milliseconds.
/// </summary>
/// <value>
/// The delay in milliseconds.
/// </value>
public int? Delay { get; set; }
}
}

View File

@@ -0,0 +1,24 @@
namespace WireMock.Admin.Mappings
{
/// <summary>
/// UrlModel
/// </summary>
public class UrlModel
{
/// <summary>
/// Gets or sets the matchers.
/// </summary>
/// <value>
/// The matchers.
/// </value>
public MatcherModel[] Matchers { get; set; }
/// <summary>
/// Gets or sets the functions.
/// </summary>
/// <value>
/// The functions.
/// </value>
public string[] Funcs { get; set; }
}
}

View File

@@ -0,0 +1,50 @@
using System;
namespace WireMock.Admin.Requests
{
/// <summary>
/// Request Log Model
/// </summary>
public class LogEntryModel
{
/// <summary>
/// Gets or sets the unique identifier.
/// </summary>
/// <value>
/// The unique identifier.
/// </value>
public Guid Guid { get; set; }
/// <summary>
/// Gets or sets the request.
/// </summary>
/// <value>
/// The request.
/// </value>
public LogRequestModel Request { get; set; }
/// <summary>
/// Gets or sets the response.
/// </summary>
/// <value>
/// The response.
/// </value>
public LogResponseModel Response { get; set; }
/// <summary>
/// Gets or sets the mapping unique identifier.
/// </summary>
/// <value>
/// The mapping unique identifier.
/// </value>
public Guid? MappingGuid { get; set; }
/// <summary>
/// Gets or sets the request match result.
/// </summary>
/// <value>
/// The request match result.
/// </value>
public LogRequestMatchModel RequestMatchResult { get; set; }
}
}

View File

@@ -0,0 +1,40 @@
namespace WireMock.Admin.Requests
{
/// <summary>
/// LogRequestMatchModel
/// </summary>
public class LogRequestMatchModel
{
/// <summary>
/// Gets or sets the match-score.
/// </summary>
/// <value>
/// The match-score.
/// </value>
public double TotalScore { get; set; }
/// <summary>
/// Gets or sets the total number of matches.
/// </summary>
/// <value>
/// The total number of matches.
/// </value>
public int TotalNumber { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this instance is perfect match.
/// </summary>
/// <value>
/// <c>true</c> if this instance is perfect match; otherwise, <c>false</c>.
/// </value>
public bool IsPerfectMatch { get; set; }
/// <summary>
/// Gets the match percentage.
/// </summary>
/// <value>
/// The match percentage.
/// </value>
public double AverageTotalScore { get; set; }
}
}

View File

@@ -0,0 +1,79 @@
using System;
using System.Collections.Generic;
using WireMock.Admin.Mappings;
using WireMock.Util;
namespace WireMock.Admin.Requests
{
/// <summary>
/// RequestMessage Model
/// </summary>
public class LogRequestModel
{
/// <summary>
/// Gets the DateTime.
/// </summary>
public DateTime DateTime { get; set; }
/// <summary>
/// Gets or sets the Path.
/// </summary>
/// <value>
/// The Path.
/// </value>
public string Path { get; set; }
/// <summary>
/// Gets or sets the absolete URL.
/// </summary>
/// <value>
/// The absolete URL.
/// </value>
public string AbsoleteUrl { get; set; }
/// <summary>
/// Gets the query.
/// </summary>
public IDictionary<string, WireMockList<string>> Query { get; set; }
/// <summary>
/// Gets or sets the method.
/// </summary>
/// <value>
/// The method.
/// </value>
public string Method { get; set; }
/// <summary>
/// Gets or sets the Headers.
/// </summary>
/// <value>
/// The Headers.
/// </value>
public IDictionary<string, string> Headers { get; set; }
/// <summary>
/// Gets or sets the Cookies.
/// </summary>
/// <value>
/// The Cookies.
/// </value>
public IDictionary<string, string> Cookies { get; set; }
/// <summary>
/// Gets or sets the body.
/// </summary>
/// <value>
/// The body.
/// </value>
public string Body { get; set; }
/// <summary>
/// Gets or sets the body encoding.
/// </summary>
/// <value>
/// The body encoding.
/// </value>
public EncodingModel BodyEncoding { get; set; }
}
}

View File

@@ -0,0 +1,36 @@
using System.Collections.Generic;
using WireMock.Admin.Mappings;
namespace WireMock.Admin.Requests
{
/// <summary>
/// Response MessageModel
/// </summary>
public class LogResponseModel
{
/// <summary>
/// Gets or sets the status code.
/// </summary>
public int StatusCode { get; set; } = 200;
/// <summary>
/// Gets the headers.
/// </summary>
public IDictionary<string, string> Headers { get; set; }
/// <summary>
/// Gets or sets the body.
/// </summary>
public string Body { get; set; }
/// <summary>
/// Gets or sets the original body.
/// </summary>
public string BodyOriginal { get; set; }
/// <summary>
/// Gets or sets the body.
/// </summary>
public EncodingModel BodyEncoding { get; set; }
}
}

View File

@@ -0,0 +1,18 @@
namespace WireMock.Admin.Settings
{
/// <summary>
/// Settings
/// </summary>
public class SettingsModel
{
/// <summary>
/// Gets or sets the global delay in milliseconds.
/// </summary>
public int? GlobalProcessingDelay { get; set; }
/// <summary>
/// Gets or sets if partial mapping is allowed.
/// </summary>
public bool? AllowPartialMapping { get; set; }
}
}

View File

@@ -0,0 +1,24 @@
using System;
using System.Threading.Tasks;
using JetBrains.Annotations;
using WireMock.Validation;
namespace WireMock
{
internal class DynamicResponseProvider : IResponseProvider
{
private readonly Func<RequestMessage, ResponseMessage> _responseMessageFunc;
public DynamicResponseProvider([NotNull] Func<RequestMessage, ResponseMessage> responseMessageFunc)
{
Check.NotNull(responseMessageFunc, nameof(responseMessageFunc));
_responseMessageFunc = responseMessageFunc;
}
public Task<ResponseMessage> ProvideResponse(RequestMessage requestMessage)
{
return Task.FromResult(_responseMessageFunc(requestMessage));
}
}
}

View File

@@ -0,0 +1,63 @@
using System.Collections;
using System.Collections.Generic;
using System.Dynamic;
namespace WireMock.Extensions
{
/// <summary>
/// Dictionary Extensions
/// </summary>
public static class DictionaryExtensions
{
/// <summary>
/// Converts IDictionary to an ExpandObject.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="dictionary">The dictionary.</param>
/// <returns></returns>
public static dynamic ToExpandoObject<T>(this IDictionary<string, T> dictionary)
{
dynamic expando = new ExpandoObject();
var expandoDic = (IDictionary<string, object>)expando;
// go through the items in the dictionary and copy over the key value pairs)
foreach (var kvp in dictionary)
{
// if the value can also be turned into an ExpandoObject, then do it!
var value = kvp.Value as IDictionary<string, object>;
if (value != null)
{
var expandoValue = value.ToExpandoObject();
expandoDic.Add(kvp.Key, expandoValue);
}
else if (kvp.Value is ICollection)
{
// iterate through the collection and convert any strin-object dictionaries
// along the way into expando objects
var itemList = new List<object>();
foreach (var item in (ICollection)kvp.Value)
{
var objects = item as IDictionary<string, object>;
if (objects != null)
{
var expandoItem = objects.ToExpandoObject();
itemList.Add(expandoItem);
}
else
{
itemList.Add(item);
}
}
expandoDic.Add(kvp.Key, itemList);
}
else
{
expandoDic.Add(kvp.Key, kvp.Value);
}
}
return expando;
}
}
}

View File

@@ -0,0 +1,34 @@
using System.Net;
using System.Net.Sockets;
namespace WireMock.Http
{
/// <summary>
/// The ports.
/// </summary>
public static class PortUtil
{
/// <summary>
/// The find free TCP port.
/// </summary>
/// <returns>
/// The <see cref="int"/>.
/// </returns>
/// <remarks>see http://stackoverflow.com/questions/138043/find-the-next-tcp-port-in-net.</remarks>
public static int FindFreeTcpPort()
{
TcpListener tcpListener = null;
try
{
tcpListener = new TcpListener(IPAddress.Loopback, 0);
tcpListener.Start();
return ((IPEndPoint)tcpListener.LocalEndpoint).Port;
}
finally
{
tcpListener?.Stop();
}
}
}
}

View File

@@ -0,0 +1,102 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using WireMock.Validation;
namespace WireMock.Http
{
/// <summary>
/// The tiny http server.
/// </summary>
public class TinyHttpServer
{
private readonly Action<HttpListenerContext> _httpHandler;
private readonly HttpListener _listener;
private CancellationTokenSource _cts;
/// <summary>
/// Gets a value indicating whether this server is started.
/// </summary>
/// <value>
/// <c>true</c> if this server is started; otherwise, <c>false</c>.
/// </value>
public bool IsStarted { get; private set; }
/// <summary>
/// Gets the url.
/// </summary>
/// <value>
/// The urls.
/// </value>
public List<Uri> Urls { get; } = new List<Uri>();
/// <summary>
/// Gets the ports.
/// </summary>
/// <value>
/// The ports.
/// </value>
public List<int> Ports { get; } = new List<int>();
/// <summary>
/// Initializes a new instance of the <see cref="TinyHttpServer"/> class.
/// </summary>
/// <param name="uriPrefixes">The uriPrefixes.</param>
/// <param name="httpHandler">The http handler.</param>
public TinyHttpServer([NotNull] Action<HttpListenerContext> httpHandler, [NotNull] params string[] uriPrefixes)
{
Check.NotNull(httpHandler, nameof(httpHandler));
Check.NotEmpty(uriPrefixes, nameof(uriPrefixes));
_httpHandler = httpHandler;
// Create a listener.
_listener = new HttpListener();
foreach (string uriPrefix in uriPrefixes)
{
var uri = new Uri(uriPrefix);
Urls.Add(uri);
Ports.Add(uri.Port);
_listener.Prefixes.Add(uriPrefix);
}
}
/// <summary>
/// Start the server.
/// </summary>
public void Start()
{
_listener.Start();
IsStarted = true;
_cts = new CancellationTokenSource();
Task.Run(
async () =>
{
using (_listener)
{
while (!_cts.Token.IsCancellationRequested)
{
HttpListenerContext context = await _listener.GetContextAsync();
_httpHandler(context);
}
}
},
_cts.Token);
}
/// <summary>
/// Stop the server.
/// </summary>
public void Stop()
{
_cts.Cancel();
}
}
}

View File

@@ -0,0 +1,60 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
namespace WireMock
{
/// <summary>
/// The http listener request mapper.
/// </summary>
public class HttpListenerRequestMapper
{
/// <summary>
/// The map.
/// </summary>
/// <param name="listenerRequest">The listener request.</param>
/// <returns>The <see cref="RequestMessage"/>.</returns>
public RequestMessage Map(HttpListenerRequest listenerRequest)
{
Uri url = listenerRequest.Url;
string verb = listenerRequest.HttpMethod;
byte[] body = GetRequestBody(listenerRequest);
Encoding bodyEncoding = body != null ? listenerRequest.ContentEncoding : null;
string bodyAsString = bodyEncoding?.GetString(body);
var listenerHeaders = listenerRequest.Headers;
var headers = listenerHeaders.AllKeys.ToDictionary(k => k, k => listenerHeaders[k]);
var cookies = new Dictionary<string, string>();
foreach (Cookie cookie in listenerRequest.Cookies)
cookies.Add(cookie.Name, cookie.Value);
return new RequestMessage(url, verb, body, bodyAsString, bodyEncoding, headers, cookies) { DateTime = DateTime.Now };
}
/// <summary>
/// The get request body.
/// </summary>
/// <param name="request">The request.</param>
/// <returns>The <see cref="string"/>.</returns>
private byte[] GetRequestBody(HttpListenerRequest request)
{
if (!request.HasEntityBody)
{
return null;
}
using (var bodyStream = request.InputStream)
{
using (var memoryStream = new MemoryStream())
{
bodyStream.CopyTo(memoryStream);
return memoryStream.ToArray();
}
}
}
}
}

View File

@@ -0,0 +1,39 @@
using System.Linq;
using System.Net;
using System.Text;
namespace WireMock
{
/// <summary>
/// The http listener response mapper.
/// </summary>
public class HttpListenerResponseMapper
{
private readonly Encoding _utf8NoBom = new UTF8Encoding(false);
/// <summary>
/// The map.
/// </summary>
/// <param name="responseMessage">
/// The response.
/// </param>
/// <param name="listenerResponse">The listenerResponse.</param>
public void Map(ResponseMessage responseMessage, HttpListenerResponse listenerResponse)
{
listenerResponse.StatusCode = responseMessage.StatusCode;
responseMessage.Headers.ToList().ForEach(pair => listenerResponse.AddHeader(pair.Key, pair.Value));
if (responseMessage.Body == null)
return;
var encoding = responseMessage.BodyEncoding ?? _utf8NoBom;
byte[] buffer = encoding.GetBytes(responseMessage.Body);
listenerResponse.ContentEncoding = encoding;
listenerResponse.ContentLength64 = buffer.Length;
listenerResponse.OutputStream.Write(buffer, 0, buffer.Length);
listenerResponse.OutputStream.Flush();
}
}
}

View File

@@ -0,0 +1,21 @@
using System.Threading.Tasks;
namespace WireMock
{
/// <summary>
/// The Response Provider interface.
/// </summary>
public interface IResponseProvider
{
/// <summary>
/// The provide response.
/// </summary>
/// <param name="requestMessage">
/// The request.
/// </param>
/// <returns>
/// The <see cref="Task"/>.
/// </returns>
Task<ResponseMessage> ProvideResponse(RequestMessage requestMessage);
}
}

View File

@@ -0,0 +1,51 @@
using System;
using WireMock.Matchers.Request;
namespace WireMock.Logging
{
/// <summary>
/// LogEntry
/// </summary>
public class LogEntry
{
/// <summary>
/// Gets or sets the unique identifier.
/// </summary>
/// <value>
/// The unique identifier.
/// </value>
public Guid Guid { get; set; }
/// <summary>
/// Gets or sets the request message.
/// </summary>
/// <value>
/// The request message.
/// </value>
public RequestMessage RequestMessage { get; set; }
/// <summary>
/// Gets or sets the response message.
/// </summary>
/// <value>
/// The response message.
/// </value>
public ResponseMessage ResponseMessage { get; set; }
/// <summary>
/// Gets or sets the request match result.
/// </summary>
/// <value>
/// The request match result.
/// </value>
public RequestMatchResult RequestMatchResult { get; set; }
/// <summary>
/// Gets or sets the mapping unique identifier.
/// </summary>
/// <value>
/// The mapping unique identifier.
/// </value>
public Guid? MappingGuid { get; set; }
}
}

View File

@@ -0,0 +1,85 @@
using System;
using System.Threading.Tasks;
using WireMock.Matchers.Request;
namespace WireMock
{
/// <summary>
/// The Mapping.
/// </summary>
public class Mapping
{
/// <summary>
/// Gets the priority.
/// </summary>
/// <value>
/// The priority.
/// </value>
public int Priority { get; }
/// <summary>
/// Gets the unique identifier.
/// </summary>
/// <value>
/// The unique identifier.
/// </value>
public Guid Guid { get; }
/// <summary>
/// The Request matcher.
/// </summary>
public IRequestMatcher RequestMatcher { get; }
/// <summary>
/// The Provider.
/// </summary>
public IResponseProvider Provider { get; }
/// <summary>
/// Initializes a new instance of the <see cref="Mapping"/> class.
/// </summary>
/// <param name="guid">The the unique identifier.</param>
/// <param name="requestMatcher">The request matcher.</param>
/// <param name="provider">The provider.</param>
/// <param name="priority">The priority for this mapping.</param>
public Mapping(Guid guid, IRequestMatcher requestMatcher, IResponseProvider provider, int priority)
{
Priority = priority;
Guid = guid;
RequestMatcher = requestMatcher;
Provider = provider;
}
/// <summary>
/// The response to.
/// </summary>
/// <param name="requestMessage">The request message.</param>
/// <returns>The <see cref="Task"/>.</returns>
public async Task<ResponseMessage> ResponseTo(RequestMessage requestMessage)
{
return await Provider.ProvideResponse(requestMessage);
}
/// <summary>
/// Determines whether the RequestMessage is handled.
/// </summary>
/// <param name="requestMessage">The request message.</param>
/// <returns>The <see cref="RequestMatchResult"/>.</returns>
public RequestMatchResult IsRequestHandled(RequestMessage requestMessage)
{
var result = new RequestMatchResult();
RequestMatcher.GetMatchingScore(requestMessage, result);
return result;
}
/// <summary>
/// Gets a value indicating whether this mapping is an Admin Interface.
/// </summary>
/// <value>
/// <c>true</c> if this mapping is an Admin Interface; otherwise, <c>false</c>.
/// </value>
public bool IsAdminInterface => Provider is DynamicResponseProvider;
}
}

View File

@@ -0,0 +1,10 @@
namespace WireMock
{
/// <summary>
/// The registration callback.
/// </summary>
/// <param name="mapping">
/// The route.
/// </param>
public delegate void RegistrationCallback(Mapping mapping);
}

View File

@@ -0,0 +1,54 @@
using System.Linq;
using JetBrains.Annotations;
using WireMock.Validation;
namespace WireMock.Matchers
{
/// <summary>
/// ExactMatcher
/// </summary>
/// <seealso cref="IMatcher" />
public class ExactMatcher : IMatcher
{
private readonly string[] _values;
/// <summary>
/// Initializes a new instance of the <see cref="ExactMatcher"/> class.
/// </summary>
/// <param name="values">The values.</param>
public ExactMatcher([NotNull] params string[] values)
{
Check.NotNull(values, nameof(values));
_values = values;
}
/// <summary>
/// Determines whether the specified input is match.
/// </summary>
/// <param name="input">The input.</param>
/// <returns>A value between 0.0 - 1.0 of the similarity.</returns>
public double IsMatch(string input)
{
return MatchScores.ToScore(_values.Select(value => value.Equals(input)));
}
/// <summary>
/// Gets the value.
/// </summary>
/// <returns>Patterns</returns>
public string[] GetPatterns()
{
return _values;
}
/// <summary>
/// Gets the name.
/// </summary>
/// <returns>Name</returns>
public string GetName()
{
return "ExactMatcher";
}
}
}

View File

@@ -0,0 +1,27 @@
namespace WireMock.Matchers
{
/// <summary>
/// IMatcher
/// </summary>
public interface IMatcher
{
/// <summary>
/// Determines whether the specified input is match.
/// </summary>
/// <param name="input">The input.</param>
/// <returns>A value between 0.0 - 1.0 of the similarity.</returns>
double IsMatch(string input);
/// <summary>
/// Gets the patterns.
/// </summary>
/// <returns>Patterns</returns>
string[] GetPatterns();
/// <summary>
/// Gets the name.
/// </summary>
/// <returns>Name</returns>
string GetName();
}
}

View File

@@ -0,0 +1,68 @@
using System;
using System.Linq;
using JetBrains.Annotations;
using Newtonsoft.Json.Linq;
using WireMock.Validation;
namespace WireMock.Matchers
{
/// <summary>
/// JSONPathMatcher
/// </summary>
/// <seealso cref="IMatcher" />
public class JsonPathMatcher : IMatcher
{
private readonly string[] _patterns;
/// <summary>
/// Initializes a new instance of the <see cref="JsonPathMatcher"/> class.
/// </summary>
/// <param name="patterns">The patterns.</param>
public JsonPathMatcher([NotNull] params string[] patterns)
{
Check.NotNull(patterns, nameof(patterns));
_patterns = patterns;
}
/// <summary>
/// Determines whether the specified input is match.
/// </summary>
/// <param name="input">The input string</param>
/// <returns>A value between 0.0 - 1.0 of the similarity.</returns>
public double IsMatch(string input)
{
if (input == null)
return MatchScores.Mismatch;
try
{
JObject o = JObject.Parse(input);
return MatchScores.ToScore(_patterns.Select(p => o.SelectToken(p) != null));
}
catch (Exception)
{
return MatchScores.Mismatch;
}
}
/// <summary>
/// Gets the patterns.
/// </summary>
/// <returns>Pattern</returns>
public string[] GetPatterns()
{
return _patterns;
}
/// <summary>
/// Gets the name.
/// </summary>
/// <returns>Name</returns>
public string GetName()
{
return "JsonPathMatcher";
}
}
}

View File

@@ -0,0 +1,58 @@
using System.Collections.Generic;
using System.Linq;
namespace WireMock.Matchers
{
/// <summary>
/// MatchScores
/// </summary>
public static class MatchScores
{
/// <summary>
/// The tolerance
/// </summary>
public const double Tolerance = 0.0001;
/// <summary>
/// The default mismatch score
/// </summary>
public const double Mismatch = 0.0;
/// <summary>
/// The default perfect match score
/// </summary>
public const double Perfect = 1.0;
/// <summary>
/// Convert a bool to the score.
/// </summary>
/// <param name="value">if set to <c>true</c> [value].</param>
/// <returns>score</returns>
public static double ToScore(bool value)
{
return value ? Perfect : Mismatch;
}
/// <summary>
/// Calculates the score from multiple funcs.
/// </summary>
/// <param name="values">The values.</param>
/// <returns>score</returns>
public static double ToScore(IEnumerable<bool> values)
{
var list = values.Select(ToScore).ToList();
return list.Sum() / list.Count;
}
/// <summary>
/// Calculates the score from multiple funcs.
/// </summary>
/// <param name="values">The values.</param>
/// <returns>score</returns>
public static double ToScore(IEnumerable<double> values)
{
var list = values.ToList();
return list.Sum() / list.Count;
}
}
}

View File

@@ -0,0 +1,85 @@
using System;
using System.Linq;
using System.Text.RegularExpressions;
using JetBrains.Annotations;
using WireMock.Validation;
namespace WireMock.Matchers
{
/// <summary>
/// Regular Expression Matcher
/// </summary>
/// <seealso cref="IMatcher" />
public class RegexMatcher : IMatcher
{
private readonly string[] _patterns;
private readonly Regex[] _expressions;
/// <summary>
/// Initializes a new instance of the <see cref="RegexMatcher"/> class.
/// </summary>
/// <param name="pattern">The pattern.</param>
/// <param name="ignoreCase">IgnoreCase</param>
public RegexMatcher([NotNull, RegexPattern] string pattern, bool ignoreCase = false) : this(new [] { pattern }, ignoreCase )
{
}
/// <summary>
/// Initializes a new instance of the <see cref="RegexMatcher"/> class.
/// </summary>
/// <param name="patterns">The patterns.</param>
/// <param name="ignoreCase">IgnoreCase</param>
public RegexMatcher([NotNull, RegexPattern] string[] patterns, bool ignoreCase = false)
{
Check.NotNull(patterns, nameof(patterns));
_patterns = patterns;
RegexOptions options = RegexOptions.Compiled;
if (ignoreCase)
options |= RegexOptions.IgnoreCase;
_expressions = patterns.Select(p => new Regex(p, options)).ToArray();
}
/// <summary>
/// Determines whether the specified input is match.
/// </summary>
/// <param name="input">The input string</param>
/// <returns>A value between 0.0 - 1.0 of the similarity.</returns>
public double IsMatch(string input)
{
if (input == null)
return MatchScores.Mismatch;
try
{
return MatchScores.ToScore(_expressions.Select(e => e.IsMatch(input)));
}
catch (Exception)
{
return MatchScores.Mismatch;
}
}
/// <summary>
/// Gets the patterns.
/// </summary>
/// <returns>Pattern</returns>
public virtual string[] GetPatterns()
{
return _patterns;
}
/// <summary>
/// Gets the name.
/// </summary>
/// <returns>
/// Name
/// </returns>
public virtual string GetName()
{
return "RegexMatcher";
}
}
}

View File

@@ -0,0 +1,18 @@
namespace WireMock.Matchers.Request
{
/// <summary>
/// CompositeMatcherType
/// </summary>
public enum CompositeMatcherType
{
/// <summary>
/// And
/// </summary>
And = 0,
/// <summary>
/// Or
/// </summary>
Or = 1
}
}

View File

@@ -0,0 +1,20 @@
using JetBrains.Annotations;
namespace WireMock.Matchers.Request
{
/// <summary>
/// The RequestMatcher interface.
/// </summary>
public interface IRequestMatcher
{
/// <summary>
/// Determines whether the specified RequestMessage is match.
/// </summary>
/// <param name="requestMessage">The RequestMessage.</param>
/// <param name="requestMatchResult">The RequestMatchResult.</param>
/// <returns>
/// A value between 0.0 - 1.0 of the similarity.
/// </returns>
double GetMatchingScore([NotNull] RequestMessage requestMessage, [NotNull] RequestMatchResult requestMatchResult);
}
}

View File

@@ -0,0 +1,57 @@
using System;
namespace WireMock.Matchers.Request
{
/// <summary>
/// RequestMatchResult
/// </summary>
public class RequestMatchResult : IComparable
{
/// <summary>
/// Gets or sets the match-score.
/// </summary>
/// <value>
/// The match-score.
/// </value>
public double TotalScore { get; set; }
/// <summary>
/// Gets or sets the total number of matches.
/// </summary>
/// <value>
/// The total number of matches.
/// </value>
public int TotalNumber { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this instance is perfect match.
/// </summary>
/// <value>
/// <c>true</c> if this instance is perfect match; otherwise, <c>false</c>.
/// </value>
public bool IsPerfectMatch => Math.Abs(TotalScore - TotalNumber) < MatchScores.Tolerance;
/// <summary>
/// Gets the match percentage.
/// </summary>
/// <value>
/// The match percentage.
/// </value>
public double AverageTotalScore => TotalNumber == 0 ? 0.0 : TotalScore / TotalNumber;
/// <summary>
/// Compares the current instance with another object of the same type and returns an integer that indicates whether the current instance precedes, follows, or occurs in the same position in the sort order as the other object.
/// </summary>
/// <param name="obj">An object to compare with this instance.</param>
/// <returns>
/// A value that indicates the relative order of the objects being compared. The return value has these meanings: Value Meaning Less than zero This instance precedes <paramref name="obj" /> in the sort order. Zero This instance occurs in the same position in the sort order as <paramref name="obj" />. Greater than zero This instance follows <paramref name="obj" /> in the sort order.
/// </returns>
/// <exception cref="System.NotImplementedException"></exception>
public int CompareTo(object obj)
{
var compareObj = (RequestMatchResult)obj;
return compareObj.AverageTotalScore.CompareTo(AverageTotalScore);
}
}
}

View File

@@ -0,0 +1,125 @@
using System;
using JetBrains.Annotations;
using WireMock.Validation;
namespace WireMock.Matchers.Request
{
/// <summary>
/// The request body matcher.
/// </summary>
public class RequestMessageBodyMatcher : IRequestMatcher
{
/// <summary>
/// The body as byte[].
/// </summary>
private readonly byte[] _bodyData;
/// <summary>
/// The body function
/// </summary>
public Func<string, bool> Func { get; }
/// <summary>
/// The body data function
/// </summary>
public Func<byte[], bool> DataFunc { get; }
/// <summary>
/// The matcher.
/// </summary>
public IMatcher Matcher { get; }
/// <summary>
/// Initializes a new instance of the <see cref="RequestMessageBodyMatcher"/> class.
/// </summary>
/// <param name="body">
/// The body Regex pattern.
/// </param>
public RequestMessageBodyMatcher([NotNull] string body) : this(new SimMetricsMatcher(body))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="RequestMessageBodyMatcher"/> class.
/// </summary>
/// <param name="body">
/// The body Regex pattern.
/// </param>
public RequestMessageBodyMatcher([NotNull] byte[] body)
{
Check.NotNull(body, nameof(body));
_bodyData = body;
}
/// <summary>
/// Initializes a new instance of the <see cref="RequestMessageBodyMatcher"/> class.
/// </summary>
/// <param name="func">
/// The body func.
/// </param>
public RequestMessageBodyMatcher([NotNull] Func<string, bool> func)
{
Check.NotNull(func, nameof(func));
Func = func;
}
/// <summary>
/// Initializes a new instance of the <see cref="RequestMessageBodyMatcher"/> class.
/// </summary>
/// <param name="func">
/// The body func.
/// </param>
public RequestMessageBodyMatcher([NotNull] Func<byte[], bool> func)
{
Check.NotNull(func, nameof(func));
DataFunc = func;
}
/// <summary>
/// Initializes a new instance of the <see cref="RequestMessageBodyMatcher"/> class.
/// </summary>
/// <param name="matcher">
/// The body matcher.
/// </param>
public RequestMessageBodyMatcher([NotNull] IMatcher matcher)
{
Check.NotNull(matcher, nameof(matcher));
Matcher = matcher;
}
/// <summary>
/// Determines whether the specified RequestMessage is match.
/// </summary>
/// <param name="requestMessage">The RequestMessage.</param>
/// <param name="requestMatchResult">The RequestMatchResult.</param>
/// <returns>
/// A value between 0.0 - 1.0 of the similarity.
/// </returns>
public double GetMatchingScore(RequestMessage requestMessage, RequestMatchResult requestMatchResult)
{
double score = IsMatch(requestMessage);
requestMatchResult.TotalScore += score;
requestMatchResult.TotalNumber++;
return score;
}
private double IsMatch(RequestMessage requestMessage)
{
if (Matcher != null)
return Matcher.IsMatch(requestMessage.Body);
if (_bodyData != null)
return MatchScores.ToScore(requestMessage.BodyAsBytes == _bodyData);
if (Func != null)
return MatchScores.ToScore(requestMessage.Body != null && Func(requestMessage.Body));
if (DataFunc != null && requestMessage.BodyAsBytes != null)
return MatchScores.ToScore(requestMessage.BodyAsBytes != null && DataFunc(requestMessage.BodyAsBytes));
return MatchScores.Mismatch;
}
}
}

View File

@@ -0,0 +1,67 @@
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using WireMock.Validation;
namespace WireMock.Matchers.Request
{
/// <summary>
/// The composite request matcher.
/// </summary>
public abstract class RequestMessageCompositeMatcher : IRequestMatcher
{
private readonly CompositeMatcherType _type;
/// <summary>
/// Gets the request matchers.
/// </summary>
/// <value>
/// The request matchers.
/// </value>
private IEnumerable<IRequestMatcher> RequestMatchers { get; }
/// <summary>
/// Initializes a new instance of the <see cref="RequestMessageCompositeMatcher"/> class.
/// </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)
{
Check.NotNull(requestMatchers, nameof(requestMatchers));
_type = type;
RequestMatchers = requestMatchers;
}
/// <summary>
/// Determines whether the specified RequestMessage is match.
/// </summary>
/// <param name="requestMessage">The RequestMessage.</param>
/// <param name="requestMatchResult">The RequestMatchResult.</param>
/// <returns>
/// A value between 0.0 - 1.0 of the similarity.
/// </returns>
public double GetMatchingScore(RequestMessage requestMessage, RequestMatchResult requestMatchResult)
{
var list = new List<double>();
if (_type == CompositeMatcherType.And)
{
foreach (var requestMatcher in RequestMatchers)
{
double score = requestMatcher.GetMatchingScore(requestMessage, requestMatchResult);
list.Add(score);
}
return list.Sum() / list.Count;
}
foreach (var requestMatcher in RequestMatchers)
{
double score = requestMatcher.GetMatchingScore(requestMessage, requestMatchResult);
list.Add(score);
}
return list.Max();
}
}
}

View File

@@ -0,0 +1,105 @@
using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using WireMock.Validation;
namespace WireMock.Matchers.Request
{
/// <summary>
/// The request cookie matcher.
/// </summary>
public class RequestMessageCookieMatcher : IRequestMatcher
{
/// <value>
/// The funcs.
/// </value>
public Func<IDictionary<string, string>, bool>[] Funcs { get; }
/// <summary>
/// The name
/// </summary>
public string Name { get; }
/// <value>
/// The matchers.
/// </value>
public IMatcher[] Matchers { get; }
/// <summary>
/// Initializes a new instance of the <see cref="RequestMessageCookieMatcher"/> class.
/// </summary>
/// <param name="name">The name.</param>
/// <param name="pattern">The pattern.</param>
/// <param name="ignoreCase">The ignoreCase.</param>
public RequestMessageCookieMatcher([NotNull] string name, [NotNull] string pattern, bool ignoreCase = true)
{
Check.NotNull(name, nameof(name));
Check.NotNull(pattern, nameof(pattern));
Name = name;
Matchers = new IMatcher[] { new WildcardMatcher(pattern, ignoreCase) };
}
/// <summary>
/// Initializes a new instance of the <see cref="RequestMessageCookieMatcher"/> class.
/// </summary>
/// <param name="name">The name.</param>
/// <param name="matchers">The matchers.</param>
public RequestMessageCookieMatcher([NotNull] string name, [NotNull] params IMatcher[] matchers)
{
Check.NotNull(name, nameof(name));
Check.NotNull(matchers, nameof(matchers));
Name = name;
Matchers = matchers;
}
/// <summary>
/// Initializes a new instance of the <see cref="RequestMessageCookieMatcher"/> class.
/// </summary>
/// <param name="funcs">The funcs.</param>
public RequestMessageCookieMatcher([NotNull] params Func<IDictionary<string, string>, bool>[] funcs)
{
Check.NotNull(funcs, nameof(funcs));
Funcs = funcs;
}
/// <summary>
/// Determines whether the specified RequestMessage is match.
/// </summary>
/// <param name="requestMessage">The RequestMessage.</param>
/// <param name="requestMatchResult">The RequestMatchResult.</param>
/// <returns>
/// A value between 0.0 - 1.0 of the similarity.
/// </returns>
public double GetMatchingScore(RequestMessage requestMessage, RequestMatchResult requestMatchResult)
{
double score = IsMatch(requestMessage);
requestMatchResult.TotalScore += score;
requestMatchResult.TotalNumber++;
return score;
}
private double IsMatch(RequestMessage requestMessage)
{
if (requestMessage.Cookies == null)
return MatchScores.Mismatch;
if (Funcs != null)
return MatchScores.ToScore(Funcs.Any(f => f(requestMessage.Cookies)));
if (Matchers == null)
return MatchScores.Mismatch;
if (!requestMessage.Cookies.ContainsKey(Name))
return MatchScores.Mismatch;
string value = requestMessage.Cookies[Name];
return Matchers.Max(m => m.IsMatch(value));
}
}
}

View File

@@ -0,0 +1,105 @@
using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using WireMock.Validation;
namespace WireMock.Matchers.Request
{
/// <summary>
/// The request header matcher.
/// </summary>
public class RequestMessageHeaderMatcher : IRequestMatcher
{
/// <summary>
/// The functions
/// </summary>
public Func<IDictionary<string, string>, bool>[] Funcs { get; }
/// <summary>
/// The name
/// </summary>
public string Name { get; }
/// <value>
/// The matchers.
/// </value>
public IMatcher[] Matchers { get; }
/// <summary>
/// Initializes a new instance of the <see cref="RequestMessageHeaderMatcher"/> class.
/// </summary>
/// <param name="name">The name.</param>
/// <param name="pattern">The pattern.</param>
/// <param name="ignoreCase">The ignoreCase.</param>
public RequestMessageHeaderMatcher([NotNull] string name, [NotNull] string pattern, bool ignoreCase = true)
{
Check.NotNull(name, nameof(name));
Check.NotNull(pattern, nameof(pattern));
Name = name;
Matchers = new IMatcher[] { new WildcardMatcher(pattern, ignoreCase) };
}
/// <summary>
/// Initializes a new instance of the <see cref="RequestMessageHeaderMatcher"/> class.
/// </summary>
/// <param name="name">The name.</param>
/// <param name="matchers">The matchers.</param>
public RequestMessageHeaderMatcher([NotNull] string name, [NotNull] params IMatcher[] matchers)
{
Check.NotNull(name, nameof(name));
Check.NotNull(matchers, nameof(matchers));
Name = name;
Matchers = matchers;
}
/// <summary>
/// Initializes a new instance of the <see cref="RequestMessageHeaderMatcher"/> class.
/// </summary>
/// <param name="funcs">The funcs.</param>
public RequestMessageHeaderMatcher([NotNull] params Func<IDictionary<string, string>, bool>[] funcs)
{
Check.NotNull(funcs, nameof(funcs));
Funcs = funcs;
}
/// <summary>
/// Determines whether the specified RequestMessage is match.
/// </summary>
/// <param name="requestMessage">The RequestMessage.</param>
/// <param name="requestMatchResult">The RequestMatchResult.</param>
/// <returns>
/// A value between 0.0 - 1.0 of the similarity.
/// </returns>
public double GetMatchingScore(RequestMessage requestMessage, RequestMatchResult requestMatchResult)
{
double score = IsMatch(requestMessage);
requestMatchResult.TotalScore += score;
requestMatchResult.TotalNumber++;
return score;
}
private double IsMatch(RequestMessage requestMessage)
{
if (requestMessage.Headers == null)
return MatchScores.Mismatch;
if (Funcs != null)
return MatchScores.ToScore(Funcs.Any(f => f(requestMessage.Headers)));
if (Matchers == null)
return MatchScores.Mismatch;
if (!requestMessage.Headers.ContainsKey(Name))
return MatchScores.Mismatch;
string value = requestMessage.Headers[Name];
return Matchers.Max(m => m.IsMatch(value));
}
}
}

View File

@@ -0,0 +1,52 @@
using System.Linq;
using JetBrains.Annotations;
using WireMock.Validation;
namespace WireMock.Matchers.Request
{
/// <summary>
/// The request verb matcher.
/// </summary>
internal class RequestMessageMethodMatcher : IRequestMatcher
{
/// <summary>
/// The methods
/// </summary>
public string[] Methods { get; }
/// <summary>
/// Initializes a new instance of the <see cref="RequestMessageMethodMatcher"/> class.
/// </summary>
/// <param name="methods">
/// The verb.
/// </param>
public RequestMessageMethodMatcher([NotNull] params string[] methods)
{
Check.NotNull(methods, nameof(methods));
Methods = methods.Select(v => v.ToLower()).ToArray();
}
/// <summary>
/// Determines whether the specified RequestMessage is match.
/// </summary>
/// <param name="requestMessage">The RequestMessage.</param>
/// <param name="requestMatchResult">The RequestMatchResult.</param>
/// <returns>
/// A value between 0.0 - 1.0 of the similarity.
/// </returns>
public double GetMatchingScore(RequestMessage requestMessage, RequestMatchResult requestMatchResult)
{
double score = IsMatch(requestMessage);
requestMatchResult.TotalScore += score;
requestMatchResult.TotalNumber++;
return score;
}
private double IsMatch(RequestMessage requestMessage)
{
return MatchScores.ToScore(Methods.Contains(requestMessage.Method));
}
}
}

View File

@@ -0,0 +1,85 @@
using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using WireMock.Util;
using WireMock.Validation;
namespace WireMock.Matchers.Request
{
/// <summary>
/// The request parameters matcher.
/// </summary>
public class RequestMessageParamMatcher : IRequestMatcher
{
/// <summary>
/// The funcs
/// </summary>
public Func<IDictionary<string, WireMockList<string>>, bool>[] Funcs { get; }
/// <summary>
/// The key
/// </summary>
public string Key { get; }
/// <summary>
/// The values
/// </summary>
public IEnumerable<string> Values { get; }
/// <summary>
/// Initializes a new instance of the <see cref="RequestMessageParamMatcher"/> class.
/// </summary>
/// <param name="key">
/// The key.
/// </param>
/// <param name="values">
/// The values.
/// </param>
public RequestMessageParamMatcher([NotNull] string key, [CanBeNull] IEnumerable<string> values)
{
Check.NotNull(key, nameof(key));
Key = key;
Values = values;
}
/// <summary>
/// Initializes a new instance of the <see cref="RequestMessageParamMatcher"/> class.
/// </summary>
/// <param name="funcs">The funcs.</param>
public RequestMessageParamMatcher([NotNull] params Func<IDictionary<string, WireMockList<string>>, bool>[] funcs)
{
Check.NotNull(funcs, nameof(funcs));
Funcs = funcs;
}
/// <summary>
/// Determines whether the specified RequestMessage is match.
/// </summary>
/// <param name="requestMessage">The RequestMessage.</param>
/// <param name="requestMatchResult">The RequestMatchResult.</param>
/// <returns>
/// A value between 0.0 - 1.0 of the similarity.
/// </returns>
public double GetMatchingScore(RequestMessage requestMessage, RequestMatchResult requestMatchResult)
{
double score = IsMatch(requestMessage);
requestMatchResult.TotalScore += score;
requestMatchResult.TotalNumber++;
return score;
}
private double IsMatch(RequestMessage requestMessage)
{
if (Funcs != null)
return MatchScores.ToScore(requestMessage.Query != null && Funcs.Any(f => f(requestMessage.Query)));
List<string> values = requestMessage.GetParameter(Key);
return MatchScores.ToScore(values?.Intersect(Values).Count() == Values.Count());
}
}
}

View File

@@ -0,0 +1,81 @@
using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using WireMock.Validation;
namespace WireMock.Matchers.Request
{
/// <summary>
/// The request path matcher.
/// </summary>
public class RequestMessagePathMatcher : IRequestMatcher
{
/// <summary>
/// The matcher.
/// </summary>
public IReadOnlyList<IMatcher> Matchers { get; }
/// <summary>
/// The path functions
/// </summary>
public Func<string, bool>[] Funcs { get; }
/// <summary>
/// Initializes a new instance of the <see cref="RequestMessagePathMatcher"/> class.
/// </summary>
/// <param name="paths">The paths.</param>
public RequestMessagePathMatcher([NotNull] params string[] paths) : this(paths.Select(path => new WildcardMatcher(path)).ToArray())
{
}
/// <summary>
/// Initializes a new instance of the <see cref="RequestMessagePathMatcher"/> class.
/// </summary>
/// <param name="matchers">The matchers.</param>
public RequestMessagePathMatcher([NotNull] params IMatcher[] matchers)
{
Check.NotNull(matchers, nameof(matchers));
Matchers = matchers;
}
/// <summary>
/// Initializes a new instance of the <see cref="RequestMessagePathMatcher"/> class.
/// </summary>
/// <param name="funcs">The path functions.</param>
public RequestMessagePathMatcher([NotNull] params Func<string, bool>[] funcs)
{
Check.NotNull(funcs, nameof(funcs));
Funcs = funcs;
}
/// <summary>
/// Determines whether the specified RequestMessage is match.
/// </summary>
/// <param name="requestMessage">The RequestMessage.</param>
/// <param name="requestMatchResult">The RequestMatchResult.</param>
/// <returns>
/// A value between 0.0 - 1.0 of the similarity.
/// </returns>
public double GetMatchingScore(RequestMessage requestMessage, RequestMatchResult requestMatchResult)
{
double score = IsMatch(requestMessage);
requestMatchResult.TotalScore += score;
requestMatchResult.TotalNumber++;
return score;
}
private double IsMatch(RequestMessage requestMessage)
{
if (Matchers != null)
return Matchers.Max(m => m.IsMatch(requestMessage.Path));
if (Funcs != null)
return MatchScores.ToScore(requestMessage.Path != null && Funcs.Any(func => func(requestMessage.Path)));
return MatchScores.Mismatch;
}
}
}

View File

@@ -0,0 +1,81 @@
using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using WireMock.Validation;
namespace WireMock.Matchers.Request
{
/// <summary>
/// The request url matcher.
/// </summary>
public class RequestMessageUrlMatcher : IRequestMatcher
{
/// <summary>
/// The matcher.
/// </summary>
public IReadOnlyList<IMatcher> Matchers { get; }
/// <summary>
/// The url functions
/// </summary>
public Func<string, bool>[] Funcs { get; }
/// <summary>
/// Initializes a new instance of the <see cref="RequestMessageUrlMatcher"/> class.
/// </summary>
/// <param name="urls">The urls.</param>
public RequestMessageUrlMatcher([NotNull] params string[] urls) : this(urls.Select(url => new WildcardMatcher(url)).ToArray())
{
}
/// <summary>
/// Initializes a new instance of the <see cref="RequestMessageUrlMatcher"/> class.
/// </summary>
/// <param name="matchers">The matchers.</param>
public RequestMessageUrlMatcher([NotNull] params IMatcher[] matchers)
{
Check.NotNull(matchers, nameof(matchers));
Matchers = matchers;
}
/// <summary>
/// Initializes a new instance of the <see cref="RequestMessageUrlMatcher"/> class.
/// </summary>
/// <param name="funcs">The url functions.</param>
public RequestMessageUrlMatcher([NotNull] params Func<string, bool>[] funcs)
{
Check.NotNull(funcs, nameof(funcs));
Funcs = funcs;
}
/// <summary>
/// Determines whether the specified RequestMessage is match.
/// </summary>
/// <param name="requestMessage">The RequestMessage.</param>
/// <param name="requestMatchResult">The RequestMatchResult.</param>
/// <returns>
/// A value between 0.0 - 1.0 of the similarity.
/// </returns>
public double GetMatchingScore(RequestMessage requestMessage, RequestMatchResult requestMatchResult)
{
double score = IsMatch(requestMessage);
requestMatchResult.TotalScore += score;
requestMatchResult.TotalNumber++;
return score;
}
private double IsMatch(RequestMessage requestMessage)
{
if (Matchers != null)
return Matchers.Max(matcher => matcher.IsMatch(requestMessage.Url));
if (Funcs != null)
return MatchScores.ToScore(requestMessage.Url != null && Funcs.Any(func => func(requestMessage.Url)));
return MatchScores.Mismatch;
}
}
}

View File

@@ -0,0 +1,114 @@
using System.Linq;
using JetBrains.Annotations;
using SimMetrics.Net;
using SimMetrics.Net.API;
using SimMetrics.Net.Metric;
using WireMock.Validation;
namespace WireMock.Matchers
{
/// <summary>
/// SimMetricsMatcher
/// </summary>
/// <seealso cref="IMatcher" />
public class SimMetricsMatcher : IMatcher
{
private readonly string[] _patterns;
private readonly SimMetricType _simMetricType;
/// <summary>
/// Initializes a new instance of the <see cref="SimMetricsMatcher"/> class.
/// </summary>
/// <param name="pattern">The pattern.</param>
/// <param name="simMetricType">The SimMetric Type</param>
public SimMetricsMatcher([NotNull] string pattern, SimMetricType simMetricType = SimMetricType.Levenstein) : this(new [] { pattern }, simMetricType)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="SimMetricsMatcher"/> class.
/// </summary>
/// <param name="patterns">The patterns.</param>
/// <param name="simMetricType">The SimMetric Type</param>
public SimMetricsMatcher([NotNull] string[] patterns, SimMetricType simMetricType = SimMetricType.Levenstein)
{
Check.NotEmpty(patterns, nameof(patterns));
_patterns = patterns;
_simMetricType = simMetricType;
}
/// <summary>
/// Determines whether the specified input is match.
/// </summary>
/// <param name="input">The input string</param>
/// <returns>A value between 0.0 - 1.0 of the similarity.</returns>
public double IsMatch(string input)
{
IStringMetric m = GetStringMetricType();
return MatchScores.ToScore(_patterns.Select(p => m.GetSimilarity(p, input)));
}
private IStringMetric GetStringMetricType()
{
switch (_simMetricType)
{
case SimMetricType.BlockDistance:
return new BlockDistance();
case SimMetricType.ChapmanLengthDeviation:
return new ChapmanLengthDeviation();
case SimMetricType.CosineSimilarity:
return new CosineSimilarity();
case SimMetricType.DiceSimilarity:
return new DiceSimilarity();
case SimMetricType.EuclideanDistance:
return new EuclideanDistance();
case SimMetricType.JaccardSimilarity:
return new JaccardSimilarity();
case SimMetricType.Jaro:
return new Jaro();
case SimMetricType.JaroWinkler:
return new JaroWinkler();
case SimMetricType.MatchingCoefficient:
return new MatchingCoefficient();
case SimMetricType.MongeElkan:
return new MongeElkan();
case SimMetricType.NeedlemanWunch:
return new NeedlemanWunch();
case SimMetricType.OverlapCoefficient:
return new OverlapCoefficient();
case SimMetricType.QGramsDistance:
return new QGramsDistance();
case SimMetricType.SmithWaterman:
return new SmithWaterman();
case SimMetricType.SmithWatermanGotoh:
return new SmithWatermanGotoh();
case SimMetricType.SmithWatermanGotohWindowedAffine:
return new SmithWatermanGotohWindowedAffine();
case SimMetricType.ChapmanMeanLength:
return new ChapmanMeanLength();
default:
return new Levenstein();
}
}
/// <summary>
/// Gets the pattern.
/// </summary>
/// <returns>Pattern</returns>
public string[] GetPatterns()
{
return _patterns;
}
/// <summary>
/// Gets the name.
/// </summary>
/// <returns>Name</returns>
public string GetName()
{
return $"SimMetricsMatcher.{_simMetricType}";
}
}
}

View File

@@ -0,0 +1,54 @@
using System.Linq;
using System.Text.RegularExpressions;
using JetBrains.Annotations;
namespace WireMock.Matchers
{
/// <summary>
/// WildcardMatcher
/// </summary>
/// <seealso cref="IMatcher" />
public class WildcardMatcher : RegexMatcher
{
private readonly string[] _patterns;
/// <summary>
/// Initializes a new instance of the <see cref="WildcardMatcher"/> class.
/// </summary>
/// <param name="pattern">The pattern.</param>
/// <param name="ignoreCase">IgnoreCase</param>
public WildcardMatcher([NotNull] string pattern, bool ignoreCase = false) : this(new [] { pattern }, ignoreCase)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="WildcardMatcher"/> class.
/// </summary>
/// <param name="patterns">The patterns.</param>
/// <param name="ignoreCase">IgnoreCase</param>
public WildcardMatcher([NotNull] string[] patterns, bool ignoreCase = false) : base(patterns.Select(pattern => "^" + Regex.Escape(pattern).Replace(@"\*", ".*").Replace(@"\?", ".") + "$").ToArray(), ignoreCase)
{
_patterns = patterns;
}
/// <summary>
/// Gets the pattern.
/// </summary>
/// <returns>Pattern</returns>
public override string[] GetPatterns()
{
return _patterns;
}
/// <summary>
/// Gets the name.
/// </summary>
/// <returns>
/// Name
/// </returns>
public override string GetName()
{
return "WildcardMatcher";
}
}
}

View File

@@ -0,0 +1,71 @@
using System;
using System.Linq;
using System.Xml;
using JetBrains.Annotations;
using WireMock.Validation;
using Wmhelp.XPath2;
namespace WireMock.Matchers
{
/// <summary>
/// XPath2Matcher
/// </summary>
/// <seealso cref="WireMock.Matchers.IMatcher" />
public class XPathMatcher : IMatcher
{
private readonly string[] _patterns;
/// <summary>
/// Initializes a new instance of the <see cref="XPathMatcher"/> class.
/// </summary>
/// <param name="patterns">The patterns.</param>
public XPathMatcher([NotNull] params string[] patterns)
{
Check.NotNull(patterns, nameof(patterns));
_patterns = patterns;
}
/// <summary>
/// Determines whether the specified input is match.
/// </summary>
/// <param name="input">The input string</param>
/// <returns>A value between 0.0 - 1.0 of the similarity.</returns>
public double IsMatch(string input)
{
if (input == null)
return MatchScores.Mismatch;
try
{
var nav = new XmlDocument { InnerXml = input }.CreateNavigator();
return MatchScores.ToScore(_patterns.Select(p => true.Equals(nav.XPath2Evaluate($"boolean({p})"))));
}
catch (Exception)
{
return MatchScores.Mismatch;
}
}
/// <summary>
/// Gets the patterns.
/// </summary>
/// <returns>Patterns</returns>
public string[] GetPatterns()
{
return _patterns;
}
/// <summary>
/// Gets the name.
/// </summary>
/// <returns>
/// Name
/// </returns>
public string GetName()
{
return "XPathMatcher";
}
}
}

View File

@@ -0,0 +1,47 @@
using System;
using JetBrains.Annotations;
using WireMock.Matchers;
namespace WireMock.RequestBuilders
{
/// <summary>
/// The BodyRequestBuilder interface.
/// </summary>
public interface IBodyRequestBuilder
{
/// <summary>
/// The with body.
/// </summary>
/// <param name="matcher">The matcher.</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithBody([NotNull] IMatcher matcher);
/// <summary>
/// The with body.
/// </summary>
/// <param name="body">The body.</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithBody(string body);
/// <summary>
/// The with body byte[].
/// </summary>
/// <param name="body">The body as byte[].</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithBody(byte[] body);
/// <summary>
/// The with body string func.
/// </summary>
/// <param name="body">The body string function.</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithBody([NotNull] Func<string, bool> body);
/// <summary>
/// The with body byte[] func.
/// </summary>
/// <param name="body">The body byte[] function.</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithBody([NotNull] Func<byte[], bool> body);
}
}

View File

@@ -0,0 +1,62 @@
using System;
using System.Collections.Generic;
using JetBrains.Annotations;
using WireMock.Matchers;
using WireMock.Matchers.Request;
namespace WireMock.RequestBuilders
{
/// <summary>
/// The HeadersAndCookieRequestBuilder interface.
/// </summary>
public interface IHeadersAndCookiesRequestBuilder : IBodyRequestBuilder, IRequestMatcher, IParamsRequestBuilder
{
/// <summary>
/// The with header.
/// </summary>
/// <param name="name">The name.</param>
/// <param name="pattern">The pattern.</param>
/// <param name="ignoreCase">ignore Case</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithHeader([NotNull] string name, string pattern, bool ignoreCase = true);
/// <summary>
/// The with header.
/// </summary>
/// <param name="name">The name.</param>
/// <param name="matchers">The matchers.</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithHeader([NotNull] string name, [NotNull] params IMatcher[] matchers);
/// <summary>
/// The with header.
/// </summary>
/// <param name="funcs">The headers funcs.</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithHeader([NotNull] params Func<IDictionary<string, string>, bool>[] funcs);
/// <summary>
/// The with cookie.
/// </summary>
/// <param name="name">The name.</param>
/// <param name="pattern">The pattern.</param>
/// <param name="ignoreCase">ignore Case</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithCookie([NotNull] string name, string pattern, bool ignoreCase = true);
/// <summary>
/// The with cookie.
/// </summary>
/// <param name="name">The name.</param>
/// <param name="matchers">The matchers.</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithCookie([NotNull] string name, [NotNull] params IMatcher[] matchers);
/// <summary>
/// The with cookie.
/// </summary>
/// <param name="cookieFuncs">The funcs.</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithCookie([NotNull] params Func<IDictionary<string, string>, bool>[] cookieFuncs);
}
}

View File

@@ -0,0 +1,65 @@
using JetBrains.Annotations;
namespace WireMock.RequestBuilders
{
/// <summary>
/// The MethodRequestBuilder interface.
/// </summary>
public interface IMethodRequestBuilder : IHeadersAndCookiesRequestBuilder
{
/// <summary>
/// The using get.
/// </summary>
/// <returns>
/// The <see cref="IRequestBuilder"/>.
/// </returns>
IRequestBuilder UsingGet();
/// <summary>
/// The using post.
/// </summary>
/// <returns>
/// The <see cref="IRequestBuilder"/>.
/// </returns>
IRequestBuilder UsingPost();
/// <summary>
/// The using delete.
/// </summary>
/// <returns>
/// The <see cref="IRequestBuilder"/>.
/// </returns>
IRequestBuilder UsingDelete();
/// <summary>
/// The using put.
/// </summary>
/// <returns>
/// The <see cref="IRequestBuilder"/>.
/// </returns>
IRequestBuilder UsingPut();
/// <summary>
/// The using head.
/// </summary>
/// <returns>
/// The <see cref="IRequestBuilder"/>.
/// </returns>
IRequestBuilder UsingHead();
/// <summary>
/// The using any verb.
/// </summary>
/// <returns>
/// The <see cref="IRequestBuilder"/>.
/// </returns>
IRequestBuilder UsingAnyVerb();
/// <summary>
/// The using verb.
/// </summary>
/// <param name="verbs">The verb.</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder UsingVerb([NotNull] params string[] verbs);
}
}

View File

@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using JetBrains.Annotations;
using WireMock.Util;
namespace WireMock.RequestBuilders
{
/// <summary>
/// The ParametersRequestBuilder interface.
/// </summary>
public interface IParamsRequestBuilder
{
/// <summary>
/// The with parameters.
/// </summary>
/// <param name="key">The key.</param>
/// <param name="values">The values.</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithParam([NotNull] string key, [CanBeNull] params string[] values);
/// <summary>
/// The with parameters.
/// </summary>
/// <param name="funcs">The funcs.</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithParam([NotNull] params Func<IDictionary<string, WireMockList<string>>, bool>[] funcs);
}
}

View File

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

View File

@@ -0,0 +1,54 @@
using System;
using JetBrains.Annotations;
using WireMock.Matchers;
namespace WireMock.RequestBuilders
{
/// <summary>
/// IUrlAndPathRequestBuilder
/// </summary>
public interface IUrlAndPathRequestBuilder : IMethodRequestBuilder
{
/// <summary>
/// The with path.
/// </summary>
/// <param name="matchers">The matchers.</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithPath([NotNull] params IMatcher[] matchers);
/// <summary>
/// The with path.
/// </summary>
/// <param name="paths">The paths.</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithPath([NotNull] params string[] paths);
/// <summary>
/// The with path.
/// </summary>
/// <param name="funcs">The path funcs.</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithPath([NotNull] params Func<string, bool>[] funcs);
/// <summary>
/// The with url.
/// </summary>
/// <param name="matchers">The matchers.</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithUrl([NotNull] params IMatcher[] matchers);
/// <summary>
/// The with url.
/// </summary>
/// <param name="urls">The urls.</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithUrl([NotNull] params string[] urls);
/// <summary>
/// The with path.
/// </summary>
/// <param name="func">The path func.</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithUrl([NotNull] params Func<string, bool>[] func);
}
}

View File

@@ -0,0 +1,407 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using WireMock.Matchers;
using WireMock.Matchers.Request;
using WireMock.Util;
using WireMock.Validation;
namespace WireMock.RequestBuilders
{
/// <summary>
/// The requests.
/// </summary>
public class Request : RequestMessageCompositeMatcher, IRequestBuilder
{
private readonly IList<IRequestMatcher> _requestMatchers;
/// <summary>
/// Creates this instance.
/// </summary>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
public static IRequestBuilder Create()
{
return new Request(new List<IRequestMatcher>());
}
/// <summary>
/// Initializes a new instance of the <see cref="Request"/> class.
/// </summary>
/// <param name="requestMatchers">The request matchers.</param>
private Request(IList<IRequestMatcher> requestMatchers) : base(requestMatchers)
{
_requestMatchers = requestMatchers;
}
/// <summary>
/// Gets the request message matchers.
/// </summary>
/// <typeparam name="T">Type of IRequestMatcher</typeparam>
/// <returns>A List{T}</returns>
public IList<T> GetRequestMessageMatchers<T>() where T : IRequestMatcher
{
return new ReadOnlyCollection<T>(_requestMatchers.Where(rm => rm is T).Cast<T>().ToList());
}
/// <summary>
/// Gets the request message matcher.
/// </summary>
/// <typeparam name="T">Type of IRequestMatcher</typeparam>
/// <returns>A RequestMatcher</returns>
public T GetRequestMessageMatcher<T>() where T : IRequestMatcher
{
return _requestMatchers.Where(rm => rm is T).Cast<T>().FirstOrDefault();
}
/// <summary>
/// The with path.
/// </summary>
/// <param name="matchers">The matchers.</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
public IRequestBuilder WithPath(params IMatcher[] matchers)
{
Check.NotEmpty(matchers, nameof(matchers));
_requestMatchers.Add(new RequestMessagePathMatcher(matchers));
return this;
}
/// <summary>
/// The with path.
/// </summary>
/// <param name="paths">The paths.</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
public IRequestBuilder WithPath(params string[] paths)
{
Check.NotEmpty(paths, nameof(paths));
_requestMatchers.Add(new RequestMessagePathMatcher(paths));
return this;
}
/// <summary>
/// The with path.
/// </summary>
/// <param name="funcs">The path func.</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
public IRequestBuilder WithPath(params Func<string, bool>[] funcs)
{
Check.NotEmpty(funcs, nameof(funcs));
_requestMatchers.Add(new RequestMessagePathMatcher(funcs));
return this;
}
/// <summary>
/// The with url.
/// </summary>
/// <param name="matchers">The matchers.</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
public IRequestBuilder WithUrl(params IMatcher[] matchers)
{
Check.NotEmpty(matchers, nameof(matchers));
_requestMatchers.Add(new RequestMessageUrlMatcher(matchers));
return this;
}
/// <summary>
/// The with url.
/// </summary>
/// <param name="urls">The urls.</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
public IRequestBuilder WithUrl(params string[] urls)
{
Check.NotEmpty(urls, nameof(urls));
_requestMatchers.Add(new RequestMessageUrlMatcher(urls));
return this;
}
/// <summary>
/// The with url.
/// </summary>
/// <param name="funcs">The url func.</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
public IRequestBuilder WithUrl(params Func<string, bool>[] funcs)
{
Check.NotEmpty(funcs, nameof(funcs));
_requestMatchers.Add(new RequestMessageUrlMatcher(funcs));
return this;
}
/// <summary>
/// The using get.
/// </summary>
/// <returns>
/// The <see cref="IRequestBuilder"/>.
/// </returns>
public IRequestBuilder UsingGet()
{
_requestMatchers.Add(new RequestMessageMethodMatcher("get"));
return this;
}
/// <summary>
/// The using post.
/// </summary>
/// <returns>
/// The <see cref="IRequestBuilder"/>.
/// </returns>
public IRequestBuilder UsingPost()
{
_requestMatchers.Add(new RequestMessageMethodMatcher("post"));
return this;
}
/// <summary>
/// The using put.
/// </summary>
/// <returns>
/// The <see cref="IRequestBuilder"/>.
/// </returns>
public IRequestBuilder UsingPut()
{
_requestMatchers.Add(new RequestMessageMethodMatcher("put"));
return this;
}
/// <summary>
/// The using delete.
/// </summary>
/// <returns>
/// The <see cref="IRequestBuilder"/>.
/// </returns>
public IRequestBuilder UsingDelete()
{
_requestMatchers.Add(new RequestMessageMethodMatcher("delete"));
return this;
}
/// <summary>
/// The using head.
/// </summary>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
public IRequestBuilder UsingHead()
{
_requestMatchers.Add(new RequestMessageMethodMatcher("head"));
return this;
}
/// <summary>
/// The using any verb.
/// </summary>
/// <returns>
/// The <see cref="IRequestBuilder"/>.
/// </returns>
public IRequestBuilder UsingAnyVerb()
{
var matchers = _requestMatchers.Where(m => m is RequestMessageMethodMatcher).ToList();
foreach (var matcher in matchers)
{
_requestMatchers.Remove(matcher);
}
return this;
}
/// <summary>
/// The using verb.
/// </summary>
/// <param name="verbs">The verbs.</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
public IRequestBuilder UsingVerb(params string[] verbs)
{
Check.NotEmpty(verbs, nameof(verbs));
_requestMatchers.Add(new RequestMessageMethodMatcher(verbs));
return this;
}
/// <summary>
/// The with body.
/// </summary>
/// <param name="body">
/// The body.
/// </param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
public IRequestBuilder WithBody(string body)
{
_requestMatchers.Add(new RequestMessageBodyMatcher(body));
return this;
}
/// <summary>
/// The with body byte[].
/// </summary>
/// <param name="body">
/// The body as byte[].
/// </param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
public IRequestBuilder WithBody(byte[] body)
{
_requestMatchers.Add(new RequestMessageBodyMatcher(body));
return this;
}
/// <summary>
/// The with body.
/// </summary>
/// <param name="func">
/// The body function.
/// </param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
public IRequestBuilder WithBody(Func<string, bool> func)
{
Check.NotNull(func, nameof(func));
_requestMatchers.Add(new RequestMessageBodyMatcher(func));
return this;
}
/// <summary>
/// The with body.
/// </summary>
/// <param name="func">
/// The body function.
/// </param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
public IRequestBuilder WithBody(Func<byte[], bool> func)
{
Check.NotNull(func, nameof(func));
_requestMatchers.Add(new RequestMessageBodyMatcher(func));
return this;
}
/// <summary>
/// The with body.
/// </summary>
/// <param name="matcher">The matcher.</param>
/// <returns>The <see cref="IRequestBuilder" />.</returns>
public IRequestBuilder WithBody(IMatcher matcher)
{
Check.NotNull(matcher, nameof(matcher));
_requestMatchers.Add(new RequestMessageBodyMatcher(matcher));
return this;
}
/// <summary>
/// The with parameters.
/// </summary>
/// <param name="key">
/// The key.
/// </param>
/// <param name="values">
/// The values.
/// </param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
public IRequestBuilder WithParam(string key, params string[] values)
{
Check.NotNull(key, nameof(key));
_requestMatchers.Add(new RequestMessageParamMatcher(key, values));
return this;
}
/// <summary>
/// The with parameters.
/// </summary>
/// <param name="funcs">The funcs.</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
public IRequestBuilder WithParam(params Func<IDictionary<string, WireMockList<string>>, bool>[] funcs)
{
Check.NotEmpty(funcs, nameof(funcs));
_requestMatchers.Add(new RequestMessageParamMatcher(funcs));
return this;
}
/// <summary>
/// With header.
/// </summary>
/// <param name="name">The name.</param>
/// <param name="pattern">The pattern.</param>
/// <param name="ignoreCase">if set to <c>true</c> [ignore case].</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
public IRequestBuilder WithHeader(string name, string pattern, bool ignoreCase = true)
{
Check.NotNull(name, nameof(name));
Check.NotNull(pattern, nameof(pattern));
_requestMatchers.Add(new RequestMessageHeaderMatcher(name, pattern, ignoreCase));
return this;
}
/// <summary>
/// With header.
/// </summary>
/// <param name="name">The name.</param>
/// <param name="matchers">The matchers.</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
public IRequestBuilder WithHeader(string name, params IMatcher[] matchers)
{
Check.NotNull(name, nameof(name));
Check.NotEmpty(matchers, nameof(matchers));
_requestMatchers.Add(new RequestMessageHeaderMatcher(name, matchers));
return this;
}
/// <summary>
/// With header.
/// </summary>
/// <param name="funcs">The funcs.</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
public IRequestBuilder WithHeader(params Func<IDictionary<string, string>, bool>[] funcs)
{
Check.NotEmpty(funcs, nameof(funcs));
_requestMatchers.Add(new RequestMessageHeaderMatcher(funcs));
return this;
}
/// <summary>
/// With cookie.
/// </summary>
/// <param name="name">The name.</param>
/// <param name="pattern">The pattern.</param>
/// <param name="ignoreCase">if set to <c>true</c> [ignore case].</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
public IRequestBuilder WithCookie(string name, string pattern, bool ignoreCase = true)
{
_requestMatchers.Add(new RequestMessageCookieMatcher(name, pattern, ignoreCase));
return this;
}
/// <summary>
/// With cookie.
/// </summary>
/// <param name="name">The name.</param>
/// <param name="matchers">The matchers.</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
public IRequestBuilder WithCookie(string name, params IMatcher[] matchers)
{
Check.NotEmpty(matchers, nameof(matchers));
_requestMatchers.Add(new RequestMessageCookieMatcher(name, matchers));
return this;
}
/// <summary>
/// With header.
/// </summary>
/// <param name="funcs">The funcs.</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
public IRequestBuilder WithCookie(params Func<IDictionary<string, string>, bool>[] funcs)
{
Check.NotEmpty(funcs, nameof(funcs));
_requestMatchers.Add(new RequestMessageCookieMatcher(funcs));
return this;
}
}
}

View File

@@ -0,0 +1,127 @@
using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using WireMock.Util;
using WireMock.Validation;
using System.Text;
namespace WireMock
{
/// <summary>
/// The request.
/// </summary>
public class RequestMessage
{
/// <summary>
/// Gets the url.
/// </summary>
public string Url { get; private set; }
/// <summary>
/// Gets the DateTime.
/// </summary>
public DateTime DateTime { get; set; }
/// <summary>
/// Gets the path.
/// </summary>
public string Path { get; }
/// <summary>
/// Gets the method.
/// </summary>
public string Method { get; }
/// <summary>
/// Gets the headers.
/// </summary>
public IDictionary<string, string> Headers { get; }
/// <summary>
/// Gets the cookies.
/// </summary>
public IDictionary<string, string> Cookies { get; }
/// <summary>
/// Gets the query.
/// </summary>
public IDictionary<string, WireMockList<string>> Query { get; } = new Dictionary<string, WireMockList<string>>();
/// <summary>
/// Gets the bodyAsBytes.
/// </summary>
public byte[] BodyAsBytes { get; }
/// <summary>
/// Gets the body.
/// </summary>
public string Body { get; }
/// <summary>
/// Gets the body encoding.
/// </summary>
public Encoding BodyEncoding { get; }
/// <summary>
/// Initializes a new instance of the <see cref="RequestMessage"/> class.
/// </summary>
/// <param name="url">The original url.</param>
/// <param name="verb">The verb.</param>
/// <param name="bodyAsBytes">The bodyAsBytes byte[].</param>
/// <param name="body">The body string.</param>
/// <param name="bodyEncoding">The body encoding</param>
/// <param name="headers">The headers.</param>
/// <param name="cookies">The cookies.</param>
public RequestMessage([NotNull] Uri url, [NotNull] string verb, [CanBeNull] byte[] bodyAsBytes = null, [CanBeNull] string body = null, [CanBeNull] Encoding bodyEncoding = null, [CanBeNull] IDictionary<string, string> headers = null, [CanBeNull] IDictionary<string, string> cookies = null)
{
Check.NotNull(url, nameof(url));
Check.NotNull(verb, nameof(verb));
Url = url.ToString();
Path = url.AbsolutePath;
Method = verb.ToLower();
BodyAsBytes = bodyAsBytes;
Body = body;
BodyEncoding = bodyEncoding;
Headers = headers;
Cookies = cookies;
string query = url.Query;
if (!string.IsNullOrEmpty(query))
{
if (query.StartsWith("?"))
{
query = query.Substring(1);
}
Query = query.Split('&').Aggregate(
new Dictionary<string, WireMockList<string>>(),
(dict, term) =>
{
var parts = term.Split('=');
var key = parts[0];
if (!dict.ContainsKey(key))
{
dict.Add(key, new WireMockList<string>());
}
if (parts.Length == 2)
dict[key].Add(parts[1]);
return dict;
});
}
}
/// <summary>
/// The get a query parameter.
/// </summary>
/// <param name="key">The key.</param>
/// <returns>The query parameter.</returns>
public List<string> GetParameter(string key)
{
return Query.ContainsKey(key) ? Query[key] : null;
}
}
}

View File

@@ -0,0 +1,35 @@
using System.Text;
using JetBrains.Annotations;
namespace WireMock.ResponseBuilders
{
/// <summary>
/// The BodyResponseBuilder interface.
/// </summary>
public interface IBodyResponseBuilder : ITransformResponseBuilder
{
/// <summary>
/// The with body.
/// </summary>
/// <param name="body">The body.</param>
/// <param name="encoding">The body encoding.</param>
/// <returns>A <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithBody([NotNull] string body, [CanBeNull] Encoding encoding = null);
/// <summary>
/// The with body.
/// </summary>
/// <param name="body">The body.</param>
/// <param name="encoding">The body encoding.</param>
/// <returns>A <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithBodyAsJson([NotNull] object body, [CanBeNull] Encoding encoding = null);
/// <summary>
/// The with body as base64.
/// </summary>
/// <param name="bodyAsbase64">The body asbase64.</param>
/// <param name="encoding">The Encoding.</param>
/// <returns>A <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithBodyAsBase64([NotNull] string bodyAsbase64, [CanBeNull] Encoding encoding = null);
}
}

View File

@@ -0,0 +1,24 @@
using System;
namespace WireMock.ResponseBuilders
{
/// <summary>
/// The DelayResponseBuilder interface.
/// </summary>
public interface IDelayResponseBuilder : IResponseProvider
{
/// <summary>
/// The with delay.
/// </summary>
/// <param name="delay">The TimeSpan to delay.</param>
/// <returns>The <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithDelay(TimeSpan delay);
/// <summary>
/// The with delay.
/// </summary>
/// <param name="milliseconds">The milliseconds to delay.</param>
/// <returns>The <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithDelay(int milliseconds);
}
}

View File

@@ -0,0 +1,26 @@
using System.Collections.Generic;
using JetBrains.Annotations;
namespace WireMock.ResponseBuilders
{
/// <summary>
/// The HeadersResponseBuilder interface.
/// </summary>
public interface IHeadersResponseBuilder : IBodyResponseBuilder
{
/// <summary>
/// The with header.
/// </summary>
/// <param name="name">The name.</param>
/// <param name="value">The value.</param>
/// <returns>The <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithHeader([NotNull] string name, string value);
/// <summary>
/// The with headers.
/// </summary>
/// <param name="headers">The headers.</param>
/// <returns>The <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithHeaders([NotNull] IDictionary<string,string> headers);
}
}

View File

@@ -0,0 +1,9 @@
namespace WireMock.ResponseBuilders
{
/// <summary>
/// The ResponseBuilder interface.
/// </summary>
public interface IResponseBuilder : IStatusCodeResponseBuilder
{
}
}

View File

@@ -0,0 +1,40 @@
using System.Net;
namespace WireMock.ResponseBuilders
{
/// <summary>
/// The StatusCodeResponseBuilder interface.
/// </summary>
public interface IStatusCodeResponseBuilder : IHeadersResponseBuilder
{
/// <summary>
/// The with status code.
/// </summary>
/// <param name="code">
/// The code.
/// </param>
/// <returns>The <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithStatusCode(int code);
/// <summary>
/// The with status code.
/// </summary>
/// <param name="code">
/// The code.
/// </param>
/// <returns>The <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithStatusCode(HttpStatusCode code);
/// <summary>
/// The with Success status code (200).
/// </summary>
/// <returns>The <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithSuccess();
/// <summary>
/// The with NotFound status code (404).
/// </summary>
/// <returns>The <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithNotFound();
}
}

View File

@@ -0,0 +1,16 @@
namespace WireMock.ResponseBuilders
{
/// <summary>
/// The BodyResponseBuilder interface.
/// </summary>
public interface ITransformResponseBuilder : IDelayResponseBuilder
{
/// <summary>
/// The with transformer.
/// </summary>
/// <returns>
/// The <see cref="IResponseBuilder"/>.
/// </returns>
IResponseBuilder WithTransformer();
}
}

View File

@@ -0,0 +1,277 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using HandlebarsDotNet;
using JetBrains.Annotations;
using Newtonsoft.Json;
using WireMock.Validation;
namespace WireMock.ResponseBuilders
{
/// <summary>
/// The Response.
/// </summary>
public class Response : IResponseBuilder
{
/// <summary>
/// The delay
/// </summary>
public TimeSpan? Delay { get; private set; }
/// <summary>
/// Gets a value indicating whether [use transformer].
/// </summary>
/// <value>
/// <c>true</c> if [use transformer]; otherwise, <c>false</c>.
/// </value>
public bool UseTransformer { get; private set; }
/// <summary>
/// Gets the response message.
/// </summary>
/// <value>
/// The response message.
/// </value>
public ResponseMessage ResponseMessage { get; }
/// <summary>
/// Creates this instance.
/// </summary>
/// <param name="responseMessage">ResponseMessage</param>
/// <returns>A <see cref="IResponseBuilder"/>.</returns>
[PublicAPI]
public static IResponseBuilder Create([CanBeNull] ResponseMessage responseMessage = null)
{
var message = responseMessage ?? new ResponseMessage { StatusCode = (int)HttpStatusCode.OK };
return new Response(message);
}
/// <summary>
/// Creates this instance.
/// </summary>
/// <returns>A <see cref="IResponseBuilder"/>.</returns>
[PublicAPI]
public static IResponseBuilder Create([NotNull] Func<ResponseMessage> func)
{
Check.NotNull(func, nameof(func));
return new Response(func());
}
/// <summary>
/// Initializes a new instance of the <see cref="Response"/> class.
/// </summary>
/// <param name="responseMessage">
/// The response.
/// </param>
private Response(ResponseMessage responseMessage)
{
ResponseMessage = responseMessage;
}
/// <summary>
/// The with status code.
/// </summary>
/// <param name="code">The code.</param>
/// <returns>A <see cref="IResponseBuilder"/>.</returns>\
[PublicAPI]
public IResponseBuilder WithStatusCode(int code)
{
ResponseMessage.StatusCode = code;
return this;
}
/// <summary>
/// The with status code.
/// </summary>
/// <param name="code">The code.</param>
/// <returns>A <see cref="IResponseBuilder"/>.</returns>
[PublicAPI]
public IResponseBuilder WithStatusCode(HttpStatusCode code)
{
return WithStatusCode((int)code);
}
/// <summary>
/// The with Success status code (200).
/// </summary>
/// <returns>A <see cref="IResponseBuilder"/>.</returns>
[PublicAPI]
public IResponseBuilder WithSuccess()
{
return WithStatusCode((int)HttpStatusCode.OK);
}
/// <summary>
/// The with NotFound status code (404).
/// </summary>
/// <returns>The <see cref="IResponseBuilder"/>.</returns>
[PublicAPI]
public IResponseBuilder WithNotFound()
{
return WithStatusCode((int)HttpStatusCode.NotFound);
}
/// <summary>
/// The with header.
/// </summary>
/// <param name="name">The name.</param>
/// <param name="value">The value.</param>
/// <returns>The <see cref="IResponseBuilder"/>.</returns>
public IResponseBuilder WithHeader(string name, string value)
{
Check.NotNull(name, nameof(name));
ResponseMessage.AddHeader(name, value);
return this;
}
/// <summary>
/// The with headers.
/// </summary>
/// <param name="headers">The headers.</param>
/// <returns></returns>
public IResponseBuilder WithHeaders(IDictionary<string, string> headers)
{
ResponseMessage.Headers = headers;
return this;
}
/// <summary>
/// The with body.
/// </summary>
/// <param name="body">The body.</param>
/// <param name="encoding">The body encoding.</param>
/// <returns>A <see cref="IResponseBuilder"/>.</returns>
public IResponseBuilder WithBody(string body, Encoding encoding = null)
{
Check.NotNull(body, nameof(body));
ResponseMessage.Body = body;
ResponseMessage.BodyEncoding = encoding ?? Encoding.UTF8;
return this;
}
/// <summary>
/// The with body (AsJson object).
/// </summary>
/// <param name="body">The body.</param>
/// <param name="encoding">The body encoding.</param>
/// <returns>A <see cref="IResponseBuilder"/>.</returns>
public IResponseBuilder WithBodyAsJson(object body, Encoding encoding = null)
{
Check.NotNull(body, nameof(body));
string jsonBody = JsonConvert.SerializeObject(body, new JsonSerializerSettings { Formatting = Formatting.None, NullValueHandling = NullValueHandling.Ignore });
if (encoding != null && !encoding.Equals(Encoding.UTF8))
{
jsonBody = encoding.GetString(Encoding.UTF8.GetBytes(jsonBody));
ResponseMessage.BodyEncoding = encoding;
}
ResponseMessage.Body = jsonBody;
return this;
}
/// <summary>
/// The with body as base64.
/// </summary>
/// <param name="bodyAsbase64">The body asbase64.</param>
/// <param name="encoding">The Encoding.</param>
/// <returns>A <see cref="IResponseBuilder"/>.</returns>
public IResponseBuilder WithBodyAsBase64(string bodyAsbase64, Encoding encoding = null)
{
Check.NotNull(bodyAsbase64, nameof(bodyAsbase64));
encoding = encoding ?? Encoding.UTF8;
ResponseMessage.Body = encoding.GetString(Convert.FromBase64String(bodyAsbase64));
ResponseMessage.BodyEncoding = encoding;
return this;
}
/// <summary>
/// The with transformer.
/// </summary>
/// <returns>
/// The <see cref="IResponseBuilder"/>.
/// </returns>
public IResponseBuilder WithTransformer()
{
UseTransformer = true;
return this;
}
/// <summary>
/// The with delay.
/// </summary>
/// <param name="delay">The TimeSpan to delay.</param>
/// <returns>The <see cref="IResponseBuilder"/>.</returns>
public IResponseBuilder WithDelay(TimeSpan delay)
{
Check.Condition(delay, d => d > TimeSpan.Zero, nameof(delay));
Delay = delay;
return this;
}
/// <summary>
/// The with delay.
/// </summary>
/// <param name="milliseconds">The milliseconds to delay.</param>
/// <returns>The <see cref="IResponseBuilder"/>.</returns>
public IResponseBuilder WithDelay(int milliseconds)
{
return WithDelay(TimeSpan.FromMilliseconds(milliseconds));
}
/// <summary>
/// The provide response.
/// </summary>
/// <param name="requestMessage">
/// The request.
/// </param>
/// <returns>
/// The <see cref="Task"/>.
/// </returns>
public async Task<ResponseMessage> ProvideResponse(RequestMessage requestMessage)
{
ResponseMessage responseMessage;
if (UseTransformer)
{
responseMessage = new ResponseMessage { StatusCode = ResponseMessage.StatusCode, BodyOriginal = ResponseMessage.Body };
var template = new { request = requestMessage };
// Body
var templateBody = Handlebars.Compile(ResponseMessage.Body);
responseMessage.Body = templateBody(template);
// Headers
var newHeaders = new Dictionary<string, string>();
foreach (var header in ResponseMessage.Headers)
{
var templateHeaderKey = Handlebars.Compile(header.Key);
var templateHeaderValue = Handlebars.Compile(header.Value);
newHeaders.Add(templateHeaderKey(template), templateHeaderValue(template));
}
responseMessage.Headers = newHeaders;
}
else
{
responseMessage = ResponseMessage;
}
if (Delay != null)
await Task.Delay(Delay.Value);
return responseMessage;
}
}
}

View File

@@ -0,0 +1,51 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Text;
namespace WireMock
{
/// <summary>
/// The response.
/// </summary>
public class ResponseMessage
{
/// <summary>
/// Gets the headers.
/// </summary>
public IDictionary<string, string> Headers { get; set; } = new ConcurrentDictionary<string, string>();
/// <summary>
/// Gets or sets the status code.
/// </summary>
public int StatusCode { get; set; } = 200;
/// <summary>
/// Gets or sets the body.
/// </summary>
public string BodyOriginal { get; set; }
/// <summary>
/// Gets or sets the body.
/// </summary>
public string Body { get; set; }
/// <summary>
/// Gets or sets the body encoding.
/// </summary>
public Encoding BodyEncoding { get; set; } = new UTF8Encoding(false);
/// <summary>
/// The add header.
/// </summary>
/// <param name="name">
/// The name.
/// </param>
/// <param name="value">
/// The value.
/// </param>
public void AddHeader(string name, string value)
{
Headers.Add(name, value);
}
}
}

View File

@@ -0,0 +1,619 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using JetBrains.Annotations;
using Newtonsoft.Json;
using SimMetrics.Net;
using WireMock.Admin.Mappings;
using WireMock.Admin.Requests;
using WireMock.Admin.Settings;
using WireMock.Logging;
using WireMock.Matchers;
using WireMock.Matchers.Request;
using WireMock.RequestBuilders;
using WireMock.ResponseBuilders;
using WireMock.Util;
using WireMock.Validation;
namespace WireMock.Server
{
/// <summary>
/// The fluent mock server.
/// </summary>
public partial class FluentMockServer
{
private const string AdminMappingsFolder = @"\__admin\mappings\";
private const string AdminMappings = "/__admin/mappings";
private const string AdminRequests = "/__admin/requests";
private const string AdminSettings = "/__admin/settings";
private readonly RegexMatcher _adminMappingsGuidPathMatcher = new RegexMatcher(@"^\/__admin\/mappings\/(\{{0,1}([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}\}{0,1})$");
private readonly RegexMatcher _adminRequestsGuidPathMatcher = new RegexMatcher(@"^\/__admin\/requests\/(\{{0,1}([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}\}{0,1})$");
private readonly JsonSerializerSettings _settings = new JsonSerializerSettings
{
Formatting = Formatting.Indented,
NullValueHandling = NullValueHandling.Ignore,
};
private void ReadStaticMappings()
{
if (!Directory.Exists(Directory.GetCurrentDirectory() + AdminMappingsFolder))
return;
foreach (string filename in Directory.EnumerateFiles(Directory.GetCurrentDirectory() + AdminMappingsFolder))
{
var json = File.ReadAllText(filename);
DeserializeAndAddMapping(json, Guid.Parse(Path.GetFileNameWithoutExtension(filename)));
}
}
private void InitAdmin()
{
// __admin/settings
Given(Request.Create().WithPath(AdminSettings).UsingGet()).RespondWith(new DynamicResponseProvider(SettingsGet));
Given(Request.Create().WithPath(AdminSettings).UsingVerb("PUT", "POST")).RespondWith(new DynamicResponseProvider(SettingsUpdate));
// __admin/mappings
Given(Request.Create().WithPath(AdminMappings).UsingGet()).RespondWith(new DynamicResponseProvider(MappingsGet));
Given(Request.Create().WithPath(AdminMappings).UsingPost()).RespondWith(new DynamicResponseProvider(MappingsPost));
Given(Request.Create().WithPath(AdminMappings).UsingDelete()).RespondWith(new DynamicResponseProvider(MappingsDelete));
// __admin/mappings/reset
Given(Request.Create().WithPath(AdminMappings + "/reset").UsingPost()).RespondWith(new DynamicResponseProvider(MappingsDelete));
// __admin/mappings/{guid}
Given(Request.Create().WithPath(_adminMappingsGuidPathMatcher).UsingGet()).RespondWith(new DynamicResponseProvider(MappingGet));
Given(Request.Create().WithPath(_adminMappingsGuidPathMatcher).UsingPut().WithHeader("Content-Type", "application/json")).RespondWith(new DynamicResponseProvider(MappingPut));
Given(Request.Create().WithPath(_adminMappingsGuidPathMatcher).UsingDelete()).RespondWith(new DynamicResponseProvider(MappingDelete));
// __admin/mappings/save
Given(Request.Create().WithPath(AdminMappings + "/save").UsingPost()).RespondWith(new DynamicResponseProvider(MappingsSave));
// __admin/requests
Given(Request.Create().WithPath(AdminRequests).UsingGet()).RespondWith(new DynamicResponseProvider(RequestsGet));
Given(Request.Create().WithPath(AdminRequests).UsingDelete()).RespondWith(new DynamicResponseProvider(RequestsDelete));
// __admin/requests/reset
Given(Request.Create().WithPath(AdminRequests + "/reset").UsingPost()).RespondWith(new DynamicResponseProvider(RequestsDelete));
// __admin/request/{guid}
Given(Request.Create().WithPath(_adminRequestsGuidPathMatcher).UsingGet()).RespondWith(new DynamicResponseProvider(RequestGet));
Given(Request.Create().WithPath(_adminRequestsGuidPathMatcher).UsingDelete()).RespondWith(new DynamicResponseProvider(RequestDelete));
// __admin/requests/find
Given(Request.Create().WithPath(AdminRequests + "/find").UsingPost()).RespondWith(new DynamicResponseProvider(RequestsFind));
}
#region Settings
private ResponseMessage SettingsGet(RequestMessage requestMessage)
{
var model = new SettingsModel
{
AllowPartialMapping = _allowPartialMapping,
GlobalProcessingDelay = _requestProcessingDelay?.Milliseconds
};
return ToJson(model);
}
private ResponseMessage SettingsUpdate(RequestMessage requestMessage)
{
var settings = JsonConvert.DeserializeObject<SettingsModel>(requestMessage.Body);
if (settings.AllowPartialMapping != null)
_allowPartialMapping = settings.AllowPartialMapping.Value;
if (settings.GlobalProcessingDelay != null)
_requestProcessingDelay = TimeSpan.FromMilliseconds(settings.GlobalProcessingDelay.Value);
return new ResponseMessage { Body = "Settings updated" };
}
#endregion Settings
#region Mapping/{guid}
private ResponseMessage MappingGet(RequestMessage requestMessage)
{
Guid guid = Guid.Parse(requestMessage.Path.Substring(AdminMappings.Length + 1));
var mapping = Mappings.FirstOrDefault(m => !m.IsAdminInterface && m.Guid == guid);
if (mapping == null)
return new ResponseMessage { StatusCode = 404, Body = "Mapping not found" };
var model = ToMappingModel(mapping);
return ToJson(model);
}
private ResponseMessage MappingPut(RequestMessage requestMessage)
{
Guid guid = Guid.Parse(requestMessage.Path.TrimStart(AdminMappings.ToCharArray()));
var mappingModel = JsonConvert.DeserializeObject<MappingModel>(requestMessage.Body);
if (mappingModel.Request == null)
return new ResponseMessage { StatusCode = 400, Body = "Request missing" };
if (mappingModel.Response == null)
return new ResponseMessage { StatusCode = 400, Body = "Response missing" };
var requestBuilder = InitRequestBuilder(mappingModel.Request);
var responseBuilder = InitResponseBuilder(mappingModel.Response);
Given(requestBuilder)
.WithGuid(guid)
.RespondWith(responseBuilder);
return new ResponseMessage { Body = "Mapping added or updated" };
}
private ResponseMessage MappingDelete(RequestMessage requestMessage)
{
Guid guid = Guid.Parse(requestMessage.Path.Substring(AdminMappings.Length + 1));
if (DeleteMapping(guid))
return new ResponseMessage { Body = "Mapping removed" };
return new ResponseMessage { Body = "Mapping not found" };
}
#endregion Mapping/{guid}
#region Mappings
private ResponseMessage MappingsSave(RequestMessage requestMessage)
{
string folder = Directory.GetCurrentDirectory() + AdminMappingsFolder;
if (!Directory.Exists(folder))
Directory.CreateDirectory(folder);
foreach (var mapping in Mappings.Where(m => !m.IsAdminInterface))
{
var model = ToMappingModel(mapping);
string json = JsonConvert.SerializeObject(model, _settings);
File.WriteAllText(Path.Combine(folder, mapping.Guid + ".json"), json);
}
return new ResponseMessage { Body = "Mappings saved to disk" };
}
private ResponseMessage MappingsGet(RequestMessage requestMessage)
{
var result = new List<MappingModel>();
foreach (var mapping in Mappings.Where(m => !m.IsAdminInterface))
{
var model = ToMappingModel(mapping);
result.Add(model);
}
return ToJson(result);
}
private ResponseMessage MappingsPost(RequestMessage requestMessage)
{
try
{
DeserializeAndAddMapping(requestMessage.Body);
}
catch (ArgumentException a)
{
return new ResponseMessage { StatusCode = 400, Body = a.Message };
}
catch (Exception e)
{
return new ResponseMessage { StatusCode = 500, Body = e.ToString() };
}
return new ResponseMessage { StatusCode = 201, Body = "Mapping added" };
}
private void DeserializeAndAddMapping(string json, Guid? guid = null)
{
var mappingModel = JsonConvert.DeserializeObject<MappingModel>(json);
Check.NotNull(mappingModel, nameof(mappingModel));
Check.NotNull(mappingModel.Request, nameof(mappingModel.Request));
Check.NotNull(mappingModel.Response, nameof(mappingModel.Response));
var requestBuilder = InitRequestBuilder(mappingModel.Request);
var responseBuilder = InitResponseBuilder(mappingModel.Response);
IRespondWithAProvider respondProvider = Given(requestBuilder);
if (guid != null)
{
respondProvider = respondProvider.WithGuid(guid.Value);
}
else if (mappingModel.Guid != null && mappingModel.Guid != Guid.Empty)
{
respondProvider = respondProvider.WithGuid(mappingModel.Guid.Value);
}
if (mappingModel.Priority != null)
respondProvider = respondProvider.AtPriority(mappingModel.Priority.Value);
respondProvider.RespondWith(responseBuilder);
}
private ResponseMessage MappingsDelete(RequestMessage requestMessage)
{
ResetMappings();
return new ResponseMessage { Body = "Mappings deleted" };
}
#endregion Mappings
#region Request/{guid}
private ResponseMessage RequestGet(RequestMessage requestMessage)
{
Guid guid = Guid.Parse(requestMessage.Path.Substring(AdminRequests.Length + 1));
var entry = LogEntries.FirstOrDefault(r => !r.RequestMessage.Path.StartsWith("/__admin/") && r.Guid == guid);
if (entry == null)
return new ResponseMessage { StatusCode = 404, Body = "Request not found" };
var model = ToLogEntryModel(entry);
return ToJson(model);
}
private ResponseMessage RequestDelete(RequestMessage requestMessage)
{
Guid guid = Guid.Parse(requestMessage.Path.Substring(AdminRequests.Length + 1));
if (DeleteLogEntry(guid))
return new ResponseMessage { Body = "Request removed" };
return new ResponseMessage { Body = "Request not found" };
}
#endregion Request/{guid}
#region Requests
private ResponseMessage RequestsGet(RequestMessage requestMessage)
{
var result = LogEntries
.Where(r => !r.RequestMessage.Path.StartsWith("/__admin/"))
.Select(ToLogEntryModel);
return ToJson(result);
}
private LogEntryModel ToLogEntryModel(LogEntry logEntry)
{
return new LogEntryModel
{
Guid = logEntry.Guid,
Request = new LogRequestModel
{
DateTime = logEntry.RequestMessage.DateTime,
Path = logEntry.RequestMessage.Path,
AbsoleteUrl = logEntry.RequestMessage.Url,
Query = logEntry.RequestMessage.Query,
Method = logEntry.RequestMessage.Method,
Body = logEntry.RequestMessage.Body,
Headers = logEntry.RequestMessage.Headers,
Cookies = logEntry.RequestMessage.Cookies,
BodyEncoding = logEntry.RequestMessage.BodyEncoding != null ? new EncodingModel
{
EncodingName = logEntry.RequestMessage.BodyEncoding.EncodingName,
CodePage = logEntry.RequestMessage.BodyEncoding.CodePage,
WebName = logEntry.RequestMessage.BodyEncoding.WebName
} : null
},
Response = new LogResponseModel
{
StatusCode = logEntry.ResponseMessage.StatusCode,
Body = logEntry.ResponseMessage.Body,
BodyOriginal = logEntry.ResponseMessage.BodyOriginal,
Headers = logEntry.ResponseMessage.Headers,
BodyEncoding = logEntry.ResponseMessage.BodyEncoding != null ? new EncodingModel
{
EncodingName = logEntry.ResponseMessage.BodyEncoding.EncodingName,
CodePage = logEntry.ResponseMessage.BodyEncoding.CodePage,
WebName = logEntry.ResponseMessage.BodyEncoding.WebName
} : null
},
MappingGuid = logEntry.MappingGuid,
RequestMatchResult = logEntry.RequestMatchResult != null ? new LogRequestMatchModel
{
TotalScore = logEntry.RequestMatchResult.TotalScore,
TotalNumber = logEntry.RequestMatchResult.TotalNumber,
IsPerfectMatch = logEntry.RequestMatchResult.IsPerfectMatch,
AverageTotalScore = logEntry.RequestMatchResult.AverageTotalScore
} : null
};
}
private ResponseMessage RequestsDelete(RequestMessage requestMessage)
{
ResetLogEntries();
return new ResponseMessage { Body = "Requests deleted" };
}
#endregion Requests
#region Requests/find
private ResponseMessage RequestsFind(RequestMessage requestMessage)
{
var requestModel = JsonConvert.DeserializeObject<RequestModel>(requestMessage.Body);
var request = (Request)InitRequestBuilder(requestModel);
var dict = new Dictionary<LogEntry, RequestMatchResult>();
foreach (var logEntry in LogEntries.Where(le => !le.RequestMessage.Path.StartsWith("/__admin/")))
{
var requestMatchResult = new RequestMatchResult();
if (request.GetMatchingScore(logEntry.RequestMessage, requestMatchResult) > 0.99)
dict.Add(logEntry, requestMatchResult);
}
var result = dict.OrderBy(x => x.Value.AverageTotalScore).Select(x => x.Key);
return ToJson(result);
}
#endregion Requests/find
private IRequestBuilder InitRequestBuilder(RequestModel requestModel)
{
IRequestBuilder requestBuilder = Request.Create();
if (requestModel.Path != null)
{
string path = requestModel.Path as string;
if (path != null)
requestBuilder = requestBuilder.WithPath(path);
else
{
var pathModel = JsonUtils.ParseJTokenToObject<PathModel>(requestModel.Path);
if (pathModel?.Matchers != null)
requestBuilder = requestBuilder.WithPath(pathModel.Matchers.Select(Map).ToArray());
}
}
if (requestModel.Url != null)
{
string url = requestModel.Url as string;
if (url != null)
requestBuilder = requestBuilder.WithUrl(url);
else
{
var urlModel = JsonUtils.ParseJTokenToObject<UrlModel>(requestModel.Url);
if (urlModel?.Matchers != null)
requestBuilder = requestBuilder.WithUrl(urlModel.Matchers.Select(Map).ToArray());
}
}
if (requestModel.Methods != null)
requestBuilder = requestBuilder.UsingVerb(requestModel.Methods);
if (requestModel.Headers != null)
{
foreach (var headerModel in requestModel.Headers.Where(h => h.Matchers != null))
{
requestBuilder = requestBuilder.WithHeader(headerModel.Name, headerModel.Matchers.Select(Map).ToArray());
}
}
if (requestModel.Cookies != null)
{
foreach (var cookieModel in requestModel.Cookies.Where(c => c.Matchers != null))
{
requestBuilder = requestBuilder.WithCookie(cookieModel.Name, cookieModel.Matchers.Select(Map).ToArray());
}
}
if (requestModel.Params != null)
{
foreach (var paramModel in requestModel.Params.Where(p => p.Values != null))
{
requestBuilder = requestBuilder.WithParam(paramModel.Name, paramModel.Values.ToArray());
}
}
if (requestModel.Body?.Matcher != null)
{
var bodyMatcher = Map(requestModel.Body.Matcher);
requestBuilder = requestBuilder.WithBody(bodyMatcher);
}
return requestBuilder;
}
private IResponseBuilder InitResponseBuilder(ResponseModel responseModel)
{
IResponseBuilder responseBuilder = Response.Create();
if (responseModel.StatusCode.HasValue)
responseBuilder = responseBuilder.WithStatusCode(responseModel.StatusCode.Value);
if (responseModel.Headers != null)
responseBuilder = responseBuilder.WithHeaders(responseModel.Headers);
if (responseModel.Body != null)
responseBuilder = responseBuilder.WithBody(responseModel.Body, ToEncoding(responseModel.BodyEncoding));
else if (responseModel.BodyAsJson != null)
responseBuilder = responseBuilder.WithBodyAsJson(responseModel.BodyAsJson, ToEncoding(responseModel.BodyEncoding));
else if (responseModel.BodyAsBase64 != null)
responseBuilder = responseBuilder.WithBodyAsBase64(responseModel.BodyAsBase64, ToEncoding(responseModel.BodyEncoding));
if (responseModel.UseTransformer)
responseBuilder = responseBuilder.WithTransformer();
if (responseModel.Delay > 0)
responseBuilder = responseBuilder.WithDelay(responseModel.Delay.Value);
return responseBuilder;
}
private MappingModel ToMappingModel(Mapping mapping)
{
var request = (Request)mapping.RequestMatcher;
var response = (Response)mapping.Provider;
var pathMatchers = request.GetRequestMessageMatchers<RequestMessagePathMatcher>();
var urlMatchers = request.GetRequestMessageMatchers<RequestMessageUrlMatcher>();
var headerMatchers = request.GetRequestMessageMatchers<RequestMessageHeaderMatcher>();
var cookieMatchers = request.GetRequestMessageMatchers<RequestMessageCookieMatcher>();
var paramsMatchers = request.GetRequestMessageMatchers<RequestMessageParamMatcher>();
var bodyMatcher = request.GetRequestMessageMatcher<RequestMessageBodyMatcher>();
var methodMatcher = request.GetRequestMessageMatcher<RequestMessageMethodMatcher>();
return new MappingModel
{
Guid = mapping.Guid,
Priority = mapping.Priority,
Request = new RequestModel
{
Path = pathMatchers != null && pathMatchers.Any() ? new PathModel
{
Matchers = Map(pathMatchers.Where(m => m.Matchers != null).SelectMany(m => m.Matchers)),
Funcs = Map(pathMatchers.Where(m => m.Funcs != null).SelectMany(m => m.Funcs))
} : null,
Url = urlMatchers != null && urlMatchers.Any() ? new UrlModel
{
Matchers = Map(urlMatchers.Where(m => m.Matchers != null).SelectMany(m => m.Matchers)),
Funcs = Map(urlMatchers.Where(m => m.Funcs != null).SelectMany(m => m.Funcs))
} : null,
Methods = methodMatcher?.Methods,
Headers = headerMatchers != null && headerMatchers.Any() ? headerMatchers.Select(hm => new HeaderModel
{
Name = hm.Name,
Matchers = Map(hm.Matchers),
Funcs = Map(hm.Funcs)
}).ToList() : null,
Cookies = cookieMatchers != null && cookieMatchers.Any() ? cookieMatchers.Select(cm => new CookieModel
{
Name = cm.Name,
Matchers = Map(cm.Matchers),
Funcs = Map(cm.Funcs)
}).ToList() : null,
Params = paramsMatchers != null && paramsMatchers.Any() ? paramsMatchers?.Select(pm => new ParamModel
{
Name = pm.Key,
Values = pm.Values?.ToList(),
Funcs = Map(pm.Funcs)
}).ToList() : null,
Body = new BodyModel
{
Matcher = bodyMatcher != null ? Map(bodyMatcher.Matcher) : null,
Func = bodyMatcher != null ? Map(bodyMatcher.Func) : null,
DataFunc = bodyMatcher != null ? Map(bodyMatcher.DataFunc) : null
}
},
Response = new ResponseModel
{
StatusCode = response.ResponseMessage.StatusCode,
Headers = response.ResponseMessage.Headers,
Body = response.ResponseMessage.Body,
UseTransformer = response.UseTransformer,
Delay = response.Delay?.Milliseconds,
BodyEncoding = response.ResponseMessage.BodyEncoding != null ? new EncodingModel
{
EncodingName = response.ResponseMessage.BodyEncoding.EncodingName,
CodePage = response.ResponseMessage.BodyEncoding.CodePage,
WebName = response.ResponseMessage.BodyEncoding.WebName
} : null
}
};
}
private MatcherModel[] Map([CanBeNull] IEnumerable<IMatcher> matchers)
{
if (matchers == null || !matchers.Any())
return null;
return matchers.Select(Map).Where(x => x != null).ToArray();
}
private MatcherModel Map([CanBeNull] IMatcher matcher)
{
if (matcher == null)
return null;
var patterns = matcher.GetPatterns();
return new MatcherModel
{
Name = matcher.GetName(),
Pattern = patterns.Length == 1 ? patterns.First() : null,
Patterns = patterns.Length > 1 ? patterns : null
};
}
private string[] Map<T>([CanBeNull] IEnumerable<Func<T, bool>> funcs)
{
if (funcs == null || !funcs.Any())
return null;
return funcs.Select(Map).Where(x => x != null).ToArray();
}
private string Map<T>([CanBeNull] Func<T, bool> func)
{
return func?.ToString();
}
private IMatcher Map([CanBeNull] MatcherModel matcher)
{
if (matcher == null)
return null;
var parts = matcher.Name.Split('.');
string matcherName = parts[0];
string matcherType = parts.Length > 1 ? parts[1] : null;
string[] patterns = matcher.Patterns ?? new[] { matcher.Pattern };
switch (matcherName)
{
case "ExactMatcher":
return new ExactMatcher(patterns);
case "RegexMatcher":
return new RegexMatcher(patterns);
case "JsonPathMatcher":
return new JsonPathMatcher(patterns);
case "XPathMatcher":
return new XPathMatcher(matcher.Pattern);
case "WildcardMatcher":
return new WildcardMatcher(patterns, matcher.IgnoreCase == true);
case "SimMetricsMatcher":
SimMetricType type = SimMetricType.Levenstein;
if (!string.IsNullOrEmpty(matcherType) && !Enum.TryParse(matcherType, out type))
throw new NotSupportedException($"Matcher '{matcherName}' with Type '{matcherType}' is not supported.");
return new SimMetricsMatcher(matcher.Pattern, type);
default:
throw new NotSupportedException($"Matcher '{matcherName}' is not supported.");
}
}
private ResponseMessage ToJson<T>(T result)
{
return new ResponseMessage
{
Body = JsonConvert.SerializeObject(result, _settings),
StatusCode = 200,
Headers = new Dictionary<string, string> { { "Content-Type", "application/json" } }
};
}
private Encoding ToEncoding(EncodingModel encodingModel)
{
return encodingModel != null ? Encoding.GetEncoding(encodingModel.CodePage) : null;
}
}
}

View File

@@ -0,0 +1,465 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using JetBrains.Annotations;
using WireMock.Http;
using WireMock.Logging;
using WireMock.Matchers;
using WireMock.Matchers.Request;
using WireMock.RequestBuilders;
using WireMock.Validation;
namespace WireMock.Server
{
/// <summary>
/// The fluent mock server.
/// </summary>
public partial class FluentMockServer
{
private readonly TinyHttpServer _httpServer;
private IList<Mapping> _mappings = new List<Mapping>();
private readonly IList<LogEntry> _logEntries = new List<LogEntry>();
private readonly HttpListenerRequestMapper _requestMapper = new HttpListenerRequestMapper();
private readonly HttpListenerResponseMapper _responseMapper = new HttpListenerResponseMapper();
private readonly object _syncRoot = new object();
private TimeSpan? _requestProcessingDelay;
private bool _allowPartialMapping;
private IMatcher _authorizationMatcher;
/// <summary>
/// Gets the ports.
/// </summary>
/// <value>
/// The ports.
/// </value>
[PublicAPI]
public List<int> Ports { get; }
/// <summary>
/// Gets the urls.
/// </summary>
[PublicAPI]
public string[] Urls { get; }
/// <summary>
/// Gets the request logs.
/// </summary>
[PublicAPI]
public IEnumerable<LogEntry> LogEntries
{
get
{
lock (((ICollection)_logEntries).SyncRoot)
{
return new ReadOnlyCollection<LogEntry>(_logEntries);
}
}
}
/// <summary>
/// The search log-entries based on matchers.
/// </summary>
/// <param name="matchers">The matchers.</param>
/// <returns>The <see cref="IEnumerable"/>.</returns>
[PublicAPI]
public IEnumerable<LogEntry> FindLogEntries([NotNull] params IRequestMatcher[] matchers)
{
lock (((ICollection)_logEntries).SyncRoot)
{
var results = new Dictionary<LogEntry, RequestMatchResult>();
foreach (var log in _logEntries)
{
var requestMatchResult = new RequestMatchResult();
foreach (var matcher in matchers)
{
matcher.GetMatchingScore(log.RequestMessage, requestMatchResult);
}
if (requestMatchResult.AverageTotalScore > 0.99)
results.Add(log, requestMatchResult);
}
return new ReadOnlyCollection<LogEntry>(results.OrderBy(x => x.Value).Select(x => x.Key).ToList());
}
}
/// <summary>
/// Gets the mappings.
/// </summary>
[PublicAPI]
public IEnumerable<Mapping> Mappings
{
get
{
lock (((ICollection)_mappings).SyncRoot)
{
return new ReadOnlyCollection<Mapping>(_mappings);
}
}
}
/// <summary>
/// Start this FluentMockServer.
/// </summary>
/// <param name="port">The port.</param>
/// <param name="ssl">The SSL support.</param>
/// <returns>The <see cref="FluentMockServer"/>.</returns>
[PublicAPI]
public static FluentMockServer Start(int port = 0, bool ssl = false)
{
Check.Condition(port, p => p >= 0, nameof(port));
if (port == 0)
port = PortUtil.FindFreeTcpPort();
return new FluentMockServer(false, port, ssl);
}
/// <summary>
/// Start this FluentMockServer.
/// </summary>
/// <param name="urls">The urls to listen on.</param>
/// <returns>The <see cref="FluentMockServer"/>.</returns>
[PublicAPI]
public static FluentMockServer Start(params string[] urls)
{
Check.NotEmpty(urls, nameof(urls));
return new FluentMockServer(false, urls);
}
/// <summary>
/// Start this FluentMockServer with the admin interface.
/// </summary>
/// <param name="port">The port.</param>
/// <param name="ssl">The SSL support.</param>
/// <returns>The <see cref="FluentMockServer"/>.</returns>
[PublicAPI]
public static FluentMockServer StartWithAdminInterface(int port = 0, bool ssl = false)
{
Check.Condition(port, p => p >= 0, nameof(port));
if (port == 0)
port = PortUtil.FindFreeTcpPort();
return new FluentMockServer(true, port, ssl);
}
/// <summary>
/// Start this FluentMockServer with the admin interface.
/// </summary>
/// <param name="urls">The urls.</param>
/// <returns>The <see cref="FluentMockServer"/>.</returns>
[PublicAPI]
public static FluentMockServer StartWithAdminInterface(params string[] urls)
{
Check.NotEmpty(urls, nameof(urls));
return new FluentMockServer(true, urls);
}
/// <summary>
/// Adds the catch all mapping.
/// </summary>
[PublicAPI]
public void AddCatchAllMapping()
{
Given(Request.Create().WithPath("/*").UsingAnyVerb())
.WithGuid(Guid.Parse("90008000-0000-4444-a17e-669cd84f1f05"))
.AtPriority(1000)
.RespondWith(new DynamicResponseProvider(request => new ResponseMessage { StatusCode = 404, Body = "No matching mapping found" }));
}
private FluentMockServer(bool startAdminInterface, int port, bool ssl) : this(startAdminInterface, (ssl ? "https" : "http") + "://localhost:" + port + "/")
{
}
private FluentMockServer(bool startAdminInterface, params string[] urls)
{
Urls = urls;
_httpServer = new TinyHttpServer(HandleRequestAsync, urls);
Ports = _httpServer.Ports;
_httpServer.Start();
if (startAdminInterface)
{
InitAdmin();
}
ReadStaticMappings();
}
/// <summary>
/// Stop this server.
/// </summary>
[PublicAPI]
public void Stop()
{
_httpServer.Stop();
}
/// <summary>
/// Resets LogEntries and Mappings.
/// </summary>
[PublicAPI]
public void Reset()
{
ResetLogEntries();
ResetMappings();
}
/// <summary>
/// Resets the LogEntries.
/// </summary>
[PublicAPI]
public void ResetLogEntries()
{
lock (((ICollection)_logEntries).SyncRoot)
{
_logEntries.Clear();
}
}
/// <summary>
/// Deletes the mapping.
/// </summary>
/// <param name="guid">The unique identifier.</param>
[PublicAPI]
public bool DeleteLogEntry(Guid guid)
{
lock (((ICollection)_logEntries).SyncRoot)
{
// Check a logentry exists with the same GUID, if so, remove it.
var existing = _logEntries.FirstOrDefault(m => m.Guid == guid);
if (existing != null)
{
_logEntries.Remove(existing);
return true;
}
return false;
}
}
/// <summary>
/// Resets the Mappings.
/// </summary>
[PublicAPI]
public void ResetMappings()
{
lock (((ICollection)_mappings).SyncRoot)
{
_mappings = _mappings.Where(m => m.Provider is DynamicResponseProvider).ToList();
}
}
/// <summary>
/// Deletes the mapping.
/// </summary>
/// <param name="guid">The unique identifier.</param>
[PublicAPI]
public bool DeleteMapping(Guid guid)
{
lock (((ICollection)_mappings).SyncRoot)
{
// Check a mapping exists with the same GUID, if so, remove it.
var existingMapping = _mappings.FirstOrDefault(m => m.Guid == guid);
if (existingMapping != null)
{
_mappings.Remove(existingMapping);
return true;
}
return false;
}
}
/// <summary>
/// The add request processing delay.
/// </summary>
/// <param name="delay">
/// The delay.
/// </param>
[PublicAPI]
public void AddGlobalProcessingDelay(TimeSpan delay)
{
lock (_syncRoot)
{
_requestProcessingDelay = delay;
}
}
/// <summary>
/// Allows the partial mapping.
/// </summary>
[PublicAPI]
public void AllowPartialMapping()
{
lock (_syncRoot)
{
_allowPartialMapping = true;
}
}
/// <summary>
/// Sets the basic authentication.
/// </summary>
/// <param name="username">The username.</param>
/// <param name="password">The password.</param>
[PublicAPI]
public void SetBasicAuthentication([NotNull] string username, [NotNull] string password)
{
Check.NotNull(username, nameof(username));
Check.NotNull(password, nameof(password));
string authorization = Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes(username + ":" + password));
_authorizationMatcher = new RegexMatcher("^(?i)BASIC " + authorization + "$");
}
/// <summary>
/// The given.
/// </summary>
/// <param name="requestMatcher">The request matcher.</param>
/// <returns>The <see cref="IRespondWithAProvider"/>.</returns>
[PublicAPI]
public IRespondWithAProvider Given(IRequestMatcher requestMatcher)
{
return new RespondWithAProvider(RegisterMapping, requestMatcher);
}
/// <summary>
/// The register mapping.
/// </summary>
/// <param name="mapping">
/// The mapping.
/// </param>
private void RegisterMapping(Mapping mapping)
{
lock (((ICollection)_mappings).SyncRoot)
{
// Check a mapping exists with the same GUID, if so, remove it first.
DeleteMapping(mapping.Guid);
_mappings.Add(mapping);
}
}
/// <summary>
/// The log request.
/// </summary>
/// <param name="entry">The request.</param>
private void LogRequest(LogEntry entry)
{
lock (((ICollection)_logEntries).SyncRoot)
{
_logEntries.Add(entry);
}
}
/// <summary>
/// The handle request.
/// </summary>
/// <param name="ctx">The HttpListenerContext.</param>
private async void HandleRequestAsync(HttpListenerContext ctx)
{
if (_requestProcessingDelay > TimeSpan.Zero)
{
lock (_syncRoot)
{
Task.Delay(_requestProcessingDelay.Value).Wait();
}
}
var request = _requestMapper.Map(ctx.Request);
ResponseMessage response = null;
Mapping targetMapping = null;
RequestMatchResult requestMatchResult = null;
try
{
var mappings = _mappings
.Select(m => new { Mapping = m, MatchResult = m.IsRequestHandled(request) })
.ToList();
if (_allowPartialMapping)
{
var partialMappings = mappings
.Where(pm => pm.Mapping.IsAdminInterface && pm.MatchResult.IsPerfectMatch || !pm.Mapping.IsAdminInterface)
.OrderBy(m => m.MatchResult)
.ThenBy(m => m.Mapping.Priority)
.ToList();
var bestPartialMatch = partialMappings.FirstOrDefault(pm => pm.MatchResult.AverageTotalScore > 0.0);
targetMapping = bestPartialMatch?.Mapping;
requestMatchResult = bestPartialMatch?.MatchResult;
}
else
{
var perfectMatch = mappings
.OrderBy(m => m.Mapping.Priority)
.FirstOrDefault(m => m.MatchResult.IsPerfectMatch);
targetMapping = perfectMatch?.Mapping;
requestMatchResult = perfectMatch?.MatchResult;
}
if (targetMapping == null)
{
response = new ResponseMessage { StatusCode = 404, Body = "No matching mapping found" };
return;
}
if (targetMapping.IsAdminInterface && _authorizationMatcher != null)
{
string authorization;
bool present = request.Headers.TryGetValue("Authorization", out authorization);
if (!present || _authorizationMatcher.IsMatch(authorization) < 1.0)
{
response = new ResponseMessage { StatusCode = 401 };
return;
}
}
response = await targetMapping.ResponseTo(request);
}
catch (Exception ex)
{
response = new ResponseMessage { StatusCode = 500, Body = ex.ToString() };
}
finally
{
var log = new LogEntry
{
Guid = Guid.NewGuid(),
RequestMessage = request,
ResponseMessage = response,
MappingGuid = targetMapping?.Guid,
RequestMatchResult = requestMatchResult
};
LogRequest(log);
_responseMapper.Map(response, ctx.Response);
ctx.Response.Close();
}
}
}
}

View File

@@ -0,0 +1,39 @@
using System;
namespace WireMock.Server
{
/// <summary>
/// IRespondWithAProvider
/// </summary>
public interface IRespondWithAProvider
{
/// <summary>
/// Define a unique identifier for this mapping.
/// </summary>
/// <param name="guid">The unique identifier.</param>
/// <returns>The <see cref="IRespondWithAProvider"/>.</returns>
IRespondWithAProvider WithGuid(Guid guid);
/// <summary>
/// Define a unique identifier for this mapping.
/// </summary>
/// <param name="guid">The unique identifier.</param>
/// <returns>The <see cref="IRespondWithAProvider"/>.</returns>
IRespondWithAProvider WithGuid(string guid);
/// <summary>
/// Define the priority for this mapping.
/// </summary>
/// <param name="priority">The priority.</param>
/// <returns>The <see cref="IRespondWithAProvider"/>.</returns>
IRespondWithAProvider AtPriority(int priority);
/// <summary>
/// The respond with.
/// </summary>
/// <param name="provider">
/// The provider.
/// </param>
void RespondWith(IResponseProvider provider);
}
}

View File

@@ -0,0 +1,81 @@
using System;
using WireMock.Matchers.Request;
namespace WireMock.Server
{
/// <summary>
/// The respond with a provider.
/// </summary>
internal class RespondWithAProvider : IRespondWithAProvider
{
private int _priority;
private Guid? _guid;
/// <summary>
/// The _registration callback.
/// </summary>
private readonly RegistrationCallback _registrationCallback;
/// <summary>
/// The _request matcher.
/// </summary>
private readonly IRequestMatcher _requestMatcher;
/// <summary>
/// Initializes a new instance of the <see cref="RespondWithAProvider"/> class.
/// </summary>
/// <param name="registrationCallback">The registration callback.</param>
/// <param name="requestMatcher">The request matcher.</param>
public RespondWithAProvider(RegistrationCallback registrationCallback, IRequestMatcher requestMatcher)
{
_registrationCallback = registrationCallback;
_requestMatcher = requestMatcher;
}
/// <summary>
/// The respond with.
/// </summary>
/// <param name="provider">
/// The provider.
/// </param>
public void RespondWith(IResponseProvider provider)
{
var mappingGuid = _guid ?? Guid.NewGuid();
_registrationCallback(new Mapping(mappingGuid, _requestMatcher, provider, _priority));
}
/// <summary>
/// Define a unique identifier for this mapping.
/// </summary>
/// <param name="guid">The unique identifier.</param>
/// <returns>The <see cref="IRespondWithAProvider"/>.</returns>
public IRespondWithAProvider WithGuid(string guid)
{
return WithGuid(Guid.Parse(guid));
}
/// <summary>
/// Define a unique identifier for this mapping.
/// </summary>
/// <param name="guid">The unique identifier.</param>
/// <returns>The <see cref="IRespondWithAProvider"/>.</returns>
public IRespondWithAProvider WithGuid(Guid guid)
{
_guid = guid;
return this;
}
/// <summary>
/// Define the priority for this mapping.
/// </summary>
/// <param name="priority">The priority.</param>
/// <returns>The <see cref="IRespondWithAProvider"/>.</returns>
public IRespondWithAProvider AtPriority(int priority)
{
_priority = priority;
return this;
}
}
}

View File

@@ -0,0 +1,19 @@
using Newtonsoft.Json.Linq;
namespace WireMock.Util
{
internal static class JsonUtils
{
public static T ParseJTokenToObject<T>(object value)
{
if (value == null)
return default(T);
JToken token = value as JToken;
if (token == null)
return default(T);
return token.ToObject<T>();
}
}
}

View File

@@ -0,0 +1,50 @@
using System.Collections.Generic;
using System.Linq;
namespace WireMock.Util
{
/// <summary>
/// A special List which overrides the ToString() to return first value.
/// </summary>
/// <typeparam name="T">The generic type</typeparam>
/// <seealso cref="List{T}" />
public class WireMockList<T> : List<T>
{
/// <summary>
/// Initializes a new instance of the <see cref="WireMockList{T}"/> class.
/// </summary>
public WireMockList()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="WireMockList{T}"/> class.
/// </summary>
/// <param name="collection">The collection whose elements are copied to the new list.</param>
public WireMockList(params T[] collection) : base(collection)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="WireMockList{T}"/> class.
/// </summary>
/// <param name="collection">The collection whose elements are copied to the new list.</param>
public WireMockList(IEnumerable<T> collection) : base(collection)
{
}
/// <summary>
/// Returns a <see cref="string" /> that represents this instance.
/// </summary>
/// <returns>
/// A <see cref="string" /> that represents this instance.
/// </returns>
public override string ToString()
{
if (this != null && this.Any())
return this.First().ToString();
return base.ToString();
}
}
}

View File

@@ -0,0 +1,143 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using JetBrains.Annotations;
// Copied from https://github.com/aspnet/EntityFramework/blob/dev/src/Shared/Check.cs
namespace WireMock.Validation
{
[ExcludeFromCodeCoverage]
[DebuggerStepThrough]
internal static class Check
{
[ContractAnnotation("value:null => halt")]
public static T Condition<T>([NoEnumeration] T value, [NotNull] Predicate<T> condition, [InvokerParameterName] [NotNull] string parameterName)
{
NotNull(condition, nameof(condition));
NotNull(value, nameof(value));
if (!condition(value))
{
NotEmpty(parameterName, nameof(parameterName));
throw new ArgumentOutOfRangeException(parameterName);
}
return value;
}
[ContractAnnotation("value:null => halt")]
public static T NotNull<T>([NoEnumeration] T value, [InvokerParameterName] [NotNull] string parameterName)
{
if (ReferenceEquals(value, null))
{
NotEmpty(parameterName, nameof(parameterName));
throw new ArgumentNullException(parameterName);
}
return value;
}
[ContractAnnotation("value:null => halt")]
public static T NotNull<T>(
[NoEnumeration] T value,
[InvokerParameterName] [NotNull] string parameterName,
[NotNull] string propertyName)
{
if (ReferenceEquals(value, null))
{
NotEmpty(parameterName, nameof(parameterName));
NotEmpty(propertyName, nameof(propertyName));
throw new ArgumentException(CoreStrings.ArgumentPropertyNull(propertyName, parameterName));
}
return value;
}
[ContractAnnotation("value:null => halt")]
public static IList<T> NotEmpty<T>(IList<T> value, [InvokerParameterName] [NotNull] string parameterName)
{
NotNull(value, parameterName);
if (value.Count == 0)
{
NotEmpty(parameterName, nameof(parameterName));
throw new ArgumentException(CoreStrings.CollectionArgumentIsEmpty(parameterName));
}
return value;
}
[ContractAnnotation("value:null => halt")]
public static string NotEmpty(string value, [InvokerParameterName] [NotNull] string parameterName)
{
Exception e = null;
if (ReferenceEquals(value, null))
{
e = new ArgumentNullException(parameterName);
}
else if (value.Trim().Length == 0)
{
e = new ArgumentException(CoreStrings.ArgumentIsEmpty(parameterName));
}
if (e != null)
{
NotEmpty(parameterName, nameof(parameterName));
throw e;
}
return value;
}
public static string NullButNotEmpty(string value, [InvokerParameterName] [NotNull] string parameterName)
{
if (!ReferenceEquals(value, null)
&& (value.Length == 0))
{
NotEmpty(parameterName, nameof(parameterName));
throw new ArgumentException(CoreStrings.ArgumentIsEmpty(parameterName));
}
return value;
}
public static IList<T> HasNoNulls<T>(IList<T> value, [InvokerParameterName] [NotNull] string parameterName)
where T : class
{
NotNull(value, parameterName);
if (value.Any(e => e == null))
{
NotEmpty(parameterName, nameof(parameterName));
throw new ArgumentException(parameterName);
}
return value;
}
public static Type ValidEntityType(Type value, [InvokerParameterName] [NotNull] string parameterName)
{
if (!value.GetTypeInfo().IsClass)
{
NotEmpty(parameterName, nameof(parameterName));
throw new ArgumentException(CoreStrings.InvalidEntityType(value, parameterName));
}
return value;
}
}
}

View File

@@ -0,0 +1,44 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using JetBrains.Annotations;
// copied from https://github.com/aspnet/EntityFramework/blob/dev/src/Microsoft.EntityFrameworkCore/Properties/CoreStrings.resx
namespace WireMock.Validation
{
[ExcludeFromCodeCoverage]
internal static class CoreStrings
{
/// <summary>
/// The property '{property}' of the argument '{argument}' cannot be null.
/// </summary>
public static string ArgumentPropertyNull([CanBeNull] string property, [CanBeNull] string argument)
{
return string.Format(CultureInfo.CurrentCulture, $"The property '{property}' of the argument '{argument}' cannot be null.", property, argument);
}
/// <summary>
/// The string argument '{argumentName}' cannot be empty.
/// </summary>
public static string ArgumentIsEmpty([CanBeNull] string argumentName)
{
return string.Format(CultureInfo.CurrentCulture, $"The string argument '{argumentName}' cannot be empty.", argumentName);
}
/// <summary>
/// The entity type '{type}' provided for the argument '{argumentName}' must be a reference type.
/// </summary>
public static string InvalidEntityType([CanBeNull] Type type, [CanBeNull] string argumentName)
{
return string.Format(CultureInfo.CurrentCulture, $"The entity type '{type}' provided for the argument '{argumentName}' must be a reference type.", type, argumentName);
}
/// <summary>
/// The collection argument '{argumentName}' must contain at least one element.
/// </summary>
public static string CollectionArgumentIsEmpty([CanBeNull] string argumentName)
{
return string.Format(CultureInfo.CurrentCulture, $"The collection argument '{argumentName}' must contain at least one element.", argumentName);
}
}
}

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0.25420" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0.25420</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>d3804228-91f4-4502-9595-39584e5a01ad</ProjectGuid>
<RootNamespace>WireMock</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

View File

@@ -0,0 +1,15 @@
,
"netstandard1.3": {
"buildOptions": { "define": [ "NETSTANDARD" ] },
"imports": [
"dotnet5.4"
],
"dependencies": {
"System.Collections.Concurrent": "4.3.0",
"System.Diagnostics.Tools": "4.3.0",
"System.Linq": "4.3.0",
"System.Net.Http": "4.3.0",
"System.Net.Sockets": "4.3.0",
"System.Threading.Tasks": "4.3.0"
}
}

View File

@@ -0,0 +1,44 @@
{
"version": "1.0.1.2",
"title": "WireMock.Net",
"description": "Lightweight Http Mocking Server for .Net, inspired by WireMock from the Java landscape.",
"authors": [ "Alexandre Victoor", "Stef Heyenrath" ],
"packOptions": {
"summary": "Lightweight Http Mocking Server for .Net, inspired by WireMock from the Java landscape.",
"tags": [ "tdd", "mock", "http", "wiremock", "test", "server", "unittest" ],
"owners": [ "Stef Heyenrath" ],
"repository": {
"type": "git",
"url": "https://github.com/StefH/WireMock.Net"
},
"projectUrl": "https://github.com/StefH/WireMock.Net",
"iconUrl": "https://raw.githubusercontent.com/StefH/WireMock.Net/master/WireMock.Net-Logo.png",
"licenseUrl": "https://raw.githubusercontent.com/StefH/WireMock.Net/master/LICENSE",
"releaseNotes": "Added more Admin-Interface commands (save/find/reset/settings) and added support for body encoding."
},
"buildOptions": {
"xmlDoc": true
},
"dependencies": {
"JetBrains.Annotations": {
"version": "10.2.1",
"type": "build"
},
"Handlebars.Net": "1.8.0",
"Newtonsoft.Json": "6.0.8",
"SimMetrics.Net": "1.0.1",
"XPath2": "1.0.3.1"
},
"frameworks": {
"net45": {
"dependencies": {
},
"frameworkAssemblies": {
}
}
}
}

3
src/_build nuget.cmd Normal file
View File

@@ -0,0 +1,3 @@
dotnet restore
dotnet pack -c Release WireMock.Net\project.json
pause

View File

@@ -0,0 +1,291 @@
using System;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using NFluent;
using NUnit.Framework;
using WireMock.Matchers;
using WireMock.RequestBuilders;
using WireMock.ResponseBuilders;
using WireMock.Server;
namespace WireMock.Net.Tests
{
[TestFixture]
[Timeout(5000)]
public class FluentMockServerTests
{
private FluentMockServer _server;
[Test]
public void FluentMockServer_Admin_Mappings_Get()
{
var guid = Guid.Parse("90356dba-b36c-469a-a17e-669cd84f1f05");
_server = FluentMockServer.Start();
_server.Given(Request.Create().WithPath("/foo1").UsingGet())
.WithGuid(guid)
.RespondWith(Response.Create().WithStatusCode(201).WithBody("1"));
_server.Given(Request.Create().WithPath("/foo2").UsingGet())
.RespondWith(Response.Create().WithStatusCode(202).WithBody("2"));
var mappings = _server.Mappings.ToArray();
Check.That(mappings).HasSize(2);
Check.That(mappings.First().RequestMatcher).IsNotNull();
Check.That(mappings.First().Provider).IsNotNull();
Check.That(mappings.First().Guid).Equals(guid);
Check.That(mappings[1].Guid).Not.Equals(guid);
}
[Test]
public void FluentMockServer_Admin_Mappings_Add_SameGuid()
{
var guid = Guid.Parse("90356dba-b36c-469a-a17e-669cd84f1f05");
_server = FluentMockServer.Start();
_server.Given(Request.Create().WithPath("/1").UsingGet())
.WithGuid(guid)
.RespondWith(Response.Create().WithStatusCode(500));
var mappings = _server.Mappings.ToArray();
Check.That(mappings).HasSize(1);
Check.That(mappings.First().Guid).Equals(guid);
_server.Given(Request.Create().WithPath("/2").UsingGet())
.WithGuid(guid)
.RespondWith(Response.Create().WithStatusCode(500));
Check.That(mappings).HasSize(1);
Check.That(mappings.First().Guid).Equals(guid);
}
[Test]
public async Task FluentMockServer_Admin_Mappings_AtPriority()
{
_server = FluentMockServer.Start();
// given
_server.Given(Request.Create().WithPath("/1").UsingGet())
.AtPriority(2)
.RespondWith(Response.Create().WithStatusCode(200));
_server.Given(Request.Create().WithPath("/1").UsingGet())
.AtPriority(1)
.RespondWith(Response.Create().WithStatusCode(400));
var mappings = _server.Mappings.ToArray();
Check.That(mappings).HasSize(2);
Check.That(mappings[0].Priority).Equals(2);
Check.That(mappings[1].Priority).Equals(1);
// when
var response = await new HttpClient().GetAsync("http://localhost:" + _server.Ports[0] + "/1");
// then
Check.That((int)response.StatusCode).IsEqualTo(400);
}
[Test]
public async Task FluentMockServer_Admin_Requests_Get()
{
// given
_server = FluentMockServer.Start();
// when
await new HttpClient().GetAsync("http://localhost:" + _server.Ports[0] + "/foo");
// then
Check.That(_server.LogEntries).HasSize(1);
var requestLogged = _server.LogEntries.First();
Check.That(requestLogged.RequestMessage.Method).IsEqualTo("get");
Check.That(requestLogged.RequestMessage.BodyAsBytes).IsNull();
}
[Test]
public async Task Should_respond_to_request()
{
// given
_server = FluentMockServer.Start();
_server
.Given(Request.Create()
.WithPath("/foo")
.UsingGet())
.RespondWith(Response.Create()
.WithStatusCode(200)
.WithBody(@"{ msg: ""Hello world!""}"));
// when
var response = await new HttpClient().GetStringAsync("http://localhost:" + _server.Ports[0] + "/foo");
// then
Check.That(response).IsEqualTo(@"{ msg: ""Hello world!""}");
}
[Test]
public async Task Should_respond_to_request_bodyAsBase64()
{
// given
_server = FluentMockServer.Start();
_server.Given(Request.Create().WithPath("/foo").UsingGet()).RespondWith(Response.Create().WithBodyAsBase64("SGVsbG8gV29ybGQ/"));
// when
var response = await new HttpClient().GetStringAsync("http://localhost:" + _server.Ports[0] + "/foo");
// then
Check.That(response).IsEqualTo("Hello World?");
}
[Test]
public async Task Should_respond_404_for_unexpected_request()
{
// given
_server = FluentMockServer.Start();
// when
var response = await new HttpClient().GetAsync("http://localhost:" + _server.Ports[0] + "/foo");
// then
Check.That(response.StatusCode).IsEqualTo(HttpStatusCode.NotFound);
Check.That((int)response.StatusCode).IsEqualTo(404);
}
[Test]
public async Task Should_find_a_request_satisfying_a_request_spec()
{
// given
_server = FluentMockServer.Start();
// when
await new HttpClient().GetAsync("http://localhost:" + _server.Ports[0] + "/foo");
await new HttpClient().GetAsync("http://localhost:" + _server.Ports[0] + "/bar");
// then
var result = _server.FindLogEntries(Request.Create().WithPath(new RegexMatcher("^/b.*"))).ToList();
Check.That(result).HasSize(1);
var requestLogged = result.First();
Check.That(requestLogged.RequestMessage.Path).IsEqualTo("/bar");
Check.That(requestLogged.RequestMessage.Url).IsEqualTo("http://localhost:" + _server.Ports[0] + "/bar");
}
[Test]
public async Task Should_reset_requestlogs()
{
// given
_server = FluentMockServer.Start();
// when
await new HttpClient().GetAsync("http://localhost:" + _server.Ports[0] + "/foo");
_server.ResetLogEntries();
// then
Check.That(_server.LogEntries).IsEmpty();
}
[Test]
public void Should_reset_mappings()
{
// given
_server = FluentMockServer.Start();
_server
.Given(Request.Create()
.WithPath("/foo")
.UsingGet())
.RespondWith(Response.Create()
.WithBody(@"{ msg: ""Hello world!""}"));
// when
_server.ResetMappings();
// then
Check.That(_server.Mappings).IsEmpty();
Check.ThatAsyncCode(() => new HttpClient().GetStringAsync("http://localhost:" + _server.Ports[0] + "/foo"))
.ThrowsAny();
}
[Test]
public async Task Should_respond_a_redirect_without_body()
{
// given
_server = FluentMockServer.Start();
_server
.Given(Request.Create()
.WithPath("/foo")
.UsingGet())
.RespondWith(Response.Create()
.WithStatusCode(307)
.WithHeader("Location", "/bar"));
_server
.Given(Request.Create()
.WithPath("/bar")
.UsingGet())
.RespondWith(Response.Create()
.WithStatusCode(200)
.WithBody("REDIRECT SUCCESSFUL"));
// when
var response = await new HttpClient().GetStringAsync("http://localhost:" + _server.Ports[0] + "/foo");
// then
Check.That(response).IsEqualTo("REDIRECT SUCCESSFUL");
}
[Test]
public async Task Should_delay_responses_for_a_given_route()
{
// given
_server = FluentMockServer.Start();
_server
.Given(Request.Create()
.WithPath("/*"))
.RespondWith(Response.Create()
.WithBody(@"{ msg: ""Hello world!""}")
.WithDelay(TimeSpan.FromMilliseconds(200)));
// when
var watch = new Stopwatch();
watch.Start();
await new HttpClient().GetStringAsync("http://localhost:" + _server.Ports[0] + "/foo");
watch.Stop();
// then
Check.That(watch.ElapsedMilliseconds).IsGreaterThan(200);
}
[Test]
public async Task Should_delay_responses()
{
// given
_server = FluentMockServer.Start();
_server.AddGlobalProcessingDelay(TimeSpan.FromMilliseconds(200));
_server
.Given(Request.Create().WithPath("/*"))
.RespondWith(Response.Create().WithBody(@"{ msg: ""Hello world!""}"));
// when
var watch = new Stopwatch();
watch.Start();
await new HttpClient().GetStringAsync("http://localhost:" + _server.Ports[0] + "/foo");
watch.Stop();
// then
Check.That(watch.ElapsedMilliseconds).IsGreaterThan(200);
}
[TearDown]
public void ShutdownServer()
{
_server.Stop();
}
}
}

View File

@@ -0,0 +1,39 @@
using System.Diagnostics.CodeAnalysis;
using System.Net.Http;
using NFluent;
using NUnit.Framework;
using WireMock.Http;
[module:
SuppressMessage("StyleCop.CSharp.DocumentationRules",
"SA1600:ElementsMustBeDocumented",
Justification = "Reviewed. Suppression is OK here, as it's a tests class.")]
[module:
SuppressMessage("StyleCop.CSharp.DocumentationRules",
"SA1633:FileMustHaveHeader",
Justification = "Reviewed. Suppression is OK here, as unknown copyright and company.")]
namespace WireMock.Net.Tests.Http
{
[TestFixture]
public class TinyHttpServerTests
{
[Test]
public void Should_call_handler_on_request()
{
// given
var port = PortUtil.FindFreeTcpPort();
bool called = false;
var urlPrefix = "http://localhost:" + port + "/";
var server = new TinyHttpServer(ctx => called = true, urlPrefix);
server.Start();
// when
var httpClient = new HttpClient();
httpClient.GetAsync(urlPrefix).Wait(3000);
// then
Check.That(called).IsTrue();
}
}
}

View File

@@ -0,0 +1,147 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using NFluent;
using NUnit.Framework;
using WireMock.Http;
namespace WireMock.Net.Tests
{
[TestFixture]
public class HttpListenerRequestMapperTests
{
private MapperServer _server;
[SetUp]
public void StartListenerServer()
{
_server = MapperServer.Start();
}
[Test]
public async Task Should_map_uri_from_listener_request()
{
// given
var client = new HttpClient();
// when
await client.GetAsync(MapperServer.UrlPrefix + "toto");
// then
Check.That(MapperServer.LastRequestMessage).IsNotNull();
Check.That(MapperServer.LastRequestMessage.Path).IsEqualTo("/toto");
}
[Test]
public async Task Should_map_verb_from_listener_request()
{
// given
var client = new HttpClient();
// when
await client.PutAsync(MapperServer.UrlPrefix, new StringContent("Hello!"));
// then
Check.That(MapperServer.LastRequestMessage).IsNotNull();
Check.That(MapperServer.LastRequestMessage.Method).IsEqualTo("put");
}
[Test]
public async Task Should_map_body_from_listener_request()
{
// given
var client = new HttpClient();
// when
await client.PutAsync(MapperServer.UrlPrefix, new StringContent("Hello!"));
// then
Check.That(MapperServer.LastRequestMessage).IsNotNull();
Check.That(MapperServer.LastRequestMessage.Body).IsEqualTo("Hello!");
}
[Test]
public async Task Should_map_headers_from_listener_request()
{
// given
var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-Alex", "1706");
// when
await client.GetAsync(MapperServer.UrlPrefix);
// then
Check.That(MapperServer.LastRequestMessage).IsNotNull();
Check.That(MapperServer.LastRequestMessage.Headers).Not.IsNullOrEmpty();
Check.That(MapperServer.LastRequestMessage.Headers.Contains(new KeyValuePair<string, string>("X-Alex", "1706"))).IsTrue();
}
[Test]
public async Task Should_map_params_from_listener_request()
{
// given
var client = new HttpClient();
// when
await client.GetAsync(MapperServer.UrlPrefix + "index.html?id=toto");
// then
Check.That(MapperServer.LastRequestMessage).IsNotNull();
Check.That(MapperServer.LastRequestMessage.Path).EndsWith("/index.html");
Check.That(MapperServer.LastRequestMessage.GetParameter("id")).HasSize(1);
}
[TearDown]
public void StopListenerServer()
{
_server.Stop();
}
private class MapperServer : TinyHttpServer
{
private static volatile RequestMessage _lastRequestMessage;
private MapperServer(Action<HttpListenerContext> httpHandler, string urlPrefix) : base(httpHandler, urlPrefix)
{
}
public static RequestMessage LastRequestMessage
{
get
{
return _lastRequestMessage;
}
private set
{
_lastRequestMessage = value;
}
}
public static string UrlPrefix { get; private set; }
public new static MapperServer Start()
{
int port = PortUtil.FindFreeTcpPort();
UrlPrefix = "http://localhost:" + port + "/";
var server = new MapperServer(
context =>
{
LastRequestMessage = new HttpListenerRequestMapper().Map(context.Request);
context.Response.Close();
}, UrlPrefix);
((TinyHttpServer)server).Start();
return server;
}
public new void Stop()
{
base.Stop();
LastRequestMessage = null;
}
}
}
}

View File

@@ -0,0 +1,133 @@
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using NFluent;
using NUnit.Framework;
using WireMock.Http;
namespace WireMock.Net.Tests
{
[TestFixture]
public class HttpListenerResponseMapperTests
{
private TinyHttpServer _server;
private Task<HttpResponseMessage> _responseMsgTask;
[Test]
public void Should_map_status_code_from_original_response()
{
// given
var response = new ResponseMessage { StatusCode = 404 };
var httpListenerResponse = CreateHttpListenerResponse();
// when
new HttpListenerResponseMapper().Map(response, httpListenerResponse);
// then
Check.That(httpListenerResponse.StatusCode).IsEqualTo(404);
}
[Test]
public void Should_map_headers_from_original_response()
{
// given
var response = new ResponseMessage();
response.AddHeader("cache-control", "no-cache");
var httpListenerResponse = CreateHttpListenerResponse();
// when
new HttpListenerResponseMapper().Map(response, httpListenerResponse);
// then
Check.That(httpListenerResponse.Headers).HasSize(1);
Check.That(httpListenerResponse.Headers.Keys).Contains("cache-control");
Check.That(httpListenerResponse.Headers.Get("cache-control")).Contains("no-cache");
}
[Test]
public void Should_map_body_from_original_response()
{
// given
var response = new ResponseMessage
{
Body = "Hello !!!"
};
var httpListenerResponse = CreateHttpListenerResponse();
// when
new HttpListenerResponseMapper().Map(response, httpListenerResponse);
// then
var responseMessage = ToResponseMessage(httpListenerResponse);
Check.That(responseMessage).IsNotNull();
var contentTask = responseMessage.Content.ReadAsStringAsync();
Check.That(contentTask.Result).IsEqualTo("Hello !!!");
}
[Test]
public void Should_map_encoded_body_from_original_response()
{
// given
var response = new ResponseMessage
{
Body = "Hello !!!",
BodyEncoding = Encoding.ASCII
};
var httpListenerResponse = CreateHttpListenerResponse();
// when
new HttpListenerResponseMapper().Map(response, httpListenerResponse);
// then
Check.That(httpListenerResponse.ContentEncoding).Equals(Encoding.ASCII);
var responseMessage = ToResponseMessage(httpListenerResponse);
Check.That(responseMessage).IsNotNull();
var contentTask = responseMessage.Content.ReadAsStringAsync();
Check.That(contentTask.Result).IsEqualTo("Hello !!!");
}
[TearDown]
public void StopServer()
{
_server?.Stop();
}
/// <summary>
/// Dirty HACK to get HttpListenerResponse instances
/// </summary>
/// <returns>
/// The <see cref="HttpListenerResponse"/>.
/// </returns>
public HttpListenerResponse CreateHttpListenerResponse()
{
var port = PortUtil.FindFreeTcpPort();
var urlPrefix = "http://localhost:" + port + "/";
var responseReady = new AutoResetEvent(false);
HttpListenerResponse response = null;
_server = new TinyHttpServer(
context =>
{
response = context.Response;
responseReady.Set();
}, urlPrefix);
_server.Start();
_responseMsgTask = new HttpClient().GetAsync(urlPrefix);
responseReady.WaitOne();
return response;
}
public HttpResponseMessage ToResponseMessage(HttpListenerResponse listenerResponse)
{
listenerResponse.Close();
_responseMsgTask.Wait();
return _responseMsgTask.Result;
}
}
}

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