From e7aed2703e7d3e7b1bef1846e86637f9aca3a6d5 Mon Sep 17 00:00:00 2001 From: pq198363-ops <246611021+pq198363-ops@users.noreply.github.com> Date: Sat, 4 Jul 2026 12:49:34 +0800 Subject: [PATCH] test: cover error handler responses --- src/errors.test.ts | 96 +++++++++++++++++++++++++++++++++++++++++ src/middleware/index.ts | 2 +- 2 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 src/errors.test.ts diff --git a/src/errors.test.ts b/src/errors.test.ts new file mode 100644 index 0000000..31e0b9b --- /dev/null +++ b/src/errors.test.ts @@ -0,0 +1,96 @@ +import { beforeEach, describe, it } from "node:test"; +import assert from "node:assert"; +import request from "supertest"; +import { createApp } from "./index.js"; +import { eventLog } from "./events.js"; +import { + apiKeyStore, + config, + pauseState, + rateBuckets, + servicesDisabled, + servicesMetadata, + servicesStore, + usageStore, + webhookStore, +} from "./store/state.js"; + +const defaultConfig = { + rateLimitPerWindow: 60, + rateLimitWindowMs: 60_000, + bulkMaxItems: 100, + eventLogCap: 10_000, +}; + +type ErrorBody = { + error?: unknown; + message?: unknown; + requestId?: unknown; + stack?: unknown; +}; + +function assertStructuredError(body: ErrorBody, expectedError: string) { + assert.strictEqual(body.error, expectedError); + assert.strictEqual(typeof body.message, "string"); + assert.ok((body.message as string).length > 0); + assert.strictEqual(typeof body.requestId, "string"); + assert.ok((body.requestId as string).length > 0); + assert.strictEqual(body.stack, undefined); + assert.ok(!(body.message as string).includes("node_modules")); +} + +beforeEach(() => { + apiKeyStore.clear(); + eventLog.length = 0; + pauseState.paused = false; + rateBuckets.clear(); + servicesDisabled.clear(); + servicesMetadata.clear(); + servicesStore.clear(); + usageStore.clear(); + webhookStore.clear(); + Object.assign(config, defaultConfig); +}); + +void describe("global error handling", () => { + void it("returns a structured 413 with requestId for oversized JSON bodies", async () => { + const app = createApp(); + + const response = await request(app) + .post("/api/v1/usage") + .send({ value: "x".repeat(101 * 1024) }); + + assert.strictEqual(response.status, 413); + assertStructuredError(response.body as ErrorBody, "payload_too_large"); + assert.strictEqual(response.body.message, "request body exceeds the 100 KiB limit"); + }); + + void it("returns a structured internal error for malformed JSON bodies", async () => { + const app = createApp(); + + const response = await request(app) + .post("/api/v1/usage") + .set("Content-Type", "application/json") + .send('{"agent":'); + + assert.strictEqual(response.status, 500); + assertStructuredError(response.body as ErrorBody, "internal_error"); + }); + + for (const method of ["get", "post"] as const) { + void it(`returns a structured 404 with method, path, and requestId for unknown ${method.toUpperCase()} routes`, async () => { + const app = createApp(); + const response = + method === "post" + ? await request(app).post("/api/v1/missing-route").send({}) + : await request(app).get("/api/v1/missing-route"); + + assert.strictEqual(response.status, 404); + assertStructuredError(response.body as ErrorBody, "not_found"); + assert.strictEqual( + response.body.message, + `No route for ${method.toUpperCase()} /api/v1/missing-route` + ); + }); + } +}); diff --git a/src/middleware/index.ts b/src/middleware/index.ts index 53859a7..2fe6e9d 100644 --- a/src/middleware/index.ts +++ b/src/middleware/index.ts @@ -20,9 +20,9 @@ import type { AgentPayRequest } from "../types.js"; */ export function installPreRouteMiddleware(app: Application): void { app.use(createCorsMiddleware()); + app.use(requestIdMiddleware); app.use(express.json({ limit: "100kb" })); app.use(securityHeadersMiddleware); - app.use(requestIdMiddleware); } /**