Files
kami-parse-server/spec/CloudCode.spec.js
Florent Vilmart b754d51e8e chore(package): update jasmine to version 3.0.0 (#4553)
* chore(package): update jasmine to version 3.0.0

Closes #4547

* Fixes failing tests for jasmine 3.0

Starting 3.0, done(something) will fail

* Update tests so they dont leverage var, but let and const

With jasmine 3.0, the randomization engine was making the test fails because of the scope of `var`

* Remove randomizer

* Use same adapter for PG tests, drop table to ensure the tests dont side effect
2018-02-17 09:55:30 -05:00

1823 lines
53 KiB
JavaScript

"use strict"
const Parse = require("parse/node");
const rp = require('request-promise');
const InMemoryCacheAdapter = require('../src/Adapters/Cache/InMemoryCacheAdapter').InMemoryCacheAdapter;
describe('Cloud Code', () => {
it('can load absolute cloud code file', done => {
reconfigureServer({ cloud: __dirname + '/cloud/cloudCodeRelativeFile.js' })
.then(() => {
Parse.Cloud.run('cloudCodeInFile', {}, result => {
expect(result).toEqual('It is possible to define cloud code in a file.');
done();
});
})
});
it('can load relative cloud code file', done => {
reconfigureServer({ cloud: './spec/cloud/cloudCodeAbsoluteFile.js' })
.then(() => {
Parse.Cloud.run('cloudCodeInFile', {}, result => {
expect(result).toEqual('It is possible to define cloud code in a file.');
done();
});
})
});
it('can create functions', done => {
Parse.Cloud.define('hello', (req, res) => {
res.success('Hello world!');
});
Parse.Cloud.run('hello', {}, result => {
expect(result).toEqual('Hello world!');
done();
});
});
it('is cleared cleared after the previous test', done => {
Parse.Cloud.run('hello', {})
.catch(error => {
expect(error.code).toEqual(141);
done();
});
});
it('basic beforeSave rejection', function(done) {
Parse.Cloud.beforeSave('BeforeSaveFail', function(req, res) {
res.error('You shall not pass!');
});
const obj = new Parse.Object('BeforeSaveFail');
obj.set('foo', 'bar');
obj.save().then(() => {
fail('Should not have been able to save BeforeSaveFailure class.');
done();
}, () => {
done();
})
});
it('returns an error', (done) => {
Parse.Cloud.define('cloudCodeWithError', (req, res) => {
/* eslint-disable no-undef */
foo.bar();
/* eslint-enable no-undef */
res.success('I better throw an error.');
});
Parse.Cloud.run('cloudCodeWithError')
.then(
() => done.fail('should not succeed'),
e => {
expect(e).toEqual(new Parse.Error(1, undefined));
done();
});
});
it('beforeSave rejection with custom error code', function(done) {
Parse.Cloud.beforeSave('BeforeSaveFailWithErrorCode', function (req, res) {
res.error(999, 'Nope');
});
const obj = new Parse.Object('BeforeSaveFailWithErrorCode');
obj.set('foo', 'bar');
obj.save().then(function() {
fail('Should not have been able to save BeforeSaveFailWithErrorCode class.');
done();
}, function(error) {
expect(error.code).toEqual(999);
expect(error.message).toEqual('Nope');
done();
});
});
it('basic beforeSave rejection via promise', function(done) {
Parse.Cloud.beforeSave('BeforeSaveFailWithPromise', function (req, res) {
const query = new Parse.Query('Yolo');
query.find().then(() => {
res.error('Nope');
}, () => {
res.success();
});
});
const obj = new Parse.Object('BeforeSaveFailWithPromise');
obj.set('foo', 'bar');
obj.save().then(function() {
fail('Should not have been able to save BeforeSaveFailure class.');
done();
}, function(error) {
expect(error.code).toEqual(Parse.Error.SCRIPT_FAILED);
expect(error.message).toEqual('Nope');
done();
})
});
it('test beforeSave changed object success', function(done) {
Parse.Cloud.beforeSave('BeforeSaveChanged', function(req, res) {
req.object.set('foo', 'baz');
res.success();
});
const obj = new Parse.Object('BeforeSaveChanged');
obj.set('foo', 'bar');
obj.save().then(function() {
const query = new Parse.Query('BeforeSaveChanged');
query.get(obj.id).then(function(objAgain) {
expect(objAgain.get('foo')).toEqual('baz');
done();
}, function(error) {
fail(error);
done();
});
}, function(error) {
fail(error);
done();
});
});
it('test beforeSave returns value on create and update', (done) => {
Parse.Cloud.beforeSave('BeforeSaveChanged', function(req, res) {
req.object.set('foo', 'baz');
res.success();
});
const obj = new Parse.Object('BeforeSaveChanged');
obj.set('foo', 'bing');
obj.save().then(() => {
expect(obj.get('foo')).toEqual('baz');
obj.set('foo', 'bar');
return obj.save().then(() => {
expect(obj.get('foo')).toEqual('baz');
done();
})
})
});
it('test afterSave ran and created an object', function(done) {
Parse.Cloud.afterSave('AfterSaveTest', function(req) {
const obj = new Parse.Object('AfterSaveProof');
obj.set('proof', req.object.id);
obj.save();
});
const obj = new Parse.Object('AfterSaveTest');
obj.save();
setTimeout(function() {
const query = new Parse.Query('AfterSaveProof');
query.equalTo('proof', obj.id);
query.find().then(function(results) {
expect(results.length).toEqual(1);
done();
}, function(error) {
fail(error);
done();
});
}, 500);
});
it('test afterSave ran on created object and returned a promise', function(done) {
Parse.Cloud.afterSave('AfterSaveTest2', function(req) {
const obj = req.object;
if(!obj.existed())
{
const promise = new Parse.Promise();
setTimeout(function(){
obj.set('proof', obj.id);
obj.save().then(function(){
promise.resolve();
});
}, 1000);
return promise;
}
});
const obj = new Parse.Object('AfterSaveTest2');
obj.save().then(function(){
const query = new Parse.Query('AfterSaveTest2');
query.equalTo('proof', obj.id);
query.find().then(function(results) {
expect(results.length).toEqual(1);
const savedObject = results[0];
expect(savedObject.get('proof')).toEqual(obj.id);
done();
},
function(error) {
fail(error);
done();
});
});
});
// TODO: Fails on CI randomly as racing
xit('test afterSave ignoring promise, object not found', function(done) {
Parse.Cloud.afterSave('AfterSaveTest2', function(req) {
const obj = req.object;
if(!obj.existed())
{
const promise = new Parse.Promise();
setTimeout(function(){
obj.set('proof', obj.id);
obj.save().then(function(){
promise.resolve();
});
}, 1000);
return promise;
}
});
const obj = new Parse.Object('AfterSaveTest2');
obj.save().then(function(){
done();
})
const query = new Parse.Query('AfterSaveTest2');
query.equalTo('proof', obj.id);
query.find().then(function(results) {
expect(results.length).toEqual(0);
},
function(error) {
fail(error);
});
});
it('test afterSave rejecting promise', function(done) {
Parse.Cloud.afterSave('AfterSaveTest2', function() {
const promise = new Parse.Promise();
setTimeout(function(){
promise.reject("THIS SHOULD BE IGNORED");
}, 1000);
return promise;
});
const obj = new Parse.Object('AfterSaveTest2');
obj.save().then(function(){
done();
}, function(error){
fail(error);
done();
})
});
it('test afterDelete returning promise, object is deleted when destroy resolves', function(done) {
Parse.Cloud.afterDelete('AfterDeleteTest2', function(req) {
const promise = new Parse.Promise();
setTimeout(function(){
const obj = new Parse.Object('AfterDeleteTestProof');
obj.set('proof', req.object.id);
obj.save().then(function(){
promise.resolve();
});
}, 1000);
return promise;
});
const errorHandler = function(error) {
fail(error);
done();
}
const obj = new Parse.Object('AfterDeleteTest2');
obj.save().then(function(){
obj.destroy().then(function(){
const query = new Parse.Query('AfterDeleteTestProof');
query.equalTo('proof', obj.id);
query.find().then(function(results) {
expect(results.length).toEqual(1);
const deletedObject = results[0];
expect(deletedObject.get('proof')).toEqual(obj.id);
done();
}, errorHandler);
}, errorHandler)
}, errorHandler);
});
it('test afterDelete ignoring promise, object is not yet deleted', function(done) {
Parse.Cloud.afterDelete('AfterDeleteTest2', function(req) {
const promise = new Parse.Promise();
setTimeout(function(){
const obj = new Parse.Object('AfterDeleteTestProof');
obj.set('proof', req.object.id);
obj.save().then(function(){
promise.resolve();
});
}, 1000);
return promise;
});
const errorHandler = function(error) {
fail(error);
done();
}
const obj = new Parse.Object('AfterDeleteTest2');
obj.save().then(function(){
obj.destroy().then(function(){
done();
})
const query = new Parse.Query('AfterDeleteTestProof');
query.equalTo('proof', obj.id);
query.find().then(function(results) {
expect(results.length).toEqual(0);
}, errorHandler);
}, errorHandler);
});
it('test beforeSave happens on update', function(done) {
Parse.Cloud.beforeSave('BeforeSaveChanged', function(req, res) {
req.object.set('foo', 'baz');
res.success();
});
const obj = new Parse.Object('BeforeSaveChanged');
obj.set('foo', 'bar');
obj.save().then(function() {
obj.set('foo', 'bar');
return obj.save();
}).then(function() {
const query = new Parse.Query('BeforeSaveChanged');
return query.get(obj.id).then(function(objAgain) {
expect(objAgain.get('foo')).toEqual('baz');
done();
});
}, function(error) {
fail(error);
done();
});
});
it('test beforeDelete failure', function(done) {
Parse.Cloud.beforeDelete('BeforeDeleteFail', function(req, res) {
res.error('Nope');
});
const obj = new Parse.Object('BeforeDeleteFail');
let id;
obj.set('foo', 'bar');
obj.save().then(() => {
id = obj.id;
return obj.destroy();
}).then(() => {
fail('obj.destroy() should have failed, but it succeeded');
done();
}, (error) => {
expect(error.code).toEqual(Parse.Error.SCRIPT_FAILED);
expect(error.message).toEqual('Nope');
const objAgain = new Parse.Object('BeforeDeleteFail', {objectId: id});
return objAgain.fetch();
}).then((objAgain) => {
if (objAgain) {
expect(objAgain.get('foo')).toEqual('bar');
} else {
fail("unable to fetch the object ", id);
}
done();
}, (error) => {
// We should have been able to fetch the object again
fail(error);
});
});
it('basic beforeDelete rejection via promise', function(done) {
Parse.Cloud.beforeSave('BeforeDeleteFailWithPromise', function (req, res) {
const query = new Parse.Query('Yolo');
query.find().then(() => {
res.error('Nope');
}, () => {
res.success();
});
});
const obj = new Parse.Object('BeforeDeleteFailWithPromise');
obj.set('foo', 'bar');
obj.save().then(function() {
fail('Should not have been able to save BeforeSaveFailure class.');
done();
}, function(error) {
expect(error.code).toEqual(Parse.Error.SCRIPT_FAILED);
expect(error.message).toEqual('Nope');
done();
})
});
it('test afterDelete ran and created an object', function(done) {
Parse.Cloud.afterDelete('AfterDeleteTest', function(req) {
const obj = new Parse.Object('AfterDeleteProof');
obj.set('proof', req.object.id);
obj.save();
});
const obj = new Parse.Object('AfterDeleteTest');
obj.save().then(function() {
obj.destroy();
});
setTimeout(function() {
const query = new Parse.Query('AfterDeleteProof');
query.equalTo('proof', obj.id);
query.find().then(function(results) {
expect(results.length).toEqual(1);
done();
}, function(error) {
fail(error);
done();
});
}, 500);
});
it('test cloud function return types', function(done) {
Parse.Cloud.define('foo', function(req, res) {
res.success({
object: {
__type: 'Object',
className: 'Foo',
objectId: '123',
x: 2,
relation: {
__type: 'Object',
className: 'Bar',
objectId: '234',
x: 3
}
},
array: [{
__type: 'Object',
className: 'Bar',
objectId: '345',
x: 2
}],
a: 2
});
});
Parse.Cloud.run('foo').then((result) => {
expect(result.object instanceof Parse.Object).toBeTruthy();
if (!result.object) {
fail("Unable to run foo");
done();
return;
}
expect(result.object.className).toEqual('Foo');
expect(result.object.get('x')).toEqual(2);
const bar = result.object.get('relation');
expect(bar instanceof Parse.Object).toBeTruthy();
expect(bar.className).toEqual('Bar');
expect(bar.get('x')).toEqual(3);
expect(Array.isArray(result.array)).toEqual(true);
expect(result.array[0] instanceof Parse.Object).toBeTruthy();
expect(result.array[0].get('x')).toEqual(2);
done();
});
});
it('test cloud function request params types', function(done) {
Parse.Cloud.define('params', function(req, res) {
expect(req.params.date instanceof Date).toBe(true);
expect(req.params.date.getTime()).toBe(1463907600000);
expect(req.params.dateList[0] instanceof Date).toBe(true);
expect(req.params.dateList[0].getTime()).toBe(1463907600000);
expect(req.params.complexStructure.date[0] instanceof Date).toBe(true);
expect(req.params.complexStructure.date[0].getTime()).toBe(1463907600000);
expect(req.params.complexStructure.deepDate.date[0] instanceof Date).toBe(true);
expect(req.params.complexStructure.deepDate.date[0].getTime()).toBe(1463907600000);
expect(req.params.complexStructure.deepDate2[0].date instanceof Date).toBe(true);
expect(req.params.complexStructure.deepDate2[0].date.getTime()).toBe(1463907600000);
// Regression for #2294
expect(req.params.file instanceof Parse.File).toBe(true);
expect(req.params.file.url()).toEqual('https://some.url');
// Regression for #2204
expect(req.params.array).toEqual(['a', 'b', 'c']);
expect(Array.isArray(req.params.array)).toBe(true);
expect(req.params.arrayOfArray).toEqual([['a', 'b', 'c'], ['d', 'e','f']]);
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({});
});
const params = {
'date': {
'__type': 'Date',
'iso': '2016-05-22T09:00:00.000Z'
},
'dateList': [
{
'__type': 'Date',
'iso': '2016-05-22T09:00:00.000Z'
}
],
'lol': 'hello',
'complexStructure': {
'date': [
{
'__type': 'Date',
'iso': '2016-05-22T09:00:00.000Z'
}
],
'deepDate': {
'date': [
{
'__type': 'Date',
'iso': '2016-05-22T09:00:00.000Z'
}
]
},
'deepDate2': [
{
'date': {
'__type': 'Date',
'iso': '2016-05-22T09:00:00.000Z'
}
}
]
},
'file': Parse.File.fromJSON({
__type: 'File',
name: 'name',
url: 'https://some.url'
}),
'array': ['a', 'b', 'c'],
'arrayOfArray': [['a', 'b', 'c'], ['d', 'e', 'f']]
};
Parse.Cloud.run('params', params).then(() => {
done();
});
});
it('test cloud function should echo keys', function(done) {
Parse.Cloud.define('echoKeys', function(req, res){
return res.success({
applicationId: Parse.applicationId,
masterKey: Parse.masterKey,
javascriptKey: Parse.javascriptKey
})
});
Parse.Cloud.run('echoKeys').then((result) => {
expect(result.applicationId).toEqual(Parse.applicationId);
expect(result.masterKey).toEqual(Parse.masterKey);
expect(result.javascriptKey).toEqual(Parse.javascriptKey);
done();
});
});
it('should properly create an object in before save', done => {
Parse.Cloud.beforeSave('BeforeSaveChanged', function(req, res) {
req.object.set('foo', 'baz');
res.success();
});
Parse.Cloud.define('createBeforeSaveChangedObject', function(req, res){
const obj = new Parse.Object('BeforeSaveChanged');
obj.save().then(() => {
res.success(obj);
})
})
Parse.Cloud.run('createBeforeSaveChangedObject').then((res) => {
expect(res.get('foo')).toEqual('baz');
done();
});
});
it('dirtyKeys are set on update', done => {
let triggerTime = 0;
// Register a mock beforeSave hook
Parse.Cloud.beforeSave('GameScore', (req, res) => {
const object = req.object;
expect(object instanceof Parse.Object).toBeTruthy();
expect(object.get('fooAgain')).toEqual('barAgain');
if (triggerTime == 0) {
// Create
expect(object.get('foo')).toEqual('bar');
} else if (triggerTime == 1) {
// Update
expect(object.dirtyKeys()).toEqual(['foo']);
expect(object.dirty('foo')).toBeTruthy();
expect(object.get('foo')).toEqual('baz');
} else {
res.error();
}
triggerTime++;
res.success();
});
const obj = new Parse.Object('GameScore');
obj.set('foo', 'bar');
obj.set('fooAgain', 'barAgain');
obj.save().then(() => {
// We only update foo
obj.set('foo', 'baz');
return obj.save();
}).then(() => {
// Make sure the checking has been triggered
expect(triggerTime).toBe(2);
done();
}, function(error) {
fail(error);
done();
});
});
it('test beforeSave unchanged success', function(done) {
Parse.Cloud.beforeSave('BeforeSaveUnchanged', function(req, res) {
res.success();
});
const obj = new Parse.Object('BeforeSaveUnchanged');
obj.set('foo', 'bar');
obj.save().then(function() {
done();
}, function(error) {
fail(error);
done();
});
});
it('test beforeDelete success', function(done) {
Parse.Cloud.beforeDelete('BeforeDeleteTest', function(req, res) {
res.success();
});
const obj = new Parse.Object('BeforeDeleteTest');
obj.set('foo', 'bar');
obj.save().then(function() {
return obj.destroy();
}).then(function() {
const objAgain = new Parse.Object('BeforeDeleteTest', obj.id);
return objAgain.fetch().then(fail, done);
}, function(error) {
fail(error);
done();
});
});
it('test save triggers get user', function(done) {
Parse.Cloud.beforeSave('SaveTriggerUser', function(req, res) {
if (req.user && req.user.id) {
res.success();
} else {
res.error('No user present on request object for beforeSave.');
}
});
Parse.Cloud.afterSave('SaveTriggerUser', function(req) {
if (!req.user || !req.user.id) {
console.log('No user present on request object for afterSave.');
}
});
const user = new Parse.User();
user.set("password", "asdf");
user.set("email", "asdf@example.com");
user.set("username", "zxcv");
user.signUp(null, {
success: function() {
const obj = new Parse.Object('SaveTriggerUser');
obj.save().then(function() {
done();
}, function(error) {
fail(error);
done();
});
}
});
});
it('beforeSave change propagates through the save response', (done) => {
Parse.Cloud.beforeSave('ChangingObject', function(request, response) {
request.object.set('foo', 'baz');
response.success();
});
const obj = new Parse.Object('ChangingObject');
obj.save({ foo: 'bar' }).then((objAgain) => {
expect(objAgain.get('foo')).toEqual('baz');
done();
}, () => {
fail('Should not have failed to save.');
done();
});
});
it('beforeSave change propagates through the afterSave #1931', (done) => {
Parse.Cloud.beforeSave('ChangingObject', function(request, response) {
request.object.unset('file');
request.object.unset('date');
response.success();
});
Parse.Cloud.afterSave('ChangingObject', function(request) {
expect(request.object.has("file")).toBe(false);
expect(request.object.has("date")).toBe(false);
expect(request.object.get('file')).toBeUndefined();
return Promise.resolve();
});
const file = new Parse.File("yolo.txt", [1,2,3], "text/plain");
file.save().then(() => {
const obj = new Parse.Object('ChangingObject');
return obj.save({ file, date: new Date() })
}).then(() => {
done();
}, () => {
fail();
done();
})
});
it('test cloud function parameter validation success', (done) => {
// Register a function with validation
Parse.Cloud.define('functionWithParameterValidation', (req, res) => {
res.success('works');
}, (request) => {
return request.params.success === 100;
});
Parse.Cloud.run('functionWithParameterValidation', {"success":100}).then(() => {
done();
}, () => {
fail('Validation should not have failed.');
done();
});
});
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.User.signUp('user', 'pass')
.then(user => {
user.set('data', 'AAA');
return user.save();
})
.then(() => Parse.Cloud.run('testQuery'))
.then(result => {
expect(result).toEqual('AAA');
Parse.User.current().set('data', 'BBB');
return Parse.User.current().save(null, {useMasterKey: true});
})
.then(() => Parse.Cloud.run('testQuery'))
.then(result => {
expect(result).toEqual('BBB');
done();
});
});
it('clears out the user cache for all sessions when the user is changed', done => {
let session1;
let session2;
let user;
const cacheAdapter = new InMemoryCacheAdapter({ ttl: 100000000 });
reconfigureServer({ cacheAdapter })
.then(() => {
Parse.Cloud.define('checkStaleUser', (request, response) => {
response.success(request.user.get('data'));
});
user = new Parse.User();
user.set('username', 'test');
user.set('password', 'moon-y');
user.set('data', 'first data');
return user.signUp();
})
.then(user => {
session1 = user.getSessionToken();
return rp({
uri: 'http://localhost:8378/1/login?username=test&password=moon-y',
json: true,
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest',
},
})
})
.then(body => {
session2 = body.sessionToken;
//Ensure both session tokens are in the cache
return Parse.Cloud.run('checkStaleUser')
})
.then(() => rp({
method: 'POST',
uri: 'http://localhost:8378/1/functions/checkStaleUser',
json: true,
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest',
'X-Parse-Session-Token': session2,
}
}))
.then(() => Parse.Promise.all([cacheAdapter.get('test:user:' + session1), cacheAdapter.get('test:user:' + session2)]))
.then(cachedVals => {
expect(cachedVals[0].objectId).toEqual(user.id);
expect(cachedVals[1].objectId).toEqual(user.id);
//Change with session 1 and then read with session 2.
user.set('data', 'second data');
return user.save()
})
.then(() => rp({
method: 'POST',
uri: 'http://localhost:8378/1/functions/checkStaleUser',
json: true,
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest',
'X-Parse-Session-Token': session2,
}
}))
.then(body => {
expect(body.result).toEqual('second data');
done();
})
.catch(error => {
fail(JSON.stringify(error));
done();
});
});
it('trivial beforeSave should not affect fetched pointers (regression test for #1238)', done => {
Parse.Cloud.beforeSave('BeforeSaveUnchanged', (req, res) => {
res.success();
});
const TestObject = Parse.Object.extend("TestObject");
const NoBeforeSaveObject = Parse.Object.extend("NoBeforeSave");
const BeforeSaveObject = Parse.Object.extend("BeforeSaveUnchanged");
const aTestObject = new TestObject();
aTestObject.set("foo", "bar");
aTestObject.save()
.then(aTestObject => {
const aNoBeforeSaveObj = new NoBeforeSaveObject();
aNoBeforeSaveObj.set("aTestObject", aTestObject);
expect(aNoBeforeSaveObj.get("aTestObject").get("foo")).toEqual("bar");
return aNoBeforeSaveObj.save();
})
.then(aNoBeforeSaveObj => {
expect(aNoBeforeSaveObj.get("aTestObject").get("foo")).toEqual("bar");
const aBeforeSaveObj = new BeforeSaveObject();
aBeforeSaveObj.set("aTestObject", aTestObject);
expect(aBeforeSaveObj.get("aTestObject").get("foo")).toEqual("bar");
return aBeforeSaveObj.save();
})
.then(aBeforeSaveObj => {
expect(aBeforeSaveObj.get("aTestObject").get("foo")).toEqual("bar");
done();
});
});
it('beforeSave should not affect fetched pointers', done => {
Parse.Cloud.beforeSave('BeforeSaveUnchanged', (req, res) => {
res.success();
});
Parse.Cloud.beforeSave('BeforeSaveChanged', function(req, res) {
req.object.set('foo', 'baz');
res.success();
});
const TestObject = Parse.Object.extend("TestObject");
const BeforeSaveUnchangedObject = Parse.Object.extend("BeforeSaveUnchanged");
const BeforeSaveChangedObject = Parse.Object.extend("BeforeSaveChanged");
const aTestObject = new TestObject();
aTestObject.set("foo", "bar");
aTestObject.save()
.then(aTestObject => {
const aBeforeSaveUnchangedObject = new BeforeSaveUnchangedObject();
aBeforeSaveUnchangedObject.set("aTestObject", aTestObject);
expect(aBeforeSaveUnchangedObject.get("aTestObject").get("foo")).toEqual("bar");
return aBeforeSaveUnchangedObject.save();
})
.then(aBeforeSaveUnchangedObject => {
expect(aBeforeSaveUnchangedObject.get("aTestObject").get("foo")).toEqual("bar");
const aBeforeSaveChangedObject = new BeforeSaveChangedObject();
aBeforeSaveChangedObject.set("aTestObject", aTestObject);
expect(aBeforeSaveChangedObject.get("aTestObject").get("foo")).toEqual("bar");
return aBeforeSaveChangedObject.save();
})
.then(aBeforeSaveChangedObject => {
expect(aBeforeSaveChangedObject.get("aTestObject").get("foo")).toEqual("bar");
expect(aBeforeSaveChangedObject.get("foo")).toEqual("baz");
done();
});
});
it('should fully delete objects when using `unset` with beforeSave (regression test for #1840)', done => {
const TestObject = Parse.Object.extend('TestObject');
const NoBeforeSaveObject = Parse.Object.extend('NoBeforeSave');
const BeforeSaveObject = Parse.Object.extend('BeforeSaveChanged');
Parse.Cloud.beforeSave('BeforeSaveChanged', (req, res) => {
const object = req.object;
object.set('before', 'save');
res.success();
});
Parse.Cloud.define('removeme', (req, res) => {
const testObject = new TestObject();
testObject.save()
.then(testObject => {
const object = new NoBeforeSaveObject({remove: testObject});
return object.save();
})
.then(object => {
object.unset('remove');
return object.save();
})
.then(object => {
res.success(object);
}).catch(res.error);
});
Parse.Cloud.define('removeme2', (req, res) => {
const testObject = new TestObject();
testObject.save()
.then(testObject => {
const object = new BeforeSaveObject({remove: testObject});
return object.save();
})
.then(object => {
object.unset('remove');
return object.save();
})
.then(object => {
res.success(object);
}).catch(res.error);
});
Parse.Cloud.run('removeme')
.then(aNoBeforeSaveObj => {
expect(aNoBeforeSaveObj.get('remove')).toEqual(undefined);
return Parse.Cloud.run('removeme2');
})
.then(aBeforeSaveObj => {
expect(aBeforeSaveObj.get('before')).toEqual('save');
expect(aBeforeSaveObj.get('remove')).toEqual(undefined);
done();
}).catch((err) => {
jfail(err);
done();
});
});
/*
TODO: fix for Postgres
trying to delete a field that doesn't exists doesn't play nice
*/
it_exclude_dbs(['postgres'])('should fully delete objects when using `unset` with beforeSave (regression test for #1840)', done => {
const TestObject = Parse.Object.extend('TestObject');
const BeforeSaveObject = Parse.Object.extend('BeforeSaveChanged');
Parse.Cloud.beforeSave('BeforeSaveChanged', (req, res) => {
const object = req.object;
object.set('before', 'save');
object.unset('remove');
res.success();
});
let object;
const testObject = new TestObject({key: 'value'});
testObject.save().then(() => {
object = new BeforeSaveObject();
return object.save().then(() => {
object.set({remove:testObject})
return object.save();
});
}).then((objectAgain) => {
expect(objectAgain.get('remove')).toBeUndefined();
expect(object.get('remove')).toBeUndefined();
done();
}).fail((err) => {
jfail(err);
done();
});
});
it('should not include relation op (regression test for #1606)', done => {
const TestObject = Parse.Object.extend('TestObject');
const BeforeSaveObject = Parse.Object.extend('BeforeSaveChanged');
let testObj;
Parse.Cloud.beforeSave('BeforeSaveChanged', (req, res) => {
const object = req.object;
object.set('before', 'save');
testObj = new TestObject();
testObj.save().then(() => {
object.relation('testsRelation').add(testObj);
res.success();
}, res.error);
});
const object = new BeforeSaveObject();
object.save().then((objectAgain) => {
// Originally it would throw as it would be a non-relation
expect(() => { objectAgain.relation('testsRelation') }).not.toThrow();
done();
}).fail((err) => {
jfail(err);
done();
})
});
/**
* Checks that incrementing a value to a zero in a beforeSave hook
* 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) {
const CloudIncrementClass = Parse.Object.extend("CloudIncrementClass");
const obj = new CloudIncrementClass();
obj.id = req.params.objectId;
obj.save().then(
function (savedObj) {
res.success(savedObj);
});
});
Parse.Cloud.beforeSave("CloudIncrementClass", function (req, res) {
const obj = req.object;
if(!req.master) {
obj.increment('points', -10);
obj.increment('num', -9);
}
res.success();
});
const CloudIncrementClass = Parse.Object.extend('CloudIncrementClass');
const obj = new CloudIncrementClass();
obj.set('points', 10);
obj.set('num', 10);
obj.save(null, {useMasterKey: true})
.then(function() {
Parse.Cloud.run('cloudIncrementClassFunction', { objectId: obj.id })
.then(function(savedObj) {
expect(savedObj.get('num')).toEqual(1);
expect(savedObj.get('points')).toEqual(0);
done();
});
});
});
/**
* Verifies that an afterSave hook throwing an exception
* will not prevent a successful save response from being returned
*/
it('should succeed on afterSave exception', (done) => {
Parse.Cloud.afterSave("AfterSaveTestClass", function () {
throw "Exception";
});
const AfterSaveTestClass = Parse.Object.extend('AfterSaveTestClass');
const obj = new AfterSaveTestClass();
obj.save().then(done, done.fail);
});
describe('cloud jobs', () => {
it('should define a job', (done) => {
expect(() => {
Parse.Cloud.job('myJob', (req, res) => {
res.success();
});
}).not.toThrow();
rp.post({
url: 'http://localhost:8378/1/jobs/myJob',
headers: {
'X-Parse-Application-Id': Parse.applicationId,
'X-Parse-Master-Key': Parse.masterKey,
},
}).then(() => {
done();
}, (err) => {
fail(err);
done();
});
});
it('should not run without master key', (done) => {
expect(() => {
Parse.Cloud.job('myJob', (req, res) => {
res.success();
});
}).not.toThrow();
rp.post({
url: 'http://localhost:8378/1/jobs/myJob',
headers: {
'X-Parse-Application-Id': Parse.applicationId,
'X-Parse-REST-API-Key': 'rest',
},
}).then(() => {
fail('Expected to be unauthorized');
done();
}, (err) => {
expect(err.statusCode).toBe(403);
done();
});
});
it('should run with master key', (done) => {
expect(() => {
Parse.Cloud.job('myJob', (req, res) => {
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();
done();
});
}).not.toThrow();
rp.post({
url: 'http://localhost:8378/1/jobs/myJob',
headers: {
'X-Parse-Application-Id': Parse.applicationId,
'X-Parse-Master-Key': Parse.masterKey,
},
}).then(() => {
}, (err) => {
fail(err);
done();
});
});
it('should run with master key basic auth', (done) => {
expect(() => {
Parse.Cloud.job('myJob', (req, res) => {
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();
done();
});
}).not.toThrow();
rp.post({
url: `http://${Parse.applicationId}:${Parse.masterKey}@localhost:8378/1/jobs/myJob`,
}).then(() => {
}, (err) => {
fail(err);
done();
});
});
it('should set the message / success on the job', (done) => {
Parse.Cloud.job('myJob', (req, res) => {
res.message('hello');
res.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);
});
}).then((jobStatus) => {
expect(jobStatus.get('message')).toEqual('hello');
expect(jobStatus.get('status')).toEqual('succeeded');
done();
}).catch(err => {
console.error(err);
jfail(err);
done();
});
});
rp.post({
url: 'http://localhost:8378/1/jobs/myJob',
headers: {
'X-Parse-Application-Id': Parse.applicationId,
'X-Parse-Master-Key': Parse.masterKey,
},
}).then(() => {
}, (err) => {
fail(err);
done();
});
});
it('should set the failure on the job', (done) => {
Parse.Cloud.job('myJob', (req, res) => {
res.error('Something went wrong').then(() => {
return getJobStatus(req.jobId);
}).then((jobStatus) => {
expect(jobStatus.get('message')).toEqual('Something went wrong');
expect(jobStatus.get('status')).toEqual('failed');
done();
}).catch(err => {
jfail(err);
done();
});
});
rp.post({
url: 'http://localhost:8378/1/jobs/myJob',
headers: {
'X-Parse-Application-Id': Parse.applicationId,
'X-Parse-Master-Key': Parse.masterKey,
},
}).then(() => {
}, (err) => {
fail(err);
done();
});
});
function getJobStatus(jobId) {
const q = new Parse.Query('_JobStatus');
return q.get(jobId, {useMasterKey: true});
}
});
});
describe('cloud functions', () => {
it('Should have request ip', (done) => {
Parse.Cloud.define('myFunction', (req, res) => {
expect(req.ip).toBeDefined();
res.success("success");
});
Parse.Cloud.run('myFunction', {}).then(() => done());
});
});
describe('beforeSave hooks', () => {
it('should have request headers', (done) => {
Parse.Cloud.beforeSave('MyObject', (req, res) => {
expect(req.headers).toBeDefined();
res.success();
});
const MyObject = Parse.Object.extend('MyObject');
const myObject = new MyObject();
myObject.save().then(() => done());
});
it('should have request ip', (done) => {
Parse.Cloud.beforeSave('MyObject', (req, res) => {
expect(req.ip).toBeDefined();
res.success();
});
const MyObject = Parse.Object.extend('MyObject');
const myObject = new MyObject();
myObject.save().then(() => done());
});
});
describe('afterSave hooks', () => {
it('should have request headers', (done) => {
Parse.Cloud.afterSave('MyObject', (req) => {
expect(req.headers).toBeDefined();
});
const MyObject = Parse.Object.extend('MyObject');
const myObject = new MyObject();
myObject.save()
.then(() => done());
});
it('should have request ip', (done) => {
Parse.Cloud.afterSave('MyObject', (req, res) => {
expect(req.ip).toBeDefined();
res.success();
});
const MyObject = Parse.Object.extend('MyObject');
const myObject = new MyObject();
myObject.save().then(() => done());
});
});
describe('beforeDelete hooks', () => {
it('should have request headers', (done) => {
Parse.Cloud.beforeDelete('MyObject', (req, res) => {
expect(req.headers).toBeDefined();
res.success();
});
const MyObject = Parse.Object.extend('MyObject');
const myObject = new MyObject();
myObject.save()
.then(myObj => myObj.destroy())
.then(() => done());
});
it('should have request ip', (done) => {
Parse.Cloud.beforeDelete('MyObject', (req, res) => {
expect(req.ip).toBeDefined();
res.success();
});
const MyObject = Parse.Object.extend('MyObject');
const myObject = new MyObject();
myObject.save()
.then(myObj => myObj.destroy())
.then(() => done());
});
});
describe('afterDelete hooks', () => {
it('should have request headers', (done) => {
Parse.Cloud.afterDelete('MyObject', (req) => {
expect(req.headers).toBeDefined();
});
const MyObject = Parse.Object.extend('MyObject');
const myObject = new MyObject();
myObject.save()
.then(myObj => myObj.destroy())
.then(() => done());
});
it('should have request ip', (done) => {
Parse.Cloud.afterDelete('MyObject', (req) => {
expect(req.ip).toBeDefined();
});
const MyObject = Parse.Object.extend('MyObject');
const myObject = new MyObject();
myObject.save()
.then(myObj => myObj.destroy())
.then(() => done());
});
});
describe('beforeFind hooks', () => {
it('should add beforeFind trigger', (done) => {
Parse.Cloud.beforeFind('MyObject', (req) => {
const q = req.query;
expect(q instanceof Parse.Query).toBe(true);
const jsonQuery = q.toJSON();
expect(jsonQuery.where.key).toEqual('value');
expect(jsonQuery.where.some).toEqual({'$gt': 10});
expect(jsonQuery.include).toEqual('otherKey,otherValue');
expect(jsonQuery.limit).toEqual(100);
expect(jsonQuery.skip).toBe(undefined);
expect(req.isGet).toEqual(false);
});
const query = new Parse.Query('MyObject');
query.equalTo('key', 'value');
query.greaterThan('some', 10);
query.include('otherKey');
query.include('otherValue');
query.find().then(() => {
done();
});
});
it('should use modify', (done) => {
Parse.Cloud.beforeFind('MyObject', (req) => {
const q = req.query;
q.equalTo('forced', true);
});
const obj0 = new Parse.Object('MyObject');
obj0.set('forced', false);
const obj1 = new Parse.Object('MyObject');
obj1.set('forced', true);
Parse.Object.saveAll([obj0, obj1]).then(() => {
const query = new Parse.Query('MyObject');
query.equalTo('forced', false);
query.find().then((results) => {
expect(results.length).toBe(1);
const firstResult = results[0];
expect(firstResult.get('forced')).toBe(true);
done();
});
});
});
it('should use the modified the query', (done) => {
Parse.Cloud.beforeFind('MyObject', (req) => {
const q = req.query;
const otherQuery = new Parse.Query('MyObject');
otherQuery.equalTo('forced', true);
return Parse.Query.or(q, otherQuery);
});
const obj0 = new Parse.Object('MyObject');
obj0.set('forced', false);
const obj1 = new Parse.Object('MyObject');
obj1.set('forced', true);
Parse.Object.saveAll([obj0, obj1]).then(() => {
const query = new Parse.Query('MyObject');
query.equalTo('forced', false);
query.find().then((results) => {
expect(results.length).toBe(2);
done();
});
});
});
it('should reject queries', (done) => {
Parse.Cloud.beforeFind('MyObject', () => {
return Promise.reject('Do not run that query');
});
const query = new Parse.Query('MyObject');
query.find().then(() => {
fail('should not succeed');
done();
}, (err) => {
expect(err.code).toBe(1);
expect(err.message).toEqual('Do not run that query');
done();
});
});
it('should handle empty where', (done) => {
Parse.Cloud.beforeFind('MyObject', (req) => {
const otherQuery = new Parse.Query('MyObject');
otherQuery.equalTo('some', true);
return Parse.Query.or(req.query, otherQuery);
});
rp.get({
url: 'http://localhost:8378/1/classes/MyObject',
headers: {
'X-Parse-Application-Id': Parse.applicationId,
'X-Parse-REST-API-Key': 'rest',
},
}).then(() => {
done();
}, (err) => {
fail(err);
done();
});
});
it('should handle sorting where', (done) => {
Parse.Cloud.beforeFind('MyObject', (req) => {
const query = req.query;
query.ascending('score');
return query;
});
const count = 20;
const objects = [];
while (objects.length != count) {
const object = new Parse.Object('MyObject');
object.set('score', Math.floor(Math.random() * 100));
objects.push(object);
}
Parse.Object.saveAll(objects).then(() => {
const query = new Parse.Query('MyObject');
return query.find();
}).then((objects) => {
let lastScore = -1;
objects.forEach((element) => {
expect(element.get('score') >= lastScore).toBe(true);
lastScore = element.get('score');
});
}).then(done).catch(done.fail);
});
it('should add beforeFind trigger using get API',(done) => {
const hook = {
method: function(req) {
expect(req.isGet).toEqual(true);
return Promise.resolve();
}
};
spyOn(hook, 'method').and.callThrough();
Parse.Cloud.beforeFind('MyObject', hook.method);
const obj = new Parse.Object('MyObject');
obj.set('secretField', 'SSID');
obj.save().then(function() {
rp({
method: 'GET',
uri: 'http://localhost:8378/1/classes/MyObject/' + obj.id,
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest'
},
json: true,
}).then(body => {
expect(body.secretField).toEqual('SSID');
expect(hook.method).toHaveBeenCalled();
done();
});
});
});
it('should have request headers', (done) => {
Parse.Cloud.beforeFind('MyObject', (req) => {
expect(req.headers).toBeDefined();
});
const MyObject = Parse.Object.extend('MyObject');
const myObject = new MyObject();
myObject.save()
.then((myObj) => {
const query = new Parse.Query('MyObject');
query.equalTo('objectId', myObj.id);
return Promise.all([
query.get(myObj.id),
query.first(),
query.find(),
]);
})
.then(() => done());
});
it('should have request ip', (done) => {
Parse.Cloud.beforeFind('MyObject', (req) => {
expect(req.ip).toBeDefined();
});
const MyObject = Parse.Object.extend('MyObject');
const myObject = new MyObject();
myObject.save()
.then((myObj) => {
const query = new Parse.Query('MyObject');
query.equalTo('objectId', myObj.id);
return Promise.all([
query.get(myObj.id),
query.first(),
query.find(),
]);
})
.then(() => done());
});
});
describe('afterFind hooks', () => {
it('should add afterFind trigger using get',(done) => {
Parse.Cloud.afterFind('MyObject', (req, res) => {
for(let i = 0 ; i < req.objects.length ; i++){
req.objects[i].set("secretField","###");
}
res.success(req.objects);
});
const obj = new Parse.Object('MyObject');
obj.set('secretField', 'SSID');
obj.save().then(function() {
const query = new Parse.Query('MyObject');
query.get(obj.id).then(function(result) {
expect(result.get('secretField')).toEqual('###');
done();
}, function(error) {
fail(error);
done();
});
}, function(error) {
fail(error);
done();
});
});
it('should add afterFind trigger using find',(done) => {
Parse.Cloud.afterFind('MyObject', (req, res) => {
for(let i = 0 ; i < req.objects.length ; i++){
req.objects[i].set("secretField","###");
}
res.success(req.objects);
});
const obj = new Parse.Object('MyObject');
obj.set('secretField', 'SSID');
obj.save().then(function() {
const query = new Parse.Query('MyObject');
query.equalTo('objectId',obj.id);
query.find().then(function(results) {
expect(results[0].get('secretField')).toEqual('###');
done();
}, function(error) {
fail(error);
done();
});
}, function(error) {
fail(error);
done();
});
});
it('should filter out results',(done) => {
Parse.Cloud.afterFind('MyObject', (req, res) => {
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);
});
const obj0 = new Parse.Object('MyObject');
obj0.set('secretField', 'SSID1');
const obj1 = new Parse.Object('MyObject');
obj1.set('secretField', 'SSID2');
Parse.Object.saveAll([obj0, obj1]).then(function() {
const query = new Parse.Query('MyObject');
query.find().then(function(results) {
expect(results[0].get('secretField')).toEqual('SSID1');
expect(results.length).toEqual(1);
done();
}, function(error) {
fail(error);
done();
});
}, function(error) {
fail(error);
done();
});
});
it('should handle failures',(done) => {
Parse.Cloud.afterFind('MyObject', (req, res) => {
res.error(Parse.Error.SCRIPT_FAILED, "It should fail");
});
const obj = new Parse.Object('MyObject');
obj.set('secretField', 'SSID');
obj.save().then(function() {
const query = new Parse.Query('MyObject');
query.equalTo('objectId',obj.id);
query.find().then(function() {
fail("AfterFind should handle response failure correctly");
done();
}, function() {
done();
});
}, function() {
done();
});
});
it('should also work with promise',(done) => {
Parse.Cloud.afterFind('MyObject', (req) => {
const promise = new Parse.Promise();
setTimeout(function(){
for(let i = 0 ; i < req.objects.length ; i++){
req.objects[i].set("secretField","###");
}
promise.resolve(req.objects);
}, 1000);
return promise;
});
const obj = new Parse.Object('MyObject');
obj.set('secretField', 'SSID');
obj.save().then(function() {
const query = new Parse.Query('MyObject');
query.equalTo('objectId',obj.id);
query.find().then(function(results) {
expect(results[0].get('secretField')).toEqual('###');
done();
}, function(error) {
fail(error);
});
}, function(error) {
fail(error);
});
});
it('should alter select', (done) => {
Parse.Cloud.beforeFind('MyObject', (req) => {
req.query.select('white');
return req.query;
});
const obj0 = new Parse.Object('MyObject')
.set('white', true)
.set('black', true);
obj0.save()
.then(() => {
new Parse.Query('MyObject')
.first()
.then(result => {
expect(result.get('white')).toBe(true);
expect(result.get('black')).toBe(undefined);
done();
});
});
});
it('should not alter select', (done) => {
const obj0 = new Parse.Object('MyObject')
.set('white', true)
.set('black', true);
obj0.save()
.then(() => {
new Parse.Query('MyObject')
.first()
.then(result => {
expect(result.get('white')).toBe(true);
expect(result.get('black')).toBe(true);
done();
});
});
});
it('should set count to true on beforeFind hooks if query is count', (done) => {
const hook = {
method: function(req) {
expect(req.count).toBe(true);
return Promise.resolve();
}
};
spyOn(hook, 'method').and.callThrough();
Parse.Cloud.beforeFind('Stuff', hook.method);
new Parse.Query('Stuff').count().then((count) => {
expect(count).toBe(0);
expect(hook.method).toHaveBeenCalled();
done();
});
});
it('should set count to false on beforeFind hooks if query is not count', (done) => {
const hook = {
method: function(req) {
expect(req.count).toBe(false);
return Promise.resolve();
}
};
spyOn(hook, 'method').and.callThrough();
Parse.Cloud.beforeFind('Stuff', hook.method);
new Parse.Query('Stuff').find().then((res) => {
expect(res.length).toBe(0);
expect(hook.method).toHaveBeenCalled();
done();
});
});
it('should have request headers', (done) => {
Parse.Cloud.afterFind('MyObject', (req, res) => {
expect(req.headers).toBeDefined();
res.success();
});
const MyObject = Parse.Object.extend('MyObject');
const myObject = new MyObject();
myObject.save()
.then((myObj) => {
const query = new Parse.Query('MyObject');
query.equalTo('objectId', myObj.id);
return Promise.all([
query.get(myObj.id),
query.first(),
query.find(),
]);
})
.then(() => done());
});
it('should have request ip', (done) => {
Parse.Cloud.afterFind('MyObject', (req, res) => {
expect(req.ip).toBeDefined();
res.success();
});
const MyObject = Parse.Object.extend('MyObject');
const myObject = new MyObject();
myObject.save()
.then((myObj) => {
const query = new Parse.Query('MyObject');
query.equalTo('objectId', myObj.id);
return Promise.all([
query.get(myObj.id),
query.first(),
query.find(),
]);
})
.then(() => done());
});
it('should validate triggers correctly', () => {
expect(() => {
Parse.Cloud.beforeSave('_Session', () => {});
}).toThrow('Triggers are not supported for _Session class.');
expect(() => {
Parse.Cloud.afterSave('_Session', () => {});
}).toThrow('Triggers are not supported for _Session class.');
expect(() => {
Parse.Cloud.beforeSave('_PushStatus', () => {});
}).toThrow('Only afterSave is allowed on _PushStatus');
expect(() => {
Parse.Cloud.afterSave('_PushStatus', () => {});
}).not.toThrow();
});
});