Skip to content

Commit 6180905

Browse files
committed
Add unit tests for handling middleware
1 parent 6afd7ab commit 6180905

File tree

2 files changed

+169
-2
lines changed

2 files changed

+169
-2
lines changed

packages/open-next/src/core/routing/middleware.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ type MiddlewareOutputEvent = InternalEvent & {
2828
externalRewrite?: boolean;
2929
};
3030

31+
type MiddlewareLoader = (path: string) => Promise<any>;
32+
function defaultMiddlewareLoader(path: string) {
33+
return import(path);
34+
}
35+
3136
// NOTE: As of Nextjs 13.4.13+, the middleware is handled outside the next-server.
3237
// OpenNext will run the middleware in a sandbox and set the appropriate req headers
3338
// and res.body prior to processing the next-server.
@@ -36,6 +41,7 @@ type MiddlewareOutputEvent = InternalEvent & {
3641
// if res.end() is return, the parent needs to return and not process next server
3742
export async function handleMiddleware(
3843
internalEvent: InternalEvent,
44+
middlewareLoader: MiddlewareLoader = defaultMiddlewareLoader,
3945
): Promise<MiddlewareOutputEvent | InternalResult> {
4046
const { query } = internalEvent;
4147
const normalizedPath = localizePath(internalEvent);
@@ -53,8 +59,7 @@ export async function handleMiddleware(
5359
const url = initialUrl.toString();
5460
// console.log("url", url, normalizedPath);
5561

56-
// @ts-expect-error - This is bundled
57-
const middleware = await import("./middleware.mjs");
62+
const middleware = await middlewareLoader("./middleware.mjs");
5863

5964
const result: Response = await middleware.default({
6065
geo: {
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
import { handleMiddleware } from "@opennextjs/aws/core/routing/middleware.js";
2+
import { convertFromQueryString } from "@opennextjs/aws/core/routing/util.js";
3+
import { InternalEvent } from "@opennextjs/aws/types/open-next.js";
4+
import { toReadableStream } from "@opennextjs/aws/utils/stream.js";
5+
import { vi } from "vitest";
6+
7+
vi.mock("@opennextjs/aws/adapters/config/index.js", () => ({
8+
NextConfig: {},
9+
MiddlewareManifest: {
10+
sortedMiddleware: ["/"],
11+
middleware: {
12+
"/": {
13+
files: [
14+
"prerender-manifest.js",
15+
"server/edge-runtime-webpack.js",
16+
"server/middleware.js",
17+
],
18+
name: "middleware",
19+
page: "/",
20+
matchers: [
21+
{
22+
regexp:
23+
"^(?:\\/(_next\\/data\\/[^/]{1,}))?(?:\\/((?!_next|favicon.ico|match|static|fonts|api\\/auth|og).*))(.json)?[\\/#\\?]?$",
24+
originalSource:
25+
"/((?!_next|favicon.ico|match|static|fonts|api/auth|og).*)",
26+
},
27+
],
28+
wasm: [],
29+
assets: [],
30+
},
31+
},
32+
functions: {},
33+
version: 2,
34+
},
35+
}));
36+
37+
vi.mock("@opennextjs/aws/core/routing/i18n/index.js", () => ({
38+
localizePath: (event: InternalEvent) => event.rawPath,
39+
}));
40+
41+
const middleware = vi.fn();
42+
const middlewareLoader = vi.fn().mockResolvedValue({
43+
default: middleware,
44+
});
45+
46+
type PartialEvent = Partial<
47+
Omit<InternalEvent, "body" | "rawPath" | "query">
48+
> & { body?: string };
49+
50+
function createEvent(event: PartialEvent): InternalEvent {
51+
const [rawPath, qs] = (event.url ?? "/").split("?", 2);
52+
return {
53+
type: "core",
54+
method: event.method ?? "GET",
55+
rawPath,
56+
url: event.url ?? "/",
57+
body: Buffer.from(event.body ?? ""),
58+
headers: event.headers ?? {},
59+
query: convertFromQueryString(qs ?? ""),
60+
cookies: event.cookies ?? {},
61+
remoteAddress: event.remoteAddress ?? "::1",
62+
};
63+
}
64+
65+
beforeEach(() => {
66+
vi.clearAllMocks();
67+
});
68+
69+
describe("handleMiddleware", () => {
70+
it("should bypass middlware for internal requests", async () => {
71+
const event = createEvent({
72+
headers: {
73+
"x-isr": "1",
74+
},
75+
});
76+
const result = await handleMiddleware(event, middlewareLoader);
77+
78+
expect(middlewareLoader).not.toBeCalled();
79+
expect(result).toEqual(event);
80+
});
81+
82+
it("should invoke middlware with redirect", async () => {
83+
const event = createEvent({});
84+
middleware.mockResolvedValue({
85+
status: 302,
86+
headers: new Headers({
87+
location: "/redirect",
88+
}),
89+
});
90+
const result = await handleMiddleware(event, middlewareLoader);
91+
92+
expect(middlewareLoader).toBeCalled();
93+
expect(result.statusCode).toEqual(302);
94+
expect(result.headers.location).toEqual("/redirect");
95+
});
96+
97+
it("should invoke middlware with rewrite", async () => {
98+
const event = createEvent({
99+
headers: {
100+
host: "localhost",
101+
},
102+
});
103+
middleware.mockResolvedValue({
104+
headers: new Headers({
105+
"x-middleware-rewrite": "http://localhost/rewrite",
106+
}),
107+
});
108+
const result = await handleMiddleware(event, middlewareLoader);
109+
110+
expect(middlewareLoader).toBeCalled();
111+
expect(result).toEqual({
112+
...event,
113+
rawPath: "/rewrite",
114+
url: "/rewrite",
115+
responseHeaders: {
116+
"x-middleware-rewrite": "http://localhost/rewrite",
117+
},
118+
externalRewrite: false,
119+
});
120+
});
121+
122+
it("should map x-middleware-request- headers as request headers", async () => {
123+
const event = createEvent({});
124+
middleware.mockResolvedValue({
125+
headers: new Headers({
126+
"x-middleware-request-custom-header": "value",
127+
}),
128+
});
129+
const result = await handleMiddleware(event, middlewareLoader);
130+
131+
expect(middlewareLoader).toBeCalled();
132+
expect(result).toEqual({
133+
...event,
134+
headers: {
135+
"custom-header": "value",
136+
},
137+
responseHeaders: {},
138+
externalRewrite: false,
139+
});
140+
});
141+
142+
it("should return a response from middleware", async () => {
143+
const event = createEvent({});
144+
const body = toReadableStream("Hello, world!");
145+
146+
middleware.mockResolvedValue({
147+
status: 200,
148+
headers: new Headers(),
149+
body,
150+
});
151+
const result = await handleMiddleware(event, middlewareLoader);
152+
153+
expect(middlewareLoader).toBeCalled();
154+
expect(result).toEqual({
155+
type: "core",
156+
statusCode: 200,
157+
headers: {},
158+
body,
159+
isBase64Encoded: false,
160+
});
161+
});
162+
});

0 commit comments

Comments
 (0)