@n1k1t/mock-server 0.3.0 → 1.0.1
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 +590 -1006
- 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,828 @@
|
|
|
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
|
+
- [Add Skills](#add-skills)
|
|
20
|
+
- [Simple Example](#simple-example)
|
|
21
|
+
- [Overview](#overview)
|
|
22
|
+
- [GUI](#gui)
|
|
23
|
+
- [Storage & Containers](#storage--containers)
|
|
24
|
+
- [Cache](#cache)
|
|
25
|
+
- [State & Seeds](#state--seeds)
|
|
26
|
+
- [Mock Server Utilities](#mock-server-utilities)
|
|
27
|
+
- [Expectation Operators ($)](#expectation-operators-)
|
|
28
|
+
- [Operator Locations](#operator-locations)
|
|
29
|
+
- [Operator Context](#operator-context)
|
|
30
|
+
- [Container Methods](#container-methods)
|
|
31
|
+
- [Usage](#usage)
|
|
32
|
+
- [Complex Expectations with $and](#complex-expectations-with-and)
|
|
33
|
+
- [Custom Logic with $exec](#custom-logic-with-exec)
|
|
34
|
+
- [Dynamic Response Manipulation](#dynamic-response-manipulation)
|
|
35
|
+
- [Binary Data and Files](#binary-data-and-files)
|
|
36
|
+
- [XML Support](#xml-support)
|
|
37
|
+
- [Simulating Network Errors](#simulating-network-errors)
|
|
38
|
+
- [Disabled By Default](#disabled-by-default)
|
|
39
|
+
- [Type Safety](#type-safety)
|
|
40
|
+
- [WebSocket Support](#websocket-support)
|
|
41
|
+
- [Request Forwarding](#request-forwarding)
|
|
42
|
+
- [Using Relative Paths with baseUrl](#using-relative-paths-with-baseurl)
|
|
43
|
+
- [Transforming Request Data Before Forwarding](#transforming-request-data-before-forwarding)
|
|
44
|
+
- [WebSocket Forwarding and Manipulation](#websocket-forwarding-and-manipulation)
|
|
45
|
+
- [Conditional Caching](#conditional-caching)
|
|
46
|
+
- [Custom Cache Key](#custom-cache-key)
|
|
47
|
+
- [Request State](#request-state)
|
|
48
|
+
- [Storage and Containers](#storage-and-containers)
|
|
49
|
+
- [Cross-Expectation Sync](#cross-expectation-sync)
|
|
50
|
+
- [Extensions](#extensions)
|
|
51
|
+
- [Redis Configuration](#redis-configuration)
|
|
52
|
+
- [Remote Expectations with RemoteClient](#remote-expectations-with-remoteclient)
|
|
53
|
+
- [Grouping with Providers](#grouping-with-providers)
|
|
54
|
+
- [Logger](#logger)
|
|
55
|
+
- [License](#license)
|
|
56
|
+
|
|
57
|
+
## Features
|
|
58
|
+
|
|
59
|
+
- **Multi-protocol Support**: Full support for both **HTTP** and **WebSocket** protocols.
|
|
60
|
+
- **Flexible Matching**: Match requests by path, method, headers, and data using regex, minimatch, or custom functions.
|
|
61
|
+
- **Payload Manipulation**: Modify request or response payloads on the fly.
|
|
62
|
+
- **Request Forwarding**: Proxy requests to real services with optional caching.
|
|
63
|
+
- **Built-in GUI**: Monitor traffic and manage expectations through a web interface.
|
|
64
|
+
- **Typed Expectations**: First-class support for TypeScript to keep your mocks type-safe.
|
|
65
|
+
- **Advanced State Management**: Store data across requests using Containers or Request State.
|
|
66
|
+
- **XML Support**: Native parsing and serialization for XML payloads.
|
|
67
|
+
|
|
68
|
+
## Installation
|
|
49
69
|
|
|
50
70
|
```bash
|
|
51
71
|
npm i @n1k1t/mock-server
|
|
52
72
|
```
|
|
53
73
|
|
|
54
|
-
##
|
|
74
|
+
## Add Skills
|
|
55
75
|
|
|
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
|
|
76
|
+
If you are using the [skills](https://www.npmjs.com/package/skills) package, you can add `n1k1t/mock-server` to your project using the following command:
|
|
81
77
|
|
|
82
78
|
```bash
|
|
83
|
-
npx
|
|
79
|
+
npx skills add n1k1t/mock-server
|
|
84
80
|
```
|
|
85
81
|
|
|
86
|
-
|
|
82
|
+
This package includes the following skills for better integration with AI agents:
|
|
87
83
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
MockServer.start({ host: 'localhost', port: 8080 });
|
|
91
|
-
```
|
|
84
|
+
- **`mock-server-basic`**: Core entities management, including server initialization, operators (`$`), expectations, containers, forwarding, and cache.
|
|
85
|
+
- **`mock-server-extensions`**: Advanced features like Redis configuration, `RemoteClient` for remote management, `Provider` for grouping/routing, and `Logger` customization.
|
|
92
86
|
|
|
93
|
-
|
|
87
|
+
## Simple Example
|
|
94
88
|
|
|
95
89
|
```ts
|
|
96
90
|
import { MockServer } from '@n1k1t/mock-server';
|
|
97
|
-
MockServer.start({ host: 'localhost', port: 8080 });
|
|
98
|
-
```
|
|
99
91
|
|
|
100
|
-
|
|
92
|
+
const server = await MockServer.start({ host: 'localhost', port: 8080 });
|
|
101
93
|
|
|
102
|
-
|
|
94
|
+
// Create a type-safe expectation using the operator compiler ($)
|
|
95
|
+
await server.client.createExpectation(({ $ }) => ({
|
|
96
|
+
schema: {
|
|
97
|
+
request: $.has('incoming.path', { $value: '/api/hello' }),
|
|
98
|
+
response: $.set('outgoing.data', { $value: { message: 'world' } }),
|
|
99
|
+
},
|
|
100
|
+
}));
|
|
101
|
+
```
|
|
103
102
|
|
|
104
|
-
|
|
103
|
+
## Overview
|
|
105
104
|
|
|
106
|
-
|
|
105
|
+
### GUI
|
|
107
106
|
|
|
108
|
-
|
|
107
|
+
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
108
|
|
|
110
|
-
|
|
109
|
+
### Storage & Containers
|
|
111
110
|
|
|
112
|
-
|
|
111
|
+
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
112
|
|
|
114
|
-
|
|
113
|
+
### Cache
|
|
115
114
|
|
|
116
|
-
|
|
115
|
+
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
116
|
|
|
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 |
|
|
117
|
+
### State & Seeds
|
|
123
118
|
|
|
124
|
-
**
|
|
119
|
+
- **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).
|
|
120
|
+
- **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
121
|
|
|
126
122
|
```ts
|
|
127
|
-
await server.client.createExpectation({
|
|
123
|
+
await server.client.createExpectation(({ $ }) => ({
|
|
128
124
|
schema: {
|
|
129
|
-
request:
|
|
130
|
-
$
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
125
|
+
request: $.and([
|
|
126
|
+
$.has('incoming.path', { $value: '/api/faker' }),
|
|
127
|
+
// Manually set seed from query parameter or any other logic
|
|
128
|
+
$.set('seed', { $exec: (seed, { context }) => context.incoming.query.seed ?? context.seed ?? 12345 }),
|
|
129
|
+
]),
|
|
130
|
+
response: $.set('outgoing.data', {
|
|
131
|
+
$exec: (payload, { faker }) => ({
|
|
132
|
+
id: faker.number.int(), // Will be consistent for the same seed
|
|
133
|
+
name: faker.person.fullName(),
|
|
134
|
+
}),
|
|
135
|
+
}),
|
|
139
136
|
},
|
|
140
|
-
});
|
|
137
|
+
}));
|
|
141
138
|
```
|
|
142
139
|
|
|
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
|
-
|
|
140
|
+
## Mock Server Utilities
|
|
141
|
+
|
|
142
|
+
### Expectation Operators ($)
|
|
143
|
+
|
|
144
|
+
Expectations are built using operators that define how to match requests and transform responses.
|
|
145
|
+
|
|
146
|
+
- `$.has(location, { $path, ... })`: Checks if a value exists at the specified path. Supports `$value`, `$match` (minimatch), `$regExp`, and `$exec`.
|
|
147
|
+
- `$.set(location, { $value, ... })`: Sets a value at the specified path.
|
|
148
|
+
- `$.merge(location, { $value, ... })`: Merges an object into the value at the specified path.
|
|
149
|
+
- `$.remove(location, { $path, ... })`: Removes the value at the specified path.
|
|
150
|
+
- `$.and([...operators])`: Logical AND of multiple operators.
|
|
151
|
+
- `$.or([...operators])`: Logical OR of multiple operators.
|
|
152
|
+
- `$.not(operator)`: Logical NOT of an operator.
|
|
153
|
+
- `$.if({ $condition, $then, $else })`: Conditional execution of operators.
|
|
154
|
+
- `$.switch({ $cases, $default })`: Switch-case execution based on the value at the path.
|
|
155
|
+
- `$.exec(fn)`: Executes custom logic. In `match` mode, it must return a boolean. In `manipulate` mode, it can modify the context.
|
|
156
|
+
|
|
157
|
+
### Operator Locations
|
|
158
|
+
|
|
159
|
+
Many operators (like `$.has`, `$.set`, `$.merge`) accept a `location` as their first argument. This defines where the operation should be performed:
|
|
160
|
+
|
|
161
|
+
- `'incoming.path'`: The URL path of the request.
|
|
162
|
+
- `'incoming.method'`: The HTTP method.
|
|
163
|
+
- `'incoming.query'`: The query parameters object.
|
|
164
|
+
- `'incoming.headers'`: The request headers object.
|
|
165
|
+
- `'incoming.data'`: The request body (parsed JSON or XML).
|
|
166
|
+
- `'outgoing.status'`: The response status code.
|
|
167
|
+
- `'outgoing.headers'`: The response headers object.
|
|
168
|
+
- `'outgoing.data'`: The response body object.
|
|
169
|
+
- `'container'`: Access to a [Container](#container-methods).
|
|
170
|
+
- `'state'`: Access to [Request State](#request-state).
|
|
171
|
+
- `'cache'`: Access to [Forward Cache](#conditional-caching) configuration.
|
|
172
|
+
- `'seed'`: Access to the [Seed](#state--seeds) value.
|
|
173
|
+
- `'error'`: For [Simulating Network Errors](#simulating-network-errors).
|
|
174
|
+
|
|
175
|
+
When targeting an object (like `headers` or `data`), you can use `$path` as the second argument to target a nested value:
|
|
176
|
+
`$.has('incoming.headers', '$path', 'content-type', { $value: 'application/json' })`
|
|
177
|
+
|
|
178
|
+
### Operator Context
|
|
179
|
+
|
|
180
|
+
When using `$exec`, the second argument provides a context with useful utilities and data:
|
|
181
|
+
|
|
182
|
+
- **`context`**: The full request/response context.
|
|
183
|
+
- `incoming`: Input data (immutable in match mode).
|
|
184
|
+
- `path`: The request URL path.
|
|
185
|
+
- `method`: HTTP method (GET, POST, etc.).
|
|
186
|
+
- `query`: Object containing query parameters.
|
|
187
|
+
- `headers`: Object containing request headers.
|
|
188
|
+
- `data`: Parsed JSON/XML request body.
|
|
189
|
+
- `outgoing`: Output data (target for manipulation).
|
|
190
|
+
- `status`: HTTP status code.
|
|
191
|
+
- `headers`: Response headers.
|
|
192
|
+
- `data`: Response body object (serialized to JSON/XML).
|
|
193
|
+
- `dataRaw`: Buffer for binary data.
|
|
194
|
+
- `stream`: Observable for WebSocket messages.
|
|
195
|
+
- `storage`: Interface to manage [Containers](#container-methods).
|
|
196
|
+
- `container`: The currently bound container (if any).
|
|
197
|
+
- `cache`: Configuration for forwarding cache.
|
|
198
|
+
- `isEnabled`: Whether caching is enabled for this request.
|
|
199
|
+
- `prefix`: Redis key prefix.
|
|
200
|
+
- `key`: Custom cache key (string or object).
|
|
201
|
+
- `ttl`: Time to live in seconds.
|
|
202
|
+
- `hasRead`: (Internal) Whether cache was read.
|
|
203
|
+
- `hasWritten`: (Internal) Whether cache was written.
|
|
204
|
+
- **`state`**: The unique [Request State](#state--seeds). Persistent only for the duration of the current request.
|
|
205
|
+
- **`mode`**: The execution phase: `'match'` (deciding if expectation applies) or `'manipulate'` (preparing the response).
|
|
206
|
+
- **`logger`**: A scoped logger for debugging (`info`, `error`, `warn`).
|
|
207
|
+
- **`faker`**: Full `@faker-js/faker` instance for generating mock data.
|
|
208
|
+
- **`_`**: Lodash utility library.
|
|
209
|
+
- **`d`**: Dayjs instance for date/time manipulation.
|
|
210
|
+
- **`rx`**: RxJS exports for advanced WebSocket stream control.
|
|
211
|
+
|
|
212
|
+
### Container Methods
|
|
213
|
+
|
|
214
|
+
Containers (accessible via `context.storage`) provide several methods for managing data:
|
|
215
|
+
|
|
216
|
+
- `storage.provide({ key, payload, ttl })`: Retrieves an existing container by key or creates a new one with the provided payload and TTL.
|
|
217
|
+
- `storage.register({ key, payload, ttl })`: Forcefully creates and registers a new container, overwriting any existing one with the same key.
|
|
218
|
+
- `storage.find(key)`: Finds an existing container by key. Returns `undefined` if not found.
|
|
219
|
+
- `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.
|
|
220
|
+
- `container.merge(payload | fn)`: Deeply merges the given payload into the current container state.
|
|
221
|
+
- `container.bind(key)`: Binds the container to an additional key.
|
|
222
|
+
- `container.unbind()`: Removes the container from the storage.
|
|
223
|
+
|
|
224
|
+
## Usage
|
|
225
|
+
|
|
226
|
+
### Complex Expectations with `$and`
|
|
227
|
+
|
|
228
|
+
You can group multiple conditions using logical operators like `$.and`, `$.or`, and `$.not` to create sophisticated matching rules.
|
|
229
|
+
|
|
230
|
+
```ts
|
|
231
|
+
await server.client.createExpectation(({ $ }) => ({
|
|
220
232
|
schema: {
|
|
221
|
-
request:
|
|
222
|
-
$
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
},
|
|
229
|
-
{
|
|
230
|
-
$has: {
|
|
231
|
-
$location: 'method',
|
|
232
|
-
$value: 'GET',
|
|
233
|
-
},
|
|
234
|
-
},
|
|
235
|
-
],
|
|
236
|
-
},
|
|
233
|
+
request: $.and([
|
|
234
|
+
$.has('incoming.path', { $value: '/api/user' }),
|
|
235
|
+
$.has('incoming.method', { $value: 'POST' }),
|
|
236
|
+
// Checks if 'content-type' header equals 'application/json' using selection with $path = content-type
|
|
237
|
+
$.has('incoming.headers', '$path', 'content-type', { $value: 'application/json' }),
|
|
238
|
+
]),
|
|
239
|
+
response: $.set('outgoing.data', { $value: { status: 'success' } }),
|
|
237
240
|
},
|
|
238
|
-
});
|
|
241
|
+
}));
|
|
239
242
|
```
|
|
240
243
|
|
|
241
|
-
###
|
|
242
|
-
|
|
243
|
-
> **!NOTE** `$exec` operators [have restrictions](#api) when it defined over `HTTP API` or `RemoteClient`
|
|
244
|
+
### Custom Logic with `$exec`
|
|
244
245
|
|
|
245
|
-
|
|
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**
|
|
246
|
+
For scenarios that require dynamic calculations or external utilities, use the `$exec` operator.
|
|
259
247
|
|
|
260
248
|
```ts
|
|
261
|
-
await server.client.createExpectation({
|
|
249
|
+
await server.client.createExpectation(({ $ }) => ({
|
|
262
250
|
schema: {
|
|
263
|
-
request: {
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
251
|
+
request: $.has('incoming.path', {
|
|
252
|
+
// Custom matching logic: checks if path starts with '/api/data'
|
|
253
|
+
$exec: (path) => path.startsWith('/api/data')
|
|
254
|
+
}),
|
|
255
|
+
response: $.set('outgoing.data', {
|
|
256
|
+
// Use built-in utils like lodash (_), dayjs (d), or faker
|
|
257
|
+
$exec: (payload, { _, d, faker }) => ({
|
|
258
|
+
id: faker.string.uuid(),
|
|
259
|
+
timestamp: d().format(),
|
|
260
|
+
values: _.range(3).map(() => _.random(1, 100)),
|
|
261
|
+
}),
|
|
262
|
+
}),
|
|
269
263
|
},
|
|
270
|
-
});
|
|
271
|
-
```
|
|
272
|
-
|
|
273
|
-
**Example using cURL**
|
|
264
|
+
}));
|
|
274
265
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
{
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
266
|
+
// Use $.exec for full control over the expectation execution
|
|
267
|
+
await server.client.createExpectation(({ $ }) => ({
|
|
268
|
+
schema: {
|
|
269
|
+
request: $.exec(({ context, logger, mode }) => {
|
|
270
|
+
// mode can be 'match' (on catching request) or 'manipulate' (on manipulation)
|
|
271
|
+
if (mode === 'match') {
|
|
272
|
+
logger.info('Checking request path', context.incoming.path);
|
|
273
|
+
return context.incoming.path === '/api/custom';
|
|
283
274
|
}
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
}
|
|
287
|
-
|
|
275
|
+
}),
|
|
276
|
+
response: $.set('outgoing.data', { $value: { status: 'custom' } }),
|
|
277
|
+
},
|
|
278
|
+
}));
|
|
288
279
|
```
|
|
289
280
|
|
|
290
|
-
###
|
|
291
|
-
|
|
292
|
-
> **!NOTE** `$exec` operators [have restrictions](#api) when it defined over `HTTP API` or `RemoteClient`
|
|
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) |
|
|
281
|
+
### Dynamic Response Manipulation
|
|
301
282
|
|
|
302
|
-
|
|
283
|
+
You can dynamically adjust the response payload based on incoming request parameters (query, body, headers, etc.).
|
|
303
284
|
|
|
304
285
|
```ts
|
|
305
|
-
await server.client.createExpectation({
|
|
286
|
+
await server.client.createExpectation(({ $ }) => ({
|
|
306
287
|
schema: {
|
|
307
|
-
request: {
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
288
|
+
request: $.has('incoming.path', { $value: '/api/profile' }),
|
|
289
|
+
response: $.set('outgoing.data', {
|
|
290
|
+
$exec: (payload, { context }) => ({
|
|
291
|
+
id: context.incoming.query.userId,
|
|
292
|
+
// Conditionally include data based on request headers
|
|
293
|
+
debug: context.incoming.headers['x-debug'] === 'true'
|
|
294
|
+
? { timestamp: Date.now() }
|
|
295
|
+
: undefined
|
|
296
|
+
})
|
|
297
|
+
}),
|
|
314
298
|
},
|
|
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
|
|
299
|
+
}));
|
|
334
300
|
```
|
|
335
301
|
|
|
336
|
-
###
|
|
337
|
-
|
|
338
|
-
> **!NOTE** `$exec` operators [have restrictions](#api) when it defined over `HTTP API` or `RemoteClient`
|
|
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) |
|
|
302
|
+
### Binary Data and Files
|
|
347
303
|
|
|
348
|
-
|
|
304
|
+
You can return binary data (Buffers) as a response body. This is useful for mocking file downloads or image responses.
|
|
349
305
|
|
|
350
306
|
```ts
|
|
351
|
-
|
|
307
|
+
import { readFile } from 'fs/promises';
|
|
308
|
+
|
|
309
|
+
await server.client.createExpectation(({ $ }) => ({
|
|
352
310
|
schema: {
|
|
353
|
-
request: {
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
311
|
+
request: $.has('incoming.path', { $value: '/api/download' }),
|
|
312
|
+
response: $.and([
|
|
313
|
+
$.set('outgoing.headers', '$path', 'content-type', { $value: 'application/pdf' }),
|
|
314
|
+
$.set('outgoing.dataRaw', {
|
|
315
|
+
$exec: async () => await readFile('./path/to/document.pdf')
|
|
316
|
+
}),
|
|
317
|
+
]),
|
|
359
318
|
},
|
|
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
|
|
319
|
+
}));
|
|
378
320
|
```
|
|
379
321
|
|
|
380
|
-
###
|
|
322
|
+
### XML Support
|
|
381
323
|
|
|
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**
|
|
324
|
+
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
325
|
|
|
390
326
|
```ts
|
|
391
|
-
await server.client.createExpectation({
|
|
327
|
+
await server.client.createExpectation(({ $ }) => ({
|
|
392
328
|
schema: {
|
|
393
|
-
request:
|
|
394
|
-
|
|
395
|
-
|
|
329
|
+
request: $.and([
|
|
330
|
+
$.has('incoming.path', { $matchAnyOf: ['/api/user*'] }),
|
|
331
|
+
$.exec(({ context }) => context.incoming.headers['content-type'] === 'application/xml'),
|
|
332
|
+
]),
|
|
333
|
+
response: $.and([
|
|
334
|
+
$.set('outgoing.headers', '$path', 'content-type', { $value: 'application/xml' }),
|
|
335
|
+
$.set('outgoing.data', {
|
|
336
|
+
$value: {
|
|
337
|
+
user: {
|
|
338
|
+
info: {
|
|
339
|
+
'#text': 'John Doe',
|
|
340
|
+
'@_type': 'mocked',
|
|
341
|
+
'@_id': 123
|
|
342
|
+
}
|
|
343
|
+
},
|
|
344
|
+
},
|
|
345
|
+
}),
|
|
346
|
+
]),
|
|
396
347
|
},
|
|
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
|
|
348
|
+
}));
|
|
412
349
|
```
|
|
413
350
|
|
|
414
|
-
###
|
|
415
|
-
|
|
416
|
-
> **!NOTE** `$exec` operators [have restrictions](#api) when it defined over `HTTP API` or `RemoteClient`
|
|
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) |
|
|
351
|
+
### Simulating Network Errors
|
|
421
352
|
|
|
422
|
-
|
|
353
|
+
You can simulate connection drops or network errors by setting the `error` property.
|
|
423
354
|
|
|
424
355
|
```ts
|
|
425
|
-
await server.client.createExpectation({
|
|
356
|
+
await server.client.createExpectation(({ $ }) => ({
|
|
426
357
|
schema: {
|
|
427
|
-
request: {
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
},
|
|
358
|
+
request: $.has('incoming.path', { $value: '/api/error' }),
|
|
359
|
+
response: $.set('error', {
|
|
360
|
+
// Common error codes: ECONNRESET, ECONNABORTED, etc.
|
|
361
|
+
$value: 'ECONNRESET'
|
|
362
|
+
}),
|
|
433
363
|
},
|
|
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
|
|
364
|
+
}));
|
|
449
365
|
```
|
|
450
366
|
|
|
451
|
-
###
|
|
367
|
+
### Disabled by Default
|
|
452
368
|
|
|
453
|
-
|
|
454
|
-
|--|--|--|
|
|
455
|
-
| `object[]` | `object[]` | Provides [operators](#operators) schemas |
|
|
456
|
-
|
|
457
|
-
**Example using application**
|
|
369
|
+
You can create expectations that are disabled by default by setting `isEnabled: false`. These can be manually enabled later via the [GUI](#gui).
|
|
458
370
|
|
|
459
371
|
```ts
|
|
460
|
-
await server.client.createExpectation({
|
|
372
|
+
await server.client.createExpectation(({ $ }) => ({
|
|
373
|
+
isEnabled: false,
|
|
461
374
|
schema: {
|
|
462
|
-
request: {
|
|
463
|
-
|
|
464
|
-
{ $has: { $location: 'path', $match: 'foo/*' } },
|
|
465
|
-
{ $has: { $location: 'method', $valueAnyOf: ['GET', 'POST'] } },
|
|
466
|
-
],
|
|
467
|
-
},
|
|
375
|
+
request: $.has('incoming.path', { $value: '/api/hidden' }),
|
|
376
|
+
response: $.set('outgoing.data', { $value: { message: 'Activate me in GUI' } }),
|
|
468
377
|
},
|
|
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
|
|
378
|
+
}));
|
|
487
379
|
```
|
|
488
380
|
|
|
489
|
-
###
|
|
490
|
-
|
|
491
|
-
| Type (application) | Type (cURL) | Description |
|
|
492
|
-
|--|--|--|
|
|
493
|
-
| `object[]` | `object[]` | Provides [operators](#operators) schemas |
|
|
381
|
+
### Type Safety
|
|
494
382
|
|
|
495
|
-
|
|
383
|
+
You can provide generic types to `createExpectation` to ensure your schemas and `$exec` functions are fully typed.
|
|
496
384
|
|
|
497
385
|
```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
|
-
}
|
|
386
|
+
interface MyContext {
|
|
387
|
+
incoming: {
|
|
388
|
+
query: {
|
|
389
|
+
userId: string;
|
|
390
|
+
expand?: boolean;
|
|
391
|
+
};
|
|
392
|
+
};
|
|
393
|
+
outgoing: {
|
|
394
|
+
data: {
|
|
395
|
+
id: string;
|
|
396
|
+
name: string;
|
|
397
|
+
};
|
|
398
|
+
};
|
|
523
399
|
}
|
|
524
|
-
EOF
|
|
525
|
-
```
|
|
526
|
-
|
|
527
|
-
### $not
|
|
528
|
-
|
|
529
|
-
| Type (application) | Type (cURL) | Description |
|
|
530
|
-
|--|--|--|
|
|
531
|
-
| `object` | `object` | Provides an [operators](#operators) schema |
|
|
532
400
|
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
```ts
|
|
536
|
-
await server.client.createExpectation({
|
|
401
|
+
await server.client.createExpectation<MyContext>(({ $ }) => ({
|
|
537
402
|
schema: {
|
|
538
|
-
request: {
|
|
539
|
-
$
|
|
540
|
-
},
|
|
403
|
+
request: $.has('incoming.query', {
|
|
404
|
+
$exec: (query) => query.userId === '123'
|
|
405
|
+
}),
|
|
406
|
+
response: $.set('outgoing.data', {
|
|
407
|
+
$exec: (payload, { faker }) => ({
|
|
408
|
+
id: '123',
|
|
409
|
+
name: faker.person.fullName()
|
|
410
|
+
})
|
|
411
|
+
}),
|
|
541
412
|
},
|
|
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
|
|
413
|
+
}));
|
|
557
414
|
```
|
|
558
415
|
|
|
559
|
-
###
|
|
416
|
+
### WebSocket Support
|
|
560
417
|
|
|
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**
|
|
418
|
+
The mock server provides first-class support for WebSockets, allowing you to intercept and mock streaming data using [RxJS](https://rxjs.dev/).
|
|
568
419
|
|
|
569
420
|
```ts
|
|
570
|
-
await server.client.createExpectation({
|
|
421
|
+
await server.client.createExpectation(({ $, utils }) => ({
|
|
422
|
+
// 1. Specify 'ws' transport
|
|
423
|
+
transports: utils.transports(['ws']),
|
|
424
|
+
|
|
571
425
|
schema: {
|
|
572
|
-
request: {
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
426
|
+
request: $.has('incoming.path', { $value: '/ws/updates' }),
|
|
427
|
+
response: $.and([
|
|
428
|
+
// 2. Set custom WS status code if needed
|
|
429
|
+
$.set('outgoing.status', { $value: 1000 }),
|
|
430
|
+
// 3. Define the outgoing stream
|
|
431
|
+
$.set('outgoing.stream', {
|
|
432
|
+
$exec: (stream, { rx }) => {
|
|
433
|
+
// Return an Observable of messages
|
|
434
|
+
return rx.from([
|
|
435
|
+
{ event: 'connected' },
|
|
436
|
+
{ event: 'data', value: 1 },
|
|
437
|
+
{ event: 'data', value: 2 },
|
|
438
|
+
]);
|
|
439
|
+
},
|
|
440
|
+
}),
|
|
441
|
+
]),
|
|
579
442
|
},
|
|
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
|
|
443
|
+
}));
|
|
599
444
|
```
|
|
600
445
|
|
|
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) |
|
|
446
|
+
### Request Forwarding
|
|
612
447
|
|
|
613
|
-
|
|
448
|
+
You can forward requests to an external API and manipulate the response before it's returned to the client.
|
|
614
449
|
|
|
615
|
-
|
|
616
|
-
await server.client.createExpectation({
|
|
450
|
+
```ts
|
|
451
|
+
await server.client.createExpectation(({ $ }) => ({
|
|
617
452
|
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
|
-
},
|
|
453
|
+
request: $.has('incoming.path', { $value: '/api/proxy' }),
|
|
454
|
+
// Forward the request to an external service
|
|
455
|
+
forward: {
|
|
456
|
+
url: 'https://api.external-service.com/data',
|
|
629
457
|
},
|
|
458
|
+
// Manipulate the response from the external service
|
|
459
|
+
response: $.set('outgoing.data', {
|
|
460
|
+
$exec: (data) => ({
|
|
461
|
+
...data,
|
|
462
|
+
source: 'mock-server',
|
|
463
|
+
timestamp: new Date().toISOString(),
|
|
464
|
+
}),
|
|
465
|
+
}),
|
|
630
466
|
},
|
|
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
|
|
467
|
+
}));
|
|
655
468
|
```
|
|
656
469
|
|
|
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)
|
|
470
|
+
### Using Relative Paths with `baseUrl`
|
|
660
471
|
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
**Examples**
|
|
472
|
+
When using `baseUrl`, the original request path is appended to the target URL.
|
|
664
473
|
|
|
665
474
|
```ts
|
|
666
|
-
await client.createExpectation
|
|
667
|
-
incoming: {
|
|
668
|
-
query: {
|
|
669
|
-
foo: 'a' | 'b' | 'c';
|
|
670
|
-
bar?: string;
|
|
671
|
-
};
|
|
672
|
-
};
|
|
673
|
-
}>(({ $ }) => ({
|
|
475
|
+
await server.client.createExpectation(({ $ }) => ({
|
|
674
476
|
schema: {
|
|
675
|
-
request: $.
|
|
676
|
-
$.has('incoming.
|
|
677
|
-
|
|
477
|
+
request: $.and([
|
|
478
|
+
$.has('incoming.path', { $value: '/api/v1/*' }),
|
|
479
|
+
// Rewrite the path from v1 to v2 before forwarding
|
|
480
|
+
$.set('incoming.path', {
|
|
481
|
+
$exec: (path) => path.replace('/v1/', '/v2/'),
|
|
482
|
+
}),
|
|
678
483
|
]),
|
|
484
|
+
forward: {
|
|
485
|
+
// If request is '/api/v1/users', it forwards to 'https://legacy-api.com/api/v2/users'
|
|
486
|
+
baseUrl: 'https://legacy-api.com',
|
|
487
|
+
},
|
|
488
|
+
response: $.set('outgoing.headers', '$path', 'x-proxied-by', { $value: 'mock-server' }),
|
|
679
489
|
},
|
|
680
490
|
}));
|
|
681
491
|
```
|
|
682
492
|
|
|
493
|
+
### Transforming Request Data Before Forwarding
|
|
494
|
+
|
|
495
|
+
You can also modify the request body or headers before they are sent to the target API.
|
|
496
|
+
|
|
683
497
|
```ts
|
|
684
|
-
|
|
498
|
+
interface SearchRequest {
|
|
685
499
|
incoming: {
|
|
686
|
-
query: {
|
|
687
|
-
foo: 'a' | 'b' | 'c';
|
|
688
|
-
bar?: string;
|
|
689
|
-
};
|
|
690
|
-
};
|
|
691
|
-
outgoing: {
|
|
692
500
|
data: {
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
baz: 'a' | 'b' | 'c';
|
|
696
|
-
};
|
|
501
|
+
query: string;
|
|
502
|
+
limit?: number;
|
|
697
503
|
};
|
|
698
504
|
};
|
|
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
|
-
}),
|
|
505
|
+
}
|
|
708
506
|
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
507
|
+
await server.client.createExpectation<SearchRequest>(({ $ }) => ({
|
|
508
|
+
schema: {
|
|
509
|
+
request: $.and([
|
|
510
|
+
$.has('incoming.path', { $value: '/api/search' }),
|
|
511
|
+
// Inject API key and transform data before forwarding
|
|
512
|
+
$.merge('incoming.headers', { $value: { 'X-API-Key': 'secret-token' } }),
|
|
513
|
+
$.set('incoming.data', {
|
|
514
|
+
$exec: (data) => ({
|
|
515
|
+
...data,
|
|
516
|
+
limit: data.limit ?? 20,
|
|
517
|
+
q: data.query, // Rename 'query' to 'q' for the external API
|
|
518
|
+
})
|
|
713
519
|
}),
|
|
714
520
|
]),
|
|
521
|
+
forward: {
|
|
522
|
+
url: 'https://external-search-service.com/v1/query',
|
|
523
|
+
},
|
|
715
524
|
},
|
|
716
525
|
}));
|
|
717
526
|
```
|
|
718
527
|
|
|
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)
|
|
528
|
+
### WebSocket Forwarding and Manipulation
|
|
731
529
|
|
|
732
|
-
|
|
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 |
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
**Example**
|
|
530
|
+
You can forward WebSocket connections to a real server and manipulate the message stream on the fly.
|
|
750
531
|
|
|
751
532
|
```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
|
-
}),
|
|
533
|
+
await server.client.createExpectation(({ $, utils }) => ({
|
|
534
|
+
transports: utils.transports(['ws']),
|
|
763
535
|
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
536
|
+
schema: {
|
|
537
|
+
request: $.has('incoming.path', { $value: '/ws/proxy' }),
|
|
538
|
+
forward: {
|
|
539
|
+
baseUrl: 'wss://real-server.com',
|
|
540
|
+
},
|
|
541
|
+
response: $.set('outgoing.stream', {
|
|
542
|
+
$exec: (stream, { rx }) => {
|
|
543
|
+
// 'stream' is the Observable from the real server
|
|
544
|
+
return stream?.pipe(
|
|
545
|
+
// Inject extra message at the beginning
|
|
546
|
+
rx.startWith({ event: 'proxy-init', timestamp: Date.now() }),
|
|
547
|
+
// Transform real messages
|
|
548
|
+
rx.map((message) => ({ ...message, intercepted: true }))
|
|
549
|
+
);
|
|
550
|
+
},
|
|
768
551
|
}),
|
|
769
552
|
},
|
|
770
553
|
}));
|
|
771
554
|
```
|
|
772
555
|
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
> **!NOTE** Cache is usable **only** to store a payload of forwarded requests
|
|
776
|
-
|
|
777
|
-
To work with cache the mock server uses [ioredis](https://www.npmjs.com/package/ioredis) package
|
|
556
|
+
### Conditional Caching
|
|
778
557
|
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
**Example**
|
|
558
|
+
You can control when the mock server should cache a forwarded response. For example, only cache responses with a `200 OK` status.
|
|
782
559
|
|
|
783
560
|
```ts
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
561
|
+
await server.client.createExpectation(({ $ }) => ({
|
|
562
|
+
schema: {
|
|
563
|
+
request: $.has('incoming.path', { $value: '/api/cacheable' }),
|
|
564
|
+
forward: {
|
|
565
|
+
baseUrl: 'https://api.service.com',
|
|
566
|
+
cache: {
|
|
567
|
+
ttl: 3600, // 1 hour
|
|
568
|
+
},
|
|
792
569
|
},
|
|
570
|
+
response: $.set('cache', '$path', 'isEnabled', {
|
|
571
|
+
$exec: (value, { context }) => context.outgoing.status === 200,
|
|
572
|
+
}),
|
|
793
573
|
},
|
|
794
|
-
});
|
|
574
|
+
}));
|
|
795
575
|
```
|
|
796
576
|
|
|
797
|
-
|
|
577
|
+
### Custom Cache Key
|
|
798
578
|
|
|
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**
|
|
579
|
+
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
580
|
|
|
814
581
|
```ts
|
|
815
|
-
await client.createExpectation(({ $ }) => ({
|
|
582
|
+
await server.client.createExpectation(({ $ }) => ({
|
|
816
583
|
schema: {
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
584
|
+
request: $.and([
|
|
585
|
+
$.has('incoming.path', { $value: '/api/cache-custom' }),
|
|
586
|
+
$.set('cache', '$path', 'key', {
|
|
587
|
+
// Use a specific header as the cache key
|
|
588
|
+
$exec: (key, { context }) => context.incoming.headers['x-user-id'],
|
|
589
|
+
}),
|
|
590
|
+
]),
|
|
821
591
|
forward: {
|
|
822
|
-
baseUrl: 'https://
|
|
823
|
-
|
|
592
|
+
baseUrl: 'https://api.service.com',
|
|
824
593
|
cache: {
|
|
825
|
-
|
|
594
|
+
isEnabled: true,
|
|
595
|
+
ttl: 600,
|
|
826
596
|
},
|
|
827
597
|
},
|
|
828
598
|
},
|
|
829
599
|
}));
|
|
830
|
-
```
|
|
831
|
-
|
|
832
|
-
## State
|
|
833
|
-
|
|
834
|
-
State is a unique storage of each request. It can be used to handle complex expectations
|
|
835
600
|
|
|
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
|
-
}>(({ $ }) => ({
|
|
601
|
+
// You can also provide an object as a key; it will be automatically hashed
|
|
602
|
+
await server.client.createExpectation(({ $ }) => ({
|
|
856
603
|
schema: {
|
|
857
604
|
request: $.and([
|
|
858
|
-
$.
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
605
|
+
$.has('incoming.path', { $value: '/api/cache-object' }),
|
|
606
|
+
$.set('cache', '$path', 'key', {
|
|
607
|
+
// Cache depends on specific properties of the incoming data
|
|
608
|
+
$exec: (key, { context }) => ({
|
|
609
|
+
id: context.incoming.data.id,
|
|
610
|
+
type: context.incoming.data.type,
|
|
611
|
+
}),
|
|
863
612
|
}),
|
|
864
613
|
]),
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
614
|
+
forward: {
|
|
615
|
+
baseUrl: 'https://api.service.com',
|
|
616
|
+
cache: { isEnabled: true },
|
|
617
|
+
},
|
|
868
618
|
},
|
|
869
619
|
}));
|
|
870
620
|
```
|
|
871
621
|
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
Seeds can help to generate content with the same values each request using [faker](https://www.npmjs.com/package/@faker-js/faker)
|
|
622
|
+
### Request State
|
|
875
623
|
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
**Example**
|
|
624
|
+
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
625
|
|
|
880
626
|
```ts
|
|
881
|
-
|
|
627
|
+
interface MyState {
|
|
628
|
+
state: {
|
|
629
|
+
internalId: number;
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
await server.client.createExpectation<MyState>(({ $ }) => ({
|
|
882
634
|
schema: {
|
|
883
635
|
request: $.and([
|
|
884
|
-
$.
|
|
636
|
+
$.has('incoming.path', { $value: '/api/stateful' }),
|
|
637
|
+
$.set('state', {
|
|
638
|
+
// Compute and store data in state for later use
|
|
639
|
+
$exec: (state) => ({ internalId: state.internalId ?? Math.random() }),
|
|
640
|
+
}),
|
|
885
641
|
]),
|
|
886
642
|
response: $.set('outgoing.data', {
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
last_name: faker.person.lastName('male'),
|
|
643
|
+
// Use the stored state in response building
|
|
644
|
+
$exec: (payload, { state }) => ({
|
|
645
|
+
id: state.internalId,
|
|
891
646
|
}),
|
|
892
647
|
}),
|
|
893
648
|
},
|
|
894
649
|
}));
|
|
895
650
|
```
|
|
896
651
|
|
|
897
|
-
|
|
652
|
+
### Storage and Containers
|
|
898
653
|
|
|
899
|
-
|
|
654
|
+
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
655
|
|
|
901
656
|
```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
|
-
}
|
|
657
|
+
interface CounterContainer {
|
|
658
|
+
container: {
|
|
659
|
+
count: number;
|
|
660
|
+
};
|
|
932
661
|
}
|
|
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
662
|
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
```bash
|
|
965
|
-
curl -H "Content-type: application/json" --location "localhost:8080/_system/ping"
|
|
663
|
+
await server.client.createExpectation<CounterContainer>(({ $ }) => ({
|
|
664
|
+
schema: {
|
|
665
|
+
request: $.set('container', {
|
|
666
|
+
$exec: (container, { context }) => context.storage
|
|
667
|
+
// provide() finds an existing container by key or creates a new one with the given payload
|
|
668
|
+
.provide({ key: 'my-counter', payload: { count: 0 } })
|
|
669
|
+
.assign((payload) => ({ count: payload.count + 1 })),
|
|
670
|
+
}),
|
|
671
|
+
response: $.set('outgoing.data', {
|
|
672
|
+
$exec: (payload, { context }) => ({
|
|
673
|
+
// Access the incremented value from the container
|
|
674
|
+
currentCount: context.container!.payload.count,
|
|
675
|
+
}),
|
|
676
|
+
}),
|
|
677
|
+
},
|
|
678
|
+
}));
|
|
966
679
|
```
|
|
967
680
|
|
|
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
|
-
```
|
|
681
|
+
### Cross-Expectation Sync
|
|
976
682
|
|
|
977
|
-
|
|
683
|
+
You can use Containers to synchronize data between different endpoints. For example, one endpoint increments a counter, and another retrieves its current value.
|
|
978
684
|
|
|
979
685
|
```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
|
-
}
|
|
686
|
+
interface SyncContainer {
|
|
687
|
+
container: {
|
|
688
|
+
count: number;
|
|
689
|
+
};
|
|
1016
690
|
}
|
|
1017
|
-
EOF
|
|
1018
|
-
```
|
|
1019
691
|
|
|
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({
|
|
692
|
+
// Expectation 1: Increments the counter
|
|
693
|
+
await server.client.createExpectation<SyncContainer>(({ $ }) => ({
|
|
694
|
+
name: 'Incrementer',
|
|
1027
695
|
schema: {
|
|
1028
|
-
request:
|
|
1029
|
-
$
|
|
1030
|
-
|
|
1031
|
-
$
|
|
1032
|
-
|
|
1033
|
-
|
|
696
|
+
request: $.and([
|
|
697
|
+
$.has('incoming.path', { $value: '/api/increment' }),
|
|
698
|
+
$.set('container', {
|
|
699
|
+
$exec: (container, { context }) => context.storage
|
|
700
|
+
.provide({ key: 'shared-counter', payload: { count: 0 } })
|
|
701
|
+
.assign((payload) => ({ count: payload.count + 1 })),
|
|
702
|
+
}),
|
|
703
|
+
]),
|
|
704
|
+
response: $.set('outgoing.data', { $value: { status: 'incremented' } }),
|
|
1034
705
|
},
|
|
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';
|
|
706
|
+
}));
|
|
1044
707
|
|
|
1045
|
-
|
|
1046
|
-
|
|
708
|
+
// Expectation 2: Retrieves the current value
|
|
709
|
+
await server.client.createExpectation<SyncContainer>(({ $ }) => ({
|
|
710
|
+
name: 'Getter',
|
|
1047
711
|
schema: {
|
|
1048
|
-
request:
|
|
1049
|
-
$
|
|
1050
|
-
|
|
1051
|
-
$
|
|
1052
|
-
|
|
1053
|
-
|
|
712
|
+
request: $.and([
|
|
713
|
+
$.has('incoming.path', { $value: '/api/count' }),
|
|
714
|
+
$.set('container', {
|
|
715
|
+
$exec: (container, { context }) => context.storage
|
|
716
|
+
.provide({ key: 'shared-counter', payload: { count: 0 } }),
|
|
717
|
+
}),
|
|
718
|
+
]),
|
|
719
|
+
response: $.set('outgoing.data', {
|
|
720
|
+
$exec: (payload, { context }) => ({
|
|
721
|
+
total: context.container!.payload.count,
|
|
722
|
+
}),
|
|
723
|
+
}),
|
|
1054
724
|
},
|
|
1055
|
-
});
|
|
1056
|
-
|
|
1057
|
-
console.log('Mock expectation has created', expectation.id);
|
|
725
|
+
}));
|
|
1058
726
|
```
|
|
1059
727
|
|
|
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 |
|
|
1070
|
-
|
|
1071
|
-
`OUTPUT`
|
|
728
|
+
## Extensions
|
|
1072
729
|
|
|
1073
|
-
|
|
1074
|
-
|--|--|--|--|--|
|
|
1075
|
-
| id | | `string` | | An expectation ID |
|
|
1076
|
-
| name | | `string` | | An expectation name |
|
|
1077
|
-
| schema | [Schema](#schema) | `object` | | Provided schema |
|
|
730
|
+
### Redis Configuration
|
|
1078
731
|
|
|
1079
|
-
|
|
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**
|
|
732
|
+
The mock server uses Redis for caching forwarded responses and persistent storage. You can configure the connection details during server startup.
|
|
1091
733
|
|
|
1092
734
|
```ts
|
|
1093
735
|
import { MockServer } from '@n1k1t/mock-server';
|
|
1094
736
|
|
|
1095
|
-
const server = await MockServer.start({
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
737
|
+
const server = await MockServer.start({
|
|
738
|
+
host: 'localhost',
|
|
739
|
+
port: 8080,
|
|
740
|
+
databases: {
|
|
741
|
+
redis: {
|
|
742
|
+
host: 'localhost',
|
|
743
|
+
port: 6379,
|
|
744
|
+
// Optional: password, keyPrefix, etc.
|
|
745
|
+
keyPrefix: 'my-mock-server:',
|
|
746
|
+
},
|
|
747
|
+
},
|
|
1099
748
|
});
|
|
1100
|
-
|
|
1101
|
-
console.log('Mock expectation has updated', expectation);
|
|
1102
749
|
```
|
|
1103
750
|
|
|
1104
|
-
|
|
751
|
+
### Remote Expectations with `RemoteClient`
|
|
752
|
+
|
|
753
|
+
The `RemoteClient` allows you to manage expectations on a running mock server instance from a remote application or a separate test suite.
|
|
1105
754
|
|
|
1106
755
|
```ts
|
|
1107
756
|
import { RemoteClient } from '@n1k1t/mock-server';
|
|
1108
757
|
|
|
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
|
|
758
|
+
// 1. Connect to a running mock server
|
|
759
|
+
const client = await RemoteClient.connect({ baseUrl: 'http://localhost:8080' });
|
|
1119
760
|
|
|
1120
|
-
|
|
761
|
+
// 2. Create expectations remotely
|
|
762
|
+
await client.createExpectation(({ $, utils }) => ({
|
|
763
|
+
// Specify allowed transports (http, ws)
|
|
764
|
+
transports: utils.transports(['http']),
|
|
1121
765
|
|
|
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
|
|
766
|
+
schema: {
|
|
767
|
+
request: $.has('path', { $value: '/api/remote-mock' }),
|
|
768
|
+
response: $.set('outgoing.data', { $value: { success: true, source: 'remote-client' } }),
|
|
769
|
+
},
|
|
770
|
+
}));
|
|
1134
771
|
```
|
|
1135
772
|
|
|
1136
|
-
|
|
773
|
+
### Grouping with Providers
|
|
1137
774
|
|
|
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**
|
|
775
|
+
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
776
|
|
|
1149
777
|
```ts
|
|
1150
|
-
import {
|
|
1151
|
-
|
|
1152
|
-
const client = await RemoteClient.connect({ host: 'localhost', port: 8080 });
|
|
1153
|
-
await client.deleteExpectations({
|
|
1154
|
-
ids: ['...'],
|
|
1155
|
-
});
|
|
1156
|
-
```
|
|
778
|
+
import { MockServer, Provider } from '@n1k1t/mock-server';
|
|
1157
779
|
|
|
1158
|
-
|
|
780
|
+
// 1. Define providers for different modules
|
|
781
|
+
const authProvider = Provider.build({ group: 'auth' });
|
|
782
|
+
const userProvider = Provider.build({ group: 'users' });
|
|
1159
783
|
|
|
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';
|
|
784
|
+
// 2. Setup server
|
|
785
|
+
const server = await MockServer.start({ port: 13000 });
|
|
1166
786
|
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
787
|
+
// 3. Add expectations to specific providers
|
|
788
|
+
await authProvider.client.createExpectation(({ $ }) => ({
|
|
789
|
+
schema: {
|
|
790
|
+
request: $.has('path', { $value: '/api/auth/login' }),
|
|
791
|
+
response: $.set('outgoing.data', { $value: { token: 'secret-token' } }),
|
|
1170
792
|
},
|
|
793
|
+
}));
|
|
1171
794
|
|
|
1172
|
-
|
|
1173
|
-
|
|
795
|
+
await userProvider.client.createExpectation(({ $ }) => ({
|
|
796
|
+
schema: {
|
|
797
|
+
request: $.has('path', { $value: '/api/users/me' }),
|
|
798
|
+
response: $.set('outgoing.data', { $value: { id: 1, name: 'John' } }),
|
|
1174
799
|
},
|
|
800
|
+
}));
|
|
1175
801
|
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
}
|
|
1179
|
-
});
|
|
802
|
+
// 4. Setup routing
|
|
803
|
+
server.router
|
|
804
|
+
.register('/api/auth/**', { provider: authProvider })
|
|
805
|
+
.register('/api/users/**', { provider: userProvider });
|
|
1180
806
|
```
|
|
1181
807
|
|
|
1182
|
-
|
|
808
|
+
### Logger
|
|
1183
809
|
|
|
1184
|
-
|
|
810
|
+
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
811
|
|
|
1186
812
|
```ts
|
|
1187
813
|
import { Logger } from '@n1k1t/mock-server';
|
|
1188
814
|
|
|
1189
|
-
// It defines your own logger methods
|
|
1190
815
|
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',
|
|
816
|
+
info: (title, ...messages) => {
|
|
817
|
+
// Send to your logging service
|
|
818
|
+
console.log(`[${title}]`, ...messages);
|
|
819
|
+
},
|
|
820
|
+
error: (title, ...messages) => {
|
|
821
|
+
// Handle errors specifically
|
|
822
|
+
},
|
|
1202
823
|
});
|
|
1203
824
|
```
|
|
1204
825
|
|
|
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**
|
|
826
|
+
Serializers allow you to mask sensitive data (like passwords or credit card numbers) before they are logged.
|
|
1217
827
|
|
|
1218
828
|
```ts
|
|
1219
|
-
import { Logger
|
|
1220
|
-
|
|
1221
|
-
// Some external logger with meta context support
|
|
1222
|
-
const external = {...};
|
|
829
|
+
import { Logger } from '@n1k1t/mock-server';
|
|
1223
830
|
|
|
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),
|
|
831
|
+
Logger.useSerializers({
|
|
832
|
+
password: () => '***',
|
|
833
|
+
token: (val: string) => `${val.slice(0, 4)}...`,
|
|
1231
834
|
});
|
|
1232
835
|
```
|
|
1233
836
|
|
|
1234
|
-
|
|
837
|
+
## License
|
|
1235
838
|
|
|
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
|
-
```
|
|
839
|
+
MIT
|