diff --git a/3.0.0.md b/3.0.0.md new file mode 100644 index 00000000..e265bac3 --- /dev/null +++ b/3.0.0.md @@ -0,0 +1,226 @@ +# Upgrading Parse Server to version 3.0.0 + +parse-server 3.0.0 comes also with the JS SDK version 2.0 which requires a migration. Follow the migration guide [here](https://github.com/parse-community/Parse-SDK-JS/blob/master/2.0.0.md). + +With the 3.0.0, parse-server has completely revamped it's cloud code interface. Gone are the backbone style calls, welcome promises and async/await. + +In order to leverage those new nodejs features, you'll need to run at least node 8.10. We've dropped the support of node 6 a few months ago, so if you haven't made the change yet, now would be a really good time to take the plunge. + +## Migrating Cloud Code Hooks + +### Synchronous validations: + +```js +// before +Parse.Cloud.beforeSave('MyClassName', function(request, response) { + if (!passesValidation(req.object)) { + response.error('Ooops something went wrong'); + } else { + response.success(); + } +}); + +// after +Parse.Cloud.beforeSave('MyClassName', (request) => { + if (!passesValidation(req.object)) { + throw 'Ooops something went wrong'; + } +}); +``` + +All methods are wrapped in promises, so you can freely `throw` `Error`, `Parse.Error` or `string` to mark an error. + +### Asynchronous validations: + +For asynchronous code, you can use promises or async / await. + +Consider the following beforeSave call that would replace the contents of a fileURL with a proper copy of the image as a Parse.File. + +```js +// before +Parse.Cloud.beforeSave('Post', function(request, response) { + Parse.Cloud.httpRequest({ + url: request.object.get('fileURL'), + success: function(contents) { + const file = new Parse.File('image.png', { base64: contents.buffer.toString('base64') }); + file.save({ + success: function() { + request.object.set('file', file); + response.success(); + }, + error: response.error + }); + }, + error: response.error + }); +}); +``` + +As we can see the current way, with backbone style callbacks is quite tough to read and maintain. +It's also not really trivial to handle errors, as you need to pass the response.error to each error handlers. + +Now it can be modernized with promises: + +```js +// after (with promises) +Parse.Cloud.beforeSave('MyClassName', (request) => { + return Parse.Cloud.httpRequest({ + url: request.object.get('fileURL') + }).then((contents) => { + const file = new Parse.File('image.png', { base64: contents.buffer.toString('base64') }); + request.object.set('file', file); + return file.save(); + }); +}); +``` + +And you can even make it better with async/await. + +```js +// after with async/await +Parse.Cloud.beforeSave('MyClassName', async (request) => { + const contents = await Parse.Cloud.httpRequest({ + url: request.object.get('fileURL') + }); + + const file = new Parse.File('image.png', { base64: contents.buffer.toString('base64') }); + request.object.set('file', file); + await file.save(); +}); +``` + +## Aborting hooks and functions + +In order to abort a `beforeSave` or any hook, you now need to throw an error: + +```js +// after with async/await +Parse.Cloud.beforeSave('MyClassName', async (request) => { + // valid, will result in a Parse.Error(SCRIPT_FAILED, 'Something went wrong') + throw 'Something went wrong' + // also valid, will fail with Parse.Error(SCRIPT_FAILED, 'Something else went wrong') + throw new Error('Something else went wrong') + // using a Parse.Error is also valid + throw new Parse.Error(1001, 'My error') +}); +``` + +## Migrating Functions + +Cloud Functions can be migrated the same way as cloud code hooks. +In functions, the response object is not passed anymore, as it is in cloud code hooks + +Continuing with the image downloader example: + +```js +// before +Parse.Cloud.define('downloadImage', function(request, response) { + Parse.Cloud.httpRequest({ + url: request.params.url, + success: function(contents) { + const file = new Parse.File(request.params.name, { base64: contents.buffer.toString('base64') }); + file.save({ + success: function() { + response.success(file); + }, + error: response.error + }); + }, + error: response.error + }); +}); +``` + +You would call this method with: + +```js +Parse.Cloud.run({ + url: '....', + name: 'my-file' +}); +``` + +To migrate this function you would follow the same practices as the ones before, and we'll jump right away to the async/await implementation: + +```js +// after with async/await +Parse.Cloud.beforeSave('MyClassName', async (request) => { + const { + url, name + } = request.params; + const response = await Parse.Cloud.httpRequest({ url }); + + const file = new Parse.File(name, { base64: response.buffer.toString('base64') }); + await file.save(); + return file; +}); +``` + +## Migrating jobs + +As with hooks and functions, jobs don't have a status object anymore. +The message method has been moved to the request object. + +```js +// before +Parse.Cloud.job('downloadImageJob', function(request, status) { + var query = new Parse.Query('ImagesToDownload'); + query.find({ + success: function(images) { + var done = 0; + for (var i = 0; i { + request.message('Doing ' + image.get('url')); + const contents = await Parse.Cloud.httpRequest({ + url: image.get('url') + }); + request.message('Got ' + image.get('url')); + const file = new Parse.File(image.get('name'), { base64: contents.buffer.toString('base64') }); + await file.save(); + request.message('Saved ' + image.get('url')); + }); + await Promise.all(promises); +}); + +``` + +As you can see the new implementation is more concise, easier to read and maintain. + +If you encounter a problem or discover an issue with this guide, or with any Parse Community project, feel free to [reach out on github](https://github.com/parse-community/parse-server/issues/new/choose) + diff --git a/README.md b/README.md index 3c5a81be..9b2d69e2 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ Parse Server works with the Express web application framework. It can be added t - [Ride the Bleeding Edge](#want-to-ride-the-bleeding-edge) - [Contributing](#contributing) - [Backers](#backers) +- [Upgrading to 3.0.0](#upgrading-to-3.0.0) - [Sponsors](#sponsors) # Getting Started @@ -380,6 +381,14 @@ Parse Server allows developers to choose from several options when hosting files `GridStoreAdapter` is used by default and requires no setup, but if you're interested in using S3 or Google Cloud Storage, additional configuration information is available in the [Parse Server guide](http://docs.parseplatform.org/parse-server/guide/#configuring-file-adapters). +# Upgrading to 3.0.0 + +Starting 3.0.0, parse-server uses the JS SDK version 2.0. +In short, parse SDK v2.0 removes the backbone style callbacks as well as the Parse.Promise object in favor of native promises. +All the Cloud Code interfaces also have been updated to reflect those changes, and all backbone style response objects are removed and replaced by Promise style resolution. + +We have written up a [migration guide](3.0.0.md), hoping this will help you transition to the next major release. + # Support For implementation related questions or technical support, please refer to the [Stack Overflow](http://stackoverflow.com/questions/tagged/parse.com) and [Server Fault](https://serverfault.com/tags/parse) communities. diff --git a/spec/CloudCode.spec.js b/spec/CloudCode.spec.js index ba8956a6..be8ec425 100644 --- a/spec/CloudCode.spec.js +++ b/spec/CloudCode.spec.js @@ -25,8 +25,8 @@ describe('Cloud Code', () => { }); it('can create functions', done => { - Parse.Cloud.define('hello', (req, res) => { - res.success('Hello world!'); + Parse.Cloud.define('hello', () => { + return 'Hello world!'; }); Parse.Cloud.run('hello', {}).then(result => { @@ -44,8 +44,8 @@ describe('Cloud Code', () => { }); it('basic beforeSave rejection', function(done) { - Parse.Cloud.beforeSave('BeforeSaveFail', function(req, res) { - res.error('You shall not pass!'); + Parse.Cloud.beforeSave('BeforeSaveFail', function() { + throw new Error('You shall not pass!'); }); const obj = new Parse.Object('BeforeSaveFail'); @@ -59,25 +59,25 @@ describe('Cloud Code', () => { }); it('returns an error', (done) => { - Parse.Cloud.define('cloudCodeWithError', (req, res) => { + Parse.Cloud.define('cloudCodeWithError', () => { /* eslint-disable no-undef */ foo.bar(); /* eslint-enable no-undef */ - res.success('I better throw an error.'); + return 'I better throw an error.'; }); Parse.Cloud.run('cloudCodeWithError') .then( () => done.fail('should not succeed'), e => { - expect(e).toEqual(new Parse.Error(1, undefined)); + expect(e).toEqual(new Parse.Error(141, 'foo is not defined')); done(); }); }); it('beforeSave rejection with custom error code', function(done) { - Parse.Cloud.beforeSave('BeforeSaveFailWithErrorCode', function (req, res) { - res.error(999, 'Nope'); + Parse.Cloud.beforeSave('BeforeSaveFailWithErrorCode', function () { + throw new Parse.Error(999, 'Nope'); }); const obj = new Parse.Object('BeforeSaveFailWithErrorCode'); @@ -93,12 +93,12 @@ describe('Cloud Code', () => { }); it('basic beforeSave rejection via promise', function(done) { - Parse.Cloud.beforeSave('BeforeSaveFailWithPromise', function (req, res) { + Parse.Cloud.beforeSave('BeforeSaveFailWithPromise', function () { const query = new Parse.Query('Yolo'); - query.find().then(() => { - res.error('Nope'); + return query.find().then(() => { + throw 'Nope'; }, () => { - res.success(); + return Promise.response(); }); }); @@ -115,9 +115,8 @@ describe('Cloud Code', () => { }); it('test beforeSave changed object success', function(done) { - Parse.Cloud.beforeSave('BeforeSaveChanged', function(req, res) { + Parse.Cloud.beforeSave('BeforeSaveChanged', function(req) { req.object.set('foo', 'baz'); - res.success(); }); const obj = new Parse.Object('BeforeSaveChanged'); @@ -138,9 +137,8 @@ describe('Cloud Code', () => { }); it('test beforeSave returns value on create and update', (done) => { - Parse.Cloud.beforeSave('BeforeSaveChanged', function(req, res) { + Parse.Cloud.beforeSave('BeforeSaveChanged', function(req) { req.object.set('foo', 'baz'); - res.success(); }); const obj = new Parse.Object('BeforeSaveChanged'); @@ -327,9 +325,8 @@ describe('Cloud Code', () => { }); it('test beforeSave happens on update', function(done) { - Parse.Cloud.beforeSave('BeforeSaveChanged', function(req, res) { + Parse.Cloud.beforeSave('BeforeSaveChanged', function(req) { req.object.set('foo', 'baz'); - res.success(); }); const obj = new Parse.Object('BeforeSaveChanged'); @@ -350,8 +347,8 @@ describe('Cloud Code', () => { }); it('test beforeDelete failure', function(done) { - Parse.Cloud.beforeDelete('BeforeDeleteFail', function(req, res) { - res.error('Nope'); + Parse.Cloud.beforeDelete('BeforeDeleteFail', function() { + throw 'Nope'; }); const obj = new Parse.Object('BeforeDeleteFail'); @@ -383,12 +380,10 @@ describe('Cloud Code', () => { }); it('basic beforeDelete rejection via promise', function(done) { - Parse.Cloud.beforeSave('BeforeDeleteFailWithPromise', function (req, res) { + Parse.Cloud.beforeSave('BeforeDeleteFailWithPromise', function () { const query = new Parse.Query('Yolo'); - query.find().then(() => { - res.error('Nope'); - }, () => { - res.success(); + return query.find().then(() => { + throw 'Nope'; }); }); @@ -431,8 +426,8 @@ describe('Cloud Code', () => { }); it('test cloud function return types', function(done) { - Parse.Cloud.define('foo', function(req, res) { - res.success({ + Parse.Cloud.define('foo', function() { + return { object: { __type: 'Object', className: 'Foo', @@ -452,7 +447,7 @@ describe('Cloud Code', () => { x: 2 }], a: 2 - }); + }; }); Parse.Cloud.run('foo').then((result) => { @@ -476,7 +471,7 @@ describe('Cloud Code', () => { }); it('test cloud function request params types', function(done) { - Parse.Cloud.define('params', function(req, res) { + Parse.Cloud.define('params', function(req) { expect(req.params.date instanceof Date).toBe(true); expect(req.params.date.getTime()).toBe(1463907600000); expect(req.params.dateList[0] instanceof Date).toBe(true); @@ -497,7 +492,7 @@ describe('Cloud Code', () => { expect(Array.isArray(req.params.arrayOfArray)).toBe(true); expect(Array.isArray(req.params.arrayOfArray[0])).toBe(true); expect(Array.isArray(req.params.arrayOfArray[1])).toBe(true); - return res.success({}); + return {}; }); const params = { @@ -550,12 +545,12 @@ describe('Cloud Code', () => { }); it('test cloud function should echo keys', function(done) { - Parse.Cloud.define('echoKeys', function(req, res){ - return res.success({ + Parse.Cloud.define('echoKeys', function() { + return { applicationId: Parse.applicationId, masterKey: Parse.masterKey, javascriptKey: Parse.javascriptKey - }) + }; }); Parse.Cloud.run('echoKeys').then((result) => { @@ -567,15 +562,14 @@ describe('Cloud Code', () => { }); it('should properly create an object in before save', done => { - Parse.Cloud.beforeSave('BeforeSaveChanged', function(req, res) { + Parse.Cloud.beforeSave('BeforeSaveChanged', function(req) { req.object.set('foo', 'baz'); - res.success(); }); - Parse.Cloud.define('createBeforeSaveChangedObject', function(req, res){ + Parse.Cloud.define('createBeforeSaveChangedObject', function() { const obj = new Parse.Object('BeforeSaveChanged'); - obj.save().then(() => { - res.success(obj); + return obj.save().then(() => { + return obj; }) }) @@ -588,7 +582,7 @@ describe('Cloud Code', () => { it('dirtyKeys are set on update', done => { let triggerTime = 0; // Register a mock beforeSave hook - Parse.Cloud.beforeSave('GameScore', (req, res) => { + Parse.Cloud.beforeSave('GameScore', (req) => { const object = req.object; expect(object instanceof Parse.Object).toBeTruthy(); expect(object.get('fooAgain')).toEqual('barAgain'); @@ -601,10 +595,9 @@ describe('Cloud Code', () => { expect(object.dirty('foo')).toBeTruthy(); expect(object.get('foo')).toEqual('baz'); } else { - res.error(); + throw new Error(); } triggerTime++; - res.success(); }); const obj = new Parse.Object('GameScore'); @@ -625,8 +618,8 @@ describe('Cloud Code', () => { }); it('test beforeSave unchanged success', function(done) { - Parse.Cloud.beforeSave('BeforeSaveUnchanged', function(req, res) { - res.success(); + Parse.Cloud.beforeSave('BeforeSaveUnchanged', function() { + return; }); const obj = new Parse.Object('BeforeSaveUnchanged'); @@ -640,8 +633,8 @@ describe('Cloud Code', () => { }); it('test beforeDelete success', function(done) { - Parse.Cloud.beforeDelete('BeforeDeleteTest', function(req, res) { - res.success(); + Parse.Cloud.beforeDelete('BeforeDeleteTest', function() { + return; }); const obj = new Parse.Object('BeforeDeleteTest'); @@ -658,11 +651,11 @@ describe('Cloud Code', () => { }); it('test save triggers get user', async (done) => { - Parse.Cloud.beforeSave('SaveTriggerUser', function(req, res) { + Parse.Cloud.beforeSave('SaveTriggerUser', function(req) { if (req.user && req.user.id) { - res.success(); + return; } else { - res.error('No user present on request object for beforeSave.'); + throw new Error('No user present on request object for beforeSave.'); } }); @@ -687,9 +680,8 @@ describe('Cloud Code', () => { }); it('beforeSave change propagates through the save response', (done) => { - Parse.Cloud.beforeSave('ChangingObject', function(request, response) { + Parse.Cloud.beforeSave('ChangingObject', function(request) { request.object.set('foo', 'baz'); - response.success(); }); const obj = new Parse.Object('ChangingObject'); obj.save({ foo: 'bar' }).then((objAgain) => { @@ -702,10 +694,9 @@ describe('Cloud Code', () => { }); it('beforeSave change propagates through the afterSave #1931', (done) => { - Parse.Cloud.beforeSave('ChangingObject', function(request, response) { + Parse.Cloud.beforeSave('ChangingObject', function(request) { request.object.unset('file'); request.object.unset('date'); - response.success(); }); Parse.Cloud.afterSave('ChangingObject', function(request) { @@ -728,8 +719,8 @@ describe('Cloud Code', () => { it('test cloud function parameter validation success', (done) => { // Register a function with validation - Parse.Cloud.define('functionWithParameterValidation', (req, res) => { - res.success('works'); + Parse.Cloud.define('functionWithParameterValidation', () => { + return 'works'; }, (request) => { return request.params.success === 100; }); @@ -743,8 +734,8 @@ describe('Cloud Code', () => { }); it('doesnt receive stale user in cloud code functions after user has been updated with master key (regression test for #1836)', done => { - Parse.Cloud.define('testQuery', function(request, response) { - response.success(request.user.get('data')); + Parse.Cloud.define('testQuery', function(request) { + return request.user.get('data'); }); Parse.User.signUp('user', 'pass') @@ -772,8 +763,8 @@ describe('Cloud Code', () => { const cacheAdapter = new InMemoryCacheAdapter({ ttl: 100000000 }); reconfigureServer({ cacheAdapter }) .then(() => { - Parse.Cloud.define('checkStaleUser', (request, response) => { - response.success(request.user.get('data')); + Parse.Cloud.define('checkStaleUser', (request) => { + return request.user.get('data'); }); user = new Parse.User(); @@ -836,9 +827,7 @@ describe('Cloud Code', () => { }); it('trivial beforeSave should not affect fetched pointers (regression test for #1238)', done => { - Parse.Cloud.beforeSave('BeforeSaveUnchanged', (req, res) => { - res.success(); - }); + Parse.Cloud.beforeSave('BeforeSaveUnchanged', () => {}); const TestObject = Parse.Object.extend("TestObject"); const NoBeforeSaveObject = Parse.Object.extend("NoBeforeSave"); @@ -868,13 +857,10 @@ describe('Cloud Code', () => { }); it('beforeSave should not affect fetched pointers', done => { - Parse.Cloud.beforeSave('BeforeSaveUnchanged', (req, res) => { - res.success(); - }); + Parse.Cloud.beforeSave('BeforeSaveUnchanged',() => {}); - Parse.Cloud.beforeSave('BeforeSaveChanged', function(req, res) { + Parse.Cloud.beforeSave('BeforeSaveChanged', function(req) { req.object.set('foo', 'baz'); - res.success(); }); const TestObject = Parse.Object.extend("TestObject"); @@ -910,15 +896,14 @@ describe('Cloud Code', () => { const NoBeforeSaveObject = Parse.Object.extend('NoBeforeSave'); const BeforeSaveObject = Parse.Object.extend('BeforeSaveChanged'); - Parse.Cloud.beforeSave('BeforeSaveChanged', (req, res) => { + Parse.Cloud.beforeSave('BeforeSaveChanged', (req) => { const object = req.object; object.set('before', 'save'); - res.success(); }); Parse.Cloud.define('removeme', (req, res) => { const testObject = new TestObject(); - testObject.save() + return testObject.save() .then(testObject => { const object = new NoBeforeSaveObject({remove: testObject}); return object.save(); @@ -927,14 +912,12 @@ describe('Cloud Code', () => { object.unset('remove'); return object.save(); }) - .then(object => { - res.success(object); - }).catch(res.error); + .catch(res.error); }); Parse.Cloud.define('removeme2', (req, res) => { const testObject = new TestObject(); - testObject.save() + return testObject.save() .then(testObject => { const object = new BeforeSaveObject({remove: testObject}); return object.save(); @@ -943,9 +926,7 @@ describe('Cloud Code', () => { object.unset('remove'); return object.save(); }) - .then(object => { - res.success(object); - }).catch(res.error); + .catch(res.error); }); Parse.Cloud.run('removeme') @@ -972,11 +953,10 @@ describe('Cloud Code', () => { const TestObject = Parse.Object.extend('TestObject'); const BeforeSaveObject = Parse.Object.extend('BeforeSaveChanged'); - Parse.Cloud.beforeSave('BeforeSaveChanged', (req, res) => { + Parse.Cloud.beforeSave('BeforeSaveChanged', (req) => { const object = req.object; object.set('before', 'save'); object.unset('remove'); - res.success(); }); let object; @@ -1001,14 +981,13 @@ describe('Cloud Code', () => { const TestObject = Parse.Object.extend('TestObject'); const BeforeSaveObject = Parse.Object.extend('BeforeSaveChanged'); let testObj; - Parse.Cloud.beforeSave('BeforeSaveChanged', (req, res) => { + Parse.Cloud.beforeSave('BeforeSaveChanged', (req) => { const object = req.object; object.set('before', 'save'); testObj = new TestObject(); - testObj.save().then(() => { + return testObj.save().then(() => { object.relation('testsRelation').add(testObj); - res.success(); - }, res.error); + }); }); const object = new BeforeSaveObject(); @@ -1027,23 +1006,19 @@ describe('Cloud Code', () => { * does not result in that key being omitted from the response. */ it('before save increment does not return undefined', (done) => { - Parse.Cloud.define("cloudIncrementClassFunction", function (req, res) { + Parse.Cloud.define("cloudIncrementClassFunction", function (req) { const CloudIncrementClass = Parse.Object.extend("CloudIncrementClass"); const obj = new CloudIncrementClass(); obj.id = req.params.objectId; - obj.save().then( - function (savedObj) { - res.success(savedObj); - }); + return obj.save(); }); - Parse.Cloud.beforeSave("CloudIncrementClass", function (req, res) { + Parse.Cloud.beforeSave("CloudIncrementClass", function (req) { const obj = req.object; if(!req.master) { obj.increment('points', -10); obj.increment('num', -9); } - res.success(); }); const CloudIncrementClass = Parse.Object.extend('CloudIncrementClass'); @@ -1077,9 +1052,7 @@ describe('Cloud Code', () => { describe('cloud jobs', () => { it('should define a job', (done) => { expect(() => { - Parse.Cloud.job('myJob', (req, res) => { - res.success(); - }); + Parse.Cloud.job('myJob', () => {}); }).not.toThrow(); rp.post({ @@ -1098,9 +1071,7 @@ describe('Cloud Code', () => { it('should not run without master key', (done) => { expect(() => { - Parse.Cloud.job('myJob', (req, res) => { - res.success(); - }); + Parse.Cloud.job('myJob', () => {}); }).not.toThrow(); rp.post({ @@ -1124,10 +1095,8 @@ describe('Cloud Code', () => { expect(req.functionName).toBeUndefined(); expect(req.jobName).toBe('myJob'); expect(typeof req.jobId).toBe('string'); - expect(typeof res.success).toBe('function'); - expect(typeof res.error).toBe('function'); - expect(typeof res.message).toBe('function'); - res.success(); + expect(typeof req.message).toBe('function'); + expect(typeof res).toBe('undefined'); done(); }); }).not.toThrow(); @@ -1151,10 +1120,8 @@ describe('Cloud Code', () => { expect(req.functionName).toBeUndefined(); expect(req.jobName).toBe('myJob'); expect(typeof req.jobId).toBe('string'); - expect(typeof res.success).toBe('function'); - expect(typeof res.error).toBe('function'); - expect(typeof res.message).toBe('function'); - res.success(); + expect(typeof req.message).toBe('function'); + expect(typeof res).toBe('undefined'); done(); }); }).not.toThrow(); @@ -1169,16 +1136,16 @@ describe('Cloud Code', () => { }); it('should set the message / success on the job', (done) => { - Parse.Cloud.job('myJob', (req, res) => { - res.message('hello'); - res.message().then(() => { + Parse.Cloud.job('myJob', (req) => { + req.message('hello'); + const promise = req.message().then(() => { return getJobStatus(req.jobId); }).then((jobStatus) => { expect(jobStatus.get('message')).toEqual('hello'); expect(jobStatus.get('status')).toEqual('running'); - return res.success().then(() => { - return getJobStatus(req.jobId); - }); + }); + promise.then(() => { + return getJobStatus(req.jobId); }).then((jobStatus) => { expect(jobStatus.get('message')).toEqual('hello'); expect(jobStatus.get('status')).toEqual('succeeded'); @@ -1188,6 +1155,7 @@ describe('Cloud Code', () => { jfail(err); done(); }); + return promise; }); rp.post({ @@ -1204,8 +1172,9 @@ describe('Cloud Code', () => { }); it('should set the failure on the job', (done) => { - Parse.Cloud.job('myJob', (req, res) => { - res.error('Something went wrong').then(() => { + Parse.Cloud.job('myJob', (req) => { + const promise = Promise.reject('Something went wrong'); + new Promise((resolve) => setTimeout(resolve, 200)).then(() => { return getJobStatus(req.jobId); }).then((jobStatus) => { expect(jobStatus.get('message')).toEqual('Something went wrong'); @@ -1215,6 +1184,7 @@ describe('Cloud Code', () => { jfail(err); done(); }); + return promise; }); rp.post({ @@ -1239,9 +1209,9 @@ describe('Cloud Code', () => { describe('cloud functions', () => { it('Should have request ip', (done) => { - Parse.Cloud.define('myFunction', (req, res) => { + Parse.Cloud.define('myFunction', (req) => { expect(req.ip).toBeDefined(); - res.success("success"); + return "success"; }); Parse.Cloud.run('myFunction', {}).then(() => done()); @@ -1250,9 +1220,8 @@ describe('cloud functions', () => { describe('beforeSave hooks', () => { it('should have request headers', (done) => { - Parse.Cloud.beforeSave('MyObject', (req, res) => { + Parse.Cloud.beforeSave('MyObject', (req) => { expect(req.headers).toBeDefined(); - res.success(); }); const MyObject = Parse.Object.extend('MyObject'); @@ -1261,9 +1230,8 @@ describe('beforeSave hooks', () => { }); it('should have request ip', (done) => { - Parse.Cloud.beforeSave('MyObject', (req, res) => { + Parse.Cloud.beforeSave('MyObject', (req) => { expect(req.ip).toBeDefined(); - res.success(); }); const MyObject = Parse.Object.extend('MyObject'); @@ -1285,9 +1253,8 @@ describe('afterSave hooks', () => { }); it('should have request ip', (done) => { - Parse.Cloud.afterSave('MyObject', (req, res) => { + Parse.Cloud.afterSave('MyObject', (req) => { expect(req.ip).toBeDefined(); - res.success(); }); const MyObject = Parse.Object.extend('MyObject'); @@ -1298,9 +1265,8 @@ describe('afterSave hooks', () => { describe('beforeDelete hooks', () => { it('should have request headers', (done) => { - Parse.Cloud.beforeDelete('MyObject', (req, res) => { + Parse.Cloud.beforeDelete('MyObject', (req) => { expect(req.headers).toBeDefined(); - res.success(); }); const MyObject = Parse.Object.extend('MyObject'); @@ -1311,9 +1277,8 @@ describe('beforeDelete hooks', () => { }); it('should have request ip', (done) => { - Parse.Cloud.beforeDelete('MyObject', (req, res) => { + Parse.Cloud.beforeDelete('MyObject', (req) => { expect(req.ip).toBeDefined(); - res.success(); }); const MyObject = Parse.Object.extend('MyObject'); @@ -1554,11 +1519,11 @@ describe('beforeFind hooks', () => { describe('afterFind hooks', () => { it('should add afterFind trigger using get',(done) => { - Parse.Cloud.afterFind('MyObject', (req, res) => { + Parse.Cloud.afterFind('MyObject', (req) => { for(let i = 0 ; i < req.objects.length ; i++){ req.objects[i].set("secretField","###"); } - res.success(req.objects); + return req.objects; }); const obj = new Parse.Object('MyObject'); obj.set('secretField', 'SSID'); @@ -1578,11 +1543,11 @@ describe('afterFind hooks', () => { }); it('should add afterFind trigger using find',(done) => { - Parse.Cloud.afterFind('MyObject', (req, res) => { + Parse.Cloud.afterFind('MyObject', (req) => { for(let i = 0 ; i < req.objects.length ; i++){ req.objects[i].set("secretField","###"); } - res.success(req.objects); + return req.objects; }); const obj = new Parse.Object('MyObject'); obj.set('secretField', 'SSID'); @@ -1603,14 +1568,14 @@ describe('afterFind hooks', () => { }); it('should filter out results',(done) => { - Parse.Cloud.afterFind('MyObject', (req, res) => { + Parse.Cloud.afterFind('MyObject', (req) => { const filteredResults = []; for(let i = 0 ; i < req.objects.length ; i++){ if(req.objects[i].get("secretField") === "SSID1") { filteredResults.push(req.objects[i]); } } - res.success(filteredResults); + return filteredResults; }); const obj0 = new Parse.Object('MyObject'); obj0.set('secretField', 'SSID1'); @@ -1633,8 +1598,8 @@ describe('afterFind hooks', () => { }); it('should handle failures',(done) => { - Parse.Cloud.afterFind('MyObject', (req, res) => { - res.error(Parse.Error.SCRIPT_FAILED, "It should fail"); + Parse.Cloud.afterFind('MyObject', () => { + throw new Parse.Error(Parse.Error.SCRIPT_FAILED, "It should fail"); }); const obj = new Parse.Object('MyObject'); obj.set('secretField', 'SSID'); @@ -1749,9 +1714,8 @@ describe('afterFind hooks', () => { }); it('should have request headers', (done) => { - Parse.Cloud.afterFind('MyObject', (req, res) => { + Parse.Cloud.afterFind('MyObject', (req) => { expect(req.headers).toBeDefined(); - res.success(); }); const MyObject = Parse.Object.extend('MyObject'); @@ -1770,9 +1734,8 @@ describe('afterFind hooks', () => { }); it('should have request ip', (done) => { - Parse.Cloud.afterFind('MyObject', (req, res) => { + Parse.Cloud.afterFind('MyObject', (req) => { expect(req.ip).toBeDefined(); - res.success(); }); const MyObject = Parse.Object.extend('MyObject'); @@ -1787,7 +1750,7 @@ describe('afterFind hooks', () => { query.find(), ]); }) - .then(() => done()); + .then(() => done()).catch(done.fail); }); it('should validate triggers correctly', () => { diff --git a/spec/CloudCodeLogger.spec.js b/spec/CloudCodeLogger.spec.js index 4471b9b2..1d1e7824 100644 --- a/spec/CloudCodeLogger.spec.js +++ b/spec/CloudCodeLogger.spec.js @@ -28,10 +28,10 @@ describe("Cloud Code Logger", () => { const config = Config.get('test'); const spy = spyOn(config.loggerController, 'log').and.callThrough(); - Parse.Cloud.define("loggerTest", (req, res) => { + Parse.Cloud.define("loggerTest", (req) => { req.log.info('logTest', 'info log', { info: 'some log' }); req.log.error('logTest', 'error log', { error: 'there was an error' }); - res.success({}); + return {}; }); return Parse.Cloud.run('loggerTest').then(() => { @@ -57,8 +57,8 @@ describe("Cloud Code Logger", () => { it('trigger should obfuscate password', done => { const logController = new LoggerController(new WinstonLoggerAdapter()); - Parse.Cloud.beforeSave(Parse.User, (req, res) => { - res.success(req.object); + Parse.Cloud.beforeSave(Parse.User, (req) => { + return req.object; }); Parse.User.signUp('tester123', 'abc') @@ -75,10 +75,10 @@ describe("Cloud Code Logger", () => { it("should expose log to trigger", (done) => { const logController = new LoggerController(new WinstonLoggerAdapter()); - Parse.Cloud.beforeSave("MyObject", (req, res) => { + Parse.Cloud.beforeSave("MyObject", (req) => { req.log.info('beforeSave MyObject', 'info log', { info: 'some log' }); req.log.error('beforeSave MyObject', 'error log', { error: 'there was an error' }); - res.success({}); + return {}; }); const obj = new Parse.Object('MyObject'); @@ -114,8 +114,8 @@ describe("Cloud Code Logger", () => { it('should truncate input and result of long lines', done => { const logController = new LoggerController(new WinstonLoggerAdapter()); const longString = fs.readFileSync(loremFile, 'utf8'); - Parse.Cloud.define('aFunction', (req, res) => { - res.success(req.params); + Parse.Cloud.define('aFunction', (req) => { + return req.params; }); Parse.Cloud.run('aFunction', { longString }) @@ -147,8 +147,8 @@ describe("Cloud Code Logger", () => { it('should log a denied beforeSave', done => { const logController = new LoggerController(new WinstonLoggerAdapter()); - Parse.Cloud.beforeSave("MyObject", (req, res) => { - res.error('uh oh!'); + Parse.Cloud.beforeSave("MyObject", () => { + throw 'uh oh!'; }); new Parse.Object('MyObject') @@ -169,8 +169,8 @@ describe("Cloud Code Logger", () => { it('should log cloud function success', done => { const logController = new LoggerController(new WinstonLoggerAdapter()); - Parse.Cloud.define('aFunction', (req, res) => { - res.success('it worked!'); + Parse.Cloud.define('aFunction', () => { + return 'it worked!'; }); Parse.Cloud.run('aFunction', { foo: 'bar' }) @@ -187,8 +187,8 @@ describe("Cloud Code Logger", () => { it('should log cloud function failure', done => { const logController = new LoggerController(new WinstonLoggerAdapter()); - Parse.Cloud.define('aFunction', (req, res) => { - res.error('it failed!'); + Parse.Cloud.define('aFunction', () => { + throw 'it failed!'; }); Parse.Cloud.run('aFunction', { foo: 'bar' }) @@ -206,10 +206,10 @@ describe("Cloud Code Logger", () => { xit('should log a changed beforeSave indicating a change', done => { const logController = new LoggerController(new WinstonLoggerAdapter()); - Parse.Cloud.beforeSave("MyObject", (req, res) => { + Parse.Cloud.beforeSave("MyObject", (req) => { const myObj = req.object; myObj.set('aChange', true); - res.success(myObj); + return myObj; }); new Parse.Object('MyObject') @@ -231,8 +231,8 @@ describe("Cloud Code Logger", () => { it('cloud function should obfuscate password', done => { const logController = new LoggerController(new WinstonLoggerAdapter()); - Parse.Cloud.define('testFunction', (req, res) => { - res.success(1002,'verify code success'); + Parse.Cloud.define('testFunction', () => { + return 'verify code success'; }); Parse.Cloud.run('testFunction', {username:'hawk',password:'123456'}) diff --git a/spec/ParseAPI.spec.js b/spec/ParseAPI.spec.js index 94be6a4d..064a43f7 100644 --- a/spec/ParseAPI.spec.js +++ b/spec/ParseAPI.spec.js @@ -343,9 +343,8 @@ describe('miscellaneous', function() { const acl = new Parse.ACL({ '*': { read: true, write: false } }); - Parse.Cloud.beforeSave('BeforeSaveAddACL', function(req, res) { + Parse.Cloud.beforeSave('BeforeSaveAddACL', function(req) { req.object.setACL(acl); - res.success(); }); const obj = new Parse.Object('BeforeSaveAddACL'); @@ -369,7 +368,7 @@ describe('miscellaneous', function() { it('object is set on create and update', done => { let triggerTime = 0; // Register a mock beforeSave hook - Parse.Cloud.beforeSave('GameScore', (req, res) => { + Parse.Cloud.beforeSave('GameScore', (req) => { const object = req.object; expect(object instanceof Parse.Object).toBeTruthy(); expect(object.get('fooAgain')).toEqual('barAgain'); @@ -387,10 +386,9 @@ describe('miscellaneous', function() { expect(object.createdAt).not.toBeUndefined(); expect(object.updatedAt).not.toBeUndefined(); } else { - res.error(); + throw new Error(); } triggerTime++; - res.success(); }); const obj = new Parse.Object('GameScore'); @@ -412,11 +410,11 @@ describe('miscellaneous', function() { it('works when object is passed to success', done => { let triggerTime = 0; // Register a mock beforeSave hook - Parse.Cloud.beforeSave('GameScore', (req, res) => { + Parse.Cloud.beforeSave('GameScore', (req) => { const object = req.object; object.set('foo', 'bar'); triggerTime++; - res.success(object); + return object; }); const obj = new Parse.Object('GameScore'); @@ -434,7 +432,7 @@ describe('miscellaneous', function() { it('original object is set on update', done => { let triggerTime = 0; // Register a mock beforeSave hook - Parse.Cloud.beforeSave('GameScore', (req, res) => { + Parse.Cloud.beforeSave('GameScore', (req) => { const object = req.object; expect(object instanceof Parse.Object).toBeTruthy(); expect(object.get('fooAgain')).toEqual('barAgain'); @@ -462,10 +460,9 @@ describe('miscellaneous', function() { expect(originalObject.updatedAt).not.toBeUndefined(); expect(originalObject.get('foo')).toEqual('bar'); } else { - res.error(); + throw new Error(); } triggerTime++; - res.success(); }); const obj = new Parse.Object('GameScore'); @@ -488,16 +485,14 @@ describe('miscellaneous', function() { it('pointer mutation properly saves object', done => { const className = 'GameScore'; - Parse.Cloud.beforeSave(className, (req, res) => { + Parse.Cloud.beforeSave(className, (req) => { const object = req.object; expect(object instanceof Parse.Object).toBeTruthy(); const child = object.get('child'); expect(child instanceof Parse.Object).toBeTruthy(); child.set('a', 'b'); - child.save().then(() => { - res.success(); - }); + return child.save(); }); const obj = new Parse.Object(className); @@ -528,18 +523,17 @@ describe('miscellaneous', function() { }); it('pointer reassign is working properly (#1288)', (done) => { - Parse.Cloud.beforeSave('GameScore', (req, res) => { + Parse.Cloud.beforeSave('GameScore', (req) => { const obj = req.object; if (obj.get('point')) { - return res.success(); + return; } const TestObject1 = Parse.Object.extend('TestObject1'); const newObj = new TestObject1({'key1': 1}); return newObj.save().then((newObj) => { obj.set('point' , newObj); - res.success(); }); }); let pointId; @@ -560,7 +554,7 @@ describe('miscellaneous', function() { it('test afterSave get full object on create and update', function(done) { let triggerTime = 0; // Register a mock beforeSave hook - Parse.Cloud.afterSave('GameScore', function(req, res) { + Parse.Cloud.afterSave('GameScore', function(req) { const object = req.object; expect(object instanceof Parse.Object).toBeTruthy(); expect(object.id).not.toBeUndefined(); @@ -574,10 +568,9 @@ describe('miscellaneous', function() { // Update expect(object.get('foo')).toEqual('baz'); } else { - res.error(); + throw new Error(); } triggerTime++; - res.success(); }); const obj = new Parse.Object('GameScore'); @@ -601,7 +594,7 @@ describe('miscellaneous', function() { let triggerTime = 0; // Register a mock beforeSave hook - Parse.Cloud.afterSave('GameScore', function(req, res) { + Parse.Cloud.afterSave('GameScore', function(req) { const object = req.object; expect(object instanceof Parse.Object).toBeTruthy(); expect(object.get('fooAgain')).toEqual('barAgain'); @@ -625,10 +618,9 @@ describe('miscellaneous', function() { expect(originalObject.updatedAt).not.toBeUndefined(); expect(originalObject.get('foo')).toEqual('bar'); } else { - res.error(); + throw new Error(); } triggerTime++; - res.success(); }); const obj = new Parse.Object('GameScore'); @@ -651,7 +643,7 @@ describe('miscellaneous', function() { it('test afterSave get full original object even req auth can not query it', (done) => { let triggerTime = 0; // Register a mock beforeSave hook - Parse.Cloud.afterSave('GameScore', function(req, res) { + Parse.Cloud.afterSave('GameScore', function(req) { const object = req.object; const originalObject = req.original; if (triggerTime == 0) { @@ -667,10 +659,9 @@ describe('miscellaneous', function() { expect(originalObject.updatedAt).not.toBeUndefined(); expect(originalObject.get('foo')).toEqual('bar'); } else { - res.error(); + throw new Error(); } triggerTime++; - res.success(); }); const obj = new Parse.Object('GameScore'); @@ -698,7 +689,7 @@ describe('miscellaneous', function() { it('afterSave flattens custom operations', done => { let triggerTime = 0; // Register a mock beforeSave hook - Parse.Cloud.afterSave('GameScore', function(req, res) { + Parse.Cloud.afterSave('GameScore', function(req) { const object = req.object; expect(object instanceof Parse.Object).toBeTruthy(); const originalObject = req.original; @@ -711,10 +702,9 @@ describe('miscellaneous', function() { // Check the originalObject expect(originalObject.get('yolo')).toEqual(1); } else { - res.error(); + throw new Error(); } triggerTime++; - res.success(); }); const obj = new Parse.Object('GameScore'); @@ -735,7 +725,7 @@ describe('miscellaneous', function() { it('beforeSave receives ACL', done => { let triggerTime = 0; // Register a mock beforeSave hook - Parse.Cloud.beforeSave('GameScore', function(req, res) { + Parse.Cloud.beforeSave('GameScore', function(req) { const object = req.object; if (triggerTime == 0) { const acl = object.getACL(); @@ -746,10 +736,9 @@ describe('miscellaneous', function() { expect(acl.getPublicReadAccess()).toBeFalsy(); expect(acl.getPublicWriteAccess()).toBeTruthy(); } else { - res.error(); + throw new Error(); } triggerTime++; - res.success(); }); const obj = new Parse.Object('GameScore'); @@ -774,7 +763,7 @@ describe('miscellaneous', function() { it('afterSave receives ACL', done => { let triggerTime = 0; // Register a mock beforeSave hook - Parse.Cloud.afterSave('GameScore', function(req, res) { + Parse.Cloud.afterSave('GameScore', function(req) { const object = req.object; if (triggerTime == 0) { const acl = object.getACL(); @@ -785,10 +774,9 @@ describe('miscellaneous', function() { expect(acl.getPublicReadAccess()).toBeFalsy(); expect(acl.getPublicWriteAccess()).toBeTruthy(); } else { - res.error(); + throw new Error(); } triggerTime++; - res.success(); }); const obj = new Parse.Object('GameScore'); @@ -860,8 +848,8 @@ describe('miscellaneous', function() { it('test cloud function error handling', (done) => { // Register a function which will fail - Parse.Cloud.define('willFail', (req, res) => { - res.error('noway'); + Parse.Cloud.define('willFail', () => { + throw new Error('noway'); }); Parse.Cloud.run('willFail').then(() => { fail('Should not have succeeded.'); @@ -875,8 +863,8 @@ describe('miscellaneous', function() { it('test cloud function error handling with custom error code', (done) => { // Register a function which will fail - Parse.Cloud.define('willFail', (req, res) => { - res.error(999, 'noway'); + Parse.Cloud.define('willFail', () => { + throw new Parse.Error(999, 'noway'); }); Parse.Cloud.run('willFail').then(() => { fail('Should not have succeeded.'); @@ -890,8 +878,8 @@ describe('miscellaneous', function() { it('test cloud function error handling with standard error code', (done) => { // Register a function which will fail - Parse.Cloud.define('willFail', (req, res) => { - res.error('noway'); + Parse.Cloud.define('willFail', () => { + throw new Error('noway'); }); Parse.Cloud.run('willFail').then(() => { fail('Should not have succeeded.'); @@ -905,11 +893,10 @@ describe('miscellaneous', function() { it('test beforeSave/afterSave get installationId', function(done) { let triggerTime = 0; - Parse.Cloud.beforeSave('GameScore', function(req, res) { + Parse.Cloud.beforeSave('GameScore', function(req) { triggerTime++; expect(triggerTime).toEqual(1); expect(req.installationId).toEqual('yolo'); - res.success(); }); Parse.Cloud.afterSave('GameScore', function(req) { triggerTime++; @@ -936,11 +923,10 @@ describe('miscellaneous', function() { it('test beforeDelete/afterDelete get installationId', function(done) { let triggerTime = 0; - Parse.Cloud.beforeDelete('GameScore', function(req, res) { + Parse.Cloud.beforeDelete('GameScore', function(req) { triggerTime++; expect(triggerTime).toEqual(1); expect(req.installationId).toEqual('yolo'); - res.success(); }); Parse.Cloud.afterDelete('GameScore', function(req) { triggerTime++; @@ -973,9 +959,8 @@ describe('miscellaneous', function() { it('test beforeDelete with locked down ACL', async () => { let called = false; - Parse.Cloud.beforeDelete('GameScore', (req, res) => { + Parse.Cloud.beforeDelete('GameScore', () => { called = true; - res.success(); }); const object = new Parse.Object('GameScore'); object.setACL(new Parse.ACL()); @@ -991,8 +976,8 @@ describe('miscellaneous', function() { }); it('test cloud function query parameters', (done) => { - Parse.Cloud.define('echoParams', (req, res) => { - res.success(req.params); + Parse.Cloud.define('echoParams', (req) => { + return req.params; }); const headers = { 'Content-Type': 'application/json', @@ -1020,8 +1005,8 @@ describe('miscellaneous', function() { it('test cloud function parameter validation', (done) => { // Register a function with validation - Parse.Cloud.define('functionWithParameterValidationFailure', (req, res) => { - res.success('noway'); + Parse.Cloud.define('functionWithParameterValidationFailure', () => { + return 'noway'; }, (request) => { return request.params.success === 100; }); @@ -1037,9 +1022,9 @@ describe('miscellaneous', function() { }); it('can handle null params in cloud functions (regression test for #1742)', done => { - Parse.Cloud.define('func', (request, response) => { + Parse.Cloud.define('func', (request) => { expect(request.params.nullParam).toEqual(null); - response.success('yay'); + return 'yay'; }); Parse.Cloud.run('func', {nullParam: null}) @@ -1053,10 +1038,10 @@ describe('miscellaneous', function() { it('can handle date params in cloud functions (#2214)', done => { const date = new Date(); - Parse.Cloud.define('dateFunc', (request, response) => { + Parse.Cloud.define('dateFunc', (request) => { expect(request.params.date.__type).toEqual('Date'); expect(request.params.date.iso).toEqual(date.toISOString()); - response.success('yay'); + return 'yay'; }); Parse.Cloud.run('dateFunc', {date: date}) @@ -1514,12 +1499,10 @@ describe('miscellaneous', function() { }); it('should not update schema beforeSave #2672', (done) => { - Parse.Cloud.beforeSave('MyObject', (request, response) => { + Parse.Cloud.beforeSave('MyObject', (request) => { if (request.object.get('secret')) { - response.error('cannot set secret here'); - return; + throw 'cannot set secret here'; } - response.success(); }); const object = new Parse.Object('MyObject'); diff --git a/spec/ParseRelation.spec.js b/spec/ParseRelation.spec.js index b388255b..4cd67160 100644 --- a/spec/ParseRelation.spec.js +++ b/spec/ParseRelation.spec.js @@ -664,10 +664,10 @@ describe('Parse.Relation testing', () => { }); it('can query roles in Cloud Code (regession test #1489)', done => { - Parse.Cloud.define('isAdmin', (request, response) => { + Parse.Cloud.define('isAdmin', (request) => { const query = new Parse.Query(Parse.Role); query.equalTo('name', 'admin'); - query.first({ useMasterKey: true }) + return query.first({ useMasterKey: true }) .then(role => { const relation = new Parse.Relation(role, 'users'); const admins = relation.query(); @@ -675,7 +675,6 @@ describe('Parse.Relation testing', () => { admins.first({ useMasterKey: true }) .then(user => { if (user) { - response.success(user); done(); } else { fail('Should have found admin user, found nothing instead'); diff --git a/spec/ParseUser.spec.js b/spec/ParseUser.spec.js index 3b2f985d..0ea3949e 100644 --- a/spec/ParseUser.spec.js +++ b/spec/ParseUser.spec.js @@ -1615,7 +1615,7 @@ describe('Parse.User testing', () => { it('should have authData in beforeSave and afterSave', async (done) => { - Parse.Cloud.beforeSave('_User', (request, response) => { + Parse.Cloud.beforeSave('_User', (request) => { const authData = request.object.get('authData'); expect(authData).not.toBeUndefined(); if (authData) { @@ -1624,10 +1624,9 @@ describe('Parse.User testing', () => { } else { fail('authData should be set'); } - response.success(); }); - Parse.Cloud.afterSave('_User', (request, response) => { + Parse.Cloud.afterSave('_User', (request) => { const authData = request.object.get('authData'); expect(authData).not.toBeUndefined(); if (authData) { @@ -1636,7 +1635,6 @@ describe('Parse.User testing', () => { } else { fail('authData should be set'); } - response.success(); }); const provider = getMockFacebookProvider(); @@ -2341,9 +2339,8 @@ describe('Parse.User testing', () => { }); it('should cleanup null authData keys ParseUser update (regression test for #1198, #2252)', (done) => { - Parse.Cloud.beforeSave('_User', (req, res) => { + Parse.Cloud.beforeSave('_User', (req) => { req.object.set('foo', 'bar'); - res.success(); }); let originalSessionToken; @@ -2540,9 +2537,9 @@ describe('Parse.User testing', () => { }); it('changes to a user should update the cache', (done) => { - Parse.Cloud.define('testUpdatedUser', (req, res) => { + Parse.Cloud.define('testUpdatedUser', (req) => { expect(req.user.get('han')).toEqual('solo'); - res.success({}); + return {}; }); const user = new Parse.User(); user.setUsername('harrison'); diff --git a/spec/PushController.spec.js b/spec/PushController.spec.js index 059f0d66..f512f962 100644 --- a/spec/PushController.spec.js +++ b/spec/PushController.spec.js @@ -558,7 +558,7 @@ describe('PushController', () => { expect(spy.calls.count()).toBe(4); const allCalls = spy.calls.all(); allCalls.forEach((call) => { - expect(call.args.length).toBe(2); + expect(call.args.length).toBe(1); const object = call.args[0].object; expect(object instanceof Parse.Object).toBe(true); }); diff --git a/spec/cloud/cloudCodeAbsoluteFile.js b/spec/cloud/cloudCodeAbsoluteFile.js index f5fcf2b8..a62b4fcc 100644 --- a/spec/cloud/cloudCodeAbsoluteFile.js +++ b/spec/cloud/cloudCodeAbsoluteFile.js @@ -1,3 +1,3 @@ -Parse.Cloud.define('cloudCodeInFile', (req, res) => { - res.success('It is possible to define cloud code in a file.'); +Parse.Cloud.define('cloudCodeInFile', () => { + return 'It is possible to define cloud code in a file.'; }); diff --git a/spec/cloud/cloudCodeRelativeFile.js b/spec/cloud/cloudCodeRelativeFile.js index f5fcf2b8..a62b4fcc 100644 --- a/spec/cloud/cloudCodeRelativeFile.js +++ b/spec/cloud/cloudCodeRelativeFile.js @@ -1,3 +1,3 @@ -Parse.Cloud.define('cloudCodeInFile', (req, res) => { - res.success('It is possible to define cloud code in a file.'); +Parse.Cloud.define('cloudCodeInFile', () => { + return 'It is possible to define cloud code in a file.'; }); diff --git a/spec/rest.spec.js b/spec/rest.spec.js index fae39b18..79a10944 100644 --- a/spec/rest.spec.js +++ b/spec/rest.spec.js @@ -101,9 +101,8 @@ describe('rest create', () => { it('handles object and subdocument', done => { const obj = { subdoc: {foo: 'bar', wu: 'tan'} }; - Parse.Cloud.beforeSave('MyClass', function(req, res) { + Parse.Cloud.beforeSave('MyClass', function() { // this beforeSave trigger should do nothing but can mess with the object - res.success(); }); rest.create(config, auth.nobody(config), 'MyClass', obj) diff --git a/spec/schemas.spec.js b/spec/schemas.spec.js index e8ec3de6..dacce31b 100644 --- a/spec/schemas.spec.js +++ b/spec/schemas.spec.js @@ -1656,11 +1656,10 @@ describe('schemas', () => { it('unset field in beforeSave should not stop object creation', (done) => { const hook = { - method: function(req, res) { + method: function(req) { if (req.object.get('undesiredField')) { req.object.unset('undesiredField'); } - return res.success(); } }; spyOn(hook, 'method').and.callThrough(); diff --git a/src/Controllers/HooksController.js b/src/Controllers/HooksController.js index db87c571..046cc1a2 100644 --- a/src/Controllers/HooksController.js +++ b/src/Controllers/HooksController.js @@ -166,7 +166,7 @@ export class HooksController { } function wrapToHTTPRequest(hook, key) { - return (req, res) => { + return (req) => { const jsonBody = {}; for (var i in req) { jsonBody[i] = req[i]; @@ -195,37 +195,38 @@ function wrapToHTTPRequest(hook, key) { logger.warn('Making outgoing webhook request without webhookKey being set!'); } - request.post(hook.url, jsonRequest, function (err, httpResponse, body) { - var result; - if (body) { - if (typeof body === "string") { - try { - body = JSON.parse(body); - } catch (e) { - err = { - error: "Malformed response", - code: -1, - partialResponse: body.substring(0, 100) - }; + return new Promise((resolve, reject) => { + request.post(hook.url, jsonRequest, function (err, httpResponse, body) { + var result; + if (body) { + if (typeof body === "string") { + try { + body = JSON.parse(body); + } catch (e) { + err = { + error: "Malformed response", + code: -1, + partialResponse: body.substring(0, 100) + }; + } + } + if (!err) { + result = body.success; + err = body.error; } } - if (!err) { - result = body.success; - err = body.error; + if (err) { + return reject(err); + } else if (hook.triggerName === 'beforeSave') { + if (typeof result === 'object') { + delete result.createdAt; + delete result.updatedAt; + } + return resolve({object: result}); + } else { + return resolve(result); } - } - - if (err) { - return res.error(err); - } else if (hook.triggerName === 'beforeSave') { - if (typeof result === 'object') { - delete result.createdAt; - delete result.updatedAt; - } - return res.success({object: result}); - } else { - return res.success(result); - } + }); }); } } diff --git a/src/Routers/FunctionsRouter.js b/src/Routers/FunctionsRouter.js index 1bb077ac..e4add79c 100644 --- a/src/Routers/FunctionsRouter.js +++ b/src/Routers/FunctionsRouter.js @@ -56,18 +56,21 @@ export class FunctionsRouter extends PromiseRouter { log: req.config.loggerController, headers: req.config.headers, ip: req.config.ip, - jobName - }; - const status = { - success: jobHandler.setSucceeded.bind(jobHandler), - error: jobHandler.setFailed.bind(jobHandler), + jobName, message: jobHandler.setMessage.bind(jobHandler) - } + }; + return jobHandler.setRunning(jobName, params).then((jobStatus) => { request.jobId = jobStatus.objectId // run the function async process.nextTick(() => { - jobFunction(request, status); + Promise.resolve().then(() => { + return jobFunction(request); + }).then((result) => { + jobHandler.setSucceeded(result); + }, (error) => { + jobHandler.setFailed(error); + }); }); return { headers: { @@ -87,13 +90,19 @@ export class FunctionsRouter extends PromiseRouter { } }); }, - error: function(code, message) { - if (!message) { - if (code instanceof Parse.Error) { - return reject(code) - } - message = code; - code = Parse.Error.SCRIPT_FAILED; + error: function(message) { + // parse error, process away + if (message instanceof Parse.Error) { + return reject(message); + } + + const code = Parse.Error.SCRIPT_FAILED; + // If it's an error, mark it as a script failed + if (typeof message === 'string') { + return reject(new Parse.Error(code, message)); + } + if (message instanceof Error) { + message = message.message; } reject(new Parse.Error(code, message)); }, @@ -106,69 +115,66 @@ export class FunctionsRouter extends PromiseRouter { const applicationId = req.config.applicationId; const theFunction = triggers.getFunction(functionName, applicationId); const theValidator = triggers.getValidator(req.params.functionName, applicationId); - if (theFunction) { - let params = Object.assign({}, req.body, req.query); - params = parseParams(params); - var request = { - params: params, - master: req.auth && req.auth.isMaster, - user: req.auth && req.auth.user, - installationId: req.info.installationId, - log: req.config.loggerController, - headers: req.config.headers, - ip: req.config.ip, - functionName - }; - - if (theValidator && typeof theValidator === "function") { - var result = theValidator(request); - if (!result) { - throw new Parse.Error(Parse.Error.VALIDATION_ERROR, 'Validation failed.'); - } - } - - return new Promise(function (resolve, reject) { - const userString = (req.auth && req.auth.user) ? req.auth.user.id : undefined; - const cleanInput = logger.truncateLogMessage(JSON.stringify(params)); - var response = FunctionsRouter.createResponseObject((result) => { - try { - const cleanResult = logger.truncateLogMessage(JSON.stringify(result.response.result)); - logger.info( - `Ran cloud function ${functionName} for user ${userString} with:\n Input: ${cleanInput }\n Result: ${cleanResult }`, - { - functionName, - params, - user: userString, - } - ); - resolve(result); - } catch (e) { - reject(e); - } - }, (error) => { - try { - logger.error( - `Failed running cloud function ${functionName} for user ${userString} with:\n Input: ${cleanInput}\n Error: ` + JSON.stringify(error), - { - functionName, - error, - params, - user: userString - } - ); - reject(error); - } catch (e) { - reject(e); - } - }); - // Force the keys before the function calls. - Parse.applicationId = req.config.applicationId; - Parse.javascriptKey = req.config.javascriptKey; - Parse.masterKey = req.config.masterKey; - theFunction(request, response); - }); - } else { + if (!theFunction) { throw new Parse.Error(Parse.Error.SCRIPT_FAILED, `Invalid function: "${functionName}"`); } + let params = Object.assign({}, req.body, req.query); + params = parseParams(params); + const request = { + params: params, + master: req.auth && req.auth.isMaster, + user: req.auth && req.auth.user, + installationId: req.info.installationId, + log: req.config.loggerController, + headers: req.config.headers, + ip: req.config.ip, + functionName + }; + + if (theValidator && typeof theValidator === "function") { + var result = theValidator(request); + if (!result) { + throw new Parse.Error(Parse.Error.VALIDATION_ERROR, 'Validation failed.'); + } + } + + return new Promise(function (resolve, reject) { + const userString = (req.auth && req.auth.user) ? req.auth.user.id : undefined; + const cleanInput = logger.truncateLogMessage(JSON.stringify(params)); + const { success, error, message } = FunctionsRouter.createResponseObject((result) => { + try { + const cleanResult = logger.truncateLogMessage(JSON.stringify(result.response.result)); + logger.info( + `Ran cloud function ${functionName} for user ${userString} with:\n Input: ${cleanInput }\n Result: ${cleanResult }`, + { + functionName, + params, + user: userString, + } + ); + resolve(result); + } catch (e) { + reject(e); + } + }, (error) => { + try { + logger.error( + `Failed running cloud function ${functionName} for user ${userString} with:\n Input: ${cleanInput}\n Error: ` + JSON.stringify(error), + { + functionName, + error, + params, + user: userString + } + ); + reject(error); + } catch (e) { + reject(e); + } + }); + return Promise.resolve().then(() => { + return theFunction(request, { message }); + }).then(success, error); + }); } } diff --git a/src/triggers.js b/src/triggers.js index cb9d1b0c..0fb03564 100644 --- a/src/triggers.js +++ b/src/triggers.js @@ -222,16 +222,11 @@ export function getResponseObject(request, resolve, reject) { } return resolve(response); }, - error: function(code, message) { - if (!message) { - if (code instanceof Parse.Error) { - return reject(code) - } - message = code; - code = Parse.Error.SCRIPT_FAILED; + error: function(error) { + if (typeof error === 'string') { + return reject(new Parse.Error(Parse.Error.SCRIPT_FAILED, error)); } - var scriptError = new Parse.Error(code, message); - return reject(scriptError); + return reject(error); } } } @@ -276,7 +271,7 @@ export function maybeRunAfterFindTrigger(triggerType, auth, className, objects, return resolve(); } const request = getRequestObject(triggerType, auth, null, null, config); - const response = getResponseObject(request, + const { success, error } = getResponseObject(request, object => { resolve(object); }, @@ -289,16 +284,18 @@ export function maybeRunAfterFindTrigger(triggerType, auth, className, objects, object.className = className; return Parse.Object.fromJSON(object); }); - const triggerPromise = trigger(request, response); - if (triggerPromise && typeof triggerPromise.then === "function") { - return triggerPromise.then(promiseResults => { - if(promiseResults) { - resolve(promiseResults); - }else{ - return reject(new Parse.Error(Parse.Error.SCRIPT_FAILED, "AfterFind expect results to be returned in the promise")); - } - }); - } + return Promise.resolve().then(() => { + const response = trigger(request); + if (response && typeof response.then === 'function') { + return response.then((results) => { + if (!results) { + throw new Parse.Error(Parse.Error.SCRIPT_FAILED, "AfterFind expect results to be returned in the promise"); + } + return results; + }); + } + return response; + }).then(success, error); }).then((results) => { logTriggerAfterHook(triggerType, className, JSON.stringify(results), auth); return results; @@ -401,7 +398,7 @@ export function maybeRunTrigger(triggerType, auth, parseObject, originalParseObj var trigger = getTrigger(parseObject.className, triggerType, config.applicationId); if (!trigger) return resolve(); var request = getRequestObject(triggerType, auth, parseObject, originalParseObject, config); - var response = getResponseObject(request, (object) => { + var { success, error } = getResponseObject(request, (object) => { logTriggerSuccessBeforeHook( triggerType, parseObject.className, parseObject.toJSON(), object, auth); resolve(object); @@ -410,27 +407,19 @@ export function maybeRunTrigger(triggerType, auth, parseObject, originalParseObj triggerType, parseObject.className, parseObject.toJSON(), auth, error); reject(error); }); - // Force the current Parse app before the trigger - Parse.applicationId = config.applicationId; - Parse.javascriptKey = config.javascriptKey || ''; - Parse.masterKey = config.masterKey; // AfterSave and afterDelete triggers can return a promise, which if they // do, needs to be resolved before this promise is resolved, // so trigger execution is synced with RestWrite.execute() call. // If triggers do not return a promise, they can run async code parallel // to the RestWrite.execute() call. - var triggerPromise = trigger(request, response); - if(triggerType === Types.afterSave || triggerType === Types.afterDelete) - { - logTriggerAfterHook(triggerType, parseObject.className, parseObject.toJSON(), auth); - if(triggerPromise && typeof triggerPromise.then === "function") { - return triggerPromise.then(resolve, resolve); + return Promise.resolve().then(() => { + const promise = trigger(request); + if(triggerType === Types.afterSave || triggerType === Types.afterDelete) { + logTriggerAfterHook(triggerType, parseObject.className, parseObject.toJSON(), auth); } - else { - return resolve(); - } - } + return promise; + }).then(success, error); }); }