summaryrefslogtreecommitdiff
path: root/node_modules/needle/test
diff options
context:
space:
mode:
author8cy <[email protected]>2020-04-03 02:37:42 -0700
committer8cy <[email protected]>2020-04-03 02:37:42 -0700
commit60867fb030bae582082340ead7dbc7efdc2f5398 (patch)
tree4c6a7356351be2e4914e15c4703172597c45656e /node_modules/needle/test
parentcommenting (diff)
downloads5nical-60867fb030bae582082340ead7dbc7efdc2f5398.tar.xz
s5nical-60867fb030bae582082340ead7dbc7efdc2f5398.zip
2020/04/03, 02:34, v1.2.0
Diffstat (limited to 'node_modules/needle/test')
-rw-r--r--node_modules/needle/test/basic_auth_spec.js196
-rw-r--r--node_modules/needle/test/compression_spec.js112
-rw-r--r--node_modules/needle/test/cookies_spec.js305
-rw-r--r--node_modules/needle/test/decoder_spec.js86
-rw-r--r--node_modules/needle/test/errors_spec.js286
-rw-r--r--node_modules/needle/test/headers_spec.js198
-rw-r--r--node_modules/needle/test/helpers.js72
-rw-r--r--node_modules/needle/test/long_string_spec.js34
-rw-r--r--node_modules/needle/test/output_spec.js254
-rw-r--r--node_modules/needle/test/parsing_spec.js494
-rw-r--r--node_modules/needle/test/post_data_spec.js1021
-rw-r--r--node_modules/needle/test/proxy_spec.js202
-rw-r--r--node_modules/needle/test/querystring_spec.js128
-rw-r--r--node_modules/needle/test/redirect_spec.js392
-rw-r--r--node_modules/needle/test/redirect_with_timeout.js45
-rw-r--r--node_modules/needle/test/request_stream_spec.js202
-rw-r--r--node_modules/needle/test/response_stream_spec.js139
-rw-r--r--node_modules/needle/test/socket_pool_spec.js66
-rw-r--r--node_modules/needle/test/url_spec.js155
-rw-r--r--node_modules/needle/test/utils/formidable.js17
-rw-r--r--node_modules/needle/test/utils/proxy.js62
-rw-r--r--node_modules/needle/test/utils/test.js104
22 files changed, 4570 insertions, 0 deletions
diff --git a/node_modules/needle/test/basic_auth_spec.js b/node_modules/needle/test/basic_auth_spec.js
new file mode 100644
index 0000000..343f1db
--- /dev/null
+++ b/node_modules/needle/test/basic_auth_spec.js
@@ -0,0 +1,196 @@
+var helpers = require('./helpers'),
+ should = require('should'),
+ needle = require('./../'),
+ server;
+
+var port = 7707;
+
+describe('Basic Auth', function() {
+
+ before(function(done) {
+ server = helpers.server({ port: port }, done);
+ })
+
+ after(function(done) {
+ server.close(done);
+ })
+
+ ///////////////// helpers
+
+ var get_auth = function(header) {
+ var token = header.split(/\s+/).pop();
+ return token && Buffer.from(token, 'base64').toString().split(':');
+ }
+
+ describe('when neither username or password are passed', function() {
+
+ it('doesnt send any Authorization headers', function(done) {
+ needle.get('localhost:' + port, { parse: true }, function(err, resp) {
+ var sent_headers = resp.body.headers;
+ Object.keys(sent_headers).should.not.containEql('authorization');
+ done();
+ })
+ })
+
+ })
+
+ describe('when username is an empty string, and password is a valid string', function() {
+
+ var opts = { username: '', password: 'foobar', parse: true };
+
+ it('doesnt send any Authorization headers', function(done) {
+ needle.get('localhost:' + port, { parse: true }, function(err, resp) {
+ var sent_headers = resp.body.headers;
+ Object.keys(sent_headers).should.not.containEql('authorization');
+ done();
+ })
+ })
+
+ });
+
+ describe('when username is a valid string, but no username is passed', function() {
+
+ var opts = { username: 'foobar', parse: true };
+
+ it('sends Authorization header', function(done) {
+ needle.get('localhost:' + port, opts, function(err, resp) {
+ var sent_headers = resp.body.headers;
+ Object.keys(sent_headers).should.containEql('authorization');
+ done();
+ })
+ })
+
+ it('Basic Auth only includes username, without colon', function(done) {
+ needle.get('localhost:' + port, opts, function(err, resp) {
+ var sent_headers = resp.body.headers;
+ var auth = get_auth(sent_headers['authorization']);
+ auth[0].should.equal('foobar');
+ auth.should.have.lengthOf(1);
+ done();
+ })
+ })
+
+ })
+
+ describe('when username is a valid string, and password is null', function() {
+
+ var opts = { username: 'foobar', password: null, parse: true };
+
+ it('sends Authorization header', function(done) {
+ needle.get('localhost:' + port, opts, function(err, resp) {
+ var sent_headers = resp.body.headers;
+ Object.keys(sent_headers).should.containEql('authorization');
+ done();
+ })
+ })
+
+ it('Basic Auth only includes both username and password', function(done) {
+ needle.get('localhost:' + port, opts, function(err, resp) {
+ var sent_headers = resp.body.headers;
+ var auth = get_auth(sent_headers['authorization']);
+ auth[0].should.equal('foobar');
+ auth[1].should.equal('');
+ done();
+ })
+ })
+
+ })
+
+ describe('when username is a valid string, and password is an empty string', function() {
+
+ var opts = { username: 'foobar', password: '', parse: true };
+
+ it('sends Authorization header', function(done) {
+ needle.get('localhost:' + port, opts, function(err, resp) {
+ var sent_headers = resp.body.headers;
+ Object.keys(sent_headers).should.containEql('authorization');
+ done();
+ })
+ })
+
+ it('Basic Auth only includes both username and password', function(done) {
+ needle.get('localhost:' + port, opts, function(err, resp) {
+ var sent_headers = resp.body.headers;
+ var auth = get_auth(sent_headers['authorization']);
+ auth[0].should.equal('foobar');
+ auth[1].should.equal('');
+ auth.should.have.lengthOf(2);
+ done();
+ })
+ })
+
+ })
+
+ describe('when username AND password are non empty strings', function() {
+
+ var opts = { username: 'foobar', password: 'jakub', parse: true };
+
+ it('sends Authorization header', function(done) {
+ needle.get('localhost:' + port, opts, function(err, resp) {
+ var sent_headers = resp.body.headers;
+ Object.keys(sent_headers).should.containEql('authorization');
+ done();
+ })
+ })
+
+ it('Basic Auth only includes both user and password', function(done) {
+ needle.get('localhost:' + port, opts, function(err, resp) {
+ var sent_headers = resp.body.headers;
+ var auth = get_auth(sent_headers['authorization']);
+ auth[0].should.equal('foobar');
+ auth[1].should.equal('jakub');
+ auth.should.have.lengthOf(2);
+ done();
+ })
+ })
+
+ })
+
+ describe('URL with @ but not username/pass', function() {
+ it('doesnt send Authorization header', function(done) {
+ var url = 'localhost:' + port + '/abc/@def/xyz.zip';
+
+ needle.get(url, {}, function(err, resp) {
+ var sent_headers = resp.body.headers;
+ Object.keys(sent_headers).should.not.containEql('authorization');
+ done();
+ })
+ })
+
+ it('sends user:pass headers if passed via options', function(done) {
+ var url = 'localhost:' + port + '/abc/@def/xyz.zip';
+
+ needle.get(url, { username: 'foo' }, function(err, resp) {
+ var sent_headers = resp.body.headers;
+ Object.keys(sent_headers).should.containEql('authorization');
+ sent_headers['authorization'].should.eql('Basic Zm9v')
+ done();
+ })
+ })
+ })
+
+ describe('when username/password are included in URL', function() {
+ var opts = { parse: true };
+
+ it('sends Authorization header', function(done) {
+ needle.get('foobar:jakub@localhost:' + port, opts, function(err, resp) {
+ var sent_headers = resp.body.headers;
+ Object.keys(sent_headers).should.containEql('authorization');
+ done();
+ })
+ })
+
+ it('Basic Auth only includes both user and password', function(done) {
+ needle.get('foobar:jakub@localhost:' + port, opts, function(err, resp) {
+ var sent_headers = resp.body.headers;
+ var auth = get_auth(sent_headers['authorization']);
+ auth[0].should.equal('foobar');
+ auth[1].should.equal('jakub');
+ auth.should.have.lengthOf(2);
+ done();
+ })
+ })
+
+ })
+
+})
diff --git a/node_modules/needle/test/compression_spec.js b/node_modules/needle/test/compression_spec.js
new file mode 100644
index 0000000..80988be
--- /dev/null
+++ b/node_modules/needle/test/compression_spec.js
@@ -0,0 +1,112 @@
+var should = require('should'),
+ needle = require('./../'),
+ http = require('http'),
+ zlib = require('zlib'),
+ stream = require('stream'),
+ port = 11123,
+ server;
+
+describe('compression', function(){
+
+ require.bind(null, 'zlib').should.not.throw()
+
+ var jsonData = '{"foo":"bar"}';
+
+ describe('when server supports compression', function(){
+
+ before(function(){
+ server = http.createServer(function(req, res) {
+ var raw = new stream.PassThrough();
+
+ var acceptEncoding = req.headers['accept-encoding'];
+ if (!acceptEncoding) {
+ acceptEncoding = '';
+ }
+
+ if (acceptEncoding.match(/\bdeflate\b/)) {
+ res.setHeader('Content-Encoding', 'deflate');
+ raw.pipe(zlib.createDeflate()).pipe(res);
+ } else if (acceptEncoding.match(/\bgzip\b/)) {
+ res.setHeader('Content-Encoding', 'gzip');
+ raw.pipe(zlib.createGzip()).pipe(res);
+ } else if (acceptEncoding.match(/\bbr\b/)) {
+ res.setHeader('Content-Encoding', 'br');
+ raw.pipe(zlib.createBrotliCompress()).pipe(res);
+ } else {
+ raw.pipe(res);
+ }
+
+ res.setHeader('Content-Type', 'application/json')
+ if (req.headers['with-bad']) {
+ res.end('foo'); // end, no deflate data
+ } else {
+ raw.end(jsonData)
+ }
+
+ })
+
+ server.listen(port);
+ });
+
+ after(function(done){
+ server.close(done);
+ })
+
+ describe('and client requests no compression', function() {
+ it('should have the body decompressed', function(done){
+ needle.get('localhost:' + port, function(err, response, body){
+ should.ifError(err);
+ body.should.have.property('foo', 'bar');
+ response.bytes.should.equal(jsonData.length);
+ done();
+ })
+ })
+ })
+
+ describe('and client requests gzip compression', function() {
+ it('should have the body decompressed', function(done){
+ needle.get('localhost:' + port, {headers: {'Accept-Encoding': 'gzip'}}, function(err, response, body){
+ should.ifError(err);
+ body.should.have.property('foo', 'bar');
+ response.bytes.should.not.equal(jsonData.length);
+ done();
+ })
+ })
+ })
+
+ describe('and client requests deflate compression', function() {
+ it('should have the body decompressed', function(done){
+ needle.get('localhost:' + port, {headers: {'Accept-Encoding': 'deflate'}}, function(err, response, body){
+ should.ifError(err);
+ body.should.have.property('foo', 'bar');
+ response.bytes.should.not.equal(jsonData.length);
+ done();
+ })
+ })
+
+ it('should rethrow errors from decompressors', function(done){
+ needle.get('localhost:' + port, {headers: {'Accept-Encoding': 'deflate', 'With-Bad': 'true'}}, function(err, response, body) {
+ should.exist(err);
+ err.message.should.equal("incorrect header check");
+ err.code.should.equal("Z_DATA_ERROR")
+ done();
+ })
+ })
+ })
+
+ describe('and client requests brotli compression', function() {
+ it('should have the body decompressed', function(done){
+ // Skip this test if Brotli is not supported
+ if (typeof zlib.BrotliDecompress !== 'function') {
+ return done();
+ }
+ needle.get('localhost:' + port, {headers: {'Accept-Encoding': 'br'}}, function(err, response, body){
+ should.ifError(err);
+ body.should.have.property('foo', 'bar');
+ response.bytes.should.not.equal(jsonData.length);
+ done();
+ })
+ })
+ })
+ })
+})
diff --git a/node_modules/needle/test/cookies_spec.js b/node_modules/needle/test/cookies_spec.js
new file mode 100644
index 0000000..04b5f11
--- /dev/null
+++ b/node_modules/needle/test/cookies_spec.js
@@ -0,0 +1,305 @@
+var needle = require('../'),
+ cookies = require('../lib/cookies'),
+ sinon = require('sinon'),
+ http = require('http'),
+ should = require('should'),
+ assert = require('assert');
+
+var WEIRD_COOKIE_NAME = 'wc',
+ BASE64_COOKIE_NAME = 'bc',
+ FORBIDDEN_COOKIE_NAME = 'fc',
+ NUMBER_COOKIE_NAME = 'nc';
+
+var WEIRD_COOKIE_VALUE = '!\'*+#()&-./0123456789:<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~',
+ BASE64_COOKIE_VALUE = 'Y29va2llCg==',
+ FORBIDDEN_COOKIE_VALUE = ' ;"\\,',
+ NUMBER_COOKIE_VALUE = 12354342;
+
+var TEST_HOST = 'localhost',
+ NO_COOKIES_TEST_PORT = 11112,
+ ALL_COOKIES_TEST_PORT = 11113;
+
+describe('cookies', function() {
+
+ var setCookieHeader, headers, server, opts;
+
+ function decode(str) {
+ return decodeURIComponent(str);
+ }
+
+ function encode(str) {
+ str = str.toString().replace(/[\x00-\x1F\x7F]/g, encodeURIComponent);
+ return str.replace(/[\s\"\,;\\%]/g, encodeURIComponent);
+ }
+
+ before(function() {
+ setCookieHeader = [
+ WEIRD_COOKIE_NAME + '=' + encode(WEIRD_COOKIE_VALUE) + ';',
+ BASE64_COOKIE_NAME + '=' + encode(BASE64_COOKIE_VALUE) + ';',
+ FORBIDDEN_COOKIE_NAME + '=' + encode(FORBIDDEN_COOKIE_VALUE) + ';',
+ NUMBER_COOKIE_NAME + '=' + encode(NUMBER_COOKIE_VALUE) + ';'
+ ];
+ });
+
+ before(function(done) {
+ serverAllCookies = http.createServer(function(req, res) {
+ res.setHeader('Content-Type', 'text/html');
+ res.setHeader('Set-Cookie', setCookieHeader);
+ res.end('200');
+ }).listen(ALL_COOKIES_TEST_PORT, TEST_HOST, done);
+ });
+
+ after(function(done) {
+ serverAllCookies.close(done);
+ });
+
+ describe('with default options', function() {
+ it('no cookie header is set on request', function(done) {
+ needle.get(
+ TEST_HOST + ':' + ALL_COOKIES_TEST_PORT, function(err, response) {
+ assert(!response.req._headers.cookie);
+ done();
+ });
+ });
+ });
+
+ describe('if response does not contain cookies', function() {
+ before(function(done) {
+ serverNoCookies = http.createServer(function(req, res) {
+ res.setHeader('Content-Type', 'text/html');
+ res.end('200');
+ }).listen(NO_COOKIES_TEST_PORT, TEST_HOST, done);
+ });
+
+ it('response.cookies is undefined', function(done) {
+ needle.get(
+ TEST_HOST + ':' + NO_COOKIES_TEST_PORT, function(error, response) {
+ assert(!response.cookies);
+ done();
+ });
+ });
+
+ after(function(done) {
+ serverNoCookies.close(done);
+ });
+ });
+
+ describe('if response contains cookies', function() {
+
+ it('puts them on resp.cookies', function(done) {
+ needle.get(
+ TEST_HOST + ':' + ALL_COOKIES_TEST_PORT, function(error, response) {
+ response.should.have.property('cookies');
+ done();
+ });
+ });
+
+ it('parses them as a object', function(done) {
+ needle.get(
+ TEST_HOST + ':' + ALL_COOKIES_TEST_PORT, function(error, response) {
+ response.cookies.should.be.an.instanceOf(Object)
+ .and.have.property(WEIRD_COOKIE_NAME);
+ response.cookies.should.have.property(BASE64_COOKIE_NAME);
+ response.cookies.should.have.property(FORBIDDEN_COOKIE_NAME);
+ response.cookies.should.have.property(NUMBER_COOKIE_NAME);
+ done();
+ });
+ });
+
+ it('must decode it', function(done) {
+ needle.get(
+ TEST_HOST + ':' + ALL_COOKIES_TEST_PORT, function(error, response) {
+ response.cookies.wc.should.be.eql(WEIRD_COOKIE_VALUE);
+ response.cookies.bc.should.be.eql(BASE64_COOKIE_VALUE);
+ response.cookies.fc.should.be.eql(FORBIDDEN_COOKIE_VALUE);
+ response.cookies.nc.should.be.eql(NUMBER_COOKIE_VALUE.toString());
+ done();
+ });
+ });
+
+ describe('when a cookie value is invalid', function() {
+
+ before(function() {
+ setCookieHeader = [
+ 'geo_city=%D1%E0%ED%EA%F2-%CF%E5%F2%E5%F0%E1%F3%F0%E3'
+ ];
+ })
+
+ it('doesnt blow up', function(done) {
+ needle.get(TEST_HOST + ':' + ALL_COOKIES_TEST_PORT, function(error, response) {
+ should.not.exist(error)
+ var whatever = 'efbfbdefbfbdefbfbdefbfbdefbfbd2defbfbdefbfbdefbfbdefbfbdefbfbdefbfbdefbfbdefbfbdefbfbd';
+ Buffer.from(response.cookies.geo_city).toString('hex').should.eql(whatever)
+ done();
+ });
+ })
+
+ })
+
+ describe('and response is a redirect', function() {
+
+ var redirectServer, testPort = 22222;
+
+ var responseCookies = [
+ [ // first req
+ WEIRD_COOKIE_NAME + '=' + encode(WEIRD_COOKIE_VALUE) + ';',
+ BASE64_COOKIE_NAME + '=' + encode(BASE64_COOKIE_VALUE) + ';',
+ 'FOO=123;'
+ ], [ // second req
+ FORBIDDEN_COOKIE_NAME + '=' + encode(FORBIDDEN_COOKIE_VALUE) + ';',
+ NUMBER_COOKIE_NAME + '=' + encode(NUMBER_COOKIE_VALUE) + ';'
+ ], [ // third red
+ 'FOO=BAR;'
+ ]
+ ]
+
+ before(function() {
+ redirectServer = http.createServer(function(req, res) {
+ var number = parseInt(req.url.replace('/', ''));
+ var nextUrl = 'http://' + TEST_HOST + ':' + testPort + '/' + (number + 1);
+
+ if (responseCookies[number]) { // got cookies
+ res.statusCode = 302;
+ res.setHeader('Set-Cookie', responseCookies[number]);
+ res.setHeader('Location', nextUrl);
+ } else if (number == 3) {
+ res.statusCode = 302; // redirect but without cookies
+ res.setHeader('Location', nextUrl);
+ }
+
+ res.end('OK');
+ }).listen(22222, TEST_HOST);
+ });
+
+ after(function(done) {
+ redirectServer.close(done);
+ })
+
+ describe('and follow_set_cookies is false', function() {
+
+ var opts = {
+ follow_set_cookies: false,
+ follow_max: 4
+ };
+
+ it('no cookie header set on redirection request', function(done) {
+ var spy = sinon.spy(cookies, 'write');
+
+ needle.get(TEST_HOST + ':' + testPort + '/0', opts, function(err, resp) {
+ spy.callCount.should.eql(0);
+ done();
+ });
+ });
+ });
+
+ describe('and follow_set_cookies is true', function() {
+ var opts = {
+ follow_set_cookies: true,
+ follow_max: 4
+ };
+
+ it('should have all the cookies', function(done) {
+ needle.get(TEST_HOST + ':' + testPort + '/0', opts, function(err, resp) {
+ resp.cookies.should.have.property(WEIRD_COOKIE_NAME);
+ resp.cookies.should.have.property(BASE64_COOKIE_NAME);
+ resp.cookies.should.have.property(FORBIDDEN_COOKIE_NAME);
+ resp.cookies.should.have.property(NUMBER_COOKIE_NAME);
+ resp.cookies.should.have.property('FOO');
+ resp.cookies.FOO.should.eql('BAR'); // should overwrite previous one
+ done();
+ });
+ });
+ });
+ });
+
+ describe('with parse_cookies = false', function() {
+ it('does not parse them', function(done) {
+ needle.get(
+ TEST_HOST + ':' + ALL_COOKIES_TEST_PORT, { parse_cookies: false }, function(error, response) {
+ assert(!response.cookies);
+ done();
+ });
+ });
+ });
+ });
+
+ describe('if request contains cookie header', function() {
+ var opts = {
+ cookies: {}
+ };
+
+ before(function() {
+ opts.cookies[WEIRD_COOKIE_NAME] = WEIRD_COOKIE_VALUE;
+ opts.cookies[BASE64_COOKIE_NAME] = BASE64_COOKIE_VALUE;
+ opts.cookies[FORBIDDEN_COOKIE_NAME] = FORBIDDEN_COOKIE_VALUE;
+ opts.cookies[NUMBER_COOKIE_NAME] = NUMBER_COOKIE_VALUE;
+ });
+
+ it('must be a valid cookie string', function(done) {
+ var COOKIE_PAIR = /^([^=\s]+)\s*=\s*("?)\s*(.*)\s*\2\s*$/;
+
+ var full_header = [
+ WEIRD_COOKIE_NAME + '=' + WEIRD_COOKIE_VALUE,
+ BASE64_COOKIE_NAME + '=' + BASE64_COOKIE_VALUE,
+ FORBIDDEN_COOKIE_NAME + '=' + encode(FORBIDDEN_COOKIE_VALUE),
+ NUMBER_COOKIE_NAME + '=' + NUMBER_COOKIE_VALUE
+ ].join('; ')
+
+ needle.get(TEST_HOST + ':' + ALL_COOKIES_TEST_PORT, opts, function(error, response) {
+ var cookieString = response.req._headers.cookie;
+ cookieString.should.be.type('string');
+
+ cookieString.split(/\s*;\s*/).forEach(function(pair) {
+ COOKIE_PAIR.test(pair).should.be.exactly(true);
+ });
+
+ cookieString.should.be.exactly(full_header);
+ done();
+ });
+ });
+
+ it('dont have to encode allowed characters', function(done) {
+ var COOKIE_PAIR = /^([^=\s]+)\s*=\s*("?)\s*(.*)\s*\2\s*$/,
+ KEY_INDEX = 1,
+ VALUE_INEX = 3;
+
+ needle.get(TEST_HOST + ':' + ALL_COOKIES_TEST_PORT, opts, function(error, response) {
+ var cookieObj = {},
+ cookieString = response.req._headers.cookie;
+
+ cookieString.split(/\s*;\s*/).forEach(function(str) {
+ var pair = COOKIE_PAIR.exec(str);
+ cookieObj[pair[KEY_INDEX]] = pair[VALUE_INEX];
+ });
+
+ cookieObj[WEIRD_COOKIE_NAME].should.be.exactly(WEIRD_COOKIE_VALUE);
+ cookieObj[BASE64_COOKIE_NAME].should.be.exactly(BASE64_COOKIE_VALUE);
+ done();
+ });
+ });
+
+ it('must encode forbidden characters', function(done) {
+ var COOKIE_PAIR = /^([^=\s]+)\s*=\s*("?)\s*(.*)\s*\2\s*$/,
+ KEY_INDEX = 1,
+ VALUE_INEX = 3;
+
+ needle.get(TEST_HOST + ':' + ALL_COOKIES_TEST_PORT, opts, function(error, response) {
+ var cookieObj = {},
+ cookieString = response.req._headers.cookie;
+
+ cookieString.split(/\s*;\s*/).forEach(function(str) {
+ var pair = COOKIE_PAIR.exec(str);
+ cookieObj[pair[KEY_INDEX]] = pair[VALUE_INEX];
+ });
+
+ cookieObj[FORBIDDEN_COOKIE_NAME].should.not.be.eql(
+ FORBIDDEN_COOKIE_VALUE);
+ cookieObj[FORBIDDEN_COOKIE_NAME].should.be.exactly(
+ encode(FORBIDDEN_COOKIE_VALUE));
+ cookieObj[FORBIDDEN_COOKIE_NAME].should.be.exactly(
+ encodeURIComponent(FORBIDDEN_COOKIE_VALUE));
+ done();
+ });
+ });
+ });
+});
diff --git a/node_modules/needle/test/decoder_spec.js b/node_modules/needle/test/decoder_spec.js
new file mode 100644
index 0000000..5225f6f
--- /dev/null
+++ b/node_modules/needle/test/decoder_spec.js
@@ -0,0 +1,86 @@
+var should = require('should'),
+ needle = require('./../'),
+ Q = require('q'),
+ chardet = require('jschardet');
+
+describe('character encoding', function() {
+
+ var url;
+ this.timeout(5000);
+
+ describe('test A', function() {
+
+ before(function() {
+ url = 'http://www.nina.jp/server/slackware/webapp/tomcat_charset.html';
+ })
+
+ describe('with decode = false', function() {
+
+ it('does not decode', function(done) {
+
+ needle.get(url, { decode: false }, function(err, resp) {
+ resp.body.should.be.a.String;
+ chardet.detect(resp.body).encoding.should.eql('windows-1252');
+ resp.body.indexOf('EUCを使う').should.eql(-1);
+ done();
+ })
+
+ })
+
+ })
+
+ describe('with decode = true', function() {
+
+ it('decodes', function(done) {
+
+ needle.get(url, { decode: true }, function(err, resp) {
+ resp.body.should.be.a.String;
+ chardet.detect(resp.body).encoding.should.eql('ascii');
+ resp.body.indexOf('EUCを使う').should.not.eql(-1);
+ done();
+ })
+
+ })
+
+ })
+
+ })
+
+ describe('test B', function() {
+
+ it('encodes to UTF-8', function(done) {
+
+ // Our Needle wrapper that requests a chinese website.
+ var task = Q.nbind(needle.get, needle, 'http://www.chinesetop100.com/');
+
+ // Different instantiations of this task
+ var tasks = [Q.fcall(task, {decode: true}),
+ Q.fcall(task, {decode: false})];
+
+ var results = tasks.map(function(task) {
+ return task.then(function(obj) {
+ return obj[0].body;
+ });
+ });
+
+ // Execute all requests concurrently
+ Q.all(results).done(function(bodies) {
+
+ var charsets = [
+ chardet.detect(bodies[0]).encoding,
+ chardet.detect(bodies[1]).encoding,
+ ]
+
+ // We wanted to decode our first stream.
+ charsets[0].should.equal('ascii');
+ bodies[0].indexOf('全球中文网站前二十强').should.not.equal(-1);
+
+ // But not our second stream.
+ charsets[1].should.equal('windows-1252');
+ bodies[1].indexOf('全球中文网站前二十强').should.equal(-1);
+
+ done();
+ });
+ })
+ })
+})
diff --git a/node_modules/needle/test/errors_spec.js b/node_modules/needle/test/errors_spec.js
new file mode 100644
index 0000000..2f488f5
--- /dev/null
+++ b/node_modules/needle/test/errors_spec.js
@@ -0,0 +1,286 @@
+var needle = require('../'),
+ sinon = require('sinon'),
+ should = require('should'),
+ http = require('http'),
+ Emitter = require('events').EventEmitter,
+ helpers = require('./helpers');
+
+var get_catch = function(url, opts) {
+ var err;
+ try {
+ needle.get(url, opts);
+ } catch(e) {
+ err = e;
+ }
+ return err;
+}
+
+describe('errors', function() {
+
+ after(function(done) {
+ setTimeout(done, 100)
+ })
+
+ describe('when host does not exist', function() {
+
+ var url = 'http://unexistinghost/foo';
+
+ describe('with callback', function() {
+
+ it('does not throw', function() {
+ var ex = get_catch(url);
+ should.not.exist(ex);
+ })
+
+ it('callbacks an error', function(done) {
+ needle.get(url, function(err) {
+ err.should.be.a.Error;
+ done();
+ })
+ })
+
+ it('error should be ENOTFOUND or EADDRINFO or EAI_AGAIN', function(done) {
+ needle.get(url, function(err) {
+ err.code.should.match(/ENOTFOUND|EADDRINFO|EAI_AGAIN/)
+ done();
+ })
+ })
+
+ it('does not callback a response', function(done) {
+ needle.get(url, function(err, resp) {
+ should.not.exist(resp);
+ done();
+ })
+ })
+
+ it('does not emit an error event', function(done) {
+ var emitted = false;
+ var req = needle.get(url, function(err, resp) { })
+
+ req.on('error', function() {
+ emitted = true;
+ })
+
+ setTimeout(function() {
+ emitted.should.eql(false);
+ done();
+ }, 100);
+ })
+
+ })
+
+ describe('without callback', function() {
+
+ it('does not throw', function() {
+ var ex = get_catch(url);
+ should.not.exist(ex);
+ })
+
+ it('emits end event once, with error', function(done) {
+ var callcount = 0,
+ stream = needle.get(url);
+
+ stream.on('done', function(err) {
+ callcount++;
+ })
+
+ setTimeout(function() {
+ callcount.should.equal(1);
+ done();
+ }, 200)
+ })
+
+ it('error should be ENOTFOUND or EADDRINFO or EAI_AGAIN', function(done) {
+ var errorific,
+ stream = needle.get(url);
+
+ stream.on('done', function(err) {
+ errorific = err;
+ })
+
+ setTimeout(function() {
+ should.exist(errorific);
+ errorific.code.should.match(/ENOTFOUND|EADDRINFO|EAI_AGAIN/)
+ done();
+ }, 200)
+ })
+
+ it('does not emit a readable event', function(done) {
+ var called = false,
+ stream = needle.get(url);
+
+ stream.on('readable', function() {
+ called = true;
+ })
+
+ setTimeout(function() {
+ called.should.be.false;
+ done();
+ }, 50)
+ })
+
+ it('does not emit an error event', function(done) {
+ var emitted = false,
+ req = needle.get(url);
+
+ req.on('error', function() {
+ emitted = true;
+ })
+
+ setTimeout(function() {
+ emitted.should.eql(false);
+ done();
+ }, 100);
+ })
+
+ })
+
+ })
+
+ describe('when request times out waiting for response', function() {
+
+ var server,
+ url = 'http://localhost:3333/foo';
+
+ var send_request = function(cb) {
+ return needle.get(url, { response_timeout: 200 }, cb);
+ }
+
+ before(function() {
+ server = helpers.server({ port: 3333, wait: 1000 });
+ })
+
+ after(function() {
+ server.close();
+ })
+
+ describe('with callback', function() {
+
+ it('aborts the request', function(done) {
+
+ var time = new Date();
+
+ send_request(function(err) {
+ var timediff = (new Date() - time);
+ timediff.should.be.within(200, 300);
+ done();
+ })
+
+ })
+
+ it('callbacks an error', function(done) {
+ send_request(function(err) {
+ err.should.be.a.Error;
+ done();
+ })
+ })
+
+ it('error should be ECONNRESET', function(done) {
+ send_request(function(err) {
+ err.code.should.equal('ECONNRESET')
+ done();
+ })
+ })
+
+ it('does not callback a response', function(done) {
+ send_request(function(err, resp) {
+ should.not.exist(resp);
+ done();
+ })
+ })
+
+ it('does not emit an error event', function(done) {
+ var emitted = false;
+
+ var req = send_request(function(err, resp) {
+ should.not.exist(resp);
+ })
+
+ req.on('error', function() {
+ emitted = true;
+ })
+
+ setTimeout(function() {
+ emitted.should.eql(false);
+ done();
+ }, 350);
+ })
+
+ })
+
+ describe('without callback', function() {
+
+ it('emits done event once, with error', function(done) {
+ var called = 0,
+ stream = send_request();
+
+ stream.on('done', function(err) {
+ called++;
+ })
+
+ setTimeout(function() {
+ called.should.equal(1);
+ done();
+ }, 250)
+ })
+
+ it('aborts the request', function(done) {
+
+ var time = new Date();
+ var stream = send_request();
+
+ stream.on('done', function(err) {
+ var timediff = (new Date() - time);
+ timediff.should.be.within(200, 300);
+ done();
+ })
+
+ })
+
+ it('error should be ECONNRESET', function(done) {
+ var error,
+ stream = send_request();
+
+ stream.on('done', function(err) {
+ error = err;
+ })
+
+ setTimeout(function() {
+ error.code.should.equal('ECONNRESET')
+ done();
+ }, 250)
+ })
+
+ it('does not emit a readable event', function(done) {
+ var called = false,
+ stream = send_request();
+
+ stream.on('readable', function() {
+ called = true;
+ })
+
+ setTimeout(function() {
+ called.should.be.false;
+ done();
+ }, 250)
+ })
+
+ it('does not emit an error event', function(done) {
+ var emitted = false;
+ var req = send_request();
+
+ req.on('error', function() {
+ emitted = true;
+ })
+
+ setTimeout(function() {
+ emitted.should.eql(false);
+ done();
+ }, 100);
+ })
+
+ })
+
+ })
+
+})
diff --git a/node_modules/needle/test/headers_spec.js b/node_modules/needle/test/headers_spec.js
new file mode 100644
index 0000000..e6693ef
--- /dev/null
+++ b/node_modules/needle/test/headers_spec.js
@@ -0,0 +1,198 @@
+var http = require('http'),
+ helpers = require('./helpers'),
+ should = require('should');
+
+var port = 54321;
+
+describe('request headers', function() {
+
+ var needle,
+ server,
+ existing_sockets,
+ original_defaultMaxSockets;
+
+ before(function(done) {
+ setTimeout(function() {
+ existing_sockets = get_active_sockets().length;
+ server = helpers.server({ port: port }, done);
+ }, 100);
+ })
+
+ after(function(done) {
+ server.close(done)
+ })
+
+ function send_request(opts, cb) {
+ needle.get('http://localhost:' + port, opts, cb);
+ }
+
+ function get_active_sockets() {
+ var handles = process._getActiveHandles();
+
+ // only return the ones that have a .end() function (like a socket)
+ return handles.filter(function(el) {
+ if (el.constructor.name.toString() == 'Socket') {
+ return el.destroyed !== true;
+ }
+ })
+ }
+
+ describe('old node versions (<0.11.4) with persistent keep-alive connections', function() {
+
+ before(function() {
+ delete require.cache[require.resolve('..')] // in case it was already loaded
+ original_defaultMaxSockets = http.Agent.defaultMaxSockets;
+ http.Agent.defaultMaxSockets = 5;
+ needle = require('..');
+ })
+
+ after(function() {
+ http.Agent.defaultMaxSockets = original_defaultMaxSockets;
+ delete require.cache[require.resolve('..')]
+ })
+
+ describe('default options', function() {
+
+ it('sends a Connection: close header', function(done) {
+ send_request({}, function(err, resp) {
+ resp.body.headers['connection'].should.eql('close');
+ done();
+ })
+ })
+
+ it('no open sockets remain after request', function(done) {
+ send_request({}, function(err, resp) {
+ get_active_sockets().length.should.eql(existing_sockets);
+ done();
+ });
+ })
+
+ })
+
+ describe('passing connection: close', function() {
+
+ it('sends a Connection: close header', function(done) {
+ send_request({ connection: 'close' }, function(err, resp) {
+ resp.body.headers['connection'].should.eql('close');
+ done();
+ })
+ })
+
+ it('no open sockets remain after request', function(done) {
+ send_request({ connection: 'close' }, function(err, resp) {
+ get_active_sockets().length.should.eql(existing_sockets);
+ done();
+ });
+ })
+
+ })
+
+ describe('passing connection: keep-alive', function() {
+
+ it('sends a Connection: keep-alive header (using options.headers.connection)', function(done) {
+ send_request({ headers: { connection: 'keep-alive' }}, function(err, resp) {
+ resp.body.headers['connection'].should.eql('keep-alive');
+ done();
+ })
+ })
+
+ it('sends a Connection: keep-alive header (using options.connection)', function(done) {
+ send_request({ connection: 'keep-alive' }, function(err, resp) {
+ resp.body.headers['connection'].should.eql('keep-alive');
+ done();
+ })
+ })
+
+ it('one open socket remain after request', function(done) {
+ send_request({ connection: 'keep-alive' }, function(err, resp) {
+ get_active_sockets().length.should.eql(existing_sockets + 1);
+ done();
+ });
+ })
+
+ })
+
+ })
+
+ describe('new node versions with smarter connection disposing', function() {
+
+ before(function() {
+ delete require.cache[require.resolve('..')]
+ original_defaultMaxSockets = http.Agent.defaultMaxSockets;
+ http.Agent.defaultMaxSockets = Infinity;
+ needle = require('..');
+ })
+
+ after(function() {
+ http.Agent.defaultMaxSockets = original_defaultMaxSockets;
+ delete require.cache[require.resolve('..')]
+ })
+
+ describe('default options', function() {
+
+ // TODO:
+ // this is weird. by default, new node versions set a 'close' header
+ // while older versions set a keep-alive header
+
+ it.skip('sets a Connection header', function(done) {
+ send_request({}, function(err, resp) {
+ // should.not.exist(resp.body.headers['connection']);
+ // done();
+ })
+ })
+
+ it.skip('one open sockets remain after request', function(done) {
+ send_request({}, function(err, resp) {
+ // get_active_sockets().length.should.eql(1);
+ // done();
+ });
+ })
+
+ })
+
+ describe('passing connection: close', function() {
+
+ it('sends a Connection: close header', function(done) {
+ send_request({ connection: 'close' }, function(err, resp) {
+ resp.body.headers['connection'].should.eql('close');
+ done();
+ })
+ })
+
+ it('no open sockets remain after request', function(done) {
+ send_request({ connection: 'close' }, function(err, resp) {
+ get_active_sockets().length.should.eql(existing_sockets);
+ done();
+ });
+ })
+
+ })
+
+ describe('passing connection: keep-alive', function() {
+
+ it('sends a Connection: keep-alive header (using options.headers.connection)', function(done) {
+ send_request({ headers: { connection: 'keep-alive' }}, function(err, resp) {
+ resp.body.headers['connection'].should.eql('keep-alive');
+ done();
+ })
+ })
+
+ it('sends a Connection: keep-alive header (using options.connection)', function(done) {
+ send_request({ connection: 'keep-alive' }, function(err, resp) {
+ resp.body.headers['connection'].should.eql('keep-alive');
+ done();
+ })
+ })
+
+ it('one open socket remain after request', function(done) {
+ send_request({ connection: 'keep-alive' }, function(err, resp) {
+ get_active_sockets().length.should.eql(existing_sockets + 1);
+ done();
+ });
+ })
+
+ })
+
+ })
+
+})
diff --git a/node_modules/needle/test/helpers.js b/node_modules/needle/test/helpers.js
new file mode 100644
index 0000000..a85f548
--- /dev/null
+++ b/node_modules/needle/test/helpers.js
@@ -0,0 +1,72 @@
+var fs = require('fs');
+
+var protocols = {
+ http : require('http'),
+ https : require('https')
+}
+
+var keys = {
+ cert : fs.readFileSync(__dirname + '/keys/ssl.cert'),
+ key : fs.readFileSync(__dirname + '/keys/ssl.key')
+}
+
+var helpers = {};
+
+helpers.server = function(opts, cb) {
+
+ var defaults = {
+ code : 200,
+ headers : {'Content-Type': 'application/json'}
+ }
+
+ var mirror_response = function(req) {
+ return JSON.stringify({
+ headers: req.headers,
+ body: req.body
+ })
+ }
+
+ var get = function(what) {
+ if (!opts[what])
+ return defaults[what];
+
+ if (typeof opts[what] == 'function')
+ return opts[what](); // set them at runtime
+ else
+ return opts[what];
+ }
+
+ var finish = function(req, res) {
+ res.writeHead(get('code'), get('headers'));
+ res.end(opts.response || mirror_response(req));
+ }
+
+ var handler = function(req, res) {
+
+ req.setEncoding('utf8'); // get as string
+ req.body = '';
+ req.on('data', function(str) { req.body += str })
+ req.socket.on('error', function(e) {
+ // res.writeHead(500, {'Content-Type': 'text/plain'});
+ // res.end('Error: ' + e.message);
+ })
+
+ setTimeout(function(){
+ finish(req, res);
+ }, opts.wait || 0);
+
+ };
+
+ var protocol = opts.protocol || 'http';
+ var server;
+
+ if (protocol == 'https')
+ server = protocols[protocol].createServer(keys, handler);
+ else
+ server = protocols[protocol].createServer(handler);
+
+ server.listen(opts.port, cb);
+ return server;
+}
+
+module.exports = helpers; \ No newline at end of file
diff --git a/node_modules/needle/test/long_string_spec.js b/node_modules/needle/test/long_string_spec.js
new file mode 100644
index 0000000..4baa4fa
--- /dev/null
+++ b/node_modules/needle/test/long_string_spec.js
@@ -0,0 +1,34 @@
+var needle = require('../'),
+ should = require('should');
+
+describe('when posting a very long string', function() {
+
+ this.timeout(20000);
+
+ function get_string(length) {
+ var str = '';
+ for (var i = 0; i < length; i++) {
+ str += 'x';
+ }
+ return str;
+ }
+
+ it("shouldn't throw an EPIPE error out of nowhere", function(done) {
+ var error;
+
+ function finished() {
+ setTimeout(function() {
+ should.not.exist(error);
+ done();
+ }, 300);
+ }
+
+ try {
+ needle.post('https://google.com', { data: get_string(Math.pow(2, 20)) }, finished)
+ } catch(e) {
+ error = e;
+ }
+
+ })
+
+})
diff --git a/node_modules/needle/test/output_spec.js b/node_modules/needle/test/output_spec.js
new file mode 100644
index 0000000..14fc5bb
--- /dev/null
+++ b/node_modules/needle/test/output_spec.js
@@ -0,0 +1,254 @@
+var should = require('should'),
+ needle = require('./../'),
+ http = require('http'),
+ sinon = require('sinon'),
+ stream = require('stream'),
+ fs = require('fs'),
+ port = 11111,
+ server;
+
+describe('with output option', function() {
+
+ var server, handler, file = '/tmp/foobar.out';
+
+ function send_request_cb(where, cb) {
+ var url = 'http://localhost:' + port + '/whatever.file';
+ return needle.get(url, { output: where }, cb);
+ }
+
+ function send_request_stream(where, cb) {
+ var url = 'http://localhost:' + port + '/whatever.file';
+ var stream = needle.get(url, { output: where });
+ stream.on('end', cb);
+ }
+
+ // this will only work in UNICES
+ function get_open_file_descriptors() {
+ var list = fs.readdirSync('/proc/self/fd');
+ return list.length;
+ }
+
+ var send_request = send_request_cb;
+
+ before(function(){
+ server = http.createServer(function(req, res) {
+ handler(req, res);
+ }).listen(port);
+ });
+
+ after(function() {
+ server.close();
+ })
+
+ beforeEach(function() {
+ try { fs.unlinkSync(file) } catch(e) { };
+ })
+
+ describe('and a 404 response', function() {
+
+ before(function() {
+ handler = function(req, res) {
+ res.writeHead(404, {'Content-Type': 'text/plain' });
+ res.end();
+ }
+ })
+
+ it('doesnt attempt to write a file', function(done) {
+ var spy = sinon.spy(fs, 'createWriteStream');
+ send_request(file, function(err, resp) {
+ resp.statusCode.should.eql(404);
+ spy.called.should.eql(false);
+ spy.restore();
+ done();
+ })
+ })
+
+ it('doesnt actually write a file', function(done) {
+ send_request(file, function(err, resp) {
+ resp.statusCode.should.eql(404);
+ fs.existsSync(file).should.eql(false);
+ done();
+ })
+ })
+
+ })
+
+ describe('and a 200 response', function() {
+
+ describe('for an empty response', function() {
+
+ before(function() {
+ handler = function(req, res) {
+ res.writeHead(200, { 'Content-Type': 'text/plain' });
+ res.end();
+ }
+ })
+
+ it('uses a writableStream', function(done) {
+ var spy = sinon.spy(fs, 'createWriteStream');
+ send_request(file, function(err, resp) {
+ resp.statusCode.should.eql(200);
+ spy.called.should.eql(true);
+ spy.restore();
+ done();
+ })
+ })
+
+ it('writes a file', function(done) {
+ fs.existsSync(file).should.eql(false);
+ send_request(file, function(err, resp) {
+ fs.existsSync(file).should.eql(true);
+ done();
+ })
+ })
+
+ it('file is zero bytes in length', function(done) {
+ send_request(file, function(err, resp) {
+ fs.statSync(file).size.should.equal(0);
+ done();
+ })
+ })
+
+ if (process.platform != 'win32') {
+ it('closes the file descriptor', function(done) {
+ var open_descriptors = get_open_file_descriptors();
+ send_request(file + Math.random(), function(err, resp) {
+ var current_descriptors = get_open_file_descriptors();
+ open_descriptors.should.eql(current_descriptors);
+ done()
+ })
+ })
+ }
+
+ })
+
+ describe('for a JSON response', function() {
+
+ before(function() {
+ handler = function(req, res) {
+ res.writeHead(200, { 'Content-Type': 'application/javascript' });
+ res.end(JSON.stringify({foo: 'bar'}));
+ }
+ })
+
+ it('uses a writableStream', function(done) {
+ var spy = sinon.spy(fs, 'createWriteStream');
+ send_request(file, function(err, resp) {
+ resp.statusCode.should.eql(200);
+ spy.called.should.eql(true);
+ spy.restore();
+ done();
+ })
+ })
+
+ it('writes a file', function(done) {
+ fs.existsSync(file).should.eql(false);
+ send_request(file, function(err, resp) {
+ fs.existsSync(file).should.eql(true);
+ done();
+ })
+ })
+
+ it('file size equals response length', function(done) {
+ send_request(file, function(err, resp) {
+ fs.statSync(file).size.should.equal(resp.bytes);
+ done();
+ })
+ })
+
+ it('response pipeline is honoured (JSON is decoded by default)', function(done) {
+ send_request_stream(file, function(err, resp) {
+ // we need to wait a bit since writing to config.output
+ // happens independently of needle's callback logic.
+ setTimeout(function() {
+ fs.readFileSync(file).toString().should.eql('{\"foo\":\"bar\"}');
+ done();
+ }, 20);
+ })
+ })
+
+ it('closes the file descriptor', function(done) {
+ var open_descriptors = get_open_file_descriptors();
+ send_request(file + Math.random(), function(err, resp) {
+ var current_descriptors = get_open_file_descriptors();
+ open_descriptors.should.eql(current_descriptors);
+ done()
+ })
+ })
+
+ })
+
+ describe('for a binary file', function() {
+
+ var pixel = Buffer.from("base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs", "base64");
+
+ before(function() {
+ handler = function(req, res) {
+ res.writeHead(200, { 'Content-Type': 'application/octet-stream', 'Transfer-Encoding': 'chunked' });
+ res.write(pixel.slice(0, 10));
+ res.write(pixel.slice(10, 20));
+ res.write(pixel.slice(20, 30));
+ res.write(pixel.slice(30));
+ res.end();
+ }
+ })
+
+ it('uses a writableStream', function(done) {
+ var spy = sinon.spy(fs, 'createWriteStream');
+ send_request(file, function(err, resp) {
+ resp.statusCode.should.eql(200);
+ spy.called.should.eql(true);
+ spy.restore();
+ done();
+ })
+ })
+
+ it('writes a file', function(done) {
+ fs.existsSync(file).should.eql(false);
+ send_request(file, function(err, resp) {
+ fs.existsSync(file).should.eql(true);
+ done();
+ })
+ })
+
+ it('file size equals response length', function(done) {
+ send_request(file, function(err, resp) {
+ fs.statSync(file).size.should.equal(resp.bytes);
+ done();
+ })
+ })
+
+ it('file is equal to original buffer', function(done) {
+ send_request(file, function(err, resp) {
+ // we need to wait a bit since writing to config.output
+ // happens independently of needle's callback logic.
+ setTimeout(function() {
+ fs.readFileSync(file).should.eql(pixel);
+ done();
+ }, 20);
+ })
+ })
+
+ it('returns the data in resp.body too', function(done) {
+ send_request(file, function(err, resp) {
+ resp.body.should.eql(pixel);
+ done();
+ })
+ })
+
+ if (process.platform != 'win32') {
+ it('closes the file descriptor', function(done) {
+ var open_descriptors = get_open_file_descriptors();
+ send_request(file + Math.random(), function(err, resp) {
+ var current_descriptors = get_open_file_descriptors();
+ open_descriptors.should.eql(current_descriptors);
+ done()
+ })
+ })
+ }
+
+ })
+
+ })
+
+})
diff --git a/node_modules/needle/test/parsing_spec.js b/node_modules/needle/test/parsing_spec.js
new file mode 100644
index 0000000..406a292
--- /dev/null
+++ b/node_modules/needle/test/parsing_spec.js
@@ -0,0 +1,494 @@
+var should = require('should'),
+ needle = require('./../'),
+ http = require('http'),
+ port = 11111,
+ server;
+
+describe('parsing', function(){
+
+ describe('when response is an JSON string', function(){
+
+ var json_string = '{"foo":"bar"}';
+
+ before(function(done){
+ server = http.createServer(function(req, res) {
+ res.setHeader('Content-Type', 'application/json');
+ res.end(json_string);
+ }).listen(port, done);
+ });
+
+ after(function(done){
+ server.close(done);
+ })
+
+ describe('and parse option is not passed', function() {
+
+ describe('with default parse_response', function() {
+
+ before(function() {
+ needle.defaults().parse_response.should.eql('all')
+ })
+
+ it('should return object', function(done){
+ needle.get('localhost:' + port, function(err, response, body){
+ should.ifError(err);
+ body.should.have.property('foo', 'bar');
+ done();
+ })
+ })
+
+ })
+
+ describe('and default parse_response is set to false', function() {
+
+ it('does NOT return object when disabled using .defaults', function(done){
+ needle.defaults({ parse_response: false })
+
+ needle.get('localhost:' + port, function(err, response, body) {
+ should.not.exist(err);
+ body.should.be.an.instanceof(Buffer)
+ body.toString().should.eql('{"foo":"bar"}');
+
+ needle.defaults({ parse_response: 'all' });
+ done();
+ })
+ })
+
+
+ })
+
+ })
+
+ describe('and parse option is true', function() {
+
+ describe('and JSON is valid', function() {
+
+ it('should return object', function(done) {
+ needle.get('localhost:' + port, { parse: true }, function(err, response, body){
+ should.not.exist(err);
+ body.should.have.property('foo', 'bar')
+ done();
+ })
+ })
+
+ it('should have a .parser = json property', function(done) {
+ needle.get('localhost:' + port, { parse: true }, function(err, resp) {
+ should.not.exist(err);
+ resp.parser.should.eql('json');
+ done();
+ })
+ })
+
+ });
+
+ describe('and response is empty', function() {
+
+ var old_json_string;
+
+ before(function() {
+ old_json_string = json_string;
+ json_string = "";
+ });
+
+ after(function() {
+ json_string = old_json_string;
+ });
+
+ it('should return an empty string', function(done) {
+ needle.get('localhost:' + port, { parse: true }, function(err, resp) {
+ should.not.exist(err);
+ resp.body.should.equal('');
+ done();
+ })
+ })
+
+ })
+
+ describe('and JSON is invalid', function() {
+
+ var old_json_string;
+
+ before(function() {
+ old_json_string = json_string;
+ json_string = "this is not going to work";
+ });
+
+ after(function() {
+ json_string = old_json_string;
+ });
+
+ it('does not throw', function(done) {
+ (function(){
+ needle.get('localhost:' + port, { parse: true }, done);
+ }).should.not.throw();
+ });
+
+ it('does NOT return object', function(done) {
+ needle.get('localhost:' + port, { parse: true }, function(err, response, body) {
+ should.not.exist(err);
+ body.should.be.a.String;
+ body.toString().should.eql('this is not going to work');
+ done();
+ })
+ })
+
+ });
+
+ })
+
+ describe('and parse option is false', function() {
+
+ it('does NOT return object', function(done){
+ needle.get('localhost:' + port, { parse: false }, function(err, response, body) {
+ should.not.exist(err);
+ body.should.be.an.instanceof(Buffer)
+ body.toString().should.eql('{"foo":"bar"}');
+ done();
+ })
+ })
+
+ it('should NOT have a .parser = json property', function(done) {
+ needle.get('localhost:' + port, { parse: false }, function(err, resp) {
+ should.not.exist(err);
+ should.not.exist(resp.parser);
+ done();
+ })
+ })
+
+ })
+
+ describe('and parse option is "xml"', function() {
+
+ it('does NOT return object', function(done){
+ needle.get('localhost:' + port, { parse: 'xml' }, function(err, response, body) {
+ should.not.exist(err);
+ body.should.be.an.instanceof(Buffer)
+ body.toString().should.eql('{"foo":"bar"}');
+ done();
+ })
+ })
+
+ it('should NOT have a .parser = json property', function(done) {
+ needle.get('localhost:' + port, { parse: 'xml' }, function(err, resp) {
+ should.not.exist(err);
+ should.not.exist(resp.parser);
+ done();
+ })
+ })
+
+ })
+
+ });
+
+ describe('when response is JSON \'false\'', function(){
+
+ var json_string = 'false';
+
+ before(function(done){
+ server = http.createServer(function(req, res) {
+ res.setHeader('Content-Type', 'application/json');
+ res.end(json_string);
+ }).listen(port, done);
+ });
+
+ after(function(done){
+ server.close(done);
+ })
+
+ describe('and parse option is not passed', function() {
+
+ it('should return object', function(done){
+ needle.get('localhost:' + port, function(err, response, body){
+ should.ifError(err);
+ body.should.equal(false);
+ done();
+ })
+ })
+
+ })
+
+ describe('and parse option is true', function() {
+
+ describe('and JSON is valid', function() {
+
+ it('should return object', function(done){
+ needle.get('localhost:' + port, { parse: true }, function(err, response, body){
+ should.not.exist(err);
+ body.should.equal(false)
+ done();
+ })
+ })
+
+ });
+
+ describe('and response is empty', function() {
+
+ var old_json_string;
+
+ before(function() {
+ old_json_string = json_string;
+ json_string = "";
+ });
+
+ after(function() {
+ json_string = old_json_string;
+ });
+
+ it('should return an empty string', function(done) {
+ needle.get('localhost:' + port, { parse: true }, function(err, resp) {
+ should.not.exist(err);
+ resp.body.should.equal('');
+ done();
+ })
+ })
+
+ })
+
+ describe('and JSON is invalid', function() {
+
+ var old_json_string;
+
+ before(function() {
+ old_json_string = json_string;
+ json_string = "this is not going to work";
+ });
+
+ after(function() {
+ json_string = old_json_string;
+ });
+
+ it('does not throw', function(done) {
+ (function(){
+ needle.get('localhost:' + port, { parse: true }, done);
+ }).should.not.throw();
+ });
+
+ it('does NOT return object', function(done) {
+ needle.get('localhost:' + port, { parse: true }, function(err, response, body) {
+ should.not.exist(err);
+ body.should.be.a.String;
+ body.toString().should.eql('this is not going to work');
+ done();
+ })
+ })
+
+ });
+
+ })
+
+ describe('and parse option is false', function() {
+
+ it('does NOT return object', function(done){
+ needle.get('localhost:' + port, { parse: false }, function(err, response, body) {
+ should.not.exist(err);
+ body.should.be.an.instanceof(Buffer)
+ body.toString().should.eql('false');
+ done();
+ })
+ })
+
+ })
+
+ describe('and parse option is "xml"', function() {
+
+ it('does NOT return object', function(done){
+ needle.get('localhost:' + port, { parse: 'xml' }, function(err, response, body) {
+ should.not.exist(err);
+ body.should.be.an.instanceof(Buffer)
+ body.toString().should.eql('false');
+ done();
+ })
+ })
+
+ })
+
+
+ });
+
+ describe('when response is an invalid XML string', function(){
+
+ before(function(done){
+ server = http.createServer(function(req, res) {
+ res.writeHeader(200, {'Content-Type': 'application/xml'})
+ res.end("<post><body there11post>")
+ }).listen(port, done);
+ });
+
+ after(function(done){
+ server.close(done);
+ })
+
+ describe('and parse_response is true', function(){
+
+ it('should return original string', function(done) {
+ needle.get('localhost:' + port, { parse_response: true }, function(err, response, body) {
+ should.not.exist(err);
+ body.should.eql('<post><body there11post>')
+ should.not.exist(body.name);
+ done();
+ })
+ })
+
+ it('should not have a .parser = xml property', function(done) {
+ needle.get('localhost:' + port, { parse_response: true }, function(err, resp) {
+ should.not.exist(err);
+ should.not.exist(resp.parser);
+ done();
+ })
+ })
+
+ })
+
+ describe('and parse response is false', function(){
+
+ it('should return valid object', function(done) {
+ needle.get('localhost:' + port, { parse_response: false }, function(err, response, body){
+ should.not.exist(err);
+ body.toString().should.eql('<post><body there11post>')
+ done();
+ })
+ })
+
+ it('should not have a .parser property', function(done) {
+ needle.get('localhost:' + port, { parse_response: false }, function(err, resp) {
+ should.not.exist(err);
+ should.not.exist(resp.parser)
+ done();
+ })
+ })
+
+ })
+
+ })
+
+ describe('when response is a valid XML string', function(){
+
+ before(function(done) {
+ server = http.createServer(function(req, res) {
+ res.writeHeader(200, {'Content-Type': 'application/xml'})
+ res.end("<post><p>hello</p><p>world</p></post>")
+ }).listen(port, done);
+ });
+
+ after(function(done) {
+ server.close(done);
+ })
+
+ describe('and parse_response is true', function(){
+
+ it('should return valid object', function(done) {
+ needle.get('localhost:' + port, { parse_response: true }, function(err, response, body) {
+ should.not.exist(err);
+ body.name.should.eql('post')
+ body.children[0].name.should.eql('p')
+ body.children[0].value.should.eql('hello')
+
+ body.children[1].name.should.eql('p')
+ body.children[1].value.should.eql('world')
+ done();
+ })
+ })
+
+ it('should have a .parser = xml property', function(done) {
+ needle.get('localhost:' + port, { parse_response: true }, function(err, resp) {
+ should.not.exist(err);
+ resp.parser.should.eql('xml');
+ done();
+ })
+ })
+
+ })
+
+ describe('and parse response is false', function(){
+
+ it('should return valid object', function(done) {
+ needle.get('localhost:' + port, { parse_response: false }, function(err, response, body){
+ should.not.exist(err);
+ body.toString().should.eql('<post><p>hello</p><p>world</p></post>')
+ done();
+ })
+ })
+
+ it('should not have a .parser property', function(done) {
+ needle.get('localhost:' + port, { parse_response: false }, function(err, resp) {
+ should.not.exist(err);
+ should.not.exist(resp.parser)
+ done();
+ })
+ })
+
+ })
+
+ })
+
+
+ describe('valid XML, using xml2js', function() {
+
+ var parsers, origParser;
+
+ before(function(done) {
+ var xml2js = require('xml2js')
+ parsers = require('../lib/parsers');
+ origParser = parsers['application/xml'];
+
+ var customParser = require('xml2js').parseString;
+ parsers.use('xml2js', ['application/xml'], function(buff, cb) {
+ var opts = { explicitRoot: true, explicitArray: false };
+ customParser(buff, opts, cb);
+ })
+
+ server = http.createServer(function(req, res) {
+ res.writeHeader(200, {'Content-Type': 'application/xml'})
+ res.end("<post><p>hello</p><p>world</p></post>")
+ }).listen(port, done);
+ });
+
+ after(function(done) {
+ parsers['application/xml'] = origParser;
+ server.close(done);
+ })
+
+ describe('and parse_response is true', function(){
+
+ it('should return valid object', function(done) {
+ needle.get('localhost:' + port, { parse_response: true }, function(err, response, body) {
+ should.not.exist(err);
+ body.should.eql({ post: { p: ['hello', 'world' ]}})
+ done();
+ })
+ })
+
+ it('should have a .parser = xml property', function(done) {
+ needle.get('localhost:' + port, { parse_response: true }, function(err, resp) {
+ should.not.exist(err);
+ resp.parser.should.eql('xml2js');
+ done();
+ })
+ })
+
+ })
+
+ describe('and parse response is false', function(){
+
+ it('should return valid object', function(done) {
+ needle.get('localhost:' + port, { parse_response: false }, function(err, response, body){
+ should.not.exist(err);
+ body.toString().should.eql('<post><p>hello</p><p>world</p></post>')
+ done();
+ })
+ })
+
+ it('should not have a .parser property', function(done) {
+ needle.get('localhost:' + port, { parse_response: false }, function(err, resp) {
+ should.not.exist(err);
+ should.not.exist(resp.parser)
+ done();
+ })
+ })
+
+ })
+
+ })
+
+
+})
diff --git a/node_modules/needle/test/post_data_spec.js b/node_modules/needle/test/post_data_spec.js
new file mode 100644
index 0000000..d9a28c6
--- /dev/null
+++ b/node_modules/needle/test/post_data_spec.js
@@ -0,0 +1,1021 @@
+var needle = require('..'),
+ http = require('http'),
+ should = require('should'),
+ sinon = require('sinon'),
+ stream = require('stream'),
+ helpers = require('./helpers');
+
+var multiparts = ['----------------------NODENEEDLEHTTPCLIENT'];
+multiparts.push(['Content-Disposition: form-data; name=\"foo\"'])
+multiparts.push(['\r\nbar\r\n----------------------NODENEEDLEHTTPCLIENT--'])
+// multiparts.push(['Content-Disposition: form-data; name=\"test\"'])
+// multiparts.push(['\r\næµè¯\r\n----------------------NODENEEDLEHTTPCLIENT--'])
+// multiparts.push(['\r\n' + Buffer.from('测试').toString() + '\r\n----------------------NODENEEDLEHTTPCLIENT--'])
+
+
+describe('post data (e.g. request body)', function() {
+
+ var stub, spy, server;
+
+ before(function(done) {
+ server = helpers.server({ port: 4321 }, done);
+ })
+
+ after(function(done) {
+ server.close(done);
+ })
+
+ afterEach(function() {
+ if (stub) stub.restore();
+ if (spy) spy.restore();
+ })
+
+ function get(data, opts, cb) {
+ return needle.request('get', 'http://localhost:' + 4321, data, opts, cb)
+ }
+
+ function post(data, opts, cb) {
+ return needle.request('post', 'http://localhost:' + 4321, data, opts, cb)
+ }
+
+ function spystub_request() {
+ var http_req = http.request;
+ stub = sinon.stub(http, 'request', function(opts, cb) {
+ var req = http_req(opts, cb);
+ spy = sinon.spy(req, 'write');
+ return req;
+ })
+ }
+
+ function check_request(method) {
+ stub.calledOnce.should.be.true;
+ stub.args[0][0]['headers']['host'].should.equal('localhost:4321');
+ stub.args[0][0]['method'].should.equal(method);
+ }
+
+ describe('with multipart: true', function() {
+
+ describe('when null', function() {
+
+ it('sends request (non multipart)', function(done) {
+ spystub_request();
+
+ post(null, { multipart: true }, function(err, resp) {
+ check_request('post');
+ done();
+ })
+ })
+
+ it('doesnt set Content-Type header', function(done) {
+ post(null, { multipart: true }, function(err, resp) {
+ should.not.exist(resp.body.headers['content-type']);
+ done();
+ })
+ })
+
+ it('doesnt change default Accept header', function(done) {
+ post(null, { multipart: true }, function(err, resp) {
+ // resp.body contains 'header' and 'body', mirroring what we sent
+ resp.body.headers['accept'].should.equal('*/*');
+ done();
+ })
+ })
+
+ it('doesnt write anything', function(done) {
+ spystub_request();
+
+ post(null, { multipart: true }, function(err, resp) {
+ spy.called.should.be.false;
+ resp.body.body.should.eql('');
+ done();
+ })
+ })
+
+ })
+
+ describe('when string', function() {
+
+ it('explodes', function() {
+ (function() {
+ post('foobar', { multipart: true })
+ }).should.throw()
+ })
+
+ })
+
+ describe('when object', function() {
+
+ describe('get request', function() {
+
+ it('sends request', function(done) {
+ spystub_request();
+
+ get({ foo: 'bar', test: '测试' }, { multipart: true }, function(err, resp) {
+ check_request('get');
+ done();
+ })
+ })
+
+ it('sets Content-Type header', function(done) {
+ post({ foo: 'bar', test: '测试' }, { multipart: true }, function(err, resp) {
+ resp.body.headers['content-type'].should.equal('multipart/form-data; boundary=--------------------NODENEEDLEHTTPCLIENT');
+ done();
+ })
+ })
+
+ it('doesnt change default Accept header', function(done) {
+ post({ foo: 'bar', test: '测试' }, { multipart: true }, function(err, resp) {
+ resp.body.headers['accept'].should.equal('*/*');
+ done();
+ })
+ })
+
+ it('writes string as buffer', function(done) {
+ spystub_request();
+
+ get({ foo: 'bar' }, { multipart: true }, function(err, resp) {
+ spy.called.should.be.true;
+
+ spy.args[0][0].should.be.an.instanceof(String);
+ spy.args[0][0].toString().should.equal(multiparts.join('\r\n'));
+ resp.body.body.should.eql(multiparts.join('\r\n'));
+ done();
+ })
+ })
+
+ it('writes japanese chars correctly as binary', function(done) {
+ spystub_request();
+
+ get({ foo: 'bar', test: '测试' }, { multipart: true }, function(err, resp) {
+ spy.called.should.be.true;
+
+ spy.args[0][0].should.be.an.instanceof(String);
+ Buffer.from(spy.args[0][0]).toString('hex').should.eql('2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d4e4f44454e4545444c4548545450434c49454e540d0a436f6e74656e742d446973706f736974696f6e3a20666f726d2d646174613b206e616d653d22666f6f220d0a0d0a6261720d0a2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d4e4f44454e4545444c4548545450434c49454e540d0a436f6e74656e742d446973706f736974696f6e3a20666f726d2d646174613b206e616d653d2274657374220d0a0d0ac3a6c2b5c28bc3a8c2afc2950d0a2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d4e4f44454e4545444c4548545450434c49454e542d2d')
+ done();
+ })
+ })
+
+
+ })
+
+ describe('post request', function() {
+
+ it('sends request', function(done) {
+ spystub_request();
+
+ post({ foo: 'bar', test: '测试' }, { multipart: true }, function(err, resp) {
+ check_request('post');
+ done();
+ })
+ })
+
+ it('writes string as buffer', function(done) {
+ spystub_request();
+
+ post({ foo: 'bar' }, { multipart: true }, function(err, resp) {
+ spy.called.should.be.true;
+ spy.args[0][0].should.be.an.instanceof(String);
+ spy.args[0][0].toString().should.equal(multiparts.join('\r\n'));
+ resp.body.body.should.eql(multiparts.join('\r\n'));
+ done();
+ })
+ })
+
+ it('writes japanese chars correctly as binary', function(done) {
+ spystub_request();
+
+ post({ foo: 'bar', test: '测试' }, { multipart: true }, function(err, resp) {
+ spy.called.should.be.true;
+ spy.args[0][0].should.be.an.instanceof(String);
+ Buffer.from(spy.args[0][0]).toString('hex').should.eql('2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d4e4f44454e4545444c4548545450434c49454e540d0a436f6e74656e742d446973706f736974696f6e3a20666f726d2d646174613b206e616d653d22666f6f220d0a0d0a6261720d0a2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d4e4f44454e4545444c4548545450434c49454e540d0a436f6e74656e742d446973706f736974696f6e3a20666f726d2d646174613b206e616d653d2274657374220d0a0d0ac3a6c2b5c28bc3a8c2afc2950d0a2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d4e4f44454e4545444c4548545450434c49454e542d2d')
+ done();
+ })
+ })
+
+ })
+
+ })
+
+ describe('when stream', function() {
+
+ var stream_for_multipart;
+
+ before(function() {
+ stream_for_multipart = new stream.Readable();
+ stream_for_multipart._read = function() {
+ this.push('foobar');
+ this.push(null);
+ }
+ })
+
+ it('explodes', function() {
+ (function() {
+ post(stream_for_multipart, { multipart: true })
+ }).should.throw()
+ })
+
+ })
+
+ })
+
+ describe('non multipart', function() {
+
+ describe('when null', function() {
+
+ describe('get request', function() {
+
+ it('sends request', function(done) {
+ spystub_request();
+
+ get(null, {}, function(err, resp) {
+ check_request('get');
+ done();
+ })
+ })
+
+ it('doesnt write anything', function(done) {
+ spystub_request();
+
+ get(null, {}, function(err, resp) {
+ spy.called.should.be.false;
+ resp.body.body.should.eql('');
+ done();
+ })
+ })
+
+ })
+
+ describe('post request', function() {
+
+ it('sends request', function(done) {
+ spystub_request();
+
+ post(null, {}, function(err, resp) {
+ check_request('post');
+ done();
+ })
+ })
+
+ it('doesnt write anything', function(done) {
+ spystub_request();
+
+ post(null, {}, function(err, resp) {
+ spy.called.should.be.false;
+ resp.body.body.should.eql('');
+ done();
+ })
+ })
+
+ })
+
+ })
+
+ describe('when string with no equal sign', function() {
+
+ describe('get request', function() {
+
+ it('explodes', function() {
+ (function() {
+ get('foobar', {})
+ }).should.throw()
+ })
+
+ })
+
+ describe('post request', function() {
+
+ it('sends request', function(done) {
+ spystub_request();
+
+ post('foobar', {}, function(err, resp) {
+ check_request('post');
+ done();
+ })
+ })
+
+ it('writes string as buffer', function(done) {
+ spystub_request();
+
+ post('foobar', {}, function(err, resp) {
+ spy.called.should.be.true;
+ spy.args[0][0].should.be.an.instanceof(Buffer);
+ spy.args[0][0].toString().should.equal('foobar');
+ resp.body.body.should.eql('foobar');
+ done();
+ })
+ })
+
+ })
+
+ })
+
+ describe('when string WITH equal sign', function() {
+
+ describe('get request', function() {
+
+ describe('with json: false (default)', function() {
+
+ it('sends request, adding data as querystring', function(done) {
+ spystub_request();
+
+ get('foo=bar', { json: false }, function(err, resp) {
+ check_request('get');
+ stub.args[0][0]['path'].should.equal('/?foo=bar')
+ done();
+ })
+ })
+
+ it('doesnt set Content-Type header', function(done) {
+ get('foo=bar', { json: false }, function(err, resp) {
+ // resp.body contains 'header' and 'body', mirroring what we sent
+ should.not.exist(resp.body.headers['content-type']);
+ done();
+ })
+ })
+
+ it('doesnt change default Accept header', function(done) {
+ get('foo=bar', { json: false }, function(err, resp) {
+ // resp.body contains 'header' and 'body', mirroring what we sent
+ resp.body.headers['accept'].should.equal('*/*');
+ done();
+ })
+ })
+
+ it('doesnt write anything', function(done) {
+ get('foo=bar', { json: false }, function(err, resp) {
+ spy.called.should.be.false;
+ resp.body.body.should.eql('');
+ done();
+ })
+ })
+
+ })
+
+ describe('with json: true', function() {
+
+ it('sends request, without setting a querystring', function(done) {
+ spystub_request();
+
+ get('foo=bar', { json: true }, function(err, resp) {
+ check_request('get');
+ stub.args[0][0]['path'].should.equal('/')
+ done();
+ })
+ })
+
+ it('sets Content-Type header', function(done) {
+ get('foo=bar', { json: true }, function(err, resp) {
+ resp.body.headers['content-type'].should.equal('application/json; charset=utf-8');
+ done();
+ })
+ })
+
+ it('set Accept header to application/json', function(done) {
+ get('foo=bar', { json: true }, function(err, resp) {
+ resp.body.headers['accept'].should.equal('application/json');
+ done();
+ })
+ })
+
+ it('writes raw string (assuming it already is JSON, so no JSON.stringify)', function(done) {
+ get('foo=bar', { json: true }, function(err, resp) {
+ spy.called.should.be.true;
+ spy.args[0][0].toString().should.eql('foo=bar')
+ resp.body.body.should.eql('foo=bar');
+ done();
+ })
+ })
+
+ })
+
+ })
+
+ describe('post request', function() {
+
+ describe('with json: false (default)', function() {
+
+ it('sends request', function(done) {
+ spystub_request();
+
+ post('foo=bar', { json: false }, function(err, resp) {
+ check_request('post');
+ done();
+ })
+ })
+
+ it('sets Content-Type header to www-form-urlencoded', function(done) {
+ post('foo=bar', { json: false }, function(err, resp) {
+ resp.body.headers['content-type'].should.equal('application/x-www-form-urlencoded');
+ done();
+ })
+ })
+
+ it('doesnt change default Accept header', function(done) {
+ post('foo=bar', { json: false }, function(err, resp) {
+ // resp.body contains 'header' and 'body', mirroring what we sent
+ resp.body.headers['accept'].should.equal('*/*');
+ done();
+ })
+ })
+
+ it('writes as buffer', function(done) {
+ post('foo=bar', { json: false }, function(err, resp) {
+ spy.called.should.be.true;
+ spy.args[0][0].should.be.an.instanceof(Buffer);
+ spy.args[0][0].toString().should.equal('foo=bar');
+ resp.body.body.should.eql('foo=bar');
+ done();
+ })
+ })
+
+ })
+
+ describe('with json: true', function() {
+
+ it('sends request', function(done) {
+ spystub_request();
+
+ post('foo=bar', { json: true }, function(err, resp) {
+ check_request('post');
+ done();
+ })
+ })
+
+ it('sets Content-Type header', function(done) {
+ post('foo=bar', { json: true }, function(err, resp) {
+ resp.body.headers['content-type'].should.equal('application/json; charset=utf-8');
+ done();
+ })
+ })
+
+ it('set Accept header to application/json', function(done) {
+ post('foo=bar', { json: true }, function(err, resp) {
+ resp.body.headers['accept'].should.equal('application/json');
+ done();
+ })
+ })
+
+ it('writes raw string (assuming it already is JSON, so no JSON.stringify)', function(done) {
+ post('foo=bar', { json: true }, function(err, resp) {
+ spy.called.should.be.true;
+ var json = JSON.stringify('foo=bar');
+ spy.args[0][0].toString().should.eql('foo=bar')
+ resp.body.body.should.eql('foo=bar');
+ done();
+ })
+ })
+
+ })
+
+ })
+
+ })
+
+ describe('when object', function() {
+
+ describe('get request', function() {
+
+ describe('with json: false (default)', function() {
+
+ it('sends request, adding data as querystring', function(done) {
+ spystub_request();
+
+ get({ foo: 'bar', test: '测试' }, { json: false }, function(err, resp) {
+ check_request('get');
+ stub.args[0][0]['path'].should.equal('/?foo=bar&test=%E6%B5%8B%E8%AF%95')
+ done();
+ })
+ })
+
+ it('doesnt set Content-Type header', function(done) {
+ get({ foo: 'bar', test: '测试' }, { json: false }, function(err, resp) {
+ // resp.body contains 'header' and 'body', mirroring what we sent
+ should.not.exist(resp.body.headers['content-type']);
+ done();
+ })
+ })
+
+ it('doesnt change default Accept header', function(done) {
+ get({ foo: 'bar', test: '测试' }, { json: false }, function(err, resp) {
+ // resp.body contains 'header' and 'body', mirroring what we sent
+ resp.body.headers['accept'].should.equal('*/*');
+ done();
+ })
+ })
+
+ it('doesnt write anything', function(done) {
+ get({ foo: 'bar', test: '测试' }, { json: false }, function(err, resp) {
+ spy.called.should.be.false;
+ resp.body.body.should.eql('');
+ done();
+ })
+ })
+
+ })
+
+ describe('with json: true', function() {
+
+ it('sends request, without setting a querystring', function(done) {
+ spystub_request();
+
+ get({ foo: 'bar', test: '测试' }, { json: true }, function(err, resp) {
+ check_request('get');
+ stub.args[0][0]['path'].should.equal('/')
+ done();
+ })
+ })
+
+ it('sets Content-Type header', function(done) {
+ get({ foo: 'bar', test: '测试' }, { json: true }, function(err, resp) {
+ resp.body.headers['content-type'].should.equal('application/json; charset=utf-8');
+ done();
+ })
+ })
+
+ it('set Accept header to application/json', function(done) {
+ get({ foo: 'bar', test: '测试' }, { json: true }, function(err, resp) {
+ resp.body.headers['accept'].should.equal('application/json');
+ done();
+ })
+ })
+
+ it('writes JSON.stringify version of object', function(done) {
+ get({ foo: 'bar', test: '测试' }, { json: true }, function(err, resp) {
+ spy.called.should.be.true;
+ var json = JSON.stringify({ foo: 'bar', test: '测试' })
+ spy.args[0][0].toString().should.eql(json)
+ resp.body.body.should.eql(json);
+ done();
+ })
+ })
+
+ })
+
+ })
+
+ describe('post request', function() {
+
+ describe('with json: false (default)', function() {
+
+ it('sends request', function(done) {
+ spystub_request();
+
+ post({ foo: 'bar', test: '测试' }, { json: false }, function(err, resp) {
+ check_request('post');
+ done();
+ })
+ })
+
+ it('sets Content-Type header to www-form-urlencoded', function(done) {
+ post({ foo: 'bar', test: '测试' }, { json: false }, function(err, resp) {
+ resp.body.headers['content-type'].should.equal('application/x-www-form-urlencoded');
+ done();
+ })
+ })
+
+ it('doesnt change default Accept header', function(done) {
+ post({ foo: 'bar', test: '测试' }, { json: false }, function(err, resp) {
+ // resp.body contains 'header' and 'body', mirroring what we sent
+ resp.body.headers['accept'].should.equal('*/*');
+ done();
+ })
+ })
+
+ it('writes as buffer', function(done) {
+ post({ foo: 'bar', test: '测试' }, { json: false }, function(err, resp) {
+ spy.called.should.be.true;
+ spy.args[0][0].should.be.an.instanceof(Buffer);
+ spy.args[0][0].toString().should.equal('foo=bar&test=%E6%B5%8B%E8%AF%95');
+ resp.body.body.should.eql('foo=bar&test=%E6%B5%8B%E8%AF%95');
+ done();
+ })
+ })
+
+ })
+
+ describe('with json: false and content_type = "application/json"', function() {
+
+ var opts = { json: false, content_type: 'application/json' };
+
+ it('sends request', function(done) {
+ spystub_request();
+
+ post({ foo: 'bar', test: '测试' }, opts, function(err, resp) {
+ check_request('post');
+ done();
+ })
+ })
+
+ it('sets Content-Type header to application/json', function(done) {
+ post({ foo: 'bar', test: '测试' }, opts, function(err, resp) {
+ resp.body.headers['content-type'].should.equal('application/json');
+ done();
+ })
+ })
+
+ it('doesnt change default Accept header', function(done) {
+ post({ foo: 'bar', test: '测试' }, opts, function(err, resp) {
+ // resp.body contains 'header' and 'body', mirroring what we sent
+ resp.body.headers['accept'].should.equal('*/*');
+ done();
+ })
+ })
+
+ it('writes as buffer', function(done) {
+ post({ foo: 'bar', test: '测试' }, opts, function(err, resp) {
+ spy.called.should.be.true;
+ spy.args[0][0].constructor.name.should.eql('Buffer');
+ spy.args[0][0].toString().should.equal('foo=bar&test=%E6%B5%8B%E8%AF%95');
+ resp.body.body.should.eql('foo=bar&test=%E6%B5%8B%E8%AF%95');
+ done();
+ })
+ })
+
+ })
+
+ describe('with json: undefined but content-type = application/json', function() {
+
+ var opts = { headers: { 'content-type': 'application/json' } };
+
+ it('sends request', function(done) {
+ spystub_request();
+
+ post({ foo: 'bar', test: '测试' }, opts, function(err, resp) {
+ check_request('post');
+ done();
+ })
+ })
+
+ it('doesnt change Content-Type header', function(done) {
+ post({ foo: 'bar', test: '测试' }, opts, function(err, resp) {
+ resp.body.headers['content-type'].should.equal('application/json');
+ done();
+ })
+ })
+
+ it('leaves default Accept header', function(done) {
+ post({ foo: 'bar', test: '测试' }, opts, function(err, resp) {
+ resp.body.headers['accept'].should.equal('*/*');
+ done();
+ })
+ })
+
+ it('writes JSON.stringified object', function(done) {
+ post({ foo: 'bar', test: '测试' }, opts, function(err, resp) {
+ spy.called.should.be.true;
+ var json = JSON.stringify({ foo: 'bar', test: '测试' })
+ spy.args[0][0].toString().should.eql(json)
+ resp.body.body.should.eql(json);
+ done();
+ })
+ })
+ })
+
+ describe('with json: true', function() {
+
+ it('sends request', function(done) {
+ spystub_request();
+
+ post({ foo: 'bar', test: '测试' }, { json: true }, function(err, resp) {
+ check_request('post');
+ done();
+ })
+ })
+
+ it('sets Content-Type header', function(done) {
+ post({ foo: 'bar', test: '测试' }, { json: true }, function(err, resp) {
+ resp.body.headers['content-type'].should.equal('application/json; charset=utf-8');
+ done();
+ })
+ })
+
+ it('set Accept header to application/json', function(done) {
+ post({ foo: 'bar', test: '测试' }, { json: true }, function(err, resp) {
+ resp.body.headers['accept'].should.equal('application/json');
+ done();
+ })
+ })
+
+ it('writes JSON.stringified object', function(done) {
+ post({ foo: 'bar', test: '测试' }, { json: true }, function(err, resp) {
+ spy.called.should.be.true;
+ var json = JSON.stringify({ foo: 'bar', test: '测试' })
+ spy.args[0][0].toString().should.eql(json)
+ resp.body.body.should.eql(json);
+ done();
+ })
+ })
+
+ })
+
+
+ describe('with json: true and content_type: */* (passed, not default)', function() {
+
+ var opts = { json: true, accept: '*/*' };
+
+ it('sends request', function(done) {
+ spystub_request();
+
+ post({ foo: 'bar', test: '测试' }, opts, function(err, resp) {
+ check_request('post');
+ done();
+ })
+ })
+
+ it('sets Content-Type header to application/json', function(done) {
+ post({ foo: 'bar', test: '测试' }, opts, function(err, resp) {
+ resp.body.headers['content-type'].should.equal('application/json; charset=utf-8');
+ done();
+ })
+ })
+
+ it('respects Accept header set by user', function(done) {
+ post({ foo: 'bar', test: '测试' }, opts, function(err, resp) {
+ resp.body.headers['accept'].should.equal('*/*');
+ done();
+ })
+ })
+
+ it('writes JSON.stringified object', function(done) {
+ post({ foo: 'bar', test: '测试' }, opts, function(err, resp) {
+ spy.called.should.be.true;
+ var json = JSON.stringify({ foo: 'bar', test: '测试' })
+ spy.args[0][0].toString().should.eql(json)
+ resp.body.body.should.eql(json);
+ done();
+ })
+ })
+
+ })
+
+ })
+
+ })
+
+ describe('when buffer', function() {
+
+ describe('get request', function() {
+
+ describe('with json: false (default)', function() {
+
+ it('sends request', function(done) {
+ spystub_request();
+
+ get(Buffer.from('foobar'), { json: false }, function(err, resp) {
+ check_request('get');
+ done();
+ })
+ })
+
+ it('sets Content-Type header', function(done) {
+ get(Buffer.from('foobar'), { json: false }, function(err, resp) {
+ // should.not.exist(resp.body.headers['content-type']);
+ resp.body.headers['content-type'].should.equal('application/x-www-form-urlencoded');
+
+ done();
+ })
+ })
+
+ it('doesnt change default Accept header', function(done) {
+ get(Buffer.from('foobar'), { json: false }, function(err, resp) {
+ // resp.body contains 'header' and 'body', mirroring what we sent
+ resp.body.headers['accept'].should.equal('*/*');
+ done();
+ })
+ })
+
+ it('writes as buffer', function(done) {
+ get(Buffer.from('foobar'), { json: false }, function(err, resp) {
+ spy.called.should.be.true;
+ spy.args[0][0].should.be.an.instanceof(Buffer);
+ spy.args[0][0].toString().should.equal('foobar');
+ resp.body.body.should.eql('foobar');
+ done();
+ })
+ })
+
+ })
+
+ describe('with json: true', function() {
+
+ it('sends request, without setting a querystring', function(done) {
+ spystub_request();
+
+ get(Buffer.from('foobar'), { json: true }, function(err, resp) {
+ check_request('get');
+ done();
+ })
+ })
+
+ it('sets Content-Type header', function(done) {
+ get(Buffer.from('foobar'), { json: true }, function(err, resp) {
+ resp.body.headers['content-type'].should.equal('application/json; charset=utf-8');
+ done();
+ })
+ })
+
+ it('set Accept header to application/json', function(done) {
+ get(Buffer.from('foobar'), { json: true }, function(err, resp) {
+ resp.body.headers['accept'].should.equal('application/json');
+ done();
+ })
+ })
+
+ it('writes JSON.stringify version of object', function(done) {
+ get(Buffer.from('foobar'), { json: true }, function(err, resp) {
+ spy.called.should.be.true;
+ spy.args[0][0].toString().should.eql('foobar')
+ resp.body.body.should.eql('foobar');
+ done();
+ })
+ })
+
+ })
+
+ })
+
+ describe('post request', function() {
+
+ describe('with json: false (default)', function() {
+
+ it('sends request', function(done) {
+ spystub_request();
+
+ post(Buffer.from('foobar'), { json: false }, function(err, resp) {
+ check_request('post');
+ done();
+ })
+ })
+
+ it('sets Content-Type header to www-form-urlencoded', function(done) {
+ post(Buffer.from('foobar'), { json: false }, function(err, resp) {
+ resp.body.headers['content-type'].should.equal('application/x-www-form-urlencoded');
+ done();
+ })
+ })
+
+ it('doesnt change default Accept header', function(done) {
+ post(Buffer.from('foobar'), { json: false }, function(err, resp) {
+ // resp.body contains 'header' and 'body', mirroring what we sent
+ resp.body.headers['accept'].should.equal('*/*');
+ done();
+ })
+ })
+
+ it('writes as buffer', function(done) {
+ post(Buffer.from('foobar'), { json: false }, function(err, resp) {
+ spy.called.should.be.true;
+ spy.args[0][0].should.be.an.instanceof(Buffer);
+ spy.args[0][0].toString().should.equal('foobar');
+ resp.body.body.should.eql('foobar');
+ done();
+ })
+ })
+
+ })
+
+ describe('with json: true', function() {
+
+ it('sends request', function(done) {
+ spystub_request();
+
+ post(Buffer.from('foobar'), { json: true }, function(err, resp) {
+ check_request('post');
+ done();
+ })
+ })
+
+ it('sets Content-Type header', function(done) {
+ post(Buffer.from('foobar'), { json: true }, function(err, resp) {
+ resp.body.headers['content-type'].should.equal('application/json; charset=utf-8');
+ done();
+ })
+ })
+
+ it('set Accept header to application/json', function(done) {
+ post(Buffer.from('foobar'), { json: true }, function(err, resp) {
+ resp.body.headers['accept'].should.equal('application/json');
+ done();
+ })
+ })
+
+ it('passes raw buffer (assuming its a JSON string beneath)', function(done) {
+ post(Buffer.from('foobar'), { json: true }, function(err, resp) {
+ spy.called.should.be.true;
+ spy.args[0][0].toString().should.eql('foobar')
+ resp.body.body.should.eql('foobar');
+ done();
+ })
+ })
+
+ })
+
+ })
+
+ })
+
+ describe('when stream', function() {
+
+ var input_stream;
+
+ beforeEach(function() {
+ input_stream = new stream.Readable();
+ input_stream._read = function() {
+ this.push('foobar');
+ this.push(null);
+ }
+ })
+
+ describe('get request', function() {
+
+ it('explodes', function() {
+ (function() {
+ get(input_stream, {})
+ }).should.throw()
+ })
+
+ });
+
+ describe('post request', function() {
+
+ describe('with json: false (default)', function() {
+
+ it('sends request', function(done) {
+ spystub_request();
+
+ post(input_stream, { json: false }, function(err, resp) {
+ check_request('post');
+ done();
+ })
+ })
+
+ it('sets Content-Type header to www-form-urlencoded', function(done) {
+ post(input_stream, { json: false }, function(err, resp) {
+ resp.body.headers['content-type'].should.equal('application/x-www-form-urlencoded');
+ done();
+ })
+ })
+
+ it('doesnt change default Accept header', function(done) {
+ post(input_stream, { json: false }, function(err, resp) {
+ // resp.body contains 'header' and 'body', mirroring what we sent
+ resp.body.headers['accept'].should.equal('*/*');
+ done();
+ })
+ })
+
+ it('writes as buffer', function(done) {
+ post(input_stream, { json: false }, function(err, resp) {
+ spy.called.should.be.true;
+ spy.args[0][0].should.be.an.instanceof(Buffer);
+ spy.args[0][0].toString().should.equal('foobar');
+ resp.body.body.should.eql('foobar');
+ done();
+ })
+ })
+
+ })
+
+ describe('with json: true', function() {
+
+ it('sends request', function(done) {
+ spystub_request();
+
+ post(input_stream, { json: true }, function(err, resp) {
+ check_request('post');
+ done();
+ })
+ })
+
+ it('sets Content-Type header', function(done) {
+ post(input_stream, { json: true }, function(err, resp) {
+ resp.body.headers['content-type'].should.equal('application/json; charset=utf-8');
+ done();
+ })
+ })
+
+ it('set Accept header to application/json', function(done) {
+ post(input_stream, { json: true }, function(err, resp) {
+ resp.body.headers['accept'].should.equal('application/json');
+ done();
+ })
+ })
+
+ it('writes JSON.stringified object', function(done) {
+ post(input_stream, { json: true }, function(err, resp) {
+ spy.called.should.be.true;
+ spy.args[0][0].toString().should.eql('foobar')
+ resp.body.body.should.eql('foobar');
+ done();
+ })
+ })
+
+ })
+
+ })
+
+ })
+
+ })
+
+})
diff --git a/node_modules/needle/test/proxy_spec.js b/node_modules/needle/test/proxy_spec.js
new file mode 100644
index 0000000..9526e7f
--- /dev/null
+++ b/node_modules/needle/test/proxy_spec.js
@@ -0,0 +1,202 @@
+var helpers = require('./helpers'),
+ should = require('should'),
+ sinon = require('sinon'),
+ http = require('http'),
+ needle = require('./../');
+
+var port = 7707;
+var url = 'localhost:' + port;
+var nonexisting_host = 'awepfokawepofawe.com';
+
+describe('proxy option', function() {
+
+ var spy, opts;
+
+ function send_request(opts, done) {
+ if (spy) spy.restore();
+ spy = sinon.spy(http, 'request');
+ needle.get(url, opts, done);
+ }
+
+ //////////////////////
+ // proxy opts helpers
+
+ function not_proxied(done) {
+ return function(err, resp) {
+ var path = spy.args[0][0].path;
+ path.should.eql('/'); // not the full original URI
+ spy.restore();
+ done();
+ }
+ }
+
+ function proxied(host, port, done) {
+ return function(err, resp) {
+ var path = spy.args[0][0].path;
+ path.should.eql('http://' + url); // the full original URI
+
+ var http_host = spy.args[0][0].host;
+ if (http_host) http_host.should.eql(host);
+
+ var http_port = spy.args[0][0].port;
+ if (http_port) http_port.should.eql(port);
+
+ spy.restore();
+ done();
+ }
+ }
+
+ //////////////////////
+ // auth helpers
+
+ function get_auth(header) {
+ var token = header.split(/\s+/).pop();
+ return token && Buffer.from(token, 'base64').toString().split(':');
+ }
+
+ function no_proxy_auth(done) {
+ return function(err, resp) {
+ var headers = spy.args[0][0].headers;
+ Object.keys(headers).should.not.containEql('proxy-authorization');
+ done();
+ }
+ }
+
+ function header_set(name, user, pass, done) {
+ return function(err, resp) {
+ var headers = spy.args[0][0].headers;
+ var auth = get_auth(headers[name]);
+ auth[0].should.eql(user);
+ auth[1].should.eql(pass);
+ done();
+ }
+ }
+
+ function proxy_auth_set(user, pass, done) {
+ return header_set('proxy-authorization', user, pass, done);
+ }
+
+ function basic_auth_set(user, pass, done) {
+ return header_set('authorization', user, pass, done);
+ }
+
+ after(function() {
+ spy.restore();
+ })
+
+ describe('when null proxy is passed', function() {
+
+ it('does not proxy', function(done) {
+ send_request({ proxy: null }, not_proxied(done))
+ })
+
+ describe('but defaults has been set', function() {
+
+ before(function() {
+ needle.defaults({ proxy: 'foobar' });
+ })
+
+ after(function() {
+ needle.defaults({ proxy: null });
+ })
+
+ it('tries to proxy anyway', function(done) {
+ send_request({}, proxied('foobar', 80, done))
+ })
+
+ })
+
+ })
+
+ describe('when weird string is passed', function() {
+
+ it('tries to proxy anyway', function(done) {
+ send_request({ proxy: 'alfalfa' }, proxied('alfalfa', 80, done))
+ })
+ })
+
+ describe('when valid url is passed', function() {
+
+ it('proxies request', function(done) {
+ send_request({ proxy: nonexisting_host + ':123/done' }, proxied(nonexisting_host, '123', done))
+ })
+
+ it('does not set a Proxy-Authorization header', function(done) {
+ send_request({ proxy: nonexisting_host + ':123/done' }, no_proxy_auth(done));
+ })
+
+ describe('and proxy url contains user:pass', function() {
+
+ before(function() {
+ opts = {
+ proxy: 'http://mj:x@' + nonexisting_host + ':123/done'
+ }
+ })
+
+ it('proxies request', function(done) {
+ send_request(opts, proxied(nonexisting_host, '123', done))
+ })
+
+ it('sets Proxy-Authorization header', function(done) {
+ send_request(opts, proxy_auth_set('mj', 'x', done));
+ })
+
+ })
+
+ describe('and a proxy_user is passed', function() {
+
+ before(function() {
+ opts = {
+ proxy: nonexisting_host + ':123',
+ proxy_user: 'someone',
+ proxy_pass: 'else'
+ }
+ })
+
+ it('proxies request', function(done) {
+ send_request(opts, proxied(nonexisting_host, '123', done))
+ })
+
+ it('sets Proxy-Authorization header', function(done) {
+ send_request(opts, proxy_auth_set('someone', 'else', done));
+ })
+
+ describe('and url also contains user:pass', function() {
+
+ it('url user:pass wins', function(done) {
+ var opts = {
+ proxy: 'http://xxx:yyy@' + nonexisting_host + ':123',
+ proxy_user: 'someone',
+ proxy_pass: 'else'
+ }
+
+ send_request(opts, proxy_auth_set('xxx', 'yyy', done));
+ })
+
+ })
+
+ describe('and options.username is also present', function() {
+
+ before(function() {
+ opts = { proxy_user: 'foobar', username: 'someone' };
+ })
+
+ it('a separate Authorization header is set', function(done) {
+ var opts = {
+ proxy: nonexisting_host + ':123',
+ proxy_user: 'someone',
+ proxy_pass: 'else',
+ username: 'test',
+ password: 'X'
+ }
+
+ send_request(opts, basic_auth_set('test', 'X', done));
+ })
+
+ })
+
+ })
+
+ })
+
+})
diff --git a/node_modules/needle/test/querystring_spec.js b/node_modules/needle/test/querystring_spec.js
new file mode 100644
index 0000000..34c1748
--- /dev/null
+++ b/node_modules/needle/test/querystring_spec.js
@@ -0,0 +1,128 @@
+var should = require('should'),
+ stringify = require('../lib/querystring').build;
+
+describe('stringify', function() {
+
+ describe('with null', function() {
+
+ it('throws', function() {
+ (function() {
+ var res = stringify(null);
+ }).should.throw();
+ })
+
+ })
+
+ describe('with a number', function() {
+
+ it('throws', function() {
+ (function() {
+ var res = stringify(100);
+ }).should.throw();
+ })
+
+ })
+
+ describe('with a string', function() {
+
+ describe('that is empty', function() {
+
+ it('throws', function() {
+ (function() {
+ var res = stringify('');
+ }).should.throw();
+ })
+
+ })
+
+ describe('that doesnt contain an equal sign', function() {
+
+ it('throws', function() {
+ (function() {
+ var res = stringify('boomshagalaga');
+ }).should.throw();
+ })
+
+ })
+
+ describe('that contains an equal sign', function() {
+
+ it('works', function() {
+ var res = stringify('hello=123');
+ res.should.eql('hello=123');
+ })
+
+ })
+
+ })
+
+ describe('with an array', function() {
+
+ describe('with key val objects', function() {
+
+ it('works', function() {
+ var res = stringify([ {foo: 'bar'} ]);
+ res.should.eql('foo=bar');
+ })
+
+ })
+
+ describe('where all elements are strings with an equal sign', function() {
+
+ it('works', function() {
+ var res = stringify([ 'bar=123', 'quux=' ]);
+ res.should.eql('bar=123&quux=');
+ })
+
+ })
+
+ describe('with random words', function() {
+
+ it('throws', function() {
+ (function() {
+ var res = stringify(['hello', 'there']);
+ }).should.throw();
+ })
+
+ })
+
+ describe('with integers', function() {
+
+ it('throws', function() {
+ (function() {
+ var res = stringify([123, 432]);
+ }).should.throw();
+ })
+
+ })
+
+ })
+
+ describe('with an object', function() {
+
+ it('works', function() {
+ var res = stringify({ test: 100 });
+ res.should.eql('test=100');
+ })
+
+ describe('with object where val is an array', function() {
+
+ it('works', function() {
+ var res = stringify({ foo: ['bar', 'baz'] });
+ res.should.eql('foo[]=bar&foo[]=baz');
+ })
+
+ })
+
+ describe('with object where val is an array of key val objects', function() {
+
+ it('works', function() {
+ var res = stringify({ foo: [{'1': 'bar'}, {'2': 'baz'}] });
+ res.should.eql('foo[][1]=bar&foo[][2]=baz');
+ })
+
+ })
+
+ })
+
+})
diff --git a/node_modules/needle/test/redirect_spec.js b/node_modules/needle/test/redirect_spec.js
new file mode 100644
index 0000000..9399c98
--- /dev/null
+++ b/node_modules/needle/test/redirect_spec.js
@@ -0,0 +1,392 @@
+var helpers = require('./helpers'),
+ should = require('should'),
+ sinon = require('sinon'),
+ needle = require('./../');
+
+var ports = {
+ http : 8888,
+ https : 9999
+}
+
+var protocols = {
+ http : require('http'),
+ https : require('https')
+}
+
+var code = 301;
+var location; // var to set the response location
+
+function response_code() {
+ return code;
+}
+
+function response_headers() {
+ return { 'Content-Type': 'text/plain', 'Location': location }
+}
+
+describe('redirects', function() {
+
+ var spies = {},
+ servers = {};
+
+ var current_protocol;
+ var hostname = require('os').hostname();
+
+ // open two servers, one that responds to a redirect
+ before(function(done) {
+
+ var conf = {
+ port : ports.http,
+ code : response_code,
+ headers : response_headers
+ }
+
+ servers.http = helpers.server(conf, function() {
+ conf.port = ports.https;
+ conf.protocol = 'https';
+ servers.https = helpers.server(conf, done);
+ });
+ })
+
+ after(function(done) {
+ servers.http.close(function() {
+ servers.https.close(done);
+ });
+ })
+
+ var prots = {'http': 'https'};
+ Object.keys(prots).forEach(function(protocol) {
+
+ current_protocol = protocol;
+ var other_protocol = protocol == 'http' ? 'https' : 'http';
+
+ var opts, // each test will modify this
+ host = '127.0.0.1',
+ url = protocol + '://' + host + ':' + ports[protocol] + '/hello';
+
+ function send_request(opts, cb) {
+ opts.rejectUnauthorized = false;
+ // console.log(' -- sending request ' + url + ' -- redirect to ' + location);
+ needle.post(url, { foo: 'bar' }, opts, cb);
+ }
+
+ function not_followed(done) {
+ send_request(opts, function(err, resp) {
+ resp.statusCode.should.eql(301);
+ if (current_protocol == 'http') {
+ spies.http.callCount.should.eql(1); // only original request
+ spies.https.callCount.should.eql(0);
+ } else {
+ spies.http.callCount.should.eql(0);
+ spies.https.callCount.should.eql(1); // only original request
+ }
+ done();
+ })
+ }
+
+ function followed_same_protocol(done) {
+ send_request(opts, function(err, resp) {
+ // the original request plus the redirect one
+ spies[current_protocol].callCount.should.eql(2);
+ done();
+ })
+
+ }
+
+ function followed_other_protocol(done) {
+ send_request(opts, function(err, resp) {
+ // on new node versions, https.request calls http.request internally,
+ // so we need to amount for that additional call.
+
+ var http_calls = protocols.http.Agent.defaultMaxSockets == Infinity ? 2 : 1;
+
+ spies.http.callCount.should.eql(http_calls); // the one(s) from http.request
+ spies.https.callCount.should.eql(1); // the one from https.request (redirect)
+ done();
+ })
+ }
+
+ // set a spy on [protocol].request
+ // so we can see how many times a request was made
+ before(function() {
+ spies.http = sinon.spy(protocols.http, 'request');
+ spies.https = sinon.spy(protocols.https, 'request');
+ })
+
+ // and make sure it is restored after each test
+ afterEach(function() {
+ spies.http.reset();
+ spies.https.reset();
+ })
+
+ after(function() {
+ spies.http.restore();
+ spies.https.restore();
+ })
+
+ describe('when overriding defaults', function() {
+
+ before(function() {
+ needle.defaults({ follow_max: 10 });
+ opts = {};
+ })
+
+ after(function() {
+ // reset values to previous
+ needle.defaults({ follow_max: 0 });
+ })
+
+ describe('and redirected to the same path on same host and protocol', function() {
+ before(function() {
+ location = url;
+ })
+ it('does not follow redirect', not_followed);
+ })
+
+ describe('and redirected to the same path on same host and different protocol', function() {
+ before(function() {
+ location = url.replace(protocol, other_protocol).replace(ports[protocol], ports[other_protocol]);
+ })
+
+ it('follows redirect', followed_other_protocol);
+ })
+
+ describe('and redirected to a different path on same host, same protocol', function() {
+ before(function() {
+ location = url.replace('/hello', '/goodbye');
+ })
+ it('follows redirect', followed_same_protocol);
+ })
+
+ describe('and redirected to a different path on same host, different protocol', function() {
+ before(function() {
+ location = url.replace('/hello', '/goodbye').replace(protocol, other_protocol).replace(ports[protocol], ports[other_protocol]);
+ })
+ it('follows redirect', followed_other_protocol);
+ })
+
+ describe('and redirected to same path on another host, same protocol', function() {
+ before(function() {
+ location = url.replace(host, hostname);
+ })
+ it('follows redirect', followed_same_protocol);
+ })
+
+ describe('and redirected to same path on another host, different protocol', function() {
+ before(function() {
+ location = url.replace(host, hostname).replace(protocol, other_protocol).replace(ports[protocol], ports[other_protocol]);
+ })
+ it('follows redirect', followed_other_protocol);
+ })
+
+ })
+
+ // false and null have the same result
+ var values = [false, null];
+ values.forEach(function(value) {
+
+ describe('when follow is ' + value, function() {
+
+ before(function() {
+ opts = { follow: value };
+ })
+
+
+
+ describe('and redirected to the same path on same host and protocol', function() {
+ before(function() {
+ location = url;
+ })
+
+ it('throws an error', function() {
+ (function() {
+ send_request(opts, function() { });
+ }).should.throw;
+ })
+
+ })
+
+ })
+
+ })
+
+ describe('when follow is true', function() {
+
+ before(function() {
+ opts = { follow: true };
+ })
+
+ describe('and redirected to the same path on same host and protocol', function() {
+ before(function() { location = url })
+
+ it('throws an error', function() {
+ (function() {
+ send_request(opts, function() { });
+ }).should.throw;
+ })
+
+ })
+
+ })
+
+ describe('when follow is > 0', function() {
+
+ before(function() {
+ needle.defaults({ follow: 10 });
+ })
+
+ after(function() {
+ needle.defaults({ follow: 0 });
+ })
+
+ describe('when keep_method is false', function() {
+
+ before(function() {
+ opts = { follow_keep_method: false };
+ })
+
+ // defaults to follow host and protocol
+ describe('and redirected to the same path on same host and different protocol', function() {
+
+ before(function() {
+ location = url.replace(protocol, other_protocol);
+ })
+
+ it('follows redirect', followed_other_protocol);
+
+ it('sends a GET request with no data', function(done) {
+ send_request(opts, function(err, resp) {
+ spies.http.args[0][0].method.should.eql('GET');
+ // spy.args[0][3].should.eql(null);
+ done();
+ })
+ })
+
+ })
+
+ })
+
+ describe('and set_referer is true', function() {
+
+ before(function() {
+ opts = { follow_set_referer: true };
+ })
+
+ // defaults to follow host and protocol
+ describe('and redirected to the same path on same host and different protocol', function() {
+
+ before(function() {
+ location = url.replace(protocol, other_protocol);
+ })
+
+ it('follows redirect', followed_other_protocol);
+
+ it('sets Referer header when following redirect', function(done) {
+ send_request(opts, function(err, resp) {
+ spies.http.args[0][0].headers['referer'].should.eql("http://" + host + ":8888/hello");
+ // spies.http.args[0][3].should.eql({ foo: 'bar'});
+ done();
+ })
+ })
+
+ })
+
+ })
+
+ describe('and keep_method is true', function() {
+
+ before(function() {
+ opts = { follow_keep_method: true };
+ })
+
+ // defaults to follow host and protocol
+ describe('and redirected to the same path on same host and different protocol', function() {
+
+ before(function() {
+ location = url.replace(protocol, other_protocol);
+ })
+
+ it('follows redirect', followed_other_protocol);
+
+ it('sends a POST request with the original data', function(done) {
+ send_request(opts, function(err, resp) {
+ spies.http.args[0][0].method.should.eql('post');
+ // spies.http.args[0][3].should.eql({ foo: 'bar'});
+ done();
+ })
+ })
+
+ })
+
+ })
+
+ describe('and if_same_host is false', function() {
+
+ before(function() {
+ opts = { follow_if_same_host: false };
+ })
+
+ // by default it will follow other protocols
+ describe('and redirected to same path on another domain, same protocol', function() {
+ before(function() {
+ location = url.replace(host, hostname);
+ })
+ it('follows redirect', followed_same_protocol);
+ })
+
+ })
+
+ describe('and if_same_host is true', function() {
+
+ before(function() {
+ opts = { follow_if_same_host: true };
+ })
+
+ // by default it will follow other protocols
+ describe('and redirected to same path on another domain, same protocol', function() {
+ before(function() {
+ location = url.replace(host, hostname);
+ })
+
+ it('does not follow redirect', not_followed);
+ })
+
+ })
+
+ describe('and if_same_protocol is false', function() {
+
+ before(function() {
+ opts = { follow_if_same_protocol: false };
+ })
+
+ // by default it will follow other hosts
+ describe('and redirected to same path on another domain, different protocol', function() {
+ before(function() {
+ location = url.replace(host, hostname).replace(protocol, other_protocol).replace(ports[protocol], ports[other_protocol]);
+ })
+ it('follows redirect', followed_other_protocol);
+ })
+
+ })
+
+ describe('and if_same_protocol is true', function() {
+
+ before(function() {
+ opts = { follow_if_same_protocol: true };
+ })
+
+ // by default it will follow other hosts
+ describe('and redirected to same path on another domain, different protocol', function() {
+ before(function() {
+ location = url.replace(host, hostname).replace(protocol, other_protocol).replace(ports[protocol], ports[other_protocol]);
+ })
+ it('does not follow redirect', not_followed);
+ })
+
+ })
+
+ })
+
+ })
+
+});
diff --git a/node_modules/needle/test/redirect_with_timeout.js b/node_modules/needle/test/redirect_with_timeout.js
new file mode 100644
index 0000000..7e8e02e
--- /dev/null
+++ b/node_modules/needle/test/redirect_with_timeout.js
@@ -0,0 +1,45 @@
+var should = require('should')
+var needle = require('./../')
+
+describe('follow redirects when read_timeout is set', function () {
+
+ it('clear timeout before following redirect', function (done) {
+ var opts = {
+ open_timeout: 1000,
+ read_timeout: 3000,
+ follow: 5,
+ user_agent: 'Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36'
+ }
+
+ var timedOut = 0
+ var redirects = 0
+
+ var timer = setTimeout(function () {
+ var hasRedirects = redirects > 0
+ hasRedirects.should.equal(true)
+ done()
+ }, opts.read_timeout || 3000)
+
+ var resp = needle.get('http://google.com/', opts, function (err, resp, body) {
+ var noErr = err === null
+ var hasBody = body.length > 0
+ noErr.should.equal(true);
+ hasBody.should.equal(true);
+ });
+
+ resp.on('redirect', function (location) {
+ redirects++
+ // console.info(' Redirected to ', location)
+ })
+
+ resp.on('timeout', function (type) {
+ timedOut++
+ timedOut.should.equal(0)
+ // console.error(' ', type, 'timeout')
+ clearTimeout(timer)
+ done()
+ })
+
+ }).timeout(30000)
+
+}) \ No newline at end of file
diff --git a/node_modules/needle/test/request_stream_spec.js b/node_modules/needle/test/request_stream_spec.js
new file mode 100644
index 0000000..1f02c8b
--- /dev/null
+++ b/node_modules/needle/test/request_stream_spec.js
@@ -0,0 +1,202 @@
+var fs = require('fs'),
+ needle = require('..'),
+ stream = require('stream'),
+ http = require('http'),
+ should = require('should'),
+ sinon = require('sinon');
+
+var port = 2233;
+
+describe('request stream length', function() {
+
+ var server, writable;
+
+ function createServer() {
+ return http.createServer(function(req, res) {
+
+ req.on('data', function(chunk) {
+ // console.log(chunk.length);
+ })
+
+ req.on('end', function() {
+ res.writeHeader(200, { 'Content-Type': 'application/json'})
+ res.end(JSON.stringify({ headers: req.headers }))
+ })
+
+ })
+ }
+
+ before(function(done) {
+ server = createServer();
+ server.listen(port, done)
+ })
+
+ beforeEach(function() {
+ writable = new stream.Readable();
+ writable._read = function() {
+ this.push('hello world');
+ this.push(null);
+ }
+ })
+
+ after(function(done) {
+ server.close(done)
+ })
+
+ function send_request(opts, cb) {
+ needle.post('http://localhost:' + port, writable, opts, function(err, resp) {
+ cb(err, resp)
+ })
+ }
+
+ describe('no stream_length set', function() {
+
+ it('doesnt set Content-Length header', function(done) {
+ send_request({}, function(err, resp) {
+ should.not.exist(resp.body.headers['content-length']);
+ done()
+ })
+ })
+
+ it('doesnt work if Transfer-Encoding is set to a blank string', function(done) {
+ send_request({ headers: { 'Transfer-Encoding': '' }}, function(err, resp) {
+ err.code.should.eql('ECONNRESET');
+ done()
+ })
+ })
+
+ it('works if Transfer-Encoding is not set', function(done) {
+ send_request({}, function(err, resp) {
+ should.not.exist(err);
+ resp.statusCode.should.eql(200);
+ done()
+ })
+ })
+
+ })
+
+ describe('stream_length set to invalid value', function() {
+
+ it('sets Content-Length header to that value', function(done) {
+ send_request({ stream_length: 5 }, function(err, resp) {
+ should.exist(err);
+ err.code.should.eql('ECONNRESET');
+ done()
+ })
+ })
+
+ it('doesnt work if Transfer-Encoding is set to a blank string', function(done) {
+ send_request({ stream_length: 5, headers: { 'Transfer-Encoding': '' }}, function(err, resp) {
+ err.code.should.eql('ECONNRESET');
+ done()
+ })
+ })
+
+ it('doesnt work if Transfer-Encoding is not set', function(done) {
+ send_request({ stream_length: 5 }, function(err, resp) {
+ err.code.should.eql('ECONNRESET');
+ done()
+ })
+ })
+
+ })
+
+ describe('stream_length is set to valid value', function() {
+
+ it('sets Content-Length header to that value', function(done) {
+ send_request({ stream_length: 11 }, function(err, resp) {
+ resp.body.headers['content-length'].should.eql('11');
+ done()
+ })
+ })
+
+ it('works if Transfer-Encoding is set to a blank string', function(done) {
+ send_request({ stream_length: 11, headers: { 'Transfer-Encoding': '' }}, function(err, resp) {
+ should.not.exist(err);
+ resp.statusCode.should.eql(200);
+ done()
+ })
+ })
+
+ it('works if Transfer-Encoding is not set', function(done) {
+ send_request({ stream_length: 11 }, function(err, resp) {
+ should.not.exist(err);
+ resp.statusCode.should.eql(200);
+ done()
+ })
+ })
+
+ })
+
+
+ describe('stream_length set to 0', function() {
+
+ describe('stream with path', function() {
+
+ var stub;
+
+ beforeEach(function() {
+ writable.path = '/foo/bar';
+ stub = sinon.stub(fs, 'stat', function(path, cb) {
+ cb(null, { size: 11 })
+ })
+ })
+
+ afterEach(function() {
+ stub.restore();
+ })
+
+ it('sets Content-Length header to streams length', function(done) {
+ send_request({ stream_length: 0 }, function(err, resp) {
+ resp.body.headers['content-length'].should.eql('11');
+ done()
+ })
+ })
+
+ it('works if Transfer-Encoding is set to a blank string', function(done) {
+ send_request({ stream_length: 0, headers: { 'Transfer-Encoding': '' }}, function(err, resp) {
+ should.not.exist(err);
+ resp.statusCode.should.eql(200);
+ done()
+ })
+ })
+
+ it('works if Transfer-Encoding is not set', function(done) {
+ send_request({ stream_length: 0 }, function(err, resp) {
+ should.not.exist(err);
+ resp.statusCode.should.eql(200);
+ done()
+ })
+ })
+
+ })
+
+ describe('stream without path', function() {
+ it('does not set Content-Length header', function(done) {
+ send_request({ stream_length: 0 }, function(err, resp) {
+ should.not.exist(resp.body.headers['content-length']);
+ done()
+ })
+ })
+
+ it('doesnt work if Transfer-Encoding is set to a blank string', function(done) {
+ send_request({ stream_length: 0, headers: { 'Transfer-Encoding': '' }}, function(err, resp) {
+ err.code.should.eql('ECONNRESET');
+ done()
+ })
+ })
+
+ it('works if Transfer-Encoding is not set', function(done) {
+ send_request({ stream_length: 0 }, function(err, resp) {
+ should.not.exist(err);
+ resp.statusCode.should.eql(200);
+ done()
+ })
+ })
+
+ })
+
+
+ })
+
+})
diff --git a/node_modules/needle/test/response_stream_spec.js b/node_modules/needle/test/response_stream_spec.js
new file mode 100644
index 0000000..ef20e38
--- /dev/null
+++ b/node_modules/needle/test/response_stream_spec.js
@@ -0,0 +1,139 @@
+var should = require('should'),
+ needle = require('./../'),
+ http = require('http'),
+ stream = require('stream'),
+ fs = require('fs'),
+ port = 11111,
+ server;
+
+describe('response streams', function() {
+
+ describe('when the server sends back json', function(){
+
+ before(function() {
+ server = http.createServer(function(req, res) {
+ res.setHeader('Content-Type', 'application/json')
+ res.end('{"foo":"bar"}')
+ }).listen(port);
+ });
+
+ after(function() {
+ server.close();
+ })
+
+ describe('and the client uses streams', function(){
+
+ it('should create a proper streams2 stream', function(done) {
+ var stream = needle.get('localhost:' + port)
+
+ // newer node versions set this to null instead of false
+ var bool = !!stream._readableState.flowing;
+ should.equal(false, bool);
+
+ var readableCalled = false;
+ stream.on('readable', function() {
+ readableCalled = true;
+ })
+
+ stream.on('done', function() {
+ readableCalled.should.be.true;
+ done();
+ });
+
+ stream.resume()
+
+ })
+
+ it('emits a single data item which is our JSON object', function(done) {
+ var stream = needle.get('localhost:' + port)
+
+ var chunks = [];
+ stream.on('readable', function () {
+ while (chunk = this.read()) {
+ chunk.should.be.an.Object;
+ chunks.push(chunk);
+ }
+ })
+
+ stream.on('done', function () {
+ chunks.should.have.length(1)
+ chunks[0].should.have.property('foo', 'bar');
+ done();
+ });
+ })
+
+ it('emits a raw buffer if we do not want to parse JSON', function(done) {
+ var stream = needle.get('localhost:' + port, { parse: false })
+
+ var chunks = [];
+ stream.on('readable', function () {
+ while (chunk = this.read()) {
+ Buffer.isBuffer(chunk).should.be.true;
+ chunks.push(chunk);
+ }
+ })
+
+ stream.on('done', function() {
+ var body = Buffer.concat(chunks).toString();
+ body.should.equal('{"foo":"bar"}')
+ done();
+ });
+ })
+
+ })
+ })
+
+ describe('when the server sends back what was posted to it', function () {
+ var file = 'asdf.txt';
+
+ before(function(done){
+ server = http.createServer(function(req, res) {
+ res.setHeader('Content-Type', 'application/octet')
+ req.pipe(res);
+ }).listen(port);
+
+ fs.writeFile(file, 'contents of stream', done);
+ });
+
+ after(function(done){
+ server.close();
+ fs.unlink(file, done);
+ })
+
+ it('can PUT a stream', function (done) {
+ var stream = needle.put('localhost:' + port, fs.createReadStream(file), { stream: true });
+
+ var chunks = [];
+ stream.on('readable', function () {
+ while (chunk = this.read()) {
+ Buffer.isBuffer(chunk).should.be.true;
+ chunks.push(chunk);
+ }
+ })
+
+ stream.on('end', function () {
+ var body = Buffer.concat(chunks).toString();
+ body.should.equal('contents of stream')
+ done();
+ });
+ });
+
+ it('can PATCH a stream', function (done) {
+ var stream = needle.patch('localhost:' + port, fs.createReadStream(file), { stream: true });
+
+ var chunks = [];
+ stream.on('readable', function () {
+ while (chunk = this.read()) {
+ Buffer.isBuffer(chunk).should.be.true;
+ chunks.push(chunk);
+ }
+ })
+
+ stream.on('end', function () {
+ var body = Buffer.concat(chunks).toString();
+ body.should.equal('contents of stream')
+ done();
+ });
+ });
+ })
+})
diff --git a/node_modules/needle/test/socket_pool_spec.js b/node_modules/needle/test/socket_pool_spec.js
new file mode 100644
index 0000000..58ffa14
--- /dev/null
+++ b/node_modules/needle/test/socket_pool_spec.js
@@ -0,0 +1,66 @@
+var needle = require('../'),
+ should = require('should'),
+ http = require('http');
+
+var server, port = 11112;
+
+describe('socket reuse', function() {
+
+ before(function() {
+ server = http.createServer(function(req, res) {
+ res.setHeader('Content-Type', 'application/json');
+ setTimeout(function() {
+ res.end('{"foo":"bar"}');
+ }, 50);
+ }).listen(port);
+ });
+
+ after(function() {
+ server.close();
+ });
+
+ describe('when sockets are reused', function() {
+
+ var httpAgent = new http.Agent({
+ keepAlive : true,
+ maxSockets : 1
+ });
+
+ it('does not duplicate listeners on .end', function(done) {
+
+ var last_error;
+ var count = 10;
+
+ function completed(err) {
+ --count || done(last_error);
+ }
+
+ function send() {
+ needle.get('localhost:' + port, { agent: httpAgent }, function(err, resp) {
+ if (err)
+ throw new Error("Unexpected error: " + err);
+
+ // lets go through all sockets and inspect all socket objects
+ for (hostTarget in httpAgent.sockets) {
+ httpAgent.sockets[hostTarget].forEach(function(socket) {
+ // normally, there are 2 internal listeners and 1 needle sets up,
+ // but to be sure the test does not fail even if newer node versions
+ // introduce additional listeners, we use a higher limit.
+ try {
+ socket.listeners('end').length.should.be.below(5, "too many listeners on the socket object's end event");
+ } catch (e) {
+ last_error = e;
+ }
+ });
+ }
+
+ completed();
+ });
+ }
+
+ for (var i = 0; i < count; i++) {
+ send();
+ }
+ });
+ });
+});
diff --git a/node_modules/needle/test/url_spec.js b/node_modules/needle/test/url_spec.js
new file mode 100644
index 0000000..8a0fa3e
--- /dev/null
+++ b/node_modules/needle/test/url_spec.js
@@ -0,0 +1,155 @@
+var needle = require('../'),
+ sinon = require('sinon'),
+ should = require('should'),
+ http = require('http'),
+ helpers = require('./helpers');
+
+var port = 3456;
+
+describe('urls', function() {
+
+ var server, url;
+
+ function send_request(cb) {
+ return needle.get(url, cb);
+ }
+
+ before(function(done){
+ server = helpers.server({ port: port }, done);
+ })
+
+ after(function(done) {
+ server.close(done);
+ })
+
+ describe('null URL', function(){
+
+ it('throws', function(){
+ (function() {
+ send_request()
+ }).should.throw();
+ })
+
+ })
+
+ describe('invalid protocol', function(){
+
+ before(function() {
+ url = 'foo://google.com/what'
+ })
+
+ it('does not throw', function(done) {
+ (function() {
+ send_request(function(err) {
+ done();
+ })
+ }).should.not.throw()
+ })
+
+ it('returns an error', function(done) {
+ send_request(function(err) {
+ err.should.be.an.Error;
+ err.code.should.match(/ENOTFOUND|EADDRINFO|EAI_AGAIN/)
+ done();
+ })
+ })
+
+ })
+
+ describe('invalid host', function(){
+
+ before(function() {
+ url = 'http://s1\\\2.com/'
+ })
+
+ it('fails', function(done) {
+ (function() {
+ send_request(function(){ })
+ }.should.throw(TypeError))
+ done()
+ })
+
+ })
+
+/*
+ describe('invalid path', function(){
+
+ before(function() {
+ url = 'http://www.google.com\\\/x\\\ %^&*() /x2.com/'
+ })
+
+ it('fails', function(done) {
+ send_request(function(err) {
+ err.should.be.an.Error;
+ done();
+ })
+ })
+
+ })
+*/
+
+ describe('valid protocol and path', function() {
+
+ before(function() {
+ url = 'http://localhost:' + port + '/foo';
+ })
+
+ it('works', function(done) {
+ send_request(function(err){
+ should.not.exist(err);
+ done();
+ })
+ })
+
+ })
+
+ describe('no protocol but with slashes and valid path', function() {
+
+ before(function() {
+ url = '//localhost:' + port + '/foo';
+ })
+
+ it('works', function(done) {
+ send_request(function(err){
+ should.not.exist(err);
+ done();
+ })
+ })
+
+ })
+
+ describe('no protocol nor slashes and valid path', function() {
+
+ before(function() {
+ url = 'localhost:' + port + '/foo';
+ })
+
+ it('works', function(done) {
+ send_request(function(err){
+ should.not.exist(err);
+ done();
+ })
+ })
+
+ })
+
+ describe('double encoding', function() {
+
+ var path = '/foo?email=' + encodeURIComponent('[email protected]');
+
+ before(function() {
+ url = 'localhost:' + port + path
+ });
+
+ it('should not occur', function(done) {
+ send_request(function(err, res) {
+ should.not.exist(err);
+ should(res.req.path).be.exactly(path);
+ done();
+ });
+
+ });
+
+ })
+
+})
diff --git a/node_modules/needle/test/utils/formidable.js b/node_modules/needle/test/utils/formidable.js
new file mode 100644
index 0000000..ba1d983
--- /dev/null
+++ b/node_modules/needle/test/utils/formidable.js
@@ -0,0 +1,17 @@
+var formidable = require('formidable'),
+ http = require('http'),
+ util = require('util');
+
+var port = process.argv[2] || 8888;
+
+http.createServer(function(req, res) {
+ var form = new formidable.IncomingForm();
+ form.parse(req, function(err, fields, files) {
+ res.writeHead(200, {'content-type': 'text/plain'});
+ res.write('received upload:\n\n');
+ console.log(util.inspect({fields: fields, files: files}))
+ res.end(util.inspect({fields: fields, files: files}));
+ });
+}).listen(port);
+
+console.log('HTTP server listening on port ' + port); \ No newline at end of file
diff --git a/node_modules/needle/test/utils/proxy.js b/node_modules/needle/test/utils/proxy.js
new file mode 100644
index 0000000..531bf49
--- /dev/null
+++ b/node_modules/needle/test/utils/proxy.js
@@ -0,0 +1,62 @@
+var http = require('http'),
+ https = require('https'),
+ url = require('url');
+
+var port = 1234,
+ log = true,
+ request_auth = false;
+
+http.createServer(function(request, response) {
+
+ console.log(request.headers);
+ console.log("Got request: " + request.url);
+ console.log("Forwarding request to " + request.headers['host']);
+
+ if (request_auth) {
+ if (!request.headers['proxy-authorization']) {
+ response.writeHead(407, {'Proxy-Authenticate': 'Basic realm="proxy.com"'})
+ return response.end('Hello.');
+ }
+ }
+
+ var remote = url.parse(request.url);
+ var protocol = remote.protocol == 'https:' ? https : http;
+
+ var opts = {
+ host: request.headers['host'],
+ port: remote.port || (remote.protocol == 'https:' ? 443 : 80),
+ method: request.method,
+ path: remote.pathname,
+ headers: request.headers
+ }
+
+ var proxy_request = protocol.request(opts, function(proxy_response){
+
+ proxy_response.on('data', function(chunk) {
+ if (log) console.log(chunk.toString());
+ response.write(chunk, 'binary');
+ });
+ proxy_response.on('end', function() {
+ response.end();
+ });
+
+ response.writeHead(proxy_response.statusCode, proxy_response.headers);
+ });
+
+ request.on('data', function(chunk) {
+ if (log) console.log(chunk.toString());
+ proxy_request.write(chunk, 'binary');
+ });
+
+ request.on('end', function() {
+ proxy_request.end();
+ });
+
+}).listen(port);
+
+process.on('uncaughtException', function(err){
+ console.log('Uncaught exception!');
+ console.log(err);
+});
+
+console.log("Proxy server listening on port " + port);
diff --git a/node_modules/needle/test/utils/test.js b/node_modules/needle/test/utils/test.js
new file mode 100644
index 0000000..8d58d70
--- /dev/null
+++ b/node_modules/needle/test/utils/test.js
@@ -0,0 +1,104 @@
+// TODO: write specs. :)
+
+var fs = require('fs'),
+ client = require('./../../');
+
+process.env.DEBUG = true;
+
+var response_callback = function(err, resp, body){
+ console.log(err);
+ if(resp) console.log("Got status code " + resp.statusCode)
+ console.log(body);
+}
+
+function simple_head(){
+ client.head('http://www.amazon.com', response_callback);
+}
+
+function simple_get(){
+ client.get('http://www.nodejs.org', response_callback);
+}
+
+function proxy_get(){
+ client.get('https://www.google.com/search?q=nodejs', {proxy: 'http://localhost:1234'}, response_callback);
+}
+
+function auth_get(){
+ client.get('https://www.twitter.com', {username: 'asd', password: '123'}, response_callback);
+}
+
+function simple_post(url){
+
+ var data = {
+ foo: 'bar',
+ baz: {
+ nested: 'attribute'
+ }
+ }
+
+ client.post(url, data, response_callback);
+
+}
+
+function multipart_post(url){
+
+ var filename = 'test_file.txt';
+ var data = 'Plain text data.\nLorem ipsum dolor sit amet.\nBla bla bla.\n';
+ fs.writeFileSync(filename, data);
+
+ var black_pixel = Buffer.from("data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs=".replace(/^data:image\/\w+;base64,/, ""), "base64");
+
+ var data = {
+ foo: 'bar',
+ bar: 'baz',
+ nested: {
+ my_document: { file: filename, content_type: 'text/plain' },
+ even: {
+ more: 'nesting'
+ }
+ },
+ pixel: { filename: 'black_pixel.gif', buffer: black_pixel, content_type: 'image/gif' },
+ field2: {value: JSON.stringify({"json":[ {"one":1}, {"two":2} ]}), content_type: 'application/json' }
+ }
+
+ client.post(url, data, {multipart: true}, function(err, resp, body){
+
+ console.log(err);
+ console.log("Got status code " + resp.statusCode)
+ console.log(body);
+ fs.unlink(filename);
+
+ });
+
+}
+
+switch(process.argv[2]){
+ case 'head':
+ simple_head();
+ break;
+ case 'get':
+ simple_get();
+ break;
+ case 'auth':
+ auth_get();
+ break;
+ case 'proxy':
+ proxy_get();
+ break;
+ case 'post':
+ simple_post(process.argv[3] || 'http://posttestserver.com/post.php');
+ break;
+ case 'multipart':
+ multipart_post(process.argv[3] || 'http://posttestserver.com/post.php?dir=example');
+ break;
+ case 'all':
+ simple_head();
+ simple_get();
+ auth_get();
+ proxy_get();
+ simple_post(process.argv[3] || 'http://posttestserver.com/post.php');
+ multipart_post(process.argv[3] || 'http://posttestserver.com/post.php?dir=example');
+ break;
+ default:
+ console.log("Usage: ./test.js [head|get|auth|proxy|multipart]")
+}