mirror of
https://github.com/cypress-io/cypress.git
synced 2026-02-08 16:19:55 -06:00
* remove experimentalSkipDomainInjection, add and deprecate injectDocumentDomain * remove experimentalSkipDomainInjection, add injectDocumentDomain * begin rethreading domain injection * complete document domain transition * move some cookie specs to separate test run * origin and privileged commands with default docdom inject * fix privileged channel when injecting document domain * rm unnecessary .getOrigin abstraction in cors lib * move remote-states in prep for refactor Replace Conditional with Polymorphism * refactor remote states to strategy pattern * cookie commands work as expected w cross origin bridge on different origins * some origin tests updated * run tests with document domain enabled * run tests actually * use correct config, swap conditional * check-ts * inject documetn domain for webkit tests * do not exec injectDocumetnDomain in parallel * fix ServerBase construction in tests to include cfg now * pass cfg to ServerBase * improved integration tests * remove document domain checks for all server integration specs - will add injectDocumentDomain cases * tests for injecting document domain when configured to * square away server integration tests * ensure cookies are set correctly, potentially * errors pkg snapshots * fix config tests * fixing config tests * somewhat improves tests for cors policies in packages/network * fix ts err in server-base * enable injectDocumentDomain for cy in cy tests * fix Policy type ref * refactor cypress-schematic ct spec to be less prone to timeouts * run vite-dev-server tests with injectDocumentDomain * rm document domain assertion from page_loading system test * add system tests that test with injectDocumentDomain and others that test with cy.origin * fix results_spec snapshot * update experimentalSkipDomainInjection system test * different behavior for certain net_stubbing tests based on injectDocumentDomain or not * fix ts * extract origin key logic from remote states, for now * move server-base and response-middleware over to new pattern * WIP - reentry * fix build, remove console.log * check-ts * fix spec frame injection * remove injection for localhost * mostly fix vite-dev-server app integration tests * fix codeframe in certain cases in chrome * drop internal stack frames from stacks intended for determining code frame data * some improvements to vite ct error codeframes * fix proxy unit tests to use document domain injection util class * rm .only * fix all vite ct error specs * rm console.log * slight refactor to util class to make easier to test * fix refactor - missing rename in files.js * several tests do not set testingtype in config, so just check against component instead of checking for e2e * revert changes to getInvocationDetails to see if that breaks tests * re-enable stack stripping in invocation details for chrome * new snapshots with more accurate invocation details * test for same-site cross-origin cookie behavior * ignore window.top ts errors * revert forcing injectDocumentDomain in vite-dev-server cy config * fix normalized whitespace for firefox "loading_failed" error * always trim trailing wsp from stack before appending additional content * force normalization of whitespace to three \n when adding additional stack details * normalize wsp between stack and additional stack to "\n \n" in firefox * remove stack_utils attempt at normalizing wsp * various cleanup: remove commented console logs, add more detailed comments * add on links to error messages * remove experimentalSkipDomainInjection from exported type defs * Update system-tests/test/experimental_skip_domain_injection_spec.ts Co-authored-by: Bill Glesias <bglesias@gmail.com> * Update packages/driver/cypress/e2e/e2e/origin/cookie_misc.cy.ts Co-authored-by: Bill Glesias <bglesias@gmail.com> * no need to coerce a boolean value to a booleanc * export base config from primary cypress config in driver for use in inject-document-domain test subset * lift experimentalSkipDomainInjection breaking option to root * rollback config/options changes * rm invalid comment * use hostname instead of origin to create cookie from automation cookie * clarify stack regex in results_spec * lint * take a stab at the changelog entries for this * Update cli/CHANGELOG.md Co-authored-by: Ryan Manuel <ryanm@cypress.io> * Update cli/CHANGELOG.md Co-authored-by: Ryan Manuel <ryanm@cypress.io> * reenable locally-failing test * changelog * snapshot updatesfor experimental skip domain injection err msg * remove packageManager declaration in package.json --------- Co-authored-by: Bill Glesias <bglesias@gmail.com> Co-authored-by: Jennifer Shehane <jennifer@cypress.io> Co-authored-by: Ryan Manuel <ryanm@cypress.io>
400 lines
11 KiB
JavaScript
400 lines
11 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 { setupFullConfigWithDefaults } = require('@packages/config')
|
|
const { ServerBase } = require(`../../lib/server-base`)
|
|
const { SocketE2E } = require(`../../lib/socket-e2e`)
|
|
const fileServer = require(`../../lib/file_server`)
|
|
const ensureUrl = require(`../../lib/util/ensure-url`)
|
|
const { getCtx } = require('@packages/data-context')
|
|
|
|
const morganFn = function () {}
|
|
|
|
mockery.registerMock('morgan', () => {
|
|
return morganFn
|
|
})
|
|
|
|
describe('lib/server', () => {
|
|
beforeEach(function () {
|
|
return setupFullConfigWithDefaults({ projectRoot: '/foo/bar/', config: { supportFile: false } }, getCtx().file.getFilesByGlob)
|
|
.then((cfg) => {
|
|
this.config = cfg
|
|
this.server = new ServerBase(cfg)
|
|
})
|
|
})
|
|
|
|
context('#close', () => {
|
|
it('resolves true successfully bailing out early', function () {
|
|
return this.server.close().then((res) => {
|
|
expect(res[0]).to.be.true
|
|
})
|
|
})
|
|
})
|
|
})
|
|
|
|
// TODO: Figure out correct configuration to run these tests and/or which ones we need to keep.
|
|
// The introduction of server-base/socket-base and the `ensureProp` function made unit testing
|
|
// the server difficult.
|
|
describe.skip('lib/server', () => {
|
|
beforeEach(function () {
|
|
this.fileServer = {
|
|
close () {},
|
|
port () {
|
|
return 1111
|
|
},
|
|
}
|
|
|
|
sinon.stub(fileServer, 'create').returns(this.fileServer)
|
|
|
|
return setupFullConfigWithDefaults({ projectRoot: '/foo/bar/' }, getCtx().file.getFilesByGlob)
|
|
.then((cfg) => {
|
|
this.config = cfg
|
|
this.server = new ServerBase(cfg)
|
|
|
|
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)
|
|
})
|
|
})
|
|
})
|
|
|
|
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(SocketE2E.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('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.remoteStates.set('https://www.google.com')
|
|
|
|
const req = {
|
|
connection: {
|
|
encrypted: true,
|
|
},
|
|
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
|
|
})
|
|
})
|
|
})
|