@n1k1t/mock-server 0.1.4 → 0.1.6

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.
Files changed (115) hide show
  1. package/README.md +992 -43
  2. package/lib/package.json +1 -1
  3. package/lib/src/config/model.d.ts +1 -0
  4. package/lib/src/config/model.d.ts.map +1 -1
  5. package/lib/src/config/model.js +1 -0
  6. package/lib/src/config/model.js.map +1 -1
  7. package/lib/src/expectations/models/expectation.d.ts +1 -1
  8. package/lib/src/expectations/models/expectation.d.ts.map +1 -1
  9. package/lib/src/expectations/models/expectation.js +4 -8
  10. package/lib/src/expectations/models/expectation.js.map +1 -1
  11. package/lib/src/expectations/models/operator.d.ts +2 -1
  12. package/lib/src/expectations/models/operator.d.ts.map +1 -1
  13. package/lib/src/expectations/models/operator.js.map +1 -1
  14. package/lib/src/expectations/models/storage.js +1 -1
  15. package/lib/src/expectations/models/storage.js.map +1 -1
  16. package/lib/src/expectations/operators/and.operator.d.ts +2 -1
  17. package/lib/src/expectations/operators/and.operator.d.ts.map +1 -1
  18. package/lib/src/expectations/operators/and.operator.js +3 -0
  19. package/lib/src/expectations/operators/and.operator.js.map +1 -1
  20. package/lib/src/expectations/operators/exec.operator.d.ts +2 -1
  21. package/lib/src/expectations/operators/exec.operator.d.ts.map +1 -1
  22. package/lib/src/expectations/operators/exec.operator.js +3 -0
  23. package/lib/src/expectations/operators/exec.operator.js.map +1 -1
  24. package/lib/src/expectations/operators/has.operator.d.ts +2 -1
  25. package/lib/src/expectations/operators/has.operator.d.ts.map +1 -1
  26. package/lib/src/expectations/operators/has.operator.js +33 -0
  27. package/lib/src/expectations/operators/has.operator.js.map +1 -1
  28. package/lib/src/expectations/operators/if.operator.d.ts +2 -1
  29. package/lib/src/expectations/operators/if.operator.d.ts.map +1 -1
  30. package/lib/src/expectations/operators/if.operator.js +7 -0
  31. package/lib/src/expectations/operators/if.operator.js.map +1 -1
  32. package/lib/src/expectations/operators/merge.operator.d.ts +2 -1
  33. package/lib/src/expectations/operators/merge.operator.d.ts.map +1 -1
  34. package/lib/src/expectations/operators/merge.operator.js +3 -0
  35. package/lib/src/expectations/operators/merge.operator.js.map +1 -1
  36. package/lib/src/expectations/operators/not.operator.d.ts +2 -1
  37. package/lib/src/expectations/operators/not.operator.d.ts.map +1 -1
  38. package/lib/src/expectations/operators/not.operator.js +3 -0
  39. package/lib/src/expectations/operators/not.operator.js.map +1 -1
  40. package/lib/src/expectations/operators/or.operator.d.ts +2 -1
  41. package/lib/src/expectations/operators/or.operator.d.ts.map +1 -1
  42. package/lib/src/expectations/operators/or.operator.js +3 -0
  43. package/lib/src/expectations/operators/or.operator.js.map +1 -1
  44. package/lib/src/expectations/operators/remove.operator.d.ts +2 -1
  45. package/lib/src/expectations/operators/remove.operator.d.ts.map +1 -1
  46. package/lib/src/expectations/operators/remove.operator.js +3 -0
  47. package/lib/src/expectations/operators/remove.operator.js.map +1 -1
  48. package/lib/src/expectations/operators/root.operator.d.ts +2 -1
  49. package/lib/src/expectations/operators/root.operator.d.ts.map +1 -1
  50. package/lib/src/expectations/operators/root.operator.js +3 -0
  51. package/lib/src/expectations/operators/root.operator.js.map +1 -1
  52. package/lib/src/expectations/operators/set.operator.d.ts +2 -1
  53. package/lib/src/expectations/operators/set.operator.d.ts.map +1 -1
  54. package/lib/src/expectations/operators/set.operator.js +13 -0
  55. package/lib/src/expectations/operators/set.operator.js.map +1 -1
  56. package/lib/src/expectations/operators/switch.operator.d.ts +2 -1
  57. package/lib/src/expectations/operators/switch.operator.d.ts.map +1 -1
  58. package/lib/src/expectations/operators/switch.operator.js +10 -0
  59. package/lib/src/expectations/operators/switch.operator.js.map +1 -1
  60. package/lib/src/expectations/types.d.ts +12 -16
  61. package/lib/src/expectations/types.d.ts.map +1 -1
  62. package/lib/src/expectations/types.js +3 -6
  63. package/lib/src/expectations/types.js.map +1 -1
  64. package/lib/src/expectations/utils.d.ts +1 -6
  65. package/lib/src/expectations/utils.d.ts.map +1 -1
  66. package/lib/src/expectations/utils.js +2 -33
  67. package/lib/src/expectations/utils.js.map +1 -1
  68. package/lib/src/index.d.ts +0 -1
  69. package/lib/src/index.d.ts.map +1 -1
  70. package/lib/src/index.js +0 -1
  71. package/lib/src/index.js.map +1 -1
  72. package/lib/src/logger/index.d.ts +2 -2
  73. package/lib/src/logger/index.d.ts.map +1 -1
  74. package/lib/src/logger/index.js +4 -2
  75. package/lib/src/logger/index.js.map +1 -1
  76. package/lib/src/logger/utils.d.ts.map +1 -1
  77. package/lib/src/logger/utils.js +6 -5
  78. package/lib/src/logger/utils.js.map +1 -1
  79. package/lib/src/server/endpoints/config.get.endpoint.d.ts +3 -0
  80. package/lib/src/server/endpoints/config.get.endpoint.d.ts.map +1 -1
  81. package/lib/src/server/endpoints/history.get-list.endpoint.d.ts +3 -3
  82. package/lib/src/server/history/model.d.ts +9 -13
  83. package/lib/src/server/history/model.d.ts.map +1 -1
  84. package/lib/src/server/history/model.js +14 -8
  85. package/lib/src/server/history/model.js.map +1 -1
  86. package/lib/src/server/history/storage.d.ts +1 -1
  87. package/lib/src/server/history/storage.d.ts.map +1 -1
  88. package/lib/src/server/history/storage.js +6 -6
  89. package/lib/src/server/history/storage.js.map +1 -1
  90. package/lib/src/server/history/types.d.ts +9 -1
  91. package/lib/src/server/history/types.d.ts.map +1 -1
  92. package/lib/src/server/middlewares/destroy.midleware.js +2 -2
  93. package/lib/src/server/middlewares/destroy.midleware.js.map +1 -1
  94. package/lib/src/server/middlewares/forward.middleware.d.ts.map +1 -1
  95. package/lib/src/server/middlewares/forward.middleware.js +22 -14
  96. package/lib/src/server/middlewares/forward.middleware.js.map +1 -1
  97. package/lib/src/server/middlewares/history.middleware.js +1 -1
  98. package/lib/src/server/middlewares/history.middleware.js.map +1 -1
  99. package/lib/src/server/middlewares/match-expectation.middleware.d.ts.map +1 -1
  100. package/lib/src/server/middlewares/match-expectation.middleware.js +10 -4
  101. package/lib/src/server/middlewares/match-expectation.middleware.js.map +1 -1
  102. package/lib/src/server/middlewares/reply.middleware.js +2 -2
  103. package/lib/src/server/middlewares/reply.middleware.js.map +1 -1
  104. package/lib/tsconfig.tsbuildinfo +1 -1
  105. package/package.json +1 -1
  106. package/public/index.html +13 -7
  107. package/public/scripts/main.js +1280 -534
  108. package/public/styles/main.css +264 -119
  109. package/screenshots/history.png +0 -0
  110. package/screenshots/preview.png +0 -0
  111. package/screenshots/strategy.png +0 -0
  112. package/lib/src/process.d.ts +0 -2
  113. package/lib/src/process.d.ts.map +0 -1
  114. package/lib/src/process.js +0 -7
  115. package/lib/src/process.js.map +0 -1
