From fb0e9bc7fe74b3eeeab7b9958586dc2a86f0b061 Mon Sep 17 00:00:00 2001 From: Robby Helms Date: Wed, 19 Jan 2022 09:10:46 -0500 Subject: [PATCH 1/5] Failing ParseUser test for #1347 --- src/__tests__/ParseUser-test.js | 68 +++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/src/__tests__/ParseUser-test.js b/src/__tests__/ParseUser-test.js index 8691513a3..042953b81 100644 --- a/src/__tests__/ParseUser-test.js +++ b/src/__tests__/ParseUser-test.js @@ -940,6 +940,74 @@ describe('ParseUser', () => { }); }); + it('user who logs out, deletes attribute, then logs back in has correct attributes', done => { + ParseUser.enableUnsafeCurrentUser(); + ParseUser._clearCache(); + CoreManager.setRESTController({ + request(method, path) { + expect(path).toBe('login'); + + return Promise.resolve( + { + objectId: 'uid2', + username: 'username', + sessionToken: '123abc', + fieldToBeDeleted: 'This field is returned in the first login but not the second', + }, + 200 + ); + }, + ajax() {}, + }); + + ParseUser.logIn('username', 'password') + .then(u => { + expect(u.isCurrent()).toBe(true); + expect(u.id).toBe('uid2'); + expect(u.get('username')).toBe('username'); + expect(u.get('fieldToBeDeleted')).toBe( + 'This field is returned in the first login but not the second' + ); + CoreManager.setRESTController({ + request() { + return Promise.resolve({}, 200); + }, + ajax() {}, + }); + return ParseUser.logOut(); + }) + .then(() => { + expect(ParseUser.current()).toBe(null); + }) + .then(() => { + CoreManager.setRESTController({ + request(method, path) { + expect(path).toBe('login'); + + return Promise.resolve( + { + objectId: 'uid2', + username: 'username', + sessionToken: '123abc', + // We assume fieldToBeDeleted was deleted while user was logged out + }, + 200 + ); + }, + ajax() {}, + }); + return ParseUser.logIn('username', 'password'); + }) + .then(u => { + expect(u.isCurrent()).toBe(true); + expect(u.id).toBe('uid2'); + expect(u.get('username')).toBe('username'); + expect(u.get('fieldToBeDeleted')).toBe(undefined); // Failing test + + done(); + }); + }); + it('can retreive a user with sessionToken (me)', async () => { ParseUser.disableUnsafeCurrentUser(); ParseUser._clearCache(); From a5e7dfbf2cbaafe69e60cb20570ed865e20ebcf6 Mon Sep 17 00:00:00 2001 From: Robby Helms Date: Wed, 19 Jan 2022 14:05:54 -0500 Subject: [PATCH 2/5] Rewrite failing test with async/await --- src/__tests__/ParseUser-test.js | 97 ++++++++++++++------------------- 1 file changed, 42 insertions(+), 55 deletions(-) diff --git a/src/__tests__/ParseUser-test.js b/src/__tests__/ParseUser-test.js index 042953b81..5d472ab63 100644 --- a/src/__tests__/ParseUser-test.js +++ b/src/__tests__/ParseUser-test.js @@ -940,72 +940,59 @@ describe('ParseUser', () => { }); }); - it('user who logs out, deletes attribute, then logs back in has correct attributes', done => { + it('user who logs out, deletes attribute, then logs back in has correct attributes', async () => { ParseUser.enableUnsafeCurrentUser(); ParseUser._clearCache(); CoreManager.setRESTController({ - request(method, path) { + async request(method, path) { expect(path).toBe('login'); + return { + objectId: 'uid2', + username: 'username', + sessionToken: '123abc', + fieldToBeDeleted: 'This field is returned in the first login but not the second', + }; + }, + ajax() {}, + }); - return Promise.resolve( - { - objectId: 'uid2', - username: 'username', - sessionToken: '123abc', - fieldToBeDeleted: 'This field is returned in the first login but not the second', - }, - 200 - ); + const user1 = await ParseUser.logIn('username', 'password'); + expect(user1.isCurrent()).toBe(true); + expect(user1.id).toBe('uid2'); + expect(user1.get('username')).toBe('username'); + expect(user1.get('fieldToBeDeleted')).toBe( + 'This field is returned in the first login but not the second' + ); + + CoreManager.setRESTController({ + async request() { + return {}; }, ajax() {}, }); - ParseUser.logIn('username', 'password') - .then(u => { - expect(u.isCurrent()).toBe(true); - expect(u.id).toBe('uid2'); - expect(u.get('username')).toBe('username'); - expect(u.get('fieldToBeDeleted')).toBe( - 'This field is returned in the first login but not the second' - ); - CoreManager.setRESTController({ - request() { - return Promise.resolve({}, 200); - }, - ajax() {}, - }); - return ParseUser.logOut(); - }) - .then(() => { - expect(ParseUser.current()).toBe(null); - }) - .then(() => { - CoreManager.setRESTController({ - request(method, path) { - expect(path).toBe('login'); + await ParseUser.logOut(); + expect(ParseUser.current()).toBe(null); - return Promise.resolve( - { - objectId: 'uid2', - username: 'username', - sessionToken: '123abc', - // We assume fieldToBeDeleted was deleted while user was logged out - }, - 200 - ); - }, - ajax() {}, - }); - return ParseUser.logIn('username', 'password'); - }) - .then(u => { - expect(u.isCurrent()).toBe(true); - expect(u.id).toBe('uid2'); - expect(u.get('username')).toBe('username'); - expect(u.get('fieldToBeDeleted')).toBe(undefined); // Failing test + CoreManager.setRESTController({ + async request(method, path) { + expect(path).toBe('login'); - done(); - }); + return { + objectId: 'uid2', + username: 'username', + sessionToken: '123abc', + // We assume fieldToBeDeleted was deleted while user was logged out + }; + }, + ajax() {}, + }); + + const user2 = await ParseUser.logIn('username', 'password'); + expect(user2.isCurrent()).toBe(true); + expect(user2.id).toBe('uid2'); + expect(user2.get('username')).toBe('username'); + expect(user2.get('fieldToBeDeleted')).toBe(undefined); // Failing test }); it('can retreive a user with sessionToken (me)', async () => { From 36cea18431af9be34bfaff05e5acfffa97803e36 Mon Sep 17 00:00:00 2001 From: Robby Helms Date: Thu, 3 Feb 2022 11:59:13 -0500 Subject: [PATCH 3/5] Failing Cloud.run test for #1347 --- src/__tests__/Cloud-test.js | 49 +++++++++++++++++++++++++++++++++ src/__tests__/ParseUser-test.js | 2 +- 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/src/__tests__/Cloud-test.js b/src/__tests__/Cloud-test.js index b65bff268..215b835b7 100644 --- a/src/__tests__/Cloud-test.js +++ b/src/__tests__/Cloud-test.js @@ -15,15 +15,22 @@ jest.dontMock('../ParseError'); jest.dontMock('../ParseObject'); jest.dontMock('../ParseQuery'); jest.dontMock('../Push'); +jest.dontMock('../parseDate'); +jest.dontMock('../ObjectStateMutations'); +jest.dontMock('../SingleInstanceStateController'); +jest.dontMock('../UniqueInstanceStateController'); const Cloud = require('../Cloud'); const CoreManager = require('../CoreManager'); const Push = require('../Push'); +const ParseObject = require('../ParseObject').default; const defaultController = CoreManager.getCloudController(); describe('Cloud', () => { beforeEach(() => { + ParseObject.enableSingleInstance(); + const run = jest.fn(); const getJobsData = jest.fn(); const startJob = jest.fn(); @@ -216,6 +223,48 @@ describe('CloudController', () => { }); }); + it('run same function twice with different responses', async () => { + const request = jest.fn(); + request.mockReturnValue( + Promise.resolve({ + success: true, + result: { + objectId: 'abc123', + className: 'Item', + __type: 'Object', + createdAt: '2015-01-01T00:00:00.000Z', + updatedAt: '2015-01-01T00:00:00.000Z', + label: 'foobar', + }, + }) + ); + + const ajax = jest.fn(); + CoreManager.setRESTController({ request: request, ajax: ajax }); + + const response1 = await Cloud.run('myfunction'); + expect(response1.get('label')).toBe('foobar'); + + request.mockReturnValue( + Promise.resolve({ + success: true, + result: { + objectId: 'abc123', + className: 'Item', + __type: 'Object', + createdAt: '2015-01-01T00:00:00.000Z', + updatedAt: '2015-01-01T00:00:00.000Z', + label2: 'control to confirm correct mock usage', + // Note that 'label' is not returned + }, + }) + ); + + const response2 = await Cloud.run('myfunction'); + expect(response2.get('label2')).toBe('control to confirm correct mock usage'); + expect(response2.get('label')).toBe(undefined); // Failing test PR #1442 + }); + it('startJob passes encoded requests', () => { Cloud.startJob('myJob', { value: 12, diff --git a/src/__tests__/ParseUser-test.js b/src/__tests__/ParseUser-test.js index 5d472ab63..c50439aa9 100644 --- a/src/__tests__/ParseUser-test.js +++ b/src/__tests__/ParseUser-test.js @@ -992,7 +992,7 @@ describe('ParseUser', () => { expect(user2.isCurrent()).toBe(true); expect(user2.id).toBe('uid2'); expect(user2.get('username')).toBe('username'); - expect(user2.get('fieldToBeDeleted')).toBe(undefined); // Failing test + expect(user2.get('fieldToBeDeleted')).toBe(undefined); // Failing test PR #1442 }); it('can retreive a user with sessionToken (me)', async () => { From d2b20ceaf15db7fbf4bd60f0f0940f2b2243292a Mon Sep 17 00:00:00 2001 From: Robby Helms Date: Mon, 7 Feb 2022 10:51:53 -0500 Subject: [PATCH 4/5] Clear server data during Cloud.run and User.logIn --- src/Cloud.js | 1 + src/ParseUser.js | 1 + 2 files changed, 2 insertions(+) diff --git a/src/Cloud.js b/src/Cloud.js index 59797e9fd..827d4d53c 100644 --- a/src/Cloud.js +++ b/src/Cloud.js @@ -121,6 +121,7 @@ const DefaultController = { if (typeof res === 'object' && Object.keys(res).length > 0 && !res.hasOwnProperty('result')) { throw new ParseError(ParseError.INVALID_JSON, 'The server returned an invalid response.'); } + ParseObject._clearAllState(); const decoded = decode(res); if (decoded && decoded.hasOwnProperty('result')) { return Promise.resolve(decoded.result); diff --git a/src/ParseUser.js b/src/ParseUser.js index d0303dac3..a097723c6 100644 --- a/src/ParseUser.js +++ b/src/ParseUser.js @@ -1088,6 +1088,7 @@ const DefaultController = { stateController.setPendingOp(user._getStateIdentifier(), 'username', undefined); stateController.setPendingOp(user._getStateIdentifier(), 'password', undefined); response.password = undefined; + user._clearServerData(); user._finishFetch(response); if (!canUseCurrentUser) { // We can't set the current user, so just return the one we logged in From 2bc68ce883e669e1fd951c3bd664b5e4584e5345 Mon Sep 17 00:00:00 2001 From: Robby Helms Date: Mon, 7 Feb 2022 15:42:27 -0500 Subject: [PATCH 5/5] Override object state from User.logIn and decode --- src/Cloud.js | 1 - src/ParseObject.js | 5 ++++- src/ParseUser.js | 3 +-- src/decode.js | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Cloud.js b/src/Cloud.js index 827d4d53c..59797e9fd 100644 --- a/src/Cloud.js +++ b/src/Cloud.js @@ -121,7 +121,6 @@ const DefaultController = { if (typeof res === 'object' && Object.keys(res).length > 0 && !res.hasOwnProperty('result')) { throw new ParseError(ParseError.INVALID_JSON, 'The server returned an invalid response.'); } - ParseObject._clearAllState(); const decoded = decode(res); if (decoded && decoded.hasOwnProperty('result')) { return Promise.resolve(decoded.result); diff --git a/src/ParseObject.js b/src/ParseObject.js index 87738d56f..c7276cd3c 100644 --- a/src/ParseObject.js +++ b/src/ParseObject.js @@ -347,10 +347,13 @@ class ParseObject { }; } - _finishFetch(serverData: AttributeMap) { + _finishFetch(serverData: AttributeMap, override?: boolean) { if (!this.id && serverData.objectId) { this.id = serverData.objectId; } + if (override) { + this._clearServerData(); + } const stateController = CoreManager.getObjectStateController(); stateController.initializeState(this._getStateIdentifier()); const decoded = {}; diff --git a/src/ParseUser.js b/src/ParseUser.js index a097723c6..13d8356e2 100644 --- a/src/ParseUser.js +++ b/src/ParseUser.js @@ -1088,8 +1088,7 @@ const DefaultController = { stateController.setPendingOp(user._getStateIdentifier(), 'username', undefined); stateController.setPendingOp(user._getStateIdentifier(), 'password', undefined); response.password = undefined; - user._clearServerData(); - user._finishFetch(response); + user._finishFetch(response, true); if (!canUseCurrentUser) { // We can't set the current user, so just return the one we logged in return Promise.resolve(user); diff --git a/src/decode.js b/src/decode.js index 42512fba1..b29bb8ebd 100644 --- a/src/decode.js +++ b/src/decode.js @@ -34,7 +34,7 @@ export default function decode(value: any): any { return ParseObject.fromJSON(value); } if (value.__type === 'Object' && value.className) { - return ParseObject.fromJSON(value); + return ParseObject.fromJSON(value, true); } if (value.__type === 'Relation') { // The parent and key fields will be populated by the parent