Compare commits

..

14 Commits

Author SHA1 Message Date
Stef Heyenrath
1e968106d1 1.4.26 2021-11-04 07:10:56 +00:00
Daniel L. Romero
49e10ed20f Support basepath from servers (#675)
* Support basepath from servers

* Refactor BasePath

* Comments applied
2021-11-02 15:28:02 +01:00
Daniel L. Romero
c3ec3a66b3 Fix random generate data in url no spaces (#676)
* Fix random generate data in url no spaces

* Format class
2021-11-01 18:53:44 +01:00
Stef Heyenrath
5644491942 1.4.26 2021-10-30 09:26:33 +02:00
Stef Heyenrath
6e5e629525 "RandomDataGenerator.Net" Version="1.0.13" 2021-10-30 09:15:43 +02:00
Daniel L. Romero
77ee123340 Support examples random data generation (#673)
* Add Support Random Generation in Examples, this commit add the bool property DynamicExamples.

* Comments applied

* Remove  '#pragma warning disable 1591'
2021-10-30 09:13:24 +02:00
Daniel L. Romero
ae2a05e86b Improve method MapSchemaToObject to support array and object (#670) 2021-10-28 08:21:30 +02:00
Stef Heyenrath
0e06cf6346 1.4.25 2021-10-27 19:22:46 +02:00
Stef Heyenrath
e9db520cc3 Add TimeSettings (Start, End and TTL) (#661)
* time

* .

* UT

* using JetBrains.Annotations;
2021-10-27 18:57:13 +02:00
Stef Heyenrath
a0e661fae9 Support Schema Example and Support AllOf in definitions (#669)
* Support an Example within a Schema

* Support AllOf in definitions

* Refactor MapSchemaAllOfToObject method and add test files

* Include schema examples

* Refactor duplicate code, create method MapPropertyAsJObject

* Remove commented code

* Merge from master + update WireMock.Net.OpenApiParser.ConsoleApp

Co-authored-by: Daniel <raiga1234@gmail.com>
2021-10-27 09:40:12 +02:00
Stef Heyenrath
25666152bb Add JsonPartialWildcardMatcher (#667)
* JsonPartialWildcardMatcher

* .

* more tests
2021-10-27 08:16:18 +02:00
Stef Heyenrath
6f5eeb5359 Use IWireMockLogger in example 2021-10-26 18:40:34 +02:00
Stef Heyenrath
7ca2095576 Update logging (add version) 2021-10-24 10:44:39 +02:00
Daniel L. Romero
74edad517b Support Array in OpenApi Examples (#664) 2021-10-21 22:12:59 +02:00
51 changed files with 3564 additions and 230 deletions

View File

@@ -1,3 +1,15 @@
# 1.4.26 (04 November 2021)
- [#670](https://github.com/WireMock-Net/WireMock.Net/pull/670) - Improve method MapSchemaToObject to support array and object [feature] contributed by [leolplex](https://github.com/leolplex)
- [#673](https://github.com/WireMock-Net/WireMock.Net/pull/673) - Support examples random data generation contributed by [leolplex](https://github.com/leolplex)
- [#675](https://github.com/WireMock-Net/WireMock.Net/pull/675) - Support basepath from servers contributed by [leolplex](https://github.com/leolplex)
- [#676](https://github.com/WireMock-Net/WireMock.Net/pull/676) - Fix random generate data in url no spaces [feature] contributed by [leolplex](https://github.com/leolplex)
# 1.4.25 (27 October 2021)
- [#661](https://github.com/WireMock-Net/WireMock.Net/pull/661) - Add TimeSettings (Start, End and TTL) [feature] contributed by [StefH](https://github.com/StefH)
- [#664](https://github.com/WireMock-Net/WireMock.Net/pull/664) - Support Array in OpenApiParser [feature] contributed by [leolplex](https://github.com/leolplex)
- [#667](https://github.com/WireMock-Net/WireMock.Net/pull/667) - Add JsonPartialWildcardMatcher [feature] contributed by [StefH](https://github.com/StefH)
- [#669](https://github.com/WireMock-Net/WireMock.Net/pull/669) - Support Schema Example and Support AllOf in definitions [feature] contributed by [StefH](https://github.com/StefH)
# 1.4.24 (20 October 2021)
- [#637](https://github.com/WireMock-Net/WireMock.Net/pull/637) - Add support for AzureAD authentication for REST admin interface [feature] contributed by [StefH](https://github.com/StefH)
- [#643](https://github.com/WireMock-Net/WireMock.Net/pull/643) - Support edge case: first object, next an array. [feature] contributed by [leolplex](https://github.com/leolplex)
@@ -119,7 +131,6 @@
- [#549](https://github.com/WireMock-Net/WireMock.Net/issues/549) - WithProxy(...) does not save the mappings to file [bug]
# 1.3.8 (03 December 2020)
- [#539](https://github.com/WireMock-Net/WireMock.Net/pull/539) - Support for partial JSON matching contributed by [gleb-osokin](https://github.com/gleb-osokin)
- [#542](https://github.com/WireMock-Net/WireMock.Net/pull/542) - Create dotnet-wiremock tool [feature] contributed by [StefH](https://github.com/StefH)
- [#543](https://github.com/WireMock-Net/WireMock.Net/pull/543) - Add support for .NET 5 [feature] contributed by [StefH](https://github.com/StefH)
- [#544](https://github.com/WireMock-Net/WireMock.Net/pull/544) - Use Java 11 in Azure Pipelines (needed for SonarCloud) [feature] contributed by [StefH](https://github.com/StefH)
@@ -127,6 +138,9 @@
- [#547](https://github.com/WireMock-Net/WireMock.Net/pull/547) - Fix Proxying with SSL and NetCoreApp3.1 [bug] contributed by [StefH](https://github.com/StefH)
- [#524](https://github.com/WireMock-Net/WireMock.Net/issues/524) - Proxying with SSL Not Working in .NET Core 3.1 [bug]
# 1.3.7 (17 November 2020)
- [#539](https://github.com/WireMock-Net/WireMock.Net/pull/539) - Support for partial JSON matching contributed by [gleb-osokin](https://github.com/gleb-osokin)
# 1.3.6 (10 November 2020)
- [#529](https://github.com/WireMock-Net/WireMock.Net/pull/529) - Add assertions for ClientIP, Url and ProxyUrl [feature] contributed by [akamud](https://github.com/akamud)
- [#535](https://github.com/WireMock-Net/WireMock.Net/pull/535) - WithCallback should use also use enum HttpStatusCode [bug] contributed by [StefH](https://github.com/StefH)

View File

@@ -4,7 +4,7 @@
</PropertyGroup>
<PropertyGroup>
<VersionPrefix>1.4.24</VersionPrefix>
<VersionPrefix>1.4.26</VersionPrefix>
<PackageReleaseNotes>See CHANGELOG.md</PackageReleaseNotes>
<PackageIcon>WireMock.Net-Logo.png</PackageIcon>
<PackageProjectUrl>https://github.com/WireMock-Net/WireMock.Net</PackageProjectUrl>

View File

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

View File

@@ -1,9 +1,7 @@
# 1.4.24 (20 October 2021)
- #637 Add support for AzureAD authentication for REST admin interface [feature]
- #643 Support edge case: first object, next an array. [feature]
- #644 Mapping headers in OpenAPI [feature]
- #649 Refactor method name MapHeaders and httpStatusCode
- #651 Implement PatternAsFile for StringMatcher [feature]
- #654 Update NotNullOrEmptyMatcher to also implement IStringMatcher [feature]
# 1.4.26 (04 November 2021)
- #670 Improve method MapSchemaToObject to support array and object [feature]
- #673 Support examples random data generation
- #675 Support basepath from servers
- #676 Fix random generate data in url no spaces [feature]
The full release notes can be found here: https://github.com/WireMock-Net/WireMock.Net/blob/master/CHANGELOG.md

View File

@@ -0,0 +1,269 @@
{
"swagger": "2.0",
"info": {
"version": "1.0.0",
"title": "Swagger Petstore",
"description": "A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification",
"termsOfService": "http://swagger.io/terms/",
"contact": {
"name": "Swagger API Team"
},
"license": {
"name": "MIT"
}
},
"host": "petstore.swagger.io",
"basePath": "/api",
"schemes": [
"http"
],
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"paths": {
"/pets": {
"get": {
"description": "Returns all pets from the system that the user has access to",
"operationId": "findPets",
"produces": [
"application/json",
"application/xml",
"text/xml",
"text/html"
],
"parameters": [{
"name": "tags",
"in": "query",
"description": "tags to filter by",
"required": false,
"type": "array",
"items": {
"type": "string"
},
"collectionFormat": "csv"
},
{
"name": "limit",
"in": "query",
"description": "maximum number of results to return",
"required": false,
"type": "integer",
"format": "int32"
}
],
"responses": {
"200": {
"description": "pet response",
"schema": {
"example": [{
"name": "MEXAMPLE",
"tag": "MEXAMPLE",
"id": 9988
}, {
"name": "OtherExample",
"tag": "OtherExample",
"id": 8877
}],
"type": "array",
"items": {
"$ref": "#/definitions/Pet"
}
}
},
"default": {
"description": "unexpected error",
"schema": {
"$ref": "#/definitions/ErrorModel"
}
}
}
},
"post": {
"description": "Creates a new pet in the store. Duplicates are allowed",
"operationId": "addPet",
"produces": [
"application/json"
],
"parameters": [{
"name": "pet",
"in": "body",
"description": "Pet to add to the store",
"required": true,
"schema": {
"$ref": "#/definitions/NewPet"
}
}],
"responses": {
"200": {
"description": "pet response",
"schema": {
"$ref": "#/definitions/Pet"
}
},
"default": {
"description": "unexpected error",
"schema": {
"$ref": "#/definitions/ErrorModel"
}
}
}
}
},
"/pet": {
"get": {
"description": "Returns a pet from the system that the user has access to",
"operationId": "findPet",
"produces": [
"application/json",
"application/xml",
"text/xml",
"text/html"
],
"parameters": [{
"name": "tags",
"in": "query",
"description": "tags to filter by",
"required": false,
"type": "array",
"items": {
"type": "string"
},
"collectionFormat": "csv"
},
{
"name": "limit",
"in": "query",
"description": "maximum number of results to return",
"required": false,
"type": "integer",
"format": "int32"
}
],
"responses": {
"200": {
"description": "pet response",
"schema": {
"$ref": "#/definitions/Pet"
}
},
"default": {
"description": "unexpected error",
"schema": {
"$ref": "#/definitions/ErrorModel"
}
}
}
}
},
"/pets/{id}": {
"get": {
"description": "Returns a user based on a single ID, if the user does not have access to the pet",
"operationId": "findPetById",
"produces": [
"application/json",
"application/xml",
"text/xml",
"text/html"
],
"parameters": [{
"name": "id",
"in": "path",
"description": "ID of pet to fetch",
"required": true,
"type": "integer",
"format": "int64"
}],
"responses": {
"200": {
"description": "pet response",
"schema": {
"$ref": "#/definitions/Pet"
}
},
"default": {
"description": "unexpected error",
"schema": {
"$ref": "#/definitions/ErrorModel"
}
}
}
},
"delete": {
"description": "deletes a single pet based on the ID supplied",
"operationId": "deletePet",
"parameters": [{
"name": "id",
"in": "path",
"description": "ID of pet to delete",
"required": true,
"type": "integer",
"format": "int64"
}],
"responses": {
"204": {
"description": "pet deleted"
},
"default": {
"description": "unexpected error",
"schema": {
"$ref": "#/definitions/ErrorModel"
}
}
}
}
}
},
"definitions": {
"Pet": {
"type": "object",
"allOf": [{
"$ref": "#/definitions/NewPet"
},
{
"required": [
"id"
],
"properties": {
"id": {
"type": "integer",
"format": "int64"
}
}
}
]
},
"NewPet": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string"
},
"tag": {
"type": "string"
}
}
},
"ErrorModel": {
"type": "object",
"required": [
"code",
"message"
],
"properties": {
"code": {
"type": "integer",
"format": "int32"
},
"message": {
"type": "string"
}
}
}
}
}

View File

@@ -0,0 +1,732 @@
swagger: '2.0'
info:
description: 'This is a sample server Petstore server. Copied from https://github.com/swagger-api/swagger-codegen/blob/master/modules/swagger-codegen/src/test/resources/2_0/petstore.yaml.'
version: 1.0.0
title: Swagger Petstore
termsOfService: 'http://swagger.io/terms/'
contact:
email: apiteam@swagger.io
license:
name: Apache-2.0
url: 'http://www.apache.org/licenses/LICENSE-2.0.html'
host: petstore.swagger.io
basePath: /v2
tags:
- name: pet
description: Everything about your Pets
externalDocs:
description: Find out more
url: 'http://swagger.io'
- name: store
description: Access to Petstore orders
- name: user
description: Operations about user
externalDocs:
description: Find out more about our store
url: 'http://swagger.io'
schemes:
- http
paths:
/pet:
post:
tags:
- pet
summary: Add a new pet to the store
description: ''
operationId: addPet
consumes:
- application/json
- application/xml
produces:
- application/xml
- application/json
parameters:
- in: body
name: body
description: Pet object that needs to be added to the store
required: true
schema:
$ref: '#/definitions/Pet'
responses:
'405':
description: Invalid input
security:
- petstore_auth:
- 'write:pets'
- 'read:pets'
put:
tags:
- pet
summary: Update an existing pet
description: ''
operationId: updatePet
consumes:
- application/json
- application/xml
produces:
- application/xml
- application/json
parameters:
- in: body
name: body
description: Pet object that needs to be added to the store
required: true
schema:
$ref: '#/definitions/Pet'
responses:
'400':
description: Invalid ID supplied
'404':
description: Pet not found
'405':
description: Validation exception
security:
- petstore_auth:
- 'write:pets'
- 'read:pets'
/pet/findByStatus:
get:
tags:
- pet
summary: Finds Pets by status
description: Multiple status values can be provided with comma separated strings
operationId: findPetsByStatus
produces:
- application/xml
- application/json
parameters:
- name: status
in: query
description: Status values that need to be considered for filter
required: true
type: array
items:
type: string
enum:
- available
- pending
- sold
default: available
collectionFormat: csv
responses:
'200':
description: successful operation
schema:
type: array
items:
$ref: '#/definitions/Pet'
'400':
description: Invalid status value
security:
- petstore_auth:
- 'write:pets'
- 'read:pets'
/pet/findByTags:
get:
tags:
- pet
summary: Finds Pets by tags
description: 'Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.'
operationId: findPetsByTags
produces:
- application/xml
- application/json
parameters:
- name: tags
in: query
description: Tags to filter by
required: true
type: array
items:
type: string
collectionFormat: csv
responses:
'200':
description: successful operation
schema:
type: array
items:
$ref: '#/definitions/Pet'
'400':
description: Invalid tag value
security:
- petstore_auth:
- 'write:pets'
- 'read:pets'
deprecated: true
'/pet/{petId}':
get:
tags:
- pet
summary: Find pet by ID
description: Returns a single pet
operationId: getPetById
produces:
- application/xml
- application/json
parameters:
- name: petId
in: path
description: ID of pet to return
required: true
type: integer
format: int64
responses:
'200':
description: successful operation
schema:
$ref: '#/definitions/Pet'
'400':
description: Invalid ID supplied
'404':
description: Pet not found
security:
- api_key: []
post:
tags:
- pet
summary: Updates a pet in the store with form data
description: ''
operationId: updatePetWithForm
consumes:
- application/x-www-form-urlencoded
produces:
- application/xml
- application/json
parameters:
- name: petId
in: path
description: ID of pet that needs to be updated
required: true
type: integer
format: int64
- name: name
in: formData
description: Updated name of the pet
required: false
type: string
- name: status
in: formData
description: Updated status of the pet
required: false
type: string
responses:
'405':
description: Invalid input
security:
- petstore_auth:
- 'write:pets'
- 'read:pets'
delete:
tags:
- pet
summary: Deletes a pet
description: ''
operationId: deletePet
produces:
- application/xml
- application/json
parameters:
- name: api_key
in: header
required: false
type: string
- name: petId
in: path
description: Pet id to delete
required: true
type: integer
format: int64
responses:
'200':
description: successful operation
'400':
description: Invalid pet value
security:
- petstore_auth:
- 'write:pets'
- 'read:pets'
'/pet/{petId}/uploadImage':
post:
tags:
- pet
summary: uploads an image
description: ''
operationId: uploadFile
consumes:
- multipart/form-data
produces:
- application/json
parameters:
- name: petId
in: path
description: ID of pet to update
required: true
type: integer
format: int64
- name: additionalMetadata
in: formData
description: Additional data to pass to server
required: false
type: string
- name: file
in: formData
description: file to upload
required: false
type: file
responses:
'200':
description: successful operation
schema:
$ref: '#/definitions/ApiResponse'
security:
- petstore_auth:
- 'write:pets'
- 'read:pets'
/store/inventory:
get:
tags:
- store
summary: Returns pet inventories by status
description: Returns a map of status codes to quantities
operationId: getInventory
produces:
- application/json
parameters: []
responses:
'200':
description: successful operation
schema:
type: object
additionalProperties:
type: integer
format: int32
security:
- api_key: []
/store/order:
post:
tags:
- store
summary: Place an order for a pet
description: ''
operationId: placeOrder
produces:
- application/xml
- application/json
parameters:
- in: body
name: body
description: order placed for purchasing the pet
required: true
schema:
$ref: '#/definitions/Order'
responses:
'200':
description: successful operation
schema:
$ref: '#/definitions/Order'
'400':
description: Invalid Order
'/store/order/{orderId}':
get:
tags:
- store
summary: Find purchase order by ID
description: 'For valid response try integer IDs with value <= 5 or > 10. Other values will generated exceptions'
operationId: getOrderById
produces:
- application/xml
- application/json
parameters:
- name: orderId
in: path
description: ID of pet that needs to be fetched
required: true
type: integer
maximum: 5
minimum: 1
format: int64
responses:
'200':
description: successful operation
schema:
$ref: '#/definitions/Order'
'400':
description: Invalid ID supplied
'404':
description: Order not found
delete:
tags:
- store
summary: Delete purchase order by ID
description: For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors
operationId: deleteOrder
produces:
- application/xml
- application/json
parameters:
- name: orderId
in: path
description: ID of the order that needs to be deleted
required: true
type: string
responses:
'400':
description: Invalid ID supplied
'404':
description: Order not found
/user:
post:
tags:
- user
summary: Create user
description: This can only be done by the logged in user.
operationId: createUser
produces:
- application/xml
- application/json
parameters:
- in: body
name: body
description: Created user object
required: true
schema:
$ref: '#/definitions/User'
responses:
default:
description: successful operation
/user/createWithArray:
post:
tags:
- user
summary: Creates list of users with given input array
description: ''
operationId: createUsersWithArrayInput
produces:
- application/xml
- application/json
parameters:
- in: body
name: body
description: List of user object
required: true
schema:
type: array
items:
$ref: '#/definitions/User'
responses:
default:
description: successful operation
/user/createWithList:
post:
tags:
- user
summary: Creates list of users with given input array
description: ''
operationId: createUsersWithListInput
produces:
- application/xml
- application/json
parameters:
- in: body
name: body
description: List of user object
required: true
schema:
type: array
items:
$ref: '#/definitions/User'
responses:
default:
description: successful operation
/user/login:
get:
tags:
- user
summary: Logs user into the system
description: ''
operationId: loginUser
produces:
- application/xml
- application/json
parameters:
- name: username
in: query
description: The user name for login
required: true
type: string
- name: password
in: query
description: The password for login in clear text
required: true
type: string
responses:
'200':
description: successful operation
schema:
type: string
headers:
X-Rate-Limit:
type: integer
format: int32
description: calls per hour allowed by the user
X-Expires-After:
type: string
format: date-time
description: date in UTC when toekn expires
'400':
description: Invalid username/password supplied
/user/logout:
get:
tags:
- user
summary: Logs out current logged in user session
description: ''
operationId: logoutUser
produces:
- application/xml
- application/json
parameters: []
responses:
default:
description: successful operation
'/user/{username}':
get:
tags:
- user
summary: Get user by user name
description: ''
operationId: getUserByName
produces:
- application/xml
- application/json
parameters:
- name: username
in: path
description: 'The name that needs to be fetched. Use user1 for testing.'
required: true
type: string
responses:
'200':
description: successful operation
schema:
$ref: '#/definitions/User'
'400':
description: Invalid username supplied
'404':
description: User not found
put:
tags:
- user
summary: Updated user
description: This can only be done by the logged in user.
operationId: updateUser
produces:
- application/xml
- application/json
parameters:
- name: username
in: path
description: name that need to be deleted
required: true
type: string
- in: body
name: body
description: Updated user object
required: true
schema:
$ref: '#/definitions/User'
responses:
'400':
description: Invalid user supplied
'404':
description: User not found
delete:
tags:
- user
summary: Delete user
description: This can only be done by the logged in user.
operationId: deleteUser
produces:
- application/xml
- application/json
parameters:
- name: username
in: path
description: The name that needs to be deleted
required: true
type: string
responses:
'400':
description: Invalid username supplied
'404':
description: User not found
securityDefinitions:
petstore_auth:
type: oauth2
authorizationUrl: 'http://petstore.swagger.io/api/oauth/dialog'
flow: implicit
scopes:
'write:pets': modify pets in your account
'read:pets': read your pets
api_key:
type: apiKey
name: api_key
in: header
definitions:
Order:
title: Pet Order
description: An order for a pets from the pet store
type: object
properties:
id:
type: integer
format: int64
petId:
type: integer
format: int64
quantity:
type: integer
format: int32
shipDate:
type: string
format: date-time
status:
type: string
description: Order Status
enum:
- placed
- approved
- delivered
complete:
type: boolean
default: false
xml:
name: Order
Category:
title: Pet category
description: A category for a pet
type: object
properties:
id:
type: integer
format: int64
name:
type: string
xml:
name: Category
User:
title: a User
description: A User who is purchasing from the pet store
type: object
properties:
id:
type: integer
format: int64
username:
type: string
firstName:
type: string
lastName:
type: string
email:
type: string
password:
type: string
phone:
type: string
userStatus:
type: integer
format: int32
description: User Status
xml:
name: User
Tag:
title: Pet Tag
description: A tag for a pet
type: object
properties:
id:
type: integer
format: int64
name:
type: string
xml:
name: Tag
Pet:
title: a Pet
description: A pet for sale in the pet store
type: object
required:
- name
- photoUrls
properties:
id:
type: integer
format: int64
category:
$ref: '#/definitions/Category'
name:
type: string
example: doggie
photoUrls:
type: array
xml:
name: photoUrl
wrapped: true
items:
type: string
tags:
type: array
xml:
name: tag
wrapped: true
items:
$ref: '#/definitions/Tag'
status:
type: string
description: pet status in the store
enum:
- available
- pending
- sold
xml:
name: Pet
ApiResponse:
title: An uploaded response
description: Describes the result of uploading an image resource
type: object
properties:
code:
type: integer
format: int32
type:
type: string
message:
type: string
#issue: https://github.com/swagger-api/swagger-codegen/issues/7980
Amount:
type: object
description: >
some description
properties:
value:
format: double
type: number
minimum: 0.01
maximum: 1000000000000000
description: >
some description
currency:
$ref: '#/definitions/Currency'
required:
- value
- currency
Currency:
type: string
pattern: '^[A-Z]{3,3}$'
description: >
some description
externalDocs:
description: Find out more about Swagger
url: 'http://swagger.io'

View File

@@ -0,0 +1,109 @@
openapi: "3.0.0"
info:
version: 1.0.0
title: Swagger Petstore
license:
name: MIT
servers:
- url: http://petstore.swagger.io/v1
paths:
/pets:
get:
summary: List all pets
operationId: listPets
tags:
- pets
parameters:
- name: limit
in: query
description: How many items to return at one time (max 100)
required: false
schema:
type: integer
format: int32
responses:
200:
description: An paged array of pets
headers:
x-next:
description: A link to the next page of responses
schema:
type: string
content:
application/json:
schema:
$ref: "#/components/schemas/Pets"
default:
description: unexpected error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
post:
summary: Create a pet
operationId: createPets
tags:
- pets
responses:
201:
description: Null response
default:
description: unexpected error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/pets/{petId}:
get:
summary: Info for a specific pet
operationId: showPetById
tags:
- pets
parameters:
- name: petId
in: path
required: true
description: The id of the pet to retrieve
schema:
type: string
responses:
200:
description: Expected response to a valid request
content:
application/json:
schema:
$ref: "#/components/schemas/Pets"
default:
description: unexpected error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
components:
schemas:
Pet:
required:
- id
- name
properties:
id:
type: integer
format: int64
name:
type: string
tag:
type: string
Pets:
type: array
items:
$ref: "#/components/schemas/Pet"
Error:
required:
- code
- message
properties:
code:
type: integer
format: int32
message:
type: string

View File

@@ -0,0 +1,96 @@
openapi: 3.0.1
info:
title: API_Test
version: v1
paths:
/WeatherForecast:
get:
tags:
- WeatherForecast
parameters:
- in: "header"
name: X-Correlation-ID
type: "string"
required: true
responses:
'200':
description: Success
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/WeatherForecast'
/leolplex:
get:
tags:
- WeatherForecast
parameters:
- in: "header"
name: X-Correlation-ID
type: "string"
required: true
responses:
'200':
description: Success
content:
application/json:
example:
- date: 2021-10-21T09:13:00.552+00:00
temperatureC: 111
temperatureF: 111
summary: Just-summary
- date: 2021-10-21T09:13:00.000+00:00
temperatureC: 222
temperatureF: 222
summary: Just-summary2
schema:
type: array
items:
$ref: '#/components/schemas/WeatherForecast'
/exampleop:
get:
responses:
"200":
description: OK
content:
application/json:
example:
id: 1
name: get food
completed: false
schema:
properties:
id:
type: integer
name:
type: string
completed:
type: boolean
completed_at:
type: string
format: date-time
nullable: true
required:
- id
- name
- completed
components:
schemas:
WeatherForecast:
type: object
properties:
date:
type: string
format: date-time
temperatureC:
type: integer
format: int32
temperatureF:
type: integer
format: int32
readOnly: true
summary:
type: string
nullable: true
additionalProperties: false

View File

@@ -1,5 +1,3 @@
using Microsoft.OpenApi.Readers;
using Newtonsoft.Json;
using System;
using System.IO;
@@ -7,15 +5,23 @@ namespace WireMock.Net.OpenApiParser.ConsoleApp
{
class Program
{
private static readonly JsonSerializerSettings Settings = new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore,
Formatting = Formatting.Indented
};
private const string Folder = "OpenApiFiles";
static void Main(string[] args)
{
Run.RunServer("petstore-openapi3.json");
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"), "http://localhost:9092/");
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"), "http://localhost:9094/");
var serverPetstore_V302_json = Run.RunServer(Path.Combine(Folder, "Swagger_Petstore_V3.0.2.json"), "http://localhost:9095/");
Console.WriteLine("Press any key to stop the servers");
Console.ReadKey();
serverOpenAPIExamples.Stop();
serverPetstore_V2_json.Stop();
serverPetstore_V2_yaml.Stop();
serverPetstore_V300_yaml.Stop();
serverPetstore_V302_json.Stop();
//IWireMockOpenApiParser parser = new WireMockOpenApiParser();

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using WireMock.Admin.Mappings;
@@ -13,14 +13,12 @@ namespace WireMock.Net.OpenApiParser.ConsoleApp
{
public static class Run
{
public static void RunServer(string path)
public static WireMockServer RunServer(string path, string url)
{
string url1 = "http://localhost:9091/";
var server = WireMockServer.Start(new WireMockServerSettings
{
AllowCSharpCodeMatcher = true,
Urls = new[] { url1 },
Urls = new[] { url },
StartAdminInterface = true,
ReadStaticMappings = false,
WatchStaticMappings = false,
@@ -38,9 +36,7 @@ namespace WireMock.Net.OpenApiParser.ConsoleApp
server.WithMappingFromOpenApiFile(path, settings, out var diag);
Console.WriteLine("Press any key to stop the server");
System.Console.ReadKey();
server.Stop();
return server;
}
public static void RunServer(IEnumerable<MappingModel> mappings)

View File

@@ -15,6 +15,21 @@
<None Update="infura.yaml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="OpenApiFiles\openAPIExamples.yaml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="OpenApiFiles\Swagger_Petstore_V2.0.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="OpenApiFiles\Swagger_Petstore_V2.0.yaml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="OpenApiFiles\Swagger_Petstore_V3.0.0.yaml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="OpenApiFiles\Swagger_Petstore_V3.0.2.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="petstore-openapi3.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>

View File

@@ -1,4 +1,5 @@
using System;
using System;
using WireMock.Models;
namespace WireMock.Admin.Mappings
{
@@ -13,6 +14,11 @@ namespace WireMock.Admin.Mappings
/// </summary>
public Guid? Guid { get; set; }
/// <summary>
/// Gets or sets the TimeSettings when which this mapping should be used.
/// </summary>
public TimeSettingsModel TimeSettings { get; set; }
/// <summary>
/// The unique title.
/// </summary>

View File

@@ -0,0 +1,26 @@
using System;
namespace WireMock.Models
{
/// <summary>
/// TimeSettingsModel: Start, End and TTL
/// </summary>
[FluentBuilder.AutoGenerateBuilder]
public class TimeSettingsModel
{
/// <summary>
/// Gets or sets the DateTime from which this mapping should be used. In case this is not defined, it's used (default behavior).
/// </summary>
public DateTime? Start { get; set; }
/// <summary>
/// Gets or sets the DateTime from until this mapping should be used. In case this is not defined, it's used forever (default behavior).
/// </summary>
public DateTime? End { get; set; }
/// <summary>
/// Gets or sets the TTL (Time To Live) in seconds for this mapping. In case this is not defined, it's used (default behavior).
/// </summary>
public int? TTL { get; set; }
}
}

View File

@@ -0,0 +1,25 @@
using System;
namespace WireMock.Models
{
/// <summary>
/// TimeSettings: Start, End and TTL
/// </summary>
public interface ITimeSettings
{
/// <summary>
/// Gets or sets the DateTime from which this mapping should be used. In case this is not defined, it's used (default behavior).
/// </summary>
DateTime? Start { get; set; }
/// <summary>
/// Gets or sets the DateTime from until this mapping should be used. In case this is not defined, it's used forever (default behavior).
/// </summary>
DateTime? End { get; set; }
/// <summary>
/// Gets or sets the TTL (Time To Live) in seconds for this mapping. In case this is not defined, it's used (default behavior).
/// </summary>
int? TTL { get; set; }
}
}

View File

@@ -26,22 +26,22 @@ namespace WireMock.Net.OpenApiParser.Mappers
_exampleValueGenerator = new ExampleValueGenerator(settings);
}
public IEnumerable<MappingModel> ToMappingModels(OpenApiPaths paths)
public IEnumerable<MappingModel> ToMappingModels(OpenApiPaths paths, IList<OpenApiServer> servers)
{
return paths.Select(p => MapPath(p.Key, p.Value)).SelectMany(x => x);
return paths.Select(p => MapPath(p.Key, p.Value, servers)).SelectMany(x => x);
}
private IEnumerable<MappingModel> MapPaths(OpenApiPaths paths)
private IEnumerable<MappingModel> MapPaths(OpenApiPaths paths, IList<OpenApiServer> servers)
{
return paths.Select(p => MapPath(p.Key, p.Value)).SelectMany(x => x);
return paths.Select(p => MapPath(p.Key, p.Value, servers)).SelectMany(x => x);
}
private IEnumerable<MappingModel> MapPath(string path, OpenApiPathItem pathItem)
private IEnumerable<MappingModel> MapPath(string path, OpenApiPathItem pathItem, IList<OpenApiServer> servers)
{
return pathItem.Operations.Select(o => MapOperationToMappingModel(path, o.Key.ToString().ToUpperInvariant(), o.Value));
return pathItem.Operations.Select(o => MapOperationToMappingModel(path, o.Key.ToString().ToUpperInvariant(), o.Value, servers));
}
private MappingModel MapOperationToMappingModel(string path, string httpMethod, OpenApiOperation operation)
private MappingModel MapOperationToMappingModel(string path, string httpMethod, OpenApiOperation operation, IList<OpenApiServer> servers)
{
var queryParameters = operation.Parameters.Where(p => p.In == ParameterLocation.Query);
var pathParameters = operation.Parameters.Where(p => p.In == ParameterLocation.Path);
@@ -52,8 +52,11 @@ namespace WireMock.Net.OpenApiParser.Mappers
TryGetContent(response.Value?.Content, out OpenApiMediaType responseContent, out string responseContentType);
var responseSchema = response.Value?.Content?.FirstOrDefault().Value?.Schema;
var responseExample = responseContent?.Example;
var responseSchemaExample = responseContent?.Schema?.Example;
var body = responseExample != null ? MapOpenApiAnyToJToken(responseExample) : MapSchemaToObject(responseSchema);
var body = responseExample != null ? MapOpenApiAnyToJToken(responseExample) :
responseSchemaExample != null ? MapOpenApiAnyToJToken(responseSchemaExample) :
MapSchemaToObject(responseSchema);
if (!int.TryParse(response.Key, out var httpStatusCode))
{
@@ -66,7 +69,7 @@ namespace WireMock.Net.OpenApiParser.Mappers
Request = new RequestModel
{
Methods = new[] { httpMethod },
Path = MapPathWithParameters(path, pathParameters),
Path = MapBasePath(servers) + MapPathWithParameters(path, pathParameters),
Params = MapQueryParameters(queryParameters),
Headers = MapRequestHeaders(headers)
},
@@ -141,6 +144,11 @@ namespace WireMock.Net.OpenApiParser.Mappers
}
}
if (schema.AllOf.Count > 0)
{
jArray.Add(MapSchemaAllOfToObject(schema));
}
return jArray;
case SchemaType.Boolean:
@@ -153,25 +161,16 @@ namespace WireMock.Net.OpenApiParser.Mappers
var propertyAsJObject = new JObject();
foreach (var schemaProperty in schema.Properties)
{
string propertyName = schemaProperty.Key;
var openApiSchema = schemaProperty.Value;
if (openApiSchema.GetSchemaType() == SchemaType.Object || openApiSchema.GetSchemaType() == SchemaType.Array)
propertyAsJObject.Add(MapPropertyAsJObject(schemaProperty.Value, schemaProperty.Key));
}
if (schema.AllOf.Count > 0)
{
foreach (var property in schema.AllOf)
{
var mapped = MapSchemaToObject(schemaProperty.Value, schemaProperty.Key);
if (mapped is JProperty jp)
foreach (var item in property.Properties)
{
propertyAsJObject.Add(jp);
propertyAsJObject.Add(MapPropertyAsJObject(item.Value, item.Key));
}
else
{
propertyAsJObject.Add(new JProperty(schemaProperty.Key, mapped));
}
}
else
{
bool propertyIsNullable = openApiSchema.Nullable || (openApiSchema.TryGetXNullable(out bool x) && x);
propertyAsJObject.Add(new JProperty(propertyName, _exampleValueGenerator.GetExampleValue(openApiSchema)));
}
}
@@ -182,6 +181,39 @@ namespace WireMock.Net.OpenApiParser.Mappers
}
}
private JObject MapSchemaAllOfToObject(OpenApiSchema schema)
{
var arrayItem = new JObject();
foreach (var property in schema.AllOf)
{
foreach (var item in property.Properties)
{
arrayItem.Add(MapPropertyAsJObject(item.Value, item.Key));
}
}
return arrayItem;
}
private object MapPropertyAsJObject(OpenApiSchema openApiSchema, string key)
{
if (openApiSchema.GetSchemaType() == SchemaType.Object || openApiSchema.GetSchemaType() == SchemaType.Array)
{
var mapped = MapSchemaToObject(openApiSchema, key);
if (mapped is JProperty jp)
{
return jp;
}
else
{
return new JProperty(key, mapped);
}
}
else
{
bool propertyIsNullable = openApiSchema.Nullable || (openApiSchema.TryGetXNullable(out bool x) && x);
return new JProperty(key, _exampleValueGenerator.GetExampleValue(openApiSchema));
}
}
private string MapPathWithParameters(string path, IEnumerable<OpenApiParameter> parameters)
{
if (parameters == null)
@@ -198,6 +230,22 @@ namespace WireMock.Net.OpenApiParser.Mappers
return newPath;
}
private string MapBasePath(IList<OpenApiServer> servers)
{
if (servers == null || servers.Count == 0)
{
return string.Empty;
}
OpenApiServer server = servers.First();
Uri uriResult;
if (Uri.TryCreate(server.Url, UriKind.RelativeOrAbsolute, out uriResult))
{
return uriResult.IsAbsoluteUri ? uriResult.AbsolutePath : uriResult.ToString();
}
return string.Empty;
}
private JToken MapOpenApiAnyToJToken(IOpenApiAny any)
{
if (any == null)
@@ -209,7 +257,14 @@ namespace WireMock.Net.OpenApiParser.Mappers
var writer = new OpenApiJsonWriter(outputString);
any.Write(writer, OpenApiSpecVersion.OpenApi3_0);
return JObject.Parse(outputString.ToString());
if (any.AnyType == AnyType.Array)
{
return JArray.Parse(outputString.ToString());
}
else
{
return JObject.Parse(outputString.ToString());
}
}
private IDictionary<string, object> MapHeaders(string responseContentType, IDictionary<string, OpenApiHeader> headers)

View File

@@ -0,0 +1,52 @@
using System;
namespace WireMock.Net.OpenApiParser.Settings
{
/// <summary>
/// A interface defining the example values to use for the different types.
/// </summary>
public interface IWireMockOpenApiParserExampleValues
{
/// <summary>
/// An example value for a Boolean.
/// </summary>
bool Boolean { get; set; }
/// <summary>
/// An example value for an Integer.
/// </summary>
int Integer { get; set; }
/// <summary>
/// An example value for a Float.
/// </summary>
float Float { get; set; }
/// <summary>
/// An example value for a Double.
/// </summary>
double Double { get; set; }
/// <summary>
/// An example value for a Date.
/// </summary>
Func<DateTime> Date { get; set; }
/// <summary>
/// An example value for a DateTime.
/// </summary>
Func<DateTime> DateTime { get; set; }
/// <summary>
/// An example value for Bytes.
/// </summary>
byte[] Bytes { get; set; }
/// <summary>
/// An example value for a Object.
/// </summary>
object Object { get; set; }
/// <summary>
/// An example value for a String.
/// </summary>
string String { get; set; }
}
}

View File

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

View File

@@ -5,26 +5,25 @@ namespace WireMock.Net.OpenApiParser.Settings
/// <summary>
/// A class defining the example values to use for the different types.
/// </summary>
public class WireMockOpenApiParserExampleValues
public class WireMockOpenApiParserExampleValues : IWireMockOpenApiParserExampleValues
{
#pragma warning disable 1591
/// <inheritdoc />
public bool Boolean { get; set; } = true;
/// <inheritdoc />
public int Integer { get; set; } = 42;
/// <inheritdoc />
public float Float { get; set; } = 4.2f;
/// <inheritdoc />
public double Double { get; set; } = 4.2d;
/// <inheritdoc />
public Func<DateTime> Date { get; set; } = () => System.DateTime.UtcNow.Date;
/// <inheritdoc />
public Func<DateTime> DateTime { get; set; } = () => System.DateTime.UtcNow;
/// <inheritdoc />
public byte[] Bytes { get; set; } = { 48, 49, 50 };
/// <inheritdoc />
public object Object { get; set; } = "example-object";
/// <inheritdoc />
public string String { get; set; } = "example-string";
#pragma warning restore 1591
}
}

View File

@@ -25,6 +25,11 @@ namespace WireMock.Net.OpenApiParser.Settings
/// <summary>
/// The example values to use
/// </summary>
public WireMockOpenApiParserExampleValues ExampleValues { get; } = new WireMockOpenApiParserExampleValues();
public IWireMockOpenApiParserExampleValues ExampleValues { get; set; } = new WireMockOpenApiParserExampleValues();
/// <summary>
/// Are examples generated dynamically?
/// </summary>
public bool DynamicExamples { get; set; } = false;
}
}

View File

@@ -13,6 +13,14 @@ namespace WireMock.Net.OpenApiParser.Utils
public ExampleValueGenerator(WireMockOpenApiParserSettings settings)
{
_settings = settings ?? throw new ArgumentNullException(nameof(settings));
if (_settings.DynamicExamples)
{
_settings.ExampleValues = new WireMockOpenApiParserDynamicExampleValues();
}
else
{
_settings.ExampleValues = new WireMockOpenApiParserExampleValues();
}
}
public object GetExampleValue(OpenApiSchema schema)

View File

@@ -25,6 +25,7 @@
<PackageReference Include="RamlToOpenApiConverter" Version="0.1.1" />
<PackageReference Include="JetBrains.Annotations" Version="2020.1.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
<PackageReference Include="RandomDataGenerator.Net" Version="1.0.13" />
<PackageReference Include="Stef.Validation" Version="0.0.3" />
</ItemGroup>

View File

@@ -62,7 +62,7 @@ namespace WireMock.Net.OpenApiParser
[PublicAPI]
public IEnumerable<MappingModel> FromDocument(OpenApiDocument openApiDocument, WireMockOpenApiParserSettings settings = null)
{
return new OpenApiPathsMapper(settings).ToMappingModels(openApiDocument.Paths);
return new OpenApiPathsMapper(settings).ToMappingModels(openApiDocument.Paths, openApiDocument.Servers);
}
}
}

View File

@@ -1,4 +1,5 @@
using System.Linq;
using System.Linq;
using System.Reflection;
using JetBrains.Annotations;
using WireMock.Logging;
using WireMock.Server;
@@ -12,6 +13,8 @@ namespace WireMock.Net.StandAlone
/// </summary>
public static class StandAloneApp
{
private static readonly string Version = typeof(StandAloneApp).GetTypeInfo().Assembly.GetName().Version.ToString();
/// <summary>
/// Start WireMock.Net standalone Server based on the IWireMockServerSettings.
/// </summary>
@@ -23,7 +26,8 @@ namespace WireMock.Net.StandAlone
var server = WireMockServer.Start(settings);
settings.Logger?.Info("WireMock.Net server listening at {0}", string.Join(",", server.Urls));
settings.Logger?.Info("Version [{0}]", Version);
settings.Logger?.Info("Server listening at {0}", string.Join(",", server.Urls));
return server;
}
@@ -40,7 +44,8 @@ namespace WireMock.Net.StandAlone
if (WireMockServerSettingsParser.TryParseArguments(args, out var settings, logger))
{
settings.Logger?.Debug("WireMock.Net server arguments [{0}]", string.Join(", ", args.Select(a => $"'{a}'")));
settings.Logger?.Info("Version [{0}]", Version);
settings.Logger?.Debug("Server arguments [{0}]", string.Join(", ", args.Select(a => $"'{a}'")));
return Start(settings);
}
@@ -61,7 +66,8 @@ namespace WireMock.Net.StandAlone
if (WireMockServerSettingsParser.TryParseArguments(args, out var settings, logger))
{
settings.Logger?.Debug("WireMock.Net server arguments [{0}]", string.Join(", ", args.Select(a => $"'{a}'")));
settings.Logger?.Info("Version [{0}]", Version);
settings.Logger?.Debug("Server arguments [{0}]", string.Join(", ", args.Select(a => $"'{a}'")));
server = Start(settings);
return true;

View File

@@ -1,23 +1,24 @@
using System.Collections.Generic;
using System.Linq;
using AnyOfTypes;
using JetBrains.Annotations;
using WireMock.Models;
namespace WireMock.Extensions
{
internal static class AnyOfExtensions
{
public static string GetPattern(this AnyOf<string, StringPattern> value)
public static string GetPattern([NotNull] this AnyOf<string, StringPattern> value)
{
return value.IsFirst ? value.First : value.Second.Pattern;
}
public static AnyOf<string, StringPattern>[] ToAnyOfPatterns(this IEnumerable<string> patterns)
public static AnyOf<string, StringPattern>[] ToAnyOfPatterns([NotNull] this IEnumerable<string> patterns)
{
return patterns.Select(p => p.ToAnyOfPattern()).ToArray();
}
public static AnyOf<string, StringPattern> ToAnyOfPattern(this string pattern)
public static AnyOf<string, StringPattern> ToAnyOfPattern([CanBeNull] this string pattern)
{
return new AnyOf<string, StringPattern>(pattern);
}

View File

@@ -0,0 +1,35 @@
using System;
using JetBrains.Annotations;
using WireMock.Models;
namespace WireMock.Extensions
{
internal static class TimeSettingsExtensions
{
public static bool IsValid([CanBeNull] this ITimeSettings settings)
{
if (settings == null)
{
return true;
}
var now = DateTime.Now;
var start = settings.Start != null ? settings.Start.Value : now;
DateTime end;
if (settings.End != null)
{
end = settings.End.Value;
}
else if (settings.TTL != null)
{
end = start.AddSeconds(settings.TTL.Value);
}
else
{
end = DateTime.MaxValue;
}
return now >= start && now <= end;
}
}
}

View File

@@ -1,4 +1,4 @@
using JetBrains.Annotations;
using JetBrains.Annotations;
using System;
using System.Threading.Tasks;
using WireMock.Matchers.Request;
@@ -18,6 +18,11 @@ namespace WireMock
/// </summary>
Guid Guid { get; }
/// <summary>
/// Gets the TimeSettings (Start, End and TTL).
/// </summary>
ITimeSettings TimeSettings { get; }
/// <summary>
/// Gets the unique title.
/// </summary>

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Threading.Tasks;
using JetBrains.Annotations;
using WireMock.Matchers.Request;
@@ -13,51 +13,54 @@ namespace WireMock
/// </summary>
public class Mapping : IMapping
{
/// <inheritdoc cref="IMapping.Guid" />
/// <inheritdoc />
public Guid Guid { get; }
/// <inheritdoc cref="IMapping.Title" />
/// <inheritdoc />
public string Title { get; }
/// <inheritdoc cref="IMapping.Path" />
/// <inheritdoc />
public string Path { get; set; }
/// <inheritdoc cref="IMapping.Priority" />
/// <inheritdoc />
public int Priority { get; }
/// <inheritdoc cref="IMapping.Scenario" />
/// <inheritdoc />
public string Scenario { get; }
/// <inheritdoc cref="IMapping.ExecutionConditionState" />
/// <inheritdoc />
public string ExecutionConditionState { get; }
/// <inheritdoc cref="IMapping.NextState" />
/// <inheritdoc />
public string NextState { get; }
/// <inheritdoc cref="IMapping.StateTimes" />
/// <inheritdoc />
public int? StateTimes { get; }
/// <inheritdoc cref="IMapping.RequestMatcher" />
/// <inheritdoc />
public IRequestMatcher RequestMatcher { get; }
/// <inheritdoc cref="IMapping.Provider" />
/// <inheritdoc />
public IResponseProvider Provider { get; }
/// <inheritdoc cref="IMapping.Settings" />
/// <inheritdoc />
public IWireMockServerSettings Settings { get; }
/// <inheritdoc cref="IMapping.IsStartState" />
/// <inheritdoc />
public bool IsStartState => Scenario == null || Scenario != null && NextState != null && ExecutionConditionState == null;
/// <inheritdoc cref="IMapping.IsAdminInterface" />
/// <inheritdoc />
public bool IsAdminInterface => Provider is DynamicResponseProvider || Provider is DynamicAsyncResponseProvider || Provider is ProxyAsyncResponseProvider;
/// <inheritdoc cref="IMapping.LogMapping" />
/// <inheritdoc />
public bool LogMapping => !(Provider is DynamicResponseProvider || Provider is DynamicAsyncResponseProvider);
/// <inheritdoc cref="IMapping.Webhooks" />
/// <inheritdoc />
public IWebhook[] Webhooks { get; }
/// <inheritdoc />
public ITimeSettings TimeSettings { get; }
/// <summary>
/// Initializes a new instance of the <see cref="Mapping"/> class.
/// </summary>
@@ -73,6 +76,7 @@ namespace WireMock
/// <param name="nextState">The next state which will occur after the current mapping execution. [Optional]</param>
/// <param name="stateTimes">Only when the current state is executed this number, the next state which will occur. [Optional]</param>
/// <param name="webhooks">The Webhooks. [Optional]</param>
/// <param name="timeSettings">The TimeSettings. [Optional]</param>
public Mapping(
Guid guid,
[CanBeNull] string title,
@@ -85,7 +89,8 @@ namespace WireMock
[CanBeNull] string executionConditionState,
[CanBeNull] string nextState,
[CanBeNull] int? stateTimes,
[CanBeNull] IWebhook[] webhooks)
[CanBeNull] IWebhook[] webhooks,
[CanBeNull] ITimeSettings timeSettings)
{
Guid = guid;
Title = title;
@@ -99,6 +104,7 @@ namespace WireMock
NextState = nextState;
StateTimes = stateTimes;
Webhooks = webhooks;
TimeSettings = timeSettings;
}
/// <inheritdoc cref="IMapping.ProvideResponseAsync" />

View File

@@ -0,0 +1,89 @@
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using Newtonsoft.Json.Linq;
namespace WireMock.Matchers
{
/// <summary>
/// Generic AbstractJsonPartialMatcher
/// </summary>
public abstract class AbstractJsonPartialMatcher : JsonMatcher
{
/// <summary>
/// Initializes a new instance of the <see cref="AbstractJsonPartialMatcher"/> class.
/// </summary>
/// <param name="value">The string value to check for equality.</param>
/// <param name="ignoreCase">Ignore the case from the PropertyName and PropertyValue (string only).</param>
/// <param name="throwException">Throw an exception when the internal matching fails because of invalid input.</param>
protected AbstractJsonPartialMatcher([NotNull] string value, bool ignoreCase = false, bool throwException = false)
: base(value, ignoreCase, throwException)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="AbstractJsonPartialMatcher"/> class.
/// </summary>
/// <param name="value">The object value to check for equality.</param>
/// <param name="ignoreCase">Ignore the case from the PropertyName and PropertyValue (string only).</param>
/// <param name="throwException">Throw an exception when the internal matching fails because of invalid input.</param>
protected AbstractJsonPartialMatcher([NotNull] object value, bool ignoreCase = false, bool throwException = false)
: base(value, ignoreCase, throwException)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="AbstractJsonPartialMatcher"/> class.
/// </summary>
/// <param name="matchBehaviour">The match behaviour.</param>
/// <param name="value">The value to check for equality.</param>
/// <param name="ignoreCase">Ignore the case from the PropertyName and PropertyValue (string only).</param>
/// <param name="throwException">Throw an exception when the internal matching fails because of invalid input.</param>
protected AbstractJsonPartialMatcher(MatchBehaviour matchBehaviour, [NotNull] object value, bool ignoreCase = false, bool throwException = false)
: base(matchBehaviour, value, ignoreCase, throwException)
{
}
/// <inheritdoc />
protected override bool IsMatch(JToken value, JToken input)
{
if (value == null || value == input)
{
return true;
}
if (input == null || value.Type != input.Type)
{
return false;
}
switch (value.Type)
{
case JTokenType.Object:
var nestedValues = value.ToObject<Dictionary<string, JToken>>();
return nestedValues?.Any() != true ||
nestedValues.All(pair => IsMatch(pair.Value, input.SelectToken(pair.Key)));
case JTokenType.Array:
var valuesArray = value.ToObject<JToken[]>();
var tokenArray = input.ToObject<JToken[]>();
if (valuesArray?.Any() != true)
{
return true;
}
return tokenArray?.Any() == true &&
valuesArray.All(subFilter => tokenArray.Any(subToken => IsMatch(subFilter, subToken)));
default:
return IsMatch(value.ToString(), input.ToString());
}
}
/// <summary>
/// Check if two strings are a match (matching can be done exact or wildcard)
/// </summary>
protected abstract bool IsMatch(string value, string input);
}
}

View File

@@ -1,87 +1,38 @@
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using Newtonsoft.Json.Linq;
namespace WireMock.Matchers
{
/// <summary>
/// JsonPartialMatcher
/// </summary>
public class JsonPartialMatcher : JsonMatcher
public class JsonPartialMatcher : AbstractJsonPartialMatcher
{
/// <inheritdoc cref="IMatcher.Name"/>
public override string Name => "JsonPartialMatcher";
/// <inheritdoc />
public override string Name => nameof(JsonPartialMatcher);
/// <summary>
/// Initializes a new instance of the <see cref="JsonPartialMatcher"/> class.
/// </summary>
/// <param name="value">The string value to check for equality.</param>
/// <param name="ignoreCase">Ignore the case from the PropertyName and PropertyValue (string only).</param>
/// <param name="throwException">Throw an exception when the internal matching fails because of invalid input.</param>
public JsonPartialMatcher([NotNull] string value, bool ignoreCase = false, bool throwException = false)
/// <inheritdoc />
public JsonPartialMatcher([NotNull] string value, bool ignoreCase = false, bool throwException = false)
: base(value, ignoreCase, throwException)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="JsonPartialMatcher"/> class.
/// </summary>
/// <param name="value">The object value to check for equality.</param>
/// <param name="ignoreCase">Ignore the case from the PropertyName and PropertyValue (string only).</param>
/// <param name="throwException">Throw an exception when the internal matching fails because of invalid input.</param>
public JsonPartialMatcher([NotNull] object value, bool ignoreCase = false, bool throwException = false)
/// <inheritdoc />
public JsonPartialMatcher([NotNull] object value, bool ignoreCase = false, bool throwException = false)
: base(value, ignoreCase, throwException)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="JsonPartialMatcher"/> class.
/// </summary>
/// <param name="matchBehaviour">The match behaviour.</param>
/// <param name="value">The value to check for equality.</param>
/// <param name="ignoreCase">Ignore the case from the PropertyName and PropertyValue (string only).</param>
/// <param name="throwException">Throw an exception when the internal matching fails because of invalid input.</param>
public JsonPartialMatcher(MatchBehaviour matchBehaviour, [NotNull] object value, bool ignoreCase = false, bool throwException = false)
/// <inheritdoc />
public JsonPartialMatcher(MatchBehaviour matchBehaviour, [NotNull] object value, bool ignoreCase = false, bool throwException = false)
: base(matchBehaviour, value, ignoreCase, throwException)
{
}
/// <inheritdoc />
protected override bool IsMatch(JToken value, JToken input)
protected override bool IsMatch(string value, string input)
{
if (value == null || value == input)
{
return true;
}
if (input == null || value.Type != input.Type)
{
return false;
}
switch (value.Type)
{
case JTokenType.Object:
var nestedValues = value.ToObject<Dictionary<string, JToken>>();
return nestedValues?.Any() != true ||
nestedValues.All(pair => IsMatch(pair.Value, input.SelectToken(pair.Key)));
case JTokenType.Array:
var valuesArray = value.ToObject<JToken[]>();
var tokenArray = input.ToObject<JToken[]>();
if (valuesArray?.Any() != true)
{
return true;
}
return tokenArray?.Any() == true &&
valuesArray.All(subFilter => tokenArray.Any(subToken => IsMatch(subFilter, subToken)));
default:
return value.ToString() == input.ToString();
}
var exactStringMatcher = new ExactMatcher(MatchBehaviour.AcceptOnMatch, ThrowException, value);
return MatchScores.IsPerfect(exactStringMatcher.IsMatch(input));
}
}
}

View File

@@ -0,0 +1,38 @@
using JetBrains.Annotations;
namespace WireMock.Matchers
{
/// <summary>
/// JsonPartialWildCardMatcher
/// </summary>
public class JsonPartialWildcardMatcher : AbstractJsonPartialMatcher
{
/// <inheritdoc />
public override string Name => nameof(JsonPartialWildcardMatcher);
/// <inheritdoc />
public JsonPartialWildcardMatcher([NotNull] string value, bool ignoreCase = false, bool throwException = false)
: base(value, ignoreCase, throwException)
{
}
/// <inheritdoc />
public JsonPartialWildcardMatcher([NotNull] object value, bool ignoreCase = false, bool throwException = false)
: base(value, ignoreCase, throwException)
{
}
/// <inheritdoc />
public JsonPartialWildcardMatcher(MatchBehaviour matchBehaviour, [NotNull] object value, bool ignoreCase = false, bool throwException = false)
: base(matchBehaviour, value, ignoreCase, throwException)
{
}
/// <inheritdoc />
protected override bool IsMatch(string value, string input)
{
var wildcardStringMatcher = new WildcardMatcher(MatchBehaviour.AcceptOnMatch, value, IgnoreCase);
return MatchScores.IsPerfect(wildcardStringMatcher.IsMatch(input));
}
}
}

View File

@@ -0,0 +1,19 @@
using System;
namespace WireMock.Models
{
/// <summary>
/// TimeSettingsModel: Start, End and TTL
/// </summary>
public class TimeSettings : ITimeSettings
{
/// <inheritdoc />
public DateTime? Start { get; set; }
/// <inheritdoc />
public DateTime? End { get; set; }
/// <inheritdoc />
public int? TTL { get; set; }
}
}

View File

@@ -1,4 +1,4 @@
#if USE_ASPNETCORE
#if USE_ASPNETCORE
using System;
using System.Collections.Generic;
using System.Linq;
@@ -118,17 +118,17 @@ namespace WireMock.Owin
});
#if NETSTANDARD1_3
_logger.Info("WireMock.Net server using netstandard1.3");
_logger.Info("Server using netstandard1.3");
#elif NETSTANDARD2_0
_logger.Info("WireMock.Net server using netstandard2.0");
_logger.Info("Server using netstandard2.0");
#elif NETSTANDARD2_1
_logger.Info("WireMock.Net server using netstandard2.1");
_logger.Info("Server using netstandard2.1");
#elif NETCOREAPP3_1
_logger.Info("WireMock.Net server using .NET Core 3.1");
_logger.Info("Server using .NET Core 3.1");
#elif NET5_0
_logger.Info("WireMock.Net server using .NET 5.0");
_logger.Info("Server using .NET 5.0");
#elif NET46
_logger.Info("WireMock.Net server using .NET Framework 4.6.1 or higher");
_logger.Info("Server using .NET Framework 4.6.1 or higher");
#endif
#if NETSTANDARD1_3

View File

@@ -1,6 +1,7 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using WireMock.Extensions;
using WireMock.Validation;
namespace WireMock.Owin
@@ -19,7 +20,8 @@ namespace WireMock.Owin
public (MappingMatcherResult Match, MappingMatcherResult Partial) FindBestMatch(RequestMessage request)
{
var mappings = new List<MappingMatcherResult>();
foreach (var mapping in _options.Mappings.Values)
foreach (var mapping in _options.Mappings.Values.Where(m => m.TimeSettings.IsValid()))
{
try
{

View File

@@ -1,4 +1,4 @@
#if !USE_ASPNETCORE
#if !USE_ASPNETCORE
using JetBrains.Annotations;
using Microsoft.Owin.Hosting;
using Owin;
@@ -63,9 +63,9 @@ namespace WireMock.Owin
private void StartServers()
{
#if NET46
_logger.Info("WireMock.Net server using .net 4.6.1 or higher");
_logger.Info("Server using .net 4.6.1 or higher");
#else
_logger.Info("WireMock.Net server using .net 4.5.x");
_logger.Info("Server using .net 4.5.x");
#endif
var servers = new List<IDisposable>();

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
@@ -21,8 +21,7 @@ namespace WireMock.Proxy
public ProxyHelper([NotNull] IWireMockServerSettings settings)
{
Check.NotNull(settings, nameof(settings));
_settings = settings;
_settings = Check.NotNull(settings, nameof(settings));
}
public async Task<(ResponseMessage Message, IMapping Mapping)> SendAsync(
@@ -105,7 +104,7 @@ namespace WireMock.Proxy
var response = Response.Create(responseMessage);
return new Mapping(Guid.NewGuid(), string.Empty, null, _settings, request, response, 0, null, null, null, null, null);
return new Mapping(Guid.NewGuid(), string.Empty, null, _settings, request, response, 0, null, null, null, null, null, null);
}
}
}

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using WireMock.Admin.Mappings;
using WireMock.Matchers.Request;
using WireMock.Models;
using WireMock.RequestBuilders;
using WireMock.ResponseBuilders;
using WireMock.Settings;
@@ -36,6 +37,7 @@ namespace WireMock.Serialization
var mappingModel = new MappingModel
{
Guid = mapping.Guid,
TimeSettings = TimeSettingsMapper.Map(mapping.TimeSettings),
Title = mapping.Title,
Priority = mapping.Priority != 0 ? mapping.Priority : (int?)null,
Scenario = mapping.Scenario,

View File

@@ -44,7 +44,7 @@ namespace WireMock.Serialization
switch (matcherName)
{
case "NotNullOrEmptyMatcher":
case nameof(NotNullOrEmptyMatcher):
return new NotNullOrEmptyMatcher(matchBehaviour);
case "CSharpCodeMatcher":
@@ -55,42 +55,46 @@ namespace WireMock.Serialization
throw new NotSupportedException("It's not allowed to use the 'CSharpCodeMatcher' because IWireMockServerSettings.AllowCSharpCodeMatcher is not set to 'true'.");
case "LinqMatcher":
case nameof(LinqMatcher):
return new LinqMatcher(matchBehaviour, throwExceptionWhenMatcherFails, stringPatterns);
case "ExactMatcher":
case nameof(ExactMatcher):
return new ExactMatcher(matchBehaviour, throwExceptionWhenMatcherFails, stringPatterns);
case "ExactObjectMatcher":
case nameof(ExactObjectMatcher):
return CreateExactObjectMatcher(matchBehaviour, stringPatterns[0], throwExceptionWhenMatcherFails);
case "RegexMatcher":
case nameof(RegexMatcher):
return new RegexMatcher(matchBehaviour, stringPatterns, ignoreCase, throwExceptionWhenMatcherFails);
case "JsonMatcher":
case nameof(JsonMatcher):
object valueForJsonMatcher = matcher.Pattern ?? matcher.Patterns;
return new JsonMatcher(matchBehaviour, valueForJsonMatcher, ignoreCase, throwExceptionWhenMatcherFails);
case "JsonPartialMatcher":
case nameof(JsonPartialMatcher):
object valueForJsonPartialMatcher = matcher.Pattern ?? matcher.Patterns;
return new JsonPartialMatcher(matchBehaviour, valueForJsonPartialMatcher, ignoreCase, throwExceptionWhenMatcherFails);
case "JsonPathMatcher":
case nameof(JsonPartialWildcardMatcher):
object valueForJsonPartialWilcardMatcher = matcher.Pattern ?? matcher.Patterns;
return new JsonPartialWildcardMatcher(matchBehaviour, valueForJsonPartialWilcardMatcher, ignoreCase, throwExceptionWhenMatcherFails);
case nameof(JsonPathMatcher):
return new JsonPathMatcher(matchBehaviour, throwExceptionWhenMatcherFails, stringPatterns);
case "JmesPathMatcher":
case nameof(JmesPathMatcher):
return new JmesPathMatcher(matchBehaviour, throwExceptionWhenMatcherFails, stringPatterns);
case "XPathMatcher":
case nameof(XPathMatcher):
return new XPathMatcher(matchBehaviour, throwExceptionWhenMatcherFails, stringPatterns);
case "WildcardMatcher":
case nameof(WildcardMatcher):
return new WildcardMatcher(matchBehaviour, stringPatterns, ignoreCase, throwExceptionWhenMatcherFails);
case "ContentTypeMatcher":
case nameof(ContentTypeMatcher):
return new ContentTypeMatcher(matchBehaviour, stringPatterns, ignoreCase, throwExceptionWhenMatcherFails);
case "SimMetricsMatcher":
case nameof(SimMetricsMatcher):
SimMetricType type = SimMetricType.Levenstein;
if (!string.IsNullOrEmpty(matcherType) && !Enum.TryParse(matcherType, out type))
{

View File

@@ -0,0 +1,27 @@
using WireMock.Models;
namespace WireMock.Serialization
{
internal static class TimeSettingsMapper
{
public static TimeSettingsModel Map(ITimeSettings settings)
{
return settings != null ? new TimeSettingsModel
{
Start = settings.Start,
End = settings.End,
TTL = settings.TTL
} : null;
}
public static ITimeSettings Map(TimeSettingsModel settings)
{
return settings != null ? new TimeSettings
{
Start = settings.Start,
End = settings.End,
TTL = settings.TTL
} : null;
}
}
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using JetBrains.Annotations;
using WireMock.Models;
@@ -24,6 +24,13 @@ namespace WireMock.Server
/// <returns>The <see cref="IRespondWithAProvider"/>.</returns>
IRespondWithAProvider WithGuid(Guid guid);
/// <summary>
/// Define the TimeSettings for this mapping.
/// </summary>
/// <param name="timeSettings">The TimeSettings.</param>
/// <returns>The <see cref="IRespondWithAProvider"/>.</returns>
IRespondWithAProvider WithTimeSettings(ITimeSettings timeSettings);
/// <summary>
/// Define a unique title for this mapping.
/// </summary>

View File

@@ -1,4 +1,4 @@
// This source file is based on mock4net by Alexandre Victoor which is licensed under the Apache 2.0 License.
// This source file is based on mock4net by Alexandre Victoor which is licensed under the Apache 2.0 License.
// For more details see 'mock4net/LICENSE.txt' and 'mock4net/readme.md' in this project root.
using System;
using System.Collections.Generic;
@@ -34,6 +34,8 @@ namespace WireMock.Server
public IWebhook[] Webhooks { get; private set; }
public ITimeSettings TimeSettings { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="RespondWithAProvider"/> class.
/// </summary>
@@ -55,7 +57,7 @@ namespace WireMock.Server
/// <param name="provider">The provider.</param>
public void RespondWith(IResponseProvider provider)
{
_registrationCallback(new Mapping(Guid, _title, _path, _settings, _requestMatcher, provider, _priority, _scenario, _executionConditionState, _nextState, _timesInSameState, Webhooks), _saveToFile);
_registrationCallback(new Mapping(Guid, _title, _path, _settings, _requestMatcher, provider, _priority, _scenario, _executionConditionState, _nextState, _timesInSameState, Webhooks, TimeSettings), _saveToFile);
}
/// <see cref="IRespondWithAProvider.WithGuid(string)"/>
@@ -149,6 +151,16 @@ namespace WireMock.Server
return WillSetStateTo(state.ToString(), times);
}
/// <inheritdoc />
public IRespondWithAProvider WithTimeSettings(ITimeSettings timeSettings)
{
Check.NotNull(timeSettings, nameof(timeSettings));
TimeSettings = timeSettings;
return this;
}
/// <see cref="IRespondWithAProvider.WithWebhook(IWebhook[])"/>
public IRespondWithAProvider WithWebhook(params IWebhook[] webhooks)
{
@@ -207,7 +219,7 @@ namespace WireMock.Server
return this;
}
private IWebhook InitWebhook(
private static IWebhook InitWebhook(
string url,
string method,
IDictionary<string, WireMockList<string>> headers,

View File

@@ -450,6 +450,11 @@ namespace WireMock.Server
respondProvider = respondProvider.WithGuid(mappingModel.Guid.Value);
}
if (mappingModel.TimeSettings != null)
{
respondProvider = respondProvider.WithTimeSettings(TimeSettingsMapper.Map(mappingModel.TimeSettings));
}
if (path != null)
{
respondProvider = respondProvider.WithPath(path);

View File

@@ -4,7 +4,6 @@ using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using JetBrains.Annotations;
using Newtonsoft.Json;
@@ -13,7 +12,6 @@ using WireMock.Authentication;
using WireMock.Exceptions;
using WireMock.Handlers;
using WireMock.Logging;
using WireMock.Matchers;
using WireMock.Matchers.Request;
using WireMock.Owin;
using WireMock.RequestBuilders;
@@ -202,8 +200,8 @@ namespace WireMock.Server
_settings.Logger = settings.Logger ?? new WireMockNullLogger();
_settings.FileSystemHandler = settings.FileSystemHandler ?? new LocalFileSystemHandler();
_settings.Logger.Info("WireMock.Net by Stef Heyenrath (https://github.com/WireMock-Net/WireMock.Net)");
_settings.Logger.Debug("WireMock.Net server settings {0}", JsonConvert.SerializeObject(settings, Formatting.Indented));
_settings.Logger.Info("By Stef Heyenrath (https://github.com/WireMock-Net/WireMock.Net)");
_settings.Logger.Debug("Server settings {0}", JsonConvert.SerializeObject(settings, Formatting.Indented));
HostUrlOptions urlOptions;
if (settings.Urls != null)

View File

@@ -50,17 +50,19 @@
<DefineConstants>USE_ASPNETCORE;NET46</DefineConstants>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="JetBrains.Annotations" Version="2020.1.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
<PackageReference Include="SimMetrics.Net" Version="1.0.5" />
<!--<PackageReference Include="Stef.Validation" Version="0.0.3" />-->
<PackageReference Include="System.Linq.Dynamic.Core" Version="1.0.12" />
<PackageReference Include="RandomDataGenerator.Net" Version="1.0.12" />
<PackageReference Include="JmesPath.Net" Version="1.0.125" />
<PackageReference Include="AnyOf" Version="0.2.0" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="JetBrains.Annotations" Version="2020.1.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
<PackageReference Include="SimMetrics.Net" Version="1.0.5" />
<!--<PackageReference Include="Stef.Validation" Version="0.0.3" />-->
<PackageReference Include="System.Linq.Dynamic.Core" Version="1.0.12" />
<PackageReference Include="RandomDataGenerator.Net" Version="1.0.13" />
<PackageReference Include="JmesPath.Net" Version="1.0.125" />
<PackageReference Include="AnyOf" Version="0.2.0" />
<!--<PackageReference Include="TinyMapper" Version="3.0.3" />-->
<!--<PackageReference Include="Mapster" Version="7.2.0" />-->
</ItemGroup>
<ItemGroup Condition="'$(Configuration)' == 'Debug - Sonar'">
<PackageReference Include="SonarAnalyzer.CSharp" Version="7.8.0.7320">
@@ -69,10 +71,10 @@
</PackageReference>
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' != 'netstandard1.3' ">
<PackageReference Include="XPath2.Extensions" Version="1.1.0" />
<PackageReference Include="Microsoft.IdentityModel.Protocols.OpenIdConnect" Version="6.12.2" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' != 'netstandard1.3' ">
<PackageReference Include="XPath2.Extensions" Version="1.1.0" />
<PackageReference Include="Microsoft.IdentityModel.Protocols.OpenIdConnect" Version="6.12.2" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net451' or '$(TargetFramework)' == 'net452' ">
<!-- Required for WebRequestHandler -->
@@ -81,6 +83,7 @@
<PackageReference Include="Microsoft.AspNet.WebApi.OwinSelfHost" Version="5.2.6" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
<PackageReference Include="Scriban.Signed" Version="2.1.4" />
<PackageReference Include="Mapster" Version="7.2.0" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net46' ">

View File

@@ -1,15 +1,16 @@
using System;
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using WireMock.Logging;
using WireMock.Net.StandAlone;
using WireMock.Server;
namespace WireMock
namespace WireMock.Net
{
public class Program
{
private static int SleepTime = 30000;
private static readonly ILogger Logger = LoggerFactory.Create(o =>
private static readonly int SleepTime = 30000;
private static readonly ILogger xLogger = LoggerFactory.Create(o =>
{
o.SetMinimumLevel(LogLevel.Debug);
o.AddSimpleConsole(options =>
@@ -18,18 +19,19 @@ namespace WireMock
options.SingleLine = false;
options.TimestampFormat = "yyyy-MM-ddTHH:mm:ss ";
});
}).CreateLogger(string.Empty);
}).CreateLogger("WireMock.Net");
private static readonly IWireMockLogger Logger = new WireMockLogger(xLogger);
private static WireMockServer Server;
static async Task Main(string[] args)
{
if (!StandAloneApp.TryStart(args, out Server, new WireMockLogger(Logger)))
if (!StandAloneApp.TryStart(args, out Server, Logger))
{
return;
}
Logger.LogInformation("Press Ctrl+C to shut down");
Logger.Info("Press Ctrl+C to shut down");
Console.CancelKeyPress += (s, e) =>
{
@@ -43,16 +45,16 @@ namespace WireMock
while (true)
{
Logger.LogInformation("WireMock.Net server running : {IsStarted}", Server.IsStarted);
Logger.Info("Server running : {IsStarted}", Server.IsStarted);
await Task.Delay(SleepTime);
}
}
private static void Stop(string why)
{
Logger.LogInformation("WireMock.Net server stopping because '{why}'", why);
Logger.Info("Server stopping because '{why}'", why);
Server.Stop();
Logger.LogInformation("WireMock.Net server stopped");
Logger.Info("Server stopped");
}
}
}

View File

@@ -1,10 +1,10 @@
using System;
using System;
using System.Text.Json;
using Microsoft.Extensions.Logging;
using WireMock.Admin.Requests;
using WireMock.Logging;
namespace WireMock
namespace WireMock.Net
{
public class WireMockLogger : IWireMockLogger
{

View File

@@ -0,0 +1,400 @@
using System;
using System.Collections.Generic;
using System.IO;
using FluentAssertions;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NFluent;
using WireMock.Matchers;
using Xunit;
namespace WireMock.Net.Tests.Matchers
{
public class JsonPartialWildcardMatcherTests
{
[Fact]
public void JsonPartialWildcardMatcher_GetName()
{
// Assign
var matcher = new JsonPartialWildcardMatcher("{}");
// Act
string name = matcher.Name;
// Assert
Check.That(name).Equals("JsonPartialWildcardMatcher");
}
[Fact]
public void JsonPartialWildcardMatcher_GetValue()
{
// Assign
var matcher = new JsonPartialWildcardMatcher("{}");
// Act
object value = matcher.Value;
// Assert
Check.That(value).Equals("{}");
}
[Fact]
public void JsonPartialWildcardMatcher_WithInvalidStringValue_Should_ThrowException()
{
// Act
Action action = () => new JsonPartialWildcardMatcher(MatchBehaviour.AcceptOnMatch, "{ \"Id\"");
// Assert
action.Should().Throw<JsonException>();
}
[Fact]
public void JsonPartialWildcardMatcher_WithInvalidObjectValue_Should_ThrowException()
{
// Act
Action action = () => new JsonPartialWildcardMatcher(MatchBehaviour.AcceptOnMatch, new MemoryStream());
// Assert
action.Should().Throw<JsonException>();
}
[Fact]
public void JsonPartialWildcardMatcher_IsMatch_WithInvalidValue_And_ThrowExceptionIsFalse_Should_ReturnMismatch()
{
// Assign
var matcher = new JsonPartialWildcardMatcher("");
// Act
double match = matcher.IsMatch(new MemoryStream());
// Assert
Check.That(match).IsEqualTo(0);
}
[Fact]
public void JsonPartialWildcardMatcher_IsMatch_WithInvalidValue_And_ThrowExceptionIsTrue_Should_ReturnMismatch()
{
// Assign
var matcher = new JsonPartialWildcardMatcher("", false, true);
// Act
Action action = () => matcher.IsMatch(new MemoryStream());
// Assert
action.Should().Throw<JsonException>();
}
[Fact]
public void JsonPartialWildcardMatcher_IsMatch_ByteArray()
{
// Assign
var bytes = new byte[0];
var matcher = new JsonPartialWildcardMatcher("");
// Act
double match = matcher.IsMatch(bytes);
// Assert
Check.That(match).IsEqualTo(0);
}
[Fact]
public void JsonPartialWildcardMatcher_IsMatch_NullString()
{
// Assign
string s = null;
var matcher = new JsonPartialWildcardMatcher("");
// Act
double match = matcher.IsMatch(s);
// Assert
Check.That(match).IsEqualTo(0);
}
[Fact]
public void JsonPartialWildcardMatcher_IsMatch_NullObject()
{
// Assign
object o = null;
var matcher = new JsonPartialWildcardMatcher("");
// Act
double match = matcher.IsMatch(o);
// Assert
Check.That(match).IsEqualTo(0);
}
[Fact]
public void JsonPartialWildcardMatcher_IsMatch_JArray()
{
// Assign
var matcher = new JsonPartialWildcardMatcher(new[] { "x", "y" });
// Act
var jArray = new JArray
{
"x",
"y"
};
double match = matcher.IsMatch(jArray);
// Assert
Assert.Equal(1.0, match);
}
[Fact]
public void JsonPartialWildcardMatcher_IsMatch_JObject()
{
// Assign
var matcher = new JsonPartialWildcardMatcher(new { Id = 1, Name = "Test" });
// Act
var jobject = new JObject
{
{ "Id", new JValue(1) },
{ "Name", new JValue("Test") }
};
double match = matcher.IsMatch(jobject);
// Assert
Assert.Equal(1.0, match);
}
[Fact]
public void JsonPartialWildcardMatcher_IsMatch_WithIgnoreCaseTrue_JObject()
{
// Assign
var matcher = new JsonPartialWildcardMatcher(new { id = 1, Name = "test" }, true);
// Act
var jobject = new JObject
{
{ "Id", new JValue(1) },
{ "NaMe", new JValue("Test") }
};
double match = matcher.IsMatch(jobject);
// Assert
Assert.Equal(1.0, match);
}
[Fact]
public void JsonPartialWildcardMatcher_IsMatch_JObjectParsed()
{
// Assign
var matcher = new JsonPartialWildcardMatcher(new { Id = 1, Name = "Test" });
// Act
var jobject = JObject.Parse("{ \"Id\" : 1, \"Name\" : \"Test\" }");
double match = matcher.IsMatch(jobject);
// Assert
Assert.Equal(1.0, match);
}
[Fact]
public void JsonPartialWildcardMatcher_IsMatch_WithIgnoreCaseTrue_JObjectParsed()
{
// Assign
var matcher = new JsonPartialWildcardMatcher(new { Id = 1, Name = "TESt" }, true);
// Act
var jobject = JObject.Parse("{ \"Id\" : 1, \"Name\" : \"Test\" }");
double match = matcher.IsMatch(jobject);
// Assert
Assert.Equal(1.0, match);
}
[Fact]
public void JsonPartialWildcardMatcher_IsMatch_JArrayAsString()
{
// Assign
var matcher = new JsonPartialWildcardMatcher("[ \"x\", \"y\" ]");
// Act
var jArray = new JArray
{
"x",
"y"
};
double match = matcher.IsMatch(jArray);
// Assert
Assert.Equal(1.0, match);
}
[Fact]
public void JsonPartialWildcardMatcher_IsMatch_JObjectAsString()
{
// Assign
var matcher = new JsonPartialWildcardMatcher("{ \"Id\" : 1, \"Name\" : \"Test\" }");
// Act
var jobject = new JObject
{
{ "Id", new JValue(1) },
{ "Name", new JValue("Test") }
};
double match = matcher.IsMatch(jobject);
// Assert
Assert.Equal(1.0, match);
}
[Fact]
public void JsonPartialWildcardMatcher_IsMatch_WithIgnoreCaseTrue_JObjectAsString()
{
// Assign
var matcher = new JsonPartialWildcardMatcher("{ \"Id\" : 1, \"Name\" : \"test\" }", true);
// Act
var jobject = new JObject
{
{ "Id", new JValue(1) },
{ "Name", new JValue("Test") }
};
double match = matcher.IsMatch(jobject);
// Assert
Assert.Equal(1.0, match);
}
[Fact]
public void JsonPartialWildcardMatcher_IsMatch_JObjectAsString_RejectOnMatch()
{
// Assign
var matcher = new JsonPartialWildcardMatcher(MatchBehaviour.RejectOnMatch, "{ \"Id\" : 1, \"Name\" : \"Test\" }");
// Act
var jobject = new JObject
{
{ "Id", new JValue(1) },
{ "Name", new JValue("Test") }
};
double match = matcher.IsMatch(jobject);
// Assert
Assert.Equal(0.0, match);
}
[Fact]
public void JsonPartialWildcardMatcher_IsMatch_JObjectWithDateTimeOffsetAsString()
{
// Assign
var matcher = new JsonPartialWildcardMatcher("{ \"preferredAt\" : \"2019-11-21T10:32:53.2210009+00:00\" }");
// Act
var jobject = new JObject
{
{ "preferredAt", new JValue("2019-11-21T10:32:53.2210009+00:00") }
};
double match = matcher.IsMatch(jobject);
// Assert
Assert.Equal(1.0, match);
}
[Theory]
[InlineData("{\"test\":\"abc\"}", "{\"test\":\"abc\",\"other\":\"xyz\"}")]
[InlineData("\"test\"", "\"test\"")]
[InlineData("123", "123")]
[InlineData("[\"test\"]", "[\"test\"]")]
[InlineData("[\"test\"]", "[\"test\", \"other\"]")]
[InlineData("[123]", "[123]")]
[InlineData("[123]", "[123, 456]")]
[InlineData("{ \"test\":\"value\" }", "{\"test\":\"value\",\"other\":123}")]
[InlineData("{ \"test\":\"value\" }", "{\"test\":\"value\"}")]
[InlineData("{\"test\":{\"nested\":\"value\"}}", "{\"test\":{\"nested\":\"value\"}}")]
public void JsonPartialWildcardMatcher_IsMatch_StringInput_IsValidMatch(string value, string input)
{
// Assign
var matcher = new JsonPartialWildcardMatcher(value);
// Act
double match = matcher.IsMatch(input);
// Assert
Assert.Equal(1.0, match);
}
[Theory]
[InlineData("{\"test\":\"*\"}", "{\"test\":\"xxx\",\"other\":\"xyz\"}")]
[InlineData("\"t*t\"", "\"test\"")]
public void JsonPartialWildcardMatcher_IsMatch_StringInputWithWildcard_IsValidMatch(string value, string input)
{
// Assign
var matcher = new JsonPartialWildcardMatcher(value);
// Act
double match = matcher.IsMatch(input);
// Assert
match.Should().Be(1.0);
}
[Theory]
[InlineData("\"test\"", null)]
[InlineData("\"test1\"", "\"test2\"")]
[InlineData("123", "1234")]
[InlineData("[\"test\"]", "[\"test1\"]")]
[InlineData("[\"test\"]", "[\"test1\", \"test2\"]")]
[InlineData("[123]", "[1234]")]
[InlineData("{}", "\"test\"")]
[InlineData("{ \"test\":\"value\" }", "{\"test\":\"value2\"}")]
[InlineData("{ \"test.nested\":\"value\" }", "{\"test\":{\"nested\":\"value1\"}}")]
[InlineData("{\"test\":{\"test1\":\"value\"}}", "{\"test\":{\"test1\":\"value1\"}}")]
[InlineData("[{ \"test.nested\":\"value\" }]", "[{\"test\":{\"nested\":\"value1\"}}]")]
public void JsonPartialWildcardMatcher_IsMatch_StringInputWithInvalidMatch(string value, string input)
{
// Assign
var matcher = new JsonPartialWildcardMatcher(value);
// Act
double match = matcher.IsMatch(input);
// Assert
Assert.Equal(0.0, match);
}
[Theory]
[InlineData("{ \"test.nested\":123 }", "{\"test\":{\"nested\":123}}")]
[InlineData("{ \"test.nested\":[123, 456] }", "{\"test\":{\"nested\":[123, 456]}}")]
[InlineData("{ \"test.nested\":\"value\" }", "{\"test\":{\"nested\":\"value\"}}")]
[InlineData("{ \"['name.with.dot']\":\"value\" }", "{\"name.with.dot\":\"value\"}")]
[InlineData("[{ \"test.nested\":\"value\" }]", "[{\"test\":{\"nested\":\"value\"}}]")]
[InlineData("[{ \"['name.with.dot']\":\"value\" }]", "[{\"name.with.dot\":\"value\"}]")]
public void JsonPartialWildcardMatcher_IsMatch_ValueAsJPathValidMatch(string value, string input)
{
// Assign
var matcher = new JsonPartialWildcardMatcher(value);
// Act
double match = matcher.IsMatch(input);
// Assert
Assert.Equal(1.0, match);
}
[Theory]
[InlineData("{ \"test.nested\":123 }", "{\"test\":{\"nested\":456}}")]
[InlineData("{ \"test.nested\":[123, 456] }", "{\"test\":{\"nested\":[1, 2]}}")]
[InlineData("{ \"test.nested\":\"value\" }", "{\"test\":{\"nested\":\"value1\"}}")]
[InlineData("{ \"['name.with.dot']\":\"value\" }", "{\"name.with.dot\":\"value1\"}")]
[InlineData("[{ \"test.nested\":\"value\" }]", "[{\"test\":{\"nested\":\"value1\"}}]")]
[InlineData("[{ \"['name.with.dot']\":\"value\" }]", "[{\"name.with.dot\":\"value1\"}]")]
public void JsonPartialWildcardMatcher_IsMatch_ValueAsJPathInvalidMatch(string value, string input)
{
// Assign
var matcher = new JsonPartialWildcardMatcher(value);
// Act
double match = matcher.IsMatch(input);
// Assert
Assert.Equal(0.0, match);
}
}
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Concurrent;
using System.Linq.Expressions;
using System.Threading.Tasks;
@@ -176,7 +176,7 @@ namespace WireMock.Net.Tests.Owin
_mappingMock.SetupGet(m => m.Provider).Returns(responseBuilder);
_mappingMock.SetupGet(m => m.Settings).Returns(settings);
var newMappingFromProxy = new Mapping(Guid.NewGuid(), "", null, settings, Request.Create(), Response.Create(), 0, null, null, null, null, null);
var newMappingFromProxy = new Mapping(Guid.NewGuid(), "", null, settings, Request.Create(), Response.Create(), 0, null, null, null, null, null, null);
_mappingMock.Setup(m => m.ProvideResponseAsync(It.IsAny<RequestMessage>())).ReturnsAsync((new ResponseMessage(), newMappingFromProxy));
var requestBuilder = Request.Create().UsingAnyMethod();
@@ -230,7 +230,7 @@ namespace WireMock.Net.Tests.Owin
_mappingMock.SetupGet(m => m.Provider).Returns(responseBuilder);
_mappingMock.SetupGet(m => m.Settings).Returns(settings);
var newMappingFromProxy = new Mapping(Guid.NewGuid(), "", null, settings, Request.Create(), Response.Create(), 0, null, null, null, null, null);
var newMappingFromProxy = new Mapping(Guid.NewGuid(), "", null, settings, Request.Create(), Response.Create(), 0, null, null, null, null, null, null);
_mappingMock.Setup(m => m.ProvideResponseAsync(It.IsAny<RequestMessage>())).ReturnsAsync((new ResponseMessage(), newMappingFromProxy));
var requestBuilder = Request.Create().UsingAnyMethod();

View File

@@ -51,7 +51,7 @@ namespace WireMock.Net.Tests.Serialization
}
}
};
var mapping = new Mapping(Guid.NewGuid(), "", null, _settings, request, response, 0, null, null, null, null, webhooks);
var mapping = new Mapping(Guid.NewGuid(), "", null, _settings, request, response, 0, null, null, null, null, webhooks, null);
// Act
var model = _sut.ToMappingModel(mapping);
@@ -120,8 +120,7 @@ namespace WireMock.Net.Tests.Serialization
}
}
};
var mapping = new Mapping(Guid.NewGuid(), "", null, _settings, request, response, 0, null, null, null, null, webhooks
);
var mapping = new Mapping(Guid.NewGuid(), "", null, _settings, request, response, 0, null, null, null, null, webhooks, null);
// Act
var model = _sut.ToMappingModel(mapping);
@@ -153,7 +152,7 @@ namespace WireMock.Net.Tests.Serialization
// Assign
var request = Request.Create();
var response = Response.Create().WithBodyAsJson(new { x = "x" }).WithTransformer();
var mapping = new Mapping(Guid.NewGuid(), "", null, _settings, request, response, 42, null, null, null, null, null);
var mapping = new Mapping(Guid.NewGuid(), "", null, _settings, request, response, 42, null, null, null, null, null, null);
// Act
var model = _sut.ToMappingModel(mapping);
@@ -164,6 +163,34 @@ namespace WireMock.Net.Tests.Serialization
model.Response.UseTransformer.Should().BeTrue();
}
[Fact]
public void ToMappingModel_WithTimeSetrtings_ReturnsCorrectTimeSettings()
{
// Assign
var start = DateTime.Now;
var ttl = 100;
var end = start.AddSeconds(ttl);
var request = Request.Create();
var response = Response.Create();
var timeSettings = new TimeSettings
{
Start = start,
End = end,
TTL = ttl
};
var mapping = new Mapping(Guid.NewGuid(), "", null, _settings, request, response, 42, null, null, null, null, null, timeSettings);
// Act
var model = _sut.ToMappingModel(mapping);
// Assert
model.Should().NotBeNull();
model.TimeSettings.Should().NotBeNull();
model.TimeSettings.Start.Should().Be(start);
model.TimeSettings.End.Should().Be(end);
model.TimeSettings.TTL.Should().Be(ttl);
}
[Fact]
public void ToMappingModel_WithDelay_ReturnsCorrectModel()
{
@@ -171,7 +198,7 @@ namespace WireMock.Net.Tests.Serialization
int delay = 1000;
var request = Request.Create();
var response = Response.Create().WithDelay(delay);
var mapping = new Mapping(Guid.NewGuid(), "", null, _settings, request, response, 42, null, null, null, null, null);
var mapping = new Mapping(Guid.NewGuid(), "", null, _settings, request, response, 42, null, null, null, null, null, null);
// Act
var model = _sut.ToMappingModel(mapping);
@@ -182,13 +209,13 @@ namespace WireMock.Net.Tests.Serialization
}
[Fact]
public void ToMappingModel_WithRandomMininumDelay_ReturnsCorrectModel()
public void ToMappingModel_WithRandomMinimumDelay_ReturnsCorrectModel()
{
// Assign
int minimumDelay = 1000;
var request = Request.Create();
var response = Response.Create().WithRandomDelay(minimumDelay);
var mapping = new Mapping(Guid.NewGuid(), "", null, _settings, request, response, 42, null, null, null, null, null);
var mapping = new Mapping(Guid.NewGuid(), "", null, _settings, request, response, 42, null, null, null, null, null, null);
// Act
var model = _sut.ToMappingModel(mapping);
@@ -208,7 +235,7 @@ namespace WireMock.Net.Tests.Serialization
int maximumDelay = 2000;
var request = Request.Create();
var response = Response.Create().WithRandomDelay(minimumDelay, maximumDelay);
var mapping = new Mapping(Guid.NewGuid(), "", null, _settings, request, response, 42, null, null, null, null, null);
var mapping = new Mapping(Guid.NewGuid(), "", null, _settings, request, response, 42, null, null, null, null, null, null);
// Act
var model = _sut.ToMappingModel(mapping);

View File

@@ -345,5 +345,24 @@ namespace WireMock.Net.Tests.Serialization
matcher.MatchBehaviour.Should().Be(MatchBehaviour.AcceptOnMatch);
matcher.Value.Should().BeEquivalentTo(pattern);
}
[Fact]
public void MatcherMapper_Map_MatcherModel_JsonPartialWilcardMatcher_Patterns_As_Object()
{
// Assign
object pattern = new { X = "*" };
var model = new MatcherModel
{
Name = "JsonPartialWildcardMatcher",
Pattern = pattern
};
// Act
var matcher = (JsonPartialWildcardMatcher)_sut.Map(model);
// Assert
matcher.MatchBehaviour.Should().Be(MatchBehaviour.AcceptOnMatch);
matcher.Value.Should().BeEquivalentTo(pattern);
}
}
}

View File

@@ -37,6 +37,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<!--<PackageReference Include="Mapster" Version="7.2.0" />-->
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
@@ -51,7 +52,7 @@
<PackageReference Include="Moq" Version="4.16.0" />
<PackageReference Include="System.Threading" Version="4.3.0" />
<PackageReference Include="RestEase" Version="1.5.5" />
<PackageReference Include="RandomDataGenerator.Net" Version="1.0.12" />
<PackageReference Include="RandomDataGenerator.Net" Version="1.0.13" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
<PackageReference Include="NFluent" Version="2.7.1" />
@@ -59,8 +60,9 @@
<!--<PackageReference Include="ReportGenerator" Version="4.8.1" />-->
<PackageReference Include="SimMetrics.Net" Version="1.0.5" />
<PackageReference Include="System.Linq.Dynamic.Core" Version="1.0.12" />
<!--<PackageReference Include="StrongNamer" Version="0.0.8" />-->
<!--<PackageReference Include="StrongNamer" Version="0.2.5" />-->
<PackageReference Include="AnyOf" Version="0.2.0" />
<!--<PackageReference Include="TinyMapper" Version="3.0.3" />-->
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net452' or '$(TargetFramework)' == 'net461'">