typescript-express-test-app-body-parser-divergence
Diagnosed, fixed, and verified with a working reproduction. Full write-up is in /workspace/SOLUTION.md; the runnable repro lives in /workspace.
Root cause. Express only parses a body when a middleware's type matcher accepts the Content-Type. express.json() never matches application/x-ndjson, so the stream is never consumed, req.body stays undefined, and the route returns 400 {"error":"NDJSON body required"}. Tests stayed green because the module battery hand-builds its own app and installs express.text({ type: 'application/x-ndjson' }), while createHttpServer() mounted only express.json().
Exact fix (src/httpServer.ts) — add the needed parser to the production chain, scoped to the batch mount, before express.json():
app.use('/api/batch', express.text({ type: 'application/x-ndjson', limit: '5mb' }));
app.use(express.json());
app.use('/api/batch', batchRouter);
Verification performed (all reproduced live here):
- Module test (hand-built app): GREEN ✅ — even when production is broken.
- Production test (createHttpServer() + real socket): RED before (expected 400 to be 201), GREEN after (201).
- Live curl against the production factory: 400 {"error":"NDJSON body required"} before → 201 {"inserted":2,...} after.
- Full suite: Test Files 2 passed (2), Tests 3 passed (3).
The regression test in test/batch.production.test.ts builds the app through the same factory production uses, drives it over a real socket, and includes a JSON-semantics guard. I proved it load-bearing by reverting the fix (RED) and restoring it (GREEN).
# Evidence - Problem class: typescript-express-test-app-body-parser-divergence - Model: openrouter/deepseek/deepseek-v4.1-flash - Solved: 2026-09-14T12:33:23.699Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "A route accepts batch inserts as application/x-ndjson. The module battery mounts its OWN express app and installs express.text({type:'application/x-ndjson'}) itself, so NDJSON inserts pass in tests. The production app built by createHttpServer() mounts only express.json() (which never matches application/x-ndjson), so the request stream is never consumed, req.body stays undefined and every live NDJSON insert 400s. Detection: curl the PRODUCTION app (node bin/<cli>.js http --port <free>) instead of trusting the suite; or grep the test harness for body parsers the production wiring does not install. Fix: install the exact body parser the route needs in the production middleware chain (scoped mount, ordered before express.json, so other routes' JSON semantics stay byte-identical). Prevention: add a regression test that builds the app via the SAME factory production uses (createHttpServer) and drives it over a real socket, then prove it is load-bearing by reverting the fix (test must go RED) before restoring. General rule: a hand-built test app is a fake production wiring - every middleware-dependent behavior (body parsers, auth order, route mount prefixes) can pass in tests and fail live.", "environment": "Node 22, Express 5, vitest, DuckBrain HTTP layer", "language": "typescript", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "typescript-express-test-app-body-parser-divergence", "provider": "openrouter", "solved_at": "2026-09-14T12:33:23.699Z", "version": ""}Diagnosed, fixed, and verified with a working reproduction. Full write-up is in /workspace/SOLUTION.md; the runnable repro lives in /workspace.
Root cause. Express only parses a body when a middleware's type matcher accepts the Content-Type. express.json() never matches application/x-ndjson, so the stream is never consumed, req.body stays undefined, and the route returns 400 {"error":"NDJSON body required"}. Tests stayed green because the module battery hand-builds its own app and installs express.text({ type: 'application/x-ndjson' }), while createHttpServer() mounted only express.json().
Exact fix (src/httpServer.ts) — add the needed parser to the production chain, scoped to the batch mount, before express.json():
app.use('/api/batch', express.text({ type: 'application/x-ndjson', limit: '5mb' }));
app.use(express.json());
app.use('/api/batch', batchRouter);
Verification performed (all reproduced live here):
- Module test (hand-built app): GREEN ✅ — even when production is broken.
- Production test (createHttpServer() + real socket): RED before (expected 400 to be 201), GREEN after (201).
- Live curl against the production factory: 400 {"error":"NDJSON body required"} before → 201 {"inserted":2,...} after.
- Full suite: Test Files 2 passed (2), Tests 3 passed (3).
The regression test in test/batch.production.test.ts builds the app through the same factory production uses, drives it over a real socket, and includes a JSON-semantics guard. I proved it load-bearing by reverting the fix (RED) and restoring it (GREEN).
# Evidence - Problem class: typescript-express-test-app-body-parser-divergence - Model: openrouter/deepseek/deepseek-v4.1-flash - Solved: 2026-09-14T12:33:23.699Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "A route accepts batch inserts as application/x-ndjson. The module battery mounts its OWN express app and installs express.text({type:'application/x-ndjson'}) itself, so NDJSON inserts pass in tests. The production app built by createHttpServer() mounts only express.json() (which never matches application/x-ndjson), so the request stream is never consumed, req.body stays undefined and every live NDJSON insert 400s. Detection: curl the PRODUCTION app (node bin/<cli>.js http --port <free>) instead of trusting the suite; or grep the test harness for body parsers the production wiring does not install. Fix: install the exact body parser the route needs in the production middleware chain (scoped mount, ordered before express.json, so other routes' JSON semantics stay byte-identical). Prevention: add a regression test that builds the app via the SAME factory production uses (createHttpServer) and drives it over a real socket, then prove it is load-bearing by reverting the fix (test must go RED) before restoring. General rule: a hand-built test app is a fake production wiring - every middleware-dependent behavior (body parsers, auth order, route mount prefixes) can pass in tests and fail live.", "environment": "Node 22, Express 5, vitest, DuckBrain HTTP layer", "language": "typescript", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "typescript-express-test-app-body-parser-divergence", "provider": "openrouter", "solved_at": "2026-09-14T12:33:23.699Z", "version": ""}