mirror of https://github.com/openclaw/openclaw.git
test: refine http body limit coverage
This commit is contained in:
parent
29b9e21b7b
commit
584e3c2916
|
|
@ -19,6 +19,49 @@ async function waitForMicrotaskTurn(): Promise<void> {
|
||||||
await new Promise<void>((resolve) => queueMicrotask(resolve));
|
await new Promise<void>((resolve) => queueMicrotask(resolve));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function expectReadPayloadTooLarge(params: {
|
||||||
|
chunks?: string[];
|
||||||
|
headers?: Record<string, string>;
|
||||||
|
maxBytes: number;
|
||||||
|
}) {
|
||||||
|
const req = createMockRequest({
|
||||||
|
chunks: params.chunks,
|
||||||
|
headers: params.headers,
|
||||||
|
emitEnd: false,
|
||||||
|
});
|
||||||
|
await expect(readRequestBodyWithLimit(req, { maxBytes: params.maxBytes })).rejects.toMatchObject({
|
||||||
|
message: "PayloadTooLarge",
|
||||||
|
});
|
||||||
|
await waitForMicrotaskTurn();
|
||||||
|
expect(req.__unhandledDestroyError).toBeUndefined();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function expectGuardPayloadTooLarge(params: {
|
||||||
|
chunks?: string[];
|
||||||
|
headers?: Record<string, string>;
|
||||||
|
maxBytes: number;
|
||||||
|
responseFormat?: "json" | "text";
|
||||||
|
responseText?: { PAYLOAD_TOO_LARGE?: string };
|
||||||
|
}) {
|
||||||
|
const req = createMockRequest({
|
||||||
|
chunks: params.chunks,
|
||||||
|
headers: params.headers,
|
||||||
|
emitEnd: false,
|
||||||
|
});
|
||||||
|
const res = createMockServerResponse();
|
||||||
|
const guard = installRequestBodyLimitGuard(req, res, {
|
||||||
|
maxBytes: params.maxBytes,
|
||||||
|
...(params.responseFormat ? { responseFormat: params.responseFormat } : {}),
|
||||||
|
...(params.responseText ? { responseText: params.responseText } : {}),
|
||||||
|
});
|
||||||
|
await waitForMicrotaskTurn();
|
||||||
|
expect(guard.isTripped()).toBe(true);
|
||||||
|
expect(guard.code()).toBe("PAYLOAD_TOO_LARGE");
|
||||||
|
expect(res.statusCode).toBe(413);
|
||||||
|
expect(req.__unhandledDestroyError).toBeUndefined();
|
||||||
|
return { req, res, guard };
|
||||||
|
}
|
||||||
|
|
||||||
function createMockRequest(params: {
|
function createMockRequest(params: {
|
||||||
chunks?: string[];
|
chunks?: string[];
|
||||||
headers?: Record<string, string>;
|
headers?: Record<string, string>;
|
||||||
|
|
@ -66,12 +109,19 @@ describe("http body limits", () => {
|
||||||
await expect(readRequestBodyWithLimit(req, { maxBytes: 1024 })).resolves.toBe('{"ok":true}');
|
await expect(readRequestBodyWithLimit(req, { maxBytes: 1024 })).resolves.toBe('{"ok":true}');
|
||||||
});
|
});
|
||||||
|
|
||||||
it("rejects oversized body", async () => {
|
it.each([
|
||||||
const req = createMockRequest({ chunks: ["x".repeat(512)] });
|
{
|
||||||
await expect(readRequestBodyWithLimit(req, { maxBytes: 64 })).rejects.toMatchObject({
|
name: "rejects oversized streamed body",
|
||||||
message: "PayloadTooLarge",
|
chunks: ["x".repeat(512)],
|
||||||
});
|
maxBytes: 64,
|
||||||
expect(req.__unhandledDestroyError).toBeUndefined();
|
},
|
||||||
|
{
|
||||||
|
name: "declared oversized content-length does not emit unhandled error",
|
||||||
|
headers: { "content-length": "9999" },
|
||||||
|
maxBytes: 128,
|
||||||
|
},
|
||||||
|
])("$name", async ({ chunks, headers, maxBytes }) => {
|
||||||
|
await expectReadPayloadTooLarge({ chunks, headers, maxBytes });
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns json parse error when body is invalid", async () => {
|
it("returns json parse error when body is invalid", async () => {
|
||||||
|
|
@ -83,34 +133,49 @@ describe("http body limits", () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("returns empty object for an empty body by default", async () => {
|
||||||
|
const req = createMockRequest({ chunks: [" "] });
|
||||||
|
const result = await readJsonBodyWithLimit(req, { maxBytes: 1024 });
|
||||||
|
expect(result).toEqual({ ok: true, value: {} });
|
||||||
|
});
|
||||||
|
|
||||||
it("returns payload-too-large for json body", async () => {
|
it("returns payload-too-large for json body", async () => {
|
||||||
const req = createMockRequest({ chunks: ["x".repeat(1024)] });
|
const req = createMockRequest({ chunks: ["x".repeat(1024)] });
|
||||||
const result = await readJsonBodyWithLimit(req, { maxBytes: 10 });
|
const result = await readJsonBodyWithLimit(req, { maxBytes: 10 });
|
||||||
expect(result).toEqual({ ok: false, code: "PAYLOAD_TOO_LARGE", error: "Payload too large" });
|
expect(result).toEqual({ ok: false, code: "PAYLOAD_TOO_LARGE", error: "Payload too large" });
|
||||||
});
|
});
|
||||||
|
|
||||||
it("guard rejects oversized declared content-length", () => {
|
it.each([
|
||||||
const req = createMockRequest({
|
{
|
||||||
|
name: "guard rejects oversized declared content-length",
|
||||||
headers: { "content-length": "9999" },
|
headers: { "content-length": "9999" },
|
||||||
emitEnd: false,
|
maxBytes: 128,
|
||||||
|
expectedBody: '{"error":"Payload too large"}',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "guard rejects streamed oversized body",
|
||||||
|
chunks: ["small", "x".repeat(256)],
|
||||||
|
maxBytes: 128,
|
||||||
|
responseFormat: "text" as const,
|
||||||
|
expectedBody: "Payload too large",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "guard uses custom response text for payload-too-large",
|
||||||
|
chunks: ["small", "x".repeat(256)],
|
||||||
|
maxBytes: 128,
|
||||||
|
responseFormat: "text" as const,
|
||||||
|
responseText: { PAYLOAD_TOO_LARGE: "Too much" },
|
||||||
|
expectedBody: "Too much",
|
||||||
|
},
|
||||||
|
])("$name", async ({ chunks, headers, maxBytes, responseFormat, responseText, expectedBody }) => {
|
||||||
|
const { res } = await expectGuardPayloadTooLarge({
|
||||||
|
chunks,
|
||||||
|
headers,
|
||||||
|
maxBytes,
|
||||||
|
...(responseFormat ? { responseFormat } : {}),
|
||||||
|
...(responseText ? { responseText } : {}),
|
||||||
});
|
});
|
||||||
const res = createMockServerResponse();
|
expect(res.body).toBe(expectedBody);
|
||||||
const guard = installRequestBodyLimitGuard(req, res, { maxBytes: 128 });
|
|
||||||
expect(guard.isTripped()).toBe(true);
|
|
||||||
expect(guard.code()).toBe("PAYLOAD_TOO_LARGE");
|
|
||||||
expect(res.statusCode).toBe(413);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("guard rejects streamed oversized body", async () => {
|
|
||||||
const req = createMockRequest({ chunks: ["small", "x".repeat(256)], emitEnd: false });
|
|
||||||
const res = createMockServerResponse();
|
|
||||||
const guard = installRequestBodyLimitGuard(req, res, { maxBytes: 128, responseFormat: "text" });
|
|
||||||
await waitForMicrotaskTurn();
|
|
||||||
expect(guard.isTripped()).toBe(true);
|
|
||||||
expect(guard.code()).toBe("PAYLOAD_TOO_LARGE");
|
|
||||||
expect(res.statusCode).toBe(413);
|
|
||||||
expect(res.body).toBe("Payload too large");
|
|
||||||
expect(req.__unhandledDestroyError).toBeUndefined();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("timeout surfaces typed error when timeoutMs is clamped", async () => {
|
it("timeout surfaces typed error when timeoutMs is clamped", async () => {
|
||||||
|
|
@ -123,29 +188,20 @@ describe("http body limits", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("guard clamps invalid maxBytes to one byte", async () => {
|
it("guard clamps invalid maxBytes to one byte", async () => {
|
||||||
const req = createMockRequest({ chunks: ["ab"], emitEnd: false });
|
const { res } = await expectGuardPayloadTooLarge({
|
||||||
const res = createMockServerResponse();
|
chunks: ["ab"],
|
||||||
const guard = installRequestBodyLimitGuard(req, res, {
|
|
||||||
maxBytes: Number.NaN,
|
maxBytes: Number.NaN,
|
||||||
responseFormat: "text",
|
responseFormat: "text",
|
||||||
});
|
});
|
||||||
await waitForMicrotaskTurn();
|
expect(res.body).toBe("Payload too large");
|
||||||
expect(guard.isTripped()).toBe(true);
|
|
||||||
expect(guard.code()).toBe("PAYLOAD_TOO_LARGE");
|
|
||||||
expect(res.statusCode).toBe(413);
|
|
||||||
expect(req.__unhandledDestroyError).toBeUndefined();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("declared oversized content-length does not emit unhandled error", async () => {
|
it("surfaces connection-closed as a typed limit error", async () => {
|
||||||
const req = createMockRequest({
|
const req = createMockRequest({ emitEnd: false });
|
||||||
headers: { "content-length": "9999" },
|
const promise = readRequestBodyWithLimit(req, { maxBytes: 128 });
|
||||||
emitEnd: false,
|
queueMicrotask(() => req.emit("close"));
|
||||||
});
|
await expect(promise).rejects.toSatisfy((error: unknown) =>
|
||||||
await expect(readRequestBodyWithLimit(req, { maxBytes: 128 })).rejects.toMatchObject({
|
isRequestBodyLimitError(error, "CONNECTION_CLOSED"),
|
||||||
message: "PayloadTooLarge",
|
);
|
||||||
});
|
|
||||||
// Wait a tick for any async destroy(err) emission.
|
|
||||||
await waitForMicrotaskTurn();
|
|
||||||
expect(req.__unhandledDestroyError).toBeUndefined();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue