Skip to content

Commit 4a218a8

Browse files
pi0claude
andcommitted
test(event): cover shared _url normalization semantics
Pins three behaviors around the in-place percent-decode of req._url: decodePathname is idempotent so repeated H3Event construction on a shared _url never double-decodes, requestWithURL shadows the parsed _url (unit-level companion to d21d93c), and srvx NodeRequestURL writes the normalized pathname back to the raw Node req.url (e2e). Co-Authored-By: Claude Fable 5 <[email protected]>
1 parent d21d93c commit 4a218a8

3 files changed

Lines changed: 65 additions & 0 deletions

File tree

test/event.test.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,23 @@ describeMatrix("event", (t, { it, expect }) => {
9393
expect(await result.text()).toBe("200");
9494
});
9595

96+
it("normalizes percent-encoded pathname and syncs the raw node url", async () => {
97+
t.app.all("/*", (event) => ({
98+
pathname: event.url.pathname,
99+
rawNodeUrl: event.runtime?.node?.req?.url ?? null,
100+
}));
101+
const res = await t.fetch("/h%65llo?q=%41");
102+
const body = await res.json();
103+
expect(body.pathname).toBe("/hello");
104+
if (t.target === "node") {
105+
// srvx NodeRequestURL writes pathname mutations back to the raw
106+
// Node request; the search string must stay untouched
107+
expect(body.rawNodeUrl).toBe("/hello?q=%41");
108+
} else {
109+
expect(body.rawNodeUrl).toBe(null);
110+
}
111+
});
112+
96113
it("event.waitUntil", () => {
97114
const event = mockEvent("/");
98115
event.req.waitUntil = vi.fn();

test/unit/event.test.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { describe, it, expect } from "vitest";
2+
import { FastURL } from "srvx";
3+
import { H3Event } from "../../src/event.ts";
4+
import { decodePathname } from "../../src/utils/internal/path.ts";
5+
6+
describe("H3Event URL normalization", () => {
7+
it("reuses the runtime-provided _url object in place", () => {
8+
const req = new Request("http://localhost/h%65llo");
9+
(req as any)._url = new FastURL("http://localhost/h%65llo");
10+
const event = new H3Event(req);
11+
expect(event.url).toBe((req as any)._url);
12+
expect(event.url.pathname).toBe("/hello");
13+
});
14+
15+
it("does not double-decode when two events share one _url", () => {
16+
const href = "http://localhost/a%2541-%41";
17+
const req = new Request(href);
18+
(req as any)._url = new FastURL(href);
19+
const first = new H3Event(req);
20+
expect(first.url.pathname).toBe("/a%2541-A");
21+
const second = new H3Event(req);
22+
expect(second.url).toBe(first.url);
23+
expect(second.url.pathname).toBe("/a%2541-A");
24+
});
25+
});
26+
27+
describe("decodePathname", () => {
28+
it("is idempotent", () => {
29+
for (const input of [
30+
"/a%41b",
31+
"/a%2541", // encoded % must stay encoded, never becoming decodable
32+
"/%2525",
33+
"/a%2Fb", // reserved chars stay encoded
34+
"/caf%C3%A9",
35+
"/plain",
36+
]) {
37+
const once = decodePathname(input);
38+
expect(decodePathname(once)).toBe(once);
39+
}
40+
});
41+
});

test/unit/request.test.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,13 @@ describe("requestWithURL", () => {
3535
expect(proxied.headers.get("x-test")).toBe("value");
3636
});
3737

38+
it("shadows the runtime-parsed _url of the source request", () => {
39+
const target = new Request("http://example.com/base/path");
40+
(target as any)._url = new URL("http://example.com/base/path");
41+
const proxied = requestWithURL(target, "http://example.com/path");
42+
expect((proxied as any)._url).toBeUndefined();
43+
});
44+
3845
it("is instanceof Request", () => {
3946
const proxied = requestWithURL(original, "http://example.com/path");
4047
expect(proxied instanceof Request).toBe(true);

0 commit comments

Comments
 (0)