mirror of
https://github.com/cypress-io/cypress.git
synced 2026-01-26 17:09:11 -06:00
* 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>
480 lines
13 KiB
JavaScript
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,
|
|
})
|
|
})
|
|
})
|
|
})
|