cassetter 0.11.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/LICENSE +21 -0
- package/README.md +169 -0
- package/dist/binding.d.ts +60 -0
- package/dist/binding.d.ts.map +1 -0
- package/dist/binding.js +12 -0
- package/dist/binding.js.map +1 -0
- package/dist/cassette.d.ts +122 -0
- package/dist/cassette.d.ts.map +1 -0
- package/dist/cassette.js +377 -0
- package/dist/cassette.js.map +1 -0
- package/dist/context.d.ts +22 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +70 -0
- package/dist/context.js.map +1 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +26 -0
- package/dist/index.js.map +1 -0
- package/dist/intercept/base.d.ts +11 -0
- package/dist/intercept/base.d.ts.map +1 -0
- package/dist/intercept/base.js +20 -0
- package/dist/intercept/base.js.map +1 -0
- package/dist/intercept/fetch.d.ts +13 -0
- package/dist/intercept/fetch.d.ts.map +1 -0
- package/dist/intercept/fetch.js +104 -0
- package/dist/intercept/fetch.js.map +1 -0
- package/dist/intercept/index.d.ts +4 -0
- package/dist/intercept/index.d.ts.map +1 -0
- package/dist/intercept/index.js +3 -0
- package/dist/intercept/index.js.map +1 -0
- package/dist/recording.d.ts +22 -0
- package/dist/recording.d.ts.map +1 -0
- package/dist/recording.js +51 -0
- package/dist/recording.js.map +1 -0
- package/dist/types.d.ts +96 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +35 -0
- package/dist/types.js.map +1 -0
- package/native/cassetter.darwin-arm64.node +0 -0
- package/native/cassetter.darwin-x64.node +0 -0
- package/native/cassetter.linux-arm64-gnu.node +0 -0
- package/native/cassetter.linux-arm64-musl.node +0 -0
- package/native/cassetter.linux-x64-gnu.node +0 -0
- package/native/cassetter.linux-x64-musl.node +0 -0
- package/native/cassetter.win32-arm64-msvc.node +0 -0
- package/native/cassetter.win32-x64-msvc.node +0 -0
- package/native/index.js +326 -0
- package/native/package.json +3 -0
- package/package.json +74 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Marcelo Trylesinski
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# cassetter (Node)
|
|
2
|
+
|
|
3
|
+
HTTP cassette recorder for Node.js tests. Safe by default.
|
|
4
|
+
|
|
5
|
+
This is the Node binding. The cassette format, request matching, security
|
|
6
|
+
filtering, and body processing are implemented in Rust
|
|
7
|
+
([`crates/cassetter-core`](../crates/cassetter-core)) and shared with the Python
|
|
8
|
+
package, so cassettes are interchangeable between them.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install --save-dev cassetter
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
The package includes native addons for macOS and Windows on x64 and ARM64. It
|
|
17
|
+
also includes Linux x64 and ARM64 addons for glibc and musl.
|
|
18
|
+
|
|
19
|
+
## Quick start
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
import { useCassette } from "cassetter";
|
|
23
|
+
|
|
24
|
+
await useCassette("tests/cassettes/users.yaml", async () => {
|
|
25
|
+
const res = await fetch("https://api.example.com/users");
|
|
26
|
+
console.log(res.status); // 200
|
|
27
|
+
});
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
The first run records real traffic. Later runs replay from the file - no network.
|
|
31
|
+
|
|
32
|
+
With vitest:
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import { expect, it } from "vitest";
|
|
36
|
+
import { useCassette } from "cassetter";
|
|
37
|
+
|
|
38
|
+
it("fetches users", async () => {
|
|
39
|
+
await useCassette("tests/cassettes/users.yaml", async () => {
|
|
40
|
+
const res = await fetch("https://api.example.com/users");
|
|
41
|
+
expect(res.status).toBe(200);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
The callback receives the cassette if you want to inspect what was recorded:
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
await useCassette("cassette.yaml", async (cassette) => {
|
|
50
|
+
await fetch("https://api.example.com/users");
|
|
51
|
+
expect(cassette.interactions).toHaveLength(1);
|
|
52
|
+
});
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Record modes
|
|
56
|
+
|
|
57
|
+
| Mode | Behavior |
|
|
58
|
+
| --- | --- |
|
|
59
|
+
| `none` | Replay only. Throws `NoMatchError` if nothing matches. |
|
|
60
|
+
| `once` | Record if the cassette doesn't exist, otherwise replay. (default) |
|
|
61
|
+
| `new_episodes` | Replay what exists, record what doesn't. |
|
|
62
|
+
| `all` | Record everything, overwriting the cassette. |
|
|
63
|
+
| `rewrite` | Delete the cassette, then record everything. |
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
await useCassette("cassette.yaml", { recordMode: "none" }, async () => { ... });
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Under `once`, an existing cassette replays only: an unmatched request throws
|
|
70
|
+
rather than silently reaching the network and appending to the file.
|
|
71
|
+
|
|
72
|
+
## Safe by default
|
|
73
|
+
|
|
74
|
+
Secrets are filtered **at write time**, so they never reach disk. The defaults
|
|
75
|
+
come from the Rust core - the same lists the Python package uses:
|
|
76
|
+
|
|
77
|
+
- **Headers**: `authorization`, `cookie`, `set-cookie`, `x-api-key`, `api-key`,
|
|
78
|
+
`x-auth-token`, `proxy-authorization`, `www-authenticate`, `x-goog-api-key`,
|
|
79
|
+
`x-amz-security-token`
|
|
80
|
+
- **Query params**: `api_key`, `apikey`, `token`, `access_token`, `client_secret`
|
|
81
|
+
- **JSON body fields**: `access_token`, `refresh_token`, `client_secret`, `password`
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
await useCassette("cassette.yaml", {
|
|
85
|
+
filterHeaders: ["x-custom-secret"],
|
|
86
|
+
bodyScrubPatterns: ["my_secret_field"],
|
|
87
|
+
replacement: "***REDACTED***",
|
|
88
|
+
}, async () => { ... });
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
These **add to** the built-in lists rather than standing in for them, so naming
|
|
92
|
+
one more header to scrub never starts recording the ones above. Read the
|
|
93
|
+
built-ins with `defaultFilterHeaders()` and friends if you need them directly.
|
|
94
|
+
|
|
95
|
+
Filtering applies to every protocol: HTTP headers, query params, and bodies;
|
|
96
|
+
gRPC metadata and the `jsonDebug` payload; WebSocket handshake headers and
|
|
97
|
+
text/JSON frame bodies. Binary protobuf bodies are stored as-is - they cannot
|
|
98
|
+
be pattern-scrubbed.
|
|
99
|
+
|
|
100
|
+
## Request matching
|
|
101
|
+
|
|
102
|
+
Defaults to method + URI:
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
await useCassette("cassette.yaml", {
|
|
106
|
+
matchOn: ["method", "uri", "json_body"],
|
|
107
|
+
ignoreJsonPaths: ["requestId", "timestamp"],
|
|
108
|
+
}, async () => { ... });
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Available matchers: `method`, `uri`, `headers`, `body`, `json_body`. An unknown
|
|
112
|
+
matcher is rejected rather than silently matching everything.
|
|
113
|
+
|
|
114
|
+
## Cassette format
|
|
115
|
+
|
|
116
|
+
YAML by default; use a `.toml` extension for TOML. TOML loads faster and
|
|
117
|
+
produces smaller files, but cannot store gRPC or WebSocket interactions.
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
await useCassette("cassette.toml", async () => { ... });
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Cassette expiry
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
await useCassette("cassette.yaml", {
|
|
127
|
+
maxAge: "30d",
|
|
128
|
+
onExpiry: "rerecord",
|
|
129
|
+
}, async () => { ... });
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
`maxAge` accepts `"24h"`, `"7d"`, `"4w"`. `onExpiry` is `warn` (default),
|
|
133
|
+
`fail`, or `rerecord`.
|
|
134
|
+
|
|
135
|
+
## Working with bodies
|
|
136
|
+
|
|
137
|
+
Bodies are `{ type, content }`, matching the cassette file. Binary content is
|
|
138
|
+
hex, so helpers are provided:
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
import { bodyToBuffer, binaryBody, binaryBodyBytes } from "cassetter";
|
|
142
|
+
|
|
143
|
+
bodyToBuffer({ type: "text", content: "hi" });
|
|
144
|
+
binaryBody(Buffer.from("hi"));
|
|
145
|
+
binaryBodyBytes({ type: "binary", content: "6869" });
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Interception
|
|
149
|
+
|
|
150
|
+
The global `fetch` is intercepted, which covers `fetch`, `undici`, and
|
|
151
|
+
libraries built on them. `ignoreLocalhost: true` lets localhost traffic through
|
|
152
|
+
untouched.
|
|
153
|
+
|
|
154
|
+
## Development
|
|
155
|
+
|
|
156
|
+
Requires a Rust toolchain and Node 18+.
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
npm install
|
|
160
|
+
npm run build:native # compile the addon into native/
|
|
161
|
+
npm test
|
|
162
|
+
npm run typecheck
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
`npm run build` does the native build plus `tsc`.
|
|
166
|
+
|
|
167
|
+
Changes to matching, security, the cassette format, or body handling belong in
|
|
168
|
+
`crates/cassetter-core` so the Python binding gets them too - see
|
|
169
|
+
[`conformance/`](../conformance) for the suite that enforces this.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loader and type surface for the native `cassetter-node` addon.
|
|
3
|
+
*
|
|
4
|
+
* Everything below is implemented in Rust by `cassetter-core` and shared with
|
|
5
|
+
* the Python binding. Structured values cross the boundary shaped exactly like
|
|
6
|
+
* the cassette file format.
|
|
7
|
+
*/
|
|
8
|
+
import type { Body, GrpcInteraction, HttpInteraction, HttpRequest, MatchConfig, SecurityConfig, WsInteraction } from "./types.js";
|
|
9
|
+
export interface MatchHit<T> {
|
|
10
|
+
index: number;
|
|
11
|
+
interaction: T;
|
|
12
|
+
}
|
|
13
|
+
export interface NativeCassette {
|
|
14
|
+
readonly version: number;
|
|
15
|
+
readonly length: number;
|
|
16
|
+
save(path: string, order?: number[], mode?: number): void;
|
|
17
|
+
toYaml(order?: number[]): string;
|
|
18
|
+
toToml(order?: number[]): string;
|
|
19
|
+
outputOrder(sortConfig?: MatchConfig, recordOrder?: number[]): number[];
|
|
20
|
+
readonly interactions: HttpInteraction[];
|
|
21
|
+
readonly playedIndices: boolean[];
|
|
22
|
+
readonly unplayedCount: number;
|
|
23
|
+
addInteraction(interaction: HttpInteraction): void;
|
|
24
|
+
markPlayed(index: number): void;
|
|
25
|
+
/** Match and mark played in one atomic step. */
|
|
26
|
+
takeMatch(request: HttpRequest, config?: MatchConfig): MatchHit<HttpInteraction> | null;
|
|
27
|
+
readonly grpcInteractions: GrpcInteraction[];
|
|
28
|
+
readonly grpcPlayed: boolean[];
|
|
29
|
+
addGrpcInteraction(interaction: GrpcInteraction): void;
|
|
30
|
+
markGrpcPlayed(index: number): void;
|
|
31
|
+
takeGrpcMatch(method: string): MatchHit<GrpcInteraction> | null;
|
|
32
|
+
readonly wsInteractions: WsInteraction[];
|
|
33
|
+
readonly wsPlayed: boolean[];
|
|
34
|
+
addWsInteraction(interaction: WsInteraction): void;
|
|
35
|
+
markWsPlayed(index: number): void;
|
|
36
|
+
takeWsMatch(uri: string): MatchHit<WsInteraction> | null;
|
|
37
|
+
}
|
|
38
|
+
interface NativeBinding {
|
|
39
|
+
Cassette: {
|
|
40
|
+
new (): NativeCassette;
|
|
41
|
+
load(path: string): NativeCassette;
|
|
42
|
+
fromYaml(content: string): NativeCassette;
|
|
43
|
+
fromToml(content: string): NativeCassette;
|
|
44
|
+
};
|
|
45
|
+
processBody(rawBytes: Buffer, contentType?: string | null, contentEncoding?: string | null, maxDecompressed?: number | null): Body;
|
|
46
|
+
scrubInteraction(interaction: HttpInteraction, config?: SecurityConfig): HttpInteraction;
|
|
47
|
+
scrubGrpcInteraction(interaction: GrpcInteraction, config?: SecurityConfig): GrpcInteraction;
|
|
48
|
+
scrubWsInteraction(interaction: WsInteraction, config?: SecurityConfig): WsInteraction;
|
|
49
|
+
defaultFilterHeaders(): string[];
|
|
50
|
+
defaultFilterQueryParameters(): string[];
|
|
51
|
+
defaultBodyScrubPatterns(): string[];
|
|
52
|
+
defaultMatchOn(): string[];
|
|
53
|
+
knownMatchers(): string[];
|
|
54
|
+
defaultReplacement(): string;
|
|
55
|
+
formatVersion(): number;
|
|
56
|
+
}
|
|
57
|
+
export declare const native: NativeBinding;
|
|
58
|
+
export declare const processBody: (rawBytes: Buffer, contentType?: string | null, contentEncoding?: string | null, maxDecompressed?: number | null) => Body, scrubInteraction: (interaction: HttpInteraction, config?: SecurityConfig) => HttpInteraction, scrubGrpcInteraction: (interaction: GrpcInteraction, config?: SecurityConfig) => GrpcInteraction, scrubWsInteraction: (interaction: WsInteraction, config?: SecurityConfig) => WsInteraction, defaultFilterHeaders: () => string[], defaultFilterQueryParameters: () => string[], defaultBodyScrubPatterns: () => string[], defaultMatchOn: () => string[], knownMatchers: () => string[], defaultReplacement: () => string, formatVersion: () => number;
|
|
59
|
+
export {};
|
|
60
|
+
//# sourceMappingURL=binding.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"binding.d.ts","sourceRoot":"","sources":["../src/binding.ts"],"names":[],"mappings":"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"}
|
package/dist/binding.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loader and type surface for the native `cassetter-node` addon.
|
|
3
|
+
*
|
|
4
|
+
* Everything below is implemented in Rust by `cassetter-core` and shared with
|
|
5
|
+
* the Python binding. Structured values cross the boundary shaped exactly like
|
|
6
|
+
* the cassette file format.
|
|
7
|
+
*/
|
|
8
|
+
import { createRequire } from "node:module";
|
|
9
|
+
const require = createRequire(import.meta.url);
|
|
10
|
+
export const native = require("../native/index.js");
|
|
11
|
+
export const { processBody, scrubInteraction, scrubGrpcInteraction, scrubWsInteraction, defaultFilterHeaders, defaultFilterQueryParameters, defaultBodyScrubPatterns, defaultMatchOn, knownMatchers, defaultReplacement, formatVersion, } = native;
|
|
12
|
+
//# sourceMappingURL=binding.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"binding.js","sourceRoot":"","sources":["../src/binding.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAoF5C,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAE/C,MAAM,CAAC,MAAM,MAAM,GAAkB,OAAO,CAAC,oBAAoB,CAAkB,CAAC;AAEpF,MAAM,CAAC,MAAM,EACX,WAAW,EACX,gBAAgB,EAChB,oBAAoB,EACpB,kBAAkB,EAClB,oBAAoB,EACpB,4BAA4B,EAC5B,wBAAwB,EACxB,cAAc,EACd,aAAa,EACb,kBAAkB,EAClB,aAAa,GACd,GAAG,MAAM,CAAC"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Record/replay orchestration over the native cassette core.
|
|
3
|
+
*
|
|
4
|
+
* The cassette format, request matching, security filtering, and body
|
|
5
|
+
* processing all live in Rust (`cassetter-core`). This file is the thin,
|
|
6
|
+
* idiomatic layer on top - the same role `src/cassetter/cassette.py` plays for
|
|
7
|
+
* the Python binding.
|
|
8
|
+
*/
|
|
9
|
+
import { RecordMode } from "./recording.js";
|
|
10
|
+
import type { Body, GrpcInteraction, GrpcResponse, HeaderMap, HttpInteraction, HttpResponse, MatchConfig, SecurityConfig, WsFrame, WsInteraction } from "./types.js";
|
|
11
|
+
/** Raised when a cassette file is required but absent. */
|
|
12
|
+
export declare class CassetteNotFoundError extends Error {
|
|
13
|
+
/** Configure a cassette. Nothing is read until `load()`. */
|
|
14
|
+
constructor(message: string);
|
|
15
|
+
}
|
|
16
|
+
/** Raised when a cassette exists but cannot be parsed. */
|
|
17
|
+
export declare class CassetteLoadError extends Error {
|
|
18
|
+
/** Configure a cassette. Nothing is read until `load()`. */
|
|
19
|
+
constructor(message: string);
|
|
20
|
+
}
|
|
21
|
+
/** Raised when a cassette is older than `maxAge` and `onExpiry` is `fail`. */
|
|
22
|
+
export declare class CassetteExpiredError extends Error {
|
|
23
|
+
/** Configure a cassette. Nothing is read until `load()`. */
|
|
24
|
+
constructor(message: string);
|
|
25
|
+
}
|
|
26
|
+
/** Raised when no recorded interaction matches, and none may be recorded. */
|
|
27
|
+
export declare class NoMatchError extends Error {
|
|
28
|
+
/** Configure a cassette. Nothing is read until `load()`. */
|
|
29
|
+
constructor(message: string);
|
|
30
|
+
}
|
|
31
|
+
export interface CassetteOptions {
|
|
32
|
+
recordMode?: RecordMode;
|
|
33
|
+
matchConfig?: MatchConfig;
|
|
34
|
+
securityConfig?: SecurityConfig;
|
|
35
|
+
maxAge?: string;
|
|
36
|
+
onExpiry?: "warn" | "fail" | "rerecord";
|
|
37
|
+
ignoreLocalhost?: boolean;
|
|
38
|
+
}
|
|
39
|
+
export declare class Cassette {
|
|
40
|
+
private readonly _path;
|
|
41
|
+
private readonly _recordMode;
|
|
42
|
+
private readonly _matchConfig;
|
|
43
|
+
private readonly _securityConfig;
|
|
44
|
+
private readonly _maxAge;
|
|
45
|
+
private readonly _onExpiry;
|
|
46
|
+
private readonly _ignoreLocalhost;
|
|
47
|
+
private _inner;
|
|
48
|
+
private _dirty;
|
|
49
|
+
/** Order interactions were recorded in, to break ties in the output order. */
|
|
50
|
+
private _recordOrders;
|
|
51
|
+
/** Next position `reserveRecordOrder` will hand out. */
|
|
52
|
+
private _nextRecordOrder;
|
|
53
|
+
/** `once` replays without recording when the cassette already existed. */
|
|
54
|
+
private _onceReplayOnly;
|
|
55
|
+
/** Mode of the cassette `rewrite` deleted, to put back on its replacement. */
|
|
56
|
+
private _rewrittenFileMode;
|
|
57
|
+
/** Configure a cassette. Nothing is read until `load()`. */
|
|
58
|
+
constructor(path: string, options?: CassetteOptions);
|
|
59
|
+
/** Where this cassette is read from and written to. */
|
|
60
|
+
get path(): string;
|
|
61
|
+
/** The record mode in force. */
|
|
62
|
+
get recordMode(): RecordMode;
|
|
63
|
+
/** Whether localhost traffic bypasses the cassette entirely. */
|
|
64
|
+
get ignoreLocalhost(): boolean;
|
|
65
|
+
/** The recorded HTTP interactions, empty before `load()`. */
|
|
66
|
+
get interactions(): HttpInteraction[];
|
|
67
|
+
/** The recorded gRPC interactions, empty before `load()`. */
|
|
68
|
+
get grpcInteractions(): GrpcInteraction[];
|
|
69
|
+
/** The recorded WebSocket interactions, empty before `load()`. */
|
|
70
|
+
get wsInteractions(): WsInteraction[];
|
|
71
|
+
/** Whether this mode may replay an existing interaction. */
|
|
72
|
+
get canReplay(): boolean;
|
|
73
|
+
/** Whether an unmatched request may go to the network and be recorded. */
|
|
74
|
+
get canRecord(): boolean;
|
|
75
|
+
/** Load from disk, or start an empty cassette based on the record mode. */
|
|
76
|
+
load(): void;
|
|
77
|
+
/**
|
|
78
|
+
* Write to disk if modified.
|
|
79
|
+
*
|
|
80
|
+
* An empty cassette is written only when a file already exists, so a
|
|
81
|
+
* re-record that captured nothing truncates the stale file instead of
|
|
82
|
+
* leaving it behind. Interactions go out in a canonical order rather than
|
|
83
|
+
* the order their responses arrived in, so a concurrent suite produces the
|
|
84
|
+
* same file every run.
|
|
85
|
+
*/
|
|
86
|
+
save(): void;
|
|
87
|
+
/** Serialize to YAML without touching the filesystem. */
|
|
88
|
+
toYaml(): string;
|
|
89
|
+
/** Serialize to TOML without touching the filesystem. */
|
|
90
|
+
toToml(): string;
|
|
91
|
+
/**
|
|
92
|
+
* Claim this interaction's position before its request is issued.
|
|
93
|
+
*
|
|
94
|
+
* Interceptors record once the response is back, so under concurrency the
|
|
95
|
+
* cassette would otherwise be written in whatever order responses arrived
|
|
96
|
+
* in - different on every run.
|
|
97
|
+
*/
|
|
98
|
+
reserveRecordOrder(): number;
|
|
99
|
+
play(method: string, uri: string, headers: HeaderMap, body: Buffer | null): HttpResponse;
|
|
100
|
+
record(method: string, uri: string, requestHeaders: HeaderMap, requestBody: Buffer | null, status: number, responseHeaders: HeaderMap, responseBody: Buffer | null, order?: number): HttpResponse;
|
|
101
|
+
/** Replay a gRPC response for `method`, or throw `NoMatchError`. */
|
|
102
|
+
playGrpc(method: string): GrpcResponse;
|
|
103
|
+
recordGrpc(method: string, metadata: HeaderMap, requestBody: Body, responseBody: Body, options?: {
|
|
104
|
+
statusCode?: number;
|
|
105
|
+
statusMessage?: string;
|
|
106
|
+
responseMetadata?: HeaderMap;
|
|
107
|
+
jsonDebug?: unknown;
|
|
108
|
+
}): GrpcResponse;
|
|
109
|
+
/** Replay a WebSocket interaction for `uri`, or throw `NoMatchError`. */
|
|
110
|
+
playWs(uri: string): WsInteraction;
|
|
111
|
+
/** Record a WebSocket connection and its frames, scrubbed. */
|
|
112
|
+
recordWs(uri: string, headers: HeaderMap, frames: WsFrame[]): void;
|
|
113
|
+
/** The native cassette, created on first use if `load()` never ran. */
|
|
114
|
+
private _ensureInner;
|
|
115
|
+
/** Apply `onExpiry` when the newest recording predates `maxAge`. */
|
|
116
|
+
private _checkExpiry;
|
|
117
|
+
/** The most recent `recordedAt` across every protocol, if any. */
|
|
118
|
+
private _newestRecordedAt;
|
|
119
|
+
}
|
|
120
|
+
/** Case-insensitive header lookup returning the first value. */
|
|
121
|
+
export declare function getHeader(headers: HeaderMap, name: string): string | null;
|
|
122
|
+
//# sourceMappingURL=cassette.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cassette.d.ts","sourceRoot":"","sources":["../src/cassette.ts"],"names":[],"mappings":"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"}
|