first commit
Some checks failed
Test examples / Test Examples (20) (push) Has been cancelled
Test examples / Test Examples (22) (push) Has been cancelled
Lock Threads / action (push) Has been cancelled
Trigger Release / start (push) Has been cancelled
Stale issue handler / stale (push) Has been cancelled
Update Font Data / create-pull-request (push) Has been cancelled
build-and-deploy / deploy-target (push) Has been cancelled
build-and-deploy / build (push) Has been cancelled
build-and-deploy / stable - aarch64-unknown-linux-musl - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-unknown-linux-musl - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-unknown-linux-gnu - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-unknown-linux-gnu - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-pc-windows-msvc - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-pc-windows-msvc - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-apple-darwin - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-apple-darwin - node@16 (push) Has been cancelled
build-and-deploy / build-wasm (nodejs) (push) Has been cancelled
build-and-deploy / build-wasm (web) (push) Has been cancelled
build-and-deploy / Deploy preview tarball (push) Has been cancelled
build-and-deploy / Potentially publish release (push) Has been cancelled
build-and-deploy / publish-turbopack-npm-packages (push) Has been cancelled
build-and-deploy / Deploy examples (push) Has been cancelled
build-and-deploy / thank you, build (push) Has been cancelled
build-and-deploy / Upload Turbopack Bytesize metrics to Datadog (push) Has been cancelled
Rspack Next.js development integration tests / Rspack integration tests (push) Has been cancelled
Rspack Next.js production integration tests / Rspack integration tests (push) Has been cancelled
Turbopack Next.js development integration tests / Next.js integration tests (push) Has been cancelled
Turbopack Next.js production integration tests / Next.js integration tests (push) Has been cancelled
Update Rspack test manifest / Update and upload Rspack development test manifest (push) Has been cancelled
Update Rspack test manifest / Update and upload Rspack production test manifest (push) Has been cancelled
Upload bundler test manifests to areweturboyet.com / Upload test results (push) Has been cancelled
Update React / create-pull-request (push) Has been cancelled
test-e2e-project-reset-cron / reset-test-project (push) Has been cancelled
Notify about the top 15 issues/PRs/feature requests (most reacted) in the last 90 days / run (push) Has been cancelled

This commit is contained in:
Arian Tron
2026-03-10 19:37:31 +03:30
commit 61f56f997c
27684 changed files with 2784175 additions and 0 deletions

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,3 @@
export default function Page() {
return <div>API - conflict</div>
}

View File

@@ -0,0 +1,3 @@
export default ({ query }, res) => {
res.status(200).json(query)
}

View File

@@ -0,0 +1,3 @@
export default (req, res) => {
res.status(200).json([{ message: 'Prioritize a non-dynamic api page' }])
}

View File

@@ -0,0 +1,4 @@
export default (req, res) => {
const { query } = req
res.status(200).json(query)
}

View File

@@ -0,0 +1,3 @@
export default function handler(req, res) {
res.json({ from: 'auth' })
}

View File

@@ -0,0 +1,11 @@
export const config = {
api: {
bodyParser: {
sizeLimit: '5mb',
},
},
}
export default (req, res) => {
res.status(200).json(req.body)
}

View File

@@ -0,0 +1,13 @@
import { NextApiRequest, NextApiResponse } from 'next'
export const config = {
api: {
bodyParser: {
sizeLimit: '5mb',
},
},
}
export default (req: NextApiRequest, res: NextApiResponse) => {
res.status(200).json(req.body)
}

View File

@@ -0,0 +1,3 @@
export default ({ query }, res) => {
res.status(200).json(query)
}

View File

@@ -0,0 +1,7 @@
export default ({ query, method }, res) => {
if (method === 'POST') {
res.status(200).json([{ title: query.title }])
} else {
res.status(200).json([{ title: 'Cool Post!' }])
}
}

View File

@@ -0,0 +1,3 @@
export default (req, res) => {
res.json(true)
}

View File

@@ -0,0 +1,6 @@
import { execSync } from 'child_process'
export default (req, res) => {
const output = execSync('echo hi').toString().trim()
res.end(output)
}

View File

@@ -0,0 +1,3 @@
export default (req, res) => {
res.status(200).send(req.cookies)
}

