Skip to content

Commit 84337a6

Browse files
committed
Add unit tests for route matching
1 parent bd2e058 commit 84337a6

File tree

1 file changed

+383
-0
lines changed

1 file changed

+383
-0
lines changed
Lines changed: 383 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,383 @@
1+
import { NextConfig } from "@opennextjs/aws/adapters/config/index.js";
2+
import {
3+
addNextConfigHeaders,
4+
handleRedirects,
5+
handleRewrites,
6+
} from "@opennextjs/aws/core/routing/matcher.js";
7+
import { convertFromQueryString } from "@opennextjs/aws/core/routing/util.js";
8+
import { InternalEvent } from "@opennextjs/aws/types/open-next.js";
9+
import { vi } from "vitest";
10+
11+
vi.mock("@opennextjs/aws/adapters/config/index.js", () => ({
12+
NextConfig: {},
13+
}));
14+
vi.mock("@opennextjs/aws/core/routing/i18n/index.js", () => ({
15+
localizePath: (event: InternalEvent) => event.rawPath,
16+
}));
17+
18+
type PartialEvent = Partial<
19+
Omit<InternalEvent, "body" | "rawPath" | "query">
20+
> & { body?: string };
21+
22+
function createEvent(event: PartialEvent): InternalEvent {
23+
const [rawPath, qs] = (event.url ?? "/").split("?", 2);
24+
return {
25+
type: "core",
26+
method: event.method ?? "GET",
27+
rawPath,
28+
url: event.url ?? "/",
29+
body: Buffer.from(event.body ?? ""),
30+
headers: event.headers ?? {},
31+
query: convertFromQueryString(qs ?? ""),
32+
cookies: event.cookies ?? {},
33+
remoteAddress: event.remoteAddress ?? "::1",
34+
};
35+
}
36+
37+
beforeEach(() => {
38+
vi.resetAllMocks();
39+
});
40+
41+
describe("addNextConfigHeaders", () => {
42+
it("should return empty object for undefined configHeaders", () => {
43+
const event = createEvent({});
44+
const result = addNextConfigHeaders(event);
45+
46+
expect(result).toEqual({});
47+
});
48+
49+
it("should return empty object for empty configHeaders", () => {
50+
const event = createEvent({});
51+
const result = addNextConfigHeaders(event, []);
52+
53+
expect(result).toEqual({});
54+
});
55+
56+
it("should return request headers for matching / route", () => {
57+
const event = createEvent({
58+
url: "/",
59+
});
60+
61+
const result = addNextConfigHeaders(event, [
62+
{
63+
source: "/",
64+
regex: "^/$",
65+
headers: [
66+
{
67+
key: "foo",
68+
value: "bar",
69+
},
70+
],
71+
},
72+
]);
73+
74+
expect(result).toEqual({
75+
foo: "bar",
76+
});
77+
});
78+
79+
it("should return empty request headers for matching / route with empty headers", () => {
80+
const event = createEvent({
81+
url: "/",
82+
});
83+
84+
const result = addNextConfigHeaders(event, [
85+
{
86+
source: "/",
87+
regex: "^/$",
88+
headers: [],
89+
},
90+
]);
91+
92+
expect(result).toEqual({});
93+
});
94+
95+
it("should return request headers for matching /* route", () => {
96+
const event = createEvent({
97+
url: "/hello-world",
98+
});
99+
100+
const result = addNextConfigHeaders(event, [
101+
{
102+
source: "/(.*)",
103+
regex: "^(?:/(.*))(?:/)?$",
104+
headers: [
105+
{
106+
key: "foo",
107+
value: "bar",
108+
},
109+
{
110+
key: "hello",
111+
value: "world",
112+
},
113+
],
114+
},
115+
]);
116+
117+
expect(result).toEqual({
118+
foo: "bar",
119+
hello: "world",
120+
});
121+
});
122+
123+
it("should return request headers for matching /* route with has condition", () => {
124+
const event = createEvent({
125+
url: "/hello-world",
126+
cookies: {
127+
match: "true",
128+
},
129+
});
130+
131+
const result = addNextConfigHeaders(event, [
132+
{
133+
source: "/(.*)",
134+
regex: "^(?:/(.*))(?:/)?$",
135+
headers: [
136+
{
137+
key: "foo",
138+
value: "bar",
139+
},
140+
],
141+
has: [{ type: "cookie", key: "match" }],
142+
},
143+
]);
144+
145+
expect(result).toEqual({
146+
foo: "bar",
147+
});
148+
});
149+
150+
it("should return request headers for matching /* route with missing condition", () => {
151+
const event = createEvent({
152+
url: "/hello-world",
153+
cookies: {
154+
match: "true",
155+
},
156+
});
157+
158+
const result = addNextConfigHeaders(event, [
159+
{
160+
source: "/(.*)",
161+
regex: "^(?:/(.*))(?:/)?$",
162+
headers: [
163+
{
164+
key: "foo",
165+
value: "bar",
166+
},
167+
],
168+
missing: [{ type: "cookie", key: "missing" }],
169+
},
170+
]);
171+
172+
expect(result).toEqual({
173+
foo: "bar",
174+
});
175+
});
176+
177+
it("should return request headers for matching /* route with has and missing condition", () => {
178+
const event = createEvent({
179+
url: "/hello-world",
180+
cookies: {
181+
match: "true",
182+
},
183+
});
184+
185+
const result = addNextConfigHeaders(event, [
186+
{
187+
source: "/(.*)",
188+
regex: "^(?:/(.*))(?:/)?$",
189+
headers: [
190+
{
191+
key: "foo",
192+
value: "bar",
193+
},
194+
],
195+
has: [{ type: "cookie", key: "match" }],
196+
missing: [{ type: "cookie", key: "missing" }],
197+
},
198+
]);
199+
200+
expect(result).toEqual({
201+
foo: "bar",
202+
});
203+
});
204+
205+
it.todo(
206+
"should exercise the error scenario: 'Error matching header <key> with value <value>'",
207+
);
208+
});
209+
210+
describe("handleRedirects", () => {
211+
it("should redirect trailing slash by default", () => {
212+
const event = createEvent({
213+
url: "/api-route/",
214+
});
215+
216+
const result = handleRedirects(event, []);
217+
218+
expect(result.statusCode).toEqual(308);
219+
expect(result.headers.Location).toEqual("/api-route");
220+
});
221+
222+
it("should not redirect trailing slash when skipTrailingSlashRedirect is true", () => {
223+
const event = createEvent({
224+
url: "/api-route/",
225+
});
226+
227+
NextConfig.skipTrailingSlashRedirect = true;
228+
const result = handleRedirects(event, []);
229+
230+
expect(result).toBeUndefined();
231+
});
232+
233+
it("should redirect matching path", () => {
234+
const event = createEvent({
235+
url: "/api-route",
236+
});
237+
238+
const result = handleRedirects(event, [
239+
{
240+
source: "/:path+",
241+
destination: "/new/:path+",
242+
internal: true,
243+
statusCode: 308,
244+
regex: "^(?:/((?:[^/]+?)(?:/(?:[^/]+?))*))/$",
245+
},
246+
]);
247+
248+
expect(result).toBeUndefined();
249+
});
250+
251+
it("should not redirect unmatched path", () => {
252+
const event = createEvent({
253+
url: "/api-route",
254+
});
255+
256+
const result = handleRedirects(event, [
257+
{
258+
source: "/foo/",
259+
destination: "/bar",
260+
internal: true,
261+
statusCode: 308,
262+
regex: "^(?:/((?:[^/]+?)(?:/(?:[^/]+?))*))/$",
263+
},
264+
]);
265+
266+
expect(result).toBeUndefined();
267+
});
268+
});
269+
270+
describe("handleRewrites", () => {
271+
it("should not rewrite with empty rewrites", () => {
272+
const event = createEvent({
273+
url: "/foo?hellp=world",
274+
});
275+
276+
console.log(event);
277+
278+
const result = handleRewrites(event, []);
279+
280+
expect(result).toEqual({
281+
internalEvent: event,
282+
isExternalRewrite: false,
283+
});
284+
});
285+
286+
it("should rewrite with matching path", () => {
287+
const event = createEvent({
288+
url: "/albums/foo/bar",
289+
});
290+
291+
const rewrites = [
292+
{
293+
source: "/albums/:album",
294+
destination: "/rewrite/albums/:album",
295+
regex: "^/albums(?:/([^/]+?))(?:/)?$",
296+
},
297+
{
298+
source: "/albums/:album/:song",
299+
destination: "/rewrite/albums/:album/:song",
300+
regex: "^/albums(?:/([^/]+?))(?:/([^/]+?))(?:/)?$",
301+
},
302+
];
303+
const result = handleRewrites(event, rewrites);
304+
305+
expect(result).toEqual({
306+
internalEvent: {
307+
...event,
308+
rawPath: "/rewrite/albums/foo/bar",
309+
url: "/rewrite/albums/foo/bar",
310+
},
311+
__rewrite: rewrites[1],
312+
isExternalRewrite: false,
313+
});
314+
});
315+
316+
it("should rewrite with matching path with has condition", () => {
317+
const event = createEvent({
318+
url: "/albums/foo",
319+
headers: {
320+
has: "true",
321+
},
322+
});
323+
324+
const rewrites = [
325+
{
326+
source: "/albums/:album",
327+
destination: "/rewrite/albums/:album",
328+
regex: "^/albums(?:/([^/]+?))(?:/)?$",
329+
has: [
330+
{
331+
type: "header",
332+
key: "has",
333+
},
334+
],
335+
},
336+
];
337+
const result = handleRewrites(event, rewrites);
338+
339+
expect(result).toEqual({
340+
internalEvent: {
341+
...event,
342+
rawPath: "/rewrite/albums/foo",
343+
url: "/rewrite/albums/foo",
344+
},
345+
__rewrite: rewrites[0],
346+
isExternalRewrite: false,
347+
});
348+
});
349+
350+
it("should rewrite with matching path with missing condition", () => {
351+
const event = createEvent({
352+
url: "/albums/foo",
353+
headers: {
354+
has: "true",
355+
},
356+
});
357+
358+
const rewrites = [
359+
{
360+
source: "/albums/:album",
361+
destination: "/rewrite/albums/:album",
362+
regex: "^/albums(?:/([^/]+?))(?:/)?$",
363+
missing: [
364+
{
365+
type: "header",
366+
key: "missing",
367+
},
368+
],
369+
},
370+
];
371+
const result = handleRewrites(event, rewrites);
372+
373+
expect(result).toEqual({
374+
internalEvent: {
375+
...event,
376+
rawPath: "/rewrite/albums/foo",
377+
url: "/rewrite/albums/foo",
378+
},
379+
__rewrite: rewrites[0],
380+
isExternalRewrite: false,
381+
});
382+
});
383+
});

0 commit comments

Comments
 (0)