Files
cypress/packages/server/test/unit/server_spec.js
Jennifer Shehane 639df99036 Rename uses of term 'whitelist' (#7782)
* Rename non-user facing instances of whitelist

* Rename server option 'whitelist' to 'ignore'

* Update use of whitelist with server to throw instead of warn

* Rename Cypress.Cookies.defaults 'whitelist' option to 'preserve'

* fix circle yml parameter parsing consistent

* compose cloning an external repo and switching to the NEXT_DEV_VERSION branch consistently

* add cypress org to repo parameter

* cd into the right dir before switching branches

* one line git checkout

* simplify passing repo

* cd into the right dir

* clone into the right dir

* oh my cd 101

* replace kitchen sink strings for 5.0.0

Co-authored-by: Brian Mann <brian.mann86@gmail.com>
2020-06-30 02:27:29 -04:00

480 lines
13 KiB
JavaScript

require('../spec_helper')
const _ = require('lodash')
const os = require('os')
const express = require('express')
const Promise = require('bluebird')
const { connect } = require('@packages/network')
const config = require(`${root}lib/config`)
const logger = require(`${root}lib/logger`)
const Server = require(`${root}lib/server`)
const Socket = require(`${root}lib/socket`)
const fileServer = require(`${root}lib/file_server`)
const ensureUrl = require(`${root}lib/util/ensure-url`)
const morganFn = function () {}
mockery.registerMock('morgan', () => {
return morganFn
})
describe('lib/server', () => {
beforeEach(function () {
this.fileServer = {
close () {},
port () {
return 1111
},
}
sinon.stub(fileServer, 'create').returns(this.fileServer)
return config.set({ projectRoot: '/foo/bar/' })
.then((cfg) => {
this.config = cfg
this.server = new Server()
this.oldFileServer = this.server._fileServer
this.server._fileServer = this.fileServer
})
})
afterEach(function () {
return this.server && this.server.close()
})
context('#createExpressApp', () => {
beforeEach(function () {
this.use = sinon.spy(express.application, 'use')
})
it('instantiates express instance without morgan', function () {
const app = this.server.createExpressApp({ morgan: false })
expect(app.get('view engine')).to.eq('html')
expect(this.use).not.to.be.calledWith(morganFn)
})
it('requires morgan if true', function () {
this.server.createExpressApp({ morgan: true })
expect(this.use).to.be.calledWith(morganFn)
})
})
context('#open', () => {
beforeEach(function () {
return sinon.stub(this.server, 'createServer').resolves()
})
it('calls #createExpressApp with morgan', function () {
sinon.spy(this.server, 'createExpressApp')
_.extend(this.config, { port: 54321, morgan: false })
return this.server.open(this.config)
.then(() => {
expect(this.server.createExpressApp).to.be.calledWithMatch({ morgan: false })
})
})
it('calls #createServer with port', function () {
_.extend(this.config, { port: 54321 })
const obj = {}
sinon.stub(this.server, 'createRoutes')
sinon.stub(this.server, 'createExpressApp').returns(obj)
return this.server.open(this.config)
.then(() => {
expect(this.server.createServer).to.be.calledWith(obj, this.config)
})
})
it('calls #createRoutes with app + config', function () {
const app = {}
const project = {}
const onError = sinon.spy()
sinon.stub(this.server, 'createRoutes')
sinon.stub(this.server, 'createExpressApp').returns(app)
return this.server.open(this.config, project, onError)
.then(() => {
expect(this.server.createRoutes).to.be.called
expect(this.server.createRoutes.lastCall.args[0].app).to.equal(app)
expect(this.server.createRoutes.lastCall.args[0].config).to.equal(this.config)
expect(this.server.createRoutes.lastCall.args[0].project).to.equal(project)
expect(this.server.createRoutes.lastCall.args[0].onError).to.equal(onError)
})
})
it('calls #createServer with port + fileServerFolder + socketIoRoute + app', function () {
const obj = {}
sinon.stub(this.server, 'createRoutes')
sinon.stub(this.server, 'createExpressApp').returns(obj)
return this.server.open(this.config)
.then(() => {
expect(this.server.createServer).to.be.calledWith(obj, this.config)
})
})
it('calls logger.setSettings with config', function () {
sinon.spy(logger, 'setSettings')
return this.server.open(this.config)
.then((ret) => {
expect(logger.setSettings).to.be.calledWith(this.config)
})
})
})
context('#createServer', () => {
beforeEach(function () {
this.port = 54321
this.app = this.server.createExpressApp({ morgan: true })
})
it('isListening=true', function () {
return this.server.createServer(this.app, { port: this.port })
.then(() => {
expect(this.server.isListening).to.be.true
})
})
it('resolves with http server port', function () {
return this.server.createServer(this.app, { port: this.port })
.spread((port) => {
expect(port).to.eq(this.port)
})
})
it('all servers listen only on localhost and no other interface', function () {
fileServer.create.restore()
this.server._fileServer = this.oldFileServer
const interfaces = _.flatten(_.values(os.networkInterfaces()))
const nonLoopback = interfaces.find((iface) => {
return (iface.family === 'IPv4') && (iface.address !== '127.0.0.1')
})
// verify that we can connect to `port` over loopback
// and not over another configured IPv4 address
const tryOnlyLoopbackConnect = (port) => {
return Promise.all([
connect.byPortAndAddress(port, '127.0.0.1'),
connect.byPortAndAddress(port, nonLoopback)
.then(() => {
throw new Error(`Shouldn't be able to connect on ${nonLoopback.address}:${port}`)
}).catch({ errno: 'ECONNREFUSED' }, () => {}),
])
}
return this.server.createServer(this.app, {})
.spread((port) => {
return Promise.map(
[
port,
this.server._fileServer.port(),
this.server._httpsProxy._sniPort,
],
tryOnlyLoopbackConnect,
)
})
})
it('resolves with warning if cannot connect to baseUrl', function () {
sinon.stub(ensureUrl, 'isListening').rejects()
return this.server.createServer(this.app, { port: this.port, baseUrl: `http://localhost:${this.port}` })
.spread((port, warning) => {
expect(warning.type).to.eq('CANNOT_CONNECT_BASE_URL_WARNING')
expect(warning.message).to.include(this.port)
})
})
context('errors', () => {
it('rejects with portInUse', function () {
return this.server.createServer(this.app, { port: this.port })
.then(() => {
return this.server.createServer(this.app, { port: this.port })
}).then(() => {
throw new Error('should have failed but didn\'t')
}).catch((err) => {
expect(err.type).to.eq('PORT_IN_USE_SHORT')
expect(err.message).to.include(this.port)
})
})
})
})
context('#end', () => {
it('calls this._socket.end', function () {
const socket = sinon.stub({
end () {},
close () {},
})
this.server._socket = socket
this.server.end()
expect(socket.end).to.be.called
})
it('is noop without this._socket', function () {
return this.server.end()
})
})
context('#startWebsockets', () => {
beforeEach(function () {
this.startListening = sinon.stub(Socket.prototype, 'startListening')
})
it('sets _socket and calls _socket#startListening', function () {
return this.server.open(this.config)
.then(() => {
const arg2 = {}
this.server.startWebsockets(1, 2, arg2)
expect(this.startListening).to.be.calledWith(this.server.getHttpServer(), 1, 2, arg2)
})
})
})
context('#reset', () => {
beforeEach(function () {
return this.server.open(this.config)
.then(() => {
this.buffers = this.server._networkProxy.http
return sinon.stub(this.buffers, 'reset')
})
})
it('resets the buffers', function () {
this.server.reset()
expect(this.buffers.reset).to.be.called
})
it('sets the domain to the previous base url if set', function () {
this.server._baseUrl = 'http://localhost:3000'
this.server.reset()
expect(this.server._remoteStrategy).to.equal('http')
})
it('sets the domain to <root> if not set', function () {
this.server.reset()
expect(this.server._remoteStrategy).to.equal('file')
})
})
context('#close', () => {
it('returns a promise', function () {
expect(this.server.close()).to.be.instanceof(Promise)
})
it('calls close on this.server', function () {
return this.server.open(this.config)
.then(() => {
return this.server.close()
})
})
it('isListening=false', function () {
return this.server.open(this.config)
.then(() => {
return this.server.close()
}).then(() => {
expect(this.server.isListening).to.be.false
})
})
it('clears settings from Log', function () {
logger.setSettings({})
return this.server.close()
.then(() => {
expect(logger.getSettings()).to.be.undefined
})
})
it('calls close on this._socket', function () {
this.server._socket = { close: sinon.spy() }
return this.server.close()
.then(() => {
expect(this.server._socket.close).to.be.calledOnce
})
})
})
context('#proxyWebsockets', () => {
beforeEach(function () {
this.proxy = sinon.stub({
ws () {},
on () {},
})
this.socket = sinon.stub({ end () {} })
this.head = {}
})
it('is noop if req.url startsWith socketIoRoute', function () {
const socket = {
remotePort: 12345,
remoteAddress: '127.0.0.1',
}
this.server._socketAllowed.add({
localPort: socket.remotePort,
once: _.noop,
})
const noop = this.server.proxyWebsockets(this.proxy, '/foo', {
url: '/foobarbaz',
socket,
})
expect(noop).to.be.undefined
})
it('calls proxy.ws with hostname + port', function () {
this.server._onDomainSet('https://www.google.com')
const req = {
url: '/',
headers: {
host: 'www.google.com',
},
}
this.server.proxyWebsockets(this.proxy, '/foo', req, this.socket, this.head)
expect(this.proxy.ws).to.be.calledWithMatch(req, this.socket, this.head, {
secure: false,
target: {
host: 'www.google.com',
port: '443',
protocol: 'https:',
},
})
})
it('ends the socket if its writable and there is no __cypress.remoteHost', function () {
const req = {
url: '/',
headers: {
cookie: 'foo=bar',
},
}
this.server.proxyWebsockets(this.proxy, '/foo', req, this.socket, this.head)
expect(this.socket.end).not.to.be.called
this.socket.writable = true
this.server.proxyWebsockets(this.proxy, '/foo', req, this.socket, this.head)
expect(this.socket.end).to.be.called
})
})
context('#_onDomainSet', () => {
beforeEach(function () {
this.server = new Server()
})
it('sets port to 443 when omitted and https:', function () {
const ret = this.server._onDomainSet('https://staging.google.com/foo/bar')
expect(ret).to.deep.eq({
auth: undefined,
origin: 'https://staging.google.com',
strategy: 'http',
domainName: 'google.com',
visiting: undefined,
fileServer: null,
props: {
port: '443',
domain: 'google',
tld: 'com',
},
})
})
it('sets port to 80 when omitted and http:', function () {
const ret = this.server._onDomainSet('http://staging.google.com/foo/bar')
expect(ret).to.deep.eq({
auth: undefined,
origin: 'http://staging.google.com',
strategy: 'http',
domainName: 'google.com',
visiting: undefined,
fileServer: null,
props: {
port: '80',
domain: 'google',
tld: 'com',
},
})
})
it('sets host + port to localhost', function () {
const ret = this.server._onDomainSet('http://localhost:4200/a/b?q=1#asdf')
expect(ret).to.deep.eq({
auth: undefined,
origin: 'http://localhost:4200',
strategy: 'http',
domainName: 'localhost',
visiting: undefined,
fileServer: null,
props: {
port: '4200',
domain: '',
tld: 'localhost',
},
})
})
it('sets <root> when not http url', function () {
this.server._server = {
address () {
return { port: 9999 }
},
}
this.server._fileServer = {
port () {
return 9998
},
}
const ret = this.server._onDomainSet('/index.html')
expect(ret).to.deep.eq({
auth: undefined,
origin: 'http://localhost:9999',
strategy: 'file',
domainName: 'localhost',
fileServer: 'http://localhost:9998',
props: null,
visiting: undefined,
})
})
})
})