mirror of
https://github.com/cypress-io/cypress.git
synced 2026-02-26 19:09:32 -06:00
* fix race condition when there's an async error in root of plugins file * return the promise * fix routes creation * fix tests * fix error throwing and add tests * update snapshots * revert changes to server.open signatures in tests * fix test * properly wrap error so it doesn't log twice * slow down test to ensure plugins error occurs before run is over * wait to log early exit error until after run start
386 lines
10 KiB
CoffeeScript
386 lines
10 KiB
CoffeeScript
require("../spec_helper")
|
|
|
|
_ = require("lodash")
|
|
os = require("os")
|
|
http = require("http")
|
|
express = require("express")
|
|
Promise = require("bluebird")
|
|
connect = require("@packages/network").connect
|
|
routes = require("#{root}lib/routes")
|
|
config = require("#{root}lib/config")
|
|
logger = require("#{root}lib/logger")
|
|
Server = require("#{root}lib/server")
|
|
Socket = require("#{root}lib/socket")
|
|
fileServer = require("#{root}lib/file_server")
|
|
ensureUrl = require("#{root}lib/util/ensure-url")
|
|
|
|
morganFn = ->
|
|
mockery.registerMock("morgan", -> morganFn)
|
|
|
|
describe "lib/server", ->
|
|
beforeEach ->
|
|
@fileServer = {
|
|
close: ->
|
|
port: -> 1111
|
|
}
|
|
sinon.stub(fileServer, "create").returns(@fileServer)
|
|
|
|
config.set({projectRoot: "/foo/bar/"})
|
|
.then (cfg) =>
|
|
@config = cfg
|
|
@server = Server()
|
|
|
|
@oldFileServer = @server._fileServer
|
|
@server._fileServer = @fileServer
|
|
|
|
afterEach ->
|
|
@server and @server.close()
|
|
|
|
context "#createExpressApp", ->
|
|
beforeEach ->
|
|
@use = sinon.spy(express.application, "use")
|
|
|
|
it "instantiates express instance without morgan", ->
|
|
app = @server.createExpressApp({ morgan: false })
|
|
expect(app.get("view engine")).to.eq("html")
|
|
expect(@use).not.to.be.calledWith(morganFn)
|
|
|
|
it "requires morgan if true", ->
|
|
@server.createExpressApp({ morgan: true })
|
|
expect(@use).to.be.calledWith(morganFn)
|
|
|
|
context "#open", ->
|
|
beforeEach ->
|
|
sinon.stub(@server, "createServer").resolves()
|
|
|
|
it "calls #createExpressApp with morgan", ->
|
|
sinon.spy(@server, "createExpressApp")
|
|
|
|
_.extend @config, {port: 54321, morgan: false}
|
|
|
|
@server.open(@config)
|
|
.then =>
|
|
expect(@server.createExpressApp).to.be.calledWithMatch({ morgan: false })
|
|
|
|
it "calls #createServer with port", ->
|
|
_.extend @config, {port: 54321}
|
|
|
|
obj = {}
|
|
|
|
sinon.stub(@server, "createRoutes")
|
|
sinon.stub(@server, "createExpressApp").returns(obj)
|
|
|
|
@server.open(@config)
|
|
.then =>
|
|
expect(@server.createServer).to.be.calledWith(obj, @config)
|
|
|
|
it "calls #createRoutes with app + config", ->
|
|
app = {}
|
|
project = {}
|
|
onError = sinon.spy()
|
|
sinon.stub(@server, "createRoutes")
|
|
sinon.stub(@server, "createExpressApp").returns(app)
|
|
|
|
@server.open(@config, project, onError)
|
|
.then =>
|
|
expect(@server.createRoutes).to.be.called
|
|
expect(@server.createRoutes.lastCall.args[0].app).to.equal(app)
|
|
expect(@server.createRoutes.lastCall.args[0].config).to.equal(@config)
|
|
expect(@server.createRoutes.lastCall.args[0].project).to.equal(project)
|
|
expect(@server.createRoutes.lastCall.args[0].onError).to.equal(onError)
|
|
|
|
it "calls #createServer with port + fileServerFolder + socketIoRoute + app", ->
|
|
obj = {}
|
|
|
|
sinon.stub(@server, "createRoutes")
|
|
sinon.stub(@server, "createExpressApp").returns(obj)
|
|
|
|
@server.open(@config)
|
|
.then =>
|
|
expect(@server.createServer).to.be.calledWith(obj, @config)
|
|
|
|
it "calls logger.setSettings with config", ->
|
|
sinon.spy(logger, "setSettings")
|
|
|
|
@server.open(@config)
|
|
.then (ret) =>
|
|
expect(logger.setSettings).to.be.calledWith(@config)
|
|
|
|
context "#createServer", ->
|
|
beforeEach ->
|
|
@port = 54321
|
|
@app = @server.createExpressApp({ morgan: true })
|
|
|
|
it "isListening=true", ->
|
|
@server.createServer(@app, {port: @port})
|
|
.then =>
|
|
expect(@server.isListening).to.be.true
|
|
|
|
it "resolves with http server port", ->
|
|
@server.createServer(@app, {port: @port})
|
|
.spread (port) =>
|
|
expect(port).to.eq(@port)
|
|
|
|
it "all servers listen only on localhost and no other interface", ->
|
|
fileServer.create.restore()
|
|
@server._fileServer = @oldFileServer
|
|
|
|
interfaces = _.flatten(_.values(os.networkInterfaces()))
|
|
nonLoopback = interfaces.find (iface) =>
|
|
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
|
|
tryOnlyLoopbackConnect = (port) =>
|
|
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" }, ->
|
|
])
|
|
|
|
@server.createServer(@app, {})
|
|
.spread (port) =>
|
|
Promise.map(
|
|
[
|
|
port
|
|
@server._fileServer.port()
|
|
@server._httpsProxy._sniPort
|
|
],
|
|
tryOnlyLoopbackConnect
|
|
)
|
|
|
|
it "resolves with warning if cannot connect to baseUrl", ->
|
|
sinon.stub(ensureUrl, "isListening").rejects()
|
|
@server.createServer(@app, {port: @port, baseUrl: "http://localhost:#{@port}"})
|
|
.spread (port, warning) =>
|
|
expect(warning.type).to.eq("CANNOT_CONNECT_BASE_URL_WARNING")
|
|
expect(warning.message).to.include(@port)
|
|
|
|
context "errors", ->
|
|
it "rejects with portInUse", ->
|
|
@server.createServer(@app, {port: @port})
|
|
.then =>
|
|
@server.createServer(@app, {port: @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(@port)
|
|
|
|
context "#end", ->
|
|
it "calls this._socket.end", ->
|
|
socket = sinon.stub({
|
|
end: ->
|
|
close: ->
|
|
})
|
|
|
|
@server._socket = socket
|
|
|
|
@server.end()
|
|
expect(socket.end).to.be.called
|
|
|
|
it "is noop without this._socket", ->
|
|
@server.end()
|
|
|
|
context "#startWebsockets", ->
|
|
beforeEach ->
|
|
@startListening = sinon.stub(Socket.prototype, "startListening")
|
|
|
|
it "sets _socket and calls _socket#startListening", ->
|
|
@server.open(@config)
|
|
.then =>
|
|
@server.startWebsockets(1, 2, 3)
|
|
|
|
expect(@startListening).to.be.calledWith(@server.getHttpServer(), 1, 2, 3)
|
|
|
|
context "#reset", ->
|
|
beforeEach ->
|
|
@server.open(@config)
|
|
.then =>
|
|
@buffers = @server._networkProxy.http
|
|
sinon.stub(@buffers, "reset")
|
|
|
|
it "resets the buffers", ->
|
|
@server.reset()
|
|
expect(@buffers.reset).to.be.called
|
|
|
|
it "sets the domain to the previous base url if set", ->
|
|
@server._baseUrl = "http://localhost:3000"
|
|
@server.reset()
|
|
expect(@server._remoteStrategy).to.equal("http")
|
|
|
|
it "sets the domain to <root> if not set", ->
|
|
@server.reset()
|
|
expect(@server._remoteStrategy).to.equal("file")
|
|
|
|
context "#close", ->
|
|
it "returns a promise", ->
|
|
expect(@server.close()).to.be.instanceof Promise
|
|
|
|
it "calls close on this.server", ->
|
|
@server.open(@config)
|
|
.then =>
|
|
@server.close()
|
|
|
|
it "isListening=false", ->
|
|
@server.open(@config)
|
|
.then =>
|
|
@server.close()
|
|
.then =>
|
|
expect(@server.isListening).to.be.false
|
|
|
|
it "clears settings from Log", ->
|
|
logger.setSettings({})
|
|
|
|
@server.close()
|
|
.then ->
|
|
expect(logger.getSettings()).to.be.undefined
|
|
|
|
it "calls close on this._socket", ->
|
|
@server._socket = {close: sinon.spy()}
|
|
|
|
@server.close()
|
|
.then =>
|
|
expect(@server._socket.close).to.be.calledOnce
|
|
|
|
context "#proxyWebsockets", ->
|
|
beforeEach ->
|
|
@proxy = sinon.stub({
|
|
ws: ->
|
|
on: ->
|
|
})
|
|
@socket = sinon.stub({end: ->})
|
|
@head = {}
|
|
|
|
it "is noop if req.url startsWith socketIoRoute", ->
|
|
socket = {
|
|
remotePort: 12345
|
|
remoteAddress: '127.0.0.1'
|
|
}
|
|
|
|
@server._socketWhitelist.add({
|
|
localPort: socket.remotePort,
|
|
once: _.noop
|
|
})
|
|
|
|
noop = @server.proxyWebsockets(@proxy, "/foo", {
|
|
url: "/foobarbaz",
|
|
socket
|
|
})
|
|
|
|
expect(noop).to.be.undefined
|
|
|
|
it "calls proxy.ws with hostname + port", ->
|
|
@server._onDomainSet("https://www.google.com")
|
|
|
|
req = {
|
|
url: "/"
|
|
headers: {
|
|
host: "www.google.com"
|
|
}
|
|
}
|
|
|
|
@server.proxyWebsockets(@proxy, "/foo", req, @socket, @head)
|
|
|
|
expect(@proxy.ws).to.be.calledWithMatch(req, @socket, @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", ->
|
|
req = {
|
|
url: "/"
|
|
headers: {
|
|
cookie: "foo=bar"
|
|
}
|
|
}
|
|
|
|
@server.proxyWebsockets(@proxy, "/foo", req, @socket, @head)
|
|
expect(@socket.end).not.to.be.called
|
|
|
|
@socket.writable = true
|
|
@server.proxyWebsockets(@proxy, "/foo", req, @socket, @head)
|
|
expect(@socket.end).to.be.called
|
|
|
|
context "#_onDomainSet", ->
|
|
beforeEach ->
|
|
@server = Server()
|
|
|
|
it "sets port to 443 when omitted and https:", ->
|
|
ret = @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:", ->
|
|
ret = @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", ->
|
|
ret = @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", ->
|
|
@server._server = {
|
|
address: -> {port: 9999}
|
|
}
|
|
|
|
@server._fileServer = {
|
|
port: -> 9998
|
|
}
|
|
|
|
ret = @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
|
|
})
|