Skip to content
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

test: add dynamic routes and suspense test case for onRequestError #67848

Merged
merged 5 commits into from
Jul 17, 2024
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
7 changes: 7 additions & 0 deletions test/e2e/on-request-error/_testing/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export async function getOutputLogJson(next, outputLogPath) {
if (!(await next.hasFile(outputLogPath))) {
return {}
}
const content = await next.readFile(outputLogPath)
return JSON.parse(content)
}
18 changes: 6 additions & 12 deletions test/e2e/on-request-error/basic/basic.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { nextTestSetup } from 'e2e-utils'
import { retry } from 'next-test-utils'
import { getOutputLogJson } from '../_testing/utils'

describe('on-request-error - basic', () => {
const { next, skipped } = nextTestSetup({
Expand All @@ -16,14 +17,6 @@ describe('on-request-error - basic', () => {

const outputLogPath = 'output-log.json'

async function getOutputLogJson() {
if (!(await next.hasFile(outputLogPath))) {
return {}
}
const content = await next.readFile(outputLogPath)
return JSON.parse(content)
}

async function validateErrorRecord({
errorMessage,
url,
Expand All @@ -37,14 +30,15 @@ describe('on-request-error - basic', () => {
}) {
// Assert the instrumentation is called
await retry(async () => {
const recordLogs = next.cliOutput
const recordLogLines = next.cliOutput
.split('\n')
.filter((log) => log.includes('[instrumentation] write-log'))
const expectedLog = recordLogs.find((log) => log.includes(errorMessage))
expect(expectedLog).toBeDefined()
expect(recordLogLines).toEqual(
expect.arrayContaining([expect.stringContaining(errorMessage)])
)
}, 5000)

const json = await getOutputLogJson()
const json = await getOutputLogJson(next, outputLogPath)
const record = json[errorMessage]

const { payload } = record
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export default function Page() {
throw new Error('server-dynamic-page-node-error')
}

export const dynamic = 'force-dynamic'
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Suspense } from 'react'

export default function Page() {
return (
<Suspense>
<Inner />
</Suspense>
)
}

function Inner() {
if (typeof window === 'undefined') {
throw new Error('server-suspense-page-node-error')
}
return 'inner'
}

export const dynamic = 'force-dynamic'
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export function GET() {
throw new Error('server-dynamic-route-node-error')
}

export const dynamic = 'force-dynamic'
12 changes: 12 additions & 0 deletions test/e2e/on-request-error/dynamic-routes/app/layout.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export const metadata = {
title: 'Next.js',
description: 'Generated by Next.js',
}

export default function RootLayout({ children }) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}
40 changes: 40 additions & 0 deletions test/e2e/on-request-error/dynamic-routes/app/write-log/route.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import fs from 'fs'
import fsp from 'fs/promises'
import path from 'path'

const dir = path.join(path.dirname(new URL(import.meta.url).pathname), '../..')
const logPath = path.join(dir, 'output-log.json')

export async function POST(req) {
let payloadString = ''
const decoder = new TextDecoder()
const reader = req.clone().body.getReader()
while (true) {
const { done, value } = await reader.read()
if (done) {
break
}

payloadString += decoder.decode(value)
}

const payload = JSON.parse(payloadString)

const json = fs.existsSync(logPath)
? JSON.parse(await fsp.readFile(logPath, 'utf8'))
: {}

if (!json[payload.message]) {
json[payload.message] = {
payload,
count: 1,
}
} else {
json[payload.message].count++
}

await fsp.writeFile(logPath, JSON.stringify(json, null, 2), 'utf8')

console.log(`[instrumentation] write-log:${payload.message}`)
return new Response(null, { status: 204 })
}
147 changes: 147 additions & 0 deletions test/e2e/on-request-error/dynamic-routes/dynamic-routes.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import { nextTestSetup } from 'e2e-utils'
import { retry } from 'next-test-utils'
import { getOutputLogJson } from '../_testing/utils'

describe('on-request-error - dynamic-routes', () => {
const { next, skipped } = nextTestSetup({
files: __dirname,
skipDeployment: true,
env: {
__NEXT_EXPERIMENTAL_INSTRUMENTATION: '1',
},
})

if (skipped) {
return
}

const outputLogPath = 'output-log.json'

async function getErrorRecord({ errorMessage }: { errorMessage: string }) {
// Assert the instrumentation is called
await retry(async () => {
const recordLogLines = next.cliOutput
.split('\n')
.filter((log) => log.includes('[instrumentation] write-log'))
expect(recordLogLines).toEqual(
expect.arrayContaining([expect.stringContaining(errorMessage)])
)
}, 5000)

const json = await getOutputLogJson(next, outputLogPath)
const record = json[errorMessage]

return record
}

beforeAll(async () => {
await next.patchFile(outputLogPath, '{}')
})

describe('app router', () => {
it('should catch app router dynamic page error with search params', async () => {
await next.fetch('/app-page/dynamic/123?apple=dope')
const record = await getErrorRecord({
errorMessage: 'server-dynamic-page-node-error',
})
expect(record).toMatchObject({
payload: {
message: 'server-dynamic-page-node-error',
request: {
url: '/app-page/dynamic/123?apple=dope',
},
context: {
routerKind: 'App Router',
routeType: 'render',
routePath: '/app-page/dynamic/[id]',
},
},
})
})

it('should catch app router dynamic routes error with search params', async () => {
await next.fetch('/app-route/dynamic/123?apple=dope')
const record = await getErrorRecord({
errorMessage: 'server-dynamic-route-node-error',
})
expect(record).toMatchObject({
payload: {
message: 'server-dynamic-route-node-error',
request: {
url: '/app-route/dynamic/123?apple=dope',
},
context: {
routerKind: 'App Router',
routeType: 'route',
routePath: '/app-route/dynamic/[id]',
},
},
})
})

it('should catch suspense rendering page error in node runtime', async () => {
await next.fetch('/app-page/suspense')
const record = await getErrorRecord({
errorMessage: 'server-suspense-page-node-error',
})

expect(record).toMatchObject({
payload: {
message: 'server-suspense-page-node-error',
request: {
url: '/app-page/suspense',
},
context: {
routerKind: 'App Router',
routeType: 'render',
routePath: '/app-page/suspense',
},
huozhi marked this conversation as resolved.
Show resolved Hide resolved
},
})
})
})

describe('pages router', () => {
it('should catch pages router dynamic page error with search params', async () => {
await next.fetch('/pages-page/dynamic/123?apple=dope')
const record = await getErrorRecord({
errorMessage: 'pages-page-node-error',
})

expect(record).toMatchObject({
payload: {
message: 'pages-page-node-error',
request: {
url: '/pages-page/dynamic/123?apple=dope',
},
context: {
routerKind: 'Pages Router',
routeType: 'render',
routePath: '/pages-page/dynamic/[id]',
},
},
})
})

it('should catch pages router dynamic API route error with search params', async () => {
await next.fetch('/api/dynamic/123?apple=dope')
const record = await getErrorRecord({
errorMessage: 'pages-api-node-error',
})

expect(record).toMatchObject({
payload: {
message: 'pages-api-node-error',
request: {
url: '/api/dynamic/123?apple=dope',
},
context: {
routerKind: 'Pages Router',
routeType: 'route',
routePath: '/api/dynamic/[id]',
},
},
})
})
})
})
13 changes: 13 additions & 0 deletions test/e2e/on-request-error/dynamic-routes/instrumentation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export function onRequestError(err, request, context) {
fetch(`http://localhost:${process.env.PORT}/write-log`, {
method: 'POST',
body: JSON.stringify({
message: err.message,
request,
context,
}),
headers: {
'Content-Type': 'application/json',
},
})
}
5 changes: 5 additions & 0 deletions test/e2e/on-request-error/dynamic-routes/next.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module.exports = {
experimental: {
instrumentationHook: true,
},
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function handler() {
throw new Error('pages-api-node-error')
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export default function Page() {
throw new Error('pages-page-node-error')
}

export function getServerSideProps() {
return {
props: {},
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { nextTestSetup } from 'e2e-utils'
import { retry } from 'next-test-utils'
import { getOutputLogJson } from '../_testing/utils'

describe('on-request-error - server-action-error', () => {
const { next, skipped } = nextTestSetup({
Expand All @@ -16,14 +17,6 @@ describe('on-request-error - server-action-error', () => {

const outputLogPath = 'output-log.json'

async function getOutputLogJson() {
if (!(await next.hasFile(outputLogPath))) {
return {}
}
const content = await next.readFile(outputLogPath)
return JSON.parse(content)
}

async function validateErrorRecord({
errorMessage,
url,
Expand All @@ -42,7 +35,7 @@ describe('on-request-error - server-action-error', () => {
)
}, 5000)

const json = await getOutputLogJson()
const json = await getOutputLogJson(next, outputLogPath)
const record = json[errorMessage]

// Assert error is recorded in the output log
Expand Down
Loading