gateway-outbound-body-headers.test.mjs
87 lines 3.0 KB
Raw
sha256:8d46372e39d2d5a54fd93a8b1c27922fe0d9b22a72197345f1d2c71701cc4ce2 feat(auth): persistent login system + C7 session introspection Human minor ⚠ breaking 16 days ago
1 /**
2 * Regression: gateway proxies with spread req.headers. After express.json(),
3 * we replace the body with JSON.stringify(merge(...)) which is longer than the
4 * client's original body. Undici keeps an explicit Content-Length from the client
5 * and may deadlock trying to write the full string (body length ≠ declared length).
6 */
7 import http from 'node:http';
8 import { once } from 'node:events';
9 import { test } from 'node:test';
10 import assert from 'node:assert/strict';
11
12 test('undici fetch does not complete promptly when Content-Length is shorter than body', async () => {
13 const fullBody = JSON.stringify({
14 path: 'inbox/hub_123.md',
15 body: 'hello world content',
16 frontmatter: JSON.stringify({ title: 'T', knowtation_editor: 'google:1', author_kind: 'human' }),
17 });
18 const staleLength = 32;
19
20 const server = http.createServer((req, res) => {
21 const chunks = [];
22 req.on('data', (c) => chunks.push(c));
23 req.on('end', () => {
24 const got = Buffer.concat(chunks).toString('utf8');
25 res.writeHead(200, { 'Content-Type': 'application/json', Connection: 'close' });
26 res.end(JSON.stringify({ receivedLen: got.length, fullLen: fullBody.length, got }));
27 });
28 });
29 server.listen(0, '127.0.0.1');
30 await once(server, 'listening');
31 const { port } = server.address();
32
33 const ac = new AbortController();
34 const t = setTimeout(() => ac.abort(), 400);
35
36 try {
37 await fetch(`http://127.0.0.1:${port}/api/v1/notes`, {
38 method: 'POST',
39 signal: ac.signal,
40 headers: {
41 'Content-Type': 'application/json',
42 'Content-Length': String(staleLength),
43 },
44 body: fullBody,
45 });
46 assert.fail('expected fetch to hang or abort when Content-Length underreports body');
47 } catch (e) {
48 assert.equal(e.name, 'AbortError');
49 } finally {
50 clearTimeout(t);
51 server.closeAllConnections?.();
52 server.close();
53 await once(server, 'close').catch(() => {});
54 }
55 });
56
57 test('undici fetch completes with full body when Content-Length is omitted', async () => {
58 const fullBody = JSON.stringify({ ok: true, pad: 'x'.repeat(200) });
59
60 const server = http.createServer((req, res) => {
61 const chunks = [];
62 req.on('data', (c) => chunks.push(c));
63 req.on('end', () => {
64 const got = Buffer.concat(chunks).toString('utf8');
65 res.writeHead(200, { 'Content-Type': 'application/json', Connection: 'close' });
66 res.end(JSON.stringify({ receivedLen: got.length, match: got === fullBody }));
67 });
68 });
69 server.listen(0, '127.0.0.1');
70 await once(server, 'listening');
71 const { port } = server.address();
72
73 try {
74 const r = await fetch(`http://127.0.0.1:${port}/t`, {
75 method: 'POST',
76 headers: { 'Content-Type': 'application/json' },
77 body: fullBody,
78 });
79 const j = await r.json();
80 assert.equal(j.receivedLen, fullBody.length);
81 assert.equal(j.match, true);
82 } finally {
83 server.closeAllConnections?.();
84 server.close();
85 await once(server, 'close').catch(() => {});
86 }
87 });
File History 2 commits
sha256:8d46372e39d2d5a54fd93a8b1c27922fe0d9b22a72197345f1d2c71701cc4ce2 feat(auth): persistent login system + C7 session introspection Human minor 16 days ago