Files
diegosouzapw--omniroute/tests/unit/middleware-header-strip-5849.test.ts
2026-07-13 13:39:12 +08:00

68 lines
2.7 KiB
TypeScript

import assert from "node:assert/strict";
import { test } from "node:test";
import {
buildStreamingResponseHeaders,
isNextMiddlewareControlHeader,
stripNextMiddlewareControlHeaders,
} from "@omniroute/open-sse/handlers/chatCore/responseHeaders.ts";
// Regression guard for issue #5849:
// Providers hosted behind a Next.js middleware (e.g. synthetic.new) leak Next's
// internal `x-middleware-*` control headers on a successful 200 response.
// Forwarding `x-middleware-rewrite` verbatim from an App Router route handler
// makes Next 16 throw `NextResponse.rewrite() was used in a app route handler`
// and return 500. Both proxy paths (streaming + JSON) must strip the family.
const MIDDLEWARE_HEADERS: [string, string][] = [
["x-middleware-rewrite", "/internal/rewrite"],
["x-middleware-next", "1"],
["x-middleware-override-headers", "x-foo"],
["x-middleware-set-cookie", "a=b"],
["x-middleware-request-foo", "bar"],
];
test("isNextMiddlewareControlHeader matches the whole x-middleware-* family (case-insensitive)", () => {
for (const [name] of MIDDLEWARE_HEADERS) {
assert.equal(isNextMiddlewareControlHeader(name), true, name);
assert.equal(isNextMiddlewareControlHeader(name.toUpperCase()), true, name);
}
assert.equal(isNextMiddlewareControlHeader("x-request-id"), false);
assert.equal(isNextMiddlewareControlHeader("content-type"), false);
});
test("streaming path: buildStreamingResponseHeaders strips x-middleware-* and preserves normal headers", () => {
const upstream = new Headers();
for (const [k, v] of MIDDLEWARE_HEADERS) upstream.append(k, v);
upstream.append("x-request-id", "req-123");
const out = buildStreamingResponseHeaders(upstream, {});
const lowerKeys = Object.keys(out).map((k) => k.toLowerCase());
for (const [name] of MIDDLEWARE_HEADERS) {
assert.ok(
!lowerKeys.includes(name.toLowerCase()),
`expected ${name} to be stripped, got: ${lowerKeys.join(", ")}`
);
}
// Normal upstream header preserved.
const requestIdKey = Object.keys(out).find((k) => k.toLowerCase() === "x-request-id");
assert.ok(requestIdKey, "x-request-id must be preserved");
assert.equal(out[requestIdKey as string], "req-123");
});
test("non-streaming JSON path: stripNextMiddlewareControlHeaders removes the family, keeps the rest", () => {
const headers = new Headers();
for (const [k, v] of MIDDLEWARE_HEADERS) headers.append(k, v);
headers.append("x-request-id", "req-456");
headers.append("content-type", "application/json");
stripNextMiddlewareControlHeaders(headers);
for (const [name] of MIDDLEWARE_HEADERS) {
assert.equal(headers.get(name), null, `${name} must be stripped`);
}
assert.equal(headers.get("x-request-id"), "req-456");
assert.equal(headers.get("content-type"), "application/json");
});