Files
cloudflare__vinext/tests/app-post-middleware-context.test.ts
T
Nathan Nguyen 763736eae0 refactor(app-rsc-entry): delegate pure helpers to typed server modules (#1017)
* refactor(app-rsc-entry): delegate pure helpers to typed server modules

The generated RSC entry template contained five inline implementations of
pure runtime helpers: post-middleware request context construction,
RSC error handler creation, request context cleanup, RSC stream preload
hint normalization, and hook warning suppression. These inline blocks
repeated runtime behavior across multiple call sites and could not be
unit-tested independently.

Move each helper to a dedicated typed module under server/ so the
generated entry imports and wires them instead of owning the logic:
- buildPostMwRequestContext → server/app-post-middleware-context.ts
- createRscOnErrorHandler → server/app-rsc-error-handler.ts
- __clearRequestContext + setNavigationContext → server/app-request-context.ts
- renderToReadableStream preload hints → server/rsc-stream-hints.ts (createRscRenderer)
- hook warning console.error patch → server/app-hook-warning-suppression.ts

Each helper module has focused unit tests. The entry template is 55 lines
shorter and now describes the app shape; the typed modules own behaviour.

* fix: remove redundant as cast and fix test module caching

app-hook-warning-suppression.ts contained an `as string` cast that was
redundant because the typeof check on the prior line already narrows the
type. The hook warning suppression tests in tests 2-4 were passing
vacuously because dynamic import() returned the cached module whose
console.error patch was no longer active after test 1 restored the
original. Use vi.resetModules() before each test to force fresh module
evaluation.

clearAppRequestContext now delegates to setAppNavigationContext(null)
instead of calling the raw navigation and root-params setters directly,
matching the DRY pattern from the original inline code.

* fix: resolve entry helper rebase conflicts

* ci: rerun checks (font-google flake)

* Update packages/vinext/src/entries/app-rsc-entry.ts

Co-authored-by: ask-bonk[bot] <249159057+ask-bonk[bot]@users.noreply.github.com>

---------

Co-authored-by: ask-bonk[bot] <249159057+ask-bonk[bot]@users.noreply.github.com>
2026-05-03 17:34:06 +01:00

92 lines
3.2 KiB
TypeScript

import { describe, expect, it, afterEach } from "vite-plus/test";
import { buildPostMwRequestContext } from "../packages/vinext/src/server/app-post-middleware-context.js";
import { setHeadersContext } from "../packages/vinext/src/shims/headers.js";
function makeRequest(
url = "https://example.com/test?q=1",
headers?: Record<string, string>,
): Request {
return new Request(url, { headers: new Headers(headers) });
}
describe("buildPostMwRequestContext", () => {
afterEach(() => {
setHeadersContext(null);
});
it("returns the same shape as requestContextFromRequest when middleware did not run", () => {
setHeadersContext(null);
const req = makeRequest("https://example.com/path?x=1", {
host: "example.com",
cookie: "session=abc",
});
const ctx = buildPostMwRequestContext(req);
// Contract: result must satisfy the RequestContext shape consumed by
// config-matchers — headers, cookies, query, host — all present.
expect(ctx.headers).toBeInstanceOf(Headers);
expect(ctx.headers.get("host")).toBe("example.com");
expect(typeof ctx.cookies).toBe("object");
expect(ctx.cookies).not.toBeInstanceOf(Map);
expect(ctx.cookies).toEqual({ session: "abc" });
expect(ctx.query.get("x")).toBe("1");
expect(ctx.host).toBe("example.com");
});
it("reads from the live ALS HeadersContext when middleware set request headers", () => {
const mwHeaders = new Headers();
mwHeaders.set("x-middleware-request-geo", "DE");
mwHeaders.set("host", "mw.example.com");
const mwCookies = new Map([
["token", "xyz"],
["lang", "en"],
]);
setHeadersContext({ headers: mwHeaders, cookies: mwCookies });
const req = makeRequest("https://original.example.com/path");
const ctx = buildPostMwRequestContext(req);
// Middleware headers take precedence over the physical request headers.
expect(ctx.headers.get("x-middleware-request-geo")).toBe("DE");
expect(ctx.host).toBe("mw.example.com");
// Cookies come from the middleware context, not the request Cookie header.
expect(ctx.cookies).toEqual({ token: "xyz", lang: "en" });
});
it("preserves query parameters from the original request URL", () => {
setHeadersContext({
headers: new Headers({ host: "x.com" }),
cookies: new Map(),
});
const req = makeRequest("https://original.example.com/blog?page=2&sort=desc");
const ctx = buildPostMwRequestContext(req);
expect(ctx.query.get("page")).toBe("2");
expect(ctx.query.get("sort")).toBe("desc");
});
it("handles an empty middleware cookie map without error", () => {
setHeadersContext({
headers: new Headers({ host: "x.com" }),
cookies: new Map(),
});
const req = makeRequest();
const ctx = buildPostMwRequestContext(req);
expect(ctx.cookies).toEqual({});
});
it("normalizes the host from middleware headers, falling back to the request URL hostname", () => {
// No Host header in middleware → normalizeHost falls back to url.hostname
setHeadersContext({
headers: new Headers(),
cookies: new Map(),
});
const req = makeRequest("https://host-from-url.example.com/page");
const ctx = buildPostMwRequestContext(req);
expect(ctx.host).toBe("host-from-url.example.com");
});
});