Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export async function GET(request: Request, { params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params;
return Response.json({ slug });
}
10 changes: 10 additions & 0 deletions examples/e2e/app-router/app/methods/get/query/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import type { NextRequest } from "next/server";

export function GET(request: NextRequest) {
const searchParams = request.nextUrl.searchParams;
const query = searchParams.get("query");
if (query === "OpenNext is awesome!") {
return Response.json({ query });
}
return new Response("Internal Server Error", { status: 500 });
}
5 changes: 5 additions & 0 deletions examples/e2e/app-router/app/methods/get/redirect/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { redirect } from "next/navigation";

export async function GET(request: Request) {
redirect("https://nextjs.org/");
}
10 changes: 10 additions & 0 deletions examples/e2e/app-router/app/methods/get/revalidate/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export const revalidate = 5;

async function getTime() {
return new Date().toISOString();
}

export async function GET() {
const time = await getTime();
return Response.json({ time });
}
10 changes: 10 additions & 0 deletions examples/e2e/app-router/app/methods/get/static/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export const dynamic = "force-static";

async function getTime() {
return new Date().toISOString();
}

export async function GET() {
const time = await getTime();
return Response.json({ time });
}
19 changes: 19 additions & 0 deletions examples/e2e/app-router/app/methods/post/cookies/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { cookies } from "next/headers";

export async function POST(request: Request) {
const formData = await request.formData();
const username = formData.get("username");
const password = formData.get("password");
if (username === "hakuna" && password === "matata") {
(await cookies()).set("auth_session", "SUPER_SECRET_SESSION_ID_1234");
return Response.json(
{
message: "ok",
},
{
status: 202,
}
);
}
return Response.json({ message: "you must login" }, { status: 401 });
}
16 changes: 16 additions & 0 deletions examples/e2e/app-router/app/methods/post/formdata/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export async function POST(request: Request) {
const formData = await request.formData();
const name = formData.get("name");
const email = formData.get("email");
if (name === "OpenNext [] () %&#!%$#" && email === "[email protected]") {
return Response.json(
{
message: "ok",
},
{
status: 202,
}
);
}
return Response.json({ message: "forbidden" }, { status: 403 });
}
76 changes: 76 additions & 0 deletions examples/e2e/app-router/app/methods/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import type { NextRequest } from "next/server";

export async function GET() {
return Response.json({
message: "OpenNext is awesome! :) :] :> :D",
});
}

export async function POST(request: Request) {
const text = await request.text();
if (text === "OpenNext is awesome! :] :) :> :D") {
return Response.json(
{
message: "ok",
},
{
status: 202,
}
);
}
return Response.json({ message: "forbidden" }, { status: 403 });
}

export async function PUT(request: Request) {
const res = (await request.json()) as {
message: string;
};
if (res.message === "OpenNext PUT") {
return Response.json({ message: "ok" }, { status: 201 });
}
return Response.json({ message: "error" }, { status: 500 });
}

export async function PATCH(request: Request) {
const res = (await request.json()) as {
message: string;
};
if (res.message === "OpenNext PATCH") {
return Response.json(
{ message: "ok", modified: true, timestamp: new Date().toISOString() },
{ status: 202 }
);
}
return Response.json({ message: "error" }, { status: 500 });
}

export async function DELETE(request: NextRequest) {
const searchParams = request.nextUrl.searchParams;
const command = searchParams.get("command");
if (command === "rm -rf / --no-preserve-root") {
return new Response(null, { status: 204 });
}
return Response.json({ message: "error" }, { status: 500 });
}

export async function HEAD() {
return new Response("hello", {
status: 200,
headers: {
"content-type": "text/html; charset=utf-8",
// Once deployed to AWS this will always be 0
// "content-length": "1234567",
"special-header": "OpenNext is the best :) :] :> :D",
},
});
}

export async function OPTIONS() {
return new Response(null, {
status: 204,
headers: {
Allow: "GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, LOVE",
Special: "OpenNext is the best :) :] :> :D",
},
});
}
170 changes: 170 additions & 0 deletions examples/e2e/app-router/e2e/methods.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import { expect, test } from "@playwright/test";

test.describe("all supported methods should work in route handlers", () => {
test("GET", async ({ request }) => {
const getRes = await request.get("/methods");
const getData = await getRes.json();
expect(getRes.status()).toEqual(200);
expect(getData.message).toEqual("OpenNext is awesome! :) :] :> :D");
});

test("POST", async ({ request }) => {
const postRes = await request.post("/methods", {
headers: {
"Content-Type": "text/plain",
},
data: "OpenNext is awesome! :] :) :> :D",
});
expect(postRes.status()).toBe(202);
const postData = await postRes.json();
expect(postData.message).toBe("ok");
const errorPostRes = await request.post("/methods", {
headers: {
"Content-Type": "text/plain",
},
data: "OpenNext is not awesome! :C",
});
expect(errorPostRes.status()).toBe(403);
const errorData = await errorPostRes.json();
expect(errorData.message).toBe("forbidden");
});

test("PUT", async ({ request }) => {
const putRes = await request.put("/methods", {
data: {
message: "OpenNext PUT",
},
});
expect(putRes.status()).toEqual(201);
const putData = await putRes.json();
expect(putData.message).toEqual("ok");
});

test("PATCH", async ({ request }) => {
const timestampBefore = new Date();
const patchRes = await request.patch("/methods", {
data: { message: "OpenNext PATCH" },
});
expect(patchRes.status()).toEqual(202);
const patchData = await patchRes.json();
expect(patchData.message).toEqual("ok");
expect(patchData.modified).toEqual(true);
expect(Date.parse(patchData.timestamp)).toBeGreaterThan(timestampBefore.getTime());
});

test("DELETE", async ({ request }) => {
const deleteRes = await request.delete("/methods", {
params: {
command: "rm -rf / --no-preserve-root",
},
});
expect(deleteRes.status()).toEqual(204);
});

test("HEAD", async ({ request }) => {
const headRes = await request.head("/methods");
expect(headRes.status()).toEqual(200);
const headers = headRes.headers();
expect(headers["content-type"]).toEqual("text/html; charset=utf-8");
// expect(headers["content-length"]).toEqual("1234567");
expect(headers["special-header"]).toEqual("OpenNext is the best :) :] :> :D");
});

test("OPTIONS", async ({ request }) => {
const optionsRes = await request.fetch("/methods", {
method: "OPTIONS",
});
expect(optionsRes.status()).toEqual(204);
const headers = optionsRes.headers();
expect(headers.allow).toBe("GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, LOVE");
expect(headers.special).toBe("OpenNext is the best :) :] :> :D");
});
});

test("formData should work in POST route handler", async ({ request }) => {
const formData = new FormData();
formData.append("name", "OpenNext [] () %&#!%$#");
formData.append("email", "[email protected]");
const postRes = await request.post("/methods/post/formdata", {
form: formData,
});
expect(postRes.status()).toBe(202);
const postData = await postRes.json();
expect(postData.message).toBe("ok");
});

test("revalidate should work in GET route handler", async ({ request, page }) => {
let time = Date.parse((await request.get("/methods/get/revalidate").then((res) => res.json())).time);
let newTime: number;
let tempTime = time;
do {
await page.waitForTimeout(1000);
time = tempTime;
const newTimeRes = await request.get("/methods/get/revalidate");
newTime = Date.parse((await newTimeRes.json()).time);
tempTime = newTime;
} while (time !== newTime);
const midTime = Date.parse((await request.get("/methods/get/revalidate").then((res) => res.json())).time);

await page.waitForTimeout(1000);
// Expect that the time is still stale
expect(midTime).toEqual(newTime);

// Wait 5 + 1 seconds for ISR to regenerate time
await page.waitForTimeout(6000);
let finalTime = newTime;
do {
await page.waitForTimeout(2000);
finalTime = Date.parse((await request.get("/methods/get/revalidate").then((res) => res.json())).time);
} while (newTime === finalTime);

expect(newTime).not.toEqual(finalTime);
});

test("should cache a static GET route", async ({ request }) => {
const res = await request.get("/methods/get/static");
expect(res.headers()["cache-control"]).toContain("s-maxage=31536000");
});

test("should be able to set cookies in route handler", async ({ request }) => {
const postRes = await request.post("/methods/post/cookies", {
form: {
username: "hakuna",
password: "matata",
},
});
expect(postRes.status()).toBe(202);
const postData = await postRes.json();
expect(postData.message).toBe("ok");
const cookies = postRes.headers()["set-cookie"];
expect(cookies).toContain("auth_session=SUPER_SECRET_SESSION_ID_1234");
});

test("should be able to redirect in route handler", async ({ request }) => {
const redirectRes = await request.get("/methods/get/redirect", {
// Disable auto-redirect to check initial response
maxRedirects: 0,
});
expect(redirectRes.status()).toBe(307);
expect(redirectRes.headers().location).toBe("https://nextjs.org/");

// Check if the redirect works
const followedRes = await request.get("/methods/get/redirect");
expect(followedRes.url()).toBe("https://nextjs.org/");
});

test("dynamic segments should work in route handlers", async ({ request }) => {
const res = await request.get("/methods/get/dynamic-segments/this-is-a-slug");
const data = await res.json();
expect(data.slug).toBe("this-is-a-slug");
});

test("query parameters should work in route handlers", async ({ request }) => {
const res = await request.get("/methods/get/query", {
params: {
query: "OpenNext is awesome!",
},
});
const data = await res.json();
expect(data.query).toBe("OpenNext is awesome!");
});
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"devDependencies": {
"@changesets/changelog-github": "^0.5.0",
"@changesets/cli": "^2.27.9",
"@playwright/test": "1.47.0",
"@playwright/test": "catalog:",
"pkg-pr-new": "^0.0.29",
"prettier": "3.3.3"
},
Expand Down
Loading