Compare commits

...

75 Commits

Author SHA1 Message Date
Stef Heyenrath dabe3a2a10 Merge branch 'master' into stef-IgnoreOpenApiErrors 2023-06-03 17:01:15 +02:00
Stef Heyenrath 1f1bc05f00 1.5.27 2023-06-03 17:00:18 +02:00
Stef Heyenrath c107e38e3b Fix WireMock.Net.FluentAssertions for net47 2023-06-02 12:03:12 +02:00
Stef Heyenrath b609191095 . 2023-06-01 21:14:13 +02:00
Stef Heyenrath b1ae9aaf46 Merge branch 'master' into stef-IgnoreOpenApiErrors 2023-06-01 21:04:21 +02:00
Stef Heyenrath a77c4fe1ac Add ".NET Framework 4.7" to WireMock.Net.FluentAssertions (#949) 2023-06-01 18:06:28 +02:00
Stef Heyenrath ad3ef83c5e . 2023-05-30 21:41:30 +02:00
Stef Heyenrath 35ffbbc7d9 RegexExampleValueGenerator 2023-05-28 23:10:09 +02:00
Stef Heyenrath c1e71707c5 Add warning logging when sending a request to a Webhook does not return status 200 (#946) 2023-05-28 10:53:54 +02:00
Oleg Nenashev 69499afe43 Update Slack link (#944)
Better to use https://slack.wiremock.org/ to have proper Slack signup
2023-05-26 10:01:16 +02:00
Stef Heyenrath aadac78577 add Slack 2023-05-25 23:49:12 +02:00
Stef Heyenrath 71393204cc 1.5.26 2023-05-25 21:36:01 +02:00
Cezary Piątek e5cc6f570c Code generator improvements (#940)
* Fix quotation marks escaping in multiline string

* Add support for JsonPartialMatcher and JsonPartialWildcardMatcher in mapping code generator
2023-05-25 20:59:13 +02:00
Stef Heyenrath 7c3a0c815d Add GetParameter method to IRequestMessage (#942) 2023-05-25 15:14:02 +02:00
Stef Heyenrath e61f08fe48 WireMockMiddleware should use HandleRequestsSynchronously correctly (#939) 2023-05-19 21:43:14 +02:00
Stef Heyenrath 11f4c47851 Add more unitests for CSharpFormatter utils (#938)
* Add unit-tests for CSharpFormatter

* .

* t
2023-05-19 16:14:26 +02:00
Stef Heyenrath 3956cd703b 1.5.25 (ReleaseNotes) 2023-05-13 11:17:03 +02:00
Stef Heyenrath 27682d0ce4 1.5.25 + Fixes in CSharpFormatter 2023-05-13 11:15:40 +02:00
Cezary Piątek 8444c8c506 Code generator improvements (#934)
* Handle new line escaping in C# mapping code generator

* Prevent date conversion when value persisted as string

* Handle object properties named as csharp keywords

* Refactor: Extract logic responsible for generating anonymous object definition to a separate class
2023-05-13 09:33:25 +02:00
Stef Heyenrath 6ef116a295 1.5.24 2023-05-07 14:48:20 +02:00
Stef Heyenrath 59195eaed8 Refactor some code (MappingConverter) 2023-05-07 14:42:00 +02:00
Cezary Piątek 7d9e450814 C# code generator improvements (#933)
* Escape quotes in generated C# strings

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

* C# .NET API

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

* update tests

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

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

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

* #630 Update replace settings type name to ProxyUrlReplaceSettings

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

* Fix formatting issues

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

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

* Reduce function complexity

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

* fix

* x

* ,

* .

* .

* .
2023-04-12 20:48:53 +02:00
Stef Heyenrath f3d52adbb2 1.5.22 2023-04-08 21:37:18 +02:00
Stef Heyenrath a8775c3b77 Include WireMockOpenApiParser project (#916)
* Fix some nullability warnings for WireMockOpenApiParser

* .

* .

* .

* opt

* FromText

* ab

* .

* private const string AdminOpenApi = "/__admin/openapi";

* fix test

* rnd

* .

* urldetails

* 0

* ,

* .

* tests

* .

* CompressionUtilsTests

* ut

* .
2023-04-08 21:25:17 +02:00
Stef Heyenrath 3e24e3452b Add Blogs 2023-04-01 11:21:16 +02:00
Walid Haidari 95bf8e31aa #912 add excluded params to proxy mapping (#914) 2023-03-24 16:35:09 +01:00
Stef Heyenrath 090989ea7f Update comments for models (#913) 2023-03-24 09:20:23 +01:00
Stef Heyenrath 651486f718 Make some classes internal + chnage some files to file-scoped namespaces 2023-03-22 21:57:23 +01:00
Stef Heyenrath 9dea577da1 1.5.21 2023-03-22 16:18:19 +01:00
Stef Heyenrath 7ca4294de6 Fixed QueryStringParser for UrlEncoded values (#911) 2023-03-22 16:15:50 +01:00
Stef Heyenrath 66245409f9 RequestBuilder : add WithBodyAsJson and WithBody (with IJsonConverter) (#908)
* RequestBuilder : add WithBodyAsJson and WithBody (with IJsonConverter)

* tests
2023-03-22 15:47:58 +01:00
Stef Heyenrath da6cb9fe0a 1.5.20 2023-03-19 10:23:24 +01:00
Stef Heyenrath b30e4faab6 Fix issue with application/x-www-form-urlencoded and ExactMatcher (#907) 2023-03-19 10:21:47 +01:00
Stef Heyenrath 1221d52c69 Add DeserializeFormUrl Encoded to the settings (#905)
* Add DeserializeFormUrlEncoded to Settings

* EmptyArray<>.Value

* .
2023-03-19 09:19:53 +01:00
Stef Heyenrath 52d2109c7e packagereleasenotes 2023-03-17 17:16:35 +01:00
Stef Heyenrath 30064b922b 1.5.19 2023-03-17 17:15:19 +01:00
Stef Heyenrath 78b94d2ebc Add WithBody with IDictionary (form-urlencoded values) (#903)
* .

* x

* fx

* fix

* f

* tests

* fix tests

* add tst
2023-03-17 17:08:45 +01:00
Stef Heyenrath 19701f5260 Update Handlebars.Net.Helpers to 2.3.15 (#904) 2023-03-15 18:30:34 +01:00
Stef Heyenrath 1269fb178f 1.5.18 2023-03-09 19:49:29 +01:00
Stef Heyenrath 7426bf76ee Add 'Data' to response which can be used during transforming the response (#893)
* Add 'Data' to response which can be used during transforming the response

* md

* hb

* fix

* Linq

* fix test

* v4

* 14

* .

* x

* remove

* s
2023-03-09 17:20:34 +01:00
dependabot[bot] 36c9d95abb Bump Microsoft.Owin in /examples/WireMock.Net.Service (#896)
Bumps [Microsoft.Owin](https://github.com/aspnet/AspNetKatana) from 2.0.2 to 4.2.2.
- [Release notes](https://github.com/aspnet/AspNetKatana/releases)
- [Commits](https://github.com/aspnet/AspNetKatana/compare/v2.0.2...v4.2.2)

---
updated-dependencies:
- dependency-name: Microsoft.Owin
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-09 17:20:15 +01:00
Stef Heyenrath 674fa89c3e ProxySettings : Add logic to not save some requests depending on HttpMethods (#900)
* Add ExcludedHttpMethods to ProxySettings

* tst

* fix

* SaveMappingSettings

* .
2023-03-09 15:28:52 +01:00
Stef Heyenrath 61cdc13fae Add extra test for WithCallback 2023-03-01 12:10:03 +01:00
Stef Heyenrath c344b73f45 Cleanup some code from JsonUtils.cs 2023-02-27 21:23:43 +01:00
Stef Heyenrath 2ac9ca207a 1.5.17 2023-02-25 12:53:41 +01:00
Stef Heyenrath f099f3a288 AdminApiMappingBuilder (#890)
* AdminApiMappingBuilder

* .

* IWireMockAdminApi

* add methods

* .
2023-02-25 12:47:06 +01:00
Stef Heyenrath 02b607cc95 Slow test (#882) 2023-02-18 18:09:21 +01:00
Stef Heyenrath 7ac89e85b7 Add WithBodyAsJson builder method with accepts a Func (#881)
* Add WithBodyAsJson builder method with accepts a Func

* ut
2023-02-06 20:50:11 +01:00
Stef Heyenrath cc4cf27101 1.5.16 2023-02-01 20:47:32 +01:00
Stef Heyenrath 6839b11d35 Add WithProxy(string proxyUrl, X509Certificate2 certificate) (#880) 2023-02-01 10:42:35 +01:00
Stef Heyenrath 1000f4409f 1.5.15 2023-01-29 10:27:53 +01:00
Stef Heyenrath 7fe2c8af78 Update REST Admin interface to support "Get Mapping(s) as C# Code" (#878)
* Add /__admin/mappings/code endpoint

* api

* fix

* .

* fix

* .

* .

* .
2023-01-29 10:24:58 +01:00
Stef Heyenrath 0fc664b404 1.5.14 2023-01-24 16:50:58 +01:00
Stef Heyenrath 770a670e53 Generate C# code from Mapping (#842)
* 1

* .

* v

* .

* .

* -

* b

* res b

* Fix UT

* .

* Verify

* v

* ...

* .

* .

* dir

* m
2023-01-24 16:45:47 +01:00
eseneckiy b4c8779d68 Fix Self referencing loop detected for property 'Parent' with type 'System.Globalization.CultureInfo' (#875)
Co-authored-by: evgeniy.s <evgeniy.s@uklon.com.ua>
2023-01-23 20:07:06 +01:00
Stef Heyenrath c85eaf1072 Add unit test example for Transformer Handlebars String.Append String.Join (#877)
* Response_ProvideResponse_Transformer_WithBodyAsJson_Handlebars_StringAppend

* fix
2023-01-20 08:21:59 +01:00
Stef Heyenrath b2a8178161 Fix unsubscribe from LogEntriesChanged event handler (#872)
* Fix unsubscribe from LogEntriesChanged event handler

* .

* f
2023-01-19 14:23:38 +01:00
Stef Heyenrath 20eb37b0c8 Add MappingBuilder to build mappings in code and export to Models or JSON (#869)
* MappingBuilder

* .

* ...

* sc

* t

* .
2023-01-06 19:11:56 +01:00
Gerhard Gradnig 742f1d1f0a Add UseWebhooksFireAndForget to Server ConvertMapping (#871)
Co-authored-by: Gerhard.Gradnig <gerhard.gradnig@admiral.at>
2023-01-05 18:33:08 +01:00
Stef Heyenrath d8927b88c8 Fix example projects 2022-12-24 17:00:41 +01:00
Stef Heyenrath 7ab136557a Include="Nullable" Version="1.3.1" 2022-12-14 17:11:45 +01:00
Stef Heyenrath 3d17913f35 1.5.13 2022-12-11 21:13:36 +01:00
billybraga 9ed6a75384 Add client certificate support (#862)
* Add client certificate support

* Add missing test certificate file

* Review fixes

* Review fixes

* Review fixes

* Review fixes
2022-12-11 20:30:47 +01:00
Stef Heyenrath 9606fee8cb Update Transformer functionality to return value instead of string (#858) 2022-12-11 11:07:56 +01:00
Stef Heyenrath 6b03dfaa8c Update WireMockServer.CreateClient/CreateClients to include handlers (#863) 2022-12-10 12:25:49 +01:00
Stef Heyenrath e2f3ffd33a Add UpdatedAt property to Mapping (#859)
* Add UpdatedAt property to Mapping

* .
2022-12-09 14:18:50 +01:00
282 changed files with 13277 additions and 6650 deletions
+2
View File
@@ -255,3 +255,5 @@ paket-files/
/test/WireMock.Net.Tests/coverage.opencover.xml /test/WireMock.Net.Tests/coverage.opencover.xml
/test/WireMock.Net.Tests/coverage.netcoreapp3.1.opencover.xml /test/WireMock.Net.Tests/coverage.netcoreapp3.1.opencover.xml
/test/WireMock.Net.Tests/coverage.net5.0.opencover.xml /test/WireMock.Net.Tests/coverage.net5.0.opencover.xml
*.received.*
+88 -2
View File
@@ -1,3 +1,88 @@
# 1.5.27 (03 June 2023)
- [#946](https://github.com/WireMock-Net/WireMock.Net/pull/946) - Add warning logging when sending a request to a Webhook does not return status 200 [feature] contributed by [StefH](https://github.com/StefH)
- [#949](https://github.com/WireMock-Net/WireMock.Net/pull/949) - Add &quot;.NET Framework 4.7&quot; to WireMock.Net.FluentAssertions [feature] contributed by [StefH](https://github.com/StefH)
- [#928](https://github.com/WireMock-Net/WireMock.Net/issues/928) - TypeLoadException when using WithHeader method. [bug]
- [#945](https://github.com/WireMock-Net/WireMock.Net/issues/945) - Webhook logging [feature]
# 1.5.26 (25 May 2023)
- [#938](https://github.com/WireMock-Net/WireMock.Net/pull/938) - Add more unitests for CSharpFormatter utils [test] contributed by [StefH](https://github.com/StefH)
- [#939](https://github.com/WireMock-Net/WireMock.Net/pull/939) - WireMockMiddleware should use HandleRequestsSynchronously correctly [bug] contributed by [StefH](https://github.com/StefH)
- [#940](https://github.com/WireMock-Net/WireMock.Net/pull/940) - Code generator improvements contributed by [cezarypiatek](https://github.com/cezarypiatek)
- [#942](https://github.com/WireMock-Net/WireMock.Net/pull/942) - Add GetParameter method to IRequestMessage [feature] contributed by [StefH](https://github.com/StefH)
- [#941](https://github.com/WireMock-Net/WireMock.Net/issues/941) - RequestMessage.GetParameter method missing from IRequestMessage interface [feature]
# 1.5.25 (13 May 2023)
- [#934](https://github.com/WireMock-Net/WireMock.Net/pull/934) - Code generator improvements [feature] contributed by [cezarypiatek](https://github.com/cezarypiatek)
# 1.5.24 (07 May 2023)
- [#926](https://github.com/WireMock-Net/WireMock.Net/pull/926) - Fix C# mapping code generator for header names [bug] contributed by [cezarypiatek](https://github.com/cezarypiatek)
- [#927](https://github.com/WireMock-Net/WireMock.Net/pull/927) - Enrich generated code with status code [feature] contributed by [cezarypiatek](https://github.com/cezarypiatek)
- [#930](https://github.com/WireMock-Net/WireMock.Net/pull/930) - Update C# mapping code generator for WithStatusCode [feature] contributed by [StefH](https://github.com/StefH)
- [#931](https://github.com/WireMock-Net/WireMock.Net/pull/931) - Add property 'IsStartedWithAdminInterface' to 'IWireMockServer' [feature] contributed by [StefH](https://github.com/StefH)
- [#933](https://github.com/WireMock-Net/WireMock.Net/pull/933) - C# code generator improvements [feature] contributed by [cezarypiatek](https://github.com/cezarypiatek)
# 1.5.23 (23 April 2023)
- [#922](https://github.com/WireMock-Net/WireMock.Net/pull/922) - Add WithProbability [feature] contributed by [StefH](https://github.com/StefH)
- [#924](https://github.com/WireMock-Net/WireMock.Net/pull/924) - Allow removal of prefix when proxying to another server (#630) [feature] contributed by [nudejustin](https://github.com/nudejustin)
- [#925](https://github.com/WireMock-Net/WireMock.Net/pull/925) - Add IgnoreCase option to ProxyUrlReplaceSettings [feature] contributed by [StefH](https://github.com/StefH)
# 1.5.22 (08 April 2023)
- [#914](https://github.com/WireMock-Net/WireMock.Net/pull/914) - #912 add excluded params to proxy mapping [feature] contributed by [walidhaidarii](https://github.com/walidhaidarii)
- [#916](https://github.com/WireMock-Net/WireMock.Net/pull/916) - Include WireMockOpenApiParser project [feature] contributed by [StefH](https://github.com/StefH)
- [#912](https://github.com/WireMock-Net/WireMock.Net/issues/912) - Feature: adding excluded params to proxy and records settings [feature]
# 1.5.21 (22 March 2023)
- [#908](https://github.com/WireMock-Net/WireMock.Net/pull/908) - RequestBuilder : add WithBodyAsJson and WithBody (with IJsonConverter) [feature] contributed by [StefH](https://github.com/StefH)
- [#911](https://github.com/WireMock-Net/WireMock.Net/pull/911) - Fixed QueryStringParser for UrlEncoded values [bug] contributed by [StefH](https://github.com/StefH)
- [#901](https://github.com/WireMock-Net/WireMock.Net/issues/901) - Matching one form-urlencoded value [feature]
# 1.5.20 (19 March 2023)
- [#905](https://github.com/WireMock-Net/WireMock.Net/pull/905) - Add DeserializeFormUrl Encoded to the settings [feature] contributed by [StefH](https://github.com/StefH)
- [#907](https://github.com/WireMock-Net/WireMock.Net/pull/907) - Fix issue with application/x-www-form-urlencoded and ExactMatcher [bug] contributed by [StefH](https://github.com/StefH)
- [#906](https://github.com/WireMock-Net/WireMock.Net/issues/906) - Upgrade to 1.5.19 breaks a form data test [bug]
# 1.5.19 (17 March 2023)
- [#903](https://github.com/WireMock-Net/WireMock.Net/pull/903) - Add WithBody with IDictionary (form-urlencoded values) [feature] contributed by [StefH](https://github.com/StefH)
- [#904](https://github.com/WireMock-Net/WireMock.Net/pull/904) - Update Handlebars.Net.Helpers to 2.3.15 [feature] contributed by [StefH](https://github.com/StefH)
# 1.5.18 (09 March 2023)
- [#893](https://github.com/WireMock-Net/WireMock.Net/pull/893) - Add 'Data' to response which can be used during transforming the response [feature] contributed by [StefH](https://github.com/StefH)
- [#896](https://github.com/WireMock-Net/WireMock.Net/pull/896) - Bump Microsoft.Owin from 2.0.2 to 4.2.2 in /examples/WireMock.Net.Service [dependencies] contributed by [dependabot[bot]](https://github.com/apps/dependabot)
- [#900](https://github.com/WireMock-Net/WireMock.Net/pull/900) - ProxySettings : Add logic to not save some requests depending on HttpMethods [feature] contributed by [StefH](https://github.com/StefH)
- [#897](https://github.com/WireMock-Net/WireMock.Net/issues/897) - WebHostBuilder.ConfigureServices method not found when using nunit3testadapter 4.4.0 [bug]
- [#899](https://github.com/WireMock-Net/WireMock.Net/issues/899) - Ignore OPTIONS request when using proxyandrecord [feature]
# 1.5.17 (25 February 2023)
- [#881](https://github.com/WireMock-Net/WireMock.Net/pull/881) - Add WithBodyAsJson builder method with accepts a Func [feature] contributed by [StefH](https://github.com/StefH)
- [#882](https://github.com/WireMock-Net/WireMock.Net/pull/882) - Add example code to test HTTP Status 400 and 500 [test] contributed by [StefH](https://github.com/StefH)
- [#890](https://github.com/WireMock-Net/WireMock.Net/pull/890) - AdminApiMappingBuilder [feature] contributed by [StefH](https://github.com/StefH)
# 1.5.16 (01 February 2023)
- [#880](https://github.com/WireMock-Net/WireMock.Net/pull/880) - Add `WithProxy(string proxyUrl, X509Certificate2 certificate)` [feature] contributed by [StefH](https://github.com/StefH)
- [#879](https://github.com/WireMock-Net/WireMock.Net/issues/879) - Possibility to pass a X509Certificate2 to WithProxy() or specifiy certificate loading options [feature]
# 1.5.15 (29 January 2023)
- [#878](https://github.com/WireMock-Net/WireMock.Net/pull/878) - Update REST Admin interface to support &quot;Get Mapping(s) as C# Code&quot; [feature] contributed by [StefH](https://github.com/StefH)
# 1.5.14 (24 January 2023)
- [#842](https://github.com/WireMock-Net/WireMock.Net/pull/842) - Generate C# code from Mapping [feature] contributed by [StefH](https://github.com/StefH)
- [#869](https://github.com/WireMock-Net/WireMock.Net/pull/869) - Add MappingBuilder to build mappings in code and export to Models or JSON [feature] contributed by [StefH](https://github.com/StefH)
- [#871](https://github.com/WireMock-Net/WireMock.Net/pull/871) - Add UseWebhooksFireAndForget to Server ConvertMapping [bug] contributed by [ggradnig](https://github.com/ggradnig)
- [#872](https://github.com/WireMock-Net/WireMock.Net/pull/872) - Fix unsubscribe from LogEntriesChanged event handler [bug] contributed by [StefH](https://github.com/StefH)
- [#875](https://github.com/WireMock-Net/WireMock.Net/pull/875) - Fix Self referencing loop detected for property [bug] contributed by [eseneckiy](https://github.com/eseneckiy)
- [#877](https://github.com/WireMock-Net/WireMock.Net/pull/877) - Add unit test example for Transformer Handlebars String.Append String.Join [test] contributed by [StefH](https://github.com/StefH)
- [#701](https://github.com/WireMock-Net/WireMock.Net/issues/701) - Allow to create MappingModel from c# to be able to configure local and remote mocks similarly [feature]
- [#867](https://github.com/WireMock-Net/WireMock.Net/issues/867) - Can I build mappings with code and save them to JSON-file without starting server [feature]
- [#870](https://github.com/WireMock-Net/WireMock.Net/issues/870) - Can not unsubscribe from LogEntriesChanged event. [bug]
# 1.5.13 (11 December 2022)
- [#858](https://github.com/WireMock-Net/WireMock.Net/pull/858) - Update Transformer functionality to return value instead of string [feature] contributed by [StefH](https://github.com/StefH)
- [#859](https://github.com/WireMock-Net/WireMock.Net/pull/859) - Add UpdatedAt property to Mapping [feature] contributed by [StefH](https://github.com/StefH)
- [#861](https://github.com/WireMock-Net/WireMock.Net/pull/861) - Add extra functionality for issue 55 contributed by [StefH](https://github.com/StefH)
- [#862](https://github.com/WireMock-Net/WireMock.Net/pull/862) - Add client certificate support [feature] contributed by [billybraga](https://github.com/billybraga)
- [#863](https://github.com/WireMock-Net/WireMock.Net/pull/863) - Update WireMockServer.CreateClient/CreateClients to include handlers [feature] contributed by [StefH](https://github.com/StefH)
- [#856](https://github.com/WireMock-Net/WireMock.Net/issues/856) - Inconsistent result with overlapping (duplicate) request [bug]
# 1.5.12 (03 December 2022) # 1.5.12 (03 December 2022)
- [#851](https://github.com/WireMock-Net/WireMock.Net/pull/851) - Fix Linux CI build + Fix opencover [feature] contributed by [StefH](https://github.com/StefH) - [#851](https://github.com/WireMock-Net/WireMock.Net/pull/851) - Fix Linux CI build + Fix opencover [feature] contributed by [StefH](https://github.com/StefH)
- [#853](https://github.com/WireMock-Net/WireMock.Net/pull/853) - Add .Net 7 [feature] contributed by [StefH](https://github.com/StefH) - [#853](https://github.com/WireMock-Net/WireMock.Net/pull/853) - Add .Net 7 [feature] contributed by [StefH](https://github.com/StefH)
@@ -625,8 +710,8 @@
- [#263](https://github.com/WireMock-Net/WireMock.Net/issues/263) - Content-Type multipart/form-data is not serialized in proxy and recording mode [bug] - [#263](https://github.com/WireMock-Net/WireMock.Net/issues/263) - Content-Type multipart/form-data is not serialized in proxy and recording mode [bug]
# 1.0.11.0 (30 March 2019) # 1.0.11.0 (30 March 2019)
- [#261](https://github.com/WireMock-Net/WireMock.Net/pull/261) - Fix BodyAsJson transform bug in ResponseMessageTransformer contributed by [psypilat](https://github.com/psypilat) - [#261](https://github.com/WireMock-Net/WireMock.Net/pull/261) - Fix BodyAsJson transform bug in ResponseMessageTransformer contributed by [ghost](https://github.com/ghost)
- [#262](https://github.com/WireMock-Net/WireMock.Net/pull/262) - Add ProvideResponse_WithJsonBodyAndTransform test contributed by [psypilat](https://github.com/psypilat) - [#262](https://github.com/WireMock-Net/WireMock.Net/pull/262) - Add ProvideResponse_WithJsonBodyAndTransform test contributed by [ghost](https://github.com/ghost)
# 1.0.10.0 (27 March 2019) # 1.0.10.0 (27 March 2019)
- [#260](https://github.com/WireMock-Net/WireMock.Net/pull/260) - Fix Response.Delay property serialization [bug] contributed by [StefH](https://github.com/StefH) - [#260](https://github.com/WireMock-Net/WireMock.Net/pull/260) - Fix Response.Delay property serialization [bug] contributed by [StefH](https://github.com/StefH)
@@ -915,6 +1000,7 @@
- [#45](https://github.com/WireMock-Net/WireMock.Net/pull/45) - Add RequestLogExpirationDuration and MaxRequestLogCount (#43) contributed by [StefH](https://github.com/StefH) - [#45](https://github.com/WireMock-Net/WireMock.Net/pull/45) - Add RequestLogExpirationDuration and MaxRequestLogCount (#43) contributed by [StefH](https://github.com/StefH)
- [#51](https://github.com/WireMock-Net/WireMock.Net/pull/51) - Observable logs contributed by [dmtrrk](https://github.com/dmtrrk) - [#51](https://github.com/WireMock-Net/WireMock.Net/pull/51) - Observable logs contributed by [dmtrrk](https://github.com/dmtrrk)
- [#15](https://github.com/WireMock-Net/WireMock.Net/issues/15) - New feature: Proxying [feature] - [#15](https://github.com/WireMock-Net/WireMock.Net/issues/15) - New feature: Proxying [feature]
- [#20](https://github.com/WireMock-Net/WireMock.Net/issues/20) - Add client certificate authentication [feature]
- [#31](https://github.com/WireMock-Net/WireMock.Net/issues/31) - Feature request: Nuget package for standalone version [feature] - [#31](https://github.com/WireMock-Net/WireMock.Net/issues/31) - Feature request: Nuget package for standalone version [feature]
- [#33](https://github.com/WireMock-Net/WireMock.Net/issues/33) - Issue with launching sample code (StandAlone server) [bug] - [#33](https://github.com/WireMock-Net/WireMock.Net/issues/33) - Issue with launching sample code (StandAlone server) [bug]
- [#38](https://github.com/WireMock-Net/WireMock.Net/issues/38) - Bug: support also listening on *:{port} - [#38](https://github.com/WireMock-Net/WireMock.Net/issues/38) - Bug: support also listening on *:{port}
+3 -2
View File
@@ -4,7 +4,7 @@
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
<VersionPrefix>1.5.12</VersionPrefix> <VersionPrefix>1.5.27</VersionPrefix>
<PackageIcon>WireMock.Net-Logo.png</PackageIcon> <PackageIcon>WireMock.Net-Logo.png</PackageIcon>
<PackageProjectUrl>https://github.com/WireMock-Net/WireMock.Net</PackageProjectUrl> <PackageProjectUrl>https://github.com/WireMock-Net/WireMock.Net</PackageProjectUrl>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression> <PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
@@ -12,6 +12,7 @@
<RepositoryType>git</RepositoryType> <RepositoryType>git</RepositoryType>
<RepositoryUrl>https://github.com/WireMock-Net/WireMock.Net</RepositoryUrl> <RepositoryUrl>https://github.com/WireMock-Net/WireMock.Net</RepositoryUrl>
<ApplicationIcon>../../resources/WireMock.Net-Logo.ico</ApplicationIcon> <ApplicationIcon>../../resources/WireMock.Net-Logo.ico</ApplicationIcon>
<PackageReadmeFile>PackageReadme.md</PackageReadmeFile>
<LangVersion>Latest</LangVersion> <LangVersion>Latest</LangVersion>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
@@ -29,7 +30,7 @@
<ItemGroup> <ItemGroup>
<None Include="../../resources/WireMock.Net-Logo.png" Pack="true" PackagePath="" /> <None Include="../../resources/WireMock.Net-Logo.png" Pack="true" PackagePath="" />
<!--<None Include="../../PackageReadme.md" Pack="true" PackagePath=""/>--> <None Include="../../PackageReadme.md" Pack="true" PackagePath=""/>
</ItemGroup> </ItemGroup>
<Choose> <Choose>
+1 -1
View File
@@ -1,6 +1,6 @@
rem https://github.com/StefH/GitHubReleaseNotes rem https://github.com/StefH/GitHubReleaseNotes
SET version=1.5.12 SET version=1.5.27
GitHubReleaseNotes --output CHANGELOG.md --skip-empty-releases --exclude-labels question invalid doc duplicate --version %version% --token %GH_TOKEN% GitHubReleaseNotes --output CHANGELOG.md --skip-empty-releases --exclude-labels question invalid doc duplicate --version %version% --token %GH_TOKEN%
+55
View File
@@ -0,0 +1,55 @@
## WireMock.Net
Lightweight Http Mocking Server for .NET, inspired by [WireMock(http://WireMock.org) from the Java landscape.
### :star: Key Features
* HTTP response stubbing, matchable on URL/Path, headers, cookies and body content patterns
* Library can be used in unit tests and integration tests
* Runs as a standalone process, as windows service, as Azure/IIS or as docker
* Configurable via a fluent C# .NET API, JSON files and JSON over HTTP
* Record/playback of stubs (proxying)
* Per-request conditional proxying
* Stateful behaviour simulation
* Response templating / transformation using Handlebars and extensions
* Can be used locally or in CI/CD scenarios
### :star: Stubbing
A core feature of WireMock.Net is the ability to return predefined HTTP responses for requests matching criteria.
See [Wiki : Stubbing](https://github.com/WireMock-Net/WireMock.Net/wiki/Stubbing).
### :star: Request Matching
WireMock.Net support advanced request-matching logic, see [Wiki : Request Matching](https://github.com/WireMock-Net/WireMock.Net/wiki/Request-Matching).
### :star: Response Templating
The response which is returned WireMock.Net can be changed using templating. This is described here [Wiki : Response Templating](https://github.com/WireMock-Net/WireMock.Net/wiki/Response-Templating).
### :star: Admin API Reference
The WireMock admin API provides functionality to define the mappings via a http interface see [Wiki : Admin API Reference](https://github.com/StefH/WireMock.Net/wiki/Admin-API-Reference).
### :star: Using
WireMock.Net can be used in several ways:
#### UnitTesting
You can use your favorite test framework and use WireMock within your tests, see
[Wiki : UnitTesting](https://github.com/StefH/WireMock.Net/wiki/Using-WireMock-in-UnitTests).
#### As a dotnet tool
It's simple to install WireMock.Net as (global) dotnet tool, see [Wiki : dotnet tool](https://github.com/StefH/WireMock.Net/wiki/WireMock-as-dotnet-tool).
#### As standalone process / console application
This is quite straight forward to launch a mock server within a console application, see [Wiki : Standalone Process](https://github.com/StefH/WireMock.Net/wiki/WireMock-as-a-standalone-process).
#### As a Windows Service
You can also run WireMock.Net as a Windows Service, follow this [WireMock-as-a-Windows-Service](https://github.com/WireMock-Net/WireMock.Net/wiki/WireMock-as-a-Windows-Service).
#### As a Web Job in Azure or application in IIS
See this link [WireMock-as-a-(Azure)-Web-App](https://github.com/WireMock-Net/WireMock.Net/wiki/WireMock-as-a-(Azure)-Web-App)
#### In a docker container
There is also a Linux and Windows-Nano container available at [hub.docker.com](https://hub.docker.com/r/sheyenrath).
For more details see also [Docker](https://github.com/WireMock-Net/WireMock.Net-docker).
#### HTTPS / SSL
More details on using HTTPS (SSL) can be found here [Wiki : HTTPS](https://github.com/WireMock-Net/WireMock.Net/wiki/Using-HTTPS-(SSL))
## :books: Documentation
For more info, see also this WIKI page: [What is WireMock.Net](https://github.com/WireMock-Net/WireMock.Net/wiki/What-Is-WireMock.Net).
+5 -5
View File
@@ -1,7 +1,7 @@
# 1.5.12 (03 December 2022) # 1.5.27 (03 June 2023)
- #851 Fix Linux CI build + Fix opencover [feature] - #946 Add warning logging when sending a request to a Webhook does not return status 200 [feature]
- #853 Add .Net 7 [feature] - #949 Add &quot;.NET Framework 4.7&quot; to WireMock.Net.FluentAssertions [feature]
- #854 Fix logic for QueryParameterMultipleValueSupport [bug] - #928 TypeLoadException when using WithHeader method. [bug]
- #857 Update some dependencies [feature] - #945 Webhook logging [feature]
The full release notes can be found here: https://github.com/WireMock-Net/WireMock.Net/blob/master/CHANGELOG.md The full release notes can be found here: https://github.com/WireMock-Net/WireMock.Net/blob/master/CHANGELOG.md
+16 -12
View File
@@ -1,24 +1,28 @@
# WireMock.Net # WireMock.Net
A C# .NET version based on [mock4net](https://github.com/alexvictoor/mock4net) which mimics the functionality from the JAVA based [WireMock.org](http://WireMock.org). A C# .NET version based on [mock4net](https://github.com/alexvictoor/mock4net) which mimics the functionality from the JAVA based [WireMock](http://WireMock.org).
For more info, see also this WIKI page: [What is WireMock.Net](https://github.com/WireMock-Net/WireMock.Net/wiki/What-Is-WireMock.Net). For more info, see also this WIKI page: [What is WireMock.Net](https://github.com/WireMock-Net/WireMock.Net/wiki/What-Is-WireMock.Net).
## Key Features ## :star: Key Features
* HTTP response stubbing, matchable on URL/Path, headers, cookies and body content patterns * HTTP response stubbing, matchable on URL/Path, headers, cookies and body content patterns
* Library can be used in unit tests and integration tests * Library can be used in unit tests and integration tests
* Runs as a standalone process, as windows service, as Azure/IIS or as docker * Runs as a standalone process, as windows service, as Azure/IIS or as docker
* Configurable via a fluent DotNet API, JSON files and JSON over HTTP * Configurable via a fluent C# .NET API, JSON files and JSON over HTTP
* Record/playback of stubs (proxying) * Record/playback of stubs (proxying)
* Per-request conditional proxying * Per-request conditional proxying
* Stateful behaviour simulation * Stateful behaviour simulation
* Response templating / transformation using Handlebars and extensions * Response templating / transformation using Handlebars and extensions
* Can be used locally or in CI/CD scenarios * Can be used locally or in CI/CD scenarios
## Info ## :memo: Blogs
- [mStack.nl : Generate C# Code from Mapping(s)](https://mstack.nl/blog/20230201-wiremock.net-tocode/)
## :computer: Project Info
| | | | | |
| --- | --- | | --- | --- |
| ***Project*** | &nbsp; | | ***Project*** | &nbsp; |
| &nbsp;&nbsp;**Chat** | [![Gitter](https://img.shields.io/gitter/room/wiremock_dotnet/Lobby.svg)](https://gitter.im/wiremock_dotnet/Lobby) | | &nbsp;&nbsp;**Chat** | [![Slack](https://badgen.net/badge/icon/slack?icon=slack&label)](https://slack.wiremock.org/) [![Gitter](https://img.shields.io/gitter/room/wiremock_dotnet/Lobby.svg)](https://gitter.im/wiremock_dotnet/Lobby) |
| &nbsp;&nbsp;**Issues** | [![GitHub issues](https://img.shields.io/github/issues/WireMock-Net/WireMock.Net.svg)](https://github.com/WireMock-Net/WireMock.Net/issues) | | &nbsp;&nbsp;**Issues** | [![GitHub issues](https://img.shields.io/github/issues/WireMock-Net/WireMock.Net.svg)](https://github.com/WireMock-Net/WireMock.Net/issues) |
| | | | | |
| ***Quality*** | &nbsp; | | ***Quality*** | &nbsp; |
@@ -27,7 +31,7 @@ For more info, see also this WIKI page: [What is WireMock.Net](https://github.co
| &nbsp;&nbsp;**Sonar Bugs** | [![Sonar Bugs](https://sonarcloud.io/api/project_badges/measure?project=WireMock-Net_WireMock.Net&metric=bugs)](https://sonarcloud.io/project/issues?id=WireMock-Net_WireMock.Net&resolved=false&types=BUG) [![Sonar Code Smells](https://sonarcloud.io/api/project_badges/measure?project=WireMock-Net_WireMock.Net&metric=code_smells)](https://sonarcloud.io/project/issues?id=WireMock-Net_WireMock.Net&resolved=false&types=CODE_SMELL) | | &nbsp;&nbsp;**Sonar Bugs** | [![Sonar Bugs](https://sonarcloud.io/api/project_badges/measure?project=WireMock-Net_WireMock.Net&metric=bugs)](https://sonarcloud.io/project/issues?id=WireMock-Net_WireMock.Net&resolved=false&types=BUG) [![Sonar Code Smells](https://sonarcloud.io/api/project_badges/measure?project=WireMock-Net_WireMock.Net&metric=code_smells)](https://sonarcloud.io/project/issues?id=WireMock-Net_WireMock.Net&resolved=false&types=CODE_SMELL) |
| &nbsp;&nbsp;**Coverage** | [![Sonar Coverage](https://sonarcloud.io/api/project_badges/measure?project=WireMock-Net_WireMock.Net&metric=coverage)](https://sonarcloud.io/component_measures?id=WireMock-Net_WireMock.Net&metric=coverage) [![codecov](https://codecov.io/gh/WireMock-Net/WireMock.Net/branch/master/graph/badge.svg)](https://codecov.io/gh/WireMock-Net/WireMock.Net)| | &nbsp;&nbsp;**Coverage** | [![Sonar Coverage](https://sonarcloud.io/api/project_badges/measure?project=WireMock-Net_WireMock.Net&metric=coverage)](https://sonarcloud.io/component_measures?id=WireMock-Net_WireMock.Net&metric=coverage) [![codecov](https://codecov.io/gh/WireMock-Net/WireMock.Net/branch/master/graph/badge.svg)](https://codecov.io/gh/WireMock-Net/WireMock.Net)|
### NuGet packages ### :package: NuGet packages
| | Official | Preview [:information_source:](https://github.com/WireMock-Net/WireMock.Net/wiki/MyGet-preview-versions) | | | Official | Preview [:information_source:](https://github.com/WireMock-Net/WireMock.Net/wiki/MyGet-preview-versions) |
| - | - | - | | - | - | - |
@@ -41,23 +45,23 @@ For more info, see also this WIKI page: [What is WireMock.Net](https://github.co
| &nbsp;&nbsp;**WireMock.Org.RestClient** | [![NuGet Badge WireMock.Org.RestClient](https://buildstats.info/nuget/WireMock.Org.RestClient)](https://www.nuget.org/packages/WireMock.Org.RestClient) | [![MyGet Badge WireMock.Org.RestClient](https://buildstats.info/myget/wiremock-net/WireMock.Org.RestClient?includePreReleases=true)](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Org.RestClient) | &nbsp;&nbsp;**WireMock.Org.RestClient** | [![NuGet Badge WireMock.Org.RestClient](https://buildstats.info/nuget/WireMock.Org.RestClient)](https://www.nuget.org/packages/WireMock.Org.RestClient) | [![MyGet Badge WireMock.Org.RestClient](https://buildstats.info/myget/wiremock-net/WireMock.Org.RestClient?includePreReleases=true)](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Org.RestClient)
## Development ## :memo: Development
For the supported frameworks and build information, see [this](https://github.com/WireMock-Net/WireMock.Net/wiki/Development-Information) page. For the supported frameworks and build information, see [this](https://github.com/WireMock-Net/WireMock.Net/wiki/Development-Information) page.
## Stubbing ## :star: Stubbing
A core feature of WireMock.Net is the ability to return predefined HTTP responses for requests matching criteria. A core feature of WireMock.Net is the ability to return predefined HTTP responses for requests matching criteria.
See [Wiki : Stubbing](https://github.com/WireMock-Net/WireMock.Net/wiki/Stubbing). See [Wiki : Stubbing](https://github.com/WireMock-Net/WireMock.Net/wiki/Stubbing).
## Request Matching ## :star: Request Matching
WireMock.Net support advanced request-matching logic, see [Wiki : Request Matching](https://github.com/WireMock-Net/WireMock.Net/wiki/Request-Matching). WireMock.Net support advanced request-matching logic, see [Wiki : Request Matching](https://github.com/WireMock-Net/WireMock.Net/wiki/Request-Matching).
## Response Templating ## :star: Response Templating
The response which is returned WireMock.Net can be changed using templating. This is described here [Wiki : Response Templating](https://github.com/WireMock-Net/WireMock.Net/wiki/Response-Templating). The response which is returned WireMock.Net can be changed using templating. This is described here [Wiki : Response Templating](https://github.com/WireMock-Net/WireMock.Net/wiki/Response-Templating).
## Admin API Reference ## :star: Admin API Reference
The WireMock admin API provides functionality to define the mappings via a http interface see [Wiki : Admin API Reference](https://github.com/StefH/WireMock.Net/wiki/Admin-API-Reference). The WireMock admin API provides functionality to define the mappings via a http interface see [Wiki : Admin API Reference](https://github.com/StefH/WireMock.Net/wiki/Admin-API-Reference).
## Using ## :star: Using
WireMock.Net can be used in several ways: WireMock.Net can be used in several ways:
### UnitTesting ### UnitTesting
+1
View File
@@ -28,6 +28,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
.github\FUNDING.yml = .github\FUNDING.yml .github\FUNDING.yml = .github\FUNDING.yml
Generate-ReleaseNotes.cmd = Generate-ReleaseNotes.cmd Generate-ReleaseNotes.cmd = Generate-ReleaseNotes.cmd
nuget.config = nuget.config nuget.config = nuget.config
PackageReadme.md = PackageReadme.md
PackageReleaseNotes.template = PackageReleaseNotes.template PackageReleaseNotes.template = PackageReleaseNotes.template
PackageReleaseNotes.txt = PackageReleaseNotes.txt PackageReleaseNotes.txt = PackageReleaseNotes.txt
README.md = README.md README.md = README.md
+2
View File
@@ -27,12 +27,14 @@
<s:Boolean x:Key="/Default/UserDictionary/Words/=Guids/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=Guids/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Heyenrath/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=Heyenrath/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Jmes/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=Jmes/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=openapi/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Pacticipant/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=Pacticipant/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Raml/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=Raml/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=randomizer/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=randomizer/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Scriban/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=Scriban/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Sigil/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=Sigil/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Stef/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=Stef/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=templated/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Victoor/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=Victoor/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Webhook/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=Webhook/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Webhooks/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=Webhooks/@EntryIndexedValue">True</s:Boolean>
+112 -65
View File
@@ -1,78 +1,125 @@
using Newtonsoft.Json;
using RestEase;
using System; using System;
using System.Net.Http.Headers; using System.Net.Http.Headers;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json;
using RestEase;
using WireMock.Admin.Settings; using WireMock.Admin.Settings;
using WireMock.Client; using WireMock.Client;
using WireMock.Client.Extensions;
namespace WireMock.Net.Client namespace WireMock.Net.Client;
class Program
{ {
class Program static async Task Main(string[] args)
{ {
static async Task Main(string[] args) // Create an implementation of the IWireMockAdminApi and pass in the base URL for the API.
var api = RestClient.For<IWireMockAdminApi>("http://localhost:9091");
// await api.ResetMappingsAsync().ConfigureAwait(false);
var mappingBuilder = api.GetMappingBuilder();
mappingBuilder.Given(m => m
.WithTitle("This is my title 1")
.WithRequest(req => req
.UsingGet()
.WithPath("/bla1")
)
.WithResponse(rsp => rsp
.WithBody("x1")
.WithHeaders(h => h.Add("h1", "v1"))
)
);
mappingBuilder.Given(m => m
.WithTitle("This is my title 2")
.WithRequest(req => req
.UsingGet()
.WithPath("/bla2")
)
.WithResponse(rsp => rsp
.WithBody("x2")
.WithHeaders(h => h.Add("h2", "v2"))
)
);
mappingBuilder.Given(m => m
.WithTitle("This is my title 3")
.WithRequest(req => req
.UsingGet()
.WithPath("/bla3")
)
.WithResponse(rsp => rsp
.WithBodyAsJson(new
{
x = "test"
}, true)
)
);
var result = await mappingBuilder.BuildAndPostAsync().ConfigureAwait(false);
Console.WriteLine($"result = {JsonConvert.SerializeObject(result)}");
var mappings = await api.GetMappingsAsync();
Console.WriteLine($"mappings = {JsonConvert.SerializeObject(mappings)}");
// Set BASIC Auth
var value = Convert.ToBase64String(Encoding.ASCII.GetBytes("a:b"));
api.Authorization = new AuthenticationHeaderValue("Basic", value);
var settings1 = await api.GetSettingsAsync();
Console.WriteLine($"settings1 = {JsonConvert.SerializeObject(settings1)}");
var settingsViaBuilder = new SettingsModelBuilder()
.WithGlobalProcessingDelay(1077)
.WithoutGlobalProcessingDelay()
.Build();
settings1.GlobalProcessingDelay = 1077;
api.PostSettingsAsync(settings1).Wait();
var settings2 = await api.GetSettingsAsync();
Console.WriteLine($"settings2 = {JsonConvert.SerializeObject(settings2)}");
mappings = await api.GetMappingsAsync();
Console.WriteLine($"mappings = {JsonConvert.SerializeObject(mappings)}");
try
{ {
// Create an implementation of the IWireMockAdminApi and pass in the base URL for the API. var guid = Guid.Parse("11111110-a633-40e8-a244-5cb80bc0ab66");
var api = RestClient.For<IWireMockAdminApi>("http://localhost:9091"); var mapping = await api.GetMappingAsync(guid);
Console.WriteLine($"mapping = {JsonConvert.SerializeObject(mapping)}");
// Set BASIC Auth
var value = Convert.ToBase64String(Encoding.ASCII.GetBytes("a:b"));
api.Authorization = new AuthenticationHeaderValue("Basic", value);
var settings1 = await api.GetSettingsAsync();
Console.WriteLine($"settings1 = {JsonConvert.SerializeObject(settings1)}");
var settingsViaBuilder = new SettingsModelBuilder()
.WithGlobalProcessingDelay(1077)
.WithoutGlobalProcessingDelay()
.Build();
settings1.GlobalProcessingDelay = 1077;
api.PostSettingsAsync(settings1).Wait();
var settings2 = await api.GetSettingsAsync();
Console.WriteLine($"settings2 = {JsonConvert.SerializeObject(settings2)}");
var mappings = await api.GetMappingsAsync();
Console.WriteLine($"mappings = {JsonConvert.SerializeObject(mappings)}");
try
{
var guid = Guid.Parse("11111110-a633-40e8-a244-5cb80bc0ab66");
var mapping = await api.GetMappingAsync(guid);
Console.WriteLine($"mapping = {JsonConvert.SerializeObject(mapping)}");
}
catch (Exception e)
{
}
var request = await api.GetRequestsAsync();
Console.WriteLine($"request = {JsonConvert.SerializeObject(request)}");
//var deleteRequestsAsync = api.DeleteRequestsAsync().Result;
//Console.WriteLine($"DeleteRequestsAsync = {deleteRequestsAsync.Status}");
//var resetRequestsAsync = api.ResetRequestsAsync().Result;
//Console.WriteLine($"ResetRequestsAsync = {resetRequestsAsync.Status}");
var scenarioStates = await api.GetScenariosAsync();
Console.WriteLine($"GetScenariosAsync = {JsonConvert.SerializeObject(scenarioStates)}");
var postFileResult = await api.PostFileAsync("1.cs", "C# Hello");
Console.WriteLine($"postFileResult = {JsonConvert.SerializeObject(postFileResult)}");
var getFileResult = await api.GetFileAsync("1.cs");
Console.WriteLine($"getFileResult = {getFileResult}");
var resetMappingsAsync = await api.ResetMappingsAsync();
Console.WriteLine($"resetMappingsAsync = {resetMappingsAsync.Status}");
var resetMappingsAndReloadStaticMappingsAsync = await api.ResetMappingsAsync(true);
Console.WriteLine($"resetMappingsAndReloadStaticMappingsAsync = {resetMappingsAndReloadStaticMappingsAsync.Status}");
Console.WriteLine("Press any key to quit");
Console.ReadKey();
} }
catch (Exception e)
{
}
var request = await api.GetRequestsAsync();
Console.WriteLine($"request = {JsonConvert.SerializeObject(request)}");
//var deleteRequestsAsync = api.DeleteRequestsAsync().Result;
//Console.WriteLine($"DeleteRequestsAsync = {deleteRequestsAsync.Status}");
//var resetRequestsAsync = api.ResetRequestsAsync().Result;
//Console.WriteLine($"ResetRequestsAsync = {resetRequestsAsync.Status}");
var scenarioStates = await api.GetScenariosAsync();
Console.WriteLine($"GetScenariosAsync = {JsonConvert.SerializeObject(scenarioStates)}");
var postFileResult = await api.PostFileAsync("1.cs", "C# Hello");
Console.WriteLine($"postFileResult = {JsonConvert.SerializeObject(postFileResult)}");
var getFileResult = await api.GetFileAsync("1.cs");
Console.WriteLine($"getFileResult = {getFileResult}");
var resetMappingsAsync = await api.ResetMappingsAsync();
Console.WriteLine($"resetMappingsAsync = {resetMappingsAsync.Status}");
var resetMappingsAndReloadStaticMappingsAsync = await api.ResetMappingsAsync(true);
Console.WriteLine($"resetMappingsAndReloadStaticMappingsAsync = {resetMappingsAndReloadStaticMappingsAsync.Status}");
Console.WriteLine("Press any key to quit");
Console.ReadKey();
} }
} }
@@ -1,19 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<ApplicationIcon>../../resources/WireMock.Net-Logo.ico</ApplicationIcon> <ApplicationIcon>../../resources/WireMock.Net-Logo.ico</ApplicationIcon>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> <ProjectReference Include="..\..\src\WireMock.Net.RestClient\WireMock.Net.RestClient.csproj" />
<PackageReference Include="RestEase" Version="1.5.7" /> </ItemGroup>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\WireMock.Net.Abstractions\WireMock.Net.Abstractions.csproj" />
<ProjectReference Include="..\..\src\WireMock.Net.RestClient\WireMock.Net.RestClient.csproj" />
</ItemGroup>
</Project> </Project>
@@ -27,7 +27,7 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\src\WireMock.Net\WireMock.Net.csproj" /> <ProjectReference Include="..\..\src\WireMock.Net\WireMock.Net.csproj" />
<PackageReference Include="Handlebars.Net.Helpers" Version="2.*" /> <!--<PackageReference Include="Handlebars.Net.Helpers" Version="2.*" />-->
<PackageReference Include="log4net" Version="2.0.15" /> <PackageReference Include="log4net" Version="2.0.15" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration" Version="6.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
@@ -21,10 +21,10 @@
<ProjectReference Include="..\..\src\WireMock.Net\WireMock.Net.csproj" /> <ProjectReference Include="..\..\src\WireMock.Net\WireMock.Net.csproj" />
<PackageReference Include="Handlebars.Net.Helpers" Version="2.*" /> <PackageReference Include="Handlebars.Net.Helpers" Version="2.*" />
<PackageReference Include="Handlebars.Net.Helpers.DynamicLinq" Version="2.*" /> <PackageReference Include="Handlebars.Net.Helpers.DynamicLinq" Version="2.*" />
<PackageReference Include="Handlebars.Net.Helpers.Json" Version="2.3.10" /> <PackageReference Include="Handlebars.Net.Helpers.Json" Version="2.3.*" />
<PackageReference Include="Handlebars.Net.Helpers.XPath" Version="2.*" /> <PackageReference Include="Handlebars.Net.Helpers.XPath" Version="2.*" />
<PackageReference Include="Handlebars.Net.Helpers.Xeger" Version="2.3.10" /> <PackageReference Include="Handlebars.Net.Helpers.Xeger" Version="2.3.*" />
<PackageReference Include="Handlebars.Net.Helpers.Random" Version="2.3.10" /> <PackageReference Include="Handlebars.Net.Helpers.Random" Version="2.3.*" />
<PackageReference Include="log4net" Version="2.0.15" /> <PackageReference Include="log4net" Version="2.0.15" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
</ItemGroup> </ItemGroup>
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
@@ -33,10 +33,10 @@
<ProjectReference Include="..\..\src\WireMock.Net\WireMock.Net.csproj" /> <ProjectReference Include="..\..\src\WireMock.Net\WireMock.Net.csproj" />
<PackageReference Include="Handlebars.Net.Helpers" Version="2.*" /> <PackageReference Include="Handlebars.Net.Helpers" Version="2.*" />
<PackageReference Include="Handlebars.Net.Helpers.DynamicLinq" Version="2.*" /> <PackageReference Include="Handlebars.Net.Helpers.DynamicLinq" Version="2.*" />
<PackageReference Include="Handlebars.Net.Helpers.Json" Version="2.3.10" /> <PackageReference Include="Handlebars.Net.Helpers.Json" Version="2.3.*" />
<PackageReference Include="Handlebars.Net.Helpers.XPath" Version="2.*" /> <PackageReference Include="Handlebars.Net.Helpers.XPath" Version="2.*" />
<PackageReference Include="Handlebars.Net.Helpers.Xeger" Version="2.3.10" /> <PackageReference Include="Handlebars.Net.Helpers.Xeger" Version="2.3.*" />
<PackageReference Include="Handlebars.Net.Helpers.Random" Version="2.3.10" /> <PackageReference Include="Handlebars.Net.Helpers.Random" Version="2.3.*" />
<PackageReference Include="log4net" Version="2.0.15" /> <PackageReference Include="log4net" Version="2.0.15" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
</ItemGroup> </ItemGroup>
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
@@ -30,10 +30,10 @@
<ProjectReference Include="..\..\src\WireMock.Net\WireMock.Net.csproj" /> <ProjectReference Include="..\..\src\WireMock.Net\WireMock.Net.csproj" />
<PackageReference Include="Handlebars.Net.Helpers" Version="2.*" /> <PackageReference Include="Handlebars.Net.Helpers" Version="2.*" />
<PackageReference Include="Handlebars.Net.Helpers.DynamicLinq" Version="2.*" /> <PackageReference Include="Handlebars.Net.Helpers.DynamicLinq" Version="2.*" />
<PackageReference Include="Handlebars.Net.Helpers.Json" Version="2.3.10" /> <PackageReference Include="Handlebars.Net.Helpers.Json" Version="2.3.*" />
<PackageReference Include="Handlebars.Net.Helpers.XPath" Version="2.*" /> <PackageReference Include="Handlebars.Net.Helpers.XPath" Version="2.*" />
<PackageReference Include="Handlebars.Net.Helpers.Xeger" Version="2.3.10" /> <PackageReference Include="Handlebars.Net.Helpers.Xeger" Version="2.3.*" />
<PackageReference Include="Handlebars.Net.Helpers.Random" Version="2.3.10" /> <PackageReference Include="Handlebars.Net.Helpers.Random" Version="2.3.*" />
<PackageReference Include="log4net" Version="2.0.15" /> <PackageReference Include="log4net" Version="2.0.15" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
</ItemGroup> </ItemGroup>
@@ -1,6 +1,8 @@
using System; using System;
using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Linq;
using System.Net; using System.Net;
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json; using Newtonsoft.Json;
@@ -33,12 +35,54 @@ namespace WireMock.Net.ConsoleApplication
} }
} }
public class Todo
{
public int Id { get; set; }
}
public static class MainApp public static class MainApp
{ {
public static void Run() public static void Run()
{ {
var s = WireMockServer.Start(); var mappingBuilder = new MappingBuilder();
s.Stop(); mappingBuilder
.Given(Request
.Create()
.WithPath(new WildcardMatcher("/param2", true))
.WithParam("key", "test")
.UsingGet())
.RespondWith(Response.Create()
.WithHeader("Content-Type", "application/json")
.WithBodyAsJson(new { result = "param2" }));
var json = mappingBuilder.ToJson();
System.Console.WriteLine("mappingBuilder : Json = {0}", json);
var todos = new Dictionary<int, Todo>();
var server = WireMockServer.Start();
server
.Given(Request.Create()
.WithPath("todos")
.UsingGet()
)
.RespondWith(Response.Create()
.WithBodyAsJson(todos.Values)
);
server
.Given(Request.Create()
.UsingGet()
.WithPath("todos")
.WithParam("id")
)
.RespondWith(Response.Create()
.WithBodyAsJson(rm => todos[int.Parse(rm.Query!["id"].ToString())])
);
var httpClient = server.CreateClient();
//server.Stop();
var httpAndHttpsWithPort = WireMockServer.Start(new WireMockServerSettings var httpAndHttpsWithPort = WireMockServer.Start(new WireMockServerSettings
{ {
@@ -57,7 +101,7 @@ namespace WireMock.Net.ConsoleApplication
string url2 = "http://localhost:9092/"; string url2 = "http://localhost:9092/";
string url3 = "https://localhost:9443/"; string url3 = "https://localhost:9443/";
var server = WireMockServer.Start(new WireMockServerSettings server = WireMockServer.Start(new WireMockServerSettings
{ {
AllowCSharpCodeMatcher = true, AllowCSharpCodeMatcher = true,
Urls = new[] { url1, url2, url3 }, Urls = new[] { url1, url2, url3 },
@@ -93,7 +137,43 @@ namespace WireMock.Net.ConsoleApplication
// server.AllowPartialMapping(); // server.AllowPartialMapping();
// 400 ms
server
.Given(Request.Create()
.WithPath("/slow/400")
.UsingPost())
.RespondWith(
Response.Create()
.WithStatusCode(400)
.WithBody("return 400")
.WithHeader("Content-Type", "text/plain")
);
// 4 sec
server
.Given(Request.Create()
.WithPath("/slow/500")
.UsingPost())
.RespondWith(
Response.Create()
.WithStatusCode(500)
.WithBody("return 500")
.WithHeader("Content-Type", "text/plain")
);
server
.Given(Request.Create()
.UsingMethod("GET")
.WithPath("/foo1")
.WithParam("p1", "xyz")
)
.WithGuid("90356dba-b36c-469a-a17e-669cd84f1f05")
.RespondWith(Response.Create()
.WithBody("Hello World")
);
server.Given(Request.Create().WithPath(MatchOperator.Or, "/mypath", "/mypath1", "/mypath2").UsingPost()) server.Given(Request.Create().WithPath(MatchOperator.Or, "/mypath", "/mypath1", "/mypath2").UsingPost())
.WithGuid("86984b0e-2516-4935-a2ef-b45bf4820d7d")
.RespondWith(Response.Create() .RespondWith(Response.Create()
.WithHeader("Content-Type", "application/json") .WithHeader("Content-Type", "application/json")
.WithBodyAsJson("{{JsonPath.SelectToken request.body \"..name\"}}") .WithBodyAsJson("{{JsonPath.SelectToken request.body \"..name\"}}")
@@ -366,14 +446,14 @@ namespace WireMock.Net.ConsoleApplication
// http://localhost:9091/trans?start=1000&stop=1&stop=2 // http://localhost:9091/trans?start=1000&stop=1&stop=2
server server
.Given(Request.Create().WithPath("/trans").UsingGet()) .Given(Request.Create().WithPath("/trans").UsingGet())
.WithGuid("90356dba-b36c-469a-a17e-669cd84f1f05") .WithGuid("90356dba-b36c-469a-a17e-669cd84f1f06")
.RespondWith(Response.Create() .RespondWith(Response.Create()
.WithStatusCode(200) .WithStatusCode(200)
.WithHeader("Content-Type", "application/json") .WithHeader("Content-Type", "application/json")
.WithHeader("Transformed-Postman-Token", "token is {{request.headers.Postman-Token}}") .WithHeader("Transformed-Postman-Token", "token is {{request.headers.Postman-Token}}")
.WithHeader("xyz_{{request.headers.Postman-Token}}", "token is {{request.headers.Postman-Token}}") .WithHeader("xyz_{{request.headers.Postman-Token}}", "token is {{request.headers.Postman-Token}}")
.WithBody(@"{""msg"": ""Hello world CATCH-ALL on /*, {{request.path}}, add={{Math.Add request.query.start.[0] 42}} bykey={{request.query.start}}, bykey={{request.query.stop}}, byidx0={{request.query.stop.[0]}}, byidx1={{request.query.stop.[1]}}"" }") .WithBody(@"{""msg"": ""Hello world CATCH-ALL on /*, {{request.path}}, add={{Math.Add request.query.start.[0] 42}} bykey={{request.query.start}}, bykey={{request.query.stop}}, byidx0={{request.query.stop.[0]}}, byidx1={{request.query.stop.[1]}}"" }")
.WithTransformer(TransformerType.Handlebars, true, ReplaceNodeOptions.None) .WithTransformer(TransformerType.Handlebars, true, ReplaceNodeOptions.EvaluateAndTryToConvert)
.WithDelay(TimeSpan.FromMilliseconds(100)) .WithDelay(TimeSpan.FromMilliseconds(100))
); );
@@ -563,6 +643,7 @@ namespace WireMock.Net.ConsoleApplication
.WithStatusCode(200) .WithStatusCode(200)
.WithHeader("Content-Type", "application/json") .WithHeader("Content-Type", "application/json")
.WithBodyAsJson(new { Id = "5bdf076c-5654-4b3e-842c-7caf1fabf8c9" })); .WithBodyAsJson(new { Id = "5bdf076c-5654-4b3e-842c-7caf1fabf8c9" }));
server server
.Given(Request.Create().WithPath("/random200or505").UsingGet()) .Given(Request.Create().WithPath("/random200or505").UsingGet())
.RespondWith(Response.Create().WithCallback(request => .RespondWith(Response.Create().WithCallback(request =>
@@ -570,7 +651,11 @@ namespace WireMock.Net.ConsoleApplication
int code = new Random().Next(1, 2) == 1 ? 505 : 200; int code = new Random().Next(1, 2) == 1 ? 505 : 200;
return new ResponseMessage return new ResponseMessage
{ {
BodyData = new BodyData { BodyAsString = "random200or505:" + code, DetectedBodyType = Types.BodyType.String }, BodyData = new BodyData
{
BodyAsString = "random200or505:" + code + ", HeadersFromRequest = " + string.Join(",", request.Headers),
DetectedBodyType = Types.BodyType.String,
},
StatusCode = code StatusCode = code
}; };
})); }));
@@ -42,11 +42,11 @@
<Reference Include="Handlebars, Version=2.1.2.0, Culture=neutral, PublicKeyToken=22225d0bf33cd661, processorArchitecture=MSIL"> <Reference Include="Handlebars, Version=2.1.2.0, Culture=neutral, PublicKeyToken=22225d0bf33cd661, processorArchitecture=MSIL">
<HintPath>..\..\packages\Handlebars.Net.2.1.2\lib\net452\Handlebars.dll</HintPath> <HintPath>..\..\packages\Handlebars.Net.2.1.2\lib\net452\Handlebars.dll</HintPath>
</Reference> </Reference>
<Reference Include="Handlebars.Net.Helpers, Version=2.3.10.0, Culture=neutral, PublicKeyToken=00d131fae0c250bc, processorArchitecture=MSIL"> <Reference Include="Handlebars.Net.Helpers, Version=2.3.12.0, Culture=neutral, PublicKeyToken=00d131fae0c250bc, processorArchitecture=MSIL">
<HintPath>..\..\packages\Handlebars.Net.Helpers.2.3.10\lib\net452\Handlebars.Net.Helpers.dll</HintPath> <HintPath>..\..\packages\Handlebars.Net.Helpers.2.3.12\lib\net452\Handlebars.Net.Helpers.dll</HintPath>
</Reference> </Reference>
<Reference Include="HandlebarsDotNet.Helpers.Core, Version=2.3.10.0, Culture=neutral, PublicKeyToken=00d131fae0c250bc, processorArchitecture=MSIL"> <Reference Include="HandlebarsDotNet.Helpers.Core, Version=2.3.12.0, Culture=neutral, PublicKeyToken=00d131fae0c250bc, processorArchitecture=MSIL">
<HintPath>..\..\packages\Handlebars.Net.Helpers.Core.2.3.10\lib\net452\HandlebarsDotNet.Helpers.Core.dll</HintPath> <HintPath>..\..\packages\Handlebars.Net.Helpers.Core.2.3.12\lib\net452\HandlebarsDotNet.Helpers.Core.dll</HintPath>
</Reference> </Reference>
<Reference Include="log4net, Version=2.0.15.0, Culture=neutral, PublicKeyToken=669e0ddf0bb1aa2a, processorArchitecture=MSIL"> <Reference Include="log4net, Version=2.0.15.0, Culture=neutral, PublicKeyToken=669e0ddf0bb1aa2a, processorArchitecture=MSIL">
<HintPath>..\..\packages\log4net.2.0.15\lib\net45\log4net.dll</HintPath> <HintPath>..\..\packages\log4net.2.0.15\lib\net45\log4net.dll</HintPath>
@@ -2,8 +2,8 @@
<packages> <packages>
<package id="AnyOf" version="0.3.0" targetFramework="net452" /> <package id="AnyOf" version="0.3.0" targetFramework="net452" />
<package id="Handlebars.Net" version="2.1.2" targetFramework="net452" /> <package id="Handlebars.Net" version="2.1.2" targetFramework="net452" />
<package id="Handlebars.Net.Helpers" version="2.3.10" targetFramework="net452" /> <package id="Handlebars.Net.Helpers" version="2.3.12" targetFramework="net452" />
<package id="Handlebars.Net.Helpers.Core" version="2.3.10" targetFramework="net452" /> <package id="Handlebars.Net.Helpers.Core" version="2.3.12" targetFramework="net452" />
<package id="log4net" version="2.0.15" targetFramework="net452" /> <package id="log4net" version="2.0.15" targetFramework="net452" />
<package id="Microsoft.Owin.Host.HttpListener" version="3.1.0" targetFramework="net452" /> <package id="Microsoft.Owin.Host.HttpListener" version="3.1.0" targetFramework="net452" />
<package id="Newtonsoft.Json" version="13.0.1" targetFramework="net452" /> <package id="Newtonsoft.Json" version="13.0.1" targetFramework="net452" />
@@ -41,11 +41,11 @@
<Reference Include="Handlebars, Version=2.1.2.0, Culture=neutral, PublicKeyToken=22225d0bf33cd661, processorArchitecture=MSIL"> <Reference Include="Handlebars, Version=2.1.2.0, Culture=neutral, PublicKeyToken=22225d0bf33cd661, processorArchitecture=MSIL">
<HintPath>..\..\packages\Handlebars.Net.2.1.2\lib\net46\Handlebars.dll</HintPath> <HintPath>..\..\packages\Handlebars.Net.2.1.2\lib\net46\Handlebars.dll</HintPath>
</Reference> </Reference>
<Reference Include="Handlebars.Net.Helpers, Version=2.3.10.0, Culture=neutral, PublicKeyToken=00d131fae0c250bc, processorArchitecture=MSIL"> <Reference Include="Handlebars.Net.Helpers, Version=2.3.12.0, Culture=neutral, PublicKeyToken=00d131fae0c250bc, processorArchitecture=MSIL">
<HintPath>..\..\packages\Handlebars.Net.Helpers.2.3.10\lib\net452\Handlebars.Net.Helpers.dll</HintPath> <HintPath>..\..\packages\Handlebars.Net.Helpers.2.3.12\lib\net46\Handlebars.Net.Helpers.dll</HintPath>
</Reference> </Reference>
<Reference Include="HandlebarsDotNet.Helpers.Core, Version=2.3.10.0, Culture=neutral, PublicKeyToken=00d131fae0c250bc, processorArchitecture=MSIL"> <Reference Include="HandlebarsDotNet.Helpers.Core, Version=2.3.12.0, Culture=neutral, PublicKeyToken=00d131fae0c250bc, processorArchitecture=MSIL">
<HintPath>..\..\packages\Handlebars.Net.Helpers.Core.2.3.10\lib\net452\HandlebarsDotNet.Helpers.Core.dll</HintPath> <HintPath>..\..\packages\Handlebars.Net.Helpers.Core.2.3.12\lib\net46\HandlebarsDotNet.Helpers.Core.dll</HintPath>
</Reference> </Reference>
<Reference Include="log4net, Version=2.0.15.0, Culture=neutral, PublicKeyToken=669e0ddf0bb1aa2a, processorArchitecture=MSIL"> <Reference Include="log4net, Version=2.0.15.0, Culture=neutral, PublicKeyToken=669e0ddf0bb1aa2a, processorArchitecture=MSIL">
<HintPath>..\..\packages\log4net.2.0.15\lib\net45\log4net.dll</HintPath> <HintPath>..\..\packages\log4net.2.0.15\lib\net45\log4net.dll</HintPath>
@@ -2,8 +2,8 @@
<packages> <packages>
<package id="AnyOf" version="0.3.0" targetFramework="net461" /> <package id="AnyOf" version="0.3.0" targetFramework="net461" />
<package id="Handlebars.Net" version="2.1.2" targetFramework="net461" /> <package id="Handlebars.Net" version="2.1.2" targetFramework="net461" />
<package id="Handlebars.Net.Helpers" version="2.3.10" targetFramework="net461" /> <package id="Handlebars.Net.Helpers" version="2.3.12" targetFramework="net461" />
<package id="Handlebars.Net.Helpers.Core" version="2.3.10" targetFramework="net461" /> <package id="Handlebars.Net.Helpers.Core" version="2.3.12" targetFramework="net461" />
<package id="log4net" version="2.0.15" targetFramework="net461" /> <package id="log4net" version="2.0.15" targetFramework="net461" />
<package id="Microsoft.Extensions.DependencyInjection.Abstractions" version="2.2.0" targetFramework="net461" /> <package id="Microsoft.Extensions.DependencyInjection.Abstractions" version="2.2.0" targetFramework="net461" />
<package id="Newtonsoft.Json" version="13.0.1" targetFramework="net461" /> <package id="Newtonsoft.Json" version="13.0.1" targetFramework="net461" />
@@ -49,29 +49,29 @@
<Reference Include="Handlebars, Version=2.1.2.0, Culture=neutral, PublicKeyToken=22225d0bf33cd661, processorArchitecture=MSIL"> <Reference Include="Handlebars, Version=2.1.2.0, Culture=neutral, PublicKeyToken=22225d0bf33cd661, processorArchitecture=MSIL">
<HintPath>..\..\packages\Handlebars.Net.2.1.2\lib\net46\Handlebars.dll</HintPath> <HintPath>..\..\packages\Handlebars.Net.2.1.2\lib\net46\Handlebars.dll</HintPath>
</Reference> </Reference>
<Reference Include="Handlebars.Net.Helpers, Version=2.3.10.0, Culture=neutral, PublicKeyToken=00d131fae0c250bc, processorArchitecture=MSIL"> <Reference Include="Handlebars.Net.Helpers, Version=2.3.12.0, Culture=neutral, PublicKeyToken=00d131fae0c250bc, processorArchitecture=MSIL">
<HintPath>..\..\packages\Handlebars.Net.Helpers.2.3.10\lib\net452\Handlebars.Net.Helpers.dll</HintPath> <HintPath>..\..\packages\Handlebars.Net.Helpers.2.3.12\lib\net46\Handlebars.Net.Helpers.dll</HintPath>
</Reference> </Reference>
<Reference Include="HandlebarsDotNet.Helpers.Core, Version=2.3.10.0, Culture=neutral, PublicKeyToken=00d131fae0c250bc, processorArchitecture=MSIL"> <Reference Include="HandlebarsDotNet.Helpers.Core, Version=2.3.12.0, Culture=neutral, PublicKeyToken=00d131fae0c250bc, processorArchitecture=MSIL">
<HintPath>..\..\packages\Handlebars.Net.Helpers.Core.2.3.10\lib\net452\HandlebarsDotNet.Helpers.Core.dll</HintPath> <HintPath>..\..\packages\Handlebars.Net.Helpers.Core.2.3.12\lib\net46\HandlebarsDotNet.Helpers.Core.dll</HintPath>
</Reference> </Reference>
<Reference Include="HandlebarsDotNet.Helpers.DynamicLinq, Version=2.3.10.0, Culture=neutral, PublicKeyToken=00d131fae0c250bc, processorArchitecture=MSIL"> <Reference Include="HandlebarsDotNet.Helpers.DynamicLinq, Version=2.3.12.0, Culture=neutral, PublicKeyToken=00d131fae0c250bc, processorArchitecture=MSIL">
<HintPath>..\..\packages\Handlebars.Net.Helpers.DynamicLinq.2.3.10\lib\net452\HandlebarsDotNet.Helpers.DynamicLinq.dll</HintPath> <HintPath>..\..\packages\Handlebars.Net.Helpers.DynamicLinq.2.3.12\lib\net46\HandlebarsDotNet.Helpers.DynamicLinq.dll</HintPath>
</Reference> </Reference>
<Reference Include="HandlebarsDotNet.Helpers.Humanizer, Version=2.3.10.0, Culture=neutral, PublicKeyToken=00d131fae0c250bc, processorArchitecture=MSIL"> <Reference Include="HandlebarsDotNet.Helpers.Humanizer, Version=2.3.12.0, Culture=neutral, PublicKeyToken=00d131fae0c250bc, processorArchitecture=MSIL">
<HintPath>..\..\packages\Handlebars.Net.Helpers.Humanizer.2.3.10\lib\net452\HandlebarsDotNet.Helpers.Humanizer.dll</HintPath> <HintPath>..\..\packages\Handlebars.Net.Helpers.Humanizer.2.3.12\lib\net46\HandlebarsDotNet.Helpers.Humanizer.dll</HintPath>
</Reference> </Reference>
<Reference Include="HandlebarsDotNet.Helpers.Json, Version=2.3.10.0, Culture=neutral, PublicKeyToken=00d131fae0c250bc, processorArchitecture=MSIL"> <Reference Include="HandlebarsDotNet.Helpers.Json, Version=2.3.12.0, Culture=neutral, PublicKeyToken=00d131fae0c250bc, processorArchitecture=MSIL">
<HintPath>..\..\packages\Handlebars.Net.Helpers.Json.2.3.10\lib\net452\HandlebarsDotNet.Helpers.Json.dll</HintPath> <HintPath>..\..\packages\Handlebars.Net.Helpers.Json.2.3.12\lib\net46\HandlebarsDotNet.Helpers.Json.dll</HintPath>
</Reference> </Reference>
<Reference Include="HandlebarsDotNet.Helpers.Random, Version=2.3.10.0, Culture=neutral, PublicKeyToken=00d131fae0c250bc, processorArchitecture=MSIL"> <Reference Include="HandlebarsDotNet.Helpers.Random, Version=2.3.12.0, Culture=neutral, PublicKeyToken=00d131fae0c250bc, processorArchitecture=MSIL">
<HintPath>..\..\packages\Handlebars.Net.Helpers.Random.2.3.10\lib\net452\HandlebarsDotNet.Helpers.Random.dll</HintPath> <HintPath>..\..\packages\Handlebars.Net.Helpers.Random.2.3.12\lib\net46\HandlebarsDotNet.Helpers.Random.dll</HintPath>
</Reference> </Reference>
<Reference Include="HandlebarsDotNet.Helpers.Xeger, Version=2.3.10.0, Culture=neutral, PublicKeyToken=00d131fae0c250bc, processorArchitecture=MSIL"> <Reference Include="HandlebarsDotNet.Helpers.Xeger, Version=2.3.12.0, Culture=neutral, PublicKeyToken=00d131fae0c250bc, processorArchitecture=MSIL">
<HintPath>..\..\packages\Handlebars.Net.Helpers.Xeger.2.3.10\lib\net452\HandlebarsDotNet.Helpers.Xeger.dll</HintPath> <HintPath>..\..\packages\Handlebars.Net.Helpers.Xeger.2.3.12\lib\net46\HandlebarsDotNet.Helpers.Xeger.dll</HintPath>
</Reference> </Reference>
<Reference Include="HandlebarsDotNet.Helpers.XPath, Version=2.3.10.0, Culture=neutral, PublicKeyToken=00d131fae0c250bc, processorArchitecture=MSIL"> <Reference Include="HandlebarsDotNet.Helpers.XPath, Version=2.3.12.0, Culture=neutral, PublicKeyToken=00d131fae0c250bc, processorArchitecture=MSIL">
<HintPath>..\..\packages\Handlebars.Net.Helpers.XPath.2.3.10\lib\net452\HandlebarsDotNet.Helpers.XPath.dll</HintPath> <HintPath>..\..\packages\Handlebars.Net.Helpers.XPath.2.3.12\lib\net46\HandlebarsDotNet.Helpers.XPath.dll</HintPath>
</Reference> </Reference>
<Reference Include="Humanizer, Version=2.14.0.0, Culture=neutral, PublicKeyToken=979442b78dfc278e, processorArchitecture=MSIL"> <Reference Include="Humanizer, Version=2.14.0.0, Culture=neutral, PublicKeyToken=979442b78dfc278e, processorArchitecture=MSIL">
<HintPath>..\..\packages\Humanizer.Core.2.14.1\lib\netstandard2.0\Humanizer.dll</HintPath> <HintPath>..\..\packages\Humanizer.Core.2.14.1\lib\netstandard2.0\Humanizer.dll</HintPath>
@@ -3,14 +3,14 @@
<package id="AnyOf" version="0.3.0" targetFramework="net472" /> <package id="AnyOf" version="0.3.0" targetFramework="net472" />
<package id="Fare" version="2.2.1" targetFramework="net472" /> <package id="Fare" version="2.2.1" targetFramework="net472" />
<package id="Handlebars.Net" version="2.1.2" targetFramework="net472" /> <package id="Handlebars.Net" version="2.1.2" targetFramework="net472" />
<package id="Handlebars.Net.Helpers" version="2.3.10" targetFramework="net472" /> <package id="Handlebars.Net.Helpers" version="2.3.12" targetFramework="net472" />
<package id="Handlebars.Net.Helpers.Core" version="2.3.10" targetFramework="net472" /> <package id="Handlebars.Net.Helpers.Core" version="2.3.12" targetFramework="net472" />
<package id="Handlebars.Net.Helpers.DynamicLinq" version="2.3.10" targetFramework="net472" /> <package id="Handlebars.Net.Helpers.DynamicLinq" version="2.3.12" targetFramework="net472" />
<package id="Handlebars.Net.Helpers.Humanizer" version="2.3.10" targetFramework="net472" /> <package id="Handlebars.Net.Helpers.Humanizer" version="2.3.12" targetFramework="net472" />
<package id="Handlebars.Net.Helpers.Json" version="2.3.10" targetFramework="net472" /> <package id="Handlebars.Net.Helpers.Json" version="2.3.12" targetFramework="net472" />
<package id="Handlebars.Net.Helpers.Random" version="2.3.10" targetFramework="net472" /> <package id="Handlebars.Net.Helpers.Random" version="2.3.12" targetFramework="net472" />
<package id="Handlebars.Net.Helpers.Xeger" version="2.3.10" targetFramework="net472" /> <package id="Handlebars.Net.Helpers.Xeger" version="2.3.12" targetFramework="net472" />
<package id="Handlebars.Net.Helpers.XPath" version="2.3.10" targetFramework="net472" /> <package id="Handlebars.Net.Helpers.XPath" version="2.3.12" targetFramework="net472" />
<package id="Humanizer" version="2.14.1" targetFramework="net472" /> <package id="Humanizer" version="2.14.1" targetFramework="net472" />
<package id="Humanizer.Core" version="2.14.1" targetFramework="net472" /> <package id="Humanizer.Core" version="2.14.1" targetFramework="net472" />
<package id="Humanizer.Core.af" version="2.14.1" targetFramework="net472" /> <package id="Humanizer.Core.af" version="2.14.1" targetFramework="net472" />
@@ -1,6 +1,6 @@
using System; using System;
using System.Collections.Specialized;
using System.Net.Http; using System.Net.Http;
using Newtonsoft.Json;
using WireMock.Server; using WireMock.Server;
using WireMock.Settings; using WireMock.Settings;
@@ -26,10 +26,8 @@ namespace WireMock.Net.Console.Proxy.Net452
} }
}); });
server.LogEntriesChanged += (sender, eventRecordArgs) => System.Console.WriteLine("Subscribing to LogEntriesChanged");
{ server.LogEntriesChanged += Server_LogEntriesChanged;
System.Console.WriteLine(JsonConvert.SerializeObject(eventRecordArgs.NewItems, Formatting.Indented));
};
var uri = new Uri(urls[0]); var uri = new Uri(urls[0]);
var form = new MultipartFormDataContent var form = new MultipartFormDataContent
@@ -38,9 +36,23 @@ namespace WireMock.Net.Console.Proxy.Net452
}; };
new HttpClient().PostAsync(uri, form).GetAwaiter().GetResult(); new HttpClient().PostAsync(uri, form).GetAwaiter().GetResult();
System.Console.WriteLine("Unsubscribing to LogEntriesChanged");
server.LogEntriesChanged -= Server_LogEntriesChanged;
form = new MultipartFormDataContent
{
{ new StringContent("data2"), "test2", "test2.txt" }
};
new HttpClient().PostAsync(uri, form).GetAwaiter().GetResult();
System.Console.WriteLine("Press any key to stop the server"); System.Console.WriteLine("Press any key to stop the server");
System.Console.ReadKey(); System.Console.ReadKey();
server.Stop(); server.Stop();
} }
private static void Server_LogEntriesChanged(object sender, NotifyCollectionChangedEventArgs eventRecordArgs)
{
System.Console.WriteLine("Server_LogEntriesChanged : {0}", eventRecordArgs.NewItems.Count);
}
} }
} }
@@ -11,7 +11,7 @@ public class DynamicDataGeneration : WireMockOpenApiParserDynamicExampleValues
get get
{ {
// Since you have your Schema, you can get if max-length is set. You can generate accurate examples with this settings // Since you have your Schema, you can get if max-length is set. You can generate accurate examples with this settings
var maxLength = Schema.MaxLength ?? 9; var maxLength = Schema?.MaxLength ?? 9;
return RandomizerFactory.GetRandomizer(new FieldOptionsTextRegex return RandomizerFactory.GetRandomizer(new FieldOptionsTextRegex
{ {
@@ -1,5 +1,6 @@
using System; using System;
using System.IO; using System.IO;
using WireMock.Net.OpenApiParser.Types;
using WireMock.RequestBuilders; using WireMock.RequestBuilders;
using WireMock.ResponseBuilders; using WireMock.ResponseBuilders;
@@ -17,8 +18,8 @@ class Program
private static void RunMockServerWithDynamicExampleGeneration() private static void RunMockServerWithDynamicExampleGeneration()
{ {
//Run your mocking framework specifieing youur Example Values generator class. // Run your mocking framework specifying your Example Values generator class.
var serverCustomer_V2_json = Run.RunServer(Path.Combine(Folder, "Swagger_Customer_V2.0.json"), "http://localhost:8090/", true, new DynamicDataGeneration(), Types.ExampleValueType.Value, Types.ExampleValueType.Value); var serverCustomer_V2_json = Run.RunServer(Path.Combine(Folder, "Swagger_Customer_V2.0.json"), "http://localhost:8090/", true, new DynamicDataGeneration(), ExampleValueType.Value, ExampleValueType.Value);
Console.WriteLine("Press any key to stop the servers"); Console.WriteLine("Press any key to stop the servers");
Console.ReadKey(); Console.ReadKey();
@@ -27,15 +28,15 @@ class Program
private static void RunOthersOpenApiParserExample() private static void RunOthersOpenApiParserExample()
{ {
var serverOpenAPIExamples = Run.RunServer(Path.Combine(Folder, "openAPIExamples.yaml"), "https://localhost:9091/"); var serverOpenAPIExamples = Run.RunServer(Path.Combine(Folder, "openAPIExamples.yaml"), "http://localhost:9091/");
var serverPetstore_V2_json = Run.RunServer(Path.Combine(Folder, "Swagger_Petstore_V2.0.json"), "https://localhost:9092/"); var serverPetstore_V2_json = Run.RunServer(Path.Combine(Folder, "Swagger_Petstore_V2.0.json"), "http://localhost:9092/");
var serverPetstore_V2_yaml = Run.RunServer(Path.Combine(Folder, "Swagger_Petstore_V2.0.yaml"), "https://localhost:9093/"); var serverPetstore_V2_yaml = Run.RunServer(Path.Combine(Folder, "Swagger_Petstore_V2.0.yaml"), "http://localhost:9093/");
var serverPetstore_V300_yaml = Run.RunServer(Path.Combine(Folder, "Swagger_Petstore_V3.0.0.yaml"), "https://localhost:9094/"); var serverPetstore_V300_yaml = Run.RunServer(Path.Combine(Folder, "Swagger_Petstore_V3.0.0.yaml"), "http://localhost:9094/");
var serverPetstore_V302_json = Run.RunServer(Path.Combine(Folder, "Swagger_Petstore_V3.0.2.json"), "https://localhost:9095/"); var serverPetstore_V302_json = Run.RunServer(Path.Combine(Folder, "Swagger_Petstore_V3.0.2.json"), "http://localhost:9095/");
var testopenapifile_json = Run.RunServer(Path.Combine(Folder, "testopenapifile.json"), "https://localhost:9096/"); var testopenapifile_json = Run.RunServer(Path.Combine(Folder, "testopenapifile.json"), "http://localhost:9096/");
var file_errorYaml = Run.RunServer(Path.Combine(Folder, "file_error.yaml"), "https://localhost:9097/"); var file_errorYaml = Run.RunServer(Path.Combine(Folder, "file_error.yaml"), "http://localhost:9097/");
var file_petJson = Run.RunServer(Path.Combine(Folder, "pet.json"), "https://localhost:9098/"); var file_petJson = Run.RunServer(Path.Combine(Folder, "pet.json"), "http://localhost:9098/");
var refsYaml = Run.RunServer(Path.Combine(Folder, "refs.yaml"), "https://localhost:9099/"); var refsYaml = Run.RunServer(Path.Combine(Folder, "refs.yaml"), "http://localhost:9099/");
testopenapifile_json testopenapifile_json
.Given(Request.Create().WithPath("/x").UsingGet()) .Given(Request.Create().WithPath("/x").UsingGet())
@@ -9,64 +9,70 @@ using WireMock.Net.OpenApiParser.Types;
using WireMock.Server; using WireMock.Server;
using WireMock.Settings; using WireMock.Settings;
namespace WireMock.Net.OpenApiParser.ConsoleApp namespace WireMock.Net.OpenApiParser.ConsoleApp;
public static class Run
{ {
public static class Run public static WireMockServer RunServer(
string path,
string url,
bool dynamicExamples = true,
IWireMockOpenApiParserExampleValues? examplesValuesGenerator = null,
ExampleValueType pathPatternToUse = ExampleValueType.Wildcard,
ExampleValueType headerPatternToUse = ExampleValueType.Wildcard
)
{ {
public static WireMockServer RunServer(string path, string url, bool dynamicExamples = true, IWireMockOpenApiParserExampleValues examplesValuesGenerator = null, ExampleValueType pathPatternToUse = ExampleValueType.Wildcard, ExampleValueType headerPatternToUse = ExampleValueType.Wildcard) var server = WireMockServer.Start(new WireMockServerSettings
{ {
var server = WireMockServer.Start(new WireMockServerSettings AllowCSharpCodeMatcher = true,
{ Urls = new[] { url },
AllowCSharpCodeMatcher = true, StartAdminInterface = true,
Urls = new[] { url }, ReadStaticMappings = true,
StartAdminInterface = true, WatchStaticMappings = false,
ReadStaticMappings = true, WatchStaticMappingsInSubdirectories = false,
WatchStaticMappings = false, Logger = new WireMockConsoleLogger(),
WatchStaticMappingsInSubdirectories = false, SaveUnmatchedRequests = true
Logger = new WireMockConsoleLogger(), });
SaveUnmatchedRequests = true
});
Console.WriteLine("WireMockServer listening at {0}", string.Join(",", server.Urls)); Console.WriteLine("WireMockServer listening at {0}", string.Join(",", server.Urls));
//server.SetBasicAuthentication("a", "b"); //server.SetBasicAuthentication("a", "b");
var settings = new WireMockOpenApiParserSettings var settings = new WireMockOpenApiParserSettings
{
DynamicExamples = dynamicExamples,
ExampleValues = examplesValuesGenerator,
PathPatternToUse = pathPatternToUse,
HeaderPatternToUse = headerPatternToUse,
};
server.WithMappingFromOpenApiFile(path, settings, out var diag);
return server;
}
public static void RunServer(IEnumerable<MappingModel> mappings)
{ {
string url1 = "http://localhost:9091/"; DynamicExamples = dynamicExamples,
ExampleValues = examplesValuesGenerator,
PathPatternToUse = pathPatternToUse,
HeaderPatternToUse = headerPatternToUse,
};
var server = WireMockServer.Start(new WireMockServerSettings server.WithMappingFromOpenApiFile(path, settings, out var diag);
{
AllowCSharpCodeMatcher = true,
Urls = new[] { url1 },
StartAdminInterface = true,
ReadStaticMappings = false,
WatchStaticMappings = false,
WatchStaticMappingsInSubdirectories = false,
Logger = new WireMockConsoleLogger(),
});
Console.WriteLine("WireMockServer listening at {0}", string.Join(",", server.Urls));
server.SetBasicAuthentication("a", "b"); return server;
}
server.WithMapping(mappings.ToArray()); public static void RunServer(IEnumerable<MappingModel> mappings)
{
string url1 = "http://localhost:9091/";
Console.WriteLine("Press any key to stop the server"); var server = WireMockServer.Start(new WireMockServerSettings
System.Console.ReadKey(); {
server.Stop(); AllowCSharpCodeMatcher = true,
} Urls = new[] { url1 },
StartAdminInterface = true,
ReadStaticMappings = false,
WatchStaticMappings = false,
WatchStaticMappingsInSubdirectories = false,
Logger = new WireMockConsoleLogger(),
});
Console.WriteLine("WireMockServer listening at {0}", string.Join(",", server.Urls));
server.SetBasicAuthentication("a", "b");
server.WithMapping(mappings.ToArray());
Console.WriteLine("Press any key to stop the server");
System.Console.ReadKey();
server.Stop();
} }
} }
@@ -5,7 +5,7 @@
<package id="Microsoft.AspNet.WebApi.Core" version="5.2.3" targetFramework="net452" /> <package id="Microsoft.AspNet.WebApi.Core" version="5.2.3" targetFramework="net452" />
<package id="Microsoft.AspNet.WebApi.Owin" version="5.2.3" targetFramework="net452" /> <package id="Microsoft.AspNet.WebApi.Owin" version="5.2.3" targetFramework="net452" />
<package id="Microsoft.AspNet.WebApi.OwinSelfHost" version="5.2.3" targetFramework="net452" /> <package id="Microsoft.AspNet.WebApi.OwinSelfHost" version="5.2.3" targetFramework="net452" />
<package id="Microsoft.Owin" version="2.0.2" targetFramework="net452" /> <package id="Microsoft.Owin" version="4.2.2" targetFramework="net452" />
<package id="Microsoft.Owin.Host.HttpListener" version="2.0.2" targetFramework="net452" /> <package id="Microsoft.Owin.Host.HttpListener" version="2.0.2" targetFramework="net452" />
<package id="Microsoft.Owin.Hosting" version="2.0.2" targetFramework="net452" /> <package id="Microsoft.Owin.Hosting" version="2.0.2" targetFramework="net452" />
<package id="Newtonsoft.Json" version="13.0.1" targetFramework="net452" /> <package id="Newtonsoft.Json" version="13.0.1" targetFramework="net452" />
@@ -3,6 +3,7 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
using log4net; using log4net;
using log4net.Config; using log4net.Config;
using log4net.Repository; using log4net.Repository;
@@ -10,71 +11,112 @@ using WireMock.RequestBuilders;
using WireMock.ResponseBuilders; using WireMock.ResponseBuilders;
using WireMock.Server; using WireMock.Server;
using WireMock.Settings; using WireMock.Settings;
using WireMock.Util;
namespace WireMock.Net.StandAlone.NETCoreApp namespace WireMock.Net.StandAlone.NETCoreApp;
static class Program
{ {
static class Program private static readonly ILoggerRepository LogRepository = LogManager.GetRepository(Assembly.GetEntryAssembly());
// private static readonly ILog Log = LogManager.GetLogger(typeof(Program));
private static int sleepTime = 30000;
private static WireMockServer _server;
static async Task Main(string[] args)
{ {
private static readonly ILoggerRepository LogRepository = LogManager.GetRepository(Assembly.GetEntryAssembly()); //await TestAsync().ConfigureAwait(false);
// private static readonly ILog Log = LogManager.GetLogger(typeof(Program)); //return;
private static int sleepTime = 30000; XmlConfigurator.Configure(LogRepository, new FileInfo("log4net.config"));
private static WireMockServer _server;
static void Main(string[] args) if (!WireMockServerSettingsParser.TryParseArguments(args, out var settings, new WireMockLog4NetLogger()))
{ {
XmlConfigurator.Configure(LogRepository, new FileInfo("log4net.config")); return;
if (!WireMockServerSettingsParser.TryParseArguments(args, out var settings, new WireMockLog4NetLogger()))
{
return;
}
settings.Logger.Debug("WireMock.Net server arguments [{0}]", string.Join(", ", args.Select(a => $"'{a}'")));
_server = WireMockServer.Start(settings);
//_server.Given(Request.Create().WithPath("/api/sap")
// .UsingPost()
// .WithBody((IBodyData xmlData) =>
// {
// //xmlData is always null
// return true;
// }))
// .RespondWith(Response.Create().WithStatusCode(System.Net.HttpStatusCode.OK));
//_server
// .Given(Request.Create()
// .UsingAnyMethod())
// .RespondWith(Response.Create()
// .WithTransformer()
// .WithBody("{{Random Type=\"Integer\" Min=100 Max=999999}} {{DateTime.Now}} {{DateTime.Now \"yyyy-MMM\"}} {{String.Format (DateTime.Now) \"MMM-dd\"}}"));
Console.WriteLine($"{DateTime.UtcNow} Press Ctrl+C to shut down");
Console.CancelKeyPress += (s, e) =>
{
Stop("CancelKeyPress");
};
System.Runtime.Loader.AssemblyLoadContext.Default.Unloading += ctx =>
{
Stop("AssemblyLoadContext.Default.Unloading");
};
while (true)
{
Console.WriteLine($"{DateTime.UtcNow} WireMock.Net server running : {_server.IsStarted}");
Thread.Sleep(sleepTime);
}
} }
private static void Stop(string why) settings.Logger.Debug("WireMock.Net server arguments [{0}]", string.Join(", ", args.Select(a => $"'{a}'")));
_server = WireMockServer.Start(settings);
//_server.Given(Request.Create().WithPath("/api/sap")
// .UsingPost()
// .WithBody((IBodyData xmlData) =>
// {
// //xmlData is always null
// return true;
// }))
// .RespondWith(Response.Create().WithStatusCode(System.Net.HttpStatusCode.OK));
//_server
// .Given(Request.Create()
// .UsingAnyMethod())
// .RespondWith(Response.Create()
// .WithTransformer()
// .WithBody("{{Random Type=\"Integer\" Min=100 Max=999999}} {{DateTime.Now}} {{DateTime.Now \"yyyy-MMM\"}} {{String.Format (DateTime.Now) \"MMM-dd\"}}"));
Console.WriteLine($"{DateTime.UtcNow} Press Ctrl+C to shut down");
Console.CancelKeyPress += (s, e) =>
{ {
Console.WriteLine($"{DateTime.UtcNow} WireMock.Net server stopping because '{why}'"); Stop("CancelKeyPress");
_server.Stop(); };
Console.WriteLine($"{DateTime.UtcNow} WireMock.Net server stopped");
System.Runtime.Loader.AssemblyLoadContext.Default.Unloading += ctx =>
{
Stop("AssemblyLoadContext.Default.Unloading");
};
while (true)
{
Console.WriteLine($"{DateTime.UtcNow} WireMock.Net server running : {_server.IsStarted}");
Thread.Sleep(sleepTime);
} }
} }
}
private static void Stop(string why)
{
Console.WriteLine($"{DateTime.UtcNow} WireMock.Net server stopping because '{why}'");
_server.Stop();
Console.WriteLine($"{DateTime.UtcNow} WireMock.Net server stopped");
}
private static async Task TestAsync()
{
for (var i = 0; i < 20; i++)
{
var server = WireMockServer.Start();
server
.Given(
Request.Create().WithPath("/some/thing").UsingGet()
)
.RespondWith(
Response.Create()
.WithStatusCode(200)
.WithHeader("Content-Type", "text/plain")
.WithBody("Hello world! : " + i)
);
server
.Given(
Request.Create().WithPath("/some/thing").UsingGet()
)
.RespondWith(
Response.Create()
.WithStatusCode(200)
.WithHeader("Content-Type", "text/plain")
.WithBody("Hello world duplicate! : " + i)
);
var client = server.CreateClient();
var response = await client.GetAsync($"{server.Url}/some/thing").ConfigureAwait(false);
var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
Console.WriteLine($"counter {i} value:{content}");
server.Reset();
server.Dispose();
server.Stop();
}
}
}
@@ -1,44 +1,43 @@
using System; using System;
using log4net; using log4net;
using Newtonsoft.Json; using Newtonsoft.Json;
using WireMock.Admin.Requests; using WireMock.Admin.Requests;
using WireMock.Logging; using WireMock.Logging;
namespace WireMock.Net.StandAlone.NETCoreApp namespace WireMock.Net.StandAlone.NETCoreApp;
internal class WireMockLog4NetLogger : IWireMockLogger
{ {
internal class WireMockLog4NetLogger : IWireMockLogger private static readonly ILog Log = LogManager.GetLogger(typeof(Program));
public void Debug(string formatString, params object[] args)
{ {
private static readonly ILog Log = LogManager.GetLogger(typeof(Program)); Log.DebugFormat(formatString, args);
}
public void Debug(string formatString, params object[] args) public void Info(string formatString, params object[] args)
{ {
Log.DebugFormat(formatString, args); Log.InfoFormat(formatString, args);
} }
public void Info(string formatString, params object[] args) public void Warn(string formatString, params object[] args)
{ {
Log.InfoFormat(formatString, args); Log.WarnFormat(formatString, args);
} }
public void Warn(string formatString, params object[] args) public void Error(string formatString, params object[] args)
{ {
Log.WarnFormat(formatString, args); Log.ErrorFormat(formatString, args);
} }
public void Error(string formatString, params object[] args) public void Error(string message, Exception exception)
{ {
Log.ErrorFormat(formatString, args); Log.Error(message, exception);
} }
public void Error(string message, Exception exception) public void DebugRequestResponse(LogEntryModel logEntryModel, bool isAdminRequest)
{ {
Log.Error(message, exception); string message = JsonConvert.SerializeObject(logEntryModel, Formatting.Indented);
} Log.DebugFormat("Admin[{0}] {1}", isAdminRequest, message);
public void DebugRequestResponse(LogEntryModel logEntryModel, bool isAdminRequest)
{
string message = JsonConvert.SerializeObject(logEntryModel, Formatting.Indented);
Log.DebugFormat("Admin[{0}] {1}", isAdminRequest, message);
}
} }
} }
@@ -19,12 +19,12 @@ public class CookieModel
public IList<MatcherModel>? Matchers { get; set; } public IList<MatcherModel>? Matchers { get; set; }
/// <summary> /// <summary>
/// Gets or sets the ignore case. /// Gets or sets the ignore case for the Cookie Name.
/// </summary> /// </summary>
public bool? IgnoreCase { get; set; } public bool? IgnoreCase { get; set; }
/// <summary> /// <summary>
/// Reject on match. /// Gets or sets the Reject on match for the Cookie Name.
/// </summary> /// </summary>
public bool? RejectOnMatch { get; set; } public bool? RejectOnMatch { get; set; }
@@ -1,19 +1,18 @@
namespace WireMock.Admin.Mappings namespace WireMock.Admin.Mappings;
/// <summary>
/// Fault Model
/// </summary>
[FluentBuilder.AutoGenerateBuilder]
public class FaultModel
{ {
/// <summary> /// <summary>
/// Fault Model /// Gets or sets the fault. Can be null, "", NONE, EMPTY_RESPONSE or MALFORMED_RESPONSE_CHUNK.
/// </summary> /// </summary>
[FluentBuilder.AutoGenerateBuilder] public string? Type { get; set; }
public class FaultModel
{
/// <summary>
/// Gets or sets the fault. Can be null, "", NONE, EMPTY_RESPONSE, MALFORMED_RESPONSE_CHUNK or RANDOM_DATA_THEN_CLOSE.
/// </summary>
public string Type { get; set; }
/// <summary> /// <summary>
/// Gets or sets the fault percentage. /// Gets or sets the fault percentage.
/// </summary> /// </summary>
public double? Percentage { get; set; } public double? Percentage { get; set; }
}
} }
@@ -9,7 +9,7 @@ namespace WireMock.Admin.Mappings;
public class HeaderModel public class HeaderModel
{ {
/// <summary> /// <summary>
/// Gets or sets the name. /// Gets or sets the name (key).
/// </summary> /// </summary>
public string Name { get; set; } = null!; public string Name { get; set; } = null!;
@@ -19,12 +19,12 @@ public class HeaderModel
public IList<MatcherModel>? Matchers { get; set; } public IList<MatcherModel>? Matchers { get; set; }
/// <summary> /// <summary>
/// Gets or sets the ignore case. /// Gets or sets the ignore case for the Header Key.
/// </summary> /// </summary>
public bool? IgnoreCase { get; set; } public bool? IgnoreCase { get; set; }
/// <summary> /// <summary>
/// Reject on match. /// Gets or sets the Reject on match for the Header Key.
/// </summary> /// </summary>
public bool? RejectOnMatch { get; set; } public bool? RejectOnMatch { get; set; }
@@ -14,6 +14,11 @@ public class MappingModel
/// </summary> /// </summary>
public Guid? Guid { get; set; } public Guid? Guid { get; set; }
/// <summary>
/// The datetime when this mapping was created or updated.
/// </summary>
public DateTime? UpdatedAt { get; set; }
/// <summary> /// <summary>
/// Gets or sets the TimeSettings when which this mapping should be used. /// Gets or sets the TimeSettings when which this mapping should be used.
/// </summary> /// </summary>
@@ -79,4 +84,18 @@ public class MappingModel
/// Fire and forget for webhooks. /// Fire and forget for webhooks.
/// </summary> /// </summary>
public bool? UseWebhooksFireAndForget { get; set; } public bool? UseWebhooksFireAndForget { get; set; }
/// <summary>
/// Data Object which can be used when WithTransformer is used.
/// e.g. lookup an path in this object using
/// <example>
/// lookup data "1"
/// </example>
/// </summary>
public object? Data { get; set; }
/// <summary>
/// The probability when this request should be matched. Value is between 0 and 1. [Optional]
/// </summary>
public double? Probability { get; set; }
} }
@@ -1,121 +1,120 @@
using System.Collections.Generic; using System.Collections.Generic;
namespace WireMock.Admin.Mappings namespace WireMock.Admin.Mappings;
/// <summary>
/// ResponseModel
/// </summary>
[FluentBuilder.AutoGenerateBuilder]
public class ResponseModel
{ {
/// <summary> /// <summary>
/// ResponseModel /// Gets or sets the HTTP status.
/// </summary> /// </summary>
[FluentBuilder.AutoGenerateBuilder] public object? StatusCode { get; set; }
public class ResponseModel
{
/// <summary>
/// Gets or sets the HTTP status.
/// </summary>
public object? StatusCode { get; set; }
/// <summary> /// <summary>
/// Gets or sets the body destination (SameAsSource, String or Bytes). /// Gets or sets the body destination (SameAsSource, String or Bytes).
/// </summary> /// </summary>
public string? BodyDestination { get; set; } public string? BodyDestination { get; set; }
/// <summary> /// <summary>
/// Gets or sets the body. /// Gets or sets the body.
/// </summary> /// </summary>
public string? Body { get; set; } public string? Body { get; set; }
/// <summary> /// <summary>
/// Gets or sets the body (as JSON object). /// Gets or sets the body (as JSON object).
/// </summary> /// </summary>
public object? BodyAsJson { get; set; } public object? BodyAsJson { get; set; }
/// <summary> /// <summary>
/// Gets or sets a value indicating whether child objects to be indented according to the Newtonsoft.Json.JsonTextWriter.Indentation and Newtonsoft.Json.JsonTextWriter.IndentChar settings. /// Gets or sets a value indicating whether child objects to be indented according to the Newtonsoft.Json.JsonTextWriter.Indentation and Newtonsoft.Json.JsonTextWriter.IndentChar settings.
/// </summary> /// </summary>
public bool? BodyAsJsonIndented { get; set; } public bool? BodyAsJsonIndented { get; set; }
/// <summary> /// <summary>
/// Gets or sets the body (as bytearray). /// Gets or sets the body (as bytearray).
/// </summary> /// </summary>
public byte[]? BodyAsBytes { get; set; } public byte[]? BodyAsBytes { get; set; }
/// <summary> /// <summary>
/// Gets or sets the body as a file. /// Gets or sets the body as a file.
/// </summary> /// </summary>
public string? BodyAsFile { get; set; } public string? BodyAsFile { get; set; }
/// <summary> /// <summary>
/// Is the body as file cached? /// Is the body as file cached?
/// </summary> /// </summary>
public bool? BodyAsFileIsCached { get; set; } public bool? BodyAsFileIsCached { get; set; }
/// <summary> /// <summary>
/// Gets or sets the body encoding. /// Gets or sets the body encoding.
/// </summary> /// </summary>
public EncodingModel? BodyEncoding { get; set; } public EncodingModel? BodyEncoding { get; set; }
/// <summary> /// <summary>
/// Use ResponseMessage Transformer. /// Use ResponseMessage Transformer.
/// </summary> /// </summary>
public bool? UseTransformer { get; set; } public bool? UseTransformer { get; set; }
/// <summary> /// <summary>
/// Gets the type of the transformer. /// Gets the type of the transformer.
/// </summary> /// </summary>
public string? TransformerType { get; set; } public string? TransformerType { get; set; }
/// <summary> /// <summary>
/// Use the Handlebars transformer for the content from the referenced BodyAsFile. /// Use the Handlebars transformer for the content from the referenced BodyAsFile.
/// </summary> /// </summary>
public bool? UseTransformerForBodyAsFile { get; set; } public bool? UseTransformerForBodyAsFile { get; set; }
/// <summary> /// <summary>
/// The ReplaceNodeOptions to use when transforming a JSON node. /// The ReplaceNodeOptions to use when transforming a JSON node.
/// </summary> /// </summary>
public string? TransformerReplaceNodeOptions { get; set; } public string? TransformerReplaceNodeOptions { get; set; }
/// <summary> /// <summary>
/// Gets or sets the headers. /// Gets or sets the headers.
/// </summary> /// </summary>
public IDictionary<string, object>? Headers { get; set; } public IDictionary<string, object>? Headers { get; set; }
/// <summary> /// <summary>
/// Gets or sets the Headers (Raw). /// Gets or sets the Headers (Raw).
/// </summary> /// </summary>
public string? HeadersRaw { get; set; } public string? HeadersRaw { get; set; }
/// <summary> /// <summary>
/// Gets or sets the delay in milliseconds. /// Gets or sets the delay in milliseconds.
/// </summary> /// </summary>
public int? Delay { get; set; } public int? Delay { get; set; }
/// <summary> /// <summary>
/// Gets or sets the minimum random delay in milliseconds. /// Gets or sets the minimum random delay in milliseconds.
/// </summary> /// </summary>
public int? MinimumRandomDelay { get; set; } public int? MinimumRandomDelay { get; set; }
/// <summary> /// <summary>
/// Gets or sets the maximum random delay in milliseconds. /// Gets or sets the maximum random delay in milliseconds.
/// </summary> /// </summary>
public int? MaximumRandomDelay { get; set; } public int? MaximumRandomDelay { get; set; }
/// <summary> /// <summary>
/// Gets or sets the Proxy URL. /// Gets or sets the Proxy URL.
/// </summary> /// </summary>
public string? ProxyUrl { get; set; } public string? ProxyUrl { get; set; }
/// <summary> /// <summary>
/// The client X509Certificate2 Thumbprint or SubjectName to use. /// The client X509Certificate2 Thumbprint or SubjectName to use.
/// </summary> /// </summary>
public string? X509Certificate2ThumbprintOrSubjectName { get; set; } public string? X509Certificate2ThumbprintOrSubjectName { get; set; }
/// <summary> /// <summary>
/// Gets or sets the fault. /// Gets or sets the fault.
/// </summary> /// </summary>
public FaultModel? Fault { get; set; } public FaultModel? Fault { get; set; }
/// <summary> /// <summary>
/// Gets or sets the WebProxy settings. /// Gets or sets the WebProxy settings.
/// </summary> /// </summary>
public WebProxyModel? WebProxy { get; set; } public WebProxyModel? WebProxy { get; set; }
}
} }
@@ -1,71 +1,76 @@
namespace WireMock.Admin.Settings; namespace WireMock.Admin.Settings;
[FluentBuilder.AutoGenerateBuilder] [FluentBuilder.AutoGenerateBuilder]
public class ProxyAndRecordSettingsModel public class ProxyAndRecordSettingsModel
{ {
/// <summary> /// <summary>
/// The clientCertificate thumbprint or subject name fragment to use. /// The clientCertificate thumbprint or subject name fragment to use.
/// Example thumbprint : "D2DBF135A8D06ACCD0E1FAD9BFB28678DF7A9818". Example subject name: "www.google.com"" /// Example thumbprint : "D2DBF135A8D06ACCD0E1FAD9BFB28678DF7A9818". Example subject name: "www.google.com""
/// </summary> /// </summary>
public string ClientX509Certificate2ThumbprintOrSubjectName { get; set; } public string ClientX509Certificate2ThumbprintOrSubjectName { get; set; }
/// <summary> /// <summary>
/// Defines the WebProxySettings. /// Defines the WebProxySettings.
/// </summary> /// </summary>
public WebProxySettingsModel WebProxySettings { get; set; } public WebProxySettingsModel WebProxySettings { get; set; }
/// <summary> /// <summary>
/// Proxy requests should follow redirection (30x). /// Proxy requests should follow redirection (30x).
/// </summary> /// </summary>
public bool? AllowAutoRedirect { get; set; } public bool? AllowAutoRedirect { get; set; }
/// <summary> /// <summary>
/// The URL to proxy. /// The URL to proxy.
/// </summary> /// </summary>
public string Url { get; set; } public string Url { get; set; }
/// <summary> /// <summary>
/// Save the mapping for each request/response to the internal Mappings. /// Save the mapping for each request/response to the internal Mappings.
/// </summary> /// </summary>
public bool SaveMapping { get; set; } public bool SaveMapping { get; set; }
/// <summary> /// <summary>
/// Save the mapping for each request/response also to a file. (Note that SaveMapping must also be set to true.) /// Save the mapping for each request/response also to a file. (Note that SaveMapping must also be set to true.)
/// </summary> /// </summary>
public bool SaveMappingToFile { get; set; } public bool SaveMappingToFile { get; set; }
/// <summary> /// <summary>
/// Only save request/response to the internal Mappings if the status code is included in this pattern. (Note that SaveMapping must also be set to true.) /// Only save request/response to the internal Mappings if the status code is included in this pattern. (Note that SaveMapping must also be set to true.)
/// The pattern can contain a single value like "200", but also ranges like "2xx", "100,300,600" or "100-299,6xx" are supported. /// The pattern can contain a single value like "200", but also ranges like "2xx", "100,300,600" or "100-299,6xx" are supported.
/// </summary> /// </summary>
public string SaveMappingForStatusCodePattern { get; set; } = "*"; public string SaveMappingForStatusCodePattern { get; set; } = "*";
/// <summary> /// <summary>
/// Defines a list from headers which will be excluded from the saved mappings. /// Defines a list from headers which will be excluded from the saved mappings.
/// </summary> /// </summary>
public string[] ExcludedHeaders { get; set; } public string[] ExcludedHeaders { get; set; }
/// <summary> /// <summary>
/// Defines a list of cookies which will be excluded from the saved mappings. /// Defines a list of cookies which will be excluded from the saved mappings.
/// </summary> /// </summary>
public string[] ExcludedCookies { get; set; } public string[] ExcludedCookies { get; set; }
/// <summary> /// <summary>
/// Prefer the Proxy Mapping over the saved Mapping (in case SaveMapping is set to <c>true</c>). /// Prefer the Proxy Mapping over the saved Mapping (in case SaveMapping is set to <c>true</c>).
/// </summary> /// </summary>
// public bool PreferProxyMapping { get; set; } // public bool PreferProxyMapping { get; set; }
/// <summary> /// <summary>
/// When SaveMapping is set to <c>true</c>, this setting can be used to control the behavior of the generated request matchers for the new mapping. /// When SaveMapping is set to <c>true</c>, this setting can be used to control the behavior of the generated request matchers for the new mapping.
/// - <c>false</c>, the default matchers will be used. /// - <c>false</c>, the default matchers will be used.
/// - <c>true</c>, the defined mappings in the request wil be used for the new mapping. /// - <c>true</c>, the defined mappings in the request wil be used for the new mapping.
/// ///
/// Default value is false. /// Default value is false.
/// </summary> /// </summary>
public bool UseDefinedRequestMatchers { get; set; } public bool UseDefinedRequestMatchers { get; set; }
/// <summary> /// <summary>
/// Append an unique GUID to the filename from the saved mapping file. /// Append an unique GUID to the filename from the saved mapping file.
/// </summary> /// </summary>
public bool AppendGuidToSavedMappingFile { get; set; } public bool AppendGuidToSavedMappingFile { get; set; }
/// <summary>
/// Defines the Replace Settings
/// </summary>
public ProxyUrlReplaceSettingsModel? ReplaceSettings { get; set; }
} }
@@ -0,0 +1,23 @@
namespace WireMock.Admin.Settings;
/// <summary>
/// Defines an old path param and a new path param to be replaced when proxying.
/// </summary>
[FluentBuilder.AutoGenerateBuilder]
public class ProxyUrlReplaceSettingsModel
{
/// <summary>
/// The old path value to be replaced by the new path value
/// </summary>
public string OldValue { get; set; } = null!;
/// <summary>
/// The new path value to replace the old value with
/// </summary>
public string NewValue { get; set; } = null!;
/// <summary>
/// Defines if the case should be ignore when replacing.
/// </summary>
public bool IgnoreCase { get; set; }
}
@@ -31,27 +31,49 @@ public class SettingsModel
public int? MaxRequestLogCount { get; set; } public int? MaxRequestLogCount { get; set; }
/// <summary> /// <summary>
/// Allow a Body for all HTTP Methods. (default set to false). /// Allow a Body for all HTTP Methods. (default set to <c>false</c>).
/// </summary> /// </summary>
public bool? AllowBodyForAllHttpMethods { get; set; } public bool? AllowBodyForAllHttpMethods { get; set; }
/// <summary> /// <summary>
/// Handle all requests synchronously. (default set to false). /// Allow only a HttpStatus Code in the response which is defined. (default set to <c>false</c>).
/// - false : also null, 0, empty or invalid HttpStatus codes are allowed.
/// - true : only codes defined in <see cref="System.Net.HttpStatusCode"/> are allowed.
/// </summary>
public bool? AllowOnlyDefinedHttpStatusCodeInResponse { get; set; }
/// <summary>
/// Set to true to disable Json deserialization when processing requests. (default set to <c>false</c>).
/// </summary>
public bool? DisableJsonBodyParsing { get; set; }
/// <summary>
/// Disable support for GZip and Deflate request body decompression. (default set to <c>false</c>).
/// </summary>
public bool? DisableRequestBodyDecompressing { get; set; }
/// <summary>
/// Set to true to disable FormUrlEncoded deserializing when processing requests. (default set to <c>false</c>).
/// </summary>
public bool? DisableDeserializeFormUrlEncoded { get; set; }
/// <summary>
/// Handle all requests synchronously. (default set to <c>false</c>).
/// </summary> /// </summary>
public bool? HandleRequestsSynchronously { get; set; } public bool? HandleRequestsSynchronously { get; set; }
/// <summary> /// <summary>
/// Throw an exception when the Matcher fails because of invalid input. (default set to false). /// Throw an exception when the Matcher fails because of invalid input. (default set to <c>false</c>).
/// </summary> /// </summary>
public bool? ThrowExceptionWhenMatcherFails { get; set; } public bool? ThrowExceptionWhenMatcherFails { get; set; }
/// <summary> /// <summary>
/// Use the RegexExtended instead of the default <see cref="Regex"/>. (default set to true). /// Use the RegexExtended instead of the default <see cref="Regex"/>. (default set to <c>true</c>).
/// </summary> /// </summary>
public bool? UseRegexExtended { get; set; } public bool? UseRegexExtended { get; set; }
/// <summary> /// <summary>
/// Save unmatched requests to a file using the <see cref="IFileSystemHandler"/>. (default set to false). /// Save unmatched requests to a file using the <see cref="IFileSystemHandler"/>. (default set to <c>false</c>).
/// </summary> /// </summary>
public bool? SaveUnmatchedRequests { get; set; } public bool? SaveUnmatchedRequests { get; set; }
@@ -86,7 +108,7 @@ public class SettingsModel
public HostingScheme? HostingScheme { get; set; } public HostingScheme? HostingScheme { get; set; }
/// <summary> /// <summary>
/// Don't save the response-string in the LogEntry when WithBody(Func{IRequestMessage, string}) or WithBody(Func{IRequestMessage, Task{string}}) is used. (default set to false). /// Don't save the response-string in the LogEntry when WithBody(Func{IRequestMessage, string}) or WithBody(Func{IRequestMessage, Task{string}}) is used. (default set to <c>false</c>).
/// </summary> /// </summary>
public bool? DoNotSaveDynamicResponseInLogEntry { get; set; } public bool? DoNotSaveDynamicResponseInLogEntry { get; set; }
@@ -96,4 +118,16 @@ public class SettingsModel
/// Default value = "All". /// Default value = "All".
/// </summary> /// </summary>
public QueryParameterMultipleValueSupport? QueryParameterMultipleValueSupport { get; set; } public QueryParameterMultipleValueSupport? QueryParameterMultipleValueSupport { get; set; }
#if NETSTANDARD1_3_OR_GREATER || NET461
/// <summary>
/// Server client certificate mode
/// </summary>
public ClientCertificateMode ClientCertificateMode { get; set; }
/// <summary>
/// Whether to accept any client certificate
/// </summary>
public bool AcceptAnyClientCertificate { get; set; }
#endif
} }
@@ -0,0 +1,139 @@
using System;
// ReSharper disable once CheckNamespace
namespace WireMock.Admin.Mappings;
/// <summary>
/// RequestModelBuilder
/// </summary>
public partial class RequestModelBuilder
{
/// <summary>
/// UsingConnect: add HTTP Method matching on `CONNECT`.
/// </summary>
/// <returns>The <see cref="RequestModelBuilder"/>.</returns>
public RequestModelBuilder UsingConnect() => WithMethods("CONNECT");
/// <summary>
/// UsingDelete: add HTTP Method matching on `DELETE`.
/// </summary>
/// <returns>The <see cref="RequestModelBuilder"/>.</returns>
public RequestModelBuilder UsingDelete() => WithMethods("DELETE");
/// <summary>
/// UsingGet: add HTTP Method matching on `GET`.
/// </summary>
/// <returns>The <see cref="RequestModelBuilder"/>.</returns>
public RequestModelBuilder UsingGet() => WithMethods("GET");
/// <summary>
/// UsingHead: Add HTTP Method matching on `HEAD`.
/// </summary>
/// <returns>The <see cref="RequestModelBuilder"/>.</returns>
public RequestModelBuilder UsingHead() => WithMethods("HEAD");
/// <summary>
/// UsingPost: add HTTP Method matching on `POST`.
/// </summary>
/// <returns>The <see cref="RequestModelBuilder"/>.</returns>
public RequestModelBuilder UsingPost() => WithMethods("POST");
/// <summary>
/// UsingPatch: add HTTP Method matching on `PATCH`.
/// </summary>
/// <returns>The <see cref="RequestModelBuilder"/>.</returns>
public RequestModelBuilder UsingPatch() => WithMethods("PATCH");
/// <summary>
/// UsingPut: add HTTP Method matching on `OPTIONS`.
/// </summary>
/// <returns>The <see cref="RequestModelBuilder"/>.</returns>
public RequestModelBuilder UsingOptions() => WithMethods("OPTIONS");
/// <summary>
/// UsingPut: add HTTP Method matching on `PUT`.
/// </summary>
/// <returns>The <see cref="RequestModelBuilder"/>.</returns>
public RequestModelBuilder UsingPut() => WithMethods("PUT");
/// <summary>
/// UsingTrace: add HTTP Method matching on `TRACE`.
/// </summary>
/// <returns>The <see cref="RequestModelBuilder"/>.</returns>
public RequestModelBuilder UsingTrace() => WithMethods("TRACE");
/// <summary>
/// UsingAnyMethod: add HTTP Method matching on any method.
/// </summary>
/// <returns>The <see cref="RequestModelBuilder"/>.</returns>
public RequestModelBuilder UsingAnyMethod() => this;
/// <summary>
/// Set the ClientIP.
/// </summary>
public RequestModelBuilder WithClientIP(string value) => WithClientIP(() => value);
/// <summary>
/// Set the ClientIP.
/// </summary>
public RequestModelBuilder WithClientIP(ClientIPModel value) => WithClientIP(() => value);
/// <summary>
/// Set the ClientIP.
/// </summary>
public RequestModelBuilder WithClientIP(Action<ClientIPModelBuilder> action)
{
return WithClientIP(() =>
{
var builder = new ClientIPModelBuilder();
action(builder);
return builder.Build();
});
}
/// <summary>
/// Set the Path.
/// </summary>
public RequestModelBuilder WithPath(string value) => WithPath(() => value);
/// <summary>
/// Set the Path.
/// </summary>
public RequestModelBuilder WithPath(PathModel value) => WithPath(() => value);
/// <summary>
/// Set the Path.
/// </summary>
public RequestModelBuilder WithPath(Action<PathModelBuilder> action)
{
return WithPath(() =>
{
var builder = new PathModelBuilder();
action(builder);
return builder.Build();
});
}
/// <summary>
/// Set the Url.
/// </summary>
public RequestModelBuilder WithUrl(string value) => WithUrl(() => value);
/// <summary>
/// Set the Url.
/// </summary>
public RequestModelBuilder WithUrl(UrlModel value) => WithUrl(() => value);
/// <summary>
/// Set the Url.
/// </summary>
public RequestModelBuilder WithUrl(Action<UrlModelBuilder> action)
{
return WithUrl(() =>
{
var builder = new UrlModelBuilder();
action(builder);
return builder.Build();
});
}
}
@@ -0,0 +1,36 @@
using System;
using System.Net;
// ReSharper disable once CheckNamespace
namespace WireMock.Admin.Mappings;
/// <summary>
/// ResponseModelBuilder
/// </summary>
public partial class ResponseModelBuilder
{
/// <summary>
/// Set the StatusCode.
/// </summary>
public ResponseModelBuilder WithStatusCode(int value) => WithStatusCode(() => value);
/// <summary>
/// Set the StatusCode.
/// </summary>
public ResponseModelBuilder WithStatusCode(HttpStatusCode value) => WithStatusCode(() => value);
/// <summary>
/// Set the Delay.
/// </summary>
public ResponseModelBuilder WithDelay(TimeSpan value) => WithDelay((int) value.TotalMilliseconds);
/// <summary>
/// Set the MinimumRandomDelay.
/// </summary>
public ResponseModelBuilder WithMinimumRandomDelay(TimeSpan value) => WithMinimumRandomDelay((int)value.TotalMilliseconds);
/// <summary>
/// Set the MaximumRandomDelay.
/// </summary>
public ResponseModelBuilder WithMaximumRandomDelay(TimeSpan value) => WithMaximumRandomDelay((int)value.TotalMilliseconds);
}
@@ -1,5 +1,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
#if NETSTANDARD1_3_OR_GREATER || NET461
using System.Security.Cryptography.X509Certificates;
#endif
using WireMock.Types; using WireMock.Types;
using WireMock.Util; using WireMock.Util;
@@ -75,6 +78,11 @@ public interface IRequestMessage
/// </summary> /// </summary>
IDictionary<string, WireMockList<string>>? Query { get; } IDictionary<string, WireMockList<string>>? Query { get; }
/// <summary>
/// Gets the query.
/// </summary>
IDictionary<string, WireMockList<string>>? QueryIgnoreCase { get; }
/// <summary> /// <summary>
/// Gets the raw query. /// Gets the raw query.
/// </summary> /// </summary>
@@ -134,4 +142,19 @@ public interface IRequestMessage
/// Gets the origin /// Gets the origin
/// </summary> /// </summary>
string Origin { get; } string Origin { get; }
/// <summary>
/// Get a query parameter.
/// </summary>
/// <param name="key">The key.</param>
/// <param name="ignoreCase">Defines if the key should be matched using case-ignore.</param>
/// <returns>The query parameter value as WireMockList or null when not found.</returns>
WireMockList<string>? GetParameter(string key, bool ignoreCase = false);
#if NETSTANDARD1_3_OR_GREATER || NET461
/// <summary>
/// Gets the connection's client certificate
/// </summary>
X509Certificate2? ClientCertificate { get; }
#endif
} }
@@ -44,7 +44,7 @@ public interface IResponseMessage
/// Gets or sets the status code. /// Gets or sets the status code.
/// </summary> /// </summary>
object? StatusCode { get; } object? StatusCode { get; }
/// <summary> /// <summary>
/// Adds the header. /// Adds the header.
/// </summary> /// </summary>
@@ -1,3 +1,4 @@
using System.Collections.Generic;
using System.Text; using System.Text;
using WireMock.Types; using WireMock.Types;
@@ -38,6 +39,11 @@ public interface IBodyData
/// </summary> /// </summary>
string? BodyAsString { get; set; } string? BodyAsString { get; set; }
/// <summary>
/// The body as Form UrlEncoded dictionary.
/// </summary>
IDictionary<string, string>? BodyAsFormUrlEncoded { get; set; }
/// <summary> /// <summary>
/// The detected body type (detection based on body content). /// The detected body type (detection based on body content).
/// </summary> /// </summary>
@@ -1,23 +1,23 @@
namespace WireMock.ResponseBuilders // ReSharper disable InconsistentNaming
namespace WireMock.ResponseBuilders;
/// <summary>
/// The FaultType enumeration
/// </summary>
public enum FaultType
{ {
/// <summary> /// <summary>
/// The FaultType enumeration /// No Fault
/// </summary> /// </summary>
public enum FaultType NONE,
{
/// <summary>
/// No Fault
/// </summary>
NONE,
/// <summary> /// <summary>
/// Return a completely empty response. /// Return a completely empty response.
/// </summary> /// </summary>
EMPTY_RESPONSE, EMPTY_RESPONSE,
/// <summary> /// <summary>
/// Send a defined status header, then garbage, then close the connection. /// Send a defined status header, then garbage, then close the connection.
/// </summary> /// </summary>
MALFORMED_RESPONSE_CHUNK MALFORMED_RESPONSE_CHUNK
}
} }
@@ -3,212 +3,232 @@ using System.Collections.Generic;
using System.Collections.Specialized; using System.Collections.Specialized;
using WireMock.Admin.Mappings; using WireMock.Admin.Mappings;
using WireMock.Logging; using WireMock.Logging;
using WireMock.Types;
namespace WireMock.Server namespace WireMock.Server;
/// <summary>
/// The fluent mock server interface.
/// </summary>
public interface IWireMockServer : IDisposable
{ {
/// <summary> /// <summary>
/// The fluent mock server interface. /// Gets a value indicating whether this server is started.
/// </summary> /// </summary>
public interface IWireMockServer : IDisposable bool IsStarted { get; }
{
/// <summary>
/// Gets a value indicating whether this server is started.
/// </summary>
bool IsStarted { get; }
/// <summary> /// <summary>
/// Gets the request logs. /// Gets a value indicating whether this server is started with the admin interface enabled.
/// </summary> /// </summary>
IEnumerable<ILogEntry> LogEntries { get; } bool IsStartedWithAdminInterface { get; }
/// <summary> /// <summary>
/// Gets the mappings as MappingModels. /// Gets the request logs.
/// </summary> /// </summary>
IEnumerable<MappingModel> MappingModels { get; } IEnumerable<ILogEntry> LogEntries { get; }
// <summary> /// <summary>
// Gets the mappings. /// Gets the mappings as MappingModels.
// </summary> /// </summary>
//[PublicAPI] IEnumerable<MappingModel> MappingModels { get; }
//IEnumerable<IMapping> Mappings { get; }
/// <summary> // <summary>
/// Gets the ports. // Gets the mappings.
/// </summary> // </summary>
List<int> Ports { get; } //[PublicAPI]
//IEnumerable<IMapping> Mappings { get; }
/// <summary> /// <summary>
/// Gets the first port. /// Gets the ports.
/// </summary> /// </summary>
int Port { get; } List<int> Ports { get; }
/// <summary> /// <summary>
/// Gets the urls. /// Gets the first port.
/// </summary> /// </summary>
string[] Urls { get; } int Port { get; }
/// <summary> /// <summary>
/// Gets the first url. /// Gets the urls.
/// </summary> /// </summary>
string? Url { get; } string[] Urls { get; }
/// <summary> /// <summary>
/// Gets the consumer. /// Gets the first url.
/// </summary> /// </summary>
string? Consumer { get; } string? Url { get; }
/// <summary> /// <summary>
/// Gets the provider. /// Gets the consumer.
/// </summary> /// </summary>
string? Provider { get; } string? Consumer { get; }
//ConcurrentDictionary<string, ScenarioState> Scenarios { get; } /// <summary>
/// Gets the provider.
/// </summary>
string? Provider { get; }
/// <summary> //ConcurrentDictionary<string, ScenarioState> Scenarios { get; }
/// Occurs when [log entries changed].
/// </summary>
event NotifyCollectionChangedEventHandler LogEntriesChanged;
/// <summary> /// <summary>
/// Adds a 'catch all mapping' /// Occurs when [log entries changed].
/// /// </summary>
/// - matches all Paths and any Methods event NotifyCollectionChangedEventHandler LogEntriesChanged;
/// - priority is set to 1000
/// - responds with a 404 "No matching mapping found"
/// </summary>
void AddCatchAllMapping();
/// <summary> /// <summary>
/// The add request processing delay. /// Adds a 'catch all mapping'
/// </summary> ///
/// <param name="delay">The delay.</param> /// - matches all Paths and any Methods
void AddGlobalProcessingDelay(TimeSpan delay); /// - priority is set to 1000
/// - responds with a 404 "No matching mapping found"
/// </summary>
void AddCatchAllMapping();
/// <summary> /// <summary>
/// Allows the partial mapping. /// The add request processing delay.
/// </summary> /// </summary>
void AllowPartialMapping(bool allow = true); /// <param name="delay">The delay.</param>
void AddGlobalProcessingDelay(TimeSpan delay);
/// <summary> /// <summary>
/// Deletes a LogEntry. /// Allows the partial mapping.
/// </summary> /// </summary>
/// <param name="guid">The unique identifier.</param> void AllowPartialMapping(bool allow = true);
bool DeleteLogEntry(Guid guid);
/// <summary> /// <summary>
/// Deletes the mapping. /// Deletes a LogEntry.
/// </summary> /// </summary>
/// <param name="guid">The unique identifier.</param> /// <param name="guid">The unique identifier.</param>
bool DeleteMapping(Guid guid); bool DeleteLogEntry(Guid guid);
//IEnumerable<LogEntry> FindLogEntries([NotNull] params IRequestMatcher[] matchers); /// <summary>
/// Deletes the mapping.
/// </summary>
/// <param name="guid">The unique identifier.</param>
bool DeleteMapping(Guid guid);
// IRespondWithAProvider Given(IRequestMatcher requestMatcher, bool saveToFile = false); //IEnumerable<LogEntry> FindLogEntries([NotNull] params IRequestMatcher[] matchers);
/// <summary> // IRespondWithAProvider Given(IRequestMatcher requestMatcher, bool saveToFile = false);
/// Reads a static mapping file and adds or updates a single mapping.
///
/// Calling this method manually forces WireMock.Net to read and apply the specified static mapping file.
/// </summary>
/// <param name="path">The path to the static mapping file.</param>
bool ReadStaticMappingAndAddOrUpdate(string path);
/// <summary> /// <summary>
/// Reads the static mappings from a folder. /// Reads a static mapping file and adds or updates a single mapping.
/// (This method is also used when WireMockServerSettings.ReadStaticMappings is set to true. ///
/// /// Calling this method manually forces WireMock.Net to read and apply the specified static mapping file.
/// Calling this method manually forces WireMock.Net to read and apply all static mapping files in the specified folder. /// </summary>
/// </summary> /// <param name="path">The path to the static mapping file.</param>
/// <param name="folder">The optional folder. If not defined, use {CurrentFolder}/__admin/mappings</param> bool ReadStaticMappingAndAddOrUpdate(string path);
void ReadStaticMappings(string? folder = null);
/// <summary> /// <summary>
/// Removes the authentication. /// Reads the static mappings from a folder.
/// </summary> /// (This method is also used when WireMockServerSettings.ReadStaticMappings is set to true.
void RemoveAuthentication(); ///
/// Calling this method manually forces WireMock.Net to read and apply all static mapping files in the specified folder.
/// </summary>
/// <param name="folder">The optional folder. If not defined, use {CurrentFolder}/__admin/mappings</param>
void ReadStaticMappings(string? folder = null);
/// <summary> /// <summary>
/// Resets LogEntries and Mappings. /// Removes the authentication.
/// </summary> /// </summary>
void Reset(); void RemoveAuthentication();
/// <summary> /// <summary>
/// Resets the Mappings. /// Resets LogEntries and Mappings.
/// </summary> /// </summary>
void ResetMappings(); void Reset();
/// <summary> /// <summary>
/// Resets all Scenarios. /// Resets the Mappings.
/// </summary> /// </summary>
void ResetScenarios(); void ResetMappings();
/// <summary> /// <summary>
/// Resets a specific Scenario by the name. /// Resets all Scenarios.
/// </summary> /// </summary>
bool ResetScenario(string name); void ResetScenarios();
/// <summary> /// <summary>
/// Resets the LogEntries. /// Resets a specific Scenario by the name.
/// </summary> /// </summary>
void ResetLogEntries(); bool ResetScenario(string name);
/// <summary> /// <summary>
/// Saves the static mappings. /// Resets the LogEntries.
/// </summary> /// </summary>
/// <param name="folder">The optional folder. If not defined, use {CurrentFolder}/__admin/mappings</param> void ResetLogEntries();
void SaveStaticMappings(string? folder = null);
/// <summary> /// <summary>
/// Sets the basic authentication. /// Saves the static mappings.
/// </summary> /// </summary>
/// <param name="tenant">The Tenant.</param> /// <param name="folder">The optional folder. If not defined, use {CurrentFolder}/__admin/mappings</param>
/// <param name="audience">The Audience or Resource.</param> void SaveStaticMappings(string? folder = null);
void SetAzureADAuthentication(string tenant, string audience);
/// <summary> /// <summary>
/// Sets the basic authentication. /// Sets the basic authentication.
/// </summary> /// </summary>
/// <param name="username">The username.</param> /// <param name="tenant">The Tenant.</param>
/// <param name="password">The password.</param> /// <param name="audience">The Audience or Resource.</param>
void SetBasicAuthentication(string username, string password); void SetAzureADAuthentication(string tenant, string audience);
/// <summary> /// <summary>
/// Sets the maximum RequestLog count. /// Sets the basic authentication.
/// </summary> /// </summary>
/// <param name="maxRequestLogCount">The maximum RequestLog count.</param> /// <param name="username">The username.</param>
void SetMaxRequestLogCount(int? maxRequestLogCount); /// <param name="password">The password.</param>
void SetBasicAuthentication(string username, string password);
/// <summary> /// <summary>
/// Sets RequestLog expiration in hours. /// Sets the maximum RequestLog count.
/// </summary> /// </summary>
/// <param name="requestLogExpirationDuration">The RequestLog expiration in hours.</param> /// <param name="maxRequestLogCount">The maximum RequestLog count.</param>
void SetRequestLogExpirationDuration(int? requestLogExpirationDuration); void SetMaxRequestLogCount(int? maxRequestLogCount);
/// <summary> /// <summary>
/// Stop this server. /// Sets RequestLog expiration in hours.
/// </summary> /// </summary>
void Stop(); /// <param name="requestLogExpirationDuration">The RequestLog expiration in hours.</param>
void SetRequestLogExpirationDuration(int? requestLogExpirationDuration);
/// <summary> /// <summary>
/// Watches the static mappings for changes. /// Stop this server.
/// </summary> /// </summary>
/// <param name="folder">The optional folder. If not defined, use {CurrentFolder}/__admin/mappings</param> void Stop();
void WatchStaticMappings(string? folder = null);
/// <summary> /// <summary>
/// Register the mappings (via <see cref="MappingModel"/>). /// Watches the static mappings for changes.
/// /// </summary>
/// This can be used if you have 1 or more <see cref="MappingModel"/> defined and want to register these in WireMock.Net directly instead of using the fluent syntax. /// <param name="folder">The optional folder. If not defined, use {CurrentFolder}/__admin/mappings</param>
/// </summary> void WatchStaticMappings(string? folder = null);
/// <param name="mappings">The MappingModels</param>
IWireMockServer WithMapping(params MappingModel[] mappings);
/// <summary> /// <summary>
/// Register the mappings (via json string). /// Register the mappings (via <see cref="MappingModel"/>).
/// ///
/// This can be used if you the mappings as json string defined and want to register these in WireMock.Net directly instead of using the fluent syntax. /// This can be used if you have 1 or more <see cref="MappingModel"/> defined and want to register these in WireMock.Net directly instead of using the fluent syntax.
/// </summary> /// </summary>
/// <param name="mappings">The mapping(s) as json string.</param> /// <param name="mappings">The MappingModels</param>
IWireMockServer WithMapping(string mappings); IWireMockServer WithMapping(params MappingModel[] mappings);
}
/// <summary>
/// Register the mappings (via json string).
///
/// This can be used if you the mappings as json string defined and want to register these in WireMock.Net directly instead of using the fluent syntax.
/// </summary>
/// <param name="mappings">The mapping(s) as json string.</param>
IWireMockServer WithMapping(string mappings);
/// <summary>
/// Get the C# code for a mapping.
/// </summary>
/// <param name="guid">The Mapping Guid.</param>
/// <param name="converterType">The <see cref="MappingConverterType"/></param>
/// <returns>C# code (null in case the mapping is not found)</returns>
string? MappingToCSharpCode(Guid guid, MappingConverterType converterType);
/// <summary>
/// Get the C# code for all mappings.
/// </summary>
/// <param name="converterType">The <see cref="MappingConverterType"/></param>
/// <returns>C# code</returns>
public string MappingsToCSharpCode(MappingConverterType converterType);
} }
+33 -29
View File
@@ -1,38 +1,42 @@
namespace WireMock.Types namespace WireMock.Types;
/// <summary>
/// The BodyType
/// </summary>
public enum BodyType
{ {
/// <summary> /// <summary>
/// The BodyType /// No body present
/// </summary> /// </summary>
public enum BodyType None,
{
/// <summary>
/// No body present
/// </summary>
None,
/// <summary> /// <summary>
/// Body is a String /// Body is a String
/// </summary> /// </summary>
String, String,
/// <summary> /// <summary>
/// Body is a Json object /// Body is a Json object
/// </summary> /// </summary>
Json, Json,
/// <summary> /// <summary>
/// Body is a Byte array /// Body is a Byte array
/// </summary> /// </summary>
Bytes, Bytes,
/// <summary> /// <summary>
/// Body is a File /// Body is a File
/// </summary> /// </summary>
File, File,
/// <summary> /// <summary>
/// Body is a MultiPart /// Body is a MultiPart
/// </summary> /// </summary>
MultiPart MultiPart,
}
/// <summary>
/// Body is a String which is x-www-form-urlencoded.
/// </summary>
FormUrlEncoded
} }
@@ -0,0 +1,31 @@
namespace WireMock.Types;
#if NETSTANDARD1_3_OR_GREATER || NET461
/// <summary>
/// Describes the client certificate requirements for a HTTPS connection.
/// This enum is the same as https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.server.kestrel.https.clientcertificatemode
/// </summary>
public enum ClientCertificateMode
{
/// <summary>
/// A client certificate is not required and will not be requested from clients.
/// </summary>
NoCertificate,
/// <summary>
/// A client certificate will be requested; however, authentication will not fail if a certificate is not provided by the client.
/// </summary>
AllowCertificate,
/// <summary>
/// A client certificate will be requested, and the client must provide a valid certificate for authentication to succeed.
/// </summary>
RequireCertificate,
/// <summary>
/// A client certificate is not required and will not be requested from clients at the start of the connection.
/// It may be requested by the application later.
/// </summary>
DelayCertificate,
}
#endif
@@ -0,0 +1,11 @@
namespace WireMock.Types;
/// <summary>
///
/// </summary>
public enum MappingConverterType
{
Server,
Builder
}
@@ -1,36 +1,25 @@
using System; namespace WireMock.Types;
namespace WireMock.Types /// <summary>
/// Logic to use when replace a JSON node using the Transformer.
/// </summary>
public enum ReplaceNodeOptions
{ {
/// <summary> /// <summary>
/// Flags to use when replace a JSON node using the Transformer. /// Try to evaluate a templated value.
/// In case this is valid, return the value and if the value can be converted to a primitive type, use that value.
/// </summary> /// </summary>
[Flags] EvaluateAndTryToConvert = 0,
public enum ReplaceNodeOptions
{
/// <summary>
/// Default
/// </summary>
None = 0
///// <summary> /// <summary>
///// Replace boolean string value to a real boolean value. (This is used by default to maintain backward compatibility.) /// Try to evaluate a templated value.
///// </summary> /// In case this is valid, return the value, else fallback to the parse behavior.
//Bool = 0b00000001, /// </summary>
Evaluate = 1,
///// <summary> /// <summary>
///// Replace integer string value to a real integer value. /// Parse templated string to a templated string.
///// </summary> /// (keep a templated string value as string value).
//Integer = 0b00000010, /// </summary>
Parse = 2
///// <summary>
///// Replace long string value to a real long value.
///// </summary>
//Long = 0b00000100,
///// <summary>
///// Replace all string values to a real values.
///// </summary>
//All = Bool | Integer | Long
}
} }
@@ -1,23 +1,22 @@
namespace WireMock.Types namespace WireMock.Types;
/// <summary>
/// The ResponseMessage Transformers
/// </summary>
public enum TransformerType
{ {
/// <summary> /// <summary>
/// The ResponseMessage Transformers /// https://github.com/Handlebars-Net/Handlebars.Net
/// </summary> /// </summary>
public enum TransformerType Handlebars,
{
/// <summary>
/// https://github.com/Handlebars-Net/Handlebars.Net
/// </summary>
Handlebars,
/// <summary> /// <summary>
/// https://github.com/scriban/scriban : default /// https://github.com/scriban/scriban : default
/// </summary> /// </summary>
Scriban, Scriban,
/// <summary> /// <summary>
/// https://github.com/scriban/scriban : DotLiquid /// https://github.com/scriban/scriban : DotLiquid
/// </summary> /// </summary>
ScribanDotLiquid ScribanDotLiquid
}
} }
@@ -1,4 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
namespace WireMock.Types; namespace WireMock.Types;
@@ -63,7 +64,8 @@ public class WireMockList<T> : List<T>
return this[0]?.ToString(); return this[0]?.ToString();
default: default:
return base.ToString(); var strings = this.Select(x => x as string ?? x?.ToString());
return string.Join(", ", strings);
} }
} }
} }
@@ -4,7 +4,7 @@
<Description>Commonly used models, enumerations and types.</Description> <Description>Commonly used models, enumerations and types.</Description>
<AssemblyTitle>WireMock.Net.Abstractions</AssemblyTitle> <AssemblyTitle>WireMock.Net.Abstractions</AssemblyTitle>
<Authors>Stef Heyenrath</Authors> <Authors>Stef Heyenrath</Authors>
<TargetFrameworks>net45;net451;netstandard1.0;netstandard2.0;netstandard2.1</TargetFrameworks> <TargetFrameworks>net45;net451;net461;netstandard1.0;netstandard1.3;netstandard2.0;netstandard2.1</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);1591;8603</NoWarn> <NoWarn>$(NoWarn);1591;8603</NoWarn>
<AssemblyName>WireMock.Net.Abstractions</AssemblyName> <AssemblyName>WireMock.Net.Abstractions</AssemblyName>
@@ -35,17 +35,21 @@
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" /> <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
<!-- See also https://mstack.nl/blog/20210801-source-generators --> <!-- See also https://mstack.nl/blog/20210801-source-generators -->
<PackageReference Include="FluentBuilder" Version="0.7.0"> <PackageReference Include="FluentBuilder" Version="0.9.0">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
</ItemGroup> </ItemGroup>
<!--<ItemGroup Condition="'$(TargetFramework)' == 'net45'"> <ItemGroup Condition="$(TargetFramework.StartsWith('netstandard')) and '$(TargetFramework)' != 'netstandard1.0'">
<PackageReference Include="Nullable" Version="1.2.1"> <PackageReference Include="System.Security.Cryptography.X509Certificates" Version="4.3.0" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net461'">
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies.net46" Version="1.0.2">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
</ItemGroup>--> </ItemGroup>
</Project> </Project>
@@ -1,49 +1,49 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<Description>FluentAssertions extensions for WireMock.Net</Description> <Description>FluentAssertions extensions for WireMock.Net</Description>
<AssemblyTitle>WireMock.Net.FluentAssertions</AssemblyTitle> <AssemblyTitle>WireMock.Net.FluentAssertions</AssemblyTitle>
<Authors>Mahmoud Ali;Stef Heyenrath</Authors> <Authors>Mahmoud Ali;Stef Heyenrath</Authors>
<TargetFrameworks>net451;netstandard1.3;netstandard2.0;netstandard2.1</TargetFrameworks> <TargetFrameworks>net451;net47;netstandard1.3;netstandard2.0;netstandard2.1</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<AssemblyName>WireMock.Net.FluentAssertions</AssemblyName> <AssemblyName>WireMock.Net.FluentAssertions</AssemblyName>
<PackageId>WireMock.Net.FluentAssertions</PackageId> <PackageId>WireMock.Net.FluentAssertions</PackageId>
<PackageTags>wiremock;FluentAssertions;UnitTest;Assert;Assertions</PackageTags> <PackageTags>wiremock;FluentAssertions;UnitTest;Assert;Assertions</PackageTags>
<RootNamespace>WireMock.FluentAssertions</RootNamespace> <RootNamespace>WireMock.FluentAssertions</RootNamespace>
<ProjectGuid>{B6269AAC-170A-4346-8B9A-579DED3D9A95}</ProjectGuid> <ProjectGuid>{B6269AAC-170A-4346-8B9A-579DED3D9A95}</ProjectGuid>
<PublishRepositoryUrl>true</PublishRepositoryUrl> <PublishRepositoryUrl>true</PublishRepositoryUrl>
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder> <AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
<EmbedUntrackedSources>true</EmbedUntrackedSources> <EmbedUntrackedSources>true</EmbedUntrackedSources>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects> <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<GenerateBindingRedirectsOutputType>true</GenerateBindingRedirectsOutputType> <GenerateBindingRedirectsOutputType>true</GenerateBindingRedirectsOutputType>
<CodeAnalysisRuleSet>../WireMock.Net/WireMock.Net.ruleset</CodeAnalysisRuleSet> <CodeAnalysisRuleSet>../WireMock.Net/WireMock.Net.ruleset</CodeAnalysisRuleSet>
<SignAssembly>true</SignAssembly> <SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>../WireMock.Net/WireMock.Net.snk</AssemblyOriginatorKeyFile> <AssemblyOriginatorKeyFile>../WireMock.Net/WireMock.Net.snk</AssemblyOriginatorKeyFile>
<!--<DelaySign>true</DelaySign>--> <!--<DelaySign>true</DelaySign>-->
<PublicSign Condition=" '$(OS)' != 'Windows_NT' ">true</PublicSign> <PublicSign Condition=" '$(OS)' != 'Windows_NT' ">true</PublicSign>
<PackageLicenseExpression>MIT</PackageLicenseExpression> <PackageLicenseExpression>MIT</PackageLicenseExpression>
<LangVersion>10</LangVersion> <LangVersion>10</LangVersion>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' "> <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<GeneratePackageOnBuild>true</GeneratePackageOnBuild> <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="JetBrains.Annotations" Version="2022.3.1" PrivateAssets="All" /> <PackageReference Include="JetBrains.Annotations" Version="2022.3.1" PrivateAssets="All" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" /> <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net451' or '$(TargetFramework)' == 'netstandard1.3'"> <ItemGroup Condition="'$(TargetFramework)' == 'net451' or '$(TargetFramework)' == 'netstandard1.3'">
<PackageReference Include="FluentAssertions" Version="5.10.3" /> <PackageReference Include="FluentAssertions" Version="5.10.3" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' != 'net451' and '$(TargetFramework)' != 'netstandard1.3'"> <ItemGroup Condition="'$(TargetFramework)' != 'net451' and '$(TargetFramework)' != 'netstandard1.3'">
<PackageReference Include="FluentAssertions" Version="6.5.1" /> <PackageReference Include="FluentAssertions" Version="6.5.1" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\WireMock.Net.Abstractions\WireMock.Net.Abstractions.csproj" /> <ProjectReference Include="..\WireMock.Net.Abstractions\WireMock.Net.Abstractions.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>
@@ -31,13 +31,13 @@ public static class WireMockServerExtensions
/// </summary> /// </summary>
/// <param name="server">The WireMockServer instance</param> /// <param name="server">The WireMockServer instance</param>
/// <param name="path">Path containing OpenAPI file to parse and use the mappings.</param> /// <param name="path">Path containing OpenAPI file to parse and use the mappings.</param>
/// <param name="diagnostic">Returns diagnostic object containing errors detected during parsing</param>
/// <param name="settings">Additional settings</param> /// <param name="settings">Additional settings</param>
/// <param name="diagnostic">Returns diagnostic object containing errors detected during parsing</param>
[PublicAPI] [PublicAPI]
public static IWireMockServer WithMappingFromOpenApiFile(this IWireMockServer server, string path, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic) public static IWireMockServer WithMappingFromOpenApiFile(this IWireMockServer server, string path, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic)
{ {
Guard.NotNull(server, nameof(server)); Guard.NotNull(server);
Guard.NotNullOrEmpty(path, nameof(path)); Guard.NotNullOrEmpty(path);
var mappings = new WireMockOpenApiParser().FromFile(path, settings, out diagnostic); var mappings = new WireMockOpenApiParser().FromFile(path, settings, out diagnostic);
@@ -80,9 +80,9 @@ public static class WireMockServerExtensions
/// </summary> /// </summary>
/// <param name="server">The WireMockServer instance</param> /// <param name="server">The WireMockServer instance</param>
/// <param name="document">The OpenAPI document to use as mappings.</param> /// <param name="document">The OpenAPI document to use as mappings.</param>
/// <param name="settings">Additional settings [optional]</param> /// <param name="settings">Additional settings [optional].</param>
[PublicAPI] [PublicAPI]
public static IWireMockServer WithMappingFromOpenApiDocument(this IWireMockServer server, OpenApiDocument document, WireMockOpenApiParserSettings settings) public static IWireMockServer WithMappingFromOpenApiDocument(this IWireMockServer server, OpenApiDocument document, WireMockOpenApiParserSettings? settings = null)
{ {
Guard.NotNull(server); Guard.NotNull(server);
Guard.NotNull(document); Guard.NotNull(document);
@@ -5,53 +5,69 @@ using Microsoft.OpenApi.Readers;
using WireMock.Admin.Mappings; using WireMock.Admin.Mappings;
using WireMock.Net.OpenApiParser.Settings; using WireMock.Net.OpenApiParser.Settings;
namespace WireMock.Net.OpenApiParser namespace WireMock.Net.OpenApiParser;
/// <summary>
/// Parse a OpenApi/Swagger/V2/V3 or Raml to WireMock MappingModels.
/// </summary>
public interface IWireMockOpenApiParser
{ {
/// <summary> /// <summary>
/// Parse a OpenApi/Swagger/V2/V3 or Raml to WireMock MappingModels. /// Generate <see cref="IReadOnlyList{MappingModel}"/> from a file-path.
/// </summary> /// </summary>
public interface IWireMockOpenApiParser /// <param name="path">The path to read the OpenApi/Swagger/V2/V3 or Raml file.</param>
{ /// <param name="diagnostic">OpenApiDiagnostic output</param>
/// <summary> /// <returns>MappingModel</returns>
/// Generate <see cref="IEnumerable{MappingModel}"/> from a file-path. IReadOnlyList<MappingModel> FromFile(string path, out OpenApiDiagnostic diagnostic);
/// </summary>
/// <param name="path">The path to read the OpenApi/Swagger/V2/V3 or Raml file.</param>
/// <param name="diagnostic">OpenApiDiagnostic output</param>
/// <returns>MappingModel</returns>
IEnumerable<MappingModel> FromFile(string path, out OpenApiDiagnostic diagnostic);
/// <summary> /// <summary>
/// Generate <see cref="IEnumerable{MappingModel}"/> from a file-path. /// Generate <see cref="IReadOnlyList{MappingModel}"/> from a file-path.
/// </summary> /// </summary>
/// <param name="path">The path to read the OpenApi/Swagger/V2/V3 or Raml file.</param> /// <param name="path">The path to read the OpenApi/Swagger/V2/V3 or Raml file.</param>
/// <param name="settings">Additional settings</param> /// <param name="settings">Additional settings</param>
/// <param name="diagnostic">OpenApiDiagnostic output</param> /// <param name="diagnostic">OpenApiDiagnostic output</param>
/// <returns>MappingModel</returns> /// <returns>MappingModel</returns>
IEnumerable<MappingModel> FromFile(string path, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic); IReadOnlyList<MappingModel> FromFile(string path, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic);
/// <summary> /// <summary>
/// Generate <see cref="IEnumerable{MappingModel}"/> from an <seealso cref="OpenApiDocument"/>. /// Generate <see cref="IReadOnlyList{MappingModel}"/> from an <seealso cref="OpenApiDocument"/>.
/// </summary> /// </summary>
/// <param name="document">The source OpenApiDocument</param> /// <param name="document">The source OpenApiDocument</param>
/// <param name="settings">Additional settings [optional]</param> /// <param name="settings">Additional settings [optional]</param>
/// <returns>MappingModel</returns> /// <returns>MappingModel</returns>
IEnumerable<MappingModel> FromDocument(OpenApiDocument document, WireMockOpenApiParserSettings? settings = null); IReadOnlyList<MappingModel> FromDocument(OpenApiDocument document, WireMockOpenApiParserSettings? settings = null);
/// <summary> /// <summary>
/// Generate <see cref="IEnumerable{MappingModel}"/> from a <seealso cref="Stream"/>. /// Generate <see cref="IReadOnlyList{MappingModel}"/> from a <seealso cref="Stream"/>.
/// </summary> /// </summary>
/// <param name="stream">The source stream</param> /// <param name="stream">The source stream</param>
/// <param name="diagnostic">OpenApiDiagnostic output</param> /// <param name="diagnostic">OpenApiDiagnostic output</param>
/// <returns>MappingModel</returns> /// <returns>MappingModel</returns>
IEnumerable<MappingModel> FromStream(Stream stream, out OpenApiDiagnostic diagnostic); IReadOnlyList<MappingModel> FromStream(Stream stream, out OpenApiDiagnostic diagnostic);
/// <summary> /// <summary>
/// Generate <see cref="IEnumerable{MappingModel}"/> from a <seealso cref="Stream"/>. /// Generate <see cref="IReadOnlyList{MappingModel}"/> from a <seealso cref="Stream"/>.
/// </summary> /// </summary>
/// <param name="stream">The source stream</param> /// <param name="stream">The source stream</param>
/// <param name="settings">Additional settings</param> /// <param name="settings">Additional settings</param>
/// <param name="diagnostic">OpenApiDiagnostic output</param> /// <param name="diagnostic">OpenApiDiagnostic output</param>
/// <returns>MappingModel</returns> /// <returns>MappingModel</returns>
IEnumerable<MappingModel> FromStream(Stream stream, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic); IReadOnlyList<MappingModel> FromStream(Stream stream, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic);
}
/// <summary>
/// Generate <see cref="IReadOnlyList{MappingModel}"/> from a <seealso cref="string"/>.
/// </summary>
/// <param name="text">The source text</param>
/// <param name="diagnostic">OpenApiDiagnostic output</param>
/// <returns>MappingModel</returns>
IReadOnlyList<MappingModel> FromText(string text, out OpenApiDiagnostic diagnostic);
/// <summary>
/// Generate <see cref="IReadOnlyList{MappingModel}"/> from a <seealso cref="string"/>.
/// </summary>
/// <param name="text">The source text</param>
/// <param name="settings">Additional settings</param>
/// <param name="diagnostic">OpenApiDiagnostic output</param>
/// <returns>MappingModel</returns>
IReadOnlyList<MappingModel> FromText(string text, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic);
} }
@@ -23,27 +23,28 @@ internal class OpenApiPathsMapper
private const string HeaderContentType = "Content-Type"; private const string HeaderContentType = "Content-Type";
private readonly WireMockOpenApiParserSettings _settings; private readonly WireMockOpenApiParserSettings _settings;
private readonly ExampleValueGenerator _exampleValueGenerator; private readonly IExampleValueGenerator _exampleValueGenerator;
private readonly IExampleValueGenerator _regexExampleValueGenerator;
public OpenApiPathsMapper(WireMockOpenApiParserSettings settings) public OpenApiPathsMapper(WireMockOpenApiParserSettings settings)
{ {
_settings = Guard.NotNull(settings); _settings = Guard.NotNull(settings);
_exampleValueGenerator = new ExampleValueGenerator(settings); _exampleValueGenerator = new ExampleValueGenerator(settings);
_regexExampleValueGenerator = new RegexExampleValueGenerator(settings);
} }
public IEnumerable<MappingModel> ToMappingModels(OpenApiPaths paths, IList<OpenApiServer> servers) public IReadOnlyList<MappingModel> ToMappingModels(OpenApiPaths? paths, IList<OpenApiServer> servers)
{ {
return paths.Select(p => MapPath(p.Key, p.Value, servers)).SelectMany(x => x); return paths?
.OrderBy(p => p.Key)
.Select(p => MapPath(p.Key, p.Value, servers))
.SelectMany(x => x)
.ToArray() ?? Array.Empty<MappingModel>();
} }
private IEnumerable<MappingModel> MapPaths(OpenApiPaths paths, IList<OpenApiServer> servers) private IReadOnlyList<MappingModel> MapPath(string path, OpenApiPathItem pathItem, IList<OpenApiServer> servers)
{ {
return paths.Select(p => MapPath(p.Key, p.Value, servers)).SelectMany(x => x); return pathItem.Operations.Select(o => MapOperationToMappingModel(path, o.Key.ToString().ToUpperInvariant(), o.Value, servers)).ToArray();
}
private IEnumerable<MappingModel> MapPath(string path, OpenApiPathItem pathItem, IList<OpenApiServer> servers)
{
return pathItem.Operations.Select(o => MapOperationToMappingModel(path, o.Key.ToString().ToUpperInvariant(), o.Value, servers));
} }
private MappingModel MapOperationToMappingModel(string path, string httpMethod, OpenApiOperation operation, IList<OpenApiServer> servers) private MappingModel MapOperationToMappingModel(string path, string httpMethod, OpenApiOperation operation, IList<OpenApiServer> servers)
@@ -123,7 +124,7 @@ internal class OpenApiPathsMapper
}; };
} }
private bool TryGetContent(IDictionary<string, OpenApiMediaType>? contents, [NotNullWhen(true)] out OpenApiMediaType? openApiMediaType, [NotNullWhen(true)] out string? contentType) private static bool TryGetContent(IDictionary<string, OpenApiMediaType>? contents, [NotNullWhen(true)] out OpenApiMediaType? openApiMediaType, [NotNullWhen(true)] out string? contentType)
{ {
openApiMediaType = null; openApiMediaType = null;
contentType = null; contentType = null;
@@ -270,54 +271,19 @@ internal class OpenApiPathsMapper
return newPath; return newPath;
} }
private string MapBasePath(IList<OpenApiServer>? servers) private IDictionary<string, object>? MapHeaders(string? responseContentType, IDictionary<string, OpenApiHeader>? headers)
{ {
if (servers == null || servers.Count == 0) var mappedHeaders = headers?.ToDictionary(
{
return string.Empty;
}
OpenApiServer server = servers.First();
if (Uri.TryCreate(server.Url, UriKind.RelativeOrAbsolute, out Uri uriResult))
{
return uriResult.IsAbsoluteUri ? uriResult.AbsolutePath : uriResult.ToString();
}
return string.Empty;
}
private JToken? MapOpenApiAnyToJToken(IOpenApiAny? any)
{
if (any == null)
{
return null;
}
using var outputString = new StringWriter();
var writer = new OpenApiJsonWriter(outputString);
any.Write(writer, OpenApiSpecVersion.OpenApi3_0);
if (any.AnyType == AnyType.Array)
{
return JArray.Parse(outputString.ToString());
}
return JObject.Parse(outputString.ToString());
}
private IDictionary<string, object?>? MapHeaders(string responseContentType, IDictionary<string, OpenApiHeader> headers)
{
var mappedHeaders = headers.ToDictionary(
item => item.Key, item => item.Key,
_ => GetExampleMatcherModel(null, _settings.HeaderPatternToUse).Pattern _ => GetExampleMatcherModel(null, _settings.HeaderPatternToUse).Pattern!
); );
if (!string.IsNullOrEmpty(responseContentType)) if (!string.IsNullOrEmpty(responseContentType))
{ {
mappedHeaders.TryAdd(HeaderContentType, responseContentType); mappedHeaders.TryAdd(HeaderContentType, responseContentType!);
} }
return mappedHeaders.Keys.Any() ? mappedHeaders : null; return mappedHeaders?.Keys.Any() == true ? mappedHeaders : null;
} }
private IList<ParamModel>? MapQueryParameters(IEnumerable<OpenApiParameter> queryParameters) private IList<ParamModel>? MapQueryParameters(IEnumerable<OpenApiParameter> queryParameters)
@@ -356,19 +322,38 @@ internal class OpenApiPathsMapper
return list.Any() ? list : null; return list.Any() ? list : null;
} }
private MatcherModel GetExampleMatcherModel(OpenApiSchema? schema, ExampleValueType type) private MatcherModel GetExampleMatcherModel(OpenApiSchema? schema, ExampleValueType exampleValueType)
{ {
return type switch return exampleValueType switch
{ {
ExampleValueType.Value => new MatcherModel { Name = "ExactMatcher", Pattern = GetExampleValueAsStringForSchemaType(schema), IgnoreCase = _settings.IgnoreCaseExampleValues }, ExampleValueType.Value => new MatcherModel
{
Name = "ExactMatcher",
Pattern = GetExampleValueAsStringForSchemaType(schema, exampleValueType),
IgnoreCase = _settings.IgnoreCaseExampleValues
},
_ => new MatcherModel { Name = "WildcardMatcher", Pattern = "*" } ExampleValueType.Regex => new MatcherModel
{
Name = "RegexMatcher",
Pattern = GetExampleValueAsStringForSchemaType(schema, exampleValueType),
IgnoreCase = _settings.IgnoreCaseExampleValues
},
_ => new MatcherModel
{
Name = "WildcardMatcher",
Pattern = "*",
IgnoreCase = _settings.IgnoreCaseExampleValues
}
}; };
} }
private string GetExampleValueAsStringForSchemaType(OpenApiSchema? schema) private string GetExampleValueAsStringForSchemaType(OpenApiSchema? schema, ExampleValueType exampleValueType)
{ {
var value = _exampleValueGenerator.GetExampleValue(schema); var value = exampleValueType == ExampleValueType.Regex ?
_regexExampleValueGenerator.GetExampleValue(schema) :
_exampleValueGenerator.GetExampleValue(schema);
return value switch return value switch
{ {
@@ -377,4 +362,39 @@ internal class OpenApiPathsMapper
_ => value.ToString(), _ => value.ToString(),
}; };
} }
private static string MapBasePath(IList<OpenApiServer>? servers)
{
if (servers == null || servers.Count == 0)
{
return string.Empty;
}
var server = servers.First();
if (Uri.TryCreate(server.Url, UriKind.RelativeOrAbsolute, out Uri uriResult))
{
return uriResult.IsAbsoluteUri ? uriResult.AbsolutePath : uriResult.ToString();
}
return string.Empty;
}
private static JToken? MapOpenApiAnyToJToken(IOpenApiAny? any)
{
if (any == null)
{
return null;
}
using var outputString = new StringWriter();
var writer = new OpenApiJsonWriter(outputString);
any.Write(writer, OpenApiSpecVersion.OpenApi3_0);
if (any.AnyType == AnyType.Array)
{
return JArray.Parse(outputString.ToString());
}
return JObject.Parse(outputString.ToString());
}
} }
@@ -36,5 +36,5 @@ public class WireMockOpenApiParserExampleValues : IWireMockOpenApiParserExampleV
public virtual string String { get; set; } = "example-string"; public virtual string String { get; set; } = "example-string";
/// <inheritdoc /> /// <inheritdoc />
public virtual OpenApiSchema? Schema { get; set; } = new OpenApiSchema(); public virtual OpenApiSchema? Schema { get; set; } = new();
} }
@@ -1,7 +1,7 @@
namespace WireMock.Net.OpenApiParser.Types; namespace WireMock.Net.OpenApiParser.Types;
/// <summary> /// <summary>
/// The example value to use /// The (example) value pattern to use.
/// </summary> /// </summary>
public enum ExampleValueType public enum ExampleValueType
{ {
@@ -12,6 +12,11 @@ public enum ExampleValueType
/// </summary> /// </summary>
Value, Value,
/// <summary>
/// Build a Regex based on the SchemaType.
/// </summary>
Regex,
/// <summary> /// <summary>
/// Just use a Wildcard (*) character. /// Just use a Wildcard (*) character.
/// </summary> /// </summary>
@@ -1,5 +1,4 @@
using System; using System.Linq;
using System.Collections.Generic;
using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Models;
using Stef.Validation; using Stef.Validation;
@@ -9,118 +8,110 @@ using WireMock.Net.OpenApiParser.Types;
namespace WireMock.Net.OpenApiParser.Utils; namespace WireMock.Net.OpenApiParser.Utils;
internal class ExampleValueGenerator internal class ExampleValueGenerator : IExampleValueGenerator
{ {
private readonly WireMockOpenApiParserSettings _settings; private readonly IWireMockOpenApiParserExampleValues _exampleValues;
public ExampleValueGenerator(WireMockOpenApiParserSettings settings) public ExampleValueGenerator(WireMockOpenApiParserSettings settings)
{ {
_settings = Guard.NotNull(settings); Guard.NotNull(settings);
// Check if user provided an own implementation // Check if user provided an own implementation
if (settings.ExampleValues is null) if (settings.ExampleValues is null)
{ {
if (_settings.DynamicExamples) if (settings.DynamicExamples)
{ {
_settings.ExampleValues = new WireMockOpenApiParserDynamicExampleValues(); _exampleValues = new WireMockOpenApiParserDynamicExampleValues();
} }
else else
{ {
_settings.ExampleValues = new WireMockOpenApiParserExampleValues(); _exampleValues = new WireMockOpenApiParserExampleValues();
} }
} }
else
{
_exampleValues = settings.ExampleValues;
}
} }
public object GetExampleValue(OpenApiSchema? schema) public object GetExampleValue(OpenApiSchema? schema)
{ {
var schemaExample = schema?.Example; var schemaExample = schema?.Example;
var schemaEnum = GetRandomEnumValue(schema?.Enum); var schemaEnum = schema?.Enum?.FirstOrDefault();
_settings.ExampleValues.Schema = schema; _exampleValues.Schema = schema;
switch (schema?.GetSchemaType()) switch (schema?.GetSchemaType())
{ {
case SchemaType.Boolean: case SchemaType.Boolean:
var exampleBoolean = schemaExample as OpenApiBoolean; var exampleBoolean = schemaExample as OpenApiBoolean;
return exampleBoolean is null ? _settings.ExampleValues.Boolean : exampleBoolean.Value; return exampleBoolean?.Value ?? _exampleValues.Boolean;
case SchemaType.Integer: case SchemaType.Integer:
switch (schema?.GetSchemaFormat()) switch (schema?.GetSchemaFormat())
{ {
case SchemaFormat.Int64: case SchemaFormat.Int64:
var exampleLong = (OpenApiLong)schemaExample; var exampleLong = schemaExample as OpenApiLong;
var enumLong = (OpenApiLong)schemaEnum; var enumLong = schemaEnum as OpenApiLong;
var valueLongEnumOrExample = enumLong is null ? exampleLong?.Value : enumLong?.Value; var valueLongEnumOrExample = enumLong?.Value ?? exampleLong?.Value;
return valueLongEnumOrExample ?? _settings.ExampleValues.Integer; return valueLongEnumOrExample ?? _exampleValues.Integer;
default: default:
var exampleInteger = (OpenApiInteger)schemaExample; var exampleInteger = schemaExample as OpenApiInteger;
var enumInteger = (OpenApiInteger)schemaEnum; var enumInteger = schemaEnum as OpenApiInteger;
var valueIntegerEnumOrExample = enumInteger is null ? exampleInteger?.Value : enumInteger?.Value; var valueIntegerEnumOrExample = enumInteger?.Value ?? exampleInteger?.Value;
return valueIntegerEnumOrExample ?? _settings.ExampleValues.Integer; return valueIntegerEnumOrExample ?? _exampleValues.Integer;
} }
case SchemaType.Number: case SchemaType.Number:
switch (schema?.GetSchemaFormat()) switch (schema?.GetSchemaFormat())
{ {
case SchemaFormat.Float: case SchemaFormat.Float:
var exampleFloat = (OpenApiFloat)schemaExample; var exampleFloat = schemaExample as OpenApiFloat;
var enumFloat = (OpenApiFloat)schemaEnum; var enumFloat = schemaEnum as OpenApiFloat;
var valueFloatEnumOrExample = enumFloat is null ? exampleFloat?.Value : enumFloat?.Value; var valueFloatEnumOrExample = enumFloat?.Value ?? exampleFloat?.Value;
return valueFloatEnumOrExample ?? _settings.ExampleValues.Float; return valueFloatEnumOrExample ?? _exampleValues.Float;
default: default:
var exampleDouble = (OpenApiDouble)schemaExample; var exampleDouble = schemaExample as OpenApiDouble;
var enumDouble = (OpenApiDouble)schemaEnum; var enumDouble = schemaEnum as OpenApiDouble;
var valueDoubleEnumOrExample = enumDouble is null ? exampleDouble?.Value : enumDouble?.Value; var valueDoubleEnumOrExample = enumDouble?.Value ?? exampleDouble?.Value;
return valueDoubleEnumOrExample ?? _settings.ExampleValues.Double; return valueDoubleEnumOrExample ?? _exampleValues.Double;
} }
default: default:
switch (schema?.GetSchemaFormat()) switch (schema?.GetSchemaFormat())
{ {
case SchemaFormat.Date: case SchemaFormat.Date:
var exampleDate = (OpenApiDate)schemaExample; var exampleDate = schemaExample as OpenApiDate;
var enumDate = (OpenApiDate)schemaEnum; var enumDate = schemaEnum as OpenApiDate;
var valueDateEnumOrExample = enumDate is null ? exampleDate?.Value : enumDate?.Value; var valueDateEnumOrExample = enumDate?.Value ?? exampleDate?.Value;
return DateTimeUtils.ToRfc3339Date(valueDateEnumOrExample ?? _settings.ExampleValues.Date()); return DateTimeUtils.ToRfc3339Date(valueDateEnumOrExample ?? _exampleValues.Date());
case SchemaFormat.DateTime: case SchemaFormat.DateTime:
var exampleDateTime = (OpenApiDateTime)schemaExample; var exampleDateTime = schemaExample as OpenApiDateTime;
var enumDateTime = (OpenApiDateTime)schemaEnum; var enumDateTime = schemaEnum as OpenApiDateTime;
var valueDateTimeEnumOrExample = enumDateTime is null ? exampleDateTime?.Value : enumDateTime?.Value; var valueDateTimeEnumOrExample = enumDateTime?.Value ?? exampleDateTime?.Value;
return DateTimeUtils.ToRfc3339DateTime(valueDateTimeEnumOrExample?.DateTime ?? _settings.ExampleValues.DateTime()); return DateTimeUtils.ToRfc3339DateTime(valueDateTimeEnumOrExample?.DateTime ?? _exampleValues.DateTime());
case SchemaFormat.Byte: case SchemaFormat.Byte:
var exampleByte = (OpenApiByte)schemaExample; var exampleByte = schemaExample as OpenApiByte;
var enumByte = (OpenApiByte)schemaEnum; var enumByte = schemaEnum as OpenApiByte;
var valueByteEnumOrExample = enumByte is null ? exampleByte?.Value : enumByte?.Value; var valueByteEnumOrExample = enumByte?.Value ?? exampleByte?.Value;
return valueByteEnumOrExample ?? _settings.ExampleValues.Bytes; return valueByteEnumOrExample ?? _exampleValues.Bytes;
case SchemaFormat.Binary: case SchemaFormat.Binary:
var exampleBinary = (OpenApiBinary)schemaExample; var exampleBinary = schemaExample as OpenApiBinary;
var enumBinary = (OpenApiBinary)schemaEnum; var enumBinary = schemaEnum as OpenApiBinary;
var valueBinaryEnumOrExample = enumBinary is null ? exampleBinary?.Value : enumBinary?.Value; var valueBinaryEnumOrExample = enumBinary?.Value ?? exampleBinary?.Value;
return valueBinaryEnumOrExample ?? _settings.ExampleValues.Object; return valueBinaryEnumOrExample ?? _exampleValues.Object;
default: default:
var exampleString = (OpenApiString)schemaExample; var exampleString = schemaExample as OpenApiString;
var enumString = (OpenApiString)schemaEnum; var enumString = schemaEnum as OpenApiString;
var valueStringEnumOrExample = enumString is null ? exampleString?.Value : enumString?.Value; var valueStringEnumOrExample = enumString?.Value ?? exampleString?.Value;
return valueStringEnumOrExample ?? _settings.ExampleValues.String; return valueStringEnumOrExample ?? _exampleValues.String;
} }
} }
} }
private static IOpenApiAny? GetRandomEnumValue(IList<IOpenApiAny>? schemaEnum)
{
if (schemaEnum?.Count > 0)
{
int maxValue = schemaEnum.Count - 1;
int randomEnum = new Random().Next(0, maxValue);
return schemaEnum[randomEnum];
}
return null;
}
} }
@@ -0,0 +1,8 @@
using Microsoft.OpenApi.Models;
namespace WireMock.Net.OpenApiParser.Utils;
internal interface IExampleValueGenerator
{
object GetExampleValue(OpenApiSchema? schema);
}
@@ -0,0 +1,40 @@
using Microsoft.OpenApi.Models;
using Stef.Validation;
using WireMock.Net.OpenApiParser.Extensions;
using WireMock.Net.OpenApiParser.Settings;
using WireMock.Net.OpenApiParser.Types;
namespace WireMock.Net.OpenApiParser.Utils;
internal class RegexExampleValueGenerator : IExampleValueGenerator
{
public RegexExampleValueGenerator(WireMockOpenApiParserSettings settings)
{
Guard.NotNull(settings);
}
public object GetExampleValue(OpenApiSchema? schema)
{
switch (schema?.GetSchemaType())
{
case SchemaType.Boolean:
return @"(true|false)";
case SchemaType.Integer:
return @"-?\d+";
case SchemaType.Number:
return @"[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?";
default:
return schema?.GetSchemaFormat() switch
{
SchemaFormat.Date => @"(\d{4})-([01]\d)-([0-3]\d)",
SchemaFormat.DateTime => @"(\d{4})-([01]\d)-([0-3]\d)T([0-2]\d):([0-5]\d):([0-5]\d)(\.\d+)?(Z|[+-][0-2]\d:[0-5]\d)",
SchemaFormat.Byte => @"(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)",
SchemaFormat.Binary => @"[a-zA-Z0-9\+/]*={0,3}",
_ => ".*"
};
}
}
}
@@ -4,7 +4,7 @@
<Description>An OpenApi (swagger) parser to generate MappingModel or mapping.json file.</Description> <Description>An OpenApi (swagger) parser to generate MappingModel or mapping.json file.</Description>
<TargetFrameworks>net46;netstandard2.0;netstandard2.1</TargetFrameworks> <TargetFrameworks>net46;netstandard2.0;netstandard2.1</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<PackageTags>wiremock;openapi;OAS;converter;parser;openapiparser</PackageTags> <PackageTags>wiremock;openapi;OAS;raml;converter;parser;openapiparser</PackageTags>
<ProjectGuid>{D3804228-91F4-4502-9595-39584E5AADAD}</ProjectGuid> <ProjectGuid>{D3804228-91F4-4502-9595-39584E5AADAD}</ProjectGuid>
<PublishRepositoryUrl>true</PublishRepositoryUrl> <PublishRepositoryUrl>true</PublishRepositoryUrl>
<CodeAnalysisRuleSet>../WireMock.Net/WireMock.Net.ruleset</CodeAnalysisRuleSet> <CodeAnalysisRuleSet>../WireMock.Net/WireMock.Net.ruleset</CodeAnalysisRuleSet>
@@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text;
using JetBrains.Annotations; using JetBrains.Annotations;
using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Readers; using Microsoft.OpenApi.Readers;
@@ -8,6 +9,7 @@ using RamlToOpenApiConverter;
using WireMock.Admin.Mappings; using WireMock.Admin.Mappings;
using WireMock.Net.OpenApiParser.Mappers; using WireMock.Net.OpenApiParser.Mappers;
using WireMock.Net.OpenApiParser.Settings; using WireMock.Net.OpenApiParser.Settings;
using WireMock.Net.OpenApiParser.Types;
namespace WireMock.Net.OpenApiParser; namespace WireMock.Net.OpenApiParser;
@@ -16,18 +18,25 @@ namespace WireMock.Net.OpenApiParser;
/// </summary> /// </summary>
public class WireMockOpenApiParser : IWireMockOpenApiParser public class WireMockOpenApiParser : IWireMockOpenApiParser
{ {
private readonly OpenApiStreamReader _reader = new OpenApiStreamReader(); private readonly WireMockOpenApiParserSettings _wireMockOpenApiParserSettings = new WireMockOpenApiParserSettings
/// <inheritdoc cref="IWireMockOpenApiParser.FromFile(string, out OpenApiDiagnostic)" />
[PublicAPI]
public IEnumerable<MappingModel> FromFile(string path, out OpenApiDiagnostic diagnostic)
{ {
return FromFile(path, new WireMockOpenApiParserSettings(), out diagnostic); HeaderPatternToUse = ExampleValueType.Regex,
QueryParameterPatternToUse = ExampleValueType.Regex,
PathPatternToUse = ExampleValueType.Regex
};
private readonly OpenApiStreamReader _reader = new();
/// <inheritdoc />
[PublicAPI]
public IReadOnlyList<MappingModel> FromFile(string path, out OpenApiDiagnostic diagnostic)
{
return FromFile(path, _wireMockOpenApiParserSettings, out diagnostic);
} }
/// <inheritdoc cref="IWireMockOpenApiParser.FromFile(string, WireMockOpenApiParserSettings, out OpenApiDiagnostic)" /> /// <inheritdoc />
[PublicAPI] [PublicAPI]
public IEnumerable<MappingModel> FromFile(string path, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic) public IReadOnlyList<MappingModel> FromFile(string path, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic)
{ {
OpenApiDocument document; OpenApiDocument document;
if (Path.GetExtension(path).EndsWith("raml", StringComparison.OrdinalIgnoreCase)) if (Path.GetExtension(path).EndsWith("raml", StringComparison.OrdinalIgnoreCase))
@@ -44,24 +53,38 @@ public class WireMockOpenApiParser : IWireMockOpenApiParser
return FromDocument(document, settings); return FromDocument(document, settings);
} }
/// <inheritdoc cref="IWireMockOpenApiParser.FromStream(Stream, out OpenApiDiagnostic)" /> /// <inheritdoc />
[PublicAPI] [PublicAPI]
public IEnumerable<MappingModel> FromStream(Stream stream, out OpenApiDiagnostic diagnostic) public IReadOnlyList<MappingModel> FromDocument(OpenApiDocument openApiDocument, WireMockOpenApiParserSettings? settings = null)
{
return new OpenApiPathsMapper(settings ?? _wireMockOpenApiParserSettings).ToMappingModels(openApiDocument.Paths, openApiDocument.Servers);
}
/// <inheritdoc />
[PublicAPI]
public IReadOnlyList<MappingModel> FromStream(Stream stream, out OpenApiDiagnostic diagnostic)
{ {
return FromDocument(_reader.Read(stream, out diagnostic)); return FromDocument(_reader.Read(stream, out diagnostic));
} }
/// <inheritdoc cref="IWireMockOpenApiParser.FromStream(Stream, WireMockOpenApiParserSettings, out OpenApiDiagnostic)" /> /// <inheritdoc />
[PublicAPI] [PublicAPI]
public IEnumerable<MappingModel> FromStream(Stream stream, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic) public IReadOnlyList<MappingModel> FromStream(Stream stream, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic)
{ {
return FromDocument(_reader.Read(stream, out diagnostic), settings); return FromDocument(_reader.Read(stream, out diagnostic), settings);
} }
/// <inheritdoc cref="IWireMockOpenApiParser.FromDocument(OpenApiDocument, WireMockOpenApiParserSettings)" /> /// <inheritdoc />
[PublicAPI] [PublicAPI]
public IEnumerable<MappingModel> FromDocument(OpenApiDocument openApiDocument, WireMockOpenApiParserSettings? settings = null) public IReadOnlyList<MappingModel> FromText(string text, out OpenApiDiagnostic diagnostic)
{ {
return new OpenApiPathsMapper(settings).ToMappingModels(openApiDocument.Paths, openApiDocument.Servers); return FromStream(new MemoryStream(Encoding.UTF8.GetBytes(text)), out diagnostic);
}
/// <inheritdoc />
[PublicAPI]
public IReadOnlyList<MappingModel> FromText(string text, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic)
{
return FromStream(new MemoryStream(Encoding.UTF8.GetBytes(text)), settings, out diagnostic);
} }
} }
@@ -0,0 +1,58 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Stef.Validation;
using WireMock.Admin.Mappings;
namespace WireMock.Client.Builders;
/// <summary>
/// AdminApiMappingBuilder
/// </summary>
public class AdminApiMappingBuilder
{
private readonly List<Action<MappingModelBuilder>> _mappingModelBuilderActions = new();
private readonly IWireMockAdminApi _api;
/// <summary>
/// AdminApiMappingBuilder
/// </summary>
/// <param name="api">The <see cref="IWireMockAdminApi"/>.</param>
public AdminApiMappingBuilder(IWireMockAdminApi api)
{
_api = Guard.NotNull(api);
}
/// <summary>
/// The Given
/// </summary>
/// <param name="mappingModelBuilderAction">The action.</param>
public void Given(Action<MappingModelBuilder> mappingModelBuilderAction)
{
_mappingModelBuilderActions.Add(Guard.NotNull(mappingModelBuilderAction));
}
/// <summary>
/// Build the mappings and post these using the <see cref="IWireMockAdminApi"/> to the WireMock.Net server.
/// </summary>
/// <param name="cancellationToken">The optional CancellationToken.</param>
/// <returns><see cref="StatusModel"/></returns>
public Task<StatusModel> BuildAndPostAsync(CancellationToken cancellationToken = default)
{
var modelMappings = new List<MappingModel>();
foreach (var mappingModelBuilderAction in _mappingModelBuilderActions)
{
cancellationToken.ThrowIfCancellationRequested();
var mappingModelBuilder = new MappingModelBuilder();
mappingModelBuilderAction(mappingModelBuilder);
modelMappings.Add(mappingModelBuilder.Build());
}
return _api.PostMappingsAsync(modelMappings, cancellationToken);
}
}
@@ -0,0 +1,46 @@
using System.Text;
using JsonConverter.Abstractions;
using JsonConverter.Newtonsoft.Json;
using WireMock.Admin.Mappings;
namespace WireMock.Client.Extensions;
/// <summary>
/// ResponseModelBuilder
/// </summary>
public static class ResponseModelBuilderExtensions
{
private static readonly Encoding Utf8NoBom = new UTF8Encoding(false);
private static readonly IJsonConverter JsonConverter = new NewtonsoftJsonConverter();
/// <summary>
/// WithBodyAsJson
/// </summary>
/// <param name="builder">The ResponseModelBuilder.</param>
/// <param name="body">The body.</param>
/// <param name="encoding">The body encoding.</param>
/// <param name="indented">Define whether child objects to be indented.</param>
public static ResponseModelBuilder WithBodyAsJson(this ResponseModelBuilder builder, object body, Encoding? encoding = null, bool? indented = null)
{
return builder.WithBodyAsBytes(() =>
{
var options = new JsonConverterOptions
{
WriteIndented = indented == true
};
var jsonBody = JsonConverter.Serialize(body, options);
return (encoding ?? Utf8NoBom).GetBytes(jsonBody);
});
}
/// <summary>
/// WithBodyAsJson
/// </summary>
/// <param name="builder">The ResponseModelBuilder.</param>
/// <param name="body">The body.</param>
/// <param name="indented">Define whether child objects to be indented.</param>
public static ResponseModelBuilder WithBodyAsJson(this ResponseModelBuilder builder, object body, bool indented)
{
return builder.WithBodyAsJson(body, null, indented);
}
}
@@ -0,0 +1,19 @@
using WireMock.Client.Builders;
namespace WireMock.Client.Extensions;
/// <summary>
/// Some extensions for <see cref="IWireMockAdminApi"/>.
/// </summary>
public static class WireMockAdminApiExtensions
{
/// <summary>
/// Get a new <see cref="AdminApiMappingBuilder"/> for the <see cref="IWireMockAdminApi"/>.
/// </summary>
/// <param name="api">See <see cref="IWireMockAdminApi"/>.</param>
/// <returns></returns>
public static AdminApiMappingBuilder GetMappingBuilder(this IWireMockAdminApi api)
{
return new AdminApiMappingBuilder(api);
}
}
+257 -191
View File
@@ -1,233 +1,299 @@
using RestEase;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Net.Http.Headers; using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using RestEase;
using WireMock.Admin.Mappings; using WireMock.Admin.Mappings;
using WireMock.Admin.Requests; using WireMock.Admin.Requests;
using WireMock.Admin.Scenarios; using WireMock.Admin.Scenarios;
using WireMock.Admin.Settings; using WireMock.Admin.Settings;
using WireMock.Types;
namespace WireMock.Client namespace WireMock.Client;
/// <summary>
/// The RestEase interface which defines all admin commands.
/// </summary>
[BasePath("__admin")]
public interface IWireMockAdminApi
{ {
/// <summary> /// <summary>
/// The RestEase interface which defines all admin commands. /// Authentication header
/// </summary> /// </summary>
[BasePath("__admin")] [Header("Authorization")]
public interface IWireMockAdminApi AuthenticationHeaderValue Authorization { get; set; }
{
/// <summary>
/// Authentication header
/// </summary>
[Header("Authorization")]
AuthenticationHeaderValue Authorization { get; set; }
/// <summary> /// <summary>
/// Get the settings. /// Get the settings.
/// </summary> /// </summary>
/// <returns>SettingsModel</returns> /// <returns>SettingsModel</returns>
[Get("settings")] [Get("settings")]
Task<SettingsModel> GetSettingsAsync(); Task<SettingsModel> GetSettingsAsync();
/// <summary> /// <summary>
/// Update the settings. /// Update the settings.
/// </summary> /// </summary>
/// <param name="settings">SettingsModel</param> /// <param name="settings">SettingsModel</param>
[Put("settings")] /// <param name="cancellationToken">The optional cancellationToken.</param>
[Header("Content-Type", "application/json")] [Put("settings")]
Task<StatusModel> PutSettingsAsync([Body] SettingsModel settings); [Header("Content-Type", "application/json")]
Task<StatusModel> PutSettingsAsync([Body] SettingsModel settings, CancellationToken cancellationToken = default);
/// <summary> /// <summary>
/// Update the settings /// Update the settings
/// </summary> /// </summary>
/// <param name="settings">SettingsModel</param> /// <param name="settings">SettingsModel</param>
[Post("settings")] /// <param name="cancellationToken">The optional cancellationToken.</param>
[Header("Content-Type", "application/json")] [Post("settings")]
Task<StatusModel> PostSettingsAsync([Body] SettingsModel settings); [Header("Content-Type", "application/json")]
Task<StatusModel> PostSettingsAsync([Body] SettingsModel settings, CancellationToken cancellationToken = default);
/// <summary> /// <summary>
/// Get the mappings. /// Get the mappings.
/// </summary> /// </summary>
/// <returns>MappingModels</returns> /// <returns>MappingModels</returns>
[Get("mappings")] /// <param name="cancellationToken">The optional cancellationToken.</param>
Task<IList<MappingModel>> GetMappingsAsync(); [Get("mappings")]
Task<IList<MappingModel>> GetMappingsAsync(CancellationToken cancellationToken = default);
/// <summary> /// <summary>
/// Add a new mapping. /// Get the C# code from all mappings
/// </summary> /// </summary>
/// <param name="mapping">MappingModel</param> /// <returns>C# code</returns>
[Post("mappings")] /// <param name="mappingConverterType">The <see cref="MappingConverterType"/>, default is Server.</param>
[Header("Content-Type", "application/json")] /// <param name="cancellationToken">The optional cancellationToken.</param>
Task<StatusModel> PostMappingAsync([Body] MappingModel mapping); [Get("mappings/code")]
Task<string> GetMappingsCodeAsync([Query] MappingConverterType mappingConverterType = MappingConverterType.Server, CancellationToken cancellationToken = default);
/// <summary> /// <summary>
/// Add new mappings. /// Add a new mapping.
/// </summary> /// </summary>
/// <param name="mappings">MappingModels</param> /// <param name="mapping">MappingModel</param>
[Post("mappings")] /// <param name="cancellationToken">The optional cancellationToken.</param>
[Header("Content-Type", "application/json")] [Post("mappings")]
Task<StatusModel> PostMappingsAsync([Body] IList<MappingModel> mappings); [Header("Content-Type", "application/json")]
Task<StatusModel> PostMappingAsync([Body] MappingModel mapping, CancellationToken cancellationToken = default);
/// <summary> /// <summary>
/// Delete all mappings. /// Add new mappings.
/// </summary> /// </summary>
[Delete("mappings")] /// <param name="mappings">MappingModels</param>
Task<StatusModel> DeleteMappingsAsync(); /// <param name="cancellationToken">The optional cancellationToken.</param>
[Post("mappings")]
[Header("Content-Type", "application/json")]
Task<StatusModel> PostMappingsAsync([Body] IList<MappingModel> mappings, CancellationToken cancellationToken = default);
/// <summary> /// <summary>
/// Delete mappings according to GUIDs /// Delete all mappings.
/// </summary> /// </summary>
/// <param name="mappings">MappingModels</param> /// <param name="cancellationToken">The optional cancellationToken.</param>
[Delete("mappings")] [Delete("mappings")]
[Header("Content-Type", "application/json")] Task<StatusModel> DeleteMappingsAsync(CancellationToken cancellationToken = default);
Task<StatusModel> DeleteMappingsAsync([Body] IList<MappingModel> mappings);
/// <summary> /// <summary>
/// Delete (reset) all mappings. /// Delete mappings according to GUIDs
/// </summary> /// </summary>
/// <param name="reloadStaticMappings">A value indicating whether to reload the static mappings after the reset.</param> /// <param name="mappings">MappingModels</param>
[Post("mappings/reset")] /// <param name="cancellationToken">The optional cancellationToken.</param>
Task<StatusModel> ResetMappingsAsync(bool? reloadStaticMappings = false); [Delete("mappings")]
[Header("Content-Type", "application/json")]
Task<StatusModel> DeleteMappingsAsync([Body] IList<MappingModel> mappings, CancellationToken cancellationToken = default);
/// <summary> /// <summary>
/// Get a mapping based on the guid /// Delete (reset) all mappings.
/// </summary> /// </summary>
/// <param name="guid">The Guid</param> /// <param name="reloadStaticMappings">A value indicating whether to reload the static mappings after the reset.</param>
/// <returns>MappingModel</returns> /// <param name="cancellationToken">The optional cancellationToken.</param>
[Get("mappings/{guid}")] [Post("mappings/reset")]
Task<MappingModel> GetMappingAsync([Path] Guid guid); Task<StatusModel> ResetMappingsAsync(bool? reloadStaticMappings = false, CancellationToken cancellationToken = default);
/// <summary> /// <summary>
/// Update a mapping based on the guid /// Get a mapping based on the guid
/// </summary> /// </summary>
/// <param name="guid">The Guid</param> /// <param name="guid">The Guid</param>
/// <param name="mapping">MappingModel</param> /// <returns>MappingModel</returns>
[Put("mappings/{guid}")] /// <param name="cancellationToken">The optional cancellationToken.</param>
[Header("Content-Type", "application/json")] [Get("mappings/{guid}")]
Task<StatusModel> PutMappingAsync([Path] Guid guid, [Body] MappingModel mapping); Task<MappingModel> GetMappingAsync([Path] Guid guid, CancellationToken cancellationToken = default);
/// <summary> /// <summary>
/// Delete a mapping based on the guid /// Get the C# code from a mapping based on the guid
/// </summary> /// </summary>
/// <param name="guid">The Guid</param> /// <param name="guid">The Guid</param>
[Delete("mappings/{guid}")] /// <param name="mappingConverterType">The optional mappingConverterType (can be Server or Builder)</param>
Task<StatusModel> DeleteMappingAsync([Path] Guid guid); /// <returns>C# code</returns>
/// <param name="cancellationToken">The optional cancellationToken.</param>
[Get("mappings/code/{guid}")]
Task<string> GetMappingCodeAsync([Path] Guid guid, [Query] MappingConverterType mappingConverterType = MappingConverterType.Server, CancellationToken cancellationToken = default);
/// <summary> /// <summary>
/// Save the mappings /// Update a mapping based on the guid
/// </summary> /// </summary>
[Post("mappings/save")] /// <param name="guid">The Guid</param>
Task<StatusModel> SaveMappingAsync(); /// <param name="mapping">MappingModel</param>
/// <param name="cancellationToken">The optional cancellationToken.</param>
[Put("mappings/{guid}")]
[Header("Content-Type", "application/json")]
Task<StatusModel> PutMappingAsync([Path] Guid guid, [Body] MappingModel mapping, CancellationToken cancellationToken = default);
/// <summary> /// <summary>
/// Get the requests. /// Delete a mapping based on the guid
/// </summary> /// </summary>
/// <returns>LogRequestModels</returns> /// <param name="guid">The Guid</param>
[Get("requests")] /// <param name="cancellationToken">The optional cancellationToken.</param>
Task<IList<LogEntryModel>> GetRequestsAsync(); [Delete("mappings/{guid}")]
Task<StatusModel> DeleteMappingAsync([Path] Guid guid, CancellationToken cancellationToken = default);
/// <summary> /// <summary>
/// Delete all requests. /// Save the mappings
/// </summary> /// </summary>
[Delete("requests")] /// <param name="cancellationToken">The optional cancellationToken.</param>
Task<StatusModel> DeleteRequestsAsync(); [Post("mappings/save")]
Task<StatusModel> SaveMappingAsync(CancellationToken cancellationToken = default);
/// <summary> /// <summary>
/// Delete (reset) all requests. /// Get the requests.
/// </summary> /// </summary>
[Post("requests/reset")] /// <returns>LogRequestModels</returns>
Task<StatusModel> ResetRequestsAsync(); /// <param name="cancellationToken">The optional cancellationToken.</param>
[Get("requests")]
Task<IList<LogEntryModel>> GetRequestsAsync(CancellationToken cancellationToken = default);
/// <summary> /// <summary>
/// Get a request based on the guid /// Delete all requests.
/// </summary> /// </summary>
/// <param name="guid">The Guid</param> /// <param name="cancellationToken">The optional cancellationToken.</param>
/// <returns>MappingModel</returns> [Delete("requests")]
[Get("requests/{guid}")] Task<StatusModel> DeleteRequestsAsync(CancellationToken cancellationToken = default);
Task<LogEntryModel> GetRequestAsync([Path] Guid guid);
/// <summary> /// <summary>
/// Delete a request based on the guid /// Delete (reset) all requests.
/// </summary> /// </summary>
/// <param name="guid">The Guid</param> /// <param name="cancellationToken">The optional cancellationToken.</param>
[Delete("requests/{guid}")] [Post("requests/reset")]
Task<StatusModel> DeleteRequestAsync([Path] Guid guid); Task<StatusModel> ResetRequestsAsync(CancellationToken cancellationToken = default);
/// <summary> /// <summary>
/// Find a request based on the criteria /// Get a request based on the guid
/// </summary> /// </summary>
/// <param name="model">The RequestModel</param> /// <param name="guid">The Guid</param>
[Post("requests/find")] /// <returns>MappingModel</returns>
[Header("Content-Type", "application/json")] /// <param name="cancellationToken">The optional cancellationToken.</param>
Task<IList<LogEntryModel>> FindRequestsAsync([Body] RequestModel model); [Get("requests/{guid}")]
Task<LogEntryModel> GetRequestAsync([Path] Guid guid, CancellationToken cancellationToken = default);
/// <summary> /// <summary>
/// Get all scenarios /// Delete a request based on the guid
/// </summary> /// </summary>
[Get("scenarios")] /// <param name="guid">The Guid</param>
Task<IList<ScenarioStateModel>> GetScenariosAsync(); /// <param name="cancellationToken">The optional cancellationToken.</param>
[Delete("requests/{guid}")]
Task<StatusModel> DeleteRequestAsync([Path] Guid guid, CancellationToken cancellationToken = default);
/// <summary> /// <summary>
/// Delete (reset) all scenarios /// Find a request based on the criteria
/// </summary> /// </summary>
[Delete("scenarios")] /// <param name="model">The RequestModel</param>
Task<StatusModel> DeleteScenariosAsync(); /// <param name="cancellationToken">The optional cancellationToken.</param>
[Post("requests/find")]
[Header("Content-Type", "application/json")]
Task<IList<LogEntryModel>> FindRequestsAsync([Body] RequestModel model, CancellationToken cancellationToken = default);
/// <summary> /// <summary>
/// Delete (reset) all scenarios /// Get all scenarios
/// </summary> /// </summary>
[Post("scenarios")] /// <param name="cancellationToken">The optional cancellationToken.</param>
Task<StatusModel> ResetScenariosAsync(); [Get("scenarios")]
Task<IList<ScenarioStateModel>> GetScenariosAsync(CancellationToken cancellationToken = default);
/// <summary> /// <summary>
/// Delete (reset) a specific scenario /// Delete (reset) all scenarios
/// </summary> /// </summary>
[Delete("scenarios/{name}")] /// <param name="cancellationToken">The optional cancellationToken.</param>
[AllowAnyStatusCode] [Delete("scenarios")]
Task<StatusModel> DeleteScenarioAsync([Path] string name); Task<StatusModel> DeleteScenariosAsync(CancellationToken cancellationToken = default);
/// <summary> /// <summary>
/// Delete (reset) all scenarios /// Delete (reset) all scenarios
/// </summary> /// </summary>
[Post("scenarios/{name}/reset")] /// <param name="cancellationToken">The optional cancellationToken.</param>
[AllowAnyStatusCode] [Post("scenarios")]
Task<StatusModel> ResetScenarioAsync([Path] string name); Task<StatusModel> ResetScenariosAsync(CancellationToken cancellationToken = default);
/// <summary> /// <summary>
/// Create a new File /// Delete (reset) a specific scenario
/// </summary> /// </summary>
/// <param name="filename">The filename</param> /// <param name="name">Scenario name.</param>
/// <param name="body">The body</param> /// <param name="cancellationToken">The optional cancellationToken.</param>
[Post("files/{filename}")] [Delete("scenarios/{name}")]
Task<StatusModel> PostFileAsync([Path] string filename, [Body] string body); [AllowAnyStatusCode]
Task<StatusModel> DeleteScenarioAsync([Path] string name, CancellationToken cancellationToken = default);
/// <summary> /// <summary>
/// Update an existing File /// Delete (reset) all scenarios
/// </summary> /// </summary>
/// <param name="filename">The filename</param> /// <param name="name">Scenario name.</param>
/// <param name="body">The body</param> /// <param name="cancellationToken">The optional cancellationToken.</param>
[Put("files/{filename}")] [Post("scenarios/{name}/reset")]
Task<StatusModel> PutFileAsync([Path] string filename, [Body] string body); [AllowAnyStatusCode]
Task<StatusModel> ResetScenarioAsync([Path] string name, CancellationToken cancellationToken = default);
/// <summary> /// <summary>
/// Get the content of an existing File /// Create a new File
/// </summary> /// </summary>
/// <param name="filename">The filename</param> /// <param name="filename">The filename</param>
[Get("files/{filename}")] /// <param name="body">The body</param>
Task<string> GetFileAsync([Path] string filename); /// <param name="cancellationToken">The optional cancellationToken.</param>
[Post("files/{filename}")]
Task<StatusModel> PostFileAsync([Path] string filename, [Body] string body, CancellationToken cancellationToken = default);
/// <summary> /// <summary>
/// Delete an existing File /// Update an existing File
/// </summary> /// </summary>
/// <param name="filename">The filename</param> /// <param name="filename">The filename</param>
[Delete("files/{filename}")] /// <param name="body">The body</param>
Task<StatusModel> DeleteFileAsync([Path] string filename); /// <param name="cancellationToken">The optional cancellationToken.</param>
[Put("files/{filename}")]
Task<StatusModel> PutFileAsync([Path] string filename, [Body] string body, CancellationToken cancellationToken = default);
/// <summary> /// <summary>
/// Check if a file exists /// Get the content of an existing File
/// </summary> /// </summary>
/// <param name="filename">The filename</param> /// <param name="filename">The filename</param>
[Head("files/{filename}")] /// <param name="cancellationToken">The optional cancellationToken.</param>
Task FileExistsAsync([Path] string filename); [Get("files/{filename}")]
} Task<string> GetFileAsync([Path] string filename, CancellationToken cancellationToken = default);
/// <summary>
/// Delete an existing File
/// </summary>
/// <param name="filename">The filename</param>
/// <param name="cancellationToken">The optional cancellationToken.</param>
[Delete("files/{filename}")]
Task<StatusModel> DeleteFileAsync([Path] string filename, CancellationToken cancellationToken = default);
/// <summary>
/// Check if a file exists
/// </summary>
/// <param name="filename">The filename</param>
/// <param name="cancellationToken">The optional cancellationToken.</param>
[Head("files/{filename}")]
Task FileExistsAsync([Path] string filename, CancellationToken cancellationToken = default);
/// <summary>
/// Convert an OpenApi / RAML document to mappings.
/// </summary>
/// <param name="text">The OpenApi or RAML document as text.</param>
/// <param name="cancellationToken">The optional cancellationToken.</param>
[Post("openapi/convert")]
Task<IReadOnlyList<MappingModel>> OpenApiConvertAsync([Body] string text, CancellationToken cancellationToken = default);
/// <summary>
/// Convert an OpenApi / RAML document to mappings and save these.
/// </summary>
/// <param name="text">The OpenApi or RAML document as text.</param>
/// <param name="cancellationToken">The optional cancellationToken.</param>
[Post("openapi/save")]
Task<StatusModel> OpenApiSaveAsync([Body] string text, CancellationToken cancellationToken = default);
} }
@@ -30,8 +30,10 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="JsonConverter.Newtonsoft.Json" Version="0.3.0" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" /> <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
<PackageReference Include="RestEase" Version="1.5.7" /> <PackageReference Include="RestEase" Version="1.5.7" />
<PackageReference Include="Stef.Validation" Version="0.1.1" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
+17
View File
@@ -0,0 +1,17 @@
{
"help": "https://go.microsoft.com/fwlink/?linkid=866610",
"root": true,
"dependentFileProviders": {
"add": {
"addedExtension": {},
"pathSegment": {
"add": {
".*": [
".cs"
]
}
}
}
}
}
@@ -0,0 +1,15 @@
#if NET451 || NET452 || NET46 || NET451 || NET461 || NETSTANDARD1_3 || NETSTANDARD2_0
using System.Text.RegularExpressions;
// ReSharper disable once CheckNamespace
namespace System;
internal static class StringExtensions
{
public static string Replace(this string text, string oldValue, string newValue, StringComparison stringComparison)
{
var options = stringComparison == StringComparison.OrdinalIgnoreCase ? RegexOptions.IgnoreCase : RegexOptions.None;
return Regex.Replace(text, oldValue, newValue, options);
}
}
#endif
@@ -7,4 +7,5 @@ internal static class WireMockConstants
public const int ProxyPriority = -2_000_000; public const int ProxyPriority = -2_000_000;
public const string ContentTypeJson = "application/json"; public const string ContentTypeJson = "application/json";
public const string ContentTypeTextPlain = "text/plain";
} }
@@ -1,35 +1,34 @@
using System; using System;
namespace WireMock.Exceptions namespace WireMock.Exceptions;
/// <summary>
/// WireMockException
/// </summary>
/// <seealso cref="Exception" />
public class WireMockException : Exception
{ {
/// <summary> /// <summary>
/// WireMockException /// Initializes a new instance of the <see cref="WireMockException"/> class.
/// </summary> /// </summary>
/// <seealso cref="Exception" /> public WireMockException()
public class WireMockException : Exception
{ {
/// <summary> }
/// Initializes a new instance of the <see cref="WireMockException"/> class.
/// </summary>
public WireMockException()
{
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="WireMockException"/> class. /// Initializes a new instance of the <see cref="WireMockException"/> class.
/// </summary> /// </summary>
/// <param name="message">The message that describes the error.</param> /// <param name="message">The message that describes the error.</param>
public WireMockException(string message) : base(message) public WireMockException(string message) : base(message)
{ {
} }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="WireMockException"/> class. /// Initializes a new instance of the <see cref="WireMockException"/> class.
/// </summary> /// </summary>
/// <param name="message">The message.</param> /// <param name="message">The message.</param>
/// <param name="inner">The inner.</param> /// <param name="inner">The inner.</param>
public WireMockException(string message, Exception inner) : base(message, inner) public WireMockException(string message, Exception inner) : base(message, inner)
{ {
}
} }
} }
@@ -0,0 +1,20 @@
using System;
using System.Reflection;
namespace WireMock.Extensions;
internal static class EnumExtensions
{
public static string GetFullyQualifiedEnumValue<T>(this T enumValue)
where T : struct, IConvertible
{
var type = typeof(T);
if (!type.GetTypeInfo().IsEnum)
{
throw new ArgumentException("T must be an enum");
}
return $"{type.Namespace}.{type.Name}.{enumValue}";
}
}
@@ -3,179 +3,178 @@ using System.IO;
using WireMock.Util; using WireMock.Util;
using Stef.Validation; using Stef.Validation;
namespace WireMock.Handlers namespace WireMock.Handlers;
/// <summary>
/// Default implementation for a handler to interact with the local file system to read and write static mapping files.
/// </summary>
public class LocalFileSystemHandler : IFileSystemHandler
{ {
private static readonly string AdminMappingsFolder = Path.Combine("__admin", "mappings");
private static readonly string UnmatchedRequestsFolder = Path.Combine("requests", "unmatched");
private readonly string _rootFolder;
/// <summary> /// <summary>
/// Default implementation for a handler to interact with the local file system to read and write static mapping files. /// Initializes a new instance of the <see cref="LocalFileSystemHandler"/> class.
/// </summary> /// </summary>
public class LocalFileSystemHandler : IFileSystemHandler public LocalFileSystemHandler() : this(Directory.GetCurrentDirectory())
{ {
private static readonly string AdminMappingsFolder = Path.Combine("__admin", "mappings"); }
private static readonly string UnmatchedRequestsFolder = Path.Combine("requests", "unmatched");
private readonly string _rootFolder; /// <summary>
/// Initializes a new instance of the <see cref="LocalFileSystemHandler"/> class.
/// </summary>
/// <param name="rootFolder">The root folder.</param>
public LocalFileSystemHandler(string rootFolder)
{
_rootFolder = rootFolder;
}
/// <summary> /// <inheritdoc cref="IFileSystemHandler.FolderExists"/>
/// Initializes a new instance of the <see cref="LocalFileSystemHandler"/> class. public virtual bool FolderExists(string path)
/// </summary> {
public LocalFileSystemHandler() : this(Directory.GetCurrentDirectory()) Guard.NotNullOrEmpty(path);
return Directory.Exists(path);
}
/// <inheritdoc cref="IFileSystemHandler.CreateFolder"/>
public virtual void CreateFolder(string path)
{
Guard.NotNullOrEmpty(path);
Directory.CreateDirectory(path);
}
/// <inheritdoc cref="IFileSystemHandler.EnumerateFiles"/>
public virtual IEnumerable<string> EnumerateFiles(string path, bool includeSubdirectories)
{
Guard.NotNullOrEmpty(path);
return includeSubdirectories ? Directory.EnumerateFiles(path, "*", SearchOption.AllDirectories) : Directory.EnumerateFiles(path);
}
/// <inheritdoc cref="IFileSystemHandler.GetMappingFolder"/>
public virtual string GetMappingFolder()
{
return Path.Combine(_rootFolder, AdminMappingsFolder);
}
/// <inheritdoc cref="IFileSystemHandler.ReadMappingFile"/>
public virtual string ReadMappingFile(string path)
{
Guard.NotNullOrEmpty(path);
return File.ReadAllText(path);
}
/// <inheritdoc cref="IFileSystemHandler.WriteMappingFile(string, string)"/>
public virtual void WriteMappingFile(string path, string text)
{
Guard.NotNullOrEmpty(path, nameof(path));
Guard.NotNull(text, nameof(text));
File.WriteAllText(path, text);
}
/// <inheritdoc cref="IFileSystemHandler.ReadResponseBodyAsFile"/>
public virtual byte[] ReadResponseBodyAsFile(string path)
{
Guard.NotNullOrEmpty(path);
path = PathUtils.CleanPath(path);
// If the file exists at the given path relative to the MappingsFolder, then return that.
// Else the path will just be as-is.
return File.ReadAllBytes(File.Exists(PathUtils.Combine(GetMappingFolder(), path)) ? PathUtils.Combine(GetMappingFolder(), path) : path);
}
/// <inheritdoc cref="IFileSystemHandler.ReadResponseBodyAsString"/>
public virtual string ReadResponseBodyAsString(string path)
{
Guard.NotNullOrEmpty(path);
path = PathUtils.CleanPath(path);
// In case the path is a filename, the path will be adjusted to the MappingFolder.
// Else the path will just be as-is.
return File.ReadAllText(File.Exists(PathUtils.Combine(GetMappingFolder(), path)) ? PathUtils.Combine(GetMappingFolder(), path) : path);
}
/// <inheritdoc cref="IFileSystemHandler.FileExists"/>
public virtual bool FileExists(string filename)
{
Guard.NotNullOrEmpty(filename);
return File.Exists(AdjustPathForMappingFolder(filename));
}
/// <inheritdoc />
public virtual void WriteFile(string filename, byte[] bytes)
{
Guard.NotNullOrEmpty(filename);
Guard.NotNull(bytes);
File.WriteAllBytes(AdjustPathForMappingFolder(filename), bytes);
}
/// <inheritdoc />
public virtual void WriteFile(string folder, string filename, byte[] bytes)
{
Guard.NotNullOrEmpty(folder);
Guard.NotNullOrEmpty(filename);
Guard.NotNull(bytes);
File.WriteAllBytes(PathUtils.Combine(folder, filename), bytes);
}
/// <inheritdoc cref="IFileSystemHandler.DeleteFile"/>
public virtual void DeleteFile(string filename)
{
Guard.NotNullOrEmpty(filename);
File.Delete(AdjustPathForMappingFolder(filename));
}
/// <inheritdoc cref="IFileSystemHandler.ReadFile"/>
public virtual byte[] ReadFile(string filename)
{
Guard.NotNullOrEmpty(filename);
return File.ReadAllBytes(AdjustPathForMappingFolder(filename));
}
/// <inheritdoc cref="IFileSystemHandler.ReadFileAsString"/>
public virtual string ReadFileAsString(string filename)
{
return File.ReadAllText(AdjustPathForMappingFolder(Guard.NotNullOrEmpty(filename, nameof(filename))));
}
/// <inheritdoc cref="IFileSystemHandler.GetUnmatchedRequestsFolder"/>
public virtual string GetUnmatchedRequestsFolder()
{
return Path.Combine(_rootFolder, UnmatchedRequestsFolder);
}
/// <inheritdoc cref="IFileSystemHandler.WriteUnmatchedRequest"/>
public virtual void WriteUnmatchedRequest(string filename, string text)
{
Guard.NotNullOrEmpty(filename);
Guard.NotNull(text);
var folder = GetUnmatchedRequestsFolder();
if (!FolderExists(folder))
{ {
CreateFolder(folder);
} }
/// <summary> File.WriteAllText(Path.Combine(folder, filename), text);
/// Initializes a new instance of the <see cref="LocalFileSystemHandler"/> class. }
/// </summary>
/// <param name="rootFolder">The root folder.</param>
public LocalFileSystemHandler(string rootFolder)
{
_rootFolder = rootFolder;
}
/// <inheritdoc cref="IFileSystemHandler.FolderExists"/> /// <summary>
public virtual bool FolderExists(string path) /// Adjusts the path to the MappingFolder.
{ /// </summary>
Guard.NotNullOrEmpty(path, nameof(path)); /// <param name="filename">The path.</param>
/// <returns>Adjusted path</returns>
return Directory.Exists(path); private string AdjustPathForMappingFolder(string filename)
} {
return Path.Combine(GetMappingFolder(), filename);
/// <inheritdoc cref="IFileSystemHandler.CreateFolder"/>
public virtual void CreateFolder(string path)
{
Guard.NotNullOrEmpty(path, nameof(path));
Directory.CreateDirectory(path);
}
/// <inheritdoc cref="IFileSystemHandler.EnumerateFiles"/>
public virtual IEnumerable<string> EnumerateFiles(string path, bool includeSubdirectories)
{
Guard.NotNullOrEmpty(path, nameof(path));
return includeSubdirectories ? Directory.EnumerateFiles(path, "*", SearchOption.AllDirectories) : Directory.EnumerateFiles(path);
}
/// <inheritdoc cref="IFileSystemHandler.GetMappingFolder"/>
public virtual string GetMappingFolder()
{
return Path.Combine(_rootFolder, AdminMappingsFolder);
}
/// <inheritdoc cref="IFileSystemHandler.ReadMappingFile"/>
public virtual string ReadMappingFile(string path)
{
Guard.NotNullOrEmpty(path, nameof(path));
return File.ReadAllText(path);
}
/// <inheritdoc cref="IFileSystemHandler.WriteMappingFile(string, string)"/>
public virtual void WriteMappingFile(string path, string text)
{
Guard.NotNullOrEmpty(path, nameof(path));
Guard.NotNull(text, nameof(text));
File.WriteAllText(path, text);
}
/// <inheritdoc cref="IFileSystemHandler.ReadResponseBodyAsFile"/>
public virtual byte[] ReadResponseBodyAsFile(string path)
{
Guard.NotNullOrEmpty(path, nameof(path));
path = PathUtils.CleanPath(path);
// If the file exists at the given path relative to the MappingsFolder, then return that.
// Else the path will just be as-is.
return File.ReadAllBytes(File.Exists(PathUtils.Combine(GetMappingFolder(), path)) ? PathUtils.Combine(GetMappingFolder(), path) : path);
}
/// <inheritdoc cref="IFileSystemHandler.ReadResponseBodyAsString"/>
public virtual string ReadResponseBodyAsString(string path)
{
Guard.NotNullOrEmpty(path, nameof(path));
path = PathUtils.CleanPath(path);
// In case the path is a filename, the path will be adjusted to the MappingFolder.
// Else the path will just be as-is.
return File.ReadAllText(File.Exists(PathUtils.Combine(GetMappingFolder(), path)) ? PathUtils.Combine(GetMappingFolder(), path) : path);
}
/// <inheritdoc cref="IFileSystemHandler.FileExists"/>
public virtual bool FileExists(string filename)
{
Guard.NotNullOrEmpty(filename, nameof(filename));
return File.Exists(AdjustPathForMappingFolder(filename));
}
/// <inheritdoc />
public virtual void WriteFile(string filename, byte[] bytes)
{
Guard.NotNullOrEmpty(filename, nameof(filename));
Guard.NotNull(bytes, nameof(bytes));
File.WriteAllBytes(AdjustPathForMappingFolder(filename), bytes);
}
/// <inheritdoc />
public virtual void WriteFile(string folder, string filename, byte[] bytes)
{
Guard.NotNullOrEmpty(folder);
Guard.NotNullOrEmpty(filename);
Guard.NotNull(bytes);
File.WriteAllBytes(PathUtils.Combine(folder, filename), bytes);
}
/// <inheritdoc cref="IFileSystemHandler.DeleteFile"/>
public virtual void DeleteFile(string filename)
{
Guard.NotNullOrEmpty(filename, nameof(filename));
File.Delete(AdjustPathForMappingFolder(filename));
}
/// <inheritdoc cref="IFileSystemHandler.ReadFile"/>
public virtual byte[] ReadFile(string filename)
{
Guard.NotNullOrEmpty(filename, nameof(filename));
return File.ReadAllBytes(AdjustPathForMappingFolder(filename));
}
/// <inheritdoc cref="IFileSystemHandler.ReadFileAsString"/>
public virtual string ReadFileAsString(string filename)
{
return File.ReadAllText(AdjustPathForMappingFolder(Guard.NotNullOrEmpty(filename, nameof(filename))));
}
/// <inheritdoc cref="IFileSystemHandler.GetUnmatchedRequestsFolder"/>
public virtual string GetUnmatchedRequestsFolder()
{
return Path.Combine(_rootFolder, UnmatchedRequestsFolder);
}
/// <inheritdoc cref="IFileSystemHandler.WriteUnmatchedRequest"/>
public virtual void WriteUnmatchedRequest(string filename, string text)
{
Guard.NotNullOrEmpty(filename, nameof(filename));
Guard.NotNull(text, nameof(text));
var folder = GetUnmatchedRequestsFolder();
if (!FolderExists(folder))
{
CreateFolder(folder);
}
File.WriteAllText(Path.Combine(folder, filename), text);
}
/// <summary>
/// Adjusts the path to the MappingFolder.
/// </summary>
/// <param name="filename">The path.</param>
/// <returns>Adjusted path</returns>
private string AdjustPathForMappingFolder(string filename)
{
return Path.Combine(GetMappingFolder(), filename);
}
} }
} }
+6 -1
View File
@@ -35,9 +35,14 @@ internal static class HttpClientBuilder
{ {
handler.ClientCertificateOptions = ClientCertificateOption.Manual; handler.ClientCertificateOptions = ClientCertificateOption.Manual;
var x509Certificate2 = CertificateLoader.LoadCertificate(settings.ClientX509Certificate2ThumbprintOrSubjectName); var x509Certificate2 = CertificateLoader.LoadCertificate(settings.ClientX509Certificate2ThumbprintOrSubjectName!);
handler.ClientCertificates.Add(x509Certificate2); handler.ClientCertificates.Add(x509Certificate2);
} }
else if (settings.Certificate != null)
{
handler.ClientCertificateOptions = ClientCertificateOption.Manual;
handler.ClientCertificates.Add(settings.Certificate);
}
handler.AllowAutoRedirect = settings.AllowAutoRedirect == true; handler.AllowAutoRedirect = settings.AllowAutoRedirect == true;
@@ -28,7 +28,7 @@ internal static class HttpRequestMessageHelper
switch (requestMessage.BodyData?.DetectedBodyType) switch (requestMessage.BodyData?.DetectedBodyType)
{ {
case BodyType.Bytes: case BodyType.Bytes:
httpRequestMessage.Content = ByteArrayContentHelper.Create(requestMessage.BodyData.BodyAsBytes, contentType); httpRequestMessage.Content = ByteArrayContentHelper.Create(requestMessage.BodyData.BodyAsBytes!, contentType);
break; break;
case BodyType.Json: case BodyType.Json:
@@ -36,7 +36,8 @@ internal static class HttpRequestMessageHelper
break; break;
case BodyType.String: case BodyType.String:
httpRequestMessage.Content = StringContentHelper.Create(requestMessage.BodyData.BodyAsString, contentType); case BodyType.FormUrlEncoded:
httpRequestMessage.Content = StringContentHelper.Create(requestMessage.BodyData.BodyAsString!, contentType);
break; break;
} }
@@ -15,7 +15,8 @@ internal static class HttpResponseMessageHelper
Uri requiredUri, Uri requiredUri,
Uri originalUri, Uri originalUri,
bool deserializeJson, bool deserializeJson,
bool decompressGzipAndDeflate) bool decompressGzipAndDeflate,
bool deserializeFormUrlEncoded)
{ {
var responseMessage = new ResponseMessage { StatusCode = (int)httpResponseMessage.StatusCode }; var responseMessage = new ResponseMessage { StatusCode = (int)httpResponseMessage.StatusCode };
@@ -44,7 +45,8 @@ internal static class HttpResponseMessageHelper
ContentType = contentTypeHeader?.FirstOrDefault(), ContentType = contentTypeHeader?.FirstOrDefault(),
DeserializeJson = deserializeJson, DeserializeJson = deserializeJson,
ContentEncoding = contentEncodingHeader?.FirstOrDefault(), ContentEncoding = contentEncodingHeader?.FirstOrDefault(),
DecompressGZipAndDeflate = decompressGzipAndDeflate DecompressGZipAndDeflate = decompressGzipAndDeflate,
DeserializeFormUrlEncoded = deserializeFormUrlEncoded
}; };
responseMessage.BodyData = await BodyParser.ParseAsync(bodyParserSettings).ConfigureAwait(false); responseMessage.BodyData = await BodyParser.ParseAsync(bodyParserSettings).ConfigureAwait(false);
} }
@@ -55,7 +57,7 @@ internal static class HttpResponseMessageHelper
// If Location header contains absolute redirect URL, and base URL is one that we proxy to, // If Location header contains absolute redirect URL, and base URL is one that we proxy to,
// we need to replace it to original one. // we need to replace it to original one.
if (string.Equals(header.Key, HttpKnownHeaderNames.Location, StringComparison.OrdinalIgnoreCase) if (string.Equals(header.Key, HttpKnownHeaderNames.Location, StringComparison.OrdinalIgnoreCase)
&& Uri.TryCreate(header.Value.First(), UriKind.Absolute, out Uri absoluteLocationUri) && Uri.TryCreate(header.Value.First(), UriKind.Absolute, out var absoluteLocationUri)
&& string.Equals(absoluteLocationUri.Host, requiredUri.Host, StringComparison.OrdinalIgnoreCase)) && string.Equals(absoluteLocationUri.Host, requiredUri.Host, StringComparison.OrdinalIgnoreCase))
{ {
var replacedLocationUri = new Uri(originalUri, absoluteLocationUri.PathAndQuery); var replacedLocationUri = new Uri(originalUri, absoluteLocationUri.PathAndQuery);
+3 -3
View File
@@ -51,14 +51,14 @@ internal class WebhookSender
switch (webhookRequest.TransformerType) switch (webhookRequest.TransformerType)
{ {
case TransformerType.Handlebars: case TransformerType.Handlebars:
var factoryHandlebars = new HandlebarsContextFactory(_settings.FileSystemHandler, _settings.HandlebarsRegistrationCallback); var factoryHandlebars = new HandlebarsContextFactory(_settings);
transformer = new Transformer(factoryHandlebars); transformer = new Transformer(_settings, factoryHandlebars);
break; break;
case TransformerType.Scriban: case TransformerType.Scriban:
case TransformerType.ScribanDotLiquid: case TransformerType.ScribanDotLiquid:
var factoryDotLiquid = new ScribanContextFactory(_settings.FileSystemHandler, webhookRequest.TransformerType); var factoryDotLiquid = new ScribanContextFactory(_settings.FileSystemHandler, webhookRequest.TransformerType);
transformer = new Transformer(factoryDotLiquid); transformer = new Transformer(_settings, factoryDotLiquid);
break; break;
default: default:
+20 -1
View File
@@ -17,6 +17,11 @@ public interface IMapping
/// </summary> /// </summary>
Guid Guid { get; } Guid Guid { get; }
/// <summary>
/// The datetime when this mapping was created or updated.
/// </summary>
public DateTime? UpdatedAt { get; set; }
/// <summary> /// <summary>
/// Gets the TimeSettings (Start, End and TTL). /// Gets the TimeSettings (Start, End and TTL).
/// </summary> /// </summary>
@@ -115,7 +120,21 @@ public interface IMapping
/// <summary> /// <summary>
/// Use Fire and Forget for the defined webhook(s). [Optional] /// Use Fire and Forget for the defined webhook(s). [Optional]
/// </summary> /// </summary>
public bool? UseWebhooksFireAndForget { get; set; } bool? UseWebhooksFireAndForget { get; }
/// <summary>
/// Data Object which can be used when WithTransformer is used.
/// e.g. lookup an path in this object using
/// <example>
/// lookup data "1"
/// </example>
/// </summary>
object? Data { get; }
/// <summary>
/// The probability when this request should be matched. Value is between 0 and 1. [Optional]
/// </summary>
double? Probability { get; }
/// <summary> /// <summary>
/// ProvideResponseAsync /// ProvideResponseAsync
+60
View File
@@ -0,0 +1,60 @@
using System;
using WireMock.Admin.Mappings;
using WireMock.Matchers.Request;
using WireMock.Server;
using WireMock.Types;
namespace WireMock;
/// <summary>
/// IMappingBuilder
/// </summary>
public interface IMappingBuilder
{
/// <summary>
/// The given.
/// </summary>
/// <param name="requestMatcher">The request matcher.</param>
/// <param name="saveToFile">Optional boolean to indicate if this mapping should be saved as static mapping file.</param>
/// <returns>The <see cref="IRespondWithAProvider"/>.</returns>
IRespondWithAProvider Given(IRequestMatcher requestMatcher, bool saveToFile = false);
/// <summary>
/// Gets all the mappings as a list.
/// </summary>
/// <returns>A list from <see cref="MappingModel"/>s.</returns>
MappingModel[] GetMappings();
/// <summary>
/// Convert all mappings to JSON.
/// </summary>
/// <returns>JSON</returns>
string ToJson();
/// <summary>
/// Save all mappings as a single JSON to a file.
/// </summary>
/// <param name="path">The file to write to.</param>
void SaveMappingsToFile(string path);
/// <summary>
/// Save all mappings as multiple JSON files (each file is 1 mapping).
/// </summary>
/// <param name="folder">The folder to write the files to.</param>
void SaveMappingsToFolder(string folder);
/// <summary>
/// Get the C# code for a mapping.
/// </summary>
/// <param name="guid">The Mapping Guid.</param>
/// <param name="converterType">The <see cref="MappingConverterType"/></param>
/// <returns>C# code (null in case the mapping is not found)</returns>
string? ToCSharpCode(Guid guid, MappingConverterType converterType);
/// <summary>
/// Get the C# code for all mappings.
/// </summary>
/// <param name="converterType">The <see cref="MappingConverterType"/></param>
/// <returns>C# code</returns>
public string ToCSharpCode(MappingConverterType converterType);
}
@@ -0,0 +1,10 @@
// Copied from https://github.com/Handlebars-Net/Handlebars.Net.Helpers/blob/master/src/Handlebars.Net.Helpers.DynamicLinq
namespace WireMock.Json;
internal class DynamicJsonClassOptions
{
public IntegerBehavior IntegerConvertBehavior { get; set; } = IntegerBehavior.UseLong;
public FloatBehavior FloatConvertBehavior { get; set; } = FloatBehavior.UseDouble;
}
@@ -0,0 +1,15 @@
// Copied from https://github.com/Handlebars-Net/Handlebars.Net.Helpers/blob/master/src/Handlebars.Net.Helpers.DynamicLinq
using System.Linq.Dynamic.Core;
namespace WireMock.Json;
internal class DynamicPropertyWithValue : DynamicProperty
{
public object? Value { get; }
public DynamicPropertyWithValue(string name, object? value) : base(name, value?.GetType() ?? typeof(object))
{
Value = value;
}
}
+24
View File
@@ -0,0 +1,24 @@
// Copied from https://github.com/Handlebars-Net/Handlebars.Net.Helpers/blob/master/src/Handlebars.Net.Helpers.DynamicLinq
namespace WireMock.Json;
/// <summary>
/// Enum to define how to convert an Float in the Json Object.
/// </summary>
internal enum FloatBehavior
{
/// <summary>
/// Convert all Float types in the Json Object to a double. (default)
/// </summary>
UseDouble = 0,
/// <summary>
/// Convert all Float types in the Json Object to a float (unless overflow).
/// </summary>
UseFloat = 1,
/// <summary>
/// Convert all Float types in the Json Object to a decimal (unless overflow).
/// </summary>
UseDecimal = 2
}
+20
View File
@@ -0,0 +1,20 @@
// Copied from https://github.com/Handlebars-Net/Handlebars.Net.Helpers/blob/master/src/Handlebars.Net.Helpers.DynamicLinq
namespace WireMock.Json;
/// <summary>
/// Enum to define how to convert an Integer in the Json Object.
/// </summary>
internal enum IntegerBehavior
{
/// <summary>
/// Convert all Integer types in the Json Object to a int (unless overflow).
/// (default)
/// </summary>
UseInt = 0,
/// <summary>
/// Convert all Integer types in the Json Object to a long.
/// </summary>
UseLong = 1
}
+202
View File
@@ -0,0 +1,202 @@
// Copied from https://github.com/Handlebars-Net/Handlebars.Net.Helpers/blob/master/src/Handlebars.Net.Helpers.DynamicLinq
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Reflection;
using Newtonsoft.Json.Linq;
namespace WireMock.Json;
internal static class JObjectExtensions
{
private class JTokenResolvers : Dictionary<JTokenType, Func<JToken, DynamicJsonClassOptions?, object?>>
{
}
private static readonly JTokenResolvers Resolvers = new()
{
{ JTokenType.Array, ConvertJTokenArray },
{ JTokenType.Boolean, (jToken, _) => jToken.Value<bool>() },
{ JTokenType.Bytes, (jToken, _) => jToken.Value<byte[]>() },
{ JTokenType.Date, (jToken, _) => jToken.Value<DateTime>() },
{ JTokenType.Float, ConvertJTokenFloat },
{ JTokenType.Guid, (jToken, _) => jToken.Value<Guid>() },
{ JTokenType.Integer, ConvertJTokenInteger },
{ JTokenType.None, (_, _) => null },
{ JTokenType.Null, (_, _) => null },
{ JTokenType.Object, ConvertJObject },
{ JTokenType.Property, ConvertJTokenProperty },
{ JTokenType.String, (jToken, _) => jToken.Value<string>() },
{ JTokenType.TimeSpan, (jToken, _) => jToken.Value<TimeSpan>() },
{ JTokenType.Undefined, (_, _) => null },
{ JTokenType.Uri, (o, _) => o.Value<Uri>() },
};
internal static DynamicClass? ToDynamicJsonClass(this JObject? src, DynamicJsonClassOptions? options = null)
{
if (src == null)
{
return null;
}
var dynamicPropertyWithValues = new List<DynamicPropertyWithValue>();
foreach (var prop in src.Properties())
{
var value = Resolvers[prop.Type](prop.Value, options);
if (value != null)
{
dynamicPropertyWithValues.Add(new DynamicPropertyWithValue(prop.Name, value));
}
}
return CreateInstance(dynamicPropertyWithValues);
}
internal static IEnumerable ToDynamicClassArray(this JArray? src, DynamicJsonClassOptions? options = null)
{
if (src == null)
{
return EmptyArray<object?>.Value;
}
return ConvertJTokenArray(src, options);
}
private static object? ConvertJObject(JToken arg, DynamicJsonClassOptions? options = null)
{
if (arg is JObject asJObject)
{
return asJObject.ToDynamicJsonClass(options);
}
return GetResolverFor(arg)(arg, options);
}
private static object PassThrough(JToken arg, DynamicJsonClassOptions? options)
{
return arg;
}
private static Func<JToken, DynamicJsonClassOptions?, object?> GetResolverFor(JToken arg)
{
return Resolvers.TryGetValue(arg.Type, out var result) ? result : PassThrough;
}
private static object ConvertJTokenFloat(JToken arg, DynamicJsonClassOptions? options = null)
{
if (arg.Type != JTokenType.Float)
{
throw new InvalidOperationException($"Unable to convert {nameof(JToken)} of type: {arg.Type} to double or float.");
}
if (options?.FloatConvertBehavior == FloatBehavior.UseFloat)
{
try
{
return arg.Value<float>();
}
catch
{
return arg.Value<double>();
}
}
if (options?.FloatConvertBehavior == FloatBehavior.UseDecimal)
{
try
{
return arg.Value<decimal>();
}
catch
{
return arg.Value<double>();
}
}
return arg.Value<double>();
}
private static object ConvertJTokenInteger(JToken arg, DynamicJsonClassOptions? options = null)
{
if (arg.Type != JTokenType.Integer)
{
throw new InvalidOperationException($"Unable to convert {nameof(JToken)} of type: {arg.Type} to long or int.");
}
var longValue = arg.Value<long>();
if (options is null || options.IntegerConvertBehavior == IntegerBehavior.UseInt)
{
if (longValue is >= int.MinValue and <= int.MaxValue)
{
return Convert.ToInt32(longValue);
}
}
return longValue;
}
private static object? ConvertJTokenProperty(JToken arg, DynamicJsonClassOptions? options = null)
{
var resolver = GetResolverFor(arg);
if (resolver is null)
{
throw new InvalidOperationException($"Unable to handle {nameof(JToken)} of type: {arg.Type}.");
}
return resolver(arg, options);
}
private static IEnumerable ConvertJTokenArray(JToken arg, DynamicJsonClassOptions? options = null)
{
if (arg is not JArray array)
{
throw new InvalidOperationException($"Unable to convert {nameof(JToken)} of type: {arg.Type} to {nameof(JArray)}.");
}
var result = new List<object?>();
foreach (var item in array)
{
result.Add(ConvertJObject(item));
}
var distinctType = FindSameTypeOf(result);
return distinctType == null ? result.ToArray() : ConvertToTypedArray(result, distinctType);
}
private static Type? FindSameTypeOf(IEnumerable<object?> src)
{
var types = src.Select(o => o?.GetType()).Distinct().OfType<Type>().ToArray();
return types.Length == 1 ? types[0] : null;
}
private static IEnumerable ConvertToTypedArray(IEnumerable<object?> src, Type newType)
{
var method = ConvertToTypedArrayGenericMethod.MakeGenericMethod(newType);
return (IEnumerable)method.Invoke(null, new object[] { src })!;
}
private static readonly MethodInfo ConvertToTypedArrayGenericMethod = typeof(JObjectExtensions).GetMethod(nameof(ConvertToTypedArrayGeneric), BindingFlags.NonPublic | BindingFlags.Static)!;
private static T[] ConvertToTypedArrayGeneric<T>(IEnumerable<object> src)
{
return src.Cast<T>().ToArray();
}
public static DynamicClass CreateInstance(IList<DynamicPropertyWithValue> dynamicPropertiesWithValue, bool createParameterCtor = true)
{
var type = DynamicClassFactory.CreateType(dynamicPropertiesWithValue.Cast<DynamicProperty>().ToArray(), createParameterCtor);
var dynamicClass = (DynamicClass)Activator.CreateInstance(type);
foreach (var dynamicPropertyWithValue in dynamicPropertiesWithValue.Where(p => p.Value != null))
{
dynamicClass.SetDynamicPropertyValue(dynamicPropertyWithValue.Name, dynamicPropertyWithValue.Value!);
}
return dynamicClass;
}
}
+20 -2
View File
@@ -15,6 +15,9 @@ public class Mapping : IMapping
/// <inheritdoc /> /// <inheritdoc />
public Guid Guid { get; } public Guid Guid { get; }
/// <inheritdoc />
public DateTime? UpdatedAt { get; set; }
/// <inheritdoc /> /// <inheritdoc />
public string? Title { get; } public string? Title { get; }
@@ -64,15 +67,22 @@ public class Mapping : IMapping
public IWebhook[]? Webhooks { get; } public IWebhook[]? Webhooks { get; }
/// <inheritdoc /> /// <inheritdoc />
public bool? UseWebhooksFireAndForget { get; set; } public bool? UseWebhooksFireAndForget { get; }
/// <inheritdoc /> /// <inheritdoc />
public ITimeSettings? TimeSettings { get; } public ITimeSettings? TimeSettings { get; }
/// <inheritdoc />
public object? Data { get; }
/// <inheritdoc />
public double? Probability { get; }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Mapping"/> class. /// Initializes a new instance of the <see cref="Mapping"/> class.
/// </summary> /// </summary>
/// <param name="guid">The unique identifier.</param> /// <param name="guid">The unique identifier.</param>
/// <param name="updatedAt">The datetime when this mapping was created.</param>
/// <param name="title">The unique title (can be null).</param> /// <param name="title">The unique title (can be null).</param>
/// <param name="description">The description (can be null).</param> /// <param name="description">The description (can be null).</param>
/// <param name="path">The full file path from this mapping title (can be null).</param> /// <param name="path">The full file path from this mapping title (can be null).</param>
@@ -87,8 +97,11 @@ public class Mapping : IMapping
/// <param name="webhooks">The Webhooks. [Optional]</param> /// <param name="webhooks">The Webhooks. [Optional]</param>
/// <param name="useWebhooksFireAndForget">Use Fire and Forget for the defined webhook(s). [Optional]</param> /// <param name="useWebhooksFireAndForget">Use Fire and Forget for the defined webhook(s). [Optional]</param>
/// <param name="timeSettings">The TimeSettings. [Optional]</param> /// <param name="timeSettings">The TimeSettings. [Optional]</param>
/// <param name="data">The data object. [Optional]</param>
/// <param name="probability">Define the probability when this request should be matched. [Optional]</param>
public Mapping( public Mapping(
Guid guid, Guid guid,
DateTime updatedAt,
string? title, string? title,
string? description, string? description,
string? path, string? path,
@@ -102,9 +115,12 @@ public class Mapping : IMapping
int? stateTimes, int? stateTimes,
IWebhook[]? webhooks, IWebhook[]? webhooks,
bool? useWebhooksFireAndForget, bool? useWebhooksFireAndForget,
ITimeSettings? timeSettings) ITimeSettings? timeSettings,
object? data,
double? probability)
{ {
Guid = guid; Guid = guid;
UpdatedAt = updatedAt;
Title = title; Title = title;
Description = description; Description = description;
Path = path; Path = path;
@@ -119,6 +135,8 @@ public class Mapping : IMapping
Webhooks = webhooks; Webhooks = webhooks;
UseWebhooksFireAndForget = useWebhooksFireAndForget; UseWebhooksFireAndForget = useWebhooksFireAndForget;
TimeSettings = timeSettings; TimeSettings = timeSettings;
Data = data;
Probability = probability;
} }
/// <inheritdoc cref="IMapping.ProvideResponseAsync" /> /// <inheritdoc cref="IMapping.ProvideResponseAsync" />
+155
View File
@@ -0,0 +1,155 @@
using System;
using System.Linq;
using System.Text;
using Newtonsoft.Json;
using Stef.Validation;
using WireMock.Admin.Mappings;
using WireMock.Matchers.Request;
using WireMock.Owin;
using WireMock.Serialization;
using WireMock.Server;
using WireMock.Settings;
using WireMock.Types;
using WireMock.Util;
namespace WireMock;
/// <summary>
/// MappingBuilder
/// </summary>
public class MappingBuilder : IMappingBuilder
{
private readonly WireMockServerSettings _settings;
private readonly IWireMockMiddlewareOptions _options;
private readonly MappingConverter _mappingConverter;
private readonly MappingToFileSaver _mappingToFileSaver;
private readonly IGuidUtils _guidUtils;
private readonly IDateTimeUtils _dateTimeUtils;
/// <summary>
/// Create a MappingBuilder
/// </summary>
/// <param name="settings">The optional <see cref="WireMockServerSettings"/>.</param>
public MappingBuilder(WireMockServerSettings? settings = null)
{
_settings = settings ?? new WireMockServerSettings();
_options = WireMockMiddlewareOptionsHelper.InitFromSettings(_settings);
var matcherMapper = new MatcherMapper(_settings);
_mappingConverter = new MappingConverter(matcherMapper);
_mappingToFileSaver = new MappingToFileSaver(_settings, _mappingConverter);
_guidUtils = new GuidUtils();
_dateTimeUtils = new DateTimeUtils();
}
internal MappingBuilder(
WireMockServerSettings settings,
IWireMockMiddlewareOptions options,
MappingConverter mappingConverter,
MappingToFileSaver mappingToFileSaver,
IGuidUtils guidUtils,
IDateTimeUtils dateTimeUtils
)
{
_settings = Guard.NotNull(settings);
_options = Guard.NotNull(options);
_mappingConverter = Guard.NotNull(mappingConverter);
_mappingToFileSaver = Guard.NotNull(mappingToFileSaver);
_guidUtils = Guard.NotNull(guidUtils);
_dateTimeUtils = Guard.NotNull(dateTimeUtils);
}
/// <inheritdoc />
public IRespondWithAProvider Given(IRequestMatcher requestMatcher, bool saveToFile = false)
{
return new RespondWithAProvider(RegisterMapping, Guard.NotNull(requestMatcher), _settings, _guidUtils, _dateTimeUtils, saveToFile);
}
/// <inheritdoc />
public MappingModel[] GetMappings()
{
return GetNonAdminMappings().Select(_mappingConverter.ToMappingModel).ToArray();
}
/// <inheritdoc />
public string ToJson()
{
return ToJson(GetMappings());
}
/// <inheritdoc />
public string? ToCSharpCode(Guid guid, MappingConverterType converterType)
{
var mapping = GetNonAdminMappings().FirstOrDefault(m => m.Guid == guid);
if (mapping is null)
{
return null;
}
var settings = new MappingConverterSettings { AddStart = true, ConverterType = converterType };
return _mappingConverter.ToCSharpCode(mapping, settings);
}
/// <inheritdoc />
public string ToCSharpCode(MappingConverterType converterType)
{
var sb = new StringBuilder();
bool addStart = true;
foreach (var mapping in GetNonAdminMappings())
{
sb.AppendLine(_mappingConverter.ToCSharpCode(mapping, new MappingConverterSettings { AddStart = addStart, ConverterType = converterType }));
if (addStart)
{
addStart = false;
}
}
return sb.ToString();
}
/// <inheritdoc />
public void SaveMappingsToFile(string path)
{
_mappingToFileSaver.SaveMappingsToFile(GetNonAdminMappings(), path);
}
/// <inheritdoc />
public void SaveMappingsToFolder(string? folder)
{
foreach (var mapping in GetNonAdminMappings())
{
_mappingToFileSaver.SaveMappingToFile(mapping, folder);
}
}
private IMapping[] GetNonAdminMappings()
{
return _options.Mappings.Values.Where(m => !m.IsAdminInterface).OrderBy(m => m.UpdatedAt).ToArray();
}
private void RegisterMapping(IMapping mapping, bool saveToFile)
{
// Check a mapping exists with the same Guid. If so, update the datetime and replace it.
if (_options.Mappings.ContainsKey(mapping.Guid))
{
mapping.UpdatedAt = _dateTimeUtils.UtcNow;
_options.Mappings[mapping.Guid] = mapping;
}
else
{
_options.Mappings.TryAdd(mapping.Guid, mapping);
}
if (saveToFile)
{
_mappingToFileSaver.SaveMappingToFile(mapping);
}
}
private static string ToJson(object value)
{
return JsonConvert.SerializeObject(value, JsonSerializationConstants.JsonSerializerSettingsDefault);
}
}
@@ -1,6 +1,5 @@
using System.Net.Http.Headers; using System.Net.Http.Headers;
using AnyOfTypes; using AnyOfTypes;
using JetBrains.Annotations;
using WireMock.Models; using WireMock.Models;
namespace WireMock.Matchers; namespace WireMock.Matchers;
+35 -18
View File
@@ -5,8 +5,8 @@ using AnyOfTypes;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Stef.Validation; using Stef.Validation;
using WireMock.Extensions; using WireMock.Extensions;
using WireMock.Json;
using WireMock.Models; using WireMock.Models;
using WireMock.Util;
namespace WireMock.Matchers; namespace WireMock.Matchers;
@@ -100,38 +100,55 @@ public class LinqMatcher : IObjectMatcher, IStringMatcher
{ {
double match = MatchScores.Mismatch; double match = MatchScores.Mismatch;
JObject value; JArray jArray;
switch (input) try
{ {
case JObject valueAsJObject: jArray = new JArray { input };
value = valueAsJObject; }
break; catch
{
case { } valueAsObject: jArray = new JArray { JToken.FromObject(input) };
value = JObject.FromObject(valueAsObject);
break;
default:
return MatchScores.Mismatch;
} }
//enumerable = jArray.ToDynamicClassArray();
//JObject value;
//switch (input)
//{
// case JObject valueAsJObject:
// value = valueAsJObject;
// break;
// case { } valueAsObject:
// value = JObject.FromObject(valueAsObject);
// break;
// default:
// return MatchScores.Mismatch;
//}
// Convert a single object to a Queryable JObject-list with 1 entry. // Convert a single object to a Queryable JObject-list with 1 entry.
var queryable1 = new[] { value }.AsQueryable(); //var queryable1 = new[] { value }.AsQueryable();
var queryable = jArray.ToDynamicClassArray().AsQueryable();
try try
{ {
// Generate the DynamicLinq select statement. // Generate the DynamicLinq select statement.
string dynamicSelect = JsonUtils.GenerateDynamicLinqStatement(value); //string dynamicSelect = JsonUtils.GenerateDynamicLinqStatement(value);
// Execute DynamicLinq Select statement. // Execute DynamicLinq Select statement.
var queryable2 = queryable1.Select(dynamicSelect); //var queryable2 = queryable1.Select(dynamicSelect);
// Use the Any(...) method to check if the result matches. // Use the Any(...) method to check if the result matches.
match = MatchScores.ToScore(_patterns.Select(pattern => queryable2.Any(pattern)).ToArray(), MatchOperator);
var patternsAsStringArray = _patterns.Select(p => p.GetPattern()).ToArray();
var scores = patternsAsStringArray.Select(p => queryable.Any(p)).ToArray();
match = MatchScores.ToScore(_patterns.Select(pattern => queryable.Any(pattern.GetPattern())).ToArray(), MatchOperator);
return MatchBehaviourHelper.Convert(MatchBehaviour, match); return MatchBehaviourHelper.Convert(MatchBehaviour, match);
} }
catch catch (Exception e)
{ {
if (ThrowException) if (ThrowException)
{ {
-1
View File
@@ -1,6 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
namespace WireMock.Matchers; namespace WireMock.Matchers;
@@ -1,3 +1,4 @@
using System;
using System.Linq; using System.Linq;
using AnyOfTypes; using AnyOfTypes;
using WireMock.Models; using WireMock.Models;
@@ -62,7 +63,7 @@ public class NotNullOrEmptyMatcher : IObjectMatcher, IStringMatcher
/// <inheritdoc cref="IStringMatcher.GetPatterns"/> /// <inheritdoc cref="IStringMatcher.GetPatterns"/>
public AnyOf<string, StringPattern>[] GetPatterns() public AnyOf<string, StringPattern>[] GetPatterns()
{ {
return new AnyOf<string, StringPattern>[0]; return EmptyArray<AnyOf<string, StringPattern>>.Value;
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -1,8 +1,7 @@
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using AnyOfTypes;
using Stef.Validation; using Stef.Validation;
using WireMock.Models;
using WireMock.Types; using WireMock.Types;
using WireMock.Util; using WireMock.Util;
@@ -33,6 +32,11 @@ public class RequestMessageBodyMatcher : IRequestMatcher
/// </summary> /// </summary>
public Func<IBodyData?, bool>? BodyDataFunc { get; } public Func<IBodyData?, bool>? BodyDataFunc { get; }
/// <summary>
/// The body data function for FormUrlEncoded
/// </summary>
public Func<IDictionary<string, string>?, bool>? FormUrlEncodedFunc { get; }
/// <summary> /// <summary>
/// The matchers. /// The matchers.
/// </summary> /// </summary>
@@ -109,6 +113,15 @@ public class RequestMessageBodyMatcher : IRequestMatcher
BodyDataFunc = Guard.NotNull(func); BodyDataFunc = Guard.NotNull(func);
} }
/// <summary>
/// Initializes a new instance of the <see cref="RequestMessageBodyMatcher"/> class.
/// </summary>
/// <param name="func">The function.</param>
public RequestMessageBodyMatcher(Func<IDictionary<string, string>?, bool> func)
{
FormUrlEncodedFunc = Guard.NotNull(func);
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="RequestMessageBodyMatcher"/> class. /// Initializes a new instance of the <see cref="RequestMessageBodyMatcher"/> class.
/// </summary> /// </summary>
@@ -144,6 +157,7 @@ public class RequestMessageBodyMatcher : IRequestMatcher
{ {
case BodyType.Json: case BodyType.Json:
case BodyType.String: case BodyType.String:
case BodyType.FormUrlEncoded:
return notNullOrEmptyMatcher.IsMatch(requestMessage.BodyData.BodyAsString); return notNullOrEmptyMatcher.IsMatch(requestMessage.BodyData.BodyAsString);
case BodyType.Bytes: case BodyType.Bytes:
@@ -158,7 +172,7 @@ public class RequestMessageBodyMatcher : IRequestMatcher
{ {
// If the body is a byte array, try to match. // If the body is a byte array, try to match.
var detectedBodyType = requestMessage.BodyData?.DetectedBodyType; var detectedBodyType = requestMessage.BodyData?.DetectedBodyType;
if (detectedBodyType is BodyType.Bytes or BodyType.String) if (detectedBodyType is BodyType.Bytes or BodyType.String or BodyType.FormUrlEncoded)
{ {
return exactObjectMatcher.IsMatch(requestMessage.BodyData?.BodyAsBytes); return exactObjectMatcher.IsMatch(requestMessage.BodyData?.BodyAsBytes);
} }
@@ -184,7 +198,7 @@ public class RequestMessageBodyMatcher : IRequestMatcher
if (matcher is IStringMatcher stringMatcher) if (matcher is IStringMatcher stringMatcher)
{ {
// If the body is a Json or a String, use the BodyAsString to match on. // If the body is a Json or a String, use the BodyAsString to match on.
if (requestMessage?.BodyData?.DetectedBodyType == BodyType.Json || requestMessage?.BodyData?.DetectedBodyType == BodyType.String) if (requestMessage?.BodyData?.DetectedBodyType is BodyType.Json or BodyType.String or BodyType.FormUrlEncoded)
{ {
return stringMatcher.IsMatch(requestMessage.BodyData.BodyAsString); return stringMatcher.IsMatch(requestMessage.BodyData.BodyAsString);
} }
@@ -206,6 +220,11 @@ public class RequestMessageBodyMatcher : IRequestMatcher
return MatchScores.ToScore(Func(requestMessage.BodyData?.BodyAsString)); return MatchScores.ToScore(Func(requestMessage.BodyData?.BodyAsString));
} }
if (FormUrlEncodedFunc != null)
{
return MatchScores.ToScore(FormUrlEncodedFunc(requestMessage.BodyData?.BodyAsFormUrlEncoded));
}
if (JsonFunc != null) if (JsonFunc != null)
{ {
return MatchScores.ToScore(JsonFunc(requestMessage.BodyData?.BodyAsJson)); return MatchScores.ToScore(JsonFunc(requestMessage.BodyData?.BodyAsJson));
@@ -1,6 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using JetBrains.Annotations;
using Stef.Validation; using Stef.Validation;
namespace WireMock.Matchers.Request; namespace WireMock.Matchers.Request;
@@ -25,9 +24,9 @@ public abstract class RequestMessageCompositeMatcher : IRequestMatcher
/// </summary> /// </summary>
/// <param name="requestMatchers">The request matchers.</param> /// <param name="requestMatchers">The request matchers.</param>
/// <param name="type">The CompositeMatcherType type (Defaults to 'And')</param> /// <param name="type">The CompositeMatcherType type (Defaults to 'And')</param>
protected RequestMessageCompositeMatcher([NotNull] IEnumerable<IRequestMatcher> requestMatchers, CompositeMatcherType type = CompositeMatcherType.And) protected RequestMessageCompositeMatcher(IEnumerable<IRequestMatcher> requestMatchers, CompositeMatcherType type = CompositeMatcherType.And)
{ {
Guard.NotNull(requestMatchers, nameof(requestMatchers)); Guard.NotNull(requestMatchers);
_type = type; _type = type;
RequestMatchers = requestMatchers; RequestMatchers = requestMatchers;
+4
View File
@@ -1,3 +1,4 @@
using System.Collections.Generic;
using System.Text; using System.Text;
using WireMock.Types; using WireMock.Types;
@@ -14,6 +15,9 @@ public class BodyData : IBodyData
/// <inheritdoc /> /// <inheritdoc />
public string? BodyAsString { get; set; } public string? BodyAsString { get; set; }
/// <inheritdoc />
public IDictionary<string, string>? BodyAsFormUrlEncoded { get; set; }
/// <inheritdoc cref="IBodyData.BodyAsJson" /> /// <inheritdoc cref="IBodyData.BodyAsJson" />
public object? BodyAsJson { get; set; } public object? BodyAsJson { get; set; }
+12 -13
View File
@@ -1,19 +1,18 @@
using System; using System;
namespace WireMock.Models namespace WireMock.Models;
/// <summary>
/// TimeSettingsModel: Start, End and TTL
/// </summary>
public class TimeSettings : ITimeSettings
{ {
/// <summary> /// <inheritdoc />
/// TimeSettingsModel: Start, End and TTL public DateTime? Start { get; set; }
/// </summary>
public class TimeSettings : ITimeSettings
{
/// <inheritdoc />
public DateTime? Start { get; set; }
/// <inheritdoc /> /// <inheritdoc />
public DateTime? End { get; set; } public DateTime? End { get; set; }
/// <inheritdoc /> /// <inheritdoc />
public int? TTL { get; set; } public int? TTL { get; set; }
}
} }
+37 -41
View File
@@ -1,51 +1,47 @@
using System; using System;
using Stef.Validation; using Stef.Validation;
namespace WireMock.Models namespace WireMock.Models;
/// <summary>
/// UrlDetails
/// </summary>
public class UrlDetails
{ {
/// <summary> /// <summary>
/// UrlDetails /// Gets the url (relative).
/// </summary> /// </summary>
public class UrlDetails public Uri Url { get; }
/// <summary>
/// Gets the AbsoluteUrl.
/// </summary>
public Uri AbsoluteUrl { get; }
/// <summary>
/// Initializes a new instance of the <see cref="UrlDetails"/> class.
/// </summary>
/// <param name="url">The URL.</param>
public UrlDetails(string url) : this(new Uri(url))
{ {
/// <summary> }
/// Gets the url (relative).
/// </summary>
public Uri Url { get; }
/// <summary> /// <summary>
/// Gets the AbsoluteUrl. /// Initializes a new instance of the <see cref="UrlDetails"/> class.
/// </summary> /// </summary>
public Uri AbsoluteUrl { get; } /// <param name="url">The URL.</param>
public UrlDetails(Uri url) : this(url, url)
{
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="UrlDetails"/> class. /// Initializes a new instance of the <see cref="UrlDetails"/> class.
/// </summary> /// </summary>
/// <param name="url">The URL.</param> /// <param name="absoluteUrl">The absolute URL.</param>
public UrlDetails(string url) : this(new Uri(url)) /// <param name="url">The URL (relative).</param>
{ public UrlDetails(Uri absoluteUrl, Uri url)
} {
AbsoluteUrl = Guard.NotNull(absoluteUrl);
/// <summary> Url = Guard.NotNull(url);
/// Initializes a new instance of the <see cref="UrlDetails"/> class.
/// </summary>
/// <param name="url">The URL.</param>
public UrlDetails(Uri url) : this(url, url)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="UrlDetails"/> class.
/// </summary>
/// <param name="absoluteUrl">The absolute URL.</param>
/// <param name="url">The URL (relative).</param>
public UrlDetails(Uri absoluteUrl, Uri url)
{
Guard.NotNull(absoluteUrl, nameof(absoluteUrl));
Guard.NotNull(url, nameof(url));
AbsoluteUrl = absoluteUrl;
Url = url;
}
} }
} }
@@ -2,9 +2,10 @@
using System.Collections.Generic; using System.Collections.Generic;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Https;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using WireMock.HttpsCertificate; using CertificateLoader = WireMock.HttpsCertificate.CertificateLoader;
namespace WireMock.Owin namespace WireMock.Owin
{ {
@@ -26,21 +27,25 @@ namespace WireMock.Owin
{ {
kestrelOptions.ListenAnyIP(urlDetail.Port, listenOptions => kestrelOptions.ListenAnyIP(urlDetail.Port, listenOptions =>
{ {
if (wireMockMiddlewareOptions.CustomCertificateDefined) listenOptions.UseHttps(options =>
{ {
listenOptions.UseHttps(CertificateLoader.LoadCertificate( if (wireMockMiddlewareOptions.CustomCertificateDefined)
wireMockMiddlewareOptions.X509StoreName, {
wireMockMiddlewareOptions.X509StoreLocation, options.ServerCertificate = CertificateLoader.LoadCertificate(
wireMockMiddlewareOptions.X509ThumbprintOrSubjectName, wireMockMiddlewareOptions.X509StoreName,
wireMockMiddlewareOptions.X509CertificateFilePath, wireMockMiddlewareOptions.X509StoreLocation,
wireMockMiddlewareOptions.X509CertificatePassword, wireMockMiddlewareOptions.X509ThumbprintOrSubjectName,
urlDetail.Host) wireMockMiddlewareOptions.X509CertificateFilePath,
); wireMockMiddlewareOptions.X509CertificatePassword,
} urlDetail.Host);
else }
{
listenOptions.UseHttps(); options.ClientCertificateMode = (ClientCertificateMode) wireMockMiddlewareOptions.ClientCertificateMode;
} if (wireMockMiddlewareOptions.AcceptAnyClientCertificate)
{
options.ClientCertificateValidation = (_, _, _) => true;
}
});
}); });
} }
else else
@@ -2,6 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Server.Kestrel; using Microsoft.AspNetCore.Server.Kestrel;
using Microsoft.AspNetCore.Server.Kestrel.Https;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using WireMock.HttpsCertificate; using WireMock.HttpsCertificate;
@@ -23,21 +24,22 @@ internal partial class AspNetCoreSelfHost
{ {
if (urlDetail.IsHttps) if (urlDetail.IsHttps)
{ {
if (wireMockMiddlewareOptions.CustomCertificateDefined) options.UseHttps(new HttpsConnectionFilterOptions
{ {
options.UseHttps(CertificateLoader.LoadCertificate( ServerCertificate = wireMockMiddlewareOptions.CustomCertificateDefined
wireMockMiddlewareOptions.X509StoreName, ? CertificateLoader.LoadCertificate(
wireMockMiddlewareOptions.X509StoreLocation, wireMockMiddlewareOptions.X509StoreName,
wireMockMiddlewareOptions.X509ThumbprintOrSubjectName, wireMockMiddlewareOptions.X509StoreLocation,
wireMockMiddlewareOptions.X509CertificateFilePath, wireMockMiddlewareOptions.X509ThumbprintOrSubjectName,
wireMockMiddlewareOptions.X509CertificatePassword, wireMockMiddlewareOptions.X509CertificateFilePath,
urlDetail.Host) wireMockMiddlewareOptions.X509CertificatePassword,
); urlDetail.Host)
} : PublicCertificateHelper.GetX509Certificate2(),
else ClientCertificateMode = (ClientCertificateMode) wireMockMiddlewareOptions.ClientCertificateMode,
{ ClientCertificateValidation = wireMockMiddlewareOptions.AcceptAnyClientCertificate
options.UseHttps(PublicCertificateHelper.GetX509Certificate2()); ? (_, _, _) => true
} : null,
});
} }
} }
} }
@@ -11,6 +11,7 @@ using Microsoft.Extensions.DependencyInjection;
using Stef.Validation; using Stef.Validation;
using WireMock.Logging; using WireMock.Logging;
using WireMock.Owin.Mappers; using WireMock.Owin.Mappers;
using WireMock.Services;
using WireMock.Util; using WireMock.Util;
namespace WireMock.Owin namespace WireMock.Owin
@@ -66,6 +67,7 @@ namespace WireMock.Owin
{ {
services.AddSingleton(_wireMockMiddlewareOptions); services.AddSingleton(_wireMockMiddlewareOptions);
services.AddSingleton<IMappingMatcher, MappingMatcher>(); services.AddSingleton<IMappingMatcher, MappingMatcher>();
services.AddSingleton<IRandomizerDoubleBetween0And1, RandomizerDoubleBetween0And1>();
services.AddSingleton<IOwinRequestMapper, OwinRequestMapper>(); services.AddSingleton<IOwinRequestMapper, OwinRequestMapper>();
services.AddSingleton<IOwinResponseMapper, OwinResponseMapper>(); services.AddSingleton<IOwinResponseMapper, OwinResponseMapper>();
@@ -42,6 +42,10 @@ internal interface IWireMockMiddlewareOptions
Action<IServiceCollection>? AdditionalServiceRegistration { get; set; } Action<IServiceCollection>? AdditionalServiceRegistration { get; set; }
CorsPolicyOptions? CorsPolicyOptions { get; set; } CorsPolicyOptions? CorsPolicyOptions { get; set; }
ClientCertificateMode ClientCertificateMode { get; set; }
bool AcceptAnyClientCertificate { get; set; }
#endif #endif
IFileSystemHandler? FileSystemHandler { get; set; } IFileSystemHandler? FileSystemHandler { get; set; }
@@ -68,7 +68,21 @@ namespace WireMock.Owin.Mappers
body = await BodyParser.ParseAsync(bodyParserSettings).ConfigureAwait(false); body = await BodyParser.ParseAsync(bodyParserSettings).ConfigureAwait(false);
} }
return new RequestMessage(options, urlDetails, method, clientIP, body, headers, cookies) { DateTime = DateTime.UtcNow }; return new RequestMessage(
options,
urlDetails,
method,
clientIP,
body,
headers,
cookies
#if USE_ASPNETCORE
, await request.HttpContext.Connection.GetClientCertificateAsync()
#endif
)
{
DateTime = DateTime.UtcNow
};
} }
private static (UrlDetails UrlDetails, string ClientIP) ParseRequest(IRequest request) private static (UrlDetails UrlDetails, string ClientIP) ParseRequest(IRequest request)

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