package/README.md CHANGED
@@ -1,88 +1,1037 @@
1
1
 
2
2
  # Mock server
3
3
 
4
- This package is actually what you need but everything about it will be described later...
4
+ Mock, match, modify and manipulate a HTTP request/response payload using flexible expectations with types
5
5
 
6
- ### Install
6
+ ![screenshot](screenshots/preview.png)
7
+
8
+ # Navigation
9
+
10
+ - [Basics](#basics)
11
+ - [How it works](#how-it-works)
12
+ - [Install](#install)
13
+ - [Start](#start)
14
+ - [GUI](#gui)
15
+ - [Mock](#mock)
16
+ - [Expectations](#expectations)
17
+ - [Schema](#schema)
18
+ - [Context](#context)
19
+ - [Utils](#utils)
20
+ - [Operators](#operators)
21
+ - [Typings](#typings)
22
+ - [API](#api)
23
+ - [Ping](#ping)
24
+ - [Create expectation](#create-expectation)
25
+ - [Update expectation](#update-expectation)
26
+ - [Delete expectation](#delete-expectation)
27
+ - [Additional](#additional)
28
+ - [Configuration](#configuration)
29
+ - [Logger](#logger)
30
+ - [Meta](#meta)
31
+ - [Plugins](#plugins)
32
+
33
+ # Basics
34
+
35
+ ## How it works
36
+
37
+ ![screenshot](screenshots/strategy.png)
38
+
39
+ 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
40
+
41
+ **In case of mocking without request forwarding:**
42
+
43
+ 1. Start mock server (for example on `localhost:8080`)
44
+ 2. Register expectation using CLI (cURL) or application lib
45
+ 3. Make request to `localhost:8080/...`
46
+ 1. The mock server matches a request payload with registred expectations
47
+ 2. Build a response using an expectation configuration
48
+
49
+ **In case of mocking with request forwarding:**
50
+
51
+ 0. Lets imagine that you have a service that hosts on `localhost:8081`
52
+ 1. Start mock server (for example on `localhost:8080`)
53
+ 2. Register expectation using CLI (cURL) or application lib
54
+ 3. Make request to `localhost:8080/...`
55
+ 1. The mock server matches a request payload with registred expectations
56
+ 2. Next is forwarding a request payload to `localhost:8081/...`
57
+ 3. Using response fetched from `localhost:8081/...` the mock server builds a response
58
+
59
+
60
+ ## Install
7
61
 
8
62
  ```bash
9
- npm i -g @n1k1t/mock-server
63
+ npm i -D @n1k1t/mock-server
10
64
  ```
11
65
 
12
- ### Start
66
+ ## Start
67
+
68
+ ### CLI
13
69
 
14
- Using console
15
70
  ```bash
16
- # It starts mock server on localhost:8080
17
- npx @n1k1t/mock-server -p 8080
71
+ npx @n1k1t/mock-server -h localhost -p 8080
18
72
  ```
19
73
 
20
- Using JavaScript
74
+ ### JavaScript
75
+
21
76
  ```js
22
77
  const { MockServer } = require('@n1k1t/mock-server');
78
+ MockServer.start({ host: 'localhost', port: 8080 });
79
+ ```
23
80
 
81
+ ### TypeScript
82
+
83
+ ```ts
84
+ import { MockServer } from '@n1k1t/mock-server';
24
85
  MockServer.start({ host: 'localhost', port: 8080 });
25
86
  ```
26
87
 
27
- Using TypeScript
88
+ ## GUI
89
+
90
+ The mock server provides built-in web panel to track everything that is going through. There are two tabs `Expectations` and `History`
91
+
92
+ By default it can be found on `/_mock/gui` of a host of mock server. Example: `localhost:8080/_mock/gui`
93
+
94
+ Also it provides convenient util to navigate through payload of expectations and requests payload
95
+
96
+ ![screenshot](screenshots/preview.png)
97
+
98
+ ## Mock
99
+
100
+ Simple examples can be found in [expectation creation API](#create-expectation)
101
+
102
+ # Expectations
103
+
104
+ ## Schema
105
+
106
+ An expectation schema can contain some rules to handle `request`, `response` and `forward`
107
+
108
+ | Property | Nested | Type | Optional | Description |
109
+ |--|--|--|--|--|
110
+ | request | [Operators](#operators) | `object` | * | Describes a way to catch by request and how to manipulate it |
111
+ | response | [Operators](#operators) | `object` | * | Describes how to manipulate response. Also can be used to catch response in case of forwarding |
112
+ | forward | | `object` | * | Describes configuration to forward a request to another host |
113
+ | | url | `string` | * | Absolute URL to target |
114
+ | | baseUrl | `string` | * | Base URL to target. The path will be provided from request |
115
+
116
+ **Example**
117
+
118
+ ```ts
119
+ await server.client.createExpectation({
120
+ schema: {
121
+ request: {
122
+ $and: [],
123
+ },
124
+ response: {
125
+ $or: [],
126
+ },
127
+ forward: {
128
+ baseUrl: 'https://example.com',
129
+ url: '/some/path',
130
+ },
131
+ },
132
+ });
133
+ ```
134
+
135
+ ## Context
136
+
137
+ | Property | Nested | `$location` | Type | Optional | Description |
138
+ |--|--|--|--|--|--|
139
+ | seed | | `seed` | `string` | * | Incoming request [seed](#seeds) |
140
+ | state | | `state` | `object` | | Incoming request [state](#state) with a custom data while request finish |
141
+ | incoming | | | `object` | | Payload with data of incoming request |
142
+ | | path | `path` | `string` | | Incoming request path |
143
+ | | method | `method` | `string` | | Incoming request method in **uppercase** |
144
+ | | headers | `incoming.headers` | `object` | | Incoming request headers with keys in **lowercase** |
145
+ | | bodyRaw | `incoming.bodyRaw` | `string` | | Incoming request source body |
146
+ | | body | `incoming.body` | `object` | * | Incoming request parsed body |
147
+ | | query | `incoming.query` | `object` | * | Incoming request query search parameters |
148
+ | | delay | `delay` | `number` | * | Delay that can be applied with [operators](#operators) |
149
+ | | error | `error` | `string` | * | Error that can be applied with [operators](#operators) |
150
+ | outgoing | | | `object` | | Payload with data of response |
151
+ | | status | `outgoing.status` | `number` | | Response status code |
152
+ | | headers | `outgoing.headers` | `number` | | Response headers |
153
+ | | dataRaw | `outgoing.dataRaw` | `string` | | Response source data |
154
+ | | data | `outgoing.data` | `any` | * | Response data |
155
+
156
+ ## Utils
157
+
158
+ Additional utils in `$exec` operator
159
+
160
+ | Property | Description |
161
+ |--|--|
162
+ | `context` | A request [context](#context) |
163
+ | `logger` | [Logger](#logger) of mock server |
164
+ | `mode` | A mode of expectation execution. Has `match` on catching request or `manipulate` on manipulation over [context](#context) |
165
+ | `meta` | A [meta](#meta) of a request |
166
+ | `_` | [Lodash](https://www.npmjs.com/package/lodash) |
167
+ | `d` | [DayJS](https://www.npmjs.com/package/dayjs) |
168
+ | `faker` | [Faker](https://www.npmjs.com/package/@faker-js/faker). Uses [seed](#seeds) if it was provided |
169
+
170
+ ## Operators
171
+
172
+ > **!NOTE** Each schema that using operators can have only one nested operator. To use more than one operator use `$and` or `$or` operators
173
+
174
+ | Operator | Optional | Description |
175
+ |--|--|--|
176
+ | [$has](#has) | * | Catches a request/response or checks a payload in [context](#context) |
177
+ | [$set](#set) | * | Sets payload in [context](#context) |
178
+ | [$merge](#merge) | * | Merges object payload in [context](#context) with provided `$value` |
179
+ | [$remove](#remove) | * | Removes payload in [context](#context) |
180
+ | [$exec](#exec) | * | Function to catch a request/response or check/manipulate payload in [context](#context) |
181
+ | [$and](#and) | * | Logical `and` |
182
+ | [$or](#or) | * | Logical `or` |
183
+ | [$not](#not) | * | Logical `not` |
184
+ | [$if](#if) | * | Logical `if` |
185
+ | [$switch](#switch) | * | Logical `switch/case` |
186
+
187
+ **Example**
188
+
189
+ ```ts
190
+ await server.client.createExpectation({
191
+ schema: {
192
+ request: {
193
+ $and: [
194
+ {
195
+ $has: {
196
+ $location: 'path',
197
+ $value: '/foo',
198
+ },
199
+ },
200
+ {
201
+ $has: {
202
+ $location: 'method',
203
+ $value: 'GET',
204
+ },
205
+ },
206
+ ],
207
+ },
208
+ },
209
+ });
210
+ ```
211
+
212
+ ### $has
213
+
214
+ | Property | Type (application) | Type (cURL) | Optional | Description |
215
+ |--|--|--|--|--|
216
+ | $location | `string` [enum](#context) | `string` [enum](#context) | | Location that describes what [context](#context) entity is selecting for operator to work with |
217
+ | $path | `string` | `string` | * | Specifies a path to payload using [lodash get](https://lodash.com/docs/4.17.15#get) |
218
+ | $jsonPath | `string` | `string` | * | Specifies a path to payload using [JSON path](https://www.npmjs.com/package/jsonpath-plus) |
219
+ | $value | `any` | `any` | * | Checks by value equality in [context](#context) using `$location` (and `$path`, `$jsonPath` if it was specified) |
220
+ | $valueAnyOf | `any[]` | `any[]` | * | Checks by any of value equality in [context](#context) using `$location` (and `$path`, `$jsonPath` if it was specified) |
221
+ | $regExp | `RegExp` | `{ source: string, flags?: string }` | * | Checks by regular expression in context using `$location` (and `$path`, `$jsonPath` if it was specified) |
222
+ | $regExpAnyOf | `RegExp[]` | `{ source: string, flags?: string }[]` | * | Checks by any of regular expression in [context](#context) using `$location` (and `$path`, `$jsonPath` if it was specified) |
223
+ | $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) |
224
+ | $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) |
225
+ | $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) |
226
+
227
+ **Example using application**
228
+
229
+ ```ts
230
+ await server.client.createExpectation({
231
+ schema: {
232
+ request: {
233
+ $has: {
234
+ $location: 'path',
235
+ $regExp: /^\/foo/,
236
+ },
237
+ },
238
+ },
239
+ });
240
+ ```
241
+
242
+ **Example using cURL**
243
+
244
+ ```bash
245
+ curl -H "Content-type: application/json" -X POST --location "localhost:8080/_mock/expectations" --data-binary @- << EOF
246
+ {
247
+ "schema": {
248
+ "request": {
249
+ "\$has": {
250
+ "\$location": "method",
251
+ "\$regExp": { "source": "^\/foo" }
252
+ }
253
+ }
254
+ }
255
+ }
256
+ EOF
257
+ ```
258
+
259
+ ### $set
260
+
261
+ | Property | Type (application) | Type (cURL) | Optional | Description |
262
+ |--|--|--|--|--|
263
+ | $location | `string` [enum](#context) | `string` [enum](#context) | | Location that describes what [context](#context) entity is selecting for operator to work with |
264
+ | $path | `string` | `string` | * | Specifies a path to payload using [lodash get](https://lodash.com/docs/4.17.15#get) |
265
+ | $jsonPath | `string` | `string` | * | Specifies a path to payload using [JSON path](https://www.npmjs.com/package/jsonpath-plus) |
266
+ | $value | `any` | `any` | * | Sets value to [context](#context) using `$location` (and `$path`, `$jsonPath` if it was specified) |
267
+ | $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) |
268
+
269
+ **Example using application**
270
+
271
+ ```ts
272
+ await server.client.createExpectation({
273
+ schema: {
274
+ request: {
275
+ $set: {
276
+ $location: 'incoming.body',
277
+ $path: 'foo',
278
+ $exec: (payload, { _ }) => _.clamp(payload, 0, 10),
279
+ },
280
+ },
281
+ },
282
+ });
283
+ ```
284
+
285
+ **Example using cURL**
286
+
287
+ ```bash
288
+ curl -H "Content-type: application/json" -X POST --location "localhost:8080/_mock/expectations" --data-binary @- << EOF
289
+ {
290
+ "schema": {
291
+ "request": {
292
+ "\$set": {
293
+ "\$location": "incoming.body",
294
+ "\$path": "foo",
295
+ "\$exec": "_.clamp(payload, 0, 10)"
296
+ }
297
+ }
298
+ }
299
+ }
300
+ EOF
301
+ ```
302
+
303
+ ### $merge
304
+
305
+ | Property | Type (application) | Type (cURL) | Optional | Description |
306
+ |--|--|--|--|--|
307
+ | $location | `string` [enum](#context) | `string` [enum](#context) | | Location that describes what [context](#context) entity is selecting for operator to work with |
308
+ | $path | `string` | `string` | * | Specifies a path to payload using [lodash get](https://lodash.com/docs/4.17.15#get) |
309
+ | $jsonPath | `string` | `string` | * | Specifies a path to payload using [JSON path](https://www.npmjs.com/package/jsonpath-plus) |
310
+ | $value | `any` | `any` | | Merges value in [context](#context) using `$location` (and `$path`, `$jsonPath` if it was specified) |
311
+
312
+ **Example using application**
313
+
314
+ ```ts
315
+ await server.client.createExpectation({
316
+ schema: {
317
+ request: {
318
+ $merge: {
319
+ $location: 'incoming.body',
320
+ $value: { has_mocked: true },
321
+ },
322
+ },
323
+ },
324
+ });
325
+ ```
326
+
327
+ **Example using cURL**
328
+
329
+ ```bash
330
+ curl -H "Content-type: application/json" -X POST --location "localhost:8080/_mock/expectations" --data-binary @- << EOF
331
+ {
332
+ "schema": {
333
+ "request": {
334
+ "\$merge": {
335
+ "\$location": "incoming.body",
336
+ "\$value": {"has_mocked": true}
337
+ }
338
+ }
339
+ }
340
+ }
341
+ EOF
342
+ ```
343
+
344
+ ### $remove
345
+
346
+ | Property | Type (application) | Type (cURL) | Optional | Description |
347
+ |--|--|--|--|--|
348
+ | $location | `string` [enum](#context) | `string` [enum](#context) | | Location that describes what [context](#context) entity is selecting for operator to work with |
349
+ | $path | `string` | `string` | * | Specifies a path to payload using [lodash get](https://lodash.com/docs/4.17.15#get) |
350
+ | $jsonPath | `string` | `string` | * | Specifies a path to payload using [JSON path](https://www.npmjs.com/package/jsonpath-plus) |
351
+
352
+ **Example using application**
353
+
354
+ ```ts
355
+ await server.client.createExpectation({
356
+ schema: {
357
+ request: {
358
+ $remove: { $location: 'outgoing.data' },
359
+ },
360
+ },
361
+ });
362
+ ```
363
+
364
+ **Example using cURL**
365
+
366
+ ```bash
367
+ curl -H "Content-type: application/json" -X POST --location "localhost:8080/_mock/expectations" --data-binary @- << EOF
368
+ {
369
+ "schema": {
370
+ "request": {
371
+ "\$remove": {"\$location": "outgoing.data"}
372
+ }
373
+ }
374
+ }
375
+ EOF
376
+ ```
377
+
378
+ ### $exec
379
+
380
+ | Type (application) | Type (cURL) | Description |
381
+ |--|--|--|--|--|
382
+ | `(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) |
383
+
384
+ **Example using application**
385
+
386
+ ```ts
387
+ await server.client.createExpectation({
388
+ schema: {
389
+ request: {
390
+ $exec: ({ context, logger }) => {
391
+ logger.info(context);
392
+ return context.incoming.path === '/foo';
393
+ },
394
+ },
395
+ },
396
+ });
397
+ ```
398
+
399
+ **Example using cURL**
400
+
401
+ ```bash
402
+ curl -H "Content-type: application/json" -X POST --location "localhost:8080/_mock/expectations" --data-binary @- << EOF
403
+ {
404
+ "schema": {
405
+ "request": {
406
+ "\$exec": "{ logger.info(context); return context.incoming.path === '/foo' }"
407
+ }
408
+ }
409
+ }
410
+ EOF
411
+ ```
412
+
413
+ ### $and
414
+
415
+ | Type (application) | Type (cURL) | Description |
416
+ |--|--|--|--|--|
417
+ | `object[]` | `object[]` | Provides [operators](#operators) schemas |
418
+
419
+ **Example using application**
420
+
421
+ ```ts
422
+ await server.client.createExpectation({
423
+ schema: {
424
+ request: {
425
+ $and: [
426
+ { $has: { $location: 'path', $match: 'foo/*' } },
427
+ { $has: { $location: 'method', $valueAnyOf: ['GET', 'POST'] } },
428
+ ],
429
+ },
430
+ },
431
+ });
432
+ ```
433
+
434
+ **Example using cURL**
435
+
436
+ ```bash
437
+ curl -H "Content-type: application/json" -X POST --location "localhost:8080/_mock/expectations" --data-binary @- << EOF
438
+ {
439
+ "schema": {
440
+ "request": {
441
+ "\$and": [
442
+ {"\$has": {"\$location": "path", "\$match": "foo/*"}},
443
+ {"\$has": {"\$location": "method", "\$valueAnyOf": ["GET", "POST"]}}
444
+ ]
445
+ }
446
+ }
447
+ }
448
+ EOF
449
+ ```
450
+
451
+ ### $or
452
+
453
+ | Type (application) | Type (cURL) | Description |
454
+ |--|--|--|--|--|
455
+ | `object[]` | `object[]` | Provides [operators](#operators) schemas |
456
+
457
+ **Example using application**
458
+
459
+ ```ts
460
+ await server.client.createExpectation({
461
+ schema: {
462
+ request: {
463
+ $or: [
464
+ { $has: { $location: 'path', $match: 'foo/*' } },
465
+ { $has: { $location: 'method', $valueAnyOf: ['GET', 'POST'] } },
466
+ ],
467
+ },
468
+ },
469
+ });
470
+ ```
471
+
472
+ **Example using cURL**
473
+
474
+ ```bash
475
+ curl -H "Content-type: application/json" -X POST --location "localhost:8080/_mock/expectations" --data-binary @- << EOF
476
+ {
477
+ "schema": {
478
+ "request": {
479
+ "\$or": [
480
+ {"\$has": {"\$location": "path", "\$match": "foo/*"}},
481
+ {"\$has": {"\$location": "method", "\$valueAnyOf": ["GET", "POST"]}}
482
+ ]
483
+ }
484
+ }
485
+ }
486
+ EOF
487
+ ```
488
+
489
+ ### $not
490
+
491
+ | Type (application) | Type (cURL) | Description |
492
+ |--|--|--|--|--|
493
+ | `object` | `object` | Provides an [operators](#operators) schema |
494
+
495
+ **Example using application**
496
+
497
+ ```ts
498
+ await server.client.createExpectation({
499
+ schema: {
500
+ request: {
501
+ $not: { $has: { $location: 'path', $match: 'foo/*' } },
502
+ },
503
+ },
504
+ });
505
+ ```
506
+
507
+ **Example using cURL**
508
+
509
+ ```bash
510
+ curl -H "Content-type: application/json" -X POST --location "localhost:8080/_mock/expectations" --data-binary @- << EOF
511
+ {
512
+ "schema": {
513
+ "request": {
514
+ "\$not": {"\$has": {"\$location": "path", "\$match": "foo/*"}}
515
+ }
516
+ }
517
+ }
518
+ EOF
519
+ ```
520
+
521
+ ### $if
522
+
523
+ | Property | Type (application) | Type (cURL) | Optional | Description |
524
+ |--|--|--|--|--|
525
+ | $condition | `object` | `object` | | Condition to check. Should contain one of `$and`, `$exec`, `$has`, `$or` or `$not` [operators](#operators) schema |
526
+ | $then | `object` | `object` | * | Logical `then`. Should contain an [operators](#operators) schema |
527
+ | $else | `object` | `object` | * | Logical `else`. Should contain an [operators](#operators) schema |
528
+
529
+ **Example using application**
530
+
531
+ ```ts
532
+ await server.client.createExpectation({
533
+ schema: {
534
+ request: {
535
+ $if: {
536
+ $condition: { $has: { $location: 'path', $match: 'foo/*' } },
537
+ $then: { $set: { $location: 'delay', $value: 5000 } },
538
+ $else: { $set: { $location: 'error', $value: 'ECONNABORTED' } },
539
+ },
540
+ },
541
+ },
542
+ });
543
+ ```
544
+
545
+ **Example using cURL**
546
+
547
+ ```bash
548
+ curl -H "Content-type: application/json" -X POST --location "localhost:8080/_mock/expectations" --data-binary @- << EOF
549
+ {
550
+ "schema": {
551
+ "request": {
552
+ "\$if": {
553
+ "\$condition": {"\$has": {"\$location": "path", "\$match": "foo/*"}},
554
+ "\$then": {"\$set": {"\$location": "delay", "\$value": 5000}},
555
+ "\$else": {"\$set": {"\$location": "error", "\$value": "ECONNABORTED"}}
556
+ }
557
+ }
558
+ }
559
+ }
560
+ EOF
561
+ ```
562
+
563
+ ### $switch
564
+
565
+ | Property | Type (application) | Type (cURL) | Optional | Description |
566
+ |--|--|--|--|--|
567
+ | $location | `string` [enum](#context) | `string` [enum](#context) | | Location that describes what [context](#context) entity is selecting for operator to work with |
568
+ | $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 |
569
+ | $default | `Record<string | number, object>` | `Record<string | number, object>` | * | Default behavior as an [operators](#operators) schema |
570
+ | $path | `string` | `string` | * | Specifies a path to payload using [lodash get](https://lodash.com/docs/4.17.15#get) |
571
+ | $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) |
572
+
573
+ **Example using application**
574
+
575
+ ```ts
576
+ await server.client.createExpectation({
577
+ schema: {
578
+ request: {
579
+ $switch: {
580
+ $location: 'method',
581
+ $cases: {
582
+ 'GET': { $set: { $location: 'delay', $value: 2000 } },
583
+ 'POST': { $set: { $location: 'delay', $value: 5000 } },
584
+ },
585
+ $default: {
586
+ $set: { $location: 'error', $value: 'ECONNABORTED' }
587
+ },
588
+ },
589
+ },
590
+ },
591
+ });
592
+ ```
593
+
594
+ **Example using cURL**
595
+
596
+ ```bash
597
+ curl -H "Content-type: application/json" -X POST --location "localhost:8080/_mock/expectations" --data-binary @- << EOF
598
+ {
599
+ "schema": {
600
+ "request": {
601
+ "\$switch": {
602
+ "\$location": "method",
603
+ "\$cases": {
604
+ "GET": {"\$set": {"\$location": "delay", "\$value": 2000}},
605
+ "POST": {"\$set": {"\$location": "delay", "\$value": 5000}}
606
+ },
607
+ "\$default": {
608
+ "\$set": {"\$location": "error", "\$value": "ECONNABORTED"}
609
+ }
610
+ }
611
+ }
612
+ }
613
+ }
614
+ EOF
615
+ ```
616
+
617
+ ## Typings
618
+
619
+ 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)
620
+
621
+ 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
622
+
623
+ **Examples**
624
+
625
+ ```ts
626
+ await client.createExpectation<{
627
+ incoming: {
628
+ query: {
629
+ foo: 'a' | 'b' | 'c';
630
+ bar?: string;
631
+ };
632
+ };
633
+ }>(({ $ }) => ({
634
+ schema: {
635
+ request: $.or([
636
+ $.has('incoming.query', '$path', 'foo', { $value: 'a' }),
637
+ $.has('incoming.query', { $match: { foo: 'b' } }),
638
+ ]),
639
+ },
640
+ }));
641
+ ```
642
+
643
+ ```ts
644
+ await client.createExpectation<{
645
+ incoming: {
646
+ query: {
647
+ foo: 'a' | 'b' | 'c';
648
+ bar?: string;
649
+ };
650
+ };
651
+ outgoing: {
652
+ data: {
653
+ foo: 'a' | 'b' | 'c';
654
+ bar?: {
655
+ baz: 'a' | 'b' | 'c';
656
+ };
657
+ };
658
+ };
659
+ }>(({ $ }) => ({
660
+ schema: {
661
+ response: $.and([
662
+ $.switch('incoming.query', '$exec', (payload) => payload.foo, {
663
+ $cases: {
664
+ 'a': $.set('outgoing.data', '$path', 'bar.baz', { $value: 'a' }),
665
+ 'b': $.set('outgoing.data', '$path', 'bar.baz', { $value: 'b' }),
666
+ },
667
+ }),
668
+
669
+ $.switch('incoming.query', '$path', 'bar', {
670
+ $cases: {
671
+ 'something': $.set('outgoing.data', '$path', 'bar.baz', { $value: 'c' }),
672
+ },
673
+ }),
674
+ ]),
675
+ },
676
+ }));
677
+ ```
678
+
679
+ # API
680
+
681
+ In general, there are 3 ways to register a mock expectation. Using cURL (or any HTTP client), via server instance or remote client (in case when the server is listening on another host)
682
+
683
+ ## Ping
684
+
685
+ `INPUT` → `GET /_mock/ping`
686
+
687
+ `OUTPUT`
688
+
689
+ | Type | Description |
690
+ |--|--|
691
+ | `string` | A `pong` message |
692
+
693
+ ### Using cURL
694
+
695
+ ```bash
696
+ curl -H "Content-type: application/json" --location "localhost:8080/_mock/ping"
697
+ ```
698
+
699
+ ### Using application lib on server side
700
+
28
701
  ```ts
29
702
  import { MockServer } from '@n1k1t/mock-server';
30
703
 
31
- MockServer.start({ host: 'localhost', port: 8080 });
704
+ const server = await MockServer.start({ host: 'localhost', port: 8080 });
705
+ await server.client.ping();
706
+ ```
707
+
708
+ ### Using application lib on remotely
709
+
710
+ ```ts
711
+ import { RemoteClient } from '@n1k1t/mock-server';
712
+
713
+ const client = await RemoteClient.connect({ host: 'localhost', port: 8080 });
714
+ await client.ping();
32
715
  ```
33
716
 
34
- ### Add expectations
717
+ ## Create expectation
718
+
719
+ `INPUT` → `POST /_mock/expectations`
720
+
721
+ | Property | Nested | Type | Optional | Description |
722
+ |--|--|--|--|--|
723
+ | schema | [Schema](#schema) | `object` | | An expectation schema |
724
+ | name | | `string` | * | A preferred name for an expectation |
725
+
726
+ `OUTPUT`
727
+
728
+ | Property | Nested | Type | Optional | Description |
729
+ |--|--|--|--|--|
730
+ | id | | `string` | | An expectation ID |
731
+ | name | | `string` | | An expectation name |
732
+ | schema | [Schema](#schema) | `object` | | Provided schema |
35
733
 
36
- Using curl
734
+ ### Using cURL
37
735
 
38
736
  ```bash
39
- # Create a passtrough expectation to port 80
40
- curl -X POST -d '{"forward": {"protocol": "HTTP", "host": "localhost", "port": 80}}' 'localhost:8080/_mock/expectations'
737
+ curl -H "Content-type: application/json" -X POST --location "localhost:8080/_mock/expectations" --data-binary @- << EOF
738
+ {
739
+ "schema": {
740
+ "request": {
741
+ "\$has": {
742
+ "\$location": "method",
743
+ "\$value": "GET"
744
+ }
745
+ }
746
+ }
747
+ }
748
+ EOF
41
749
  ```
42
750
 
43
- Using remote client
751
+ ### Using application lib on server side
752
+
753
+ ```ts
754
+ import { MockServer } from '@n1k1t/mock-server';
755
+
756
+ const server = await MockServer.start({ host: 'localhost', port: 8080 });
757
+ const expectation = await server.client.createExpectation({
758
+ schema: {
759
+ request: {
760
+ $has: {
761
+ $location: 'method',
762
+ $value: 'GET',
763
+ },
764
+ },
765
+ },
766
+ });
767
+
768
+ console.log('Mock expectation has created', expectation.id);
769
+ ```
770
+
771
+ ### Using application lib on remotely
44
772
 
45
773
  ```ts
46
774
  import { RemoteClient } from '@n1k1t/mock-server';
47
775
 
48
- RemoteClient
49
- .connect({ host: 'localhost', port: 8080 })
50
- .then(async (client) => {
51
- await client.createExpectation({
52
- forward: {
53
- timeout: 2 * 60 * 1000,
54
- protocol: 'HTTP',
55
- host: 'localhost',
56
- port: 80,
776
+ const client = await RemoteClient.connect({ host: 'localhost', port: 8080 });
777
+ const expectation = await client.createExpectation({
778
+ schema: {
779
+ request: {
780
+ $has: {
781
+ $location: 'method',
782
+ $value: 'GET',
57
783
  },
58
- });
59
- });
784
+ },
785
+ },
786
+ });
787
+
788
+ console.log('Mock expectation has created', expectation.id);
60
789
  ```
61
790
 
62
- Using client on mock server side
791
+ ## Update expectation
792
+
793
+ `INPUT` → `PUT /_mock/expectations`
794
+
795
+ | Property | Nested | Type | Optional | Description |
796
+ |--|--|--|--|--|
797
+ | id | | `string` | | ID of a registred expectation |
798
+ | set | | `object` | | A payload to set |
799
+ | | name | `string` | * | A preferred name for an expectation |
800
+ | | schema | [Schema](#schema) | * | An expectation schema |
801
+
802
+ `OUTPUT`
803
+
804
+ | Property | Nested | Type | Optional | Description |
805
+ |--|--|--|--|--|
806
+ | id | | `string` | | An expectation ID |
807
+ | name | | `string` | | An expectation name |
808
+ | schema | [Schema](#schema) | `object` | | Provided schema |
809
+
810
+ ### Using cURL
811
+
812
+ ```bash
813
+ curl -H "Content-type: application/json" -X PUT --location "localhost:8080/_mock/expectations" --data-binary @- << EOF
814
+ {
815
+ "id": "...",
816
+ "set": {"name": "The expectation"}
817
+ }
818
+ EOF
819
+ ```
820
+
821
+ ### Using application lib on server side
63
822
 
64
823
  ```ts
65
824
  import { MockServer } from '@n1k1t/mock-server';
66
825
 
67
- MockServer
68
- .start({ host: 'localhost', port: 8080 })
69
- .then(async ({ client }) => {
70
- await client.createExpectation({
71
- forward: {
72
- timeout: 2 * 60 * 1000,
73
- protocol: 'HTTP',
74
- host: 'localhost',
75
- port: 80,
76
- },
77
- });
78
- });
826
+ const server = await MockServer.start({ host: 'localhost', port: 8080 });
827
+ const expectation = await server.client.updateExpectation({
828
+ id: '...',
829
+ set: { name: 'The expectation' }
830
+ });
831
+
832
+ console.log('Mock expectation has updated', expectation);
79
833
  ```
80
834
 
81
- ### GUI
835
+ ### Using application lib on remotely
836
+
837
+ ```ts
838
+ import { RemoteClient } from '@n1k1t/mock-server';
839
+
840
+ const client = await RemoteClient.connect({ host: 'localhost', port: 8080 });
841
+ const expectation = await client.updateExpectation({
842
+ id: '...',
843
+ set: { name: 'The expectation' }
844
+ });
845
+
846
+ console.log('Mock expectation has updated', expectation);
847
+ ```
82
848
 
83
- To access the GUI of mock server you have to navigate on `/_mock/gui/`
849
+ ## Delete expectation
850
+
851
+ `INPUT` → `DELETE /_mock/expectations`
852
+
853
+ | Property | Nested | Type | Optional | Description |
854
+ |--|--|--|--|--|
855
+ | ids | | `string[]` | * | An expectation IDs list to delete. Or **delete all expectations** if not provided |
856
+
857
+ ### Using cURL
84
858
 
85
- Example
86
859
  ```bash
87
- localhost:8080/_mock/gui/
860
+ curl -H "Content-type: application/json" -X DELETE --location "localhost:8080/_mock/expectations" --data-binary @- << EOF
861
+ {
862
+ "ids": ["..."]
863
+ }
864
+ EOF
865
+ ```
866
+
867
+ ### Using application lib on server side
868
+
869
+ ```ts
870
+ import { MockServer } from '@n1k1t/mock-server';
871
+
872
+ const server = await MockServer.start({ host: 'localhost', port: 8080 });
873
+ await server.client.deleteExpectations({
874
+ ids: ['...'],
875
+ });
876
+ ```
877
+
878
+ ### Using application lib on remotely
879
+
880
+ ```ts
881
+ import { RemoteClient } from '@n1k1t/mock-server';
882
+
883
+ const client = await RemoteClient.connect({ host: 'localhost', port: 8080 });
884
+ await client.deleteExpectations({
885
+ ids: ['...'],
886
+ });
887
+ ```
888
+
889
+ # Additional
890
+
891
+ ## Configuration
892
+
893
+ > **!NOTE** Configuration must be provided in the same script like mock server
894
+
895
+ ```ts
896
+ import { config } from '@n1k1t/mock-server';
897
+
898
+ config.merge({
899
+ logger: {
900
+ level: 'D', // Logger level (default: D)
901
+ },
902
+
903
+ gui: {
904
+ title: 'My app', // Title for a GUI application page (default: Mock server)
905
+ },
906
+
907
+ history: {
908
+ limit: 100, // Limit for history of requests (default: 100)
909
+ },
910
+ });
911
+ ```
912
+
913
+ ## Logger
914
+
915
+ > **!NOTE** Configuration must be provided in the same script like mock server
916
+
917
+ ```ts
918
+ import { Logger } from '@n1k1t/mock-server';
919
+
920
+ // It defines your own logger methods
921
+ Logger.useExternal({
922
+ debug: (...messages: string[]) => console.debug(...messages),
923
+ info: (...messages: string[]) => console.log(...messages),
924
+ warn: (...messages: string[]) => console.warn(...messages),
925
+ error: (...messages: string[]) => console.error(...messages),
926
+ fatal: (...messages: string[]) => console.error(...messages),
927
+ });
928
+
929
+ // It defines a JSON serializers to mask some private data by keys on objects
930
+ Logger.useSerializers({
931
+ cvv: () => '***',
932
+ card: (payload: string) => payload.slice(0, 8) + 'xxxx',
933
+ });
934
+ ```
935
+
936
+ ## Meta
937
+
938
+ 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`.
939
+
940
+ The `metaStorage.provide()` returns an instance of `meta` that contains basic data like:
941
+
942
+ | Property | Type | Optional | Description |
943
+ |--|--|--|--|
944
+ | operationId | `string` | | UUID v4 |
945
+ | requestId | `string` | * | `X-Request-Id` from `incoming.headers` |
946
+
947
+ **Setup**
948
+
949
+ ```ts
950
+ import { Logger, metaStorage } from '@n1k1t/mock-server';
951
+
952
+ // Some external logger with meta context support
953
+ const external = {...};
954
+
955
+ // It defines your own logger methods
956
+ Logger.useExternal({
957
+ debug: (...messages: string[]) => external.debug(metaStorage.provide(), ...messages),
958
+ info: (...messages: string[]) => external.log(metaStorage.provide(), ...messages),
959
+ warn: (...messages: string[]) => external.warn(metaStorage.provide(), ...messages),
960
+ error: (...messages: string[]) => external.error(metaStorage.provide(), ...messages),
961
+ fatal: (...messages: string[]) => external.error(metaStorage.provide(), ...messages),
962
+ });
963
+ ```
964
+
965
+ **Usage**
966
+
967
+ ```ts
968
+ await server.client.createExpectation({
969
+ schema: {
970
+ request: {
971
+ $exec: ({ context, logger }) => {
972
+ // Here logger should have a meta context like { operationId: '...' }
973
+ logger.info('Before')
974
+ },
975
+ $exec: ({ context, logger, meta }) => {
976
+ // It enriches meta context for further logs of request
977
+ meta.merge({ foo: 'bar' });
978
+ },
979
+ $exec: ({ context, logger, meta }) => {
980
+ // Now logger should have a meta context like { foo: 'bar', operationId: '...' }
981
+ logger.info('After')
982
+ },
983
+ },
984
+ },
985
+ });
986
+ ```
987
+
988
+ ## Plugins
989
+
990
+ > **!NOTE** Configuration must be provided in the same script like mock server
991
+
992
+ | Plugin | Description |
993
+ |--|--|
994
+ | `incoming.body` | Describes how to handle incoming body |
995
+ | `outgoing.response` | Describes how to reply |
996
+ | `forward.request` | Describes how provide an [axios](https://www.npmjs.com/package/axios) request config to forward a request |
997
+ | `forward.response` | Describes how to parse [axios](https://www.npmjs.com/package/axios) response of a forwarded request |
998
+
999
+ Example of **`incoming.body`** plugin
1000
+
1001
+ ```ts
1002
+ server.context.plugins.register('incoming.body', async (request) => {
1003
+ let raw = '';
1004
+
1005
+ request.on('data', chunk => raw += chunk);
1006
+ await new Promise(resolve => request.on('end', resolve));
1007
+
1008
+ return { raw };
1009
+ });
1010
+ ```
1011
+
1012
+ Example of **`outgoing.response`** plugin
1013
+
1014
+ ```ts
1015
+ server.context.plugins.register('outgoing.response', (response, context) => {
1016
+ context.response.writeHead(context.outgoing.status ?? 200, context.outgoing.headers);
1017
+ context.response.write(context.outgoing.dataRaw);
1018
+ context.response.end();
1019
+ });
1020
+ ```
1021
+
1022
+ Example of **`forward.request`** plugin
1023
+
1024
+ ```ts
1025
+ server.context.plugins.register('forward.request', (config) => ({
1026
+ ...config,
1027
+ url: config.url.replace('/api_v1', '/api_v2'),
1028
+ }));
1029
+ ```
1030
+
1031
+ Example of **`forward.response`** plugin
1032
+
1033
+ ```ts
1034
+ server.context.plugins.register('forward.response', async (response: AxiosResponse<Buffer>) => ({
1035
+ raw: response.data.toString(),
1036
+ }));
88
1037
  ```