diff --git a/spec/ParseServerRESTController.spec.js b/spec/ParseServerRESTController.spec.js index 31d1f5aec7..6e8576ba13 100644 --- a/spec/ParseServerRESTController.spec.js +++ b/spec/ParseServerRESTController.spec.js @@ -2,6 +2,7 @@ const ParseServerRESTController = require('../lib/ParseServerRESTController') .ParseServerRESTController; const ParseServer = require('../lib/ParseServer').default; const Parse = require('parse/node').Parse; +const request = require('../lib/request'); let RESTController; @@ -519,6 +520,65 @@ describe('ParseServerRESTController', () => { ); }); + it('should strip undefined values from cloud function responses (with directAccess)', async () => { + Parse.Cloud.define('returnUndefinedValues', () => { + return { + definedKey: 'value', + undefinedKey: undefined, + nested: { a: 1, b: undefined }, + arrayWithUndefined: [1, undefined, 3], + }; + }); + + const res = await RESTController.request( + 'POST', + '/functions/returnUndefinedValues', + {}, + { useMasterKey: true } + ); + + expect(res.result.definedKey).toEqual('value'); + expect(res.result.undefinedKey).toBeUndefined(); + expect(Object.hasOwnProperty.call(res.result, 'undefinedKey')).toBe(false); + expect(res.result.nested.a).toEqual(1); + expect(Object.hasOwnProperty.call(res.result.nested, 'b')).toBe(false); + expect(res.result.arrayWithUndefined).toEqual([1, null, 3]); + }); + + it('should strip undefined values from cloud function responses (without directAccess)', async () => { + Parse.Cloud.define('returnUndefinedValuesHTTP', () => { + return { + definedKey: 'value', + undefinedKey: undefined, + nested: { a: 1, b: undefined }, + arrayWithUndefined: [1, undefined, 3], + }; + }); + + const serverURL = 'http://localhost:8378/1'; + const headers = { + 'Content-Type': 'application/json', + 'X-Parse-Application-Id': Parse.applicationId, + 'X-Parse-Master-Key': Parse.masterKey, + }; + + const res = await request({ + method: 'POST', + headers, + url: `${serverURL}/functions/returnUndefinedValuesHTTP`, + body: {}, + }); + + const result = res.data.result; + + expect(result.definedKey).toEqual('value'); + expect(result.undefinedKey).toBeUndefined(); + expect(Object.hasOwnProperty.call(result, 'undefinedKey')).toBe(false); + expect(result.nested.a).toEqual(1); + expect(Object.hasOwnProperty.call(result.nested, 'b')).toBe(false); + expect(result.arrayWithUndefined).toEqual([1, null, 3]); + }); + it('ensures sessionTokens are properly handled', async () => { const user = await Parse.User.signUp('user', 'pass'); const sessionToken = user.getSessionToken(); @@ -675,4 +735,104 @@ describe('ParseServerRESTController', () => { const result = await Parse.Push.getPushStatus(pushStatusId); expect(result.id).toBe(pushStatusId); }); + + it('should not convert undefined values to null on update with directAccess', async () => { + const createRes = await RESTController.request('POST', '/classes/MyObject', { + presentField: 'hello', + }); + expect(createRes.objectId).toBeDefined(); + + await RESTController.request('PUT', `/classes/MyObject/${createRes.objectId}`, { + presentField: 'updated', + absentField: undefined, + nested: { absentField: undefined, presentField: 'value' }, + }); + + const getRes = await RESTController.request('GET', `/classes/MyObject/${createRes.objectId}`); + + expect(getRes.presentField).toBe('updated'); + expect(getRes.absentField).toBeUndefined(); + expect('absentField' in getRes).toBe(false); + expect(getRes.nested).toBeDefined(); + expect(getRes.nested.presentField).toBe('value'); + expect('absentField' in getRes.nested).toBe(false); + }); + + it('should not convert undefined values to null on create with directAccess', async () => { + const createRes = await RESTController.request('POST', '/classes/MyObject', { + presentField: 'hello', + absentField: undefined, + }); + expect(createRes.objectId).toBeDefined(); + + const getRes = await RESTController.request('GET', `/classes/MyObject/${createRes.objectId}`); + + expect(getRes.presentField).toBe('hello'); + expect(getRes.absentField).toBeUndefined(); + expect('absentField' in getRes).toBe(false); + }); + + it('should not convert undefined values to null on update without directAccess (HTTP mode)', async () => { + const serverURL = 'http://localhost:8378/1'; + const headers = { + 'Content-Type': 'application/json', + 'X-Parse-Application-Id': Parse.applicationId, + 'X-Parse-Master-Key': Parse.masterKey, + }; + + const createRes = await request({ + method: 'POST', + headers, + url: `${serverURL}/classes/MyObject`, + body: { presentField: 'hello' }, + }); + expect(createRes.data.objectId).toBeDefined(); + + await request({ + method: 'PUT', + headers, + url: `${serverURL}/classes/MyObject/${createRes.data.objectId}`, + body: { presentField: 'updated', absentField: undefined, nested: { absentField: undefined, presentField: 'value' } }, + }); + + const getRes = await request({ + method: 'GET', + headers, + url: `${serverURL}/classes/MyObject/${createRes.data.objectId}`, + }); + + expect(getRes.data.presentField).toBe('updated'); + expect(getRes.data.absentField).toBeUndefined(); + expect('absentField' in getRes.data).toBe(false); + expect(getRes.data.nested).toBeDefined(); + expect(getRes.data.nested.presentField).toBe('value'); + expect('absentField' in getRes.data.nested).toBe(false); + }); + + it('should not convert undefined values to null on create without directAccess (HTTP mode)', async () => { + const serverURL = 'http://localhost:8378/1'; + const headers = { + 'Content-Type': 'application/json', + 'X-Parse-Application-Id': Parse.applicationId, + 'X-Parse-Master-Key': Parse.masterKey, + }; + + const createRes = await request({ + method: 'POST', + headers, + url: `${serverURL}/classes/MyObject`, + body: { presentField: 'hello', absentField: undefined }, + }); + expect(createRes.data.objectId).toBeDefined(); + + const getRes = await request({ + method: 'GET', + headers, + url: `${serverURL}/classes/MyObject/${createRes.data.objectId}`, + }); + + expect(getRes.data.presentField).toBe('hello'); + expect(getRes.data.absentField).toBeUndefined(); + expect('absentField' in getRes.data).toBe(false); + }); }); diff --git a/src/ParseServerRESTController.js b/src/ParseServerRESTController.js index 9ec4b6f86e..bc7ff564da 100644 --- a/src/ParseServerRESTController.js +++ b/src/ParseServerRESTController.js @@ -113,7 +113,7 @@ function ParseServerRESTController(applicationId, router) { return new Promise((resolve, reject) => { getAuth(options, config).then(auth => { const request = { - body: data, + body: JSON.parse(JSON.stringify(data)), config, auth, info: { @@ -122,7 +122,7 @@ function ParseServerRESTController(applicationId, router) { installationId: options.installationId, context: options.context || {}, }, - query, + query: query ? JSON.parse(JSON.stringify(query)) : query, }; return Promise.resolve() .then(() => { @@ -131,10 +131,11 @@ function ParseServerRESTController(applicationId, router) { .then( resp => { const { response, status, headers = {} } = resp; + const strippedResponse = JSON.parse(JSON.stringify(response)); if (options.returnStatus) { - resolve({ ...response, _status: status, _headers: headers }); + resolve({ ...strippedResponse, _status: status, _headers: headers }); } else { - resolve(response); + resolve(strippedResponse); } }, err => {