nock 0.56.0 → 0.59.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -106,7 +106,7 @@ var scope = nock('http://myapp.iriscouch.com')
106
106
  });
107
107
  ```
108
108
 
109
- The request body can be a string, a RegExp, or a JSON object.
109
+ The request body can be a string, a RegExp, a JSON object or a function.
110
110
 
111
111
  ```js
112
112
  var scope = nock('http://myapp.iriscouch.com')
@@ -118,6 +118,20 @@ var scope = nock('http://myapp.iriscouch.com')
118
118
  });
119
119
  ```
120
120
 
121
+ If the request body is a function, return true if it should be considered a match:
122
+ ```js
123
+ var scope = nock('http://myapp.iriscouch.com')
124
+ .post('/users', function(body) {
125
+ return body.id === '123ABC';
126
+ })
127
+ .reply(201, {
128
+ ok: true,
129
+ id: '123ABC',
130
+ rev: '946B7D1C'
131
+ });
132
+
133
+ ```
134
+
121
135
  ## Specifying replies
122
136
 
123
137
  You can specify the return status code for a path on the first argument of reply like this:
@@ -227,6 +241,32 @@ var scope = nock('http://www.headdy.com')
227
241
  .reply(200, 'The default headers should come too');
228
242
  ```
229
243
 
244
+ ### Including Content-Length Header Automatically
245
+
246
+ When using `scope.reply()` to set a response body manually, you can have the
247
+ `Content-Length` header calculated automatically.
248
+
249
+ ```js
250
+ var scope = nock('http://www.headdy.com')
251
+ .replyContentLength()
252
+ .get('/')
253
+ .reply(200, { hello: 'world' });
254
+ ```
255
+
256
+ **NOTE:** this does not work with streams or other advanced means of specifying
257
+ the reply body.
258
+
259
+ ### Including Date Header Automatically
260
+
261
+ You can automatically append a `Date` header to your mock reply:
262
+
263
+ ```js
264
+ var scope = nock('http://www.headdy.com')
265
+ .replyDate(new Date(2015, 0, 1)) // defaults to now, must use a Date object
266
+ .get('/')
267
+ .reply(200, { hello: 'world' });
268
+ ```
269
+
230
270
  ## HTTP Verbs
231
271
 
232
272
  Nock supports any HTTP verb, and it has convenience methods for the GET, POST, PUT, HEAD, DELETE, PATCH and MERGE HTTP verbs.
package/lib/intercept.js CHANGED
@@ -371,7 +371,15 @@ function activate() {
371
371
  });
372
372
 
373
373
  if (! matches && allowUnmocked) {
374
- return overriddenRequest(options, callback);
374
+ if (proto === 'https') {
375
+ var ClientRequest = http.ClientRequest;
376
+ http.ClientRequest = originalClientRequest;
377
+ req = overriddenRequest(options, callback);
378
+ http.ClientRequest = ClientRequest;
379
+ } else {
380
+ req = overriddenRequest(options, callback);
381
+ }
382
+ return req;
375
383
  }
376
384
 
377
385
  // NOTE: Since we already overrode the http.ClientRequest we are in fact constructing
