Comparing version 0.7.3 to 0.8.0
@@ -1,10 +0,12 @@ | ||
var path = require('path') | ||
, http = require('http') | ||
, https = require('https') | ||
, url = require('url') | ||
, EventEmitter = require('events').EventEmitter; | ||
var RequestOverrider = require('./request_overrider'), | ||
path = require('path'), | ||
url = require('url'), | ||
inherits = require('util').inherits, | ||
EventEmitter = require('events').EventEmitter, | ||
http = require('http'), | ||
ClientRequest = http.ClientRequest; | ||
var allInterceptors = {}; | ||
function addGlobalInterceptor(key, interceptor) { | ||
function add(key, interceptor) { | ||
if (! allInterceptors.hasOwnProperty(key)) { | ||
@@ -16,11 +18,10 @@ allInterceptors[key] = []; | ||
function remove(interceptor) { | ||
var key = interceptor._key.split(' '), | ||
u = url.parse(key[1]), | ||
hostKey = u.protocol + '//' + u.host, | ||
interceptors = allInterceptors[hostKey], | ||
interceptor, | ||
thisInterceptor; | ||
function remove(interceptor) { | ||
var key = interceptor._key.split(' '); | ||
var u = url.parse(key[1]); | ||
var hostKey = u.protocol + '//' + u.host; | ||
var interceptors = allInterceptors[hostKey]; | ||
var interceptor; | ||
var thisInterceptor; | ||
for(var i = 0; i < interceptors.length; i++) { | ||
@@ -36,337 +37,70 @@ thisInterceptor = interceptors[i]; | ||
function stringifyRequest(options) { | ||
var method = options.method || 'GET'; | ||
var path = options.path; | ||
var body = options.body; | ||
if (body && typeof(body) !== 'string') { | ||
body = body.toString(); | ||
function interceptorsFor(options) { | ||
var basePath; | ||
if (!options.host) { | ||
options.host = options.hostname; | ||
if (options.port) | ||
options.host += ":" + options.port; | ||
} | ||
return method + ' ' + path + ' ' + body; | ||
} | ||
options.proto = options.proto || 'http'; | ||
function getHeader(request, name) { | ||
if (!request._headers) return; | ||
basePath = options.proto + '://' + options.host; | ||
var key = name.toLowerCase(); | ||
return request._headers[key]; | ||
return allInterceptors[basePath] || []; | ||
} | ||
function setHeader(request, name, value) { | ||
var key = name.toLowerCase(); | ||
// ----- Extending http.ClientRequest | ||
request._headers = request._headers || {}; | ||
request._headerNames = request._headerNames || {}; | ||
request._headers[key] = value; | ||
request._headerNames[key] = name; | ||
} | ||
function OverridenClientRequest(options, defaultPort) { | ||
var interceptors = interceptorsFor(options); | ||
function processRequest(interceptors, options, callback) { | ||
var req = new EventEmitter() | ||
, response = new EventEmitter() | ||
, requestBodyBuffers = [] | ||
, aborted | ||
, end | ||
, ended; | ||
if (options.headers) { | ||
var headers = options.headers; | ||
var keys = Object.keys(headers); | ||
for (var i = 0, l = keys.length; i < l; i++) { | ||
var key = keys[i]; | ||
setHeader(req, key, headers[key]); | ||
}; | ||
} | ||
options.getHeader = function(name) { | ||
return getHeader(req, name); | ||
}; | ||
if (options.host && !getHeader(req, 'host')) { | ||
var hostHeader = options.host; | ||
if (options.host && +options.port !== 80) { | ||
hostHeader += ':' + options.port; | ||
if (interceptors.length) { | ||
var overrider = RequestOverrider(this, options, interceptors, remove); | ||
for(var propName in overrider) { | ||
this[propName] = overrider[propName]; | ||
} | ||
setHeader(req, 'Host', hostHeader); | ||
} else { | ||
ClientRequest.apply(this, arguments); | ||
} | ||
req.write = function(buffer, encoding) { | ||
if (buffer && !aborted) { | ||
if (! Buffer.isBuffer(buffer)) { | ||
buffer = new Buffer(buffer, encoding); | ||
} | ||
requestBodyBuffers.push(buffer); | ||
} | ||
}; | ||
req.end = function(buffer, encoding) { | ||
if (!aborted && !ended) { | ||
req.write(buffer, encoding); | ||
end(); | ||
req.emit('end'); | ||
} | ||
}; | ||
req.abort = function() { | ||
aborted = true; | ||
if (!ended) { | ||
end(); | ||
} | ||
var err = new Error(); | ||
err.code = 'aborted' | ||
response.emit('close', err); | ||
}; | ||
end = function() { | ||
ended = true; | ||
var encoding | ||
, requestBody | ||
, responseBody | ||
, interceptor | ||
, paused | ||
, next = [] | ||
, callnext; | ||
} | ||
inherits(OverridenClientRequest, ClientRequest); | ||
var requestBodySize = 0; | ||
var copyTo = 0; | ||
requestBodyBuffers.forEach(function(b) { | ||
requestBodySize += b.length; | ||
}); | ||
requestBody = new Buffer(requestBodySize); | ||
requestBodyBuffers.forEach(function(b) { | ||
b.copy(requestBody, copyTo); | ||
copyTo += b.length; | ||
}); | ||
requestBody = requestBody.toString(); | ||
interceptors = interceptors.filter(function(interceptor) { | ||
return interceptor.match(options, requestBody); | ||
}); | ||
if (interceptors.length < 1) { throw new Error("Nock: No match for HTTP request " + stringifyRequest(options)); } | ||
interceptor = interceptors.shift(); | ||
http.ClientRequest = OverridenClientRequest; | ||
response.statusCode = interceptor.statusCode || 200; | ||
response.headers = interceptor.headers || {}; | ||
response.readable = true; | ||
responseBody = interceptor.body; | ||
if (! Buffer.isBuffer(responseBody)) { | ||
responseBody = new Buffer(responseBody); | ||
} | ||
response.setEncoding = function(newEncoding) { | ||
encoding = newEncoding; | ||
}; | ||
remove(interceptor); | ||
interceptor.discard(); | ||
// ----- Overriding http.request and https.request: | ||
if (aborted) { return; } | ||
[ 'http', 'https'].forEach( | ||
function(proto) { | ||
response.pause = function() { | ||
paused = true; | ||
}; | ||
var moduleName = proto, // 1 to 1 match of protocol and module is fortunate :) | ||
module = require(moduleName), | ||
oldRequest = module.request; | ||
module.request = function(options, callback) { | ||
response.resume = function() { | ||
paused = false; | ||
callnext(); | ||
}; | ||
var interceptors, | ||
req, | ||
res; | ||
options.proto = proto; | ||
interceptors = interceptorsFor(options); | ||
response.pipe = function(dest, options) { | ||
var source = this; | ||
function ondata(chunk) { | ||
if (dest.writable) { | ||
if (false === dest.write(chunk)) source.pause(); | ||
if (interceptors.length) { | ||
req = new EventEmitter(); | ||
res = RequestOverrider(req, options, interceptors, remove); | ||
if (callback) { | ||
res.on('response', callback); | ||
} | ||
return req; | ||
} else { | ||
return oldRequest.apply(module, arguments); | ||
} | ||
function ondrain() { | ||
if (source.readable) source.resume(); | ||
} | ||
source.on('data', ondata); | ||
dest.on('drain', ondrain); | ||
// If the 'end' option is not supplied, dest.end() will be called when | ||
// source gets the 'end' or 'close' events. Only dest.end() once, and | ||
// only when all sources have ended. | ||
if (!options || options.end !== false) { | ||
dest._pipeCount = dest._pipeCount || 0; | ||
dest._pipeCount++; | ||
source.on('end', onend); | ||
source.on('close', onclose); | ||
} | ||
var didOnEnd = false; | ||
function onend() { | ||
if (didOnEnd) return; | ||
didOnEnd = true; | ||
dest._pipeCount--; | ||
// remove the listeners | ||
cleanup(); | ||
if (dest._pipeCount > 0) { | ||
// waiting for other incoming streams to end. | ||
return; | ||
} | ||
dest.end(); | ||
} | ||
function onclose() { | ||
if (didOnEnd) return; | ||
didOnEnd = true; | ||
dest._pipeCount--; | ||
// remove the listeners | ||
cleanup(); | ||
if (dest._pipeCount > 0) { | ||
// waiting for other incoming streams to end. | ||
return; | ||
} | ||
dest.destroy(); | ||
} | ||
// don't leave dangling pipes when there are errors. | ||
function onerror(er) { | ||
cleanup(); | ||
if (this.listeners('error').length === 0) { | ||
throw er; // Unhandled stream error in pipe. | ||
} | ||
} | ||
source.on('error', onerror); | ||
dest.on('error', onerror); | ||
// guarantee that source streams can be paused and resumed, even | ||
// if the only effect is to proxy the event back up the pipe chain. | ||
if (!source.pause) { | ||
source.pause = function() { | ||
source.emit('pause'); | ||
}; | ||
} | ||
if (!source.resume) { | ||
source.resume = function() { | ||
source.emit('resume'); | ||
}; | ||
} | ||
function onpause() { | ||
source.pause(); | ||
} | ||
dest.on('pause', onpause); | ||
function onresume() { | ||
if (source.readable) source.resume(); | ||
} | ||
dest.on('resume', onresume); | ||
// remove all the event listeners that were added. | ||
function cleanup() { | ||
source.removeListener('data', ondata); | ||
dest.removeListener('drain', ondrain); | ||
source.removeListener('end', onend); | ||
source.removeListener('close', onclose); | ||
dest.removeListener('pause', onpause); | ||
dest.removeListener('resume', onresume); | ||
source.removeListener('error', onerror); | ||
dest.removeListener('error', onerror); | ||
source.removeListener('end', cleanup); | ||
source.removeListener('close', cleanup); | ||
dest.removeListener('end', cleanup); | ||
dest.removeListener('close', cleanup); | ||
} | ||
source.on('end', cleanup); | ||
source.on('close', cleanup); | ||
dest.on('end', cleanup); | ||
dest.on('close', cleanup); | ||
dest.emit('pipe', source); | ||
}; | ||
next.push(function() { | ||
if (encoding) { | ||
responseBody = responseBody.toString(encoding); | ||
} | ||
response.emit('data', responseBody); | ||
}); | ||
next.push(function() { | ||
response.emit('end'); | ||
}); | ||
callnext = function() { | ||
if (paused || next.length === 0 || aborted) { return; } | ||
process.nextTick(function() { | ||
next.shift()(); | ||
callnext(); | ||
}); | ||
}; | ||
process.nextTick(function() { | ||
if (typeof callback === 'function') { | ||
callback(response); | ||
} | ||
req.emit('response', response); | ||
callnext(); | ||
}); | ||
}; | ||
return req; | ||
} | ||
var httpRequest = http.request; | ||
http.request = function(options, callback) { | ||
if (!options.host) { | ||
options.host = options.hostname; | ||
if (options.port) | ||
options.host += ":" + options.port; | ||
} | ||
var basePath = 'http://' + options.host | ||
, interceptors = allInterceptors[basePath]; | ||
if (interceptors) { | ||
return processRequest(interceptors, options, callback); | ||
} else { | ||
return httpRequest.apply(http, arguments); | ||
} | ||
}; | ||
); | ||
var httpsRequest = https.request; | ||
https.request = function(options, callback) { | ||
var basePath = 'https://' + options.host | ||
, interceptors = allInterceptors[basePath]; | ||
options.https = true; | ||
if (interceptors && interceptors.length > 0) { | ||
return processRequest(interceptors, options, callback); | ||
} else { | ||
return httpsRequest.apply(https, arguments); | ||
} | ||
}; | ||
module.exports = addGlobalInterceptor; | ||
module.exports = add; |
@@ -64,3 +64,5 @@ var http = require('http'); | ||
callback.apply(res, arguments); | ||
if (callback) { | ||
callback.apply(res, arguments); | ||
} | ||
@@ -67,0 +69,0 @@ }); |
@@ -82,4 +82,4 @@ var path = require('path') | ||
, matches | ||
, proto = options.https ? 'https': 'http'; | ||
, proto = options.proto; | ||
if (transformPathFunction) { path = transformPathFunction(path); } | ||
@@ -86,0 +86,0 @@ if (typeof(body) !== 'string') { |
{ "name" : "nock" | ||
, "description" : "HTTP Server mocking for Node.js" | ||
, "tags" : ["Mock", "HTTP", "testing", "isolation"] | ||
, "version" : "0.7.3" | ||
, "version" : "0.8.0" | ||
, "author" : "Pedro Teixeira <pedro.teixeira@gmail.com>" | ||
@@ -6,0 +6,0 @@ , "contributors" : |
@@ -40,35 +40,37 @@ var nock = require('../.') | ||
var dataCalled = false; | ||
var scope = nock('http://www.yahoo.com') | ||
.get('/') | ||
.reply(200, "Hello World!"); | ||
var req = http.request({ | ||
host: "www.amazon.com" | ||
, path: '/' | ||
, port: 80 | ||
}, function(res) { | ||
var req = http.request({ | ||
host: "www.amazon.com" | ||
, path: '/' | ||
, port: 80 | ||
}, function(res) { | ||
t.equal(res.statusCode, 200); | ||
res.on('end', function() { | ||
var doneFails = false; | ||
t.equal(res.statusCode, 200); | ||
res.on('end', function() { | ||
var doneFails = false; | ||
t.ok(dataCalled); | ||
try { | ||
scope.done(); | ||
} catch(err) { | ||
doneFails = true; | ||
} | ||
t.ok(doneFails); | ||
t.end(); | ||
}); | ||
res.on('data', function(data) { | ||
dataCalled = true; | ||
}); | ||
t.ok(dataCalled); | ||
try { | ||
scope.done(); | ||
} catch(err) { | ||
doneFails = true; | ||
} | ||
t.ok(doneFails); | ||
t.end(); | ||
}); | ||
req.end(); | ||
t.end(); | ||
res.on('data', function(data) { | ||
dataCalled = true; | ||
}); | ||
}); | ||
req.on('error', function(err) { | ||
if (err.code !== 'ECONNREFUSED') { | ||
throw err; | ||
} | ||
t.end(); | ||
}); | ||
req.end(); | ||
}); | ||
@@ -197,2 +199,3 @@ | ||
res.on('end', function() { | ||
console.log('all done here'); | ||
scope.done(); | ||
@@ -214,10 +217,12 @@ t.end(); | ||
var ended = 0; | ||
var end = function() { | ||
scope.done(); | ||
t.end(); | ||
}; | ||
function callback() { | ||
ended += 1; | ||
if (ended === 2) { | ||
scope.done(); | ||
t.end(); | ||
} | ||
} | ||
http.get({ | ||
host: "api.headdy.com" | ||
, method: 'GET' | ||
, path: '/one' | ||
@@ -234,7 +239,3 @@ , port: 80 | ||
res.on('end', function() { | ||
if (++ended === 2) { | ||
end(); | ||
} | ||
}); | ||
res.on('end', callback); | ||
}); | ||
@@ -244,3 +245,2 @@ | ||
host: "api.headdy.com" | ||
, method: 'GET' | ||
, path: '/two' | ||
@@ -257,7 +257,3 @@ , port: 80 | ||
res.on('end', function() { | ||
if (++ended === 2) { | ||
end(); | ||
} | ||
}); | ||
res.on('end', callback); | ||
}); | ||
@@ -876,3 +872,3 @@ | ||
}, function(res) { | ||
t.equal(res.statusCode, 200); | ||
@@ -901,3 +897,3 @@ res.on('end', function() { | ||
res.on('end', function() { | ||
t.ok(dataCalled); | ||
t.ok(dataCalled, 'data event called'); | ||
scope.done(); | ||
@@ -915,1 +911,110 @@ t.end(); | ||
}); | ||
tap.test("can use ClientRequest using GET", function(t) { | ||
var dataCalled = false | ||
var scope = nock('http://www2.clientrequester.com') | ||
.get('/dsad') | ||
.reply(202, "HEHE!"); | ||
var req = new http.ClientRequest({ | ||
host: "www2.clientrequester.com" | ||
, path: '/dsad' | ||
}); | ||
req.end(); | ||
req.on('response', function(res) { | ||
t.equal(res.statusCode, 202); | ||
res.on('end', function() { | ||
t.ok(dataCalled, "data event was called"); | ||
scope.done(); | ||
t.end(); | ||
}); | ||
res.on('data', function(data) { | ||
dataCalled = true; | ||
t.ok(data instanceof Buffer, "data should be buffer"); | ||
t.equal(data.toString(), "HEHE!", "response should match"); | ||
}); | ||
}); | ||
req.end(); | ||
}); | ||
tap.test("can use ClientRequest using POST", function(t) { | ||
var dataCalled = false | ||
var scope = nock('http://www2.clientrequester.com') | ||
.post('/posthere/please', 'heyhey this is the body') | ||
.reply(201, "DOOONE!"); | ||
var req = new http.ClientRequest({ | ||
host: "www2.clientrequester.com" | ||
, path: '/posthere/please' | ||
, method: 'POST' | ||
}); | ||
req.write('heyhey this is the body'); | ||
req.end(); | ||
req.on('response', function(res) { | ||
t.equal(res.statusCode, 201); | ||
res.on('end', function() { | ||
t.ok(dataCalled, "data event was called"); | ||
scope.done(); | ||
t.end(); | ||
}); | ||
res.on('data', function(data) { | ||
dataCalled = true; | ||
t.ok(data instanceof Buffer, "data should be buffer"); | ||
t.equal(data.toString(), "DOOONE!", "response should match"); | ||
}); | ||
}); | ||
req.end(); | ||
}); | ||
tap.test("same url matches twice", function(t) { | ||
var scope = nock('http://www.twicematcher.com') | ||
.get('/hey') | ||
.reply(200, "First match") | ||
.get('/hey') | ||
.reply(201, "Second match"); | ||
var replied = 0; | ||
function callback() { | ||
replied += 1; | ||
if (replied == 2) { | ||
scope.done(); | ||
t.end(); | ||
} | ||
} | ||
http.get({ | ||
host: "www.twicematcher.com" | ||
, path: '/hey' | ||
}, function(res) { | ||
t.equal(res.statusCode, 200); | ||
res.on('data', function(data) { | ||
t.equal(data.toString(), 'First match', 'should match first request response body'); | ||
}); | ||
res.on('end', callback); | ||
}); | ||
http.get({ | ||
host: "www.twicematcher.com" | ||
, path: '/hey' | ||
}, function(res) { | ||
t.equal(res.statusCode, 201); | ||
res.on('data', function(data) { | ||
t.equal(data.toString(), 'Second match', 'should match second request response body'); | ||
}); | ||
res.on('end', callback); | ||
}); | ||
}); |
@@ -29,2 +29,14 @@ var nock = require('../.') | ||
return req; | ||
}); | ||
tap.test('checks if callback is specified', function(t) { | ||
var options = { | ||
host: 'www.google.com', method: 'GET', path: '/', port: 80 | ||
}; | ||
nock.restore(); | ||
nock.recorder.rec(true); | ||
http.request(options, undefined).end(); | ||
t.end(); | ||
}); |
Sorry, the diff of this file is not supported yet
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
49358
13
1466
5
2