From 3ccb31cae2d413d71c444abb98d49620d6bd9dce Mon Sep 17 00:00:00 2001 From: decaffeinate Date: Tue, 21 Apr 2020 14:13:04 -0400 Subject: [PATCH 1/7] decaffeinate: Rename request.coffee and 1 other file from .coffee to .js --- packages/server/lib/{request.coffee => request.js} | 0 .../server/test/unit/{request_spec.coffee => request_spec.js} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename packages/server/lib/{request.coffee => request.js} (100%) rename packages/server/test/unit/{request_spec.coffee => request_spec.js} (100%) diff --git a/packages/server/lib/request.coffee b/packages/server/lib/request.js similarity index 100% rename from packages/server/lib/request.coffee rename to packages/server/lib/request.js diff --git a/packages/server/test/unit/request_spec.coffee b/packages/server/test/unit/request_spec.js similarity index 100% rename from packages/server/test/unit/request_spec.coffee rename to packages/server/test/unit/request_spec.js From aef667d71de05c43ea6fec11261c5e8c486f24a8 Mon Sep 17 00:00:00 2001 From: decaffeinate Date: Tue, 21 Apr 2020 14:13:10 -0400 Subject: [PATCH 2/7] decaffeinate: Convert request.coffee and 1 other file to JS --- packages/server/lib/request.js | 1152 ++++++++++---------- packages/server/test/unit/request_spec.js | 1168 +++++++++++---------- 2 files changed, 1231 insertions(+), 1089 deletions(-) diff --git a/packages/server/lib/request.js b/packages/server/lib/request.js index c900cf6baca1..1afdf843605e 100644 --- a/packages/server/lib/request.js +++ b/packages/server/lib/request.js @@ -1,126 +1,143 @@ -_ = require("lodash") -r = require("@cypress/request") -rp = require("@cypress/request-promise") -url = require("url") -tough = require("tough-cookie") -debug = require("debug")("cypress:server:request") -Promise = require("bluebird") -stream = require("stream") -duplexify = require("duplexify") -agent = require("@packages/network").agent -statusCode = require("./util/status_code") -streamBuffer = require("./util/stream_buffer").streamBuffer - -SERIALIZABLE_COOKIE_PROPS = ['name', 'value', 'domain', 'expiry', 'path', 'secure', 'hostOnly', 'httpOnly', 'sameSite'] -NETWORK_ERRORS = "ECONNREFUSED ECONNRESET EPIPE EHOSTUNREACH EAI_AGAIN ENOTFOUND".split(" ") -VERBOSE_REQUEST_OPTS = "followRedirect strictSSL".split(" ") -HTTP_CLIENT_REQUEST_EVENTS = "abort connect continue information socket timeout upgrade".split(" ") -TLS_VERSION_ERROR_RE = /TLSV1_ALERT_PROTOCOL_VERSION|UNSUPPORTED_PROTOCOL/ -SAMESITE_NONE_RE = /; +samesite=(?:'none'|"none"|none)/i - -process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0" - -convertSameSiteToughToExtension = (sameSite, setCookie) => - ## tough-cookie@4.0.0 uses 'none' as a default, so run this regex to detect if - ## SameSite=None was not explicitly set - ## @see https://github.com/salesforce/tough-cookie/issues/191 - isUnspecified = sameSite is "none" and !SAMESITE_NONE_RE.test(setCookie) - - if isUnspecified - ## not explicitly set, so fall back to the browser's default - return undefined - - if sameSite is 'none' - return 'no_restriction' - - return sameSite - -getOriginalHeaders = (req = {}) -> - ## the request instance holds an instance - ## of the original ClientRequest - ## as the 'req' property which holds the - ## original headers else fall back to - ## the normal req.headers - _.get(req, 'req.headers', req.headers) - -getDelayForRetry = (options = {}) -> - { err, opts, delaysRemaining, retryIntervals, onNext, onElse } = options - - delay = delaysRemaining.shift() - - if not _.isNumber(delay) - ## no more delays, bailing - debug("exhausted all attempts retrying request %o", merge(opts, { err })) - - return onElse() - - ## figure out which attempt we're on... - attempt = retryIntervals.length - delaysRemaining.length - - ## if this ECONNREFUSED and we are - ## retrying greater than 1 second - ## then divide the delay interval - ## by 10 so it doesn't wait as long to retry - ## TODO: do we really want to do this? - if delay >= 1000 and _.get(err, "code") is "ECONNREFUSED" - delay = delay / 10 +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * DS103: Rewrite code to no longer use __guard__ + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const _ = require("lodash"); +let r = require("@cypress/request"); +let rp = require("@cypress/request-promise"); +const url = require("url"); +const tough = require("tough-cookie"); +const debug = require("debug")("cypress:server:request"); +const Promise = require("bluebird"); +const stream = require("stream"); +const duplexify = require("duplexify"); +const { + agent +} = require("@packages/network"); +const statusCode = require("./util/status_code"); +const { + streamBuffer +} = require("./util/stream_buffer"); + +const SERIALIZABLE_COOKIE_PROPS = ['name', 'value', 'domain', 'expiry', 'path', 'secure', 'hostOnly', 'httpOnly', 'sameSite']; +const NETWORK_ERRORS = "ECONNREFUSED ECONNRESET EPIPE EHOSTUNREACH EAI_AGAIN ENOTFOUND".split(" "); +const VERBOSE_REQUEST_OPTS = "followRedirect strictSSL".split(" "); +const HTTP_CLIENT_REQUEST_EVENTS = "abort connect continue information socket timeout upgrade".split(" "); +const TLS_VERSION_ERROR_RE = /TLSV1_ALERT_PROTOCOL_VERSION|UNSUPPORTED_PROTOCOL/; +const SAMESITE_NONE_RE = /; +samesite=(?:'none'|"none"|none)/i; + +process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"; + +const convertSameSiteToughToExtension = (sameSite, setCookie) => { + //# tough-cookie@4.0.0 uses 'none' as a default, so run this regex to detect if + //# SameSite=None was not explicitly set + //# @see https://github.com/salesforce/tough-cookie/issues/191 + const isUnspecified = (sameSite === "none") && !SAMESITE_NONE_RE.test(setCookie); + + if (isUnspecified) { + //# not explicitly set, so fall back to the browser's default + return undefined; + } + + if (sameSite === 'none') { + return 'no_restriction'; + } + + return sameSite; +}; + +const getOriginalHeaders = (req = {}) => //# the request instance holds an instance +//# of the original ClientRequest +//# as the 'req' property which holds the +//# original headers else fall back to +//# the normal req.headers +_.get(req, 'req.headers', req.headers); + +const getDelayForRetry = function(options = {}) { + const { err, opts, delaysRemaining, retryIntervals, onNext, onElse } = options; + + let delay = delaysRemaining.shift(); + + if (!_.isNumber(delay)) { + //# no more delays, bailing + debug("exhausted all attempts retrying request %o", merge(opts, { err })); + + return onElse(); + } + + //# figure out which attempt we're on... + const attempt = retryIntervals.length - delaysRemaining.length; + + //# if this ECONNREFUSED and we are + //# retrying greater than 1 second + //# then divide the delay interval + //# by 10 so it doesn't wait as long to retry + //# TODO: do we really want to do this? + if ((delay >= 1000) && (_.get(err, "code") === "ECONNREFUSED")) { + delay = delay / 10; + } debug("retrying request %o", merge(opts, { delay, attempt, - })) + })); - return onNext(delay, attempt) + return onNext(delay, attempt); +}; -hasRetriableStatusCodeFailure = (res, retryOnStatusCodeFailure) -> - ## everything must be true in order to - ## retry a status code failure - _.every([ - retryOnStatusCodeFailure, - !statusCode.isOk(res.statusCode) - ]) +const hasRetriableStatusCodeFailure = (res, retryOnStatusCodeFailure) => //# everything must be true in order to +//# retry a status code failure +_.every([ + retryOnStatusCodeFailure, + !statusCode.isOk(res.statusCode) +]); -isRetriableError = (err = {}, retryOnNetworkFailure) -> - _.every([ - retryOnNetworkFailure, - _.includes(NETWORK_ERRORS, err.code) - ]) +const isRetriableError = (err = {}, retryOnNetworkFailure) => _.every([ + retryOnNetworkFailure, + _.includes(NETWORK_ERRORS, err.code) +]); -maybeRetryOnNetworkFailure = (err, options = {}) -> - { +const maybeRetryOnNetworkFailure = function(err, options = {}) { + const { opts, retryIntervals, delaysRemaining, retryOnNetworkFailure, onNext, onElse, - } = options + } = options; - debug("received an error making http request %o", merge(opts, { err })) + debug("received an error making http request %o", merge(opts, { err })); - isTlsVersionError = TLS_VERSION_ERROR_RE.test(err.message) + const isTlsVersionError = TLS_VERSION_ERROR_RE.test(err.message); - if isTlsVersionError - ## because doing every connection via TLSv1 can lead to slowdowns, we set it only on failure - ## https://github.com/cypress-io/cypress/pull/6705 - debug('detected TLS version error, setting min version to TLSv1') - opts.minVersion = 'TLSv1' + if (isTlsVersionError) { + //# because doing every connection via TLSv1 can lead to slowdowns, we set it only on failure + //# https://github.com/cypress-io/cypress/pull/6705 + debug('detected TLS version error, setting min version to TLSv1'); + opts.minVersion = 'TLSv1'; + } - if not isTlsVersionError and not isRetriableError(err, retryOnNetworkFailure) - return onElse() + if (!isTlsVersionError && !isRetriableError(err, retryOnNetworkFailure)) { + return onElse(); + } - ## else see if we have more delays left... - getDelayForRetry({ + //# else see if we have more delays left... + return getDelayForRetry({ err, opts, retryIntervals, delaysRemaining, onNext, onElse, - }) + }); +}; -maybeRetryOnStatusCodeFailure = (res, options = {}) -> - { +const maybeRetryOnStatusCodeFailure = function(res, options = {}) { + const { err, opts, requestId, @@ -129,540 +146,601 @@ maybeRetryOnStatusCodeFailure = (res, options = {}) -> retryOnStatusCodeFailure, onNext, onElse, - } = options + } = options; debug("received status code & headers on request %o", { requestId, statusCode: res.statusCode, headers: _.pick(res.headers, 'content-type', 'set-cookie', 'location') - }) + }); - ## is this a retryable status code failure? - if not hasRetriableStatusCodeFailure(res, retryOnStatusCodeFailure) - ## if not then we're done here - return onElse() + //# is this a retryable status code failure? + if (!hasRetriableStatusCodeFailure(res, retryOnStatusCodeFailure)) { + //# if not then we're done here + return onElse(); + } - ## else see if we have more delays left... - getDelayForRetry({ + //# else see if we have more delays left... + return getDelayForRetry({ err, opts, retryIntervals, delaysRemaining, onNext, onElse, - }) + }); +}; -merge = (args...) -> - _.chain({}) - .extend(args...) - .omit(VERBOSE_REQUEST_OPTS) - .value() +var merge = (...args) => _.chain({}) +.extend(...args) +.omit(VERBOSE_REQUEST_OPTS) +.value(); -pick = (resp = {}) -> - req = resp.request ? {} +const pick = function(resp = {}) { + const req = resp.request != null ? resp.request : {}; - headers = getOriginalHeaders(req) + const headers = getOriginalHeaders(req); - { - "Request Body": req.body ? null - "Request Headers": headers - "Request URL": req.href - "Response Body": resp.body ? null - "Response Headers": resp.headers + return { + "Request Body": req.body != null ? req.body : null, + "Request Headers": headers, + "Request URL": req.href, + "Response Body": resp.body != null ? resp.body : null, + "Response Headers": resp.headers, "Response Status": resp.statusCode - } + }; +}; -createRetryingRequestPromise = (opts) -> - { +var createRetryingRequestPromise = function(opts) { + const { requestId, retryIntervals, delaysRemaining, retryOnNetworkFailure, retryOnStatusCodeFailure - } = opts + } = opts; - retry = (delay) -> - return Promise.delay(delay) - .then -> - createRetryingRequestPromise(opts) + const retry = delay => Promise.delay(delay) + .then(() => createRetryingRequestPromise(opts)); return rp(opts) - .catch (err) -> - - ## rp wraps network errors in a RequestError, so might need to unwrap it to check - maybeRetryOnNetworkFailure(err.error or err, { - opts, - retryIntervals, - delaysRemaining, - retryOnNetworkFailure, - onNext: retry - onElse: -> - throw err - }) - .then (res) -> - ## ok, no net error, but what about a bad status code? - maybeRetryOnStatusCodeFailure(res, { - opts, - requestId, - retryIntervals, - delaysRemaining, - retryOnStatusCodeFailure, - onNext: retry - onElse: _.constant(res) - }) - -pipeEvent = (source, destination, event) -> - source.on event, (args...) -> - destination.emit(event, args...) - -createRetryingRequestStream = (opts = {}) -> - { + .catch(err => //# rp wraps network errors in a RequestError, so might need to unwrap it to check + maybeRetryOnNetworkFailure(err.error || err, { + opts, + retryIntervals, + delaysRemaining, + retryOnNetworkFailure, + onNext: retry, + onElse() { + throw err; + } + })).then(res => //# ok, no net error, but what about a bad status code? + maybeRetryOnStatusCodeFailure(res, { + opts, + requestId, + retryIntervals, + delaysRemaining, + retryOnStatusCodeFailure, + onNext: retry, + onElse: _.constant(res) + })); +}; + +const pipeEvent = (source, destination, event) => source.on(event, (...args) => destination.emit(event, ...args)); + +const createRetryingRequestStream = function(opts = {}) { + const { requestId, retryIntervals, delaysRemaining, retryOnNetworkFailure, retryOnStatusCodeFailure - } = opts - - req = null - - delayStream = stream.PassThrough() - reqBodyBuffer = streamBuffer() - retryStream = duplexify(reqBodyBuffer, delayStream) - - cleanup = -> - if reqBodyBuffer - ## null req body out to free memory - reqBodyBuffer.unpipeAll() - reqBodyBuffer = null - - emitError = (err) -> - retryStream.emit("error", err) - - cleanup() - - tryStartStream = -> - ## if our request has been aborted - ## in the time that we were waiting to retry - ## then immediately bail - if retryStream.aborted - return - - reqStream = r(opts) - didReceiveResponse = false - - retry = (delay, attempt) -> - retryStream.emit("retry", { attempt, delay }) - - setTimeout(tryStartStream, delay) - - ## if we're retrying and we previous piped - ## into the reqStream, then reapply this now - if req - reqStream.emit('pipe', req) - reqBodyBuffer.createReadStream().pipe(reqStream) - - ## forward the abort call to the underlying request - retryStream.abort = -> - debug('aborting', { requestId }) - retryStream.aborted = true - - reqStream.abort() - - onPiped = (src) -> - ## store this IncomingMessage so we can reapply it - ## if we need to retry - req = src - - ## https://github.com/request/request/blob/b3a218dc7b5689ce25be171e047f0d4f0eef8919/request.js#L493 - ## the request lib expects this 'pipe' event in - ## order to copy the request headers onto the - ## outgoing message - so we manually pipe it here - src.pipe(reqStream) - - ## when this passthrough stream is being piped into - ## then make sure we properly "forward" and connect - ## forward it to the real reqStream which enables - ## request to read off the IncomingMessage readable stream - retryStream.once("pipe", onPiped) - - reqStream.on "error", (err) -> - if didReceiveResponse - ## if we've already begun processing the requests - ## response, then that means we failed during transit - ## and its no longer safe to retry. all we can do now - ## is propogate the error upwards - debug("received an error on request after response started %o", merge(opts, { err })) - - return emitError(err) - - ## otherwise, see if we can retry another request under the hood... - maybeRetryOnNetworkFailure(err, { + } = opts; + + let req = null; + + const delayStream = stream.PassThrough(); + let reqBodyBuffer = streamBuffer(); + const retryStream = duplexify(reqBodyBuffer, delayStream); + + const cleanup = function() { + if (reqBodyBuffer) { + //# null req body out to free memory + reqBodyBuffer.unpipeAll(); + return reqBodyBuffer = null; + } + }; + + const emitError = function(err) { + retryStream.emit("error", err); + + return cleanup(); + }; + + var tryStartStream = function() { + //# if our request has been aborted + //# in the time that we were waiting to retry + //# then immediately bail + if (retryStream.aborted) { + return; + } + + const reqStream = r(opts); + let didReceiveResponse = false; + + const retry = function(delay, attempt) { + retryStream.emit("retry", { attempt, delay }); + + return setTimeout(tryStartStream, delay); + }; + + //# if we're retrying and we previous piped + //# into the reqStream, then reapply this now + if (req) { + reqStream.emit('pipe', req); + reqBodyBuffer.createReadStream().pipe(reqStream); + } + + //# forward the abort call to the underlying request + retryStream.abort = function() { + debug('aborting', { requestId }); + retryStream.aborted = true; + + return reqStream.abort(); + }; + + const onPiped = function(src) { + //# store this IncomingMessage so we can reapply it + //# if we need to retry + req = src; + + //# https://github.com/request/request/blob/b3a218dc7b5689ce25be171e047f0d4f0eef8919/request.js#L493 + //# the request lib expects this 'pipe' event in + //# order to copy the request headers onto the + //# outgoing message - so we manually pipe it here + return src.pipe(reqStream); + }; + + //# when this passthrough stream is being piped into + //# then make sure we properly "forward" and connect + //# forward it to the real reqStream which enables + //# request to read off the IncomingMessage readable stream + retryStream.once("pipe", onPiped); + + reqStream.on("error", function(err) { + if (didReceiveResponse) { + //# if we've already begun processing the requests + //# response, then that means we failed during transit + //# and its no longer safe to retry. all we can do now + //# is propogate the error upwards + debug("received an error on request after response started %o", merge(opts, { err })); + + return emitError(err); + } + + //# otherwise, see if we can retry another request under the hood... + return maybeRetryOnNetworkFailure(err, { opts, retryIntervals, delaysRemaining, retryOnNetworkFailure, - onNext: retry - onElse: -> - emitError(err) - }) - - reqStream.once "request", (req) -> - ## remove the pipe listener since once the request has - ## been made, we cannot pipe into the reqStream anymore - retryStream.removeListener("pipe", onPiped) - - reqStream.once "response", (incomingRes) -> - didReceiveResponse = true - - ## ok, no net error, but what about a bad status code? - maybeRetryOnStatusCodeFailure(incomingRes, { + onNext: retry, + onElse() { + return emitError(err); + } + }); + }); + + reqStream.once("request", req => //# remove the pipe listener since once the request has + //# been made, we cannot pipe into the reqStream anymore + retryStream.removeListener("pipe", onPiped)); + + return reqStream.once("response", function(incomingRes) { + didReceiveResponse = true; + + //# ok, no net error, but what about a bad status code? + return maybeRetryOnStatusCodeFailure(incomingRes, { opts, requestId, delaysRemaining, retryIntervals, retryOnStatusCodeFailure, - onNext: retry - onElse: -> - debug("successful response received", { requestId }) - - cleanup() - - ## forward the response event upwards which should happen - ## prior to the pipe event, same as what request does - ## https://github.com/request/request/blob/master/request.js#L1059 - retryStream.emit("response", incomingRes) - - reqStream.pipe(delayStream) - - ## `http.ClientRequest` events - _.map(HTTP_CLIENT_REQUEST_EVENTS, _.partial(pipeEvent, reqStream, retryStream)) - }) - - tryStartStream() - - return retryStream - -caseInsensitiveGet = (obj, property) -> - lowercaseProperty = property.toLowerCase() - - for key in Object.keys(obj) - if key.toLowerCase() == lowercaseProperty - return obj[key] - -## first, attempt to set on an existing property with differing case -## if that fails, set the lowercase `property` -caseInsensitiveSet = (obj, property, val) -> - lowercaseProperty = property.toLowerCase() - - for key in Object.keys(obj) - if key.toLowerCase() == lowercaseProperty - return obj[key] = val - - obj[lowercaseProperty] = val - -setDefaults = (opts) -> - _ - .chain(opts) - .defaults({ - requestId: _.uniqueId('request') - retryIntervals: [0, 1000, 2000, 2000] - retryOnNetworkFailure: true - retryOnStatusCodeFailure: false - }) - .thru (opts) -> - _.defaults(opts, { - delaysRemaining: _.clone(opts.retryIntervals) - }) - .value() - -module.exports = (options = {}) -> - defaults = { - timeout: options.timeout - agent: agent - ## send keep-alive with requests since Chrome won't send it in proxy mode - ## https://github.com/cypress-io/cypress/pull/3531#issuecomment-476269041 - headers: { - "Connection": "keep-alive" - } - proxy: null ## upstream proxying is handled by CombinedAgent - } + onNext: retry, + onElse() { + debug("successful response received", { requestId }); - r = r.defaults(defaults) - rp = rp.defaults(defaults) + cleanup(); - return { - r: require("@cypress/request") + //# forward the response event upwards which should happen + //# prior to the pipe event, same as what request does + //# https://github.com/request/request/blob/master/request.js#L1059 + retryStream.emit("response", incomingRes); - rp: require("@cypress/request-promise") + reqStream.pipe(delayStream); - getDelayForRetry + //# `http.ClientRequest` events + return _.map(HTTP_CLIENT_REQUEST_EVENTS, _.partial(pipeEvent, reqStream, retryStream)); + } + }); + }); + }; - setDefaults + tryStartStream(); - create: (strOrOpts, promise) -> - switch - when _.isString(strOrOpts) - opts = { - url: strOrOpts - } - else - opts = strOrOpts - - opts = setDefaults(opts) - - if promise - createRetryingRequestPromise(opts) - else - createRetryingRequestStream(opts) + return retryStream; +}; - contentTypeIsJson: (response) -> - ## TODO: use https://github.com/jshttp/type-is for this - ## https://github.com/cypress-io/cypress/pull/5166 - response?.headers?["content-type"]?.split(';', 2)[0].endsWith("json") +const caseInsensitiveGet = function(obj, property) { + const lowercaseProperty = property.toLowerCase(); - parseJsonBody: (body) -> - try - JSON.parse(body) - catch e - body + for (let key of Object.keys(obj)) { + if (key.toLowerCase() === lowercaseProperty) { + return obj[key]; + } + } +}; - normalizeResponse: (push, response) -> - req = response.request ? {} +//# first, attempt to set on an existing property with differing case +//# if that fails, set the lowercase `property` +const caseInsensitiveSet = function(obj, property, val) { + const lowercaseProperty = property.toLowerCase(); - push(response) + for (let key of Object.keys(obj)) { + if (key.toLowerCase() === lowercaseProperty) { + return obj[key] = val; + } + } - response = _.pick(response, "statusCode", "body", "headers") + return obj[lowercaseProperty] = val; +}; + +const setDefaults = opts => _ +.chain(opts) +.defaults({ + requestId: _.uniqueId('request'), + retryIntervals: [0, 1000, 2000, 2000], + retryOnNetworkFailure: true, + retryOnStatusCodeFailure: false +}) +.thru(opts => _.defaults(opts, { + delaysRemaining: _.clone(opts.retryIntervals) +})).value(); + +module.exports = function(options = {}) { + const defaults = { + timeout: options.timeout, + agent, + //# send keep-alive with requests since Chrome won't send it in proxy mode + //# https://github.com/cypress-io/cypress/pull/3531#issuecomment-476269041 + headers: { + "Connection": "keep-alive" + }, + proxy: null //# upstream proxying is handled by CombinedAgent + }; - ## normalize status - response.status = response.statusCode - delete response.statusCode + r = r.defaults(defaults); + rp = rp.defaults(defaults); - _.extend(response, { - ## normalize what is an ok status code - statusText: statusCode.getText(response.status) - isOkStatusCode: statusCode.isOk(response.status) - requestHeaders: getOriginalHeaders(req) - requestBody: req.body - }) + return { + r: require("@cypress/request"), - ## if body is a string and content type is json - ## try to convert the body to JSON - if _.isString(response.body) and @contentTypeIsJson(response) - response.body = @parseJsonBody(response.body) + rp: require("@cypress/request-promise"), - return response + getDelayForRetry, - setRequestCookieHeader: (req, reqUrl, automationFn, existingHeader) -> - automationFn('get:cookies', { url: reqUrl }) - .then (cookies) -> - debug('got cookies from browser %o', { reqUrl, cookies }) - header = cookies.map (cookie) -> - "#{cookie.name}=#{cookie.value}" - .join("; ") || undefined + setDefaults, - if header - if existingHeader - ## existingHeader = whatever Cookie header the user is already trying to set - debug('there is an existing cookie header, merging %o', { header, existingHeader }) - ## order does not not matter here - ## @see https://tools.ietf.org/html/rfc6265#section-4.2.2 - header = [existingHeader, header].join(';') + create(strOrOpts, promise) { + let opts; + switch (false) { + case !_.isString(strOrOpts): + opts = { + url: strOrOpts + }; + break; + default: + opts = strOrOpts; + } - caseInsensitiveSet(req.headers, 'Cookie', header) + opts = setDefaults(opts); - setCookiesOnBrowser: (res, resUrl, automationFn) -> - cookies = res.headers['set-cookie'] - if !cookies - return Promise.resolve() + if (promise) { + return createRetryingRequestPromise(opts); + } else { + return createRetryingRequestStream(opts); + } + }, + + contentTypeIsJson(response) { + //# TODO: use https://github.com/jshttp/type-is for this + //# https://github.com/cypress-io/cypress/pull/5166 + return __guard__(__guard__(response != null ? response.headers : undefined, x1 => x1["content-type"]), x => x.split(';', 2)[0].endsWith("json")); + }, + + parseJsonBody(body) { + try { + return JSON.parse(body); + } catch (e) { + return body; + } + }, - if !(cookies instanceof Array) - cookies = [cookies] + normalizeResponse(push, response) { + const req = response.request != null ? response.request : {}; - parsedUrl = url.parse(resUrl) - defaultDomain = parsedUrl.hostname + push(response); - debug('setting cookies on browser %o', { url: parsedUrl.href, defaultDomain, cookies }) + response = _.pick(response, "statusCode", "body", "headers"); - Promise.map cookies, (cyCookie) -> - cookie = tough.Cookie.parse(cyCookie, { loose: true }) + //# normalize status + response.status = response.statusCode; + delete response.statusCode; - debug('parsing cookie %o', { cyCookie, toughCookie: cookie }) + _.extend(response, { + //# normalize what is an ok status code + statusText: statusCode.getText(response.status), + isOkStatusCode: statusCode.isOk(response.status), + requestHeaders: getOriginalHeaders(req), + requestBody: req.body + }); - if not cookie - ## ignore invalid cookies (same as browser behavior) - ## https://github.com/cypress-io/cypress/issues/6890 - debug('tough-cookie failed to parse, ignoring') - return + //# if body is a string and content type is json + //# try to convert the body to JSON + if (_.isString(response.body) && this.contentTypeIsJson(response)) { + response.body = this.parseJsonBody(response.body); + } - cookie.name = cookie.key + return response; + }, + + setRequestCookieHeader(req, reqUrl, automationFn, existingHeader) { + return automationFn('get:cookies', { url: reqUrl }) + .then(function(cookies) { + debug('got cookies from browser %o', { reqUrl, cookies }); + let header = cookies.map(cookie => `${cookie.name}=${cookie.value}`).join("; ") || undefined; + + if (header) { + if (existingHeader) { + //# existingHeader = whatever Cookie header the user is already trying to set + debug('there is an existing cookie header, merging %o', { header, existingHeader }); + //# order does not not matter here + //# @see https://tools.ietf.org/html/rfc6265#section-4.2.2 + header = [existingHeader, header].join(';'); + } - if not cookie.domain - ## take the domain from the URL - cookie.domain = defaultDomain - cookie.hostOnly = true + return caseInsensitiveSet(req.headers, 'Cookie', header); + } + }); + }, - if not tough.domainMatch(defaultDomain, cookie.domain) - debug('domain match failed:', { defaultDomain }) - return + setCookiesOnBrowser(res, resUrl, automationFn) { + let cookies = res.headers['set-cookie']; + if (!cookies) { + return Promise.resolve(); + } - expiry = cookie.expiryTime() - if isFinite(expiry) - cookie.expiry = expiry / 1000 + if (!(cookies instanceof Array)) { + cookies = [cookies]; + } - cookie.sameSite = convertSameSiteToughToExtension(cookie.sameSite, cyCookie) + const parsedUrl = url.parse(resUrl); + const defaultDomain = parsedUrl.hostname; - cookie = _.pick(cookie, SERIALIZABLE_COOKIE_PROPS) + debug('setting cookies on browser %o', { url: parsedUrl.href, defaultDomain, cookies }); - automationCmd = 'set:cookie' + return Promise.map(cookies, function(cyCookie) { + let cookie = tough.Cookie.parse(cyCookie, { loose: true }); - if expiry <= 0 - automationCmd = 'clear:cookie' + debug('parsing cookie %o', { cyCookie, toughCookie: cookie }); - automationFn(automationCmd, cookie) - .catch (err) -> - debug('automation threw an error during cookie change %o', { automationCmd, cyCookie, cookie, err }) + if (!cookie) { + //# ignore invalid cookies (same as browser behavior) + //# https://github.com/cypress-io/cypress/issues/6890 + debug('tough-cookie failed to parse, ignoring'); + return; + } - sendStream: (headers, automationFn, options = {}) -> - _.defaults options, { - headers: {} - onBeforeReqInit: (fn) -> fn() - } + cookie.name = cookie.key; - if not caseInsensitiveGet(options.headers, "user-agent") and (ua = headers["user-agent"]) - options.headers["user-agent"] = ua + if (!cookie.domain) { + //# take the domain from the URL + cookie.domain = defaultDomain; + cookie.hostOnly = true; + } - _.extend options, { - strictSSL: false - } + if (!tough.domainMatch(defaultDomain, cookie.domain)) { + debug('domain match failed:', { defaultDomain }); + return; + } - self = @ + const expiry = cookie.expiryTime(); + if (isFinite(expiry)) { + cookie.expiry = expiry / 1000; + } - followRedirect = options.followRedirect + cookie.sameSite = convertSameSiteToughToExtension(cookie.sameSite, cyCookie); - currentUrl = options.url + cookie = _.pick(cookie, SERIALIZABLE_COOKIE_PROPS); - options.followRedirect = (incomingRes) -> - if followRedirect and not followRedirect(incomingRes) - return false + let automationCmd = 'set:cookie'; - newUrl = url.resolve(currentUrl, incomingRes.headers.location) + if (expiry <= 0) { + automationCmd = 'clear:cookie'; + } - ## and when we know we should follow the redirect - ## we need to override the init method and - ## first set the received cookies on the browser - ## and then grab the cookies for the new url - self.setCookiesOnBrowser(incomingRes, currentUrl, automationFn) - .then (cookies) => - self.setRequestCookieHeader(@, newUrl, automationFn) - .then => - currentUrl = newUrl - true + return automationFn(automationCmd, cookie) + .catch(err => debug('automation threw an error during cookie change %o', { automationCmd, cyCookie, cookie, err })); + }); + }, - @setRequestCookieHeader(options, options.url, automationFn, caseInsensitiveGet(options.headers, 'cookie')) - .then => - return => - debug("sending request as stream %o", merge(options)) + sendStream(headers, automationFn, options = {}) { + let ua; + _.defaults(options, { + headers: {}, + onBeforeReqInit(fn) { return fn(); } + }); - @create(options) + if (!caseInsensitiveGet(options.headers, "user-agent") && (ua = headers["user-agent"])) { + options.headers["user-agent"] = ua; + } - sendPromise: (headers, automationFn, options = {}) -> - _.defaults options, { - headers: {} - gzip: true - cookies: true + _.extend(options, { + strictSSL: false + }); + + const self = this; + + const { + followRedirect + } = options; + + let currentUrl = options.url; + + options.followRedirect = function(incomingRes) { + if (followRedirect && !followRedirect(incomingRes)) { + return false; + } + + const newUrl = url.resolve(currentUrl, incomingRes.headers.location); + + //# and when we know we should follow the redirect + //# we need to override the init method and + //# first set the received cookies on the browser + //# and then grab the cookies for the new url + return self.setCookiesOnBrowser(incomingRes, currentUrl, automationFn) + .then(cookies => { + return self.setRequestCookieHeader(this, newUrl, automationFn); + }).then(() => { + currentUrl = newUrl; + return true; + }); + }; + + return this.setRequestCookieHeader(options, options.url, automationFn, caseInsensitiveGet(options.headers, 'cookie')) + .then(() => { + return () => { + debug("sending request as stream %o", merge(options)); + + return this.create(options); + }; + }); + }, + + sendPromise(headers, automationFn, options = {}) { + let a, c, ua; + _.defaults(options, { + headers: {}, + gzip: true, + cookies: true, followRedirect: true - } + }); - if not caseInsensitiveGet(options.headers, "user-agent") and (ua = headers["user-agent"]) - options.headers["user-agent"] = ua + if (!caseInsensitiveGet(options.headers, "user-agent") && (ua = headers["user-agent"])) { + options.headers["user-agent"] = ua; + } - ## normalize case sensitivity - ## to be lowercase - if a = options.headers.Accept - delete options.headers.Accept - options.headers.accept = a + //# normalize case sensitivity + //# to be lowercase + if (a = options.headers.Accept) { + delete options.headers.Accept; + options.headers.accept = a; + } - ## https://github.com/cypress-io/cypress/issues/338 + //# https://github.com/cypress-io/cypress/issues/338 _.defaults(options.headers, { accept: "*/*" - }) + }); _.extend(options, { - strictSSL: false - simple: false + strictSSL: false, + simple: false, resolveWithFullResponse: true - }) - - ## https://github.com/cypress-io/cypress/issues/322 - ## either turn these both on or off - options.followAllRedirects = options.followRedirect - - if options.form is true - ## reset form to whatever body is - ## and nuke body - options.form = options.body - delete options.json - delete options.body - - self = @ - - send = => - ms = Date.now() - - redirects = [] - requestResponses = [] - - push = (response) -> - requestResponses.push(pick(response)) - - currentUrl = options.url - - if options.followRedirect - options.followRedirect = (incomingRes) -> - newUrl = url.resolve(currentUrl, incomingRes.headers.location) - - ## normalize the url - redirects.push([incomingRes.statusCode, newUrl].join(": ")) - - push(incomingRes) - - ## and when we know we should follow the redirect - ## we need to override the init method and - ## first set the new cookies on the browser - ## and then grab the cookies for the new url - self.setCookiesOnBrowser(incomingRes, currentUrl, automationFn) - .then => - self.setRequestCookieHeader(@, newUrl, automationFn) - .then => - currentUrl = newUrl - true - - @create(options, true) - .then(@normalizeResponse.bind(@, push)) - .then (resp) => - ## TODO: move duration somewhere...? - ## does node store this somewhere? - ## we could probably calculate this ourselves - ## by using the date headers - resp.duration = Date.now() - ms - resp.allRequestResponses = requestResponses - - if redirects.length - resp.redirects = redirects - - if options.followRedirect is false and (loc = resp.headers.location) - ## resolve the new location head against - ## the current url - resp.redirectedToUrl = url.resolve(options.url, loc) - - @setCookiesOnBrowser(resp, currentUrl, automationFn) - .return(resp) - - if c = options.cookies - self.setRequestCookieHeader(options, options.url, automationFn, caseInsensitiveGet(options.headers, 'cookie')) - .then(send) - else - send() + }); + + //# https://github.com/cypress-io/cypress/issues/322 + //# either turn these both on or off + options.followAllRedirects = options.followRedirect; + + if (options.form === true) { + //# reset form to whatever body is + //# and nuke body + options.form = options.body; + delete options.json; + delete options.body; + } - } + const self = this; + + const send = () => { + const ms = Date.now(); + + const redirects = []; + const requestResponses = []; + + const push = response => requestResponses.push(pick(response)); + + let currentUrl = options.url; + + if (options.followRedirect) { + options.followRedirect = function(incomingRes) { + const newUrl = url.resolve(currentUrl, incomingRes.headers.location); + + //# normalize the url + redirects.push([incomingRes.statusCode, newUrl].join(": ")); + + push(incomingRes); + + //# and when we know we should follow the redirect + //# we need to override the init method and + //# first set the new cookies on the browser + //# and then grab the cookies for the new url + return self.setCookiesOnBrowser(incomingRes, currentUrl, automationFn) + .then(() => { + return self.setRequestCookieHeader(this, newUrl, automationFn); + }).then(() => { + currentUrl = newUrl; + return true; + }); + }; + } + + return this.create(options, true) + .then(this.normalizeResponse.bind(this, push)) + .then(resp => { + //# TODO: move duration somewhere...? + //# does node store this somewhere? + //# we could probably calculate this ourselves + //# by using the date headers + let loc; + resp.duration = Date.now() - ms; + resp.allRequestResponses = requestResponses; + + if (redirects.length) { + resp.redirects = redirects; + } + + if ((options.followRedirect === false) && (loc = resp.headers.location)) { + //# resolve the new location head against + //# the current url + resp.redirectedToUrl = url.resolve(options.url, loc); + } + + return this.setCookiesOnBrowser(resp, currentUrl, automationFn) + .return(resp); + }); + }; + + if ((c = options.cookies)) { + return self.setRequestCookieHeader(options, options.url, automationFn, caseInsensitiveGet(options.headers, 'cookie')) + .then(send); + } else { + return send(); + } + } + + }; +}; + +function __guard__(value, transform) { + return (typeof value !== 'undefined' && value !== null) ? transform(value) : undefined; +} \ No newline at end of file diff --git a/packages/server/test/unit/request_spec.js b/packages/server/test/unit/request_spec.js index 756829e8490c..7257b5e28a87 100644 --- a/packages/server/test/unit/request_spec.js +++ b/packages/server/test/unit/request_spec.js @@ -1,371 +1,397 @@ -require("../spec_helper") +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +require("../spec_helper"); -_ = require("lodash") -http = require("http") -Bluebird = require("bluebird") -Request = require("#{root}lib/request") -snapshot = require("snap-shot-it") +const _ = require("lodash"); +const http = require("http"); +const Bluebird = require("bluebird"); +const Request = require(`${root}lib/request`); +const snapshot = require("snap-shot-it"); -request = Request({timeout: 100}) +const request = Request({timeout: 100}); -testAttachingCookiesWith = (fn) -> - set = sinon.spy(request, 'setCookiesOnBrowser') - get = sinon.spy(request, 'setRequestCookieHeader') +const testAttachingCookiesWith = function(fn) { + const set = sinon.spy(request, 'setCookiesOnBrowser'); + const get = sinon.spy(request, 'setRequestCookieHeader'); nock("http://localhost:1234") .get("/") .reply(302, "", { - 'set-cookie': 'one=1' + 'set-cookie': 'one=1', location: "/second" }) .get("/second") .reply(302, "", { - 'set-cookie': 'two=2' + 'set-cookie': 'two=2', location: "/third" }) .get("/third") .reply(200, "", { 'set-cookie': 'three=3' - }) - - fn() - .then -> - snapshot({ - setCalls: set.getCalls().map (call) -> - { - currentUrl: call.args[1], - setCookie: call.args[0].headers['set-cookie'] - } - getCalls: get.getCalls().map (call) -> - { - newUrl: _.get(call, 'args.1') - } - }) - -describe "lib/request", -> - beforeEach -> - @fn = sinon.stub() - @fn.withArgs('set:cookie').resolves({}) - @fn.withArgs('get:cookies').resolves([]) - - it "is defined", -> - expect(request).to.be.an("object") - - context "#getDelayForRetry", -> - it "divides by 10 when delay >= 1000 and err.code = ECONNREFUSED", -> - retryIntervals = [1,2,3,4] - delaysRemaining = [0, 999, 1000, 2000] - - err = { + }); + + return fn() + .then(() => snapshot({ + setCalls: set.getCalls().map(call => ({ + currentUrl: call.args[1], + setCookie: call.args[0].headers['set-cookie'] + })), + getCalls: get.getCalls().map(call => ({ + newUrl: _.get(call, 'args.1') + })) + })); +}; + +describe("lib/request", function() { + beforeEach(function() { + this.fn = sinon.stub(); + this.fn.withArgs('set:cookie').resolves({}); + return this.fn.withArgs('get:cookies').resolves([]); + }); + + it("is defined", () => expect(request).to.be.an("object")); + + context("#getDelayForRetry", function() { + it("divides by 10 when delay >= 1000 and err.code = ECONNREFUSED", function() { + const retryIntervals = [1,2,3,4]; + const delaysRemaining = [0, 999, 1000, 2000]; + + const err = { code: "ECONNREFUSED" - } + }; - onNext = sinon.stub() + const onNext = sinon.stub(); - retryIntervals.forEach -> - request.getDelayForRetry({ - err, - onNext, - retryIntervals, - delaysRemaining, - }) + retryIntervals.forEach(() => request.getDelayForRetry({ + err, + onNext, + retryIntervals, + delaysRemaining, + })); - expect(delaysRemaining).to.be.empty - expect(onNext.args).to.deep.eq([ - [0, 1] - [999, 2] - [100, 3] + expect(delaysRemaining).to.be.empty; + return expect(onNext.args).to.deep.eq([ + [0, 1], + [999, 2], + [100, 3], [200, 4] - ]) + ]); + }); - it "does not divide by 10 when err.code != ECONNREFUSED", -> - retryIntervals = [1,2,3,4] - delaysRemaining = [2000, 2000, 2000, 2000] + it("does not divide by 10 when err.code != ECONNREFUSED", function() { + const retryIntervals = [1,2,3,4]; + const delaysRemaining = [2000, 2000, 2000, 2000]; - err = { + const err = { code: "ECONNRESET" - } + }; - onNext = sinon.stub() + const onNext = sinon.stub(); request.getDelayForRetry({ err, onNext, retryIntervals, delaysRemaining, - }) + }); - expect(delaysRemaining).to.have.length(3) - expect(onNext).to.be.calledWith(2000, 1) + expect(delaysRemaining).to.have.length(3); + return expect(onNext).to.be.calledWith(2000, 1); + }); - it "calls onElse when delaysRemaining is exhausted", -> - retryIntervals = [1,2,3,4] - delaysRemaining = [] + return it("calls onElse when delaysRemaining is exhausted", function() { + const retryIntervals = [1,2,3,4]; + const delaysRemaining = []; - onNext = sinon.stub() - onElse = sinon.stub() + const onNext = sinon.stub(); + const onElse = sinon.stub(); request.getDelayForRetry({ - onElse + onElse, onNext, retryIntervals, delaysRemaining, - }) + }); - expect(onElse).to.be.calledWithExactly() - expect(onNext).not.to.be.called + expect(onElse).to.be.calledWithExactly(); + return expect(onNext).not.to.be.called; + }); + }); - context "#setDefaults", -> - it "delaysRemaining to retryIntervals clone", -> - retryIntervals = [1,2,3,4] + context("#setDefaults", function() { + it("delaysRemaining to retryIntervals clone", function() { + const retryIntervals = [1,2,3,4]; - opts = request.setDefaults({ retryIntervals }) + const opts = request.setDefaults({ retryIntervals }); - expect(opts.retryIntervals).to.eq(retryIntervals) - expect(opts.delaysRemaining).not.to.eq(retryIntervals) - expect(opts.delaysRemaining).to.deep.eq(retryIntervals) + expect(opts.retryIntervals).to.eq(retryIntervals); + expect(opts.delaysRemaining).not.to.eq(retryIntervals); + return expect(opts.delaysRemaining).to.deep.eq(retryIntervals); + }); - it "retryIntervals to [0, 1000, 2000, 2000] by default", -> - opts = request.setDefaults({}) + it("retryIntervals to [0, 1000, 2000, 2000] by default", function() { + const opts = request.setDefaults({}); - expect(opts.retryIntervals).to.deep.eq([0, 1000, 2000, 2000]) + return expect(opts.retryIntervals).to.deep.eq([0, 1000, 2000, 2000]); + }); - it "delaysRemaining can be overridden", -> - delaysRemaining = [1] - opts = request.setDefaults({ delaysRemaining }) + return it("delaysRemaining can be overridden", function() { + const delaysRemaining = [1]; + const opts = request.setDefaults({ delaysRemaining }); - expect(opts.delaysRemaining).to.eq(delaysRemaining) + return expect(opts.delaysRemaining).to.eq(delaysRemaining); + }); + }); - context "#normalizeResponse", -> - beforeEach -> - @push = sinon.stub() + context("#normalizeResponse", function() { + beforeEach(function() { + return this.push = sinon.stub(); + }); - it "sets status to statusCode and deletes statusCode", -> - expect(request.normalizeResponse(@push, { - statusCode: 404 + it("sets status to statusCode and deletes statusCode", function() { + expect(request.normalizeResponse(this.push, { + statusCode: 404, request: { - headers: {foo: "bar"} + headers: {foo: "bar"}, body: "body" } })).to.deep.eq({ - status: 404 - statusText: "Not Found" - isOkStatusCode: false - requestHeaders: {foo: "bar"} + status: 404, + statusText: "Not Found", + isOkStatusCode: false, + requestHeaders: {foo: "bar"}, requestBody: "body" - }) - - expect(@push).to.be.calledOnce - - it "picks out status body and headers", -> - expect(request.normalizeResponse(@push, { - foo: "bar" - req: {} - originalHeaders: {} - headers: {"Content-Length": 50} - body: "foo" - statusCode: 200 + }); + + return expect(this.push).to.be.calledOnce; + }); + + return it("picks out status body and headers", function() { + expect(request.normalizeResponse(this.push, { + foo: "bar", + req: {}, + originalHeaders: {}, + headers: {"Content-Length": 50}, + body: "foo", + statusCode: 200, request: { - headers: {foo: "bar"} + headers: {foo: "bar"}, body: "body" } })).to.deep.eq({ - body: "foo" - headers: {"Content-Length": 50} - status: 200 - statusText: "OK" - isOkStatusCode: true - requestHeaders: {foo: "bar"} + body: "foo", + headers: {"Content-Length": 50}, + status: 200, + statusText: "OK", + isOkStatusCode: true, + requestHeaders: {foo: "bar"}, requestBody: "body" - }) + }); - expect(@push).to.be.calledOnce + return expect(this.push).to.be.calledOnce; + }); + }); - context "#create", -> - beforeEach (done) -> - @hits = 0 + context("#create", function() { + beforeEach(function(done) { + this.hits = 0; - @srv = http.createServer (req, res) => - @hits++ + this.srv = http.createServer((req, res) => { + this.hits++; - switch req.url - when "/never-ends" - res.writeHead(200) - res.write("foo\n") - when "/econnreset" - req.socket.destroy() + switch (req.url) { + case "/never-ends": + res.writeHead(200); + return res.write("foo\n"); + case "/econnreset": + return req.socket.destroy(); + } + }); - @srv.listen(9988, done) + return this.srv.listen(9988, done); + }); - afterEach -> - @srv.close() + afterEach(function() { + return this.srv.close(); + }); - context "retries for streams", -> - it "does not retry on a timeout", -> - opts = request.setDefaults({ - url: "http://localhost:9988/never-ends" + context("retries for streams", function() { + it("does not retry on a timeout", function() { + const opts = request.setDefaults({ + url: "http://localhost:9988/never-ends", timeout: 1000 - }) + }); - stream = request.create(opts) + const stream = request.create(opts); - retries = 0 + let retries = 0; - stream.on "retry", -> - retries++ + stream.on("retry", () => retries++); - p = Bluebird.fromCallback (cb) -> - stream.on "error", cb + const p = Bluebird.fromCallback(cb => stream.on("error", cb)); - expect(p).to.be.rejected - .then (err) -> - expect(err.code).to.eq('ESOCKETTIMEDOUT') - expect(retries).to.eq(0) + return expect(p).to.be.rejected + .then(function(err) { + expect(err.code).to.eq('ESOCKETTIMEDOUT'); + return expect(retries).to.eq(0); + }); + }); - it "retries 4x on a connection reset", -> - opts = { - url: "http://localhost:9988/econnreset" - retryIntervals: [0, 1, 2, 3] + it("retries 4x on a connection reset", function() { + const opts = { + url: "http://localhost:9988/econnreset", + retryIntervals: [0, 1, 2, 3], timeout: 1000 - } + }; - stream = request.create(opts) + const stream = request.create(opts); - retries = 0 + let retries = 0; - stream.on "retry", -> - retries++ + stream.on("retry", () => retries++); - p = Bluebird.fromCallback (cb) -> - stream.on "error", cb + const p = Bluebird.fromCallback(cb => stream.on("error", cb)); - expect(p).to.be.rejected - .then (err) -> - expect(err.code).to.eq('ECONNRESET') - expect(retries).to.eq(4) + return expect(p).to.be.rejected + .then(function(err) { + expect(err.code).to.eq('ECONNRESET'); + return expect(retries).to.eq(4); + }); + }); - it "retries 4x on a NXDOMAIN (ENOTFOUND)", -> - nock.enableNetConnect() + return it("retries 4x on a NXDOMAIN (ENOTFOUND)", function() { + nock.enableNetConnect(); - opts = { - url: "http://will-never-exist.invalid.example.com" - retryIntervals: [0, 1, 2, 3] + const opts = { + url: "http://will-never-exist.invalid.example.com", + retryIntervals: [0, 1, 2, 3], timeout: 1000 - } + }; - stream = request.create(opts) + const stream = request.create(opts); - retries = 0 + let retries = 0; - stream.on "retry", -> - retries++ + stream.on("retry", () => retries++); - p = Bluebird.fromCallback (cb) -> - stream.on "error", cb + const p = Bluebird.fromCallback(cb => stream.on("error", cb)); - expect(p).to.be.rejected - .then (err) -> - expect(err.code).to.eq('ENOTFOUND') - expect(retries).to.eq(4) + return expect(p).to.be.rejected + .then(function(err) { + expect(err.code).to.eq('ENOTFOUND'); + return expect(retries).to.eq(4); + }); + }); + }); - context "retries for promises", -> - it "does not retry on a timeout", -> - opts = { - url: "http://localhost:9988/never-ends" + return context("retries for promises", function() { + it("does not retry on a timeout", function() { + const opts = { + url: "http://localhost:9988/never-ends", timeout: 100 - } - - request.create(opts, true) - .then -> - throw new Error('should not reach') - .catch (err) => - expect(err.error.code).to.eq('ESOCKETTIMEDOUT') - expect(@hits).to.eq(1) - - it "retries 4x on a connection reset", -> - opts = { - url: "http://localhost:9988/econnreset" - retryIntervals: [0, 1, 2, 3] + }; + + return request.create(opts, true) + .then(function() { + throw new Error('should not reach');}).catch(err => { + expect(err.error.code).to.eq('ESOCKETTIMEDOUT'); + return expect(this.hits).to.eq(1); + }); + }); + + return it("retries 4x on a connection reset", function() { + const opts = { + url: "http://localhost:9988/econnreset", + retryIntervals: [0, 1, 2, 3], timeout: 250 - } - - request.create(opts, true) - .then -> - throw new Error('should not reach') - .catch (err) => - expect(err.error.code).to.eq('ECONNRESET') - expect(@hits).to.eq(5) - - context "#sendPromise", -> - it "sets strictSSL=false", -> - init = sinon.spy(request.rp.Request.prototype, "init") + }; + + return request.create(opts, true) + .then(function() { + throw new Error('should not reach');}).catch(err => { + expect(err.error.code).to.eq('ECONNRESET'); + return expect(this.hits).to.eq(5); + }); + }); + }); + }); + + context("#sendPromise", function() { + it("sets strictSSL=false", function() { + const init = sinon.spy(request.rp.Request.prototype, "init"); nock("http://www.github.com") .get("/foo") - .reply 200, "hello", { + .reply(200, "hello", { "Content-Type": "text/html" - } + }); - request.sendPromise({}, @fn, { - url: "http://www.github.com/foo" + return request.sendPromise({}, this.fn, { + url: "http://www.github.com/foo", cookies: false }) - .then -> - expect(init).to.be.calledWithMatch({strictSSL: false}) + .then(() => expect(init).to.be.calledWithMatch({strictSSL: false})); + }); - it "sets simple=false", -> + it("sets simple=false", function() { nock("http://www.github.com") .get("/foo") - .reply(500, "") + .reply(500, ""); - ## should not bomb on 500 - ## because simple = false - request.sendPromise({}, @fn, { - url: "http://www.github.com/foo" + //# should not bomb on 500 + //# because simple = false + return request.sendPromise({}, this.fn, { + url: "http://www.github.com/foo", cookies: false - }) + }); + }); - it "sets resolveWithFullResponse=true", -> + it("sets resolveWithFullResponse=true", function() { nock("http://www.github.com") .get("/foo") .reply(200, "hello", { "Content-Type": "text/html" - }) + }); - request.sendPromise({}, @fn, { - url: "http://www.github.com/foo" - cookies: false + return request.sendPromise({}, this.fn, { + url: "http://www.github.com/foo", + cookies: false, body: "foobarbaz" }) - .then (resp) -> - expect(resp).to.have.keys("status", "body", "headers", "duration", "isOkStatusCode", "statusText", "allRequestResponses", "requestBody", "requestHeaders") - - expect(resp.status).to.eq(200) - expect(resp.statusText).to.eq("OK") - expect(resp.body).to.eq("hello") - expect(resp.headers).to.deep.eq({"content-type": "text/html"}) - expect(resp.isOkStatusCode).to.be.true - expect(resp.requestBody).to.eq("foobarbaz") + .then(function(resp) { + expect(resp).to.have.keys("status", "body", "headers", "duration", "isOkStatusCode", "statusText", "allRequestResponses", "requestBody", "requestHeaders"); + + expect(resp.status).to.eq(200); + expect(resp.statusText).to.eq("OK"); + expect(resp.body).to.eq("hello"); + expect(resp.headers).to.deep.eq({"content-type": "text/html"}); + expect(resp.isOkStatusCode).to.be.true; + expect(resp.requestBody).to.eq("foobarbaz"); expect(resp.requestHeaders).to.deep.eq({ - "accept": "*/*" - "accept-encoding": "gzip, deflate" - "connection": "keep-alive" - "content-length": 9 + "accept": "*/*", + "accept-encoding": "gzip, deflate", + "connection": "keep-alive", + "content-length": 9, "host": "www.github.com" - }) - expect(resp.allRequestResponses).to.deep.eq([ + }); + return expect(resp.allRequestResponses).to.deep.eq([ { - "Request Body": "foobarbaz" - "Request Headers": {"accept": "*/*", "accept-encoding": "gzip, deflate", "connection": "keep-alive", "content-length": 9, "host": "www.github.com"} - "Request URL": "http://www.github.com/foo" - "Response Body": "hello" - "Response Headers": {"content-type": "text/html"} + "Request Body": "foobarbaz", + "Request Headers": {"accept": "*/*", "accept-encoding": "gzip, deflate", "connection": "keep-alive", "content-length": 9, "host": "www.github.com"}, + "Request URL": "http://www.github.com/foo", + "Response Body": "hello", + "Response Headers": {"content-type": "text/html"}, "Response Status": 200 } - ]) + ]); + }); + }); - it "includes redirects", -> - @fn.resolves() + it("includes redirects", function() { + this.fn.resolves(); nock("http://www.github.com") .get("/dashboard") @@ -379,509 +405,547 @@ describe "lib/request", -> .get("/login") .reply(200, "log in", { "Content-Type": "text/html" - }) + }); - request.sendPromise({}, @fn, { - url: "http://www.github.com/dashboard" + return request.sendPromise({}, this.fn, { + url: "http://www.github.com/dashboard", cookies: false }) - .then (resp) -> - expect(resp).to.have.keys("status", "body", "headers", "duration", "isOkStatusCode", "statusText", "allRequestResponses", "redirects", "requestBody", "requestHeaders") - - expect(resp.status).to.eq(200) - expect(resp.statusText).to.eq("OK") - expect(resp.body).to.eq("log in") - expect(resp.headers).to.deep.eq({"content-type": "text/html"}) - expect(resp.isOkStatusCode).to.be.true - expect(resp.requestBody).to.be.undefined + .then(function(resp) { + expect(resp).to.have.keys("status", "body", "headers", "duration", "isOkStatusCode", "statusText", "allRequestResponses", "redirects", "requestBody", "requestHeaders"); + + expect(resp.status).to.eq(200); + expect(resp.statusText).to.eq("OK"); + expect(resp.body).to.eq("log in"); + expect(resp.headers).to.deep.eq({"content-type": "text/html"}); + expect(resp.isOkStatusCode).to.be.true; + expect(resp.requestBody).to.be.undefined; expect(resp.redirects).to.deep.eq([ - "301: http://www.github.com/auth" + "301: http://www.github.com/auth", "302: http://www.github.com/login" - ]) + ]); expect(resp.requestHeaders).to.deep.eq({ - "accept": "*/*" - "accept-encoding": "gzip, deflate" - "connection": "keep-alive" - "referer": "http://www.github.com/auth" + "accept": "*/*", + "accept-encoding": "gzip, deflate", + "connection": "keep-alive", + "referer": "http://www.github.com/auth", "host": "www.github.com" - }) - expect(resp.allRequestResponses).to.deep.eq([ + }); + return expect(resp.allRequestResponses).to.deep.eq([ { - "Request Body": null - "Request Headers": {"accept": "*/*", "accept-encoding": "gzip, deflate", "connection": "keep-alive", "host": "www.github.com"} - "Request URL": "http://www.github.com/dashboard" - "Response Body": null - "Response Headers": {"content-type": "application/json", "location": "/auth"} + "Request Body": null, + "Request Headers": {"accept": "*/*", "accept-encoding": "gzip, deflate", "connection": "keep-alive", "host": "www.github.com"}, + "Request URL": "http://www.github.com/dashboard", + "Response Body": null, + "Response Headers": {"content-type": "application/json", "location": "/auth"}, "Response Status": 301 }, { - "Request Body": null - "Request Headers": {"accept": "*/*", "accept-encoding": "gzip, deflate", "connection": "keep-alive", "host": "www.github.com", "referer": "http://www.github.com/dashboard"} - "Request URL": "http://www.github.com/auth" - "Response Body": null - "Response Headers": {"content-type": "application/json", "location": "/login"} + "Request Body": null, + "Request Headers": {"accept": "*/*", "accept-encoding": "gzip, deflate", "connection": "keep-alive", "host": "www.github.com", "referer": "http://www.github.com/dashboard"}, + "Request URL": "http://www.github.com/auth", + "Response Body": null, + "Response Headers": {"content-type": "application/json", "location": "/login"}, "Response Status": 302 }, { - "Request Body": null - "Request Headers": {"accept": "*/*", "accept-encoding": "gzip, deflate", "connection": "keep-alive", "host": "www.github.com", "referer": "http://www.github.com/auth"} - "Request URL": "http://www.github.com/login" - "Response Body": "log in" - "Response Headers": {"content-type": "text/html"} + "Request Body": null, + "Request Headers": {"accept": "*/*", "accept-encoding": "gzip, deflate", "connection": "keep-alive", "host": "www.github.com", "referer": "http://www.github.com/auth"}, + "Request URL": "http://www.github.com/login", + "Response Body": "log in", + "Response Headers": {"content-type": "text/html"}, "Response Status": 200 } - ]) + ]); + }); + }); - it "catches errors", -> - nock.enableNetConnect() + it("catches errors", function() { + nock.enableNetConnect(); - req = Request({ timeout: 2000 }) + const req = Request({ timeout: 2000 }); - req.sendPromise({}, @fn, { - url: "http://localhost:1111/foo" + return req.sendPromise({}, this.fn, { + url: "http://localhost:1111/foo", cookies: false }) - .then -> - throw new Error("should have failed but didnt") - .catch (err) -> - expect(err.message).to.eq("Error: connect ECONNREFUSED 127.0.0.1:1111") + .then(function() { + throw new Error("should have failed but didnt");}).catch(err => expect(err.message).to.eq("Error: connect ECONNREFUSED 127.0.0.1:1111")); + }); - it "parses response body as json if content-type application/json response headers", -> + it("parses response body as json if content-type application/json response headers", function() { nock("http://localhost:8080") .get("/status.json") .reply(200, JSON.stringify({status: "ok"}), { "Content-Type": "application/json" - }) + }); - request.sendPromise({}, @fn, { - url: "http://localhost:8080/status.json" + return request.sendPromise({}, this.fn, { + url: "http://localhost:8080/status.json", cookies: false }) - .then (resp) -> - expect(resp.body).to.deep.eq({status: "ok"}) + .then(resp => expect(resp.body).to.deep.eq({status: "ok"})); + }); - it "parses response body as json if content-type application/vnd.api+json response headers", -> + it("parses response body as json if content-type application/vnd.api+json response headers", function() { nock("http://localhost:8080") .get("/status.json") .reply(200, JSON.stringify({status: "ok"}), { "Content-Type": "application/vnd.api+json" - }) + }); - request.sendPromise({}, @fn, { - url: "http://localhost:8080/status.json" + return request.sendPromise({}, this.fn, { + url: "http://localhost:8080/status.json", cookies: false }) - .then (resp) -> - expect(resp.body).to.deep.eq({status: "ok"}) + .then(resp => expect(resp.body).to.deep.eq({status: "ok"})); + }); - it "revives from parsing bad json", -> + it("revives from parsing bad json", function() { nock("http://localhost:8080") .get("/status.json") .reply(200, "{bad: 'json'}", { "Content-Type": "application/json" - }) + }); - request.sendPromise({}, @fn, { - url: "http://localhost:8080/status.json" + return request.sendPromise({}, this.fn, { + url: "http://localhost:8080/status.json", cookies: false }) - .then (resp) -> - expect(resp.body).to.eq("{bad: 'json'}") + .then(resp => expect(resp.body).to.eq("{bad: 'json'}")); + }); - it "sets duration on response", -> + it("sets duration on response", function() { nock("http://localhost:8080") .get("/foo") .delay(10) .reply(200, "123", { "Content-Type": "text/plain" - }) + }); - request.sendPromise({}, @fn, { - url: "http://localhost:8080/foo" + return request.sendPromise({}, this.fn, { + url: "http://localhost:8080/foo", cookies: false }) - .then (resp) -> - expect(resp.duration).to.be.a("Number") - expect(resp.duration).to.be.gt(0) + .then(function(resp) { + expect(resp.duration).to.be.a("Number"); + return expect(resp.duration).to.be.gt(0); + }); + }); - it "sends up user-agent headers", -> + it("sends up user-agent headers", function() { nock("http://localhost:8080") .matchHeader("user-agent", "foobarbaz") .get("/foo") - .reply(200, "derp") + .reply(200, "derp"); - headers = {} - headers["user-agent"] = "foobarbaz" + const headers = {}; + headers["user-agent"] = "foobarbaz"; - request.sendPromise(headers, @fn, { - url: "http://localhost:8080/foo" + return request.sendPromise(headers, this.fn, { + url: "http://localhost:8080/foo", cookies: false }) - .then (resp) -> - expect(resp.body).to.eq("derp") + .then(resp => expect(resp.body).to.eq("derp")); + }); - it "sends connection: keep-alive by default", -> + it("sends connection: keep-alive by default", function() { nock("http://localhost:8080") .matchHeader("connection", "keep-alive") .get("/foo") - .reply(200, "it worked") + .reply(200, "it worked"); - request.sendPromise({}, @fn, { - url: "http://localhost:8080/foo" + return request.sendPromise({}, this.fn, { + url: "http://localhost:8080/foo", cookies: false }) - .then (resp) -> - expect(resp.body).to.eq("it worked") + .then(resp => expect(resp.body).to.eq("it worked")); + }); - it "lower cases headers", -> + it("lower cases headers", function() { nock("http://localhost:8080") .matchHeader("test", "true") .get("/foo") - .reply(200, "derp") + .reply(200, "derp"); - headers = {} - headers["user-agent"] = "foobarbaz" + const headers = {}; + headers["user-agent"] = "foobarbaz"; - request.sendPromise(headers, @fn, { - url: "http://localhost:8080/foo" + return request.sendPromise(headers, this.fn, { + url: "http://localhost:8080/foo", cookies: false, headers: { 'TEST': true, } }) - .then (resp) -> - expect(resp.body).to.eq("derp") + .then(resp => expect(resp.body).to.eq("derp")); + }); - it "allows overriding user-agent in headers", -> + it("allows overriding user-agent in headers", function() { nock("http://localhost:8080") .matchHeader("user-agent", "custom-agent") .get("/foo") - .reply(200, "derp") + .reply(200, "derp"); - headers = {'user-agent': 'test'} + const headers = {'user-agent': 'test'}; - request.sendPromise(headers, @fn, { - url: "http://localhost:8080/foo" + return request.sendPromise(headers, this.fn, { + url: "http://localhost:8080/foo", cookies: false, headers: { 'User-Agent': "custom-agent", }, }) - .then (resp) -> - expect(resp.body).to.eq("derp") + .then(resp => expect(resp.body).to.eq("derp")); + }); - context "accept header", -> - it "sets to */* by default", -> + context("accept header", function() { + it("sets to */* by default", function() { nock("http://localhost:8080") .matchHeader("accept", "*/*") .get("/headers") - .reply(200) + .reply(200); - request.sendPromise({}, @fn, { - url: "http://localhost:8080/headers" + return request.sendPromise({}, this.fn, { + url: "http://localhost:8080/headers", cookies: false }) - .then (resp) -> - expect(resp.status).to.eq(200) + .then(resp => expect(resp.status).to.eq(200)); + }); - it "can override accept header", -> + it("can override accept header", function() { nock("http://localhost:8080") .matchHeader("accept", "text/html") .get("/headers") - .reply(200) + .reply(200); - request.sendPromise({}, @fn, { - url: "http://localhost:8080/headers" - cookies: false + return request.sendPromise({}, this.fn, { + url: "http://localhost:8080/headers", + cookies: false, headers: { accept: "text/html" } }) - .then (resp) -> - expect(resp.status).to.eq(200) + .then(resp => expect(resp.status).to.eq(200)); + }); - it "can override Accept header", -> + return it("can override Accept header", function() { nock("http://localhost:8080") .matchHeader("accept", "text/plain") .get("/headers") - .reply(200) + .reply(200); - request.sendPromise({}, @fn, { - url: "http://localhost:8080/headers" - cookies: false + return request.sendPromise({}, this.fn, { + url: "http://localhost:8080/headers", + cookies: false, headers: { Accept: "text/plain" } }) - .then (resp) -> - expect(resp.status).to.eq(200) + .then(resp => expect(resp.status).to.eq(200)); + }); + }); - context "qs", -> - it "can accept qs", -> - nock("http://localhost:8080") - .get("/foo?bar=baz&q=1") - .reply(200) + context("qs", () => it("can accept qs", function() { + nock("http://localhost:8080") + .get("/foo?bar=baz&q=1") + .reply(200); - request.sendPromise({}, @fn, { - url: "http://localhost:8080/foo" - cookies: false - qs: { - bar: "baz" - q: 1 - } - }) - .then (resp) -> - expect(resp.status).to.eq(200) + return request.sendPromise({}, this.fn, { + url: "http://localhost:8080/foo", + cookies: false, + qs: { + bar: "baz", + q: 1 + } + }) + .then(resp => expect(resp.status).to.eq(200)); + })); - context "followRedirect", -> - beforeEach -> - @fn.resolves() + context("followRedirect", function() { + beforeEach(function() { + return this.fn.resolves(); + }); - it "by default follow redirects", -> + it("by default follow redirects", function() { nock("http://localhost:8080") .get("/dashboard") .reply(302, "", { location: "http://localhost:8080/login" }) .get("/login") - .reply(200, "login") + .reply(200, "login"); - request.sendPromise({}, @fn, { - url: "http://localhost:8080/dashboard" - cookies: false + return request.sendPromise({}, this.fn, { + url: "http://localhost:8080/dashboard", + cookies: false, followRedirect: true }) - .then (resp) -> - expect(resp.status).to.eq(200) - expect(resp.body).to.eq("login") - expect(resp).not.to.have.property("redirectedToUrl") - - it "follows non-GET redirects by default", -> + .then(function(resp) { + expect(resp.status).to.eq(200); + expect(resp.body).to.eq("login"); + return expect(resp).not.to.have.property("redirectedToUrl"); + }); + }); + + it("follows non-GET redirects by default", function() { nock("http://localhost:8080") .post("/login") .reply(302, "", { location: "http://localhost:8080/dashboard" }) .get("/dashboard") - .reply(200, "dashboard") + .reply(200, "dashboard"); - request.sendPromise({}, @fn, { - method: "POST" - url: "http://localhost:8080/login" + return request.sendPromise({}, this.fn, { + method: "POST", + url: "http://localhost:8080/login", cookies: false }) - .then (resp) -> - expect(resp.status).to.eq(200) - expect(resp.body).to.eq("dashboard") - expect(resp).not.to.have.property("redirectedToUrl") - - it "can turn off following redirects", -> + .then(function(resp) { + expect(resp.status).to.eq(200); + expect(resp.body).to.eq("dashboard"); + return expect(resp).not.to.have.property("redirectedToUrl"); + }); + }); + + it("can turn off following redirects", function() { nock("http://localhost:8080") .get("/dashboard") .reply(302, "", { location: "http://localhost:8080/login" }) .get("/login") - .reply(200, "login") + .reply(200, "login"); - request.sendPromise({}, @fn, { - url: "http://localhost:8080/dashboard" - cookies: false + return request.sendPromise({}, this.fn, { + url: "http://localhost:8080/dashboard", + cookies: false, followRedirect: false }) - .then (resp) -> - expect(resp.status).to.eq(302) - expect(resp.body).to.eq("") - expect(resp.redirectedToUrl).to.eq("http://localhost:8080/login") - - it "resolves redirectedToUrl on relative redirects", -> + .then(function(resp) { + expect(resp.status).to.eq(302); + expect(resp.body).to.eq(""); + return expect(resp.redirectedToUrl).to.eq("http://localhost:8080/login"); + }); + }); + + it("resolves redirectedToUrl on relative redirects", function() { nock("http://localhost:8080") .get("/dashboard") .reply(302, "", { - location: "/login" ## absolute-relative pathname + location: "/login" //# absolute-relative pathname }) .get("/login") - .reply(200, "login") + .reply(200, "login"); - request.sendPromise({}, @fn, { - url: "http://localhost:8080/dashboard" - cookies: false + return request.sendPromise({}, this.fn, { + url: "http://localhost:8080/dashboard", + cookies: false, followRedirect: false }) - .then (resp) -> - expect(resp.status).to.eq(302) - expect(resp.redirectedToUrl).to.eq("http://localhost:8080/login") + .then(function(resp) { + expect(resp.status).to.eq(302); + return expect(resp.redirectedToUrl).to.eq("http://localhost:8080/login"); + }); + }); - it "resolves redirectedToUrl to another domain", -> + it("resolves redirectedToUrl to another domain", function() { nock("http://localhost:8080") .get("/dashboard") .reply(301, "", { location: "https://www.google.com/login" }) .get("/login") - .reply(200, "login") + .reply(200, "login"); - request.sendPromise({}, @fn, { - url: "http://localhost:8080/dashboard" - cookies: false + return request.sendPromise({}, this.fn, { + url: "http://localhost:8080/dashboard", + cookies: false, followRedirect: false }) - .then (resp) -> - expect(resp.status).to.eq(301) - expect(resp.redirectedToUrl).to.eq("https://www.google.com/login") + .then(function(resp) { + expect(resp.status).to.eq(301); + return expect(resp.redirectedToUrl).to.eq("https://www.google.com/login"); + }); + }); - it "does not included redirectedToUrl when following redirects", -> + it("does not included redirectedToUrl when following redirects", function() { nock("http://localhost:8080") .get("/dashboard") .reply(302, "", { location: "http://localhost:8080/login" }) .get("/login") - .reply(200, "login") + .reply(200, "login"); - request.sendPromise({}, @fn, { - url: "http://localhost:8080/dashboard" + return request.sendPromise({}, this.fn, { + url: "http://localhost:8080/dashboard", cookies: false }) - .then (resp) -> - expect(resp.status).to.eq(200) - expect(resp).not.to.have.property("redirectedToUrl") - - it "gets + attaches the cookies at each redirect", -> - testAttachingCookiesWith => - request.sendPromise({}, @fn, { + .then(function(resp) { + expect(resp.status).to.eq(200); + return expect(resp).not.to.have.property("redirectedToUrl"); + }); + }); + + return it("gets + attaches the cookies at each redirect", function() { + return testAttachingCookiesWith(() => { + return request.sendPromise({}, this.fn, { url: "http://localhost:1234/" - }) - - context "form=true", -> - beforeEach -> - nock("http://localhost:8080") - .matchHeader("Content-Type", "application/x-www-form-urlencoded") - .post("/login", "foo=bar&baz=quux") - .reply(200, "") - - it "takes converts body to x-www-form-urlencoded and sets header", -> - request.sendPromise({}, @fn, { - url: "http://localhost:8080/login" - method: "POST" - cookies: false - form: true + }); + }); + }); + }); + + context("form=true", function() { + beforeEach(() => nock("http://localhost:8080") + .matchHeader("Content-Type", "application/x-www-form-urlencoded") + .post("/login", "foo=bar&baz=quux") + .reply(200, "")); + + it("takes converts body to x-www-form-urlencoded and sets header", function() { + return request.sendPromise({}, this.fn, { + url: "http://localhost:8080/login", + method: "POST", + cookies: false, + form: true, body: { - foo: "bar" + foo: "bar", baz: "quux" } }) - .then (resp) -> - expect(resp.status).to.eq(200) - expect(resp.body).to.eq("") + .then(function(resp) { + expect(resp.status).to.eq(200); + return expect(resp.body).to.eq(""); + }); + }); - it "does not send body", -> - init = sinon.spy(request.rp.Request.prototype, "init") + it("does not send body", function() { + const init = sinon.spy(request.rp.Request.prototype, "init"); - body = { - foo: "bar" + const body = { + foo: "bar", baz: "quux" - } - - request.sendPromise({}, @fn, { - url: "http://localhost:8080/login" - method: "POST" - cookies: false - form: true - json: true - body: body + }; + + return request.sendPromise({}, this.fn, { + url: "http://localhost:8080/login", + method: "POST", + cookies: false, + form: true, + json: true, + body }) - .then (resp) -> - expect(resp.status).to.eq(200) - expect(resp.body).to.eq("") - expect(init).not.to.be.calledWithMatch({body: body}) - - it "does not set json=true", -> - init = sinon.spy(request.rp.Request.prototype, "init") - - request.sendPromise({}, @fn, { - url: "http://localhost:8080/login" - method: "POST" - cookies: false - form: true - json: true + .then(function(resp) { + expect(resp.status).to.eq(200); + expect(resp.body).to.eq(""); + return expect(init).not.to.be.calledWithMatch({body}); + }); + }); + + return it("does not set json=true", function() { + const init = sinon.spy(request.rp.Request.prototype, "init"); + + return request.sendPromise({}, this.fn, { + url: "http://localhost:8080/login", + method: "POST", + cookies: false, + form: true, + json: true, body: { - foo: "bar" + foo: "bar", baz: "quux" } }) - .then (resp) -> - expect(resp.status).to.eq(200) - expect(resp.body).to.eq("") - expect(init).not.to.be.calledWithMatch({json: true}) - - context "bad headers", -> - beforeEach (done) -> - @srv = http.createServer (req, res) -> - res.writeHead(200) - res.end() - - @srv.listen(9988, done) - - afterEach -> - @srv.close() - - it "recovers from bad headers", -> - request.sendPromise({}, @fn, { - url: "http://localhost:9988/foo" - cookies: false + .then(function(resp) { + expect(resp.status).to.eq(200); + expect(resp.body).to.eq(""); + return expect(init).not.to.be.calledWithMatch({json: true}); + }); + }); + }); + + return context("bad headers", function() { + beforeEach(function(done) { + this.srv = http.createServer(function(req, res) { + res.writeHead(200); + return res.end(); + }); + + return this.srv.listen(9988, done); + }); + + afterEach(function() { + return this.srv.close(); + }); + + it("recovers from bad headers", function() { + return request.sendPromise({}, this.fn, { + url: "http://localhost:9988/foo", + cookies: false, headers: { "x-text": "אבגד" } }) - .then -> - throw new Error("should have failed") - .catch (err) -> - expect(err.message).to.eq "TypeError [ERR_INVALID_CHAR]: Invalid character in header content [\"x-text\"]" - - it "handles weird content in the body just fine", -> - request.sendPromise({}, @fn, { - url: "http://localhost:9988/foo" - cookies: false - json: true + .then(function() { + throw new Error("should have failed");}).catch(err => expect(err.message).to.eq("TypeError [ERR_INVALID_CHAR]: Invalid character in header content [\"x-text\"]")); + }); + + return it("handles weird content in the body just fine", function() { + return request.sendPromise({}, this.fn, { + url: "http://localhost:9988/foo", + cookies: false, + json: true, body: { "x-text": "אבגד" } - }) + }); + }); + }); + }); - context "#sendStream", -> - it "allows overriding user-agent in headers", -> + return context("#sendStream", function() { + it("allows overriding user-agent in headers", function() { nock("http://localhost:8080") .matchHeader("user-agent", "custom-agent") .get("/foo") - .reply(200, "derp") + .reply(200, "derp"); - sinon.spy(request, "create") - @fn.resolves({}) + sinon.spy(request, "create"); + this.fn.resolves({}); - headers = {'user-agent': 'test'} + const headers = {'user-agent': 'test'}; - options = { - url: "http://localhost:8080/foo" + const options = { + url: "http://localhost:8080/foo", cookies: false, headers: { 'user-agent': "custom-agent", }, - } - - request.sendStream(headers, @fn, options) - .then (beginFn) -> - beginFn() - expect(request.create).to.be.calledOnce - expect(request.create).to.be.calledWith(options) - - it "gets + attaches the cookies at each redirect", -> - testAttachingCookiesWith => - request.sendStream({}, @fn, { - url: "http://localhost:1234/" + }; + + return request.sendStream(headers, this.fn, options) + .then(function(beginFn) { + beginFn(); + expect(request.create).to.be.calledOnce; + return expect(request.create).to.be.calledWith(options); + }); + }); + + return it("gets + attaches the cookies at each redirect", function() { + return testAttachingCookiesWith(() => { + return request.sendStream({}, this.fn, { + url: "http://localhost:1234/", followRedirect: _.stubTrue }) - .then (fn) => - req = fn() - - new Promise (resolve, reject) => - req.on('response', resolve) - req.on('error', reject) + .then(fn => { + const req = fn(); + + return new Promise((resolve, reject) => { + req.on('response', resolve); + return req.on('error', reject); + }); + }); + }); + }); + }); +}); From 4517888937412fa77ee82c12c83dd290e80522b9 Mon Sep 17 00:00:00 2001 From: decaffeinate Date: Tue, 21 Apr 2020 14:13:44 -0400 Subject: [PATCH 3/7] decaffeinate: Run post-processing cleanups on request.coffee and 1 other file --- packages/server/lib/request.js | 931 ++++++------- packages/server/test/unit/request_spec.js | 1454 +++++++++++---------- 2 files changed, 1266 insertions(+), 1119 deletions(-) diff --git a/packages/server/lib/request.js b/packages/server/lib/request.js index 1afdf843605e..2700513b53a3 100644 --- a/packages/server/lib/request.js +++ b/packages/server/lib/request.js @@ -1,3 +1,8 @@ +/* eslint-disable + brace-style, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. /* * decaffeinate suggestions: * DS102: Remove unnecessary code created because of implicit returns @@ -5,102 +10,108 @@ * DS207: Consider shorter variations of null checks * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -const _ = require("lodash"); -let r = require("@cypress/request"); -let rp = require("@cypress/request-promise"); -const url = require("url"); -const tough = require("tough-cookie"); -const debug = require("debug")("cypress:server:request"); -const Promise = require("bluebird"); -const stream = require("stream"); -const duplexify = require("duplexify"); +const _ = require('lodash') +let r = require('@cypress/request') +let rp = require('@cypress/request-promise') +const url = require('url') +const tough = require('tough-cookie') +const debug = require('debug')('cypress:server:request') +const Promise = require('bluebird') +const stream = require('stream') +const duplexify = require('duplexify') const { - agent -} = require("@packages/network"); -const statusCode = require("./util/status_code"); + agent, +} = require('@packages/network') +const statusCode = require('./util/status_code') const { - streamBuffer -} = require("./util/stream_buffer"); + streamBuffer, +} = require('./util/stream_buffer') -const SERIALIZABLE_COOKIE_PROPS = ['name', 'value', 'domain', 'expiry', 'path', 'secure', 'hostOnly', 'httpOnly', 'sameSite']; -const NETWORK_ERRORS = "ECONNREFUSED ECONNRESET EPIPE EHOSTUNREACH EAI_AGAIN ENOTFOUND".split(" "); -const VERBOSE_REQUEST_OPTS = "followRedirect strictSSL".split(" "); -const HTTP_CLIENT_REQUEST_EVENTS = "abort connect continue information socket timeout upgrade".split(" "); -const TLS_VERSION_ERROR_RE = /TLSV1_ALERT_PROTOCOL_VERSION|UNSUPPORTED_PROTOCOL/; -const SAMESITE_NONE_RE = /; +samesite=(?:'none'|"none"|none)/i; +const SERIALIZABLE_COOKIE_PROPS = ['name', 'value', 'domain', 'expiry', 'path', 'secure', 'hostOnly', 'httpOnly', 'sameSite'] +const NETWORK_ERRORS = 'ECONNREFUSED ECONNRESET EPIPE EHOSTUNREACH EAI_AGAIN ENOTFOUND'.split(' ') +const VERBOSE_REQUEST_OPTS = 'followRedirect strictSSL'.split(' ') +const HTTP_CLIENT_REQUEST_EVENTS = 'abort connect continue information socket timeout upgrade'.split(' ') +const TLS_VERSION_ERROR_RE = /TLSV1_ALERT_PROTOCOL_VERSION|UNSUPPORTED_PROTOCOL/ +const SAMESITE_NONE_RE = /; +samesite=(?:'none'|"none"|none)/i -process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"; +process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0' const convertSameSiteToughToExtension = (sameSite, setCookie) => { - //# tough-cookie@4.0.0 uses 'none' as a default, so run this regex to detect if - //# SameSite=None was not explicitly set - //# @see https://github.com/salesforce/tough-cookie/issues/191 - const isUnspecified = (sameSite === "none") && !SAMESITE_NONE_RE.test(setCookie); + // tough-cookie@4.0.0 uses 'none' as a default, so run this regex to detect if + // SameSite=None was not explicitly set + // @see https://github.com/salesforce/tough-cookie/issues/191 + const isUnspecified = (sameSite === 'none') && !SAMESITE_NONE_RE.test(setCookie) if (isUnspecified) { - //# not explicitly set, so fall back to the browser's default - return undefined; + // not explicitly set, so fall back to the browser's default + return undefined } if (sameSite === 'none') { - return 'no_restriction'; + return 'no_restriction' } - return sameSite; -}; + return sameSite +} -const getOriginalHeaders = (req = {}) => //# the request instance holds an instance -//# of the original ClientRequest -//# as the 'req' property which holds the -//# original headers else fall back to -//# the normal req.headers -_.get(req, 'req.headers', req.headers); +const getOriginalHeaders = (req = {}) => // the request instance holds an instance +// of the original ClientRequest +// as the 'req' property which holds the +// original headers else fall back to +// the normal req.headers +{ + return _.get(req, 'req.headers', req.headers) +} -const getDelayForRetry = function(options = {}) { - const { err, opts, delaysRemaining, retryIntervals, onNext, onElse } = options; +const getDelayForRetry = function (options = {}) { + const { err, opts, delaysRemaining, retryIntervals, onNext, onElse } = options - let delay = delaysRemaining.shift(); + let delay = delaysRemaining.shift() if (!_.isNumber(delay)) { - //# no more delays, bailing - debug("exhausted all attempts retrying request %o", merge(opts, { err })); + // no more delays, bailing + debug('exhausted all attempts retrying request %o', merge(opts, { err })) - return onElse(); + return onElse() } - //# figure out which attempt we're on... - const attempt = retryIntervals.length - delaysRemaining.length; + // figure out which attempt we're on... + const attempt = retryIntervals.length - delaysRemaining.length - //# if this ECONNREFUSED and we are - //# retrying greater than 1 second - //# then divide the delay interval - //# by 10 so it doesn't wait as long to retry - //# TODO: do we really want to do this? - if ((delay >= 1000) && (_.get(err, "code") === "ECONNREFUSED")) { - delay = delay / 10; + // if this ECONNREFUSED and we are + // retrying greater than 1 second + // then divide the delay interval + // by 10 so it doesn't wait as long to retry + // TODO: do we really want to do this? + if ((delay >= 1000) && (_.get(err, 'code') === 'ECONNREFUSED')) { + delay = delay / 10 } - debug("retrying request %o", merge(opts, { + debug('retrying request %o', merge(opts, { delay, attempt, - })); + })) - return onNext(delay, attempt); -}; + return onNext(delay, attempt) +} -const hasRetriableStatusCodeFailure = (res, retryOnStatusCodeFailure) => //# everything must be true in order to -//# retry a status code failure -_.every([ - retryOnStatusCodeFailure, - !statusCode.isOk(res.statusCode) -]); +const hasRetriableStatusCodeFailure = (res, retryOnStatusCodeFailure) => // everything must be true in order to +// retry a status code failure +{ + return _.every([ + retryOnStatusCodeFailure, + !statusCode.isOk(res.statusCode), + ]) +} -const isRetriableError = (err = {}, retryOnNetworkFailure) => _.every([ - retryOnNetworkFailure, - _.includes(NETWORK_ERRORS, err.code) -]); +const isRetriableError = (err = {}, retryOnNetworkFailure) => { + return _.every([ + retryOnNetworkFailure, + _.includes(NETWORK_ERRORS, err.code), + ]) +} -const maybeRetryOnNetworkFailure = function(err, options = {}) { +const maybeRetryOnNetworkFailure = function (err, options = {}) { const { opts, retryIntervals, @@ -108,24 +119,24 @@ const maybeRetryOnNetworkFailure = function(err, options = {}) { retryOnNetworkFailure, onNext, onElse, - } = options; + } = options - debug("received an error making http request %o", merge(opts, { err })); + debug('received an error making http request %o', merge(opts, { err })) - const isTlsVersionError = TLS_VERSION_ERROR_RE.test(err.message); + const isTlsVersionError = TLS_VERSION_ERROR_RE.test(err.message) if (isTlsVersionError) { - //# because doing every connection via TLSv1 can lead to slowdowns, we set it only on failure - //# https://github.com/cypress-io/cypress/pull/6705 - debug('detected TLS version error, setting min version to TLSv1'); - opts.minVersion = 'TLSv1'; + // because doing every connection via TLSv1 can lead to slowdowns, we set it only on failure + // https://github.com/cypress-io/cypress/pull/6705 + debug('detected TLS version error, setting min version to TLSv1') + opts.minVersion = 'TLSv1' } if (!isTlsVersionError && !isRetriableError(err, retryOnNetworkFailure)) { - return onElse(); + return onElse() } - //# else see if we have more delays left... + // else see if we have more delays left... return getDelayForRetry({ err, opts, @@ -133,10 +144,10 @@ const maybeRetryOnNetworkFailure = function(err, options = {}) { delaysRemaining, onNext, onElse, - }); -}; + }) +} -const maybeRetryOnStatusCodeFailure = function(res, options = {}) { +const maybeRetryOnStatusCodeFailure = function (res, options = {}) { const { err, opts, @@ -146,21 +157,21 @@ const maybeRetryOnStatusCodeFailure = function(res, options = {}) { retryOnStatusCodeFailure, onNext, onElse, - } = options; + } = options - debug("received status code & headers on request %o", { + debug('received status code & headers on request %o', { requestId, statusCode: res.statusCode, - headers: _.pick(res.headers, 'content-type', 'set-cookie', 'location') - }); + headers: _.pick(res.headers, 'content-type', 'set-cookie', 'location'), + }) - //# is this a retryable status code failure? + // is this a retryable status code failure? if (!hasRetriableStatusCodeFailure(res, retryOnStatusCodeFailure)) { - //# if not then we're done here - return onElse(); + // if not then we're done here + return onElse() } - //# else see if we have more delays left... + // else see if we have more delays left... return getDelayForRetry({ err, opts, @@ -168,177 +179,193 @@ const maybeRetryOnStatusCodeFailure = function(res, options = {}) { delaysRemaining, onNext, onElse, - }); -}; + }) +} -var merge = (...args) => _.chain({}) -.extend(...args) -.omit(VERBOSE_REQUEST_OPTS) -.value(); +const merge = (...args) => { + return _.chain({}) + .extend(...args) + .omit(VERBOSE_REQUEST_OPTS) + .value() +} -const pick = function(resp = {}) { - const req = resp.request != null ? resp.request : {}; +const pick = function (resp = {}) { + const req = resp.request != null ? resp.request : {} - const headers = getOriginalHeaders(req); + const headers = getOriginalHeaders(req) return { - "Request Body": req.body != null ? req.body : null, - "Request Headers": headers, - "Request URL": req.href, - "Response Body": resp.body != null ? resp.body : null, - "Response Headers": resp.headers, - "Response Status": resp.statusCode - }; -}; - -var createRetryingRequestPromise = function(opts) { + 'Request Body': req.body != null ? req.body : null, + 'Request Headers': headers, + 'Request URL': req.href, + 'Response Body': resp.body != null ? resp.body : null, + 'Response Headers': resp.headers, + 'Response Status': resp.statusCode, + } +} + +const createRetryingRequestPromise = function (opts) { const { requestId, retryIntervals, delaysRemaining, retryOnNetworkFailure, - retryOnStatusCodeFailure - } = opts; - - const retry = delay => Promise.delay(delay) - .then(() => createRetryingRequestPromise(opts)); - - return rp(opts) - .catch(err => //# rp wraps network errors in a RequestError, so might need to unwrap it to check - maybeRetryOnNetworkFailure(err.error || err, { - opts, - retryIntervals, - delaysRemaining, - retryOnNetworkFailure, - onNext: retry, - onElse() { - throw err; - } - })).then(res => //# ok, no net error, but what about a bad status code? - maybeRetryOnStatusCodeFailure(res, { - opts, - requestId, - retryIntervals, - delaysRemaining, retryOnStatusCodeFailure, - onNext: retry, - onElse: _.constant(res) - })); -}; + } = opts -const pipeEvent = (source, destination, event) => source.on(event, (...args) => destination.emit(event, ...args)); + const retry = (delay) => { + return Promise.delay(delay) + .then(() => { + return createRetryingRequestPromise(opts) + }) + } -const createRetryingRequestStream = function(opts = {}) { + return rp(opts) + .catch((err) => // rp wraps network errors in a RequestError, so might need to unwrap it to check + { + return maybeRetryOnNetworkFailure(err.error || err, { + opts, + retryIntervals, + delaysRemaining, + retryOnNetworkFailure, + onNext: retry, + onElse () { + throw err + }, + }) + }).then((res) => // ok, no net error, but what about a bad status code? + { + return maybeRetryOnStatusCodeFailure(res, { + opts, + requestId, + retryIntervals, + delaysRemaining, + retryOnStatusCodeFailure, + onNext: retry, + onElse: _.constant(res), + }) + }) +} + +const pipeEvent = (source, destination, event) => { + return source.on(event, (...args) => { + return destination.emit(event, ...args) + }) +} + +const createRetryingRequestStream = function (opts = {}) { const { requestId, retryIntervals, delaysRemaining, retryOnNetworkFailure, - retryOnStatusCodeFailure - } = opts; + retryOnStatusCodeFailure, + } = opts - let req = null; + let req = null - const delayStream = stream.PassThrough(); - let reqBodyBuffer = streamBuffer(); - const retryStream = duplexify(reqBodyBuffer, delayStream); + const delayStream = stream.PassThrough() + let reqBodyBuffer = streamBuffer() + const retryStream = duplexify(reqBodyBuffer, delayStream) - const cleanup = function() { + const cleanup = function () { if (reqBodyBuffer) { - //# null req body out to free memory - reqBodyBuffer.unpipeAll(); - return reqBodyBuffer = null; + // null req body out to free memory + reqBodyBuffer.unpipeAll() + reqBodyBuffer = null } - }; + } - const emitError = function(err) { - retryStream.emit("error", err); + const emitError = function (err) { + retryStream.emit('error', err) - return cleanup(); - }; + return cleanup() + } - var tryStartStream = function() { - //# if our request has been aborted - //# in the time that we were waiting to retry - //# then immediately bail + const tryStartStream = function () { + // if our request has been aborted + // in the time that we were waiting to retry + // then immediately bail if (retryStream.aborted) { - return; + return } - const reqStream = r(opts); - let didReceiveResponse = false; + const reqStream = r(opts) + let didReceiveResponse = false - const retry = function(delay, attempt) { - retryStream.emit("retry", { attempt, delay }); + const retry = function (delay, attempt) { + retryStream.emit('retry', { attempt, delay }) - return setTimeout(tryStartStream, delay); - }; + return setTimeout(tryStartStream, delay) + } - //# if we're retrying and we previous piped - //# into the reqStream, then reapply this now + // if we're retrying and we previous piped + // into the reqStream, then reapply this now if (req) { - reqStream.emit('pipe', req); - reqBodyBuffer.createReadStream().pipe(reqStream); + reqStream.emit('pipe', req) + reqBodyBuffer.createReadStream().pipe(reqStream) + } + + // forward the abort call to the underlying request + retryStream.abort = function () { + debug('aborting', { requestId }) + retryStream.aborted = true + + return reqStream.abort() + } + + const onPiped = function (src) { + // store this IncomingMessage so we can reapply it + // if we need to retry + req = src + + // https://github.com/request/request/blob/b3a218dc7b5689ce25be171e047f0d4f0eef8919/request.js#L493 + // the request lib expects this 'pipe' event in + // order to copy the request headers onto the + // outgoing message - so we manually pipe it here + return src.pipe(reqStream) } - //# forward the abort call to the underlying request - retryStream.abort = function() { - debug('aborting', { requestId }); - retryStream.aborted = true; - - return reqStream.abort(); - }; - - const onPiped = function(src) { - //# store this IncomingMessage so we can reapply it - //# if we need to retry - req = src; - - //# https://github.com/request/request/blob/b3a218dc7b5689ce25be171e047f0d4f0eef8919/request.js#L493 - //# the request lib expects this 'pipe' event in - //# order to copy the request headers onto the - //# outgoing message - so we manually pipe it here - return src.pipe(reqStream); - }; - - //# when this passthrough stream is being piped into - //# then make sure we properly "forward" and connect - //# forward it to the real reqStream which enables - //# request to read off the IncomingMessage readable stream - retryStream.once("pipe", onPiped); - - reqStream.on("error", function(err) { + // when this passthrough stream is being piped into + // then make sure we properly "forward" and connect + // forward it to the real reqStream which enables + // request to read off the IncomingMessage readable stream + retryStream.once('pipe', onPiped) + + reqStream.on('error', (err) => { if (didReceiveResponse) { - //# if we've already begun processing the requests - //# response, then that means we failed during transit - //# and its no longer safe to retry. all we can do now - //# is propogate the error upwards - debug("received an error on request after response started %o", merge(opts, { err })); + // if we've already begun processing the requests + // response, then that means we failed during transit + // and its no longer safe to retry. all we can do now + // is propogate the error upwards + debug('received an error on request after response started %o', merge(opts, { err })) - return emitError(err); + return emitError(err) } - //# otherwise, see if we can retry another request under the hood... + // otherwise, see if we can retry another request under the hood... return maybeRetryOnNetworkFailure(err, { opts, retryIntervals, delaysRemaining, retryOnNetworkFailure, onNext: retry, - onElse() { - return emitError(err); - } - }); - }); - - reqStream.once("request", req => //# remove the pipe listener since once the request has - //# been made, we cannot pipe into the reqStream anymore - retryStream.removeListener("pipe", onPiped)); - - return reqStream.once("response", function(incomingRes) { - didReceiveResponse = true; - - //# ok, no net error, but what about a bad status code? + onElse () { + return emitError(err) + }, + }) + }) + + reqStream.once('request', (req) => // remove the pipe listener since once the request has + // been made, we cannot pipe into the reqStream anymore + { + return retryStream.removeListener('pipe', onPiped) + }) + + return reqStream.once('response', (incomingRes) => { + didReceiveResponse = true + + // ok, no net error, but what about a bad status code? return maybeRetryOnStatusCodeFailure(incomingRes, { opts, requestId, @@ -346,401 +373,429 @@ const createRetryingRequestStream = function(opts = {}) { retryIntervals, retryOnStatusCodeFailure, onNext: retry, - onElse() { - debug("successful response received", { requestId }); + onElse () { + debug('successful response received', { requestId }) - cleanup(); + cleanup() - //# forward the response event upwards which should happen - //# prior to the pipe event, same as what request does - //# https://github.com/request/request/blob/master/request.js#L1059 - retryStream.emit("response", incomingRes); + // forward the response event upwards which should happen + // prior to the pipe event, same as what request does + // https://github.com/request/request/blob/master/request.js#L1059 + retryStream.emit('response', incomingRes) - reqStream.pipe(delayStream); + reqStream.pipe(delayStream) - //# `http.ClientRequest` events - return _.map(HTTP_CLIENT_REQUEST_EVENTS, _.partial(pipeEvent, reqStream, retryStream)); - } - }); - }); - }; + // `http.ClientRequest` events + return _.map(HTTP_CLIENT_REQUEST_EVENTS, _.partial(pipeEvent, reqStream, retryStream)) + }, + }) + }) + } - tryStartStream(); + tryStartStream() - return retryStream; -}; + return retryStream +} -const caseInsensitiveGet = function(obj, property) { - const lowercaseProperty = property.toLowerCase(); +const caseInsensitiveGet = function (obj, property) { + const lowercaseProperty = property.toLowerCase() for (let key of Object.keys(obj)) { if (key.toLowerCase() === lowercaseProperty) { - return obj[key]; + return obj[key] } } -}; +} -//# first, attempt to set on an existing property with differing case -//# if that fails, set the lowercase `property` -const caseInsensitiveSet = function(obj, property, val) { - const lowercaseProperty = property.toLowerCase(); +// first, attempt to set on an existing property with differing case +// if that fails, set the lowercase `property` +const caseInsensitiveSet = function (obj, property, val) { + const lowercaseProperty = property.toLowerCase() for (let key of Object.keys(obj)) { if (key.toLowerCase() === lowercaseProperty) { - return obj[key] = val; + obj[key] = val } } - return obj[lowercaseProperty] = val; -}; - -const setDefaults = opts => _ -.chain(opts) -.defaults({ - requestId: _.uniqueId('request'), - retryIntervals: [0, 1000, 2000, 2000], - retryOnNetworkFailure: true, - retryOnStatusCodeFailure: false -}) -.thru(opts => _.defaults(opts, { - delaysRemaining: _.clone(opts.retryIntervals) -})).value(); - -module.exports = function(options = {}) { + obj[lowercaseProperty] = val +} + +const setDefaults = (opts) => { + return _ + .chain(opts) + .defaults({ + requestId: _.uniqueId('request'), + retryIntervals: [0, 1000, 2000, 2000], + retryOnNetworkFailure: true, + retryOnStatusCodeFailure: false, + }) + .thru((opts) => { + return _.defaults(opts, { + delaysRemaining: _.clone(opts.retryIntervals), + }) + }).value() +} + +module.exports = function (options = {}) { const defaults = { timeout: options.timeout, agent, - //# send keep-alive with requests since Chrome won't send it in proxy mode - //# https://github.com/cypress-io/cypress/pull/3531#issuecomment-476269041 + // send keep-alive with requests since Chrome won't send it in proxy mode + // https://github.com/cypress-io/cypress/pull/3531#issuecomment-476269041 headers: { - "Connection": "keep-alive" + 'Connection': 'keep-alive', }, - proxy: null //# upstream proxying is handled by CombinedAgent - }; + proxy: null, // upstream proxying is handled by CombinedAgent + } - r = r.defaults(defaults); - rp = rp.defaults(defaults); + r = r.defaults(defaults) + rp = rp.defaults(defaults) return { - r: require("@cypress/request"), + r: require('@cypress/request'), - rp: require("@cypress/request-promise"), + rp: require('@cypress/request-promise'), getDelayForRetry, setDefaults, - create(strOrOpts, promise) { - let opts; - switch (false) { - case !_.isString(strOrOpts): - opts = { - url: strOrOpts - }; - break; - default: - opts = strOrOpts; + create (strOrOpts, promise) { + let opts + + if (_.isString(strOrOpts)) { + opts = { + url: strOrOpts, + } + } else { + opts = strOrOpts } - opts = setDefaults(opts); + opts = setDefaults(opts) if (promise) { - return createRetryingRequestPromise(opts); - } else { - return createRetryingRequestStream(opts); + return createRetryingRequestPromise(opts) } + + return createRetryingRequestStream(opts) }, - contentTypeIsJson(response) { - //# TODO: use https://github.com/jshttp/type-is for this - //# https://github.com/cypress-io/cypress/pull/5166 - return __guard__(__guard__(response != null ? response.headers : undefined, x1 => x1["content-type"]), x => x.split(';', 2)[0].endsWith("json")); + contentTypeIsJson (response) { + // TODO: use https://github.com/jshttp/type-is for this + // https://github.com/cypress-io/cypress/pull/5166 + return __guard__(__guard__(response != null ? response.headers : undefined, (x1) => { + return x1['content-type'] + }), (x) => { + return x.split(';', 2)[0].endsWith('json') + }) }, - parseJsonBody(body) { + parseJsonBody (body) { try { - return JSON.parse(body); + return JSON.parse(body) } catch (e) { - return body; + return body } }, - normalizeResponse(push, response) { - const req = response.request != null ? response.request : {}; + normalizeResponse (push, response) { + const req = response.request != null ? response.request : {} - push(response); + push(response) - response = _.pick(response, "statusCode", "body", "headers"); + response = _.pick(response, 'statusCode', 'body', 'headers') - //# normalize status - response.status = response.statusCode; - delete response.statusCode; + // normalize status + response.status = response.statusCode + delete response.statusCode _.extend(response, { - //# normalize what is an ok status code - statusText: statusCode.getText(response.status), + // normalize what is an ok status code + statusText: statusCode.getText(response.status), isOkStatusCode: statusCode.isOk(response.status), requestHeaders: getOriginalHeaders(req), - requestBody: req.body - }); + requestBody: req.body, + }) - //# if body is a string and content type is json - //# try to convert the body to JSON + // if body is a string and content type is json + // try to convert the body to JSON if (_.isString(response.body) && this.contentTypeIsJson(response)) { - response.body = this.parseJsonBody(response.body); + response.body = this.parseJsonBody(response.body) } - return response; + return response }, - setRequestCookieHeader(req, reqUrl, automationFn, existingHeader) { + setRequestCookieHeader (req, reqUrl, automationFn, existingHeader) { return automationFn('get:cookies', { url: reqUrl }) - .then(function(cookies) { - debug('got cookies from browser %o', { reqUrl, cookies }); - let header = cookies.map(cookie => `${cookie.name}=${cookie.value}`).join("; ") || undefined; + .then((cookies) => { + debug('got cookies from browser %o', { reqUrl, cookies }) + let header = cookies.map((cookie) => { + return `${cookie.name}=${cookie.value}` + }).join('; ') || undefined if (header) { if (existingHeader) { - //# existingHeader = whatever Cookie header the user is already trying to set - debug('there is an existing cookie header, merging %o', { header, existingHeader }); - //# order does not not matter here - //# @see https://tools.ietf.org/html/rfc6265#section-4.2.2 - header = [existingHeader, header].join(';'); + // existingHeader = whatever Cookie header the user is already trying to set + debug('there is an existing cookie header, merging %o', { header, existingHeader }) + // order does not not matter here + // @see https://tools.ietf.org/html/rfc6265#section-4.2.2 + header = [existingHeader, header].join(';') } - return caseInsensitiveSet(req.headers, 'Cookie', header); + return caseInsensitiveSet(req.headers, 'Cookie', header) } - }); + }) }, - setCookiesOnBrowser(res, resUrl, automationFn) { - let cookies = res.headers['set-cookie']; + setCookiesOnBrowser (res, resUrl, automationFn) { + let cookies = res.headers['set-cookie'] + if (!cookies) { - return Promise.resolve(); + return Promise.resolve() } if (!(cookies instanceof Array)) { - cookies = [cookies]; + cookies = [cookies] } - const parsedUrl = url.parse(resUrl); - const defaultDomain = parsedUrl.hostname; + const parsedUrl = url.parse(resUrl) + const defaultDomain = parsedUrl.hostname - debug('setting cookies on browser %o', { url: parsedUrl.href, defaultDomain, cookies }); + debug('setting cookies on browser %o', { url: parsedUrl.href, defaultDomain, cookies }) - return Promise.map(cookies, function(cyCookie) { - let cookie = tough.Cookie.parse(cyCookie, { loose: true }); + return Promise.map(cookies, (cyCookie) => { + let cookie = tough.Cookie.parse(cyCookie, { loose: true }) - debug('parsing cookie %o', { cyCookie, toughCookie: cookie }); + debug('parsing cookie %o', { cyCookie, toughCookie: cookie }) if (!cookie) { - //# ignore invalid cookies (same as browser behavior) - //# https://github.com/cypress-io/cypress/issues/6890 - debug('tough-cookie failed to parse, ignoring'); - return; + // ignore invalid cookies (same as browser behavior) + // https://github.com/cypress-io/cypress/issues/6890 + debug('tough-cookie failed to parse, ignoring') + + return } - cookie.name = cookie.key; + cookie.name = cookie.key if (!cookie.domain) { - //# take the domain from the URL - cookie.domain = defaultDomain; - cookie.hostOnly = true; + // take the domain from the URL + cookie.domain = defaultDomain + cookie.hostOnly = true } if (!tough.domainMatch(defaultDomain, cookie.domain)) { - debug('domain match failed:', { defaultDomain }); - return; + debug('domain match failed:', { defaultDomain }) + + return } - const expiry = cookie.expiryTime(); + const expiry = cookie.expiryTime() + if (isFinite(expiry)) { - cookie.expiry = expiry / 1000; + cookie.expiry = expiry / 1000 } - cookie.sameSite = convertSameSiteToughToExtension(cookie.sameSite, cyCookie); + cookie.sameSite = convertSameSiteToughToExtension(cookie.sameSite, cyCookie) - cookie = _.pick(cookie, SERIALIZABLE_COOKIE_PROPS); + cookie = _.pick(cookie, SERIALIZABLE_COOKIE_PROPS) - let automationCmd = 'set:cookie'; + let automationCmd = 'set:cookie' if (expiry <= 0) { - automationCmd = 'clear:cookie'; + automationCmd = 'clear:cookie' } return automationFn(automationCmd, cookie) - .catch(err => debug('automation threw an error during cookie change %o', { automationCmd, cyCookie, cookie, err })); - }); + .catch((err) => { + return debug('automation threw an error during cookie change %o', { automationCmd, cyCookie, cookie, err }) + }) + }) }, - sendStream(headers, automationFn, options = {}) { - let ua; + sendStream (headers, automationFn, options = {}) { + let ua + _.defaults(options, { headers: {}, - onBeforeReqInit(fn) { return fn(); } - }); + onBeforeReqInit (fn) { + return fn() + }, + }) - if (!caseInsensitiveGet(options.headers, "user-agent") && (ua = headers["user-agent"])) { - options.headers["user-agent"] = ua; + if (!caseInsensitiveGet(options.headers, 'user-agent') && (ua = headers['user-agent'])) { + options.headers['user-agent'] = ua } _.extend(options, { - strictSSL: false - }); + strictSSL: false, + }) - const self = this; + const self = this const { - followRedirect - } = options; + followRedirect, + } = options - let currentUrl = options.url; + let currentUrl = options.url - options.followRedirect = function(incomingRes) { + options.followRedirect = function (incomingRes) { if (followRedirect && !followRedirect(incomingRes)) { - return false; + return false } - const newUrl = url.resolve(currentUrl, incomingRes.headers.location); + const newUrl = url.resolve(currentUrl, incomingRes.headers.location) - //# and when we know we should follow the redirect - //# we need to override the init method and - //# first set the received cookies on the browser - //# and then grab the cookies for the new url + // and when we know we should follow the redirect + // we need to override the init method and + // first set the received cookies on the browser + // and then grab the cookies for the new url return self.setCookiesOnBrowser(incomingRes, currentUrl, automationFn) - .then(cookies => { - return self.setRequestCookieHeader(this, newUrl, automationFn); - }).then(() => { - currentUrl = newUrl; - return true; - }); - }; + .then((cookies) => { + return self.setRequestCookieHeader(this, newUrl, automationFn) + }).then(() => { + currentUrl = newUrl + + return true + }) + } return this.setRequestCookieHeader(options, options.url, automationFn, caseInsensitiveGet(options.headers, 'cookie')) .then(() => { return () => { - debug("sending request as stream %o", merge(options)); + debug('sending request as stream %o', merge(options)) - return this.create(options); - }; - }); + return this.create(options) + } + }) }, - sendPromise(headers, automationFn, options = {}) { - let a, c, ua; + sendPromise (headers, automationFn, options = {}) { + let a; let c; let ua + _.defaults(options, { headers: {}, gzip: true, cookies: true, - followRedirect: true - }); + followRedirect: true, + }) - if (!caseInsensitiveGet(options.headers, "user-agent") && (ua = headers["user-agent"])) { - options.headers["user-agent"] = ua; + if (!caseInsensitiveGet(options.headers, 'user-agent') && (ua = headers['user-agent'])) { + options.headers['user-agent'] = ua } - //# normalize case sensitivity - //# to be lowercase - if (a = options.headers.Accept) { - delete options.headers.Accept; - options.headers.accept = a; + // normalize case sensitivity + // to be lowercase + a = options.headers.Accept + + if (a) { + delete options.headers.Accept + options.headers.accept = a } - //# https://github.com/cypress-io/cypress/issues/338 + // https://github.com/cypress-io/cypress/issues/338 _.defaults(options.headers, { - accept: "*/*" - }); + accept: '*/*', + }) _.extend(options, { strictSSL: false, simple: false, - resolveWithFullResponse: true - }); + resolveWithFullResponse: true, + }) - //# https://github.com/cypress-io/cypress/issues/322 - //# either turn these both on or off - options.followAllRedirects = options.followRedirect; + // https://github.com/cypress-io/cypress/issues/322 + // either turn these both on or off + options.followAllRedirects = options.followRedirect if (options.form === true) { - //# reset form to whatever body is - //# and nuke body - options.form = options.body; - delete options.json; - delete options.body; + // reset form to whatever body is + // and nuke body + options.form = options.body + delete options.json + delete options.body } - const self = this; + const self = this const send = () => { - const ms = Date.now(); + const ms = Date.now() - const redirects = []; - const requestResponses = []; + const redirects = [] + const requestResponses = [] - const push = response => requestResponses.push(pick(response)); + const push = (response) => { + return requestResponses.push(pick(response)) + } - let currentUrl = options.url; + let currentUrl = options.url if (options.followRedirect) { - options.followRedirect = function(incomingRes) { - const newUrl = url.resolve(currentUrl, incomingRes.headers.location); + options.followRedirect = function (incomingRes) { + const newUrl = url.resolve(currentUrl, incomingRes.headers.location) - //# normalize the url - redirects.push([incomingRes.statusCode, newUrl].join(": ")); + // normalize the url + redirects.push([incomingRes.statusCode, newUrl].join(': ')) - push(incomingRes); + push(incomingRes) - //# and when we know we should follow the redirect - //# we need to override the init method and - //# first set the new cookies on the browser - //# and then grab the cookies for the new url + // and when we know we should follow the redirect + // we need to override the init method and + // first set the new cookies on the browser + // and then grab the cookies for the new url return self.setCookiesOnBrowser(incomingRes, currentUrl, automationFn) .then(() => { - return self.setRequestCookieHeader(this, newUrl, automationFn); - }).then(() => { - currentUrl = newUrl; - return true; - }); - }; + return self.setRequestCookieHeader(this, newUrl, automationFn) + }).then(() => { + currentUrl = newUrl + + return true + }) + } } return this.create(options, true) .then(this.normalizeResponse.bind(this, push)) - .then(resp => { - //# TODO: move duration somewhere...? - //# does node store this somewhere? - //# we could probably calculate this ourselves - //# by using the date headers - let loc; - resp.duration = Date.now() - ms; - resp.allRequestResponses = requestResponses; + .then((resp) => { + // TODO: move duration somewhere...? + // does node store this somewhere? + // we could probably calculate this ourselves + // by using the date headers + let loc + + resp.duration = Date.now() - ms + resp.allRequestResponses = requestResponses if (redirects.length) { - resp.redirects = redirects; + resp.redirects = redirects } if ((options.followRedirect === false) && (loc = resp.headers.location)) { - //# resolve the new location head against - //# the current url - resp.redirectedToUrl = url.resolve(options.url, loc); + // resolve the new location head against + // the current url + resp.redirectedToUrl = url.resolve(options.url, loc) } return this.setCookiesOnBrowser(resp, currentUrl, automationFn) - .return(resp); - }); - }; + .return(resp) + }) + } + + c = options.cookies - if ((c = options.cookies)) { + if (c) { return self.setRequestCookieHeader(options, options.url, automationFn, caseInsensitiveGet(options.headers, 'cookie')) - .then(send); - } else { - return send(); + .then(send) } - } - }; -}; + return send() + }, + + } +} -function __guard__(value, transform) { - return (typeof value !== 'undefined' && value !== null) ? transform(value) : undefined; -} \ No newline at end of file +function __guard__ (value, transform) { + return (typeof value !== 'undefined' && value !== null) ? transform(value) : undefined +} diff --git a/packages/server/test/unit/request_spec.js b/packages/server/test/unit/request_spec.js index 7257b5e28a87..8fb51ae024ca 100644 --- a/packages/server/test/unit/request_spec.js +++ b/packages/server/test/unit/request_spec.js @@ -1,951 +1,1043 @@ +/* eslint-disable + default-case, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. /* * decaffeinate suggestions: * DS102: Remove unnecessary code created because of implicit returns * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -require("../spec_helper"); +require('../spec_helper') -const _ = require("lodash"); -const http = require("http"); -const Bluebird = require("bluebird"); -const Request = require(`${root}lib/request`); -const snapshot = require("snap-shot-it"); +const _ = require('lodash') +const http = require('http') +const Bluebird = require('bluebird') +const Request = require(`${root}lib/request`) +const snapshot = require('snap-shot-it') -const request = Request({timeout: 100}); +const request = Request({ timeout: 100 }) -const testAttachingCookiesWith = function(fn) { - const set = sinon.spy(request, 'setCookiesOnBrowser'); - const get = sinon.spy(request, 'setRequestCookieHeader'); +const testAttachingCookiesWith = function (fn) { + const set = sinon.spy(request, 'setCookiesOnBrowser') + const get = sinon.spy(request, 'setRequestCookieHeader') - nock("http://localhost:1234") - .get("/") - .reply(302, "", { + nock('http://localhost:1234') + .get('/') + .reply(302, '', { 'set-cookie': 'one=1', - location: "/second" + location: '/second', }) - .get("/second") - .reply(302, "", { + .get('/second') + .reply(302, '', { 'set-cookie': 'two=2', - location: "/third" + location: '/third', + }) + .get('/third') + .reply(200, '', { + 'set-cookie': 'three=3', }) - .get("/third") - .reply(200, "", { - 'set-cookie': 'three=3' - }); return fn() - .then(() => snapshot({ - setCalls: set.getCalls().map(call => ({ - currentUrl: call.args[1], - setCookie: call.args[0].headers['set-cookie'] - })), - getCalls: get.getCalls().map(call => ({ - newUrl: _.get(call, 'args.1') - })) - })); -}; - -describe("lib/request", function() { - beforeEach(function() { - this.fn = sinon.stub(); - this.fn.withArgs('set:cookie').resolves({}); - return this.fn.withArgs('get:cookies').resolves([]); - }); - - it("is defined", () => expect(request).to.be.an("object")); - - context("#getDelayForRetry", function() { - it("divides by 10 when delay >= 1000 and err.code = ECONNREFUSED", function() { - const retryIntervals = [1,2,3,4]; - const delaysRemaining = [0, 999, 1000, 2000]; + .then(() => { + return snapshot({ + setCalls: set.getCalls().map((call) => { + return { + currentUrl: call.args[1], + setCookie: call.args[0].headers['set-cookie'], + } + }), + getCalls: get.getCalls().map((call) => { + return { + newUrl: _.get(call, 'args.1'), + } + }), + }) + }) +} + +describe('lib/request', () => { + beforeEach(function () { + this.fn = sinon.stub() + this.fn.withArgs('set:cookie').resolves({}) + + return this.fn.withArgs('get:cookies').resolves([]) + }) + + it('is defined', () => { + expect(request).to.be.an('object') + }) + + context('#getDelayForRetry', () => { + it('divides by 10 when delay >= 1000 and err.code = ECONNREFUSED', () => { + const retryIntervals = [1, 2, 3, 4] + const delaysRemaining = [0, 999, 1000, 2000] const err = { - code: "ECONNREFUSED" - }; + code: 'ECONNREFUSED', + } - const onNext = sinon.stub(); + const onNext = sinon.stub() - retryIntervals.forEach(() => request.getDelayForRetry({ - err, - onNext, - retryIntervals, - delaysRemaining, - })); + retryIntervals.forEach(() => { + return request.getDelayForRetry({ + err, + onNext, + retryIntervals, + delaysRemaining, + }) + }) + + expect(delaysRemaining).to.be.empty - expect(delaysRemaining).to.be.empty; - return expect(onNext.args).to.deep.eq([ + expect(onNext.args).to.deep.eq([ [0, 1], [999, 2], [100, 3], - [200, 4] - ]); - }); + [200, 4], + ]) + }) - it("does not divide by 10 when err.code != ECONNREFUSED", function() { - const retryIntervals = [1,2,3,4]; - const delaysRemaining = [2000, 2000, 2000, 2000]; + it('does not divide by 10 when err.code != ECONNREFUSED', () => { + const retryIntervals = [1, 2, 3, 4] + const delaysRemaining = [2000, 2000, 2000, 2000] const err = { - code: "ECONNRESET" - }; + code: 'ECONNRESET', + } - const onNext = sinon.stub(); + const onNext = sinon.stub() request.getDelayForRetry({ err, onNext, retryIntervals, delaysRemaining, - }); + }) - expect(delaysRemaining).to.have.length(3); - return expect(onNext).to.be.calledWith(2000, 1); - }); + expect(delaysRemaining).to.have.length(3) - return it("calls onElse when delaysRemaining is exhausted", function() { - const retryIntervals = [1,2,3,4]; - const delaysRemaining = []; + expect(onNext).to.be.calledWith(2000, 1) + }) - const onNext = sinon.stub(); - const onElse = sinon.stub(); + it('calls onElse when delaysRemaining is exhausted', () => { + const retryIntervals = [1, 2, 3, 4] + const delaysRemaining = [] + + const onNext = sinon.stub() + const onElse = sinon.stub() request.getDelayForRetry({ onElse, onNext, retryIntervals, delaysRemaining, - }); + }) + + expect(onElse).to.be.calledWithExactly() - expect(onElse).to.be.calledWithExactly(); - return expect(onNext).not.to.be.called; - }); - }); + expect(onNext).not.to.be.called + }) + }) + + context('#setDefaults', () => { + it('delaysRemaining to retryIntervals clone', () => { + const retryIntervals = [1, 2, 3, 4] - context("#setDefaults", function() { - it("delaysRemaining to retryIntervals clone", function() { - const retryIntervals = [1,2,3,4]; + const opts = request.setDefaults({ retryIntervals }) - const opts = request.setDefaults({ retryIntervals }); + expect(opts.retryIntervals).to.eq(retryIntervals) + expect(opts.delaysRemaining).not.to.eq(retryIntervals) - expect(opts.retryIntervals).to.eq(retryIntervals); - expect(opts.delaysRemaining).not.to.eq(retryIntervals); - return expect(opts.delaysRemaining).to.deep.eq(retryIntervals); - }); + expect(opts.delaysRemaining).to.deep.eq(retryIntervals) + }) - it("retryIntervals to [0, 1000, 2000, 2000] by default", function() { - const opts = request.setDefaults({}); + it('retryIntervals to [0, 1000, 2000, 2000] by default', () => { + const opts = request.setDefaults({}) - return expect(opts.retryIntervals).to.deep.eq([0, 1000, 2000, 2000]); - }); + expect(opts.retryIntervals).to.deep.eq([0, 1000, 2000, 2000]) + }) - return it("delaysRemaining can be overridden", function() { - const delaysRemaining = [1]; - const opts = request.setDefaults({ delaysRemaining }); + it('delaysRemaining can be overridden', () => { + const delaysRemaining = [1] + const opts = request.setDefaults({ delaysRemaining }) - return expect(opts.delaysRemaining).to.eq(delaysRemaining); - }); - }); + expect(opts.delaysRemaining).to.eq(delaysRemaining) + }) + }) - context("#normalizeResponse", function() { - beforeEach(function() { - return this.push = sinon.stub(); - }); + context('#normalizeResponse', () => { + beforeEach(function () { + this.push = sinon.stub() + }) - it("sets status to statusCode and deletes statusCode", function() { + it('sets status to statusCode and deletes statusCode', function () { expect(request.normalizeResponse(this.push, { statusCode: 404, request: { - headers: {foo: "bar"}, - body: "body" - } + headers: { foo: 'bar' }, + body: 'body', + }, })).to.deep.eq({ status: 404, - statusText: "Not Found", + statusText: 'Not Found', isOkStatusCode: false, - requestHeaders: {foo: "bar"}, - requestBody: "body" - }); + requestHeaders: { foo: 'bar' }, + requestBody: 'body', + }) - return expect(this.push).to.be.calledOnce; - }); + expect(this.push).to.be.calledOnce + }) - return it("picks out status body and headers", function() { + it('picks out status body and headers', function () { expect(request.normalizeResponse(this.push, { - foo: "bar", + foo: 'bar', req: {}, originalHeaders: {}, - headers: {"Content-Length": 50}, - body: "foo", + headers: { 'Content-Length': 50 }, + body: 'foo', statusCode: 200, request: { - headers: {foo: "bar"}, - body: "body" - } + headers: { foo: 'bar' }, + body: 'body', + }, })).to.deep.eq({ - body: "foo", - headers: {"Content-Length": 50}, + body: 'foo', + headers: { 'Content-Length': 50 }, status: 200, - statusText: "OK", + statusText: 'OK', isOkStatusCode: true, - requestHeaders: {foo: "bar"}, - requestBody: "body" - }); + requestHeaders: { foo: 'bar' }, + requestBody: 'body', + }) - return expect(this.push).to.be.calledOnce; - }); - }); + expect(this.push).to.be.calledOnce + }) + }) - context("#create", function() { - beforeEach(function(done) { - this.hits = 0; + context('#create', () => { + beforeEach(function (done) { + this.hits = 0 this.srv = http.createServer((req, res) => { - this.hits++; + this.hits++ switch (req.url) { - case "/never-ends": - res.writeHead(200); - return res.write("foo\n"); - case "/econnreset": - return req.socket.destroy(); + case '/never-ends': + res.writeHead(200) + + return res.write('foo\n') + case '/econnreset': + return req.socket.destroy() } - }); + }) - return this.srv.listen(9988, done); - }); + return this.srv.listen(9988, done) + }) - afterEach(function() { - return this.srv.close(); - }); + afterEach(function () { + return this.srv.close() + }) - context("retries for streams", function() { - it("does not retry on a timeout", function() { + context('retries for streams', () => { + it('does not retry on a timeout', () => { const opts = request.setDefaults({ - url: "http://localhost:9988/never-ends", - timeout: 1000 - }); + url: 'http://localhost:9988/never-ends', + timeout: 1000, + }) - const stream = request.create(opts); + const stream = request.create(opts) - let retries = 0; + let retries = 0 - stream.on("retry", () => retries++); + stream.on('retry', () => { + return retries++ + }) - const p = Bluebird.fromCallback(cb => stream.on("error", cb)); + const p = Bluebird.fromCallback((cb) => { + return stream.on('error', cb) + }) - return expect(p).to.be.rejected - .then(function(err) { - expect(err.code).to.eq('ESOCKETTIMEDOUT'); - return expect(retries).to.eq(0); - }); - }); + expect(p).to.be.rejected + .then((err) => { + expect(err.code).to.eq('ESOCKETTIMEDOUT') - it("retries 4x on a connection reset", function() { + expect(retries).to.eq(0) + }) + }) + + it('retries 4x on a connection reset', () => { const opts = { - url: "http://localhost:9988/econnreset", + url: 'http://localhost:9988/econnreset', retryIntervals: [0, 1, 2, 3], - timeout: 1000 - }; + timeout: 1000, + } - const stream = request.create(opts); + const stream = request.create(opts) - let retries = 0; + let retries = 0 - stream.on("retry", () => retries++); + stream.on('retry', () => { + return retries++ + }) - const p = Bluebird.fromCallback(cb => stream.on("error", cb)); + const p = Bluebird.fromCallback((cb) => { + return stream.on('error', cb) + }) - return expect(p).to.be.rejected - .then(function(err) { - expect(err.code).to.eq('ECONNRESET'); - return expect(retries).to.eq(4); - }); - }); + expect(p).to.be.rejected + .then((err) => { + expect(err.code).to.eq('ECONNRESET') - return it("retries 4x on a NXDOMAIN (ENOTFOUND)", function() { - nock.enableNetConnect(); + expect(retries).to.eq(4) + }) + }) + + it('retries 4x on a NXDOMAIN (ENOTFOUND)', () => { + nock.enableNetConnect() const opts = { - url: "http://will-never-exist.invalid.example.com", + url: 'http://will-never-exist.invalid.example.com', retryIntervals: [0, 1, 2, 3], - timeout: 1000 - }; + timeout: 1000, + } + + const stream = request.create(opts) - const stream = request.create(opts); + let retries = 0 - let retries = 0; + stream.on('retry', () => { + return retries++ + }) - stream.on("retry", () => retries++); + const p = Bluebird.fromCallback((cb) => { + return stream.on('error', cb) + }) - const p = Bluebird.fromCallback(cb => stream.on("error", cb)); + expect(p).to.be.rejected + .then((err) => { + expect(err.code).to.eq('ENOTFOUND') - return expect(p).to.be.rejected - .then(function(err) { - expect(err.code).to.eq('ENOTFOUND'); - return expect(retries).to.eq(4); - }); - }); - }); + expect(retries).to.eq(4) + }) + }) + }) - return context("retries for promises", function() { - it("does not retry on a timeout", function() { + context('retries for promises', () => { + it('does not retry on a timeout', function () { const opts = { - url: "http://localhost:9988/never-ends", - timeout: 100 - }; + url: 'http://localhost:9988/never-ends', + timeout: 100, + } return request.create(opts, true) - .then(function() { - throw new Error('should not reach');}).catch(err => { - expect(err.error.code).to.eq('ESOCKETTIMEDOUT'); - return expect(this.hits).to.eq(1); - }); - }); - - return it("retries 4x on a connection reset", function() { + .then(() => { + throw new Error('should not reach') + }).catch((err) => { + expect(err.error.code).to.eq('ESOCKETTIMEDOUT') + + expect(this.hits).to.eq(1) + }) + }) + + it('retries 4x on a connection reset', function () { const opts = { - url: "http://localhost:9988/econnreset", + url: 'http://localhost:9988/econnreset', retryIntervals: [0, 1, 2, 3], - timeout: 250 - }; + timeout: 250, + } return request.create(opts, true) - .then(function() { - throw new Error('should not reach');}).catch(err => { - expect(err.error.code).to.eq('ECONNRESET'); - return expect(this.hits).to.eq(5); - }); - }); - }); - }); - - context("#sendPromise", function() { - it("sets strictSSL=false", function() { - const init = sinon.spy(request.rp.Request.prototype, "init"); - - nock("http://www.github.com") - .get("/foo") - .reply(200, "hello", { - "Content-Type": "text/html" - }); + .then(() => { + throw new Error('should not reach') + }).catch((err) => { + expect(err.error.code).to.eq('ECONNRESET') - return request.sendPromise({}, this.fn, { - url: "http://www.github.com/foo", - cookies: false + expect(this.hits).to.eq(5) + }) }) - .then(() => expect(init).to.be.calledWithMatch({strictSSL: false})); - }); + }) + }) + + context('#sendPromise', () => { + it('sets strictSSL=false', function () { + const init = sinon.spy(request.rp.Request.prototype, 'init') - it("sets simple=false", function() { - nock("http://www.github.com") - .get("/foo") - .reply(500, ""); + nock('http://www.github.com') + .get('/foo') + .reply(200, 'hello', { + 'Content-Type': 'text/html', + }) - //# should not bomb on 500 - //# because simple = false return request.sendPromise({}, this.fn, { - url: "http://www.github.com/foo", - cookies: false - }); - }); - - it("sets resolveWithFullResponse=true", function() { - nock("http://www.github.com") - .get("/foo") - .reply(200, "hello", { - "Content-Type": "text/html" - }); + url: 'http://www.github.com/foo', + cookies: false, + }) + .then(() => { + expect(init).to.be.calledWithMatch({ strictSSL: false }) + }) + }) + it('sets simple=false', function () { + nock('http://www.github.com') + .get('/foo') + .reply(500, '') + + // should not bomb on 500 + // because simple = false return request.sendPromise({}, this.fn, { - url: "http://www.github.com/foo", + url: 'http://www.github.com/foo', cookies: false, - body: "foobarbaz" }) - .then(function(resp) { - expect(resp).to.have.keys("status", "body", "headers", "duration", "isOkStatusCode", "statusText", "allRequestResponses", "requestBody", "requestHeaders"); + }) + + it('sets resolveWithFullResponse=true', function () { + nock('http://www.github.com') + .get('/foo') + .reply(200, 'hello', { + 'Content-Type': 'text/html', + }) - expect(resp.status).to.eq(200); - expect(resp.statusText).to.eq("OK"); - expect(resp.body).to.eq("hello"); - expect(resp.headers).to.deep.eq({"content-type": "text/html"}); - expect(resp.isOkStatusCode).to.be.true; - expect(resp.requestBody).to.eq("foobarbaz"); + return request.sendPromise({}, this.fn, { + url: 'http://www.github.com/foo', + cookies: false, + body: 'foobarbaz', + }) + .then((resp) => { + expect(resp).to.have.keys('status', 'body', 'headers', 'duration', 'isOkStatusCode', 'statusText', 'allRequestResponses', 'requestBody', 'requestHeaders') + + expect(resp.status).to.eq(200) + expect(resp.statusText).to.eq('OK') + expect(resp.body).to.eq('hello') + expect(resp.headers).to.deep.eq({ 'content-type': 'text/html' }) + expect(resp.isOkStatusCode).to.be.true + expect(resp.requestBody).to.eq('foobarbaz') expect(resp.requestHeaders).to.deep.eq({ - "accept": "*/*", - "accept-encoding": "gzip, deflate", - "connection": "keep-alive", - "content-length": 9, - "host": "www.github.com" - }); - return expect(resp.allRequestResponses).to.deep.eq([ + 'accept': '*/*', + 'accept-encoding': 'gzip, deflate', + 'connection': 'keep-alive', + 'content-length': 9, + 'host': 'www.github.com', + }) + + expect(resp.allRequestResponses).to.deep.eq([ { - "Request Body": "foobarbaz", - "Request Headers": {"accept": "*/*", "accept-encoding": "gzip, deflate", "connection": "keep-alive", "content-length": 9, "host": "www.github.com"}, - "Request URL": "http://www.github.com/foo", - "Response Body": "hello", - "Response Headers": {"content-type": "text/html"}, - "Response Status": 200 - } - ]); - }); - }); - - it("includes redirects", function() { - this.fn.resolves(); - - nock("http://www.github.com") - .get("/dashboard") + 'Request Body': 'foobarbaz', + 'Request Headers': { 'accept': '*/*', 'accept-encoding': 'gzip, deflate', 'connection': 'keep-alive', 'content-length': 9, 'host': 'www.github.com' }, + 'Request URL': 'http://www.github.com/foo', + 'Response Body': 'hello', + 'Response Headers': { 'content-type': 'text/html' }, + 'Response Status': 200, + }, + ]) + }) + }) + + it('includes redirects', function () { + this.fn.resolves() + + nock('http://www.github.com') + .get('/dashboard') .reply(301, null, { - "location": "/auth" + 'location': '/auth', }) - .get("/auth") + .get('/auth') .reply(302, null, { - "location": "/login" + 'location': '/login', + }) + .get('/login') + .reply(200, 'log in', { + 'Content-Type': 'text/html', }) - .get("/login") - .reply(200, "log in", { - "Content-Type": "text/html" - }); return request.sendPromise({}, this.fn, { - url: "http://www.github.com/dashboard", - cookies: false - }) - .then(function(resp) { - expect(resp).to.have.keys("status", "body", "headers", "duration", "isOkStatusCode", "statusText", "allRequestResponses", "redirects", "requestBody", "requestHeaders"); - - expect(resp.status).to.eq(200); - expect(resp.statusText).to.eq("OK"); - expect(resp.body).to.eq("log in"); - expect(resp.headers).to.deep.eq({"content-type": "text/html"}); - expect(resp.isOkStatusCode).to.be.true; - expect(resp.requestBody).to.be.undefined; + url: 'http://www.github.com/dashboard', + cookies: false, + }) + .then((resp) => { + expect(resp).to.have.keys('status', 'body', 'headers', 'duration', 'isOkStatusCode', 'statusText', 'allRequestResponses', 'redirects', 'requestBody', 'requestHeaders') + + expect(resp.status).to.eq(200) + expect(resp.statusText).to.eq('OK') + expect(resp.body).to.eq('log in') + expect(resp.headers).to.deep.eq({ 'content-type': 'text/html' }) + expect(resp.isOkStatusCode).to.be.true + expect(resp.requestBody).to.be.undefined expect(resp.redirects).to.deep.eq([ - "301: http://www.github.com/auth", - "302: http://www.github.com/login" - ]); + '301: http://www.github.com/auth', + '302: http://www.github.com/login', + ]) + expect(resp.requestHeaders).to.deep.eq({ - "accept": "*/*", - "accept-encoding": "gzip, deflate", - "connection": "keep-alive", - "referer": "http://www.github.com/auth", - "host": "www.github.com" - }); - return expect(resp.allRequestResponses).to.deep.eq([ + 'accept': '*/*', + 'accept-encoding': 'gzip, deflate', + 'connection': 'keep-alive', + 'referer': 'http://www.github.com/auth', + 'host': 'www.github.com', + }) + + expect(resp.allRequestResponses).to.deep.eq([ { - "Request Body": null, - "Request Headers": {"accept": "*/*", "accept-encoding": "gzip, deflate", "connection": "keep-alive", "host": "www.github.com"}, - "Request URL": "http://www.github.com/dashboard", - "Response Body": null, - "Response Headers": {"content-type": "application/json", "location": "/auth"}, - "Response Status": 301 + 'Request Body': null, + 'Request Headers': { 'accept': '*/*', 'accept-encoding': 'gzip, deflate', 'connection': 'keep-alive', 'host': 'www.github.com' }, + 'Request URL': 'http://www.github.com/dashboard', + 'Response Body': null, + 'Response Headers': { 'content-type': 'application/json', 'location': '/auth' }, + 'Response Status': 301, }, { - "Request Body": null, - "Request Headers": {"accept": "*/*", "accept-encoding": "gzip, deflate", "connection": "keep-alive", "host": "www.github.com", "referer": "http://www.github.com/dashboard"}, - "Request URL": "http://www.github.com/auth", - "Response Body": null, - "Response Headers": {"content-type": "application/json", "location": "/login"}, - "Response Status": 302 + 'Request Body': null, + 'Request Headers': { 'accept': '*/*', 'accept-encoding': 'gzip, deflate', 'connection': 'keep-alive', 'host': 'www.github.com', 'referer': 'http://www.github.com/dashboard' }, + 'Request URL': 'http://www.github.com/auth', + 'Response Body': null, + 'Response Headers': { 'content-type': 'application/json', 'location': '/login' }, + 'Response Status': 302, }, { - "Request Body": null, - "Request Headers": {"accept": "*/*", "accept-encoding": "gzip, deflate", "connection": "keep-alive", "host": "www.github.com", "referer": "http://www.github.com/auth"}, - "Request URL": "http://www.github.com/login", - "Response Body": "log in", - "Response Headers": {"content-type": "text/html"}, - "Response Status": 200 - } - ]); - }); - }); - - it("catches errors", function() { - nock.enableNetConnect(); - - const req = Request({ timeout: 2000 }); + 'Request Body': null, + 'Request Headers': { 'accept': '*/*', 'accept-encoding': 'gzip, deflate', 'connection': 'keep-alive', 'host': 'www.github.com', 'referer': 'http://www.github.com/auth' }, + 'Request URL': 'http://www.github.com/login', + 'Response Body': 'log in', + 'Response Headers': { 'content-type': 'text/html' }, + 'Response Status': 200, + }, + ]) + }) + }) + + it('catches errors', function () { + nock.enableNetConnect() + + const req = Request({ timeout: 2000 }) return req.sendPromise({}, this.fn, { - url: "http://localhost:1111/foo", - cookies: false + url: 'http://localhost:1111/foo', + cookies: false, }) - .then(function() { - throw new Error("should have failed but didnt");}).catch(err => expect(err.message).to.eq("Error: connect ECONNREFUSED 127.0.0.1:1111")); - }); + .then(() => { + throw new Error('should have failed but didnt') + }).catch((err) => { + expect(err.message).to.eq('Error: connect ECONNREFUSED 127.0.0.1:1111') + }) + }) - it("parses response body as json if content-type application/json response headers", function() { - nock("http://localhost:8080") - .get("/status.json") - .reply(200, JSON.stringify({status: "ok"}), { - "Content-Type": "application/json" - }); + it('parses response body as json if content-type application/json response headers', function () { + nock('http://localhost:8080') + .get('/status.json') + .reply(200, JSON.stringify({ status: 'ok' }), { + 'Content-Type': 'application/json', + }) return request.sendPromise({}, this.fn, { - url: "http://localhost:8080/status.json", - cookies: false + url: 'http://localhost:8080/status.json', + cookies: false, }) - .then(resp => expect(resp.body).to.deep.eq({status: "ok"})); - }); + .then((resp) => { + expect(resp.body).to.deep.eq({ status: 'ok' }) + }) + }) - it("parses response body as json if content-type application/vnd.api+json response headers", function() { - nock("http://localhost:8080") - .get("/status.json") - .reply(200, JSON.stringify({status: "ok"}), { - "Content-Type": "application/vnd.api+json" - }); + it('parses response body as json if content-type application/vnd.api+json response headers', function () { + nock('http://localhost:8080') + .get('/status.json') + .reply(200, JSON.stringify({ status: 'ok' }), { + 'Content-Type': 'application/vnd.api+json', + }) return request.sendPromise({}, this.fn, { - url: "http://localhost:8080/status.json", - cookies: false + url: 'http://localhost:8080/status.json', + cookies: false, }) - .then(resp => expect(resp.body).to.deep.eq({status: "ok"})); - }); + .then((resp) => { + expect(resp.body).to.deep.eq({ status: 'ok' }) + }) + }) - it("revives from parsing bad json", function() { - nock("http://localhost:8080") - .get("/status.json") - .reply(200, "{bad: 'json'}", { - "Content-Type": "application/json" - }); + it('revives from parsing bad json', function () { + nock('http://localhost:8080') + .get('/status.json') + .reply(200, '{bad: \'json\'}', { + 'Content-Type': 'application/json', + }) return request.sendPromise({}, this.fn, { - url: "http://localhost:8080/status.json", - cookies: false + url: 'http://localhost:8080/status.json', + cookies: false, + }) + .then((resp) => { + expect(resp.body).to.eq('{bad: \'json\'}') }) - .then(resp => expect(resp.body).to.eq("{bad: 'json'}")); - }); + }) - it("sets duration on response", function() { - nock("http://localhost:8080") - .get("/foo") + it('sets duration on response', function () { + nock('http://localhost:8080') + .get('/foo') .delay(10) - .reply(200, "123", { - "Content-Type": "text/plain" - }); + .reply(200, '123', { + 'Content-Type': 'text/plain', + }) return request.sendPromise({}, this.fn, { - url: "http://localhost:8080/foo", - cookies: false + url: 'http://localhost:8080/foo', + cookies: false, + }) + .then((resp) => { + expect(resp.duration).to.be.a('Number') + + expect(resp.duration).to.be.gt(0) }) - .then(function(resp) { - expect(resp.duration).to.be.a("Number"); - return expect(resp.duration).to.be.gt(0); - }); - }); + }) - it("sends up user-agent headers", function() { - nock("http://localhost:8080") - .matchHeader("user-agent", "foobarbaz") - .get("/foo") - .reply(200, "derp"); + it('sends up user-agent headers', function () { + nock('http://localhost:8080') + .matchHeader('user-agent', 'foobarbaz') + .get('/foo') + .reply(200, 'derp') - const headers = {}; - headers["user-agent"] = "foobarbaz"; + const headers = {} + + headers['user-agent'] = 'foobarbaz' return request.sendPromise(headers, this.fn, { - url: "http://localhost:8080/foo", - cookies: false + url: 'http://localhost:8080/foo', + cookies: false, + }) + .then((resp) => { + expect(resp.body).to.eq('derp') }) - .then(resp => expect(resp.body).to.eq("derp")); - }); + }) - it("sends connection: keep-alive by default", function() { - nock("http://localhost:8080") - .matchHeader("connection", "keep-alive") - .get("/foo") - .reply(200, "it worked"); + it('sends connection: keep-alive by default', function () { + nock('http://localhost:8080') + .matchHeader('connection', 'keep-alive') + .get('/foo') + .reply(200, 'it worked') return request.sendPromise({}, this.fn, { - url: "http://localhost:8080/foo", - cookies: false + url: 'http://localhost:8080/foo', + cookies: false, + }) + .then((resp) => { + expect(resp.body).to.eq('it worked') }) - .then(resp => expect(resp.body).to.eq("it worked")); - }); + }) + + it('lower cases headers', function () { + nock('http://localhost:8080') + .matchHeader('test', 'true') + .get('/foo') + .reply(200, 'derp') - it("lower cases headers", function() { - nock("http://localhost:8080") - .matchHeader("test", "true") - .get("/foo") - .reply(200, "derp"); + const headers = {} - const headers = {}; - headers["user-agent"] = "foobarbaz"; + headers['user-agent'] = 'foobarbaz' return request.sendPromise(headers, this.fn, { - url: "http://localhost:8080/foo", + url: 'http://localhost:8080/foo', cookies: false, headers: { 'TEST': true, - } + }, + }) + .then((resp) => { + expect(resp.body).to.eq('derp') }) - .then(resp => expect(resp.body).to.eq("derp")); - }); + }) - it("allows overriding user-agent in headers", function() { - nock("http://localhost:8080") - .matchHeader("user-agent", "custom-agent") - .get("/foo") - .reply(200, "derp"); + it('allows overriding user-agent in headers', function () { + nock('http://localhost:8080') + .matchHeader('user-agent', 'custom-agent') + .get('/foo') + .reply(200, 'derp') - const headers = {'user-agent': 'test'}; + const headers = { 'user-agent': 'test' } return request.sendPromise(headers, this.fn, { - url: "http://localhost:8080/foo", + url: 'http://localhost:8080/foo', cookies: false, headers: { - 'User-Agent': "custom-agent", + 'User-Agent': 'custom-agent', }, }) - .then(resp => expect(resp.body).to.eq("derp")); - }); + .then((resp) => { + expect(resp.body).to.eq('derp') + }) + }) - context("accept header", function() { - it("sets to */* by default", function() { - nock("http://localhost:8080") - .matchHeader("accept", "*/*") - .get("/headers") - .reply(200); + context('accept header', () => { + it('sets to */* by default', function () { + nock('http://localhost:8080') + .matchHeader('accept', '*/*') + .get('/headers') + .reply(200) return request.sendPromise({}, this.fn, { - url: "http://localhost:8080/headers", - cookies: false + url: 'http://localhost:8080/headers', + cookies: false, + }) + .then((resp) => { + expect(resp.status).to.eq(200) }) - .then(resp => expect(resp.status).to.eq(200)); - }); + }) - it("can override accept header", function() { - nock("http://localhost:8080") - .matchHeader("accept", "text/html") - .get("/headers") - .reply(200); + it('can override accept header', function () { + nock('http://localhost:8080') + .matchHeader('accept', 'text/html') + .get('/headers') + .reply(200) return request.sendPromise({}, this.fn, { - url: "http://localhost:8080/headers", + url: 'http://localhost:8080/headers', cookies: false, headers: { - accept: "text/html" - } + accept: 'text/html', + }, + }) + .then((resp) => { + expect(resp.status).to.eq(200) }) - .then(resp => expect(resp.status).to.eq(200)); - }); + }) - return it("can override Accept header", function() { - nock("http://localhost:8080") - .matchHeader("accept", "text/plain") - .get("/headers") - .reply(200); + it('can override Accept header', function () { + nock('http://localhost:8080') + .matchHeader('accept', 'text/plain') + .get('/headers') + .reply(200) return request.sendPromise({}, this.fn, { - url: "http://localhost:8080/headers", + url: 'http://localhost:8080/headers', cookies: false, headers: { - Accept: "text/plain" - } + Accept: 'text/plain', + }, }) - .then(resp => expect(resp.status).to.eq(200)); - }); - }); + .then((resp) => { + expect(resp.status).to.eq(200) + }) + }) + }) - context("qs", () => it("can accept qs", function() { - nock("http://localhost:8080") - .get("/foo?bar=baz&q=1") - .reply(200); + context('qs', () => { + it('can accept qs', function () { + nock('http://localhost:8080') + .get('/foo?bar=baz&q=1') + .reply(200) - return request.sendPromise({}, this.fn, { - url: "http://localhost:8080/foo", - cookies: false, - qs: { - bar: "baz", - q: 1 - } + return request.sendPromise({}, this.fn, { + url: 'http://localhost:8080/foo', + cookies: false, + qs: { + bar: 'baz', + q: 1, + }, + }) + .then((resp) => { + expect(resp.status).to.eq(200) + }) }) - .then(resp => expect(resp.status).to.eq(200)); - })); + }) - context("followRedirect", function() { - beforeEach(function() { - return this.fn.resolves(); - }); + context('followRedirect', () => { + beforeEach(function () { + return this.fn.resolves() + }) - it("by default follow redirects", function() { - nock("http://localhost:8080") - .get("/dashboard") - .reply(302, "", { - location: "http://localhost:8080/login" + it('by default follow redirects', function () { + nock('http://localhost:8080') + .get('/dashboard') + .reply(302, '', { + location: 'http://localhost:8080/login', }) - .get("/login") - .reply(200, "login"); + .get('/login') + .reply(200, 'login') return request.sendPromise({}, this.fn, { - url: "http://localhost:8080/dashboard", + url: 'http://localhost:8080/dashboard', cookies: false, - followRedirect: true + followRedirect: true, + }) + .then((resp) => { + expect(resp.status).to.eq(200) + expect(resp.body).to.eq('login') + + expect(resp).not.to.have.property('redirectedToUrl') }) - .then(function(resp) { - expect(resp.status).to.eq(200); - expect(resp.body).to.eq("login"); - return expect(resp).not.to.have.property("redirectedToUrl"); - }); - }); + }) - it("follows non-GET redirects by default", function() { - nock("http://localhost:8080") - .post("/login") - .reply(302, "", { - location: "http://localhost:8080/dashboard" + it('follows non-GET redirects by default', function () { + nock('http://localhost:8080') + .post('/login') + .reply(302, '', { + location: 'http://localhost:8080/dashboard', }) - .get("/dashboard") - .reply(200, "dashboard"); + .get('/dashboard') + .reply(200, 'dashboard') return request.sendPromise({}, this.fn, { - method: "POST", - url: "http://localhost:8080/login", - cookies: false - }) - .then(function(resp) { - expect(resp.status).to.eq(200); - expect(resp.body).to.eq("dashboard"); - return expect(resp).not.to.have.property("redirectedToUrl"); - }); - }); - - it("can turn off following redirects", function() { - nock("http://localhost:8080") - .get("/dashboard") - .reply(302, "", { - location: "http://localhost:8080/login" - }) - .get("/login") - .reply(200, "login"); + method: 'POST', + url: 'http://localhost:8080/login', + cookies: false, + }) + .then((resp) => { + expect(resp.status).to.eq(200) + expect(resp.body).to.eq('dashboard') + + expect(resp).not.to.have.property('redirectedToUrl') + }) + }) + + it('can turn off following redirects', function () { + nock('http://localhost:8080') + .get('/dashboard') + .reply(302, '', { + location: 'http://localhost:8080/login', + }) + .get('/login') + .reply(200, 'login') return request.sendPromise({}, this.fn, { - url: "http://localhost:8080/dashboard", + url: 'http://localhost:8080/dashboard', cookies: false, - followRedirect: false + followRedirect: false, }) - .then(function(resp) { - expect(resp.status).to.eq(302); - expect(resp.body).to.eq(""); - return expect(resp.redirectedToUrl).to.eq("http://localhost:8080/login"); - }); - }); + .then((resp) => { + expect(resp.status).to.eq(302) + expect(resp.body).to.eq('') - it("resolves redirectedToUrl on relative redirects", function() { - nock("http://localhost:8080") - .get("/dashboard") - .reply(302, "", { - location: "/login" //# absolute-relative pathname + expect(resp.redirectedToUrl).to.eq('http://localhost:8080/login') }) - .get("/login") - .reply(200, "login"); + }) + + it('resolves redirectedToUrl on relative redirects', function () { + nock('http://localhost:8080') + .get('/dashboard') + .reply(302, '', { + location: '/login', // absolute-relative pathname + }) + .get('/login') + .reply(200, 'login') return request.sendPromise({}, this.fn, { - url: "http://localhost:8080/dashboard", + url: 'http://localhost:8080/dashboard', cookies: false, - followRedirect: false + followRedirect: false, }) - .then(function(resp) { - expect(resp.status).to.eq(302); - return expect(resp.redirectedToUrl).to.eq("http://localhost:8080/login"); - }); - }); + .then((resp) => { + expect(resp.status).to.eq(302) + + expect(resp.redirectedToUrl).to.eq('http://localhost:8080/login') + }) + }) - it("resolves redirectedToUrl to another domain", function() { - nock("http://localhost:8080") - .get("/dashboard") - .reply(301, "", { - location: "https://www.google.com/login" + it('resolves redirectedToUrl to another domain', function () { + nock('http://localhost:8080') + .get('/dashboard') + .reply(301, '', { + location: 'https://www.google.com/login', }) - .get("/login") - .reply(200, "login"); + .get('/login') + .reply(200, 'login') return request.sendPromise({}, this.fn, { - url: "http://localhost:8080/dashboard", + url: 'http://localhost:8080/dashboard', cookies: false, - followRedirect: false + followRedirect: false, }) - .then(function(resp) { - expect(resp.status).to.eq(301); - return expect(resp.redirectedToUrl).to.eq("https://www.google.com/login"); - }); - }); + .then((resp) => { + expect(resp.status).to.eq(301) - it("does not included redirectedToUrl when following redirects", function() { - nock("http://localhost:8080") - .get("/dashboard") - .reply(302, "", { - location: "http://localhost:8080/login" + expect(resp.redirectedToUrl).to.eq('https://www.google.com/login') }) - .get("/login") - .reply(200, "login"); + }) + + it('does not included redirectedToUrl when following redirects', function () { + nock('http://localhost:8080') + .get('/dashboard') + .reply(302, '', { + location: 'http://localhost:8080/login', + }) + .get('/login') + .reply(200, 'login') return request.sendPromise({}, this.fn, { - url: "http://localhost:8080/dashboard", - cookies: false + url: 'http://localhost:8080/dashboard', + cookies: false, + }) + .then((resp) => { + expect(resp.status).to.eq(200) + + expect(resp).not.to.have.property('redirectedToUrl') }) - .then(function(resp) { - expect(resp.status).to.eq(200); - return expect(resp).not.to.have.property("redirectedToUrl"); - }); - }); + }) - return it("gets + attaches the cookies at each redirect", function() { + it('gets + attaches the cookies at each redirect', function () { return testAttachingCookiesWith(() => { return request.sendPromise({}, this.fn, { - url: "http://localhost:1234/" - }); - }); - }); - }); - - context("form=true", function() { - beforeEach(() => nock("http://localhost:8080") - .matchHeader("Content-Type", "application/x-www-form-urlencoded") - .post("/login", "foo=bar&baz=quux") - .reply(200, "")); - - it("takes converts body to x-www-form-urlencoded and sets header", function() { + url: 'http://localhost:1234/', + }) + }) + }) + }) + + context('form=true', () => { + beforeEach(() => { + return nock('http://localhost:8080') + .matchHeader('Content-Type', 'application/x-www-form-urlencoded') + .post('/login', 'foo=bar&baz=quux') + .reply(200, '') + }) + + it('takes converts body to x-www-form-urlencoded and sets header', function () { return request.sendPromise({}, this.fn, { - url: "http://localhost:8080/login", - method: "POST", + url: 'http://localhost:8080/login', + method: 'POST', cookies: false, form: true, body: { - foo: "bar", - baz: "quux" - } + foo: 'bar', + baz: 'quux', + }, }) - .then(function(resp) { - expect(resp.status).to.eq(200); - return expect(resp.body).to.eq(""); - }); - }); + .then((resp) => { + expect(resp.status).to.eq(200) - it("does not send body", function() { - const init = sinon.spy(request.rp.Request.prototype, "init"); + expect(resp.body).to.eq('') + }) + }) + + it('does not send body', function () { + const init = sinon.spy(request.rp.Request.prototype, 'init') - const body = { - foo: "bar", - baz: "quux" - }; + const body = { + foo: 'bar', + baz: 'quux', + } return request.sendPromise({}, this.fn, { - url: "http://localhost:8080/login", - method: "POST", + url: 'http://localhost:8080/login', + method: 'POST', cookies: false, form: true, json: true, - body + body, }) - .then(function(resp) { - expect(resp.status).to.eq(200); - expect(resp.body).to.eq(""); - return expect(init).not.to.be.calledWithMatch({body}); - }); - }); + .then((resp) => { + expect(resp.status).to.eq(200) + expect(resp.body).to.eq('') + + expect(init).not.to.be.calledWithMatch({ body }) + }) + }) - return it("does not set json=true", function() { - const init = sinon.spy(request.rp.Request.prototype, "init"); + it('does not set json=true', function () { + const init = sinon.spy(request.rp.Request.prototype, 'init') return request.sendPromise({}, this.fn, { - url: "http://localhost:8080/login", - method: "POST", + url: 'http://localhost:8080/login', + method: 'POST', cookies: false, form: true, json: true, body: { - foo: "bar", - baz: "quux" - } - }) - .then(function(resp) { - expect(resp.status).to.eq(200); - expect(resp.body).to.eq(""); - return expect(init).not.to.be.calledWithMatch({json: true}); - }); - }); - }); - - return context("bad headers", function() { - beforeEach(function(done) { - this.srv = http.createServer(function(req, res) { - res.writeHead(200); - return res.end(); - }); - - return this.srv.listen(9988, done); - }); - - afterEach(function() { - return this.srv.close(); - }); - - it("recovers from bad headers", function() { + foo: 'bar', + baz: 'quux', + }, + }) + .then((resp) => { + expect(resp.status).to.eq(200) + expect(resp.body).to.eq('') + + expect(init).not.to.be.calledWithMatch({ json: true }) + }) + }) + }) + + context('bad headers', () => { + beforeEach(function (done) { + this.srv = http.createServer((req, res) => { + res.writeHead(200) + + return res.end() + }) + + return this.srv.listen(9988, done) + }) + + afterEach(function () { + return this.srv.close() + }) + + it('recovers from bad headers', function () { return request.sendPromise({}, this.fn, { - url: "http://localhost:9988/foo", + url: 'http://localhost:9988/foo', cookies: false, headers: { - "x-text": "אבגד" - } + 'x-text': 'אבגד', + }, + }) + .then(() => { + throw new Error('should have failed') + }).catch((err) => { + expect(err.message).to.eq('TypeError [ERR_INVALID_CHAR]: Invalid character in header content ["x-text"]') }) - .then(function() { - throw new Error("should have failed");}).catch(err => expect(err.message).to.eq("TypeError [ERR_INVALID_CHAR]: Invalid character in header content [\"x-text\"]")); - }); + }) - return it("handles weird content in the body just fine", function() { + it('handles weird content in the body just fine', function () { return request.sendPromise({}, this.fn, { - url: "http://localhost:9988/foo", + url: 'http://localhost:9988/foo', cookies: false, json: true, body: { - "x-text": "אבגד" - } - }); - }); - }); - }); - - return context("#sendStream", function() { - it("allows overriding user-agent in headers", function() { - nock("http://localhost:8080") - .matchHeader("user-agent", "custom-agent") - .get("/foo") - .reply(200, "derp"); - - sinon.spy(request, "create"); - this.fn.resolves({}); - - const headers = {'user-agent': 'test'}; - - const options = { - url: "http://localhost:8080/foo", + 'x-text': 'אבגד', + }, + }) + }) + }) + }) + + context('#sendStream', () => { + it('allows overriding user-agent in headers', function () { + nock('http://localhost:8080') + .matchHeader('user-agent', 'custom-agent') + .get('/foo') + .reply(200, 'derp') + + sinon.spy(request, 'create') + this.fn.resolves({}) + + const headers = { 'user-agent': 'test' } + + const options = { + url: 'http://localhost:8080/foo', cookies: false, headers: { - 'user-agent': "custom-agent", + 'user-agent': 'custom-agent', }, - }; + } return request.sendStream(headers, this.fn, options) - .then(function(beginFn) { - beginFn(); - expect(request.create).to.be.calledOnce; - return expect(request.create).to.be.calledWith(options); - }); - }); - - return it("gets + attaches the cookies at each redirect", function() { + .then((beginFn) => { + beginFn() + expect(request.create).to.be.calledOnce + + expect(request.create).to.be.calledWith(options) + }) + }) + + it('gets + attaches the cookies at each redirect', function () { return testAttachingCookiesWith(() => { return request.sendStream({}, this.fn, { - url: "http://localhost:1234/", - followRedirect: _.stubTrue + url: 'http://localhost:1234/', + followRedirect: _.stubTrue, }) - .then(fn => { - const req = fn(); + .then((fn) => { + const req = fn() return new Promise((resolve, reject) => { - req.on('response', resolve); - return req.on('error', reject); - }); - }); - }); - }); - }); -}); + req.on('response', resolve) + + return req.on('error', reject) + }) + }) + }) + }) + }) +}) From 69cfbad06b85c68d05113e5d388de64e3bb0a45f Mon Sep 17 00:00:00 2001 From: Zach Bloomquist Date: Tue, 21 Apr 2020 14:11:14 -0400 Subject: [PATCH 4/7] cleanup request.js decaf --- ...request_spec.coffee.js => request_spec.js} | 0 packages/server/lib/request.js | 78 +++++++------------ packages/server/test/unit/request_spec.js | 45 +++++------ 3 files changed, 46 insertions(+), 77 deletions(-) rename packages/server/__snapshots__/{request_spec.coffee.js => request_spec.js} (100%) diff --git a/packages/server/__snapshots__/request_spec.coffee.js b/packages/server/__snapshots__/request_spec.js similarity index 100% rename from packages/server/__snapshots__/request_spec.coffee.js rename to packages/server/__snapshots__/request_spec.js diff --git a/packages/server/lib/request.js b/packages/server/lib/request.js index 2700513b53a3..53751ec5b06d 100644 --- a/packages/server/lib/request.js +++ b/packages/server/lib/request.js @@ -1,15 +1,3 @@ -/* eslint-disable - brace-style, -*/ -// TODO: This file was created by bulk-decaffeinate. -// Fix any style issues and re-enable lint. -/* - * decaffeinate suggestions: - * DS102: Remove unnecessary code created because of implicit returns - * DS103: Rewrite code to no longer use __guard__ - * DS207: Consider shorter variations of null checks - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md - */ const _ = require('lodash') let r = require('@cypress/request') let rp = require('@cypress/request-promise') @@ -19,13 +7,9 @@ const debug = require('debug')('cypress:server:request') const Promise = require('bluebird') const stream = require('stream') const duplexify = require('duplexify') -const { - agent, -} = require('@packages/network') +const { agent } = require('@packages/network') const statusCode = require('./util/status_code') -const { - streamBuffer, -} = require('./util/stream_buffer') +const { streamBuffer } = require('./util/stream_buffer') const SERIALIZABLE_COOKIE_PROPS = ['name', 'value', 'domain', 'expiry', 'path', 'secure', 'hostOnly', 'httpOnly', 'sameSite'] const NETWORK_ERRORS = 'ECONNREFUSED ECONNRESET EPIPE EHOSTUNREACH EAI_AGAIN ENOTFOUND'.split(' ') @@ -54,12 +38,12 @@ const convertSameSiteToughToExtension = (sameSite, setCookie) => { return sameSite } -const getOriginalHeaders = (req = {}) => // the request instance holds an instance -// of the original ClientRequest -// as the 'req' property which holds the -// original headers else fall back to -// the normal req.headers -{ +const getOriginalHeaders = (req = {}) => { + // the request instance holds an instance + // of the original ClientRequest + // as the 'req' property which holds the + // original headers else fall back to + // the normal req.headers return _.get(req, 'req.headers', req.headers) } @@ -95,9 +79,9 @@ const getDelayForRetry = function (options = {}) { return onNext(delay, attempt) } -const hasRetriableStatusCodeFailure = (res, retryOnStatusCodeFailure) => // everything must be true in order to -// retry a status code failure -{ +const hasRetriableStatusCodeFailure = (res, retryOnStatusCodeFailure) => { + // everything must be true in order to + // retry a status code failure return _.every([ retryOnStatusCodeFailure, !statusCode.isOk(res.statusCode), @@ -221,8 +205,8 @@ const createRetryingRequestPromise = function (opts) { } return rp(opts) - .catch((err) => // rp wraps network errors in a RequestError, so might need to unwrap it to check - { + .catch((err) => { + // rp wraps network errors in a RequestError, so might need to unwrap it to check return maybeRetryOnNetworkFailure(err.error || err, { opts, retryIntervals, @@ -233,8 +217,8 @@ const createRetryingRequestPromise = function (opts) { throw err }, }) - }).then((res) => // ok, no net error, but what about a bad status code? - { + }).then((res) => { + // ok, no net error, but what about a bad status code? return maybeRetryOnStatusCodeFailure(res, { opts, requestId, @@ -249,7 +233,7 @@ const createRetryingRequestPromise = function (opts) { const pipeEvent = (source, destination, event) => { return source.on(event, (...args) => { - return destination.emit(event, ...args) + destination.emit(event, ...args) }) } @@ -279,7 +263,7 @@ const createRetryingRequestStream = function (opts = {}) { const emitError = function (err) { retryStream.emit('error', err) - return cleanup() + cleanup() } const tryStartStream = function () { @@ -311,7 +295,7 @@ const createRetryingRequestStream = function (opts = {}) { debug('aborting', { requestId }) retryStream.aborted = true - return reqStream.abort() + reqStream.abort() } const onPiped = function (src) { @@ -323,7 +307,7 @@ const createRetryingRequestStream = function (opts = {}) { // the request lib expects this 'pipe' event in // order to copy the request headers onto the // outgoing message - so we manually pipe it here - return src.pipe(reqStream) + src.pipe(reqStream) } // when this passthrough stream is being piped into @@ -356,13 +340,13 @@ const createRetryingRequestStream = function (opts = {}) { }) }) - reqStream.once('request', (req) => // remove the pipe listener since once the request has - // been made, we cannot pipe into the reqStream anymore - { - return retryStream.removeListener('pipe', onPiped) + reqStream.once('request', (req) => { + // remove the pipe listener since once the request has + // been made, we cannot pipe into the reqStream anymore + retryStream.removeListener('pipe', onPiped) }) - return reqStream.once('response', (incomingRes) => { + reqStream.once('response', (incomingRes) => { didReceiveResponse = true // ok, no net error, but what about a bad status code? @@ -484,11 +468,9 @@ module.exports = function (options = {}) { contentTypeIsJson (response) { // TODO: use https://github.com/jshttp/type-is for this // https://github.com/cypress-io/cypress/pull/5166 - return __guard__(__guard__(response != null ? response.headers : undefined, (x1) => { - return x1['content-type'] - }), (x) => { - return x.split(';', 2)[0].endsWith('json') - }) + if (response && response.headers && response.headers['content-type']) { + return response.headers['content-type'].split(';', 2)[0].endsWith('json') + } }, parseJsonBody (body) { @@ -653,7 +635,7 @@ module.exports = function (options = {}) { // first set the received cookies on the browser // and then grab the cookies for the new url return self.setCookiesOnBrowser(incomingRes, currentUrl, automationFn) - .then((cookies) => { + .then(() => { return self.setRequestCookieHeader(this, newUrl, automationFn) }).then(() => { currentUrl = newUrl @@ -795,7 +777,3 @@ module.exports = function (options = {}) { } } - -function __guard__ (value, transform) { - return (typeof value !== 'undefined' && value !== null) ? transform(value) : undefined -} diff --git a/packages/server/test/unit/request_spec.js b/packages/server/test/unit/request_spec.js index 8fb51ae024ca..7110a2b5b045 100644 --- a/packages/server/test/unit/request_spec.js +++ b/packages/server/test/unit/request_spec.js @@ -1,13 +1,3 @@ -/* eslint-disable - default-case, -*/ -// TODO: This file was created by bulk-decaffeinate. -// Fix any style issues and re-enable lint. -/* - * decaffeinate suggestions: - * DS102: Remove unnecessary code created because of implicit returns - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md - */ require('../spec_helper') const _ = require('lodash') @@ -60,8 +50,7 @@ describe('lib/request', () => { beforeEach(function () { this.fn = sinon.stub() this.fn.withArgs('set:cookie').resolves({}) - - return this.fn.withArgs('get:cookies').resolves([]) + this.fn.withArgs('get:cookies').resolves([]) }) it('is defined', () => { @@ -229,10 +218,12 @@ describe('lib/request', () => { return res.write('foo\n') case '/econnreset': return req.socket.destroy() + default: + break } }) - return this.srv.listen(9988, done) + this.srv.listen(9988, done) }) afterEach(function () { @@ -251,14 +242,14 @@ describe('lib/request', () => { let retries = 0 stream.on('retry', () => { - return retries++ + retries++ }) const p = Bluebird.fromCallback((cb) => { - return stream.on('error', cb) + stream.on('error', cb) }) - expect(p).to.be.rejected + return expect(p).to.be.rejected .then((err) => { expect(err.code).to.eq('ESOCKETTIMEDOUT') @@ -278,14 +269,14 @@ describe('lib/request', () => { let retries = 0 stream.on('retry', () => { - return retries++ + retries++ }) const p = Bluebird.fromCallback((cb) => { - return stream.on('error', cb) + stream.on('error', cb) }) - expect(p).to.be.rejected + return expect(p).to.be.rejected .then((err) => { expect(err.code).to.eq('ECONNRESET') @@ -307,14 +298,14 @@ describe('lib/request', () => { let retries = 0 stream.on('retry', () => { - return retries++ + retries++ }) const p = Bluebird.fromCallback((cb) => { - return stream.on('error', cb) + stream.on('error', cb) }) - expect(p).to.be.rejected + return expect(p).to.be.rejected .then((err) => { expect(err.code).to.eq('ENOTFOUND') @@ -737,7 +728,7 @@ describe('lib/request', () => { context('followRedirect', () => { beforeEach(function () { - return this.fn.resolves() + this.fn.resolves() }) it('by default follow redirects', function () { @@ -879,7 +870,7 @@ describe('lib/request', () => { context('form=true', () => { beforeEach(() => { - return nock('http://localhost:8080') + nock('http://localhost:8080') .matchHeader('Content-Type', 'application/x-www-form-urlencoded') .post('/login', 'foo=bar&baz=quux') .reply(200, '') @@ -955,10 +946,10 @@ describe('lib/request', () => { this.srv = http.createServer((req, res) => { res.writeHead(200) - return res.end() + res.end() }) - return this.srv.listen(9988, done) + this.srv.listen(9988, done) }) afterEach(function () { @@ -1034,7 +1025,7 @@ describe('lib/request', () => { return new Promise((resolve, reject) => { req.on('response', resolve) - return req.on('error', reject) + req.on('error', reject) }) }) }) From eae1a893ff792a6fca382f6b41c2c9399f70676f Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Wed, 22 Apr 2020 13:35:30 -0400 Subject: [PATCH 5/7] Investigate spec on mac (#7111) * run scripts unit tests on Mac too * build this branch on Mac on CircleCI * skip some mock file system tests, run them only on Linux * skip dynamically * use function expression --- circle.yml | 25 +++++++++++++++++++-- scripts/unit/binary/util/packages-spec.js | 27 +++++++++++++++++++---- 2 files changed, 46 insertions(+), 6 deletions(-) diff --git a/circle.yml b/circle.yml index 201d5666dbe0..8574c390494f 100644 --- a/circle.yml +++ b/circle.yml @@ -8,7 +8,7 @@ macBuildFilters: &macBuildFilters branches: only: - develop - - try-new-electron-sign + - investigate-spec-on-mac defaults: &defaults parallelism: 1 @@ -325,6 +325,17 @@ jobs: command: yarn type-check --ignore-progress - store-npm-logs + build-system-unit-tests: + <<: *defaults + parallelism: 1 + steps: + - attach_workspace: + at: ~/ + # make sure mocha runs + - run: yarn test-mocha + # test binary build code + - run: yarn test-scripts + "server-unit-tests": <<: *defaults parallelism: 2 @@ -1166,6 +1177,9 @@ linux-workflow: &linux-workflow - cli-visual-tests: requires: - build + - build-system-unit-tests: + requires: + - build - unit-tests: requires: - build @@ -1403,7 +1417,14 @@ mac-workflow: &mac-workflow requires: - Mac build - # maybe run unit tests? + - build-system-unit-tests: + name: Mac build system unit tests + executor: mac + requires: + - Mac build + <<: *macBuildFilters + + # maybe run all unit tests? - build-npm-package: name: Mac NPM package diff --git a/scripts/unit/binary/util/packages-spec.js b/scripts/unit/binary/util/packages-spec.js index 2f81a84fac3c..59c747128d76 100644 --- a/scripts/unit/binary/util/packages-spec.js +++ b/scripts/unit/binary/util/packages-spec.js @@ -1,3 +1,4 @@ +/* global sinon */ const os = require('os') const _ = require('lodash') const path = require('path') @@ -5,6 +6,7 @@ const proxyquire = require('proxyquire') const mockfs = require('mock-fs') const _snapshot = require('snap-shot-it') const chai = require('chai') +const debug = require('debug')('test') chai.use(require('chai-as-promised')) @@ -26,6 +28,8 @@ const snapshot = (...args) => { describe('packages', () => { it('can copy files from package.json', async () => { + sinon.stub(os, 'tmpdir').returns('/tmp') + mockfs({ 'packages': { 'coffee': { @@ -77,7 +81,12 @@ describe('packages', () => { }) describe('transformRequires', () => { - it('can find and replace symlink requires', async () => { + it('can find and replace symlink requires', async function () { + // these tests really refuse to work on Mac, so for now run it only on Linux + if (os.platform() !== 'linux') { + return this.skip() + } + const buildRoot = 'build/linux/Cypress/resources/app' mockfs({ @@ -101,12 +110,22 @@ describe('transformRequires', () => { // should return number of transformed requires await expect(transformRequires(buildRoot)).to.eventually.eq(2) - // console.log(getFs()) + const files = getFs() - snapshot(getFs()) + if (debug.enabled) { + debug('returned file system') + /* eslint-disable-next-line no-console */ + console.error(files) + } + + snapshot(files) }) - it('can find and replace symlink requires on win32', async () => { + it('can find and replace symlink requires on win32', async function () { + if (os.platform() !== 'linux') { + return this.skip() + } + const { transformRequires } = proxyquire('../../../binary/util/transform-requires', { path: path.win32 }) const buildRoot = 'build/linux/Cypress/resources/app' From 70c084a7d4995e7b00c4e6b83e1aab9984e07c72 Mon Sep 17 00:00:00 2001 From: Jennifer Shehane Date: Thu, 23 Apr 2020 14:36:00 +0630 Subject: [PATCH 6/7] =?UTF-8?q?Update=20window=20message:=20display=20'yar?= =?UTF-8?q?n=20upgrade'=20instead=20of=20`yarn=E2=80=A6=20(#7101)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * update msg in update banner to use 'yarn upgrade' instead of `yarn add` * update test + add percy snapshot --- packages/desktop-gui/cypress/integration/update_banner_spec.js | 3 ++- packages/desktop-gui/src/update/update-banner.jsx | 3 +-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/desktop-gui/cypress/integration/update_banner_spec.js b/packages/desktop-gui/cypress/integration/update_banner_spec.js index 540e42ce789b..e4b0fd338a9b 100644 --- a/packages/desktop-gui/cypress/integration/update_banner_spec.js +++ b/packages/desktop-gui/cypress/integration/update_banner_spec.js @@ -115,7 +115,8 @@ describe('Update Banner', function () { it('modal has info about updating package.json', function () { cy.get('.modal').contains(`npm install --save-dev cypress@${NEW_VERSION}`) - cy.get('.modal').contains(`yarn add cypress@${NEW_VERSION}`) + cy.get('.modal').contains(`yarn upgrade cypress@${NEW_VERSION}`) + cy.percySnapshot() }) it('links to \'open\' doc on click of open command', function () { diff --git a/packages/desktop-gui/src/update/update-banner.jsx b/packages/desktop-gui/src/update/update-banner.jsx index c3df20c0e176..9606ac28dce6 100644 --- a/packages/desktop-gui/src/update/update-banner.jsx +++ b/packages/desktop-gui/src/update/update-banner.jsx @@ -86,8 +86,7 @@ class UpdateBanner extends Component {
  • If using npm, run npm install --save-dev cypress@{appStore.newVersion}
    - If using yarn, run yarn add cypress@{appStore.newVersion} - + If using yarn, run yarn upgrade cypress@{appStore.newVersion}
  • Run node_modules/.bin/cypress open to open the new version. From b17f597338c2db3c0f7ffd4809ae40767b035da9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 23 Apr 2020 14:39:19 +0630 Subject: [PATCH 7/7] =?UTF-8?q?chore(deps):=20Update=20dependency=20react-?= =?UTF-8?q?select=20to=20version=203.1.0=20=F0=9F=8C=9F=20(#7103)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Renovate Bot --- packages/desktop-gui/package.json | 2 +- yarn.lock | 32 ++++++++++++++++++++++++------- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/packages/desktop-gui/package.json b/packages/desktop-gui/package.json index 83ec2fa69b53..67b9906f6caf 100644 --- a/packages/desktop-gui/package.json +++ b/packages/desktop-gui/package.json @@ -41,7 +41,7 @@ "react-dom": "16.12.0", "react-inspector": "4.0.0", "react-loader": "2.4.7", - "react-select": "3.0.8", + "react-select": "3.1.0", "webpack": "4.35.3", "webpack-cli": "3.3.2" }, diff --git a/yarn.lock b/yarn.lock index c144c0b37bcd..37b2a9a769b2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8811,7 +8811,7 @@ cssstyle@^1.0.0, cssstyle@^1.1.1: dependencies: cssom "0.3.x" -csstype@^2.2.0, csstype@^2.5.7: +csstype@^2.2.0, csstype@^2.5.7, csstype@^2.6.7: version "2.6.10" resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.10.tgz#e63af50e66d7c266edb6b32909cfd0aabe03928b" integrity sha512-D34BqZU4cIlMCY93rZHbrq9pjTAQJ3U8S8rfBqjwHxkGPThWFjzZDQpgMJY0QViLxth6ZKYiwFBo14RdN44U/w== @@ -9591,6 +9591,14 @@ dom-helpers@^3.2.1, dom-helpers@^3.3.1, dom-helpers@^3.4.0: dependencies: "@babel/runtime" "^7.1.2" +dom-helpers@^5.0.1: + version "5.1.4" + resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.1.4.tgz#4609680ab5c79a45f2531441f1949b79d6587f4b" + integrity sha512-TjMyeVUvNEnOnhzs6uAn9Ya47GmMo3qq7m+Lr/3ON0Rs5kHvb8I+SQYjLUSYn7qhEm0QjW0yrBkvz9yOrwwz1A== + dependencies: + "@babel/runtime" "^7.8.7" + csstype "^2.6.7" + dom-serializer@0: version "0.2.2" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51" @@ -19761,10 +19769,10 @@ react-overlays@^0.8.0: react-transition-group "^2.2.0" warning "^3.0.0" -react-select@3.0.8: - version "3.0.8" - resolved "https://registry.yarnpkg.com/react-select/-/react-select-3.0.8.tgz#06ff764e29db843bcec439ef13e196865242e0c1" - integrity sha512-v9LpOhckLlRmXN5A6/mGGEft4FMrfaBFTGAnuPHcUgVId7Je42kTq9y0Z+Ye5z8/j0XDT3zUqza8gaRaI1PZIg== +react-select@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/react-select/-/react-select-3.1.0.tgz#ab098720b2e9fe275047c993f0d0caf5ded17c27" + integrity sha512-wBFVblBH1iuCBprtpyGtd1dGMadsG36W5/t2Aj8OE6WbByDg5jIFyT7X5gT+l0qmT5TqWhxX+VsKJvCEl2uL9g== dependencies: "@babel/runtime" "^7.4.4" "@emotion/cache" "^10.0.9" @@ -19773,7 +19781,7 @@ react-select@3.0.8: memoize-one "^5.0.0" prop-types "^15.6.0" react-input-autosize "^2.2.2" - react-transition-group "^2.2.1" + react-transition-group "^4.3.0" react-test-renderer@^16.0.0-0: version "16.13.1" @@ -19785,7 +19793,7 @@ react-test-renderer@^16.0.0-0: react-is "^16.8.6" scheduler "^0.19.1" -react-transition-group@^2.0.0, react-transition-group@^2.2.0, react-transition-group@^2.2.1: +react-transition-group@^2.0.0, react-transition-group@^2.2.0: version "2.9.0" resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.9.0.tgz#df9cdb025796211151a436c69a8f3b97b5b07c8d" integrity sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg== @@ -19795,6 +19803,16 @@ react-transition-group@^2.0.0, react-transition-group@^2.2.0, react-transition-g prop-types "^15.6.2" react-lifecycles-compat "^3.0.4" +react-transition-group@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.3.0.tgz#fea832e386cf8796c58b61874a3319704f5ce683" + integrity sha512-1qRV1ZuVSdxPlPf4O8t7inxUGpdyO5zG9IoNfJxSO0ImU2A1YWkEQvFPuIPZmMLkg5hYs7vv5mMOyfgSkvAwvw== + dependencies: + "@babel/runtime" "^7.5.5" + dom-helpers "^5.0.1" + loose-envify "^1.4.0" + prop-types "^15.6.2" + react@15.6.2, react@^15.3.2: version "15.6.2" resolved "https://registry.yarnpkg.com/react/-/react-15.6.2.tgz#dba0434ab439cfe82f108f0f511663908179aa72"