View File

@@ -0,0 +1,32 @@
import Cors from 'cors'
// Helper method to wait for a middleware to execute before continuing
// And to throw an error when an error happens in a middleware
function initMiddleware(middleware) {
return (req, res) =>
new Promise((resolve, reject) => {
middleware(req, res, (result) => {
if (result instanceof Error) {
return reject(result)
}
return resolve(result)
})
})
}
// Initialize the cors middleware
const cors = initMiddleware(
// You can read more about the available options here: https://github.com/expressjs/cors#configuration-options
Cors({
// Only allow requests with GET, POST and OPTIONS
methods: ['GET', 'POST', 'OPTIONS'],
})
)
export default async function handler(req, res) {
// Run cors
await cors(req, res)
// Rest of the API logic
res.json({ message: 'Hello Everyone!' })
}

View File

@@ -0,0 +1,3 @@
export default (req, res) => {
res.status(500).json({ error: 'Server error!' })
}

View File

@@ -0,0 +1,5 @@
export default (_req, res) => {
setTimeout(() => {
res.send('hello world')
}, 0)
}

View File

@@ -0,0 +1,11 @@
export default (_req, res) => {
setTimeout(() => {
res.send('hello world')
}, 0)
}
export const config = {
api: {
externalResolver: true,
},
}

View File

@@ -0,0 +1,6 @@
/* eslint-disable-next-line */
import fs from 'fs'
export default (req, res) => {
res.send('Index should work')
}

View File

@@ -0,0 +1,3 @@
export default (req, res) => {
res.json(null)
}

View File

@@ -0,0 +1,3 @@
export default (req, res) => {
res.json('Hello world!')
}

View File

@@ -0,0 +1,3 @@
export default (req, res) => {
res.json(undefined)
}

View File

@@ -0,0 +1,6 @@
export default (req, res) => {
for (let i = 0; i <= 4 * 1024 * 1024; i++) {
res.write('.')
}
res.end()
}

View File

@@ -0,0 +1,10 @@
export const config = {
api: {
responseLimit: '5mb',
},
}
export default (req, res) => {
let body = '.'.repeat(6 * 1024 * 1024)
res.send(body)
}

View File

@@ -0,0 +1,10 @@
export const config = {
api: {
responseLimit: false,
},
}
export default (req, res) => {
let body = '.'.repeat(4 * 1024 * 1024)
res.send(body)
}

View File

@@ -0,0 +1,4 @@
export default (req, res) => {
let body = '.'.repeat(4 * 1024 * 1024)
res.send(body)
}

View File

@@ -0,0 +1,21 @@
export const config = {
api: {
bodyParser: false,
},
}
export default (req, res) => {
return new Promise((resolve) => {
if (!req.body) {
let buffer = ''
req.on('data', (chunk) => {
buffer += chunk
})
req.on('end', () => {
res.status(200).json(JSON.parse(Buffer.from(buffer).toString()))
resolve()
})
}
})
}

View File

@@ -0,0 +1,4 @@
export default (req, res) => {
res.write('')
res.end()
}

View File

@@ -0,0 +1,3 @@
export default (req, res) => {
res.status(200).json(req.body)
}

View File

@@ -0,0 +1,11 @@
export const config = {
api: {
bodyParser: true,
},
}
export default (req, res) => {
if (req.body) {
res.status(200).json({ message: 'Parsed body' })
}
}

View File

@@ -0,0 +1,21 @@
import httpProxy from 'http-proxy'
export default async function handler(req, res) {
const port = req.headers.host.split(':').pop()
const proxy = httpProxy.createProxy({
target: `http://127.0.0.1:${port}/${
req.query.buildId
? process.env.NEXT_DEPLOYMENT_ID
? `_next/static/${req.query.buildId}/_ssgManifest.js?dpl=${process.env.NEXT_DEPLOYMENT_ID}`
: `_next/static/${req.query.buildId}/_ssgManifest.js`
: `user`
}`,
ignorePath: true,
})
await new Promise((resolve, reject) => {
proxy.on('error', reject)
proxy.on('close', resolve)
proxy.web(req, res)
})
}

View File

@@ -0,0 +1,3 @@
export default (req, res) => {
res.status(200).send(req.query)
}

