'use strict'; // These tests check the "find" functionality of the REST API. const auth = require('../lib/Auth'); const Config = require('../lib/Config'); const rest = require('../lib/rest'); const RestQuery = require('../lib/RestQuery'); const request = require('../lib/request'); const querystring = require('querystring'); let config; let database; const nobody = auth.nobody(config); describe('rest query', () => { beforeEach(() => { config = Config.get('test'); database = config.database; }); it('basic query', done => { rest .create(config, nobody, 'TestObject', {}) .then(() => { return rest.find(config, nobody, 'TestObject', {}); }) .then(response => { expect(response.results.length).toEqual(1); done(); }); }); it('query with limit', done => { rest .create(config, nobody, 'TestObject', { foo: 'baz' }) .then(() => { return rest.create(config, nobody, 'TestObject', { foo: 'qux' }); }) .then(() => { return rest.find(config, nobody, 'TestObject', {}, { limit: 1 }); }) .then(response => { expect(response.results.length).toEqual(1); expect(response.results[0].foo).toBeTruthy(); done(); }); }); const data = { username: 'blah', password: 'pass', sessionToken: 'abc123', }; it_exclude_dbs(['postgres'])( 'query for user w/ legacy credentials without masterKey has them stripped from results', done => { database .create('_User', data) .then(() => { return rest.find(config, nobody, '_User'); }) .then(result => { const user = result.results[0]; expect(user.username).toEqual('blah'); expect(user.sessionToken).toBeUndefined(); expect(user.password).toBeUndefined(); done(); }); } ); it_exclude_dbs(['postgres'])( 'query for user w/ legacy credentials with masterKey has them stripped from results', done => { database .create('_User', data) .then(() => { return rest.find(config, { isMaster: true }, '_User'); }) .then(result => { const user = result.results[0]; expect(user.username).toEqual('blah'); expect(user.sessionToken).toBeUndefined(); expect(user.password).toBeUndefined(); done(); }); } ); // Created to test a scenario in AnyPic it_exclude_dbs(['postgres'])('query with include', done => { let photo = { foo: 'bar', }; let user = { username: 'aUsername', password: 'aPassword', ACL: { '*': { read: true } }, }; const activity = { type: 'comment', photo: { __type: 'Pointer', className: 'TestPhoto', objectId: '', }, fromUser: { __type: 'Pointer', className: '_User', objectId: '', }, }; const queryWhere = { photo: { __type: 'Pointer', className: 'TestPhoto', objectId: '', }, type: 'comment', }; const queryOptions = { include: 'fromUser', order: 'createdAt', limit: 30, }; rest .create(config, nobody, 'TestPhoto', photo) .then(p => { photo = p; return rest.create(config, nobody, '_User', user); }) .then(u => { user = u.response; activity.photo.objectId = photo.objectId; activity.fromUser.objectId = user.objectId; return rest.create(config, nobody, 'TestActivity', activity); }) .then(() => { queryWhere.photo.objectId = photo.objectId; return rest.find(config, nobody, 'TestActivity', queryWhere, queryOptions); }) .then(response => { const results = response.results; expect(results.length).toEqual(1); expect(typeof results[0].objectId).toEqual('string'); expect(typeof results[0].photo).toEqual('object'); expect(typeof results[0].fromUser).toEqual('object'); expect(typeof results[0].fromUser.username).toEqual('string'); done(); }) .catch(error => { console.log(error); }); }); it('query non-existent class when disabled client class creation', done => { const customConfig = Object.assign({}, config, { allowClientClassCreation: false, }); rest.find(customConfig, auth.nobody(customConfig), 'ClientClassCreation', {}).then( () => { fail('Should throw an error'); done(); }, err => { expect(err.code).toEqual(Parse.Error.OPERATION_FORBIDDEN); expect(err.message).toEqual( 'This user is not allowed to access ' + 'non-existent class: ClientClassCreation' ); done(); } ); }); it('query existent class when disabled client class creation', async () => { const customConfig = Object.assign({}, config, { allowClientClassCreation: false, }); const schema = await config.database.loadSchema(); const actualSchema = await schema.addClassIfNotExists('ClientClassCreation', {}); expect(actualSchema.className).toEqual('ClientClassCreation'); await schema.reloadData({ clearCache: true }); // Should not throw const result = await rest.find( customConfig, auth.nobody(customConfig), 'ClientClassCreation', {} ); expect(result.results.length).toEqual(0); }); it('query internal field', async () => { const internalFields = [ '_email_verify_token', '_perishable_token', '_tombstone', '_email_verify_token_expires_at', '_failed_login_count', '_account_lockout_expires_at', '_password_changed_at', '_password_history', ]; await Promise.all([ ...internalFields.map(field => expectAsync(new Parse.Query(Parse.User).exists(field).find()).toBeRejectedWith( new Parse.Error(Parse.Error.INVALID_KEY_NAME, `Invalid key name: ${field}`) ) ), ...internalFields.map(field => new Parse.Query(Parse.User).exists(field).find({ useMasterKey: true }) ), ]); }); it('query protected field', async () => { const user = new Parse.User(); user.setUsername('username1'); user.setPassword('password'); await user.signUp(); const config = Config.get(Parse.applicationId); const obj = new Parse.Object('Test'); obj.set('owner', user); obj.set('test', 'test'); obj.set('zip', 1234); await obj.save(); const schema = await config.database.loadSchema(); await schema.updateClass( 'Test', {}, { get: { '*': true }, find: { '*': true }, protectedFields: { [user.id]: ['zip'] }, } ); await Promise.all([ new Parse.Query('Test').exists('test').find(), expectAsync(new Parse.Query('Test').exists('zip').find()).toBeRejectedWith( new Parse.Error( Parse.Error.OPERATION_FORBIDDEN, 'This user is not allowed to query zip on class Test' ) ), ]); }); it('query protected field with matchesQuery', async () => { const user = new Parse.User(); user.setUsername('username1'); user.setPassword('password'); await user.signUp(); const test = new Parse.Object('TestObject', { user }); await test.save(); const subQuery = new Parse.Query(Parse.User); subQuery.exists('_perishable_token'); await expectAsync( new Parse.Query('TestObject').matchesQuery('user', subQuery).find() ).toBeRejectedWith( new Parse.Error(Parse.Error.INVALID_KEY_NAME, 'Invalid key name: _perishable_token') ); }); it('query with wrongly encoded parameter', done => { rest .create(config, nobody, 'TestParameterEncode', { foo: 'bar' }) .then(() => { return rest.create(config, nobody, 'TestParameterEncode', { foo: 'baz', }); }) .then(() => { const headers = { 'X-Parse-Application-Id': 'test', 'X-Parse-REST-API-Key': 'rest', }; const p0 = request({ headers: headers, url: 'http://localhost:8378/1/classes/TestParameterEncode?' + querystring .stringify({ where: '{"foo":{"$ne": "baz"}}', limit: 1, }) .replace('=', '%3D'), }).then(fail, response => { const error = response.data; expect(error.code).toEqual(Parse.Error.INVALID_QUERY); }); const p1 = request({ headers: headers, url: 'http://localhost:8378/1/classes/TestParameterEncode?' + querystring .stringify({ limit: 1, }) .replace('=', '%3D'), }).then(fail, response => { const error = response.data; expect(error.code).toEqual(Parse.Error.INVALID_QUERY); }); return Promise.all([p0, p1]); }) .then(done) .catch(err => { jfail(err); fail('should not fail'); done(); }); }); it('query with limit = 0', done => { rest .create(config, nobody, 'TestObject', { foo: 'baz' }) .then(() => { return rest.create(config, nobody, 'TestObject', { foo: 'qux' }); }) .then(() => { return rest.find(config, nobody, 'TestObject', {}, { limit: 0 }); }) .then(response => { expect(response.results.length).toEqual(0); done(); }); }); it('query with limit = 0 and count = 1', done => { rest .create(config, nobody, 'TestObject', { foo: 'baz' }) .then(() => { return rest.create(config, nobody, 'TestObject', { foo: 'qux' }); }) .then(() => { return rest.find(config, nobody, 'TestObject', {}, { limit: 0, count: 1 }); }) .then(response => { expect(response.results.length).toEqual(0); expect(response.count).toEqual(2); done(); }); }); it('makes sure null pointers are handed correctly #2189', done => { const object = new Parse.Object('AnObject'); const anotherObject = new Parse.Object('AnotherObject'); anotherObject .save() .then(() => { object.set('values', [null, null, anotherObject]); return object.save(); }) .then(() => { const query = new Parse.Query('AnObject'); query.include('values'); return query.first(); }) .then( result => { const values = result.get('values'); expect(values.length).toBe(3); let anotherObjectFound = false; let nullCounts = 0; for (const value of values) { if (value === null) { nullCounts++; } else if (value instanceof Parse.Object) { anotherObjectFound = true; } } expect(nullCounts).toBe(2); expect(anotherObjectFound).toBeTruthy(); done(); }, err => { console.error(err); fail(err); done(); } ); }); }); describe('RestQuery.each', () => { beforeEach(() => { config = Config.get('test'); }); it('should run each', async () => { const objects = []; while (objects.length != 10) { objects.push(new Parse.Object('Object', { value: objects.length })); } const config = Config.get('test'); await Parse.Object.saveAll(objects); const query = new RestQuery( config, auth.master(config), 'Object', { value: { $gt: 2 } }, { limit: 2 } ); const spy = spyOn(query, 'execute').and.callThrough(); const classSpy = spyOn(RestQuery.prototype, 'execute').and.callThrough(); const results = []; await query.each(result => { expect(result.value).toBeGreaterThan(2); results.push(result); }); expect(spy.calls.count()).toBe(0); expect(classSpy.calls.count()).toBe(4); expect(results.length).toBe(7); }); it('should work with query on relations', async () => { const objectA = new Parse.Object('Letter', { value: 'A' }); const objectB = new Parse.Object('Letter', { value: 'B' }); const object1 = new Parse.Object('Number', { value: '1' }); const object2 = new Parse.Object('Number', { value: '2' }); const object3 = new Parse.Object('Number', { value: '3' }); const object4 = new Parse.Object('Number', { value: '4' }); await Parse.Object.saveAll([object1, object2, object3, object4]); objectA.relation('numbers').add(object1); objectB.relation('numbers').add(object2); await Parse.Object.saveAll([objectA, objectB]); const config = Config.get('test'); /** * Two queries needed since objectId are sorted and we can't know which one * going to be the first and then skip by the $gt added by each */ const queryOne = new RestQuery( config, auth.master(config), 'Letter', { numbers: { __type: 'Pointer', className: 'Number', objectId: object1.id, }, }, { limit: 1 } ); const queryTwo = new RestQuery( config, auth.master(config), 'Letter', { numbers: { __type: 'Pointer', className: 'Number', objectId: object2.id, }, }, { limit: 1 } ); const classSpy = spyOn(RestQuery.prototype, 'execute').and.callThrough(); const resultsOne = []; const resultsTwo = []; await queryOne.each(result => { resultsOne.push(result); }); await queryTwo.each(result => { resultsTwo.push(result); }); expect(classSpy.calls.count()).toBe(4); expect(resultsOne.length).toBe(1); expect(resultsTwo.length).toBe(1); }); it('test afterSave response object is return', done => { Parse.Cloud.beforeSave('TestObject2', function (req) { req.object.set('tobeaddbefore', true); req.object.set('tobeaddbeforeandremoveafter', true); }); Parse.Cloud.afterSave('TestObject2', function (req) { const jsonObject = req.object.toJSON(); delete jsonObject.todelete; delete jsonObject.tobeaddbeforeandremoveafter; jsonObject.toadd = true; return jsonObject; }); rest.create(config, nobody, 'TestObject2', { todelete: true, tokeep: true }).then(response => { expect(response.response.toadd).toBeTruthy(); expect(response.response.tokeep).toBeTruthy(); expect(response.response.tobeaddbefore).toBeTruthy(); expect(response.response.tobeaddbeforeandremoveafter).toBeUndefined(); expect(response.response.todelete).toBeUndefined(); done(); }); }); it('test afterSave should not affect save response', async () => { Parse.Cloud.beforeSave('TestObject2', ({ object }) => { object.set('addedBeforeSave', true); }); Parse.Cloud.afterSave('TestObject2', ({ object }) => { object.set('addedAfterSave', true); object.unset('initialToRemove'); }); const { response } = await rest.create(config, nobody, 'TestObject2', { initialSave: true, initialToRemove: true, }); expect(Object.keys(response).sort()).toEqual([ 'addedAfterSave', 'addedBeforeSave', 'createdAt', 'initialToRemove', 'objectId', ]); }); });