-
Notifications
You must be signed in to change notification settings - Fork 177
add more e2e for route handlers in app-router #780
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 10 commits
33006dc
0dc9761
37cc7e0
025d2e2
cda7ede
1ebf709
89a8451
20736bc
119e4d6
619ce01
3b094ff
570b2dc
43bd42d
eaee64d
0a10078
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| --- | ||
| "@opennextjs/aws": patch | ||
| --- | ||
|
|
||
| Add more e2e for route handlers in app-router example app | ||
|
|
||
| - All supported HTTP methods (GET, POST, PUT, PATCH, DELETE, HEAD, and OPTIONS) | ||
| - That formData works in POST route handler | ||
| - That revalidation (ISR) works in GET route handler | ||
| - That you can cache a GET route with force-static | ||
| - That you can set cookies in a route handler | ||
| - That you should be able to redirect in a route handler | ||
| - Dynamic segments in a route handler | ||
| - Query parameters |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -46,6 +46,9 @@ | |
| "rules": { | ||
| "suspicious": { | ||
| "noRedeclare": "off" | ||
| }, | ||
| "complexity": { | ||
| "useLiteralKeys": "off" | ||
| } | ||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| export async function GET( | ||
| request: Request, | ||
| { params }: { params: Promise<{ slug: string }> }, | ||
| ) { | ||
| const { slug } = await params; | ||
| return Response.json({ slug }); | ||
| } |
| 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 }); | ||
| } |
| 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/"); | ||
| } |
| 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 }); | ||
| } |
| 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 }); | ||
| } |
| 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 }); | ||
| } |
| 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 }); | ||
| } |
| 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", | ||
| }, | ||
| }); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,193 @@ | ||
| 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"]).toContain("GET"); | ||
| expect(headers["allow"]).toContain("HEAD"); | ||
| expect(headers["allow"]).toContain("POST"); | ||
| expect(headers["allow"]).toContain("PUT"); | ||
| expect(headers["allow"]).toContain("PATCH"); | ||
| expect(headers["allow"]).toContain("DELETE"); | ||
| expect(headers["allow"]).toContain("OPTIONS"); | ||
| expect(headers["allow"]).toContain("LOVE"); | ||
| expect(headers["special"]).toContain("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, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Posting a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nvm updating Playwright fixes this |
||
| }); | ||
| 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"]).toBe("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!"); | ||
| }); | ||
Uh oh!
There was an error while loading. Please reload this page.