package/lib/match_body.js CHANGED
@@ -27,17 +27,13 @@ function matchBody(spec, body) {
27
27
 
28
28
  // try to transform body to json
29
29
  var json;
30
- if (typeof spec === 'object') {
30
+ if (typeof spec === 'object' || typeof spec === 'function') {
31
31
  try { json = JSON.parse(body);} catch(err) {}
32
32
  if (json !== undefined) {
33
33
  body = json;
34
34
  }
35
35
  else
36
- if (
37
- (typeof spec === 'object') &&
38
- options.headers
39
- )
40
- {
36
+ if (options.headers) {
41
37
  var contentType = options.headers['Content-Type'] ||
42
38
  options.headers['content-type'];
43
39
 
@@ -47,6 +43,10 @@ function matchBody(spec, body) {
47
43
  }
48
44
  }
49
45
 
46
+ if (typeof spec === "function") {
47
+ return spec.call(this, body);
48
+ }
49
+
50
50
  try {
51
51
  deepEqual(spec, body);
52
52
  return true;
package/lib/recorder.js CHANGED
@@ -6,6 +6,7 @@ var common = require('./common');
6
6
  var intercept = require('./intercept');
7
7
  var debug = require('debug')('nock.recorder');
8
8
  var _ = require('lodash');
9
+ var Stream = require('stream');
9
10
 
10
11
  var SEPARATOR = '\n<<<<<<-- cut here -->>>>>>\n';
11
12
  var recordingInProgress = false;
@@ -83,6 +84,7 @@ var getBodyFromChunks = function(chunks, headers) {
83
84
  };
84
85
 
85
86
  function generateRequestAndResponseObject(req, bodyChunks, options, res, dataChunks) {
87
+
86
88
  options.path = req.path;
87
89
  return {
88
90
  scope: getScope(options),
@@ -197,17 +199,7 @@ function record(rec_options) {
197
199
  options = parse(options);
198
200
  }
199
201
 
200
- var dataChunks = [];
201
-
202
- res.on('data', function(data) {
203
- debug(thisRecordingId, 'new', proto, 'data chunk');
204
- dataChunks.push(data);
205
- });
206
-
207
- if (proto === 'https') {
208
- options._https_ = true;
209
- }
210
-
202
+ // We put our 'end' listener to the front of the listener array.
211
203
  res.once('end', function() {
212
204
  debug(thisRecordingId, proto, 'intercepted request ended');
213
205
 
@@ -248,10 +240,94 @@ function record(rec_options) {
248
240
  }
249
241
  });
250
242
 
243
+ var dataChunks = [];
244
+
245
+ // Give the actual client a chance to setup its listeners.
246
+ // We will use the listener information to figure out
247
+ // how we need to feed the intercepted data back to the client.
251
248
  if (callback) {
252
249
  callback(res, options, callback);
253
250
  }
254
251
 
252
+ // Handle clients that listen to 'readable' by intercepting them
253
+ // and feeding them the data manually.
254
+ var readableListeners = res.listeners('readable');
255
+ if (!_.isEmpty(readableListeners)) {
256
+
257
+ debug('handle readable listeners');
258
+
259
+ // We will replace the client's listeners with our own and manually
260
+ // invoke them.
261
+ _.each(readableListeners, function(listener) {
262
+ res.removeListener('readable', listener);
263
+ });
264
+
265
+ // Repleace the actual Stream.Readable prototype 'read' function
266
+ // so that we can control what the client listener will be reading.
267
+ var prototypeRead = Stream.Readable.prototype.read;
268
+ var currentReadIndex = 0;
269
+ res.read = function() {
270
+
271
+ debug(thisRecordingId, 'client reading data on', proto, dataChunks.length);
272
+
273
+ // Feed the data to the client through from our collected data chunks.
274
+ if (currentReadIndex < dataChunks.length) {
275
+ debug('chunk', chunk, 'read');
276
+ var chunk = dataChunks[currentReadIndex];
277
+ ++currentReadIndex;
278
+ return chunk;
279
+ } else {
280
+ debug('no more chunks to read');
281
+ return null;
282
+ }
283
+
284
+ };
285
+
286
+ // Put our own listener instead of the removed client listener.
287
+ var onReadable = function(data) {
288
+ debug(thisRecordingId, 'new readable data on', proto);
289
+ var chunk;
290
+ // Use the prototypeRead function to actually read the data.
291
+ while (null !== (chunk = prototypeRead.call(res))) {
292
+ debug('read', chunk);
293
+ dataChunks.push(chunk);
294
+ }
295
+ // Manually invoke the user listeners emulating 'readable' event.
296
+ _.each(readableListeners, function(listener) {
297
+ listener();
298
+ });
299
+ };
300
+
301
+ res.on('readable', onReadable);
302
+
303
+ } else {
304
+
305
+ // In all other cases we (for now at least) fall back on intercepting
306
+ // 'data' events.
307
+ debug('fall back on our original implementation');
308
+
309
+ // Since we gave client the chance to setup its listeners
310
+ // before us, we need to remove them and setup our own.
311
+ _.each(res.listeners('data'), function(listener) {
312
+ res.removeListener('data', listener);
313
+ })
314
+ _.each(res.listeners('readable'), function(listener) {
315
+ res.removeListener('readable', listener);
316
+ })
317
+
318
+ var onData = function(data) {
319
+ debug(thisRecordingId, 'new data chunk on', proto);
320
+ dataChunks.push(data);
321
+ };
322
+ res.on('data', onData);
323
+ }
324
+
325
+ debug('finished setting up intercepting');
326
+
327
+ if (proto === 'https') {
328
+ options._https_ = true;
329
+ }
330
+
255
331
  });
256
332
 
257
333
  var oldWrite = req.write;
package/lib/scope.js CHANGED
@@ -30,6 +30,8 @@ function startScope(basePath, options) {
30
30
  urlParts = url.parse(basePath),
31
31
  port = urlParts.port || ((urlParts.protocol === 'http:') ? 80 : 443),
32
32
  persist = false,
33
+ contentLen = false,
34
+ date = null,
33
35
  basePathname = urlParts.pathname.replace(/\/$/, '');
34
36
 
35
37
  basePath = urlParts.protocol + '//' + urlParts.hostname + ':' + port;
@@ -75,6 +77,11 @@ function startScope(basePath, options) {
75
77
  headers = mixin(scope._defaultReplyHeaders, headers);
76
78
  }
77
79
 
80
+ if (date) {
81
+ headers = headers || {};
82
+ headers['date'] = date.toUTCString();
83
+ }
84
+
78
85
  if (headers !== undefined) {
79
86
  this.headers = {};
80
87
 
@@ -101,6 +108,9 @@ function startScope(basePath, options) {
101
108
  if (!this.headers['content-type']) {
102
109
  this.headers['content-type'] = 'application/json';
103
110
  }
111
+ if (contentLen) {
112
+ this.headers['content-length'] = body.length;
113
+ }
104
114
  } catch(err) {
105
115
  throw new Error('Error encoding response body into JSON');
106
116
  }
@@ -493,6 +503,16 @@ function startScope(basePath, options) {
493
503
  return persist;
494
504
  }
495
505
 
506
+ function replyContentLength() {
507
+ contentLen = true;
508
+ return this;
509
+ }
510
+
511
+ function replyDate(d) {
512
+ date = d || new Date();
513
+ return this;
514
+ }
515
+
496
516
 
497
517
  scope = {
498
518
  get: get
@@ -513,6 +533,8 @@ function startScope(basePath, options) {
513
533
  , persist: _persist
514
534
  , shouldPersist: shouldPersist
515
535
  , pendingMocks: pendingMocks
536
+ , replyContentLength: replyContentLength
537
+ , replyDate: replyDate
516
538
  };
517
539
 
518
540
  return scope;
package/package.json CHANGED
@@ -7,7 +7,7 @@
7
7
  "testing",
8
8
  "isolation"
9
9
  ],
10
- "version": "0.56.0",
10
+ "version": "0.59.1",
11
11
  "author": "Pedro Teixeira <pedro.teixeira@gmail.com>",
12
12
  "contributors": [
13
13
  {
@@ -116,7 +116,7 @@
116
116
  "jshint": "^2.5.6",
117
117
  "needle": "^0.7.1",
118
118
  "pre-commit": "0.0.9",
119
- "request": "*",
119
+ "request": "2.51.0",
120
120
  "restler": "3.2.2",
121
121
  "restify": "^2.8.1",
122
122
  "superagent": "~0.15.7",
@@ -0,0 +1,24 @@
1
+ 'use strict';
2
+
3
+ var test = require('tap').test;
4
+ var mikealRequest = require('request');
5
+
6
+ test('allowUnmock for https', function(t) {
7
+ var nock = require('../');
8
+ var scope = nock('https://www.google.com/', {allowUnmocked: true})
9
+ .get('/pathneverhit')
10
+ .reply(200, {foo: 'bar'});
11
+
12
+ var options = {
13
+ method: 'GET',
14
+ uri: 'https://www.google.com'
15
+ };
16
+
17
+ mikealRequest(options, function(err, resp, body) {
18
+ t.notOk(err, 'should be no error');
19
+ t.true(typeof body !== 'undefined', 'body should not be undefined');
20
+ t.true(body.length !== 0, 'body should not be empty');
21
+ t.end();
22
+ return console.log(resp.statusCode, 'body length: ', body.length);
23
+ });
24
+ });
@@ -340,6 +340,34 @@ test("post with regexp as spec", function(t) {
340
340
  req.end();
341
341
  });
342
342
 
343
+ test("post with function as spec", function(t) {
344
+ var scope = nock('http://www.google.com')
345
+ .post('/echo', function(body) {
346
+ return body === 'key=val';
347
+ })
348
+ .reply(200, function(uri, body) {
349
+ return ['OK', uri, body].join(' ');
350
+ });
351
+
352
+ var req = http.request({
353
+ host: "www.google.com"
354
+ , method: 'POST'
355
+ , path: '/echo'
356
+ , port: 80
357
+ }, function(res) {
358
+ res.on('end', function() {
359
+ scope.done();
360
+ t.end();
361
+ });
362
+ res.on('data', function(data) {
363
+ t.equal(data.toString(), 'OK /echo key=val' , 'response should match');
364
+ });
365
+ });
366
+
367
+ req.write('key=val');
368
+ req.end();
369
+ });
370
+
343
371
  test("post with chaining on call", function(t) {
344
372
  var input = 'key=val';
345
373
 
@@ -1035,6 +1063,8 @@ test("reply with JSON", function(t) {
1035
1063
 
1036
1064
  res.setEncoding('utf8');
1037
1065
  t.equal(res.statusCode, 200);
1066
+ t.notOk(res.headers['date']);
1067
+ t.notOk(res.headers['content-length']);
1038
1068
  t.equal(res.headers['content-type'], 'application/json');
1039
1069
  res.on('end', function() {
1040
1070
  t.ok(dataCalled);
@@ -1052,6 +1082,47 @@ test("reply with JSON", function(t) {
1052
1082
 
1053
1083
  });
1054
1084
 
1085
+ test("reply with content-length header", function(t){
1086
+ var scope = nock('http://www.jsonreplier.com')
1087
+ .replyContentLength()
1088
+ .get('/')
1089
+ .reply(200, {hello: "world"});
1090
+
1091
+ var req = http.get({
1092
+ host: "www.jsonreplier.com"
1093
+ , path: '/'
1094
+ , port: 80
1095
+ }, function(res) {
1096
+ t.equal(res.headers['content-length'], 17);
1097
+ res.on('end', function() {
1098
+ scope.done();
1099
+ t.end();
1100
+ });
1101
+ });
1102
+ });
1103
+
1104
+ test("reply with date header", function(t){
1105
+ var date = new Date();
1106
+
1107
+ var scope = nock('http://www.jsonreplier.com')
1108
+ .replyDate(date)
1109
+ .get('/')
1110
+ .reply(200, {hello: "world"});
1111
+
1112
+ var req = http.get({
1113
+ host: "www.jsonreplier.com"
1114
+ , path: '/'
1115
+ , port: 80
1116
+ }, function(res) {
1117
+ console.error(res.headers);
1118
+ t.equal(res.headers['date'], date.toUTCString());
1119
+ res.on('end', function() {
1120
+ scope.done();
1121
+ t.end();
1122
+ });
1123
+ });
1124
+ });
1125
+
1055
1126
  test("filter path with function", function(t) {
1056
1127
  var scope = nock('http://www.filterurls.com')
1057
1128
  .filteringPath(function(path) {
@@ -543,24 +543,88 @@ tap.test('records request headers except user-agent if enable_reqheaders_recordi
543
543
  });
544
544
 
545
545
  tap.test('includes query parameters from superagent', function(t) {
546
- nock.restore();
547
- nock.recorder.clear();
548
- t.equal(nock.recorder.play().length, 0);
546
+ nock.restore();
547
+ nock.recorder.clear();
548
+ t.equal(nock.recorder.play().length, 0);
549
549
 
550
- nock.recorder.rec({
551
- dont_print: true,
552
- output_objects: true
550
+ nock.recorder.rec({
551
+ dont_print: true,
552
+ output_objects: true
553
+ });
554
+
555
+ superagent.get('http://google.com')
556
+ .query({q: 'test search' })
557
+ .end(function(res) {
558
+ nock.restore();
559
+ var ret = nock.recorder.play();
560
+ t.true(ret.length >= 1);
561
+ t.equal(ret[0].path, '/?q=test%20search');
562
+ t.end();
553
563
  });
564
+ });
565
+
566
+ tap.test('works with clients listening for readable', function(t) {
567
+ nock.restore();
568
+ nock.recorder.clear();
569
+ t.equal(nock.recorder.play().length, 0);
570
+
571
+ var REQUEST_BODY = 'ABCDEF';
572
+ var RESPONSE_BODY = '012345';
573
+
574
+ // Create test http server and perform the tests while it's up.
575
+ var testServer = http.createServer(function (req, res) {
576
+ res.write(RESPONSE_BODY);
577
+ res.end();
578
+ }).listen(8081, function(err) {
579
+
580
+ // t.equal(err, undefined);
581
+
582
+ var options = { host:'localhost'
583
+ , port:testServer.address().port
584
+ , path:'/' }
585
+ ;
586
+
587
+ var rec_options = {
588
+ dont_print: true,
589
+ output_objects: true
590
+ };
591
+
592
+ nock.recorder.rec(rec_options);
593
+
594
+ var req = http.request(options, function(res) {
595
+ var readableCount = 0;
596
+ var chunkCount = 0;
597
+ res.on('readable', function() {
598
+ ++readableCount;
599
+ var chunk;
600
+ while (null !== (chunk = res.read())) {
601
+ t.equal(chunk.toString(), RESPONSE_BODY);
602
+ ++chunkCount;
603
+ }
604
+ });
605
+ res.once('end', function() {
606
+ nock.restore();
607
+ var ret = nock.recorder.play();
608
+ t.equal(ret.length, 1);
609
+ ret = ret[0];
610
+ t.type(ret, 'object');
611
+ t.equal(readableCount, 1);
612
+ t.equal(chunkCount, 1);
613
+ t.equal(ret.scope, "http://localhost:" + options.port);
614
+ t.equal(ret.method, "GET");
615
+ t.equal(ret.body, REQUEST_BODY);
616
+ t.equal(ret.status, 200);
617
+ t.equal(ret.response, RESPONSE_BODY);
618
+ t.end();
619
+
620
+ // Close the test server, we are done with it.
621
+ testServer.close();
622
+ });
623
+ });
624
+
625
+ req.end(REQUEST_BODY);
626
+ });
554
627
 
555
- superagent.get('http://google.com')
556
- .query({q: 'test search' })
557
- .end(function(res) {
558
- nock.restore();
559
- var ret = nock.recorder.play();
560
- t.true(ret.length >= 1);
561
- t.equal(ret[0].path, '/?q=test%20search');
562
- t.end();
563
- });
564
628
  });
565
629
 
566
630
  tap.test("teardown", function(t) {
@@ -572,4 +636,4 @@ tap.test("teardown", function(t) {
572
636
  }
573
637
  t.deepEqual(leaks, [], 'No leaks');
574
638
  t.end();
575
- });
639
+ });
@@ -0,0 +1,28 @@
1
+ 'use strict';
2
+
3
+ var assert = require('assert');
4
+ var test = require('tap').test;
5
+ var mikealRequest = require('request');
6
+ var nock = require('../');
7
+
8
+ test("follows refirects", function(t) {
9
+ nock('http://redirecter.com')
10
+ .get('/YourAccount')
11
+ .reply(302, undefined, {
12
+ 'Location': 'http://redirecter.com/Login'
13
+ })
14
+ .get('/Login')
15
+ .reply(200, 'Here is the login page');
16
+
17
+ mikealRequest('http://redirecter.com/YourAccount', function(err, res, body) {
18
+ if (err) {
19
+ throw err;
20
+ }
21
+
22
+ assert.equal(res.statusCode, 200);
23
+
24
+ assert.equal(body, 'Here is the login page');
25
+ t.end();
26
+ });
27
+
28
+ });