From 2048e7695f055d67278a6daa6ab761f887f2e8dd Mon Sep 17 00:00:00 2001 From: clawbot Date: Wed, 12 Aug 2026 09:41:57 +0000 Subject: [PATCH] test: close the empty-array hole in the e2e unstubbed-request guard (closes #187) 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. --- TODO.md | 8 ++++++++ tests/e2e/network.js | 25 +++++++++++++++++-------- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/TODO.md b/TODO.md index 28d093b..1e673b5 100644 --- a/TODO.md +++ b/TODO.md @@ -44,6 +44,14 @@ undefined identifiers, which is how # Completed Steps +- 2026-08-12: Closed the empty-array hole in the end-to-end unstubbed-request + guard. `batch.every()` is vacuously true on `[]`, so a POST with body `[]` was + answered `200 []` instead of failing the suite; the guard now rejects an empty + batch, demonstrated green-before/red-after with a throwaway probe. The comment + claiming `postData()` returns `null` for undecodable bodies was corrected to + the two real paths — an absent or empty body decodes to `null`, a binary body + decodes lossily into invalid JSON + ([#187](https://git.eeqj.de/sneak/AutistMask/issues/187)). - 2026-08-12: The transaction confirmation screen has browser coverage. The end-to-end suite reaches ConfirmTx for both the native ETH and the ERC-20 path off a funded-balance fixture, and asserts the pending, funded, over-balance diff --git a/tests/e2e/network.js b/tests/e2e/network.js index d04b57a..c134ee7 100644 --- a/tests/e2e/network.js +++ b/tests/e2e/network.js @@ -297,17 +297,26 @@ async function handleRpc(route, postData, opts, report) { // ethers batches by default, so the body may be an array. const batch = Array.isArray(payload) ? payload : [payload]; - // Anything that is not a JSON-RPC object, or a batch of them, is not - // RPC at all and must be reported like any other unrecognised - // outbound traffic rather than dereferenced. request.postData() - // returns null both for a bodyless POST and for a body Playwright - // cannot decode as UTF-8 (sendBeacon with a Blob, or any binary - // payload), so this is not an empty-string special case: it rejects - // every non-object payload, exactly as the catch above rejects every - // unparseable one. + // 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. if ( payload === null || typeof payload !== "object" || + batch.length === 0 || !batch.every((req) => req !== null && typeof req === "object") ) { report("unstubbed request: POST " + route.request().url());