test: unit tests for server module (#5154)

This commit is contained in:
Xin Du (Clark) 2019-03-04 20:12:33 +00:00 committed by GitHub
parent e3991aacdf
commit cc573a4925
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 1875 additions and 14 deletions

View File

@ -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: [

View File

@ -11,6 +11,10 @@ describe('build', () => {
jest.spyOn(utils, 'createLock').mockImplementation(() => () => {})
})
afterAll(() => {
process.exit.mockRestore()
})
afterEach(() => jest.resetAllMocks())
test('has run function', () => {

View File

@ -11,6 +11,10 @@ describe('generate', () => {
jest.spyOn(utils, 'createLock').mockImplementation(() => () => {})
})
afterAll(() => {
process.exit.mockRestore()
})
afterEach(() => jest.resetAllMocks())
test('has run function', () => {

View File

@ -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

View File

@ -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
}

View File

@ -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
}

View 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)
})
})

View 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)
})
})

View 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)
})
})

View 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()
})
})

View 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)
})
})

View 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)
})
})

View 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()
})
})

View 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()
})
})