summaryrefslogtreecommitdiff
path: root/follow-redirects/test/test.js
diff options
context:
space:
mode:
Diffstat (limited to 'follow-redirects/test/test.js')
-rw-r--r--follow-redirects/test/test.js1694
1 files changed, 1694 insertions, 0 deletions
diff --git a/follow-redirects/test/test.js b/follow-redirects/test/test.js
new file mode 100644
index 0000000..6e99b3d
--- /dev/null
+++ b/follow-redirects/test/test.js
@@ -0,0 +1,1694 @@
+var express = require("express");
+var assert = require("assert");
+var net = require("net");
+var server = require("./server")({ https: 3601, http: 3600 });
+var url = require("url");
+var followRedirects = require("..");
+var http = followRedirects.http;
+var https = followRedirects.https;
+var fs = require("fs");
+var path = require("path");
+var lolex = require("lolex");
+
+var util = require("./util");
+var concat = require("concat-stream");
+var concatJson = util.concatJson;
+var delay = util.delay;
+var redirectsTo = util.redirectsTo;
+var sendsJson = util.sendsJson;
+var asPromise = util.asPromise;
+
+var testFile = path.resolve(__dirname, "assets/input.txt");
+var testFileBuffer = fs.readFileSync(testFile);
+var testFileString = testFileBuffer.toString();
+
+var nodeMajorVersion = Number.parseInt(process.version.match(/\d+/)[0], 10);
+
+describe("follow-redirects", function () {
+ function httpsOptions(app) {
+ return {
+ app: app,
+ protocol: "https",
+ cert: fs.readFileSync(path.resolve(__dirname, "assets/TestServer.crt")),
+ key: fs.readFileSync(path.resolve(__dirname, "assets/TestServer.pem")),
+ };
+ }
+ var ca = fs.readFileSync(path.resolve(__dirname, "assets/TestCA.crt"));
+
+ var app;
+ var app2;
+ var originalMaxRedirects;
+ var originalMaxBodyLength;
+
+ beforeEach(function () {
+ originalMaxRedirects = followRedirects.maxRedirects;
+ originalMaxBodyLength = followRedirects.maxBodyLength;
+ app = express();
+ app2 = express();
+ });
+
+ afterEach(function () {
+ followRedirects.maxRedirects = originalMaxRedirects;
+ followRedirects.maxBodyLength = originalMaxBodyLength;
+ return server.stop();
+ });
+
+ it("http.get with string and callback - redirect", function () {
+ app.get("/a", redirectsTo("/b"));
+ app.get("/b", redirectsTo("/c"));
+ app.get("/c", redirectsTo("/d"));
+ app.get("/d", redirectsTo("/e"));
+ app.get("/e", redirectsTo("/f"));
+ app.get("/f", sendsJson({ a: "b" }));
+
+ return server.start(app)
+ .then(asPromise(function (resolve, reject) {
+ http.get("http://localhost:3600/a", concatJson(resolve, reject)).on("error", reject);
+ }))
+ .then(function (res) {
+ assert.deepEqual(res.parsedJson, { a: "b" });
+ assert.deepEqual(res.responseUrl, "http://localhost:3600/f");
+ });
+ });
+
+ it("http.get with URL object and callback - redirect", function () {
+ if (nodeMajorVersion < 10) {
+ this.skip();
+ }
+
+ app.get("/a", redirectsTo("/b"));
+ app.get("/b", redirectsTo("/c"));
+ app.get("/c", redirectsTo("/d"));
+ app.get("/d", redirectsTo("/e"));
+ app.get("/e", redirectsTo("/f"));
+ app.get("/f", sendsJson({ a: "b" }));
+
+ return server.start(app)
+ .then(asPromise(function (resolve, reject) {
+ http.get(
+ new URL("http://localhost:3600/a"),
+ concatJson(resolve, reject)
+ ).on("error", reject);
+ }))
+ .then(function (res) {
+ assert.deepEqual(res.parsedJson, { a: "b" });
+ assert.deepEqual(res.responseUrl, "http://localhost:3600/f");
+ });
+ });
+
+ it("http.get with options object and callback - redirect", function () {
+ app.get("/a", redirectsTo("/b"));
+ app.get("/b", redirectsTo("/c"));
+ app.get("/c", redirectsTo("/d"));
+ app.get("/d", redirectsTo("/e"));
+ app.get("/e", redirectsTo("/f"));
+ app.get("/f", sendsJson({ a: "b" }));
+
+ return server.start(app)
+ .then(asPromise(function (resolve, reject) {
+ var options = {
+ hostname: "localhost",
+ port: 3600,
+ path: "/a",
+ method: "GET",
+ };
+ http.get(options, concatJson(resolve, reject)).on("error", reject);
+ }))
+ .then(function (res) {
+ assert.deepEqual(res.parsedJson, { a: "b" });
+ assert.deepEqual(res.responseUrl, "http://localhost:3600/f");
+ });
+ });
+
+ it("http.get with string and callback - no redirect", function () {
+ app.get("/a", sendsJson({ a: "b" }));
+
+ return server.start(app)
+ .then(asPromise(function (resolve, reject) {
+ http.get("http://localhost:3600/a", concatJson(resolve, reject)).on("error", reject);
+ }))
+ .then(function (res) {
+ assert.deepEqual(res.parsedJson, { a: "b" });
+ assert.deepEqual(res.responseUrl, "http://localhost:3600/a");
+ });
+ });
+
+ it("http.get with options object and callback - no redirect", function () {
+ app.get("/a", sendsJson({ a: "b" }));
+
+ return server.start(app)
+ .then(asPromise(function (resolve, reject) {
+ var options = {
+ hostname: "localhost",
+ port: 3600,
+ path: "/a?xyz",
+ method: "GET",
+ };
+ http.get(options, concatJson(resolve, reject)).on("error", reject);
+ }))
+ .then(function (res) {
+ assert.deepEqual(res.parsedJson, { a: "b" });
+ assert.deepEqual(res.responseUrl, "http://localhost:3600/a?xyz");
+ });
+ });
+
+ it("http.get with host option and callback - redirect", function () {
+ app.get("/a", redirectsTo("/b"));
+ app.get("/b", redirectsTo("/c"));
+ app.get("/c", redirectsTo("/d"));
+ app.get("/d", redirectsTo("/e"));
+ app.get("/e", redirectsTo("/f"));
+ app.get("/f", sendsJson({ a: "b" }));
+
+ return server.start(app)
+ .then(asPromise(function (resolve, reject) {
+ var options = {
+ host: "localhost",
+ port: 3600,
+ path: "/a",
+ method: "GET",
+ };
+ http.get(options, concatJson(resolve, reject)).on("error", reject);
+ }))
+ .then(function (res) {
+ assert.deepEqual(res.parsedJson, { a: "b" });
+ assert.deepEqual(res.responseUrl, "http://localhost:3600/f");
+ });
+ });
+
+ it("http.get with response event", function () {
+ app.get("/a", redirectsTo("/b"));
+ app.get("/b", redirectsTo("/c"));
+ app.get("/c", redirectsTo("/d"));
+ app.get("/d", redirectsTo("/e"));
+ app.get("/e", redirectsTo("/f"));
+ app.get("/f", sendsJson({ a: "b" }));
+
+ return server.start(app)
+ .then(asPromise(function (resolve, reject) {
+ http.get("http://localhost:3600/a")
+ .on("response", concatJson(resolve, reject))
+ .on("error", reject);
+ }))
+ .then(function (res) {
+ assert.deepEqual(res.parsedJson, { a: "b" });
+ assert.deepEqual(res.responseUrl, "http://localhost:3600/f");
+ });
+ });
+
+ it("should return with the original status code if the response does not contain a location header", function () {
+ app.get("/a", function (req, res) {
+ res.status(307).end();
+ });
+
+ return server.start(app)
+ .then(asPromise(function (resolve, reject) {
+ http.get("http://localhost:3600/a", resolve).on("error", reject);
+ }))
+ .then(function (res) {
+ assert.equal(res.statusCode, 307);
+ assert.deepEqual(res.responseUrl, "http://localhost:3600/a");
+ res.on("data", function () {
+ // noop to consume the stream (server won't shut down otherwise).
+ });
+ });
+ });
+
+ it("should emit connection errors on the returned stream", function () {
+ app.get("/a", redirectsTo("http://localhost:36002/b"));
+
+ return server.start(app)
+ .then(asPromise(function (resolve, reject) {
+ http.get("http://localhost:3600/a", reject).on("error", resolve);
+ }))
+ .then(function (error) {
+ assert.equal(error.code, "ECONNREFUSED");
+ });
+ });
+
+ it("should emit socket events on the returned stream", function () {
+ app.get("/a", sendsJson({ a: "b" }));
+
+ return server.start(app)
+ .then(asPromise(function (resolve, reject) {
+ http.get("http://localhost:3600/a")
+ .on("socket", resolve)
+ .on("error", reject);
+ }))
+ .then(function (socket) {
+ assert(socket instanceof net.Socket, "socket event should emit with socket");
+ });
+ });
+
+ it("should emit connect events on the returned stream", function () {
+ app.get("/a", sendsJson({ a: "b" }));
+
+ var req;
+ return server.start(app)
+ .then(asPromise(function (resolve, reject) {
+ req = http.get("http://localhost:3600/a");
+ req.on("connect", function (response, socket, head) {
+ resolve({ response: response, socket: socket, head: head });
+ });
+ req.on("error", reject);
+ req._currentRequest.emit("connect", "r", "s", "h");
+ }))
+ .then(function (args) {
+ req.abort();
+ assert.equal(args.response, "r");
+ assert.equal(args.socket, "s");
+ assert.equal(args.head, "h");
+ });
+ });
+
+ it("emits an error on redirects with an invalid location", function () {
+ if (nodeMajorVersion < 10) {
+ this.skip();
+ }
+
+ app.get("/a", function (req, res) {
+ // Explictly send response with invalid Location header
+ res.socket.write("HTTP/1.1 301 Moved Permanently\n");
+ res.socket.write("Location: http://смольный-институт.рф\n");
+ res.socket.write("\n");
+ res.socket.end();
+ });
+
+ return server.start(app)
+ .then(asPromise(function (resolve) {
+ http.get("http://localhost:3600/a").on("error", resolve);
+ }))
+ .then(function (error) {
+ assert(error instanceof Error);
+ assert.equal(error.code, "ERR_FR_REDIRECTION_FAILURE");
+ assert.equal(error.message, "Redirected request failed: Request path contains unescaped characters");
+ assert(error.cause instanceof Error);
+ assert.equal(error.cause.code, "ERR_UNESCAPED_CHARACTERS");
+ });
+ });
+
+ it("emits an error when the request fails for another reason", function () {
+ app.get("/a", function (req, res) {
+ res.socket.write("HTTP/1.1 301 Moved Permanently\n");
+ res.socket.write("Location: other\n");
+ res.socket.write("\n");
+ res.socket.end();
+ });
+
+ return server.start(app)
+ .then(asPromise(function (resolve) {
+ var request = http.get("http://localhost:3600/a");
+ request._performRequest = function () {
+ throw new Error("custom");
+ };
+ request.on("error", resolve);
+ }))
+ .then(function (error) {
+ assert(error instanceof Error);
+ assert.equal(error.message, "Redirected request failed: custom");
+ });
+ });
+
+ describe("setTimeout", function () {
+ var clock;
+ beforeEach(function () {
+ clock = lolex.install();
+ });
+ afterEach(function () {
+ clock.uninstall();
+ });
+
+ it("clears timeouts after a successful response", function () {
+ app.get("/redirect", redirectsTo("/timeout"));
+ app.get("/timeout", delay(clock, 2000, sendsJson({ didnot: "timeout" })));
+
+ var req;
+ return server.start(app)
+ .then(asPromise(function (resolve, reject) {
+ req = http.get("http://localhost:3600/redirect", concatJson(resolve, reject));
+ req.on("error", reject);
+ req.setTimeout(3000, function () {
+ throw new Error("should not have timed out");
+ });
+ }))
+ .then(function (res) {
+ assert.deepEqual(res.parsedJson, { didnot: "timeout" });
+ assert.deepEqual(res.responseUrl, "http://localhost:3600/timeout");
+ clock.tick(5000);
+ });
+ });
+
+ it("clears timeouts after an error response", function () {
+ app.get("/redirect", redirectsTo("http://localhost:3602/b"));
+
+ var req;
+ return server.start(app)
+ .then(asPromise(function (resolve, reject) {
+ req = http.get("http://localhost:3600/redirect", reject);
+ req.setTimeout(3000, function () {
+ throw new Error("should not have timed out");
+ });
+ req.on("error", resolve);
+ }))
+ .then(function (error) {
+ assert.equal(error.code, "ECONNREFUSED");
+ clock.tick(5000);
+ });
+ });
+
+ it("sets a timeout when the socket already exists", function () {
+ app.get("/timeout", delay(clock, 5000, sendsJson({ timed: "out" })));
+
+ return server.start(app)
+ .then(asPromise(function (resolve, reject) {
+ var req = http.get("http://localhost:3600/timeout", function () {
+ throw new Error("should have timed out");
+ });
+ req.on("error", reject);
+ req.on("socket", function () {
+ assert(req.socket instanceof net.Socket);
+ req.setTimeout(3000, function () {
+ req.abort();
+ resolve();
+ });
+ });
+ }));
+ });
+
+ it("destroys the socket after configured inactivity period", function () {
+ app.get("/data", delay(clock, 3000, sendsJson({ took: "toolongtosenddata" })));
+
+ return server.start(app)
+ .then(asPromise(function (resolve, reject) {
+ var req = http.get("http://localhost:3600/data", concatJson(reject, reject));
+ req.on("error", reject);
+ req.setTimeout(100, function () {
+ throw new Error("should not have timed out");
+ });
+ req.on("socket", function () {
+ req.socket.on("timeout", function () {
+ resolve();
+ });
+ });
+ }));
+ });
+
+ it("should timeout on the final request", function () {
+ app.get("/redirect1", redirectsTo("/redirect2"));
+ app.get("/redirect2", redirectsTo("/timeout"));
+ app.get("/timeout", delay(clock, 5000, sendsJson({ timed: "out" })));
+
+ return server.start(app)
+ .then(asPromise(function (resolve, reject) {
+ var req = http.get("http://localhost:3600/redirect1", function () {
+ throw new Error("should have timed out");
+ });
+ req.on("error", reject);
+ req.setTimeout(1000, function () {
+ req.abort();
+ resolve();
+ });
+ }));
+ });
+
+ it("should include redirect delays in the timeout", function () {
+ app.get("/redirect1", delay(clock, 1000, redirectsTo("/redirect2")));
+ app.get("/redirect2", delay(clock, 1000, redirectsTo("/redirect3")));
+ app.get("/redirect3", delay(clock, 1000, "/timeout"));
+ app.get("/timeout", delay(clock, 1000, sendsJson({ timed: "out" })));
+
+ return server.start(app)
+ .then(asPromise(function (resolve, reject) {
+ var req = http.get("http://localhost:3600/redirect1", function () {
+ throw new Error("should have timed out");
+ });
+ req.on("error", reject);
+ req.setTimeout(2000, function () {
+ req.abort();
+ resolve();
+ });
+ }));
+ });
+
+ it("overrides existing timeouts", function () {
+ app.get("/redirect", redirectsTo("/timeout"));
+ app.get("/timeout", delay(clock, 5000, sendsJson({ timed: "out" })));
+
+ return server.start(app)
+ .then(asPromise(function (resolve, reject) {
+ var req = http.get("http://localhost:3600/redirect", function () {
+ throw new Error("should have timed out");
+ });
+ req.on("error", reject);
+
+ var callbacks = 0;
+ function timeoutCallback() {
+ if (++callbacks === 3) {
+ req.abort();
+ resolve(callbacks);
+ }
+ }
+ req.setTimeout(10000, timeoutCallback);
+ req.setTimeout(10000, timeoutCallback);
+ req.setTimeout(1000, timeoutCallback);
+ }));
+ });
+ });
+
+ it("should follow redirects over https", function () {
+ app.get("/a", redirectsTo("/b"));
+ app.get("/b", redirectsTo("/c"));
+ app.get("/c", sendsJson({ baz: "quz" }));
+
+ return server.start(httpsOptions(app))
+ .then(asPromise(function (resolve, reject) {
+ var opts = url.parse("https://localhost:3601/a");
+ opts.ca = ca;
+ https.get(opts, concatJson(resolve, reject)).on("error", reject);
+ }))
+ .then(function (res) {
+ assert.deepEqual(res.parsedJson, { baz: "quz" });
+ assert.deepEqual(res.responseUrl, "https://localhost:3601/c");
+ });
+ });
+
+ it("should destroy responses", function () {
+ app.get("/a", hangingRedirectTo("/b"));
+ app.get("/b", hangingRedirectTo("/c"));
+ app.get("/c", hangingRedirectTo("/d"));
+ app.get("/d", hangingRedirectTo("/e"));
+ app.get("/e", hangingRedirectTo("/f"));
+ app.get("/f", sendsJson({ a: "b" }));
+
+ function hangingRedirectTo(destination) {
+ return function (req, res) {
+ res.writeHead(301, { location: destination });
+ res.write(new Array(128).join(" "));
+ };
+ }
+
+ return server.start(app)
+ .then(asPromise(function (resolve, reject) {
+ http.get("http://localhost:3600/a", concatJson(resolve, reject)).on("error", reject);
+ }))
+ .then(function (res) {
+ assert.deepEqual(res.parsedJson, { a: "b" });
+ assert.deepEqual(res.responseUrl, "http://localhost:3600/f");
+ });
+ });
+
+ it("should honor query params in redirects", function () {
+ app.get("/a", redirectsTo("/b?greeting=hello"));
+ app.get("/b", function (req, res) {
+ res.json({ greeting: req.query.greeting });
+ });
+
+ return server.start(app)
+ .then(asPromise(function (resolve, reject) {
+ http.get("http://localhost:3600/a", concatJson(resolve, reject)).on("error", reject);
+ }))
+ .then(function (res) {
+ assert.deepEqual(res.parsedJson, { greeting: "hello" });
+ assert.deepEqual(res.responseUrl, "http://localhost:3600/b?greeting=hello");
+ });
+ });
+
+ it("should allow aborting", function () {
+ var request;
+
+ app.get("/a", redirectsTo("/b"));
+ app.get("/b", redirectsTo("/c"));
+ app.get("/c", function () {
+ request.abort();
+ });
+
+ return server.start(app)
+ .then(asPromise(function (resolve, reject) {
+ var currentTime = Date.now();
+ request = http.get("http://localhost:3600/a", resolve);
+ assert(request.aborted === false || // Node >= v11.0.0
+ typeof request.aborted === "undefined"); // Node < v11.0.0
+ request.on("response", reject);
+ request.on("error", reject);
+ request.on("abort", onAbort);
+ function onAbort() {
+ assert(request.aborted === true || // Node >= v11.0.0
+ typeof request.aborted === "number" &&
+ request.aborted > currentTime); // Node < v11.0.0
+ request.removeListener("error", reject);
+ request.on("error", noop);
+ resolve();
+ }
+ }));
+ });
+
+ it("should provide connection", function () {
+ var request;
+
+ app.get("/a", sendsJson({}));
+
+ return server.start(app)
+ .then(asPromise(function (resolve) {
+ request = http.get("http://localhost:3600/a", resolve);
+ }))
+ .then(function () {
+ assert(request.connection instanceof net.Socket);
+ });
+ });
+
+ it("should provide flushHeaders", function () {
+ app.get("/a", redirectsTo("/b"));
+ app.get("/b", sendsJson({ foo: "bar" }));
+
+ return server.start(app)
+ .then(asPromise(function (resolve, reject) {
+ var request = http.get("http://localhost:3600/a", resolve);
+ request.flushHeaders();
+ request.on("error", reject);
+ }));
+ });
+
+ it("should provide getHeader", function () {
+ var req = http.request("http://localhost:3600/a");
+ req.setHeader("my-header", "my value");
+ assert.equal(req.getHeader("my-header"), "my value");
+ req.abort();
+ });
+
+ it("should provide removeHeader", function () {
+ app.get("/a", redirectsTo("/b"));
+ app.get("/b", function (req, res) {
+ res.end(JSON.stringify(req.headers));
+ });
+
+ return server.start(app)
+ .then(asPromise(function (resolve, reject) {
+ var req = http.request("http://localhost:3600/a", concatJson(resolve, reject));
+ req.setHeader("my-header", "my value");
+ assert.equal(req.getHeader("my-header"), "my value");
+ req.removeHeader("my-header");
+ assert.equal(req.getHeader("my-header"), undefined);
+ req.end();
+ }))
+ .then(function (res) {
+ var headers = res.parsedJson;
+ assert.equal(headers["my-header"], undefined);
+ });
+ });
+
+ it("should provide setHeader", function () {
+ app.get("/a", redirectsTo("/b"));
+ app.get("/b", function (req, res) {
+ res.end(JSON.stringify(req.headers));
+ });
+
+ return server.start(app)
+ .then(asPromise(function (resolve, reject) {
+ var req = http.request("http://localhost:3600/a", concatJson(resolve, reject));
+ req.setHeader("my-header", "my value");
+ assert.equal(req.getHeader("my-header"), "my value");
+ req.end();
+ }))
+ .then(function (res) {
+ var headers = res.parsedJson;
+ assert.equal(headers["my-header"], "my value");
+ });
+ });
+
+ it("should provide setNoDelay", function () {
+ app.get("/a", redirectsTo("/b"));
+ app.get("/b", sendsJson({ foo: "bar" }));
+
+ return server.start(app)
+ .then(asPromise(function (resolve, reject) {
+ var request = http.get("http://localhost:3600/a", resolve);
+ request.setNoDelay(true);
+ request.on("error", reject);
+ }));
+ });
+
+ it("should provide setSocketKeepAlive", function () {
+ app.get("/a", redirectsTo("/b"));
+ app.get("/b", sendsJson({ foo: "bar" }));
+
+ return server.start(app)
+ .then(asPromise(function (resolve) {
+ var request = http.get("http://localhost:3600/a", resolve);
+ request.setSocketKeepAlive(true);
+ }));
+ });
+
+ it("should provide setTimeout", function () {
+ app.get("/a", redirectsTo("/b"));
+ app.get("/b", sendsJson({ foo: "bar" }));
+
+ return server.start(app)
+ .then(asPromise(function (resolve) {
+ var request = http.get("http://localhost:3600/a", resolve);
+ request.setTimeout(1000);
+ }));
+ });
+
+ it("should provide socket", function () {
+ var request;
+
+ app.get("/a", sendsJson({}));
+
+ return server.start(app)
+ .then(asPromise(function (resolve) {
+ request = http.get("http://localhost:3600/a", resolve);
+ }))
+ .then(function () {
+ assert(request.socket instanceof net.Socket);
+ });
+ });
+
+ describe("should obey a `maxRedirects` property", function () {
+ beforeEach(function () {
+ var i = 22;
+ while (i > 0) {
+ app.get("/r" + i, redirectsTo("/r" + --i));
+ }
+ app.get("/r0", sendsJson({ foo: "bar" }));
+ });
+
+ it("which defaults to 21", function () {
+ return server.start(app)
+ // 21 redirects should work fine
+ .then(asPromise(function (resolve, reject) {
+ http.get("http://localhost:3600/r21", concatJson(resolve, reject)).on("error", reject);
+ }))
+ .then(function (res) {
+ assert.deepEqual(res.parsedJson, { foo: "bar" });
+ assert.deepEqual(res.responseUrl, "http://localhost:3600/r0");
+ })
+ // 22 redirects should fail
+ .then(asPromise(function (resolve, reject) {
+ http.get("http://localhost:3600/r22", reject).on("error", resolve);
+ }))
+ .then(function (error) {
+ assert(error instanceof Error);
+ assert.equal(error.code, "ERR_FR_TOO_MANY_REDIRECTS");
+ assert.equal(error.message, "Maximum number of redirects exceeded");
+ });
+ });
+
+ it("which can be set globally", function () {
+ followRedirects.maxRedirects = 22;
+ return server.start(app)
+ .then(asPromise(function (resolve, reject) {
+ http.get("http://localhost:3600/r22", concatJson(resolve, reject)).on("error", reject);
+ }))
+ .then(function (res) {
+ assert.deepEqual(res.parsedJson, { foo: "bar" });
+ assert.deepEqual(res.responseUrl, "http://localhost:3600/r0");
+ });
+ });
+
+ it("set as an option on an individual request", function () {
+ var u = url.parse("http://localhost:3600/r2");
+ u.maxRedirects = 1;
+
+ return server.start(app)
+ .then(asPromise(function (resolve, reject) {
+ http.get(u, reject).on("error", resolve);
+ }))
+ .then(function (error) {
+ assert(error instanceof Error);
+ assert.equal(error.code, "ERR_FR_TOO_MANY_REDIRECTS");
+ assert.equal(error.message, "Maximum number of redirects exceeded");
+ });
+ });
+ });
+
+ describe("the trackRedirects option", function () {
+ beforeEach(function () {
+ app.get("/a", redirectsTo("/b"));
+ app.get("/b", redirectsTo("/c"));
+ app.get("/c", sendsJson({}));
+ });
+
+ describe("when not set", function () {
+ it("should not track redirects", function () {
+ return server.start(app)
+ .then(asPromise(function (resolve, reject) {
+ var opts = url.parse("http://localhost:3600/a");
+ http.get(opts, concatJson(resolve, reject)).on("error", reject);
+ }))
+ .then(function (res) {
+ var redirects = res.redirects;
+ assert.equal(redirects.length, 0);
+ });
+ });
+ });
+
+ describe("when set to true", function () {
+ it("should track redirects", function () {
+ return server.start(app)
+ .then(asPromise(function (resolve, reject) {
+ var opts = url.parse("http://localhost:3600/a");
+ opts.trackRedirects = true;
+ http.get(opts, concatJson(resolve, reject)).on("error", reject);
+ }))
+ .then(function (res) {
+ var redirects = res.redirects;
+ assert.equal(redirects.length, 3);
+
+ assert.equal(redirects[0].url, "http://localhost:3600/a");
+ assert.equal(redirects[0].statusCode, 302);
+ assert.equal(redirects[0].headers["content-type"], "text/plain; charset=utf-8");
+
+ assert.equal(redirects[1].url, "http://localhost:3600/b");
+ assert.equal(redirects[1].statusCode, 302);
+ assert.equal(redirects[1].headers["content-type"], "text/plain; charset=utf-8");
+
+ assert.equal(redirects[2].url, "http://localhost:3600/c");
+ assert.equal(redirects[2].statusCode, 200);
+ assert.equal(redirects[2].headers["content-type"], "application/json; charset=utf-8");
+ });
+ });
+ });
+ });
+
+ describe("should switch to safe methods when appropriate", function () {
+ function itChangesMethod(statusCode, postToGet, changeAll) {
+ describe("when redirecting with status code " + statusCode, function () {
+ itRedirectsWith(statusCode, "GET", "GET");
+ itRedirectsWith(statusCode, "HEAD", "HEAD");
+ itRedirectsWith(statusCode, "POST", postToGet ? "GET" : "POST");
+ itRedirectsWith(statusCode, "PUT", changeAll ? "GET" : "PUT");
+ itRedirectsWith(statusCode, "DELETE", changeAll ? "GET" : "DELETE");
+ });
+ }
+
+ function itRedirectsWith(statusCode, originalMethod, redirectedMethod) {
+ var description = "should " +
+ (originalMethod === redirectedMethod ? "reuse " + originalMethod :
+ "switch from " + originalMethod + " to " + redirectedMethod);
+ it(description, function () {
+ app[originalMethod.toLowerCase()]("/a", redirectsTo(statusCode, "/b"));
+ app[redirectedMethod.toLowerCase()]("/b", sendsJson({ a: "b" }));
+
+ return server.start(app)
+ .then(asPromise(function (resolve, reject) {
+ var opts = url.parse("http://localhost:3600/a");
+ opts.method = originalMethod;
+ http.request(opts, resolve).on("error", reject).end();
+ }))
+ .then(function (res) {
+ assert.deepEqual(res.responseUrl, "http://localhost:3600/b");
+ if (res.statusCode !== 200) {
+ throw new Error("Did not use " + redirectedMethod);
+ }
+ });
+ });
+ }
+
+ itChangesMethod(300, false);
+ itChangesMethod(301, true);
+ itChangesMethod(302, true);
+ itChangesMethod(303, true, true);
+ itChangesMethod(307, false);
+ });
+
+ describe("should handle cross protocol redirects ", function () {
+ it("(https -> http -> https)", function () {
+ app.get("/a", redirectsTo("http://localhost:3600/b"));
+ app2.get("/b", redirectsTo("https://localhost:3601/c"));
+ app.get("/c", sendsJson({ yes: "no" }));
+
+ return Promise.all([server.start(httpsOptions(app)), server.start(app2)])
+ .then(asPromise(function (resolve, reject) {
+ var opts = url.parse("https://localhost:3601/a");
+ opts.ca = ca;
+ https.get(opts, concatJson(resolve, reject)).on("error", reject);
+ }))
+ .then(function (res) {
+ assert.deepEqual(res.parsedJson, { yes: "no" });
+ assert.deepEqual(res.responseUrl, "https://localhost:3601/c");
+ });
+ });
+
+ it("(http -> https -> http)", function () {
+ app.get("/a", redirectsTo("https://localhost:3601/b"));
+ app2.get("/b", redirectsTo("http://localhost:3600/c"));
+ app.get("/c", sendsJson({ hello: "goodbye" }));
+
+ return Promise.all([server.start(app), server.start(httpsOptions(app2))])
+ .then(asPromise(function (resolve, reject) {
+ var opts = url.parse("http://localhost:3600/a");
+ opts.ca = ca;
+ http.get(opts, concatJson(resolve, reject)).on("error", reject);
+ }))
+ .then(function (res) {
+ assert.deepEqual(res.parsedJson, { hello: "goodbye" });
+ assert.deepEqual(res.responseUrl, "http://localhost:3600/c");
+ });
+ });
+ });
+
+ describe("should error on an unsupported protocol redirect", function () {
+ it("(http -> about)", function () {
+ app.get("/a", redirectsTo("about:blank"));
+
+ return server.start(app)
+ .then(asPromise(function (resolve, reject) {
+ http.get("http://localhost:3600/a")
+ .on("response", function () { return reject(new Error("unexpected response")); })
+ .on("error", reject);
+ }))
+ .catch(function (error) {
+ assert(error instanceof Error);
+ assert(error instanceof TypeError);
+ assert.equal(error.message, "Unsupported protocol about:");
+ });
+ });
+ });
+
+ it("should wait for an explicit call to end", function () {
+ var redirected = false;
+ app.post("/a", redirectsTo(307, "http://localhost:3600/b"));
+ app.post("/b", redirectsTo(307, "http://localhost:3600/c"));
+ app.post("/c", redirectsTo(307, "http://localhost:3600/d"));
+ app.post("/d", function (req, res) {
+ redirected = true;
+ req.pipe(res);
+ });
+
+ var req;
+
+ return server.start(app)
+ .then(asPromise(function (resolve, reject) {
+ req = http.request("http://localhost:3600/a", { method: "POST" }, resolve);
+ req.write(testFileString);
+ req.on("error", reject);
+ }))
+ .then(asPromise(function (resolve, reject, res) {
+ assert(redirected);
+ // If we can still write to the request, it wasn't closed yet
+ req.write(testFileString);
+ req.end();
+ res.pipe(concat({ encoding: "string" }, resolve)).on("error", reject);
+ }))
+ .then(function (str) {
+ assert.equal(str, testFileString + testFileString);
+ });
+ });
+
+ it("errors on write after end", function () {
+ app.post("/a", function (req, res) {
+ req.pipe(res);
+ });
+
+ return server.start(app)
+ .then(function () {
+ var req = http.request("http://localhost:3600/a", { method: "POST" });
+ req.write(testFileString);
+ req.end();
+ try {
+ req.write(testFileString);
+ }
+ catch (error) {
+ assert(error instanceof Error);
+ assert.equal(error.code, "ERR_STREAM_WRITE_AFTER_END");
+ assert.equal(error.message, "write after end");
+ return;
+ }
+ finally {
+ req.abort();
+ }
+ throw new Error("no error");
+ });
+ });
+
+ it("should support writing into request stream without redirects", function () {
+ app.post("/a", function (req, res) {
+ req.pipe(res);
+ });
+
+ return server.start(app)
+ .then(asPromise(function (resolve, reject) {
+ var req = http.request("http://localhost:3600/a", { method: "POST" }, resolve);
+ req.end(testFileBuffer, "buffer");
+ req.on("error", reject);
+ }))
+ .then(asPromise(function (resolve, reject, res) {
+ assert.deepEqual(res.responseUrl, "http://localhost:3600/a");
+ res.pipe(concat({ encoding: "string" }, resolve)).on("error", reject);
+ }))
+ .then(function (str) {
+ assert.equal(str, testFileString);
+ });
+ });
+
+ it("should support writing into request stream with redirects", function () {
+ app.post("/a", redirectsTo(307, "http://localhost:3600/b"));
+ app.post("/b", redirectsTo(307, "http://localhost:3600/c"));
+ app.post("/c", redirectsTo(307, "http://localhost:3600/d"));
+ app.post("/d", function (req, res) {
+ req.pipe(res);
+ });
+
+ return server.start(app)
+ .then(asPromise(function (resolve, reject) {
+ var req = http.request("http://localhost:3600/a", { method: "POST" }, resolve);
+ req.end(testFileBuffer, "buffer");
+ req.on("error", reject);
+ }))
+ .then(asPromise(function (resolve, reject, res) {
+ res.pipe(concat({ encoding: "string" }, resolve)).on("error", reject);
+ }))
+ .then(function (str) {
+ assert.equal(str, testFileString);
+ });
+ });
+
+ it("should support piping into request stream without redirects", function () {
+ app.post("/a", function (req, res) {
+ req.pipe(res);
+ });
+
+ return server.start(app)
+ .then(asPromise(function (resolve, reject) {
+ var req = http.request("http://localhost:3600/a", { method: "POST" }, resolve);
+ fs.createReadStream(testFile).pipe(req);
+ req.on("error", reject);
+ }))
+ .then(asPromise(function (resolve, reject, res) {
+ assert.deepEqual(res.responseUrl, "http://localhost:3600/a");
+ res.pipe(concat({ encoding: "string" }, resolve)).on("error", reject);
+ }))
+ .then(function (str) {
+ assert.equal(str, testFileString);
+ });
+ });
+
+ it("should support piping into request stream with redirects", function () {
+ app.post("/a", redirectsTo(307, "http://localhost:3600/b"));
+ app.post("/b", redirectsTo(307, "http://localhost:3600/c"));
+ app.post("/c", redirectsTo(307, "http://localhost:3600/d"));
+ app.post("/d", function (req, res) {
+ req.pipe(res);
+ });
+
+ return server.start(app)
+ .then(asPromise(function (resolve, reject) {
+ var req = http.request("http://localhost:3600/a", { method: "POST" }, resolve);
+ fs.createReadStream(testFile).pipe(req);
+ req.on("error", reject);
+ }))
+ .then(asPromise(function (resolve, reject, res) {
+ res.pipe(concat({ encoding: "string" }, resolve)).on("error", reject);
+ }))
+ .then(function (str) {
+ assert.equal(str, testFileString);
+ });
+ });
+
+ it("should support piping into request stream with explicit Content-Length without redirects", function () {
+ app.post("/a", function (req, res) {
+ req.pipe(res);
+ });
+
+ var opts = url.parse("http://localhost:3600/a");
+ opts.method = "POST";
+ opts.headers = {
+ "Content-Length": testFileBuffer.byteLength,
+ };
+
+ return server.start(app)
+ .then(asPromise(function (resolve, reject) {
+ var req = http.request(opts, resolve);
+ fs.createReadStream(testFile).pipe(req);
+ req.on("error", reject);
+ }))
+ .then(asPromise(function (resolve, reject, res) {
+ assert.deepEqual(res.responseUrl, "http://localhost:3600/a");
+ res.pipe(concat({ encoding: "string" }, resolve)).on("error", reject);
+ }))
+ .then(function (str) {
+ assert.equal(str, testFileString);
+ });
+ });
+
+ it("should support piping into request stream with explicit Content-Length with redirects", function () {
+ app.post("/a", redirectsTo(307, "http://localhost:3600/b"));
+ app.post("/b", redirectsTo(307, "http://localhost:3600/c"));
+ app.post("/c", redirectsTo(307, "http://localhost:3600/d"));
+ app.post("/d", function (req, res) {
+ req.pipe(res);
+ });
+
+ var opts = url.parse("http://localhost:3600/a");
+ opts.method = "POST";
+ opts.headers = {
+ "Content-Length": testFileBuffer.byteLength,
+ };
+
+ return server.start(app)
+ .then(asPromise(function (resolve, reject) {
+ var req = http.request(opts, resolve);
+ fs.createReadStream(testFile).pipe(req);
+ req.on("error", reject);
+ }))
+ .then(asPromise(function (resolve, reject, res) {
+ res.pipe(concat({ encoding: "string" }, resolve)).on("error", reject);
+ }))
+ .then(function (str) {
+ assert.equal(str, testFileString);
+ });
+ });
+
+ describe("should obey a `maxBodyLength` property", function () {
+ it("which defaults to 10MB", function () {
+ assert.equal(followRedirects.maxBodyLength, 10 * 1024 * 1024);
+ });
+
+ it("set globally, on write", function () {
+ app.post("/a", function (req, res) {
+ req.pipe(res);
+ });
+
+ followRedirects.maxBodyLength = 8;
+ return server.start(app)
+ .then(asPromise(function (resolve, reject) {
+ var req = http.request("http://localhost:3600/a", { method: "POST" }, reject);
+ req.write("12345678");
+ req.on("error", resolve);
+ req.write("9");
+ }))
+ .then(function (error) {
+ assert.equal(error.message, "Request body larger than maxBodyLength limit");
+ });
+ });
+
+ it("set per request, on write", function () {
+ app.post("/a", function (req, res) {
+ req.pipe(res);
+ });
+ var opts = url.parse("http://localhost:3600/a");
+ opts.method = "POST";
+ opts.maxBodyLength = 8;
+
+ return server.start(app)
+ .then(asPromise(function (resolve, reject) {
+ var req = http.request(opts, reject);
+ req.write("12345678");
+ req.on("error", resolve);
+ req.write("9");
+ }))
+ .then(function (error) {
+ assert(error instanceof Error);
+ assert.equal(error.code, "ERR_FR_MAX_BODY_LENGTH_EXCEEDED");
+ assert.equal(error.message, "Request body larger than maxBodyLength limit");
+ });
+ });
+
+ it("set globally, on end", function () {
+ app.post("/a", function (req, res) {
+ req.pipe(res);
+ });
+
+ followRedirects.maxBodyLength = 8;
+ return server.start(app)
+ .then(asPromise(function (resolve, reject) {
+ var req = http.request("http://localhost:3600/a", { method: "POST" }, reject);
+ req.write("12345678");
+ req.on("error", resolve);
+ req.end("9");
+ }))
+ .then(function (error) {
+ assert(error instanceof Error);
+ assert.equal(error.code, "ERR_FR_MAX_BODY_LENGTH_EXCEEDED");
+ assert.equal(error.message, "Request body larger than maxBodyLength limit");
+ });
+ });
+
+ it("set per request, on end", function () {
+ app.post("/a", function (req, res) {
+ req.pipe(res);
+ });
+ var opts = url.parse("http://localhost:3600/a");
+ opts.method = "POST";
+ opts.maxBodyLength = 8;
+
+ return server.start(app)
+ .then(asPromise(function (resolve, reject) {
+ var req = http.request(opts, reject);
+ req.write("12345678");
+ req.on("error", resolve);
+ req.end("9");
+ }))
+ .then(function (error) {
+ assert(error instanceof Error);
+ assert.equal(error.code, "ERR_FR_MAX_BODY_LENGTH_EXCEEDED");
+ assert.equal(error.message, "Request body larger than maxBodyLength limit");
+ });
+ });
+ });
+
+ describe("writing invalid data", function () {
+ it("throws an error", function () {
+ var req = http.request("http://example.org/");
+ var error = null;
+ try {
+ req.write(12345678);
+ }
+ catch (e) {
+ error = e;
+ }
+ req.abort();
+ assert(error instanceof Error);
+ assert(error instanceof TypeError);
+ assert.equal(error.message, "data should be a string, Buffer or Uint8Array");
+ });
+ });
+
+ describe("when switching from POST to GET", function () {
+ it("should drop the entity and associated headers", function () {
+ app.post("/a", redirectsTo(302, "http://localhost:3600/b"));
+ app.get("/b", function (req, res) {
+ res.write(JSON.stringify(req.headers));
+ req.pipe(res); // will invalidate JSON if non-empty
+ });
+
+ var opts = url.parse("http://localhost:3600/a");
+ opts.method = "POST";
+ opts.headers = {
+ "other": "value",
+ "content-type": "application/javascript",
+ "Content-Length": testFileBuffer.byteLength,
+ };
+
+ return server.start(app)
+ .then(asPromise(function (resolve, reject) {
+ var req = http.request(opts, resolve);
+ fs.createReadStream(testFile).pipe(req);
+ req.on("error", reject);
+ }))
+ .then(asPromise(function (resolve, reject, res) {
+ res.pipe(concat({ encoding: "string" }, resolve)).on("error", reject);
+ }))
+ .then(function (str) {
+ var body = JSON.parse(str);
+ assert.equal(body.host, "localhost:3600");
+ assert.equal(body.other, "value");
+ assert.equal(body["content-type"], undefined);
+ assert.equal(body["content-length"], undefined);
+ });
+ });
+ });
+
+ describe("when redirecting to a different host while the host header is set", function () {
+ it("uses the new host header", function () {
+ app.get("/a", redirectsTo(302, "http://localhost:3600/b"));
+ app.get("/b", function (req, res) {
+ res.write(JSON.stringify(req.headers));
+ req.pipe(res); // will invalidate JSON if non-empty
+ });
+
+ return server.start(app)
+ .then(asPromise(function (resolve, reject) {
+ var opts = url.parse("http://localhost:3600/a");
+ opts.headers = { hOsT: "otherhost.com" };
+ http.get(opts, resolve).on("error", reject);
+ }))
+ .then(asPromise(function (resolve, reject, res) {
+ assert.deepEqual(res.statusCode, 200);
+ assert.deepEqual(res.responseUrl, "http://localhost:3600/b");
+ res.pipe(concat({ encoding: "string" }, resolve)).on("error", reject);
+ }))
+ .then(function (str) {
+ var body = JSON.parse(str);
+ assert.equal(body.host, "localhost:3600");
+ });
+ });
+ });
+
+ describe("when the client passes an Authorization header", function () {
+ it("keeps the header when redirected to the same host", function () {
+ app.get("/a", redirectsTo(302, "/b"));
+ app.get("/b", function (req, res) {
+ res.end(JSON.stringify(req.headers));
+ });
+
+ var opts = url.parse("http://localhost:3600/a");
+ opts.headers = {
+ authorization: "bearer my-token-1234",
+ };
+
+ return server.start(app)
+ .then(asPromise(function (resolve, reject) {
+ http.get(opts, resolve).on("error", reject);
+ }))
+ .then(asPromise(function (resolve, reject, res) {
+ res.pipe(concat({ encoding: "string" }, resolve)).on("error", reject);
+ }))
+ .then(function (str) {
+ var body = JSON.parse(str);
+ assert.equal(body.host, "localhost:3600");
+ assert.equal(body.authorization, "bearer my-token-1234");
+ });
+ });
+
+ it("keeps the header when redirected to the same host via header", function () {
+ app.get("/a", redirectsTo(302, "http://localhost:3600/b"));
+ app.get("/b", function (req, res) {
+ res.end(JSON.stringify(req.headers));
+ });
+
+ var opts = url.parse("http://127.0.0.1:3600/a");
+ opts.headers = {
+ host: "localhost",
+ authorization: "bearer my-token-1234",
+ };
+
+ return server.start(app)
+ .then(asPromise(function (resolve, reject) {
+ http.get(opts, resolve).on("error", reject);
+ }))
+ .then(asPromise(function (resolve, reject, res) {
+ res.pipe(concat({ encoding: "string" }, resolve)).on("error", reject);
+ }))
+ .then(function (str) {
+ var body = JSON.parse(str);
+ assert.equal(body.host, "localhost:3600");
+ assert.equal(body.authorization, "bearer my-token-1234");
+ });
+ });
+
+ it("drops the header when redirected to a different host", function () {
+ app.get("/a", redirectsTo(302, "http://127.0.0.1:3600/b"));
+ app.get("/b", function (req, res) {
+ res.end(JSON.stringify(req.headers));
+ });
+
+ var opts = url.parse("http://localhost:3600/a");
+ opts.headers = {
+ authorization: "bearer my-token-1234",
+ };
+
+ return server.start(app)
+ .then(asPromise(function (resolve, reject) {
+ http.get(opts, resolve).on("error", reject);
+ }))
+ .then(asPromise(function (resolve, reject, res) {
+ res.pipe(concat({ encoding: "string" }, resolve)).on("error", reject);
+ }))
+ .then(function (str) {
+ var body = JSON.parse(str);
+ assert.equal(body.host, "127.0.0.1:3600");
+ assert.equal(body.authorization, undefined);
+ });
+ });
+
+ it("drops the header when redirected from a different host via header", function () {
+ app.get("/a", redirectsTo(302, "http://127.0.0.1:3600/b"));
+ app.get("/b", function (req, res) {
+ res.end(JSON.stringify(req.headers));
+ });
+
+ var opts = url.parse("http://127.0.0.1:3600/a");
+ opts.headers = {
+ host: "localhost",
+ authorization: "bearer my-token-1234",
+ };
+
+ return server.start(app)
+ .then(asPromise(function (resolve, reject) {
+ http.get(opts, resolve).on("error", reject);
+ }))
+ .then(asPromise(function (resolve, reject, res) {
+ res.pipe(concat({ encoding: "string" }, resolve)).on("error", reject);
+ }))
+ .then(function (str) {
+ var body = JSON.parse(str);
+ assert.equal(body.host, "127.0.0.1:3600");
+ assert.equal(body.authorization, undefined);
+ });
+ });
+ });
+
+ describe("when the followRedirects option is set to false", function () {
+ it("does not redirect", function () {
+ app.get("/a", redirectsTo(302, "/b"));
+ app.get("/b", sendsJson({ a: "b" }));
+
+ return server.start(app)
+ .then(asPromise(function (resolve, reject) {
+ var opts = url.parse("http://localhost:3600/a");
+ opts.followRedirects = false;
+ http.get(opts, resolve).on("error", reject);
+ }))
+ .then(function (res) {
+ assert.deepEqual(res.statusCode, 302);
+ assert.deepEqual(res.responseUrl, "http://localhost:3600/a");
+ });
+ });
+ });
+
+ describe("should choose the right agent per protocol", function () {
+ it("(https -> http -> https)", function () {
+ app.get("/a", redirectsTo("http://localhost:3600/b"));
+ app2.get("/b", redirectsTo("https://localhost:3601/c"));
+ app.get("/c", sendsJson({ yes: "no" }));
+
+ var httpAgent = addRequestLogging(new http.Agent());
+ var httpsAgent = addRequestLogging(new https.Agent());
+ function addRequestLogging(agent) {
+ agent._requests = [];
+ agent._addRequest = agent.addRequest;
+ agent.addRequest = function (request, options) {
+ this._requests.push(options.path);
+ this._addRequest(request, options);
+ };
+ return agent;
+ }
+
+ return Promise.all([server.start(httpsOptions(app)), server.start(app2)])
+ .then(asPromise(function (resolve, reject) {
+ var opts = url.parse("https://localhost:3601/a");
+ opts.ca = ca;
+ opts.agents = { http: httpAgent, https: httpsAgent };
+ https.get(opts, concatJson(resolve, reject)).on("error", reject);
+ }))
+ .then(function (res) {
+ assert.deepEqual(httpAgent._requests, ["/b"]);
+ assert.deepEqual(httpsAgent._requests, ["/a", "/c"]);
+ assert.deepEqual(res.parsedJson, { yes: "no" });
+ assert.deepEqual(res.responseUrl, "https://localhost:3601/c");
+ });
+ });
+ });
+
+ describe("should not hang on empty writes", function () {
+ it("when data is the empty string without encoding", function () {
+ app.post("/a", sendsJson({ foo: "bar" }));
+
+ return server.start(app)
+ .then(asPromise(function (resolve, reject) {
+ var req = http.request("http://localhost:3600/a", { method: "POST" }, resolve);
+ req.write("");
+ req.write("", function () {
+ req.end("");
+ });
+ req.on("error", reject);
+ }))
+ .then(asPromise(function (resolve, reject, res) {
+ assert.deepEqual(res.responseUrl, "http://localhost:3600/a");
+ res.pipe(concat({ encoding: "string" }, resolve)).on("error", reject);
+ }));
+ });
+
+ it("when data is the empty string with encoding", function () {
+ app.post("/a", sendsJson({ foo: "bar" }));
+
+ return server.start(app)
+ .then(asPromise(function (resolve, reject) {
+ var req = http.request("http://localhost:3600/a", { method: "POST" }, resolve);
+ req.write("");
+ req.write("", "utf8", function () {
+ req.end("", "utf8");
+ });
+ req.on("error", reject);
+ }))
+ .then(asPromise(function (resolve, reject, res) {
+ assert.deepEqual(res.responseUrl, "http://localhost:3600/a");
+ res.pipe(concat({ encoding: "string" }, resolve)).on("error", reject);
+ }));
+ });
+
+ it("when data is Buffer.from('')", function () {
+ app.post("/a", sendsJson({ foo: "bar" }));
+
+ return server.start(app)
+ .then(asPromise(function (resolve, reject) {
+ var req = http.request("http://localhost:3600/a", { method: "POST" }, resolve);
+ req.write(Buffer.from(""));
+ req.write(Buffer.from(""), function () {
+ req.end(Buffer.from(""));
+ });
+ req.on("error", reject);
+ }))
+ .then(asPromise(function (resolve, reject, res) {
+ assert.deepEqual(res.responseUrl, "http://localhost:3600/a");
+ res.pipe(concat({ encoding: "string" }, resolve)).on("error", reject);
+ }));
+ });
+ });
+
+ describe("end accepts as arguments", function () {
+ var called;
+ function setCalled() {
+ called = true;
+ }
+
+ beforeEach(function () {
+ app.post("/a", function (req, res) {
+ req.pipe(res);
+ });
+ called = false;
+ });
+
+
+ it("(none)", function () {
+ return server.start(app)
+ .then(asPromise(function (resolve) {
+ var req = http.request("http://localhost:3600/a", { method: "POST" }, resolve);
+ req.end();
+ }))
+ .then(asPromise(function (resolve, reject, res) {
+ res.pipe(concat({ encoding: "string" }, resolve));
+ }))
+ .then(function (body) {
+ assert.equal(body, "");
+ });
+ });
+
+ it("the empty string", function () {
+ return server.start(app)
+ .then(asPromise(function (resolve) {
+ var req = http.request("http://localhost:3600/a", { method: "POST" }, resolve);
+ req.end("");
+ }))
+ .then(asPromise(function (resolve, reject, res) {
+ res.pipe(concat({ encoding: "string" }, resolve));
+ }))
+ .then(function (body) {
+ assert.equal(body, "");
+ });
+ });
+
+ it("a non-empty string", function () {
+ return server.start(app)
+ .then(asPromise(function (resolve) {
+ var req = http.request("http://localhost:3600/a", { method: "POST" }, resolve);
+ req.end("abc");
+ }))
+ .then(asPromise(function (resolve, reject, res) {
+ res.pipe(concat({ encoding: "string" }, resolve));
+ }))
+ .then(function (body) {
+ assert.equal(body, "abc");
+ });
+ });
+
+ it("a non-empty string and an encoding", function () {
+ return server.start(app)
+ .then(asPromise(function (resolve) {
+ var req = http.request("http://localhost:3600/a", { method: "POST" }, resolve);
+ req.end("abc", "utf8");
+ }))
+ .then(asPromise(function (resolve, reject, res) {
+ res.pipe(concat({ encoding: "string" }, resolve));
+ }))
+ .then(function (body) {
+ assert.equal(body, "abc");
+ });
+ });
+
+ it("a non-empty string, an encoding, and a callback", function () {
+ return server.start(app)
+ .then(asPromise(function (resolve) {
+ var req = http.request("http://localhost:3600/a", { method: "POST" }, resolve);
+ req.end("abc", "utf8", setCalled);
+ }))
+ .then(asPromise(function (resolve, reject, res) {
+ res.pipe(concat({ encoding: "string" }, resolve));
+ }))
+ .then(function (body) {
+ assert.equal(body, "abc");
+ assert.equal(called, true);
+ });
+ });
+
+ it("a non-empty string and a callback", function () {
+ return server.start(app)
+ .then(asPromise(function (resolve) {
+ var req = http.request("http://localhost:3600/a", { method: "POST" }, resolve);
+ req.end("abc", setCalled);
+ }))
+ .then(asPromise(function (resolve, reject, res) {
+ res.pipe(concat({ encoding: "string" }, resolve));
+ }))
+ .then(function (body) {
+ assert.equal(body, "abc");
+ assert.equal(called, true);
+ });
+ });
+
+ it("a callback", function () {
+ return server.start(app)
+ .then(asPromise(function (resolve) {
+ var req = http.request("http://localhost:3600/a", { method: "POST" }, resolve);
+ req.end(setCalled);
+ }))
+ .then(asPromise(function (resolve, reject, res) {
+ res.pipe(concat({ encoding: "string" }, resolve));
+ }))
+ .then(function (body) {
+ assert.equal(body, "");
+ assert.equal(called, true);
+ });
+ });
+ });
+
+ describe("change request options before redirects", function () {
+ it("only call beforeRedirect on redirects, not the inital http call", function () {
+ app.get("/a", sendsJson({ a: "b" }));
+
+ return server.start(app)
+ .then(asPromise(function (resolve, reject) {
+ var options = {
+ host: "localhost",
+ port: 3600,
+ path: "/a",
+ method: "GET",
+ beforeRedirect: function () {
+ assert.fail("this should only be called on redirects");
+ },
+ };
+ http.get(options, concatJson(resolve, reject)).on("error", reject);
+ }))
+ .then(function (res) {
+ assert.deepEqual(res.parsedJson, { a: "b" });
+ assert.deepEqual(res.responseUrl, "http://localhost:3600/a");
+ });
+ });
+
+ it("ignore beforeRedirect if not a function", function () {
+ app.get("/a", redirectsTo("/b"));
+ app.get("/b", redirectsTo("/c"));
+ app.get("/c", sendsJson({ a: "b" }));
+
+ return server.start(app)
+ .then(asPromise(function (resolve, reject) {
+ var options = {
+ host: "localhost",
+ port: 3600,
+ path: "/a",
+ method: "GET",
+ beforeRedirect: 42,
+ };
+ http.get(options, concatJson(resolve, reject)).on("error", reject);
+ }))
+ .then(function (res) {
+ assert.deepEqual(res.parsedJson, { a: "b" });
+ assert.deepEqual(res.responseUrl, "http://localhost:3600/c");
+ });
+ });
+
+ it("append new header with every redirect", function () {
+ app.get("/a", redirectsTo("/b"));
+ app.get("/b", redirectsTo("/c"));
+ app.get("/c", function (req, res) {
+ res.json(req.headers);
+ });
+ var callsToTransform = 0;
+
+ return server.start(app)
+ .then(asPromise(function (resolve, reject) {
+ var options = {
+ host: "localhost",
+ port: 3600,
+ path: "/a",
+ method: "GET",
+ beforeRedirect: function (optionz) {
+ callsToTransform++;
+ if (optionz.path === "/b") {
+ optionz.headers["header-a"] = "value A";
+ }
+ else if (optionz.path === "/c") {
+ optionz.headers["header-b"] = "value B";
+ }
+ },
+ };
+ http.get(options, concatJson(resolve, reject)).on("error", reject);
+ }))
+ .then(function (res) {
+ assert.strictEqual(callsToTransform, 2);
+ assert.strictEqual(res.parsedJson["header-a"], "value A");
+ assert.strictEqual(res.parsedJson["header-b"], "value B");
+ assert.deepEqual(res.responseUrl, "http://localhost:3600/c");
+ });
+ });
+
+ it("abort request chain after throwing an error", function () {
+ var redirected = false;
+ app.get("/a", redirectsTo("/b"));
+ app.get("/b", function () {
+ redirected = true;
+ throw new Error("redirected request should have been aborted");
+ });
+
+ return server.start(app)
+ .then(asPromise(function (resolve, reject) {
+ var options = {
+ host: "localhost",
+ port: 3600,
+ path: "/a",
+ method: "GET",
+ beforeRedirect: function () {
+ throw new Error("no redirects!");
+ },
+ };
+ http.get(options, concatJson(resolve, reject)).on("error", reject);
+ }))
+ .then(function () {
+ assert.fail("request chain should have been aborted");
+ })
+ .catch(function (error) {
+ assert(!redirected);
+ assert(error instanceof Error);
+ assert.equal(error.message, "no redirects!");
+ });
+ });
+
+ it("access response header in beforeRedirect", function () {
+ app.get("/a", redirectsTo("/b"));
+ app.get("/b", function (req, res) {
+ res.json(req.headers);
+ });
+
+ return server.start(app)
+ .then(asPromise(function (resolve, reject) {
+ var options = {
+ host: "localhost",
+ port: 3600,
+ path: "/a",
+ method: "GET",
+ beforeRedirect: function (optionz, response) {
+ optionz.headers.testheader = "itsAtest" + response.headers.location;
+ },
+ };
+ http.get(options, concatJson(resolve, reject)).on("error", reject);
+ }))
+ .then(function (res) {
+ assert.strictEqual(res.parsedJson.testheader, "itsAtest/b");
+ assert.deepEqual(res.responseUrl, "http://localhost:3600/b");
+ });
+ });
+ });
+});
+
+function noop() { /* noop */ }