-
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
Merged
Merged
Changes from all commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
33006dc
add methods to app-router
sommeeeer 0dc9761
add additional routes
sommeeeer 37cc7e0
add tests for additional routes
sommeeeer 025d2e2
lint
sommeeeer cda7ede
changeset
sommeeeer 1ebf709
Delete .changeset/moody-monkeys-explode.md
sommeeeer 89a8451
changeset patch
sommeeeer 20736bc
Merge branch 'verbs' of github.com:sommeeeer/opennextjs-aws into verbs
sommeeeer 119e4d6
fix cookies
sommeeeer 619ce01
remove next gh issue
sommeeeer 3b094ff
literal keys
sommeeeer 570b2dc
toBe instead of toContain
sommeeeer 43bd42d
lint
sommeeeer eaee64d
rm changeset
sommeeeer 0a10078
toContain in cookies test
sommeeeer File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
7 changes: 7 additions & 0 deletions
7
examples/app-router/app/methods/get/dynamic-segments/[slug]/route.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 }); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 }); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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/"); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 }); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 }); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 }); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 }); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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", | ||
| }, | ||
| }); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,188 @@ | ||
| 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"]).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!"); | ||
| }); | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Posting a
FormDatadoesn't work for Cloudflare, posting an object is fine (as in the cookies test). Wondering why it works here?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nvm updating Playwright fixes this