test: close the empty-array hole in the e2e unstubbed-request guard (closes #187) #267

Merged
clawbot merged 1 commits from test/issue-187-empty-batch-guard into next 2026-08-12 11:50:39 +02:00
Collaborator

Closes #187.

The hole

In tests/e2e/network.js the guard reporting unrecognised POST bodies ended in
batch.every(...). Array.prototype.every is vacuously true on an empty array,
so a POST with body [] was fulfilled with 200 [] instead of being reported.
That is a hole in the one mechanism whose entire job is to make unrecognised
outbound traffic fail the suite rather than pass silently.

The fix is one clause: batch.length === 0 is now rejected alongside the
existing checks. No real JSON-RPC batch is empty, so nothing legitimate is
caught by it. The guard got stricter; nothing was loosened, and ALLOWED_ERRORS
is still []. TRAILING_WATCH_MS and the interception canary were not touched.

Demonstrated by execution

A throwaway probe (four tests, each firing one POST the harness does not stub,
asserting nothing — the run itself is the assertion) was added, both runs
captured, then the probe removed.

GREEN BEFORE[] sails through:

ok 27 - ConfirmTx reports a failed ERC-20 estimate as unknown, not as a fee problem (#238)
# probe [] -> fulfilled
ok 28 - PROBE empty-array batch []
# probe bodyless -> rejected: Failed to fetch
not ok 29 - PROBE bodyless POST
  uncaught browser errors during this test
  network: unstubbed request: POST https://e2e-probe.invalid/rpc
  console.error: Failed to load resource: net::ERR_FAILED
# probe scalar -> rejected: Failed to fetch
not ok 30 - PROBE scalar JSON body
  uncaught browser errors during this test
  network: unstubbed request: POST https://e2e-probe.invalid/rpc
  console.error: Failed to load resource: net::ERR_FAILED
# probe [null] -> rejected: Failed to fetch
not ok 31 - PROBE [null] batch
  uncaught browser errors during this test
  network: unstubbed request: POST https://e2e-probe.invalid/rpc
  console.error: Failed to load resource: net::ERR_FAILED
# 28/31 tests passed
# FAILED

RED AFTER — same probe, same run, with the fix:

ok 27 - ConfirmTx reports a failed ERC-20 estimate as unknown, not as a fee problem (#238)
# probe [] -> rejected: Failed to fetch
not ok 28 - PROBE empty-array batch []
  uncaught browser errors during this test
  network: unstubbed request: POST https://e2e-probe.invalid/rpc
  console.error: Failed to load resource: net::ERR_FAILED
# probe bodyless -> rejected: Failed to fetch
not ok 29 - PROBE bodyless POST
  uncaught browser errors during this test
  network: unstubbed request: POST https://e2e-probe.invalid/rpc
  console.error: Failed to load resource: net::ERR_FAILED
# probe scalar -> rejected: Failed to fetch
not ok 30 - PROBE scalar JSON body
  uncaught browser errors during this test
  network: unstubbed request: POST https://e2e-probe.invalid/rpc
  console.error: Failed to load resource: net::ERR_FAILED
# probe [null] -> rejected: Failed to fetch
not ok 31 - PROBE [null] batch
  uncaught browser errors during this test
  network: unstubbed request: POST https://e2e-probe.invalid/rpc
  console.error: Failed to load resource: net::ERR_FAILED
# 27/31 tests passed
# FAILED

# probe [] -> fulfilled becoming rejected: Failed to fetch, and ok 28
becoming not ok 28 with network: unstubbed request: POST, is the whole
change.

Siblings still report

Visible in both captures above: bodyless POST, scalar JSON body and
[null] batch are reported and fail in the before run and in the after run
alike. No regression.

Legitimate stubbed calls unaffected

Tests 1-27 stayed green throughout, including the ConfirmTx section whose
fixtures widened the RPC stub (eth_getBlockByNumber for any block parameter,
eth_getCode). Nothing legitimate started being reported as unstubbed.

The corrected comment

The old comment claimed postData() returns null for a body Playwright cannot
decode as UTF-8, citing sendBeacon with a Blob. That is wrong.
playwright-core@1.56.0 (lib/client/network.js:89) does
buffer.toString("utf-8") || null, so a binary body is decoded lossily into
mojibake rather than yielding null. The guard was load-bearing either way, but
for a different reason than stated. It now reads:

    // Anything that is not a JSON-RPC object, or a NON-EMPTY batch of
    // them, is not RPC at all and must be reported like any other
    // unrecognised outbound traffic rather than dereferenced.
    //
    // The length check is not decoration: every() is vacuously true on an
    // empty array, so without it a POST with body [] was answered 200 []
    // and escaped the guard entirely (issue #187). No real batch is empty,
    // so nothing legitimate is caught by it.
    //
    // Two distinct paths land a non-RPC body here, and neither is an
    // empty-string special case. playwright-core's postData() is
    // `buffer.toString("utf-8") || null`, so an absent or empty body
    // decodes to null, JSON.parse("null") yields null, and the type guard
    // below reports it. A binary body is instead decoded LOSSILY into
    // mojibake — not null — which is not valid JSON, so the catch above
    // reports that one. Both end up reported; only the route differs.

Verification

  • make check: green. 25 test suites, 576 tests passed; test-verify-build
    18 cases passed; prettier --check . clean.
  • make test-e2e: green, # 27/27 tests passed, run after the probe was
    removed and after the rebase onto next.
  • make fmt run; git status clean before committing. Rebased onto
    origin/next at 5af89a1 immediately before pushing.

Change is confined to tests/e2e/network.js (plus the TODO.md entry).

Closes [#187](https://git.eeqj.de/sneak/AutistMask/issues/187). ## The hole In `tests/e2e/network.js` the guard reporting unrecognised POST bodies ended in `batch.every(...)`. `Array.prototype.every` is vacuously true on an empty array, so a POST with body `[]` was fulfilled with `200 []` instead of being reported. That is a hole in the one mechanism whose entire job is to make unrecognised outbound traffic fail the suite rather than pass silently. The fix is one clause: `batch.length === 0` is now rejected alongside the existing checks. No real JSON-RPC batch is empty, so nothing legitimate is caught by it. The guard got stricter; nothing was loosened, and `ALLOWED_ERRORS` is still `[]`. `TRAILING_WATCH_MS` and the interception canary were not touched. ## Demonstrated by execution A throwaway probe (four tests, each firing one POST the harness does not stub, asserting nothing — the run itself is the assertion) was added, both runs captured, then the probe removed. **GREEN BEFORE** — `[]` sails through: ``` ok 27 - ConfirmTx reports a failed ERC-20 estimate as unknown, not as a fee problem (#238) # probe [] -> fulfilled ok 28 - PROBE empty-array batch [] # probe bodyless -> rejected: Failed to fetch not ok 29 - PROBE bodyless POST uncaught browser errors during this test network: unstubbed request: POST https://e2e-probe.invalid/rpc console.error: Failed to load resource: net::ERR_FAILED # probe scalar -> rejected: Failed to fetch not ok 30 - PROBE scalar JSON body uncaught browser errors during this test network: unstubbed request: POST https://e2e-probe.invalid/rpc console.error: Failed to load resource: net::ERR_FAILED # probe [null] -> rejected: Failed to fetch not ok 31 - PROBE [null] batch uncaught browser errors during this test network: unstubbed request: POST https://e2e-probe.invalid/rpc console.error: Failed to load resource: net::ERR_FAILED # 28/31 tests passed # FAILED ``` **RED AFTER** — same probe, same run, with the fix: ``` ok 27 - ConfirmTx reports a failed ERC-20 estimate as unknown, not as a fee problem (#238) # probe [] -> rejected: Failed to fetch not ok 28 - PROBE empty-array batch [] uncaught browser errors during this test network: unstubbed request: POST https://e2e-probe.invalid/rpc console.error: Failed to load resource: net::ERR_FAILED # probe bodyless -> rejected: Failed to fetch not ok 29 - PROBE bodyless POST uncaught browser errors during this test network: unstubbed request: POST https://e2e-probe.invalid/rpc console.error: Failed to load resource: net::ERR_FAILED # probe scalar -> rejected: Failed to fetch not ok 30 - PROBE scalar JSON body uncaught browser errors during this test network: unstubbed request: POST https://e2e-probe.invalid/rpc console.error: Failed to load resource: net::ERR_FAILED # probe [null] -> rejected: Failed to fetch not ok 31 - PROBE [null] batch uncaught browser errors during this test network: unstubbed request: POST https://e2e-probe.invalid/rpc console.error: Failed to load resource: net::ERR_FAILED # 27/31 tests passed # FAILED ``` `# probe [] -> fulfilled` becoming `rejected: Failed to fetch`, and `ok 28` becoming `not ok 28` with `network: unstubbed request: POST`, is the whole change. ### Siblings still report Visible in both captures above: **bodyless POST**, **scalar JSON body** and **`[null]` batch** are reported and fail in the before run and in the after run alike. No regression. ### Legitimate stubbed calls unaffected Tests 1-27 stayed green throughout, including the ConfirmTx section whose fixtures widened the RPC stub (`eth_getBlockByNumber` for any block parameter, `eth_getCode`). Nothing legitimate started being reported as unstubbed. ## The corrected comment The old comment claimed `postData()` returns `null` for a body Playwright cannot decode as UTF-8, citing `sendBeacon` with a Blob. That is wrong. `playwright-core@1.56.0` (`lib/client/network.js:89`) does `buffer.toString("utf-8") || null`, so a binary body is decoded **lossily** into mojibake rather than yielding `null`. The guard was load-bearing either way, but for a different reason than stated. It now reads: ``` // Anything that is not a JSON-RPC object, or a NON-EMPTY batch of // them, is not RPC at all and must be reported like any other // unrecognised outbound traffic rather than dereferenced. // // The length check is not decoration: every() is vacuously true on an // empty array, so without it a POST with body [] was answered 200 [] // and escaped the guard entirely (issue #187). No real batch is empty, // so nothing legitimate is caught by it. // // Two distinct paths land a non-RPC body here, and neither is an // empty-string special case. playwright-core's postData() is // `buffer.toString("utf-8") || null`, so an absent or empty body // decodes to null, JSON.parse("null") yields null, and the type guard // below reports it. A binary body is instead decoded LOSSILY into // mojibake — not null — which is not valid JSON, so the catch above // reports that one. Both end up reported; only the route differs. ``` ## Verification - `make check`: green. 25 test suites, 576 tests passed; `test-verify-build` 18 cases passed; `prettier --check .` clean. - `make test-e2e`: green, `# 27/27 tests passed`, run after the probe was removed and after the rebase onto `next`. - `make fmt` run; `git status` clean before committing. Rebased onto `origin/next` at `5af89a1` immediately before pushing. Change is confined to `tests/e2e/network.js` (plus the `TODO.md` entry).
clawbot added 1 commit 2026-08-12 11:43:14 +02:00
test: close the empty-array hole in the e2e unstubbed-request guard (closes #187)
All checks were successful
check / check (push) Successful in 42s
2048e7695f
batch.every() is vacuously true on an empty array, so a POST with body []
was fulfilled with 200 [] instead of being reported as an unstubbed
request. The one mechanism whose job is to fail the suite on unrecognised
outbound traffic let it through. Reject an empty batch explicitly; no real
JSON-RPC batch is empty, so nothing legitimate is caught by it.

Demonstrated by execution with a throwaway probe firing four POSTs the
harness does not stub. Before: [] fulfilled and its probe passed, while
the bodyless, scalar-JSON and [null] probes were reported and failed.
After: all four reported and failed, with tests 1-27 still green.

Also corrects the comment above the guard. postData() is
buffer.toString("utf-8") || null in playwright-core, so a binary body is
decoded lossily into invalid JSON and reported by the catch, not returned
as null; only an absent or empty body yields null and reaches the type
guard. Both paths report, but the stated reason was wrong.
clawbot added the needs-review label 2026-08-12 11:43:22 +02:00
clawbot self-assigned this 2026-08-12 11:43:24 +02:00
Author
Collaborator

PASS — independently reproduced both directions in my own clone ([] fulfilled 200 [] on origin/next, reported as unstubbed request: POST at 2048e76), and both comment paths by execution including a binary body the PR did not fire, which decoded lossily to mojibake and was reported by the catch as unstubbed RPC: unparseable body while bodyless and empty-string bodies reported via the type guard; the change is strictly narrowing (an added disjunct in an || chain of pure predicates, reachable only for []), all four sibling cases report identically before and after, make check green (25 suites / 576 tests, test-verify-build 18 cases, prettier clean) and make test-e2e 27/27 executed not cached, single commit fast-forwardable onto origin/next at 5af89a1, scope confined to tests/e2e/network.js and TODO.md with ALLOWED_ERRORS still [] and TRAILING_WATCH_MS and the canary untouched.

PASS — independently reproduced both directions in my own clone (`[]` fulfilled `200 []` on `origin/next`, reported as `unstubbed request: POST` at `2048e76`), and both comment paths by execution including a binary body the PR did not fire, which decoded lossily to mojibake and was reported by the `catch` as `unstubbed RPC: unparseable body` while bodyless and empty-string bodies reported via the type guard; the change is strictly narrowing (an added disjunct in an `||` chain of pure predicates, reachable only for `[]`), all four sibling cases report identically before and after, `make check` green (25 suites / 576 tests, `test-verify-build` 18 cases, prettier clean) and `make test-e2e` 27/27 executed not cached, single commit fast-forwardable onto `origin/next` at `5af89a1`, scope confined to `tests/e2e/network.js` and `TODO.md` with `ALLOWED_ERRORS` still `[]` and `TRAILING_WATCH_MS` and the canary untouched.
clawbot merged commit 18b47cd579 into next 2026-08-12 11:50:39 +02:00
clawbot deleted branch test/issue-187-empty-batch-guard 2026-08-12 11:50:39 +02:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sneak/AutistMask#267