From d434e4ed94cefc9be8ef7d0a44d56752629acb32 Mon Sep 17 00:00:00 2001 From: Horia Radu Date: Thu, 21 Jul 2016 13:06:12 +0300 Subject: [PATCH] Stringify query params which are objects as JSON Stringify query params which are objects in a way in which empty ararys and null values are preserved instead of removed (default querystring implementation). fix: https://github.com/strongloop/strong-remoting/issues/324 --- lib/http-invocation.js | 13 +++- test/http-invocation.test.js | 145 +++++++++++++++++++++++++++++++++++ 2 files changed, 156 insertions(+), 2 deletions(-) diff --git a/lib/http-invocation.js b/lib/http-invocation.js index abf50487..72b40c19 100644 --- a/lib/http-invocation.js +++ b/lib/http-invocation.js @@ -128,7 +128,7 @@ HttpInvocation.prototype._processArg = function(req, verb, query, accept) { // From the query string if (val !== undefined) { query = query || {}; - query[name] = val; + query[name] = serializeQueryStringValue(val, accept); } break; case 'header': @@ -148,7 +148,7 @@ HttpInvocation.prototype._processArg = function(req, verb, query, accept) { // default to query string for GET if (val !== undefined) { query = query || {}; - query[name] = val; + query[name] = serializeQueryStringValue(val, accept); } } else { // default to storing args on the body for !GET @@ -159,6 +159,15 @@ HttpInvocation.prototype._processArg = function(req, verb, query, accept) { return query; }; +function serializeQueryStringValue(val, accept) { + if ((accept.type === 'object' || accept.type === 'string') && + typeof val === 'object') { + return JSON.stringify(val); + } else { + return val; + } +} + /** * Build args object from the http context's `req` and `res`. */ diff --git a/test/http-invocation.test.js b/test/http-invocation.test.js index 505a38c9..12d379c0 100644 --- a/test/http-invocation.test.js +++ b/test/http-invocation.test.js @@ -193,6 +193,133 @@ describe('HttpInvocation', function() { inv.transformResponse(res, body, cb); } }); + + describe('createRequest', function() { + it('creates a simple request', function() { + var inv = givenInvocationForEndpoint(null, []); + var expectedReq = { method: 'GET', + url: 'http://base/testModel/testMethod', + protocol: 'http:', + json: true, + }; + expect(inv.createRequest()).to.eql(expectedReq); + }); + + it('makes primitive type arguments as query params', function() { + var accepts = [ + { arg: 'a', type: 'number' }, + { arg: 'b', type: 'string' }, + ]; + var aValue = 2; + var bValue = 'foo'; + var inv = givenInvocationForEndpoint(accepts, [aValue, bValue]); + var expectedReq = { method: 'GET', + url: 'http://base/testModel/testMethod?a=2&b=foo', + protocol: 'http:', + json: true, + }; + expect(inv.createRequest()).to.eql(expectedReq); + }); + + it('makes an array argument as a query param', function() { + var accepts = [ + { arg: 'a', type: 'object' }, + ]; + var aValue = [1, 2, 3]; + var inv = givenInvocationForEndpoint(accepts, [aValue]); + var expectedReq = { method: 'GET', + url: 'http://base/testModel/testMethod?a=' + encodeURIComponent('[1,2,3]'), + protocol: 'http:', + json: true, + }; + expect(inv.createRequest()).to.eql(expectedReq); + }); + + it('keeps an empty array as a query param', function() { + var accepts = [ + { arg: 'a', type: 'object' }, + ]; + var aValue = []; + var inv = givenInvocationForEndpoint(accepts, [aValue]); + var expectedReq = { method: 'GET', + url: 'http://base/testModel/testMethod?a=' + encodeURIComponent('[]'), + protocol: 'http:', + json: true, + }; + expect(inv.createRequest()).to.eql(expectedReq); + }); + + it('keeps an empty array as a body param for a POST request', function() { + var accepts = [ + { arg: 'a', type: 'object' }, + ]; + var aValue = []; + var inv = givenInvocationForEndpoint(accepts, [aValue], 'POST'); + var expectedReq = { method: 'POST', + url: 'http://base/testModel/testMethod', + protocol: 'http:', + json: true, + body: { + a: [], + }, + }; + expect(inv.createRequest()).to.eql(expectedReq); + }); + + it('handles a loopback filter as a query param', function() { + var accepts = [ + { arg: 'filter', type: 'object' }, + ]; + var filter = { + where: { + id: { + inq: [1, 2], + }, + typeId: { + inq: [], + }, + }, + include: ['related'], + }; + var inv = givenInvocationForEndpoint(accepts, [filter]); + var expectedFilter = + '{"where":{"id":{"inq":[1,2]},"typeId":{"inq":[]}},"include":["related"]}'; + var expectedReq = { method: 'GET', + url: 'http://base/testModel/testMethod?filter=' + + encodeURIComponent(expectedFilter), + protocol: 'http:', + json: true, + }; + expect(inv.createRequest()).to.eql(expectedReq); + }); + }); + + it('handles a loopback filter as a body param for a POST request', function() { + var accepts = [ + { arg: 'filter', type: 'object' }, + ]; + var filter = { + where: { + id: { + inq: [1, 2], + }, + typeId: { + inq: [], + }, + }, + include: ['related'], + }; + var inv = givenInvocationForEndpoint(accepts, [filter], 'POST'); + var expectedReq = { method: 'POST', + url: 'http://base/testModel/testMethod', + protocol: 'http:', + json: true, + body: { + filter: filter, + }, + }; + expect(inv.createRequest()).to.eql(expectedReq); + }); }); function givenSharedStaticMethod(fn, config) { @@ -217,3 +344,21 @@ function givenInvocation(method, params) { params.auth, params.typeRegistry || new TypeRegistry()); } + +function givenInvocationForEndpoint(accepts, args, verb) { + var method = givenSharedStaticMethod({ + accepts: accepts, + }); + method.getEndpoints = function() { + return [createEndpoint({ verb: verb || 'GET' })]; + }; + return givenInvocation(method, { ctorArgs: [], args: args, baseUrl: 'http://base' }); +} + +function createEndpoint(config) { + config = config || {}; + return { + verb: config.verb || 'GET', + fullPath: config.fullPath || '/testModel/testMethod', + }; +}