@n1k1t/mock-server 0.1.5 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1123 -43
- package/lib/package.json +1 -1
- package/lib/src/client/utils.d.ts.map +1 -1
- package/lib/src/client/utils.js +3 -0
- package/lib/src/client/utils.js.map +1 -1
- package/lib/src/config/model.d.ts +1 -0
- package/lib/src/config/model.d.ts.map +1 -1
- package/lib/src/config/model.js +1 -0
- package/lib/src/config/model.js.map +1 -1
- package/lib/src/expectations/models/expectation.d.ts +1 -1
- package/lib/src/expectations/models/expectation.d.ts.map +1 -1
- package/lib/src/expectations/models/expectation.js +1 -1
- package/lib/src/expectations/models/expectation.js.map +1 -1
- package/lib/src/expectations/operators/merge.operator.d.ts +10 -3
- package/lib/src/expectations/operators/merge.operator.d.ts.map +1 -1
- package/lib/src/expectations/operators/merge.operator.js +21 -4
- package/lib/src/expectations/operators/merge.operator.js.map +1 -1
- package/lib/src/expectations/types.d.ts +0 -1
- package/lib/src/expectations/types.d.ts.map +1 -1
- package/lib/src/expectations/types.js.map +1 -1
- package/lib/src/logger/index.d.ts +2 -2
- package/lib/src/logger/index.d.ts.map +1 -1
- package/lib/src/logger/index.js +1 -1
- package/lib/src/logger/index.js.map +1 -1
- package/lib/src/server/endpoints/config.get.endpoint.d.ts +3 -0
- package/lib/src/server/endpoints/config.get.endpoint.d.ts.map +1 -1
- package/lib/src/server/index.d.ts +1 -0
- package/lib/src/server/index.d.ts.map +1 -1
- package/lib/src/server/index.js +4 -1
- package/lib/src/server/index.js.map +1 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/public/scripts/main.js +75 -29
- package/public/styles/main.css +67 -0
- package/screenshots/history.png +0 -0
- package/screenshots/preview.png +0 -0
- package/screenshots/strategy.png +0 -0
package/README.md
CHANGED
|
@@ -1,88 +1,1168 @@
|
|
|
1
1
|
|
|
2
|
+
|
|
2
3
|
# Mock server
|
|
3
4
|
|
|
4
|
-
|
|
5
|
+
Mock, match, modify and manipulate a HTTP request/response payload using flexible expectations with types
|
|
6
|
+
|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
# Navigation
|
|
10
|
+
|
|
11
|
+
- [Basics](#basics)
|
|
12
|
+
- [How it works](#how-it-works)
|
|
13
|
+
- [Install](#install)
|
|
14
|
+
- [Start](#start)
|
|
15
|
+
- [GUI](#gui)
|
|
16
|
+
- [Mock](#mock)
|
|
17
|
+
- [Expectations](#expectations)
|
|
18
|
+
- [Schema](#schema)
|
|
19
|
+
- [Context](#context)
|
|
20
|
+
- [Utils](#utils)
|
|
21
|
+
- [Operators](#operators)
|
|
22
|
+
- [Typings](#typings)
|
|
23
|
+
- [State](#state)
|
|
24
|
+
- [Seeds](#seeds)
|
|
25
|
+
- [XML](#xml)
|
|
26
|
+
- [API](#api)
|
|
27
|
+
- [Ping](#ping)
|
|
28
|
+
- [Create expectation](#create-expectation)
|
|
29
|
+
- [Update expectation](#update-expectation)
|
|
30
|
+
- [Delete expectation](#delete-expectation)
|
|
31
|
+
- [Additional](#additional)
|
|
32
|
+
- [Configuration](#configuration)
|
|
33
|
+
- [Logger](#logger)
|
|
34
|
+
- [Meta](#meta)
|
|
35
|
+
- [Plugins](#plugins)
|
|
36
|
+
|
|
37
|
+
# Basics
|
|
38
|
+
|
|
39
|
+
## How it works
|
|
40
|
+
|
|
41
|
+

|
|
42
|
+
|
|
43
|
+
According on the picture above, main idea is to generate or modify response from some backend service. The mock server provides many scenarios to do that
|
|
44
|
+
|
|
45
|
+
**In case of mocking without request forwarding:**
|
|
46
|
+
|
|
47
|
+
1. Start mock server (for example on `localhost:8080`)
|
|
48
|
+
2. Register expectation using CLI (cURL) or application lib
|
|
49
|
+
3. Make request to `localhost:8080/...`
|
|
50
|
+
1. The mock server matches a request payload with registred expectations
|
|
51
|
+
2. Build a response using an expectation configuration
|
|
5
52
|
|
|
6
|
-
|
|
53
|
+
**In case of mocking with request forwarding:**
|
|
54
|
+
|
|
55
|
+
0. Lets imagine that you have a service that hosts on `localhost:8081`
|
|
56
|
+
1. Start mock server (for example on `localhost:8080`)
|
|
57
|
+
2. Register expectation using CLI (cURL) or application lib
|
|
58
|
+
3. Make request to `localhost:8080/...`
|
|
59
|
+
1. The mock server matches a request payload with registred expectations
|
|
60
|
+
2. Next is forwarding a request payload to `localhost:8081/...`
|
|
61
|
+
3. Using response fetched from `localhost:8081/...` the mock server builds a response
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
## Install
|
|
7
65
|
|
|
8
66
|
```bash
|
|
9
|
-
npm i
|
|
67
|
+
npm i @n1k1t/mock-server
|
|
10
68
|
```
|
|
11
69
|
|
|
12
|
-
|
|
70
|
+
## Start
|
|
71
|
+
|
|
72
|
+
### CLI
|
|
13
73
|
|
|
14
|
-
Using console
|
|
15
74
|
```bash
|
|
16
|
-
|
|
17
|
-
npx @n1k1t/mock-server -p 8080
|
|
75
|
+
npx @n1k1t/mock-server -h localhost -p 8080
|
|
18
76
|
```
|
|
19
77
|
|
|
20
|
-
|
|
78
|
+
### JavaScript
|
|
79
|
+
|
|
21
80
|
```js
|
|
22
81
|
const { MockServer } = require('@n1k1t/mock-server');
|
|
82
|
+
MockServer.start({ host: 'localhost', port: 8080 });
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### TypeScript
|
|
23
86
|
|
|
87
|
+
```ts
|
|
88
|
+
import { MockServer } from '@n1k1t/mock-server';
|
|
24
89
|
MockServer.start({ host: 'localhost', port: 8080 });
|
|
25
90
|
```
|
|
26
91
|
|
|
27
|
-
|
|
92
|
+
## GUI
|
|
93
|
+
|
|
94
|
+
The mock server provides built-in web panel to track everything that is going through. There are two tabs `Expectations` and `History`
|
|
95
|
+
|
|
96
|
+
By default it can be found on `/_mock/gui` of a host of mock server. Example: `localhost:8080/_mock/gui`
|
|
97
|
+
|
|
98
|
+
Also it provides convenient util to navigate through payload of expectations and requests payload
|
|
99
|
+
|
|
100
|
+

|
|
101
|
+
|
|
102
|
+
## Mock
|
|
103
|
+
|
|
104
|
+
Simple examples can be found in [expectation creation API](#create-expectation)
|
|
105
|
+
|
|
106
|
+
# Expectations
|
|
107
|
+
|
|
108
|
+
## Schema
|
|
109
|
+
|
|
110
|
+
An expectation schema can contain some rules to handle `request`, `response` and `forward`
|
|
111
|
+
|
|
112
|
+
| Property | Nested | Type | Optional | Description |
|
|
113
|
+
|--|--|--|--|--|
|
|
114
|
+
| request | [Operators](#operators) | `object` | * | Describes a way to catch by request and how to manipulate it |
|
|
115
|
+
| response | [Operators](#operators) | `object` | * | Describes how to manipulate response. Also can be used to catch response in case of forwarding |
|
|
116
|
+
| forward | | `object` | * | Describes configuration to forward a request to another host |
|
|
117
|
+
| | url | `string` | * | Absolute URL to target |
|
|
118
|
+
| | baseUrl | `string` | * | Base URL to target. The path will be provided from request |
|
|
119
|
+
|
|
120
|
+
**Example**
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
await server.client.createExpectation({
|
|
124
|
+
schema: {
|
|
125
|
+
request: {
|
|
126
|
+
$and: [],
|
|
127
|
+
},
|
|
128
|
+
response: {
|
|
129
|
+
$or: [],
|
|
130
|
+
},
|
|
131
|
+
forward: {
|
|
132
|
+
baseUrl: 'https://example.com',
|
|
133
|
+
url: '/some/path',
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Context
|
|
140
|
+
|
|
141
|
+
| Property | Nested | `$location` | Type | Optional | Description |
|
|
142
|
+
|--|--|--|--|--|--|
|
|
143
|
+
| seed | | `seed` | `string` | * | Incoming request [seed](#seeds) |
|
|
144
|
+
| state | | `state` | `object` | | Incoming request [state](#state) with a custom data while request finish |
|
|
145
|
+
| incoming | | | `object` | | Payload with data of incoming request |
|
|
146
|
+
| | path | `path` | `string` | | Incoming request path |
|
|
147
|
+
| | method | `method` | `string` | | Incoming request method in **uppercase** |
|
|
148
|
+
| | headers | `incoming.headers` | `object` | | Incoming request headers with keys in **lowercase** |
|
|
149
|
+
| | bodyRaw | `incoming.bodyRaw` | `string` | | Incoming request source body |
|
|
150
|
+
| | body | `incoming.body` | `object` | * | Incoming request parsed body |
|
|
151
|
+
| | query | `incoming.query` | `object` | * | Incoming request query search parameters |
|
|
152
|
+
| | delay | `delay` | `number` | * | Delay that can be applied with [operators](#operators) |
|
|
153
|
+
| | error | `error` | `string` | * | Error that can be applied with [operators](#operators) |
|
|
154
|
+
| outgoing | | | `object` | | Payload with data of response |
|
|
155
|
+
| | status | `outgoing.status` | `number` | | Response status code |
|
|
156
|
+
| | headers | `outgoing.headers` | `number` | | Response headers |
|
|
157
|
+
| | dataRaw | `outgoing.dataRaw` | `string` | | Response source data |
|
|
158
|
+
| | data | `outgoing.data` | `any` | * | Response data |
|
|
159
|
+
|
|
160
|
+
## Utils
|
|
161
|
+
|
|
162
|
+
Additional utils in `$exec` operator
|
|
163
|
+
|
|
164
|
+
| Property | Description |
|
|
165
|
+
|--|--|
|
|
166
|
+
| `context` | A request [context](#context) |
|
|
167
|
+
| `logger` | [Logger](#logger) of mock server |
|
|
168
|
+
| `mode` | A mode of expectation execution. Has `match` on catching request or `manipulate` on manipulation over [context](#context) |
|
|
169
|
+
| `meta` | A [meta](#meta) of a request |
|
|
170
|
+
| `_` | [Lodash](https://www.npmjs.com/package/lodash) |
|
|
171
|
+
| `d` | [DayJS](https://www.npmjs.com/package/dayjs) |
|
|
172
|
+
| `faker` | [Faker](https://www.npmjs.com/package/@faker-js/faker). Uses [seed](#seeds) if it was provided |
|
|
173
|
+
|
|
174
|
+
## Operators
|
|
175
|
+
|
|
176
|
+
> **!NOTE** Each schema that using operators can have only one nested operator. To use more than one operator use `$and` or `$or` operators
|
|
177
|
+
|
|
178
|
+
| Operator | Optional | Description |
|
|
179
|
+
|--|--|--|
|
|
180
|
+
| [$has](#has) | * | Catches a request/response or checks a payload in [context](#context) |
|
|
181
|
+
| [$set](#set) | * | Sets payload in [context](#context) |
|
|
182
|
+
| [$merge](#merge) | * | Merges object payload in [context](#context) with provided `$value` |
|
|
183
|
+
| [$remove](#remove) | * | Removes payload in [context](#context) |
|
|
184
|
+
| [$exec](#exec) | * | Function to catch a request/response or check/manipulate payload in [context](#context) |
|
|
185
|
+
| [$and](#and) | * | Logical `and` |
|
|
186
|
+
| [$or](#or) | * | Logical `or` |
|
|
187
|
+
| [$not](#not) | * | Logical `not` |
|
|
188
|
+
| [$if](#if) | * | Logical `if` |
|
|
189
|
+
| [$switch](#switch) | * | Logical `switch/case` |
|
|
190
|
+
|
|
191
|
+
**Example**
|
|
192
|
+
|
|
193
|
+
```ts
|
|
194
|
+
await server.client.createExpectation({
|
|
195
|
+
schema: {
|
|
196
|
+
request: {
|
|
197
|
+
$and: [
|
|
198
|
+
{
|
|
199
|
+
$has: {
|
|
200
|
+
$location: 'path',
|
|
201
|
+
$value: '/foo',
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
$has: {
|
|
206
|
+
$location: 'method',
|
|
207
|
+
$value: 'GET',
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
],
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
});
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### $has
|
|
217
|
+
|
|
218
|
+
> **!NOTE** `$exec` operators [have restrictions](#api) when it defined over `HTTP API` or `RemoteClient`
|
|
219
|
+
|
|
220
|
+
| Property | Type (application) | Type (cURL) | Optional | Description |
|
|
221
|
+
|--|--|--|--|--|
|
|
222
|
+
| $location | `string` [enum](#context) | `string` [enum](#context) | | Location that describes what [context](#context) entity is selecting for operator to work with |
|
|
223
|
+
| $path | `string` | `string` | * | Specifies a path to payload using [lodash get](https://lodash.com/docs/4.17.15#get) |
|
|
224
|
+
| $jsonPath | `string` | `string` | * | Specifies a path to payload using [JSON path](https://www.npmjs.com/package/jsonpath-plus) |
|
|
225
|
+
| $value | `any` | `any` | * | Checks by value equality in [context](#context) using `$location` (and `$path`, `$jsonPath` if it was specified) |
|
|
226
|
+
| $valueAnyOf | `any[]` | `any[]` | * | Checks by any of value equality in [context](#context) using `$location` (and `$path`, `$jsonPath` if it was specified) |
|
|
227
|
+
| $regExp | `RegExp` | `{ source: string, flags?: string }` | * | Checks by regular expression in context using `$location` (and `$path`, `$jsonPath` if it was specified) |
|
|
228
|
+
| $regExpAnyOf | `RegExp[]` | `{ source: string, flags?: string }[]` | * | Checks by any of regular expression in [context](#context) using `$location` (and `$path`, `$jsonPath` if it was specified) |
|
|
229
|
+
| $match | `string ∣ object` | `string ∣ object` | * | Checks by minimatch for `string` and `number` (example `/foo/*/bar` or `2**`) or similar `object` by passing object payload in [context](#context) using `$location` (and `$path`, `$jsonPath` if it was specified) |
|
|
230
|
+
| $matchAnyOf | `(string ∣ object)[]` | `(string ∣ object)[]` | * | Checks by any of minimatch for `string` and `number` (example `/foo/*/bar` or `2**`) or similar `object` by passing object payload in [context](#context) using `$location` (and `$path`, `$jsonPath` if it was specified) |
|
|
231
|
+
| $exec | `(payload, utils) => boolean` | `string` | * | Checks payload in [context](#context) by function with arguments where `payload` is selected entity using `$location` (and `$path`, `$jsonPath` if it was specified) and `utils` is [utils](#utils) |
|
|
232
|
+
|
|
233
|
+
**Example using application**
|
|
234
|
+
|
|
235
|
+
```ts
|
|
236
|
+
await server.client.createExpectation({
|
|
237
|
+
schema: {
|
|
238
|
+
request: {
|
|
239
|
+
$has: {
|
|
240
|
+
$location: 'path',
|
|
241
|
+
$regExp: /^\/foo/,
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
},
|
|
245
|
+
});
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
**Example using cURL**
|
|
249
|
+
|
|
250
|
+
```bash
|
|
251
|
+
curl -H "Content-type: application/json" -X POST --location "localhost:8080/_mock/expectations" --data-binary @- << EOF
|
|
252
|
+
{
|
|
253
|
+
"schema": {
|
|
254
|
+
"request": {
|
|
255
|
+
"\$has": {
|
|
256
|
+
"\$location": "method",
|
|
257
|
+
"\$regExp": { "source": "^\/foo" }
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
EOF
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### $set
|
|
266
|
+
|
|
267
|
+
> **!NOTE** `$exec` operators [have restrictions](#api) when it defined over `HTTP API` or `RemoteClient`
|
|
268
|
+
|
|
269
|
+
| Property | Type (application) | Type (cURL) | Optional | Description |
|
|
270
|
+
|--|--|--|--|--|
|
|
271
|
+
| $location | `string` [enum](#context) | `string` [enum](#context) | | Location that describes what [context](#context) entity is selecting for operator to work with |
|
|
272
|
+
| $path | `string` | `string` | * | Specifies a path to payload using [lodash get](https://lodash.com/docs/4.17.15#get) |
|
|
273
|
+
| $jsonPath | `string` | `string` | * | Specifies a path to payload using [JSON path](https://www.npmjs.com/package/jsonpath-plus) |
|
|
274
|
+
| $value | `any` | `any` | * | Sets value to [context](#context) using `$location` (and `$path`, `$jsonPath` if it was specified) |
|
|
275
|
+
| $exec | `(payload, utils) => any` | `string` | * | Sets payload in [context](#context) by function with arguments where `payload` is selected entity using `$location` (and `$path`, `$jsonPath` if it was specified) and `utils` is [utils](#utils) |
|
|
276
|
+
|
|
277
|
+
**Example using application**
|
|
278
|
+
|
|
279
|
+
```ts
|
|
280
|
+
await server.client.createExpectation({
|
|
281
|
+
schema: {
|
|
282
|
+
request: {
|
|
283
|
+
$set: {
|
|
284
|
+
$location: 'incoming.body',
|
|
285
|
+
$path: 'foo',
|
|
286
|
+
$exec: (payload, { _ }) => _.clamp(payload, 0, 10),
|
|
287
|
+
},
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
});
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
**Example using cURL**
|
|
294
|
+
|
|
295
|
+
```bash
|
|
296
|
+
curl -H "Content-type: application/json" -X POST --location "localhost:8080/_mock/expectations" --data-binary @- << EOF
|
|
297
|
+
{
|
|
298
|
+
"schema": {
|
|
299
|
+
"request": {
|
|
300
|
+
"\$set": {
|
|
301
|
+
"\$location": "incoming.body",
|
|
302
|
+
"\$path": "foo",
|
|
303
|
+
"\$exec": "_.clamp(payload, 0, 10)"
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
EOF
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### $merge
|
|
312
|
+
|
|
313
|
+
> **!NOTE** `$exec` operators [have restrictions](#api) when it defined over `HTTP API` or `RemoteClient`
|
|
314
|
+
|
|
315
|
+
| Property | Type (application) | Type (cURL) | Optional | Description |
|
|
316
|
+
|--|--|--|--|--|
|
|
317
|
+
| $location | `string` [enum](#context) | `string` [enum](#context) | | Location that describes what [context](#context) entity is selecting for operator to work with |
|
|
318
|
+
| $path | `string` | `string` | * | Specifies a path to payload using [lodash get](https://lodash.com/docs/4.17.15#get) |
|
|
319
|
+
| $jsonPath | `string` | `string` | * | Specifies a path to payload using [JSON path](https://www.npmjs.com/package/jsonpath-plus) |
|
|
320
|
+
| $value | `object` | `object` | * | Merges value in [context](#context) using `$location` (and `$path`, `$jsonPath` if it was specified) |
|
|
321
|
+
| $exec | `(payload, utils) => any` | `string` | * | Merges payload in [context](#context) by function with arguments where `payload` is selected entity using `$location` (and `$path`, `$jsonPath` if it was specified) and `utils` is [utils](#utils) |
|
|
322
|
+
|
|
323
|
+
**Example using application**
|
|
324
|
+
|
|
325
|
+
```ts
|
|
326
|
+
await server.client.createExpectation({
|
|
327
|
+
schema: {
|
|
328
|
+
request: {
|
|
329
|
+
$merge: {
|
|
330
|
+
$location: 'incoming.body',
|
|
331
|
+
$value: { has_mocked: true },
|
|
332
|
+
},
|
|
333
|
+
},
|
|
334
|
+
},
|
|
335
|
+
});
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
**Example using cURL**
|
|
339
|
+
|
|
340
|
+
```bash
|
|
341
|
+
curl -H "Content-type: application/json" -X POST --location "localhost:8080/_mock/expectations" --data-binary @- << EOF
|
|
342
|
+
{
|
|
343
|
+
"schema": {
|
|
344
|
+
"request": {
|
|
345
|
+
"\$merge": {
|
|
346
|
+
"\$location": "incoming.body",
|
|
347
|
+
"\$value": {"has_mocked": true}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
EOF
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
### $remove
|
|
356
|
+
|
|
357
|
+
| Property | Type (application) | Type (cURL) | Optional | Description |
|
|
358
|
+
|--|--|--|--|--|
|
|
359
|
+
| $location | `string` [enum](#context) | `string` [enum](#context) | | Location that describes what [context](#context) entity is selecting for operator to work with |
|
|
360
|
+
| $path | `string` | `string` | * | Specifies a path to payload using [lodash get](https://lodash.com/docs/4.17.15#get) |
|
|
361
|
+
| $jsonPath | `string` | `string` | * | Specifies a path to payload using [JSON path](https://www.npmjs.com/package/jsonpath-plus) |
|
|
362
|
+
|
|
363
|
+
**Example using application**
|
|
364
|
+
|
|
365
|
+
```ts
|
|
366
|
+
await server.client.createExpectation({
|
|
367
|
+
schema: {
|
|
368
|
+
request: {
|
|
369
|
+
$remove: { $location: 'outgoing.data' },
|
|
370
|
+
},
|
|
371
|
+
},
|
|
372
|
+
});
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
**Example using cURL**
|
|
376
|
+
|
|
377
|
+
```bash
|
|
378
|
+
curl -H "Content-type: application/json" -X POST --location "localhost:8080/_mock/expectations" --data-binary @- << EOF
|
|
379
|
+
{
|
|
380
|
+
"schema": {
|
|
381
|
+
"request": {
|
|
382
|
+
"\$remove": {"\$location": "outgoing.data"}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
EOF
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
### $exec
|
|
390
|
+
|
|
391
|
+
> **!NOTE** `$exec` operators [have restrictions](#api) when it defined over `HTTP API` or `RemoteClient`
|
|
392
|
+
|
|
393
|
+
| Type (application) | Type (cURL) | Description |
|
|
394
|
+
|--|--|--|
|
|
395
|
+
| `(utils) => boolean ∣ unknown` | `string` | Does something you want or catch request/response payload in [context](#context) by function with arguments where `utils` is [utils](#utils) |
|
|
396
|
+
|
|
397
|
+
**Example using application**
|
|
398
|
+
|
|
399
|
+
```ts
|
|
400
|
+
await server.client.createExpectation({
|
|
401
|
+
schema: {
|
|
402
|
+
request: {
|
|
403
|
+
$exec: ({ context, logger }) => {
|
|
404
|
+
logger.info(context);
|
|
405
|
+
return context.incoming.path === '/foo';
|
|
406
|
+
},
|
|
407
|
+
},
|
|
408
|
+
},
|
|
409
|
+
});
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
**Example using cURL**
|
|
413
|
+
|
|
414
|
+
```bash
|
|
415
|
+
curl -H "Content-type: application/json" -X POST --location "localhost:8080/_mock/expectations" --data-binary @- << EOF
|
|
416
|
+
{
|
|
417
|
+
"schema": {
|
|
418
|
+
"request": {
|
|
419
|
+
"\$exec": "{ logger.info(context); return context.incoming.path === '/foo' }"
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
EOF
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
### $and
|
|
427
|
+
|
|
428
|
+
| Type (application) | Type (cURL) | Description |
|
|
429
|
+
|--|--|--|
|
|
430
|
+
| `object[]` | `object[]` | Provides [operators](#operators) schemas |
|
|
431
|
+
|
|
432
|
+
**Example using application**
|
|
433
|
+
|
|
434
|
+
```ts
|
|
435
|
+
await server.client.createExpectation({
|
|
436
|
+
schema: {
|
|
437
|
+
request: {
|
|
438
|
+
$and: [
|
|
439
|
+
{ $has: { $location: 'path', $match: 'foo/*' } },
|
|
440
|
+
{ $has: { $location: 'method', $valueAnyOf: ['GET', 'POST'] } },
|
|
441
|
+
],
|
|
442
|
+
},
|
|
443
|
+
},
|
|
444
|
+
});
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
**Example using cURL**
|
|
448
|
+
|
|
449
|
+
```bash
|
|
450
|
+
curl -H "Content-type: application/json" -X POST --location "localhost:8080/_mock/expectations" --data-binary @- << EOF
|
|
451
|
+
{
|
|
452
|
+
"schema": {
|
|
453
|
+
"request": {
|
|
454
|
+
"\$and": [
|
|
455
|
+
{"\$has": {"\$location": "path", "\$match": "foo/*"}},
|
|
456
|
+
{"\$has": {"\$location": "method", "\$valueAnyOf": ["GET", "POST"]}}
|
|
457
|
+
]
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
EOF
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
### $or
|
|
465
|
+
|
|
466
|
+
| Type (application) | Type (cURL) | Description |
|
|
467
|
+
|--|--|--|
|
|
468
|
+
| `object[]` | `object[]` | Provides [operators](#operators) schemas |
|
|
469
|
+
|
|
470
|
+
**Example using application**
|
|
471
|
+
|
|
472
|
+
```ts
|
|
473
|
+
await server.client.createExpectation({
|
|
474
|
+
schema: {
|
|
475
|
+
request: {
|
|
476
|
+
$or: [
|
|
477
|
+
{ $has: { $location: 'path', $match: 'foo/*' } },
|
|
478
|
+
{ $has: { $location: 'method', $valueAnyOf: ['GET', 'POST'] } },
|
|
479
|
+
],
|
|
480
|
+
},
|
|
481
|
+
},
|
|
482
|
+
});
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
**Example using cURL**
|
|
486
|
+
|
|
487
|
+
```bash
|
|
488
|
+
curl -H "Content-type: application/json" -X POST --location "localhost:8080/_mock/expectations" --data-binary @- << EOF
|
|
489
|
+
{
|
|
490
|
+
"schema": {
|
|
491
|
+
"request": {
|
|
492
|
+
"\$or": [
|
|
493
|
+
{"\$has": {"\$location": "path", "\$match": "foo/*"}},
|
|
494
|
+
{"\$has": {"\$location": "method", "\$valueAnyOf": ["GET", "POST"]}}
|
|
495
|
+
]
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
EOF
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
### $not
|
|
503
|
+
|
|
504
|
+
| Type (application) | Type (cURL) | Description |
|
|
505
|
+
|--|--|--|
|
|
506
|
+
| `object` | `object` | Provides an [operators](#operators) schema |
|
|
507
|
+
|
|
508
|
+
**Example using application**
|
|
509
|
+
|
|
510
|
+
```ts
|
|
511
|
+
await server.client.createExpectation({
|
|
512
|
+
schema: {
|
|
513
|
+
request: {
|
|
514
|
+
$not: { $has: { $location: 'path', $match: 'foo/*' } },
|
|
515
|
+
},
|
|
516
|
+
},
|
|
517
|
+
});
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
**Example using cURL**
|
|
521
|
+
|
|
522
|
+
```bash
|
|
523
|
+
curl -H "Content-type: application/json" -X POST --location "localhost:8080/_mock/expectations" --data-binary @- << EOF
|
|
524
|
+
{
|
|
525
|
+
"schema": {
|
|
526
|
+
"request": {
|
|
527
|
+
"\$not": {"\$has": {"\$location": "path", "\$match": "foo/*"}}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
EOF
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
### $if
|
|
535
|
+
|
|
536
|
+
| Property | Type (application) | Type (cURL) | Optional | Description |
|
|
537
|
+
|--|--|--|--|--|
|
|
538
|
+
| $condition | `object` | `object` | | Condition to check. Should contain one of `$and`, `$exec`, `$has`, `$or` or `$not` [operators](#operators) schema |
|
|
539
|
+
| $then | `object` | `object` | * | Logical `then`. Should contain an [operators](#operators) schema |
|
|
540
|
+
| $else | `object` | `object` | * | Logical `else`. Should contain an [operators](#operators) schema |
|
|
541
|
+
|
|
542
|
+
**Example using application**
|
|
543
|
+
|
|
544
|
+
```ts
|
|
545
|
+
await server.client.createExpectation({
|
|
546
|
+
schema: {
|
|
547
|
+
request: {
|
|
548
|
+
$if: {
|
|
549
|
+
$condition: { $has: { $location: 'path', $match: 'foo/*' } },
|
|
550
|
+
$then: { $set: { $location: 'delay', $value: 5000 } },
|
|
551
|
+
$else: { $set: { $location: 'error', $value: 'ECONNABORTED' } },
|
|
552
|
+
},
|
|
553
|
+
},
|
|
554
|
+
},
|
|
555
|
+
});
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
**Example using cURL**
|
|
559
|
+
|
|
560
|
+
```bash
|
|
561
|
+
curl -H "Content-type: application/json" -X POST --location "localhost:8080/_mock/expectations" --data-binary @- << EOF
|
|
562
|
+
{
|
|
563
|
+
"schema": {
|
|
564
|
+
"request": {
|
|
565
|
+
"\$if": {
|
|
566
|
+
"\$condition": {"\$has": {"\$location": "path", "\$match": "foo/*"}},
|
|
567
|
+
"\$then": {"\$set": {"\$location": "delay", "\$value": 5000}},
|
|
568
|
+
"\$else": {"\$set": {"\$location": "error", "\$value": "ECONNABORTED"}}
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
EOF
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
### $switch
|
|
577
|
+
|
|
578
|
+
> **!NOTE** `$exec` operators [have restrictions](#api) when it defined over `HTTP API` or `RemoteClient`
|
|
579
|
+
|
|
580
|
+
| Property | Type (application) | Type (cURL) | Optional | Description |
|
|
581
|
+
|--|--|--|--|--|
|
|
582
|
+
| $location | `string` [enum](#context) | `string` [enum](#context) | | Location that describes what [context](#context) entity is selecting for operator to work with |
|
|
583
|
+
| $cases | `Record<string ∣ number, object>` | `Record<string ∣ number, object>` | | An object where `key` is an extracted value from [enum](#context) using `$location` (and `$path`, `$exec` if it was specified) and `value` is an [operators](#operators) schema |
|
|
584
|
+
| $default | `object` | `object` | * | Default behavior as an [operators](#operators) schema |
|
|
585
|
+
| $path | `string` | `string` | * | Specifies a path to payload using [lodash get](https://lodash.com/docs/4.17.15#get) |
|
|
586
|
+
| $exec | `(payload, utils) => any` | `string` | * | Sets payload in [context](#context) by function with arguments where `payload` is selected entity using `$location` and `utils` is [utils](#utils) |
|
|
587
|
+
|
|
588
|
+
**Example using application**
|
|
589
|
+
|
|
590
|
+
```ts
|
|
591
|
+
await server.client.createExpectation({
|
|
592
|
+
schema: {
|
|
593
|
+
request: {
|
|
594
|
+
$switch: {
|
|
595
|
+
$location: 'method',
|
|
596
|
+
$cases: {
|
|
597
|
+
'GET': { $set: { $location: 'delay', $value: 2000 } },
|
|
598
|
+
'POST': { $set: { $location: 'delay', $value: 5000 } },
|
|
599
|
+
},
|
|
600
|
+
$default: {
|
|
601
|
+
$set: { $location: 'error', $value: 'ECONNABORTED' }
|
|
602
|
+
},
|
|
603
|
+
},
|
|
604
|
+
},
|
|
605
|
+
},
|
|
606
|
+
});
|
|
607
|
+
```
|
|
608
|
+
|
|
609
|
+
**Example using cURL**
|
|
610
|
+
|
|
611
|
+
```bash
|
|
612
|
+
curl -H "Content-type: application/json" -X POST --location "localhost:8080/_mock/expectations" --data-binary @- << EOF
|
|
613
|
+
{
|
|
614
|
+
"schema": {
|
|
615
|
+
"request": {
|
|
616
|
+
"\$switch": {
|
|
617
|
+
"\$location": "method",
|
|
618
|
+
"\$cases": {
|
|
619
|
+
"GET": {"\$set": {"\$location": "delay", "\$value": 2000}},
|
|
620
|
+
"POST": {"\$set": {"\$location": "delay", "\$value": 5000}}
|
|
621
|
+
},
|
|
622
|
+
"\$default": {
|
|
623
|
+
"\$set": {"\$location": "error", "\$value": "ECONNABORTED"}
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
EOF
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
## Typings
|
|
633
|
+
|
|
634
|
+
The application client lib provides approach to keep typings using function predicate to `create` or `update` expectation with a generic argument. The generic type should have the same schema like [context](#context)
|
|
635
|
+
|
|
636
|
+
The function predicate provides an object argument with `$` that contains simplified API to build typed expectation schemas. Some operators have `using` predicate that can contain `$path`, `$jsonPath` or `$exec` selectors
|
|
637
|
+
|
|
638
|
+
**Examples**
|
|
639
|
+
|
|
640
|
+
```ts
|
|
641
|
+
await client.createExpectation<{
|
|
642
|
+
incoming: {
|
|
643
|
+
query: {
|
|
644
|
+
foo: 'a' | 'b' | 'c';
|
|
645
|
+
bar?: string;
|
|
646
|
+
};
|
|
647
|
+
};
|
|
648
|
+
}>(({ $ }) => ({
|
|
649
|
+
schema: {
|
|
650
|
+
request: $.or([
|
|
651
|
+
$.has('incoming.query', '$path', 'foo', { $value: 'a' }),
|
|
652
|
+
$.has('incoming.query', { $match: { foo: 'b' } }),
|
|
653
|
+
]),
|
|
654
|
+
},
|
|
655
|
+
}));
|
|
656
|
+
```
|
|
657
|
+
|
|
658
|
+
```ts
|
|
659
|
+
await client.createExpectation<{
|
|
660
|
+
incoming: {
|
|
661
|
+
query: {
|
|
662
|
+
foo: 'a' | 'b' | 'c';
|
|
663
|
+
bar?: string;
|
|
664
|
+
};
|
|
665
|
+
};
|
|
666
|
+
outgoing: {
|
|
667
|
+
data: {
|
|
668
|
+
foo: 'a' | 'b' | 'c';
|
|
669
|
+
bar?: {
|
|
670
|
+
baz: 'a' | 'b' | 'c';
|
|
671
|
+
};
|
|
672
|
+
};
|
|
673
|
+
};
|
|
674
|
+
}>(({ $ }) => ({
|
|
675
|
+
schema: {
|
|
676
|
+
response: $.and([
|
|
677
|
+
$.switch('incoming.query', '$exec', (payload) => payload.foo, {
|
|
678
|
+
$cases: {
|
|
679
|
+
'a': $.set('outgoing.data', '$path', 'bar.baz', { $value: 'a' }),
|
|
680
|
+
'b': $.set('outgoing.data', '$path', 'bar.baz', { $value: 'b' }),
|
|
681
|
+
},
|
|
682
|
+
}),
|
|
683
|
+
|
|
684
|
+
$.switch('incoming.query', '$path', 'bar', {
|
|
685
|
+
$cases: {
|
|
686
|
+
'something': $.set('outgoing.data', '$path', 'bar.baz', { $value: 'c' }),
|
|
687
|
+
},
|
|
688
|
+
}),
|
|
689
|
+
]),
|
|
690
|
+
},
|
|
691
|
+
}));
|
|
692
|
+
```
|
|
693
|
+
|
|
694
|
+
## State
|
|
695
|
+
|
|
696
|
+
State is a unique storage of each request. It can be used to handle complex expectations
|
|
697
|
+
|
|
698
|
+
By default an object of state extracts from `X-Use-Mock-State` in `incoming.headers` (as serialized json in **base64 encoding**) or creates an empty object
|
|
699
|
+
|
|
700
|
+
**Example**
|
|
701
|
+
|
|
702
|
+
```ts
|
|
703
|
+
await client.createExpectation<{
|
|
704
|
+
state: {
|
|
705
|
+
id?: number;
|
|
706
|
+
};
|
|
707
|
+
incoming: {
|
|
708
|
+
query: {
|
|
709
|
+
foo: 'a' | 'b' | 'c';
|
|
710
|
+
};
|
|
711
|
+
};
|
|
712
|
+
outgoing: {
|
|
713
|
+
data: {
|
|
714
|
+
id: number;
|
|
715
|
+
};
|
|
716
|
+
};
|
|
717
|
+
}>(({ $ }) => ({
|
|
718
|
+
schema: {
|
|
719
|
+
request: $.and([
|
|
720
|
+
$.switch('incoming.query', '$exec', (payload) => payload.foo, {
|
|
721
|
+
$cases: {
|
|
722
|
+
'a': $.set('state', '$path', 'id', { $value: 1 }),
|
|
723
|
+
'b': $.set('state', '$path', 'id', { $value: 2 }),
|
|
724
|
+
},
|
|
725
|
+
}),
|
|
726
|
+
]),
|
|
727
|
+
response: $.set('outgoing.data', {
|
|
728
|
+
$exec: (payload, { state }) => ({ id: state.id ?? 0 }),
|
|
729
|
+
}),
|
|
730
|
+
},
|
|
731
|
+
}));
|
|
732
|
+
```
|
|
733
|
+
|
|
734
|
+
## Seeds
|
|
735
|
+
|
|
736
|
+
Seeds can help to generate content with the same values each request using [faker](https://www.npmjs.com/package/@faker-js/faker)
|
|
737
|
+
|
|
738
|
+
By default a number of seed takes from `X-Use-Mock-Seed` in `incoming.headers`
|
|
739
|
+
|
|
740
|
+
**Example**
|
|
741
|
+
|
|
742
|
+
```ts
|
|
743
|
+
await client.createExpectation(({ $ }) => ({
|
|
744
|
+
schema: {
|
|
745
|
+
request: $.and([
|
|
746
|
+
$.set('seed', { $exec: (seed) => seed ?? 123 }),
|
|
747
|
+
]),
|
|
748
|
+
response: $.set('outgoing.data', {
|
|
749
|
+
$exec: (payload, { faker }) => ({
|
|
750
|
+
id: faker.number.int({ max: 1000, min: 500 }),
|
|
751
|
+
first_name: faker.person.firstName('male'),
|
|
752
|
+
last_name: faker.person.lastName('male'),
|
|
753
|
+
}),
|
|
754
|
+
}),
|
|
755
|
+
},
|
|
756
|
+
}));
|
|
757
|
+
```
|
|
758
|
+
|
|
759
|
+
## XML
|
|
760
|
+
|
|
761
|
+
The mock server uses the [fast-xml-parser](https://www.npmjs.com/package/fast-xml-parser) package to parse and serialize XML payload with options:
|
|
762
|
+
|
|
763
|
+
```ts
|
|
764
|
+
{
|
|
765
|
+
ignoreAttributes: false,
|
|
766
|
+
}
|
|
767
|
+
```
|
|
768
|
+
|
|
769
|
+
To define a `incoming.data` as XML in incoming request `incoming.headers` should have `Content-Type: application/xml`.
|
|
770
|
+
|
|
771
|
+
The same with `outgoing.data` and `outgoing.headers`
|
|
772
|
+
|
|
773
|
+
**Example of serialized XML**
|
|
774
|
+
|
|
775
|
+
```xml
|
|
776
|
+
<tag type="default">
|
|
777
|
+
<nested type="nested">456</nested>
|
|
778
|
+
123
|
|
779
|
+
</tag>
|
|
780
|
+
```
|
|
781
|
+
|
|
782
|
+
**Example of parsed XML**
|
|
783
|
+
|
|
784
|
+
```json
|
|
785
|
+
{
|
|
786
|
+
"tag":{
|
|
787
|
+
"nested":{
|
|
788
|
+
"#text":456,
|
|
789
|
+
"@_type":"nested"
|
|
790
|
+
},
|
|
791
|
+
"#text":123,
|
|
792
|
+
"@_type":"default"
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
```
|
|
796
|
+
|
|
797
|
+
To parse an XML manually the application lib provides utils:
|
|
798
|
+
|
|
799
|
+
```ts
|
|
800
|
+
import { parsePayload, serializePayload } from '@n1k1t/mock-server';
|
|
801
|
+
|
|
802
|
+
const parsed = parsePayload('xml', '<tag>123</tag>'); // { tag: 123 }
|
|
803
|
+
const serialized = serializePayload('xml', parsed); // '<tag>123</tag>'
|
|
804
|
+
```
|
|
805
|
+
|
|
806
|
+
# API
|
|
807
|
+
|
|
808
|
+
The mock server provides 3 different ways to work with. There are: `HTTP API` (eg using cURL), `RemoteClient` provided by application lib to connect and work with existent mock server on another host and `MockServer.client` on the same host (application script)
|
|
809
|
+
|
|
810
|
+
The `HTTP API` and `RemoteClient` have some usage restrictions like:
|
|
811
|
+
- Every `$exec` operator **cannot have an access to variables outside the function**. If you need to use some extra variables or modules that implemented in outer scope you have to use the `MockServer.client` to setup everything on the mock server side host
|
|
812
|
+
- Plugins are not supported
|
|
813
|
+
|
|
814
|
+
## Ping
|
|
815
|
+
|
|
816
|
+
`INPUT` → `GET /_mock/ping`
|
|
817
|
+
|
|
818
|
+
`OUTPUT`
|
|
819
|
+
|
|
820
|
+
| Type | Description |
|
|
821
|
+
|--|--|
|
|
822
|
+
| `string` | A `pong` message |
|
|
823
|
+
|
|
824
|
+
**Using cURL**
|
|
825
|
+
|
|
826
|
+
```bash
|
|
827
|
+
curl -H "Content-type: application/json" --location "localhost:8080/_mock/ping"
|
|
828
|
+
```
|
|
829
|
+
|
|
830
|
+
**Using application lib on server side**
|
|
831
|
+
|
|
28
832
|
```ts
|
|
29
833
|
import { MockServer } from '@n1k1t/mock-server';
|
|
30
834
|
|
|
31
|
-
MockServer.start({ host: 'localhost', port: 8080 });
|
|
835
|
+
const server = await MockServer.start({ host: 'localhost', port: 8080 });
|
|
836
|
+
await server.client.ping();
|
|
837
|
+
```
|
|
838
|
+
|
|
839
|
+
**Using application lib on remotely**
|
|
840
|
+
|
|
841
|
+
```ts
|
|
842
|
+
import { RemoteClient } from '@n1k1t/mock-server';
|
|
843
|
+
|
|
844
|
+
const client = await RemoteClient.connect({ host: 'localhost', port: 8080 });
|
|
845
|
+
await client.ping();
|
|
32
846
|
```
|
|
33
847
|
|
|
34
|
-
|
|
848
|
+
## Create expectation
|
|
35
849
|
|
|
36
|
-
|
|
850
|
+
`INPUT` → `POST /_mock/expectations`
|
|
851
|
+
|
|
852
|
+
| Property | Nested | Type | Optional | Description |
|
|
853
|
+
|--|--|--|--|--|
|
|
854
|
+
| schema | [Schema](#schema) | `object` | | An expectation schema |
|
|
855
|
+
| name | | `string` | * | A preferred name for an expectation |
|
|
856
|
+
|
|
857
|
+
`OUTPUT`
|
|
858
|
+
|
|
859
|
+
| Property | Nested | Type | Optional | Description |
|
|
860
|
+
|--|--|--|--|--|
|
|
861
|
+
| id | | `string` | | An expectation ID |
|
|
862
|
+
| name | | `string` | | An expectation name |
|
|
863
|
+
| schema | [Schema](#schema) | `object` | | Provided schema |
|
|
864
|
+
|
|
865
|
+
**Using cURL**
|
|
37
866
|
|
|
38
867
|
```bash
|
|
39
|
-
|
|
40
|
-
|
|
868
|
+
curl -H "Content-type: application/json" -X POST --location "localhost:8080/_mock/expectations" --data-binary @- << EOF
|
|
869
|
+
{
|
|
870
|
+
"schema": {
|
|
871
|
+
"request": {
|
|
872
|
+
"\$has": {
|
|
873
|
+
"\$location": "method",
|
|
874
|
+
"\$value": "GET"
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
EOF
|
|
880
|
+
```
|
|
881
|
+
|
|
882
|
+
**Using application lib on server side**
|
|
883
|
+
|
|
884
|
+
```ts
|
|
885
|
+
import { MockServer } from '@n1k1t/mock-server';
|
|
886
|
+
|
|
887
|
+
const server = await MockServer.start({ host: 'localhost', port: 8080 });
|
|
888
|
+
const expectation = await server.client.createExpectation({
|
|
889
|
+
schema: {
|
|
890
|
+
request: {
|
|
891
|
+
$has: {
|
|
892
|
+
$location: 'method',
|
|
893
|
+
$value: 'GET',
|
|
894
|
+
},
|
|
895
|
+
},
|
|
896
|
+
},
|
|
897
|
+
});
|
|
898
|
+
|
|
899
|
+
console.log('Mock expectation has created', expectation.id);
|
|
41
900
|
```
|
|
42
901
|
|
|
43
|
-
Using
|
|
902
|
+
**Using application lib on remotely**
|
|
44
903
|
|
|
45
904
|
```ts
|
|
46
905
|
import { RemoteClient } from '@n1k1t/mock-server';
|
|
47
906
|
|
|
48
|
-
RemoteClient
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
host: 'localhost',
|
|
56
|
-
port: 80,
|
|
907
|
+
const client = await RemoteClient.connect({ host: 'localhost', port: 8080 });
|
|
908
|
+
const expectation = await client.createExpectation({
|
|
909
|
+
schema: {
|
|
910
|
+
request: {
|
|
911
|
+
$has: {
|
|
912
|
+
$location: 'method',
|
|
913
|
+
$value: 'GET',
|
|
57
914
|
},
|
|
58
|
-
}
|
|
59
|
-
}
|
|
915
|
+
},
|
|
916
|
+
},
|
|
917
|
+
});
|
|
918
|
+
|
|
919
|
+
console.log('Mock expectation has created', expectation.id);
|
|
60
920
|
```
|
|
61
921
|
|
|
62
|
-
|
|
922
|
+
## Update expectation
|
|
923
|
+
|
|
924
|
+
`INPUT` → `PUT /_mock/expectations`
|
|
925
|
+
|
|
926
|
+
| Property | Nested | Type | Optional | Description |
|
|
927
|
+
|--|--|--|--|--|
|
|
928
|
+
| id | | `string` | | ID of a registred expectation |
|
|
929
|
+
| set | | `object` | | A payload to set |
|
|
930
|
+
| | name | `string` | * | A preferred name for an expectation |
|
|
931
|
+
| | schema | [Schema](#schema) | * | An expectation schema |
|
|
932
|
+
|
|
933
|
+
`OUTPUT`
|
|
934
|
+
|
|
935
|
+
| Property | Nested | Type | Optional | Description |
|
|
936
|
+
|--|--|--|--|--|
|
|
937
|
+
| id | | `string` | | An expectation ID |
|
|
938
|
+
| name | | `string` | | An expectation name |
|
|
939
|
+
| schema | [Schema](#schema) | `object` | | Provided schema |
|
|
940
|
+
|
|
941
|
+
**Using cURL**
|
|
942
|
+
|
|
943
|
+
```bash
|
|
944
|
+
curl -H "Content-type: application/json" -X PUT --location "localhost:8080/_mock/expectations" --data-binary @- << EOF
|
|
945
|
+
{
|
|
946
|
+
"id": "...",
|
|
947
|
+
"set": {"name": "The expectation"}
|
|
948
|
+
}
|
|
949
|
+
EOF
|
|
950
|
+
```
|
|
951
|
+
|
|
952
|
+
**Using application lib on server side**
|
|
63
953
|
|
|
64
954
|
```ts
|
|
65
955
|
import { MockServer } from '@n1k1t/mock-server';
|
|
66
956
|
|
|
67
|
-
MockServer
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
957
|
+
const server = await MockServer.start({ host: 'localhost', port: 8080 });
|
|
958
|
+
const expectation = await server.client.updateExpectation({
|
|
959
|
+
id: '...',
|
|
960
|
+
set: { name: 'The expectation' }
|
|
961
|
+
});
|
|
962
|
+
|
|
963
|
+
console.log('Mock expectation has updated', expectation);
|
|
964
|
+
```
|
|
965
|
+
|
|
966
|
+
**Using application lib on remotely**
|
|
967
|
+
|
|
968
|
+
```ts
|
|
969
|
+
import { RemoteClient } from '@n1k1t/mock-server';
|
|
970
|
+
|
|
971
|
+
const client = await RemoteClient.connect({ host: 'localhost', port: 8080 });
|
|
972
|
+
const expectation = await client.updateExpectation({
|
|
973
|
+
id: '...',
|
|
974
|
+
set: { name: 'The expectation' }
|
|
975
|
+
});
|
|
976
|
+
|
|
977
|
+
console.log('Mock expectation has updated', expectation);
|
|
79
978
|
```
|
|
80
979
|
|
|
81
|
-
|
|
980
|
+
## Delete expectation
|
|
981
|
+
|
|
982
|
+
`INPUT` → `DELETE /_mock/expectations`
|
|
983
|
+
|
|
984
|
+
| Property | Nested | Type | Optional | Description |
|
|
985
|
+
|--|--|--|--|--|
|
|
986
|
+
| ids | | `string[]` | * | An expectation IDs list to delete. Or **delete all expectations** if not provided |
|
|
82
987
|
|
|
83
|
-
|
|
988
|
+
**Using cURL**
|
|
84
989
|
|
|
85
|
-
Example
|
|
86
990
|
```bash
|
|
87
|
-
localhost:8080/_mock/
|
|
991
|
+
curl -H "Content-type: application/json" -X DELETE --location "localhost:8080/_mock/expectations" --data-binary @- << EOF
|
|
992
|
+
{
|
|
993
|
+
"ids": ["..."]
|
|
994
|
+
}
|
|
995
|
+
EOF
|
|
996
|
+
```
|
|
997
|
+
|
|
998
|
+
**Using application lib on server side**
|
|
999
|
+
|
|
1000
|
+
```ts
|
|
1001
|
+
import { MockServer } from '@n1k1t/mock-server';
|
|
1002
|
+
|
|
1003
|
+
const server = await MockServer.start({ host: 'localhost', port: 8080 });
|
|
1004
|
+
await server.client.deleteExpectations({
|
|
1005
|
+
ids: ['...'],
|
|
1006
|
+
});
|
|
1007
|
+
```
|
|
1008
|
+
|
|
1009
|
+
**Using application lib on remotely**
|
|
1010
|
+
|
|
1011
|
+
```ts
|
|
1012
|
+
import { RemoteClient } from '@n1k1t/mock-server';
|
|
1013
|
+
|
|
1014
|
+
const client = await RemoteClient.connect({ host: 'localhost', port: 8080 });
|
|
1015
|
+
await client.deleteExpectations({
|
|
1016
|
+
ids: ['...'],
|
|
1017
|
+
});
|
|
1018
|
+
```
|
|
1019
|
+
|
|
1020
|
+
# Additional
|
|
1021
|
+
|
|
1022
|
+
## Configuration
|
|
1023
|
+
|
|
1024
|
+
> **!NOTE** Configuration must be provided in the same script like mock server
|
|
1025
|
+
|
|
1026
|
+
```ts
|
|
1027
|
+
import { config } from '@n1k1t/mock-server';
|
|
1028
|
+
|
|
1029
|
+
config.merge({
|
|
1030
|
+
logger: {
|
|
1031
|
+
level: 'D', // Logger level (default: D)
|
|
1032
|
+
},
|
|
1033
|
+
|
|
1034
|
+
gui: {
|
|
1035
|
+
title: 'My app', // Title for a GUI application page (default: Mock server)
|
|
1036
|
+
},
|
|
1037
|
+
|
|
1038
|
+
history: {
|
|
1039
|
+
limit: 100, // Limit for history of requests (default: 100)
|
|
1040
|
+
},
|
|
1041
|
+
});
|
|
1042
|
+
```
|
|
1043
|
+
|
|
1044
|
+
## Logger
|
|
1045
|
+
|
|
1046
|
+
> **!NOTE** Configuration must be provided in the same script like mock server
|
|
1047
|
+
|
|
1048
|
+
```ts
|
|
1049
|
+
import { Logger } from '@n1k1t/mock-server';
|
|
1050
|
+
|
|
1051
|
+
// It defines your own logger methods
|
|
1052
|
+
Logger.useExternal({
|
|
1053
|
+
debug: (...messages: string[]) => console.debug(...messages),
|
|
1054
|
+
info: (...messages: string[]) => console.log(...messages),
|
|
1055
|
+
warn: (...messages: string[]) => console.warn(...messages),
|
|
1056
|
+
error: (...messages: string[]) => console.error(...messages),
|
|
1057
|
+
fatal: (...messages: string[]) => console.error(...messages),
|
|
1058
|
+
});
|
|
1059
|
+
|
|
1060
|
+
// It defines a JSON serializers to mask some private data by keys on objects
|
|
1061
|
+
Logger.useSerializers({
|
|
1062
|
+
cvv: () => '***',
|
|
1063
|
+
card: (payload: string) => payload.slice(0, 8) + 'xxxx',
|
|
1064
|
+
});
|
|
1065
|
+
```
|
|
1066
|
+
|
|
1067
|
+
## Meta
|
|
1068
|
+
|
|
1069
|
+
Some loggers (like `banyan` and etc) provide a meta context for logs with some data. To keep a meta contexts between requests the mock server provides a `metaStorage` using native node `AsyncLocalStorage`.
|
|
1070
|
+
|
|
1071
|
+
The `metaStorage.provide()` returns an instance of `meta` that contains basic data like:
|
|
1072
|
+
|
|
1073
|
+
| Property | Type | Optional | Description |
|
|
1074
|
+
|--|--|--|--|
|
|
1075
|
+
| operationId | `string` | | UUID v4 |
|
|
1076
|
+
| requestId | `string` | * | `X-Request-Id` from `incoming.headers` |
|
|
1077
|
+
|
|
1078
|
+
**Setup**
|
|
1079
|
+
|
|
1080
|
+
```ts
|
|
1081
|
+
import { Logger, metaStorage } from '@n1k1t/mock-server';
|
|
1082
|
+
|
|
1083
|
+
// Some external logger with meta context support
|
|
1084
|
+
const external = {...};
|
|
1085
|
+
|
|
1086
|
+
// It defines your own logger methods
|
|
1087
|
+
Logger.useExternal({
|
|
1088
|
+
debug: (...messages: string[]) => external.debug(metaStorage.provide(), ...messages),
|
|
1089
|
+
info: (...messages: string[]) => external.log(metaStorage.provide(), ...messages),
|
|
1090
|
+
warn: (...messages: string[]) => external.warn(metaStorage.provide(), ...messages),
|
|
1091
|
+
error: (...messages: string[]) => external.error(metaStorage.provide(), ...messages),
|
|
1092
|
+
fatal: (...messages: string[]) => external.error(metaStorage.provide(), ...messages),
|
|
1093
|
+
});
|
|
1094
|
+
```
|
|
1095
|
+
|
|
1096
|
+
**Usage**
|
|
1097
|
+
|
|
1098
|
+
```ts
|
|
1099
|
+
await server.client.createExpectation({
|
|
1100
|
+
schema: {
|
|
1101
|
+
request: {
|
|
1102
|
+
$exec: ({ context, logger }) => {
|
|
1103
|
+
// Here logger should have a meta context like { operationId: '...' }
|
|
1104
|
+
logger.info('Before')
|
|
1105
|
+
},
|
|
1106
|
+
$exec: ({ context, logger, meta }) => {
|
|
1107
|
+
// It enriches meta context for further logs of request
|
|
1108
|
+
meta.merge({ foo: 'bar' });
|
|
1109
|
+
},
|
|
1110
|
+
$exec: ({ context, logger, meta }) => {
|
|
1111
|
+
// Now logger should have a meta context like { foo: 'bar', operationId: '...' }
|
|
1112
|
+
logger.info('After')
|
|
1113
|
+
},
|
|
1114
|
+
},
|
|
1115
|
+
},
|
|
1116
|
+
});
|
|
1117
|
+
```
|
|
1118
|
+
|
|
1119
|
+
## Plugins
|
|
1120
|
+
|
|
1121
|
+
> **!NOTE** Configuration must be provided in the same script like mock server
|
|
1122
|
+
|
|
1123
|
+
| Plugin | Description |
|
|
1124
|
+
|--|--|
|
|
1125
|
+
| `incoming.body` | Describes how to handle incoming body |
|
|
1126
|
+
| `outgoing.response` | Describes how to reply |
|
|
1127
|
+
| `forward.request` | Describes how provide an [axios](https://www.npmjs.com/package/axios) request config to forward a request |
|
|
1128
|
+
| `forward.response` | Describes how to parse [axios](https://www.npmjs.com/package/axios) response of a forwarded request |
|
|
1129
|
+
|
|
1130
|
+
**Example of `incoming.body` plugin**
|
|
1131
|
+
|
|
1132
|
+
```ts
|
|
1133
|
+
server.context.plugins.register('incoming.body', async (request) => {
|
|
1134
|
+
let raw = '';
|
|
1135
|
+
|
|
1136
|
+
request.on('data', chunk => raw += chunk);
|
|
1137
|
+
await new Promise(resolve => request.on('end', resolve));
|
|
1138
|
+
|
|
1139
|
+
return { raw };
|
|
1140
|
+
});
|
|
1141
|
+
```
|
|
1142
|
+
|
|
1143
|
+
**Example of `outgoing.response` plugin**
|
|
1144
|
+
|
|
1145
|
+
```ts
|
|
1146
|
+
server.context.plugins.register('outgoing.response', (response, context) => {
|
|
1147
|
+
context.response.writeHead(context.outgoing.status ?? 200, context.outgoing.headers);
|
|
1148
|
+
context.response.write(context.outgoing.dataRaw);
|
|
1149
|
+
context.response.end();
|
|
1150
|
+
});
|
|
1151
|
+
```
|
|
1152
|
+
|
|
1153
|
+
**Example of `forward.request` plugin**
|
|
1154
|
+
|
|
1155
|
+
```ts
|
|
1156
|
+
server.context.plugins.register('forward.request', (config) => ({
|
|
1157
|
+
...config,
|
|
1158
|
+
url: config.url.replace('/api_v1', '/api_v2'),
|
|
1159
|
+
}));
|
|
1160
|
+
```
|
|
1161
|
+
|
|
1162
|
+
**Example of `forward.response` plugin**
|
|
1163
|
+
|
|
1164
|
+
```ts
|
|
1165
|
+
server.context.plugins.register('forward.response', async (response: AxiosResponse<Buffer>) => ({
|
|
1166
|
+
raw: response.data.toString(),
|
|
1167
|
+
}));
|
|
88
1168
|
```
|