View File

@@ -0,0 +1,3 @@
export default (req, res) => {
res.redirect(301, '/login')
}

View File

@@ -0,0 +1,3 @@
export default (req, res) => {
res.redirect('/login')
}

View File

@@ -0,0 +1,3 @@
export default (req, res) => {
res.redirect(307)
}

View File

@@ -0,0 +1,3 @@
export default (req, res) => {
res.redirect(null)
}

View File

@@ -0,0 +1,7 @@
export default function handler(req, res) {
if (req.query.invalid) {
// test the warning when content is added for a 204 response
return res.status(204).json({ hello: 'world' })
}
return res.status(204).send()
}

View File

@@ -0,0 +1,4 @@
export default (req, res) => {
console.log('hi')
return {}
}

View File

@@ -0,0 +1,10 @@
import fetch from 'node-fetch'
export default async (req, res) => {
const dataRes = await fetch(
`http://localhost:${req.query.port}/api/query?hello=from-pipe`
)
res.status(dataRes.status)
dataRes.body.pipe(res)
}

View File

@@ -0,0 +1,3 @@
export default async (req, res) => {
throw new Error('User error')
}

View File

@@ -0,0 +1,3 @@
export default (req, res) => {
throw new Error('User error')
}

View File

@@ -0,0 +1,9 @@
export default ({ query }, res) => {
const users = [{ name: 'Tim' }, { name: 'Jon' }]
const response = query.name
? users.filter((user) => user.name === query.name)
: users
res.status(200).json(response)
}

View File

@@ -0,0 +1,3 @@
export default function Page() {
return <div>API - support</div>
}

View File

@@ -0,0 +1,9 @@
export default function Page() {
return <div>User</div>
}
export function getServerSideProps() {
return {
props: {},
}
}

View File

