a815ba9f79
This PR adds support for [Middleware as per RFC ](https://github.com/vercel/next.js/discussions/29750). ## Feature - [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Documentation added - [ ] Telemetry added. In case of a feature if it's used or not. - [ ] Errors have helpful link attached, see `contributing.md` ## Documentation / Examples - [ ] Make sure the linting passes
212 lines
6.5 KiB
TypeScript
212 lines
6.5 KiB
TypeScript
/* eslint-env jest */
|
|
|
|
import { Blob, File, FormData } from 'next/dist/compiled/formdata-node'
|
|
import { Body } from 'next/dist/server/web/spec-compliant/body'
|
|
import { Crypto } from 'next/dist/server/web/sandbox/polyfills'
|
|
import { Headers } from 'next/dist/server/web/spec-compliant/headers'
|
|
import { streamToIterator } from 'next/dist/server/web/utils'
|
|
import * as streams from 'web-streams-polyfill/ponyfill'
|
|
|
|
class Implementation extends Body {
|
|
headers: Headers
|
|
constructor(init: BodyInit) {
|
|
super(init)
|
|
this.headers = new Headers()
|
|
}
|
|
}
|
|
|
|
beforeAll(() => {
|
|
global['Blob'] = Blob
|
|
global['crypto'] = new Crypto()
|
|
global['File'] = File
|
|
global['FormData'] = FormData
|
|
global['Headers'] = Headers
|
|
global['ReadableStream'] = streams.ReadableStream
|
|
global['TransformStream'] = streams.TransformStream
|
|
})
|
|
|
|
afterAll(() => {
|
|
delete global['Blob']
|
|
delete global['crypto']
|
|
delete global['File']
|
|
delete global['Headers']
|
|
delete global['FormData']
|
|
delete global['ReadableStream']
|
|
delete global['TransformStream']
|
|
})
|
|
|
|
it('flags the body as used after reading it', async () => {
|
|
const { readable, writable } = new TransformStream()
|
|
const encoder = new TextEncoder()
|
|
const writer = writable.getWriter()
|
|
writer.write(encoder.encode(''))
|
|
writer.close()
|
|
|
|
const body = new Implementation(readable)
|
|
expect(body.bodyUsed).toEqual(false)
|
|
|
|
const reader = body.body.getReader()
|
|
expect(body.bodyUsed).toEqual(false)
|
|
|
|
while (true) {
|
|
const { done } = await reader.read()
|
|
if (done) break
|
|
}
|
|
|
|
reader.releaseLock()
|
|
expect(body.bodyUsed).toEqual(true)
|
|
})
|
|
|
|
it('throws when the body was directly consumed', async () => {
|
|
global['crypto'].getRandomValues = (array: number[]) => array
|
|
const object = { hello: 'world' }
|
|
const blob = new Blob([JSON.stringify(object, null, 2)], {
|
|
type: 'application/json',
|
|
})
|
|
|
|
const formData = new FormData()
|
|
formData.append('name', 'John')
|
|
formData.append('lastname', 'Doe')
|
|
formData.append('metadata', blob)
|
|
|
|
const body = new Implementation(formData)
|
|
|
|
// Read the body through the stream
|
|
for await (const item of streamToIterator(body.body)) {
|
|
expect(item).toBeTruthy()
|
|
}
|
|
|
|
// It allows to access again the already read stream
|
|
const reader = body.body.getReader()
|
|
const { done } = await reader.read()
|
|
expect(done).toBeTruthy()
|
|
|
|
const error = await body.text().catch((err) => err)
|
|
expect(error).toBeInstanceOf(TypeError)
|
|
expect(error.message).toEqual(
|
|
'Body has already been used. It can only be used once. Use tee() first if you need to read it twice.'
|
|
)
|
|
})
|
|
|
|
it('throws when the body was indirectly consumed', async () => {
|
|
global['crypto'].getRandomValues = (array: number[]) => array
|
|
const object = { hello: 'world' }
|
|
const blob = new Blob([JSON.stringify(object, null, 2)], {
|
|
type: 'application/json',
|
|
})
|
|
|
|
const formData = new FormData()
|
|
formData.append('name', 'John')
|
|
formData.append('lastname', 'Doe')
|
|
formData.append('metadata', blob)
|
|
|
|
const body = new Implementation(formData)
|
|
const text = await body.text()
|
|
expect(text).toBeTruthy()
|
|
|
|
const error = await body.text().catch((err) => err)
|
|
expect(error).toBeInstanceOf(TypeError)
|
|
expect(error.message).toEqual(
|
|
'Body has already been used. It can only be used once. Use tee() first if you need to read it twice.'
|
|
)
|
|
})
|
|
|
|
it('allows to read a FormData body as text', async () => {
|
|
global['crypto'].getRandomValues = (array: number[]) => array
|
|
const object = { hello: 'world' }
|
|
const blob = new Blob([JSON.stringify(object, null, 2)], {
|
|
type: 'application/json',
|
|
})
|
|
|
|
const formData = new FormData()
|
|
formData.append('name', 'John')
|
|
formData.append('lastname', 'Doe')
|
|
formData.append('metadata', blob)
|
|
|
|
const body = new Implementation(formData)
|
|
const text = await body.text()
|
|
expect(text).toMatchInlineSnapshot(`
|
|
"--0000000000000000000000000000000000000000000000000000000000000000
|
|
Content-Disposition: form-data; name=\\"name\\"
|
|
|
|
John
|
|
--0000000000000000000000000000000000000000000000000000000000000000
|
|
Content-Disposition: form-data; name=\\"lastname\\"
|
|
|
|
Doe
|
|
--0000000000000000000000000000000000000000000000000000000000000000
|
|
Content-Disposition: form-data; name=\\"metadata\\"; filename=\\"blob\\"
|
|
Content-Type: application/json
|
|
|
|
{
|
|
\\"hello\\": \\"world\\"
|
|
}
|
|
--0000000000000000000000000000000000000000000000000000000000000000--
|
|
|
|
"
|
|
`)
|
|
})
|
|
|
|
it('allows to read a null body as ArrayBuffer', async () => {
|
|
const body = new Implementation(null)
|
|
const value = await body.arrayBuffer()
|
|
expect(new Uint8Array(value)).toHaveLength(0)
|
|
})
|
|
|
|
it('allows to read a text body as ArrayBuffer', async () => {
|
|
const body = new Implementation('Hello world')
|
|
const enc = new TextEncoder()
|
|
const dec = new TextDecoder()
|
|
|
|
const value = await body.arrayBuffer()
|
|
const decoded = dec.decode(value)
|
|
expect(decoded).toEqual('Hello world')
|
|
expect(value).toEqual(enc.encode('Hello world').buffer)
|
|
})
|
|
|
|
it('allows to read a chunked body as ArrayBuffer', async () => {
|
|
const { readable, writable } = new TransformStream()
|
|
const encoder = new TextEncoder()
|
|
const writer = writable.getWriter()
|
|
writer.write(encoder.encode('Hello '))
|
|
writer.write(encoder.encode('world!'))
|
|
writer.close()
|
|
|
|
const body = new Implementation(readable)
|
|
const value = await body.arrayBuffer()
|
|
const decoder = new TextDecoder()
|
|
const decoded = decoder.decode(value)
|
|
expect(decoded).toEqual('Hello world!')
|
|
expect(value).toEqual(encoder.encode('Hello world!').buffer)
|
|
})
|
|
|
|
it('allows to read a URLSearchParams body as FormData', async () => {
|
|
const params = new URLSearchParams('q=URLUtils.searchParams&topic=api')
|
|
const body = new Implementation(params)
|
|
const formData = await body.formData()
|
|
expect(formData.get('topic')).toEqual('api')
|
|
})
|
|
|
|
it('allows to read a Blob body as Blob', async () => {
|
|
const object = { hello: 'world' }
|
|
const str = JSON.stringify(object, null, 2)
|
|
const body = new Implementation(new Blob([str]))
|
|
const blob = await body.blob()
|
|
const txt = await blob.text()
|
|
expect(txt).toEqual(str)
|
|
})
|
|
|
|
it('allows to read a text body as JSON', async () => {
|
|
const body = new Implementation(JSON.stringify({ message: 'hi', value: 10 }))
|
|
const value = await body.json()
|
|
expect(value).toEqual({ message: 'hi', value: 10 })
|
|
})
|
|
|
|
it('throws when reading a text body as JSON but it is invalid', async () => {
|
|
const body = new Implementation('{ hi: "there", ')
|
|
const error = await body.json().catch((err) => err)
|
|
expect(error).toBeInstanceOf(TypeError)
|
|
expect(error.message).toEqual(
|
|
'invalid json body reason: Unexpected token h in JSON at position 2'
|
|
)
|
|
})
|