├── .npmignore ├── assets ├── reply_file_1.txt └── reply_file_2.txt.gz ├── .gitignore ├── .travis.yml ├── index.js ├── lib ├── mixin.js ├── socket.js ├── match_body.js ├── delayed_body.js ├── common.js ├── recorder.js ├── intercept.js ├── request_overrider.js └── scope.js ├── .jshintrc ├── tests ├── test_aws_dynamo.js ├── test_common.js ├── test_recorder.js └── test_intercept.js ├── package.json └── README.md /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .gitignore 3 | -------------------------------------------------------------------------------- /assets/reply_file_1.txt: -------------------------------------------------------------------------------- 1 | Hello from the file! -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | - "0.11" 5 | -------------------------------------------------------------------------------- /assets/reply_file_2.txt.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pose/nock/master/assets/reply_file_2.txt.gz -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var recorder = require('./lib/recorder') 2 | module.exports = require('./lib/scope'); 3 | 4 | module.exports.recorder = { 5 | rec : recorder.record 6 | , clear : recorder.clear 7 | , play : recorder.outputs 8 | }; 9 | module.exports.restore = recorder.restore; -------------------------------------------------------------------------------- /lib/mixin.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function clone(o) { 4 | return JSON.parse(JSON.stringify(o)); 5 | } 6 | 7 | function mixin(a, b) { 8 | if (! a) { a = {}; } 9 | if (! b) {b = {}; } 10 | a = clone(a); 11 | for(var prop in b) { 12 | a[prop] = b[prop]; 13 | } 14 | return a; 15 | } 16 | 17 | module.exports = mixin; -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "boss": true, 3 | "node": true, 4 | "strict": true, 5 | "white": true, 6 | "smarttabs": true, 7 | "maxlen": 80, 8 | "newcap": false, 9 | "undef": true, 10 | "unused": true, 11 | "onecase": true, 12 | "indent": 2, 13 | "sub": true, 14 | 15 | "laxcomma": true, 16 | "maxlen": 120, 17 | "asi": true, 18 | "loopfunc": true, 19 | "unused": false 20 | } 21 | -------------------------------------------------------------------------------- /lib/socket.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var EventEmitter = require('events').EventEmitter; 4 | 5 | module.exports = Socket; 6 | 7 | function Socket() { 8 | var socket = new EventEmitter(); 9 | 10 | socket.writable = true; 11 | 12 | socket.setNoDelay = noop; 13 | socket.setTimeout = noop; 14 | socket.setKeepAlive = noop; 15 | socket.destroy = noop; 16 | 17 | socket.getPeerCertificate = getPeerCertificate; 18 | 19 | return socket; 20 | } 21 | 22 | function noop() {} 23 | 24 | function getPeerCertificate() { 25 | return new Buffer((Math.random() * 10000 + Date.now()).toString()).toString('base64'); 26 | } -------------------------------------------------------------------------------- /tests/test_aws_dynamo.js: -------------------------------------------------------------------------------- 1 | var nock = require('../'); 2 | var AWS = require('aws-sdk'); 3 | var test = require('tap').test; 4 | 5 | test('works with dynamodb', function(t) { 6 | var done = false; 7 | var REGION = 'us-east-1'; 8 | 9 | AWS.config.update({ 10 | 'region': REGION, 11 | 'sslEnabled': true, 12 | 'accessKeyId': 'ACCESSKEYID', 13 | 'secretAccessKey': 'SECRETACCESSKEY' 14 | }); 15 | 16 | var db = new AWS.DynamoDB(); 17 | 18 | nock('https://dynamodb.' + REGION + '.amazonaws.com') 19 | .post('/') 20 | .reply(200, {}); 21 | 22 | db.getItem( 23 | { 24 | Key: { 25 | Artist: { 26 | S: 'Lady Gaga' 27 | } 28 | }, 29 | TableName: 'Music' 30 | }, 31 | function(err, resp) { 32 | if (err) throw err; 33 | t.deepEqual(resp, {}); 34 | t.end(); 35 | }); 36 | }); 37 | 38 | -------------------------------------------------------------------------------- /lib/match_body.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var deepEqual = require('assert').deepEqual; 4 | var qs = require('querystring'); 5 | 6 | module.exports = 7 | function matchBody(spec, body) { 8 | if (typeof spec === 'undefined') { 9 | return true; 10 | } 11 | var options = this || {}; 12 | 13 | if (Buffer.isBuffer(body)) { 14 | body = body.toString(); 15 | } 16 | 17 | // try to transform body to json 18 | var json; 19 | if (typeof spec === 'object') { 20 | try { json = JSON.parse(body);} catch(err) {} 21 | if (json !== undefined) { 22 | body = json; 23 | } 24 | else 25 | if ( 26 | (typeof spec === 'object') && 27 | options.headers 28 | ) 29 | { 30 | var contentType = options.headers['Content-Type'] || 31 | options.headers['content-type']; 32 | 33 | if (contentType.match(/application\/x-www-form-urlencoded/)) { 34 | body = qs.parse(body); 35 | } 36 | } 37 | } 38 | 39 | try { 40 | deepEqual(spec, body); 41 | return true; 42 | } catch(err) { 43 | return false; 44 | } 45 | 46 | }; 47 | -------------------------------------------------------------------------------- /tests/test_common.js: -------------------------------------------------------------------------------- 1 | 2 | var common = require('../lib/common') 3 | , tap = require('tap'); 4 | 5 | tap.test('isBinaryBuffer works', function(t) { 6 | 7 | // Returns false for non-buffers. 8 | t.false(common.isBinaryBuffer()); 9 | t.false(common.isBinaryBuffer('')); 10 | 11 | // Returns true for binary buffers. 12 | t.true(common.isBinaryBuffer(new Buffer('8001', 'hex'))); 13 | 14 | // Returns false for buffers containing strings. 15 | t.false(common.isBinaryBuffer(new Buffer('8001', 'utf8'))); 16 | 17 | t.end(); 18 | 19 | }); 20 | 21 | tap.test('headersFieldNamesToLowerCase works', function(t) { 22 | 23 | var headers = { 24 | 'HoSt': 'example.com', 25 | 'Content-typE': 'plain/text' 26 | }; 27 | 28 | var lowerCaseHeaders = common.headersFieldNamesToLowerCase(headers); 29 | 30 | t.equal(headers.HoSt, lowerCaseHeaders.host); 31 | t.equal(headers['Content-typE'], lowerCaseHeaders['content-type']); 32 | t.end(); 33 | 34 | }); 35 | 36 | tap.test('headersFieldNamesToLowerCase throws on conflicting keys', function(t) { 37 | 38 | var headers = { 39 | 'HoSt': 'example.com', 40 | 'HOST': 'example.com' 41 | }; 42 | 43 | try { 44 | common.headersFieldNamesToLowerCase(headers); 45 | } catch(e) { 46 | t.equal(e.toString(), 'Error: Failed to convert header keys to lower case due to field name conflict: host'); 47 | t.end(); 48 | } 49 | 50 | }); 51 | 52 | tap.test('deleteHeadersField deletes fields with case-insensitive field names', function(t) { 53 | 54 | var headers = { 55 | HoSt: 'example.com', 56 | 'Content-typE': 'plain/text' 57 | }; 58 | 59 | t.true(headers.HoSt); 60 | t.true(headers['Content-typE']); 61 | 62 | common.deleteHeadersField(headers, 'HOST'); 63 | common.deleteHeadersField(headers, 'CONTENT-TYPE'); 64 | 65 | t.false(headers.HoSt); 66 | t.false(headers['Content-typE']); 67 | t.end(); 68 | 69 | }); 70 | 71 | -------------------------------------------------------------------------------- /lib/delayed_body.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Creates a stream which becomes the response body of the interceptor when a 5 | * delay is set. The stream outputs the intended body and EOF after the delay. 6 | * 7 | * @param {String|Buffer|Stream} body - the body to write/pipe out 8 | * @param {Integer} ms - The delay in milliseconds 9 | * @constructor 10 | */ 11 | module.exports = DelayedBody; 12 | 13 | var Transform = require('stream').Transform; 14 | var EventEmitter = require('events').EventEmitter; 15 | var noop = function () {}; 16 | var util = require('util'); 17 | 18 | function isStream(obj) { 19 | var is = obj && (typeof a !== 'string') && (! Buffer.isBuffer(obj)) && (typeof obj.setEncoding === 'function'); 20 | return is; 21 | } 22 | 23 | if (!Transform) { 24 | // for barebones compatibility for node < 0.10 25 | var FakeTransformStream = function () { 26 | EventEmitter.call(this); 27 | }; 28 | util.inherits(FakeTransformStream, EventEmitter); 29 | FakeTransformStream.prototype.pause = noop; 30 | FakeTransformStream.prototype.resume = noop; 31 | FakeTransformStream.prototype.setEncoding = noop; 32 | FakeTransformStream.prototype.write = function (chunk, encoding) { 33 | var self = this; 34 | process.nextTick(function () { 35 | self.emit('data', chunk, encoding); 36 | }); 37 | }; 38 | FakeTransformStream.prototype.end = function (chunk) { 39 | var self = this; 40 | if (chunk) { 41 | self.write(chunk); 42 | } 43 | process.nextTick(function () { 44 | self.emit('end'); 45 | }); 46 | }; 47 | 48 | Transform = FakeTransformStream; 49 | } 50 | 51 | function DelayedBody(ms, body) { 52 | Transform.call(this); 53 | 54 | var self = this; 55 | var data = ''; 56 | var ended = false; 57 | 58 | if (isStream(body)) { 59 | body.on('data', function (chunk) { 60 | data += Buffer.isBuffer(chunk) ? chunk.toString() : chunk; 61 | }); 62 | 63 | body.once('end', function () { 64 | ended = true; 65 | }); 66 | 67 | body.resume(); 68 | } 69 | 70 | setTimeout(function () { 71 | if (isStream(body) && !ended) { 72 | body.once('end', function () { 73 | self.end(data); 74 | }); 75 | } else { 76 | self.end(data || body); 77 | } 78 | }, ms); 79 | } 80 | util.inherits(DelayedBody, Transform); 81 | 82 | DelayedBody.prototype._transform = function (chunk, encoding, cb) { 83 | this.push(chunk); 84 | process.nextTick(cb); 85 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nock", 3 | "description": "HTTP Server mocking for Node.js", 4 | "tags": [ 5 | "Mock", 6 | "HTTP", 7 | "testing", 8 | "isolation" 9 | ], 10 | "version": "0.48.1", 11 | "author": "Pedro Teixeira ", 12 | "contributors": [ 13 | { 14 | "name": "Roly Fentanes" 15 | }, 16 | { 17 | "name": "Alexander Simmerl" 18 | }, 19 | { 20 | "name": "Pedro Teixeira" 21 | }, 22 | { 23 | "name": "Nuno Job" 24 | }, 25 | { 26 | "name": "Ian Young" 27 | }, 28 | { 29 | "name": "nilsbunger" 30 | }, 31 | { 32 | "name": "bacchusrx", 33 | "email": "bacchusrx@eightstar.ca" 34 | }, 35 | { 36 | "name": "Fabiano França" 37 | }, 38 | { 39 | "name": "Sascha Drews" 40 | }, 41 | { 42 | "name": "Mike Swift" 43 | }, 44 | { 45 | "name": "James Herdman" 46 | }, 47 | { 48 | "name": "David Björklund" 49 | }, 50 | { 51 | "name": "Andrew Kramolisch" 52 | }, 53 | { 54 | "name": "Balazs Nagy" 55 | }, 56 | { 57 | "name": "Brian J Brennan" 58 | }, 59 | { 60 | "name": "Attila Incze" 61 | }, 62 | { 63 | "name": "Mac Angell" 64 | }, 65 | { 66 | "name": "Tom Hosford" 67 | }, 68 | { 69 | "name": "Aurélien Thieriot" 70 | }, 71 | { 72 | "name": "Alex Zylman" 73 | }, 74 | { 75 | "name": "Celestino Gomes", 76 | "email": "contact@tinogomes.com" 77 | }, 78 | { 79 | "name": "David Rousselie" 80 | }, 81 | { 82 | "name": "spenceralger" 83 | }, 84 | { 85 | "name": "Ivan Erceg", 86 | "url": "https://github.com/ierceg", 87 | "email": "ivan@softwaremarbles.com" 88 | } 89 | ], 90 | "repository": { 91 | "type": "git", 92 | "url": "http://github.com/pgte/nock.git" 93 | }, 94 | "bugs": { 95 | "url": "http://github.com/pgte/nock/issues" 96 | }, 97 | "engines": [ 98 | "node >= 0.10.0" 99 | ], 100 | "main": "./index", 101 | "dependencies": { 102 | "propagate": "0.3.x", 103 | "lodash": "2.4.1", 104 | "debug": "^1.0.4" 105 | }, 106 | "devDependencies": { 107 | "aws-sdk": "^2.0.15", 108 | "hyperquest": "^0.3.0", 109 | "jshint": "^2.5.6", 110 | "needle": "^0.7.1", 111 | "pre-commit": "0.0.9", 112 | "request": "*", 113 | "restify": "^2.8.1", 114 | "superagent": "~0.15.7", 115 | "tap": "*" 116 | }, 117 | "scripts": { 118 | "test": "node node_modules/tap/bin/tap.js --dd tests", 119 | "jshint": "jshint lib/*.js" 120 | }, 121 | "pre-commit": [ 122 | "jshint", 123 | "test" 124 | ], 125 | "license": "MIT" 126 | } 127 | -------------------------------------------------------------------------------- /lib/common.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('lodash'); 4 | var debug = require('debug')('nock.common'); 5 | 6 | /** 7 | * Normalizes the request options so that it always has `host` property. 8 | * 9 | * @param {Object} options - a parsed options object of the request 10 | */ 11 | var normalizeRequestOptions = function(options) { 12 | options.proto = options.proto || (options._https_ ? 'https': 'http'); 13 | options.port = options.port || ((options.proto === 'http') ? 80 : 443); 14 | if (options.host) { 15 | options.hostname = options.hostname || options.host.split(':')[0]; 16 | } 17 | options.host = (options.hostname || 'localhost') + ':' + options.port; 18 | 19 | /// lowercase host names 20 | ['hostname', 'host'].forEach(function(attr) { 21 | if (options[attr]) { 22 | options[attr] = options[attr].toLowerCase(); 23 | } 24 | }) 25 | 26 | return options; 27 | }; 28 | 29 | /** 30 | * Returns true if the data contained in buffer is binary which in this case means 31 | * that it cannot be reconstructed from its utf8 representation. 32 | * 33 | * @param {Object} buffer - a Buffer object 34 | */ 35 | var isBinaryBuffer = function(buffer) { 36 | 37 | if(!Buffer.isBuffer(buffer)) { 38 | return false; 39 | } 40 | 41 | // Test if the buffer can be reconstructed verbatim from its utf8 encoding. 42 | var utfEncodedBuffer = buffer.toString('utf8'); 43 | var reconstructedBuffer = new Buffer(utfEncodedBuffer, 'utf8'); 44 | var compareBuffers = function(lhs, rhs) { 45 | if(lhs.length !== rhs.length) { 46 | return false; 47 | } 48 | 49 | for(var i = 0; i < lhs.length; ++i) { 50 | if(lhs[i] !== rhs[i]) { 51 | return false; 52 | } 53 | } 54 | 55 | return true; 56 | }; 57 | 58 | // If the buffers are *not* equal then this is a "binary buffer" 59 | // meaning that it cannot be faitfully represented in utf8. 60 | return !compareBuffers(buffer, reconstructedBuffer); 61 | 62 | }; 63 | 64 | /** 65 | * If the chunks are Buffer objects then it returns a single Buffer object with the data from all the chunks. 66 | * If the chunks are strings then it returns a single string value with data from all the chunks. 67 | * 68 | * @param {Array} chunks - an array of Buffer objects or strings 69 | */ 70 | var mergeChunks = function(chunks) { 71 | 72 | if(_.isEmpty(chunks)) { 73 | return new Buffer(0); 74 | } 75 | 76 | // We assume that all chunks are Buffer objects if the first is buffer object. 77 | var areBuffers = Buffer.isBuffer(_.first(chunks)); 78 | 79 | if(!areBuffers) { 80 | // When the chunks are not buffers we assume that they are strings. 81 | return chunks.join(''); 82 | } 83 | 84 | // Merge all the buffers into a single Buffer object. 85 | return Buffer.concat(chunks); 86 | 87 | }; 88 | 89 | // Array where all information about all the overridden requests are held. 90 | var requestOverride = []; 91 | 92 | /** 93 | * Overrides the current `request` function of `http` and `https` modules with 94 | * our own version which intercepts issues HTTP/HTTPS requests and forwards them 95 | * to the given `newRequest` function. 96 | * 97 | * @param {Function} newRequest - a function handling requests; it accepts four arguments: 98 | * - proto - a string with the overridden module's protocol name (either `http` or `https`) 99 | * - overriddenRequest - the overridden module's request function already bound to module's object 100 | * - options - the options of the issued request 101 | * - callback - the callback of the issued request 102 | */ 103 | var overrideRequests = function(newRequest) { 104 | debug('overriding requests'); 105 | 106 | ['http', 'https'].forEach(function(proto) { 107 | debug('- overriding request for', proto); 108 | 109 | var moduleName = proto, // 1 to 1 match of protocol and module is fortunate :) 110 | module = require(moduleName), 111 | overriddenRequest = module.request; 112 | 113 | if(requestOverride[moduleName]) { 114 | throw new Error('Module\'s request already overridden for ' + moduleName + ' protocol.'); 115 | } 116 | 117 | // Store the properties of the overridden request so that it can be restored later on. 118 | requestOverride[moduleName] = { 119 | module: module, 120 | request: overriddenRequest 121 | }; 122 | 123 | module.request = function(options, callback) { 124 | return newRequest(proto, overriddenRequest.bind(module), options, callback); 125 | }; 126 | 127 | debug('- overridden request for', proto); 128 | }); 129 | }; 130 | 131 | /** 132 | * Restores `request` function of `http` and `https` modules to values they 133 | * held before they were overridden by us. 134 | */ 135 | var restoreOverriddenRequests = function() { 136 | debug('restoring requests'); 137 | 138 | // Restore any overridden requests. 139 | _(requestOverride).keys().each(function(proto) { 140 | debug('- restoring request for', proto); 141 | 142 | var override = requestOverride[proto]; 143 | if(override) { 144 | override.module.request = override.request; 145 | debug('- restored request for', proto); 146 | } 147 | }); 148 | requestOverride = []; 149 | }; 150 | 151 | function stringifyRequest(options, body) { 152 | var method = options.method || 'GET'; 153 | 154 | if (body && typeof(body) !== 'string') { 155 | body = body.toString(); 156 | } 157 | 158 | var port = options.port; 159 | if (! port) port = (options.proto == 'https' ? '443' : '80'); 160 | 161 | if (options.proto == 'https' && port == '443' || 162 | options.proto == 'http' && port == '80') { 163 | port = ''; 164 | } 165 | 166 | if (port) port = ':' + port; 167 | 168 | return method + ' ' + options.proto + '://' + options.hostname + port + options.path + ' ' + body; 169 | } 170 | 171 | function isContentEncoded(headers) { 172 | if(!headers) { 173 | return false; 174 | } 175 | 176 | var contentEncoding = headers['content-encoding']; 177 | return _.isString(contentEncoding) && contentEncoding !== ''; 178 | } 179 | 180 | var headersFieldNamesToLowerCase = function(headers) { 181 | if(!_.isObject(headers)) { 182 | return headers; 183 | } 184 | 185 | // For each key in the headers, delete its value and reinsert it with lower-case key. 186 | // Keys represent headers field names. 187 | var lowerCaseHeaders = {}; 188 | _(headers).keys().each(function(fieldName) { 189 | var lowerCaseFieldName = fieldName.toLowerCase(); 190 | if(!_.isUndefined(lowerCaseHeaders[lowerCaseFieldName])) { 191 | throw new Error('Failed to convert header keys to lower case due to field name conflict: ' + lowerCaseFieldName); 192 | } 193 | lowerCaseHeaders[lowerCaseFieldName] = headers[fieldName]; 194 | }); 195 | 196 | return lowerCaseHeaders; 197 | }; 198 | 199 | /** 200 | * Deletes the given `fieldName` property from `headers` object by performing 201 | * case-insensitive search through keys. 202 | * 203 | * @headers {Object} headers - object of header field names and values 204 | * @fieldName {String} field name - string with the case-insensitive field name 205 | */ 206 | var deleteHeadersField = function(headers, fieldNameToDelete) { 207 | 208 | if(!_.isObject(headers) || !_.isString(fieldNameToDelete)) { 209 | return; 210 | } 211 | 212 | var lowerCaseFieldNameToDelete = fieldNameToDelete.toLowerCase(); 213 | 214 | // Search through the headers and delete all values whose field name matches the given field name. 215 | _(headers).keys().each(function(fieldName) { 216 | var lowerCaseFieldName = fieldName.toLowerCase(); 217 | if(lowerCaseFieldName === lowerCaseFieldNameToDelete) { 218 | delete headers[fieldName]; 219 | // We don't stop here but continue in order to remove *all* matching field names 220 | // (even though if seen regorously there shouldn't be any) 221 | } 222 | }); 223 | 224 | }; 225 | 226 | exports.normalizeRequestOptions = normalizeRequestOptions; 227 | exports.isBinaryBuffer = isBinaryBuffer; 228 | exports.mergeChunks = mergeChunks; 229 | exports.overrideRequests = overrideRequests; 230 | exports.restoreOverriddenRequests = restoreOverriddenRequests; 231 | exports.stringifyRequest = stringifyRequest; 232 | exports.isContentEncoded = isContentEncoded; 233 | exports.headersFieldNamesToLowerCase = headersFieldNamesToLowerCase; 234 | exports.deleteHeadersField = deleteHeadersField; 235 | -------------------------------------------------------------------------------- /lib/recorder.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var inspect = require('util').inspect; 4 | var parse = require('url').parse; 5 | var common = require('./common'); 6 | var intercept = require('./intercept'); 7 | var debug = require('debug')('nock.recorder'); 8 | var _ = require('lodash'); 9 | 10 | var SEPARATOR = '\n<<<<<<-- cut here -->>>>>>\n'; 11 | var recordingInProgress = false; 12 | var outputs = []; 13 | 14 | function getScope(options) { 15 | 16 | common.normalizeRequestOptions(options); 17 | 18 | var scope = []; 19 | if (options._https_) { 20 | scope.push('https://'); 21 | } else { 22 | scope.push('http://'); 23 | } 24 | 25 | scope.push(options.host); 26 | 27 | // If a non-standard port wasn't specified in options.host, include it from options.port. 28 | if(options.host.indexOf(':') === -1 && 29 | options.port && 30 | ((options._https_ && options.port.toString() !== '443') || 31 | (!options._https_ && options.port.toString() !== '80'))) { 32 | scope.push(':'); 33 | scope.push(options.port); 34 | } 35 | 36 | return scope.join(''); 37 | 38 | } 39 | 40 | function getMethod(options) { 41 | 42 | return (options.method || 'GET'); 43 | 44 | } 45 | 46 | var getBodyFromChunks = function(chunks, headers) { 47 | 48 | // If we have headers and there is content-encoding it means that 49 | // the body shouldn't be merged but instead persisted as an array 50 | // of hex strings so that the responses can be mocked one by one. 51 | if(common.isContentEncoded(headers)) { 52 | return _.map(chunks, function(chunk) { 53 | if(!Buffer.isBuffer(chunk)) { 54 | throw new Error('content-encoded responses must all be binary buffers'); 55 | } 56 | 57 | return chunk.toString('hex'); 58 | }); 59 | } 60 | 61 | var mergedBuffer = common.mergeChunks(chunks); 62 | 63 | // The merged buffer can be one of three things: 64 | // 1. A binary buffer which then has to be recorded as a hex string. 65 | // 2. A string buffer which represents a JSON object. 66 | // 3. A string buffer which doesn't represent a JSON object. 67 | 68 | if(common.isBinaryBuffer(mergedBuffer)) { 69 | return mergedBuffer.toString('hex'); 70 | } else { 71 | var maybeStringifiedJson = mergedBuffer.toString('utf8'); 72 | try { 73 | return JSON.parse(maybeStringifiedJson); 74 | } catch(err) { 75 | return maybeStringifiedJson; 76 | } 77 | } 78 | 79 | }; 80 | 81 | function generateRequestAndResponseObject(req, bodyChunks, options, res, dataChunks) { 82 | options.path = req.path; 83 | return { 84 | scope: getScope(options), 85 | method: getMethod(options), 86 | path: options.path, 87 | body: getBodyFromChunks(bodyChunks), 88 | status: res.statusCode, 89 | response: getBodyFromChunks(dataChunks, res.headers), 90 | headers: res.headers, 91 | reqheaders: req._headers 92 | }; 93 | 94 | } 95 | 96 | function generateRequestAndResponse(req, bodyChunks, options, res, dataChunks) { 97 | 98 | var requestBody = getBodyFromChunks(bodyChunks); 99 | var responseBody = getBodyFromChunks(dataChunks, res.headers); 100 | 101 | var ret = []; 102 | ret.push('\nnock(\''); 103 | ret.push(getScope(options)); 104 | ret.push('\')\n'); 105 | ret.push(' .'); 106 | ret.push(getMethod(options).toLowerCase()); 107 | ret.push('(\''); 108 | ret.push(options.path); 109 | ret.push("'"); 110 | if (requestBody) { 111 | ret.push(', '); 112 | ret.push(JSON.stringify(requestBody)); 113 | } 114 | ret.push(")\n"); 115 | if (req.headers) { 116 | for (var k in req.headers) { 117 | ret.push(' .matchHeader(' + JSON.stringify(k) + ', ' + JSON.stringify(req.headers[k]) + ')\n'); 118 | } 119 | } 120 | 121 | ret.push(' .reply('); 122 | ret.push(res.statusCode.toString()); 123 | ret.push(', '); 124 | ret.push(JSON.stringify(responseBody)); 125 | if (res.headers) { 126 | ret.push(', '); 127 | ret.push(inspect(res.headers)); 128 | } 129 | ret.push(');\n'); 130 | 131 | return ret.join(''); 132 | } 133 | 134 | // This module variable is used to identify a unique recording ID in order to skip 135 | // spurious requests that sometimes happen. This problem has been, so far, 136 | // exclusively detected in nock's unit testing where 'checks if callback is specified' 137 | // interferes with other tests as its t.end() is invoked without waiting for request 138 | // to finish (which is the point of the test). 139 | var currentRecordingId = 0; 140 | 141 | function record(rec_options) { 142 | 143 | // Set the new current recording ID and capture its value in this instance of record(). 144 | currentRecordingId = currentRecordingId + 1; 145 | var thisRecordingId = currentRecordingId; 146 | 147 | debug('start recording', thisRecordingId, JSON.stringify(rec_options)); 148 | 149 | // Trying to start recording with recording already in progress implies an error 150 | // in the recording configuration (double recording makes no sense and used to lead 151 | // to duplicates in output) 152 | if(recordingInProgress) { 153 | throw new Error('Nock recording already in progress'); 154 | } 155 | 156 | recordingInProgress = true; 157 | 158 | // Originaly the parameters was a dont_print boolean flag. 159 | // To keep the existing code compatible we take that case into account. 160 | var optionsIsObject = typeof rec_options === 'object'; 161 | var dont_print = (typeof rec_options === 'boolean' && rec_options) || 162 | (optionsIsObject && rec_options.dont_print); 163 | var output_objects = optionsIsObject && rec_options.output_objects; 164 | var enable_reqheaders_recording = optionsIsObject && rec_options.enable_reqheaders_recording; 165 | var logging = (optionsIsObject && rec_options.logging) || console.log; 166 | 167 | debug(thisRecordingId, 'restoring overridden requests before new overrides'); 168 | // To preserve backward compatibility (starting recording wasn't throwing if nock was already active) 169 | // we restore any requests that may have been overridden by other parts of nock (e.g. intercept) 170 | // NOTE: This is hacky as hell but it keeps the backward compatibility *and* allows correct 171 | // behavior in the face of other modules also overriding ClientRequest. 172 | common.restoreOverriddenRequests(); 173 | // We restore ClientRequest as it messes with recording of modules that also override ClientRequest (e.g. xhr2) 174 | intercept.restoreOverriddenClientRequest(); 175 | 176 | // We override the requests so that we can save information on them before executing. 177 | common.overrideRequests(function(proto, overriddenRequest, options, callback) { 178 | 179 | var bodyChunks = []; 180 | 181 | // Node 0.11 https.request calls http.request -- don't want to record things 182 | // twice. 183 | if (options._recording) { 184 | return overriddenRequest(options, callback); 185 | } 186 | options._recording = true; 187 | 188 | var req = overriddenRequest(options, function(res) { 189 | 190 | debug(thisRecordingId, 'intercepting', proto, 'request to record'); 191 | 192 | if (typeof options === 'string') { 193 | options = parse(options); 194 | } 195 | 196 | var dataChunks = []; 197 | 198 | res.on('data', function(data) { 199 | debug(thisRecordingId, 'new', proto, 'data chunk'); 200 | dataChunks.push(data); 201 | }); 202 | 203 | if (proto === 'https') { 204 | options._https_ = true; 205 | } 206 | 207 | res.once('end', function() { 208 | debug(thisRecordingId, proto, 'intercepted request ended'); 209 | 210 | var out; 211 | if(output_objects) { 212 | out = generateRequestAndResponseObject(req, bodyChunks, options, res, dataChunks); 213 | if(out.reqheaders) { 214 | // We never record user-agent headers as they are worse than useless - 215 | // they actually make testing more difficult without providing any benefit (see README) 216 | common.deleteHeadersField(out.reqheaders, 'user-agent'); 217 | 218 | // Remove request headers completely unless it was explicitly enabled by the user (see README) 219 | if(!enable_reqheaders_recording) { 220 | delete out.reqheaders; 221 | } 222 | } 223 | } else { 224 | out = generateRequestAndResponse(req, bodyChunks, options, res, dataChunks); 225 | } 226 | 227 | // Check that the request was made during the current recording. 228 | // If it hasn't then skip it. There is no other simple way to handle 229 | // this as it depends on the timing of requests and responses. Throwing 230 | // will make some recordings/unit tests faily randomly depending on how 231 | // fast/slow the response arrived. 232 | // If you are seeing this error then you need to make sure that all 233 | // the requests made during a single recording session finish before 234 | // ending the same recording session. 235 | if(thisRecordingId !== currentRecordingId) { 236 | debug('skipping recording of an out-of-order request', out); 237 | return; 238 | } 239 | 240 | outputs.push(out); 241 | 242 | if (!dont_print) { 243 | logging(SEPARATOR + out + SEPARATOR); 244 | } 245 | }); 246 | 247 | if (callback) { 248 | callback(res, options, callback); 249 | } 250 | 251 | }); 252 | 253 | var oldWrite = req.write; 254 | req.write = function(data) { 255 | if ('undefined' !== typeof(data)) { 256 | if (data) { 257 | debug(thisRecordingId, 'new', proto, 'body chunk'); 258 | bodyChunks.push(data); 259 | } 260 | oldWrite.call(req, data); 261 | } 262 | }; 263 | 264 | return req; 265 | }); 266 | } 267 | 268 | // Restores *all* the overridden http/https modules' properties. 269 | function restore() { 270 | debug(currentRecordingId, 'restoring all the overridden http/https properties'); 271 | 272 | common.restoreOverriddenRequests(); 273 | intercept.restoreOverriddenClientRequest(); 274 | recordingInProgress = false; 275 | } 276 | 277 | function clear() { 278 | outputs = []; 279 | } 280 | 281 | exports.record = record; 282 | exports.outputs = function() { 283 | return outputs; 284 | }; 285 | exports.restore = restore; 286 | exports.clear = clear; 287 | -------------------------------------------------------------------------------- /lib/intercept.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @module nock/intercepts 5 | */ 6 | 7 | var RequestOverrider = require('./request_overrider'), 8 | common = require('./common'), 9 | url = require('url'), 10 | inherits = require('util').inherits, 11 | http = require('http'), 12 | parse = require('url').parse, 13 | _ = require('lodash'), 14 | debug = require('debug')('nock.intercept'); 15 | 16 | /** 17 | * @name NetConnectNotAllowedError 18 | * @private 19 | * @desc Error trying to make a connection when disabled external access. 20 | * @class 21 | * @example 22 | * nock.disableNetConnect(); 23 | * http.get('http://zombo.com'); 24 | * // throw NetConnectNotAllowedError 25 | */ 26 | function NetConnectNotAllowedError(host) { 27 | Error.call(this); 28 | 29 | this.name = 'NetConnectNotAllowedError'; 30 | this.message = 'Nock: Not allow net connect for "' + host + '"'; 31 | 32 | Error.captureStackTrace(this, this.constructor); 33 | } 34 | 35 | inherits(NetConnectNotAllowedError, Error); 36 | 37 | var allInterceptors = {}, 38 | allowNetConnect = /.*/; 39 | 40 | /** 41 | * Enabled real request. 42 | * @public 43 | * @param {String|RegExp} matcher=RegExp.new('.*') Expression to match 44 | * @example 45 | * // Enables all real requests 46 | * nock.enableNetConnect(); 47 | * @example 48 | * // Enables real requests for url that matches google 49 | * nock.enableNetConnect('google'); 50 | * @example 51 | * // Enables real requests for url that matches google and amazon 52 | * nock.enableNetConnect(/(google|amazon)/); 53 | */ 54 | function enableNetConnect(matcher) { 55 | if (typeof matcher === 'string') { 56 | allowNetConnect = new RegExp(matcher); 57 | } else if (typeof matcher === 'object' && typeof matcher.test === 'function') { 58 | allowNetConnect = matcher; 59 | } else { 60 | allowNetConnect = /.*/; 61 | } 62 | } 63 | 64 | function isEnabledForNetConnect(options) { 65 | common.normalizeRequestOptions(options); 66 | 67 | return allowNetConnect && allowNetConnect.test(options.host); 68 | } 69 | 70 | /** 71 | * Disable all real requests. 72 | * @public 73 | * @param {String|RegExp} matcher=RegExp.new('.*') Expression to match 74 | * @example 75 | * nock.disableNetConnect(); 76 | */ 77 | function disableNetConnect() { 78 | allowNetConnect = false; 79 | } 80 | 81 | function isOn() { 82 | return !isOff(); 83 | } 84 | 85 | function isOff() { 86 | return process.env.NOCK_OFF === 'true'; 87 | } 88 | 89 | function add(key, interceptor, scope, scopeOptions, host) { 90 | if (! allInterceptors.hasOwnProperty(key)) { 91 | allInterceptors[key] = []; 92 | } 93 | interceptor.__nock_scope = scope; 94 | 95 | // We need scope's key and scope options for scope filtering function (if defined) 96 | interceptor.__nock_scopeKey = key; 97 | interceptor.__nock_scopeOptions = scopeOptions; 98 | // We need scope's host for setting correct request headers for filtered scopes. 99 | interceptor.__nock_scopeHost = host; 100 | 101 | allInterceptors[key].push(interceptor); 102 | } 103 | 104 | function remove(interceptor) { 105 | 106 | if (interceptor.__nock_scope.shouldPersist()) { 107 | return; 108 | } 109 | 110 | interceptor.counter -= 1; 111 | if (interceptor.counter > 0) { 112 | return; 113 | } 114 | 115 | var key = interceptor._key.split(' '), 116 | u = url.parse(key[1]), 117 | hostKey = u.protocol + '//' + u.host, 118 | interceptors = allInterceptors[hostKey], 119 | thisInterceptor; 120 | 121 | if (interceptors) { 122 | for(var i = 0; i < interceptors.length; i++) { 123 | thisInterceptor = interceptors[i]; 124 | if (thisInterceptor === interceptor) { 125 | interceptors.splice(i, 1); 126 | break; 127 | } 128 | } 129 | 130 | } 131 | } 132 | 133 | function removeAll() { 134 | allInterceptors = {}; 135 | } 136 | 137 | function hasHadInterceptors(options) { 138 | var basePath, 139 | isBasePathMatched, 140 | isScopedMatched; 141 | 142 | common.normalizeRequestOptions(options); 143 | 144 | basePath = options.proto + '://' + options.host; 145 | 146 | debug('looking for interceptors for basepath'); 147 | 148 | _.each(allInterceptors, function(interceptor, key) { 149 | if (key === basePath) { 150 | isBasePathMatched = true; 151 | 152 | // false to short circuit the .each 153 | return false; 154 | } 155 | 156 | _.each(interceptor, function(scope) { 157 | var filteringScope = scope.__nock_scopeOptions.filteringScope; 158 | 159 | // If scope filtering function is defined and returns a truthy value 160 | // then we have to treat this as a match. 161 | if (filteringScope && filteringScope(basePath)) { 162 | debug('found matching scope interceptor'); 163 | 164 | // Keep the filtered scope (its key) to signal the rest of the module 165 | // that this wasn't an exact but filtered match. 166 | scope.__nock_filteredScope = scope.__nock_scopeKey; 167 | isScopedMatched = true; 168 | // Break out of _.each for scopes. 169 | return false; 170 | } 171 | }); 172 | 173 | // Returning falsy value here (which will happen if we have found our matching interceptor) 174 | // will break out of _.each for all interceptors. 175 | return !isScopedMatched; 176 | }); 177 | 178 | return (isScopedMatched || isBasePathMatched); 179 | } 180 | 181 | function interceptorsFor(options) { 182 | var basePath; 183 | 184 | common.normalizeRequestOptions(options); 185 | 186 | basePath = options.proto + '://' + options.host; 187 | 188 | debug('filtering interceptors for basepath', basePath); 189 | 190 | // First try to use filteringScope if any of the interceptors has it defined. 191 | var matchingInterceptor; 192 | _.each(allInterceptors, function(interceptor, key) { 193 | _.each(interceptor, function(scope) { 194 | var filteringScope = scope.__nock_scopeOptions.filteringScope; 195 | 196 | // If scope filtering function is defined and returns a truthy value 197 | // then we have to treat this as a match. 198 | if(filteringScope && filteringScope(basePath)) { 199 | debug('found matching scope interceptor'); 200 | 201 | // Keep the filtered scope (its key) to signal the rest of the module 202 | // that this wasn't an exact but filtered match. 203 | scope.__nock_filteredScope = scope.__nock_scopeKey; 204 | matchingInterceptor = interceptor; 205 | // Break out of _.each for scopes. 206 | return false; 207 | } 208 | }); 209 | 210 | // Returning falsy value here (which will happen if we have found our matching interceptor) 211 | // will break out of _.each for all interceptors. 212 | return !matchingInterceptor; 213 | }); 214 | 215 | if(matchingInterceptor) { 216 | return matchingInterceptor; 217 | } 218 | 219 | return allInterceptors[basePath] || []; 220 | } 221 | 222 | function removeInterceptor(options) { 223 | var baseUrl, key, method; 224 | 225 | common.normalizeRequestOptions(options); 226 | baseUrl = options.proto + '://' + options.host; 227 | 228 | if (allInterceptors[baseUrl].length > 0) { 229 | if (options.path) { 230 | method = options.method && options.method.toUpperCase() || 'GET'; 231 | key = method + ' ' + baseUrl + (options.path || '/'); 232 | 233 | for (var i = 0; i < allInterceptors[baseUrl].length; i++) { 234 | if (allInterceptors[baseUrl][i]._key === key) { 235 | allInterceptors[baseUrl].splice(i, 1); 236 | break; 237 | } 238 | } 239 | } else { 240 | allInterceptors[baseUrl].length = 0; 241 | } 242 | 243 | return true; 244 | } 245 | 246 | return false; 247 | } 248 | 249 | // Variable where we keep the ClientRequest we have overridden 250 | // (which might or might not be node's original http.ClientRequest) 251 | var originalClientRequest; 252 | 253 | function ErroringClientRequest(error) { 254 | http.OutgoingMessage.call(this); 255 | process.nextTick(function() { 256 | this.emit('error', error); 257 | }.bind(this)); 258 | } 259 | 260 | inherits(ErroringClientRequest, http.ClientRequest); 261 | 262 | function overrideClientRequest() { 263 | debug('Overriding ClientRequest'); 264 | 265 | if(originalClientRequest) { 266 | throw new Error('Nock already overrode http.ClientRequest'); 267 | } 268 | 269 | // ----- Extending http.ClientRequest 270 | 271 | // Define the overriding client request that nock uses internally. 272 | function OverriddenClientRequest(options, cb) { 273 | http.OutgoingMessage.call(this); 274 | 275 | if (hasHadInterceptors(options)) { 276 | // Filter the interceptors per request options. 277 | var interceptors = interceptorsFor(options); 278 | 279 | debug('using', interceptors.length, 'interceptors'); 280 | 281 | // Use filtered interceptors to intercept requests. 282 | var overrider = RequestOverrider(this, options, interceptors, remove, cb); 283 | for(var propName in overrider) { 284 | if (overrider.hasOwnProperty(propName)) { 285 | this[propName] = overrider[propName]; 286 | } 287 | } 288 | } else { 289 | debug('falling back to original ClientRequest'); 290 | 291 | // Fallback to original ClientRequest if nock is off or the net connection is enabled. 292 | if(isOff() || isEnabledForNetConnect(options)) { 293 | originalClientRequest.apply(this, arguments); 294 | } else { 295 | process.nextTick(function () { 296 | var error = new NetConnectNotAllowedError(options.host); 297 | this.emit('error', error); 298 | }.bind(this)); 299 | } 300 | } 301 | } 302 | inherits(OverriddenClientRequest, http.ClientRequest); 303 | 304 | // Override the http module's request but keep the original so that we can use it and later restore it. 305 | // NOTE: We only override http.ClientRequest as https module also uses it. 306 | originalClientRequest = http.ClientRequest; 307 | http.ClientRequest = OverriddenClientRequest; 308 | 309 | debug('ClientRequest overridden'); 310 | } 311 | 312 | function restoreOverriddenClientRequest() { 313 | debug('restoring overriden ClientRequest'); 314 | 315 | // Restore the ClientRequest we have overridden. 316 | if(!originalClientRequest) { 317 | debug('- ClientRequest was not overridden'); 318 | } else { 319 | http.ClientRequest = originalClientRequest; 320 | originalClientRequest = undefined; 321 | 322 | debug('- ClientRequest restored'); 323 | } 324 | } 325 | 326 | function isActive() { 327 | 328 | // If ClientRequest has been overwritten by Nock then originalClientRequest is not undefined. 329 | // This means that Nock has been activated. 330 | return !_.isUndefined(originalClientRequest); 331 | 332 | } 333 | 334 | function activate() { 335 | 336 | if(originalClientRequest) { 337 | throw new Error('Nock already active'); 338 | } 339 | 340 | overrideClientRequest(); 341 | 342 | // ----- Overriding http.request and https.request: 343 | 344 | common.overrideRequests(function(proto, overriddenRequest, options, callback) { 345 | 346 | // NOTE: overriddenRequest is already bound to its module. 347 | 348 | var req, 349 | res; 350 | 351 | if (typeof options === 'string') { 352 | options = parse(options); 353 | } 354 | options.proto = proto; 355 | 356 | if (isOn() && hasHadInterceptors(options)) { 357 | 358 | var interceptors, 359 | matches = false, 360 | allowUnmocked = false; 361 | 362 | interceptors = interceptorsFor(options); 363 | 364 | interceptors.forEach(function(interceptor) { 365 | if (! allowUnmocked && interceptor.options.allowUnmocked) { allowUnmocked = true; } 366 | if (interceptor.matchIndependentOfBody(options)) { matches = true; } 367 | }); 368 | 369 | if (! matches && allowUnmocked) { 370 | return overriddenRequest(options, callback); 371 | } 372 | 373 | // NOTE: Since we already overrode the http.ClientRequest we are in fact constructing 374 | // our own OverriddenClientRequest. 375 | req = new http.ClientRequest(options); 376 | 377 | res = RequestOverrider(req, options, interceptors, remove); 378 | if (callback) { 379 | res.on('response', callback); 380 | } 381 | return req; 382 | } else { 383 | if (isOff() || isEnabledForNetConnect(options)) { 384 | return overriddenRequest(options, callback); 385 | } else { 386 | var error = new NetConnectNotAllowedError(options.host); 387 | return new ErroringClientRequest(error); 388 | } 389 | } 390 | }); 391 | 392 | } 393 | 394 | activate(); 395 | 396 | module.exports = add; 397 | module.exports.removeAll = removeAll; 398 | module.exports.removeInterceptor = removeInterceptor; 399 | module.exports.isOn = isOn; 400 | module.exports.activate = activate; 401 | module.exports.isActive = isActive; 402 | module.exports.enableNetConnect = enableNetConnect; 403 | module.exports.disableNetConnect = disableNetConnect; 404 | module.exports.overrideClientRequest = overrideClientRequest; 405 | module.exports.restoreOverriddenClientRequest = restoreOverriddenClientRequest; 406 | -------------------------------------------------------------------------------- /tests/test_recorder.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var nock = require('../.') 4 | , tap = require('tap') 5 | , http = require('http') 6 | , https = require('https') 7 | , _ = require('lodash') 8 | , debug = require('debug')('nock.test_recorder') 9 | , mikealRequest = require('request') 10 | , superagent = require('superagent'); 11 | 12 | var globalCount; 13 | 14 | tap.test("setup", function(t) { 15 | globalCount = Object.keys(global).length; 16 | t.end(); 17 | }); 18 | 19 | tap.test('recording turns off nock interception (backward compatibility behavior)', function(t) { 20 | 21 | // We ensure that there are no overrides. 22 | nock.restore(); 23 | t.false(nock.isActive()); 24 | // We active the nock overriding - as it's done by merely loading nock. 25 | nock.activate(); 26 | t.true(nock.isActive()); 27 | // We start recording. 28 | nock.recorder.rec(); 29 | // Nothing happens (nothing has been thrown) - which was the original behavior - 30 | // and mocking has been deactivated. 31 | t.false(nock.isActive()); 32 | 33 | t.end(); 34 | 35 | }); 36 | 37 | tap.test('records', function(t) { 38 | nock.restore(); 39 | nock.recorder.clear(); 40 | t.equal(nock.recorder.play().length, 0); 41 | var options = { method: 'POST' 42 | , host:'google.com' 43 | , port:80 44 | , path:'/' } 45 | ; 46 | 47 | nock.recorder.rec(true); 48 | var req = http.request(options, function(res) { 49 | res.resume(); 50 | var ret; 51 | res.once('end', function() { 52 | nock.restore(); 53 | ret = nock.recorder.play(); 54 | t.equal(ret.length, 1); 55 | t.type(ret[0], 'string'); 56 | t.equal(ret[0].indexOf("\nnock('http://google.com:80')\n .post('/', \"ABCDEF\")\n .reply("), 0); 57 | t.end(); 58 | }); 59 | }); 60 | req.end('ABCDEF'); 61 | }); 62 | 63 | tap.test('records objects', function(t) { 64 | nock.restore(); 65 | nock.recorder.clear(); 66 | t.equal(nock.recorder.play().length, 0); 67 | var options = { method: 'POST' 68 | , host:'google.com' 69 | , path:'/' } 70 | ; 71 | 72 | nock.recorder.rec({ 73 | dont_print: true, 74 | output_objects: true 75 | }); 76 | var req = http.request(options, function(res) { 77 | res.resume(); 78 | res.once('end', function() { 79 | nock.restore(); 80 | var ret = nock.recorder.play(); 81 | t.equal(ret.length, 1); 82 | ret = ret[0]; 83 | t.type(ret, 'object'); 84 | t.equal(ret.scope, "http://google.com:80"); 85 | t.equal(ret.method, "POST"); 86 | t.ok(typeof(ret.status) !== 'undefined'); 87 | t.ok(typeof(ret.response) !== 'undefined'); 88 | t.end(); 89 | }); 90 | }); 91 | req.end('012345'); 92 | }); 93 | 94 | tap.test('checks if callback is specified', function(t) { 95 | var options = { 96 | host: 'www.google.com', method: 'GET', path: '/', port: 80 97 | }; 98 | 99 | nock.restore(); 100 | nock.recorder.clear(); 101 | t.equal(nock.recorder.play().length, 0); 102 | nock.recorder.rec(true); 103 | 104 | http.request(options).end(); 105 | t.end(); 106 | }); 107 | 108 | tap.test('when request body is json, it goes unstringified', function(t) { 109 | var payload = {a: 1, b: true}; 110 | var options = { 111 | method: 'POST', 112 | host: 'www.google.com', 113 | path: '/', 114 | port: 80 115 | }; 116 | 117 | nock.restore(); 118 | nock.recorder.clear(); 119 | nock.recorder.rec(true); 120 | 121 | var request = http.request(options, function(res) { 122 | res.resume(); 123 | res.once('end', function() { 124 | var ret = nock.recorder.play(); 125 | t.ok(ret.length >= 1); 126 | ret = ret[1] || ret[0]; 127 | t.equal(ret.indexOf("\nnock('http://www.google.com:80')\n .post('/', {\"a\":1,\"b\":true})\n .reply("), 0); 128 | t.end(); 129 | }); 130 | }); 131 | 132 | request.end(JSON.stringify(payload)); 133 | }); 134 | 135 | tap.test('when request body is json, it goes unstringified in objects', function(t) { 136 | var payload = {a: 1, b: true}; 137 | var options = { 138 | method: 'POST', 139 | host: 'www.google.com', 140 | path: '/', 141 | port: 80 142 | }; 143 | 144 | nock.restore(); 145 | nock.recorder.clear(); 146 | nock.recorder.rec({ 147 | dont_print: true, 148 | output_objects: true 149 | }); 150 | 151 | var request = http.request(options, function(res) { 152 | res.resume(); 153 | res.once('end', function() { 154 | var ret = nock.recorder.play(); 155 | t.ok(ret.length >= 1); 156 | ret = ret[1] || ret[0]; 157 | t.type(ret, 'object'); 158 | t.equal(ret.scope, "http://www.google.com:80"); 159 | t.equal(ret.method, "POST"); 160 | t.ok(ret.body && ret.body.a && ret.body.a === payload.a && ret.body.b && ret.body.b === payload.b); 161 | t.ok(typeof(ret.status) !== 'undefined'); 162 | t.ok(typeof(ret.response) !== 'undefined'); 163 | t.end(); 164 | }); 165 | }); 166 | 167 | request.end(JSON.stringify(payload)); 168 | }); 169 | 170 | tap.test('records nonstandard ports', function(t) { 171 | nock.restore(); 172 | nock.recorder.clear(); 173 | t.equal(nock.recorder.play().length, 0); 174 | 175 | var REQUEST_BODY = 'ABCDEF'; 176 | var RESPONSE_BODY = '012345'; 177 | 178 | // Create test http server and perform the tests while it's up. 179 | var testServer = http.createServer(function (req, res) { 180 | res.write(RESPONSE_BODY); 181 | res.end(); 182 | }).listen(8081, function(err) { 183 | 184 | t.equal(err, undefined); 185 | 186 | var options = { host:'localhost' 187 | , port:testServer.address().port 188 | , path:'/' } 189 | ; 190 | 191 | var rec_options = { 192 | dont_print: true, 193 | output_objects: true 194 | }; 195 | 196 | nock.recorder.rec(rec_options); 197 | 198 | var req = http.request(options, function(res) { 199 | res.resume(); 200 | res.once('end', function() { 201 | nock.restore(); 202 | var ret = nock.recorder.play(); 203 | t.equal(ret.length, 1); 204 | ret = ret[0]; 205 | t.type(ret, 'object'); 206 | t.equal(ret.scope, "http://localhost:" + options.port); 207 | t.equal(ret.method, "GET"); 208 | t.equal(ret.body, REQUEST_BODY); 209 | t.equal(ret.status, 200); 210 | t.equal(ret.response, RESPONSE_BODY); 211 | t.end(); 212 | 213 | // Close the test server, we are done with it. 214 | testServer.close(); 215 | }); 216 | }); 217 | 218 | req.end(REQUEST_BODY); 219 | }); 220 | 221 | }); 222 | 223 | tap.test('rec() throws when reenvoked with already recorder requests', function(t) { 224 | nock.restore(); 225 | nock.recorder.clear(); 226 | t.equal(nock.recorder.play().length, 0); 227 | 228 | nock.recorder.rec(); 229 | try { 230 | nock.recorder.rec(); 231 | // This line should never be reached. 232 | t.ok(false); 233 | t.end(); 234 | } catch(e) { 235 | t.equal(e.toString(), 'Error: Nock recording already in progress'); 236 | t.end(); 237 | } 238 | }); 239 | 240 | tap.test('records https correctly', function(t) { 241 | nock.restore(); 242 | nock.recorder.clear(); 243 | t.equal(nock.recorder.play().length, 0); 244 | 245 | var options = { method: 'POST' 246 | , host:'google.com' 247 | , path:'/' } 248 | ; 249 | 250 | nock.recorder.rec({ 251 | dont_print: true, 252 | output_objects: true 253 | }); 254 | 255 | var req = https.request(options, function(res) { 256 | res.resume(); 257 | var ret; 258 | res.once('end', function() { 259 | nock.restore(); 260 | ret = nock.recorder.play(); 261 | t.equal(ret.length, 1); 262 | ret = ret[0]; 263 | t.type(ret, 'object'); 264 | t.equal(ret.scope, "https://google.com:443"); 265 | t.equal(ret.method, "POST"); 266 | t.ok(typeof(ret.status) !== 'undefined'); 267 | t.ok(typeof(ret.response) !== 'undefined'); 268 | t.end(); 269 | }); 270 | }); 271 | req.end('012345'); 272 | }); 273 | 274 | tap.test('records request headers correctly', function(t) { 275 | nock.restore(); 276 | nock.recorder.clear(); 277 | t.equal(nock.recorder.play().length, 0); 278 | 279 | nock.recorder.rec({ 280 | dont_print: true, 281 | output_objects: true, 282 | enable_reqheaders_recording: true 283 | }); 284 | 285 | var req = http.request({ 286 | hostname: 'www.example.com', 287 | path: '/', 288 | method: 'GET', 289 | auth: 'foo:bar' 290 | }, function(res) { 291 | res.resume(); 292 | res.once('end', function() { 293 | nock.restore(); 294 | var ret = nock.recorder.play(); 295 | t.equal(ret.length, 1); 296 | ret = ret[0]; 297 | t.type(ret, 'object'); 298 | t.equivalent(ret.reqheaders, { 299 | host: 'www.example.com', 300 | 'authorization': 'Basic Zm9vOmJhcg==' 301 | }); 302 | t.end(); 303 | }); 304 | } 305 | ); 306 | req.end(); 307 | }); 308 | 309 | tap.test('records and replays gzipped nocks correctly', function(t) { 310 | 311 | nock.restore(); 312 | nock.recorder.clear(); 313 | t.equal(nock.recorder.play().length, 0); 314 | 315 | nock.recorder.rec({ 316 | dont_print: true, 317 | output_objects: true 318 | }); 319 | 320 | var makeRequest = function(callback) { 321 | superagent.get('http://bit.ly/1hKHiTe', callback); 322 | }; 323 | 324 | debug('make request to record'); 325 | 326 | makeRequest(function(err, resp) { 327 | 328 | debug('recorded request finished'); 329 | 330 | t.ok(!err); 331 | t.ok(resp); 332 | t.ok(resp.headers); 333 | t.equal(resp.headers['content-encoding'], 'gzip'); 334 | 335 | nock.restore(); 336 | var nockDefs = nock.recorder.play(); 337 | nock.recorder.clear(); 338 | nock.activate(); 339 | 340 | t.equal(nockDefs.length, 2); 341 | var nocks = nock.define(nockDefs); 342 | 343 | debug('make request to mock'); 344 | 345 | makeRequest(function(mockedErr, mockedResp) { 346 | 347 | debug('mocked request finished'); 348 | 349 | t.equal(err, mockedErr); 350 | t.equal(mockedResp.body, mockedResp.body); 351 | t.equal(mockedResp.headers['content-encoding'], 'gzip'); 352 | 353 | _.each(nocks, function(nock) { 354 | nock.done(); 355 | }); 356 | 357 | t.end(); 358 | 359 | }); 360 | }); 361 | 362 | }); 363 | 364 | tap.test('records and replays nocks correctly', function(t) { 365 | 366 | nock.restore(); 367 | nock.recorder.clear(); 368 | t.equal(nock.recorder.play().length, 0); 369 | 370 | nock.recorder.rec({ 371 | dont_print: true, 372 | output_objects: true 373 | }); 374 | 375 | var makeRequest = function(callback) { 376 | 377 | var options = { 378 | method: 'GET', 379 | uri: 'http://bit.ly/1hKHiTe', 380 | }; 381 | 382 | mikealRequest(options, callback); 383 | 384 | }; 385 | 386 | debug('make request to record'); 387 | 388 | makeRequest(function(err, resp, body) { 389 | 390 | debug('recorded request finished'); 391 | 392 | t.ok(!err); 393 | t.ok(resp); 394 | t.ok(body); 395 | 396 | nock.restore(); 397 | var nockDefs = nock.recorder.play(); 398 | nock.recorder.clear(); 399 | nock.activate(); 400 | 401 | t.equal(nockDefs.length, 2); 402 | var nocks = nock.define(nockDefs); 403 | 404 | debug('make request to mock'); 405 | 406 | makeRequest(function(mockedErr, mockedResp, mockedBody) { 407 | 408 | debug('mocked request finished'); 409 | 410 | t.equal(err, mockedErr); 411 | t.equal(body, mockedBody); 412 | 413 | _.each(nocks, function(nock) { 414 | nock.done(); 415 | }); 416 | 417 | t.end(); 418 | 419 | }); 420 | }); 421 | 422 | }); 423 | 424 | tap.test('doesn\'t record request headers by default', function(t) { 425 | nock.restore(); 426 | nock.recorder.clear(); 427 | t.equal(nock.recorder.play().length, 0); 428 | 429 | nock.recorder.rec({ 430 | dont_print: true, 431 | output_objects: true 432 | }); 433 | 434 | var req = http.request({ 435 | hostname: 'www.example.com', 436 | path: '/', 437 | method: 'GET', 438 | auth: 'foo:bar' 439 | }, function(res) { 440 | res.resume(); 441 | res.once('end', function() { 442 | nock.restore(); 443 | var ret = nock.recorder.play(); 444 | t.equal(ret.length, 1); 445 | ret = ret[0]; 446 | t.type(ret, 'object'); 447 | t.false(ret.reqheaders); 448 | t.end(); 449 | }); 450 | } 451 | ); 452 | req.end(); 453 | }); 454 | 455 | tap.test('records request headers except user-agent if enable_reqheaders_recording is set to true', function(t) { 456 | nock.restore(); 457 | nock.recorder.clear(); 458 | t.equal(nock.recorder.play().length, 0); 459 | 460 | nock.recorder.rec({ 461 | dont_print: true, 462 | output_objects: true, 463 | enable_reqheaders_recording: true 464 | }); 465 | 466 | var req = http.request({ 467 | hostname: 'www.example.com', 468 | path: '/', 469 | method: 'GET', 470 | auth: 'foo:bar' 471 | }, function(res) { 472 | res.resume(); 473 | res.once('end', function() { 474 | nock.restore(); 475 | var ret = nock.recorder.play(); 476 | t.equal(ret.length, 1); 477 | ret = ret[0]; 478 | t.type(ret, 'object'); 479 | t.true(ret.reqheaders); 480 | t.false(ret.reqheaders['user-agent']); 481 | t.end(); 482 | }); 483 | } 484 | ); 485 | req.end(); 486 | }); 487 | 488 | tap.test('includes query parameters from superagent', function(t) { 489 | nock.restore(); 490 | nock.recorder.clear(); 491 | t.equal(nock.recorder.play().length, 0); 492 | 493 | nock.recorder.rec({ 494 | dont_print: true, 495 | output_objects: true 496 | }); 497 | 498 | superagent.get('http://google.com') 499 | .query({q: 'test search' }) 500 | .end(function(res) { 501 | nock.restore(); 502 | var ret = nock.recorder.play(); 503 | t.true(ret.length >= 1); 504 | t.equal(ret[0].path, '/?q=test%20search'); 505 | t.end(); 506 | }); 507 | }); 508 | 509 | tap.test("teardown", function(t) { 510 | t.deepEqual(Object.keys(global) 511 | .splice(globalCount, Number.MAX_VALUE), 512 | [], 'No leaks'); 513 | t.end(); 514 | }); -------------------------------------------------------------------------------- /lib/request_overrider.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var EventEmitter = require('events').EventEmitter, 4 | http = require('http'), 5 | propagate = require('propagate'), 6 | DelayedBody = require('./delayed_body'), 7 | OutgoingMessage = http.OutgoingMessage, 8 | ClientRequest = http.ClientRequest, 9 | common = require('./common'), 10 | Socket = require('./socket'), 11 | _ = require('lodash'), 12 | debug = require('debug')('nock.request_overrider'); 13 | 14 | function getHeader(request, name) { 15 | if (!request._headers) { 16 | return; 17 | } 18 | 19 | var key = name.toLowerCase(); 20 | 21 | return request._headers[key]; 22 | } 23 | 24 | function setHeader(request, name, value) { 25 | var key = name.toLowerCase(); 26 | 27 | request._headers = request._headers || {}; 28 | request._headerNames = request._headerNames || {}; 29 | request._removedHeader = request._removedHeader || {}; 30 | 31 | request._headers[key] = value; 32 | request._headerNames[key] = name; 33 | } 34 | 35 | function isStream(obj) { 36 | var is = obj && (typeof obj !== 'string') && (!Buffer.isBuffer(obj)) && (typeof obj.setEncoding === 'function'); 37 | return is; 38 | } 39 | 40 | // Sets request headers of the given request. This is needed during both matching phase 41 | // (in case header filters were specified) and mocking phase (to correctly pass mocked 42 | // request headers). 43 | function setRequestHeaders(req, options, interceptor) { 44 | // We mock request headers if these were specified. 45 | if (interceptor.reqheaders) { 46 | var reqheaders = interceptor.reqheaders; 47 | _(interceptor.reqheaders).keys().each(function(key) { 48 | setHeader(req, key, reqheaders[key]); 49 | }); 50 | } 51 | 52 | // If a filtered scope is being used we have to use scope's host 53 | // in the header, otherwise 'host' header won't match. 54 | // NOTE: We use lower-case header field names throught Nock. 55 | var HOST_HEADER = 'host'; 56 | if(interceptor.__nock_filteredScope && interceptor.__nock_scopeHost) { 57 | if(options && options.headers) { 58 | options.headers[HOST_HEADER] = interceptor.__nock_scopeHost; 59 | } 60 | setHeader(req, HOST_HEADER, interceptor.__nock_scopeHost); 61 | } else { 62 | // For all other cases, we always add host header equal to the 63 | // requested host unless it was already defined. 64 | if (options.host && !getHeader(req, HOST_HEADER)) { 65 | var hostHeader = options.host; 66 | 67 | if (options.port === 80 || options.port === 443) { 68 | hostHeader = hostHeader.split(':')[0]; 69 | } 70 | 71 | setHeader(req, HOST_HEADER, hostHeader); 72 | } 73 | } 74 | 75 | } 76 | 77 | function RequestOverrider(req, options, interceptors, remove, cb) { 78 | 79 | var response = new OutgoingMessage(new EventEmitter()), 80 | requestBodyBuffers = [], 81 | originalInterceptors = interceptors, 82 | aborted, 83 | emitError, 84 | end, 85 | ended, 86 | headers, 87 | keys, 88 | key, 89 | i, 90 | l; 91 | 92 | // We may be changing the options object and we don't want those 93 | // changes affecting the user so we use a clone of the object. 94 | options = _.clone(options) || {}; 95 | 96 | response.req = req; 97 | 98 | if (options.headers) { 99 | // We use lower-case header field names throught Nock. 100 | options.headers = common.headersFieldNamesToLowerCase(options.headers); 101 | 102 | headers = options.headers; 103 | keys = Object.keys(headers); 104 | 105 | for (i = 0, l = keys.length; i < l; i++) { 106 | key = keys[i]; 107 | 108 | setHeader(req, key, headers[key]); 109 | } 110 | } 111 | 112 | /// options.auth 113 | if (options.auth && (! options.headers || ! options.headers.authorization)) { 114 | setHeader(req, 'Authorization', 'Basic ' + (new Buffer(options.auth)).toString('base64')); 115 | } 116 | 117 | // Mock response.connection and request.connection 118 | // Fixes: https://github.com/flatiron/nock/issues/74 119 | if (! response.connection) { 120 | response.connection = new EventEmitter(); 121 | } 122 | 123 | if (! req.connection) { 124 | req.connection = new EventEmitter(); 125 | } 126 | 127 | req.path = options.path; 128 | 129 | options.getHeader = function(name) { 130 | return getHeader(req, name); 131 | }; 132 | 133 | req.socket = Socket(); 134 | 135 | req.write = function(buffer, encoding) { 136 | if (buffer && !aborted) { 137 | if (! Buffer.isBuffer(buffer)) { 138 | buffer = new Buffer(buffer, encoding); 139 | } 140 | requestBodyBuffers.push(buffer); 141 | } 142 | }; 143 | 144 | req.end = function(buffer, encoding) { 145 | if (!aborted && !ended) { 146 | req.write(buffer, encoding); 147 | end(cb); 148 | req.emit('finish'); 149 | req.emit('end'); 150 | } 151 | }; 152 | 153 | req.abort = function() { 154 | aborted = true; 155 | if (!ended) { 156 | end(); 157 | } 158 | var err = new Error(); 159 | err.code = 'aborted'; 160 | response.emit('close', err); 161 | }; 162 | 163 | // restify listens for a 'socket' event to 164 | // be emitted before calling end(), which causes 165 | // nock to hang with restify. The following logic 166 | // fakes the socket behavior for restify, 167 | // Fixes: https://github.com/pgte/nock/issues/79 168 | req.once = req.on = function(event, listener) { 169 | // emit a fake socket. 170 | if (event == 'socket') { 171 | listener(req.socket); 172 | req.socket.emit('connect', req.socket); 173 | req.socket.emit('secureConnect', req.socket); 174 | } 175 | 176 | EventEmitter.prototype.on.call(this, event, listener); 177 | return this; 178 | }; 179 | 180 | emitError = function(error) { 181 | process.nextTick(function () { 182 | req.emit('error', error); 183 | }); 184 | }; 185 | 186 | end = function(cb) { 187 | ended = true; 188 | var encoding, 189 | requestBody, 190 | responseBody, 191 | responseBuffers, 192 | interceptor, 193 | paused, 194 | mockEmits = []; 195 | 196 | // When request body is a binary buffer we internally use in its hexadecimal representation. 197 | var requestBodyBuffer = common.mergeChunks(requestBodyBuffers); 198 | var isBinaryRequestBodyBuffer = common.isBinaryBuffer(requestBodyBuffer); 199 | if(isBinaryRequestBodyBuffer) { 200 | requestBody = requestBodyBuffer.toString('hex'); 201 | } else { 202 | requestBody = requestBodyBuffer.toString('utf8'); 203 | } 204 | 205 | /// put back the path into options 206 | /// because bad behaving agents like superagent 207 | /// like to change request.path in mid-flight. 208 | options.path = req.path; 209 | interceptors = interceptors.filter(function(interceptor) { 210 | // For correct matching we need to have correct request headers - if these were specified. 211 | setRequestHeaders(req, options, interceptor); 212 | 213 | return interceptor.match(options, requestBody); 214 | }); 215 | 216 | if (interceptors.length < 1) { 217 | // Try to find a hostname match 218 | interceptors = originalInterceptors.filter(function(interceptor) { 219 | return interceptor.match(options, requestBody, true); 220 | }); 221 | if (interceptors.length && req instanceof ClientRequest) { 222 | interceptor = interceptors[0]; 223 | if (interceptor.options.allowUnmocked) { 224 | var newReq = new ClientRequest(options, cb); 225 | propagate(newReq, req); 226 | // We send the raw buffer as we received it, not as we interpreted it. 227 | newReq.end(requestBodyBuffer); 228 | return; 229 | } 230 | } 231 | 232 | emitError(new Error("Nock: No match for request " + common.stringifyRequest(options, requestBody))); 233 | return; 234 | } 235 | 236 | debug('interceptor identified, starting mocking'); 237 | 238 | interceptor = interceptors.shift(); 239 | 240 | response.statusCode = interceptor.statusCode || 200; 241 | response.headers = interceptor.headers || {}; 242 | 243 | // We again set request headers, now for our matched interceptor. 244 | setRequestHeaders(req, options, interceptor); 245 | 246 | if (typeof interceptor.body === 'function') { 247 | 248 | responseBody = interceptor.body(options.path, requestBody) || ''; 249 | 250 | } else { 251 | 252 | // If the content is encoded we know that the response body *must* be an array 253 | // of response buffers which should be mocked one by one. 254 | // (otherwise decompressions after the first one fails as unzip expects to receive 255 | // buffer by buffer and not one single merged buffer) 256 | if(common.isContentEncoded(response.headers) && ! isStream(interceptor.body)) { 257 | 258 | if (interceptor.delayInMs) { 259 | emitError(new Error('Response delay is currently not supported with content-encoded responses.')); 260 | return; 261 | } 262 | 263 | var buffers = interceptor.body; 264 | if(!_.isArray(buffers)) { 265 | emitError( 266 | new Error( 267 | 'content-encoded response must be an array of binary buffers and not ' + typeof(buffers))); 268 | return; 269 | } 270 | 271 | responseBuffers = _.map(buffers, function(buffer) { 272 | return new Buffer(buffer, 'hex'); 273 | }); 274 | 275 | } else { 276 | 277 | responseBody = interceptor.body; 278 | 279 | // If the request was binary then we assume that the response will be binary as well. 280 | // In that case we send the response as a Buffer object as that's what the client will expect. 281 | if(isBinaryRequestBodyBuffer && typeof(responseBody) === 'string') { 282 | // Try to create the buffer from the interceptor's body response as hex. 283 | try { 284 | responseBody = new Buffer(responseBody, 'hex'); 285 | } catch(err) { 286 | debug('exception during Buffer construction from hex data:', responseBody, '-', err); 287 | } 288 | 289 | // Creating buffers does not necessarily throw errors, check for difference in size 290 | if (!responseBody || (interceptor.body.length > 0 && responseBody.length === 0)) { 291 | // We fallback on constructing buffer from utf8 representation of the body. 292 | responseBody = new Buffer(interceptor.body, 'utf8'); 293 | } 294 | } 295 | 296 | } 297 | } 298 | 299 | // Transform the response body if it exists (it may not exist if we have `responseBuffers` instead) 300 | if(responseBody) { 301 | debug('transform the response body'); 302 | 303 | if (!Buffer.isBuffer(responseBody) && !isStream(responseBody)) { 304 | if (typeof responseBody === 'string') { 305 | responseBody = new Buffer(responseBody); 306 | } else { 307 | responseBody = JSON.stringify(responseBody); 308 | } 309 | } 310 | 311 | if (interceptor.delayInMs) { 312 | debug('delaying the response for', interceptor.delayInMs, 'milliseconds'); 313 | responseBody = new DelayedBody(interceptor.delayInMs, responseBody); 314 | } 315 | 316 | if (isStream(responseBody)) { 317 | debug('response body is a stream'); 318 | responseBody.pause(); 319 | responseBody.on('data', function(d) { 320 | response.emit('data', d); 321 | }); 322 | responseBody.on('end', function() { 323 | response.emit('end'); 324 | }); 325 | responseBody.on('error', function(err) { 326 | response.emit('error', err); 327 | }); 328 | } else if (responseBody && !Buffer.isBuffer(responseBody)) { 329 | if (typeof responseBody === 'string') { 330 | responseBody = new Buffer(responseBody); 331 | } else { 332 | responseBody = JSON.stringify(responseBody); 333 | response.headers['content-type'] = 'application/json'; 334 | } 335 | } 336 | } 337 | 338 | remove(interceptor); 339 | interceptor.discard(); 340 | 341 | if (aborted) { return; } 342 | 343 | response.setEncoding = function(newEncoding) { 344 | encoding = newEncoding; 345 | }; 346 | 347 | response.pause = function() { 348 | debug('pausing mocking'); 349 | paused = true; 350 | if (isStream(responseBody)) { 351 | responseBody.pause(); 352 | } 353 | }; 354 | 355 | response.resume = function() { 356 | debug('resuming mocking'); 357 | paused = false; 358 | if (isStream(responseBody)) { 359 | responseBody.resume(); 360 | } 361 | mockNextEmit(); 362 | }; 363 | 364 | var read = false; 365 | response.read = function() { 366 | debug('reading response body'); 367 | if (isStream(responseBody) && responseBody.read) { 368 | return responseBody.read(); 369 | } else { 370 | if (! read) { 371 | read = true; 372 | return responseBody; 373 | } else { 374 | return null; 375 | } 376 | } 377 | }; 378 | 379 | // HACK: Flag our response object as readable so that it can be automatically 380 | // resumed by Node when drain happens. This enables working with some agents 381 | // that behave differently than built-in agent (e.g. needle, superagent). 382 | // The correct way to implement this would be to use IncomingMessage instead 383 | // of OutgoingMessage class to mock responses. 384 | response.readable = true; 385 | 386 | /// response.client.authorized = true 387 | /// fixes https://github.com/pgte/nock/issues/158 388 | response.client = _.extend(response.client || {}, { 389 | authorized: true 390 | }); 391 | 392 | // `mockEmits` is an array of emits to be performed during mocking. 393 | if (typeof responseBody !== "undefined") { 394 | mockEmits.push(function() { 395 | if (encoding) { 396 | debug('transforming body per its encoding'); 397 | if (isStream(responseBody)) { 398 | responseBody.setEncoding(encoding); 399 | } else { 400 | responseBody = responseBody.toString(encoding); 401 | } 402 | } 403 | if (! isStream(responseBody)) { 404 | debug('emitting response body'); 405 | response.emit('data', responseBody); 406 | response.emit('readable'); 407 | } 408 | }); 409 | } else { 410 | // We will emit response buffers one by one. 411 | _.each(responseBuffers, function(buffer) { 412 | mockEmits.push(function() { 413 | debug('emitting response buffer'); 414 | response.emit('data', buffer); 415 | response.emit('readable'); 416 | }); 417 | }); 418 | } 419 | 420 | if (!isStream(responseBody)) { 421 | mockEmits.push(function() { 422 | debug('emitting end'); 423 | response.emit('end'); 424 | }); 425 | } 426 | 427 | function mockNextEmit() { 428 | debug('mocking next emit'); 429 | 430 | // We don't use `process.nextTick` because we *want* (very much so!) 431 | // for I/O to happen before the next `emit` since we are precisely mocking I/O. 432 | // Otherwise the writing to output pipe may stall invoking pause() 433 | // without ever calling resume() (the observed was behavior on superagent 434 | // with Node v0.10.26) 435 | setImmediate(function() { 436 | if (paused || mockEmits.length === 0 || aborted) { 437 | debug('mocking paused, aborted or finished'); 438 | return; 439 | } 440 | 441 | var nextMockEmit = mockEmits.shift(); 442 | nextMockEmit(); 443 | 444 | // Recursively invoke to mock the next emit after the last one was handled. 445 | mockNextEmit(); 446 | }); 447 | } 448 | 449 | debug('mocking', mockEmits.length, 'emits'); 450 | 451 | process.nextTick(function() { 452 | var respond = function() { 453 | debug('emitting response'); 454 | 455 | if (typeof cb === 'function') { 456 | debug('callback with response'); 457 | cb(response); 458 | } 459 | 460 | req.emit('response', response); 461 | 462 | if (isStream(responseBody)) { 463 | debug('resuming response stream'); 464 | responseBody.resume(); 465 | } 466 | 467 | mockNextEmit(); 468 | }; 469 | 470 | if (interceptor.delayConnectionInMs && interceptor.delayConnectionInMs > 0) { 471 | setTimeout(respond, interceptor.delayConnectionInMs); 472 | } else { 473 | respond(); 474 | } 475 | }); 476 | }; 477 | 478 | return req; 479 | } 480 | 481 | module.exports = RequestOverrider; 482 | -------------------------------------------------------------------------------- /lib/scope.js: -------------------------------------------------------------------------------- 1 | /* jshint strict:false */ 2 | /** 3 | * @module nock/scope 4 | */ 5 | var fs = require('fs') 6 | , globalIntercept = require('./intercept') 7 | , mixin = require('./mixin') 8 | , matchBody = require('./match_body') 9 | , common = require('./common') 10 | , assert = require('assert') 11 | , url = require('url') 12 | , _ = require('lodash') 13 | , debug = require('debug')('nock.scope'); 14 | 15 | function isStream(obj) { 16 | return (typeof obj !== 'undefined') && 17 | (typeof a !== 'string') && 18 | (! Buffer.isBuffer(obj)) && 19 | (typeof obj.setEncoding === 'function'); 20 | } 21 | 22 | function startScope(basePath, options) { 23 | var interceptors = {}, 24 | scope, 25 | transformPathFunction, 26 | transformRequestBodyFunction, 27 | matchHeaders = [], 28 | logger = debug, 29 | scopeOptions = options || {}, 30 | urlParts = url.parse(basePath), 31 | port = urlParts.port || ((urlParts.protocol === 'http:') ? 80 : 443), 32 | persist = false; 33 | 34 | basePath = urlParts.protocol + '//' + urlParts.hostname + ':' + port; 35 | 36 | function add(key, interceptor, scope) { 37 | if (! interceptors.hasOwnProperty(key)) { 38 | interceptors[key] = []; 39 | } 40 | interceptors[key].push(interceptor); 41 | globalIntercept(basePath, interceptor, scope, scopeOptions, urlParts.hostname); 42 | } 43 | 44 | function remove(key, interceptor) { 45 | if (persist) { 46 | return; 47 | } 48 | var arr = interceptors[key]; 49 | if (arr) { 50 | arr.splice(arr.indexOf(interceptor), 1); 51 | if (arr.length === 0) { delete interceptors[key]; } 52 | } 53 | } 54 | 55 | function intercept(uri, method, requestBody, interceptorOptions) { 56 | var interceptorMatchHeaders = []; 57 | var key = method.toUpperCase() + ' ' + basePath + uri; 58 | 59 | function reply(statusCode, body, headers) { 60 | this.statusCode = statusCode; 61 | 62 | // We use lower-case headers throught Nock. 63 | headers = common.headersFieldNamesToLowerCase(headers); 64 | 65 | this.options = interceptorOptions || {}; 66 | for(var opt in scopeOptions) { 67 | if(typeof this.options[opt] === 'undefined') { 68 | this.options[opt] = scopeOptions[opt]; 69 | } 70 | } 71 | 72 | if (scope._defaultReplyHeaders) { 73 | headers = headers || {}; 74 | headers = mixin(scope._defaultReplyHeaders, headers); 75 | } 76 | 77 | if (headers !== undefined) { 78 | this.headers = {}; 79 | 80 | // makes sure all keys in headers are in lower case 81 | for (var key2 in headers) { 82 | if (headers.hasOwnProperty(key2)) { 83 | this.headers[key2.toLowerCase()] = headers[key2]; 84 | } 85 | } 86 | } 87 | 88 | // If the content is not encoded we may need to transform the response body. 89 | // Otherwise we leave it as it is. 90 | if(!common.isContentEncoded(headers)) { 91 | if (body && typeof(body) !== 'string' && 92 | typeof(body) !== 'function' && 93 | !Buffer.isBuffer(body) && 94 | !isStream(body)) { 95 | try { 96 | body = JSON.stringify(body); 97 | if (!this.headers) { 98 | this.headers = {}; 99 | } 100 | if (!this.headers['content-type']) { 101 | this.headers['content-type'] = 'application/json'; 102 | } 103 | } catch(err) { 104 | throw new Error('Error encoding response body into JSON'); 105 | } 106 | } 107 | } 108 | 109 | this.body = body; 110 | 111 | add(key, this, scope, scopeOptions); 112 | return scope; 113 | } 114 | 115 | function replyWithFile(statusCode, filePath, headers) { 116 | var readStream = fs.createReadStream(filePath); 117 | readStream.pause(); 118 | this.filePath = filePath; 119 | return reply.call(this, statusCode, readStream, headers); 120 | } 121 | 122 | var matchStringOrRegexp = function(target, pattern) { 123 | if (pattern instanceof RegExp) { 124 | return target.toString().match(pattern); 125 | } else { 126 | return target === pattern; 127 | } 128 | }; 129 | 130 | function match(options, body, hostNameOnly) { 131 | if (hostNameOnly) { 132 | return options.hostname === urlParts.hostname; 133 | } 134 | 135 | var method = options.method || 'GET' 136 | , path = options.path 137 | , matches 138 | , proto = options.proto; 139 | 140 | if (transformPathFunction) { 141 | path = transformPathFunction(path); 142 | } 143 | if (typeof(body) !== 'string') { 144 | body = body.toString(); 145 | } 146 | if (transformRequestBodyFunction) { 147 | body = transformRequestBodyFunction(body); 148 | } 149 | 150 | var checkHeaders = function(header) { 151 | if (typeof header.value === 'function') { 152 | return header.value(options.getHeader(header.name)); 153 | } 154 | return matchStringOrRegexp(options.getHeader(header.name), header.value); 155 | }; 156 | 157 | if (!matchHeaders.every(checkHeaders) || 158 | !interceptorMatchHeaders.every(checkHeaders)) { 159 | logger('headers don\'t match'); 160 | return false; 161 | } 162 | 163 | // Also match request headers 164 | // https://github.com/pgte/nock/issues/163 165 | function reqheaderMatches(key) { 166 | // We don't try to match request headers if these weren't even specified in the request. 167 | if(! options.headers) { 168 | return true; 169 | } 170 | 171 | // We skip 'host' header comparison unless it's available in both mock and actual request. 172 | // This because 'host' may get inserted by Nock itself and then get recorder. 173 | // NOTE: We use lower-case header field names throught Nock. 174 | if(key === 'host' && 175 | (_.isUndefined(options.headers[key]) || 176 | _.isUndefined(this.reqheaders[key]))) 177 | { 178 | return true; 179 | } 180 | 181 | if(options.headers[key] === this.reqheaders[key]) { 182 | return true; 183 | } 184 | 185 | debug('request header field doesn\'t match:', key, options.headers[key], this.reqheaders[key]); 186 | return false; 187 | } 188 | 189 | var reqHeadersMatch = 190 | ! this.reqheaders || 191 | Object.keys(this.reqheaders).every(reqheaderMatches.bind(this)); 192 | 193 | if (!reqHeadersMatch) { 194 | return false; 195 | } 196 | 197 | var matchKey = method.toUpperCase() + ' '; 198 | 199 | // If we have a filtered scope then we use it instead reconstructing 200 | // the scope from the request options (proto, host and port) as these 201 | // two won't necessarily match and we have to remove the scope that was 202 | // matched (vs. that was defined). 203 | if(this.__nock_filteredScope) { 204 | matchKey += this.__nock_filteredScope; 205 | } else { 206 | matchKey += proto + '://' + options.host; 207 | if ( 208 | options.port && options.host.indexOf(':') < 0 && 209 | (options.port !== 80 || options.proto !== 'http') && 210 | (options.port !== 443 || options.proto !== 'https') 211 | ) { 212 | matchKey += ":" + options.port; 213 | } 214 | } 215 | matchKey += path; 216 | matches = matchKey === this._key; 217 | logger('matching ' + matchKey + ' to ' + this._key + ': ' + matches); 218 | if (matches) { 219 | matches = (matchBody.call(options, this._requestBody, body)); 220 | if(!matches) { 221 | logger('bodies don\'t match'); 222 | } 223 | } 224 | 225 | return matches; 226 | } 227 | 228 | function matchIndependentOfBody(options) { 229 | var method = options.method || 'GET' 230 | , path = options.path 231 | , proto = options.proto; 232 | 233 | if (transformPathFunction) { 234 | path = transformPathFunction(path); 235 | } 236 | 237 | var checkHeaders = function(header) { 238 | return options.getHeader && matchStringOrRegexp(options.getHeader(header.name), header.value); 239 | }; 240 | 241 | if (!matchHeaders.every(checkHeaders) || 242 | !interceptorMatchHeaders.every(checkHeaders)) { 243 | return false; 244 | } 245 | 246 | var matchKey = method + ' ' + proto + '://' + options.host + path; 247 | return this._key === matchKey; 248 | } 249 | 250 | function filteringPath() { 251 | if (typeof arguments[0] === 'function') { 252 | this.transformFunction = arguments[0]; 253 | } 254 | return this; 255 | } 256 | 257 | function discard() { 258 | if (persist && this.filePath) { 259 | this.body = fs.createReadStream(this.filePath); 260 | this.body.pause(); 261 | } 262 | 263 | if (!persist && this.counter < 1) { 264 | remove(this._key, this); 265 | } 266 | } 267 | 268 | function matchHeader(name, value) { 269 | interceptorMatchHeaders.push({ name: name, value: value }); 270 | return this; 271 | } 272 | 273 | /** 274 | * Set number of times will repeat the interceptor 275 | * @name times 276 | * @param Integer Number of times to repeat (should be > 0) 277 | * @public 278 | * @example 279 | * // Will repeat mock 5 times for same king of request 280 | * nock('http://zombo.com).get('/').times(5).reply(200, 'Ok'); 281 | */ 282 | function times(newCounter) { 283 | if (newCounter < 1) { 284 | return this; 285 | } 286 | 287 | this.counter = newCounter; 288 | 289 | return this; 290 | } 291 | 292 | /** 293 | * An sugar sintaxe for times(1) 294 | * @name once 295 | * @see {@link times} 296 | * @public 297 | * @example 298 | * nock('http://zombo.com).get('/').once.reply(200, 'Ok'); 299 | */ 300 | function once() { 301 | return this.times(1); 302 | } 303 | 304 | /** 305 | * An sugar sintaxe for times(2) 306 | * @name twixe 307 | * @see {@link times} 308 | * @public 309 | * @example 310 | * nock('http://zombo.com).get('/').twice.reply(200, 'Ok'); 311 | */ 312 | function twice() { 313 | return this.times(2); 314 | } 315 | 316 | /** 317 | * An sugar sintaxe for times(3). 318 | * @name thrice 319 | * @see {@link times} 320 | * @public 321 | * @example 322 | * nock('http://zombo.com).get('/').thrice.reply(200, 'Ok'); 323 | */ 324 | function thrice() { 325 | return this.times(3); 326 | } 327 | 328 | /** 329 | * Delay the response by a certain number of ms. 330 | * 331 | * @param {integer} ms - Number of milliseconds to wait 332 | * @return {scope} - the current scope for chaining 333 | */ 334 | function delay(ms) { 335 | this.delayInMs = ms; 336 | return this; 337 | } 338 | 339 | /** 340 | * Delay the connection by a certain number of ms. 341 | * 342 | * @param {integer} ms - Number of milliseconds to wait 343 | * @return {scope} - the current scope for chaining 344 | */ 345 | function delayConnection(ms) { 346 | this.delayConnectionInMs = ms; 347 | return this; 348 | } 349 | 350 | var interceptor = { 351 | _key: key 352 | , counter: 1 353 | , _requestBody: requestBody 354 | // We use lower-case header field names throught Nock. 355 | , reqheaders: common.headersFieldNamesToLowerCase((options && options.reqheaders) || {}) 356 | , reply: reply 357 | , replyWithFile: replyWithFile 358 | , discard: discard 359 | , match: match 360 | , matchIndependentOfBody: matchIndependentOfBody 361 | , filteringPath: filteringPath 362 | , matchHeader: matchHeader 363 | , times: times 364 | , once: once 365 | , twice: twice 366 | , thrice: thrice 367 | , delay: delay 368 | , delayConnection: delayConnection 369 | }; 370 | 371 | return interceptor; 372 | } 373 | 374 | function get(uri, requestBody, options) { 375 | return intercept(uri, 'GET', requestBody, options); 376 | } 377 | 378 | function post(uri, requestBody, options) { 379 | return intercept(uri, 'POST', requestBody, options); 380 | } 381 | 382 | function put(uri, requestBody, options) { 383 | return intercept(uri, 'PUT', requestBody, options); 384 | } 385 | 386 | function head(uri, requestBody, options) { 387 | return intercept(uri, 'HEAD', requestBody, options); 388 | } 389 | 390 | function patch(uri, requestBody, options) { 391 | return intercept(uri, 'PATCH', requestBody, options); 392 | } 393 | 394 | function merge(uri, requestBody, options) { 395 | return intercept(uri, 'MERGE', requestBody, options); 396 | } 397 | 398 | function _delete(uri, requestBody, options) { 399 | return intercept(uri, 'DELETE', requestBody, options); 400 | } 401 | 402 | function pendingMocks() { 403 | return Object.keys(interceptors); 404 | } 405 | 406 | function isDone() { 407 | 408 | // if nock is turned off, it always says it's done 409 | if (! globalIntercept.isOn()) { return true; } 410 | 411 | var keys = Object.keys(interceptors); 412 | if (keys.length === 0) { 413 | return true; 414 | } else { 415 | var doneHostCount = 0; 416 | 417 | keys.forEach(function(key) { 418 | var doneInterceptorCount = 0; 419 | 420 | interceptors[key].forEach(function(interceptor) { 421 | var isDefined = (typeof interceptor.options.requireDone !== 'undefined'); 422 | if (isDefined && interceptor.options.requireDone === false) { 423 | doneInterceptorCount += 1; 424 | } 425 | }); 426 | 427 | if( doneInterceptorCount === interceptors[key].length ) { 428 | doneHostCount += 1; 429 | } 430 | }); 431 | return (doneHostCount === keys.length); 432 | } 433 | } 434 | 435 | function done() { 436 | assert.ok(isDone(), "Mocks not yet satisfied:\n" + pendingMocks().join("\n")); 437 | } 438 | 439 | function buildFilter() { 440 | var filteringArguments = arguments; 441 | 442 | if (arguments[0] instanceof RegExp) { 443 | return function(path) { 444 | if (path) { 445 | path = path.replace(filteringArguments[0], filteringArguments[1]); 446 | } 447 | return path; 448 | }; 449 | } else if (typeof (arguments[0]) === 'function') { 450 | return arguments[0]; 451 | } 452 | } 453 | 454 | function filteringPath() { 455 | transformPathFunction = buildFilter.apply(undefined, arguments); 456 | if (!transformPathFunction) { 457 | throw new Error('Invalid arguments: filtering path should be a function or a regular expression'); 458 | } 459 | return this; 460 | } 461 | 462 | function filteringRequestBody() { 463 | transformRequestBodyFunction = buildFilter.apply(undefined, arguments); 464 | if (!transformRequestBodyFunction) { 465 | throw new Error('Invalid arguments: filtering request body should be a function or a regular expression'); 466 | } 467 | return this; 468 | } 469 | 470 | function matchHeader(name, value) { 471 | // We use lower-case header field names throught Nock. 472 | matchHeaders.push({ name: name.toLowerCase(), value: value }); 473 | return this; 474 | } 475 | 476 | function defaultReplyHeaders(headers) { 477 | this._defaultReplyHeaders = common.headersFieldNamesToLowerCase(headers); 478 | return this; 479 | } 480 | 481 | function log(newLogger) { 482 | logger = newLogger; 483 | return this; 484 | } 485 | 486 | function _persist() { 487 | persist = true; 488 | return this; 489 | } 490 | 491 | function shouldPersist() { 492 | return persist; 493 | } 494 | 495 | 496 | scope = { 497 | get: get 498 | , post: post 499 | , delete: _delete 500 | , put: put 501 | , merge: merge 502 | , patch: patch 503 | , head: head 504 | , intercept: intercept 505 | , done: done 506 | , isDone: isDone 507 | , filteringPath: filteringPath 508 | , filteringRequestBody: filteringRequestBody 509 | , matchHeader: matchHeader 510 | , defaultReplyHeaders: defaultReplyHeaders 511 | , log: log 512 | , persist: _persist 513 | , shouldPersist: shouldPersist 514 | , pendingMocks: pendingMocks 515 | }; 516 | 517 | return scope; 518 | } 519 | 520 | function cleanAll() { 521 | globalIntercept.removeAll(); 522 | return module.exports; 523 | } 524 | 525 | function loadDefs(path) { 526 | var contents = fs.readFileSync(path); 527 | return JSON.parse(contents); 528 | } 529 | 530 | function load(path) { 531 | return define(loadDefs(path)); 532 | } 533 | 534 | function getStatusFromDefinition(nockDef) { 535 | // Backward compatibility for when `status` was encoded as string in `reply`. 536 | if(!_.isUndefined(nockDef.reply)) { 537 | // Try parsing `reply` property. 538 | var parsedReply = parseInt(nockDef.reply, 10); 539 | if(_.isNumber(parsedReply)) { 540 | return parsedReply; 541 | } 542 | } 543 | 544 | var DEFAULT_STATUS_OK = 200; 545 | return nockDef.status || DEFAULT_STATUS_OK; 546 | } 547 | 548 | function getScopeFromDefinition(nockDef) { 549 | 550 | // Backward compatibility for when `port` was part of definition. 551 | if(!_.isUndefined(nockDef.port)) { 552 | // Include `port` into scope if it doesn't exist. 553 | var options = url.parse(nockDef.scope); 554 | if(_.isNull(options.port)) { 555 | return nockDef.scope + ':' + nockDef.port; 556 | } else { 557 | if(parseInt(options.port) !== parseInt(nockDef.port)) { 558 | throw new Error('Mismatched port numbers in scope and port properties of nock definition.'); 559 | } 560 | } 561 | } 562 | 563 | return nockDef.scope; 564 | } 565 | 566 | function tryJsonParse(string) { 567 | try { 568 | return JSON.parse(string); 569 | } catch(err) { 570 | return string; 571 | } 572 | } 573 | 574 | function define(nockDefs) { 575 | 576 | var nocks = []; 577 | 578 | nockDefs.forEach(function(nockDef) { 579 | 580 | var nscope = getScopeFromDefinition(nockDef) 581 | , npath = nockDef.path 582 | , method = nockDef.method.toLowerCase() || "get" 583 | , status = getStatusFromDefinition(nockDef) 584 | , headers = nockDef.headers || {} 585 | , reqheaders = nockDef.reqheaders || {} 586 | , body = nockDef.body || '' 587 | , options = nockDef.options || {}; 588 | 589 | // We use request headers for both filtering (see below) and mocking. 590 | // Here we are setting up mocked request headers but we don't want to 591 | // be changing the user's options object so we clone it first. 592 | options = _.clone(options) || {}; 593 | options.reqheaders = reqheaders; 594 | 595 | // Response is not always JSON as it could be a string or binary data or 596 | // even an array of binary buffers (e.g. when content is enconded) 597 | var response; 598 | if(!nockDef.response) { 599 | response = ''; 600 | } else { 601 | response = _.isString(nockDef.response) ? tryJsonParse(nockDef.response) : nockDef.response; 602 | } 603 | 604 | var nock; 605 | if(body==="*") { 606 | nock = startScope(nscope, options).filteringRequestBody(function() { 607 | return "*"; 608 | })[method](npath, "*").reply(status, response, headers); 609 | } else { 610 | nock = startScope(nscope, options); 611 | // If request headers were specified filter by them. 612 | if(reqheaders !== {}) { 613 | for (var k in reqheaders) { 614 | nock.matchHeader(k, reqheaders[k]); 615 | } 616 | } 617 | nock.intercept(npath, method, body).reply(status, response, headers); 618 | } 619 | 620 | nocks.push(nock); 621 | 622 | }); 623 | 624 | return nocks; 625 | } 626 | 627 | 628 | 629 | module.exports = startScope; 630 | 631 | module.exports.cleanAll = cleanAll; 632 | module.exports.activate = globalIntercept.activate; 633 | module.exports.isActive = globalIntercept.isActive; 634 | module.exports.removeInterceptor = globalIntercept.removeInterceptor; 635 | module.exports.disableNetConnect = globalIntercept.disableNetConnect; 636 | module.exports.enableNetConnect = globalIntercept.enableNetConnect; 637 | module.exports.load = load; 638 | module.exports.loadDefs = loadDefs; 639 | module.exports.define = define; 640 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nock [![Build Status](https://secure.travis-ci.org/pgte/nock.png)](http://travis-ci.org/pgte/nock) 2 | [![Gitter chat](https://badges.gitter.im/pgte/nock.png)](https://gitter.im/pgte/nock) 3 | 4 | Nock is an HTTP mocking and expectations library for Node.js 5 | 6 | Nock can be used to test modules that perform HTTP requests in isolation. 7 | 8 | For instance, if a module performs HTTP requests to a CouchDB server or makes HTTP requests to the Amazon API, you can test that module in isolation. 9 | 10 | This does NOT work with Browserify, only node.js 11 | 12 | 13 | # Install 14 | 15 | ```sh 16 | $ npm install nock 17 | ``` 18 | 19 | # Use 20 | 21 | On your test, you can setup your mocking object like this: 22 | 23 | ```js 24 | var nock = require('nock'); 25 | 26 | var couchdb = nock('http://myapp.iriscouch.com') 27 | .get('/users/1') 28 | .reply(200, { 29 | _id: '123ABC', 30 | _rev: '946B7D1C', 31 | username: 'pgte', 32 | email: 'pedro.teixeira@gmail.com' 33 | }); 34 | ``` 35 | 36 | This setup says that we will intercept every HTTP call to `http://myapp.iriscouch.com`. 37 | 38 | It will intercept an HTTP GET request to '/users/1' and reply with a status 200, and the body will contain a user representation in JSON. 39 | 40 | Then the test can call the module, and the module will do the HTTP requests. 41 | 42 | ## READ THIS 43 | 44 | When you setup an interceptor for an URL and that interceptor is used, it is removed from the interceptor list. 45 | This means that you can intercept 2 or more calls to the same URL and return different things on each of them. 46 | It also means that you must setup one interceptor for each request you are going to have, otherwise nock will throw an error because that URL was not present in the interceptor list. 47 | 48 | ## Specifying request body 49 | 50 | You can specify the request body to be matched as the second argument to the `get`, `post`, `put` or `delete` specifications like this: 51 | 52 | ```js 53 | var scope = nock('http://myapp.iriscouch.com') 54 | .post('/users', { 55 | username: 'pgte', 56 | email: 'pedro.teixeira@gmail.com' 57 | }) 58 | .reply(201, { 59 | ok: true, 60 | id: '123ABC', 61 | rev: '946B7D1C' 62 | }); 63 | ``` 64 | 65 | The request body can be a string or a JSON object. 66 | 67 | ## Specifying replies 68 | 69 | You can specify the return status code for a path on the first argument of reply like this: 70 | 71 | ```js 72 | var scope = nock('http://myapp.iriscouch.com') 73 | .get('/users/1') 74 | .reply(404); 75 | ``` 76 | 77 | You can also specify the reply body as a string: 78 | 79 | ```js 80 | var scope = nock('http://www.google.com') 81 | .get('/') 82 | .reply(200, 'Hello from Google!'); 83 | ``` 84 | 85 | or as a JSON-encoded object: 86 | 87 | ```js 88 | var scope = nock('http://myapp.iriscouch.com') 89 | .get('/') 90 | .reply(200, { 91 | username: 'pgte', 92 | email: 'pedro.teixeira@gmail.com', 93 | _id: '4324243fsd' 94 | }); 95 | ``` 96 | 97 | or even as a file: 98 | 99 | ```js 100 | var scope = nock('http://myapp.iriscouch.com') 101 | .get('/') 102 | .replyWithFile(200, __dirname + '/replies/user.json'); 103 | ``` 104 | 105 | Instead of an object or a buffer you can also pass in a callback to be evaluated for the value of the response body: 106 | 107 | ```js 108 | var scope = nock('http://www.google.com') 109 | .filteringRequestBody(/.*/, '*') 110 | .post('/echo', '*') 111 | .reply(201, function(uri, requestBody) { 112 | return requestBody; 113 | }); 114 | ``` 115 | 116 | 117 | A Stream works too: 118 | ```js 119 | var scope = nock('http://www.google.com') 120 | .get('/cat-poems') 121 | .reply(200, function(uri, requestBody) { 122 | return fs.createReadStream('cat-poems.txt'); 123 | }); 124 | ``` 125 | 126 | ## Specifying headers 127 | 128 | ### Header field names are case-insensitive 129 | 130 | Per [HTTP/1.1 4.2 Message Headers](http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2) specification, all message headers are case insensitive and thus internally Nock uses lower-case for all field names even if some other combination of cases was specified either in mocking specification or in mocked requests themselves. 131 | 132 | ### Specifying Request Headers 133 | 134 | You can specify the request headers like this: 135 | 136 | ``` 137 | var scope = nock('http://www.example.com', { 138 | reqheaders: { 139 | 'authorization': 'Basic Auth' 140 | } 141 | }) 142 | .get('/') 143 | .reply(200); 144 | ``` 145 | 146 | If `reqheaders` is not specified or if `host` is not part of it, Nock will automatically add `host` value to request header. 147 | 148 | If no request headers are specified for mocking then Nock will automatically skip matching of request headers. Since `host` header is a special case which may get automatically inserted by Nock, its matching is skipped unless it was *also* specified in the request being mocked. 149 | 150 | ### Specifying Reply Headers 151 | 152 | You can specify the reply headers like this: 153 | 154 | ```js 155 | var scope = nock('http://www.headdy.com') 156 | .get('/') 157 | .reply(200, 'Hello World!', { 158 | 'X-My-Headers': 'My Header value' 159 | }); 160 | ``` 161 | 162 | ### Default Reply Headers 163 | 164 | You can also specify default reply headers for all responses like this: 165 | 166 | ```js 167 | var scope = nock('http://www.headdy.com') 168 | .defaultReplyHeaders({ 169 | 'X-Powered-By': 'Rails', 170 | 'Content-Type': 'application/json' 171 | }) 172 | .get('/') 173 | .reply(200, 'The default headers should come too'); 174 | ``` 175 | 176 | ## HTTP Verbs 177 | 178 | Nock supports any HTTP verb, and it has convenience methods for the GET, POST, PUT, HEAD, DELETE, PATCH and MERGE HTTP verbs. 179 | 180 | You can intercept any HTTP verb using `.intercept(path, verb [, requestBody [, options]])`: 181 | 182 | ```js 183 | scope('http://my.domain.com') 184 | .intercept('/path', 'PATCH') 185 | .reply(304); 186 | ``` 187 | 188 | ## Support for HTTP and HTTPS 189 | 190 | By default nock assumes HTTP. If you need to use HTTPS you can specify the `https://` prefix like this: 191 | 192 | ```js 193 | var scope = nock('https://secure.my.server.com') 194 | // ... 195 | ``` 196 | 197 | ## Non-standard ports 198 | 199 | You are able to specify a non-standard port like this: 200 | 201 | ```js 202 | var scope = nock('http://my.server.com:8081') 203 | ... 204 | ``` 205 | 206 | ## Repeat response n times 207 | 208 | You are able to specify the number of times to repeat the same response. 209 | 210 | ```js 211 | nock('http://zombo.com').get('/').times(4).reply(200, 'Ok'); 212 | 213 | http.get('http://zombo.com/'); // respond body "Ok" 214 | http.get('http://zombo.com/'); // respond body "Ok" 215 | http.get('http://zombo.com/'); // respond body "Ok" 216 | http.get('http://zombo.com/'); // respond body "Ok" 217 | http.get('http://zombo.com/'); // respond with zombo.com result 218 | ``` 219 | 220 | Sugar sintaxe 221 | 222 | ```js 223 | nock('http://zombo.com').get('/').once().reply(200, 'Ok'); 224 | nock('http://zombo.com').get('/').twice().reply(200, 'Ok'); 225 | nock('http://zombo.com').get('/').thrice().reply(200, 'Ok'); 226 | ``` 227 | 228 | ## Delay the response 229 | 230 | You are able to specify the number of milliseconds that your reply should be delayed. 231 | 232 | ```js 233 | nock('http://my.server.com') 234 | .get('/') 235 | .delay(2000) // 2 seconds 236 | .reply(200, '') 237 | ``` 238 | 239 | NOTE: the [`'response'`](http://nodejs.org/api/http.html#http_event_response) event will occur immediately, but the [IncomingMessage](http://nodejs.org/api/http.html#http_http_incomingmessage) not emit it's `'end'` event until after the delay. 240 | 241 | ## Delay the connection 242 | 243 | You are able to specify the number of milliseconds that your connection should be delayed. 244 | 245 | ```js 246 | nock('http://my.server.com') 247 | .get('/') 248 | .delayConnection(2000) // 2 seconds 249 | .reply(200, '') 250 | ``` 251 | 252 | ## Chaining 253 | 254 | You can chain behaviour like this: 255 | 256 | ```js 257 | var scope = nock('http://myapp.iriscouch.com') 258 | .get('/users/1') 259 | .reply(404) 260 | .post('/users', { 261 | username: 'pgte', 262 | email: 'pedro.teixeira@gmail.com' 263 | }) 264 | .reply(201, { 265 | ok: true, 266 | id: '123ABC', 267 | rev: '946B7D1C' 268 | }) 269 | .get('/users/123ABC') 270 | .reply(200, { 271 | _id: '123ABC', 272 | _rev: '946B7D1C', 273 | username: 'pgte', 274 | email: 'pedro.teixeira@gmail.com' 275 | }); 276 | ``` 277 | 278 | ## Scope filtering 279 | 280 | You can filter the scope (protocol, domain and port through) of a nock through a function. This filtering functions is defined at the moment of defining the nock's scope through its optional `options` parameters: 281 | 282 | This can be useful, for instance, if you have a node moduel that randomly changes subdomains to which it sends requests (e.g. Dropbox node module is like that) 283 | 284 | ```js 285 | var scope = nock('https://api.dropbox.com', { 286 | filteringScope: function(scope) { 287 | return /^https:\/\/api[0-9]*.dropbox.com/.test(scope); 288 | } 289 | }) 290 | .get('/1/metadata/auto/Photos?include_deleted=false&list=true') 291 | .reply(200); 292 | ``` 293 | 294 | ## Path filtering 295 | 296 | You can also filter the URLs based on a function. 297 | 298 | This can be useful, for instance, if you have random or time-dependent data in your URL. 299 | 300 | You can use a regexp for replacement, just like String.prototype.replace: 301 | 302 | ```js 303 | var scope = nock('http://api.myservice.com') 304 | .filteringPath(/password=[^&]*/g, 'password=XXX') 305 | .get('/users/1?password=XXX') 306 | .reply(200, 'user'); 307 | ``` 308 | 309 | Or you can use a function: 310 | 311 | ```js 312 | var scope = nock('http://api.myservice.com') 313 | .filteringPath(function(path) { 314 | return '/ABC'; 315 | }) 316 | .get('/ABC') 317 | .reply(200, 'user'); 318 | ``` 319 | 320 | Note that `scope.filteringPath` is not cummulative: it should only be used once per scope. 321 | 322 | ## Request Body filtering 323 | 324 | You can also filter the request body based on a function. 325 | 326 | This can be useful, for instance, if you have random or time-dependent data in your request body. 327 | 328 | You can use a regexp for replacement, just like String.prototype.replace: 329 | 330 | ```js 331 | var scope = nock('http://api.myservice.com') 332 | .filteringRequestBody(/password=[^&]*/g, 'password=XXX') 333 | .post('/users/1', 'data=ABC&password=XXX') 334 | .reply(201, 'OK'); 335 | ``` 336 | 337 | Or you can use a function: 338 | 339 | ```js 340 | var scope = nock('http://api.myservice.com') 341 | .filteringRequestBody(function(path) { 342 | return 'ABC'; 343 | }) 344 | .post('/', 'ABC') 345 | .reply(201, 'OK'); 346 | ``` 347 | 348 | ## Request Headers Matching 349 | 350 | If you need to match requests only if certain request headers match, you can. 351 | 352 | ```js 353 | var scope = nock('http://api.myservice.com') 354 | .matchHeader('accept', 'application/json') 355 | .get('/') 356 | .reply(200, { 357 | data: 'hello world' 358 | }) 359 | ``` 360 | 361 | You can also use a regexp for the header body. 362 | 363 | ```js 364 | var scope = nock('http://api.myservice.com') 365 | .matchHeader('User-Agent', /Mozilla\/.*/) 366 | .get('/') 367 | .reply(200, { 368 | data: 'hello world' 369 | }) 370 | ``` 371 | 372 | You can also use a function for the header body. 373 | 374 | ```js 375 | var scope = nock('http://api.myservice.com') 376 | .matchHeader('content-length', function (val) { 377 | return val >= 1000; 378 | }) 379 | .get('/') 380 | .reply(200, { 381 | data: 'hello world' 382 | }) 383 | ``` 384 | 385 | ## Allow __unmocked__ requests on a mocked hostname 386 | 387 | If you need some request on the same host name to be mocked and some others to **really** go through the HTTP stack, you can use the `allowUnmocked` option like this: 388 | 389 | ```js 390 | options = {allowUnmocked: true}; 391 | var scope = nock('http://my.existing.service.com', options) 392 | .get('/my/url') 393 | .reply(200, 'OK!'); 394 | 395 | // GET /my/url => goes through nock 396 | // GET /other/url => actually makes request to the server 397 | ``` 398 | 399 | # Expectations 400 | 401 | Every time an HTTP request is performed for a scope that is mocked, Nock expects to find a handler for it. If it doesn't, it will throw an error. 402 | 403 | Calls to nock() return a scope which you can assert by calling `scope.done()`. This will assert that all specified calls on that scope were performed. 404 | 405 | Example: 406 | 407 | ```js 408 | var google = nock('http://google.com') 409 | .get('/') 410 | .reply(200, 'Hello from Google!'); 411 | 412 | // do some stuff 413 | 414 | setTimeout(function() { 415 | google.done(); // will throw an assertion error if meanwhile a "GET http://google.com" was not performed. 416 | }, 5000); 417 | ``` 418 | 419 | ## .isDone() 420 | 421 | You can also call `isDone()`, which will return a boolean saying if all the expectations are met or not (instead of throwing an exception); 422 | 423 | ## .cleanAll() 424 | 425 | You can cleanup all the prepared mocks (could be useful to cleanup some state after a failed test) like this: 426 | 427 | ```js 428 | nock.cleanAll(); 429 | ``` 430 | ## .persist() 431 | 432 | You can make all the interceptors for a scope persist by calling `.persist()` on it: 433 | 434 | ```js 435 | var scope = nock('http://persisssists.con') 436 | .persist() 437 | .get('/') 438 | .reply(200, 'Persisting all the way'); 439 | ``` 440 | 441 | ## .pendingMocks() 442 | 443 | If a scope is not done, you can inspect the scope to infer which ones are still pending using the `scope.pendingMocks()` function: 444 | 445 | ```js 446 | if (!scope.isDone()) { 447 | console.error('pending mocks: %j', scope.pendingMocks()); 448 | } 449 | ``` 450 | 451 | # Logging 452 | 453 | Nock can log matches if you pass in a log function like this: 454 | 455 | ```js 456 | var google = nock('http://google.com') 457 | .log(console.log) 458 | ... 459 | ``` 460 | 461 | # Restoring 462 | 463 | You can restore the HTTP interceptor to the normal unmocked behaviour by calling: 464 | 465 | ```js 466 | nock.restore(); 467 | ``` 468 | 469 | # Turning Nock Off (experimental!) 470 | 471 | You can bypass Nock completely by setting `NOCK_OFF` environment variable to `"true"`. 472 | 473 | This way you can have your tests hit the real servers just by switching on this environment variable. 474 | 475 | ```js 476 | $ NOCK_OFF=true node my_test.js 477 | ``` 478 | 479 | # Enable/Disable real HTTP request 480 | 481 | As default, if you do not mock a host, a real HTTP request will do, but sometimes you should not permit real HTTP request, so... 482 | 483 | For disable real http request. 484 | 485 | ```js 486 | nock.disableNetConnect(); 487 | ``` 488 | 489 | So, if you try to request any host not 'nocked', it will thrown an NetConnectNotAllowedError. 490 | 491 | ```js 492 | nock.disableNetConnect(); 493 | http.get('http://google.com/'); 494 | // this code throw NetConnectNotAllowedError with message: 495 | // Nock: Not allow net connect for "google.com:80" 496 | ``` 497 | 498 | For enabled real HTTP requests. 499 | 500 | ```js 501 | nock.enableNetConnect(); 502 | ``` 503 | 504 | You could restrict real HTTP request... 505 | 506 | ```js 507 | // using a string 508 | nock.enableNetConnect('amazon.com'); 509 | 510 | // or a RegExp 511 | nock.enableNetConnect(/(amazon|github).com/); 512 | 513 | http.get('http://www.amazon.com/'); 514 | http.get('http://github.com/'); // only for second example 515 | 516 | // This request will be done! 517 | http.get('http://google.com/'); 518 | // this will throw NetConnectNotAllowedError with message: 519 | // Nock: Not allow net connect for "google.com:80" 520 | ``` 521 | 522 | # Recording 523 | 524 | This is a cool feature: 525 | 526 | Guessing what the HTTP calls are is a mess, specially if you are introducing nock on your already-coded tests. 527 | 528 | For these cases where you want to mock an existing live system you can record and playback the HTTP calls like this: 529 | 530 | ```js 531 | nock.recorder.rec(); 532 | // Some HTTP calls happen and the nock code necessary to mock 533 | // those calls will be outputted to console 534 | ``` 535 | 536 | ## `dont_print` option 537 | 538 | If you just want to capture the generated code into a var as an array you can use: 539 | 540 | ```js 541 | nock.recorder.rec({ 542 | dont_print: true 543 | }); 544 | // ... some HTTP calls 545 | var nockCalls = nock.recorder.play(); 546 | ``` 547 | 548 | The `nockCalls` var will contain an array of strings representing the generated code you need. 549 | 550 | Copy and paste that code into your tests, customize at will, and you're done! 551 | 552 | (Remember that you should do this one test at a time). 553 | 554 | ## `output_objects` option 555 | 556 | In case you want to generate the code yourself or use the test data in some other way, you can pass the `output_objects` option to `rec`: 557 | 558 | ```js 559 | nock.recorder.rec({ 560 | output_objects: true 561 | }); 562 | // ... some HTTP calls 563 | var nockCallObjects = nock.recorder.play(); 564 | ``` 565 | 566 | The returned call objects have the following properties: 567 | 568 | * `scope` - the scope of the call including the protocol and non-standard ports (e.g. `'https://github.com:12345'`) 569 | * `method` - the HTTP verb of the call (e.g. `'GET'`) 570 | * `path` - the path of the call (e.g. `'/pgte/nock'`) 571 | * `body` - the body of the call, if any 572 | * `status` - the HTTP status of the reply (e.g. `200`) 573 | * `response` - the body of the reply which can be a JSON, string, hex string representing binary buffers or an array of such hex strings (when handling `content-encoded` in reply header) 574 | * `headers` - the headers of the reply 575 | * `reqheader` - the headers of the request 576 | 577 | If you save this as a JSON file, you can load them directly through `nock.load(path)`. Then you can post-process them before using them in the tests for example to add them request body filtering (shown here fixing timestamps to match the ones captured during recording): 578 | 579 | ```js 580 | nocks = nock.load(pathToJson); 581 | nocks.forEach(function(nock) { 582 | nock.filteringRequestBody = function(body) { 583 | if(typeof(body) !== 'string') { 584 | return body; 585 | } 586 | 587 | return body.replace(/(timestamp):([0-9]+)/g, function(match, key, value) { 588 | return key + ':timestampCapturedDuringRecording' 589 | }); 590 | }; 591 | }); 592 | ``` 593 | 594 | Alternatively, if you need to pre-process the captured nock definitions before using them (e.g. to add scope filtering) then you can use `nock.loadDefs(path)` and `nock.define(nockDefs)`. Shown here is scope filtering for Dropbox node module which constantly changes the subdomain to which it sends the requests: 595 | 596 | ```js 597 | // Pre-process the nock definitions as scope filtering has to be defined before the nocks are defined (due to its very hacky nature). 598 | var nockDefs = nock.loadDefs(pathToJson); 599 | nockDefs.forEach(function(def) { 600 | // Do something with the definition object e.g. scope filtering. 601 | def.options = def.options || {}; 602 | def.options.filteringScope = function(scope) { 603 | return /^https:\/\/api[0-9]*.dropbox.com/.test(scope); 604 | }; 605 | } 606 | 607 | // Load the nocks from pre-processed definitions. 608 | var nocks = nock.define(nockDefs); 609 | ``` 610 | 611 | ## `enable_reqheaders_recording` option 612 | 613 | Recording request headers by default is deemed more trouble than its worth as some of them depend on the timestamp or other values that may change after the tests have been recorder thus leading to complex postprocessing of recorded tests. Thus by default the request headers are not recorded. 614 | 615 | The genuine use cases for recording request headers (e.g. checking authorization) can be handled manually or by using `enable_reqheaders_recording` in `recorder.rec()` options. 616 | 617 | ```js 618 | nock.recorder.rec({ 619 | dont_print: true, 620 | output_objects: true, 621 | enable_reqheaders_recording: true 622 | }); 623 | ``` 624 | 625 | Note that even when request headers recording is enabled Nock will never record `user-agent` headers. `user-agent` values change with the version of Node and underlying operating system and are thus useless for matching as all that they can indicate is that the user agent isn't the one that was used to record the tests. 626 | 627 | ## .removeInterceptor() 628 | This allows removing a specific interceptor for a url. It's useful when there's a list of common interceptors but one test requires one of them to behave differently. 629 | 630 | Examples: 631 | ```js 632 | nock.removeInterceptor({ 633 | hostname : 'localhost', 634 | path : '/mockedResource' 635 | }); 636 | ``` 637 | 638 | ```js 639 | nock.removeInterceptor({ 640 | hostname : 'localhost', 641 | path : '/login' 642 | method: 'POST' 643 | proto : 'https' 644 | }); 645 | ``` 646 | 647 | # How does it work? 648 | 649 | Nock works by overriding Node's `http.request` function. Also, it overrides `http.ClientRequest` too to cover for modules that use it directly. 650 | 651 | # PROTIP 652 | 653 | If you don't want to match the request body you can use this trick (by @theycallmeswift): 654 | 655 | ```js 656 | var scope = nock('http://api.myservice.com') 657 | .filteringRequestBody(function(path) { 658 | return '*'; 659 | }) 660 | .post('/some_uri', '*') 661 | .reply(200, 'OK'); 662 | ``` 663 | 664 | # License 665 | 666 | (The MIT License) 667 | 668 | Copyright (c) 2011 Pedro Teixeira. http://about.me/pedroteixeira 669 | 670 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 671 | 672 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 673 | 674 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 675 | -------------------------------------------------------------------------------- /tests/test_intercept.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fs = require('fs'); 4 | var nock = require('../.'); 5 | var http = require('http'); 6 | var https = require('https'); 7 | var util = require('util'); 8 | var events = require('events'); 9 | var stream = require('stream'); 10 | var test = require('tap').test; 11 | var mikealRequest = require('request'); 12 | var superagent = require('superagent'); 13 | var needle = require("needle"); 14 | var restify = require('restify'); 15 | var domain = require('domain'); 16 | var hyperquest = require('hyperquest'); 17 | 18 | var globalCount; 19 | 20 | test("setup", function(t) { 21 | globalCount = Object.keys(global).length; 22 | t.end(); 23 | }); 24 | 25 | test("double activation throws exception", function(t) { 26 | nock.restore(); 27 | t.false(nock.isActive()); 28 | try { 29 | nock.activate(); 30 | t.true(nock.isActive()); 31 | nock.activate(); 32 | // This line should never be reached. 33 | t.false(true); 34 | } catch(e) { 35 | t.equal(e.toString(), 'Error: Nock already active'); 36 | } 37 | t.true(nock.isActive()); 38 | t.end(); 39 | }); 40 | 41 | test("allow override works (2)", function(t) { 42 | var scope = 43 | nock("https://httpbin.org",{allowUnmocked: true}). 44 | post("/post"). 45 | reply(200,"99problems"); 46 | 47 | var options = { 48 | method: "POST", 49 | uri: "https://httpbin.org/post", 50 | json: { 51 | some: "data" 52 | } 53 | }; 54 | 55 | mikealRequest(options, function(err, resp, body) { 56 | scope.done(); 57 | t.end(); 58 | return console.log(resp.statusCode, body); 59 | }); 60 | }); 61 | 62 | test("get gets mocked", function(t) { 63 | var dataCalled = false; 64 | 65 | var scope = nock('http://www.google.com') 66 | .get('/') 67 | .reply(200, "Hello World!"); 68 | 69 | var req = http.request({ 70 | host: "www.google.com", 71 | path: '/', 72 | port: 80 73 | }, function(res) { 74 | 75 | t.equal(res.statusCode, 200); 76 | res.on('end', function() { 77 | t.ok(dataCalled); 78 | scope.done(); 79 | t.end(); 80 | }); 81 | res.on('data', function(data) { 82 | dataCalled = true; 83 | t.ok(data instanceof Buffer, "data should be buffer"); 84 | t.equal(data.toString(), "Hello World!", "response should match"); 85 | }); 86 | 87 | }); 88 | 89 | req.end(); 90 | }); 91 | 92 | test("post", function(t) { 93 | var dataCalled = false; 94 | 95 | var scope = nock('http://www.google.com') 96 | .post('/form') 97 | .reply(201, "OK!"); 98 | 99 | var req = http.request({ 100 | host: "www.google.com", 101 | method: 'POST', 102 | path: '/form', 103 | port: 80 104 | }, function(res) { 105 | 106 | t.equal(res.statusCode, 201); 107 | res.on('end', function() { 108 | t.ok(dataCalled); 109 | scope.done(); 110 | t.end(); 111 | }); 112 | res.on('data', function(data) { 113 | dataCalled = true; 114 | t.ok(data instanceof Buffer, "data should be buffer"); 115 | t.equal(data.toString(), "OK!", "response should match"); 116 | }); 117 | 118 | }); 119 | 120 | req.end(); 121 | }); 122 | 123 | 124 | 125 | test("post with empty response body", function(t) { 126 | var scope = nock('http://www.google.com') 127 | .post('/form') 128 | .reply(200); 129 | 130 | var req = http.request({ 131 | host: "www.google.com", 132 | method: 'POST', 133 | path: '/form', 134 | port: 80 135 | }, function(res) { 136 | 137 | t.equal(res.statusCode, 200); 138 | res.on('end', function() { 139 | scope.done(); 140 | t.end(); 141 | }); 142 | res.on('data', function() { 143 | t.fail("No body should be returned"); 144 | }); 145 | 146 | }); 147 | req.end(); 148 | }); 149 | 150 | test("post, lowercase", function(t) { 151 | var dataCalled = false; 152 | 153 | var scope = nock('http://www.google.com') 154 | .post('/form') 155 | .reply(200, "OK!"); 156 | 157 | var req = http.request({ 158 | host: "www.google.com", 159 | method: 'POST', 160 | path: '/form', 161 | port: 80 162 | }, function(res) { 163 | 164 | t.equal(res.statusCode, 200); 165 | res.on('end', function() { 166 | t.notOk(dataCalled); 167 | scope.done(); 168 | t.end(); 169 | }); 170 | res.on('data', function() { 171 | dataCalled = true; 172 | t.end(); 173 | }); 174 | }); 175 | 176 | req.end(); 177 | }); 178 | 179 | test("get with reply callback", function(t) { 180 | var scope = nock('http://www.google.com') 181 | .get('/') 182 | .reply(200, function() { 183 | return 'OK!'; 184 | }); 185 | 186 | var req = http.request({ 187 | host: "www.google.com", 188 | path: '/', 189 | port: 80 190 | }, function(res) { 191 | res.on('end', function() { 192 | scope.done(); 193 | t.end(); 194 | }); 195 | res.on('data', function(data) { 196 | t.equal(data.toString(), 'OK!', 'response should match'); 197 | }); 198 | }); 199 | 200 | req.end(); 201 | }); 202 | 203 | test("get to different subdomain with reply callback and filtering scope", 204 | function(t) { 205 | // We scope for www.google.com but through scope filtering we 206 | // will accept any .google.com 207 | var scope = nock('http://www.google.com', { 208 | filteringScope: function(scope) { 209 | return /^http:\/\/.*\.google\.com/.test(scope); 210 | } 211 | }) 212 | .get('/') 213 | .reply(200, function() { 214 | return 'OK!'; 215 | }); 216 | 217 | var req = http.request({ 218 | host: "any-subdomain-will-do.google.com", 219 | path: '/', 220 | port: 80 221 | }, function(res) { 222 | res.on('end', function() { 223 | scope.done(); 224 | t.end(); 225 | }); 226 | res.on('data', function(data) { 227 | t.equal(data.toString(), 'OK!', 'response should match'); 228 | }); 229 | }); 230 | 231 | req.end(); 232 | }); 233 | 234 | test("get with reply callback returning object", function(t) { 235 | var scope = nock('http://www.googlezzzz.com') 236 | .get('/') 237 | .reply(200, function() { 238 | return { message: 'OK!' }; 239 | }); 240 | 241 | var req = http.request({ 242 | host: "www.googlezzzz.com", 243 | path: '/', 244 | port: 80 245 | }, function(res) { 246 | res.on('end', function() { 247 | scope.done(); 248 | t.end(); 249 | }); 250 | res.on('data', function(data) { 251 | t.equal(data.toString(), JSON.stringify({ message: 'OK!' }), 252 | 'response should match'); 253 | }); 254 | }); 255 | 256 | req.end(); 257 | }); 258 | 259 | test("post with reply callback, uri, and request body", function(t) { 260 | var input = 'key=val'; 261 | 262 | var scope = nock('http://www.google.com') 263 | .post('/echo', input) 264 | .reply(200, function(uri, body) { 265 | return ['OK', uri, body].join(' '); 266 | }); 267 | 268 | var req = http.request({ 269 | host: "www.google.com" 270 | , method: 'POST' 271 | , path: '/echo' 272 | , port: 80 273 | }, function(res) { 274 | res.on('end', function() { 275 | scope.done(); 276 | t.end(); 277 | }); 278 | res.on('data', function(data) { 279 | t.equal(data.toString(), 'OK /echo key=val' , 'response should match'); 280 | }); 281 | }); 282 | 283 | req.write(input); 284 | req.end(); 285 | }); 286 | 287 | test("post with chaining on call", function(t) { 288 | var input = 'key=val'; 289 | 290 | var scope = nock('http://www.google.com') 291 | .post('/echo', input) 292 | .reply(200, function(uri, body) { 293 | return ['OK', uri, body].join(' '); 294 | }); 295 | 296 | var req = http.request({ 297 | host: "www.google.com" 298 | , method: 'POST' 299 | , path: '/echo' 300 | , port: 80 301 | }, function(res) { 302 | res.on('end', function() { 303 | scope.done(); 304 | t.end(); 305 | }); 306 | res.on('data', function(data) { 307 | t.equal(data.toString(), 'OK /echo key=val' , 'response should match'); 308 | }); 309 | }).on('error', function(error){ 310 | t.equal(error, null); 311 | t.end(); 312 | }); 313 | req.end(input); 314 | }); 315 | 316 | test("reply with callback and filtered path and body", function(t) { 317 | var noPrematureExecution = false; 318 | 319 | var scope = nock('http://www.realcallback.com') 320 | .filteringPath(/.*/, '*') 321 | .filteringRequestBody(/.*/, '*') 322 | .post('*', '*') 323 | .reply(200, function(uri, body) { 324 | t.assert(noPrematureExecution); 325 | return ['OK', uri, body].join(' '); 326 | }); 327 | 328 | var req = http.request({ 329 | host: "www.realcallback.com" 330 | , method: 'POST' 331 | , path: '/original/path' 332 | , port: 80 333 | }, function(res) { 334 | t.equal(res.statusCode, 200); 335 | res.on('end', function() { 336 | scope.done(); 337 | t.end(); 338 | }); 339 | res.on('data', function(data) { 340 | t.equal(data.toString(), 'OK /original/path original=body' , 'response should match'); 341 | }); 342 | }); 343 | 344 | noPrematureExecution = true; 345 | req.end('original=body'); 346 | }); 347 | 348 | test("isDone", function(t) { 349 | var scope = nock('http://www.google.com') 350 | .get('/') 351 | .reply(200, "Hello World!"); 352 | 353 | t.notOk(scope.isDone(), "not done when a request is outstanding"); 354 | 355 | var req = http.request({ 356 | host: "www.google.com" 357 | , path: '/' 358 | , port: 80 359 | }, function(res) { 360 | t.equal(res.statusCode, 200); 361 | res.on('end', function() { 362 | t.ok(scope.isDone(), "done after request is made"); 363 | scope.done(); 364 | t.end(); 365 | }); 366 | }); 367 | 368 | req.end(); 369 | }); 370 | 371 | test("requireDone", function(t) { 372 | var scope = nock('http://www.google.com') 373 | .get('/', false, { requireDone: false }) 374 | .reply(200, "Hello World!"); 375 | 376 | t.ok(scope.isDone(), "done when a requireDone is set to false"); 377 | 378 | scope.get('/', false, { requireDone: true}) 379 | .reply(200, "Hello World!"); 380 | 381 | t.notOk(scope.isDone(), "not done when a requireDone is explicitly set to true"); 382 | 383 | nock.cleanAll() 384 | t.end(); 385 | }); 386 | 387 | test("request headers exposed", function(t) { 388 | 389 | var scope = nock('http://www.headdy.com') 390 | .get('/') 391 | .reply(200, "Hello World!", {'X-My-Headers': 'My Header value'}); 392 | 393 | var req = http.get({ 394 | host: "www.headdy.com" 395 | , method: 'GET' 396 | , path: '/' 397 | , port: 80 398 | , headers: {'X-My-Headers': 'My custom Header value'} 399 | }, function(res) { 400 | res.on('end', function() { 401 | scope.done(); 402 | t.end(); 403 | }); 404 | }); 405 | 406 | t.equivalent(req._headers, {'x-my-headers': 'My custom Header value', 'host': 'www.headdy.com'}); 407 | }); 408 | 409 | test("headers work", function(t) { 410 | 411 | var scope = nock('http://www.headdy.com') 412 | .get('/') 413 | .reply(200, "Hello World!", {'X-My-Headers': 'My Header value'}); 414 | 415 | var req = http.request({ 416 | host: "www.headdy.com" 417 | , method: 'GET' 418 | , path: '/' 419 | , port: 80 420 | }, function(res) { 421 | t.equal(res.statusCode, 200); 422 | res.on('end', function() { 423 | t.equivalent(res.headers, {'x-my-headers': 'My Header value'}); 424 | scope.done(); 425 | t.end(); 426 | }); 427 | }); 428 | 429 | req.end(); 430 | 431 | }); 432 | 433 | test("match headers", function(t) { 434 | var scope = nock('http://www.headdy.com') 435 | .get('/') 436 | .matchHeader('x-my-headers', 'My custom Header value') 437 | .reply(200, "Hello World!"); 438 | 439 | http.get({ 440 | host: "www.headdy.com" 441 | , method: 'GET' 442 | , path: '/' 443 | , port: 80 444 | , headers: {'X-My-Headers': 'My custom Header value'} 445 | }, function(res) { 446 | res.setEncoding('utf8'); 447 | t.equal(res.statusCode, 200); 448 | 449 | res.on('data', function(data) { 450 | t.equal(data, 'Hello World!'); 451 | }); 452 | 453 | res.on('end', function() { 454 | scope.done(); 455 | t.end(); 456 | }); 457 | }); 458 | 459 | }); 460 | 461 | test("multiple match headers", function(t) { 462 | var scope = nock('http://www.headdy.com') 463 | .get('/') 464 | .matchHeader('x-my-headers', 'My custom Header value') 465 | .reply(200, "Hello World!") 466 | .get('/') 467 | .matchHeader('x-my-headers', 'other value') 468 | .reply(200, "Hello World other value!"); 469 | 470 | http.get({ 471 | host: "www.headdy.com" 472 | , method: 'GET' 473 | , path: '/' 474 | , port: 80 475 | , headers: {'X-My-Headers': 'other value'} 476 | }, function(res) { 477 | res.setEncoding('utf8'); 478 | t.equal(res.statusCode, 200); 479 | 480 | res.on('data', function(data) { 481 | t.equal(data, 'Hello World other value!'); 482 | }); 483 | 484 | res.on('end', function() { 485 | http.get({ 486 | host: "www.headdy.com" 487 | , method: 'GET' 488 | , path: '/' 489 | , port: 80 490 | , headers: {'X-My-Headers': 'My custom Header value'} 491 | }, function(res) { 492 | res.setEncoding('utf8'); 493 | t.equal(res.statusCode, 200); 494 | 495 | res.on('data', function(data) { 496 | t.equal(data, 'Hello World!'); 497 | }); 498 | 499 | res.on('end', function() { 500 | scope.done(); 501 | t.end(); 502 | }); 503 | }); 504 | }); 505 | }); 506 | 507 | }); 508 | 509 | test("match headers with regexp", function(t) { 510 | var scope = nock('http://www.headier.com') 511 | .get('/') 512 | .matchHeader('x-my-headers', /My He.d.r [0-9.]+/) 513 | .reply(200, "Hello World!"); 514 | 515 | http.get({ 516 | host: "www.headier.com" 517 | , method: 'GET' 518 | , path: '/' 519 | , port: 80 520 | , headers: {'X-My-Headers': 'My Header 1.0'} 521 | }, function(res) { 522 | res.setEncoding('utf8'); 523 | t.equal(res.statusCode, 200); 524 | 525 | res.on('data', function(data) { 526 | t.equal(data, 'Hello World!'); 527 | }); 528 | 529 | res.on('end', function() { 530 | scope.done(); 531 | t.end(); 532 | }); 533 | }); 534 | 535 | }); 536 | 537 | test("match headers on number with regexp", function(t) { 538 | var scope = nock('http://www.headier.com') 539 | .get('/') 540 | .matchHeader('x-my-headers', /\d+/) 541 | .reply(200, "Hello World!"); 542 | 543 | http.get({ 544 | host: "www.headier.com" 545 | , method: 'GET' 546 | , path: '/' 547 | , port: 80 548 | , headers: {'X-My-Headers': 123} 549 | }, function(res) { 550 | res.setEncoding('utf8'); 551 | t.equal(res.statusCode, 200); 552 | 553 | res.on('data', function(data) { 554 | t.equal(data, 'Hello World!'); 555 | }); 556 | 557 | res.on('end', function() { 558 | scope.done(); 559 | t.end(); 560 | }); 561 | }); 562 | 563 | }); 564 | 565 | test("match headers with function", function(t) { 566 | var scope = nock('http://www.headier.com') 567 | .get('/') 568 | .matchHeader('x-my-headers', function (val) { 569 | return val > 123; 570 | }) 571 | .reply(200, "Hello World!"); 572 | 573 | http.get({ 574 | host: "www.headier.com" 575 | , method: 'GET' 576 | , path: '/' 577 | , port: 80 578 | , headers: {'X-My-Headers': 456} 579 | }, function(res) { 580 | res.setEncoding('utf8'); 581 | t.equal(res.statusCode, 200); 582 | 583 | res.on('data', function(data) { 584 | t.equal(data, 'Hello World!'); 585 | }); 586 | 587 | res.on('end', function() { 588 | scope.done(); 589 | t.end(); 590 | }); 591 | }); 592 | 593 | }); 594 | 595 | test("match all headers", function(t) { 596 | var scope = nock('http://api.headdy.com') 597 | .matchHeader('accept', 'application/json') 598 | .get('/one') 599 | .reply(200, { hello: "world" }) 600 | .get('/two') 601 | .reply(200, { a: 1, b: 2, c: 3 }); 602 | 603 | var ended = 0; 604 | function callback() { 605 | ended += 1; 606 | if (ended === 2) { 607 | scope.done(); 608 | t.end(); 609 | } 610 | } 611 | 612 | http.get({ 613 | host: "api.headdy.com" 614 | , path: '/one' 615 | , port: 80 616 | , headers: {'Accept': 'application/json'} 617 | }, function(res) { 618 | res.setEncoding('utf8'); 619 | t.equal(res.statusCode, 200); 620 | 621 | res.on('data', function(data) { 622 | t.equal(data, '{"hello":"world"}'); 623 | }); 624 | 625 | res.on('end', callback); 626 | }); 627 | 628 | http.get({ 629 | host: "api.headdy.com" 630 | , path: '/two' 631 | , port: 80 632 | , headers: {'accept': 'application/json'} 633 | }, function(res) { 634 | res.setEncoding('utf8'); 635 | t.equal(res.statusCode, 200); 636 | 637 | res.on('data', function(data) { 638 | t.equal(data, '{"a":1,"b":2,"c":3}'); 639 | }); 640 | 641 | res.on('end', callback); 642 | }); 643 | 644 | }); 645 | 646 | test("header manipulation", function(t) { 647 | var scope = nock('http://example.com') 648 | .get('/accounts') 649 | .reply(200, { accounts: [{ id: 1, name: 'Joe Blow' }] }) 650 | , req; 651 | 652 | req = http.get({ host: 'example.com', path: '/accounts' }, function (res) { 653 | res.on('end', function () { 654 | scope.done(); 655 | t.end(); 656 | }); 657 | }); 658 | 659 | req.setHeader('X-Custom-Header', 'My Value'); 660 | t.equal(req.getHeader('X-Custom-Header'), 'My Value', 'Custom header was not set'); 661 | 662 | req.removeHeader('X-Custom-Header'); 663 | t.notOk(req.getHeader('X-Custom-Header'), 'Custom header was not removed'); 664 | 665 | req.end(); 666 | }); 667 | 668 | test("head", function(t) { 669 | var dataCalled = false; 670 | 671 | var scope = nock('http://www.google.com') 672 | .head('/form') 673 | .reply(201, "OK!"); 674 | 675 | var req = http.request({ 676 | host: "www.google.com" 677 | , method: 'HEAD' 678 | , path: '/form' 679 | , port: 80 680 | }, function(res) { 681 | 682 | t.equal(res.statusCode, 201); 683 | res.on('end', function() { 684 | scope.done(); 685 | t.end(); 686 | }); 687 | }); 688 | 689 | req.end(); 690 | }); 691 | 692 | test("body data is differentiating", function(t) { 693 | var doneCount = 0 694 | , scope = nock('http://www.boddydiff.com') 695 | .post('/', 'abc') 696 | .reply(200, "Hey 1") 697 | .post('/', 'def') 698 | .reply(200, "Hey 2"); 699 | 700 | function done(t) { 701 | doneCount += 1; 702 | t.end(); 703 | }; 704 | 705 | 706 | t.test("A", function(t) { 707 | var req = http.request({ 708 | host: "www.boddydiff.com" 709 | , method: 'POST' 710 | , path: '/' 711 | , port: 80 712 | }, function(res) { 713 | var dataCalled = false; 714 | t.equal(res.statusCode, 200); 715 | res.on('end', function() { 716 | t.ok(dataCalled); 717 | done(t); 718 | }); 719 | res.on('data', function(data) { 720 | dataCalled = true; 721 | t.ok(data instanceof Buffer, "data should be buffer"); 722 | t.equal(data.toString(), "Hey 1", "response should match"); 723 | }); 724 | }); 725 | 726 | req.end('abc'); 727 | }); 728 | 729 | t.test("B", function(t) { 730 | var req = http.request({ 731 | host: "www.boddydiff.com" 732 | , method: 'POST' 733 | , path: '/' 734 | , port: 80 735 | }, function(res) { 736 | var dataCalled = false; 737 | t.equal(res.statusCode, 200); 738 | res.on('end', function() { 739 | t.ok(dataCalled); 740 | done(t); 741 | }); 742 | res.on('data', function(data) { 743 | dataCalled = true; 744 | t.ok(data instanceof Buffer, "data should be buffer"); 745 | t.equal(data.toString(), "Hey 2", "response should match"); 746 | }); 747 | }); 748 | 749 | req.end('def'); 750 | }); 751 | 752 | }); 753 | 754 | test("chaining", function(t) { 755 | var repliedCount = 0; 756 | var scope = nock('http://www.spiffy.com') 757 | .get('/') 758 | .reply(200, "Hello World!") 759 | .post('/form') 760 | .reply(201, "OK!"); 761 | 762 | function endOne(t) { 763 | repliedCount += 1; 764 | if (t === 2) { 765 | scope.done(); 766 | } 767 | t.end(); 768 | } 769 | 770 | t.test("post", function(t) { 771 | var dataCalled; 772 | var req = http.request({ 773 | host: "www.spiffy.com" 774 | , method: 'POST' 775 | , path: '/form' 776 | , port: 80 777 | }, function(res) { 778 | 779 | t.equal(res.statusCode, 201); 780 | res.on('end', function() { 781 | t.ok(dataCalled); 782 | endOne(t); 783 | }); 784 | res.on('data', function(data) { 785 | dataCalled = true; 786 | t.ok(data instanceof Buffer, "data should be buffer"); 787 | t.equal(data.toString(), "OK!", "response should match"); 788 | }); 789 | 790 | }); 791 | 792 | req.end(); 793 | }); 794 | 795 | t.test("get", function(t) { 796 | var dataCalled; 797 | var req = http.request({ 798 | host: "www.spiffy.com" 799 | , method: 'GET' 800 | , path: '/' 801 | , port: 80 802 | }, function(res) { 803 | 804 | t.equal(res.statusCode, 200); 805 | res.on('end', function() { 806 | t.ok(dataCalled); 807 | scope.done(); 808 | t.end(); 809 | }); 810 | res.on('data', function(data) { 811 | dataCalled = true; 812 | t.ok(data instanceof Buffer, "data should be buffer"); 813 | t.equal(data.toString(), "Hello World!", "response should match"); 814 | }); 815 | 816 | }); 817 | 818 | req.end(); 819 | }); 820 | }); 821 | 822 | test("encoding", function(t) { 823 | var dataCalled = false 824 | 825 | var scope = nock('http://www.encoderz.com') 826 | .get('/') 827 | .reply(200, "Hello World!"); 828 | 829 | var req = http.request({ 830 | host: "www.encoderz.com" 831 | , path: '/' 832 | , port: 80 833 | }, function(res) { 834 | 835 | res.setEncoding('base64'); 836 | 837 | t.equal(res.statusCode, 200); 838 | res.on('end', function() { 839 | t.ok(dataCalled); 840 | scope.done(); 841 | t.end(); 842 | }); 843 | res.on('data', function(data) { 844 | dataCalled = true; 845 | t.type(data, 'string', "data should be string"); 846 | t.equal(data, "SGVsbG8gV29ybGQh", "response should match base64 encoding"); 847 | }); 848 | 849 | }); 850 | 851 | req.end(); 852 | }); 853 | 854 | test("reply with file", function(t) { 855 | var dataCalled = false 856 | 857 | var scope = nock('http://www.filereplier.com') 858 | .get('/') 859 | .replyWithFile(200, __dirname + '/../assets/reply_file_1.txt') 860 | .get('/test') 861 | .reply(200, 'Yay!'); 862 | 863 | var req = http.request({ 864 | host: "www.filereplier.com" 865 | , path: '/' 866 | , port: 80 867 | }, function(res) { 868 | 869 | t.equal(res.statusCode, 200); 870 | res.on('end', function() { 871 | t.ok(dataCalled); 872 | t.end(); 873 | }); 874 | res.on('data', function(data) { 875 | dataCalled = true; 876 | t.equal(data.toString(), "Hello from the file!", "response should match"); 877 | }); 878 | 879 | }); 880 | 881 | req.end(); 882 | 883 | }); 884 | 885 | test("reply with file and pipe response", function(t) { 886 | var scope = nock('http://www.files.com') 887 | .get('/') 888 | .replyWithFile(200, __dirname + '/../assets/reply_file_1.txt') 889 | 890 | var req = http.get({ 891 | host: "www.files.com" 892 | , path: '/' 893 | , port: 80 894 | }, function(res) { 895 | var str = ''; 896 | var fakeStream = new(require('stream').Stream); 897 | fakeStream.writable = true; 898 | 899 | fakeStream.write = function(d) { 900 | str += d; 901 | }; 902 | 903 | fakeStream.end = function() { 904 | t.equal(str, "Hello from the file!", "response should match"); 905 | t.end(); 906 | }; 907 | 908 | res.pipe(fakeStream); 909 | res.setEncoding('utf8'); 910 | t.equal(res.statusCode, 200); 911 | 912 | }); 913 | 914 | }); 915 | 916 | test("reply with file with headers", function(t) { 917 | var dataCalled = false 918 | 919 | var scope = nock('http://www.filereplier2.com') 920 | .get('/') 921 | .replyWithFile(200, __dirname + '/../assets/reply_file_2.txt.gz', { 922 | 'content-encoding': 'gzip' 923 | }); 924 | 925 | var req = http.request({ 926 | host: "www.filereplier2.com" 927 | , path: '/' 928 | , port: 80 929 | }, function(res) { 930 | 931 | t.equal(res.statusCode, 200); 932 | res.on('end', function() { 933 | t.ok(dataCalled); 934 | t.end(); 935 | }); 936 | res.on('data', function(data) { 937 | dataCalled = true; 938 | t.equal(data.length, 57); 939 | }); 940 | 941 | }); 942 | 943 | req.end(); 944 | 945 | }); 946 | 947 | test("reply with file with mikeal/request", function(t) { 948 | var scope = nock('http://www.files.com') 949 | .get('/') 950 | .replyWithFile(200, __dirname + '/../assets/reply_file_1.txt') 951 | 952 | var options = { uri: 'http://www.files.com/', onResponse: true }; 953 | mikealRequest('http://www.files.com/', function(err, res, body) { 954 | if (err) { 955 | throw err; 956 | } 957 | 958 | res.setEncoding('utf8'); 959 | t.equal(res.statusCode, 200); 960 | 961 | t.equal(body, "Hello from the file!", "response should match"); 962 | t.end(); 963 | }); 964 | 965 | }); 966 | 967 | test("reply with JSON", function(t) { 968 | var dataCalled = false 969 | 970 | var scope = nock('http://www.jsonreplier.com') 971 | .get('/') 972 | .reply(200, {hello: "world"}); 973 | 974 | var req = http.request({ 975 | host: "www.jsonreplier.com" 976 | , path: '/' 977 | , port: 80 978 | }, function(res) { 979 | 980 | res.setEncoding('utf8'); 981 | t.equal(res.statusCode, 200); 982 | t.equal(res.headers['content-type'], 'application/json'); 983 | res.on('end', function() { 984 | t.ok(dataCalled); 985 | scope.done(); 986 | t.end(); 987 | }); 988 | res.on('data', function(data) { 989 | dataCalled = true; 990 | t.equal(data.toString(), '{"hello":"world"}', "response should match"); 991 | }); 992 | 993 | }); 994 | 995 | req.end(); 996 | 997 | }); 998 | 999 | test("filter path with function", function(t) { 1000 | var scope = nock('http://www.filterurls.com') 1001 | .filteringPath(function(path) { 1002 | return '/?a=2&b=1'; 1003 | }) 1004 | .get('/?a=2&b=1') 1005 | .reply(200, "Hello World!"); 1006 | 1007 | var req = http.request({ 1008 | host: "www.filterurls.com" 1009 | , method: 'GET' 1010 | , path: '/?a=1&b=2' 1011 | , port: 80 1012 | }, function(res) { 1013 | t.equal(res.statusCode, 200); 1014 | res.on('end', function() { 1015 | scope.done(); 1016 | t.end(); 1017 | }); 1018 | }); 1019 | 1020 | req.end(); 1021 | }); 1022 | 1023 | test("filter path with regexp", function(t) { 1024 | var scope = nock('http://www.filterurlswithregexp.com') 1025 | .filteringPath(/\d/g, '3') 1026 | .get('/?a=3&b=3') 1027 | .reply(200, "Hello World!"); 1028 | 1029 | var req = http.request({ 1030 | host: "www.filterurlswithregexp.com" 1031 | , method: 'GET' 1032 | , path: '/?a=1&b=2' 1033 | , port: 80 1034 | }, function(res) { 1035 | t.equal(res.statusCode, 200); 1036 | res.on('end', function() { 1037 | scope.done(); 1038 | t.end(); 1039 | }); 1040 | }); 1041 | 1042 | req.end(); 1043 | }); 1044 | 1045 | test("filter body with function", function(t) { 1046 | var scope = nock('http://www.filterboddiez.com') 1047 | .filteringRequestBody(function(body) { 1048 | t.equal(body, 'mamma mia'); 1049 | return 'mamma tua'; 1050 | }) 1051 | .post('/', 'mamma tua') 1052 | .reply(200, "Hello World!"); 1053 | 1054 | var req = http.request({ 1055 | host: "www.filterboddiez.com" 1056 | , method: 'POST' 1057 | , path: '/' 1058 | , port: 80 1059 | }, function(res) { 1060 | t.equal(res.statusCode, 200); 1061 | res.on('end', function() { 1062 | scope.done(); 1063 | t.end(); 1064 | }); 1065 | }); 1066 | 1067 | req.end('mamma mia'); 1068 | }); 1069 | 1070 | test("filter body with regexp", function(t) { 1071 | var scope = nock('http://www.filterboddiezregexp.com') 1072 | .filteringRequestBody(/mia/, 'nostra') 1073 | .post('/', 'mamma nostra') 1074 | .reply(200, "Hello World!"); 1075 | 1076 | var req = http.request({ 1077 | host: "www.filterboddiezregexp.com" 1078 | , method: 'POST' 1079 | , path: '/' 1080 | , port: 80 1081 | }, function(res) { 1082 | t.equal(res.statusCode, 200); 1083 | res.on('end', function() { 1084 | scope.done(); 1085 | t.end(); 1086 | }); 1087 | }); 1088 | 1089 | req.end('mamma mia'); 1090 | }); 1091 | 1092 | test("abort request", function(t) { 1093 | var scope = nock('http://www.google.com') 1094 | .get('/hey') 1095 | .reply(200, 'nobody'); 1096 | 1097 | var req = http.request({ 1098 | host: 'www.google.com' 1099 | , path: '/hey' 1100 | }); 1101 | 1102 | req.on('response', function(res) { 1103 | res.on('close', function(err) { 1104 | t.equal(err.code, 'aborted'); 1105 | scope.done(); 1106 | t.end(); 1107 | }); 1108 | 1109 | res.on('end', function() { 1110 | t.true(false, 'this should never execute'); 1111 | }); 1112 | 1113 | req.abort(); 1114 | }); 1115 | 1116 | req.end(); 1117 | }); 1118 | 1119 | test("pause response before data", function(t) { 1120 | var scope = nock('http://www.mouse.com') 1121 | .get('/pauser') 1122 | .reply(200, 'nobody'); 1123 | 1124 | var req = http.request({ 1125 | host: 'www.mouse.com' 1126 | , path: '/pauser' 1127 | }); 1128 | 1129 | req.on('response', function(res) { 1130 | res.pause(); 1131 | 1132 | var waited = false; 1133 | setTimeout(function() { 1134 | waited = true; 1135 | res.resume(); 1136 | }, 500); 1137 | 1138 | res.on('data', function(data) { 1139 | t.true(waited); 1140 | }); 1141 | 1142 | res.on('end', function() { 1143 | scope.done(); 1144 | t.end(); 1145 | }); 1146 | }); 1147 | 1148 | req.end(); 1149 | }); 1150 | 1151 | test("pause response after data", function(t) { 1152 | var scope = nock('http://pauseme.com') 1153 | .get('/') 1154 | .reply(200, 'nobody'); 1155 | 1156 | var req = http.get({ 1157 | host: 'pauseme.com' 1158 | , path: '/' 1159 | }, function(res) { 1160 | var waited = false; 1161 | setTimeout(function() { 1162 | waited = true; 1163 | res.resume(); 1164 | }, 500); 1165 | 1166 | res.on('data', function(data) { 1167 | t.false(waited); 1168 | res.pause(); 1169 | }); 1170 | 1171 | res.on('end', function() { 1172 | t.true(waited); 1173 | scope.done(); 1174 | t.end(); 1175 | }); 1176 | }); 1177 | }); 1178 | 1179 | test("response pipe", function(t) { 1180 | var dest = (function() { 1181 | function Constructor() { 1182 | events.EventEmitter.call(this); 1183 | 1184 | this.buffer = new Buffer(0); 1185 | this.writable = true; 1186 | } 1187 | 1188 | util.inherits(Constructor, events.EventEmitter); 1189 | 1190 | Constructor.prototype.end = function() { 1191 | this.emit('end'); 1192 | }; 1193 | 1194 | Constructor.prototype.write = function(chunk) { 1195 | var buf = new Buffer(this.buffer.length + chunk.length); 1196 | 1197 | this.buffer.copy(buf); 1198 | chunk.copy(buf, this.buffer.length); 1199 | 1200 | this.buffer = buf; 1201 | 1202 | return true; 1203 | }; 1204 | 1205 | return new Constructor(); 1206 | })(); 1207 | 1208 | var scope = nock('http://pauseme.com') 1209 | .get('/') 1210 | .reply(200, 'nobody'); 1211 | 1212 | var req = http.get({ 1213 | host: 'pauseme.com' 1214 | , path: '/' 1215 | }, function(res) { 1216 | dest.on('pipe', function() { 1217 | t.pass('should emit "pipe" event') 1218 | }); 1219 | 1220 | dest.on('end', function() { 1221 | scope.done(); 1222 | t.equal(dest.buffer.toString(), 'nobody'); 1223 | t.end(); 1224 | }); 1225 | 1226 | res.pipe(dest); 1227 | }); 1228 | }); 1229 | 1230 | test("response pipe without implicit end", function(t) { 1231 | var dest = (function() { 1232 | function Constructor() { 1233 | events.EventEmitter.call(this); 1234 | 1235 | this.buffer = new Buffer(0); 1236 | this.writable = true; 1237 | } 1238 | 1239 | util.inherits(Constructor, events.EventEmitter); 1240 | 1241 | Constructor.prototype.end = function() { 1242 | this.emit('end'); 1243 | }; 1244 | 1245 | Constructor.prototype.write = function(chunk) { 1246 | var buf = new Buffer(this.buffer.length + chunk.length); 1247 | 1248 | this.buffer.copy(buf); 1249 | chunk.copy(buf, this.buffer.length); 1250 | 1251 | this.buffer = buf; 1252 | 1253 | return true; 1254 | }; 1255 | 1256 | return new Constructor(); 1257 | })(); 1258 | 1259 | var scope = nock('http://pauseme.com') 1260 | .get('/') 1261 | .reply(200, 'nobody'); 1262 | 1263 | var req = http.get({ 1264 | host: 'pauseme.com' 1265 | , path: '/' 1266 | }, function(res) { 1267 | dest.on('end', function() { 1268 | t.fail('should not call end implicitly'); 1269 | }); 1270 | 1271 | res.on('end', function() { 1272 | scope.done(); 1273 | t.pass('should emit end event'); 1274 | t.end(); 1275 | }); 1276 | 1277 | res.pipe(dest, {end: false}); 1278 | }); 1279 | }); 1280 | 1281 | test("chaining API", function(t) { 1282 | var scope = nock('http://chainchomp.com') 1283 | .get('/one') 1284 | .reply(200, 'first one') 1285 | .get('/two') 1286 | .reply(200, 'second one'); 1287 | 1288 | http.get({ 1289 | host: 'chainchomp.com' 1290 | , path: '/one' 1291 | }, function(res) { 1292 | res.setEncoding('utf8'); 1293 | t.equal(res.statusCode, 200, 'status should be ok'); 1294 | res.on('data', function(data) { 1295 | t.equal(data, 'first one', 'should be equal to first reply'); 1296 | }); 1297 | 1298 | res.on('end', function() { 1299 | 1300 | http.get({ 1301 | host: 'chainchomp.com' 1302 | , path: '/two' 1303 | }, function(res) { 1304 | res.setEncoding('utf8'); 1305 | t.equal(res.statusCode, 200, 'status should be ok'); 1306 | res.on('data', function(data) { 1307 | t.equal(data, 'second one', 'should be qual to second reply'); 1308 | }); 1309 | 1310 | res.on('end', function() { 1311 | scope.done(); 1312 | t.end(); 1313 | }); 1314 | }); 1315 | 1316 | }); 1317 | }); 1318 | }); 1319 | 1320 | test("same URI", function(t) { 1321 | var scope = nock('http://sameurii.com') 1322 | .get('/abc') 1323 | .reply(200, 'first one') 1324 | .get('/abc') 1325 | .reply(200, 'second one'); 1326 | 1327 | http.get({ 1328 | host: 'sameurii.com' 1329 | , path: '/abc' 1330 | }, function(res) { 1331 | res.on('data', function(data) { 1332 | res.setEncoding('utf8'); 1333 | t.equal(data.toString(), 'first one', 'should be qual to first reply'); 1334 | res.on('end', function() { 1335 | http.get({ 1336 | host: 'sameurii.com' 1337 | , path: '/abc' 1338 | }, function(res) { 1339 | res.setEncoding('utf8'); 1340 | res.on('data', function(data) { 1341 | t.equal(data.toString(), 'second one', 'should be qual to second reply'); 1342 | res.on('end', function() { 1343 | scope.done(); 1344 | t.end(); 1345 | }); 1346 | }); 1347 | }); 1348 | }); 1349 | }); 1350 | }); 1351 | }); 1352 | 1353 | test("can use hostname instead of host", function(t) { 1354 | var scope = nock('http://www.google.com') 1355 | .get('/') 1356 | .reply(200, "Hello World!"); 1357 | 1358 | var req = http.request({ 1359 | hostname: "www.google.com" 1360 | , path: '/' 1361 | }, function(res) { 1362 | 1363 | t.equal(res.statusCode, 200); 1364 | res.on('end', function() { 1365 | scope.done(); 1366 | t.end(); 1367 | }); 1368 | }); 1369 | 1370 | req.end(); 1371 | }); 1372 | 1373 | test('hostname is case insensitive', function(t) { 1374 | var scope = nock('http://caseinsensitive.com') 1375 | .get('/path') 1376 | .reply(200, "hey"); 1377 | 1378 | var options = { 1379 | hostname: 'cASEinsensitivE.com', 1380 | path: '/path', 1381 | method: 'GET' 1382 | }; 1383 | 1384 | var req = http.request(options, function(res) { 1385 | scope.done(); 1386 | t.end(); 1387 | }); 1388 | 1389 | req.end(); 1390 | }); 1391 | 1392 | 1393 | test("can take a port", function(t) { 1394 | var scope = nock('http://www.myserver.com:3333') 1395 | .get('/') 1396 | .reply(200, "Hello World!"); 1397 | 1398 | var req = http.request({ 1399 | hostname: "www.myserver.com" 1400 | , path: '/' 1401 | , port: 3333 1402 | }, function(res) { 1403 | 1404 | t.equal(res.statusCode, 200); 1405 | res.on('end', function() { 1406 | scope.done(); 1407 | t.end(); 1408 | }); 1409 | }); 1410 | 1411 | req.end(); 1412 | }); 1413 | 1414 | test("can use https", function(t) { 1415 | var dataCalled = false 1416 | 1417 | var scope = nock('https://google.com') 1418 | .get('/') 1419 | .reply(200, "Hello World!"); 1420 | 1421 | var req = https.request({ 1422 | host: "google.com" 1423 | , path: '/' 1424 | }, function(res) { 1425 | t.equal(res.statusCode, 200); 1426 | res.on('end', function() { 1427 | t.ok(dataCalled, 'data event called'); 1428 | scope.done(); 1429 | t.end(); 1430 | }); 1431 | res.on('data', function(data) { 1432 | dataCalled = true; 1433 | t.ok(data instanceof Buffer, "data should be buffer"); 1434 | t.equal(data.toString(), "Hello World!", "response should match"); 1435 | }); 1436 | }); 1437 | 1438 | req.end(); 1439 | }); 1440 | 1441 | test("emits error if https route is missing", function(t) { 1442 | var dataCalled = false 1443 | 1444 | var scope = nock('https://google.com') 1445 | .get('/') 1446 | .reply(200, "Hello World!"); 1447 | 1448 | var req = https.request({ 1449 | host: "google.com" 1450 | , path: '/abcdef892932' 1451 | }, function(res) { 1452 | throw new Error('should not come here!'); 1453 | }); 1454 | 1455 | req.end(); 1456 | 1457 | // This listener is intentionally after the end call so make sure that 1458 | // listeners added after the end will catch the error 1459 | req.on('error', function (err) { 1460 | t.equal(err.message.trim(), 'Nock: No match for request GET https://google.com/abcdef892932'); 1461 | t.end(); 1462 | }); 1463 | }); 1464 | 1465 | test("emits error if https route is missing", function(t) { 1466 | var dataCalled = false 1467 | 1468 | var scope = nock('https://google.com:123') 1469 | .get('/') 1470 | .reply(200, "Hello World!"); 1471 | 1472 | var req = https.request({ 1473 | host: "google.com", 1474 | port: 123, 1475 | path: '/dsadsads' 1476 | }, function(res) { 1477 | throw new Error('should not come here!'); 1478 | }); 1479 | 1480 | req.end(); 1481 | 1482 | // This listener is intentionally after the end call so make sure that 1483 | // listeners added after the end will catch the error 1484 | req.on('error', function (err) { 1485 | t.equal(err.message.trim(), 'Nock: No match for request GET https://google.com:123/dsadsads'); 1486 | t.end(); 1487 | }); 1488 | }); 1489 | 1490 | test("can use ClientRequest using GET", function(t) { 1491 | 1492 | var dataCalled = false 1493 | 1494 | var scope = nock('http://www2.clientrequester.com') 1495 | .get('/dsad') 1496 | .reply(202, "HEHE!"); 1497 | 1498 | var req = new http.ClientRequest({ 1499 | host: "www2.clientrequester.com" 1500 | , path: '/dsad' 1501 | }); 1502 | req.end(); 1503 | 1504 | req.on('response', function(res) { 1505 | t.equal(res.statusCode, 202); 1506 | res.on('end', function() { 1507 | t.ok(dataCalled, "data event was called"); 1508 | scope.done(); 1509 | t.end(); 1510 | }); 1511 | res.on('data', function(data) { 1512 | dataCalled = true; 1513 | t.ok(data instanceof Buffer, "data should be buffer"); 1514 | t.equal(data.toString(), "HEHE!", "response should match"); 1515 | }); 1516 | }); 1517 | 1518 | req.end(); 1519 | }); 1520 | 1521 | test("can use ClientRequest using POST", function(t) { 1522 | 1523 | var dataCalled = false 1524 | 1525 | var scope = nock('http://www2.clientrequester.com') 1526 | .post('/posthere/please', 'heyhey this is the body') 1527 | .reply(201, "DOOONE!"); 1528 | 1529 | var req = new http.ClientRequest({ 1530 | host: "www2.clientrequester.com" 1531 | , path: '/posthere/please' 1532 | , method: 'POST' 1533 | }); 1534 | req.write('heyhey this is the body'); 1535 | req.end(); 1536 | 1537 | req.on('response', function(res) { 1538 | t.equal(res.statusCode, 201); 1539 | res.on('end', function() { 1540 | t.ok(dataCalled, "data event was called"); 1541 | scope.done(); 1542 | t.end(); 1543 | }); 1544 | res.on('data', function(data) { 1545 | dataCalled = true; 1546 | t.ok(data instanceof Buffer, "data should be buffer"); 1547 | t.equal(data.toString(), "DOOONE!", "response should match"); 1548 | }); 1549 | }); 1550 | 1551 | req.end(); 1552 | }); 1553 | 1554 | test("same url matches twice", function(t) { 1555 | var scope = nock('http://www.twicematcher.com') 1556 | .get('/hey') 1557 | .reply(200, "First match") 1558 | .get('/hey') 1559 | .reply(201, "Second match"); 1560 | 1561 | var replied = 0; 1562 | 1563 | function callback() { 1564 | replied += 1; 1565 | if (replied == 2) { 1566 | scope.done(); 1567 | t.end(); 1568 | } 1569 | } 1570 | 1571 | http.get({ 1572 | host: "www.twicematcher.com" 1573 | , path: '/hey' 1574 | }, function(res) { 1575 | t.equal(res.statusCode, 200); 1576 | 1577 | res.on('data', function(data) { 1578 | t.equal(data.toString(), 'First match', 'should match first request response body'); 1579 | }); 1580 | 1581 | res.on('end', callback); 1582 | }); 1583 | 1584 | http.get({ 1585 | host: "www.twicematcher.com" 1586 | , path: '/hey' 1587 | }, function(res) { 1588 | t.equal(res.statusCode, 201); 1589 | 1590 | res.on('data', function(data) { 1591 | t.equal(data.toString(), 'Second match', 'should match second request response body'); 1592 | }); 1593 | 1594 | res.on('end', callback); 1595 | }); 1596 | 1597 | }); 1598 | 1599 | test("scopes are independent", function(t) { 1600 | var scope1 = nock('http://www34.google.com') 1601 | .get('/') 1602 | .reply(200, "Hello World!"); 1603 | var scope2 = nock('http://www34.google.com') 1604 | .get('/') 1605 | .reply(200, "Hello World!"); 1606 | 1607 | var req = http.request({ 1608 | host: "www34.google.com" 1609 | , path: '/' 1610 | , port: 80 1611 | }, function(res) { 1612 | res.on('end', function() { 1613 | t.ok(scope1.isDone()); 1614 | t.ok(! scope2.isDone()); // fails 1615 | t.end(); 1616 | }); 1617 | }); 1618 | 1619 | req.end(); 1620 | }); 1621 | 1622 | test("two scopes with the same request are consumed", function(t) { 1623 | var scope1 = nock('http://www36.google.com') 1624 | .get('/') 1625 | .reply(200, "Hello World!"); 1626 | 1627 | var scope2 = nock('http://www36.google.com') 1628 | .get('/') 1629 | .reply(200, "Hello World!"); 1630 | 1631 | var doneCount = 0; 1632 | function done() { 1633 | doneCount += 1; 1634 | if (doneCount == 2) { 1635 | t.end(); 1636 | } 1637 | } 1638 | 1639 | for (var i = 0; i < 2; i += 1) { 1640 | var req = http.request({ 1641 | host: "www36.google.com" 1642 | , path: '/' 1643 | , port: 80 1644 | }, function(res) { 1645 | res.on('end', done); 1646 | }); 1647 | 1648 | req.end(); 1649 | } 1650 | }); 1651 | 1652 | test("allow unmocked option works", function(t) { 1653 | var scope = nock('http://www.google.com', {allowUnmocked: true}) 1654 | .get('/abc') 1655 | .reply(200, 'Hey!') 1656 | .get('/wont/get/here') 1657 | .reply(200, 'Hi!'); 1658 | 1659 | function secondIsDone() { 1660 | t.ok(! scope.isDone()); 1661 | http.request({ 1662 | host: "www.google.com" 1663 | , path: "/" 1664 | , port: 80 1665 | }, function(res) { 1666 | res.destroy(); 1667 | t.assert(res.statusCode < 400 && res.statusCode >= 200, 'GET Google Home page'); 1668 | t.end(); 1669 | } 1670 | ).end(); 1671 | } 1672 | 1673 | function firstIsDone() { 1674 | t.ok(! scope.isDone()); 1675 | http.request({ 1676 | host: "www.google.com" 1677 | , path: "/does/not/exist/dskjsakdj" 1678 | , port: 80 1679 | }, function(res) { 1680 | t.assert(res.statusCode === 404, 'Google say it does not exist'); 1681 | res.on('data', function() {}); 1682 | res.on('end', secondIsDone); 1683 | } 1684 | ).end(); 1685 | } 1686 | 1687 | http.request({ 1688 | host: "www.google.com" 1689 | , path: "/abc" 1690 | , port: 80 1691 | }, function(res) { 1692 | res.on('end', firstIsDone); 1693 | } 1694 | ).end(); 1695 | }); 1696 | 1697 | test("default reply headers work", function(t) { 1698 | var scope = nock('http://default.reply.headers.com') 1699 | .defaultReplyHeaders({'X-Powered-By': 'Meeee', 'X-Another-Header': 'Hey man!'}) 1700 | .get('/') 1701 | .reply(200, '', {A: 'b'}); 1702 | 1703 | function done(res) { 1704 | t.deepEqual(res.headers, {'x-powered-by': 'Meeee', 'x-another-header': 'Hey man!', a: 'b'}); 1705 | t.end(); 1706 | } 1707 | 1708 | http.request({ 1709 | host: 'default.reply.headers.com' 1710 | , path: '/' 1711 | }, done).end(); 1712 | }); 1713 | 1714 | test("JSON encoded replies set the content-type header", function(t) { 1715 | var scope = nock('http://localhost') 1716 | .get('/') 1717 | .reply(200, { 1718 | A: 'b' 1719 | }); 1720 | 1721 | function done(res) { 1722 | scope.done(); 1723 | t.equal(res.statusCode, 200); 1724 | t.equal(res.headers['content-type'], 'application/json'); 1725 | t.end(); 1726 | } 1727 | 1728 | http.request({ 1729 | host: 'localhost' 1730 | , path: '/' 1731 | }, done).end(); 1732 | }); 1733 | 1734 | 1735 | test("JSON encoded replies does not overwrite existing content-type header", function(t) { 1736 | var scope = nock('http://localhost') 1737 | .get('/') 1738 | .reply(200, { 1739 | A: 'b' 1740 | }, { 1741 | 'Content-Type': 'unicorns' 1742 | }); 1743 | 1744 | function done(res) { 1745 | scope.done(); 1746 | t.equal(res.statusCode, 200); 1747 | t.equal(res.headers['content-type'], 'unicorns'); 1748 | t.end(); 1749 | } 1750 | 1751 | http.request({ 1752 | host: 'localhost' 1753 | , path: '/' 1754 | }, done).end(); 1755 | }); 1756 | 1757 | test("blank response doesn't have content-type application/json attached to it", function(t) { 1758 | var scope = nock('http://localhost') 1759 | .get('/') 1760 | .reply(200); 1761 | 1762 | function done(res) { 1763 | t.equal(res.statusCode, 200); 1764 | t.notEqual(res.headers['content-type'], "application/json"); 1765 | t.end(); 1766 | } 1767 | 1768 | http.request({ 1769 | host: 'localhost' 1770 | , path: '/' 1771 | }, done).end(); 1772 | }); 1773 | 1774 | test('clean all works', function(t) { 1775 | var scope = nock('http://amazon.com') 1776 | .get('/nonexistent') 1777 | .reply(200); 1778 | 1779 | var req = http.get({host: 'amazon.com', path: '/nonexistent'}, function(res) { 1780 | t.assert(res.statusCode === 200, "should mock before cleanup"); 1781 | 1782 | nock.cleanAll(); 1783 | 1784 | var req = http.get({host: 'amazon.com', path: '/nonexistent'}, function(res) { 1785 | res.destroy(); 1786 | t.assert(res.statusCode !== 200, "should clean up properly"); 1787 | t.end(); 1788 | }).on('error', function(err) { 1789 | t.end(); 1790 | }); 1791 | }); 1792 | 1793 | }); 1794 | 1795 | test('username and password works', function(t) { 1796 | var scope = nock('http://passwordyy.com') 1797 | .get('/') 1798 | .reply(200, "Welcome, username"); 1799 | 1800 | http.request({ 1801 | hostname: 'passwordyy.com', 1802 | auth: "username:password", 1803 | path: '/' 1804 | }, function(res) { 1805 | scope.done(); 1806 | t.end(); 1807 | }).end(); 1808 | }); 1809 | 1810 | 1811 | test('works with mikeal/request and username and password', function(t) { 1812 | var scope = nock('http://passwordyyyyy.com') 1813 | .get('/abc') 1814 | .reply(200, "Welcome, username"); 1815 | 1816 | mikealRequest({uri: 'http://username:password@passwordyyyyy.com/abc', log:true}, function(err, res, body) { 1817 | t.ok(! err, 'error'); 1818 | t.ok(scope.isDone()); 1819 | t.equal(body, "Welcome, username"); 1820 | t.end(); 1821 | }); 1822 | 1823 | }); 1824 | 1825 | test('different ports work works', function(t) { 1826 | var scope = nock('http://abc.portyyyy.com:8081') 1827 | .get('/pathhh') 1828 | .reply(200, "Welcome, username"); 1829 | 1830 | http.request({ 1831 | hostname: 'abc.portyyyy.com', 1832 | port: 8081, 1833 | path: '/pathhh' 1834 | }, function(res) { 1835 | scope.done(); 1836 | t.end(); 1837 | }).end(); 1838 | }); 1839 | 1840 | test('different ports work work with Mikeal request', function(t) { 1841 | var scope = nock('http://abc.portyyyy.com:8082') 1842 | .get('/pathhh') 1843 | .reply(200, "Welcome to Mikeal Request!"); 1844 | 1845 | mikealRequest.get('http://abc.portyyyy.com:8082/pathhh', function(err, res, body) { 1846 | t.ok(! err, 'no error'); 1847 | t.equal(body, 'Welcome to Mikeal Request!'); 1848 | t.ok(scope.isDone()); 1849 | t.end(); 1850 | }); 1851 | }); 1852 | 1853 | test('explicitly specifiying port 80 works', function(t) { 1854 | var scope = nock('http://abc.portyyyy.com:80') 1855 | .get('/pathhh') 1856 | .reply(200, "Welcome, username"); 1857 | 1858 | http.request({ 1859 | hostname: 'abc.portyyyy.com', 1860 | port: 80, 1861 | path: '/pathhh' 1862 | }, function(res) { 1863 | scope.done(); 1864 | t.end(); 1865 | }).end(); 1866 | }); 1867 | 1868 | test('post with object', function(t) { 1869 | var scope = nock('http://uri') 1870 | .post('/claim', {some_data: "something"}) 1871 | .reply(200); 1872 | 1873 | http.request({ 1874 | hostname: 'uri', 1875 | port: 80, 1876 | method: "POST", 1877 | path: '/claim' 1878 | }, function(res) { 1879 | scope.done(); 1880 | t.end(); 1881 | }).end('{"some_data":"something"}'); 1882 | 1883 | }); 1884 | 1885 | test('accept string as request target', function(t) { 1886 | var dataCalled = false; 1887 | var scope = nock('http://www.example.com') 1888 | .get('/') 1889 | .reply(200, "Hello World!"); 1890 | 1891 | http.get('http://www.example.com', function(res) { 1892 | t.equal(res.statusCode, 200); 1893 | 1894 | res.on('data', function(data) { 1895 | dataCalled = true; 1896 | t.ok(data instanceof Buffer, "data should be buffer"); 1897 | t.equal(data.toString(), "Hello World!", "response should match"); 1898 | }); 1899 | 1900 | res.on('end', function() { 1901 | t.ok(dataCalled); 1902 | scope.done(); 1903 | t.end(); 1904 | }); 1905 | }); 1906 | }); 1907 | 1908 | test('request has path', function(t) { 1909 | var scope = nock('http://haspath.com') 1910 | .get('/the/path/to/infinity') 1911 | .reply(200); 1912 | 1913 | var req = http.request({ 1914 | hostname: 'haspath.com', 1915 | port: 80, 1916 | method: "GET", 1917 | path: '/the/path/to/infinity' 1918 | }, function(res) { 1919 | scope.done(); 1920 | t.equal(req.path, '/the/path/to/infinity', 'should have req.path set to /the/path/to/infinity'); 1921 | t.end(); 1922 | }); 1923 | req.end(); 1924 | }); 1925 | 1926 | test('persists interceptors', function(t) { 1927 | var scope = nock('http://persisssists.con') 1928 | .persist() 1929 | .get('/') 1930 | .reply(200, "Persisting all the way"); 1931 | 1932 | http.get('http://persisssists.con/', function(res) { 1933 | t.ok(! scope.isDone()); 1934 | http.get('http://persisssists.con/', function(res) { 1935 | t.ok(! scope.isDone()); 1936 | t.end(); 1937 | }).end(); 1938 | }).end(); 1939 | }); 1940 | 1941 | test("persist reply with file", function(t) { 1942 | var dataCalled = false 1943 | 1944 | var scope = nock('http://www.filereplier.com') 1945 | .persist() 1946 | .get('/') 1947 | .replyWithFile(200, __dirname + '/../assets/reply_file_1.txt') 1948 | .get('/test') 1949 | .reply(200, 'Yay!'); 1950 | 1951 | for (var i=0; i < 2; i++) { 1952 | var req = http.request({ 1953 | host: "www.filereplier.com" 1954 | , path: '/' 1955 | , port: 80 1956 | }, function(res) { 1957 | 1958 | t.equal(res.statusCode, 200); 1959 | res.on('end', function() { 1960 | t.ok(dataCalled); 1961 | }); 1962 | res.on('data', function(data) { 1963 | dataCalled = true; 1964 | t.equal(data.toString(), "Hello from the file!", "response should match"); 1965 | }); 1966 | 1967 | }); 1968 | req.end(); 1969 | } 1970 | t.end(); 1971 | 1972 | }); 1973 | 1974 | test('(re-)activate after restore', function(t) { 1975 | var scope = nock('http://google.com') 1976 | .get('/') 1977 | .reply(200, 'Hello, World!'); 1978 | 1979 | nock.restore(); 1980 | t.false(nock.isActive()); 1981 | 1982 | http.get('http://google.com/', function(res) { 1983 | res.resume(); 1984 | res.on('end', function() { 1985 | t.ok(!scope.isDone()); 1986 | 1987 | nock.activate(); 1988 | t.true(nock.isActive()); 1989 | http.get('http://google.com', function(res) { 1990 | res.resume(); 1991 | res.on('end', function() { 1992 | t.ok(scope.isDone()); 1993 | t.end(); 1994 | }); 1995 | }).end(); 1996 | }); 1997 | }).end(); 1998 | }); 1999 | 2000 | test("allow unmocked option works with https", function(t) { 2001 | t.plan(5) 2002 | var scope = nock('https://www.google.com', {allowUnmocked: true}) 2003 | .get('/abc') 2004 | .reply(200, 'Hey!') 2005 | .get('/wont/get/here') 2006 | .reply(200, 'Hi!'); 2007 | 2008 | function secondIsDone() { 2009 | t.ok(! scope.isDone()); 2010 | https.request({ 2011 | host: "www.google.com" 2012 | , path: "/" 2013 | }, function(res) { 2014 | res.resume(); 2015 | t.ok(true, 'Google replied to /'); 2016 | res.destroy(); 2017 | t.assert(res.statusCode < 400 && res.statusCode >= 200, 'GET Google Home page'); 2018 | }).end(); 2019 | } 2020 | 2021 | function firstIsDone() { 2022 | t.ok(! scope.isDone(), 'scope is not done'); 2023 | https.request({ 2024 | host: "www.google.com" 2025 | , path: "/does/not/exist/dskjsakdj" 2026 | }, function(res) { 2027 | t.equal(404, res.statusCode, 'real google response status code'); 2028 | res.on('data', function() {}); 2029 | res.on('end', secondIsDone); 2030 | }).end(); 2031 | } 2032 | 2033 | https.request({ 2034 | host: "www.google.com" 2035 | , path: "/abc" 2036 | }, function(res) { 2037 | res.on('end', firstIsDone); 2038 | }).end(); 2039 | }); 2040 | 2041 | 2042 | test('allow unmocked post with json data', function(t) { 2043 | var scope = nock('https://httpbin.org', { allowUnmocked: true }). 2044 | get("/abc"). 2045 | reply(200, "Hey!"); 2046 | 2047 | var options = { 2048 | method: 'POST', 2049 | uri: 'https://httpbin.org/post', 2050 | json: { some: 'data' } 2051 | }; 2052 | 2053 | mikealRequest(options, function(err, resp, body) { 2054 | t.equal(200, resp.statusCode) 2055 | t.end(); 2056 | }); 2057 | }); 2058 | 2059 | test('allow unordered body with json encoding', function(t) { 2060 | var scope = 2061 | nock('http://wtfjs.org') 2062 | .post('/like-wtf', { 2063 | foo: 'bar', 2064 | bar: 'foo' 2065 | }) 2066 | .reply(200, 'Heyyyy!'); 2067 | 2068 | mikealRequest({ 2069 | uri: 'http://wtfjs.org/like-wtf', 2070 | method: 'POST', 2071 | json: { 2072 | bar: 'foo', 2073 | foo: 'bar' 2074 | }}, 2075 | function (e, r, body) { 2076 | t.equal(body, 'Heyyyy!'); 2077 | scope.done(); 2078 | t.end(); 2079 | }); 2080 | }); 2081 | 2082 | test('allow unordered body with form encoding', function(t) { 2083 | var scope = 2084 | nock('http://wtfjs.org') 2085 | .post('/like-wtf', { 2086 | foo: 'bar', 2087 | bar: 'foo' 2088 | }) 2089 | .reply(200, 'Heyyyy!'); 2090 | 2091 | mikealRequest({ 2092 | uri: 'http://wtfjs.org/like-wtf', 2093 | method: 'POST', 2094 | form: { 2095 | bar: 'foo', 2096 | foo: 'bar' 2097 | }}, 2098 | function (e, r, body) { 2099 | t.equal(body, 'Heyyyy!'); 2100 | scope.done(); 2101 | t.end(); 2102 | }); 2103 | }); 2104 | 2105 | 2106 | test('allow string json spec', function(t) { 2107 | var bodyObject = {bar: 'foo', foo: 'bar'}; 2108 | 2109 | var scope = 2110 | nock('http://wtfjs.org') 2111 | .post('/like-wtf', JSON.stringify(bodyObject)) 2112 | .reply(200, 'Heyyyy!'); 2113 | 2114 | mikealRequest({ 2115 | uri: 'http://wtfjs.org/like-wtf', 2116 | method: 'POST', 2117 | json: { 2118 | bar: 'foo', 2119 | foo: 'bar' 2120 | }}, 2121 | function (e, r, body) { 2122 | t.equal(body, 'Heyyyy!'); 2123 | scope.done(); 2124 | t.end(); 2125 | }); 2126 | }); 2127 | 2128 | test('has a req property on the response', function(t) { 2129 | var scope = nock('http://wtfjs.org').get('/like-wtf').reply(200); 2130 | var req = http.request('http://wtfjs.org/like-wtf', function(res) { 2131 | res.on('end', function() { 2132 | t.ok(res.req, "req property doesn't exist"); 2133 | scope.done(); 2134 | t.end(); 2135 | }); 2136 | }); 2137 | req.end(); 2138 | }); 2139 | 2140 | test('disabled real HTTP request', function(t) { 2141 | nock.disableNetConnect(); 2142 | 2143 | http.get('http://www.amazon.com', function(res) { 2144 | throw "should not request this"; 2145 | }).on('error', function(err) { 2146 | t.equal(err.message, 'Nock: Not allow net connect for "www.amazon.com:80"'); 2147 | t.end(); 2148 | }); 2149 | 2150 | nock.enableNetConnect(); 2151 | }); 2152 | 2153 | test('NetConnectNotAllowedError is instance of Error', function(t) { 2154 | nock.disableNetConnect(); 2155 | 2156 | http.get('http://www.amazon.com', function(res) { 2157 | throw "should not request this"; 2158 | }).on('error', function (err) { 2159 | t.type(err, 'Error'); 2160 | t.end(); 2161 | }); 2162 | 2163 | nock.enableNetConnect(); 2164 | }); 2165 | 2166 | test('NetConnectNotAllowedError exposes the stack', function(t) { 2167 | nock.disableNetConnect(); 2168 | 2169 | http.get('http://www.amazon.com', function(res) { 2170 | throw "should not request this"; 2171 | }).on('error', function (err) { 2172 | t.notEqual(err.stack, undefined); 2173 | t.end(); 2174 | }); 2175 | 2176 | nock.enableNetConnect(); 2177 | }); 2178 | 2179 | test('enable real HTTP request only for google.com, via string', function(t) { 2180 | nock.enableNetConnect('google.com'); 2181 | 2182 | http.get('http://google.com.br/').on('error', function(err) { 2183 | throw err; 2184 | }); 2185 | 2186 | http.get('http://www.amazon.com', function(res) { 2187 | throw "should not deliver this request" 2188 | }).on('error', function (err) { 2189 | t.equal(err.message, 'Nock: Not allow net connect for "www.amazon.com:80"'); 2190 | }); 2191 | 2192 | t.end(); 2193 | nock.enableNetConnect(); 2194 | }); 2195 | 2196 | test('enable real HTTP request only for google.com, via regexp', function(t) { 2197 | nock.enableNetConnect(/google\.com/); 2198 | 2199 | http.get('http://google.com.br/').on('error', function(err) { 2200 | throw err; 2201 | }); 2202 | 2203 | http.get('http://www.amazon.com', function(res) { 2204 | throw "should not request this"; 2205 | }).on('error', function (err) { 2206 | t.equal(err.message, 'Nock: Not allow net connect for "www.amazon.com:80"'); 2207 | t.end(); 2208 | }); 2209 | 2210 | nock.enableNetConnect(); 2211 | }); 2212 | 2213 | test('repeating once', function(t) { 2214 | nock.disableNetConnect(); 2215 | 2216 | var _mock = nock('http://zombo.com') 2217 | .get('/') 2218 | .once() 2219 | .reply(200, "Hello World!"); 2220 | 2221 | http.get('http://zombo.com', function(res) { 2222 | t.equal(200, res.statusCode, 'first request'); 2223 | }); 2224 | 2225 | nock.cleanAll() 2226 | t.end(); 2227 | 2228 | nock.enableNetConnect(); 2229 | }); 2230 | 2231 | test('repeating twice', function(t) { 2232 | nock.disableNetConnect(); 2233 | 2234 | var _mock = nock('http://zombo.com') 2235 | .get('/') 2236 | .twice() 2237 | .reply(200, "Hello World!"); 2238 | 2239 | for (var i=0; i < 2; i++) { 2240 | http.get('http://zombo.com', function(res) { 2241 | t.equal(200, res.statusCode, 'first request'); 2242 | }); 2243 | }; 2244 | 2245 | nock.cleanAll() 2246 | t.end(); 2247 | 2248 | nock.enableNetConnect(); 2249 | }); 2250 | 2251 | test('repeating thrice', function(t) { 2252 | nock.disableNetConnect(); 2253 | 2254 | var _mock = nock('http://zombo.com') 2255 | .get('/') 2256 | .thrice() 2257 | .reply(200, "Hello World!"); 2258 | 2259 | for (var i=0; i < 3; i++) { 2260 | http.get('http://zombo.com', function(res) { 2261 | t.equal(200, res.statusCode, 'first request'); 2262 | }); 2263 | }; 2264 | 2265 | nock.cleanAll() 2266 | t.end(); 2267 | 2268 | nock.enableNetConnect(); 2269 | }); 2270 | 2271 | test('repeating response 4 times', function(t) { 2272 | nock.disableNetConnect(); 2273 | 2274 | var _mock = nock('http://zombo.com') 2275 | .get('/') 2276 | .times(4) 2277 | .reply(200, "Hello World!"); 2278 | 2279 | for (var i=0; i < 4; i++) { 2280 | http.get('http://zombo.com', function(res) { 2281 | t.equal(200, res.statusCode, 'first request'); 2282 | }); 2283 | }; 2284 | 2285 | nock.cleanAll() 2286 | t.end(); 2287 | 2288 | nock.enableNetConnect(); 2289 | }); 2290 | 2291 | 2292 | test('superagent works', function(t) { 2293 | var responseText = 'Yay superagent!'; 2294 | var headers = { 'Content-Type': 'text/plain'}; 2295 | nock('http://superagent.cz') 2296 | .get('/somepath') 2297 | .reply(200, responseText, headers); 2298 | 2299 | superagent 2300 | .get('http://superagent.cz/somepath') 2301 | .end(function(err, res) { 2302 | t.equal(res.text, responseText); 2303 | t.end(); 2304 | }); 2305 | }); 2306 | 2307 | test('superagent works with query string', function(t) { 2308 | var responseText = 'Yay superagentzzz'; 2309 | var headers = { 'Content-Type': 'text/plain'}; 2310 | nock('http://superagent.cz') 2311 | .get('/somepath?a=b') 2312 | .reply(200, responseText, headers); 2313 | 2314 | superagent 2315 | .get('http://superagent.cz/somepath?a=b') 2316 | .end(function(err, res) { 2317 | t.equal(res.text, responseText); 2318 | t.end(); 2319 | }); 2320 | }); 2321 | 2322 | test('superagent posts', function(t) { 2323 | nock('http://superagent.cz') 2324 | .post('/somepath?b=c') 2325 | .reply(204); 2326 | 2327 | superagent 2328 | .post('http://superagent.cz/somepath?b=c') 2329 | .send('some data') 2330 | .end(function(err, res) { 2331 | t.equal(res.status, 204); 2332 | t.end(); 2333 | }); 2334 | }); 2335 | 2336 | test('response is streams2 compatible', function(t) { 2337 | var responseText = 'streams2 streams2 streams2'; 2338 | nock('http://stream2hostnameftw') 2339 | .get('/somepath') 2340 | .reply(200, responseText); 2341 | 2342 | 2343 | http.request({ 2344 | host: "stream2hostnameftw" 2345 | , path: "/somepath" 2346 | }, function(res) { 2347 | res.setEncoding('utf8'); 2348 | 2349 | var body = ''; 2350 | 2351 | res.on('readable', function() { 2352 | var buf; 2353 | while (buf = res.read()) 2354 | body += buf; 2355 | }); 2356 | 2357 | res.once('end', function() { 2358 | t.equal(body, responseText); 2359 | t.end(); 2360 | }); 2361 | 2362 | }).end(); 2363 | 2364 | }); 2365 | 2366 | function checkDuration(t, ms) { 2367 | var _end = t.end; 2368 | var start = process.hrtime(); 2369 | t.end = function () { 2370 | var fin = process.hrtime(start); 2371 | var finMs = 2372 | (fin[0] * 1e+9) + // seconds -> ms 2373 | (fin[1] * 1e-6); // nanoseconds -> ms 2374 | 2375 | /// innaccurate timers 2376 | ms = ms * 0.9; 2377 | 2378 | t.ok(finMs >= ms, 'Duration of ' + Math.round(finMs) + 'ms should be longer than ' + ms + 'ms'); 2379 | _end.call(t); 2380 | }; 2381 | } 2382 | 2383 | test('calling delay delays the response', function (t) { 2384 | checkDuration(t, 100); 2385 | 2386 | nock('http://funk') 2387 | .get('/') 2388 | .delay(100) 2389 | .reply(200, 'OK'); 2390 | 2391 | http.get('http://funk/', function (res) { 2392 | res.setEncoding('utf8'); 2393 | 2394 | var body = ''; 2395 | 2396 | res.on('data', function(chunk) { 2397 | body += chunk; 2398 | }); 2399 | 2400 | res.once('end', function() { 2401 | t.equal(body, 'OK'); 2402 | t.end(); 2403 | }); 2404 | }); 2405 | }); 2406 | 2407 | test('using reply callback with delay provides proper arguments', function (t) { 2408 | nock('http://localhost') 2409 | .get('/') 2410 | .delay(100) 2411 | .reply(200, function (path, requestBody) { 2412 | t.equal(path, '/', 'path arg should be set'); 2413 | t.equal(requestBody, 'OK', 'requestBody arg should be set'); 2414 | t.end(); 2415 | }); 2416 | 2417 | http.request('http://localhost/', function () {}).end('OK'); 2418 | }); 2419 | 2420 | test('delay works with replyWithFile', function (t) { 2421 | checkDuration(t, 100); 2422 | nock('http://localhost') 2423 | .get('/') 2424 | .delay(100) 2425 | .replyWithFile(200, __dirname + '/../assets/reply_file_1.txt'); 2426 | 2427 | http.request('http://localhost/', function (res) { 2428 | res.setEncoding('utf8'); 2429 | 2430 | var body = ''; 2431 | 2432 | res.on('data', function(chunk) { 2433 | body += chunk; 2434 | }); 2435 | 2436 | res.once('end', function() { 2437 | t.equal(body, 'Hello from the file!', 'the body should eql the text from the file'); 2438 | t.end(); 2439 | }); 2440 | }).end('OK'); 2441 | }); 2442 | 2443 | test('delay works with when you return a generic stream from the reply callback', function (t) { 2444 | checkDuration(t, 100); 2445 | nock('http://localhost') 2446 | .get('/') 2447 | .delay(100) 2448 | .reply(200, function (path, reqBody) { 2449 | return fs.createReadStream(__dirname + '/../assets/reply_file_1.txt'); 2450 | }); 2451 | 2452 | http.request('http://localhost/', function (res) { 2453 | res.setEncoding('utf8'); 2454 | 2455 | var body = ''; 2456 | 2457 | res.on('data', function(chunk) { 2458 | body += chunk; 2459 | }); 2460 | 2461 | res.once('end', function() { 2462 | t.equal(body, 'Hello from the file!', 'the body should eql the text from the file'); 2463 | t.end(); 2464 | }); 2465 | }).end('OK'); 2466 | }); 2467 | 2468 | test("finish event fired before end event (bug-139)", function(t) { 2469 | var scope = nock('http://www.filterboddiezregexp.com') 2470 | .filteringRequestBody(/mia/, 'nostra') 2471 | .post('/', 'mamma nostra') 2472 | .reply(200, "Hello World!"); 2473 | 2474 | var finishCalled = false; 2475 | var req = http.request({ 2476 | host: "www.filterboddiezregexp.com" 2477 | , method: 'POST' 2478 | , path: '/' 2479 | , port: 80 2480 | }, function(res) { 2481 | t.equal(finishCalled, true); 2482 | t.equal(res.statusCode, 200); 2483 | res.on('end', function() { 2484 | scope.done(); 2485 | t.end(); 2486 | }); 2487 | }); 2488 | 2489 | req.on('finish', function() { 2490 | finishCalled = true; 2491 | }); 2492 | 2493 | req.end('mamma mia'); 2494 | 2495 | }); 2496 | 2497 | if (stream.Readable) { 2498 | test('when a stream is used for the response body, it will not be read until after the response event', function (t) { 2499 | var responseEvent = false; 2500 | var text = 'Hello World\n'; 2501 | 2502 | function SimpleStream(opt) { 2503 | stream.Readable.call(this, opt); 2504 | } 2505 | util.inherits(SimpleStream, stream.Readable); 2506 | SimpleStream.prototype._read = function() { 2507 | t.ok(responseEvent); 2508 | this.push(text); 2509 | this.push(null); 2510 | }; 2511 | 2512 | nock('http://localhost') 2513 | .get('/') 2514 | .reply(200, function (path, reqBody) { 2515 | return new SimpleStream(); 2516 | }); 2517 | 2518 | http.get('http://localhost/', function (res) { 2519 | responseEvent = true; 2520 | res.setEncoding('utf8'); 2521 | 2522 | var body = ''; 2523 | 2524 | res.on('data', function(chunk) { 2525 | body += chunk; 2526 | }); 2527 | 2528 | res.once('end', function() { 2529 | t.equal(body, text); 2530 | t.end(); 2531 | }); 2532 | }); 2533 | }); 2534 | } 2535 | 2536 | test('calling delayConnection delays the connection', function (t) { 2537 | checkDuration(t, 100); 2538 | 2539 | nock('http://funk') 2540 | .get('/') 2541 | .delayConnection(100) 2542 | .reply(200, 'OK'); 2543 | 2544 | http.get('http://funk/', function (res) { 2545 | res.setEncoding('utf8'); 2546 | 2547 | var body = ''; 2548 | 2549 | res.on('data', function(chunk) { 2550 | body += chunk; 2551 | }); 2552 | 2553 | res.once('end', function() { 2554 | t.equal(body, 'OK'); 2555 | t.end(); 2556 | }); 2557 | }); 2558 | }); 2559 | 2560 | test('using reply callback with delayConnection provides proper arguments', function (t) { 2561 | nock('http://localhost') 2562 | .get('/') 2563 | .delayConnection(100) 2564 | .reply(200, function (path, requestBody) { 2565 | t.equal(path, '/', 'path arg should be set'); 2566 | t.equal(requestBody, 'OK', 'requestBody arg should be set'); 2567 | t.end(); 2568 | }); 2569 | 2570 | http.request('http://localhost/', function () {}).end('OK'); 2571 | }); 2572 | 2573 | test('delayConnection works with replyWithFile', function (t) { 2574 | checkDuration(t, 100); 2575 | nock('http://localhost') 2576 | .get('/') 2577 | .delayConnection(100) 2578 | .replyWithFile(200, __dirname + '/../assets/reply_file_1.txt'); 2579 | 2580 | http.request('http://localhost/', function (res) { 2581 | res.setEncoding('utf8'); 2582 | 2583 | var body = ''; 2584 | 2585 | res.on('data', function(chunk) { 2586 | body += chunk; 2587 | }); 2588 | 2589 | res.once('end', function() { 2590 | t.equal(body, 'Hello from the file!', 'the body should eql the text from the file'); 2591 | t.end(); 2592 | }); 2593 | }).end('OK'); 2594 | }); 2595 | 2596 | test('delayConnection works with when you return a generic stream from the reply callback', function (t) { 2597 | checkDuration(t, 100); 2598 | nock('http://localhost') 2599 | .get('/') 2600 | .delayConnection(100) 2601 | .reply(200, function (path, reqBody) { 2602 | return fs.createReadStream(__dirname + '/../assets/reply_file_1.txt'); 2603 | }); 2604 | 2605 | var req = http.request('http://localhost/', function (res) { 2606 | res.setEncoding('utf8'); 2607 | 2608 | var body = ''; 2609 | 2610 | res.on('data', function(chunk) { 2611 | body += chunk; 2612 | }); 2613 | 2614 | res.once('end', function() { 2615 | t.equal(body, 'Hello from the file!', 'the body should eql the text from the file'); 2616 | t.end(); 2617 | }); 2618 | }).end('OK'); 2619 | }); 2620 | 2621 | test('define() is backward compatible', function(t) { 2622 | var nockDef = { 2623 | "scope":"http://example.com", 2624 | // "port" has been deprecated 2625 | "port":12345, 2626 | "method":"GET", 2627 | "path":"/", 2628 | // "reply" has been deprected 2629 | "reply":"500" 2630 | }; 2631 | 2632 | var nocks = nock.define([nockDef]); 2633 | 2634 | t.ok(nocks); 2635 | 2636 | var req = new http.request({ 2637 | host: 'example.com', 2638 | port: nockDef.port, 2639 | method: nockDef.method, 2640 | path: nockDef.path 2641 | }, function(res) { 2642 | t.equal(res.statusCode, 500); 2643 | 2644 | res.once('end', function() { 2645 | t.end(); 2646 | }); 2647 | }); 2648 | 2649 | req.on('error', function(err) { 2650 | console.error(err); 2651 | // This should never happen. 2652 | t.ok(false, 'Error should never occur.'); 2653 | t.end(); 2654 | }); 2655 | 2656 | req.end(); 2657 | 2658 | }); 2659 | 2660 | test('define() works with non-JSON responses', function(t) { 2661 | var nockDef = { 2662 | "scope":"http://example.com", 2663 | "method":"POST", 2664 | "path":"/", 2665 | "body":"�", 2666 | "status":200, 2667 | "response":"�" 2668 | }; 2669 | 2670 | var nocks = nock.define([nockDef]); 2671 | 2672 | t.ok(nocks); 2673 | 2674 | var req = new http.request({ 2675 | host: 'example.com', 2676 | method: nockDef.method, 2677 | path: nockDef.path 2678 | }, function(res) { 2679 | t.equal(res.statusCode, nockDef.status); 2680 | 2681 | var dataChunks = []; 2682 | 2683 | res.on('data', function(chunk) { 2684 | dataChunks.push(chunk); 2685 | }); 2686 | 2687 | res.once('end', function() { 2688 | var response = Buffer.concat(dataChunks); 2689 | t.equal(response.toString('utf8'), nockDef.response, 'responses match'); 2690 | t.end(); 2691 | }); 2692 | }); 2693 | 2694 | req.on('error', function(err) { 2695 | console.error(err); 2696 | // This should never happen. 2697 | t.ok(false, 'Error should never occur.'); 2698 | t.end(); 2699 | }); 2700 | 2701 | req.write(nockDef.body); 2702 | req.end(); 2703 | 2704 | }); 2705 | 2706 | test('define() works with binary buffers', function(t) { 2707 | var nockDef = { 2708 | "scope":"http://example.com", 2709 | "method":"POST", 2710 | "path":"/", 2711 | "body":"8001", 2712 | "status":200, 2713 | "response":"8001" 2714 | }; 2715 | 2716 | var nocks = nock.define([nockDef]); 2717 | 2718 | t.ok(nocks); 2719 | 2720 | var req = new http.request({ 2721 | host: 'example.com', 2722 | method: nockDef.method, 2723 | path: nockDef.path 2724 | }, function(res) { 2725 | t.equal(res.statusCode, nockDef.status); 2726 | 2727 | var dataChunks = []; 2728 | 2729 | res.on('data', function(chunk) { 2730 | dataChunks.push(chunk); 2731 | }); 2732 | 2733 | res.once('end', function() { 2734 | var response = Buffer.concat(dataChunks); 2735 | t.equal(response.toString('hex'), nockDef.response, 'responses match'); 2736 | t.end(); 2737 | }); 2738 | }); 2739 | 2740 | req.on('error', function(err) { 2741 | console.error(err); 2742 | // This should never happen. 2743 | t.ok(false, 'Error should never occur.'); 2744 | t.end(); 2745 | }); 2746 | 2747 | req.write(new Buffer(nockDef.body, 'hex')); 2748 | req.end(); 2749 | 2750 | }); 2751 | 2752 | test('issue #163 - Authorization header isn\'t mocked', function(t) { 2753 | function makeRequest(cb) { 2754 | var r = http.request( 2755 | { 2756 | hostname: 'www.example.com', 2757 | path: '/', 2758 | method: 'GET', 2759 | auth: 'foo:bar' 2760 | }, 2761 | function(res) { 2762 | cb(res.req._headers); 2763 | } 2764 | ); 2765 | r.end(); 2766 | } 2767 | 2768 | makeRequest(function(headers) { 2769 | var n = nock('http://www.example.com', { 2770 | reqheaders: { 'authorization': 'Basic Zm9vOmJhcg==' } 2771 | }).get('/').reply(200); 2772 | 2773 | makeRequest(function(nockHeader) { 2774 | n.done(); 2775 | t.equivalent(headers, nockHeader); 2776 | t.end(); 2777 | }); 2778 | }); 2779 | }); 2780 | 2781 | test('define() uses reqheaders', function(t) { 2782 | var nockDef = { 2783 | "scope":"http://example.com", 2784 | "method":"GET", 2785 | "path":"/", 2786 | "status":200, 2787 | "reqheaders": { 2788 | host: 'example.com', 2789 | 'authorization': 'Basic Zm9vOmJhcg==' 2790 | } 2791 | }; 2792 | 2793 | var nocks = nock.define([nockDef]); 2794 | 2795 | t.ok(nocks); 2796 | 2797 | var req = new http.request({ 2798 | host: 'example.com', 2799 | method: nockDef.method, 2800 | path: nockDef.path, 2801 | auth: 'foo:bar' 2802 | }, function(res) { 2803 | t.equal(res.statusCode, nockDef.status); 2804 | 2805 | res.once('end', function() { 2806 | t.equivalent(res.req._headers, nockDef.reqheaders); 2807 | t.end(); 2808 | }); 2809 | }); 2810 | req.end(); 2811 | 2812 | }); 2813 | 2814 | test('sending binary and receiving JSON should work ', function(t) { 2815 | var scope = nock('http://example.com') 2816 | .filteringRequestBody(/.*/, '*') 2817 | .post('/some/path', '*') 2818 | .reply(201, { foo: '61' }, { 2819 | 'Content-Type': 'application/json' 2820 | }); 2821 | 2822 | mikealRequest({ 2823 | method: 'POST', 2824 | uri: 'http://example.com/some/path', 2825 | body: new Buffer('ffd8ffe000104a46494600010101006000600000ff', 'hex'), 2826 | headers: { 'Accept': 'application/json', 'Content-Length': 23861 } 2827 | }, function(err, res, body) { 2828 | scope.done(); 2829 | 2830 | t.equal(res.statusCode, 201); 2831 | t.equal(body.length, 12); 2832 | 2833 | var json; 2834 | try { 2835 | json = JSON.parse(body); 2836 | } catch (e) { 2837 | json = {}; 2838 | } 2839 | 2840 | t.equal(json.foo, '61'); 2841 | t.end(); 2842 | } 2843 | ); 2844 | }); 2845 | 2846 | test('fix #146 - resume() is automatically invoked when the response is drained', function(t) { 2847 | var replyLength = 1024 * 1024; 2848 | var replyBuffer = new Buffer((new Array(replyLength + 1)).join(".")); 2849 | t.equal(replyBuffer.length, replyLength); 2850 | 2851 | nock("http://www.abc.com") 2852 | .get("/abc") 2853 | .reply(200, replyBuffer); 2854 | 2855 | needle.get("http://www.abc.com/abc", function(err, res, buffer) { 2856 | t.notOk(err); 2857 | t.ok(res); 2858 | t.ok(buffer); 2859 | t.equal(buffer, replyBuffer); 2860 | t.end(); 2861 | }); 2862 | }); 2863 | 2864 | test("handles get with restify client", function(t) { 2865 | var scope = 2866 | nock("https://www.example.com"). 2867 | get("/get"). 2868 | reply(200, 'get'); 2869 | 2870 | var client = restify.createClient({ 2871 | url: 'https://www.example.com' 2872 | }) 2873 | 2874 | client.get('/get', function(err, req, res) { 2875 | req.on('result', function(err, res) { 2876 | res.body = ''; 2877 | res.setEncoding('utf8'); 2878 | res.on('data', function(chunk) { 2879 | res.body += chunk; 2880 | }); 2881 | 2882 | res.on('end', function() { 2883 | t.equal(res.body, 'get') 2884 | t.end(); 2885 | scope.done(); 2886 | }); 2887 | }); 2888 | }); 2889 | }); 2890 | 2891 | test("handles post with restify client", function(t) { 2892 | var scope = 2893 | nock("https://www.example.com"). 2894 | post("/post", 'hello world'). 2895 | reply(200, 'post'); 2896 | 2897 | var client = restify.createClient({ 2898 | url: 'https://www.example.com' 2899 | }) 2900 | 2901 | client.post('/post', function(err, req, res) { 2902 | req.on('result', function(err, res) { 2903 | res.body = ''; 2904 | res.setEncoding('utf8'); 2905 | res.on('data', function(chunk) { 2906 | res.body += chunk; 2907 | }); 2908 | 2909 | res.on('end', function() { 2910 | t.equal(res.body, 'post') 2911 | t.end(); 2912 | scope.done(); 2913 | }); 2914 | }); 2915 | 2916 | req.write('hello world'); 2917 | req.end(); 2918 | }); 2919 | }); 2920 | 2921 | test("handles get with restify JsonClient", function(t) { 2922 | var scope = 2923 | nock("https://www.example.com"). 2924 | get("/get"). 2925 | reply(200, {get: 'ok'}); 2926 | 2927 | var client = restify.createJsonClient({ 2928 | url: 'https://www.example.com' 2929 | }) 2930 | 2931 | client.get('/get', function(err, req, res, obj) { 2932 | t.equal(obj.get, 'ok'); 2933 | t.end(); 2934 | scope.done(); 2935 | }); 2936 | }); 2937 | 2938 | test("handles post with restify JsonClient", function(t) { 2939 | var scope = 2940 | nock("https://www.example.com"). 2941 | post("/post", {username: 'banana'}). 2942 | reply(200, {post: 'ok'}); 2943 | 2944 | var client = restify.createJsonClient({ 2945 | url: 'https://www.example.com' 2946 | }) 2947 | 2948 | client.post('/post', {username: 'banana'}, function(err, req, res, obj) { 2949 | t.equal(obj.post, 'ok'); 2950 | t.end(); 2951 | scope.done(); 2952 | }); 2953 | }); 2954 | 2955 | test("handles 404 with restify JsonClient", function(t) { 2956 | var scope = 2957 | nock("https://www.example.com"). 2958 | put("/404"). 2959 | reply(404); 2960 | 2961 | var client = restify.createJsonClient({ 2962 | url: 'https://www.example.com' 2963 | }) 2964 | 2965 | client.put('/404', function(err, req, res, obj) { 2966 | t.equal(res.statusCode, 404); 2967 | t.end(); 2968 | scope.done(); 2969 | }); 2970 | }); 2971 | 2972 | test("handles 500 with restify JsonClient", function(t) { 2973 | var scope = 2974 | nock("https://www.example.com"). 2975 | delete("/500"). 2976 | reply(500); 2977 | 2978 | var client = restify.createJsonClient({ 2979 | url: 'https://www.example.com' 2980 | }) 2981 | 2982 | client.del('/500', function(err, req, res, obj) { 2983 | t.equal(res.statusCode, 500); 2984 | t.end(); 2985 | scope.done(); 2986 | }); 2987 | }); 2988 | 2989 | test('test request timeout option', function(t) { 2990 | 2991 | nock('http://example.com') 2992 | .get('/test') 2993 | .reply(200, JSON.stringify({ foo: 'bar' })); 2994 | 2995 | var options = { 2996 | url: 'http://example.com/test', 2997 | method: 'GET', 2998 | timeout: 2000 2999 | }; 3000 | 3001 | mikealRequest(options, function(err, res, body) { 3002 | t.strictEqual(err, null); 3003 | t.equal(body, '{"foo":"bar"}'); 3004 | t.end(); 3005 | }); 3006 | }); 3007 | 3008 | 3009 | test('done fails when specified request header is missing', function(t) { 3010 | var scope = nock('http://example.com', { 3011 | reqheaders: { 3012 | "X-App-Token": "apptoken", 3013 | "X-Auth-Token": "apptoken" 3014 | } 3015 | }) 3016 | .post('/resource') 3017 | .reply(200, { status: "ok" }); 3018 | 3019 | var d = domain.create(); 3020 | 3021 | d.run(function() { 3022 | mikealRequest({ 3023 | method: 'POST', 3024 | uri: 'http://example.com/resource', 3025 | headers: { 3026 | "X-App-Token": "apptoken" 3027 | } 3028 | }); 3029 | }); 3030 | 3031 | d.once('error', function(err) { 3032 | t.ok(err.message.match(/No match/)); 3033 | t.end(); 3034 | }); 3035 | }); 3036 | 3037 | test('done does not fail when specified request header is not missing', function(t) { 3038 | var scope = nock('http://example.com', { 3039 | reqheaders: { 3040 | "X-App-Token": "apptoken", 3041 | "X-Auth-Token": "apptoken" 3042 | } 3043 | }) 3044 | .post('/resource') 3045 | .reply(200, { status: "ok" }); 3046 | 3047 | mikealRequest({ 3048 | method: 'POST', 3049 | uri: 'http://example.com/resource', 3050 | headers: { 3051 | "X-App-Token": "apptoken", 3052 | "X-Auth-Token": "apptoken" 3053 | } 3054 | }, function(err, res, body) { 3055 | t.type(err, 'null'); 3056 | t.equal(res.statusCode, 200); 3057 | t.end(); 3058 | }); 3059 | 3060 | }); 3061 | 3062 | test('mikeal/request with delayConnection and request.timeout', function(t) { 3063 | var endpoint = nock("http://some-server.com") 3064 | .post("/") 3065 | .delayConnection(1000) 3066 | .reply(200, {}); 3067 | 3068 | mikealRequest.post({ 3069 | url: "http://some-server.com/", 3070 | timeout: 10 3071 | }, 3072 | function (err) { 3073 | t.type(err, 'Error'); 3074 | t.equal(err && err.code, "ETIMEDOUT"); 3075 | t.end(); 3076 | }); 3077 | }); 3078 | 3079 | test("get correct filtering with scope and request headers filtering", function(t) { 3080 | var responseText = 'OK!'; 3081 | var responseHeaders = { 'Content-Type': 'text/plain'}; 3082 | var requestHeaders = { host: 'a.subdomain.of.google.com' }; 3083 | 3084 | var scope = nock('http://a.subdomain.of.google.com', { 3085 | filteringScope: function(scope) { 3086 | return (/^http:\/\/.*\.google\.com/).test(scope); 3087 | } 3088 | }) 3089 | .get('/somepath') 3090 | .reply(200, responseText, responseHeaders); 3091 | 3092 | var dataCalled = false; 3093 | var host = 'some.other.subdomain.of.google.com'; 3094 | var req = http.get({ 3095 | host: host, 3096 | method: 'GET', 3097 | path: '/somepath', 3098 | port: 80 3099 | }, function(res) { 3100 | res.on('data', function(data) { 3101 | dataCalled = true; 3102 | t.equal(data.toString(), responseText); 3103 | }); 3104 | res.on('end', function() { 3105 | t.true(dataCalled); 3106 | scope.done(); 3107 | t.end(); 3108 | }); 3109 | }); 3110 | 3111 | t.equivalent(req._headers, { host: requestHeaders.host }); 3112 | 3113 | }); 3114 | 3115 | test('mocking succeeds even when mocked and specified request header names have different cases', function(t) { 3116 | var scope = nock('http://example.com', { 3117 | reqheaders: { 3118 | "x-app-token": "apptoken", 3119 | "x-auth-token": "apptoken" 3120 | } 3121 | }) 3122 | .post('/resource') 3123 | .reply(200, { status: "ok" }); 3124 | 3125 | mikealRequest({ 3126 | method: 'POST', 3127 | uri: 'http://example.com/resource', 3128 | headers: { 3129 | "X-App-TOKEN": "apptoken", 3130 | "X-Auth-TOKEN": "apptoken" 3131 | } 3132 | }, function(err, res, body) { 3133 | t.type(err, 'null'); 3134 | t.equal(res.statusCode, 200); 3135 | t.end(); 3136 | }); 3137 | 3138 | }); 3139 | 3140 | test('mocking succeeds even when host request header is not specified', function(t) { 3141 | var scope = nock('http://example.com') 3142 | .post('/resource') 3143 | .reply(200, { status: "ok" }); 3144 | 3145 | mikealRequest({ 3146 | method: 'POST', 3147 | uri: 'http://example.com/resource', 3148 | headers: { 3149 | "X-App-TOKEN": "apptoken", 3150 | "X-Auth-TOKEN": "apptoken" 3151 | } 3152 | }, function(err, res, body) { 3153 | t.type(err, 'null'); 3154 | t.equal(res.statusCode, 200); 3155 | t.end(); 3156 | }); 3157 | 3158 | }); 3159 | 3160 | test('mikeal/request with strictSSL: true', function(t) { 3161 | var scope = nock('https://strictssl.com') 3162 | .post('/what') 3163 | .reply(200, { status: "ok" }); 3164 | 3165 | mikealRequest({ 3166 | method: 'POST', 3167 | uri: 'https://strictssl.com/what', 3168 | strictSSL: true 3169 | }, function(err, res, body) { 3170 | t.type(err, 'null'); 3171 | t.equal(res && res.statusCode, 200); 3172 | t.end(); 3173 | }); 3174 | 3175 | }); 3176 | 3177 | test('response readable pull stream works as expected', function(t) { 3178 | var scope = nock('http://streamingalltheway.com') 3179 | .get('/ssstream') 3180 | .reply(200, "this is the response body yeah"); 3181 | 3182 | var req = http.request({ 3183 | host: "streamingalltheway.com" 3184 | , path: '/ssstream' 3185 | , port: 80 3186 | }, function(res) { 3187 | 3188 | var responseBody = ''; 3189 | t.equal(res.statusCode, 200); 3190 | res.on('readable', function() { 3191 | var chunk; 3192 | while (null !== (chunk = res.read())) { 3193 | responseBody += chunk.toString(); 3194 | } 3195 | if (chunk === null) { 3196 | t.equal(responseBody, "this is the response body yeah"); 3197 | t.end(); 3198 | } 3199 | }); 3200 | }); 3201 | 3202 | req.end(); 3203 | }); 3204 | 3205 | test(".setNoDelay", function(t) { 3206 | var dataCalled = false 3207 | 3208 | var scope = nock('http://nodelayyy.com') 3209 | .get('/yay') 3210 | .reply(200, "Hi"); 3211 | 3212 | var req = http.request({ 3213 | host: "nodelayyy.com" 3214 | , path: '/yay' 3215 | , port: 80 3216 | }, function(res) { 3217 | 3218 | t.equal(res.statusCode, 200); 3219 | res.on('end', t.end.bind(t)); 3220 | 3221 | }); 3222 | 3223 | req.setNoDelay(true); 3224 | 3225 | req.end(); 3226 | }); 3227 | 3228 | test("match basic authentication header", function(t) { 3229 | var username = 'testuser' 3230 | , password = 'testpassword' 3231 | , authString = username + ":" + password 3232 | , encrypted = (new Buffer(authString)).toString( 'base64' ); 3233 | 3234 | var scope = nock('http://www.headdy.com') 3235 | .get('/') 3236 | .matchHeader('Authorization', function(val) { 3237 | var expected = 'Basic ' + encrypted; 3238 | return val == expected; 3239 | }) 3240 | .reply(200, "Hello World!"); 3241 | 3242 | http.get({ 3243 | host: "www.headdy.com" 3244 | , path: '/' 3245 | , port: 80 3246 | , auth: authString 3247 | }, function(res) { 3248 | res.setEncoding('utf8'); 3249 | t.equal(res.statusCode, 200); 3250 | 3251 | res.on('data', function(data) { 3252 | t.equal(data, 'Hello World!'); 3253 | }); 3254 | 3255 | res.on('end', function() { 3256 | scope.done(); 3257 | t.end(); 3258 | }); 3259 | }); 3260 | 3261 | }); 3262 | 3263 | test('request emits socket', function(t) { 3264 | var scope = nock('http://gotzsocketz.com') 3265 | .get('/') 3266 | .reply(200, "hey"); 3267 | 3268 | var req = http.get('http://gotzsocketz.com'); 3269 | req.once('socket', function(socket) { 3270 | t.type(socket, Object); 3271 | t.type(socket.getPeerCertificate(), 'string'); 3272 | t.end(); 3273 | }); 3274 | }); 3275 | 3276 | test('socket emits connect and secureConnect', function(t) { 3277 | t.plan(3); 3278 | 3279 | var scope = nock('http://gotzsocketz.com') 3280 | .post('/') 3281 | .reply(200, "hey"); 3282 | 3283 | var req = http.request({ 3284 | host: "gotzsocketz.com" 3285 | , path: '/' 3286 | , method: 'POST' 3287 | }); 3288 | 3289 | req.on('socket', function(socket) { 3290 | socket.once('connect', function() { 3291 | req.end(); 3292 | t.ok(true); 3293 | }); 3294 | socket.once('secureConnect', function() { 3295 | t.ok(true); 3296 | }); 3297 | }); 3298 | 3299 | req.once('response', function(res) { 3300 | res.setEncoding('utf8'); 3301 | res.on('data', function(d) { 3302 | t.equal(d, 'hey'); 3303 | }); 3304 | }); 3305 | }); 3306 | 3307 | test('socket setKeepAlive', function(t) { 3308 | var scope = nock('http://setkeepalive.com') 3309 | .get('/') 3310 | .reply(200, "hey"); 3311 | 3312 | var req = http.get('http://setkeepalive.com'); 3313 | req.once('socket', function(socket) { 3314 | socket.setKeepAlive(true); 3315 | t.end(); 3316 | }); 3317 | }); 3318 | 3319 | test('hyperquest works', function(t) { 3320 | nock('http://hyperquest.com') 3321 | .get('/somepath') 3322 | .reply(200, 'Yay hyperquest!'); 3323 | 3324 | var req = hyperquest('http://hyperquest.com/somepath'); 3325 | var reply = ''; 3326 | req.on('data', function(d) { 3327 | reply += d; 3328 | }); 3329 | req.once('end', function() { 3330 | t.equals(reply, 'Yay hyperquest!'); 3331 | t.end(); 3332 | }); 3333 | }); 3334 | 3335 | test('remove interceptor for GET resource', function(t) { 3336 | var scope = nock('http://example.org') 3337 | .get('/somepath') 3338 | .reply(200, 'hey'); 3339 | 3340 | var mocks = scope.pendingMocks(); 3341 | t.deepEqual(mocks, ['GET http://example.org:80/somepath']); 3342 | 3343 | var result = nock.removeInterceptor({ 3344 | hostname : 'example.org', 3345 | path : '/somepath' 3346 | }); 3347 | t.ok(result, 'result should be true'); 3348 | 3349 | nock('http://example.org') 3350 | .get('/somepath') 3351 | .reply(202, 'other-content'); 3352 | 3353 | http.get({ 3354 | host: 'example.org', 3355 | path : '/somepath' 3356 | }, function(res) { 3357 | res.setEncoding('utf8'); 3358 | t.equal(res.statusCode, 202); 3359 | 3360 | res.on('data', function(data) { 3361 | t.equal(data, 'other-content'); 3362 | }); 3363 | 3364 | res.on('end', function() { 3365 | t.end(); 3366 | }); 3367 | }); 3368 | }); 3369 | 3370 | test('remove interceptor for not found resource', function(t) { 3371 | var result = nock.removeInterceptor({ 3372 | hostname : 'example.org', 3373 | path : '/somepath' 3374 | }); 3375 | t.notOk(result, 'result should be false as no interceptor was found'); 3376 | t.end(); 3377 | }); 3378 | 3379 | test('isDone() must consider repeated responses', function(t) { 3380 | 3381 | var scope = nock('http://www.example.com') 3382 | .get('/') 3383 | .times(2) 3384 | .reply(204); 3385 | 3386 | function makeRequest(callback) { 3387 | var req = http.request({ 3388 | host: "www.example.com", 3389 | path: '/', 3390 | port: 80 3391 | }, function(res) { 3392 | t.equal(res.statusCode, 204); 3393 | res.on('end', callback); 3394 | }); 3395 | req.end(); 3396 | } 3397 | 3398 | t.notOk(scope.isDone(), "should not be done before all requests"); 3399 | makeRequest(function() { 3400 | t.notOk(scope.isDone(), "should not yet be done after the first request"); 3401 | makeRequest(function() { 3402 | t.ok(scope.isDone(), "should be done after the two requests are made"); 3403 | scope.done(); 3404 | t.end(); 3405 | }); 3406 | }); 3407 | 3408 | }); 3409 | 3410 | test('you must setup an interceptor for each request', function(t) { 3411 | var scope = nock('http://www.example.com') 3412 | .get('/hey') 3413 | .reply(200, 'First match'); 3414 | 3415 | mikealRequest.get('http://www.example.com/hey', function(error, res, body) { 3416 | t.equal(res.statusCode, 200); 3417 | t.equal(body, 'First match', 'should match first request response body'); 3418 | 3419 | mikealRequest.get('http://www.example.com/hey', function(error, res, body) { 3420 | t.equal(error && error.toString(), 'Error: Nock: No match for request GET http://www.example.com/hey '); 3421 | scope.done(); 3422 | t.end(); 3423 | }); 3424 | }); 3425 | }); 3426 | 3427 | test("teardown", function(t) { 3428 | t.deepEqual(Object.keys(global) 3429 | .splice(globalCount, Number.MAX_VALUE), 3430 | [], 'No leaks'); 3431 | t.end(); 3432 | }); --------------------------------------------------------------------------------