Repository: node-nock/nock
Pull request #1110 (2018-04-05 21:57:11)

feat(match_body): urlencoded body matches not only string values
It's first step to resolving issue #1106.

I think, that this behaviour is good, but can be unexpected for long-time nock users. Maybe we need an option to enable this behaviour?
~~Also RegExp values are turning into strings. It's not good.~~ (Updated)
I can continue what I started if the whole approach is acceptable.
Review please @gr2m.


gr2m: hey sorry I’m out vacationing (mostly) until April 22nd. At first glance the changes look good to me, as long as all current tests pass I’m okay with it. If we break things, we’ll fix them and improve the tests :)

Patch:
diff --git a/lib/match_body.js b/lib/match_body.js
index 867a32e..b0d5b8b 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,33 @@ function matchBody(spec, body) {
     spec = spec.replace(/\r?\n|\r/g, '');
   }
 
+  if (isUrlencoded) {
+    spec = mapValuesDeep(spec, (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((v) => mapValuesDeep(v, cb))
+  }
+  if (_.isPlainObject(obj)) {
+    return _.mapValues(obj, (v) => 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();
+  });
+});