Files
cypress/packages/server/test/unit/request_spec.coffee
Zach Bloomquist c1a345dce2 Improved proxy support (#3531)
* https-proxy: unused file

* server: wrap all https requests that use a proxy

* server: use request lib in ensureUrl if proxy is in use. this makes runs tab work behind a proxy

* electron: pass --proxy-server to app itself, so the embedded github login page works

* cli: first attempt at env vars from windows registry

* cli: api cleanup

* cli: lint

* cli: fix crash on no proxy, add tests

* add desktop-gui watch to terminals.json

* cli: pass along --proxy-source

* electron: pass --proxy-bypass-list too

* server: whitelist proxy* args

* cli: better wording

* desktop-gui: display proxy settings

* extension: force proxy [wip]

* extension: finally, i am victorious over coffeescript

* extension: add -loopback to bypasslist

* extension: revert changes

Revert "extension: force proxy [wip]"

This reverts commit 3ab6ba42a763f25ee65f12eb8b79eb597efc9b11.

* desktop-gui: skip proxysettings if there aren't any

* https-proxy, server: proxy directConnections using https-proxy-agent

* https-agent: pool httpsAgents

* https-proxy: work when they're not on a proxy

* https-proxy: ci - use agent 1.0

* https-proxy: tests

* desktop-gui: hide proxy settings when not using proxy

* https-proxy: pass req through to https-proxy-agent callback

* cli: use get-windows-proxy

* desktop-gui: always show proxy settings

* server: use get-windows-proxy

* electron, server: supply electron proxy config when window launched

* server: fix

* https-proxy: cleanup

* server: clean up ensureUrl

* https-proxy: cleanup

* cli: fix

* cli: fix destructuring

* server: enable ForeverAgent to pool HTTPS/HTTP connections

#3192

* server: updating snapshot

* https-proxy: don't crash, do error if proxy unreachable

* https-proxy:

* get-windows-proxy@1.0.0

* https-proxy: use proxy-from-env to decide on a proxy for a url

* server: fallback to HTTP_PROXY globally if HTTPS_PROXY not set

* server: proxy args test

* cli: add proxy tests

* cli: add test that loadSystemProxySettings is called during download

* cli, server: account for the fact that CI has some proxy vars set

* https-proxy: ""

* cli, https-proxy, server: ""

* desktop-gui: update settings gui

* desktop-gui: cypress tests for proxy settings

* server: strict undefined check

* cli, server: move get-windows-proxy to scope, optionalDeps

* server, cli: use new and improved get-windows-proxy

* cli, server: 1.5.0

* server: re-check for proxy since cli may have failed to load the lib

* server, cli: 1.5.1

* server: NO_PROXY=localhost by default, clean up

* https-proxy: disable Nagle's on proxy sockets

\#3192

* https-proxy: use setNoDelay on upstream, cache https agent

* https-proxy: test basic auth

* https-proxy: add todo: remove this

* server: add custom HTTP(s) Agent implementation w keepalive, tunneling

* server: typescript for agent

* add ts to zunder

* server: more ts

* ts: add missing Agent type declaration

* server: create CombinedAgent

* server: use agent in more places

* ts: more declarations

* server: make script work even if debug port not supplied

* server: begin some testing

* server, ts: agent, tests

* server: test

* server: agent works with websockets now

* server: update snapshot

* server: work out some more bugs with websockets

* server: more websockets

* server: add net_profiler

* https-proxy: fix dangling socket on direct connection

* server: fix potential 'headers have already been sent'

* https-proxy: nab another dangler

* server: update test to expect agent

* https-proxy: fix failing test

* desktop-gui: change on-link

* server: add tests for empty response case

* server: tests

* server: send keep-alive with requests

* server: make net profiler hook on socket.connect

* server: only hook profiler once

* server: update tests, add keep-alive test

* server: only regen headers if needed

* server: move http_overrides into CombinedAgent, make it proxy-proof

for #112

* server: update snapshot

* server: undo

* server: avoid circular dependency

* https-proxy, server: use our Agent instead of https-proxy-agent

* server: add dependency back

* cli: actually use proxy for download

* server, launcher, ts: typescript

* Revert "server, launcher, ts: typescript"

This reverts commit d3f8b8bbb6.

* Revert "Revert "server, launcher, ts: typescript""

This reverts commit 818dfdfd00.

* ts, server: respond to PR

* server, ts: types

* ts: really fix types

* https-proxy, server: export CA from https-proxy

* agent, server, https-proxy: move agent to own package

* agent => networking, move connect into networking

* fix tests

* fix test

* networking: respond to PR changes, add more unit tests

* rename ctx

* networking, ts: add more tests

* server: add ensureUrl tests

* https-proxy: remove https-proxy-agent

* server: use CombinedAgent for API

* server: updates

* add proxy performance tests

* add perf tests to workflow

* circle

* run perf tests with --no-sandbox

* networking, ts: ch-ch-ch-ch-changes

* server, networking: pr changes

* run networking tests in circle

* server: fix performance test

* https-proxy: test that sockets are being closed

* https-proxy: write, not emit

* networking: fix test

* networking: bubble err in connect

* networking: style

* networking: clean p connect error handling

* networking => network

* server: make perf tests really work

* server: really report

* server: use args from browser

* server: use AI to determine max run time

* server: load electron only when needed


Co-authored-by: Brian Mann <brian@cypress.io>
2019-03-31 23:39:10 -04:00

587 lines
17 KiB
CoffeeScript

require("../spec_helper")
http = require("http")
Request = require("#{root}lib/request")
request = Request({timeout: 100})
describe "lib/request", ->
it "is defined", ->
expect(request).to.be.an("object")
context "#reduceCookieToArray", ->
it "converts object to array of key values", ->
obj = {
foo: "bar"
baz: "quux"
}
expect(request.reduceCookieToArray(obj)).to.deep.eq(["foo=bar", "baz=quux"])
context "#createCookieString", ->
it "joins array by '; '", ->
obj = {
foo: "bar"
baz: "quux"
}
expect(request.createCookieString(obj)).to.eq("foo=bar; baz=quux")
context "#normalizeResponse", ->
beforeEach ->
@push = sinon.stub()
it "sets status to statusCode and deletes statusCode", ->
expect(request.normalizeResponse(@push, {
statusCode: 404
request: {
headers: {foo: "bar"}
body: "body"
}
})).to.deep.eq({
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: "<html>foo</html>"
statusCode: 200
request: {
headers: {foo: "bar"}
body: "body"
}
})).to.deep.eq({
body: "<html>foo</html>"
headers: {"Content-Length": 50}
status: 200
statusText: "OK"
isOkStatusCode: true
requestHeaders: {foo: "bar"}
requestBody: "body"
})
expect(@push).to.be.calledOnce
context "#send", ->
beforeEach ->
@fn = sinon.stub()
it "sets strictSSL=false", ->
init = sinon.spy(request.rp.Request.prototype, "init")
nock("http://www.github.com")
.get("/foo")
.reply 200, "hello", {
"Content-Type": "text/html"
}
request.send({}, @fn, {
url: "http://www.github.com/foo"
cookies: false
})
.then ->
expect(init).to.be.calledWithMatch({strictSSL: false})
it "sets simple=false", ->
nock("http://www.github.com")
.get("/foo")
.reply(500, "")
## should not bomb on 500
## because simple = false
request.send({}, @fn, {
url: "http://www.github.com/foo"
cookies: false
})
it "sets resolveWithFullResponse=true", ->
nock("http://www.github.com")
.get("/foo")
.reply(200, "hello", {
"Content-Type": "text/html"
})
request.send({}, @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"
})
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", ->
nock("http://www.github.com")
.get("/dashboard")
.reply(301, null, {
"location": "/auth"
})
.get("/auth")
.reply(302, null, {
"location": "/login"
})
.get("/login")
.reply(200, "log in", {
"Content-Type": "text/html"
})
request.send({}, @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
expect(resp.redirects).to.deep.eq([
"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"
})
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": {"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": {"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 "sends Cookie header, and body", ->
nock("http://localhost:8080")
.matchHeader("Cookie", "foo=bar; baz=quux")
.post("/users", {
first: "brian"
last: "mann"
})
.reply(200, {id: 1})
request.send({}, @fn, {
url: "http://localhost:8080/users"
method: "POST"
cookies: {foo: "bar", baz: "quux"}
json: true
body: {
first: "brian"
last: "mann"
}
})
.then (resp) ->
expect(resp.status).to.eq(200)
expect(resp.body.id).to.eq(1)
it "catches errors", ->
nock.enableNetConnect()
request.send({}, @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")
it "parses response body as json if content-type application/json response headers", ->
nock("http://localhost:8080")
.get("/status.json")
.reply(200, JSON.stringify({status: "ok"}), {
"Content-Type": "application/json"
})
request.send({}, @fn, {
url: "http://localhost:8080/status.json"
cookies: false
})
.then (resp) ->
expect(resp.body).to.deep.eq({status: "ok"})
it "revives from parsing bad json", ->
nock("http://localhost:8080")
.get("/status.json")
.reply(200, "{bad: 'json'}", {
"Content-Type": "application/json"
})
request.send({}, @fn, {
url: "http://localhost:8080/status.json"
cookies: false
})
.then (resp) ->
expect(resp.body).to.eq("{bad: 'json'}")
it "sets duration on response", ->
nock("http://localhost:8080")
.get("/foo")
.delay(10)
.reply(200, "123", {
"Content-Type": "text/plain"
})
request.send({}, @fn, {
url: "http://localhost:8080/foo"
cookies: false
})
.then (resp) ->
expect(resp.duration).to.be.a("Number")
expect(resp.duration).to.be.gt(0)
it "sends up user-agent headers", ->
nock("http://localhost:8080")
.matchHeader("user-agent", "foobarbaz")
.get("/foo")
.reply(200, "derp")
headers = {}
headers["user-agent"] = "foobarbaz"
request.send(headers, @fn, {
url: "http://localhost:8080/foo"
cookies: false
})
.then (resp) ->
expect(resp.body).to.eq("derp")
it "sends connection: keep-alive by default", ->
nock("http://localhost:8080")
.matchHeader("connection", "keep-alive")
.get("/foo")
.reply(200, "it worked")
request.send({}, @fn, {
url: "http://localhost:8080/foo"
cookies: false
})
.then (resp) ->
expect(resp.body).to.eq("it worked")
context "accept header", ->
it "sets to */* by default", ->
nock("http://localhost:8080")
.matchHeader("accept", "*/*")
.get("/headers")
.reply(200)
request.send({}, @fn, {
url: "http://localhost:8080/headers"
cookies: false
})
.then (resp) ->
expect(resp.status).to.eq(200)
it "can override accept header", ->
nock("http://localhost:8080")
.matchHeader("accept", "text/html")
.get("/headers")
.reply(200)
request.send({}, @fn, {
url: "http://localhost:8080/headers"
cookies: false
headers: {
accept: "text/html"
}
})
.then (resp) ->
expect(resp.status).to.eq(200)
it "can override Accept header", ->
nock("http://localhost:8080")
.matchHeader("accept", "text/plain")
.get("/headers")
.reply(200)
request.send({}, @fn, {
url: "http://localhost:8080/headers"
cookies: false
headers: {
Accept: "text/plain"
}
})
.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)
request.send({}, @fn, {
url: "http://localhost:8080/foo"
cookies: false
qs: {
bar: "baz"
q: 1
}
})
.then (resp) ->
expect(resp.status).to.eq(200)
context "followRedirect", ->
it "by default follow redirects", ->
nock("http://localhost:8080")
.get("/dashboard")
.reply(302, "", {
location: "http://localhost:8080/login"
})
.get("/login")
.reply(200, "login")
request.send({}, @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", ->
nock("http://localhost:8080")
.post("/login")
.reply(302, "", {
location: "http://localhost:8080/dashboard"
})
.get("/dashboard")
.reply(200, "dashboard")
request.send({}, @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", ->
nock("http://localhost:8080")
.get("/dashboard")
.reply(302, "", {
location: "http://localhost:8080/login"
})
.get("/login")
.reply(200, "login")
request.send({}, @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", ->
nock("http://localhost:8080")
.get("/dashboard")
.reply(302, "", {
location: "/login" ## absolute-relative pathname
})
.get("/login")
.reply(200, "login")
request.send({}, @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")
it "resolves redirectedToUrl to another domain", ->
nock("http://localhost:8080")
.get("/dashboard")
.reply(301, "", {
location: "https://www.google.com/login"
})
.get("/login")
.reply(200, "login")
request.send({}, @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")
it "does not included redirectedToUrl when following redirects", ->
nock("http://localhost:8080")
.get("/dashboard")
.reply(302, "", {
location: "http://localhost:8080/login"
})
.get("/login")
.reply(200, "login")
request.send({}, @fn, {
url: "http://localhost:8080/dashboard"
cookies: false
})
.then (resp) ->
expect(resp.status).to.eq(200)
expect(resp).not.to.have.property("redirectedToUrl")
context "form=true", ->
beforeEach ->
nock("http://localhost:8080")
.matchHeader("Content-Type", "application/x-www-form-urlencoded")
.post("/login", "foo=bar&baz=quux")
.reply(200, "<html></html>")
it "takes converts body to x-www-form-urlencoded and sets header", ->
request.send({}, @fn, {
url: "http://localhost:8080/login"
method: "POST"
cookies: false
form: true
body: {
foo: "bar"
baz: "quux"
}
})
.then (resp) ->
expect(resp.status).to.eq(200)
expect(resp.body).to.eq("<html></html>")
it "does not send body", ->
init = sinon.spy(request.rp.Request.prototype, "init")
body = {
foo: "bar"
baz: "quux"
}
request.send({}, @fn, {
url: "http://localhost:8080/login"
method: "POST"
cookies: false
form: true
json: true
body: body
})
.then (resp) ->
expect(resp.status).to.eq(200)
expect(resp.body).to.eq("<html></html>")
expect(init).not.to.be.calledWithMatch({body: body})
it "does not set json=true", ->
init = sinon.spy(request.rp.Request.prototype, "init")
request.send({}, @fn, {
url: "http://localhost:8080/login"
method: "POST"
cookies: false
form: true
json: true
body: {
foo: "bar"
baz: "quux"
}
})
.then (resp) ->
expect(resp.status).to.eq(200)
expect(resp.body).to.eq("<html></html>")
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.send({}, @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: The header content contains invalid characters"
it "handles weird content in the body just fine", ->
request.send({}, @fn, {
url: "http://localhost:9988/foo"
cookies: false
json: true
body: {
"x-text": "אבגד"
}
})