mirror of
https://github.com/cloudflare/vinext.git
synced 2026-09-14 19:04:59 +08:00
bc586788a2
* fix(middleware): preserve query params on rewrite When middleware (or a `next.config.js` rewrite) sends a request to a new target, vinext was throwing away the original request's query string, breaking `getServerSideProps`/route-handler `query` for any URL of the form `/rewrite-me?key=value`. Match Next.js's behaviour: `Object.assign(parsedUrl.query, rewrittenParsedUrl.query)` — keep the original query first, let the rewrite-target's query win on key conflicts. Applied to: - middleware rewrites (middleware-runtime.ts) - beforeFiles / afterFiles / fallback config rewrites (prod-server.ts, index.ts, deploy.ts) Closes #1342 References: - https://github.com/vercel/next.js/blob/canary/packages/next/src/server/route-modules/route-module.ts (Object.assign merge) - https://github.com/vercel/next.js/blob/canary/test/e2e/edge-pages-support/index.test.ts (rewrite query expectations) * test(query): document URL-encoded space normalization on rewrite merge Addresses Bonk review observations on #1375: - Add test confirming `%20` → `+` normalization through URLSearchParams is intentional and lossless (downstream consumers re-parse). - Drop unreachable `?? ""` fallback in mergeRewriteQuery; `String#split` always yields a string.
116 lines
4.6 KiB
TypeScript
116 lines
4.6 KiB
TypeScript
import { describe, expect, it } from "vite-plus/test";
|
|
|
|
import { appendSearchParamsToUrl, mergeRewriteQuery } from "../packages/vinext/src/utils/query.js";
|
|
|
|
describe("mergeRewriteQuery", () => {
|
|
it("preserves original query params when the rewrite target has none", () => {
|
|
const result = mergeRewriteQuery("http://localhost/rewrite-me?hello=world", "/");
|
|
expect(result).toBe("/?hello=world");
|
|
});
|
|
|
|
it("returns the rewrite URL unchanged when the original has no query", () => {
|
|
const result = mergeRewriteQuery("http://localhost/rewrite-me", "/target?foo=bar");
|
|
expect(result).toBe("/target?foo=bar");
|
|
});
|
|
|
|
it("merges original params with rewrite-target params (rewrite wins on conflicts)", () => {
|
|
const result = mergeRewriteQuery(
|
|
"http://localhost/path?hello=original&keep=this",
|
|
"/target?hello=from-rewrite",
|
|
);
|
|
expect(result).toContain("hello=from-rewrite");
|
|
expect(result).toContain("keep=this");
|
|
expect(result).not.toContain("hello=original");
|
|
});
|
|
|
|
it("preserves the rewrite URL's pathname and hash", () => {
|
|
const result = mergeRewriteQuery("http://localhost/path?foo=1", "/target?bar=2#section");
|
|
expect(result).toBe("/target?foo=1&bar=2#section");
|
|
});
|
|
|
|
it("supports absolute rewrite URLs (preserves origin)", () => {
|
|
const result = mergeRewriteQuery(
|
|
"http://localhost/path?foo=1",
|
|
"http://localhost/target?bar=2",
|
|
);
|
|
expect(result).toBe("http://localhost/target?foo=1&bar=2");
|
|
});
|
|
|
|
it("ignores empty query string after the question mark in original", () => {
|
|
const result = mergeRewriteQuery("http://localhost/path?", "/target");
|
|
expect(result).toBe("/target");
|
|
});
|
|
|
|
it("preserves multi-value keys from the original query", () => {
|
|
const result = mergeRewriteQuery("http://localhost/path?tag=a&tag=b", "/target?other=1");
|
|
expect(result).toContain("tag=a");
|
|
expect(result).toContain("tag=b");
|
|
expect(result).toContain("other=1");
|
|
});
|
|
|
|
it("uses rewrite-target multi-values when the same key appears in both", () => {
|
|
const result = mergeRewriteQuery(
|
|
"http://localhost/path?tag=orig1&tag=orig2",
|
|
"/target?tag=rw1&tag=rw2",
|
|
);
|
|
// Original tags are removed; rewrite-target tags survive.
|
|
const params = new URLSearchParams(result.slice(result.indexOf("?") + 1));
|
|
expect(params.getAll("tag")).toEqual(["rw1", "rw2"]);
|
|
});
|
|
|
|
it("normalizes URL-encoded space (%20) to + via URLSearchParams", () => {
|
|
// URLSearchParams.toString() emits `+` for spaces. Downstream consumers all
|
|
// re-parse via URLSearchParams or new URL(), so this normalization is safe
|
|
// and lossless. This test documents the intentional behavior.
|
|
const result = mergeRewriteQuery("http://localhost/path?foo=hello%20world", "/target");
|
|
expect(result).toBe("/target?foo=hello+world");
|
|
expect(new URLSearchParams(result.slice(result.indexOf("?") + 1)).get("foo")).toBe(
|
|
"hello world",
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("appendSearchParamsToUrl", () => {
|
|
it("adds query params to a path with no existing query", () => {
|
|
const url = appendSearchParamsToUrl("/search", [["q", "vinext"]]);
|
|
expect(url).toBe("/search?q=vinext");
|
|
});
|
|
|
|
it("preserves existing query params when appending new ones", () => {
|
|
const url = appendSearchParamsToUrl("/search?lang=en", [["q", "vinext"]]);
|
|
expect(url).toBe("/search?lang=en&q=vinext");
|
|
});
|
|
|
|
it("preserves duplicate keys from the existing query string", () => {
|
|
const url = appendSearchParamsToUrl("/search?tag=a&tag=b", [["tag", "c"]]);
|
|
expect(url).toBe("/search?tag=a&tag=b&tag=c");
|
|
});
|
|
|
|
it("preserves hash fragments after appending query params", () => {
|
|
const url = appendSearchParamsToUrl("/search?lang=en#results", [["q", "vinext"]]);
|
|
expect(url).toBe("/search?lang=en&q=vinext#results");
|
|
});
|
|
|
|
it("preserves hashes when the base URL has no existing query string", () => {
|
|
const url = appendSearchParamsToUrl("/search#results", [["q", "vinext"]]);
|
|
expect(url).toBe("/search?q=vinext#results");
|
|
});
|
|
|
|
it("preserves absolute URLs", () => {
|
|
const url = appendSearchParamsToUrl("https://example.com/search?lang=en#results", [
|
|
["q", "vinext"],
|
|
]);
|
|
expect(url).toBe("https://example.com/search?lang=en&q=vinext#results");
|
|
});
|
|
|
|
it("returns the original URL when there are no params to append", () => {
|
|
const url = appendSearchParamsToUrl("/search?lang=en#results", []);
|
|
expect(url).toBe("/search?lang=en#results");
|
|
});
|
|
|
|
it("supports query-only relative URLs", () => {
|
|
const url = appendSearchParamsToUrl("?lang=en", [["q", "vinext"]]);
|
|
expect(url).toBe("?lang=en&q=vinext");
|
|
});
|
|
});
|