@n1k1t/mock-server 0.3.0 → 1.0.0
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 +583 -1013
- package/README.old.md +1255 -0
- package/lib/package.json +15 -6
- package/lib/src/client/helpers/expectations.d.ts +15 -45
- package/lib/src/client/helpers/expectations.d.ts.map +1 -1
- package/lib/src/client/helpers/expectations.js.map +1 -1
- package/lib/src/client/methods/expectations-group.update.method.js +1 -1
- package/lib/src/client/methods/expectations-group.update.method.js.map +1 -1
- package/lib/src/client/methods/expectations.create.method.js +1 -1
- package/lib/src/client/methods/expectations.create.method.js.map +1 -1
- package/lib/src/client/methods/expectations.delete.method.js +1 -1
- package/lib/src/client/methods/expectations.delete.method.js.map +1 -1
- package/lib/src/client/methods/expectations.update.method.js +1 -1
- package/lib/src/client/methods/expectations.update.method.js.map +1 -1
- package/lib/src/client/methods/ping.method.js +1 -1
- package/lib/src/client/methods/ping.method.js.map +1 -1
- package/lib/src/client/methods/providers.create.method.js +1 -1
- package/lib/src/client/methods/providers.create.method.js.map +1 -1
- package/lib/src/client/methods/providers.delete.method.js +1 -1
- package/lib/src/client/methods/providers.delete.method.js.map +1 -1
- package/lib/src/client/models/client.d.ts +4 -4
- package/lib/src/client/models/client.d.ts.map +1 -1
- package/lib/src/client/models/client.js +6 -3
- package/lib/src/client/models/client.js.map +1 -1
- package/lib/src/client/models/types.d.ts +3 -0
- package/lib/src/client/models/types.d.ts.map +1 -1
- package/lib/src/client/models/types.js.map +1 -1
- package/lib/src/client/types.d.ts +1 -1
- package/lib/src/client/types.d.ts.map +1 -1
- package/lib/src/client/utils.d.ts +1 -1
- package/lib/src/client/utils.d.ts.map +1 -1
- package/lib/src/client/utils.js.map +1 -1
- package/lib/src/config/index.d.ts +1 -1
- package/lib/src/config/index.js +1 -1
- package/lib/src/config/index.js.map +1 -1
- package/lib/src/expectations/__utils__/index.d.ts.map +1 -1
- package/lib/src/expectations/__utils__/index.js +0 -1
- package/lib/src/expectations/__utils__/index.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.map +1 -1
- package/lib/src/expectations/models/storage.d.ts +5 -5
- package/lib/src/expectations/models/storage.d.ts.map +1 -1
- package/lib/src/expectations/models/storage.js.map +1 -1
- package/lib/src/expectations/types.d.ts +18 -15
- package/lib/src/expectations/types.d.ts.map +1 -1
- package/lib/src/expectations/types.js +0 -1
- package/lib/src/expectations/types.js.map +1 -1
- package/lib/src/expectations/utils/location.d.ts.map +1 -1
- package/lib/src/expectations/utils/location.js +0 -6
- package/lib/src/expectations/utils/location.js.map +1 -1
- package/lib/src/server/endpoints/cache.backup.endpoint.js +1 -1
- package/lib/src/server/endpoints/cache.backup.endpoint.js.map +1 -1
- package/lib/src/server/endpoints/cache.delete.endpoint.js +1 -1
- package/lib/src/server/endpoints/cache.delete.endpoint.js.map +1 -1
- package/lib/src/server/endpoints/cache.restore.endpoint.d.ts.map +1 -1
- package/lib/src/server/endpoints/cache.restore.endpoint.js +8 -3
- package/lib/src/server/endpoints/cache.restore.endpoint.js.map +1 -1
- package/lib/src/server/endpoints/cache.restore.stream.endpoint.js +1 -1
- package/lib/src/server/endpoints/cache.restore.stream.endpoint.js.map +1 -1
- package/lib/src/server/endpoints/cache.usage.get.endpoint.js +1 -1
- package/lib/src/server/endpoints/cache.usage.get.endpoint.js.map +1 -1
- package/lib/src/server/endpoints/config.get.endpoint.d.ts +1 -1
- package/lib/src/server/endpoints/expectations.get-by-id.endpoint.js +1 -1
- package/lib/src/server/endpoints/expectations.get-by-id.endpoint.js.map +1 -1
- package/lib/src/server/endpoints/expectations.update.endpoint.d.ts +1 -1
- package/lib/src/server/endpoints/expectations.update.endpoint.d.ts.map +1 -1
- package/lib/src/server/endpoints/gui.endpoint.js +2 -2
- package/lib/src/server/endpoints/gui.endpoint.js.map +1 -1
- package/lib/src/server/endpoints/history.compact.get-list.endpoint.d.ts +2 -2
- package/lib/src/server/endpoints/history.get-by-id.endpoint.d.ts +1 -1
- package/lib/src/server/endpoints/history.get-by-id.endpoint.js +1 -1
- package/lib/src/server/endpoints/history.get-by-id.endpoint.js.map +1 -1
- package/lib/src/server/index.d.ts +2 -5
- package/lib/src/server/index.d.ts.map +1 -1
- package/lib/src/server/index.js +10 -9
- package/lib/src/server/index.js.map +1 -1
- package/lib/src/server/models/context/index.d.ts +6 -6
- package/lib/src/server/models/context/index.d.ts.map +1 -1
- package/lib/src/server/models/context/index.js +0 -4
- package/lib/src/server/models/context/index.js.map +1 -1
- package/lib/src/server/models/context/snapshot.d.ts +2 -3
- package/lib/src/server/models/context/snapshot.d.ts.map +1 -1
- package/lib/src/server/models/context/snapshot.js +6 -6
- package/lib/src/server/models/context/snapshot.js.map +1 -1
- package/lib/src/server/models/context/types.d.ts +10 -9
- package/lib/src/server/models/context/types.d.ts.map +1 -1
- package/lib/src/server/models/context/utils.d.ts.map +1 -1
- package/lib/src/server/models/context/utils.js +2 -6
- package/lib/src/server/models/context/utils.js.map +1 -1
- package/lib/src/server/models/endpoint.d.ts +2 -2
- package/lib/src/server/models/endpoint.d.ts.map +1 -1
- package/lib/src/server/models/executor/index.d.ts +2 -2
- package/lib/src/server/models/executor/index.d.ts.map +1 -1
- package/lib/src/server/models/executor/index.js +38 -6
- package/lib/src/server/models/executor/index.js.map +1 -1
- package/lib/src/server/models/history/model.d.ts +4 -6
- package/lib/src/server/models/history/model.d.ts.map +1 -1
- package/lib/src/server/models/history/model.js +2 -7
- package/lib/src/server/models/history/model.js.map +1 -1
- package/lib/src/server/models/index.d.ts +2 -0
- package/lib/src/server/models/index.d.ts.map +1 -1
- package/lib/src/server/models/index.js +2 -0
- package/lib/src/server/models/index.js.map +1 -1
- package/lib/src/server/models/message.d.ts +16 -0
- package/lib/src/server/models/message.d.ts.map +1 -0
- package/lib/src/server/models/message.js +53 -0
- package/lib/src/server/models/message.js.map +1 -0
- package/lib/src/server/models/providers/model.d.ts +2 -2
- package/lib/src/server/models/providers/model.d.ts.map +1 -1
- package/lib/src/server/models/providers/storage.js +1 -1
- package/lib/src/server/models/providers/storage.js.map +1 -1
- package/lib/src/server/models/providers/system.d.ts.map +1 -1
- package/lib/src/server/models/providers/system.js +0 -1
- package/lib/src/server/models/providers/system.js.map +1 -1
- package/lib/src/server/models/router.js +1 -1
- package/lib/src/server/models/router.js.map +1 -1
- package/lib/src/server/models/transports/storage.js +1 -1
- package/lib/src/server/models/transports/storage.js.map +1 -1
- package/lib/src/server/models/websocket/errors/index.d.ts +2 -0
- package/lib/src/server/models/websocket/errors/index.d.ts.map +1 -0
- package/lib/src/server/models/websocket/errors/index.js +18 -0
- package/lib/src/server/models/websocket/errors/index.js.map +1 -0
- package/lib/src/server/models/websocket/errors/websocket-connection.error.d.ts +9 -0
- package/lib/src/server/models/websocket/errors/websocket-connection.error.d.ts.map +1 -0
- package/lib/src/server/models/websocket/errors/websocket-connection.error.js +32 -0
- package/lib/src/server/models/websocket/errors/websocket-connection.error.js.map +1 -0
- package/lib/src/server/models/websocket/factory.d.ts +9 -0
- package/lib/src/server/models/websocket/factory.d.ts.map +1 -0
- package/lib/src/server/models/websocket/factory.js +23 -0
- package/lib/src/server/models/websocket/factory.js.map +1 -0
- package/lib/src/server/models/websocket/index.d.ts +5 -0
- package/lib/src/server/models/websocket/index.d.ts.map +1 -0
- package/lib/src/server/models/websocket/index.js +21 -0
- package/lib/src/server/models/websocket/index.js.map +1 -0
- package/lib/src/server/models/websocket/model.d.ts +29 -0
- package/lib/src/server/models/websocket/model.d.ts.map +1 -0
- package/lib/src/server/models/websocket/model.js +164 -0
- package/lib/src/server/models/websocket/model.js.map +1 -0
- package/lib/src/server/models/websocket/types.d.ts +11 -0
- package/lib/src/server/models/websocket/types.d.ts.map +1 -0
- package/lib/src/server/models/websocket/types.js +3 -0
- package/lib/src/server/models/websocket/types.js.map +1 -0
- package/lib/src/server/transports/http.transport/context.d.ts +1 -1
- package/lib/src/server/transports/http.transport/context.d.ts.map +1 -1
- package/lib/src/server/transports/http.transport/context.js +10 -3
- package/lib/src/server/transports/http.transport/context.js.map +1 -1
- package/lib/src/server/transports/http.transport/executor.d.ts +3 -13
- package/lib/src/server/transports/http.transport/executor.d.ts.map +1 -1
- package/lib/src/server/transports/http.transport/executor.js +7 -6
- package/lib/src/server/transports/http.transport/executor.js.map +1 -1
- package/lib/src/server/transports/http.transport/index.d.ts +1 -1
- package/lib/src/server/transports/http.transport/index.d.ts.map +1 -1
- package/lib/src/server/transports/http.transport/index.js +2 -3
- package/lib/src/server/transports/http.transport/index.js.map +1 -1
- package/lib/src/server/transports/index.d.ts +1 -1
- package/lib/src/server/transports/index.d.ts.map +1 -1
- package/lib/src/server/transports/index.js +1 -1
- package/lib/src/server/transports/index.js.map +1 -1
- package/lib/src/server/transports/{internal → system}/http.transport/context.d.ts +6 -6
- package/lib/src/server/transports/system/http.transport/context.d.ts.map +1 -0
- package/lib/src/server/transports/{internal → system}/http.transport/context.js +7 -7
- package/lib/src/server/transports/system/http.transport/context.js.map +1 -0
- package/lib/src/server/transports/system/http.transport/executor.d.ts +21 -0
- package/lib/src/server/transports/system/http.transport/executor.d.ts.map +1 -0
- package/lib/src/server/transports/{internal → system}/http.transport/executor.js +5 -5
- package/lib/src/server/transports/system/http.transport/executor.js.map +1 -0
- package/lib/src/server/transports/{internal → system}/http.transport/index.d.ts +5 -5
- package/lib/src/server/transports/system/http.transport/index.d.ts.map +1 -0
- package/lib/src/server/transports/{internal → system}/http.transport/index.js +5 -5
- package/lib/src/server/transports/system/http.transport/index.js.map +1 -0
- package/lib/src/server/transports/system/http.transport/reply.d.ts +10 -0
- package/lib/src/server/transports/system/http.transport/reply.d.ts.map +1 -0
- package/lib/src/server/transports/{internal → system}/http.transport/reply.js +4 -4
- package/lib/src/server/transports/system/http.transport/reply.js.map +1 -0
- package/lib/src/server/transports/system/index.d.ts.map +1 -0
- package/lib/src/server/transports/system/index.js.map +1 -0
- package/lib/src/server/transports/{internal → system}/io.transport/context.d.ts +6 -6
- package/lib/src/server/transports/system/io.transport/context.d.ts.map +1 -0
- package/lib/src/server/transports/{internal → system}/io.transport/context.js +6 -6
- package/lib/src/server/transports/system/io.transport/context.js.map +1 -0
- package/lib/src/server/transports/{internal → system}/io.transport/executor.d.ts +3 -3
- package/lib/src/server/transports/system/io.transport/executor.d.ts.map +1 -0
- package/lib/src/server/transports/{internal → system}/io.transport/executor.js +3 -3
- package/lib/src/server/transports/system/io.transport/executor.js.map +1 -0
- package/lib/src/server/transports/system/io.transport/index.d.ts +14 -0
- package/lib/src/server/transports/system/io.transport/index.d.ts.map +1 -0
- package/lib/src/server/transports/{internal → system}/io.transport/index.js +5 -5
- package/lib/src/server/transports/system/io.transport/index.js.map +1 -0
- package/lib/src/server/transports/system/io.transport/reply.d.ts +10 -0
- package/lib/src/server/transports/system/io.transport/reply.d.ts.map +1 -0
- package/lib/src/server/transports/{internal → system}/io.transport/reply.js +4 -4
- package/lib/src/server/transports/system/io.transport/reply.js.map +1 -0
- package/lib/src/server/transports/system/utils.d.ts.map +1 -0
- package/lib/src/server/transports/system/utils.js.map +1 -0
- package/lib/src/server/transports/ws.transport/context.d.ts +13 -12
- package/lib/src/server/transports/ws.transport/context.d.ts.map +1 -1
- package/lib/src/server/transports/ws.transport/context.js +20 -28
- package/lib/src/server/transports/ws.transport/context.js.map +1 -1
- package/lib/src/server/transports/ws.transport/executor.d.ts +7 -5
- package/lib/src/server/transports/ws.transport/executor.d.ts.map +1 -1
- package/lib/src/server/transports/ws.transport/executor.js +77 -25
- package/lib/src/server/transports/ws.transport/executor.js.map +1 -1
- package/lib/src/server/transports/ws.transport/index.d.ts +3 -3
- package/lib/src/server/transports/ws.transport/index.d.ts.map +1 -1
- package/lib/src/server/transports/ws.transport/index.js +29 -54
- package/lib/src/server/transports/ws.transport/index.js.map +1 -1
- package/lib/src/server/types/index.d.ts +2 -4
- package/lib/src/server/types/index.d.ts.map +1 -1
- package/lib/src/utils/common.d.ts +6 -3
- package/lib/src/utils/common.d.ts.map +1 -1
- package/lib/src/utils/common.js +22 -11
- package/lib/src/utils/common.js.map +1 -1
- package/package.json +15 -6
- package/public/assets/{index-DoUtVarD.js → index-DoLXR0S_.js} +6 -1
- package/public/index.html +1 -1
- package/lib/bin/index.d.ts +0 -3
- package/lib/bin/index.d.ts.map +0 -1
- package/lib/bin/index.js +0 -32
- package/lib/bin/index.js.map +0 -1
- package/lib/src/server/transports/internal/http.transport/context.d.ts.map +0 -1
- package/lib/src/server/transports/internal/http.transport/context.js.map +0 -1
- package/lib/src/server/transports/internal/http.transport/executor.d.ts +0 -21
- package/lib/src/server/transports/internal/http.transport/executor.d.ts.map +0 -1
- package/lib/src/server/transports/internal/http.transport/executor.js.map +0 -1
- package/lib/src/server/transports/internal/http.transport/index.d.ts.map +0 -1
- package/lib/src/server/transports/internal/http.transport/index.js.map +0 -1
- package/lib/src/server/transports/internal/http.transport/reply.d.ts +0 -10
- package/lib/src/server/transports/internal/http.transport/reply.d.ts.map +0 -1
- package/lib/src/server/transports/internal/http.transport/reply.js.map +0 -1
- package/lib/src/server/transports/internal/index.d.ts.map +0 -1
- package/lib/src/server/transports/internal/index.js.map +0 -1
- package/lib/src/server/transports/internal/io.transport/context.d.ts.map +0 -1
- package/lib/src/server/transports/internal/io.transport/context.js.map +0 -1
- package/lib/src/server/transports/internal/io.transport/executor.d.ts.map +0 -1
- package/lib/src/server/transports/internal/io.transport/executor.js.map +0 -1
- package/lib/src/server/transports/internal/io.transport/index.d.ts +0 -14
- package/lib/src/server/transports/internal/io.transport/index.d.ts.map +0 -1
- package/lib/src/server/transports/internal/io.transport/index.js.map +0 -1
- package/lib/src/server/transports/internal/io.transport/reply.d.ts +0 -10
- package/lib/src/server/transports/internal/io.transport/reply.d.ts.map +0 -1
- package/lib/src/server/transports/internal/io.transport/reply.js.map +0 -1
- package/lib/src/server/transports/internal/utils.d.ts.map +0 -1
- package/lib/src/server/transports/internal/utils.js.map +0 -1
- /package/lib/src/server/transports/{internal → system}/index.d.ts +0 -0
- /package/lib/src/server/transports/{internal → system}/index.js +0 -0
- /package/lib/src/server/transports/{internal → system}/utils.d.ts +0 -0
- /package/lib/src/server/transports/{internal → system}/utils.js +0 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<div align='center'>
|
|
2
2
|
<h1>Mock server</h1>
|
|
3
|
-
<p>
|
|
3
|
+
<p>The ultimate toolkit to <b>intercept, transform, and simulate</b> HTTP/WS traffic with type-safe expectations</p>
|
|
4
4
|
|
|
5
5
|
<img src="https://raw.githubusercontent.com/n1k1t/mock-server/refs/heads/master/images/preview.png?raw=true" />
|
|
6
6
|
|
|
@@ -12,1244 +12,814 @@
|
|
|
12
12
|
%20div%201000&label=coverage)
|
|
13
13
|
</div>
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
- [
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
- [
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
- [
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
- [
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
#
|
|
47
|
-
|
|
48
|
-
|
|
15
|
+
This mock server provides complete control over your network layer. It allows you to simulate missing APIs, modify real backend responses on the fly, and proxy traffic with advanced caching. Designed for flexibility, it handles complex scenarios with ease while maintaining strict type safety across all your expectations.
|
|
16
|
+
|
|
17
|
+
- [Features](#features)
|
|
18
|
+
- [Installation](#installation)
|
|
19
|
+
- [Simple Example](#simple-example)
|
|
20
|
+
- [Overview](#overview)
|
|
21
|
+
- [GUI](#gui)
|
|
22
|
+
- [Storage & Containers](#storage--containers)
|
|
23
|
+
- [Cache](#cache)
|
|
24
|
+
- [State & Seeds](#state--seeds)
|
|
25
|
+
- [Mock Server Utilities](#mock-server-utilities)
|
|
26
|
+
- [Expectation Operators ($)](#expectation-operators-)
|
|
27
|
+
- [Operator Locations](#operator-locations)
|
|
28
|
+
- [Operator Context](#operator-context)
|
|
29
|
+
- [Container Methods](#container-methods)
|
|
30
|
+
- [Usage](#usage)
|
|
31
|
+
- [Complex Expectations with $and](#complex-expectations-with-and)
|
|
32
|
+
- [Custom Logic with $exec](#custom-logic-with-exec)
|
|
33
|
+
- [Dynamic Response Manipulation](#dynamic-response-manipulation)
|
|
34
|
+
- [Binary Data and Files](#binary-data-and-files)
|
|
35
|
+
- [XML Support](#xml-support)
|
|
36
|
+
- [Simulating Network Errors](#simulating-network-errors)
|
|
37
|
+
- [Disabled By Default](#disabled-by-default)
|
|
38
|
+
- [Type Safety](#type-safety)
|
|
39
|
+
- [WebSocket Support](#websocket-support)
|
|
40
|
+
- [Request Forwarding](#request-forwarding)
|
|
41
|
+
- [Using Relative Paths with baseUrl](#using-relative-paths-with-baseurl)
|
|
42
|
+
- [Transforming Request Data Before Forwarding](#transforming-request-data-before-forwarding)
|
|
43
|
+
- [WebSocket Forwarding and Manipulation](#websocket-forwarding-and-manipulation)
|
|
44
|
+
- [Conditional Caching](#conditional-caching)
|
|
45
|
+
- [Custom Cache Key](#custom-cache-key)
|
|
46
|
+
- [Request State](#request-state)
|
|
47
|
+
- [Storage and Containers](#storage-and-containers)
|
|
48
|
+
- [Cross-Expectation Sync](#cross-expectation-sync)
|
|
49
|
+
- [Extensions](#extensions)
|
|
50
|
+
- [Redis Configuration](#redis-configuration)
|
|
51
|
+
- [Remote Expectations with RemoteClient](#remote-expectations-with-remoteclient)
|
|
52
|
+
- [Grouping with Providers](#grouping-with-providers)
|
|
53
|
+
- [Logger](#logger)
|
|
54
|
+
- [License](#license)
|
|
55
|
+
|
|
56
|
+
## Features
|
|
57
|
+
|
|
58
|
+
- **Multi-protocol Support**: Full support for both **HTTP** and **WebSocket** protocols.
|
|
59
|
+
- **Flexible Matching**: Match requests by path, method, headers, and data using regex, minimatch, or custom functions.
|
|
60
|
+
- **Payload Manipulation**: Modify request or response payloads on the fly.
|
|
61
|
+
- **Request Forwarding**: Proxy requests to real services with optional caching.
|
|
62
|
+
- **Built-in GUI**: Monitor traffic and manage expectations through a web interface.
|
|
63
|
+
- **Typed Expectations**: First-class support for TypeScript to keep your mocks type-safe.
|
|
64
|
+
- **Advanced State Management**: Store data across requests using Containers or Request State.
|
|
65
|
+
- **XML Support**: Native parsing and serialization for XML payloads.
|
|
66
|
+
|
|
67
|
+
## Installation
|
|
49
68
|
|
|
50
69
|
```bash
|
|
51
70
|
npm i @n1k1t/mock-server
|
|
52
71
|
```
|
|
53
72
|
|
|
54
|
-
##
|
|
55
|
-
|
|
56
|
-

|
|
57
|
-
|
|
58
|
-
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
|
|
59
|
-
|
|
60
|
-
**In case of mocking without request forwarding:**
|
|
61
|
-
|
|
62
|
-
1. Start mock server (for example on `localhost:8080`)
|
|
63
|
-
2. Register expectation using CLI (cURL) or application lib
|
|
64
|
-
3. Make request to `localhost:8080/...`
|
|
65
|
-
1. The mock server matches a request payload with registered expectations
|
|
66
|
-
2. Build a response using an expectation configuration
|
|
67
|
-
|
|
68
|
-
**In case of mocking with request forwarding:**
|
|
69
|
-
|
|
70
|
-
0. Lets imagine that you have a service that hosts on `localhost:8081`
|
|
71
|
-
1. Start mock server (for example on `localhost:8080`)
|
|
72
|
-
2. Register expectation using CLI (cURL) or application lib
|
|
73
|
-
3. Make request to `localhost:8080/...`
|
|
74
|
-
1. The mock server matches a request payload with registered expectations
|
|
75
|
-
2. Next is forwarding a request payload to `localhost:8081/...`
|
|
76
|
-
3. Using response fetched from `localhost:8081/...` the mock server builds a response
|
|
77
|
-
|
|
78
|
-
## Start
|
|
79
|
-
|
|
80
|
-
### CLI
|
|
81
|
-
|
|
82
|
-
```bash
|
|
83
|
-
npx mock -h localhost -p 8080
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
### JavaScript
|
|
87
|
-
|
|
88
|
-
```js
|
|
89
|
-
const { MockServer } = require('@n1k1t/mock-server');
|
|
90
|
-
MockServer.start({ host: 'localhost', port: 8080 });
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
### TypeScript
|
|
73
|
+
## Simple Example
|
|
94
74
|
|
|
95
75
|
```ts
|
|
96
76
|
import { MockServer } from '@n1k1t/mock-server';
|
|
97
|
-
MockServer.start({ host: 'localhost', port: 8080 });
|
|
98
|
-
```
|
|
99
77
|
|
|
100
|
-
|
|
78
|
+
const server = await MockServer.start({ host: 'localhost', port: 8080 });
|
|
101
79
|
|
|
102
|
-
|
|
80
|
+
// Create a type-safe expectation using the operator compiler ($)
|
|
81
|
+
await server.client.createExpectation(({ $ }) => ({
|
|
82
|
+
schema: {
|
|
83
|
+
request: $.has('incoming.path', { $value: '/api/hello' }),
|
|
84
|
+
response: $.set('outgoing.data', { $value: { message: 'world' } }),
|
|
85
|
+
},
|
|
86
|
+
}));
|
|
87
|
+
```
|
|
103
88
|
|
|
104
|
-
|
|
89
|
+
## Overview
|
|
105
90
|
|
|
106
|
-
|
|
91
|
+
### GUI
|
|
107
92
|
|
|
108
|
-
|
|
93
|
+
The mock server provides a built-in web panel to track everything that is going through. By default, it can be found on `/_system/gui`. Example: `localhost:8080/_system/gui`.
|
|
109
94
|
|
|
110
|
-
|
|
95
|
+
### Storage & Containers
|
|
111
96
|
|
|
112
|
-
|
|
97
|
+
Storage provides access to read/write **Containers**—temporary cells used to sync expectations or store data between requests. Containers have a configurable TTL (Time to Live) and can be merged or assigned new payloads.
|
|
113
98
|
|
|
114
|
-
|
|
99
|
+
### Cache
|
|
115
100
|
|
|
116
|
-
|
|
101
|
+
Cache is used to store payloads of forwarded requests, powered by [ioredis](https://www.npmjs.com/package/ioredis). It allows for long-term persistence of mocked responses from real backends.
|
|
117
102
|
|
|
118
|
-
|
|
119
|
-
|--|--|--|--|--|
|
|
120
|
-
| request | [Operators](#operators) | `object` | * | Describes a way to catch by request and how to manipulate it |
|
|
121
|
-
| response | [Operators](#operators) | `object` | * | Describes how to manipulate response. Also can be used to catch response in case of forwarding |
|
|
122
|
-
| forward | [Forwarding](#forwarding) | `object` | * | Describes configuration to forward a request to another host |
|
|
103
|
+
### State & Seeds
|
|
123
104
|
|
|
124
|
-
**
|
|
105
|
+
- **State**: A unique storage for each individual request, typically extracted from headers. By default, it's extracted from the `X-Use-Mock-State` header (JSON in Base64).
|
|
106
|
+
- **Seeds**: Help generate consistent random data using [faker](https://www.npmjs.com/package/@faker-js/faker). By default, the seed value is extracted from the `X-Use-Mock-Seed` header. You can also manually set the seed within an expectation using the `seed` location:
|
|
125
107
|
|
|
126
108
|
```ts
|
|
127
|
-
await server.client.createExpectation({
|
|
109
|
+
await server.client.createExpectation(({ $ }) => ({
|
|
128
110
|
schema: {
|
|
129
|
-
request:
|
|
130
|
-
$
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
111
|
+
request: $.and([
|
|
112
|
+
$.has('incoming.path', { $value: '/api/faker' }),
|
|
113
|
+
// Manually set seed from query parameter or any other logic
|
|
114
|
+
$.set('seed', { $exec: (seed, { context }) => context.incoming.query.seed ?? context.seed ?? 12345 }),
|
|
115
|
+
]),
|
|
116
|
+
response: $.set('outgoing.data', {
|
|
117
|
+
$exec: (payload, { faker }) => ({
|
|
118
|
+
id: faker.number.int(), // Will be consistent for the same seed
|
|
119
|
+
name: faker.person.fullName(),
|
|
120
|
+
}),
|
|
121
|
+
}),
|
|
139
122
|
},
|
|
140
|
-
});
|
|
123
|
+
}));
|
|
141
124
|
```
|
|
142
125
|
|
|
143
|
-
##
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
126
|
+
## Mock Server Utilities
|
|
127
|
+
|
|
128
|
+
### Expectation Operators ($)
|
|
129
|
+
|
|
130
|
+
Expectations are built using operators that define how to match requests and transform responses.
|
|
131
|
+
|
|
132
|
+
- `$.has(location, { $path, ... })`: Checks if a value exists at the specified path. Supports `$value`, `$match` (minimatch), `$regExp`, and `$exec`.
|
|
133
|
+
- `$.set(location, { $value, ... })`: Sets a value at the specified path.
|
|
134
|
+
- `$.merge(location, { $value, ... })`: Merges an object into the value at the specified path.
|
|
135
|
+
- `$.remove(location, { $path, ... })`: Removes the value at the specified path.
|
|
136
|
+
- `$.and([...operators])`: Logical AND of multiple operators.
|
|
137
|
+
- `$.or([...operators])`: Logical OR of multiple operators.
|
|
138
|
+
- `$.not(operator)`: Logical NOT of an operator.
|
|
139
|
+
- `$.if({ $condition, $then, $else })`: Conditional execution of operators.
|
|
140
|
+
- `$.switch({ $cases, $default })`: Switch-case execution based on the value at the path.
|
|
141
|
+
- `$.exec(fn)`: Executes custom logic. In `match` mode, it must return a boolean. In `manipulate` mode, it can modify the context.
|
|
142
|
+
|
|
143
|
+
### Operator Locations
|
|
144
|
+
|
|
145
|
+
Many operators (like `$.has`, `$.set`, `$.merge`) accept a `location` as their first argument. This defines where the operation should be performed:
|
|
146
|
+
|
|
147
|
+
- `'incoming.path'`: The URL path of the request.
|
|
148
|
+
- `'incoming.method'`: The HTTP method.
|
|
149
|
+
- `'incoming.query'`: The query parameters object.
|
|
150
|
+
- `'incoming.headers'`: The request headers object.
|
|
151
|
+
- `'incoming.data'`: The request body (parsed JSON or XML).
|
|
152
|
+
- `'outgoing.status'`: The response status code.
|
|
153
|
+
- `'outgoing.headers'`: The response headers object.
|
|
154
|
+
- `'outgoing.data'`: The response body object.
|
|
155
|
+
- `'container'`: Access to a [Container](#container-methods).
|
|
156
|
+
- `'state'`: Access to [Request State](#request-state).
|
|
157
|
+
- `'cache'`: Access to [Forward Cache](#conditional-caching) configuration.
|
|
158
|
+
- `'seed'`: Access to the [Seed](#state--seeds) value.
|
|
159
|
+
- `'error'`: For [Simulating Network Errors](#simulating-network-errors).
|
|
160
|
+
|
|
161
|
+
When targeting an object (like `headers` or `data`), you can use `$path` as the second argument to target a nested value:
|
|
162
|
+
`$.has('incoming.headers', '$path', 'content-type', { $value: 'application/json' })`
|
|
163
|
+
|
|
164
|
+
### Operator Context
|
|
165
|
+
|
|
166
|
+
When using `$exec`, the second argument provides a context with useful utilities and data:
|
|
167
|
+
|
|
168
|
+
- **`context`**: The full request/response context.
|
|
169
|
+
- `incoming`: Input data (immutable in match mode).
|
|
170
|
+
- `path`: The request URL path.
|
|
171
|
+
- `method`: HTTP method (GET, POST, etc.).
|
|
172
|
+
- `query`: Object containing query parameters.
|
|
173
|
+
- `headers`: Object containing request headers.
|
|
174
|
+
- `data`: Parsed JSON/XML request body.
|
|
175
|
+
- `outgoing`: Output data (target for manipulation).
|
|
176
|
+
- `status`: HTTP status code.
|
|
177
|
+
- `headers`: Response headers.
|
|
178
|
+
- `data`: Response body object (serialized to JSON/XML).
|
|
179
|
+
- `dataRaw`: Buffer for binary data.
|
|
180
|
+
- `stream`: Observable for WebSocket messages.
|
|
181
|
+
- `storage`: Interface to manage [Containers](#container-methods).
|
|
182
|
+
- `container`: The currently bound container (if any).
|
|
183
|
+
- `cache`: Configuration for forwarding cache.
|
|
184
|
+
- `isEnabled`: Whether caching is enabled for this request.
|
|
185
|
+
- `prefix`: Redis key prefix.
|
|
186
|
+
- `key`: Custom cache key (string or object).
|
|
187
|
+
- `ttl`: Time to live in seconds.
|
|
188
|
+
- `hasRead`: (Internal) Whether cache was read.
|
|
189
|
+
- `hasWritten`: (Internal) Whether cache was written.
|
|
190
|
+
- **`state`**: The unique [Request State](#state--seeds). Persistent only for the duration of the current request.
|
|
191
|
+
- **`mode`**: The execution phase: `'match'` (deciding if expectation applies) or `'manipulate'` (preparing the response).
|
|
192
|
+
- **`logger`**: A scoped logger for debugging (`info`, `error`, `warn`).
|
|
193
|
+
- **`faker`**: Full `@faker-js/faker` instance for generating mock data.
|
|
194
|
+
- **`_`**: Lodash utility library.
|
|
195
|
+
- **`d`**: Dayjs instance for date/time manipulation.
|
|
196
|
+
- **`rx`**: RxJS exports for advanced WebSocket stream control.
|
|
197
|
+
|
|
198
|
+
### Container Methods
|
|
199
|
+
|
|
200
|
+
Containers (accessible via `context.storage`) provide several methods for managing data:
|
|
201
|
+
|
|
202
|
+
- `storage.provide({ key, payload, ttl })`: Retrieves an existing container by key or creates a new one with the provided payload and TTL.
|
|
203
|
+
- `storage.register({ key, payload, ttl })`: Forcefully creates and registers a new container, overwriting any existing one with the same key.
|
|
204
|
+
- `storage.find(key)`: Finds an existing container by key. Returns `undefined` if not found.
|
|
205
|
+
- `container.assign(payload | fn)`: Shallows merges the given payload into the current container state. If a function is provided, it receives the current payload and returns the new one.
|
|
206
|
+
- `container.merge(payload | fn)`: Deeply merges the given payload into the current container state.
|
|
207
|
+
- `container.bind(key)`: Binds the container to an additional key.
|
|
208
|
+
- `container.unbind()`: Removes the container from the storage.
|
|
209
|
+
|
|
210
|
+
## Usage
|
|
211
|
+
|
|
212
|
+
### Complex Expectations with `$and`
|
|
213
|
+
|
|
214
|
+
You can group multiple conditions using logical operators like `$.and`, `$.or`, and `$.not` to create sophisticated matching rules.
|
|
215
|
+
|
|
216
|
+
```ts
|
|
217
|
+
await server.client.createExpectation(({ $ }) => ({
|
|
220
218
|
schema: {
|
|
221
|
-
request:
|
|
222
|
-
$
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
},
|
|
229
|
-
{
|
|
230
|
-
$has: {
|
|
231
|
-
$location: 'method',
|
|
232
|
-
$value: 'GET',
|
|
233
|
-
},
|
|
234
|
-
},
|
|
235
|
-
],
|
|
236
|
-
},
|
|
219
|
+
request: $.and([
|
|
220
|
+
$.has('incoming.path', { $value: '/api/user' }),
|
|
221
|
+
$.has('incoming.method', { $value: 'POST' }),
|
|
222
|
+
// Checks if 'content-type' header equals 'application/json' using selection with $path = content-type
|
|
223
|
+
$.has('incoming.headers', '$path', 'content-type', { $value: 'application/json' }),
|
|
224
|
+
]),
|
|
225
|
+
response: $.set('outgoing.data', { $value: { status: 'success' } }),
|
|
237
226
|
},
|
|
238
|
-
});
|
|
227
|
+
}));
|
|
239
228
|
```
|
|
240
229
|
|
|
241
|
-
###
|
|
230
|
+
### Custom Logic with `$exec`
|
|
242
231
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
| Property | Type (application) | Type (cURL) | Optional | Description |
|
|
246
|
-
|--|--|--|--|--|
|
|
247
|
-
| $location | `string` [enum](#context) | `string` [enum](#context) | | Location that describes what [context](#context) entity is selecting for operator to work with |
|
|
248
|
-
| $path | `string` | `string` | * | Specifies a path to payload using [lodash get](https://lodash.com/docs/4.17.15#get) |
|
|
249
|
-
| $jsonPath | `string` | `string` | * | Specifies a path to payload using [JSON path](https://www.npmjs.com/package/jsonpath-plus) |
|
|
250
|
-
| $value | `any` | `any` | * | Checks by value equality in [context](#context) using `$location` (and `$path`, `$jsonPath` if it was specified) |
|
|
251
|
-
| $valueAnyOf | `any[]` | `any[]` | * | Checks by any of value equality in [context](#context) using `$location` (and `$path`, `$jsonPath` if it was specified) |
|
|
252
|
-
| $regExp | `RegExp` | `{ source: string, flags?: string }` | * | Checks by regular expression in context using `$location` (and `$path`, `$jsonPath` if it was specified) |
|
|
253
|
-
| $regExpAnyOf | `RegExp[]` | `{ source: string, flags?: string }[]` | * | Checks by any of regular expression in [context](#context) using `$location` (and `$path`, `$jsonPath` if it was specified) |
|
|
254
|
-
| $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) |
|
|
255
|
-
| $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) |
|
|
256
|
-
| $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) |
|
|
257
|
-
|
|
258
|
-
**Example using application**
|
|
232
|
+
For scenarios that require dynamic calculations or external utilities, use the `$exec` operator.
|
|
259
233
|
|
|
260
234
|
```ts
|
|
261
|
-
await server.client.createExpectation({
|
|
235
|
+
await server.client.createExpectation(({ $ }) => ({
|
|
262
236
|
schema: {
|
|
263
|
-
request: {
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
237
|
+
request: $.has('incoming.path', {
|
|
238
|
+
// Custom matching logic: checks if path starts with '/api/data'
|
|
239
|
+
$exec: (path) => path.startsWith('/api/data')
|
|
240
|
+
}),
|
|
241
|
+
response: $.set('outgoing.data', {
|
|
242
|
+
// Use built-in utils like lodash (_), dayjs (d), or faker
|
|
243
|
+
$exec: (payload, { _, d, faker }) => ({
|
|
244
|
+
id: faker.string.uuid(),
|
|
245
|
+
timestamp: d().format(),
|
|
246
|
+
values: _.range(3).map(() => _.random(1, 100)),
|
|
247
|
+
}),
|
|
248
|
+
}),
|
|
269
249
|
},
|
|
270
|
-
});
|
|
271
|
-
```
|
|
272
|
-
|
|
273
|
-
**Example using cURL**
|
|
250
|
+
}));
|
|
274
251
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
{
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
252
|
+
// Use $.exec for full control over the expectation execution
|
|
253
|
+
await server.client.createExpectation(({ $ }) => ({
|
|
254
|
+
schema: {
|
|
255
|
+
request: $.exec(({ context, logger, mode }) => {
|
|
256
|
+
// mode can be 'match' (on catching request) or 'manipulate' (on manipulation)
|
|
257
|
+
if (mode === 'match') {
|
|
258
|
+
logger.info('Checking request path', context.incoming.path);
|
|
259
|
+
return context.incoming.path === '/api/custom';
|
|
283
260
|
}
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
}
|
|
287
|
-
|
|
261
|
+
}),
|
|
262
|
+
response: $.set('outgoing.data', { $value: { status: 'custom' } }),
|
|
263
|
+
},
|
|
264
|
+
}));
|
|
288
265
|
```
|
|
289
266
|
|
|
290
|
-
###
|
|
267
|
+
### Dynamic Response Manipulation
|
|
291
268
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
| Property | Type (application) | Type (cURL) | Optional | Description |
|
|
295
|
-
|--|--|--|--|--|
|
|
296
|
-
| $location | `string` [enum](#context) | `string` [enum](#context) | | Location that describes what [context](#context) entity is selecting for operator to work with |
|
|
297
|
-
| $path | `string` | `string` | * | Specifies a path to payload using [lodash get](https://lodash.com/docs/4.17.15#get) |
|
|
298
|
-
| $jsonPath | `string` | `string` | * | Specifies a path to payload using [JSON path](https://www.npmjs.com/package/jsonpath-plus) |
|
|
299
|
-
| $value | `any` | `any` | * | Sets value to [context](#context) using `$location` (and `$path`, `$jsonPath` if it was specified) |
|
|
300
|
-
| $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) |
|
|
301
|
-
|
|
302
|
-
**Example using application**
|
|
269
|
+
You can dynamically adjust the response payload based on incoming request parameters (query, body, headers, etc.).
|
|
303
270
|
|
|
304
271
|
```ts
|
|
305
|
-
await server.client.createExpectation({
|
|
272
|
+
await server.client.createExpectation(({ $ }) => ({
|
|
306
273
|
schema: {
|
|
307
|
-
request: {
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
274
|
+
request: $.has('incoming.path', { $value: '/api/profile' }),
|
|
275
|
+
response: $.set('outgoing.data', {
|
|
276
|
+
$exec: (payload, { context }) => ({
|
|
277
|
+
id: context.incoming.query.userId,
|
|
278
|
+
// Conditionally include data based on request headers
|
|
279
|
+
debug: context.incoming.headers['x-debug'] === 'true'
|
|
280
|
+
? { timestamp: Date.now() }
|
|
281
|
+
: undefined
|
|
282
|
+
})
|
|
283
|
+
}),
|
|
314
284
|
},
|
|
315
|
-
});
|
|
316
|
-
```
|
|
317
|
-
|
|
318
|
-
**Example using cURL**
|
|
319
|
-
|
|
320
|
-
```bash
|
|
321
|
-
curl -H "Content-type: application/json" -X POST --location "localhost:8080/_system/expectations" --data-binary @- << EOF
|
|
322
|
-
{
|
|
323
|
-
"schema": {
|
|
324
|
-
"request": {
|
|
325
|
-
"\$set": {
|
|
326
|
-
"\$location": "incoming.data",
|
|
327
|
-
"\$path": "foo",
|
|
328
|
-
"\$exec": "_.clamp(payload, 0, 10)"
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
EOF
|
|
285
|
+
}));
|
|
334
286
|
```
|
|
335
287
|
|
|
336
|
-
###
|
|
288
|
+
### Binary Data and Files
|
|
337
289
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
| Property | Type (application) | Type (cURL) | Optional | Description |
|
|
341
|
-
|--|--|--|--|--|
|
|
342
|
-
| $location | `string` [enum](#context) | `string` [enum](#context) | | Location that describes what [context](#context) entity is selecting for operator to work with |
|
|
343
|
-
| $path | `string` | `string` | * | Specifies a path to payload using [lodash get](https://lodash.com/docs/4.17.15#get) |
|
|
344
|
-
| $jsonPath | `string` | `string` | * | Specifies a path to payload using [JSON path](https://www.npmjs.com/package/jsonpath-plus) |
|
|
345
|
-
| $value | `object` | `object` | * | Merges value in [context](#context) using `$location` (and `$path`, `$jsonPath` if it was specified) |
|
|
346
|
-
| $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) |
|
|
347
|
-
|
|
348
|
-
**Example using application**
|
|
290
|
+
You can return binary data (Buffers) as a response body. This is useful for mocking file downloads or image responses.
|
|
349
291
|
|
|
350
292
|
```ts
|
|
351
|
-
|
|
293
|
+
import { readFile } from 'fs/promises';
|
|
294
|
+
|
|
295
|
+
await server.client.createExpectation(({ $ }) => ({
|
|
352
296
|
schema: {
|
|
353
|
-
request: {
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
297
|
+
request: $.has('incoming.path', { $value: '/api/download' }),
|
|
298
|
+
response: $.and([
|
|
299
|
+
$.set('outgoing.headers', '$path', 'content-type', { $value: 'application/pdf' }),
|
|
300
|
+
$.set('outgoing.dataRaw', {
|
|
301
|
+
$exec: async () => await readFile('./path/to/document.pdf')
|
|
302
|
+
}),
|
|
303
|
+
]),
|
|
359
304
|
},
|
|
360
|
-
});
|
|
361
|
-
```
|
|
362
|
-
|
|
363
|
-
**Example using cURL**
|
|
364
|
-
|
|
365
|
-
```bash
|
|
366
|
-
curl -H "Content-type: application/json" -X POST --location "localhost:8080/_system/expectations" --data-binary @- << EOF
|
|
367
|
-
{
|
|
368
|
-
"schema": {
|
|
369
|
-
"request": {
|
|
370
|
-
"\$merge": {
|
|
371
|
-
"\$location": "incoming.data",
|
|
372
|
-
"\$value": {"has_mocked": true}
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
EOF
|
|
305
|
+
}));
|
|
378
306
|
```
|
|
379
307
|
|
|
380
|
-
###
|
|
308
|
+
### XML Support
|
|
381
309
|
|
|
382
|
-
|
|
383
|
-
|--|--|--|--|--|
|
|
384
|
-
| $location | `string` [enum](#context) | `string` [enum](#context) | | Location that describes what [context](#context) entity is selecting for operator to work with |
|
|
385
|
-
| $path | `string` | `string` | * | Specifies a path to payload using [lodash get](https://lodash.com/docs/4.17.15#get) |
|
|
386
|
-
| $jsonPath | `string` | `string` | * | Specifies a path to payload using [JSON path](https://www.npmjs.com/package/jsonpath-plus) |
|
|
387
|
-
|
|
388
|
-
**Example using application**
|
|
310
|
+
The mock server provides native support for XML payloads via [fast-xml-parser](https://www.npmjs.com/package/fast-xml-parser) with `ignoreAttributes: false`. It automatically parses incoming XML and can serialize objects back to XML in the response.
|
|
389
311
|
|
|
390
312
|
```ts
|
|
391
|
-
await server.client.createExpectation({
|
|
313
|
+
await server.client.createExpectation(({ $ }) => ({
|
|
392
314
|
schema: {
|
|
393
|
-
request:
|
|
394
|
-
|
|
395
|
-
|
|
315
|
+
request: $.and([
|
|
316
|
+
$.has('incoming.path', { $matchAnyOf: ['/api/user*'] }),
|
|
317
|
+
$.exec(({ context }) => context.incoming.headers['content-type'] === 'application/xml'),
|
|
318
|
+
]),
|
|
319
|
+
response: $.and([
|
|
320
|
+
$.set('outgoing.headers', '$path', 'content-type', { $value: 'application/xml' }),
|
|
321
|
+
$.set('outgoing.data', {
|
|
322
|
+
$value: {
|
|
323
|
+
user: {
|
|
324
|
+
info: {
|
|
325
|
+
'#text': 'John Doe',
|
|
326
|
+
'@_type': 'mocked',
|
|
327
|
+
'@_id': 123
|
|
328
|
+
}
|
|
329
|
+
},
|
|
330
|
+
},
|
|
331
|
+
}),
|
|
332
|
+
]),
|
|
396
333
|
},
|
|
397
|
-
});
|
|
398
|
-
```
|
|
399
|
-
|
|
400
|
-
**Example using cURL**
|
|
401
|
-
|
|
402
|
-
```bash
|
|
403
|
-
curl -H "Content-type: application/json" -X POST --location "localhost:8080/_system/expectations" --data-binary @- << EOF
|
|
404
|
-
{
|
|
405
|
-
"schema": {
|
|
406
|
-
"request": {
|
|
407
|
-
"\$remove": {"\$location": "outgoing.data"}
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
EOF
|
|
334
|
+
}));
|
|
412
335
|
```
|
|
413
336
|
|
|
414
|
-
###
|
|
337
|
+
### Simulating Network Errors
|
|
415
338
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
| Type (application) | Type (cURL) | Description |
|
|
419
|
-
|--|--|--|
|
|
420
|
-
| `(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) |
|
|
421
|
-
|
|
422
|
-
**Example using application**
|
|
339
|
+
You can simulate connection drops or network errors by setting the `error` property.
|
|
423
340
|
|
|
424
341
|
```ts
|
|
425
|
-
await server.client.createExpectation({
|
|
342
|
+
await server.client.createExpectation(({ $ }) => ({
|
|
426
343
|
schema: {
|
|
427
|
-
request: {
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
},
|
|
344
|
+
request: $.has('incoming.path', { $value: '/api/error' }),
|
|
345
|
+
response: $.set('error', {
|
|
346
|
+
// Common error codes: ECONNRESET, ECONNABORTED, etc.
|
|
347
|
+
$value: 'ECONNRESET'
|
|
348
|
+
}),
|
|
433
349
|
},
|
|
434
|
-
});
|
|
435
|
-
```
|
|
436
|
-
|
|
437
|
-
**Example using cURL**
|
|
438
|
-
|
|
439
|
-
```bash
|
|
440
|
-
curl -H "Content-type: application/json" -X POST --location "localhost:8080/_system/expectations" --data-binary @- << EOF
|
|
441
|
-
{
|
|
442
|
-
"schema": {
|
|
443
|
-
"request": {
|
|
444
|
-
"\$exec": "{ logger.info(context); return context.incoming.path === '/foo' }"
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
EOF
|
|
350
|
+
}));
|
|
449
351
|
```
|
|
450
352
|
|
|
451
|
-
###
|
|
452
|
-
|
|
453
|
-
| Type (application) | Type (cURL) | Description |
|
|
454
|
-
|--|--|--|
|
|
455
|
-
| `object[]` | `object[]` | Provides [operators](#operators) schemas |
|
|
353
|
+
### Disabled by Default
|
|
456
354
|
|
|
457
|
-
|
|
355
|
+
You can create expectations that are disabled by default by setting `isEnabled: false`. These can be manually enabled later via the [GUI](#gui).
|
|
458
356
|
|
|
459
357
|
```ts
|
|
460
|
-
await server.client.createExpectation({
|
|
358
|
+
await server.client.createExpectation(({ $ }) => ({
|
|
359
|
+
isEnabled: false,
|
|
461
360
|
schema: {
|
|
462
|
-
request: {
|
|
463
|
-
|
|
464
|
-
{ $has: { $location: 'path', $match: 'foo/*' } },
|
|
465
|
-
{ $has: { $location: 'method', $valueAnyOf: ['GET', 'POST'] } },
|
|
466
|
-
],
|
|
467
|
-
},
|
|
361
|
+
request: $.has('incoming.path', { $value: '/api/hidden' }),
|
|
362
|
+
response: $.set('outgoing.data', { $value: { message: 'Activate me in GUI' } }),
|
|
468
363
|
},
|
|
469
|
-
});
|
|
470
|
-
```
|
|
471
|
-
|
|
472
|
-
**Example using cURL**
|
|
473
|
-
|
|
474
|
-
```bash
|
|
475
|
-
curl -H "Content-type: application/json" -X POST --location "localhost:8080/_system/expectations" --data-binary @- << EOF
|
|
476
|
-
{
|
|
477
|
-
"schema": {
|
|
478
|
-
"request": {
|
|
479
|
-
"\$and": [
|
|
480
|
-
{"\$has": {"\$location": "path", "\$match": "foo/*"}},
|
|
481
|
-
{"\$has": {"\$location": "method", "\$valueAnyOf": ["GET", "POST"]}}
|
|
482
|
-
]
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
EOF
|
|
364
|
+
}));
|
|
487
365
|
```
|
|
488
366
|
|
|
489
|
-
###
|
|
490
|
-
|
|
491
|
-
| Type (application) | Type (cURL) | Description |
|
|
492
|
-
|--|--|--|
|
|
493
|
-
| `object[]` | `object[]` | Provides [operators](#operators) schemas |
|
|
367
|
+
### Type Safety
|
|
494
368
|
|
|
495
|
-
|
|
369
|
+
You can provide generic types to `createExpectation` to ensure your schemas and `$exec` functions are fully typed.
|
|
496
370
|
|
|
497
371
|
```ts
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
```bash
|
|
513
|
-
curl -H "Content-type: application/json" -X POST --location "localhost:8080/_system/expectations" --data-binary @- << EOF
|
|
514
|
-
{
|
|
515
|
-
"schema": {
|
|
516
|
-
"request": {
|
|
517
|
-
"\$or": [
|
|
518
|
-
{"\$has": {"\$location": "path", "\$match": "foo/*"}},
|
|
519
|
-
{"\$has": {"\$location": "method", "\$valueAnyOf": ["GET", "POST"]}}
|
|
520
|
-
]
|
|
521
|
-
}
|
|
522
|
-
}
|
|
372
|
+
interface MyContext {
|
|
373
|
+
incoming: {
|
|
374
|
+
query: {
|
|
375
|
+
userId: string;
|
|
376
|
+
expand?: boolean;
|
|
377
|
+
};
|
|
378
|
+
};
|
|
379
|
+
outgoing: {
|
|
380
|
+
data: {
|
|
381
|
+
id: string;
|
|
382
|
+
name: string;
|
|
383
|
+
};
|
|
384
|
+
};
|
|
523
385
|
}
|
|
524
|
-
EOF
|
|
525
|
-
```
|
|
526
|
-
|
|
527
|
-
### $not
|
|
528
386
|
|
|
529
|
-
|
|
530
|
-
|--|--|--|
|
|
531
|
-
| `object` | `object` | Provides an [operators](#operators) schema |
|
|
532
|
-
|
|
533
|
-
**Example using application**
|
|
534
|
-
|
|
535
|
-
```ts
|
|
536
|
-
await server.client.createExpectation({
|
|
387
|
+
await server.client.createExpectation<MyContext>(({ $ }) => ({
|
|
537
388
|
schema: {
|
|
538
|
-
request: {
|
|
539
|
-
$
|
|
540
|
-
},
|
|
389
|
+
request: $.has('incoming.query', {
|
|
390
|
+
$exec: (query) => query.userId === '123'
|
|
391
|
+
}),
|
|
392
|
+
response: $.set('outgoing.data', {
|
|
393
|
+
$exec: (payload, { faker }) => ({
|
|
394
|
+
id: '123',
|
|
395
|
+
name: faker.person.fullName()
|
|
396
|
+
})
|
|
397
|
+
}),
|
|
541
398
|
},
|
|
542
|
-
});
|
|
543
|
-
```
|
|
544
|
-
|
|
545
|
-
**Example using cURL**
|
|
546
|
-
|
|
547
|
-
```bash
|
|
548
|
-
curl -H "Content-type: application/json" -X POST --location "localhost:8080/_system/expectations" --data-binary @- << EOF
|
|
549
|
-
{
|
|
550
|
-
"schema": {
|
|
551
|
-
"request": {
|
|
552
|
-
"\$not": {"\$has": {"\$location": "path", "\$match": "foo/*"}}
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
EOF
|
|
399
|
+
}));
|
|
557
400
|
```
|
|
558
401
|
|
|
559
|
-
###
|
|
402
|
+
### WebSocket Support
|
|
560
403
|
|
|
561
|
-
|
|
562
|
-
|--|--|--|--|--|
|
|
563
|
-
| $condition | `object` | `object` | | Condition to check. Should contain one of `$and`, `$exec`, `$has`, `$or` or `$not` [operators](#operators) schema |
|
|
564
|
-
| $then | `object` | `object` | * | Logical `then`. Should contain an [operators](#operators) schema |
|
|
565
|
-
| $else | `object` | `object` | * | Logical `else`. Should contain an [operators](#operators) schema |
|
|
566
|
-
|
|
567
|
-
**Example using application**
|
|
404
|
+
The mock server provides first-class support for WebSockets, allowing you to intercept and mock streaming data using [RxJS](https://rxjs.dev/).
|
|
568
405
|
|
|
569
406
|
```ts
|
|
570
|
-
await server.client.createExpectation({
|
|
407
|
+
await server.client.createExpectation(({ $, utils }) => ({
|
|
408
|
+
// 1. Specify 'ws' transport
|
|
409
|
+
transports: utils.transports(['ws']),
|
|
410
|
+
|
|
571
411
|
schema: {
|
|
572
|
-
request: {
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
412
|
+
request: $.has('incoming.path', { $value: '/ws/updates' }),
|
|
413
|
+
response: $.and([
|
|
414
|
+
// 2. Set custom WS status code if needed
|
|
415
|
+
$.set('outgoing.status', { $value: 1000 }),
|
|
416
|
+
// 3. Define the outgoing stream
|
|
417
|
+
$.set('outgoing.stream', {
|
|
418
|
+
$exec: (stream, { rx }) => {
|
|
419
|
+
// Return an Observable of messages
|
|
420
|
+
return rx.from([
|
|
421
|
+
{ event: 'connected' },
|
|
422
|
+
{ event: 'data', value: 1 },
|
|
423
|
+
{ event: 'data', value: 2 },
|
|
424
|
+
]);
|
|
425
|
+
},
|
|
426
|
+
}),
|
|
427
|
+
]),
|
|
579
428
|
},
|
|
580
|
-
});
|
|
581
|
-
```
|
|
582
|
-
|
|
583
|
-
**Example using cURL**
|
|
584
|
-
|
|
585
|
-
```bash
|
|
586
|
-
curl -H "Content-type: application/json" -X POST --location "localhost:8080/_system/expectations" --data-binary @- << EOF
|
|
587
|
-
{
|
|
588
|
-
"schema": {
|
|
589
|
-
"request": {
|
|
590
|
-
"\$if": {
|
|
591
|
-
"\$condition": {"\$has": {"\$location": "path", "\$match": "foo/*"}},
|
|
592
|
-
"\$then": {"\$set": {"\$location": "delay", "\$value": 5000}},
|
|
593
|
-
"\$else": {"\$set": {"\$location": "error", "\$value": "ECONNABORTED"}}
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
EOF
|
|
429
|
+
}));
|
|
599
430
|
```
|
|
600
431
|
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
> **!NOTE** `$exec` operators [have restrictions](#api) when it defined over `HTTP API` or `RemoteClient`
|
|
604
|
-
|
|
605
|
-
| Property | Type (application) | Type (cURL) | Optional | Description |
|
|
606
|
-
|--|--|--|--|--|
|
|
607
|
-
| $location | `string` [enum](#context) | `string` [enum](#context) | | Location that describes what [context](#context) entity is selecting for operator to work with |
|
|
608
|
-
| $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 |
|
|
609
|
-
| $default | `object` | `object` | * | Default behavior as an [operators](#operators) schema |
|
|
610
|
-
| $path | `string` | `string` | * | Specifies a path to payload using [lodash get](https://lodash.com/docs/4.17.15#get) |
|
|
611
|
-
| $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) |
|
|
432
|
+
### Request Forwarding
|
|
612
433
|
|
|
613
|
-
|
|
434
|
+
You can forward requests to an external API and manipulate the response before it's returned to the client.
|
|
614
435
|
|
|
615
|
-
|
|
616
|
-
await server.client.createExpectation({
|
|
436
|
+
```ts
|
|
437
|
+
await server.client.createExpectation(({ $ }) => ({
|
|
617
438
|
schema: {
|
|
618
|
-
request: {
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
'GET': { $set: { $location: 'delay', $value: 2000 } },
|
|
623
|
-
'POST': { $set: { $location: 'delay', $value: 5000 } },
|
|
624
|
-
},
|
|
625
|
-
$default: {
|
|
626
|
-
$set: { $location: 'error', $value: 'ECONNABORTED' }
|
|
627
|
-
},
|
|
628
|
-
},
|
|
439
|
+
request: $.has('incoming.path', { $value: '/api/proxy' }),
|
|
440
|
+
// Forward the request to an external service
|
|
441
|
+
forward: {
|
|
442
|
+
url: 'https://api.external-service.com/data',
|
|
629
443
|
},
|
|
444
|
+
// Manipulate the response from the external service
|
|
445
|
+
response: $.set('outgoing.data', {
|
|
446
|
+
$exec: (data) => ({
|
|
447
|
+
...data,
|
|
448
|
+
source: 'mock-server',
|
|
449
|
+
timestamp: new Date().toISOString(),
|
|
450
|
+
}),
|
|
451
|
+
}),
|
|
630
452
|
},
|
|
631
|
-
});
|
|
632
|
-
```
|
|
633
|
-
|
|
634
|
-
**Example using cURL**
|
|
635
|
-
|
|
636
|
-
```bash
|
|
637
|
-
curl -H "Content-type: application/json" -X POST --location "localhost:8080/_system/expectations" --data-binary @- << EOF
|
|
638
|
-
{
|
|
639
|
-
"schema": {
|
|
640
|
-
"request": {
|
|
641
|
-
"\$switch": {
|
|
642
|
-
"\$location": "method",
|
|
643
|
-
"\$cases": {
|
|
644
|
-
"GET": {"\$set": {"\$location": "delay", "\$value": 2000}},
|
|
645
|
-
"POST": {"\$set": {"\$location": "delay", "\$value": 5000}}
|
|
646
|
-
},
|
|
647
|
-
"\$default": {
|
|
648
|
-
"\$set": {"\$location": "error", "\$value": "ECONNABORTED"}
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
EOF
|
|
453
|
+
}));
|
|
655
454
|
```
|
|
656
455
|
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
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)
|
|
456
|
+
### Using Relative Paths with `baseUrl`
|
|
660
457
|
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
**Examples**
|
|
458
|
+
When using `baseUrl`, the original request path is appended to the target URL.
|
|
664
459
|
|
|
665
460
|
```ts
|
|
666
|
-
await client.createExpectation
|
|
667
|
-
incoming: {
|
|
668
|
-
query: {
|
|
669
|
-
foo: 'a' | 'b' | 'c';
|
|
670
|
-
bar?: string;
|
|
671
|
-
};
|
|
672
|
-
};
|
|
673
|
-
}>(({ $ }) => ({
|
|
461
|
+
await server.client.createExpectation(({ $ }) => ({
|
|
674
462
|
schema: {
|
|
675
|
-
request: $.
|
|
676
|
-
$.has('incoming.
|
|
677
|
-
|
|
463
|
+
request: $.and([
|
|
464
|
+
$.has('incoming.path', { $value: '/api/v1/*' }),
|
|
465
|
+
// Rewrite the path from v1 to v2 before forwarding
|
|
466
|
+
$.set('incoming.path', {
|
|
467
|
+
$exec: (path) => path.replace('/v1/', '/v2/'),
|
|
468
|
+
}),
|
|
678
469
|
]),
|
|
470
|
+
forward: {
|
|
471
|
+
// If request is '/api/v1/users', it forwards to 'https://legacy-api.com/api/v2/users'
|
|
472
|
+
baseUrl: 'https://legacy-api.com',
|
|
473
|
+
},
|
|
474
|
+
response: $.set('outgoing.headers', '$path', 'x-proxied-by', { $value: 'mock-server' }),
|
|
679
475
|
},
|
|
680
476
|
}));
|
|
681
477
|
```
|
|
682
478
|
|
|
479
|
+
### Transforming Request Data Before Forwarding
|
|
480
|
+
|
|
481
|
+
You can also modify the request body or headers before they are sent to the target API.
|
|
482
|
+
|
|
683
483
|
```ts
|
|
684
|
-
|
|
484
|
+
interface SearchRequest {
|
|
685
485
|
incoming: {
|
|
686
|
-
query: {
|
|
687
|
-
foo: 'a' | 'b' | 'c';
|
|
688
|
-
bar?: string;
|
|
689
|
-
};
|
|
690
|
-
};
|
|
691
|
-
outgoing: {
|
|
692
486
|
data: {
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
baz: 'a' | 'b' | 'c';
|
|
696
|
-
};
|
|
487
|
+
query: string;
|
|
488
|
+
limit?: number;
|
|
697
489
|
};
|
|
698
490
|
};
|
|
699
|
-
}
|
|
700
|
-
schema: {
|
|
701
|
-
response: $.and([
|
|
702
|
-
$.switch('incoming.query', '$exec', (payload) => payload.foo, {
|
|
703
|
-
$cases: {
|
|
704
|
-
'a': $.set('outgoing.data', '$path', 'bar.baz', { $value: 'a' }),
|
|
705
|
-
'b': $.set('outgoing.data', '$path', 'bar.baz', { $value: 'b' }),
|
|
706
|
-
},
|
|
707
|
-
}),
|
|
491
|
+
}
|
|
708
492
|
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
493
|
+
await server.client.createExpectation<SearchRequest>(({ $ }) => ({
|
|
494
|
+
schema: {
|
|
495
|
+
request: $.and([
|
|
496
|
+
$.has('incoming.path', { $value: '/api/search' }),
|
|
497
|
+
// Inject API key and transform data before forwarding
|
|
498
|
+
$.merge('incoming.headers', { $value: { 'X-API-Key': 'secret-token' } }),
|
|
499
|
+
$.set('incoming.data', {
|
|
500
|
+
$exec: (data) => ({
|
|
501
|
+
...data,
|
|
502
|
+
limit: data.limit ?? 20,
|
|
503
|
+
q: data.query, // Rename 'query' to 'q' for the external API
|
|
504
|
+
})
|
|
713
505
|
}),
|
|
714
506
|
]),
|
|
507
|
+
forward: {
|
|
508
|
+
url: 'https://external-search-service.com/v1/query',
|
|
509
|
+
},
|
|
715
510
|
},
|
|
716
511
|
}));
|
|
717
512
|
```
|
|
718
513
|
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
Storage is a temporary storage that provides an access to read/write [containers](#containers)
|
|
722
|
-
|
|
723
|
-
| Property | Type | Description |
|
|
724
|
-
|--|--|--|
|
|
725
|
-
| find | `(key: string ∣ object) => Container ∣ null` | Finds a container in storage. Every `key` provided as `object` will hashed using [FNV1A-64](https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function) algorithm |
|
|
726
|
-
| delete | `(key: string ∣ object) => Container ∣ null` | Deletes a container in storage. Every `key` provided as `object` will hashed using [FNV1A-64](https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function) algorithm |
|
|
727
|
-
| register | `(configuration: Container) => Container` | Registers a container in storage (overrides if existent) |
|
|
728
|
-
| provide | `(configuration: Container) => Container` | Finds or registers a container in storage |
|
|
729
|
-
|
|
730
|
-
As a temporary storage it has a job to garbage an expired containers. Use `containers.expiredCleaningInterval` to setup an interval of clearance in [configuration](#configuration)
|
|
731
|
-
|
|
732
|
-
> **!NOTE** See example of usage in [containers](#containers) section below
|
|
733
|
-
|
|
734
|
-
## Containers
|
|
735
|
-
|
|
736
|
-
| Property | Type | Description |
|
|
737
|
-
|--|--|--|
|
|
738
|
-
| key | `string` | A key of container |
|
|
739
|
-
| prefix | `string` | A prefix of container |
|
|
740
|
-
| payload | `object` | An object with custom data |
|
|
741
|
-
| ttl | `number` | Time to live of container in seconds **(default: 1h)** |
|
|
742
|
-
| expiresAt | `number` | An expiration date/time as unix timestamp with milliseconds |
|
|
743
|
-
| bind | `(key: string ∣ object) => Container` | Binds a container to one more key. Every `key` provided as `object` will hashed using [FNV1A-64](https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function) algorithm |
|
|
744
|
-
| unbind | `(key: string ∣ object) => Container` | Unbinds a container from key. Every `key` provided as `object` will hashed using [FNV1A-64](https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function) algorithm |
|
|
745
|
-
| assign | `(payload: object ∣ (payload: object) => object) => Container` | Uses as payload predicate to assign payload values to existent |
|
|
746
|
-
| merge | `(payload: object ∣ (payload: object) => object) => Container` | Uses as payload predicate to deep merge of payload values with existent |
|
|
514
|
+
### WebSocket Forwarding and Manipulation
|
|
747
515
|
|
|
748
|
-
|
|
749
|
-
**Example**
|
|
516
|
+
You can forward WebSocket connections to a real server and manipulate the message stream on the fly.
|
|
750
517
|
|
|
751
518
|
```ts
|
|
752
|
-
await client.createExpectation
|
|
753
|
-
|
|
754
|
-
counter: number;
|
|
755
|
-
};
|
|
756
|
-
}>(({ $ }) => ({
|
|
757
|
-
schema: {
|
|
758
|
-
request: $.set('container', {
|
|
759
|
-
$exec: (container, { context }) => context.storage
|
|
760
|
-
.provide({ key: 'foo', payload: { counter: 0 } })
|
|
761
|
-
.assign((payload) => ({ counter: payload.counter + 1 }))
|
|
762
|
-
}),
|
|
519
|
+
await server.client.createExpectation(({ $, utils }) => ({
|
|
520
|
+
transports: utils.transports(['ws']),
|
|
763
521
|
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
522
|
+
schema: {
|
|
523
|
+
request: $.has('incoming.path', { $value: '/ws/proxy' }),
|
|
524
|
+
forward: {
|
|
525
|
+
baseUrl: 'wss://real-server.com',
|
|
526
|
+
},
|
|
527
|
+
response: $.set('outgoing.stream', {
|
|
528
|
+
$exec: (stream, { rx }) => {
|
|
529
|
+
// 'stream' is the Observable from the real server
|
|
530
|
+
return stream?.pipe(
|
|
531
|
+
// Inject extra message at the beginning
|
|
532
|
+
rx.startWith({ event: 'proxy-init', timestamp: Date.now() }),
|
|
533
|
+
// Transform real messages
|
|
534
|
+
rx.map((message) => ({ ...message, intercepted: true }))
|
|
535
|
+
);
|
|
536
|
+
},
|
|
768
537
|
}),
|
|
769
538
|
},
|
|
770
539
|
}));
|
|
771
540
|
```
|
|
772
541
|
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
> **!NOTE** Cache is usable **only** to store a payload of forwarded requests
|
|
542
|
+
### Conditional Caching
|
|
776
543
|
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
To configure it use `database.redis` configuration on the mock server start options
|
|
780
|
-
|
|
781
|
-
**Example**
|
|
544
|
+
You can control when the mock server should cache a forwarded response. For example, only cache responses with a `200 OK` status.
|
|
782
545
|
|
|
783
546
|
```ts
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
547
|
+
await server.client.createExpectation(({ $ }) => ({
|
|
548
|
+
schema: {
|
|
549
|
+
request: $.has('incoming.path', { $value: '/api/cacheable' }),
|
|
550
|
+
forward: {
|
|
551
|
+
baseUrl: 'https://api.service.com',
|
|
552
|
+
cache: {
|
|
553
|
+
ttl: 3600, // 1 hour
|
|
554
|
+
},
|
|
792
555
|
},
|
|
556
|
+
response: $.set('cache', '$path', 'isEnabled', {
|
|
557
|
+
$exec: (value, { context }) => context.outgoing.status === 200,
|
|
558
|
+
}),
|
|
793
559
|
},
|
|
794
|
-
});
|
|
560
|
+
}));
|
|
795
561
|
```
|
|
796
562
|
|
|
797
|
-
|
|
563
|
+
### Custom Cache Key
|
|
798
564
|
|
|
799
|
-
|
|
800
|
-
1. Preparing incoming request...
|
|
801
|
-
2. Preparing [request schema](#schema) in expectation...
|
|
802
|
-
3. Setting up cache configuration from [context](#context) or [forward.cache](#forwarding)...
|
|
803
|
-
4. If `cache.isEnabled` is equals `true` the mock server checks a cache using provided configuration
|
|
804
|
-
5. If `key` was not provided a key for cache will calculated with `path`, `method`, `data` and `query` property values using [FNV1A-64](https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function) algorithm
|
|
805
|
-
6. If cache was found then step `7` is skipping
|
|
806
|
-
7. Forwarding a request....
|
|
807
|
-
8. Preparing [response schema](#schema) in expectation...
|
|
808
|
-
9. Setting up cache configuration from [context](#context)...
|
|
809
|
-
10. If `cache.isEnabled` is equals `true` the mock server will write a cache over provided `ttl`
|
|
810
|
-
11. Replying...
|
|
811
|
-
|
|
812
|
-
**Example**
|
|
565
|
+
By default, the cache key is a hash generated from the `incoming` object (path, method, data, query). You can override this with a custom key.
|
|
813
566
|
|
|
814
567
|
```ts
|
|
815
|
-
await client.createExpectation(({ $ }) => ({
|
|
568
|
+
await server.client.createExpectation(({ $ }) => ({
|
|
816
569
|
schema: {
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
570
|
+
request: $.and([
|
|
571
|
+
$.has('incoming.path', { $value: '/api/cache-custom' }),
|
|
572
|
+
$.set('cache', '$path', 'key', {
|
|
573
|
+
// Use a specific header as the cache key
|
|
574
|
+
$exec: (key, { context }) => context.incoming.headers['x-user-id'],
|
|
575
|
+
}),
|
|
576
|
+
]),
|
|
821
577
|
forward: {
|
|
822
|
-
baseUrl: 'https://
|
|
823
|
-
|
|
578
|
+
baseUrl: 'https://api.service.com',
|
|
824
579
|
cache: {
|
|
825
|
-
|
|
580
|
+
isEnabled: true,
|
|
581
|
+
ttl: 600,
|
|
826
582
|
},
|
|
827
583
|
},
|
|
828
584
|
},
|
|
829
585
|
}));
|
|
830
|
-
```
|
|
831
|
-
|
|
832
|
-
## State
|
|
833
|
-
|
|
834
|
-
State is a unique storage of each request. It can be used to handle complex expectations
|
|
835
586
|
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
**Example**
|
|
839
|
-
|
|
840
|
-
```ts
|
|
841
|
-
await client.createExpectation<{
|
|
842
|
-
state: {
|
|
843
|
-
id?: number;
|
|
844
|
-
};
|
|
845
|
-
incoming: {
|
|
846
|
-
query: {
|
|
847
|
-
foo: 'a' | 'b' | 'c';
|
|
848
|
-
};
|
|
849
|
-
};
|
|
850
|
-
outgoing: {
|
|
851
|
-
data: {
|
|
852
|
-
id: number;
|
|
853
|
-
};
|
|
854
|
-
};
|
|
855
|
-
}>(({ $ }) => ({
|
|
587
|
+
// You can also provide an object as a key; it will be automatically hashed
|
|
588
|
+
await server.client.createExpectation(({ $ }) => ({
|
|
856
589
|
schema: {
|
|
857
590
|
request: $.and([
|
|
858
|
-
$.
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
591
|
+
$.has('incoming.path', { $value: '/api/cache-object' }),
|
|
592
|
+
$.set('cache', '$path', 'key', {
|
|
593
|
+
// Cache depends on specific properties of the incoming data
|
|
594
|
+
$exec: (key, { context }) => ({
|
|
595
|
+
id: context.incoming.data.id,
|
|
596
|
+
type: context.incoming.data.type,
|
|
597
|
+
}),
|
|
863
598
|
}),
|
|
864
599
|
]),
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
600
|
+
forward: {
|
|
601
|
+
baseUrl: 'https://api.service.com',
|
|
602
|
+
cache: { isEnabled: true },
|
|
603
|
+
},
|
|
868
604
|
},
|
|
869
605
|
}));
|
|
870
606
|
```
|
|
871
607
|
|
|
872
|
-
|
|
608
|
+
### Request State
|
|
873
609
|
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
By default a number of seed takes from `X-Use-Mock-Seed` in `incoming.headers`
|
|
877
|
-
|
|
878
|
-
**Example**
|
|
610
|
+
State is a unique storage for each request. It can be used to pass data between operators or expectations. By default, it's extracted from the `X-Use-Mock-State` header (JSON in Base64).
|
|
879
611
|
|
|
880
612
|
```ts
|
|
881
|
-
|
|
613
|
+
interface MyState {
|
|
614
|
+
state: {
|
|
615
|
+
internalId: number;
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
await server.client.createExpectation<MyState>(({ $ }) => ({
|
|
882
620
|
schema: {
|
|
883
621
|
request: $.and([
|
|
884
|
-
$.
|
|
622
|
+
$.has('incoming.path', { $value: '/api/stateful' }),
|
|
623
|
+
$.set('state', {
|
|
624
|
+
// Compute and store data in state for later use
|
|
625
|
+
$exec: (state) => ({ internalId: state.internalId ?? Math.random() }),
|
|
626
|
+
}),
|
|
885
627
|
]),
|
|
886
628
|
response: $.set('outgoing.data', {
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
last_name: faker.person.lastName('male'),
|
|
629
|
+
// Use the stored state in response building
|
|
630
|
+
$exec: (payload, { state }) => ({
|
|
631
|
+
id: state.internalId,
|
|
891
632
|
}),
|
|
892
633
|
}),
|
|
893
634
|
},
|
|
894
635
|
}));
|
|
895
636
|
```
|
|
896
637
|
|
|
897
|
-
|
|
638
|
+
### Storage and Containers
|
|
898
639
|
|
|
899
|
-
|
|
640
|
+
Storage provides access to **Containers**—temporary cells used to store data between requests or sync multiple expectations. Unlike `state`, which is request-scoped, Containers are persistent until they expire (TTL).
|
|
900
641
|
|
|
901
642
|
```ts
|
|
902
|
-
{
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
To define a `incoming.data` as XML in incoming request `incoming.headers` should have `Content-Type: application/xml`.
|
|
908
|
-
|
|
909
|
-
The same with `outgoing.data` and `outgoing.headers`
|
|
910
|
-
|
|
911
|
-
**Example of serialized XML**
|
|
912
|
-
|
|
913
|
-
```xml
|
|
914
|
-
<tag type="default">
|
|
915
|
-
<nested type="nested">456</nested>
|
|
916
|
-
123
|
|
917
|
-
</tag>
|
|
918
|
-
```
|
|
919
|
-
|
|
920
|
-
**Example of parsed XML**
|
|
921
|
-
|
|
922
|
-
```json
|
|
923
|
-
{
|
|
924
|
-
"tag":{
|
|
925
|
-
"nested":{
|
|
926
|
-
"#text":456,
|
|
927
|
-
"@_type":"nested"
|
|
928
|
-
},
|
|
929
|
-
"#text":123,
|
|
930
|
-
"@_type":"default"
|
|
931
|
-
}
|
|
643
|
+
interface CounterContainer {
|
|
644
|
+
container: {
|
|
645
|
+
count: number;
|
|
646
|
+
};
|
|
932
647
|
}
|
|
933
|
-
```
|
|
934
|
-
|
|
935
|
-
To parse an XML manually the application lib provides utils:
|
|
936
|
-
|
|
937
|
-
```ts
|
|
938
|
-
import { parsePayload, serializePayload } from '@n1k1t/mock-server';
|
|
939
|
-
|
|
940
|
-
const parsed = parsePayload('xml', '<tag>123</tag>'); // { tag: 123 }
|
|
941
|
-
const serialized = serializePayload('xml', parsed); // '<tag>123</tag>'
|
|
942
|
-
```
|
|
943
|
-
|
|
944
|
-
# API
|
|
945
|
-
|
|
946
|
-
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)
|
|
947
|
-
|
|
948
|
-
The `HTTP API` and `RemoteClient` have some usage restrictions like:
|
|
949
|
-
- 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
|
|
950
|
-
- Plugins are not supported
|
|
951
|
-
|
|
952
|
-
## Ping
|
|
953
|
-
|
|
954
|
-
`INPUT` → `GET /_system/ping`
|
|
955
|
-
|
|
956
|
-
`OUTPUT`
|
|
957
|
-
|
|
958
|
-
| Type | Description |
|
|
959
|
-
|--|--|
|
|
960
|
-
| `string` | A `pong` message |
|
|
961
648
|
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
649
|
+
await server.client.createExpectation<CounterContainer>(({ $ }) => ({
|
|
650
|
+
schema: {
|
|
651
|
+
request: $.set('container', {
|
|
652
|
+
$exec: (container, { context }) => context.storage
|
|
653
|
+
// provide() finds an existing container by key or creates a new one with the given payload
|
|
654
|
+
.provide({ key: 'my-counter', payload: { count: 0 } })
|
|
655
|
+
.assign((payload) => ({ count: payload.count + 1 })),
|
|
656
|
+
}),
|
|
657
|
+
response: $.set('outgoing.data', {
|
|
658
|
+
$exec: (payload, { context }) => ({
|
|
659
|
+
// Access the incremented value from the container
|
|
660
|
+
currentCount: context.container!.payload.count,
|
|
661
|
+
}),
|
|
662
|
+
}),
|
|
663
|
+
},
|
|
664
|
+
}));
|
|
966
665
|
```
|
|
967
666
|
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
```ts
|
|
971
|
-
import { MockServer } from '@n1k1t/mock-server';
|
|
972
|
-
|
|
973
|
-
const server = await MockServer.start({ host: 'localhost', port: 8080 });
|
|
974
|
-
await server.client.ping();
|
|
975
|
-
```
|
|
667
|
+
### Cross-Expectation Sync
|
|
976
668
|
|
|
977
|
-
|
|
669
|
+
You can use Containers to synchronize data between different endpoints. For example, one endpoint increments a counter, and another retrieves its current value.
|
|
978
670
|
|
|
979
671
|
```ts
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
```
|
|
985
|
-
|
|
986
|
-
## Create expectation
|
|
987
|
-
|
|
988
|
-
`INPUT` → `POST /_system/expectations`
|
|
989
|
-
|
|
990
|
-
| Property | Nested | Type | Optional | Description |
|
|
991
|
-
|--|--|--|--|--|
|
|
992
|
-
| schema | [Schema](#schema) | `object` | | An expectation schema |
|
|
993
|
-
| name | | `string` | * | A preferred name for an expectation |
|
|
994
|
-
|
|
995
|
-
`OUTPUT`
|
|
996
|
-
|
|
997
|
-
| Property | Nested | Type | Optional | Description |
|
|
998
|
-
|--|--|--|--|--|
|
|
999
|
-
| id | | `string` | | An expectation ID |
|
|
1000
|
-
| name | | `string` | | An expectation name |
|
|
1001
|
-
| schema | [Schema](#schema) | `object` | | Provided schema |
|
|
1002
|
-
|
|
1003
|
-
**Using cURL**
|
|
1004
|
-
|
|
1005
|
-
```bash
|
|
1006
|
-
curl -H "Content-type: application/json" -X POST --location "localhost:8080/_system/expectations" --data-binary @- << EOF
|
|
1007
|
-
{
|
|
1008
|
-
"schema": {
|
|
1009
|
-
"request": {
|
|
1010
|
-
"\$has": {
|
|
1011
|
-
"\$location": "method",
|
|
1012
|
-
"\$value": "GET"
|
|
1013
|
-
}
|
|
1014
|
-
}
|
|
1015
|
-
}
|
|
672
|
+
interface SyncContainer {
|
|
673
|
+
container: {
|
|
674
|
+
count: number;
|
|
675
|
+
};
|
|
1016
676
|
}
|
|
1017
|
-
EOF
|
|
1018
|
-
```
|
|
1019
677
|
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
import { MockServer } from '@n1k1t/mock-server';
|
|
1024
|
-
|
|
1025
|
-
const server = await MockServer.start({ host: 'localhost', port: 8080 });
|
|
1026
|
-
const expectation = await server.client.createExpectation({
|
|
678
|
+
// Expectation 1: Increments the counter
|
|
679
|
+
await server.client.createExpectation<SyncContainer>(({ $ }) => ({
|
|
680
|
+
name: 'Incrementer',
|
|
1027
681
|
schema: {
|
|
1028
|
-
request:
|
|
1029
|
-
$
|
|
1030
|
-
|
|
1031
|
-
$
|
|
1032
|
-
|
|
1033
|
-
|
|
682
|
+
request: $.and([
|
|
683
|
+
$.has('incoming.path', { $value: '/api/increment' }),
|
|
684
|
+
$.set('container', {
|
|
685
|
+
$exec: (container, { context }) => context.storage
|
|
686
|
+
.provide({ key: 'shared-counter', payload: { count: 0 } })
|
|
687
|
+
.assign((payload) => ({ count: payload.count + 1 })),
|
|
688
|
+
}),
|
|
689
|
+
]),
|
|
690
|
+
response: $.set('outgoing.data', { $value: { status: 'incremented' } }),
|
|
1034
691
|
},
|
|
1035
|
-
});
|
|
1036
|
-
|
|
1037
|
-
console.log('Mock expectation has created', expectation.id);
|
|
1038
|
-
```
|
|
1039
|
-
|
|
1040
|
-
**Using application lib on remotely**
|
|
1041
|
-
|
|
1042
|
-
```ts
|
|
1043
|
-
import { RemoteClient } from '@n1k1t/mock-server';
|
|
692
|
+
}));
|
|
1044
693
|
|
|
1045
|
-
|
|
1046
|
-
|
|
694
|
+
// Expectation 2: Retrieves the current value
|
|
695
|
+
await server.client.createExpectation<SyncContainer>(({ $ }) => ({
|
|
696
|
+
name: 'Getter',
|
|
1047
697
|
schema: {
|
|
1048
|
-
request:
|
|
1049
|
-
$
|
|
1050
|
-
|
|
1051
|
-
$
|
|
1052
|
-
|
|
1053
|
-
|
|
698
|
+
request: $.and([
|
|
699
|
+
$.has('incoming.path', { $value: '/api/count' }),
|
|
700
|
+
$.set('container', {
|
|
701
|
+
$exec: (container, { context }) => context.storage
|
|
702
|
+
.provide({ key: 'shared-counter', payload: { count: 0 } }),
|
|
703
|
+
}),
|
|
704
|
+
]),
|
|
705
|
+
response: $.set('outgoing.data', {
|
|
706
|
+
$exec: (payload, { context }) => ({
|
|
707
|
+
total: context.container!.payload.count,
|
|
708
|
+
}),
|
|
709
|
+
}),
|
|
1054
710
|
},
|
|
1055
|
-
});
|
|
1056
|
-
|
|
1057
|
-
console.log('Mock expectation has created', expectation.id);
|
|
711
|
+
}));
|
|
1058
712
|
```
|
|
1059
713
|
|
|
1060
|
-
##
|
|
1061
|
-
|
|
1062
|
-
`INPUT` → `PUT /_system/expectations`
|
|
1063
|
-
|
|
1064
|
-
| Property | Nested | Type | Optional | Description |
|
|
1065
|
-
|--|--|--|--|--|
|
|
1066
|
-
| id | | `string` | | ID of a registered expectation |
|
|
1067
|
-
| set | | `object` | | A payload to set |
|
|
1068
|
-
| | name | `string` | * | A preferred name for an expectation |
|
|
1069
|
-
| | schema | [Schema](#schema) | * | An expectation schema |
|
|
714
|
+
## Extensions
|
|
1070
715
|
|
|
1071
|
-
|
|
716
|
+
### Redis Configuration
|
|
1072
717
|
|
|
1073
|
-
|
|
1074
|
-
|--|--|--|--|--|
|
|
1075
|
-
| id | | `string` | | An expectation ID |
|
|
1076
|
-
| name | | `string` | | An expectation name |
|
|
1077
|
-
| schema | [Schema](#schema) | `object` | | Provided schema |
|
|
1078
|
-
|
|
1079
|
-
**Using cURL**
|
|
1080
|
-
|
|
1081
|
-
```bash
|
|
1082
|
-
curl -H "Content-type: application/json" -X PUT --location "localhost:8080/_system/expectations" --data-binary @- << EOF
|
|
1083
|
-
{
|
|
1084
|
-
"id": "...",
|
|
1085
|
-
"set": {"name": "The expectation"}
|
|
1086
|
-
}
|
|
1087
|
-
EOF
|
|
1088
|
-
```
|
|
1089
|
-
|
|
1090
|
-
**Using application lib on server side**
|
|
718
|
+
The mock server uses Redis for caching forwarded responses and persistent storage. You can configure the connection details during server startup.
|
|
1091
719
|
|
|
1092
720
|
```ts
|
|
1093
721
|
import { MockServer } from '@n1k1t/mock-server';
|
|
1094
722
|
|
|
1095
|
-
const server = await MockServer.start({
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
723
|
+
const server = await MockServer.start({
|
|
724
|
+
host: 'localhost',
|
|
725
|
+
port: 8080,
|
|
726
|
+
databases: {
|
|
727
|
+
redis: {
|
|
728
|
+
host: 'localhost',
|
|
729
|
+
port: 6379,
|
|
730
|
+
// Optional: password, keyPrefix, etc.
|
|
731
|
+
keyPrefix: 'my-mock-server:',
|
|
732
|
+
},
|
|
733
|
+
},
|
|
1099
734
|
});
|
|
1100
|
-
|
|
1101
|
-
console.log('Mock expectation has updated', expectation);
|
|
1102
735
|
```
|
|
1103
736
|
|
|
1104
|
-
|
|
737
|
+
### Remote Expectations with `RemoteClient`
|
|
738
|
+
|
|
739
|
+
The `RemoteClient` allows you to manage expectations on a running mock server instance from a remote application or a separate test suite.
|
|
1105
740
|
|
|
1106
741
|
```ts
|
|
1107
742
|
import { RemoteClient } from '@n1k1t/mock-server';
|
|
1108
743
|
|
|
1109
|
-
|
|
1110
|
-
const
|
|
1111
|
-
id: '...',
|
|
1112
|
-
set: { name: 'The expectation' }
|
|
1113
|
-
});
|
|
1114
|
-
|
|
1115
|
-
console.log('Mock expectation has updated', expectation);
|
|
1116
|
-
```
|
|
1117
|
-
|
|
1118
|
-
## Delete expectation
|
|
744
|
+
// 1. Connect to a running mock server
|
|
745
|
+
const client = await RemoteClient.connect({ baseUrl: 'http://localhost:8080' });
|
|
1119
746
|
|
|
1120
|
-
|
|
747
|
+
// 2. Create expectations remotely
|
|
748
|
+
await client.createExpectation(({ $, utils }) => ({
|
|
749
|
+
// Specify allowed transports (http, ws)
|
|
750
|
+
transports: utils.transports(['http']),
|
|
1121
751
|
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
```bash
|
|
1129
|
-
curl -H "Content-type: application/json" -X DELETE --location "localhost:8080/_system/expectations" --data-binary @- << EOF
|
|
1130
|
-
{
|
|
1131
|
-
"ids": ["..."]
|
|
1132
|
-
}
|
|
1133
|
-
EOF
|
|
752
|
+
schema: {
|
|
753
|
+
request: $.has('path', { $value: '/api/remote-mock' }),
|
|
754
|
+
response: $.set('outgoing.data', { $value: { success: true, source: 'remote-client' } }),
|
|
755
|
+
},
|
|
756
|
+
}));
|
|
1134
757
|
```
|
|
1135
758
|
|
|
1136
|
-
|
|
759
|
+
### Grouping with Providers
|
|
1137
760
|
|
|
1138
|
-
|
|
1139
|
-
import { MockServer } from '@n1k1t/mock-server';
|
|
1140
|
-
|
|
1141
|
-
const server = await MockServer.start({ host: 'localhost', port: 8080 });
|
|
1142
|
-
await server.client.deleteExpectations({
|
|
1143
|
-
ids: ['...'],
|
|
1144
|
-
});
|
|
1145
|
-
```
|
|
1146
|
-
|
|
1147
|
-
**Using application lib on remotely**
|
|
761
|
+
Providers allow grouping expectations and isolating them from each other. They are also used to route requests to specific sets of expectations based on path patterns.
|
|
1148
762
|
|
|
1149
763
|
```ts
|
|
1150
|
-
import {
|
|
1151
|
-
|
|
1152
|
-
const client = await RemoteClient.connect({ host: 'localhost', port: 8080 });
|
|
1153
|
-
await client.deleteExpectations({
|
|
1154
|
-
ids: ['...'],
|
|
1155
|
-
});
|
|
1156
|
-
```
|
|
764
|
+
import { MockServer, Provider } from '@n1k1t/mock-server';
|
|
1157
765
|
|
|
1158
|
-
|
|
766
|
+
// 1. Define providers for different modules
|
|
767
|
+
const authProvider = Provider.build({ group: 'auth' });
|
|
768
|
+
const userProvider = Provider.build({ group: 'users' });
|
|
1159
769
|
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
> **!NOTE** Configuration must be provided in the same script like mock server
|
|
1163
|
-
|
|
1164
|
-
```ts
|
|
1165
|
-
import { config } from '@n1k1t/mock-server';
|
|
770
|
+
// 2. Setup server
|
|
771
|
+
const server = await MockServer.start({ port: 13000 });
|
|
1166
772
|
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
773
|
+
// 3. Add expectations to specific providers
|
|
774
|
+
await authProvider.client.createExpectation(({ $ }) => ({
|
|
775
|
+
schema: {
|
|
776
|
+
request: $.has('path', { $value: '/api/auth/login' }),
|
|
777
|
+
response: $.set('outgoing.data', { $value: { token: 'secret-token' } }),
|
|
1170
778
|
},
|
|
779
|
+
}));
|
|
1171
780
|
|
|
1172
|
-
|
|
1173
|
-
|
|
781
|
+
await userProvider.client.createExpectation(({ $ }) => ({
|
|
782
|
+
schema: {
|
|
783
|
+
request: $.has('path', { $value: '/api/users/me' }),
|
|
784
|
+
response: $.set('outgoing.data', { $value: { id: 1, name: 'John' } }),
|
|
1174
785
|
},
|
|
786
|
+
}));
|
|
1175
787
|
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
}
|
|
1179
|
-
});
|
|
788
|
+
// 4. Setup routing
|
|
789
|
+
server.router
|
|
790
|
+
.register('/api/auth/**', { provider: authProvider })
|
|
791
|
+
.register('/api/users/**', { provider: userProvider });
|
|
1180
792
|
```
|
|
1181
793
|
|
|
1182
|
-
|
|
794
|
+
### Logger
|
|
1183
795
|
|
|
1184
|
-
|
|
796
|
+
You can customize how the mock server logs information by using serializers or by redirecting logs to an external logger (e.g., Sentry, Winston).
|
|
1185
797
|
|
|
1186
798
|
```ts
|
|
1187
799
|
import { Logger } from '@n1k1t/mock-server';
|
|
1188
800
|
|
|
1189
|
-
// It defines your own logger methods
|
|
1190
801
|
Logger.useExternal({
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
// It defines a JSON serializers to mask some private data by keys on objects
|
|
1199
|
-
Logger.useSerializers({
|
|
1200
|
-
cvv: () => '***',
|
|
1201
|
-
card: (payload: string) => payload.slice(0, 8) + 'xxxx',
|
|
802
|
+
info: (title, ...messages) => {
|
|
803
|
+
// Send to your logging service
|
|
804
|
+
console.log(`[${title}]`, ...messages);
|
|
805
|
+
},
|
|
806
|
+
error: (title, ...messages) => {
|
|
807
|
+
// Handle errors specifically
|
|
808
|
+
},
|
|
1202
809
|
});
|
|
1203
810
|
```
|
|
1204
811
|
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
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`.
|
|
1208
|
-
|
|
1209
|
-
The `metaStorage.provide()` returns an instance of `meta` that contains basic data like:
|
|
1210
|
-
|
|
1211
|
-
| Property | Type | Optional | Description |
|
|
1212
|
-
|--|--|--|--|
|
|
1213
|
-
| operationId | `string` | | UUID v4 |
|
|
1214
|
-
| requestId | `string` | * | `X-Request-Id` from `incoming.headers` |
|
|
1215
|
-
|
|
1216
|
-
**Setup**
|
|
812
|
+
Serializers allow you to mask sensitive data (like passwords or credit card numbers) before they are logged.
|
|
1217
813
|
|
|
1218
814
|
```ts
|
|
1219
|
-
import { Logger
|
|
1220
|
-
|
|
1221
|
-
// Some external logger with meta context support
|
|
1222
|
-
const external = {...};
|
|
815
|
+
import { Logger } from '@n1k1t/mock-server';
|
|
1223
816
|
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
info: (...messages: string[]) => external.log(metaStorage.provide(), ...messages),
|
|
1228
|
-
warn: (...messages: string[]) => external.warn(metaStorage.provide(), ...messages),
|
|
1229
|
-
error: (...messages: string[]) => external.error(metaStorage.provide(), ...messages),
|
|
1230
|
-
fatal: (...messages: string[]) => external.error(metaStorage.provide(), ...messages),
|
|
817
|
+
Logger.useSerializers({
|
|
818
|
+
password: () => '***',
|
|
819
|
+
token: (val: string) => `${val.slice(0, 4)}...`,
|
|
1231
820
|
});
|
|
1232
821
|
```
|
|
1233
822
|
|
|
1234
|
-
|
|
823
|
+
## License
|
|
1235
824
|
|
|
1236
|
-
|
|
1237
|
-
await server.client.createExpectation({
|
|
1238
|
-
schema: {
|
|
1239
|
-
request: {
|
|
1240
|
-
$exec: ({ context, logger }) => {
|
|
1241
|
-
// Here logger should have a meta context like { operationId: '...' }
|
|
1242
|
-
logger.info('Before')
|
|
1243
|
-
},
|
|
1244
|
-
$exec: ({ context, logger, meta }) => {
|
|
1245
|
-
// It enriches meta context for further logs of request
|
|
1246
|
-
meta.merge({ foo: 'bar' });
|
|
1247
|
-
},
|
|
1248
|
-
$exec: ({ context, logger, meta }) => {
|
|
1249
|
-
// Now logger should have a meta context like { foo: 'bar', operationId: '...' }
|
|
1250
|
-
logger.info('After')
|
|
1251
|
-
},
|
|
1252
|
-
},
|
|
1253
|
-
},
|
|
1254
|
-
});
|
|
1255
|
-
```
|
|
825
|
+
MIT
|