diff --git a/spec/GridFSBucketStorageAdapter.spec.js b/spec/GridFSBucketStorageAdapter.spec.js index c551984596..7baae8d03a 100644 --- a/spec/GridFSBucketStorageAdapter.spec.js +++ b/spec/GridFSBucketStorageAdapter.spec.js @@ -41,16 +41,12 @@ describe('GridFSBucket and GridStore interop', () => { expect(gfsResult.toString('utf8')).toBe(twoMegabytesFile); }); - it( - 'properly deletes a file from GridFS', - async () => { - const gfsAdapter = new GridFSBucketAdapter(databaseURI); - await gfsAdapter.createFile('myFileName', 'a simple file'); - await gfsAdapter.deleteFile('myFileName'); - await expectMissingFile(gfsAdapter, 'myFileName'); - }, - 1000000 - ); + it('properly deletes a file from GridFS', async () => { + const gfsAdapter = new GridFSBucketAdapter(databaseURI); + await gfsAdapter.createFile('myFileName', 'a simple file'); + await gfsAdapter.deleteFile('myFileName'); + await expectMissingFile(gfsAdapter, 'myFileName'); + }, 1000000); it('properly overrides files', async () => { const gfsAdapter = new GridFSBucketAdapter(databaseURI); diff --git a/spec/ReadPreferenceOption.spec.js b/spec/ReadPreferenceOption.spec.js index 89e6e6ee68..dc41100c76 100644 --- a/spec/ReadPreferenceOption.spec.js +++ b/spec/ReadPreferenceOption.spec.js @@ -108,23 +108,67 @@ describe_only_db('mongo')('Read preference option', () => { const query = new Parse.Query('MyObject'); query.equalTo('boolKey', false); - query.find().then(results => { - expect(results.length).toBe(1); - expect(results[0].get('boolKey')).toBe(false); - - let myObjectReadPreference = null; - databaseAdapter.database.serverConfig.cursor.calls - .all() - .forEach(call => { - if (call.args[0].indexOf('MyObject') >= 0) { - myObjectReadPreference = call.args[2].readPreference.preference; - } - }); - - expect(myObjectReadPreference).toEqual(ReadPreference.SECONDARY); - - done(); + query + .find() + .then(results => { + expect(results.length).toBe(1); + expect(results[0].get('boolKey')).toBe(false); + + let myObjectReadPreference = null; + databaseAdapter.database.serverConfig.cursor.calls + .all() + .forEach(call => { + if (call.args[0].indexOf('MyObject') >= 0) { + myObjectReadPreference = call.args[2].readPreference.preference; + } + }); + + expect(myObjectReadPreference).toEqual(ReadPreference.SECONDARY); + + done(); + }) + .catch(done.fail); + }); + }); + + it('should check read preference as case insensitive', done => { + const databaseAdapter = Config.get(Parse.applicationId).database.adapter; + + const obj0 = new Parse.Object('MyObject'); + obj0.set('boolKey', false); + const obj1 = new Parse.Object('MyObject'); + obj1.set('boolKey', true); + + Parse.Object.saveAll([obj0, obj1]).then(() => { + spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough(); + + Parse.Cloud.beforeFind('MyObject', req => { + req.readPreference = 'sEcOnDarY'; }); + + const query = new Parse.Query('MyObject'); + query.equalTo('boolKey', false); + + query + .find() + .then(results => { + expect(results.length).toBe(1); + expect(results[0].get('boolKey')).toBe(false); + + let myObjectReadPreference = null; + databaseAdapter.database.serverConfig.cursor.calls + .all() + .forEach(call => { + if (call.args[0].indexOf('MyObject') >= 0) { + myObjectReadPreference = call.args[2].readPreference.preference; + } + }); + + expect(myObjectReadPreference).toEqual(ReadPreference.SECONDARY); + + done(); + }) + .catch(done.fail); }); }); @@ -147,23 +191,26 @@ describe_only_db('mongo')('Read preference option', () => { const query = new Parse.Query('MyObject'); query.equalTo('boolKey', false); - query.find().then(results => { - expect(results.length).toBe(1); - expect(results[0].get('boolKey')).toBe(true); + query + .find() + .then(results => { + expect(results.length).toBe(1); + expect(results[0].get('boolKey')).toBe(true); - let myObjectReadPreference = null; - databaseAdapter.database.serverConfig.cursor.calls - .all() - .forEach(call => { - if (call.args[0].indexOf('MyObject') >= 0) { - myObjectReadPreference = call.args[2].readPreference.preference; - } - }); + let myObjectReadPreference = null; + databaseAdapter.database.serverConfig.cursor.calls + .all() + .forEach(call => { + if (call.args[0].indexOf('MyObject') >= 0) { + myObjectReadPreference = call.args[2].readPreference.preference; + } + }); - expect(myObjectReadPreference).toEqual(ReadPreference.SECONDARY); + expect(myObjectReadPreference).toEqual(ReadPreference.SECONDARY); - done(); - }); + done(); + }) + .catch(done.fail); }); }); @@ -189,23 +236,26 @@ describe_only_db('mongo')('Read preference option', () => { const query = new Parse.Query('MyObject'); query.equalTo('boolKey', false); - query.find().then(results => { - expect(results.length).toBe(1); - expect(results[0].get('boolKey')).toBe(true); + query + .find() + .then(results => { + expect(results.length).toBe(1); + expect(results[0].get('boolKey')).toBe(true); - let myObjectReadPreference = null; - databaseAdapter.database.serverConfig.cursor.calls - .all() - .forEach(call => { - if (call.args[0].indexOf('MyObject') >= 0) { - myObjectReadPreference = call.args[2].readPreference.preference; - } - }); + let myObjectReadPreference = null; + databaseAdapter.database.serverConfig.cursor.calls + .all() + .forEach(call => { + if (call.args[0].indexOf('MyObject') >= 0) { + myObjectReadPreference = call.args[2].readPreference.preference; + } + }); - expect(myObjectReadPreference).toEqual(ReadPreference.SECONDARY); + expect(myObjectReadPreference).toEqual(ReadPreference.SECONDARY); - done(); - }); + done(); + }) + .catch(done.fail); }); }); @@ -231,23 +281,26 @@ describe_only_db('mongo')('Read preference option', () => { const query = new Parse.Query('MyObject'); query.equalTo('boolKey', false); - query.find().then(results => { - expect(results.length).toBe(1); - expect(results[0].get('boolKey')).toBe(true); + query + .find() + .then(results => { + expect(results.length).toBe(1); + expect(results[0].get('boolKey')).toBe(true); - let myObjectReadPreference = null; - databaseAdapter.database.serverConfig.cursor.calls - .all() - .forEach(call => { - if (call.args[0].indexOf('MyObject') >= 0) { - myObjectReadPreference = call.args[2].readPreference.preference; - } - }); + let myObjectReadPreference = null; + databaseAdapter.database.serverConfig.cursor.calls + .all() + .forEach(call => { + if (call.args[0].indexOf('MyObject') >= 0) { + myObjectReadPreference = call.args[2].readPreference.preference; + } + }); - expect(myObjectReadPreference).toEqual(ReadPreference.SECONDARY); + expect(myObjectReadPreference).toEqual(ReadPreference.SECONDARY); - done(); - }); + done(); + }) + .catch(done.fail); }); }); @@ -269,25 +322,28 @@ describe_only_db('mongo')('Read preference option', () => { const query = new Parse.Query('MyObject'); query.equalTo('boolKey', false); - query.find().then(results => { - expect(results.length).toBe(1); - expect(results[0].get('boolKey')).toBe(false); - - let myObjectReadPreference = null; - databaseAdapter.database.serverConfig.cursor.calls - .all() - .forEach(call => { - if (call.args[0].indexOf('MyObject') >= 0) { - myObjectReadPreference = call.args[2].readPreference.preference; - } - }); - - expect(myObjectReadPreference).toEqual( - ReadPreference.PRIMARY_PREFERRED - ); + query + .find() + .then(results => { + expect(results.length).toBe(1); + expect(results[0].get('boolKey')).toBe(false); - done(); - }); + let myObjectReadPreference = null; + databaseAdapter.database.serverConfig.cursor.calls + .all() + .forEach(call => { + if (call.args[0].indexOf('MyObject') >= 0) { + myObjectReadPreference = call.args[2].readPreference.preference; + } + }); + + expect(myObjectReadPreference).toEqual( + ReadPreference.PRIMARY_PREFERRED + ); + + done(); + }) + .catch(done.fail); }); }); @@ -309,25 +365,28 @@ describe_only_db('mongo')('Read preference option', () => { const query = new Parse.Query('MyObject'); query.equalTo('boolKey', false); - query.find().then(results => { - expect(results.length).toBe(1); - expect(results[0].get('boolKey')).toBe(false); - - let myObjectReadPreference = null; - databaseAdapter.database.serverConfig.cursor.calls - .all() - .forEach(call => { - if (call.args[0].indexOf('MyObject') >= 0) { - myObjectReadPreference = call.args[2].readPreference.preference; - } - }); - - expect(myObjectReadPreference).toEqual( - ReadPreference.SECONDARY_PREFERRED - ); + query + .find() + .then(results => { + expect(results.length).toBe(1); + expect(results[0].get('boolKey')).toBe(false); - done(); - }); + let myObjectReadPreference = null; + databaseAdapter.database.serverConfig.cursor.calls + .all() + .forEach(call => { + if (call.args[0].indexOf('MyObject') >= 0) { + myObjectReadPreference = call.args[2].readPreference.preference; + } + }); + + expect(myObjectReadPreference).toEqual( + ReadPreference.SECONDARY_PREFERRED + ); + + done(); + }) + .catch(done.fail); }); }); @@ -349,23 +408,26 @@ describe_only_db('mongo')('Read preference option', () => { const query = new Parse.Query('MyObject'); query.equalTo('boolKey', false); - query.find().then(results => { - expect(results.length).toBe(1); - expect(results[0].get('boolKey')).toBe(false); + query + .find() + .then(results => { + expect(results.length).toBe(1); + expect(results[0].get('boolKey')).toBe(false); - let myObjectReadPreference = null; - databaseAdapter.database.serverConfig.cursor.calls - .all() - .forEach(call => { - if (call.args[0].indexOf('MyObject') >= 0) { - myObjectReadPreference = call.args[2].readPreference.preference; - } - }); + let myObjectReadPreference = null; + databaseAdapter.database.serverConfig.cursor.calls + .all() + .forEach(call => { + if (call.args[0].indexOf('MyObject') >= 0) { + myObjectReadPreference = call.args[2].readPreference.preference; + } + }); - expect(myObjectReadPreference).toEqual(ReadPreference.NEAREST); + expect(myObjectReadPreference).toEqual(ReadPreference.NEAREST); - done(); - }); + done(); + }) + .catch(done.fail); }); }); @@ -386,22 +448,25 @@ describe_only_db('mongo')('Read preference option', () => { const query = new Parse.Query('MyObject'); - query.get(obj0.id).then(result => { - expect(result.get('boolKey')).toBe(false); + query + .get(obj0.id) + .then(result => { + expect(result.get('boolKey')).toBe(false); - let myObjectReadPreference = null; - databaseAdapter.database.serverConfig.cursor.calls - .all() - .forEach(call => { - if (call.args[0].indexOf('MyObject') >= 0) { - myObjectReadPreference = call.args[2].readPreference.preference; - } - }); + let myObjectReadPreference = null; + databaseAdapter.database.serverConfig.cursor.calls + .all() + .forEach(call => { + if (call.args[0].indexOf('MyObject') >= 0) { + myObjectReadPreference = call.args[2].readPreference.preference; + } + }); - expect(myObjectReadPreference).toEqual(ReadPreference.SECONDARY); + expect(myObjectReadPreference).toEqual(ReadPreference.SECONDARY); - done(); - }); + done(); + }) + .catch(done.fail); }); }); @@ -428,23 +493,249 @@ describe_only_db('mongo')('Read preference option', () => { 'X-Parse-REST-API-Key': 'rest', }, json: true, - }).then(response => { - const body = response.data; - expect(body.boolKey).toBe(false); - - let myObjectReadPreference = null; - databaseAdapter.database.serverConfig.cursor.calls - .all() - .forEach(call => { - if (call.args[0].indexOf('MyObject') >= 0) { - myObjectReadPreference = call.args[2].readPreference.preference; - } - }); - - expect(myObjectReadPreference).toEqual(ReadPreference.SECONDARY); - - done(); + }) + .then(response => { + const body = response.data; + expect(body.boolKey).toBe(false); + + let myObjectReadPreference = null; + databaseAdapter.database.serverConfig.cursor.calls + .all() + .forEach(call => { + if (call.args[0].indexOf('MyObject') >= 0) { + myObjectReadPreference = call.args[2].readPreference.preference; + } + }); + + expect(myObjectReadPreference).toEqual(ReadPreference.SECONDARY); + + done(); + }) + .catch(done.fail); + }); + }); + + it('should change read preference for GET directly from API', done => { + const databaseAdapter = Config.get(Parse.applicationId).database.adapter; + + const obj0 = new Parse.Object('MyObject'); + obj0.set('boolKey', false); + const obj1 = new Parse.Object('MyObject'); + obj1.set('boolKey', true); + + Parse.Object.saveAll([obj0, obj1]).then(() => { + spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough(); + + request({ + method: 'GET', + url: + 'http://localhost:8378/1/classes/MyObject/' + + obj0.id + + '?readPreference=SECONDARY', + headers: { + 'X-Parse-Application-Id': 'test', + 'X-Parse-REST-API-Key': 'rest', + }, + json: true, + }) + .then(response => { + expect(response.data.boolKey).toBe(false); + + let myObjectReadPreference = null; + databaseAdapter.database.serverConfig.cursor.calls + .all() + .forEach(call => { + if (call.args[0].indexOf('MyObject') >= 0) { + myObjectReadPreference = call.args[2].readPreference.preference; + } + }); + + expect(myObjectReadPreference).toEqual(ReadPreference.SECONDARY); + + done(); + }) + .catch(done.fail); + }); + }); + + it('should change read preference for GET using API through the beforeFind overriding API option', done => { + const databaseAdapter = Config.get(Parse.applicationId).database.adapter; + + const obj0 = new Parse.Object('MyObject'); + obj0.set('boolKey', false); + const obj1 = new Parse.Object('MyObject'); + obj1.set('boolKey', true); + + Parse.Object.saveAll([obj0, obj1]).then(() => { + spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough(); + + Parse.Cloud.beforeFind('MyObject', req => { + req.readPreference = 'SECONDARY_PREFERRED'; }); + + request({ + method: 'GET', + url: + 'http://localhost:8378/1/classes/MyObject/' + + obj0.id + + '?readPreference=SECONDARY', + headers: { + 'X-Parse-Application-Id': 'test', + 'X-Parse-REST-API-Key': 'rest', + }, + json: true, + }) + .then(response => { + expect(response.data.boolKey).toBe(false); + + let myObjectReadPreference = null; + databaseAdapter.database.serverConfig.cursor.calls + .all() + .forEach(call => { + if (call.args[0].indexOf('MyObject') >= 0) { + myObjectReadPreference = call.args[2].readPreference.preference; + } + }); + + expect(myObjectReadPreference).toEqual( + ReadPreference.SECONDARY_PREFERRED + ); + + done(); + }) + .catch(done.fail); + }); + }); + + it('should change read preference for FIND using API through beforeFind trigger', done => { + const databaseAdapter = Config.get(Parse.applicationId).database.adapter; + + const obj0 = new Parse.Object('MyObject'); + obj0.set('boolKey', false); + const obj1 = new Parse.Object('MyObject'); + obj1.set('boolKey', true); + + Parse.Object.saveAll([obj0, obj1]).then(() => { + spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough(); + + Parse.Cloud.beforeFind('MyObject', req => { + req.readPreference = 'SECONDARY'; + }); + + request({ + method: 'GET', + url: 'http://localhost:8378/1/classes/MyObject/', + headers: { + 'X-Parse-Application-Id': 'test', + 'X-Parse-REST-API-Key': 'rest', + }, + json: true, + }) + .then(response => { + expect(response.data.results.length).toEqual(2); + + let myObjectReadPreference = null; + databaseAdapter.database.serverConfig.cursor.calls + .all() + .forEach(call => { + if (call.args[0].indexOf('MyObject') >= 0) { + myObjectReadPreference = call.args[2].readPreference.preference; + } + }); + + expect(myObjectReadPreference).toEqual(ReadPreference.SECONDARY); + + done(); + }) + .catch(done.fail); + }); + }); + + it('should change read preference for FIND directly from API', done => { + const databaseAdapter = Config.get(Parse.applicationId).database.adapter; + + const obj0 = new Parse.Object('MyObject'); + obj0.set('boolKey', false); + const obj1 = new Parse.Object('MyObject'); + obj1.set('boolKey', true); + + Parse.Object.saveAll([obj0, obj1]).then(() => { + spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough(); + + request({ + method: 'GET', + url: + 'http://localhost:8378/1/classes/MyObject?readPreference=SECONDARY', + headers: { + 'X-Parse-Application-Id': 'test', + 'X-Parse-REST-API-Key': 'rest', + }, + json: true, + }) + .then(response => { + expect(response.data.results.length).toEqual(2); + + let myObjectReadPreference = null; + databaseAdapter.database.serverConfig.cursor.calls + .all() + .forEach(call => { + if (call.args[0].indexOf('MyObject') >= 0) { + myObjectReadPreference = call.args[2].readPreference.preference; + } + }); + + expect(myObjectReadPreference).toEqual(ReadPreference.SECONDARY); + + done(); + }) + .catch(done.fail); + }); + }); + + it('should change read preference for FIND using API through the beforeFind overriding API option', done => { + const databaseAdapter = Config.get(Parse.applicationId).database.adapter; + + const obj0 = new Parse.Object('MyObject'); + obj0.set('boolKey', false); + const obj1 = new Parse.Object('MyObject'); + obj1.set('boolKey', true); + + Parse.Object.saveAll([obj0, obj1]).then(() => { + spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough(); + + Parse.Cloud.beforeFind('MyObject', req => { + req.readPreference = 'SECONDARY_PREFERRED'; + }); + + request({ + method: 'GET', + url: + 'http://localhost:8378/1/classes/MyObject/?readPreference=SECONDARY', + headers: { + 'X-Parse-Application-Id': 'test', + 'X-Parse-REST-API-Key': 'rest', + }, + json: true, + }) + .then(response => { + expect(response.data.results.length).toEqual(2); + + let myObjectReadPreference = null; + databaseAdapter.database.serverConfig.cursor.calls + .all() + .forEach(call => { + if (call.args[0].indexOf('MyObject') >= 0) { + myObjectReadPreference = call.args[2].readPreference.preference; + } + }); + + expect(myObjectReadPreference).toEqual( + ReadPreference.SECONDARY_PREFERRED + ); + + done(); + }) + .catch(done.fail); }); }); @@ -466,26 +757,29 @@ describe_only_db('mongo')('Read preference option', () => { const query = new Parse.Query('MyObject'); query.equalTo('boolKey', false); - query.count().then(result => { - expect(result).toBe(1); + query + .count() + .then(result => { + expect(result).toBe(1); - let myObjectReadPreference = null; - databaseAdapter.database.serverConfig.cursor.calls - .all() - .forEach(call => { - if (call.args[0].indexOf('MyObject') >= 0) { - myObjectReadPreference = call.args[2].readPreference.preference; - } - }); + let myObjectReadPreference = null; + databaseAdapter.database.serverConfig.cursor.calls + .all() + .forEach(call => { + if (call.args[0].indexOf('MyObject') >= 0) { + myObjectReadPreference = call.args[2].readPreference.preference; + } + }); - expect(myObjectReadPreference).toEqual(ReadPreference.SECONDARY); + expect(myObjectReadPreference).toEqual(ReadPreference.SECONDARY); - done(); - }); + done(); + }) + .catch(done.fail); }); }); - it('should find includes in primary by default', done => { + it('should find includes in same replica of readPreference by default', done => { const databaseAdapter = Config.get(Parse.applicationId).database.adapter; const obj0 = new Parse.Object('MyObject0'); @@ -509,43 +803,47 @@ describe_only_db('mongo')('Read preference option', () => { query.include('myObject1'); query.include('myObject1.myObject0'); - query.find().then(results => { - expect(results.length).toBe(1); - const firstResult = results[0]; - expect(firstResult.get('boolKey')).toBe(false); - expect(firstResult.get('myObject1').get('boolKey')).toBe(true); - expect( - firstResult - .get('myObject1') - .get('myObject0') - .get('boolKey') - ).toBe(false); - - let myObjectReadPreference0 = null; - let myObjectReadPreference1 = null; - let myObjectReadPreference2 = null; - databaseAdapter.database.serverConfig.cursor.calls - .all() - .forEach(call => { - if (call.args[0].indexOf('MyObject0') >= 0) { - myObjectReadPreference0 = true; - expect(call.args[2].readPreference).toBe(null); - } - if (call.args[0].indexOf('MyObject1') >= 0) { - myObjectReadPreference1 = true; - expect(call.args[2].readPreference).toBe(null); - } - if (call.args[0].indexOf('MyObject2') >= 0) { - myObjectReadPreference2 = call.args[2].readPreference.preference; - } - }); - - expect(myObjectReadPreference0).toBe(true); - expect(myObjectReadPreference1).toBe(true); - expect(myObjectReadPreference2).toEqual(ReadPreference.SECONDARY); - - done(); - }); + query + .find() + .then(results => { + expect(results.length).toBe(1); + const firstResult = results[0]; + expect(firstResult.get('boolKey')).toBe(false); + expect(firstResult.get('myObject1').get('boolKey')).toBe(true); + expect( + firstResult + .get('myObject1') + .get('myObject0') + .get('boolKey') + ).toBe(false); + + let myObjectReadPreference0 = null; + let myObjectReadPreference1 = null; + let myObjectReadPreference2 = null; + databaseAdapter.database.serverConfig.cursor.calls + .all() + .forEach(call => { + if (call.args[0].indexOf('MyObject0') >= 0) { + myObjectReadPreference0 = + call.args[2].readPreference.preference; + } + if (call.args[0].indexOf('MyObject1') >= 0) { + myObjectReadPreference1 = + call.args[2].readPreference.preference; + } + if (call.args[0].indexOf('MyObject2') >= 0) { + myObjectReadPreference2 = + call.args[2].readPreference.preference; + } + }); + + expect(myObjectReadPreference0).toEqual(ReadPreference.SECONDARY); + expect(myObjectReadPreference1).toEqual(ReadPreference.SECONDARY); + expect(myObjectReadPreference2).toEqual(ReadPreference.SECONDARY); + + done(); + }) + .catch(done.fail); }); }); @@ -574,47 +872,188 @@ describe_only_db('mongo')('Read preference option', () => { query.include('myObject1'); query.include('myObject1.myObject0'); - query.find().then(results => { - expect(results.length).toBe(1); - const firstResult = results[0]; - expect(firstResult.get('boolKey')).toBe(false); - expect(firstResult.get('myObject1').get('boolKey')).toBe(true); - expect( - firstResult - .get('myObject1') - .get('myObject0') - .get('boolKey') - ).toBe(false); - - let myObjectReadPreference0 = null; - let myObjectReadPreference1 = null; - let myObjectReadPreference2 = null; - databaseAdapter.database.serverConfig.cursor.calls - .all() - .forEach(call => { - if (call.args[0].indexOf('MyObject0') >= 0) { - myObjectReadPreference0 = call.args[2].readPreference.preference; - } - if (call.args[0].indexOf('MyObject1') >= 0) { - myObjectReadPreference1 = call.args[2].readPreference.preference; - } - if (call.args[0].indexOf('MyObject2') >= 0) { - myObjectReadPreference2 = call.args[2].readPreference.preference; - } - }); - - expect(myObjectReadPreference0).toEqual(ReadPreference.SECONDARY); - expect(myObjectReadPreference1).toEqual(ReadPreference.SECONDARY); - expect(myObjectReadPreference2).toEqual( - ReadPreference.SECONDARY_PREFERRED - ); + query + .find() + .then(results => { + expect(results.length).toBe(1); + const firstResult = results[0]; + expect(firstResult.get('boolKey')).toBe(false); + expect(firstResult.get('myObject1').get('boolKey')).toBe(true); + expect( + firstResult + .get('myObject1') + .get('myObject0') + .get('boolKey') + ).toBe(false); + + let myObjectReadPreference0 = null; + let myObjectReadPreference1 = null; + let myObjectReadPreference2 = null; + databaseAdapter.database.serverConfig.cursor.calls + .all() + .forEach(call => { + if (call.args[0].indexOf('MyObject0') >= 0) { + myObjectReadPreference0 = + call.args[2].readPreference.preference; + } + if (call.args[0].indexOf('MyObject1') >= 0) { + myObjectReadPreference1 = + call.args[2].readPreference.preference; + } + if (call.args[0].indexOf('MyObject2') >= 0) { + myObjectReadPreference2 = + call.args[2].readPreference.preference; + } + }); - done(); - }); + expect(myObjectReadPreference0).toEqual(ReadPreference.SECONDARY); + expect(myObjectReadPreference1).toEqual(ReadPreference.SECONDARY); + expect(myObjectReadPreference2).toEqual( + ReadPreference.SECONDARY_PREFERRED + ); + + done(); + }) + .catch(done.fail); + }); + }); + + it('should change includes read preference when finding through API', done => { + const databaseAdapter = Config.get(Parse.applicationId).database.adapter; + + const obj0 = new Parse.Object('MyObject0'); + obj0.set('boolKey', false); + const obj1 = new Parse.Object('MyObject1'); + obj1.set('boolKey', true); + obj1.set('myObject0', obj0); + const obj2 = new Parse.Object('MyObject2'); + obj2.set('boolKey', false); + obj2.set('myObject1', obj1); + + Parse.Object.saveAll([obj0, obj1, obj2]).then(() => { + spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough(); + + request({ + method: 'GET', + url: + 'http://localhost:8378/1/classes/MyObject2/' + + obj2.id + + '?include=' + + JSON.stringify(['myObject1', 'myObject1.myObject0']) + + '&readPreference=SECONDARY_PREFERRED&includeReadPreference=SECONDARY', + headers: { + 'X-Parse-Application-Id': 'test', + 'X-Parse-REST-API-Key': 'rest', + }, + json: true, + }) + .then(response => { + const firstResult = response.data; + expect(firstResult.boolKey).toBe(false); + expect(firstResult.myObject1.boolKey).toBe(true); + expect(firstResult.myObject1.myObject0.boolKey).toBe(false); + + let myObjectReadPreference0 = null; + let myObjectReadPreference1 = null; + let myObjectReadPreference2 = null; + databaseAdapter.database.serverConfig.cursor.calls + .all() + .forEach(call => { + if (call.args[0].indexOf('MyObject0') >= 0) { + myObjectReadPreference0 = + call.args[2].readPreference.preference; + } + if (call.args[0].indexOf('MyObject1') >= 0) { + myObjectReadPreference1 = + call.args[2].readPreference.preference; + } + if (call.args[0].indexOf('MyObject2') >= 0) { + myObjectReadPreference2 = + call.args[2].readPreference.preference; + } + }); + + expect(myObjectReadPreference0).toEqual(ReadPreference.SECONDARY); + expect(myObjectReadPreference1).toEqual(ReadPreference.SECONDARY); + expect(myObjectReadPreference2).toEqual( + ReadPreference.SECONDARY_PREFERRED + ); + + done(); + }) + .catch(done.fail); + }); + }); + + it('should change includes read preference when getting through API', done => { + const databaseAdapter = Config.get(Parse.applicationId).database.adapter; + + const obj0 = new Parse.Object('MyObject0'); + obj0.set('boolKey', false); + const obj1 = new Parse.Object('MyObject1'); + obj1.set('boolKey', true); + obj1.set('myObject0', obj0); + const obj2 = new Parse.Object('MyObject2'); + obj2.set('boolKey', false); + obj2.set('myObject1', obj1); + + Parse.Object.saveAll([obj0, obj1, obj2]).then(() => { + spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough(); + + request({ + method: 'GET', + url: + 'http://localhost:8378/1/classes/MyObject2?where=' + + JSON.stringify({ boolKey: false }) + + '&include=' + + JSON.stringify(['myObject1', 'myObject1.myObject0']) + + '&readPreference=SECONDARY_PREFERRED&includeReadPreference=SECONDARY', + headers: { + 'X-Parse-Application-Id': 'test', + 'X-Parse-REST-API-Key': 'rest', + }, + json: true, + }) + .then(response => { + expect(response.data.results.length).toBe(1); + const firstResult = response.data.results[0]; + expect(firstResult.boolKey).toBe(false); + expect(firstResult.myObject1.boolKey).toBe(true); + expect(firstResult.myObject1.myObject0.boolKey).toBe(false); + + let myObjectReadPreference0 = null; + let myObjectReadPreference1 = null; + let myObjectReadPreference2 = null; + databaseAdapter.database.serverConfig.cursor.calls + .all() + .forEach(call => { + if (call.args[0].indexOf('MyObject0') >= 0) { + myObjectReadPreference0 = + call.args[2].readPreference.preference; + } + if (call.args[0].indexOf('MyObject1') >= 0) { + myObjectReadPreference1 = + call.args[2].readPreference.preference; + } + if (call.args[0].indexOf('MyObject2') >= 0) { + myObjectReadPreference2 = + call.args[2].readPreference.preference; + } + }); + + expect(myObjectReadPreference0).toEqual(ReadPreference.SECONDARY); + expect(myObjectReadPreference1).toEqual(ReadPreference.SECONDARY); + expect(myObjectReadPreference2).toEqual( + ReadPreference.SECONDARY_PREFERRED + ); + + done(); + }) + .catch(done.fail); }); }); - it('should find subqueries in primary by default', done => { + it('should find subqueries in same replica of readPreference by default', done => { const databaseAdapter = Config.get(Parse.applicationId).database.adapter; const obj0 = new Parse.Object('MyObject0'); @@ -642,35 +1081,39 @@ describe_only_db('mongo')('Read preference option', () => { const query2 = new Parse.Query('MyObject2'); query2.matchesQuery('myObject1', query1); - query2.find().then(results => { - expect(results.length).toBe(1); - expect(results[0].get('boolKey')).toBe(false); - - let myObjectReadPreference0 = null; - let myObjectReadPreference1 = null; - let myObjectReadPreference2 = null; - databaseAdapter.database.serverConfig.cursor.calls - .all() - .forEach(call => { - if (call.args[0].indexOf('MyObject0') >= 0) { - myObjectReadPreference0 = true; - expect(call.args[2].readPreference).toBe(null); - } - if (call.args[0].indexOf('MyObject1') >= 0) { - myObjectReadPreference1 = true; - expect(call.args[2].readPreference).toBe(null); - } - if (call.args[0].indexOf('MyObject2') >= 0) { - myObjectReadPreference2 = call.args[2].readPreference.preference; - } - }); - - expect(myObjectReadPreference0).toBe(true); - expect(myObjectReadPreference1).toBe(true); - expect(myObjectReadPreference2).toEqual(ReadPreference.SECONDARY); - - done(); - }); + query2 + .find() + .then(results => { + expect(results.length).toBe(1); + expect(results[0].get('boolKey')).toBe(false); + + let myObjectReadPreference0 = null; + let myObjectReadPreference1 = null; + let myObjectReadPreference2 = null; + databaseAdapter.database.serverConfig.cursor.calls + .all() + .forEach(call => { + if (call.args[0].indexOf('MyObject0') >= 0) { + myObjectReadPreference0 = + call.args[2].readPreference.preference; + } + if (call.args[0].indexOf('MyObject1') >= 0) { + myObjectReadPreference1 = + call.args[2].readPreference.preference; + } + if (call.args[0].indexOf('MyObject2') >= 0) { + myObjectReadPreference2 = + call.args[2].readPreference.preference; + } + }); + + expect(myObjectReadPreference0).toEqual(ReadPreference.SECONDARY); + expect(myObjectReadPreference1).toEqual(ReadPreference.SECONDARY); + expect(myObjectReadPreference2).toEqual(ReadPreference.SECONDARY); + + done(); + }) + .catch(done.fail); }); }); @@ -703,35 +1146,41 @@ describe_only_db('mongo')('Read preference option', () => { const query2 = new Parse.Query('MyObject2'); query2.matchesQuery('myObject1', query1); - query2.find().then(results => { - expect(results.length).toBe(1); - expect(results[0].get('boolKey')).toBe(false); - - let myObjectReadPreference0 = null; - let myObjectReadPreference1 = null; - let myObjectReadPreference2 = null; - databaseAdapter.database.serverConfig.cursor.calls - .all() - .forEach(call => { - if (call.args[0].indexOf('MyObject0') >= 0) { - myObjectReadPreference0 = call.args[2].readPreference.preference; - } - if (call.args[0].indexOf('MyObject1') >= 0) { - myObjectReadPreference1 = call.args[2].readPreference.preference; - } - if (call.args[0].indexOf('MyObject2') >= 0) { - myObjectReadPreference2 = call.args[2].readPreference.preference; - } - }); - - expect(myObjectReadPreference0).toEqual(ReadPreference.SECONDARY); - expect(myObjectReadPreference1).toEqual(ReadPreference.SECONDARY); - expect(myObjectReadPreference2).toEqual( - ReadPreference.SECONDARY_PREFERRED - ); + query2 + .find() + .then(results => { + expect(results.length).toBe(1); + expect(results[0].get('boolKey')).toBe(false); - done(); - }); + let myObjectReadPreference0 = null; + let myObjectReadPreference1 = null; + let myObjectReadPreference2 = null; + databaseAdapter.database.serverConfig.cursor.calls + .all() + .forEach(call => { + if (call.args[0].indexOf('MyObject0') >= 0) { + myObjectReadPreference0 = + call.args[2].readPreference.preference; + } + if (call.args[0].indexOf('MyObject1') >= 0) { + myObjectReadPreference1 = + call.args[2].readPreference.preference; + } + if (call.args[0].indexOf('MyObject2') >= 0) { + myObjectReadPreference2 = + call.args[2].readPreference.preference; + } + }); + + expect(myObjectReadPreference0).toEqual(ReadPreference.SECONDARY); + expect(myObjectReadPreference1).toEqual(ReadPreference.SECONDARY); + expect(myObjectReadPreference2).toEqual( + ReadPreference.SECONDARY_PREFERRED + ); + + done(); + }) + .catch(done.fail); }); }); @@ -764,35 +1213,41 @@ describe_only_db('mongo')('Read preference option', () => { const query2 = new Parse.Query('MyObject2'); query2.doesNotMatchQuery('myObject1', query1); - query2.find().then(results => { - expect(results.length).toBe(1); - expect(results[0].get('boolKey')).toBe(false); - - let myObjectReadPreference0 = null; - let myObjectReadPreference1 = null; - let myObjectReadPreference2 = null; - databaseAdapter.database.serverConfig.cursor.calls - .all() - .forEach(call => { - if (call.args[0].indexOf('MyObject0') >= 0) { - myObjectReadPreference0 = call.args[2].readPreference.preference; - } - if (call.args[0].indexOf('MyObject1') >= 0) { - myObjectReadPreference1 = call.args[2].readPreference.preference; - } - if (call.args[0].indexOf('MyObject2') >= 0) { - myObjectReadPreference2 = call.args[2].readPreference.preference; - } - }); - - expect(myObjectReadPreference0).toEqual(ReadPreference.SECONDARY); - expect(myObjectReadPreference1).toEqual(ReadPreference.SECONDARY); - expect(myObjectReadPreference2).toEqual( - ReadPreference.SECONDARY_PREFERRED - ); + query2 + .find() + .then(results => { + expect(results.length).toBe(1); + expect(results[0].get('boolKey')).toBe(false); - done(); - }); + let myObjectReadPreference0 = null; + let myObjectReadPreference1 = null; + let myObjectReadPreference2 = null; + databaseAdapter.database.serverConfig.cursor.calls + .all() + .forEach(call => { + if (call.args[0].indexOf('MyObject0') >= 0) { + myObjectReadPreference0 = + call.args[2].readPreference.preference; + } + if (call.args[0].indexOf('MyObject1') >= 0) { + myObjectReadPreference1 = + call.args[2].readPreference.preference; + } + if (call.args[0].indexOf('MyObject2') >= 0) { + myObjectReadPreference2 = + call.args[2].readPreference.preference; + } + }); + + expect(myObjectReadPreference0).toEqual(ReadPreference.SECONDARY); + expect(myObjectReadPreference1).toEqual(ReadPreference.SECONDARY); + expect(myObjectReadPreference2).toEqual( + ReadPreference.SECONDARY_PREFERRED + ); + + done(); + }) + .catch(done.fail); }); }); @@ -826,35 +1281,123 @@ describe_only_db('mongo')('Read preference option', () => { query2.matchesKeyInQuery('boolKey', 'boolKey', query0); query2.doesNotMatchKeyInQuery('boolKey', 'boolKey', query1); - query2.find().then(results => { - expect(results.length).toBe(1); - expect(results[0].get('boolKey')).toBe(false); - - let myObjectReadPreference0 = null; - let myObjectReadPreference1 = null; - let myObjectReadPreference2 = null; - databaseAdapter.database.serverConfig.cursor.calls - .all() - .forEach(call => { - if (call.args[0].indexOf('MyObject0') >= 0) { - myObjectReadPreference0 = call.args[2].readPreference.preference; - } - if (call.args[0].indexOf('MyObject1') >= 0) { - myObjectReadPreference1 = call.args[2].readPreference.preference; - } - if (call.args[0].indexOf('MyObject2') >= 0) { - myObjectReadPreference2 = call.args[2].readPreference.preference; - } - }); - - expect(myObjectReadPreference0).toEqual(ReadPreference.SECONDARY); - expect(myObjectReadPreference1).toEqual(ReadPreference.SECONDARY); - expect(myObjectReadPreference2).toEqual( - ReadPreference.SECONDARY_PREFERRED - ); + query2 + .find() + .then(results => { + expect(results.length).toBe(1); + expect(results[0].get('boolKey')).toBe(false); + + let myObjectReadPreference0 = null; + let myObjectReadPreference1 = null; + let myObjectReadPreference2 = null; + databaseAdapter.database.serverConfig.cursor.calls + .all() + .forEach(call => { + if (call.args[0].indexOf('MyObject0') >= 0) { + myObjectReadPreference0 = + call.args[2].readPreference.preference; + } + if (call.args[0].indexOf('MyObject1') >= 0) { + myObjectReadPreference1 = + call.args[2].readPreference.preference; + } + if (call.args[0].indexOf('MyObject2') >= 0) { + myObjectReadPreference2 = + call.args[2].readPreference.preference; + } + }); + + expect(myObjectReadPreference0).toEqual(ReadPreference.SECONDARY); + expect(myObjectReadPreference1).toEqual(ReadPreference.SECONDARY); + expect(myObjectReadPreference2).toEqual( + ReadPreference.SECONDARY_PREFERRED + ); + + done(); + }) + .catch(done.fail); + }); + }); + + it('should change subqueries read preference when using matchesKeyInQuery and doesNotMatchKeyInQuery to find through API', done => { + const databaseAdapter = Config.get(Parse.applicationId).database.adapter; + + const obj0 = new Parse.Object('MyObject0'); + obj0.set('boolKey', false); + const obj1 = new Parse.Object('MyObject1'); + obj1.set('boolKey', true); + obj1.set('myObject0', obj0); + const obj2 = new Parse.Object('MyObject2'); + obj2.set('boolKey', false); + obj2.set('myObject1', obj1); + + Parse.Object.saveAll([obj0, obj1, obj2]).then(() => { + spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough(); - done(); + const whereString = JSON.stringify({ + boolKey: { + $select: { + query: { + className: 'MyObject0', + where: { boolKey: false }, + }, + key: 'boolKey', + }, + $dontSelect: { + query: { + className: 'MyObject1', + where: { boolKey: true }, + }, + key: 'boolKey', + }, + }, }); + + request({ + method: 'GET', + url: + 'http://localhost:8378/1/classes/MyObject2/?where=' + + whereString + + '&readPreference=SECONDARY_PREFERRED&subqueryReadPreference=SECONDARY', + headers: { + 'X-Parse-Application-Id': 'test', + 'X-Parse-REST-API-Key': 'rest', + }, + json: true, + }) + .then(response => { + expect(response.data.results.length).toBe(1); + expect(response.data.results[0].boolKey).toBe(false); + + let myObjectReadPreference0 = null; + let myObjectReadPreference1 = null; + let myObjectReadPreference2 = null; + databaseAdapter.database.serverConfig.cursor.calls + .all() + .forEach(call => { + if (call.args[0].indexOf('MyObject0') >= 0) { + myObjectReadPreference0 = + call.args[2].readPreference.preference; + } + if (call.args[0].indexOf('MyObject1') >= 0) { + myObjectReadPreference1 = + call.args[2].readPreference.preference; + } + if (call.args[0].indexOf('MyObject2') >= 0) { + myObjectReadPreference2 = + call.args[2].readPreference.preference; + } + }); + + expect(myObjectReadPreference0).toEqual(ReadPreference.SECONDARY); + expect(myObjectReadPreference1).toEqual(ReadPreference.SECONDARY); + expect(myObjectReadPreference2).toEqual( + ReadPreference.SECONDARY_PREFERRED + ); + + done(); + }) + .catch(done.fail); }); }); }); diff --git a/src/Adapters/Storage/Mongo/MongoStorageAdapter.js b/src/Adapters/Storage/Mongo/MongoStorageAdapter.js index 0e74a83224..a289cceedd 100644 --- a/src/Adapters/Storage/Mongo/MongoStorageAdapter.js +++ b/src/Adapters/Storage/Mongo/MongoStorageAdapter.js @@ -926,6 +926,9 @@ export class MongoStorageAdapter implements StorageAdapter { } _parseReadPreference(readPreference: ?string): ?string { + if (readPreference) { + readPreference = readPreference.toUpperCase(); + } switch (readPreference) { case 'PRIMARY': readPreference = ReadPreference.PRIMARY; diff --git a/src/RestQuery.js b/src/RestQuery.js index a9cceffded..443668c374 100644 --- a/src/RestQuery.js +++ b/src/RestQuery.js @@ -14,6 +14,9 @@ const AlwaysSelectedKeys = ['objectId', 'createdAt', 'updatedAt', 'ACL']; // include // keys // redirectClassNameForKey +// readPreference +// includeReadPreference +// subqueryReadPreference function RestQuery( config, auth, @@ -362,6 +365,8 @@ RestQuery.prototype.replaceInQuery = function() { if (this.restOptions.subqueryReadPreference) { additionalOptions.readPreference = this.restOptions.subqueryReadPreference; additionalOptions.subqueryReadPreference = this.restOptions.subqueryReadPreference; + } else if (this.restOptions.readPreference) { + additionalOptions.readPreference = this.restOptions.readPreference; } var subquery = new RestQuery( @@ -421,6 +426,8 @@ RestQuery.prototype.replaceNotInQuery = function() { if (this.restOptions.subqueryReadPreference) { additionalOptions.readPreference = this.restOptions.subqueryReadPreference; additionalOptions.subqueryReadPreference = this.restOptions.subqueryReadPreference; + } else if (this.restOptions.readPreference) { + additionalOptions.readPreference = this.restOptions.readPreference; } var subquery = new RestQuery( @@ -484,6 +491,8 @@ RestQuery.prototype.replaceSelect = function() { if (this.restOptions.subqueryReadPreference) { additionalOptions.readPreference = this.restOptions.subqueryReadPreference; additionalOptions.subqueryReadPreference = this.restOptions.subqueryReadPreference; + } else if (this.restOptions.readPreference) { + additionalOptions.readPreference = this.restOptions.readPreference; } var subquery = new RestQuery( @@ -545,6 +554,8 @@ RestQuery.prototype.replaceDontSelect = function() { if (this.restOptions.subqueryReadPreference) { additionalOptions.readPreference = this.restOptions.subqueryReadPreference; additionalOptions.subqueryReadPreference = this.restOptions.subqueryReadPreference; + } else if (this.restOptions.readPreference) { + additionalOptions.readPreference = this.restOptions.readPreference; } var subquery = new RestQuery( @@ -809,6 +820,8 @@ function includePath(config, auth, response, path, restOptions = {}) { includeRestOptions.readPreference = restOptions.includeReadPreference; includeRestOptions.includeReadPreference = restOptions.includeReadPreference; + } else if (restOptions.readPreference) { + includeRestOptions.readPreference = restOptions.readPreference; } const queryPromises = Object.keys(pointersHash).map(className => { diff --git a/src/Routers/ClassesRouter.js b/src/Routers/ClassesRouter.js index b4269c055e..8c6094fe72 100644 --- a/src/Routers/ClassesRouter.js +++ b/src/Routers/ClassesRouter.js @@ -3,7 +3,13 @@ import rest from '../rest'; import _ from 'lodash'; import Parse from 'parse/node'; -const ALLOWED_GET_QUERY_KEYS = ['keys', 'include']; +const ALLOWED_GET_QUERY_KEYS = [ + 'keys', + 'include', + 'readPreference', + 'includeReadPreference', + 'subqueryReadPreference', +]; export class ClassesRouter extends PromiseRouter { className(req) { @@ -57,12 +63,21 @@ export class ClassesRouter extends PromiseRouter { } } - if (typeof body.keys == 'string') { + if (typeof body.keys === 'string') { options.keys = body.keys; } if (body.include) { options.include = String(body.include); } + if (typeof body.readPreference === 'string') { + options.readPreference = body.readPreference; + } + if (typeof body.includeReadPreference === 'string') { + options.includeReadPreference = body.includeReadPreference; + } + if (typeof body.subqueryReadPreference === 'string') { + options.subqueryReadPreference = body.subqueryReadPreference; + } return rest .get( @@ -154,6 +169,9 @@ export class ClassesRouter extends PromiseRouter { 'includeAll', 'redirectClassNameForKey', 'where', + 'readPreference', + 'includeReadPreference', + 'subqueryReadPreference', ]; for (const key of Object.keys(body)) { @@ -188,6 +206,15 @@ export class ClassesRouter extends PromiseRouter { if (body.includeAll) { options.includeAll = true; } + if (typeof body.readPreference === 'string') { + options.readPreference = body.readPreference; + } + if (typeof body.includeReadPreference === 'string') { + options.includeReadPreference = body.includeReadPreference; + } + if (typeof body.subqueryReadPreference === 'string') { + options.subqueryReadPreference = body.subqueryReadPreference; + } return options; } diff --git a/src/vendor/mongodbUrl.js b/src/vendor/mongodbUrl.js index ca8a1faca0..a96fd35f92 100644 --- a/src/vendor/mongodbUrl.js +++ b/src/vendor/mongodbUrl.js @@ -101,7 +101,7 @@ Url.prototype.parse = function(url, parseQueryString, slashesDenoteHost) { code === 10 /*\n*/ || code === 12 /*\f*/ || code === 160 /*\u00A0*/ || - code === 65279 /*\uFEFF*/; + code === 65279; /*\uFEFF*/ if (start === -1) { if (isWs) continue; lastPos = start = i; @@ -193,7 +193,7 @@ Url.prototype.parse = function(url, parseQueryString, slashesDenoteHost) { // how the browser resolves relative URLs. if (slashesDenoteHost || proto || /^\/\/[^@\/]+@[^@\/]+/.test(rest)) { var slashes = - rest.charCodeAt(0) === 47 /*/*/ && rest.charCodeAt(1) === 47 /*/*/; + rest.charCodeAt(0) === 47 /*/*/ && rest.charCodeAt(1) === 47; /*/*/ if (slashes && !(proto && hostlessProtocol[proto])) { rest = rest.slice(2); this.slashes = true; @@ -285,7 +285,7 @@ Url.prototype.parse = function(url, parseQueryString, slashesDenoteHost) { // assume that it's an IPv6 address. var ipv6Hostname = hostname.charCodeAt(0) === 91 /*[*/ && - hostname.charCodeAt(hostname.length - 1) === 93 /*]*/; + hostname.charCodeAt(hostname.length - 1) === 93; /*]*/ // validate a little. if (!ipv6Hostname) { @@ -868,11 +868,11 @@ Url.prototype.resolveObject = function(relative) { // put the host back if (psychotic) { - result.hostname = result.host = isAbsolute - ? '' - : srcPath.length - ? srcPath.shift() - : ''; + if (isAbsolute) { + result.hostname = result.host = ''; + } else { + result.hostname = result.host = srcPath.length ? srcPath.shift() : ''; + } //occasionally the auth can get stuck only in host //this especially happens in cases like //url.resolveObject('mailto:local1@domain1', 'local2@domain2')