@zerotal/testing 1.14.3 → 1.15.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/CHANGELOG.md CHANGED
@@ -8,6 +8,20 @@ follows the Zerotal monorepo's unified versioning.
8
8
 
9
9
  ## [Unreleased]
10
10
 
11
+ ## [1.15.0] — 2026-09-04
12
+
13
+ ### Fixed
14
+
15
+ - **A test that stubbed `globalThis.fetch` also answered the test client's own requests.**
16
+ `TestApp` drives the app over a loopback server through whatever `fetch` was current, so
17
+ faking an outbound integration — the ordinary way to test one — made unrelated route tests
18
+ fail with `connection refused`, or come back holding the third-party's JSON. The failure
19
+ presents as "my test client cannot reach my app", which sends you looking at the server.
20
+
21
+ `TestApp` now holds the real `fetch`, captured at module load before any test can replace
22
+ it. An outbound stub cannot intercept an inbound request, and no passthrough is needed for
23
+ the test client.
24
+
11
25
  ## [1.11.0] — 2026-08-31
12
26
 
13
27
  ### Changed
package/api-surface.md CHANGED
@@ -3,6 +3,9 @@
3
3
  <!-- AUTO-GENERATED by scripts/api-surface.ts. Do not edit by hand.
4
4
  Run `bun run api:surface` to regenerate after an intentional API change. -->
5
5
 
6
+ > Not a guide — this is a flat, alphabetical snapshot used to detect unintended
7
+ > API changes. For how to use these, read the [documentation](https://zerotal.dev/docs).
8
+
6
9
  ## . `(./src/index.ts)`
7
10
 
8
11
  class EventFake = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zerotal/testing",
3
- "version": "1.14.3",
3
+ "version": "1.15.0",
4
4
  "license": "MIT",
5
5
  "maturity": "stable",
6
6
  "private": false,
@@ -32,14 +32,14 @@
32
32
  "typecheck": "tsc --noEmit"
33
33
  },
34
34
  "dependencies": {
35
- "@zerotal/core": "1.14.3",
36
- "@zerotal/orm": "1.14.3",
37
- "@zerotal/queue": "1.14.3",
38
- "@zerotal/notifications": "1.14.3"
35
+ "@zerotal/core": "1.15.0",
36
+ "@zerotal/orm": "1.15.0",
37
+ "@zerotal/queue": "1.15.0",
38
+ "@zerotal/notifications": "1.15.0"
39
39
  },
40
40
  "devDependencies": {
41
41
  "typescript": "^5.8.0",
42
- "@zerotal/session": "1.14.3"
42
+ "@zerotal/session": "1.15.0"
43
43
  },
44
44
  "description": "Testing utilities for Zerotal — an in-process test app, HTTP helpers, and database refresh.",
45
45
  "keywords": [
package/src/TestApp.ts CHANGED
@@ -5,6 +5,24 @@ import { resetTestState } from "./resetTestState.ts";
5
5
 
6
6
  type BunServer = { port: number; stop(drain?: boolean): void };
7
7
 
8
+ /**
9
+ * The real `fetch`, captured at module load — before any test has had a chance
10
+ * to replace `globalThis.fetch`.
11
+ *
12
+ * The test client talks to the app over a real loopback server, so it used to
13
+ * call whatever `fetch` was current. A test that stubs `globalThis.fetch` to
14
+ * fake an outbound integration therefore also answered the test client's own
15
+ * requests: three unrelated route tests fail with `connection refused` or come
16
+ * back holding the third-party's JSON, and the failure reads as "my test client
17
+ * cannot reach my app", which sends you looking at the server.
18
+ *
19
+ * Binding it here means an outbound stub can never intercept an inbound request.
20
+ * A test that genuinely wants to intercept the test client has `app.app` and the
21
+ * router; a test that wants to stub an integration should not have to know this
22
+ * exists at all.
23
+ */
24
+ const _fetch: typeof globalThis.fetch = globalThis.fetch.bind(globalThis);
25
+
8
26
  /**
9
27
  * Wrapper around a running Application that provides an HTTP test client.
10
28
  * Obtain an instance via createTestApp().
@@ -251,7 +269,7 @@ export class TestApp {
251
269
 
252
270
  if (this._errors) this._errors.lastError = undefined;
253
271
 
254
- let res = await fetch(full, { ...init, headers, redirect: "manual" });
272
+ let res = await _fetch(full, { ...init, headers, redirect: "manual" });
255
273
 
256
274
  if (this._followRedirects) {
257
275
  let hops = 0;
@@ -267,7 +285,7 @@ export class TestApp {
267
285
  const cookiePair = setCookie.split(";")[0] ?? "";
268
286
  nextHeaders.set("Cookie", existing ? `${existing}; ${cookiePair}` : cookiePair);
269
287
  }
270
- res = await fetch(nextUrl, { method: "GET", headers: nextHeaders, redirect: "manual" });
288
+ res = await _fetch(nextUrl, { method: "GET", headers: nextHeaders, redirect: "manual" });
271
289
  hops++;
272
290
  }
273
291
  }