Skip to content

Commit 163d01d

Browse files
committed
test(clickclack): harden response cap proof
1 parent 9c497b1 commit 163d01d

1 file changed

Lines changed: 45 additions & 15 deletions

File tree

extensions/clickclack/src/http-client.test.ts

Lines changed: 45 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { createServer, type Server } from "node:http";
22
import { describe, expect, it, vi } from "vitest";
33
import { createClickClackClient } from "./http-client.js";
44

5-
const CLICKCLACK_JSON_CAP_BYTES = 16 * 1024 * 1024;
65
const LOOPBACK_RESPONSE_BYTES = 18 * 1024 * 1024;
76

87
async function listenLoopbackServer(server: Server): Promise<number> {
@@ -25,23 +24,54 @@ function createOversizedJsonServer(): { server: Server; closed: Promise<number>
2524
const closed = new Promise<number>((resolve) => {
2625
resolveClosed = resolve;
2726
});
28-
const server = createServer((_req, res) => {
27+
const server = createServer((req, res) => {
2928
let sentBytes = 0;
30-
const chunk = Buffer.alloc(64 * 1024, 0x20);
31-
res.writeHead(200, { "content-type": "application/json" });
32-
const timer = setInterval(() => {
33-
if (sentBytes >= LOOPBACK_RESPONSE_BYTES) {
34-
clearInterval(timer);
35-
res.end();
36-
return;
29+
let stopped = false;
30+
let prefixSent = false;
31+
const prefixChunk = Buffer.from('{"user":{"id":"');
32+
const bodyChunk = Buffer.alloc(64 * 1024, 0x61);
33+
const suffixChunk = Buffer.from('"}}');
34+
const writeBuffer = (buffer: Buffer) => {
35+
sentBytes += buffer.length;
36+
if (!res.write(buffer)) {
37+
res.once("drain", writeChunks);
38+
return false;
39+
}
40+
return true;
41+
};
42+
const writeChunks = () => {
43+
if (!prefixSent) {
44+
prefixSent = true;
45+
if (!writeBuffer(prefixChunk)) {
46+
return;
47+
}
48+
}
49+
while (true) {
50+
if (stopped) {
51+
return;
52+
}
53+
if (sentBytes + bodyChunk.length + suffixChunk.length >= LOOPBACK_RESPONSE_BYTES) {
54+
break;
55+
}
56+
if (!writeBuffer(bodyChunk)) {
57+
return;
58+
}
3759
}
38-
sentBytes += chunk.length;
39-
res.write(chunk);
40-
}, 1);
60+
if (!stopped) {
61+
sentBytes += suffixChunk.length;
62+
res.end(suffixChunk);
63+
}
64+
};
65+
res.writeHead(200, { connection: "close", "content-type": "application/json" });
4166
res.on("close", () => {
42-
clearInterval(timer);
67+
stopped = true;
4368
resolveClosed(sentBytes);
4469
});
70+
req.on("aborted", () => {
71+
stopped = true;
72+
res.destroy();
73+
});
74+
writeChunks();
4575
});
4676
return { server, closed };
4777
}
@@ -96,8 +126,8 @@ describe("ClickClack HTTP client", () => {
96126
await expect(client.me()).rejects.toThrow(
97127
"ClickClack response: JSON response exceeds 16777216 bytes",
98128
);
99-
await expect(closed).resolves.toBeLessThan(LOOPBACK_RESPONSE_BYTES);
100-
await expect(closed).resolves.toBeGreaterThan(CLICKCLACK_JSON_CAP_BYTES);
129+
const sentBytes = await closed;
130+
expect(sentBytes).toBeLessThan(LOOPBACK_RESPONSE_BYTES);
101131
} finally {
102132
server.close();
103133
}

0 commit comments

Comments
 (0)