Repository: node-nock/nock (js)

Issue:
Remove AIRPLANE mode from tests in test_intercept.js
Tracking the fix here for #1077 

Pull request description:
[WIP] Remove airplane dependency from "test_intercept.js"
Fixes #1103 . Part of #1077 

Patch:
diff --git a/lib/match_body.js b/lib/match_body.js
index 867a32e..a65fe2e 100644
--- a/lib/match_body.js
+++ b/lib/match_body.js
@@ -2,6 +2,7 @@
 
 var deepEqual = require('deep-equal');
 var qs = require('qs');
+var _ = require('lodash')
 
 module.exports =
 function matchBody(spec, body) {
@@ -14,10 +15,14 @@ function matchBody(spec, body) {
     body = body.toString();
   }
 
-  var contentType = options.headers && (options.headers['Content-Type'] ||
-                                        options.headers['content-type']);
+  var contentType = (
+    options.headers &&
+    (options.headers['Content-Type'] || options.headers['content-type']) ||
+    ''
+  ).toString();
 
-  var isMultipart = contentType && contentType.toString().match(/multipart/);
+  var isMultipart = contentType.indexOf('multipart') >= 0;
+  var isUrlencoded = contentType.indexOf('application/x-www-form-urlencoded') >= 0;
 
   // try to transform body to json
   var json;
@@ -25,10 +30,8 @@ function matchBody(spec, body) {
     try { json = JSON.parse(body);} catch(err) {}
     if (json !== undefined) {
       body = json;
-    } else {
-      if (contentType && contentType.toString().match(/application\/x-www-form-urlencoded/)) {
-        body = qs.parse(body, { allowDots: true });
-      }
+    } else if (isUrlencoded) {
+      body = qs.parse(body, { allowDots: true });
     }
   }
 
@@ -54,9 +57,37 @@ function matchBody(spec, body) {
     spec = spec.replace(/\r?\n|\r/g, '');
   }
 
+  if (isUrlencoded) {
+    spec = mapValuesDeep(spec, function(val) {
+      if (_.isRegExp(val)) {
+        return val
+      }
+      return val + ''
+    })
+  }
+
   return deepEqualExtended(spec, body);
 };
 
+
+/**
+ * Based on lodash issue discussion
+ * https://github.com/lodash/lodash/issues/1244
+ */
+function mapValuesDeep(obj, cb) {
+  if (_.isArray(obj)) {
+    return obj.map(function(v) {
+      return mapValuesDeep(v, cb)
+    })
+  }
+  if (_.isPlainObject(obj)) {
+    return _.mapValues(obj, function(v) {
+      return mapValuesDeep(v, cb)
+    })
+  }
+  return cb(obj)
+}
+
 function deepEqualExtended(spec, body) {
   if (spec && spec.constructor === RegExp) {
     return spec.test(body);


Test patch:
diff --git a/tests/test_body_match.js b/tests/test_body_match.js
index c2f454f..9666a31 100644
--- a/tests/test_body_match.js
+++ b/tests/test_body_match.js
@@ -174,3 +174,49 @@ test('array like urlencoded form posts are correctly parsed', function(t) {
     t.end();
   });
 });
+
+test('urlencoded form posts are matched with non-string values', function(t) {
+
+  nock('http://encodingsareus.com')
+      .post('/', {
+        boolean: true,
+        number: 1,
+        values: [false, -1, 'test']
+      })
+      .reply(200);
+
+  mikealRequest({
+    url: 'http://encodingsareus.com/',
+    method: 'post',
+    form: {
+      boolean: true,
+      number: 1,
+      values: [false, -1, 'test']
+    }
+  }, function(err, res) {
+    if (err) throw err;
+    assert.equal(res.statusCode, 200);
+    t.end();
+  });
+});
+
+test('urlencoded form posts are matched with regexp', function(t) {
+
+  nock('http://encodingsareus.com')
+      .post('/', {
+        regexp: /^xyz$/,
+      })
+      .reply(200);
+
+  mikealRequest({
+    url: 'http://encodingsareus.com/',
+    method: 'post',
+    form: {
+      regexp: 'xyz',
+    }
+  }, function(err, res) {
+    if (err) throw err;
+    assert.equal(res.statusCode, 200);
+    t.end();
+  });
+});
diff --git a/tests/test_intercept.js b/tests/test_intercept.js
index 0c9a03f..6edbabf 100644
--- a/tests/test_intercept.js
+++ b/tests/test_intercept.js
@@ -2,6 +2,7 @@
 
 var fs      = require('fs');
 var nock    = require('../.');
+var url     = require('url');
 var http    = require('http');
 var https   = require('https');
 var util    = require('util');
@@ -2105,53 +2106,84 @@ test("two scopes with the same request are consumed", function(t) {
   }
 });
 
