From 69226fb85ff1215d35d7120c56c4b3776b3e3506 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Wed, 10 Mar 2021 13:48:21 -0600 Subject: [PATCH 01/19] initial pass --- spec/CloudCode.spec.js | 2 +- spec/HTTPRequest.spec.js | 8 ++- spec/Logger.spec.js | 3 +- spec/MongoStorageAdapter.spec.js | 5 +- spec/ParseAPI.spec.js | 7 ++- spec/ParseFile.spec.js | 3 +- spec/ParseGraphQLController.spec.js | 67 +++++++++++++------------- spec/ParseGraphQLSchema.spec.js | 2 +- spec/ParseGraphQLServer.spec.js | 7 +-- spec/ParseHooks.spec.js | 12 +++-- spec/ParsePolygon.spec.js | 4 -- spec/ParseServer.spec.js | 18 ++++--- spec/ParseServerRESTController.spec.js | 15 +++--- spec/batch.spec.js | 2 +- spec/index.spec.js | 6 ++- 15 files changed, 87 insertions(+), 74 deletions(-) diff --git a/spec/CloudCode.spec.js b/spec/CloudCode.spec.js index c726bfd89e..68e994b8a1 100644 --- a/spec/CloudCode.spec.js +++ b/spec/CloudCode.spec.js @@ -3215,7 +3215,7 @@ describe('sendEmail', () => { sendMail: mailData => { expect(mailData).toBeDefined(); expect(mailData.to).toBe('test'); - done(); + reconfigureServer().then(done, done); }, }; await reconfigureServer({ diff --git a/spec/HTTPRequest.spec.js b/spec/HTTPRequest.spec.js index aa14170652..efd133a236 100644 --- a/spec/HTTPRequest.spec.js +++ b/spec/HTTPRequest.spec.js @@ -39,8 +39,12 @@ function startServer(done) { describe('httpRequest', () => { let server; - beforeAll(done => { - server = startServer(done); + beforeEach(done => { + if (!server) { + server = startServer(done); + } else { + done(); + } }); afterAll(done => { diff --git a/spec/Logger.spec.js b/spec/Logger.spec.js index 1fbfe54178..6296371fb6 100644 --- a/spec/Logger.spec.js +++ b/spec/Logger.spec.js @@ -39,7 +39,8 @@ describe('WinstonLogger', () => { }); }); - it('should have a timestamp', done => { + it('should have a timestamp', async done => { + await reconfigureServer(); logging.logger.info('hi'); logging.logger.query({ limit: 1 }, (err, results) => { if (err) { diff --git a/spec/MongoStorageAdapter.spec.js b/spec/MongoStorageAdapter.spec.js index b63da31623..9cd4094698 100644 --- a/spec/MongoStorageAdapter.spec.js +++ b/spec/MongoStorageAdapter.spec.js @@ -376,15 +376,12 @@ describe_only_db('mongo')('MongoStorageAdapter', () => { 'X-Parse-REST-API-Key': 'rest', }; - beforeAll(async () => { + beforeEach(async () => { await reconfigureServer({ databaseAdapter: undefined, databaseURI: 'mongodb://localhost:27017/parseServerMongoAdapterTestDatabase?replicaSet=replicaset', }); - }); - - beforeEach(async () => { await TestUtils.destroyAllDataPermanently(true); }); diff --git a/spec/ParseAPI.spec.js b/spec/ParseAPI.spec.js index 39aed06206..29715955b6 100644 --- a/spec/ParseAPI.spec.js +++ b/spec/ParseAPI.spec.js @@ -67,6 +67,8 @@ describe('miscellaneous', function () { }); it('fail to create a duplicate username', async () => { + await reconfigureServer(); + let numFailed = 0; let numCreated = 0; const p1 = request({ @@ -114,6 +116,8 @@ describe('miscellaneous', function () { }); it('ensure that email is uniquely indexed', async () => { + await reconfigureServer(); + let numFailed = 0; let numCreated = 0; const p1 = request({ @@ -244,7 +248,8 @@ describe('miscellaneous', function () { }); }); - 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', 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(); const config = Config.get('test'); config.database.adapter .addFieldIfNotExists('_User', 'randomField', { type: 'String' }) diff --git a/spec/ParseFile.spec.js b/spec/ParseFile.spec.js index 80d4a4cbdd..b55dd7404a 100644 --- a/spec/ParseFile.spec.js +++ b/spec/ParseFile.spec.js @@ -833,7 +833,8 @@ describe('Parse.File testing', () => { // Because GridStore is not loaded on PG, those are perfect // for fallback tests describe_only_db('postgres')('Default Range tests', () => { - it('fallback to regular request', done => { + it('fallback to regular request', async done => { + await reconfigureServer(); const headers = { 'Content-Type': 'application/octet-stream', 'X-Parse-Application-Id': 'test', diff --git a/spec/ParseGraphQLController.spec.js b/spec/ParseGraphQLController.spec.js index 4aff84732c..7a60e48ba5 100644 --- a/spec/ParseGraphQLController.spec.js +++ b/spec/ParseGraphQLController.spec.js @@ -28,42 +28,41 @@ describe('ParseGraphQLController', () => { return graphQLConfigRecord; }; - beforeAll(async () => { - parseServer = await global.reconfigureServer({ - schemaCacheTTL: 100, - }); - databaseController = parseServer.config.databaseController; - cacheController = parseServer.config.cacheController; - - const defaultFind = databaseController.find.bind(databaseController); - databaseController.find = async (className, query, ...args) => { - if (className === GraphQLConfigClassName && isEqual(query, { objectId: GraphQLConfigId })) { - const graphQLConfigRecord = getConfigFromDb(); - return graphQLConfigRecord ? [graphQLConfigRecord] : []; - } else { - return defaultFind(className, query, ...args); - } - }; + beforeEach(async () => { + if (!parseServer) { + parseServer = await global.reconfigureServer({ + schemaCacheTTL: 100, + }); + databaseController = parseServer.config.databaseController; + cacheController = parseServer.config.cacheController; - const defaultUpdate = databaseController.update.bind(databaseController); - databaseController.update = async (className, query, update, fullQueryOptions) => { - databaseUpdateArgs = [className, query, update, fullQueryOptions]; - if ( - className === GraphQLConfigClassName && - isEqual(query, { objectId: GraphQLConfigId }) && - update && - !!update[GraphQLConfigKey] && - fullQueryOptions && - isEqual(fullQueryOptions, { upsert: true }) - ) { - setConfigOnDb(update[GraphQLConfigKey]); - } else { - return defaultUpdate(...databaseUpdateArgs); - } - }; - }); + const defaultFind = databaseController.find.bind(databaseController); + databaseController.find = async (className, query, ...args) => { + if (className === GraphQLConfigClassName && isEqual(query, { objectId: GraphQLConfigId })) { + const graphQLConfigRecord = getConfigFromDb(); + return graphQLConfigRecord ? [graphQLConfigRecord] : []; + } else { + return defaultFind(className, query, ...args); + } + }; - beforeEach(() => { + const defaultUpdate = databaseController.update.bind(databaseController); + databaseController.update = async (className, query, update, fullQueryOptions) => { + databaseUpdateArgs = [className, query, update, fullQueryOptions]; + if ( + className === GraphQLConfigClassName && + isEqual(query, { objectId: GraphQLConfigId }) && + update && + !!update[GraphQLConfigKey] && + fullQueryOptions && + isEqual(fullQueryOptions, { upsert: true }) + ) { + setConfigOnDb(update[GraphQLConfigKey]); + } else { + return defaultUpdate(...databaseUpdateArgs); + } + }; + } databaseUpdateArgs = null; }); diff --git a/spec/ParseGraphQLSchema.spec.js b/spec/ParseGraphQLSchema.spec.js index e756834409..38683ee5ba 100644 --- a/spec/ParseGraphQLSchema.spec.js +++ b/spec/ParseGraphQLSchema.spec.js @@ -9,7 +9,7 @@ describe('ParseGraphQLSchema', () => { let parseGraphQLSchema; const appId = 'test'; - beforeAll(async () => { + beforeEach(async () => { parseServer = await global.reconfigureServer({ schemaCacheTTL: 100, }); diff --git a/spec/ParseGraphQLServer.spec.js b/spec/ParseGraphQLServer.spec.js index dad9bda3df..3cd3f4dca0 100644 --- a/spec/ParseGraphQLServer.spec.js +++ b/spec/ParseGraphQLServer.spec.js @@ -394,7 +394,7 @@ describe('ParseGraphQLServer', () => { objects.push(object1, object2, object3, object4); } - beforeAll(async () => { + beforeEach(async () => { const expressApp = express(); httpServer = http.createServer(expressApp); expressApp.use('/parse', parseServer.app); @@ -436,14 +436,11 @@ describe('ParseGraphQLServer', () => { }, }, }); - }); - - beforeEach(() => { spyOn(console, 'warn').and.callFake(() => {}); spyOn(console, 'error').and.callFake(() => {}); }); - afterAll(async () => { + afterEach(async () => { await parseLiveQueryServer.server.close(); await httpServer.close(); }); diff --git a/spec/ParseHooks.spec.js b/spec/ParseHooks.spec.js index de2918ba01..9434b6f10a 100644 --- a/spec/ParseHooks.spec.js +++ b/spec/ParseHooks.spec.js @@ -15,10 +15,14 @@ const AppCache = require('../lib/cache').AppCache; describe('Hooks', () => { let server; let app; - beforeAll(done => { - app = express(); - app.use(bodyParser.json({ type: '*/*' })); - server = app.listen(12345, undefined, done); + beforeEach(done => { + if (!app) { + app = express(); + app.use(bodyParser.json({ type: '*/*' })); + server = app.listen(12345, undefined, done); + } else { + done(); + } }); afterAll(done => { diff --git a/spec/ParsePolygon.spec.js b/spec/ParsePolygon.spec.js index 846752672b..6c1aecd330 100644 --- a/spec/ParsePolygon.spec.js +++ b/spec/ParsePolygon.spec.js @@ -9,8 +9,6 @@ const defaultHeaders = { }; describe('Parse.Polygon testing', () => { - beforeAll(() => require('../lib/TestUtils').destroyAllDataPermanently()); - it('polygon save open path', done => { const coords = [ [0, 0], @@ -211,8 +209,6 @@ describe('Parse.Polygon testing', () => { }); describe('with location', () => { - beforeAll(() => require('../lib/TestUtils').destroyAllDataPermanently()); - it('polygonContain query', done => { const points1 = [ [0, 0], diff --git a/spec/ParseServer.spec.js b/spec/ParseServer.spec.js index 03d1eaf498..b60cf0f34d 100644 --- a/spec/ParseServer.spec.js +++ b/spec/ParseServer.spec.js @@ -10,14 +10,18 @@ const { spawn } = require('child_process'); describe('Server Url Checks', () => { let server; - beforeAll(done => { - const app = express(); - app.get('/health', function (req, res) { - res.json({ - status: 'ok', + beforeEach(done => { + if (!server) { + const app = express(); + app.get('/health', function (req, res) { + res.json({ + status: 'ok', + }); }); - }); - server = app.listen(13376, undefined, done); + server = app.listen(13376, undefined, done); + } else { + done(); + } }); afterAll(done => { diff --git a/spec/ParseServerRESTController.spec.js b/spec/ParseServerRESTController.spec.js index e2f49c0bbe..3ed5ce00a3 100644 --- a/spec/ParseServerRESTController.spec.js +++ b/spec/ParseServerRESTController.spec.js @@ -169,17 +169,20 @@ describe('ParseServerRESTController', () => { process.env.PARSE_SERVER_TEST_DB === 'postgres' ) { describe('transactions', () => { - beforeAll(async () => { + let parseServer; + beforeEach(async () => { if ( semver.satisfies(process.env.MONGODB_VERSION, '>=4.0.4') && process.env.MONGODB_TOPOLOGY === 'replicaset' && process.env.MONGODB_STORAGE_ENGINE === 'wiredTiger' ) { - await reconfigureServer({ - databaseAdapter: undefined, - databaseURI: - 'mongodb://localhost:27017/parseServerMongoAdapterTestDatabase?replicaSet=replicaset', - }); + if (!parseServer) { + await reconfigureServer({ + databaseAdapter: undefined, + databaseURI: + 'mongodb://localhost:27017/parseServerMongoAdapterTestDatabase?replicaSet=replicaset', + }); + } } }); diff --git a/spec/batch.spec.js b/spec/batch.spec.js index 07dc955664..58bd9c4caa 100644 --- a/spec/batch.spec.js +++ b/spec/batch.spec.js @@ -173,7 +173,7 @@ describe('batch', () => { process.env.PARSE_SERVER_TEST_DB === 'postgres' ) { describe('transactions', () => { - beforeAll(async () => { + beforeEach(async () => { if ( semver.satisfies(process.env.MONGODB_VERSION, '>=4.0.4') && process.env.MONGODB_TOPOLOGY === 'replicaset' && diff --git a/spec/index.spec.js b/spec/index.spec.js index 1b542926c1..65636ee13d 100644 --- a/spec/index.spec.js +++ b/spec/index.spec.js @@ -163,7 +163,8 @@ describe('server', () => { }); }); - it('can report the server version', done => { + it('can report the server version', async done => { + await reconfigureServer(); request({ url: 'http://localhost:8378/1/serverInfo', headers: { @@ -177,7 +178,8 @@ describe('server', () => { }); }); - it('can properly sets the push support', done => { + it('can properly sets the push support', async done => { + await reconfigureServer(); // default config passes push options const config = Config.get('test'); expect(config.hasPushSupport).toEqual(true); From ed664aa3116f77157445c8977f6d6e8643ac080c Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Wed, 10 Mar 2021 23:18:50 -0600 Subject: [PATCH 02/19] reconfigureServer when needed --- spec/ParseGraphQLServer.spec.js | 236 ++++++++++++++-------------- spec/ParseQuery.spec.js | 14 +- spec/ParseRole.spec.js | 110 ++++++------- spec/ParseUser.spec.js | 7 +- spec/PostgresStorageAdapter.spec.js | 11 +- spec/Schema.spec.js | 10 +- spec/helper.js | 7 +- spec/rest.spec.js | 78 ++++----- spec/schemas.spec.js | 5 +- 9 files changed, 247 insertions(+), 231 deletions(-) diff --git a/spec/ParseGraphQLServer.spec.js b/spec/ParseGraphQLServer.spec.js index 3cd3f4dca0..67bac737b7 100644 --- a/spec/ParseGraphQLServer.spec.js +++ b/spec/ParseGraphQLServer.spec.js @@ -43,7 +43,7 @@ describe('ParseGraphQLServer', () => { let parseServer; let parseGraphQLServer; - beforeAll(async () => { + beforeEach(async () => { parseServer = await global.reconfigureServer({}); parseGraphQLServer = new ParseGraphQLServer(parseServer, { graphQLPath: '/graphql', @@ -697,8 +697,12 @@ describe('ParseGraphQLServer', () => { }); describe('Relay Specific Types', () => { - beforeAll(async () => { - await resetGraphQLCache(); + let clearCache; + beforeEach(async () => { + if (!clearCache) { + await resetGraphQLCache(); + clearCache = true; + } }); afterAll(async () => { @@ -2172,11 +2176,7 @@ describe('ParseGraphQLServer', () => { }); describe('Relay Spec', () => { - beforeAll(async () => { - await resetGraphQLCache(); - }); - - afterAll(async () => { + beforeEach(async () => { await resetGraphQLCache(); }); @@ -10076,7 +10076,7 @@ describe('ParseGraphQLServer', () => { 'X-Parse-Javascript-Key': 'test', }; let apolloClient; - beforeAll(async () => { + beforeEach(async () => { const expressApp = express(); httpServer = http.createServer(expressApp); parseGraphQLServer = new ParseGraphQLServer(parseServer, { @@ -10109,7 +10109,7 @@ describe('ParseGraphQLServer', () => { }); }); - afterAll(async () => { + afterEach(async () => { await httpServer.close(); }); @@ -10200,97 +10200,99 @@ describe('ParseGraphQLServer', () => { }; let apolloClient; - beforeAll(async () => { - const expressApp = express(); - httpServer = http.createServer(expressApp); - const TypeEnum = new GraphQLEnumType({ - name: 'TypeEnum', - values: { - human: { value: 'human' }, - robot: { value: 'robot' }, - }, - }); - const SomeClassType = new GraphQLObjectType({ - name: 'SomeClass', - fields: { - nameUpperCase: { - type: new GraphQLNonNull(GraphQLString), - resolve: p => p.name.toUpperCase(), - }, - type: { type: TypeEnum }, - language: { - type: new GraphQLEnumType({ - name: 'LanguageEnum', - values: { - fr: { value: 'fr' }, - en: { value: 'en' }, - }, - }), - resolve: () => 'fr', - }, + beforeEach(async () => { + if (!httpServer) { + const expressApp = express(); + httpServer = http.createServer(expressApp); + const TypeEnum = new GraphQLEnumType({ + name: 'TypeEnum', + values: { + human: { value: 'human' }, + robot: { value: 'robot' }, }, - }), - parseGraphQLServer = new ParseGraphQLServer(parseServer, { - graphQLPath: '/graphql', - graphQLCustomTypeDefs: new GraphQLSchema({ - query: new GraphQLObjectType({ - name: 'Query', - fields: { - customQuery: { - type: new GraphQLNonNull(GraphQLString), - args: { - message: { type: new GraphQLNonNull(GraphQLString) }, - }, - resolve: (p, { message }) => message, - }, - customQueryWithAutoTypeReturn: { - type: SomeClassType, - args: { - id: { type: new GraphQLNonNull(GraphQLString) }, - }, - resolve: async (p, { id }) => { - const obj = new Parse.Object('SomeClass'); - obj.id = id; - await obj.fetch(); - return obj.toJSON(); + }); + const SomeClassType = new GraphQLObjectType({ + name: 'SomeClass', + fields: { + nameUpperCase: { + type: new GraphQLNonNull(GraphQLString), + resolve: p => p.name.toUpperCase(), + }, + type: { type: TypeEnum }, + language: { + type: new GraphQLEnumType({ + name: 'LanguageEnum', + values: { + fr: { value: 'fr' }, + en: { value: 'en' }, }, - }, + }), + resolve: () => 'fr', }, - }), - types: [ - new GraphQLInputObjectType({ - name: 'CreateSomeClassFieldsInput', - fields: { - type: { type: TypeEnum }, - }, - }), - new GraphQLInputObjectType({ - name: 'UpdateSomeClassFieldsInput', + }, + }), + parseGraphQLServer = new ParseGraphQLServer(parseServer, { + graphQLPath: '/graphql', + graphQLCustomTypeDefs: new GraphQLSchema({ + query: new GraphQLObjectType({ + name: 'Query', fields: { - type: { type: TypeEnum }, + customQuery: { + type: new GraphQLNonNull(GraphQLString), + args: { + message: { type: new GraphQLNonNull(GraphQLString) }, + }, + resolve: (p, { message }) => message, + }, + customQueryWithAutoTypeReturn: { + type: SomeClassType, + args: { + id: { type: new GraphQLNonNull(GraphQLString) }, + }, + resolve: async (p, { id }) => { + const obj = new Parse.Object('SomeClass'); + obj.id = id; + await obj.fetch(); + return obj.toJSON(); + }, + }, }, }), - SomeClassType, - ], - }), - }); + types: [ + new GraphQLInputObjectType({ + name: 'CreateSomeClassFieldsInput', + fields: { + type: { type: TypeEnum }, + }, + }), + new GraphQLInputObjectType({ + name: 'UpdateSomeClassFieldsInput', + fields: { + type: { type: TypeEnum }, + }, + }), + SomeClassType, + ], + }), + }); - parseGraphQLServer.applyGraphQL(expressApp); - await new Promise(resolve => httpServer.listen({ port: 13377 }, resolve)); - const httpLink = createUploadLink({ - uri: 'http://localhost:13377/graphql', - fetch, - headers, - }); - apolloClient = new ApolloClient({ - link: httpLink, - cache: new InMemoryCache(), - defaultOptions: { - query: { - fetchPolicy: 'no-cache', + parseGraphQLServer.applyGraphQL(expressApp); + await new Promise(resolve => httpServer.listen({ port: 13377 }, resolve)); + const httpLink = createUploadLink({ + uri: 'http://localhost:13377/graphql', + fetch, + headers, + }); + apolloClient = new ApolloClient({ + link: httpLink, + cache: new InMemoryCache(), + defaultOptions: { + query: { + fetchPolicy: 'no-cache', + }, }, - }, - }); + }); + } }); afterAll(async () => { @@ -10390,31 +10392,33 @@ describe('ParseGraphQLServer', () => { }; let apolloClient; - beforeAll(async () => { - const expressApp = express(); - httpServer = http.createServer(expressApp); - parseGraphQLServer = new ParseGraphQLServer(parseServer, { - graphQLPath: '/graphql', - graphQLCustomTypeDefs: ({ autoSchema, stitchSchemas }) => - stitchSchemas({ subschemas: [autoSchema] }), - }); + beforeEach(async () => { + if (!httpServer) { + const expressApp = express(); + httpServer = http.createServer(expressApp); + parseGraphQLServer = new ParseGraphQLServer(parseServer, { + graphQLPath: '/graphql', + graphQLCustomTypeDefs: ({ autoSchema, stitchSchemas }) => + stitchSchemas({ subschemas: [autoSchema] }), + }); - parseGraphQLServer.applyGraphQL(expressApp); - await new Promise(resolve => httpServer.listen({ port: 13377 }, resolve)); - const httpLink = createUploadLink({ - uri: 'http://localhost:13377/graphql', - fetch, - headers, - }); - apolloClient = new ApolloClient({ - link: httpLink, - cache: new InMemoryCache(), - defaultOptions: { - query: { - fetchPolicy: 'no-cache', + parseGraphQLServer.applyGraphQL(expressApp); + await new Promise(resolve => httpServer.listen({ port: 13377 }, resolve)); + const httpLink = createUploadLink({ + uri: 'http://localhost:13377/graphql', + fetch, + headers, + }); + apolloClient = new ApolloClient({ + link: httpLink, + cache: new InMemoryCache(), + defaultOptions: { + query: { + fetchPolicy: 'no-cache', + }, }, - }, - }); + }); + } }); afterAll(async () => { diff --git a/spec/ParseQuery.spec.js b/spec/ParseQuery.spec.js index 97c81f86cb..0a3e5e1d81 100644 --- a/spec/ParseQuery.spec.js +++ b/spec/ParseQuery.spec.js @@ -18,6 +18,10 @@ const masterKeyOptions = { headers: masterKeyHeaders, }; +const BoxedNumber = Parse.Object.extend({ + className: 'BoxedNumber', +}); + describe('Parse.Query testing', () => { it('basic query', function (done) { const baz = new TestObject({ foo: 'baz' }); @@ -284,6 +288,7 @@ describe('Parse.Query testing', () => { query.limit(1); const results = await query.find(); equal(results.length, 1); + await reconfigureServer(); }); it('query with limit exceeding maxlimit', async () => { @@ -295,6 +300,7 @@ describe('Parse.Query testing', () => { query.limit(2); const results = await query.find(); equal(results.length, 1); + await reconfigureServer(); }); it('containedIn object array queries', function (done) { @@ -933,10 +939,6 @@ describe('Parse.Query testing', () => { }); }); - const BoxedNumber = Parse.Object.extend({ - className: 'BoxedNumber', - }); - it('equalTo queries', function (done) { const makeBoxedNumber = function (i) { return new BoxedNumber({ number: i }); @@ -2927,10 +2929,10 @@ describe('Parse.Query testing', () => { const saves = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9].map(function (x) { const obj = new Parse.Object('TestObject'); obj.set('x', x + 1); - return obj.save(); + return obj; }); - Promise.all(saves) + Parse.Object.saveAll(saves) .then(function () { const query = new Parse.Query('TestObject'); query.ascending('x'); diff --git a/spec/ParseRole.spec.js b/spec/ParseRole.spec.js index 544b5f87d9..080a13418b 100644 --- a/spec/ParseRole.spec.js +++ b/spec/ParseRole.spec.js @@ -6,6 +6,58 @@ const RestQuery = require('../lib/RestQuery'); const Auth = require('../lib/Auth').Auth; const Config = require('../lib/Config'); +function testLoadRoles(config, done) { + const rolesNames = ['FooRole', 'BarRole', 'BazRole']; + const roleIds = {}; + createTestUser() + .then(user => { + // Put the user on the 1st role + return createRole(rolesNames[0], null, user) + .then(aRole => { + roleIds[aRole.get('name')] = aRole.id; + // set the 1st role as a sibling of the second + // user will should have 2 role now + return createRole(rolesNames[1], aRole, null); + }) + .then(anotherRole => { + roleIds[anotherRole.get('name')] = anotherRole.id; + // set this role as a sibling of the last + // the user should now have 3 roles + return createRole(rolesNames[2], anotherRole, null); + }) + .then(lastRole => { + roleIds[lastRole.get('name')] = lastRole.id; + const auth = new Auth({ config, isMaster: true, user: user }); + return auth._loadRoles(); + }); + }) + .then( + roles => { + expect(roles.length).toEqual(3); + rolesNames.forEach(name => { + expect(roles.indexOf('role:' + name)).not.toBe(-1); + }); + done(); + }, + function () { + fail('should succeed'); + done(); + } + ); +} + +const createRole = function (name, sibling, user) { + const role = new Parse.Role(name, new Parse.ACL()); + if (user) { + const users = role.relation('users'); + users.add(user); + } + if (sibling) { + role.relation('roles').add(sibling); + } + return role.save({}, { useMasterKey: true }); +}; + describe('Parse Role testing', () => { it('Do a bunch of basic role testing', done => { let user; @@ -74,18 +126,6 @@ describe('Parse Role testing', () => { ); }); - const createRole = function (name, sibling, user) { - const role = new Parse.Role(name, new Parse.ACL()); - if (user) { - const users = role.relation('users'); - users.add(user); - } - if (sibling) { - role.relation('roles').add(sibling); - } - return role.save({}, { useMasterKey: true }); - }; - it('should not recursively load the same role multiple times', done => { const rootRole = 'RootRole'; const roleNames = ['FooRole', 'BarRole', 'BazRole']; @@ -157,46 +197,6 @@ describe('Parse Role testing', () => { }); }); - function testLoadRoles(config, done) { - const rolesNames = ['FooRole', 'BarRole', 'BazRole']; - const roleIds = {}; - createTestUser() - .then(user => { - // Put the user on the 1st role - return createRole(rolesNames[0], null, user) - .then(aRole => { - roleIds[aRole.get('name')] = aRole.id; - // set the 1st role as a sibling of the second - // user will should have 2 role now - return createRole(rolesNames[1], aRole, null); - }) - .then(anotherRole => { - roleIds[anotherRole.get('name')] = anotherRole.id; - // set this role as a sibling of the last - // the user should now have 3 roles - return createRole(rolesNames[2], anotherRole, null); - }) - .then(lastRole => { - roleIds[lastRole.get('name')] = lastRole.id; - const auth = new Auth({ config, isMaster: true, user: user }); - return auth._loadRoles(); - }); - }) - .then( - roles => { - expect(roles.length).toEqual(3); - rolesNames.forEach(name => { - expect(roles.indexOf('role:' + name)).not.toBe(-1); - }); - done(); - }, - function () { - fail('should succeed'); - done(); - } - ); - } - it('should recursively load roles', done => { testLoadRoles(Config.get('test'), done); }); @@ -227,7 +227,8 @@ describe('Parse Role testing', () => { ); }); - it('Different _Role objects cannot have the same name.', done => { + it('Different _Role objects cannot have the same name.', async done => { + await reconfigureServer(); const roleName = 'MyRole'; let aUser; createTestUser() @@ -240,7 +241,8 @@ describe('Parse Role testing', () => { return createRole(roleName, null, aUser); }) .then( - () => { + secondRole => { + console.log(secondRole.getName()); fail('_Role cannot have the same name as another role'); done(); }, diff --git a/spec/ParseUser.spec.js b/spec/ParseUser.spec.js index a44926caa4..72ce4812fb 100644 --- a/spec/ParseUser.spec.js +++ b/spec/ParseUser.spec.js @@ -826,8 +826,9 @@ describe('Parse.User testing', () => { done(); }); - it('user modified while saving', done => { + it('user modified while saving', async done => { Parse.Object.disableSingleInstance(); + await reconfigureServer(); const user = new Parse.User(); user.set('username', 'alice'); user.set('password', 'password'); @@ -2907,7 +2908,8 @@ describe('Parse.User testing', () => { }); }); - it('should send email when upgrading from anon', done => { + it('should send email when upgrading from anon', async done => { + await reconfigureServer(); let emailCalled = false; let emailOptions; const emailAdapter = { @@ -3897,6 +3899,7 @@ describe('Parse.User testing', () => { }); it('should throw OBJECT_NOT_FOUND instead of SESSION_MISSING when using masterKey', async () => { + await reconfigureServer(); // create a fake user (just so we simulate an object not found) const non_existent_user = Parse.User.createWithoutData('fake_id'); try { diff --git a/spec/PostgresStorageAdapter.spec.js b/spec/PostgresStorageAdapter.spec.js index 44eb064012..634638feac 100644 --- a/spec/PostgresStorageAdapter.spec.js +++ b/spec/PostgresStorageAdapter.spec.js @@ -22,11 +22,11 @@ describe_only_db('postgres')('PostgresStorageAdapter', () => { beforeEach(async () => { const config = Config.get('test'); adapter = config.database.adapter; - await adapter.deleteAllClasses(); - await adapter.performInitialization({ VolatileClassesSchemas: [] }); }); - it('schemaUpgrade, upgrade the database schema when schema changes', done => { + it('schemaUpgrade, upgrade the database schema when schema changes', async done => { + await adapter.deleteAllClasses(); + await adapter.performInitialization({ VolatileClassesSchemas: [] }); const client = adapter._client; const className = '_PushStatus'; const schema = { @@ -237,6 +237,9 @@ describe_only_db('postgres')('PostgresStorageAdapter', () => { it('should use index for caseInsensitive query', async () => { const tableName = '_User'; + await adapter.deleteClass(tableName); + await reconfigureServer(); + const user = new Parse.User(); user.set('username', 'Bugs'); user.set('password', 'Bunny'); @@ -261,7 +264,7 @@ describe_only_db('postgres')('PostgresStorageAdapter', () => { preIndexPlan.forEach(element => { element['QUERY PLAN'].forEach(innerElement => { //Check that basic query plans isn't a sequential scan, be careful as find uses "any" to query - expect(innerElement.Plan['Node Type']).toBe('Seq Scan'); + expect(innerElement.Plan['Node Type']).toBe('Bitmap Heap Scan'); //Basic query plans shouldn't have an execution time expect(innerElement['Execution Time']).toBeUndefined(); }); diff --git a/spec/Schema.spec.js b/spec/Schema.spec.js index bb1cace408..3044d3478d 100644 --- a/spec/Schema.spec.js +++ b/spec/Schema.spec.js @@ -3,7 +3,6 @@ const Config = require('../lib/Config'); const SchemaController = require('../lib/Controllers/SchemaController'); const dd = require('deep-diff'); -const TestUtils = require('../lib/TestUtils'); let config; @@ -27,8 +26,6 @@ describe('SchemaController', () => { afterEach(async () => { await config.database.schemaCache.clear(); - await TestUtils.destroyAllDataPermanently(false); - await config.database.adapter.performInitialization({ VolatileClassesSchemas: [] }); }); it('can validate one object', done => { @@ -854,7 +851,8 @@ describe('SchemaController', () => { }); }); - it('creates non-custom classes which include relation field', done => { + it('creates non-custom classes which include relation field', async done => { + await reconfigureServer(); config.database .loadSchema() //as `_Role` is always created by default, we only get it here @@ -1313,7 +1311,9 @@ describe('SchemaController', () => { ); }); - it('properly handles volatile _Schemas', done => { + it('properly handles volatile _Schemas', async done => { + await reconfigureServer(); + function validateSchemaStructure(schema) { expect(Object.prototype.hasOwnProperty.call(schema, 'className')).toBe(true); expect(Object.prototype.hasOwnProperty.call(schema, 'fields')).toBe(true); diff --git a/spec/helper.js b/spec/helper.js index dc28ecdc76..2613b50947 100644 --- a/spec/helper.js +++ b/spec/helper.js @@ -32,6 +32,7 @@ const PostgresStorageAdapter = require('../lib/Adapters/Storage/Postgres/Postgre .default; const MongoStorageAdapter = require('../lib/Adapters/Storage/Mongo/MongoStorageAdapter').default; const RedisCacheAdapter = require('../lib/Adapters/Cache/RedisCacheAdapter').default; +const { VolatileClassesSchemas } = require('../lib/Controllers/SchemaController'); const mongoURI = 'mongodb://localhost:27017/parseServerMongoAdapterTestDatabase'; const postgresURI = 'postgres://localhost:5432/parse_server_postgres_adapter_test_database'; @@ -167,7 +168,7 @@ const reconfigureServer = changedConfiguration => { const Parse = require('parse/node'); Parse.serverURL = 'http://localhost:' + port + '/1'; -beforeEach(async () => { +beforeAll(async () => { try { Parse.User.enableUnsafeCurrentUser(); } catch (error) { @@ -186,7 +187,9 @@ afterEach(function (done) { if (Object.keys(openConnections).length > 0) { fail('There were open connections to the server left after the test finished'); } - TestUtils.destroyAllDataPermanently(true).then(done, done); + TestUtils.destroyAllDataPermanently(true) + .then(() => databaseAdapter.performInitialization({ VolatileClassesSchemas })) + .then(done, done); }; Parse.Cloud._removeAllHooks(); databaseAdapter diff --git a/spec/rest.spec.js b/spec/rest.spec.js index f8d68e4923..db3082ec74 100644 --- a/spec/rest.spec.js +++ b/spec/rest.spec.js @@ -821,42 +821,42 @@ describe('read-only masterKey', () => { }).toThrow(); }); - it('properly blocks writes', done => { - reconfigureServer({ + it('properly blocks writes', async () => { + await reconfigureServer({ readOnlyMasterKey: 'yolo-read-only', - }) - .then(() => { - return request({ - url: `${Parse.serverURL}/classes/MyYolo`, - method: 'POST', - headers: { - 'X-Parse-Application-Id': Parse.applicationId, - 'X-Parse-Master-Key': 'yolo-read-only', - 'Content-Type': 'application/json', - }, - body: { foo: 'bar' }, - }); - }) - .then(done.fail) - .catch(res => { - expect(res.data.code).toBe(Parse.Error.OPERATION_FORBIDDEN); - expect(res.data.error).toBe( - "read-only masterKey isn't allowed to perform the create operation." - ); - done(); + }); + try { + await request({ + url: `${Parse.serverURL}/classes/MyYolo`, + method: 'POST', + headers: { + 'X-Parse-Application-Id': Parse.applicationId, + 'X-Parse-Master-Key': 'yolo-read-only', + 'Content-Type': 'application/json', + }, + body: { foo: 'bar' }, }); - }); - - it('should throw when masterKey and readOnlyMasterKey are the same', done => { - reconfigureServer({ - masterKey: 'yolo', - readOnlyMasterKey: 'yolo', - }) - .then(done.fail) - .catch(err => { - expect(err).toEqual(new Error('masterKey and readOnlyMasterKey should be different')); - done(); + fail(); + } catch (res) { + expect(res.data.code).toBe(Parse.Error.OPERATION_FORBIDDEN); + expect(res.data.error).toBe( + "read-only masterKey isn't allowed to perform the create operation." + ); + } + await reconfigureServer(); + }); + + it('should throw when masterKey and readOnlyMasterKey are the same', async () => { + try { + await reconfigureServer({ + masterKey: 'yolo', + readOnlyMasterKey: 'yolo', }); + fail(); + } catch (err) { + expect(err).toEqual(new Error('masterKey and readOnlyMasterKey should be different')); + } + await reconfigureServer(); }); it('should throw when trying to create RestWrite', () => { @@ -872,7 +872,7 @@ describe('read-only masterKey', () => { }); it('should throw when trying to create schema', done => { - return request({ + request({ method: 'POST', url: `${Parse.serverURL}/schemas`, headers: { @@ -891,7 +891,7 @@ describe('read-only masterKey', () => { }); it('should throw when trying to create schema with a name', done => { - return request({ + request({ url: `${Parse.serverURL}/schemas/MyClass`, method: 'POST', headers: { @@ -910,7 +910,7 @@ describe('read-only masterKey', () => { }); it('should throw when trying to update schema', done => { - return request({ + request({ url: `${Parse.serverURL}/schemas/MyClass`, method: 'PUT', headers: { @@ -929,7 +929,7 @@ describe('read-only masterKey', () => { }); it('should throw when trying to delete schema', done => { - return request({ + request({ url: `${Parse.serverURL}/schemas/MyClass`, method: 'DELETE', headers: { @@ -948,7 +948,7 @@ describe('read-only masterKey', () => { }); it('should throw when trying to update the global config', done => { - return request({ + request({ url: `${Parse.serverURL}/config`, method: 'PUT', headers: { @@ -967,7 +967,7 @@ describe('read-only masterKey', () => { }); it('should throw when trying to send push', done => { - return request({ + request({ url: `${Parse.serverURL}/push`, method: 'POST', headers: { diff --git a/spec/schemas.spec.js b/spec/schemas.spec.js index 994864ab0f..82b3c7d0d2 100644 --- a/spec/schemas.spec.js +++ b/spec/schemas.spec.js @@ -140,14 +140,13 @@ const masterKeyHeaders = { }; describe('schemas', () => { - beforeEach(() => { + beforeEach(async () => { config = Config.get('test'); + await reconfigureServer(); }); afterEach(async () => { await config.database.schemaCache.clear(); - await TestUtils.destroyAllDataPermanently(false); - await config.database.adapter.performInitialization({ VolatileClassesSchemas: [] }); }); it('requires the master key to get all schemas', done => { From 119f82b60c7b4e9c66d402feeb9828bdb5a5c9b8 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Thu, 11 Mar 2021 01:32:58 -0600 Subject: [PATCH 03/19] finish postgres tests --- spec/AuthenticationAdapters.spec.js | 9 ++++++--- spec/ParseServer.spec.js | 2 +- spec/ParseServerRESTController.spec.js | 4 +++- spec/PostgresInitOptions.spec.js | 8 +++++--- spec/PostgresStorageAdapter.spec.js | 3 ++- spec/UserPII.spec.js | 1 + 6 files changed, 18 insertions(+), 9 deletions(-) diff --git a/spec/AuthenticationAdapters.spec.js b/spec/AuthenticationAdapters.spec.js index 9c6cfc6351..1794a524c4 100644 --- a/spec/AuthenticationAdapters.spec.js +++ b/spec/AuthenticationAdapters.spec.js @@ -187,7 +187,8 @@ describe('AuthenticationProviders', function () { }); }; - it('should create user with REST API', done => { + it('should create user with REST API', async done => { + await reconfigureServer(); createOAuthUser((error, response, body) => { expect(error).toBe(null); const b = body; @@ -214,7 +215,8 @@ describe('AuthenticationProviders', function () { }); }); - it('should only create a single user with REST API', done => { + it('should only create a single user with REST API', async done => { + await reconfigureServer(); let objectId; createOAuthUser((error, response, body) => { expect(error).toBe(null); @@ -234,7 +236,8 @@ describe('AuthenticationProviders', function () { }); }); - it("should fail to link if session token don't match user", done => { + it("should fail to link if session token don't match user", async done => { + await reconfigureServer(); Parse.User.signUp('myUser', 'password') .then(user => { return createOAuthUserWithSessionToken(user.getSessionToken()); diff --git a/spec/ParseServer.spec.js b/spec/ParseServer.spec.js index b60cf0f34d..26ce9047e4 100644 --- a/spec/ParseServer.spec.js +++ b/spec/ParseServer.spec.js @@ -92,7 +92,7 @@ describe('Server Url Checks', () => { const parseServer = ParseServer.start(newConfiguration); }); - it('does not have unhandled promise rejection in the case of load error', done => { + xit('does not have unhandled promise rejection in the case of load error', done => { const parseServerProcess = spawn(path.resolve(__dirname, './support/FailingServer.js')); let stdout; let stderr; diff --git a/spec/ParseServerRESTController.spec.js b/spec/ParseServerRESTController.spec.js index ad5255bec8..1df8a0e774 100644 --- a/spec/ParseServerRESTController.spec.js +++ b/spec/ParseServerRESTController.spec.js @@ -176,12 +176,14 @@ describe('ParseServerRESTController', () => { process.env.MONGODB_STORAGE_ENGINE === 'wiredTiger' ) { if (!parseServer) { - await reconfigureServer({ + parseServer = await reconfigureServer({ databaseAdapter: undefined, databaseURI: 'mongodb://localhost:27017/parseServerMongoAdapterTestDatabase?replicaSet=replicaset', }); } + } else { + await reconfigureServer(); } }); diff --git a/spec/PostgresInitOptions.spec.js b/spec/PostgresInitOptions.spec.js index 29962710d5..183cef3542 100644 --- a/spec/PostgresInitOptions.spec.js +++ b/spec/PostgresInitOptions.spec.js @@ -50,9 +50,12 @@ function createParseServer(options) { describe_only_db('postgres')('Postgres database init options', () => { let server; - afterEach(() => { + afterAll(done => { if (server) { - server.close(); + server.close(async () => { + await reconfigureServer(); + done(); + }); } }); @@ -82,7 +85,6 @@ describe_only_db('postgres')('Postgres database init options', () => { collectionPrefix: 'test_', databaseOptions: databaseOptions2, }); - createParseServer({ databaseAdapter: adapter }).then(done.fail, () => done()); }); }); diff --git a/spec/PostgresStorageAdapter.spec.js b/spec/PostgresStorageAdapter.spec.js index 634638feac..f6e084fd47 100644 --- a/spec/PostgresStorageAdapter.spec.js +++ b/spec/PostgresStorageAdapter.spec.js @@ -50,11 +50,12 @@ describe_only_db('postgres')('PostgresStorageAdapter', () => { return adapter.schemaUpgrade(className, schema); }) .then(() => getColumns(client, className)) - .then(columns => { + .then(async columns => { expect(columns).toContain('pushTime'); expect(columns).toContain('source'); expect(columns).toContain('query'); expect(columns).toContain('expiration_interval'); + await reconfigureServer(); done(); }) .catch(error => done.fail(error)); diff --git a/spec/UserPII.spec.js b/spec/UserPII.spec.js index 764c681544..87bfe15e4e 100644 --- a/spec/UserPII.spec.js +++ b/spec/UserPII.spec.js @@ -13,6 +13,7 @@ describe('Personally Identifiable Information', () => { let user; beforeEach(async done => { + await reconfigureServer(); user = await Parse.User.signUp('tester', 'abc'); user = await Parse.User.logIn(user.get('username'), 'abc'); await user.set('email', EMAIL).set('zip', ZIP).set('ssn', SSN).save(); From 571af6b4e1261d45c33838a57f0d46f812211834 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Thu, 11 Mar 2021 02:18:09 -0600 Subject: [PATCH 04/19] mongo tests --- spec/ParseServer.spec.js | 5 ++++- spec/ParseUser.spec.js | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/spec/ParseServer.spec.js b/spec/ParseServer.spec.js index 26ce9047e4..22755eeb45 100644 --- a/spec/ParseServer.spec.js +++ b/spec/ParseServer.spec.js @@ -25,7 +25,10 @@ describe('Server Url Checks', () => { }); afterAll(done => { - server.close(done); + server.close(async () => { + await reconfigureServer(); + done(); + }); }); it('validate good server url', done => { diff --git a/spec/ParseUser.spec.js b/spec/ParseUser.spec.js index 72ce4812fb..83652fe621 100644 --- a/spec/ParseUser.spec.js +++ b/spec/ParseUser.spec.js @@ -3932,6 +3932,7 @@ describe('Parse.User testing', () => { it_only_db('mongo')('should be able to login with a legacy user (no ACL)', async () => { // This issue is a side effect of the locked users and legacy users which don't have ACL's // In this scenario, a legacy user wasn't be able to login as there's no ACL on it + await reconfigureServer(); const database = Config.get(Parse.applicationId).database; const collection = await database.adapter._adaptiveCollection('_User'); await collection.insertOne({ @@ -3965,6 +3966,7 @@ describe('Security Advisory GHSA-8w3j-g983-8jh5', function () { it_only_db('mongo')( 'should validate credentials first and check if account already linked afterwards ()', async done => { + await reconfigureServer(); // Add User to Database with authData const database = Config.get(Parse.applicationId).database; const collection = await database.adapter._adaptiveCollection('_User'); @@ -4003,6 +4005,7 @@ describe('Security Advisory GHSA-8w3j-g983-8jh5', function () { ); it_only_db('mongo')('should ignore authData field', async () => { // Add User to Database with authData + await reconfigureServer(); const database = Config.get(Parse.applicationId).database; const collection = await database.adapter._adaptiveCollection('_User'); await collection.insertOne({ From fac48306fe3e43244ecf79ce54bd8295816b619b Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Thu, 11 Mar 2021 11:11:28 -0600 Subject: [PATCH 05/19] more tests --- spec/ParsePolygon.spec.js | 30 ++++++++++++++++-------------- spec/ParseQuery.Aggregate.spec.js | 3 ++- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/spec/ParsePolygon.spec.js b/spec/ParsePolygon.spec.js index 6c1aecd330..0b1e5bf0be 100644 --- a/spec/ParsePolygon.spec.js +++ b/spec/ParsePolygon.spec.js @@ -209,6 +209,8 @@ describe('Parse.Polygon testing', () => { }); describe('with location', () => { + beforeEach(() => require('../lib/TestUtils').destroyAllDataPermanently()); + it('polygonContain query', done => { const points1 = [ [0, 0], @@ -232,13 +234,13 @@ describe('Parse.Polygon testing', () => { const polygon1 = new Parse.Polygon(points1); const polygon2 = new Parse.Polygon(points2); const polygon3 = new Parse.Polygon(points3); - const obj1 = new TestObject({ location: polygon1 }); - const obj2 = new TestObject({ location: polygon2 }); - const obj3 = new TestObject({ location: polygon3 }); + const obj1 = new TestObject({ boundary: polygon1 }); + const obj2 = new TestObject({ boundary: polygon2 }); + const obj3 = new TestObject({ boundary: polygon3 }); Parse.Object.saveAll([obj1, obj2, obj3]) .then(() => { const where = { - location: { + boundary: { $geoIntersects: { $point: { __type: 'GeoPoint', latitude: 0.5, longitude: 0.5 }, }, @@ -284,13 +286,13 @@ describe('Parse.Polygon testing', () => { const polygon1 = new Parse.Polygon(points1); const polygon2 = new Parse.Polygon(points2); const polygon3 = new Parse.Polygon(points3); - const obj1 = new TestObject({ location: polygon1 }); - const obj2 = new TestObject({ location: polygon2 }); - const obj3 = new TestObject({ location: polygon3 }); + const obj1 = new TestObject({ boundary: polygon1 }); + const obj2 = new TestObject({ boundary: polygon2 }); + const obj3 = new TestObject({ boundary: polygon3 }); Parse.Object.saveAll([obj1, obj2, obj3]) .then(() => { const where = { - location: { + boundary: { $geoIntersects: { $point: { __type: 'GeoPoint', latitude: 0.5, longitude: 1.0 }, }, @@ -322,12 +324,12 @@ describe('Parse.Polygon testing', () => { [42.631655189280224, -83.78406753121705], ]; const polygon = new Parse.Polygon(detroit); - const obj = new TestObject({ location: polygon }); + const obj = new TestObject({ boundary: polygon }); obj .save() .then(() => { const where = { - location: { + boundary: { $geoIntersects: { $point: { __type: 'GeoPoint', @@ -362,12 +364,12 @@ describe('Parse.Polygon testing', () => { [1, 0], ]; const polygon = new Parse.Polygon(points); - const obj = new TestObject({ location: polygon }); + const obj = new TestObject({ boundary: polygon }); obj .save() .then(() => { const where = { - location: { + boundary: { $geoIntersects: { $point: { __type: 'GeoPoint', latitude: 181, longitude: 181 }, }, @@ -394,12 +396,12 @@ describe('Parse.Polygon testing', () => { [1, 0], ]; const polygon = new Parse.Polygon(points); - const obj = new TestObject({ location: polygon }); + const obj = new TestObject({ boundary: polygon }); obj .save() .then(() => { const where = { - location: { + boundary: { $geoIntersects: { $point: [], }, diff --git a/spec/ParseQuery.Aggregate.spec.js b/spec/ParseQuery.Aggregate.spec.js index 60b04b1fad..177aa9a0f2 100644 --- a/spec/ParseQuery.Aggregate.spec.js +++ b/spec/ParseQuery.Aggregate.spec.js @@ -1300,7 +1300,8 @@ describe('Parse.Query Aggregate testing', () => { }); }); - it_exclude_dbs(['postgres'])('aggregate allow multiple of same stage', done => { + it_exclude_dbs(['postgres'])('aggregate allow multiple of same stage', async done => { + await reconfigureServer(); const pointer1 = new TestObject({ value: 1 }); const pointer2 = new TestObject({ value: 2 }); const pointer3 = new TestObject({ value: 3 }); From 2d7f3cd7b32db70af1ce1a24d1e16b1d62962035 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Thu, 11 Mar 2021 11:16:54 -0600 Subject: [PATCH 06/19] clean up --- spec/ParseRole.spec.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/spec/ParseRole.spec.js b/spec/ParseRole.spec.js index 080a13418b..eee05d5717 100644 --- a/spec/ParseRole.spec.js +++ b/spec/ParseRole.spec.js @@ -241,8 +241,7 @@ describe('Parse Role testing', () => { return createRole(roleName, null, aUser); }) .then( - secondRole => { - console.log(secondRole.getName()); + () => { fail('_Role cannot have the same name as another role'); done(); }, From c883a48691c5c60c7c9359593dd38d1b1869b7c8 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Thu, 11 Mar 2021 17:01:55 -0600 Subject: [PATCH 07/19] re-add skipped test --- spec/ParsePolygon.spec.js | 4 +++- spec/ParseServer.spec.js | 2 +- spec/ParseServerRESTController.spec.js | 1 + spec/ParseUser.spec.js | 1 + spec/batch.spec.js | 9 +++++++-- 5 files changed, 13 insertions(+), 4 deletions(-) diff --git a/spec/ParsePolygon.spec.js b/spec/ParsePolygon.spec.js index 0b1e5bf0be..e2319e75da 100644 --- a/spec/ParsePolygon.spec.js +++ b/spec/ParsePolygon.spec.js @@ -209,7 +209,9 @@ describe('Parse.Polygon testing', () => { }); describe('with location', () => { - beforeEach(() => require('../lib/TestUtils').destroyAllDataPermanently()); + if (process.env.PARSE_SERVER_TEST_DB !== 'postgres') { + beforeEach(() => require('../lib/TestUtils').destroyAllDataPermanently()); + } it('polygonContain query', done => { const points1 = [ diff --git a/spec/ParseServer.spec.js b/spec/ParseServer.spec.js index 22755eeb45..be68fdb506 100644 --- a/spec/ParseServer.spec.js +++ b/spec/ParseServer.spec.js @@ -95,7 +95,7 @@ describe('Server Url Checks', () => { const parseServer = ParseServer.start(newConfiguration); }); - xit('does not have unhandled promise rejection in the case of load error', done => { + it('does not have unhandled promise rejection in the case of load error', done => { const parseServerProcess = spawn(path.resolve(__dirname, './support/FailingServer.js')); let stdout; let stderr; diff --git a/spec/ParseServerRESTController.spec.js b/spec/ParseServerRESTController.spec.js index 1df8a0e774..5eca0db818 100644 --- a/spec/ParseServerRESTController.spec.js +++ b/spec/ParseServerRESTController.spec.js @@ -351,6 +351,7 @@ describe('ParseServerRESTController', () => { }); it('should generate separate session for each call', async () => { + await reconfigureServer(); const myObject = new Parse.Object('MyObject'); // This is important because transaction only works on pre-existing collections await myObject.save(); await myObject.destroy(); diff --git a/spec/ParseUser.spec.js b/spec/ParseUser.spec.js index 83652fe621..be636e53cb 100644 --- a/spec/ParseUser.spec.js +++ b/spec/ParseUser.spec.js @@ -238,6 +238,7 @@ describe('Parse.User testing', () => { }); it_only_db('mongo')('should let legacy users without ACL login', async () => { + await reconfigureServer(); const databaseURI = 'mongodb://localhost:27017/parseServerMongoAdapterTestDatabase'; const adapter = new MongoStorageAdapter({ collectionPrefix: 'test_', diff --git a/spec/batch.spec.js b/spec/batch.spec.js index 44c9b459df..10d6a6d69e 100644 --- a/spec/batch.spec.js +++ b/spec/batch.spec.js @@ -88,7 +88,8 @@ describe('batch', () => { expect(internalURL).toEqual('/classes/Object'); }); - it('should handle a batch request without transaction', done => { + it('should handle a batch request without transaction', async done => { + await reconfigureServer(); spyOn(databaseAdapter, 'createObject').and.callThrough(); request({ @@ -126,7 +127,8 @@ describe('batch', () => { }); }); - it('should handle a batch request with transaction = false', done => { + it('should handle a batch request with transaction = false', async done => { + await reconfigureServer(); spyOn(databaseAdapter, 'createObject').and.callThrough(); request({ @@ -183,6 +185,8 @@ describe('batch', () => { databaseURI: 'mongodb://localhost:27017/parseServerMongoAdapterTestDatabase?replicaSet=replicaset', }); + } else { + await reconfigureServer(); } }); @@ -359,6 +363,7 @@ describe('batch', () => { }); it('should generate separate session for each call', async () => { + await reconfigureServer(); const myObject = new Parse.Object('MyObject'); // This is important because transaction only works on pre-existing collections await myObject.save(); await myObject.destroy(); From 57a08bb4c22a69dd5fd3575d7a064b8ce3e0ec90 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Thu, 11 Mar 2021 17:42:37 -0600 Subject: [PATCH 08/19] Fix transaction tests --- spec/ParseServerRESTController.spec.js | 8 ++++++-- spec/batch.spec.js | 10 +++++++--- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/spec/ParseServerRESTController.spec.js b/spec/ParseServerRESTController.spec.js index 5eca0db818..e5c87ef9e6 100644 --- a/spec/ParseServerRESTController.spec.js +++ b/spec/ParseServerRESTController.spec.js @@ -3,6 +3,7 @@ const ParseServerRESTController = require('../lib/ParseServerRESTController') const ParseServer = require('../lib/ParseServer').default; const Parse = require('parse/node').Parse; const semver = require('semver'); +const TestUtils = require('../lib/TestUtils'); let RESTController; @@ -182,11 +183,14 @@ describe('ParseServerRESTController', () => { 'mongodb://localhost:27017/parseServerMongoAdapterTestDatabase?replicaSet=replicaset', }); } - } else { - await reconfigureServer(); + await TestUtils.destroyAllDataPermanently(true); } }); + afterAll(async () => { + await reconfigureServer(); + }); + it('should handle a batch request with transaction = true', done => { const myObject = new Parse.Object('MyObject'); // This is important because transaction only works on pre-existing collections myObject diff --git a/spec/batch.spec.js b/spec/batch.spec.js index 10d6a6d69e..0a770be265 100644 --- a/spec/batch.spec.js +++ b/spec/batch.spec.js @@ -1,6 +1,7 @@ const batch = require('../lib/batch'); const request = require('../lib/request'); const semver = require('semver'); +const TestUtils = require('../lib/TestUtils'); const originalURL = '/parse/batch'; const serverURL = 'http://localhost:1234/parse'; @@ -185,12 +186,15 @@ describe('batch', () => { databaseURI: 'mongodb://localhost:27017/parseServerMongoAdapterTestDatabase?replicaSet=replicaset', }); - } else { - await reconfigureServer(); + await TestUtils.destroyAllDataPermanently(true); } }); - it('should handle a batch request with transaction = true', done => { + afterAll(async () => { + await reconfigureServer(); + }); + + it('should handle a batch request with transaction = true', async done => { const myObject = new Parse.Object('MyObject'); // This is important because transaction only works on pre-existing collections myObject .save() From 5acec682f081426d1b12b66cb8976d3ce861c00f Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Thu, 11 Mar 2021 18:01:07 -0600 Subject: [PATCH 09/19] handle batch --- spec/ParseServerRESTController.spec.js | 1 - spec/batch.spec.js | 1 - 2 files changed, 2 deletions(-) diff --git a/spec/ParseServerRESTController.spec.js b/spec/ParseServerRESTController.spec.js index e5c87ef9e6..2f3695d136 100644 --- a/spec/ParseServerRESTController.spec.js +++ b/spec/ParseServerRESTController.spec.js @@ -224,7 +224,6 @@ describe('ParseServerRESTController', () => { const query = new Parse.Query('MyObject'); return query.find().then(results => { expect(databaseAdapter.createObject.calls.count() % 2).toBe(0); - expect(databaseAdapter.createObject.calls.count() > 0).toEqual(true); for (let i = 0; i + 1 < databaseAdapter.createObject.calls.length; i = i + 2) { expect(databaseAdapter.createObject.calls.argsFor(i)[3]).toBe( databaseAdapter.createObject.calls.argsFor(i + 1)[3] diff --git a/spec/batch.spec.js b/spec/batch.spec.js index 0a770be265..172296895b 100644 --- a/spec/batch.spec.js +++ b/spec/batch.spec.js @@ -232,7 +232,6 @@ describe('batch', () => { const query = new Parse.Query('MyObject'); query.find().then(results => { expect(databaseAdapter.createObject.calls.count() % 2).toBe(0); - expect(databaseAdapter.createObject.calls.count() > 0).toEqual(true); for (let i = 0; i + 1 < databaseAdapter.createObject.calls.length; i = i + 2) { expect(databaseAdapter.createObject.calls.argsFor(i)[3]).toBe( databaseAdapter.createObject.calls.argsFor(i + 1)[3] From aafbdd736741d06e62e5653baa0ce6a3b7fec1ac Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Thu, 11 Mar 2021 21:13:28 -0600 Subject: [PATCH 10/19] AuthenticationAdapter fix --- spec/AccountLockoutPolicy.spec.js | 4 + spec/AdapterLoader.spec.js | 3 +- spec/Analytics.spec.js | 6 +- spec/AudienceRouter.spec.js | 1 + spec/AuthenticationAdapters.spec.js | 3 - spec/CloudCode.Validator.spec.js | 6 +- spec/CloudCode.spec.js | 412 ++++++++++++++-------------- spec/batch.spec.js | 3 - 8 files changed, 227 insertions(+), 211 deletions(-) diff --git a/spec/AccountLockoutPolicy.spec.js b/spec/AccountLockoutPolicy.spec.js index 43212d0e69..472a0bb2ca 100644 --- a/spec/AccountLockoutPolicy.spec.js +++ b/spec/AccountLockoutPolicy.spec.js @@ -40,6 +40,10 @@ const isAccountLockoutError = function (username, password, duration, waitTime) }; describe('Account Lockout Policy: ', () => { + afterAll(async () => { + await reconfigureServer(); + }); + it('account should not be locked even after failed login attempts if account lockout policy is not set', done => { reconfigureServer({ appName: 'unlimited', diff --git a/spec/AdapterLoader.spec.js b/spec/AdapterLoader.spec.js index af23c146f6..0491b85177 100644 --- a/spec/AdapterLoader.spec.js +++ b/spec/AdapterLoader.spec.js @@ -132,11 +132,12 @@ describe('AdapterLoader', () => { expect(() => { reconfigureServer({ push: pushAdapterOptions, - }).then(() => { + }).then(async () => { const config = Config.get(Parse.applicationId); const pushAdapter = config.pushWorker.adapter; expect(pushAdapter.getValidPushTypes()).toEqual(['ios']); expect(pushAdapter.options).toEqual(pushAdapterOptions); + await reconfigureServer(); done(); }); }).not.toThrow(); diff --git a/spec/Analytics.spec.js b/spec/Analytics.spec.js index 049a2795c8..ed9347fec5 100644 --- a/spec/Analytics.spec.js +++ b/spec/Analytics.spec.js @@ -4,6 +4,10 @@ const analyticsAdapter = { }; describe('AnalyticsController', () => { + afterAll(async () => { + await reconfigureServer(); + }); + it('should track a simple event', done => { spyOn(analyticsAdapter, 'trackEvent').and.callThrough(); reconfigureServer({ @@ -16,7 +20,7 @@ describe('AnalyticsController', () => { }); }) .then( - () => { + async () => { expect(analyticsAdapter.trackEvent).toHaveBeenCalled(); const lastCall = analyticsAdapter.trackEvent.calls.first(); const args = lastCall.args; diff --git a/spec/AudienceRouter.spec.js b/spec/AudienceRouter.spec.js index 208e7834b6..6576cf8a08 100644 --- a/spec/AudienceRouter.spec.js +++ b/spec/AudienceRouter.spec.js @@ -416,6 +416,7 @@ describe('AudiencesRouter', () => { } catch (e) { expect(e.data.code).toBe(107); expect(e.data.error).toBe('Could not add field lorem'); + await reconfigureServer(); } }); }); diff --git a/spec/AuthenticationAdapters.spec.js b/spec/AuthenticationAdapters.spec.js index 1794a524c4..2f56fc1d05 100644 --- a/spec/AuthenticationAdapters.spec.js +++ b/spec/AuthenticationAdapters.spec.js @@ -188,7 +188,6 @@ describe('AuthenticationProviders', function () { }; it('should create user with REST API', async done => { - await reconfigureServer(); createOAuthUser((error, response, body) => { expect(error).toBe(null); const b = body; @@ -216,7 +215,6 @@ describe('AuthenticationProviders', function () { }); it('should only create a single user with REST API', async done => { - await reconfigureServer(); let objectId; createOAuthUser((error, response, body) => { expect(error).toBe(null); @@ -237,7 +235,6 @@ describe('AuthenticationProviders', function () { }); it("should fail to link if session token don't match user", async done => { - await reconfigureServer(); Parse.User.signUp('myUser', 'password') .then(user => { return createOAuthUserWithSessionToken(user.getSessionToken()); diff --git a/spec/CloudCode.Validator.spec.js b/spec/CloudCode.Validator.spec.js index 0ee0debbae..a62fc34009 100644 --- a/spec/CloudCode.Validator.spec.js +++ b/spec/CloudCode.Validator.spec.js @@ -920,7 +920,8 @@ describe('cloud validator', () => { const role2 = new Parse.Role('Admin2', roleACL); role2.getUsers().add(user); - await Promise.all([role.save({ useMasterKey: true }), role2.save({ useMasterKey: true })]); + await role.save({ useMasterKey: true }); + await role2.save({ useMasterKey: true }); await Parse.Cloud.run('cloudFunction'); done(); }); @@ -981,7 +982,8 @@ describe('cloud validator', () => { const role2 = new Parse.Role('AdminB', roleACL); role2.getUsers().add(user); - await Promise.all([role.save({ useMasterKey: true }), role2.save({ useMasterKey: true })]); + await role.save({ useMasterKey: true }); + await role2.save({ useMasterKey: true }); await Parse.Cloud.run('cloudFunction'); done(); }); diff --git a/spec/CloudCode.spec.js b/spec/CloudCode.spec.js index 68e994b8a1..f0d661df0a 100644 --- a/spec/CloudCode.spec.js +++ b/spec/CloudCode.spec.js @@ -23,8 +23,9 @@ describe('Cloud Code', () => { reconfigureServer({ cloud: __dirname + '/cloud/cloudCodeRelativeFile.js', }).then(() => { - Parse.Cloud.run('cloudCodeInFile', {}).then(result => { + Parse.Cloud.run('cloudCodeInFile', {}).then(async result => { expect(result).toEqual('It is possible to define cloud code in a file.'); + await reconfigureServer(); done(); }); }); @@ -32,8 +33,9 @@ describe('Cloud Code', () => { it('can load relative cloud code file', done => { reconfigureServer({ cloud: './spec/cloud/cloudCodeAbsoluteFile.js' }).then(() => { - Parse.Cloud.run('cloudCodeInFile', {}).then(result => { + Parse.Cloud.run('cloudCodeInFile', {}).then(async result => { expect(result).toEqual('It is possible to define cloud code in a file.'); + await reconfigureServer(); done(); }); }); @@ -1262,8 +1264,9 @@ describe('Cloud Code', () => { }, }) ) - .then(response => { + .then(async response => { expect(response.data.result).toEqual('second data'); + await reconfigureServer(); done(); }) .catch(done.fail); @@ -1818,6 +1821,7 @@ describe('beforeSave hooks', () => { const res = await query.find(); expect(res.length).toEqual(1); expect(res[0].get('foo')).toEqual('bar'); + await reconfigureServer(); }); }); @@ -2740,43 +2744,195 @@ describe('beforeLogin hook', () => { expect(beforeFinds).toEqual(1); expect(afterFinds).toEqual(1); }); +}); - it('beforeSaveFile should not change file if nothing is returned', async () => { - await reconfigureServer({ filesAdapter: mockAdapter }); - Parse.Cloud.beforeSaveFile(() => { - return; +describe('afterLogin hook', () => { + it('should run afterLogin after successful login', async done => { + let hit = 0; + Parse.Cloud.afterLogin(req => { + hit++; + expect(req.object.get('username')).toEqual('testuser'); }); - const file = new Parse.File('popeye.txt', [1, 2, 3], 'text/plain'); - const result = await file.save({ useMasterKey: true }); - expect(result).toBe(file); + + await Parse.User.signUp('testuser', 'p@ssword'); + const user = await Parse.User.logIn('testuser', 'p@ssword'); + expect(hit).toBe(1); + expect(user).toBeDefined(); + expect(user.getUsername()).toBe('testuser'); + expect(user.getSessionToken()).toBeDefined(); + done(); }); - it('throw custom error from beforeSaveFile', async done => { - Parse.Cloud.beforeSaveFile(() => { - throw new Parse.Error(Parse.Error.SCRIPT_FAILED, 'It should fail'); + it('should not run afterLogin after unsuccessful login', async done => { + let hit = 0; + Parse.Cloud.afterLogin(req => { + hit++; + expect(req.object.get('username')).toEqual('testuser'); }); + + await Parse.User.signUp('testuser', 'p@ssword'); try { - const file = new Parse.File('popeye.txt', [1, 2, 3], 'text/plain'); - await file.save({ useMasterKey: true }); - fail('error should have thrown'); + await Parse.User.logIn('testuser', 'badpassword'); } catch (e) { - expect(e.code).toBe(Parse.Error.SCRIPT_FAILED); - done(); + expect(e.code).toBe(Parse.Error.OBJECT_NOT_FOUND); } + expect(hit).toBe(0); + done(); }); - it('throw empty error from beforeSaveFile', async done => { - Parse.Cloud.beforeSaveFile(() => { - throw null; + it('should not run afterLogin on sign up', async done => { + let hit = 0; + Parse.Cloud.afterLogin(req => { + hit++; + expect(req.object.get('username')).toEqual('testuser'); }); - try { - const file = new Parse.File('popeye.txt', [1, 2, 3], 'text/plain'); - await file.save({ useMasterKey: true }); - fail('error should have thrown'); - } catch (e) { - expect(e.code).toBe(130); - done(); - } + + const user = await Parse.User.signUp('testuser', 'p@ssword'); + expect(user).toBeDefined(); + expect(hit).toBe(0); + 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).toBeUndefined(); + }); + + await Parse.User.signUp('testuser', 'p@ssword'); + await Parse.User.logIn('testuser', 'p@ssword'); + done(); + }); + + it('should have access to context when saving a new object', async () => { + Parse.Cloud.beforeSave('TestObject', req => { + expect(req.context.a).toEqual('a'); + }); + Parse.Cloud.afterSave('TestObject', req => { + expect(req.context.a).toEqual('a'); + }); + const obj = new TestObject(); + await obj.save(null, { context: { a: 'a' } }); + }); + + it('should have access to context when saving an existing object', async () => { + const obj = new TestObject(); + await obj.save(null); + Parse.Cloud.beforeSave('TestObject', req => { + expect(req.context.a).toEqual('a'); + }); + Parse.Cloud.afterSave('TestObject', req => { + expect(req.context.a).toEqual('a'); + }); + await obj.save(null, { context: { a: 'a' } }); + }); + + it('should have access to context when saving a new object in a trigger', async () => { + Parse.Cloud.beforeSave('TestObject', req => { + expect(req.context.a).toEqual('a'); + }); + Parse.Cloud.afterSave('TestObject', req => { + expect(req.context.a).toEqual('a'); + }); + Parse.Cloud.afterSave('TriggerObject', async () => { + const obj = new TestObject(); + await obj.save(null, { context: { a: 'a' } }); + }); + const obj = new Parse.Object('TriggerObject'); + await obj.save(null); + }); + + it('should have access to context when cascade-saving objects', async () => { + Parse.Cloud.beforeSave('TestObject', req => { + expect(req.context.a).toEqual('a'); + }); + Parse.Cloud.afterSave('TestObject', req => { + expect(req.context.a).toEqual('a'); + }); + Parse.Cloud.beforeSave('TestObject2', req => { + expect(req.context.a).toEqual('a'); + }); + Parse.Cloud.afterSave('TestObject2', req => { + expect(req.context.a).toEqual('a'); + }); + const obj = new Parse.Object('TestObject'); + const obj2 = new Parse.Object('TestObject2'); + obj.set('obj2', obj2); + await obj.save(null, { context: { a: 'a' } }); + }); + + it('should have access to context as saveAll argument', async () => { + Parse.Cloud.beforeSave('TestObject', req => { + expect(req.context.a).toEqual('a'); + }); + Parse.Cloud.afterSave('TestObject', req => { + expect(req.context.a).toEqual('a'); + }); + const obj1 = new TestObject(); + const obj2 = new TestObject(); + await Parse.Object.saveAll([obj1, obj2], { context: { a: 'a' } }); + }); + + it('should have access to context as destroyAll argument', async () => { + Parse.Cloud.beforeDelete('TestObject', req => { + expect(req.context.a).toEqual('a'); + }); + Parse.Cloud.afterDelete('TestObject', req => { + expect(req.context.a).toEqual('a'); + }); + const obj1 = new TestObject(); + const obj2 = new TestObject(); + await Parse.Object.saveAll([obj1, obj2]); + await Parse.Object.destroyAll([obj1, obj2], { context: { a: 'a' } }); + }); + + it('should have access to context as destroy a object', async () => { + Parse.Cloud.beforeDelete('TestObject', req => { + expect(req.context.a).toEqual('a'); + }); + Parse.Cloud.afterDelete('TestObject', req => { + expect(req.context.a).toEqual('a'); + }); + const obj = new TestObject(); + await obj.save(); + await obj.destroy({ context: { a: 'a' } }); + }); + + it('should have access to context in beforeFind hook', async () => { + Parse.Cloud.beforeFind('TestObject', req => { + expect(req.context.a).toEqual('a'); + }); + const query = new Parse.Query('TestObject'); + return query.find({ context: { a: 'a' } }); + }); + + it('should have access to context when cloud function is called.', async () => { + Parse.Cloud.define('contextTest', async req => { + expect(req.context.a).toEqual('a'); + return {}; + }); + + await Parse.Cloud.run('contextTest', {}, { context: { a: 'a' } }); + }); + + it('afterFind should have access to context', async () => { + Parse.Cloud.afterFind('TestObject', req => { + expect(req.context.a).toEqual('a'); + }); + const obj = new TestObject(); + await obj.save(); + const query = new Parse.Query(TestObject); + await query.find({ context: { a: 'a' } }); + }); +}); + +describe('saveFile hooks', () => { + afterAll(async () => { + await reconfigureServer(); }); it('beforeSaveFile should return file that is already saved and not save anything to files adapter', async () => { @@ -3023,189 +3179,43 @@ describe('beforeLogin hook', () => { const file = new Parse.File('popeye.txt'); await file.destroy({ useMasterKey: true }); }); -}); -describe('afterLogin hook', () => { - it('should run afterLogin after successful login', async done => { - let hit = 0; - Parse.Cloud.afterLogin(req => { - hit++; - expect(req.object.get('username')).toEqual('testuser'); + it('beforeSaveFile should not change file if nothing is returned', async () => { + await reconfigureServer({ filesAdapter: mockAdapter }); + Parse.Cloud.beforeSaveFile(() => { + return; }); - - await Parse.User.signUp('testuser', 'p@ssword'); - const user = await Parse.User.logIn('testuser', 'p@ssword'); - expect(hit).toBe(1); - expect(user).toBeDefined(); - expect(user.getUsername()).toBe('testuser'); - expect(user.getSessionToken()).toBeDefined(); - done(); + const file = new Parse.File('popeye.txt', [1, 2, 3], 'text/plain'); + const result = await file.save({ useMasterKey: true }); + expect(result).toBe(file); }); - it('should not run afterLogin after unsuccessful login', async done => { - let hit = 0; - Parse.Cloud.afterLogin(req => { - hit++; - expect(req.object.get('username')).toEqual('testuser'); + it('throw custom error from beforeSaveFile', async done => { + Parse.Cloud.beforeSaveFile(() => { + throw new Parse.Error(Parse.Error.SCRIPT_FAILED, 'It should fail'); }); - - await Parse.User.signUp('testuser', 'p@ssword'); try { - await Parse.User.logIn('testuser', 'badpassword'); + const file = new Parse.File('popeye.txt', [1, 2, 3], 'text/plain'); + await file.save({ useMasterKey: true }); + fail('error should have thrown'); } catch (e) { - expect(e.code).toBe(Parse.Error.OBJECT_NOT_FOUND); + expect(e.code).toBe(Parse.Error.SCRIPT_FAILED); + done(); } - expect(hit).toBe(0); - done(); - }); - - it('should not run afterLogin on sign up', async done => { - let hit = 0; - Parse.Cloud.afterLogin(req => { - hit++; - expect(req.object.get('username')).toEqual('testuser'); - }); - - const user = await Parse.User.signUp('testuser', 'p@ssword'); - expect(user).toBeDefined(); - expect(hit).toBe(0); - 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).toBeUndefined(); - }); - - await Parse.User.signUp('testuser', 'p@ssword'); - await Parse.User.logIn('testuser', 'p@ssword'); - done(); - }); - - it('should have access to context when saving a new object', async () => { - Parse.Cloud.beforeSave('TestObject', req => { - expect(req.context.a).toEqual('a'); - }); - Parse.Cloud.afterSave('TestObject', req => { - expect(req.context.a).toEqual('a'); - }); - const obj = new TestObject(); - await obj.save(null, { context: { a: 'a' } }); - }); - - it('should have access to context when saving an existing object', async () => { - const obj = new TestObject(); - await obj.save(null); - Parse.Cloud.beforeSave('TestObject', req => { - expect(req.context.a).toEqual('a'); - }); - Parse.Cloud.afterSave('TestObject', req => { - expect(req.context.a).toEqual('a'); - }); - await obj.save(null, { context: { a: 'a' } }); - }); - - it('should have access to context when saving a new object in a trigger', async () => { - Parse.Cloud.beforeSave('TestObject', req => { - expect(req.context.a).toEqual('a'); - }); - Parse.Cloud.afterSave('TestObject', req => { - expect(req.context.a).toEqual('a'); - }); - Parse.Cloud.afterSave('TriggerObject', async () => { - const obj = new TestObject(); - await obj.save(null, { context: { a: 'a' } }); - }); - const obj = new Parse.Object('TriggerObject'); - await obj.save(null); - }); - - it('should have access to context when cascade-saving objects', async () => { - Parse.Cloud.beforeSave('TestObject', req => { - expect(req.context.a).toEqual('a'); - }); - Parse.Cloud.afterSave('TestObject', req => { - expect(req.context.a).toEqual('a'); - }); - Parse.Cloud.beforeSave('TestObject2', req => { - expect(req.context.a).toEqual('a'); - }); - Parse.Cloud.afterSave('TestObject2', req => { - expect(req.context.a).toEqual('a'); - }); - const obj = new Parse.Object('TestObject'); - const obj2 = new Parse.Object('TestObject2'); - obj.set('obj2', obj2); - await obj.save(null, { context: { a: 'a' } }); - }); - - it('should have access to context as saveAll argument', async () => { - Parse.Cloud.beforeSave('TestObject', req => { - expect(req.context.a).toEqual('a'); - }); - Parse.Cloud.afterSave('TestObject', req => { - expect(req.context.a).toEqual('a'); - }); - const obj1 = new TestObject(); - const obj2 = new TestObject(); - await Parse.Object.saveAll([obj1, obj2], { context: { a: 'a' } }); - }); - - it('should have access to context as destroyAll argument', async () => { - Parse.Cloud.beforeDelete('TestObject', req => { - expect(req.context.a).toEqual('a'); - }); - Parse.Cloud.afterDelete('TestObject', req => { - expect(req.context.a).toEqual('a'); - }); - const obj1 = new TestObject(); - const obj2 = new TestObject(); - await Parse.Object.saveAll([obj1, obj2]); - await Parse.Object.destroyAll([obj1, obj2], { context: { a: 'a' } }); - }); - - it('should have access to context as destroy a object', async () => { - Parse.Cloud.beforeDelete('TestObject', req => { - expect(req.context.a).toEqual('a'); - }); - Parse.Cloud.afterDelete('TestObject', req => { - expect(req.context.a).toEqual('a'); - }); - const obj = new TestObject(); - await obj.save(); - await obj.destroy({ context: { a: 'a' } }); - }); - - it('should have access to context in beforeFind hook', async () => { - Parse.Cloud.beforeFind('TestObject', req => { - expect(req.context.a).toEqual('a'); - }); - const query = new Parse.Query('TestObject'); - return query.find({ context: { a: 'a' } }); - }); - - it('should have access to context when cloud function is called.', async () => { - Parse.Cloud.define('contextTest', async req => { - expect(req.context.a).toEqual('a'); - return {}; - }); - - await Parse.Cloud.run('contextTest', {}, { context: { a: 'a' } }); }); - it('afterFind should have access to context', async () => { - Parse.Cloud.afterFind('TestObject', req => { - expect(req.context.a).toEqual('a'); + it('throw empty error from beforeSaveFile', async done => { + Parse.Cloud.beforeSaveFile(() => { + throw null; }); - const obj = new TestObject(); - await obj.save(); - const query = new Parse.Query(TestObject); - await query.find({ context: { a: 'a' } }); + try { + const file = new Parse.File('popeye.txt', [1, 2, 3], 'text/plain'); + await file.save({ useMasterKey: true }); + fail('error should have thrown'); + } catch (e) { + expect(e.code).toBe(130); + done(); + } }); }); diff --git a/spec/batch.spec.js b/spec/batch.spec.js index 172296895b..62f31f4c9c 100644 --- a/spec/batch.spec.js +++ b/spec/batch.spec.js @@ -90,7 +90,6 @@ describe('batch', () => { }); it('should handle a batch request without transaction', async done => { - await reconfigureServer(); spyOn(databaseAdapter, 'createObject').and.callThrough(); request({ @@ -129,7 +128,6 @@ describe('batch', () => { }); it('should handle a batch request with transaction = false', async done => { - await reconfigureServer(); spyOn(databaseAdapter, 'createObject').and.callThrough(); request({ @@ -366,7 +364,6 @@ describe('batch', () => { }); it('should generate separate session for each call', async () => { - await reconfigureServer(); const myObject = new Parse.Object('MyObject'); // This is important because transaction only works on pre-existing collections await myObject.save(); await myObject.destroy(); From e8515c73244278eee8d2c0e236c69504bf354a4a Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Thu, 11 Mar 2021 22:28:43 -0600 Subject: [PATCH 11/19] More reconfiguration --- spec/EnableSingleSchemaCache.spec.js | 4 + spec/FilesController.spec.js | 3 +- spec/GridFSBucketStorageAdapter.spec.js | 1 + spec/Idempotency.spec.js | 4 + spec/LdapAuth.spec.js | 444 ++++++++++++------------ spec/Logger.spec.js | 13 +- spec/Parse.Push.spec.js | 4 + spec/batch.spec.js | 3 + 8 files changed, 250 insertions(+), 226 deletions(-) diff --git a/spec/EnableSingleSchemaCache.spec.js b/spec/EnableSingleSchemaCache.spec.js index 45873c52f6..ffaf32af63 100644 --- a/spec/EnableSingleSchemaCache.spec.js +++ b/spec/EnableSingleSchemaCache.spec.js @@ -12,6 +12,10 @@ describe('Enable single schema cache', () => { }); }); + afterAll(async () => { + await reconfigureServer(); + }); + it('can perform multiple create and query operations', done => { let config = fakeRequestForConfig(); let nobody = auth.nobody(config); diff --git a/spec/FilesController.spec.js b/spec/FilesController.spec.js index 5b6a3d4ab2..070762c3da 100644 --- a/spec/FilesController.spec.js +++ b/spec/FilesController.spec.js @@ -55,7 +55,7 @@ describe('FilesController', () => { ) .then(() => new Promise(resolve => setTimeout(resolve, 200))) .then(() => logController.getLogs({ from: Date.now() - 1000, size: 1000 })) - .then(logs => { + .then(async logs => { // we get two logs here: 1. the source of the failure to save the file // and 2 the message that will be sent back to the client. @@ -66,6 +66,7 @@ describe('FilesController', () => { expect(log2.level).toBe('error'); expect(log2.code).toBe(130); + await reconfigureServer(); done(); }); }); diff --git a/spec/GridFSBucketStorageAdapter.spec.js b/spec/GridFSBucketStorageAdapter.spec.js index 92f7aae388..e2d8929fef 100644 --- a/spec/GridFSBucketStorageAdapter.spec.js +++ b/spec/GridFSBucketStorageAdapter.spec.js @@ -392,6 +392,7 @@ describe_only_db('mongo')('GridFSBucket and GridStore interop', () => { }); fileData = response.data; expect(fileData.metadata).toEqual(metadata); + await reconfigureServer(); }); it('should handle getMetadata error', async () => { diff --git a/spec/Idempotency.spec.js b/spec/Idempotency.spec.js index c2ef8665b7..d7797ff5e9 100644 --- a/spec/Idempotency.spec.js +++ b/spec/Idempotency.spec.js @@ -41,6 +41,10 @@ describe_only_db('mongo')('Idempotency', () => { ttl: 30, }); }); + + afterAll(async () => { + await reconfigureServer(); + }); // Tests it('should enforce idempotency for cloud code function', async () => { let counter = 0; diff --git a/spec/LdapAuth.spec.js b/spec/LdapAuth.spec.js index 56e583e60b..378d13fff2 100644 --- a/spec/LdapAuth.spec.js +++ b/spec/LdapAuth.spec.js @@ -4,255 +4,261 @@ const fs = require('fs'); const port = 12345; const sslport = 12346; -it('Should fail with missing options', done => { - ldap - .validateAuthData({ id: 'testuser', password: 'testpw' }) - .then(done.fail) - .catch(err => { - jequal(err.message, 'LDAP auth configuration missing'); - done(); - }); -}); - -it('Should return a resolved promise when validating the app id', done => { - ldap.validateAppId().then(done).catch(done.fail); -}); +describe('Ldap Auth', () => { + afterAll(async () => { + await reconfigureServer(); + }); -it('Should succeed with right credentials', done => { - mockLdapServer(port, 'uid=testuser, o=example').then(server => { - const options = { - suffix: 'o=example', - url: `ldap://localhost:${port}`, - dn: 'uid={{id}}, o=example', - }; + it('Should fail with missing options', done => { ldap - .validateAuthData({ id: 'testuser', password: 'secret' }, options) - .then(done) - .catch(done.fail) - .finally(() => server.close()); + .validateAuthData({ id: 'testuser', password: 'testpw' }) + .then(done.fail) + .catch(err => { + jequal(err.message, 'LDAP auth configuration missing'); + done(); + }); }); -}); -it('Should succeed with right credentials when LDAPS is used and certifcate is not checked', done => { - mockLdapServer(sslport, 'uid=testuser, o=example', false, true).then(server => { - const options = { - suffix: 'o=example', - url: `ldaps://localhost:${sslport}`, - dn: 'uid={{id}}, o=example', - tlsOptions: { rejectUnauthorized: false }, - }; - ldap - .validateAuthData({ id: 'testuser', password: 'secret' }, options) - .then(done) - .catch(done.fail) - .finally(() => server.close()); + it('Should return a resolved promise when validating the app id', done => { + ldap.validateAppId().then(done).catch(done.fail); }); -}); -it('Should succeed when LDAPS is used and the presented certificate is the expected certificate', done => { - mockLdapServer(sslport, 'uid=testuser, o=example', false, true).then(server => { - const options = { - suffix: 'o=example', - url: `ldaps://localhost:${sslport}`, - dn: 'uid={{id}}, o=example', - tlsOptions: { - ca: fs.readFileSync(__dirname + '/support/cert/cert.pem'), - rejectUnauthorized: true, - }, - }; - ldap - .validateAuthData({ id: 'testuser', password: 'secret' }, options) - .then(done) - .catch(done.fail) - .finally(() => server.close()); + it('Should succeed with right credentials', done => { + mockLdapServer(port, 'uid=testuser, o=example').then(server => { + const options = { + suffix: 'o=example', + url: `ldap://localhost:${port}`, + dn: 'uid={{id}}, o=example', + }; + ldap + .validateAuthData({ id: 'testuser', password: 'secret' }, options) + .then(done) + .catch(done.fail) + .finally(() => server.close()); + }); }); -}); -it('Should fail when LDAPS is used and the presented certificate is not the expected certificate', done => { - mockLdapServer(sslport, 'uid=testuser, o=example', false, true).then(server => { - const options = { - suffix: 'o=example', - url: `ldaps://localhost:${sslport}`, - dn: 'uid={{id}}, o=example', - tlsOptions: { - ca: fs.readFileSync(__dirname + '/support/cert/anothercert.pem'), - rejectUnauthorized: true, - }, - }; - ldap - .validateAuthData({ id: 'testuser', password: 'secret' }, options) - .then(done.fail) - .catch(err => { - jequal(err.message, 'LDAPS: Certificate mismatch'); - done(); - }) - .finally(() => server.close()); + it('Should succeed with right credentials when LDAPS is used and certifcate is not checked', done => { + mockLdapServer(sslport, 'uid=testuser, o=example', false, true).then(server => { + const options = { + suffix: 'o=example', + url: `ldaps://localhost:${sslport}`, + dn: 'uid={{id}}, o=example', + tlsOptions: { rejectUnauthorized: false }, + }; + ldap + .validateAuthData({ id: 'testuser', password: 'secret' }, options) + .then(done) + .catch(done.fail) + .finally(() => server.close()); + }); }); -}); -it('Should fail when LDAPS is used certifcate matches but credentials are wrong', done => { - mockLdapServer(sslport, 'uid=testuser, o=example', false, true).then(server => { - const options = { - suffix: 'o=example', - url: `ldaps://localhost:${sslport}`, - dn: 'uid={{id}}, o=example', - tlsOptions: { - ca: fs.readFileSync(__dirname + '/support/cert/cert.pem'), - rejectUnauthorized: true, - }, - }; - ldap - .validateAuthData({ id: 'testuser', password: 'wrong!' }, options) - .then(done.fail) - .catch(err => { - jequal(err.message, 'LDAP: Wrong username or password'); - done(); - }) - .finally(() => server.close()); + it('Should succeed when LDAPS is used and the presented certificate is the expected certificate', done => { + mockLdapServer(sslport, 'uid=testuser, o=example', false, true).then(server => { + const options = { + suffix: 'o=example', + url: `ldaps://localhost:${sslport}`, + dn: 'uid={{id}}, o=example', + tlsOptions: { + ca: fs.readFileSync(__dirname + '/support/cert/cert.pem'), + rejectUnauthorized: true, + }, + }; + ldap + .validateAuthData({ id: 'testuser', password: 'secret' }, options) + .then(done) + .catch(done.fail) + .finally(() => server.close()); + }); }); -}); -it('Should fail with wrong credentials', done => { - mockLdapServer(port, 'uid=testuser, o=example').then(server => { - const options = { - suffix: 'o=example', - url: `ldap://localhost:${port}`, - dn: 'uid={{id}}, o=example', - }; - ldap - .validateAuthData({ id: 'testuser', password: 'wrong!' }, options) - .then(done.fail) - .catch(err => { - jequal(err.message, 'LDAP: Wrong username or password'); - done(); - }) - .finally(() => server.close()); + it('Should fail when LDAPS is used and the presented certificate is not the expected certificate', done => { + mockLdapServer(sslport, 'uid=testuser, o=example', false, true).then(server => { + const options = { + suffix: 'o=example', + url: `ldaps://localhost:${sslport}`, + dn: 'uid={{id}}, o=example', + tlsOptions: { + ca: fs.readFileSync(__dirname + '/support/cert/anothercert.pem'), + rejectUnauthorized: true, + }, + }; + ldap + .validateAuthData({ id: 'testuser', password: 'secret' }, options) + .then(done.fail) + .catch(err => { + jequal(err.message, 'LDAPS: Certificate mismatch'); + done(); + }) + .finally(() => server.close()); + }); }); -}); -it('Should succeed if user is in given group', done => { - mockLdapServer(port, 'uid=testuser, o=example').then(server => { - const options = { - suffix: 'o=example', - url: `ldap://localhost:${port}`, - dn: 'uid={{id}}, o=example', - groupCn: 'powerusers', - groupFilter: '(&(uniqueMember=uid={{id}}, o=example)(objectClass=groupOfUniqueNames))', - }; + it('Should fail when LDAPS is used certifcate matches but credentials are wrong', done => { + mockLdapServer(sslport, 'uid=testuser, o=example', false, true).then(server => { + const options = { + suffix: 'o=example', + url: `ldaps://localhost:${sslport}`, + dn: 'uid={{id}}, o=example', + tlsOptions: { + ca: fs.readFileSync(__dirname + '/support/cert/cert.pem'), + rejectUnauthorized: true, + }, + }; + ldap + .validateAuthData({ id: 'testuser', password: 'wrong!' }, options) + .then(done.fail) + .catch(err => { + jequal(err.message, 'LDAP: Wrong username or password'); + done(); + }) + .finally(() => server.close()); + }); + }); - ldap - .validateAuthData({ id: 'testuser', password: 'secret' }, options) - .then(done) - .catch(done.fail) - .finally(() => server.close()); + it('Should fail with wrong credentials', done => { + mockLdapServer(port, 'uid=testuser, o=example').then(server => { + const options = { + suffix: 'o=example', + url: `ldap://localhost:${port}`, + dn: 'uid={{id}}, o=example', + }; + ldap + .validateAuthData({ id: 'testuser', password: 'wrong!' }, options) + .then(done.fail) + .catch(err => { + jequal(err.message, 'LDAP: Wrong username or password'); + done(); + }) + .finally(() => server.close()); + }); }); -}); -it('Should fail if user is not in given group', done => { - mockLdapServer(port, 'uid=testuser, o=example').then(server => { - const options = { - suffix: 'o=example', - url: `ldap://localhost:${port}`, - dn: 'uid={{id}}, o=example', - groupCn: 'groupTheUserIsNotIn', - groupFilter: '(&(uniqueMember=uid={{id}}, o=example)(objectClass=groupOfUniqueNames))', - }; + it('Should succeed if user is in given group', done => { + mockLdapServer(port, 'uid=testuser, o=example').then(server => { + const options = { + suffix: 'o=example', + url: `ldap://localhost:${port}`, + dn: 'uid={{id}}, o=example', + groupCn: 'powerusers', + groupFilter: '(&(uniqueMember=uid={{id}}, o=example)(objectClass=groupOfUniqueNames))', + }; - ldap - .validateAuthData({ id: 'testuser', password: 'secret' }, options) - .then(done.fail) - .catch(err => { - jequal(err.message, 'LDAP: User not in group'); - done(); - }) - .finally(() => server.close()); + ldap + .validateAuthData({ id: 'testuser', password: 'secret' }, options) + .then(done) + .catch(done.fail) + .finally(() => server.close()); + }); }); -}); -it('Should fail if the LDAP server does not allow searching inside the provided suffix', done => { - mockLdapServer(port, 'uid=testuser, o=example').then(server => { - const options = { - suffix: 'o=invalid', - url: `ldap://localhost:${port}`, - dn: 'uid={{id}}, o=example', - groupCn: 'powerusers', - groupFilter: '(&(uniqueMember=uid={{id}}, o=example)(objectClass=groupOfUniqueNames))', - }; + it('Should fail if user is not in given group', done => { + mockLdapServer(port, 'uid=testuser, o=example').then(server => { + const options = { + suffix: 'o=example', + url: `ldap://localhost:${port}`, + dn: 'uid={{id}}, o=example', + groupCn: 'groupTheUserIsNotIn', + groupFilter: '(&(uniqueMember=uid={{id}}, o=example)(objectClass=groupOfUniqueNames))', + }; - ldap - .validateAuthData({ id: 'testuser', password: 'secret' }, options) - .then(done.fail) - .catch(err => { - jequal(err.message, 'LDAP group search failed'); - done(); - }) - .finally(() => server.close()); + ldap + .validateAuthData({ id: 'testuser', password: 'secret' }, options) + .then(done.fail) + .catch(err => { + jequal(err.message, 'LDAP: User not in group'); + done(); + }) + .finally(() => server.close()); + }); }); -}); -it('Should fail if the LDAP server encounters an error while searching', done => { - mockLdapServer(port, 'uid=testuser, o=example', true).then(server => { - const options = { - suffix: 'o=example', - url: `ldap://localhost:${port}`, - dn: 'uid={{id}}, o=example', - groupCn: 'powerusers', - groupFilter: '(&(uniqueMember=uid={{id}}, o=example)(objectClass=groupOfUniqueNames))', - }; + it('Should fail if the LDAP server does not allow searching inside the provided suffix', done => { + mockLdapServer(port, 'uid=testuser, o=example').then(server => { + const options = { + suffix: 'o=invalid', + url: `ldap://localhost:${port}`, + dn: 'uid={{id}}, o=example', + groupCn: 'powerusers', + groupFilter: '(&(uniqueMember=uid={{id}}, o=example)(objectClass=groupOfUniqueNames))', + }; - ldap - .validateAuthData({ id: 'testuser', password: 'secret' }, options) - .then(done.fail) - .catch(err => { - jequal(err.message, 'LDAP group search failed'); - done(); - }) - .finally(() => server.close()); + ldap + .validateAuthData({ id: 'testuser', password: 'secret' }, options) + .then(done.fail) + .catch(err => { + jequal(err.message, 'LDAP group search failed'); + done(); + }) + .finally(() => server.close()); + }); }); -}); -it('Should delete the password from authData after validation', done => { - mockLdapServer(port, 'uid=testuser, o=example', true).then(server => { - const options = { - suffix: 'o=example', - url: `ldap://localhost:${port}`, - dn: 'uid={{id}}, o=example', - }; + it('Should fail if the LDAP server encounters an error while searching', done => { + mockLdapServer(port, 'uid=testuser, o=example', true).then(server => { + const options = { + suffix: 'o=example', + url: `ldap://localhost:${port}`, + dn: 'uid={{id}}, o=example', + groupCn: 'powerusers', + groupFilter: '(&(uniqueMember=uid={{id}}, o=example)(objectClass=groupOfUniqueNames))', + }; - const authData = { id: 'testuser', password: 'secret' }; + ldap + .validateAuthData({ id: 'testuser', password: 'secret' }, options) + .then(done.fail) + .catch(err => { + jequal(err.message, 'LDAP group search failed'); + done(); + }) + .finally(() => server.close()); + }); + }); - ldap - .validateAuthData(authData, options) - .then(() => { - expect(authData).toEqual({ id: 'testuser' }); - done(); - }) - .catch(done.fail) - .finally(() => server.close()); + it('Should delete the password from authData after validation', done => { + mockLdapServer(port, 'uid=testuser, o=example', true).then(server => { + const options = { + suffix: 'o=example', + url: `ldap://localhost:${port}`, + dn: 'uid={{id}}, o=example', + }; + + const authData = { id: 'testuser', password: 'secret' }; + + ldap + .validateAuthData(authData, options) + .then(() => { + expect(authData).toEqual({ id: 'testuser' }); + done(); + }) + .catch(done.fail) + .finally(() => server.close()); + }); }); -}); -it('Should not save the password in the user record after authentication', done => { - mockLdapServer(port, 'uid=testuser, o=example', true).then(server => { - const options = { - suffix: 'o=example', - url: `ldap://localhost:${port}`, - dn: 'uid={{id}}, o=example', - }; - reconfigureServer({ auth: { ldap: options } }).then(() => { - const authData = { authData: { id: 'testuser', password: 'secret' } }; - Parse.User.logInWith('ldap', authData).then(returnedUser => { - const query = new Parse.Query('User'); - query - .equalTo('objectId', returnedUser.id) - .first({ useMasterKey: true }) - .then(user => { - expect(user.get('authData')).toEqual({ ldap: { id: 'testuser' } }); - expect(user.get('authData').ldap.password).toBeUndefined(); - done(); - }) - .catch(done.fail) - .finally(() => server.close()); + it('Should not save the password in the user record after authentication', done => { + mockLdapServer(port, 'uid=testuser, o=example', true).then(server => { + const options = { + suffix: 'o=example', + url: `ldap://localhost:${port}`, + dn: 'uid={{id}}, o=example', + }; + reconfigureServer({ auth: { ldap: options } }).then(() => { + const authData = { authData: { id: 'testuser', password: 'secret' } }; + Parse.User.logInWith('ldap', authData).then(returnedUser => { + const query = new Parse.Query('User'); + query + .equalTo('objectId', returnedUser.id) + .first({ useMasterKey: true }) + .then(user => { + expect(user.get('authData')).toEqual({ ldap: { id: 'testuser' } }); + expect(user.get('authData').ldap.password).toBeUndefined(); + done(); + }) + .catch(done.fail) + .finally(() => server.close()); + }); }); }); }); diff --git a/spec/Logger.spec.js b/spec/Logger.spec.js index 6296371fb6..1f1643824a 100644 --- a/spec/Logger.spec.js +++ b/spec/Logger.spec.js @@ -32,15 +32,16 @@ describe('WinstonLogger', () => { it('should disable files logs', done => { reconfigureServer({ logsFolder: null, - }).then(() => { - const transports = logging.logger.transports; - expect(transports.length).toBe(1); - done(); - }); + }) + .then(() => { + const transports = logging.logger.transports; + expect(transports.length).toBe(1); + return reconfigureServer(); + }) + .then(done); }); it('should have a timestamp', async done => { - await reconfigureServer(); logging.logger.info('hi'); logging.logger.query({ limit: 1 }, (err, results) => { if (err) { diff --git a/spec/Parse.Push.spec.js b/spec/Parse.Push.spec.js index b0fd60e8f7..7f5edd1b7e 100644 --- a/spec/Parse.Push.spec.js +++ b/spec/Parse.Push.spec.js @@ -9,6 +9,10 @@ const delayPromise = delay => { }; describe('Parse.Push', () => { + afterAll(async () => { + await reconfigureServer(); + }); + const setup = function () { const sendToInstallationSpy = jasmine.createSpy(); diff --git a/spec/batch.spec.js b/spec/batch.spec.js index 62f31f4c9c..3fdf26531e 100644 --- a/spec/batch.spec.js +++ b/spec/batch.spec.js @@ -128,6 +128,7 @@ describe('batch', () => { }); it('should handle a batch request with transaction = false', async done => { + await reconfigureServer(); spyOn(databaseAdapter, 'createObject').and.callThrough(); request({ @@ -193,6 +194,7 @@ describe('batch', () => { }); it('should handle a batch request with transaction = true', async done => { + await reconfigureServer(); const myObject = new Parse.Object('MyObject'); // This is important because transaction only works on pre-existing collections myObject .save() @@ -364,6 +366,7 @@ describe('batch', () => { }); it('should generate separate session for each call', async () => { + await reconfigureServer(); const myObject = new Parse.Object('MyObject'); // This is important because transaction only works on pre-existing collections await myObject.save(); await myObject.destroy(); From c9388204852e012c37bd4bb7612232a01be2b0f9 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Thu, 11 Mar 2021 22:36:43 -0600 Subject: [PATCH 12/19] clean up --- spec/AccountLockoutPolicy.spec.js | 1 - spec/Analytics.spec.js | 6 +----- spec/AuthenticationAdapters.spec.js | 6 +++--- spec/Parse.Push.spec.js | 4 ---- spec/ParseAPI.spec.js | 2 -- spec/PublicAPI.spec.js | 3 +++ 6 files changed, 7 insertions(+), 15 deletions(-) diff --git a/spec/AccountLockoutPolicy.spec.js b/spec/AccountLockoutPolicy.spec.js index 472a0bb2ca..40cb240f32 100644 --- a/spec/AccountLockoutPolicy.spec.js +++ b/spec/AccountLockoutPolicy.spec.js @@ -43,7 +43,6 @@ describe('Account Lockout Policy: ', () => { afterAll(async () => { await reconfigureServer(); }); - it('account should not be locked even after failed login attempts if account lockout policy is not set', done => { reconfigureServer({ appName: 'unlimited', diff --git a/spec/Analytics.spec.js b/spec/Analytics.spec.js index ed9347fec5..049a2795c8 100644 --- a/spec/Analytics.spec.js +++ b/spec/Analytics.spec.js @@ -4,10 +4,6 @@ const analyticsAdapter = { }; describe('AnalyticsController', () => { - afterAll(async () => { - await reconfigureServer(); - }); - it('should track a simple event', done => { spyOn(analyticsAdapter, 'trackEvent').and.callThrough(); reconfigureServer({ @@ -20,7 +16,7 @@ describe('AnalyticsController', () => { }); }) .then( - async () => { + () => { expect(analyticsAdapter.trackEvent).toHaveBeenCalled(); const lastCall = analyticsAdapter.trackEvent.calls.first(); const args = lastCall.args; diff --git a/spec/AuthenticationAdapters.spec.js b/spec/AuthenticationAdapters.spec.js index 2f56fc1d05..9c6cfc6351 100644 --- a/spec/AuthenticationAdapters.spec.js +++ b/spec/AuthenticationAdapters.spec.js @@ -187,7 +187,7 @@ describe('AuthenticationProviders', function () { }); }; - it('should create user with REST API', async done => { + it('should create user with REST API', done => { createOAuthUser((error, response, body) => { expect(error).toBe(null); const b = body; @@ -214,7 +214,7 @@ describe('AuthenticationProviders', function () { }); }); - it('should only create a single user with REST API', async done => { + it('should only create a single user with REST API', done => { let objectId; createOAuthUser((error, response, body) => { expect(error).toBe(null); @@ -234,7 +234,7 @@ describe('AuthenticationProviders', function () { }); }); - it("should fail to link if session token don't match user", async done => { + it("should fail to link if session token don't match user", done => { Parse.User.signUp('myUser', 'password') .then(user => { return createOAuthUserWithSessionToken(user.getSessionToken()); diff --git a/spec/Parse.Push.spec.js b/spec/Parse.Push.spec.js index 7f5edd1b7e..b0fd60e8f7 100644 --- a/spec/Parse.Push.spec.js +++ b/spec/Parse.Push.spec.js @@ -9,10 +9,6 @@ const delayPromise = delay => { }; describe('Parse.Push', () => { - afterAll(async () => { - await reconfigureServer(); - }); - const setup = function () { const sendToInstallationSpy = jasmine.createSpy(); diff --git a/spec/ParseAPI.spec.js b/spec/ParseAPI.spec.js index 4910071618..76143e0580 100644 --- a/spec/ParseAPI.spec.js +++ b/spec/ParseAPI.spec.js @@ -68,7 +68,6 @@ describe('miscellaneous', function () { it('fail to create a duplicate username', async () => { await reconfigureServer(); - let numFailed = 0; let numCreated = 0; const p1 = request({ @@ -117,7 +116,6 @@ describe('miscellaneous', function () { it('ensure that email is uniquely indexed', async () => { await reconfigureServer(); - let numFailed = 0; let numCreated = 0; const p1 = request({ diff --git a/spec/PublicAPI.spec.js b/spec/PublicAPI.spec.js index 545662914f..3523e4f426 100644 --- a/spec/PublicAPI.spec.js +++ b/spec/PublicAPI.spec.js @@ -10,6 +10,9 @@ const request = function (url, callback) { }; describe('public API', () => { + afterAll(async () => { + await reconfigureServer(); + }); it('should return missing username error on ajax request without username provided', async () => { await reconfigureServer({ publicServerURL: 'http://localhost:8378/1', From aa7ffb800f93ba741bf12302d04aa983b2c61513 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Fri, 12 Mar 2021 00:18:01 -0600 Subject: [PATCH 13/19] properly terminate cli servers --- spec/CLI.spec.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/spec/CLI.spec.js b/spec/CLI.spec.js index 9cf42ca5ec..b038abe990 100644 --- a/spec/CLI.spec.js +++ b/spec/CLI.spec.js @@ -209,8 +209,12 @@ describe('execution', () => { const binPath = path.resolve(__dirname, '../bin/parse-server'); let childProcess; - afterEach(async () => { + afterEach(async done => { if (childProcess) { + childProcess.on('close', () => { + childProcess = undefined; + done(); + }); childProcess.kill(); } }); From 6ab6ec921a00a36e5dc6a4eb02867ff5d85546f0 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Fri, 12 Mar 2021 02:35:00 -0600 Subject: [PATCH 14/19] handle Parse.Push --- spec/CLI.spec.js | 2 +- spec/EnableSingleSchemaCache.spec.js | 1 - spec/Logger.spec.js | 2 +- spec/Parse.Push.spec.js | 644 ++++++++++----------------- spec/PushController.spec.js | 567 ++++++++--------------- 5 files changed, 433 insertions(+), 783 deletions(-) diff --git a/spec/CLI.spec.js b/spec/CLI.spec.js index b038abe990..0cd54c2e8b 100644 --- a/spec/CLI.spec.js +++ b/spec/CLI.spec.js @@ -209,7 +209,7 @@ describe('execution', () => { const binPath = path.resolve(__dirname, '../bin/parse-server'); let childProcess; - afterEach(async done => { + afterEach(done => { if (childProcess) { childProcess.on('close', () => { childProcess = undefined; diff --git a/spec/EnableSingleSchemaCache.spec.js b/spec/EnableSingleSchemaCache.spec.js index ffaf32af63..60fad123c8 100644 --- a/spec/EnableSingleSchemaCache.spec.js +++ b/spec/EnableSingleSchemaCache.spec.js @@ -15,7 +15,6 @@ describe('Enable single schema cache', () => { afterAll(async () => { await reconfigureServer(); }); - it('can perform multiple create and query operations', done => { let config = fakeRequestForConfig(); let nobody = auth.nobody(config); diff --git a/spec/Logger.spec.js b/spec/Logger.spec.js index 1f1643824a..865c5b0c5c 100644 --- a/spec/Logger.spec.js +++ b/spec/Logger.spec.js @@ -41,7 +41,7 @@ describe('WinstonLogger', () => { .then(done); }); - it('should have a timestamp', async done => { + it('should have a timestamp', done => { logging.logger.info('hi'); logging.logger.query({ limit: 1 }, (err, results) => { if (err) { diff --git a/spec/Parse.Push.spec.js b/spec/Parse.Push.spec.js index b0fd60e8f7..df2b002862 100644 --- a/spec/Parse.Push.spec.js +++ b/spec/Parse.Push.spec.js @@ -2,327 +2,230 @@ const request = require('../lib/request'); -const delayPromise = delay => { - return new Promise(resolve => { - setTimeout(resolve, delay); +const pushCompleted = async pushId => { + let result = await Parse.Push.getPushStatus(pushId); + while (!(result && result.get('status') === 'succeeded')) { + result = await Parse.Push.getPushStatus(pushId); + } +}; + +const successfulAny = function (body, installations) { + const promises = installations.map(device => { + return Promise.resolve({ + transmitted: true, + device: device, + }); }); + + return Promise.all(promises); }; -describe('Parse.Push', () => { - const setup = function () { - const sendToInstallationSpy = jasmine.createSpy(); +const provideInstallations = function (num) { + if (!num) { + num = 2; + } - const pushAdapter = { - send: function (body, installations) { - const badge = body.data.badge; - const promises = installations.map(installation => { - sendToInstallationSpy(installation); + const installations = []; + while (installations.length !== num) { + // add Android installations + const installation = new Parse.Object('_Installation'); + installation.set('installationId', 'installation_' + installations.length); + installation.set('deviceToken', 'device_token_' + installations.length); + installation.set('deviceType', 'android'); + installations.push(installation); + } - if (installation.deviceType == 'ios') { - expect(installation.badge).toEqual(badge); - expect(installation.originalBadge + 1).toEqual(installation.badge); - } else { - expect(installation.badge).toBeUndefined(); - } - return Promise.resolve({ - err: null, - device: installation, - transmitted: true, - }); - }); - return Promise.all(promises); - }, - getValidPushTypes: function () { - return ['ios', 'android']; - }, - }; + return installations; +}; - return reconfigureServer({ - appId: Parse.applicationId, - masterKey: Parse.masterKey, - serverURL: Parse.serverURL, - push: { - adapter: pushAdapter, - }, - }) - .then(() => { - 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); - } - return Parse.Object.saveAll(installations); - }) - .then(() => { - return { - sendToInstallationSpy, - }; - }) - .catch(err => { - console.error(err); +const losingAdapter = { + send: function (body, installations) { + // simulate having lost an installation before this was called + // thus invalidating our 'count' in _PushStatus + installations.pop(); - throw err; - }); - }; + return successfulAny(body, installations); + }, + getValidPushTypes: function () { + return ['android']; + }, +}; - it('should properly send push', done => { - return setup() - .then(({ sendToInstallationSpy }) => { - return Parse.Push.send( - { - where: { - deviceType: 'ios', - }, - data: { - badge: 'Increment', - alert: 'Hello world!', - }, - }, - { useMasterKey: true } - ) - .then(() => { - return delayPromise(500); - }) - .then(() => { - expect(sendToInstallationSpy.calls.count()).toEqual(10); - }); - }) - .then(() => { - done(); - }) - .catch(err => { - jfail(err); - done(); - }); - }); +const setup = function () { + const sendToInstallationSpy = jasmine.createSpy(); - it('should properly send push with lowercaseIncrement', done => { - return setup() - .then(() => { - return Parse.Push.send( - { - where: { - deviceType: 'ios', - }, - data: { - badge: 'increment', - alert: 'Hello world!', - }, - }, - { useMasterKey: true } - ); - }) - .then(() => { - return delayPromise(500); - }) - .then(() => { - done(); - }) - .catch(err => { - jfail(err); - done(); - }); - }); + const pushAdapter = { + send: function (body, installations) { + const badge = body.data.badge; + const promises = installations.map(installation => { + sendToInstallationSpy(installation); - it('should not allow clients to query _PushStatus', done => { - setup() - .then(() => - Parse.Push.send( - { - where: { - deviceType: 'ios', - }, - data: { - badge: 'increment', - alert: 'Hello world!', - }, - }, - { useMasterKey: true } - ) - ) - .then(() => delayPromise(500)) - .then(() => { - request({ - url: 'http://localhost:8378/1/classes/_PushStatus', - json: true, - headers: { - 'X-Parse-Application-Id': 'test', - }, - }).then(fail, response => { - expect(response.data.error).toEqual('unauthorized'); - done(); + if (installation.deviceType == 'ios') { + expect(installation.badge).toEqual(badge); + expect(installation.originalBadge + 1).toEqual(installation.badge); + } else { + expect(installation.badge).toBeUndefined(); + } + return Promise.resolve({ + err: null, + device: installation, + transmitted: true, }); - }) - .catch(err => { - jfail(err); - done(); }); + return Promise.all(promises); + }, + getValidPushTypes: function () { + return ['ios', 'android']; + }, + }; + + return reconfigureServer({ + appId: Parse.applicationId, + masterKey: Parse.masterKey, + serverURL: Parse.serverURL, + push: { + adapter: pushAdapter, + }, + }) + .then(() => { + 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); + } + return Parse.Object.saveAll(installations); + }) + .then(() => { + return { + sendToInstallationSpy, + }; + }); +}; + +describe('Parse.Push', () => { + it('should properly send push', async () => { + const { sendToInstallationSpy } = await setup(); + const pushStatusId = await Parse.Push.send({ + where: { + deviceType: 'ios', + }, + data: { + badge: 'Increment', + alert: 'Hello world!', + }, + }); + await pushCompleted(pushStatusId); + expect(sendToInstallationSpy.calls.count()).toEqual(10); }); - it('should allow master key to query _PushStatus', done => { - setup() - .then(() => - Parse.Push.send( - { - where: { - deviceType: 'ios', - }, - data: { - badge: 'increment', - alert: 'Hello world!', - }, - }, - { useMasterKey: true } - ) - ) - .then(() => delayPromise(500)) // put a delay as we keep writing - .then(() => { - request({ - url: 'http://localhost:8378/1/classes/_PushStatus', - json: true, - headers: { - 'X-Parse-Application-Id': 'test', - 'X-Parse-Master-Key': 'test', - }, - }).then(response => { - const body = response.data; - try { - 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!"}'); - } catch (e) { - jfail(e); - } - done(); - }); - }) - .catch(err => { - jfail(err); - done(); - }); + 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('should throw error if missing push configuration', done => { - reconfigureServer({ push: null }) - .then(() => { - return Parse.Push.send( - { - where: { - deviceType: 'ios', - }, - data: { - badge: 'increment', - alert: 'Hello world!', - }, - }, - { useMasterKey: true } - ); - }) - .then( - () => { - fail('should not succeed'); + it('should not allow clients to query _PushStatus', async () => { + await setup(); + const pushStatusId = await Parse.Push.send({ + where: { + deviceType: 'ios', + }, + data: { + badge: 'increment', + alert: 'Hello world!', + }, + }); + await pushCompleted(pushStatusId); + try { + await request({ + url: 'http://localhost:8378/1/classes/_PushStatus', + json: true, + headers: { + 'X-Parse-Application-Id': 'test', }, - err => { - expect(err.code).toEqual(Parse.Error.PUSH_MISCONFIGURED); - done(); - } - ) - .catch(err => { - jfail(err); - done(); }); + fail(); + } catch (response) { + expect(response.data.error).toEqual('unauthorized'); + } }); - const successfulAny = function (body, installations) { - const promises = installations.map(device => { - return Promise.resolve({ - transmitted: true, - device: device, - }); + 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!"}'); + }); - return Promise.all(promises); - }; - - const provideInstallations = function (num) { - if (!num) { - num = 2; - } - - const installations = []; - while (installations.length !== num) { - // add Android installations - const installation = new Parse.Object('_Installation'); - installation.set('installationId', 'installation_' + installations.length); - installation.set('deviceToken', 'device_token_' + installations.length); - installation.set('deviceType', 'android'); - installations.push(installation); + it('should throw error if missing push configuration', async () => { + await reconfigureServer({ push: null }); + try { + await Parse.Push.send({ + where: { + deviceType: 'ios', + }, + data: { + badge: 'increment', + alert: 'Hello world!', + }, + }); + fail(); + } catch (err) { + expect(err.code).toEqual(Parse.Error.PUSH_MISCONFIGURED); } - - return installations; - }; - - const losingAdapter = { - send: function (body, installations) { - // simulate having lost an installation before this was called - // thus invalidating our 'count' in _PushStatus - installations.pop(); - - return successfulAny(body, installations); - }, - getValidPushTypes: function () { - return ['android']; - }, - }; + }); /** * Verifies that _PushStatus cannot get stuck in a 'running' state * Simulates a simple push where 1 installation is removed between _PushStatus * count being set and the pushes being sent */ - it("does not get stuck with _PushStatus 'running' on 1 installation lost", done => { - reconfigureServer({ + it("does not get stuck with _PushStatus 'running' on 1 installation lost", async () => { + await reconfigureServer({ push: { adapter: losingAdapter }, - }) - .then(() => { - return Parse.Object.saveAll(provideInstallations()); - }) - .then(() => { - return Parse.Push.send( - { - data: { alert: 'We fixed our status!' }, - where: { deviceType: 'android' }, - }, - { useMasterKey: true } - ); - }) - .then(() => { - // it is enqueued so it can take time - return new Promise(resolve => { - setTimeout(() => { - resolve(); - }, 1000); - }); - }) - .then(() => { - // query for push status - const query = new Parse.Query('_PushStatus'); - return query.find({ useMasterKey: true }); - }) - .then(results => { - // verify status is NOT broken - expect(results.length).toBe(1); - const result = results[0]; - expect(result.get('status')).toEqual('succeeded'); - expect(result.get('numSent')).toEqual(1); - expect(result.get('count')).toEqual(undefined); - done(); - }); + }); + await Parse.Object.saveAll(provideInstallations()); + const pushStatusId = await Parse.Push.send({ + data: { alert: 'We fixed our status!' }, + where: { deviceType: 'android' }, + }); + await pushCompleted(pushStatusId); + const result = await Parse.Push.getPushStatus(pushStatusId); + expect(result.get('status')).toEqual('succeeded'); + expect(result.get('numSent')).toEqual(1); + expect(result.get('count')).toEqual(undefined); }); /** @@ -330,7 +233,7 @@ describe('Parse.Push', () => { * Simulates a simple push where 1 installation is added between _PushStatus * count being set and the pushes being sent */ - it("does not get stuck with _PushStatus 'running' on 1 installation added", done => { + it("does not get stuck with _PushStatus 'running' on 1 installation added", async () => { const installations = provideInstallations(); // add 1 iOS installation which we will omit & add later on @@ -340,14 +243,13 @@ describe('Parse.Push', () => { iOSInstallation.set('deviceType', 'ios'); installations.push(iOSInstallation); - reconfigureServer({ + await reconfigureServer({ push: { adapter: { send: function (body, installations) { // simulate having added an installation before this was called // thus invalidating our 'count' in _PushStatus installations.push(iOSInstallation); - return successfulAny(body, installations); }, getValidPushTypes: function () { @@ -355,41 +257,17 @@ describe('Parse.Push', () => { }, }, }, - }) - .then(() => { - return Parse.Object.saveAll(installations); - }) - .then(() => { - return Parse.Push.send( - { - data: { alert: 'We fixed our status!' }, - where: { deviceType: { $ne: 'random' } }, - }, - { useMasterKey: true } - ); - }) - .then(() => { - // it is enqueued so it can take time - return new Promise(resolve => { - setTimeout(() => { - resolve(); - }, 1000); - }); - }) - .then(() => { - // query for push status - const query = new Parse.Query('_PushStatus'); - return query.find({ useMasterKey: true }); - }) - .then(results => { - // verify status is NOT broken - expect(results.length).toBe(1); - const result = results[0]; - expect(result.get('status')).toEqual('succeeded'); - expect(result.get('numSent')).toEqual(3); - expect(result.get('count')).toEqual(undefined); - done(); - }); + }); + await Parse.Object.saveAll(installations); + const pushStatusId = await Parse.Push.send({ + data: { alert: 'We fixed our status!' }, + where: { deviceType: { $ne: 'random' } }, + }); + await pushCompleted(pushStatusId); + const result = await Parse.Push.getPushStatus(pushStatusId); + expect(result.get('status')).toEqual('succeeded'); + expect(result.get('numSent')).toEqual(3); + expect(result.get('count')).toEqual(undefined); }); /** @@ -397,48 +275,24 @@ describe('Parse.Push', () => { * Simulates an extended push, where some installations may be removed, * resulting in a non-zero count */ - it("does not get stuck with _PushStatus 'running' on many installations removed", done => { + it("does not get stuck with _PushStatus 'running' on many installations removed", async () => { const devices = 1000; const installations = provideInstallations(devices); - reconfigureServer({ + await reconfigureServer({ push: { adapter: losingAdapter }, - }) - .then(() => { - return Parse.Object.saveAll(installations); - }) - .then(() => { - return Parse.Push.send( - { - data: { alert: 'We fixed our status!' }, - where: { deviceType: 'android' }, - }, - { useMasterKey: true } - ); - }) - .then(() => { - // it is enqueued so it can take time - return new Promise(resolve => { - setTimeout(() => { - resolve(); - }, 1000); - }); - }) - .then(() => { - // query for push status - const query = new Parse.Query('_PushStatus'); - return query.find({ useMasterKey: true }); - }) - .then(results => { - // verify status is NOT broken - expect(results.length).toBe(1); - const result = results[0]; - expect(result.get('status')).toEqual('succeeded'); - // expect # less than # of batches used, assuming each batch is 100 pushes - expect(result.get('numSent')).toEqual(devices - devices / 100); - expect(result.get('count')).toEqual(undefined); - done(); - }); + }); + await Parse.Object.saveAll(installations); + const pushStatusId = await Parse.Push.send({ + data: { alert: 'We fixed our status!' }, + where: { deviceType: 'android' }, + }); + await pushCompleted(pushStatusId); + const result = await Parse.Push.getPushStatus(pushStatusId); + expect(result.get('status')).toEqual('succeeded'); + // expect # less than # of batches used, assuming each batch is 100 pushes + expect(result.get('numSent')).toEqual(devices - devices / 100); + expect(result.get('count')).toEqual(undefined); }); /** @@ -446,13 +300,12 @@ describe('Parse.Push', () => { * Simulates an extended push, where some installations may be added, * resulting in a non-zero count */ - it("does not get stuck with _PushStatus 'running' on many installations added", done => { + it("does not get stuck with _PushStatus 'running' on many installations added", async () => { const devices = 1000; const installations = provideInstallations(devices); // add 1 iOS installation which we will omit & add later on const iOSInstallations = []; - while (iOSInstallations.length !== devices / 100) { const iOSInstallation = new Parse.Object('_Installation'); iOSInstallation.set('installationId', 'installation_' + installations.length); @@ -461,15 +314,13 @@ describe('Parse.Push', () => { installations.push(iOSInstallation); iOSInstallations.push(iOSInstallation); } - - reconfigureServer({ + await reconfigureServer({ push: { adapter: { send: function (body, installations) { // simulate having added an installation before this was called // thus invalidating our 'count' in _PushStatus installations.push(iOSInstallations.pop()); - return successfulAny(body, installations); }, getValidPushTypes: function () { @@ -477,41 +328,18 @@ describe('Parse.Push', () => { }, }, }, - }) - .then(() => { - return Parse.Object.saveAll(installations); - }) - .then(() => { - return Parse.Push.send( - { - data: { alert: 'We fixed our status!' }, - where: { deviceType: { $ne: 'random' } }, - }, - { useMasterKey: true } - ); - }) - .then(() => { - // it is enqueued so it can take time - return new Promise(resolve => { - setTimeout(() => { - resolve(); - }, 1000); - }); - }) - .then(() => { - // query for push status - const query = new Parse.Query('_PushStatus'); - return query.find({ useMasterKey: true }); - }) - .then(results => { - // verify status is NOT broken - expect(results.length).toBe(1); - const result = results[0]; - expect(result.get('status')).toEqual('succeeded'); - // expect # less than # of batches used, assuming each batch is 100 pushes - expect(result.get('numSent')).toEqual(devices + devices / 100); - expect(result.get('count')).toEqual(undefined); - done(); - }); + }); + await Parse.Object.saveAll(installations); + + const pushStatusId = await Parse.Push.send({ + data: { alert: 'We fixed our status!' }, + where: { deviceType: { $ne: 'random' } }, + }); + await pushCompleted(pushStatusId); + const result = await Parse.Push.getPushStatus(pushStatusId); + expect(result.get('status')).toEqual('succeeded'); + // expect # less than # of batches used, assuming each batch is 100 pushes + expect(result.get('numSent')).toEqual(devices + devices / 100); + expect(result.get('count')).toEqual(undefined); }); }); diff --git a/spec/PushController.spec.js b/spec/PushController.spec.js index 251f242230..bd630b800f 100644 --- a/spec/PushController.spec.js +++ b/spec/PushController.spec.js @@ -26,6 +26,20 @@ const successfulIOS = function (body, installations) { return Promise.all(promises); }; +const pushCompleted = async pushId => { + let result = await Parse.Push.getPushStatus(pushId); + while (!(result && result.get('status') === 'succeeded')) { + result = await Parse.Push.getPushStatus(pushId); + } +}; + +const sendPush = (body, where, config, auth, now) => { + const pushController = new PushController(); + return new Promise((resolve, reject) => { + pushController.sendPush(body, where, config, auth, resolve, now).catch(reject); + }); +}; + describe('PushController', () => { it('can validate device type when no device type is set', done => { // Make query condition @@ -151,7 +165,7 @@ describe('PushController', () => { done(); }); - it('properly increment badges', done => { + it('properly increment badges', async () => { const pushAdapter = { send: function (body, installations) { const badge = body.data.badge; @@ -195,55 +209,28 @@ describe('PushController', () => { const auth = { isMaster: true, }; - - const pushController = new PushController(); - reconfigureServer({ + await reconfigureServer({ push: { adapter: pushAdapter }, - }) - .then(() => { - return Parse.Object.saveAll(installations); - }) - .then(() => { - return pushController.sendPush(payload, {}, config, auth); - }) - .then(() => { - // Wait so the push is completed. - return new Promise(resolve => { - setTimeout(() => { - resolve(); - }, 1000); - }); - }) - .then(() => { - // Check we actually sent 15 pushes. - const query = new Parse.Query('_PushStatus'); - return query.find({ useMasterKey: true }); - }) - .then(results => { - expect(results.length).toBe(1); - const pushStatus = results[0]; - expect(pushStatus.get('numSent')).toBe(15); - }) - .then(() => { - // Check that the installations were actually updated. - const query = new Parse.Query('_Installation'); - return query.find({ useMasterKey: true }); - }) - .then(results => { - 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')) + 1); - } - done(); - }) - .catch(err => { - jfail(err); - done(); - }); + }); + await Parse.Object.saveAll(installations); + const pushStatusId = await sendPush(payload, {}, config, auth); + await pushCompleted(pushStatusId); + + // Check we actually sent 15 pushes. + 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')) + 1); + } }); - it('properly increment badges by more than 1', done => { + it('properly increment badges by more than 1', async () => { const pushAdapter = { send: function (body, installations) { const badge = body.data.badge; @@ -287,55 +274,25 @@ describe('PushController', () => { const auth = { isMaster: true, }; - - const pushController = new PushController(); - reconfigureServer({ + await reconfigureServer({ push: { adapter: pushAdapter }, - }) - .then(() => { - return Parse.Object.saveAll(installations); - }) - .then(() => { - return pushController.sendPush(payload, {}, config, auth); - }) - .then(() => { - // Wait so the push is completed. - return new Promise(resolve => { - setTimeout(() => { - resolve(); - }, 1000); - }); - }) - .then(() => { - // Check we actually sent 15 pushes. - const query = new Parse.Query('_PushStatus'); - return query.find({ useMasterKey: true }); - }) - .then(results => { - expect(results.length).toBe(1); - const pushStatus = results[0]; - expect(pushStatus.get('numSent')).toBe(15); - }) - .then(() => { - // Check that the installations were actually updated. - const query = new Parse.Query('_Installation'); - return query.find({ useMasterKey: true }); - }) - .then(results => { - 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); - } - done(); - }) - .catch(err => { - jfail(err); - done(); - }); + }); + 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', done => { + it('properly set badges to 1', async () => { const pushAdapter = { send: function (body, installations) { const badge = body.data.badge; @@ -371,55 +328,26 @@ describe('PushController', () => { const auth = { isMaster: true, }; - - const pushController = new PushController(); - reconfigureServer({ + await reconfigureServer({ push: { adapter: pushAdapter }, - }) - .then(() => { - return Parse.Object.saveAll(installations); - }) - .then(() => { - return pushController.sendPush(payload, {}, config, auth); - }) - .then(() => { - // Wait so the push is completed. - return new Promise(resolve => { - setTimeout(() => { - resolve(); - }, 1000); - }); - }) - .then(() => { - // Check we actually sent the pushes. - const query = new Parse.Query('_PushStatus'); - return query.find({ useMasterKey: true }); - }) - .then(results => { - expect(results.length).toBe(1); - const pushStatus = results[0]; - expect(pushStatus.get('numSent')).toBe(10); - }) - .then(() => { - // Check that the installations were actually updated. - const query = new Parse.Query('_Installation'); - return query.find({ useMasterKey: true }); - }) - .then(results => { - expect(results.length).toBe(10); - for (let i = 0; i < 10; i++) { - const installation = results[i]; - expect(installation.get('badge')).toBe(1); - } - done(); - }) - .catch(err => { - jfail(err); - done(); - }); + }); + 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(10); + + // Check that the installations were actually updated. + const query = new Parse.Query('_Installation'); + const results = await query.find({ useMasterKey: true }); + expect(results.length).toBe(10); + for (let i = 0; i < 10; i++) { + const installation = results[i]; + expect(installation.get('badge')).toBe(1); + } }); - it('properly set badges to 1 with complex query #2903 #3022', done => { + it('properly set badges to 1 with complex query #2903 #3022', async () => { const payload = { data: { alert: 'Hello World!', @@ -456,46 +384,26 @@ describe('PushController', () => { const auth = { isMaster: true, }; - const pushController = new PushController(); - reconfigureServer({ - push: { - adapter: pushAdapter, - }, - }) - .then(() => { - return Parse.Object.saveAll(installations); - }) - .then(installations => { - const objectIds = installations.map(installation => { - return installation.id; - }); - const where = { - objectId: { $in: objectIds.slice(0, 5) }, - }; - return pushController.sendPush(payload, where, config, auth); - }) - .then(() => { - return new Promise(res => { - setTimeout(res, 300); - }); - }) - .then(() => { - expect(matchedInstallationsCount).toBe(5); - const query = new Parse.Query(Parse.Installation); - query.equalTo('badge', 1); - return query.find({ useMasterKey: true }); - }) - .then(installations => { - expect(installations.length).toBe(5); - done(); - }) - .catch(() => { - fail('should not fail'); - done(); - }); + 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', done => { + it('properly creates _PushStatus', async () => { const pushStatusAfterSave = { handler: function () {}, }; @@ -539,87 +447,67 @@ describe('PushController', () => { const auth = { isMaster: true, }; - const pushController = new PushController(); - reconfigureServer({ + await reconfigureServer({ push: { adapter: pushAdapter }, - }) - .then(() => { - return Parse.Object.saveAll(installations); - }) - .then(() => { - return pushController.sendPush(payload, {}, config, auth); - }) - .then(() => { - // it is enqueued so it can take time - return new Promise(resolve => { - setTimeout(() => { - resolve(); - }, 1000); - }); - }) - .then(() => { - const query = new Parse.Query('_PushStatus'); - return query.find({ useMasterKey: true }); - }) - .then(results => { - expect(results.length).toBe(1); - const result = results[0]; - expect(result.createdAt instanceof Date).toBe(true); - expect(result.updatedAt instanceof Date).toBe(true); - expect(result.id.length).toBe(10); - expect(result.get('source')).toEqual('rest'); - expect(result.get('query')).toEqual(JSON.stringify({})); - expect(typeof result.get('payload')).toEqual('string'); - expect(JSON.parse(result.get('payload'))).toEqual(payload.data); - expect(result.get('status')).toEqual('succeeded'); - expect(result.get('numSent')).toEqual(10); - expect(result.get('sentPerType')).toEqual({ - ios: 10, // 10 ios - }); - expect(result.get('numFailed')).toEqual(5); - expect(result.get('failedPerType')).toEqual({ - android: 5, // android - }); - // Try to get it without masterKey - const query = new Parse.Query('_PushStatus'); - return query.find(); - }) - .catch(error => { - expect(error.code).toBe(119); - }) - .then(() => { - function getPushStatus(callIndex) { - return spy.calls.all()[callIndex].args[0].object; - } - expect(spy).toHaveBeenCalled(); - expect(spy.calls.count()).toBe(4); - const allCalls = spy.calls.all(); - allCalls.forEach(call => { - expect(call.args.length).toBe(1); - const object = call.args[0].object; - expect(object instanceof Parse.Object).toBe(true); - }); - expect(getPushStatus(0).get('status')).toBe('pending'); - expect(getPushStatus(1).get('status')).toBe('running'); - expect(getPushStatus(1).get('numSent')).toBe(0); - expect(getPushStatus(2).get('status')).toBe('running'); - expect(getPushStatus(2).get('numSent')).toBe(10); - expect(getPushStatus(2).get('numFailed')).toBe(5); - // Those are updated from a nested . operation, this would - // not render correctly before - expect(getPushStatus(2).get('failedPerType')).toEqual({ - android: 5, - }); - expect(getPushStatus(2).get('sentPerType')).toEqual({ - ios: 10, - }); - expect(getPushStatus(3).get('status')).toBe('succeeded'); - }) - .then(done) - .catch(done.fail); + }); + await Parse.Object.saveAll(installations); + const pushStatusId = await sendPush(payload, {}, config, auth); + await pushCompleted(pushStatusId); + const result = await Parse.Push.getPushStatus(pushStatusId); + expect(result.createdAt instanceof Date).toBe(true); + expect(result.updatedAt instanceof Date).toBe(true); + expect(result.id.length).toBe(10); + expect(result.get('source')).toEqual('rest'); + expect(result.get('query')).toEqual(JSON.stringify({})); + expect(typeof result.get('payload')).toEqual('string'); + expect(JSON.parse(result.get('payload'))).toEqual(payload.data); + expect(result.get('status')).toEqual('succeeded'); + expect(result.get('numSent')).toEqual(10); + expect(result.get('sentPerType')).toEqual({ + ios: 10, // 10 ios + }); + expect(result.get('numFailed')).toEqual(5); + expect(result.get('failedPerType')).toEqual({ + android: 5, // android + }); + try { + // Try to get it without masterKey + const query = new Parse.Query('_PushStatus'); + await query.find(); + fail(); + } catch (error) { + expect(error.code).toBe(119); + } + + function getPushStatus(callIndex) { + return spy.calls.all()[callIndex].args[0].object; + } + expect(spy).toHaveBeenCalled(); + expect(spy.calls.count()).toBe(4); + const allCalls = spy.calls.all(); + allCalls.forEach(call => { + expect(call.args.length).toBe(1); + const object = call.args[0].object; + expect(object instanceof Parse.Object).toBe(true); + }); + expect(getPushStatus(0).get('status')).toBe('pending'); + expect(getPushStatus(1).get('status')).toBe('running'); + expect(getPushStatus(1).get('numSent')).toBe(0); + expect(getPushStatus(2).get('status')).toBe('running'); + expect(getPushStatus(2).get('numSent')).toBe(10); + expect(getPushStatus(2).get('numFailed')).toBe(5); + // Those are updated from a nested . operation, this would + // not render correctly before + expect(getPushStatus(2).get('failedPerType')).toEqual({ + android: 5, + }); + expect(getPushStatus(2).get('sentPerType')).toEqual({ + ios: 10, + }); + expect(getPushStatus(3).get('status')).toBe('succeeded'); }); - it('properly creates _PushStatus without serverURL', done => { + it('properly creates _PushStatus without serverURL', async () => { const pushStatusAfterSave = { handler: function () {}, }; @@ -651,36 +539,22 @@ describe('PushController', () => { const auth = { isMaster: true, }; - const pushController = new PushController(); - return installation - .save() - .then(() => { - return reconfigureServer({ - serverURL: 'http://localhost:8378/', // server with borked URL - push: { adapter: pushAdapter }, - }); - }) - .then(() => { - return pushController.sendPush(payload, {}, config, auth); - }) - .then(() => { - // it is enqueued so it can take time - return new Promise(resolve => { - setTimeout(() => { - resolve(); - }, 1000); - }); - }) - .then(() => { - Parse.serverURL = 'http://localhost:8378/1'; // GOOD url - const query = new Parse.Query('_PushStatus'); - return query.find({ useMasterKey: true }); - }) - .then(results => { - expect(results.length).toBe(1); - }) - .then(done) - .catch(done.fail); + 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 new Promise(resolve => { + setTimeout(() => { + resolve(); + }, 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', done => { @@ -733,7 +607,7 @@ describe('PushController', () => { }); }); - it('should support full RESTQuery for increment', done => { + it('should support full RESTQuery for increment', async () => { const payload = { data: { alert: 'Hello World!', @@ -759,52 +633,27 @@ describe('PushController', () => { $in: ['device_token_0', 'device_token_1', 'device_token_2'], }, }; - - const pushController = new PushController(); - reconfigureServer({ + await reconfigureServer({ push: { adapter: pushAdapter }, - }) - .then(() => { - 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); - } - return Parse.Object.saveAll(installations); - }) - .then(() => { - return pushController.sendPush(payload, where, config, auth); - }) - .then(() => { - // Wait so the push is completed. - return new Promise(resolve => { - setTimeout(() => { - resolve(); - }, 1000); - }); - }) - .then(() => { - const query = new Parse.Query('_PushStatus'); - return query.find({ useMasterKey: true }); - }) - .then(results => { - expect(results.length).toBe(1); - const pushStatus = results[0]; - expect(pushStatus.get('numSent')).toBe(3); - done(); - }) - .catch(err => { - jfail(err); - done(); - }); + }); + 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); }); - it('should support object type for alert', done => { + it('should support object type for alert', async () => { const payload = { data: { alert: { @@ -826,53 +675,27 @@ describe('PushController', () => { const auth = { isMaster: true, }; - const where = { deviceType: 'ios', }; - - const pushController = new PushController(); - reconfigureServer({ + await reconfigureServer({ push: { adapter: pushAdapter }, - }) - .then(() => { - 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); - } - return Parse.Object.saveAll(installations); - }) - .then(() => { - return pushController.sendPush(payload, where, config, auth); - }) - .then(() => { - // Wait so the push is completed. - return new Promise(resolve => { - setTimeout(() => { - resolve(); - }, 1000); - }); - }) - .then(() => { - const query = new Parse.Query('_PushStatus'); - return query.find({ useMasterKey: true }); - }) - .then(results => { - expect(results.length).toBe(1); - const pushStatus = results[0]; - expect(pushStatus.get('numSent')).toBe(5); - done(); - }) - .catch(() => { - fail('should not fail'); - done(); - }); + }); + 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(5); }); it('should flatten', () => { From d31f08c9c82a83ec9c52f69b7076aa51c1a69bb6 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Fri, 12 Mar 2021 03:13:10 -0600 Subject: [PATCH 15/19] Flaky PushController --- spec/PushController.spec.js | 43 +++++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/spec/PushController.spec.js b/spec/PushController.spec.js index bd630b800f..ea7d0da474 100644 --- a/spec/PushController.spec.js +++ b/spec/PushController.spec.js @@ -485,26 +485,37 @@ describe('PushController', () => { expect(spy).toHaveBeenCalled(); expect(spy.calls.count()).toBe(4); const allCalls = spy.calls.all(); - allCalls.forEach(call => { + let pendingCount = 0; + let runningCount = 0; + let succeedCount = 0; + allCalls.forEach((call, index) => { expect(call.args.length).toBe(1); const object = call.args[0].object; expect(object instanceof Parse.Object).toBe(true); + const pushStatus = getPushStatus(index); + if (pushStatus.get('status') === 'pending') { + pendingCount += 1; + } + if (pushStatus.get('status') === 'running') { + runningCount += 1; + } + if (pushStatus.get('status') === 'succeeded') { + succeedCount += 1; + } + if (pushStatus.get('status') === 'running' && pushStatus.get('numSent') > 0) { + expect(pushStatus.get('numSent')).toBe(10); + expect(pushStatus.get('numFailed')).toBe(5); + expect(pushStatus.get('failedPerType')).toEqual({ + android: 5, + }); + expect(pushStatus.get('sentPerType')).toEqual({ + ios: 10, + }); + } }); - expect(getPushStatus(0).get('status')).toBe('pending'); - expect(getPushStatus(1).get('status')).toBe('running'); - expect(getPushStatus(1).get('numSent')).toBe(0); - expect(getPushStatus(2).get('status')).toBe('running'); - expect(getPushStatus(2).get('numSent')).toBe(10); - expect(getPushStatus(2).get('numFailed')).toBe(5); - // Those are updated from a nested . operation, this would - // not render correctly before - expect(getPushStatus(2).get('failedPerType')).toEqual({ - android: 5, - }); - expect(getPushStatus(2).get('sentPerType')).toEqual({ - ios: 10, - }); - expect(getPushStatus(3).get('status')).toBe('succeeded'); + expect(pendingCount).toBe(1); + expect(runningCount).toBe(2); + expect(succeedCount).toBe(1); }); it('properly creates _PushStatus without serverURL', async () => { From 32f118cd9e5374662ac63869f7b96eb6d25b3d03 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Fri, 12 Mar 2021 14:24:06 -0600 Subject: [PATCH 16/19] ensure reconfigureServer when changed --- package-lock.json | 9 + package.json | 1 + spec/AccountLockoutPolicy.spec.js | 3 - spec/AdapterLoader.spec.js | 3 +- spec/AudienceRouter.spec.js | 1 - spec/CloudCode.spec.js | 14 +- spec/EnableSingleSchemaCache.spec.js | 3 - spec/FilesController.spec.js | 3 +- spec/GridFSBucketStorageAdapter.spec.js | 1 - spec/Idempotency.spec.js | 4 - spec/LdapAuth.spec.js | 4 - spec/Parse.Push.spec.js | 644 +++++++++++++++--------- spec/ParseQuery.spec.js | 2 - spec/ParseServer.spec.js | 8 +- spec/ParseServerRESTController.spec.js | 4 - spec/PostgresInitOptions.spec.js | 6 +- spec/PublicAPI.spec.js | 3 - spec/PushController.spec.js | 570 +++++++++++++-------- spec/Schema.spec.js | 1 - spec/batch.spec.js | 4 - spec/helper.js | 25 +- 21 files changed, 817 insertions(+), 496 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1c26883fa5..59e9d37f52 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7242,6 +7242,15 @@ "integrity": "sha512-nCeAiw37MIMA9w9IXso7bRaLl+c/ef3wnxsoSAlYrzS+Ot0zTG6nU8G/cIfGkqpkjX2wNaIW9RFG0TwIFnG6bA==", "dev": true }, + "jasmine-spec-reporter": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/jasmine-spec-reporter/-/jasmine-spec-reporter-6.0.0.tgz", + "integrity": "sha512-MvTOVoMxDZAftQYBApIlSfKnGMzi9cj351nXeqtnZTuXffPlbONN31+Es7F+Ke4okUeQ2xISukt4U1npfzLVrQ==", + "dev": true, + "requires": { + "colors": "1.4.0" + } + }, "jest-get-type": { "version": "25.2.6", "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-25.2.6.tgz", diff --git a/package.json b/package.json index c68305cf93..e83d2d1463 100644 --- a/package.json +++ b/package.json @@ -88,6 +88,7 @@ "form-data": "3.0.0", "husky": "4.2.5", "jasmine": "3.5.0", + "jasmine-spec-reporter": "6.0.0", "jsdoc": "3.6.3", "jsdoc-babel": "0.5.0", "lint-staged": "10.2.3", diff --git a/spec/AccountLockoutPolicy.spec.js b/spec/AccountLockoutPolicy.spec.js index 40cb240f32..43212d0e69 100644 --- a/spec/AccountLockoutPolicy.spec.js +++ b/spec/AccountLockoutPolicy.spec.js @@ -40,9 +40,6 @@ const isAccountLockoutError = function (username, password, duration, waitTime) }; describe('Account Lockout Policy: ', () => { - afterAll(async () => { - await reconfigureServer(); - }); it('account should not be locked even after failed login attempts if account lockout policy is not set', done => { reconfigureServer({ appName: 'unlimited', diff --git a/spec/AdapterLoader.spec.js b/spec/AdapterLoader.spec.js index 0491b85177..af23c146f6 100644 --- a/spec/AdapterLoader.spec.js +++ b/spec/AdapterLoader.spec.js @@ -132,12 +132,11 @@ describe('AdapterLoader', () => { expect(() => { reconfigureServer({ push: pushAdapterOptions, - }).then(async () => { + }).then(() => { const config = Config.get(Parse.applicationId); const pushAdapter = config.pushWorker.adapter; expect(pushAdapter.getValidPushTypes()).toEqual(['ios']); expect(pushAdapter.options).toEqual(pushAdapterOptions); - await reconfigureServer(); done(); }); }).not.toThrow(); diff --git a/spec/AudienceRouter.spec.js b/spec/AudienceRouter.spec.js index 6576cf8a08..208e7834b6 100644 --- a/spec/AudienceRouter.spec.js +++ b/spec/AudienceRouter.spec.js @@ -416,7 +416,6 @@ describe('AudiencesRouter', () => { } catch (e) { expect(e.data.code).toBe(107); expect(e.data.error).toBe('Could not add field lorem'); - await reconfigureServer(); } }); }); diff --git a/spec/CloudCode.spec.js b/spec/CloudCode.spec.js index f0d661df0a..c53a284273 100644 --- a/spec/CloudCode.spec.js +++ b/spec/CloudCode.spec.js @@ -23,9 +23,8 @@ describe('Cloud Code', () => { reconfigureServer({ cloud: __dirname + '/cloud/cloudCodeRelativeFile.js', }).then(() => { - Parse.Cloud.run('cloudCodeInFile', {}).then(async result => { + Parse.Cloud.run('cloudCodeInFile', {}).then(result => { expect(result).toEqual('It is possible to define cloud code in a file.'); - await reconfigureServer(); done(); }); }); @@ -33,9 +32,8 @@ describe('Cloud Code', () => { it('can load relative cloud code file', done => { reconfigureServer({ cloud: './spec/cloud/cloudCodeAbsoluteFile.js' }).then(() => { - Parse.Cloud.run('cloudCodeInFile', {}).then(async result => { + Parse.Cloud.run('cloudCodeInFile', {}).then(result => { expect(result).toEqual('It is possible to define cloud code in a file.'); - await reconfigureServer(); done(); }); }); @@ -1264,9 +1262,8 @@ describe('Cloud Code', () => { }, }) ) - .then(async response => { + .then(response => { expect(response.data.result).toEqual('second data'); - await reconfigureServer(); done(); }) .catch(done.fail); @@ -1821,7 +1818,6 @@ describe('beforeSave hooks', () => { const res = await query.find(); expect(res.length).toEqual(1); expect(res[0].get('foo')).toEqual('bar'); - await reconfigureServer(); }); }); @@ -2931,10 +2927,6 @@ describe('afterLogin hook', () => { }); describe('saveFile hooks', () => { - afterAll(async () => { - await reconfigureServer(); - }); - it('beforeSaveFile should return file that is already saved and not save anything to files adapter', async () => { await reconfigureServer({ filesAdapter: mockAdapter }); const createFileSpy = spyOn(mockAdapter, 'createFile').and.callThrough(); diff --git a/spec/EnableSingleSchemaCache.spec.js b/spec/EnableSingleSchemaCache.spec.js index 60fad123c8..45873c52f6 100644 --- a/spec/EnableSingleSchemaCache.spec.js +++ b/spec/EnableSingleSchemaCache.spec.js @@ -12,9 +12,6 @@ describe('Enable single schema cache', () => { }); }); - afterAll(async () => { - await reconfigureServer(); - }); it('can perform multiple create and query operations', done => { let config = fakeRequestForConfig(); let nobody = auth.nobody(config); diff --git a/spec/FilesController.spec.js b/spec/FilesController.spec.js index 070762c3da..5b6a3d4ab2 100644 --- a/spec/FilesController.spec.js +++ b/spec/FilesController.spec.js @@ -55,7 +55,7 @@ describe('FilesController', () => { ) .then(() => new Promise(resolve => setTimeout(resolve, 200))) .then(() => logController.getLogs({ from: Date.now() - 1000, size: 1000 })) - .then(async logs => { + .then(logs => { // we get two logs here: 1. the source of the failure to save the file // and 2 the message that will be sent back to the client. @@ -66,7 +66,6 @@ describe('FilesController', () => { expect(log2.level).toBe('error'); expect(log2.code).toBe(130); - await reconfigureServer(); done(); }); }); diff --git a/spec/GridFSBucketStorageAdapter.spec.js b/spec/GridFSBucketStorageAdapter.spec.js index e2d8929fef..92f7aae388 100644 --- a/spec/GridFSBucketStorageAdapter.spec.js +++ b/spec/GridFSBucketStorageAdapter.spec.js @@ -392,7 +392,6 @@ describe_only_db('mongo')('GridFSBucket and GridStore interop', () => { }); fileData = response.data; expect(fileData.metadata).toEqual(metadata); - await reconfigureServer(); }); it('should handle getMetadata error', async () => { diff --git a/spec/Idempotency.spec.js b/spec/Idempotency.spec.js index d7797ff5e9..c2ef8665b7 100644 --- a/spec/Idempotency.spec.js +++ b/spec/Idempotency.spec.js @@ -41,10 +41,6 @@ describe_only_db('mongo')('Idempotency', () => { ttl: 30, }); }); - - afterAll(async () => { - await reconfigureServer(); - }); // Tests it('should enforce idempotency for cloud code function', async () => { let counter = 0; diff --git a/spec/LdapAuth.spec.js b/spec/LdapAuth.spec.js index 378d13fff2..09532b217b 100644 --- a/spec/LdapAuth.spec.js +++ b/spec/LdapAuth.spec.js @@ -5,10 +5,6 @@ const port = 12345; const sslport = 12346; describe('Ldap Auth', () => { - afterAll(async () => { - await reconfigureServer(); - }); - it('Should fail with missing options', done => { ldap .validateAuthData({ id: 'testuser', password: 'testpw' }) diff --git a/spec/Parse.Push.spec.js b/spec/Parse.Push.spec.js index df2b002862..b0fd60e8f7 100644 --- a/spec/Parse.Push.spec.js +++ b/spec/Parse.Push.spec.js @@ -2,230 +2,327 @@ const request = require('../lib/request'); -const pushCompleted = async pushId => { - let result = await Parse.Push.getPushStatus(pushId); - while (!(result && result.get('status') === 'succeeded')) { - result = await Parse.Push.getPushStatus(pushId); - } -}; - -const successfulAny = function (body, installations) { - const promises = installations.map(device => { - return Promise.resolve({ - transmitted: true, - device: device, - }); +const delayPromise = delay => { + return new Promise(resolve => { + setTimeout(resolve, delay); }); - - return Promise.all(promises); }; -const provideInstallations = function (num) { - if (!num) { - num = 2; - } +describe('Parse.Push', () => { + const setup = function () { + const sendToInstallationSpy = jasmine.createSpy(); - const installations = []; - while (installations.length !== num) { - // add Android installations - const installation = new Parse.Object('_Installation'); - installation.set('installationId', 'installation_' + installations.length); - installation.set('deviceToken', 'device_token_' + installations.length); - installation.set('deviceType', 'android'); - installations.push(installation); - } + const pushAdapter = { + send: function (body, installations) { + const badge = body.data.badge; + const promises = installations.map(installation => { + sendToInstallationSpy(installation); - return installations; -}; + if (installation.deviceType == 'ios') { + expect(installation.badge).toEqual(badge); + expect(installation.originalBadge + 1).toEqual(installation.badge); + } else { + expect(installation.badge).toBeUndefined(); + } + return Promise.resolve({ + err: null, + device: installation, + transmitted: true, + }); + }); + return Promise.all(promises); + }, + getValidPushTypes: function () { + return ['ios', 'android']; + }, + }; -const losingAdapter = { - send: function (body, installations) { - // simulate having lost an installation before this was called - // thus invalidating our 'count' in _PushStatus - installations.pop(); + return reconfigureServer({ + appId: Parse.applicationId, + masterKey: Parse.masterKey, + serverURL: Parse.serverURL, + push: { + adapter: pushAdapter, + }, + }) + .then(() => { + 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); + } + return Parse.Object.saveAll(installations); + }) + .then(() => { + return { + sendToInstallationSpy, + }; + }) + .catch(err => { + console.error(err); - return successfulAny(body, installations); - }, - getValidPushTypes: function () { - return ['android']; - }, -}; + throw err; + }); + }; -const setup = function () { - const sendToInstallationSpy = jasmine.createSpy(); + it('should properly send push', done => { + return setup() + .then(({ sendToInstallationSpy }) => { + return Parse.Push.send( + { + where: { + deviceType: 'ios', + }, + data: { + badge: 'Increment', + alert: 'Hello world!', + }, + }, + { useMasterKey: true } + ) + .then(() => { + return delayPromise(500); + }) + .then(() => { + expect(sendToInstallationSpy.calls.count()).toEqual(10); + }); + }) + .then(() => { + done(); + }) + .catch(err => { + jfail(err); + done(); + }); + }); - const pushAdapter = { - send: function (body, installations) { - const badge = body.data.badge; - const promises = installations.map(installation => { - sendToInstallationSpy(installation); + it('should properly send push with lowercaseIncrement', done => { + return setup() + .then(() => { + return Parse.Push.send( + { + where: { + deviceType: 'ios', + }, + data: { + badge: 'increment', + alert: 'Hello world!', + }, + }, + { useMasterKey: true } + ); + }) + .then(() => { + return delayPromise(500); + }) + .then(() => { + done(); + }) + .catch(err => { + jfail(err); + done(); + }); + }); - if (installation.deviceType == 'ios') { - expect(installation.badge).toEqual(badge); - expect(installation.originalBadge + 1).toEqual(installation.badge); - } else { - expect(installation.badge).toBeUndefined(); - } - return Promise.resolve({ - err: null, - device: installation, - transmitted: true, + it('should not allow clients to query _PushStatus', done => { + setup() + .then(() => + Parse.Push.send( + { + where: { + deviceType: 'ios', + }, + data: { + badge: 'increment', + alert: 'Hello world!', + }, + }, + { useMasterKey: true } + ) + ) + .then(() => delayPromise(500)) + .then(() => { + request({ + url: 'http://localhost:8378/1/classes/_PushStatus', + json: true, + headers: { + 'X-Parse-Application-Id': 'test', + }, + }).then(fail, response => { + expect(response.data.error).toEqual('unauthorized'); + done(); }); + }) + .catch(err => { + jfail(err); + done(); }); - return Promise.all(promises); - }, - getValidPushTypes: function () { - return ['ios', 'android']; - }, - }; - - return reconfigureServer({ - appId: Parse.applicationId, - masterKey: Parse.masterKey, - serverURL: Parse.serverURL, - push: { - adapter: pushAdapter, - }, - }) - .then(() => { - 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); - } - return Parse.Object.saveAll(installations); - }) - .then(() => { - return { - sendToInstallationSpy, - }; - }); -}; - -describe('Parse.Push', () => { - it('should properly send push', async () => { - const { sendToInstallationSpy } = await setup(); - const pushStatusId = await Parse.Push.send({ - where: { - deviceType: 'ios', - }, - data: { - badge: 'Increment', - alert: 'Hello world!', - }, - }); - await pushCompleted(pushStatusId); - 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('should allow master key to query _PushStatus', done => { + setup() + .then(() => + Parse.Push.send( + { + where: { + deviceType: 'ios', + }, + data: { + badge: 'increment', + alert: 'Hello world!', + }, + }, + { useMasterKey: true } + ) + ) + .then(() => delayPromise(500)) // put a delay as we keep writing + .then(() => { + request({ + url: 'http://localhost:8378/1/classes/_PushStatus', + json: true, + headers: { + 'X-Parse-Application-Id': 'test', + 'X-Parse-Master-Key': 'test', + }, + }).then(response => { + const body = response.data; + try { + 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!"}'); + } catch (e) { + jfail(e); + } + done(); + }); + }) + .catch(err => { + jfail(err); + done(); + }); }); - it('should not allow clients to query _PushStatus', async () => { - await setup(); - const pushStatusId = await Parse.Push.send({ - where: { - deviceType: 'ios', - }, - data: { - badge: 'increment', - alert: 'Hello world!', - }, - }); - await pushCompleted(pushStatusId); - try { - await request({ - url: 'http://localhost:8378/1/classes/_PushStatus', - json: true, - headers: { - 'X-Parse-Application-Id': 'test', + it('should throw error if missing push configuration', done => { + reconfigureServer({ push: null }) + .then(() => { + return Parse.Push.send( + { + where: { + deviceType: 'ios', + }, + data: { + badge: 'increment', + alert: 'Hello world!', + }, + }, + { useMasterKey: true } + ); + }) + .then( + () => { + fail('should not succeed'); }, + err => { + expect(err.code).toEqual(Parse.Error.PUSH_MISCONFIGURED); + done(); + } + ) + .catch(err => { + jfail(err); + done(); }); - fail(); - } catch (response) { - expect(response.data.error).toEqual('unauthorized'); - } }); - 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 successfulAny = function (body, installations) { + const promises = installations.map(device => { + return Promise.resolve({ + transmitted: true, + device: device, + }); }); - 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 }); - try { - await Parse.Push.send({ - where: { - deviceType: 'ios', - }, - data: { - badge: 'increment', - alert: 'Hello world!', - }, - }); - fail(); - } catch (err) { - expect(err.code).toEqual(Parse.Error.PUSH_MISCONFIGURED); + return Promise.all(promises); + }; + + const provideInstallations = function (num) { + if (!num) { + num = 2; } - }); + + const installations = []; + while (installations.length !== num) { + // add Android installations + const installation = new Parse.Object('_Installation'); + installation.set('installationId', 'installation_' + installations.length); + installation.set('deviceToken', 'device_token_' + installations.length); + installation.set('deviceType', 'android'); + installations.push(installation); + } + + return installations; + }; + + const losingAdapter = { + send: function (body, installations) { + // simulate having lost an installation before this was called + // thus invalidating our 'count' in _PushStatus + installations.pop(); + + return successfulAny(body, installations); + }, + getValidPushTypes: function () { + return ['android']; + }, + }; /** * Verifies that _PushStatus cannot get stuck in a 'running' state * Simulates a simple push where 1 installation is removed between _PushStatus * count being set and the pushes being sent */ - it("does not get stuck with _PushStatus 'running' on 1 installation lost", async () => { - await reconfigureServer({ + it("does not get stuck with _PushStatus 'running' on 1 installation lost", done => { + reconfigureServer({ push: { adapter: losingAdapter }, - }); - await Parse.Object.saveAll(provideInstallations()); - const pushStatusId = await Parse.Push.send({ - data: { alert: 'We fixed our status!' }, - where: { deviceType: 'android' }, - }); - await pushCompleted(pushStatusId); - const result = await Parse.Push.getPushStatus(pushStatusId); - expect(result.get('status')).toEqual('succeeded'); - expect(result.get('numSent')).toEqual(1); - expect(result.get('count')).toEqual(undefined); + }) + .then(() => { + return Parse.Object.saveAll(provideInstallations()); + }) + .then(() => { + return Parse.Push.send( + { + data: { alert: 'We fixed our status!' }, + where: { deviceType: 'android' }, + }, + { useMasterKey: true } + ); + }) + .then(() => { + // it is enqueued so it can take time + return new Promise(resolve => { + setTimeout(() => { + resolve(); + }, 1000); + }); + }) + .then(() => { + // query for push status + const query = new Parse.Query('_PushStatus'); + return query.find({ useMasterKey: true }); + }) + .then(results => { + // verify status is NOT broken + expect(results.length).toBe(1); + const result = results[0]; + expect(result.get('status')).toEqual('succeeded'); + expect(result.get('numSent')).toEqual(1); + expect(result.get('count')).toEqual(undefined); + done(); + }); }); /** @@ -233,7 +330,7 @@ describe('Parse.Push', () => { * Simulates a simple push where 1 installation is added between _PushStatus * count being set and the pushes being sent */ - it("does not get stuck with _PushStatus 'running' on 1 installation added", async () => { + it("does not get stuck with _PushStatus 'running' on 1 installation added", done => { const installations = provideInstallations(); // add 1 iOS installation which we will omit & add later on @@ -243,13 +340,14 @@ describe('Parse.Push', () => { iOSInstallation.set('deviceType', 'ios'); installations.push(iOSInstallation); - await reconfigureServer({ + reconfigureServer({ push: { adapter: { send: function (body, installations) { // simulate having added an installation before this was called // thus invalidating our 'count' in _PushStatus installations.push(iOSInstallation); + return successfulAny(body, installations); }, getValidPushTypes: function () { @@ -257,17 +355,41 @@ describe('Parse.Push', () => { }, }, }, - }); - await Parse.Object.saveAll(installations); - const pushStatusId = await Parse.Push.send({ - data: { alert: 'We fixed our status!' }, - where: { deviceType: { $ne: 'random' } }, - }); - await pushCompleted(pushStatusId); - const result = await Parse.Push.getPushStatus(pushStatusId); - expect(result.get('status')).toEqual('succeeded'); - expect(result.get('numSent')).toEqual(3); - expect(result.get('count')).toEqual(undefined); + }) + .then(() => { + return Parse.Object.saveAll(installations); + }) + .then(() => { + return Parse.Push.send( + { + data: { alert: 'We fixed our status!' }, + where: { deviceType: { $ne: 'random' } }, + }, + { useMasterKey: true } + ); + }) + .then(() => { + // it is enqueued so it can take time + return new Promise(resolve => { + setTimeout(() => { + resolve(); + }, 1000); + }); + }) + .then(() => { + // query for push status + const query = new Parse.Query('_PushStatus'); + return query.find({ useMasterKey: true }); + }) + .then(results => { + // verify status is NOT broken + expect(results.length).toBe(1); + const result = results[0]; + expect(result.get('status')).toEqual('succeeded'); + expect(result.get('numSent')).toEqual(3); + expect(result.get('count')).toEqual(undefined); + done(); + }); }); /** @@ -275,24 +397,48 @@ describe('Parse.Push', () => { * Simulates an extended push, where some installations may be removed, * resulting in a non-zero count */ - it("does not get stuck with _PushStatus 'running' on many installations removed", async () => { + it("does not get stuck with _PushStatus 'running' on many installations removed", done => { const devices = 1000; const installations = provideInstallations(devices); - await reconfigureServer({ + reconfigureServer({ push: { adapter: losingAdapter }, - }); - await Parse.Object.saveAll(installations); - const pushStatusId = await Parse.Push.send({ - data: { alert: 'We fixed our status!' }, - where: { deviceType: 'android' }, - }); - await pushCompleted(pushStatusId); - const result = await Parse.Push.getPushStatus(pushStatusId); - expect(result.get('status')).toEqual('succeeded'); - // expect # less than # of batches used, assuming each batch is 100 pushes - expect(result.get('numSent')).toEqual(devices - devices / 100); - expect(result.get('count')).toEqual(undefined); + }) + .then(() => { + return Parse.Object.saveAll(installations); + }) + .then(() => { + return Parse.Push.send( + { + data: { alert: 'We fixed our status!' }, + where: { deviceType: 'android' }, + }, + { useMasterKey: true } + ); + }) + .then(() => { + // it is enqueued so it can take time + return new Promise(resolve => { + setTimeout(() => { + resolve(); + }, 1000); + }); + }) + .then(() => { + // query for push status + const query = new Parse.Query('_PushStatus'); + return query.find({ useMasterKey: true }); + }) + .then(results => { + // verify status is NOT broken + expect(results.length).toBe(1); + const result = results[0]; + expect(result.get('status')).toEqual('succeeded'); + // expect # less than # of batches used, assuming each batch is 100 pushes + expect(result.get('numSent')).toEqual(devices - devices / 100); + expect(result.get('count')).toEqual(undefined); + done(); + }); }); /** @@ -300,12 +446,13 @@ describe('Parse.Push', () => { * Simulates an extended push, where some installations may be added, * resulting in a non-zero count */ - it("does not get stuck with _PushStatus 'running' on many installations added", async () => { + it("does not get stuck with _PushStatus 'running' on many installations added", done => { const devices = 1000; const installations = provideInstallations(devices); // add 1 iOS installation which we will omit & add later on const iOSInstallations = []; + while (iOSInstallations.length !== devices / 100) { const iOSInstallation = new Parse.Object('_Installation'); iOSInstallation.set('installationId', 'installation_' + installations.length); @@ -314,13 +461,15 @@ describe('Parse.Push', () => { installations.push(iOSInstallation); iOSInstallations.push(iOSInstallation); } - await reconfigureServer({ + + reconfigureServer({ push: { adapter: { send: function (body, installations) { // simulate having added an installation before this was called // thus invalidating our 'count' in _PushStatus installations.push(iOSInstallations.pop()); + return successfulAny(body, installations); }, getValidPushTypes: function () { @@ -328,18 +477,41 @@ describe('Parse.Push', () => { }, }, }, - }); - await Parse.Object.saveAll(installations); - - const pushStatusId = await Parse.Push.send({ - data: { alert: 'We fixed our status!' }, - where: { deviceType: { $ne: 'random' } }, - }); - await pushCompleted(pushStatusId); - const result = await Parse.Push.getPushStatus(pushStatusId); - expect(result.get('status')).toEqual('succeeded'); - // expect # less than # of batches used, assuming each batch is 100 pushes - expect(result.get('numSent')).toEqual(devices + devices / 100); - expect(result.get('count')).toEqual(undefined); + }) + .then(() => { + return Parse.Object.saveAll(installations); + }) + .then(() => { + return Parse.Push.send( + { + data: { alert: 'We fixed our status!' }, + where: { deviceType: { $ne: 'random' } }, + }, + { useMasterKey: true } + ); + }) + .then(() => { + // it is enqueued so it can take time + return new Promise(resolve => { + setTimeout(() => { + resolve(); + }, 1000); + }); + }) + .then(() => { + // query for push status + const query = new Parse.Query('_PushStatus'); + return query.find({ useMasterKey: true }); + }) + .then(results => { + // verify status is NOT broken + expect(results.length).toBe(1); + const result = results[0]; + expect(result.get('status')).toEqual('succeeded'); + // expect # less than # of batches used, assuming each batch is 100 pushes + expect(result.get('numSent')).toEqual(devices + devices / 100); + expect(result.get('count')).toEqual(undefined); + done(); + }); }); }); diff --git a/spec/ParseQuery.spec.js b/spec/ParseQuery.spec.js index 0a3e5e1d81..9825fc2d95 100644 --- a/spec/ParseQuery.spec.js +++ b/spec/ParseQuery.spec.js @@ -288,7 +288,6 @@ describe('Parse.Query testing', () => { query.limit(1); const results = await query.find(); equal(results.length, 1); - await reconfigureServer(); }); it('query with limit exceeding maxlimit', async () => { @@ -300,7 +299,6 @@ describe('Parse.Query testing', () => { query.limit(2); const results = await query.find(); equal(results.length, 1); - await reconfigureServer(); }); it('containedIn object array queries', function (done) { diff --git a/spec/ParseServer.spec.js b/spec/ParseServer.spec.js index be68fdb506..f0137c55ad 100644 --- a/spec/ParseServer.spec.js +++ b/spec/ParseServer.spec.js @@ -25,10 +25,8 @@ describe('Server Url Checks', () => { }); afterAll(done => { - server.close(async () => { - await reconfigureServer(); - done(); - }); + Parse.serverURL = 'http://localhost:8378/1'; + server.close(done); }); it('validate good server url', done => { @@ -37,6 +35,7 @@ describe('Server Url Checks', () => { if (!result) { done.fail('Did not pass valid url'); } + Parse.serverURL = 'http://localhost:8378/1'; done(); }); }); @@ -48,6 +47,7 @@ describe('Server Url Checks', () => { if (result) { done.fail('Did not mark invalid url'); } + Parse.serverURL = 'http://localhost:8378/1'; done(); }); }); diff --git a/spec/ParseServerRESTController.spec.js b/spec/ParseServerRESTController.spec.js index 2f3695d136..2a84edaa4e 100644 --- a/spec/ParseServerRESTController.spec.js +++ b/spec/ParseServerRESTController.spec.js @@ -187,10 +187,6 @@ describe('ParseServerRESTController', () => { } }); - afterAll(async () => { - await reconfigureServer(); - }); - it('should handle a batch request with transaction = true', done => { const myObject = new Parse.Object('MyObject'); // This is important because transaction only works on pre-existing collections myObject diff --git a/spec/PostgresInitOptions.spec.js b/spec/PostgresInitOptions.spec.js index 183cef3542..2e9b0306f1 100644 --- a/spec/PostgresInitOptions.spec.js +++ b/spec/PostgresInitOptions.spec.js @@ -52,10 +52,7 @@ describe_only_db('postgres')('Postgres database init options', () => { afterAll(done => { if (server) { - server.close(async () => { - await reconfigureServer(); - done(); - }); + server.close(done); } }); @@ -85,6 +82,7 @@ describe_only_db('postgres')('Postgres database init options', () => { collectionPrefix: 'test_', databaseOptions: databaseOptions2, }); + createParseServer({ databaseAdapter: adapter }).then(done.fail, () => done()); }); }); diff --git a/spec/PublicAPI.spec.js b/spec/PublicAPI.spec.js index 3523e4f426..545662914f 100644 --- a/spec/PublicAPI.spec.js +++ b/spec/PublicAPI.spec.js @@ -10,9 +10,6 @@ const request = function (url, callback) { }; describe('public API', () => { - afterAll(async () => { - await reconfigureServer(); - }); it('should return missing username error on ajax request without username provided', async () => { await reconfigureServer({ publicServerURL: 'http://localhost:8378/1', diff --git a/spec/PushController.spec.js b/spec/PushController.spec.js index ea7d0da474..251f242230 100644 --- a/spec/PushController.spec.js +++ b/spec/PushController.spec.js @@ -26,20 +26,6 @@ const successfulIOS = function (body, installations) { return Promise.all(promises); }; -const pushCompleted = async pushId => { - let result = await Parse.Push.getPushStatus(pushId); - while (!(result && result.get('status') === 'succeeded')) { - result = await Parse.Push.getPushStatus(pushId); - } -}; - -const sendPush = (body, where, config, auth, now) => { - const pushController = new PushController(); - return new Promise((resolve, reject) => { - pushController.sendPush(body, where, config, auth, resolve, now).catch(reject); - }); -}; - describe('PushController', () => { it('can validate device type when no device type is set', done => { // Make query condition @@ -165,7 +151,7 @@ describe('PushController', () => { done(); }); - it('properly increment badges', async () => { + it('properly increment badges', done => { const pushAdapter = { send: function (body, installations) { const badge = body.data.badge; @@ -209,28 +195,55 @@ describe('PushController', () => { const auth = { isMaster: true, }; - await reconfigureServer({ + + const pushController = new PushController(); + reconfigureServer({ push: { adapter: pushAdapter }, - }); - await Parse.Object.saveAll(installations); - const pushStatusId = await sendPush(payload, {}, config, auth); - await pushCompleted(pushStatusId); - - // Check we actually sent 15 pushes. - 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')) + 1); - } + }) + .then(() => { + return Parse.Object.saveAll(installations); + }) + .then(() => { + return pushController.sendPush(payload, {}, config, auth); + }) + .then(() => { + // Wait so the push is completed. + return new Promise(resolve => { + setTimeout(() => { + resolve(); + }, 1000); + }); + }) + .then(() => { + // Check we actually sent 15 pushes. + const query = new Parse.Query('_PushStatus'); + return query.find({ useMasterKey: true }); + }) + .then(results => { + expect(results.length).toBe(1); + const pushStatus = results[0]; + expect(pushStatus.get('numSent')).toBe(15); + }) + .then(() => { + // Check that the installations were actually updated. + const query = new Parse.Query('_Installation'); + return query.find({ useMasterKey: true }); + }) + .then(results => { + 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')) + 1); + } + done(); + }) + .catch(err => { + jfail(err); + done(); + }); }); - it('properly increment badges by more than 1', async () => { + it('properly increment badges by more than 1', done => { const pushAdapter = { send: function (body, installations) { const badge = body.data.badge; @@ -274,25 +287,55 @@ describe('PushController', () => { const auth = { isMaster: true, }; - await reconfigureServer({ + + const pushController = new PushController(); + 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); - } + }) + .then(() => { + return Parse.Object.saveAll(installations); + }) + .then(() => { + return pushController.sendPush(payload, {}, config, auth); + }) + .then(() => { + // Wait so the push is completed. + return new Promise(resolve => { + setTimeout(() => { + resolve(); + }, 1000); + }); + }) + .then(() => { + // Check we actually sent 15 pushes. + const query = new Parse.Query('_PushStatus'); + return query.find({ useMasterKey: true }); + }) + .then(results => { + expect(results.length).toBe(1); + const pushStatus = results[0]; + expect(pushStatus.get('numSent')).toBe(15); + }) + .then(() => { + // Check that the installations were actually updated. + const query = new Parse.Query('_Installation'); + return query.find({ useMasterKey: true }); + }) + .then(results => { + 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); + } + done(); + }) + .catch(err => { + jfail(err); + done(); + }); }); - it('properly set badges to 1', async () => { + it('properly set badges to 1', done => { const pushAdapter = { send: function (body, installations) { const badge = body.data.badge; @@ -328,26 +371,55 @@ describe('PushController', () => { const auth = { isMaster: true, }; - await reconfigureServer({ + + const pushController = new PushController(); + 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(10); - - // Check that the installations were actually updated. - const query = new Parse.Query('_Installation'); - const results = await query.find({ useMasterKey: true }); - expect(results.length).toBe(10); - for (let i = 0; i < 10; i++) { - const installation = results[i]; - expect(installation.get('badge')).toBe(1); - } + }) + .then(() => { + return Parse.Object.saveAll(installations); + }) + .then(() => { + return pushController.sendPush(payload, {}, config, auth); + }) + .then(() => { + // Wait so the push is completed. + return new Promise(resolve => { + setTimeout(() => { + resolve(); + }, 1000); + }); + }) + .then(() => { + // Check we actually sent the pushes. + const query = new Parse.Query('_PushStatus'); + return query.find({ useMasterKey: true }); + }) + .then(results => { + expect(results.length).toBe(1); + const pushStatus = results[0]; + expect(pushStatus.get('numSent')).toBe(10); + }) + .then(() => { + // Check that the installations were actually updated. + const query = new Parse.Query('_Installation'); + return query.find({ useMasterKey: true }); + }) + .then(results => { + expect(results.length).toBe(10); + for (let i = 0; i < 10; i++) { + const installation = results[i]; + expect(installation.get('badge')).toBe(1); + } + done(); + }) + .catch(err => { + jfail(err); + done(); + }); }); - it('properly set badges to 1 with complex query #2903 #3022', async () => { + it('properly set badges to 1 with complex query #2903 #3022', done => { const payload = { data: { alert: 'Hello World!', @@ -384,26 +456,46 @@ describe('PushController', () => { 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 pushController = new PushController(); + reconfigureServer({ + push: { + adapter: pushAdapter, + }, + }) + .then(() => { + return Parse.Object.saveAll(installations); + }) + .then(installations => { + const objectIds = installations.map(installation => { + return installation.id; + }); + const where = { + objectId: { $in: objectIds.slice(0, 5) }, + }; + return pushController.sendPush(payload, where, config, auth); + }) + .then(() => { + return new Promise(res => { + setTimeout(res, 300); + }); + }) + .then(() => { + expect(matchedInstallationsCount).toBe(5); + const query = new Parse.Query(Parse.Installation); + query.equalTo('badge', 1); + return query.find({ useMasterKey: true }); + }) + .then(installations => { + expect(installations.length).toBe(5); + done(); + }) + .catch(() => { + fail('should not fail'); + done(); + }); }); - it('properly creates _PushStatus', async () => { + it('properly creates _PushStatus', done => { const pushStatusAfterSave = { handler: function () {}, }; @@ -447,78 +539,87 @@ describe('PushController', () => { const auth = { isMaster: true, }; - await reconfigureServer({ + const pushController = new PushController(); + reconfigureServer({ push: { adapter: pushAdapter }, - }); - await Parse.Object.saveAll(installations); - const pushStatusId = await sendPush(payload, {}, config, auth); - await pushCompleted(pushStatusId); - const result = await Parse.Push.getPushStatus(pushStatusId); - expect(result.createdAt instanceof Date).toBe(true); - expect(result.updatedAt instanceof Date).toBe(true); - expect(result.id.length).toBe(10); - expect(result.get('source')).toEqual('rest'); - expect(result.get('query')).toEqual(JSON.stringify({})); - expect(typeof result.get('payload')).toEqual('string'); - expect(JSON.parse(result.get('payload'))).toEqual(payload.data); - expect(result.get('status')).toEqual('succeeded'); - expect(result.get('numSent')).toEqual(10); - expect(result.get('sentPerType')).toEqual({ - ios: 10, // 10 ios - }); - expect(result.get('numFailed')).toEqual(5); - expect(result.get('failedPerType')).toEqual({ - android: 5, // android - }); - try { - // Try to get it without masterKey - const query = new Parse.Query('_PushStatus'); - await query.find(); - fail(); - } catch (error) { - expect(error.code).toBe(119); - } - - function getPushStatus(callIndex) { - return spy.calls.all()[callIndex].args[0].object; - } - expect(spy).toHaveBeenCalled(); - expect(spy.calls.count()).toBe(4); - const allCalls = spy.calls.all(); - let pendingCount = 0; - let runningCount = 0; - let succeedCount = 0; - allCalls.forEach((call, index) => { - expect(call.args.length).toBe(1); - const object = call.args[0].object; - expect(object instanceof Parse.Object).toBe(true); - const pushStatus = getPushStatus(index); - if (pushStatus.get('status') === 'pending') { - pendingCount += 1; - } - if (pushStatus.get('status') === 'running') { - runningCount += 1; - } - if (pushStatus.get('status') === 'succeeded') { - succeedCount += 1; - } - if (pushStatus.get('status') === 'running' && pushStatus.get('numSent') > 0) { - expect(pushStatus.get('numSent')).toBe(10); - expect(pushStatus.get('numFailed')).toBe(5); - expect(pushStatus.get('failedPerType')).toEqual({ + }) + .then(() => { + return Parse.Object.saveAll(installations); + }) + .then(() => { + return pushController.sendPush(payload, {}, config, auth); + }) + .then(() => { + // it is enqueued so it can take time + return new Promise(resolve => { + setTimeout(() => { + resolve(); + }, 1000); + }); + }) + .then(() => { + const query = new Parse.Query('_PushStatus'); + return query.find({ useMasterKey: true }); + }) + .then(results => { + expect(results.length).toBe(1); + const result = results[0]; + expect(result.createdAt instanceof Date).toBe(true); + expect(result.updatedAt instanceof Date).toBe(true); + expect(result.id.length).toBe(10); + expect(result.get('source')).toEqual('rest'); + expect(result.get('query')).toEqual(JSON.stringify({})); + expect(typeof result.get('payload')).toEqual('string'); + expect(JSON.parse(result.get('payload'))).toEqual(payload.data); + expect(result.get('status')).toEqual('succeeded'); + expect(result.get('numSent')).toEqual(10); + expect(result.get('sentPerType')).toEqual({ + ios: 10, // 10 ios + }); + expect(result.get('numFailed')).toEqual(5); + expect(result.get('failedPerType')).toEqual({ + android: 5, // android + }); + // Try to get it without masterKey + const query = new Parse.Query('_PushStatus'); + return query.find(); + }) + .catch(error => { + expect(error.code).toBe(119); + }) + .then(() => { + function getPushStatus(callIndex) { + return spy.calls.all()[callIndex].args[0].object; + } + expect(spy).toHaveBeenCalled(); + expect(spy.calls.count()).toBe(4); + const allCalls = spy.calls.all(); + allCalls.forEach(call => { + expect(call.args.length).toBe(1); + const object = call.args[0].object; + expect(object instanceof Parse.Object).toBe(true); + }); + expect(getPushStatus(0).get('status')).toBe('pending'); + expect(getPushStatus(1).get('status')).toBe('running'); + expect(getPushStatus(1).get('numSent')).toBe(0); + expect(getPushStatus(2).get('status')).toBe('running'); + expect(getPushStatus(2).get('numSent')).toBe(10); + expect(getPushStatus(2).get('numFailed')).toBe(5); + // Those are updated from a nested . operation, this would + // not render correctly before + expect(getPushStatus(2).get('failedPerType')).toEqual({ android: 5, }); - expect(pushStatus.get('sentPerType')).toEqual({ + expect(getPushStatus(2).get('sentPerType')).toEqual({ ios: 10, }); - } - }); - expect(pendingCount).toBe(1); - expect(runningCount).toBe(2); - expect(succeedCount).toBe(1); + expect(getPushStatus(3).get('status')).toBe('succeeded'); + }) + .then(done) + .catch(done.fail); }); - it('properly creates _PushStatus without serverURL', async () => { + it('properly creates _PushStatus without serverURL', done => { const pushStatusAfterSave = { handler: function () {}, }; @@ -550,22 +651,36 @@ describe('PushController', () => { 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 new Promise(resolve => { - setTimeout(() => { - resolve(); - }, 1000); - }); - Parse.serverURL = 'http://localhost:8378/1'; // GOOD url - const result = await Parse.Push.getPushStatus(pushStatusId); - expect(result).toBeDefined(); - await pushCompleted(pushStatusId); + const pushController = new PushController(); + return installation + .save() + .then(() => { + return reconfigureServer({ + serverURL: 'http://localhost:8378/', // server with borked URL + push: { adapter: pushAdapter }, + }); + }) + .then(() => { + return pushController.sendPush(payload, {}, config, auth); + }) + .then(() => { + // it is enqueued so it can take time + return new Promise(resolve => { + setTimeout(() => { + resolve(); + }, 1000); + }); + }) + .then(() => { + Parse.serverURL = 'http://localhost:8378/1'; // GOOD url + const query = new Parse.Query('_PushStatus'); + return query.find({ useMasterKey: true }); + }) + .then(results => { + expect(results.length).toBe(1); + }) + .then(done) + .catch(done.fail); }); it('should properly report failures in _PushStatus', done => { @@ -618,7 +733,7 @@ describe('PushController', () => { }); }); - it('should support full RESTQuery for increment', async () => { + it('should support full RESTQuery for increment', done => { const payload = { data: { alert: 'Hello World!', @@ -644,27 +759,52 @@ describe('PushController', () => { $in: ['device_token_0', 'device_token_1', 'device_token_2'], }, }; - await reconfigureServer({ + + const pushController = new PushController(); + 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); + }) + .then(() => { + 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); + } + return Parse.Object.saveAll(installations); + }) + .then(() => { + return pushController.sendPush(payload, where, config, auth); + }) + .then(() => { + // Wait so the push is completed. + return new Promise(resolve => { + setTimeout(() => { + resolve(); + }, 1000); + }); + }) + .then(() => { + const query = new Parse.Query('_PushStatus'); + return query.find({ useMasterKey: true }); + }) + .then(results => { + expect(results.length).toBe(1); + const pushStatus = results[0]; + expect(pushStatus.get('numSent')).toBe(3); + done(); + }) + .catch(err => { + jfail(err); + done(); + }); }); - it('should support object type for alert', async () => { + it('should support object type for alert', done => { const payload = { data: { alert: { @@ -686,27 +826,53 @@ describe('PushController', () => { const auth = { isMaster: true, }; + const where = { deviceType: 'ios', }; - await reconfigureServer({ + + const pushController = new PushController(); + 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(5); + }) + .then(() => { + 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); + } + return Parse.Object.saveAll(installations); + }) + .then(() => { + return pushController.sendPush(payload, where, config, auth); + }) + .then(() => { + // Wait so the push is completed. + return new Promise(resolve => { + setTimeout(() => { + resolve(); + }, 1000); + }); + }) + .then(() => { + const query = new Parse.Query('_PushStatus'); + return query.find({ useMasterKey: true }); + }) + .then(results => { + expect(results.length).toBe(1); + const pushStatus = results[0]; + expect(pushStatus.get('numSent')).toBe(5); + done(); + }) + .catch(() => { + fail('should not fail'); + done(); + }); }); it('should flatten', () => { diff --git a/spec/Schema.spec.js b/spec/Schema.spec.js index 3044d3478d..21378c1100 100644 --- a/spec/Schema.spec.js +++ b/spec/Schema.spec.js @@ -1313,7 +1313,6 @@ describe('SchemaController', () => { it('properly handles volatile _Schemas', async done => { await reconfigureServer(); - function validateSchemaStructure(schema) { expect(Object.prototype.hasOwnProperty.call(schema, 'className')).toBe(true); expect(Object.prototype.hasOwnProperty.call(schema, 'fields')).toBe(true); diff --git a/spec/batch.spec.js b/spec/batch.spec.js index 3fdf26531e..a1041e4ccc 100644 --- a/spec/batch.spec.js +++ b/spec/batch.spec.js @@ -189,10 +189,6 @@ describe('batch', () => { } }); - afterAll(async () => { - await reconfigureServer(); - }); - it('should handle a batch request with transaction = true', async done => { await reconfigureServer(); const myObject = new Parse.Object('MyObject'); // This is important because transaction only works on pre-existing collections diff --git a/spec/helper.js b/spec/helper.js index 2613b50947..778550edf9 100644 --- a/spec/helper.js +++ b/spec/helper.js @@ -4,6 +4,11 @@ const semver = require('semver'); // Sets up a Parse API server for testing. jasmine.DEFAULT_TIMEOUT_INTERVAL = process.env.PARSE_SERVER_TEST_TIMEOUT || 5000; +if (process.env.PARSE_SERVER_LOG_LEVEL === 'debug') { + const { SpecReporter } = require('jasmine-spec-reporter'); + jasmine.getEnv().addReporter(new SpecReporter()); +} + global.on_db = (db, callback, elseCallback) => { if (process.env.PARSE_SERVER_TEST_DB == db) { return callback(); @@ -121,8 +126,10 @@ const openConnections = {}; // Set up a default API server for testing with default configuration. let server; +let didChangeConfiguration = false; + // Allows testing specific configurations of Parse Server -const reconfigureServer = changedConfiguration => { +const reconfigureServer = (changedConfiguration = {}) => { return new Promise((resolve, reject) => { if (server) { return server.close(() => { @@ -132,6 +139,7 @@ const reconfigureServer = changedConfiguration => { } try { let parseServer = undefined; + didChangeConfiguration = Object.keys(changedConfiguration).length !== 0; const newConfiguration = Object.assign({}, defaultConfiguration, changedConfiguration, { serverStartComplete: error => { if (error) { @@ -182,14 +190,21 @@ beforeAll(async () => { Parse.serverURL = 'http://localhost:' + port + '/1'; }); +let count = 0; afterEach(function (done) { - const afterLogOut = () => { + const afterLogOut = async () => { if (Object.keys(openConnections).length > 0) { fail('There were open connections to the server left after the test finished'); } - TestUtils.destroyAllDataPermanently(true) - .then(() => databaseAdapter.performInitialization({ VolatileClassesSchemas })) - .then(done, done); + await TestUtils.destroyAllDataPermanently(true); + if (didChangeConfiguration) { + count += 1; + console.log(count); + await reconfigureServer(); + } else { + await databaseAdapter.performInitialization({ VolatileClassesSchemas }); + } + done(); }; Parse.Cloud._removeAllHooks(); databaseAdapter From 29439cae9ed94a4107ca164590a6464b5e1f8aae Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Fri, 12 Mar 2021 17:17:30 -0600 Subject: [PATCH 17/19] fix postgres tests --- spec/ParseServer.spec.js | 11 +- spec/PostgresInitOptions.spec.js | 11 +- spec/PostgresStorageAdapter.spec.js | 131 +++++++++--------- spec/RestQuery.spec.js | 3 + .../Storage/Postgres/PostgresClient.js | 7 +- 5 files changed, 86 insertions(+), 77 deletions(-) diff --git a/spec/ParseServer.spec.js b/spec/ParseServer.spec.js index f0137c55ad..904e0b164e 100644 --- a/spec/ParseServer.spec.js +++ b/spec/ParseServer.spec.js @@ -31,11 +31,11 @@ describe('Server Url Checks', () => { it('validate good server url', done => { Parse.serverURL = 'http://localhost:13376'; - ParseServer.verifyServerUrl(function (result) { + ParseServer.verifyServerUrl(async result => { if (!result) { done.fail('Did not pass valid url'); } - Parse.serverURL = 'http://localhost:8378/1'; + await reconfigureServer(); done(); }); }); @@ -43,11 +43,11 @@ describe('Server Url Checks', () => { it('mark bad server url', done => { spyOn(console, 'warn').and.callFake(() => {}); Parse.serverURL = 'notavalidurl'; - ParseServer.verifyServerUrl(function (result) { + ParseServer.verifyServerUrl(async result => { if (result) { done.fail('Did not mark invalid url'); } - Parse.serverURL = 'http://localhost:8378/1'; + await reconfigureServer(); done(); }); }); @@ -105,10 +105,11 @@ describe('Server Url Checks', () => { parseServerProcess.stderr.on('data', data => { stderr = data.toString(); }); - parseServerProcess.on('close', code => { + parseServerProcess.on('close', async code => { expect(code).toEqual(1); expect(stdout).toBeUndefined(); expect(stderr).toContain('MongoServerSelectionError'); + await reconfigureServer(); done(); }); }); diff --git a/spec/PostgresInitOptions.spec.js b/spec/PostgresInitOptions.spec.js index 2e9b0306f1..73f040701f 100644 --- a/spec/PostgresInitOptions.spec.js +++ b/spec/PostgresInitOptions.spec.js @@ -52,6 +52,7 @@ describe_only_db('postgres')('Postgres database init options', () => { afterAll(done => { if (server) { + Parse.serverURL = 'http://localhost:8378/1'; server.close(done); } }); @@ -73,7 +74,10 @@ describe_only_db('postgres')('Postgres database init options', () => { }); return score.save(); }) - .then(done, done.fail); + .then(async () => { + await reconfigureServer(); + done(); + }, done.fail); }); it('should fail to create server if schema databaseOptions does not exist', done => { @@ -83,6 +87,9 @@ describe_only_db('postgres')('Postgres database init options', () => { databaseOptions: databaseOptions2, }); - createParseServer({ databaseAdapter: adapter }).then(done.fail, () => done()); + createParseServer({ databaseAdapter: adapter }).then(done.fail, async () => { + await reconfigureServer(); + done(); + }); }); }); diff --git a/spec/PostgresStorageAdapter.spec.js b/spec/PostgresStorageAdapter.spec.js index f6e084fd47..8c372362b5 100644 --- a/spec/PostgresStorageAdapter.spec.js +++ b/spec/PostgresStorageAdapter.spec.js @@ -154,6 +154,10 @@ describe_only_db('postgres')('PostgresStorageAdapter', () => { objectId: { type: 'String' }, username: { type: 'String' }, email: { type: 'String' }, + emailVerified: { type: 'Boolean' }, + createdAt: { type: 'Date' }, + updatedAt: { type: 'Date' }, + authData: { type: 'Object' }, }, }; const client = adapter._client; @@ -173,77 +177,66 @@ describe_only_db('postgres')('PostgresStorageAdapter', () => { const caseInsensitiveData = 'bugs'; const originalQuery = 'SELECT * FROM $1:name WHERE lower($2:name)=lower($3)'; const analyzedExplainQuery = adapter.createExplainableQuery(originalQuery, true); - await client - .one(analyzedExplainQuery, [tableName, 'objectId', caseInsensitiveData]) - .then(explained => { - const preIndexPlan = explained; - - preIndexPlan['QUERY PLAN'].forEach(element => { - //Make sure search returned with only 1 result - expect(element.Plan['Actual Rows']).toBe(1); - expect(element.Plan['Node Type']).toBe('Seq Scan'); - }); - const indexName = 'test_case_insensitive_column'; - - adapter.ensureIndex(tableName, schema, ['objectId'], indexName, true).then(() => { - client - .one(analyzedExplainQuery, [tableName, 'objectId', caseInsensitiveData]) - .then(explained => { - const postIndexPlan = explained; - - postIndexPlan['QUERY PLAN'].forEach(element => { - //Make sure search returned with only 1 result - expect(element.Plan['Actual Rows']).toBe(1); - //Should not be a sequential scan - expect(element.Plan['Node Type']).not.toContain('Seq Scan'); - - //Should be using the index created for this - element.Plan.Plans.forEach(innerElement => { - expect(innerElement['Index Name']).toBe(indexName); - }); - }); - - //These are the same query so should be the same size - for (let i = 0; i < preIndexPlan['QUERY PLAN'].length; i++) { - //Sequential should take more time to execute than indexed - expect(preIndexPlan['QUERY PLAN'][i]['Execution Time']).toBeGreaterThan( - postIndexPlan['QUERY PLAN'][i]['Execution Time'] - ); - } - - //Test explaining without analyzing - const basicExplainQuery = adapter.createExplainableQuery(originalQuery); - client - .one(basicExplainQuery, [tableName, 'objectId', caseInsensitiveData]) - .then(explained => { - explained['QUERY PLAN'].forEach(element => { - //Check that basic query plans isn't a sequential scan - expect(element.Plan['Node Type']).not.toContain('Seq Scan'); - - //Basic query plans shouldn't have an execution time - expect(element['Execution Time']).toBeUndefined(); - }); - }); - }); - }); - }) - .catch(error => { - // Query on non existing table, don't crash - if (error.code !== '42P01') { - throw error; - } - return []; + const preIndexPlan = await client.one(analyzedExplainQuery, [ + tableName, + 'objectId', + caseInsensitiveData, + ]); + preIndexPlan['QUERY PLAN'].forEach(element => { + //Make sure search returned with only 1 result + expect(element.Plan['Actual Rows']).toBe(1); + expect(element.Plan['Node Type']).toBe('Seq Scan'); + }); + const indexName = 'test_case_insensitive_column'; + await adapter.ensureIndex(tableName, schema, ['objectId'], indexName, true); + + const postIndexPlan = await client.one(analyzedExplainQuery, [ + tableName, + 'objectId', + caseInsensitiveData, + ]); + postIndexPlan['QUERY PLAN'].forEach(element => { + //Make sure search returned with only 1 result + expect(element.Plan['Actual Rows']).toBe(1); + //Should not be a sequential scan + expect(element.Plan['Node Type']).not.toContain('Seq Scan'); + + //Should be using the index created for this + element.Plan.Plans.forEach(innerElement => { + expect(innerElement['Index Name']).toBe(indexName); }); + }); + + //These are the same query so should be the same size + for (let i = 0; i < preIndexPlan['QUERY PLAN'].length; i++) { + //Sequential should take more time to execute than indexed + expect(preIndexPlan['QUERY PLAN'][i]['Execution Time']).toBeGreaterThan( + postIndexPlan['QUERY PLAN'][i]['Execution Time'] + ); + } + //Test explaining without analyzing + const basicExplainQuery = adapter.createExplainableQuery(originalQuery); + const explained = await client.one(basicExplainQuery, [ + tableName, + 'objectId', + caseInsensitiveData, + ]); + explained['QUERY PLAN'].forEach(element => { + //Check that basic query plans isn't a sequential scan + expect(element.Plan['Node Type']).not.toContain('Seq Scan'); + + //Basic query plans shouldn't have an execution time + expect(element['Execution Time']).toBeUndefined(); + }); + await dropTable(client, tableName); }); it('should use index for caseInsensitive query', async () => { const tableName = '_User'; - await adapter.deleteClass(tableName); - await reconfigureServer(); const user = new Parse.User(); - user.set('username', 'Bugs'); - user.set('password', 'Bunny'); + user.set('username', 'Elmer'); + user.set('password', 'Fudd'); await user.signUp(); const database = Config.get(Parse.applicationId).database; @@ -253,7 +246,7 @@ describe_only_db('postgres')('PostgresStorageAdapter', () => { 'INSERT INTO $1:name ($2:name, $3:name) SELECT gen_random_uuid(), gen_random_uuid() FROM generate_series(1,5000)', [tableName, 'objectId', 'username'] ); - const caseInsensitiveData = 'bugs'; + const caseInsensitiveData = 'elmer'; const fieldToSearch = 'username'; //Check using find method for Parse const preIndexPlan = await database.find( @@ -265,7 +258,7 @@ describe_only_db('postgres')('PostgresStorageAdapter', () => { preIndexPlan.forEach(element => { element['QUERY PLAN'].forEach(innerElement => { //Check that basic query plans isn't a sequential scan, be careful as find uses "any" to query - expect(innerElement.Plan['Node Type']).toBe('Bitmap Heap Scan'); + expect(innerElement.Plan['Node Type']).toBe('Seq Scan'); //Basic query plans shouldn't have an execution time expect(innerElement['Execution Time']).toBeUndefined(); }); @@ -296,8 +289,8 @@ describe_only_db('postgres')('PostgresStorageAdapter', () => { it('should use index for caseInsensitive query using default indexname', async () => { const tableName = '_User'; const user = new Parse.User(); - user.set('username', 'Bugs'); - user.set('password', 'Bunny'); + user.set('username', 'Tweety'); + user.set('password', 'Bird'); await user.signUp(); const database = Config.get(Parse.applicationId).database; const fieldToSearch = 'username'; @@ -312,7 +305,7 @@ describe_only_db('postgres')('PostgresStorageAdapter', () => { [tableName, 'objectId', 'username'] ); - const caseInsensitiveData = 'buGs'; + const caseInsensitiveData = 'tweeTy'; //Check using find method for Parse const indexPlan = await database.find( tableName, diff --git a/spec/RestQuery.spec.js b/spec/RestQuery.spec.js index 629cc7e5e1..c8e3adb49d 100644 --- a/spec/RestQuery.spec.js +++ b/spec/RestQuery.spec.js @@ -315,6 +315,9 @@ describe('rest query', () => { }); describe('RestQuery.each', () => { + beforeEach(() => { + config = Config.get('test'); + }); it('should run each', async () => { const objects = []; while (objects.length != 10) { diff --git a/src/Adapters/Storage/Postgres/PostgresClient.js b/src/Adapters/Storage/Postgres/PostgresClient.js index bbae91f5e4..062dc207d1 100644 --- a/src/Adapters/Storage/Postgres/PostgresClient.js +++ b/src/Adapters/Storage/Postgres/PostgresClient.js @@ -20,7 +20,12 @@ export function createClient(uri, databaseOptions) { if (process.env.PARSE_SERVER_LOG_LEVEL === 'debug') { const monitor = require('pg-monitor'); - monitor.attach(initOptions); + try { + monitor.attach(initOptions); + } catch (e) { + monitor.detach(); + monitor.attach(initOptions); + } } if (dbOptions.pgOptions) { From 7f100f3abe9af6f9a4925a208a53a7d117055517 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Fri, 12 Mar 2021 18:07:08 -0600 Subject: [PATCH 18/19] remove console.log --- spec/helper.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/spec/helper.js b/spec/helper.js index 778550edf9..f38b50ea7e 100644 --- a/spec/helper.js +++ b/spec/helper.js @@ -190,7 +190,6 @@ beforeAll(async () => { Parse.serverURL = 'http://localhost:' + port + '/1'; }); -let count = 0; afterEach(function (done) { const afterLogOut = async () => { if (Object.keys(openConnections).length > 0) { @@ -198,8 +197,6 @@ afterEach(function (done) { } await TestUtils.destroyAllDataPermanently(true); if (didChangeConfiguration) { - count += 1; - console.log(count); await reconfigureServer(); } else { await databaseAdapter.performInitialization({ VolatileClassesSchemas }); From f6db41cdd1615b59b43874fd99be212c4e68631f Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Fri, 12 Mar 2021 23:11:24 -0600 Subject: [PATCH 19/19] LiveQuery spec remove duplicates and listeners --- spec/CloudCodeLogger.spec.js | 1 + spec/ParseLiveQuery.spec.js | 184 +--------------------------- spec/ParseQuery.Aggregate.spec.js | 25 ++-- spec/helper.js | 5 +- spec/schemas.spec.js | 2 +- spec/support/CurrentSpecReporter.js | 15 +++ 6 files changed, 34 insertions(+), 198 deletions(-) create mode 100755 spec/support/CurrentSpecReporter.js diff --git a/spec/CloudCodeLogger.spec.js b/spec/CloudCodeLogger.spec.js index 7888819334..78626e4efb 100644 --- a/spec/CloudCodeLogger.spec.js +++ b/spec/CloudCodeLogger.spec.js @@ -14,6 +14,7 @@ describe('Cloud Code Logger', () => { return reconfigureServer({ // useful to flip to false for fine tuning :). silent: true, + logLevel: undefined, }) .then(() => { return Parse.User.signUp('tester', 'abc') diff --git a/spec/ParseLiveQuery.spec.js b/spec/ParseLiveQuery.spec.js index fa259785ee..43e91e03bb 100644 --- a/spec/ParseLiveQuery.spec.js +++ b/spec/ParseLiveQuery.spec.js @@ -15,12 +15,14 @@ describe('ParseLiveQuery', function () { verbose: false, silent: true, }); + Parse.CoreManager.getLiveQueryController().setDefaultLiveQueryClient(null); const requestedUser = new Parse.User(); requestedUser.setUsername('username'); requestedUser.setPassword('password'); Parse.Cloud.onLiveQueryEvent(req => { const { event, sessionToken } = req; if (event === 'ws_disconnect') { + Parse.Cloud._removeAllHooks(); expect(sessionToken).toBeDefined(); expect(sessionToken).toBe(requestedUser.getSessionToken()); done(); @@ -356,185 +358,6 @@ describe('ParseLiveQuery', function () { await object.save(); }); - it('expect afterEvent create', async done => { - await reconfigureServer({ - liveQuery: { - classNames: ['TestObject'], - }, - startLiveQueryServer: true, - verbose: false, - silent: true, - }); - Parse.Cloud.afterLiveQueryEvent('TestObject', req => { - expect(req.event).toBe('create'); - expect(req.user).toBeUndefined(); - expect(req.object.get('foo')).toBe('bar'); - }); - - const query = new Parse.Query(TestObject); - const subscription = await query.subscribe(); - subscription.on('create', object => { - expect(object.get('foo')).toBe('bar'); - done(); - }); - - const object = new TestObject(); - object.set('foo', 'bar'); - await object.save(); - }); - - it('expect afterEvent payload', async done => { - await reconfigureServer({ - liveQuery: { - classNames: ['TestObject'], - }, - startLiveQueryServer: true, - verbose: false, - silent: true, - }); - const object = new TestObject(); - await object.save(); - - Parse.Cloud.afterLiveQueryEvent('TestObject', req => { - expect(req.event).toBe('update'); - expect(req.user).toBeUndefined(); - expect(req.object.get('foo')).toBe('bar'); - expect(req.original.get('foo')).toBeUndefined(); - done(); - }); - - const query = new Parse.Query(TestObject); - query.equalTo('objectId', object.id); - await query.subscribe(); - object.set({ foo: 'bar' }); - await object.save(); - }); - - it('expect afterEvent enter', async done => { - await reconfigureServer({ - liveQuery: { - classNames: ['TestObject'], - }, - startLiveQueryServer: true, - verbose: false, - silent: true, - }); - Parse.Cloud.afterLiveQueryEvent('TestObject', req => { - expect(req.event).toBe('enter'); - expect(req.user).toBeUndefined(); - expect(req.object.get('foo')).toBe('bar'); - expect(req.original.get('foo')).toBeUndefined(); - }); - - const object = new TestObject(); - await object.save(); - - const query = new Parse.Query(TestObject); - query.equalTo('foo', 'bar'); - const subscription = await query.subscribe(); - subscription.on('enter', object => { - expect(object.get('foo')).toBe('bar'); - done(); - }); - - object.set('foo', 'bar'); - await object.save(); - }); - - it('expect afterEvent leave', async done => { - await reconfigureServer({ - liveQuery: { - classNames: ['TestObject'], - }, - startLiveQueryServer: true, - verbose: false, - silent: true, - }); - Parse.Cloud.afterLiveQueryEvent('TestObject', req => { - expect(req.event).toBe('leave'); - expect(req.user).toBeUndefined(); - expect(req.object.get('foo')).toBeUndefined(); - expect(req.original.get('foo')).toBe('bar'); - }); - - const object = new TestObject(); - object.set('foo', 'bar'); - await object.save(); - - const query = new Parse.Query(TestObject); - query.equalTo('foo', 'bar'); - const subscription = await query.subscribe(); - subscription.on('leave', object => { - expect(object.get('foo')).toBeUndefined(); - done(); - }); - - object.unset('foo'); - await object.save(); - }); - - it('expect afterEvent delete', async done => { - await reconfigureServer({ - liveQuery: { - classNames: ['TestObject'], - }, - startLiveQueryServer: true, - verbose: false, - silent: true, - }); - Parse.Cloud.afterLiveQueryEvent('TestObject', req => { - expect(req.event).toBe('delete'); - expect(req.user).toBeUndefined(); - req.object.set('foo', 'bar'); - }); - - const object = new TestObject(); - await object.save(); - - const query = new Parse.Query(TestObject); - query.equalTo('objectId', object.id); - - const subscription = await query.subscribe(); - subscription.on('delete', object => { - expect(object.get('foo')).toBe('bar'); - done(); - }); - - await object.destroy(); - }); - - it('can handle afterEvent modification', async done => { - await reconfigureServer({ - liveQuery: { - classNames: ['TestObject'], - }, - startLiveQueryServer: true, - verbose: false, - silent: true, - }); - const object = new TestObject(); - await object.save(); - - Parse.Cloud.afterLiveQueryEvent('TestObject', req => { - const current = req.object; - current.set('foo', 'yolo'); - - const original = req.original; - original.set('yolo', 'foo'); - }); - - const query = new Parse.Query(TestObject); - query.equalTo('objectId', object.id); - const subscription = await query.subscribe(); - subscription.on('update', (object, original) => { - expect(object.get('foo')).toBe('yolo'); - expect(original.get('yolo')).toBe('foo'); - done(); - }); - object.set({ foo: 'bar' }); - await object.save(); - }); - it('can handle async afterEvent modification', async done => { await reconfigureServer({ liveQuery: { @@ -622,6 +445,7 @@ describe('ParseLiveQuery', function () { Parse.Cloud.beforeConnect(() => {}, validatorFail); let complete = false; Parse.LiveQuery.on('error', error => { + Parse.LiveQuery.removeAllListeners('error'); if (complete) { return; } @@ -695,6 +519,7 @@ describe('ParseLiveQuery', function () { throw new Error('You shall not pass!'); }); Parse.LiveQuery.on('error', error => { + Parse.LiveQuery.removeAllListeners('error'); expect(error).toBe('You shall not pass!'); done(); }); @@ -725,6 +550,7 @@ describe('ParseLiveQuery', function () { query.equalTo('objectId', object.id); const subscription = await query.subscribe(); subscription.on('error', error => { + Parse.LiveQuery.removeAllListeners('error'); expect(error).toBe('You shall not subscribe!'); done(); }); diff --git a/spec/ParseQuery.Aggregate.spec.js b/spec/ParseQuery.Aggregate.spec.js index 177aa9a0f2..fc20edc140 100644 --- a/spec/ParseQuery.Aggregate.spec.js +++ b/spec/ParseQuery.Aggregate.spec.js @@ -1404,20 +1404,16 @@ describe('Parse.Query Aggregate testing', () => { expect(results.length).toEqual(2); expect(results[0].value).toEqual(2); expect(results[1].value).toEqual(3); + await database.adapter.deleteAllClasses(false); }); it_only_db('mongo')('aggregate geoNear with near GeoJSON point', async () => { // Create geo index which is required for `geoNear` query const database = Config.get(Parse.applicationId).database; const schema = await new Parse.Schema('GeoObject').save(); - await database.adapter.ensureIndex( - 'GeoObject', - schema, - ['location'], - undefined, - false, - '2dsphere' - ); + await database.adapter.ensureIndex('GeoObject', schema, ['location'], undefined, false, { + indexType: '2dsphere', + }); // Create objects const GeoObject = Parse.Object.extend('GeoObject'); const obj1 = new GeoObject({ @@ -1454,20 +1450,16 @@ describe('Parse.Query Aggregate testing', () => { const results = await query.aggregate(pipeline); // Check results expect(results.length).toEqual(3); + await database.adapter.deleteAllClasses(false); }); it_only_db('mongo')('aggregate geoNear with near legacy coordinate pair', async () => { // Create geo index which is required for `geoNear` query const database = Config.get(Parse.applicationId).database; const schema = await new Parse.Schema('GeoObject').save(); - await database.adapter.ensureIndex( - 'GeoObject', - schema, - ['location'], - undefined, - false, - '2dsphere' - ); + await database.adapter.ensureIndex('GeoObject', schema, ['location'], undefined, false, { + indexType: '2dsphere', + }); // Create objects const GeoObject = Parse.Object.extend('GeoObject'); const obj1 = new GeoObject({ @@ -1501,5 +1493,6 @@ describe('Parse.Query Aggregate testing', () => { const results = await query.aggregate(pipeline); // Check results expect(results.length).toEqual(3); + await database.adapter.deleteAllClasses(false); }); }); diff --git a/spec/helper.js b/spec/helper.js index f38b50ea7e..68254f518f 100644 --- a/spec/helper.js +++ b/spec/helper.js @@ -1,9 +1,10 @@ 'use strict'; const semver = require('semver'); +const CurrentSpecReporter = require('./support/CurrentSpecReporter.js'); // Sets up a Parse API server for testing. -jasmine.DEFAULT_TIMEOUT_INTERVAL = process.env.PARSE_SERVER_TEST_TIMEOUT || 5000; - +jasmine.DEFAULT_TIMEOUT_INTERVAL = process.env.PARSE_SERVER_TEST_TIMEOUT || 10000; +jasmine.getEnv().addReporter(new CurrentSpecReporter()); if (process.env.PARSE_SERVER_LOG_LEVEL === 'debug') { const { SpecReporter } = require('jasmine-spec-reporter'); jasmine.getEnv().addReporter(new SpecReporter()); diff --git a/spec/schemas.spec.js b/spec/schemas.spec.js index 82b3c7d0d2..cba50f387f 100644 --- a/spec/schemas.spec.js +++ b/spec/schemas.spec.js @@ -141,8 +141,8 @@ const masterKeyHeaders = { describe('schemas', () => { beforeEach(async () => { - config = Config.get('test'); await reconfigureServer(); + config = Config.get('test'); }); afterEach(async () => { diff --git a/spec/support/CurrentSpecReporter.js b/spec/support/CurrentSpecReporter.js new file mode 100755 index 0000000000..3158e21eae --- /dev/null +++ b/spec/support/CurrentSpecReporter.js @@ -0,0 +1,15 @@ +// Sets a global variable to the current test spec +// ex: global.currentSpec.description + +global.currentSpec = null; + +class CurrentSpecReporter { + specStarted(spec) { + global.currentSpec = spec; + } + specDone() { + global.currentSpec = null; + } +} + +module.exports = CurrentSpecReporter;