Spaces:
Running
Running
File size: 3,237 Bytes
6c3af4e | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 | import { Agent, createServer, request, type Server } from "node:http";
import type { AddressInfo } from "node:net";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
vi.mock("hash-wasm", () => ({ argon2Verify: async () => false }));
vi.mock("./verifyTokenAndRateLimit.ts", () => ({
consumeRateLimitPoint: async () => true,
}));
/**
* The body cap answers mid-upload and drops the socket to stop reading. Only a
* real connection shows what that costs the caller's next request: a mocked
* req/res has no pooled connection to be dropped underneath it.
*
* Runs under the default jsdom environment on purpose. `node:http` works there,
* and a per-file environment pragma cannot be used to switch it: the global
* setup file `client/setupTests.ts` reaches for `window`, so the node
* environment fails at import. The two `*.integration.test.ts` files escape
* that through `vitest.integration.config.ts`, which declares no setup file,
* but `npm test` does not run that config.
*/
describe("validateAccessKeyServerHook over a real socket", () => {
let server: Server;
let port: number;
let originalAccessKeys: string | undefined;
beforeEach(async () => {
originalAccessKeys = process.env.ACCESS_KEYS;
process.env.ACCESS_KEYS = "test-key";
const { validateAccessKeyServerHook } = await import(
"./validateAccessKeyServerHook.ts"
);
server = createServer((req, res) => {
const middlewares = {
use: (
handler: (req: unknown, res: unknown, next: () => void) => void,
) =>
handler(req, res, () => {
res.statusCode = 404;
res.end();
}),
};
validateAccessKeyServerHook({ middlewares } as never);
});
await new Promise<void>((resolve) => server.listen(0, resolve));
port = (server.address() as AddressInfo).port;
});
afterEach(async () => {
await new Promise<void>((resolve) => server.close(() => resolve()));
if (originalAccessKeys === undefined) delete process.env.ACCESS_KEYS;
else process.env.ACCESS_KEYS = originalAccessKeys;
});
function post(agent: Agent, payload: string | Buffer) {
return new Promise<string>((resolve) => {
const req = request(
{ port, method: "POST", path: "/api/validate-access-key", agent },
(res) => {
res.resume();
res.on("end", () => resolve(`status=${res.statusCode}`));
},
);
req.on("error", (error) =>
resolve(`error=${(error as NodeJS.ErrnoException).code}`),
);
req.end(payload);
});
}
it("refuses an oversized body with 413 without breaking the next request", async () => {
const agent = new Agent({ keepAlive: true, maxSockets: 1 });
try {
expect(await post(agent, Buffer.alloc(64 * 1024, "x"))).toBe(
"status=413",
);
// A fresh connection, because the 413 told the agent not to pool the one
// the server then dropped. Without that header the agent reuses the
// dropped socket and this is an ECONNRESET.
expect(await post(agent, JSON.stringify({ accessKeyHash: "x" }))).toBe(
"status=200",
);
} finally {
agent.destroy();
}
});
});
|