-test("allow unmocked option works", {skip: process.env.AIRPLANE}, function(t) {
-  var scope = nock('http://www.google.com', {allowUnmocked: true})
-    .get('/abc')
-    .reply(200, 'Hey!')
-    .get('/wont/get/here')
-    .reply(200, 'Hi!');
+test("allow unmocked option works", function(t) {
+  t.plan(7)
+
+  const server = http.createServer((request, response) => {
+    t.pass('server received a request')
+
+    switch (url.parse(request.url).pathname) {
+      case '/':
+        response.writeHead(200)
+        response.write('server served a response')
+        break
+      case '/not/available':
+        response.writeHead(404)
+        break
+      case '/abc':
+        response.writeHead(200)
+        response.write('server served a response')
+        break
+    }
 
-  function secondIsDone() {
-    t.ok(! scope.isDone());
-    http.request({
-        host: "www.google.com"
-      , path: "/"
-      , port: 80
-      }, function(res) {
-        res.destroy();
-        t.assert(res.statusCode < 400 && res.statusCode >= 200, 'GET Google Home page');
-        t.end();
-      }
-    ).end();
-  }
+    response.end()
+  })
 
-  function firstIsDone() {
-    t.ok(! scope.isDone());
-    http.request({
-        host: "www.google.com"
-      , path: "/does/not/exist/dskjsakdj"
-      , port: 80
-      }, function(res) {
-        t.assert(res.statusCode === 404, 'Google say it does not exist');
-        res.on('data', function() {});
-        res.on('end', secondIsDone);
-      }
-    ).end();
-  }
+  server.listen(() => {
+    const scope = nock(`http://localhost:${server.address().port}`, {allowUnmocked: true})
+      .get('/abc')
+      .reply(304, 'served from our mock')
+      .get('/wont/get/here')
+      .reply(304, 'served from our mock')
 
-  http.request({
-      host: "www.google.com"
-    , path: "/abc"
-    , port: 80
-    }, function(res) {
-      res.on('end', firstIsDone);
+    function secondIsDone() {
+      t.ok(! scope.isDone())
+
+      http.request({
+        host: "localhost",
+        path: "/",
+        port: server.address().port
+      }, response => {
+        response.destroy()
+
+        t.assert(response.statusCode == 200, 'Do not intercept /');
+
+        server.close(t.end)
+      }).end()
+    }
+
+    function firstIsDone() {
+      t.ok(! scope.isDone())
+
+      http.request({
+        host: "localhost",
+        path: "/not/available",
+        port: server.address().port
+      }, response => {
+        t.assert(response.statusCode === 404, 'Server says it does not exist')
+
+        response.on('data', function() {})
+        response.on('end', secondIsDone)
+      }).end()
+    }
+
+    const request = http.request({
+      host: "localhost",
+      path: "/abc",
+      port: server.address().port
+    }, response => {
+      t.assert(response.statusCode == 304, 'Intercept /abc')
+
+      response.on('end', firstIsDone)
       // Streams start in 'paused' mode and must be started.
       // See https://nodejs.org/api/stream.html#stream_class_stream_readable
-      res.resume();
-    }
-  ).end();
-});
+      response.resume()
+    })
+
+    request.on('error', t.error)
+    request.end()
+  })
+})
 
 test("default reply headers work", function(t) {
   nock('http://default.reply.headers.com')
@@ -2655,31 +2687,55 @@ test("persist reply with file", function(t) {
   }, t.end.bind(t));
 });
 
-test('(re-)activate after restore', {skip: process.env.AIRPLANE}, function(t) {
-  var scope = nock('http://google.com')
-    .get('/')
-    .reply(200, 'Hello, World!');
+test('(re-)activate after restore', function(t) {
+  t.plan(7)
 
-  nock.restore();
-  t.false(nock.isActive());
+  const server = http.createServer((request, response) => {
+    t.pass('server received a request')
 
-  http.get('http://google.com/', function(res) {
-    res.resume();
-    res.on('end', function() {
-      t.ok(!scope.isDone());
+    switch (url.parse(request.url).pathname) {
+      case '/':
+        response.writeHead(200)
+        response.write('server served a response')
+        break
+    }
+
+    response.end()
+  })
+
+  server.listen(() => {
+    const scope = nock(`http://localhost:${server.address().port}`)
+      .get('/')
+      .reply(304, 'served from our mock')
+
+      nock.restore()
+      t.false(nock.isActive())
+
+      http.get(`http://localhost:${server.address().port}`, function(res) {
+        res.resume()
+
+        t.is(200, res.statusCode)
 
-      nock.activate();
-      t.true(nock.isActive());
-      http.get('http://google.com', function(res) {
-        res.resume();
         res.on('end', function() {
-          t.ok(scope.isDone());
-          t.end();
-        });
-      }).end();
-    });
-  }).end();
-});
+          t.ok(!scope.isDone())
+
+          nock.activate()
+          t.true(nock.isActive())
+          http.get(`http://localhost:${server.address().port}`, function(res) {
+            res.resume()
+
+            t.is(304, res.statusCode)
+
+            res.on('end', function() {
+              t.ok(scope.isDone())
+
+              server.close(t.end)
+            });
+          })
+        })
+      })
+  })
+})
 
 test("allow unmocked option works with https", {skip: process.env.AIRPLANE}, function(t) {
   t.plan(5)