mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-11 08:33:53 +00:00
test: unit tests for server module (#5154)
This commit is contained in:
parent
e3991aacdf
commit
cc573a4925
@ -19,7 +19,8 @@ module.exports = {
|
||||
|
||||
coveragePathIgnorePatterns: [
|
||||
'node_modules/(?!(@nuxt|nuxt))',
|
||||
'packages/webpack/src/config/plugins/vue'
|
||||
'packages/webpack/src/config/plugins/vue',
|
||||
'packages/server/src/jsdom'
|
||||
],
|
||||
|
||||
testPathIgnorePatterns: [
|
||||
|
@ -11,6 +11,10 @@ describe('build', () => {
|
||||
jest.spyOn(utils, 'createLock').mockImplementation(() => () => {})
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
process.exit.mockRestore()
|
||||
})
|
||||
|
||||
afterEach(() => jest.resetAllMocks())
|
||||
|
||||
test('has run function', () => {
|
||||
|
@ -11,6 +11,10 @@ describe('generate', () => {
|
||||
jest.spyOn(utils, 'createLock').mockImplementation(() => () => {})
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
process.exit.mockRestore()
|
||||
})
|
||||
|
||||
afterEach(() => jest.resetAllMocks())
|
||||
|
||||
test('has run function', () => {
|
||||
|
@ -53,7 +53,6 @@ export default async function renderAndGetWindow(
|
||||
ssr ? `window.${globals.context}` : `<div id="${globals.id}">`
|
||||
)
|
||||
|
||||
/* istanbul ignore if */
|
||||
if (!nuxtExists) {
|
||||
const error = new Error('Could not load the nuxt app')
|
||||
error.body = window.document.body.innerHTML
|
||||
|
@ -12,15 +12,6 @@ export default ({ resources, options }) => async function errorMiddleware(err, r
|
||||
message: err.message || 'Nuxt Server Error',
|
||||
name: !err.name || err.name === 'Error' ? 'NuxtServerError' : err.name
|
||||
}
|
||||
const errorFull = err instanceof Error
|
||||
? err
|
||||
: typeof err === 'string'
|
||||
? new Error(err)
|
||||
: new Error(err.message || JSON.stringify(err))
|
||||
|
||||
errorFull.name = error.name
|
||||
errorFull.statusCode = error.statusCode
|
||||
errorFull.stack = err.stack || undefined
|
||||
|
||||
const sendResponse = (content, type = 'text/html') => {
|
||||
// Set Headers
|
||||
@ -62,6 +53,16 @@ export default ({ resources, options }) => async function errorMiddleware(err, r
|
||||
return
|
||||
}
|
||||
|
||||
const errorFull = err instanceof Error
|
||||
? err
|
||||
: typeof err === 'string'
|
||||
? new Error(err)
|
||||
: new Error(err.message || JSON.stringify(err))
|
||||
|
||||
errorFull.name = error.name
|
||||
errorFull.statusCode = error.statusCode
|
||||
errorFull.stack = err.stack || undefined
|
||||
|
||||
// Show stack trace
|
||||
const youch = new Youch(
|
||||
errorFull,
|
||||
@ -90,7 +91,6 @@ const readSourceFactory = ({ srcDir, rootDir, buildDir }) => async function read
|
||||
frame.fileName = sanitizeName(frame.fileName)
|
||||
|
||||
// Return if fileName is unknown
|
||||
/* istanbul ignore if */
|
||||
if (!frame.fileName) {
|
||||
return
|
||||
}
|
||||
|
@ -77,7 +77,6 @@ export default ({ options, nuxt, renderRoute, resources }) => async function nux
|
||||
await nuxt.callHook('render:routeDone', url, result, context)
|
||||
return html
|
||||
} catch (err) {
|
||||
/* istanbul ignore if */
|
||||
if (context && context.redirected) {
|
||||
consola.error(err)
|
||||
return err
|
||||
@ -95,7 +94,6 @@ const defaultPushAssets = (preloadFiles, shouldPush, publicPath, options) => {
|
||||
const links = []
|
||||
preloadFiles.forEach(({ file, asType, fileWithoutQuery, modern }) => {
|
||||
// By default, we only preload scripts or css
|
||||
/* istanbul ignore if */
|
||||
if (!shouldPush && asType !== 'script' && asType !== 'style') {
|
||||
return
|
||||
}
|
||||
|
17
packages/server/test/contest.test.js
Normal file
17
packages/server/test/contest.test.js
Normal file
@ -0,0 +1,17 @@
|
||||
import ServerContext from '../src/context'
|
||||
|
||||
describe('server: ServerContext', () => {
|
||||
test('should construct context', () => {
|
||||
const server = {
|
||||
nuxt: { id: 'test-contest-nuxt' },
|
||||
globals: { id: 'test-contest-globals' },
|
||||
options: { id: 'test-contest-options' },
|
||||
resources: { id: 'test-contest-resources' }
|
||||
}
|
||||
const context = new ServerContext(server)
|
||||
expect(context.nuxt).toBe(server.nuxt)
|
||||
expect(context.globals).toEqual(server.globals)
|
||||
expect(context.options).toEqual(server.options)
|
||||
expect(context.resources).toEqual(server.resources)
|
||||
})
|
||||
})
|
14
packages/server/test/index.test.js
Normal file
14
packages/server/test/index.test.js
Normal file
@ -0,0 +1,14 @@
|
||||
import { Server, Listener } from '../src'
|
||||
|
||||
jest.mock('../src/server', () => ({
|
||||
server: true
|
||||
})).mock('../src/listener', () => ({
|
||||
listener: true
|
||||
}))
|
||||
|
||||
describe('server: entry', () => {
|
||||
test('should export Server and Listener', () => {
|
||||
expect(Server.server).toEqual(true)
|
||||
expect(Listener.listener).toEqual(true)
|
||||
})
|
||||
})
|
395
packages/server/test/listener.test.js
Normal file
395
packages/server/test/listener.test.js
Normal file
@ -0,0 +1,395 @@
|
||||
import http from 'http'
|
||||
import https from 'https'
|
||||
import enableDestroy from 'server-destroy'
|
||||
import ip from 'ip'
|
||||
import consola from 'consola'
|
||||
import pify from 'pify'
|
||||
|
||||
import Listener from '../src/listener'
|
||||
|
||||
jest.mock('http')
|
||||
jest.mock('https')
|
||||
jest.mock('server-destroy')
|
||||
jest.mock('ip')
|
||||
jest.mock('pify')
|
||||
|
||||
describe('server: listener', () => {
|
||||
const mockServer = () => {
|
||||
const server = {
|
||||
address: jest.fn(),
|
||||
on: jest.fn(),
|
||||
listen: jest.fn((listenArgs, callback) => {
|
||||
Promise.resolve().then(callback)
|
||||
return server
|
||||
}),
|
||||
destroy: jest.fn()
|
||||
}
|
||||
return server
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
test('should construct listener', () => {
|
||||
const options = {
|
||||
port: 3000,
|
||||
host: 'localhost',
|
||||
socket: jest.fn(),
|
||||
https: { id: 'test-listener-https' },
|
||||
app: jest.fn(),
|
||||
dev: false
|
||||
}
|
||||
const listener = new Listener(options)
|
||||
|
||||
expect(listener.port).toEqual(options.port)
|
||||
expect(listener.host).toEqual(options.host)
|
||||
expect(listener.socket).toEqual(options.socket)
|
||||
expect(listener.https).toEqual(options.https)
|
||||
expect(listener.app).toEqual(options.app)
|
||||
expect(listener.dev).toEqual(options.dev)
|
||||
expect(listener.listening).toEqual(false)
|
||||
expect(listener._server).toBe(null)
|
||||
expect(listener.server).toBe(null)
|
||||
expect(listener.address).toBe(null)
|
||||
expect(listener.url).toBe(null)
|
||||
})
|
||||
|
||||
test('should listen http host and port', async () => {
|
||||
const server = mockServer()
|
||||
http.createServer.mockReturnValueOnce(server)
|
||||
|
||||
const options = {
|
||||
port: 3000,
|
||||
host: 'localhost',
|
||||
https: false,
|
||||
app: jest.fn(),
|
||||
dev: false
|
||||
}
|
||||
const listener = new Listener(options)
|
||||
listener.computeURL = jest.fn()
|
||||
|
||||
await listener.listen()
|
||||
|
||||
expect(http.createServer).toBeCalledTimes(1)
|
||||
expect(http.createServer).toBeCalledWith(options.app)
|
||||
expect(server.on).toBeCalledTimes(1)
|
||||
expect(server.on).toBeCalledWith('error', expect.any(Function))
|
||||
expect(server.listen).toBeCalledTimes(1)
|
||||
expect(server.listen).toBeCalledWith(
|
||||
{
|
||||
host: options.host,
|
||||
port: options.port,
|
||||
exclusive: false
|
||||
},
|
||||
expect.any(Function)
|
||||
)
|
||||
expect(listener.server).toBe(server)
|
||||
expect(enableDestroy).toBeCalledTimes(1)
|
||||
expect(enableDestroy).toBeCalledWith(listener.server)
|
||||
expect(pify).toBeCalledTimes(1)
|
||||
expect(pify).toBeCalledWith(listener.server.destroy)
|
||||
expect(listener.computeURL).toBeCalledTimes(1)
|
||||
expect(listener.listening).toEqual(true)
|
||||
})
|
||||
|
||||
test('should listen https host and port', async () => {
|
||||
const server = mockServer()
|
||||
https.createServer.mockReturnValueOnce(server)
|
||||
|
||||
const options = {
|
||||
port: 3000,
|
||||
host: 'localhost',
|
||||
https: { key: 'test-listener' },
|
||||
app: jest.fn(),
|
||||
dev: false
|
||||
}
|
||||
const listener = new Listener(options)
|
||||
listener.computeURL = jest.fn()
|
||||
|
||||
await listener.listen()
|
||||
|
||||
expect(https.createServer).toBeCalledTimes(1)
|
||||
expect(https.createServer).toBeCalledWith(options.https, options.app)
|
||||
expect(server.on).toBeCalledTimes(1)
|
||||
expect(server.on).toBeCalledWith('error', expect.any(Function))
|
||||
expect(server.listen).toBeCalledTimes(1)
|
||||
expect(server.listen).toBeCalledWith(
|
||||
{
|
||||
host: options.host,
|
||||
port: options.port,
|
||||
exclusive: false
|
||||
},
|
||||
expect.any(Function)
|
||||
)
|
||||
expect(listener.server).toBe(server)
|
||||
expect(enableDestroy).toBeCalledTimes(1)
|
||||
expect(enableDestroy).toBeCalledWith(listener.server)
|
||||
expect(pify).toBeCalledTimes(1)
|
||||
expect(pify).toBeCalledWith(listener.server.destroy)
|
||||
expect(listener.computeURL).toBeCalledTimes(1)
|
||||
expect(listener.listening).toEqual(true)
|
||||
})
|
||||
|
||||
test('should listen unix socket host and port', async () => {
|
||||
const server = mockServer()
|
||||
http.createServer.mockReturnValueOnce(server)
|
||||
|
||||
const options = {
|
||||
port: 3000,
|
||||
host: 'localhost',
|
||||
https: false,
|
||||
socket: '/var/nuxt/unix.socket',
|
||||
app: jest.fn(),
|
||||
dev: false
|
||||
}
|
||||
const listener = new Listener(options)
|
||||
listener.computeURL = jest.fn()
|
||||
|
||||
await listener.listen()
|
||||
|
||||
expect(http.createServer).toBeCalledTimes(1)
|
||||
expect(http.createServer).toBeCalledWith(options.app)
|
||||
expect(server.on).toBeCalledTimes(1)
|
||||
expect(server.on).toBeCalledWith('error', expect.any(Function))
|
||||
expect(server.listen).toBeCalledTimes(1)
|
||||
expect(server.listen).toBeCalledWith(
|
||||
{
|
||||
path: options.socket,
|
||||
exclusive: false
|
||||
},
|
||||
expect.any(Function)
|
||||
)
|
||||
expect(listener.server).toBe(server)
|
||||
expect(enableDestroy).toBeCalledTimes(1)
|
||||
expect(enableDestroy).toBeCalledWith(listener.server)
|
||||
expect(pify).toBeCalledTimes(1)
|
||||
expect(pify).toBeCalledWith(listener.server.destroy)
|
||||
expect(listener.computeURL).toBeCalledTimes(1)
|
||||
expect(listener.listening).toEqual(true)
|
||||
})
|
||||
|
||||
test('should prevent listening multiple times', async () => {
|
||||
const options = {
|
||||
port: 3000,
|
||||
host: 'localhost',
|
||||
https: false,
|
||||
app: jest.fn(),
|
||||
dev: false
|
||||
}
|
||||
const listener = new Listener(options)
|
||||
listener.computeURL = jest.fn()
|
||||
|
||||
listener.listening = true
|
||||
await listener.listen()
|
||||
|
||||
expect(http.createServer).not.toBeCalled()
|
||||
})
|
||||
|
||||
test('should throw error if error occurred or listen failed', async () => {
|
||||
const server = mockServer()
|
||||
http.createServer.mockReturnValueOnce(server)
|
||||
|
||||
const options = {
|
||||
port: 3000,
|
||||
host: 'localhost',
|
||||
https: false,
|
||||
app: jest.fn(),
|
||||
dev: false
|
||||
}
|
||||
const listener = new Listener(options)
|
||||
listener.computeURL = jest.fn()
|
||||
listener.serverErrorHandler = jest.fn()
|
||||
|
||||
const serverError = new Error('error occurred')
|
||||
server.listen.mockImplementationOnce((listenArgs, callback) => {
|
||||
Promise.resolve().then(callback)
|
||||
const errorListener = server.on.mock.calls[0][1]
|
||||
errorListener(serverError)
|
||||
return server
|
||||
})
|
||||
await listener.listen()
|
||||
expect(listener.serverErrorHandler).toBeCalledTimes(1)
|
||||
expect(listener.serverErrorHandler).toBeCalledWith(serverError)
|
||||
|
||||
http.createServer.mockReturnValueOnce(server)
|
||||
listener.serverErrorHandler.mockClear()
|
||||
|
||||
const listenError = new Error('listen failed')
|
||||
server.listen.mockImplementationOnce((listenArgs, callback) => {
|
||||
Promise.resolve().then(() => callback(listenError))
|
||||
return server
|
||||
})
|
||||
await listener.listen()
|
||||
expect(listener.serverErrorHandler).toBeCalledTimes(1)
|
||||
expect(listener.serverErrorHandler).toBeCalledWith(listenError)
|
||||
})
|
||||
|
||||
test('should compute http url', () => {
|
||||
const options = {
|
||||
port: 3000,
|
||||
host: 'localhost'
|
||||
}
|
||||
const listener = new Listener(options)
|
||||
listener.server = mockServer()
|
||||
|
||||
listener.server.address.mockReturnValueOnce({
|
||||
address: 'localhost',
|
||||
port: 3000
|
||||
})
|
||||
listener.computeURL()
|
||||
expect(listener.host).toEqual('localhost')
|
||||
expect(listener.port).toEqual(3000)
|
||||
expect(listener.url).toEqual('http://localhost:3000')
|
||||
|
||||
listener.server.address.mockReturnValueOnce({
|
||||
address: '127.0.0.1',
|
||||
port: 3001
|
||||
})
|
||||
listener.computeURL()
|
||||
expect(listener.host).toEqual('localhost')
|
||||
expect(listener.port).toEqual(3001)
|
||||
expect(listener.url).toEqual('http://localhost:3001')
|
||||
|
||||
ip.address.mockReturnValueOnce('192.168.0.1')
|
||||
listener.server.address.mockReturnValueOnce({
|
||||
address: '0.0.0.0',
|
||||
port: 3002
|
||||
})
|
||||
listener.computeURL()
|
||||
expect(listener.host).toEqual('192.168.0.1')
|
||||
expect(listener.port).toEqual(3002)
|
||||
expect(listener.url).toEqual('http://192.168.0.1:3002')
|
||||
})
|
||||
|
||||
test('should compute https url', () => {
|
||||
const options = {
|
||||
port: 3000,
|
||||
host: 'localhost',
|
||||
https: true
|
||||
}
|
||||
const listener = new Listener(options)
|
||||
listener.server = mockServer()
|
||||
|
||||
listener.server.address.mockReturnValueOnce({
|
||||
address: 'localhost',
|
||||
port: 3000
|
||||
})
|
||||
listener.computeURL()
|
||||
expect(listener.host).toEqual('localhost')
|
||||
expect(listener.port).toEqual(3000)
|
||||
expect(listener.url).toEqual('https://localhost:3000')
|
||||
|
||||
listener.server.address.mockReturnValueOnce({
|
||||
address: '127.0.0.1',
|
||||
port: 3001
|
||||
})
|
||||
listener.computeURL()
|
||||
expect(listener.host).toEqual('localhost')
|
||||
expect(listener.port).toEqual(3001)
|
||||
expect(listener.url).toEqual('https://localhost:3001')
|
||||
|
||||
ip.address.mockReturnValueOnce('192.168.0.1')
|
||||
listener.server.address.mockReturnValueOnce({
|
||||
address: '0.0.0.0',
|
||||
port: 3002
|
||||
})
|
||||
listener.computeURL()
|
||||
expect(listener.host).toEqual('192.168.0.1')
|
||||
expect(listener.port).toEqual(3002)
|
||||
expect(listener.url).toEqual('https://192.168.0.1:3002')
|
||||
})
|
||||
|
||||
test('should compute unix socket url', () => {
|
||||
const options = {
|
||||
socket: true
|
||||
}
|
||||
const listener = new Listener(options)
|
||||
listener.server = mockServer()
|
||||
|
||||
listener.server.address.mockReturnValueOnce('/var/nuxt/unix.socket')
|
||||
listener.computeURL()
|
||||
|
||||
expect(listener.url).toEqual('unix+http:///var/nuxt/unix.socket')
|
||||
})
|
||||
|
||||
test('should throw error in serverErrorHandler', () => {
|
||||
const listener = new Listener({})
|
||||
|
||||
const error = new Error('server error')
|
||||
expect(() => listener.serverErrorHandler(error)).toThrow(error)
|
||||
})
|
||||
|
||||
test('should throw address in use error', () => {
|
||||
const listener = new Listener({})
|
||||
listener.host = 'localhost'
|
||||
listener.port = 3000
|
||||
|
||||
const addressInUse = new Error()
|
||||
addressInUse.code = 'EADDRINUSE'
|
||||
expect(() => listener.serverErrorHandler(addressInUse)).toThrow('Address `localhost:3000` is already in use.')
|
||||
})
|
||||
|
||||
test('should fallback to a random port in address in use error', async () => {
|
||||
const listener = new Listener({ dev: true })
|
||||
listener.host = 'localhost'
|
||||
listener.port = 3000
|
||||
listener.close = jest.fn(() => Promise.resolve())
|
||||
listener.listen = jest.fn()
|
||||
|
||||
const addressInUse = new Error()
|
||||
addressInUse.code = 'EADDRINUSE'
|
||||
|
||||
await listener.serverErrorHandler(addressInUse)
|
||||
|
||||
expect(consola.warn).toBeCalledTimes(1)
|
||||
expect(consola.warn).toBeCalledWith('Address `localhost:3000` is already in use.')
|
||||
expect(consola.info).toBeCalledTimes(1)
|
||||
expect(consola.info).toBeCalledWith('Trying a random port...')
|
||||
expect(listener.port).toEqual('0')
|
||||
expect(listener.close).toBeCalledTimes(1)
|
||||
expect(listener.listen).toBeCalledTimes(1)
|
||||
})
|
||||
|
||||
test('should close server', async () => {
|
||||
const listener = new Listener({})
|
||||
const server = mockServer()
|
||||
listener.listening = true
|
||||
listener._server = server
|
||||
listener.server = server
|
||||
listener.server.listening = true
|
||||
listener.address = 'localhost'
|
||||
listener.url = 'http://localhost:3000'
|
||||
|
||||
await listener.close()
|
||||
|
||||
expect(server.destroy).toBeCalledTimes(1)
|
||||
expect(consola.debug).toBeCalledTimes(1)
|
||||
expect(consola.debug).toBeCalledWith('server closed')
|
||||
expect(listener.listening).toEqual(false)
|
||||
expect(listener._server).toBe(null)
|
||||
expect(listener.server).toBe(null)
|
||||
expect(listener.address).toBe(null)
|
||||
expect(listener.url).toBe(null)
|
||||
})
|
||||
|
||||
test('should prevent destroying server if server is not listening', async () => {
|
||||
const listener = new Listener({})
|
||||
const server = mockServer()
|
||||
listener.listening = true
|
||||
listener._server = server
|
||||
listener.server = server
|
||||
listener.address = 'localhost'
|
||||
listener.url = 'http://localhost:3000'
|
||||
|
||||
await listener.close()
|
||||
|
||||
expect(server.destroy).not.toBeCalled()
|
||||
expect(consola.debug).not.toBeCalled()
|
||||
expect(listener.listening).toEqual(false)
|
||||
expect(listener._server).toBe(null)
|
||||
expect(listener.server).toBe(null)
|
||||
expect(listener.address).toBe(null)
|
||||
expect(listener.url).toBe(null)
|
||||
})
|
||||
})
|
277
packages/server/test/middleware/error.test.js
Normal file
277
packages/server/test/middleware/error.test.js
Normal file
@ -0,0 +1,277 @@
|
||||
import path from 'path'
|
||||
import fs from 'fs-extra'
|
||||
import consola from 'consola'
|
||||
|
||||
import Youch from '@nuxtjs/youch'
|
||||
|
||||
import createErrorMiddleware from '../../src/middleware/error'
|
||||
|
||||
jest.mock('path')
|
||||
jest.mock('fs-extra')
|
||||
jest.mock('@nuxtjs/youch', () => jest.fn(() => ({
|
||||
toHTML: jest.fn(() => 'youch html'),
|
||||
toJSON: jest.fn(() => 'youch json')
|
||||
})))
|
||||
|
||||
const createParams = () => ({
|
||||
resources: { errorTemplate: jest.fn(() => `error template`) },
|
||||
options: {
|
||||
srcDir: '/var/nuxt/src',
|
||||
rootDir: '/var/nuxt',
|
||||
buildDir: '/var/nuxt/dist',
|
||||
router: { base: '/' }
|
||||
}
|
||||
})
|
||||
|
||||
const createServerContext = () => ({
|
||||
req: { headers: {} },
|
||||
res: { setHeader: jest.fn(), end: jest.fn() },
|
||||
next: jest.fn()
|
||||
})
|
||||
|
||||
describe('server: errorMiddleware', () => {
|
||||
beforeAll(() => {
|
||||
path.join.mockImplementation((...args) => `join(${args.join(', ')})`)
|
||||
path.resolve.mockImplementation((...args) => `resolve(${args.join(', ')})`)
|
||||
fs.readFile.mockImplementation(() => Promise.resolve())
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
test('should return error middleware', () => {
|
||||
const errorMiddleware = createErrorMiddleware({})
|
||||
expect(errorMiddleware).toBeInstanceOf(Function)
|
||||
})
|
||||
|
||||
test('should send html error response', async () => {
|
||||
const params = createParams()
|
||||
const errorMiddleware = createErrorMiddleware(params)
|
||||
const error = new Error()
|
||||
const ctx = createServerContext()
|
||||
|
||||
await errorMiddleware(error, ctx.req, ctx.res, ctx.next)
|
||||
|
||||
expect(consola.error).toBeCalledWith(error)
|
||||
expect(ctx.res.statusCode).toEqual(500)
|
||||
expect(ctx.res.statusMessage).toEqual('NuxtServerError')
|
||||
expect(ctx.res.setHeader).toBeCalledTimes(3)
|
||||
expect(ctx.res.setHeader).nthCalledWith(1, 'Content-Type', 'text/html; charset=utf-8')
|
||||
expect(ctx.res.setHeader).nthCalledWith(2, 'Content-Length', Buffer.byteLength('error template'))
|
||||
expect(ctx.res.setHeader).nthCalledWith(3, 'Cache-Control', 'no-cache, no-store, max-age=0, must-revalidate')
|
||||
expect(params.resources.errorTemplate).toBeCalledTimes(1)
|
||||
expect(params.resources.errorTemplate).toBeCalledWith({
|
||||
status: 500,
|
||||
message: 'Nuxt Server Error',
|
||||
name: 'NuxtServerError'
|
||||
})
|
||||
expect(ctx.res.end).toBeCalledTimes(1)
|
||||
expect(ctx.res.end).toBeCalledWith('error template', 'utf-8')
|
||||
})
|
||||
|
||||
test('should send json error response', async () => {
|
||||
const params = createParams()
|
||||
const errorMiddleware = createErrorMiddleware(params)
|
||||
const error = {
|
||||
statusCode: 404,
|
||||
name: 'NuxtTestError',
|
||||
message: 'test error'
|
||||
}
|
||||
const ctx = createServerContext()
|
||||
ctx.req.headers.accept = 'application/json'
|
||||
|
||||
await errorMiddleware(error, ctx.req, ctx.res, ctx.next)
|
||||
|
||||
const errJson = JSON.stringify({
|
||||
status: error.statusCode,
|
||||
message: error.message,
|
||||
name: error.name
|
||||
}, undefined, 2)
|
||||
expect(consola.error).not.toBeCalled()
|
||||
expect(ctx.res.statusCode).toEqual(404)
|
||||
expect(ctx.res.statusMessage).toEqual(error.name)
|
||||
expect(ctx.res.setHeader).toBeCalledTimes(3)
|
||||
expect(ctx.res.setHeader).nthCalledWith(1, 'Content-Type', 'text/json; charset=utf-8')
|
||||
expect(ctx.res.setHeader).nthCalledWith(2, 'Content-Length', Buffer.byteLength(errJson))
|
||||
expect(ctx.res.setHeader).nthCalledWith(3, 'Cache-Control', 'no-cache, no-store, max-age=0, must-revalidate')
|
||||
expect(params.resources.errorTemplate).not.toBeCalled()
|
||||
expect(ctx.res.end).toBeCalledTimes(1)
|
||||
expect(ctx.res.end).toBeCalledWith(errJson, 'utf-8')
|
||||
})
|
||||
|
||||
test('should send html error response by youch in debug mode', async () => {
|
||||
const params = createParams()
|
||||
params.options.debug = true
|
||||
const errorMiddleware = createErrorMiddleware(params)
|
||||
const error = new Error('test error')
|
||||
error.statusCode = 503
|
||||
error.name = 'NuxtTestError'
|
||||
const ctx = createServerContext()
|
||||
|
||||
await errorMiddleware(error, ctx.req, ctx.res, ctx.next)
|
||||
|
||||
const errHtml = 'youch html'
|
||||
expect(Youch).toBeCalledTimes(1)
|
||||
expect(Youch).toBeCalledWith(
|
||||
error,
|
||||
ctx.req,
|
||||
expect.any(Function),
|
||||
params.options.router.base,
|
||||
true
|
||||
)
|
||||
expect(ctx.res.statusCode).toEqual(503)
|
||||
expect(ctx.res.statusMessage).toEqual(error.name)
|
||||
expect(ctx.res.setHeader).toBeCalledTimes(3)
|
||||
expect(ctx.res.setHeader).nthCalledWith(1, 'Content-Type', 'text/html; charset=utf-8')
|
||||
expect(ctx.res.setHeader).nthCalledWith(2, 'Content-Length', Buffer.byteLength(errHtml))
|
||||
expect(ctx.res.setHeader).nthCalledWith(3, 'Cache-Control', 'no-cache, no-store, max-age=0, must-revalidate')
|
||||
expect(params.resources.errorTemplate).not.toBeCalled()
|
||||
expect(ctx.res.end).toBeCalledTimes(1)
|
||||
expect(ctx.res.end).toBeCalledWith(errHtml, 'utf-8')
|
||||
})
|
||||
|
||||
test('should send json error response by youch in debug mode', async () => {
|
||||
const params = createParams()
|
||||
params.options.debug = true
|
||||
const errorMiddleware = createErrorMiddleware(params)
|
||||
const error = {
|
||||
statusCode: 404,
|
||||
name: 'NuxtTestError',
|
||||
message: 'test error'
|
||||
}
|
||||
const ctx = createServerContext()
|
||||
ctx.req.headers.accept = 'application/json'
|
||||
|
||||
await errorMiddleware(error, ctx.req, ctx.res, ctx.next)
|
||||
|
||||
const errJson = JSON.stringify('youch json', undefined, 2)
|
||||
const errorFull = new Error(error.message)
|
||||
errorFull.name = error.name
|
||||
errorFull.statusCode = error.statusCode
|
||||
errorFull.stack = undefined
|
||||
expect(Youch).toBeCalledTimes(1)
|
||||
expect(Youch).toBeCalledWith(
|
||||
errorFull,
|
||||
ctx.req,
|
||||
expect.any(Function),
|
||||
params.options.router.base,
|
||||
true
|
||||
)
|
||||
expect(ctx.res.statusCode).toEqual(404)
|
||||
expect(ctx.res.statusMessage).toEqual(error.name)
|
||||
expect(ctx.res.setHeader).toBeCalledTimes(3)
|
||||
expect(ctx.res.setHeader).nthCalledWith(1, 'Content-Type', 'text/json; charset=utf-8')
|
||||
expect(ctx.res.setHeader).nthCalledWith(2, 'Content-Length', Buffer.byteLength(errJson))
|
||||
expect(ctx.res.setHeader).nthCalledWith(3, 'Cache-Control', 'no-cache, no-store, max-age=0, must-revalidate')
|
||||
expect(params.resources.errorTemplate).not.toBeCalled()
|
||||
expect(ctx.res.end).toBeCalledTimes(1)
|
||||
expect(ctx.res.end).toBeCalledWith(errJson, 'utf-8')
|
||||
})
|
||||
|
||||
test('should search all possible paths when read source', async () => {
|
||||
const params = createParams()
|
||||
params.options.debug = true
|
||||
const errorMiddleware = createErrorMiddleware(params)
|
||||
const error = {}
|
||||
const ctx = createServerContext()
|
||||
|
||||
await errorMiddleware(error, ctx.req, ctx.res, ctx.next)
|
||||
|
||||
const frame = { fileName: 'webpack:///test-error.js?desc=test' }
|
||||
const readSource = Youch.mock.calls[0][2]
|
||||
await readSource(frame)
|
||||
|
||||
const fileName = 'test-error.js'
|
||||
expect(frame).toEqual({ fileName })
|
||||
expect(path.resolve).toBeCalledTimes(5)
|
||||
expect(fs.readFile).toBeCalledTimes(5)
|
||||
expect(fs.readFile).nthCalledWith(1, `resolve(${params.options.srcDir}, ${fileName})`, 'utf-8')
|
||||
expect(fs.readFile).nthCalledWith(2, `resolve(${params.options.rootDir}, ${fileName})`, 'utf-8')
|
||||
expect(fs.readFile).nthCalledWith(3, `resolve(join(${params.options.buildDir}, dist, server), ${fileName})`, 'utf-8')
|
||||
expect(fs.readFile).nthCalledWith(4, `resolve(${params.options.buildDir}, ${fileName})`, 'utf-8')
|
||||
expect(fs.readFile).nthCalledWith(5, `resolve(${process.cwd()}, ${fileName})`, 'utf-8')
|
||||
})
|
||||
|
||||
test('should return source content after read source', async () => {
|
||||
const params = createParams()
|
||||
params.options.debug = true
|
||||
const errorMiddleware = createErrorMiddleware(params)
|
||||
const error = {}
|
||||
const ctx = createServerContext()
|
||||
|
||||
await errorMiddleware(error, ctx.req, ctx.res, ctx.next)
|
||||
|
||||
const frame = { fileName: 'webpack:///test-error.js?desc=test' }
|
||||
const readSource = Youch.mock.calls[0][2]
|
||||
fs.readFile.mockImplementationOnce(() => Promise.resolve('source content'))
|
||||
await readSource(frame)
|
||||
|
||||
const fileName = 'test-error.js'
|
||||
expect(frame).toEqual({
|
||||
fileName,
|
||||
contents: 'source content',
|
||||
fullPath: `resolve(${params.options.srcDir}, ${fileName})`
|
||||
})
|
||||
})
|
||||
|
||||
test('should return relative fileName if fileName is absolute path', async () => {
|
||||
const params = createParams()
|
||||
params.options.debug = true
|
||||
const errorMiddleware = createErrorMiddleware(params)
|
||||
const error = {}
|
||||
const ctx = createServerContext()
|
||||
|
||||
await errorMiddleware(error, ctx.req, ctx.res, ctx.next)
|
||||
|
||||
const frame = { fileName: 'webpack:///test-error.js?desc=test' }
|
||||
const readSource = Youch.mock.calls[0][2]
|
||||
fs.readFile.mockImplementationOnce(() => Promise.resolve('source content'))
|
||||
path.isAbsolute.mockReturnValueOnce(true)
|
||||
path.relative.mockImplementationOnce((...args) => `relative(${args.join(', ')})`)
|
||||
await readSource(frame)
|
||||
|
||||
const fullPath = `resolve(${params.options.srcDir}, test-error.js)`
|
||||
expect(frame).toEqual({
|
||||
fileName: `relative(${params.options.rootDir}, ${fullPath})`,
|
||||
contents: 'source content',
|
||||
fullPath
|
||||
})
|
||||
})
|
||||
|
||||
test('should ignore error when reading source', async () => {
|
||||
const params = createParams()
|
||||
params.options.debug = true
|
||||
const errorMiddleware = createErrorMiddleware(params)
|
||||
const error = {}
|
||||
const ctx = createServerContext()
|
||||
|
||||
await errorMiddleware(error, ctx.req, ctx.res, ctx.next)
|
||||
|
||||
const frame = { fileName: 'webpack:///test-error.js?desc=test' }
|
||||
const readSource = Youch.mock.calls[0][2]
|
||||
fs.readFile.mockReturnValueOnce(Promise.reject(new Error('read failed')))
|
||||
await readSource(frame)
|
||||
|
||||
const fileName = 'test-error.js'
|
||||
expect(frame).toEqual({ fileName })
|
||||
expect(path.resolve).toBeCalledTimes(5)
|
||||
expect(fs.readFile).toBeCalledTimes(5)
|
||||
})
|
||||
|
||||
test('should return if fileName is unknown when read source', async () => {
|
||||
const params = createParams()
|
||||
params.options.debug = true
|
||||
const errorMiddleware = createErrorMiddleware(params)
|
||||
const error = {}
|
||||
const ctx = createServerContext()
|
||||
|
||||
await errorMiddleware(error, ctx.req, ctx.res, ctx.next)
|
||||
|
||||
const frame = {}
|
||||
const readSource = Youch.mock.calls[0][2]
|
||||
await readSource(frame)
|
||||
|
||||
expect(frame.fileName).toBeNull()
|
||||
})
|
||||
})
|
142
packages/server/test/middleware/modern.test.js
Normal file
142
packages/server/test/middleware/modern.test.js
Normal file
@ -0,0 +1,142 @@
|
||||
import consola from 'consola'
|
||||
|
||||
jest.mock('chalk', () => ({
|
||||
green: {
|
||||
bold: modern => `greenBold(${modern})`
|
||||
}
|
||||
}))
|
||||
|
||||
const createContext = () => ({
|
||||
resources: {},
|
||||
options: {
|
||||
render: {}
|
||||
}
|
||||
})
|
||||
|
||||
const createServerContext = () => ({
|
||||
req: { headers: {} },
|
||||
next: jest.fn()
|
||||
})
|
||||
|
||||
describe('server: modernMiddleware', () => {
|
||||
let createModernMiddleware
|
||||
|
||||
beforeEach(() => {
|
||||
jest.isolateModules(() => {
|
||||
createModernMiddleware = require('../../src/middleware/modern').default
|
||||
})
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
test('should return modern middleware', () => {
|
||||
const modernMiddleware = createModernMiddleware({})
|
||||
expect(modernMiddleware).toBeInstanceOf(Function)
|
||||
})
|
||||
|
||||
test('should not detect modern build if modern mode is specified', () => {
|
||||
const context = createContext()
|
||||
const modernMiddleware = createModernMiddleware({ context })
|
||||
const ctx = createServerContext()
|
||||
|
||||
context.options.modern = false
|
||||
modernMiddleware(ctx.req, ctx.res, ctx.next)
|
||||
context.options.modern = 'client'
|
||||
modernMiddleware(ctx.req, ctx.res, ctx.next)
|
||||
context.options.modern = 'server'
|
||||
modernMiddleware(ctx.req, ctx.res, ctx.next)
|
||||
|
||||
expect(ctx.req.modernMode).toEqual(false)
|
||||
})
|
||||
|
||||
test('should detect client modern build and display message', () => {
|
||||
const context = createContext()
|
||||
const modernMiddleware = createModernMiddleware({ context })
|
||||
const ctx = createServerContext()
|
||||
|
||||
context.resources.modernManifest = {}
|
||||
modernMiddleware(ctx.req, ctx.res, ctx.next)
|
||||
expect(context.options.modern).toEqual('client')
|
||||
expect(consola.info).toBeCalledWith('Modern bundles are detected. Modern mode (greenBold(client)) is enabled now.')
|
||||
})
|
||||
|
||||
test('should detect server modern build and display message', () => {
|
||||
const context = createContext()
|
||||
const modernMiddleware = createModernMiddleware({ context })
|
||||
const ctx = createServerContext()
|
||||
|
||||
context.options.render.ssr = true
|
||||
context.resources.modernManifest = {}
|
||||
modernMiddleware(ctx.req, ctx.res, ctx.next)
|
||||
expect(context.options.modern).toEqual('server')
|
||||
expect(consola.info).toBeCalledWith('Modern bundles are detected. Modern mode (greenBold(server)) is enabled now.')
|
||||
})
|
||||
|
||||
test('should not detect modern browser if modern build is not found', () => {
|
||||
const context = createContext()
|
||||
const modernMiddleware = createModernMiddleware({ context })
|
||||
const ctx = createServerContext()
|
||||
|
||||
modernMiddleware(ctx.req, ctx.res, ctx.next)
|
||||
|
||||
expect(ctx.req.modernMode).toBeUndefined()
|
||||
})
|
||||
|
||||
test('should not detect modern browser if connect has been detected', () => {
|
||||
const context = createContext()
|
||||
const modernMiddleware = createModernMiddleware({ context })
|
||||
const ctx = createServerContext()
|
||||
ctx.req.socket = { isModernBrowser: true }
|
||||
|
||||
context.options.dev = true
|
||||
context.options.modern = 'server'
|
||||
modernMiddleware(ctx.req, ctx.res, ctx.next)
|
||||
|
||||
expect(ctx.req.modernMode).toEqual(true)
|
||||
})
|
||||
|
||||
test('should detect modern browser based on user-agent', () => {
|
||||
const context = createContext()
|
||||
const modernMiddleware = createModernMiddleware({ context })
|
||||
const ctx = createServerContext()
|
||||
const ua = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36'
|
||||
ctx.req.headers['user-agent'] = ua
|
||||
ctx.req.socket = {}
|
||||
|
||||
context.options.dev = true
|
||||
context.options.modern = 'server'
|
||||
modernMiddleware(ctx.req, ctx.res, ctx.next)
|
||||
|
||||
expect(ctx.req.socket.isModernBrowser).toEqual(true)
|
||||
expect(ctx.req.modernMode).toEqual(true)
|
||||
})
|
||||
|
||||
test('should detect legacy browser based on user-agent', () => {
|
||||
const context = createContext()
|
||||
const modernMiddleware = createModernMiddleware({ context })
|
||||
const ctx = createServerContext()
|
||||
const ua = 'Mozilla/5.0 (Windows; U; MSIE 9.0; WIndows NT 9.0; en-US))'
|
||||
ctx.req.headers['user-agent'] = ua
|
||||
ctx.req.socket = {}
|
||||
|
||||
context.options.dev = true
|
||||
context.options.modern = 'client'
|
||||
modernMiddleware(ctx.req, ctx.res, ctx.next)
|
||||
|
||||
expect(ctx.req.socket.isModernBrowser).toEqual(false)
|
||||
})
|
||||
|
||||
test('should ignore illegal user-agent', () => {
|
||||
const context = createContext()
|
||||
const modernMiddleware = createModernMiddleware({ context })
|
||||
const ctx = createServerContext()
|
||||
const ua = 'illegal user agent'
|
||||
ctx.req.headers['user-agent'] = ua
|
||||
ctx.req.socket = {}
|
||||
|
||||
context.options.dev = true
|
||||
context.options.modern = 'client'
|
||||
modernMiddleware(ctx.req, ctx.res, ctx.next)
|
||||
|
||||
expect(ctx.req.socket.isModernBrowser).toEqual(false)
|
||||
})
|
||||
})
|
309
packages/server/test/middleware/nuxt.test.js
Normal file
309
packages/server/test/middleware/nuxt.test.js
Normal file
@ -0,0 +1,309 @@
|
||||
import generateETag from 'etag'
|
||||
import fresh from 'fresh'
|
||||
import consola from 'consola'
|
||||
|
||||
import createNuxtMiddleware from '../../src/middleware/nuxt'
|
||||
|
||||
jest.mock('etag', () => jest.fn(() => 'etag-hash'))
|
||||
jest.mock('fresh')
|
||||
|
||||
const createContext = () => ({
|
||||
options: {
|
||||
render: { http2: false },
|
||||
build: {}
|
||||
},
|
||||
nuxt: {
|
||||
callHook: jest.fn()
|
||||
},
|
||||
renderRoute: jest.fn(),
|
||||
resources: {}
|
||||
})
|
||||
|
||||
const createServerContext = () => ({
|
||||
req: { headers: {}, url: 'http://localhost/test/server' },
|
||||
res: { headers: {}, setHeader: jest.fn(), end: jest.fn() },
|
||||
next: jest.fn()
|
||||
})
|
||||
|
||||
describe('server: nuxtMiddleware', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
test('should return nuxt middleware', () => {
|
||||
const nuxtMiddleware = createNuxtMiddleware({})
|
||||
expect(nuxtMiddleware).toBeInstanceOf(Function)
|
||||
})
|
||||
|
||||
test('should render route in nuxt middleware', async () => {
|
||||
const context = createContext()
|
||||
const result = { html: 'rendered html' }
|
||||
context.renderRoute.mockReturnValue(result)
|
||||
const nuxtMiddleware = createNuxtMiddleware(context)
|
||||
const { req, res, next } = createServerContext()
|
||||
|
||||
const html = await nuxtMiddleware(req, res, next)
|
||||
|
||||
expect(context.renderRoute).toBeCalledTimes(1)
|
||||
expect(context.renderRoute).toBeCalledWith(req.url, { req, res })
|
||||
|
||||
expect(context.nuxt.callHook).toBeCalledTimes(2)
|
||||
expect(context.nuxt.callHook).nthCalledWith(1, 'render:route', req.url, result, { req, res })
|
||||
expect(context.nuxt.callHook).nthCalledWith(2, 'render:routeDone', req.url, result, { req, res })
|
||||
|
||||
expect(res.setHeader).toBeCalledTimes(3)
|
||||
expect(res.setHeader).nthCalledWith(1, 'Content-Type', 'text/html; charset=utf-8')
|
||||
expect(res.setHeader).nthCalledWith(2, 'Accept-Ranges', 'none')
|
||||
expect(res.setHeader).nthCalledWith(3, 'Content-Length', Buffer.byteLength(result.html))
|
||||
|
||||
expect(res.end).toBeCalledTimes(1)
|
||||
expect(res.end).toBeCalledWith(result.html, 'utf8')
|
||||
expect(res.statusCode).toEqual(200)
|
||||
expect(html).toEqual(result.html)
|
||||
})
|
||||
|
||||
test('should early return if route is redirected', async () => {
|
||||
const context = createContext()
|
||||
const result = { html: 'rendered html', redirected: true }
|
||||
context.renderRoute.mockReturnValue(result)
|
||||
const nuxtMiddleware = createNuxtMiddleware(context)
|
||||
const { req, res, next } = createServerContext()
|
||||
|
||||
const html = await nuxtMiddleware(req, res, next)
|
||||
|
||||
expect(context.nuxt.callHook).toBeCalledTimes(2)
|
||||
expect(context.nuxt.callHook).nthCalledWith(1, 'render:route', req.url, result, { req, res })
|
||||
expect(context.nuxt.callHook).nthCalledWith(2, 'render:routeDone', req.url, result, { req, res })
|
||||
|
||||
expect(res.setHeader).not.toBeCalled()
|
||||
expect(res.end).not.toBeCalled()
|
||||
expect(res.statusCode).toEqual(200)
|
||||
expect(html).toEqual(result.html)
|
||||
})
|
||||
|
||||
test('should set error status code when error occurred', async () => {
|
||||
const context = createContext()
|
||||
const result = { html: 'rendered html', error: new Error('render error') }
|
||||
const nuxt = { error: { statusCode: 404 } }
|
||||
context.renderRoute.mockImplementation((url, ctx) => {
|
||||
ctx.nuxt = nuxt
|
||||
return result
|
||||
})
|
||||
const nuxtMiddleware = createNuxtMiddleware(context)
|
||||
const { req, res, next } = createServerContext()
|
||||
|
||||
const html = await nuxtMiddleware(req, res, next)
|
||||
|
||||
expect(context.nuxt.callHook).toBeCalledTimes(2)
|
||||
expect(context.nuxt.callHook).nthCalledWith(1, 'render:route', req.url, result, { req, res, nuxt })
|
||||
expect(context.nuxt.callHook).nthCalledWith(2, 'render:routeDone', req.url, result, { req, res, nuxt })
|
||||
|
||||
expect(res.statusCode).toEqual(404)
|
||||
expect(html).toEqual(result.html)
|
||||
})
|
||||
|
||||
test('should add etag after rendering', async () => {
|
||||
const context = createContext()
|
||||
const result = { html: 'rendered html' }
|
||||
context.renderRoute.mockReturnValue(result)
|
||||
context.options.render.etag = true
|
||||
const nuxtMiddleware = createNuxtMiddleware(context)
|
||||
const { req, res, next } = createServerContext()
|
||||
|
||||
await nuxtMiddleware(req, res, next)
|
||||
|
||||
expect(generateETag).toBeCalledTimes(1)
|
||||
expect(generateETag).toBeCalledWith('rendered html', true)
|
||||
expect(res.setHeader).nthCalledWith(1, 'ETag', 'etag-hash')
|
||||
})
|
||||
|
||||
test('should return 304 if request is fresh', async () => {
|
||||
const context = createContext()
|
||||
const result = { html: 'rendered html' }
|
||||
context.renderRoute.mockReturnValue(result)
|
||||
context.options.render.etag = true
|
||||
const nuxtMiddleware = createNuxtMiddleware(context)
|
||||
const { req, res, next } = createServerContext()
|
||||
fresh.mockReturnValue(true)
|
||||
|
||||
await nuxtMiddleware(req, res, next)
|
||||
|
||||
expect(res.statusCode).toEqual(304)
|
||||
expect(res.end).toBeCalledTimes(1)
|
||||
expect(res.end).toBeCalledWith()
|
||||
})
|
||||
|
||||
test('should add http2 links header if http2 push is enabled', async () => {
|
||||
const context = createContext()
|
||||
const result = {
|
||||
html: 'rendered html',
|
||||
getPreloadFiles: jest.fn(() => ['/nuxt/preload1.js', '/nuxt/preload2.js'])
|
||||
}
|
||||
context.renderRoute.mockReturnValue(result)
|
||||
const pushAssets = jest.fn((req, res, publicPath, preloadFiles) => preloadFiles)
|
||||
context.options.render.http2 = { push: true, pushAssets }
|
||||
context.resources = { clientManifest: { publicPath: '/nuxt' } }
|
||||
|
||||
const nuxtMiddleware = createNuxtMiddleware(context)
|
||||
const { req, res, next } = createServerContext()
|
||||
fresh.mockReturnValue(true)
|
||||
|
||||
await nuxtMiddleware(req, res, next)
|
||||
|
||||
expect(pushAssets).toBeCalledWith(req, res, '/nuxt', ['/nuxt/preload1.js', '/nuxt/preload2.js'])
|
||||
expect(res.setHeader).nthCalledWith(1, 'Link', '/nuxt/preload1.js, /nuxt/preload2.js')
|
||||
})
|
||||
|
||||
test('should only include script and style in http2 push by default', async () => {
|
||||
const context = createContext()
|
||||
const result = {
|
||||
html: 'rendered html',
|
||||
getPreloadFiles: jest.fn(() => [
|
||||
{ file: '/nuxt/preload1.js', asType: 'script' },
|
||||
{ file: '/nuxt/preload2.js', asType: 'script' },
|
||||
{ file: '/nuxt/style.css', asType: 'style' },
|
||||
{ file: '/nuxt/font.woff', asType: 'font' }
|
||||
])
|
||||
}
|
||||
context.renderRoute.mockReturnValue(result)
|
||||
context.options.render.http2 = { push: true }
|
||||
context.resources = { clientManifest: { publicPath: '/nuxt' } }
|
||||
|
||||
const nuxtMiddleware = createNuxtMiddleware(context)
|
||||
const { req, res, next } = createServerContext()
|
||||
fresh.mockReturnValue(true)
|
||||
|
||||
await nuxtMiddleware(req, res, next)
|
||||
|
||||
expect(res.setHeader).nthCalledWith(1, 'Link', '</nuxt/nuxt/preload1.js>; rel=preload; as=script, </nuxt/nuxt/preload2.js>; rel=preload; as=script, </nuxt/nuxt/style.css>; rel=preload; as=style')
|
||||
})
|
||||
|
||||
test('should ignore preload files which are excluded by shouldPush', async () => {
|
||||
const context = createContext()
|
||||
const result = {
|
||||
html: 'rendered html',
|
||||
getPreloadFiles: jest.fn(() => [
|
||||
{ file: '/nuxt/preload1.js', asType: 'script' },
|
||||
{ file: '/nuxt/preload2.js', asType: 'script', modern: true },
|
||||
{ file: '/nuxt/style.css', asType: 'style' },
|
||||
{ file: '/nuxt/font.woff', asType: 'font' }
|
||||
])
|
||||
}
|
||||
context.renderRoute.mockReturnValue(result)
|
||||
context.options.dev = true
|
||||
context.options.build.crossorigin = 'use-credentials'
|
||||
context.options.render.http2 = {
|
||||
push: true,
|
||||
shouldPush: jest.fn((fileWithoutQuery, asType) => asType === 'script')
|
||||
}
|
||||
context.resources = { clientManifest: { publicPath: '/nuxt' } }
|
||||
|
||||
const nuxtMiddleware = createNuxtMiddleware(context)
|
||||
const { req, res, next } = createServerContext()
|
||||
fresh.mockReturnValue(true)
|
||||
|
||||
await nuxtMiddleware(req, res, next)
|
||||
|
||||
expect(consola.warn).toBeCalledWith('http2.shouldPush is deprecated. Use http2.pushAssets function')
|
||||
expect(context.options.render.http2.shouldPush).toBeCalledTimes(4)
|
||||
expect(res.setHeader).nthCalledWith(1, 'Link', '</nuxt/nuxt/preload1.js>; rel=preload; crossorigin=use-credentials; as=script, </nuxt/nuxt/preload2.js>; rel=modulepreload; crossorigin=use-credentials; as=script')
|
||||
})
|
||||
|
||||
test('should add csp header if csp is enabled', async () => {
|
||||
const context = createContext()
|
||||
const result = { html: 'rendered html', cspScriptSrcHashes: ['sha256-hashes'] }
|
||||
context.renderRoute.mockReturnValue(result)
|
||||
context.options.render.csp = true
|
||||
|
||||
const nuxtMiddleware = createNuxtMiddleware(context)
|
||||
const { req, res, next } = createServerContext()
|
||||
fresh.mockReturnValue(true)
|
||||
|
||||
await nuxtMiddleware(req, res, next)
|
||||
|
||||
expect(res.setHeader).nthCalledWith(1, 'Content-Security-Policy', "script-src 'self' sha256-hashes")
|
||||
})
|
||||
|
||||
test('should support allowedSources for setting csp header', async () => {
|
||||
const context = createContext()
|
||||
const result = { html: 'rendered html', cspScriptSrcHashes: ['sha256-hashes'] }
|
||||
context.renderRoute.mockReturnValue(result)
|
||||
context.options.dev = true
|
||||
context.options.render.csp = {
|
||||
reportOnly: true,
|
||||
allowedSources: ['/nuxt/*.js', '/nuxt/images/*']
|
||||
}
|
||||
|
||||
const nuxtMiddleware = createNuxtMiddleware(context)
|
||||
const { req, res, next } = createServerContext()
|
||||
fresh.mockReturnValue(true)
|
||||
|
||||
await nuxtMiddleware(req, res, next)
|
||||
|
||||
expect(res.setHeader).nthCalledWith(
|
||||
1,
|
||||
'Content-Security-Policy-Report-Only',
|
||||
"script-src 'self' 'unsafe-eval' sha256-hashes /nuxt/*.js /nuxt/images/*"
|
||||
)
|
||||
})
|
||||
|
||||
test('should support policies for setting csp header', async () => {
|
||||
const context = createContext()
|
||||
const result = { html: 'rendered html', cspScriptSrcHashes: ['sha256-hashes'] }
|
||||
context.renderRoute.mockReturnValue(result)
|
||||
context.options.dev = true
|
||||
context.options.render.csp = {
|
||||
policies: {
|
||||
'script-src': [
|
||||
'/nuxt.js',
|
||||
'/test.js'
|
||||
],
|
||||
'report-uri': [
|
||||
'/report'
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
const nuxtMiddleware = createNuxtMiddleware(context)
|
||||
const { req, res, next } = createServerContext()
|
||||
fresh.mockReturnValue(true)
|
||||
|
||||
await nuxtMiddleware(req, res, next)
|
||||
|
||||
expect(res.setHeader).nthCalledWith(
|
||||
1,
|
||||
'Content-Security-Policy',
|
||||
"script-src sha256-hashes 'self' /nuxt.js /test.js; report-uri /report"
|
||||
)
|
||||
})
|
||||
|
||||
test('should catch error during running nuxt middleware', async () => {
|
||||
const context = createContext()
|
||||
const err = Error('render error')
|
||||
context.renderRoute.mockImplementation(() => {
|
||||
throw err
|
||||
})
|
||||
const nuxtMiddleware = createNuxtMiddleware(context)
|
||||
const { req, res, next } = createServerContext()
|
||||
fresh.mockReturnValue(true)
|
||||
|
||||
await nuxtMiddleware(req, res, next)
|
||||
|
||||
expect(next).toBeCalledWith(err)
|
||||
})
|
||||
|
||||
test('should log and return error during redirecting in nuxt middleware', async () => {
|
||||
const context = createContext()
|
||||
const err = Error('render error')
|
||||
context.renderRoute.mockImplementation((url, ctx) => {
|
||||
ctx.redirected = true
|
||||
throw err
|
||||
})
|
||||
const nuxtMiddleware = createNuxtMiddleware(context)
|
||||
const { req, res, next } = createServerContext()
|
||||
fresh.mockReturnValue(true)
|
||||
|
||||
expect(await nuxtMiddleware(req, res, next)).toBe(err)
|
||||
expect(consola.error).toBeCalledWith(err)
|
||||
})
|
||||
})
|
103
packages/server/test/middleware/timing.test.js
Normal file
103
packages/server/test/middleware/timing.test.js
Normal file
@ -0,0 +1,103 @@
|
||||
import consola from 'consola'
|
||||
import onHeaders from 'on-headers'
|
||||
import { Timer } from '@nuxt/utils'
|
||||
|
||||
import createTimingMiddleware from '../../src/middleware/timing'
|
||||
|
||||
jest.mock('on-headers')
|
||||
jest.mock('@nuxt/utils')
|
||||
|
||||
const createServerContext = () => ({
|
||||
req: {},
|
||||
res: {
|
||||
getHeader: jest.fn(),
|
||||
setHeader: jest.fn()
|
||||
},
|
||||
next: jest.fn()
|
||||
})
|
||||
|
||||
describe('server: timingMiddleware', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
test('should return timing middleware', () => {
|
||||
const timingMiddleware = createTimingMiddleware({})
|
||||
expect(timingMiddleware).toBeInstanceOf(Function)
|
||||
})
|
||||
|
||||
test('should warn duplicate registration', () => {
|
||||
const timingMiddleware = createTimingMiddleware({})
|
||||
const ctx = createServerContext()
|
||||
|
||||
ctx.res.timing = true
|
||||
timingMiddleware(ctx.req, ctx.res, ctx.next)
|
||||
|
||||
expect(consola.warn).toBeCalledWith('server-timing is already registered.')
|
||||
})
|
||||
|
||||
test('should register timer for recording timing', () => {
|
||||
const timingMiddleware = createTimingMiddleware({ total: true })
|
||||
const ctx = createServerContext()
|
||||
|
||||
timingMiddleware(ctx.req, ctx.res, ctx.next)
|
||||
|
||||
expect(ctx.res.timing).toBeDefined()
|
||||
expect(Timer.prototype.start).toBeCalledTimes(1)
|
||||
expect(Timer.prototype.start).toBeCalledWith('total', 'Nuxt Server Time')
|
||||
expect(onHeaders).toBeCalledTimes(1)
|
||||
expect(onHeaders).toBeCalledWith(ctx.res, expect.any(Function))
|
||||
expect(ctx.next).toBeCalledTimes(1)
|
||||
})
|
||||
|
||||
test('should add Server-Timing header before sending header', () => {
|
||||
const timingMiddleware = createTimingMiddleware({ total: true })
|
||||
const ctx = createServerContext()
|
||||
|
||||
timingMiddleware(ctx.req, ctx.res, ctx.next)
|
||||
|
||||
const headerCallback = onHeaders.mock.calls[0][1]
|
||||
Timer.prototype.end.mockReturnValueOnce({
|
||||
name: 'total',
|
||||
duration: 300,
|
||||
description: 'Nuxt Server Time'
|
||||
})
|
||||
headerCallback()
|
||||
|
||||
expect(ctx.res.timing.end).toBeCalledTimes(1)
|
||||
expect(ctx.res.timing.end).toBeCalledWith('total')
|
||||
expect(ctx.res.getHeader).toBeCalledTimes(1)
|
||||
expect(ctx.res.getHeader).toBeCalledWith('Server-Timing')
|
||||
expect(ctx.res.setHeader).toBeCalledTimes(1)
|
||||
expect(ctx.res.setHeader).toBeCalledWith('Server-Timing', 'total;dur=300;desc="Nuxt Server Time"')
|
||||
})
|
||||
|
||||
test('should ignore desc if empty', () => {
|
||||
const timingMiddleware = createTimingMiddleware({})
|
||||
const ctx = createServerContext()
|
||||
|
||||
timingMiddleware(ctx.req, ctx.res, ctx.next)
|
||||
|
||||
expect(
|
||||
ctx.res.timing.formatHeader({
|
||||
name: 'timing-test',
|
||||
duration: 300
|
||||
})
|
||||
).toEqual('timing-test;dur=300')
|
||||
})
|
||||
|
||||
test('should not send Server-Timing header if empty', () => {
|
||||
const timingMiddleware = createTimingMiddleware({ total: true })
|
||||
const ctx = createServerContext()
|
||||
|
||||
timingMiddleware(ctx.req, ctx.res, ctx.next)
|
||||
|
||||
const headerCallback = onHeaders.mock.calls[0][1]
|
||||
headerCallback()
|
||||
|
||||
expect(ctx.res.timing.end).toBeCalledTimes(1)
|
||||
expect(ctx.res.timing.end).toBeCalledWith('total')
|
||||
expect(ctx.res.getHeader).not.toBeCalled()
|
||||
expect(ctx.res.setHeader).not.toBeCalled()
|
||||
})
|
||||
})
|
598
packages/server/test/server.test.js
Normal file
598
packages/server/test/server.test.js
Normal file
@ -0,0 +1,598 @@
|
||||
import path from 'path'
|
||||
import connect from 'connect'
|
||||
import consola from 'consola'
|
||||
import serveStatic from 'serve-static'
|
||||
import servePlaceholder from 'serve-placeholder'
|
||||
import launchMiddleware from 'launch-editor-middleware'
|
||||
import { determineGlobals, isUrl } from '@nuxt/utils'
|
||||
import { VueRenderer } from '@nuxt/vue-renderer'
|
||||
|
||||
import Server from '../src/server'
|
||||
import Listener from '../src/listener'
|
||||
import ServerContext from '../src/context'
|
||||
import renderAndGetWindow from '../src/jsdom'
|
||||
import nuxtMiddleware from '../src/middleware/nuxt'
|
||||
import errorMiddleware from '../src/middleware/error'
|
||||
import createModernMiddleware from '../src/middleware/modern'
|
||||
import createTimingMiddleware from '../src/middleware/timing'
|
||||
|
||||
jest.mock('connect')
|
||||
jest.mock('serve-static')
|
||||
jest.mock('serve-placeholder')
|
||||
jest.mock('launch-editor-middleware')
|
||||
jest.mock('@nuxt/utils')
|
||||
jest.mock('@nuxt/vue-renderer')
|
||||
jest.mock('../src/listener')
|
||||
jest.mock('../src/context')
|
||||
jest.mock('../src/jsdom')
|
||||
jest.mock('../src/middleware/nuxt')
|
||||
jest.mock('../src/middleware/error')
|
||||
jest.mock('../src/middleware/modern')
|
||||
jest.mock('../src/middleware/timing')
|
||||
|
||||
describe('server: server', () => {
|
||||
const createNuxt = () => ({
|
||||
options: {
|
||||
dir: {
|
||||
static: 'var/nuxt/static'
|
||||
},
|
||||
srcDir: '/var/nuxt/src',
|
||||
buildDir: '/var/nuxt/build',
|
||||
globalName: 'test-global-name',
|
||||
globals: { id: 'test-globals' },
|
||||
build: {
|
||||
publicPath: '__nuxt_test'
|
||||
},
|
||||
render: {
|
||||
id: 'test-render',
|
||||
dist: {
|
||||
id: 'test-render-dist'
|
||||
},
|
||||
static: {
|
||||
id: 'test-render-static',
|
||||
prefix: 'test-render-static-prefix'
|
||||
}
|
||||
},
|
||||
server: {},
|
||||
serverMiddleware: []
|
||||
},
|
||||
hook: jest.fn(),
|
||||
callHook: jest.fn(),
|
||||
resolver: {
|
||||
requireModule: jest.fn()
|
||||
}
|
||||
})
|
||||
|
||||
beforeAll(() => {
|
||||
jest.spyOn(path, 'join').mockImplementation((...args) => `join(${args.join(', ')})`)
|
||||
jest.spyOn(path, 'resolve').mockImplementation((...args) => `resolve(${args.join(', ')})`)
|
||||
connect.mockReturnValue({ use: jest.fn() })
|
||||
serveStatic.mockImplementation(dir => ({ id: 'test-serve-static', dir }))
|
||||
createModernMiddleware.mockImplementation(options => ({
|
||||
id: 'test-modern-middleware',
|
||||
...options
|
||||
}))
|
||||
nuxtMiddleware.mockImplementation(options => ({
|
||||
id: 'test-nuxt-middleware',
|
||||
...options
|
||||
}))
|
||||
errorMiddleware.mockImplementation(options => ({
|
||||
id: 'test-error-middleware',
|
||||
...options
|
||||
}))
|
||||
createTimingMiddleware.mockImplementation(options => ({
|
||||
id: 'test-timing-middleware',
|
||||
...options
|
||||
}))
|
||||
launchMiddleware.mockImplementation(options => ({
|
||||
id: 'test-open-in-editor-middleware',
|
||||
...options
|
||||
}))
|
||||
servePlaceholder.mockImplementation(options => ({
|
||||
key: 'test-serve-placeholder',
|
||||
...options
|
||||
}))
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
path.join.mockRestore()
|
||||
path.resolve.mockRestore()
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
test('should construct server', () => {
|
||||
const nuxt = createNuxt()
|
||||
determineGlobals.mockReturnValueOnce({
|
||||
...nuxt.options.globals,
|
||||
name: nuxt.options.globalName
|
||||
})
|
||||
let server = new Server(nuxt)
|
||||
|
||||
expect(server.nuxt).toBe(nuxt)
|
||||
expect(server.options).toBe(nuxt.options)
|
||||
expect(server.publicPath).toBe('__nuxt_test')
|
||||
expect(server.resources).toEqual({})
|
||||
expect(server.devMiddleware).toBeNull()
|
||||
expect(server.hotMiddleware).toBeNull()
|
||||
expect(server.listeners).toEqual([])
|
||||
expect(connect).toBeCalledTimes(1)
|
||||
expect(server.nuxt.hook).toBeCalledTimes(1)
|
||||
expect(server.nuxt.hook).toBeCalledWith('close', expect.any(Function))
|
||||
|
||||
const closeHook = server.nuxt.hook.mock.calls[0][1]
|
||||
server.close = jest.fn()
|
||||
expect(server.close).not.toBeCalled()
|
||||
closeHook()
|
||||
expect(server.close).toBeCalledTimes(1)
|
||||
|
||||
nuxt.options.build._publicPath = 'http://localhost:3000/test'
|
||||
isUrl.mockReturnValueOnce(true)
|
||||
server = new Server(nuxt)
|
||||
expect(server.publicPath).toBe(nuxt.options.build._publicPath)
|
||||
})
|
||||
|
||||
test('should be ready for listening', async () => {
|
||||
const nuxt = createNuxt()
|
||||
const server = new Server(nuxt)
|
||||
const renderer = {
|
||||
ready: jest.fn()
|
||||
}
|
||||
const context = jest.fn()
|
||||
VueRenderer.mockImplementationOnce(() => renderer)
|
||||
ServerContext.mockImplementationOnce(() => context)
|
||||
server.setupMiddleware = jest.fn()
|
||||
path.join.mockRestore()
|
||||
path.resolve.mockRestore()
|
||||
|
||||
await server.ready()
|
||||
|
||||
expect(server.nuxt.callHook).toBeCalledTimes(2)
|
||||
expect(server.nuxt.callHook).nthCalledWith(1, 'render:before', server, server.options.render)
|
||||
expect(server.nuxt.callHook).nthCalledWith(2, 'render:done', server)
|
||||
expect(ServerContext).toBeCalledTimes(1)
|
||||
expect(ServerContext).toBeCalledWith(server)
|
||||
expect(VueRenderer).toBeCalledTimes(1)
|
||||
expect(VueRenderer).toBeCalledWith(context)
|
||||
expect(server.renderer).toBe(renderer)
|
||||
expect(renderer.ready).toBeCalledTimes(1)
|
||||
expect(server.setupMiddleware).toBeCalledTimes(1)
|
||||
|
||||
jest.spyOn(path, 'join').mockImplementation((...args) => `join(${args.join(', ')})`)
|
||||
jest.spyOn(path, 'resolve').mockImplementation((...args) => `resolve(${args.join(', ')})`)
|
||||
})
|
||||
|
||||
test('should setup middleware', async () => {
|
||||
const nuxt = createNuxt()
|
||||
const server = new Server(nuxt)
|
||||
server.useMiddleware = jest.fn()
|
||||
server.renderer = {
|
||||
context: { id: 'test-server-context' }
|
||||
}
|
||||
|
||||
await server.setupMiddleware()
|
||||
|
||||
expect(server.nuxt.callHook).toBeCalledTimes(2)
|
||||
expect(server.nuxt.callHook).nthCalledWith(1, 'render:setupMiddleware', server.app)
|
||||
expect(server.nuxt.callHook).nthCalledWith(2, 'render:errorMiddleware', server.app)
|
||||
|
||||
expect(server.useMiddleware).toBeCalledTimes(5)
|
||||
expect(serveStatic).toBeCalledTimes(2)
|
||||
expect(serveStatic).nthCalledWith(1, 'resolve(/var/nuxt/src, var/nuxt/static)', server.options.render.static)
|
||||
expect(server.useMiddleware).nthCalledWith(1, {
|
||||
dir: 'resolve(/var/nuxt/src, var/nuxt/static)',
|
||||
id: 'test-serve-static',
|
||||
prefix: 'test-render-static-prefix'
|
||||
})
|
||||
expect(serveStatic).nthCalledWith(2, 'resolve(/var/nuxt/build, dist, client)', server.options.render.dist)
|
||||
expect(server.useMiddleware).nthCalledWith(2, {
|
||||
handler: {
|
||||
dir: 'resolve(/var/nuxt/build, dist, client)',
|
||||
id: 'test-serve-static'
|
||||
},
|
||||
path: '__nuxt_test'
|
||||
})
|
||||
|
||||
const modernMiddleware = {
|
||||
context: server.renderer.context
|
||||
}
|
||||
expect(createModernMiddleware).toBeCalledTimes(1)
|
||||
expect(createModernMiddleware).toBeCalledWith(modernMiddleware)
|
||||
expect(server.useMiddleware).nthCalledWith(3, {
|
||||
id: 'test-modern-middleware',
|
||||
...modernMiddleware
|
||||
})
|
||||
|
||||
const nuxtMiddlewareOpts = {
|
||||
options: server.options,
|
||||
nuxt: server.nuxt,
|
||||
renderRoute: expect.any(Function),
|
||||
resources: server.resources
|
||||
}
|
||||
expect(nuxtMiddleware).toBeCalledTimes(1)
|
||||
expect(nuxtMiddleware).toBeCalledWith(nuxtMiddlewareOpts)
|
||||
expect(server.useMiddleware).nthCalledWith(4, {
|
||||
id: 'test-nuxt-middleware',
|
||||
...nuxtMiddlewareOpts
|
||||
})
|
||||
|
||||
const errorMiddlewareOpts = {
|
||||
resources: server.resources,
|
||||
options: server.options
|
||||
}
|
||||
expect(errorMiddleware).toBeCalledTimes(1)
|
||||
expect(errorMiddleware).toBeCalledWith(errorMiddlewareOpts)
|
||||
expect(server.useMiddleware).nthCalledWith(5, {
|
||||
id: 'test-error-middleware',
|
||||
...errorMiddlewareOpts
|
||||
})
|
||||
})
|
||||
|
||||
test('should setup compressor middleware', async () => {
|
||||
const nuxt = createNuxt()
|
||||
nuxt.options.render.compressor = jest.fn()
|
||||
const server = new Server(nuxt)
|
||||
server.useMiddleware = jest.fn()
|
||||
server.renderer = {
|
||||
context: { id: 'test-server-context' }
|
||||
}
|
||||
|
||||
await server.setupMiddleware()
|
||||
expect(server.useMiddleware).nthCalledWith(1, nuxt.options.render.compressor)
|
||||
|
||||
server.useMiddleware.mockClear()
|
||||
nuxt.options.render.compressor = { id: 'test-render-compressor' }
|
||||
nuxt.resolver.requireModule.mockImplementationOnce(name => jest.fn(options => ({
|
||||
name,
|
||||
...options
|
||||
})))
|
||||
await server.setupMiddleware()
|
||||
expect(nuxt.resolver.requireModule).nthCalledWith(1, 'compression')
|
||||
expect(server.useMiddleware).nthCalledWith(1, {
|
||||
id: 'test-render-compressor',
|
||||
name: 'compression'
|
||||
})
|
||||
})
|
||||
|
||||
test('should setup timing middleware', async () => {
|
||||
const nuxt = createNuxt()
|
||||
nuxt.options.server.timing = { id: 'test-server-timing' }
|
||||
const server = new Server(nuxt)
|
||||
server.useMiddleware = jest.fn()
|
||||
server.renderer = {
|
||||
context: { id: 'test-server-context' }
|
||||
}
|
||||
|
||||
await server.setupMiddleware()
|
||||
|
||||
expect(createTimingMiddleware).nthCalledWith(1, { id: 'test-server-timing' })
|
||||
expect(server.useMiddleware).nthCalledWith(1, { id: 'test-server-timing' })
|
||||
})
|
||||
|
||||
test('should setup dev middleware', async () => {
|
||||
const nuxt = createNuxt()
|
||||
nuxt.options.dev = true
|
||||
const server = new Server(nuxt)
|
||||
server.useMiddleware = jest.fn()
|
||||
server.renderer = {
|
||||
context: { id: 'test-server-context' }
|
||||
}
|
||||
|
||||
await server.setupMiddleware()
|
||||
|
||||
expect(server.useMiddleware).nthCalledWith(1, {
|
||||
id: 'test-modern-middleware',
|
||||
context: server.renderer.context
|
||||
})
|
||||
|
||||
const devMiddleware = server.useMiddleware.mock.calls[1][0]
|
||||
|
||||
const req = { id: 'req' }
|
||||
const res = { id: 'res' }
|
||||
const next = jest.fn()
|
||||
await devMiddleware(req, res, next)
|
||||
expect(next).toBeCalledTimes(1)
|
||||
|
||||
next.mockClear()
|
||||
server.devMiddleware = { client: jest.fn() }
|
||||
server.hotMiddleware = { client: jest.fn() }
|
||||
await devMiddleware(req, res, next)
|
||||
expect(server.devMiddleware.client).nthCalledWith(1, req, res)
|
||||
expect(server.hotMiddleware.client).nthCalledWith(1, req, res)
|
||||
expect(next).toBeCalledTimes(1)
|
||||
|
||||
next.mockClear()
|
||||
req.modernMode = true
|
||||
server.devMiddleware = { modern: jest.fn() }
|
||||
server.hotMiddleware = { modern: jest.fn() }
|
||||
await devMiddleware(req, res, next)
|
||||
expect(server.devMiddleware.modern).nthCalledWith(1, req, res)
|
||||
expect(server.hotMiddleware.modern).nthCalledWith(1, req, res)
|
||||
expect(next).toBeCalledTimes(1)
|
||||
})
|
||||
|
||||
test('should setup open-in-editor middleware', async () => {
|
||||
const nuxt = createNuxt()
|
||||
nuxt.options.dev = true
|
||||
nuxt.options.debug = true
|
||||
nuxt.options.editor = { id: 'test-editor' }
|
||||
const server = new Server(nuxt)
|
||||
server.useMiddleware = jest.fn()
|
||||
server.renderer = {
|
||||
context: { id: 'test-server-context' }
|
||||
}
|
||||
|
||||
await server.setupMiddleware()
|
||||
|
||||
expect(launchMiddleware).toBeCalledTimes(1)
|
||||
expect(launchMiddleware).toBeCalledWith({ id: 'test-editor' })
|
||||
|
||||
expect(server.useMiddleware).nthCalledWith(3, {
|
||||
handler: { id: 'test-editor' },
|
||||
path: '__open-in-editor'
|
||||
})
|
||||
})
|
||||
|
||||
test('should setup server middleware', async () => {
|
||||
const nuxt = createNuxt()
|
||||
nuxt.options.serverMiddleware = [
|
||||
{ id: 'test-server-middleware-1' },
|
||||
{ id: 'test-server-middleware-2' }
|
||||
]
|
||||
const server = new Server(nuxt)
|
||||
server.useMiddleware = jest.fn()
|
||||
server.renderer = {
|
||||
context: { id: 'test-server-context' }
|
||||
}
|
||||
|
||||
await server.setupMiddleware()
|
||||
|
||||
expect(server.useMiddleware).nthCalledWith(4, { id: 'test-server-middleware-1' })
|
||||
expect(server.useMiddleware).nthCalledWith(5, { id: 'test-server-middleware-2' })
|
||||
})
|
||||
|
||||
test('should setup fallback middleware', async () => {
|
||||
const nuxt = createNuxt()
|
||||
nuxt.options.render.fallback = {
|
||||
dist: { id: 'test-render-fallback-dist' },
|
||||
static: { id: 'test-render-fallback-static' }
|
||||
}
|
||||
const server = new Server(nuxt)
|
||||
server.useMiddleware = jest.fn()
|
||||
server.renderer = {
|
||||
context: { id: 'test-server-context' }
|
||||
}
|
||||
|
||||
await server.setupMiddleware()
|
||||
expect(servePlaceholder).toBeCalledTimes(2)
|
||||
expect(server.useMiddleware).nthCalledWith(4, {
|
||||
handler: {
|
||||
id: 'test-render-fallback-dist',
|
||||
key: 'test-serve-placeholder'
|
||||
},
|
||||
path: '__nuxt_test'
|
||||
})
|
||||
expect(server.useMiddleware).nthCalledWith(5, {
|
||||
handler: {
|
||||
id: 'test-render-fallback-static',
|
||||
key: 'test-serve-placeholder'
|
||||
},
|
||||
path: '/'
|
||||
})
|
||||
|
||||
servePlaceholder.mockClear()
|
||||
nuxt.options.render.fallback = {}
|
||||
await server.setupMiddleware()
|
||||
expect(servePlaceholder).not.toBeCalled()
|
||||
})
|
||||
|
||||
test('should use object middleware', () => {
|
||||
const nuxt = createNuxt()
|
||||
nuxt.options.router = { base: '/' }
|
||||
const server = new Server(nuxt)
|
||||
const handler = jest.fn()
|
||||
|
||||
server.useMiddleware({
|
||||
handler
|
||||
})
|
||||
|
||||
expect(nuxt.resolver.requireModule).not.toBeCalled()
|
||||
expect(server.app.use).toBeCalledTimes(1)
|
||||
expect(server.app.use).toBeCalledWith(nuxt.options.router.base, handler)
|
||||
})
|
||||
|
||||
test('should use function module middleware', () => {
|
||||
const nuxt = createNuxt()
|
||||
nuxt.options.router = { base: '/' }
|
||||
const server = new Server(nuxt)
|
||||
const handler = jest.fn()
|
||||
nuxt.resolver.requireModule.mockReturnValueOnce(handler)
|
||||
|
||||
server.useMiddleware('test-middleware')
|
||||
|
||||
expect(nuxt.resolver.requireModule).toBeCalledTimes(1)
|
||||
expect(nuxt.resolver.requireModule).toBeCalledWith('test-middleware')
|
||||
expect(server.app.use).toBeCalledTimes(1)
|
||||
expect(server.app.use).toBeCalledWith(nuxt.options.router.base, handler)
|
||||
})
|
||||
|
||||
test('should use object module middleware', () => {
|
||||
const nuxt = createNuxt()
|
||||
nuxt.options.router = { base: '/' }
|
||||
const server = new Server(nuxt)
|
||||
const handler = jest.fn()
|
||||
nuxt.resolver.requireModule.mockReturnValueOnce({
|
||||
handler,
|
||||
prefix: false,
|
||||
path: '//middleware'
|
||||
})
|
||||
|
||||
server.useMiddleware('test-middleware')
|
||||
|
||||
expect(nuxt.resolver.requireModule).toBeCalledTimes(1)
|
||||
expect(nuxt.resolver.requireModule).toBeCalledWith('test-middleware')
|
||||
expect(server.app.use).toBeCalledTimes(1)
|
||||
expect(server.app.use).toBeCalledWith('/middleware', handler)
|
||||
})
|
||||
|
||||
test('should throw error when module resolves failed', () => {
|
||||
const nuxt = createNuxt()
|
||||
nuxt.options.router = { base: '/' }
|
||||
const server = new Server(nuxt)
|
||||
const error = Error('middleware resolves failed')
|
||||
nuxt.resolver.requireModule.mockImplementationOnce(() => {
|
||||
throw error
|
||||
})
|
||||
|
||||
expect(() => server.useMiddleware('test-middleware')).toThrow(error)
|
||||
expect(consola.error).toBeCalledTimes(1)
|
||||
expect(consola.error).toBeCalledWith(error)
|
||||
})
|
||||
|
||||
test('should only log error when module resolves failed in dev mode', () => {
|
||||
const nuxt = createNuxt()
|
||||
nuxt.options.dev = true
|
||||
nuxt.options.router = { base: '/' }
|
||||
const server = new Server(nuxt)
|
||||
const error = Error('middleware resolves failed')
|
||||
nuxt.resolver.requireModule.mockImplementationOnce(() => {
|
||||
throw error
|
||||
})
|
||||
|
||||
server.useMiddleware('test-middleware')
|
||||
|
||||
expect(consola.error).toBeCalledTimes(1)
|
||||
expect(consola.error).toBeCalledWith(error)
|
||||
})
|
||||
|
||||
test('should render route via renderer', () => {
|
||||
const nuxt = createNuxt()
|
||||
const server = new Server(nuxt)
|
||||
server.renderer = { renderRoute: jest.fn() }
|
||||
|
||||
server.renderRoute('test-render-route')
|
||||
|
||||
expect(server.renderer.renderRoute).toBeCalledTimes(1)
|
||||
expect(server.renderer.renderRoute).toBeCalledWith('test-render-route')
|
||||
})
|
||||
|
||||
test('should load resources via renderer', () => {
|
||||
const nuxt = createNuxt()
|
||||
const server = new Server(nuxt)
|
||||
server.renderer = { loadResources: jest.fn() }
|
||||
|
||||
server.loadResources('test-load-resources')
|
||||
|
||||
expect(server.renderer.loadResources).toBeCalledTimes(1)
|
||||
expect(server.renderer.loadResources).toBeCalledWith('test-load-resources')
|
||||
})
|
||||
|
||||
test('should render and get window', () => {
|
||||
const nuxt = createNuxt()
|
||||
const globals = {
|
||||
...nuxt.options.globals,
|
||||
name: nuxt.options.globalName,
|
||||
loadedCallback: jest.fn()
|
||||
}
|
||||
determineGlobals.mockReturnValueOnce(globals)
|
||||
const server = new Server(nuxt)
|
||||
|
||||
server.renderAndGetWindow('/render/window')
|
||||
|
||||
expect(renderAndGetWindow).toBeCalledTimes(1)
|
||||
expect(renderAndGetWindow).toBeCalledWith('/render/window', {}, {
|
||||
loadedCallback: globals.loadedCallback,
|
||||
ssr: nuxt.options.render.ssr,
|
||||
globals: globals
|
||||
})
|
||||
})
|
||||
|
||||
test('should listen server', async () => {
|
||||
const nuxt = createNuxt()
|
||||
const server = new Server(nuxt)
|
||||
const listener = {
|
||||
listen: jest.fn(),
|
||||
server: jest.fn()
|
||||
}
|
||||
Listener.mockImplementationOnce(() => {
|
||||
return listener
|
||||
})
|
||||
|
||||
await server.listen(3000, 'localhost', '/var/nuxt/unix.socket')
|
||||
|
||||
expect(Listener).toBeCalledWith({
|
||||
port: 3000,
|
||||
host: 'localhost',
|
||||
socket: '/var/nuxt/unix.socket',
|
||||
https: undefined,
|
||||
app: server.app,
|
||||
dev: server.options.dev
|
||||
})
|
||||
expect(listener.listen).toBeCalledTimes(1)
|
||||
expect(server.listeners).toEqual([ listener ])
|
||||
expect(server.nuxt.callHook).toBeCalledTimes(1)
|
||||
expect(server.nuxt.callHook).toBeCalledWith('listen', listener.server, listener)
|
||||
})
|
||||
|
||||
test('should listen server via options.server', async () => {
|
||||
const nuxt = createNuxt()
|
||||
nuxt.options.server = {
|
||||
host: 'localhost',
|
||||
port: '3000',
|
||||
socket: '/var/nuxt/unix.socket',
|
||||
https: true
|
||||
}
|
||||
const server = new Server(nuxt)
|
||||
|
||||
await server.listen()
|
||||
|
||||
expect(Listener).toBeCalledWith({
|
||||
...nuxt.options.server,
|
||||
app: server.app,
|
||||
dev: server.options.dev
|
||||
})
|
||||
})
|
||||
|
||||
test('should close server', async () => {
|
||||
const removeAllListeners = jest.fn()
|
||||
connect.mockReturnValueOnce({ use: jest.fn(), removeAllListeners })
|
||||
const nuxt = createNuxt()
|
||||
const server = new Server(nuxt)
|
||||
const listener = { close: jest.fn() }
|
||||
server.listeners = [ listener ]
|
||||
server.renderer = { close: jest.fn() }
|
||||
server.resources = { id: 'test-resources' }
|
||||
|
||||
await server.close()
|
||||
|
||||
expect(server.__closed).toEqual(true)
|
||||
expect(listener.close).toBeCalledTimes(1)
|
||||
expect(server.listeners).toEqual([])
|
||||
expect(server.renderer.close).toBeCalledTimes(1)
|
||||
expect(removeAllListeners).toBeCalledTimes(1)
|
||||
expect(server.app).toBeNull()
|
||||
expect(server.resources).toEqual({})
|
||||
})
|
||||
|
||||
test('should prevent closing server multiple times', async () => {
|
||||
const removeAllListeners = jest.fn()
|
||||
connect.mockReturnValueOnce({ use: jest.fn(), removeAllListeners })
|
||||
const nuxt = createNuxt()
|
||||
const server = new Server(nuxt)
|
||||
server.renderer = {}
|
||||
|
||||
await server.close()
|
||||
|
||||
expect(server.__closed).toEqual(true)
|
||||
expect(removeAllListeners).toBeCalledTimes(1)
|
||||
|
||||
removeAllListeners.mockClear()
|
||||
|
||||
await server.close()
|
||||
|
||||
expect(server.__closed).toEqual(true)
|
||||
expect(removeAllListeners).not.toBeCalled()
|
||||
})
|
||||
})
|
Loading…
Reference in New Issue
Block a user