req.path was compared to the canonical '/api/voice/extract' only, but
Express routes case-insensitively and ignores a trailing slash by default.
'/api/voice/extract/' therefore reached the controller with the 100 kb
parser, and 413'd every recording past ~20 seconds — a failure that reads
as a broken microphone rather than a routing detail.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
POST /voice/extract returned 500 for any real recording. The threshold was
exactly 100 kb — Express's body-parser default — which is about 20 seconds of
audio, so the endpoint was unusable at its own 2-minute cap.
The scoped parser was registered as a path-mounted json() stacked in front of a
default one, which relied on two implicit behaviours: Express stripping the
mount path, and body-parser skipping a request another parser had already
handled. That coupling broke when the surrounding middleware order shifted, and
it broke silently — the parser was still registered, just no longer the one that
ran. Bisected by dumping the Express layer stack and confirming the raw error was
`entity.too.large` with `limit: 102400`.
Replaced with a single middleware that picks a parser by path. No mount-path
stripping, no dependence on parser ordering. Extracted to common/body-parsers.ts
so it is covered by a unit test rather than only reachable through main.ts, which
createTestingModule never executes.
The test is mutation-checked: forcing the default parser fails 2 of its 5 cases.
It also pins that the larger limit does not leak app-wide, and that a merely
similar path (/api/voice/extract/extra) does not get it.
Verified against the compiled server: 300 kb now reaches /api/voice/extract,
/api/auth/login still rejects it, and ordinary requests are unaffected.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>