From ca3fe68d2a9968ed461249c62a7c8753c2e68c09 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Thu, 21 Mar 2024 09:44:18 -0500 Subject: [PATCH 1/5] feat: Add support for setting `Parse.ACL` from json --- integration/test/ParseACLTest.js | 20 ++++++++- integration/test/ParseEventuallyQueueTest.js | 43 ++++++++++++++++++++ src/ParseObject.js | 7 ++-- src/__tests__/ParseObject-test.js | 39 ++++++++---------- 4 files changed, 81 insertions(+), 28 deletions(-) diff --git a/integration/test/ParseACLTest.js b/integration/test/ParseACLTest.js index 4251f059c..669f191c7 100644 --- a/integration/test/ParseACLTest.js +++ b/integration/test/ParseACLTest.js @@ -8,9 +8,11 @@ describe('Parse.ACL', () => { Parse.User.enableUnsafeCurrentUser(); }); - it('acl must be valid', () => { + it('can handle invalid acl', () => { const user = new Parse.User(); - assert.equal(user.setACL(`Ceci n'est pas un ACL.`), false); + user.setACL(`Ceci n'est pas un ACL.`) + assert.equal(user.getACL(), null); + assert.equal(user.get('ACL'), null); }); it('can refresh object with acl', async () => { @@ -27,6 +29,20 @@ describe('Parse.ACL', () => { assert(o); }); + it('can set ACL from json', async () => { + Parse.User.enableUnsafeCurrentUser(); + const user = new Parse.User(); + const object = new TestObject(); + user.set('username', 'torn'); + user.set('password', 'acl'); + await user.signUp(); + const acl = new Parse.ACL(user); + object.setACL(acl); + const json = object.toJSON(); + await object.save(json); + assert.equal(acl.equals(object.getACL()), true); + }); + it('disables public get access', async () => { const user = new Parse.User(); const object = new TestObject(); diff --git a/integration/test/ParseEventuallyQueueTest.js b/integration/test/ParseEventuallyQueueTest.js index 6ce6358cb..1d8157553 100644 --- a/integration/test/ParseEventuallyQueueTest.js +++ b/integration/test/ParseEventuallyQueueTest.js @@ -220,6 +220,49 @@ describe('Parse EventuallyQueue', () => { assert.strictEqual(results.length, 1); }); + it('can saveEventually on object with ACL', async () => { + Parse.User.enableUnsafeCurrentUser(); + const parseServer = await reconfigureServer(); + const user = new Parse.User(); + user.set('username', 'torn'); + user.set('password', 'acl'); + await user.signUp(); + + const acl = new Parse.ACL(user); + const object = new TestObject({ hash: 'saveSecret' }); + object.setACL(acl); + + await new Promise((resolve) => parseServer.server.close(resolve)); + + await object.saveEventually(); + + let length = await Parse.EventuallyQueue.length(); + assert(Parse.EventuallyQueue.isPolling()); + assert.strictEqual(length, 1); + + await reconfigureServer({}); + + while (Parse.EventuallyQueue.isPolling()) { + await sleep(100); + } + assert.strictEqual(Parse.EventuallyQueue.isPolling(), false); + + length = await Parse.EventuallyQueue.length(); + while (length) { + await sleep(100); + } + length = await Parse.EventuallyQueue.length(); + assert.strictEqual(length, 0); + + const query = new Parse.Query('TestObject'); + query.equalTo('hash', 'saveSecret'); + let results = await query.find(); + while (results.length === 0) { + results = await query.find(); + } + assert.strictEqual(results.length, 1); + }); + it('can destroyEventually', async () => { const parseServer = await reconfigureServer(); const object = new TestObject({ hash: 'deleteSecret' }); diff --git a/src/ParseObject.js b/src/ParseObject.js index 32701191b..047e24be6 100644 --- a/src/ParseObject.js +++ b/src/ParseObject.js @@ -603,6 +603,10 @@ class ParseObject { * @returns {*} */ get(attr: string): mixed { + if (attr === 'ACL') { + const acl = this.attributes[attr]; + return acl instanceof ParseACL ? acl : null; + } return this.attributes[attr]; } @@ -1043,9 +1047,6 @@ class ParseObject { * @see Parse.Object#set */ validate(attrs: AttributeMap): ParseError | boolean { - if (attrs.hasOwnProperty('ACL') && !(attrs.ACL instanceof ParseACL)) { - return new ParseError(ParseError.OTHER_CAUSE, 'ACL must be a Parse ACL.'); - } for (const key in attrs) { if (!/^[A-Za-z][0-9A-Za-z_.]*$/.test(key)) { return new ParseError(ParseError.INVALID_KEY_NAME); diff --git a/src/__tests__/ParseObject-test.js b/src/__tests__/ParseObject-test.js index c67c4a3e1..2a5d9ad00 100644 --- a/src/__tests__/ParseObject-test.js +++ b/src/__tests__/ParseObject-test.js @@ -364,6 +364,13 @@ describe('ParseObject', () => { expect(o.getACL()).toEqual(ACL); }); + it('encodes ACL from json', () => { + const ACL = new ParseACL({ user1: { read: true } }); + const o = new ParseObject('Item'); + o.set({ ACL: ACL.toJSON() }); + expect(o.getACL()).toEqual(ACL); + }); + it('can be rendered to JSON', () => { let o = new ParseObject('Item'); o.set({ @@ -869,12 +876,6 @@ describe('ParseObject', () => { it('can validate attributes', () => { const o = new ParseObject('Listing'); - expect( - o.validate({ - ACL: 'not an acl', - }) - ).toEqual(new ParseError(ParseError.OTHER_CAUSE, 'ACL must be a Parse ACL.')); - expect( o.validate({ 'invalid!key': 12, @@ -896,8 +897,6 @@ describe('ParseObject', () => { it('validates attributes on set()', () => { const o = new ParseObject('Listing'); - expect(o.set('ACL', 'not an acl')).toBe(false); - expect(o.set('ACL', { '*': { read: true, write: false } })).toBe(o); expect(o.set('$$$', 'o_O')).toBe(false); o.set('$$$', 'o_O', { @@ -1616,27 +1615,21 @@ describe('ParseObject', () => { }); }); - it('accepts attribute changes on save', done => { + it('accepts attribute changes on save', async () => { CoreManager.getRESTController()._setXHR( mockXHR([ - { - status: 200, - response: { objectId: 'newattributes' }, - }, + { status: 200, response: { objectId: 'newattributes' } }, + { status: 200, response: { objectId: 'newattributes' } }, ]) ); let o = new ParseObject('Item'); - o.save({ key: 'value' }) - .then(() => { - expect(o.get('key')).toBe('value'); + await o.save({ key: 'value' }) + expect(o.get('key')).toBe('value'); - o = new ParseObject('Item'); - return o.save({ ACL: 'not an acl' }); - }) - .then(null, error => { - expect(error.code).toBe(-1); - done(); - }); + o = new ParseObject('Item'); + await o.save({ ACL: 'not an acl' }); + expect(o.getACL()).toBe(null); + expect(o.get('ACL')).toBe(null); }); it('accepts context on save', async () => { From dadf24e29f06c2f1b345899c8dad37fca562e179 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Thu, 21 Mar 2024 10:21:58 -0500 Subject: [PATCH 2/5] improve coverage --- src/__tests__/ParseObject-test.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/__tests__/ParseObject-test.js b/src/__tests__/ParseObject-test.js index 2a5d9ad00..6d20f0960 100644 --- a/src/__tests__/ParseObject-test.js +++ b/src/__tests__/ParseObject-test.js @@ -907,6 +907,16 @@ describe('ParseObject', () => { }); }); + it('validates attributes on save()', async () => { + const o = new ParseObject('Listing'); + try { + await o.save({ '$$$': 'o_O' }); + expect(true).toBe(false); + } catch (e) { + expect(e.code).toBe(ParseError.INVALID_KEY_NAME); + } + }); + it('ignores validation if ignoreValidation option is passed to set()', () => { const o = new ParseObject('Listing'); expect(o.set('$$$', 'o_O', { ignoreValidation: true })).toBe(o); From fd5550c8926ad944f5348b227ff50e5aece48672 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Tue, 26 Mar 2024 15:26:27 -0500 Subject: [PATCH 3/5] simplify tests --- src/__tests__/ParseObject-test.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/__tests__/ParseObject-test.js b/src/__tests__/ParseObject-test.js index 6d20f0960..7f529daa8 100644 --- a/src/__tests__/ParseObject-test.js +++ b/src/__tests__/ParseObject-test.js @@ -909,12 +909,9 @@ describe('ParseObject', () => { it('validates attributes on save()', async () => { const o = new ParseObject('Listing'); - try { - await o.save({ '$$$': 'o_O' }); - expect(true).toBe(false); - } catch (e) { - expect(e.code).toBe(ParseError.INVALID_KEY_NAME); - } + await expect(o.save({ '$$$': 'o_O' })).rejects.toEqual( + new ParseError(ParseError.INVALID_KEY_NAME) + ); }); it('ignores validation if ignoreValidation option is passed to set()', () => { From e822869f79b4f9597222e366efcc699dd6a2e203 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Sat, 30 Mar 2024 20:59:44 -0500 Subject: [PATCH 4/5] fix validation --- integration/test/ParseACLTest.js | 7 +++--- src/ParseObject.js | 20 +++++++++------- src/__tests__/ParseObject-test.js | 39 ++++++++++++++++++------------- 3 files changed, 37 insertions(+), 29 deletions(-) diff --git a/integration/test/ParseACLTest.js b/integration/test/ParseACLTest.js index 669f191c7..96b2ade21 100644 --- a/integration/test/ParseACLTest.js +++ b/integration/test/ParseACLTest.js @@ -8,11 +8,10 @@ describe('Parse.ACL', () => { Parse.User.enableUnsafeCurrentUser(); }); - it('can handle invalid acl', () => { + it('acl must be valid', () => { const user = new Parse.User(); - user.setACL(`Ceci n'est pas un ACL.`) - assert.equal(user.getACL(), null); - assert.equal(user.get('ACL'), null); + assert.equal(user.setACL(`Ceci n'est pas un ACL.`), false); + console.log(user.getACL()); }); it('can refresh object with acl', async () => { diff --git a/src/ParseObject.js b/src/ParseObject.js index 047e24be6..ca2c433a6 100644 --- a/src/ParseObject.js +++ b/src/ParseObject.js @@ -603,10 +603,6 @@ class ParseObject { * @returns {*} */ get(attr: string): mixed { - if (attr === 'ACL') { - const acl = this.attributes[attr]; - return acl instanceof ParseACL ? acl : null; - } return this.attributes[attr]; } @@ -1047,6 +1043,9 @@ class ParseObject { * @see Parse.Object#set */ validate(attrs: AttributeMap): ParseError | boolean { + if (attrs.hasOwnProperty('ACL') && !(attrs.ACL instanceof ParseACL)) { + return new ParseError(ParseError.OTHER_CAUSE, 'ACL must be a Parse ACL.'); + } for (const key in attrs) { if (!/^[A-Za-z][0-9A-Za-z_.]*$/.test(key)) { return new ParseError(ParseError.INVALID_KEY_NAME); @@ -1309,15 +1308,18 @@ class ParseObject { options = arg3; } + options = options || {}; if (attrs) { - const validation = this.validate(attrs); - if (validation) { - return Promise.reject(validation); + let validationError; + options.error = (_, validation) => { + validationError = validation; + }; + const success = this.set(attrs, options); + if (!success) { + return Promise.reject(validationError); } - this.set(attrs, options); } - options = options || {}; const saveOptions = {}; if (options.hasOwnProperty('useMasterKey')) { saveOptions.useMasterKey = !!options.useMasterKey; diff --git a/src/__tests__/ParseObject-test.js b/src/__tests__/ParseObject-test.js index 7f529daa8..2066a8979 100644 --- a/src/__tests__/ParseObject-test.js +++ b/src/__tests__/ParseObject-test.js @@ -876,6 +876,12 @@ describe('ParseObject', () => { it('can validate attributes', () => { const o = new ParseObject('Listing'); + expect( + o.validate({ + ACL: 'not an acl', + }) + ).toEqual(new ParseError(ParseError.OTHER_CAUSE, 'ACL must be a Parse ACL.')); + expect( o.validate({ 'invalid!key': 12, @@ -897,6 +903,8 @@ describe('ParseObject', () => { it('validates attributes on set()', () => { const o = new ParseObject('Listing'); + expect(o.set('ACL', 'not an acl')).toBe(false); + expect(o.set('ACL', { '*': { read: true, write: false } })).toBe(o); expect(o.set('$$$', 'o_O')).toBe(false); o.set('$$$', 'o_O', { @@ -907,13 +915,6 @@ describe('ParseObject', () => { }); }); - it('validates attributes on save()', async () => { - const o = new ParseObject('Listing'); - await expect(o.save({ '$$$': 'o_O' })).rejects.toEqual( - new ParseError(ParseError.INVALID_KEY_NAME) - ); - }); - it('ignores validation if ignoreValidation option is passed to set()', () => { const o = new ParseObject('Listing'); expect(o.set('$$$', 'o_O', { ignoreValidation: true })).toBe(o); @@ -1622,21 +1623,27 @@ describe('ParseObject', () => { }); }); - it('accepts attribute changes on save', async () => { + it('accepts attribute changes on save', done => { CoreManager.getRESTController()._setXHR( mockXHR([ - { status: 200, response: { objectId: 'newattributes' } }, - { status: 200, response: { objectId: 'newattributes' } }, + { + status: 200, + response: { objectId: 'newattributes' }, + }, ]) ); let o = new ParseObject('Item'); - await o.save({ key: 'value' }) - expect(o.get('key')).toBe('value'); + o.save({ key: 'value' }) + .then(() => { + expect(o.get('key')).toBe('value'); - o = new ParseObject('Item'); - await o.save({ ACL: 'not an acl' }); - expect(o.getACL()).toBe(null); - expect(o.get('ACL')).toBe(null); + o = new ParseObject('Item'); + return o.save({ ACL: 'not an acl' }); + }) + .then(null, error => { + expect(error.code).toBe(-1); + done(); + }); }); it('accepts context on save', async () => { From 087e08b519cd1bd1e081d5d660e786af7a264c7b Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Sat, 30 Mar 2024 21:02:36 -0500 Subject: [PATCH 5/5] clean up --- integration/test/ParseACLTest.js | 1 - src/ParseObject.js | 1 - 2 files changed, 2 deletions(-) diff --git a/integration/test/ParseACLTest.js b/integration/test/ParseACLTest.js index 96b2ade21..d1340cfe7 100644 --- a/integration/test/ParseACLTest.js +++ b/integration/test/ParseACLTest.js @@ -11,7 +11,6 @@ describe('Parse.ACL', () => { it('acl must be valid', () => { const user = new Parse.User(); assert.equal(user.setACL(`Ceci n'est pas un ACL.`), false); - console.log(user.getACL()); }); it('can refresh object with acl', async () => { diff --git a/src/ParseObject.js b/src/ParseObject.js index ca2c433a6..eafa7f45d 100644 --- a/src/ParseObject.js +++ b/src/ParseObject.js @@ -1319,7 +1319,6 @@ class ParseObject { return Promise.reject(validationError); } } - const saveOptions = {}; if (options.hasOwnProperty('useMasterKey')) { saveOptions.useMasterKey = !!options.useMasterKey;