test: add unit tests for Pages Router api-handler#318
test: add unit tests for Pages Router api-handler#318james-elicx merged 1 commit intocloudflare:mainfrom
Conversation
commit: |
|
/bigbonk review |
There was a problem hiding this comment.
Good test suite — clean structure, well-organized sections, sensible mocks. 34 tests covering the main code paths in api-handler.ts is solid. A few observations and suggestions below, mostly minor.
The .gitignore addition for .worktrees/ is fine.
Overall this is ready to merge with one comment fix and optionally a couple of missing coverage items worth considering.
|
|
||
| // ── MAX_BODY_SIZE enforcement ────────────────────────────────────── | ||
|
|
||
| describe("MAX_BODY_SIZE enforcement", () => { |
There was a problem hiding this comment.
The MAX_BODY_SIZE test duplicates the full mockReq inline construction (lines 297-314) instead of reusing the mockReq() helper. The reason is that mockReq pushes the body eagerly via queueMicrotask, so you need manual stream control for chunked pushing.
Consider factoring out the Object.assign(stream, { ... }) boilerplate into a helper like mockReqStream() that returns both the stream and the typed req, so the MAX_BODY_SIZE test stays DRY:
function mockReqStream(method: string, url: string, headers: Record<string, string> = {}) {
const stream = new PassThrough();
const req = Object.assign(stream, {
method, url, headers: { ...headers },
httpVersion: "1.1", httpVersionMajor: 1, httpVersionMinor: 1,
complete: false, connection: null, socket: null, aborted: false,
rawHeaders: [] as string[], trailers: {} as Record<string, string | undefined>,
rawTrailers: [] as string[], statusCode: undefined, statusMessage: undefined,
}) as unknown as http.IncomingMessage;
return { stream, req };
}Then mockReq can also use this internally. Not blocking, but it'd make the test easier to maintain.
| */ | ||
| import { describe, it, expect, vi } from "vitest"; | ||
| import { PassThrough } from "node:stream"; | ||
| import http from "node:http"; |
There was a problem hiding this comment.
Nit: http is imported as a default import but only used for the type http.IncomingMessage and http.ServerResponse. Since these are only used as type annotations (in the return types of mockReq/mockRes), you could use:
import type { IncomingMessage, ServerResponse } from "node:http";and reference them directly, avoiding importing the entire http module at runtime. This is cosmetic — no functional impact.
| expect(server.ssrFixStacktrace).toHaveBeenCalledWith(error); | ||
| }); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
A few coverage gaps worth considering for follow-up (not blocking this PR):
-
Async handler — all handler mocks are synchronous. The source
await handler(apiReq, apiRes)handles async handlers too. Adding one test with a handler that returns a Promise would cover that path. -
ssrLoadModulefailure — ifssrLoadModulerejects (e.g., syntax error in the API route file), the catch block handles it. No test covers this. -
content-typewith charset — e.g.,application/json; charset=utf-8. The source uses.includes()matching so it works, but a test would document that behavior. -
res.json()setting statusCode — currently only tested viares.status(201).json(...). A standaloneres.json()withoutres.status()that verifies the default 200 status code would be explicit.
These are all nice-to-haves, not blockers.
|
Review posted successfully. Here's a summary of the feedback: Overall: Good test suite, ready to merge with one fix. Comments posted:
|
Cover body parsing (JSON, form-urlencoded, plain text, empty, malformed), cookie parsing (single, multiple, values with =, missing header), req/res extensions (status chaining, json, send for objects/strings/numbers/null, redirect with default 307 and custom status codes), query string and dynamic param merging (including array promotion for duplicate keys), MAX_BODY_SIZE enforcement (413 for >1 MB), and error handling (missing default export, non-function export, handler throws, ssrFixStacktrace integration).
b3b1102 to
f4bca37
Compare
|
/bigbonk review |
There was a problem hiding this comment.
Solid test suite. 34 tests covering the key code paths in api-handler.ts — body parsing, cookie parsing, res extensions, query/param merging, MAX_BODY_SIZE enforcement, and error handling. The mocks are well-constructed and the test structure is clean.
All 34 tests pass locally. A few minor observations below, none blocking.
| */ | ||
| import { describe, it, expect, vi } from "vitest"; | ||
| import { PassThrough } from "node:stream"; | ||
| import http from "node:http"; |
There was a problem hiding this comment.
Nit: http is imported as a value import but only used for types (http.IncomingMessage, http.ServerResponse in the return types and casts). Using a type-only import avoids pulling in the module at runtime:
| import http from "node:http"; | |
| import type { IncomingMessage, ServerResponse } from "node:http"; |
Then replace http.IncomingMessage → IncomingMessage and http.ServerResponse → ServerResponse throughout. Cosmetic only.
| const stream = new PassThrough(); | ||
| const req = Object.assign(stream, { | ||
| method: "POST", | ||
| url: "/api/upload", | ||
| headers: { "content-type": "text/plain" } as Record<string, string>, | ||
| httpVersion: "1.1", | ||
| httpVersionMajor: 1, | ||
| httpVersionMinor: 1, | ||
| complete: false, | ||
| connection: null, | ||
| socket: null, | ||
| aborted: false, | ||
| rawHeaders: [] as string[], | ||
| trailers: {} as Record<string, string | undefined>, | ||
| rawTrailers: [] as string[], | ||
| statusCode: undefined, | ||
| statusMessage: undefined, | ||
| }) as unknown as http.IncomingMessage; |
There was a problem hiding this comment.
The inline Object.assign(stream, { ... }) block duplicates the same property set from mockReq (lines 33-49). Consider extracting a shared mockReqStream() helper that returns { stream, req } so both mockReq and this test can reuse it. The only difference is that mockReq eagerly pushes the body via queueMicrotask while this test needs manual stream control — a clean factoring would separate stream construction from body injection.
Not blocking, but it would make future maintenance easier if the mock properties need updating.
| } | ||
| } | ||
| }, | ||
| end(data?: string) { |
There was a problem hiding this comment.
Minor fidelity gap: the mock end() only accepts string, but the real ServerResponse.end() also accepts Buffer and an encoding argument. This works fine for the current tests since api-handler.ts only ever calls end() with strings, but worth noting in case the source adds Buffer support later.
Also, calling end() with no data (as res.redirect() does) leaves _body as "", which works but means tests can't distinguish between "end called with empty string" and "end called with no argument". Not an issue today since no test checks for that distinction.
| expect(server.ssrFixStacktrace).toHaveBeenCalledWith(error); | ||
| }); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
A couple of additional coverage items worth considering as follow-ups (all non-blocking):
-
Async handler — all handler mocks are synchronous. Adding one test with
async (req, res) => { await somePromise; res.json(...) }would exercise theawait handler(apiReq, apiRes)path in the source. -
ssrLoadModulerejection — ifssrLoadModulethrows (e.g., syntax error in the API route file), the catch block handles it. No test covers this path. -
res.send()withundefined—String(undefined)produces"undefined", which is probably not ideal behavior. A test documenting this would be useful, even if the source doesn't handle it specially today. -
res.send()with arrays — arrays are objects and would take the JSON branch. A test forres.send([1, 2, 3])would document that behavior explicitly.
|
Review posted successfully. Here's a summary of what I found: Verdict: Approved — this is a well-structured test suite ready to merge. What's good:
Comments posted (all non-blocking):
|
Summary
handleApiRoute()inserver/api-handler.tsMAX_BODY_SIZEenforcement (rejects >1MB with 413)=in values, missing header)res.status()chaining,res.json(),res.send()type inference,res.redirect()default 307 vs customssrFixStacktracecalledUses a minimal
ViteDevServermock (onlyssrLoadModule+ssrFixStacktrace) and lightweightIncomingMessage/ServerResponsemocks viaPassThroughstreams.Test plan
npx vitest run tests/api-handler.test.ts— 34/34 passing