@loopingai/core 0.2.0 → 0.3.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 +22 -3
- package/dist/testing/index.d.ts +6 -6
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +5 -5
- package/dist/testing/index.js.map +1 -1
- package/dist/testing/node.d.ts +39 -29
- package/dist/testing/node.d.ts.map +1 -1
- package/dist/testing/node.js +39 -29
- package/dist/testing/node.js.map +1 -1
- package/dist/testing/vcr-global-setup.d.ts +12 -0
- package/dist/testing/vcr-global-setup.d.ts.map +1 -1
- package/dist/testing/vcr-global-setup.js +12 -3
- package/dist/testing/vcr-global-setup.js.map +1 -1
- package/dist/testing/vcr-shared.d.ts +24 -6
- package/dist/testing/vcr-shared.d.ts.map +1 -1
- package/dist/testing/vcr-shared.js +19 -6
- package/dist/testing/vcr-shared.js.map +1 -1
- package/dist/testing/vcr-spec.d.ts +10 -1
- package/dist/testing/vcr-spec.d.ts.map +1 -1
- package/dist/testing/vcr-spec.js +40 -8
- package/dist/testing/vcr-spec.js.map +1 -1
- package/dist/testing/vcr-store.d.ts +87 -0
- package/dist/testing/vcr-store.d.ts.map +1 -0
- package/dist/testing/vcr-store.js +192 -0
- package/dist/testing/vcr-store.js.map +1 -0
- package/dist/testing/vcr.d.ts +103 -66
- package/dist/testing/vcr.d.ts.map +1 -1
- package/dist/testing/vcr.js +228 -163
- package/dist/testing/vcr.js.map +1 -1
- package/package.json +13 -13
package/README.md
CHANGED
|
@@ -198,7 +198,7 @@ not drag in the A2A adapter, and the test harness cannot reach a production bund
|
|
|
198
198
|
| `@loopingai/core/subagent` | `RecipeSubagentBase`, resumable runs, fingerprinting, workspace |
|
|
199
199
|
| `@loopingai/core/db` | `AgentDB`, `notify_tasks` + `subtasks` schema, migrations, `PluginStore` |
|
|
200
200
|
| `@loopingai/core/testing` | VCR, `FakeSession`, `mockModel`, DO helpers, JWK fixtures — _workerd realm_ |
|
|
201
|
-
| `@loopingai/core/testing/node` | the VCR recorder — _Node realm, never import from a spec_
|
|
201
|
+
| `@loopingai/core/testing/node` | the VCR recorder + cassette store — _Node realm, never import from a spec_ |
|
|
202
202
|
| `@loopingai/core/eslint` | the `no-deprecated-object-properties` rule |
|
|
203
203
|
|
|
204
204
|
`/testing*` and `/eslint` are structurally incapable of entering a runtime graph, and
|
|
@@ -363,8 +363,27 @@ await withDb("accepts a turn once", async (db) => {
|
|
|
363
363
|
```
|
|
364
364
|
|
|
365
365
|
- **VCR** — record/replay real HTTP against on-disk cassettes, split across the Node
|
|
366
|
-
and workerd realms because specs run in workerd, which has no filesystem.
|
|
367
|
-
|
|
366
|
+
and workerd realms because specs run in workerd, which has no filesystem. The
|
|
367
|
+
recorder is a Miniflare `outboundService`, so it works on any
|
|
368
|
+
`@cloudflare/vitest-pool-workers` from 0.18 up and needs no `undici`:
|
|
369
|
+
|
|
370
|
+
```ts
|
|
371
|
+
// vitest.config.ts
|
|
372
|
+
const vcr = createVcr({
|
|
373
|
+
snapshotsDir: path.resolve(import.meta.dirname, "test/snapshots"),
|
|
374
|
+
record: recordFromEnv(), // RECORD=1
|
|
375
|
+
excludeHeaders: ["authorization", "x-api-key"] // never written to a cassette
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
cloudflareTest({ miniflare: { outboundService: vcr.outboundService } });
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
Then `setupRecording()` at the top of a spec gives every `it` its own cassette,
|
|
382
|
+
auto-named from the file + describe + test names. Cassettes match on method, URL
|
|
383
|
+
and body — never on headers, so a runtime upgrade cannot invalidate them — and a
|
|
384
|
+
request with no active cassette is blocked rather than reaching the network.
|
|
385
|
+
Point vitest's `globalSetup` at `@loopingai/core/testing/vcr-global-setup`.
|
|
386
|
+
|
|
368
387
|
- **Fakes** — `FakeSession` (a `SessionLike` reference implementation) and `mockModel`
|
|
369
388
|
(a scripted `LanguageModel`), so a loop can be driven with no model call at all.
|
|
370
389
|
- **Fixtures** — Ed25519 keypairs and a gateway-JWT signer, so the zero-trust path is
|
package/dist/testing/index.d.ts
CHANGED
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
* does not exist in a deployed Worker.
|
|
8
8
|
*
|
|
9
9
|
* **This barrel is the workerd half** — safe to import from a spec. The VCR
|
|
10
|
-
* recorder needs `
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
10
|
+
* recorder needs `node:fs`, so it lives behind `@loopingai/core/testing/node`
|
|
11
|
+
* and must not be re-exported here: pulling it into this graph would drag Node
|
|
12
|
+
* builtins into every spec that wanted a fixture. `vcr-shared.ts` is the seam
|
|
13
|
+
* both realms may load.
|
|
14
14
|
*
|
|
15
15
|
* Three things live here:
|
|
16
16
|
*
|
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
* - **Fixtures** — Ed25519 keypairs and a gateway-JWT signer, so the zero-trust
|
|
22
22
|
* path can be exercised end to end without a real gateway.
|
|
23
23
|
*/
|
|
24
|
-
export { setupRecording, cassetteNameFor } from "./vcr-spec.js";
|
|
25
|
-
export { VCR_CONTROL_ORIGIN, CASSETTE_NAME_RE } from "./vcr-shared.js";
|
|
24
|
+
export { setupRecording, cassetteNameFor, type SetupRecordingOptions } from "./vcr-spec.js";
|
|
25
|
+
export { VCR_CONTROL_ORIGIN, VCR_MARKER_HEADER, CASSETTE_NAME_RE, type VcrReleaseResult } from "./vcr-shared.js";
|
|
26
26
|
export { FakeSession } from "./fake-session.js";
|
|
27
27
|
export { mockModel, finalReply, type MockStep } from "./mock-model.js";
|
|
28
28
|
export { makeGatewayToken, type GatewayTokenOptions } from "./auth.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EACL,cAAc,EACd,eAAe,EACf,KAAK,qBAAqB,EAC3B,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,kBAAkB,EAClB,iBAAiB,EACjB,gBAAgB,EAChB,KAAK,gBAAgB,EACtB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,KAAK,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAEvE,OAAO,EAAE,gBAAgB,EAAE,KAAK,mBAAmB,EAAE,MAAM,WAAW,CAAC;AACvE,OAAO,EACL,YAAY,EACZ,cAAc,EACd,sBAAsB,EACtB,wBAAwB,EACxB,iBAAiB,EACjB,gBAAgB,EAChB,UAAU,EACV,QAAQ,EACT,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,KAAK,aAAa,EAAE,MAAM,SAAS,CAAC"}
|
package/dist/testing/index.js
CHANGED
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
* does not exist in a deployed Worker.
|
|
8
8
|
*
|
|
9
9
|
* **This barrel is the workerd half** — safe to import from a spec. The VCR
|
|
10
|
-
* recorder needs `
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
10
|
+
* recorder needs `node:fs`, so it lives behind `@loopingai/core/testing/node`
|
|
11
|
+
* and must not be re-exported here: pulling it into this graph would drag Node
|
|
12
|
+
* builtins into every spec that wanted a fixture. `vcr-shared.ts` is the seam
|
|
13
|
+
* both realms may load.
|
|
14
14
|
*
|
|
15
15
|
* Three things live here:
|
|
16
16
|
*
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* path can be exercised end to end without a real gateway.
|
|
23
23
|
*/
|
|
24
24
|
export { setupRecording, cassetteNameFor } from "./vcr-spec.js";
|
|
25
|
-
export { VCR_CONTROL_ORIGIN, CASSETTE_NAME_RE } from "./vcr-shared.js";
|
|
25
|
+
export { VCR_CONTROL_ORIGIN, VCR_MARKER_HEADER, CASSETTE_NAME_RE } from "./vcr-shared.js";
|
|
26
26
|
export { FakeSession } from "./fake-session.js";
|
|
27
27
|
export { mockModel, finalReply } from "./mock-model.js";
|
|
28
28
|
export { makeGatewayToken } from "./auth.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EACL,cAAc,EACd,eAAe,EAEhB,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,kBAAkB,EAClB,iBAAiB,EACjB,gBAAgB,EAEjB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,SAAS,EAAE,UAAU,EAAiB,MAAM,iBAAiB,CAAC;AAEvE,OAAO,EAAE,gBAAgB,EAA4B,MAAM,WAAW,CAAC;AACvE,OAAO,EACL,YAAY,EACZ,cAAc,EACd,sBAAsB,EACtB,wBAAwB,EACxB,iBAAiB,EACjB,gBAAgB,EAChB,UAAU,EACV,QAAQ,EACT,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,SAAS,EAAE,aAAa,EAAsB,MAAM,SAAS,CAAC"}
|
package/dist/testing/node.d.ts
CHANGED
|
@@ -2,46 +2,56 @@
|
|
|
2
2
|
* `@loopingai/core/testing/node` — the **Node-realm** half of the VCR harness.
|
|
3
3
|
*
|
|
4
4
|
* Separate from `@loopingai/core/testing` because the two halves cannot share a
|
|
5
|
-
* module graph. This side
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* module graph. This side reaches `node:fs`; the other side runs inside workerd,
|
|
6
|
+
* which has no filesystem. `vcr-shared.ts` is the only thing both may touch, and
|
|
7
|
+
* it is deliberately dependency-free so it can load in either realm.
|
|
8
8
|
*
|
|
9
9
|
* Import this from a Vitest **config** (or anything else running in Node), never
|
|
10
10
|
* from a spec:
|
|
11
11
|
*
|
|
12
12
|
* ```ts
|
|
13
13
|
* // vitest.config.ts
|
|
14
|
-
* import {
|
|
14
|
+
* import { defineConfig } from "vitest/config";
|
|
15
|
+
* import { cloudflareTest } from "@cloudflare/vitest-pool-workers";
|
|
16
|
+
* import path from "node:path";
|
|
17
|
+
* import { createVcr, recordFromEnv } from "@loopingai/core/testing/node";
|
|
18
|
+
*
|
|
19
|
+
* const vcr = createVcr({
|
|
20
|
+
* snapshotsDir: path.resolve(import.meta.dirname, "test/snapshots"),
|
|
21
|
+
* record: recordFromEnv(),
|
|
22
|
+
* // Why a committed cassette is safe, and why playback needs no credentials.
|
|
23
|
+
* excludeHeaders: ["x-api-key", "authorization", "cookie", "set-cookie"]
|
|
24
|
+
* });
|
|
15
25
|
*
|
|
16
26
|
* export default defineConfig({
|
|
17
|
-
* plugins: [
|
|
27
|
+
* plugins: [
|
|
28
|
+
* cloudflareTest({
|
|
29
|
+
* wrangler: { configPath: "./wrangler.jsonc" },
|
|
30
|
+
* miniflare: { outboundService: vcr.outboundService }
|
|
31
|
+
* })
|
|
32
|
+
* ],
|
|
18
33
|
* test: { globalSetup: ["@loopingai/core/testing/vcr-global-setup"] }
|
|
19
34
|
* });
|
|
20
35
|
* ```
|
|
21
36
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* `undici`
|
|
37
|
-
* `
|
|
38
|
-
* second copy, and the fix is to align yours rather than to debug the message.
|
|
39
|
-
*
|
|
40
|
-
* For the common case — wiring cassette flush/teardown — you do not need this
|
|
41
|
-
* subpath at all. Point `globalSetup` at
|
|
42
|
-
* `@loopingai/core/testing/vcr-global-setup`, which is this module's `setup`/
|
|
43
|
-
* `teardown` pair already packaged for Vitest.
|
|
37
|
+
* `RECORD=1` captures live traffic into every cassette a run activates; anything
|
|
38
|
+
* else replays. A host with no active cassette is **blocked**, so a test that
|
|
39
|
+
* was never wired for recording cannot reach the network by accident.
|
|
40
|
+
*
|
|
41
|
+
* ## Version constraints: there are none
|
|
42
|
+
*
|
|
43
|
+
* This used to carry two, both of which failed unreadably, and both of which
|
|
44
|
+
* were consequences of installing the recorder as Miniflare's `fetchMock`:
|
|
45
|
+
* the pool had to be `^0.18` (Miniflare 5 dropped the option, and pool 0.20
|
|
46
|
+
* removed it from its overrides), and your `undici` had to be byte-identical to
|
|
47
|
+
* Miniflare's (`fetchMock` was validated with `instanceof MockAgent`).
|
|
48
|
+
*
|
|
49
|
+
* The recorder is now a plain `outboundService` function — the hook `fetchMock`
|
|
50
|
+
* was sugar over in the first place. It is present and identical in Miniflare 4
|
|
51
|
+
* and 5, it type-checks against both, and it involves no `undici` at all. Core
|
|
52
|
+
* declares no `undici` peer and the pool peer is open.
|
|
44
53
|
*/
|
|
45
|
-
export {
|
|
46
|
-
export {
|
|
54
|
+
export { createVcr, recordFromEnv, closeVcr, type Vcr, type VcrOptions, type VcrRequest, type VcrRequestHeaders, type VcrOutboundService } from "./vcr.js";
|
|
55
|
+
export { Cassette, requestKey, replayable, type CassetteEntry, type CassetteOptions, type RecordedRequest, type RecordedResponse, type ReplayableResponse } from "./vcr-store.js";
|
|
56
|
+
export { VCR_CONTROL_ORIGIN, VCR_MARKER_HEADER, CASSETTE_NAME_RE, type VcrReleaseResult } from "./vcr-shared.js";
|
|
47
57
|
//# sourceMappingURL=node.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../src/testing/node.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../src/testing/node.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoDG;AAEH,OAAO,EACL,SAAS,EACT,aAAa,EACb,QAAQ,EACR,KAAK,GAAG,EACR,KAAK,UAAU,EACf,KAAK,UAAU,EACf,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EACxB,MAAM,UAAU,CAAC;AAElB,OAAO,EACL,QAAQ,EACR,UAAU,EACV,UAAU,EACV,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,EACxB,MAAM,gBAAgB,CAAC;AAExB,OAAO,EACL,kBAAkB,EAClB,iBAAiB,EACjB,gBAAgB,EAChB,KAAK,gBAAgB,EACtB,MAAM,iBAAiB,CAAC"}
|
package/dist/testing/node.js
CHANGED
|
@@ -2,46 +2,56 @@
|
|
|
2
2
|
* `@loopingai/core/testing/node` — the **Node-realm** half of the VCR harness.
|
|
3
3
|
*
|
|
4
4
|
* Separate from `@loopingai/core/testing` because the two halves cannot share a
|
|
5
|
-
* module graph. This side
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* module graph. This side reaches `node:fs`; the other side runs inside workerd,
|
|
6
|
+
* which has no filesystem. `vcr-shared.ts` is the only thing both may touch, and
|
|
7
|
+
* it is deliberately dependency-free so it can load in either realm.
|
|
8
8
|
*
|
|
9
9
|
* Import this from a Vitest **config** (or anything else running in Node), never
|
|
10
10
|
* from a spec:
|
|
11
11
|
*
|
|
12
12
|
* ```ts
|
|
13
13
|
* // vitest.config.ts
|
|
14
|
-
* import {
|
|
14
|
+
* import { defineConfig } from "vitest/config";
|
|
15
|
+
* import { cloudflareTest } from "@cloudflare/vitest-pool-workers";
|
|
16
|
+
* import path from "node:path";
|
|
17
|
+
* import { createVcr, recordFromEnv } from "@loopingai/core/testing/node";
|
|
18
|
+
*
|
|
19
|
+
* const vcr = createVcr({
|
|
20
|
+
* snapshotsDir: path.resolve(import.meta.dirname, "test/snapshots"),
|
|
21
|
+
* record: recordFromEnv(),
|
|
22
|
+
* // Why a committed cassette is safe, and why playback needs no credentials.
|
|
23
|
+
* excludeHeaders: ["x-api-key", "authorization", "cookie", "set-cookie"]
|
|
24
|
+
* });
|
|
15
25
|
*
|
|
16
26
|
* export default defineConfig({
|
|
17
|
-
* plugins: [
|
|
27
|
+
* plugins: [
|
|
28
|
+
* cloudflareTest({
|
|
29
|
+
* wrangler: { configPath: "./wrangler.jsonc" },
|
|
30
|
+
* miniflare: { outboundService: vcr.outboundService }
|
|
31
|
+
* })
|
|
32
|
+
* ],
|
|
18
33
|
* test: { globalSetup: ["@loopingai/core/testing/vcr-global-setup"] }
|
|
19
34
|
* });
|
|
20
35
|
* ```
|
|
21
36
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* `undici`
|
|
37
|
-
* `
|
|
38
|
-
* second copy, and the fix is to align yours rather than to debug the message.
|
|
39
|
-
*
|
|
40
|
-
* For the common case — wiring cassette flush/teardown — you do not need this
|
|
41
|
-
* subpath at all. Point `globalSetup` at
|
|
42
|
-
* `@loopingai/core/testing/vcr-global-setup`, which is this module's `setup`/
|
|
43
|
-
* `teardown` pair already packaged for Vitest.
|
|
37
|
+
* `RECORD=1` captures live traffic into every cassette a run activates; anything
|
|
38
|
+
* else replays. A host with no active cassette is **blocked**, so a test that
|
|
39
|
+
* was never wired for recording cannot reach the network by accident.
|
|
40
|
+
*
|
|
41
|
+
* ## Version constraints: there are none
|
|
42
|
+
*
|
|
43
|
+
* This used to carry two, both of which failed unreadably, and both of which
|
|
44
|
+
* were consequences of installing the recorder as Miniflare's `fetchMock`:
|
|
45
|
+
* the pool had to be `^0.18` (Miniflare 5 dropped the option, and pool 0.20
|
|
46
|
+
* removed it from its overrides), and your `undici` had to be byte-identical to
|
|
47
|
+
* Miniflare's (`fetchMock` was validated with `instanceof MockAgent`).
|
|
48
|
+
*
|
|
49
|
+
* The recorder is now a plain `outboundService` function — the hook `fetchMock`
|
|
50
|
+
* was sugar over in the first place. It is present and identical in Miniflare 4
|
|
51
|
+
* and 5, it type-checks against both, and it involves no `undici` at all. Core
|
|
52
|
+
* declares no `undici` peer and the pool peer is open.
|
|
44
53
|
*/
|
|
45
|
-
export {
|
|
46
|
-
export {
|
|
54
|
+
export { createVcr, recordFromEnv, closeVcr } from "./vcr.js";
|
|
55
|
+
export { Cassette, requestKey, replayable } from "./vcr-store.js";
|
|
56
|
+
export { VCR_CONTROL_ORIGIN, VCR_MARKER_HEADER, CASSETTE_NAME_RE } from "./vcr-shared.js";
|
|
47
57
|
//# sourceMappingURL=node.js.map
|
package/dist/testing/node.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node.js","sourceRoot":"","sources":["../../src/testing/node.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"node.js","sourceRoot":"","sources":["../../src/testing/node.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoDG;AAEH,OAAO,EACL,SAAS,EACT,aAAa,EACb,QAAQ,EAMT,MAAM,UAAU,CAAC;AAElB,OAAO,EACL,QAAQ,EACR,UAAU,EACV,UAAU,EAMX,MAAM,gBAAgB,CAAC;AAExB,OAAO,EACL,kBAAkB,EAClB,iBAAiB,EACjB,gBAAgB,EAEjB,MAAM,iBAAiB,CAAC"}
|
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vitest `globalSetup`: the exported teardown runs after all tests but before
|
|
3
|
+
* Vite tears down its server, so it is the last point at which a cassette can
|
|
4
|
+
* still be written.
|
|
5
|
+
*
|
|
6
|
+
* Belt and braces rather than load-bearing — each cassette is already flushed
|
|
7
|
+
* when its test releases it, so a run that ends normally has nothing left to do
|
|
8
|
+
* here, and a run that dies mid-test keeps everything recorded up to that point.
|
|
9
|
+
* (Under the old undici recorder this step was mandatory: it held real sockets
|
|
10
|
+
* and a self-refreshing auto-flush timer open, and a `RECORD=1` run hung on
|
|
11
|
+
* "close timed out" without it. Neither exists now.)
|
|
12
|
+
*/
|
|
1
13
|
export declare function setup(): void;
|
|
2
14
|
export declare function teardown(): Promise<void>;
|
|
3
15
|
//# sourceMappingURL=vcr-global-setup.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vcr-global-setup.d.ts","sourceRoot":"","sources":["../../src/testing/vcr-global-setup.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"vcr-global-setup.d.ts","sourceRoot":"","sources":["../../src/testing/vcr-global-setup.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;GAWG;AACH,wBAAgB,KAAK,IAAI,IAAI,CAAG;AAEhC,wBAAsB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAE9C"}
|
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
import { closeVcr } from "./vcr.js";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Vitest `globalSetup`: the exported teardown runs after all tests but before
|
|
4
|
+
* Vite tears down its server, so it is the last point at which a cassette can
|
|
5
|
+
* still be written.
|
|
6
|
+
*
|
|
7
|
+
* Belt and braces rather than load-bearing — each cassette is already flushed
|
|
8
|
+
* when its test releases it, so a run that ends normally has nothing left to do
|
|
9
|
+
* here, and a run that dies mid-test keeps everything recorded up to that point.
|
|
10
|
+
* (Under the old undici recorder this step was mandatory: it held real sockets
|
|
11
|
+
* and a self-refreshing auto-flush timer open, and a `RECORD=1` run hung on
|
|
12
|
+
* "close timed out" without it. Neither exists now.)
|
|
13
|
+
*/
|
|
5
14
|
export function setup() { }
|
|
6
15
|
export async function teardown() {
|
|
7
16
|
await closeVcr();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vcr-global-setup.js","sourceRoot":"","sources":["../../src/testing/vcr-global-setup.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAEpC
|
|
1
|
+
{"version":3,"file":"vcr-global-setup.js","sourceRoot":"","sources":["../../src/testing/vcr-global-setup.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAEpC;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,KAAK,KAAU,CAAC;AAEhC,MAAM,CAAC,KAAK,UAAU,QAAQ;IAC5B,MAAM,QAAQ,EAAE,CAAC;AACnB,CAAC"}
|
|
@@ -1,16 +1,34 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Constants shared between the two sides of the VCR harness that must NOT import
|
|
3
|
-
* each other: the Node-side
|
|
4
|
-
* worker-side spec helper (`vcr-spec.ts`, runs in
|
|
5
|
-
* dependency-free so both realms can load it.
|
|
3
|
+
* each other: the Node-side recorder (`vcr.ts` / `vcr-store.ts`, which reach
|
|
4
|
+
* `node:fs`) and the worker-side spec helper (`vcr-spec.ts`, which runs in
|
|
5
|
+
* workerd). Keep this file dependency-free so both realms can load it.
|
|
6
6
|
*/
|
|
7
7
|
/**
|
|
8
8
|
* Reserved origin for the in-band control channel. A recorded spec `fetch`es
|
|
9
|
-
* here
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* here — through the same Miniflare `outboundService` every worker fetch already
|
|
10
|
+
* flows through — to tell the Node-side recorder which cassette is active for
|
|
11
|
+
* the current test. See `setupRecording` (vcr-spec.ts) and `createVcr` (vcr.ts).
|
|
12
|
+
* Never a real network host.
|
|
12
13
|
*/
|
|
13
14
|
export declare const VCR_CONTROL_ORIGIN = "https://vcr.internal";
|
|
15
|
+
/**
|
|
16
|
+
* Stamped on every response the recorder produces.
|
|
17
|
+
*
|
|
18
|
+
* The point is to prove the recorder is *installed*. A Miniflare option that no
|
|
19
|
+
* longer exists is silently ignored rather than rejected — which is exactly how
|
|
20
|
+
* a harness still wired to `fetchMock` on pool 0.20 failed: nothing intercepted
|
|
21
|
+
* anything, every request escaped to the real network, and each one died as
|
|
22
|
+
* `internal error; reference = …` naming nothing. Checking for this header on
|
|
23
|
+
* the control response turns that whole class of failure into one named error
|
|
24
|
+
* before any test runs.
|
|
25
|
+
*/
|
|
26
|
+
export declare const VCR_MARKER_HEADER = "x-vcr";
|
|
27
|
+
/** Body of a successful `POST /release`: what the recorder could not serve. */
|
|
28
|
+
export interface VcrReleaseResult {
|
|
29
|
+
/** `"<METHOD> <url>"` for each blocked or unmatched request in the test. */
|
|
30
|
+
misses: string[];
|
|
31
|
+
}
|
|
14
32
|
/**
|
|
15
33
|
* Cassette filenames are derived from kebab-cased file + describe + test names
|
|
16
34
|
* (see `cassetteNameFor`), so they are always lowercase `a-z0-9-` plus the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vcr-shared.d.ts","sourceRoot":"","sources":["../../src/testing/vcr-shared.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH
|
|
1
|
+
{"version":3,"file":"vcr-shared.d.ts","sourceRoot":"","sources":["../../src/testing/vcr-shared.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,kBAAkB,yBAAyB,CAAC;AAEzD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,iBAAiB,UAAU,CAAC;AAEzC,+EAA+E;AAC/E,MAAM,WAAW,gBAAgB;IAC/B,4EAA4E;IAC5E,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,QAAiC,CAAC"}
|
|
@@ -1,16 +1,29 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Constants shared between the two sides of the VCR harness that must NOT import
|
|
3
|
-
* each other: the Node-side
|
|
4
|
-
* worker-side spec helper (`vcr-spec.ts`, runs in
|
|
5
|
-
* dependency-free so both realms can load it.
|
|
3
|
+
* each other: the Node-side recorder (`vcr.ts` / `vcr-store.ts`, which reach
|
|
4
|
+
* `node:fs`) and the worker-side spec helper (`vcr-spec.ts`, which runs in
|
|
5
|
+
* workerd). Keep this file dependency-free so both realms can load it.
|
|
6
6
|
*/
|
|
7
7
|
/**
|
|
8
8
|
* Reserved origin for the in-band control channel. A recorded spec `fetch`es
|
|
9
|
-
* here
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* here — through the same Miniflare `outboundService` every worker fetch already
|
|
10
|
+
* flows through — to tell the Node-side recorder which cassette is active for
|
|
11
|
+
* the current test. See `setupRecording` (vcr-spec.ts) and `createVcr` (vcr.ts).
|
|
12
|
+
* Never a real network host.
|
|
12
13
|
*/
|
|
13
14
|
export const VCR_CONTROL_ORIGIN = "https://vcr.internal";
|
|
15
|
+
/**
|
|
16
|
+
* Stamped on every response the recorder produces.
|
|
17
|
+
*
|
|
18
|
+
* The point is to prove the recorder is *installed*. A Miniflare option that no
|
|
19
|
+
* longer exists is silently ignored rather than rejected — which is exactly how
|
|
20
|
+
* a harness still wired to `fetchMock` on pool 0.20 failed: nothing intercepted
|
|
21
|
+
* anything, every request escaped to the real network, and each one died as
|
|
22
|
+
* `internal error; reference = …` naming nothing. Checking for this header on
|
|
23
|
+
* the control response turns that whole class of failure into one named error
|
|
24
|
+
* before any test runs.
|
|
25
|
+
*/
|
|
26
|
+
export const VCR_MARKER_HEADER = "x-vcr";
|
|
14
27
|
/**
|
|
15
28
|
* Cassette filenames are derived from kebab-cased file + describe + test names
|
|
16
29
|
* (see `cassetteNameFor`), so they are always lowercase `a-z0-9-` plus the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vcr-shared.js","sourceRoot":"","sources":["../../src/testing/vcr-shared.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH
|
|
1
|
+
{"version":3,"file":"vcr-shared.js","sourceRoot":"","sources":["../../src/testing/vcr-shared.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,sBAAsB,CAAC;AAEzD;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,OAAO,CAAC;AAQzC;;;;;GAKG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,8BAA8B,CAAC"}
|
|
@@ -7,11 +7,20 @@ import type { RunnerTestCase } from "vitest";
|
|
|
7
7
|
* Exported for debugging / the cassette-rename step.
|
|
8
8
|
*/
|
|
9
9
|
export declare function cassetteNameFor(task: RunnerTestCase): string;
|
|
10
|
+
export interface SetupRecordingOptions {
|
|
11
|
+
/**
|
|
12
|
+
* Let a test finish even though the recorder could not serve one of its
|
|
13
|
+
* requests. Only for a spec that deliberately exercises a failing `fetch`;
|
|
14
|
+
* the default is to fail, because a silent miss reads as a broken assertion
|
|
15
|
+
* several frames from its cause.
|
|
16
|
+
*/
|
|
17
|
+
allowMisses?: boolean;
|
|
18
|
+
}
|
|
10
19
|
/**
|
|
11
20
|
* Call once at the top of a recorded spec file (outside any `describe`). Adds
|
|
12
21
|
* per-test hooks that activate the test's cassette before it runs and release
|
|
13
22
|
* it after. A missing cassette **fails** the test with instructions to record
|
|
14
23
|
* (never skips — CI must go red so the gap is visible).
|
|
15
24
|
*/
|
|
16
|
-
export declare function setupRecording(): void;
|
|
25
|
+
export declare function setupRecording(options?: SetupRecordingOptions): void;
|
|
17
26
|
//# sourceMappingURL=vcr-spec.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vcr-spec.d.ts","sourceRoot":"","sources":["../../src/testing/vcr-spec.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAmB,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"vcr-spec.d.ts","sourceRoot":"","sources":["../../src/testing/vcr-spec.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAmB,MAAM,QAAQ,CAAC;AAsD9D;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,cAAc,GAAG,MAAM,CAW5D;AAED,MAAM,WAAW,qBAAqB;IACpC;;;;;OAKG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAgBD;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,OAAO,GAAE,qBAA0B,GAAG,IAAI,CAkDxE"}
|
package/dist/testing/vcr-spec.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { beforeEach, afterEach } from "vitest";
|
|
2
|
-
import { VCR_CONTROL_ORIGIN } from "./vcr-shared.js";
|
|
2
|
+
import { VCR_CONTROL_ORIGIN, VCR_MARKER_HEADER } from "./vcr-shared.js";
|
|
3
3
|
/**
|
|
4
4
|
* Worker-side half of the VCR harness. `setupRecording()` is the *only* thing a
|
|
5
5
|
* recorded spec wires up: one call at the top of the file gives every `it` its
|
|
6
6
|
* own cassette, auto-named from the file + describe + test names, recorded under
|
|
7
7
|
* `RECORD=1` and replayed otherwise. No per-recipe config, no registry.
|
|
8
8
|
*
|
|
9
|
-
* How it reaches the Node-side
|
|
10
|
-
* the active cassette is announced over an in-band control channel — a
|
|
11
|
-
* to {@link VCR_CONTROL_ORIGIN} that `
|
|
12
|
-
*
|
|
9
|
+
* How it reaches the Node-side recorder: specs run in workerd (no filesystem),
|
|
10
|
+
* so the active cassette is announced over an in-band control channel — a
|
|
11
|
+
* `fetch` to {@link VCR_CONTROL_ORIGIN} that `createVcr` (vcr.ts) answers from
|
|
12
|
+
* the same Miniflare `outboundService` every worker fetch already flows through.
|
|
13
13
|
*/
|
|
14
14
|
const kebab = (s) => s
|
|
15
15
|
.toLowerCase()
|
|
@@ -60,18 +60,35 @@ export function cassetteNameFor(task) {
|
|
|
60
60
|
}
|
|
61
61
|
return [rel, ...suites, task.name].map(kebab).join("--") + ".snapshot.json";
|
|
62
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* The recorder answers every control request with {@link VCR_MARKER_HEADER}, so
|
|
65
|
+
* its absence — or a `fetch` that rejects outright, which is what a request to
|
|
66
|
+
* a host that does not resolve does — means nothing is intercepting outbound
|
|
67
|
+
* traffic at all.
|
|
68
|
+
*/
|
|
69
|
+
const NOT_INSTALLED = `The VCR recorder is not installed, so ${VCR_CONTROL_ORIGIN} went to the ` +
|
|
70
|
+
`real network. Wire it into your vitest config:\n\n` +
|
|
71
|
+
` const vcr = createVcr({ snapshotsDir, record: recordFromEnv() });\n` +
|
|
72
|
+
` cloudflareTest({ miniflare: { outboundService: vcr.outboundService } })\n\n` +
|
|
73
|
+
`(\`fetchMock\` is not it: pool 0.20 removed the option, and an unknown key ` +
|
|
74
|
+
`in \`miniflare\` is ignored rather than rejected.)`;
|
|
63
75
|
/**
|
|
64
76
|
* Call once at the top of a recorded spec file (outside any `describe`). Adds
|
|
65
77
|
* per-test hooks that activate the test's cassette before it runs and release
|
|
66
78
|
* it after. A missing cassette **fails** the test with instructions to record
|
|
67
79
|
* (never skips — CI must go red so the gap is visible).
|
|
68
80
|
*/
|
|
69
|
-
export function setupRecording() {
|
|
81
|
+
export function setupRecording(options = {}) {
|
|
70
82
|
beforeEach(async (ctx) => {
|
|
71
83
|
const cassette = cassetteNameFor(ctx.task);
|
|
72
84
|
const res = await fetch(`${VCR_CONTROL_ORIGIN}/use?cassette=${cassette}`, {
|
|
73
85
|
method: "POST"
|
|
86
|
+
}).catch(() => {
|
|
87
|
+
throw new Error(NOT_INSTALLED);
|
|
74
88
|
});
|
|
89
|
+
if (res.headers.get(VCR_MARKER_HEADER) === null) {
|
|
90
|
+
throw new Error(NOT_INSTALLED);
|
|
91
|
+
}
|
|
75
92
|
if (res.status === 404) {
|
|
76
93
|
throw new Error(`No VCR cassette "${cassette}". Record it with \`RECORD=1\` ` +
|
|
77
94
|
`(add \`-t "${ctx.task.name}"\` to record only this test), which needs ` +
|
|
@@ -79,7 +96,7 @@ export function setupRecording() {
|
|
|
79
96
|
}
|
|
80
97
|
if (res.status === 409) {
|
|
81
98
|
throw new Error(`VCR cassette "${cassette}" could not activate: another recorded test is ` +
|
|
82
|
-
`already using the shared
|
|
99
|
+
`already using the shared recorder. Recorded specs must not run in ` +
|
|
83
100
|
`parallel — keep them sequential.`);
|
|
84
101
|
}
|
|
85
102
|
if (!res.ok) {
|
|
@@ -87,7 +104,22 @@ export function setupRecording() {
|
|
|
87
104
|
}
|
|
88
105
|
});
|
|
89
106
|
afterEach(async () => {
|
|
90
|
-
await fetch(`${VCR_CONTROL_ORIGIN}/release`, {
|
|
107
|
+
const res = await fetch(`${VCR_CONTROL_ORIGIN}/release`, {
|
|
108
|
+
method: "POST"
|
|
109
|
+
});
|
|
110
|
+
if (options.allowMisses === true || !res.ok)
|
|
111
|
+
return;
|
|
112
|
+
const { misses } = (await res.json());
|
|
113
|
+
if (misses.length === 0)
|
|
114
|
+
return;
|
|
115
|
+
// A miss cannot reject the Worker's own `fetch()` — Miniflare turns anything
|
|
116
|
+
// an outbound service throws into a 500 — so the request under test sees a
|
|
117
|
+
// 500 body and fails somewhere downstream, if at all. Reporting it here is
|
|
118
|
+
// what makes the *cause* the failure.
|
|
119
|
+
throw new Error(`VCR could not serve ${misses.length} request(s) in this test:\n` +
|
|
120
|
+
misses.map((m) => ` • ${m}`).join("\n") +
|
|
121
|
+
`\nRe-record with \`RECORD=1\`, or pass ` +
|
|
122
|
+
`\`setupRecording({ allowMisses: true })\` if the failure is the point.`);
|
|
91
123
|
});
|
|
92
124
|
}
|
|
93
125
|
//# sourceMappingURL=vcr-spec.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vcr-spec.js","sourceRoot":"","sources":["../../src/testing/vcr-spec.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AAE/C,OAAO,
|
|
1
|
+
{"version":3,"file":"vcr-spec.js","sourceRoot":"","sources":["../../src/testing/vcr-spec.ts"],"names":[],"mappings":"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"}
|