mirror of
https://github.com/cypress-io/cypress.git
synced 2026-05-08 07:50:24 -05:00
chore: refactor @packages/https-proxy to TypeScript and use vitest instead of mocha (#32718)
* chore: convert @packages/https-proxy to TypeScript and use vitest instead of mocha * chore: address comments from code review * Update packages/https-proxy/vitest.config.ts
This commit is contained in:
@@ -1,14 +0,0 @@
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const sslRootCas = require('ssl-root-cas')
|
||||
|
||||
sslRootCas
|
||||
.inject()
|
||||
.addFile(path.join(__dirname, 'certs', 'server', 'my-root-ca.crt.pem'))
|
||||
|
||||
const options = {
|
||||
key: fs.readFileSync(path.join(__dirname, 'certs', 'server', 'my-server.key.pem')),
|
||||
cert: fs.readFileSync(path.join(__dirname, 'certs', 'server', 'my-server.crt.pem')),
|
||||
}
|
||||
|
||||
module.exports = options
|
||||
@@ -0,0 +1,7 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
export const options = {
|
||||
key: fs.readFileSync(path.join(__dirname, 'certs', 'server', 'my-server.key.pem')),
|
||||
cert: fs.readFileSync(path.join(__dirname, 'certs', 'server', 'my-server.crt.pem')),
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import { CA } from '../../../lib/ca'
|
||||
|
||||
CA.create(process.env.CA_PATH)
|
||||
@@ -15,7 +15,7 @@ CA_PATH=../../../ca
|
||||
rm -rf $CA_PATH
|
||||
|
||||
# ensure regular root CA exists
|
||||
node -e "require('@packages/https-proxy/lib/ca').create('$CA_PATH')"
|
||||
CA_PATH=$CA_PATH tsx create-certs.ts
|
||||
|
||||
echo "remove and relink test CA pems"
|
||||
for f in ca client server
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
/* eslint-disable
|
||||
no-console,
|
||||
*/
|
||||
const http = require('http')
|
||||
const Promise = require('bluebird')
|
||||
|
||||
const srv = http.createServer((req, res) => {
|
||||
console.log('HTTP SERVER REQUEST URL:', req.url)
|
||||
console.log('HTTP SERVER REQUEST HEADERS:', req.headers)
|
||||
|
||||
res.setHeader('Content-Type', 'text/html')
|
||||
res.writeHead(200)
|
||||
|
||||
res.end('<html><body>http server</body></html>')
|
||||
})
|
||||
|
||||
module.exports = {
|
||||
srv,
|
||||
|
||||
start () {
|
||||
return new Promise((resolve) => {
|
||||
srv.listen(8080, () => {
|
||||
console.log('server listening on port: 8080')
|
||||
|
||||
resolve(srv)
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
stop () {
|
||||
return new Promise((resolve) => {
|
||||
srv.close(resolve)
|
||||
})
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/* eslint-disable
|
||||
no-console,
|
||||
*/
|
||||
import http from 'http'
|
||||
import type { IncomingMessage, ServerResponse } from 'http'
|
||||
|
||||
export const srv = http.createServer((req: IncomingMessage, res: ServerResponse) => {
|
||||
console.log('HTTP SERVER REQUEST URL:', req.url)
|
||||
console.log('HTTP SERVER REQUEST HEADERS:', req.headers)
|
||||
|
||||
res.setHeader('Content-Type', 'text/html')
|
||||
res.writeHead(200)
|
||||
|
||||
res.end('<html><body>http server</body></html>')
|
||||
})
|
||||
|
||||
export const start = () => {
|
||||
return new Promise<http.Server>((resolve) => {
|
||||
srv.listen(8080, () => {
|
||||
console.log('server listening on port: 8080')
|
||||
|
||||
resolve(srv)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export const stop = () => {
|
||||
return new Promise<Error | undefined>((resolve) => {
|
||||
srv.close(resolve)
|
||||
})
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
/* eslint-disable
|
||||
no-console,
|
||||
*/
|
||||
const https = require('https')
|
||||
const Promise = require('bluebird')
|
||||
const { allowDestroy } = require('@packages/network')
|
||||
const certs = require('./certs')
|
||||
|
||||
const defaultOnRequest = function (req, res) {
|
||||
console.log('HTTPS SERVER REQUEST URL:', req.url)
|
||||
console.log('HTTPS SERVER REQUEST HEADERS:', req.headers)
|
||||
|
||||
res.setHeader('Content-Type', 'text/html')
|
||||
res.writeHead(200)
|
||||
|
||||
res.end('<html><head></head><body>https server</body></html>')
|
||||
}
|
||||
|
||||
let servers = []
|
||||
|
||||
const create = (onRequest) => {
|
||||
return https.createServer(certs, onRequest != null ? onRequest : defaultOnRequest)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
create,
|
||||
|
||||
start (port, onRequest) {
|
||||
return new Promise((resolve) => {
|
||||
const srv = create(onRequest)
|
||||
|
||||
allowDestroy(srv)
|
||||
|
||||
servers.push(srv)
|
||||
|
||||
srv.listen(port, () => {
|
||||
console.log(`server listening on port: ${port}`)
|
||||
|
||||
resolve(srv)
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
stop () {
|
||||
const stop = (srv) => {
|
||||
return new Promise((resolve) => {
|
||||
srv.destroy(resolve)
|
||||
})
|
||||
}
|
||||
|
||||
return Promise.map(servers, stop)
|
||||
.then(() => {
|
||||
servers = []
|
||||
})
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/* eslint-disable
|
||||
no-console,
|
||||
*/
|
||||
import https from 'https'
|
||||
import { allowDestroy } from '@packages/network'
|
||||
import { options } from './certs'
|
||||
import type { IncomingMessage, ServerResponse } from 'http'
|
||||
|
||||
const defaultOnRequest = function (req: IncomingMessage, res: ServerResponse) {
|
||||
console.log('HTTPS SERVER REQUEST URL:', req.url)
|
||||
console.log('HTTPS SERVER REQUEST HEADERS:', req.headers)
|
||||
|
||||
res.setHeader('Content-Type', 'text/html')
|
||||
res.writeHead(200)
|
||||
|
||||
res.end('<html><head></head><body>https server</body></html>')
|
||||
}
|
||||
|
||||
let servers: https.Server<typeof IncomingMessage, typeof ServerResponse>[] = []
|
||||
|
||||
export const create = (onRequest: (req: IncomingMessage, res: ServerResponse) => void) => {
|
||||
return https.createServer(options, onRequest != null ? onRequest : defaultOnRequest)
|
||||
}
|
||||
|
||||
export const start = (port: number, onRequest?: (req: IncomingMessage, res: ServerResponse) => void) => {
|
||||
return new Promise<https.Server>((resolve) => {
|
||||
const srv = create(onRequest)
|
||||
|
||||
allowDestroy(srv)
|
||||
|
||||
servers.push(srv)
|
||||
|
||||
srv.listen(port, () => {
|
||||
console.log(`server listening on port: ${port}`)
|
||||
|
||||
resolve(srv)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export const stop = async () => {
|
||||
const stopServer = (srv: https.Server) => {
|
||||
return new Promise<Error | undefined>((resolve) => {
|
||||
srv.destroy(resolve)
|
||||
})
|
||||
}
|
||||
|
||||
await Promise.all(servers.map((server) => stopServer(server)))
|
||||
|
||||
servers = []
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
/* eslint-disable no-console */
|
||||
const { request } = require('../spec_helper')
|
||||
const { allowDestroy } = require('@packages/network')
|
||||
const http = require('http')
|
||||
const path = require('path')
|
||||
const httpsProxy = require('../../lib/proxy')
|
||||
|
||||
let prx = null
|
||||
|
||||
const pipe = (req, res) => {
|
||||
return req.pipe(request(req.url))
|
||||
.on('error', () => {
|
||||
console.log('**ERROR**', req.url)
|
||||
req.statusCode = 500
|
||||
|
||||
res.end()
|
||||
}).pipe(res)
|
||||
}
|
||||
|
||||
const onConnect = (req, socket, head, proxy) => {
|
||||
return proxy.connect(req, socket, head, {
|
||||
onDirectConnection (req, socket, head) {
|
||||
return ['localhost:8444', 'localhost:12344'].includes(req.url)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const onRequest = (req, res) => {
|
||||
return pipe(req, res)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
reset () {
|
||||
return httpsProxy.reset()
|
||||
},
|
||||
|
||||
start (port) {
|
||||
prx = http.createServer()
|
||||
|
||||
allowDestroy(prx)
|
||||
|
||||
const dir = path.join(process.cwd(), 'ca')
|
||||
|
||||
return httpsProxy.create(dir, port, {
|
||||
onUpgrade (req, socket, head) {},
|
||||
|
||||
onRequest (req, res) {
|
||||
console.log('ON REQUEST FROM OUTER PROXY', req.url, req.headers, req.method)
|
||||
|
||||
if (req.url.includes('replace')) {
|
||||
const {
|
||||
write,
|
||||
} = res
|
||||
|
||||
res.write = function (chunk) {
|
||||
chunk = Buffer.from(chunk.toString().replace('https server', 'replaced content'))
|
||||
|
||||
return write.call(this, chunk)
|
||||
}
|
||||
|
||||
return pipe(req, res)
|
||||
}
|
||||
|
||||
return pipe(req, res)
|
||||
},
|
||||
})
|
||||
.then((proxy) => {
|
||||
prx.on('request', onRequest)
|
||||
|
||||
prx.on('connect', (req, socket, head) => {
|
||||
return onConnect(req, socket, head, proxy)
|
||||
})
|
||||
|
||||
return new Promise((resolve) => {
|
||||
prx.listen(port, () => {
|
||||
prx.proxy = proxy
|
||||
console.log(`server listening on port: ${port}`)
|
||||
|
||||
resolve(proxy)
|
||||
})
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
stop () {
|
||||
return new Promise((resolve) => {
|
||||
return prx.destroy(resolve)
|
||||
}).then(() => {
|
||||
return prx.proxy.close()
|
||||
})
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/* eslint-disable no-console */
|
||||
import request from '@cypress/request-promise'
|
||||
import { allowDestroy } from '@packages/network'
|
||||
import http from 'http'
|
||||
import path from 'path'
|
||||
import { create as createProxy, reset as resetProxy } from '../../lib/proxy'
|
||||
import type { IncomingMessage, ServerResponse } from 'http'
|
||||
import type { Socket } from 'net'
|
||||
import type { Server } from '../../lib/server'
|
||||
|
||||
let prx = null
|
||||
|
||||
const pipe = (req: IncomingMessage, res: ServerResponse) => {
|
||||
return req.pipe(request(req.url))
|
||||
.on('error', () => {
|
||||
console.log('**ERROR**', req.url)
|
||||
req.statusCode = 500
|
||||
|
||||
res.end()
|
||||
}).pipe(res)
|
||||
}
|
||||
|
||||
const onConnect = (req: IncomingMessage, socket: Socket, head: Buffer, proxy: any) => {
|
||||
return proxy.connect(req, socket, head, {
|
||||
onDirectConnection (req: IncomingMessage, socket: Socket, head: Buffer) {
|
||||
return ['localhost:8444', 'localhost:12344'].includes(req.url)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const onRequest = (req: IncomingMessage, res: ServerResponse) => {
|
||||
return pipe(req, res)
|
||||
}
|
||||
|
||||
export const reset = () => resetProxy()
|
||||
|
||||
export const start = (port: number): Promise<Server> => {
|
||||
prx = http.createServer()
|
||||
|
||||
allowDestroy(prx)
|
||||
|
||||
const dir = path.join(process.cwd(), 'ca')
|
||||
|
||||
return createProxy(dir, port, {
|
||||
onUpgrade (req: IncomingMessage, socket: Socket, head: Buffer) {},
|
||||
|
||||
onRequest (req: IncomingMessage, res: ServerResponse) {
|
||||
console.log('ON REQUEST FROM OUTER PROXY', req.url, req.headers, req.method)
|
||||
|
||||
if (req.url.includes('replace')) {
|
||||
const {
|
||||
write,
|
||||
} = res
|
||||
|
||||
res.write = function (chunk) {
|
||||
chunk = Buffer.from(chunk.toString().replace('https server', 'replaced content'))
|
||||
|
||||
return write.call(this, chunk)
|
||||
}
|
||||
|
||||
return pipe(req, res)
|
||||
}
|
||||
|
||||
return pipe(req, res)
|
||||
},
|
||||
})
|
||||
.then((proxy) => {
|
||||
prx.on('request', onRequest)
|
||||
|
||||
prx.on('connect', (req, socket, head) => {
|
||||
return onConnect(req, socket, head, proxy)
|
||||
})
|
||||
|
||||
return new Promise((resolve) => {
|
||||
prx.listen(port, () => {
|
||||
prx.proxy = proxy
|
||||
console.log(`server listening on port: ${port}`)
|
||||
|
||||
resolve(proxy)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export const stop = () => {
|
||||
return new Promise((resolve) => {
|
||||
return prx.destroy(resolve)
|
||||
}).then(() => {
|
||||
return prx.proxy.close()
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,389 @@
|
||||
import { it, describe, expect, beforeEach, beforeAll, afterEach, afterAll, vi } from 'vitest'
|
||||
import path from 'path'
|
||||
import request from '@cypress/request-promise'
|
||||
import DebugProxy from '@cypress/debugging-proxy'
|
||||
import https from 'https'
|
||||
import net from 'net'
|
||||
import network from '@packages/network'
|
||||
import { start as startProxy, stop as stopProxy, reset as resetProxy } from '../helpers/proxy'
|
||||
import { start as startHttpServer, stop as stopHttpServer } from '../helpers/http_server'
|
||||
import { start as startHttpsServer, stop as stopHttpsServer } from '../helpers/https_server'
|
||||
import fs from 'fs/promises'
|
||||
import { Server } from '../../lib/server'
|
||||
|
||||
describe('Proxy', () => {
|
||||
let proxy: Server
|
||||
|
||||
// clean out the ca directory before and after all tests
|
||||
beforeAll(async function () {
|
||||
try {
|
||||
await fs.rm(path.join(__dirname, '../', '../', 'ca'), { recursive: true })
|
||||
} catch (err) {
|
||||
// if the directory does not exist, we can ignore the error
|
||||
if (err.code !== 'ENOENT') {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
afterAll(async function () {
|
||||
try {
|
||||
await fs.rm(path.join(__dirname, '../', '../', 'ca'), { recursive: true })
|
||||
} catch (err) {
|
||||
// if the directory does not exist, we can ignore the error
|
||||
if (err.code !== 'ENOENT') {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
beforeEach(async function () {
|
||||
await Promise.all([
|
||||
startHttpServer(),
|
||||
startHttpsServer(8443),
|
||||
startHttpsServer(8444),
|
||||
])
|
||||
|
||||
proxy = await startProxy(3333)
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await Promise.all([
|
||||
stopHttpServer(),
|
||||
stopHttpsServer(),
|
||||
stopProxy(),
|
||||
])
|
||||
})
|
||||
|
||||
it('can request the googles', async function () {
|
||||
await Promise.all([
|
||||
request({
|
||||
strictSSL: false,
|
||||
proxy: 'http://localhost:3333',
|
||||
url: 'https://www.google.com',
|
||||
}),
|
||||
|
||||
request({
|
||||
strictSSL: false,
|
||||
proxy: 'http://localhost:3333',
|
||||
url: 'https://mail.google.com',
|
||||
}),
|
||||
|
||||
request({
|
||||
strictSSL: false,
|
||||
proxy: 'http://localhost:3333',
|
||||
url: 'https://google.com',
|
||||
}),
|
||||
])
|
||||
// give some padding to external
|
||||
// network request
|
||||
}, 10000)
|
||||
|
||||
it('can call the httpsDirectly without a proxy', async () => {
|
||||
await request({
|
||||
strictSSL: false,
|
||||
url: 'https://localhost:8443',
|
||||
secureProtocol: 'TLSv1_2_method',
|
||||
})
|
||||
})
|
||||
|
||||
it('can boot the httpsServer', async () => {
|
||||
const html = await request({
|
||||
strictSSL: false,
|
||||
url: 'https://localhost:8443/',
|
||||
proxy: 'http://localhost:3333',
|
||||
})
|
||||
|
||||
expect(html).toContain('https server')
|
||||
})
|
||||
|
||||
it('yields the onRequest callback', async () => {
|
||||
const html = await request({
|
||||
strictSSL: false,
|
||||
url: 'https://localhost:8443/replace',
|
||||
proxy: 'http://localhost:3333',
|
||||
})
|
||||
|
||||
expect(html).toContain('replaced content')
|
||||
})
|
||||
|
||||
it('closes outgoing connections when client disconnects', async function () {
|
||||
const connectSpy = vi.spyOn(net, 'connect')
|
||||
|
||||
await request({
|
||||
strictSSL: false,
|
||||
url: 'https://localhost:8444/replace',
|
||||
proxy: 'http://localhost:3333',
|
||||
resolveWithFullResponse: true,
|
||||
})
|
||||
|
||||
// ensure the outgoing socket created for this connection was destroyed
|
||||
expect(connectSpy).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
it('can boot the httpServer', async () => {
|
||||
const html = await request({
|
||||
strictSSL: false,
|
||||
url: 'http://localhost:8080/',
|
||||
proxy: 'http://localhost:3333',
|
||||
})
|
||||
|
||||
expect(html).toContain('http server')
|
||||
})
|
||||
|
||||
describe('generating certificates', { retry: 4 }, function () {
|
||||
it('reuses existing certificates', async function () {
|
||||
await request({
|
||||
strictSSL: false,
|
||||
url: 'https://localhost:8443/',
|
||||
proxy: 'http://localhost:3333',
|
||||
})
|
||||
|
||||
resetProxy()
|
||||
|
||||
// should not generate missing certificates
|
||||
let genSpy = vi.spyOn(proxy, '_generateMissingCertificates')
|
||||
|
||||
await request({
|
||||
strictSSL: false,
|
||||
url: 'https://localhost:8443/',
|
||||
proxy: 'http://localhost:3333',
|
||||
})
|
||||
|
||||
expect(genSpy).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
// @see https://github.com/cypress-io/cypress/issues/8705
|
||||
it('handles errors with reusing existing certificates', async function () {
|
||||
await proxy._ca.removeAll()
|
||||
|
||||
resetProxy()
|
||||
let genSpy = vi.spyOn(proxy, '_generateMissingCertificates')
|
||||
|
||||
await request({
|
||||
strictSSL: false,
|
||||
url: 'https://localhost:8443/',
|
||||
proxy: 'http://localhost:3333',
|
||||
})
|
||||
|
||||
resetProxy()
|
||||
expect(genSpy).toHaveBeenCalledExactlyOnceWith('localhost')
|
||||
|
||||
const privateKeyPath = proxy._ca.getPrivateKeyPath('localhost')
|
||||
const key = (await fs.readFile(privateKeyPath)).toString().trim()
|
||||
|
||||
expect(key).toMatch(/^-----BEGIN RSA PRIVATE KEY-----/)
|
||||
expect(key).toMatch(/-----END RSA PRIVATE KEY-----$/)
|
||||
|
||||
await fs.writeFile(privateKeyPath, 'some random garbage')
|
||||
|
||||
await request({
|
||||
strictSSL: false,
|
||||
url: 'https://localhost:8443/',
|
||||
proxy: 'http://localhost:3333',
|
||||
})
|
||||
|
||||
expect(genSpy).toHaveBeenCalledTimes(2)
|
||||
expect(genSpy).toHaveBeenNthCalledWith(1, 'localhost')
|
||||
expect(genSpy).toHaveBeenNthCalledWith(2, 'localhost')
|
||||
|
||||
const key2 = (await fs.readFile(privateKeyPath)).toString().trim()
|
||||
|
||||
expect(key2).toMatch(/^-----BEGIN RSA PRIVATE KEY-----/)
|
||||
expect(key2).toMatch(/-----END RSA PRIVATE KEY-----$/)
|
||||
})
|
||||
|
||||
// https://github.com/cypress-io/cypress/issues/771
|
||||
it('generates certs and can proxy requests for HTTPS requests to IPs', async function () {
|
||||
let generateMissingCertificatesSpy = vi.spyOn(proxy, '_generateMissingCertificates')
|
||||
let getServerPortSpy = vi.spyOn(proxy, '_getServerPortForIp')
|
||||
|
||||
await Promise.all([
|
||||
startHttpsServer(8445),
|
||||
proxy._ca.removeAll(),
|
||||
])
|
||||
|
||||
await request({
|
||||
strictSSL: false,
|
||||
url: 'https://127.0.0.1:8445/',
|
||||
proxy: 'http://localhost:3333',
|
||||
})
|
||||
|
||||
// this should not stand up its own https server
|
||||
await request({
|
||||
strictSSL: false,
|
||||
url: 'https://localhost:8443/',
|
||||
proxy: 'http://localhost:3333',
|
||||
})
|
||||
|
||||
expect(proxy._ipServers['127.0.0.1']).toBeInstanceOf(https.Server)
|
||||
expect(getServerPortSpy).toHaveBeenCalledExactlyOnceWith('127.0.0.1', expect.any(Object))
|
||||
|
||||
expect(generateMissingCertificatesSpy).toHaveBeenCalledTimes(2)
|
||||
}, 5000)
|
||||
|
||||
// https://github.com/cypress-io/cypress/issues/9220
|
||||
it('handles errors with addContext', async function () {
|
||||
let connectSpy = vi.spyOn(proxy, 'connect')
|
||||
|
||||
vi.spyOn(proxy._sniServer, 'addContext').mockImplementation(() => {
|
||||
throw new Error('error adding context')
|
||||
})
|
||||
|
||||
try {
|
||||
await request({
|
||||
strictSSL: false,
|
||||
url: 'https://localhost:8443/',
|
||||
proxy: 'http://localhost:3333',
|
||||
})
|
||||
|
||||
throw new Error('should not reach here')
|
||||
} catch (err) {
|
||||
expect(err.message).toMatch(/Client network socket disconnected before secure TLS connection was established/)
|
||||
// This scenario will cause an error but we should clean
|
||||
// ensure the outgoing socket created for this connection was destroyed
|
||||
expect(connectSpy).toHaveBeenCalledOnce()
|
||||
|
||||
const socket = connectSpy.mock.calls[0][1]
|
||||
|
||||
expect(socket.destroyed).toBe(true)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('closing', () => {
|
||||
it('resets sslServers and can reopen', async function () {
|
||||
await request({
|
||||
strictSSL: false,
|
||||
url: 'https://localhost:8443/',
|
||||
proxy: 'http://localhost:3333',
|
||||
})
|
||||
|
||||
await stopProxy()
|
||||
await startProxy(3333)
|
||||
|
||||
// make sure missing certificates are not generated and are reused
|
||||
let generateMissingCertificatesSpy = vi.spyOn(proxy, '_generateMissingCertificates')
|
||||
|
||||
await request({
|
||||
strictSSL: false,
|
||||
url: 'https://localhost:8443/',
|
||||
proxy: 'http://localhost:3333',
|
||||
})
|
||||
|
||||
expect(generateMissingCertificatesSpy).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('with an upstream proxy', () => {
|
||||
let upstream: DebugProxy
|
||||
|
||||
beforeEach(function () {
|
||||
vi.unstubAllEnvs()
|
||||
// PROXY vars should override npm_config vars, so set them to cause failures if they are used
|
||||
// @see https://github.com/cypress-io/cypress/pull/8295
|
||||
vi.stubEnv('npm_config_proxy', 'http://erroneously-used-npm-proxy.invalid')
|
||||
vi.stubEnv('npm_config_https_proxy', 'http://erroneously-used-npm-proxy.invalid')
|
||||
vi.stubEnv('npm_config_noproxy', 'just,some,nonsense')
|
||||
vi.stubEnv('NO_PROXY', '')
|
||||
vi.stubEnv('HTTP_PROXY', 'http://localhost:2222')
|
||||
vi.stubEnv('HTTPS_PROXY', 'http://localhost:2222')
|
||||
|
||||
upstream = new DebugProxy({
|
||||
keepRequests: true,
|
||||
})
|
||||
|
||||
return upstream.start(2222)
|
||||
})
|
||||
|
||||
afterEach(async function () {
|
||||
await upstream.stop()
|
||||
})
|
||||
|
||||
it('passes a request to an https server through the upstream', async function () {
|
||||
upstream._onConnect = function (domain, port) {
|
||||
expect(domain).toEqual('localhost')
|
||||
expect(port).toEqual('8444')
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
const res = await request({
|
||||
strictSSL: false,
|
||||
url: 'https://localhost:8444/',
|
||||
proxy: 'http://localhost:3333',
|
||||
})
|
||||
|
||||
expect(res).toContain('https server')
|
||||
})
|
||||
|
||||
it('uses HTTP basic auth when provided', async function () {
|
||||
upstream.setAuth({
|
||||
username: 'foo',
|
||||
password: 'bar',
|
||||
})
|
||||
|
||||
upstream._onConnect = function (domain, port) {
|
||||
expect(domain).toEqual('localhost')
|
||||
expect(port).toEqual('8444')
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
vi.stubEnv('HTTP_PROXY', 'http://foo:bar@localhost:2222')
|
||||
vi.stubEnv('HTTPS_PROXY', 'http://foo:bar@localhost:2222')
|
||||
|
||||
const res = await request({
|
||||
strictSSL: false,
|
||||
url: 'https://localhost:8444/',
|
||||
proxy: 'http://localhost:3333',
|
||||
})
|
||||
|
||||
expect(res).toContain('https server')
|
||||
})
|
||||
|
||||
it('closes outgoing connections when client disconnects', async function () {
|
||||
const connectSpy = vi.spyOn(net, 'connect')
|
||||
|
||||
await request({
|
||||
strictSSL: false,
|
||||
url: 'https://localhost:8444/replace',
|
||||
proxy: 'http://localhost:3333',
|
||||
resolveWithFullResponse: true,
|
||||
forever: false,
|
||||
})
|
||||
|
||||
// ensure the outgoing socket created for this connection was destroyed
|
||||
expect(connectSpy).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
// https://github.com/cypress-io/cypress/issues/4257
|
||||
it('passes through to SNI when it is intercepted and not through proxy', async function () {
|
||||
const createSocket = vi.spyOn(network.connect, 'createRetryingSocket').mockImplementation((_, cb) => {
|
||||
cb(new Error('stub'))
|
||||
})
|
||||
const createProxyConn = vi.spyOn(network.agent.httpsAgent, 'createUpstreamProxyConnection')
|
||||
|
||||
try {
|
||||
await request({
|
||||
strictSSL: false,
|
||||
url: 'https://localhost:8443',
|
||||
proxy: 'http://localhost:3333',
|
||||
resolveWithFullResponse: true,
|
||||
forever: false,
|
||||
})
|
||||
|
||||
throw new Error('should not succeed')
|
||||
} catch (err) {
|
||||
expect(err.message).toContain('Client network socket disconnected before secure TLS connection was established')
|
||||
|
||||
expect(createProxyConn).not.toHaveBeenCalled()
|
||||
|
||||
expect(createSocket).toHaveBeenCalledWith({
|
||||
port: proxy._sniPort,
|
||||
host: 'localhost',
|
||||
}, expect.any(Function))
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,386 +0,0 @@
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
|
||||
|
||||
const { request, expect } = require('../spec_helper')
|
||||
const DebugProxy = require('@cypress/debugging-proxy')
|
||||
const https = require('https')
|
||||
const net = require('net')
|
||||
const network = require('@packages/network')
|
||||
const Promise = require('bluebird')
|
||||
const proxy = require('../helpers/proxy')
|
||||
const httpServer = require('../helpers/http_server')
|
||||
const httpsServer = require('../helpers/https_server')
|
||||
const fs = require('fs').promises
|
||||
|
||||
describe('Proxy', () => {
|
||||
beforeEach(function () {
|
||||
return Promise.join(
|
||||
httpServer.start(),
|
||||
|
||||
httpsServer.start(8443),
|
||||
|
||||
httpsServer.start(8444),
|
||||
|
||||
proxy.start(3333)
|
||||
.then((proxy1) => {
|
||||
this.proxy = proxy1
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
return Promise.join(
|
||||
httpServer.stop(),
|
||||
httpsServer.stop(),
|
||||
proxy.stop(),
|
||||
)
|
||||
})
|
||||
|
||||
it('can request the googles', function () {
|
||||
// give some padding to external
|
||||
// network request
|
||||
this.timeout(10000)
|
||||
|
||||
return Promise.all([
|
||||
request({
|
||||
strictSSL: false,
|
||||
proxy: 'http://localhost:3333',
|
||||
url: 'https://www.google.com',
|
||||
}),
|
||||
|
||||
request({
|
||||
strictSSL: false,
|
||||
proxy: 'http://localhost:3333',
|
||||
url: 'https://mail.google.com',
|
||||
}),
|
||||
|
||||
request({
|
||||
strictSSL: false,
|
||||
proxy: 'http://localhost:3333',
|
||||
url: 'https://google.com',
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it('can call the httpsDirectly without a proxy', () => {
|
||||
return request({
|
||||
strictSSL: false,
|
||||
url: 'https://localhost:8443',
|
||||
})
|
||||
})
|
||||
|
||||
it('can boot the httpsServer', () => {
|
||||
return request({
|
||||
strictSSL: false,
|
||||
url: 'https://localhost:8443/',
|
||||
proxy: 'http://localhost:3333',
|
||||
})
|
||||
.then((html) => {
|
||||
expect(html).to.include('https server')
|
||||
})
|
||||
})
|
||||
|
||||
it('yields the onRequest callback', () => {
|
||||
return request({
|
||||
strictSSL: false,
|
||||
url: 'https://localhost:8443/replace',
|
||||
proxy: 'http://localhost:3333',
|
||||
})
|
||||
.then((html) => {
|
||||
expect(html).to.include('replaced content')
|
||||
})
|
||||
})
|
||||
|
||||
it('closes outgoing connections when client disconnects', async function () {
|
||||
this.sandbox.spy(net, 'connect')
|
||||
|
||||
await request({
|
||||
strictSSL: false,
|
||||
url: 'https://localhost:8444/replace',
|
||||
proxy: 'http://localhost:3333',
|
||||
resolveWithFullResponse: true,
|
||||
})
|
||||
|
||||
// ensure the outgoing socket created for this connection was destroyed
|
||||
expect(net.connect).calledOnce
|
||||
|
||||
const socket = net.connect.getCalls()[0].returnValue
|
||||
|
||||
// sometimes the close event happens before we can attach the listener, causing this test to flake
|
||||
if (!socket.destroyed || !socket.readyState === 'closed') {
|
||||
await new Promise((resolve) => {
|
||||
socket.on('close', () => {
|
||||
expect(socket.destroyed).to.be.true
|
||||
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
it('can boot the httpServer', () => {
|
||||
return request({
|
||||
strictSSL: false,
|
||||
url: 'http://localhost:8080/',
|
||||
proxy: 'http://localhost:3333',
|
||||
})
|
||||
|
||||
.then((html) => {
|
||||
expect(html).to.include('http server')
|
||||
})
|
||||
})
|
||||
|
||||
context('generating certificates', function () {
|
||||
this.retries(4)
|
||||
|
||||
it('reuses existing certificates', function () {
|
||||
return request({
|
||||
strictSSL: false,
|
||||
url: 'https://localhost:8443/',
|
||||
proxy: 'http://localhost:3333',
|
||||
})
|
||||
.then(() => {
|
||||
proxy.reset()
|
||||
|
||||
// force this to reject if its called
|
||||
this.sandbox.stub(this.proxy, '_generateMissingCertificates').rejects(new Error('should not call'))
|
||||
|
||||
return request({
|
||||
strictSSL: false,
|
||||
url: 'https://localhost:8443/',
|
||||
proxy: 'http://localhost:3333',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// @see https://github.com/cypress-io/cypress/issues/8705
|
||||
it('handles errors with reusing existing certificates', async function () {
|
||||
await this.proxy._ca.removeAll()
|
||||
|
||||
proxy.reset()
|
||||
const genSpy = this.sandbox.spy(this.proxy, '_generateMissingCertificates')
|
||||
|
||||
await request({
|
||||
strictSSL: false,
|
||||
url: 'https://localhost:8443/',
|
||||
proxy: 'http://localhost:3333',
|
||||
})
|
||||
|
||||
proxy.reset()
|
||||
expect(genSpy).to.be.calledWith('localhost').and.calledOnce
|
||||
|
||||
const privateKeyPath = this.proxy._ca.getPrivateKeyPath('localhost')
|
||||
const key = (await fs.readFile(privateKeyPath)).toString().trim()
|
||||
|
||||
expect(key).to.match(/^-----BEGIN RSA PRIVATE KEY-----/)
|
||||
.and.match(/-----END RSA PRIVATE KEY-----$/)
|
||||
|
||||
await fs.writeFile(privateKeyPath, 'some random garbage')
|
||||
|
||||
await request({
|
||||
strictSSL: false,
|
||||
url: 'https://localhost:8443/',
|
||||
proxy: 'http://localhost:3333',
|
||||
})
|
||||
|
||||
expect(genSpy).to.always.have.been.calledWith('localhost').and.calledTwice
|
||||
|
||||
const key2 = (await fs.readFile(privateKeyPath)).toString().trim()
|
||||
|
||||
expect(key2).to.match(/^-----BEGIN RSA PRIVATE KEY-----/)
|
||||
.and.match(/-----END RSA PRIVATE KEY-----$/)
|
||||
})
|
||||
|
||||
// https://github.com/cypress-io/cypress/issues/771
|
||||
it('generates certs and can proxy requests for HTTPS requests to IPs', function () {
|
||||
this.timeout(5000)
|
||||
|
||||
this.sandbox.spy(this.proxy, '_generateMissingCertificates')
|
||||
this.sandbox.spy(this.proxy, '_getServerPortForIp')
|
||||
|
||||
return Promise.all([
|
||||
httpsServer.start(8445),
|
||||
this.proxy._ca.removeAll(),
|
||||
])
|
||||
.then(() => {
|
||||
return request({
|
||||
strictSSL: false,
|
||||
url: 'https://127.0.0.1:8445/',
|
||||
proxy: 'http://localhost:3333',
|
||||
})
|
||||
}).then(() => {
|
||||
// this should not stand up its own https server
|
||||
return request({
|
||||
strictSSL: false,
|
||||
url: 'https://localhost:8443/',
|
||||
proxy: 'http://localhost:3333',
|
||||
})
|
||||
}).then(() => {
|
||||
expect(this.proxy._ipServers['127.0.0.1']).to.be.an.instanceOf(https.Server)
|
||||
expect(this.proxy._getServerPortForIp).to.be.calledWith('127.0.0.1').and.be.calledOnce
|
||||
|
||||
expect(this.proxy._generateMissingCertificates).to.be.calledTwice
|
||||
})
|
||||
})
|
||||
|
||||
// https://github.com/cypress-io/cypress/issues/9220
|
||||
it('handles errors with addContext', async function () {
|
||||
this.sandbox.spy(this.proxy, 'connect')
|
||||
this.sandbox.stub(this.proxy._sniServer, 'addContext').throws(new Error('error adding context'))
|
||||
|
||||
return request({
|
||||
strictSSL: false,
|
||||
url: 'https://localhost:8443/',
|
||||
proxy: 'http://localhost:3333',
|
||||
}).catch(() => {
|
||||
// This scenario will cause an error but we should clean
|
||||
// ensure the outgoing socket created for this connection was destroyed
|
||||
expect(this.proxy.connect).calledOnce
|
||||
const socket = this.proxy.connect.getCalls()[0].args[1]
|
||||
|
||||
expect(socket.destroyed).to.be.true
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
context('closing', () => {
|
||||
it('resets sslServers and can reopen', function () {
|
||||
return request({
|
||||
strictSSL: false,
|
||||
url: 'https://localhost:8443/',
|
||||
proxy: 'http://localhost:3333',
|
||||
})
|
||||
.then(() => {
|
||||
return proxy.stop()
|
||||
}).then(() => {
|
||||
return proxy.start(3333)
|
||||
}).then(() => {
|
||||
// force this to reject if its called
|
||||
this.sandbox.stub(this.proxy, '_generateMissingCertificates').rejects(new Error('should not call'))
|
||||
|
||||
return request({
|
||||
strictSSL: false,
|
||||
url: 'https://localhost:8443/',
|
||||
proxy: 'http://localhost:3333',
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
context('with an upstream proxy', () => {
|
||||
beforeEach(function () {
|
||||
// PROXY vars should override npm_config vars, so set them to cause failures if they are used
|
||||
// @see https://github.com/cypress-io/cypress/pull/8295
|
||||
process.env.npm_config_proxy = process.env.npm_config_https_proxy = 'http://erroneously-used-npm-proxy.invalid'
|
||||
process.env.npm_config_noproxy = 'just,some,nonsense'
|
||||
|
||||
process.env.NO_PROXY = ''
|
||||
process.env.HTTP_PROXY = process.env.HTTPS_PROXY = 'http://localhost:2222'
|
||||
|
||||
this.upstream = new DebugProxy({
|
||||
keepRequests: true,
|
||||
})
|
||||
|
||||
return this.upstream.start(2222)
|
||||
})
|
||||
|
||||
it('passes a request to an https server through the upstream', function () {
|
||||
this.upstream._onConnect = function (domain, port) {
|
||||
expect(domain).to.eq('localhost')
|
||||
expect(port).to.eq('8444')
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
return request({
|
||||
strictSSL: false,
|
||||
url: 'https://localhost:8444/',
|
||||
proxy: 'http://localhost:3333',
|
||||
}).then((res) => {
|
||||
expect(res).to.contain('https server')
|
||||
})
|
||||
})
|
||||
|
||||
it('uses HTTP basic auth when provided', function () {
|
||||
this.upstream.setAuth({
|
||||
username: 'foo',
|
||||
password: 'bar',
|
||||
})
|
||||
|
||||
this.upstream._onConnect = function (domain, port) {
|
||||
expect(domain).to.eq('localhost')
|
||||
expect(port).to.eq('8444')
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
process.env.HTTP_PROXY = process.env.HTTPS_PROXY = 'http://foo:bar@localhost:2222'
|
||||
|
||||
return request({
|
||||
strictSSL: false,
|
||||
url: 'https://localhost:8444/',
|
||||
proxy: 'http://localhost:3333',
|
||||
}).then((res) => {
|
||||
expect(res).to.contain('https server')
|
||||
})
|
||||
})
|
||||
|
||||
it('closes outgoing connections when client disconnects', async function () {
|
||||
this.sandbox.spy(net, 'connect')
|
||||
|
||||
await request({
|
||||
strictSSL: false,
|
||||
url: 'https://localhost:8444/replace',
|
||||
proxy: 'http://localhost:3333',
|
||||
resolveWithFullResponse: true,
|
||||
forever: false,
|
||||
})
|
||||
|
||||
// ensure the outgoing socket created for this connection was destroyed
|
||||
expect(net.connect).calledOnce
|
||||
const socket = net.connect.getCalls()[0].returnValue
|
||||
|
||||
// sometimes the close event happens before we can attach the listener, causing this test to flake
|
||||
if (!socket.destroyed || !socket.readyState === 'closed') {
|
||||
await new Promise((resolve) => {
|
||||
socket.on('close', () => {
|
||||
expect(socket.destroyed).to.be.true
|
||||
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// https://github.com/cypress-io/cypress/issues/4257
|
||||
it('passes through to SNI when it is intercepted and not through proxy', function () {
|
||||
const createSocket = this.sandbox.stub(network.connect, 'createRetryingSocket').callsArgWith(1, new Error('stub'))
|
||||
const createProxyConn = this.sandbox.spy(network.agent.httpsAgent, 'createUpstreamProxyConnection')
|
||||
|
||||
return request({
|
||||
strictSSL: false,
|
||||
url: 'https://localhost:8443',
|
||||
proxy: 'http://localhost:3333',
|
||||
resolveWithFullResponse: true,
|
||||
forever: false,
|
||||
})
|
||||
.then(() => {
|
||||
throw new Error('should not succeed')
|
||||
}).catch({ message: 'Error: Client network socket disconnected before secure TLS connection was established' }, () => {
|
||||
expect(createProxyConn).to.not.be.called
|
||||
|
||||
expect(createSocket).to.be.calledWith({
|
||||
port: this.proxy._sniPort,
|
||||
host: 'localhost',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
return afterEach(function () {
|
||||
this.upstream.stop()
|
||||
delete process.env.HTTP_PROXY
|
||||
delete process.env.HTTPS_PROXY
|
||||
|
||||
delete process.env.NO_PROXY
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,5 +0,0 @@
|
||||
test/unit
|
||||
test/integration
|
||||
--reporter spec
|
||||
--compilers ts:@packages/ts/register
|
||||
--recursive
|
||||
@@ -1,20 +0,0 @@
|
||||
const chai = require('chai')
|
||||
const sinon = require('sinon')
|
||||
const Promise = require('bluebird')
|
||||
const sinonChai = require('sinon-chai')
|
||||
const request = require('@cypress/request-promise')
|
||||
const supertest = require('supertest')
|
||||
|
||||
require('sinon-as-promised')(Promise)
|
||||
|
||||
chai.use(sinonChai)
|
||||
|
||||
beforeEach(function () {
|
||||
this.sandbox = sinon.sandbox.create()
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
return this.sandbox.restore()
|
||||
})
|
||||
|
||||
module.exports = { request, sinon, supertest, expect: chai.expect }
|
||||
@@ -0,0 +1,244 @@
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
||||
import os from 'os'
|
||||
import fs from 'fs-extra'
|
||||
import path from 'path'
|
||||
import { CA } from '../../lib/ca'
|
||||
|
||||
vi.mock('fs-extra', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
default: {
|
||||
// @ts-expect-error
|
||||
...actual.default,
|
||||
readFile: vi.fn(),
|
||||
outputFile: vi.fn(),
|
||||
remove: vi.fn(),
|
||||
stat: vi.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
// make sure the properties we set on the certificate are reflected
|
||||
const validateCertificate = (ca: CA) => {
|
||||
expect(ca).toBeInstanceOf(CA)
|
||||
|
||||
// should represent an instance of a pki.Certificate object
|
||||
expect(ca.CAcert).toBeInstanceOf(Object)
|
||||
expect(ca.CAcert.version).toEqual(2)
|
||||
expect(ca.CAcert.extensions).toBeInstanceOf(Array)
|
||||
expect(ca.CAcert.extensions).toHaveLength(5)
|
||||
expect(ca.CAcert.extensions).toEqual(expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
name: 'basicConstraints',
|
||||
cA: true,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
name: 'keyUsage',
|
||||
keyCertSign: true,
|
||||
digitalSignature: true,
|
||||
nonRepudiation: true,
|
||||
keyEncipherment: true,
|
||||
dataEncipherment: true,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
name: 'extKeyUsage',
|
||||
serverAuth: true,
|
||||
clientAuth: true,
|
||||
codeSigning: true,
|
||||
emailProtection: true,
|
||||
timeStamping: true,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
name: 'nsCertType',
|
||||
client: true,
|
||||
server: true,
|
||||
email: true,
|
||||
objsign: true,
|
||||
sslCA: true,
|
||||
emailCA: true,
|
||||
objCA: true,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
name: 'subjectKeyIdentifier',
|
||||
}),
|
||||
]))
|
||||
|
||||
expect(ca.CAcert.issuer.attributes).toBeInstanceOf(Array)
|
||||
expect(ca.CAcert.issuer.attributes).toHaveLength(6)
|
||||
expect(ca.CAcert.issuer.attributes).toEqual(expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
name: 'commonName',
|
||||
value: 'CypressProxyCA',
|
||||
}), expect.objectContaining({
|
||||
name: 'countryName',
|
||||
value: 'Internet',
|
||||
}), expect.objectContaining({
|
||||
shortName: 'ST',
|
||||
value: 'Internet',
|
||||
}), expect.objectContaining({
|
||||
name: 'localityName',
|
||||
value: 'Internet',
|
||||
}), expect.objectContaining({
|
||||
name: 'organizationName',
|
||||
value: 'Cypress.io',
|
||||
}), expect.objectContaining({
|
||||
shortName: 'OU',
|
||||
value: 'CA',
|
||||
}),
|
||||
]))
|
||||
|
||||
expect(ca.CAcert.subject.attributes).toBeInstanceOf(Array)
|
||||
expect(ca.CAcert.subject.attributes).toHaveLength(6)
|
||||
expect(ca.CAcert.subject.attributes).toEqual(expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
name: 'commonName',
|
||||
value: 'CypressProxyCA',
|
||||
}), expect.objectContaining({
|
||||
name: 'countryName',
|
||||
value: 'Internet',
|
||||
}), expect.objectContaining({
|
||||
shortName: 'ST',
|
||||
value: 'Internet',
|
||||
}), expect.objectContaining({
|
||||
name: 'localityName',
|
||||
value: 'Internet',
|
||||
}), expect.objectContaining({
|
||||
name: 'organizationName',
|
||||
value: 'Cypress.io',
|
||||
}), expect.objectContaining({
|
||||
shortName: 'OU',
|
||||
value: 'CA',
|
||||
}),
|
||||
]))
|
||||
|
||||
expect(ca.CAcert.validity.notBefore).toBeInstanceOf(Date)
|
||||
expect(ca.CAcert.validity.notAfter).toBeInstanceOf(Date)
|
||||
|
||||
// cert should be valid for 10 years
|
||||
expect(ca.CAcert.validity.notAfter.getFullYear()).toEqual(ca.CAcert.validity.notBefore.getFullYear() + 10)
|
||||
|
||||
expect(ca.CAcert.serialNumber).toEqual(expect.any(String))
|
||||
|
||||
// should represent an instance of a pki.rsa.KeyPair object
|
||||
expect(ca.CAkeys).toBeInstanceOf(Object)
|
||||
expect(ca.CAkeys).toHaveProperty('privateKey')
|
||||
expect(ca.CAkeys).toHaveProperty('publicKey')
|
||||
}
|
||||
|
||||
describe('lib/ca', () => {
|
||||
let tmpDir: string
|
||||
|
||||
beforeEach(async () => {
|
||||
vi.useFakeTimers()
|
||||
tmpDir = path.join(os.tmpdir(), 'cy-ca')
|
||||
vi.mocked(fs.outputFile).mockClear()
|
||||
vi.mocked(fs.remove).mockClear()
|
||||
vi.mocked(fs.readFile).mockClear()
|
||||
vi.mocked(fs.remove).mockClear()
|
||||
vi.mocked(fs.readFile).mockClear()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers()
|
||||
})
|
||||
|
||||
describe('#generateServerCertificateKeys', () => {
|
||||
it('generates certs for each host', async function () {
|
||||
const ca = await CA.create(tmpDir)
|
||||
|
||||
const [certPem, keyPrivatePem] = await ca.generateServerCertificateKeys('www.cypress.io')
|
||||
|
||||
expect(certPem).to.include('-----BEGIN CERTIFICATE-----')
|
||||
|
||||
expect(keyPrivatePem).to.include('-----BEGIN RSA PRIVATE KEY-----')
|
||||
})
|
||||
})
|
||||
|
||||
describe('.create', () => {
|
||||
it('loads existing certs and keys if version matches CA_VERSIONand certs can be loaded from disk', async function () {
|
||||
// have some mock certs and keys for the tests as a string
|
||||
const MOCK_CERT_PEM = `-----BEGIN CERTIFICATE-----\r\nMIIEADCCAuigAwIBAgIQ6MJNvbbXbCX7WclKcbjYeDANBgkqhkiG9w0BAQsFADB0\r\nMRcwFQYDVQQDEw5DeXByZXNzUHJveHlDQTERMA8GA1UEBhMISW50ZXJuZXQxETAP\r\nBgNVBAgTCEludGVybmV0MREwDwYDVQQHEwhJbnRlcm5ldDETMBEGA1UEChMKQ3lw\r\ncmVzcy5pbzELMAkGA1UECxMCQ0EwHhcNMjUxMDEzMDA1NjA5WhcNMzUxMDEzMDA1\r\nNjA5WjB0MRcwFQYDVQQDEw5DeXByZXNzUHJveHlDQTERMA8GA1UEBhMISW50ZXJu\r\nZXQxETAPBgNVBAgTCEludGVybmV0MREwDwYDVQQHEwhJbnRlcm5ldDETMBEGA1UE\r\nChMKQ3lwcmVzcy5pbzELMAkGA1UECxMCQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IB\r\nDwAwggEKAoIBAQDKOjGEdnGWigjUxWGSFESJKHL8NtGJiu+yfPp+sQNOlm1xmoGQ\r\n/fqcwVznS5Tg6R04awCmOkJ+vBay5ZlMn7l1ke95HPnHlVdUFd+rIgQ3GIQcoF6n\r\nigop7fl6nHQch5G7plmYl996r5BR9I3V0mnG37W0X6puPx75cniW2o7mswduvraW\r\nw8DY/QrA1AgNquOSrwklJbxbAzwQQSI8qezaVJKlPLdtDbDKtZqYotw+68Emd0ul\r\nml72f4gYFwlLkqUObku+s6X+8QZwsVfVqp4nX1JMHlT2HeoXqZpwVqeyufXT1ptT\r\npTwS0P95+47mHqgKNygtPMUGx7WqCrDWzSPXAgMBAAGjgY0wgYowDAYDVR0TBAUw\r\nAwEB/zALBgNVHQ8EBAMCAvQwOwYDVR0lBDQwMgYIKwYBBQUHAwEGCCsGAQUFBwMC\r\nBggrBgEFBQcDAwYIKwYBBQUHAwQGCCsGAQUFBwMIMBEGCWCGSAGG+EIBAQQEAwIA\r\n9zAdBgNVHQ4EFgQUJuQ4X4StMVeALE08ZtcMiyGT91kwDQYJKoZIhvcNAQELBQAD\r\nggEBALKuo2EZs+X0VFM95PcEYPa2a3F+7jdTOCa7aorbew3jiQQUSWH8RYDkmIg5\r\nDI9r6qq5sBmJfsVdTnxpn6w4DIY3dHd1UkGI+zEgKvPzqHwMUHNJGV3U79/JGfV2\r\nQZKllDQlJVEtNPo0o0TLvbPqAET5Z11gfQ1ZNZ4OBYAoJCZg8UflzVpeMsQCorNG\r\nzMObOnOVXWi6QHSQDkwMI3QVvseK1SfMgqcdgyodbEVJoEeay/B5mlYYvhs8iuSc\r\ncfkl3LZx/NtmetyM2SIVI+M7kkKQfcluOMQcBP3UCZ1/m7RajFbe55koi5EgGeFH\r\n1jg+r2qr6VclijKRfbqR6L4JBdg=\r\n-----END CERTIFICATE-----\r\n`
|
||||
const MOCK_KEY_PRIVATE_PEM = `-----BEGIN RSA PRIVATE KEY-----\r\nMIIEpAIBAAKCAQEA3/Acf8Fy6ECV88Qsz9fVA61cSK5Ki+wS88SYdz41xFp7ssew\r\nwYZvB9Aqii4vu8/to1AItLTV7I5ZEJEZJvlffK7fp15WMEqlpEHVAb3DoNw5ZAPY\r\n56liRHMi0RPV6t/QsyKTP/LYgNaDt+A9HE/My+E/+VfniOOE36tL9UxD+gipffLt\r\n+17c2oeT3Vzin3K4VuxNGtaFwUKUR+XJWcpGxxa/3GT3F6Xfmy1JTnDaFVGHL6ug\r\nW56z8uDyf8UUNaA8HSo2Ak+2Mc32xS0/+5FUSHdp5J8/ghjvnrRl01qmjnKVNSU8\r\n9wYPdoNw0D2N4uOoV2W8J2F76Gq1qlSO6bM1pwIDAQABAoIBAAcN6M/rd6OyWSbv\r\nMJwxh9/QR75wYx/KRYSRVl43QvlXAluM59gI1JmR6K0mrFFFyQ4ieMu8gJqtl0eq\r\n0niEVYo3dgsvMRbfWx10B3JBGJcKKPKqHlyZ3OMcH2Ynsk7uUwQ5nBrhGwnf+BFE\r\nSpiIOQLZKys/JieNR0PGgSOOjfuj2BoS+RMl30tRFOY/4QZP4oyuG/DND77+Cz/z\r\n7vBvF/78HoXfqbSQfLjT+D7kOvTVwF+jDYCUjMpo9uhQfWEA0a0+E8ZdIfzvtEca\r\nzhY18T0hvcDiq+revXjG9uOh7dPls1INdZazM85asife7GVmVEMmAsTXBlPU5wi0\r\nulqso60CgYEA/nRFSASQi5ySyt6De3PKEWc5GhFBVrA5YANKd2olMKvNbkm6W4TZ\r\n9mvJKxHmc2uTIOaRBAgOOZgNrYX2GgFC38IrNPtYBxTl+YmbFX+n4Z7Rp5s+HMz/\r\nVP6e8BLNaSYBT3Cqj1Nb/qfeqHgGmTs2Wk1qWeonyeoXyTO5fWd2JWMCgYEA4Uxh\r\nreKZr6Uz0BWhga7c74fkOg38CWzyW6OBuizlsxUCkzt4gGOXHOqb2O/pN/f2bBDB\r\nME4aErHngRTqW4vSpLPPsgvAxR1Q/dB8YUpesnO1pTFVIcWyVK/HIfo8Fg8ohNpE\r\nyEeemEVvtXz4nLK/Q8/KqV9KCuuCXucaqG4W0+0CgYEAvqTCm7i/y7pdyR16CW6x\r\ngOSDxeITwC18b1FH47xlbNfrrKwUsikRXS1YpapdrTB2JXpaQFkAv2oLJW1u/ADh\r\n5+AEm0eNppCj1Zih1zOzxrlFf3wyx0VYMIgs8NZFjHhrFuflAkmEbYG8syBqYTgZ\r\n+wJxojhr4z4+4AKfATQZMt0CgYATAdenzNs8Z0qUvo5um2sGRkep4i4mOWvE8Wlr\r\nZIhIcHhUJYtIAZ7pEJ3vUmYxk5jViyBRS/WFKD8os7QF3yj5PjZChh1QQ+XmU+V6\r\na8TLd1mWwy+0drJR1LaPFkZlcgfwFV4CK5Cktg7zl8R9q9LZDLnDSke73hyUlxi3\r\npvoEDQKBgQCgJVyo0p3x8bQe8RLKHb6la710I12Px4+sujImdG5+Spqrxyq5ii36\r\nbj/U3txxbqRg8RIzBZ1Zvde8TKt7RyhFqpkCfBQkqd/5/zriHOQZ7lgSNtjq5cYB\r\nEQ/JzKUoa0lAEuAxWQ7eSs8qy+Q36VphSTcNAqCt7rFhWmuASJVObg==\r\n-----END RSA PRIVATE KEY-----\r\n`
|
||||
const MOCK_KEY_PUBLIC_PEM = `-----BEGIN PUBLIC KEY-----\r\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3/Acf8Fy6ECV88Qsz9fV\r\nA61cSK5Ki+wS88SYdz41xFp7ssewwYZvB9Aqii4vu8/to1AItLTV7I5ZEJEZJvlf\r\nfK7fp15WMEqlpEHVAb3DoNw5ZAPY56liRHMi0RPV6t/QsyKTP/LYgNaDt+A9HE/M\r\ny+E/+VfniOOE36tL9UxD+gipffLt+17c2oeT3Vzin3K4VuxNGtaFwUKUR+XJWcpG\r\nxxa/3GT3F6Xfmy1JTnDaFVGHL6ugW56z8uDyf8UUNaA8HSo2Ak+2Mc32xS0/+5FU\r\nSHdp5J8/ghjvnrRl01qmjnKVNSU89wYPdoNw0D2N4uOoV2W8J2F76Gq1qlSO6bM1\r\npwIDAQAB\r\n-----END PUBLIC KEY-----\r\n`
|
||||
|
||||
// @ts-expect-error
|
||||
vi.mocked(fs.readFile).mockImplementation((pathToRead: string) => {
|
||||
if (pathToRead === `${tmpDir}/ca_version.txt`) {
|
||||
// return version 1 to match current CA_VERSION
|
||||
return Promise.resolve('1')
|
||||
}
|
||||
|
||||
if (pathToRead === `${tmpDir}/certs/ca.pem`) {
|
||||
return Promise.resolve(MOCK_CERT_PEM)
|
||||
}
|
||||
|
||||
if (pathToRead === `${tmpDir}/keys/ca.private.key`) {
|
||||
return Promise.resolve(MOCK_KEY_PRIVATE_PEM)
|
||||
}
|
||||
|
||||
if (pathToRead === `${tmpDir}/keys/ca.public.key`) {
|
||||
return Promise.resolve(MOCK_KEY_PUBLIC_PEM)
|
||||
}
|
||||
|
||||
return Promise.reject(new Error(`file not found: ${pathToRead}. Did you remember to mock it?`))
|
||||
})
|
||||
|
||||
const ca = await CA.create(tmpDir)
|
||||
|
||||
validateCertificate(ca)
|
||||
|
||||
// should not attempt to write the certs and keys if discovering cert succeeds version check and can load certs from disk
|
||||
expect(fs.outputFile).not.toHaveBeenCalledWith(`${tmpDir}/certs/ca.pem`, expect.any(String))
|
||||
expect(fs.outputFile).not.toHaveBeenCalledWith(`${tmpDir}/keys/ca.private.key`, expect.any(String))
|
||||
expect(fs.outputFile).not.toHaveBeenCalledWith(`${tmpDir}/keys/ca.public.key`, expect.any(String))
|
||||
expect(fs.outputFile).not.toHaveBeenCalledWith(`${tmpDir}/ca_version.txt`, '1')
|
||||
})
|
||||
|
||||
it('clears out CA folder if no ca_version.txt is found and generates certs and keys', async function () {
|
||||
vi.mocked(fs.readFile).mockImplementation((pathToRead: string) => {
|
||||
// mock failing of loading anything certificate related
|
||||
|
||||
return Promise.reject(new Error(`file not found: ${pathToRead}`))
|
||||
})
|
||||
|
||||
const ca = await CA.create(tmpDir)
|
||||
|
||||
validateCertificate(ca)
|
||||
|
||||
// if discovering the version fails or can't discover certs, the temp dir for cy-ca should be removed
|
||||
expect(fs.remove).toHaveBeenCalledWith(tmpDir)
|
||||
|
||||
// should attempt to write the certs and keys if discovering cert fails version check or otherwise
|
||||
expect(fs.outputFile).toHaveBeenCalledWith(`${tmpDir}/certs/ca.pem`, expect.any(String))
|
||||
expect(fs.outputFile).toHaveBeenCalledWith(`${tmpDir}/keys/ca.private.key`, expect.any(String))
|
||||
expect(fs.outputFile).toHaveBeenCalledWith(`${tmpDir}/keys/ca.public.key`, expect.any(String))
|
||||
expect(fs.outputFile).toHaveBeenCalledWith(`${tmpDir}/ca_version.txt`, '1')
|
||||
})
|
||||
|
||||
it('clears out CA folder with old CA_VERSION and regenerates certs and keys', async function () {
|
||||
// @ts-expect-error
|
||||
vi.mocked(fs.readFile).mockImplementation((pathToRead: string) => {
|
||||
if (pathToRead === `${tmpDir}/ca_version.txt`) {
|
||||
// return version 0 to return version mismatch
|
||||
return Promise.resolve('0')
|
||||
}
|
||||
|
||||
return Promise.reject(new Error(`file not found: ${pathToRead}`))
|
||||
})
|
||||
|
||||
const ca = await CA.create(tmpDir)
|
||||
|
||||
validateCertificate(ca)
|
||||
|
||||
// if discovering the version is a mismatch, the temp dir for cy-ca should be removed
|
||||
expect(fs.remove).toHaveBeenCalledWith(tmpDir)
|
||||
|
||||
// should attempt to write the certs and keys if discovering cert fails version check or otherwise
|
||||
expect(fs.outputFile).toHaveBeenCalledWith(`${tmpDir}/certs/ca.pem`, expect.any(String))
|
||||
expect(fs.outputFile).toHaveBeenCalledWith(`${tmpDir}/keys/ca.private.key`, expect.any(String))
|
||||
expect(fs.outputFile).toHaveBeenCalledWith(`${tmpDir}/keys/ca.public.key`, expect.any(String))
|
||||
expect(fs.outputFile).toHaveBeenCalledWith(`${tmpDir}/ca_version.txt`, '1')
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,161 +0,0 @@
|
||||
/* eslint-disable no-console */
|
||||
const { expect } = require('../spec_helper')
|
||||
|
||||
let fs = require('fs-extra')
|
||||
const path = require('path')
|
||||
const Promise = require('bluebird')
|
||||
const CA = require('../../lib/ca')
|
||||
|
||||
fs = Promise.promisifyAll(fs)
|
||||
|
||||
describe('lib/ca', () => {
|
||||
beforeEach(function () {
|
||||
this.timeout(5000)
|
||||
|
||||
this.dir = path.join(process.cwd(), 'tmp')
|
||||
|
||||
return fs.ensureDirAsync(this.dir)
|
||||
.then(() => {
|
||||
console.time('creating CA')
|
||||
|
||||
return CA.create(this.dir)
|
||||
.tap(() => {
|
||||
console.timeEnd('creating CA')
|
||||
})
|
||||
}).then((ca) => {
|
||||
this.ca = ca
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
return fs.removeAsync(this.dir)
|
||||
})
|
||||
|
||||
context('#generateServerCertificateKeys', () => {
|
||||
it('generates certs for each host', async function () {
|
||||
console.time('generating cert')
|
||||
|
||||
await this.ca.generateServerCertificateKeys('www.cypress.io')
|
||||
.spread((certPem, keyPrivatePem) => {
|
||||
console.timeEnd('generating cert')
|
||||
expect(certPem).to.include('-----BEGIN CERTIFICATE-----')
|
||||
|
||||
expect(keyPrivatePem).to.include('-----BEGIN RSA PRIVATE KEY-----')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
context('.create', () => {
|
||||
it('returns a new CA instance', function () {
|
||||
expect(this.ca).to.be.an.instanceof(CA)
|
||||
})
|
||||
|
||||
it('creates certs + keys dir', function () {
|
||||
return Promise.join(
|
||||
fs.statAsync(path.join(this.dir, 'certs')),
|
||||
fs.statAsync(path.join(this.dir, 'keys')),
|
||||
)
|
||||
})
|
||||
|
||||
it('writes certs/ca.pem', function () {
|
||||
return fs.statAsync(path.join(this.dir, 'certs', 'ca.pem'))
|
||||
})
|
||||
|
||||
it('writes keys/ca.private.key', function () {
|
||||
return fs.statAsync(path.join(this.dir, 'keys', 'ca.private.key'))
|
||||
})
|
||||
|
||||
it('writes keys/ca.public.key', function () {
|
||||
return fs.statAsync(path.join(this.dir, 'keys', 'ca.public.key'))
|
||||
})
|
||||
|
||||
it('sets ca.CAcert', function () {
|
||||
expect(this.ca.CAcert).to.be.an('object')
|
||||
})
|
||||
|
||||
it('sets ca.CAkeys', function () {
|
||||
expect(this.ca.CAkeys).to.be.an('object')
|
||||
expect(this.ca.CAkeys).to.have.a.property('privateKey')
|
||||
|
||||
expect(this.ca.CAkeys).to.have.a.property('publicKey')
|
||||
})
|
||||
|
||||
describe('existing CA folder', () => {
|
||||
beforeEach(function () {
|
||||
this.sandbox.spy(CA.prototype, 'loadCA')
|
||||
this.generateCA = this.sandbox.spy(CA.prototype, 'generateCA')
|
||||
|
||||
return CA.create(this.dir)
|
||||
.then((ca2) => {
|
||||
this.ca2 = ca2
|
||||
})
|
||||
})
|
||||
|
||||
describe('CA versioning', () => {
|
||||
beforeEach(function () {
|
||||
this.removeAll = this.sandbox.spy(CA.prototype, 'removeAll')
|
||||
})
|
||||
|
||||
it('clears out CA folder with no ca_version.txt', function () {
|
||||
expect(this.generateCA).to.not.be.called
|
||||
|
||||
return fs.remove(path.join(this.dir, 'ca_version.txt'))
|
||||
.then(() => {
|
||||
return CA.create(this.dir)
|
||||
}).then(() => {
|
||||
expect(this.removeAll).to.be.calledOnce
|
||||
expect(this.generateCA).to.be.calledOnce
|
||||
})
|
||||
})
|
||||
|
||||
it('clears out CA folder with old ca_version', function () {
|
||||
expect(this.generateCA).to.not.be.called
|
||||
|
||||
return fs.outputFile(path.join(this.dir, 'ca_version.txt'), '0')
|
||||
.then(() => {
|
||||
return CA.create(this.dir)
|
||||
}).then(() => {
|
||||
expect(this.removeAll).to.be.calledOnce
|
||||
expect(this.generateCA).to.be.calledOnce
|
||||
})
|
||||
})
|
||||
|
||||
it('keeps CA folder with version of at least 1', function () {
|
||||
expect(this.generateCA).to.not.be.called
|
||||
|
||||
return fs.outputFile(path.join(this.dir, 'ca_version.txt'), '1')
|
||||
.then(() => {
|
||||
return CA.create(this.dir)
|
||||
}).then(() => {
|
||||
expect(this.removeAll).to.not.be.called
|
||||
expect(this.generateCA).to.not.be.called
|
||||
|
||||
return fs.outputFile(path.join(this.dir, 'ca_version.txt'), '100')
|
||||
}).then(() => {
|
||||
return CA.create(this.dir)
|
||||
}).then(() => {
|
||||
expect(this.removeAll).to.not.be.called
|
||||
expect(this.generateCA).to.not.be.called
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('calls loadCA and not generateCA', () => {
|
||||
expect(CA.prototype.loadCA).to.be.calledOnce
|
||||
|
||||
expect(CA.prototype.generateCA).not.to.be.called
|
||||
})
|
||||
|
||||
it('sets ca.CAcert', function () {
|
||||
expect(this.ca2.CAcert).to.be.an('object')
|
||||
})
|
||||
|
||||
it('sets ca.CAkeys', function () {
|
||||
expect(this.ca2.CAkeys).to.be.an('object')
|
||||
expect(this.ca2.CAkeys).to.have.a.property('privateKey')
|
||||
|
||||
expect(this.ca2.CAkeys).to.have.a.property('publicKey')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,117 @@
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
||||
import EE from 'events'
|
||||
import { create as createServer } from '../../lib/server'
|
||||
import { CA } from '../../lib/ca'
|
||||
import type { Server } from '../../lib/server'
|
||||
import type { Socket } from 'net'
|
||||
import type { IncomingMessage } from 'http'
|
||||
|
||||
vi.mock('../../lib/ca')
|
||||
|
||||
describe('lib/server', () => {
|
||||
let port: number
|
||||
let ca: CA
|
||||
let setup: (options?: any) => Promise<Server>
|
||||
|
||||
beforeEach(function () {
|
||||
vi.unstubAllEnvs()
|
||||
setup = async (options = {}) => {
|
||||
ca = await CA.create()
|
||||
port = 12345
|
||||
|
||||
return createServer(ca, port, options)
|
||||
}
|
||||
})
|
||||
|
||||
describe('#listen', () => {
|
||||
it('calls options.onUpgrade with req, socket head', async function () {
|
||||
const onUpgrade = vi.fn()
|
||||
|
||||
const srv = await setup({ onUpgrade })
|
||||
|
||||
srv._sniServer.emit('upgrade', 1, 2, 3)
|
||||
|
||||
expect(onUpgrade).toHaveBeenCalledWith(1, 2, 3)
|
||||
})
|
||||
|
||||
it('calls options.onRequest with req, res', async function () {
|
||||
const onRequest = vi.fn()
|
||||
const req = { url: 'https://www.foobar.com', headers: { host: 'www.foobar.com' } }
|
||||
const res = {}
|
||||
|
||||
const srv = await setup({ onRequest })
|
||||
|
||||
srv._sniServer.emit('request', req, res)
|
||||
|
||||
expect(onRequest).toHaveBeenCalledWith(req, res)
|
||||
})
|
||||
|
||||
it('calls options.onError with err and port and destroys the client socket', async function () {
|
||||
const socket = new EE() as Socket
|
||||
|
||||
socket.destroy = vi.fn()
|
||||
const head: Buffer = Buffer.from('')
|
||||
|
||||
return new Promise<void>(async (resolve) => {
|
||||
const onError = function (err: Error, socket2: Socket, head2: Buffer, port: string) {
|
||||
expect(err.message).toEqual('connect ECONNREFUSED 127.0.0.1:8444')
|
||||
|
||||
expect(socket).toEqual(socket2)
|
||||
expect(head).toEqual(head2)
|
||||
expect(port).toEqual('8444')
|
||||
|
||||
expect(socket.destroy).toHaveBeenCalledOnce()
|
||||
|
||||
resolve()
|
||||
}
|
||||
|
||||
const srv = await setup({ onError })
|
||||
|
||||
srv._makeConnection(socket, head, '8444', 'localhost')
|
||||
})
|
||||
})
|
||||
|
||||
// https://github.com/cypress-io/cypress/issues/3250
|
||||
it('does not crash when an erroneous URL is provided, just destroys socket', function () {
|
||||
const socket = new EE() as Socket
|
||||
|
||||
socket.destroy = vi.fn()
|
||||
const head: Buffer = Buffer.from('')
|
||||
|
||||
return new Promise<void>(async (resolve) => {
|
||||
const onError = function (err: Error, socket2: Socket, head2: Buffer, port: string) {
|
||||
expect(err.message).toEqual('getaddrinfo ENOTFOUND %7Balgolia_application_id%7D-dsn.algolia.net')
|
||||
|
||||
expect(socket).toEqual(socket2)
|
||||
expect(head).toEqual(head2)
|
||||
expect(port).toEqual('443')
|
||||
|
||||
expect(socket.destroy).toHaveBeenCalledOnce()
|
||||
|
||||
resolve()
|
||||
}
|
||||
|
||||
const srv = await setup({ onError })
|
||||
|
||||
srv._makeConnection(socket, head, '443', '%7Balgolia_application_id%7D-dsn.algolia.net')
|
||||
})
|
||||
})
|
||||
|
||||
// https://github.com/cypress-io/cypress/issues/9220
|
||||
it('does not crash when a blank URL is parsed and instead only destroys the socket', function () {
|
||||
const socket = new EE() as Socket
|
||||
|
||||
socket.destroy = vi.fn()
|
||||
const head: Buffer = Buffer.from('')
|
||||
|
||||
return new Promise<void>(async (resolve) => {
|
||||
const srv = await setup()
|
||||
|
||||
srv.connect({ url: '%20:443' } as IncomingMessage, socket, head)
|
||||
expect(socket.destroy).toHaveBeenCalledOnce()
|
||||
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,112 +0,0 @@
|
||||
const { expect } = require('../spec_helper')
|
||||
|
||||
const EE = require('events')
|
||||
const Server = require('../../lib/server')
|
||||
|
||||
describe('lib/server', () => {
|
||||
beforeEach(function () {
|
||||
this.setup = (options = {}) => {
|
||||
this.ca = {}
|
||||
this.port = 12345
|
||||
|
||||
return Server.create(this.ca, this.port, options)
|
||||
}
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
delete process.env.HTTPS_PROXY
|
||||
|
||||
delete process.env.NO_PROXY
|
||||
})
|
||||
|
||||
context('#listen', () => {
|
||||
it('calls options.onUpgrade with req, socket head', function () {
|
||||
const onUpgrade = this.sandbox.stub()
|
||||
|
||||
return this.setup({ onUpgrade })
|
||||
.then((srv) => {
|
||||
srv._sniServer.emit('upgrade', 1, 2, 3)
|
||||
|
||||
expect(onUpgrade).to.be.calledWith(1, 2, 3)
|
||||
})
|
||||
})
|
||||
|
||||
it('calls options.onRequest with req, res', function () {
|
||||
const onRequest = this.sandbox.stub()
|
||||
const req = { url: 'https://www.foobar.com', headers: { host: 'www.foobar.com' } }
|
||||
const res = {}
|
||||
|
||||
return this.setup({ onRequest })
|
||||
.then((srv) => {
|
||||
srv._sniServer.emit('request', req, res)
|
||||
|
||||
expect(onRequest).to.be.calledWith(req, res)
|
||||
})
|
||||
})
|
||||
|
||||
it('calls options.onError with err and port and destroys the client socket', function (done) {
|
||||
const socket = new EE()
|
||||
|
||||
socket.destroy = this.sandbox.stub()
|
||||
const head = {}
|
||||
|
||||
const onError = function (err, socket2, head2, port) {
|
||||
expect(err.message).to.eq('connect ECONNREFUSED 127.0.0.1:8444')
|
||||
|
||||
expect(socket).to.eq(socket2)
|
||||
expect(head).to.eq(head2)
|
||||
expect(port).to.eq('8444')
|
||||
|
||||
expect(socket.destroy).to.be.calledOnce
|
||||
|
||||
done()
|
||||
}
|
||||
|
||||
this.setup({ onError })
|
||||
.then((srv) => {
|
||||
srv._makeConnection(socket, head, '8444', 'localhost')
|
||||
})
|
||||
})
|
||||
|
||||
// https://github.com/cypress-io/cypress/issues/3250
|
||||
it('does not crash when an erroneous URL is provided, just destroys socket', function (done) {
|
||||
const socket = new EE()
|
||||
|
||||
socket.destroy = this.sandbox.stub()
|
||||
const head = {}
|
||||
|
||||
const onError = function (err, socket2, head2, port) {
|
||||
expect(err.message).to.eq('getaddrinfo ENOTFOUND %7Balgolia_application_id%7D-dsn.algolia.net')
|
||||
|
||||
expect(socket).to.eq(socket2)
|
||||
expect(head).to.eq(head2)
|
||||
expect(port).to.eq('443')
|
||||
|
||||
expect(socket.destroy).to.be.calledOnce
|
||||
|
||||
done()
|
||||
}
|
||||
|
||||
this.setup({ onError })
|
||||
.then((srv) => {
|
||||
srv._makeConnection(socket, head, '443', '%7Balgolia_application_id%7D-dsn.algolia.net')
|
||||
})
|
||||
})
|
||||
|
||||
// https://github.com/cypress-io/cypress/issues/9220
|
||||
it('does not crash when a blank URL is parsed and instead only destroys the socket', function (done) {
|
||||
const socket = new EE()
|
||||
|
||||
socket.destroy = this.sandbox.stub()
|
||||
const head = {}
|
||||
|
||||
this.setup()
|
||||
.then((srv) => {
|
||||
srv.connect({ url: '%20:443' }, socket, head)
|
||||
expect(socket.destroy).to.be.calledOnce
|
||||
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user