From 18b47cd5799fb968deac48e3f2533e1f81b4b6cf Mon Sep 17 00:00:00 2001 From: clawbot Date: Wed, 12 Aug 2026 11:50:38 +0200 Subject: [PATCH] test: close the empty-batch hole in the e2e unstubbed-request guard (closes #187) The guard that reports unrecognised POST bodies used batch.every(), which is vacuously true on an empty array, so a POST with body [] was answered 200 [] and escaped the one mechanism whose job is to make unrecognised outbound traffic fail the suite rather than pass silently. Unreachable in practice today, which is exactly the qualifier that stops being true later. The comment explaining the guard also described a mechanism that does not exist: playwright-core decodes a binary body lossily rather than returning null, so such a body reaches the JSON parse as mojibake and is reported by the catch, while only an absent or empty body decodes to null and is reported by the type guard. Both are reported; the comment now describes the two real routes. --- 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());