diff --git a/spec/AudienceRouter.spec.js b/spec/AudienceRouter.spec.js index f2302d32f7..af8c326bae 100644 --- a/spec/AudienceRouter.spec.js +++ b/spec/AudienceRouter.spec.js @@ -317,59 +317,58 @@ describe('AudiencesRouter', () => { ); }); - 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); - }); - }); - }); - }); + it_id('af1111b5-3251-4b40-8f06-fb0fc624fa91')( + '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((error, rows) => { + expect(error).toEqual(undefined); + 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); + }); + }); + }); + }); + } + ); it('should be able to search on audiences', done => { Parse._request( diff --git a/spec/AuthenticationAdapters.spec.js b/spec/AuthenticationAdapters.spec.js index ee232887c9..7a0b13a700 100644 --- a/spec/AuthenticationAdapters.spec.js +++ b/spec/AuthenticationAdapters.spec.js @@ -2045,26 +2045,29 @@ describe('facebook limited auth adapter', () => { } }); - it('should use algorithm from key header to verify id_token', 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')( + 'should use algorithm from key header to verify id_token', + 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' } }; @@ -2095,89 +2098,101 @@ describe('facebook limited auth adapter', () => { } }); - it('(using client id as string) should verify id_token', 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('(using client id as array) should verify id_token', 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('(using client id as array with multiple items) should verify id_token', 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('(using client id as string) should throw error with with invalid jwt issuer', 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')( + '(using client id as string) should verify id_token', + 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')( + '(using client id as array) should verify id_token', + 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')( + '(using client id as array with multiple items) should verify id_token', + 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')( + '(using client id as string) should throw error with with invalid jwt issuer', + 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 @@ -2208,32 +2223,35 @@ describe('facebook limited auth adapter', () => { } }); - it('(using client id as string) should throw error with with invalid jwt issuer', 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: 'INSERT ID HERE', - token: 'INSERT FACEBOOK TOKEN HERE WITH INVALID JWT ISSUER', - }, - { clientId: 'INSERT CLIENT ID HERE' } - ); - 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' - ); + it_id('afbe5693-e2b8-4e9c-b470-eda4b6b31dce')( + '(using client id as string) should throw error with with invalid jwt issuer', + 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: 'INSERT ID HERE', + token: 'INSERT FACEBOOK TOKEN HERE WITH INVALID JWT ISSUER', + }, + { clientId: 'INSERT CLIENT ID HERE' } + ); + 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 @@ -2286,28 +2304,31 @@ describe('facebook limited auth adapter', () => { } }); - it('should throw error with with invalid user id', 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')( + 'should throw error with with invalid user id', + 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/CLI.spec.js b/spec/CLI.spec.js index 9c6a4775a5..2ffa76fd47 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(); } }); @@ -248,7 +252,7 @@ describe('execution', () => { } }); - it('should start Parse Server', done => { + it_id('a0ab74b4-f805-4e03-b31d-b5cd59e64495')('should start Parse Server', done => { const env = { ...process.env }; env.NODE_OPTIONS = '--dns-result-order=ipv4first --trace-deprecation'; childProcess = spawn( @@ -261,70 +265,79 @@ describe('execution', () => { handleError(childProcess, done); }); - 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')( + '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('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')( + '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('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')( + '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.spec.js b/spec/CloudCode.spec.js index fcfeba416b..a3ca52493c 100644 --- a/spec/CloudCode.spec.js +++ b/spec/CloudCode.spec.js @@ -2316,7 +2316,7 @@ describe('beforeFind hooks', () => { ); }); - it('should handle empty where', done => { + it_id('6ef0d226-af30-4dfd-8306-972a1b4becd3')('should handle empty where', done => { Parse.Cloud.beforeFind('MyObject', req => { const otherQuery = new Parse.Query('MyObject'); otherQuery.equalTo('some', true); @@ -2918,55 +2918,61 @@ describe('afterFind hooks', () => { }).toThrow('Only the _Session class is allowed for the afterLogout trigger.'); }); - 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')( + '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('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')( + '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; @@ -3032,37 +3038,40 @@ describe('afterFind hooks', () => { expect(calledAfter).toBe(false); }); - 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')( + '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; @@ -3346,20 +3355,23 @@ describe('beforeLogin hook', () => { expect(response).toEqual(error); }); - it('should have expected data in request', 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')( + 'should have expected data in request', + 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; @@ -3463,20 +3475,23 @@ describe('afterLogin hook', () => { done(); }); - it('should have expected data in request', 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')( + 'should have expected data in request', + 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 => { diff --git a/spec/CloudCodeLogger.spec.js b/spec/CloudCodeLogger.spec.js index abb11024eb..f19e9adc3e 100644 --- a/spec/CloudCodeLogger.spec.js +++ b/spec/CloudCodeLogger.spec.js @@ -37,7 +37,7 @@ describe('Cloud Code Logger', () => { // Note that helpers takes care of logout. // see helpers.js:afterEach - it('should expose log to functions', () => { + it_id('02d53b97-3ec7-46fb-abb6-176fd6e85590')('should expose log to functions', () => { const spy = spyOn(Config.get('test').loggerController, 'log').and.callThrough(); Parse.Cloud.define('loggerTest', req => { req.log.info('logTest', 'info log', { info: 'some log' }); @@ -67,7 +67,7 @@ describe('Cloud Code Logger', () => { }); }); - it('trigger should obfuscate password', done => { + it_id('768412f5-d32f-4134-89a6-08949781a6c0')('trigger should obfuscate password', done => { Parse.Cloud.beforeSave(Parse.User, req => { return req.object; }); @@ -82,7 +82,7 @@ describe('Cloud Code Logger', () => { .then(null, e => done.fail(e)); }); - it('should expose log to trigger', done => { + it_id('3c394047-272e-4728-9d02-9eaa660d2ed2')('should expose log to trigger', done => { Parse.Cloud.beforeSave('MyObject', req => { req.log.info('beforeSave MyObject', 'info log', { info: 'some log' }); req.log.error('beforeSave MyObject', 'error log', { @@ -120,25 +120,28 @@ describe('Cloud Code Logger', () => { expect(truncatedString.length).toBe(1015); // truncate length + the string '... (truncated)' }); - 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')( + '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('should log an afterSave', done => { + it_id('9857e15d-bb18-478d-8a67-fdaad3e89565')('should log an afterSave', done => { Parse.Cloud.afterSave('MyObject', () => {}); new Parse.Object('MyObject') .save() @@ -151,7 +154,7 @@ describe('Cloud Code Logger', () => { .then(null, e => done.fail(e)); }); - it('should log a denied beforeSave', done => { + it_id('ec13a296-f8b1-4fc6-985a-3593462edd9c')('should log a denied beforeSave', done => { Parse.Cloud.beforeSave('MyObject', () => { throw 'uh oh!'; }); @@ -174,7 +177,7 @@ describe('Cloud Code Logger', () => { }); }); - it('should log cloud function success', done => { + it_id('3e0caa45-60d6-41af-829a-fd389710c132')('should log cloud function success', done => { Parse.Cloud.define('aFunction', () => { return 'it worked!'; }); @@ -189,41 +192,44 @@ describe('Cloud Code Logger', () => { }); }); - 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')( + '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', () => {}); @@ -260,7 +266,7 @@ describe('Cloud Code Logger', () => { expect(calls).toEqual({ beforeSave: 'warn', afterSave: undefined }); }); - it('should log cloud function failure', done => { + it_id('97e0eafa-cde6-4a9a-9e53-7db98bacbc62')('should log cloud function failure', done => { Parse.Cloud.define('aFunction', () => { throw 'it failed!'; }); @@ -311,19 +317,22 @@ describe('Cloud Code Logger', () => { .then(null, e => done.fail(JSON.stringify(e))); }).pend('needs more work.....'); - it('cloud function should obfuscate password', done => { - Parse.Cloud.define('testFunction', () => { - return 'verify code success'; - }); + it_id('b86e8168-8370-4730-a4ba-24ca3016ad66')( + '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 a1c7e3dca5..78f791b936 100644 --- a/spec/DefinedSchemas.spec.js +++ b/spec/DefinedSchemas.spec.js @@ -642,41 +642,44 @@ describe('DefinedSchemas', () => { expect(logger.error).toHaveBeenCalledWith(`Failed to run migrations: ${error.toString()}`); }); - 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')( + '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 39ad6e5edd..49649e00d8 100644 --- a/spec/EmailVerificationToken.spec.js +++ b/spec/EmailVerificationToken.spec.js @@ -104,195 +104,207 @@ describe('Email Verification Token Expiration: ', () => { }); }); - 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')( + '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('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')( + '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('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')( + '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('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')( + '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('can conditionally send emails', async () => { + it_id('9365c53c-b8b4-41f7-a3c1-77882f76a89c')('can conditionally send emails', async () => { let sendEmailOptions; const emailAdapter = { sendVerificationEmail: options => { @@ -351,85 +363,97 @@ describe('Email Verification Token Expiration: ', () => { expect(verifySpy).toHaveBeenCalled(); }); - 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('b3549300-bed7-4a5e-bed5-792dbfead960')( + '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('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('d812de87-33d1-495e-a6e8-3485f6dc3589')( + '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('provides full user object in email verification function on email and username change', async () => { const emailAdapter = { @@ -467,164 +491,173 @@ describe('Email Verification Token Expiration: ', () => { await user.save(null, { useMasterKey: true }); }); - 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_id('a8c1f820-822f-4a37-9d08-a968cac8369d')( + '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(); - }); + 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('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(); + it_id('36d277eb-ec7c-4a39-9108-435b68228741')( + '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(() => 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(); - }); + .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('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', - }; + it_id('4f444704-ec4b-4dff-b947-614b1c6971c4')( + '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', + }; - // 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' - ); + // 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(); }); - }) - .catch(error => { - jfail(error); - 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(); @@ -755,92 +788,95 @@ describe('Email Verification Token Expiration: ', () => { }); }); - 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]; - }); + it_id('28f2140d-48bd-44ac-a141-ba60ea8d9713')( + '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(newUser => { - // store this user before we make our email request - userBeforeRequest = newUser; - - expect(sendVerificationEmailCallCount).toBe(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; - 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(); + expect(sendVerificationEmailCallCount).toBe(1); - // 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]; + 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(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(); - }); - }); + }) + .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(); @@ -850,7 +886,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 +952,151 @@ describe('Email Verification Token Expiration: ', () => { done(); }); - 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')( + '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('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')( + '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 +1277,70 @@ describe('Email Verification Token Expiration: ', () => { }); }); - 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')( + '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/Idempotency.spec.js b/spec/Idempotency.spec.js index 813923b1ff..df76a4fc9f 100644 --- a/spec/Idempotency.spec.js +++ b/spec/Idempotency.spec.js @@ -46,52 +46,58 @@ describe('Idempotency', () => { }); // Tests - 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')( + '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('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')( + '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', @@ -119,140 +125,158 @@ describe('Idempotency', () => { } ); - 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('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')( + '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('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')( + '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('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')( + '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('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')( + '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('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')( + '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')( + '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 eb119fe56c..b3ee96e53b 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('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('does scrub complex passwords on GET login', done => { - reconfigureServer({ - verbose: true, - }) - .then(function () { - return request({ + it_id('e36d6141-2a20-41d0-85fc-d1534c3e4bae')( + '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('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')( + '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')( + '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 af547ec77a..bd3a6cd7bf 100644 --- a/spec/Middlewares.spec.js +++ b/spec/Middlewares.spec.js @@ -32,7 +32,7 @@ describe('middlewares', () => { AppCache.del(fakeReq.body._ApplicationId); }); - it('should use _ContentType if provided', done => { + it_id('4cc18d90-1763-4725-97fa-f63fb4692fc4')('should use _ContentType if provided', done => { AppCachePut(fakeReq.body._ApplicationId, { masterKeyIps: ['127.0.0.1'], }); @@ -128,49 +128,55 @@ describe('middlewares', () => { const otherKeys = BodyKeys.filter( otherKey => otherKey !== infoKey && otherKey !== 'javascriptKey' ); - 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 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')( + '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('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('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')( + '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('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')( + '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 5f86922b08..3b6e316fdc 100644 --- a/spec/PagesRouter.spec.js +++ b/spec/PagesRouter.spec.js @@ -738,148 +738,157 @@ describe('Pages Router', () => { ); }); - 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('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('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')( + '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')( + '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')( + '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('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')( + '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 143922852d..df96c8030f 100644 --- a/spec/Parse.Push.spec.js +++ b/spec/Parse.Push.spec.js @@ -111,7 +111,7 @@ const setup = function () { }; describe('Parse.Push', () => { - it('should properly send push', async () => { + it_id('d1e591c4-2b21-466b-9ee2-5be467b6b771')('should properly send push', async () => { const { sendToInstallationSpy } = await setup(); const pushStatusId = await Parse.Push.send({ where: { @@ -126,71 +126,80 @@ describe('Parse.Push', () => { expect(sendToInstallationSpy.calls.count()).toEqual(10); }); - 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')( + '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('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')( + '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')( + '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('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 58c1134bb1..6bd1e04332 100644 --- a/spec/ParseAPI.spec.js +++ b/spec/ParseAPI.spec.js @@ -163,90 +163,96 @@ describe('miscellaneous', function () { expect(numCreated).toBe(1); }); - 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')( + '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('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')( + '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,7 @@ describe('miscellaneous', function () { }, fail); }); - it('increment with a user object', function (done) { + it_id('33db6efe-7c02-496c-8595-0ef627a94103')('increment with a user object', function (done) { createTestUser() .then(user => { user.increment('foo'); @@ -317,7 +323,7 @@ describe('miscellaneous', function () { ); }); - it('save various data types', function (done) { + it_id('bef99522-bcfd-4f79-ba9e-3c3845550401')('save various data types', function (done) { const obj = new TestObject(); obj.set('date', new Date()); obj.set('array', [1, 2, 3]); @@ -951,7 +957,7 @@ describe('miscellaneous', function () { ); }); - it('return the updated fields on PUT', async () => { + it_id('e9e718a9-4465-4158-b13e-f146855a8892')('return the updated fields on PUT', async () => { const obj = new Parse.Object('GameScore'); const pointer = new Parse.Object('Child'); await pointer.save(); @@ -1023,83 +1029,86 @@ describe('miscellaneous', function () { expect(body.updatedAt).not.toBeUndefined(); }); - 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('ea358b59-03c0-45c9-abc7-1fdd67573029')( + '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 +1500,50 @@ describe('miscellaneous', function () { }); }); - 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')( + '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'); @@ -1680,7 +1692,7 @@ describe('miscellaneous', function () { }); }); - it('purge all objects in class', done => { + it_id('8f99ee20-3da7-45ec-b867-ea0eb87524a9')('purge all objects in class', done => { const object = new Parse.Object('TestObject'); object.set('foo', 'bar'); const object2 = new Parse.Object('TestObject'); diff --git a/spec/ParseGeoPoint.spec.js b/spec/ParseGeoPoint.spec.js index 8454e60c74..04429da80a 100644 --- a/spec/ParseGeoPoint.spec.js +++ b/spec/ParseGeoPoint.spec.js @@ -93,7 +93,7 @@ describe('Parse.GeoPoint testing', () => { ); }); - it('geo line', async done => { + it_id('bbd9e2f6-7f61-458f-98f2-4a563586cd8d')('geo line', async done => { const line = []; for (let i = 0; i < 10; ++i) { const obj = new TestObject(); @@ -143,7 +143,7 @@ describe('Parse.GeoPoint testing', () => { ); }); - it('geo max distance medium', async () => { + it_id('e1e86b38-b8a4-4109-8330-a324fe628e0c')('geo max distance medium', async () => { const objects = []; [0, 1, 2].map(function (i) { const obj = new TestObject(); @@ -207,7 +207,7 @@ describe('Parse.GeoPoint testing', () => { done(); }); - it('geo max distance in km california', async () => { + it_id('05f1a454-56b1-4f2e-908e-408a9222cbae')('geo max distance in km california', async () => { await makeSomeGeoPoints(); const sfo = new Parse.GeoPoint(37.6189722, -122.3748889); const query = new Parse.Query(TestObject); @@ -246,16 +246,19 @@ describe('Parse.GeoPoint testing', () => { equal(results.length, 3); }); - 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')( + '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(); @@ -276,7 +279,7 @@ describe('Parse.GeoPoint testing', () => { equal(results.length, 0); }); - it('returns nearest location', async () => { + it_id('9e35a89e-bc2c-4ec5-b25a-8d1890a55233')('returns nearest location', async () => { await makeSomeGeoPoints(); const sfo = new Parse.GeoPoint(37.6189722, -122.3748889); const query = new Parse.Query(TestObject); @@ -286,7 +289,7 @@ describe('Parse.GeoPoint testing', () => { equal(results[1].get('name'), 'Sacramento'); }); - it('works with geobox queries', done => { + it_id('6df434b0-142d-4302-bbc6-a6ec5a9d9c68')('works with geobox queries', done => { const inbound = new Parse.GeoPoint(1.5, 1.5); const onbound = new Parse.GeoPoint(10, 10); const outbound = new Parse.GeoPoint(20, 20); @@ -356,7 +359,7 @@ describe('Parse.GeoPoint testing', () => { }); }); - it('supports withinPolygon open path', done => { + it_id('d9fbc5c6-f767-47d6-bb44-3858eb9df15a')('supports withinPolygon open path', done => { const inbound = new Parse.GeoPoint(1.5, 1.5); const onbound = new Parse.GeoPoint(10, 10); const outbound = new Parse.GeoPoint(20, 20); @@ -394,7 +397,7 @@ describe('Parse.GeoPoint testing', () => { }, done.fail); }); - it('supports withinPolygon closed path', done => { + it_id('3ec537bd-839a-4c93-a48b-b4a249820074')('supports withinPolygon closed path', done => { const inbound = new Parse.GeoPoint(1.5, 1.5); const onbound = new Parse.GeoPoint(10, 10); const outbound = new Parse.GeoPoint(20, 20); @@ -433,7 +436,7 @@ describe('Parse.GeoPoint testing', () => { }, done.fail); }); - it('supports withinPolygon Polygon object', done => { + it_id('0a248e11-3598-480a-9ab5-8a0b259258e4')('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); @@ -752,38 +755,44 @@ describe('Parse.GeoPoint testing', () => { equal(count, 1); }); - 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')( + '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('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')( + '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 9f8b109ed3..7f23f2b313 100644 --- a/spec/ParseGlobalConfig.spec.js +++ b/spec/ParseGlobalConfig.spec.js @@ -157,7 +157,7 @@ describe('a GlobalConfig', () => { }); }); - it('properly handles delete op', done => { + it_id('5ebbd0cf-d1a5-49d9-aac7-5216abc5cb62')('properly handles delete op', done => { request({ method: 'PUT', url: 'http://localhost:8378/1/config', diff --git a/spec/ParseGraphQLSchema.spec.js b/spec/ParseGraphQLSchema.spec.js index 0d4cd7d721..9f04fc3607 100644 --- a/spec/ParseGraphQLSchema.spec.js +++ b/spec/ParseGraphQLSchema.spec.js @@ -500,77 +500,83 @@ describe('ParseGraphQLSchema', () => { }); }); describe('alias', () => { - 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')( + '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('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')( + '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 8450cb0bbe..595b883480 100644 --- a/spec/ParseGraphQLServer.spec.js +++ b/spec/ParseGraphQLServer.spec.js @@ -130,14 +130,17 @@ describe('ParseGraphQLServer', () => { set: () => {}, }; - 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')( + "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,623 +1398,719 @@ describe('ParseGraphQLServer', () => { await resetGraphQLCache(); }); - 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')( + '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('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')( + '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('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')( + '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('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')( + '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('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')( + '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; + `, + }) + ).data.createSuperCar.superCar; - expect(superCarId).toBeTruthy(); + expect(superCarId).toBeTruthy(); - await expectAsync( - apolloClient.query({ - query: gql` - mutation InvalidUpdateSuperCar($id: ID!) { - updateSuperCar(input: { id: $id, fields: { engine: "petrol" } }) { - clientMutationId + await expectAsync( + apolloClient.query({ + query: gql` + mutation InvalidUpdateSuperCar($id: ID!) { + updateSuperCar(input: { id: $id, fields: { engine: "petrol" } }) { + clientMutationId + } } - } - `, - variables: { - id: superCarId, - }, - }) - ).toBeRejected(); + `, + variables: { + id: superCarId, + }, + }) + ).toBeRejected(); - const updatedSuperCar = ( - await apolloClient.query({ - query: gql` - mutation ValidUpdateSuperCar($id: ID!) { - updateSuperCar(input: { id: $id, fields: { mileage: 2000 } }) { - clientMutationId + 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(); - }); + `, + variables: { + id: superCarId, + }, + }) + ).data.updateSuperCar; + expect(updatedSuperCar).toBeTruthy(); + } + ); - 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' }, - }); + it_id('fc9237e9-3e63-4b55-9c1d-e6269f613a93')( + '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 resetGraphQLCache(); + await resetGraphQLCache(); - const { - data: { __type }, - } = await apolloClient.query({ - query: gql` - query requiredFields { - __type(name: "CreateSuperCarFieldsInput") { - inputFields { - name - type { - kind + const { + data: { __type }, + } = await apolloClient.query({ + query: gql` + query requiredFields { + __type(name: "CreateSuperCarFieldsInput") { + inputFields { + name + type { + kind + } } } } - } - `, - }); - 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'); + `, + }); + 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 requiredFields { - __type(name: "SuperCar") { - fields { - name - type { - kind + const { + data: { __type: __type2 }, + } = await apolloClient.query({ + query: gql` + query requiredFields { + __type(name: "SuperCar") { + fields { + name + type { + kind + } } } } - } - `, - }); - 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'); - }); + `, + }); + 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('should only allow the supplied output fields for a class', async () => { - const schemaController = await parseServer.config.databaseController.loadSchema(); + it_id('83b6895a-7dfd-4e3b-a5ce-acdb1fa39705')( + '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' }, - }); + 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', - }); + 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'], + await parseGraphQLServer.setGraphQLConfig({ + classConfigs: [ + { + className: 'SuperCar', + type: { + outputFields: ['engine', 'doors', 'price', 'mileage'], + }, }, - }, - ], - }); + ], + }); - await resetGraphQLCache(); + await resetGraphQLCache(); - await expectAsync( - apolloClient.query({ - query: gql` - query GetSuperCar($id: ID!) { - superCar(id: $id) { - id - objectId - engine - doors - price - mileage - insuranceCertificate + 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, + }, + }) + ).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(); + `, + 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 GetSuperCar($id: ID!) { + superCar(id: $id) { + engine + } + } + `, + variables: { + id: superCar.id, + }, + }) + ).toBeRejected(); + getSuperCar = ( + await apolloClient.query({ + query: gql` + query GetSuperCar($id: ID!) { + superCar(id: $id) { + id + objectId + } + } + `, + variables: { + id: superCar.id, + }, + }) + ).data.superCar; + expect(getSuperCar.objectId).toBe(superCar.id); + } + ); + + it_id('67dfcf94-92fb-45a3-a012-3b22c81899ba')( + '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({ + model: 'McLaren', + engine: 'petrol', + doors: 3, + price: '£7500', + mileage: 0, + insuranceCertificate: 'private-file.pdf', + }); + + await parseGraphQLServer.setGraphQLConfig({ + classConfigs: [ + { + className: 'SuperCar', + type: { + constraintFields: ['engine', 'doors', 'price'], + }, + }, + ], + }); + + await resetGraphQLCache(); - await parseGraphQLServer.setGraphQLConfig({ - classConfigs: [ - { - className: 'SuperCar', - type: { - outputFields: [], - }, - }, - ], - }); + await expectAsync( + apolloClient.query({ + query: gql` + query FindSuperCar { + superCars(where: { insuranceCertificate: { equalTo: "private-file.pdf" } }) { + count + } + } + `, + }) + ).toBeRejected(); - await resetGraphQLCache(); - await expectAsync( - apolloClient.query({ - query: gql` - query GetSuperCar($id: ID!) { - superCar(id: $id) { - engine - } - } - `, - variables: { - id: superCar.id, - }, - }) - ).toBeRejected(); - getSuperCar = ( - await apolloClient.query({ - query: gql` - query GetSuperCar($id: ID!) { - superCar(id: $id) { - id - objectId - } - } - `, - variables: { - id: superCar.id, - }, - }) - ).data.superCar; - expect(getSuperCar.objectId).toBe(superCar.id); - }); + await expectAsync( + apolloClient.query({ + query: gql` + query FindSuperCar { + superCars(where: { mileage: { equalTo: 0 } }) { + count + } + } + `, + }) + ).toBeRejected(); - it('should only allow the supplied constraint fields for a class', async () => { - try { + 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')( + 'should only allow the supplied sort 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' }, }); await new Parse.Object('SuperCar').save({ - model: 'McLaren', engine: 'petrol', doors: 3, price: '£7500', mileage: 0, - insuranceCertificate: 'private-file.pdf', }); await parseGraphQLServer.setGraphQLConfig({ @@ -2019,7 +2118,23 @@ describe('ParseGraphQLServer', () => { { className: 'SuperCar', type: { - constraintFields: ['engine', 'doors', 'price'], + sortFields: [ + { + field: 'doors', + asc: true, + desc: true, + }, + { + field: 'price', + asc: true, + desc: true, + }, + { + field: 'mileage', + asc: true, + desc: false, + }, + ], }, }, ], @@ -2031,195 +2146,110 @@ describe('ParseGraphQLServer', () => { apolloClient.query({ query: gql` query FindSuperCar { - superCars(where: { insuranceCertificate: { equalTo: "private-file.pdf" } }) { - count + superCars(order: [engine_ASC]) { + edges { + node { + id + } + } } } `, }) ).toBeRejected(); - await expectAsync( apolloClient.query({ query: gql` query FindSuperCar { - superCars(where: { mileage: { equalTo: 0 } }) { - count + superCars(order: [engine_DESC]) { + edges { + node { + id + } + } } } `, }) ).toBeRejected(); - await expectAsync( apolloClient.query({ query: gql` query FindSuperCar { - superCars(where: { engine: { equalTo: "petrol" } }) { - count + superCars(order: [mileage_DESC]) { + edges { + node { + id + } + } } } `, }) - ).toBeResolved(); - } catch (e) { - handleError(e); - } - }); - - 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 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: [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 +4951,53 @@ describe('ParseGraphQLServer', () => { ).toEqual(['someValue1', 'someValue2']); }); - 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')( + '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,54 +5065,57 @@ describe('ParseGraphQLServer', () => { } }); - 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); + it_id('0fd03d3c-a2c8-4fac-95cc-2391a3032ca2')( + '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(); + 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 + 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', + `, + variables: { + where: { + someField: { + matchesRegex: '^someValue', + }, }, + order: ['numberField_DESC', 'someField_ASC'], + skip: 4, + first: 2, }, - order: ['numberField_DESC', 'someField_ASC'], - skip: 4, - first: 2, - }, - }); + }); - expect(result.data.find.edges.map(obj => obj.node.someField)).toEqual([ - 'someValue14', - 'someValue17', - ]); - }); + expect(result.data.find.edges.map(obj => obj.node.someField)).toEqual([ + 'someValue14', + 'someValue17', + ]); + } + ); - it('should support pagination', async () => { + it_id('588a70c6-2932-4d3b-a838-a74c59d8cffb')('should support pagination', async () => { const numberArray = (first, last) => { const array = []; for (let i = first; i <= last; i++) { @@ -5221,7 +5257,7 @@ describe('ParseGraphQLServer', () => { expect(result.data.someClasses.pageInfo.hasNextPage).toEqual(true); }); - it('should support count', async () => { + it_id('4f6a5f20-9642-4cf0-b31d-e739672a9096')('should support count', async () => { await prepareData(); await parseGraphQLServer.parseGraphQLSchema.schemaCache.clear(); @@ -5324,7 +5360,7 @@ describe('ParseGraphQLServer', () => { expect(result.data.find.count).toEqual(2); }); - it('should respect max limit', async () => { + it_id('942b57be-ca8a-4a5b-8104-2adef8743b1a')('should respect max limit', async () => { parseServer = await global.reconfigureServer({ maxLimit: 10, }); @@ -5365,67 +5401,70 @@ describe('ParseGraphQLServer', () => { expect(result.data.find.count).toEqual(100); }); - it('should support keys argument', async () => { - await prepareData(); + it_id('952634f0-0ad5-4a08-8da2-187c1bd9ee94')( + '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 +5810,69 @@ describe('ParseGraphQLServer', () => { ).toEqual([object3.id, object1.id, object2.id]); }); - it('should support including relation', async () => { - await prepareData(); + it_id('47a6adf3-1cb4-4d92-b74c-e480363f9cb5')( + '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 +6718,164 @@ describe('ParseGraphQLServer', () => { }); }); - 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')( + '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', () => { @@ -8320,7 +8365,7 @@ describe('ParseGraphQLServer', () => { expect(schema.fields.updatedAt.type).toEqual('Date'); }); - it('should support ACL', async () => { + it_id('93e748f6-ad9b-4c31-8e1e-c5685e2382fb')('should support ACL', async () => { const someClass = new Parse.Object('SomeClass'); await someClass.save(); @@ -9074,232 +9119,235 @@ describe('ParseGraphQLServer', () => { expect(result2.companies.edges[0].node.objectId).toEqual(company1.id); }); - 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')( + '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 { @@ -10374,7 +10422,7 @@ describe('ParseGraphQLServer', () => { } }); - it('should support Bytes', async () => { + it_id('43303db7-c5a7-4bc0-91c3-57e03fffa225')('should support Bytes', async () => { try { const someFieldValue = 'aGVsbG8gd29ybGQ='; @@ -10461,7 +10509,7 @@ describe('ParseGraphQLServer', () => { } }); - it('should support Geo Points', async () => { + it_id('6a253e47-6959-4427-b841-c0c1fa77cf01')('should support Geo Points', async () => { try { const someFieldValue = { __typename: 'GeoPoint', diff --git a/spec/ParseHooks.spec.js b/spec/ParseHooks.spec.js index 8d7c653fa2..1cbe4a25af 100644 --- a/spec/ParseHooks.spec.js +++ b/spec/ParseHooks.spec.js @@ -54,7 +54,7 @@ describe('Hooks', () => { ); }); - it('should CRUD a function registration', done => { + it_id('26c9a13d-3d71-452e-a91c-9a4589be021c')('should CRUD a function registration', done => { // Create Parse.Hooks.createFunction('My-Test-Function', 'http://someurl') .then(response => { @@ -98,7 +98,7 @@ describe('Hooks', () => { }); }); - it('should CRUD a trigger registration', done => { + it_id('7a81069e-2ee9-47fb-8e27-1120eda09e99')('should CRUD a trigger registration', done => { // Create Parse.Hooks.createTrigger('MyClass', 'beforeDelete', 'http://someurl') .then( @@ -188,76 +188,82 @@ describe('Hooks', () => { }); }); - 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')( + '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('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')( + '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('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')( + '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('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')( + 'should run the function on the test server', + done => { + app.post('/SomeFunction', function (req, res) { + res.json({ success: 'OK!' }); + }); - 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('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('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')( + '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('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')( + '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('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')( + '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('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')( + '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')( + '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')( + '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 d462ad4626..c2c94de1bd 100644 --- a/spec/ParseInstallation.spec.js +++ b/spec/ParseInstallation.spec.js @@ -406,7 +406,7 @@ describe('Installations', () => { }); }); - it('updating with new channels', done => { + it_id('95955e90-04bc-4437-920e-b84bc30dba01')('updating with new channels', done => { const input = { installationId: '12345678-abcd-abcd-abcd-123456789abc', deviceType: 'android', @@ -856,58 +856,61 @@ describe('Installations', () => { }); }); - 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')( + '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('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')( + '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 @@ -1239,7 +1245,7 @@ describe('Installations', () => { }); }); - it('can use push with beforeSave', async () => { + it_id('e581faea-c1b4-4c64-af8c-52287ce6cd06')('can use push with beforeSave', async () => { const input = { deviceToken: '11433856eed2f1285fb3aa11136718c1198ed5647875096952c66bf8cb976306', deviceType: 'ios', diff --git a/spec/ParseLiveQuery.spec.js b/spec/ParseLiveQuery.spec.js index 22e0918d1b..b08932109c 100644 --- a/spec/ParseLiveQuery.spec.js +++ b/spec/ParseLiveQuery.spec.js @@ -916,64 +916,67 @@ describe('ParseLiveQuery', function () { }, 1000); }); - 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, + it_id('39a9191f-26dd-4e05-a379-297a67928de8')( + '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({ @@ -1146,7 +1149,7 @@ describe('ParseLiveQuery', function () { await object.save(); }); - it('does shutdown liveQuery server', async () => { + it_id('2f95d8a9-7675-45ba-a4a6-e45cb7efb1fb')('does shutdown liveQuery server', async () => { await reconfigureServer({ appId: 'test_app_id' }); const config = { appId: 'hello_test', diff --git a/spec/ParsePolygon.spec.js b/spec/ParsePolygon.spec.js index fd16832391..4163ea00db 100644 --- a/spec/ParsePolygon.spec.js +++ b/spec/ParsePolygon.spec.js @@ -62,7 +62,7 @@ describe('Parse.Polygon testing', () => { }, done.fail); }); - it('polygon equalTo (open/closed) path', done => { + it_id('3019353b-d5b3-4e53-bcb1-716418328bdd')('polygon equalTo (open/closed) path', done => { const openPoints = [ [0, 0], [0, 1], diff --git a/spec/ParseQuery.Aggregate.spec.js b/spec/ParseQuery.Aggregate.spec.js index a61305286c..77181e30ce 100644 --- a/spec/ParseQuery.Aggregate.spec.js +++ b/spec/ParseQuery.Aggregate.spec.js @@ -95,7 +95,7 @@ describe('Parse.Query Aggregate testing', () => { }); }); - it('group by field', done => { + it_id('add7050f-65d5-4a13-b526-5bd1ee09c7f1')('group by field', done => { const options = Object.assign({}, masterKeyOptions, { body: { $group: { _id: '$name' }, @@ -115,7 +115,7 @@ describe('Parse.Query Aggregate testing', () => { .catch(done.fail); }); - it('group by pipeline operator', async () => { + it_id('0ab0d776-e45d-419a-9b35-3d11933b77d1')('group by pipeline operator', async () => { const options = Object.assign({}, masterKeyOptions, { body: { pipeline: { @@ -133,7 +133,7 @@ describe('Parse.Query Aggregate testing', () => { expect(resp.results[2].objectId).not.toBe(undefined); }); - it('group by empty object', done => { + it_id('b6b42145-7eb4-47aa-ada6-8c1444420e07')('group by empty object', done => { const obj = new TestObject(); const pipeline = [ { @@ -152,7 +152,7 @@ describe('Parse.Query Aggregate testing', () => { }); }); - it('group by empty string', done => { + it_id('0f5f6869-e675-41b9-9ad2-52b201124fb0')('group by empty string', done => { const obj = new TestObject(); const pipeline = [ { @@ -171,7 +171,7 @@ describe('Parse.Query Aggregate testing', () => { }); }); - it('group by empty array', done => { + it_id('b9c4f1b4-47f4-4ff4-88fb-586711f57e4a')('group by empty array', done => { const obj = new TestObject(); const pipeline = [ { @@ -190,7 +190,7 @@ describe('Parse.Query Aggregate testing', () => { }); }); - it('group by multiple columns ', done => { + it_id('bf5ee3e5-986c-4994-9c8d-79310283f602')('group by multiple columns ', done => { const obj1 = new TestObject(); const obj2 = new TestObject(); const obj3 = new TestObject(); @@ -216,7 +216,7 @@ describe('Parse.Query Aggregate testing', () => { }); }); - it('group by date object', done => { + it_id('3e652c61-78e1-4541-83ac-51ad1def9874')('group by date object', done => { const obj1 = new TestObject(); const obj2 = new TestObject(); const obj3 = new TestObject(); @@ -246,7 +246,7 @@ describe('Parse.Query Aggregate testing', () => { }); }); - it('group by date object transform', done => { + it_id('5d3a0f73-1f49-46f3-9be5-caf1eaefec79')('group by date object transform', done => { const obj1 = new TestObject(); const obj2 = new TestObject(); const obj3 = new TestObject(); @@ -276,7 +276,7 @@ describe('Parse.Query Aggregate testing', () => { }); }); - it('group by number', done => { + it_id('1f9b10f7-dc0e-467f-b506-a303b9c36258')('group by number', done => { const options = Object.assign({}, masterKeyOptions, { body: { $group: { _id: '$score' }, @@ -296,7 +296,7 @@ describe('Parse.Query Aggregate testing', () => { .catch(done.fail); }); - it_exclude_dbs(['postgres'])('group and multiply transform', done => { + it_id('c7695018-03de-49e4-8a72-d4d956f70deb')('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 = [ @@ -319,7 +319,7 @@ describe('Parse.Query Aggregate testing', () => { }); }); - it_exclude_dbs(['postgres'])('project and multiply transform', done => { + it_id('2d278175-7594-4b29-bef4-04c778b7a42f')('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 = [ @@ -351,7 +351,7 @@ describe('Parse.Query Aggregate testing', () => { }); }); - it_exclude_dbs(['postgres'])('project without objectId transform', done => { + it_id('9c9d9318-3a9e-4c2a-8a09-d3aa52c7505b')('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 = [ @@ -383,7 +383,7 @@ describe('Parse.Query Aggregate testing', () => { }); }); - it_exclude_dbs(['postgres'])('project updatedAt only transform', done => { + it_id('f92c82ac-1993-4758-b718-45689dfc4154')('project updatedAt only transform', done => { const pipeline = [ { $project: { _id: 0, updatedAt: 1 }, @@ -401,7 +401,7 @@ describe('Parse.Query Aggregate testing', () => { }); }); - it_exclude_dbs(['postgres'])( + it_id('99566b1d-778d-4444-9deb-c398108e659d')( 'can group by any date field (it does not work if you have dirty data)', // rows in your collection with non date data in the field that is supposed to be a date done => { const obj1 = new TestObject({ dateField2019: new Date(1990, 11, 1) }); @@ -472,7 +472,7 @@ describe('Parse.Query Aggregate testing', () => { } ); - it('group by pointer', done => { + it_id('bf3c2704-b721-4b1b-92fa-e1b129ae4aff')('group by pointer', done => { const pointer1 = new TestObject(); const pointer2 = new TestObject(); const obj1 = new TestObject({ pointer: pointer1 }); @@ -493,7 +493,7 @@ describe('Parse.Query Aggregate testing', () => { }); }); - it('group sum query', done => { + it_id('9ee9e8c0-a590-4af9-97a9-4b8e5080ffae')('group sum query', done => { const options = Object.assign({}, masterKeyOptions, { body: { $group: { _id: null, total: { $sum: '$score' } }, @@ -509,7 +509,7 @@ describe('Parse.Query Aggregate testing', () => { .catch(done.fail); }); - it('group count query', done => { + it_id('39133cd6-5bdf-4917-b672-a9d7a9157b6f')('group count query', done => { const options = Object.assign({}, masterKeyOptions, { body: { $group: { _id: null, total: { $sum: 1 } }, @@ -525,7 +525,7 @@ describe('Parse.Query Aggregate testing', () => { .catch(done.fail); }); - it('group min query', done => { + it_id('48685ff3-066f-4353-82e7-87f39d812ff7')('group min query', done => { const options = Object.assign({}, masterKeyOptions, { body: { $group: { _id: null, minScore: { $min: '$score' } }, @@ -541,7 +541,7 @@ describe('Parse.Query Aggregate testing', () => { .catch(done.fail); }); - it('group max query', done => { + it_id('581efea6-6525-4e10-96d9-76d32c73e7a9')('group max query', done => { const options = Object.assign({}, masterKeyOptions, { body: { $group: { _id: null, maxScore: { $max: '$score' } }, @@ -557,7 +557,7 @@ describe('Parse.Query Aggregate testing', () => { .catch(done.fail); }); - it('group avg query', done => { + it_id('5f880de2-b97f-43d1-89b7-ad903a4be4e2')('group avg query', done => { const options = Object.assign({}, masterKeyOptions, { body: { $group: { _id: null, avgScore: { $avg: '$score' } }, @@ -573,7 +573,7 @@ describe('Parse.Query Aggregate testing', () => { .catch(done.fail); }); - it('limit query', done => { + it_id('58e7a1a0-fae1-4993-b336-7bcbd5b7c786')('limit query', done => { const options = Object.assign({}, masterKeyOptions, { body: { $limit: 2, @@ -587,7 +587,7 @@ describe('Parse.Query Aggregate testing', () => { .catch(done.fail); }); - it('sort ascending query', done => { + it_id('c892a3d2-8ae8-4b88-bf2b-3c958e1cacd8')('sort ascending query', done => { const options = Object.assign({}, masterKeyOptions, { body: { $sort: { name: 1 }, @@ -605,7 +605,7 @@ describe('Parse.Query Aggregate testing', () => { .catch(done.fail); }); - it('sort decending query', done => { + it_id('79d4bc2e-8b69-42ec-8526-20d17e968ab3')('sort decending query', done => { const options = Object.assign({}, masterKeyOptions, { body: { $sort: { name: -1 }, @@ -623,7 +623,7 @@ describe('Parse.Query Aggregate testing', () => { .catch(done.fail); }); - it('skip query', done => { + it_id('b3d97d48-bd6b-444d-be64-cc1fd4738266')('skip query', done => { const options = Object.assign({}, masterKeyOptions, { body: { $skip: 2, @@ -637,7 +637,7 @@ describe('Parse.Query Aggregate testing', () => { .catch(done.fail); }); - it('match comparison date query', done => { + it_id('4a7daee3-5ba1-4c8b-b406-1846a73a64c8')('match comparison date query', done => { const today = new Date(); const yesterday = new Date(); const tomorrow = new Date(); @@ -658,24 +658,27 @@ describe('Parse.Query Aggregate testing', () => { }); }); - 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')( + '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('match comparison query', done => { + it_id('3d73d23a-fce1-4ac0-972a-50f6a550f348')('match comparison query', done => { const options = Object.assign({}, masterKeyOptions, { body: { $match: { score: { $gt: 15 } }, @@ -690,7 +693,7 @@ describe('Parse.Query Aggregate testing', () => { .catch(done.fail); }); - it('match multiple comparison query', done => { + it_id('11772059-6c93-41ac-8dfe-e55b6c97e16f')('match multiple comparison query', done => { const options = Object.assign({}, masterKeyOptions, { body: { $match: { score: { $gt: 5, $lt: 15 } }, @@ -707,7 +710,7 @@ describe('Parse.Query Aggregate testing', () => { .catch(done.fail); }); - it('match complex comparison query', done => { + it_id('ca2efb04-8f73-40ca-a5fc-79d0032bc398')('match complex comparison query', done => { const options = Object.assign({}, masterKeyOptions, { body: { $match: { score: { $gt: 5, $lt: 15 }, views: { $gt: 850, $lt: 1000 } }, @@ -723,7 +726,7 @@ describe('Parse.Query Aggregate testing', () => { .catch(done.fail); }); - it('match comparison and equality query', done => { + it_id('5ef9dcbe-fe54-4db2-b8fb-58c87c6ff072')('match comparison and equality query', done => { const options = Object.assign({}, masterKeyOptions, { body: { $match: { score: { $gt: 5, $lt: 15 }, views: 900 }, @@ -739,7 +742,7 @@ describe('Parse.Query Aggregate testing', () => { .catch(done.fail); }); - it('match $or query', done => { + it_id('c910a6af-58df-46aa-bbf8-da014a04cdcd')('match $or query', done => { const options = Object.assign({}, masterKeyOptions, { body: { $match: { @@ -762,7 +765,7 @@ describe('Parse.Query Aggregate testing', () => { .catch(done.fail); }); - it('match objectId query', done => { + it_id('0f768dc2-0675-4e45-a763-5ca9c895fa5f')('match objectId query', done => { const obj1 = new TestObject(); const obj2 = new TestObject(); Parse.Object.saveAll([obj1, obj2]) @@ -778,7 +781,7 @@ describe('Parse.Query Aggregate testing', () => { }); }); - it('match field query', done => { + it_id('27349e04-0d9d-453f-ad85-1a811631582d')('match field query', done => { const obj1 = new TestObject({ name: 'TestObject1' }); const obj2 = new TestObject({ name: 'TestObject2' }); Parse.Object.saveAll([obj1, obj2]) @@ -794,7 +797,7 @@ describe('Parse.Query Aggregate testing', () => { }); }); - it('match pointer query', done => { + it_id('9222e025-d450-4699-8d5b-c5cf9a64fb24')('match pointer query', done => { const pointer1 = new PointerObject(); const pointer2 = new PointerObject(); const obj1 = new TestObject({ pointer: pointer1 }); @@ -817,7 +820,7 @@ describe('Parse.Query Aggregate testing', () => { }); }); - it_exclude_dbs(['postgres'])('match exists query', done => { + it_id('3a1e2cdc-52c7-4060-bc90-b06d557d85ce')('match exists query', done => { const pipeline = [{ $match: { score: { $exists: true } } }]; const query = new Parse.Query(TestObject); query.aggregate(pipeline).then(results => { @@ -826,7 +829,7 @@ describe('Parse.Query Aggregate testing', () => { }); }); - it('match date query - createdAt', done => { + it_id('0adea3f4-73f7-4b48-a7dd-c764ceb947ec')('match date query - createdAt', done => { const obj1 = new TestObject(); const obj2 = new TestObject(); @@ -845,7 +848,7 @@ describe('Parse.Query Aggregate testing', () => { }); }); - it('match date query - updatedAt', done => { + it_id('cdc0eecb-f547-4881-84cc-c06fb46a636a')('match date query - updatedAt', done => { const obj1 = new TestObject(); const obj2 = new TestObject(); @@ -864,7 +867,7 @@ describe('Parse.Query Aggregate testing', () => { }); }); - it('match date query - empty', done => { + it_id('621fe00a-1127-4341-a8e1-fc579b7ed8bd')('match date query - empty', done => { const obj1 = new TestObject(); const obj2 = new TestObject(); @@ -882,7 +885,7 @@ describe('Parse.Query Aggregate testing', () => { }); }); - it_exclude_dbs(['postgres'])('match pointer with operator query', done => { + it_id('802ffc99-861b-4b72-90a6-0c666a2e3fd8')('match pointer with operator query', done => { const pointer = new PointerObject(); const obj1 = new TestObject({ pointer }); @@ -905,7 +908,7 @@ describe('Parse.Query Aggregate testing', () => { }); }); - it_exclude_dbs(['postgres'])('match null values', async () => { + it_id('28090280-7c3e-47f8-8bf6-bebf8566a36c')('match null values', async () => { const obj1 = new Parse.Object('MyCollection'); obj1.set('language', 'en'); obj1.set('otherField', 1); @@ -955,7 +958,7 @@ describe('Parse.Query Aggregate testing', () => { ).toEqual([1, 2, 3, 4]); }); - it('project query', done => { + it_id('df63d1f5-7c37-4ed9-8bc5-20d82f29f509')('project query', done => { const options = Object.assign({}, masterKeyOptions, { body: { $project: { name: 1 }, @@ -975,7 +978,7 @@ describe('Parse.Query Aggregate testing', () => { .catch(done.fail); }); - it('multiple project query', done => { + it_id('69224bbb-8ea0-4ab4-af23-398b6432f668')('multiple project query', done => { const options = Object.assign({}, masterKeyOptions, { body: { $project: { name: 1, score: 1, sender: 1 }, @@ -995,7 +998,7 @@ describe('Parse.Query Aggregate testing', () => { .catch(done.fail); }); - it('project pointer query', done => { + it_id('97ce4c7c-8d9f-4ffd-9352-394bc9867bab')('project pointer query', done => { const pointer = new PointerObject(); const obj = new TestObject({ pointer, name: 'hello' }); @@ -1018,7 +1021,7 @@ describe('Parse.Query Aggregate testing', () => { }); }); - it('project with group query', done => { + it_id('3940aac3-ac49-4279-8083-af9096de636f')('project with group query', done => { const options = Object.assign({}, masterKeyOptions, { body: { $project: { score: 1 }, @@ -1074,7 +1077,7 @@ describe('Parse.Query Aggregate testing', () => { .catch(done.fail); }); - it('distinct query', done => { + it_id('985e7a66-d4f5-4f72-bd54-ee44670e0ab0')('distinct query', done => { const options = Object.assign({}, masterKeyOptions, { body: { distinct: 'score' }, }); @@ -1088,7 +1091,7 @@ describe('Parse.Query Aggregate testing', () => { .catch(done.fail); }); - it('distinct query with where', done => { + it_id('ef157f86-c456-4a4c-8dac-81910bd0f716')('distinct query with where', done => { const options = Object.assign({}, masterKeyOptions, { body: { distinct: 'score', @@ -1105,7 +1108,7 @@ describe('Parse.Query Aggregate testing', () => { .catch(done.fail); }); - it('distinct query with where string', done => { + it_id('7f5275cc-2c34-42bc-8a09-43378419c326')('distinct query with where string', done => { const options = Object.assign({}, masterKeyOptions, { body: { distinct: 'score', @@ -1120,7 +1123,7 @@ describe('Parse.Query Aggregate testing', () => { .catch(done.fail); }); - it('distinct nested', done => { + it_id('383b7248-e457-4373-8d5c-f9359384347e')('distinct nested', done => { const options = Object.assign({}, masterKeyOptions, { body: { distinct: 'sender.group' }, }); @@ -1134,7 +1137,7 @@ describe('Parse.Query Aggregate testing', () => { .catch(done.fail); }); - it('distinct pointer', done => { + it_id('20f14464-adb7-428c-ac7a-5a91a1952a64')('distinct pointer', done => { const pointer1 = new PointerObject(); const pointer2 = new PointerObject(); const obj1 = new TestObject({ pointer: pointer1 }); @@ -1153,36 +1156,42 @@ describe('Parse.Query Aggregate testing', () => { }); }); - 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')( + '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('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')( + '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('distinct array', done => { + it_id('21988fce-8326-425f-82f0-cd444ca3671b')('distinct array', done => { const options = Object.assign({}, masterKeyOptions, { body: { distinct: 'size' }, }); @@ -1197,13 +1206,13 @@ describe('Parse.Query Aggregate testing', () => { .catch(done.fail); }); - it('distinct objectId', async () => { + it_id('633fde06-c4af-474b-9841-3ccabc24dd4f')('distinct objectId', async () => { const query = new Parse.Query(TestObject); const results = await query.distinct('objectId'); expect(results.length).toBe(4); }); - it('distinct createdAt', async () => { + it_id('8f9706f4-2703-42f1-b524-f2f7e72bbfe7')('distinct createdAt', async () => { const object1 = new TestObject({ createdAt_test: true }); await object1.save(); const object2 = new TestObject({ createdAt_test: true }); @@ -1214,7 +1223,7 @@ describe('Parse.Query Aggregate testing', () => { expect(results.length).toBe(2); }); - it('distinct updatedAt', async () => { + it_id('3562e600-8ce5-4d6d-96df-8ff969e81421')('distinct updatedAt', async () => { const object1 = new TestObject({ updatedAt_test: true }); await object1.save(); const object2 = new TestObject(); @@ -1227,7 +1236,7 @@ describe('Parse.Query Aggregate testing', () => { expect(results.length).toBe(2); }); - it('distinct null field', done => { + it_id('5012cfb1-b0aa-429d-a94f-d32d8aa0b7f9')('distinct null field', done => { const options = Object.assign({}, masterKeyOptions, { body: { distinct: 'distinctField' }, }); @@ -1256,108 +1265,114 @@ describe('Parse.Query Aggregate testing', () => { .catch(done.fail); }); - 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')( + '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_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')( + '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 e6614e5d81..bbbbf9421b 100644 --- a/spec/ParseQuery.FullTextSearch.spec.js +++ b/spec/ParseQuery.FullTextSearch.spec.js @@ -30,7 +30,7 @@ const fullTextHelper = async () => { }; describe('Parse.Query Full Text Search testing', () => { - it('fullTextSearch: $search', async () => { + it_id('77ba6779-6584-4e09-8e7e-31f89e741d6a')('fullTextSearch: $search', async () => { await fullTextHelper(); const query = new Parse.Query('TestObject'); query.fullText('subject', 'coffee'); @@ -38,7 +38,7 @@ describe('Parse.Query Full Text Search testing', () => { expect(results.length).toBe(3); }); - it('fullTextSearch: $search, sort', async () => { + it_id('d1992ea6-6d92-4bfa-a487-2a49fbcf8f0d')('fullTextSearch: $search, sort', async () => { await fullTextHelper(); const query = new Parse.Query('TestObject'); query.fullText('subject', 'coffee'); @@ -51,7 +51,7 @@ describe('Parse.Query Full Text Search testing', () => { expect(results[2].get('score')); }); - it('fulltext descending by $score', async () => { + it_id('07172595-50de-4be2-984a-d3136bebb22e')('fulltext descending by $score', async () => { await fullTextHelper(); const query = new Parse.Query('TestObject'); query.fullText('subject', 'coffee'); @@ -68,7 +68,7 @@ describe('Parse.Query Full Text Search testing', () => { expect(second.get('score') >= third.get('score')).toBeTrue(); }); - it('fullTextSearch: $language', async () => { + it_id('8e821973-3fae-4e7c-8152-766228a18cdd')('fullTextSearch: $language', async () => { await fullTextHelper(); const query = new Parse.Query('TestObject'); query.fullText('subject', 'leche', { language: 'spanish' }); @@ -76,7 +76,7 @@ describe('Parse.Query Full Text Search testing', () => { expect(resp.length).toBe(2); }); - it('fullTextSearch: $diacriticSensitive', async () => { + it_id('7d3da216-9582-40ee-a2fe-8316feaf5c0c')('fullTextSearch: $diacriticSensitive', async () => { await fullTextHelper(); const query = new Parse.Query('TestObject'); query.fullText('subject', 'CAFÉ', { diacriticSensitive: true }); @@ -84,62 +84,77 @@ describe('Parse.Query Full Text Search testing', () => { expect(resp.length).toBe(1); }); - it('fullTextSearch: $search, invalid input', async () => { - await fullTextHelper(); - const invalidQuery = async () => { - const where = { - subject: { - $text: { - $search: true, + it_id('dade10c8-2b9c-4f43-bb3f-a13bbd82ac22')( + '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('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')( + '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('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')( + '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('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')( + '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 6dc19bc6f2..10fbe5cd12 100644 --- a/spec/ParseQuery.spec.js +++ b/spec/ParseQuery.spec.js @@ -590,7 +590,9 @@ describe('Parse.Query testing', () => { }); }); - it('containsAll object array queries', function (done) { + it_id('25bb35a6-e953-4d6d-a31c-66324d5ae076')('containsAll object array queries', function ( + done + ) { const MessageSet = Parse.Object.extend({ className: 'MessageSet' }); const messageList = []; @@ -705,40 +707,43 @@ describe('Parse.Query testing', () => { }); }); - 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')( + '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'); @@ -884,7 +889,7 @@ describe('Parse.Query testing', () => { ); }); - it('containedBy pointer array', done => { + it_id('01a15195-dde2-4368-b996-d746a4ede3a1')('containedBy pointer array', done => { const objects = Array.from(Array(10).keys()).map(idx => { const obj = new Parse.Object('Object'); obj.set('key', idx); @@ -1668,7 +1673,9 @@ describe('Parse.Query testing', () => { .catch(done.fail); }); - it('can order on an object number field', function (done) { + it_id('65c8238d-cf02-49d0-a919-8a17f5a58280')('can order on an object number field', function ( + done + ) { const testSet = [ { sortField: { value: 10 } }, { sortField: { value: 1 } }, @@ -1689,26 +1696,29 @@ describe('Parse.Query testing', () => { .catch(done.fail); }); - 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('d8f0bead-b931-4d66-8b0c-28c5705e463c')( + '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']; @@ -2111,30 +2121,33 @@ describe('Parse.Query testing', () => { .then(done); }); - 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')( + '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(); @@ -3786,7 +3799,7 @@ describe('Parse.Query testing', () => { }); }); - it('notEqual with array of pointers', done => { + it_id('56b09b92-c756-4bae-8c32-1c32b5b4c397')('notEqual with array of pointers', done => { const children = []; const parents = []; const promises = []; @@ -4003,7 +4016,7 @@ describe('Parse.Query testing', () => { ); }); - it('should properly interpret a query v2', done => { + it_id('7079f0ef-47b3-4a1e-aac0-32654dadaa27')('should properly interpret a query v2', done => { const user = new Parse.User(); user.set('username', 'foo'); user.set('password', 'bar'); @@ -4082,48 +4095,51 @@ describe('Parse.Query testing', () => { }); }); - 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')( + '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'); @@ -5013,48 +5029,51 @@ describe('Parse.Query testing', () => { equal(results[0].get('name'), group2.get('name')); }); - 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')( + '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); @@ -5114,37 +5133,40 @@ describe('Parse.Query testing', () => { .catch(() => done()); }); - 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')( + '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 }; diff --git a/spec/ParseRole.spec.js b/spec/ParseRole.spec.js index 31de5b661e..34251b4b1f 100644 --- a/spec/ParseRole.spec.js +++ b/spec/ParseRole.spec.js @@ -126,76 +126,79 @@ describe('Parse Role testing', () => { ); }); - 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')( + '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/ParseUser.spec.js b/spec/ParseUser.spec.js index e97db08a6c..dd67107546 100644 --- a/spec/ParseUser.spec.js +++ b/spec/ParseUser.spec.js @@ -2257,68 +2257,80 @@ describe('Parse.User testing', () => { }); describe('case insensitive signup not allowed', () => { - 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')( + '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('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('1650014e-5394-4809-9d94-dc554bf6d892')( + '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('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('123dd769-8de7-46e8-9f6e-9aa1b0c334a4')( + '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('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')( + '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('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')( + '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('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')( + '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 4ea6ed2002..37f0d29062 100644 --- a/spec/PasswordPolicy.spec.js +++ b/spec/PasswordPolicy.spec.js @@ -3,65 +3,68 @@ const request = require('../lib/request'); describe('Password Policy: ', () => { - 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')( + '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('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')( + '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('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('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')( + '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('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('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')( + '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')( + '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')( + '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 c1c44af23d..3d01532c5c 100644 --- a/spec/PointerPermissions.spec.js +++ b/spec/PointerPermissions.spec.js @@ -226,59 +226,62 @@ describe('Pointer Permissions', () => { }); }); - 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')( + '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); @@ -1070,7 +1073,7 @@ describe('Pointer Permissions', () => { } }); - it('should work with write', async done => { + it_id('1bbb9ed6-5558-4ce5-a238-b1a2015d273f')('should work with write', async done => { const config = Config.get(Parse.applicationId); const user = new Parse.User(); const user2 = new Parse.User(); @@ -1203,50 +1206,53 @@ describe('Pointer Permissions', () => { done(); }); - 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')( + '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); @@ -2517,18 +2523,21 @@ describe('Pointer Permissions', () => { done(); }); - it('should fail for user not listed', async done => { - await updateCLP({ - get: { - pointerFields: ['moderators'], - }, - }); + it_id('9ba681d5-59f5-4996-b36d-6647d23e6a44')( + '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 +2694,37 @@ describe('Pointer Permissions', () => { done(); }); - it('should be allowed (multiple users in array)', async done => { - await updateCLP({ - update: { - pointerFields: ['moderators'], - }, - }); + it_id('2b19234a-a471-48b4-bd1a-27bd286d066f')( + '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('should fail for user not listed', async done => { - await updateCLP({ - update: { - pointerFields: ['moderators'], - }, - }); + it_id('bcdb158d-c0b6-45e3-84ab-a3636f7cb470')( + '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 +2779,21 @@ describe('Pointer Permissions', () => { done(); }); - it('should fail for user not listed', async done => { - await updateCLP({ - delete: { - pointerFields: ['owners'], - }, - }); + it_id('70aa3853-6e26-4c38-a927-2ddb24ced7d4')( + '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 +2892,25 @@ describe('Pointer Permissions', () => { done(); }); - 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')( + '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 +2967,50 @@ describe('Pointer Permissions', () => { await initialize(); }); - 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')( + '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('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')( + '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 572fc9bcf9..d5b11a37ae 100644 --- a/spec/PushController.spec.js +++ b/spec/PushController.spec.js @@ -168,7 +168,7 @@ describe('PushController', () => { done(); }); - it('properly increment badges', async () => { + it_id('01e3e1b8-fad2-4249-b664-5a3efaab8cb1')('properly increment badges', async () => { const pushAdapter = { send: function (body, installations) { const badge = body.data.badge; @@ -233,69 +233,72 @@ describe('PushController', () => { } }); - 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')( + '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('properly set badges to 1', async () => { + it_id('758dd579-aa91-4010-9033-8d48d3463644')('properly set badges to 1', async () => { const pushAdapter = { send: function (body, installations) { const badge = body.data.badge; @@ -350,63 +353,66 @@ describe('PushController', () => { } }); - 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')( + '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('properly creates _PushStatus', async () => { + it_id('667f31c0-b458-4f61-ab57-668c04e3cc0b')('properly creates _PushStatus', async () => { const pushStatusAfterSave = { handler: function () {}, }; @@ -521,51 +527,54 @@ describe('PushController', () => { expect(succeedCount).toBe(1); }); - 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')( + '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('should support full RESTQuery for increment', async () => { - const payload = { - data: { - alert: 'Hello World!', - badge: 'Increment', - }, - }; + it_id('53551fc3-b975-4774-92e6-7e5f3c05e105')( + '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 = { @@ -1030,7 +1042,7 @@ describe('PushController', () => { // No installation is in es so only 1 call for fr, and another for default }); - it('should update audiences', async () => { + it_id('ef2e5569-50c3-40c2-ab49-175cdbd5f024')('should update audiences', async () => { const pushAdapter = { send: function (body, installations) { return successfulTransmissions(body, installations); diff --git a/spec/PushWorker.spec.js b/spec/PushWorker.spec.js index 63e0945110..644a5f7c82 100644 --- a/spec/PushWorker.spec.js +++ b/spec/PushWorker.spec.js @@ -271,7 +271,7 @@ describe('PushWorker', () => { toAwait.then(done).catch(done); }); - it('tracks push status per UTC offsets', done => { + it_id('764d28ab-241b-4b96-8ce9-e03541850e3f')('tracks push status per UTC offsets', done => { const config = Config.get('test'); const handler = pushStatusHandler(config); const spy = spyOn(rest, 'update').and.callThrough(); diff --git a/spec/RegexVulnerabilities.spec.js b/spec/RegexVulnerabilities.spec.js index 5d3bdf254d..4e9d77b19f 100644 --- a/spec/RegexVulnerabilities.spec.js +++ b/spec/RegexVulnerabilities.spec.js @@ -97,27 +97,30 @@ describe('Regex Vulnerabilities', function () { expect(this.user.get('emailVerified')).toEqual(false); }); - it('should work with plain token', async function () { - expect(this.user.get('emailVerified')).toEqual(false); - const current = await request({ - method: 'GET', - url: `http://localhost:8378/1/classes/_User/${this.user.id}`, - json: true, - headers: { - 'X-Parse-Application-Id': 'test', - 'X-Parse-Rest-API-Key': 'test', - 'X-Parse-Maintenance-Key': 'test2', - 'Content-Type': 'application/json', - }, - }).then(res => res.data); - // It should work - await request({ - url: `${serverURL}/apps/test/verify_email?username=someemail@somedomain.com&token=${current._email_verify_token}`, - method: 'GET', - }); - await this.user.fetch({ useMasterKey: true }); - expect(this.user.get('emailVerified')).toEqual(true); - }); + it_id('92bbb86d-bcda-49fa-8d79-aa0501078044')( + 'should work with plain token', + async function () { + expect(this.user.get('emailVerified')).toEqual(false); + const current = await request({ + method: 'GET', + url: `http://localhost:8378/1/classes/_User/${this.user.id}`, + json: true, + headers: { + 'X-Parse-Application-Id': 'test', + 'X-Parse-Rest-API-Key': 'test', + 'X-Parse-Maintenance-Key': 'test2', + 'Content-Type': 'application/json', + }, + }).then(res => res.data); + // It should work + await request({ + url: `${serverURL}/apps/test/verify_email?username=someemail@somedomain.com&token=${current._email_verify_token}`, + method: 'GET', + }); + await this.user.fetch({ useMasterKey: true }); + expect(this.user.get('emailVerified')).toEqual(true); + } + ); }); describe('on password reset', function () { diff --git a/spec/RestQuery.spec.js b/spec/RestQuery.spec.js index 023d3b4790..3826d88f55 100644 --- a/spec/RestQuery.spec.js +++ b/spec/RestQuery.spec.js @@ -392,7 +392,7 @@ describe('RestQuery.each', () => { beforeEach(() => { config = Config.get('test'); }); - it('should run each', async () => { + it_id('3416c90b-ee2e-4bb5-9231-46cd181cd0a2')('should run each', async () => { const objects = []; while (objects.length != 10) { objects.push(new Parse.Object('Object', { value: objects.length })); @@ -419,7 +419,7 @@ describe('RestQuery.each', () => { expect(results.length).toBe(7); }); - it('should work with query on relations', async () => { + it_id('0fe22501-4b18-461e-b87d-82ceac4a496e')('should work with query on relations', async () => { const objectA = new Parse.Object('Letter', { value: 'A' }); const objectB = new Parse.Object('Letter', { value: 'B' }); diff --git a/spec/SchemaPerformance.spec.js b/spec/SchemaPerformance.spec.js index 17238b0ed6..41210e8085 100644 --- a/spec/SchemaPerformance.spec.js +++ b/spec/SchemaPerformance.spec.js @@ -205,7 +205,7 @@ describe('Schema Performance', function () { expect(getAllSpy.calls.count()).toBe(2); }); - it('does reload with schemaCacheTtl', async () => { + it_id('9dd70965-b683-4cb8-b43a-44c1f4def9f4')('does reload with schemaCacheTtl', async () => { const databaseURI = process.env.PARSE_SERVER_TEST_DB === 'postgres' ? process.env.PARSE_SERVER_TEST_DATABASE_URI @@ -241,7 +241,7 @@ describe('Schema Performance', function () { expect(spy.reloadCalls).toBe(1); }); - it('cannot set invalid databaseOptions', async () => { + it_id('b0ae21f2-c947-48ed-a0db-e8900d45a4c8')('cannot set invalid databaseOptions', async () => { const expectError = async (key, value, expected) => expectAsync( reconfigureServer({ databaseAdapter: undefined, databaseOptions: { [key]: value } }) diff --git a/spec/Uniqueness.spec.js b/spec/Uniqueness.spec.js index 72ba5b9872..e0eb9c0864 100644 --- a/spec/Uniqueness.spec.js +++ b/spec/Uniqueness.spec.js @@ -68,25 +68,28 @@ describe('Uniqueness', function () { }); }); - 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')( + '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 03c979abe6..56a26aa575 100644 --- a/spec/UserController.spec.js +++ b/spec/UserController.spec.js @@ -5,7 +5,7 @@ const Auth = require('../lib/Auth'); describe('UserController', () => { describe('sendVerificationEmail', () => { describe('parseFrameURL not provided', () => { - it('uses publicServerURL', async () => { + it_id('61338330-eca7-4c33-8816-7ff05966f43b')('uses publicServerURL', async () => { await reconfigureServer({ publicServerURL: 'http://www.example.com', customPages: { @@ -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('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')( + '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/ValidationAndPasswordsReset.spec.js b/spec/ValidationAndPasswordsReset.spec.js index fb163ad567..3042ebdb83 100644 --- a/spec/ValidationAndPasswordsReset.spec.js +++ b/spec/ValidationAndPasswordsReset.spec.js @@ -33,32 +33,35 @@ describe('Custom Pages, Email Verification, Password Reset', () => { }); }); - 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')( + '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 = { @@ -158,7 +161,7 @@ describe('Custom Pages, Email Verification, Password Reset', () => { }, 200); }); - it('does send with a simple adapter', done => { + it_id('33d31119-c724-4f5d-83ec-f56815d23df3')('does send with a simple adapter', done => { let calls = 0; const emailAdapter = { sendMail: function (options) { @@ -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('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')( + '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('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')( + '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('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('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')( + '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')( + '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 4ceff47d5f..a7ed352f4d 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('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')( + '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/rest.spec.js b/spec/rest.spec.js index dc4e78ad0a..922f9c3c10 100644 --- a/spec/rest.spec.js +++ b/spec/rest.spec.js @@ -247,7 +247,7 @@ describe('rest create', () => { }); }); - it('handles array, object, date', done => { + it_id('6c30306f-328c-47f2-88a7-2deffaee997f')('handles array, object, date', done => { const now = new Date(); const obj = { array: [1, 2, 3], diff --git a/spec/schemas.spec.js b/spec/schemas.spec.js index 9557dd7924..5c4d9eb211 100644 --- a/spec/schemas.spec.js +++ b/spec/schemas.spec.js @@ -3650,7 +3650,7 @@ describe('schemas', () => { }); }); - it_exclude_dbs(['postgres'])('get indexes on startup', done => { + it_id('5d0926b2-2d31-459d-a2b1-23ecc32e72a3')('get indexes on startup', done => { const obj = new Parse.Object('TestObject'); obj .save() @@ -3673,7 +3673,7 @@ describe('schemas', () => { }); }); - it_exclude_dbs(['postgres'])('get compound indexes on startup', done => { + it_id('9f2ba51a-6a9c-4b25-9da0-51c82ac65f90')('get compound indexes on startup', done => { const obj = new Parse.Object('TestObject'); obj.set('subject', 'subject'); obj.set('comment', 'comment'); @@ -3708,35 +3708,38 @@ describe('schemas', () => { }); }); - 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')( + '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/vulnerabilities.spec.js b/spec/vulnerabilities.spec.js index c0f519e78d..bbc2240716 100644 --- a/spec/vulnerabilities.spec.js +++ b/spec/vulnerabilities.spec.js @@ -203,37 +203,40 @@ describe('Vulnerabilities', () => { ); }); - 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')( + '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({