From 6fab714f7060e3844b392bd93c6716be7b884da6 Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 23 Dec 2024 15:55:59 +1100 Subject: [PATCH 1/3] Update package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 830d796bbb..4bcf199e5d 100644 --- a/package.json +++ b/package.json @@ -128,7 +128,7 @@ "test:mongodb:7.0.1": "npm run test:mongodb --dbversion=7.0.1", "test:mongodb:8.0.3": "npm run test:mongodb --dbversion=8.0.3", "test:postgres:testonly": "cross-env PARSE_SERVER_TEST_DB=postgres PARSE_SERVER_TEST_DATABASE_URI=postgres://postgres:password@localhost:5432/parse_server_postgres_adapter_test_database npm run testonly", - "pretest": "", + "pretest": "pretest": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=5.3.2} MONGODB_TOPOLOGY=${MONGODB_TOPOLOGY:=standalone} mongodb-runner start -t ${MONGODB_TOPOLOGY} --version ${MONGODB_VERSION} -- --port 27017", "testonly": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=5.3.2} MONGODB_TOPOLOGY=${MONGODB_TOPOLOGY:=standalone} TESTING=1 jasmine", "test": "npm run testonly", "posttest": "cross-env mongodb-runner stop --all", From adb48ed98dc578ec6a17789e1e04d5442eb0ac73 Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 23 Dec 2024 15:56:42 +1100 Subject: [PATCH 2/3] Update package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4bcf199e5d..fe2dcfedd9 100644 --- a/package.json +++ b/package.json @@ -128,7 +128,7 @@ "test:mongodb:7.0.1": "npm run test:mongodb --dbversion=7.0.1", "test:mongodb:8.0.3": "npm run test:mongodb --dbversion=8.0.3", "test:postgres:testonly": "cross-env PARSE_SERVER_TEST_DB=postgres PARSE_SERVER_TEST_DATABASE_URI=postgres://postgres:password@localhost:5432/parse_server_postgres_adapter_test_database npm run testonly", - "pretest": "pretest": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=5.3.2} MONGODB_TOPOLOGY=${MONGODB_TOPOLOGY:=standalone} mongodb-runner start -t ${MONGODB_TOPOLOGY} --version ${MONGODB_VERSION} -- --port 27017", + "pretest": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=5.3.2} MONGODB_TOPOLOGY=${MONGODB_TOPOLOGY:=standalone} mongodb-runner start -t ${MONGODB_TOPOLOGY} --version ${MONGODB_VERSION} -- --port 27017", "testonly": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=5.3.2} MONGODB_TOPOLOGY=${MONGODB_TOPOLOGY:=standalone} TESTING=1 jasmine", "test": "npm run testonly", "posttest": "cross-env mongodb-runner stop --all", From 1c42f555e6edd761f44b29e3c7110edbbe6f5ce5 Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 23 Dec 2024 16:00:52 +1100 Subject: [PATCH 3/3] remove consoles --- spec/AudienceRouter.spec.js | 107 +- spec/Auth.spec.js | 10 +- spec/AuthenticationAdapters.spec.js | 254 +- spec/AuthenticationAdaptersV2.spec.js | 4 +- spec/CLI.spec.js | 143 +- spec/CloudCode.Validator.spec.js | 123 +- spec/CloudCode.spec.js | 469 ++-- spec/CloudCodeLogger.spec.js | 123 +- spec/DefinedSchemas.spec.js | 69 +- spec/EmailVerificationToken.spec.js | 1679 +++++++------- spec/FilesController.spec.js | 17 +- spec/Idempotency.spec.js | 358 +-- spec/LogsRouter.spec.js | 153 +- spec/Middlewares.spec.js | 122 +- spec/PagesRouter.spec.js | 336 +-- spec/Parse.Push.spec.js | 123 +- spec/ParseAPI.spec.js | 557 ++--- spec/ParseConfigKey.spec.js | 108 +- spec/ParseGeoPoint.spec.js | 197 +- spec/ParseGlobalConfig.spec.js | 8 +- spec/ParseGraphQLSchema.spec.js | 122 +- spec/ParseGraphQLServer.spec.js | 2681 +++++++++++----------- spec/ParseHooks.spec.js | 646 +++--- spec/ParseInstallation.spec.js | 236 +- spec/ParseLiveQuery.spec.js | 190 +- spec/ParseObject.spec.js | 5 +- spec/ParseQuery.Aggregate.spec.js | 649 +++--- spec/ParseQuery.FullTextSearch.spec.js | 134 +- spec/ParseQuery.spec.js | 545 ++--- spec/ParseRole.spec.js | 127 +- spec/ParseServerRESTController.spec.js | 31 +- spec/ParseUser.spec.js | 346 +-- spec/PasswordPolicy.spec.js | 626 ++--- spec/PointerPermissions.spec.js | 440 ++-- spec/PushController.spec.js | 406 ++-- spec/PushWorker.spec.js | 177 +- spec/RestQuery.spec.js | 121 +- spec/SchemaPerformance.spec.js | 35 +- spec/Uniqueness.spec.js | 41 +- spec/UserController.spec.js | 79 +- spec/Utils.spec.js | 14 +- spec/ValidationAndPasswordsReset.spec.js | 349 +-- spec/WinstonLoggerAdapter.spec.js | 87 +- spec/batch.spec.js | 4 +- spec/helper.js | 6 +- spec/rest.spec.js | 33 +- spec/schemas.spec.js | 175 +- spec/support/CurrentSpecReporter.js | 41 +- spec/support/MockLdapServer.js | 5 +- spec/vulnerabilities.spec.js | 65 +- src/Auth.js | 8 +- src/Config.js | 8 +- src/Controllers/DatabaseController.js | 16 +- src/Controllers/FilesController.js | 4 +- src/Controllers/SchemaController.js | 20 +- src/Controllers/UserController.js | 13 +- src/GraphQL/parseGraphQLUtils.js | 4 +- src/ParseServer.js | 4 +- src/PromiseRouter.js | 2 - src/RestQuery.js | 9 +- src/RestWrite.js | 83 +- src/Routers/ClassesRouter.js | 68 +- src/Routers/FunctionsRouter.js | 50 +- src/Routers/GlobalConfigRouter.js | 54 +- src/Routers/UsersRouter.js | 47 +- src/SchemaMigrations/DefinedSchemas.js | 16 +- src/StatusHandler.js | 3 +- src/TestUtils.js | 2 +- src/Triggers/ConfigTrigger.js | 28 +- src/Triggers/FileTrigger.js | 62 +- src/Triggers/Logger.js | 21 +- src/Triggers/QueryTrigger.js | 12 +- src/Triggers/Trigger.js | 11 +- src/Triggers/TriggerResponse.js | 2 +- src/Triggers/TriggerStore.js | 2 +- src/Triggers/Validator.js | 8 +- src/Utils.js | 2 +- src/cloud-code/Parse.Cloud.js | 2 +- src/middlewares.js | 20 +- src/rest.js | 39 +- src/triggers.js | 28 +- src/vendor/mongodbUrl.js | 247 +- 82 files changed, 7603 insertions(+), 6668 deletions(-) diff --git a/spec/AudienceRouter.spec.js b/spec/AudienceRouter.spec.js index 1525147a40..da0ebdc96c 100644 --- a/spec/AudienceRouter.spec.js +++ b/spec/AudienceRouter.spec.js @@ -317,59 +317,62 @@ describe('AudiencesRouter', () => { ); }); - it_id('af1111b5-3251-4b40-8f06-fb0fc624fa91')(it_exclude_dbs(['postgres']))('should support legacy parse.com audience fields', done => { - const database = Config.get(Parse.applicationId).database.adapter.database; - const now = new Date(); - Parse._request( - 'POST', - 'push_audiences', - { name: 'My Audience', query: JSON.stringify({ deviceType: 'ios' }) }, - { useMasterKey: true } - ).then(audience => { - database - .collection('test__Audience') - .updateOne( - { _id: audience.objectId }, - { - $set: { - times_used: 1, - _last_used: now, - }, - } - ) - .then(result => { - expect(result).toBeTruthy(); + it_id('af1111b5-3251-4b40-8f06-fb0fc624fa91')(it_exclude_dbs(['postgres']))( + 'should support legacy parse.com audience fields', + done => { + const database = Config.get(Parse.applicationId).database.adapter.database; + const now = new Date(); + Parse._request( + 'POST', + 'push_audiences', + { name: 'My Audience', query: JSON.stringify({ deviceType: 'ios' }) }, + { useMasterKey: true } + ).then(audience => { + database + .collection('test__Audience') + .updateOne( + { _id: audience.objectId }, + { + $set: { + times_used: 1, + _last_used: now, + }, + } + ) + .then(result => { + expect(result).toBeTruthy(); - database - .collection('test__Audience') - .find({ _id: audience.objectId }) - .toArray() - .then(rows => { - expect(rows[0]['times_used']).toEqual(1); - expect(rows[0]['_last_used']).toEqual(now); - Parse._request( - 'GET', - 'push_audiences/' + audience.objectId, - {}, - { useMasterKey: true } - ) - .then(audience => { - expect(audience.name).toEqual('My Audience'); - expect(audience.query.deviceType).toEqual('ios'); - expect(audience.timesUsed).toEqual(1); - expect(audience.lastUsed).toEqual(now.toISOString()); - done(); - }) - .catch(error => { - done.fail(error); - }); - }) - .catch(error => { - done.fail(error); - }); - }); - }); - }); + database + .collection('test__Audience') + .find({ _id: audience.objectId }) + .toArray() + .then(rows => { + expect(rows[0]['times_used']).toEqual(1); + expect(rows[0]['_last_used']).toEqual(now); + Parse._request( + 'GET', + 'push_audiences/' + audience.objectId, + {}, + { useMasterKey: true } + ) + .then(audience => { + expect(audience.name).toEqual('My Audience'); + expect(audience.query.deviceType).toEqual('ios'); + expect(audience.timesUsed).toEqual(1); + expect(audience.lastUsed).toEqual(now.toISOString()); + done(); + }) + .catch(error => { + done.fail(error); + }); + }) + .catch(error => { + done.fail(error); + }); + }); + }); + } + ); it('should be able to search on audiences', done => { Parse._request( diff --git a/spec/Auth.spec.js b/spec/Auth.spec.js index 4284c7365b..88ec3d5648 100644 --- a/spec/Auth.spec.js +++ b/spec/Auth.spec.js @@ -237,16 +237,10 @@ describe('extendSessionOnUse', () => { const { shouldUpdateSessionExpiry } = require('../lib/Auth'); let update = new Date(Date.now() - 86410 * 1000); - const res = shouldUpdateSessionExpiry( - { sessionLength: 86460 }, - { updatedAt: update } - ); + const res = shouldUpdateSessionExpiry({ sessionLength: 86460 }, { updatedAt: update }); update = new Date(Date.now() - 43210 * 1000); - const res2 = shouldUpdateSessionExpiry( - { sessionLength: 86460 }, - { updatedAt: update } - ); + const res2 = shouldUpdateSessionExpiry({ sessionLength: 86460 }, { updatedAt: update }); expect(res).toBe(true); expect(res2).toBe(false); diff --git a/spec/AuthenticationAdapters.spec.js b/spec/AuthenticationAdapters.spec.js index 32fcdca891..0e796a5347 100644 --- a/spec/AuthenticationAdapters.spec.js +++ b/spec/AuthenticationAdapters.spec.js @@ -2022,26 +2022,29 @@ describe('facebook limited auth adapter', () => { } }); - it_id('7bfa55ab-8fd7-4526-992e-6de3df16bf9c')(it)('should use algorithm from key header to verify id_token (facebook.com)', async () => { - const fakeClaim = { - iss: 'https://www.facebook.com', - aud: 'secret', - exp: Date.now(), - sub: 'the_user_id', - }; - const fakeDecodedToken = { header: { kid: '123', alg: 'RS256' } }; - const fakeSigningKey = { kid: '123', rsaPublicKey: 'the_rsa_public_key' }; - spyOn(authUtils, 'getHeaderFromToken').and.callFake(() => fakeDecodedToken.header); - spyOn(authUtils, 'getSigningKey').and.resolveTo(fakeSigningKey); - spyOn(jwt, 'verify').and.callFake(() => fakeClaim); + it_id('7bfa55ab-8fd7-4526-992e-6de3df16bf9c')(it)( + 'should use algorithm from key header to verify id_token (facebook.com)', + async () => { + const fakeClaim = { + iss: 'https://www.facebook.com', + aud: 'secret', + exp: Date.now(), + sub: 'the_user_id', + }; + const fakeDecodedToken = { header: { kid: '123', alg: 'RS256' } }; + const fakeSigningKey = { kid: '123', rsaPublicKey: 'the_rsa_public_key' }; + spyOn(authUtils, 'getHeaderFromToken').and.callFake(() => fakeDecodedToken.header); + spyOn(authUtils, 'getSigningKey').and.resolveTo(fakeSigningKey); + spyOn(jwt, 'verify').and.callFake(() => fakeClaim); - const result = await facebook.validateAuthData( - { id: 'the_user_id', token: 'the_token' }, - { clientId: 'secret' } - ); - expect(result).toEqual(fakeClaim); - expect(jwt.verify.calls.first().args[2].algorithms).toEqual(fakeDecodedToken.header.alg); - }); + const result = await facebook.validateAuthData( + { id: 'the_user_id', token: 'the_token' }, + { clientId: 'secret' } + ); + expect(result).toEqual(fakeClaim); + expect(jwt.verify.calls.first().args[2].algorithms).toEqual(fakeDecodedToken.header.alg); + } + ); it('should not verify invalid id_token', async () => { const fakeDecodedToken = { header: { kid: '123', alg: 'RS256' } }; @@ -2072,89 +2075,101 @@ describe('facebook limited auth adapter', () => { } }); - it_id('4bcb1a1a-11f8-4e12-a3f6-73f7e25e355a')(it)('using client id as string) should verify id_token (facebook.com)', async () => { - const fakeClaim = { - iss: 'https://www.facebook.com', - aud: 'secret', - exp: Date.now(), - sub: 'the_user_id', - }; - const fakeDecodedToken = { header: { kid: '123', alg: 'RS256' } }; - const fakeSigningKey = { kid: '123', rsaPublicKey: 'the_rsa_public_key' }; - spyOn(authUtils, 'getHeaderFromToken').and.callFake(() => fakeDecodedToken); - spyOn(authUtils, 'getSigningKey').and.resolveTo(fakeSigningKey); - spyOn(jwt, 'verify').and.callFake(() => fakeClaim); - - const result = await facebook.validateAuthData( - { id: 'the_user_id', token: 'the_token' }, - { clientId: 'secret' } - ); - expect(result).toEqual(fakeClaim); - }); - - it_id('c521a272-2ac2-4d8b-b5ed-ea250336d8b1')(it)('(using client id as array) should verify id_token (facebook.com)', async () => { - const fakeClaim = { - iss: 'https://www.facebook.com', - aud: 'secret', - exp: Date.now(), - sub: 'the_user_id', - }; - const fakeDecodedToken = { header: { kid: '123', alg: 'RS256' } }; - const fakeSigningKey = { kid: '123', rsaPublicKey: 'the_rsa_public_key' }; - spyOn(authUtils, 'getHeaderFromToken').and.callFake(() => fakeDecodedToken); - spyOn(authUtils, 'getSigningKey').and.resolveTo(fakeSigningKey); - spyOn(jwt, 'verify').and.callFake(() => fakeClaim); - - const result = await facebook.validateAuthData( - { id: 'the_user_id', token: 'the_token' }, - { clientId: ['secret'] } - ); - expect(result).toEqual(fakeClaim); - }); - - it_id('e3f16404-18e9-4a87-a555-4710cfbdac67')(it)('(using client id as array with multiple items) should verify id_token (facebook.com)', async () => { - const fakeClaim = { - iss: 'https://www.facebook.com', - aud: 'secret', - exp: Date.now(), - sub: 'the_user_id', - }; - const fakeDecodedToken = { header: { kid: '123', alg: 'RS256' } }; - const fakeSigningKey = { kid: '123', rsaPublicKey: 'the_rsa_public_key' }; - spyOn(authUtils, 'getHeaderFromToken').and.callFake(() => fakeDecodedToken); - spyOn(authUtils, 'getSigningKey').and.resolveTo(fakeSigningKey); - spyOn(jwt, 'verify').and.callFake(() => fakeClaim); - - const result = await facebook.validateAuthData( - { id: 'the_user_id', token: 'the_token' }, - { clientId: ['secret', 'secret 123'] } - ); - expect(result).toEqual(fakeClaim); - }); - - it_id('549c33a1-3a6b-4732-8cf6-8f010ad4569c')(it)('(using client id as string) should throw error with with invalid jwt issuer (facebook.com)', async () => { - const fakeClaim = { - iss: 'https://not.facebook.com', - sub: 'the_user_id', - }; - const fakeDecodedToken = { header: { kid: '123', alg: 'RS256' } }; - const fakeSigningKey = { kid: '123', rsaPublicKey: 'the_rsa_public_key' }; - spyOn(authUtils, 'getHeaderFromToken').and.callFake(() => fakeDecodedToken); - spyOn(authUtils, 'getSigningKey').and.resolveTo(fakeSigningKey); - spyOn(jwt, 'verify').and.callFake(() => fakeClaim); - - try { - await facebook.validateAuthData( + it_id('4bcb1a1a-11f8-4e12-a3f6-73f7e25e355a')(it)( + 'using client id as string) should verify id_token (facebook.com)', + async () => { + const fakeClaim = { + iss: 'https://www.facebook.com', + aud: 'secret', + exp: Date.now(), + sub: 'the_user_id', + }; + const fakeDecodedToken = { header: { kid: '123', alg: 'RS256' } }; + const fakeSigningKey = { kid: '123', rsaPublicKey: 'the_rsa_public_key' }; + spyOn(authUtils, 'getHeaderFromToken').and.callFake(() => fakeDecodedToken); + spyOn(authUtils, 'getSigningKey').and.resolveTo(fakeSigningKey); + spyOn(jwt, 'verify').and.callFake(() => fakeClaim); + + const result = await facebook.validateAuthData( { id: 'the_user_id', token: 'the_token' }, { clientId: 'secret' } ); - fail(); - } catch (e) { - expect(e.message).toBe( - 'id token not issued by correct OpenID provider - expected: https://www.facebook.com | from: https://not.facebook.com' + expect(result).toEqual(fakeClaim); + } + ); + + it_id('c521a272-2ac2-4d8b-b5ed-ea250336d8b1')(it)( + '(using client id as array) should verify id_token (facebook.com)', + async () => { + const fakeClaim = { + iss: 'https://www.facebook.com', + aud: 'secret', + exp: Date.now(), + sub: 'the_user_id', + }; + const fakeDecodedToken = { header: { kid: '123', alg: 'RS256' } }; + const fakeSigningKey = { kid: '123', rsaPublicKey: 'the_rsa_public_key' }; + spyOn(authUtils, 'getHeaderFromToken').and.callFake(() => fakeDecodedToken); + spyOn(authUtils, 'getSigningKey').and.resolveTo(fakeSigningKey); + spyOn(jwt, 'verify').and.callFake(() => fakeClaim); + + const result = await facebook.validateAuthData( + { id: 'the_user_id', token: 'the_token' }, + { clientId: ['secret'] } ); + expect(result).toEqual(fakeClaim); } - }); + ); + + it_id('e3f16404-18e9-4a87-a555-4710cfbdac67')(it)( + '(using client id as array with multiple items) should verify id_token (facebook.com)', + async () => { + const fakeClaim = { + iss: 'https://www.facebook.com', + aud: 'secret', + exp: Date.now(), + sub: 'the_user_id', + }; + const fakeDecodedToken = { header: { kid: '123', alg: 'RS256' } }; + const fakeSigningKey = { kid: '123', rsaPublicKey: 'the_rsa_public_key' }; + spyOn(authUtils, 'getHeaderFromToken').and.callFake(() => fakeDecodedToken); + spyOn(authUtils, 'getSigningKey').and.resolveTo(fakeSigningKey); + spyOn(jwt, 'verify').and.callFake(() => fakeClaim); + + const result = await facebook.validateAuthData( + { id: 'the_user_id', token: 'the_token' }, + { clientId: ['secret', 'secret 123'] } + ); + expect(result).toEqual(fakeClaim); + } + ); + + it_id('549c33a1-3a6b-4732-8cf6-8f010ad4569c')(it)( + '(using client id as string) should throw error with with invalid jwt issuer (facebook.com)', + async () => { + const fakeClaim = { + iss: 'https://not.facebook.com', + sub: 'the_user_id', + }; + const fakeDecodedToken = { header: { kid: '123', alg: 'RS256' } }; + const fakeSigningKey = { kid: '123', rsaPublicKey: 'the_rsa_public_key' }; + spyOn(authUtils, 'getHeaderFromToken').and.callFake(() => fakeDecodedToken); + spyOn(authUtils, 'getSigningKey').and.resolveTo(fakeSigningKey); + spyOn(jwt, 'verify').and.callFake(() => fakeClaim); + + try { + await facebook.validateAuthData( + { id: 'the_user_id', token: 'the_token' }, + { clientId: 'secret' } + ); + fail(); + } catch (e) { + expect(e.message).toBe( + 'id token not issued by correct OpenID provider - expected: https://www.facebook.com | from: https://not.facebook.com' + ); + } + } + ); // TODO: figure out a way to generate our own facebook signed tokens, perhaps with a parse facebook account // and a private key @@ -2263,28 +2278,31 @@ describe('facebook limited auth adapter', () => { } }); - it_id('c194d902-e697-46c9-a303-82c2d914473c')(it)('should throw error with with invalid user id (facebook.com)', async () => { - const fakeClaim = { - iss: 'https://www.facebook.com', - aud: 'invalid_client_id', - sub: 'a_different_user_id', - }; - const fakeDecodedToken = { header: { kid: '123', alg: 'RS256' } }; - const fakeSigningKey = { kid: '123', rsaPublicKey: 'the_rsa_public_key' }; - spyOn(authUtils, 'getHeaderFromToken').and.callFake(() => fakeDecodedToken); - spyOn(authUtils, 'getSigningKey').and.resolveTo(fakeSigningKey); - spyOn(jwt, 'verify').and.callFake(() => fakeClaim); - - try { - await facebook.validateAuthData( - { id: 'the_user_id', token: 'the_token' }, - { clientId: 'secret' } - ); - fail(); - } catch (e) { - expect(e.message).toBe('auth data is invalid for this user.'); + it_id('c194d902-e697-46c9-a303-82c2d914473c')(it)( + 'should throw error with with invalid user id (facebook.com)', + async () => { + const fakeClaim = { + iss: 'https://www.facebook.com', + aud: 'invalid_client_id', + sub: 'a_different_user_id', + }; + const fakeDecodedToken = { header: { kid: '123', alg: 'RS256' } }; + const fakeSigningKey = { kid: '123', rsaPublicKey: 'the_rsa_public_key' }; + spyOn(authUtils, 'getHeaderFromToken').and.callFake(() => fakeDecodedToken); + spyOn(authUtils, 'getSigningKey').and.resolveTo(fakeSigningKey); + spyOn(jwt, 'verify').and.callFake(() => fakeClaim); + + try { + await facebook.validateAuthData( + { id: 'the_user_id', token: 'the_token' }, + { clientId: 'secret' } + ); + fail(); + } catch (e) { + expect(e.message).toBe('auth data is invalid for this user.'); + } } - }); + ); }); describe('OTP TOTP auth adatper', () => { diff --git a/spec/AuthenticationAdaptersV2.spec.js b/spec/AuthenticationAdaptersV2.spec.js index d5aa8a5898..92ea18a0d5 100644 --- a/spec/AuthenticationAdaptersV2.spec.js +++ b/spec/AuthenticationAdaptersV2.spec.js @@ -488,7 +488,7 @@ describe('Auth Adapter features', () => { await user.save({ authData: { - baseAdapter: { id: 'baseAdapter', token: "sometoken1" }, + baseAdapter: { id: 'baseAdapter', token: 'sometoken1' }, }, }); @@ -497,7 +497,7 @@ describe('Auth Adapter features', () => { const user2 = new Parse.User(); await user2.save({ authData: { - baseAdapter: { id: 'baseAdapter', token: "sometoken2" }, + baseAdapter: { id: 'baseAdapter', token: 'sometoken2' }, }, }); diff --git a/spec/CLI.spec.js b/spec/CLI.spec.js index f78b542e52..04295e969b 100644 --- a/spec/CLI.spec.js +++ b/spec/CLI.spec.js @@ -213,7 +213,11 @@ describe('execution', () => { childProcess.stdout.on('data', data => { data = data.toString(); aggregatedData.push(data); - if (requiredData.every(required => aggregatedData.some(aggregated => aggregated.includes(required)))) { + if ( + requiredData.every(required => + aggregatedData.some(aggregated => aggregated.includes(required)) + ) + ) { done(); } }); @@ -261,70 +265,79 @@ describe('execution', () => { handleError(childProcess, done); }); - it_id('d7165081-b133-4cba-901b-19128ce41301')(it)('should start Parse Server with GraphQL', async done => { - const env = { ...process.env }; - env.NODE_OPTIONS = '--dns-result-order=ipv4first --trace-deprecation'; - childProcess = spawn( - binPath, - [ - '--appId', - 'test', - '--masterKey', - 'test', - '--databaseURI', - databaseURI, - '--port', - '1340', - '--mountGraphQL', - ], - { env } - ); - handleStdout(childProcess, done, aggregatedData, [ - 'parse-server running on', - 'GraphQL running on', - ]); - handleStderr(childProcess, done); - handleError(childProcess, done); - }); + it_id('d7165081-b133-4cba-901b-19128ce41301')(it)( + 'should start Parse Server with GraphQL', + async done => { + const env = { ...process.env }; + env.NODE_OPTIONS = '--dns-result-order=ipv4first --trace-deprecation'; + childProcess = spawn( + binPath, + [ + '--appId', + 'test', + '--masterKey', + 'test', + '--databaseURI', + databaseURI, + '--port', + '1340', + '--mountGraphQL', + ], + { env } + ); + handleStdout(childProcess, done, aggregatedData, [ + 'parse-server running on', + 'GraphQL running on', + ]); + handleStderr(childProcess, done); + handleError(childProcess, done); + } + ); - it_id('2769cdb4-ce8a-484d-8a91-635b5894ba7e')(it)('should start Parse Server with GraphQL and Playground', async done => { - const env = { ...process.env }; - env.NODE_OPTIONS = '--dns-result-order=ipv4first --trace-deprecation'; - childProcess = spawn( - binPath, - [ - '--appId', - 'test', - '--masterKey', - 'test', - '--databaseURI', - databaseURI, - '--port', - '1341', - '--mountGraphQL', - '--mountPlayground', - ], - { env } - ); - handleStdout(childProcess, done, aggregatedData, [ - 'parse-server running on', - 'Playground running on', - 'GraphQL running on', - ]); - handleStderr(childProcess, done); - handleError(childProcess, done); - }); + it_id('2769cdb4-ce8a-484d-8a91-635b5894ba7e')(it)( + 'should start Parse Server with GraphQL and Playground', + async done => { + const env = { ...process.env }; + env.NODE_OPTIONS = '--dns-result-order=ipv4first --trace-deprecation'; + childProcess = spawn( + binPath, + [ + '--appId', + 'test', + '--masterKey', + 'test', + '--databaseURI', + databaseURI, + '--port', + '1341', + '--mountGraphQL', + '--mountPlayground', + ], + { env } + ); + handleStdout(childProcess, done, aggregatedData, [ + 'parse-server running on', + 'Playground running on', + 'GraphQL running on', + ]); + handleStderr(childProcess, done); + handleError(childProcess, done); + } + ); - it_id('23caddd7-bfea-4869-8bd4-0f2cd283c8bd')(it)('can start Parse Server with auth via CLI', done => { - const env = { ...process.env }; - env.NODE_OPTIONS = '--dns-result-order=ipv4first --trace-deprecation'; - childProcess = spawn( - binPath, - ['--databaseURI', databaseURI, './spec/configs/CLIConfigAuth.json'], - { env } - ); - handleStdout(childProcess, done, aggregatedData, ['parse-server running on']); - handleStderr(childProcess, done); - handleError(childProcess, done); - }); + it_id('23caddd7-bfea-4869-8bd4-0f2cd283c8bd')(it)( + 'can start Parse Server with auth via CLI', + done => { + const env = { ...process.env }; + env.NODE_OPTIONS = '--dns-result-order=ipv4first --trace-deprecation'; + childProcess = spawn( + binPath, + ['--databaseURI', databaseURI, './spec/configs/CLIConfigAuth.json'], + { env } + ); + handleStdout(childProcess, done, aggregatedData, ['parse-server running on']); + handleStderr(childProcess, done); + handleError(childProcess, done); + } + ); }); diff --git a/spec/CloudCode.Validator.spec.js b/spec/CloudCode.Validator.spec.js index 11ccc82766..9b33ddc213 100644 --- a/spec/CloudCode.Validator.spec.js +++ b/spec/CloudCode.Validator.spec.js @@ -734,37 +734,43 @@ describe('cloud validator', () => { done(); }); - it_id('893eec0c-41bd-4adf-8f0a-306087ad8d61')(it)('basic beforeSave Parse.Config skipWithMasterKey', async () => { - Parse.Cloud.beforeSave( - Parse.Config, - () => { - throw 'beforeSaveFile should have resolved using master key.'; - }, - { - skipWithMasterKey: true, - } - ); - const config = await testConfig(); - expect(config.get('internal')).toBe('i'); - expect(config.get('string')).toBe('s'); - expect(config.get('number')).toBe(12); - }); - - it_id('91e739a4-6a38-405c-8f83-f36d48220734')(it)('basic afterSave Parse.Config skipWithMasterKey', async () => { - Parse.Cloud.afterSave( - Parse.Config, - () => { - throw 'beforeSaveFile should have resolved using master key.'; - }, - { - skipWithMasterKey: true, - } - ); - const config = await testConfig(); - expect(config.get('internal')).toBe('i'); - expect(config.get('string')).toBe('s'); - expect(config.get('number')).toBe(12); - }); + it_id('893eec0c-41bd-4adf-8f0a-306087ad8d61')(it)( + 'basic beforeSave Parse.Config skipWithMasterKey', + async () => { + Parse.Cloud.beforeSave( + Parse.Config, + () => { + throw 'beforeSaveFile should have resolved using master key.'; + }, + { + skipWithMasterKey: true, + } + ); + const config = await testConfig(); + expect(config.get('internal')).toBe('i'); + expect(config.get('string')).toBe('s'); + expect(config.get('number')).toBe(12); + } + ); + + it_id('91e739a4-6a38-405c-8f83-f36d48220734')(it)( + 'basic afterSave Parse.Config skipWithMasterKey', + async () => { + Parse.Cloud.afterSave( + Parse.Config, + () => { + throw 'beforeSaveFile should have resolved using master key.'; + }, + { + skipWithMasterKey: true, + } + ); + const config = await testConfig(); + expect(config.get('internal')).toBe('i'); + expect(config.get('string')).toBe('s'); + expect(config.get('number')).toBe(12); + } + ); it('beforeSave validateMasterKey and skipWithMasterKey fail', async function (done) { Parse.Cloud.beforeSave( @@ -1531,23 +1537,29 @@ describe('cloud validator', () => { } }); - it_id('32ca1a99-7f2b-429d-a7cf-62b6661d0af6')(it)('validate beforeSave Parse.Config', async () => { - Parse.Cloud.beforeSave(Parse.Config, () => {}, validatorSuccess); - const config = await testConfig(); - expect(config.get('internal')).toBe('i'); - expect(config.get('string')).toBe('s'); - expect(config.get('number')).toBe(12); - }); + it_id('32ca1a99-7f2b-429d-a7cf-62b6661d0af6')(it)( + 'validate beforeSave Parse.Config', + async () => { + Parse.Cloud.beforeSave(Parse.Config, () => {}, validatorSuccess); + const config = await testConfig(); + expect(config.get('internal')).toBe('i'); + expect(config.get('string')).toBe('s'); + expect(config.get('number')).toBe(12); + } + ); - it_id('c84d11e7-d09c-4843-ad98-f671511bf612')(it)('validate beforeSave Parse.Config fail', async () => { - Parse.Cloud.beforeSave(Parse.Config, () => {}, validatorFail); - try { - await testConfig(); - fail('cloud function should have failed.'); - } catch (e) { - expect(e.code).toBe(Parse.Error.VALIDATION_ERROR); + it_id('c84d11e7-d09c-4843-ad98-f671511bf612')(it)( + 'validate beforeSave Parse.Config fail', + async () => { + Parse.Cloud.beforeSave(Parse.Config, () => {}, validatorFail); + try { + await testConfig(); + fail('cloud function should have failed.'); + } catch (e) { + expect(e.code).toBe(Parse.Error.VALIDATION_ERROR); + } } - }); + ); it_id('b18b9a6a-0e35-4b60-9771-30f53501df3c')(it)('validate afterSave Parse.Config', async () => { Parse.Cloud.afterSave(Parse.Config, () => {}, validatorSuccess); @@ -1557,15 +1569,18 @@ describe('cloud validator', () => { expect(config.get('number')).toBe(12); }); - it_id('ef761222-1758-4614-b984-da84d73fc10c')(it)('validate afterSave Parse.Config fail', async () => { - Parse.Cloud.afterSave(Parse.Config, () => {}, validatorFail); - try { - await testConfig(); - fail('cloud function should have failed.'); - } catch (e) { - expect(e.code).toBe(Parse.Error.VALIDATION_ERROR); + it_id('ef761222-1758-4614-b984-da84d73fc10c')(it)( + 'validate afterSave Parse.Config fail', + async () => { + Parse.Cloud.afterSave(Parse.Config, () => {}, validatorFail); + try { + await testConfig(); + fail('cloud function should have failed.'); + } catch (e) { + expect(e.code).toBe(Parse.Error.VALIDATION_ERROR); + } } - }); + ); it('Should have validator', async done => { Parse.Cloud.define( diff --git a/spec/CloudCode.spec.js b/spec/CloudCode.spec.js index 8d7031f0a9..da7af37c1a 100644 --- a/spec/CloudCode.spec.js +++ b/spec/CloudCode.spec.js @@ -2919,55 +2919,61 @@ describe('afterFind hooks', () => { }).toThrow('Only the _Session class is allowed for the afterLogout trigger.'); }); - it_id('c16159b5-e8ee-42d5-8fe3-e2f7c006881d')(it)('should skip afterFind hooks for aggregate', done => { - const hook = { - method: function () { - return Promise.reject(); - }, - }; - spyOn(hook, 'method').and.callThrough(); - Parse.Cloud.afterFind('MyObject', hook.method); - const obj = new Parse.Object('MyObject'); - const pipeline = [ - { - $group: { _id: {} }, - }, - ]; - obj - .save() - .then(() => { - const query = new Parse.Query('MyObject'); - return query.aggregate(pipeline); - }) - .then(results => { - expect(results[0].objectId).toEqual(null); - expect(hook.method).not.toHaveBeenCalled(); - done(); - }); - }); + it_id('c16159b5-e8ee-42d5-8fe3-e2f7c006881d')(it)( + 'should skip afterFind hooks for aggregate', + done => { + const hook = { + method: function () { + return Promise.reject(); + }, + }; + spyOn(hook, 'method').and.callThrough(); + Parse.Cloud.afterFind('MyObject', hook.method); + const obj = new Parse.Object('MyObject'); + const pipeline = [ + { + $group: { _id: {} }, + }, + ]; + obj + .save() + .then(() => { + const query = new Parse.Query('MyObject'); + return query.aggregate(pipeline); + }) + .then(results => { + expect(results[0].objectId).toEqual(null); + expect(hook.method).not.toHaveBeenCalled(); + done(); + }); + } + ); - it_id('ca55c90d-36db-422c-9060-a30583ce5224')(it)('should skip afterFind hooks for distinct', done => { - const hook = { - method: function () { - return Promise.reject(); - }, - }; - spyOn(hook, 'method').and.callThrough(); - Parse.Cloud.afterFind('MyObject', hook.method); - const obj = new Parse.Object('MyObject'); - obj.set('score', 10); - obj - .save() - .then(() => { - const query = new Parse.Query('MyObject'); - return query.distinct('score'); - }) - .then(results => { - expect(results[0]).toEqual(10); - expect(hook.method).not.toHaveBeenCalled(); - done(); - }); - }); + it_id('ca55c90d-36db-422c-9060-a30583ce5224')(it)( + 'should skip afterFind hooks for distinct', + done => { + const hook = { + method: function () { + return Promise.reject(); + }, + }; + spyOn(hook, 'method').and.callThrough(); + Parse.Cloud.afterFind('MyObject', hook.method); + const obj = new Parse.Object('MyObject'); + obj.set('score', 10); + obj + .save() + .then(() => { + const query = new Parse.Query('MyObject'); + return query.distinct('score'); + }) + .then(results => { + expect(results[0]).toEqual(10); + expect(hook.method).not.toHaveBeenCalled(); + done(); + }); + } + ); it('should throw error if context header is malformed', async () => { let calledBefore = false; @@ -3033,37 +3039,40 @@ describe('afterFind hooks', () => { expect(calledAfter).toBe(false); }); - it_id('55ef1741-cf72-4a7c-a029-00cb75f53233')(it)('should expose context in beforeSave/afterSave via header', async () => { - let calledBefore = false; - let calledAfter = false; - Parse.Cloud.beforeSave('TestObject', req => { - expect(req.object.get('foo')).toEqual('bar'); - expect(req.context.otherKey).toBe(1); - expect(req.context.key).toBe('value'); - calledBefore = true; - }); - Parse.Cloud.afterSave('TestObject', req => { - expect(req.object.get('foo')).toEqual('bar'); - expect(req.context.otherKey).toBe(1); - expect(req.context.key).toBe('value'); - calledAfter = true; - }); - const req = request({ - method: 'POST', - url: 'http://localhost:8378/1/classes/TestObject', - headers: { - 'X-Parse-Application-Id': 'test', - 'X-Parse-REST-API-Key': 'rest', - 'X-Parse-Cloud-Context': '{"key":"value","otherKey":1}', - }, - body: { - foo: 'bar', - }, - }); - await req; - expect(calledBefore).toBe(true); - expect(calledAfter).toBe(true); - }); + it_id('55ef1741-cf72-4a7c-a029-00cb75f53233')(it)( + 'should expose context in beforeSave/afterSave via header', + async () => { + let calledBefore = false; + let calledAfter = false; + Parse.Cloud.beforeSave('TestObject', req => { + expect(req.object.get('foo')).toEqual('bar'); + expect(req.context.otherKey).toBe(1); + expect(req.context.key).toBe('value'); + calledBefore = true; + }); + Parse.Cloud.afterSave('TestObject', req => { + expect(req.object.get('foo')).toEqual('bar'); + expect(req.context.otherKey).toBe(1); + expect(req.context.key).toBe('value'); + calledAfter = true; + }); + const req = request({ + method: 'POST', + url: 'http://localhost:8378/1/classes/TestObject', + headers: { + 'X-Parse-Application-Id': 'test', + 'X-Parse-REST-API-Key': 'rest', + 'X-Parse-Cloud-Context': '{"key":"value","otherKey":1}', + }, + body: { + foo: 'bar', + }, + }); + await req; + expect(calledBefore).toBe(true); + expect(calledAfter).toBe(true); + } + ); it('should override header context with body context in beforeSave/afterSave', async () => { let calledBefore = false; @@ -3347,20 +3356,23 @@ describe('beforeLogin hook', () => { expect(response).toEqual(error); }); - it_id('5656d6d7-65ef-43d1-8ca6-6942ae3614d5')(it)('should have expected data in request in beforeLogin', async done => { - Parse.Cloud.beforeLogin(req => { - expect(req.object).toBeDefined(); - expect(req.user).toBeUndefined(); - expect(req.headers).toBeDefined(); - expect(req.ip).toBeDefined(); - expect(req.installationId).toBeDefined(); - expect(req.context).toBeDefined(); - }); + it_id('5656d6d7-65ef-43d1-8ca6-6942ae3614d5')(it)( + 'should have expected data in request in beforeLogin', + async done => { + Parse.Cloud.beforeLogin(req => { + expect(req.object).toBeDefined(); + expect(req.user).toBeUndefined(); + expect(req.headers).toBeDefined(); + expect(req.ip).toBeDefined(); + expect(req.installationId).toBeDefined(); + expect(req.context).toBeDefined(); + }); - await Parse.User.signUp('tupac', 'shakur'); - await Parse.User.logIn('tupac', 'shakur'); - done(); - }); + await Parse.User.signUp('tupac', 'shakur'); + await Parse.User.logIn('tupac', 'shakur'); + done(); + } + ); it('afterFind should not be triggered when saving an object', async () => { let beforeSaves = 0; @@ -3464,20 +3476,23 @@ describe('afterLogin hook', () => { done(); }); - it_id('e86155c4-62e1-4c6e-ab4a-9ac6c87c60f2')(it)('should have expected data in request in afterLogin', async done => { - Parse.Cloud.afterLogin(req => { - expect(req.object).toBeDefined(); - expect(req.user).toBeDefined(); - expect(req.headers).toBeDefined(); - expect(req.ip).toBeDefined(); - expect(req.installationId).toBeDefined(); - expect(req.context).toBeDefined(); - }); + it_id('e86155c4-62e1-4c6e-ab4a-9ac6c87c60f2')(it)( + 'should have expected data in request in afterLogin', + async done => { + Parse.Cloud.afterLogin(req => { + expect(req.object).toBeDefined(); + expect(req.user).toBeDefined(); + expect(req.headers).toBeDefined(); + expect(req.ip).toBeDefined(); + expect(req.installationId).toBeDefined(); + expect(req.context).toBeDefined(); + }); - await Parse.User.signUp('testuser', 'p@ssword'); - await Parse.User.logIn('testuser', 'p@ssword'); - done(); - }); + await Parse.User.signUp('testuser', 'p@ssword'); + await Parse.User.logIn('testuser', 'p@ssword'); + done(); + } + ); it('context options should override _context object property when saving a new object', async () => { Parse.Cloud.beforeSave('TestObject', req => { @@ -3926,61 +3941,70 @@ describe('Cloud Config hooks', () => { return Parse.Config.save({ internal: 'i', string: 's', number: 12 }, { internal: true }); } - it_id('997fe20a-96f7-454a-a5b0-c155b8d02f05')(it)('beforeSave(Parse.Config) can run hook with new config', async () => { - let count = 0; - Parse.Cloud.beforeSave(Parse.Config, (req) => { - expect(req.object).toBeDefined(); - expect(req.original).toBeUndefined(); - expect(req.user).toBeUndefined(); - expect(req.headers).toBeDefined(); - expect(req.ip).toBeDefined(); - expect(req.installationId).toBeDefined(); - expect(req.context).toBeDefined(); - const config = req.object; + it_id('997fe20a-96f7-454a-a5b0-c155b8d02f05')(it)( + 'beforeSave(Parse.Config) can run hook with new config', + async () => { + let count = 0; + Parse.Cloud.beforeSave(Parse.Config, req => { + expect(req.object).toBeDefined(); + expect(req.original).toBeUndefined(); + expect(req.user).toBeUndefined(); + expect(req.headers).toBeDefined(); + expect(req.ip).toBeDefined(); + expect(req.installationId).toBeDefined(); + expect(req.context).toBeDefined(); + const config = req.object; + expect(config.get('internal')).toBe('i'); + expect(config.get('string')).toBe('s'); + expect(config.get('number')).toBe(12); + count += 1; + }); + await testConfig(); + const config = await Parse.Config.get({ useMasterKey: true }); expect(config.get('internal')).toBe('i'); expect(config.get('string')).toBe('s'); expect(config.get('number')).toBe(12); - count += 1; - }); - await testConfig(); - const config = await Parse.Config.get({ useMasterKey: true }); - expect(config.get('internal')).toBe('i'); - expect(config.get('string')).toBe('s'); - expect(config.get('number')).toBe(12); - expect(count).toBe(1); - }); + expect(count).toBe(1); + } + ); - it_id('06a9b66c-ffb4-43d1-a025-f7d2192500e7')(it)('beforeSave(Parse.Config) can run hook with existing config', async () => { - let count = 0; - Parse.Cloud.beforeSave(Parse.Config, (req) => { - if (count === 0) { - expect(req.object.get('number')).toBe(12); - expect(req.original).toBeUndefined(); - } - if (count === 1) { - expect(req.object.get('number')).toBe(13); - expect(req.original.get('number')).toBe(12); - } - count += 1; - }); - await testConfig(); - await Parse.Config.save({ number: 13 }); - expect(count).toBe(2); - }); + it_id('06a9b66c-ffb4-43d1-a025-f7d2192500e7')(it)( + 'beforeSave(Parse.Config) can run hook with existing config', + async () => { + let count = 0; + Parse.Cloud.beforeSave(Parse.Config, req => { + if (count === 0) { + expect(req.object.get('number')).toBe(12); + expect(req.original).toBeUndefined(); + } + if (count === 1) { + expect(req.object.get('number')).toBe(13); + expect(req.original.get('number')).toBe(12); + } + count += 1; + }); + await testConfig(); + await Parse.Config.save({ number: 13 }); + expect(count).toBe(2); + } + ); - it_id('ca76de8e-671b-4c2d-9535-bd28a855fa1a')(it)('beforeSave(Parse.Config) should not change config if nothing is returned', async () => { - let count = 0; - Parse.Cloud.beforeSave(Parse.Config, () => { - count += 1; - return; - }); - await testConfig(); - const config = await Parse.Config.get({ useMasterKey: true }); - expect(config.get('internal')).toBe('i'); - expect(config.get('string')).toBe('s'); - expect(config.get('number')).toBe(12); - expect(count).toBe(1); - }); + it_id('ca76de8e-671b-4c2d-9535-bd28a855fa1a')(it)( + 'beforeSave(Parse.Config) should not change config if nothing is returned', + async () => { + let count = 0; + Parse.Cloud.beforeSave(Parse.Config, () => { + count += 1; + return; + }); + await testConfig(); + const config = await Parse.Config.get({ useMasterKey: true }); + expect(config.get('internal')).toBe('i'); + expect(config.get('string')).toBe('s'); + expect(config.get('number')).toBe(12); + expect(count).toBe(1); + } + ); it('beforeSave(Parse.Config) throw custom error', async () => { Parse.Cloud.beforeSave(Parse.Config, () => { @@ -4021,60 +4045,69 @@ describe('Cloud Config hooks', () => { } }); - it_id('3e7a75c0-6c2e-4c7e-b042-6eb5f23acf94')(it)('afterSave(Parse.Config) can run hook with new config', async () => { - let count = 0; - Parse.Cloud.afterSave(Parse.Config, (req) => { - expect(req.object).toBeDefined(); - expect(req.original).toBeUndefined(); - expect(req.user).toBeUndefined(); - expect(req.headers).toBeDefined(); - expect(req.ip).toBeDefined(); - expect(req.installationId).toBeDefined(); - expect(req.context).toBeDefined(); - const config = req.object; + it_id('3e7a75c0-6c2e-4c7e-b042-6eb5f23acf94')(it)( + 'afterSave(Parse.Config) can run hook with new config', + async () => { + let count = 0; + Parse.Cloud.afterSave(Parse.Config, req => { + expect(req.object).toBeDefined(); + expect(req.original).toBeUndefined(); + expect(req.user).toBeUndefined(); + expect(req.headers).toBeDefined(); + expect(req.ip).toBeDefined(); + expect(req.installationId).toBeDefined(); + expect(req.context).toBeDefined(); + const config = req.object; + expect(config.get('internal')).toBe('i'); + expect(config.get('string')).toBe('s'); + expect(config.get('number')).toBe(12); + count += 1; + }); + await testConfig(); + const config = await Parse.Config.get({ useMasterKey: true }); expect(config.get('internal')).toBe('i'); expect(config.get('string')).toBe('s'); expect(config.get('number')).toBe(12); - count += 1; - }); - await testConfig(); - const config = await Parse.Config.get({ useMasterKey: true }); - expect(config.get('internal')).toBe('i'); - expect(config.get('string')).toBe('s'); - expect(config.get('number')).toBe(12); - expect(count).toBe(1); - }); - - it_id('5cffb28a-2924-4857-84bb-f5778d80372a')(it)('afterSave(Parse.Config) can run hook with existing config', async () => { - let count = 0; - Parse.Cloud.afterSave(Parse.Config, (req) => { - if (count === 0) { - expect(req.object.get('number')).toBe(12); - expect(req.original).toBeUndefined(); - } - if (count === 1) { - expect(req.object.get('number')).toBe(13); - expect(req.original.get('number')).toBe(12); - } - count += 1; - }); - await testConfig(); - await Parse.Config.save({ number: 13 }); - expect(count).toBe(2); - }); + expect(count).toBe(1); + } + ); - it_id('49883992-ce91-4797-85f9-7cce1f819407')(it)('afterSave(Parse.Config) should throw error', async () => { - Parse.Cloud.afterSave(Parse.Config, () => { - throw new Parse.Error(400, 'It should fail'); - }); - try { + it_id('5cffb28a-2924-4857-84bb-f5778d80372a')(it)( + 'afterSave(Parse.Config) can run hook with existing config', + async () => { + let count = 0; + Parse.Cloud.afterSave(Parse.Config, req => { + if (count === 0) { + expect(req.object.get('number')).toBe(12); + expect(req.original).toBeUndefined(); + } + if (count === 1) { + expect(req.object.get('number')).toBe(13); + expect(req.original.get('number')).toBe(12); + } + count += 1; + }); await testConfig(); - fail('error should have thrown'); - } catch (e) { - expect(e.code).toBe(400); - expect(e.message).toBe('It should fail'); + await Parse.Config.save({ number: 13 }); + expect(count).toBe(2); } - }); + ); + + it_id('49883992-ce91-4797-85f9-7cce1f819407')(it)( + 'afterSave(Parse.Config) should throw error', + async () => { + Parse.Cloud.afterSave(Parse.Config, () => { + throw new Parse.Error(400, 'It should fail'); + }); + try { + await testConfig(); + fail('error should have thrown'); + } catch (e) { + expect(e.code).toBe(400); + expect(e.message).toBe('It should fail'); + } + } + ); }); describe('sendEmail', () => { @@ -4114,10 +4147,10 @@ describe('custom HTTP codes', () => { headers: { 'X-Parse-Application-Id': 'test', 'X-Parse-REST-API-Key': 'rest', - } + }, }); - expect(request.status).toBe(201); + expect(request.status).toBe(201); }); it('should set custom headers in save hook', async () => { @@ -4130,7 +4163,7 @@ describe('custom HTTP codes', () => { headers: { 'X-Parse-Application-Id': 'test', 'X-Parse-REST-API-Key': 'rest', - } + }, }); expect(request.headers.get('X-Custom-Header')).toBe('custom-value'); @@ -4139,7 +4172,7 @@ describe('custom HTTP codes', () => { it('should set custom statusCode in delete hook', async () => { Parse.Cloud.beforeDelete('TestObject', (req, res) => { res.status(201); - return true + return true; }); const obj = new Parse.Object('TestObject'); @@ -4150,7 +4183,7 @@ describe('custom HTTP codes', () => { headers: { 'X-Parse-Application-Id': 'test', 'X-Parse-REST-API-Key': 'rest', - } + }, }); expect(request.status).toBe(201); @@ -4168,7 +4201,7 @@ describe('custom HTTP codes', () => { headers: { 'X-Parse-Application-Id': 'test', 'X-Parse-REST-API-Key': 'rest', - } + }, }); expect(request.headers.get('X-Custom-Header')).toBe('custom-value'); @@ -4183,7 +4216,7 @@ describe('custom HTTP codes', () => { headers: { 'X-Parse-Application-Id': 'test', 'X-Parse-REST-API-Key': 'rest', - } + }, }); expect(request.status).toBe(201); @@ -4198,7 +4231,7 @@ describe('custom HTTP codes', () => { headers: { 'X-Parse-Application-Id': 'test', 'X-Parse-REST-API-Key': 'rest', - } + }, }); expect(request.headers.get('X-Custom-Header')).toBe('custom-value'); @@ -4215,7 +4248,7 @@ describe('custom HTTP codes', () => { headers: { 'X-Parse-Application-Id': 'test', 'X-Parse-REST-API-Key': 'rest', - } + }, }); expect(response.status).toBe(201); @@ -4232,7 +4265,7 @@ describe('custom HTTP codes', () => { headers: { 'X-Parse-Application-Id': 'test', 'X-Parse-REST-API-Key': 'rest', - } + }, }); expect(response.headers.get('X-Custom-Header')).toBe('custom-value'); @@ -4250,7 +4283,7 @@ describe('custom HTTP codes', () => { 'X-Parse-Application-Id': 'test', 'X-Parse-REST-API-Key': 'rest', }, - body: JSON.stringify({ username: 'test@example.com', password: 'password' }) + body: JSON.stringify({ username: 'test@example.com', password: 'password' }), }); expect(response.status).toBe(201); @@ -4268,7 +4301,7 @@ describe('custom HTTP codes', () => { 'X-Parse-Application-Id': 'test', 'X-Parse-REST-API-Key': 'rest', }, - body: JSON.stringify({ username: 'test@example.com', password: 'password' }) + body: JSON.stringify({ username: 'test@example.com', password: 'password' }), }); expect(response.headers.get('X-Custom-Header')).toBe('custom-value'); @@ -4287,7 +4320,7 @@ describe('custom HTTP codes', () => { 'X-Parse-REST-API-Key': 'rest', 'Content-Type': 'text/plain', }, - body: file.getData() + body: file.getData(), }); expect(response.status).toBe(201); @@ -4306,9 +4339,9 @@ describe('custom HTTP codes', () => { 'X-Parse-REST-API-Key': 'rest', 'Content-Type': 'text/plain', }, - body: file.getData() + body: file.getData(), }); expect(response.headers.get('X-Custom-Header')).toBe('custom-value'); }); -}) \ No newline at end of file +}); diff --git a/spec/CloudCodeLogger.spec.js b/spec/CloudCodeLogger.spec.js index c5cb1bb1b9..05b04944af 100644 --- a/spec/CloudCodeLogger.spec.js +++ b/spec/CloudCodeLogger.spec.js @@ -120,23 +120,26 @@ describe('Cloud Code Logger', () => { expect(truncatedString.length).toBe(1015); // truncate length + the string '... (truncated)' }); - it_id('4a009b1f-9203-49ca-8d48-5b45f4eedbdf')(it)('should truncate input and result of long lines', done => { - const longString = fs.readFileSync(loremFile, 'utf8'); - Parse.Cloud.define('aFunction', req => { - return req.params; - }); + it_id('4a009b1f-9203-49ca-8d48-5b45f4eedbdf')(it)( + 'should truncate input and result of long lines', + done => { + const longString = fs.readFileSync(loremFile, 'utf8'); + Parse.Cloud.define('aFunction', req => { + return req.params; + }); - Parse.Cloud.run('aFunction', { longString }) - .then(() => { - const log = spy.calls.mostRecent().args; - expect(log[0]).toEqual('info'); - expect(log[1]).toMatch( - /Ran cloud function aFunction for user [^ ]* with:\n {2}Input: {.*?\(truncated\)$/m - ); - done(); - }) - .then(null, e => done.fail(e)); - }); + Parse.Cloud.run('aFunction', { longString }) + .then(() => { + const log = spy.calls.mostRecent().args; + expect(log[0]).toEqual('info'); + expect(log[1]).toMatch( + /Ran cloud function aFunction for user [^ ]* with:\n {2}Input: {.*?\(truncated\)$/m + ); + done(); + }) + .then(null, e => done.fail(e)); + } + ); it_id('9857e15d-bb18-478d-8a67-fdaad3e89565')(it)('should log an afterSave', done => { Parse.Cloud.afterSave('MyObject', () => {}); @@ -189,41 +192,44 @@ describe('Cloud Code Logger', () => { }); }); - it_id('8088de8a-7cba-4035-8b05-4a903307e674')(it)('should log cloud function execution using the custom log level', async done => { - Parse.Cloud.define('aFunction', () => { - return 'it worked!'; - }); + it_id('8088de8a-7cba-4035-8b05-4a903307e674')(it)( + 'should log cloud function execution using the custom log level', + async done => { + Parse.Cloud.define('aFunction', () => { + return 'it worked!'; + }); - Parse.Cloud.define('bFunction', () => { - throw new Error('Failed'); - }); + Parse.Cloud.define('bFunction', () => { + throw new Error('Failed'); + }); - await Parse.Cloud.run('aFunction', { foo: 'bar' }).then(() => { - const log = spy.calls.allArgs().find(log => log[1].startsWith('Ran cloud function '))?.[0]; - expect(log).toEqual('info'); - }); + await Parse.Cloud.run('aFunction', { foo: 'bar' }).then(() => { + const log = spy.calls.allArgs().find(log => log[1].startsWith('Ran cloud function '))?.[0]; + expect(log).toEqual('info'); + }); - await reconfigureServer({ - silent: true, - logLevels: { - cloudFunctionSuccess: 'warn', - cloudFunctionError: 'info', - }, - }); + await reconfigureServer({ + silent: true, + logLevels: { + cloudFunctionSuccess: 'warn', + cloudFunctionError: 'info', + }, + }); - spy = spyOn(Config.get('test').loggerController.adapter, 'log').and.callThrough(); + spy = spyOn(Config.get('test').loggerController.adapter, 'log').and.callThrough(); - try { - await Parse.Cloud.run('bFunction', { foo: 'bar' }); - throw new Error('bFunction should have failed'); - } catch { - const log = spy.calls - .allArgs() - .find(log => log[1].startsWith('Failed running cloud function bFunction for '))?.[0]; - expect(log).toEqual('info'); - done(); + try { + await Parse.Cloud.run('bFunction', { foo: 'bar' }); + throw new Error('bFunction should have failed'); + } catch { + const log = spy.calls + .allArgs() + .find(log => log[1].startsWith('Failed running cloud function bFunction for '))?.[0]; + expect(log).toEqual('info'); + done(); + } } - }); + ); it('should log cloud function triggers using the custom log level', async () => { Parse.Cloud.beforeSave('TestClass', () => {}); @@ -311,19 +317,22 @@ describe('Cloud Code Logger', () => { .then(null, e => done.fail(JSON.stringify(e))); }).pend('needs more work.....'); - it_id('b86e8168-8370-4730-a4ba-24ca3016ad66')(it)('cloud function should obfuscate password', done => { - Parse.Cloud.define('testFunction', () => { - return 'verify code success'; - }); + it_id('b86e8168-8370-4730-a4ba-24ca3016ad66')(it)( + 'cloud function should obfuscate password', + done => { + Parse.Cloud.define('testFunction', () => { + return 'verify code success'; + }); - Parse.Cloud.run('testFunction', { username: 'hawk', password: '123456' }) - .then(() => { - const entry = spy.calls.mostRecent().args; - expect(entry[2].params.password).toMatch(/\*\*\*\*\*\*\*\*/); - done(); - }) - .then(null, e => done.fail(e)); - }); + Parse.Cloud.run('testFunction', { username: 'hawk', password: '123456' }) + .then(() => { + const entry = spy.calls.mostRecent().args; + expect(entry[2].params.password).toMatch(/\*\*\*\*\*\*\*\*/); + done(); + }) + .then(null, e => done.fail(e)); + } + ); it('should only log once for object not found', async () => { const config = Config.get('test'); diff --git a/spec/DefinedSchemas.spec.js b/spec/DefinedSchemas.spec.js index e3d6fd51fe..3d6f493e66 100644 --- a/spec/DefinedSchemas.spec.js +++ b/spec/DefinedSchemas.spec.js @@ -643,41 +643,44 @@ describe('DefinedSchemas', () => { expect(logger.error).toHaveBeenCalledWith(`Failed to run migrations: ${error.toString()}`); }); - it_id('a18bf4f2-25c8-4de3-b986-19cb1ab163b8')(it)('should perform migration in parallel without failing', async () => { - const server = await reconfigureServer(); - const logger = require('../lib/logger').logger; - spyOn(logger, 'error').and.callThrough(); - const migrationOptions = { - definitions: [ - { - className: 'Test', - fields: { aField: { type: 'String' } }, - indexes: { aField: { aField: 1 } }, - classLevelPermissions: { - create: { requiresAuthentication: true }, + it_id('a18bf4f2-25c8-4de3-b986-19cb1ab163b8')(it)( + 'should perform migration in parallel without failing', + async () => { + const server = await reconfigureServer(); + const logger = require('../lib/logger').logger; + spyOn(logger, 'error').and.callThrough(); + const migrationOptions = { + definitions: [ + { + className: 'Test', + fields: { aField: { type: 'String' } }, + indexes: { aField: { aField: 1 } }, + classLevelPermissions: { + create: { requiresAuthentication: true }, + }, }, - }, - ], - }; - - // Simulate parallel deployment - await Promise.all([ - new DefinedSchemas(migrationOptions, server.config).execute(), - new DefinedSchemas(migrationOptions, server.config).execute(), - new DefinedSchemas(migrationOptions, server.config).execute(), - new DefinedSchemas(migrationOptions, server.config).execute(), - new DefinedSchemas(migrationOptions, server.config).execute(), - ]); - - const testSchema = (await Parse.Schema.all()).find( - ({ className }) => className === migrationOptions.definitions[0].className - ); + ], + }; - expect(testSchema.indexes.aField).toEqual({ aField: 1 }); - expect(testSchema.fields.aField).toEqual({ type: 'String' }); - expect(testSchema.classLevelPermissions.create).toEqual({ requiresAuthentication: true }); - expect(logger.error).toHaveBeenCalledTimes(0); - }); + // Simulate parallel deployment + await Promise.all([ + new DefinedSchemas(migrationOptions, server.config).execute(), + new DefinedSchemas(migrationOptions, server.config).execute(), + new DefinedSchemas(migrationOptions, server.config).execute(), + new DefinedSchemas(migrationOptions, server.config).execute(), + new DefinedSchemas(migrationOptions, server.config).execute(), + ]); + + const testSchema = (await Parse.Schema.all()).find( + ({ className }) => className === migrationOptions.definitions[0].className + ); + + expect(testSchema.indexes.aField).toEqual({ aField: 1 }); + expect(testSchema.fields.aField).toEqual({ type: 'String' }); + expect(testSchema.classLevelPermissions.create).toEqual({ requiresAuthentication: true }); + expect(logger.error).toHaveBeenCalledTimes(0); + } + ); it('should not affect cacheAdapter', async () => { const server = await reconfigureServer(); diff --git a/spec/EmailVerificationToken.spec.js b/spec/EmailVerificationToken.spec.js index 1e9f6a7830..47e6f094dd 100644 --- a/spec/EmailVerificationToken.spec.js +++ b/spec/EmailVerificationToken.spec.js @@ -104,193 +104,205 @@ describe('Email Verification Token Expiration: ', () => { }); }); - it_id('f20dd3c2-87d9-4bc6-a51d-4ea2834acbcc')(it)('if user clicks on the email verify link before email verification token expiration then show the verify email success page', done => { - const user = new Parse.User(); - let sendEmailOptions; - const emailAdapter = { - sendVerificationEmail: options => { - sendEmailOptions = options; - }, - sendPasswordResetEmail: () => Promise.resolve(), - sendMail: () => {}, - }; - reconfigureServer({ - appName: 'emailVerifyToken', - verifyUserEmails: true, - emailAdapter: emailAdapter, - emailVerifyTokenValidityDuration: 5, // 5 seconds - publicServerURL: 'http://localhost:8378/1', - }) - .then(() => { - user.setUsername('testEmailVerifyTokenValidity'); - user.setPassword('expiringToken'); - user.set('email', 'user@parse.com'); - return user.signUp(); + it_id('f20dd3c2-87d9-4bc6-a51d-4ea2834acbcc')(it)( + 'if user clicks on the email verify link before email verification token expiration then show the verify email success page', + done => { + const user = new Parse.User(); + let sendEmailOptions; + const emailAdapter = { + sendVerificationEmail: options => { + sendEmailOptions = options; + }, + sendPasswordResetEmail: () => Promise.resolve(), + sendMail: () => {}, + }; + reconfigureServer({ + appName: 'emailVerifyToken', + verifyUserEmails: true, + emailAdapter: emailAdapter, + emailVerifyTokenValidityDuration: 5, // 5 seconds + publicServerURL: 'http://localhost:8378/1', }) - .then(() => jasmine.timeout()) - .then(() => { - request({ - url: sendEmailOptions.link, - followRedirects: false, - }).then(response => { - expect(response.status).toEqual(302); - expect(response.text).toEqual( - 'Found. Redirecting to http://localhost:8378/1/apps/verify_email_success.html?username=testEmailVerifyTokenValidity' - ); + .then(() => { + user.setUsername('testEmailVerifyTokenValidity'); + user.setPassword('expiringToken'); + user.set('email', 'user@parse.com'); + return user.signUp(); + }) + .then(() => jasmine.timeout()) + .then(() => { + request({ + url: sendEmailOptions.link, + followRedirects: false, + }).then(response => { + expect(response.status).toEqual(302); + expect(response.text).toEqual( + 'Found. Redirecting to http://localhost:8378/1/apps/verify_email_success.html?username=testEmailVerifyTokenValidity' + ); + done(); + }); + }) + .catch(error => { + jfail(error); done(); }); - }) - .catch(error => { - jfail(error); - done(); - }); - }); + } + ); - it_id('94956799-c85e-4297-b879-e2d1f985394c')(it)('if user clicks on the email verify link before email verification token expiration then emailVerified should be true', done => { - const user = new Parse.User(); - let sendEmailOptions; - const emailAdapter = { - sendVerificationEmail: options => { - sendEmailOptions = options; - }, - sendPasswordResetEmail: () => Promise.resolve(), - sendMail: () => {}, - }; - reconfigureServer({ - appName: 'emailVerifyToken', - verifyUserEmails: true, - emailAdapter: emailAdapter, - emailVerifyTokenValidityDuration: 5, // 5 seconds - publicServerURL: 'http://localhost:8378/1', - }) - .then(() => { - user.setUsername('testEmailVerifyTokenValidity'); - user.setPassword('expiringToken'); - user.set('email', 'user@parse.com'); - return user.signUp(); + it_id('94956799-c85e-4297-b879-e2d1f985394c')(it)( + 'if user clicks on the email verify link before email verification token expiration then emailVerified should be true', + done => { + const user = new Parse.User(); + let sendEmailOptions; + const emailAdapter = { + sendVerificationEmail: options => { + sendEmailOptions = options; + }, + sendPasswordResetEmail: () => Promise.resolve(), + sendMail: () => {}, + }; + reconfigureServer({ + appName: 'emailVerifyToken', + verifyUserEmails: true, + emailAdapter: emailAdapter, + emailVerifyTokenValidityDuration: 5, // 5 seconds + publicServerURL: 'http://localhost:8378/1', }) - .then(() => jasmine.timeout()) - .then(() => { - request({ - url: sendEmailOptions.link, - followRedirects: false, - }).then(response => { - expect(response.status).toEqual(302); - user - .fetch() - .then(() => { - expect(user.get('emailVerified')).toEqual(true); - done(); - }) - .catch(error => { - jfail(error); - done(); - }); + .then(() => { + user.setUsername('testEmailVerifyTokenValidity'); + user.setPassword('expiringToken'); + user.set('email', 'user@parse.com'); + return user.signUp(); + }) + .then(() => jasmine.timeout()) + .then(() => { + request({ + url: sendEmailOptions.link, + followRedirects: false, + }).then(response => { + expect(response.status).toEqual(302); + user + .fetch() + .then(() => { + expect(user.get('emailVerified')).toEqual(true); + done(); + }) + .catch(error => { + jfail(error); + done(); + }); + }); + }) + .catch(error => { + jfail(error); + done(); }); - }) - .catch(error => { - jfail(error); - done(); - }); - }); + } + ); - it_id('25f3f895-c987-431c-9841-17cb6aaf18b5')(it)('if user clicks on the email verify link before email verification token expiration then user should be able to login', done => { - const user = new Parse.User(); - let sendEmailOptions; - const emailAdapter = { - sendVerificationEmail: options => { - sendEmailOptions = options; - }, - sendPasswordResetEmail: () => Promise.resolve(), - sendMail: () => {}, - }; - reconfigureServer({ - appName: 'emailVerifyToken', - verifyUserEmails: true, - emailAdapter: emailAdapter, - emailVerifyTokenValidityDuration: 5, // 5 seconds - publicServerURL: 'http://localhost:8378/1', - }) - .then(() => { - user.setUsername('testEmailVerifyTokenValidity'); - user.setPassword('expiringToken'); - user.set('email', 'user@parse.com'); - return user.signUp(); + it_id('25f3f895-c987-431c-9841-17cb6aaf18b5')(it)( + 'if user clicks on the email verify link before email verification token expiration then user should be able to login', + done => { + const user = new Parse.User(); + let sendEmailOptions; + const emailAdapter = { + sendVerificationEmail: options => { + sendEmailOptions = options; + }, + sendPasswordResetEmail: () => Promise.resolve(), + sendMail: () => {}, + }; + reconfigureServer({ + appName: 'emailVerifyToken', + verifyUserEmails: true, + emailAdapter: emailAdapter, + emailVerifyTokenValidityDuration: 5, // 5 seconds + publicServerURL: 'http://localhost:8378/1', }) - .then(() => jasmine.timeout()) - .then(() => { - request({ - url: sendEmailOptions.link, - followRedirects: false, - }).then(response => { - expect(response.status).toEqual(302); - Parse.User.logIn('testEmailVerifyTokenValidity', 'expiringToken') - .then(user => { - expect(typeof user).toBe('object'); - expect(user.get('emailVerified')).toBe(true); - done(); - }) - .catch(error => { - jfail(error); - done(); - }); + .then(() => { + user.setUsername('testEmailVerifyTokenValidity'); + user.setPassword('expiringToken'); + user.set('email', 'user@parse.com'); + return user.signUp(); + }) + .then(() => jasmine.timeout()) + .then(() => { + request({ + url: sendEmailOptions.link, + followRedirects: false, + }).then(response => { + expect(response.status).toEqual(302); + Parse.User.logIn('testEmailVerifyTokenValidity', 'expiringToken') + .then(user => { + expect(typeof user).toBe('object'); + expect(user.get('emailVerified')).toBe(true); + done(); + }) + .catch(error => { + jfail(error); + done(); + }); + }); + }) + .catch(error => { + jfail(error); + done(); }); - }) - .catch(error => { - jfail(error); - done(); - }); - }); + } + ); - it_id('c6a3e188-9065-4f50-842d-454d1e82f289')(it)('sets the _email_verify_token_expires_at and _email_verify_token fields after user SignUp', done => { - const user = new Parse.User(); - let sendEmailOptions; - const emailAdapter = { - sendVerificationEmail: options => { - sendEmailOptions = options; - }, - sendPasswordResetEmail: () => Promise.resolve(), - sendMail: () => {}, - }; - reconfigureServer({ - appName: 'emailVerifyToken', - verifyUserEmails: true, - emailAdapter: emailAdapter, - emailVerifyTokenValidityDuration: 5, // 5 seconds - publicServerURL: 'http://localhost:8378/1', - }) - .then(() => { - user.setUsername('sets_email_verify_token_expires_at'); - user.setPassword('expiringToken'); - user.set('email', 'user@parse.com'); - return user.signUp(); - }) - .then(() => { - const config = Config.get('test'); - return config.database.find( - '_User', - { - username: 'sets_email_verify_token_expires_at', - }, - {}, - Auth.maintenance(config) - ); - }) - .then(results => { - expect(results.length).toBe(1); - const user = results[0]; - expect(typeof user).toBe('object'); - expect(user.emailVerified).toEqual(false); - expect(typeof user._email_verify_token).toBe('string'); - expect(typeof user._email_verify_token_expires_at).toBe('object'); - expect(sendEmailOptions).toBeDefined(); - done(); + it_id('c6a3e188-9065-4f50-842d-454d1e82f289')(it)( + 'sets the _email_verify_token_expires_at and _email_verify_token fields after user SignUp', + done => { + const user = new Parse.User(); + let sendEmailOptions; + const emailAdapter = { + sendVerificationEmail: options => { + sendEmailOptions = options; + }, + sendPasswordResetEmail: () => Promise.resolve(), + sendMail: () => {}, + }; + reconfigureServer({ + appName: 'emailVerifyToken', + verifyUserEmails: true, + emailAdapter: emailAdapter, + emailVerifyTokenValidityDuration: 5, // 5 seconds + publicServerURL: 'http://localhost:8378/1', }) - .catch(error => { - jfail(error); - done(); - }); - }); + .then(() => { + user.setUsername('sets_email_verify_token_expires_at'); + user.setPassword('expiringToken'); + user.set('email', 'user@parse.com'); + return user.signUp(); + }) + .then(() => { + const config = Config.get('test'); + return config.database.find( + '_User', + { + username: 'sets_email_verify_token_expires_at', + }, + {}, + Auth.maintenance(config) + ); + }) + .then(results => { + expect(results.length).toBe(1); + const user = results[0]; + expect(typeof user).toBe('object'); + expect(user.emailVerified).toEqual(false); + expect(typeof user._email_verify_token).toBe('string'); + expect(typeof user._email_verify_token_expires_at).toBe('object'); + expect(sendEmailOptions).toBeDefined(); + done(); + }) + .catch(error => { + jfail(error); + done(); + }); + } + ); it_id('9365c53c-b8b4-41f7-a3c1-77882f76a89c')(it)('can conditionally send emails', async () => { let sendEmailOptions; @@ -351,497 +363,527 @@ describe('Email Verification Token Expiration: ', () => { expect(verifySpy).toHaveBeenCalled(); }); - it_id('b3549300-bed7-4a5e-bed5-792dbfead960')(it)('can conditionally send emails and allow conditional login', async () => { - let sendEmailOptions; - const emailAdapter = { - sendVerificationEmail: options => { - sendEmailOptions = options; - }, - sendPasswordResetEmail: () => Promise.resolve(), - sendMail: () => {}, - }; - const verifyUserEmails = { - method(req) { - expect(Object.keys(req)).toEqual(['original', 'object', 'master', 'ip', 'installationId']); - if (req.object.get('username') === 'no_email') { + it_id('b3549300-bed7-4a5e-bed5-792dbfead960')(it)( + 'can conditionally send emails and allow conditional login', + async () => { + let sendEmailOptions; + const emailAdapter = { + sendVerificationEmail: options => { + sendEmailOptions = options; + }, + sendPasswordResetEmail: () => Promise.resolve(), + sendMail: () => {}, + }; + const verifyUserEmails = { + method(req) { + expect(Object.keys(req)).toEqual([ + 'original', + 'object', + 'master', + 'ip', + 'installationId', + ]); + if (req.object.get('username') === 'no_email') { + return false; + } + return true; + }, + }; + const verifySpy = spyOn(verifyUserEmails, 'method').and.callThrough(); + await reconfigureServer({ + appName: 'emailVerifyToken', + verifyUserEmails: verifyUserEmails.method, + preventLoginWithUnverifiedEmail: verifyUserEmails.method, + emailAdapter: emailAdapter, + emailVerifyTokenValidityDuration: 5, // 5 seconds + publicServerURL: 'http://localhost:8378/1', + }); + const user = new Parse.User(); + user.setUsername('no_email'); + user.setPassword('expiringToken'); + user.set('email', 'user@example.com'); + await user.signUp(); + expect(sendEmailOptions).toBeUndefined(); + expect(user.getSessionToken()).toBeDefined(); + expect(verifySpy).toHaveBeenCalledTimes(2); + const user2 = new Parse.User(); + user2.setUsername('email'); + user2.setPassword('expiringToken'); + user2.set('email', 'user2@example.com'); + await user2.signUp(); + await jasmine.timeout(); + expect(user2.getSessionToken()).toBeUndefined(); + expect(sendEmailOptions).toBeDefined(); + expect(verifySpy).toHaveBeenCalledTimes(5); + } + ); + + it_id('d812de87-33d1-495e-a6e8-3485f6dc3589')(it)( + 'can conditionally send user email verification', + async () => { + const emailAdapter = { + sendVerificationEmail: () => {}, + sendPasswordResetEmail: () => Promise.resolve(), + sendMail: () => {}, + }; + const sendVerificationEmail = { + method(req) { + expect(req.user).toBeDefined(); + expect(req.master).toBeDefined(); return false; - } - return true; - }, - }; - const verifySpy = spyOn(verifyUserEmails, 'method').and.callThrough(); - await reconfigureServer({ - appName: 'emailVerifyToken', - verifyUserEmails: verifyUserEmails.method, - preventLoginWithUnverifiedEmail: verifyUserEmails.method, - emailAdapter: emailAdapter, - emailVerifyTokenValidityDuration: 5, // 5 seconds - publicServerURL: 'http://localhost:8378/1', - }); - const user = new Parse.User(); - user.setUsername('no_email'); - user.setPassword('expiringToken'); - user.set('email', 'user@example.com'); - await user.signUp(); - expect(sendEmailOptions).toBeUndefined(); - expect(user.getSessionToken()).toBeDefined(); - expect(verifySpy).toHaveBeenCalledTimes(2); - const user2 = new Parse.User(); - user2.setUsername('email'); - user2.setPassword('expiringToken'); - user2.set('email', 'user2@example.com'); - await user2.signUp(); - await jasmine.timeout(); - expect(user2.getSessionToken()).toBeUndefined(); - expect(sendEmailOptions).toBeDefined(); - expect(verifySpy).toHaveBeenCalledTimes(5); - }); + }, + }; + const sendSpy = spyOn(sendVerificationEmail, 'method').and.callThrough(); + await reconfigureServer({ + appName: 'emailVerifyToken', + verifyUserEmails: true, + emailAdapter: emailAdapter, + emailVerifyTokenValidityDuration: 5, // 5 seconds + publicServerURL: 'http://localhost:8378/1', + sendUserEmailVerification: sendVerificationEmail.method, + }); + const emailSpy = spyOn(emailAdapter, 'sendVerificationEmail').and.callThrough(); + const newUser = new Parse.User(); + newUser.setUsername('unsets_email_verify_token_expires_at'); + newUser.setPassword('expiringToken'); + newUser.set('email', 'user@example.com'); + await newUser.signUp(); + await Parse.User.requestEmailVerification('user@example.com'); + await jasmine.timeout(); + expect(sendSpy).toHaveBeenCalledTimes(2); + expect(emailSpy).toHaveBeenCalledTimes(0); + } + ); - it_id('d812de87-33d1-495e-a6e8-3485f6dc3589')(it)('can conditionally send user email verification', async () => { - const emailAdapter = { - sendVerificationEmail: () => {}, - sendPasswordResetEmail: () => Promise.resolve(), - sendMail: () => {}, - }; - const sendVerificationEmail = { - method(req) { - expect(req.user).toBeDefined(); - expect(req.master).toBeDefined(); - return false; - }, - }; - const sendSpy = spyOn(sendVerificationEmail, 'method').and.callThrough(); - await reconfigureServer({ - appName: 'emailVerifyToken', - verifyUserEmails: true, - emailAdapter: emailAdapter, - emailVerifyTokenValidityDuration: 5, // 5 seconds - publicServerURL: 'http://localhost:8378/1', - sendUserEmailVerification: sendVerificationEmail.method, - }); - const emailSpy = spyOn(emailAdapter, 'sendVerificationEmail').and.callThrough(); - const newUser = new Parse.User(); - newUser.setUsername('unsets_email_verify_token_expires_at'); - newUser.setPassword('expiringToken'); - newUser.set('email', 'user@example.com'); - await newUser.signUp(); - await Parse.User.requestEmailVerification('user@example.com'); - await jasmine.timeout(); - expect(sendSpy).toHaveBeenCalledTimes(2); - expect(emailSpy).toHaveBeenCalledTimes(0); - }); - - it_id('d98babc1-feb8-4b5e-916c-57dc0a6ed9fb')(it)('provides full user object in email verification function on email and username change', async () => { - const emailAdapter = { - sendVerificationEmail: () => {}, - sendPasswordResetEmail: () => Promise.resolve(), - sendMail: () => {}, - }; - const sendVerificationEmail = { - method(req) { - expect(req.user).toBeDefined(); - expect(req.user.id).toBeDefined(); - expect(req.user.get('createdAt')).toBeDefined(); - expect(req.user.get('updatedAt')).toBeDefined(); - expect(req.master).toBeDefined(); - return false; - }, - }; - await reconfigureServer({ - appName: 'emailVerifyToken', - verifyUserEmails: true, - emailAdapter: emailAdapter, - emailVerifyTokenValidityDuration: 5, - publicServerURL: 'http://localhost:8378/1', - sendUserEmailVerification: sendVerificationEmail.method, - }); - const user = new Parse.User(); - user.setPassword('password'); - user.setUsername('new@example.com'); - user.setEmail('user@example.com'); - await user.save(null, { useMasterKey: true }); - - // Update email and username - user.setUsername('new@example.com'); - user.setEmail('new@example.com'); - await user.save(null, { useMasterKey: true }); - }); - - it_id('a8c1f820-822f-4a37-9d08-a968cac8369d')(it)('beforeSave options do not change existing behaviour', async () => { - let sendEmailOptions; - const emailAdapter = { - sendVerificationEmail: options => { - sendEmailOptions = options; - }, - sendPasswordResetEmail: () => Promise.resolve(), - sendMail: () => {}, - }; - await reconfigureServer({ - appName: 'emailVerifyToken', - verifyUserEmails: true, - emailAdapter: emailAdapter, - emailVerifyTokenValidityDuration: 5, // 5 seconds - publicServerURL: 'http://localhost:8378/1', - }); - const emailSpy = spyOn(emailAdapter, 'sendVerificationEmail').and.callThrough(); - const newUser = new Parse.User(); - newUser.setUsername('unsets_email_verify_token_expires_at'); - newUser.setPassword('expiringToken'); - newUser.set('email', 'user@parse.com'); - await newUser.signUp(); - await jasmine.timeout(); - const response = await request({ - url: sendEmailOptions.link, - followRedirects: false, - }); - expect(response.status).toEqual(302); - const config = Config.get('test'); - const results = await config.database.find('_User', { - username: 'unsets_email_verify_token_expires_at', - }); - - expect(results.length).toBe(1); - const user = results[0]; - expect(typeof user).toBe('object'); - expect(user.emailVerified).toEqual(true); - expect(typeof user._email_verify_token).toBe('undefined'); - expect(typeof user._email_verify_token_expires_at).toBe('undefined'); - expect(emailSpy).toHaveBeenCalled(); - }); - - it_id('36d277eb-ec7c-4a39-9108-435b68228741')(it)('unsets the _email_verify_token_expires_at and _email_verify_token fields in the User class if email verification is successful', done => { - const user = new Parse.User(); - let sendEmailOptions; - const emailAdapter = { - sendVerificationEmail: options => { - sendEmailOptions = options; - }, - sendPasswordResetEmail: () => Promise.resolve(), - sendMail: () => {}, - }; - reconfigureServer({ - appName: 'emailVerifyToken', - verifyUserEmails: true, - emailAdapter: emailAdapter, - emailVerifyTokenValidityDuration: 5, // 5 seconds - publicServerURL: 'http://localhost:8378/1', - }) - .then(() => { - user.setUsername('unsets_email_verify_token_expires_at'); - user.setPassword('expiringToken'); - user.set('email', 'user@parse.com'); - return user.signUp(); - }) - .then(() => jasmine.timeout()) - .then(() => { - request({ - url: sendEmailOptions.link, - followRedirects: false, - }).then(response => { - expect(response.status).toEqual(302); - const config = Config.get('test'); - return config.database - .find('_User', { - username: 'unsets_email_verify_token_expires_at', - }) - .then(results => { - expect(results.length).toBe(1); - return results[0]; - }) - .then(user => { - expect(typeof user).toBe('object'); - expect(user.emailVerified).toEqual(true); - expect(typeof user._email_verify_token).toBe('undefined'); - expect(typeof user._email_verify_token_expires_at).toBe('undefined'); - done(); - }) - .catch(error => { - jfail(error); - done(); - }); - }); - }) - .catch(error => { - jfail(error); - done(); + it_id('d98babc1-feb8-4b5e-916c-57dc0a6ed9fb')(it)( + 'provides full user object in email verification function on email and username change', + async () => { + const emailAdapter = { + sendVerificationEmail: () => {}, + sendPasswordResetEmail: () => Promise.resolve(), + sendMail: () => {}, + }; + const sendVerificationEmail = { + method(req) { + expect(req.user).toBeDefined(); + expect(req.user.id).toBeDefined(); + expect(req.user.get('createdAt')).toBeDefined(); + expect(req.user.get('updatedAt')).toBeDefined(); + expect(req.master).toBeDefined(); + return false; + }, + }; + await reconfigureServer({ + appName: 'emailVerifyToken', + verifyUserEmails: true, + emailAdapter: emailAdapter, + emailVerifyTokenValidityDuration: 5, + publicServerURL: 'http://localhost:8378/1', + sendUserEmailVerification: sendVerificationEmail.method, }); - }); + const user = new Parse.User(); + user.setPassword('password'); + user.setUsername('new@example.com'); + user.setEmail('user@example.com'); + await user.save(null, { useMasterKey: true }); - it_id('4f444704-ec4b-4dff-b947-614b1c6971c4')(it)('clicking on the email verify link by an email VERIFIED user that was setup before enabling the expire email verify token should show email verify email success', done => { - const user = new Parse.User(); - let sendEmailOptions; - const emailAdapter = { - sendVerificationEmail: options => { - sendEmailOptions = options; - }, - sendPasswordResetEmail: () => Promise.resolve(), - sendMail: () => {}, - }; - const serverConfig = { - appName: 'emailVerifyToken', - verifyUserEmails: true, - emailAdapter: emailAdapter, - publicServerURL: 'http://localhost:8378/1', - }; + // Update email and username + user.setUsername('new@example.com'); + user.setEmail('new@example.com'); + await user.save(null, { useMasterKey: true }); + } + ); - // setup server WITHOUT enabling the expire email verify token flag - reconfigureServer(serverConfig) - .then(() => { - user.setUsername('testEmailVerifyTokenValidity'); - user.setPassword('expiringToken'); - user.set('email', 'user@parse.com'); - return user.signUp(); - }) - .then(() => jasmine.timeout()) - .then(() => { - return request({ - url: sendEmailOptions.link, - followRedirects: false, - }).then(response => { - expect(response.status).toEqual(302); - return user.fetch(); - }); - }) - .then(() => { - expect(user.get('emailVerified')).toEqual(true); - // RECONFIGURE the server i.e., ENABLE the expire email verify token flag - serverConfig.emailVerifyTokenValidityDuration = 5; // 5 seconds - return reconfigureServer(serverConfig); - }) - .then(() => { - request({ - url: sendEmailOptions.link, - followRedirects: false, - }).then(response => { - expect(response.status).toEqual(302); - expect(response.text).toEqual( - 'Found. Redirecting to http://localhost:8378/1/apps/verify_email_success.html?username=testEmailVerifyTokenValidity' - ); - done(); - }); - }) - .catch(error => { - jfail(error); - done(); + it_id('a8c1f820-822f-4a37-9d08-a968cac8369d')(it)( + 'beforeSave options do not change existing behaviour', + async () => { + let sendEmailOptions; + const emailAdapter = { + sendVerificationEmail: options => { + sendEmailOptions = options; + }, + sendPasswordResetEmail: () => Promise.resolve(), + sendMail: () => {}, + }; + await reconfigureServer({ + appName: 'emailVerifyToken', + verifyUserEmails: true, + emailAdapter: emailAdapter, + emailVerifyTokenValidityDuration: 5, // 5 seconds + publicServerURL: 'http://localhost:8378/1', + }); + const emailSpy = spyOn(emailAdapter, 'sendVerificationEmail').and.callThrough(); + const newUser = new Parse.User(); + newUser.setUsername('unsets_email_verify_token_expires_at'); + newUser.setPassword('expiringToken'); + newUser.set('email', 'user@parse.com'); + await newUser.signUp(); + await jasmine.timeout(); + const response = await request({ + url: sendEmailOptions.link, + followRedirects: false, + }); + expect(response.status).toEqual(302); + const config = Config.get('test'); + const results = await config.database.find('_User', { + username: 'unsets_email_verify_token_expires_at', }); - }); - it('clicking on the email verify link by an email UNVERIFIED user that was setup before enabling the expire email verify token should show invalid verficiation link page', done => { - const user = new Parse.User(); - let sendEmailOptions; - const emailAdapter = { - sendVerificationEmail: options => { - sendEmailOptions = options; - }, - sendPasswordResetEmail: () => Promise.resolve(), - sendMail: () => {}, - }; - const serverConfig = { - appName: 'emailVerifyToken', - verifyUserEmails: true, - emailAdapter: emailAdapter, - publicServerURL: 'http://localhost:8378/1', - }; + expect(results.length).toBe(1); + const user = results[0]; + expect(typeof user).toBe('object'); + expect(user.emailVerified).toEqual(true); + expect(typeof user._email_verify_token).toBe('undefined'); + expect(typeof user._email_verify_token_expires_at).toBe('undefined'); + expect(emailSpy).toHaveBeenCalled(); + } + ); - // setup server WITHOUT enabling the expire email verify token flag - reconfigureServer(serverConfig) - .then(() => { - user.setUsername('testEmailVerifyTokenValidity'); - user.setPassword('expiringToken'); - user.set('email', 'user@parse.com'); - return user.signUp(); - }) - .then(() => { - // just get the user again - DO NOT email verify the user - return user.fetch(); - }) - .then(() => { - expect(user.get('emailVerified')).toEqual(false); - // RECONFIGURE the server i.e., ENABLE the expire email verify token flag - serverConfig.emailVerifyTokenValidityDuration = 5; // 5 seconds - return reconfigureServer(serverConfig); + it_id('36d277eb-ec7c-4a39-9108-435b68228741')(it)( + 'unsets the _email_verify_token_expires_at and _email_verify_token fields in the User class if email verification is successful', + done => { + const user = new Parse.User(); + let sendEmailOptions; + const emailAdapter = { + sendVerificationEmail: options => { + sendEmailOptions = options; + }, + sendPasswordResetEmail: () => Promise.resolve(), + sendMail: () => {}, + }; + reconfigureServer({ + appName: 'emailVerifyToken', + verifyUserEmails: true, + emailAdapter: emailAdapter, + emailVerifyTokenValidityDuration: 5, // 5 seconds + publicServerURL: 'http://localhost:8378/1', }) - .then(() => { - request({ - url: sendEmailOptions.link, - followRedirects: false, - }).then(response => { - expect(response.status).toEqual(302); - expect(response.text).toEqual( - 'Found. Redirecting to http://localhost:8378/1/apps/invalid_verification_link.html?username=testEmailVerifyTokenValidity&appId=test' - ); + .then(() => { + user.setUsername('unsets_email_verify_token_expires_at'); + user.setPassword('expiringToken'); + user.set('email', 'user@parse.com'); + return user.signUp(); + }) + .then(() => jasmine.timeout()) + .then(() => { + request({ + url: sendEmailOptions.link, + followRedirects: false, + }).then(response => { + expect(response.status).toEqual(302); + const config = Config.get('test'); + return config.database + .find('_User', { + username: 'unsets_email_verify_token_expires_at', + }) + .then(results => { + expect(results.length).toBe(1); + return results[0]; + }) + .then(user => { + expect(typeof user).toBe('object'); + expect(user.emailVerified).toEqual(true); + expect(typeof user._email_verify_token).toBe('undefined'); + expect(typeof user._email_verify_token_expires_at).toBe('undefined'); + done(); + }) + .catch(error => { + jfail(error); + done(); + }); + }); + }) + .catch(error => { + jfail(error); done(); }); - }) - .catch(error => { - jfail(error); - done(); - }); - }); - - it_id('b6c87f35-d887-477d-bc86-a9217a424f53')(it)('setting the email on the user should set a new email verification token and new expiration date for the token when expire email verify token flag is set', done => { - const user = new Parse.User(); - let userBeforeEmailReset; + } + ); - let sendEmailOptions; - const emailAdapter = { - sendVerificationEmail: options => { - sendEmailOptions = options; - }, - sendPasswordResetEmail: () => Promise.resolve(), - sendMail: () => {}, - }; - const serverConfig = { - appName: 'emailVerifyToken', - verifyUserEmails: true, - emailAdapter: emailAdapter, - emailVerifyTokenValidityDuration: 5, // 5 seconds - publicServerURL: 'http://localhost:8378/1', - }; + it_id('4f444704-ec4b-4dff-b947-614b1c6971c4')(it)( + 'clicking on the email verify link by an email VERIFIED user that was setup before enabling the expire email verify token should show email verify email success', + done => { + const user = new Parse.User(); + let sendEmailOptions; + const emailAdapter = { + sendVerificationEmail: options => { + sendEmailOptions = options; + }, + sendPasswordResetEmail: () => Promise.resolve(), + sendMail: () => {}, + }; + const serverConfig = { + appName: 'emailVerifyToken', + verifyUserEmails: true, + emailAdapter: emailAdapter, + publicServerURL: 'http://localhost:8378/1', + }; - reconfigureServer(serverConfig) - .then(() => { - user.setUsername('newEmailVerifyTokenOnEmailReset'); - user.setPassword('expiringToken'); - user.set('email', 'user@parse.com'); - return user.signUp(); - }) - .then(() => { - const config = Config.get('test'); - return config.database - .find('_User', { username: 'newEmailVerifyTokenOnEmailReset' }) - .then(results => { - return results[0]; + // setup server WITHOUT enabling the expire email verify token flag + reconfigureServer(serverConfig) + .then(() => { + user.setUsername('testEmailVerifyTokenValidity'); + user.setPassword('expiringToken'); + user.set('email', 'user@parse.com'); + return user.signUp(); + }) + .then(() => jasmine.timeout()) + .then(() => { + return request({ + url: sendEmailOptions.link, + followRedirects: false, + }).then(response => { + expect(response.status).toEqual(302); + return user.fetch(); }); - }) - .then(userFromDb => { - expect(typeof userFromDb).toBe('object'); - userBeforeEmailReset = userFromDb; - - // trigger another token generation by setting the email - user.set('email', 'user@parse.com'); - return new Promise(resolve => { - // wait for half a sec to get a new expiration time - setTimeout(() => resolve(user.save()), 500); - }); - }) - .then(() => { - const config = Config.get('test'); - return config.database - .find( - '_User', - { username: 'newEmailVerifyTokenOnEmailReset' }, - {}, - Auth.maintenance(config) - ) - .then(results => { - return results[0]; + }) + .then(() => { + expect(user.get('emailVerified')).toEqual(true); + // RECONFIGURE the server i.e., ENABLE the expire email verify token flag + serverConfig.emailVerifyTokenValidityDuration = 5; // 5 seconds + return reconfigureServer(serverConfig); + }) + .then(() => { + request({ + url: sendEmailOptions.link, + followRedirects: false, + }).then(response => { + expect(response.status).toEqual(302); + expect(response.text).toEqual( + 'Found. Redirecting to http://localhost:8378/1/apps/verify_email_success.html?username=testEmailVerifyTokenValidity' + ); + done(); }); - }) - .then(userAfterEmailReset => { - expect(typeof userAfterEmailReset).toBe('object'); - expect(userBeforeEmailReset._email_verify_token).not.toEqual( - userAfterEmailReset._email_verify_token - ); - expect(userBeforeEmailReset._email_verify_token_expires_at).not.toEqual( - userAfterEmailReset._email_verify_token_expires_at - ); - expect(sendEmailOptions).toBeDefined(); - done(); - }) - .catch(error => { - jfail(error); - done(); - }); - }); + }) + .catch(error => { + jfail(error); + done(); + }); + } + ); - it_id('28f2140d-48bd-44ac-a141-ba60ea8d9713')(it)('should send a new verification email when a resend is requested and the user is UNVERIFIED', done => { + it('clicking on the email verify link by an email UNVERIFIED user that was setup before enabling the expire email verify token should show invalid verficiation link page', done => { const user = new Parse.User(); let sendEmailOptions; - let sendVerificationEmailCallCount = 0; - let userBeforeRequest; const emailAdapter = { sendVerificationEmail: options => { sendEmailOptions = options; - sendVerificationEmailCallCount++; }, sendPasswordResetEmail: () => Promise.resolve(), sendMail: () => {}, }; - reconfigureServer({ + const serverConfig = { appName: 'emailVerifyToken', - verifyUserEmails: true, - emailAdapter: emailAdapter, - emailVerifyTokenValidityDuration: 5, // 5 seconds - publicServerURL: 'http://localhost:8378/1', - }) - .then(() => { - user.setUsername('resends_verification_token'); - user.setPassword('expiringToken'); - user.set('email', 'user@parse.com'); - return user.signUp(); - }) - .then(() => { - const config = Config.get('test'); - return config.database - .find('_User', { username: 'resends_verification_token' }) - .then(results => { - return results[0]; - }); - }) - .then(newUser => { - // store this user before we make our email request - userBeforeRequest = newUser; - - expect(sendVerificationEmailCallCount).toBe(1); - - return request({ - url: 'http://localhost:8378/1/verificationEmailRequest', - method: 'POST', - body: { - email: 'user@parse.com', - }, - headers: { - 'X-Parse-Application-Id': Parse.applicationId, - 'X-Parse-REST-API-Key': 'rest', - 'Content-Type': 'application/json', - }, - }); + verifyUserEmails: true, + emailAdapter: emailAdapter, + publicServerURL: 'http://localhost:8378/1', + }; + + // setup server WITHOUT enabling the expire email verify token flag + reconfigureServer(serverConfig) + .then(() => { + user.setUsername('testEmailVerifyTokenValidity'); + user.setPassword('expiringToken'); + user.set('email', 'user@parse.com'); + return user.signUp(); }) - .then(response => { - expect(response.status).toBe(200); + .then(() => { + // just get the user again - DO NOT email verify the user + return user.fetch(); }) - .then(() => jasmine.timeout()) .then(() => { - expect(sendVerificationEmailCallCount).toBe(2); - expect(sendEmailOptions).toBeDefined(); - - // query for this user again - const config = Config.get('test'); - return config.database - .find('_User', { username: 'resends_verification_token' }, {}, Auth.maintenance(config)) - .then(results => { - return results[0]; - }); + expect(user.get('emailVerified')).toEqual(false); + // RECONFIGURE the server i.e., ENABLE the expire email verify token flag + serverConfig.emailVerifyTokenValidityDuration = 5; // 5 seconds + return reconfigureServer(serverConfig); }) - .then(userAfterRequest => { - // verify that our token & expiration has been changed for this new request - expect(typeof userAfterRequest).toBe('object'); - expect(userBeforeRequest._email_verify_token).not.toEqual( - userAfterRequest._email_verify_token - ); - expect(userBeforeRequest._email_verify_token_expires_at).not.toEqual( - userAfterRequest._email_verify_token_expires_at - ); - done(); + .then(() => { + request({ + url: sendEmailOptions.link, + followRedirects: false, + }).then(response => { + expect(response.status).toEqual(302); + expect(response.text).toEqual( + 'Found. Redirecting to http://localhost:8378/1/apps/invalid_verification_link.html?username=testEmailVerifyTokenValidity&appId=test' + ); + done(); + }); }) .catch(error => { - console.log(error); jfail(error); done(); }); }); + it_id('b6c87f35-d887-477d-bc86-a9217a424f53')(it)( + 'setting the email on the user should set a new email verification token and new expiration date for the token when expire email verify token flag is set', + done => { + const user = new Parse.User(); + let userBeforeEmailReset; + + let sendEmailOptions; + const emailAdapter = { + sendVerificationEmail: options => { + sendEmailOptions = options; + }, + sendPasswordResetEmail: () => Promise.resolve(), + sendMail: () => {}, + }; + const serverConfig = { + appName: 'emailVerifyToken', + verifyUserEmails: true, + emailAdapter: emailAdapter, + emailVerifyTokenValidityDuration: 5, // 5 seconds + publicServerURL: 'http://localhost:8378/1', + }; + + reconfigureServer(serverConfig) + .then(() => { + user.setUsername('newEmailVerifyTokenOnEmailReset'); + user.setPassword('expiringToken'); + user.set('email', 'user@parse.com'); + return user.signUp(); + }) + .then(() => { + const config = Config.get('test'); + return config.database + .find('_User', { username: 'newEmailVerifyTokenOnEmailReset' }) + .then(results => { + return results[0]; + }); + }) + .then(userFromDb => { + expect(typeof userFromDb).toBe('object'); + userBeforeEmailReset = userFromDb; + + // trigger another token generation by setting the email + user.set('email', 'user@parse.com'); + return new Promise(resolve => { + // wait for half a sec to get a new expiration time + setTimeout(() => resolve(user.save()), 500); + }); + }) + .then(() => { + const config = Config.get('test'); + return config.database + .find( + '_User', + { username: 'newEmailVerifyTokenOnEmailReset' }, + {}, + Auth.maintenance(config) + ) + .then(results => { + return results[0]; + }); + }) + .then(userAfterEmailReset => { + expect(typeof userAfterEmailReset).toBe('object'); + expect(userBeforeEmailReset._email_verify_token).not.toEqual( + userAfterEmailReset._email_verify_token + ); + expect(userBeforeEmailReset._email_verify_token_expires_at).not.toEqual( + userAfterEmailReset._email_verify_token_expires_at + ); + expect(sendEmailOptions).toBeDefined(); + done(); + }) + .catch(error => { + jfail(error); + done(); + }); + } + ); + + it_id('28f2140d-48bd-44ac-a141-ba60ea8d9713')(it)( + 'should send a new verification email when a resend is requested and the user is UNVERIFIED', + done => { + const user = new Parse.User(); + let sendEmailOptions; + let sendVerificationEmailCallCount = 0; + let userBeforeRequest; + const emailAdapter = { + sendVerificationEmail: options => { + sendEmailOptions = options; + sendVerificationEmailCallCount++; + }, + sendPasswordResetEmail: () => Promise.resolve(), + sendMail: () => {}, + }; + reconfigureServer({ + appName: 'emailVerifyToken', + verifyUserEmails: true, + emailAdapter: emailAdapter, + emailVerifyTokenValidityDuration: 5, // 5 seconds + publicServerURL: 'http://localhost:8378/1', + }) + .then(() => { + user.setUsername('resends_verification_token'); + user.setPassword('expiringToken'); + user.set('email', 'user@parse.com'); + return user.signUp(); + }) + .then(() => { + const config = Config.get('test'); + return config.database + .find('_User', { username: 'resends_verification_token' }) + .then(results => { + return results[0]; + }); + }) + .then(newUser => { + // store this user before we make our email request + userBeforeRequest = newUser; + + expect(sendVerificationEmailCallCount).toBe(1); + + return request({ + url: 'http://localhost:8378/1/verificationEmailRequest', + method: 'POST', + body: { + email: 'user@parse.com', + }, + headers: { + 'X-Parse-Application-Id': Parse.applicationId, + 'X-Parse-REST-API-Key': 'rest', + 'Content-Type': 'application/json', + }, + }); + }) + .then(response => { + expect(response.status).toBe(200); + }) + .then(() => jasmine.timeout()) + .then(() => { + expect(sendVerificationEmailCallCount).toBe(2); + expect(sendEmailOptions).toBeDefined(); + + // query for this user again + const config = Config.get('test'); + return config.database + .find('_User', { username: 'resends_verification_token' }, {}, Auth.maintenance(config)) + .then(results => { + return results[0]; + }); + }) + .then(userAfterRequest => { + // verify that our token & expiration has been changed for this new request + expect(typeof userAfterRequest).toBe('object'); + expect(userBeforeRequest._email_verify_token).not.toEqual( + userAfterRequest._email_verify_token + ); + expect(userBeforeRequest._email_verify_token_expires_at).not.toEqual( + userAfterRequest._email_verify_token_expires_at + ); + done(); + }) + .catch(error => { + console.log(error); + jfail(error); + done(); + }); + } + ); + it('provides function arguments in verifyUserEmails on verificationEmailRequest', async () => { const user = new Parse.User(); user.setUsername('user'); @@ -850,7 +892,7 @@ describe('Email Verification Token Expiration: ', () => { await user.signUp(); const verifyUserEmails = { - method: async (params) => { + method: async params => { expect(params.object).toBeInstanceOf(Parse.User); expect(params.ip).toBeDefined(); expect(params.master).toBeDefined(); @@ -916,133 +958,151 @@ describe('Email Verification Token Expiration: ', () => { done(); }); - it_id('0e66b7f6-2c07-4117-a8b9-605aa31a1e29')(it)('should match codes with emailVerifyTokenReuseIfValid', async done => { - let sendEmailOptions; - let sendVerificationEmailCallCount = 0; - const emailAdapter = { - sendVerificationEmail: options => { - sendEmailOptions = options; - sendVerificationEmailCallCount++; - }, - sendPasswordResetEmail: () => Promise.resolve(), - sendMail: () => {}, - }; - await reconfigureServer({ - appName: 'emailVerifyToken', - verifyUserEmails: true, - emailAdapter: emailAdapter, - emailVerifyTokenValidityDuration: 5 * 60, // 5 minutes - publicServerURL: 'http://localhost:8378/1', - emailVerifyTokenReuseIfValid: true, - }); - const user = new Parse.User(); - user.setUsername('resends_verification_token'); - user.setPassword('expiringToken'); - user.set('email', 'user@example.com'); - await user.signUp(); + it_id('0e66b7f6-2c07-4117-a8b9-605aa31a1e29')(it)( + 'should match codes with emailVerifyTokenReuseIfValid', + async done => { + let sendEmailOptions; + let sendVerificationEmailCallCount = 0; + const emailAdapter = { + sendVerificationEmail: options => { + sendEmailOptions = options; + sendVerificationEmailCallCount++; + }, + sendPasswordResetEmail: () => Promise.resolve(), + sendMail: () => {}, + }; + await reconfigureServer({ + appName: 'emailVerifyToken', + verifyUserEmails: true, + emailAdapter: emailAdapter, + emailVerifyTokenValidityDuration: 5 * 60, // 5 minutes + publicServerURL: 'http://localhost:8378/1', + emailVerifyTokenReuseIfValid: true, + }); + const user = new Parse.User(); + user.setUsername('resends_verification_token'); + user.setPassword('expiringToken'); + user.set('email', 'user@example.com'); + await user.signUp(); - const config = Config.get('test'); - const [userBeforeRequest] = await config.database.find('_User', { - username: 'resends_verification_token', - }, {}, Auth.maintenance(config)); - // store this user before we make our email request - expect(sendVerificationEmailCallCount).toBe(1); - await new Promise(resolve => { - setTimeout(() => { - resolve(); - }, 1000); - }); - const response = await request({ - url: 'http://localhost:8378/1/verificationEmailRequest', - method: 'POST', - body: { - email: 'user@example.com', - }, - headers: { - 'X-Parse-Application-Id': Parse.applicationId, - 'X-Parse-REST-API-Key': 'rest', - 'Content-Type': 'application/json', - }, - }); - await jasmine.timeout(); - expect(response.status).toBe(200); - expect(sendVerificationEmailCallCount).toBe(2); - expect(sendEmailOptions).toBeDefined(); + const config = Config.get('test'); + const [userBeforeRequest] = await config.database.find( + '_User', + { + username: 'resends_verification_token', + }, + {}, + Auth.maintenance(config) + ); + // store this user before we make our email request + expect(sendVerificationEmailCallCount).toBe(1); + await new Promise(resolve => { + setTimeout(() => { + resolve(); + }, 1000); + }); + const response = await request({ + url: 'http://localhost:8378/1/verificationEmailRequest', + method: 'POST', + body: { + email: 'user@example.com', + }, + headers: { + 'X-Parse-Application-Id': Parse.applicationId, + 'X-Parse-REST-API-Key': 'rest', + 'Content-Type': 'application/json', + }, + }); + await jasmine.timeout(); + expect(response.status).toBe(200); + expect(sendVerificationEmailCallCount).toBe(2); + expect(sendEmailOptions).toBeDefined(); - const [userAfterRequest] = await config.database.find('_User', { - username: 'resends_verification_token', - }, {}, Auth.maintenance(config)); + const [userAfterRequest] = await config.database.find( + '_User', + { + username: 'resends_verification_token', + }, + {}, + Auth.maintenance(config) + ); - // Verify that token & expiration haven't been changed for this new request - expect(typeof userAfterRequest).toBe('object'); - expect(userBeforeRequest._email_verify_token).toBeDefined(); - expect(userBeforeRequest._email_verify_token).toEqual(userAfterRequest._email_verify_token); - expect(userBeforeRequest._email_verify_token_expires_at).toBeDefined(); - expect(userBeforeRequest._email_verify_token_expires_at).toEqual(userAfterRequest._email_verify_token_expires_at); - done(); - }); + // Verify that token & expiration haven't been changed for this new request + expect(typeof userAfterRequest).toBe('object'); + expect(userBeforeRequest._email_verify_token).toBeDefined(); + expect(userBeforeRequest._email_verify_token).toEqual(userAfterRequest._email_verify_token); + expect(userBeforeRequest._email_verify_token_expires_at).toBeDefined(); + expect(userBeforeRequest._email_verify_token_expires_at).toEqual( + userAfterRequest._email_verify_token_expires_at + ); + done(); + } + ); - it_id('1ed9a6c2-bebc-4813-af30-4f4a212544c2')(it)('should not send a new verification email when a resend is requested and the user is VERIFIED', done => { - const user = new Parse.User(); - let sendEmailOptions; - let sendVerificationEmailCallCount = 0; - const emailAdapter = { - sendVerificationEmail: options => { - sendEmailOptions = options; - sendVerificationEmailCallCount++; - }, - sendPasswordResetEmail: () => Promise.resolve(), - sendMail: () => {}, - }; - reconfigureServer({ - appName: 'emailVerifyToken', - verifyUserEmails: true, - emailAdapter: emailAdapter, - emailVerifyTokenValidityDuration: 5, // 5 seconds - publicServerURL: 'http://localhost:8378/1', - }) - .then(() => { - user.setUsername('no_new_verification_token_once_verified'); - user.setPassword('expiringToken'); - user.set('email', 'user@parse.com'); - return user.signUp(); - }) - .then(() => jasmine.timeout()) - .then(() => { - return request({ - url: sendEmailOptions.link, - followRedirects: false, - }).then(response => { - expect(response.status).toEqual(302); - }); + it_id('1ed9a6c2-bebc-4813-af30-4f4a212544c2')(it)( + 'should not send a new verification email when a resend is requested and the user is VERIFIED', + done => { + const user = new Parse.User(); + let sendEmailOptions; + let sendVerificationEmailCallCount = 0; + const emailAdapter = { + sendVerificationEmail: options => { + sendEmailOptions = options; + sendVerificationEmailCallCount++; + }, + sendPasswordResetEmail: () => Promise.resolve(), + sendMail: () => {}, + }; + reconfigureServer({ + appName: 'emailVerifyToken', + verifyUserEmails: true, + emailAdapter: emailAdapter, + emailVerifyTokenValidityDuration: 5, // 5 seconds + publicServerURL: 'http://localhost:8378/1', }) - .then(() => { - expect(sendVerificationEmailCallCount).toBe(1); - - return request({ - url: 'http://localhost:8378/1/verificationEmailRequest', - method: 'POST', - body: { - email: 'user@parse.com', - }, - headers: { - 'X-Parse-Application-Id': Parse.applicationId, - 'X-Parse-REST-API-Key': 'rest', - 'Content-Type': 'application/json', - }, + .then(() => { + user.setUsername('no_new_verification_token_once_verified'); + user.setPassword('expiringToken'); + user.set('email', 'user@parse.com'); + return user.signUp(); }) - .then(fail, res => res) - .then(response => { - expect(response.status).toBe(400); - expect(sendVerificationEmailCallCount).toBe(1); - done(); + .then(() => jasmine.timeout()) + .then(() => { + return request({ + url: sendEmailOptions.link, + followRedirects: false, + }).then(response => { + expect(response.status).toEqual(302); }); - }) - .catch(error => { - jfail(error); - done(); - }); - }); + }) + .then(() => { + expect(sendVerificationEmailCallCount).toBe(1); + + return request({ + url: 'http://localhost:8378/1/verificationEmailRequest', + method: 'POST', + body: { + email: 'user@parse.com', + }, + headers: { + 'X-Parse-Application-Id': Parse.applicationId, + 'X-Parse-REST-API-Key': 'rest', + 'Content-Type': 'application/json', + }, + }) + .then(fail, res => res) + .then(response => { + expect(response.status).toBe(400); + expect(sendVerificationEmailCallCount).toBe(1); + done(); + }); + }) + .catch(error => { + jfail(error); + done(); + }); + } + ); it('should not send a new verification email if this user does not exist', done => { let sendEmailOptions; @@ -1223,67 +1283,70 @@ describe('Email Verification Token Expiration: ', () => { }); }); - it_id('b082d387-4974-4d45-a0d9-0c85ca2d7cbf')(it)('emailVerified should be set to false after changing from an already verified email', done => { - const user = new Parse.User(); - let sendEmailOptions; - const emailAdapter = { - sendVerificationEmail: options => { - sendEmailOptions = options; - }, - sendPasswordResetEmail: () => Promise.resolve(), - sendMail: () => {}, - }; - reconfigureServer({ - appName: 'emailVerifyToken', - verifyUserEmails: true, - emailAdapter: emailAdapter, - emailVerifyTokenValidityDuration: 5, // 5 seconds - publicServerURL: 'http://localhost:8378/1', - }) - .then(() => { - user.setUsername('testEmailVerifyTokenValidity'); - user.setPassword('expiringToken'); - user.set('email', 'user@parse.com'); - return user.signUp(); + it_id('b082d387-4974-4d45-a0d9-0c85ca2d7cbf')(it)( + 'emailVerified should be set to false after changing from an already verified email', + done => { + const user = new Parse.User(); + let sendEmailOptions; + const emailAdapter = { + sendVerificationEmail: options => { + sendEmailOptions = options; + }, + sendPasswordResetEmail: () => Promise.resolve(), + sendMail: () => {}, + }; + reconfigureServer({ + appName: 'emailVerifyToken', + verifyUserEmails: true, + emailAdapter: emailAdapter, + emailVerifyTokenValidityDuration: 5, // 5 seconds + publicServerURL: 'http://localhost:8378/1', }) - .then(() => jasmine.timeout()) - .then(() => { - request({ - url: sendEmailOptions.link, - followRedirects: false, - }).then(response => { - expect(response.status).toEqual(302); - Parse.User.logIn('testEmailVerifyTokenValidity', 'expiringToken') - .then(user => { - expect(typeof user).toBe('object'); - expect(user.get('emailVerified')).toBe(true); + .then(() => { + user.setUsername('testEmailVerifyTokenValidity'); + user.setPassword('expiringToken'); + user.set('email', 'user@parse.com'); + return user.signUp(); + }) + .then(() => jasmine.timeout()) + .then(() => { + request({ + url: sendEmailOptions.link, + followRedirects: false, + }).then(response => { + expect(response.status).toEqual(302); + Parse.User.logIn('testEmailVerifyTokenValidity', 'expiringToken') + .then(user => { + expect(typeof user).toBe('object'); + expect(user.get('emailVerified')).toBe(true); - user.set('email', 'newEmail@parse.com'); - return user.save(); - }) - .then(() => user.fetch()) - .then(user => { - expect(typeof user).toBe('object'); - expect(user.get('email')).toBe('newEmail@parse.com'); - expect(user.get('emailVerified')).toBe(false); + user.set('email', 'newEmail@parse.com'); + return user.save(); + }) + .then(() => user.fetch()) + .then(user => { + expect(typeof user).toBe('object'); + expect(user.get('email')).toBe('newEmail@parse.com'); + expect(user.get('emailVerified')).toBe(false); - request({ - url: sendEmailOptions.link, - followRedirects: false, - }).then(response => { - expect(response.status).toEqual(302); + request({ + url: sendEmailOptions.link, + followRedirects: false, + }).then(response => { + expect(response.status).toEqual(302); + done(); + }); + }) + .catch(error => { + jfail(error); done(); }); - }) - .catch(error => { - jfail(error); - done(); - }); + }); + }) + .catch(error => { + jfail(error); + done(); }); - }) - .catch(error => { - jfail(error); - done(); - }); - }); + } + ); }); diff --git a/spec/FilesController.spec.js b/spec/FilesController.spec.js index 30acf7d13c..2ee4ca0377 100644 --- a/spec/FilesController.spec.js +++ b/spec/FilesController.spec.js @@ -26,9 +26,9 @@ describe('FilesController', () => { const gridFSAdapter = new GridFSBucketAdapter('mongodb://localhost:27017/parse'); gridFSAdapter.getFileLocation = (config, filename) => { return config.mount + '/files/' + config.applicationId + '/' + encodeURIComponent(filename); - } + }; const filesController = new FilesController(gridFSAdapter); - const result = await filesController.expandFilesInObject(config, function () { }); + const result = await filesController.expandFilesInObject(config, function () {}); expect(result).toBeUndefined(); @@ -50,9 +50,9 @@ describe('FilesController', () => { gridFSAdapter.getFileLocation = async (config, filename) => { await Promise.resolve(); return config.mount + '/files/' + config.applicationId + '/' + encodeURIComponent(filename); - } + }; const filesController = new FilesController(gridFSAdapter); - const result = await filesController.expandFilesInObject(config, function () { }); + const result = await filesController.expandFilesInObject(config, function () {}); expect(result).toBeUndefined(); @@ -76,7 +76,9 @@ describe('FilesController', () => { name: 'mock-name', __type: 'File', }; - gridFSAdapter.getFileLocation = jasmine.createSpy('getFileLocation').and.returnValue(Promise.resolve('mock-url')); + gridFSAdapter.getFileLocation = jasmine + .createSpy('getFileLocation') + .and.returnValue(Promise.resolve('mock-url')); const filesController = new FilesController(gridFSAdapter); const anObject = { aFile: fullFile }; @@ -93,7 +95,9 @@ describe('FilesController', () => { name: 'mock-name', __type: 'File', }; - gridFSAdapter.getFileLocation = jasmine.createSpy('getFileLocation').and.returnValue(Promise.resolve('mock-url')); + gridFSAdapter.getFileLocation = jasmine + .createSpy('getFileLocation') + .and.returnValue(Promise.resolve('mock-url')); const filesController = new FilesController(gridFSAdapter); const anObject = { aFile: fullFile }; @@ -102,7 +106,6 @@ describe('FilesController', () => { expect(anObject.aFile.url).toEqual('mock-url'); }); - it_only_db('mongo')('should pass databaseOptions to GridFSBucketAdapter', async () => { await reconfigureServer({ databaseURI: 'mongodb://localhost:27017/parse', diff --git a/spec/Idempotency.spec.js b/spec/Idempotency.spec.js index 14d0469b86..fd666d4360 100644 --- a/spec/Idempotency.spec.js +++ b/spec/Idempotency.spec.js @@ -50,52 +50,58 @@ describe('Idempotency', () => { }); // Tests - it_id('e25955fd-92eb-4b22-b8b7-38980e5cb223')(it)('should enforce idempotency for cloud code function', async () => { - let counter = 0; - Parse.Cloud.define('myFunction', () => { - counter++; - }); - const params = { - method: 'POST', - url: 'http://localhost:8378/1/functions/myFunction', - headers: { - 'X-Parse-Application-Id': Parse.applicationId, - 'X-Parse-Master-Key': Parse.masterKey, - 'X-Parse-Request-Id': 'abc-123', - }, - }; - expect(Config.get(Parse.applicationId).idempotencyOptions.ttl).toBe(ttl); - await request(params); - await request(params).then(fail, e => { - expect(e.status).toEqual(400); - expect(e.data.error).toEqual('Duplicate request'); - }); - expect(counter).toBe(1); - }); + it_id('e25955fd-92eb-4b22-b8b7-38980e5cb223')(it)( + 'should enforce idempotency for cloud code function', + async () => { + let counter = 0; + Parse.Cloud.define('myFunction', () => { + counter++; + }); + const params = { + method: 'POST', + url: 'http://localhost:8378/1/functions/myFunction', + headers: { + 'X-Parse-Application-Id': Parse.applicationId, + 'X-Parse-Master-Key': Parse.masterKey, + 'X-Parse-Request-Id': 'abc-123', + }, + }; + expect(Config.get(Parse.applicationId).idempotencyOptions.ttl).toBe(ttl); + await request(params); + await request(params).then(fail, e => { + expect(e.status).toEqual(400); + expect(e.data.error).toEqual('Duplicate request'); + }); + expect(counter).toBe(1); + } + ); - it_id('be2fbe16-8178-485e-9a12-6fb541096480')(it)('should delete request entry after TTL', async () => { - let counter = 0; - Parse.Cloud.define('myFunction', () => { - counter++; - }); - const params = { - method: 'POST', - url: 'http://localhost:8378/1/functions/myFunction', - headers: { - 'X-Parse-Application-Id': Parse.applicationId, - 'X-Parse-Master-Key': Parse.masterKey, - 'X-Parse-Request-Id': 'abc-123', - }, - }; - await expectAsync(request(params)).toBeResolved(); - if (SIMULATE_TTL) { - await deleteRequestEntry('abc-123'); - } else { - await new Promise(resolve => setTimeout(resolve, maxTimeOut)); + it_id('be2fbe16-8178-485e-9a12-6fb541096480')(it)( + 'should delete request entry after TTL', + async () => { + let counter = 0; + Parse.Cloud.define('myFunction', () => { + counter++; + }); + const params = { + method: 'POST', + url: 'http://localhost:8378/1/functions/myFunction', + headers: { + 'X-Parse-Application-Id': Parse.applicationId, + 'X-Parse-Master-Key': Parse.masterKey, + 'X-Parse-Request-Id': 'abc-123', + }, + }; + await expectAsync(request(params)).toBeResolved(); + if (SIMULATE_TTL) { + await deleteRequestEntry('abc-123'); + } else { + await new Promise(resolve => setTimeout(resolve, maxTimeOut)); + } + await expectAsync(request(params)).toBeResolved(); + expect(counter).toBe(2); } - await expectAsync(request(params)).toBeResolved(); - expect(counter).toBe(2); - }); + ); it_only_db('postgres')( 'should delete request entry when postgress ttl function is called', @@ -123,140 +129,158 @@ describe('Idempotency', () => { } ); - it_id('e976d0cc-a57f-45d4-9472-b9b052db6490')(it)('should enforce idempotency for cloud code jobs', async () => { - let counter = 0; - Parse.Cloud.job('myJob', () => { - counter++; - }); - const params = { - method: 'POST', - url: 'http://localhost:8378/1/jobs/myJob', - headers: { - 'X-Parse-Application-Id': Parse.applicationId, - 'X-Parse-Master-Key': Parse.masterKey, - 'X-Parse-Request-Id': 'abc-123', - }, - }; - await expectAsync(request(params)).toBeResolved(); - await request(params).then(fail, e => { - expect(e.status).toEqual(400); - expect(e.data.error).toEqual('Duplicate request'); - }); - expect(counter).toBe(1); - }); - - it_id('7c84a3d4-e1b6-4a0d-99f1-af3cf1a6b3d8')(it)('should enforce idempotency for class object creation', async () => { - let counter = 0; - Parse.Cloud.afterSave('MyClass', () => { - counter++; - }); - const params = { - method: 'POST', - url: 'http://localhost:8378/1/classes/MyClass', - headers: { - 'X-Parse-Application-Id': Parse.applicationId, - 'X-Parse-Master-Key': Parse.masterKey, - 'X-Parse-Request-Id': 'abc-123', - }, - }; - await expectAsync(request(params)).toBeResolved(); - await request(params).then(fail, e => { - expect(e.status).toEqual(400); - expect(e.data.error).toEqual('Duplicate request'); - }); - expect(counter).toBe(1); - }); + it_id('e976d0cc-a57f-45d4-9472-b9b052db6490')(it)( + 'should enforce idempotency for cloud code jobs', + async () => { + let counter = 0; + Parse.Cloud.job('myJob', () => { + counter++; + }); + const params = { + method: 'POST', + url: 'http://localhost:8378/1/jobs/myJob', + headers: { + 'X-Parse-Application-Id': Parse.applicationId, + 'X-Parse-Master-Key': Parse.masterKey, + 'X-Parse-Request-Id': 'abc-123', + }, + }; + await expectAsync(request(params)).toBeResolved(); + await request(params).then(fail, e => { + expect(e.status).toEqual(400); + expect(e.data.error).toEqual('Duplicate request'); + }); + expect(counter).toBe(1); + } + ); - it_id('a030f2dd-5d21-46ac-b53d-9d714f35d72a')(it)('should enforce idempotency for user object creation', async () => { - let counter = 0; - Parse.Cloud.afterSave('_User', () => { - counter++; - }); - const params = { - method: 'POST', - url: 'http://localhost:8378/1/users', - body: { - username: 'user', - password: 'pass', - }, - headers: { - 'X-Parse-Application-Id': Parse.applicationId, - 'X-Parse-Master-Key': Parse.masterKey, - 'X-Parse-Request-Id': 'abc-123', - }, - }; - await expectAsync(request(params)).toBeResolved(); - await request(params).then(fail, e => { - expect(e.status).toEqual(400); - expect(e.data.error).toEqual('Duplicate request'); - }); - expect(counter).toBe(1); - }); + it_id('7c84a3d4-e1b6-4a0d-99f1-af3cf1a6b3d8')(it)( + 'should enforce idempotency for class object creation', + async () => { + let counter = 0; + Parse.Cloud.afterSave('MyClass', () => { + counter++; + }); + const params = { + method: 'POST', + url: 'http://localhost:8378/1/classes/MyClass', + headers: { + 'X-Parse-Application-Id': Parse.applicationId, + 'X-Parse-Master-Key': Parse.masterKey, + 'X-Parse-Request-Id': 'abc-123', + }, + }; + await expectAsync(request(params)).toBeResolved(); + await request(params).then(fail, e => { + expect(e.status).toEqual(400); + expect(e.data.error).toEqual('Duplicate request'); + }); + expect(counter).toBe(1); + } + ); - it_id('064c469b-091c-4ba9-9043-be461f26a3eb')(it)('should enforce idempotency for installation object creation', async () => { - let counter = 0; - Parse.Cloud.afterSave('_Installation', () => { - counter++; - }); - const params = { - method: 'POST', - url: 'http://localhost:8378/1/installations', - body: { - installationId: '1', - deviceType: 'ios', - }, - headers: { - 'X-Parse-Application-Id': Parse.applicationId, - 'X-Parse-Master-Key': Parse.masterKey, - 'X-Parse-Request-Id': 'abc-123', - }, - }; - await expectAsync(request(params)).toBeResolved(); - await request(params).then(fail, e => { - expect(e.status).toEqual(400); - expect(e.data.error).toEqual('Duplicate request'); - }); - expect(counter).toBe(1); - }); + it_id('a030f2dd-5d21-46ac-b53d-9d714f35d72a')(it)( + 'should enforce idempotency for user object creation', + async () => { + let counter = 0; + Parse.Cloud.afterSave('_User', () => { + counter++; + }); + const params = { + method: 'POST', + url: 'http://localhost:8378/1/users', + body: { + username: 'user', + password: 'pass', + }, + headers: { + 'X-Parse-Application-Id': Parse.applicationId, + 'X-Parse-Master-Key': Parse.masterKey, + 'X-Parse-Request-Id': 'abc-123', + }, + }; + await expectAsync(request(params)).toBeResolved(); + await request(params).then(fail, e => { + expect(e.status).toEqual(400); + expect(e.data.error).toEqual('Duplicate request'); + }); + expect(counter).toBe(1); + } + ); - it_id('f11670b6-fa9c-4f21-a268-ae4b6bbff7fd')(it)('should not interfere with calls of different request ID', async () => { - let counter = 0; - Parse.Cloud.afterSave('MyClass', () => { - counter++; - }); - const promises = [...Array(100).keys()].map(() => { + it_id('064c469b-091c-4ba9-9043-be461f26a3eb')(it)( + 'should enforce idempotency for installation object creation', + async () => { + let counter = 0; + Parse.Cloud.afterSave('_Installation', () => { + counter++; + }); const params = { method: 'POST', - url: 'http://localhost:8378/1/classes/MyClass', + url: 'http://localhost:8378/1/installations', + body: { + installationId: '1', + deviceType: 'ios', + }, headers: { 'X-Parse-Application-Id': Parse.applicationId, 'X-Parse-Master-Key': Parse.masterKey, - 'X-Parse-Request-Id': uuid.v4(), + 'X-Parse-Request-Id': 'abc-123', }, }; - return request(params); - }); - await expectAsync(Promise.all(promises)).toBeResolved(); - expect(counter).toBe(100); - }); + await expectAsync(request(params)).toBeResolved(); + await request(params).then(fail, e => { + expect(e.status).toEqual(400); + expect(e.data.error).toEqual('Duplicate request'); + }); + expect(counter).toBe(1); + } + ); - it_id('0ecd2cd2-dafb-4a2b-bb2b-9ad4c9aca777')(it)('should re-throw any other error unchanged when writing request entry fails for any other reason', async () => { - spyOn(rest, 'create').and.rejectWith(new Parse.Error(0, 'some other error')); - Parse.Cloud.define('myFunction', () => {}); - const params = { - method: 'POST', - url: 'http://localhost:8378/1/functions/myFunction', - headers: { - 'X-Parse-Application-Id': Parse.applicationId, - 'X-Parse-Master-Key': Parse.masterKey, - 'X-Parse-Request-Id': 'abc-123', - }, - }; - await request(params).then(fail, e => { - expect(e.status).toEqual(400); - expect(e.data.error).toEqual('some other error'); - }); - }); + it_id('f11670b6-fa9c-4f21-a268-ae4b6bbff7fd')(it)( + 'should not interfere with calls of different request ID', + async () => { + let counter = 0; + Parse.Cloud.afterSave('MyClass', () => { + counter++; + }); + const promises = [...Array(100).keys()].map(() => { + const params = { + method: 'POST', + url: 'http://localhost:8378/1/classes/MyClass', + headers: { + 'X-Parse-Application-Id': Parse.applicationId, + 'X-Parse-Master-Key': Parse.masterKey, + 'X-Parse-Request-Id': uuid.v4(), + }, + }; + return request(params); + }); + await expectAsync(Promise.all(promises)).toBeResolved(); + expect(counter).toBe(100); + } + ); + + it_id('0ecd2cd2-dafb-4a2b-bb2b-9ad4c9aca777')(it)( + 'should re-throw any other error unchanged when writing request entry fails for any other reason', + async () => { + spyOn(rest, 'create').and.rejectWith(new Parse.Error(0, 'some other error')); + Parse.Cloud.define('myFunction', () => {}); + const params = { + method: 'POST', + url: 'http://localhost:8378/1/functions/myFunction', + headers: { + 'X-Parse-Application-Id': Parse.applicationId, + 'X-Parse-Master-Key': Parse.masterKey, + 'X-Parse-Request-Id': 'abc-123', + }, + }; + await request(params).then(fail, e => { + expect(e.status).toEqual(400); + expect(e.data.error).toEqual('some other error'); + }); + } + ); it('should use default configuration when none is set', async () => { await setup({}); diff --git a/spec/LogsRouter.spec.js b/spec/LogsRouter.spec.js index b25ac25be5..bc2befa29c 100644 --- a/spec/LogsRouter.spec.js +++ b/spec/LogsRouter.spec.js @@ -75,50 +75,19 @@ describe_only(() => { /** * Verifies simple passwords in GET login requests with special characters are scrubbed from the verbose log */ - it_id('e36d6141-2a20-41d0-85fc-d1534c3e4bae')(it)('does scrub simple passwords on GET login', done => { - reconfigureServer({ - verbose: true, - }).then(function () { - request({ - headers: headers, - url: 'http://localhost:8378/1/login?username=test&password=simplepass.com', - }) - .catch(() => {}) - .then(() => { - request({ - url: 'http://localhost:8378/1/scriptlog?size=4&level=verbose', - headers: headers, - }).then(response => { - const body = response.data; - expect(response.status).toEqual(200); - // 4th entry is our actual GET request - expect(body[2].url).toEqual('/1/login?username=test&password=********'); - expect(body[2].message).toEqual( - 'REQUEST for [GET] /1/login?username=test&password=********: {}' - ); - done(); - }); - }); - }); - }); - - /** - * Verifies complex passwords in GET login requests with special characters are scrubbed from the verbose log - */ - it_id('24b277c5-250f-4a35-a449-2c8c519d4c03')(it)('does scrub complex passwords on GET login', done => { - reconfigureServer({ - verbose: true, - }) - .then(function () { - return request({ + it_id('e36d6141-2a20-41d0-85fc-d1534c3e4bae')(it)( + 'does scrub simple passwords on GET login', + done => { + reconfigureServer({ + verbose: true, + }).then(function () { + request({ headers: headers, - // using urlencoded password, 'simple @,/?:&=+$#pass.com' - url: - 'http://localhost:8378/1/login?username=test&password=simple%20%40%2C%2F%3F%3A%26%3D%2B%24%23pass.com', + url: 'http://localhost:8378/1/login?username=test&password=simplepass.com', }) .catch(() => {}) .then(() => { - return request({ + request({ url: 'http://localhost:8378/1/scriptlog?size=4&level=verbose', headers: headers, }).then(response => { @@ -132,42 +101,82 @@ describe_only(() => { done(); }); }); - }) - .catch(done.fail); - }); + }); + } + ); /** - * Verifies fields in POST login requests are NOT present in the verbose log + * Verifies complex passwords in GET login requests with special characters are scrubbed from the verbose log */ - it_id('33143ec9-b32d-467c-ba65-ff2bbefdaadd')(it)('does not have password field in POST login', done => { - reconfigureServer({ - verbose: true, - }).then(function () { - request({ - method: 'POST', - headers: headers, - url: 'http://localhost:8378/1/login', - body: { - username: 'test', - password: 'simplepass.com', - }, + it_id('24b277c5-250f-4a35-a449-2c8c519d4c03')(it)( + 'does scrub complex passwords on GET login', + done => { + reconfigureServer({ + verbose: true, }) - .catch(() => {}) - .then(() => { - request({ - url: 'http://localhost:8378/1/scriptlog?size=4&level=verbose', + .then(function () { + return request({ headers: headers, - }).then(response => { - const body = response.data; - expect(response.status).toEqual(200); - // 4th entry is our actual GET request - expect(body[2].url).toEqual('/1/login'); - expect(body[2].message).toEqual( - 'REQUEST for [POST] /1/login: {\n "username": "test",\n "password": "********"\n}' - ); - done(); + // using urlencoded password, 'simple @,/?:&=+$#pass.com' + url: + 'http://localhost:8378/1/login?username=test&password=simple%20%40%2C%2F%3F%3A%26%3D%2B%24%23pass.com', + }) + .catch(() => {}) + .then(() => { + return request({ + url: 'http://localhost:8378/1/scriptlog?size=4&level=verbose', + headers: headers, + }).then(response => { + const body = response.data; + expect(response.status).toEqual(200); + // 4th entry is our actual GET request + expect(body[2].url).toEqual('/1/login?username=test&password=********'); + expect(body[2].message).toEqual( + 'REQUEST for [GET] /1/login?username=test&password=********: {}' + ); + done(); + }); + }); + }) + .catch(done.fail); + } + ); + + /** + * Verifies fields in POST login requests are NOT present in the verbose log + */ + it_id('33143ec9-b32d-467c-ba65-ff2bbefdaadd')(it)( + 'does not have password field in POST login', + done => { + reconfigureServer({ + verbose: true, + }).then(function () { + request({ + method: 'POST', + headers: headers, + url: 'http://localhost:8378/1/login', + body: { + username: 'test', + password: 'simplepass.com', + }, + }) + .catch(() => {}) + .then(() => { + request({ + url: 'http://localhost:8378/1/scriptlog?size=4&level=verbose', + headers: headers, + }).then(response => { + const body = response.data; + expect(response.status).toEqual(200); + // 4th entry is our actual GET request + expect(body[2].url).toEqual('/1/login'); + expect(body[2].message).toEqual( + 'REQUEST for [POST] /1/login: {\n "username": "test",\n "password": "********"\n}' + ); + done(); + }); }); - }); - }); - }); + }); + } + ); }); diff --git a/spec/Middlewares.spec.js b/spec/Middlewares.spec.js index c0fcb659e5..a6129ecf51 100644 --- a/spec/Middlewares.spec.js +++ b/spec/Middlewares.spec.js @@ -128,49 +128,55 @@ describe('middlewares', () => { const otherKeys = BodyKeys.filter( otherKey => otherKey !== infoKey && otherKey !== 'javascriptKey' ); - it_id('f9abd7ac-b1f4-4607-b9b0-365ff0559d84')(it)(`it should pull ${bodyKey} into req.info`, done => { - AppCachePut(fakeReq.body._ApplicationId, { - masterKeyIps: ['0.0.0.0/0'], - }); - fakeReq.ip = '127.0.0.1'; - fakeReq.body[bodyKey] = keyValue; - middlewares.handleParseHeaders(fakeReq, fakeRes, () => { - expect(fakeReq.body[bodyKey]).toEqual(undefined); - expect(fakeReq.info[infoKey]).toEqual(keyValue); + it_id('f9abd7ac-b1f4-4607-b9b0-365ff0559d84')(it)( + `it should pull ${bodyKey} into req.info`, + done => { + AppCachePut(fakeReq.body._ApplicationId, { + masterKeyIps: ['0.0.0.0/0'], + }); + fakeReq.ip = '127.0.0.1'; + fakeReq.body[bodyKey] = keyValue; + middlewares.handleParseHeaders(fakeReq, fakeRes, () => { + expect(fakeReq.body[bodyKey]).toEqual(undefined); + expect(fakeReq.info[infoKey]).toEqual(keyValue); + + otherKeys.forEach(otherKey => { + expect(fakeReq.info[otherKey]).toEqual(undefined); + }); - otherKeys.forEach(otherKey => { - expect(fakeReq.info[otherKey]).toEqual(undefined); + done(); }); + } + ); + }); - done(); + it_id('4a0bce41-c536-4482-a873-12ed023380e2')(it)( + 'should not succeed and log if the ip does not belong to masterKeyIps list', + async () => { + const logger = require('../lib/logger').logger; + spyOn(logger, 'error').and.callFake(() => {}); + AppCachePut(fakeReq.body._ApplicationId, { + masterKey: 'masterKey', + masterKeyIps: ['10.0.0.1'], }); - }); - }); + fakeReq.ip = '127.0.0.1'; + fakeReq.headers['x-parse-master-key'] = 'masterKey'; - it_id('4a0bce41-c536-4482-a873-12ed023380e2')(it)('should not succeed and log if the ip does not belong to masterKeyIps list', async () => { - const logger = require('../lib/logger').logger; - spyOn(logger, 'error').and.callFake(() => {}); - AppCachePut(fakeReq.body._ApplicationId, { - masterKey: 'masterKey', - masterKeyIps: ['10.0.0.1'], - }); - fakeReq.ip = '127.0.0.1'; - fakeReq.headers['x-parse-master-key'] = 'masterKey'; + let error; - let error; + try { + await new Promise(resolve => middlewares.handleParseHeaders(fakeReq, fakeRes, resolve)); + } catch (err) { + error = err; + } - try { - await new Promise(resolve => middlewares.handleParseHeaders(fakeReq, fakeRes, resolve)); - } catch (err) { - error = err; + expect(error).toBeDefined(); + expect(error.message).toEqual(`unauthorized`); + expect(logger.error).toHaveBeenCalledWith( + `Request using master key rejected as the request IP address '127.0.0.1' is not set in Parse Server option 'masterKeyIps'.` + ); } - - expect(error).toBeDefined(); - expect(error.message).toEqual(`unauthorized`); - expect(logger.error).toHaveBeenCalledWith( - `Request using master key rejected as the request IP address '127.0.0.1' is not set in Parse Server option 'masterKeyIps'.` - ); - }); + ); it('should not succeed and log if the ip does not belong to maintenanceKeyIps list', async () => { const logger = require('../lib/logger').logger; @@ -197,27 +203,33 @@ describe('middlewares', () => { ); }); - it_id('2f7fadec-a87c-4626-90d1-65c75653aea9')(it)('should succeed if the ip does belong to masterKeyIps list', async () => { - AppCachePut(fakeReq.body._ApplicationId, { - masterKey: 'masterKey', - masterKeyIps: ['10.0.0.1'], - }); - fakeReq.ip = '10.0.0.1'; - fakeReq.headers['x-parse-master-key'] = 'masterKey'; - await new Promise(resolve => middlewares.handleParseHeaders(fakeReq, fakeRes, resolve)); - expect(fakeReq.auth.isMaster).toBe(true); - }); + it_id('2f7fadec-a87c-4626-90d1-65c75653aea9')(it)( + 'should succeed if the ip does belong to masterKeyIps list', + async () => { + AppCachePut(fakeReq.body._ApplicationId, { + masterKey: 'masterKey', + masterKeyIps: ['10.0.0.1'], + }); + fakeReq.ip = '10.0.0.1'; + fakeReq.headers['x-parse-master-key'] = 'masterKey'; + await new Promise(resolve => middlewares.handleParseHeaders(fakeReq, fakeRes, resolve)); + expect(fakeReq.auth.isMaster).toBe(true); + } + ); - it_id('2b251fd4-d43c-48f4-ada9-c8458e40c12a')(it)('should allow any ip to use masterKey if masterKeyIps is empty', async () => { - AppCachePut(fakeReq.body._ApplicationId, { - masterKey: 'masterKey', - masterKeyIps: ['0.0.0.0/0'], - }); - fakeReq.ip = '10.0.0.1'; - fakeReq.headers['x-parse-master-key'] = 'masterKey'; - await new Promise(resolve => middlewares.handleParseHeaders(fakeReq, fakeRes, resolve)); - expect(fakeReq.auth.isMaster).toBe(true); - }); + it_id('2b251fd4-d43c-48f4-ada9-c8458e40c12a')(it)( + 'should allow any ip to use masterKey if masterKeyIps is empty', + async () => { + AppCachePut(fakeReq.body._ApplicationId, { + masterKey: 'masterKey', + masterKeyIps: ['0.0.0.0/0'], + }); + fakeReq.ip = '10.0.0.1'; + fakeReq.headers['x-parse-master-key'] = 'masterKey'; + await new Promise(resolve => middlewares.handleParseHeaders(fakeReq, fakeRes, resolve)); + expect(fakeReq.auth.isMaster).toBe(true); + } + ); it('can set trust proxy', async () => { const server = await reconfigureServer({ trustProxy: 1 }); diff --git a/spec/PagesRouter.spec.js b/spec/PagesRouter.spec.js index ca61fa4f5a..29e8744e20 100644 --- a/spec/PagesRouter.spec.js +++ b/spec/PagesRouter.spec.js @@ -738,148 +738,157 @@ describe('Pages Router', () => { ); }); - it_id('2845c2ea-23ba-45d2-a33f-63181d419bca')(it)('localizes end-to-end for verify email: success', async () => { - await reconfigureServer(config); - const sendVerificationEmail = spyOn( - config.emailAdapter, - 'sendVerificationEmail' - ).and.callThrough(); - const user = new Parse.User(); - user.setUsername('exampleUsername'); - user.setPassword('examplePassword'); - user.set('email', 'mail@example.com'); - await user.signUp(); - await jasmine.timeout(); - - const link = sendVerificationEmail.calls.all()[0].args[0].link; - const linkWithLocale = new URL(link); - linkWithLocale.searchParams.append(pageParams.locale, exampleLocale); - - const linkResponse = await request({ - url: linkWithLocale.toString(), - followRedirects: false, - }); - expect(linkResponse.status).toBe(200); - - const pagePath = pageResponse.calls.all()[0].args[0]; - expect(pagePath).toMatch( - new RegExp(`\/${exampleLocale}\/${pages.emailVerificationSuccess.defaultFile}`) - ); - }); - - it_id('f2272b94-b4ac-474f-8e47-1ca74de136f5')(it)('localizes end-to-end for verify email: invalid verification link - link send success', async () => { - await reconfigureServer(config); - const sendVerificationEmail = spyOn( - config.emailAdapter, - 'sendVerificationEmail' - ).and.callThrough(); - const user = new Parse.User(); - user.setUsername('exampleUsername'); - user.setPassword('examplePassword'); - user.set('email', 'mail@example.com'); - await user.signUp(); - await jasmine.timeout(); - - const link = sendVerificationEmail.calls.all()[0].args[0].link; - const linkWithLocale = new URL(link); - linkWithLocale.searchParams.append(pageParams.locale, exampleLocale); - linkWithLocale.searchParams.set(pageParams.token, 'invalidToken'); - - const linkResponse = await request({ - url: linkWithLocale.toString(), - followRedirects: false, - }); - expect(linkResponse.status).toBe(200); - - const appId = linkResponse.headers['x-parse-page-param-appid']; - const locale = linkResponse.headers['x-parse-page-param-locale']; - const username = linkResponse.headers['x-parse-page-param-username']; - const publicServerUrl = linkResponse.headers['x-parse-page-param-publicserverurl']; - const invalidVerificationPagePath = pageResponse.calls.all()[0].args[0]; - expect(appId).toBeDefined(); - expect(locale).toBe(exampleLocale); - expect(username).toBeDefined(); - expect(publicServerUrl).toBeDefined(); - expect(invalidVerificationPagePath).toMatch( - new RegExp(`\/${exampleLocale}\/${pages.emailVerificationLinkExpired.defaultFile}`) - ); - - const formUrl = `${publicServerUrl}/apps/${appId}/resend_verification_email`; - const formResponse = await request({ - url: formUrl, - method: 'POST', - body: { - locale, - username, - }, - headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, - followRedirects: false, - }); - expect(formResponse.status).toEqual(303); - expect(formResponse.text).toContain( - `/${locale}/${pages.emailVerificationSendSuccess.defaultFile}` - ); - }); - - it_id('1d46d36a-e455-4ae7-8717-e0d286e95f02')(it)('localizes end-to-end for verify email: invalid verification link - link send fail', async () => { - await reconfigureServer(config); - const sendVerificationEmail = spyOn( - config.emailAdapter, - 'sendVerificationEmail' - ).and.callThrough(); - const user = new Parse.User(); - user.setUsername('exampleUsername'); - user.setPassword('examplePassword'); - user.set('email', 'mail@example.com'); - await user.signUp(); - await jasmine.timeout(); - - const link = sendVerificationEmail.calls.all()[0].args[0].link; - const linkWithLocale = new URL(link); - linkWithLocale.searchParams.append(pageParams.locale, exampleLocale); - linkWithLocale.searchParams.set(pageParams.token, 'invalidToken'); - - const linkResponse = await request({ - url: linkWithLocale.toString(), - followRedirects: false, - }); - expect(linkResponse.status).toBe(200); - - const appId = linkResponse.headers['x-parse-page-param-appid']; - const locale = linkResponse.headers['x-parse-page-param-locale']; - const username = linkResponse.headers['x-parse-page-param-username']; - const publicServerUrl = linkResponse.headers['x-parse-page-param-publicserverurl']; - await jasmine.timeout(); + it_id('2845c2ea-23ba-45d2-a33f-63181d419bca')(it)( + 'localizes end-to-end for verify email: success', + async () => { + await reconfigureServer(config); + const sendVerificationEmail = spyOn( + config.emailAdapter, + 'sendVerificationEmail' + ).and.callThrough(); + const user = new Parse.User(); + user.setUsername('exampleUsername'); + user.setPassword('examplePassword'); + user.set('email', 'mail@example.com'); + await user.signUp(); + await jasmine.timeout(); + + const link = sendVerificationEmail.calls.all()[0].args[0].link; + const linkWithLocale = new URL(link); + linkWithLocale.searchParams.append(pageParams.locale, exampleLocale); + + const linkResponse = await request({ + url: linkWithLocale.toString(), + followRedirects: false, + }); + expect(linkResponse.status).toBe(200); + + const pagePath = pageResponse.calls.all()[0].args[0]; + expect(pagePath).toMatch( + new RegExp(`\/${exampleLocale}\/${pages.emailVerificationSuccess.defaultFile}`) + ); + } + ); + + it_id('f2272b94-b4ac-474f-8e47-1ca74de136f5')(it)( + 'localizes end-to-end for verify email: invalid verification link - link send success', + async () => { + await reconfigureServer(config); + const sendVerificationEmail = spyOn( + config.emailAdapter, + 'sendVerificationEmail' + ).and.callThrough(); + const user = new Parse.User(); + user.setUsername('exampleUsername'); + user.setPassword('examplePassword'); + user.set('email', 'mail@example.com'); + await user.signUp(); + await jasmine.timeout(); + + const link = sendVerificationEmail.calls.all()[0].args[0].link; + const linkWithLocale = new URL(link); + linkWithLocale.searchParams.append(pageParams.locale, exampleLocale); + linkWithLocale.searchParams.set(pageParams.token, 'invalidToken'); + + const linkResponse = await request({ + url: linkWithLocale.toString(), + followRedirects: false, + }); + expect(linkResponse.status).toBe(200); + + const appId = linkResponse.headers['x-parse-page-param-appid']; + const locale = linkResponse.headers['x-parse-page-param-locale']; + const username = linkResponse.headers['x-parse-page-param-username']; + const publicServerUrl = linkResponse.headers['x-parse-page-param-publicserverurl']; + const invalidVerificationPagePath = pageResponse.calls.all()[0].args[0]; + expect(appId).toBeDefined(); + expect(locale).toBe(exampleLocale); + expect(username).toBeDefined(); + expect(publicServerUrl).toBeDefined(); + expect(invalidVerificationPagePath).toMatch( + new RegExp(`\/${exampleLocale}\/${pages.emailVerificationLinkExpired.defaultFile}`) + ); - const invalidVerificationPagePath = pageResponse.calls.all()[0].args[0]; - expect(appId).toBeDefined(); - expect(locale).toBe(exampleLocale); - expect(username).toBeDefined(); - expect(publicServerUrl).toBeDefined(); - expect(invalidVerificationPagePath).toMatch( - new RegExp(`\/${exampleLocale}\/${pages.emailVerificationLinkExpired.defaultFile}`) - ); + const formUrl = `${publicServerUrl}/apps/${appId}/resend_verification_email`; + const formResponse = await request({ + url: formUrl, + method: 'POST', + body: { + locale, + username, + }, + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + followRedirects: false, + }); + expect(formResponse.status).toEqual(303); + expect(formResponse.text).toContain( + `/${locale}/${pages.emailVerificationSendSuccess.defaultFile}` + ); + } + ); + + it_id('1d46d36a-e455-4ae7-8717-e0d286e95f02')(it)( + 'localizes end-to-end for verify email: invalid verification link - link send fail', + async () => { + await reconfigureServer(config); + const sendVerificationEmail = spyOn( + config.emailAdapter, + 'sendVerificationEmail' + ).and.callThrough(); + const user = new Parse.User(); + user.setUsername('exampleUsername'); + user.setPassword('examplePassword'); + user.set('email', 'mail@example.com'); + await user.signUp(); + await jasmine.timeout(); + + const link = sendVerificationEmail.calls.all()[0].args[0].link; + const linkWithLocale = new URL(link); + linkWithLocale.searchParams.append(pageParams.locale, exampleLocale); + linkWithLocale.searchParams.set(pageParams.token, 'invalidToken'); + + const linkResponse = await request({ + url: linkWithLocale.toString(), + followRedirects: false, + }); + expect(linkResponse.status).toBe(200); + + const appId = linkResponse.headers['x-parse-page-param-appid']; + const locale = linkResponse.headers['x-parse-page-param-locale']; + const username = linkResponse.headers['x-parse-page-param-username']; + const publicServerUrl = linkResponse.headers['x-parse-page-param-publicserverurl']; + await jasmine.timeout(); + + const invalidVerificationPagePath = pageResponse.calls.all()[0].args[0]; + expect(appId).toBeDefined(); + expect(locale).toBe(exampleLocale); + expect(username).toBeDefined(); + expect(publicServerUrl).toBeDefined(); + expect(invalidVerificationPagePath).toMatch( + new RegExp(`\/${exampleLocale}\/${pages.emailVerificationLinkExpired.defaultFile}`) + ); - spyOn(UserController.prototype, 'resendVerificationEmail').and.callFake(() => - Promise.reject('failed to resend verification email') - ); + spyOn(UserController.prototype, 'resendVerificationEmail').and.callFake(() => + Promise.reject('failed to resend verification email') + ); - const formUrl = `${publicServerUrl}/apps/${appId}/resend_verification_email`; - const formResponse = await request({ - url: formUrl, - method: 'POST', - body: { - locale, - username, - }, - headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, - followRedirects: false, - }); - expect(formResponse.status).toEqual(303); - expect(formResponse.text).toContain( - `/${locale}/${pages.emailVerificationSendFail.defaultFile}` - ); - }); + const formUrl = `${publicServerUrl}/apps/${appId}/resend_verification_email`; + const formResponse = await request({ + url: formUrl, + method: 'POST', + body: { + locale, + username, + }, + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + followRedirects: false, + }); + expect(formResponse.status).toEqual(303); + expect(formResponse.text).toContain( + `/${locale}/${pages.emailVerificationSendFail.defaultFile}` + ); + } + ); it('localizes end-to-end for resend verification email: invalid link', async () => { await reconfigureServer(config); @@ -1183,29 +1192,32 @@ describe('Pages Router', () => { ); }); - it_id('81c1c28e-5dfd-4ffb-a09b-283156c08483')(it)('email verification works with custom endpoint', async () => { - config.pages.pagesEndpoint = 'customEndpoint'; - await reconfigureServer(config); - const sendVerificationEmail = spyOn( - config.emailAdapter, - 'sendVerificationEmail' - ).and.callThrough(); - const user = new Parse.User(); - user.setUsername('exampleUsername'); - user.setPassword('examplePassword'); - user.set('email', 'mail@example.com'); - await user.signUp(); - await jasmine.timeout(); - - const link = sendVerificationEmail.calls.all()[0].args[0].link; - const linkResponse = await request({ - url: link, - followRedirects: false, - }); - expect(linkResponse.status).toBe(200); - const pagePath = pageResponse.calls.all()[0].args[0]; - expect(pagePath).toMatch(new RegExp(`\/${pages.emailVerificationSuccess.defaultFile}`)); - }); + it_id('81c1c28e-5dfd-4ffb-a09b-283156c08483')(it)( + 'email verification works with custom endpoint', + async () => { + config.pages.pagesEndpoint = 'customEndpoint'; + await reconfigureServer(config); + const sendVerificationEmail = spyOn( + config.emailAdapter, + 'sendVerificationEmail' + ).and.callThrough(); + const user = new Parse.User(); + user.setUsername('exampleUsername'); + user.setPassword('examplePassword'); + user.set('email', 'mail@example.com'); + await user.signUp(); + await jasmine.timeout(); + + const link = sendVerificationEmail.calls.all()[0].args[0].link; + const linkResponse = await request({ + url: link, + followRedirects: false, + }); + expect(linkResponse.status).toBe(200); + const pagePath = pageResponse.calls.all()[0].args[0]; + expect(pagePath).toMatch(new RegExp(`\/${pages.emailVerificationSuccess.defaultFile}`)); + } + ); }); }); }); diff --git a/spec/Parse.Push.spec.js b/spec/Parse.Push.spec.js index 6303496de1..61dbdba103 100644 --- a/spec/Parse.Push.spec.js +++ b/spec/Parse.Push.spec.js @@ -126,71 +126,80 @@ describe('Parse.Push', () => { expect(sendToInstallationSpy.calls.count()).toEqual(10); }); - it_id('2a58e3c7-b6f3-4261-a384-6c893b2ac3f3')(it)('should properly send push with lowercaseIncrement', async () => { - await setup(); - const pushStatusId = await Parse.Push.send({ - where: { - deviceType: 'ios', - }, - data: { - badge: 'increment', - alert: 'Hello world!', - }, - }); - await pushCompleted(pushStatusId); - }); + it_id('2a58e3c7-b6f3-4261-a384-6c893b2ac3f3')(it)( + 'should properly send push with lowercaseIncrement', + async () => { + await setup(); + const pushStatusId = await Parse.Push.send({ + where: { + deviceType: 'ios', + }, + data: { + badge: 'increment', + alert: 'Hello world!', + }, + }); + await pushCompleted(pushStatusId); + } + ); - it_id('e21780b6-2cdd-467e-8013-81030f3288e9')(it)('should not allow clients to query _PushStatus', async () => { - await setup(); - const pushStatusId = await Parse.Push.send({ - where: { - deviceType: 'ios', - }, - data: { - badge: 'increment', - alert: 'Hello world!', - }, - }); - await pushCompleted(pushStatusId); - try { - await request({ + it_id('e21780b6-2cdd-467e-8013-81030f3288e9')(it)( + 'should not allow clients to query _PushStatus', + async () => { + await setup(); + const pushStatusId = await Parse.Push.send({ + where: { + deviceType: 'ios', + }, + data: { + badge: 'increment', + alert: 'Hello world!', + }, + }); + await pushCompleted(pushStatusId); + try { + await request({ + url: 'http://localhost:8378/1/classes/_PushStatus', + json: true, + headers: { + 'X-Parse-Application-Id': 'test', + }, + }); + fail(); + } catch (response) { + expect(response.data.error).toEqual('unauthorized'); + } + } + ); + + it_id('924cf5f5-f684-4925-978a-e52c0c457366')(it)( + 'should allow master key to query _PushStatus', + async () => { + await setup(); + const pushStatusId = await Parse.Push.send({ + where: { + deviceType: 'ios', + }, + data: { + badge: 'increment', + alert: 'Hello world!', + }, + }); + await pushCompleted(pushStatusId); + const response = await request({ url: 'http://localhost:8378/1/classes/_PushStatus', json: true, headers: { 'X-Parse-Application-Id': 'test', + 'X-Parse-Master-Key': 'test', }, }); - fail(); - } catch (response) { - expect(response.data.error).toEqual('unauthorized'); + const body = response.data; + expect(body.results.length).toEqual(1); + expect(body.results[0].query).toEqual('{"deviceType":"ios"}'); + expect(body.results[0].payload).toEqual('{"badge":"increment","alert":"Hello world!"}'); } - }); - - it_id('924cf5f5-f684-4925-978a-e52c0c457366')(it)('should allow master key to query _PushStatus', async () => { - await setup(); - const pushStatusId = await Parse.Push.send({ - where: { - deviceType: 'ios', - }, - data: { - badge: 'increment', - alert: 'Hello world!', - }, - }); - await pushCompleted(pushStatusId); - const response = await request({ - url: 'http://localhost:8378/1/classes/_PushStatus', - json: true, - headers: { - 'X-Parse-Application-Id': 'test', - 'X-Parse-Master-Key': 'test', - }, - }); - const body = response.data; - expect(body.results.length).toEqual(1); - expect(body.results[0].query).toEqual('{"deviceType":"ios"}'); - expect(body.results[0].payload).toEqual('{"badge":"increment","alert":"Hello world!"}'); - }); + ); it('should throw error if missing push configuration', async () => { await reconfigureServer({ push: null }); diff --git a/spec/ParseAPI.spec.js b/spec/ParseAPI.spec.js index a178a1b863..178085f987 100644 --- a/spec/ParseAPI.spec.js +++ b/spec/ParseAPI.spec.js @@ -163,90 +163,96 @@ describe('miscellaneous', function () { expect(numCreated).toBe(1); }); - it_id('be1b9ac7-5e5f-4e91-b044-2bd8fb7622ad')(it)('ensure that if people already have duplicate users, they can still sign up new users', async done => { - try { - await Parse.User.logOut(); - } catch (e) { - /* ignore */ + it_id('be1b9ac7-5e5f-4e91-b044-2bd8fb7622ad')(it)( + 'ensure that if people already have duplicate users, they can still sign up new users', + async done => { + try { + await Parse.User.logOut(); + } catch (e) { + /* ignore */ + } + const config = Config.get('test'); + // Remove existing data to clear out unique index + TestUtils.destroyAllDataPermanently() + .then(() => config.database.adapter.performInitialization({ VolatileClassesSchemas: [] })) + .then(() => config.database.adapter.createClass('_User', userSchema)) + .then(() => + config.database.adapter + .createObject('_User', userSchema, { objectId: 'x', username: 'u' }) + .catch(fail) + ) + .then(() => + config.database.adapter + .createObject('_User', userSchema, { objectId: 'y', username: 'u' }) + .catch(fail) + ) + // Create a new server to try to recreate the unique indexes + .then(reconfigureServer) + .catch(error => { + expect(error.code).toEqual(Parse.Error.DUPLICATE_VALUE); + const user = new Parse.User(); + user.setPassword('asdf'); + user.setUsername('zxcv'); + return user.signUp().catch(fail); + }) + .then(() => { + const user = new Parse.User(); + user.setPassword('asdf'); + user.setUsername('u'); + return user.signUp(); + }) + .then(() => { + fail('should not have been able to sign up'); + done(); + }) + .catch(error => { + expect(error.code).toEqual(Parse.Error.USERNAME_TAKEN); + done(); + }); } - const config = Config.get('test'); - // Remove existing data to clear out unique index - TestUtils.destroyAllDataPermanently() - .then(() => config.database.adapter.performInitialization({ VolatileClassesSchemas: [] })) - .then(() => config.database.adapter.createClass('_User', userSchema)) - .then(() => - config.database.adapter - .createObject('_User', userSchema, { objectId: 'x', username: 'u' }) - .catch(fail) - ) - .then(() => - config.database.adapter - .createObject('_User', userSchema, { objectId: 'y', username: 'u' }) - .catch(fail) - ) - // Create a new server to try to recreate the unique indexes - .then(reconfigureServer) - .catch(error => { - expect(error.code).toEqual(Parse.Error.DUPLICATE_VALUE); - const user = new Parse.User(); - user.setPassword('asdf'); - user.setUsername('zxcv'); - return user.signUp().catch(fail); - }) - .then(() => { - const user = new Parse.User(); - user.setPassword('asdf'); - user.setUsername('u'); - return user.signUp(); - }) - .then(() => { - fail('should not have been able to sign up'); - done(); - }) - .catch(error => { - expect(error.code).toEqual(Parse.Error.USERNAME_TAKEN); - done(); - }); - }); - - it_id('d00f907e-41b9-40f6-8168-63e832199a8c')(it)('ensure that if people already have duplicate emails, they can still sign up new users', done => { - const config = Config.get('test'); - // Remove existing data to clear out unique index - TestUtils.destroyAllDataPermanently() - .then(() => config.database.adapter.performInitialization({ VolatileClassesSchemas: [] })) - .then(() => config.database.adapter.createClass('_User', userSchema)) - .then(() => - config.database.adapter.createObject('_User', userSchema, { - objectId: 'x', - email: 'a@b.c', + ); + + it_id('d00f907e-41b9-40f6-8168-63e832199a8c')(it)( + 'ensure that if people already have duplicate emails, they can still sign up new users', + done => { + const config = Config.get('test'); + // Remove existing data to clear out unique index + TestUtils.destroyAllDataPermanently() + .then(() => config.database.adapter.performInitialization({ VolatileClassesSchemas: [] })) + .then(() => config.database.adapter.createClass('_User', userSchema)) + .then(() => + config.database.adapter.createObject('_User', userSchema, { + objectId: 'x', + email: 'a@b.c', + }) + ) + .then(() => + config.database.adapter.createObject('_User', userSchema, { + objectId: 'y', + email: 'a@b.c', + }) + ) + .then(reconfigureServer) + .catch(() => { + const user = new Parse.User(); + user.setPassword('asdf'); + user.setUsername('qqq'); + user.setEmail('unique@unique.unique'); + return user.signUp().catch(fail); }) - ) - .then(() => - config.database.adapter.createObject('_User', userSchema, { - objectId: 'y', - email: 'a@b.c', + .then(() => { + const user = new Parse.User(); + user.setPassword('asdf'); + user.setUsername('www'); + user.setEmail('a@b.c'); + return user.signUp(); }) - ) - .then(reconfigureServer) - .catch(() => { - const user = new Parse.User(); - user.setPassword('asdf'); - user.setUsername('qqq'); - user.setEmail('unique@unique.unique'); - return user.signUp().catch(fail); - }) - .then(() => { - const user = new Parse.User(); - user.setPassword('asdf'); - user.setUsername('www'); - user.setEmail('a@b.c'); - return user.signUp(); - }) - .catch(error => { - expect(error.code).toEqual(Parse.Error.EMAIL_TAKEN); - done(); - }); - }); + .catch(error => { + expect(error.code).toEqual(Parse.Error.EMAIL_TAKEN); + done(); + }); + } + ); it('ensure that if you try to sign up a user with a unique username and email, but duplicates in some other field that has a uniqueness constraint, you get a regular duplicate value error', async done => { await reconfigureServer(); @@ -289,7 +295,9 @@ describe('miscellaneous', function () { }, fail); }); - it_id('33db6efe-7c02-496c-8595-0ef627a94103')(it)('increment with a user object', function (done) { + it_id('33db6efe-7c02-496c-8595-0ef627a94103')(it)('increment with a user object', function ( + done + ) { createTestUser() .then(user => { user.increment('foo'); @@ -951,155 +959,161 @@ describe('miscellaneous', function () { ); }); - it_id('e9e718a9-4465-4158-b13e-f146855a8892')(it)('return the updated fields on PUT', async () => { - const obj = new Parse.Object('GameScore'); - const pointer = new Parse.Object('Child'); - await pointer.save(); - obj.set( - 'point', - new Parse.GeoPoint({ - latitude: 37.4848, - longitude: -122.1483, - }) - ); - obj.set('array', ['obj1', 'obj2']); - obj.set('objects', { a: 'b' }); - obj.set('string', 'abc'); - obj.set('bool', true); - obj.set('number', 1); - obj.set('date', new Date()); - obj.set('pointer', pointer); - const headers = { - 'Content-Type': 'application/json', - 'X-Parse-Application-Id': 'test', - 'X-Parse-REST-API-Key': 'rest', - 'X-Parse-Installation-Id': 'yolo', - }; - const saveResponse = await request({ - method: 'POST', - headers: headers, - url: 'http://localhost:8378/1/classes/GameScore', - body: JSON.stringify({ - a: 'hello', - c: 1, - d: ['1'], - e: ['1'], - f: ['1', '2'], - ...obj.toJSON(), - }), - }); - expect(Object.keys(saveResponse.data).sort()).toEqual(['createdAt', 'objectId']); - obj.id = saveResponse.data.objectId; - const response = await request({ - method: 'PUT', - headers: headers, - url: 'http://localhost:8378/1/classes/GameScore/' + obj.id, - body: JSON.stringify({ - a: 'b', - c: { __op: 'Increment', amount: 2 }, - d: { __op: 'Add', objects: ['2'] }, - e: { __op: 'AddUnique', objects: ['1', '2'] }, - f: { __op: 'Remove', objects: ['2'] }, - selfThing: { - __type: 'Pointer', - className: 'GameScore', - objectId: obj.id, - }, - }), - }); - const body = response.data; - expect(Object.keys(body).sort()).toEqual(['c', 'd', 'e', 'f', 'updatedAt']); - expect(body.a).toBeUndefined(); - expect(body.c).toEqual(3); // 2+1 - expect(body.d.length).toBe(2); - expect(body.d.indexOf('1') > -1).toBe(true); - expect(body.d.indexOf('2') > -1).toBe(true); - expect(body.e.length).toBe(2); - expect(body.e.indexOf('1') > -1).toBe(true); - expect(body.e.indexOf('2') > -1).toBe(true); - expect(body.f.length).toBe(1); - expect(body.f.indexOf('1') > -1).toBe(true); - expect(body.selfThing).toBeUndefined(); - expect(body.updatedAt).not.toBeUndefined(); - }); - - it_id('ea358b59-03c0-45c9-abc7-1fdd67573029')(it)('should response should not change with triggers', async () => { - const obj = new Parse.Object('GameScore'); - const pointer = new Parse.Object('Child'); - Parse.Cloud.beforeSave('GameScore', request => { - return request.object; - }); - Parse.Cloud.afterSave('GameScore', request => { - return request.object; - }); - await pointer.save(); - obj.set( - 'point', - new Parse.GeoPoint({ - latitude: 37.4848, - longitude: -122.1483, - }) - ); - obj.set('array', ['obj1', 'obj2']); - obj.set('objects', { a: 'b' }); - obj.set('string', 'abc'); - obj.set('bool', true); - obj.set('number', 1); - obj.set('date', new Date()); - obj.set('pointer', pointer); - const headers = { - 'Content-Type': 'application/json', - 'X-Parse-Application-Id': 'test', - 'X-Parse-REST-API-Key': 'rest', - 'X-Parse-Installation-Id': 'yolo', - }; - const saveResponse = await request({ - method: 'POST', - headers: headers, - url: 'http://localhost:8378/1/classes/GameScore', - body: JSON.stringify({ - a: 'hello', - c: 1, - d: ['1'], - e: ['1'], - f: ['1', '2'], - ...obj.toJSON(), - }), - }); - expect(Object.keys(saveResponse.data).sort()).toEqual(['createdAt', 'objectId']); - obj.id = saveResponse.data.objectId; - const response = await request({ - method: 'PUT', - headers: headers, - url: 'http://localhost:8378/1/classes/GameScore/' + obj.id, - body: JSON.stringify({ - a: 'b', - c: { __op: 'Increment', amount: 2 }, - d: { __op: 'Add', objects: ['2'] }, - e: { __op: 'AddUnique', objects: ['1', '2'] }, - f: { __op: 'Remove', objects: ['2'] }, - selfThing: { - __type: 'Pointer', - className: 'GameScore', - objectId: obj.id, - }, - }), - }); - const body = response.data; - expect(Object.keys(body).sort()).toEqual(['c', 'd', 'e', 'f', 'updatedAt']); - expect(body.a).toBeUndefined(); - expect(body.c).toEqual(3); // 2+1 - expect(body.d.length).toBe(2); - expect(body.d.indexOf('1') > -1).toBe(true); - expect(body.d.indexOf('2') > -1).toBe(true); - expect(body.e.length).toBe(2); - expect(body.e.indexOf('1') > -1).toBe(true); - expect(body.e.indexOf('2') > -1).toBe(true); - expect(body.f.length).toBe(1); - expect(body.f.indexOf('1') > -1).toBe(true); - expect(body.selfThing).toBeUndefined(); - expect(body.updatedAt).not.toBeUndefined(); - }); + it_id('e9e718a9-4465-4158-b13e-f146855a8892')(it)( + 'return the updated fields on PUT', + async () => { + const obj = new Parse.Object('GameScore'); + const pointer = new Parse.Object('Child'); + await pointer.save(); + obj.set( + 'point', + new Parse.GeoPoint({ + latitude: 37.4848, + longitude: -122.1483, + }) + ); + obj.set('array', ['obj1', 'obj2']); + obj.set('objects', { a: 'b' }); + obj.set('string', 'abc'); + obj.set('bool', true); + obj.set('number', 1); + obj.set('date', new Date()); + obj.set('pointer', pointer); + const headers = { + 'Content-Type': 'application/json', + 'X-Parse-Application-Id': 'test', + 'X-Parse-REST-API-Key': 'rest', + 'X-Parse-Installation-Id': 'yolo', + }; + const saveResponse = await request({ + method: 'POST', + headers: headers, + url: 'http://localhost:8378/1/classes/GameScore', + body: JSON.stringify({ + a: 'hello', + c: 1, + d: ['1'], + e: ['1'], + f: ['1', '2'], + ...obj.toJSON(), + }), + }); + expect(Object.keys(saveResponse.data).sort()).toEqual(['createdAt', 'objectId']); + obj.id = saveResponse.data.objectId; + const response = await request({ + method: 'PUT', + headers: headers, + url: 'http://localhost:8378/1/classes/GameScore/' + obj.id, + body: JSON.stringify({ + a: 'b', + c: { __op: 'Increment', amount: 2 }, + d: { __op: 'Add', objects: ['2'] }, + e: { __op: 'AddUnique', objects: ['1', '2'] }, + f: { __op: 'Remove', objects: ['2'] }, + selfThing: { + __type: 'Pointer', + className: 'GameScore', + objectId: obj.id, + }, + }), + }); + const body = response.data; + expect(Object.keys(body).sort()).toEqual(['c', 'd', 'e', 'f', 'updatedAt']); + expect(body.a).toBeUndefined(); + expect(body.c).toEqual(3); // 2+1 + expect(body.d.length).toBe(2); + expect(body.d.indexOf('1') > -1).toBe(true); + expect(body.d.indexOf('2') > -1).toBe(true); + expect(body.e.length).toBe(2); + expect(body.e.indexOf('1') > -1).toBe(true); + expect(body.e.indexOf('2') > -1).toBe(true); + expect(body.f.length).toBe(1); + expect(body.f.indexOf('1') > -1).toBe(true); + expect(body.selfThing).toBeUndefined(); + expect(body.updatedAt).not.toBeUndefined(); + } + ); + + it_id('ea358b59-03c0-45c9-abc7-1fdd67573029')(it)( + 'should response should not change with triggers', + async () => { + const obj = new Parse.Object('GameScore'); + const pointer = new Parse.Object('Child'); + Parse.Cloud.beforeSave('GameScore', request => { + return request.object; + }); + Parse.Cloud.afterSave('GameScore', request => { + return request.object; + }); + await pointer.save(); + obj.set( + 'point', + new Parse.GeoPoint({ + latitude: 37.4848, + longitude: -122.1483, + }) + ); + obj.set('array', ['obj1', 'obj2']); + obj.set('objects', { a: 'b' }); + obj.set('string', 'abc'); + obj.set('bool', true); + obj.set('number', 1); + obj.set('date', new Date()); + obj.set('pointer', pointer); + const headers = { + 'Content-Type': 'application/json', + 'X-Parse-Application-Id': 'test', + 'X-Parse-REST-API-Key': 'rest', + 'X-Parse-Installation-Id': 'yolo', + }; + const saveResponse = await request({ + method: 'POST', + headers: headers, + url: 'http://localhost:8378/1/classes/GameScore', + body: JSON.stringify({ + a: 'hello', + c: 1, + d: ['1'], + e: ['1'], + f: ['1', '2'], + ...obj.toJSON(), + }), + }); + expect(Object.keys(saveResponse.data).sort()).toEqual(['createdAt', 'objectId']); + obj.id = saveResponse.data.objectId; + const response = await request({ + method: 'PUT', + headers: headers, + url: 'http://localhost:8378/1/classes/GameScore/' + obj.id, + body: JSON.stringify({ + a: 'b', + c: { __op: 'Increment', amount: 2 }, + d: { __op: 'Add', objects: ['2'] }, + e: { __op: 'AddUnique', objects: ['1', '2'] }, + f: { __op: 'Remove', objects: ['2'] }, + selfThing: { + __type: 'Pointer', + className: 'GameScore', + objectId: obj.id, + }, + }), + }); + const body = response.data; + expect(Object.keys(body).sort()).toEqual(['c', 'd', 'e', 'f', 'updatedAt']); + expect(body.a).toBeUndefined(); + expect(body.c).toEqual(3); // 2+1 + expect(body.d.length).toBe(2); + expect(body.d.indexOf('1') > -1).toBe(true); + expect(body.d.indexOf('2') > -1).toBe(true); + expect(body.e.length).toBe(2); + expect(body.e.indexOf('1') > -1).toBe(true); + expect(body.e.indexOf('2') > -1).toBe(true); + expect(body.f.length).toBe(1); + expect(body.f.indexOf('1') > -1).toBe(true); + expect(body.selfThing).toBeUndefined(); + expect(body.updatedAt).not.toBeUndefined(); + } + ); it('test cloud function error handling', done => { // Register a function which will fail @@ -1491,47 +1505,50 @@ describe('miscellaneous', function () { }); }); - it_id('b2cd9cf2-13fa-4acd-aaa9-6f81fc1858db')(it)('properly returns incremented values (#1554)', done => { - const headers = { - 'Content-Type': 'application/json', - 'X-Parse-Application-Id': 'test', - 'X-Parse-REST-API-Key': 'rest', - }; - const requestOptions = { - headers: headers, - url: 'http://localhost:8378/1/classes/AnObject', - json: true, - }; - const object = new Parse.Object('AnObject'); - - function runIncrement(amount) { - const options = Object.assign({}, requestOptions, { - body: { - key: { - __op: 'Increment', - amount: amount, + it_id('b2cd9cf2-13fa-4acd-aaa9-6f81fc1858db')(it)( + 'properly returns incremented values (#1554)', + done => { + const headers = { + 'Content-Type': 'application/json', + 'X-Parse-Application-Id': 'test', + 'X-Parse-REST-API-Key': 'rest', + }; + const requestOptions = { + headers: headers, + url: 'http://localhost:8378/1/classes/AnObject', + json: true, + }; + const object = new Parse.Object('AnObject'); + + function runIncrement(amount) { + const options = Object.assign({}, requestOptions, { + body: { + key: { + __op: 'Increment', + amount: amount, + }, }, - }, - url: 'http://localhost:8378/1/classes/AnObject/' + object.id, - method: 'PUT', - }); - return request(options).then(res => res.data); - } + url: 'http://localhost:8378/1/classes/AnObject/' + object.id, + method: 'PUT', + }); + return request(options).then(res => res.data); + } - object - .save() - .then(() => { - return runIncrement(1); - }) - .then(res => { - expect(res.key).toBe(1); - return runIncrement(-1); - }) - .then(res => { - expect(res.key).toBe(0); - done(); - }); - }); + object + .save() + .then(() => { + return runIncrement(1); + }) + .then(res => { + expect(res.key).toBe(1); + return runIncrement(-1); + }) + .then(res => { + expect(res.key).toBe(0); + done(); + }); + } + ); it('ignores _RevocableSession "header" send by JS SDK', done => { const object = new Parse.Object('AnObject'); diff --git a/spec/ParseConfigKey.spec.js b/spec/ParseConfigKey.spec.js index a8f62681d9..5eacb65faa 100644 --- a/spec/ParseConfigKey.spec.js +++ b/spec/ParseConfigKey.spec.js @@ -11,53 +11,57 @@ describe('Config Keys', () => { }); it('recognizes invalid keys in root', async () => { - await expectAsync(reconfigureServer({ - ...defaultConfiguration, - invalidKey: 1, - })).toBeResolved(); - const error = loggerErrorSpy.calls.all().reduce((s, call) => s += call.args[0], ''); + await expectAsync( + reconfigureServer({ + ...defaultConfiguration, + invalidKey: 1, + }) + ).toBeResolved(); + const error = loggerErrorSpy.calls.all().reduce((s, call) => (s += call.args[0]), ''); expect(error).toMatch(invalidKeyErrorMessage); }); it('recognizes invalid keys in pages.customUrls', async () => { - await expectAsync(reconfigureServer({ - ...defaultConfiguration, - pages: { - customUrls: { - invalidKey: 1, - EmailVerificationSendFail: 1, - } - } - })).toBeResolved(); - const error = loggerErrorSpy.calls.all().reduce((s, call) => s += call.args[0], ''); + await expectAsync( + reconfigureServer({ + ...defaultConfiguration, + pages: { + customUrls: { + invalidKey: 1, + EmailVerificationSendFail: 1, + }, + }, + }) + ).toBeResolved(); + const error = loggerErrorSpy.calls.all().reduce((s, call) => (s += call.args[0]), ''); expect(error).toMatch(invalidKeyErrorMessage); expect(error).toMatch(`invalidKey`); expect(error).toMatch(`EmailVerificationSendFail`); }); it('recognizes invalid keys in liveQueryServerOptions', async () => { - await expectAsync(reconfigureServer({ - ...defaultConfiguration, - liveQueryServerOptions: { - invalidKey: 1, - MasterKey: 1, - } - })).toBeResolved(); - const error = loggerErrorSpy.calls.all().reduce((s, call) => s += call.args[0], ''); + await expectAsync( + reconfigureServer({ + ...defaultConfiguration, + liveQueryServerOptions: { + invalidKey: 1, + MasterKey: 1, + }, + }) + ).toBeResolved(); + const error = loggerErrorSpy.calls.all().reduce((s, call) => (s += call.args[0]), ''); expect(error).toMatch(invalidKeyErrorMessage); expect(error).toMatch(`MasterKey`); }); it('recognizes invalid keys in rateLimit', async () => { - await expectAsync(reconfigureServer({ - ...defaultConfiguration, - rateLimit: [ - { invalidKey: 1 }, - { RequestPath: 1 }, - { RequestTimeWindow: 1 }, - ] - })).toBeRejected(); - const error = loggerErrorSpy.calls.all().reduce((s, call) => s += call.args[0], ''); + await expectAsync( + reconfigureServer({ + ...defaultConfiguration, + rateLimit: [{ invalidKey: 1 }, { RequestPath: 1 }, { RequestTimeWindow: 1 }], + }) + ).toBeRejected(); + const error = loggerErrorSpy.calls.all().reduce((s, call) => (s += call.args[0]), ''); expect(error).toMatch(invalidKeyErrorMessage); expect(error).toMatch('rateLimit\\[0\\]\\.invalidKey'); expect(error).toMatch('rateLimit\\[1\\]\\.RequestPath'); @@ -65,24 +69,32 @@ describe('Config Keys', () => { }); it('recognizes valid keys in default configuration', async () => { - await expectAsync(reconfigureServer({ - ...defaultConfiguration, - })).toBeResolved(); - expect(loggerErrorSpy.calls.all().reduce((s, call) => s += call.args[0], '')).not.toMatch(invalidKeyErrorMessage); + await expectAsync( + reconfigureServer({ + ...defaultConfiguration, + }) + ).toBeResolved(); + expect(loggerErrorSpy.calls.all().reduce((s, call) => (s += call.args[0]), '')).not.toMatch( + invalidKeyErrorMessage + ); }); it_only_db('mongo')('recognizes valid keys in databaseOptions (MongoDB)', async () => { - await expectAsync(reconfigureServer({ - databaseURI: 'mongodb://localhost:27017/parse', - filesAdapter: null, - databaseAdapter: null, - databaseOptions: { - retryWrites: true, - maxTimeMS: 1000, - maxStalenessSeconds: 10, - maxPoolSize: 10, - }, - })).toBeResolved(); - expect(loggerErrorSpy.calls.all().reduce((s, call) => s += call.args[0], '')).not.toMatch(invalidKeyErrorMessage); + await expectAsync( + reconfigureServer({ + databaseURI: 'mongodb://localhost:27017/parse', + filesAdapter: null, + databaseAdapter: null, + databaseOptions: { + retryWrites: true, + maxTimeMS: 1000, + maxStalenessSeconds: 10, + maxPoolSize: 10, + }, + }) + ).toBeResolved(); + expect(loggerErrorSpy.calls.all().reduce((s, call) => (s += call.args[0]), '')).not.toMatch( + invalidKeyErrorMessage + ); }); }); diff --git a/spec/ParseGeoPoint.spec.js b/spec/ParseGeoPoint.spec.js index f154f0048e..ac104c1a47 100644 --- a/spec/ParseGeoPoint.spec.js +++ b/spec/ParseGeoPoint.spec.js @@ -207,16 +207,19 @@ describe('Parse.GeoPoint testing', () => { done(); }); - it_id('05f1a454-56b1-4f2e-908e-408a9222cbae')(it)('geo max distance in km california', async () => { - await makeSomeGeoPoints(); - const sfo = new Parse.GeoPoint(37.6189722, -122.3748889); - const query = new Parse.Query(TestObject); - query.withinKilometers('location', sfo, 3700.0); - const results = await query.find(); - equal(results.length, 2); - equal(results[0].get('name'), 'San Francisco'); - equal(results[1].get('name'), 'Sacramento'); - }); + it_id('05f1a454-56b1-4f2e-908e-408a9222cbae')(it)( + 'geo max distance in km california', + async () => { + await makeSomeGeoPoints(); + const sfo = new Parse.GeoPoint(37.6189722, -122.3748889); + const query = new Parse.Query(TestObject); + query.withinKilometers('location', sfo, 3700.0); + const results = await query.find(); + equal(results.length, 2); + equal(results[0].get('name'), 'San Francisco'); + equal(results[1].get('name'), 'Sacramento'); + } + ); it('geo max distance in km bay area', async () => { await makeSomeGeoPoints(); @@ -246,16 +249,19 @@ describe('Parse.GeoPoint testing', () => { equal(results.length, 3); }); - it_id('9ee376ad-dd6c-4c17-ad28-c7899a4411f1')(it)('geo max distance in miles california', async () => { - await makeSomeGeoPoints(); - const sfo = new Parse.GeoPoint(37.6189722, -122.3748889); - const query = new Parse.Query(TestObject); - query.withinMiles('location', sfo, 2200.0); - const results = await query.find(); - equal(results.length, 2); - equal(results[0].get('name'), 'San Francisco'); - equal(results[1].get('name'), 'Sacramento'); - }); + it_id('9ee376ad-dd6c-4c17-ad28-c7899a4411f1')(it)( + 'geo max distance in miles california', + async () => { + await makeSomeGeoPoints(); + const sfo = new Parse.GeoPoint(37.6189722, -122.3748889); + const query = new Parse.Query(TestObject); + query.withinMiles('location', sfo, 2200.0); + const results = await query.find(); + equal(results.length, 2); + equal(results[0].get('name'), 'San Francisco'); + equal(results[1].get('name'), 'Sacramento'); + } + ); it('geo max distance in miles bay area', async () => { await makeSomeGeoPoints(); @@ -433,48 +439,51 @@ describe('Parse.GeoPoint testing', () => { }, done.fail); }); - it_id('0a248e11-3598-480a-9ab5-8a0b259258e4')(it)('supports withinPolygon Polygon object', done => { - const inbound = new Parse.GeoPoint(1.5, 1.5); - const onbound = new Parse.GeoPoint(10, 10); - const outbound = new Parse.GeoPoint(20, 20); - const obj1 = new Parse.Object('Polygon', { location: inbound }); - const obj2 = new Parse.Object('Polygon', { location: onbound }); - const obj3 = new Parse.Object('Polygon', { location: outbound }); - const polygon = { - __type: 'Polygon', - coordinates: [ - [0, 0], - [10, 0], - [10, 10], - [0, 10], - [0, 0], - ], - }; - Parse.Object.saveAll([obj1, obj2, obj3]) - .then(() => { - const where = { - location: { - $geoWithin: { - $polygon: polygon, + it_id('0a248e11-3598-480a-9ab5-8a0b259258e4')(it)( + 'supports withinPolygon Polygon object', + done => { + const inbound = new Parse.GeoPoint(1.5, 1.5); + const onbound = new Parse.GeoPoint(10, 10); + const outbound = new Parse.GeoPoint(20, 20); + const obj1 = new Parse.Object('Polygon', { location: inbound }); + const obj2 = new Parse.Object('Polygon', { location: onbound }); + const obj3 = new Parse.Object('Polygon', { location: outbound }); + const polygon = { + __type: 'Polygon', + coordinates: [ + [0, 0], + [10, 0], + [10, 10], + [0, 10], + [0, 0], + ], + }; + Parse.Object.saveAll([obj1, obj2, obj3]) + .then(() => { + const where = { + location: { + $geoWithin: { + $polygon: polygon, + }, }, - }, - }; - return request({ - method: 'POST', - url: Parse.serverURL + '/classes/Polygon', - body: { where, _method: 'GET' }, - headers: { - 'X-Parse-Application-Id': Parse.applicationId, - 'X-Parse-Javascript-Key': Parse.javaScriptKey, - 'Content-Type': 'application/json', - }, - }); - }) - .then(resp => { - expect(resp.data.results.length).toBe(2); - done(); - }, done.fail); - }); + }; + return request({ + method: 'POST', + url: Parse.serverURL + '/classes/Polygon', + body: { where, _method: 'GET' }, + headers: { + 'X-Parse-Application-Id': Parse.applicationId, + 'X-Parse-Javascript-Key': Parse.javaScriptKey, + 'Content-Type': 'application/json', + }, + }); + }) + .then(resp => { + expect(resp.data.results.length).toBe(2); + done(); + }, done.fail); + } + ); it('invalid Polygon object withinPolygon', done => { const point = new Parse.GeoPoint(1.5, 1.5); @@ -752,38 +761,44 @@ describe('Parse.GeoPoint testing', () => { equal(count, 1); }); - it_id('0b073d31-0d41-41e7-bd60-f636ffb759dc')(it)('withinKilometers complex supports count', async () => { - const inside = new Parse.GeoPoint(10, 10); - const middle = new Parse.GeoPoint(20, 20); - const outside = new Parse.GeoPoint(30, 30); - const obj1 = new Parse.Object('TestObject', { location: inside }); - const obj2 = new Parse.Object('TestObject', { location: middle }); - const obj3 = new Parse.Object('TestObject', { location: outside }); + it_id('0b073d31-0d41-41e7-bd60-f636ffb759dc')(it)( + 'withinKilometers complex supports count', + async () => { + const inside = new Parse.GeoPoint(10, 10); + const middle = new Parse.GeoPoint(20, 20); + const outside = new Parse.GeoPoint(30, 30); + const obj1 = new Parse.Object('TestObject', { location: inside }); + const obj2 = new Parse.Object('TestObject', { location: middle }); + const obj3 = new Parse.Object('TestObject', { location: outside }); - await Parse.Object.saveAll([obj1, obj2, obj3]); + await Parse.Object.saveAll([obj1, obj2, obj3]); - const q1 = new Parse.Query(TestObject).withinKilometers('location', inside, 5); - const q2 = new Parse.Query(TestObject).withinKilometers('location', middle, 5); - const query = Parse.Query.or(q1, q2); - const count = await query.count(); + const q1 = new Parse.Query(TestObject).withinKilometers('location', inside, 5); + const q2 = new Parse.Query(TestObject).withinKilometers('location', middle, 5); + const query = Parse.Query.or(q1, q2); + const count = await query.count(); - equal(count, 2); - }); + equal(count, 2); + } + ); - it_id('26c9a13d-3d71-452e-a91c-9a4589be021c')(it)('fails to fetch geopoints that are specifically not at (0,0)', async () => { - const tmp = new TestObject({ - location: new Parse.GeoPoint({ latitude: 0, longitude: 0 }), - }); - const tmp2 = new TestObject({ - location: new Parse.GeoPoint({ - latitude: 49.2577142, - longitude: -123.1941149, - }), - }); - await Parse.Object.saveAll([tmp, tmp2]); - const query = new Parse.Query(TestObject); - query.notEqualTo('location', new Parse.GeoPoint({ latitude: 0, longitude: 0 })); - const results = await query.find(); - expect(results.length).toEqual(1); - }); + it_id('26c9a13d-3d71-452e-a91c-9a4589be021c')(it)( + 'fails to fetch geopoints that are specifically not at (0,0)', + async () => { + const tmp = new TestObject({ + location: new Parse.GeoPoint({ latitude: 0, longitude: 0 }), + }); + const tmp2 = new TestObject({ + location: new Parse.GeoPoint({ + latitude: 49.2577142, + longitude: -123.1941149, + }), + }); + await Parse.Object.saveAll([tmp, tmp2]); + const query = new Parse.Query(TestObject); + query.notEqualTo('location', new Parse.GeoPoint({ latitude: 0, longitude: 0 })); + const results = await query.find(); + expect(results.length).toEqual(1); + } + ); }); diff --git a/spec/ParseGlobalConfig.spec.js b/spec/ParseGlobalConfig.spec.js index 0967a5a54a..d4971dfe3a 100644 --- a/spec/ParseGlobalConfig.spec.js +++ b/spec/ParseGlobalConfig.spec.js @@ -115,28 +115,28 @@ describe('a GlobalConfig', () => { }); it_only_db('mongo')('can addUnique', async () => { - await Parse.Config.save({ companies: { __op: 'AddUnique', objects: ['PA', 'RS', 'E'] } }); + await Parse.Config.save({ companies: { __op: 'AddUnique', objects: ['PA', 'RS', 'E'] } }); const config = await Parse.Config.get(); const companies = config.get('companies'); expect(companies).toEqual(['US', 'DK', 'PA', 'RS', 'E']); }); it_only_db('mongo')('can add to array', async () => { - await Parse.Config.save({ companies: { __op: 'Add', objects: ['PA'] } }); + await Parse.Config.save({ companies: { __op: 'Add', objects: ['PA'] } }); const config = await Parse.Config.get(); const companies = config.get('companies'); expect(companies).toEqual(['US', 'DK', 'PA']); }); it_only_db('mongo')('can remove from array', async () => { - await Parse.Config.save({ companies: { __op: 'Remove', objects: ['US'] } }); + await Parse.Config.save({ companies: { __op: 'Remove', objects: ['US'] } }); const config = await Parse.Config.get(); const companies = config.get('companies'); expect(companies).toEqual(['DK']); }); it('can increment', async () => { - await Parse.Config.save({ counter: { __op: 'Increment', amount: 49 } }); + await Parse.Config.save({ counter: { __op: 'Increment', amount: 49 } }); const config = await Parse.Config.get(); const counter = config.get('counter'); expect(counter).toEqual(69); diff --git a/spec/ParseGraphQLSchema.spec.js b/spec/ParseGraphQLSchema.spec.js index 0b3d9a9007..199c52756c 100644 --- a/spec/ParseGraphQLSchema.spec.js +++ b/spec/ParseGraphQLSchema.spec.js @@ -500,77 +500,83 @@ describe('ParseGraphQLSchema', () => { }); }); describe('alias', () => { - it_id('45282d26-f4c7-4d2d-a7b6-cd8741d5322f')(it)('Should be able to define alias for get and find query', async () => { - const parseGraphQLSchema = new ParseGraphQLSchema({ - databaseController, - parseGraphQLController, - log: defaultLogger, - appId, - }); - - await parseGraphQLSchema.parseGraphQLController.updateGraphQLConfig({ - classConfigs: [ - { - className: 'Data', - query: { - get: true, - getAlias: 'precious_data', - find: true, - findAlias: 'data_results', + it_id('45282d26-f4c7-4d2d-a7b6-cd8741d5322f')(it)( + 'Should be able to define alias for get and find query', + async () => { + const parseGraphQLSchema = new ParseGraphQLSchema({ + databaseController, + parseGraphQLController, + log: defaultLogger, + appId, + }); + + await parseGraphQLSchema.parseGraphQLController.updateGraphQLConfig({ + classConfigs: [ + { + className: 'Data', + query: { + get: true, + getAlias: 'precious_data', + find: true, + findAlias: 'data_results', + }, }, - }, - ], - }); + ], + }); - const data = new Parse.Object('Data'); + const data = new Parse.Object('Data'); - await data.save(); + await data.save(); - await parseGraphQLSchema.schemaCache.clear(); - await parseGraphQLSchema.load(); + await parseGraphQLSchema.schemaCache.clear(); + await parseGraphQLSchema.load(); - const queries1 = parseGraphQLSchema.graphQLQueries; + const queries1 = parseGraphQLSchema.graphQLQueries; - expect(Object.keys(queries1)).toContain('data_results'); - expect(Object.keys(queries1)).toContain('precious_data'); - }); + expect(Object.keys(queries1)).toContain('data_results'); + expect(Object.keys(queries1)).toContain('precious_data'); + } + ); - it_id('f04b46e3-a25d-401d-a315-3298cfee1df8')(it)('Should be able to define alias for mutation', async () => { - const parseGraphQLSchema = new ParseGraphQLSchema({ - databaseController, - parseGraphQLController, - log: defaultLogger, - appId, - }); + it_id('f04b46e3-a25d-401d-a315-3298cfee1df8')(it)( + 'Should be able to define alias for mutation', + async () => { + const parseGraphQLSchema = new ParseGraphQLSchema({ + databaseController, + parseGraphQLController, + log: defaultLogger, + appId, + }); - await parseGraphQLSchema.parseGraphQLController.updateGraphQLConfig({ - classConfigs: [ - { - className: 'Track', - mutation: { - create: true, - createAlias: 'addTrack', - update: true, - updateAlias: 'modifyTrack', - destroy: true, - destroyAlias: 'eraseTrack', + await parseGraphQLSchema.parseGraphQLController.updateGraphQLConfig({ + classConfigs: [ + { + className: 'Track', + mutation: { + create: true, + createAlias: 'addTrack', + update: true, + updateAlias: 'modifyTrack', + destroy: true, + destroyAlias: 'eraseTrack', + }, }, - }, - ], - }); + ], + }); - const data = new Parse.Object('Track'); + const data = new Parse.Object('Track'); - await data.save(); + await data.save(); - await parseGraphQLSchema.schemaCache.clear(); - await parseGraphQLSchema.load(); + await parseGraphQLSchema.schemaCache.clear(); + await parseGraphQLSchema.load(); - const mutations = parseGraphQLSchema.graphQLMutations; + const mutations = parseGraphQLSchema.graphQLMutations; - expect(Object.keys(mutations)).toContain('addTrack'); - expect(Object.keys(mutations)).toContain('modifyTrack'); - expect(Object.keys(mutations)).toContain('eraseTrack'); - }); + expect(Object.keys(mutations)).toContain('addTrack'); + expect(Object.keys(mutations)).toContain('modifyTrack'); + expect(Object.keys(mutations)).toContain('eraseTrack'); + } + ); }); }); diff --git a/spec/ParseGraphQLServer.spec.js b/spec/ParseGraphQLServer.spec.js index e1353d8db2..21c5951b7d 100644 --- a/spec/ParseGraphQLServer.spec.js +++ b/spec/ParseGraphQLServer.spec.js @@ -9,7 +9,8 @@ const { updateCLP } = require('./support/dev'); const pluralize = require('pluralize'); const { getMainDefinition } = require('@apollo/client/utilities'); -const createUploadLink = (...args) => import('apollo-upload-client/createUploadLink.mjs').then(({ default: fn }) => fn(...args)); +const createUploadLink = (...args) => + import('apollo-upload-client/createUploadLink.mjs').then(({ default: fn }) => fn(...args)); const { SubscriptionClient } = require('subscriptions-transport-ws'); const { WebSocketLink } = require('@apollo/client/link/ws'); const { mergeSchemas } = require('@graphql-tools/schema'); @@ -130,14 +131,17 @@ describe('ParseGraphQLServer', () => { set: () => {}, }; - it_id('0696675e-060f-414f-bc77-9d57f31807f5')(it)('should return schema and context with req\'s info, config and auth', async () => { - const options = await parseGraphQLServer._getGraphQLOptions(); - expect(options.schema).toEqual(parseGraphQLServer.parseGraphQLSchema.graphQLSchema); - const contextResponse = await options.context({ req, res }); - expect(contextResponse.info).toEqual(req.info); - expect(contextResponse.config).toEqual(req.config); - expect(contextResponse.auth).toEqual(req.auth); - }); + it_id('0696675e-060f-414f-bc77-9d57f31807f5')(it)( + "should return schema and context with req's info, config and auth", + async () => { + const options = await parseGraphQLServer._getGraphQLOptions(); + expect(options.schema).toEqual(parseGraphQLServer.parseGraphQLSchema.graphQLSchema); + const contextResponse = await options.context({ req, res }); + expect(contextResponse.info).toEqual(req.info); + expect(contextResponse.config).toEqual(req.config); + expect(contextResponse.auth).toEqual(req.auth); + } + ); it('should load GraphQL schema in every call', async () => { const originalLoad = parseGraphQLServer.parseGraphQLSchema.load; @@ -1395,618 +1399,518 @@ describe('ParseGraphQLServer', () => { await resetGraphQLCache(); }); - it_id('d6a23a2f-ca18-4b15-bc73-3e636f99e6bc')(it)('should only include types in the enabledForClasses list', async () => { - const schemaController = await parseServer.config.databaseController.loadSchema(); - await schemaController.addClassIfNotExists('SuperCar', { - foo: { type: 'String' }, - }); + it_id('d6a23a2f-ca18-4b15-bc73-3e636f99e6bc')(it)( + 'should only include types in the enabledForClasses list', + async () => { + const schemaController = await parseServer.config.databaseController.loadSchema(); + await schemaController.addClassIfNotExists('SuperCar', { + foo: { type: 'String' }, + }); - const graphQLConfig = { - enabledForClasses: ['SuperCar'], - }; - await parseGraphQLServer.setGraphQLConfig(graphQLConfig); - await resetGraphQLCache(); + const graphQLConfig = { + enabledForClasses: ['SuperCar'], + }; + await parseGraphQLServer.setGraphQLConfig(graphQLConfig); + await resetGraphQLCache(); - const { data } = await apolloClient.query({ - query: gql` - query UserType { - userType: __type(name: "User") { - fields { - name + const { data } = await apolloClient.query({ + query: gql` + query UserType { + userType: __type(name: "User") { + fields { + name + } } - } - superCarType: __type(name: "SuperCar") { - fields { - name + superCarType: __type(name: "SuperCar") { + fields { + name + } } } - } - `, - }); - expect(data.userType).toBeNull(); - expect(data.superCarType).toBeTruthy(); - }); - it_id('1db2aceb-d24e-4929-ba43-8dbb5d0395e1')(it)('should not include types in the disabledForClasses list', async () => { - const schemaController = await parseServer.config.databaseController.loadSchema(); - await schemaController.addClassIfNotExists('SuperCar', { - foo: { type: 'String' }, - }); + `, + }); + expect(data.userType).toBeNull(); + expect(data.superCarType).toBeTruthy(); + } + ); + it_id('1db2aceb-d24e-4929-ba43-8dbb5d0395e1')(it)( + 'should not include types in the disabledForClasses list', + async () => { + const schemaController = await parseServer.config.databaseController.loadSchema(); + await schemaController.addClassIfNotExists('SuperCar', { + foo: { type: 'String' }, + }); - const graphQLConfig = { - disabledForClasses: ['SuperCar'], - }; - await parseGraphQLServer.setGraphQLConfig(graphQLConfig); - await resetGraphQLCache(); + const graphQLConfig = { + disabledForClasses: ['SuperCar'], + }; + await parseGraphQLServer.setGraphQLConfig(graphQLConfig); + await resetGraphQLCache(); - const { data } = await apolloClient.query({ - query: gql` - query UserType { - userType: __type(name: "User") { - fields { - name + const { data } = await apolloClient.query({ + query: gql` + query UserType { + userType: __type(name: "User") { + fields { + name + } } - } - superCarType: __type(name: "SuperCar") { - fields { - name + superCarType: __type(name: "SuperCar") { + fields { + name + } } } - } - `, - }); - expect(data.superCarType).toBeNull(); - expect(data.userType).toBeTruthy(); - }); - it_id('85c2e02f-0239-4819-b66e-392e0125f6c5')(it)('should remove query operations when disabled', async () => { - const superCar = new Parse.Object('SuperCar'); - await superCar.save({ foo: 'bar' }); - const customer = new Parse.Object('Customer'); - await customer.save({ foo: 'bar' }); + `, + }); + expect(data.superCarType).toBeNull(); + expect(data.userType).toBeTruthy(); + } + ); + it_id('85c2e02f-0239-4819-b66e-392e0125f6c5')(it)( + 'should remove query operations when disabled', + async () => { + const superCar = new Parse.Object('SuperCar'); + await superCar.save({ foo: 'bar' }); + const customer = new Parse.Object('Customer'); + await customer.save({ foo: 'bar' }); - await expectAsync( - apolloClient.query({ - query: gql` - query GetSuperCar($id: ID!) { - superCar(id: $id) { - id + await expectAsync( + apolloClient.query({ + query: gql` + query GetSuperCar($id: ID!) { + superCar(id: $id) { + id + } } - } - `, - variables: { - id: superCar.id, - }, - }) - ).toBeResolved(); + `, + variables: { + id: superCar.id, + }, + }) + ).toBeResolved(); - await expectAsync( - apolloClient.query({ - query: gql` - query FindCustomer { - customers { - count + await expectAsync( + apolloClient.query({ + query: gql` + query FindCustomer { + customers { + count + } } - } - `, - }) - ).toBeResolved(); + `, + }) + ).toBeResolved(); - const graphQLConfig = { - classConfigs: [ - { - className: 'SuperCar', - query: { - get: false, - find: true, + const graphQLConfig = { + classConfigs: [ + { + className: 'SuperCar', + query: { + get: false, + find: true, + }, }, - }, - { - className: 'Customer', - query: { - get: true, - find: false, + { + className: 'Customer', + query: { + get: true, + find: false, + }, }, - }, - ], - }; - await parseGraphQLServer.setGraphQLConfig(graphQLConfig); - await resetGraphQLCache(); + ], + }; + await parseGraphQLServer.setGraphQLConfig(graphQLConfig); + await resetGraphQLCache(); - await expectAsync( - apolloClient.query({ - query: gql` - query GetSuperCar($id: ID!) { - superCar(id: $id) { - id + await expectAsync( + apolloClient.query({ + query: gql` + query GetSuperCar($id: ID!) { + superCar(id: $id) { + id + } } - } - `, - variables: { - id: superCar.id, - }, - }) - ).toBeRejected(); - await expectAsync( - apolloClient.query({ - query: gql` - query GetCustomer($id: ID!) { - customer(id: $id) { - id + `, + variables: { + id: superCar.id, + }, + }) + ).toBeRejected(); + await expectAsync( + apolloClient.query({ + query: gql` + query GetCustomer($id: ID!) { + customer(id: $id) { + id + } } - } - `, - variables: { - id: customer.id, - }, - }) - ).toBeResolved(); - await expectAsync( - apolloClient.query({ - query: gql` - query FindSuperCar { - superCars { - count + `, + variables: { + id: customer.id, + }, + }) + ).toBeResolved(); + await expectAsync( + apolloClient.query({ + query: gql` + query FindSuperCar { + superCars { + count + } } - } - `, - }) - ).toBeResolved(); - await expectAsync( - apolloClient.query({ - query: gql` - query FindCustomer { - customers { - count + `, + }) + ).toBeResolved(); + await expectAsync( + apolloClient.query({ + query: gql` + query FindCustomer { + customers { + count + } } - } - `, - }) - ).toBeRejected(); - }); + `, + }) + ).toBeRejected(); + } + ); - it_id('972161a6-8108-4e99-a1a5-71d0267d26c2')(it)('should remove mutation operations, create, update and delete, when disabled', async () => { - const superCar1 = new Parse.Object('SuperCar'); - await superCar1.save({ foo: 'bar' }); - const customer1 = new Parse.Object('Customer'); - await customer1.save({ foo: 'bar' }); + it_id('972161a6-8108-4e99-a1a5-71d0267d26c2')(it)( + 'should remove mutation operations, create, update and delete, when disabled', + async () => { + const superCar1 = new Parse.Object('SuperCar'); + await superCar1.save({ foo: 'bar' }); + const customer1 = new Parse.Object('Customer'); + await customer1.save({ foo: 'bar' }); - await expectAsync( - apolloClient.query({ + await expectAsync( + apolloClient.query({ + query: gql` + mutation UpdateSuperCar($id: ID!, $foo: String!) { + updateSuperCar(input: { id: $id, fields: { foo: $foo } }) { + clientMutationId + } + } + `, + variables: { + id: superCar1.id, + foo: 'lah', + }, + }) + ).toBeResolved(); + + await expectAsync( + apolloClient.query({ + query: gql` + mutation DeleteCustomer($id: ID!) { + deleteCustomer(input: { id: $id }) { + clientMutationId + } + } + `, + variables: { + id: customer1.id, + }, + }) + ).toBeResolved(); + + const { data: customerData } = await apolloClient.query({ query: gql` - mutation UpdateSuperCar($id: ID!, $foo: String!) { - updateSuperCar(input: { id: $id, fields: { foo: $foo } }) { - clientMutationId + mutation CreateCustomer($foo: String!) { + createCustomer(input: { fields: { foo: $foo } }) { + customer { + id + } } } `, variables: { - id: superCar1.id, - foo: 'lah', + foo: 'rah', }, - }) - ).toBeResolved(); + }); + expect(customerData.createCustomer.customer).toBeTruthy(); - await expectAsync( - apolloClient.query({ + // used later + const customer2Id = customerData.createCustomer.customer.id; + + await parseGraphQLServer.setGraphQLConfig({ + classConfigs: [ + { + className: 'SuperCar', + mutation: { + create: true, + update: false, + destroy: true, + }, + }, + { + className: 'Customer', + mutation: { + create: false, + update: true, + destroy: false, + }, + }, + ], + }); + await resetGraphQLCache(); + + const { data: superCarData } = await apolloClient.query({ query: gql` - mutation DeleteCustomer($id: ID!) { - deleteCustomer(input: { id: $id }) { - clientMutationId + mutation CreateSuperCar($foo: String!) { + createSuperCar(input: { fields: { foo: $foo } }) { + superCar { + id + } } } `, variables: { - id: customer1.id, + foo: 'mah', }, - }) - ).toBeResolved(); + }); + expect(superCarData.createSuperCar).toBeTruthy(); + const superCar3Id = superCarData.createSuperCar.superCar.id; - const { data: customerData } = await apolloClient.query({ - query: gql` - mutation CreateCustomer($foo: String!) { - createCustomer(input: { fields: { foo: $foo } }) { - customer { - id + await expectAsync( + apolloClient.query({ + query: gql` + mutation UpdateSupercar($id: ID!, $foo: String!) { + updateSuperCar(input: { id: $id, fields: { foo: $foo } }) { + clientMutationId + } } - } - } - `, - variables: { - foo: 'rah', - }, - }); - expect(customerData.createCustomer.customer).toBeTruthy(); - - // used later - const customer2Id = customerData.createCustomer.customer.id; - - await parseGraphQLServer.setGraphQLConfig({ - classConfigs: [ - { - className: 'SuperCar', - mutation: { - create: true, - update: false, - destroy: true, - }, - }, - { - className: 'Customer', - mutation: { - create: false, - update: true, - destroy: false, + `, + variables: { + id: superCar3Id, }, - }, - ], - }); - await resetGraphQLCache(); - - const { data: superCarData } = await apolloClient.query({ - query: gql` - mutation CreateSuperCar($foo: String!) { - createSuperCar(input: { fields: { foo: $foo } }) { - superCar { - id - } - } - } - `, - variables: { - foo: 'mah', - }, - }); - expect(superCarData.createSuperCar).toBeTruthy(); - const superCar3Id = superCarData.createSuperCar.superCar.id; - - await expectAsync( - apolloClient.query({ - query: gql` - mutation UpdateSupercar($id: ID!, $foo: String!) { - updateSuperCar(input: { id: $id, fields: { foo: $foo } }) { - clientMutationId - } - } - `, - variables: { - id: superCar3Id, - }, - }) - ).toBeRejected(); + }) + ).toBeRejected(); - await expectAsync( - apolloClient.query({ - query: gql` - mutation DeleteSuperCar($id: ID!) { - deleteSuperCar(input: { id: $id }) { - clientMutationId + await expectAsync( + apolloClient.query({ + query: gql` + mutation DeleteSuperCar($id: ID!) { + deleteSuperCar(input: { id: $id }) { + clientMutationId + } } - } - `, - variables: { - id: superCar3Id, - }, - }) - ).toBeResolved(); + `, + variables: { + id: superCar3Id, + }, + }) + ).toBeResolved(); - await expectAsync( - apolloClient.query({ - query: gql` - mutation CreateCustomer($foo: String!) { - createCustomer(input: { fields: { foo: $foo } }) { - customer { - id + await expectAsync( + apolloClient.query({ + query: gql` + mutation CreateCustomer($foo: String!) { + createCustomer(input: { fields: { foo: $foo } }) { + customer { + id + } } } - } - `, - variables: { - foo: 'rah', - }, - }) - ).toBeRejected(); - await expectAsync( - apolloClient.query({ - query: gql` - mutation UpdateCustomer($id: ID!, $foo: String!) { - updateCustomer(input: { id: $id, fields: { foo: $foo } }) { - clientMutationId + `, + variables: { + foo: 'rah', + }, + }) + ).toBeRejected(); + await expectAsync( + apolloClient.query({ + query: gql` + mutation UpdateCustomer($id: ID!, $foo: String!) { + updateCustomer(input: { id: $id, fields: { foo: $foo } }) { + clientMutationId + } } - } - `, - variables: { - id: customer2Id, - foo: 'tah', - }, - }) - ).toBeResolved(); - await expectAsync( - apolloClient.query({ - query: gql` - mutation DeleteCustomer($id: ID!, $foo: String!) { - deleteCustomer(input: { id: $id }) { - clientMutationId + `, + variables: { + id: customer2Id, + foo: 'tah', + }, + }) + ).toBeResolved(); + await expectAsync( + apolloClient.query({ + query: gql` + mutation DeleteCustomer($id: ID!, $foo: String!) { + deleteCustomer(input: { id: $id }) { + clientMutationId + } } - } - `, - variables: { - id: customer2Id, - }, - }) - ).toBeRejected(); - }); + `, + variables: { + id: customer2Id, + }, + }) + ).toBeRejected(); + } + ); - it_id('4af763b1-ff86-43c7-ba30-060a1c07e730')(it)('should only allow the supplied create and update fields for a class', async () => { - const schemaController = await parseServer.config.databaseController.loadSchema(); - await schemaController.addClassIfNotExists('SuperCar', { - engine: { type: 'String' }, - doors: { type: 'Number' }, - price: { type: 'String' }, - mileage: { type: 'Number' }, - }); + it_id('4af763b1-ff86-43c7-ba30-060a1c07e730')(it)( + 'should only allow the supplied create and update fields for a class', + async () => { + const schemaController = await parseServer.config.databaseController.loadSchema(); + await schemaController.addClassIfNotExists('SuperCar', { + engine: { type: 'String' }, + doors: { type: 'Number' }, + price: { type: 'String' }, + mileage: { type: 'Number' }, + }); - await parseGraphQLServer.setGraphQLConfig({ - classConfigs: [ - { - className: 'SuperCar', - type: { - inputFields: { - create: ['engine', 'doors', 'price'], - update: ['price', 'mileage'], + await parseGraphQLServer.setGraphQLConfig({ + classConfigs: [ + { + className: 'SuperCar', + type: { + inputFields: { + create: ['engine', 'doors', 'price'], + update: ['price', 'mileage'], + }, }, }, - }, - ], - }); + ], + }); - await resetGraphQLCache(); + await resetGraphQLCache(); - await expectAsync( - apolloClient.query({ - query: gql` - mutation InvalidCreateSuperCar { - createSuperCar(input: { fields: { engine: "diesel", mileage: 1000 } }) { - superCar { - id + await expectAsync( + apolloClient.query({ + query: gql` + mutation InvalidCreateSuperCar { + createSuperCar(input: { fields: { engine: "diesel", mileage: 1000 } }) { + superCar { + id + } } } - } - `, - }) - ).toBeRejected(); - const { id: superCarId } = ( - await apolloClient.query({ - query: gql` - mutation ValidCreateSuperCar { - createSuperCar( - input: { fields: { engine: "diesel", doors: 5, price: "£10000" } } - ) { - superCar { - id + `, + }) + ).toBeRejected(); + const { id: superCarId } = ( + await apolloClient.query({ + query: gql` + mutation ValidCreateSuperCar { + createSuperCar( + input: { fields: { engine: "diesel", doors: 5, price: "£10000" } } + ) { + superCar { + id + } } } - } - `, - }) - ).data.createSuperCar.superCar; - - expect(superCarId).toBeTruthy(); - - await expectAsync( - apolloClient.query({ - query: gql` - mutation InvalidUpdateSuperCar($id: ID!) { - updateSuperCar(input: { id: $id, fields: { engine: "petrol" } }) { - clientMutationId - } - } - `, - variables: { - id: superCarId, - }, - }) - ).toBeRejected(); - - const updatedSuperCar = ( - await apolloClient.query({ - query: gql` - mutation ValidUpdateSuperCar($id: ID!) { - updateSuperCar(input: { id: $id, fields: { mileage: 2000 } }) { - clientMutationId - } - } - `, - variables: { - id: superCarId, - }, - }) - ).data.updateSuperCar; - expect(updatedSuperCar).toBeTruthy(); - }); - - it_id('fc9237e9-3e63-4b55-9c1d-e6269f613a93')(it)('should handle required fields from the Parse class', async () => { - const schemaController = await parseServer.config.databaseController.loadSchema(); - await schemaController.addClassIfNotExists('SuperCar', { - engine: { type: 'String', required: true }, - doors: { type: 'Number', required: true }, - price: { type: 'String' }, - mileage: { type: 'Number' }, - }); + `, + }) + ).data.createSuperCar.superCar; - await resetGraphQLCache(); + expect(superCarId).toBeTruthy(); - const { - data: { __type }, - } = await apolloClient.query({ - query: gql` - query requiredFields { - __type(name: "CreateSuperCarFieldsInput") { - inputFields { - name - type { - kind + await expectAsync( + apolloClient.query({ + query: gql` + mutation InvalidUpdateSuperCar($id: ID!) { + updateSuperCar(input: { id: $id, fields: { engine: "petrol" } }) { + clientMutationId } } - } - } - `, - }); - expect(__type.inputFields.find(o => o.name === 'price').type.kind).toEqual('SCALAR'); - expect(__type.inputFields.find(o => o.name === 'engine').type.kind).toEqual('NON_NULL'); - expect(__type.inputFields.find(o => o.name === 'doors').type.kind).toEqual('NON_NULL'); + `, + variables: { + id: superCarId, + }, + }) + ).toBeRejected(); - const { - data: { __type: __type2 }, - } = await apolloClient.query({ - query: gql` - query requiredFields { - __type(name: "SuperCar") { - fields { - name - type { - kind + const updatedSuperCar = ( + await apolloClient.query({ + query: gql` + mutation ValidUpdateSuperCar($id: ID!) { + updateSuperCar(input: { id: $id, fields: { mileage: 2000 } }) { + clientMutationId } } - } - } - `, - }); - expect(__type2.fields.find(o => o.name === 'price').type.kind).toEqual('SCALAR'); - expect(__type2.fields.find(o => o.name === 'engine').type.kind).toEqual('NON_NULL'); - expect(__type2.fields.find(o => o.name === 'doors').type.kind).toEqual('NON_NULL'); - }); - - it_id('83b6895a-7dfd-4e3b-a5ce-acdb1fa39705')(it)('should only allow the supplied output fields for a class', async () => { - const schemaController = await parseServer.config.databaseController.loadSchema(); - - await schemaController.addClassIfNotExists('SuperCar', { - engine: { type: 'String' }, - doors: { type: 'Number' }, - price: { type: 'String' }, - mileage: { type: 'Number' }, - insuranceClaims: { type: 'Number' }, - }); - - const superCar = await new Parse.Object('SuperCar').save({ - engine: 'petrol', - doors: 3, - price: '£7500', - mileage: 0, - insuranceCertificate: 'private-file.pdf', - }); - - await parseGraphQLServer.setGraphQLConfig({ - classConfigs: [ - { - className: 'SuperCar', - type: { - outputFields: ['engine', 'doors', 'price', 'mileage'], + `, + variables: { + id: superCarId, }, - }, - ], - }); - - await resetGraphQLCache(); + }) + ).data.updateSuperCar; + expect(updatedSuperCar).toBeTruthy(); + } + ); - await expectAsync( - apolloClient.query({ - query: gql` - query GetSuperCar($id: ID!) { - superCar(id: $id) { - id - objectId - engine - doors - price - mileage - insuranceCertificate - } - } - `, - variables: { - id: superCar.id, - }, - }) - ).toBeRejected(); - let getSuperCar = ( - await apolloClient.query({ - query: gql` - query GetSuperCar($id: ID!) { - superCar(id: $id) { - id - objectId - engine - doors - price - mileage - } - } - `, - variables: { - id: superCar.id, - }, - }) - ).data.superCar; - expect(getSuperCar).toBeTruthy(); + it_id('fc9237e9-3e63-4b55-9c1d-e6269f613a93')(it)( + 'should handle required fields from the Parse class', + async () => { + const schemaController = await parseServer.config.databaseController.loadSchema(); + await schemaController.addClassIfNotExists('SuperCar', { + engine: { type: 'String', required: true }, + doors: { type: 'Number', required: true }, + price: { type: 'String' }, + mileage: { type: 'Number' }, + }); - await parseGraphQLServer.setGraphQLConfig({ - classConfigs: [ - { - className: 'SuperCar', - type: { - outputFields: [], - }, - }, - ], - }); + await resetGraphQLCache(); - await resetGraphQLCache(); - await expectAsync( - apolloClient.query({ + const { + data: { __type }, + } = await apolloClient.query({ query: gql` - query GetSuperCar($id: ID!) { - superCar(id: $id) { - engine + query requiredFields { + __type(name: "CreateSuperCarFieldsInput") { + inputFields { + name + type { + kind + } + } } } `, - variables: { - id: superCar.id, - }, - }) - ).toBeRejected(); - getSuperCar = ( - await apolloClient.query({ + }); + expect(__type.inputFields.find(o => o.name === 'price').type.kind).toEqual('SCALAR'); + expect(__type.inputFields.find(o => o.name === 'engine').type.kind).toEqual('NON_NULL'); + expect(__type.inputFields.find(o => o.name === 'doors').type.kind).toEqual('NON_NULL'); + + const { + data: { __type: __type2 }, + } = await apolloClient.query({ query: gql` - query GetSuperCar($id: ID!) { - superCar(id: $id) { - id - objectId + query requiredFields { + __type(name: "SuperCar") { + fields { + name + type { + kind + } + } } } `, - variables: { - id: superCar.id, - }, - }) - ).data.superCar; - expect(getSuperCar.objectId).toBe(superCar.id); - }); + }); + expect(__type2.fields.find(o => o.name === 'price').type.kind).toEqual('SCALAR'); + expect(__type2.fields.find(o => o.name === 'engine').type.kind).toEqual('NON_NULL'); + expect(__type2.fields.find(o => o.name === 'doors').type.kind).toEqual('NON_NULL'); + } + ); - it_id('67dfcf94-92fb-45a3-a012-3b22c81899ba')(it)('should only allow the supplied constraint fields for a class', async () => { - try { + it_id('83b6895a-7dfd-4e3b-a5ce-acdb1fa39705')(it)( + 'should only allow the supplied output fields for a class', + async () => { const schemaController = await parseServer.config.databaseController.loadSchema(); await schemaController.addClassIfNotExists('SuperCar', { - model: { type: 'String' }, engine: { type: 'String' }, doors: { type: 'Number' }, price: { type: 'String' }, mileage: { type: 'Number' }, - insuranceCertificate: { type: 'String' }, + insuranceClaims: { type: 'Number' }, }); - await new Parse.Object('SuperCar').save({ - model: 'McLaren', + const superCar = await new Parse.Object('SuperCar').save({ engine: 'petrol', doors: 3, price: '£7500', @@ -2019,7 +1923,7 @@ describe('ParseGraphQLServer', () => { { className: 'SuperCar', type: { - constraintFields: ['engine', 'doors', 'price'], + outputFields: ['engine', 'doors', 'price', 'mileage'], }, }, ], @@ -2030,196 +1934,323 @@ describe('ParseGraphQLServer', () => { await expectAsync( apolloClient.query({ query: gql` - query FindSuperCar { - superCars(where: { insuranceCertificate: { equalTo: "private-file.pdf" } }) { - count + query GetSuperCar($id: ID!) { + superCar(id: $id) { + id + objectId + engine + doors + price + mileage + insuranceCertificate } } `, + variables: { + id: superCar.id, + }, }) ).toBeRejected(); + let getSuperCar = ( + await apolloClient.query({ + query: gql` + query GetSuperCar($id: ID!) { + superCar(id: $id) { + id + objectId + engine + doors + price + mileage + } + } + `, + variables: { + id: superCar.id, + }, + }) + ).data.superCar; + expect(getSuperCar).toBeTruthy(); + await parseGraphQLServer.setGraphQLConfig({ + classConfigs: [ + { + className: 'SuperCar', + type: { + outputFields: [], + }, + }, + ], + }); + + await resetGraphQLCache(); await expectAsync( apolloClient.query({ query: gql` - query FindSuperCar { - superCars(where: { mileage: { equalTo: 0 } }) { - count + query GetSuperCar($id: ID!) { + superCar(id: $id) { + engine } } `, + variables: { + id: superCar.id, + }, }) ).toBeRejected(); - - await expectAsync( - apolloClient.query({ + getSuperCar = ( + await apolloClient.query({ query: gql` - query FindSuperCar { - superCars(where: { engine: { equalTo: "petrol" } }) { - count + query GetSuperCar($id: ID!) { + superCar(id: $id) { + id + objectId } } `, + variables: { + id: superCar.id, + }, }) - ).toBeResolved(); - } catch (e) { - handleError(e); + ).data.superCar; + expect(getSuperCar.objectId).toBe(superCar.id); } - }); - - it_id('a3bdbd5d-8779-42fe-91a1-7a7f90a6177b')(it)('should only allow the supplied sort fields for a class', async () => { - const schemaController = await parseServer.config.databaseController.loadSchema(); + ); - await schemaController.addClassIfNotExists('SuperCar', { - engine: { type: 'String' }, - doors: { type: 'Number' }, - price: { type: 'String' }, - mileage: { type: 'Number' }, - }); + it_id('67dfcf94-92fb-45a3-a012-3b22c81899ba')(it)( + 'should only allow the supplied constraint fields for a class', + async () => { + try { + const schemaController = await parseServer.config.databaseController.loadSchema(); + + await schemaController.addClassIfNotExists('SuperCar', { + model: { type: 'String' }, + engine: { type: 'String' }, + doors: { type: 'Number' }, + price: { type: 'String' }, + mileage: { type: 'Number' }, + insuranceCertificate: { type: 'String' }, + }); - await new Parse.Object('SuperCar').save({ - engine: 'petrol', - doors: 3, - price: '£7500', - mileage: 0, - }); + await new Parse.Object('SuperCar').save({ + model: 'McLaren', + engine: 'petrol', + doors: 3, + price: '£7500', + mileage: 0, + insuranceCertificate: 'private-file.pdf', + }); - await parseGraphQLServer.setGraphQLConfig({ - classConfigs: [ - { - className: 'SuperCar', - type: { - sortFields: [ - { - field: 'doors', - asc: true, - desc: true, - }, - { - field: 'price', - asc: true, - desc: true, - }, - { - field: 'mileage', - asc: true, - desc: false, + await parseGraphQLServer.setGraphQLConfig({ + classConfigs: [ + { + className: 'SuperCar', + type: { + constraintFields: ['engine', 'doors', 'price'], }, - ], + }, + ], + }); + + await resetGraphQLCache(); + + await expectAsync( + apolloClient.query({ + query: gql` + query FindSuperCar { + superCars(where: { insuranceCertificate: { equalTo: "private-file.pdf" } }) { + count + } + } + `, + }) + ).toBeRejected(); + + await expectAsync( + apolloClient.query({ + query: gql` + query FindSuperCar { + superCars(where: { mileage: { equalTo: 0 } }) { + count + } + } + `, + }) + ).toBeRejected(); + + await expectAsync( + apolloClient.query({ + query: gql` + query FindSuperCar { + superCars(where: { engine: { equalTo: "petrol" } }) { + count + } + } + `, + }) + ).toBeResolved(); + } catch (e) { + handleError(e); + } + } + ); + + it_id('a3bdbd5d-8779-42fe-91a1-7a7f90a6177b')(it)( + 'should only allow the supplied sort fields for a class', + async () => { + const schemaController = await parseServer.config.databaseController.loadSchema(); + + await schemaController.addClassIfNotExists('SuperCar', { + engine: { type: 'String' }, + doors: { type: 'Number' }, + price: { type: 'String' }, + mileage: { type: 'Number' }, + }); + + await new Parse.Object('SuperCar').save({ + engine: 'petrol', + doors: 3, + price: '£7500', + mileage: 0, + }); + + await parseGraphQLServer.setGraphQLConfig({ + classConfigs: [ + { + className: 'SuperCar', + type: { + sortFields: [ + { + field: 'doors', + asc: true, + desc: true, + }, + { + field: 'price', + asc: true, + desc: true, + }, + { + field: 'mileage', + asc: true, + desc: false, + }, + ], + }, }, - }, - ], - }); + ], + }); - await resetGraphQLCache(); + await resetGraphQLCache(); - await expectAsync( - apolloClient.query({ - query: gql` - query FindSuperCar { - superCars(order: [engine_ASC]) { - edges { - node { - id + await expectAsync( + apolloClient.query({ + query: gql` + query FindSuperCar { + superCars(order: [engine_ASC]) { + edges { + node { + id + } } } } - } - `, - }) - ).toBeRejected(); - await expectAsync( - apolloClient.query({ - query: gql` - query FindSuperCar { - superCars(order: [engine_DESC]) { - edges { - node { - id + `, + }) + ).toBeRejected(); + await expectAsync( + apolloClient.query({ + query: gql` + query FindSuperCar { + superCars(order: [engine_DESC]) { + edges { + node { + id + } } } } - } - `, - }) - ).toBeRejected(); - await expectAsync( - apolloClient.query({ - query: gql` - query FindSuperCar { - superCars(order: [mileage_DESC]) { - edges { - node { - id + `, + }) + ).toBeRejected(); + await expectAsync( + apolloClient.query({ + query: gql` + query FindSuperCar { + superCars(order: [mileage_DESC]) { + edges { + node { + id + } } } } - } - `, - }) - ).toBeRejected(); + `, + }) + ).toBeRejected(); - await expectAsync( - apolloClient.query({ - query: gql` - query FindSuperCar { - superCars(order: [mileage_ASC]) { - edges { - node { - id + await expectAsync( + apolloClient.query({ + query: gql` + query FindSuperCar { + superCars(order: [mileage_ASC]) { + edges { + node { + id + } } } } - } - `, - }) - ).toBeResolved(); - await expectAsync( - apolloClient.query({ - query: gql` - query FindSuperCar { - superCars(order: [doors_ASC]) { - edges { - node { - id + `, + }) + ).toBeResolved(); + await expectAsync( + apolloClient.query({ + query: gql` + query FindSuperCar { + superCars(order: [doors_ASC]) { + edges { + node { + id + } } } } - } - `, - }) - ).toBeResolved(); - await expectAsync( - apolloClient.query({ - query: gql` - query FindSuperCar { - superCars(order: [price_DESC]) { - edges { - node { - id + `, + }) + ).toBeResolved(); + await expectAsync( + apolloClient.query({ + query: gql` + query FindSuperCar { + superCars(order: [price_DESC]) { + edges { + node { + id + } } } } - } - `, - }) - ).toBeResolved(); - await expectAsync( - apolloClient.query({ - query: gql` - query FindSuperCar { - superCars(order: [price_ASC, doors_DESC]) { - edges { - node { - id + `, + }) + ).toBeResolved(); + await expectAsync( + apolloClient.query({ + query: gql` + query FindSuperCar { + superCars(order: [price_ASC, doors_DESC]) { + edges { + node { + id + } } } } - } - `, - }) - ).toBeResolved(); - }); + `, + }) + ).toBeResolved(); + } + ); }); describe('Relay Spec', () => { @@ -4921,50 +4952,53 @@ describe('ParseGraphQLServer', () => { ).toEqual(['someValue1', 'someValue2']); }); - it_id('accc59be-fd13-46c5-a103-ec63f2ad6670')(it)('should support full text search', async () => { - try { - const obj = new Parse.Object('FullTextSearchTest'); - obj.set('field1', 'Parse GraphQL Server'); - obj.set('field2', 'It rocks!'); - await obj.save(); + it_id('accc59be-fd13-46c5-a103-ec63f2ad6670')(it)( + 'should support full text search', + async () => { + try { + const obj = new Parse.Object('FullTextSearchTest'); + obj.set('field1', 'Parse GraphQL Server'); + obj.set('field2', 'It rocks!'); + await obj.save(); - await parseGraphQLServer.parseGraphQLSchema.schemaCache.clear(); + await parseGraphQLServer.parseGraphQLSchema.schemaCache.clear(); - const result = await apolloClient.query({ - query: gql` - query FullTextSearchTests($where: FullTextSearchTestWhereInput) { - fullTextSearchTests(where: $where) { - edges { - node { - objectId + const result = await apolloClient.query({ + query: gql` + query FullTextSearchTests($where: FullTextSearchTestWhereInput) { + fullTextSearchTests(where: $where) { + edges { + node { + objectId + } } } } - } - `, - context: { - headers: { - 'X-Parse-Master-Key': 'test', + `, + context: { + headers: { + 'X-Parse-Master-Key': 'test', + }, }, - }, - variables: { - where: { - field1: { - text: { - search: { - term: 'graphql', + variables: { + where: { + field1: { + text: { + search: { + term: 'graphql', + }, }, }, }, }, - }, - }); + }); - expect(result.data.fullTextSearchTests.edges[0].node.objectId).toEqual(obj.id); - } catch (e) { - handleError(e); + expect(result.data.fullTextSearchTests.edges[0].node.objectId).toEqual(obj.id); + } catch (e) { + handleError(e); + } } - }); + ); it('should support in query key', async () => { try { @@ -5032,194 +5066,200 @@ describe('ParseGraphQLServer', () => { } }); - it_id('0fd03d3c-a2c8-4fac-95cc-2391a3032ca2')(it)('should support order, skip and first arguments', async () => { - const promises = []; - for (let i = 0; i < 100; i++) { - const obj = new Parse.Object('SomeClass'); - obj.set('someField', `someValue${i < 10 ? '0' : ''}${i}`); - obj.set('numberField', i % 3); - promises.push(obj.save()); - } - await Promise.all(promises); - - await parseGraphQLServer.parseGraphQLSchema.schemaCache.clear(); - - const result = await apolloClient.query({ - query: gql` - query FindSomeObjects( - $where: SomeClassWhereInput - $order: [SomeClassOrder!] - $skip: Int - $first: Int - ) { - find: someClasses(where: $where, order: $order, skip: $skip, first: $first) { - edges { - node { - someField - } - } - } - } - `, - variables: { - where: { - someField: { - matchesRegex: '^someValue', - }, - }, - order: ['numberField_DESC', 'someField_ASC'], - skip: 4, - first: 2, - }, - }); - - expect(result.data.find.edges.map(obj => obj.node.someField)).toEqual([ - 'someValue14', - 'someValue17', - ]); - }); - - it_id('588a70c6-2932-4d3b-a838-a74c59d8cffb')(it)('should support pagination', async () => { - const numberArray = (first, last) => { - const array = []; - for (let i = first; i <= last; i++) { - array.push(i); + it_id('0fd03d3c-a2c8-4fac-95cc-2391a3032ca2')(it)( + 'should support order, skip and first arguments', + async () => { + const promises = []; + for (let i = 0; i < 100; i++) { + const obj = new Parse.Object('SomeClass'); + obj.set('someField', `someValue${i < 10 ? '0' : ''}${i}`); + obj.set('numberField', i % 3); + promises.push(obj.save()); } - return array; - }; - - const promises = []; - for (let i = 0; i < 100; i++) { - const obj = new Parse.Object('SomeClass'); - obj.set('numberField', i); - promises.push(obj.save()); - } - await Promise.all(promises); + await Promise.all(promises); - await parseGraphQLServer.parseGraphQLSchema.schemaCache.clear(); + await parseGraphQLServer.parseGraphQLSchema.schemaCache.clear(); - const find = async ({ skip, after, first, before, last } = {}) => { - return await apolloClient.query({ + const result = await apolloClient.query({ query: gql` query FindSomeObjects( + $where: SomeClassWhereInput $order: [SomeClassOrder!] $skip: Int - $after: String $first: Int - $before: String - $last: Int ) { - someClasses( - order: $order - skip: $skip - after: $after - first: $first - before: $before - last: $last - ) { + find: someClasses(where: $where, order: $order, skip: $skip, first: $first) { edges { - cursor node { - numberField + someField } } - count - pageInfo { - hasPreviousPage - startCursor - endCursor - hasNextPage - } } } `, variables: { - order: ['numberField_ASC'], - skip, - after, - first, - before, - last, + where: { + someField: { + matchesRegex: '^someValue', + }, + }, + order: ['numberField_DESC', 'someField_ASC'], + skip: 4, + first: 2, }, }); - }; - let result = await find(); - expect(result.data.someClasses.edges.map(edge => edge.node.numberField)).toEqual( - numberArray(0, 99) - ); - expect(result.data.someClasses.count).toEqual(100); - expect(result.data.someClasses.pageInfo.hasPreviousPage).toEqual(false); - expect(result.data.someClasses.pageInfo.startCursor).toEqual( - result.data.someClasses.edges[0].cursor - ); - expect(result.data.someClasses.pageInfo.endCursor).toEqual( - result.data.someClasses.edges[99].cursor - ); - expect(result.data.someClasses.pageInfo.hasNextPage).toEqual(false); + expect(result.data.find.edges.map(obj => obj.node.someField)).toEqual([ + 'someValue14', + 'someValue17', + ]); + } + ); - result = await find({ first: 10 }); - expect(result.data.someClasses.edges.map(edge => edge.node.numberField)).toEqual( - numberArray(0, 9) - ); - expect(result.data.someClasses.count).toEqual(100); - expect(result.data.someClasses.pageInfo.hasPreviousPage).toEqual(false); - expect(result.data.someClasses.pageInfo.startCursor).toEqual( - result.data.someClasses.edges[0].cursor - ); - expect(result.data.someClasses.pageInfo.endCursor).toEqual( - result.data.someClasses.edges[9].cursor - ); - expect(result.data.someClasses.pageInfo.hasNextPage).toEqual(true); + it_id('588a70c6-2932-4d3b-a838-a74c59d8cffb')(it)( + 'should support pagination', + async () => { + const numberArray = (first, last) => { + const array = []; + for (let i = first; i <= last; i++) { + array.push(i); + } + return array; + }; + + const promises = []; + for (let i = 0; i < 100; i++) { + const obj = new Parse.Object('SomeClass'); + obj.set('numberField', i); + promises.push(obj.save()); + } + await Promise.all(promises); - result = await find({ - first: 10, - after: result.data.someClasses.pageInfo.endCursor, - }); - expect(result.data.someClasses.edges.map(edge => edge.node.numberField)).toEqual( - numberArray(10, 19) - ); - expect(result.data.someClasses.count).toEqual(100); - expect(result.data.someClasses.pageInfo.hasPreviousPage).toEqual(true); - expect(result.data.someClasses.pageInfo.startCursor).toEqual( - result.data.someClasses.edges[0].cursor - ); - expect(result.data.someClasses.pageInfo.endCursor).toEqual( - result.data.someClasses.edges[9].cursor - ); - expect(result.data.someClasses.pageInfo.hasNextPage).toEqual(true); + await parseGraphQLServer.parseGraphQLSchema.schemaCache.clear(); - result = await find({ last: 10 }); - expect(result.data.someClasses.edges.map(edge => edge.node.numberField)).toEqual( - numberArray(90, 99) - ); - expect(result.data.someClasses.count).toEqual(100); - expect(result.data.someClasses.pageInfo.hasPreviousPage).toEqual(true); - expect(result.data.someClasses.pageInfo.startCursor).toEqual( - result.data.someClasses.edges[0].cursor - ); - expect(result.data.someClasses.pageInfo.endCursor).toEqual( - result.data.someClasses.edges[9].cursor - ); - expect(result.data.someClasses.pageInfo.hasNextPage).toEqual(false); + const find = async ({ skip, after, first, before, last } = {}) => { + return await apolloClient.query({ + query: gql` + query FindSomeObjects( + $order: [SomeClassOrder!] + $skip: Int + $after: String + $first: Int + $before: String + $last: Int + ) { + someClasses( + order: $order + skip: $skip + after: $after + first: $first + before: $before + last: $last + ) { + edges { + cursor + node { + numberField + } + } + count + pageInfo { + hasPreviousPage + startCursor + endCursor + hasNextPage + } + } + } + `, + variables: { + order: ['numberField_ASC'], + skip, + after, + first, + before, + last, + }, + }); + }; + + let result = await find(); + expect(result.data.someClasses.edges.map(edge => edge.node.numberField)).toEqual( + numberArray(0, 99) + ); + expect(result.data.someClasses.count).toEqual(100); + expect(result.data.someClasses.pageInfo.hasPreviousPage).toEqual(false); + expect(result.data.someClasses.pageInfo.startCursor).toEqual( + result.data.someClasses.edges[0].cursor + ); + expect(result.data.someClasses.pageInfo.endCursor).toEqual( + result.data.someClasses.edges[99].cursor + ); + expect(result.data.someClasses.pageInfo.hasNextPage).toEqual(false); + + result = await find({ first: 10 }); + expect(result.data.someClasses.edges.map(edge => edge.node.numberField)).toEqual( + numberArray(0, 9) + ); + expect(result.data.someClasses.count).toEqual(100); + expect(result.data.someClasses.pageInfo.hasPreviousPage).toEqual(false); + expect(result.data.someClasses.pageInfo.startCursor).toEqual( + result.data.someClasses.edges[0].cursor + ); + expect(result.data.someClasses.pageInfo.endCursor).toEqual( + result.data.someClasses.edges[9].cursor + ); + expect(result.data.someClasses.pageInfo.hasNextPage).toEqual(true); + + result = await find({ + first: 10, + after: result.data.someClasses.pageInfo.endCursor, + }); + expect(result.data.someClasses.edges.map(edge => edge.node.numberField)).toEqual( + numberArray(10, 19) + ); + expect(result.data.someClasses.count).toEqual(100); + expect(result.data.someClasses.pageInfo.hasPreviousPage).toEqual(true); + expect(result.data.someClasses.pageInfo.startCursor).toEqual( + result.data.someClasses.edges[0].cursor + ); + expect(result.data.someClasses.pageInfo.endCursor).toEqual( + result.data.someClasses.edges[9].cursor + ); + expect(result.data.someClasses.pageInfo.hasNextPage).toEqual(true); + + result = await find({ last: 10 }); + expect(result.data.someClasses.edges.map(edge => edge.node.numberField)).toEqual( + numberArray(90, 99) + ); + expect(result.data.someClasses.count).toEqual(100); + expect(result.data.someClasses.pageInfo.hasPreviousPage).toEqual(true); + expect(result.data.someClasses.pageInfo.startCursor).toEqual( + result.data.someClasses.edges[0].cursor + ); + expect(result.data.someClasses.pageInfo.endCursor).toEqual( + result.data.someClasses.edges[9].cursor + ); + expect(result.data.someClasses.pageInfo.hasNextPage).toEqual(false); - result = await find({ - last: 10, - before: result.data.someClasses.pageInfo.startCursor, - }); - expect(result.data.someClasses.edges.map(edge => edge.node.numberField)).toEqual( - numberArray(80, 89) - ); - expect(result.data.someClasses.count).toEqual(100); - expect(result.data.someClasses.pageInfo.hasPreviousPage).toEqual(true); - expect(result.data.someClasses.pageInfo.startCursor).toEqual( - result.data.someClasses.edges[0].cursor - ); - expect(result.data.someClasses.pageInfo.endCursor).toEqual( - result.data.someClasses.edges[9].cursor - ); - expect(result.data.someClasses.pageInfo.hasNextPage).toEqual(true); - }); + result = await find({ + last: 10, + before: result.data.someClasses.pageInfo.startCursor, + }); + expect(result.data.someClasses.edges.map(edge => edge.node.numberField)).toEqual( + numberArray(80, 89) + ); + expect(result.data.someClasses.count).toEqual(100); + expect(result.data.someClasses.pageInfo.hasPreviousPage).toEqual(true); + expect(result.data.someClasses.pageInfo.startCursor).toEqual( + result.data.someClasses.edges[0].cursor + ); + expect(result.data.someClasses.pageInfo.endCursor).toEqual( + result.data.someClasses.edges[9].cursor + ); + expect(result.data.someClasses.pageInfo.hasNextPage).toEqual(true); + } + ); it_id('4f6a5f20-9642-4cf0-b31d-e739672a9096')(it)('should support count', async () => { await prepareData(); @@ -5324,108 +5364,114 @@ describe('ParseGraphQLServer', () => { expect(result.data.find.count).toEqual(2); }); - it_id('942b57be-ca8a-4a5b-8104-2adef8743b1a')(it)('should respect max limit', async () => { - parseServer = await global.reconfigureServer({ - maxLimit: 10, - }); + it_id('942b57be-ca8a-4a5b-8104-2adef8743b1a')(it)( + 'should respect max limit', + async () => { + parseServer = await global.reconfigureServer({ + maxLimit: 10, + }); - const promises = []; - for (let i = 0; i < 100; i++) { - const obj = new Parse.Object('SomeClass'); - promises.push(obj.save()); - } - await Promise.all(promises); + const promises = []; + for (let i = 0; i < 100; i++) { + const obj = new Parse.Object('SomeClass'); + promises.push(obj.save()); + } + await Promise.all(promises); - await parseGraphQLServer.parseGraphQLSchema.schemaCache.clear(); + await parseGraphQLServer.parseGraphQLSchema.schemaCache.clear(); - const result = await apolloClient.query({ - query: gql` - query FindSomeObjects($limit: Int) { - find: someClasses(where: { id: { exists: true } }, first: $limit) { - edges { - node { - id + const result = await apolloClient.query({ + query: gql` + query FindSomeObjects($limit: Int) { + find: someClasses(where: { id: { exists: true } }, first: $limit) { + edges { + node { + id + } } + count } - count } - } - `, - variables: { - limit: 50, - }, - context: { - headers: { - 'X-Parse-Master-Key': 'test', + `, + variables: { + limit: 50, }, - }, - }); + context: { + headers: { + 'X-Parse-Master-Key': 'test', + }, + }, + }); - expect(result.data.find.edges.length).toEqual(10); - expect(result.data.find.count).toEqual(100); - }); + expect(result.data.find.edges.length).toEqual(10); + expect(result.data.find.count).toEqual(100); + } + ); - it_id('952634f0-0ad5-4a08-8da2-187c1bd9ee94')(it)('should support keys argument', async () => { - await prepareData(); + it_id('952634f0-0ad5-4a08-8da2-187c1bd9ee94')(it)( + 'should support keys argument', + async () => { + await prepareData(); - await parseGraphQLServer.parseGraphQLSchema.schemaCache.clear(); + await parseGraphQLServer.parseGraphQLSchema.schemaCache.clear(); - const result1 = await apolloClient.query({ - query: gql` - query FindSomeObject($where: GraphQLClassWhereInput) { - find: graphQLClasses(where: $where) { - edges { - node { - someField + const result1 = await apolloClient.query({ + query: gql` + query FindSomeObject($where: GraphQLClassWhereInput) { + find: graphQLClasses(where: $where) { + edges { + node { + someField + } } } } - } - `, - variables: { - where: { - id: { equalTo: object3.id }, + `, + variables: { + where: { + id: { equalTo: object3.id }, + }, }, - }, - context: { - headers: { - 'X-Parse-Session-Token': user1.getSessionToken(), + context: { + headers: { + 'X-Parse-Session-Token': user1.getSessionToken(), + }, }, - }, - }); + }); - const result2 = await apolloClient.query({ - query: gql` - query FindSomeObject($where: GraphQLClassWhereInput) { - find: graphQLClasses(where: $where) { - edges { - node { - someField - pointerToUser { - username + const result2 = await apolloClient.query({ + query: gql` + query FindSomeObject($where: GraphQLClassWhereInput) { + find: graphQLClasses(where: $where) { + edges { + node { + someField + pointerToUser { + username + } } } } } - } - `, - variables: { - where: { - id: { equalTo: object3.id }, + `, + variables: { + where: { + id: { equalTo: object3.id }, + }, }, - }, - context: { - headers: { - 'X-Parse-Session-Token': user1.getSessionToken(), + context: { + headers: { + 'X-Parse-Session-Token': user1.getSessionToken(), + }, }, - }, - }); + }); - expect(result1.data.find.edges[0].node.someField).toBeDefined(); - expect(result1.data.find.edges[0].node.pointerToUser).toBeUndefined(); - expect(result2.data.find.edges[0].node.someField).toBeDefined(); - expect(result2.data.find.edges[0].node.pointerToUser).toBeDefined(); - }); + expect(result1.data.find.edges[0].node.someField).toBeDefined(); + expect(result1.data.find.edges[0].node.pointerToUser).toBeUndefined(); + expect(result2.data.find.edges[0].node.someField).toBeDefined(); + expect(result2.data.find.edges[0].node.pointerToUser).toBeDefined(); + } + ); it('should support include argument', async () => { await prepareData(); @@ -5771,66 +5817,69 @@ describe('ParseGraphQLServer', () => { ).toEqual([object3.id, object1.id, object2.id]); }); - it_id('47a6adf3-1cb4-4d92-b74c-e480363f9cb5')(it)('should support including relation', async () => { - await prepareData(); + it_id('47a6adf3-1cb4-4d92-b74c-e480363f9cb5')(it)( + 'should support including relation', + async () => { + await prepareData(); - await parseGraphQLServer.parseGraphQLSchema.schemaCache.clear(); + await parseGraphQLServer.parseGraphQLSchema.schemaCache.clear(); - const result1 = await apolloClient.query({ - query: gql` - query FindRoles { - roles { - edges { - node { - name + const result1 = await apolloClient.query({ + query: gql` + query FindRoles { + roles { + edges { + node { + name + } } } } - } - `, - variables: {}, - context: { - headers: { - 'X-Parse-Session-Token': user1.getSessionToken(), + `, + variables: {}, + context: { + headers: { + 'X-Parse-Session-Token': user1.getSessionToken(), + }, }, - }, - }); + }); - const result2 = await apolloClient.query({ - query: gql` - query FindRoles { - roles { - edges { - node { - name - users { - edges { - node { - username + const result2 = await apolloClient.query({ + query: gql` + query FindRoles { + roles { + edges { + node { + name + users { + edges { + node { + username + } } } } } } } - } - `, - variables: {}, - context: { - headers: { - 'X-Parse-Session-Token': user1.getSessionToken(), + `, + variables: {}, + context: { + headers: { + 'X-Parse-Session-Token': user1.getSessionToken(), + }, }, - }, - }); + }); - expect(result1.data.roles.edges[0].node.name).toBeDefined(); - expect(result1.data.roles.edges[0].node.users).toBeUndefined(); - expect(result1.data.roles.edges[0].node.roles).toBeUndefined(); - expect(result2.data.roles.edges[0].node.name).toBeDefined(); - expect(result2.data.roles.edges[0].node.users).toBeDefined(); - expect(result2.data.roles.edges[0].node.users.edges[0].node.username).toBeDefined(); - expect(result2.data.roles.edges[0].node.roles).toBeUndefined(); - }); + expect(result1.data.roles.edges[0].node.name).toBeDefined(); + expect(result1.data.roles.edges[0].node.users).toBeUndefined(); + expect(result1.data.roles.edges[0].node.roles).toBeUndefined(); + expect(result2.data.roles.edges[0].node.name).toBeDefined(); + expect(result2.data.roles.edges[0].node.users).toBeDefined(); + expect(result2.data.roles.edges[0].node.users.edges[0].node.username).toBeDefined(); + expect(result2.data.roles.edges[0].node.roles).toBeUndefined(); + } + ); }); }); @@ -6676,161 +6725,164 @@ describe('ParseGraphQLServer', () => { }); }); - it_id('f722e98e-1fd7-45c5-ade3-5177e3d542e8')(it)('should unset fields when null used on update/create', async () => { - const customerSchema = new Parse.Schema('Customer'); - customerSchema.addString('aString'); - customerSchema.addBoolean('aBoolean'); - customerSchema.addDate('aDate'); - customerSchema.addArray('aArray'); - customerSchema.addGeoPoint('aGeoPoint'); - customerSchema.addPointer('aPointer', 'Customer'); - customerSchema.addObject('aObject'); - customerSchema.addPolygon('aPolygon'); - await customerSchema.save(); + it_id('f722e98e-1fd7-45c5-ade3-5177e3d542e8')(it)( + 'should unset fields when null used on update/create', + async () => { + const customerSchema = new Parse.Schema('Customer'); + customerSchema.addString('aString'); + customerSchema.addBoolean('aBoolean'); + customerSchema.addDate('aDate'); + customerSchema.addArray('aArray'); + customerSchema.addGeoPoint('aGeoPoint'); + customerSchema.addPointer('aPointer', 'Customer'); + customerSchema.addObject('aObject'); + customerSchema.addPolygon('aPolygon'); + await customerSchema.save(); - await parseGraphQLServer.parseGraphQLSchema.schemaCache.clear(); + await parseGraphQLServer.parseGraphQLSchema.schemaCache.clear(); - const cus = new Parse.Object('Customer'); - await cus.save({ aString: 'hello' }); - - const fields = { - aString: "i'm string", - aBoolean: true, - aDate: new Date().toISOString(), - aArray: ['hello', 1], - aGeoPoint: { latitude: 30, longitude: 30 }, - aPointer: { link: cus.id }, - aObject: { prop: { subprop: 1 }, prop2: 'test' }, - aPolygon: [ - { latitude: 30, longitude: 30 }, - { latitude: 31, longitude: 31 }, - { latitude: 32, longitude: 32 }, - { latitude: 30, longitude: 30 }, - ], - }; - const nullFields = Object.keys(fields).reduce((acc, k) => ({ ...acc, [k]: null }), {}); - const result = await apolloClient.mutate({ - mutation: gql` - mutation CreateCustomer($input: CreateCustomerInput!) { - createCustomer(input: $input) { - customer { - id - aString - aBoolean - aDate - aArray { - ... on Element { - value + const cus = new Parse.Object('Customer'); + await cus.save({ aString: 'hello' }); + + const fields = { + aString: "i'm string", + aBoolean: true, + aDate: new Date().toISOString(), + aArray: ['hello', 1], + aGeoPoint: { latitude: 30, longitude: 30 }, + aPointer: { link: cus.id }, + aObject: { prop: { subprop: 1 }, prop2: 'test' }, + aPolygon: [ + { latitude: 30, longitude: 30 }, + { latitude: 31, longitude: 31 }, + { latitude: 32, longitude: 32 }, + { latitude: 30, longitude: 30 }, + ], + }; + const nullFields = Object.keys(fields).reduce((acc, k) => ({ ...acc, [k]: null }), {}); + const result = await apolloClient.mutate({ + mutation: gql` + mutation CreateCustomer($input: CreateCustomerInput!) { + createCustomer(input: $input) { + customer { + id + aString + aBoolean + aDate + aArray { + ... on Element { + value + } } - } - aGeoPoint { - longitude - latitude - } - aPointer { - objectId - } - aObject - aPolygon { - longitude - latitude - } - } - } - } - `, - variables: { - input: { fields }, - }, - }); - const { - data: { - createCustomer: { - customer: { aPointer, aArray, id, ...otherFields }, - }, - }, - } = result; - expect(id).toBeDefined(); - delete otherFields.__typename; - delete otherFields.aGeoPoint.__typename; - otherFields.aPolygon.forEach(v => { - delete v.__typename; - }); - expect({ - ...otherFields, - aPointer: { link: aPointer.objectId }, - aArray: aArray.map(({ value }) => value), - }).toEqual(fields); - - const updated = await apolloClient.mutate({ - mutation: gql` - mutation UpdateCustomer($input: UpdateCustomerInput!) { - updateCustomer(input: $input) { - customer { - aString - aBoolean - aDate - aArray { - ... on Element { - value + aGeoPoint { + longitude + latitude + } + aPointer { + objectId + } + aObject + aPolygon { + longitude + latitude } - } - aGeoPoint { - longitude - latitude - } - aPointer { - objectId - } - aObject - aPolygon { - longitude - latitude } } } - } - `, - variables: { - input: { fields: nullFields, id }, - }, - }); - const { - data: { - updateCustomer: { customer }, - }, - } = updated; - delete customer.__typename; - expect(Object.keys(customer).length).toEqual(8); - Object.keys(customer).forEach(k => { - expect(customer[k]).toBeNull(); - }); - try { - const queryResult = await apolloClient.query({ - query: gql` - query getEmptyCustomer($where: CustomerWhereInput!) { - customers(where: $where) { - edges { - node { - id + `, + variables: { + input: { fields }, + }, + }); + const { + data: { + createCustomer: { + customer: { aPointer, aArray, id, ...otherFields }, + }, + }, + } = result; + expect(id).toBeDefined(); + delete otherFields.__typename; + delete otherFields.aGeoPoint.__typename; + otherFields.aPolygon.forEach(v => { + delete v.__typename; + }); + expect({ + ...otherFields, + aPointer: { link: aPointer.objectId }, + aArray: aArray.map(({ value }) => value), + }).toEqual(fields); + + const updated = await apolloClient.mutate({ + mutation: gql` + mutation UpdateCustomer($input: UpdateCustomerInput!) { + updateCustomer(input: $input) { + customer { + aString + aBoolean + aDate + aArray { + ... on Element { + value + } + } + aGeoPoint { + longitude + latitude + } + aPointer { + objectId + } + aObject + aPolygon { + longitude + latitude } } } } `, variables: { - where: Object.keys(fields).reduce( - (acc, k) => ({ ...acc, [k]: { exists: false } }), - {} - ), + input: { fields: nullFields, id }, + }, + }); + const { + data: { + updateCustomer: { customer }, }, + } = updated; + delete customer.__typename; + expect(Object.keys(customer).length).toEqual(8); + Object.keys(customer).forEach(k => { + expect(customer[k]).toBeNull(); }); + try { + const queryResult = await apolloClient.query({ + query: gql` + query getEmptyCustomer($where: CustomerWhereInput!) { + customers(where: $where) { + edges { + node { + id + } + } + } + } + `, + variables: { + where: Object.keys(fields).reduce( + (acc, k) => ({ ...acc, [k]: { exists: false } }), + {} + ), + }, + }); - expect(queryResult.data.customers.edges.length).toEqual(1); - } catch (e) { - console.error(JSON.stringify(e)); + expect(queryResult.data.customers.edges.length).toEqual(1); + } catch (e) { + console.error(JSON.stringify(e)); + } } - }); + ); }); describe('Files Mutations', () => { @@ -9074,232 +9126,235 @@ describe('ParseGraphQLServer', () => { expect(result2.companies.edges[0].node.objectId).toEqual(company1.id); }); - it_id('f4312f2c-90bb-4583-b033-02078ae0ce84')(it)('should support relational where query', async () => { - const president = new Parse.Object('President'); - president.set('name', 'James'); - await president.save(); + it_id('f4312f2c-90bb-4583-b033-02078ae0ce84')(it)( + 'should support relational where query', + async () => { + const president = new Parse.Object('President'); + president.set('name', 'James'); + await president.save(); - const employee = new Parse.Object('Employee'); - employee.set('name', 'John'); - await employee.save(); + const employee = new Parse.Object('Employee'); + employee.set('name', 'John'); + await employee.save(); - const company1 = new Parse.Object('Company'); - company1.set('name', 'imACompany1'); - await company1.save(); + const company1 = new Parse.Object('Company'); + company1.set('name', 'imACompany1'); + await company1.save(); - const company2 = new Parse.Object('Company'); - company2.set('name', 'imACompany2'); - company2.relation('employees').add([employee]); - await company2.save(); + const company2 = new Parse.Object('Company'); + company2.set('name', 'imACompany2'); + company2.relation('employees').add([employee]); + await company2.save(); - const country = new Parse.Object('Country'); - country.set('name', 'imACountry'); - country.relation('companies').add([company1, company2]); - await country.save(); + const country = new Parse.Object('Country'); + country.set('name', 'imACountry'); + country.relation('companies').add([company1, company2]); + await country.save(); - const country2 = new Parse.Object('Country'); - country2.set('name', 'imACountry2'); - country2.relation('companies').add([company1]); - await country2.save(); + const country2 = new Parse.Object('Country'); + country2.set('name', 'imACountry2'); + country2.relation('companies').add([company1]); + await country2.save(); - const country3 = new Parse.Object('Country'); - country3.set('name', 'imACountry3'); - country3.set('president', president); - await country3.save(); + const country3 = new Parse.Object('Country'); + country3.set('name', 'imACountry3'); + country3.set('president', president); + await country3.save(); - await parseGraphQLServer.parseGraphQLSchema.schemaCache.clear(); + await parseGraphQLServer.parseGraphQLSchema.schemaCache.clear(); - let { - data: { - countries: { edges: result }, - }, - } = await apolloClient.query({ - query: gql` - query findCountry($where: CountryWhereInput) { - countries(where: $where) { - edges { - node { - id - objectId - companies { - edges { - node { - id - objectId - name + let { + data: { + countries: { edges: result }, + }, + } = await apolloClient.query({ + query: gql` + query findCountry($where: CountryWhereInput) { + countries(where: $where) { + edges { + node { + id + objectId + companies { + edges { + node { + id + objectId + name + } } } } } } } - } - `, - variables: { - where: { - companies: { - have: { - employees: { have: { name: { equalTo: 'John' } } }, + `, + variables: { + where: { + companies: { + have: { + employees: { have: { name: { equalTo: 'John' } } }, + }, }, }, }, - }, - }); - expect(result.length).toEqual(1); - result = result[0].node; - expect(result.objectId).toEqual(country.id); - expect(result.companies.edges.length).toEqual(2); + }); + expect(result.length).toEqual(1); + result = result[0].node; + expect(result.objectId).toEqual(country.id); + expect(result.companies.edges.length).toEqual(2); - const { - data: { - countries: { edges: result2 }, - }, - } = await apolloClient.query({ - query: gql` - query findCountry($where: CountryWhereInput) { - countries(where: $where) { - edges { - node { - id - objectId - companies { - edges { - node { - id - objectId - name + const { + data: { + countries: { edges: result2 }, + }, + } = await apolloClient.query({ + query: gql` + query findCountry($where: CountryWhereInput) { + countries(where: $where) { + edges { + node { + id + objectId + companies { + edges { + node { + id + objectId + name + } } } } } } } - } - `, - variables: { - where: { - companies: { - have: { - OR: [ - { name: { equalTo: 'imACompany1' } }, - { name: { equalTo: 'imACompany2' } }, - ], + `, + variables: { + where: { + companies: { + have: { + OR: [ + { name: { equalTo: 'imACompany1' } }, + { name: { equalTo: 'imACompany2' } }, + ], + }, }, }, }, - }, - }); - expect(result2.length).toEqual(2); + }); + expect(result2.length).toEqual(2); - const { - data: { - countries: { edges: result3 }, - }, - } = await apolloClient.query({ - query: gql` - query findCountry($where: CountryWhereInput) { - countries(where: $where) { - edges { - node { - id - name + const { + data: { + countries: { edges: result3 }, + }, + } = await apolloClient.query({ + query: gql` + query findCountry($where: CountryWhereInput) { + countries(where: $where) { + edges { + node { + id + name + } } } } - } - `, - variables: { - where: { - companies: { exists: false }, + `, + variables: { + where: { + companies: { exists: false }, + }, }, - }, - }); - expect(result3.length).toEqual(1); - expect(result3[0].node.name).toEqual('imACountry3'); + }); + expect(result3.length).toEqual(1); + expect(result3[0].node.name).toEqual('imACountry3'); - const { - data: { - countries: { edges: result4 }, - }, - } = await apolloClient.query({ - query: gql` - query findCountry($where: CountryWhereInput) { - countries(where: $where) { - edges { - node { - id - name + const { + data: { + countries: { edges: result4 }, + }, + } = await apolloClient.query({ + query: gql` + query findCountry($where: CountryWhereInput) { + countries(where: $where) { + edges { + node { + id + name + } } } } - } - `, - variables: { - where: { - president: { exists: false }, + `, + variables: { + where: { + president: { exists: false }, + }, }, - }, - }); - expect(result4.length).toEqual(2); - const { - data: { - countries: { edges: result5 }, - }, - } = await apolloClient.query({ - query: gql` - query findCountry($where: CountryWhereInput) { - countries(where: $where) { - edges { - node { - id - name + }); + expect(result4.length).toEqual(2); + const { + data: { + countries: { edges: result5 }, + }, + } = await apolloClient.query({ + query: gql` + query findCountry($where: CountryWhereInput) { + countries(where: $where) { + edges { + node { + id + name + } } } } - } - `, - variables: { - where: { - president: { exists: true }, + `, + variables: { + where: { + president: { exists: true }, + }, }, - }, - }); - expect(result5.length).toEqual(1); - const { - data: { - countries: { edges: result6 }, - }, - } = await apolloClient.query({ - query: gql` - query findCountry($where: CountryWhereInput) { - countries(where: $where) { - edges { - node { - id - objectId - name + }); + expect(result5.length).toEqual(1); + const { + data: { + countries: { edges: result6 }, + }, + } = await apolloClient.query({ + query: gql` + query findCountry($where: CountryWhereInput) { + countries(where: $where) { + edges { + node { + id + objectId + name + } } } } - } - `, - variables: { - where: { - companies: { - haveNot: { - OR: [ - { name: { equalTo: 'imACompany1' } }, - { name: { equalTo: 'imACompany2' } }, - ], + `, + variables: { + where: { + companies: { + haveNot: { + OR: [ + { name: { equalTo: 'imACompany1' } }, + { name: { equalTo: 'imACompany2' } }, + ], + }, }, }, }, - }, - }); - expect(result6.length).toEqual(1); - expect(result6.length).toEqual(1); - expect(result6[0].node.name).toEqual('imACountry3'); - }); + }); + expect(result6.length).toEqual(1); + expect(result6.length).toEqual(1); + expect(result6[0].node.name).toEqual('imACountry3'); + } + ); it('should support files', async () => { try { diff --git a/spec/ParseHooks.spec.js b/spec/ParseHooks.spec.js index 57205b1685..ca99805a27 100644 --- a/spec/ParseHooks.spec.js +++ b/spec/ParseHooks.spec.js @@ -188,76 +188,82 @@ describe('Hooks', () => { }); }); - it_id('f7ad092f-81dc-4729-afd1-3b02db2f0948')(it)('should fail trying to create two times the same function', done => { - Parse.Hooks.createFunction('my_new_function', 'http://url.com') - .then(() => jasmine.timeout()) - .then( - () => { - return Parse.Hooks.createFunction('my_new_function', 'http://url.com'); - }, - () => { - fail('should create a new function'); - } - ) - .then( - () => { - fail('should not be able to create the same function'); - }, - err => { - expect(err).not.toBe(undefined); - expect(err).not.toBe(null); - if (err) { - expect(err.code).toBe(143); - expect(err.message).toBe('function name: my_new_function already exists'); + it_id('f7ad092f-81dc-4729-afd1-3b02db2f0948')(it)( + 'should fail trying to create two times the same function', + done => { + Parse.Hooks.createFunction('my_new_function', 'http://url.com') + .then(() => jasmine.timeout()) + .then( + () => { + return Parse.Hooks.createFunction('my_new_function', 'http://url.com'); + }, + () => { + fail('should create a new function'); } - return Parse.Hooks.removeFunction('my_new_function'); - } - ) - .then( - () => { - done(); - }, - err => { - jfail(err); - done(); - } - ); - }); + ) + .then( + () => { + fail('should not be able to create the same function'); + }, + err => { + expect(err).not.toBe(undefined); + expect(err).not.toBe(null); + if (err) { + expect(err.code).toBe(143); + expect(err.message).toBe('function name: my_new_function already exists'); + } + return Parse.Hooks.removeFunction('my_new_function'); + } + ) + .then( + () => { + done(); + }, + err => { + jfail(err); + done(); + } + ); + } + ); - it_id('4db8c249-9174-4e8e-b959-55c8ea959a02')(it)('should fail trying to create two times the same trigger', done => { - Parse.Hooks.createTrigger('MyClass', 'beforeSave', 'http://url.com') - .then( - () => { - return Parse.Hooks.createTrigger('MyClass', 'beforeSave', 'http://url.com'); - }, - () => { - fail('should create a new trigger'); - } - ) - .then( - () => { - fail('should not be able to create the same trigger'); - }, - err => { - expect(err).not.toBe(undefined); - expect(err).not.toBe(null); - if (err) { - expect(err.code).toBe(143); - expect(err.message).toBe('class MyClass already has trigger beforeSave'); + it_id('4db8c249-9174-4e8e-b959-55c8ea959a02')(it)( + 'should fail trying to create two times the same trigger', + done => { + Parse.Hooks.createTrigger('MyClass', 'beforeSave', 'http://url.com') + .then( + () => { + return Parse.Hooks.createTrigger('MyClass', 'beforeSave', 'http://url.com'); + }, + () => { + fail('should create a new trigger'); } - return Parse.Hooks.removeTrigger('MyClass', 'beforeSave'); - } - ) - .then( - () => { - done(); - }, - err => { - jfail(err); - done(); - } - ); - }); + ) + .then( + () => { + fail('should not be able to create the same trigger'); + }, + err => { + expect(err).not.toBe(undefined); + expect(err).not.toBe(null); + if (err) { + expect(err.code).toBe(143); + expect(err.message).toBe('class MyClass already has trigger beforeSave'); + } + return Parse.Hooks.removeTrigger('MyClass', 'beforeSave'); + } + ) + .then( + () => { + done(); + }, + err => { + jfail(err); + done(); + } + ); + } + ); it("should fail trying to update a function that don't exist", done => { Parse.Hooks.updateFunction('A_COOL_FUNCTION', 'http://url.com') @@ -359,164 +365,102 @@ describe('Hooks', () => { }); }); - it_id('96d99414-b739-4e36-b3f4-8135e0be83ea')(it)('should create hooks and properly preload them', done => { - const promises = []; - for (let i = 0; i < 5; i++) { - promises.push( - Parse.Hooks.createTrigger('MyClass' + i, 'beforeSave', 'http://url.com/beforeSave/' + i) - ); - promises.push(Parse.Hooks.createFunction('AFunction' + i, 'http://url.com/function' + i)); - } + it_id('96d99414-b739-4e36-b3f4-8135e0be83ea')(it)( + 'should create hooks and properly preload them', + done => { + const promises = []; + for (let i = 0; i < 5; i++) { + promises.push( + Parse.Hooks.createTrigger('MyClass' + i, 'beforeSave', 'http://url.com/beforeSave/' + i) + ); + promises.push(Parse.Hooks.createFunction('AFunction' + i, 'http://url.com/function' + i)); + } - Promise.all(promises) - .then( - function () { - for (let i = 0; i < 5; i++) { - // Delete everything from memory, as the server just started - triggers.removeTrigger('beforeSave', 'MyClass' + i, Parse.applicationId); - triggers.removeFunction('AFunction' + i, Parse.applicationId); - expect( - triggers.getTrigger('MyClass' + i, 'beforeSave', Parse.applicationId) - ).toBeUndefined(); - expect(triggers.getFunction('AFunction' + i, Parse.applicationId)).toBeUndefined(); + Promise.all(promises) + .then( + function () { + for (let i = 0; i < 5; i++) { + // Delete everything from memory, as the server just started + triggers.removeTrigger('beforeSave', 'MyClass' + i, Parse.applicationId); + triggers.removeFunction('AFunction' + i, Parse.applicationId); + expect( + triggers.getTrigger('MyClass' + i, 'beforeSave', Parse.applicationId) + ).toBeUndefined(); + expect(triggers.getFunction('AFunction' + i, Parse.applicationId)).toBeUndefined(); + } + const hooksController = new HooksController( + Parse.applicationId, + Config.get('test').database + ); + return hooksController.load(); + }, + err => { + jfail(err); + fail('Should properly create all hooks'); + done(); } - const hooksController = new HooksController( - Parse.applicationId, - Config.get('test').database - ); - return hooksController.load(); - }, - err => { - jfail(err); - fail('Should properly create all hooks'); - done(); - } - ) - .then( - function () { - for (let i = 0; i < 5; i++) { - expect( - triggers.getTrigger('MyClass' + i, 'beforeSave', Parse.applicationId) - ).not.toBeUndefined(); - expect(triggers.getFunction('AFunction' + i, Parse.applicationId)).not.toBeUndefined(); + ) + .then( + function () { + for (let i = 0; i < 5; i++) { + expect( + triggers.getTrigger('MyClass' + i, 'beforeSave', Parse.applicationId) + ).not.toBeUndefined(); + expect( + triggers.getFunction('AFunction' + i, Parse.applicationId) + ).not.toBeUndefined(); + } + done(); + }, + err => { + jfail(err); + fail('should properly load all hooks'); + done(); } - done(); - }, - err => { - jfail(err); - fail('should properly load all hooks'); - done(); - } - ); - }); - - it_id('fe7d41eb-e570-4804-ac1f-8b6c407fdafe')(it)('should run the function on the test server', done => { - app.post('/SomeFunction', function (req, res) { - res.json({ success: 'OK!' }); - }); + ); + } + ); - Parse.Hooks.createFunction('SOME_TEST_FUNCTION', hookServerURL + '/SomeFunction') - .then( - function () { - return Parse.Cloud.run('SOME_TEST_FUNCTION'); - }, - err => { - jfail(err); - fail('Should not fail creating a function'); - done(); - } - ) - .then( - function (res) { - expect(res).toBe('OK!'); - done(); - }, - err => { - jfail(err); - fail('Should not fail calling a function'); - done(); - } - ); - }); + it_id('fe7d41eb-e570-4804-ac1f-8b6c407fdafe')(it)( + 'should run the function on the test server', + done => { + app.post('/SomeFunction', function (req, res) { + res.json({ success: 'OK!' }); + }); - it_id('63985b4c-a212-4a86-aa0e-eb4600bb485b')(it)('should run the function on the test server (error handling)', done => { - app.post('/SomeFunctionError', function (req, res) { - res.json({ error: { code: 1337, error: 'hacking that one!' } }); - }); - // The function is deleted as the DB is dropped between calls - Parse.Hooks.createFunction('SOME_TEST_FUNCTION', hookServerURL + '/SomeFunctionError') - .then( - function () { - return Parse.Cloud.run('SOME_TEST_FUNCTION'); - }, - err => { - jfail(err); - fail('Should not fail creating a function'); - done(); - } - ) - .then( - function () { - fail('Should not succeed calling that function'); - done(); - }, - err => { - expect(err).not.toBe(undefined); - expect(err).not.toBe(null); - if (err) { - expect(err.code).toBe(Parse.Error.SCRIPT_FAILED); - expect(err.message.code).toEqual(1337); - expect(err.message.error).toEqual('hacking that one!'); + Parse.Hooks.createFunction('SOME_TEST_FUNCTION', hookServerURL + '/SomeFunction') + .then( + function () { + return Parse.Cloud.run('SOME_TEST_FUNCTION'); + }, + err => { + jfail(err); + fail('Should not fail creating a function'); + done(); } - done(); - } - ); - }); - - it_id('bacc1754-2a3a-4a7a-8d0e-f80af36da1ef')(it)('should provide X-Parse-Webhook-Key when defined', done => { - app.post('/ExpectingKey', function (req, res) { - if (req.get('X-Parse-Webhook-Key') === 'hook') { - res.json({ success: 'correct key provided' }); - } else { - res.json({ error: 'incorrect key provided' }); - } - }); - - Parse.Hooks.createFunction('SOME_TEST_FUNCTION', hookServerURL + '/ExpectingKey') - .then( - function () { - return Parse.Cloud.run('SOME_TEST_FUNCTION'); - }, - err => { - jfail(err); - fail('Should not fail creating a function'); - done(); - } - ) - .then( - function (res) { - expect(res).toBe('correct key provided'); - done(); - }, - err => { - jfail(err); - fail('Should not fail calling a function'); - done(); - } - ); - }); + ) + .then( + function (res) { + expect(res).toBe('OK!'); + done(); + }, + err => { + jfail(err); + fail('Should not fail calling a function'); + done(); + } + ); + } + ); - it_id('eeb67946-42c6-4581-89af-2abb4927913e')(it)('should not pass X-Parse-Webhook-Key if not provided', done => { - reconfigureServer({ webhookKey: undefined }).then(() => { - app.post('/ExpectingKeyAlso', function (req, res) { - if (req.get('X-Parse-Webhook-Key') === 'hook') { - res.json({ success: 'correct key provided' }); - } else { - res.json({ error: 'incorrect key provided' }); - } + it_id('63985b4c-a212-4a86-aa0e-eb4600bb485b')(it)( + 'should run the function on the test server (error handling)', + done => { + app.post('/SomeFunctionError', function (req, res) { + res.json({ error: { code: 1337, error: 'hacking that one!' } }); }); - - Parse.Hooks.createFunction('SOME_TEST_FUNCTION', hookServerURL + '/ExpectingKeyAlso') + // The function is deleted as the DB is dropped between calls + Parse.Hooks.createFunction('SOME_TEST_FUNCTION', hookServerURL + '/SomeFunctionError') .then( function () { return Parse.Cloud.run('SOME_TEST_FUNCTION'); @@ -537,105 +481,197 @@ describe('Hooks', () => { expect(err).not.toBe(null); if (err) { expect(err.code).toBe(Parse.Error.SCRIPT_FAILED); - expect(err.message).toEqual('incorrect key provided'); + expect(err.message.code).toEqual(1337); + expect(err.message.error).toEqual('hacking that one!'); } done(); } ); - }); - }); + } + ); - it_id('21decb65-4b93-4791-85a3-ab124a9ea3ac')(it)('should run the beforeSave hook on the test server', done => { - let triggerCount = 0; - app.post('/BeforeSaveSome', function (req, res) { - triggerCount++; - const object = req.body.object; - object.hello = 'world'; - // Would need parse cloud express to set much more - // But this should override the key upon return - res.json({ success: object }); - }); - // The function is deleted as the DB is dropped between calls - Parse.Hooks.createTrigger('SomeRandomObject', 'beforeSave', hookServerURL + '/BeforeSaveSome') - .then(function () { - const obj = new Parse.Object('SomeRandomObject'); - return obj.save(); - }) - .then(function (res) { - expect(triggerCount).toBe(1); - return res.fetch(); - }) - .then(function (res) { - expect(res.get('hello')).toEqual('world'); - done(); - }) - .catch(err => { - jfail(err); - fail('Should not fail creating a function'); - done(); + it_id('bacc1754-2a3a-4a7a-8d0e-f80af36da1ef')(it)( + 'should provide X-Parse-Webhook-Key when defined', + done => { + app.post('/ExpectingKey', function (req, res) { + if (req.get('X-Parse-Webhook-Key') === 'hook') { + res.json({ success: 'correct key provided' }); + } else { + res.json({ error: 'incorrect key provided' }); + } }); - }); - it_id('52e3152b-5514-4418-9e76-1f394368b8fb')(it)('beforeSave hooks should correctly handle responses containing entire object', done => { - app.post('/BeforeSaveSome2', function (req, res) { - const object = Parse.Object.fromJSON(req.body.object); - object.set('hello', 'world'); - res.json({ success: object }); - }); - Parse.Hooks.createTrigger('SomeRandomObject2', 'beforeSave', hookServerURL + '/BeforeSaveSome2') - .then(function () { - const obj = new Parse.Object('SomeRandomObject2'); - return obj.save(); - }) - .then(function (res) { - return res.save(); - }) - .then(function (res) { - expect(res.get('hello')).toEqual('world'); - done(); - }) - .catch(err => { - fail(`Should not fail: ${JSON.stringify(err)}`); - done(); - }); - }); + Parse.Hooks.createFunction('SOME_TEST_FUNCTION', hookServerURL + '/ExpectingKey') + .then( + function () { + return Parse.Cloud.run('SOME_TEST_FUNCTION'); + }, + err => { + jfail(err); + fail('Should not fail creating a function'); + done(); + } + ) + .then( + function (res) { + expect(res).toBe('correct key provided'); + done(); + }, + err => { + jfail(err); + fail('Should not fail calling a function'); + done(); + } + ); + } + ); + + it_id('eeb67946-42c6-4581-89af-2abb4927913e')(it)( + 'should not pass X-Parse-Webhook-Key if not provided', + done => { + reconfigureServer({ webhookKey: undefined }).then(() => { + app.post('/ExpectingKeyAlso', function (req, res) { + if (req.get('X-Parse-Webhook-Key') === 'hook') { + res.json({ success: 'correct key provided' }); + } else { + res.json({ error: 'incorrect key provided' }); + } + }); - it_id('d27a7587-abb5-40d5-9805-051ee91de474')(it)('should run the afterSave hook on the test server', done => { - let triggerCount = 0; - let newObjectId; - app.post('/AfterSaveSome', function (req, res) { - triggerCount++; - const obj = new Parse.Object('AnotherObject'); - obj.set('foo', 'bar'); - obj.save().then(function (obj) { - newObjectId = obj.id; - res.json({ success: {} }); + Parse.Hooks.createFunction('SOME_TEST_FUNCTION', hookServerURL + '/ExpectingKeyAlso') + .then( + function () { + return Parse.Cloud.run('SOME_TEST_FUNCTION'); + }, + err => { + jfail(err); + fail('Should not fail creating a function'); + done(); + } + ) + .then( + function () { + fail('Should not succeed calling that function'); + done(); + }, + err => { + expect(err).not.toBe(undefined); + expect(err).not.toBe(null); + if (err) { + expect(err.code).toBe(Parse.Error.SCRIPT_FAILED); + expect(err.message).toEqual('incorrect key provided'); + } + done(); + } + ); }); - }); - // The function is deleted as the DB is dropped between calls - Parse.Hooks.createTrigger('SomeRandomObject', 'afterSave', hookServerURL + '/AfterSaveSome') - .then(function () { - const obj = new Parse.Object('SomeRandomObject'); - return obj.save(); - }) - .then(function () { - return new Promise(resolve => { - setTimeout(() => { - expect(triggerCount).toBe(1); - new Parse.Query('AnotherObject').get(newObjectId).then(r => resolve(r)); - }, 500); + } + ); + + it_id('21decb65-4b93-4791-85a3-ab124a9ea3ac')(it)( + 'should run the beforeSave hook on the test server', + done => { + let triggerCount = 0; + app.post('/BeforeSaveSome', function (req, res) { + triggerCount++; + const object = req.body.object; + object.hello = 'world'; + // Would need parse cloud express to set much more + // But this should override the key upon return + res.json({ success: object }); + }); + // The function is deleted as the DB is dropped between calls + Parse.Hooks.createTrigger('SomeRandomObject', 'beforeSave', hookServerURL + '/BeforeSaveSome') + .then(function () { + const obj = new Parse.Object('SomeRandomObject'); + return obj.save(); + }) + .then(function (res) { + expect(triggerCount).toBe(1); + return res.fetch(); + }) + .then(function (res) { + expect(res.get('hello')).toEqual('world'); + done(); + }) + .catch(err => { + jfail(err); + fail('Should not fail creating a function'); + done(); }); - }) - .then(function (res) { - expect(res.get('foo')).toEqual('bar'); - done(); - }) - .catch(err => { - jfail(err); - fail('Should not fail creating a function'); - done(); + } + ); + + it_id('52e3152b-5514-4418-9e76-1f394368b8fb')(it)( + 'beforeSave hooks should correctly handle responses containing entire object', + done => { + app.post('/BeforeSaveSome2', function (req, res) { + const object = Parse.Object.fromJSON(req.body.object); + object.set('hello', 'world'); + res.json({ success: object }); }); - }); + Parse.Hooks.createTrigger( + 'SomeRandomObject2', + 'beforeSave', + hookServerURL + '/BeforeSaveSome2' + ) + .then(function () { + const obj = new Parse.Object('SomeRandomObject2'); + return obj.save(); + }) + .then(function (res) { + return res.save(); + }) + .then(function (res) { + expect(res.get('hello')).toEqual('world'); + done(); + }) + .catch(err => { + fail(`Should not fail: ${JSON.stringify(err)}`); + done(); + }); + } + ); + + it_id('d27a7587-abb5-40d5-9805-051ee91de474')(it)( + 'should run the afterSave hook on the test server', + done => { + let triggerCount = 0; + let newObjectId; + app.post('/AfterSaveSome', function (req, res) { + triggerCount++; + const obj = new Parse.Object('AnotherObject'); + obj.set('foo', 'bar'); + obj.save().then(function (obj) { + newObjectId = obj.id; + res.json({ success: {} }); + }); + }); + // The function is deleted as the DB is dropped between calls + Parse.Hooks.createTrigger('SomeRandomObject', 'afterSave', hookServerURL + '/AfterSaveSome') + .then(function () { + const obj = new Parse.Object('SomeRandomObject'); + return obj.save(); + }) + .then(function () { + return new Promise(resolve => { + setTimeout(() => { + expect(triggerCount).toBe(1); + new Parse.Query('AnotherObject').get(newObjectId).then(r => resolve(r)); + }, 500); + }); + }) + .then(function (res) { + expect(res.get('foo')).toEqual('bar'); + done(); + }) + .catch(err => { + jfail(err); + fail('Should not fail creating a function'); + done(); + }); + } + ); }); describe('triggers', () => { diff --git a/spec/ParseInstallation.spec.js b/spec/ParseInstallation.spec.js index c03a727b4a..8dd91c5a05 100644 --- a/spec/ParseInstallation.spec.js +++ b/spec/ParseInstallation.spec.js @@ -856,58 +856,61 @@ describe('Installations', () => { }); }); - it_id('22311bc7-3f4f-42c1-a958-57083929e80d')(it)('update is linking two existing objects w/ increment', done => { - const installId = '12345678-abcd-abcd-abcd-123456789abc'; - const t = '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'; - let input = { - installationId: installId, - deviceType: 'ios', - }; - rest - .create(config, auth.nobody(config), '_Installation', input) - .then(() => { - input = { - deviceToken: t, - deviceType: 'ios', - }; - return rest.create(config, auth.nobody(config), '_Installation', input); - }) - .then(() => - database.adapter.find('_Installation', installationSchema, { deviceToken: t }, {}) - ) - .then(results => { - expect(results.length).toEqual(1); - input = { - deviceToken: t, - installationId: installId, - deviceType: 'ios', - score: { - __op: 'Increment', - amount: 1, - }, - }; - return rest.update( - config, - auth.nobody(config), - '_Installation', - { objectId: results[0].objectId }, - input - ); - }) - .then(() => database.adapter.find('_Installation', installationSchema, {}, {})) - .then(results => { - expect(results.length).toEqual(1); - expect(results[0].installationId).toEqual(installId); - expect(results[0].deviceToken).toEqual(t); - expect(results[0].deviceType).toEqual('ios'); - expect(results[0].score).toEqual(1); - done(); - }) - .catch(error => { - jfail(error); - done(); - }); - }); + it_id('22311bc7-3f4f-42c1-a958-57083929e80d')(it)( + 'update is linking two existing objects w/ increment', + done => { + const installId = '12345678-abcd-abcd-abcd-123456789abc'; + const t = '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'; + let input = { + installationId: installId, + deviceType: 'ios', + }; + rest + .create(config, auth.nobody(config), '_Installation', input) + .then(() => { + input = { + deviceToken: t, + deviceType: 'ios', + }; + return rest.create(config, auth.nobody(config), '_Installation', input); + }) + .then(() => + database.adapter.find('_Installation', installationSchema, { deviceToken: t }, {}) + ) + .then(results => { + expect(results.length).toEqual(1); + input = { + deviceToken: t, + installationId: installId, + deviceType: 'ios', + score: { + __op: 'Increment', + amount: 1, + }, + }; + return rest.update( + config, + auth.nobody(config), + '_Installation', + { objectId: results[0].objectId }, + input + ); + }) + .then(() => database.adapter.find('_Installation', installationSchema, {}, {})) + .then(results => { + expect(results.length).toEqual(1); + expect(results[0].installationId).toEqual(installId); + expect(results[0].deviceToken).toEqual(t); + expect(results[0].deviceType).toEqual('ios'); + expect(results[0].score).toEqual(1); + done(); + }) + .catch(error => { + jfail(error); + done(); + }); + } + ); it('update is linking two existing with installation id', done => { const installId = '12345678-abcd-abcd-abcd-123456789abc'; @@ -969,70 +972,73 @@ describe('Installations', () => { }); }); - it_id('f2975078-eab7-4287-a932-288842e3cfb9')(it)('update is linking two existing with installation id w/ op', done => { - const installId = '12345678-abcd-abcd-abcd-123456789abc'; - const t = '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'; - let input = { - installationId: installId, - deviceType: 'ios', - }; - let installObj; - let tokenObj; - rest - .create(config, auth.nobody(config), '_Installation', input) - .then(() => database.adapter.find('_Installation', installationSchema, {}, {})) - .then(results => { - expect(results.length).toEqual(1); - installObj = results[0]; - input = { - deviceToken: t, - deviceType: 'ios', - }; - return rest.create(config, auth.nobody(config), '_Installation', input); - }) - .then(() => - database.adapter.find('_Installation', installationSchema, { deviceToken: t }, {}) - ) - .then(results => { - expect(results.length).toEqual(1); - tokenObj = results[0]; - input = { - installationId: installId, - deviceToken: t, - deviceType: 'ios', - score: { - __op: 'Increment', - amount: 1, - }, - }; - return rest.update( - config, - auth.nobody(config), - '_Installation', - { objectId: installObj.objectId }, - input - ); - }) - .then(() => - database.adapter.find( - '_Installation', - installationSchema, - { objectId: tokenObj.objectId }, - {} + it_id('f2975078-eab7-4287-a932-288842e3cfb9')(it)( + 'update is linking two existing with installation id w/ op', + done => { + const installId = '12345678-abcd-abcd-abcd-123456789abc'; + const t = '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'; + let input = { + installationId: installId, + deviceType: 'ios', + }; + let installObj; + let tokenObj; + rest + .create(config, auth.nobody(config), '_Installation', input) + .then(() => database.adapter.find('_Installation', installationSchema, {}, {})) + .then(results => { + expect(results.length).toEqual(1); + installObj = results[0]; + input = { + deviceToken: t, + deviceType: 'ios', + }; + return rest.create(config, auth.nobody(config), '_Installation', input); + }) + .then(() => + database.adapter.find('_Installation', installationSchema, { deviceToken: t }, {}) ) - ) - .then(results => { - expect(results.length).toEqual(1); - expect(results[0].installationId).toEqual(installId); - expect(results[0].deviceToken).toEqual(t); - expect(results[0].score).toEqual(1); - done(); - }) - .catch(error => { - jfail(error); - done(); - }); - }); + .then(results => { + expect(results.length).toEqual(1); + tokenObj = results[0]; + input = { + installationId: installId, + deviceToken: t, + deviceType: 'ios', + score: { + __op: 'Increment', + amount: 1, + }, + }; + return rest.update( + config, + auth.nobody(config), + '_Installation', + { objectId: installObj.objectId }, + input + ); + }) + .then(() => + database.adapter.find( + '_Installation', + installationSchema, + { objectId: tokenObj.objectId }, + {} + ) + ) + .then(results => { + expect(results.length).toEqual(1); + expect(results[0].installationId).toEqual(installId); + expect(results[0].deviceToken).toEqual(t); + expect(results[0].score).toEqual(1); + done(); + }) + .catch(error => { + jfail(error); + done(); + }); + } + ); it('ios merge existing same token no installation id', done => { // Test creating installation when there is an existing object with the diff --git a/spec/ParseLiveQuery.spec.js b/spec/ParseLiveQuery.spec.js index a65eef6084..8a60dc3316 100644 --- a/spec/ParseLiveQuery.spec.js +++ b/spec/ParseLiveQuery.spec.js @@ -878,101 +878,107 @@ describe('ParseLiveQuery', function () { await expectAsync(query.subscribe()).toBeRejectedWith(new Error('Invalid session token')); }); - it_id('4ccc9508-ae6a-46ec-932a-9f5e49ab3b9e')(it)('handle invalid websocket payload length', async done => { - await reconfigureServer({ - liveQuery: { - classNames: ['TestObject'], - }, - startLiveQueryServer: true, - verbose: false, - silent: true, - websocketTimeout: 100, - }); - const object = new TestObject(); - await object.save(); - - const query = new Parse.Query(TestObject); - query.equalTo('objectId', object.id); - const subscription = await query.subscribe(); - - // All control frames must have a payload length of 125 bytes or less. - // https://tools.ietf.org/html/rfc6455#section-5.5 - // - // 0x89 = 10001001 = ping - // 0xfe = 11111110 = first bit is masking the remaining 7 are 1111110 or 126 the payload length - // https://tools.ietf.org/html/rfc6455#section-5.2 - const client = await Parse.CoreManager.getLiveQueryController().getDefaultLiveQueryClient(); - client.socket._socket.write(Buffer.from([0x89, 0xfe])); - - subscription.on('update', async object => { - expect(object.get('foo')).toBe('bar'); - done(); - }); - // Wait for Websocket timeout to reconnect - setTimeout(async () => { - object.set({ foo: 'bar' }); + it_id('4ccc9508-ae6a-46ec-932a-9f5e49ab3b9e')(it)( + 'handle invalid websocket payload length', + async done => { + await reconfigureServer({ + liveQuery: { + classNames: ['TestObject'], + }, + startLiveQueryServer: true, + verbose: false, + silent: true, + websocketTimeout: 100, + }); + const object = new TestObject(); await object.save(); - }, 1000); - }); - - it_id('39a9191f-26dd-4e05-a379-297a67928de8')(it)('should execute live query update on email validation', async done => { - const emailAdapter = { - sendVerificationEmail: () => {}, - sendPasswordResetEmail: () => Promise.resolve(), - sendMail: () => {}, - }; - - await reconfigureServer({ - maintenanceKey: 'test2', - liveQuery: { - classNames: [Parse.User], - }, - startLiveQueryServer: true, - verbose: false, - silent: true, - websocketTimeout: 100, - appName: 'liveQueryEmailValidation', - verifyUserEmails: true, - emailAdapter: emailAdapter, - emailVerifyTokenValidityDuration: 20, // 0.5 second - publicServerURL: 'http://localhost:8378/1', - }).then(() => { - const user = new Parse.User(); - user.set('password', 'asdf'); - user.set('email', 'asdf@example.com'); - user.set('username', 'zxcv'); - user - .signUp() - .then(() => { - const config = Config.get('test'); - return config.database.find( - '_User', - { - username: 'zxcv', - }, - {}, - Auth.maintenance(config) - ); - }) - .then(async results => { - const foundUser = results[0]; - const query = new Parse.Query('_User'); - query.equalTo('objectId', foundUser.objectId); - const subscription = await query.subscribe(); - - subscription.on('update', async object => { - expect(object).toBeDefined(); - expect(object.get('emailVerified')).toBe(true); - done(); - }); - const userController = new UserController(emailAdapter, 'test', { - verifyUserEmails: true, + const query = new Parse.Query(TestObject); + query.equalTo('objectId', object.id); + const subscription = await query.subscribe(); + + // All control frames must have a payload length of 125 bytes or less. + // https://tools.ietf.org/html/rfc6455#section-5.5 + // + // 0x89 = 10001001 = ping + // 0xfe = 11111110 = first bit is masking the remaining 7 are 1111110 or 126 the payload length + // https://tools.ietf.org/html/rfc6455#section-5.2 + const client = await Parse.CoreManager.getLiveQueryController().getDefaultLiveQueryClient(); + client.socket._socket.write(Buffer.from([0x89, 0xfe])); + + subscription.on('update', async object => { + expect(object.get('foo')).toBe('bar'); + done(); + }); + // Wait for Websocket timeout to reconnect + setTimeout(async () => { + object.set({ foo: 'bar' }); + await object.save(); + }, 1000); + } + ); + + it_id('39a9191f-26dd-4e05-a379-297a67928de8')(it)( + 'should execute live query update on email validation', + async done => { + const emailAdapter = { + sendVerificationEmail: () => {}, + sendPasswordResetEmail: () => Promise.resolve(), + sendMail: () => {}, + }; + + await reconfigureServer({ + maintenanceKey: 'test2', + liveQuery: { + classNames: [Parse.User], + }, + startLiveQueryServer: true, + verbose: false, + silent: true, + websocketTimeout: 100, + appName: 'liveQueryEmailValidation', + verifyUserEmails: true, + emailAdapter: emailAdapter, + emailVerifyTokenValidityDuration: 20, // 0.5 second + publicServerURL: 'http://localhost:8378/1', + }).then(() => { + const user = new Parse.User(); + user.set('password', 'asdf'); + user.set('email', 'asdf@example.com'); + user.set('username', 'zxcv'); + user + .signUp() + .then(() => { + const config = Config.get('test'); + return config.database.find( + '_User', + { + username: 'zxcv', + }, + {}, + Auth.maintenance(config) + ); + }) + .then(async results => { + const foundUser = results[0]; + const query = new Parse.Query('_User'); + query.equalTo('objectId', foundUser.objectId); + const subscription = await query.subscribe(); + + subscription.on('update', async object => { + expect(object).toBeDefined(); + expect(object.get('emailVerified')).toBe(true); + done(); + }); + + const userController = new UserController(emailAdapter, 'test', { + verifyUserEmails: true, + }); + userController.verifyEmail(foundUser.username, foundUser._email_verify_token); }); - userController.verifyEmail(foundUser.username, foundUser._email_verify_token); - }); - }); - }); + }); + } + ); it('should not broadcast event to client with invalid session token - avisory GHSA-2xm2-xj2q-qgpj', async done => { await reconfigureServer({ diff --git a/spec/ParseObject.spec.js b/spec/ParseObject.spec.js index 1905ec3130..da0a108597 100644 --- a/spec/ParseObject.spec.js +++ b/spec/ParseObject.spec.js @@ -570,7 +570,10 @@ describe('Parse.Object testing', () => { it_only_db('mongo')('can increment array nested fields', async () => { const obj = new TestObject(); - obj.set('items', [ { value: 'a', count: 5 }, { value: 'b', count: 1 } ]); + obj.set('items', [ + { value: 'a', count: 5 }, + { value: 'b', count: 1 }, + ]); await obj.save(); obj.increment('items.0.count', 15); obj.increment('items.1.count', 4); diff --git a/spec/ParseQuery.Aggregate.spec.js b/spec/ParseQuery.Aggregate.spec.js index 126135392a..201977d459 100644 --- a/spec/ParseQuery.Aggregate.spec.js +++ b/spec/ParseQuery.Aggregate.spec.js @@ -296,112 +296,125 @@ describe('Parse.Query Aggregate testing', () => { .catch(done.fail); }); - it_id('c7695018-03de-49e4-8a72-d4d956f70deb')(it_exclude_dbs(['postgres']))('group and multiply transform', done => { - const obj1 = new TestObject({ name: 'item a', quantity: 2, price: 10 }); - const obj2 = new TestObject({ name: 'item b', quantity: 5, price: 5 }); - const pipeline = [ - { - $group: { - _id: null, - total: { $sum: { $multiply: ['$quantity', '$price'] } }, + it_id('c7695018-03de-49e4-8a72-d4d956f70deb')(it_exclude_dbs(['postgres']))( + 'group and multiply transform', + done => { + const obj1 = new TestObject({ name: 'item a', quantity: 2, price: 10 }); + const obj2 = new TestObject({ name: 'item b', quantity: 5, price: 5 }); + const pipeline = [ + { + $group: { + _id: null, + total: { $sum: { $multiply: ['$quantity', '$price'] } }, + }, }, - }, - ]; - Parse.Object.saveAll([obj1, obj2]) - .then(() => { - const query = new Parse.Query(TestObject); - return query.aggregate(pipeline); - }) - .then(results => { - expect(results.length).toEqual(1); - expect(results[0].total).toEqual(45); - done(); - }); - }); + ]; + Parse.Object.saveAll([obj1, obj2]) + .then(() => { + const query = new Parse.Query(TestObject); + return query.aggregate(pipeline); + }) + .then(results => { + expect(results.length).toEqual(1); + expect(results[0].total).toEqual(45); + done(); + }); + } + ); - it_id('2d278175-7594-4b29-bef4-04c778b7a42f')(it_exclude_dbs(['postgres']))('project and multiply transform', done => { - const obj1 = new TestObject({ name: 'item a', quantity: 2, price: 10 }); - const obj2 = new TestObject({ name: 'item b', quantity: 5, price: 5 }); - const pipeline = [ - { - $match: { quantity: { $exists: true } }, - }, - { - $project: { - name: 1, - total: { $multiply: ['$quantity', '$price'] }, + it_id('2d278175-7594-4b29-bef4-04c778b7a42f')(it_exclude_dbs(['postgres']))( + 'project and multiply transform', + done => { + const obj1 = new TestObject({ name: 'item a', quantity: 2, price: 10 }); + const obj2 = new TestObject({ name: 'item b', quantity: 5, price: 5 }); + const pipeline = [ + { + $match: { quantity: { $exists: true } }, }, - }, - ]; - Parse.Object.saveAll([obj1, obj2]) - .then(() => { - const query = new Parse.Query(TestObject); - return query.aggregate(pipeline); - }) - .then(results => { - expect(results.length).toEqual(2); - if (results[0].name === 'item a') { + { + $project: { + name: 1, + total: { $multiply: ['$quantity', '$price'] }, + }, + }, + ]; + Parse.Object.saveAll([obj1, obj2]) + .then(() => { + const query = new Parse.Query(TestObject); + return query.aggregate(pipeline); + }) + .then(results => { + expect(results.length).toEqual(2); + if (results[0].name === 'item a') { + expect(results[0].total).toEqual(20); + expect(results[1].total).toEqual(25); + } else { + expect(results[0].total).toEqual(25); + expect(results[1].total).toEqual(20); + } + done(); + }); + } + ); + + it_id('9c9d9318-3a9e-4c2a-8a09-d3aa52c7505b')(it_exclude_dbs(['postgres']))( + 'project without objectId transform', + done => { + const obj1 = new TestObject({ name: 'item a', quantity: 2, price: 10 }); + const obj2 = new TestObject({ name: 'item b', quantity: 5, price: 5 }); + const pipeline = [ + { + $match: { quantity: { $exists: true } }, + }, + { + $project: { + _id: 0, + total: { $multiply: ['$quantity', '$price'] }, + }, + }, + { + $sort: { total: 1 }, + }, + ]; + Parse.Object.saveAll([obj1, obj2]) + .then(() => { + const query = new Parse.Query(TestObject); + return query.aggregate(pipeline); + }) + .then(results => { + expect(results.length).toEqual(2); expect(results[0].total).toEqual(20); + expect(results[0].objectId).toEqual(undefined); expect(results[1].total).toEqual(25); - } else { - expect(results[0].total).toEqual(25); - expect(results[1].total).toEqual(20); - } - done(); - }); - }); + expect(results[1].objectId).toEqual(undefined); + done(); + }); + } + ); - it_id('9c9d9318-3a9e-4c2a-8a09-d3aa52c7505b')(it_exclude_dbs(['postgres']))('project without objectId transform', done => { - const obj1 = new TestObject({ name: 'item a', quantity: 2, price: 10 }); - const obj2 = new TestObject({ name: 'item b', quantity: 5, price: 5 }); - const pipeline = [ - { - $match: { quantity: { $exists: true } }, - }, - { - $project: { - _id: 0, - total: { $multiply: ['$quantity', '$price'] }, + it_id('f92c82ac-1993-4758-b718-45689dfc4154')(it_exclude_dbs(['postgres']))( + 'project updatedAt only transform', + done => { + const pipeline = [ + { + $project: { _id: 0, updatedAt: 1 }, }, - }, - { - $sort: { total: 1 }, - }, - ]; - Parse.Object.saveAll([obj1, obj2]) - .then(() => { - const query = new Parse.Query(TestObject); - return query.aggregate(pipeline); - }) - .then(results => { - expect(results.length).toEqual(2); - expect(results[0].total).toEqual(20); - expect(results[0].objectId).toEqual(undefined); - expect(results[1].total).toEqual(25); - expect(results[1].objectId).toEqual(undefined); + ]; + const query = new Parse.Query(TestObject); + query.aggregate(pipeline).then(results => { + expect(results.length).toEqual(4); + for (let i = 0; i < results.length; i++) { + const item = results[i]; + expect(Object.prototype.hasOwnProperty.call(item, 'updatedAt')).toEqual(true); + expect(Object.prototype.hasOwnProperty.call(item, 'objectId')).toEqual(false); + } done(); }); - }); - - it_id('f92c82ac-1993-4758-b718-45689dfc4154')(it_exclude_dbs(['postgres']))('project updatedAt only transform', done => { - const pipeline = [ - { - $project: { _id: 0, updatedAt: 1 }, - }, - ]; - const query = new Parse.Query(TestObject); - query.aggregate(pipeline).then(results => { - expect(results.length).toEqual(4); - for (let i = 0; i < results.length; i++) { - const item = results[i]; - expect(Object.prototype.hasOwnProperty.call(item, 'updatedAt')).toEqual(true); - expect(Object.prototype.hasOwnProperty.call(item, 'objectId')).toEqual(false); - } - done(); - }); - }); + } + ); - it_id('99566b1d-778d-4444-9deb-c398108e659d')(it_exclude_dbs(['postgres']))('can group by any date field (it does not work if you have dirty data)', + it_id('99566b1d-778d-4444-9deb-c398108e659d')(it_exclude_dbs(['postgres']))( + 'can group by any date field (it does not work if you have dirty data)', done => { // rows in your collection with non date data in the field that is supposed to be a date const obj1 = new TestObject({ dateField2019: new Date(1990, 11, 1) }); @@ -658,22 +671,25 @@ describe('Parse.Query Aggregate testing', () => { }); }); - it_id('d98c8c20-6dac-4d74-8228-85a1ae46a7d0')(it)('should aggregate with Date object (directAccess)', async () => { - const rest = require('../lib/rest'); - const auth = require('../lib/Auth'); - const TestObject = Parse.Object.extend('TestObject'); - const date = new Date(); - await new TestObject({ date: date }).save(null, { useMasterKey: true }); - const config = Config.get(Parse.applicationId); - const resp = await rest.find( - config, - auth.master(config), - 'TestObject', - {}, - { pipeline: [{ $match: { date: { $lte: new Date() } } }] } - ); - expect(resp.results.length).toBe(1); - }); + it_id('d98c8c20-6dac-4d74-8228-85a1ae46a7d0')(it)( + 'should aggregate with Date object (directAccess)', + async () => { + const rest = require('../lib/rest'); + const auth = require('../lib/Auth'); + const TestObject = Parse.Object.extend('TestObject'); + const date = new Date(); + await new TestObject({ date: date }).save(null, { useMasterKey: true }); + const config = Config.get(Parse.applicationId); + const resp = await rest.find( + config, + auth.master(config), + 'TestObject', + {}, + { pipeline: [{ $match: { date: { $lte: new Date() } } }] } + ); + expect(resp.results.length).toBe(1); + } + ); it_id('3d73d23a-fce1-4ac0-972a-50f6a550f348')(it)('match comparison query', done => { const options = Object.assign({}, masterKeyOptions, { @@ -817,14 +833,17 @@ describe('Parse.Query Aggregate testing', () => { }); }); - it_id('3a1e2cdc-52c7-4060-bc90-b06d557d85ce')(it_exclude_dbs(['postgres']))('match exists query', done => { - const pipeline = [{ $match: { score: { $exists: true } } }]; - const query = new Parse.Query(TestObject); - query.aggregate(pipeline).then(results => { - expect(results.length).toEqual(4); - done(); - }); - }); + it_id('3a1e2cdc-52c7-4060-bc90-b06d557d85ce')(it_exclude_dbs(['postgres']))( + 'match exists query', + done => { + const pipeline = [{ $match: { score: { $exists: true } } }]; + const query = new Parse.Query(TestObject); + query.aggregate(pipeline).then(results => { + expect(results.length).toEqual(4); + done(); + }); + } + ); it_id('0adea3f4-73f7-4b48-a7dd-c764ceb947ec')(it)('match date query - createdAt', done => { const obj1 = new TestObject(); @@ -882,78 +901,84 @@ describe('Parse.Query Aggregate testing', () => { }); }); - it_id('802ffc99-861b-4b72-90a6-0c666a2e3fd8')(it_exclude_dbs(['postgres']))('match pointer with operator query', done => { - const pointer = new PointerObject(); + it_id('802ffc99-861b-4b72-90a6-0c666a2e3fd8')(it_exclude_dbs(['postgres']))( + 'match pointer with operator query', + done => { + const pointer = new PointerObject(); - const obj1 = new TestObject({ pointer }); - const obj2 = new TestObject({ pointer }); - const obj3 = new TestObject(); + const obj1 = new TestObject({ pointer }); + const obj2 = new TestObject({ pointer }); + const obj3 = new TestObject(); - Parse.Object.saveAll([pointer, obj1, obj2, obj3]) - .then(() => { - const pipeline = [{ $match: { pointer: { $exists: true } } }]; - const query = new Parse.Query(TestObject); - return query.aggregate(pipeline); - }) - .then(results => { - expect(results.length).toEqual(2); - expect(results[0].pointer.objectId).toEqual(pointer.id); - expect(results[1].pointer.objectId).toEqual(pointer.id); - expect(results.some(result => result.objectId === obj1.id)).toEqual(true); - expect(results.some(result => result.objectId === obj2.id)).toEqual(true); - done(); - }); - }); + Parse.Object.saveAll([pointer, obj1, obj2, obj3]) + .then(() => { + const pipeline = [{ $match: { pointer: { $exists: true } } }]; + const query = new Parse.Query(TestObject); + return query.aggregate(pipeline); + }) + .then(results => { + expect(results.length).toEqual(2); + expect(results[0].pointer.objectId).toEqual(pointer.id); + expect(results[1].pointer.objectId).toEqual(pointer.id); + expect(results.some(result => result.objectId === obj1.id)).toEqual(true); + expect(results.some(result => result.objectId === obj2.id)).toEqual(true); + done(); + }); + } + ); - it_id('28090280-7c3e-47f8-8bf6-bebf8566a36c')(it_exclude_dbs(['postgres']))('match null values', async () => { - const obj1 = new Parse.Object('MyCollection'); - obj1.set('language', 'en'); - obj1.set('otherField', 1); - const obj2 = new Parse.Object('MyCollection'); - obj2.set('language', 'en'); - obj2.set('otherField', 2); - const obj3 = new Parse.Object('MyCollection'); - obj3.set('language', null); - obj3.set('otherField', 3); - const obj4 = new Parse.Object('MyCollection'); - obj4.set('language', null); - obj4.set('otherField', 4); - const obj5 = new Parse.Object('MyCollection'); - obj5.set('language', 'pt'); - obj5.set('otherField', 5); - const obj6 = new Parse.Object('MyCollection'); - obj6.set('language', 'pt'); - obj6.set('otherField', 6); - await Parse.Object.saveAll([obj1, obj2, obj3, obj4, obj5, obj6]); - - expect( - ( - await new Parse.Query('MyCollection').aggregate([ - { - $match: { - language: { $in: [null, 'en'] }, + it_id('28090280-7c3e-47f8-8bf6-bebf8566a36c')(it_exclude_dbs(['postgres']))( + 'match null values', + async () => { + const obj1 = new Parse.Object('MyCollection'); + obj1.set('language', 'en'); + obj1.set('otherField', 1); + const obj2 = new Parse.Object('MyCollection'); + obj2.set('language', 'en'); + obj2.set('otherField', 2); + const obj3 = new Parse.Object('MyCollection'); + obj3.set('language', null); + obj3.set('otherField', 3); + const obj4 = new Parse.Object('MyCollection'); + obj4.set('language', null); + obj4.set('otherField', 4); + const obj5 = new Parse.Object('MyCollection'); + obj5.set('language', 'pt'); + obj5.set('otherField', 5); + const obj6 = new Parse.Object('MyCollection'); + obj6.set('language', 'pt'); + obj6.set('otherField', 6); + await Parse.Object.saveAll([obj1, obj2, obj3, obj4, obj5, obj6]); + + expect( + ( + await new Parse.Query('MyCollection').aggregate([ + { + $match: { + language: { $in: [null, 'en'] }, + }, }, - }, - ]) - ) - .map(value => value.otherField) - .sort() - ).toEqual([1, 2, 3, 4]); - - expect( - ( - await new Parse.Query('MyCollection').aggregate([ - { - $match: { - $or: [{ language: 'en' }, { language: null }], + ]) + ) + .map(value => value.otherField) + .sort() + ).toEqual([1, 2, 3, 4]); + + expect( + ( + await new Parse.Query('MyCollection').aggregate([ + { + $match: { + $or: [{ language: 'en' }, { language: null }], + }, }, - }, - ]) - ) - .map(value => value.otherField) - .sort() - ).toEqual([1, 2, 3, 4]); - }); + ]) + ) + .map(value => value.otherField) + .sort() + ).toEqual([1, 2, 3, 4]); + } + ); it_id('df63d1f5-7c37-4ed9-8bc5-20d82f29f509')(it)('project query', done => { const options = Object.assign({}, masterKeyOptions, { @@ -1153,34 +1178,40 @@ describe('Parse.Query Aggregate testing', () => { }); }); - it_id('91e6cb94-2837-44b7-b057-0c4965057caa')(it)('distinct class does not exist return empty', done => { - const options = Object.assign({}, masterKeyOptions, { - body: { distinct: 'unknown' }, - }); - get(Parse.serverURL + '/aggregate/UnknownClass', options) - .then(resp => { - expect(resp.results.length).toBe(0); - done(); - }) - .catch(done.fail); - }); + it_id('91e6cb94-2837-44b7-b057-0c4965057caa')(it)( + 'distinct class does not exist return empty', + done => { + const options = Object.assign({}, masterKeyOptions, { + body: { distinct: 'unknown' }, + }); + get(Parse.serverURL + '/aggregate/UnknownClass', options) + .then(resp => { + expect(resp.results.length).toBe(0); + done(); + }) + .catch(done.fail); + } + ); - it_id('bd15daaf-8dc7-458c-81e2-170026f4a8a7')(it)('distinct field does not exist return empty', done => { - const options = Object.assign({}, masterKeyOptions, { - body: { distinct: 'unknown' }, - }); - const obj = new TestObject(); - obj - .save() - .then(() => { - return get(Parse.serverURL + '/aggregate/TestObject', options); - }) - .then(resp => { - expect(resp.results.length).toBe(0); - done(); - }) - .catch(done.fail); - }); + it_id('bd15daaf-8dc7-458c-81e2-170026f4a8a7')(it)( + 'distinct field does not exist return empty', + done => { + const options = Object.assign({}, masterKeyOptions, { + body: { distinct: 'unknown' }, + }); + const obj = new TestObject(); + obj + .save() + .then(() => { + return get(Parse.serverURL + '/aggregate/TestObject', options); + }) + .then(resp => { + expect(resp.results.length).toBe(0); + done(); + }) + .catch(done.fail); + } + ); it_id('21988fce-8326-425f-82f0-cd444ca3671b')(it)('distinct array', done => { const options = Object.assign({}, masterKeyOptions, { @@ -1256,108 +1287,114 @@ describe('Parse.Query Aggregate testing', () => { .catch(done.fail); }); - it_id('d9c19419-e99d-4d9f-b7f3-418e49ee47dd')(it)('does not return sensitive hidden properties', done => { - const options = Object.assign({}, masterKeyOptions, { - body: { - $match: { - score: { - $gt: 5, + it_id('d9c19419-e99d-4d9f-b7f3-418e49ee47dd')(it)( + 'does not return sensitive hidden properties', + done => { + const options = Object.assign({}, masterKeyOptions, { + body: { + $match: { + score: { + $gt: 5, + }, }, }, - }, - }); - - const username = 'leaky_user'; - const score = 10; - - const user = new Parse.User(); - user.setUsername(username); - user.setPassword('password'); - user.set('score', score); - user - .signUp() - .then(function () { - return get(Parse.serverURL + '/aggregate/_User', options); - }) - .then(function (resp) { - expect(resp.results.length).toBe(1); - const result = resp.results[0]; - - // verify server-side keys are not present... - expect(result._hashed_password).toBe(undefined); - expect(result._wperm).toBe(undefined); - expect(result._rperm).toBe(undefined); - expect(result._acl).toBe(undefined); - expect(result._created_at).toBe(undefined); - expect(result._updated_at).toBe(undefined); - - // verify createdAt, updatedAt and others are present - expect(result.createdAt).not.toBe(undefined); - expect(result.updatedAt).not.toBe(undefined); - expect(result.objectId).not.toBe(undefined); - expect(result.username).toBe(username); - expect(result.score).toBe(score); - - done(); - }) - .catch(function (err) { - fail(err); }); - }); - it_id('0a23e791-e9b5-457a-9bf9-9c5ecf406f42')(it_exclude_dbs(['postgres']))('aggregate allow multiple of same stage', async done => { - await reconfigureServer({ silent: false }); - const pointer1 = new TestObject({ value: 1 }); - const pointer2 = new TestObject({ value: 2 }); - const pointer3 = new TestObject({ value: 3 }); + const username = 'leaky_user'; + const score = 10; + + const user = new Parse.User(); + user.setUsername(username); + user.setPassword('password'); + user.set('score', score); + user + .signUp() + .then(function () { + return get(Parse.serverURL + '/aggregate/_User', options); + }) + .then(function (resp) { + expect(resp.results.length).toBe(1); + const result = resp.results[0]; + + // verify server-side keys are not present... + expect(result._hashed_password).toBe(undefined); + expect(result._wperm).toBe(undefined); + expect(result._rperm).toBe(undefined); + expect(result._acl).toBe(undefined); + expect(result._created_at).toBe(undefined); + expect(result._updated_at).toBe(undefined); + + // verify createdAt, updatedAt and others are present + expect(result.createdAt).not.toBe(undefined); + expect(result.updatedAt).not.toBe(undefined); + expect(result.objectId).not.toBe(undefined); + expect(result.username).toBe(username); + expect(result.score).toBe(score); - const obj1 = new TestObject({ pointer: pointer1, name: 'Hello' }); - const obj2 = new TestObject({ pointer: pointer2, name: 'Hello' }); - const obj3 = new TestObject({ pointer: pointer3, name: 'World' }); + done(); + }) + .catch(function (err) { + fail(err); + }); + } + ); - const options = Object.assign({}, masterKeyOptions, { - body: { - pipeline: [ - { - $match: { name: 'Hello' }, - }, - { - // Transform className$objectId to objectId and store in new field tempPointer - $project: { - tempPointer: { $substr: ['$_p_pointer', 11, -1] }, // Remove TestObject$ + it_id('0a23e791-e9b5-457a-9bf9-9c5ecf406f42')(it_exclude_dbs(['postgres']))( + 'aggregate allow multiple of same stage', + async done => { + await reconfigureServer({ silent: false }); + const pointer1 = new TestObject({ value: 1 }); + const pointer2 = new TestObject({ value: 2 }); + const pointer3 = new TestObject({ value: 3 }); + + const obj1 = new TestObject({ pointer: pointer1, name: 'Hello' }); + const obj2 = new TestObject({ pointer: pointer2, name: 'Hello' }); + const obj3 = new TestObject({ pointer: pointer3, name: 'World' }); + + const options = Object.assign({}, masterKeyOptions, { + body: { + pipeline: [ + { + $match: { name: 'Hello' }, }, - }, - { - // Left Join, replace objectId stored in tempPointer with an actual object - $lookup: { - from: 'test_TestObject', - localField: 'tempPointer', - foreignField: '_id', - as: 'tempPointer', + { + // Transform className$objectId to objectId and store in new field tempPointer + $project: { + tempPointer: { $substr: ['$_p_pointer', 11, -1] }, // Remove TestObject$ + }, }, - }, - { - // lookup returns an array, Deconstructs an array field to objects - $unwind: { - path: '$tempPointer', + { + // Left Join, replace objectId stored in tempPointer with an actual object + $lookup: { + from: 'test_TestObject', + localField: 'tempPointer', + foreignField: '_id', + as: 'tempPointer', + }, }, - }, - { - $match: { 'tempPointer.value': 2 }, - }, - ], - }, - }); - Parse.Object.saveAll([pointer1, pointer2, pointer3, obj1, obj2, obj3]) - .then(() => { - return get(Parse.serverURL + '/aggregate/TestObject', options); - }) - .then(resp => { - expect(resp.results.length).toEqual(1); - expect(resp.results[0].tempPointer.value).toEqual(2); - done(); + { + // lookup returns an array, Deconstructs an array field to objects + $unwind: { + path: '$tempPointer', + }, + }, + { + $match: { 'tempPointer.value': 2 }, + }, + ], + }, }); - }); + Parse.Object.saveAll([pointer1, pointer2, pointer3, obj1, obj2, obj3]) + .then(() => { + return get(Parse.serverURL + '/aggregate/TestObject', options); + }) + .then(resp => { + expect(resp.results.length).toEqual(1); + expect(resp.results[0].tempPointer.value).toEqual(2); + done(); + }); + } + ); it_only_db('mongo')('aggregate geoNear with location query', async () => { // Create geo index which is required for `geoNear` query diff --git a/spec/ParseQuery.FullTextSearch.spec.js b/spec/ParseQuery.FullTextSearch.spec.js index 11760ec161..e32fab5de2 100644 --- a/spec/ParseQuery.FullTextSearch.spec.js +++ b/spec/ParseQuery.FullTextSearch.spec.js @@ -76,70 +76,88 @@ describe('Parse.Query Full Text Search testing', () => { expect(resp.length).toBe(2); }); - it_id('7d3da216-9582-40ee-a2fe-8316feaf5c0c')(it)('fullTextSearch: $diacriticSensitive', async () => { - await fullTextHelper(); - const query = new Parse.Query('TestObject'); - query.fullText('subject', 'CAFÉ', { diacriticSensitive: true }); - const resp = await query.find(); - expect(resp.length).toBe(1); - }); + it_id('7d3da216-9582-40ee-a2fe-8316feaf5c0c')(it)( + 'fullTextSearch: $diacriticSensitive', + async () => { + await fullTextHelper(); + const query = new Parse.Query('TestObject'); + query.fullText('subject', 'CAFÉ', { diacriticSensitive: true }); + const resp = await query.find(); + expect(resp.length).toBe(1); + } + ); - it_id('dade10c8-2b9c-4f43-bb3f-a13bbd82ac22')(it)('fullTextSearch: $search, invalid input', async () => { - await fullTextHelper(); - const invalidQuery = async () => { - const where = { - subject: { - $text: { - $search: true, + it_id('dade10c8-2b9c-4f43-bb3f-a13bbd82ac22')(it)( + 'fullTextSearch: $search, invalid input', + async () => { + await fullTextHelper(); + const invalidQuery = async () => { + const where = { + subject: { + $text: { + $search: true, + }, }, - }, + }; + try { + await request({ + method: 'POST', + url: 'http://localhost:8378/1/classes/TestObject', + body: { where, _method: 'GET' }, + headers: { + 'X-Parse-Application-Id': 'test', + 'X-Parse-REST-API-Key': 'test', + 'Content-Type': 'application/json', + }, + }); + } catch (e) { + throw new Parse.Error(e.data.code, e.data.error); + } }; - try { - await request({ - method: 'POST', - url: 'http://localhost:8378/1/classes/TestObject', - body: { where, _method: 'GET' }, - headers: { - 'X-Parse-Application-Id': 'test', - 'X-Parse-REST-API-Key': 'test', - 'Content-Type': 'application/json', - }, - }); - } catch (e) { - throw new Parse.Error(e.data.code, e.data.error); - } - }; - await expectAsync(invalidQuery()).toBeRejectedWith( - new Parse.Error(Parse.Error.INVALID_JSON, 'bad $text: $search, should be object') - ); - }); + await expectAsync(invalidQuery()).toBeRejectedWith( + new Parse.Error(Parse.Error.INVALID_JSON, 'bad $text: $search, should be object') + ); + } + ); - it_id('ff7c6b1c-4712-4847-bb76-f4e1f641f7b5')(it)('fullTextSearch: $language, invalid input', async () => { - await fullTextHelper(); - const query = new Parse.Query('TestObject'); - query.fullText('subject', 'leche', { language: true }); - await expectAsync(query.find()).toBeRejectedWith( - new Parse.Error(Parse.Error.INVALID_JSON, 'bad $text: $language, should be string') - ); - }); + it_id('ff7c6b1c-4712-4847-bb76-f4e1f641f7b5')(it)( + 'fullTextSearch: $language, invalid input', + async () => { + await fullTextHelper(); + const query = new Parse.Query('TestObject'); + query.fullText('subject', 'leche', { language: true }); + await expectAsync(query.find()).toBeRejectedWith( + new Parse.Error(Parse.Error.INVALID_JSON, 'bad $text: $language, should be string') + ); + } + ); - it_id('de262dbc-ec75-4ec6-9217-fbb90146c272')(it)('fullTextSearch: $caseSensitive, invalid input', async () => { - await fullTextHelper(); - const query = new Parse.Query('TestObject'); - query.fullText('subject', 'leche', { caseSensitive: 'string' }); - await expectAsync(query.find()).toBeRejectedWith( - new Parse.Error(Parse.Error.INVALID_JSON, 'bad $text: $caseSensitive, should be boolean') - ); - }); + it_id('de262dbc-ec75-4ec6-9217-fbb90146c272')(it)( + 'fullTextSearch: $caseSensitive, invalid input', + async () => { + await fullTextHelper(); + const query = new Parse.Query('TestObject'); + query.fullText('subject', 'leche', { caseSensitive: 'string' }); + await expectAsync(query.find()).toBeRejectedWith( + new Parse.Error(Parse.Error.INVALID_JSON, 'bad $text: $caseSensitive, should be boolean') + ); + } + ); - it_id('b7b7b3a9-8d6c-4f98-a0ff-0113593d06d4')(it)('fullTextSearch: $diacriticSensitive, invalid input', async () => { - await fullTextHelper(); - const query = new Parse.Query('TestObject'); - query.fullText('subject', 'leche', { diacriticSensitive: 'string' }); - await expectAsync(query.find()).toBeRejectedWith( - new Parse.Error(Parse.Error.INVALID_JSON, 'bad $text: $diacriticSensitive, should be boolean') - ); - }); + it_id('b7b7b3a9-8d6c-4f98-a0ff-0113593d06d4')(it)( + 'fullTextSearch: $diacriticSensitive, invalid input', + async () => { + await fullTextHelper(); + const query = new Parse.Query('TestObject'); + query.fullText('subject', 'leche', { diacriticSensitive: 'string' }); + await expectAsync(query.find()).toBeRejectedWith( + new Parse.Error( + Parse.Error.INVALID_JSON, + 'bad $text: $diacriticSensitive, should be boolean' + ) + ); + } + ); }); describe_only_db('mongo')('[mongodb] Parse.Query Full Text Search testing', () => { diff --git a/spec/ParseQuery.spec.js b/spec/ParseQuery.spec.js index e6f3b1e08a..757bde7be9 100644 --- a/spec/ParseQuery.spec.js +++ b/spec/ParseQuery.spec.js @@ -6,7 +6,8 @@ const Parse = require('parse/node'); const request = require('../lib/request'); -const ParseServerRESTController = require('../lib/ParseServerRESTController').ParseServerRESTController; +const ParseServerRESTController = require('../lib/ParseServerRESTController') + .ParseServerRESTController; const ParseServer = require('../lib/ParseServer').default; const masterKeyHeaders = { @@ -592,7 +593,9 @@ describe('Parse.Query testing', () => { }); }); - it_id('25bb35a6-e953-4d6d-a31c-66324d5ae076')(it)('containsAll object array queries', function (done) { + it_id('25bb35a6-e953-4d6d-a31c-66324d5ae076')(it)('containsAll object array queries', function ( + done + ) { const MessageSet = Parse.Object.extend({ className: 'MessageSet' }); const messageList = []; @@ -707,40 +710,43 @@ describe('Parse.Query testing', () => { }); }); - it_id('3ea6ae04-bcc2-453d-8817-4c64d059c2f6')(it)('containsAllStartingWith values must be all of type starting with regex', done => { - const object = new Parse.Object('Object'); - object.set('strings', ['the', 'brown', 'lazy', 'fox', 'jumps']); + it_id('3ea6ae04-bcc2-453d-8817-4c64d059c2f6')(it)( + 'containsAllStartingWith values must be all of type starting with regex', + done => { + const object = new Parse.Object('Object'); + object.set('strings', ['the', 'brown', 'lazy', 'fox', 'jumps']); - object - .save() - .then(() => { - equal(object.isNew(), false); + object + .save() + .then(() => { + equal(object.isNew(), false); - return request({ - url: Parse.serverURL + '/classes/Object', - qs: { - where: JSON.stringify({ - strings: { - $all: [ - { $regex: '^\\Qthe\\E' }, - { $regex: '^\\Qlazy\\E' }, - { $regex: '^\\Qfox\\E' }, - { $unknown: /unknown/ }, - ], - }, - }), - }, - headers: { - 'X-Parse-Application-Id': Parse.applicationId, - 'X-Parse-Javascript-Key': Parse.javaScriptKey, - 'Content-Type': 'application/json', - }, + return request({ + url: Parse.serverURL + '/classes/Object', + qs: { + where: JSON.stringify({ + strings: { + $all: [ + { $regex: '^\\Qthe\\E' }, + { $regex: '^\\Qlazy\\E' }, + { $regex: '^\\Qfox\\E' }, + { $unknown: /unknown/ }, + ], + }, + }), + }, + headers: { + 'X-Parse-Application-Id': Parse.applicationId, + 'X-Parse-Javascript-Key': Parse.javaScriptKey, + 'Content-Type': 'application/json', + }, + }); + }) + .then(done.fail, function () { + done(); }); - }) - .then(done.fail, function () { - done(); - }); - }); + } + ); it('containsAllStartingWith empty array values should return empty results', done => { const object = new Parse.Object('Object'); @@ -1670,47 +1676,53 @@ describe('Parse.Query testing', () => { .catch(done.fail); }); - it_id('65c8238d-cf02-49d0-a919-8a17f5a58280')(it)('can order on an object number field', function (done) { - const testSet = [ - { sortField: { value: 10 } }, - { sortField: { value: 1 } }, - { sortField: { value: 5 } }, - ]; - - const objects = testSet.map(e => new Parse.Object('Test', e)); - Parse.Object.saveAll(objects) - .then(() => new Parse.Query('Test').addDescending('sortField.value').first()) - .then(result => { - expect(result.get('sortField').value).toBe(10); - return new Parse.Query('Test').addAscending('sortField.value').first(); - }) - .then(result => { - expect(result.get('sortField').value).toBe(1); - done(); - }) - .catch(done.fail); - }); - - it_id('d8f0bead-b931-4d66-8b0c-28c5705e463c')(it)('can order on an object number field (level 2)', function (done) { - const testSet = [ - { sortField: { value: { field: 10 } } }, - { sortField: { value: { field: 1 } } }, - { sortField: { value: { field: 5 } } }, - ]; - - const objects = testSet.map(e => new Parse.Object('Test', e)); - Parse.Object.saveAll(objects) - .then(() => new Parse.Query('Test').addDescending('sortField.value.field').first()) - .then(result => { - expect(result.get('sortField').value.field).toBe(10); - return new Parse.Query('Test').addAscending('sortField.value.field').first(); - }) - .then(result => { - expect(result.get('sortField').value.field).toBe(1); - done(); - }) - .catch(done.fail); - }); + it_id('65c8238d-cf02-49d0-a919-8a17f5a58280')(it)( + 'can order on an object number field', + function (done) { + const testSet = [ + { sortField: { value: 10 } }, + { sortField: { value: 1 } }, + { sortField: { value: 5 } }, + ]; + + const objects = testSet.map(e => new Parse.Object('Test', e)); + Parse.Object.saveAll(objects) + .then(() => new Parse.Query('Test').addDescending('sortField.value').first()) + .then(result => { + expect(result.get('sortField').value).toBe(10); + return new Parse.Query('Test').addAscending('sortField.value').first(); + }) + .then(result => { + expect(result.get('sortField').value).toBe(1); + done(); + }) + .catch(done.fail); + } + ); + + it_id('d8f0bead-b931-4d66-8b0c-28c5705e463c')(it)( + 'can order on an object number field (level 2)', + function (done) { + const testSet = [ + { sortField: { value: { field: 10 } } }, + { sortField: { value: { field: 1 } } }, + { sortField: { value: { field: 5 } } }, + ]; + + const objects = testSet.map(e => new Parse.Object('Test', e)); + Parse.Object.saveAll(objects) + .then(() => new Parse.Query('Test').addDescending('sortField.value.field').first()) + .then(result => { + expect(result.get('sortField').value.field).toBe(10); + return new Parse.Query('Test').addAscending('sortField.value.field').first(); + }) + .then(result => { + expect(result.get('sortField').value.field).toBe(1); + done(); + }) + .catch(done.fail); + } + ); it('order by ascending number then descending string', function (done) { const strings = ['a', 'b', 'c', 'd']; @@ -2113,30 +2125,33 @@ describe('Parse.Query testing', () => { .then(done); }); - it_id('823852f6-1de5-45ba-a2b9-ed952fcc6012')(it)('Use a regex that requires all modifiers', function (done) { - const thing = new TestObject(); - thing.set('myString', 'PArSe\nCom'); - Parse.Object.saveAll([thing]).then(function () { - const query = new Parse.Query(TestObject); - query.matches( - 'myString', - "parse # First fragment. We'll write this in one case but match insensitively\n" + - '.com # Second fragment. This can be separated by any character, including newline;' + - 'however, this comment must end with a newline to recognize it as a comment\n', - 'mixs' - ); - query.find().then( - function (results) { - equal(results.length, 1); - done(); - }, - function (err) { - jfail(err); - done(); - } - ); - }); - }); + it_id('823852f6-1de5-45ba-a2b9-ed952fcc6012')(it)( + 'Use a regex that requires all modifiers', + function (done) { + const thing = new TestObject(); + thing.set('myString', 'PArSe\nCom'); + Parse.Object.saveAll([thing]).then(function () { + const query = new Parse.Query(TestObject); + query.matches( + 'myString', + "parse # First fragment. We'll write this in one case but match insensitively\n" + + '.com # Second fragment. This can be separated by any character, including newline;' + + 'however, this comment must end with a newline to recognize it as a comment\n', + 'mixs' + ); + query.find().then( + function (results) { + equal(results.length, 1); + done(); + }, + function (err) { + jfail(err); + done(); + } + ); + }); + } + ); it('Regular expression constructor includes modifiers inline', function (done) { const thing = new TestObject(); @@ -4005,51 +4020,54 @@ describe('Parse.Query testing', () => { ); }); - it_id('7079f0ef-47b3-4a1e-aac0-32654dadaa27')(it)('should properly interpret a query v2', done => { - const user = new Parse.User(); - user.set('username', 'foo'); - user.set('password', 'bar'); - return user - .save() - .then(user => { - const objIdQuery = new Parse.Query('_User').equalTo('objectId', user.id); - const blockedUserQuery = user.relation('blockedUsers').query(); - - const aResponseQuery = new Parse.Query('MatchRelationshipActivityResponse'); - aResponseQuery.equalTo('userA', user); - aResponseQuery.equalTo('userAResponse', 1); - - const bResponseQuery = new Parse.Query('MatchRelationshipActivityResponse'); - bResponseQuery.equalTo('userB', user); - bResponseQuery.equalTo('userBResponse', 1); - - const matchOr = Parse.Query.or(aResponseQuery, bResponseQuery); - const matchRelationshipA = new Parse.Query('_User'); - matchRelationshipA.matchesKeyInQuery('objectId', 'userAObjectId', matchOr); - const matchRelationshipB = new Parse.Query('_User'); - matchRelationshipB.matchesKeyInQuery('objectId', 'userBObjectId', matchOr); - - const orQuery = Parse.Query.or( - objIdQuery, - blockedUserQuery, - matchRelationshipA, - matchRelationshipB + it_id('7079f0ef-47b3-4a1e-aac0-32654dadaa27')(it)( + 'should properly interpret a query v2', + done => { + const user = new Parse.User(); + user.set('username', 'foo'); + user.set('password', 'bar'); + return user + .save() + .then(user => { + const objIdQuery = new Parse.Query('_User').equalTo('objectId', user.id); + const blockedUserQuery = user.relation('blockedUsers').query(); + + const aResponseQuery = new Parse.Query('MatchRelationshipActivityResponse'); + aResponseQuery.equalTo('userA', user); + aResponseQuery.equalTo('userAResponse', 1); + + const bResponseQuery = new Parse.Query('MatchRelationshipActivityResponse'); + bResponseQuery.equalTo('userB', user); + bResponseQuery.equalTo('userBResponse', 1); + + const matchOr = Parse.Query.or(aResponseQuery, bResponseQuery); + const matchRelationshipA = new Parse.Query('_User'); + matchRelationshipA.matchesKeyInQuery('objectId', 'userAObjectId', matchOr); + const matchRelationshipB = new Parse.Query('_User'); + matchRelationshipB.matchesKeyInQuery('objectId', 'userBObjectId', matchOr); + + const orQuery = Parse.Query.or( + objIdQuery, + blockedUserQuery, + matchRelationshipA, + matchRelationshipB + ); + const query = new Parse.Query('_User'); + query.doesNotMatchQuery('objectId', orQuery); + return query.find(); + }) + .then( + () => { + done(); + }, + err => { + jfail(err); + fail('should not fail'); + done(); + } ); - const query = new Parse.Query('_User'); - query.doesNotMatchQuery('objectId', orQuery); - return query.find(); - }) - .then( - () => { - done(); - }, - err => { - jfail(err); - fail('should not fail'); - done(); - } - ); - }); + } + ); it('should match a key in an array (#3195)', function (done) { const AuthorObject = Parse.Object.extend('Author'); @@ -4084,48 +4102,51 @@ describe('Parse.Query testing', () => { }); }); - it_id('d95818c0-9e3c-41e6-be20-e7bafb59eefb')(it)('should find objects with array of pointers', done => { - const objects = []; - while (objects.length != 5) { - const object = new Parse.Object('ContainedObject'); - object.set('index', objects.length); - objects.push(object); - } + it_id('d95818c0-9e3c-41e6-be20-e7bafb59eefb')(it)( + 'should find objects with array of pointers', + done => { + const objects = []; + while (objects.length != 5) { + const object = new Parse.Object('ContainedObject'); + object.set('index', objects.length); + objects.push(object); + } - Parse.Object.saveAll(objects) - .then(objects => { - const container = new Parse.Object('Container'); - const pointers = objects.map(obj => { - return { - __type: 'Pointer', - className: 'ContainedObject', - objectId: obj.id, - }; + Parse.Object.saveAll(objects) + .then(objects => { + const container = new Parse.Object('Container'); + const pointers = objects.map(obj => { + return { + __type: 'Pointer', + className: 'ContainedObject', + objectId: obj.id, + }; + }); + container.set('objects', pointers); + const container2 = new Parse.Object('Container'); + container2.set('objects', pointers.slice(2, 3)); + return Parse.Object.saveAll([container, container2]); + }) + .then(() => { + const inQuery = new Parse.Query('ContainedObject'); + inQuery.greaterThanOrEqualTo('index', 1); + const query = new Parse.Query('Container'); + query.matchesQuery('objects', inQuery); + return query.find(); + }) + .then(results => { + if (results) { + expect(results.length).toBe(2); + } + done(); + }) + .catch(err => { + jfail(err); + fail('should not fail'); + done(); }); - container.set('objects', pointers); - const container2 = new Parse.Object('Container'); - container2.set('objects', pointers.slice(2, 3)); - return Parse.Object.saveAll([container, container2]); - }) - .then(() => { - const inQuery = new Parse.Query('ContainedObject'); - inQuery.greaterThanOrEqualTo('index', 1); - const query = new Parse.Query('Container'); - query.matchesQuery('objects', inQuery); - return query.find(); - }) - .then(results => { - if (results) { - expect(results.length).toBe(2); - } - done(); - }) - .catch(err => { - jfail(err); - fail('should not fail'); - done(); - }); - }); + } + ); it('query with two OR subqueries (regression test #1259)', done => { const relatedObject = new Parse.Object('Class2'); @@ -5015,48 +5036,51 @@ describe('Parse.Query testing', () => { equal(results[0].get('name'), group2.get('name')); }); - it_id('8886b994-fbb8-487d-a863-43bbd2b24b73')(it)('withJSON supports geoWithin.centerSphere', done => { - const inbound = new Parse.GeoPoint(1.5, 1.5); - const onbound = new Parse.GeoPoint(10, 10); - const outbound = new Parse.GeoPoint(20, 20); - const obj1 = new Parse.Object('TestObject', { location: inbound }); - const obj2 = new Parse.Object('TestObject', { location: onbound }); - const obj3 = new Parse.Object('TestObject', { location: outbound }); - const center = new Parse.GeoPoint(0, 0); - const distanceInKilometers = 1569 + 1; // 1569km is the approximate distance between {0, 0} and {10, 10}. - Parse.Object.saveAll([obj1, obj2, obj3]) - .then(() => { - const q = new Parse.Query(TestObject); - const jsonQ = q.toJSON(); - jsonQ.where.location = { - $geoWithin: { - $centerSphere: [center, distanceInKilometers / 6371.0], - }, - }; - q.withJSON(jsonQ); - return q.find(); - }) - .then(results => { - equal(results.length, 2); - const q = new Parse.Query(TestObject); - const jsonQ = q.toJSON(); - jsonQ.where.location = { - $geoWithin: { - $centerSphere: [[0, 0], distanceInKilometers / 6371.0], - }, - }; - q.withJSON(jsonQ); - return q.find(); - }) - .then(results => { - equal(results.length, 2); - done(); - }) - .catch(error => { - fail(error); - done(); - }); - }); + it_id('8886b994-fbb8-487d-a863-43bbd2b24b73')(it)( + 'withJSON supports geoWithin.centerSphere', + done => { + const inbound = new Parse.GeoPoint(1.5, 1.5); + const onbound = new Parse.GeoPoint(10, 10); + const outbound = new Parse.GeoPoint(20, 20); + const obj1 = new Parse.Object('TestObject', { location: inbound }); + const obj2 = new Parse.Object('TestObject', { location: onbound }); + const obj3 = new Parse.Object('TestObject', { location: outbound }); + const center = new Parse.GeoPoint(0, 0); + const distanceInKilometers = 1569 + 1; // 1569km is the approximate distance between {0, 0} and {10, 10}. + Parse.Object.saveAll([obj1, obj2, obj3]) + .then(() => { + const q = new Parse.Query(TestObject); + const jsonQ = q.toJSON(); + jsonQ.where.location = { + $geoWithin: { + $centerSphere: [center, distanceInKilometers / 6371.0], + }, + }; + q.withJSON(jsonQ); + return q.find(); + }) + .then(results => { + equal(results.length, 2); + const q = new Parse.Query(TestObject); + const jsonQ = q.toJSON(); + jsonQ.where.location = { + $geoWithin: { + $centerSphere: [[0, 0], distanceInKilometers / 6371.0], + }, + }; + q.withJSON(jsonQ); + return q.find(); + }) + .then(results => { + equal(results.length, 2); + done(); + }) + .catch(error => { + fail(error); + done(); + }); + } + ); it('withJSON with geoWithin.centerSphere fails without parameters', done => { const q = new Parse.Query(TestObject); @@ -5116,37 +5140,40 @@ describe('Parse.Query testing', () => { .catch(() => done()); }); - it_id('02d4e7e6-859a-4ab6-878d-135ccc77040e')(it)('can add new config to existing config', async () => { - await request({ - method: 'PUT', - url: 'http://localhost:8378/1/config', - json: true, - body: { - params: { - files: [{ __type: 'File', name: 'name', url: 'http://url' }], + it_id('02d4e7e6-859a-4ab6-878d-135ccc77040e')(it)( + 'can add new config to existing config', + async () => { + await request({ + method: 'PUT', + url: 'http://localhost:8378/1/config', + json: true, + body: { + params: { + files: [{ __type: 'File', name: 'name', url: 'http://url' }], + }, }, - }, - headers: masterKeyHeaders, - }); + headers: masterKeyHeaders, + }); - await request({ - method: 'PUT', - url: 'http://localhost:8378/1/config', - json: true, - body: { - params: { newConfig: 'good' }, - }, - headers: masterKeyHeaders, - }); + await request({ + method: 'PUT', + url: 'http://localhost:8378/1/config', + json: true, + body: { + params: { newConfig: 'good' }, + }, + headers: masterKeyHeaders, + }); - const result = await Parse.Config.get(); - equal(result.get('files')[0].toJSON(), { - __type: 'File', - name: 'name', - url: 'http://url', - }); - equal(result.get('newConfig'), 'good'); - }); + const result = await Parse.Config.get(); + equal(result.get('files')[0].toJSON(), { + __type: 'File', + name: 'name', + url: 'http://url', + }); + equal(result.get('newConfig'), 'good'); + } + ); it('can set object type key', async () => { const data = { bar: true, baz: 100 }; @@ -5284,7 +5311,10 @@ describe('Parse.Query testing', () => { }); Parse.CoreManager.setRESTController( - ParseServerRESTController(Parse.applicationId, ParseServer.promiseRouter({ appId: Parse.applicationId })) + ParseServerRESTController( + Parse.applicationId, + ParseServer.promiseRouter({ appId: Parse.applicationId }) + ) ); const user = new Parse.User(); @@ -5297,13 +5327,14 @@ describe('Parse.Query testing', () => { score.set('score', 1); await score.save(); - await new Parse.Query('_User') - .equalTo('objectId', user.id) - .eachBatch(async ([user]) => { + await new Parse.Query('_User').equalTo('objectId', user.id).eachBatch( + async ([user]) => { const score = await new Parse.Query('Score') .equalTo('player', user) .distinct('score', { useMasterKey: true }); expect(score).toEqual([1]); - }, { useMasterKey: true }); + }, + { useMasterKey: true } + ); }); }); diff --git a/spec/ParseRole.spec.js b/spec/ParseRole.spec.js index 35a91c6c15..0a9eb6df92 100644 --- a/spec/ParseRole.spec.js +++ b/spec/ParseRole.spec.js @@ -126,76 +126,79 @@ describe('Parse Role testing', () => { ); }); - it_id('b03abe32-e8e4-4666-9b81-9c804aa53400')(it)('should not recursively load the same role multiple times', done => { - const rootRole = 'RootRole'; - const roleNames = ['FooRole', 'BarRole', 'BazRole']; - const allRoles = [rootRole].concat(roleNames); - - const roleObjs = {}; - const createAllRoles = function (user) { - const promises = allRoles.map(function (roleName) { - return createRole(roleName, null, user).then(function (roleObj) { - roleObjs[roleName] = roleObj; - return roleObj; + it_id('b03abe32-e8e4-4666-9b81-9c804aa53400')(it)( + 'should not recursively load the same role multiple times', + done => { + const rootRole = 'RootRole'; + const roleNames = ['FooRole', 'BarRole', 'BazRole']; + const allRoles = [rootRole].concat(roleNames); + + const roleObjs = {}; + const createAllRoles = function (user) { + const promises = allRoles.map(function (roleName) { + return createRole(roleName, null, user).then(function (roleObj) { + roleObjs[roleName] = roleObj; + return roleObj; + }); }); - }); - return Promise.all(promises); - }; + return Promise.all(promises); + }; - const restExecute = spyOn(RestQuery._UnsafeRestQuery.prototype, 'execute').and.callThrough(); + const restExecute = spyOn(RestQuery._UnsafeRestQuery.prototype, 'execute').and.callThrough(); - let user, auth, getAllRolesSpy; - createTestUser() - .then(newUser => { - user = newUser; - return createAllRoles(user); - }) - .then(roles => { - const rootRoleObj = roleObjs[rootRole]; - roles.forEach(function (role, i) { - // Add all roles to the RootRole - if (role.id !== rootRoleObj.id) { - role.relation('roles').add(rootRoleObj); - } - // Add all "roleNames" roles to the previous role - if (i > 0) { - role.relation('roles').add(roles[i - 1]); - } - }); + let user, auth, getAllRolesSpy; + createTestUser() + .then(newUser => { + user = newUser; + return createAllRoles(user); + }) + .then(roles => { + const rootRoleObj = roleObjs[rootRole]; + roles.forEach(function (role, i) { + // Add all roles to the RootRole + if (role.id !== rootRoleObj.id) { + role.relation('roles').add(rootRoleObj); + } + // Add all "roleNames" roles to the previous role + if (i > 0) { + role.relation('roles').add(roles[i - 1]); + } + }); - return Parse.Object.saveAll(roles, { useMasterKey: true }); - }) - .then(() => { - auth = new Auth({ - config: Config.get('test'), - isMaster: true, - user: user, - }); - getAllRolesSpy = spyOn(auth, '_getAllRolesNamesForRoleIds').and.callThrough(); + return Parse.Object.saveAll(roles, { useMasterKey: true }); + }) + .then(() => { + auth = new Auth({ + config: Config.get('test'), + isMaster: true, + user: user, + }); + getAllRolesSpy = spyOn(auth, '_getAllRolesNamesForRoleIds').and.callThrough(); - return auth._loadRoles(); - }) - .then(roles => { - expect(roles.length).toEqual(4); + return auth._loadRoles(); + }) + .then(roles => { + expect(roles.length).toEqual(4); - allRoles.forEach(function (name) { - expect(roles.indexOf('role:' + name)).not.toBe(-1); - }); + allRoles.forEach(function (name) { + expect(roles.indexOf('role:' + name)).not.toBe(-1); + }); - // 1 Query for the initial setup - // 1 query for the parent roles - expect(restExecute.calls.count()).toEqual(2); + // 1 Query for the initial setup + // 1 query for the parent roles + expect(restExecute.calls.count()).toEqual(2); - // 1 call for the 1st layer of roles - // 1 call for the 2nd layer - expect(getAllRolesSpy.calls.count()).toEqual(2); - done(); - }) - .catch(() => { - fail('should succeed'); - done(); - }); - }); + // 1 call for the 1st layer of roles + // 1 call for the 2nd layer + expect(getAllRolesSpy.calls.count()).toEqual(2); + done(); + }) + .catch(() => { + fail('should succeed'); + done(); + }); + } + ); it('should recursively load roles', done => { testLoadRoles(Config.get('test'), done); diff --git a/spec/ParseServerRESTController.spec.js b/spec/ParseServerRESTController.spec.js index fbc244ab87..1001f7f204 100644 --- a/spec/ParseServerRESTController.spec.js +++ b/spec/ParseServerRESTController.spec.js @@ -163,9 +163,7 @@ describe('ParseServerRESTController', () => { const results = await query.find(); expect(createSpy.calls.count()).toBe(2); for (let i = 0; i + 1 < createSpy.calls.length; i = i + 2) { - expect(createSpy.calls.argsFor(i)[3]).toBe( - createSpy.calls.argsFor(i + 1)[3] - ); + expect(createSpy.calls.argsFor(i)[3]).toBe(createSpy.calls.argsFor(i + 1)[3]); } expect(results.map(result => result.get('key')).sort()).toEqual(['value1', 'value2']); }); @@ -660,16 +658,23 @@ describe('ParseServerRESTController', () => { data: { alert: 'We return status!' }, where: { deviceType: 'ios' }, }; - const res = await RESTController.request('POST', 'batch', { - requests: [{ - method: 'POST', - path: '/push', - body: payload, - }], - }, { - useMasterKey: true, - returnStatus: true, - }); + const res = await RESTController.request( + 'POST', + 'batch', + { + requests: [ + { + method: 'POST', + path: '/push', + body: payload, + }, + ], + }, + { + useMasterKey: true, + returnStatus: true, + } + ); const pushStatusId = res[0]._headers['X-Parse-Push-Status-Id']; expect(pushStatusId).toBeDefined(); diff --git a/spec/ParseUser.spec.js b/spec/ParseUser.spec.js index 89686a794d..d1d5a71a20 100644 --- a/spec/ParseUser.spec.js +++ b/spec/ParseUser.spec.js @@ -588,7 +588,7 @@ describe('Parse.User testing', () => { } }); - fit('never locks himself up', async () => { + it('never locks himself up', async () => { const user = new Parse.User(); await user.signUp({ username: 'username', @@ -2257,68 +2257,80 @@ describe('Parse.User testing', () => { }); describe('case insensitive signup not allowed', () => { - it_id('464eddc2-7a46-413d-888e-b43b040f1511')(it)('signup should fail with duplicate case insensitive username with basic setter', async () => { - const user = new Parse.User(); - user.set('username', 'test1'); - user.set('password', 'test'); - await user.signUp(); - - const user2 = new Parse.User(); - user2.set('username', 'Test1'); - user2.set('password', 'test'); - await expectAsync(user2.signUp()).toBeRejectedWith( - new Parse.Error(Parse.Error.USERNAME_TAKEN, 'Account already exists for this username.') - ); - }); + it_id('464eddc2-7a46-413d-888e-b43b040f1511')(it)( + 'signup should fail with duplicate case insensitive username with basic setter', + async () => { + const user = new Parse.User(); + user.set('username', 'test1'); + user.set('password', 'test'); + await user.signUp(); - it_id('1cef005b-d5f0-4699-af0c-bb0af27d2437')(it)('signup should fail with duplicate case insensitive username with field specific setter', async () => { - const user = new Parse.User(); - user.setUsername('test1'); - user.setPassword('test'); - await user.signUp(); + const user2 = new Parse.User(); + user2.set('username', 'Test1'); + user2.set('password', 'test'); + await expectAsync(user2.signUp()).toBeRejectedWith( + new Parse.Error(Parse.Error.USERNAME_TAKEN, 'Account already exists for this username.') + ); + } + ); - const user2 = new Parse.User(); - user2.setUsername('Test1'); - user2.setPassword('test'); - await expectAsync(user2.signUp()).toBeRejectedWith( - new Parse.Error(Parse.Error.USERNAME_TAKEN, 'Account already exists for this username.') - ); - }); + it_id('1cef005b-d5f0-4699-af0c-bb0af27d2437')(it)( + 'signup should fail with duplicate case insensitive username with field specific setter', + async () => { + const user = new Parse.User(); + user.setUsername('test1'); + user.setPassword('test'); + await user.signUp(); - it_id('12735529-98d1-42c0-b437-3b47fe78ddde')(it)('signup should fail with duplicate case insensitive email', async () => { - const user = new Parse.User(); - user.setUsername('test1'); - user.setPassword('test'); - user.setEmail('test@example.com'); - await user.signUp(); + const user2 = new Parse.User(); + user2.setUsername('Test1'); + user2.setPassword('test'); + await expectAsync(user2.signUp()).toBeRejectedWith( + new Parse.Error(Parse.Error.USERNAME_TAKEN, 'Account already exists for this username.') + ); + } + ); - const user2 = new Parse.User(); - user2.setUsername('test2'); - user2.setPassword('test'); - user2.setEmail('Test@Example.Com'); - await expectAsync(user2.signUp()).toBeRejectedWith( - new Parse.Error(Parse.Error.EMAIL_TAKEN, 'Account already exists for this email address.') - ); - }); + it_id('12735529-98d1-42c0-b437-3b47fe78ddde')(it)( + 'signup should fail with duplicate case insensitive email', + async () => { + const user = new Parse.User(); + user.setUsername('test1'); + user.setPassword('test'); + user.setEmail('test@example.com'); + await user.signUp(); - it_id('66e51d52-2420-4b62-8a0d-c7e1b384763e')(it)('edit should fail with duplicate case insensitive email', async () => { - const user = new Parse.User(); - user.setUsername('test1'); - user.setPassword('test'); - user.setEmail('test@example.com'); - await user.signUp(); + const user2 = new Parse.User(); + user2.setUsername('test2'); + user2.setPassword('test'); + user2.setEmail('Test@Example.Com'); + await expectAsync(user2.signUp()).toBeRejectedWith( + new Parse.Error(Parse.Error.EMAIL_TAKEN, 'Account already exists for this email address.') + ); + } + ); - const user2 = new Parse.User(); - user2.setUsername('test2'); - user2.setPassword('test'); - user2.setEmail('Foo@Example.Com'); - await user2.signUp(); + it_id('66e51d52-2420-4b62-8a0d-c7e1b384763e')(it)( + 'edit should fail with duplicate case insensitive email', + async () => { + const user = new Parse.User(); + user.setUsername('test1'); + user.setPassword('test'); + user.setEmail('test@example.com'); + await user.signUp(); - user2.setEmail('Test@Example.Com'); - await expectAsync(user2.save()).toBeRejectedWith( - new Parse.Error(Parse.Error.EMAIL_TAKEN, 'Account already exists for this email address.') - ); - }); + const user2 = new Parse.User(); + user2.setUsername('test2'); + user2.setPassword('test'); + user2.setEmail('Foo@Example.Com'); + await user2.signUp(); + + user2.setEmail('Test@Example.Com'); + await expectAsync(user2.save()).toBeRejectedWith( + new Parse.Error(Parse.Error.EMAIL_TAKEN, 'Account already exists for this email address.') + ); + } + ); describe('anonymous users', () => { it('should not fail on case insensitive matches', async () => { @@ -2952,119 +2964,125 @@ describe('Parse.User testing', () => { }); }); - it_id('1be98368-19ac-4c77-8531-762a114f43fb')(it)('should send email when upgrading from anon', async done => { - await reconfigureServer(); - let emailCalled = false; - let emailOptions; - const emailAdapter = { - sendVerificationEmail: options => { - emailOptions = options; - emailCalled = true; - }, - sendPasswordResetEmail: () => Promise.resolve(), - sendMail: () => Promise.resolve(), - }; - await reconfigureServer({ - appName: 'unused', - verifyUserEmails: true, - emailAdapter: emailAdapter, - publicServerURL: 'http://localhost:8378/1', - }); - // Simulate anonymous user save - return request({ - method: 'POST', - url: 'http://localhost:8378/1/classes/_User', - headers: { - 'X-Parse-Application-Id': Parse.applicationId, - 'X-Parse-REST-API-Key': 'rest', - 'Content-Type': 'application/json', - }, - body: { - authData: { - anonymous: { id: '00000000-0000-0000-0000-000000000001' }, + it_id('1be98368-19ac-4c77-8531-762a114f43fb')(it)( + 'should send email when upgrading from anon', + async done => { + await reconfigureServer(); + let emailCalled = false; + let emailOptions; + const emailAdapter = { + sendVerificationEmail: options => { + emailOptions = options; + emailCalled = true; }, - }, - }) - .then(response => { - const user = response.data; - return request({ - method: 'PUT', - url: 'http://localhost:8378/1/classes/_User/' + user.objectId, - headers: { - 'X-Parse-Application-Id': Parse.applicationId, - 'X-Parse-Session-Token': user.sessionToken, - 'X-Parse-REST-API-Key': 'rest', - 'Content-Type': 'application/json', - }, - body: { - authData: { anonymous: null }, - username: 'user', - email: 'user@email.com', - password: 'password', + sendPasswordResetEmail: () => Promise.resolve(), + sendMail: () => Promise.resolve(), + }; + await reconfigureServer({ + appName: 'unused', + verifyUserEmails: true, + emailAdapter: emailAdapter, + publicServerURL: 'http://localhost:8378/1', + }); + // Simulate anonymous user save + return request({ + method: 'POST', + url: 'http://localhost:8378/1/classes/_User', + headers: { + 'X-Parse-Application-Id': Parse.applicationId, + 'X-Parse-REST-API-Key': 'rest', + 'Content-Type': 'application/json', + }, + body: { + authData: { + anonymous: { id: '00000000-0000-0000-0000-000000000001' }, }, - }); - }) - .then(() => jasmine.timeout()) - .then(() => { - expect(emailCalled).toBe(true); - expect(emailOptions).not.toBeUndefined(); - expect(emailOptions.user.get('email')).toEqual('user@email.com'); - done(); + }, }) - .catch(err => { - jfail(err); - fail('no request should fail: ' + JSON.stringify(err)); - done(); - }); - }); + .then(response => { + const user = response.data; + return request({ + method: 'PUT', + url: 'http://localhost:8378/1/classes/_User/' + user.objectId, + headers: { + 'X-Parse-Application-Id': Parse.applicationId, + 'X-Parse-Session-Token': user.sessionToken, + 'X-Parse-REST-API-Key': 'rest', + 'Content-Type': 'application/json', + }, + body: { + authData: { anonymous: null }, + username: 'user', + email: 'user@email.com', + password: 'password', + }, + }); + }) + .then(() => jasmine.timeout()) + .then(() => { + expect(emailCalled).toBe(true); + expect(emailOptions).not.toBeUndefined(); + expect(emailOptions.user.get('email')).toEqual('user@email.com'); + done(); + }) + .catch(err => { + jfail(err); + fail('no request should fail: ' + JSON.stringify(err)); + done(); + }); + } + ); - it_id('bf668670-39fa-44d3-a9a9-cad52f36d272')(it)('should not send email when email is not a string', async done => { - let emailCalled = false; - let emailOptions; - const emailAdapter = { - sendVerificationEmail: options => { - emailOptions = options; - emailCalled = true; - }, - sendPasswordResetEmail: () => Promise.resolve(), - sendMail: () => Promise.resolve(), - }; - await reconfigureServer({ - appName: 'unused', - verifyUserEmails: true, - emailAdapter: emailAdapter, - publicServerURL: 'http://localhost:8378/1', - }); - const user = new Parse.User(); - user.set('username', 'asdf@jkl.com'); - user.set('password', 'zxcv'); - user.set('email', 'asdf@jkl.com'); - await user.signUp(); - request({ - method: 'POST', - url: 'http://localhost:8378/1/requestPasswordReset', - headers: { - 'X-Parse-Application-Id': Parse.applicationId, - 'X-Parse-Session-Token': user.sessionToken, - 'X-Parse-REST-API-Key': 'rest', - 'Content-Type': 'application/json', - }, - body: { - email: { $regex: '^asd' }, - }, - }) - .then(res => { - fail('no request should succeed: ' + JSON.stringify(res)); - done(); - }) - .catch(err => { - expect(emailCalled).toBeTruthy(); - expect(emailOptions).toBeDefined(); - expect(err.status).toBe(400); - expect(err.text).toMatch('{"code":125,"error":"you must provide a valid email string"}'); - done(); + it_id('bf668670-39fa-44d3-a9a9-cad52f36d272')(it)( + 'should not send email when email is not a string', + async done => { + let emailCalled = false; + let emailOptions; + const emailAdapter = { + sendVerificationEmail: options => { + emailOptions = options; + emailCalled = true; + }, + sendPasswordResetEmail: () => Promise.resolve(), + sendMail: () => Promise.resolve(), + }; + await reconfigureServer({ + appName: 'unused', + verifyUserEmails: true, + emailAdapter: emailAdapter, + publicServerURL: 'http://localhost:8378/1', }); - }); + const user = new Parse.User(); + user.set('username', 'asdf@jkl.com'); + user.set('password', 'zxcv'); + user.set('email', 'asdf@jkl.com'); + await user.signUp(); + request({ + method: 'POST', + url: 'http://localhost:8378/1/requestPasswordReset', + headers: { + 'X-Parse-Application-Id': Parse.applicationId, + 'X-Parse-Session-Token': user.sessionToken, + 'X-Parse-REST-API-Key': 'rest', + 'Content-Type': 'application/json', + }, + body: { + email: { $regex: '^asd' }, + }, + }) + .then(res => { + fail('no request should succeed: ' + JSON.stringify(res)); + done(); + }) + .catch(err => { + expect(emailCalled).toBeTruthy(); + expect(emailOptions).toBeDefined(); + expect(err.status).toBe(400); + expect(err.text).toMatch('{"code":125,"error":"you must provide a valid email string"}'); + done(); + }); + } + ); it('should aftersave with full object', done => { let hit = 0; diff --git a/spec/PasswordPolicy.spec.js b/spec/PasswordPolicy.spec.js index b39790cfba..f70390f430 100644 --- a/spec/PasswordPolicy.spec.js +++ b/spec/PasswordPolicy.spec.js @@ -3,65 +3,68 @@ const request = require('../lib/request'); describe('Password Policy: ', () => { - it_id('b400a867-9f05-496f-af79-933aa588dde5')(it)('should show the invalid link page if the user clicks on the password reset link after the token expires', done => { - const user = new Parse.User(); - let sendEmailOptions; - const emailAdapter = { - sendVerificationEmail: () => Promise.resolve(), - sendPasswordResetEmail: options => { - sendEmailOptions = options; - }, - sendMail: () => {}, - }; - reconfigureServer({ - appName: 'passwordPolicy', - emailAdapter: emailAdapter, - passwordPolicy: { - resetTokenValidityDuration: 0.5, // 0.5 second - }, - publicServerURL: 'http://localhost:8378/1', - }) - .then(() => { - user.setUsername('testResetTokenValidity'); - user.setPassword('original'); - user.set('email', 'user@parse.com'); - return user.signUp(); + it_id('b400a867-9f05-496f-af79-933aa588dde5')(it)( + 'should show the invalid link page if the user clicks on the password reset link after the token expires', + done => { + const user = new Parse.User(); + let sendEmailOptions; + const emailAdapter = { + sendVerificationEmail: () => Promise.resolve(), + sendPasswordResetEmail: options => { + sendEmailOptions = options; + }, + sendMail: () => {}, + }; + reconfigureServer({ + appName: 'passwordPolicy', + emailAdapter: emailAdapter, + passwordPolicy: { + resetTokenValidityDuration: 0.5, // 0.5 second + }, + publicServerURL: 'http://localhost:8378/1', }) - .then(() => { - Parse.User.requestPasswordReset('user@parse.com').catch(err => { + .then(() => { + user.setUsername('testResetTokenValidity'); + user.setPassword('original'); + user.set('email', 'user@parse.com'); + return user.signUp(); + }) + .then(() => { + Parse.User.requestPasswordReset('user@parse.com').catch(err => { + jfail(err); + fail('Reset password request should not fail'); + done(); + }); + }) + .then(() => { + // wait for a bit more than the validity duration set + setTimeout(() => { + expect(sendEmailOptions).not.toBeUndefined(); + + request({ + url: sendEmailOptions.link, + followRedirects: false, + simple: false, + resolveWithFullResponse: true, + }) + .then(response => { + expect(response.status).toEqual(302); + expect(response.text).toEqual( + 'Found. Redirecting to http://localhost:8378/1/apps/invalid_link.html' + ); + done(); + }) + .catch(error => { + fail(error); + }); + }, 1000); + }) + .catch(err => { jfail(err); - fail('Reset password request should not fail'); done(); }); - }) - .then(() => { - // wait for a bit more than the validity duration set - setTimeout(() => { - expect(sendEmailOptions).not.toBeUndefined(); - - request({ - url: sendEmailOptions.link, - followRedirects: false, - simple: false, - resolveWithFullResponse: true, - }) - .then(response => { - expect(response.status).toEqual(302); - expect(response.text).toEqual( - 'Found. Redirecting to http://localhost:8378/1/apps/invalid_link.html' - ); - done(); - }) - .catch(error => { - fail(error); - }); - }, 1000); - }) - .catch(err => { - jfail(err); - done(); - }); - }); + } + ); it('should show the reset password page if the user clicks on the password reset link before the token expires', done => { const user = new Parse.User(); @@ -150,34 +153,37 @@ describe('Password Policy: ', () => { done(); }); - it_id('7d98e1f2-ae89-4038-9ea7-5254854ea42e')(it)('should keep reset token with resetTokenReuseIfValid', async done => { - const sendEmailOptions = []; - const emailAdapter = { - sendVerificationEmail: () => Promise.resolve(), - sendPasswordResetEmail: options => { - sendEmailOptions.push(options); - }, - sendMail: () => {}, - }; - await reconfigureServer({ - appName: 'passwordPolicy', - emailAdapter: emailAdapter, - passwordPolicy: { - resetTokenValidityDuration: 5 * 60, // 5 minutes - resetTokenReuseIfValid: true, - }, - publicServerURL: 'http://localhost:8378/1', - }); - const user = new Parse.User(); - user.setUsername('testResetTokenValidity'); - user.setPassword('original'); - user.set('email', 'user@example.com'); - await user.signUp(); - await Parse.User.requestPasswordReset('user@example.com'); - await Parse.User.requestPasswordReset('user@example.com'); - expect(sendEmailOptions[0].link).toBe(sendEmailOptions[1].link); - done(); - }); + it_id('7d98e1f2-ae89-4038-9ea7-5254854ea42e')(it)( + 'should keep reset token with resetTokenReuseIfValid', + async done => { + const sendEmailOptions = []; + const emailAdapter = { + sendVerificationEmail: () => Promise.resolve(), + sendPasswordResetEmail: options => { + sendEmailOptions.push(options); + }, + sendMail: () => {}, + }; + await reconfigureServer({ + appName: 'passwordPolicy', + emailAdapter: emailAdapter, + passwordPolicy: { + resetTokenValidityDuration: 5 * 60, // 5 minutes + resetTokenReuseIfValid: true, + }, + publicServerURL: 'http://localhost:8378/1', + }); + const user = new Parse.User(); + user.setUsername('testResetTokenValidity'); + user.setPassword('original'); + user.set('email', 'user@example.com'); + await user.signUp(); + await Parse.User.requestPasswordReset('user@example.com'); + await Parse.User.requestPasswordReset('user@example.com'); + expect(sendEmailOptions[0].link).toBe(sendEmailOptions[1].link); + done(); + } + ); it('should throw with invalid resetTokenReuseIfValid', async done => { const sendEmailOptions = []; @@ -1164,248 +1170,260 @@ describe('Password Policy: ', () => { }); }); - it_id('d7d0a93e-efe6-48c0-b622-0f7fb570ccc1')(it)('should succeed if logged in before password expires', done => { - const user = new Parse.User(); - reconfigureServer({ - appName: 'passwordPolicy', - passwordPolicy: { - maxPasswordAge: 1, // 1 day - }, - publicServerURL: 'http://localhost:8378/1', - }).then(() => { - user.setUsername('user1'); - user.setPassword('user1'); - user.set('email', 'user1@parse.com'); - user - .signUp() - .then(() => { - Parse.User.logIn('user1', 'user1') - .then(() => { - done(); - }) - .catch(error => { - jfail(error); - fail('Login should have succeeded before password expiry.'); - done(); - }); - }) - .catch(error => { - jfail(error); - fail('Signup failed.'); - done(); - }); - }); - }); - - it_id('22428408-8763-445d-9833-2b2d92008f62')(it)('should fail if logged in after password expires', done => { - const user = new Parse.User(); - reconfigureServer({ - appName: 'passwordPolicy', - passwordPolicy: { - maxPasswordAge: 0.5 / (24 * 60 * 60), // 0.5 sec - }, - publicServerURL: 'http://localhost:8378/1', - }).then(() => { - user.setUsername('user1'); - user.setPassword('user1'); - user.set('email', 'user1@parse.com'); - user - .signUp() - .then(() => { - // wait for a bit more than the validity duration set - setTimeout(() => { + it_id('d7d0a93e-efe6-48c0-b622-0f7fb570ccc1')(it)( + 'should succeed if logged in before password expires', + done => { + const user = new Parse.User(); + reconfigureServer({ + appName: 'passwordPolicy', + passwordPolicy: { + maxPasswordAge: 1, // 1 day + }, + publicServerURL: 'http://localhost:8378/1', + }).then(() => { + user.setUsername('user1'); + user.setPassword('user1'); + user.set('email', 'user1@parse.com'); + user + .signUp() + .then(() => { Parse.User.logIn('user1', 'user1') .then(() => { - fail('logIn should have failed'); done(); }) .catch(error => { - expect(error.code).toEqual(Parse.Error.OBJECT_NOT_FOUND); - expect(error.message).toEqual( - 'Your password has expired. Please reset your password.' - ); + jfail(error); + fail('Login should have succeeded before password expiry.'); done(); }); - }, 1000); - }) - .catch(error => { - jfail(error); - fail('Signup failed.'); - done(); - }); - }); - }); - - it_id('cc97a109-e35f-4f94-b942-3a6134921cdd')(it)('should apply password expiry policy to existing user upon first login after policy is enabled', done => { - const user = new Parse.User(); - reconfigureServer({ - appName: 'passwordPolicy', - publicServerURL: 'http://localhost:8378/1', - }).then(() => { - user.setUsername('user1'); - user.setPassword('user1'); - user.set('email', 'user1@parse.com'); - user - .signUp() - .then(() => { - Parse.User.logOut() - .then(() => { - reconfigureServer({ - appName: 'passwordPolicy', - passwordPolicy: { - maxPasswordAge: 0.5 / (24 * 60 * 60), // 0.5 sec - }, - publicServerURL: 'http://localhost:8378/1', - }).then(() => { - Parse.User.logIn('user1', 'user1') - .then(() => { - Parse.User.logOut() - .then(() => { - // wait for a bit more than the validity duration set - setTimeout(() => { - Parse.User.logIn('user1', 'user1') - .then(() => { - fail('logIn should have failed'); - done(); - }) - .catch(error => { - expect(error.code).toEqual(Parse.Error.OBJECT_NOT_FOUND); - expect(error.message).toEqual( - 'Your password has expired. Please reset your password.' - ); - done(); - }); - }, 2000); - }) - .catch(error => { - jfail(error); - fail('logout should have succeeded'); - done(); - }); - }) - .catch(error => { - jfail(error); - fail('Login failed.'); - done(); - }); - }); - }) - .catch(error => { - jfail(error); - fail('logout should have succeeded'); - done(); - }); - }) - .catch(error => { - jfail(error); - fail('Signup failed.'); - done(); - }); - }); - }); - - it_id('d1e6ab9d-c091-4fea-b952-08b7484bfc89')(it)('should reset password timestamp when password is reset', done => { - const user = new Parse.User(); - const emailAdapter = { - sendVerificationEmail: () => Promise.resolve(), - sendPasswordResetEmail: options => { - request({ - url: options.link, - followRedirects: false, - simple: false, - resolveWithFullResponse: true, - }) - .then(response => { - expect(response.status).toEqual(302); - const re = /http:\/\/localhost:8378\/1\/apps\/choose_password\?token=([a-zA-Z0-9]+)\&id=test\&username=user1/; - const match = response.text.match(re); - if (!match) { - fail('should have a token'); - done(); - return; - } - const token = match[1]; + }) + .catch(error => { + jfail(error); + fail('Signup failed.'); + done(); + }); + }); + } + ); - request({ - method: 'POST', - url: 'http://localhost:8378/1/apps/test/request_password_reset', - body: `new_password=uuser11&token=${token}&username=user1`, - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - }, - followRedirects: false, - simple: false, - resolveWithFullResponse: true, - }) - .then(response => { - expect(response.status).toEqual(302); - expect(response.text).toEqual( - 'Found. Redirecting to http://localhost:8378/1/apps/password_reset_success.html?username=user1' - ); + it_id('22428408-8763-445d-9833-2b2d92008f62')(it)( + 'should fail if logged in after password expires', + done => { + const user = new Parse.User(); + reconfigureServer({ + appName: 'passwordPolicy', + passwordPolicy: { + maxPasswordAge: 0.5 / (24 * 60 * 60), // 0.5 sec + }, + publicServerURL: 'http://localhost:8378/1', + }).then(() => { + user.setUsername('user1'); + user.setPassword('user1'); + user.set('email', 'user1@parse.com'); + user + .signUp() + .then(() => { + // wait for a bit more than the validity duration set + setTimeout(() => { + Parse.User.logIn('user1', 'user1') + .then(() => { + fail('logIn should have failed'); + done(); + }) + .catch(error => { + expect(error.code).toEqual(Parse.Error.OBJECT_NOT_FOUND); + expect(error.message).toEqual( + 'Your password has expired. Please reset your password.' + ); + done(); + }); + }, 1000); + }) + .catch(error => { + jfail(error); + fail('Signup failed.'); + done(); + }); + }); + } + ); - Parse.User.logIn('user1', 'uuser11') - .then(function () { - done(); - }) - .catch(err => { - jfail(err); - fail('should login with new password'); - done(); - }); + it_id('cc97a109-e35f-4f94-b942-3a6134921cdd')(it)( + 'should apply password expiry policy to existing user upon first login after policy is enabled', + done => { + const user = new Parse.User(); + reconfigureServer({ + appName: 'passwordPolicy', + publicServerURL: 'http://localhost:8378/1', + }).then(() => { + user.setUsername('user1'); + user.setPassword('user1'); + user.set('email', 'user1@parse.com'); + user + .signUp() + .then(() => { + Parse.User.logOut() + .then(() => { + reconfigureServer({ + appName: 'passwordPolicy', + passwordPolicy: { + maxPasswordAge: 0.5 / (24 * 60 * 60), // 0.5 sec + }, + publicServerURL: 'http://localhost:8378/1', + }).then(() => { + Parse.User.logIn('user1', 'user1') + .then(() => { + Parse.User.logOut() + .then(() => { + // wait for a bit more than the validity duration set + setTimeout(() => { + Parse.User.logIn('user1', 'user1') + .then(() => { + fail('logIn should have failed'); + done(); + }) + .catch(error => { + expect(error.code).toEqual(Parse.Error.OBJECT_NOT_FOUND); + expect(error.message).toEqual( + 'Your password has expired. Please reset your password.' + ); + done(); + }); + }, 2000); + }) + .catch(error => { + jfail(error); + fail('logout should have succeeded'); + done(); + }); + }) + .catch(error => { + jfail(error); + fail('Login failed.'); + done(); + }); + }); }) .catch(error => { jfail(error); - fail('Failed to POST request password reset'); + fail('logout should have succeeded'); + done(); }); }) .catch(error => { jfail(error); - fail('Failed to get the reset link'); + fail('Signup failed.'); + done(); }); - }, - sendMail: () => {}, - }; - reconfigureServer({ - appName: 'passwordPolicy', - emailAdapter: emailAdapter, - passwordPolicy: { - maxPasswordAge: 0.5 / (24 * 60 * 60), // 0.5 sec - }, - publicServerURL: 'http://localhost:8378/1', - }).then(() => { - user.setUsername('user1'); - user.setPassword('user1'); - user.set('email', 'user1@parse.com'); - user - .signUp() - .then(() => { - // wait for a bit more than the validity duration set - setTimeout(() => { - Parse.User.logIn('user1', 'user1') - .then(() => { - fail('logIn should have failed'); + }); + } + ); + + it_id('d1e6ab9d-c091-4fea-b952-08b7484bfc89')(it)( + 'should reset password timestamp when password is reset', + done => { + const user = new Parse.User(); + const emailAdapter = { + sendVerificationEmail: () => Promise.resolve(), + sendPasswordResetEmail: options => { + request({ + url: options.link, + followRedirects: false, + simple: false, + resolveWithFullResponse: true, + }) + .then(response => { + expect(response.status).toEqual(302); + const re = /http:\/\/localhost:8378\/1\/apps\/choose_password\?token=([a-zA-Z0-9]+)\&id=test\&username=user1/; + const match = response.text.match(re); + if (!match) { + fail('should have a token'); done(); + return; + } + const token = match[1]; + + request({ + method: 'POST', + url: 'http://localhost:8378/1/apps/test/request_password_reset', + body: `new_password=uuser11&token=${token}&username=user1`, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + followRedirects: false, + simple: false, + resolveWithFullResponse: true, }) - .catch(error => { - expect(error.code).toEqual(Parse.Error.OBJECT_NOT_FOUND); - expect(error.message).toEqual( - 'Your password has expired. Please reset your password.' - ); - Parse.User.requestPasswordReset('user1@parse.com').catch(err => { - jfail(err); - fail('Reset password request should not fail'); + .then(response => { + expect(response.status).toEqual(302); + expect(response.text).toEqual( + 'Found. Redirecting to http://localhost:8378/1/apps/password_reset_success.html?username=user1' + ); + + Parse.User.logIn('user1', 'uuser11') + .then(function () { + done(); + }) + .catch(err => { + jfail(err); + fail('should login with new password'); + done(); + }); + }) + .catch(error => { + jfail(error); + fail('Failed to POST request password reset'); + }); + }) + .catch(error => { + jfail(error); + fail('Failed to get the reset link'); + }); + }, + sendMail: () => {}, + }; + reconfigureServer({ + appName: 'passwordPolicy', + emailAdapter: emailAdapter, + passwordPolicy: { + maxPasswordAge: 0.5 / (24 * 60 * 60), // 0.5 sec + }, + publicServerURL: 'http://localhost:8378/1', + }).then(() => { + user.setUsername('user1'); + user.setPassword('user1'); + user.set('email', 'user1@parse.com'); + user + .signUp() + .then(() => { + // wait for a bit more than the validity duration set + setTimeout(() => { + Parse.User.logIn('user1', 'user1') + .then(() => { + fail('logIn should have failed'); done(); + }) + .catch(error => { + expect(error.code).toEqual(Parse.Error.OBJECT_NOT_FOUND); + expect(error.message).toEqual( + 'Your password has expired. Please reset your password.' + ); + Parse.User.requestPasswordReset('user1@parse.com').catch(err => { + jfail(err); + fail('Reset password request should not fail'); + done(); + }); }); - }); - }, 1000); - }) - .catch(error => { - jfail(error); - fail('Signup failed.'); - done(); - }); - }); - }); + }, 1000); + }) + .catch(error => { + jfail(error); + fail('Signup failed.'); + done(); + }); + }); + } + ); it('should fail if passwordPolicy.maxPasswordHistory is not a number', done => { reconfigureServer({ diff --git a/spec/PointerPermissions.spec.js b/spec/PointerPermissions.spec.js index a4cf43899d..94257681c5 100644 --- a/spec/PointerPermissions.spec.js +++ b/spec/PointerPermissions.spec.js @@ -226,59 +226,62 @@ describe('Pointer Permissions', () => { }); }); - it_id('f38c35e7-d804-4d32-986d-2579e25d2461')(it)('should query on pointer permission enabled column', done => { - const config = Config.get(Parse.applicationId); - const user = new Parse.User(); - const user2 = new Parse.User(); - user.set({ - username: 'user1', - password: 'password', - }); - user2.set({ - username: 'user2', - password: 'password', - }); - const obj = new Parse.Object('AnObject'); - const obj2 = new Parse.Object('AnObject'); - user - .signUp() - .then(() => { - return user2.signUp(); - }) - .then(() => { - Parse.User.logOut(); - }) - .then(() => { - obj.set('owner', user); - return Parse.Object.saveAll([obj, obj2]); - }) - .then(() => { - return config.database.loadSchema().then(schema => { - return schema.updateClass( - 'AnObject', - {}, - { find: {}, get: {}, readUserFields: ['owner'] } - ); + it_id('f38c35e7-d804-4d32-986d-2579e25d2461')(it)( + 'should query on pointer permission enabled column', + done => { + const config = Config.get(Parse.applicationId); + const user = new Parse.User(); + const user2 = new Parse.User(); + user.set({ + username: 'user1', + password: 'password', + }); + user2.set({ + username: 'user2', + password: 'password', + }); + const obj = new Parse.Object('AnObject'); + const obj2 = new Parse.Object('AnObject'); + user + .signUp() + .then(() => { + return user2.signUp(); + }) + .then(() => { + Parse.User.logOut(); + }) + .then(() => { + obj.set('owner', user); + return Parse.Object.saveAll([obj, obj2]); + }) + .then(() => { + return config.database.loadSchema().then(schema => { + return schema.updateClass( + 'AnObject', + {}, + { find: {}, get: {}, readUserFields: ['owner'] } + ); + }); + }) + .then(() => { + return Parse.User.logIn('user1', 'password'); + }) + .then(() => { + const q = new Parse.Query('AnObject'); + q.equalTo('owner', user2); + return q.find(); + }) + .then(res => { + expect(res.length).toBe(0); + done(); + }) + .catch(err => { + jfail(err); + fail('should not fail'); + done(); }); - }) - .then(() => { - return Parse.User.logIn('user1', 'password'); - }) - .then(() => { - const q = new Parse.Query('AnObject'); - q.equalTo('owner', user2); - return q.find(); - }) - .then(res => { - expect(res.length).toBe(0); - done(); - }) - .catch(err => { - jfail(err); - fail('should not fail'); - done(); - }); - }); + } + ); it('should not allow creating objects', done => { const config = Config.get(Parse.applicationId); @@ -1203,50 +1206,53 @@ describe('Pointer Permissions', () => { done(); }); - it_id('8a7d188c-b75c-4eac-90b6-9b0b11f873ae')(it)('should query on pointer permission enabled column', async done => { - const config = Config.get(Parse.applicationId); - const user = new Parse.User(); - const user2 = new Parse.User(); - const user3 = new Parse.User(); - user.set({ - username: 'user1', - password: 'password', - }); - user2.set({ - username: 'user2', - password: 'password', - }); - user3.set({ - username: 'user3', - password: 'password', - }); - const obj = new Parse.Object('AnObject'); - const obj2 = new Parse.Object('AnObject'); + it_id('8a7d188c-b75c-4eac-90b6-9b0b11f873ae')(it)( + 'should query on pointer permission enabled column', + async done => { + const config = Config.get(Parse.applicationId); + const user = new Parse.User(); + const user2 = new Parse.User(); + const user3 = new Parse.User(); + user.set({ + username: 'user1', + password: 'password', + }); + user2.set({ + username: 'user2', + password: 'password', + }); + user3.set({ + username: 'user3', + password: 'password', + }); + const obj = new Parse.Object('AnObject'); + const obj2 = new Parse.Object('AnObject'); - await user.signUp(); - await user2.signUp(); - await user3.signUp(); - await Parse.User.logOut(); + await user.signUp(); + await user2.signUp(); + await user3.signUp(); + await Parse.User.logOut(); - obj.set('owners', [user, user2]); - await Parse.Object.saveAll([obj, obj2]); + obj.set('owners', [user, user2]); + await Parse.Object.saveAll([obj, obj2]); - const schema = await config.database.loadSchema(); - await schema.updateClass('AnObject', {}, { find: {}, get: {}, readUserFields: ['owners'] }); + const schema = await config.database.loadSchema(); + await schema.updateClass('AnObject', {}, { find: {}, get: {}, readUserFields: ['owners'] }); - for (const owner of ['user1', 'user2']) { - await Parse.User.logIn(owner, 'password'); - try { - const q = new Parse.Query('AnObject'); - q.equalTo('owners', user3); - const result = await q.find(); - expect(result.length).toBe(0); - } catch (err) { - done.fail('should not fail'); + for (const owner of ['user1', 'user2']) { + await Parse.User.logIn(owner, 'password'); + try { + const q = new Parse.Query('AnObject'); + q.equalTo('owners', user3); + const result = await q.find(); + expect(result.length).toBe(0); + } catch (err) { + done.fail('should not fail'); + } } + done(); } - done(); - }); + ); it('should not query using arrays on pointer permission enabled column', async done => { const config = Config.get(Parse.applicationId); @@ -2058,18 +2064,21 @@ describe('Pointer Permissions', () => { done(); }); - it_id('9ba681d5-59f5-4996-b36d-6647d23e6a44')(it)('should fail for user not listed', async done => { - await updateCLP({ - get: { - pointerFields: ['owner'], - }, - }); + it_id('9ba681d5-59f5-4996-b36d-6647d23e6a44')(it)( + 'should fail for user not listed', + async done => { + await updateCLP({ + get: { + pointerFields: ['owner'], + }, + }); - await logIn(user2); + await logIn(user2); - await expectAsync(actionGet(obj1.id)).toBeRejectedWith(OBJECT_NOT_FOUND); - done(); - }); + await expectAsync(actionGet(obj1.id)).toBeRejectedWith(OBJECT_NOT_FOUND); + done(); + } + ); it('should not allow other actions', async done => { await updateCLP({ @@ -2218,18 +2227,21 @@ describe('Pointer Permissions', () => { done(); }); - it_id('bcdb158d-c0b6-45e3-84ab-a3636f7cb470')(it)('should fail for user not listed', async done => { - await updateCLP({ - update: { - pointerFields: ['owner'], - }, - }); + it_id('bcdb158d-c0b6-45e3-84ab-a3636f7cb470')(it)( + 'should fail for user not listed', + async done => { + await updateCLP({ + update: { + pointerFields: ['owner'], + }, + }); - await logIn(user2); + await logIn(user2); - await expectAsync(actionUpdate(obj1)).toBeRejectedWith(OBJECT_NOT_FOUND); - done(); - }); + await expectAsync(actionUpdate(obj1)).toBeRejectedWith(OBJECT_NOT_FOUND); + done(); + } + ); it('should not allow other actions', async done => { await updateCLP({ @@ -2270,18 +2282,21 @@ describe('Pointer Permissions', () => { done(); }); - it_id('70aa3853-6e26-4c38-a927-2ddb24ced7d4')(it)('should fail for user not listed', async done => { - await updateCLP({ - delete: { - pointerFields: ['owner'], - }, - }); + it_id('70aa3853-6e26-4c38-a927-2ddb24ced7d4')(it)( + 'should fail for user not listed', + async done => { + await updateCLP({ + delete: { + pointerFields: ['owner'], + }, + }); - await logIn(user2); + await logIn(user2); - await expectAsync(actionDelete(obj1)).toBeRejectedWith(OBJECT_NOT_FOUND); - done(); - }); + await expectAsync(actionDelete(obj1)).toBeRejectedWith(OBJECT_NOT_FOUND); + done(); + } + ); it('should not allow other actions', async done => { await updateCLP({ @@ -2517,18 +2532,21 @@ describe('Pointer Permissions', () => { done(); }); - it_id('84a42339-c7b5-4735-a431-57b46535b073')(it)('should fail for user not listed', async done => { - await updateCLP({ - get: { - pointerFields: ['moderators'], - }, - }); + it_id('84a42339-c7b5-4735-a431-57b46535b073')(it)( + 'should fail for user not listed', + async done => { + await updateCLP({ + get: { + pointerFields: ['moderators'], + }, + }); - await logIn(user1); + await logIn(user1); - await expectAsync(actionGet(obj3.id)).toBeRejectedWith(OBJECT_NOT_FOUND); - done(); - }); + await expectAsync(actionGet(obj3.id)).toBeRejectedWith(OBJECT_NOT_FOUND); + done(); + } + ); it('should not allow other actions', async done => { await updateCLP({ @@ -2685,31 +2703,37 @@ describe('Pointer Permissions', () => { done(); }); - it_id('2b19234a-a471-48b4-bd1a-27bd286d066f')(it)('should be allowed (multiple users in array)', async done => { - await updateCLP({ - update: { - pointerFields: ['moderators'], - }, - }); + it_id('2b19234a-a471-48b4-bd1a-27bd286d066f')(it)( + 'should be allowed (multiple users in array)', + async done => { + await updateCLP({ + update: { + pointerFields: ['moderators'], + }, + }); - await logIn(user2); + await logIn(user2); - await expectAsync(actionUpdate(obj1)).toBeResolved(); - done(); - }); + await expectAsync(actionUpdate(obj1)).toBeResolved(); + done(); + } + ); - it_id('1abb9f4a-fb24-48c7-8025-3001d6cf8737')(it)('should fail for user not listed', async done => { - await updateCLP({ - update: { - pointerFields: ['moderators'], - }, - }); + it_id('1abb9f4a-fb24-48c7-8025-3001d6cf8737')(it)( + 'should fail for user not listed', + async done => { + await updateCLP({ + update: { + pointerFields: ['moderators'], + }, + }); - await logIn(user2); + await logIn(user2); - await expectAsync(actionUpdate(obj3)).toBeRejectedWith(OBJECT_NOT_FOUND); - done(); - }); + await expectAsync(actionUpdate(obj3)).toBeRejectedWith(OBJECT_NOT_FOUND); + done(); + } + ); it('should not allow other actions', async done => { await updateCLP({ @@ -2764,18 +2788,21 @@ describe('Pointer Permissions', () => { done(); }); - it_id('3175a0e3-e51e-4b84-a2e6-50bbcc582123')(it)('should fail for user not listed', async done => { - await updateCLP({ - delete: { - pointerFields: ['owners'], - }, - }); + it_id('3175a0e3-e51e-4b84-a2e6-50bbcc582123')(it)( + 'should fail for user not listed', + async done => { + await updateCLP({ + delete: { + pointerFields: ['owners'], + }, + }); - await logIn(user1); + await logIn(user1); - await expectAsync(actionDelete(obj3)).toBeRejectedWith(OBJECT_NOT_FOUND); - done(); - }); + await expectAsync(actionDelete(obj3)).toBeRejectedWith(OBJECT_NOT_FOUND); + done(); + } + ); it('should not allow other actions', async done => { await updateCLP({ @@ -2874,22 +2901,25 @@ describe('Pointer Permissions', () => { done(); }); - it_id('51e896e9-73b3-404f-b5ff-bdb99005a9f7')(it)('should be restricted when updating object without addField permission', async done => { - await updateCLP({ - update: { - '*': true, - }, - addField: { - pointerFields: ['moderators'], - }, - }); + it_id('51e896e9-73b3-404f-b5ff-bdb99005a9f7')(it)( + 'should be restricted when updating object without addField permission', + async done => { + await updateCLP({ + update: { + '*': true, + }, + addField: { + pointerFields: ['moderators'], + }, + }); - await logIn(user1); + await logIn(user1); - await expectAsync(actionAddFieldOnUpdate(obj2)).toBeRejectedWith(OBJECT_NOT_FOUND); + await expectAsync(actionAddFieldOnUpdate(obj2)).toBeRejectedWith(OBJECT_NOT_FOUND); - done(); - }); + done(); + } + ); }); }); @@ -2946,44 +2976,50 @@ describe('Pointer Permissions', () => { await initialize(); }); - it_id('b43db366-8cce-4a11-9cf2-eeee9603d40b')(it)('should not limit the scope of grouped read permissions', async done => { - await updateCLP({ - get: { - pointerFields: ['owner'], - }, - readUserFields: ['moderators'], - }); + it_id('b43db366-8cce-4a11-9cf2-eeee9603d40b')(it)( + 'should not limit the scope of grouped read permissions', + async done => { + await updateCLP({ + get: { + pointerFields: ['owner'], + }, + readUserFields: ['moderators'], + }); - await logIn(user2); + await logIn(user2); - await expectAsync(actionGet(obj1.id)).toBeResolved(); + await expectAsync(actionGet(obj1.id)).toBeResolved(); - const found = await actionFind(); - expect(found.length).toBe(2); + const found = await actionFind(); + expect(found.length).toBe(2); - const counted = await actionCount(); - expect(counted).toBe(2); + const counted = await actionCount(); + expect(counted).toBe(2); - done(); - }); + done(); + } + ); - it_id('bbb1686d-0e2a-4365-8b64-b5faa3e7b9cf')(it)('should not limit the scope of grouped write permissions', async done => { - await updateCLP({ - update: { - pointerFields: ['owner'], - }, - writeUserFields: ['moderators'], - }); + it_id('bbb1686d-0e2a-4365-8b64-b5faa3e7b9cf')(it)( + 'should not limit the scope of grouped write permissions', + async done => { + await updateCLP({ + update: { + pointerFields: ['owner'], + }, + writeUserFields: ['moderators'], + }); - await logIn(user2); + await logIn(user2); - await expectAsync(actionUpdate(obj1)).toBeResolved(); - await expectAsync(actionAddFieldOnUpdate(obj1)).toBeResolved(); - await expectAsync(actionDelete(obj1)).toBeResolved(); - // [create] and [addField on create] can't be enabled with pointer by design + await expectAsync(actionUpdate(obj1)).toBeResolved(); + await expectAsync(actionAddFieldOnUpdate(obj1)).toBeResolved(); + await expectAsync(actionDelete(obj1)).toBeResolved(); + // [create] and [addField on create] can't be enabled with pointer by design - done(); - }); + done(); + } + ); it('should not inherit scope of grouped read permissions from another field', async done => { await updateCLP({ diff --git a/spec/PushController.spec.js b/spec/PushController.spec.js index 875825025d..2f487df0ce 100644 --- a/spec/PushController.spec.js +++ b/spec/PushController.spec.js @@ -233,67 +233,70 @@ describe('PushController', () => { } }); - it_id('14afcedf-e65d-41cd-981e-07f32df84c14')(it)('properly increment badges by more than 1', async () => { - const pushAdapter = { - send: function (body, installations) { - const badge = body.data.badge; - installations.forEach(installation => { - expect(installation.badge).toEqual(badge); - expect(installation.originalBadge + 3).toEqual(installation.badge); - }); - return successfulTransmissions(body, installations); - }, - getValidPushTypes: function () { - return ['ios', 'android']; - }, - }; - const payload = { - data: { - alert: 'Hello World!', - badge: { __op: 'Increment', amount: 3 }, - }, - }; - const installations = []; - while (installations.length != 10) { - const installation = new Parse.Object('_Installation'); - installation.set('installationId', 'installation_' + installations.length); - installation.set('deviceToken', 'device_token_' + installations.length); - installation.set('badge', installations.length); - installation.set('originalBadge', installations.length); - installation.set('deviceType', 'ios'); - installations.push(installation); - } + it_id('14afcedf-e65d-41cd-981e-07f32df84c14')(it)( + 'properly increment badges by more than 1', + async () => { + const pushAdapter = { + send: function (body, installations) { + const badge = body.data.badge; + installations.forEach(installation => { + expect(installation.badge).toEqual(badge); + expect(installation.originalBadge + 3).toEqual(installation.badge); + }); + return successfulTransmissions(body, installations); + }, + getValidPushTypes: function () { + return ['ios', 'android']; + }, + }; + const payload = { + data: { + alert: 'Hello World!', + badge: { __op: 'Increment', amount: 3 }, + }, + }; + const installations = []; + while (installations.length != 10) { + const installation = new Parse.Object('_Installation'); + installation.set('installationId', 'installation_' + installations.length); + installation.set('deviceToken', 'device_token_' + installations.length); + installation.set('badge', installations.length); + installation.set('originalBadge', installations.length); + installation.set('deviceType', 'ios'); + installations.push(installation); + } - while (installations.length != 15) { - const installation = new Parse.Object('_Installation'); - installation.set('installationId', 'installation_' + installations.length); - installation.set('deviceToken', 'device_token_' + installations.length); - installation.set('badge', installations.length); - installation.set('originalBadge', installations.length); - installation.set('deviceType', 'android'); - installations.push(installation); - } - const config = Config.get(Parse.applicationId); - const auth = { - isMaster: true, - }; - await reconfigureServer({ - push: { adapter: pushAdapter }, - }); - await Parse.Object.saveAll(installations); - const pushStatusId = await sendPush(payload, {}, config, auth); - await pushCompleted(pushStatusId); - const pushStatus = await Parse.Push.getPushStatus(pushStatusId); - expect(pushStatus.get('numSent')).toBe(15); - // Check that the installations were actually updated. - const query = new Parse.Query('_Installation'); - const results = await query.find({ useMasterKey: true }); - expect(results.length).toBe(15); - for (let i = 0; i < 15; i++) { - const installation = results[i]; - expect(installation.get('badge')).toBe(parseInt(installation.get('originalBadge')) + 3); + while (installations.length != 15) { + const installation = new Parse.Object('_Installation'); + installation.set('installationId', 'installation_' + installations.length); + installation.set('deviceToken', 'device_token_' + installations.length); + installation.set('badge', installations.length); + installation.set('originalBadge', installations.length); + installation.set('deviceType', 'android'); + installations.push(installation); + } + const config = Config.get(Parse.applicationId); + const auth = { + isMaster: true, + }; + await reconfigureServer({ + push: { adapter: pushAdapter }, + }); + await Parse.Object.saveAll(installations); + const pushStatusId = await sendPush(payload, {}, config, auth); + await pushCompleted(pushStatusId); + const pushStatus = await Parse.Push.getPushStatus(pushStatusId); + expect(pushStatus.get('numSent')).toBe(15); + // Check that the installations were actually updated. + const query = new Parse.Query('_Installation'); + const results = await query.find({ useMasterKey: true }); + expect(results.length).toBe(15); + for (let i = 0; i < 15; i++) { + const installation = results[i]; + expect(installation.get('badge')).toBe(parseInt(installation.get('originalBadge')) + 3); + } } - }); + ); it_id('758dd579-aa91-4010-9033-8d48d3463644')(it)('properly set badges to 1', async () => { const pushAdapter = { @@ -350,61 +353,64 @@ describe('PushController', () => { } }); - it_id('75c39ae3-06ac-4354-b321-931e81c5a927')(it)('properly set badges to 1 with complex query #2903 #3022', async () => { - const payload = { - data: { - alert: 'Hello World!', - badge: 1, - }, - }; - const installations = []; - while (installations.length != 10) { - const installation = new Parse.Object('_Installation'); - installation.set('installationId', 'installation_' + installations.length); - installation.set('deviceToken', 'device_token_' + installations.length); - installation.set('badge', installations.length); - installation.set('originalBadge', installations.length); - installation.set('deviceType', 'ios'); - installations.push(installation); - } - let matchedInstallationsCount = 0; - const pushAdapter = { - send: function (body, installations) { - matchedInstallationsCount += installations.length; - const badge = body.data.badge; - installations.forEach(installation => { - expect(installation.badge).toEqual(badge); - expect(1).toEqual(installation.badge); - }); - return successfulTransmissions(body, installations); - }, - getValidPushTypes: function () { - return ['ios']; - }, - }; + it_id('75c39ae3-06ac-4354-b321-931e81c5a927')(it)( + 'properly set badges to 1 with complex query #2903 #3022', + async () => { + const payload = { + data: { + alert: 'Hello World!', + badge: 1, + }, + }; + const installations = []; + while (installations.length != 10) { + const installation = new Parse.Object('_Installation'); + installation.set('installationId', 'installation_' + installations.length); + installation.set('deviceToken', 'device_token_' + installations.length); + installation.set('badge', installations.length); + installation.set('originalBadge', installations.length); + installation.set('deviceType', 'ios'); + installations.push(installation); + } + let matchedInstallationsCount = 0; + const pushAdapter = { + send: function (body, installations) { + matchedInstallationsCount += installations.length; + const badge = body.data.badge; + installations.forEach(installation => { + expect(installation.badge).toEqual(badge); + expect(1).toEqual(installation.badge); + }); + return successfulTransmissions(body, installations); + }, + getValidPushTypes: function () { + return ['ios']; + }, + }; - const config = Config.get(Parse.applicationId); - const auth = { - isMaster: true, - }; - await reconfigureServer({ - push: { adapter: pushAdapter }, - }); - await Parse.Object.saveAll(installations); - const objectIds = installations.map(installation => { - return installation.id; - }); - const where = { - objectId: { $in: objectIds.slice(0, 5) }, - }; - const pushStatusId = await sendPush(payload, where, config, auth); - await pushCompleted(pushStatusId); - expect(matchedInstallationsCount).toBe(5); - const query = new Parse.Query(Parse.Installation); - query.equalTo('badge', 1); - const results = await query.find({ useMasterKey: true }); - expect(results.length).toBe(5); - }); + const config = Config.get(Parse.applicationId); + const auth = { + isMaster: true, + }; + await reconfigureServer({ + push: { adapter: pushAdapter }, + }); + await Parse.Object.saveAll(installations); + const objectIds = installations.map(installation => { + return installation.id; + }); + const where = { + objectId: { $in: objectIds.slice(0, 5) }, + }; + const pushStatusId = await sendPush(payload, where, config, auth); + await pushCompleted(pushStatusId); + expect(matchedInstallationsCount).toBe(5); + const query = new Parse.Query(Parse.Installation); + query.equalTo('badge', 1); + const results = await query.find({ useMasterKey: true }); + expect(results.length).toBe(5); + } + ); it_id('667f31c0-b458-4f61-ab57-668c04e3cc0b')(it)('properly creates _PushStatus', async () => { const pushStatusAfterSave = { @@ -521,51 +527,54 @@ describe('PushController', () => { expect(succeedCount).toBe(1); }); - it_id('30e0591a-56de-4720-8c60-7d72291b532a')(it)('properly creates _PushStatus without serverURL', async () => { - const pushStatusAfterSave = { - handler: function () {}, - }; - Parse.Cloud.afterSave('_PushStatus', pushStatusAfterSave.handler); - const installation = new Parse.Object('_Installation'); - installation.set('installationId', 'installation'); - installation.set('deviceToken', 'device_token'); - installation.set('badge', 0); - installation.set('originalBadge', 0); - installation.set('deviceType', 'ios'); + it_id('30e0591a-56de-4720-8c60-7d72291b532a')(it)( + 'properly creates _PushStatus without serverURL', + async () => { + const pushStatusAfterSave = { + handler: function () {}, + }; + Parse.Cloud.afterSave('_PushStatus', pushStatusAfterSave.handler); + const installation = new Parse.Object('_Installation'); + installation.set('installationId', 'installation'); + installation.set('deviceToken', 'device_token'); + installation.set('badge', 0); + installation.set('originalBadge', 0); + installation.set('deviceType', 'ios'); - const payload = { - data: { - alert: 'Hello World!', - badge: 1, - }, - }; + const payload = { + data: { + alert: 'Hello World!', + badge: 1, + }, + }; - const pushAdapter = { - send: function (body, installations) { - return successfulIOS(body, installations); - }, - getValidPushTypes: function () { - return ['ios']; - }, - }; + const pushAdapter = { + send: function (body, installations) { + return successfulIOS(body, installations); + }, + getValidPushTypes: function () { + return ['ios']; + }, + }; - const config = Config.get(Parse.applicationId); - const auth = { - isMaster: true, - }; - await installation.save(); - await reconfigureServer({ - serverURL: 'http://localhost:8378/', // server with borked URL - push: { adapter: pushAdapter }, - }); - const pushStatusId = await sendPush(payload, {}, config, auth); - // it is enqueued so it can take time - await jasmine.timeout(1000); - Parse.serverURL = 'http://localhost:8378/1'; // GOOD url - const result = await Parse.Push.getPushStatus(pushStatusId); - expect(result).toBeDefined(); - await pushCompleted(pushStatusId); - }); + const config = Config.get(Parse.applicationId); + const auth = { + isMaster: true, + }; + await installation.save(); + await reconfigureServer({ + serverURL: 'http://localhost:8378/', // server with borked URL + push: { adapter: pushAdapter }, + }); + const pushStatusId = await sendPush(payload, {}, config, auth); + // it is enqueued so it can take time + await jasmine.timeout(1000); + Parse.serverURL = 'http://localhost:8378/1'; // GOOD url + const result = await Parse.Push.getPushStatus(pushStatusId); + expect(result).toBeDefined(); + await pushCompleted(pushStatusId); + } + ); it('should properly report failures in _PushStatus', async () => { const pushAdapter = { @@ -615,51 +624,54 @@ describe('PushController', () => { } }); - it_id('53551fc3-b975-4774-92e6-7e5f3c05e105')(it)('should support full RESTQuery for increment', async () => { - const payload = { - data: { - alert: 'Hello World!', - badge: 'Increment', - }, - }; + it_id('53551fc3-b975-4774-92e6-7e5f3c05e105')(it)( + 'should support full RESTQuery for increment', + async () => { + const payload = { + data: { + alert: 'Hello World!', + badge: 'Increment', + }, + }; - const pushAdapter = { - send: function (body, installations) { - return successfulTransmissions(body, installations); - }, - getValidPushTypes: function () { - return ['ios']; - }, - }; - const config = Config.get(Parse.applicationId); - const auth = { - isMaster: true, - }; + const pushAdapter = { + send: function (body, installations) { + return successfulTransmissions(body, installations); + }, + getValidPushTypes: function () { + return ['ios']; + }, + }; + const config = Config.get(Parse.applicationId); + const auth = { + isMaster: true, + }; - const where = { - deviceToken: { - $in: ['device_token_0', 'device_token_1', 'device_token_2'], - }, - }; - await reconfigureServer({ - push: { adapter: pushAdapter }, - }); - const installations = []; - while (installations.length != 5) { - const installation = new Parse.Object('_Installation'); - installation.set('installationId', 'installation_' + installations.length); - installation.set('deviceToken', 'device_token_' + installations.length); - installation.set('badge', installations.length); - installation.set('originalBadge', installations.length); - installation.set('deviceType', 'ios'); - installations.push(installation); + const where = { + deviceToken: { + $in: ['device_token_0', 'device_token_1', 'device_token_2'], + }, + }; + await reconfigureServer({ + push: { adapter: pushAdapter }, + }); + const installations = []; + while (installations.length != 5) { + const installation = new Parse.Object('_Installation'); + installation.set('installationId', 'installation_' + installations.length); + installation.set('deviceToken', 'device_token_' + installations.length); + installation.set('badge', installations.length); + installation.set('originalBadge', installations.length); + installation.set('deviceType', 'ios'); + installations.push(installation); + } + await Parse.Object.saveAll(installations); + const pushStatusId = await sendPush(payload, where, config, auth); + await pushCompleted(pushStatusId); + const pushStatus = await Parse.Push.getPushStatus(pushStatusId); + expect(pushStatus.get('numSent')).toBe(3); } - await Parse.Object.saveAll(installations); - const pushStatusId = await sendPush(payload, where, config, auth); - await pushCompleted(pushStatusId); - const pushStatus = await Parse.Push.getPushStatus(pushStatusId); - expect(pushStatus.get('numSent')).toBe(3); - }); + ); it('should support object type for alert', async () => { const payload = { diff --git a/spec/PushWorker.spec.js b/spec/PushWorker.spec.js index 6299962d52..23e8206fe3 100644 --- a/spec/PushWorker.spec.js +++ b/spec/PushWorker.spec.js @@ -271,99 +271,102 @@ describe('PushWorker', () => { toAwait.then(done).catch(done); }); - it_id('764d28ab-241b-4b96-8ce9-e03541850e3f')(it)('tracks push status per UTC offsets', done => { - const config = Config.get('test'); - const handler = pushStatusHandler(config); - const spy = spyOn(rest, 'update').and.callThrough(); - const UTCOffset = 1; - handler - .setInitial() - .then(() => { - return handler.trackSent( - [ - { - transmitted: false, - device: { - deviceToken: 1, - deviceType: 'ios', + it_id('764d28ab-241b-4b96-8ce9-e03541850e3f')(it)( + 'tracks push status per UTC offsets', + done => { + const config = Config.get('test'); + const handler = pushStatusHandler(config); + const spy = spyOn(rest, 'update').and.callThrough(); + const UTCOffset = 1; + handler + .setInitial() + .then(() => { + return handler.trackSent( + [ + { + transmitted: false, + device: { + deviceToken: 1, + deviceType: 'ios', + }, }, - }, - { - transmitted: true, - device: { - deviceToken: 1, - deviceType: 'ios', + { + transmitted: true, + device: { + deviceToken: 1, + deviceType: 'ios', + }, }, + ], + UTCOffset + ); + }) + .then(() => { + expect(spy).toHaveBeenCalled(); + const lastCall = spy.calls.mostRecent(); + expect(lastCall.args[2]).toBe(`_PushStatus`); + expect(lastCall.args[4]).toEqual({ + numSent: { __op: 'Increment', amount: 1 }, + numFailed: { __op: 'Increment', amount: 1 }, + 'sentPerType.ios': { __op: 'Increment', amount: 1 }, + 'failedPerType.ios': { __op: 'Increment', amount: 1 }, + [`sentPerUTCOffset.${UTCOffset}`]: { __op: 'Increment', amount: 1 }, + [`failedPerUTCOffset.${UTCOffset}`]: { + __op: 'Increment', + amount: 1, }, - ], - UTCOffset - ); - }) - .then(() => { - expect(spy).toHaveBeenCalled(); - const lastCall = spy.calls.mostRecent(); - expect(lastCall.args[2]).toBe(`_PushStatus`); - expect(lastCall.args[4]).toEqual({ - numSent: { __op: 'Increment', amount: 1 }, - numFailed: { __op: 'Increment', amount: 1 }, - 'sentPerType.ios': { __op: 'Increment', amount: 1 }, - 'failedPerType.ios': { __op: 'Increment', amount: 1 }, - [`sentPerUTCOffset.${UTCOffset}`]: { __op: 'Increment', amount: 1 }, - [`failedPerUTCOffset.${UTCOffset}`]: { - __op: 'Increment', - amount: 1, - }, - count: { __op: 'Increment', amount: -1 }, - status: 'running', - }); - const query = new Parse.Query('_PushStatus'); - return query.get(handler.objectId, { useMasterKey: true }); - }) - .then(pushStatus => { - const sentPerUTCOffset = pushStatus.get('sentPerUTCOffset'); - expect(sentPerUTCOffset['1']).toBe(1); - const failedPerUTCOffset = pushStatus.get('failedPerUTCOffset'); - expect(failedPerUTCOffset['1']).toBe(1); - return handler.trackSent( - [ - { - transmitted: false, - device: { - deviceToken: 1, - deviceType: 'ios', + count: { __op: 'Increment', amount: -1 }, + status: 'running', + }); + const query = new Parse.Query('_PushStatus'); + return query.get(handler.objectId, { useMasterKey: true }); + }) + .then(pushStatus => { + const sentPerUTCOffset = pushStatus.get('sentPerUTCOffset'); + expect(sentPerUTCOffset['1']).toBe(1); + const failedPerUTCOffset = pushStatus.get('failedPerUTCOffset'); + expect(failedPerUTCOffset['1']).toBe(1); + return handler.trackSent( + [ + { + transmitted: false, + device: { + deviceToken: 1, + deviceType: 'ios', + }, }, - }, - { - transmitted: true, - device: { - deviceToken: 1, - deviceType: 'ios', + { + transmitted: true, + device: { + deviceToken: 1, + deviceType: 'ios', + }, }, - }, - { - transmitted: true, - device: { - deviceToken: 1, - deviceType: 'ios', + { + transmitted: true, + device: { + deviceToken: 1, + deviceType: 'ios', + }, }, - }, - ], - UTCOffset - ); - }) - .then(() => { - const query = new Parse.Query('_PushStatus'); - return query.get(handler.objectId, { useMasterKey: true }); - }) - .then(pushStatus => { - const sentPerUTCOffset = pushStatus.get('sentPerUTCOffset'); - expect(sentPerUTCOffset['1']).toBe(3); - const failedPerUTCOffset = pushStatus.get('failedPerUTCOffset'); - expect(failedPerUTCOffset['1']).toBe(2); - }) - .then(done) - .catch(done.fail); - }); + ], + UTCOffset + ); + }) + .then(() => { + const query = new Parse.Query('_PushStatus'); + return query.get(handler.objectId, { useMasterKey: true }); + }) + .then(pushStatus => { + const sentPerUTCOffset = pushStatus.get('sentPerUTCOffset'); + expect(sentPerUTCOffset['1']).toBe(3); + const failedPerUTCOffset = pushStatus.get('failedPerUTCOffset'); + expect(failedPerUTCOffset['1']).toBe(2); + }) + .then(done) + .catch(done.fail); + } + ); it('tracks push status per UTC offsets with negative offsets', done => { const config = Config.get('test'); diff --git a/spec/RestQuery.spec.js b/spec/RestQuery.spec.js index 6fe3c0fa18..131433e177 100644 --- a/spec/RestQuery.spec.js +++ b/spec/RestQuery.spec.js @@ -419,69 +419,72 @@ describe('RestQuery.each', () => { expect(results.length).toBe(7); }); - it_id('0fe22501-4b18-461e-b87d-82ceac4a496e')(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 = await RestQuery({ - method: RestQuery.Method.get, - config, - auth: auth.master(config), - className: 'Letter', - restWhere: { - numbers: { - __type: 'Pointer', - className: 'Number', - objectId: object1.id, + it_id('0fe22501-4b18-461e-b87d-82ceac4a496e')(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 = await RestQuery({ + method: RestQuery.Method.get, + config, + auth: auth.master(config), + className: 'Letter', + restWhere: { + numbers: { + __type: 'Pointer', + className: 'Number', + objectId: object1.id, + }, }, - }, - restOptions: { limit: 1 }, - }); + restOptions: { limit: 1 }, + }); - const queryTwo = await RestQuery({ - method: RestQuery.Method.get, - config, - auth: auth.master(config), - className: 'Letter', - restWhere: { - numbers: { - __type: 'Pointer', - className: 'Number', - objectId: object2.id, + const queryTwo = await RestQuery({ + method: RestQuery.Method.get, + config, + auth: auth.master(config), + className: 'Letter', + restWhere: { + numbers: { + __type: 'Pointer', + className: 'Number', + objectId: object2.id, + }, }, - }, - restOptions: { limit: 1 }, - }); + restOptions: { limit: 1 }, + }); - const classSpy = spyOn(RestQuery._UnsafeRestQuery.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); - }); + const classSpy = spyOn(RestQuery._UnsafeRestQuery.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) { diff --git a/spec/SchemaPerformance.spec.js b/spec/SchemaPerformance.spec.js index 728b88804c..4429f9e4b0 100644 --- a/spec/SchemaPerformance.spec.js +++ b/spec/SchemaPerformance.spec.js @@ -241,21 +241,24 @@ describe('Schema Performance', function () { expect(spy.reloadCalls).toBe(1); }); - it_id('b0ae21f2-c947-48ed-a0db-e8900d45a4c8')(it)('cannot set invalid databaseOptions', async () => { - const expectError = async (key, value, expected) => - expectAsync( - reconfigureServer({ databaseAdapter: undefined, databaseOptions: { [key]: value } }) - ).toBeRejectedWith(`databaseOptions.${key} must be a ${expected}`); - for (const databaseOptions of [[], 0, 'string']) { - await expectAsync( - reconfigureServer({ databaseAdapter: undefined, databaseOptions }) - ).toBeRejectedWith(`databaseOptions must be an object`); + it_id('b0ae21f2-c947-48ed-a0db-e8900d45a4c8')(it)( + 'cannot set invalid databaseOptions', + async () => { + const expectError = async (key, value, expected) => + expectAsync( + reconfigureServer({ databaseAdapter: undefined, databaseOptions: { [key]: value } }) + ).toBeRejectedWith(`databaseOptions.${key} must be a ${expected}`); + for (const databaseOptions of [[], 0, 'string']) { + await expectAsync( + reconfigureServer({ databaseAdapter: undefined, databaseOptions }) + ).toBeRejectedWith(`databaseOptions must be an object`); + } + for (const value of [null, 0, 'string', {}, []]) { + await expectError('enableSchemaHooks', value, 'boolean'); + } + for (const value of [null, false, 'string', {}, []]) { + await expectError('schemaCacheTtl', value, 'number'); + } } - for (const value of [null, 0, 'string', {}, []]) { - await expectError('enableSchemaHooks', value, 'boolean'); - } - for (const value of [null, false, 'string', {}, []]) { - await expectError('schemaCacheTtl', value, 'number'); - } - }); + ); }); diff --git a/spec/Uniqueness.spec.js b/spec/Uniqueness.spec.js index 92ee6ea92c..7f53ac54bb 100644 --- a/spec/Uniqueness.spec.js +++ b/spec/Uniqueness.spec.js @@ -68,25 +68,28 @@ describe('Uniqueness', function () { }); }); - it_id('802650a9-a6db-447e-88d0-8aae99100088')(it)('fails when attempting to ensure uniqueness of fields that are not currently unique', done => { - const o1 = new Parse.Object('UniqueFail'); - o1.set('key', 'val'); - const o2 = new Parse.Object('UniqueFail'); - o2.set('key', 'val'); - Parse.Object.saveAll([o1, o2]) - .then(() => { - const config = Config.get('test'); - return config.database.adapter.ensureUniqueness( - 'UniqueFail', - { fields: { key: { __type: 'String' } } }, - ['key'] - ); - }) - .catch(error => { - expect(error.code).toEqual(Parse.Error.DUPLICATE_VALUE); - done(); - }); - }); + it_id('802650a9-a6db-447e-88d0-8aae99100088')(it)( + 'fails when attempting to ensure uniqueness of fields that are not currently unique', + done => { + const o1 = new Parse.Object('UniqueFail'); + o1.set('key', 'val'); + const o2 = new Parse.Object('UniqueFail'); + o2.set('key', 'val'); + Parse.Object.saveAll([o1, o2]) + .then(() => { + const config = Config.get('test'); + return config.database.adapter.ensureUniqueness( + 'UniqueFail', + { fields: { key: { __type: 'String' } } }, + ['key'] + ); + }) + .catch(error => { + expect(error.code).toEqual(Parse.Error.DUPLICATE_VALUE); + done(); + }); + } + ); it_exclude_dbs(['postgres'])('can do compound uniqueness', done => { const config = Config.get('test'); diff --git a/spec/UserController.spec.js b/spec/UserController.spec.js index e240d4666a..4d2e944117 100644 --- a/spec/UserController.spec.js +++ b/spec/UserController.spec.js @@ -30,48 +30,65 @@ describe('UserController', () => { await user.signUp(); const config = Config.get('test'); - const rawUser = await config.database.find('_User', { username }, {}, Auth.maintenance(config)); + const rawUser = await config.database.find( + '_User', + { username }, + {}, + Auth.maintenance(config) + ); const rawUsername = rawUser[0].username; const rawToken = rawUser[0]._email_verify_token; expect(rawToken).toBeDefined(); expect(rawUsername).toBe(username); - expect(emailOptions.link).toEqual(`http://www.example.com/apps/test/verify_email?token=${rawToken}&username=${username}`); + expect(emailOptions.link).toEqual( + `http://www.example.com/apps/test/verify_email?token=${rawToken}&username=${username}` + ); }); }); describe('parseFrameURL provided', () => { - it_id('673c2bb1-049e-4dda-b6be-88c866260036')(it)('uses parseFrameURL and includes the destination in the link parameter', async () => { - await reconfigureServer({ - publicServerURL: 'http://www.example.com', - customPages: { - parseFrameURL: 'http://someother.example.com/handle-parse-iframe', - }, - verifyUserEmails: true, - emailAdapter, - appName: 'test', - }); + it_id('673c2bb1-049e-4dda-b6be-88c866260036')(it)( + 'uses parseFrameURL and includes the destination in the link parameter', + async () => { + await reconfigureServer({ + publicServerURL: 'http://www.example.com', + customPages: { + parseFrameURL: 'http://someother.example.com/handle-parse-iframe', + }, + verifyUserEmails: true, + emailAdapter, + appName: 'test', + }); - let emailOptions; - emailAdapter.sendVerificationEmail = options => { - emailOptions = options; - return Promise.resolve(); - }; + let emailOptions; + emailAdapter.sendVerificationEmail = options => { + emailOptions = options; + return Promise.resolve(); + }; - const username = 'verificationUser'; - const user = new Parse.User(); - user.setUsername(username); - user.setPassword('pass'); - user.setEmail('verification@example.com'); - await user.signUp(); + const username = 'verificationUser'; + const user = new Parse.User(); + user.setUsername(username); + user.setPassword('pass'); + user.setEmail('verification@example.com'); + await user.signUp(); - const config = Config.get('test'); - const rawUser = await config.database.find('_User', { username }, {}, Auth.maintenance(config)); - const rawUsername = rawUser[0].username; - const rawToken = rawUser[0]._email_verify_token; - expect(rawToken).toBeDefined(); - expect(rawUsername).toBe(username); - expect(emailOptions.link).toEqual(`http://someother.example.com/handle-parse-iframe?link=%2Fapps%2Ftest%2Fverify_email&token=${rawToken}&username=${username}`); - }); + const config = Config.get('test'); + const rawUser = await config.database.find( + '_User', + { username }, + {}, + Auth.maintenance(config) + ); + const rawUsername = rawUser[0].username; + const rawToken = rawUser[0]._email_verify_token; + expect(rawToken).toBeDefined(); + expect(rawUsername).toBe(username); + expect(emailOptions.link).toEqual( + `http://someother.example.com/handle-parse-iframe?link=%2Fapps%2Ftest%2Fverify_email&token=${rawToken}&username=${username}` + ); + } + ); }); }); }); diff --git a/spec/Utils.spec.js b/spec/Utils.spec.js index 3aa31a74b0..0bf516c54f 100644 --- a/spec/Utils.spec.js +++ b/spec/Utils.spec.js @@ -7,28 +7,28 @@ describe('Utils', () => { a: 1, b: { c: 2, - d: 3 + d: 3, }, - e: 4 + e: 4, }; Utils.addNestedKeysToRoot(obj, 'b'); expect(obj).toEqual({ a: 1, c: 2, d: 3, - e: 4 + e: 4, }); }); it('should not modify the object if the key does not exist', async () => { const obj = { a: 1, - e: 4 + e: 4, }; Utils.addNestedKeysToRoot(obj, 'b'); expect(obj).toEqual({ a: 1, - e: 4 + e: 4, }); }); @@ -36,13 +36,13 @@ describe('Utils', () => { const obj = { a: 1, b: 2, - e: 4 + e: 4, }; Utils.addNestedKeysToRoot(obj, 'b'); expect(obj).toEqual({ a: 1, b: 2, - e: 4 + e: 4, }); }); }); diff --git a/spec/ValidationAndPasswordsReset.spec.js b/spec/ValidationAndPasswordsReset.spec.js index 7c5334261a..67bf610add 100644 --- a/spec/ValidationAndPasswordsReset.spec.js +++ b/spec/ValidationAndPasswordsReset.spec.js @@ -33,32 +33,35 @@ describe('Custom Pages, Email Verification, Password Reset', () => { }); }); - it_id('5e558687-40f3-496c-9e4f-af6100bd1b2f')(it)('sends verification email if email verification is enabled', done => { - const emailAdapter = { - sendVerificationEmail: () => Promise.resolve(), - sendPasswordResetEmail: () => Promise.resolve(), - sendMail: () => Promise.resolve(), - }; - reconfigureServer({ - appName: 'unused', - verifyUserEmails: true, - emailAdapter: emailAdapter, - publicServerURL: 'http://localhost:8378/1', - }).then(async () => { - spyOn(emailAdapter, 'sendVerificationEmail'); - const user = new Parse.User(); - user.setPassword('asdf'); - user.setUsername('zxcv'); - user.setEmail('testIfEnabled@parse.com'); - await user.signUp(); - await jasmine.timeout(); - expect(emailAdapter.sendVerificationEmail).toHaveBeenCalled(); - user.fetch().then(() => { - expect(user.get('emailVerified')).toEqual(false); - done(); + it_id('5e558687-40f3-496c-9e4f-af6100bd1b2f')(it)( + 'sends verification email if email verification is enabled', + done => { + const emailAdapter = { + sendVerificationEmail: () => Promise.resolve(), + sendPasswordResetEmail: () => Promise.resolve(), + sendMail: () => Promise.resolve(), + }; + reconfigureServer({ + appName: 'unused', + verifyUserEmails: true, + emailAdapter: emailAdapter, + publicServerURL: 'http://localhost:8378/1', + }).then(async () => { + spyOn(emailAdapter, 'sendVerificationEmail'); + const user = new Parse.User(); + user.setPassword('asdf'); + user.setUsername('zxcv'); + user.setEmail('testIfEnabled@parse.com'); + await user.signUp(); + await jasmine.timeout(); + expect(emailAdapter.sendVerificationEmail).toHaveBeenCalled(); + user.fetch().then(() => { + expect(user.get('emailVerified')).toEqual(false); + done(); + }); }); - }); - }); + } + ); it('does not send verification email when verification is enabled and email is not set', done => { const emailAdapter = { @@ -278,7 +281,7 @@ describe('Custom Pages, Email Verification, Password Reset', () => { await user.signUp(); const verifyUserEmails = { - method: async (params) => { + method: async params => { expect(params.object).toBeInstanceOf(Parse.User); expect(params.ip).toBeDefined(); expect(params.master).toBeDefined(); @@ -305,43 +308,46 @@ describe('Custom Pages, Email Verification, Password Reset', () => { expect(verifyUserEmailsSpy).toHaveBeenCalledTimes(2); }); - it_id('2a5d24be-2ca5-4385-b580-1423bd392e43')(it)('allows user to login only after user clicks on the link to confirm email address if preventLoginWithUnverifiedEmail is set to true', async () => { - let sendEmailOptions; - const emailAdapter = { - sendVerificationEmail: options => { - sendEmailOptions = options; - }, - sendPasswordResetEmail: () => Promise.resolve(), - sendMail: () => {}, - }; - await reconfigureServer({ - appName: 'emailing app', - verifyUserEmails: true, - preventLoginWithUnverifiedEmail: true, - emailAdapter: emailAdapter, - publicServerURL: 'http://localhost:8378/1', - }); - let user = new Parse.User(); - user.setPassword('other-password'); - user.setUsername('user'); - user.set('email', 'user@example.com'); - await user.signUp(); - await jasmine.timeout(); - expect(sendEmailOptions).not.toBeUndefined(); - const response = await request({ - url: sendEmailOptions.link, - followRedirects: false, - }); - expect(response.status).toEqual(302); - expect(response.text).toEqual( - 'Found. Redirecting to http://localhost:8378/1/apps/verify_email_success.html?username=user' - ); - user = await new Parse.Query(Parse.User).first({ useMasterKey: true }); - expect(user.get('emailVerified')).toEqual(true); - user = await Parse.User.logIn('user', 'other-password'); - expect(typeof user).toBe('object'); - expect(user.get('emailVerified')).toBe(true); - }); + it_id('2a5d24be-2ca5-4385-b580-1423bd392e43')(it)( + 'allows user to login only after user clicks on the link to confirm email address if preventLoginWithUnverifiedEmail is set to true', + async () => { + let sendEmailOptions; + const emailAdapter = { + sendVerificationEmail: options => { + sendEmailOptions = options; + }, + sendPasswordResetEmail: () => Promise.resolve(), + sendMail: () => {}, + }; + await reconfigureServer({ + appName: 'emailing app', + verifyUserEmails: true, + preventLoginWithUnverifiedEmail: true, + emailAdapter: emailAdapter, + publicServerURL: 'http://localhost:8378/1', + }); + let user = new Parse.User(); + user.setPassword('other-password'); + user.setUsername('user'); + user.set('email', 'user@example.com'); + await user.signUp(); + await jasmine.timeout(); + expect(sendEmailOptions).not.toBeUndefined(); + const response = await request({ + url: sendEmailOptions.link, + followRedirects: false, + }); + expect(response.status).toEqual(302); + expect(response.text).toEqual( + 'Found. Redirecting to http://localhost:8378/1/apps/verify_email_success.html?username=user' + ); + user = await new Parse.Query(Parse.User).first({ useMasterKey: true }); + expect(user.get('emailVerified')).toEqual(true); + user = await Parse.User.logIn('user', 'other-password'); + expect(typeof user).toBe('object'); + expect(user.get('emailVerified')).toBe(true); + } + ); it('allows user to login if email is not verified but preventLoginWithUnverifiedEmail is set to false', done => { reconfigureServer({ @@ -381,34 +387,37 @@ describe('Custom Pages, Email Verification, Password Reset', () => { }); }); - it_id('a18a07af-0319-4f15-8237-28070c5948fa')(it)('does not allow signup with preventSignupWithUnverified', async () => { - let sendEmailOptions; - const emailAdapter = { - sendVerificationEmail: options => { - sendEmailOptions = options; - }, - sendPasswordResetEmail: () => Promise.resolve(), - sendMail: () => {}, - }; - await reconfigureServer({ - appName: 'test', - publicServerURL: 'http://localhost:1337/1', - verifyUserEmails: true, - preventLoginWithUnverifiedEmail: true, - preventSignupWithUnverifiedEmail: true, - emailAdapter, - }); - const newUser = new Parse.User(); - newUser.setPassword('asdf'); - newUser.setUsername('zxcv'); - newUser.set('email', 'test@example.com'); - await expectAsync(newUser.signUp()).toBeRejectedWith( - new Parse.Error(Parse.Error.EMAIL_NOT_FOUND, 'User email is not verified.') - ); - const user = await new Parse.Query(Parse.User).first({ useMasterKey: true }); - expect(user).toBeDefined(); - expect(sendEmailOptions).toBeDefined(); - }); + it_id('a18a07af-0319-4f15-8237-28070c5948fa')(it)( + 'does not allow signup with preventSignupWithUnverified', + async () => { + let sendEmailOptions; + const emailAdapter = { + sendVerificationEmail: options => { + sendEmailOptions = options; + }, + sendPasswordResetEmail: () => Promise.resolve(), + sendMail: () => {}, + }; + await reconfigureServer({ + appName: 'test', + publicServerURL: 'http://localhost:1337/1', + verifyUserEmails: true, + preventLoginWithUnverifiedEmail: true, + preventSignupWithUnverifiedEmail: true, + emailAdapter, + }); + const newUser = new Parse.User(); + newUser.setPassword('asdf'); + newUser.setUsername('zxcv'); + newUser.set('email', 'test@example.com'); + await expectAsync(newUser.signUp()).toBeRejectedWith( + new Parse.Error(Parse.Error.EMAIL_NOT_FOUND, 'User email is not verified.') + ); + const user = await new Parse.Query(Parse.User).first({ useMasterKey: true }); + expect(user).toBeDefined(); + expect(sendEmailOptions).toBeDefined(); + } + ); it('fails if you include an emailAdapter, set a publicServerURL, but have no appName and send a password reset email', done => { reconfigureServer({ @@ -616,87 +625,93 @@ describe('Custom Pages, Email Verification, Password Reset', () => { }); }); - it_id('45f550a2-a2b2-4b2b-b533-ccbf96139cc9')(it)('receives the app name and user in the adapter', done => { - let emailSent = false; - const emailAdapter = { - sendVerificationEmail: options => { - expect(options.appName).toEqual('emailing app'); - expect(options.user.get('email')).toEqual('user@parse.com'); - emailSent = true; - }, - sendPasswordResetEmail: () => Promise.resolve(), - sendMail: () => {}, - }; - reconfigureServer({ - appName: 'emailing app', - verifyUserEmails: true, - emailAdapter: emailAdapter, - publicServerURL: 'http://localhost:8378/1', - }).then(async () => { - const user = new Parse.User(); - user.setPassword('asdf'); - user.setUsername('zxcv'); - user.set('email', 'user@parse.com'); - await user.signUp(); - await jasmine.timeout(); - expect(emailSent).toBe(true); - done(); - }); - }); - - it_id('ea37ef62-aad8-4a17-8dfe-35e5b2986f0f')(it)('when you click the link in the email it sets emailVerified to true and redirects you', done => { - const user = new Parse.User(); - let sendEmailOptions; - const emailAdapter = { - sendVerificationEmail: options => { - sendEmailOptions = options; - }, - sendPasswordResetEmail: () => Promise.resolve(), - sendMail: () => {}, - }; - reconfigureServer({ - appName: 'emailing app', - verifyUserEmails: true, - emailAdapter: emailAdapter, - publicServerURL: 'http://localhost:8378/1', - }) - .then(() => { - user.setPassword('other-password'); - user.setUsername('user'); + it_id('45f550a2-a2b2-4b2b-b533-ccbf96139cc9')(it)( + 'receives the app name and user in the adapter', + done => { + let emailSent = false; + const emailAdapter = { + sendVerificationEmail: options => { + expect(options.appName).toEqual('emailing app'); + expect(options.user.get('email')).toEqual('user@parse.com'); + emailSent = true; + }, + sendPasswordResetEmail: () => Promise.resolve(), + sendMail: () => {}, + }; + reconfigureServer({ + appName: 'emailing app', + verifyUserEmails: true, + emailAdapter: emailAdapter, + publicServerURL: 'http://localhost:8378/1', + }).then(async () => { + const user = new Parse.User(); + user.setPassword('asdf'); + user.setUsername('zxcv'); user.set('email', 'user@parse.com'); - return user.signUp(); + await user.signUp(); + await jasmine.timeout(); + expect(emailSent).toBe(true); + done(); + }); + } + ); + + it_id('ea37ef62-aad8-4a17-8dfe-35e5b2986f0f')(it)( + 'when you click the link in the email it sets emailVerified to true and redirects you', + done => { + const user = new Parse.User(); + let sendEmailOptions; + const emailAdapter = { + sendVerificationEmail: options => { + sendEmailOptions = options; + }, + sendPasswordResetEmail: () => Promise.resolve(), + sendMail: () => {}, + }; + reconfigureServer({ + appName: 'emailing app', + verifyUserEmails: true, + emailAdapter: emailAdapter, + publicServerURL: 'http://localhost:8378/1', }) - .then(() => jasmine.timeout()) - .then(() => { - expect(sendEmailOptions).not.toBeUndefined(); - request({ - url: sendEmailOptions.link, - followRedirects: false, - }).then(response => { - expect(response.status).toEqual(302); - expect(response.text).toEqual( - 'Found. Redirecting to http://localhost:8378/1/apps/verify_email_success.html?username=user' - ); - user - .fetch() - .then( - () => { - expect(user.get('emailVerified')).toEqual(true); - done(); - }, - err => { + .then(() => { + user.setPassword('other-password'); + user.setUsername('user'); + user.set('email', 'user@parse.com'); + return user.signUp(); + }) + .then(() => jasmine.timeout()) + .then(() => { + expect(sendEmailOptions).not.toBeUndefined(); + request({ + url: sendEmailOptions.link, + followRedirects: false, + }).then(response => { + expect(response.status).toEqual(302); + expect(response.text).toEqual( + 'Found. Redirecting to http://localhost:8378/1/apps/verify_email_success.html?username=user' + ); + user + .fetch() + .then( + () => { + expect(user.get('emailVerified')).toEqual(true); + done(); + }, + err => { + jfail(err); + fail('this should not fail'); + done(); + } + ) + .catch(err => { jfail(err); - fail('this should not fail'); done(); - } - ) - .catch(err => { - jfail(err); - done(); - }); + }); + }); }); - }); - }); + } + ); it('redirects you to invalid link if you try to verify email incorrectly', done => { reconfigureServer({ diff --git a/spec/WinstonLoggerAdapter.spec.js b/spec/WinstonLoggerAdapter.spec.js index 81bdc213de..3d36ac9dc9 100644 --- a/spec/WinstonLoggerAdapter.spec.js +++ b/spec/WinstonLoggerAdapter.spec.js @@ -174,50 +174,53 @@ describe_only(() => { describe_only(() => { return process.env.PARSE_SERVER_LOG_LEVEL !== 'debug'; })('verbose logs', () => { - it_id('9ca72994-d255-4c11-a5a2-693c99ee2cdb')(it)('mask sensitive information in _User class', done => { - reconfigureServer({ verbose: true }) - .then(() => createTestUser()) - .then(() => { - const winstonLoggerAdapter = new WinstonLoggerAdapter(); - return winstonLoggerAdapter.query({ - from: new Date(Date.now() - 500), - size: 100, - level: 'verbose', - }); - }) - .then(results => { - const logString = JSON.stringify(results); - expect(logString.match(/\*\*\*\*\*\*\*\*/g).length).not.toBe(0); - expect(logString.match(/moon-y/g)).toBe(null); - - const headers = { - 'X-Parse-Application-Id': 'test', - 'X-Parse-REST-API-Key': 'rest', - }; - request({ - headers: headers, - url: 'http://localhost:8378/1/login?username=test&password=moon-y', - }).then(() => { + it_id('9ca72994-d255-4c11-a5a2-693c99ee2cdb')(it)( + 'mask sensitive information in _User class', + done => { + reconfigureServer({ verbose: true }) + .then(() => createTestUser()) + .then(() => { const winstonLoggerAdapter = new WinstonLoggerAdapter(); - return winstonLoggerAdapter - .query({ - from: new Date(Date.now() - 500), - size: 100, - level: 'verbose', - }) - .then(results => { - const logString = JSON.stringify(results); - expect(logString.match(/\*\*\*\*\*\*\*\*/g).length).not.toBe(0); - expect(logString.match(/moon-y/g)).toBe(null); - done(); - }); + return winstonLoggerAdapter.query({ + from: new Date(Date.now() - 500), + size: 100, + level: 'verbose', + }); + }) + .then(results => { + const logString = JSON.stringify(results); + expect(logString.match(/\*\*\*\*\*\*\*\*/g).length).not.toBe(0); + expect(logString.match(/moon-y/g)).toBe(null); + + const headers = { + 'X-Parse-Application-Id': 'test', + 'X-Parse-REST-API-Key': 'rest', + }; + request({ + headers: headers, + url: 'http://localhost:8378/1/login?username=test&password=moon-y', + }).then(() => { + const winstonLoggerAdapter = new WinstonLoggerAdapter(); + return winstonLoggerAdapter + .query({ + from: new Date(Date.now() - 500), + size: 100, + level: 'verbose', + }) + .then(results => { + const logString = JSON.stringify(results); + expect(logString.match(/\*\*\*\*\*\*\*\*/g).length).not.toBe(0); + expect(logString.match(/moon-y/g)).toBe(null); + done(); + }); + }); + }) + .catch(err => { + fail(JSON.stringify(err)); + done(); }); - }) - .catch(err => { - fail(JSON.stringify(err)); - done(); - }); - }); + } + ); it('verbose logs should interpolate string', async () => { await reconfigureServer({ verbose: true }); diff --git a/spec/batch.spec.js b/spec/batch.spec.js index 8c9ef27a6b..66a743b1d9 100644 --- a/spec/batch.spec.js +++ b/spec/batch.spec.js @@ -242,9 +242,7 @@ describe('batch', () => { const results = await query.find(); expect(createSpy.calls.count()).toBe(2); for (let i = 0; i + 1 < createSpy.calls.length; i = i + 2) { - expect(createSpy.calls.argsFor(i)[3]).toBe( - createSpy.calls.argsFor(i + 1)[3] - ); + expect(createSpy.calls.argsFor(i)[3]).toBe(createSpy.calls.argsFor(i + 1)[3]); } expect(results.map(result => result.get('key')).sort()).toEqual(['value1', 'value2']); }); diff --git a/spec/helper.js b/spec/helper.js index 08a3a4ac22..c5c35e474c 100644 --- a/spec/helper.js +++ b/spec/helper.js @@ -231,7 +231,11 @@ afterEach(function (done) { const afterLogOut = async () => { // Jasmine process uses one connection if (Object.keys(openConnections).length > 1) { - console.warn(`There were ${Object.keys(openConnections).length} open connections to the server left after the test finished`); + console.warn( + `There were ${ + Object.keys(openConnections).length + } open connections to the server left after the test finished` + ); } await TestUtils.destroyAllDataPermanently(true); SchemaCache.clear(); diff --git a/spec/rest.spec.js b/spec/rest.spec.js index fed64c988b..92e25c3941 100644 --- a/spec/rest.spec.js +++ b/spec/rest.spec.js @@ -787,21 +787,24 @@ describe('rest create', () => { ); }); - it_id('3ce563bf-93aa-4d0b-9af9-c5fb246ac9fc')(it)('cannot get object in _GlobalConfig if not masterKey through pointer', async () => { - await Parse.Config.save({ privateData: 'secret' }, { privateData: true }); - const obj2 = new Parse.Object('TestObject'); - obj2.set('globalConfigPointer', { - __type: 'Pointer', - className: '_GlobalConfig', - objectId: 1, - }); - await obj2.save(); - const query = new Parse.Query('TestObject'); - query.include('globalConfigPointer'); - await expectAsync(query.get(obj2.id)).toBeRejectedWithError( - "Clients aren't allowed to perform the get operation on the _GlobalConfig collection." - ); - }); + it_id('3ce563bf-93aa-4d0b-9af9-c5fb246ac9fc')(it)( + 'cannot get object in _GlobalConfig if not masterKey through pointer', + async () => { + await Parse.Config.save({ privateData: 'secret' }, { privateData: true }); + const obj2 = new Parse.Object('TestObject'); + obj2.set('globalConfigPointer', { + __type: 'Pointer', + className: '_GlobalConfig', + objectId: 1, + }); + await obj2.save(); + const query = new Parse.Query('TestObject'); + query.include('globalConfigPointer'); + await expectAsync(query.get(obj2.id)).toBeRejectedWithError( + "Clients aren't allowed to perform the get operation on the _GlobalConfig collection." + ); + } + ); it('locks down session', done => { let currentUser; diff --git a/spec/schemas.spec.js b/spec/schemas.spec.js index d02f081ccf..6451ad8e4d 100644 --- a/spec/schemas.spec.js +++ b/spec/schemas.spec.js @@ -3650,93 +3650,102 @@ describe('schemas', () => { }); }); - it_id('5d0926b2-2d31-459d-a2b1-23ecc32e72a3')(it_exclude_dbs(['postgres']))('get indexes on startup', done => { - const obj = new Parse.Object('TestObject'); - obj - .save() - .then(() => { - return reconfigureServer({ - appId: 'test', - restAPIKey: 'test', - publicServerURL: 'http://localhost:8378/1', - }); - }) - .then(() => { - request({ - url: 'http://localhost:8378/1/schemas/TestObject', - headers: masterKeyHeaders, - json: true, - }).then(response => { - expect(response.data.indexes._id_).toBeDefined(); - done(); + it_id('5d0926b2-2d31-459d-a2b1-23ecc32e72a3')(it_exclude_dbs(['postgres']))( + 'get indexes on startup', + done => { + const obj = new Parse.Object('TestObject'); + obj + .save() + .then(() => { + return reconfigureServer({ + appId: 'test', + restAPIKey: 'test', + publicServerURL: 'http://localhost:8378/1', + }); + }) + .then(() => { + request({ + url: 'http://localhost:8378/1/schemas/TestObject', + headers: masterKeyHeaders, + json: true, + }).then(response => { + expect(response.data.indexes._id_).toBeDefined(); + done(); + }); }); - }); - }); + } + ); - it_id('9f2ba51a-6a9c-4b25-9da0-51c82ac65f90')(it_exclude_dbs(['postgres']))('get compound indexes on startup', done => { - const obj = new Parse.Object('TestObject'); - obj.set('subject', 'subject'); - obj.set('comment', 'comment'); - obj - .save() - .then(() => { - return config.database.adapter.createIndex('TestObject', { - subject: 'text', - comment: 'text', - }); - }) - .then(() => { - return reconfigureServer({ - appId: 'test', - restAPIKey: 'test', - publicServerURL: 'http://localhost:8378/1', + it_id('9f2ba51a-6a9c-4b25-9da0-51c82ac65f90')(it_exclude_dbs(['postgres']))( + 'get compound indexes on startup', + done => { + const obj = new Parse.Object('TestObject'); + obj.set('subject', 'subject'); + obj.set('comment', 'comment'); + obj + .save() + .then(() => { + return config.database.adapter.createIndex('TestObject', { + subject: 'text', + comment: 'text', + }); + }) + .then(() => { + return reconfigureServer({ + appId: 'test', + restAPIKey: 'test', + publicServerURL: 'http://localhost:8378/1', + }); + }) + .then(() => { + request({ + url: 'http://localhost:8378/1/schemas/TestObject', + headers: masterKeyHeaders, + json: true, + }).then(response => { + expect(response.data.indexes._id_).toBeDefined(); + expect(response.data.indexes._id_._id).toEqual(1); + expect(response.data.indexes.subject_text_comment_text).toBeDefined(); + expect(response.data.indexes.subject_text_comment_text.subject).toEqual('text'); + expect(response.data.indexes.subject_text_comment_text.comment).toEqual('text'); + done(); + }); }); - }) - .then(() => { - request({ - url: 'http://localhost:8378/1/schemas/TestObject', - headers: masterKeyHeaders, - json: true, - }).then(response => { - expect(response.data.indexes._id_).toBeDefined(); - expect(response.data.indexes._id_._id).toEqual(1); - expect(response.data.indexes.subject_text_comment_text).toBeDefined(); - expect(response.data.indexes.subject_text_comment_text.subject).toEqual('text'); - expect(response.data.indexes.subject_text_comment_text.comment).toEqual('text'); + } + ); + + it_id('cbd5d897-b938-43a4-8f5a-5d02dd2be9be')(it_exclude_dbs(['postgres']))( + 'cannot update to duplicate value on unique index', + done => { + const index = { + code: 1, + }; + const obj1 = new Parse.Object('UniqueIndexClass'); + obj1.set('code', 1); + const obj2 = new Parse.Object('UniqueIndexClass'); + obj2.set('code', 2); + const adapter = config.database.adapter; + adapter + ._adaptiveCollection('UniqueIndexClass') + .then(collection => { + return collection._ensureSparseUniqueIndexInBackground(index); + }) + .then(() => { + return obj1.save(); + }) + .then(() => { + return obj2.save(); + }) + .then(() => { + obj1.set('code', 2); + return obj1.save(); + }) + .then(done.fail) + .catch(error => { + expect(error.code).toEqual(Parse.Error.DUPLICATE_VALUE); done(); }); - }); - }); - - it_id('cbd5d897-b938-43a4-8f5a-5d02dd2be9be')(it_exclude_dbs(['postgres']))('cannot update to duplicate value on unique index', done => { - const index = { - code: 1, - }; - const obj1 = new Parse.Object('UniqueIndexClass'); - obj1.set('code', 1); - const obj2 = new Parse.Object('UniqueIndexClass'); - obj2.set('code', 2); - const adapter = config.database.adapter; - adapter - ._adaptiveCollection('UniqueIndexClass') - .then(collection => { - return collection._ensureSparseUniqueIndexInBackground(index); - }) - .then(() => { - return obj1.save(); - }) - .then(() => { - return obj2.save(); - }) - .then(() => { - obj1.set('code', 2); - return obj1.save(); - }) - .then(done.fail) - .catch(error => { - expect(error.code).toEqual(Parse.Error.DUPLICATE_VALUE); - done(); - }); - }); + } + ); }); }); diff --git a/spec/support/CurrentSpecReporter.js b/spec/support/CurrentSpecReporter.js index 0b3aab4fba..b22059321f 100755 --- a/spec/support/CurrentSpecReporter.js +++ b/spec/support/CurrentSpecReporter.js @@ -11,15 +11,15 @@ global.currentSpec = null; */ const flakyTests = [ // Timeout - "ParseLiveQuery handle invalid websocket payload length", + 'ParseLiveQuery handle invalid websocket payload length', // Unhandled promise rejection: TypeError: message.split is not a function - "rest query query internal field", + 'rest query query internal field', // TypeError: Cannot read properties of undefined (reading 'link') - "UserController sendVerificationEmail parseFrameURL not provided uses publicServerURL", + 'UserController sendVerificationEmail parseFrameURL not provided uses publicServerURL', // TypeError: Cannot read properties of undefined (reading 'link') - "UserController sendVerificationEmail parseFrameURL provided uses parseFrameURL and includes the destination in the link parameter", + 'UserController sendVerificationEmail parseFrameURL provided uses parseFrameURL and includes the destination in the link parameter', // Expected undefined to be defined - "Email Verification Token Expiration: sets the _email_verify_token_expires_at and _email_verify_token fields after user SignUp", + 'Email Verification Token Expiration: sets the _email_verify_token_expires_at and _email_verify_token fields after user SignUp', ]; /** The minimum execution time in seconds for a test to be considered slow. */ @@ -50,29 +50,34 @@ class CurrentSpecReporter { } } -global.displayTestStats = function() { - const times = Object.values(timerMap).sort((a,b) => b - a).filter(time => time >= slowTestLimit); +global.displayTestStats = function () { + const times = Object.values(timerMap) + .sort((a, b) => b - a) + .filter(time => time >= slowTestLimit); if (times.length > 0) { console.log(`Slow tests with execution time >=${slowTestLimit}s:`); } - times.forEach((time) => { - console.warn(`${time.toFixed(1)}s:`, Object.keys(timerMap).find(key => timerMap[key] === time)); + times.forEach(time => { + console.warn( + `${time.toFixed(1)}s:`, + Object.keys(timerMap).find(key => timerMap[key] === time) + ); }); console.log('\n'); - duplicates.forEach((spec) => { + duplicates.forEach(spec => { console.warn('Duplicate spec: ' + spec); }); console.log('\n'); - Object.keys(retryMap).forEach((spec) => { + Object.keys(retryMap).forEach(spec => { console.warn(`Flaky test: ${spec} failed ${retryMap[spec]} times`); }); console.log('\n'); }; -global.retryFlakyTests = function() { +global.retryFlakyTests = function () { const originalSpecConstructor = jasmine.Spec; - jasmine.Spec = function(attrs) { + jasmine.Spec = function (attrs) { const spec = new originalSpecConstructor(attrs); const originalTestFn = spec.queueableFn.fn; const runOriginalTest = () => { @@ -81,12 +86,12 @@ global.retryFlakyTests = function() { return originalTestFn(); } else { // handle done() callback - return new Promise((resolve) => { + return new Promise(resolve => { originalTestFn(resolve); }); } }; - spec.queueableFn.fn = async function() { + spec.queueableFn.fn = async function () { const isFlaky = flakyTests.includes(spec.result.fullName); const runs = isFlaky ? retries : 1; let exceptionCaught; @@ -101,8 +106,8 @@ global.retryFlakyTests = function() { } catch (exception) { exceptionCaught = exception; } - const failed = !spec.markedPending && - (exceptionCaught || spec.result.failedExpectations.length != 0); + const failed = + !spec.markedPending && (exceptionCaught || spec.result.failedExpectations.length != 0); if (!failed) { break; } @@ -117,6 +122,6 @@ global.retryFlakyTests = function() { }; return spec; }; -} +}; module.exports = CurrentSpecReporter; diff --git a/spec/support/MockLdapServer.js b/spec/support/MockLdapServer.js index 935f0703d6..270a9603cc 100644 --- a/spec/support/MockLdapServer.js +++ b/spec/support/MockLdapServer.js @@ -10,8 +10,9 @@ function newServer(port, dn, provokeSearchError = false, ssl = false) { const server = ssl ? ldapjs.createServer(tlsOptions) : ldapjs.createServer(); server.bind('o=example', function (req, res, next) { - if (req.dn.toString() !== dn || req.credentials !== 'secret') - { return next(new ldapjs.InvalidCredentialsError()); } + if (req.dn.toString() !== dn || req.credentials !== 'secret') { + return next(new ldapjs.InvalidCredentialsError()); + } res.end(); return next(); }); diff --git a/spec/vulnerabilities.spec.js b/spec/vulnerabilities.spec.js index 5eb3c6cb93..dec62b6b17 100644 --- a/spec/vulnerabilities.spec.js +++ b/spec/vulnerabilities.spec.js @@ -40,7 +40,9 @@ describe('Vulnerabilities', () => { it('refuses session token of user with poisoned object ID', async () => { await expectAsync( new Parse.Query(Parse.User).find({ sessionToken: poisonedUser.getSessionToken() }) - ).toBeRejectedWith(new Parse.Error(Parse.Error.INTERNAL_SERVER_ERROR, 'Invalid object ID.')); + ).toBeRejectedWith( + new Parse.Error(Parse.Error.INTERNAL_SERVER_ERROR, 'Invalid object ID.') + ); await new Parse.Query(Parse.User).find({ sessionToken: innocentUser.getSessionToken() }); }); }); @@ -248,37 +250,40 @@ describe('Vulnerabilities', () => { ); }); - it_id('e8b5f1e1-8326-4c70-b5f4-1e8678dfff8d')(it)('denies creating a hook with polluted data', async () => { - const express = require('express'); - const bodyParser = require('body-parser'); - const port = 34567; - const hookServerURL = 'http://localhost:' + port; - const app = express(); - app.use(bodyParser.json({ type: '*/*' })); - const server = await new Promise(resolve => { - const res = app.listen(port, undefined, () => resolve(res)); - }); - app.post('/BeforeSave', function (req, res) { - const object = Parse.Object.fromJSON(req.body.object); - object.set('hello', 'world'); - object.set('obj', { - constructor: { - prototype: { - dummy: 0, + it_id('e8b5f1e1-8326-4c70-b5f4-1e8678dfff8d')(it)( + 'denies creating a hook with polluted data', + async () => { + const express = require('express'); + const bodyParser = require('body-parser'); + const port = 34567; + const hookServerURL = 'http://localhost:' + port; + const app = express(); + app.use(bodyParser.json({ type: '*/*' })); + const server = await new Promise(resolve => { + const res = app.listen(port, undefined, () => resolve(res)); + }); + app.post('/BeforeSave', function (req, res) { + const object = Parse.Object.fromJSON(req.body.object); + object.set('hello', 'world'); + object.set('obj', { + constructor: { + prototype: { + dummy: 0, + }, }, - }, + }); + res.json({ success: object }); }); - res.json({ success: object }); - }); - await Parse.Hooks.createTrigger('TestObject', 'beforeSave', hookServerURL + '/BeforeSave'); - await expectAsync(new Parse.Object('TestObject').save()).toBeRejectedWith( - new Parse.Error( - Parse.Error.INVALID_KEY_NAME, - 'Prohibited keyword in request data: {"key":"constructor"}.' - ) - ); - await new Promise(resolve => server.close(resolve)); - }); + await Parse.Hooks.createTrigger('TestObject', 'beforeSave', hookServerURL + '/BeforeSave'); + await expectAsync(new Parse.Object('TestObject').save()).toBeRejectedWith( + new Parse.Error( + Parse.Error.INVALID_KEY_NAME, + 'Prohibited keyword in request data: {"key":"constructor"}.' + ) + ); + await new Promise(resolve => server.close(resolve)); + } + ); it('denies write request with custom denylist of key/value', async () => { await reconfigureServer({ diff --git a/src/Auth.js b/src/Auth.js index dbb34f9a62..6874858346 100644 --- a/src/Auth.js +++ b/src/Auth.js @@ -434,11 +434,15 @@ const findUsersWithAuthData = (config, authData) => { }; const hasMutatedAuthData = (authData, userAuthData) => { - if (!userAuthData) { return { hasMutatedAuthData: true, mutatedAuthData: authData }; } + if (!userAuthData) { + return { hasMutatedAuthData: true, mutatedAuthData: authData }; + } const mutatedAuthData = {}; Object.keys(authData).forEach(provider => { // Anonymous provider is not handled this way - if (provider === 'anonymous') { return; } + if (provider === 'anonymous') { + return; + } const providerData = authData[provider]; const userProviderAuthData = userAuthData[provider]; if (!isDeepStrictEqual(providerData, userProviderAuthData)) { diff --git a/src/Config.js b/src/Config.js index c4884434ca..8d9123906e 100644 --- a/src/Config.js +++ b/src/Config.js @@ -139,7 +139,9 @@ export class Config { } static validateCustomPages(customPages) { - if (!customPages) { return; } + if (!customPages) { + return; + } if (Object.prototype.toString.call(customPages) !== '[object Object]') { throw Error('Parse Server option customPages must be an object.'); @@ -209,7 +211,9 @@ export class Config { } static validateSchemaOptions(schema: SchemaOptions) { - if (!schema) { return; } + if (!schema) { + return; + } if (Object.prototype.toString.call(schema) !== '[object Object]') { throw 'Parse Server option schema must be an object.'; } diff --git a/src/Controllers/DatabaseController.js b/src/Controllers/DatabaseController.js index 0050216e2c..85bf5e8119 100644 --- a/src/Controllers/DatabaseController.js +++ b/src/Controllers/DatabaseController.js @@ -142,7 +142,9 @@ const filterSensitiveData = ( object: any ) => { let userId = null; - if (auth && auth.user) { userId = auth.user.id; } + if (auth && auth.user) { + userId = auth.user.id; + } // replace protectedFields when using pointer-permissions const perms = @@ -1592,12 +1594,18 @@ class DatabaseController { schema && schema.getClassLevelPermissions ? schema.getClassLevelPermissions(className) : schema; - if (!perms) { return null; } + if (!perms) { + return null; + } const protectedFields = perms.protectedFields; - if (!protectedFields) { return null; } + if (!protectedFields) { + return null; + } - if (aclGroup.indexOf(query.objectId) > -1) { return null; } + if (aclGroup.indexOf(query.objectId) > -1) { + return null; + } // for queries where "keys" are set and do not include all 'userField':{field}, // we have to transparently include it, and then remove before returning to client diff --git a/src/Controllers/FilesController.js b/src/Controllers/FilesController.js index a88c527b00..21ab5efe4c 100644 --- a/src/Controllers/FilesController.js +++ b/src/Controllers/FilesController.js @@ -18,7 +18,7 @@ export class FilesController extends AdaptableController { const extname = path.extname(filename); const hasExtension = extname.length > 0; - const mime = (await import('mime')).default + const mime = (await import('mime')).default; if (!hasExtension && contentType && mime.getExtension(contentType)) { filename = filename + '.' + mime.getExtension(contentType); } else if (hasExtension && !contentType) { @@ -34,7 +34,7 @@ export class FilesController extends AdaptableController { return { url: location, name: filename, - } + }; } deleteFile(config, filename) { diff --git a/src/Controllers/SchemaController.js b/src/Controllers/SchemaController.js index 5d539055cf..ee1d4abf3b 100644 --- a/src/Controllers/SchemaController.js +++ b/src/Controllers/SchemaController.js @@ -666,10 +666,18 @@ const VolatileClassesSchemas = [ ]; const dbTypeMatchesObjectType = (dbType: SchemaField | string, objectType: SchemaField) => { - if (dbType.type !== objectType.type) { return false; } - if (dbType.targetClass !== objectType.targetClass) { return false; } - if (dbType === objectType.type) { return true; } - if (dbType.type === objectType.type) { return true; } + if (dbType.type !== objectType.type) { + return false; + } + if (dbType.targetClass !== objectType.targetClass) { + return false; + } + if (dbType === objectType.type) { + return true; + } + if (dbType.type === objectType.type) { + return true; + } return false; }; @@ -1020,7 +1028,9 @@ export default class SchemaController { } const fieldType = fields[fieldName]; const error = fieldTypeIsInvalid(fieldType); - if (error) { return { code: error.code, error: error.message }; } + if (error) { + return { code: error.code, error: error.message }; + } if (fieldType.defaultValue !== undefined) { let defaultValueType = getType(fieldType.defaultValue); if (typeof defaultValueType === 'string') { diff --git a/src/Controllers/UserController.js b/src/Controllers/UserController.js index ac896c51c2..41d48795b1 100644 --- a/src/Controllers/UserController.js +++ b/src/Controllers/UserController.js @@ -122,7 +122,9 @@ export class UserController extends AdaptableController { if (expiresDate && expiresDate.__type == 'Date') { expiresDate = new Date(expiresDate.iso); } - if (expiresDate < new Date()) { throw 'The password reset link has expired'; } + if (expiresDate < new Date()) { + throw 'The password reset link has expired'; + } } return results[0]; }); @@ -213,7 +215,7 @@ export class UserController extends AdaptableController { master, installationId, ip, - resendRequest: true + resendRequest: true, }); if (!shouldSend) { return; @@ -226,7 +228,12 @@ export class UserController extends AdaptableController { if (!aUser || aUser.emailVerified) { throw undefined; } - const generate = await this.regenerateEmailVerifyToken(aUser, req.auth?.isMaster, req.auth?.installationId, req.ip); + const generate = await this.regenerateEmailVerifyToken( + aUser, + req.auth?.isMaster, + req.auth?.installationId, + req.ip + ); if (generate) { this.sendVerificationEmail(aUser, req); } diff --git a/src/GraphQL/parseGraphQLUtils.js b/src/GraphQL/parseGraphQLUtils.js index f1194784cb..d7164525a7 100644 --- a/src/GraphQL/parseGraphQLUtils.js +++ b/src/GraphQL/parseGraphQLUtils.js @@ -23,7 +23,9 @@ export const extractKeysAndInclude = selectedFields => { selectedFields = selectedFields.filter(field => !field.includes('__typename')); // Handles "id" field for both current and included objects selectedFields = selectedFields.map(field => { - if (field === 'id') { return 'objectId'; } + if (field === 'id') { + return 'objectId'; + } return field.endsWith('.id') ? `${field.substring(0, field.lastIndexOf('.id'))}.objectId` : field; diff --git a/src/ParseServer.js b/src/ParseServer.js index db30637db8..05f17cf33b 100644 --- a/src/ParseServer.js +++ b/src/ParseServer.js @@ -88,7 +88,9 @@ class ParseServer { if (!Object.prototype.hasOwnProperty.call(ref, key)) { result.push(prefix + key); } else { - if (ref[key] === '') { continue; } + if (ref[key] === '') { + continue; + } let res = []; if (Array.isArray(original[key]) && Array.isArray(ref[key])) { const type = ref[key][0]; diff --git a/src/PromiseRouter.js b/src/PromiseRouter.js index ed214c7469..cb67aeee04 100644 --- a/src/PromiseRouter.js +++ b/src/PromiseRouter.js @@ -8,7 +8,6 @@ import Parse from 'parse/node'; import express from 'express'; import log from './logger'; -import { inspect } from 'util'; const Layer = require('express/lib/router/layer'); function validateParameter(key, value) { @@ -187,7 +186,6 @@ function makeExpressHandler(appId, promiseHandler) { }; } - function maskSensitiveUrl(req) { let maskUrl = req.originalUrl.toString(); const shouldMaskUrl = diff --git a/src/RestQuery.js b/src/RestQuery.js index 3c53292340..2c72a8bb9d 100644 --- a/src/RestQuery.js +++ b/src/RestQuery.js @@ -46,7 +46,7 @@ async function RestQuery({ runAfterFind = true, runBeforeFind = true, context, - response + response, }) { if (![RestQuery.Method.find, RestQuery.Method.get].includes(method)) { throw new Parse.Error(Parse.Error.INVALID_QUERY, 'bad query type'); @@ -751,7 +751,12 @@ _UnsafeRestQuery.prototype.runFind = async function (options = {}) { if (options.op) { findOptions.op = options.op; } - const results = await this.config.database.find(this.className, this.restWhere, findOptions, this.auth); + const results = await this.config.database.find( + this.className, + this.restWhere, + findOptions, + this.auth + ); if (this.className === '_User' && !findOptions.explain) { for (var result of results) { this.cleanResultAuthData(result); diff --git a/src/RestWrite.js b/src/RestWrite.js index b01af2e268..0ab4b628e9 100644 --- a/src/RestWrite.js +++ b/src/RestWrite.js @@ -27,7 +27,18 @@ import { requiredColumns } from './Controllers/SchemaController'; // RestWrite will handle objectId, createdAt, and updatedAt for // everything. It also knows to use triggers and special modifications // for the _User class. -function RestWrite(config, auth, className, query, data, originalData, clientSDK, context, action, responseObject) { +function RestWrite( + config, + auth, + className, + query, + data, + originalData, + clientSDK, + context, + action, + responseObject +) { if (auth.isReadOnly) { throw new Parse.Error( Parse.Error.OPERATION_FORBIDDEN, @@ -508,7 +519,9 @@ RestWrite.prototype.ensureUniqueAuthDataId = async function () { key => this.data.authData[key] && this.data.authData[key].id ); - if (!hasAuthDataId) { return; } + if (!hasAuthDataId) { + return; + } const r = await Auth.findUsersWithAuthData(this.config, this.data.authData); const results = this.filteredObjectsByACL(r); @@ -551,7 +564,6 @@ RestWrite.prototype.handleAuthData = async function (authData) { // User found with provided authData if (results.length === 1) { - this.storage.authProvider = Object.keys(authData).join(','); const { hasMutatedAuthData, mutatedAuthData } = Auth.hasMutatedAuthData( @@ -813,7 +825,9 @@ RestWrite.prototype._validateEmail = function () { }; RestWrite.prototype._validatePasswordPolicy = function () { - if (!this.config.passwordPolicy) { return Promise.resolve(); } + if (!this.config.passwordPolicy) { + return Promise.resolve(); + } return this._validatePasswordRequirements().then(() => { return this._validatePasswordHistory(); }); @@ -847,18 +861,20 @@ RestWrite.prototype._validatePasswordRequirements = function () { if (this.config.passwordPolicy.doNotAllowUsername === true) { if (this.data.username) { // username is not passed during password reset - if (this.data.password.indexOf(this.data.username) >= 0) - { return Promise.reject(new Parse.Error(Parse.Error.VALIDATION_ERROR, containsUsernameError)); } + if (this.data.password.indexOf(this.data.username) >= 0) { + return Promise.reject(new Parse.Error(Parse.Error.VALIDATION_ERROR, containsUsernameError)); + } } else { // retrieve the User object using objectId during password reset return this.config.database.find('_User', { objectId: this.objectId() }).then(results => { if (results.length != 1) { throw undefined; } - if (this.data.password.indexOf(results[0].username) >= 0) - { return Promise.reject( - new Parse.Error(Parse.Error.VALIDATION_ERROR, containsUsernameError) - ); } + if (this.data.password.indexOf(results[0].username) >= 0) { + return Promise.reject( + new Parse.Error(Parse.Error.VALIDATION_ERROR, containsUsernameError) + ); + } return Promise.resolve(); }); } @@ -882,19 +898,21 @@ RestWrite.prototype._validatePasswordHistory = function () { } const user = results[0]; let oldPasswords = []; - if (user._password_history) - { oldPasswords = _.take( - user._password_history, - this.config.passwordPolicy.maxPasswordHistory - 1 - ); } + if (user._password_history) { + oldPasswords = _.take( + user._password_history, + this.config.passwordPolicy.maxPasswordHistory - 1 + ); + } oldPasswords.push(user.password); const newPassword = this.data.password; // compare the new password hash with all old password hashes const promises = oldPasswords.map(function (hash) { return passwordCrypto.compare(newPassword, hash).then(result => { - if (result) - // reject if there is a match - { return Promise.reject('REPEAT_PASSWORD'); } + if (result) { + // reject if there is a match + return Promise.reject('REPEAT_PASSWORD'); + } return Promise.resolve(); }); }); @@ -904,14 +922,15 @@ RestWrite.prototype._validatePasswordHistory = function () { return Promise.resolve(); }) .catch(err => { - if (err === 'REPEAT_PASSWORD') - // a match was found - { return Promise.reject( - new Parse.Error( - Parse.Error.VALIDATION_ERROR, - `New password should not be the same as last ${this.config.passwordPolicy.maxPasswordHistory} passwords.` - ) - ); } + if (err === 'REPEAT_PASSWORD') { + // a match was found + return Promise.reject( + new Parse.Error( + Parse.Error.VALIDATION_ERROR, + `New password should not be the same as last ${this.config.passwordPolicy.maxPasswordHistory} passwords.` + ) + ); + } throw err; }); }); @@ -945,10 +964,16 @@ RestWrite.prototype.createSessionTokenIfNeeded = async function () { // Get verification conditions which can be booleans or functions; the purpose of this async/await // structure is to avoid unnecessarily executing subsequent functions if previous ones fail in the // conditional statement below, as a developer may decide to execute expensive operations in them - const verifyUserEmails = async () => this.config.verifyUserEmails === true || (typeof this.config.verifyUserEmails === 'function' && await Promise.resolve(this.config.verifyUserEmails(request)) === true); - const preventLoginWithUnverifiedEmail = async () => this.config.preventLoginWithUnverifiedEmail === true || (typeof this.config.preventLoginWithUnverifiedEmail === 'function' && await Promise.resolve(this.config.preventLoginWithUnverifiedEmail(request)) === true); + const verifyUserEmails = async () => + this.config.verifyUserEmails === true || + (typeof this.config.verifyUserEmails === 'function' && + (await Promise.resolve(this.config.verifyUserEmails(request))) === true); + const preventLoginWithUnverifiedEmail = async () => + this.config.preventLoginWithUnverifiedEmail === true || + (typeof this.config.preventLoginWithUnverifiedEmail === 'function' && + (await Promise.resolve(this.config.preventLoginWithUnverifiedEmail(request))) === true); // If verification is required - if (await verifyUserEmails() && await preventLoginWithUnverifiedEmail()) { + if ((await verifyUserEmails()) && (await preventLoginWithUnverifiedEmail())) { this.storage.rejectSignup = true; return; } diff --git a/src/Routers/ClassesRouter.js b/src/Routers/ClassesRouter.js index 602b82078f..75e934bca4 100644 --- a/src/Routers/ClassesRouter.js +++ b/src/Routers/ClassesRouter.js @@ -34,18 +34,17 @@ export class ClassesRouter extends PromiseRouter { } const triggerResponse = new TriggerResponse(); - const response = await rest - .find( - req.config, - req.auth, - this.className(req), - body.where, - options, - req.info.clientSDK, - req.info.context, - triggerResponse - ); - return triggerResponse.toResponseObject({response}); + const response = await rest.find( + req.config, + req.auth, + this.className(req), + body.where, + options, + req.info.clientSDK, + req.info.context, + triggerResponse + ); + return triggerResponse.toResponseObject({ response }); } // Returns a promise for a {response} object. @@ -79,17 +78,16 @@ export class ClassesRouter extends PromiseRouter { } const responseObject = new TriggerResponse(); - const response = await rest - .get( - req.config, - req.auth, - this.className(req), - req.params.objectId, - options, - req.info.clientSDK, - req.info.context, - responseObject - ); + const response = await rest.get( + req.config, + req.auth, + this.className(req), + req.params.objectId, + options, + req.info.clientSDK, + req.info.context, + responseObject + ); if (!response.results || response.results.length == 0) { throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.'); } @@ -104,7 +102,7 @@ export class ClassesRouter extends PromiseRouter { } } return responseObject.toResponseObject({ - response: response.results[0] + response: response.results[0], }); } @@ -117,7 +115,7 @@ export class ClassesRouter extends PromiseRouter { throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, 'Invalid object ID.'); } const responseObject = new TriggerResponse(); - const response = await rest.create( + const response = await rest.create( req.config, req.auth, this.className(req), @@ -149,10 +147,16 @@ export class ClassesRouter extends PromiseRouter { async handleDelete(req) { const response = new TriggerResponse(); - await rest - .del(req.config, req.auth, this.className(req), req.params.objectId, req.info.context, response); + await rest.del( + req.config, + req.auth, + this.className(req), + req.params.objectId, + req.info.context, + response + ); return response.toResponseObject({ - response: {} + response: {}, }); } @@ -244,8 +248,12 @@ export class ClassesRouter extends PromiseRouter { mountRoutes() { this.route('GET', '/classes/:className', req => this.handleFind(req)); this.route('GET', '/classes/:className/:objectId', req => this.handleGet(req)); - this.route('POST', '/classes/:className', promiseEnsureIdempotency, req => this.handleCreate(req)); - this.route('PUT', '/classes/:className/:objectId', promiseEnsureIdempotency, req => this.handleUpdate(req)) + this.route('POST', '/classes/:className', promiseEnsureIdempotency, req => + this.handleCreate(req) + ); + this.route('PUT', '/classes/:className/:objectId', promiseEnsureIdempotency, req => + this.handleUpdate(req) + ); this.route('DELETE', '/classes/:className/:objectId', req => this.handleDelete(req)); } } diff --git a/src/Routers/FunctionsRouter.js b/src/Routers/FunctionsRouter.js index 0c99eb5105..726e8dfb37 100644 --- a/src/Routers/FunctionsRouter.js +++ b/src/Routers/FunctionsRouter.js @@ -1,7 +1,7 @@ // FunctionsRouter.js var Parse = require('parse/node').Parse, -triggers = require('../triggers'); + triggers = require('../triggers'); import PromiseRouter from '../PromiseRouter'; import { promiseEnforceMasterKeyAccess, promiseEnsureIdempotency } from '../middlewares'; @@ -107,14 +107,14 @@ export class FunctionsRouter extends PromiseRouter { const functionName = req.params.functionName; const applicationId = req.config.applicationId; const theFunction = triggers.getFunction(functionName, applicationId); - + if (!theFunction) { throw new Parse.Error(Parse.Error.SCRIPT_FAILED, `Invalid function: "${functionName}"`); } let params = Object.assign({}, req.body, req.query); params = parseParams(params, req.config); - + const request = { params, master: req.auth?.isMaster, @@ -128,18 +128,18 @@ export class FunctionsRouter extends PromiseRouter { }; const response = new TriggerResponse(); - + const userString = req.auth.user?.id; - + try { - // Run the optional validator - await triggers.maybeRunValidator(request, functionName, req.auth); - - // Execute the function - const result = await theFunction(request, response); - - if (req.config.logLevels.cloudFunctionSuccess !== 'silent') { - const cleanInput = logger.truncateLogMessage(JSON.stringify(params)); + // Run the optional validator + await triggers.maybeRunValidator(request, functionName, req.auth); + + // Execute the function + const result = await theFunction(request, response); + + if (req.config.logLevels.cloudFunctionSuccess !== 'silent') { + const cleanInput = logger.truncateLogMessage(JSON.stringify(params)); const cleanResult = logger.truncateLogMessage(JSON.stringify(result.response?.result)); logger[req.config.logLevels.cloudFunctionSuccess]( `Ran cloud function ${functionName} for user ${userString} with:\n Input: ${cleanInput}\n Result: ${cleanResult}`, @@ -150,29 +150,29 @@ export class FunctionsRouter extends PromiseRouter { } ); } - - return response.toResponseObject({ - response: { - result: Parse._encode(result), - } - }); + + return response.toResponseObject({ + response: { + result: Parse._encode(result), + }, + }); } catch (error) { - console.log(error); - error = triggers.resolveError(error); + const err = triggers.resolveError(error); if (req.config.logLevels.cloudFunctionError !== 'silent') { const cleanInput = logger.truncateLogMessage(JSON.stringify(params)); logger[req.config.logLevels.cloudFunctionError]( - `Failed running cloud function ${functionName} for user ${userString} with:\n Input: ${cleanInput}\n Error: ${JSON.stringify(error)}`, + `Failed running cloud function ${functionName} for user ${userString} with:\n Input: ${cleanInput}\n Error: ${JSON.stringify( + err + )}`, { functionName, - error, + err, params, user: userString, } ); } - throw error; + throw err; } } - } diff --git a/src/Routers/GlobalConfigRouter.js b/src/Routers/GlobalConfigRouter.js index 1fcdfe968a..01cc325e30 100644 --- a/src/Routers/GlobalConfigRouter.js +++ b/src/Routers/GlobalConfigRouter.js @@ -55,29 +55,67 @@ export class GlobalConfigRouter extends PromiseRouter { return acc; }, {}); const className = triggers.getClassName(Parse.Config); - const hasBeforeSaveHook = triggers.triggerExists(className, triggers.Types.beforeSave, req.config.applicationId); - const hasAfterSaveHook = triggers.triggerExists(className, triggers.Types.afterSave, req.config.applicationId); + const hasBeforeSaveHook = triggers.triggerExists( + className, + triggers.Types.beforeSave, + req.config.applicationId + ); + const hasAfterSaveHook = triggers.triggerExists( + className, + triggers.Types.afterSave, + req.config.applicationId + ); let originalConfigObject; let updatedConfigObject; const configObject = new Parse.Config(); configObject.attributes = params; - const results = await req.config.database.find('_GlobalConfig', { objectId: '1' }, { limit: 1 }); + const results = await req.config.database.find( + '_GlobalConfig', + { objectId: '1' }, + { limit: 1 } + ); const isNew = results.length !== 1; if (!isNew && (hasBeforeSaveHook || hasAfterSaveHook)) { originalConfigObject = getConfigFromParams(results[0].params); } try { - await triggers.maybeRunGlobalConfigTrigger(triggers.Types.beforeSave, req.auth, configObject, originalConfigObject, req.config, req.context); + await triggers.maybeRunGlobalConfigTrigger( + triggers.Types.beforeSave, + req.auth, + configObject, + originalConfigObject, + req.config, + req.context + ); if (isNew) { - await req.config.database.update('_GlobalConfig', { objectId: '1' }, update, { upsert: true }, true) + await req.config.database.update( + '_GlobalConfig', + { objectId: '1' }, + update, + { upsert: true }, + true + ); updatedConfigObject = configObject; } else { - const result = await req.config.database.update('_GlobalConfig', { objectId: '1' }, update, {}, true); + const result = await req.config.database.update( + '_GlobalConfig', + { objectId: '1' }, + update, + {}, + true + ); updatedConfigObject = getConfigFromParams(result.params); } - await triggers.maybeRunGlobalConfigTrigger(triggers.Types.afterSave, req.auth, updatedConfigObject, originalConfigObject, req.config, req.context); - return { response: { result: true } } + await triggers.maybeRunGlobalConfigTrigger( + triggers.Types.afterSave, + req.auth, + updatedConfigObject, + originalConfigObject, + req.config, + req.context + ); + return { response: { result: true } }; } catch (err) { const error = triggers.resolveError(err, { code: Parse.Error.SCRIPT_FAILED, diff --git a/src/Routers/UsersRouter.js b/src/Routers/UsersRouter.js index a633fd3604..37b570fbe1 100644 --- a/src/Routers/UsersRouter.js +++ b/src/Routers/UsersRouter.js @@ -148,13 +148,23 @@ export class UsersRouter extends ClassesRouter { // If request doesn't use master or maintenance key with ignoring email verification if (!((req.auth.isMaster || req.auth.isMaintenance) && ignoreEmailVerification)) { - // Get verification conditions which can be booleans or functions; the purpose of this async/await // structure is to avoid unnecessarily executing subsequent functions if previous ones fail in the // conditional statement below, as a developer may decide to execute expensive operations in them - const verifyUserEmails = async () => req.config.verifyUserEmails === true || (typeof req.config.verifyUserEmails === 'function' && await Promise.resolve(req.config.verifyUserEmails(request)) === true); - const preventLoginWithUnverifiedEmail = async () => req.config.preventLoginWithUnverifiedEmail === true || (typeof req.config.preventLoginWithUnverifiedEmail === 'function' && await Promise.resolve(req.config.preventLoginWithUnverifiedEmail(request)) === true); - if (await verifyUserEmails() && await preventLoginWithUnverifiedEmail() && !user.emailVerified) { + const verifyUserEmails = async () => + req.config.verifyUserEmails === true || + (typeof req.config.verifyUserEmails === 'function' && + (await Promise.resolve(req.config.verifyUserEmails(request))) === true); + const preventLoginWithUnverifiedEmail = async () => + req.config.preventLoginWithUnverifiedEmail === true || + (typeof req.config.preventLoginWithUnverifiedEmail === 'function' && + (await Promise.resolve(req.config.preventLoginWithUnverifiedEmail(request))) === + true); + if ( + (await verifyUserEmails()) && + (await preventLoginWithUnverifiedEmail()) && + !user.emailVerified + ) { throw new Parse.Error(Parse.Error.EMAIL_NOT_FOUND, 'User email is not verified.'); } } @@ -253,12 +263,13 @@ export class UsersRouter extends ClassesRouter { const expiresAt = new Date( changedAt.getTime() + 86400000 * req.config.passwordPolicy.maxPasswordAge ); - if (expiresAt < new Date()) - // fail of current time is past password expiry time - { throw new Parse.Error( - Parse.Error.OBJECT_NOT_FOUND, - 'Your password has expired. Please reset your password.' - ); } + if (expiresAt < new Date()) { + // fail of current time is past password expiry time + throw new Parse.Error( + Parse.Error.OBJECT_NOT_FOUND, + 'Your password has expired. Please reset your password.' + ); + } } } @@ -317,7 +328,7 @@ export class UsersRouter extends ClassesRouter { } await req.config.authDataManager.runAfterFind(req, user.authData); - return beforeLoginResponse.toResponseObject({response: user}); + return beforeLoginResponse.toResponseObject({ response: user }); } /** @@ -484,7 +495,12 @@ export class UsersRouter extends ClassesRouter { ); } - const results = await req.config.database.find('_User', { email: email }, {}, Auth.maintenance(req.config)); + const results = await req.config.database.find( + '_User', + { email: email }, + {}, + Auth.maintenance(req.config) + ); if (!results.length || results.length < 1) { throw new Parse.Error(Parse.Error.EMAIL_NOT_FOUND, `No user found with email ${email}`); } @@ -498,7 +514,12 @@ export class UsersRouter extends ClassesRouter { } const userController = req.config.userController; - const send = await userController.regenerateEmailVerifyToken(user, req.auth.isMaster, req.auth.installationId, req.ip); + const send = await userController.regenerateEmailVerifyToken( + user, + req.auth.isMaster, + req.auth.installationId, + req.ip + ); if (send) { userController.sendVerificationEmail(user, req); } diff --git a/src/SchemaMigrations/DefinedSchemas.js b/src/SchemaMigrations/DefinedSchemas.js index e9c685797c..f7cd7d5fa1 100644 --- a/src/SchemaMigrations/DefinedSchemas.js +++ b/src/SchemaMigrations/DefinedSchemas.js @@ -80,7 +80,9 @@ export class DefinedSchemas { logger.info('Running Migrations Completed'); } catch (e) { logger.error(`Failed to run migrations: ${e}`); - if (process.env.NODE_ENV === 'production') { process.exit(1); } + if (process.env.NODE_ENV === 'production') { + process.exit(1); + } } } @@ -108,7 +110,9 @@ export class DefinedSchemas { this.checkForMissingSchemas(); await this.enforceCLPForNonProvidedClass(); } catch (e) { - if (timeout) { clearTimeout(timeout); } + if (timeout) { + clearTimeout(timeout); + } if (this.retries < this.maxRetries) { this.retries++; // first retry 1sec, 2sec, 3sec total 6sec retry sequence @@ -118,7 +122,9 @@ export class DefinedSchemas { await this.executeMigrations(); } else { logger.error(`Failed to run migrations: ${e}`); - if (process.env.NODE_ENV === 'production') { process.exit(1); } + if (process.env.NODE_ENV === 'production') { + process.exit(1); + } } } } @@ -428,7 +434,9 @@ export class DefinedSchemas { const keysB: string[] = Object.keys(objB); // Check key name - if (keysA.length !== keysB.length) { return false; } + if (keysA.length !== keysB.length) { + return false; + } return keysA.every(k => objA[k] === objB[k]); } diff --git a/src/StatusHandler.js b/src/StatusHandler.js index fecfb268ec..59c77e4b95 100644 --- a/src/StatusHandler.js +++ b/src/StatusHandler.js @@ -248,7 +248,8 @@ export function pushStatusHandler(config, existingObjectId) { if ( error?.code === 'messaging/registration-token-not-registered' || error?.code === 'messaging/invalid-registration-token' || - (error?.code === 'messaging/invalid-argument' && error?.message === 'The registration token is not a valid FCM registration token') + (error?.code === 'messaging/invalid-argument' && + error?.message === 'The registration token is not a valid FCM registration token') ) { devicesToRemove.push(token); } diff --git a/src/TestUtils.js b/src/TestUtils.js index 912a459519..94eedd9034 100644 --- a/src/TestUtils.js +++ b/src/TestUtils.js @@ -40,5 +40,5 @@ export function resolvingPromise() { } export function sleep(ms) { - return new Promise((resolve) => setTimeout(resolve, ms)); + return new Promise(resolve => setTimeout(resolve, ms)); } diff --git a/src/Triggers/ConfigTrigger.js b/src/Triggers/ConfigTrigger.js index 9ed4395a12..7f4c807aae 100644 --- a/src/Triggers/ConfigTrigger.js +++ b/src/Triggers/ConfigTrigger.js @@ -1,14 +1,28 @@ -import {getRequestObject} from './Trigger'; -import { maybeRunValidator } from "./Validator"; +import { getRequestObject } from './Trigger'; +import { maybeRunValidator } from './Validator'; import { logTriggerSuccessBeforeHook, logTriggerErrorBeforeHook } from './Logger'; -import {getClassName} from './Utils'; -import {getTrigger} from './TriggerStore'; -export async function maybeRunGlobalConfigTrigger(triggerType, auth, configObject, originalConfigObject, config, context) { +import { getClassName } from './Utils'; +import { getTrigger } from './TriggerStore'; +export async function maybeRunGlobalConfigTrigger( + triggerType, + auth, + configObject, + originalConfigObject, + config, + context +) { const GlobalConfigClassName = getClassName(Parse.Config); const configTrigger = getTrigger(GlobalConfigClassName, triggerType, config.applicationId); if (typeof configTrigger === 'function') { try { - const request = getRequestObject(triggerType, auth, configObject, originalConfigObject, config, context); + const request = getRequestObject( + triggerType, + auth, + configObject, + originalConfigObject, + config, + context + ); await maybeRunValidator(request, `${triggerType}.${GlobalConfigClassName}`, auth); if (request.skipWithMasterKey) { return configObject; @@ -36,4 +50,4 @@ export async function maybeRunGlobalConfigTrigger(triggerType, auth, configObjec } } return configObject; -} \ No newline at end of file +} diff --git a/src/Triggers/FileTrigger.js b/src/Triggers/FileTrigger.js index 7c74763a95..fbe866bdee 100644 --- a/src/Triggers/FileTrigger.js +++ b/src/Triggers/FileTrigger.js @@ -1,7 +1,7 @@ -import {getClassName} from './Utils'; -import {getTrigger} from './TriggerStore'; -import {logTriggerSuccessBeforeHook, logTriggerErrorBeforeHook} from './Logger'; -import {maybeRunValidator} from './Validator'; +import { getClassName } from './Utils'; +import { getTrigger } from './TriggerStore'; +import { logTriggerSuccessBeforeHook, logTriggerErrorBeforeHook } from './Logger'; +import { maybeRunValidator } from './Validator'; export function getRequestFileObject(triggerType, auth, fileObject, config) { const request = { @@ -34,31 +34,31 @@ export async function maybeRunFileTrigger(triggerType, fileObject, config, auth, if (typeof fileTrigger !== 'function') { return fileObject; } - try { - const request = getRequestFileObject(triggerType, auth, fileObject, config); - await maybeRunValidator(request, `${triggerType}.${FileClassName}`, auth); - if (request.skipWithMasterKey) { - return fileObject; - } - const result = await fileTrigger(request, responseObject); - logTriggerSuccessBeforeHook( - triggerType, - 'Parse.File', - { ...fileObject.file.toJSON(), fileSize: fileObject.fileSize }, - result, - auth, - config.logLevels.triggerBeforeSuccess - ); - return result || fileObject; - } catch (error) { - logTriggerErrorBeforeHook( - triggerType, - 'Parse.File', - { ...fileObject.file.toJSON(), fileSize: fileObject.fileSize }, - auth, - error, - config.logLevels.triggerBeforeError - ); - throw error; + try { + const request = getRequestFileObject(triggerType, auth, fileObject, config); + await maybeRunValidator(request, `${triggerType}.${FileClassName}`, auth); + if (request.skipWithMasterKey) { + return fileObject; } -} \ No newline at end of file + const result = await fileTrigger(request, responseObject); + logTriggerSuccessBeforeHook( + triggerType, + 'Parse.File', + { ...fileObject.file.toJSON(), fileSize: fileObject.fileSize }, + result, + auth, + config.logLevels.triggerBeforeSuccess + ); + return result || fileObject; + } catch (error) { + logTriggerErrorBeforeHook( + triggerType, + 'Parse.File', + { ...fileObject.file.toJSON(), fileSize: fileObject.fileSize }, + auth, + error, + config.logLevels.triggerBeforeError + ); + throw error; + } +} diff --git a/src/Triggers/Logger.js b/src/Triggers/Logger.js index 8758cd302c..e2dd32362e 100644 --- a/src/Triggers/Logger.js +++ b/src/Triggers/Logger.js @@ -1,16 +1,15 @@ +import logger from '../logger'; export function logTriggerAfterHook(triggerType, className, input, auth, logLevel) { if (logLevel === 'silent') { return; } const cleanInput = logger.truncateLogMessage(JSON.stringify(input)); logger[logLevel]( - `${triggerType} triggered for ${className} for user ${userIdForLog( - auth - )}:\n Input: ${cleanInput}`, + `${triggerType} triggered for ${className} for user ${auth?.user?.id}:\n Input: ${cleanInput}`, { className, triggerType, - user: userIdForLog(auth), + user: auth?.user?.id, } ); } @@ -22,13 +21,11 @@ export function logTriggerSuccessBeforeHook(triggerType, className, input, resul const cleanInput = logger.truncateLogMessage(JSON.stringify(input)); const cleanResult = logger.truncateLogMessage(JSON.stringify(result)); logger[logLevel]( - `${triggerType} triggered for ${className} for user ${userIdForLog( - auth - )}:\n Input: ${cleanInput}\n Result: ${cleanResult}`, + `${triggerType} triggered for ${className} for user ${auth?.user?.id}:\n Input: ${cleanInput}\n Result: ${cleanResult}`, { className, triggerType, - user: userIdForLog(auth), + user: auth?.user?.id, } ); } @@ -39,14 +36,12 @@ export function logTriggerErrorBeforeHook(triggerType, className, input, auth, e } const cleanInput = logger.truncateLogMessage(JSON.stringify(input)); logger[logLevel]( - `${triggerType} failed for ${className} for user ${userIdForLog( - auth - )}:\n Input: ${cleanInput}\n Error: ${JSON.stringify(error)}`, + `${triggerType} failed for ${className} for user ${auth?.user?.id}:\n Input: ${cleanInput}\n Error: ${JSON.stringify(error)}`, { className, triggerType, error, - user: userIdForLog(auth), + user: auth?.user?.id, } ); -} \ No newline at end of file +} diff --git a/src/Triggers/QueryTrigger.js b/src/Triggers/QueryTrigger.js index c18d77af5d..343a2da7f1 100644 --- a/src/Triggers/QueryTrigger.js +++ b/src/Triggers/QueryTrigger.js @@ -1,8 +1,8 @@ -import { getTrigger, Types } from "./TriggerStore"; -import {getRequestObject} from './Trigger'; -import { resolveError, toJSONwithObjects } from "./Utils"; -import { maybeRunValidator } from "./Validator"; -import { logTriggerAfterHook, logTriggerSuccessBeforeHook } from "./Logger"; +import { getTrigger, Types } from './TriggerStore'; +import { getRequestObject } from './Trigger'; +import { resolveError, toJSONwithObjects } from './Utils'; +import { maybeRunValidator } from './Validator'; +import { logTriggerAfterHook, logTriggerSuccessBeforeHook } from './Logger'; function getResponseObject(request, resolve, reject) { return { @@ -250,4 +250,4 @@ function getRequestQueryObject(triggerType, auth, query, count, config, context, request['installationId'] = auth.installationId; } return request; -} \ No newline at end of file +} diff --git a/src/Triggers/Trigger.js b/src/Triggers/Trigger.js index 857ea14df6..c650135d0a 100644 --- a/src/Triggers/Trigger.js +++ b/src/Triggers/Trigger.js @@ -1,7 +1,7 @@ -import { getTrigger, Types } from "./TriggerStore"; -import { maybeRunValidator } from "./Validator"; -import { logTriggerAfterHook, logTriggerErrorBeforeHook } from "./Logger"; -import { toJSONwithObjects } from "./Utils"; +import { getTrigger, Types } from './TriggerStore'; +import { maybeRunValidator } from './Validator'; +import { logTriggerAfterHook, logTriggerErrorBeforeHook } from './Logger'; +import { toJSONwithObjects } from './Utils'; export function getRequestObject( triggerType, @@ -104,7 +104,6 @@ export async function maybeRunTrigger( } return responseData; - } catch (err) { logTriggerErrorBeforeHook( triggerType, @@ -150,4 +149,4 @@ function processTriggerResponse(request, response) { } return {}; -} \ No newline at end of file +} diff --git a/src/Triggers/TriggerResponse.js b/src/Triggers/TriggerResponse.js index 3b421a1d1b..c86b62e905 100644 --- a/src/Triggers/TriggerResponse.js +++ b/src/Triggers/TriggerResponse.js @@ -15,4 +15,4 @@ export default class TriggerResponse { location: response.location, }; } -} \ No newline at end of file +} diff --git a/src/Triggers/TriggerStore.js b/src/Triggers/TriggerStore.js index cd5e53b691..185e388a5a 100644 --- a/src/Triggers/TriggerStore.js +++ b/src/Triggers/TriggerStore.js @@ -176,4 +176,4 @@ export function getValidator(functionName, applicationId) { export function getJob(jobName, applicationId) { return get(Category.Jobs, jobName, applicationId); -} \ No newline at end of file +} diff --git a/src/Triggers/Validator.js b/src/Triggers/Validator.js index cba50ec183..b0c477fd24 100644 --- a/src/Triggers/Validator.js +++ b/src/Triggers/Validator.js @@ -1,5 +1,5 @@ -import {getValidator} from './TriggerStore' -import {resolveError} from './Utils' +import { getValidator } from './TriggerStore'; +import { resolveError } from './Utils'; export function maybeRunValidator(request, functionName, auth) { const theValidator = getValidator(functionName, Parse.applicationId); if (!theValidator) { @@ -113,9 +113,7 @@ async function builtInTriggerValidator(options, request, auth) { } } - console.log('builtInTriggerValidator', request.object); if (opt.constant && request.object) { - console.log('constant', key, val); if (request.original) { request.object.revert(key); } else if (opt.default != null) { @@ -195,4 +193,4 @@ async function builtInTriggerValidator(options, request, auth) { } await Promise.all(optionPromises); } -} \ No newline at end of file +} diff --git a/src/Utils.js b/src/Utils.js index b77a3d85d7..f621b8c53b 100644 --- a/src/Utils.js +++ b/src/Utils.js @@ -389,7 +389,7 @@ class Utils { * addNestedKeysToRoot(obj, 'b'); * console.log(obj); * // Output: { a: 1, e: 4, c: 2, d: 3 } - */ + */ static addNestedKeysToRoot(obj, key) { if (obj[key] && typeof obj[key] === 'object') { // Add nested keys to root diff --git a/src/cloud-code/Parse.Cloud.js b/src/cloud-code/Parse.Cloud.js index 9c08ecca57..1fcea1f67d 100644 --- a/src/cloud-code/Parse.Cloud.js +++ b/src/cloud-code/Parse.Cloud.js @@ -79,7 +79,7 @@ const getRoute = parseClass => { _User: 'users', _Session: 'sessions', '@File': 'files', - '@Config' : 'config', + '@Config': 'config', }[parseClass] || 'classes'; if (parseClass === '@File') { return `/${route}/:id?(.*)`; diff --git a/src/middlewares.js b/src/middlewares.js index 178692b447..db9d4ab834 100644 --- a/src/middlewares.js +++ b/src/middlewares.js @@ -24,7 +24,9 @@ const getMountForRequest = function (req) { }; const getBlockList = (ipRangeList, store) => { - if (store.get('blockList')) { return store.get('blockList'); } + if (store.get('blockList')) { + return store.get('blockList'); + } const blockList = new BlockList(); ipRangeList.forEach(fullIp => { if (fullIp === '::/0' || fullIp === '::') { @@ -50,9 +52,15 @@ export const checkIp = (ip, ipRangeList, store) => { const incomingIpIsV4 = isIPv4(ip); const blockList = getBlockList(ipRangeList, store); - if (store.get(ip)) { return true; } - if (store.get('allowAllIpv4') && incomingIpIsV4) { return true; } - if (store.get('allowAllIpv6') && !incomingIpIsV4) { return true; } + if (store.get(ip)) { + return true; + } + if (store.get('allowAllIpv4') && incomingIpIsV4) { + return true; + } + if (store.get('allowAllIpv6') && !incomingIpIsV4) { + return true; + } const result = blockList.check(ip, incomingIpIsV4 ? 'ipv4' : 'ipv6'); // If the ip is in the list, we store the result in the store @@ -386,7 +394,9 @@ function getClientIp(req) { } function httpAuth(req) { - if (!(req.req || req).headers.authorization) { return; } + if (!(req.req || req).headers.authorization) { + return; + } var header = (req.req || req).headers.authorization; var appId, masterKey, javascriptKey; diff --git a/src/rest.js b/src/rest.js index efc9b2f395..bafdfcc150 100644 --- a/src/rest.js +++ b/src/rest.js @@ -25,7 +25,16 @@ function checkLiveQuery(className, config) { } // Returns a promise for an object with optional keys 'results' and 'count'. -const find = async (config, auth, className, restWhere, restOptions, clientSDK, context, response) => { +const find = async ( + config, + auth, + className, + restWhere, + restOptions, + clientSDK, + context, + response +) => { const query = await RestQuery({ method: RestQuery.Method.find, config, @@ -35,13 +44,22 @@ const find = async (config, auth, className, restWhere, restOptions, clientSDK, restOptions, clientSDK, context, - response + response, }); return query.execute(); }; // get is just like find but only queries an objectId. -const get = async (config, auth, className, objectId, restOptions, clientSDK, context, response) => { +const get = async ( + config, + auth, + className, + objectId, + restOptions, + clientSDK, + context, + response +) => { var restWhere = { objectId }; const query = await RestQuery({ method: RestQuery.Method.get, @@ -161,7 +179,18 @@ function del(config, auth, className, objectId, context, responseObject) { // Returns a promise for a {response, status, location} object. function create(config, auth, className, restObject, clientSDK, context, response) { enforceRoleSecurity('create', className, auth); - var write = new RestWrite(config, auth, className, null, restObject, null, clientSDK, context, undefined, response); + var write = new RestWrite( + config, + auth, + className, + null, + restObject, + null, + clientSDK, + context, + undefined, + response + ); return write.execute(); } @@ -186,7 +215,7 @@ function update(config, auth, className, restWhere, restObject, clientSDK, conte runAfterFind: false, runBeforeFind: false, context, - response + response, }); return query.execute({ op: 'update', diff --git a/src/triggers.js b/src/triggers.js index d3139f8210..09a4e40835 100644 --- a/src/triggers.js +++ b/src/triggers.js @@ -1,10 +1,20 @@ -import { _unregisterAll, getTrigger, Types, triggerExists, addTrigger, addFunction, getFunction, getJob, runLiveQueryEventHandlers } from "./Triggers/TriggerStore"; -import { maybeRunTrigger, getRequestObject } from "./Triggers/Trigger"; -import { getClassName, inflate, resolveError, toJSONwithObjects } from "./Triggers/Utils"; -import { maybeRunQueryTrigger,maybeRunAfterFindTrigger } from "./Triggers/QueryTrigger"; -import {maybeRunValidator} from "./Triggers/Validator"; -import { maybeRunFileTrigger } from "./Triggers/FileTrigger"; -import { maybeRunGlobalConfigTrigger } from "./Triggers/ConfigTrigger"; +import { + _unregisterAll, + getTrigger, + Types, + triggerExists, + addTrigger, + addFunction, + getFunction, + getJob, + runLiveQueryEventHandlers, +} from './Triggers/TriggerStore'; +import { maybeRunTrigger, getRequestObject } from './Triggers/Trigger'; +import { getClassName, inflate, resolveError, toJSONwithObjects } from './Triggers/Utils'; +import { maybeRunQueryTrigger, maybeRunAfterFindTrigger } from './Triggers/QueryTrigger'; +import { maybeRunValidator } from './Triggers/Validator'; +import { maybeRunFileTrigger } from './Triggers/FileTrigger'; +import { maybeRunGlobalConfigTrigger } from './Triggers/ConfigTrigger'; export { _unregisterAll, @@ -26,5 +36,5 @@ export { maybeRunGlobalConfigTrigger, maybeRunAfterFindTrigger, toJSONwithObjects, - runLiveQueryEventHandlers -} \ No newline at end of file + runLiveQueryEventHandlers, +}; diff --git a/src/vendor/mongodbUrl.js b/src/vendor/mongodbUrl.js index 4b5c501e41..74df01888a 100644 --- a/src/vendor/mongodbUrl.js +++ b/src/vendor/mongodbUrl.js @@ -66,7 +66,9 @@ const querystring = require('querystring'); /* istanbul ignore next: improve coverage */ function urlParse(url, parseQueryString, slashesDenoteHost) { - if (url instanceof Url) { return url; } + if (url instanceof Url) { + return url; + } var u = new Url(); u.parse(url, parseQueryString, slashesDenoteHost); @@ -101,7 +103,9 @@ Url.prototype.parse = function (url, parseQueryString, slashesDenoteHost) { code === 160 /*\u00A0*/ || code === 65279; /*\uFEFF*/ if (start === -1) { - if (isWs) { continue; } + if (isWs) { + continue; + } lastPos = start = i; } else { if (inWs) { @@ -125,7 +129,9 @@ Url.prototype.parse = function (url, parseQueryString, slashesDenoteHost) { split = true; break; case 92: // '\\' - if (i - lastPos > 0) { rest += url.slice(lastPos, i); } + if (i - lastPos > 0) { + rest += url.slice(lastPos, i); + } rest += '/'; lastPos = i + 1; break; @@ -141,8 +147,11 @@ Url.prototype.parse = function (url, parseQueryString, slashesDenoteHost) { // We didn't convert any backslashes if (end === -1) { - if (start === 0) { rest = url; } - else { rest = url.slice(start); } + if (start === 0) { + rest = url; + } else { + rest = url.slice(start); + } } else { rest = url.slice(start, end); } @@ -235,13 +244,17 @@ Url.prototype.parse = function (url, parseQueryString, slashesDenoteHost) { case 124: // '|' case 125: // '}' // Characters that are never ever allowed in a hostname from RFC 2396 - if (nonHost === -1) { nonHost = i; } + if (nonHost === -1) { + nonHost = i; + } break; case 35: // '#' case 47: // '/' case 63: // '?' // Find the first instance of any host-ending characters - if (nonHost === -1) { nonHost = i; } + if (nonHost === -1) { + nonHost = i; + } hostEnd = i; break; case 64: // '@' @@ -251,7 +264,9 @@ Url.prototype.parse = function (url, parseQueryString, slashesDenoteHost) { nonHost = -1; break; } - if (hostEnd !== -1) { break; } + if (hostEnd !== -1) { + break; + } } start = 0; if (atSign !== -1) { @@ -271,7 +286,9 @@ Url.prototype.parse = function (url, parseQueryString, slashesDenoteHost) { // we've indicated that there is a hostname, // so even if it's empty, it has to be present. - if (typeof this.hostname !== 'string') { this.hostname = ''; } + if (typeof this.hostname !== 'string') { + this.hostname = ''; + } var hostname = this.hostname; @@ -283,7 +300,9 @@ Url.prototype.parse = function (url, parseQueryString, slashesDenoteHost) { // validate a little. if (!ipv6Hostname) { const result = validateHostname(this, rest, hostname); - if (result !== undefined) { rest = result; } + if (result !== undefined) { + rest = result; + } } // hostnames are always lower case. @@ -318,7 +337,9 @@ Url.prototype.parse = function (url, parseQueryString, slashesDenoteHost) { // escaped, even if encodeURIComponent doesn't think they // need to be. const result = autoEscapeStr(rest); - if (result !== undefined) { rest = result; } + if (result !== undefined) { + rest = result; + } } var questionIdx = -1; @@ -354,7 +375,9 @@ Url.prototype.parse = function (url, parseQueryString, slashesDenoteHost) { var firstIdx = questionIdx !== -1 && (hashIdx === -1 || questionIdx < hashIdx) ? questionIdx : hashIdx; if (firstIdx === -1) { - if (rest.length > 0) { this.pathname = rest; } + if (rest.length > 0) { + this.pathname = rest; + } } else if (firstIdx > 0) { this.pathname = rest.slice(0, firstIdx); } @@ -378,7 +401,9 @@ Url.prototype.parse = function (url, parseQueryString, slashesDenoteHost) { function validateHostname(self, rest, hostname) { for (var i = 0, lastPos; i <= hostname.length; ++i) { var code; - if (i < hostname.length) { code = hostname.charCodeAt(i); } + if (i < hostname.length) { + code = hostname.charCodeAt(i); + } if (code === 46 /*.*/ || i === hostname.length) { if (i - lastPos > 0) { if (i - lastPos > 63) { @@ -405,7 +430,9 @@ function validateHostname(self, rest, hostname) { } // Invalid host character self.hostname = hostname.slice(0, i); - if (i < hostname.length) { return '/' + hostname.slice(i) + rest; } + if (i < hostname.length) { + return '/' + hostname.slice(i) + rest; + } break; } } @@ -419,80 +446,113 @@ function autoEscapeStr(rest) { // Also escape single quotes in case of an XSS attack switch (rest.charCodeAt(i)) { case 9: // '\t' - if (i - lastPos > 0) { newRest += rest.slice(lastPos, i); } + if (i - lastPos > 0) { + newRest += rest.slice(lastPos, i); + } newRest += '%09'; lastPos = i + 1; break; case 10: // '\n' - if (i - lastPos > 0) { newRest += rest.slice(lastPos, i); } + if (i - lastPos > 0) { + newRest += rest.slice(lastPos, i); + } newRest += '%0A'; lastPos = i + 1; break; case 13: // '\r' - if (i - lastPos > 0) { newRest += rest.slice(lastPos, i); } + if (i - lastPos > 0) { + newRest += rest.slice(lastPos, i); + } newRest += '%0D'; lastPos = i + 1; break; case 32: // ' ' - if (i - lastPos > 0) { newRest += rest.slice(lastPos, i); } + if (i - lastPos > 0) { + newRest += rest.slice(lastPos, i); + } newRest += '%20'; lastPos = i + 1; break; case 34: // '"' - if (i - lastPos > 0) { newRest += rest.slice(lastPos, i); } + if (i - lastPos > 0) { + newRest += rest.slice(lastPos, i); + } newRest += '%22'; lastPos = i + 1; break; case 39: // '\'' - if (i - lastPos > 0) { newRest += rest.slice(lastPos, i); } + if (i - lastPos > 0) { + newRest += rest.slice(lastPos, i); + } newRest += '%27'; lastPos = i + 1; break; case 60: // '<' - if (i - lastPos > 0) { newRest += rest.slice(lastPos, i); } + if (i - lastPos > 0) { + newRest += rest.slice(lastPos, i); + } newRest += '%3C'; lastPos = i + 1; break; case 62: // '>' - if (i - lastPos > 0) { newRest += rest.slice(lastPos, i); } + if (i - lastPos > 0) { + newRest += rest.slice(lastPos, i); + } newRest += '%3E'; lastPos = i + 1; break; case 92: // '\\' - if (i - lastPos > 0) { newRest += rest.slice(lastPos, i); } + if (i - lastPos > 0) { + newRest += rest.slice(lastPos, i); + } newRest += '%5C'; lastPos = i + 1; break; case 94: // '^' - if (i - lastPos > 0) { newRest += rest.slice(lastPos, i); } + if (i - lastPos > 0) { + newRest += rest.slice(lastPos, i); + } newRest += '%5E'; lastPos = i + 1; break; case 96: // '`' - if (i - lastPos > 0) { newRest += rest.slice(lastPos, i); } + if (i - lastPos > 0) { + newRest += rest.slice(lastPos, i); + } newRest += '%60'; lastPos = i + 1; break; case 123: // '{' - if (i - lastPos > 0) { newRest += rest.slice(lastPos, i); } + if (i - lastPos > 0) { + newRest += rest.slice(lastPos, i); + } newRest += '%7B'; lastPos = i + 1; break; case 124: // '|' - if (i - lastPos > 0) { newRest += rest.slice(lastPos, i); } + if (i - lastPos > 0) { + newRest += rest.slice(lastPos, i); + } newRest += '%7C'; lastPos = i + 1; break; case 125: // '}' - if (i - lastPos > 0) { newRest += rest.slice(lastPos, i); } + if (i - lastPos > 0) { + newRest += rest.slice(lastPos, i); + } newRest += '%7D'; lastPos = i + 1; break; } } - if (lastPos === 0) { return; } - if (lastPos < rest.length) { return newRest + rest.slice(lastPos); } - else { return newRest; } + if (lastPos === 0) { + return; + } + if (lastPos < rest.length) { + return newRest + rest.slice(lastPos); + } else { + return newRest; + } } // format a parsed object into a url string @@ -502,12 +562,15 @@ function urlFormat(obj) { // If it's an obj, this is a no-op. // this way, you can call url_format() on strings // to clean up potentially wonky urls. - if (typeof obj === 'string') { obj = urlParse(obj); } - else if (typeof obj !== 'object' || obj === null) - { throw new TypeError( - 'Parameter "urlObj" must be an object, not ' + obj === null ? 'null' : typeof obj - ); } - else if (!(obj instanceof Url)) { return Url.prototype.format.call(obj); } + if (typeof obj === 'string') { + obj = urlParse(obj); + } else if (typeof obj !== 'object' || obj === null) { + throw new TypeError( + 'Parameter "urlObj" must be an object, not ' + obj === null ? 'null' : typeof obj + ); + } else if (!(obj instanceof Url)) { + return Url.prototype.format.call(obj); + } return obj.format(); } @@ -535,47 +598,63 @@ Url.prototype.format = function () { } } - if (this.query !== null && typeof this.query === 'object') - { query = querystring.stringify(this.query); } + if (this.query !== null && typeof this.query === 'object') { + query = querystring.stringify(this.query); + } var search = this.search || (query && '?' + query) || ''; - if (protocol && protocol.charCodeAt(protocol.length - 1) !== 58 /*:*/) { protocol += ':'; } + if (protocol && protocol.charCodeAt(protocol.length - 1) !== 58 /*:*/) { + protocol += ':'; + } var newPathname = ''; var lastPos = 0; for (var i = 0; i < pathname.length; ++i) { switch (pathname.charCodeAt(i)) { case 35: // '#' - if (i - lastPos > 0) { newPathname += pathname.slice(lastPos, i); } + if (i - lastPos > 0) { + newPathname += pathname.slice(lastPos, i); + } newPathname += '%23'; lastPos = i + 1; break; case 63: // '?' - if (i - lastPos > 0) { newPathname += pathname.slice(lastPos, i); } + if (i - lastPos > 0) { + newPathname += pathname.slice(lastPos, i); + } newPathname += '%3F'; lastPos = i + 1; break; } } if (lastPos > 0) { - if (lastPos !== pathname.length) { pathname = newPathname + pathname.slice(lastPos); } - else { pathname = newPathname; } + if (lastPos !== pathname.length) { + pathname = newPathname + pathname.slice(lastPos); + } else { + pathname = newPathname; + } } // only the slashedProtocols get the //. Not mailto:, xmpp:, etc. // unless they had them to begin with. if (this.slashes || ((!protocol || slashedProtocol[protocol]) && host !== false)) { host = '//' + (host || ''); - if (pathname && pathname.charCodeAt(0) !== 47 /*/*/) { pathname = '/' + pathname; } + if (pathname && pathname.charCodeAt(0) !== 47 /*/*/) { + pathname = '/' + pathname; + } } else if (!host) { host = ''; } search = search.replace('#', '%23'); - if (hash && hash.charCodeAt(0) !== 35 /*#*/) { hash = '#' + hash; } - if (search && search.charCodeAt(0) !== 63 /*?*/) { search = '?' + search; } + if (hash && hash.charCodeAt(0) !== 35 /*#*/) { + hash = '#' + hash; + } + if (search && search.charCodeAt(0) !== 63 /*?*/) { + search = '?' + search; + } return protocol + host + pathname + search + hash; }; @@ -592,7 +671,9 @@ Url.prototype.resolve = function (relative) { /* istanbul ignore next: improve coverage */ function urlResolveObject(source, relative) { - if (!source) { return relative; } + if (!source) { + return relative; + } return urlParse(source, false, true).resolveObject(relative); } @@ -627,7 +708,9 @@ Url.prototype.resolveObject = function (relative) { var rkeys = Object.keys(relative); for (var rk = 0; rk < rkeys.length; rk++) { var rkey = rkeys[rk]; - if (rkey !== 'protocol') { result[rkey] = relative[rkey]; } + if (rkey !== 'protocol') { + result[rkey] = relative[rkey]; + } } //urlParse appends trailing / to urls like http://www.example.com @@ -672,10 +755,18 @@ Url.prototype.resolveObject = function (relative) { break; } } - if (!relative.host) { relative.host = ''; } - if (!relative.hostname) { relative.hostname = ''; } - if (relPath[0] !== '') { relPath.unshift(''); } - if (relPath.length < 2) { relPath.unshift(''); } + if (!relative.host) { + relative.host = ''; + } + if (!relative.hostname) { + relative.hostname = ''; + } + if (relPath[0] !== '') { + relPath.unshift(''); + } + if (relPath.length < 2) { + relPath.unshift(''); + } result.pathname = relPath.join('/'); } else { result.pathname = relative.pathname; @@ -714,16 +805,22 @@ Url.prototype.resolveObject = function (relative) { result.hostname = ''; result.port = null; if (result.host) { - if (srcPath[0] === '') { srcPath[0] = result.host; } - else { srcPath.unshift(result.host); } + if (srcPath[0] === '') { + srcPath[0] = result.host; + } else { + srcPath.unshift(result.host); + } } result.host = ''; if (relative.protocol) { relative.hostname = null; relative.port = null; if (relative.host) { - if (relPath[0] === '') { relPath[0] = relative.host; } - else { relPath.unshift(relative.host); } + if (relPath[0] === '') { + relPath[0] = relative.host; + } else { + relPath.unshift(relative.host); + } } relative.host = null; } @@ -742,7 +839,9 @@ Url.prototype.resolveObject = function (relative) { } else if (relPath.length) { // it's relative // throw away the existing file, and take the new path instead. - if (!srcPath) { srcPath = []; } + if (!srcPath) { + srcPath = []; + } srcPath.pop(); srcPath = srcPath.concat(relPath); result.search = relative.search; @@ -879,19 +978,24 @@ Url.prototype.parseHost = function () { } host = host.slice(0, host.length - port.length); } - if (host) { this.hostname = host; } + if (host) { + this.hostname = host; + } }; // About 1.5x faster than the two-arg version of Array#splice(). /* istanbul ignore next: improve coverage */ function spliceOne(list, index) { - for (var i = index, k = i + 1, n = list.length; k < n; i += 1, k += 1) { list[i] = list[k]; } + for (var i = index, k = i + 1, n = list.length; k < n; i += 1, k += 1) { + list[i] = list[k]; + } list.pop(); } var hexTable = new Array(256); -for (var i = 0; i < 256; ++i) -{ hexTable[i] = '%' + ((i < 16 ? '0' : '') + i.toString(16)).toUpperCase(); } +for (var i = 0; i < 256; ++i) { + hexTable[i] = '%' + ((i < 16 ? '0' : '') + i.toString(16)).toUpperCase(); +} /* istanbul ignore next: improve coverage */ function encodeAuth(str) { // faster encodeURIComponent alternative for encoding auth uri components @@ -920,7 +1024,9 @@ function encodeAuth(str) { continue; } - if (i - lastPos > 0) { out += str.slice(lastPos, i); } + if (i - lastPos > 0) { + out += str.slice(lastPos, i); + } lastPos = i + 1; @@ -945,8 +1051,11 @@ function encodeAuth(str) { // Surrogate pair ++i; var c2; - if (i < str.length) { c2 = str.charCodeAt(i) & 0x3ff; } - else { c2 = 0; } + if (i < str.length) { + c2 = str.charCodeAt(i) & 0x3ff; + } else { + c2 = 0; + } c = 0x10000 + (((c & 0x3ff) << 10) | c2); out += hexTable[0xf0 | (c >> 18)] + @@ -954,7 +1063,11 @@ function encodeAuth(str) { hexTable[0x80 | ((c >> 6) & 0x3f)] + hexTable[0x80 | (c & 0x3f)]; } - if (lastPos === 0) { return str; } - if (lastPos < str.length) { return out + str.slice(lastPos); } + if (lastPos === 0) { + return str; + } + if (lastPos < str.length) { + return out + str.slice(lastPos); + } return out; }