@noodleseed/one 0.22.0 → 0.23.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/dist/commands/author-loop.d.ts.map +1 -1
- package/dist/commands/author-loop.js +2 -2
- package/dist/commands/author-loop.js.map +1 -1
- package/dist/commands/catalog-dispatch.d.ts.map +1 -1
- package/dist/commands/catalog-dispatch.js +21 -2
- package/dist/commands/catalog-dispatch.js.map +1 -1
- package/dist/commands/deploy-ops.d.ts.map +1 -1
- package/dist/commands/deploy-ops.js +28 -11
- package/dist/commands/deploy-ops.js.map +1 -1
- package/dist/first-run.d.ts +11 -0
- package/dist/first-run.d.ts.map +1 -1
- package/dist/first-run.js +19 -4
- package/dist/first-run.js.map +1 -1
- package/dist/project-scaffold-templates.d.ts +11 -0
- package/dist/project-scaffold-templates.d.ts.map +1 -0
- package/dist/project-scaffold-templates.js +359 -0
- package/dist/project-scaffold-templates.js.map +1 -0
- package/dist/project.d.ts.map +1 -1
- package/dist/project.js +1 -323
- package/dist/project.js.map +1 -1
- package/dist/react.d.ts +2 -2
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +1 -1
- package/dist/react.js.map +1 -1
- package/dist/validate.d.ts +12 -0
- package/dist/validate.d.ts.map +1 -1
- package/dist/validate.js +11 -0
- package/dist/validate.js.map +1 -1
- package/node_modules/@noodle-borg/agent-kit/dist/skill-content.d.ts.map +1 -1
- package/node_modules/@noodle-borg/agent-kit/dist/skill-content.js +6 -9
- package/node_modules/@noodle-borg/agent-kit/dist/skill-content.js.map +1 -1
- package/node_modules/@noodle-borg/agent-kit/package.json +1 -1
- package/node_modules/@noodle-borg/authoring/dist/index.d.ts +3 -3
- package/node_modules/@noodle-borg/authoring/dist/index.d.ts.map +1 -1
- package/node_modules/@noodle-borg/authoring/dist/index.js +1 -1
- package/node_modules/@noodle-borg/authoring/dist/index.js.map +1 -1
- package/node_modules/@noodle-borg/authoring/dist/react/components.d.ts +302 -7
- package/node_modules/@noodle-borg/authoring/dist/react/components.d.ts.map +1 -1
- package/node_modules/@noodle-borg/authoring/dist/react/components.js +39 -7
- package/node_modules/@noodle-borg/authoring/dist/react/components.js.map +1 -1
- package/node_modules/@noodle-borg/authoring/dist/react/index.d.ts +4 -2
- package/node_modules/@noodle-borg/authoring/dist/react/index.d.ts.map +1 -1
- package/node_modules/@noodle-borg/authoring/dist/react/index.js +2 -1
- package/node_modules/@noodle-borg/authoring/dist/react/index.js.map +1 -1
- package/node_modules/@noodle-borg/authoring/dist/react/semantic-components.d.ts +2618 -0
- package/node_modules/@noodle-borg/authoring/dist/react/semantic-components.d.ts.map +1 -0
- package/node_modules/@noodle-borg/authoring/dist/react/semantic-components.js +110 -0
- package/node_modules/@noodle-borg/authoring/dist/react/semantic-components.js.map +1 -0
- package/node_modules/@noodle-borg/authoring/dist/react.d.ts +2 -2
- package/node_modules/@noodle-borg/authoring/dist/react.d.ts.map +1 -1
- package/node_modules/@noodle-borg/authoring/dist/react.js +1 -1
- package/node_modules/@noodle-borg/authoring/dist/react.js.map +1 -1
- package/node_modules/@noodle-borg/authoring/dist/recording.d.ts +1 -1
- package/node_modules/@noodle-borg/authoring/dist/recording.d.ts.map +1 -1
- package/node_modules/@noodle-borg/authoring/dist/recording.js +14 -1
- package/node_modules/@noodle-borg/authoring/dist/recording.js.map +1 -1
- package/node_modules/@noodle-borg/authoring/dist/server.d.ts +25 -2
- package/node_modules/@noodle-borg/authoring/dist/server.d.ts.map +1 -1
- package/node_modules/@noodle-borg/authoring/dist/server.js +7 -3
- package/node_modules/@noodle-borg/authoring/dist/server.js.map +1 -1
- package/node_modules/@noodle-borg/compiler/dist/compile.d.ts.map +1 -1
- package/node_modules/@noodle-borg/compiler/dist/compile.js +20 -8
- package/node_modules/@noodle-borg/compiler/dist/compile.js.map +1 -1
- package/node_modules/@noodle-borg/compiler/dist/manifest/schema.d.ts +2 -0
- package/node_modules/@noodle-borg/compiler/dist/manifest/schema.d.ts.map +1 -1
- package/node_modules/@noodle-borg/compiler/dist/manifest/schema.js +6 -0
- package/node_modules/@noodle-borg/compiler/dist/manifest/schema.js.map +1 -1
- package/node_modules/@noodle-borg/connector-http/package.json +1 -1
- package/node_modules/@noodle-borg/protocol/dist/mapping.d.ts.map +1 -1
- package/node_modules/@noodle-borg/protocol/dist/mapping.js +8 -0
- package/node_modules/@noodle-borg/protocol/dist/mapping.js.map +1 -1
- package/node_modules/hono/dist/adapter/aws-lambda/handler.js +15 -21
- package/node_modules/hono/dist/adapter/bun/websocket.js +3 -1
- package/node_modules/hono/dist/adapter/lambda-edge/handler.js +8 -2
- package/node_modules/hono/dist/cjs/adapter/aws-lambda/handler.js +15 -21
- package/node_modules/hono/dist/cjs/adapter/bun/websocket.js +3 -1
- package/node_modules/hono/dist/cjs/adapter/lambda-edge/handler.js +8 -2
- package/node_modules/hono/dist/cjs/helper/css/common.js +3 -1
- package/node_modules/hono/dist/cjs/helper/css/index.js +9 -1
- package/node_modules/hono/dist/cjs/jsx/base.js +8 -14
- package/node_modules/hono/dist/cjs/jsx/components.js +41 -21
- package/node_modules/hono/dist/cjs/jsx/context.js +131 -5
- package/node_modules/hono/dist/cjs/jsx/streaming.js +9 -7
- package/node_modules/hono/dist/cjs/middleware/cors/index.js +2 -5
- package/node_modules/hono/dist/cjs/middleware/serve-static/index.js +2 -2
- package/node_modules/hono/dist/cjs/utils/body.js +12 -4
- package/node_modules/hono/dist/cjs/utils/buffer.js +2 -1
- package/node_modules/hono/dist/cjs/validator/validator.js +3 -3
- package/node_modules/hono/dist/helper/css/common.js +3 -1
- package/node_modules/hono/dist/helper/css/index.js +9 -1
- package/node_modules/hono/dist/jsx/base.js +15 -15
- package/node_modules/hono/dist/jsx/components.js +42 -22
- package/node_modules/hono/dist/jsx/context.js +129 -5
- package/node_modules/hono/dist/jsx/streaming.js +10 -8
- package/node_modules/hono/dist/middleware/cors/index.js +2 -5
- package/node_modules/hono/dist/middleware/serve-static/index.js +2 -2
- package/node_modules/hono/dist/types/adapter/aws-lambda/handler.d.ts +1 -1
- package/node_modules/hono/dist/types/jsx/base.d.ts +1 -3
- package/node_modules/hono/dist/types/jsx/context.d.ts +39 -0
- package/node_modules/hono/dist/types/middleware/context-storage/index.d.ts +2 -2
- package/node_modules/hono/dist/types/utils/body.d.ts +1 -1
- package/node_modules/hono/dist/utils/body.js +12 -4
- package/node_modules/hono/dist/utils/buffer.js +2 -1
- package/node_modules/hono/dist/validator/validator.js +3 -3
- package/node_modules/hono/package.json +9 -10
- package/node_modules/tailwindcss/dist/chunk-HMCCH6MG.mjs +1 -0
- package/node_modules/tailwindcss/dist/default-theme.mjs +1 -1
- package/node_modules/tailwindcss/dist/lib.js +13 -13
- package/node_modules/tailwindcss/dist/lib.mjs +18 -18
- package/node_modules/tailwindcss/package.json +2 -2
- package/node_modules/undici/README.md +3 -2
- package/node_modules/undici/docs/docs/api/Agent.md +248 -44
- package/node_modules/undici/docs/docs/api/BalancedPool.md +246 -48
- package/node_modules/undici/docs/docs/api/CacheStorage.md +171 -13
- package/node_modules/undici/docs/docs/api/CacheStore.md +294 -98
- package/node_modules/undici/docs/docs/api/Client.md +365 -178
- package/node_modules/undici/docs/docs/api/ClientStats.md +80 -14
- package/node_modules/undici/docs/docs/api/Connector.md +118 -36
- package/node_modules/undici/docs/docs/api/ContentType.md +65 -25
- package/node_modules/undici/docs/docs/api/Cookies.md +125 -47
- package/node_modules/undici/docs/docs/api/Debug.md +34 -18
- package/node_modules/undici/docs/docs/api/DiagnosticsChannel.md +364 -164
- package/node_modules/undici/docs/docs/api/Dispatcher.md +479 -1093
- package/node_modules/undici/docs/docs/api/EnvHttpProxyAgent.md +101 -87
- package/node_modules/undici/docs/docs/api/Errors.md +503 -40
- package/node_modules/undici/docs/docs/api/EventSource.md +294 -32
- package/node_modules/undici/docs/docs/api/Fetch.md +680 -31
- package/node_modules/undici/docs/docs/api/GlobalInstallation.md +78 -98
- package/node_modules/undici/docs/docs/api/H2CClient.md +298 -176
- package/node_modules/undici/docs/docs/api/Interceptors.md +355 -0
- package/node_modules/undici/docs/docs/api/MockAgent.md +314 -322
- package/node_modules/undici/docs/docs/api/MockCallHistory.md +272 -98
- package/node_modules/undici/docs/docs/api/MockCallHistoryLog.md +189 -26
- package/node_modules/undici/docs/docs/api/MockClient.md +151 -33
- package/node_modules/undici/docs/docs/api/MockErrors.md +56 -5
- package/node_modules/undici/docs/docs/api/MockPool.md +299 -263
- package/node_modules/undici/docs/docs/api/Pool.md +235 -43
- package/node_modules/undici/docs/docs/api/PoolStats.md +119 -13
- package/node_modules/undici/docs/docs/api/ProxyAgent.md +179 -160
- package/node_modules/undici/docs/docs/api/RedirectHandler.md +238 -63
- package/node_modules/undici/docs/docs/api/RetryAgent.md +113 -26
- package/node_modules/undici/docs/docs/api/RetryHandler.md +161 -77
- package/node_modules/undici/docs/docs/api/RoundRobinPool.md +166 -72
- package/node_modules/undici/docs/docs/api/SnapshotAgent.md +264 -542
- package/node_modules/undici/docs/docs/api/Socks5ProxyAgent.md +162 -186
- package/node_modules/undici/docs/docs/api/Util.md +68 -11
- package/node_modules/undici/docs/docs/api/WebSocket.md +475 -80
- package/node_modules/undici/docs/docs/api/api-lifecycle.md +100 -32
- package/node_modules/undici/docs/docs/best-practices/client-certificate.md +2 -0
- package/node_modules/undici/docs/docs/best-practices/crawling.md +3 -1
- package/node_modules/undici/docs/docs/best-practices/migrating-from-v7-to-v8.md +6 -4
- package/node_modules/undici/docs/docs/best-practices/mocking-request.md +9 -7
- package/node_modules/undici/docs/docs/best-practices/proxy.md +3 -1
- package/node_modules/undici/docs/docs/best-practices/undici-vs-builtin-fetch.md +10 -8
- package/node_modules/undici/docs/docs/best-practices/writing-tests.md +2 -0
- package/node_modules/undici/docs/docs/{GettingStarted.md → getting-started.md} +22 -19
- package/node_modules/undici/docs/docs/index.md +779 -0
- package/node_modules/undici/docs/docs/site.json +125 -0
- package/node_modules/undici/docs/docs/type-map.json +79 -0
- package/node_modules/undici/lib/api/api-request.js +7 -1
- package/node_modules/undici/lib/api/readable.js +47 -2
- package/node_modules/undici/lib/core/errors.js +20 -0
- package/node_modules/undici/lib/core/request.js +1 -1
- package/node_modules/undici/lib/core/util.js +24 -1
- package/node_modules/undici/lib/dispatcher/client-h1.js +92 -1
- package/node_modules/undici/lib/dispatcher/client-h2.js +322 -118
- package/node_modules/undici/lib/dispatcher/client.js +6 -2
- package/node_modules/undici/lib/dispatcher/dispatcher-base.js +1 -0
- package/node_modules/undici/lib/dispatcher/proxy-agent.js +42 -7
- package/node_modules/undici/lib/dispatcher/socks5-proxy-agent.js +4 -2
- package/node_modules/undici/lib/handler/redirect-handler.js +1 -0
- package/node_modules/undici/lib/handler/retry-handler.js +57 -19
- package/node_modules/undici/lib/util/cache.js +8 -2
- package/node_modules/undici/lib/web/cookies/parse.js +20 -27
- package/node_modules/undici/lib/web/cookies/util.js +1 -1
- package/node_modules/undici/lib/web/eventsource/eventsource.js +7 -18
- package/node_modules/undici/lib/web/eventsource/util.js +32 -1
- package/node_modules/undici/lib/web/fetch/body.js +43 -0
- package/node_modules/undici/lib/web/fetch/constants.js +1 -1
- package/node_modules/undici/lib/web/fetch/index.js +11 -2
- package/node_modules/undici/lib/web/fetch/request.js +1 -0
- package/node_modules/undici/lib/web/fetch/util.js +4 -1
- package/node_modules/undici/lib/web/websocket/receiver.js +20 -3
- package/node_modules/undici/lib/web/websocket/stream/websocketstream.js +8 -1
- package/node_modules/undici/lib/web/websocket/websocket.js +3 -1
- package/node_modules/undici/package.json +1 -1
- package/node_modules/undici/types/client.d.ts +6 -1
- package/node_modules/undici/types/cookies.d.ts +1 -1
- package/node_modules/undici/types/errors.d.ts +10 -0
- package/node_modules/undici/types/fetch.d.ts +1 -0
- package/node_modules/undici/types/handlers.d.ts +2 -0
- package/node_modules/undici/types/proxy-agent.d.ts +7 -0
- package/package.json +4 -4
- package/react/styles.css +510 -5
- package/react/styles.source.css +850 -0
- package/node_modules/hono/dist/tsconfig.build.tsbuildinfo +0 -1
- package/node_modules/tailwindcss/dist/chunk-XVOTX5KS.mjs +0 -1
|
@@ -1,639 +1,361 @@
|
|
|
1
1
|
# SnapshotAgent
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<!--introduced_in=v7.13.0-->
|
|
4
|
+
<!--type=module-->
|
|
5
|
+
<!-- source_link=lib/mock/snapshot-agent.js -->
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
> Stability: 1 - Experimental
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
`SnapshotAgent` records real HTTP responses and replays them on later requests,
|
|
10
|
+
allowing tests to run against deterministic, captured data instead of a live
|
|
11
|
+
network. It extends [`MockAgent`][], so it can be installed with
|
|
12
|
+
[`setGlobalDispatcher()`][] or passed explicitly through the `dispatcher`
|
|
13
|
+
option, and it works with every undici API (`fetch`, `request`, `stream`,
|
|
14
|
+
`pipeline`, and so on).
|
|
11
15
|
|
|
12
|
-
|
|
16
|
+
A `SnapshotAgent` operates in one of three modes. In `'record'` mode it performs
|
|
17
|
+
real requests and writes the responses to a snapshot file. In `'playback'` mode
|
|
18
|
+
it serves responses from the snapshot file without touching the network. In
|
|
19
|
+
`'update'` mode it replays existing snapshots and records any request that has no
|
|
20
|
+
matching snapshot.
|
|
13
21
|
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
### Parameters
|
|
19
|
-
|
|
20
|
-
- **options** `Object` (optional)
|
|
21
|
-
- **mode** `String` - The snapshot mode: `'record'`, `'playback'`, or `'update'`. Default: `'record'`
|
|
22
|
-
- **snapshotPath** `String` - Path to the snapshot file for loading/saving
|
|
23
|
-
- **maxSnapshots** `Number` - Maximum number of snapshots to keep in memory. Default: `Infinity`
|
|
24
|
-
- **autoFlush** `Boolean` - Whether to automatically save snapshots to disk. Default: `false`
|
|
25
|
-
- **flushInterval** `Number` - Interval in milliseconds for auto-flush. Default: `30000`
|
|
26
|
-
- **matchHeaders** `Array<String>` - Specific headers to include in request matching. Default: all headers
|
|
27
|
-
- **ignoreHeaders** `Array<String>` - Headers to ignore during request matching
|
|
28
|
-
- **excludeHeaders** `Array<String>` - Headers to exclude from snapshots (for security)
|
|
29
|
-
- **matchBody** `Boolean` - Whether to include request body in matching. Default: `true`
|
|
30
|
-
- **normalizeBody** `Function` - Optional function `(body) => string` to normalize the request body before matching (e.g. strip volatile fields like timestamps). Only used when `matchBody` is `true`.
|
|
31
|
-
- **matchQuery** `Boolean` - Whether to include query parameters in matching. Default: `true`
|
|
32
|
-
- **normalizeQuery** `Function` - Optional function `(query: URLSearchParams) => string` to normalize query parameters before matching (e.g. strip volatile params like cache-busters). Only used when `matchQuery` is `true`.
|
|
33
|
-
- **caseSensitive** `Boolean` - Whether header matching is case-sensitive. Default: `false`
|
|
34
|
-
- **shouldRecord** `Function` - Callback to determine if a request should be recorded
|
|
35
|
-
- **shouldPlayback** `Function` - Callback to determine if a request should be played back
|
|
36
|
-
- **excludeUrls** `Array` - URL patterns (strings or RegExp) to exclude from recording/playback
|
|
37
|
-
- All other options from `MockAgent` are supported
|
|
38
|
-
|
|
39
|
-
### Modes
|
|
40
|
-
|
|
41
|
-
#### Record Mode (`'record'`)
|
|
42
|
-
Makes real HTTP requests and saves the responses to snapshots.
|
|
43
|
-
|
|
44
|
-
```javascript
|
|
45
|
-
import { SnapshotAgent, setGlobalDispatcher } from 'undici'
|
|
22
|
+
```mjs
|
|
23
|
+
import { SnapshotAgent, setGlobalDispatcher, fetch } from 'undici'
|
|
46
24
|
|
|
47
|
-
const agent = new SnapshotAgent({
|
|
25
|
+
const agent = new SnapshotAgent({
|
|
48
26
|
mode: 'record',
|
|
49
|
-
snapshotPath: './
|
|
27
|
+
snapshotPath: './snapshots/api.json'
|
|
50
28
|
})
|
|
51
29
|
setGlobalDispatcher(agent)
|
|
52
30
|
|
|
53
|
-
// Makes real requests and records them
|
|
54
31
|
const response = await fetch('https://api.example.com/users')
|
|
55
32
|
const users = await response.json()
|
|
56
33
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
34
|
+
await agent.close()
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Constructing a `SnapshotAgent` emits an `ExperimentalWarning` once per process.
|
|
38
|
+
|
|
39
|
+
## Class: `SnapshotAgent`
|
|
40
|
+
|
|
41
|
+
<!-- YAML
|
|
42
|
+
added: v7.13.0
|
|
43
|
+
-->
|
|
44
|
+
|
|
45
|
+
* Extends: {MockAgent}
|
|
46
|
+
|
|
47
|
+
The captured interactions are managed by an internal `SnapshotRecorder`, which
|
|
48
|
+
is accessible through [`agent.getRecorder()`][]. Because `SnapshotAgent` extends
|
|
49
|
+
[`MockAgent`][], all `MockAgent` and [`Dispatcher`][] methods and events are also
|
|
50
|
+
available.
|
|
51
|
+
|
|
52
|
+
### `new SnapshotAgent([options])`
|
|
53
|
+
|
|
54
|
+
<!-- YAML
|
|
55
|
+
added: v7.13.0
|
|
56
|
+
-->
|
|
57
|
+
|
|
58
|
+
* `options` {Object} (optional) Accepts all [`MockAgent`][] options plus the
|
|
59
|
+
following snapshot-specific options.
|
|
60
|
+
* `mode` {string} The snapshot mode. One of `'record'`, `'playback'`, or
|
|
61
|
+
`'update'`. **Default:** `'record'`.
|
|
62
|
+
* `snapshotPath` {string} Path to the snapshot file used for loading and
|
|
63
|
+
saving. Required when `mode` is `'playback'` or `'update'`. **Default:**
|
|
64
|
+
`null`.
|
|
65
|
+
* `maxSnapshots` {number} Maximum number of snapshots to keep in memory.
|
|
66
|
+
**Default:** `Infinity`.
|
|
67
|
+
* `autoFlush` {boolean} When `true`, snapshots are written to `snapshotPath`
|
|
68
|
+
automatically as they are recorded. **Default:** `false`.
|
|
69
|
+
* `flushInterval` {number} Interval, in milliseconds, between automatic flushes
|
|
70
|
+
when `autoFlush` is enabled. **Default:** `30000`.
|
|
71
|
+
* `matchHeaders` {string[]} Names of the only headers to include when matching
|
|
72
|
+
a request against a snapshot. When omitted, all headers are matched.
|
|
73
|
+
* `ignoreHeaders` {string[]} Header names to ignore when matching a request.
|
|
74
|
+
* `excludeHeaders` {string[]} Header names to strip from snapshots entirely,
|
|
75
|
+
so that sensitive values are never written to disk.
|
|
76
|
+
* `matchBody` {boolean} When `true`, the request body is included in request
|
|
77
|
+
matching. **Default:** `true`.
|
|
78
|
+
* `normalizeBody` {Function} Normalizes the request body before it is matched
|
|
79
|
+
or stored, for example to strip volatile fields such as timestamps. Only
|
|
80
|
+
applied when `matchBody` is `true`.
|
|
81
|
+
* `body` {string|Buffer|null|undefined} The raw request body.
|
|
82
|
+
* Returns: {string} The normalized body used for matching.
|
|
83
|
+
* `matchQuery` {boolean} When `true`, query parameters are included in request
|
|
84
|
+
matching. **Default:** `true`.
|
|
85
|
+
* `normalizeQuery` {Function} Normalizes the query string before it is matched,
|
|
86
|
+
for example to strip cache-busting parameters. Only applied when `matchQuery`
|
|
87
|
+
is `true`.
|
|
88
|
+
* `query` {URLSearchParams} The parsed query parameters.
|
|
89
|
+
* Returns: {string} The normalized query string used for matching.
|
|
90
|
+
* `caseSensitive` {boolean} When `true`, header matching is case-sensitive.
|
|
91
|
+
**Default:** `false`.
|
|
92
|
+
* `shouldRecord` {Function} Decides whether a given request is recorded. Only
|
|
93
|
+
used in `'record'` and `'update'` modes.
|
|
94
|
+
* `requestOpts` {Object} The dispatch options of the request.
|
|
95
|
+
* Returns: {boolean} `true` to record the request.
|
|
96
|
+
* `shouldPlayback` {Function} Decides whether a given request is served from a
|
|
97
|
+
snapshot. Only used in `'playback'` and `'update'` modes.
|
|
98
|
+
* `requestOpts` {Object} The dispatch options of the request.
|
|
99
|
+
* Returns: {boolean} `true` to play the request back from a snapshot.
|
|
100
|
+
* `excludeUrls` {Array<string|RegExp>} URL patterns that are excluded from
|
|
101
|
+
recording and playback. String patterns match case-insensitively as
|
|
102
|
+
substrings of the URL; `RegExp` patterns are tested against the URL. Matching
|
|
103
|
+
requests are passed through to a real [`Agent`][] instead.
|
|
104
|
+
|
|
105
|
+
Creates a new `SnapshotAgent`. Throws an [`InvalidArgumentError`][] when `mode`
|
|
106
|
+
is `'playback'` or `'update'` and `snapshotPath` is not provided, and throws an
|
|
107
|
+
`InvalidArgumentError` when `mode` is not one of the supported values.
|
|
108
|
+
|
|
109
|
+
When `mode` is `'playback'` or `'update'` and `snapshotPath` is set, snapshots
|
|
110
|
+
are loaded from the file automatically; a missing file is ignored until the first
|
|
111
|
+
request requires it.
|
|
112
|
+
|
|
113
|
+
```mjs
|
|
65
114
|
import { SnapshotAgent, setGlobalDispatcher } from 'undici'
|
|
66
115
|
|
|
67
116
|
const agent = new SnapshotAgent({
|
|
68
117
|
mode: 'playback',
|
|
69
|
-
snapshotPath: './
|
|
70
|
-
})
|
|
71
|
-
setGlobalDispatcher(agent)
|
|
72
|
-
|
|
73
|
-
// Uses recorded response instead of real request
|
|
74
|
-
const response = await fetch('https://api.example.com/users')
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
#### Update Mode (`'update'`)
|
|
78
|
-
Uses existing snapshots when available, but records new ones for missing requests.
|
|
79
|
-
|
|
80
|
-
```javascript
|
|
81
|
-
import { SnapshotAgent, setGlobalDispatcher } from 'undici'
|
|
82
|
-
|
|
83
|
-
const agent = new SnapshotAgent({
|
|
84
|
-
mode: 'update',
|
|
85
|
-
snapshotPath: './test/snapshots/api-calls.json'
|
|
118
|
+
snapshotPath: './snapshots/api.json'
|
|
86
119
|
})
|
|
87
120
|
setGlobalDispatcher(agent)
|
|
88
|
-
|
|
89
|
-
// Uses snapshot if exists, otherwise makes real request and records it
|
|
90
|
-
const response = await fetch('https://api.example.com/new-endpoint')
|
|
91
121
|
```
|
|
92
122
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
### `agent.saveSnapshots([filePath])`
|
|
96
|
-
|
|
97
|
-
Saves all recorded snapshots to a file.
|
|
98
|
-
|
|
99
|
-
#### Parameters
|
|
100
|
-
|
|
101
|
-
- **filePath** `String` (optional) - Path to save snapshots. Uses constructor `snapshotPath` if not provided.
|
|
102
|
-
|
|
103
|
-
#### Returns
|
|
104
|
-
|
|
105
|
-
`Promise<void>`
|
|
106
|
-
|
|
107
|
-
```javascript
|
|
108
|
-
await agent.saveSnapshots('./custom-snapshots.json')
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
## Advanced Configuration
|
|
112
|
-
|
|
113
|
-
### Body Matching
|
|
114
|
-
|
|
115
|
-
By default (`matchBody: true`) the full request body string is included in the snapshot key. Set it to `false` to ignore the body entirely, or use `normalizeBody` to strip volatile fields (like timestamps) before matching:
|
|
116
|
-
|
|
117
|
-
```javascript
|
|
118
|
-
const agent = new SnapshotAgent({
|
|
119
|
-
mode: 'playback',
|
|
120
|
-
snapshotPath: './snapshots.json',
|
|
121
|
-
|
|
122
|
-
// Match on everything except the timestamp field
|
|
123
|
-
normalizeBody: (body) => {
|
|
124
|
-
if (!body) return ''
|
|
125
|
-
const parsed = JSON.parse(String(body))
|
|
126
|
-
delete parsed.timestamp
|
|
127
|
-
return JSON.stringify(parsed)
|
|
128
|
-
}
|
|
129
|
-
})
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
`normalizeBody` receives the raw body (`string | Buffer | null | undefined`) and must return a `string`. It runs at both record and playback time so the hash is consistent. Two requests match the same snapshot whenever their normalized strings are identical.
|
|
133
|
-
|
|
134
|
-
### Header Filtering
|
|
135
|
-
|
|
136
|
-
Control which headers are used for request matching and what gets stored in snapshots:
|
|
137
|
-
|
|
138
|
-
```javascript
|
|
139
|
-
const agent = new SnapshotAgent({
|
|
140
|
-
mode: 'record',
|
|
141
|
-
snapshotPath: './snapshots.json',
|
|
142
|
-
|
|
143
|
-
// Only match these specific headers
|
|
144
|
-
matchHeaders: ['content-type', 'accept'],
|
|
145
|
-
|
|
146
|
-
// Ignore these headers during matching (but still store them)
|
|
147
|
-
ignoreHeaders: ['user-agent', 'date'],
|
|
148
|
-
|
|
149
|
-
// Exclude sensitive headers from snapshots entirely
|
|
150
|
-
excludeHeaders: ['authorization', 'x-api-key', 'cookie']
|
|
151
|
-
})
|
|
152
|
-
```
|
|
123
|
+
The mode controls how requests are handled:
|
|
153
124
|
|
|
154
|
-
|
|
125
|
+
* `'record'` performs the real request and stores the response.
|
|
155
126
|
|
|
156
|
-
|
|
127
|
+
```mjs
|
|
128
|
+
import { SnapshotAgent, setGlobalDispatcher, fetch } from 'undici'
|
|
157
129
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
// Only record GET requests to specific endpoints
|
|
164
|
-
shouldRecord: (requestOpts) => {
|
|
165
|
-
const url = new URL(requestOpts.path, requestOpts.origin)
|
|
166
|
-
return requestOpts.method === 'GET' && url.pathname.startsWith('/api/v1/')
|
|
167
|
-
},
|
|
168
|
-
|
|
169
|
-
// Skip authentication endpoints during playback
|
|
170
|
-
shouldPlayback: (requestOpts) => {
|
|
171
|
-
const url = new URL(requestOpts.path, requestOpts.origin)
|
|
172
|
-
return !url.pathname.includes('/auth/')
|
|
173
|
-
}
|
|
174
|
-
})
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
### URL Pattern Exclusion
|
|
178
|
-
|
|
179
|
-
Exclude specific URLs from recording/playback using patterns:
|
|
180
|
-
|
|
181
|
-
```javascript
|
|
182
|
-
const agent = new SnapshotAgent({
|
|
183
|
-
mode: 'record',
|
|
184
|
-
snapshotPath: './snapshots.json',
|
|
185
|
-
|
|
186
|
-
excludeUrls: [
|
|
187
|
-
'https://analytics.example.com', // String match
|
|
188
|
-
/\/api\/v\d+\/health/, // Regex pattern
|
|
189
|
-
'telemetry' // Substring match
|
|
190
|
-
]
|
|
191
|
-
})
|
|
192
|
-
```
|
|
193
|
-
|
|
194
|
-
### Memory Management
|
|
195
|
-
|
|
196
|
-
Configure automatic memory and disk management:
|
|
197
|
-
|
|
198
|
-
```javascript
|
|
199
|
-
const agent = new SnapshotAgent({
|
|
200
|
-
mode: 'record',
|
|
201
|
-
snapshotPath: './snapshots.json',
|
|
202
|
-
|
|
203
|
-
// Keep only 1000 snapshots in memory
|
|
204
|
-
maxSnapshots: 1000,
|
|
205
|
-
|
|
206
|
-
// Automatically save to disk every 30 seconds
|
|
207
|
-
autoFlush: true,
|
|
208
|
-
flushInterval: 30000
|
|
209
|
-
})
|
|
210
|
-
```
|
|
211
|
-
|
|
212
|
-
### Sequential Response Handling
|
|
213
|
-
|
|
214
|
-
Handle multiple responses for the same request (similar to nock):
|
|
215
|
-
|
|
216
|
-
```javascript
|
|
217
|
-
// In record mode, multiple identical requests get recorded as separate responses
|
|
218
|
-
const agent = new SnapshotAgent({ mode: 'record', snapshotPath: './sequential.json' })
|
|
130
|
+
const agent = new SnapshotAgent({
|
|
131
|
+
mode: 'record',
|
|
132
|
+
snapshotPath: './snapshots/api.json'
|
|
133
|
+
})
|
|
134
|
+
setGlobalDispatcher(agent)
|
|
219
135
|
|
|
220
|
-
|
|
221
|
-
await
|
|
136
|
+
await fetch('https://api.example.com/users')
|
|
137
|
+
await agent.saveSnapshots()
|
|
138
|
+
```
|
|
222
139
|
|
|
223
|
-
|
|
224
|
-
|
|
140
|
+
* `'playback'` serves the recorded response without any network access. A
|
|
141
|
+
request with no matching snapshot rejects with an [`UndiciError`][] whose
|
|
142
|
+
message begins with `No snapshot found`.
|
|
225
143
|
|
|
226
|
-
|
|
144
|
+
```mjs
|
|
145
|
+
import { SnapshotAgent, setGlobalDispatcher, fetch } from 'undici'
|
|
227
146
|
|
|
228
|
-
|
|
229
|
-
|
|
147
|
+
const agent = new SnapshotAgent({
|
|
148
|
+
mode: 'playback',
|
|
149
|
+
snapshotPath: './snapshots/api.json'
|
|
150
|
+
})
|
|
151
|
+
setGlobalDispatcher(agent)
|
|
230
152
|
|
|
231
|
-
|
|
232
|
-
|
|
153
|
+
const response = await fetch('https://api.example.com/users')
|
|
154
|
+
```
|
|
233
155
|
|
|
234
|
-
|
|
235
|
-
|
|
156
|
+
* `'update'` replays an existing snapshot when one is found and otherwise records
|
|
157
|
+
the request like `'record'` mode.
|
|
236
158
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
```
|
|
159
|
+
```mjs
|
|
160
|
+
import { SnapshotAgent, setGlobalDispatcher, fetch } from 'undici'
|
|
240
161
|
|
|
241
|
-
|
|
162
|
+
const agent = new SnapshotAgent({
|
|
163
|
+
mode: 'update',
|
|
164
|
+
snapshotPath: './snapshots/api.json'
|
|
165
|
+
})
|
|
166
|
+
setGlobalDispatcher(agent)
|
|
242
167
|
|
|
243
|
-
|
|
168
|
+
await fetch('https://api.example.com/new-endpoint')
|
|
169
|
+
```
|
|
244
170
|
|
|
245
|
-
|
|
246
|
-
// Load existing snapshots
|
|
247
|
-
await agent.loadSnapshots('./old-snapshots.json')
|
|
171
|
+
### `agent.loadSnapshots([filePath])`
|
|
248
172
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
173
|
+
<!-- YAML
|
|
174
|
+
added: v7.13.0
|
|
175
|
+
-->
|
|
252
176
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
)
|
|
177
|
+
* `filePath` {string} (optional) Path to read snapshots from. **Default:** the
|
|
178
|
+
`snapshotPath` given to the constructor.
|
|
179
|
+
* Returns: {Promise<void>} Resolves when the snapshots have been loaded.
|
|
257
180
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
snapshot
|
|
262
|
-
})))
|
|
181
|
+
Loads snapshots from disk into memory. In `'playback'` mode this also installs
|
|
182
|
+
the corresponding [`MockAgent`][] interceptors so that subsequent requests are
|
|
183
|
+
matched against the loaded snapshots.
|
|
263
184
|
|
|
264
|
-
|
|
265
|
-
await agent.
|
|
185
|
+
```mjs
|
|
186
|
+
await agent.loadSnapshots('./snapshots/api.json')
|
|
266
187
|
```
|
|
267
188
|
|
|
268
|
-
### `agent.
|
|
269
|
-
|
|
270
|
-
Loads snapshots from a file.
|
|
271
|
-
|
|
272
|
-
#### Parameters
|
|
189
|
+
### `agent.saveSnapshots([filePath])`
|
|
273
190
|
|
|
274
|
-
|
|
191
|
+
<!-- YAML
|
|
192
|
+
added: v7.13.0
|
|
193
|
+
-->
|
|
275
194
|
|
|
276
|
-
|
|
195
|
+
* `filePath` {string} (optional) Path to write snapshots to. **Default:** the
|
|
196
|
+
`snapshotPath` given to the constructor.
|
|
197
|
+
* Returns: {Promise<void>} Resolves when the snapshots have been written.
|
|
277
198
|
|
|
278
|
-
|
|
199
|
+
Writes all recorded snapshots to disk.
|
|
279
200
|
|
|
280
|
-
```
|
|
281
|
-
await agent.
|
|
201
|
+
```mjs
|
|
202
|
+
await agent.saveSnapshots('./snapshots/api.json')
|
|
282
203
|
```
|
|
283
204
|
|
|
284
205
|
### `agent.getRecorder()`
|
|
285
206
|
|
|
286
|
-
|
|
207
|
+
<!-- YAML
|
|
208
|
+
added: v7.13.0
|
|
209
|
+
-->
|
|
287
210
|
|
|
288
|
-
|
|
211
|
+
* Returns: {SnapshotRecorder} The internal recorder that stores the captured
|
|
212
|
+
interactions.
|
|
289
213
|
|
|
290
|
-
`SnapshotRecorder
|
|
214
|
+
Returns the underlying `SnapshotRecorder`. The recorder exposes lower-level
|
|
215
|
+
operations over the captured data, including `record()`, `findSnapshot()`,
|
|
216
|
+
`getSnapshots()`, `size()`, and `clear()`. The recorder is an internal type
|
|
217
|
+
returned for inspection; it is not exported from the package root.
|
|
291
218
|
|
|
292
|
-
```
|
|
219
|
+
```mjs
|
|
293
220
|
const recorder = agent.getRecorder()
|
|
294
221
|
console.log(`Recorded ${recorder.size()} interactions`)
|
|
295
222
|
```
|
|
296
223
|
|
|
297
224
|
### `agent.getMode()`
|
|
298
225
|
|
|
299
|
-
|
|
226
|
+
<!-- YAML
|
|
227
|
+
added: v7.13.0
|
|
228
|
+
-->
|
|
300
229
|
|
|
301
|
-
|
|
230
|
+
* Returns: {string} The current mode: `'record'`, `'playback'`, or `'update'`.
|
|
302
231
|
|
|
303
|
-
|
|
232
|
+
Returns the mode the agent was constructed with.
|
|
304
233
|
|
|
305
234
|
### `agent.clearSnapshots()`
|
|
306
235
|
|
|
307
|
-
|
|
236
|
+
<!-- YAML
|
|
237
|
+
added: v7.13.0
|
|
238
|
+
-->
|
|
308
239
|
|
|
309
|
-
|
|
310
|
-
agent.clearSnapshots()
|
|
311
|
-
```
|
|
312
|
-
|
|
313
|
-
## Working with Different Request Types
|
|
314
|
-
|
|
315
|
-
### GET Requests
|
|
316
|
-
|
|
317
|
-
```javascript
|
|
318
|
-
// Record mode
|
|
319
|
-
const agent = new SnapshotAgent({ mode: 'record', snapshotPath: './get-snapshots.json' })
|
|
320
|
-
setGlobalDispatcher(agent)
|
|
321
|
-
|
|
322
|
-
const response = await fetch('https://jsonplaceholder.typicode.com/posts/1')
|
|
323
|
-
const post = await response.json()
|
|
324
|
-
|
|
325
|
-
await agent.saveSnapshots()
|
|
326
|
-
```
|
|
327
|
-
|
|
328
|
-
### POST Requests with Body
|
|
329
|
-
|
|
330
|
-
```javascript
|
|
331
|
-
// Record mode
|
|
332
|
-
const agent = new SnapshotAgent({ mode: 'record', snapshotPath: './post-snapshots.json' })
|
|
333
|
-
setGlobalDispatcher(agent)
|
|
334
|
-
|
|
335
|
-
const response = await fetch('https://jsonplaceholder.typicode.com/posts', {
|
|
336
|
-
method: 'POST',
|
|
337
|
-
headers: { 'Content-Type': 'application/json' },
|
|
338
|
-
body: JSON.stringify({ title: 'Test Post', body: 'Content' })
|
|
339
|
-
})
|
|
340
|
-
|
|
341
|
-
await agent.saveSnapshots()
|
|
342
|
-
```
|
|
343
|
-
|
|
344
|
-
### Using with `undici.request`
|
|
345
|
-
|
|
346
|
-
SnapshotAgent works with all undici APIs, not just fetch:
|
|
347
|
-
|
|
348
|
-
```javascript
|
|
349
|
-
import { SnapshotAgent, request, setGlobalDispatcher } from 'undici'
|
|
350
|
-
|
|
351
|
-
const agent = new SnapshotAgent({ mode: 'record', snapshotPath: './request-snapshots.json' })
|
|
352
|
-
setGlobalDispatcher(agent)
|
|
240
|
+
* Returns: {undefined}
|
|
353
241
|
|
|
354
|
-
|
|
355
|
-
const data = await body.json()
|
|
242
|
+
Removes all snapshots from memory.
|
|
356
243
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
## Test Integration
|
|
361
|
-
|
|
362
|
-
### Basic Test Setup
|
|
363
|
-
|
|
364
|
-
```javascript
|
|
365
|
-
import { test } from 'node:test'
|
|
366
|
-
import { SnapshotAgent, setGlobalDispatcher, getGlobalDispatcher } from 'undici'
|
|
367
|
-
|
|
368
|
-
test('API integration test', async (t) => {
|
|
369
|
-
const originalDispatcher = getGlobalDispatcher()
|
|
370
|
-
|
|
371
|
-
const agent = new SnapshotAgent({
|
|
372
|
-
mode: 'playback',
|
|
373
|
-
snapshotPath: './test/snapshots/api-test.json'
|
|
374
|
-
})
|
|
375
|
-
setGlobalDispatcher(agent)
|
|
376
|
-
|
|
377
|
-
t.after(() => setGlobalDispatcher(originalDispatcher))
|
|
378
|
-
|
|
379
|
-
// This will use recorded data
|
|
380
|
-
const response = await fetch('https://api.example.com/users')
|
|
381
|
-
const users = await response.json()
|
|
382
|
-
|
|
383
|
-
assert(Array.isArray(users))
|
|
384
|
-
assert(users.length > 0)
|
|
385
|
-
})
|
|
244
|
+
```mjs
|
|
245
|
+
agent.clearSnapshots()
|
|
386
246
|
```
|
|
387
247
|
|
|
388
|
-
###
|
|
389
|
-
|
|
390
|
-
```javascript
|
|
391
|
-
const mode = process.env.SNAPSHOT_MODE || 'playback'
|
|
392
|
-
|
|
393
|
-
const agent = new SnapshotAgent({
|
|
394
|
-
mode,
|
|
395
|
-
snapshotPath: './test/snapshots/integration.json'
|
|
396
|
-
})
|
|
248
|
+
### `agent.resetCallCounts()`
|
|
397
249
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
250
|
+
<!-- YAML
|
|
251
|
+
added: v7.13.0
|
|
252
|
+
-->
|
|
401
253
|
|
|
402
|
-
|
|
254
|
+
* Returns: {undefined}
|
|
403
255
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
mode,
|
|
408
|
-
snapshotPath: `./test/snapshots/${testName}.json`
|
|
409
|
-
})
|
|
410
|
-
}
|
|
256
|
+
Resets the call count of every snapshot to zero. Useful between tests when the
|
|
257
|
+
same agent is reused, since matching a snapshot in `'playback'` mode increments
|
|
258
|
+
its call count.
|
|
411
259
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
setGlobalDispatcher(agent)
|
|
415
|
-
|
|
416
|
-
// Test implementation...
|
|
417
|
-
})
|
|
260
|
+
```mjs
|
|
261
|
+
agent.resetCallCounts()
|
|
418
262
|
```
|
|
419
263
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
Snapshots are stored as JSON with the following structure:
|
|
423
|
-
|
|
424
|
-
```json
|
|
425
|
-
[
|
|
426
|
-
{
|
|
427
|
-
"hash": "dGVzdC1oYXNo...",
|
|
428
|
-
"snapshot": {
|
|
429
|
-
"request": {
|
|
430
|
-
"method": "GET",
|
|
431
|
-
"url": "https://api.example.com/users",
|
|
432
|
-
"headers": {
|
|
433
|
-
"authorization": "Bearer token"
|
|
434
|
-
},
|
|
435
|
-
"body": undefined
|
|
436
|
-
},
|
|
437
|
-
"response": {
|
|
438
|
-
"statusCode": 200,
|
|
439
|
-
"headers": {
|
|
440
|
-
"content-type": "application/json"
|
|
441
|
-
},
|
|
442
|
-
"body": "eyJkYXRhIjoidGVzdCJ9", // base64 encoded
|
|
443
|
-
"trailers": {}
|
|
444
|
-
},
|
|
445
|
-
"timestamp": "2024-01-01T00:00:00.000Z"
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
]
|
|
449
|
-
```
|
|
264
|
+
### `agent.deleteSnapshot(requestOpts)`
|
|
450
265
|
|
|
451
|
-
|
|
266
|
+
<!-- YAML
|
|
267
|
+
added: v7.13.0
|
|
268
|
+
-->
|
|
452
269
|
|
|
453
|
-
|
|
270
|
+
* `requestOpts` {Object} Dispatch options identifying the snapshot to remove.
|
|
271
|
+
* Returns: {boolean} `true` if a matching snapshot was deleted, `false` if none
|
|
272
|
+
was found.
|
|
454
273
|
|
|
455
|
-
|
|
274
|
+
Deletes a single snapshot that matches the given request options.
|
|
456
275
|
|
|
457
|
-
```
|
|
458
|
-
const
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
// Exclude sensitive headers from snapshots
|
|
463
|
-
excludeHeaders: [
|
|
464
|
-
'authorization',
|
|
465
|
-
'x-api-key',
|
|
466
|
-
'cookie',
|
|
467
|
-
'set-cookie',
|
|
468
|
-
'x-auth-token',
|
|
469
|
-
'x-csrf-token'
|
|
470
|
-
],
|
|
471
|
-
|
|
472
|
-
// Filter out requests with sensitive data
|
|
473
|
-
shouldRecord: (requestOpts) => {
|
|
474
|
-
const url = new URL(requestOpts.path, requestOpts.origin)
|
|
475
|
-
|
|
476
|
-
// Don't record authentication endpoints
|
|
477
|
-
if (url.pathname.includes('/auth/') || url.pathname.includes('/login')) {
|
|
478
|
-
return false
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
// Don't record if request contains sensitive body data
|
|
482
|
-
if (requestOpts.body && typeof requestOpts.body === 'string') {
|
|
483
|
-
const body = requestOpts.body.toLowerCase()
|
|
484
|
-
if (body.includes('password') || body.includes('secret')) {
|
|
485
|
-
return false
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
return true
|
|
490
|
-
}
|
|
276
|
+
```mjs
|
|
277
|
+
const deleted = agent.deleteSnapshot({
|
|
278
|
+
method: 'GET',
|
|
279
|
+
origin: 'https://api.example.com',
|
|
280
|
+
path: '/users'
|
|
491
281
|
})
|
|
492
282
|
```
|
|
493
283
|
|
|
494
|
-
###
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
const response = await fetch('https://api.example.com/nonexistent')
|
|
521
|
-
} catch (error) {
|
|
522
|
-
if (error.message.includes('No snapshot found')) {
|
|
523
|
-
// Handle missing snapshot
|
|
524
|
-
console.log('Snapshot not found for this request')
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
```
|
|
528
|
-
|
|
529
|
-
### Handling Network Errors in Record Mode
|
|
530
|
-
|
|
531
|
-
```javascript
|
|
532
|
-
const agent = new SnapshotAgent({ mode: 'record', snapshotPath: './snapshots.json' })
|
|
533
|
-
|
|
534
|
-
try {
|
|
535
|
-
const response = await fetch('https://nonexistent-api.example.com/data')
|
|
536
|
-
} catch (error) {
|
|
537
|
-
// Network errors are not recorded as snapshots
|
|
538
|
-
console.log('Network error:', error.message)
|
|
539
|
-
}
|
|
540
|
-
```
|
|
541
|
-
|
|
542
|
-
## Best Practices
|
|
543
|
-
|
|
544
|
-
### 1. Organize Snapshots by Test Suite
|
|
545
|
-
|
|
546
|
-
```javascript
|
|
547
|
-
// Use descriptive snapshot file names
|
|
548
|
-
const agent = new SnapshotAgent({
|
|
549
|
-
mode: 'playback',
|
|
550
|
-
snapshotPath: `./test/snapshots/${testSuiteName}-${testName}.json`
|
|
284
|
+
### `agent.getSnapshotInfo(requestOpts)`
|
|
285
|
+
|
|
286
|
+
<!-- YAML
|
|
287
|
+
added: v7.13.0
|
|
288
|
+
-->
|
|
289
|
+
|
|
290
|
+
* `requestOpts` {Object} Dispatch options identifying the snapshot to inspect.
|
|
291
|
+
* Returns: {Object|null} Information about the matching snapshot, or `null` when
|
|
292
|
+
none is found.
|
|
293
|
+
* `hash` {string} The hash used to key the snapshot.
|
|
294
|
+
* `request` {Object} The recorded request.
|
|
295
|
+
* `method` {string} The request method.
|
|
296
|
+
* `url` {string} The request URL.
|
|
297
|
+
* `headers` {Record<string, string>} The recorded request headers.
|
|
298
|
+
* `body` {string} The recorded request body, when present.
|
|
299
|
+
* `responseCount` {number} The number of recorded responses for the request.
|
|
300
|
+
* `callCount` {number} The number of times the snapshot has been matched.
|
|
301
|
+
* `timestamp` {string} The ISO timestamp at which the snapshot was recorded.
|
|
302
|
+
|
|
303
|
+
Returns metadata about a snapshot without returning the response payload.
|
|
304
|
+
|
|
305
|
+
```mjs
|
|
306
|
+
const info = agent.getSnapshotInfo({
|
|
307
|
+
method: 'GET',
|
|
308
|
+
origin: 'https://api.example.com',
|
|
309
|
+
path: '/users'
|
|
551
310
|
})
|
|
552
311
|
```
|
|
553
312
|
|
|
554
|
-
###
|
|
555
|
-
|
|
556
|
-
Add snapshot files to version control to ensure consistent test behavior across environments:
|
|
313
|
+
### `agent.replaceSnapshots(snapshotData)`
|
|
557
314
|
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
```
|
|
315
|
+
<!-- YAML
|
|
316
|
+
added: v7.13.0
|
|
317
|
+
-->
|
|
562
318
|
|
|
563
|
-
|
|
319
|
+
* `snapshotData` {Array} The snapshot data to load, replacing any existing
|
|
320
|
+
snapshots.
|
|
321
|
+
* `hash` {string} The hash used to key the snapshot.
|
|
322
|
+
* `snapshot` {Object} The snapshot entry.
|
|
323
|
+
* Returns: {undefined}
|
|
564
324
|
|
|
565
|
-
|
|
566
|
-
test('API test', async (t) => {
|
|
567
|
-
const agent = new SnapshotAgent({
|
|
568
|
-
mode: 'playback',
|
|
569
|
-
snapshotPath: './test/snapshots/temp-test.json'
|
|
570
|
-
})
|
|
571
|
-
|
|
572
|
-
// Clean up after test
|
|
573
|
-
t.after(() => {
|
|
574
|
-
agent.clearSnapshots()
|
|
575
|
-
})
|
|
576
|
-
})
|
|
577
|
-
```
|
|
325
|
+
Replaces all in-memory snapshots with the provided data.
|
|
578
326
|
|
|
579
|
-
|
|
327
|
+
```mjs
|
|
328
|
+
const recorder = agent.getRecorder()
|
|
329
|
+
const snapshots = recorder.getSnapshots()
|
|
580
330
|
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
snapshotPath: './test/snapshots/validation.json'
|
|
586
|
-
})
|
|
587
|
-
|
|
588
|
-
const recorder = agent.getRecorder()
|
|
589
|
-
const snapshots = recorder.getSnapshots()
|
|
590
|
-
|
|
591
|
-
// Validate snapshot structure
|
|
592
|
-
assert(snapshots.length > 0, 'Should have recorded snapshots')
|
|
593
|
-
assert(snapshots[0].request.url.startsWith('https://'), 'Should use HTTPS')
|
|
594
|
-
})
|
|
331
|
+
agent.replaceSnapshots(snapshots.map((snapshot, index) => ({
|
|
332
|
+
hash: `snapshot-${index}`,
|
|
333
|
+
snapshot
|
|
334
|
+
})))
|
|
595
335
|
```
|
|
596
336
|
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
### vs Manual MockAgent Setup
|
|
337
|
+
### `agent.close()`
|
|
600
338
|
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
const mockPool = mockAgent.get('https://api.example.com')
|
|
339
|
+
<!-- YAML
|
|
340
|
+
added: v7.13.0
|
|
341
|
+
-->
|
|
605
342
|
|
|
606
|
-
|
|
607
|
-
path: '/users',
|
|
608
|
-
method: 'GET'
|
|
609
|
-
}).reply(200, [
|
|
610
|
-
{ id: 1, name: 'User 1' },
|
|
611
|
-
{ id: 2, name: 'User 2' }
|
|
612
|
-
])
|
|
613
|
-
```
|
|
343
|
+
* Returns: {Promise<void>} Resolves once the agent and its resources are closed.
|
|
614
344
|
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
345
|
+
Closes the agent and releases its resources. In `'record'` and `'update'` modes
|
|
346
|
+
the recorder is flushed to disk first. In `'playback'` mode nothing is written,
|
|
347
|
+
because matching a snapshot mutates its call count and saving would needlessly
|
|
348
|
+
rewrite the snapshot file. The real [`Agent`][], when one was created, and the
|
|
349
|
+
underlying [`MockAgent`][] are closed as well.
|
|
620
350
|
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
// Automatically replays recorded response
|
|
351
|
+
```mjs
|
|
352
|
+
await agent.close()
|
|
624
353
|
```
|
|
625
354
|
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
- ✅ More efficient for high-performance scenarios
|
|
634
|
-
|
|
635
|
-
## See Also
|
|
636
|
-
|
|
637
|
-
- [MockAgent](/docs/docs/api/MockAgent.md) - Manual mocking for more control
|
|
638
|
-
- [MockCallHistory](/docs/docs/api/MockCallHistory.md) - Inspecting request history
|
|
639
|
-
- [Testing Best Practices](/docs/docs/best-practices/writing-tests.md) - General testing guidance
|
|
355
|
+
[`Agent`]: Agent.md#class-agent
|
|
356
|
+
[`Dispatcher`]: Dispatcher.md#class-dispatcher
|
|
357
|
+
[`InvalidArgumentError`]: Errors.md#undicierrorsundiciinvalidargumenterror
|
|
358
|
+
[`MockAgent`]: MockAgent.md#class-mockagent
|
|
359
|
+
[`UndiciError`]: Errors.md#undicierrorsundicierror
|
|
360
|
+
[`agent.getRecorder()`]: #agentgetrecorder
|
|
361
|
+
[`setGlobalDispatcher()`]: Dispatcher.md#setglobaldispatcherdispatcher
|