@@ -0,0 +1,676 @@
/* eslint-env jest */
import fs from 'fs-extra'
import { join } from 'path'
import AbortController from 'abort-controller'
import {
killApp,
findPort,
launchApp,
fetchViaHTTP,
File,
renderViaHTTP,
nextBuild,
nextStart,
getPageFileFromBuildManifest,
getPageFileFromPagesManifest,
check,
} from 'next-test-utils'
import json from '../big.json'
const appDir = join(__dirname, '../')
const originalIsNextDev = global.isNextDev
let appPort
let stderr
let mode
let app
function runTests(dev = false) {
beforeAll(() => {
// isNextDev is used for getDistDir, where it is used for reading the build manifest files.
global.isNextDev = dev
})
afterAll(() => {
global.isNextDev = originalIsNextDev
})
it('should not strip .json from API route', async () => {
const res = await fetchViaHTTP(appPort, '/api/hello.json')
expect(res.status).toBe(200)
expect(await res.json()).toEqual({ post: 'hello.json' })
})
it('should handle proxying to self correctly', async () => {
const res1 = await fetchViaHTTP(appPort, '/api/proxy-self')
expect(res1.status).toBe(200)
expect(await res1.text()).toContain('User')
const buildId = dev
? 'development'
: await fs.readFile(join(appDir, '.next', 'BUILD_ID'), 'utf8')
const res2 = await fetchViaHTTP(
appPort,
`/api/proxy-self?buildId=${buildId}`
)
expect(res2.status).toBe(200)
expect(await res2.text()).toContain('__SSG_MANIFEST')
})
it('should respond from /api/auth/[...nextauth] correctly', async () => {
const res = await fetchViaHTTP(appPort, '/api/auth/signin', undefined, {
redirect: 'manual',
})
expect(res.status).toBe(200)
expect(await res.json()).toEqual({ from: 'auth' })
})
it('should handle 204 status correctly', async () => {
const res = await fetchViaHTTP(appPort, '/api/status-204', undefined, {
redirect: 'manual',
})
expect(res.status).toBe(204)
expect(res.headers.get('content-type')).toBe(null)
expect(res.headers.get('content-length')).toBe(null)
expect(res.headers.get('transfer-encoding')).toBe(null)
const stderrIdx = stderr.length
const res2 = await fetchViaHTTP(
appPort,
'/api/status-204',
{ invalid: '1' },
{
redirect: 'manual',
}
)
expect(res2.status).toBe(204)
expect(res2.headers.get('content-type')).toBe(null)
expect(res2.headers.get('content-length')).toBe(null)
expect(res2.headers.get('transfer-encoding')).toBe(null)
if (dev) {
await check(
() => stderr.slice(stderrIdx),
/A body was attempted to be set with a 204 statusCode/
)
}
})
it('should render page', async () => {
const html = await renderViaHTTP(appPort, '/')
expect(html).toMatch(/API - support/)
})
it('should return 404 for undefined path', async () => {
const { status } = await fetchViaHTTP(
appPort,
'/api/not/unexisting/page/really',
null,
{}
)
expect(status).toEqual(404)
})
it('should not conflict with /api routes', async () => {
const res = await fetchViaHTTP(appPort, '/api-conflict')
expect(res.status).not.toEqual(404)
})
it('should set cors headers when adding cors middleware', async () => {
const res = await fetchViaHTTP(appPort, '/api/cors', null, {
method: 'OPTIONS',
headers: {
origin: 'example.com',
},
})
expect(res.status).toEqual(204)
expect(res.headers.get('access-control-allow-methods')).toEqual(
'GET,POST,OPTIONS'
)
})
it('should work with index api', async () => {
const text = await fetchViaHTTP(appPort, '/api', null, {}).then(
(res) => res.ok && res.text()
)
expect(text).toEqual('Index should work')
})
it('should return custom error', async () => {
const data = await fetchViaHTTP(appPort, '/api/error', null, {})
const json = await data.json()
expect(data.status).toEqual(500)
expect(json).toEqual({ error: 'Server error!' })
})
it('should throw Internal Server Error', async () => {
const res = await fetchViaHTTP(appPort, '/api/user-error', null, {})
const text = await res.text()
expect(res.status).toBe(500)
if (dev) {
expect(text).toContain('User error')
} else {
expect(text).toBe('Internal Server Error')
}
})
it('should throw Internal Server Error (async)', async () => {
const res = await fetchViaHTTP(appPort, '/api/user-error-async', null, {})
const text = await res.text()
expect(res.status).toBe(500)
if (dev) {
expect(text).toContain('User error')
} else {
expect(text).toBe('Internal Server Error')
}
})
it('should parse JSON body', async () => {
const data = await fetchViaHTTP(appPort, '/api/parse', null, {
method: 'POST',
headers: {
'Content-Type': 'application/json; charset=utf-8',
},
body: JSON.stringify([{ title: 'Nextjs' }]),
}).then((res) => res.ok && res.json())
expect(data).toEqual([{ title: 'Nextjs' }])
})
it('should special-case empty JSON body', async () => {
const data = await fetchViaHTTP(appPort, '/api/parse', null, {
method: 'POST',
headers: {
'Content-Type': 'application/json; charset=utf-8',
},
}).then((res) => res.ok && res.json())
expect(data).toEqual({})
})
it('should support boolean for JSON in api page', async () => {
const res = await fetchViaHTTP(appPort, '/api/bool', null, {})
const body = res.ok ? await res.json() : null
expect(res.status).toBe(200)
expect(res.headers.get('content-type')).toBe(
'application/json; charset=utf-8'
)
expect(body).toBe(true)
})
it('should support undefined response body', async () => {
const res = await fetchViaHTTP(appPort, '/api/json-undefined', null, {})
const body = res.ok ? await res.text() : null
expect(body).toBe('')
})
it('should support string in JSON response body', async () => {
const res = await fetchViaHTTP(appPort, '/api/json-string', null, {})
const body = res.ok ? await res.text() : null
expect(body).toBe('"Hello world!"')
})
it('should support null in JSON response body', async () => {
const res = await fetchViaHTTP(appPort, '/api/json-null')
const body = res.ok ? await res.json() : 'Not null'
expect(res.status).toBe(200)
expect(res.headers.get('content-type')).toBe(
'application/json; charset=utf-8'
)
expect(body).toBe(null)
})
it('should return error with invalid JSON', async () => {
const data = await fetchViaHTTP(appPort, '/api/parse', null, {
method: 'POST',
headers: {
'Content-Type': 'application/json; charset=utf-8',
},
body: `{"message":Invalid"}`,
})
expect(data.status).toEqual(400)
expect(data.statusText).toEqual('Invalid JSON')
})
// TODO: Investigate this test flaking
it.skip('should return error exceeded body limit', async () => {
let res
let error
try {
res = await fetchViaHTTP(appPort, '/api/parse', null, {
method: 'POST',
headers: {
'Content-Type': 'application/json; charset=utf-8',
},
body: JSON.stringify(json),
})
} catch (err) {
error = err
}
if (error) {
// This is a temporary workaround for testing since node doesn't handle
// closed connections when POSTing data to an endpoint correctly
// https://github.com/nodejs/node/issues/12339
// TODO: investigate re-enabling this after above issue has been
// addressed in node or `node-fetch`
expect(error.code).toBe('EPIPE')
} else {
expect(res.status).toEqual(413)
expect(res.statusText).toEqual('Body exceeded 1mb limit')
}
})
it('should parse bigger body then 1mb', async () => {
const data = await fetchViaHTTP(appPort, '/api/big-parse', null, {
method: 'POST',
headers: {
'Content-Type': 'application/json; charset=utf-8',
},
body: JSON.stringify(json),
})
expect(data.status).toEqual(200)
})
it('should support etag spec', async () => {
const response = await fetchViaHTTP(appPort, '/api/blog')
const etag = response.headers.get('etag')
const unmodifiedResponse = await fetchViaHTTP(appPort, '/api/blog', null, {
headers: { 'If-None-Match': etag },
})
expect(unmodifiedResponse.status).toBe(304)
})
it('should parse urlencoded body', async () => {
const body = {
title: 'Nextjs',
description: 'The React Framework for Production',
}
const formBody = Object.keys(body)
.map((key) => {
return `${encodeURIComponent(key)}=${encodeURIComponent(body[key])}`
})
.join('&')
const data = await fetchViaHTTP(appPort, '/api/parse', null, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-Form-urlencoded',
},
body: formBody,
}).then((res) => res.ok && res.json())
expect(data).toEqual({
title: 'Nextjs',
description: 'The React Framework for Production',
})
})
it('should parse body in handler', async () => {
const data = await fetchViaHTTP(appPort, '/api/no-parsing', null, {
method: 'POST',
headers: {
'Content-Type': 'application/json; charset=utf-8',
},
body: JSON.stringify([{ title: 'Nextjs' }]),
}).then((res) => res.ok && res.json())
expect(data).toEqual([{ title: 'Nextjs' }])
})
it('should parse body with config', async () => {
const data = await fetchViaHTTP(appPort, '/api/parsing', null, {
method: 'POST',
headers: {
'Content-Type': 'application/json; charset=utf-8',
},
body: JSON.stringify([{ title: 'Nextjs' }]),
}).then((res) => res.ok && res.json())
expect(data).toEqual({ message: 'Parsed body' })
})
it('should show friendly error for invalid redirect', async () => {
await fetchViaHTTP(appPort, '/api/redirect-error', null, {})
await check(() => {
expect(stderr).toContain(
`Invalid redirect arguments. Please use a single argument URL, e.g. res.redirect('/destination') or use a status code and URL, e.g. res.redirect(307, '/destination').`
)
return 'yes'
}, 'yes')
})
it('should show friendly error in case of passing null as first argument redirect', async () => {
await fetchViaHTTP(appPort, '/api/redirect-null', null, {})
check(() => {
expect(stderr).toContain(
`Invalid redirect arguments. Please use a single argument URL, e.g. res.redirect('/destination') or use a status code and URL, e.g. res.redirect(307, '/destination').`
)
return 'yes'
}, 'yes')
})
it('should redirect with status code 307', async () => {
const res = await fetchViaHTTP(appPort, '/api/redirect-307', null, {
redirect: 'manual',
})
expect(res.status).toEqual(307)
const text = await res.text()
expect(text).toEqual('/login')
})
it('should redirect to login', async () => {
const res = await fetchViaHTTP(appPort, '/api/redirect-307', null, {})
expect(res.redirected).toBe(true)
expect(res.url).toContain('/login')
})
it('should redirect with status code 301', async () => {
const res = await fetchViaHTTP(appPort, '/api/redirect-301', null, {
redirect: 'manual',
})
expect(res.status).toEqual(301)
const text = await res.text()
expect(text).toEqual('/login')
})
it('should return empty query object', async () => {
const data = await fetchViaHTTP(appPort, '/api/query', null, {}).then(
(res) => res.ok && res.json()
)
expect(data).toEqual({})
})
it('should parse query correctly', async () => {
const data = await fetchViaHTTP(
appPort,
'/api/query?a=1&b=2&a=3',
null,
{}
).then((res) => res.ok && res.json())
expect(data).toEqual({ a: ['1', '3'], b: '2' })
})
it('should return empty cookies object', async () => {
const data = await fetchViaHTTP(appPort, '/api/cookies', null, {}).then(
(res) => res.ok && res.json()
)
expect(data).toEqual({})
})
it('should return cookies object', async () => {
const data = await fetchViaHTTP(appPort, '/api/cookies', null, {
headers: {
Cookie: 'nextjs=cool;',
},
}).then((res) => res.ok && res.json())
expect(data).toEqual({ nextjs: 'cool' })
})
it('should return 200 on POST on pages', async () => {
const res = await fetchViaHTTP(appPort, '/user', null, {
method: 'POST',
})
expect(res.status).toEqual(200)
})
it('should return JSON on post on API', async () => {
const data = await fetchViaHTTP(appPort, '/api/blog?title=Nextjs', null, {
method: 'POST',
}).then((res) => res.ok && res.json())
expect(data).toEqual([{ title: 'Nextjs' }])
})
it('should return data on dynamic route', async () => {
const data = await fetchViaHTTP(appPort, '/api/post-1', null, {}).then(
(res) => res.ok && res.json()
)
expect(data).toEqual({ post: 'post-1' })
})
it('should work with dynamic params and search string', async () => {
const data = await fetchViaHTTP(
appPort,
'/api/post-1?val=1',
null,
{}
).then((res) => res.ok && res.json())
expect(data).toEqual({ val: '1', post: 'post-1' })
})
it('should work with dynamic params and search string like lambda', async () => {
const res = await fetchViaHTTP(appPort, '/api/post-1?val=1')
const json = await res.json()
expect(json).toEqual({ val: '1', post: 'post-1' })
})
it('should prioritize a non-dynamic page', async () => {
const data = await fetchViaHTTP(
appPort,
'/api/post-1/comments',
null,
{}
).then((res) => res.ok && res.json())
expect(data).toEqual([{ message: 'Prioritize a non-dynamic api page' }])
})
it('should return data on dynamic nested route', async () => {
const data = await fetchViaHTTP(
appPort,
'/api/post-1/comment-1',
null,
{}
).then((res) => res.ok && res.json())
expect(data).toEqual({ post: 'post-1', comment: 'comment-1' })
})
it('should 404 on optional dynamic api page', async () => {
const res = await fetchViaHTTP(appPort, '/api/blog/543/comment', null, {})
expect(res.status).toBe(404)
})
it('should return data on dynamic optional nested route', async () => {
const data = await fetchViaHTTP(
appPort,
'/api/blog/post-1/comment/1',
null,
{}
).then((res) => res.ok && res.json())
expect(data).toEqual({ post: 'post-1', id: '1' })
})
it('should work with child_process correctly', async () => {
const data = await renderViaHTTP(appPort, '/api/child-process')
expect(data).toBe('hi')
})
it('should work with nullable payload', async () => {
const data = await renderViaHTTP(appPort, '/api/nullable-payload')
expect(data).toBe('')
})
it('should warn if response body is larger than 4MB', async () => {
let res = await fetchViaHTTP(appPort, '/api/large-response')
expect(res.ok).toBeTruthy()
expect(stderr).toContain(
'API response for /api/large-response exceeds 4MB. API Routes are meant to respond quickly.'
)
res = await fetchViaHTTP(appPort, '/api/large-chunked-response')
expect(res.ok).toBeTruthy()
expect(stderr).toContain(
'API response for /api/large-chunked-response exceeds 4MB. API Routes are meant to respond quickly.'
)
})
it('should not warn if response body is larger than 4MB with responseLimit config = false', async () => {
await check(async () => {
let res = await fetchViaHTTP(appPort, '/api/large-response-with-config')
expect(res.ok).toBeTruthy()
expect(stderr).not.toContain(
'API response for /api/large-response-with-config exceeds 4MB. API Routes are meant to respond quickly.'
)
return 'success'
}, 'success')
})
it('should warn with configured size if response body is larger than configured size', async () => {
await check(async () => {
let res = await fetchViaHTTP(
appPort,
'/api/large-response-with-config-size'
)
expect(res.ok).toBeTruthy()
expect(stderr).toContain(
'API response for /api/large-response-with-config-size exceeds 5MB. API Routes are meant to respond quickly.'
)
return 'success'
}, 'success')
})
if (dev) {
it('should compile only server code in development', async () => {
await fetchViaHTTP(appPort, '/api/users')
expect(() => getPageFileFromBuildManifest(appDir, '/api/users')).toThrow(
/No files for page/
)
expect(getPageFileFromPagesManifest(appDir, '/api/users')).toBeTruthy()
})
it('should show warning when the API resolves without ending the request in development mode', async () => {
const controller = new AbortController()
setTimeout(() => {
controller.abort()
}, 2000)
await fetchViaHTTP(appPort, '/api/test-no-end', undefined, {
signal: controller.signal,
}).catch(() => {})
await check(
() => stderr,
/API resolved without sending a response for \/api\/test-no-end, this may result in stalled requests/
)
})
it('should not show warning when the API resolves and the response is piped', async () => {
const startIdx = stderr.length > 0 ? stderr.length - 1 : stderr.length
await fetchViaHTTP(appPort, `/api/test-res-pipe`, { port: appPort })
expect(stderr.slice(startIdx)).not.toContain(
`API resolved without sending a response for /api/test-res-pipe`
)
})
it('should show false positive warning if not using externalResolver flag', async () => {
const apiURL = '/api/external-resolver-false-positive'
const req = await fetchViaHTTP(appPort, apiURL)
expect(await req.text()).toBe('hello world')
check(() => {
expect(stderr).toContain(
`API resolved without sending a response for ${apiURL}, this may result in stalled requests.`
)
return 'yes'
}, 'yes')
})
it('should not show warning if using externalResolver flag', async () => {
const startIdx = stderr.length > 0 ? stderr.length - 1 : stderr.length
const apiURL = '/api/external-resolver'
const req = await fetchViaHTTP(appPort, apiURL)
expect(stderr.slice(startIdx)).not.toContain(
`API resolved without sending a response for ${apiURL}`
)
expect(await req.text()).toBe('hello world')
})
} else {
it('should show error with output export', async () => {
const nextConfig = new File(join(appDir, 'next.config.js'))
nextConfig.write(`module.exports = { output: 'export' }`)
try {
const { stderr, code } = await nextBuild(appDir, [], { stderr: true })
expect(stderr).toContain('https://nextjs.org/docs/messages/gssp-export')
expect(code).toBe(1)
} finally {
nextConfig.delete()
}
})
it('should build api routes', async () => {
const pagesManifest = JSON.parse(
await fs.readFile(
join(appDir, `.next/${mode}/pages-manifest.json`),
'utf8'
)
)
expect(Object.keys(pagesManifest).includes('/api/[post]')).toBeTruthy()
const res = await fetchViaHTTP(appPort, '/api/nextjs')
const json = await res.json()
expect(json).toEqual({ post: 'nextjs' })
const buildManifest = JSON.parse(
await fs.readFile(join(appDir, '.next/build-manifest.json'), 'utf8')
)
expect(
Object.keys(buildManifest.pages).includes('/api-conflict')
).toBeTruthy()
})
}
}
describe('API routes', () => {
describe('dev support', () => {
beforeAll(async () => {
stderr = ''
appPort = await findPort()
app = await launchApp(appDir, appPort, {
onStderr: (msg) => {
stderr += msg
},
})
})
afterAll(() => killApp(app))
runTests(true)
})
;(process.env.TURBOPACK_DEV ? describe.skip : describe)(
'production mode',
() => {
beforeAll(async () => {
await nextBuild(appDir)
mode = 'server'
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
afterAll(() => killApp(app))
runTests()
}
)
})